diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index c8493faf..1c8d3f57 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "scribe", "description": "Scribe for Claude Code: connects the scribe MCP server, adds the hooks that deliver live project state and relevant records at the right moment, ships the shared client-neutral Scribe skills (using-scribe, writing-plans, reporting-back, systematic-debugging, verification, brainstorming, reusing-code, shape-accounting), and syncs your saved Scribe Processes as skills (/scribe:sync).", - "version": "2026.10.05.2003", + "version": "2026.10.05.2039", "author": { "name": "Bryan Van Deusen" }, diff --git a/plugin/hooks/scribe_static_context.md b/plugin/hooks/scribe_static_context.md index 062af163..64550e14 100644 --- a/plugin/hooks/scribe_static_context.md +++ b/plugin/hooks/scribe_static_context.md @@ -16,8 +16,10 @@ What only Claude Code needs said: Scribe works alongside them. - **Lines injected beside your work are retrieval.** When the operator sends a message, and before a write or a command, Scribe may add rules, preferences, - notes and prior art that resemble what you are doing. Open the ones that - apply; using-scribe says what a quiet turn means. + notes and prior art that resemble what you are doing — and, when a tool call + or the reply ending a turn reaches a moment of work, the rules mounted on + it. Open the ones that apply; using-scribe says what a quiet turn means and + how to correct a moment that fired wrongly. - **Compact at clean seams.** Because work is recorded as you go, a compaction is safe once in-flight state is logged. After finishing a block of work in a long session, log it to Scribe, then tell the operator it's a good moment to diff --git a/plugin/skills/using-scribe/SKILL.md b/plugin/skills/using-scribe/SKILL.md index 850ffbf0..8fcba475 100644 --- a/plugin/skills/using-scribe/SKILL.md +++ b/plugin/skills/using-scribe/SKILL.md @@ -87,6 +87,12 @@ Two constraints on *how* that's achieved: then the global rules plus that project's own, never another project's. `enter_project(id)` lists the project's own rules by title. + **A rule about WHEN arrives at its moment, by lookup.** A rule mounted on a + moment of work (`list_moments`: `work.deliver`, `reply.report`, …) arrives + whenever an action reaches that moment, in a line naming both — nothing + said needs to resemble it. [moments.md](moments.md) says how to read those + lines, how to correct a moment that misfires, and when to mount one. + **`kind` says how much force a record carries, and it is never something to infer.** A **rule** must be followed: ignoring it breaks something or crosses a boundary. A **preference** records how the operator wants work @@ -288,6 +294,9 @@ moment rather than on every turn: before giving a note a check. - [missed-retrieval.md](missed-retrieval.md) — a rule that missed the moment it governed, or keeps arriving where it doesn't apply. +- [moments.md](moments.md) — a line that says a rule arrived *at* a moment, a + reply held for one read, an action that reached the wrong moment or none, + and a line proposing a mount. ## You are the judge of what the record says diff --git a/plugin/skills/using-scribe/missed-retrieval.md b/plugin/skills/using-scribe/missed-retrieval.md index c062d255..affaa00a 100644 --- a/plugin/skills/using-scribe/missed-retrieval.md +++ b/plugin/skills/using-scribe/missed-retrieval.md @@ -11,6 +11,21 @@ does **either noticer**: the operator saying *"that should have fired"*, and you noticing it yourself — you reached for a rule nobody offered you, or you were handed the same rule five times and set it aside five times. +**First ask whether it missed a WHEN or a WHAT.** A rule about a point in the +work — it governs delivering, finishing, verifying, reporting, whatever the +work is about — is not a trigger to reword: no wording resembles every piece +of work that reaches that point. Mount it on the moment instead +(`update_rule(moments=[...])`, from `list_moments`); a mount is a lookup, and +it arrives every time the moment happens. When it is already mounted and still +did not arrive, the action did not reach the moment on this install — offer +`map_action` for that action. The reverse — a mounted rule arriving where it +does not apply — is a mount or a mapping that is wrong: take the moment off +the rule, or `unmap_action` the action that reached it. Each of these +changes what the operator's sessions receive, so offer it in one line and make +it on their yes. [moments.md](moments.md) has the +detail. A rule about a subject missed its WHAT, and the rest of this page is +for that. + **Take it to the record first and the dial second.** A rule's `when_to_apply` IS the text its similarity score is computed against, so when a rule misses a moment it governs, the overwhelmingly likely cause is that its trigger does not diff --git a/plugin/skills/using-scribe/moments.md b/plugin/skills/using-scribe/moments.md new file mode 100644 index 00000000..31ddb5eb --- /dev/null +++ b/plugin/skills/using-scribe/moments.md @@ -0,0 +1,66 @@ +# Moments — rules that arrive when the work reaches a point + +Part of the using-scribe skill. Read it when a line says a rule arrived *at* a +moment, when a reply is held for one read, when an action reached the wrong +moment or none, and when a line proposes mounting a rule. + +## What a moment is + +Most rules reach you by resemblance: what you are doing looks like what the +rule is about. A rule about WHEN — finishing, delivering, verifying, +reporting — resembles nothing said at that point, so resemblance misses it. +Such a rule is **mounted** on the moments of work it belongs to, and arrives +by lookup whenever an action reaches one. `list_moments` names them, each +with what is happening at it and the kinds of action that typically reach it: +`work.deliver` when work is sent beyond the place it was made, `work.finish` +when a piece of work is declared done, `reply.report` at the reply that ends a +turn, and so on. A named procedure is its own moment, `skill.`, reached when it is +loaded. + +Which ACTIONS reach a moment is the install's: one operator delivers with a +push, another with a deploy script. Shipped defaults cover the common ones, +and each install corrects them for itself. + +## Reading a line that arrived at a moment + +The line names the moment and the action that reached it — *"at work.deliver, +reached by `git push`"* — and the rule, with `get_rule(N)` to read it. Read it +as you would any rule that arrived beside your work: it binds just as hard, +and it came because of what you are doing now, not because of what you said. + +The reply that ends a turn is a moment too. When a rule mounted at +`reply.report` has not been opened this session, the reply may be held once +with its name: open it, then send the reply — unchanged, if it already does +what the rule asks. The same rule is never held twice. + +## When the moment is wrong — correct it in the session + +A moment can fire on an action that is not that moment here, or an action can +plainly be a moment and fire nothing. Either way the fix is one call, and it +belongs in the session that noticed, not on a settings page: + +- **An action reached the wrong moment** — the line names a moment that is not + what you did: offer `unmap_action(tool, moment, match, reason)`. +- **An action was a moment and nothing arrived** — the operator ships with + their own script, and nothing mounted on `work.deliver` came: + offer `map_action(tool, moment, match, reason)`. + +Offer it in one line, the way the operator would say it ("that deploy script +is a deliver and nothing fired — map it?"), and make it on their yes. The +correction lasts for every later session on the install; `list_moments` shows +what each action reaches now. + +## When a line proposes a mount + +A rule that keeps being opened just after the same moment, across several +sessions, probably belongs on that moment. A line says so, naming the rule, +the moment and how often. It is a question for the operator, not a change you +make: offer it in one line, and record their answer with +`judge_rule_moments` — `confirm` mounts the rule, `reject` with their reason +stops the question being asked again. + +The same tool answers proposals from a pass over the rules +(`rules_to_mount`, `propose_rule_moments`, `rule_moment_proposals`): a pass +proposes, and only the operator's yes mounts. Writing a NEW rule is different +— its moments are part of writing it, and +[writing-records.md](writing-records.md) says how. diff --git a/plugin/skills/using-scribe/writing-records.md b/plugin/skills/using-scribe/writing-records.md index 1de8793e..854d4eee 100644 --- a/plugin/skills/using-scribe/writing-records.md +++ b/plugin/skills/using-scribe/writing-records.md @@ -1,13 +1,15 @@ # Writing a rule, a lesson, or a note that asserts a fact Part of the using-scribe skill. Read it before `create_rule`, -`create_project_rule`, `create_preference` or `create_lesson`; when a lesson +`create_project_rule`, `create_preference`, `create_process` or +`create_lesson`; when a lesson arrives that names the situation you are actually in; when the operator decides how some area of the work must behave; and before filling `verify_with` or `expires_when` on a note. ## Contents - Where a new rule goes — its home, its trigger, what already covers the moment +- When it applies — the moments a rule, a preference or a process is for - A ruling goes on the System it governs - A lesson grows each time it proves itself - A lesson names the rule it is an instance of @@ -39,6 +41,22 @@ with no trigger is not a quiet rule, it is an unreachable one. Write the moment in the words a session actually produces — the command, the error, the half-formed ask — not the category it belongs to. +**Then ask WHEN it applies, as well as what it is about.** A trigger is +matched by resemblance, and a rule about a point in the work — finishing, +delivering, verifying, reporting, asking — resembles nothing said at that +point. Give such a rule its moments as you write it: `list_moments` names +them, and `moments=[...]` on `create_rule`, `create_project_rule` or +`create_preference` mounts it, so it arrives whenever an action reaches one. +Keep the trigger anyway: it is the net for the moments nobody mapped. A rule +about a subject — a library, a file, a style — has no moment; it is reached +by meaning, and leaving `moments` empty is the answer, not an omission. A rule +often has both: a point in the work, and the words a session uses at it. + +A stored **process** says the same about itself: `create_process(moments=…)` +names the moments the procedure is for, so loading it reaches them and the +rules mounted there arrive with it. A rule that only applies inside one +procedure mounts on that procedure's own moment, `skill.`. + **Before writing one, ask what already covers that moment.** `what_might_apply("the moment you are about to write a record for")` — fifty candidates and no bar, so an existing record cannot hide under a threshold the diff --git a/src/scribe/mcp/server.py b/src/scribe/mcp/server.py index 5b1d5ce2..1457a511 100644 --- a/src/scribe/mcp/server.py +++ b/src/scribe/mcp/server.py @@ -43,14 +43,15 @@ from quart import Quart # decision #4027 and the notes it supersedes. _INSTRUCTIONS = """ Scribe is the operator's system of record, and yours: recall before acting, -record as you go, keep one copy here rather than in local memory files. Each -practice below is stated in full in the using-scribe skill (if your client -reads Agent Skills) and in each tool's description. +record as you go, keep one copy here, not in local memory files. Each +practice is stated in full in the using-scribe skill and each tool's +description. - Start with enter_project(id): the project, open work, Systems and design system. An `inception` key: ask what it inherits, then decide_project_inception. -- Rules are not preloaded; one arrives when your work matches it. Before a +- Rules are not preloaded; one arrives when your work matches it or reaches + a moment it is mounted on (list_moments; mount rules about WHEN). Before a consequential act, what_might_apply("what you are about to do"); search(content_type="rule") reads one you suspect. Silence means nothing matched, not none. Rules bind; preferences guide and you keep them current; diff --git a/tests/test_guidance_ownership.py b/tests/test_guidance_ownership.py index 7fd8b707..fba0bfea 100644 --- a/tests/test_guidance_ownership.py +++ b/tests/test_guidance_ownership.py @@ -185,6 +185,19 @@ TOPICS: tuple[Topic, ...] = ( Topic("where a new rule goes, and its trigger", U, ("create_project_rule", "when_to_apply"), "whichever home it gets"), Topic("a rule vs the other entities", U, ("standing instruction",), "first ask whether it's a rule at all"), + # Moments (milestone 458 step 8). Delivery at a moment and its in-session + # corrections are owned by using-scribe's moments.md; the index carries one + # clause, and a new rule's moments are asked where its home and trigger are. + Topic("rules mounted on a moment arrive by lookup; correct a misfire in-session", U, + ("list_moments", "map_action", "unmap_action", "judge_rule_moments"), + "it belongs in the session that noticed, not on a settings page", + index=("list_moments", "moment")), + Topic("a new rule about when gets its moments as it is written", U, + ("moments=[...]", "create_process(moments="), + "then ask when it applies, as well as what it is about"), + Topic("a missed when is mounted or mapped, not reworded", U, + ("update_rule(moments=",), + "first ask whether it missed a when or a what"), # Milestone 416 step 9: the tuning tools shipped in #4102/#4104 and were # named on NO instruction surface — measured, `retrieval_tuning_history` # returned zero events. Machinery with no route to it.