diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index a6ac874..9117cca 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.01.1724", + "version": "2026.10.02.2259", "author": { "name": "Bryan Van Deusen" }, diff --git a/plugin/skills/reporting-back/SKILL.md b/plugin/skills/reporting-back/SKILL.md index 87aba75..0253700 100644 --- a/plugin/skills/reporting-back/SKILL.md +++ b/plugin/skills/reporting-back/SKILL.md @@ -153,6 +153,15 @@ name. Before asking, check whether you can find the answer yourself — something that can be read or looked up is a fact to check, not a question to send. +**An option carries assumptions, and the operator approves only what they can +see.** When an option keeps or depends on existing behaviour — a limit, a +default, a fallback already in place — say whose call that behaviour was: the +operator's, citing the ruling ("you ruled this, #N"), or a past session's that +nobody confirmed ("a past session chose this; you were never asked"). Choosing +between options is not a decision about the assumptions they share, and an +unlabelled one gets approved as if it had been asked. An assumption that +contradicts a recorded ruling is a **Conflict**, not an option's fine print. + ## Answers — the operator asked something | Kind | Sections | diff --git a/plugin/skills/using-scribe/SKILL.md b/plugin/skills/using-scribe/SKILL.md index c469ab9..9f3528e 100644 --- a/plugin/skills/using-scribe/SKILL.md +++ b/plugin/skills/using-scribe/SKILL.md @@ -52,6 +52,12 @@ Two constraints on *how* that's achieved: re-deriving it or opening a duplicate. When a project is in scope, pass its `project_id` so results stay scoped. + **Code tells you what a thing does, not what was wanted.** A limit, a + default or a fallback found by reading the work is a past session's choice + until a record says the operator made it. Before presenting one as a + safeguard or a known cost, read the `Rulings` on the System it belongs to; + when none speaks to it, call it unconfirmed rather than settled. + 2. **Rules are binding, and they reach you by retrieval.** No call loads the operator's rules and no standing set is handed to a session. A rule arrives when what you are about to do resembles what it is about — a command, the @@ -227,6 +233,13 @@ Two constraints on *how* that's achieved: re-measurement, a reversed decision), pass the old id in `supersedes` so the stale record is demoted and labelled rather than left competing. + **What the operator decided an area must do is a ruling, and it lives on + that System** — a `Rulings` section in its description, written the turn + they decide. A quote in a work log reaches the next session only if a + search happens to match it; the description arrives with every record + filed there. [writing-records.md](writing-records.md) has the two tests and + the form. + 10. **A note that asserts a fact about someone else's software can carry its own check** (`verify_with`, `expires_when`), swept by `notes_due_for_verification`. Most notes should not: read @@ -264,7 +277,8 @@ moment rather than on every turn: (`decide_project_inception` when `enter_project` returns `inception`). - [writing-records.md](writing-records.md) — before writing a rule, preference or lesson (where it goes, its `when_to_apply`, what already covers the - moment), and before giving a note a check. + moment), when the operator decides how an area must behave (a ruling), and + 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. diff --git a/plugin/skills/using-scribe/writing-records.md b/plugin/skills/using-scribe/writing-records.md index 0c86018..df8f6d7 100644 --- a/plugin/skills/using-scribe/writing-records.md +++ b/plugin/skills/using-scribe/writing-records.md @@ -2,11 +2,13 @@ Part of the using-scribe skill. Read it before `create_rule`, `create_project_rule`, `create_preference` or `create_lesson`; when a lesson -arrives that names the situation you are actually in; and before filling +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 +- 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 - A note that asserts a fact can carry its own check @@ -65,6 +67,52 @@ insight that costs time is a **lesson** (`create_lesson`), keyed to the situation it applies to so a later session meets it there. Both are first-class outcomes of noticing something, not what's left when a rule proposal fails. +## A ruling goes on the System it governs + +A **ruling** is the operator's decision about what the work itself does in one +area: how it behaves, what it must never do, which way a trade-off goes. It is +not a rule. A rule governs how *you* work and reaches you when your work +resembles it; a ruling governs what the *thing being built* does, and it has to +reach every session working in that area, whatever words that session is using. + +Two tests, and it needs both: + +- **The operator decided it.** They said it, or approved it when it was put to + them as its own question. Approving an option is not approving every + assumption the option carried — only what it named. +- **A later change could plausibly undo it.** "Failed work is retried until it + succeeds" qualifies. The name of the table that tracks the retries does not; + that is implementation, and it lives in the code and the logs. + +A choice a session made on its own is not a ruling, however sound. Keeping the +two apart is the point: once code embodies a session's choice, it reads exactly +like the operator's intent, and the next session builds on it as if it were. + +**Where it goes: a `Rulings` section at the end of the System's description**, +one line each — the statement, who decided, when, and the record it came from: + + Rulings + - Failed work is retried until it succeeds; no attempt limit. (Operator, 2026-09-19, #1234; restated 2026-10-02, #1290) + +The description arrives with every record filed under that System, so a ruling +there reaches each session working in the area without having to win a search. +A quote inside a work log does not: it surfaces only when a query happens to +match it, and the passage that matches is usually the prose around it — often a +session's *reading* of the ruling rather than the ruling. + +**When: in the turn the operator decides, before building on it.** +`get_system(id)`, then `update_system(id, description=...)` with the whole +description — the field is replaced, not appended to. Also when the operator +corrects work that departed from something they had already said: that ruling +existed and did not reach the work, and writing it where it will is half the +fix. No System fits? The area is probably unnamed — create it. + +**It holds what is in force, not its history.** When a ruling is overturned, +change or remove its line and cite the record that changed it; the work log +keeps the history. If the charter above the section contradicts a ruling — +usually a charter written before the decision — fix that sentence in the same +edit. A stale charter line is a ruling nobody made, and it is read as one. + ## A lesson grows each time it proves itself When one arrives and the situation it names is the one you are actually in, you diff --git a/src/scribe/mcp/tools/systems.py b/src/scribe/mcp/tools/systems.py index bd8784b..1886927 100644 --- a/src/scribe/mcp/tools/systems.py +++ b/src/scribe/mcp/tools/systems.py @@ -188,6 +188,13 @@ async def create_system( charter, not just a label: the description is what tells a later session whether a record belongs here. + The description is also where the operator's RULINGS for the area live: + a `Rulings` section at its end, one line per decision the operator made + about how this area must behave — the statement, who decided, when, and + the record it came from. The description arrives with every record filed + under the System, so a ruling there reaches each session working in the + area; a quote in a work log reaches one only if a search matches it. + Args: project_id: The project this system belongs to (required). name: Short label (required). @@ -303,6 +310,15 @@ async def update_system( ) -> dict: """Update a System. Only explicitly provided fields change. + `description` REPLACES the whole text, so read it with get_system first + and send it back entire. Recording a RULING — the operator's decision + about how this area must behave — is an edit here: add or change its line + in the `Rulings` section at the end, in the turn they decide, citing the + record it came from. Overturned, the line changes rather than gaining a + successor: the section holds what is in force, and the work log keeps the + history. If the charter above it contradicts a ruling, fix that sentence + in the same edit. + Args: status: 'active' or 'archived'. Archive a system to retire it without losing history; archived systems hide from default lists. diff --git a/tests/test_guidance_ownership.py b/tests/test_guidance_ownership.py index ccffb91..1779915 100644 --- a/tests/test_guidance_ownership.py +++ b/tests/test_guidance_ownership.py @@ -195,6 +195,16 @@ TOPICS: tuple[Topic, ...] = ( ("what_might_apply",), "ask what already covers that moment"), Topic("reference notes update in place; dev-logs don't", U, ("reference note",), "state updates in place; chronicles don't"), + # Milestone 444: an operator's ruling was kept only as a quote in a work + # log, beside a session's reading of it that contradicted it, and three + # later sessions built on the reading. No index marker: both fire + # mid-work — when the operator decides, and when code is read as intent. + Topic("an operator's ruling lives on the System it governs", U, + ("rulings", "update_system", "the operator decided it"), + "a stale charter line is a ruling nobody made"), + Topic("code says what a thing does, not what was wanted", U, + ("rulings", "unconfirmed"), + "code tells you what a thing does, not what was wanted"), # ── process arcs — owned by their skills ── Topic("plan in a milestone, steps created together", "skill:writing-plans", ("start_planning", "{{ref:"), "a milestone earns its place when the work has an arc", index=("start_planning",)), @@ -229,6 +239,12 @@ TOPICS: tuple[Topic, ...] = ( Topic("a settled decision is acted on, not re-opened", "skill:reporting-back", ("already made",), "reads as contradicting yourself rather than as being careful"), + # Milestone 444: a cap a past session chose rode inside an option as a + # "known cost", the operator approved the option, and the cap read as + # theirs from then on. + Topic("an option says whose call each carried assumption was", + "skill:reporting-back", ("past session chose this", "conflict"), + "an unlabelled one gets approved as if it had been asked"), # Milestone 409 step 8: `placement` rides a WRITE, so a task the reply only # cites arrived with nothing vouching for it — which is how a step finished # four days earlier was reported as the open one (#4154).