From 90986da8839c5108b8cbe774f4e75bfad7ec56b3 Mon Sep 17 00:00:00 2001 From: Jussi Rajala Date: Fri, 4 Sep 2026 12:39:13 +0300 Subject: [PATCH 1/4] process(rdd-start): session entry answers "what next" from the store queue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rdd-start` already owned the right behaviour — preflight lists the pending human decisions, and taking the scope presents the routed queue — but nothing made a session actually reach it when the question arrived as a question. An orientation ask ("what should I do next?") matched none of the description's triggers, which are all work verbs: start, continue, pick up. The session answered from version control instead, and recommended repository chores while a queue of open human gates sat unread in the context window. Four additions, all to `rdd-start`: - the description now triggers on the orientation forms — what to work on next, where the loop stands, what is waiting on a decision, what is blocked; - preflight states that a host-delivered session-start brief IS the store's answer arriving early, not ambient background, and must be dated against the store revision before it is relied on. A projection that cannot be dated is reported as unknown rather than presented as current — a real failure mode: a cached queue can answer while the live read refuses on an expired token; - taking the scope covers the case where the request is an orientation question rather than a scope, and says plainly that version control, change lists, and the working tree describe the repository rather than the loop; - what is presented is ranked by what a single human answer releases, with the awaiting-decision / ready / awaiting-acceptance distribution stated, because an acceptance bucket that dwarfs the ready bucket is a finding about where the loop is stalled rather than a list to hand over uncounted. Report gains the matching rule: process and tooling work that carries no requirement record is labelled as such and never substitutes for the queue. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0115VUeQov83Qf7tipMR5S3V --- skills/rdd-start/SKILL.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/skills/rdd-start/SKILL.md b/skills/rdd-start/SKILL.md index bf2e101..30cb70f 100644 --- a/skills/rdd-start/SKILL.md +++ b/skills/rdd-start/SKILL.md @@ -1,6 +1,6 @@ --- name: rdd-start -description: Enter a delivery session — verify the process-store binding and active release, reconcile answered gates, take or prompt for the work scope, and route to the phase the loop actually needs, holding commit and check discipline for the whole session. Use at the start of any product-work session, or when asked to start, continue, or pick up requirement-driven work. Not a substitute for any phase skill. +description: Enter a delivery session — verify the process-store binding and active release, reconcile answered gates, take or prompt for the work scope, and route to the phase the loop actually needs, holding commit and check discipline for the whole session. Use at the start of any product-work session; when asked to start, continue, or pick up requirement-driven work; and whenever the question is what to work on next, where the loop stands, what is waiting on a decision, or what is blocked. Not a substitute for any phase skill. --- # Start a delivery session @@ -25,15 +25,36 @@ Read the project `AGENTS.md` and the canonical `PROCESS.md` — installed at header against the store revision. A stale snapshot is refreshed, never edited. +The pending-decision projection may already have been delivered into the +session by the host — a session-start brief injected as context rather than +requested. That is the store's own answer arriving early, not ambient +background: date it against the store revision before relying on it, and +refresh it when it cannot be dated. A projection whose currency is unknown is +reported as unknown, never presented as current. + An unmet preflight fact is the report. Do not select work past it. ## Take the scope Accept the work scope as the argument: an Epic id, a single SR id, or a raw -request. Without one, present the current work selection and the routed -`PROPOSED`/`TODO` queue and ask the human to choose; never pick a release +request. Without one — including when the request is an orientation question +rather than a scope — answer from the store: read the pending human decisions +and the routed `PROPOSED`/`TODO` queue through the store's own projection +read, present them, and ask the human to choose. Never pick a release commitment silently. +Version control, change lists, and the working tree describe the repository, +not the loop. They are never the source for what to do next; a session that +answers an orientation question from them has skipped this skill. + +Rank what you present by what a single human answer releases: an `OPEN` human +gate holding built `IN_REVIEW` work outranks unstarted work, and a gate +holding many items outranks one holding few. State the distribution across +awaiting-decision, ready-to-build, and awaiting-acceptance. A queue whose +awaiting-acceptance bucket dwarfs its ready bucket is a finding about where +the loop is stalled — report it as one rather than leaving the reader to count +rows. + Freeze the selection per `PROCESS.md` work scope and record it in the work-selection record. Packet depth is proportional to the frozen scope; no packet item may be omitted. @@ -64,3 +85,8 @@ complete loop. Report the store binding and how it was confirmed, the active release and its source, pending human decisions, the frozen scope and fingerprint, and the phase entered — or the exact preflight fact that stopped the session. + +Repository and process work that carries no requirement record — tooling, +instructions, delivery infrastructure — is reported separately and labeled as +such. It is real work and may be urgent, but it is not what the queue is +asking for and never substitutes for the queue in the answer. From 32d1abe05d6e06496df40033b5313ec33107f227 Mon Sep 17 00:00:00 2001 From: Jussi Rajala Date: Fri, 4 Sep 2026 12:47:59 +0300 Subject: [PATCH 2/4] process: an orientation ask is session entry, in the file always read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skill fix alone was circular. `rdd-start/SKILL.md` is read once something routes a session to it; `AGENTS.md` is required reading before any product work, so it is the file that does the routing. Widening only the skill's triggers left the failure intact for exactly the session that never opened the skill — which is the session the change is for. `Required reading` gated on four verbs: planning, changing, reviewing, delivering. Being asked what to do next is none of them, so the entry condition never fired and the queue went unread. Now the gate names the orientation asks alongside the four verbs, and a closing paragraph states where the answer comes from: the store's pending human decisions and routed work, through `rdd-start`. Version control, change lists, and the working tree describe the repository rather than the loop and never answer the question. The concrete read stays project-owned — this package is host-agnostic and must not name one CLI's commands — but naming it is now an obligation on the project's root `AGENTS.md`, and an omission is reportable rather than silently absorbed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0115VUeQov83Qf7tipMR5S3V --- AGENTS.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index c89477b..bd32750 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,9 @@ transition and report the conflict rather than creating a silent local variant. ## Required reading -Before planning, changing, reviewing, or delivering product work: +Before planning, changing, reviewing, or delivering product work — and +before answering what to work on next, where the loop stands, what is waiting +on a decision, or what is blocked: 1. read the project's root `AGENTS.md` for project-specific rules; 2. read this file and `PROCESS.md`; @@ -39,6 +41,13 @@ Before planning, changing, reviewing, or delivering product work: 4. read the relevant product sources, requirement records, optional epic, and active work-selection record. +Being asked what to do next is session entry, not a shortcut past it. The +answer is the store's pending human decisions and routed work, read through +`skills/rdd-start/SKILL.md`. Version control, change lists, and the working +tree describe the repository rather than the loop, and never answer that +question. A project's root `AGENTS.md` names the concrete read for its own +store; if it names none, that omission is the report. + ## Binding process Follow `PROCESS.md`. Skills apply it and `file-state/` serializes its records; From 10e7460bc25c79f520ac70ee81a31c82f2be40f0 Mon Sep 17 00:00:00 2001 From: Jussi Rajala Date: Fri, 4 Sep 2026 18:15:30 +0300 Subject: [PATCH 3/4] process: the authority owns the rule the other two files apply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AGENTS.md` §Instruction ownership is explicit that `PROCESS.md` defines the canonical process and that skills apply it rather than redefining it. The two earlier commits put a rule into `AGENTS.md` and `rdd-start` that `PROCESS.md` never states — a rule asserted by files whose own charter says they may not assert one. Correct behaviour, wrong home. Three places state it where it belongs: - `Authority` already closes its source table by warning that code proves existing behavior rather than intended behavior. Repository state deserves the same caveat and never had one: branches, diffs, change lists and version control's own review queues prove what the repository contains, not what the loop holds, and are never a source for selection, status, or priority. The table has no row for them because they are not sources — but that silence is what let a session reach for them first. - The `Enter session` phase row required a frozen scope routed to its earliest unmet phase, which is not the only legitimate exit. Answering an orientation request from the current pending-decision projection is the other, and is now named as such. - `State records` said the projection is regenerated and is never lifecycle authority — what it is not, twice, and never what it is for. It now also says what it is for: the session's answer to what to work on next, read from the store, dated against the store revision, presented ranked by what a single human answer releases. A projection delivered into a session ahead of the request is that same answer arriving early, not background context. `rdd-deliver` needs no matching change: it defers all semantics to `PROCESS.md` and already reconciles projections in its first step. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0115VUeQov83Qf7tipMR5S3V --- PROCESS.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index 6a825e8..604eb71 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -51,7 +51,10 @@ be established from authoritative records, code, tests, or runtime evidence. | `EPIC:#
` | Existing Epic record | Missing support is an open question. Conflicting support remains a conflict -until a human resolves it. Code proves existing behavior, not intended behavior. +until a human resolves it. Code proves existing behavior, not intended +behavior. Repository state — branches, diffs, change lists, and version +control's own review queues — proves what the repository contains, not what +the loop holds; it is never a source for selection, status, or priority. Here, **material** means capable of changing correctness, security, data integrity, a public contract, trace completeness, acceptance, or testability. @@ -288,7 +291,7 @@ focused skill alone only when the requested scope explicitly ends at that pass. | Phase | Skill | Required exit | |---|---|---| -| Enter session | `rdd-start` | Store binding and single active release verified from the store; answered gates reconciled; frozen scope routed to its earliest unmet phase | +| Enter session | `rdd-start` | Store binding and single active release verified from the store; answered gates reconciled; frozen scope routed to its earliest unmet phase, or an orientation request answered from the current pending-decision projection | | Source/classify | `rdd-discover` | Authoritative input or an exact confirmation gate; no unconfirmed requirement proceeds | | Plan/reconnaissance | `rdd-plan` | Entry-packet items 1–6 and the human brief at a named revision | | Cold review | `rdd-cold-review` | Current cold-review trace verdict and finding dispositions | @@ -443,6 +446,12 @@ selections, and selection history. `BACKLOG.md` stores unrouted triage items and gap records. Derived queues and progress views — including the pending human-decision projection — are regenerated, not backed up separately. +That projection is never lifecycle authority, and it is the session's answer +to what to work on next: it is read from the store, dated against the store +revision, and presented — ranked by what a single human answer releases. A +projection delivered into a session ahead of the request is that same answer +arriving early, not background context. + | Concern | Authority | |---|---| | Product/domain/architecture/contracts | Product documents and schemas | From f044c8cc317a8caad9906aff53cba651e9e1015a Mon Sep 17 00:00:00 2001 From: Jussi Rajala Date: Sun, 6 Sep 2026 00:18:53 +0300 Subject: [PATCH 4/4] process(rdd-audit): the citation audit stops hiding rot behind a stray "not", and can be floored against a vacuous scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects in skills/rdd-audit/audit-citations.mjs, both in the one direction a citation audit must never fail — making a broken audit read as a clean pass. The absence exemption excused a *failing* citation whenever `not` or `never` appeared in the 60-char window before it. Those two words carry no absence meaning on their own: "the handler is not used anymore; its logic moved to CODE:new.go:900" is an ordinary sentence, and if new.go is 400 lines the citation is rot — but the trailing "is not" excused it. Every other absence word names a missing thing directly (no, there is no, missing, does not exist, absent, without); not/never are removed, so a deliberate gap still reads "there is no X" and stays excused, while an unrelated negation no longer grants a real broken citation a pass. Verified on a fixture: a citation to a non-existent line trailing "...is not used; see" flips from excused to broken, while "there is no " stays excused. Second, the tool exits 0 on 0/0 citations — if the scan matches nothing (an extension the list does not cover, a mis-pointed root) it reports a clean pass: the residual of the ".NET printed 10/10 while skipping 710" failure, where the count itself is the tell. A new opt-in --min lets the CALLER, which knows its corpus, assert a floor; the generic tool stays generic (default 0), so a citation-light repository is unaffected. The floor lives in the gate, not the reader. No test harness exists here; verified by running the pre- and post-change tool over a crafted git fixture (RUN:2026-09-06). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RnQSEg1xmgDoQpnecvN4bR --- skills/rdd-audit/audit-citations.mjs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/skills/rdd-audit/audit-citations.mjs b/skills/rdd-audit/audit-citations.mjs index 480703a..24642d3 100755 --- a/skills/rdd-audit/audit-citations.mjs +++ b/skills/rdd-audit/audit-citations.mjs @@ -61,9 +61,22 @@ const ELIDED = /(?:CODE:|`)(?:[A-Za-z0-9_.\\[\\]\-]+\/)*\.\.\.\/[A-Za-z0-9_.\/\\ // C8: a row naming a gap is not a citation. "there is no test_draft_service.py" // is the most useful thing a derivation pass produces, and an audit that counts // it as broken teaches the next pass to stop naming what is missing. -const ABSENCE = /\b(no|not|missing|absent|never|does not exist|there is no|without)\b[^.]{0,60}$/i; +// Bare `not`/`never` are NOT in this list: alone they carry no absence meaning +// ("the handler is not used anymore; its logic moved to `CODE:new.go:900`"), yet +// inside the 60-char window they excused any failing citation trailing an +// unrelated negation — hiding real rot behind a stray "is not". Every word kept +// names absence directly; a deliberate gap still reads "no"/"there is no"/etc. +const ABSENCE = /\b(no|missing|absent|does not exist|there is no|without)\b[^.]{0,60}$/i; const roots = process.argv.slice(2).filter((a) => !a.startsWith("--")); +// A vacuity floor the CALLER sets, because only the caller knows its corpus. A +// generic run stays generic (default 0); a gate over a corpus that always +// carries citations passes --min=N, so a scan that silently matched almost +// nothing — the ".NET estate printed 10/10 while skipping 710" failure in its +// residual form, where the count itself is the tell — exits non-zero instead of +// reading as a clean pass. +const minArg = process.argv.find((a) => a.startsWith("--min=")); +const minCitations = minArg ? Math.max(0, parseInt(minArg.slice(6), 10) || 0) : 0; const targets = roots.length ? roots : ["docs", ...(existsSync("ARCHITECTURE.md") ? ["ARCHITECTURE.md"] : [])]; // Refuse to audit the prompts. Teaching material deliberately @@ -275,6 +288,7 @@ function check(doc, text, m) { } const total = ok + broken.length; +const belowFloor = minCitations > 0 && total < minCitations; console.log(`${ok}/${total} citations resolve across ${docs.length} documents` + (gaps ? ` (+${gaps} named gaps excused — a cited path stated as absent)` : "")); @@ -286,6 +300,10 @@ if (broken.length) { console.log(`\n${broken.length} broken:`); for (const b of broken) console.log(` ${b.doc}:${b.lineNo} ${b.path} — ${b.why}`); } -if (!elided.length && !broken.length) console.log("no elided paths"); +if (belowFloor) { + console.log(`\nvacuity floor: ${total} citation(s) checked, below the required --min ${minCitations} —` + + " too little matched to be a real audit (a broken scan otherwise reads as a clean pass)"); +} +if (!elided.length && !broken.length && !belowFloor) console.log("no elided paths"); -process.exit(broken.length || elided.length ? 1 : 0); +process.exit(broken.length || elided.length || belowFloor ? 1 : 0);