diff --git a/src/scribe/routes/plugin.py b/src/scribe/routes/plugin.py index 6ba1b68f..21e83cb6 100644 --- a/src/scribe/routes/plugin.py +++ b/src/scribe/routes/plugin.py @@ -72,14 +72,14 @@ async def session_context(): unbound repo yields a "bind this repo" hint instead. This is how the active project is determined — the plugin never pins a project id. project_id (optional int) — the project named directly, and the only - key a caller outside a git repo has (#4085): the plugin's hooks + key a caller outside a git repo has: the plugin's hooks send it when a `.scribe` marker file names a project. Takes precedence over `repo`. Access-checked like any other read — an id this account cannot read loads no project rather than failing. source (optional str) — the host's SessionStart source (startup, resume, compact, clear, fork); decides what the claim section says. session_id (optional str) — the session's id, so a claim bound to it - reads as this session's own (milestone 381). + reads as this session's own. """ project_id, _repo, unbound_repo = await _project_scope() result = await plugin_ctx_svc.build_session_context( @@ -109,7 +109,7 @@ async def autoinject_retrieve(): project_id (opt) — explicit project scope override (ad-hoc/testing). exclude_ids (opt) — comma-separated note ids already injected this session; skipped so each note injects at most once. - ctx (opt) — the tail of the last assistant reply (#4364). The + ctx (opt) — the tail of the last assistant reply. The notes AND rule arms append it to a short prompt, so a follow-up like "yes do that" still names what it is about — and a rule or lesson arrives @@ -119,22 +119,22 @@ async def autoinject_retrieve(): (opt) session. SHARED with /prior-art and /tool-rules on purpose: one session keeps ONE rule ledger, so a rule named by any arm is not re-announced by - another. Ages out (#3751), so salience decays. + another. Ages out, so salience decays. held_rule_ids — comma-separated rule ids the session actually (opt) OPENED, observed from the `get_rule` call itself rather than claimed. A rule on exclude_rule_ids was NAMED; a rule here was READ, and the two say different things about what the reader holds — - so they get different lines (#4100). Shares the + so they get different lines. Shares the ledger directory and the same clear-on-compact. shapes_seen (opt) — comma-separated reply-shape keys this session was - already shown in full (milestone 500). The core + already shown in full. The core goes out in full when `core` is absent, as its one-line reminder when present; the hook's ledger is swept on compaction, which is how the full core comes back after one. - THE TURN'S REPLY SHAPE COMES FIRST (milestone 500 step 3). Every turn ends + THE TURN'S REPLY SHAPE COMES FIRST. Every turn ends in a reply and this is the last point before it is written, so the core shape and whatever is mounted on `reply.report` lead the payload. Fresh shape keys come back as `shape_keys` for the ledger. @@ -165,7 +165,7 @@ async def autoinject_retrieve(): g.user.id, q, project_id=project_id, exclude_ids=exclude_ids, context=ctx, ) # The one place both arms' lines meet, so the one place a lesson and a - # rule arriving TOGETHER can be counted (#4637). Keyed on the operator's + # rule arriving TOGETHER can be counted. Keyed on the operator's # prompt — the situation — and fails open to "". proposal = await lesson_rules_svc.co_surfaced( g.user.id, result.get("lesson_ids") or [], rules.get("shown_rule_ids") or [], @@ -191,13 +191,13 @@ async def autoinject_retrieve(): @login_required @memoized_query_embeddings async def pre_tool_rules(): - """Standing rules for the plugin's PreToolUse hook on ACTIONS (#3476). + """Standing rules for the plugin's PreToolUse hook on ACTIONS. Answers "does a recorded rule speak to the command about to be run?" — the sibling of /prior-art, which can only answer that question about a code write. Rules about which tool to reach for (don't curl the forge, don't - stand up a stack, don't run the suite locally) had no retrieval surface at - all before this, which is why they all had to live in the resident preload. + stand up a stack, don't run the suite locally) have no other retrieval + surface: no code write ever happens for them to ride on. Titles + trigger only, never the statement: the hint says a rule may apply and hands over `get_rule(id)`. One rule at most (RULEHINT_LIMIT), and empty @@ -219,18 +219,17 @@ async def pre_tool_rules(): held_rule_ids (opt) — rule ids the session actually OPENED, as against merely named. Named and read are different claims about the reader's - context, so they get different lines - (#4100). + context, so they get different lines. root, cwd (opt) — the repo's absolute root and the command's working directory. They turn the paths the command names into repo-relative ones, for - the rulings arm (milestone 444). + the rulings arm. seen_ruling_systems (opt) — comma-separated System ids whose rulings were already shown this session, by either arm; those get a one-line reference. SHARED with /prior-art, like exclude_rule_ids. - Returns `context`, `rule_ids`, and `checkpoint` (#4214, milestone 419), + Returns `context`, `rule_ids`, and `checkpoint`, plus `ruling_system_ids` — the Systems whose rulings were shown in full on this call — when there were any. @@ -268,7 +267,7 @@ async def pre_tool_rules(): @plugin_bp.get("/moment-tools") @login_required async def moment_tools(): - """The tools whose calls can deliver a mounted rule (milestone 458 step 4). + """The tools whose calls can deliver a mounted rule. Read by the plugin's catch-all PreToolUse hook once per session window and kept on disk, so a call that cannot reach a mounted rule costs no request. @@ -282,7 +281,7 @@ async def moment_tools(): @plugin_bp.post("/moment") @login_required async def moment(): - """The rules mounted on the moments this tool call reaches (milestone 458). + """The rules mounted on the moments this tool call reaches. Body: the PreToolUse event as Claude Code delivers it — `tool_name` and `tool_input` are read, nothing else. Sent whole because which input field @@ -298,7 +297,7 @@ async def moment(): `moments` (the names reached). A lookup, not a ranked search: no retrieval_logs row; each fresh rule is recorded surfaced with its moment. - A moment that carries a default reply shape (milestone 500) brings it too, + A moment that carries a default reply shape brings it too, ahead of the rules: in full unless `shapes_seen` names it, then as its reminder. `shape_keys` are the ones sent in full, for the hook's ledger. """ @@ -330,7 +329,7 @@ async def moment(): @plugin_bp.post("/rule-opened") @login_required async def rule_opened(): - """A session opened a rule shortly after these acts (milestone 458 step 7). + """A session opened a rule shortly after these acts. Body: `{"rule_id", "session_id", "acts": [, ...]}` — the calls the plugin saw in the window before the `get_rule`. Each is @@ -360,16 +359,15 @@ async def rule_opened(): @login_required @memoized_query_embeddings async def reply_rules(): - """Whether the reply that ends a turn is held for one read (milestone 458). + """Whether the reply that ends a turn is held for one read. Called by the plugin's Stop hook with the finished reply. Two halves: the rules MOUNTED on the reply moments (`reply.report`, and `reply.ask` when the reply asks something), and the reply text against every rule's trigger — the backstop for whatever the earlier arms missed. - ONE END-OF-TURN REQUEST (milestone 500 step 4): a reply that closed tasks - is also checked here for the completion sections (services/report_check), - which used to be a Stop hook and an endpoint of its own. Both holds fold + ONE END-OF-TURN REQUEST: a reply that closed tasks is also checked here + for the completion sections (services/report_check). Both holds fold into one `reason`; `report_check` says what the section check recorded, so the hook knows the rewrite is one to report. @@ -447,7 +445,7 @@ async def write_path_prior_art(): exclude_ids (opt) — comma-separated REUSE-class ids already surfaced this session. exclude_sync_ids (opt) — comma-separated SYNC-class ids (snippets - recorded AT the edited file, #2708) already + recorded AT the edited file) already surfaced. A separate channel on purpose: a reuse hint shown early must not suppress the record-sync nudge when the recorded file is edited later. @@ -458,31 +456,31 @@ async def write_path_prior_art(): every subsequent write. held_rule_ids (opt) — RULE ids the session actually OPENED, as against merely named; drives the third - reference wording (#4100). + reference wording. exclude_derive (opt) — comma-separated derive keys (a derive group id or `canon:`) already named this - session by the ledger arm (#2900); its own + session by the ledger arm; its own channel, like the two above. seen_ruling_systems (opt) — System ids whose rulings were already shown - this session (milestone 444); shared with + this session; shared with /tool-rules. The response's `ruling_system_ids` are the ones shown in full on this call. (Returns a `checkpoint` block on the same contract as /tool-rules — see that endpoint. The write-path HOOK deliberately does not act on it: scribe_prior_art.sh carries a tested property that it never - returns a permissionDecision, on the operator's decision that a recall - aid may not stand in the way of a write. The block is computed and + returns a permissionDecision, because a recall aid must not stand in + the way of a write. The block is computed and returned so that decision can be revisited with evidence rather than re-argued, and so a change of mind is a hook edit and not a feature.) shapes (opt) — comma-separated `kind:name` definitions the hook found in (or enclosing) the payload, kind being - css|sym. The shape ledger's write-path feed - (#2791): when the session recently PULLED a + css|sym. The shape ledger's write-path feed: + when the session recently PULLED a snippet this payload references or resembles, these carry it as a proposal — evidence for the agent's own end-of-turn judgment, never a - verdict (milestone 439). Honoured only for a + verdict. Honoured only for a caller allowed to write — a read-scoped key still gets the hint, and never changes the ledger on a GET. @@ -537,7 +535,7 @@ def _parse_shapes(raw: str) -> list[tuple[str, str]]: @plugin_bp.get("/shape-check") @login_required async def shape_check(): - """The end-of-turn question (milestone 439): what did this turn write that + """The end-of-turn question: what did this turn write that nobody has judged? The client's write hooks keep a ledger of the definitions each turn wrote; @@ -602,7 +600,7 @@ async def shape_check(): @plugin_bp.get("/claim-session") @login_required async def claim_session(): - """Bind the harness's session id to the caller's live claim on a task (milestone 381). + """Bind the harness's session id to the caller's live claim on a task. Called by `scribe_claim_session.sh` after `update_task` / `add_task_log`. The server has already stamped the claim on that write; this only says @@ -631,7 +629,7 @@ async def claim_session(): @plugin_bp.get("/release-session") @login_required async def release_session(): - """Release the claims a session held, as it ends (milestone 381 step 4). + """Release the claims a session held, as it ends. Called by `scribe_session_end.sh`. Best-effort by design: SessionEnd does not fire on a crash, so the claim's lease — not this call — is what makes a