@nurix/etna 0.20.9 → 0.20.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/catalog/rules/cloudflare.md +8 -0
- package/data/catalog/rules/database.md +10 -0
- package/data/catalog/rules/frontend.md +10 -0
- package/data/catalog/rules/pnpm.md +9 -0
- package/data/catalog/rules/react-native.md +10 -0
- package/data/catalog/rules/rust-desktop.md +8 -0
- package/data/catalog/rules/temporal.md +9 -0
- package/data/harness-index.json +1 -1
- package/data/harnesses/code/CLAUDE.md +6 -4
- package/data/harnesses/code/agents/polygraph-applier.md +24 -0
- package/data/harnesses/code/agents/polygraph-finder.md +31 -0
- package/data/harnesses/code/agents/polygraph-verifier.md +20 -0
- package/data/harnesses/code/codex/agents/polygraph-applier.toml +21 -0
- package/data/harnesses/code/codex/agents/polygraph-finder.toml +28 -0
- package/data/harnesses/code/codex/agents/polygraph-verifier.toml +17 -0
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/awareness.mjs +23 -6
- package/data/harnesses/code/hooks/lib/ownership.mjs +51 -19
- package/data/harnesses/code/hooks/lib/plans.mjs +11 -1
- package/data/harnesses/code/rules/cloudflare.md +8 -0
- package/data/harnesses/code/rules/database.md +10 -0
- package/data/harnesses/code/rules/frontend.md +10 -0
- package/data/harnesses/code/rules/pnpm.md +9 -0
- package/data/harnesses/code/skills/documentation/SKILL.md +10 -3
- package/data/harnesses/code/skills/documentation/references/frontmatter.md +21 -7
- package/data/harnesses/code/skills/documentation/references/instruction-style.md +1 -1
- package/data/harnesses/code/skills/documentation/templates/Readme.md +3 -1
- package/data/harnesses/code/skills/documentation/templates/package.md +131 -0
- package/data/harnesses/code/skills/documentation/templates/plan.md +2 -2
- package/data/harnesses/code/skills/documentation/templates/structure.md +9 -3
- package/data/harnesses/code/skills/documentation/templates/system.md +143 -0
- package/data/harnesses/code/skills/polygraph/SKILL.md +71 -0
- package/data/harnesses/code/skills/polygraph/references/dispatch.md +80 -0
- package/data/harnesses/code/skills/polygraph/references/rows.md +57 -0
- package/data/harnesses/code/skills/polygraph/scripts/audit.mjs +113 -0
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/SKILL.md +10 -3
- package/data/harnesses/delivery/skills/documentation/references/frontmatter.md +21 -7
- package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +1 -1
- package/data/harnesses/delivery/skills/documentation/templates/Readme.md +3 -1
- package/data/harnesses/delivery/skills/documentation/templates/package.md +131 -0
- package/data/harnesses/delivery/skills/documentation/templates/plan.md +2 -2
- package/data/harnesses/delivery/skills/documentation/templates/structure.md +9 -3
- package/data/harnesses/delivery/skills/documentation/templates/system.md +143 -0
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/documentation/SKILL.md +10 -3
- package/data/harnesses/etna/skills/documentation/references/frontmatter.md +21 -7
- package/data/harnesses/etna/skills/documentation/references/instruction-style.md +1 -1
- package/data/harnesses/etna/skills/documentation/templates/Readme.md +3 -1
- package/data/harnesses/etna/skills/documentation/templates/package.md +131 -0
- package/data/harnesses/etna/skills/documentation/templates/plan.md +2 -2
- package/data/harnesses/etna/skills/documentation/templates/structure.md +9 -3
- package/data/harnesses/etna/skills/documentation/templates/system.md +143 -0
- package/package.json +2 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Dispatch — packs, roles, packets, recovery, price
|
|
2
|
+
|
|
3
|
+
Load when: composing a context pack, dispatching any role, packeting rows, recovering a dead or turn-capped agent, or pricing a run.
|
|
4
|
+
|
|
5
|
+
## The run folder
|
|
6
|
+
|
|
7
|
+
`.claude/state/polygraph/{YYYY-MM-DD-slug}/` — machine-local, never tracked, deleted when the run closes:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
partials/{doc}.psv one per finder — its whole output, no header
|
|
11
|
+
findings.psv the merge, header first — the working copy
|
|
12
|
+
malformed.txt quarantined lines, kept
|
|
13
|
+
packets/{doc}.txt one per applier: `doc: {path}` then row ids and bounce notes
|
|
14
|
+
verify-{group}.psv id|check|confidence|citations|note
|
|
15
|
+
apply-{doc}.psv id|outcome|note
|
|
16
|
+
packs/{doc}.md the context pack each finder and applier reads
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Work items pass through files, never through a dispatch call's arguments — a resumed agent may not carry arguments, and one that dies on a missing argument dies before it reads anything.
|
|
20
|
+
|
|
21
|
+
## The context pack — the agent's whole reading list
|
|
22
|
+
|
|
23
|
+
One page per target document, written by you before the first dispatch. A brief that sends an agent to the charter, the backlog, the ownership index, or the findings file hands it a 500 KB read to extract 2 KB of fact — and hands the same read to every other agent in the wave. The pack carries the 2 KB:
|
|
24
|
+
|
|
25
|
+
| Section | Content |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| Target | the doc path; in gate mode, the plan path and the Proposed Changes paths that intersect this doc's `owns:` |
|
|
28
|
+
| Code | the doc's `owns:` globs, resolved to the files that exist today |
|
|
29
|
+
| Precedent | the quotations from guardrails, accepted decisions, and doctrine that **name** a subject this doc covers — the naming words only, with `path:line` |
|
|
30
|
+
| Already tracked | backlog and register rows that name a subject this doc covers, as `path:line` |
|
|
31
|
+
| Rulings | owner answers that bind this subject, verbatim |
|
|
32
|
+
| Read budget | the two clauses below, verbatim |
|
|
33
|
+
|
|
34
|
+
The two clauses every pack carries:
|
|
35
|
+
|
|
36
|
+
> Tree state: re-check every citation against today's tree before acting; a claim that fails to reproduce dies as *did not reproduce* — never becomes an edit. READ-ONLY everywhere except your one output file.
|
|
37
|
+
|
|
38
|
+
> Read budget: the pack is your reading list — read nothing outside it without naming in your return why the pack was insufficient. Grep before you read; never read a file over 20 KB without a line range; read a file once. Compose one shell block per question, not one call per file.
|
|
39
|
+
|
|
40
|
+
## The roles
|
|
41
|
+
|
|
42
|
+
| Role | Agent | Receives | Writes | Returns |
|
|
43
|
+
| --- | --- | --- | --- | --- |
|
|
44
|
+
| Finder | `polygraph-finder` | the pack path, the partial path | `partials/{doc}.psv` — one row per claim, ten fields, no header | ≤ 120 words: the file path, row count, up to three findings, limitations |
|
|
45
|
+
| Verifier | `polygraph-verifier` | a queue of `id`, `subject`, `claim` — no evidence, no verdict | `verify-{group}.psv` | ≤ 120 words: the file path and counts per check |
|
|
46
|
+
| Applier | `polygraph-applier` | the packet path, the pack path | the packet's docs; `apply-{doc}.psv` | ≤ 120 words: counts per outcome, docs touched, lint result, anything for the register |
|
|
47
|
+
|
|
48
|
+
Each agent definition carries its role's doctrine and its turn cap; a dispatch names the target and the paths and nothing else. **Findings go in the file, never in the return** — every word returned is re-billed on every later turn of the session.
|
|
49
|
+
|
|
50
|
+
Where the runtime has no such agent installed, dispatch an independent subagent on the cheapest capable tier with the agent file's body pasted in as its brief, and say so in the report.
|
|
51
|
+
|
|
52
|
+
## Packets — one doc, one applier
|
|
53
|
+
|
|
54
|
+
Routing is judgment, so you compose packets by hand; `audit.mjs` asserts the one mechanical invariant: **no document appears in two packets** — appliers run in parallel, and two agents in one file silently lose each other's edits.
|
|
55
|
+
|
|
56
|
+
**Packet by the doc the fix belongs in, not the doc a row's evidence cites.** A row's citations say where the *evidence* lives; the fix usually belongs elsewhere, and a bounced row's `needs-cross-doc` note says exactly where. A row with no derivable doc goes to the owner — never into a packet "to see what the agent makes of it".
|
|
57
|
+
|
|
58
|
+
By verdict: QUESTION and `depends_on` rows → the owner, inline. CONTESTED → you, with both sets of evidence. RESIDUE, and any row whose subject is a source file — a stale comment included — → the register. Everything else → the owning doc's packet.
|
|
59
|
+
|
|
60
|
+
## Verification — budgeted by what it caught
|
|
61
|
+
|
|
62
|
+
- **The applier re-check is the law.** In the proving run it killed more false positives than every other mechanism combined.
|
|
63
|
+
- **Blind re-derivation is discretionary.** Send a row *you* doubt — claim and subject only. As a mandatory stage it refuted nothing: finders' self-doubt is conservative, and the errors live in the confident pile, which is where your own read of the evidence looks. Disagreement makes the row CONTESTED; a verification never overwrites a verdict.
|
|
64
|
+
|
|
65
|
+
## Fold-back
|
|
66
|
+
|
|
67
|
+
After each applier returns, its outcomes become row state by your own edit, then `audit.mjs` checks the arithmetic:
|
|
68
|
+
|
|
69
|
+
- `applied` · `registered` · `exempt-frozen` → `resolved`, the resolution stamped with how.
|
|
70
|
+
- `refuted-on-apply` → `resolved`, resolution `did not reproduce`. A legitimate death; its count is the quality signal you report.
|
|
71
|
+
- `needs-cross-doc` · `blocked-on-question` · `skipped` → stays open — repacket, or hold for the answer.
|
|
72
|
+
- COHERENT rows close when recorded; they carry no work.
|
|
73
|
+
|
|
74
|
+
## Recovery
|
|
75
|
+
|
|
76
|
+
**The file on disk is the recovery unit, never the wave.** A dead agent costs its remainder; read what landed and relaunch only that. A finder that returns **partial** at its turn cap is resumed once from its partial — a second partial is accepted as that doc's sweep and named as such in the report. Every dispatch names its output file and a time bound; an agent past its bound with no file is dead — relaunch it, never wait on it, and never let one runaway agent hold the run. Gates — lint, links, ownership — run in your own seat.
|
|
77
|
+
|
|
78
|
+
## Price
|
|
79
|
+
|
|
80
|
+
State before dispatching, whenever a run exceeds ten documents or an owner directive names a tier: the per-doc cost at the pinned tier, the wave count under the concurrency ceiling, and the total; for a stronger tier, the multiplier and both totals. The directive and the estimate go in the changelog entry.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Rows — the row, the verdicts, the residue guard
|
|
2
|
+
|
|
3
|
+
Load when: writing or reading any row, judging a CONTESTED or QUESTION row, deciding whether a subject is residue, or ruling on a document's right to exist.
|
|
4
|
+
|
|
5
|
+
## The row
|
|
6
|
+
|
|
7
|
+
Pipe-delimited, one row per claim, **ten fields**, header line first. A literal `|` inside text is written `¦`; a newline becomes `; `.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|resolution
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Field | Content |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `id` | `{DOC}-{NNN}` — the target document's stem and a running number, unique within the run |
|
|
16
|
+
| `subject` | the stable key — a file, table, route, command, or surface |
|
|
17
|
+
| `claim` | one sentence: what is asserted true or false |
|
|
18
|
+
| `evidence` | verbose support — `file:line` citations or the exact command and its result. **No citation, no row.** A verdict leaning on a precedent quotes the precedent's naming words here |
|
|
19
|
+
| `counter_evidence` | what cuts the other way, including `already-registered: path:line` for a known item |
|
|
20
|
+
| `verdict` | one of the seven below |
|
|
21
|
+
| `confidence` | 0.0–1.0, honest — a signal that tells the primary where to look, never a routing key |
|
|
22
|
+
| `depends_on` | empty, or the owner question the row waits on, as the question's own words in brief |
|
|
23
|
+
| `status` | `open` · `resolved` · `reopened` |
|
|
24
|
+
| `resolution` | how it closed — `applied` · `registered` · `did not reproduce` · `answered` · `exempt-frozen` — empty while open |
|
|
25
|
+
|
|
26
|
+
**A negative claim about a long document quotes the passage read** — the sentence believed wrong, or the section where the missing statement should live. Unsupported negative claims are the dominant false-positive class, and they concentrate in the longest documents.
|
|
27
|
+
|
|
28
|
+
## The seven verdicts
|
|
29
|
+
|
|
30
|
+
| Verdict | Meaning | Disposition |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `COHERENT` | doc and code agree — emitted at the audited unit so coverage is countable; closed when recorded | none |
|
|
33
|
+
| `MISDESCRIBED` | the doc is wrong about built behaviour | an applier fixes the doc |
|
|
34
|
+
| `UNDOCUMENTED` | code no doc claims — judged against the precedent pack **first**: condemned code is RESIDUE, never a documentation gap | an applier documents it in the owning doc |
|
|
35
|
+
| `UNBUILT` | a documented design with no code behind it — decided-unbuilt (the repo's marker convention), abandoned, or a document that should die; only the owner distinguishes the last two, so suspecting either makes the row a QUESTION | the marker |
|
|
36
|
+
| `RESIDUE` | code a standing decision condemns by name — running, even load-bearing, and still retiring | a register row, never a doc edit |
|
|
37
|
+
| `CONTESTED` | the evidence conflicts — a verifier and a finder disagree, or a claim has no code to arbitrate and a sibling document says otherwise | the primary judges with both sets of evidence in hand |
|
|
38
|
+
| `QUESTION` | only the owner can resolve it — intent, not evidence, is what is missing | asked inline, answered in the same pass |
|
|
39
|
+
|
|
40
|
+
A disposition is the verdict's default; where the primary overrides it, the resolution says so. **A verification never overwrites a verdict** — disagreement makes the row CONTESTED.
|
|
41
|
+
|
|
42
|
+
## The residue guard
|
|
43
|
+
|
|
44
|
+
Before an UNDOCUMENTED row becomes a doc edit, its subject is tested against the precedent pack — the one place the audit can silently undo the repo's own decisions. The test is **naming**: a precedent matches only when it names the subject — the file, table, route, command, or surface by the product's own name for it — and the row quotes the naming words into `evidence`. A precedent describing a *resembling* thing is not a match; shared adjectives ("legacy", "old export lane") are not a match; a misread pronoun is not a match. Unsure → QUESTION, never a silent RESIDUE.
|
|
45
|
+
|
|
46
|
+
The asymmetry runs both ways. A missed match resurfaces: the register is owner-read, and an UNDOCUMENTED row that should have been RESIDUE is caught when the owner reads the doc it produced. A false match does not: it retires code the product still ships and withholds its documentation — worse than the disease. When in doubt, ask.
|
|
47
|
+
|
|
48
|
+
## Frozen records and code
|
|
49
|
+
|
|
50
|
+
- **Frozen records are exempt** — a completed plan, a changelog entry, an archived document resolves `exempt-frozen`; its claims were true when frozen and the tree does not correct history.
|
|
51
|
+
- **The audit never edits code.** A code-side finding — a bug, a stale source comment, the unbuilt, residue — exits as a register row for the planning workflow, however small the edit and however directly it is asked for. **Markdown is not source**: every document read may be edited under the applier discipline, package and root READMEs included; the line is doc versus code, not proximity to `src/`.
|
|
52
|
+
|
|
53
|
+
## A document's right to exist
|
|
54
|
+
|
|
55
|
+
A wholly stale document passes a sentence-by-sentence audit one true sentence at a time, so a finder that reads a document whose subject is gone, whose authority another doc has absorbed, or whose content belongs inside a sibling emits **one QUESTION row proposing deletion or merge**, carrying the inbound-reference count (who links here, which index rows list it, which ownership globs it claims). Deletion is owner-approved, always. An approved deletion removes the file, repoints every inbound reference to the surviving authority, and leaves **no redirect stub** — an annotation, a banner, or a status flag left in the reading path is not a source of truth however carefully the surviving sentences are hedged. A document whose subject survives but whose content is wrong is MISDESCRIBED, not a deletion candidate.
|
|
56
|
+
|
|
57
|
+
**Abandonment has no marker.** A design decided against is not owed: its claims come **out** of every marker that carried them rather than being reworded inside one, and the document survives as the record of a decision not to build, its status saying so. The tell that this went wrong: a reader finishes the paragraph still expecting the feature.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// skills/polygraph/scripts/audit.mjs - The audit's one script, and it is READ-ONLY: it validates
|
|
2
|
+
// the run's findings file, cross-checks verifier and applier outcome files against the rows, and
|
|
3
|
+
// asserts the packet-collision invariant. It never writes a byte — the primary holds the pen;
|
|
4
|
+
// this holds the arithmetic. A checker that is wrong emits a wrong warning; a script that acted
|
|
5
|
+
// on its own verdicts could destroy a refutation with no tally, which is the defect class this
|
|
6
|
+
// replaces.
|
|
7
|
+
//
|
|
8
|
+
// Usage: node audit.mjs <run-dir> (the run folder holding findings.psv)
|
|
9
|
+
// Exit: 0 clean · 1 findings to read · 2 unusable input
|
|
10
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
const HEADER = 'id|subject|claim|evidence|counter_evidence|verdict|confidence|depends_on|status|resolution';
|
|
14
|
+
const FIELDS = HEADER.split('|').length; // 10
|
|
15
|
+
const VERDICTS = new Set(['COHERENT', 'MISDESCRIBED', 'UNDOCUMENTED', 'UNBUILT', 'RESIDUE', 'CONTESTED', 'QUESTION']);
|
|
16
|
+
const STATUSES = new Set(['open', 'resolved', 'reopened']);
|
|
17
|
+
const RESOLVING = new Set(['applied', 'exempt-frozen', 'refuted-on-apply']);
|
|
18
|
+
const STAYING = new Set(['needs-cross-doc', 'blocked-on-question', 'skipped']);
|
|
19
|
+
const CHECKS = new Set(['CONFIRMED', 'REFUTED', 'ADJUSTED']);
|
|
20
|
+
|
|
21
|
+
const run = process.argv[2];
|
|
22
|
+
if (!run || !existsSync(run) || !statSync(run).isDirectory()) { console.error('usage: node audit.mjs <run-dir>'); process.exit(2); }
|
|
23
|
+
const findingsPath = join(run, 'findings.psv');
|
|
24
|
+
if (!existsSync(findingsPath)) { console.error(`no findings file at ${findingsPath}`); process.exit(2); }
|
|
25
|
+
|
|
26
|
+
const problems = []; // schema violations and cross-check disagreements → exit 1
|
|
27
|
+
const notes = []; // informational — never fails the run
|
|
28
|
+
|
|
29
|
+
// ---- parse + validate the working file -------------------------------------------------------
|
|
30
|
+
const lines = readFileSync(findingsPath, 'utf8').split('\n');
|
|
31
|
+
if (lines[0].trim() !== HEADER) { console.error(`bad or missing header line in ${findingsPath}`); process.exit(2); }
|
|
32
|
+
|
|
33
|
+
const rows = new Map(); // id → { verdict, status, resolution, line }
|
|
34
|
+
for (let n = 1; n < lines.length; n++) {
|
|
35
|
+
const line = lines[n];
|
|
36
|
+
if (!line.trim()) continue;
|
|
37
|
+
const p = line.split('|');
|
|
38
|
+
const at = `line ${n + 1}`;
|
|
39
|
+
if (p.length !== FIELDS) { problems.push(`${at}: ${p.length} fields, expected ${FIELDS}`); continue; }
|
|
40
|
+
const [id, , claim, evidence, , verdict, confidence, , status, resolution] = p;
|
|
41
|
+
if (!id.trim()) { problems.push(`${at}: empty id`); continue; }
|
|
42
|
+
if (rows.has(id)) problems.push(`${at} (${id}): duplicate id — first seen at line ${rows.get(id).line}`);
|
|
43
|
+
if (!VERDICTS.has(verdict)) problems.push(`${at} (${id}): unknown verdict "${verdict}"`);
|
|
44
|
+
const conf = Number(confidence);
|
|
45
|
+
if (!(conf >= 0 && conf <= 1)) problems.push(`${at} (${id}): confidence "${confidence}" outside 0..1`);
|
|
46
|
+
if (!STATUSES.has(status)) problems.push(`${at} (${id}): unknown status "${status}"`);
|
|
47
|
+
if (status === 'resolved' && !resolution.trim())
|
|
48
|
+
problems.push(`${at} (${id}): resolved with an empty resolution — how did it close?`);
|
|
49
|
+
if (!claim.trim() || !evidence.trim())
|
|
50
|
+
problems.push(`${at} (${id}): empty ${!claim.trim() ? 'claim' : 'evidence'} — no citation, no row`);
|
|
51
|
+
rows.set(id, { verdict, status, resolution, line: n + 1 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---- cross-check outcome files against row status --------------------------------------------
|
|
55
|
+
const files = readdirSync(run).filter((f) => f.endsWith('.psv'));
|
|
56
|
+
|
|
57
|
+
// apply-*.psv → id|outcome|note
|
|
58
|
+
for (const f of files.filter((f) => f.startsWith('apply-'))) {
|
|
59
|
+
for (const line of readFileSync(join(run, f), 'utf8').split('\n')) {
|
|
60
|
+
if (!line.trim()) continue;
|
|
61
|
+
const [id, outcome] = line.split('|').map((s) => (s ?? '').trim());
|
|
62
|
+
const row = rows.get(id);
|
|
63
|
+
if (!row) { problems.push(`${f}: outcome for unknown row "${id}"`); continue; }
|
|
64
|
+
if (!RESOLVING.has(outcome) && !STAYING.has(outcome)) { problems.push(`${f} (${id}): unknown outcome "${outcome}"`); continue; }
|
|
65
|
+
if (RESOLVING.has(outcome) && row.status === 'open')
|
|
66
|
+
problems.push(`${f} (${id}): outcome "${outcome}" but the row is still open — the fold missed it`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// verify-*.psv → id|check|confidence|citations|note ; disagreement across files is a finding
|
|
71
|
+
const seenChecks = new Map(); // id → { check, file }
|
|
72
|
+
for (const f of files.filter((f) => f.startsWith('verify-'))) {
|
|
73
|
+
for (const line of readFileSync(join(run, f), 'utf8').split('\n')) {
|
|
74
|
+
if (!line.trim()) continue;
|
|
75
|
+
const [id, check] = line.split('|').map((s) => (s ?? '').trim());
|
|
76
|
+
const row = rows.get(id);
|
|
77
|
+
if (!row) { problems.push(`${f}: verification of unknown row "${id}"`); continue; }
|
|
78
|
+
if (!CHECKS.has(check)) { problems.push(`${f} (${id}): unknown check "${check}" — CONFIRMED/REFUTED/ADJUSTED only`); continue; }
|
|
79
|
+
const earlier = seenChecks.get(id);
|
|
80
|
+
if (earlier && earlier.check !== check)
|
|
81
|
+
problems.push(`${id}: two verifiers disagree — ${earlier.check} in ${earlier.file}, ${check} in ${f} → the row is CONTESTED, judge it`);
|
|
82
|
+
else seenChecks.set(id, { check, file: f });
|
|
83
|
+
if (check === 'REFUTED' && row.verdict !== 'CONTESTED' && row.status !== 'reopened')
|
|
84
|
+
notes.push(`${id}: a verifier REFUTED this row and it is neither CONTESTED nor reopened — judged already, or missed?`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// packets/*.txt → `doc: path` lines; no doc in two packets
|
|
89
|
+
const packetsDir = join(run, 'packets');
|
|
90
|
+
if (existsSync(packetsDir)) {
|
|
91
|
+
const claims = new Map(); // doc → packet file
|
|
92
|
+
for (const f of readdirSync(packetsDir).filter((f) => f.endsWith('.txt'))) {
|
|
93
|
+
for (const line of readFileSync(join(packetsDir, f), 'utf8').split('\n')) {
|
|
94
|
+
const m = /^doc:\s*(.+)$/.exec(line.trim());
|
|
95
|
+
if (!m) continue;
|
|
96
|
+
const doc = m[1].trim();
|
|
97
|
+
if (claims.has(doc)) problems.push(`packets: "${doc}" claimed by both ${claims.get(doc)} and ${f} — two appliers in one file lose edits`);
|
|
98
|
+
else claims.set(doc, f);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- report -----------------------------------------------------------------------------------
|
|
104
|
+
const total = rows.size;
|
|
105
|
+
const resolved = [...rows.values()].filter((r) => r.status === 'resolved').length;
|
|
106
|
+
const hist = {};
|
|
107
|
+
for (const r of rows.values()) hist[r.verdict] = (hist[r.verdict] || 0) + 1;
|
|
108
|
+
console.log(`rows: ${total} · resolved: ${resolved} · open: ${total - resolved}`);
|
|
109
|
+
console.log(`verdicts: ${Object.entries(hist).map(([k, v]) => `${k} ${v}`).join(' · ') || 'none'}`);
|
|
110
|
+
for (const n of notes) console.log(`~ ${n}`);
|
|
111
|
+
for (const p of problems) console.log(`✗ ${p}`);
|
|
112
|
+
console.log(problems.length ? `${problems.length} finding(s) to read` : 'clean');
|
|
113
|
+
process.exit(problems.length ? 1 : 0);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "delivery",
|
|
3
3
|
"name": "delivery",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.30",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
"seeds": [],
|
|
11
11
|
"tools": [],
|
|
12
12
|
"docsRoot": "docs",
|
|
13
|
-
"digest": "
|
|
13
|
+
"digest": "688776066c84"
|
|
14
14
|
}
|
|
@@ -60,6 +60,8 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
60
60
|
| Baseline / reconcile docs | `maintenance` agent (baseline mode) — it calls this skill per template |
|
|
61
61
|
| Document feature/module (LLD) | [`templates/feature.md`](templates/feature.md) |
|
|
62
62
|
| Document data flow / state / Redux | [`templates/feature.md`](templates/feature.md) — they are sections of the feature doc |
|
|
63
|
+
| Document a workspace package (`docs/architecture/packages/{package}.md`) | [`templates/package.md`](templates/package.md) — the **residual** owner of `packages/{name}/**`: its manifest, build, entry, layout and every behavior no system or feature doc claims at higher specificity |
|
|
64
|
+
| Document one system spanning packages (`docs/architecture/systems/{system}.md`) | [`templates/system.md`](templates/system.md) — one machine end to end; its `owns:` names the machine's subtrees, narrower than the package glob, so it wins by specificity. Not a screen — a system doc written as a giant feature doc is the mistake it prevents |
|
|
63
65
|
| Create/update changelog entry | [`templates/changelog-entry.md`](templates/changelog-entry.md) (record — one per unit of work; carries the refinement fields, frozen inside its commit, distilled by the `retro` agent) |
|
|
64
66
|
| Create/update plan | [`templates/plan.md`](templates/plan.md) (record) |
|
|
65
67
|
| Record/revise architecture decision | [`templates/architecture-decision.md`](templates/architecture-decision.md) — one AD per subject; revisions update it in place |
|
|
@@ -82,7 +84,7 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
82
84
|
| Add to protected files | [`templates/protected-files.md`](templates/protected-files.md) |
|
|
83
85
|
| Create/update root README | [`templates/root-readme.md`](templates/root-readme.md) |
|
|
84
86
|
| Scaffold a standard doc (docs index, `structure.md`, CHANGELOG, backlog, memory) | [`templates/Readme.md`](templates/Readme.md) — the index names each scaffold file |
|
|
85
|
-
| Derive/update
|
|
87
|
+
| Derive/update an owning doc's `owns:` (feature, package, system) | [`references/frontmatter.md`](references/frontmatter.md) |
|
|
86
88
|
| Update existing doc | § Editing an existing doc (below) |
|
|
87
89
|
| Instruction file (`CLAUDE.md`, a rule, an agent, skill prose) | [`references/instruction-style.md`](references/instruction-style.md) — not these templates; every-load prose stays lean, and the present-tense rule still applies |
|
|
88
90
|
| Author or restructure a **skill** (fit, triggers, shape, anatomy, shipping) | The skill-authoring field guide — the `etna` repo (`nurixlabs/etna`), folder `docs/playbooks/skills/`, chapters `00`–`10`. **No etna checkout available ⇒ ask the user for access to the etna repo, or proceed from first principles** with [`references/instruction-style.md`](references/instruction-style.md) governing the prose. This row is the only place the playbook is referenced |
|
|
@@ -105,6 +107,8 @@ The two guarantees are complementary and both break on mixing: a reader holding
|
|
|
105
107
|
| Document | Nature | Name | Lifecycle |
|
|
106
108
|
| --- | --- | --- | --- |
|
|
107
109
|
| Feature (LLD) | Truth | `kebab-case.md` | Living; `status:` `draft` → `active` → `deprecated` |
|
|
110
|
+
| Package | Truth | `{package-folder}.md` | Living; `status:` as the feature doc |
|
|
111
|
+
| System | Truth | `{machine}.md` | Living; `status:` as the feature doc |
|
|
108
112
|
| Architecture Decision | Truth | `AD-NNN-kebab-case.md` | Living; `Proposed` → `Accepted` → `Deprecated`; revisions rewrite the body + append to its Decision Changelog |
|
|
109
113
|
| Steering docs (overview, domain model, glossary, non-goals, security, testing strategy) | Truth | fixed names | Living |
|
|
110
114
|
| API reference / environment registry | Truth | fixed names | Living |
|
|
@@ -125,8 +129,11 @@ doc, plus the genre's own keys beside it; a document without its frontmatter fai
|
|
|
125
129
|
contract and is not done. **There is no frontmatter-less genre.** A feature or UX doc carries the
|
|
126
130
|
full set — `owns:` (required), `module:` (optional), `keywords:` (required), `status:` (required),
|
|
127
131
|
`applies:` (the architecture docs it obeys; omitted when none), optional `last-reviewed:`. A
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
**package** doc (`docs/architecture/packages/`) and a **system** doc (`docs/architecture/systems/`)
|
|
133
|
+
carry the same set minus `module:` — a package is already a bounded unit and a machine spanning
|
|
134
|
+
packages belongs to no single context. A behavioral checklist (`docs/tests/`) carries
|
|
135
|
+
`type: checklist` and `verifies:` — the doc whose promises it proves. An acceptance-criteria doc
|
|
136
|
+
(`docs/acceptance/`) carries `type:` and `status:`.
|
|
130
137
|
|
|
131
138
|
**The frontmatter is where the docs tree's edges live**, and a doc that declares none is a node
|
|
132
139
|
nothing can reach: no change implicates it, so it rots without the rot being detectable. Deriving
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Frontmatter Derivation — the doc graph
|
|
2
2
|
|
|
3
|
-
Load when: creating a doc that declares an edge (a feature doc, a UX doc, a behavioral checklist), updating one whose scope moved, or running a baseline/reconcile pass over many docs.
|
|
3
|
+
Load when: creating a doc that declares an edge (a feature, package or system doc, a UX doc, a behavioral checklist), updating one whose scope moved, or running a baseline/reconcile pass over many docs.
|
|
4
4
|
|
|
5
5
|
**A docs tree is a graph, and every edge in it is declared in frontmatter.** Two kinds of edge exist: **doc → code** (`owns:`), and **doc → doc** (`verifies:`, `applies:`). The SessionStart hook reads them all and injects the generated indexes; there is no hand-maintained map of either kind.
|
|
6
6
|
|
|
@@ -14,13 +14,25 @@ The consuming end is also the end that **knows**. A checklist author knows what
|
|
|
14
14
|
|
|
15
15
|
## When to run
|
|
16
16
|
|
|
17
|
-
- **Baseline / reconcile** — for every
|
|
18
|
-
- **New
|
|
17
|
+
- **Baseline / reconcile** — for every owning doc whose code paths aren't yet declared in its `owns:` frontmatter (the `maintenance` agent (baseline mode) calls this)
|
|
18
|
+
- **New owning doc** — when creating one via the [Feature](../templates/feature.md), [Package](../templates/package.md) or [System](../templates/system.md) template, project its `owns:` globs
|
|
19
19
|
- **Doc update** — when the documented scope changes, update the doc's `owns:` globs
|
|
20
20
|
|
|
21
|
+
## Which genres own code
|
|
22
|
+
|
|
23
|
+
**Three do, and no others: `feature`, `package`, `system`.** They layer by glob specificity over the same tree, which is what lets a repo be documented at three altitudes without subtracting anything by hand:
|
|
24
|
+
|
|
25
|
+
| Genre | Claims | Wins because |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `package` (`docs/architecture/packages/`) | `packages/{name}/**` — the whole tree, one glob | Nothing; it is the **residual** owner and loses every narrower claim, keeping what no one else takes |
|
|
28
|
+
| `system` (`docs/architecture/systems/`) | the subtrees and exact files one machine is made of | Its globs are narrower than the package glob — specificity |
|
|
29
|
+
| `feature` (`docs/features/`) | one capability's or one surface's globs | Same — specificity over the package glob |
|
|
30
|
+
|
|
31
|
+
Every other genre owns no code and carries no `owns:`. That includes the cross-cutting doctrine in `docs/architecture/` (and `docs/architecture/common/`), which describes patterns rather than regions — a surface declares it is bound by one through `applies:`, and the doctrine never claims the surface back. Checklists, acceptance docs, ADs, operations procedures, research and brainstorm records own nothing.
|
|
32
|
+
|
|
21
33
|
## How to derive the frontmatter
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
An owning doc's ownership frontmatter is `owns:` (required), `module:` (optional — feature docs only; a package is already a bounded unit and a system spans contexts by definition), `keywords:` (required), and `status:` (required). An optional `last-reviewed:` date (stamped whenever the doc is substantively reviewed or updated) gives the `maintenance` agent a staleness signal to rank its audits by — it is tooling metadata, never narrated in the body. `type:` (required — the genre, enum = the template names) and `rev:` (generator-written generation stamp) are the same class: external tooling reads them, sessions **preserve them on edits** and never narrate or inject them. The `maintenance` agent stamps `type:` on docs that predate the key. `slug:` completes the set on generated docs: the identity key, minted once at creation and never re-derived from a display name — in the repo the filename is the slug, and the `maintenance` agent re-aligns the two when they diverge.
|
|
24
36
|
|
|
25
37
|
**1. `owns:`** (the path scope)
|
|
26
38
|
|
|
@@ -35,15 +47,17 @@ Prefer one region glob over many file globs. Ten paths say nothing about which i
|
|
|
35
47
|
| A feature's own directory | `src/feature/**` |
|
|
36
48
|
| A feature split across two directories | One glob each |
|
|
37
49
|
| A single entry-point file, and nothing beside it | The exact file path |
|
|
50
|
+
| A whole workspace package, residually | `packages/{name}/**` — one glob, on the package doc |
|
|
51
|
+
| A machine spanning packages | One glob or exact path per subtree it is made of, on the system doc |
|
|
38
52
|
| A shared file several features import | **Nothing — leave it unowned** |
|
|
39
53
|
|
|
40
54
|
**An unowned shared file is the system working, not a gap.** A shared module's authority is the contract it exposes, not any one consumer, so a library-references file or a common utility legitimately has no owning doc. Claiming it would make the index bigger and the routing less certain at once.
|
|
41
55
|
|
|
42
56
|
Glob each pattern against the codebase. At `status: active`, drop any that match nothing; at `status: draft`, projected globs may match nothing yet — confirm them against real code when the doc flips to `active`.
|
|
43
57
|
|
|
44
|
-
**2. `module:`**
|
|
58
|
+
**2. `module:`** (feature docs only)
|
|
45
59
|
|
|
46
|
-
The single bounded-context label this feature belongs to, or omit it for a cross-cutting feature that belongs to no single context. A module is a label, not a folder — features sharing a `module:` value are what ties that module to code (a filter over the generated ownership index).
|
|
60
|
+
The single bounded-context label this feature belongs to, or omit it for a cross-cutting feature that belongs to no single context. A module is a label, not a folder — features sharing a `module:` value are what ties that module to code (a filter over the generated ownership index). Package and system docs omit it entirely.
|
|
47
61
|
|
|
48
62
|
**3. `keywords:` & `status:`**
|
|
49
63
|
|
|
@@ -67,7 +81,7 @@ That edge has a property the others lack, and it is worth naming: **a `Pinned:`
|
|
|
67
81
|
|
|
68
82
|
## Rules
|
|
69
83
|
|
|
70
|
-
- One canonical doc per path. A doc's `owns:` globs must not overlap another doc's at equal specificity — the narrower glob wins; equal-specificity overlap is an ownership conflict to resolve.
|
|
84
|
+
- One canonical doc per path. A doc's `owns:` globs must not overlap another doc's at equal specificity — the narrower glob wins; equal-specificity overlap is an ownership conflict to resolve. Overlap at **unequal** specificity is the layering working as designed: a package doc's `packages/{name}/**` is meant to be outranked, and what survives every narrower claim is exactly what that doc describes.
|
|
71
85
|
- Use glob patterns for directories, explicit paths for individual files. At `active`, never list paths that don't exist in the codebase; a `draft`'s projected paths are confirmed at `active`.
|
|
72
86
|
- When a doc covers a deep feature with sub-specs (e.g., `editor/`), each sub-spec owns its own globs — don't duplicate a path across siblings.
|
|
73
87
|
- For baseline/reconcile with many docs, use parallel sub-agents to read source and propose `owns:` globs.
|
|
@@ -4,7 +4,7 @@ The shared style for every file an agent obeys — a `CLAUDE.md`, a rule, an age
|
|
|
4
4
|
|
|
5
5
|
## The test for every line
|
|
6
6
|
|
|
7
|
-
An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and rules load on **every turn of every session** — the most expensive prose in a repo per word; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
|
|
7
|
+
An instruction file is paid for whenever it loads, scaled by its load class: `CLAUDE.md` and unscoped rules load on **every turn of every session** — the most expensive prose in a repo per word; a `paths:`-scoped rule loads on those same terms from its first matching file read on; an agent definition loads per dispatch, a skill per invocation. The bar is not "is this true?" but **"must this be in front of the agent whenever this file loads?"** Measure weight in tokens, not lines; trim biggest-first on every edit.
|
|
8
8
|
|
|
9
9
|
**Rewriting, the test is armor vs. narration.** A reason clause either blocks a move an agent would otherwise make — the failure is counter-intuitive, the wrong fix looks correct, or the rule reads as arbitrary tidiness and gets "improved away" — or it explains the self-evident. Ask of each: *does the forbidden move look correct to an agent mid-task?* Keep the first kind, cut the second. **A constraint may lose its explanation; it may never lose its existence** — compression is fewer words for the same constraints, so a passage that cannot shrink without dropping one stays long.
|
|
10
10
|
|
|
@@ -5,6 +5,8 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
5
5
|
| Template | File | Nature |
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| Feature (LLD) | [feature.md](feature.md) | Truth |
|
|
8
|
+
| Package (one workspace package) | [package.md](package.md) | Truth |
|
|
9
|
+
| System (one machine spanning packages) | [system.md](system.md) | Truth |
|
|
8
10
|
| Architecture Decision (AD) | [architecture-decision.md](architecture-decision.md) | Truth (Decision Changelog tail sanctioned) |
|
|
9
11
|
| System overview | [system-overview.md](system-overview.md) | Truth |
|
|
10
12
|
| Domain model | [domain-model.md](domain-model.md) | Truth |
|
|
@@ -35,4 +37,4 @@ One file per template. Every template is either a **truth document** (states cur
|
|
|
35
37
|
|
|
36
38
|
**Baseline scaffolds** — files that must exist after every baseline; the `maintenance` agent (baseline mode) creates any that are missing: [docs-readme.md](docs-readme.md), [structure.md](structure.md), [changelog-index.md](changelog-index.md), [backlog.md](backlog.md), [memory-main.md](memory-main.md), and [root-readme.md](root-readme.md) (never overwriting an existing root README).
|
|
37
39
|
|
|
38
|
-
**Ownership.**
|
|
40
|
+
**Ownership.** Three genres carry code — **feature**, **package**, **system** — and each is the canonical reference for the code paths in its **`owns:` frontmatter**, the only ownership key. To find what a doc owns, read its `owns:` globs; to find the doc for a path, match the path against the injected ownership index (the union of every owning doc's globs), narrower glob wins. That is what layers them: a package doc claims `packages/{name}/**` and is the tree's **residual** owner, while a system doc claims the narrower subtrees of one machine and a feature doc claims one surface — both win those paths by specificity, and the package doc keeps everything neither claims. Two docs may never claim a path at **equal** specificity. Every other genre here owns no code, including the cross-cutting doctrine in `docs/architecture/common/`. See [`../references/frontmatter.md`](../references/frontmatter.md) for turning code into `owns:` globs.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Package Documentation (docs/architecture/packages/{package}.md)
|
|
2
|
+
|
|
3
|
+
Truth document. Use for: **one workspace package — what it is and how it behaves**. One file per package, named for the package's folder (`packages/cli/` → `docs/architecture/packages/cli.md`). It states the package's own machinery: its manifest and published identity, its build and entry point, its source layout, and every behavior of its tree that no system or feature document claims.
|
|
4
|
+
|
|
5
|
+
**A package document is the residual owner of its own tree.** Its `owns:` is the whole package glob — `["packages/{name}/**"]` — and that glob is the *broadest* claim anything makes on those paths, so it loses to every narrower one. A system document naming `packages/{name}/src/server/runs/**` wins those files by specificity; a feature document naming `packages/{name}/src/surfaces/apps/**` wins those. What is left after every narrower claim is what this document describes, and it describes that exhaustively. **The residual claim is a promise of completeness, not of scope**: a package document with nothing left after the systems and features have claimed their subtrees is still owed — the manifest, the build, the entry, the layout, the test wiring and the deploy shape are always residual.
|
|
6
|
+
|
|
7
|
+
**Two rules bind the glob.** Neither genre may claim a path another document claims at **equal specificity** — equal-specificity overlap is an ownership conflict to resolve before either document ships, not a duplication to live with. And every glob **matches at least one file on disk** at `status: active`; a projected glob is permitted only at `draft`, and is confirmed when the document flips.
|
|
8
|
+
|
|
9
|
+
**How this differs from its neighbours.** A **feature** document covers one capability or one page a person names, and owns that surface's globs. A **system** document covers one machine that spans packages, and owns the subtrees that machine is made of. A **package** document covers one unit of distribution — a manifest, a build, a binary or a bundle — and owns what is left of it. The unit is the package boundary, so if the subject you are writing about does not stop at a `package.json`, it is not this genre. Route it: one screen → feature; one machine crossing two packages → [system](system.md).
|
|
10
|
+
|
|
11
|
+
**Required vs optional.** Required in every package document: the **frontmatter**, the **opening** (title, identity line, what the package is), the **ownership boundary** (what this document does not describe and which document does), at least one **behavior section**, **Files**, and **Related**. Everything else is optional — include a section only when the package has something real to put in it. A package with no test wiring of its own gets no verification section; a package that nothing else in the repo consumes says so in one line rather than growing a Consumers section. A short accurate document beats a full skeleton with empty headings.
|
|
12
|
+
|
|
13
|
+
Package documents run long because they are exhaustive over their residue — roughly 150 to 600 lines is the working range, and the top of that range is a package holding an application, not a document that has lost discipline.
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
---
|
|
17
|
+
owns: ["packages/{name}/**"] # required — the whole package tree, one glob; the residual claim
|
|
18
|
+
keywords: [package-name, binary, build, entry] # required — discovery terms: the npm name, the binary, the surfaces
|
|
19
|
+
status: active # required — active | draft | deprecated
|
|
20
|
+
applies: [docs/architecture/common/api.md] # optional — cross-cutting doctrine this package is BOUND by (obligation, not a bibliography); omit when none
|
|
21
|
+
type: package # required tooling key — genre stamp; preserve on edits, never narrate
|
|
22
|
+
---
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> A package document carries no `module:` — a package is already a bounded unit, and a second label
|
|
26
|
+
> for the same boundary is a thing to keep in sync for nothing. `last-reviewed:` is optional and,
|
|
27
|
+
> like `type:`, is tooling metadata: preserve it, never narrate it.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
# {package-path} — {the one-line role}
|
|
31
|
+
|
|
32
|
+
**Package:** `@scope/name` · **Ships as:** {a public npm package with one binary `x` / a private
|
|
33
|
+
source-first workspace package / a Worker on {domain}} · **Source:** `packages/{name}`
|
|
34
|
+
|
|
35
|
+
{One paragraph: what this package is and the job it does in the product. Name what it holds that
|
|
36
|
+
nothing else does, and what it deliberately does not hold. Written for someone who has not opened
|
|
37
|
+
the folder.}
|
|
38
|
+
|
|
39
|
+
{One paragraph: the ownership boundary. This document is the residual owner of `packages/{name}/**`;
|
|
40
|
+
these subtrees are claimed at higher specificity by these documents and are pointed at, not
|
|
41
|
+
restated. Where the routing is more than a sentence, make it the table below instead.}
|
|
42
|
+
|
|
43
|
+
{Optional, and worth its line whenever the body names paths: "Paths below are relative to
|
|
44
|
+
`packages/{name}/` unless they name another package."}
|
|
45
|
+
|
|
46
|
+
## What is built and what is not
|
|
47
|
+
|
|
48
|
+
{Optional — required in substance whenever some of the tree does not run. State plainly which
|
|
49
|
+
halves exist and which do not, with the source evidence for each claim, and say what a reader who
|
|
50
|
+
runs the unbuilt half will actually observe. A package whose subject as a whole does not run is
|
|
51
|
+
`status: draft` and says so here; a scoped gap in a working package stays `active` and takes an
|
|
52
|
+
UNBUILT marker at the section that describes the gap.}
|
|
53
|
+
|
|
54
|
+
## Ownership — packages/{name}
|
|
55
|
+
|
|
56
|
+
{Optional — use the table form when more than two documents claim subtrees of this package.}
|
|
57
|
+
|
|
58
|
+
**The package glob is the residual claim.** A system or feature document's `owns:` is more specific
|
|
59
|
+
and wins by specificity, so this document describes only what none of them claims, and points to
|
|
60
|
+
each for the rest.
|
|
61
|
+
|
|
62
|
+
| Path | What it is |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `src/index.ts` | {the residue: the entry, the dispatch, the layout} |
|
|
65
|
+
|
|
66
|
+
| Subtree | Stated and owned by |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `src/{machine}/**` | [{system}](../systems/{system}.md) |
|
|
69
|
+
| `src/surfaces/{x}/**` | [{feature}](../../features/{feature}.md) |
|
|
70
|
+
|
|
71
|
+
## {Behavior sections — named for this package's own machinery}
|
|
72
|
+
|
|
73
|
+
{The body. There is no fixed section list, and inventing one would be a lie: what a CLI owes
|
|
74
|
+
(commands, the store, the on-ramp) and what a UI package owes (the host port, the frame, the route
|
|
75
|
+
zones, the component pool) share no headings. Name each section for the thing it describes and
|
|
76
|
+
order them the way a reader meets them — identity and build first, then the surface, then what it
|
|
77
|
+
writes, then how it is verified. Prefer a table wherever the content is a roster (commands, routes,
|
|
78
|
+
exports, bindings, environment variables); prefer prose wherever the content is a rule and its
|
|
79
|
+
reason. State mechanisms and contracts, never pasted implementations.}
|
|
80
|
+
|
|
81
|
+
## Tests
|
|
82
|
+
|
|
83
|
+
{Optional. What proves this package, and what each suite pins — a `Suite | Pins` table, or the
|
|
84
|
+
commands a reader runs. A package whose verification is a build check names the check instead
|
|
85
|
+
(`## Checks`, `## Build and test`); a package with no suite of its own says so in one line in the
|
|
86
|
+
build section and carries no heading here.}
|
|
87
|
+
|
|
88
|
+
## Files
|
|
89
|
+
|
|
90
|
+
| Path | Role |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `packages/{name}/package.json` | {the manifest: the bin, the exports, the scripts} |
|
|
93
|
+
| `packages/{name}/src/index.ts` | {the entry} |
|
|
94
|
+
|
|
95
|
+
{One row or bullet per file this document owns, with what it holds — the roster a reader uses to go
|
|
96
|
+
from the document to the code. Close with the inverse where it helps: "Read here, owned elsewhere:"
|
|
97
|
+
naming the paths the body describes but another document claims.}
|
|
98
|
+
|
|
99
|
+
## Related
|
|
100
|
+
|
|
101
|
+
- [`{system}.md`](../systems/{system}.md) — {why a reader of this document would open that one}
|
|
102
|
+
- [`{other-package}.md`]({other-package}.md) — {the seam between the two}
|
|
103
|
+
- [`{doctrine}.md`](../common/{doctrine}.md) — {the cross-cutting contract this package answers to}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### The identity line
|
|
107
|
+
|
|
108
|
+
The bolded `·`-separated run under the title is what a reader checks first, and it answers three
|
|
109
|
+
questions no body section should have to: **what is this called** on the registry or in the
|
|
110
|
+
workspace, **how does it ship** (a public tarball, a private source-first package, a deployed
|
|
111
|
+
Worker, a signed installer), and **where is its source**. A package that ships two artifacts names
|
|
112
|
+
both. Add a `**Crate:**` or `**Deploys as:**` term where the package has one; drop any term that
|
|
113
|
+
would restate the title.
|
|
114
|
+
|
|
115
|
+
### Marking what is decided and unbuilt
|
|
116
|
+
|
|
117
|
+
Where the package holds code for a design that is decided and not built — or a surface whose other
|
|
118
|
+
half does not exist — wrap the prose in a marker so the gap is machine-findable and its evidence
|
|
119
|
+
travels with it:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
<!-- UNBUILT:START — {what is decided, where it is decided (an AD or an owner ruling), and the
|
|
123
|
+
source evidence that it is not built: the file that does not exist, the route that is not mounted,
|
|
124
|
+
the binding that is not declared} -->
|
|
125
|
+
{the prose describing the unbuilt half}
|
|
126
|
+
<!-- UNBUILT:END -->
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**A design that was decided *against* carries no marker.** A marker means the work is owed and
|
|
130
|
+
absent; using one for a rejected design turns the register of debt into a register of opinions.
|
|
131
|
+
Say in prose that it is not built and not owed, and name the decision that settled it.
|
|
@@ -8,13 +8,13 @@ The `boundary` skill parses the **Proposed Changes** sections to check boundary
|
|
|
8
8
|
|
|
9
9
|
**Doc Obligations is the one ledger the plan's author writes.** Deriving tests from behavior is judgment, which is why `testing` owns that section and it is marked never-hand-author; resolving Proposed Changes' file paths to the feature docs that own them is a **lookup** against the ownership index already injected at session start. Different work, different rule — fill it yourself, while the touch list is fresh.
|
|
10
10
|
|
|
11
|
-
The **`Gates:`** line carries
|
|
11
|
+
The **`Gates:`** line carries two machine-read segments. The compliance hook greps **`Boundary:`** — implementation edits inside the plan's scope are blocked while it reads `Boundary: pending`; the `boundary` skill rewrites it to `Boundary: passed YYYY-MM-DD` or `Boundary: blocked YYYY-MM-DD`. The other two plan gates are enforced by **section presence**: a plan with no `## Test Obligations` or no `## Doc Obligations` section blocks the same edits — the sections are the artifacts, and the gate derives from them, not from a claim about them. **`Polygraph:`** is the completion gate's trace: it reads `Polygraph: pending` from the first draft until the `polygraph` skill (gate mode) certifies the plan's documents — every Doc Obligations row discharged **updated** plus every `Spec:` doc — and rewrites it to `Polygraph: passed YYYY-MM-DD (N docs · N claims · N corrected · N registered)`. A plan that touched no document writes `Polygraph: owes nothing — {reason}` by hand; nothing else on this segment is ever hand-written. A Complete plan still reading `pending` raises a `needs-session:` queue row and fails CI. Never hand-edit the line to skip a gate.
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
# {Feature Name}
|
|
15
15
|
|
|
16
16
|
**Status:** Draft | In Progress | Complete | Abandoned
|
|
17
|
-
**Gates:** Boundary: pending
|
|
17
|
+
**Gates:** Boundary: pending · Polygraph: pending
|
|
18
18
|
**Spec:** [docs/features/{feature}.md](../../features/{feature}.md) <!-- one line per feature doc this plan implements against; a plan spanning three features names three -->
|
|
19
19
|
**Started:** YYYY-MM-DD
|
|
20
20
|
**Completed:** YYYY-MM-DD <!-- fill in the SAME edit that flips Status to Complete — along with every ledger Outcome cell: the flip freezes the file instantly, so anything left for a second edit is unreachable (the frozen-record gate rightly blocks it) -->
|
|
@@ -18,8 +18,10 @@ Each topic has one canonical location. Read from here, not from plans or changel
|
|
|
18
18
|
|
|
19
19
|
| Topic | Authority | Key Files |
|
|
20
20
|
| ----- | --------- | --------- |
|
|
21
|
-
| Code-path ownership (path → doc + module) |
|
|
21
|
+
| Code-path ownership (path → doc + module) | `owns:` frontmatter on features, packages and systems | the generated ownership index |
|
|
22
22
|
| System overview | `docs/architecture/` | system-design.md |
|
|
23
|
+
| One workspace package | `docs/architecture/packages/` | {package-folder}.md |
|
|
24
|
+
| One machine spanning packages | `docs/architecture/systems/` | {machine}.md |
|
|
23
25
|
| Steering (domain model, glossary, non-goals) | `docs/architecture/` | domain-model.md, glossary.md, non-goals.md |
|
|
24
26
|
| Architecture decisions (ADs) | `docs/architecture/decisions/` | AD-NNN-*.md |
|
|
25
27
|
| API surface | `docs/architecture/` | api.md |
|
|
@@ -45,8 +47,12 @@ Every doc is a **truth document** (states current absolutes, topic-named, update
|
|
|
45
47
|
|
|
46
48
|
## Folder Rules
|
|
47
49
|
|
|
48
|
-
- `docs/features/`: Living reference for implemented features. Each feature doc **carries** the `owns:` / `module:` / `keywords:` / `status:` frontmatter — `owns:` declares the code-path globs it owns (the only ownership key), `module:` is an optional bounded-context label, `keywords:` are discovery terms, `status:` is `active`/`draft`/`deprecated`. The SessionStart hook unions every
|
|
49
|
-
- `docs/architecture/`: The truth layer for system-wide shape: overview, domain model, glossary, non-goals, API surface, security, and `decisions/` (ADs — one per subject, updated in place, journey in each AD's Decision Changelog). Update when cross-cutting reality changes.
|
|
50
|
+
- `docs/features/`: Living reference for implemented features. Each feature doc **carries** the `owns:` / `module:` / `keywords:` / `status:` frontmatter — `owns:` declares the code-path globs it owns (the only ownership key), `module:` is an optional bounded-context label, `keywords:` are discovery terms, `status:` is `active`/`draft`/`deprecated`. The SessionStart hook unions every owning doc's `owns:` globs into the injected ownership index.
|
|
51
|
+
- `docs/architecture/`: The truth layer for system-wide shape: overview, domain model, glossary, non-goals, API surface, security, and `decisions/` (ADs — one per subject, updated in place, journey in each AD's Decision Changelog). Update when cross-cutting reality changes. The cross-cutting doctrine directly under `architecture/` (and under `architecture/common/` where a repo separates it) **owns no code** — it describes patterns every surface obeys, and `applies:` is how a surface declares it is bound.
|
|
52
|
+
- `docs/architecture/packages/`: One doc per workspace package (`{package-folder}.md`), present in a workspace repo. Each is the **residual owner** of its tree — `owns: ["packages/{name}/**"]` — describing the manifest, build, entry, layout and every behavior no system or feature doc claims at higher specificity. Template: the `documentation` skill's `templates/package.md`.
|
|
53
|
+
- `docs/architecture/systems/`: One doc per machine that spans packages (`{machine}.md`) — the walk, the egress, the ingest. Its `owns:` names the machine's own subtrees and files, narrower than the package glob, so it wins those paths by specificity and the package doc keeps the rest. Not a surface doc: what a person sees stays in `docs/features/`. Template: the `documentation` skill's `templates/system.md`.
|
|
54
|
+
|
|
55
|
+
**Three genres carry `owns:`** — features, packages, systems — and no others. Their globs layer by specificity (package `**` residual, system and feature narrower), and two docs may never claim a path at **equal** specificity: that is an ownership conflict to resolve, not a duplication to live with. Every glob matches at least one file on disk at `status: active`.
|
|
50
56
|
- `docs/operations/`: How-to procedures for setup, development, and deployment, plus the environment registry. Update when workflows change. One-shot migrations do NOT live here — they are records (`docs/history/migrations/`).
|
|
51
57
|
- `docs/tests/`: Testing strategy and test docs. Present when the repo has a test posture worth documenting.
|
|
52
58
|
- `docs/history/plans/`: Records. Must have `Status:` field. Never modify after completion.
|