docs(6206): routes/plugin comments drop record and milestone citations

The pre-tool rules docstring says why action rules need their own surface
instead of narrating that they once lived in the preload; the write-path
checkpoint note states the reason the hook never blocks rather than whose
decision it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 20:36:52 -04:00
co-authored by Claude Opus 5.5
parent 87e8f8f777
commit edf725687a
+33 -35
View File
@@ -72,14 +72,14 @@ async def session_context():
unbound repo yields a "bind this repo" hint instead. This is how the unbound repo yields a "bind this repo" hint instead. This is how the
active project is determined — the plugin never pins a project id. active project is determined — the plugin never pins a project id.
project_id (optional int) — the project named directly, and the only 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 send it when a `.scribe` marker file names a project. Takes
precedence over `repo`. Access-checked like any other read — an id precedence over `repo`. Access-checked like any other read — an id
this account cannot read loads no project rather than failing. this account cannot read loads no project rather than failing.
source (optional str) — the host's SessionStart source (startup, source (optional str) — the host's SessionStart source (startup,
resume, compact, clear, fork); decides what the claim section says. resume, compact, clear, fork); decides what the claim section says.
session_id (optional str) — the session's id, so a claim bound to it 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() project_id, _repo, unbound_repo = await _project_scope()
result = await plugin_ctx_svc.build_session_context( 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). project_id (opt) — explicit project scope override (ad-hoc/testing).
exclude_ids (opt) — comma-separated note ids already injected this exclude_ids (opt) — comma-separated note ids already injected this
session; skipped so each note injects at most once. 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, notes AND rule arms append it to a short prompt,
so a follow-up like "yes do that" still names so a follow-up like "yes do that" still names
what it is about — and a rule or lesson arrives 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 (opt) session. SHARED with /prior-art and /tool-rules on
purpose: one session keeps ONE rule ledger, so a purpose: one session keeps ONE rule ledger, so a
rule named by any arm is not re-announced by 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 held_rule_ids — comma-separated rule ids the session actually
(opt) OPENED, observed from the `get_rule` call itself (opt) OPENED, observed from the `get_rule` call itself
rather than claimed. A rule on exclude_rule_ids rather than claimed. A rule on exclude_rule_ids
was NAMED; a rule here was READ, and the two say was NAMED; a rule here was READ, and the two say
different things about what the reader holds — 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. ledger directory and the same clear-on-compact.
shapes_seen (opt) — comma-separated reply-shape keys this session was 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 goes out in full when `core` is absent, as its
one-line reminder when present; the hook's one-line reminder when present; the hook's
ledger is swept on compaction, which is how the ledger is swept on compaction, which is how the
full core comes back after one. 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 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 and whatever is mounted on `reply.report` lead the payload. Fresh
shape keys come back as `shape_keys` for the ledger. 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, 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 # 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 "". # prompt — the situation — and fails open to "".
proposal = await lesson_rules_svc.co_surfaced( proposal = await lesson_rules_svc.co_surfaced(
g.user.id, result.get("lesson_ids") or [], rules.get("shown_rule_ids") or [], g.user.id, result.get("lesson_ids") or [], rules.get("shown_rule_ids") or [],
@@ -191,13 +191,13 @@ async def autoinject_retrieve():
@login_required @login_required
@memoized_query_embeddings @memoized_query_embeddings
async def pre_tool_rules(): 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 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 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 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 stand up a stack, don't run the suite locally) have no other retrieval
all before this, which is why they all had to live in the resident preload. surface: no code write ever happens for them to ride on.
Titles + trigger only, never the statement: the hint says a rule may apply 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 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 held_rule_ids (opt) — rule ids the session actually OPENED, as
against merely named. Named and read are against merely named. Named and read are
different claims about the reader's different claims about the reader's
context, so they get different lines context, so they get different lines.
(#4100).
root, cwd (opt) — the repo's absolute root and the command's root, cwd (opt) — the repo's absolute root and the command's
working directory. They turn the paths the working directory. They turn the paths the
command names into repo-relative ones, for 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 seen_ruling_systems (opt) — comma-separated System ids whose rulings
were already shown this session, by either were already shown this session, by either
arm; those get a one-line reference. SHARED arm; those get a one-line reference. SHARED
with /prior-art, like exclude_rule_ids. 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 plus `ruling_system_ids` — the Systems whose rulings were shown in full on
this call — when there were any. this call — when there were any.
@@ -268,7 +267,7 @@ async def pre_tool_rules():
@plugin_bp.get("/moment-tools") @plugin_bp.get("/moment-tools")
@login_required @login_required
async def moment_tools(): 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 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. 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") @plugin_bp.post("/moment")
@login_required @login_required
async def moment(): 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 Body: the PreToolUse event as Claude Code delivers it — `tool_name` and
`tool_input` are read, nothing else. Sent whole because which input field `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 `moments` (the names reached). A lookup, not a ranked search: no
retrieval_logs row; each fresh rule is recorded surfaced with its moment. 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 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. 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") @plugin_bp.post("/rule-opened")
@login_required @login_required
async def rule_opened(): 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": [<PreToolUse event>, ...]}` — Body: `{"rule_id", "session_id", "acts": [<PreToolUse event>, ...]}` —
the calls the plugin saw in the window before the `get_rule`. Each is the calls the plugin saw in the window before the `get_rule`. Each is
@@ -360,16 +359,15 @@ async def rule_opened():
@login_required @login_required
@memoized_query_embeddings @memoized_query_embeddings
async def reply_rules(): 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 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 rules MOUNTED on the reply moments (`reply.report`, and `reply.ask` when
the reply asks something), and the reply text against every rule's the reply asks something), and the reply text against every rule's
trigger — the backstop for whatever the earlier arms missed. trigger — the backstop for whatever the earlier arms missed.
ONE END-OF-TURN REQUEST (milestone 500 step 4): a reply that closed tasks ONE END-OF-TURN REQUEST: a reply that closed tasks is also checked here
is also checked here for the completion sections (services/report_check), for the completion sections (services/report_check). Both holds fold
which used to be a Stop hook and an endpoint of its own. Both holds fold
into one `reason`; `report_check` says what the section check recorded, so into one `reason`; `report_check` says what the section check recorded, so
the hook knows the rewrite is one to report. 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 exclude_ids (opt) — comma-separated REUSE-class ids already surfaced
this session. this session.
exclude_sync_ids (opt) — comma-separated SYNC-class ids (snippets 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 surfaced. A separate channel on purpose: a reuse
hint shown early must not suppress the record-sync hint shown early must not suppress the record-sync
nudge when the recorded file is edited later. nudge when the recorded file is edited later.
@@ -458,31 +456,31 @@ async def write_path_prior_art():
every subsequent write. every subsequent write.
held_rule_ids (opt) — RULE ids the session actually OPENED, as held_rule_ids (opt) — RULE ids the session actually OPENED, as
against merely named; drives the third against merely named; drives the third
reference wording (#4100). reference wording.
exclude_derive (opt) — comma-separated derive keys (a derive group id exclude_derive (opt) — comma-separated derive keys (a derive group id
or `canon:<snippet_id>`) already named this or `canon:<snippet_id>`) already named this
session by the ledger arm (#2900); its own session by the ledger arm; its own
channel, like the two above. channel, like the two above.
seen_ruling_systems (opt) — System ids whose rulings were already shown 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` /tool-rules. The response's `ruling_system_ids`
are the ones shown in full on this call. are the ones shown in full on this call.
(Returns a `checkpoint` block on the same contract as /tool-rules — (Returns a `checkpoint` block on the same contract as /tool-rules —
see that endpoint. The write-path HOOK deliberately does not act on see that endpoint. The write-path HOOK deliberately does not act on
it: scribe_prior_art.sh carries a tested property that it never it: scribe_prior_art.sh carries a tested property that it never
returns a permissionDecision, on the operator's decision that a recall returns a permissionDecision, because a recall aid must not stand in
aid may not stand in the way of a write. The block is computed and the way of a write. The block is computed and
returned so that decision can be revisited with evidence rather than 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.) 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 shapes (opt) — comma-separated `kind:name` definitions the hook
found in (or enclosing) the payload, kind being found in (or enclosing) the payload, kind being
css|sym. The shape ledger's write-path feed css|sym. The shape ledger's write-path feed:
(#2791): when the session recently PULLED a when the session recently PULLED a
snippet this payload references or resembles, snippet this payload references or resembles,
these carry it as a proposal — evidence for the these carry it as a proposal — evidence for the
agent's own end-of-turn judgment, never a 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 caller allowed to write — a read-scoped key still
gets the hint, and never changes the ledger on a gets the hint, and never changes the ledger on a
GET. GET.
@@ -537,7 +535,7 @@ def _parse_shapes(raw: str) -> list[tuple[str, str]]:
@plugin_bp.get("/shape-check") @plugin_bp.get("/shape-check")
@login_required @login_required
async def shape_check(): 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? nobody has judged?
The client's write hooks keep a ledger of the definitions each turn wrote; 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") @plugin_bp.get("/claim-session")
@login_required @login_required
async def claim_session(): 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`. 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 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") @plugin_bp.get("/release-session")
@login_required @login_required
async def release_session(): 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 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 not fire on a crash, so the claim's lease — not this call — is what makes a