From 918e90b41e681c6aad48d733f3e8da5ef2e4e7f5 Mon Sep 17 00:00:00 2001 From: mattias-modernpath Date: Sun, 6 Sep 2026 22:34:29 +0300 Subject: [PATCH 1/4] test: reproduce citation audit false passes --- tests/audit-citations.test.mjs | 144 +++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 tests/audit-citations.test.mjs diff --git a/tests/audit-citations.test.mjs b/tests/audit-citations.test.mjs new file mode 100644 index 0000000..ca1c249 --- /dev/null +++ b/tests/audit-citations.test.mjs @@ -0,0 +1,144 @@ +import assert from 'node:assert/strict'; +import { execFileSync, spawnSync } from 'node:child_process'; +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const auditor = fileURLToPath(new URL('../skills/rdd-audit/audit-citations.mjs', import.meta.url)); + +function audit(t, document, extraFiles = {}, args = ['claims.md']) { + const dir = mkdtempSync(join(tmpdir(), 'rdd-citation-test-')); + t.after(() => rmSync(dir, { recursive: true, force: true })); + execFileSync('git', ['init', '-q', dir]); + const files = { + 'claims.md': document, + 'behavior.test.ts': 'function checksBehavior() {}\nfunction TestParent() { test("Subtest", () => {}); }\ntest("rejects invalid input", () => {});\nfunction x() {}\n', + 'spec.md': '# Contract\n## Follow-Up\n', + ...extraFiles, + }; + for (const [path, content] of Object.entries(files)) { + const target = join(dir, path); + mkdirSync(join(target, '..'), { recursive: true }); + writeFileSync(target, content); + } + execFileSync('git', ['add', '.'], { cwd: dir }); + const result = spawnSync(process.execPath, [auditor, ...args], { cwd: dir, encoding: 'utf8' }); + assert.ifError(result.error); + return { status: result.status, output: result.stdout + result.stderr }; +} + +test('a nonexistent canonical TEST file fails instead of checking nothing', t => { + const result = audit(t, 'Required evidence: TEST:absent/behavior.test.ts:checksBehavior'); + assert.equal(result.status, 1, result.output); + assert.match(result.output, /no such file/); +}); + +test('valid canonical TEST references are actually checked', t => { + const result = audit(t, 'TEST:behavior.test.ts:checksBehavior'); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /1\/1 citations resolve/); + assert.match(result.output, /checked=1/); +}); + +test('missing test name fails even when its file exists', t => { + const result = audit(t, 'TEST:behavior.test.ts:checksAnotherBehavior'); + assert.equal(result.status, 1, result.output); + assert.match(result.output, /no such test or symbol/); +}); + +test('a matching name prefix does not satisfy the cited identity', t => { + const result = audit(t, 'TEST:behavior.test.ts:checksBehav'); + assert.equal(result.status, 1, result.output); +}); + +test('short names and complete hierarchical test names are checked', t => { + const result = audit(t, 'TEST:behavior.test.ts:x\nTEST:behavior.test.ts:TestParent/Subtest'); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /2\/2 citations resolve/); +}); + +test('a missing subtest is not silently truncated to its existing parent', t => { + const result = audit(t, 'TEST:behavior.test.ts:TestParent/MissingSubtest'); + assert.equal(result.status, 1, result.output); + assert.match(result.output, /MissingSubtest/); +}); + +test('quoted test names with spaces resolve', t => { + const result = audit(t, 'TEST:behavior.test.ts:"rejects invalid input"'); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /checked=1/); +}); + +test('CODE, bare line ranges and DOC references still resolve', t => { + const result = audit(t, 'CODE:behavior.test.ts:checksBehavior\n`behavior.test.ts:1,3-4`\nDOC:spec.md#Follow-Up'); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /3\/3 citations resolve/); +}); + +test('a missing line in the end of a compound range fails', t => { + const result = audit(t, '`behavior.test.ts:1,3-400`'); + assert.equal(result.status, 1, result.output); +}); + +test('short CODE symbols cannot degrade to file-only checks', t => { + const result = audit(t, 'CODE:behavior.test.ts:y'); + assert.equal(result.status, 1, result.output); + assert.match(result.output, /no such test or symbol/); +}); + +test('unsupported CODE extensions are visible', t => { + const result = audit(t, 'CODE:behavior.wat:checksBehavior'); + assert.equal(result.status, 2, result.output); + assert.match(result.output, /unsupported=1/); +}); + +test('missing quoted test names do not pass on a neighboring string', t => { + const result = audit(t, 'TEST:behavior.test.ts:"rejects invalid"'); + assert.equal(result.status, 1, result.output); +}); + +test('an unsupported TEST suffix is not truncated into a pass', t => { + const result = audit(t, 'TEST:behavior.test.ts:TestParent/'); + assert.equal(result.status, 2, result.output); + assert.match(result.output, /unsupported=1/); +}); + +test('unsupported canonical reference is visible beside passing references', t => { + const result = audit(t, 'CODE:behavior.test.ts:checksBehavior\nTEST:behavior.test.ts'); + assert.equal(result.status, 2, result.output); + assert.match(result.output, /unsupported=1/); +}); + +test('no citations is not a successful validation', t => { + const result = audit(t, '# No checkable claims'); + assert.equal(result.status, 2, result.output); + assert.match(result.output, /no citations checked/i); +}); + +test('a missing requested root is not silently ignored', t => { + const result = audit(t, 'CODE:behavior.test.ts:checksBehavior', {}, ['claims.md', 'absent-docs']); + assert.equal(result.status, 2, result.output); + assert.match(result.output, /absent-docs/); +}); + +test('example-only input is exempt, never counted as a successful check', t => { + const result = audit(t, '```text example-citation\nTEST:absent.test.ts:example\nDOC:absent.md#Example\n```'); + assert.equal(result.status, 2, result.output); + assert.match(result.output, /exempt=2/); + assert.match(result.output, /checked=0/); +}); + +test('known gaps remain separate from checked citations', t => { + const result = audit(t, 'No CODE:missing.ts exists.\nTEST:behavior.test.ts:checksBehavior'); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /gaps=1/); + assert.match(result.output, /checked=1/); +}); + +test('elided canonical TEST paths fail', t => { + const result = audit(t, 'TEST:.../behavior.test.ts:checksBehavior'); + assert.equal(result.status, 1, result.output); + assert.match(result.output, /1 elided path/); +}); From f0d80ada4d087afd09f827f131c2e0a7c1f88563 Mon Sep 17 00:00:00 2001 From: mattias-modernpath Date: Sun, 6 Sep 2026 22:35:46 +0300 Subject: [PATCH 2/4] fix: make RDD lifecycle and skills resumable --- PROCESS.md | 205 +++- README.md | 21 +- file-state/BACKLOG.md | 7 +- file-state/GATES.md | 7 +- file-state/REQUIREMENTS.md | 36 +- file-state/WORK-SELECTION.md | 37 +- skills/rdd-audit/SKILL.md | 1036 ++--------------- skills/rdd-audit/audit-citations.mjs | 76 +- skills/rdd-audit/references/audit-examples.md | 964 +++++++++++++++ skills/rdd-build/SKILL.md | 30 +- skills/rdd-build/agents/openai.yaml | 4 +- skills/rdd-cold-review/SKILL.md | 19 +- skills/rdd-completion-review/SKILL.md | 27 +- skills/rdd-deliver/SKILL.md | 27 +- skills/rdd-discover/SKILL.md | 8 + skills/rdd-engineering-check/SKILL.md | 26 +- skills/rdd-entry-review/SKILL.md | 18 +- skills/rdd-plan/SKILL.md | 13 +- skills/rdd-reverse-engineer/SKILL.md | 602 +++------- .../rdd-reverse-engineer/agents/openai.yaml | 4 +- .../references/modernpath-adapter.md | 58 + .../references/recovered-design.md | 36 + skills/rdd-start/SKILL.md | 27 +- skills/rdd-triage/SKILL.md | 12 +- skills/rdd-verify/SKILL.md | 40 +- tests/audit-citations.test.mjs | 8 + tests/process-scenarios.md | 184 +++ 27 files changed, 2014 insertions(+), 1518 deletions(-) create mode 100644 skills/rdd-audit/references/audit-examples.md create mode 100644 skills/rdd-reverse-engineer/references/modernpath-adapter.md create mode 100644 skills/rdd-reverse-engineer/references/recovered-design.md create mode 100644 tests/process-scenarios.md diff --git a/PROCESS.md b/PROCESS.md index f04dced..f08b78e 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -141,8 +141,12 @@ A directly sourced requirement may start `PROPOSED`. An Epic uses `PROPOSED -> TODO -> IN_PROGRESS -> IN_REVIEW -> DONE` and the same side states; it has no `DERIVED` state. -On release from `BLOCKED` or `DEFERRED`, restore only the strongest state -supported by current gates and evidence. +When applying `BLOCKED` or `DEFERRED`, record the hold separately from each +affected item's suspended-from lifecycle state, with its gate/evidence basis. +Do not flatten differently progressed members into one scope-level prior state. +On release, restore only the strongest state supported by current gates and +evidence; the recorded prior state is provenance, not permission to restore +invalidated progress. Keep the hold and restoration history. Acceptance scenarios, code, test cases, and planning artifacts have no work lifecycle. Evidence conclusions are not completion states: @@ -179,8 +183,10 @@ While a requirement is `DERIVED`: - label every proposed relation `CANDIDATE`; - exclude it from authoritative trace, release, readiness, coverage, progress, and completion; -- do not create or advance related requirements, acceptance content, - reconnaissance, tests, implementation, verification, or delivery. +- do not use the candidate to authorize related requirements, acceptance + content, implementation reconnaissance, tests, verification, or delivery. + Read-only observation and independently sourced candidates with candidate-only + links may be recorded during adoption; they confer no downstream authority. ```text DERIVED -> confirmed/corrected ----------> PROPOSED @@ -212,6 +218,33 @@ failure leaves the gate `ANSWERED` and preserves its holds. Feedback not attached to an exact `OPEN` gate is a source or proposed decision, not a gate answer. +### Fingerprint ownership + +Fingerprint inputs are explicit and canonically ordered; each result records +the input manifest as well as its hash. Never hash an entire mutable record. +Lifecycle status, timestamps, and a gate's own result id, verdict, findings, +answer, or application state are not inputs to that gate. References to +prerequisite results are inputs where the table specifies them. + +| Fingerprint | Inputs | +|---|---| +| Confirmation/policy/release decision | Exact candidate or decision packet, named item/release ids, sources, relevant input-record revisions and prerequisites; no resulting status or application revision | +| Selection | Selected item content, declared relations, exact scope, owner, and release; no EC resolution or lifecycle status | +| Planning | Selection fingerprint; Entry-packet items 1–6 and 8; named reconnaissance baseline and affected surface; exact applicable EC ids and approved versions | +| Cold review | Planning fingerprint and exact current planning-engineering result reference | +| Entry | Planning fingerprint and exact current engineering/cold-review result references, including their findings and dispositions; no entry verdict or answer | +| Candidate/delivered engineering | Target kind, selection/planning references, actual target code/configuration fingerprint and revision, affected surface, applicable EC ids/versions, and verification inputs | +| Completion | Exact named items, delivered revision, applicable evidence assessments, review findings/dispositions, delivered engineering result, and reconciled delivery facts; no completion verdict or answer | + +Review outputs are attached records, not additions to planning inputs. Recording +cold-review findings does not stale planning engineering; changing the plan in +response does. Replacing a prerequisite review result stales dependent entry +review. Authorized implementation changes do not change the reconnaissance +baseline or planning inputs; new surface, changed policy, or material baseline +drift does. Preserve applied approvals as history and record whether they still +authorize the affected work. Result references never include their own dependent +gate, so fingerprint dependencies are acyclic. + ### Strict human transitions | Transition | Required trace `PASS` before human input | @@ -221,6 +254,7 @@ not a gate answer. | EC `ACTIVE -> SUPERSEDED/RETIRED` | Successor or retirement effect and affected scope are complete | | Requirement `PROPOSED/PENDING_VERIFICATION -> TODO` | Its Entry packet is complete at the exact fingerprint | | Epic `PROPOSED -> TODO` | Its Entry packet and every selected member's entry trace are complete | +| Renew entry authority for invalidated approved work | Updated Entry packet and reviews pass for the exact affected subset; retain implementation history and resume at the strongest supported state | | Requirement `IN_REVIEW -> DONE` | Its completion predicate is satisfied at the delivered fingerprint | | Epic `IN_REVIEW -> DONE` | Every member is already `DONE` or named and completion-eligible in the same gate; the Epic completion predicate is satisfied | @@ -257,16 +291,43 @@ An agent or deterministic check may apply these only from a current trace-gate | SR `TODO -> IN_PROGRESS` | Approved entry fingerprint and expected lower RED | | UR `TODO -> IN_PROGRESS` | Expected upper RED or a required SR is `IN_PROGRESS` | | Epic `TODO -> IN_PROGRESS` | An in-scope member is `IN_PROGRESS` | -| SR `IN_PROGRESS -> IN_REVIEW` | Its lower trace is current and passes | +| SR `IN_PROGRESS -> IN_REVIEW` | Its lower trace is current and passes; any required corrective rechecks pass | | UR `IN_PROGRESS -> IN_REVIEW` | Required SRs are `IN_REVIEW/DONE`; current upper evidence passes | | Epic `IN_PROGRESS -> IN_REVIEW` | Members are `IN_REVIEW/DONE`; applicable trace gates pass | -Agents may also apply evidence-invalidation demotions, and may apply `BLOCKED` +Agents may also apply evidence-invalidation and review-correction demotions, +and may apply `BLOCKED` from an established impediment and release it when the impediment is gone. Applying `DEFERRED` records a postponement decision and requires an attributable human source. No automated transition creates or substitutes for a human answer. +### Review corrections within approved scope + +An observed upper-flow failure or an implementation/review/engineering finding +may reopen affected `IN_REVIEW` work as `IN_PROGRESS`. Record the direct source, +affected SRs and dependent UR/Epic items, correction boundary, unchanged entry +approval, and checks to rerun. This is a corrective demotion, not a forward +transition requiring a passing implementation trace. Propagate only through +declared dependencies; unaffected items retain their strongest supported state. +Stale affected implementation-review, candidate/delivered engineering, and +completion gates and supersede their unclosed human gates. Do not stale planning, +cold-review, or entry authority when their recorded inputs are unchanged. + +Use `rdd-build` for the correction. For a behavior defect, establish a focused +reproduction RED against the approved clause or upper scenario. For a +behavior-preserving engineering correction, the recorded conformance failure +is the work target; do not invent a new behavioral requirement or RED. Preserve +admissible historical RED, rerun affected behavioral/regression evidence, and +rerun the failed review/check after correction. Integration stays blocked until +the pre-delivery audit and candidate engineering trace pass. + +If already-delivered `DONE` work is proven defective, reopen only the affected +trace via the same scoped demotion; a new delivered fingerprint requires a +successor human completion gate. Prospective EC activation alone does not reopen +earlier `DONE` work. Changed intent, scope, EC applicability, or a material +decision returns to planning and renewed entry approval instead of this route. + ## Work scope | Scope | Use when | Required relations | @@ -278,6 +339,14 @@ Expand single-SR work to Epic scope when it changes user outcome or acceptance, requires another SR, or introduces a cross-cutting decision. Related approved items repeat entry approval only when their approved scope changes. +A selection may contain mixed lifecycle states. Evaluate prerequisites per +item and route only the unfinished or invalidated subset. Keep unaffected +`IN_REVIEW`/`DONE` items and their approvals; do not reset a whole Epic to make +its statuses uniform. Completion-ready selected requirements are `IN_REVIEW` +or `DONE` with their applicable traces satisfied; only eligible `IN_REVIEW` +items receive new completion transitions. An obsolete member requires an +authoritative scope/removal decision before it can be excluded from readiness. + ## Planning and readiness Planning consists of packet authoring, independent cold review, and entry @@ -289,7 +358,7 @@ may be omitted. ### Entry packet -The fingerprinted packet must contain: +The entry packet consists of planning inputs and attached review outputs: 1. authoritative item content, declared relations, scope, owner, and release; 2. UR scenarios and thin SRs where applicable; @@ -328,9 +397,14 @@ for cold-review `PASS`. Engineering findings join the cold-review finding list, but the broader technical review remains responsible for risks not expressed as ECs; the flat EC set is not presumed complete. -Entry review evaluates the complete packet at its exact fingerprint. Only a -current entry trace `PASS` may open the human entry gate. Do not create or -change tests or implementation until every selected item is `TODO`. +Entry review evaluates the complete packet at its entry fingerprint. Only a +current entry trace `PASS` may open the human entry gate. Before changing tests +or implementation, every item whose tests or implementation will be affected needs +current applied entry approval and no active hold. New entrants become `TODO`; +already-approved work resumes in its strongest supported state. Review +corrections reopen affected items before edits; no scope-wide `TODO` reset is +required. A renewed entry gate may reauthorize invalidated approved work without +pretending its earlier implementation or approvals never existed. ## Development loop @@ -349,7 +423,7 @@ focused skill alone only when the requested scope explicitly ends at that pass. | 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 | -| Entry | `rdd-entry-review` | Applied human approval and selected items in `TODO`, or an explicit non-entry result | +| Entry | `rdd-entry-review` | Current applied approval for the affected subset; new entrants `TODO`, unchanged items preserved, or an explicit non-entry result | | Execute changed SR | `rdd-build` | Current lower evidence; eligible SR in `IN_REVIEW`; selected UR evidence updated independently | | Verify as-built requirement | `rdd-verify` | Current UR upper or SR lower evidence; eligible requirement in `IN_REVIEW` | | Deliver/complete | `rdd-completion-review` | Delivered revision, reconciled records, completion trace, and applied human result | @@ -364,15 +438,15 @@ against the planning fingerprint. Completion review invokes it first against the candidate code before integration, then records a separate result against the delivered fingerprint. It evaluates EC conformance and records engineering trace gates; it does not perform the rest of either review or change lifecycle -state. Approved implementation changes do not stale the planning result because -code is not an input to that result; they require the separate candidate and -delivered results. +state. Approved implementation changes do not replace the fixed reconnaissance +baseline and therefore do not stale planning by themselves; they require the +separate candidate and delivered results. ### AI TDD inner loop -After human entry places the selected scope in `TODO`, the AI owns the automatic -`TODO -> IN_PROGRESS -> IN_REVIEW` transitions. It does not request human input -while the approved fingerprint remains unchanged. +After applicable human entry approvals are applied, the AI owns the automatic +`TODO -> IN_PROGRESS -> IN_REVIEW` transitions and scoped review corrections. +It does not request human input while the approved fingerprint remains unchanged. ```text establish selected UR upper RED @@ -386,8 +460,10 @@ establish selected UR upper RED Run the loop as follows: -1. Establish the expected upper RED for every selected UR requiring new - evidence. A standalone SR has no upper step. +1. Establish the expected upper RED for every selected UR that lacks admissible + historical RED. Reuse retained observations for unchanged clauses/assertions; + do not recreate an already-observed failure for each subsequent SR. A + standalone SR has no upper step. 2. If an SR trace is unmet, select one approved clause, establish its focused lower RED, implement the smallest passing behavior, and perform scoped behavior-preserving cleanup. @@ -396,9 +472,10 @@ Run the loop as follows: 4. Re-evaluate every selected SR lower trace and UR upper trace independently. A trace `FAIL` caused by unmet approved behavior starts another iteration; it does not request human input. -5. Exit to `IN_REVIEW` only when every selected SR lower trace is current and +5. Exit to completion review only when every selected SR lower trace is current and `PASS`, and every selected UR upper trace is current and `PASS` with all of - its required SRs in `IN_REVIEW` or `DONE`. + its required SRs in `IN_REVIEW` or `DONE`. Advance eligible unfinished items + to `IN_REVIEW`; preserve unchanged `DONE` items. Use a reviewable feature branch and preserve RED and passing fingerprints. For `PENDING_VERIFICATION`, demonstrate regression sensitivity with a safe @@ -406,7 +483,8 @@ temporary local mutation or equivalent targeted failure, then restore it. The restored implementation may require no product-code change. If an upper failure remains after all planned SR lower traces pass, diagnose it. -Repeat the inner loop when the failure is within approved behavior. Return to +Use the review-correction route when the failure is within approved behavior, +including when all affected SRs already reached `IN_REVIEW`. Return to the earliest planning pass when satisfying it requires a new or changed requirement, relation, scope, architecture, acceptance rule, priority, release, workflow, or material technical decision. Record an external impediment as a @@ -424,20 +502,34 @@ an explicit incomplete handoff, not completion. ## Evidence and completion -A test result is immutable. Rerunning creates a new result. +A test observation is immutable. Rerunning creates a new result. Reassessment +appends its validity, basis, and time without rewriting the observed outcome, +test, fingerprint, revision, command, or report. + +Roles are `BASELINE_RED`, `SENSITIVITY_RED`, `PASSING`, and `REGRESSION`. +Baseline RED records the expected failure before implementation. Sensitivity +RED records a safe temporary mutation or equivalent targeted failure, plus the +mutation/target and verified restoration. Each run has its own result record. Outcome is `PASS`, `FAIL`, or `SKIP`. Validity is: | Validity | Meaning | |---|---| -| `CURRENT` | Matches the exact clause, content/code fingerprint, and revision | -| `STALE` | A traced input changed after the result | -| `INVALID` | The tested content is unreachable, reverted, abandoned, or not delivered | +| `CURRENT` | Passing/regression evidence matches the required clause/assertion, code/configuration fingerprint, environment, and candidate or delivered revision | +| `RETAINED` | Historical RED remains admissible for its unchanged clause/assertion and expected failure cause at its recorded baseline or mutation fingerprint | +| `STALE` | An input required for this result's role changed without a new run or confirming assessment | +| `INVALID` | The observation or failure cause is unsound, the subject is unreachable, restoration is unproven, or passing evidence claims code that was reverted, abandoned, or not delivered at the required target | | `INHERITED_UNVERIFIED` | Carried from another revision or change without a confirming run | -Only `CURRENT` evidence linked to the exact clause, test case, code/content -fingerprint, and revision counts. Broad suites prove only exercised assertions. -Line numbers are navigation hints, not test identities. +Required RED is an observed `FAIL` assessed `RETAINED`; required passing and +regression runs must be `PASS` and `CURRENT`. A current lower/upper trace contains +both roles; it does not claim that historical RED ran at the delivered revision. +Approved implementation changes, successful GREEN, and verified restoration of +a sensitivity mutation do not invalidate historical RED. Changed clauses, +assertions, or an incorrect failure cause require reassessment and, when the +old observation no longer demonstrates the new target, a new RED. Retention +never substitutes for current passing evidence. Broad suites prove only +exercised assertions. Line numbers are navigation hints, not test identities. | Requirement/evidence | Required evidence | |---|---| @@ -464,11 +556,13 @@ completion gate. Material approved-scope changes stale entry approval and send work back to planning. Delivery may proceed only after the pre-delivery candidate has a current passing -engineering trace. A completion human gate may open only when named items are -`IN_REVIEW`, code is delivered, evidence is current at the delivered revision, +engineering trace. A completion human gate may open only when items named for +new completion transitions are `IN_REVIEW`, code is delivered, passing/regression +evidence is current at the delivered revision, historical RED remains admissible, state is reconciled, candidate relations are excluded, the separate delivered engineering trace is current and passing, and gaps/deferrals/decisions are -disclosed. +disclosed. Unchanged `DONE` members may be referenced as satisfied dependencies +without receiving another completion transition. | Item | `DONE` predicate after human acceptance | |---|---| @@ -509,8 +603,9 @@ file-state/ `EPICS.md` stores optional grouping records. `REQUIREMENTS.md` stores URs, SRs, declared relations, and trace references. `ENGINEERING-CONSTRAINTS.md` stores the flat EC set and its lifecycle. `GATES.md` stores every trace and human gate -record. `WORK-SELECTION.md` stores the frozen scope, suspended selections, and -selection history. `BACKLOG.md` stores unrouted triage items and gap records. +record. `WORK-SELECTION.md` stores the release registry, frozen scope, per-item +holds, adoption campaigns, 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. @@ -525,9 +620,19 @@ Every gate record stores id, kind, transition/purpose, exact scope, prerequisite fingerprint, state/verdict/answer, actor/evaluator, sources, timestamps, application state/revision, and predecessor/successor. -Every evidence record stores targeted clause, stable test case, outcome, role, -validity, command/report, environment when relevant, fingerprint, revision, and -code link. +Every evidence record stores an immutable result id, targeted clause/assertion +fingerprint, stable test case, outcome, role, command/report, environment when +relevant, tested fingerprint/revision, and code link. Its append-only validity +assessments record the assessment target, basis, and time. Sensitivity results +also record the mutation and restoration proof. A delivered confirmation names +the prior run and proves equivalent relevant code/configuration and environment +at the new target; otherwise rerun. A matching file hash alone cannot confirm a +runtime-dependent result. + +When migrating combined RED/passing records, preserve the original observations +and split only facts established by their run reports. Missing per-run metadata +is unverified, not permission to copy the passing revision onto historical RED. +Re-establish evidence when the original observation cannot be recovered. Apply a human answer only when its `ANSWERED` gate fingerprint is current: @@ -539,7 +644,7 @@ Apply a human answer only when its `ANSWERED` gate fingerprint is current: After every transition, update the complete affected graph and run checks for: - valid identities/statuses and reciprocal declared relations; -- stable test identities, revision-pinned validity, and invalidation cascades; +- stable test identities, role-specific validity, and invalidation cascades; - exact gate fingerprints and legal gate/state transitions; - exact applicable active EC sets and current engineering-trace results; - no `TODO` without applied entry approval; @@ -563,9 +668,29 @@ human decisions. | Unclear ownership/cross-cutting concern | Triage backlog | | Contradicted or removed behavior | Conflict or `OBSOLETE` with replacement | -A project's release registry holds exactly one active release, and release -selection requires a `USER:` source. Drift between repository records and the +A project's release registry holds exactly one active release before work +selection, and release selection requires a `USER:` source. After checking the +store binding, reconcile applicable already-answered release decisions before +enforcing that invariant. Validate their recorded prerequisites, registry input +revision, scope, and human source; do not require their intended output (an +active release) as an input prerequisite. Reconcile other answered gates before +selection. Apply answers idempotently; stale answers require successor gates. +Drift between repository records and the store binding is a defect to report, not a variance to work around. `DERIVED` items are not release commitments. Preserve competing authoritative sources and request a human decision; never resolve intent by timestamp or weaken a trace to make records agree. + +### Resumable adoption + +Reverse engineering may start when no requirement corpus exists, or resume a +recorded adoption campaign over explicitly uncovered contexts. Record campaign +identity, original baseline, latest inspected revision, context inventory, +per-context progress, candidate ids, confirmation gates, and remaining work in +work selection. Existing records from that campaign are not a preflight failure. +Skip completed contexts and reconcile partial contexts by stable ids; never +overwrite confirmed content or duplicate a candidate on resume. Revision drift +requires rechecking affected observations. An unrelated established corpus uses +discovery/planning unless a human explicitly authorizes bounded adoption of its +uncovered surface. Adoption coverage counts inspected/dispositioned observations, +not authoritative requirement readiness; `DERIVED` items remain held. diff --git a/README.md b/README.md index 670fdac..9e6d6ac 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,9 @@ apply that model; `file-state/` serializes its records without redefining it. | `AGENTS.md` | shared agent policy and canonical entry point | | `PROCESS.md` | complete canonical process | | `CLAUDE.md` | root compatibility entry required for Claude discovery | -| `skills/` | full-loop orchestration plus focused procedures for discovery, planning, review, building, triage, completion, and as-built verification; a focused flat-EC evaluator (`rdd-engineering-check`); corpus adoption for codebases without requirement records (`rdd-reverse-engineer`); a shared document/citation auditing utility (`rdd-audit`) | +| `skills/` | full-loop orchestration and focused procedures with input/write/exit contracts; a flat-EC evaluator; resumable bounded corpus adoption; a scoped document/citation auditor with optional examples and adapter references | | `file-state/` | canonical serialization shapes for Epic, requirement, engineering-constraint, gate, work-selection, and backlog/gap records | +| `tests/` | executable citation-auditor regressions and lifecycle walkthrough scenarios | ## Distribution @@ -75,4 +76,22 @@ evidence rules, engineering-constraint semantics, or record ownership belong in `PROCESS.md`. Validate internal links and search the skills and flat-file shapes for competing authority statements whenever it changes. +Run executable tooling regressions with Node's built-in test runner: + +```bash +node --test tests/*.test.mjs +git diff --check +``` + +Use [the lifecycle scenarios](tests/process-scenarios.md) to forward-test changed +instructions in a fresh reviewer context. These are behavioral walkthroughs, +not an implemented process-store engine or a substitute for an adopter's +transition tests. Frontmatter/link validation alone cannot establish that the +loop is executable. + +The evidence schema separates immutable runs from append-only assessments: +historical RED may be `RETAINED`, while passing/regression evidence must be +`CURRENT` at the relevant target. Older combined rows need their original run +reports to migrate accurately; do not infer missing historical fingerprints. + License: MIT. diff --git a/file-state/BACKLOG.md b/file-state/BACKLOG.md index 65e71f2..f5add5c 100644 --- a/file-state/BACKLOG.md +++ b/file-state/BACKLOG.md @@ -11,13 +11,14 @@ Neither record type is a requirement. Neither counts toward trace, release, readiness, coverage, progress, or completion. Promotion out of this file always goes through `PROCESS.md` routing — a directly sourced item to `PROPOSED`, an -inferred one to `DERIVED` plus its confirmation gate. +inferred behavior to `DERIVED` plus its confirmation gate, and an observed or +requested engineering rule to `PROPOSED` EC plus its human activation gate. ## Triage backlog Discoveries with unclear ownership or a cross-cutting concern, held until a -human assigns them. An item that already has an owner and a source is a -requirement and does not belong here. +human assigns them. An item whose owner and route are known belongs in the +appropriate requirement, EC, gap, or decision record rather than this backlog. ## BACKLOG-«NNN» — «Discovery in one line» diff --git a/file-state/GATES.md b/file-state/GATES.md index d771c54..36c10e5 100644 --- a/file-state/GATES.md +++ b/file-state/GATES.md @@ -15,11 +15,12 @@ recorded prerequisite trace gate is unreadable, not implicitly open. ## GATE-«AREA»-«NNN» — «Transition or decision purpose» -- **Kind:** trace or human / «confirmation, EC activation/retirement, engineering-check (planning/candidate/delivered), entry, decision, cold-review, start-review, completion» +- **Kind:** trace or human / «confirmation, EC activation/retirement, engineering-check (planning/candidate/delivered), entry, release selection, decision, cold-review, start-review, completion» - **Transition / purpose:** «exact state transition, or the decision being asked» -- **Exact scope:** «named EPIC/UR/SR/EC ids this gate covers; one answer may cover an Epic and named members» +- **Exact scope:** «named EPIC/UR/SR/EC or release ids this gate covers; one answer may cover an Epic and named members» - **Prerequisites:** «gate ids that must be PASS before this one may leave DRAFT, or none» -- **Fingerprint:** «content/code fingerprint the gate was evaluated at» +- **Fingerprint / input manifest:** «kind-specific hash and exact inputs per PROCESS.md Fingerprint ownership; excludes this gate's outputs and application state» +- **Evaluation target:** «PLANNING / CANDIDATE / DELIVERED + target revision and applicable EC ids/versions for engineering gates; otherwise the exact decision/review target» - **State:** «trace: PENDING / PASS / FAIL / STALE — human: DRAFT / OPEN / ANSWERED / CLOSED / SUPERSEDED» - **Verdict / answer:** «trace verdict with exact blockers, or the human answer as given» - **Actor / evaluator:** «real human actor and role for a human gate; evaluating agent or check for a trace gate» diff --git a/file-state/REQUIREMENTS.md b/file-state/REQUIREMENTS.md index a85ca14..4d61940 100644 --- a/file-state/REQUIREMENTS.md +++ b/file-state/REQUIREMENTS.md @@ -22,13 +22,33 @@ ### Trace references -| Evidence class | Target | Code | Test case | RED result | Passing result | Outcome | Environment | Fingerprint | Validity/revision | -|---|---|---|---|---|---|---|---|---|---| -| UR upper | «UR scenario or N/A» | «code refs» | TEST: | RUN: | RUN: | PASS / FAIL / SKIP | «when relevant» | «content/code fingerprint» | CURRENT / STALE / INVALID / INHERITED_UNVERIFIED + revision | -| SR lower | «SR clause or N/A» | CODE: | TEST: | RUN: | RUN: | PASS / FAIL / SKIP | «when relevant» | «content/code fingerprint» | CURRENT / STALE / INVALID / INHERITED_UNVERIFIED + revision | - -The RED/Passing split carries each result's role; `Outcome`, `Environment`, -and `Fingerprint` carry the remaining mandated evidence-record fields. +| Evidence class | Target | Code | Test case | Historical RED result ids | Passing/regression result ids | +|---|---|---|---|---|---| +| UR upper | «UR scenario or N/A» | «code refs» | TEST: | «result ids» | «result ids» | +| SR lower | «SR clause or N/A» | CODE: | TEST: | «result ids» | «result ids» | + +### Evidence results + +Repeat this block for each immutable run; never share one outcome or fingerprint +between RED and passing results. Store-backed serializers may reference the +equivalent complete result records by id. + +- **Result id / role:** «id» / BASELINE_RED / SENSITIVITY_RED / PASSING / REGRESSION +- **Target / assertion fingerprint:** «exact clause or scenario and exercised assertions» +- **Test case / code:** TEST: / CODE: +- **Observed outcome:** PASS / FAIL / SKIP +- **Command / report:** RUN:«exact command and preserved report» +- **Environment:** «relevant configuration/runtime, or N/A with basis» +- **Tested fingerprint / revision:** «code/configuration fingerprint and revision; record patch/tree fingerprint for an uncommitted mutation» +- **Expected RED cause:** «assertion and observed expected failure, or N/A» +- **Mutation / restoration:** «sensitivity mutation/target, original fingerprint, restored fingerprint and verification; N/A for other roles» + +Validity assessments are append-only; changing an assessment never rewrites the +observation. Historical RED uses `RETAINED`, not a claim of passing at delivery. + +| Assessed at | Validity | Assessment target/revision | Basis / confirming run | +|---|---|---|---| +| «timestamp» | CURRENT / RETAINED / STALE / INVALID / INHERITED_UNVERIFIED | «baseline or candidate/delivered fingerprint + revision» | «role-specific basis and direct evidence; prior run/equivalence proof when confirming at a new target» | ### Gates and delivery @@ -38,4 +58,6 @@ and `Fingerprint` carry the remaining mandated evidence-record fields. - **Engineering-check gates:** «GATES.md gate ids» - **Completion gates:** «GATES.md gate ids» - **Delivered revision:** «repository + revision or not delivered» +- **Review corrections:** «direct finding, unchanged approval, affected items, correction boundary, required reruns, and resolution; or none» +- **Hold history:** «WORK-SELECTION.md per-item hold/restoration rows, or none» - **Gaps / deferrals / blockers / notes:** «refs or none» diff --git a/file-state/WORK-SELECTION.md b/file-state/WORK-SELECTION.md index 2a21270..a9856b6 100644 --- a/file-state/WORK-SELECTION.md +++ b/file-state/WORK-SELECTION.md @@ -9,6 +9,16 @@ - **Source store/revision:** «database revision or repository SHA» - **Active release:** «release with its USER: source» +## Release registry + +Record every release and its selection decision; the active-release field above +references the sole active row after reconciliation. Apply current answered +release decisions before checking that exactly one row is active. + +| Release id | State | USER source | Selection gate | Applied at revision | +|---|---|---|---|---| +| «id» | INACTIVE / ACTIVE | «attributable release decision» | «gate id» | «application revision or pending» | + Work selection is authoritative state, not a derived queue. It records which scope is frozen, at which fingerprint, and which phase it is waiting on. Rendered queues, progress counts, and dashboards are regenerated from the @@ -22,18 +32,37 @@ authoritative store and are not recorded here. - **Frozen at fingerprint:** «selected content/scope fingerprint; EC applicability is unresolved here» - **Reconnaissance revision:** «named revision the packet was authored against» - **Applicable EC set / fingerprint:** «unresolved before reconnaissance; then exact active EC ids and set fingerprint» +- **Planning inputs / fingerprint:** «manifest and hash per PROCESS.md; excludes attached review outputs» +- **Review attachments:** «engineering/cold-review result ids, findings/dispositions, independent reviewer context and reviewed fingerprint» +- **Entry fingerprint / approvals:** «planning + prerequisite review references; per-item current applied approvals, not a shared lifecycle status» +- **Next action:** «affected item subset, next legal action, required inputs, and exact resume condition» - **Current phase:** «source / plan / cold review / entry / build / verify / completion / triage» - **Waiting on:** «gate id, blocker, external prerequisite, or nothing» - **Owner:** «who holds the selection» ## Suspended selections -One row per scope held at `BLOCKED` or `DEFERRED`, so the suspended state is -recoverable rather than inferred. +One row per held item, including differently progressed members of a held +scope. Preserve each suspended-from state and its gate/evidence basis. Append +release facts after reassessment; do not overwrite the prior-state record. -| Scope | Suspended status | Restored-to status | Reason | Owner | Target | Blocker/gate | +| Scope / item | Hold state | Suspended from / basis | Held at | Reason / owner / target | Blocker/gate | Released at / restored to / basis | |---|---|---|---|---|---|---| -| «EPIC/SR id» | BLOCKED or DEFERRED | «strongest state supported when released» | «reason» | «owner» | «target» | «gate id or ref» | +| «scope id / EPIC, UR or SR id» | BLOCKED or DEFERRED | «prior lifecycle status + gate/evidence refs» | «timestamp/revision» | «reason, owner, target» | «gate id or ref» | «pending, or timestamp/revision + strongest supported state + current gate/evidence refs» | + +## Adoption campaigns + +Adoption progress is not release commitment or delivery readiness. Retain the +campaign after a context is handed to the normal loop, so another context can +resume without re-deriving confirmed records. + +- **Campaign id / authority:** «stable id and USER request authorizing the bounded surface» +- **Original baseline / latest inspected revision:** «repository revisions» +- **Context inventory / scope:** «explicit context set and inventory references» + +| Context | Progress | Observation keys / candidate ids | Confirmation gates | Remaining work / next action | +|---|---|---|---|---| +| «context id» | NOT_STARTED / PARTIAL / AWAITING_CONFIRMATION / HANDED_OFF | «stable source/behavior keys and exact candidate ids» | «gate ids» | «uncovered observations, drift to recheck, or next delivery pass» | ## Selection history diff --git a/skills/rdd-audit/SKILL.md b/skills/rdd-audit/SKILL.md index 460f575..5fc285c 100644 --- a/skills/rdd-audit/SKILL.md +++ b/skills/rdd-audit/SKILL.md @@ -1,964 +1,80 @@ --- name: rdd-audit -description: Audit a document corpus, a ledger, or a check you wrote — resolve citations, diff an inventory both directions, judge a count, and recognise how an instrument fails. Use when verifying that documents still describe the code, when a measurement disagrees with a previous one, when writing or widening a matcher, or when deciding whether a finding is real before acting on it. A shared utility invoked by rdd-reverse-engineer, rdd-cold-review, and rdd-completion-review — it establishes facts and findings, routes them through rdd-triage, and never assigns lifecycle state. +description: Check document and record claims against code, resolve citations, compare inventories in both directions, and validate measurement coverage. Use from cold review, completion review, or adoption, or for an explicitly requested document/instrument audit. Produces sourced findings without assigning lifecycle state or granting approval. --- -# Auditing what a document claims - -A shared utility, not a phase. `rdd-reverse-engineer` invokes it before -claiming coverage, `rdd-cold-review` and `rdd-completion-review` invoke it to -verify citations and inventories against the code, and any pass correcting a -stale claim may load it alone. Its contract: - -- it establishes facts about documents, records, and the instruments that - check them; it never assigns or advances a lifecycle state; -- its findings are discoveries — route them through `rdd-triage`, which owns - where they land; -- invoked inside the loop it is scoped to the affected surface; the - full-corpus sweep is a deliberate act — at adoption, before a release, or - when `rdd-start` reports drift — never an every-iteration cost. - -Read the project `AGENTS.md` and the canonical `PROCESS.md` -(`.modernpath/rdd/PROCESS.md` in a consuming repository) for the authority -and reconciliation rules these checks serve. - -Two sections, and they answer different questions. **Citations** asks *does this -reference resolve* — mechanical, and the place a pass first writes a checker. -**The audit** asks *is this document still true, and is my check trustworthy* — -which is mostly about not believing your own instrument. - ---- - -## Citations — does every reference resolve? - -Every `file:line` you wrote must resolve. This is mechanical, it takes seconds, -and it is the cheapest guard against a ledger that reads well and points nowhere. - -**Run the script; do not re-derive it.** - -```bash -# beside this skill; installed at .modernpath/rdd/skills/rdd-audit/ in a consuming repository -node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs # docs/ + ARCHITECTURE.md -node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs tasks # or any root -``` - -It sweeps both citation shapes over every markdown file under the given roots, -resolves by path suffix against `git ls-files`, flags elided paths, excuses named -gaps, and exits non-zero on failure. The rest of this section explains *why* each -of those rules exists — read it when the output surprises you, and when you are -tempted to write your own. - -**That temptation is the point.** This procedure was re-derived by hand five times -in one session and was wrong three of those. Each rewrite looked correct and -produced a confident number. The rules below are the scar tissue: - -**Skip named absences.** A row that says *"no test — there is no -`test_draft_service.py` in the repository"* is naming a gap, which is the most -useful thing a derivation pass produces. It is not a citation, and an audit that -counts it as broken teaches the next pass to stop naming what is missing -(8 of 8 "broken citations" in a 3,222-citation ledger were -absences stated correctly). Ignore any reference preceded by *no*, *missing*, -*there is no*, or *does not exist*. - -**And resolve paths properly before reporting a failure.** A citation written -`db/models.py:1483` may live six directories deep; a bare `admin.py` may match two -files, only one of which is long enough. Match on path suffix, accept if **any** -candidate satisfies the line, and search the whole repository rather than one -subtree. Three separate audit scripts written in one session each reported the -ledger as broken when the resolver was at fault. Prove your -checker on a citation you know is good before you trust its failures. - -### How an instrument fails, and in which direction - -#### Reading what the instrument told you - -**A near-total failure rate is a broken instrument, not a discovery.** Before -believing a result, ask what fraction of the population it condemns. A check that -indicts 100% of anything has found a convention it does not understand. Near 10% -the balance tips and a finding becomes likelier than a bug. The converse holds: a -check that passes *everything* on its first run has usually matched nothing — -which is why a new check earns trust by being made to fail on purpose. - -**A module path is not a table name.** A `compliance/` directory prefix is not -part of `schema "change_plan_messages"`. **Read the declaration, never the -filename** — the `schema "…"` line for Ecto, `__tablename__`/`@Table` for an ORM, -the DDL for SQL. - -**A shell utility that fails on your data reports an empty result, not an error.** -`sort` exits on non-UTF-8 bytes and swallows its input; the pipeline prints a -clean, wrong, empty answer. Run text sweeps under `LC_ALL=C`, and treat *"none -found"* with the same suspicion as a 100% failure rate. - -**An overstated gap makes the wrong decision for you.** A gap measured at 52 -files across four subtrees was filed as too big to fix; re-measured with the -population defined it was 12, and closed the same day. **Audit the numbers that -license inaction hardest** — an overstated gap buys a permanent deferral, while -an understated one is corrected the moment someone starts work. - -**A count with an undefined population is not a measurement.** One question — *how -many cited tests name their requirement?* — gave 12, 35, 45 and 123 across four -bug-free runs, differing only in what counted as a cited test. **State the -population next to the criteria**, and when a re-measurement disagrees with a -recorded one, **suspect the population before the matcher**: a factor-of-ten -spread is what a definition disagreement looks like. - -**Report the instrument's count and the verified count separately.** "35 -mismatches" implies each was inspected. Say *upper bound*, and name the ones that -were. - -#### Changing a matcher - -**Widening a matcher to fix a false negative is the moment to write the -false-positive test** — not after. Each widening below let something through that -must not pass, and every one was caught by adding the negative case, none by -re-reading the regex: - -| Widened to accept | Also accepted, wrongly | -|---|---| -| `APPROVED` anywhere on a line | `SPEC-APPROVED` — a different gate | -| any heading containing "approval" | `## Pre-approval audit` | -| every line of a section | rows belonging to the other gate | - -A widened pattern reads as *"now it accepts X"*; the question that matters is -what **else** it accepts. Write the shape you must still reject, and watch it fail. - -**Two implementations with *different* fixtures beat either suite alone.** A -parity mirror or a port is justified as a compatibility requirement; its quieter -value is that its tests were written by someone solving the same problem from -another angle, so it holds fixtures the original never thought to write. When a -widening left one suite green and failed the mirror on a case the first lacked, -the disagreement was the only detector. **Run the mirror's tests before your own -conclusions**, and copy back the fixture that surprised you. - -**A widening that clears more than you expected is a warning, not a result.** If a -change fixes more cases than the one you were chasing, find out which extra ones -moved before booking them. - -**Be most suspicious when the new behaviour is a pass.** A false negative annoys -someone; a false positive silently asserts that a thing was checked. - -#### When the tool says nothing, or says it sideways - -**Do not let display truncation become evidence.** A check that printed cited -lines through `cut -c1-46` made six correct citations look wrong. When a check -disagrees with a document, **widen the view before you edit** — print the whole -line, and confirm with a second method sharing no code with the first. Agreement -between `grep -n` and `awk` means something; agreement between a script and its -own truncated output means nothing. - -**Read a checker's first run as a test of the checker, not of the corpus.** Four -checks each found a bug in *themselves* on first contact with real data: an -exemption applied before resolving (skipped 116 resolvable citations), an elision -gate whose hits were prose, an escape fix that collapsed counts 2220 → 483, an -anchor matcher that mangled its own character class. None was found by re-reading -the code. Budget the first run for debugging the instrument. - -**Silence is not agreement.** A command producing *no output* has not been shown -to have run — a fixture using an unscanned extension reports `0/0`, a gate run -from the wrong directory reports *"checks pass"*, a command inside a broken `&&` -chain never executes. Verify the **effect**, not the exit code, and never pipe to -`tail`: the last two lines of no output are no output. - -A gate proves what it checks, never that it was reached. - -#### Which direction to distrust - -**Both directions of failure happen; only one is self-correcting.** - -| | What it does | How it ends | -|---|---|---| -| **False alarm** — a regex matching `.ex` inside `.exs`; a truncated display | sends you *to* the evidence | caught within minutes — you open the file to fix it and the code disagrees | -| **False pass** — a sweep over a subset reporting `56/56`; an exemption applied too early | sends you *away* from the evidence | survives until something unrelated exposes it; several persisted for months | - -Budget suspicion asymmetrically. A false alarm costs one round trip and pays for -itself. A false pass costs nothing today and everything later, and **nothing in -your own workflow will surface it** — the only reliable detectors are a second -implementation and a number that does not match the change. - -**Stop when the evidence explains *why*, not when it establishes *that*.** A -finding whose meaning changes as you look closer has not finished changing. One -sequence ran: *93 rows stranded on the server* → *but the ids are in `tasks/`* → -*so the builder is dropping rows* → *they were retired deliberately, and the data -model says so in its own headings*. Each reading pointed at a different action; -the one that held explained why the state exists. **The most alarming reading was -third of four, and it was wrong** — alarm is not evidence of depth. - -**Splitting findings into "certain" and "ambiguous" puts the danger in the wrong -bucket.** The ambiguous pile gets read carefully because you cannot act on it; the -certain pile gets *acted on*, so a detector error inside it goes straight into an -edit. → **Before acting on the confident half, re-derive it once with a stricter -matcher.** If the count drops, your certainty came from the tool. - -#### What the pattern never saw - -**Count what your pattern did *not* match — a loose pattern does not over-report, -it stops looking.** Citations use a grammar: `file:N`, `file:N-M`, `file:N,M`, -`file:N-M,P`. A pattern matching `file:(\d+)` matches all of them and reads only -the first number, so `466,480` scores as one citation and 480 is never examined. - -1. **Write down the grammar before the regex.** If the data has lists, ranges or - optional parts, enumerate them. -2. **Measure coverage, not just hits.** If a line holds four numbers and your - matcher reports one, that gap is the finding. - -**Report the result as a fraction (`140/140 resolve`), and make the denominator -the whole corpus.** A pass that greps only its own `CODE:` prefix measures the -citations it thought to prefix — one corpus reported `56/56` while carrying 11 -bare `` `adapter.ex:48` `` references no audit had ever seen. Sweep both shapes, -resolving bare ones by path suffix against the tracked file list. -### What the sweep must cover, and what it still cannot tell you - -**And sweep every document, not the ones this pass wrote.** The subset error -recurs one level up, and it is easy to miss because each fraction looks complete. -The same corpus reported `56/56` over the derived set, then `67/67` once bare -references were counted, then **`119/123`** once the sweep covered `docs/**` — and -the four failures were in a document no earlier audit had opened, because it was -not part of the derived set. They were **elided paths**: `CODE:.../ai/proxy.ex`, -written by an author who knew where the file was and left the reader a citation -that resolves to nothing. - -```bash -grep -rn 'CODE:\.\.\.' docs/ # elided paths — expect no output -``` - -Elision is worth a check of its own because it is invisible to a reader and to a -naive resolver alike: the line *looks* like a citation, and a suffix-matching -audit can even accept it if it strips the dots. Write the path from the repository -root, every time. - -**Resolving is not being right, and the gap between them is where rot lives.** -Every check above is structural: the file exists, the file is long enough. A -citation can pass all of them and point at a line that no longer says what the -claim says. a corpus reported `50/50 resolve` while one -document quoted a per-file cap of 15,000 characters that applied only when the LLM -proxy was **off** — the code selects between two budgets, and the smaller one -(8,000) governs any deployment using the proxy. The citation resolved perfectly. - -**So cite the line that carries the claim, not the definition that contains it.** -That document cited a function's `defp` head while quoting an expression three -lines inside it. Pointing at the head is what let the quote go unchecked — nobody, -human or script, could compare the claim to the cited line, because the cited line -was a signature. When you quote or paraphrase a specific expression, cite *its* -line; reserve the head for claims about the function as a whole. - -Then one more check becomes possible and worth running: **for every citation where -the prose quotes code, assert the quoted text appears in the cited range.** That is -still mechanical, and it catches the drift the existence check cannot. - -**A token-overlap heuristic is a triage list, not a gate.** Comparing symbols named -in the prose against tokens on the cited line flagged 14 of 52 citations in that -same corpus; 13 were false — the surrounding text was a table, and the "prose" was -a neighbouring cell. One was real, and it was the one above. Run it to decide what -to read, never to decide what to report; a checker with a 93% false-positive rate -teaches the next pass to ignore it. - ---- - -## The audit — is the document still true, and is your check trustworthy? - -Everything below applies to any document this process produces or inherits — a -recovered design corpus, an adopted `ARCHITECTURE.md`, per-context documents, -and the serialized process records. It is written as one section because these checks are a single -discipline, not per-document advice. - -**Adopting is not accepting. Audit what you adopt.** A maintained document is -maintained *as of some date*, and the code moved after it. Before you adopt one, -run at least one **countable** check — a set the document enumerates against the -same set in the code: - -- tables it lists versus `__tablename__` declarations -- services it names versus what the compose file and CI actually build -- endpoints it documents versus the routers on disk - -On one real corpus that check took two commands and found a `Database Schema` -section that was **6 tables short of the code and named 2 that had been dropped** -— the document was edited 2026-06-10 while the services changed through -2026-08-03. A reader trusting it would have looked for a table the migrations had -deleted. - -Then **extend it in place** with what you found, and say in the document that you -did, with the date and what you reconciled against. A silent correction leaves -the next reader unable to tell which parts have been checked. - -**Two commands that do this well:** - -```bash -grep -oE '__tablename__ = "[a-z_]+"' | sed 's/.*"\(.*\)"/\1/' | sort -u > /tmp/real -# extract the same set from the document, then: -comm -23 /tmp/real /tmp/doc # in the code, undocumented -comm -13 /tmp/real /tmp/doc # documented, no longer real — the sharper finding -``` - -### Groundedness first, then correctness - -**Measure groundedness before you measure correctness — `0 broken` on `0 -citations` is not a pass.** A citation audit reports what it found wrong among -the claims that can be checked. A document that cites nothing cannot be wrong by -that measure, and will score perfectly. - -Run this first, and read it as the headline result: - -```bash -docs=$(ls docs/*.md docs/**/*.md 2>/dev/null | wc -l) -cited=$(grep -rl 'CODE:' docs/ 2>/dev/null | wc -l) -echo "$cited of $docs documents carry at least one citation" -``` - -Measured across three real corpora, which is the range to -expect: - -| Corpus | Documents | With ≥1 citation | Citations | -|---|---:|---:|---:| -| derived by this process | 26 | 22 (84%) | 1,003 | -| a workspace's own design docs | 48 | 9 (18%) | 48 | -| a repository with 21,410 code files and no derivation pass | 36 | **0 (0%)** | **0** | - -The third is the case to recognise. Thirty-six documents describing a substantial -system, none of them making a single checkable claim about it — and the citation -audit returned "0 broken", which looks like health. - -**Audit a document's own status tags — they are a promise, and they are -machine-checkable.** A design document that marks each item **EXISTS** or -**TODO**, **built** or **planned**, is making a per-claim assertion far sharper -than its prose. That tagging is usually the most useful thing in the document and -the least maintained. - -One audited API contract carried 69 endpoints tagged EXISTS: **five -were not in the router at all**, their only matches being LiveView modules rather -than JSON routes. A team building a frontend against that contract would have -discovered the gap at runtime. The other 64 EXISTS tags and all 65 TODO tags -held — so the document was 96% right, and the 4% was concentrated exactly where a -reader would act on it. - -Note which question actually paid. The first comparison asked *"is this endpoint -in the router?"* and returned 40 of 138 missing — mostly the document's own TODOs -and scope-prefix noise. The useful question was **"is this document's claim about -itself true?"**, which is answerable, small, and directly actionable. - -**Design documents and derived documents are different genera, and the audit only -speaks to the second.** A document written *before* the code says what should be -true; a derived document says what is. Do not "fix" the first kind by hanging -citations on it — check instead whether the system it describes was ever built -that way, and record the answer as findings. An uncited design corpus over a -large codebase is a **drift question**, not a formatting defect. - -**Expect the ratio to be alarming and mostly fine.** A "documents with zero -citations" query over one workspace returned **48 of 57**, and -nearly all of them were right to have none — API contracts, a ubiquitous-language -glossary, feature specs, product positioning. Sort by genus before you react, or -the number will push you into hanging citations on documents that precede the -code, which destroys what they are for. - -**Among the derived ones, though, uncited predicts wrong.** In the same corpus the -grounded derived documents had almost no defects; the one derived document with -**zero** citations had three, all in the direction that misleads someone doing -real work — two endpoint paths listed without the mount prefix they are registered -under, and two endpoints missing entirely. Nobody had checked, because there was -nothing to check against. Ground a derived document and you are not tidying it; -you are running its first test. - -**And watch the citation form itself.** Two citations in that corpus were written -`CODE: path` with a space, which every checker's regex missed — so they were never -verified by anything, in a document that declares itself *generated from code*. -One of the two resolved only by suffix; the path as written did not exist. A -convention followed 130 times and broken twice is broken invisibly. - -### Citations: whole, rooted, and re-checked - -**A citation must resolve, which means it must be whole.** Never elide a path. -`CODE:.../job_processor.py:265` reads tidily and is worthless: a reader cannot -open it and a checker cannot verify it. Write the repository-relative path in -full, every time, however long. - -Run the shipped audit over the corpus before you call a pass done — it reads -every citation in every document in seconds: - -```bash -node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs # docs/ -node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs tasks epics process -``` - -**This section used to carry an inline Python re-implementation, and removing it -is the point.** That snippet swept `docs/**` -only, matched the `CODE:` prefix and not bare `` `file.ex:12` `` citations, held -two line-parts so everything after the second in `file:N,M,P` was invisible, and -closed by claiming *"a failure is always real"* — which five separate false alarms -in one session disproved. Every one of those flaws was fixed in the script and -would have had to be fixed again in the snippet. - -A skill that tells a reader to hand-roll the check it also ships is not offering -a choice; it is offering the version nobody maintains. - -### The runbook, the published copy, and being findable - -**Audit the runbook — the lines a reader will type.** Ports, URLs, commands and -env var names are the most checkable claims in any document and the most -expensive to get wrong: a wrong architecture paragraph misleads, a wrong port -wastes an afternoon and gives no clue whether the reader broke the setup or the -document is lying. - -Take every URL and port the document names, and confirm each against what the -compose file or deploy config actually **publishes** — not what a service listens -on internally. The two are different, and documents routinely conflate them. - -One *Access points* table listed `http://localhost`, -`http://localhost:8082` and `http://localhost:8083/health`. The compose file -published exactly two ports and the strings `8082` and `8083` appeared **nowhere -in it**; both application services declared no `ports:` at all. Every URL but the -database was fiction, and the service sections above repeated it. - -A one-line check catches this class: - -```bash -grep -oE 'localhost:?[0-9]*' | sort -u # what the document promises -grep -oE '"[^"]*:[0-9]+"' docker-compose.yml # what is actually published -``` - -**"Listens on" is not "reachable".** A service with an internal port and no -published mapping is reachable only from inside the network. Say which it is — -the reader's next command depends on it. - -**Audit the published copy, not just the file.** A document that syncs to a -platform now exists twice, and the copy a reader opens is the remote one. Sync is -usually hook-driven and fire-and-forget, so a failed push leaves the repository -right and the platform stale with nothing raising a hand. - -Compare the far side after a pass. Content length is enough to catch drift -without pulling every document back: - -```sql --- the shape of the check, against whatever table holds the synced copies -select external_id, length(content) from system_documents -where external_id is not null order by external_id; -``` - -then diff those against the files on disk. In one checked sync, 21 documents -across two systems matched exactly — which is the result to expect and worth -recording, because the interesting version of this check is the day it does not. - -**The failure this guards against is silent by construction.** Earlier the same -day, a server rejecting *every* batch produced no error anywhere a person would -see it; the workspace looked fine and the platform was hours behind. A length -comparison would have shown it in one query. - -**A document nobody can find has not been written.** After producing or -superseding anything, update the corpus index — usually `docs/00-overview.md` or -a README table — and then check the index against the filesystem: - -```bash -ls docs/*.md | xargs -n1 basename | while read f; do - grep -q "$f" docs/00-overview.md || echo "unlisted: $f" -done -``` - -One pass had written five documents, five ADRs and a guide, -synced them, and audited them — and listed **none of them** in the index, which -still routed readers to a document the same pass had marked superseded. The -corpus was correct and unreachable. - -**An index row carries a status, not just a name.** *Exists* and *is in force* -are different facts, and a filename alone conveys the first while implying the -second. One index gained nineteen rows; reading each document -to write its row revealed that one was a **tombstone** — its content had moved -months earlier and the file remained as a pointer — and another was an explicit -**proposal that was never ratified**. A reader meeting either as a bare filename -would have taken it for current guidance. - -Use a status vocabulary that distinguishes them — `derived`, `decided`, -`proposed`, `superseded`, `moved`, `living`, `operational`, `report` — and take -each row's purpose from that document's own heading and opening line. That keeps -the job mechanical enough to finish, and stops the index asserting things the -documents do not. - -Two cautions from running it. **Wildcard rows are invisible to exact matching**: -a `10–17 | 10-.md …` row legitimately covers eight files, and the naive -check called all eight missing — inspect the hits. And **listing is not -describing**: adding a filename with no one-line purpose makes the index longer -without making the corpus navigable, so where a backlog of unlisted documents -exists, record it as editorial work rather than padding the table. - -### Staleness travels in groups - -#### Enumerate, then diff - -**Audit an inventory against the thing it inventories.** A document that lists -*what exists* — a structure table, a context register, an endpoint table — makes -a claim no other check can reach. Citations resolve, counts match, prose stays -coherent, and the list is still missing things it purports to enumerate. Nothing -contradicts an absence. - -Three instances in one session, each found this way and by -nothing else: - -| Inventory | Checked against | Found | -|---|---|---| -| `00`'s repository structure table | the filesystem, and `AGENTS.md` | **4 of 9 subtrees missing**, including the decided auth boundary | -| an overview's *"the pass seeded one context"* | `ls tasks/*-REQUIREMENTS.md` | **eleven** ledgers existed | -| an architecture note's endpoint table | the router's mount block | 2 paths wrong, **2 endpoints absent** | - -**Enumerate the real thing, then diff the document against it — not the reverse.** -Reading the document and checking each entry exists finds *wrong* entries and -never *missing* ones, and missing is the half that rots. - -**"The real thing" is itself a derivation, and the first one is usually wrong.** -Enumerating one estate's tables gave **171** from `create table(`; the -real figure was **112** once 59 drops and 7 renames were applied — a 53% -overstatement that would have manufactured a finding. And 112 was still the wrong -denominator: the document claimed to map *Ecto schemas*, of which there were -**148**. **Enumerate the population the document claims to cover, not the one -that greps most easily** — and for anything with a history (migrations, changelogs, -event logs) the current set is creations *minus removals*, never creations. - -**A missing entry has three degrees, and only one is a defect.** Reporting all 49 -omissions as "undocumented" would have been false and would have buried the part -that mattered. Separate them before writing a word: - -1. **In a sibling document, absent here** — the two disagree; say which is more - current (29 of them; `02` was ahead of `40`). -2. **Specified elsewhere, but in no index or map** — the spec exists and the map - never caught up; point at its real home rather than restating it. -3. **Nowhere at all** — the only genuine gap. Six of the forty-nine, and the - only ones worth a requirement. - -Collapsing these into one number is how an audit becomes noise: a reader who -checks two entries, finds them documented elsewhere, and stops will discount the -whole finding — including the six that were real. - -#### Scoping an audit honestly - -**Before writing "not checked in this pass", price it.** Once a -banner was written saying the reverse comparison had not been run — and running -it took one command and found two real errors. The known-unknown note exists for -audits that would be **expensive or noisy**, not for ones a minute would settle; -used as a default it turns honest scoping into a licence to skip. The test is -cheap to apply: if you can state precisely what the check would be, you are -usually already most of the way through doing it. - -**Sometimes the right call is not to run the diff — then say what that leaves -unchecked.** A realtime catalog scoped itself to *cross-context* events; the code -carried 59 message atoms across 111 broadcast sites, most of them intra-context -progress. Diffing those would have produced dozens of false gaps and taught -everyone to ignore the next audit. Declining was correct. **Silence about it was -not** — the document now names the narrower claim nobody has verified: that the -catalog is *closed*. An audit you chose not to run is a known unknown, and -writing it down is the difference between scoping and quietly implying coverage. - -**An audit banner suppresses the next audit, so record which direction it ran.** -One API contract carried a prominent *"Tag audit: -69 endpoints tagged EXISTS, of which 5 are not in the router"* -— specific, honest, recent, and one-directional. It checked each documented entry -against the router and never the router against the document. Enumerating the -router found **264 routes across 42 prefixes** and a *"read first"* paragraph -claiming six whole contexts had **no JSON API**; all six had one, and the -controllers predated the audit by a month. The banner is why nobody looked: a -document that says it was verified reads as verified. **Write what was compared -against what** — *"every EXISTS tag checked against the router; the router was -not checked against this document"* — so the next pass knows which half is -unexamined instead of inferring both. - -**And weigh the two directions differently.** A documented-but-absent entry fails -loudly the first time someone calls it. A real-but-undocumented one fails as -silent duplicated work — six contexts' worth of backend scheduled that already -existed. The cheap direction is the one that gets audited; the expensive one is -the one that needs you to enumerate. - -For an HTTP inventory specifically, three things make the enumeration wrong if -you skip them: parse **scopes** so nested prefixes compose into full paths, drop -**comment lines** (a commented-out route reads as live), and remember that a -framework's non-verb route macros — Phoenix's `live`, mounts, forwards — are not -matched by a verb regex and are not JSON either. Getting any of those wrong -changes the count by enough to invent or hide a finding. - -#### After you correct something - -**A stale claim is rarely alone — grep for it after you correct it.** A document -states the same fact in more than one place: once in prose and once in a "key -patterns" list, once in an overview and once in a table. One -wrong migration mechanism was corrected in a schema section while an identical -claim sat forty lines below in a patterns list, and shipped uncorrected. After -every fix, search the corpus for the distinctive phrase and the thing it names. - -**A correction banner is not a correction, and it is worse than none.** A -runbook was once corrected by adding a *"superseded in part"* note -to the section that described a removed credential — and two later paragraphs in -the same file went on describing it as in use, one of them in the Notes section a -reader actually lands on. The banner made the document look maintained, which -made the surviving stale claims look reviewed. Either correct every instance or -don't signal that you did. - -**And the fact escapes the document.** The same retired credential was still -being handed to operators by `.kamal/secrets-common.example`, which told them to -mint it and set it. A fact lives in prose, in the example config that -operationalizes it, in templates, and in the code that reads the variable — and -correcting the prose is the easiest of the four. After correcting a fact, grep -the **repository** for the identifier, not the document for the sentence. - -That sweep is also where the real finding usually is. Chasing which of two -contradictory paragraphs was true is what surfaced that the credential was gone -from the *deployment* and still live in the *code* — a difference neither -paragraph stated, and a better result than picking a winner between them. **Treat -a document contradicting itself as evidence about the system, not as a typo.** - -**Audit the whole document, not the section you came for.** The same pass that -found the schema section six tables short stopped there — and the service -sections, unexamined, said "four job types" where the enum had five. A document -drifts uniformly; a section that is stale is evidence about its neighbours, not -an isolated defect. - -**And be as ready to be wrong as the document is.** In the same audit the pass -suspected `google-genai SDK directly` was stale, because the dependency manifest -listed `spaik-sdk`. Both were true: the backend goes through `spaik-sdk`, the -worker imports `google.genai` directly, and the *pass's own* document was the one -with the incomplete claim. Check the code before correcting a document — the -existing text may be recording something you have not found yet. - -#### Claims that age without being edited - -**A claim with a shelf life carries the moment it was true.** Counts, hashes, -versions, "currently N" — these are measurements, not facts, and a document that -states them bare will be wrong without ever being edited. - -Two ways to keep them honest, in order of preference: - -1. **Write the invariant, and let the instance illustrate it.** *"A credential - ping and a generation call carry different timeouts by design"* survives a new - vendor; *"six of the eleven adapters use 10 seconds"* does not. -2. **Where the number is the point, date it** — `RUN:` in the same - sentence, and say which direction it moves. *"88 of 132 paths resolved when - measured, a figure that falls as checkouts are pruned"* tells a reader both - what was true and how to think about it later. - -A byte-hash asserting that three files are identical is the sharp case: the -durable claim is *"a lockstep test fails if they drift"*; the hash is evidence for -a moment and needs its date beside it. - -**Traceability is bidirectional, and only one direction is ever checked.** A -ledger row citing a test is audited to death — the path resolves, the line exists, -the extension is right. Whether the **test** names the requirement is checked by -nothing, and that is the direction that survives the ledger being reorganised: a -test carrying `REQ-PLN-061` in its `describe` can be traced back from the code -even if every ledger row is rewritten. - -Measure it, because the number is not what you expect. On one measured workspace, -**129 of 181** cited test files named a requirement that cites them — **52 did -not**, in a workspace whose ledger citations were 100% resolving. - -Two cautions, both learned by getting it wrong. **Recognise a test by its path or -suffix**, never by the word *test* appearing in a filename: the first run counted -58 because it matched `specification_pipeline.ex` and `test_execution.ex`, which -are implementation. And **naming any one covering requirement is enough** — a test -covering three requirements does not need three ids, and demanding that turns -traceability into bookkeeping nobody maintains. - -**Abbreviation is how a correct citation becomes a broken one.** Sweeping 1,359 -file paths across a workspace's ledgers found **two** -failures, and neither was stale — both were *shortened*. One dropped a directory -segment (`controllers/app_token_controller.ex` for -`controllers/auth/app_token_controller.ex`); the other dropped a filename prefix -(`bridges_test.exs` for `work_events_bridges_test.exs`), in a sentence that had -just named `bridges.ex`, so the shorthand read naturally to whoever wrote it. - -That is the failure mode to expect in a mature corpus. A citation is rarely wrong -because the file moved — a move breaks a build. It is wrong because a writer -mid-sentence wrote the part a human reader would need and dropped the part a tool -needs. **Paste paths; never retype them**, and be most suspicious of the second -mention of a file, where the writer already has the context and the reader of the -tool does not. - -**Every citation resolves from the repository root**, not from wherever the code -felt close. In a monorepo with vendored subtrees this is not pedantry: in one measured workspace -**19 of 40** citations in one workspace's documents were written -relative to a subtree — `apps/core/lib/...` — which exists only under the -vendored subtree's own root. They read correctly beside the code and -cannot be opened by a reader at the root, which is where the document lives. - -Set the checker's working directory to the repository root and let it fail -loudly; a citation that only resolves after a human guesses the prefix is not a -citation. - -### Your own workspace, and documents against each other - -**But not on the prompts — and the tool now refuses.** Teaching material quotes -citations that must not resolve: an elided path shown as the thing *not* to -write, a template row citing `domain/user.ts`, a finding cited from the -repository it was found in. Pointed at `.claude/` or the installed `.modernpath/rdd/skills/` the audit reports broken and -elided paths that are all correct prose, and the obvious next move deletes the -lesson. It exits 2 with an explanation instead; `--force-prompts` overrides it for -a reader who will inspect every hit. - -That refusal was priced before it was written, not assumed: **132 backticked -paths across the prompts, 21 unresolved, all 21 legitimate** — templates, other -repositories, and paths the skill *instructs a pass to create*. Four looked like -real errors until opened. Nothing was being missed. - -**Run this audit on your own workspace, not only on the one you are analysing.** -The same pass that fixed elided citations in a client repository had left three -of them, and nineteen subtree-relative paths, in its own — because it had never -pointed the check at itself. - -**Line numbers rot faster than paths.** A citation surviving this check proves -the file exists and the line is in range, not that the line still says what you -claimed. Spot-check a sample by reading them back; cite a range when the exact -line is likely to move. - -**Cross-check the documents against each other, not only against the code.** -Where two documents in one repository describe the same set, the disagreement -tells you which one is being maintained. - -`ARCHITECTURE.md` was six tables short and named two that had -been dropped — while `docs/02-bounded-contexts.md`, sitting beside it, listed -**all 34 correctly**, every one owned by exactly one context. The pass had -adopted the stale document as `03` without noticing the accurate one next door. - -So when you adopt, compare the candidate against the corpus first. The document -that agrees with the code is the one to adopt or to extend from; the one that -disagrees is a finding, and often tells you *when* maintenance stopped. - -**Read the siblings before you enumerate the code — it is the cheaper audit, and -often the same finding.** One realtime map listed three PubSub -topics that do not exist. Finding it by enumeration meant extracting 111 -broadcast call sites across 1,075 files, and the first two extractors were wrong -(one matched progress payloads, not topics). **Two sibling documents already said -all three were net-new** — one called that context "mostly new projections", -another called the feature net-new over a flat column, and the third put the data -in a table column rather than behind a topic. Minutes, not an extractor. - -**And weigh agreement by count.** Three documents agreeing against a fourth is -stronger evidence than any single pairwise comparison, and it points at the -outlier without needing the code at all. Use the code to *confirm* the outlier, -not to discover it. - -**A check is useful when you can afford to inspect every hit.** That is the -threshold to iterate towards, and it usually takes two or three attempts at the -instrument rather than one. - -On one pass, verifying a data model's column claims: the first attempt -checked every backticked token in each row and returned **39 suspects out of -69** — it was reading the writer-service column as if those were column names. -Narrowing to the constraints cell and stripping enum braces gave **33 checked, 3 -hits**, and all three turned out to be values rather than columns. Three is -reviewable by hand; thirty-nine is a pile nobody reads, and a check nobody reads -is worse than none because it looks like diligence. - -Tighten the extraction until the hit list is short enough to read, then read all -of it. **Never report the raw hit list as findings** — every hit needs a human -look before it becomes a claim. - -**Exclude what the language treats as inert, and read the document's own caveats -before reporting a gap.** An extractor that cannot tell live code from commented -code manufactures findings. - -One route audit reported three surfaces missing from a document -titled *"every view, its actors, its use case"*. All three were in a commented-out -legacy block — and the document already listed them, with ten others, under a -**Dead surface** heading, citing the exact line range and raising a question about -the two admin routes still live above them. The document was more thorough than -the check. - -So before a gap becomes a finding: strip comments and disabled blocks from what -you extract, then search the document for the thing you think is missing — -including its "dead", "deprecated", "not covered" and "open questions" sections. -Documents written by a careful pass usually record their own exclusions, and a -gap that is already named is not a gap. - -### Report what held, not only what broke - -> How a *matcher* fails — false alarms against false passes, the danger in the -> "certain" bucket, counting what a pattern did not match — is in **Citations** -> above, where a pass first writes a checker. This section is what you then say. - -#### Reading a result before acting on it - -**Re-read the evidence at the moment you decide to act, not when you measure.** A -count is reported once and reused — in a row, a summary, the next decision — each -reuse further from the instrument. When a number is about to become a change, -**open two of the things it counts.** It costs a minute and is the last point at -which a measurement error is cheap. (A finding of *"3 rows with no question"* -survived measurement, a requirement, a commit and publication; acting on it opened -all three and every one had its question, in a field the matcher could not see.) - -**Watch for rules that grew around a bad number.** That finding had already -sprouted an acceptance criterion derived from a population that did not exist. A -wrong count does not merely misreport — it becomes policy, and the policy outlives -the correction unless you go looking for it. - -**A cross-reference is a claim, and its qualifier is the load-bearing part.** A -decision listing *"**Resolves:** OQ-101, OQ-102, …"* looks like authorisation to -close them; the same line ends *"(theme T-B **partial**)"*. **Batch-closing on a -cross-reference asserts a completeness its own author declined to claim** — and it -is tempting because it makes a number go down. When a reference resolves N things -at once, read what it says about *itself* first. - -**Read the false positives before dismissing them — sometimes they are the -finding.** A sweep scored 146 cited, 144 resolved; the two failures were library -modules the enumeration had not scanned. Dismissing them was correct about the -sweep and would have lost the result: one was `Ecto.Enum`, and a canonical -document stated flatly that `Ecto.Enum` is not used here. Ten schemas use it. Ask -what made a false positive *look* plausible before deleting it. - -#### When nothing was wrong - -**A clean audit is a result, and needs a banner as much as a dirty one.** Record -what was compared and what was left unchecked — an unbannered document invites the -next pass to redo the work, and *"we checked and it held"* is what stops that. - -**A clean audit is the cheapest moment to mechanise the rule it just confirmed.** -The sweep has done the expensive half: it established the corpus is clean, so the -new check goes green on the first run and you never mix enforcement with cleanup. -Add the same check after it breaks and you must fix N violations and land the -guard in one change — which is when guards get watered down to fit the mess. - -**And the exemptions come out of the sweep.** When a rule held across 948 rows -with four exceptions, all one status, that status became the carve-out. Had they -been a status the rule should cover, the answer would differ — and no amount of -thinking in advance would have said so. - -→ Every time an audit comes back clean, ask what one-line check would keep it that -way, and whether anything enforces it today. - -#### Writing the report - -**Say what held, not only what broke.** A derived document that passes its audit -is a result, and a report listing only corrections implies the rest was unread. - -**A finding needs its counterweight when the counterweight changes how it reads.** -*"Four optional credentials behave four different ways"* reads as carelessness -until you add that the two which would compromise the system fail loudly at boot. -Same facts, opposite conclusion. - -**Record what you checked and found correct, not only what you fixed.** Otherwise -the next pass re-derives it, and a corpus accumulates repeated audits of the same -clean thing while the unexamined half stays unexamined. - -**A confirmation you wrote yourself is not evidence.** Prove the instrument can -report the other answer before believing this one. The shapes that lie: - -- a check reporting **perfection** — feed it something known-broken; -- a check reporting **catastrophe** — a real document is rarely 80% wrong; -- a script printing **"done"** unconditionally; -- a **runtime probe** answering from the wrong state (`function_exported?` before - the module is loaded); -- an **exit status from the wrong command** — a pipeline returns the last stage's - status, so `… | tail` reports the success of `tail`. - -#### What actually rots - -**It is not documents that rot, it is *claims about sets*.** A corpus wrong about -which enums exist scored 146 of 146 on the module names it cited. Named references -hold — someone wrote the name while looking at it, and a rename breaks a build -long before it breaks a document. Quantified claims do not: - -| Shape | Example | Ages | -|---|---|---| -| Named reference | *"`Aiengine.Repo` injects the tenant filter"* | slowly — checkable and load-bearing | -| Coverage claim | *"every endpoint named here is in `07`"* | fast — new ones arrive without touching the document | -| Convention claim | *"enums are never `Ecto.Enum`"* | fast and silently — one exception falsifies it | -| Bare count | *"the ~80 Ecto schemas"* | fastest — wrong without anyone editing anything | - -**Aim an audit at the quantifiers** — *every, all, none, never, only, the N* — not -at the nouns. Grepping for those finds more rot per minute than reading, and each -hit settles with one command. - -**The quantifier is not what makes a claim fragile — writing it without opening -the code is.** The same sweep against a *derived* corpus found every absolute -holding. A derived pass states an absolute only after enumerating, so its -absolutes survive; a design document states one as an intention, and intentions -acquire exceptions silently. That is where to spend an audit first. - -Two cautions when running it. Most quantifier hits in a UX or product document are -**prescriptive** — *"never display a secret after creation"* is a requirement, not -a claim about code, and checking it against code is a category error. And scope -the check to the **declaration** you are testing: counting constraints in a window -around a class catches its neighbours. - -### Ids, capture, and siblings - -**Every id a document cites must be defined somewhere — check both directions.** -An id cited but never defined is a dangling reference; one defined but never cited -is either dead, or a gap in the document that should point at it. - -**Scope the check to the ids this workspace owns.** A platform that analyses other -repositories will carry ids belonging to them; a sweep that does not scope reports -those as dangling forever. - -**Register by reference, not by copy.** The full statement belongs in one place; -everywhere else cites its id. Two copies of a question diverge, and the reader -cannot tell which is current. - -**A finding recorded only in prose is not captured.** An audit ending with a note -inside the document it audited has told the document about itself. A discovery -becomes a ledger row or a backlog line, with provenance, or it is carried rather -than captured. - -**Cross-check siblings.** Where two documents describe the same set, the -disagreement tells you which one is being maintained. - -### Audit this document too - -**A prompt's own counts rot like any other document's.** An earlier version of this material said *"Three phases, in this order"* directly above a block listing -**four** — the sentence was written when there were three and never revisited -when D was added. It had shipped that way to two workspaces. - -Sweep your own stated counts against the thing they count, the same way you would -sweep a document you inherited: - -```bash -# the leading [^-[:alnum:]] matters: \b alone matches "six" inside "Thirty-six" -grep -onE '(^|[^-[:alnum:]])(two|three|four|five|six|seven|eight|nine|ten)[[:space:]]+(phases|documents|rules|steps|files|ways|copies)' SKILL.md -``` - -That refinement came from running this very command on this very -file: of 14 hits, one was *"six documents"* matched inside -*"Thirty-six documents"*. It is the same substring trap that has now produced a -false finding three times in one session — `NFR` inside `INFRA`, `Q-AGT-001` -inside `REQ-AGT-001`, and this. **A word boundary is not a word boundary when the -neighbouring character is a hyphen.** - -Then check each against its list. Here *"five documents"* matched five rows and -*"three files"* matched the contract's three copies — one defect in four claims, -which is roughly the hit rate to expect from any document nobody has counted. - -**Audit this document too — a prompt is a document.** Everything above applies to -the skill itself, and it fails the same ways. - -Once a rule was inserted by anchoring on a phrase, and the anchor -sat **inside a numbered checklist**: the new rule became "step 6", its code block -landed between steps, and the two-line step it displaced vanished. Nothing -errored. The skill still rendered. It was found by counting the steps and reading -them back — the same check the sequence itself prescribes. - -So after editing any structured document programmatically, verify the -**structure**, not just the text: item counts, balanced code fences, headings -still where they were. An anchored replacement is a blind edit, and prose anchors -are unstable exactly where documents are most structured. - -### The audit as a sequence - -The rules above are the reasoning; this is the order to run them in. Each step is -minutes, and each one has caught a real defect. - -1. **Groundedness.** `grep -rl 'CODE:' docs/ | wc -l` against the document count. - Zero citations over a real codebase is the headline finding, not a pass. -2. **Citations resolve**, from the repository root, no elided paths. The - shipped script does the whole corpus in seconds. -3. **Read a sample back.** Five citations, opened, compared to the sentence that - cites them. Resolution is not correctness. -4. **One countable set per document** — tables against `__tablename__`, routes - against the router, ports against what compose publishes, commands against - `package.json`. Probe the check with a value you know is absent. -5. **Status tags.** If the document marks claims EXISTS/TODO or built/planned, - verify those — they are the sharpest promise it makes. -6. **Ids resolve, and siblings agree.** Every `Q-`/`REQ-`/`ADR-` id cited is - defined somewhere; where two documents describe the same set, the - disagreement names the stale one. -7. **The runbook** — every URL, port and command a reader will type. -8. **The index.** Every document listed, with a status; every listed document - present. -9. **The published copy**, if the corpus syncs anywhere. -10. **Record what held**, with the date, inside the documents themselves. - -**Inspect every hit before writing any of it down.** Across one session this -sequence produced four false findings — an ASCII tree read as paths, commented-out -routes read as live, a writer-service column read as column names, a -module-plus-function read as a missing module — and each looked exactly like a -real defect until it was opened. +# Audit document and record claims + +Read the project `AGENTS.md` and canonical `PROCESS.md`. This shared utility +establishes facts; its invoking pass owns the gate and routes corrections through +`rdd-triage`. Limit routine reviews to the affected surface. A whole-corpus sweep +requires an adoption/release scope or an explicit audit request. + +## Execution contract + +- Input: named document/record set, claim type, affected repository revision, + authoritative sources, and explicit inventory populations; no lifecycle change + is implied by invoking this utility. +- Writes: sourced findings, measurement output, limitations, and a durable + handoff to the caller; do not silently correct normative intent or product code. +- Exit: checked claims and exact unresolved/unsupported findings. A tool failure + or zero checked population cannot satisfy the invoking gate. + +## Procedure + +1. Distinguish intended behavior from descriptions of existing code. Missing code + support is relevant to an as-built claim; it does not disprove a normative + requirement. Record the audit's scope, revision, and excluded populations. +2. Run the shipped citation auditor from the target repository root, using the + installed skill path (or this repository's `skills/rdd-audit/` path): + + ```bash + node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs docs tasks + ``` + + Inspect recognized, checked, unresolved, unsupported, and exempt counts. + Canonical `CODE:`, `TEST:`, `DOC:` and supported bare references are structural + checks, not proof of behavior. Read unsupported forms and use a suitable + resolver or correct the citation; never count them as checked. Zero checked + citations requires an explicit scope/groundedness finding, not a passing trace. +3. Open cited sources and compare them to the claims. Stable test-name presence + does not prove registration, execution, production reachability, or the cited + assertion; `rdd-verify` establishes those when behavioral evidence is required. +4. Enumerate the actual population an inventory claims to cover, then compare + both directions: documented-but-absent and present-but-undocumented. Account + for route prefixes, disabled/commented code, drops/renames, and stated + exclusions. Report each direction and population separately. +5. Check applicable status tags, ids/relations, sibling documents, runbook + commands/ports, and index entries. For an authorized synchronized corpus, + compare the published copy too. Do not trigger publication merely to audit it. +6. Challenge the instrument with known-good and known-broken inputs. Investigate + suspiciously complete passes, high failure rates, and unexpected count changes; + neither direction proves that the checker or corpus is right. Inspect every + candidate finding before reporting it as a defect. +7. Record what held, what failed, direct sources, population denominators, + unchecked claims, and the exact next action. The caller imports findings into + its gate or routes them through triage. A finding's substance must be durable, + not only a pointer to disposable review notes. + +## Citation tool boundary + +The checker resolves paths and line ranges, document headings, and supported +symbol/test names. Named references establish lexical presence only, including +the components of hierarchical test names; test runners must establish their +actual identity and execution. Quoted test names support spaces. Unsupported +syntax is reported, not skipped. Examples explicitly marked `example-citation` +and named gaps are exemptions, never successful checks. + +Exit 0 means at least one reference was checked and all recognized references +were resolved or explicitly exempted. Exit 1 reports broken/elided references; +exit 2 reports unsupported inputs, no checks, or setup/usage problems. Read the +whole output and any exemptions before using a successful exit as narrow +structural evidence. + +## Supporting examples + +For surprising checker results, matcher changes, or a substantial corpus audit, +read [audit-examples.md](references/audit-examples.md). It preserves worked cases +on false passes, population mismatches, bidirectional inventories, runbooks, +published copies, and misleading counts. These examples support the checklist; +they do not impose a full-corpus audit on every development iteration. diff --git a/skills/rdd-audit/audit-citations.mjs b/skills/rdd-audit/audit-citations.mjs index 480703a..cadca72 100755 --- a/skills/rdd-audit/audit-citations.mjs +++ b/skills/rdd-audit/audit-citations.mjs @@ -20,8 +20,9 @@ // Neither is worth suppressing automatically: a rule broad enough to catch them // would hide real rot. Read the failures before acting on them. // -// Exits 0 when every citation resolves, 1 otherwise. Default roots: docs/ plus -// ARCHITECTURE.md if present. +// Exits 0 after at least one real check with no unresolved/unsupported input; +// 1 for broken/elided references; 2 for unsupported/no-check/setup input. +// This is a structural check, never proof of test registration or execution. import { execSync } from "node:child_process"; import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; @@ -39,7 +40,11 @@ const EXT = "exs|tsx|yaml|proto|json|ex|go|js|ts|yml|sh|py|rb|rs|java|kt|toml|sq // A citation may end at a NAME instead of a line — a test // identifier survives edits to the file, a line number does not. Group 4 is // that name; unchecked, `file.go:TestGoneForever` passed on file existence. -const PREFIXED = new RegExp(`CODE: ?([A-Za-z0-9_./\\[\\]\\-]+?\\.(?:${EXT}))(?![A-Za-z0-9])(?::(?:((?:\\d+(?:-\\d+)?)(?:,\\d+(?:-\\d+)?)*)|([A-Za-z_][A-Za-z0-9_]{2,})))?`, "g"); +const PREFIXED = new RegExp(`CODE: ?([A-Za-z0-9_./\\[\\]\\-]+?\\.(?:${EXT}))(?![A-Za-z0-9])(?::(?:((?:\\d+(?:-\\d+)?)(?:,\\d+(?:-\\d+)?)*)|([A-Za-z_$][A-Za-z0-9_$]*(?:[./#-][A-Za-z0-9_$]+)*)))?(?![A-Za-z0-9_/:#-])`, "g"); +// TEST requires a stable name, optionally quoted for spaces. Consume the whole +// hierarchical identity so a missing subtest cannot pass on its parent alone. +// File extensions are unrestricted here; unsupported forms are reported below. +const TESTREF = /TEST: ?([A-Za-z0-9_./\[\]-]+\.[A-Za-z0-9]+):(?:"([^"\r\n]+)"|'([^'\r\n]+)'|([A-Za-z_$][A-Za-z0-9_$]*(?:[./#-][A-Za-z0-9_$]+)*))(?![A-Za-z0-9_/:#-])/g; // The full grammar, not the first two parts: a citation may list lines and // ranges — file:N, file:N-M, file:N,M, file:N-M,P. A pattern holding only two // groups matches every one of them and silently skips the rest; 188 citations @@ -56,7 +61,7 @@ const BARE = new RegExp("`([A-Za-z0-9_./\\[\\]\\-]+\\.(?:" + EXT + ")):((?:\\d+( // checked by nothing until RUN:2026-08-14, when 17 of them turned out to hide // one malformed compound anchor (`#2.1/#5`). const DOCREF = /DOC: ?([A-Za-z0-9_./\-]+\.md)(?:#([^ )|`\u00b7]+))?/g; -const ELIDED = /(?:CODE:|`)(?:[A-Za-z0-9_.\\[\\]\-]+\/)*\.\.\.\/[A-Za-z0-9_.\/\\[\\]\-]*\.(?:exs|tsx|ex|go|js|ts|py|rb|rs|java|kt|sql|yml|yaml|json|sh|heex)\b/g; +const ELIDED = /(?:CODE:|TEST:|`)(?:[A-Za-z0-9_.\[\]-]+\/)*\.\.\.\/[A-Za-z0-9_.\/\[\]-]+\.[A-Za-z0-9]+\b/g; // 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 @@ -64,7 +69,12 @@ const ELIDED = /(?:CODE:|`)(?:[A-Za-z0-9_.\\[\\]\-]+\/)*\.\.\.\/[A-Za-z0-9_.\/\\ const ABSENCE = /\b(no|not|missing|absent|never|does not exist|there is no|without)\b[^.]{0,60}$/i; const roots = process.argv.slice(2).filter((a) => !a.startsWith("--")); -const targets = roots.length ? roots : ["docs", ...(existsSync("ARCHITECTURE.md") ? ["ARCHITECTURE.md"] : [])]; +const targets = roots.length ? roots : ["docs", "ARCHITECTURE.md"].filter(existsSync); +const missingTargets = targets.filter(t => !existsSync(t)); +if (missingTargets.length) { + console.error("missing audit root(s): " + missingTargets.join(", ")); + process.exit(2); +} // Refuse to audit the prompts. Teaching material deliberately // quotes citations that do not resolve — an elided `CODE:.../job_processor.py` @@ -152,11 +162,13 @@ function markdownFiles(target) { : e.name.endsWith(".md") ? [join(target, e.name)] : []); } -const docs = targets.flatMap(markdownFiles).sort(); +const docs = [...new Set(targets.flatMap(markdownFiles))].sort(); let ok = 0; let gaps = 0; +let exempt = 0; const broken = []; const elided = []; +const unsupported = []; // A fenced block whose opening line carries `example-citation` is a VERBATIM // DISPLAY of citations, not a set of claims: a client-facing document showing a @@ -187,6 +199,8 @@ for (const doc of docs) { const lines = text.split("\n"); const exemptRanges = exemptFenceRanges(lines); const inExemptFence = (idx) => exemptRanges.some(([a, b]) => idx >= a && idx < b); + const isExample = idx => inExemptFence(idx) || + //.test(lines[text.slice(0, idx).split("\n").length - 1]); for (const m of text.matchAll(ELIDED)) { const line = text.slice(0, m.index).split("\n").length; @@ -202,15 +216,26 @@ for (const doc of docs) { const spans = []; for (const m of text.matchAll(PREFIXED)) { spans.push([m.index, m.index + m[0].length]); - if (inExemptFence(m.index)) { ok++; continue; } + if (isExample(m.index)) { exempt++; continue; } check(doc, text, m); } + for (const m of text.matchAll(TESTREF)) { + spans.push([m.index, m.index + m[0].length]); + if (isExample(m.index)) { exempt++; continue; } + const normalized = Object.assign( + [m[0], m[1], undefined, m[2] ?? m[3] ?? m[4]], + { index: m.index }, + ); + check(doc, text, normalized); + } for (const m of text.matchAll(BARE)) { if (spans.some(([a, b]) => m.index >= a && m.index < b)) continue; - if (inExemptFence(m.index)) { ok++; continue; } + if (isExample(m.index)) { exempt++; continue; } check(doc, text, m); } for (const m of text.matchAll(DOCREF)) { + spans.push([m.index, m.index + m[0].length]); + if (isExample(m.index)) { exempt++; continue; } const lineNo = text.slice(0, m.index).split("\n").length; const found = candidates(m[1]); if (!found.length) { broken.push({ doc, lineNo, path: m[1], why: "no such document" }); continue; } @@ -228,6 +253,16 @@ for (const doc of docs) { } ok++; } + // Count markers independently of the supported grammar. Otherwise an unknown + // extension or missing TEST identity disappears from the denominator. + for (const m of text.matchAll(/\b(?:CODE|TEST|DOC):/g)) { + if (spans.some(([a]) => a === m.index)) continue; + if (isExample(m.index)) { exempt++; continue; } + unsupported.push({ + doc, lineNo: text.slice(0, m.index).split("\n").length, + snippet: text.slice(m.index).split("\n")[0].slice(0, 120), + }); + } } // The absence exemption is applied ONLY to a citation that would otherwise fail. @@ -248,13 +283,13 @@ function check(doc, text, m) { // line out. Deliberately per-line — exempting the file would blind the audit // to real rot in the same document, and this audit has caught real rot there // (RUN:2026-08-17: two migration paths and a dead anchor). - if (//.test(text.split("\n")[lineNo - 1] ?? "")) { ok++; return; } + if (//.test(text.split("\n")[lineNo - 1] ?? "")) { exempt++; return; } const found = candidates(path); // A named citation is verified by the name, not the position — that is the // whole point of citing one. if (name && found.length) { - if (found.some((f) => readFileSync(f, "utf8").includes(name))) { ok++; return; } + if (found.some(f => namePresent(readFileSync(f, "utf8"), name))) { ok++; return; } broken.push({ doc, lineNo, path: `${path}:${name}`, why: "no such test or symbol in the file" }); return; } @@ -278,6 +313,10 @@ const total = ok + broken.length; console.log(`${ok}/${total} citations resolve across ${docs.length} documents` + (gaps ? ` (+${gaps} named gaps excused — a cited path stated as absent)` : "")); +console.log("recognized=" + (total + gaps + exempt) + "; checked=" + total + + "; unresolved=" + broken.length + "; unsupported=" + unsupported.length + + "; exempt=" + exempt + "; gaps=" + gaps); + if (elided.length) { console.log(`\n${elided.length} elided path(s) — a citation that resolves to nothing:`); for (const e of elided) console.log(` ${e.doc}:${e.line} ${e.snippet}`); @@ -288,4 +327,19 @@ if (broken.length) { } if (!elided.length && !broken.length) console.log("no elided paths"); -process.exit(broken.length || elided.length ? 1 : 0); +for (const entry of unsupported) { + console.log("unsupported: " + entry.doc + ":" + entry.lineNo + " " + entry.snippet); +} +if (!total) console.log("no citations checked — not evidence of trace completeness"); +process.exit(broken.length || elided.length ? 1 : unsupported.length || !total ? 2 : 0); + +function namePresent(source, name) { + const escape = value => value.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&"); + const tokenPresent = value => new RegExp("(^|[^\\w$])" + escape(value) + "(?![\\w$])", "m").test(source); + // This checks lexical components, not their registration/nesting in a runner. + // Quoted names containing spaces must occur as a complete quoted string. + if (/\s/.test(name)) { + return source.includes(JSON.stringify(name)) || source.includes("'" + name + "'"); + } + return name.split(/[./#]/).every(tokenPresent); +} diff --git a/skills/rdd-audit/references/audit-examples.md b/skills/rdd-audit/references/audit-examples.md new file mode 100644 index 0000000..958ac4a --- /dev/null +++ b/skills/rdd-audit/references/audit-examples.md @@ -0,0 +1,964 @@ +# Audit examples and failure patterns + +Supporting case studies for `rdd-audit`. The main skill and `PROCESS.md` own the +current execution contract. Historical counts and examples below illustrate +failure modes, not universal thresholds or authorization to change a repository. +The script lives one directory above this reference, beside `SKILL.md`. + +A shared utility, not a phase. `rdd-reverse-engineer` invokes it before +claiming coverage, `rdd-cold-review` and `rdd-completion-review` invoke it to +verify citations and inventories against the code, and any pass correcting a +stale claim may load it alone. Its contract: + +- it establishes facts about documents, records, and the instruments that + check them; it never assigns or advances a lifecycle state; +- its findings are discoveries — route them through `rdd-triage`, which owns + where they land; +- invoked inside the loop it is scoped to the affected surface; the + full-corpus sweep is a deliberate act — at adoption, before a release, or + when `rdd-start` reports drift — never an every-iteration cost. + +Read the project `AGENTS.md` and the canonical `PROCESS.md` +(`.modernpath/rdd/PROCESS.md` in a consuming repository) for the authority +and reconciliation rules these checks serve. + +Two sections, and they answer different questions. **Citations** asks *does this +reference resolve* — mechanical, and the place a pass first writes a checker. +**The audit** asks *is this document still true, and is my check trustworthy* — +which is mostly about not believing your own instrument. + +--- + +## Citations — does every reference resolve? + +Every `file:line` you wrote must resolve. This is mechanical, it takes seconds, +and it is the cheapest guard against a ledger that reads well and points nowhere. + +**Run the script; do not re-derive it.** + +```bash +# beside this skill; installed at .modernpath/rdd/skills/rdd-audit/ in a consuming repository +node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs # docs/ + ARCHITECTURE.md +node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs tasks # or any root +``` + +It sweeps both citation shapes over every markdown file under the given roots, +resolves by path suffix against `git ls-files`, flags elided paths, excuses named +gaps, and exits non-zero on failure. The rest of this section explains *why* each +of those rules exists — read it when the output surprises you, and when you are +tempted to write your own. + +**That temptation is the point.** This procedure was re-derived by hand five times +in one session and was wrong three of those. Each rewrite looked correct and +produced a confident number. The rules below are the scar tissue: + +**Skip named absences.** A row that says *"no test — there is no +`test_draft_service.py` in the repository"* is naming a gap, which is the most +useful thing a derivation pass produces. It is not a citation, and an audit that +counts it as broken teaches the next pass to stop naming what is missing +(8 of 8 "broken citations" in a 3,222-citation ledger were +absences stated correctly). Ignore any reference preceded by *no*, *missing*, +*there is no*, or *does not exist*. + +**And resolve paths properly before reporting a failure.** A citation written +`db/models.py:1483` may live six directories deep; a bare `admin.py` may match two +files, only one of which is long enough. Match on path suffix, accept if **any** +candidate satisfies the line, and search the whole repository rather than one +subtree. Three separate audit scripts written in one session each reported the +ledger as broken when the resolver was at fault. Prove your +checker on a citation you know is good before you trust its failures. + +### How an instrument fails, and in which direction + +#### Reading what the instrument told you + +**A near-total failure rate is a broken instrument, not a discovery.** Before +believing a result, ask what fraction of the population it condemns. A check that +indicts 100% of anything has found a convention it does not understand. Near 10% +the balance tips and a finding becomes likelier than a bug. The converse holds: a +check that passes *everything* on its first run has usually matched nothing — +which is why a new check earns trust by being made to fail on purpose. + +**A module path is not a table name.** A `compliance/` directory prefix is not +part of `schema "change_plan_messages"`. **Read the declaration, never the +filename** — the `schema "…"` line for Ecto, `__tablename__`/`@Table` for an ORM, +the DDL for SQL. + +**A shell utility that fails on your data reports an empty result, not an error.** +`sort` exits on non-UTF-8 bytes and swallows its input; the pipeline prints a +clean, wrong, empty answer. Run text sweeps under `LC_ALL=C`, and treat *"none +found"* with the same suspicion as a 100% failure rate. + +**An overstated gap makes the wrong decision for you.** A gap measured at 52 +files across four subtrees was filed as too big to fix; re-measured with the +population defined it was 12, and closed the same day. **Audit the numbers that +license inaction hardest** — an overstated gap buys a permanent deferral, while +an understated one is corrected the moment someone starts work. + +**A count with an undefined population is not a measurement.** One question — *how +many cited tests name their requirement?* — gave 12, 35, 45 and 123 across four +bug-free runs, differing only in what counted as a cited test. **State the +population next to the criteria**, and when a re-measurement disagrees with a +recorded one, **suspect the population before the matcher**: a factor-of-ten +spread is what a definition disagreement looks like. + +**Report the instrument's count and the verified count separately.** "35 +mismatches" implies each was inspected. Say *upper bound*, and name the ones that +were. + +#### Changing a matcher + +**Widening a matcher to fix a false negative is the moment to write the +false-positive test** — not after. Each widening below let something through that +must not pass, and every one was caught by adding the negative case, none by +re-reading the regex: + +| Widened to accept | Also accepted, wrongly | +|---|---| +| `APPROVED` anywhere on a line | `SPEC-APPROVED` — a different gate | +| any heading containing "approval" | `## Pre-approval audit` | +| every line of a section | rows belonging to the other gate | + +A widened pattern reads as *"now it accepts X"*; the question that matters is +what **else** it accepts. Write the shape you must still reject, and watch it fail. + +**Two implementations with *different* fixtures beat either suite alone.** A +parity mirror or a port is justified as a compatibility requirement; its quieter +value is that its tests were written by someone solving the same problem from +another angle, so it holds fixtures the original never thought to write. When a +widening left one suite green and failed the mirror on a case the first lacked, +the disagreement was the only detector. **Run the mirror's tests before your own +conclusions**, and copy back the fixture that surprised you. + +**A widening that clears more than you expected is a warning, not a result.** If a +change fixes more cases than the one you were chasing, find out which extra ones +moved before booking them. + +**Be most suspicious when the new behaviour is a pass.** A false negative annoys +someone; a false positive silently asserts that a thing was checked. + +#### When the tool says nothing, or says it sideways + +**Do not let display truncation become evidence.** A check that printed cited +lines through `cut -c1-46` made six correct citations look wrong. When a check +disagrees with a document, **widen the view before you edit** — print the whole +line, and confirm with a second method sharing no code with the first. Agreement +between `grep -n` and `awk` means something; agreement between a script and its +own truncated output means nothing. + +**Read a checker's first run as a test of the checker, not of the corpus.** Four +checks each found a bug in *themselves* on first contact with real data: an +exemption applied before resolving (skipped 116 resolvable citations), an elision +gate whose hits were prose, an escape fix that collapsed counts 2220 → 483, an +anchor matcher that mangled its own character class. None was found by re-reading +the code. Budget the first run for debugging the instrument. + +**Silence is not agreement.** A command producing *no output* has not been shown +to have run — a fixture using an unscanned extension reports `0/0`, a gate run +from the wrong directory reports *"checks pass"*, a command inside a broken `&&` +chain never executes. Verify the **effect**, not the exit code, and never pipe to +`tail`: the last two lines of no output are no output. + +A gate proves what it checks, never that it was reached. + +#### Which direction to distrust + +**Both directions of failure happen; only one is self-correcting.** + +| | What it does | How it ends | +|---|---|---| +| **False alarm** — a regex matching `.ex` inside `.exs`; a truncated display | sends you *to* the evidence | caught within minutes — you open the file to fix it and the code disagrees | +| **False pass** — a sweep over a subset reporting `56/56`; an exemption applied too early | sends you *away* from the evidence | survives until something unrelated exposes it; several persisted for months | + +Budget suspicion asymmetrically. A false alarm costs one round trip and pays for +itself. A false pass costs nothing today and everything later, and **nothing in +your own workflow will surface it** — the only reliable detectors are a second +implementation and a number that does not match the change. + +**Stop when the evidence explains *why*, not when it establishes *that*.** A +finding whose meaning changes as you look closer has not finished changing. One +sequence ran: *93 rows stranded on the server* → *but the ids are in `tasks/`* → +*so the builder is dropping rows* → *they were retired deliberately, and the data +model says so in its own headings*. Each reading pointed at a different action; +the one that held explained why the state exists. **The most alarming reading was +third of four, and it was wrong** — alarm is not evidence of depth. + +**Splitting findings into "certain" and "ambiguous" puts the danger in the wrong +bucket.** The ambiguous pile gets read carefully because you cannot act on it; the +certain pile gets *acted on*, so a detector error inside it goes straight into an +edit. → **Before acting on the confident half, re-derive it once with a stricter +matcher.** If the count drops, your certainty came from the tool. + +#### What the pattern never saw + +**Count what your pattern did *not* match — a loose pattern does not over-report, +it stops looking.** Citations use a grammar: `file:N`, `file:N-M`, `file:N,M`, +`file:N-M,P`. A pattern matching `file:(\d+)` matches all of them and reads only +the first number, so `466,480` scores as one citation and 480 is never examined. + +1. **Write down the grammar before the regex.** If the data has lists, ranges or + optional parts, enumerate them. +2. **Measure coverage, not just hits.** If a line holds four numbers and your + matcher reports one, that gap is the finding. + +**Report the result as a fraction (`140/140 resolve`), and make the denominator +the whole corpus.** A pass that greps only its own `CODE:` prefix measures the +citations it thought to prefix — one corpus reported `56/56` while carrying 11 +bare `` `adapter.ex:48` `` references no audit had ever seen. Sweep both shapes, +resolving bare ones by path suffix against the tracked file list. +### What the sweep must cover, and what it still cannot tell you + +**And sweep every document, not the ones this pass wrote.** The subset error +recurs one level up, and it is easy to miss because each fraction looks complete. +The same corpus reported `56/56` over the derived set, then `67/67` once bare +references were counted, then **`119/123`** once the sweep covered `docs/**` — and +the four failures were in a document no earlier audit had opened, because it was +not part of the derived set. They were **elided paths**: `CODE:.../ai/proxy.ex`, +written by an author who knew where the file was and left the reader a citation +that resolves to nothing. + +```bash +grep -rn 'CODE:\.\.\.' docs/ # elided paths — expect no output +``` + +Elision is worth a check of its own because it is invisible to a reader and to a +naive resolver alike: the line *looks* like a citation, and a suffix-matching +audit can even accept it if it strips the dots. Write the path from the repository +root, every time. + +**Resolving is not being right, and the gap between them is where rot lives.** +Every check above is structural: the file exists, the file is long enough. A +citation can pass all of them and point at a line that no longer says what the +claim says. a corpus reported `50/50 resolve` while one +document quoted a per-file cap of 15,000 characters that applied only when the LLM +proxy was **off** — the code selects between two budgets, and the smaller one +(8,000) governs any deployment using the proxy. The citation resolved perfectly. + +**So cite the line that carries the claim, not the definition that contains it.** +That document cited a function's `defp` head while quoting an expression three +lines inside it. Pointing at the head is what let the quote go unchecked — nobody, +human or script, could compare the claim to the cited line, because the cited line +was a signature. When you quote or paraphrase a specific expression, cite *its* +line; reserve the head for claims about the function as a whole. + +Then one more check becomes possible and worth running: **for every citation where +the prose quotes code, assert the quoted text appears in the cited range.** That is +still mechanical, and it catches the drift the existence check cannot. + +**A token-overlap heuristic is a triage list, not a gate.** Comparing symbols named +in the prose against tokens on the cited line flagged 14 of 52 citations in that +same corpus; 13 were false — the surrounding text was a table, and the "prose" was +a neighbouring cell. One was real, and it was the one above. Run it to decide what +to read, never to decide what to report; a checker with a 93% false-positive rate +teaches the next pass to ignore it. + +--- + +## The audit — is the document still true, and is your check trustworthy? + +Everything below applies to any document this process produces or inherits — a +recovered design corpus, an adopted `ARCHITECTURE.md`, per-context documents, +and the serialized process records. It is written as one section because these checks are a single +discipline, not per-document advice. + +**Adopting is not accepting. Audit what you adopt.** A maintained document is +maintained *as of some date*, and the code moved after it. Before you adopt one, +run at least one **countable** check — a set the document enumerates against the +same set in the code: + +- tables it lists versus `__tablename__` declarations +- services it names versus what the compose file and CI actually build +- endpoints it documents versus the routers on disk + +On one real corpus that check took two commands and found a `Database Schema` +section that was **6 tables short of the code and named 2 that had been dropped** +— the document was edited 2026-06-10 while the services changed through +2026-08-03. A reader trusting it would have looked for a table the migrations had +deleted. + +Then **extend it in place** with what you found, and say in the document that you +did, with the date and what you reconciled against. A silent correction leaves +the next reader unable to tell which parts have been checked. + +**Two commands that do this well:** + +```bash +grep -oE '__tablename__ = "[a-z_]+"' | sed 's/.*"\(.*\)"/\1/' | sort -u > /tmp/real +# extract the same set from the document, then: +comm -23 /tmp/real /tmp/doc # in the code, undocumented +comm -13 /tmp/real /tmp/doc # documented, no longer real — the sharper finding +``` + +### Groundedness first, then correctness + +**Measure groundedness before you measure correctness — `0 broken` on `0 +citations` is not a pass.** A citation audit reports what it found wrong among +the claims that can be checked. A document that cites nothing cannot be wrong by +that measure, and will score perfectly. + +Run this first, and read it as the headline result: + +```bash +docs=$(ls docs/*.md docs/**/*.md 2>/dev/null | wc -l) +cited=$(grep -rl 'CODE:' docs/ 2>/dev/null | wc -l) +echo "$cited of $docs documents carry at least one citation" +``` + +Measured across three real corpora, which is the range to +expect: + +| Corpus | Documents | With ≥1 citation | Citations | +|---|---:|---:|---:| +| derived by this process | 26 | 22 (84%) | 1,003 | +| a workspace's own design docs | 48 | 9 (18%) | 48 | +| a repository with 21,410 code files and no derivation pass | 36 | **0 (0%)** | **0** | + +The third is the case to recognise. Thirty-six documents describing a substantial +system, none of them making a single checkable claim about it — and the citation +audit returned "0 broken", which looks like health. + +**Audit a document's own status tags — they are a promise, and they are +machine-checkable.** A design document that marks each item **EXISTS** or +**TODO**, **built** or **planned**, is making a per-claim assertion far sharper +than its prose. That tagging is usually the most useful thing in the document and +the least maintained. + +One audited API contract carried 69 endpoints tagged EXISTS: **five +were not in the router at all**, their only matches being LiveView modules rather +than JSON routes. A team building a frontend against that contract would have +discovered the gap at runtime. The other 64 EXISTS tags and all 65 TODO tags +held — so the document was 96% right, and the 4% was concentrated exactly where a +reader would act on it. + +Note which question actually paid. The first comparison asked *"is this endpoint +in the router?"* and returned 40 of 138 missing — mostly the document's own TODOs +and scope-prefix noise. The useful question was **"is this document's claim about +itself true?"**, which is answerable, small, and directly actionable. + +**Design documents and derived documents are different genera, and the audit only +speaks to the second.** A document written *before* the code says what should be +true; a derived document says what is. Do not "fix" the first kind by hanging +citations on it — check instead whether the system it describes was ever built +that way, and record the answer as findings. An uncited design corpus over a +large codebase is a **drift question**, not a formatting defect. + +**Expect the ratio to be alarming and mostly fine.** A "documents with zero +citations" query over one workspace returned **48 of 57**, and +nearly all of them were right to have none — API contracts, a ubiquitous-language +glossary, feature specs, product positioning. Sort by genus before you react, or +the number will push you into hanging citations on documents that precede the +code, which destroys what they are for. + +**Among the derived ones, though, uncited predicts wrong.** In the same corpus the +grounded derived documents had almost no defects; the one derived document with +**zero** citations had three, all in the direction that misleads someone doing +real work — two endpoint paths listed without the mount prefix they are registered +under, and two endpoints missing entirely. Nobody had checked, because there was +nothing to check against. Ground a derived document and you are not tidying it; +you are running its first test. + +**And watch the citation form itself.** Two citations in that corpus were written +`CODE: path` with a space, which every checker's regex missed — so they were never +verified by anything, in a document that declares itself *generated from code*. +One of the two resolved only by suffix; the path as written did not exist. A +convention followed 130 times and broken twice is broken invisibly. + +### Citations: whole, rooted, and re-checked + +**A citation must resolve, which means it must be whole.** Never elide a path. +`CODE:.../job_processor.py:265` reads tidily and is worthless: a reader cannot +open it and a checker cannot verify it. Write the repository-relative path in +full, every time, however long. + +Run the shipped audit over the corpus before you call a pass done — it reads +every citation in every document in seconds: + +```bash +node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs # docs/ +node .modernpath/rdd/skills/rdd-audit/audit-citations.mjs tasks epics process +``` + +**This section used to carry an inline Python re-implementation, and removing it +is the point.** That snippet swept `docs/**` +only, matched the `CODE:` prefix and not bare `` `file.ex:12` `` citations, held +two line-parts so everything after the second in `file:N,M,P` was invisible, and +closed by claiming *"a failure is always real"* — which five separate false alarms +in one session disproved. Every one of those flaws was fixed in the script and +would have had to be fixed again in the snippet. + +A skill that tells a reader to hand-roll the check it also ships is not offering +a choice; it is offering the version nobody maintains. + +### The runbook, the published copy, and being findable + +**Audit the runbook — the lines a reader will type.** Ports, URLs, commands and +env var names are the most checkable claims in any document and the most +expensive to get wrong: a wrong architecture paragraph misleads, a wrong port +wastes an afternoon and gives no clue whether the reader broke the setup or the +document is lying. + +Take every URL and port the document names, and confirm each against what the +compose file or deploy config actually **publishes** — not what a service listens +on internally. The two are different, and documents routinely conflate them. + +One *Access points* table listed `http://localhost`, +`http://localhost:8082` and `http://localhost:8083/health`. The compose file +published exactly two ports and the strings `8082` and `8083` appeared **nowhere +in it**; both application services declared no `ports:` at all. Every URL but the +database was fiction, and the service sections above repeated it. + +A one-line check catches this class: + +```bash +grep -oE 'localhost:?[0-9]*' | sort -u # what the document promises +grep -oE '"[^"]*:[0-9]+"' docker-compose.yml # what is actually published +``` + +**"Listens on" is not "reachable".** A service with an internal port and no +published mapping is reachable only from inside the network. Say which it is — +the reader's next command depends on it. + +**Audit the published copy, not just the file.** A document that syncs to a +platform now exists twice, and the copy a reader opens is the remote one. Sync is +usually hook-driven and fire-and-forget, so a failed push leaves the repository +right and the platform stale with nothing raising a hand. + +Compare the far side after a pass. Content length is enough to catch drift +without pulling every document back: + +```sql +-- the shape of the check, against whatever table holds the synced copies +select external_id, length(content) from system_documents +where external_id is not null order by external_id; +``` + +then diff those against the files on disk. In one checked sync, 21 documents +across two systems matched exactly — which is the result to expect and worth +recording, because the interesting version of this check is the day it does not. + +**The failure this guards against is silent by construction.** Earlier the same +day, a server rejecting *every* batch produced no error anywhere a person would +see it; the workspace looked fine and the platform was hours behind. A length +comparison would have shown it in one query. + +**A document nobody can find has not been written.** After producing or +superseding anything, update the corpus index — usually `docs/00-overview.md` or +a README table — and then check the index against the filesystem: + +```bash +ls docs/*.md | xargs -n1 basename | while read f; do + grep -q "$f" docs/00-overview.md || echo "unlisted: $f" +done +``` + +One pass had written five documents, five ADRs and a guide, +synced them, and audited them — and listed **none of them** in the index, which +still routed readers to a document the same pass had marked superseded. The +corpus was correct and unreachable. + +**An index row carries a status, not just a name.** *Exists* and *is in force* +are different facts, and a filename alone conveys the first while implying the +second. One index gained nineteen rows; reading each document +to write its row revealed that one was a **tombstone** — its content had moved +months earlier and the file remained as a pointer — and another was an explicit +**proposal that was never ratified**. A reader meeting either as a bare filename +would have taken it for current guidance. + +Use a status vocabulary that distinguishes them — `derived`, `decided`, +`proposed`, `superseded`, `moved`, `living`, `operational`, `report` — and take +each row's purpose from that document's own heading and opening line. That keeps +the job mechanical enough to finish, and stops the index asserting things the +documents do not. + +Two cautions from running it. **Wildcard rows are invisible to exact matching**: +a `10–17 | 10-.md …` row legitimately covers eight files, and the naive +check called all eight missing — inspect the hits. And **listing is not +describing**: adding a filename with no one-line purpose makes the index longer +without making the corpus navigable, so where a backlog of unlisted documents +exists, record it as editorial work rather than padding the table. + +### Staleness travels in groups + +#### Enumerate, then diff + +**Audit an inventory against the thing it inventories.** A document that lists +*what exists* — a structure table, a context register, an endpoint table — makes +a claim no other check can reach. Citations resolve, counts match, prose stays +coherent, and the list is still missing things it purports to enumerate. Nothing +contradicts an absence. + +Three instances in one session, each found this way and by +nothing else: + +| Inventory | Checked against | Found | +|---|---|---| +| `00`'s repository structure table | the filesystem, and `AGENTS.md` | **4 of 9 subtrees missing**, including the decided auth boundary | +| an overview's *"the pass seeded one context"* | `ls tasks/*-REQUIREMENTS.md` | **eleven** ledgers existed | +| an architecture note's endpoint table | the router's mount block | 2 paths wrong, **2 endpoints absent** | + +**Enumerate the real thing, then diff the document against it — not the reverse.** +Reading the document and checking each entry exists finds *wrong* entries and +never *missing* ones, and missing is the half that rots. + +**"The real thing" is itself a derivation, and the first one is usually wrong.** +Enumerating one estate's tables gave **171** from `create table(`; the +real figure was **112** once 59 drops and 7 renames were applied — a 53% +overstatement that would have manufactured a finding. And 112 was still the wrong +denominator: the document claimed to map *Ecto schemas*, of which there were +**148**. **Enumerate the population the document claims to cover, not the one +that greps most easily** — and for anything with a history (migrations, changelogs, +event logs) the current set is creations *minus removals*, never creations. + +**A missing entry has three degrees, and only one is a defect.** Reporting all 49 +omissions as "undocumented" would have been false and would have buried the part +that mattered. Separate them before writing a word: + +1. **In a sibling document, absent here** — the two disagree; say which is more + current (29 of them; `02` was ahead of `40`). +2. **Specified elsewhere, but in no index or map** — the spec exists and the map + never caught up; point at its real home rather than restating it. +3. **Nowhere at all** — the only genuine gap. Six of the forty-nine, and the + only ones worth a requirement. + +Collapsing these into one number is how an audit becomes noise: a reader who +checks two entries, finds them documented elsewhere, and stops will discount the +whole finding — including the six that were real. + +#### Scoping an audit honestly + +**Before writing "not checked in this pass", price it.** Once a +banner was written saying the reverse comparison had not been run — and running +it took one command and found two real errors. The known-unknown note exists for +audits that would be **expensive or noisy**, not for ones a minute would settle; +used as a default it turns honest scoping into a licence to skip. The test is +cheap to apply: if you can state precisely what the check would be, you are +usually already most of the way through doing it. + +**Sometimes the right call is not to run the diff — then say what that leaves +unchecked.** A realtime catalog scoped itself to *cross-context* events; the code +carried 59 message atoms across 111 broadcast sites, most of them intra-context +progress. Diffing those would have produced dozens of false gaps and taught +everyone to ignore the next audit. Declining was correct. **Silence about it was +not** — the document now names the narrower claim nobody has verified: that the +catalog is *closed*. An audit you chose not to run is a known unknown, and +writing it down is the difference between scoping and quietly implying coverage. + +**An audit banner suppresses the next audit, so record which direction it ran.** +One API contract carried a prominent *"Tag audit: +69 endpoints tagged EXISTS, of which 5 are not in the router"* +— specific, honest, recent, and one-directional. It checked each documented entry +against the router and never the router against the document. Enumerating the +router found **264 routes across 42 prefixes** and a *"read first"* paragraph +claiming six whole contexts had **no JSON API**; all six had one, and the +controllers predated the audit by a month. The banner is why nobody looked: a +document that says it was verified reads as verified. **Write what was compared +against what** — *"every EXISTS tag checked against the router; the router was +not checked against this document"* — so the next pass knows which half is +unexamined instead of inferring both. + +**And weigh the two directions differently.** A documented-but-absent entry fails +loudly the first time someone calls it. A real-but-undocumented one fails as +silent duplicated work — six contexts' worth of backend scheduled that already +existed. The cheap direction is the one that gets audited; the expensive one is +the one that needs you to enumerate. + +For an HTTP inventory specifically, three things make the enumeration wrong if +you skip them: parse **scopes** so nested prefixes compose into full paths, drop +**comment lines** (a commented-out route reads as live), and remember that a +framework's non-verb route macros — Phoenix's `live`, mounts, forwards — are not +matched by a verb regex and are not JSON either. Getting any of those wrong +changes the count by enough to invent or hide a finding. + +#### After you correct something + +**A stale claim is rarely alone — grep for it after you correct it.** A document +states the same fact in more than one place: once in prose and once in a "key +patterns" list, once in an overview and once in a table. One +wrong migration mechanism was corrected in a schema section while an identical +claim sat forty lines below in a patterns list, and shipped uncorrected. After +every fix, search the corpus for the distinctive phrase and the thing it names. + +**A correction banner is not a correction, and it is worse than none.** A +runbook was once corrected by adding a *"superseded in part"* note +to the section that described a removed credential — and two later paragraphs in +the same file went on describing it as in use, one of them in the Notes section a +reader actually lands on. The banner made the document look maintained, which +made the surviving stale claims look reviewed. Either correct every instance or +don't signal that you did. + +**And the fact escapes the document.** The same retired credential was still +being handed to operators by `.kamal/secrets-common.example`, which told them to +mint it and set it. A fact lives in prose, in the example config that +operationalizes it, in templates, and in the code that reads the variable — and +correcting the prose is the easiest of the four. After correcting a fact, grep +the **repository** for the identifier, not the document for the sentence. + +That sweep is also where the real finding usually is. Chasing which of two +contradictory paragraphs was true is what surfaced that the credential was gone +from the *deployment* and still live in the *code* — a difference neither +paragraph stated, and a better result than picking a winner between them. **Treat +a document contradicting itself as evidence about the system, not as a typo.** + +**Audit the whole document, not the section you came for.** The same pass that +found the schema section six tables short stopped there — and the service +sections, unexamined, said "four job types" where the enum had five. A document +drifts uniformly; a section that is stale is evidence about its neighbours, not +an isolated defect. + +**And be as ready to be wrong as the document is.** In the same audit the pass +suspected `google-genai SDK directly` was stale, because the dependency manifest +listed `spaik-sdk`. Both were true: the backend goes through `spaik-sdk`, the +worker imports `google.genai` directly, and the *pass's own* document was the one +with the incomplete claim. Check the code before correcting a document — the +existing text may be recording something you have not found yet. + +#### Claims that age without being edited + +**A claim with a shelf life carries the moment it was true.** Counts, hashes, +versions, "currently N" — these are measurements, not facts, and a document that +states them bare will be wrong without ever being edited. + +Two ways to keep them honest, in order of preference: + +1. **Write the invariant, and let the instance illustrate it.** *"A credential + ping and a generation call carry different timeouts by design"* survives a new + vendor; *"six of the eleven adapters use 10 seconds"* does not. +2. **Where the number is the point, date it** — `RUN:` in the same + sentence, and say which direction it moves. *"88 of 132 paths resolved when + measured, a figure that falls as checkouts are pruned"* tells a reader both + what was true and how to think about it later. + +A byte-hash asserting that three files are identical is the sharp case: the +durable claim is *"a lockstep test fails if they drift"*; the hash is evidence for +a moment and needs its date beside it. + +**Traceability is bidirectional, and only one direction is ever checked.** A +ledger row citing a test is audited to death — the path resolves, the line exists, +the extension is right. Whether the **test** names the requirement is checked by +nothing, and that is the direction that survives the ledger being reorganised: a +test carrying `REQ-PLN-061` in its `describe` can be traced back from the code +even if every ledger row is rewritten. + +Measure it, because the number is not what you expect. On one measured workspace, +**129 of 181** cited test files named a requirement that cites them — **52 did +not**, in a workspace whose ledger citations were 100% resolving. + +Two cautions, both learned by getting it wrong. **Recognise a test by its path or +suffix**, never by the word *test* appearing in a filename: the first run counted +58 because it matched `specification_pipeline.ex` and `test_execution.ex`, which +are implementation. And **naming any one covering requirement is enough** — a test +covering three requirements does not need three ids, and demanding that turns +traceability into bookkeeping nobody maintains. + +**Abbreviation is how a correct citation becomes a broken one.** Sweeping 1,359 +file paths across a workspace's ledgers found **two** +failures, and neither was stale — both were *shortened*. One dropped a directory +segment (`controllers/app_token_controller.ex` for +`controllers/auth/app_token_controller.ex`); the other dropped a filename prefix +(`bridges_test.exs` for `work_events_bridges_test.exs`), in a sentence that had +just named `bridges.ex`, so the shorthand read naturally to whoever wrote it. + +That is the failure mode to expect in a mature corpus. A citation is rarely wrong +because the file moved — a move breaks a build. It is wrong because a writer +mid-sentence wrote the part a human reader would need and dropped the part a tool +needs. **Paste paths; never retype them**, and be most suspicious of the second +mention of a file, where the writer already has the context and the reader of the +tool does not. + +**Every citation resolves from the repository root**, not from wherever the code +felt close. In a monorepo with vendored subtrees this is not pedantry: in one measured workspace +**19 of 40** citations in one workspace's documents were written +relative to a subtree — `apps/core/lib/...` — which exists only under the +vendored subtree's own root. They read correctly beside the code and +cannot be opened by a reader at the root, which is where the document lives. + +Set the checker's working directory to the repository root and let it fail +loudly; a citation that only resolves after a human guesses the prefix is not a +citation. + +### Your own workspace, and documents against each other + +**But not on the prompts — and the tool now refuses.** Teaching material quotes +citations that must not resolve: an elided path shown as the thing *not* to +write, a template row citing `domain/user.ts`, a finding cited from the +repository it was found in. Pointed at `.claude/` or the installed `.modernpath/rdd/skills/` the audit reports broken and +elided paths that are all correct prose, and the obvious next move deletes the +lesson. It exits 2 with an explanation instead; `--force-prompts` overrides it for +a reader who will inspect every hit. + +That refusal was priced before it was written, not assumed: **132 backticked +paths across the prompts, 21 unresolved, all 21 legitimate** — templates, other +repositories, and paths the skill *instructs a pass to create*. Four looked like +real errors until opened. Nothing was being missed. + +**Run this audit on your own workspace, not only on the one you are analysing.** +The same pass that fixed elided citations in a client repository had left three +of them, and nineteen subtree-relative paths, in its own — because it had never +pointed the check at itself. + +**Line numbers rot faster than paths.** A citation surviving this check proves +the file exists and the line is in range, not that the line still says what you +claimed. Spot-check a sample by reading them back; cite a range when the exact +line is likely to move. + +**Cross-check the documents against each other, not only against the code.** +Where two documents in one repository describe the same set, the disagreement +tells you which one is being maintained. + +`ARCHITECTURE.md` was six tables short and named two that had +been dropped — while `docs/02-bounded-contexts.md`, sitting beside it, listed +**all 34 correctly**, every one owned by exactly one context. The pass had +adopted the stale document as `03` without noticing the accurate one next door. + +So when you adopt, compare the candidate against the corpus first. The document +that agrees with the code is the one to adopt or to extend from; the one that +disagrees is a finding, and often tells you *when* maintenance stopped. + +**Read the siblings before you enumerate the code — it is the cheaper audit, and +often the same finding.** One realtime map listed three PubSub +topics that do not exist. Finding it by enumeration meant extracting 111 +broadcast call sites across 1,075 files, and the first two extractors were wrong +(one matched progress payloads, not topics). **Two sibling documents already said +all three were net-new** — one called that context "mostly new projections", +another called the feature net-new over a flat column, and the third put the data +in a table column rather than behind a topic. Minutes, not an extractor. + +**And weigh agreement by count.** Three documents agreeing against a fourth is +stronger evidence than any single pairwise comparison, and it points at the +outlier without needing the code at all. Use the code to *confirm* the outlier, +not to discover it. + +**A check is useful when you can afford to inspect every hit.** That is the +threshold to iterate towards, and it usually takes two or three attempts at the +instrument rather than one. + +On one pass, verifying a data model's column claims: the first attempt +checked every backticked token in each row and returned **39 suspects out of +69** — it was reading the writer-service column as if those were column names. +Narrowing to the constraints cell and stripping enum braces gave **33 checked, 3 +hits**, and all three turned out to be values rather than columns. Three is +reviewable by hand; thirty-nine is a pile nobody reads, and a check nobody reads +is worse than none because it looks like diligence. + +Tighten the extraction until the hit list is short enough to read, then read all +of it. **Never report the raw hit list as findings** — every hit needs a human +look before it becomes a claim. + +**Exclude what the language treats as inert, and read the document's own caveats +before reporting a gap.** An extractor that cannot tell live code from commented +code manufactures findings. + +One route audit reported three surfaces missing from a document +titled *"every view, its actors, its use case"*. All three were in a commented-out +legacy block — and the document already listed them, with ten others, under a +**Dead surface** heading, citing the exact line range and raising a question about +the two admin routes still live above them. The document was more thorough than +the check. + +So before a gap becomes a finding: strip comments and disabled blocks from what +you extract, then search the document for the thing you think is missing — +including its "dead", "deprecated", "not covered" and "open questions" sections. +Documents written by a careful pass usually record their own exclusions, and a +gap that is already named is not a gap. + +### Report what held, not only what broke + +> How a *matcher* fails — false alarms against false passes, the danger in the +> "certain" bucket, counting what a pattern did not match — is in **Citations** +> above, where a pass first writes a checker. This section is what you then say. + +#### Reading a result before acting on it + +**Re-read the evidence at the moment you decide to act, not when you measure.** A +count is reported once and reused — in a row, a summary, the next decision — each +reuse further from the instrument. When a number is about to become a change, +**open two of the things it counts.** It costs a minute and is the last point at +which a measurement error is cheap. (A finding of *"3 rows with no question"* +survived measurement, a requirement, a commit and publication; acting on it opened +all three and every one had its question, in a field the matcher could not see.) + +**Watch for rules that grew around a bad number.** That finding had already +sprouted an acceptance criterion derived from a population that did not exist. A +wrong count does not merely misreport — it becomes policy, and the policy outlives +the correction unless you go looking for it. + +**A cross-reference is a claim, and its qualifier is the load-bearing part.** A +decision listing *"**Resolves:** OQ-101, OQ-102, …"* looks like authorisation to +close them; the same line ends *"(theme T-B **partial**)"*. **Batch-closing on a +cross-reference asserts a completeness its own author declined to claim** — and it +is tempting because it makes a number go down. When a reference resolves N things +at once, read what it says about *itself* first. + +**Read the false positives before dismissing them — sometimes they are the +finding.** A sweep scored 146 cited, 144 resolved; the two failures were library +modules the enumeration had not scanned. Dismissing them was correct about the +sweep and would have lost the result: one was `Ecto.Enum`, and a canonical +document stated flatly that `Ecto.Enum` is not used here. Ten schemas use it. Ask +what made a false positive *look* plausible before deleting it. + +#### When nothing was wrong + +**A clean audit is a result, and needs a banner as much as a dirty one.** Record +what was compared and what was left unchecked — an unbannered document invites the +next pass to redo the work, and *"we checked and it held"* is what stops that. + +**A clean audit is the cheapest moment to mechanise the rule it just confirmed.** +The sweep has done the expensive half: it established the corpus is clean, so the +new check goes green on the first run and you never mix enforcement with cleanup. +Add the same check after it breaks and you must fix N violations and land the +guard in one change — which is when guards get watered down to fit the mess. + +**And the exemptions come out of the sweep.** When a rule held across 948 rows +with four exceptions, all one status, that status became the carve-out. Had they +been a status the rule should cover, the answer would differ — and no amount of +thinking in advance would have said so. + +→ Every time an audit comes back clean, ask what one-line check would keep it that +way, and whether anything enforces it today. + +#### Writing the report + +**Say what held, not only what broke.** A derived document that passes its audit +is a result, and a report listing only corrections implies the rest was unread. + +**A finding needs its counterweight when the counterweight changes how it reads.** +*"Four optional credentials behave four different ways"* reads as carelessness +until you add that the two which would compromise the system fail loudly at boot. +Same facts, opposite conclusion. + +**Record what you checked and found correct, not only what you fixed.** Otherwise +the next pass re-derives it, and a corpus accumulates repeated audits of the same +clean thing while the unexamined half stays unexamined. + +**A confirmation you wrote yourself is not evidence.** Prove the instrument can +report the other answer before believing this one. The shapes that lie: + +- a check reporting **perfection** — feed it something known-broken; +- a check reporting **catastrophe** — a real document is rarely 80% wrong; +- a script printing **"done"** unconditionally; +- a **runtime probe** answering from the wrong state (`function_exported?` before + the module is loaded); +- an **exit status from the wrong command** — a pipeline returns the last stage's + status, so `… | tail` reports the success of `tail`. + +#### What actually rots + +**It is not documents that rot, it is *claims about sets*.** A corpus wrong about +which enums exist scored 146 of 146 on the module names it cited. Named references +hold — someone wrote the name while looking at it, and a rename breaks a build +long before it breaks a document. Quantified claims do not: + +| Shape | Example | Ages | +|---|---|---| +| Named reference | *"`Aiengine.Repo` injects the tenant filter"* | slowly — checkable and load-bearing | +| Coverage claim | *"every endpoint named here is in `07`"* | fast — new ones arrive without touching the document | +| Convention claim | *"enums are never `Ecto.Enum`"* | fast and silently — one exception falsifies it | +| Bare count | *"the ~80 Ecto schemas"* | fastest — wrong without anyone editing anything | + +**Aim an audit at the quantifiers** — *every, all, none, never, only, the N* — not +at the nouns. Grepping for those finds more rot per minute than reading, and each +hit settles with one command. + +**The quantifier is not what makes a claim fragile — writing it without opening +the code is.** The same sweep against a *derived* corpus found every absolute +holding. A derived pass states an absolute only after enumerating, so its +absolutes survive; a design document states one as an intention, and intentions +acquire exceptions silently. That is where to spend an audit first. + +Two cautions when running it. Most quantifier hits in a UX or product document are +**prescriptive** — *"never display a secret after creation"* is a requirement, not +a claim about code, and checking it against code is a category error. And scope +the check to the **declaration** you are testing: counting constraints in a window +around a class catches its neighbours. + +### Ids, capture, and siblings + +**Every id a document cites must be defined somewhere — check both directions.** +An id cited but never defined is a dangling reference; one defined but never cited +is either dead, or a gap in the document that should point at it. + +**Scope the check to the ids this workspace owns.** A platform that analyses other +repositories will carry ids belonging to them; a sweep that does not scope reports +those as dangling forever. + +**Register by reference, not by copy.** The full statement belongs in one place; +everywhere else cites its id. Two copies of a question diverge, and the reader +cannot tell which is current. + +**A finding recorded only in prose is not captured.** An audit ending with a note +inside the document it audited has told the document about itself. A discovery +becomes a ledger row or a backlog line, with provenance, or it is carried rather +than captured. + +**Cross-check siblings.** Where two documents describe the same set, the +disagreement tells you which one is being maintained. + +### Audit this document too + +**A prompt's own counts rot like any other document's.** An earlier version of this material said *"Three phases, in this order"* directly above a block listing +**four** — the sentence was written when there were three and never revisited +when D was added. It had shipped that way to two workspaces. + +Sweep your own stated counts against the thing they count, the same way you would +sweep a document you inherited: + +```bash +# the leading [^-[:alnum:]] matters: \b alone matches "six" inside "Thirty-six" +grep -onE '(^|[^-[:alnum:]])(two|three|four|five|six|seven|eight|nine|ten)[[:space:]]+(phases|documents|rules|steps|files|ways|copies)' SKILL.md +``` + +That refinement came from running this very command on this very +file: of 14 hits, one was *"six documents"* matched inside +*"Thirty-six documents"*. It is the same substring trap that has now produced a +false finding three times in one session — `NFR` inside `INFRA`, `Q-AGT-001` +inside `REQ-AGT-001`, and this. **A word boundary is not a word boundary when the +neighbouring character is a hyphen.** + +Then check each against its list. Here *"five documents"* matched five rows and +*"three files"* matched the contract's three copies — one defect in four claims, +which is roughly the hit rate to expect from any document nobody has counted. + +**Audit this document too — a prompt is a document.** Everything above applies to +the skill itself, and it fails the same ways. + +Once a rule was inserted by anchoring on a phrase, and the anchor +sat **inside a numbered checklist**: the new rule became "step 6", its code block +landed between steps, and the two-line step it displaced vanished. Nothing +errored. The skill still rendered. It was found by counting the steps and reading +them back — the same check the sequence itself prescribes. + +So after editing any structured document programmatically, verify the +**structure**, not just the text: item counts, balanced code fences, headings +still where they were. An anchored replacement is a blind edit, and prose anchors +are unstable exactly where documents are most structured. + +### The audit as a sequence + +The rules above are the reasoning; this is the order to run them in. Each step is +minutes, and each one has caught a real defect. + +1. **Groundedness.** `grep -rl 'CODE:' docs/ | wc -l` against the document count. + Zero citations over a real codebase is the headline finding, not a pass. +2. **Citations resolve**, from the repository root, no elided paths. The + shipped script does the whole corpus in seconds. +3. **Read a sample back.** Five citations, opened, compared to the sentence that + cites them. Resolution is not correctness. +4. **One countable set per document** — tables against `__tablename__`, routes + against the router, ports against what compose publishes, commands against + `package.json`. Probe the check with a value you know is absent. +5. **Status tags.** If the document marks claims EXISTS/TODO or built/planned, + verify those — they are the sharpest promise it makes. +6. **Ids resolve, and siblings agree.** Every `Q-`/`REQ-`/`ADR-` id cited is + defined somewhere; where two documents describe the same set, the + disagreement names the stale one. +7. **The runbook** — every URL, port and command a reader will type. +8. **The index.** Every document listed, with a status; every listed document + present. +9. **The published copy**, if the corpus syncs anywhere. +10. **Record what held**, with the date, inside the documents themselves. + +**Inspect every hit before writing any of it down.** Across one session this +sequence produced four false findings — an ASCII tree read as paths, commented-out +routes read as live, a writer-service column read as column names, a +module-plus-function read as a missing module — and each looked exactly like a +real defect until it was opened. diff --git a/skills/rdd-build/SKILL.md b/skills/rdd-build/SKILL.md index 2753432..dc87dfb 100644 --- a/skills/rdd-build/SKILL.md +++ b/skills/rdd-build/SKILL.md @@ -1,6 +1,6 @@ --- name: rdd-build -description: Execute the AI-owned TDD loop for one approved system requirement from TODO through current lower evidence and IN_REVIEW. Use for new or changed SR behavior after human entry approval. Repeat RED, GREEN, cleanup, lower verification, and separate affected-UR upper validation until the approved trace needs pass or an exact replanning condition is found. Use rdd-verify instead for confirmed as-built PENDING_VERIFICATION behavior. +description: Build or correct one approved SR through current evidence and IN_REVIEW. Use after entry for changed behavior or an in-scope review/engineering failure, reopening reviewed work through the canonical correction route. Repeat focused RED/GREEN, cleanup, and separate upper validation as appropriate. Use rdd-verify for confirmed as-built evidence work. --- # Build one SR slice @@ -13,12 +13,18 @@ code, tests, and current records. ## Procedure 1. Orient and select exactly one approved `TODO` or `IN_PROGRESS` SR with - current planning and no active hold. Work on a reviewable feature branch and - preserve unrelated changes. + current planning and no active hold. For a reviewed item, first record and + apply the scoped review-correction demotion in `PROCESS.md`, including the + unchanged approval and required reruns. Work on a reviewable feature branch + and preserve unrelated changes. 2. Before its first implementation iteration, establish every selected affected - UR's upper RED for the expected reason. Keep that evidence on the UR. + UR's upper RED for the expected reason unless admissible retained RED already + covers the unchanged scenario/assertions. Keep that evidence on the UR. 3. Select one unmet approved SR clause, establish its focused lower RED for the - expected reason, and link the stable test identity to the clause. + expected reason, and link the stable test identity to the clause. For a + behavioral review defect, reproduce the approved lower or upper failure. + For a behavior-preserving engineering correction, select the recorded EC or + review finding instead; preserve retained RED and do not invent a clause. 4. Implement the smallest behavior that makes the focused evidence pass. Capture discoveries instead of silently expanding scope. 5. Perform requirement-scoped cleanup or record a no-op. Return to RED if the @@ -28,7 +34,10 @@ code, tests, and current records. evidence, including live-browser and screenshot evidence for UI behavior. 7. Re-evaluate the selected SR clauses and affected UR scenarios. Repeat from step 3 while an unmet result is caused by approved behavior in this SR. -8. Move the SR to `IN_REVIEW` when its lower trace passes. An affected UR moves + Rerun the failed engineering/review check and resolve its finding before + closing a correction; candidate checks evaluate the final corrected code. +8. Move the SR to `IN_REVIEW` when its lower trace passes and its corrective + findings are resolved by the required rechecks. An affected UR moves to `IN_REVIEW` only when its upper trace passes and every required SR is `IN_REVIEW` or `DONE`. 9. Reconcile the affected graph and derived views. Return remaining approved @@ -45,3 +54,12 @@ workflow, or material technical decision. Record an external blocker exactly. Report the planning revision, RED and passing observations, code and test references, cleanup, final gates, status changes, discoveries, gaps, and the exact next skill or hold. + +## Execution contract + +- Input: one SR with current entry authority, no hold, and an unmet approved + clause or sourced correction; reviewed work is reopened before edits. +- Writes: scoped code/tests, immutable run observations and assessments, + correction results, automatic transitions, and affected graph/next action. +- Exit: eligible `IN_REVIEW` work to completion, another approved correction, + or a precise changed-scope/human/external prerequisite. Never integrate here. diff --git a/skills/rdd-build/agents/openai.yaml b/skills/rdd-build/agents/openai.yaml index 5a9bdd1..0863478 100644 --- a/skills/rdd-build/agents/openai.yaml +++ b/skills/rdd-build/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "RDD Build" - short_description: "Execute one red-first SR slice" - default_prompt: "Use $rdd-build to execute the next approved SR slice." + short_description: "Build or correct an approved SR slice" + default_prompt: "Use $rdd-build to execute the next approved SR slice or in-scope review correction." diff --git a/skills/rdd-cold-review/SKILL.md b/skills/rdd-cold-review/SKILL.md index c416749..0345d9f 100644 --- a/skills/rdd-cold-review/SKILL.md +++ b/skills/rdd-cold-review/SKILL.md @@ -11,6 +11,13 @@ in a consuming repository), versioned product sources, selected requirements, optional epic/specifications, active engineering constraints, technical reconnaissance, and repository state at the recorded revision. +Use a fresh reviewer session/context that did not author the plan, or a separate +human reviewer. Supply the frozen planning manifest, authoritative sources, +selected records, EC set, and repository revision. Do not supply the author's +private reasoning as evidence. Record reviewer identity/context and reviewed +fingerprint. If an independent context cannot be obtained, report that unmet +prerequisite; loading this skill in the authoring conversation is insufficient. + ## Procedure 1. Audit the authoritative graph and selected scope without relying on @@ -29,7 +36,8 @@ reconnaissance, and repository state at the recorded revision. 6. Identify any product, architecture, acceptance, or scope choice that lacks human authority. 7. Record each finding with severity, direct source, owner, and disposition as - `OPEN`, `RESOLVED`, `DEFERRED`, or `REJECTED`. + `OPEN`, `RESOLVED`, `DEFERRED`, or `REJECTED`. Attach findings and verdict to + the reviewed inputs; do not mutate the planning snapshot to store outputs. 8. Return the cold-review trace gate `PASS` only when the engineering trace is current and passing and the material-finding rule in `PROCESS.md` is satisfied. Otherwise return `FAIL` with exact blockers. @@ -46,3 +54,12 @@ verdict as entry approval. Lead with material findings, then state the reviewed fingerprints, finding dispositions, engineering and cold-review trace-gate verdicts, and the exact handoff: `rdd-plan` after a failure or `rdd-entry-review` after a current pass. + +## Execution contract + +- Input: frozen planning inputs and independently inspectable sources in a + non-authoring reviewer context; item status alone is not proof of readiness. +- Writes: engineering/cold-review results, sourced findings and dispositions, + reviewer provenance, and next action; no plan or implementation correction. +- Exit: current PASS to entry review, or FAIL with the earliest affected + prerequisite. Plan changes require a new snapshot and dependent reviews. diff --git a/skills/rdd-completion-review/SKILL.md b/skills/rdd-completion-review/SKILL.md index dd02551..1f2d5f8 100644 --- a/skills/rdd-completion-review/SKILL.md +++ b/skills/rdd-completion-review/SKILL.md @@ -15,24 +15,33 @@ records, and derived views. 1. Treat completion as unproven. Audit every scoped requirement clause, acceptance scenario, declared relation, gate, evidence result, and completion condition against direct current sources. + Use `rdd-audit` on the candidate records, citations, and affected inventories + during this pre-delivery audit; its findings must be routed before integration. 2. Stop for any `DERIVED` dependency, candidate link counted as authoritative, - stale or inherited-unverified evidence, missing RED observation, material + stale or inherited-unverified required evidence, missing retained RED, material cold-review finding, undisclosed gap, or incomplete reconciliation. 3. Invoke `skills/rdd-engineering-check/SKILL.md` with target `CANDIDATE` against the completed pre-delivery code. Do not integrate unless the complete applicable active EC set has a current engineering trace `PASS`. + On an in-scope implementation failure, record the correction and reopen + affected reviewed items per `PROCESS.md`, then hand off to `rdd-build`. + Changed scope/policy goes through triage to planning. Do not manufacture + an evidence invalidation merely to make a correction eligible. 4. If the pre-delivery audit and candidate engineering check pass, deliver through the project's authorized integration path while keeping awaiting entities `IN_REVIEW`. -5. Re-run or confirm evidence against the delivered revision and reconcile all - authoritative records and derived views. Use `skills/rdd-audit/SKILL.md` to +5. Re-run or confirm passing/regression evidence against the delivered revision; + reassess historical RED for retention, without rerunning it on delivered + code. Reconcile authoritative records and derived views. Use + `skills/rdd-audit/SKILL.md` to verify that delivered records, citations, and documents still describe the code; a finding it surfaces is a stop condition or routes through `rdd-triage`, never a silent correction. 6. Invoke `skills/rdd-engineering-check/SKILL.md` with target `DELIVERED` and rerun or confirm the candidate checks against the delivered fingerprint. Record a separate delivered engineering result and stop unless it is current - and `PASS`. + and `PASS`. Route a failure through the same correction/planning decision; + a delivered failure requires a corrective delivery, not completion approval. 7. Record completion trace `PASS` only for the exact eligible fingerprint. Only then make the scoped human completion gate `OPEN` and present its brief. 8. Do not answer the gate for the human. If the authorized human answers, @@ -48,3 +57,13 @@ Report proven, contradicted, incomplete, indirect, and missing completion facts; delivered revision; reconciliation result; gate states; applied transitions; applicable ECs and planning/candidate/delivered engineering verdicts; and remaining work. + +## Execution contract + +- Input: a completion-ready selection with `IN_REVIEW`/unchanged `DONE` members, + role-appropriate evidence, review results, and an authorized integration path. +- Writes: candidate/delivered results, authorized integration, reconciled records, + completion gates and attributable applications; preserve already-DONE members. +- Exit: accepted named items DONE, an exact OPEN human gate, or correction/plan/ + external hold with affected ids and next action. No integration after a failed + candidate audit or engineering check. diff --git a/skills/rdd-deliver/SKILL.md b/skills/rdd-deliver/SKILL.md index b195c05..f80dd89 100644 --- a/skills/rdd-deliver/SKILL.md +++ b/skills/rdd-deliver/SKILL.md @@ -18,12 +18,17 @@ all semantics; this skill owns phase order and continuation. prerequisite. Never start from the most convenient phase. 3. If input is not authoritative or is `DERIVED`, apply `rdd-discover` and its confirmation gate. Continue only with confirmed requirements and relations. -4. Apply `rdd-plan`, then `rdd-cold-review` with its separate engineering check, - then `rdd-entry-review`. Repeat from the earliest stale or failed pass until - the exact selected scope is `TODO`. +4. For items lacking current entry authority, apply `rdd-plan`, independent + `rdd-cold-review` with its engineering check, then `rdd-entry-review`. Repeat + from the earliest stale or failed prerequisite until the affected subset has + current applied approval. New entrants become `TODO`; preserve unchanged + approved `IN_PROGRESS`, `IN_REVIEW`, and `DONE` items. 5. Run the AI TDD inner loop below. Apply `rdd-build` to changed SRs and `rdd-verify` to human-confirmed as-built URs or SRs. Continue until every - selected requirement satisfies its applicable trace and is `IN_REVIEW`. + selected requirement satisfies its applicable trace and is `IN_REVIEW` or + `DONE`. Use the review-correction route for an in-scope failure on reviewed + work; do not wait for an unmet lower clause when the failure is engineering + conformance or an upper scenario. 6. Apply `rdd-completion-review` to audit, deliver, recheck the delivered revision, reconcile records, run the completion trace gate, and apply the human completion answer. @@ -42,8 +47,9 @@ remains unchanged: 1. Evaluate every selected UR upper trace and SR lower trace. Establish any required initial RED observations. -2. Select the next unmet approved SR clause. Apply `rdd-build` or `rdd-verify` - until its lower trace is current and passing. +2. Select the next unmet approved SR clause or recorded in-scope correction. + Reopen affected reviewed items per `PROCESS.md` before edits. Apply + `rdd-build` or `rdd-verify` until the required traces/checks pass. 3. Rerun affected UR scenarios and update their separate upper evidence. 4. Repeat for any failing or stale approved trace. Do not stop after the first GREEN result or completed SR while another selected trace remains unmet. @@ -73,3 +79,12 @@ Report the selected scope and fingerprint, completed phases, current lifecycle states, product/engineering/human gates, evidence and delivered revision, discoveries, and either the terminal result or the exact next phase and prerequisite. + +## Execution contract + +- Input: selected scope, per-item approvals, evidence assessments, review + findings, and next-action record; mixed states are expected on resume. +- Writes: only the records and implementation authorized by each invoked pass. +- Exit: `DONE`/`OBSOLETE`, or an exact human/external hold. A failed review routes + to scoped correction or the earliest invalidated planning pass, not an + unexecutable handoff. Record affected ids, current inputs, and next action. diff --git a/skills/rdd-discover/SKILL.md b/skills/rdd-discover/SKILL.md index 02a1bca..760074b 100644 --- a/skills/rdd-discover/SKILL.md +++ b/skills/rdd-discover/SKILL.md @@ -37,3 +37,11 @@ Derived requirement hold, Gates, and Discoveries/releases/conflicts. Report changed sources, confirmed facts, `DERIVED` candidates, conflicts, confirmation-gate results, questions requiring human authority, and either the next eligible planning input or exact hold. + +## Execution contract + +- Input: raw sources, observations, or candidates; no downstream approval assumed. +- Writes: sourced documentation, candidate/confirmation records, attributable + confirmation applications, and next action; no tests or implementation. +- Exit: authoritative planning input or an exact confirmation/decision hold; + candidate relations remain separate from authoritative scope. diff --git a/skills/rdd-engineering-check/SKILL.md b/skills/rdd-engineering-check/SKILL.md index 1b2c2da..4b62845 100644 --- a/skills/rdd-engineering-check/SKILL.md +++ b/skills/rdd-engineering-check/SKILL.md @@ -36,15 +36,18 @@ reconnaissance, relevant code and configuration, and current gate records. is complete, every applicable EC is proven conformant, and no applicability conflict is unresolved. If no EC applies, record `0 applicable`, the resolution basis, and `PASS`; silence is not an evaluated result. -6. Apply staleness only from the inputs owned by that result: - - `PLANNING` becomes `STALE` when the selected scope, packet, +6. Apply staleness when any recorded input in that result's kind-specific + manifest changes, never from its own output or lifecycle status: + - `PLANNING` becomes `STALE` when the selected scope, planning inputs, reconnaissance revision or affected surface, applicable EC set, or an applicable EC changes. Approved implementation changes alone do not stale - it. - - `CANDIDATE` becomes `STALE` when its candidate code fingerprint or - applicable EC set changes. - - `DELIVERED` becomes `STALE` when its delivered fingerprint or applicable - EC set changes. + it. Appending review findings, verdicts, or human answers does not alter + those inputs; use the fingerprint manifests defined by `PROCESS.md`. + - `CANDIDATE` becomes `STALE` when its candidate code/configuration, + revision, scope/planning reference, applicability, or verification inputs + change. + - `DELIVERED` becomes `STALE` when the corresponding delivered-target + inputs change. Keep the historical candidate result distinct. 7. Feed the findings and gate verdict to the invoking review. Candidate code that expands the approved affected surface invalidates planning rather than being treated as an ordinary code-fingerprint change. @@ -60,3 +63,12 @@ Report the evaluated fingerprint and revision; applicable EC ids with the scope match that selected each one; evidence and result per EC; exclusions, ambiguities, and findings; the engineering trace-gate verdict; and the exact handoff to the invoking cold or completion review. + +## Execution contract + +- Input: exact target kind/fingerprint, authoritative EC versions/effective + points, and resolved affected surface; no assumed lifecycle prerequisites. +- Writes: target-specific engineering results, per-EC evidence and findings; + no implementation, constraint-policy, approval, or lifecycle changes. +- Exit: PASS or exact FAIL to the invoking review. The caller routes unchanged- + scope implementation failures to review correction and changed inputs to plan. diff --git a/skills/rdd-entry-review/SKILL.md b/skills/rdd-entry-review/SKILL.md index bdd3a03..5d25254 100644 --- a/skills/rdd-entry-review/SKILL.md +++ b/skills/rdd-entry-review/SKILL.md @@ -18,7 +18,8 @@ packet, cold-review findings, current gate records, and relevant sources. stale reconnaissance, incomplete implementation context, inadequate RED strategy, a missing/failed/stale engineering trace, or unresolved material cold-review findings. -3. Record the entry trace gate against the exact content fingerprint. Keep the +3. Record the entry trace against the entry fingerprint: planning inputs plus + current prerequisite review references, excluding this gate's outputs. Keep the human gate `DRAFT` when the trace does not pass. 4. After a current trace `PASS`, make only the exact scoped human gate `OPEN` and present its brief and recommendation in **plain product language** — see @@ -30,11 +31,22 @@ packet, cold-review findings, current gate records, and relevant sources. record the real actor, role, scope, answer, and `USER:` source; apply only named transitions and reconcile all affected records. 6. Move approved named `PROPOSED` or `PENDING_VERIFICATION` requirements and - any named proposed epic to `TODO`. Otherwise retain the strongest honest - state and route requested changes. + any named proposed epic to `TODO`. Renew invalidated approvals only for the + named affected subset; preserve implementation history and each item's + strongest supported state. Unchanged approved members need no new entry + answer. Otherwise retain the strongest honest state and route changes. ## Report Report the entry-trace verdict, exact human-gate state, applied transitions, remaining blockers, and the exact handoff: `rdd-build`, `rdd-verify`, or the earliest planning pass that must be repeated. + +## Execution contract + +- Input: completed planning and independent review results, exact affected + approval subset, and current gates; existing approved members may be further on. +- Writes: entry trace, human gate, attributable answer/application, named + transitions, and reconciled next action; no implementation or invented answer. +- Exit: current applied entry authority, an OPEN human gate, or failed/stale + prerequisites routed to the earliest affected planning pass. diff --git a/skills/rdd-plan/SKILL.md b/skills/rdd-plan/SKILL.md index c31a6ba..cf701be 100644 --- a/skills/rdd-plan/SKILL.md +++ b/skills/rdd-plan/SKILL.md @@ -37,10 +37,21 @@ sections of `PROCESS.md`. trace prerequisites pass. Record blockers, conflicts, gaps, and deferrals rather than guessing. 7. Assemble Entry-packet items 1–6 and the product-language brief. Reconcile - planning records, then hand off to `rdd-cold-review` for item 7. + planning records and freeze the planning input manifest/fingerprint per + `PROCESS.md`. Hand that snapshot to `rdd-cold-review`; item 7 is an attached + review output, not an input to the planning fingerprint. ## Report Report the selected scope, authoritative graph, reconnaissance revision, applicable EC set and fingerprint, planned evidence, unresolved decisions, blockers, and cold-review input. + +## Execution contract + +- Input: authoritative selected items/relations and sources; no `DERIVED` + dependency or candidate link is used as authority. +- Writes: affected planning records, reconnaissance, initial EC resolution, + decision packets, and the versioned planning input manifest; no product edits. +- Exit: independent-review handoff with exact inputs, or a sourced ambiguity, + confirmation/decision gate, and the facts needed to resume. diff --git a/skills/rdd-reverse-engineer/SKILL.md b/skills/rdd-reverse-engineer/SKILL.md index 6f99da2..b690608 100644 --- a/skills/rdd-reverse-engineer/SKILL.md +++ b/skills/rdd-reverse-engineer/SKILL.md @@ -1,448 +1,170 @@ --- name: rdd-reverse-engineer -description: Bootstrap a requirement corpus from an existing codebase that has none — inventory observable behavior by bounded context against explicit denominators, create every inferred requirement as DERIVED with candidate-only relations, build exact confirmation packets, and hand confirmed scope to the normal delivery loop. Use to adopt a repository that has code but no authoritative requirement records; never for a workspace that already has them (use rdd-plan there), and never as a substitute for any phase — it creates no acceptance content, tests, release commitments, or authoritative relations. +description: Start or resume a bounded adoption campaign that derives requirement candidates from existing code. Inventory observable behavior, preserve confirmed records, record inferred URs/SRs as DERIVED with candidate-only relations, and hand confirmed scope to the standard delivery loop. Use for an empty corpus or explicitly authorized uncovered contexts, not to overwrite an established corpus or bypass entry. --- -# Bootstrap a corpus from an existing codebase - -An optional orchestrator over the standard process, not another lifecycle. A -repository arrives with a hundred thousand lines and no requirement records; -this pass gives it a corpus that says what the code observably does, marks -every inferred statement as awaiting human confirmation, and routes what a -human confirms into the same loop every other requirement travels. - -Read the project `AGENTS.md` and the canonical `PROCESS.md` -(`.modernpath/rdd/PROCESS.md` in a consuming repository) first. `PROCESS.md` -owns the `DERIVED` hold, the confirmation gate, and every status this pass may -apply; nothing here redefines them. - -## Preflight — when this pass applies - -1. **Stop if an authoritative requirement corpus exists** — a store binding - with requirement records, or populated `file-state/REQUIREMENTS.md` - equivalents. Re-deriving over a real corpus overwrites decisions people - made deliberately; use `rdd-plan` to extend it instead. A bare `tasks/` or - `docs/` directory proves nothing — other conventions use those names, so - check for the records, not the directory. -2. **Identify the process store** per `PROCESS.md` — store-backed or - file-backed — and serialize every record this pass creates through the - `file-state/` shapes for that store. This pass never invents a third - representation. - - **A ledger-format workspace materializes the requirement corpus too.** Where - a workspace's tooling reads `tasks/-REQUIREMENTS.md` — the shape the - `rdd-ledger` adapter and `modernpath factory sync` ingest — write the - corpus there as well as to `file-state/REQUIREMENTS.md`, one file per - context, in the dashboard/detail-block shape. **Keep the `UR-`/`SR-` id - prefix** — it is how a ledger row says which kind it is, and the store - routes user and system requirements to different tables. Writing every row - as `REQ-` files a user requirement under a system requirement's evidence - class, which is a silent loss, not a formatting choice. `REQ--NNN` - remains valid and still means a system requirement. - `file-state/` alone is not enough for those workspaces: in a store-backed - repository it is a projection, so a corpus that exists only there never - reaches the store: sync reads its `tasks/` glob, finds nothing, and coverage - reports zero rows over the whole tree after a complete pass. This - instruction already says so for Epics and for NFRs; saying it for the - requirement corpus is the same rule, not a new one. -3. **Take what analysis exists as a lens.** A platform knowledge core, a - maintained `ARCHITECTURE.md`, human-written guides — read them all before - the code. Each proposes; the code decides. Every claim this pass records - cites `CODE:`, `DOC:`, or `TEST:` sources it verified itself. A guide that - cannot be confirmed in code becomes an open question naming the guide, - never a silently adopted fact — and this pass never authors a guide, which - would launder its assumptions into an input. - -## Four phases, and the order is the method - -```text -A DOMAIN schema and analysis -> entities, invariants, contexts -B SURFACES every view, its actors -> journeys carrying candidate URs -C REQUIREMENTS entry points, both halves -> DERIVED candidates, cross-linked -D ARCHITECTURE the shape around it all -> recovered design documents -``` - -A–C loop, one bounded context per pass, until the context map lists no -context without records; phase D runs **once per system**, after at least one -full A–C pass, because its documents need the context map and the data model. -A pass that starts at C produces a corpus of refusals with no statement of -what the product is for — that question is settled in the schema and the -views, which an endpoint walk never visits. - -**Phase A** reads the schema directly — models, migrations, constraints — -beside whatever analysis exists. For each entity: what it is, who writes it, -and what the schema enforces; those constraints are invariants nobody wrote -down anywhere else. Contexts are drawn by **aggregate ownership** — who -writes which table — never by route-file layout. - -**Phase B** walks every surface: which actors reach it (cite the **role -gate** — it is a fact in code), what each actor can do there, and which entry -points it calls. Group the views into user journeys; each journey is a -candidate epic carrying a candidate **user requirement** — an actor, an -outcome, and the views that serve it, every one cited. Candidate groupings -serialize to `file-state/EPICS.md` (ledger-format workspaces materialize an -`epics/` directory). - -**Phase C** derives candidates from the entry-point inventory below. -**Phase D** writes the recovered design documents, further down. - -## Inventory observable behavior — the denominators - -Enumerate mechanically, by bounded context, before deriving anything. The -counts are denominators; coverage is measured against them, and no class may -be silently omitted — a genuinely inapplicable class (no client app, no jobs) -is a stated fact in the report, not a skipped row. - -| Class | Enumerate | -|---|---| -| Entry points | every HTTP route, RPC procedure, worker, scheduled job, webhook, event handler, CLI command — and every agent/LLM tool surface, which carries its own authorization and is the class most often missed | -| Data models | every table/model, with the invariants the schema enforces — uniqueness, foreign keys, nullability, state machines | -| Access control | every guard, middleware, policy, role gate | -| User-visible flows | every route/view/flow in each client application, and which actors reach it (cite the gate — it is a fact in code) | -| Integrations | every external system, enumerated from injected credentials and configuration, not only from named modules — an adapter wired in config has no module to find | -| Tests | every test file, so existing evidence can be traced rather than rewritten | - -Draw context boundaries by who writes which aggregate, not by route-file -layout or deployment units — a map drawn from those describes the build -system, not the business. A table written by two contexts is a single-writer -violation: report it, never smooth it over. - -Work the entry-point list, not the error branches. For each entry point, -derive both halves: what it does and for whom, and what it refuses — the -rejection branches are where invariants live, and they are the requirements -most worth having. - -## Derive — one observable behavior, one candidate - -A requirement is something a change could **breach**. A sentence that merely -describes how the code is shaped cannot be breached — it is documentation, and -it belongs in the recovered design documents this pass writes alongside the -corpus, not in a requirement record. - -| Sentence | Verdict | -|---|---| -| "An event's type is at most 64 characters" | requirement — a change can breach it | -| "Only the lead may open a draft" | requirement | -| "An event's detail is unstructured JSON with no schema" | documentation — adding a schema breaches nothing | -| "The model and the migration agree, column for column" | a test, not a requirement | -| "Append-only is a convention, not a constraint" | a finding — record it as one | - -- One behavior, one candidate — not one function, one candidate. A - three-function validation chain enforcing one rule is one requirement; an - endpoint with five distinct observable behaviors is five. -- State behavior, never implementation: *"a refund reverses the VAT it - charged"*, not *"RefundService calls VatCalculator.reverse"*. -- Declare the canonical kind on every candidate: `UR` for an actor-outcome - behavior served by identified surfaces, `SR` for a system behavior at a - boundary. Group views into user journeys and derive candidate URs from - them — a UR carries the same evidence burden as an SR and needs it more, - because a user story reads as true even when nobody checked. Cite the view, - the route, and the gate, or leave it out. -- A hardcoded threshold is either a business rule nobody wrote down (a - candidate) or an accident (an open question). Never present an inferred - intent as a derived fact: code answers *what*, rarely *why* — a threshold - with no comment, a branch nobody can date, a rule contradicting another is - an open question for the confirmation packet, not a guess. - -While establishing these facts, prefer the artefact over the description of -it; read the whole expression, never the grep hit; search the entity alone -rather than conjoining verb and noun on one line (deletion code almost never -names its table beside the verb); exclude comment lines from behavioral -evidence while still citing a comment as evidence of what the code *says*; -and support an absence claim only with a named search — *"nothing does X"* -requires stating which files were opened to reject it, and a clean absence -after a real search is a finding to state plainly, with the search shown. -Where a decision is recorded but not deployed, write both halves labelled -**Decided** and **Deployed** — they are different facts with different -evidence. - -Link candidate URs and SRs both ways, then report the **join report**: a view -calling an entry point that does not exist is a broken or unfinished surface; -an entry point no view calls is dead surface or an undocumented integration. -Neither is visible from one side alone, and an empty join over a codebase of -any size is a claim that needs its own evidence. - -## Everything lands DERIVED - -Every inferred requirement is created `DERIVED`, exactly as `PROCESS.md` -§Derived requirement hold prescribes: candidate statement, inference sources, -proposed relations all labelled `CANDIDATE`, conflicts, consequences, and a -confirmation brief — the **Candidate packet** slot in the requirement shape. -`DERIVED` rows are excluded from authoritative trace, release, readiness, -coverage, progress, and completion, and this pass never creates or advances -anything related to them. - -The legacy failure this replaces: assigning `PENDING_VERIFICATION` directly -to derived rows. That status is human-confirmed as-built behavior; inference -is not confirmation, however good the citations. Only a human answer moves a -row out of `DERIVED`. - -Three routings that are not `DERIVED` candidates: - -- a **pre-existing red test** is work someone started, not shipped behavior — - record it as a discovery for `rdd-triage`, and say the suite was already - red there before this pass arrived; -- a code path that **provably cannot run as written** is a finding, not a - behavior; -- a discovery with unclear ownership or a cross-cutting concern goes to the - triage backlog shape, not the requirement shape. - -## The coverage contract — the only definition of "done enough" - -**The floor is 90% of every denominator class, the target is 100%, and the -claim is a script's exit code — never a sentence.** Two recorded failures -share one cause: a 46-row "complete" corpus over an 80-endpoint, -two-application estate, and a "110/110 endpoints routed" claim that audited -to 65/110 the same day. Both stated coverage as prose, and prose drifts -toward optimism. - -- For each denominator class, write the enumeration and matching as a small - script kept in the repository (e.g. `tools/endpoint-coverage-audit.py`) - that prints `N/N` per context, lists every miss, and - **exits non-zero below the floor** — coverage stays re-checkable by anyone, forever. The - report **embeds the scripts' verbatim output**; a coverage claim without - embedded audit output is invalid, and an orchestrator receiving one - re-runs the audit rather than relaying the claim. -- **No row budgets exist.** There is no such thing as a row budget: the - grain is one candidate per observable behavior, however many that yields. - A reference estate correctly derived carried **978 requirements** across - 13 contexts, at per-context densities of 50–130; a context reporting 6 - rows over 30 endpoints is under-derived, full stop. Grouping is legal only - where the observable behavior is genuinely one, and the grouped units are - always enumerated on the row. -- **Below the floor, the pass is not done.** Keep deriving, or hand off an - honest partial that names the precise remainder — a partial is a handoff, - never an endpoint, and a silent truncation reads as "covered everything". -- **No silent fallbacks.** A tool that fails — an analysis export missing, a - test runner broken, a script erroring — is named in the report with what - it blocked; work continues on every path that does not depend on it. -- Where the workspace ships a standing coverage instrument (for ModernPath - workspaces, `modernpath coverage --json`), run it before deriving anything - and pin its output as the pass's *before*; take targets from its - ranked uncovered directories rather than from taste, and passing over the - top-ranked gap needs a stated reason. At the end of the pass - the same command is the after — the delta is the pass's receipt, and a pass whose - delta on its declared target is zero did not happen, whatever its prose - says. An instrument's untraced test files list is standing input for - `rdd-verify`: an existing green test nobody traced is the cheapest - verification available once its row is confirmed. - -Before claiming anything, invoke `skills/rdd-audit/SKILL.md` over what this -pass produced: every citation resolves from the repository root -(§"Citations — does every reference resolve?"), every inventory is diffed in -both directions, and the coverage numbers carry their populations -(§"The audit"). - -### The full sweep — every context, one run, stated cost - -One context per pass is the default because it protects derivation rigor. -When the human explicitly asks for the complete adoption, the sweep is a -different contract, not a shortcut, and it runs unattended: -**one invocation loops** measure → target → derive → audit, taking the next ranked gap each -iteration, until every denominator-class floor passes **and** the -**file-coverage floor** passes — coverage lands **well over 50%** of the -source-file inventory (operationally, keep looping below 60% -cited-or-dispositioned; files legitimately outside the behavioral surface, -such as type barrels, migrations, and generated code, count only when -explicitly dispositioned in the report — a disposition is written, never -assumed). There is **no human checkpoint inside the loop**: candidates still -land `DERIVED`, and the confirmation gates open in the terminating report, -which embeds the final audit output as proof — deferring confirmation to the -end of the sweep is not skipping it. On an honest-partial handoff the -orchestrating agent **relaunches for the remainder** automatically rather -than reporting the partial and waiting; asking the human to notice -under-coverage is the failure mode this contract exists to prevent. - -## Phase D — the recovered design documents, once per system - -Phase C says what the system does; none of it says what the system *is*. - -Read `docs/guides/` — every human-written guide — before phase A begins. A -guide is a **lens**, never a source: it directs attention, every claim still -cites the code or config it came from, a guide that cannot be confirmed -becomes an open question naming the guide, and the pass never writes one. - -## D1. The five documents - -System-wide, written once — no per-context fan-out: - -| Document | Answers | +# Adopt an existing codebase + +Read the project `AGENTS.md`, canonical `PROCESS.md`, store binding, and any +existing adoption campaign in work selection. This is an optional orchestrator, +not a new lifecycle. Observed code establishes behavior, not intended authority. + +## Execution contract + +- Input: an authorized bounded adoption scope, repository baseline, store + binding, context inventory, and existing campaign/requirement/gate records. +- Writes: observational inventories/documents, independently sourced DERIVED + candidates, candidate-only relations, proposed ECs, confirmation packets, + attributable confirmation applications, and durable campaign progress. +- Exit: a bounded context handed to the normal loop, an exact confirmation or + external hold, or an honest partial with remaining observations and next action. + No product code, tests, release commitments, or inferred approvals are created. + +## Preflight and resume + +1. Identify and validate the authoritative store. Use its supported adapter; + file-backed projects write canonical shapes directly, store-backed projects + use their record API and refresh projections. Never write a projection as if + it were an import source. +2. Read the adoption campaign before testing whether records exist: + - With no corpus, record a new campaign for the user's requested scope. + - With the same campaign, resume uncovered or partial contexts. Existing + candidates and confirmed records from earlier passes are expected. + - With an unrelated established corpus, use discovery/planning unless the + human explicitly authorized bounded adoption of uncovered surface. +3. Record campaign id, authority, original baseline, latest inspected revision, + context inventory, stable observation-to-candidate keys, confirmation gates, + and next action in `WORK-SELECTION.md`. Skip handed-off contexts. Reconcile + partial contexts by id; never duplicate candidates or overwrite confirmed + statements, relations, dispositions, or gate answers. +4. Recheck observations affected by revision drift before reusing them. + Source changes that contradict confirmed records route through triage. +5. Read relevant existing architecture and product sources as context. Confirm + as-built claims directly in code/tests; retain intended-versus-observed + disagreement as a sourced question rather than rewriting human intent. + +For a ModernPath ledger-backed project, read +[the ModernPath adapter](references/modernpath-adapter.md) before serializing or +running its commands. Other projects use their own adapter contract; do not +assume ModernPath tooling is installed. + +## Inventory, then derive one bounded context + +Work in this order: + +1. **Domain:** inspect schemas, migrations, entities, invariants, writers and + readers. Propose context boundaries from aggregate ownership and observed + behavior, using existing authoritative boundaries when available. +2. **Surfaces:** enumerate actors, role gates, views and other interfaces, and + the entry points they reach. Identify user journeys and candidate URs. +3. **Behavior:** walk entry points and the implementation they call. Describe + both the successful outcome and material rejection/failure behavior. Derive + one candidate per independently describable behavior, not per function. +4. **Recovered design:** after a context pass establishes the map and data model, + update the system-wide observed design where useful. Read + [recovered-design guidance](references/recovered-design.md) for this output. + On later passes reconcile newly observed surface rather than creating + competing per-context architecture documents. + +Mechanically enumerate these populations before claiming coverage: + +| Class | Population | |---|---| -| `docs/03-architecture.md` | key subsystems, external interfaces, datastores, the path one request takes — the document a reader opens first | -| `docs/20-deployment-topology.md` | what runs where and what a request crosses; fold into `03` for a single-stack estate | -| `docs/21-integrations.md` | each external system — direction, protocol, and **what happens when it is unreachable**, which no dependency list gives you | -| `docs/22-cross-cutting.md` | auth, tenancy, secrets, observability, resilience *as implemented*, each concern naming its enforcement point | -| `docs/23-data-flow.md` | where a value originates, what transforms it, where it lands, which trust boundaries it crosses | - -Before writing any document, look for what the repository **already covers** -— a maintained architecture document, existing ADRs. Adopt it or extend it -in place rather than writing a competing file, and supersede only -deliberately, with a coverage diff proving nothing is lost. - -## D2. Decision records — `docs/adr/NNNN-.md` - -One record per decision the code has plainly already made — datastore, -transport, isolation, deployment shape — with status **`observed`**, a third -status beside accepted and superseded: the pass can prove a decision was -made, never that anyone ratified it, and an ADR claiming a ratification the -repository never performed is the same lie as a completion with no evidence. - -## D3. Non-functional requirements — `tasks/NFR-REQUIREMENTS.md` - -Ids follow the ledger convention, `REQ-NFR-NNN`, not `NFR--NNN`: the -ledger row regex matches `REQ--NNN` and parses nothing otherwise, so rows -written any other way are silently ingested as none. - -Quality attributes get their own requirement context, serialized like any -other — ledger-format workspaces materialize it at the path above — one -category per row from exactly eight: -`performance` · `scalability` · `availability` · `security` · `privacy` · -`operability` · `maintainability` · `compatibility`; an open list becomes -forty overlapping labels within two passes. Sweep for latent thresholds -(timeouts, pool sizes, retry counts, rate limits, cache TTLs, payload caps), -but first check whether the behavior **already has a requirement** — an NFR -row for a threshold another row owns is a duplicate wearing a different id. -Every remaining bare constant becomes a candidate held as a question — -`BLOCKED` on the only thing that matters: is the value a target, a measured -limit, or the first number someone typed? A `timeout: 30_000` gives the -value, never the intent, and asserting intent from a constant is the same -failure the `DERIVED` hold exists to prevent. - -**D5 — when phase D is done:** the five documents present or explicitly -folded, every integration carrying a failure entry or an open question, -every ADR at `observed`, the NFR sweep run with every bare-constant row held -as a question, and **citations resolve** across all of it — checked, not -trusted. Phase D is idempotent — ADRs key by slug, NFR rows by the -`file:symbol` the threshold lives at — and it measures nothing: configured -thresholds are read, latencies are never invented, and threat models are a -person's to write, seeded by `22`, never substituted by this pass. - -## Relation serialization - -A relation this pass declares must reach the graph. Write it in the Candidate -packet, in one of these exact shapes — the reader parses ids that follow the -verb, and nothing else: - -``` -Proposed relations (CANDIDATE): requires SR-KERNEL-030, SR-KERNEL-031. -``` - -``` -Proposed relations (CANDIDATE): serves UR-KERNEL-002. -``` - -`requires` names the rows that take this one as their parent; `serves` names -this row's parent. A row may also carry its parent in a dedicated field — a -`UR` ledger column, a bare `UR-…` in the `Source` cell, or a detail bullet: - -``` -- **UR:** UR-KERNEL-002 -``` - -A dedicated field always wins: a packet sentence never overwrites a parent an -author stated in a field of its own. - -Prose that merely mentions a requirement is a citation, not a relation, and is -read as none — `Conflict — SR-KERNEL-033 records that …` declares nothing. -Naming an id no row carries is a corpus defect and is reported, never dropped. - -**Do not invent a shape.** Three estates have been synced with every row -orphaned because a pass wrote parents in a form the reader did not parse -(REQ-CROSS-076, SR-SY-1402, REQ-CROSS-286). The forms above are the contract -between this skill and `internal/rdd`, and a test asserts that every example in -this section parses. A new shape needs a reader in the same change. - -Declaring nothing is a real answer, and a common one: most derived rows have no -parent to propose. Say so by writing no relation clause — not by inventing a -plausible one. The pass reports how many rows declare a relation against how -many exist, and that ratio is a quality signal about the derivation, not a -number to inflate. - -## Confirmation — the only exit for a candidate - -Build exact confirmation gates per `PROCESS.md` §Strict human transitions: -the candidate packet and exact confirmation scope complete before the gate -opens, each gate carrying the standard brief. One human answer may cover -explicitly named candidates — batch confirmation over a context's candidates -is the expected shape; a gate per row is a denial-of-service on the person -this pass is meant to help. Never batch by wildcard or range: the gate names -every id it covers. - -Apply answers exactly as `PROCESS.md` routes them: - -```text -confirmed accurate as-built --> PENDING_VERIFICATION -confirmed/corrected intent ---> PROPOSED -rejected ---------------------> OBSOLETE -``` - -Confirmation proves the requirement exists. It does not approve entry, make -candidate links authoritative, prove behavior, or select a release — a -derived corpus describes what already ships and is never stamped into an -active release. - -## Handoff - -Confirmed scope enters the standard loop and this orchestrator's job ends: -`rdd-plan` → `rdd-cold-review` → `rdd-entry-review`, then `rdd-verify` for -`PENDING_VERIFICATION` rows or `rdd-build` for `PROPOSED` behavior — those -passes own the advance to `IN_REVIEW` — then `rdd-completion-review`. -`rdd-start`'s session discipline binds throughout: run the project's -deterministic process checks (for ModernPath workspaces, `modernpath check`) -before every commit of derived records, chained so a failure stops the -commit. Comprehensive is reached by repetition — one bounded -context per pass, confirmed and handed off, until the context map lists no -context without records — not by one enormous unreviewable pass. - -## This pass never - -- creates or advances acceptance content, tests, implementation, - verification, or delivery while its candidates are `DERIVED`; -- assigns `PENDING_VERIFICATION`, `TODO`, or any status past `DERIVED` - without an applied human answer; -- makes a candidate relation authoritative, or invents a parent to complete - a trace; -- commits derived items to a release; -- edits product code — a pass that edits code can be reviewed as neither - documentation nor a change; -- guesses a business rule to avoid recording an open question. - -## Coverage floor — the pass is not finished at 59% - -Before reporting, run the workspace's own instrument and read the number it -gives, not one of your own: - -``` -modernpath coverage -``` - -**A pass below 60% file coverage is incomplete, not merely modest.** It means -the corpus describes what the system exposes — routes, pages, tables — and not -what implements it. Entry points are the easy half: a route handler is named in -one place and reads like a requirement already. The modules behind it are where -behaviour actually lives, and a corpus that skips them cannot support a change. - -Two rules follow: - -- **Derive against the implementation layer too.** A `lib/`, `services/`, - `domain/` or `internal/` module that holds a rule, a calculation, a state - transition or an integration is behaviour a requirement must name and cite. - Reaching a directory only through the handler that calls it does not cover it. -- **Read the per-app breakdown, not just the total.** One directory sitting far - below the rest is the gap; a healthy total can hide it. Report each app's - number, and treat a low one as a finding with a reason — "these 81 modules are - presentation-only" is an answer, silence is not. - -The instrument counts what git accounts for, so a vendored or gitignored tree -never inflates or deflates the result. If the number still looks wrong, say so -and show the breakdown rather than quietly adopting a denominator that flatters -the pass. +| Entry points | HTTP/RPC routes, workers/jobs, webhooks/events, CLI and agent/tool surfaces | +| Data models | Tables/models and schema-enforced constraints | +| Access control | Guards, middleware, policies and role gates | +| User-visible flows | Routes/views/flows in every relevant client and the actors who reach them | +| Integrations | External systems visible in configuration/credentials as well as modules | +| Tests | Existing test files and their observed targets; not yet verification evidence | + +State genuinely inapplicable classes and why. Inspect implementation modules as +well as handlers. A table written by multiple contexts is an ownership question; +it is an EC violation only if an applicable active rule makes it one. + +Cite complete repository-relative paths and inspect the whole relevant expression, +call chain and production input path. Code comments establish what was written, +not that the described behavior runs. An absence claim needs a named search +population and method. Keep decided and deployed facts separate. + +## Candidate authority and relations + +Every inferred behavior starts `DERIVED`, with canonical kind UR or SR, +candidate statement, direct sources, conflicts/questions, consequences, +confirmation brief, and proposed relations explicitly labelled `CANDIDATE`. +Use the canonical Candidate packet, not live acceptance content or implementation +reconnaissance. Independently observed candidates may be linked to each other as +proposals; none authorizes downstream work before confirmation. + +A UR describes an actor/outcome grounded in observed surfaces; an SR describes +a system behavior at a boundary. An observed engineering convention routes to a +flat `PROPOSED` EC, not a behavioral requirement. A bare constant whose intent +is unknown remains a candidate/question, not an invented normative target or a +`BLOCKED` requirement in place of the DERIVED hold. + +Compare proposed UR/SR joins both ways. A view calling a missing entry point and +an entry point with no identified caller are findings to investigate, not +automatic proof of dead code. Preserve legitimate integrations without views. +Serialize proposed links in the canonical candidate packet; only an applicable +adapter may specify an import grammar, and it must preserve candidate status. +Do not invent a parent to raise a relation count. + +Pre-existing failing tests, unreachable paths, and unclear-ownership discoveries +route to triage/backlog with direct evidence. They are not verified shipped +behavior. Candidate groups may be recorded on a PROPOSED Epic with membership +labelled CANDIDATE; they do not establish authoritative readiness. + +## Measure observation coverage + +Record before/after inventories, matching rules, numerators, denominators and +misses for the selected context. Use the project's standing instrument when it +measures that population; otherwise retain a small reproducible inventory audit. +Do not interpret zero authoritative coverage as a failed derivation: DERIVED +candidates are deliberately excluded from delivery-readiness coverage. + +Use declared project coverage targets. In their absence, the default adoption +target is 100% inspected/dispositioned observations, with a completion floor of +90% in every applicable denominator class; a full-system sweep also requires +60% of its source-file inventory cited or explicitly dispositioned. Record the +population and any human-approved target changes before claiming completion. +Do not apply a whole-system denominator to a one-context pass. Exclusions must +be explicit; generated code or presentation-only files are not silent credits. + +Run `rdd-audit` over the pass's output, including citations and both inventory +directions. Report exact command output and misses. A partial remains partial +even if many candidates were written. On resume, a zero new-row delta can be +correct when existing candidates were reconciled or confirmed; report the actual +progress rather than requiring duplicate records to make a metric increase. + +For an explicitly requested full adoption, repeat bounded passes over the +recorded remaining contexts until the declared floors pass or an exact human/ +external prerequisite blocks further work. Candidates may remain DERIVED while +independent contexts are observed; confirmation is still required before any +downstream delivery. An interruption preserves campaign progress and exact +resume input, rather than restarting from an empty-corpus guard. + +## Confirmation and handoff + +Prepare exact confirmation gates with complete candidate packets and plain +product-language briefs. A batch may name multiple exact ids, never a wildcard +or range. Apply attributable answers only through `PROCESS.md`: + +- confirmed accurate as-built → `PENDING_VERIFICATION`; +- confirmed/corrected intended behavior → `PROPOSED`; +- rejected → `OBSOLETE`. + +Confirmation does not approve entry, validate behavior, authorize candidate +relations, or select a release. Hand confirmed scope to `rdd-plan`, independent +`rdd-cold-review`, and `rdd-entry-review`, followed by `rdd-verify` or +`rdd-build`, then completion review. Standard session preflight applies before +delivery selection; observational campaign progress itself is not a release +commitment. + +Persist context progress as PARTIAL, AWAITING_CONFIRMATION, or HANDED_OFF with +exact candidate/gate ids and remaining work. A later invocation resumes another +context using the same campaign; the confirmed corpus is preserved. ## Report -Report the contexts inventoried and the one derived; every denominator class -as `N/N` with misses and stated-inapplicable classes; candidates created by -kind; conflicts, open questions, and backlog discoveries routed; the audit -result over the pass's own output; the confirmation gates now open and the -exact ids each covers; and which contexts remain, with the one this pass -would take next. +Report campaign/baseline, selected and remaining contexts, each population's +coverage and misses, source/authority conflicts, candidate and proposed-EC ids, +audit results and limitations, confirmation gates, and the exact next action. diff --git a/skills/rdd-reverse-engineer/agents/openai.yaml b/skills/rdd-reverse-engineer/agents/openai.yaml index 4a0eae5..7c87613 100644 --- a/skills/rdd-reverse-engineer/agents/openai.yaml +++ b/skills/rdd-reverse-engineer/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "RDD Reverse-Engineer" - short_description: "Adopt a codebase: derive DERIVED candidates" - default_prompt: "Use $rdd-reverse-engineer to bootstrap a requirement corpus from this codebase." + short_description: "Start or resume bounded codebase adoption" + default_prompt: "Use $rdd-reverse-engineer to start or resume requirement adoption for this codebase's uncovered contexts." diff --git a/skills/rdd-reverse-engineer/references/modernpath-adapter.md b/skills/rdd-reverse-engineer/references/modernpath-adapter.md new file mode 100644 index 0000000..623a202 --- /dev/null +++ b/skills/rdd-reverse-engineer/references/modernpath-adapter.md @@ -0,0 +1,58 @@ +# ModernPath adoption adapter + +Read only for a project whose instructions identify ModernPath ledger ingestion +as its supported process-store adapter. These are adapter conventions, not +process authority. Check the installed CLI/adapter contract before using a +command or import shape; version differences must be surfaced rather than +silently changing canonical semantics. + +## Required operations + +The adapter must read/validate binding and registry state, upsert stable record +ids without overwriting newer versions, preserve explicit UR/SR kinds and +candidate relation status, apply attributable gate answers idempotently, run +process checks, and refresh revision-stamped snapshots. If an operation cannot +preserve these facts, report the adapter gap and stop that write. + +## Ledger imports and projections + +Where the installed ingestion contract reads `tasks/-REQUIREMENTS.md`, use +that import shape for candidates and the approved sync operation to reach the +store. `file-state/` working-set snapshots are projections, not a second input. +Do not independently author both representations as authorities. + +Prefer `UR-`/`SR-` prefixes and retain the canonical kind explicitly. A legacy +`REQ-` id is valid only when its canonical kind survives the adapter round trip; +if the installed reader defaults it to SR, do not use it to encode a UR. Use an +adapter-supported id/shape or report the mismatch. + +Some ledger readers accept these candidate-packet relation forms: + +```text +Proposed relations (CANDIDATE): requires SR-KERNEL-030, SR-KERNEL-031. +Proposed relations (CANDIDATE): serves UR-KERNEL-002. +``` + +The first proposes that a UR requires the named SRs; the second proposes that an +SR serves the named UR. Verify candidate status after import. A dedicated +authoritative relation field must not be populated merely to make an inferred +link parse. Conflicting fields are a reconciliation failure, not permission to +overwrite a confirmed relation. Undefined ids are reported, never dropped. + +Use an `epics/` or NFR ledger import only when that project adapter supports it. +Quality-attribute behavior follows the same canonical UR/SR hold as other +behavior; observed engineering rules belong in PROPOSED EC records. A legacy +`REQ-NFR-NNN` convention must not change either rule. + +## Commands and measurement + +Typical installed commands are `modernpath factory sync`, `modernpath check`, +and `modernpath coverage --json`. Confirm local help/project instructions and +authorization before executing them; this reference does not grant publication +or synchronization authority. + +Run configured deterministic checks before committing records and verify the +store's resulting revisions after authorized synchronization. Capture standing +coverage before/after, with per-app breakdowns and the tool's actual population. +If it excludes DERIVED rows, use a separate observation inventory for adoption +progress; do not promote candidates or fabricate rows to increase coverage. diff --git a/skills/rdd-reverse-engineer/references/recovered-design.md b/skills/rdd-reverse-engineer/references/recovered-design.md new file mode 100644 index 0000000..ac4eb87 --- /dev/null +++ b/skills/rdd-reverse-engineer/references/recovered-design.md @@ -0,0 +1,36 @@ +# Recover observed design + +Read when the adoption request includes system design documentation. Reuse or +extend maintained documents rather than creating a competing numbered corpus. +The following are output topics, not mandatory filenames or new normative rules. + +| Topic | Record | +|---|---| +| Architecture | Subsystems, external interfaces, stores, and an end-to-end request path | +| Deployment | Runtime units, boundaries and published interfaces; fold into architecture for a simple stack | +| Integrations | Direction, protocol, enforcement point and observed failure handling, or an explicit question | +| Cross-cutting behavior | Auth, tenancy, secrets, observability and resilience as implemented | +| Data flow | Origins, transformations, destinations and trust-boundary crossings | + +Inspect existing guides as context, then cite code/configuration for observed +claims. Preserve intended-versus-deployed differences. Do not author a guide and +then cite it as independent authority for the inference it just introduced. +Update the existing document index with scope and observed/proposed/current +status so recovered descriptions are not mistaken for approved policy. + +When ADRs are useful, mark recovered decisions `observed`, never `accepted` +without attributable authority. Reconcile by existing id/slug on subsequent +passes. Record uncertainty and alternatives as observations, not ratification. + +Sweep quality attributes and latent thresholds: timeouts, pool sizes, retries, +rate limits, cache TTLs, payload caps and similar settings. First check existing +records to avoid duplicating behavior under an NFR label. Unknown intent remains +a question attached to a DERIVED behavioral candidate or PROPOSED engineering +constraint as appropriate. A configured value is not a measured limit or a +human-approved target. + +Do not manufacture performance measurements or threat-model decisions from +configuration. Record enforcement and failure paths with direct sources. Audit +citations, compare inventories both ways, and preserve gaps explicitly. On a +later context pass, reconcile newly observed facts without rewriting confirmed +requirements or silently changing policy. diff --git a/skills/rdd-start/SKILL.md b/skills/rdd-start/SKILL.md index bf2e101..28dc706 100644 --- a/skills/rdd-start/SKILL.md +++ b/skills/rdd-start/SKILL.md @@ -15,13 +15,16 @@ Read the project `AGENTS.md` and the canonical `PROCESS.md` — installed at confirm it is reachable. In a store-backed repository, confirm the binding identity from the store itself, never from a number quoted in instructions; report binding drift as a defect, not a variance. -2. Confirm the release registry holds exactly one active release with a - `USER:` source. No active release, or more than one, stops selection until - a human answers. -3. Reconcile answered human gates and apply their consequences, then list the +2. Read current authoritative records and reconcile applicable answered release + decisions first. Validate their recorded input revision, prerequisites, + scope, and `USER:` source; apply once, without requiring the intended active + release to exist already. A stale answer needs a successor gate. +3. Confirm the reconciled release registry holds exactly one active release + with a `USER:` source. Otherwise stop selection at the exact release decision. +4. Reconcile other answered human gates and apply their consequences, then list the pending human decisions — only `OPEN` human gates with current passing prerequisites. -4. Refresh the session's working-set snapshots and check each file's snapshot +5. Refresh the session's working-set snapshots and check each file's snapshot header against the store revision. A stale snapshot is refreshed, never edited. @@ -29,6 +32,11 @@ An unmet preflight fact is the report. Do not select work past it. ## Take the scope +Resume an existing selection by its recorded next action and per-item approvals; +do not reset its members or re-freeze unchanged inputs simply because a session +restarted. For adoption, retain the campaign record independently of the current +delivery selection. + 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 @@ -64,3 +72,12 @@ 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. + +## Execution contract + +- Input: repository binding, registry, gate records, and requested/current scope; + mixed item states are allowed, and no work is selected before preflight passes. +- Writes: applicable answered-gate consequences, reconciled snapshots, selection + and exact next-action record; no inferred human decision or product edits. +- Exit: earliest legal phase with durable inputs, or the precise failed + preflight/decision and condition for resuming it. diff --git a/skills/rdd-triage/SKILL.md b/skills/rdd-triage/SKILL.md index 320b6c1..ddc64e4 100644 --- a/skills/rdd-triage/SKILL.md +++ b/skills/rdd-triage/SKILL.md @@ -29,7 +29,9 @@ order. until an attributable human answer is applied. 6. For feedback, determine whether one standalone or UR-linked SR can address it without changing user outcome, acceptance, or a cross-cutting decision. - Otherwise route it to epic-scoped planning. + For an implementation/review failure within current approval, record the + correction boundary and required reruns, apply the scoped corrective demotion, + and route to `rdd-build`. Otherwise route changed scope to planning. 7. Re-evaluate stale gates and evidence, then reconcile authoritative records, release scope, work selection, and derived views. Never promote to `TODO` without the strict entry gate. @@ -41,3 +43,11 @@ Do not change product code in this pass. Report each routed item, attributable source, changed state, stale evidence or gates, remaining human decisions, and the exact focused skill that resumes the loop. + +## Execution contract + +- Input: sourced discovery or feedback and affected state at any lifecycle point. +- Writes: routed candidates, decisions, findings, holds with per-item suspended- + from state, justified demotions, and reconciled next-action records; no code. +- Exit: exact affected scope and next skill, or an attributable decision/external + hold. Releasing a hold reassesses evidence; it does not blindly restore status. diff --git a/skills/rdd-verify/SKILL.md b/skills/rdd-verify/SKILL.md index 5b96891..7adcd30 100644 --- a/skills/rdd-verify/SKILL.md +++ b/skills/rdd-verify/SKILL.md @@ -31,8 +31,9 @@ approval or prove delivery. `DONE` also requires the applicable approval, authoritative-source delivery, and reconciliation conditions. - If the implementation contradicts the row, record the discovery and route a - separate red-first change. Do not silently change behavior during a - verification pass. + red-first correction through triage: use build when current approval covers + it, or renewed planning when scope/intent changes. Do not change behavior + during the verification pass. ## Enter through the same gate as any other change @@ -67,11 +68,11 @@ introduces a cross-cutting decision, return it to Epic-scoped planning. In either scope, fulfill the current planning, reconnaissance, cold-review, test-strategy, work-selection, and entry-brief facts. -Then obtain strict human entry approval for every selected requirement and -Epic. An already approved related entity does not return to `TODO` merely -because another requirement starts. Move the selected requirement to `TODO` -before changing a test. If the Entry packet is incomplete or the entry answer -is absent, stop and route the entry first. +Then require current applied human entry approval for the affected requirements +and Epic. New entrants move to `TODO` before tests change; approved resumed +items keep their strongest supported state. Reopen reviewed work via the +canonical correction/invalidation route before edits. If the Entry packet or +approval is absent or stale, route that prerequisite first. Verification outside the authoritative work selection is invisible to planning, and a test written before the gate cannot be traced to approved intent. @@ -93,7 +94,10 @@ For each row, require all of the following: that it executed. 8. Observe the expected failure before the passing result. For already-shipped behavior, use a safe local mutation or equivalent targeted failure, restore - it immediately, and inspect the diff before continuing. + it immediately, and inspect the diff before continuing. Record a separate + `SENSITIVITY_RED` result with mutation and restoration proof and assess it + `RETAINED`. Reuse retained RED for unchanged clauses/assertions on resume; + restored code does not invalidate that historical observation. 9. Run proportional regression gates and record evidence against the current revision. 10. Cite evidence by stable test path and name, for example @@ -113,7 +117,9 @@ assertion remains unverified. demonstrate its relevant failure mode. Otherwise, add the smallest test. 4. Restore any temporary mutation, run the focused test green, then run the required regression gates. -5. Update the authoritative requirement, optional related epic, evidence, and +5. Store separate RED and passing/regression observations with their own + fingerprints and role-specific validity assessments. Update the authoritative + requirement, optional related epic, evidence, and work-selection records atomically; in a store-backed repository, refresh the materialized snapshots afterwards. 6. Advance only the evidence conclusion justified by the run. Move the selected @@ -155,9 +161,11 @@ assertion remains unverified. Do not weaken a test to promote a row. - If the row is only an inference that no human confirmed as a requirement, - move it to `DERIVED`, record candidate links, and emit its confirmation gate. + report the authority contradiction through triage, hold downstream work, and + emit its confirmation gate; do not silently erase an existing human approval. - If the implementation cannot satisfy the row, record the contradiction and - route the required new or changed SR through planning. + route through triage. An in-scope defect uses the approved correction route + to build; a missing or changed requirement/decision needs planning. - If verification needs unavailable infrastructure, keep the row `PENDING_VERIFICATION` before entry; after entry, use `BLOCKED` and record the suspended `TODO` or `IN_PROGRESS` state. @@ -181,3 +189,13 @@ Report: Exit only when every touched row has current direct evidence or an explicit reason it remains unverified, all temporary mutations are gone, and repository state passes its deterministic checks. + +## Execution contract + +- Input: confirmed as-built scope with current applied entry approval, no hold, + and missing/stale evidence; new entrants TODO, resumed work IN_PROGRESS. +- Writes: scoped tests and safely restored mutations, per-run observations and + assessments, evidence-backed transitions, and durable next action; no new behavior. +- Exit: applicable current trace and IN_REVIEW, or an exact missing fact/hold. + Completion belongs to `rdd-completion-review`; observed defects route to build + only after triage establishes the existing approval or renewed entry it needs. diff --git a/tests/audit-citations.test.mjs b/tests/audit-citations.test.mjs index ca1c249..8416b7b 100644 --- a/tests/audit-citations.test.mjs +++ b/tests/audit-citations.test.mjs @@ -123,6 +123,14 @@ test('a missing requested root is not silently ignored', t => { assert.match(result.output, /absent-docs/); }); +test('default roots include a standalone architecture document without docs', t => { + const result = audit(t, '# Unselected claims', { + 'ARCHITECTURE.md': 'CODE:behavior.test.ts:checksBehavior', + }, []); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /checked=1/); +}); + test('example-only input is exempt, never counted as a successful check', t => { const result = audit(t, '```text example-citation\nTEST:absent.test.ts:example\nDOC:absent.md#Example\n```'); assert.equal(result.status, 2, result.output); diff --git a/tests/process-scenarios.md b/tests/process-scenarios.md new file mode 100644 index 0000000..5903708 --- /dev/null +++ b/tests/process-scenarios.md @@ -0,0 +1,184 @@ +# Lifecycle forward-test scenarios + +These fixtures exercise instructions, not a production state-store engine. +Use a fresh reviewer context with `PROCESS.md`, the affected skills and canonical +record shapes. Give it the initial facts and event first, then compare its +derived next actions to the expectations below. Do not supply prior review +conclusions as evidence. Live stores, integration and human answers are simulated. + +For every scenario, record the next legal skill/action, changed records, +preserved approvals, evidence assessments, stale gates, and exact stop condition. +A path that needs an invented transition, invented human answer, or omitted +check fails. Frontmatter and text-matching checks do not evaluate these cases. + +## New behavior, retained RED, and delivery + +Initial facts: SR-S and linked UR-U have current applied entry approval and +status TODO. Their lower/upper expected failures were observed at revision R0. +Both traces pass after implementation and cleanup at R1. Candidate engineering +passes. Integration produces R2 with equivalent relevant code/configuration and +environment, confirmed with direct evidence. + +Event: Continue delivery through the human completion gate. + +Expected: Separate immutable RED and passing/regression results retain their +actual fingerprints. RED is assessed RETAINED; passing evidence is CURRENT at +R1, then confirmed or rerun for R2 with an append-only assessment. Planning and +entry remain valid for approved implementation. Candidate engineering precedes +integration, a separate delivered result follows, and human completion opens +only after reconciliation and delivered predicates pass. No DONE before the +attributable answer is applied. + +Negative variation: The assertions or approved clause materially change. Old +RED is not automatically reusable; reassess its relevance and establish new +RED when it no longer demonstrates the target. Changed approved scope replans. + +## As-built sensitivity mutation + +Initial facts: A confirmed as-built SR has entered TODO with current approval. +Its real subject is reachable with production-valid inputs. A safe temporary +mutation produces the expected failure; the original implementation is restored. + +Event: Record evidence and continue verification. + +Expected: Record SENSITIVITY_RED with tested mutation fingerprint, failure cause, +original/restored fingerprints and restoration proof. Assess it RETAINED rather +than INVALID merely because the mutation is gone. Run the restored subject and +regression gates; advance through the normal automatic states, then hand off to +completion. No claim that the mutation was delivered. + +Negative variation: Restoration is unproven, or the test invokes an input that +cannot reach the subject in production. Evidence remains invalid/unverified and +cannot advance review readiness. + +## Mixed-state Epic resume + +Initial facts: Selected Epic E contains SR-A DONE, SR-B IN_REVIEW, and SR-C TODO. +A and B have unchanged current approvals and supporting evidence. C's required +entry approval is applied. The selection and declared dependencies are unchanged. + +Event: Resume end-to-end delivery. + +Expected: Route C to its unmet execution prerequisite. Do not reset A/B, repeat +their entry answers, or wait for the whole Epic to become TODO. Readiness accepts +members IN_REVIEW or DONE with their applicable traces. Completion transitions +name only eligible unfinished items; A is a satisfied dependency. + +Negative variation: C changes an approved shared contract on which A depends. +Invalidate only the affected scope and dependent evidence/approvals, including A +where the declared dependency actually makes it affected. + +## Candidate engineering correction + +Initial facts: SR-S is IN_REVIEW; all behavioral evidence passes. An active EC +requires a naming convention. Candidate engineering finds a nonconforming name. +The correction is behavior-preserving and within current scope and decisions. + +Event: Continue from failed candidate engineering. + +Expected: Do not integrate. Record the direct finding, correction boundary, +unchanged entry authority, affected item ids and reruns. Apply the corrective +demotion to IN_PROGRESS. Build uses the finding as its target without inventing +behavioral requirements or artificial RED. Preserve retained RED, rerun affected +behavioral/regression evidence and candidate engineering after the correction, +then return to completion. Unchanged planning/cold/entry authority is preserved. + +Negative variation: Correcting the finding requires a new architecture decision +or expands EC applicability. Route to planning and the exact human decision; +the in-scope correction route cannot authorize the expansion. + +## Upper failure after all lower traces pass + +Initial facts: All planned SR lower traces pass and SRs are IN_REVIEW. UR-U's +upper scenario fails due to an implementation defect in SR-S within current +approval; other SRs are unaffected. + +Event: Continue the inner loop. + +Expected: Record the upper failure and reopen S with its dependent items as +needed. Reproduce the approved failure, correct S, and rerun lower/regression +and upper evidence. Do not require a new unrelated lower clause or demote every +SR. Move U to IN_REVIEW only after its upper evidence and required-SR predicates +pass. A defect discovered in `rdd-verify` uses triage to this same route, not +mandatory replanning when existing authority already covers the correction. + +## Review-output fingerprint stability + +Initial facts: Planning snapshot P has a current planning-engineering PASS. A +fresh independent reviewer checks P and produces findings, dispositions and a +cold-review verdict without altering planned inputs. + +Event: Attach the review output and evaluate entry. + +Expected: P and its engineering result remain current. Cold review references P +and its engineering prerequisite. Entry references those prerequisite results +and their dispositions, excluding its own answer/application. The reviewer +context and inspected revision are recorded. + +Negative variations: Editing the plan stales dependent reviews. Replacing a +prerequisite review result stales entry. Loading cold-review instructions in the +author's existing context does not satisfy reviewer independence. + +## Answered release selection on startup + +Initial facts: The store binding is correct and reachable. Registry revision V0 +has no active release. A human release-selection gate is ANSWERED, its V0 input +and prerequisite trace remain applicable, and consequences have not been applied. + +Event: Start or resume a session. + +Expected: Validate and apply the recorded answer before requiring one active +release, reconcile consequences, close the applied gate, then enforce the +single-active-release invariant and select work. The next startup does not +apply the answer twice or ask the same question again. + +Negative variation: The answer's input is stale. Do not apply it or infer a +replacement decision; report the successor-gate requirement before selection. + +## Adoption campaign resume + +Initial facts: Campaign X records context A as handed off with confirmed +requirements. Context B is uncovered. Context C is partially observed with +stable candidate ids and an open confirmation gate. The campaign is authorized. + +Event: Resume X in a new session. + +Expected: Existing records do not fail the empty-corpus guard. Preserve A's +confirmed statements and answers. Continue B or the recorded next partial +context; reconcile C by id without duplicate candidates. Record original and +current inspected revisions, per-context progress, remaining observations and +next action. Candidate coverage is not authoritative delivery readiness. + +Negative variations: An unrelated established corpus has no bounded-adoption +authorization; use discovery/planning. Revision drift affecting an existing +observation requires rechecking it and routing conflicts with confirmed content. + +## Hold and partial invalidation + +Initial facts: Epic E is held, with A previously IN_REVIEW and B previously +IN_PROGRESS. Each item's hold row records its suspended-from state and evidence +basis. A's required passing evidence becomes stale during the hold. + +Event: The external impediment clears. + +Expected: Preserve each prior-state record, reassess current approvals/evidence, +and append release facts. A resumes IN_PROGRESS while B keeps its supported +progress. Other unaffected members are not reset. No blind restoration of A to +IN_REVIEW and no loss of the reason it was previously eligible. + +## Prospective EC activation and delivered failures + +Initial facts: Scope A was DONE before a new EC's effective point; B is a +nonterminal selection whose resolved applicability now includes that EC. + +Event: Apply the attributable EC activation decision. + +Expected: Replan B and stale its dependent engineering/cold/entry results. Do +not reopen A unless the activation decision explicitly names remediation. + +Separate event: A delivered revision fails an EC that already applied to that +delivery. Record the failure and prevent completion. Use the correction or +planning route according to scope, deliver the correction, and recheck the new +target. If previously DONE work was proven defective, a successor human +completion gate is needed after correction; acceptance is not reused at a new +delivered fingerprint. From 3961f42947c96e11af1a7c6573753fa4fd2cd3e1 Mon Sep 17 00:00:00 2001 From: mattias-modernpath Date: Mon, 7 Sep 2026 10:19:51 +0300 Subject: [PATCH 3/4] test: reproduce citation suffix and template-name defects --- tests/audit-citations.test.mjs | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/audit-citations.test.mjs b/tests/audit-citations.test.mjs index 8416b7b..a72880d 100644 --- a/tests/audit-citations.test.mjs +++ b/tests/audit-citations.test.mjs @@ -105,6 +105,58 @@ test('an unsupported TEST suffix is not truncated into a pass', t => { assert.match(result.output, /unsupported=1/); }); +for (const reference of [ + 'TEST:behavior.test.ts:checksBehavior[MissingCase]', + 'TEST:behavior.test.ts:checksBehavior(MissingCase)', + 'TEST:behavior.test.ts:checksBehavior{MissingCase}', + 'TEST:behavior.test.ts:checksBehavior@MissingCase', + 'TEST:behavior.test.ts:checksBehavior', + 'TEST:behavior.test.ts:checksBehavior.MissingCase[OtherCase]', + 'TEST:behavior.test.ts:"rejects invalid input"[MissingCase]', + 'CODE:behavior.test.ts:checksBehavior[MissingCase]', +]) { + test('unsupported suffix is not checked as a valid prefix: ' + reference, t => { + const result = audit(t, reference); + assert.equal(result.status, 2, result.output); + assert.match(result.output, /unsupported=1/); + assert.match(result.output, /checked=0/); + }); +} + +test('ordinary Markdown and prose citation endings still resolve', t => { + const result = audit(t, [ + '`TEST:behavior.test.ts:checksBehavior`', + '(TEST:behavior.test.ts:checksBehavior).', + 'TEST:behavior.test.ts:checksBehavior, with more prose.', + '| TEST:behavior.test.ts:"rejects invalid input" |', + 'TEST:behavior.test.ts:checksBehavior.', + ].join('\n')); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /checked=5/); +}); + +test('quoted citations resolve static template-literal test names', t => { + const result = audit(t, 'TEST:behavior.test.ts:"rejects invalid input"', { + 'behavior.test.ts': 'test(`rejects invalid input`, () => {});\n', + }); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /checked=1/); +}); + +test('a static template-literal name must match the whole string', t => { + const result = audit(t, 'TEST:behavior.test.ts:"rejects invalid input"', { + 'behavior.test.ts': 'test(`rejects invalid input twice`, () => {});\n', + }); + assert.equal(result.status, 1, result.output); +}); + +test('template interpolation text is not a static test name', t => { + const result = audit(t, 'TEST:behavior.test.ts:"rejects ${value}"', { + 'behavior.test.ts': 'test(`rejects ${value}`, () => {});\n', + }); + assert.equal(result.status, 1, result.output); +}); + test('unsupported canonical reference is visible beside passing references', t => { const result = audit(t, 'CODE:behavior.test.ts:checksBehavior\nTEST:behavior.test.ts'); assert.equal(result.status, 2, result.output); From 35876d5fd3dd6dc00eefb8bccd28778f066cd570 Mon Sep 17 00:00:00 2001 From: mattias-modernpath Date: Mon, 7 Sep 2026 10:25:47 +0300 Subject: [PATCH 4/4] fix: close citation gaps and support UR-owned test corrections --- PROCESS.md | 28 ++++++++------ skills/rdd-audit/SKILL.md | 9 +++-- skills/rdd-audit/audit-citations.mjs | 14 ++++++- skills/rdd-build/SKILL.md | 2 +- skills/rdd-completion-review/SKILL.md | 7 +++- skills/rdd-deliver/SKILL.md | 10 +++-- skills/rdd-engineering-check/SKILL.md | 8 ++-- skills/rdd-triage/SKILL.md | 11 +++--- skills/rdd-verify/SKILL.md | 54 ++++++++++++++++++++------- tests/audit-citations.test.mjs | 13 +++++++ tests/process-scenarios.md | 28 ++++++++++++++ 11 files changed, 140 insertions(+), 44 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index f08b78e..96a8692 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -292,7 +292,7 @@ An agent or deterministic check may apply these only from a current trace-gate | UR `TODO -> IN_PROGRESS` | Expected upper RED or a required SR is `IN_PROGRESS` | | Epic `TODO -> IN_PROGRESS` | An in-scope member is `IN_PROGRESS` | | SR `IN_PROGRESS -> IN_REVIEW` | Its lower trace is current and passes; any required corrective rechecks pass | -| UR `IN_PROGRESS -> IN_REVIEW` | Required SRs are `IN_REVIEW/DONE`; current upper evidence passes | +| UR `IN_PROGRESS -> IN_REVIEW` | Required SRs are `IN_REVIEW/DONE`; current upper evidence and required corrective rechecks pass | | Epic `IN_PROGRESS -> IN_REVIEW` | Members are `IN_REVIEW/DONE`; applicable trace gates pass | Agents may also apply evidence-invalidation and review-correction demotions, @@ -306,7 +306,7 @@ a human answer. An observed upper-flow failure or an implementation/review/engineering finding may reopen affected `IN_REVIEW` work as `IN_PROGRESS`. Record the direct source, -affected SRs and dependent UR/Epic items, correction boundary, unchanged entry +affected URs/SRs and their dependent items, correction boundary, unchanged entry approval, and checks to rerun. This is a corrective demotion, not a forward transition requiring a passing implementation trace. Propagate only through declared dependencies; unaffected items retain their strongest supported state. @@ -314,9 +314,13 @@ Stale affected implementation-review, candidate/delivered engineering, and completion gates and supersede their unclosed human gates. Do not stale planning, cold-review, or entry authority when their recorded inputs are unchanged. -Use `rdd-build` for the correction. For a behavior defect, establish a focused -reproduction RED against the approved clause or upper scenario. For a -behavior-preserving engineering correction, the recorded conformance failure +Route product implementation corrections to `rdd-build` under an approved SR. +Route corrections confined to already-approved UR/SR tests to `rdd-verify`, +including a UR-only Epic with no required SRs. A test-only correction does not +require an invented SR or new relation. If a product implementation change is +needed but no approved SR covers it, return to planning. For a behavior defect, +establish a focused reproduction RED against the approved clause or upper +scenario. For a behavior-preserving engineering correction, the recorded conformance failure is the work target; do not invent a new behavioral requirement or RED. Preserve admissible historical RED, rerun affected behavioral/regression evidence, and rerun the failed review/check after correction. Integration stays blocked until @@ -425,7 +429,7 @@ focused skill alone only when the requested scope explicitly ends at that pass. | Cold review | `rdd-cold-review` | Current cold-review trace verdict and finding dispositions | | Entry | `rdd-entry-review` | Current applied approval for the affected subset; new entrants `TODO`, unchanged items preserved, or an explicit non-entry result | | Execute changed SR | `rdd-build` | Current lower evidence; eligible SR in `IN_REVIEW`; selected UR evidence updated independently | -| Verify as-built requirement | `rdd-verify` | Current UR upper or SR lower evidence; eligible requirement in `IN_REVIEW` | +| Verify as-built behavior / correct approved tests | `rdd-verify` | Current UR upper or SR lower evidence and passing corrective rechecks; eligible requirement in `IN_REVIEW` | | Deliver/complete | `rdd-completion-review` | Delivered revision, reconciled records, completion trace, and applied human result | | Route change | `rdd-triage` | Discovery assigned to the earliest phase it invalidates | @@ -449,9 +453,10 @@ After applicable human entry approvals are applied, the AI owns the automatic It does not request human input while the approved fingerprint remains unchanged. ```text -establish selected UR upper RED - -> select an unmet approved SR clause - -> SR lower RED -> GREEN -> CLEAN -> lower verify +establish or reuse selected UR upper RED + -> select an unmet approved trace or recorded correction + -> changed SR: lower RED -> GREEN -> CLEAN -> lower verify + as-built evidence / test-only correction: rdd-verify -> rerun affected UR upper evidence -> all applicable trace gates PASS? no -> repeat @@ -465,8 +470,9 @@ Run the loop as follows: do not recreate an already-observed failure for each subsequent SR. A standalone SR has no upper step. 2. If an SR trace is unmet, select one approved clause, establish its focused - lower RED, implement the smallest passing behavior, and perform scoped - behavior-preserving cleanup. + lower RED, implement the smallest passing behavior, and perform scoped + behavior-preserving cleanup. For as-built UR evidence or a test-only review + correction, use `rdd-verify` without requiring an SR clause. 3. Run the SR's focused and boundary-appropriate regression gates on the cleaned content, then rerun each affected UR scenario. 4. Re-evaluate every selected SR lower trace and UR upper trace independently. diff --git a/skills/rdd-audit/SKILL.md b/skills/rdd-audit/SKILL.md index 5fc285c..27fcc1f 100644 --- a/skills/rdd-audit/SKILL.md +++ b/skills/rdd-audit/SKILL.md @@ -61,9 +61,12 @@ requires an adoption/release scope or an explicit audit request. The checker resolves paths and line ranges, document headings, and supported symbol/test names. Named references establish lexical presence only, including the components of hierarchical test names; test runners must establish their -actual identity and execution. Quoted test names support spaces. Unsupported -syntax is reported, not skipped. Examples explicitly marked `example-citation` -and named gaps are exemptions, never successful checks. +actual identity and execution. Quoted test names support spaces, including names +declared in static backtick strings; interpolated templates are not static names. +A parsed name must end at a citation boundary, not an unsupported suffix such as +`[Case]`; quote the whole name when punctuation is part of its identity. +Unsupported syntax is reported, not skipped. Examples explicitly marked +`example-citation` and named gaps are exemptions, never successful checks. Exit 0 means at least one reference was checked and all recognized references were resolved or explicitly exempted. Exit 1 reports broken/elided references; diff --git a/skills/rdd-audit/audit-citations.mjs b/skills/rdd-audit/audit-citations.mjs index cadca72..821f041 100755 --- a/skills/rdd-audit/audit-citations.mjs +++ b/skills/rdd-audit/audit-citations.mjs @@ -215,11 +215,13 @@ for (const doc of docs) { // not counted twice. const spans = []; for (const m of text.matchAll(PREFIXED)) { + if (!citationEnds(text, m)) continue; spans.push([m.index, m.index + m[0].length]); if (isExample(m.index)) { exempt++; continue; } check(doc, text, m); } for (const m of text.matchAll(TESTREF)) { + if (!citationEnds(text, m)) continue; spans.push([m.index, m.index + m[0].length]); if (isExample(m.index)) { exempt++; continue; } const normalized = Object.assign( @@ -333,13 +335,23 @@ for (const entry of unsupported) { if (!total) console.log("no citations checked — not evidence of trace completeness"); process.exit(broken.length || elided.length ? 1 : unsupported.length || !total ? 2 : 0); +// A supported name prefix is not a supported citation. Only accept an actual +// end, prose separator, or closing Markdown delimiter; leave other suffixes for +// the independent marker scan to report as unsupported. +function citationEnds(text, match) { + const suffix = text.slice(match.index + match[0].length); + return /^(?:$|[\s`"'|)\]]|[.,;!?](?=$|[\s`"'|)\]]))/.test(suffix); +} + function namePresent(source, name) { const escape = value => value.replace(/[.*+?^$()|[\]{}\\]/g, "\\$&"); const tokenPresent = value => new RegExp("(^|[^\\w$])" + escape(value) + "(?![\\w$])", "m").test(source); // This checks lexical components, not their registration/nesting in a runner. // Quoted names containing spaces must occur as a complete quoted string. if (/\s/.test(name)) { - return source.includes(JSON.stringify(name)) || source.includes("'" + name + "'"); + const staticTemplate = !name.includes('${') && !name.includes('`') && + source.includes('`' + name + '`'); + return source.includes(JSON.stringify(name)) || source.includes("'" + name + "'") || staticTemplate; } return name.split(/[./#]/).every(tokenPresent); } diff --git a/skills/rdd-build/SKILL.md b/skills/rdd-build/SKILL.md index dc87dfb..f42412e 100644 --- a/skills/rdd-build/SKILL.md +++ b/skills/rdd-build/SKILL.md @@ -39,7 +39,7 @@ code, tests, and current records. 8. Move the SR to `IN_REVIEW` when its lower trace passes and its corrective findings are resolved by the required rechecks. An affected UR moves to `IN_REVIEW` only when its upper trace passes and every required SR is - `IN_REVIEW` or `DONE`. + `IN_REVIEW` or `DONE`, with any UR-owned corrective rechecks also passing. 9. Reconcile the affected graph and derived views. Return remaining approved trace failures to `rdd-deliver` for another AI iteration. Hand fully eligible `IN_REVIEW` scope to `rdd-completion-review`; do not deliver or solicit diff --git a/skills/rdd-completion-review/SKILL.md b/skills/rdd-completion-review/SKILL.md index 1f2d5f8..3a18eb0 100644 --- a/skills/rdd-completion-review/SKILL.md +++ b/skills/rdd-completion-review/SKILL.md @@ -23,8 +23,11 @@ records, and derived views. 3. Invoke `skills/rdd-engineering-check/SKILL.md` with target `CANDIDATE` against the completed pre-delivery code. Do not integrate unless the complete applicable active EC set has a current engineering trace `PASS`. - On an in-scope implementation failure, record the correction and reopen - affected reviewed items per `PROCESS.md`, then hand off to `rdd-build`. + On an in-scope implementation/test failure, record the correction and reopen + affected reviewed items per `PROCESS.md`. Hand product implementation changes + to `rdd-build` under an approved SR, and test-only corrections to `rdd-verify`, + including UR-owned acceptance tests with no required SR. Do not fabricate an + SR for a test correction; a product change without approved SR scope replans. Changed scope/policy goes through triage to planning. Do not manufacture an evidence invalidation merely to make a correction eligible. 4. If the pre-delivery audit and candidate engineering check pass, deliver diff --git a/skills/rdd-deliver/SKILL.md b/skills/rdd-deliver/SKILL.md index f80dd89..18548e6 100644 --- a/skills/rdd-deliver/SKILL.md +++ b/skills/rdd-deliver/SKILL.md @@ -24,7 +24,8 @@ all semantics; this skill owns phase order and continuation. current applied approval. New entrants become `TODO`; preserve unchanged approved `IN_PROGRESS`, `IN_REVIEW`, and `DONE` items. 5. Run the AI TDD inner loop below. Apply `rdd-build` to changed SRs and - `rdd-verify` to human-confirmed as-built URs or SRs. Continue until every + `rdd-verify` to human-confirmed as-built URs/SRs or their approved test-only + corrections (including UR-only scope). Continue until every selected requirement satisfies its applicable trace and is `IN_REVIEW` or `DONE`. Use the review-correction route for an in-scope failure on reviewed work; do not wait for an unmet lower clause when the failure is engineering @@ -47,9 +48,12 @@ remains unchanged: 1. Evaluate every selected UR upper trace and SR lower trace. Establish any required initial RED observations. -2. Select the next unmet approved SR clause or recorded in-scope correction. +2. Select the next unmet approved SR clause, UR verification scenario, or + recorded in-scope correction. Reopen affected reviewed items per `PROCESS.md` before edits. Apply - `rdd-build` or `rdd-verify` until the required traces/checks pass. + `rdd-build` for product implementation under an approved SR, or `rdd-verify` + for as-built evidence and test-only corrections under the UR/SR owner, until + the required traces/checks pass. Do not invent an SR for UR-owned tests. 3. Rerun affected UR scenarios and update their separate upper evidence. 4. Repeat for any failing or stale approved trace. Do not stop after the first GREEN result or completed SR while another selected trace remains unmet. diff --git a/skills/rdd-engineering-check/SKILL.md b/skills/rdd-engineering-check/SKILL.md index 4b62845..06dc766 100644 --- a/skills/rdd-engineering-check/SKILL.md +++ b/skills/rdd-engineering-check/SKILL.md @@ -1,6 +1,6 @@ --- name: rdd-engineering-check -description: Evaluate the complete flat set of active engineering constraints applicable to an exact planning, pre-delivery candidate, or delivered fingerprint. Use from RDD cold review and completion review. Produces a distinct engineering trace verdict and findings for each target; it does not replace technical review, change constraint authority, or advance lifecycle state. +description: Evaluate the complete flat set of active engineering constraints applicable to an exact planning, pre-delivery candidate, or delivered fingerprint. Use from RDD cold/completion review and their build/verify correction rechecks. Produces a distinct engineering trace verdict and findings for each target; it does not replace technical review, change constraint authority, or advance lifecycle state. --- # Check engineering constraints @@ -48,7 +48,7 @@ reconnaissance, relevant code and configuration, and current gate records. change. - `DELIVERED` becomes `STALE` when the corresponding delivered-target inputs change. Keep the historical candidate result distinct. -7. Feed the findings and gate verdict to the invoking review. Candidate code +7. Feed the findings and gate verdict to the invoking review or correction pass. Candidate code that expands the approved affected surface invalidates planning rather than being treated as an ordinary code-fingerprint change. @@ -62,7 +62,7 @@ superseded, or retired EC as active. Report the evaluated fingerprint and revision; applicable EC ids with the scope match that selected each one; evidence and result per EC; exclusions, ambiguities, and findings; the engineering trace-gate verdict; and the exact -handoff to the invoking cold or completion review. +handoff to the invoking review or build/verify correction pass. ## Execution contract @@ -70,5 +70,5 @@ handoff to the invoking cold or completion review. points, and resolved affected surface; no assumed lifecycle prerequisites. - Writes: target-specific engineering results, per-EC evidence and findings; no implementation, constraint-policy, approval, or lifecycle changes. -- Exit: PASS or exact FAIL to the invoking review. The caller routes unchanged- +- Exit: PASS or exact FAIL to the invoking pass. The caller routes unchanged- scope implementation failures to review correction and changed inputs to plan. diff --git a/skills/rdd-triage/SKILL.md b/skills/rdd-triage/SKILL.md index ddc64e4..58f713f 100644 --- a/skills/rdd-triage/SKILL.md +++ b/skills/rdd-triage/SKILL.md @@ -27,11 +27,12 @@ order. 5. Route a proposed architecture, quality, language, service, domain, or code rule to a flat `PROPOSED` EC and its activation gate. It remains inactive until an attributable human answer is applied. -6. For feedback, determine whether one standalone or UR-linked SR can address - it without changing user outcome, acceptance, or a cross-cutting decision. - For an implementation/review failure within current approval, record the - correction boundary and required reruns, apply the scoped corrective demotion, - and route to `rdd-build`. Otherwise route changed scope to planning. +6. For feedback, identify whether the correction affects product implementation + or only already-approved tests. Within current approval, record its boundary + and required reruns and apply the scoped corrective demotion. Route test-only + corrections to `rdd-verify` under their UR/SR owner, even when a UR has no + required SR; route product implementation changes to `rdd-build` under an + approved SR. Otherwise route changed scope or missing SR authority to planning. 7. Re-evaluate stale gates and evidence, then reconcile authoritative records, release scope, work selection, and derived views. Never promote to `TODO` without the strict entry gate. diff --git a/skills/rdd-verify/SKILL.md b/skills/rdd-verify/SKILL.md index 7adcd30..9d9a357 100644 --- a/skills/rdd-verify/SKILL.md +++ b/skills/rdd-verify/SKILL.md @@ -1,9 +1,9 @@ --- name: rdd-verify -description: Verify human-confirmed PENDING_VERIFICATION URs and SRs against real behavior, add missing tests, and advance only evidence-backed state. Use after an as-built requirement has been confirmed and needs current UR upper or SR lower evidence. Repeat verification inside the AI TDD loop without bypassing approval or delivery. Never use for DERIVED requirements or new behavior; run confirmation or the normal red-first build loop instead. +description: Verify human-confirmed as-built URs/SRs or correct their already-approved tests, including UR-only scope with no required SR. Add missing evidence and resolve test-only review or engineering findings without changing product behavior or acceptance content. Requires current entry approval; never use for DERIVED requirements or new product implementation. --- -# Verify confirmed as-built requirements +# Verify requirements and correct approved tests Turn behavior described from shipped code into current direct UR upper or SR lower evidence. @@ -15,8 +15,8 @@ completion meanings. ## Keep the transition honest -This skill verifies confirmed as-built behavior. It does not grant human -approval or prove delivery. +This skill verifies confirmed as-built behavior and handles corrections confined +to already-approved tests. It does not grant approval or change product behavior. - Never run this skill for a `DERIVED` requirement. `DERIVED` means no human has confirmed that the requirement exists; its proposed links are candidate @@ -27,13 +27,14 @@ approval or prove delivery. - For an SR, record `LOWER_VERIFIED` and move it to `IN_REVIEW` only when its lower trace is current. For a UR, record `UPPER_VALIDATED` and move it to `IN_REVIEW` only when its upper trace and required-SR conditions are current. + Either kind also requires its corrective rechecks to pass before review. - Never move a requirement to `DONE` from test evidence alone. `DONE` also requires the applicable approval, authoritative-source delivery, and reconciliation conditions. - If the implementation contradicts the row, record the discovery and route a - red-first correction through triage: use build when current approval covers - it, or renewed planning when scope/intent changes. Do not change behavior - during the verification pass. + red-first correction through triage: use build when an approved SR covers + it, or planning when SR authority is absent or scope/intent changes. Do not + change behavior during the verification pass. ## Enter through the same gate as any other change @@ -76,6 +77,28 @@ approval is absent or stale, route that prerequisite first. Verification outside the authoritative work selection is invisible to planning, and a test written before the gate cannot be traced to approved intent. +## Correct already-approved tests + +For a review/EC failure confined to tests, accept the recorded correction even +when behavioral evidence still passes. Identify its UR/SR owner, unchanged entry +approval, correction boundary and required reruns. Reopen reviewed items via the +canonical correction route before edits. A UR-only Epic needs no fabricated SR. + +Make only the test correction; preserve product behavior and approved acceptance +content. Do not weaken assertions to satisfy an EC. Reassess retained RED against +the targeted clauses/assertions; a naming-only edit needs no artificial RED. +If a test is renamed, preserve prior observations and record the identity mapping +and retention basis rather than rewriting historical test names. Changed +assertions require renewed sensitivity evidence when the old observation no +longer demonstrates the target. + +Run affected upper/lower tests and proportional regression gates, then rerun the +failed engineering/review check at the corrected candidate fingerprint. Resolve +the finding only after that check passes. Reconcile evidence, affected lifecycle +states and the next action; return to completion only after both behavioral and +corrective checks pass. Product implementation changes leave this pass and need +an approved SR/build route or planning; acceptance or policy changes replan. + ## Establish the evidence bar For each row, require all of the following: @@ -123,7 +146,8 @@ assertion remains unverified. work-selection records atomically; in a store-backed repository, refresh the materialized snapshots afterwards. 6. Advance only the evidence conclusion justified by the run. Move the selected - requirement to `IN_REVIEW` only if its applicable trace gates pass; otherwise + requirement to `IN_REVIEW` only if its applicable trace gates and required + corrective rechecks pass; otherwise leave it at the strongest supported non-final state. 7. Repeat for every approved scenario or clause lacking current evidence. Do not request human input for an evidence failure within the approved @@ -164,8 +188,8 @@ Do not weaken a test to promote a row. report the authority contradiction through triage, hold downstream work, and emit its confirmation gate; do not silently erase an existing human approval. - If the implementation cannot satisfy the row, record the contradiction and - route through triage. An in-scope defect uses the approved correction route - to build; a missing or changed requirement/decision needs planning. + route through triage. A product defect uses build only under an approved SR; + missing SR authority or a changed requirement/decision needs planning. - If verification needs unavailable infrastructure, keep the row `PENDING_VERIFICATION` before entry; after entry, use `BLOCKED` and record the suspended `TODO` or `IN_PROGRESS` state. @@ -192,10 +216,12 @@ state passes its deterministic checks. ## Execution contract -- Input: confirmed as-built scope with current applied entry approval, no hold, - and missing/stale evidence; new entrants TODO, resumed work IN_PROGRESS. +- Input: confirmed as-built scope or an already-approved test-only correction, + with current entry approval and no hold; missing/stale evidence or a recorded + review/EC finding is sufficient. New entrants TODO, resumed work IN_PROGRESS; + reviewed work is reopened first. UR-owned tests do not require an SR. - Writes: scoped tests and safely restored mutations, per-run observations and assessments, evidence-backed transitions, and durable next action; no new behavior. - Exit: applicable current trace and IN_REVIEW, or an exact missing fact/hold. - Completion belongs to `rdd-completion-review`; observed defects route to build - only after triage establishes the existing approval or renewed entry it needs. + Completion belongs to `rdd-completion-review`; product defects route to build + only under an approved SR, or to planning if that authority is missing. diff --git a/tests/audit-citations.test.mjs b/tests/audit-citations.test.mjs index a72880d..f1fa0a3 100644 --- a/tests/audit-citations.test.mjs +++ b/tests/audit-citations.test.mjs @@ -135,6 +135,19 @@ test('ordinary Markdown and prose citation endings still resolve', t => { assert.match(result.output, /checked=5/); }); +test('a quoted parameterized name is checked as a complete name', t => { + const result = audit(t, 'TEST:behavior.test.ts:"checksBehavior[ActualCase]"', { + 'behavior.test.ts': 'test("checksBehavior[ActualCase]", () => {});\n', + }); + assert.equal(result.status, 0, result.output); + assert.match(result.output, /checked=1/); +}); + +test('a missing quoted parameterized name does not pass on its base name', t => { + const result = audit(t, 'TEST:behavior.test.ts:"checksBehavior[MissingCase]"'); + assert.equal(result.status, 1, result.output); +}); + test('quoted citations resolve static template-literal test names', t => { const result = audit(t, 'TEST:behavior.test.ts:"rejects invalid input"', { 'behavior.test.ts': 'test(`rejects invalid input`, () => {});\n', diff --git a/tests/process-scenarios.md b/tests/process-scenarios.md index 5903708..7673075 100644 --- a/tests/process-scenarios.md +++ b/tests/process-scenarios.md @@ -87,6 +87,34 @@ Negative variation: Correcting the finding requires a new architecture decision or expands EC applicability. Route to planning and the exact human decision; the in-scope correction route cannot authorize the expansion. +## UR-only acceptance-test correction + +Initial facts: Epic E contains only a confirmed as-built UR-U with no required +SRs. Current entry approval covers adding its acceptance tests. U/E are +IN_REVIEW, upper evidence passes, and sensitivity RED is RETAINED. Candidate +engineering finds that a new test name violates an existing active EC; the +affected surface, product behavior and acceptance content are unchanged. + +Event: Continue from the candidate failure and correct the test name. + +Expected: Block integration and record a correction owned by U. Reopen U and +dependent E to IN_PROGRESS, preserving current planning/cold/entry authority. +Route from completion or triage to `rdd-verify`, whose correction input accepts +the EC finding even though behavioral evidence still passes. Rename only the +test, record the old/new identity mapping and retention basis, and preserve +historical observations. Do not invent an SR, relation, behavioral requirement, +or artificial RED. Rerun upper tests, proportional regression gates, and the +failed candidate engineering check. Return U/E to IN_REVIEW only after the +behavioral and corrective rechecks pass, then resume completion with delivery, +separate delivered engineering evidence and human acceptance. + +Negative variations: An unresolved EC finding keeps the correction open. If +the correction changes assertions, reassess sensitivity and obtain new evidence +where needed. If actual product implementation must change and no approved SR +covers it, route to planning for the necessary authority; verification cannot +make that change under the UR test-correction exception. Changed acceptance, +policy or affected surface also requires planning. + ## Upper failure after all lower traces pass Initial facts: All planned SR lower traces pass and SRs are IN_REVIEW. UR-U's