@naswerks/engineering-loop 0.2.1 → 0.4.0

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.
Files changed (35) hide show
  1. package/.claude-plugin/marketplace.json +2 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +70 -0
  4. package/README.md +29 -4
  5. package/package.json +1 -1
  6. package/references/docs-workflow.md +103 -10
  7. package/references/engineering-loop.md +27 -2
  8. package/skills/docs-audit-feature/SKILL.md +24 -20
  9. package/skills/docs-audit-full/SKILL.md +13 -8
  10. package/skills/docs-backlog/SKILL.md +6 -3
  11. package/skills/docs-process/SKILL.md +62 -65
  12. package/skills/docs-status/SKILL.md +21 -15
  13. package/skills/docs-write/SKILL.md +9 -6
  14. package/skills/init/SKILL.md +74 -17
  15. package/skills/init/scripts/doctor.mjs +73 -11
  16. package/skills/init/scripts/owned.mjs +96 -0
  17. package/skills/init/scripts/refresh.mjs +129 -0
  18. package/skills/spec-child/SKILL.md +1 -1
  19. package/skills/spec-parent/SKILL.md +20 -9
  20. package/skills/spec-pipeline/SKILL.md +23 -13
  21. package/skills/spec-retro/SKILL.md +5 -2
  22. package/skills/spec-review/SKILL.md +10 -6
  23. package/skills/spec-seat/SKILL.md +10 -5
  24. package/templates/docs/_meta/doc-index.md +1 -1
  25. package/templates/docs/infrastructure/background-work.md +1 -1
  26. package/templates/docs/infrastructure/realtime-events.md +1 -1
  27. package/templates/docs/patterns/backend-patterns.md +1 -1
  28. package/templates/docs/patterns/code-organization.md +1 -1
  29. package/templates/docs/patterns/codegen.md +1 -1
  30. package/templates/docs/patterns/design-tokens.md +1 -1
  31. package/templates/docs/patterns/frontend-patterns.md +1 -1
  32. package/templates/docs/patterns/long-running-workflows.md +1 -1
  33. package/templates/docs/patterns/state-management.md +1 -1
  34. package/templates/docs/patterns/testing.md +1 -1
  35. package/templates/docs/patterns/ui-style-guide.md +1 -1
@@ -0,0 +1,96 @@
1
+ // The repository-owned sections: the four spaces in the two method docs where a repository writes its own
2
+ // formats in place of the loop's. One list, read by the doctor, by refresh and by the pins, so the three can
3
+ // never disagree about what a space is. Everything else a space means (what the skills read from it, where
4
+ // the method docs echo it, the rails items) is read from the pack's own references/, never restated here.
5
+
6
+ // The one grep that lists every space, and nothing else, with its line number.
7
+ export const GREP = "rg -n '^#+ .*\\(repository-owned\\)' docs/_meta/";
8
+ export const MARKED = /^#+ .*\(repository-owned\)/;
9
+
10
+ // The line every skill that needs a format carries, verbatim, near its top.
11
+ export const FIND_FORMAT = "**Find the format first.** This repository writes its formats (the doc header and status words, where an effort is filed, its rails) in the sections `rg -n '^#+ .*\\(repository-owned\\)' docs/_meta/` lists; read the one a step names before that step writes. This skill names the section, never the format.";
12
+
13
+ // Each space by its heading, in the order the pack writes them, with the spellings a copy taken before the
14
+ // sections were marked still carries. `earlier` is the only fact here that the references cannot supply.
15
+ export const OWNED = [
16
+ { file: 'docs-workflow.md', heading: '## The archive convention (repository-owned)', title: 'The archive convention', earlier: ['## The archive convention'] },
17
+ { file: 'docs-workflow.md', heading: '## Doc metadata (repository-owned)', title: 'Doc metadata', earlier: ['## Doc metadata (header + lineage)', '## Doc metadata'] },
18
+ { file: 'docs-workflow.md', heading: '## Templates (repository-owned)', title: 'Templates', earlier: ['## Templates'] },
19
+ { file: 'engineering-loop.md', heading: "## This repository's rails (repository-owned)", title: "This repository's rails", earlier: [] },
20
+ ];
21
+
22
+ export const STAMP = /^(<!-- naswerks-loop: version=)([0-9]+\.[0-9]+\.[0-9]+[^ ]*)( -->)/;
23
+
24
+ export function compareVersions(a, b) {
25
+ const pa = a.split(/[.-]/).map((x) => parseInt(x, 10) || 0), pb = b.split(/[.-]/).map((x) => parseInt(x, 10) || 0);
26
+ for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); }
27
+ return 0;
28
+ }
29
+
30
+ // A file as lines that keep their own terminators, so an untouched line is written back byte for byte.
31
+ export function linesOf(text) {
32
+ return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
33
+ }
34
+ export const bare = (line) => line.replace(/\r?\n$/, '').replace(/^/, '');
35
+ export const eolOf = (text) => (text.includes('\r\n') ? '\r\n' : '\n');
36
+
37
+ // The index of every H2 heading outside a fenced block. The Templates space holds fenced examples whose
38
+ // own `## ` lines are template text, not sections.
39
+ export function h2Index(lines) {
40
+ const out = [];
41
+ let fence = null;
42
+ lines.forEach((raw, i) => {
43
+ const line = bare(raw);
44
+ const f = /^\s*(```|~~~)/.exec(line);
45
+ if (f) { fence = fence === null ? f[1] : (fence === f[1] ? null : fence); return; }
46
+ if (fence === null && /^## /.test(line)) out.push({ i, heading: line.trimEnd() });
47
+ });
48
+ return out;
49
+ }
50
+
51
+ // A section by heading: the heading line's index and the index of the next H2 (or the end of the file).
52
+ export function findSection(lines, heading) {
53
+ const hs = h2Index(lines);
54
+ const k = hs.findIndex((h) => h.heading === heading);
55
+ if (k < 0) return null;
56
+ return { start: hs[k].i, end: k + 1 < hs.length ? hs[k + 1].i : lines.length };
57
+ }
58
+
59
+ // The rails items a section carries, by the bold lead-in of each top-level bullet: `- **Policy.** ...`.
60
+ export function railsItems(sectionLines) {
61
+ const items = [];
62
+ for (let i = 0; i < sectionLines.length; i++) {
63
+ const m = /^- \*\*(.+?)\.?\*\*/.exec(bare(sectionLines[i]));
64
+ if (!m) continue;
65
+ let j = i + 1;
66
+ while (j < sectionLines.length && /^\s+\S/.test(bare(sectionLines[j]))) j++;
67
+ items.push({ name: m[1].replace(/\.$/, ''), text: sectionLines.slice(i, j).join(''), first: i, last: j - 1 });
68
+ }
69
+ return items;
70
+ }
71
+
72
+ // The stamp line: line 1, or the first non-blank line after a leading frontmatter block.
73
+ export function stampIndex(lines) {
74
+ let i = 0;
75
+ if (bare(lines[0] ?? '') === '---') {
76
+ i = 1;
77
+ while (i < lines.length && bare(lines[i]) !== '---') i++;
78
+ i++;
79
+ while (i < lines.length && bare(lines[i]).trim() === '') i++;
80
+ }
81
+ return i < lines.length && STAMP.test(bare(lines[i])) ? i : -1;
82
+ }
83
+
84
+ // The map's rows in the pack's docs-workflow.md: what the skills read from each space, and where the method
85
+ // docs echo it. Keyed by the space's title.
86
+ export function mapOf(workflowText) {
87
+ const lines = linesOf(workflowText.replace(/\r\n/g, '\n'));
88
+ const sec = findSection(lines, '## The repository-owned sections');
89
+ const out = {};
90
+ if (!sec) return out;
91
+ for (const raw of lines.slice(sec.start, sec.end)) {
92
+ const m = /^\|\s*\*\*(.+?)\*\*[^|]*\|([^|]*)\|([^|]*)\|/.exec(bare(raw));
93
+ if (m) out[m[1].trim()] = { reads: m[2].trim(), echoed: m[3].trim() };
94
+ }
95
+ return out;
96
+ }
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ // refresh — bring a repository's copies of the two method docs up to this pack version by writing ONLY their
3
+ // repository-owned sections and the stamp. Every other line is left exactly as it is: the loop text in a
4
+ // copy is the repository's, whatever it has done to it. Dry run by default; --write applies the rows it
5
+ // printed. Node >= 22, no dependencies. Usage: node refresh.mjs [repo-root] [--write]
6
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { join, resolve, dirname } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { OWNED, STAMP, compareVersions, linesOf, bare, eolOf, h2Index, findSection, railsItems, stampIndex, mapOf } from './owned.mjs';
10
+
11
+ const args = process.argv.slice(2);
12
+ const write = args.includes('--write');
13
+ const repo = resolve(args.find((a) => !a.startsWith('--')) || process.cwd());
14
+ const packRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
15
+ const packVersion = JSON.parse(readFileSync(join(packRoot, '.claude-plugin', 'plugin.json'), 'utf8')).version;
16
+ const packText = (file) => readFileSync(join(packRoot, 'references', file), 'utf8').replace(/\r\n/g, '\n');
17
+
18
+ const rows = [];
19
+ const row = (file, subject, action) => rows.push({ file, subject, action });
20
+ let changedFiles = 0;
21
+
22
+ console.log(`init refresh — ${repo} — pack ${packVersion}`);
23
+ console.log('writes only the repository-owned sections and the stamp; every other line is left as it is.\n');
24
+
25
+ for (const file of ['docs-workflow.md', 'engineering-loop.md']) {
26
+ const path = join(repo, 'docs', '_meta', file);
27
+ const shown = `docs/_meta/${file}`;
28
+ if (!existsSync(path)) { row(shown, '(file)', 'missing — `init` writes it; refresh only updates a copy that exists'); continue; }
29
+ const original = readFileSync(path, 'utf8');
30
+ const eol = eolOf(original);
31
+ let lines = linesOf(original);
32
+ const sIdx = stampIndex(lines);
33
+ if (sIdx < 0) { row(shown, '(file)', 'no naswerks-loop stamp on its first line — not recognised as a copy of the method doc; nothing written'); continue; }
34
+
35
+ const packLines = linesOf(packText(file));
36
+ const withEol = (text) => text.replace(/\r?\n/g, eol);
37
+ let changed = false;
38
+
39
+ for (const space of OWNED.filter((o) => o.file === file)) {
40
+ const hs = h2Index(lines);
41
+ const present = hs.find((h) => h.heading === space.heading);
42
+ if (present) {
43
+ row(shown, space.heading, 'kept — yours, byte for byte');
44
+ } else {
45
+ const was = hs.find((h) => space.earlier.includes(h.heading));
46
+ if (was) {
47
+ const raw = lines[was.i];
48
+ lines[was.i] = space.heading + raw.slice(bare(raw).length);
49
+ row(shown, space.heading, `heading marked — was "${was.heading}"; only that line changes`);
50
+ changed = true;
51
+ } else {
52
+ // Insert the pack's own section before the first pack heading after it that this copy also has.
53
+ const pack = findSection(packLines, space.heading);
54
+ const packHs = h2Index(packLines);
55
+ const after = packHs.filter((h) => h.i > pack.start).map((h) => h.heading);
56
+ const anchor = after.map((h) => hs.find((c) => c.heading === h)).find(Boolean);
57
+ const chunk = linesOf(withEol(packLines.slice(pack.start, pack.end).join('')));
58
+ if (anchor) {
59
+ lines.splice(anchor.i, 0, ...chunk);
60
+ row(shown, space.heading, `added — the pack's default, before "${anchor.heading}"`);
61
+ } else {
62
+ if (lines.length && !/\n$/.test(lines[lines.length - 1])) lines[lines.length - 1] += eol;
63
+ lines.push(eol, ...chunk);
64
+ row(shown, space.heading, 'added — the pack\'s default, at the end of the file');
65
+ }
66
+ changed = true;
67
+ }
68
+ }
69
+
70
+ // The rails: a bullet this version names that the copy's section lacks is appended as its absent-line.
71
+ if (space.heading.includes('rails')) {
72
+ const packSec = findSection(packLines, space.heading);
73
+ const packItems = railsItems(packLines.slice(packSec.start, packSec.end));
74
+ const sec = findSection(lines, space.heading);
75
+ const have = railsItems(lines.slice(sec.start, sec.end)).map((it) => it.name.toLowerCase());
76
+ const missing = packItems.filter((it) => !have.includes(it.name.toLowerCase()));
77
+ if (missing.length) {
78
+ let at = sec.end;
79
+ while (at > sec.start + 1 && bare(lines[at - 1]).trim() === '') at--;
80
+ if (!/\n$/.test(lines[at - 1])) lines[at - 1] += eol;
81
+ const add = [];
82
+ if (!have.length) add.push(eol);
83
+ for (const it of missing) add.push(...linesOf(withEol(it.text)));
84
+ lines.splice(at, 0, ...add);
85
+ for (const it of missing) row(shown, `rails item: ${it.name}`, 'item added — its absent-line, at the end of the section');
86
+ changed = true;
87
+ }
88
+ }
89
+ }
90
+
91
+ // The stamp names the pack version whose repository-owned sections this copy carries.
92
+ const stampAt = stampIndex(lines);
93
+ const current = STAMP.exec(bare(lines[stampAt]))[2];
94
+ const cmp = compareVersions(current, packVersion);
95
+ if (cmp < 0) {
96
+ const raw = lines[stampAt];
97
+ const bom = raw.startsWith('') ? '' : '';
98
+ lines[stampAt] = bom + raw.slice(bom.length).replace(STAMP, `$1${packVersion}$3`);
99
+ row(shown, 'stamp', `${current} to ${packVersion}`);
100
+ changed = true;
101
+ } else if (cmp > 0) row(shown, 'stamp', `${current} — ahead of this pack; left as it is`);
102
+ else row(shown, 'stamp', `${current} — current`);
103
+
104
+ if (changed) {
105
+ changedFiles++;
106
+ if (write) writeFileSync(path, lines.join(''));
107
+ }
108
+ }
109
+
110
+ const width = Math.max(...rows.map((r) => r.subject.length), 7);
111
+ const fw = Math.max(...rows.map((r) => r.file.length), 4);
112
+ console.log(`| ${'file'.padEnd(fw)} | ${'section'.padEnd(width)} | action |`);
113
+ console.log(`|${'-'.repeat(fw + 2)}|${'-'.repeat(width + 2)}|--------|`);
114
+ for (const r of rows) console.log(`| ${r.file.padEnd(fw)} | ${r.subject.padEnd(width)} | ${r.action} |`);
115
+
116
+ const map = mapOf(packText('docs-workflow.md'));
117
+ if (Object.keys(map).length) {
118
+ console.log('\nyours — what this version\'s skills read from each section (check that yours answers each), and the');
119
+ console.log('loop passages that echo it (rewrite them too if you replaced the section):');
120
+ for (const space of OWNED) {
121
+ const m = map[space.title];
122
+ if (m) console.log(`- ${space.title}: reads ${m.reads}${m.echoed && m.echoed !== '—' ? `; echoed in ${m.echoed}` : ''}`);
123
+ }
124
+ }
125
+
126
+ console.log(`\nloop text — not touched. The pack's own is in ${join(packRoot, 'references').replace(/\\/g, '/')}/ to compare.`);
127
+ if (!changedFiles) console.log('\nnothing to do — every section present, every stamp current.');
128
+ else if (write) console.log(`\nwritten — ${changedFiles} file(s), exactly the rows above.`);
129
+ else console.log('\ndry run — nothing written. Run again with --write to apply exactly the rows above.');
@@ -42,7 +42,7 @@ not a missing-file finding. `patterns/codegen.md` is read by any seat that touch
42
42
  |---|---|
43
43
  | **frontend** | `patterns/state-management.md` · `patterns/ui-style-guide.md` · `patterns/design-tokens.md` · `infrastructure/realtime-events.md` (the poke/refetch concept trips sessions up — read it even for "pure UI" work) · `patterns/codegen.md` when the client consumes generated types |
44
44
  | **backend** | `infrastructure/realtime-events.md` (the poke/refetch concept trips sessions up — read it even for "pure API" work) · `infrastructure/background-work.md` · `patterns/long-running-workflows.md` as the task warrants · `patterns/codegen.md` when the server side emits generated code |
45
- | **mixed** | **NOT every set by default** — read the adds for the surfaces your spec ACTUALLY touches, decided at recon once you have read the spec. **This is the default role and it covers every surface with no role of its own** — a CLI, a sidecar, a worker project; docs work needs nothing beyond the Step 0 floor |
45
+ | **mixed** | **NOT every set by default** — read the adds for the surfaces your spec ACTUALLY touches, decided at recon once you have read the spec. **This is the default role and it covers every surface with no role of its own** — a CLI, a sidecar, a worker project — and its reading list is the rows for those surfaces in `docs/_meta/doc-index.md` § Role reading lists; docs work needs nothing beyond the Step 0 floor |
46
46
 
47
47
  **This repository's own docs reach you as paths too, not as an index to consult:** the coordinator copies
48
48
  your role's rows from `docs/_meta/doc-index.md` § Role reading lists into your prompt's `EXTRA DOCS` line.
@@ -9,6 +9,8 @@ Turn this session into the **coordinator** for a `docs/research/{topic}/` spec p
9
9
  phase 3 of the backlog model (`docs-backlog`, `spec-pipeline`, **`spec-parent`**, seats build,
10
10
  `docs-process`).
11
11
 
12
+ **Find the format first.** This repository writes its formats (the doc header and status words, where an effort is filed, its rails) in the sections `rg -n '^#+ .*\(repository-owned\)' docs/_meta/` lists; read the one a step names before that step writes. This skill names the section, never the format.
13
+
12
14
  **You are one of the three kinds that do NOT compose `spec-seat`** — that file is the contract for the
13
15
  seats you ARM, and it says so itself. This skill is your whole contract: you write rulings rather than
14
16
  receive them, you own the tracker, and you hold the close. The control plane's seed says so out loud
@@ -285,7 +287,7 @@ read the board and answer on first receipt rather than spending a round-trip.
285
287
  `.md` files (`live-checks.md`, the numbered specs, the umbrella) are inside your scoped write path —
286
288
  same gate, same receipt as your ledger. **This also means a line you have PROVEN false in a spec or
287
289
  a live-check row is yours to strike at the source.** Research stage only — after docs-process
288
- archives the folder, only the ledger remains writable.
290
+ files the folder, only the ledger remains writable.
289
291
  Every session's TLDR section (e) row belongs there, plus any status only you observed. Unaggregated
290
292
  rows are the most common thing to fall through, and the human's sitting is what they are for.
291
293
  3. **Run `spec-retro`.** It is written FROM the tracker, which is why step 1 comes first — a retro
@@ -294,8 +296,10 @@ read the board and answer on first receipt rather than spending a round-trip.
294
296
  grades stay independent.** Do not reconcile them or soften yours to match; two accounts of one run
295
297
  are the point, and whoever reads them both does the reconciling.
296
298
  4. **Commit the retro** by explicit path and **write `BUILD COMPLETE`** into `project-status.md`.
297
- 5. **Push the effort's branch. Create the PR if none is open** (`gh pr create`); if one is already open
298
- the push is enough. These are your own git — gated and receipted like any other publish. **The
299
+ 5. **Push the effort's branch. Create the PR if none is open** — where and how
300
+ `docs/_meta/engineering-loop.md` § This repository's rails (repository-owned) says (when it names no
301
+ route, ask the human once and suggest recording the answer there); if one is already open the push is
302
+ enough. These are your own git — gated and receipted like any other publish. **The
299
303
  push command must be the ENTIRE command** (a leading `cd` disqualifies it), and keep git-verb words
300
304
  out of any echo label around it — the deny layer matches TEXT, not parsed verbs, and a decorative
301
305
  `=== remote tip ===` header over a read-only block will park.
@@ -306,9 +310,12 @@ read the board and answer on first receipt rather than spending a round-trip.
306
310
  are **contingent on the merge**, so say so in the close: the docs describe what the PR will land.
307
311
 
308
312
  **P MOVES THE EFFORT HOME OUT OF YOUR REACH — the tracker pass in step 1 is your LAST chance at
309
- the specs and live-checks.** P `git mv`s `docs/research/{topic}/` to `docs/archive/{topic}/`. The
310
- ledger survives the rename (its write predicate admits `docs/{research|archive}/*/project-status.md`),
311
- but the effort-home lane (`live-checks.md`, the specs) is research only — the archive is frozen
313
+ the specs and live-checks.** P `git mv`s `docs/research/{topic}/` to the archive home
314
+ `docs/_meta/docs-workflow.md` § The archive convention (repository-owned) names, or to the umbrella's
315
+ `Archive:` line when it carries one. The ledger survives the
316
+ rename (its write predicate admits `docs/{research|archive}/*/project-status.md`; a home elsewhere
317
+ is outside it, so there P's write to the tracker is the last one), but the effort-home lane
318
+ (`live-checks.md`, the specs) is research only — the archive is frozen
312
319
  testimony. So finish those before P runs, and record P's own outcome in P's TLDR rather than
313
320
  planning to journal it afterwards.
314
321
 
@@ -364,6 +371,9 @@ so read them as run-time obligations, not close-out ones:
364
371
  - Plan TLDR as **plain text BEFORE any plan-approval mode**; questions as **plain numbered text, then
365
372
  STOP** — never an interactive question tool (the human can't copy from it).
366
373
  - **No commit/push without your relayed call.** Explicit staging only — never `git add -A`.
374
+ - **This repository's rails add to these rules.** `docs/_meta/engineering-loop.md` § This repository's rails
375
+ (repository-owned) names the policy, the gates a commit passes, the suite before a PR, the PR route and
376
+ any stricter staging — bake every rail in beside these, and never let one subtract from them.
367
377
  - **The unattended git form, and it binds YOU as much as the seats: one git command per call, no shell
368
378
  metacharacters.** The FORM check reads the whole command string, so chaining, redirects or substitution
369
379
  disqualify it — `git add … && git commit … && git push …` parks, while the same three sent separately
@@ -488,7 +498,8 @@ REVIEW row (R):
488
498
 
489
499
  DOCS-PROCESS row (P):
490
500
  You are the DOCS-PROCESS seat: invoke the docs-process skill before anything else.
491
- Effort: {topic} · archive target: docs/archive/{topic}/
501
+ Effort: {topic} · archive target: {per docs/_meta/docs-workflow.md § The archive convention
502
+ (repository-owned), or the umbrella's Archive: line when it carries one} · shape: {as that section says}
492
503
  Working docs for THIS effort, and ONLY these: {explicit list — docs/working/ is a shared
493
504
  queue and may hold other efforts' docs; do not infer membership from the folder}
494
505
  Research/spec docs to archive: docs/research/{topic}/
@@ -563,8 +574,8 @@ PARENT RESPONSE — FINAL TLDR ACCEPTED {(+ what I verified myself)}.
563
574
  VERIFY before committing: verify-staged {same list} — commit only on exit 0. Non-zero means
564
575
  git add silently dropped files (usually a .gitignore rule); STOP, report the output verbatim,
565
576
  and wait — do not force-add, do not widen .gitignore.
566
- Message: {conventional message naming the spec}
567
- Trailer: {the trailer this repo's commit convention requires, if any — name YOUR model}
577
+ Message: {a message the rails' commit gates accept, naming the spec}
578
+ Trailer: {the trailer the rails' commit convention requires, if any — name YOUR model}
568
579
  Then push. Post-commit status must show only the known coordinator-owned/unrelated files, and
569
580
  the close-out report carries the one-line STAGING VERIFIED receipt — a commit report
570
581
  without it is incomplete (the whole defect class is silence; an unrequired receipt is
@@ -10,6 +10,8 @@ the two-phase backlog model: `docs-backlog` parks an idea; `spec-pipeline` gradu
10
10
  into specs. **The plan is throwaway — the specs carry every crumb.** A future session reads ONE spec + the
11
11
  umbrella and can build without the plan or the original conversation.
12
12
 
13
+ **Find the format first.** This repository writes its formats (the doc header and status words, where an effort is filed, its rails) in the sections `rg -n '^#+ .*\(repository-owned\)' docs/_meta/` lists; read the one a step names before that step writes. This skill names the section, never the format.
14
+
13
15
  ## The two-phase model
14
16
 
15
17
  | Phase | When | Skill | Output |
@@ -111,8 +113,8 @@ sessions/ WHO PUTS IT THERE
111
113
  **"Do you want DOCS-PROCESS as a row?"** In-pipeline gives you a zero-touch close: living docs patched
112
114
  and the effort archived without a second sitting. **State the trade honestly:** `docs-process` is
113
115
  designed as a fresh session over *merged* code, so in-pipeline it patches living docs against code that
114
- is only **about to be** merged, and the `research/{topic}` to `archive/{topic}` rename rides the same
115
- PR. If the PR is not merged, or is partly reverted, those docs describe something absent. **Say in the
116
+ is only **about to be** merged, and the move from `research/{topic}` into the archive home rides the
117
+ same PR. If the PR is not merged, or is partly reverted, those docs describe something absent. **Say in the
116
118
  brief that the living-doc patches are contingent on the merge.** Out-of-pipeline is equally valid and
117
119
  cheap — every artifact is durable, so it runs cold, later, over several efforts at once.
118
120
 
@@ -125,7 +127,7 @@ sessions/ WHO PUTS IT THERE
125
127
 
126
128
  ```
127
129
  02..NN the build seats
128
- {R} REVIEW — spec-review, if chosen. Runs on `git diff main...<branch>`; no PR yet
130
+ {R} REVIEW — spec-review, if chosen. Runs on `git diff {base}...<branch>` (the rails' base branch); no PR yet
129
131
  {F} FIXIT — MAY materialise, one maximum, only if the review classifies an (a)+(b)
130
132
  finding AND the coordinator rules it in. Not authored up front; the
131
133
  coordinator charters it from the review's named list
@@ -146,7 +148,8 @@ sessions/ WHO PUTS IT THERE
146
148
 
147
149
  3. **Write the specs**, using the spec-doc template in `docs/_meta/docs-workflow.md`. Each per-seat
148
150
  spec opens with the spec-doc blockquote (`Builds on [prior]` + `Source: [umbrella / research]`) and the
149
- `<!-- meta: type=spec; status=draft; verified={date}; lineage=N -->` line, so the set stays navigable.
151
+ machine header `docs/_meta/docs-workflow.md` § Doc metadata (repository-owned) prescribes, at the word for
152
+ `draft`, so the set stays navigable.
150
153
 
151
154
  **Six things every slice carries**, each earned by a session that did not have it:
152
155
 
@@ -178,7 +181,7 @@ sessions/ WHO PUTS IT THERE
178
181
  bans, and the builder is then forced to choose between the letter and the point. State the property
179
182
  and the letter can never contradict it.
180
183
  - **A `last verified against` line — the date AND the sha the spec was checked against.** The
181
- `meta: verified` stamp records authoring; this tells the BUILDER what the spec's facts are relative
184
+ header's verified date records authoring; this tells the BUILDER what the spec's facts are relative
182
185
  to. **Recon's first act is to test whether the slice is already satisfied** — specs do get overtaken
183
186
  between authoring and build, and a session that discovers this at recon closes in minutes while one
184
187
  that assumes the work is pending rebuilds what already exists.
@@ -199,7 +202,7 @@ sessions/ WHO PUTS IT THERE
199
202
  which spec moves it, and have the others leave it alone.
200
203
 
201
204
  4. **Name the RESEARCH the effort came from, in the umbrella, by path.** The idea that fed this work
202
- archives *with* it — that is how a topic folder ends up holding the whole arc from "thing we'd like to
205
+ archives *with* it — that is how its archive home ends up holding the whole arc from "thing we'd like to
203
206
  do" to "done." When the research doc already lives at `research/{topic}/`, it travels for free. **When
204
207
  it does not** — a standalone `research/{something}.md`, or several — the umbrella's `Source:` / Lineage
205
208
  is the only record that it belongs to this effort.
@@ -208,7 +211,13 @@ sessions/ WHO PUTS IT THERE
208
211
  list, and a research doc nobody named is one that stays behind in the running tab after its effort has
209
212
  shipped. Two lines in the umbrella now saves a hunt through a shared folder later.
210
213
 
211
- 5. **Stop.** The specs stay in `research/{topic}/`. Do NOT implement, do NOT touch living docs, do NOT
214
+ 5. **Name the archive destination when only you can.** Read `docs/_meta/docs-workflow.md` § The archive
215
+ convention (repository-owned). If where a pipeline files depends on something only this session knows
216
+ (which living feature the effort belongs to, say), resolve it now and write the path into the
217
+ umbrella's blockquote as an `Archive:` line; `spec-parent` hands it to the docs-process row and
218
+ `docs-process` files there. A convention that needs nothing from you needs no line.
219
+
220
+ 6. **Stop.** The specs stay in `research/{topic}/`. Do NOT implement, do NOT touch living docs, do NOT
212
221
  archive — those are later phases. Output the new files only.
213
222
 
214
223
  ## The `00-ignition-brief.md` template
@@ -231,7 +240,7 @@ requires no authoring at all.
231
240
 
232
241
  > You are the COORDINATOR of the {topic} pipeline. Read this, then the umbrella (01-*.md), then the
233
242
  > tracker (project-status.md — yours), then EVERY numbered spec before arming the first seat.
234
- <!-- meta: type=spec; status=draft; verified={date}; lineage=N -->
243
+ {the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for `draft`}
235
244
 
236
245
  **Last verified against:** {date}, `{sha}`. Ignition re-verifies this brief against live HEAD and
237
246
  corrects drift; it does not re-author it.
@@ -320,7 +329,8 @@ stays short because a task buried in a run row cannot be diffed or reviewed the
320
329
  ```
321
330
 
322
331
  **Do not restate the ceremony here.** The coordinator's procedure lives in `spec-parent` and its policy
323
- rides the un-skippable policy suffix the control plane supplies; a brief that re-teaches them accretes
332
+ is the one § This repository's rails (repository-owned) names, which a hosted run also carries in the
333
+ un-skippable policy suffix the control plane supplies; a brief that re-teaches them accretes
324
334
  into prompt-law that breaks the coordinator it was meant to steer. The brief carries THIS RUN's
325
335
  choreography and nothing else.
326
336
 
@@ -338,7 +348,7 @@ viewer because they are instructions to the coordinator, not content for the hum
338
348
  # {Topic} — Project Status
339
349
 
340
350
  > The coordinator's ledger. Advanced at every session boundary, never reconstructed at close.
341
- <!-- meta: type=working; status=wip; verified={date}; lineage=N -->
351
+ {the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for `wip`}
342
352
 
343
353
  ## What this is
344
354
  {one paragraph}
@@ -434,9 +444,9 @@ to edit it directly.
434
444
  folder), the chat transcript, and any subagent reports vanish. Inline their load-bearing content; never
435
445
  point at ephemeral state as the source of truth. Cross-links to *other durable docs* are fine.
436
446
  - **Lifecycle.** Specs live in `research/{topic}/` through the build. When the effort ships, `docs-process`
437
- archives the umbrella + every spec + the working docs together into `archive/{topic}/` — the spec set
438
- *is* the design history.
439
- - **Anti-fabrication.** A spec's `<!-- meta: verified -->` is the date it was *authored against the code it
447
+ archives the umbrella + every spec + the working docs together into the home `docs-workflow.md` § The
448
+ archive convention (repository-owned) names, in its shape — the spec set *is* the design history.
449
+ - **Anti-fabrication.** A spec's header verified date is the date it was *authored against the code it
440
450
  cites*, not a promise the code stays unchanged. Only write facts you actually checked this run.
441
451
  - **Author against the HEAD the run will start from — and know what that does not buy you.** Authoring close
442
452
  to HEAD reduces drift; it does not substitute for a seat reading the code. Specs authored against the
@@ -10,6 +10,8 @@ today that perspective evaporates the moment the run closes. This skill turns it
10
10
 
11
11
  **It is not a summary. It is an audit — of the pipeline, and of the seat that ran it.**
12
12
 
13
+ **Find the format first.** This repository writes its formats (the doc header and status words, where an effort is filed, its rails) in the sections `rg -n '^#+ .*\(repository-owned\)' docs/_meta/` lists; read the one a step names before that step writes. This skill names the section, never the format.
14
+
13
15
  Run it as the **last act of the coordinator seat**, after the final session's close-out commit — before or
14
16
  after the PR is cut, either works. Output: `docs/research/{topic}/sessions/parent-retrospective.md`. It is
15
17
  a scoped write on the pipeline's branch, so it rides the PR to the base branch either way.
@@ -381,8 +383,9 @@ is the look of a record with none of a record's properties.
381
383
  4. **Do not fix anything.** Findings become proposed fixits. The retro is a read.
382
384
  5. **Then** commit the retro (explicit path) and push it (`git push origin <the effort's branch>`), and
383
385
  write `BUILD COMPLETE` into `docs/research/{topic}/project-status.md` (the tracker's close marker). **If no
384
- PR exists yet, create it yourself** — `gh pr create` on the effort's branch; one branch, one PR from any
385
- seat. **If one is already open, the push is enough.** These three git commands are your own close-out
386
+ PR exists yet, create it yourself** — on the effort's branch, where and how `docs/_meta/engineering-loop.md`
387
+ § This repository's rails (repository-owned) says (when it names no route, ask the human once and suggest
388
+ recording the answer there); one branch, one PR from any seat. **If one is already open, the push is enough.** These three git commands are your own close-out
386
389
  lane and they are gated and receipted like any other publish. **The push command must be the ENTIRE
387
390
  command** — a leading `cd` disqualifies it — and keep git-verb words out of any echo label you write
388
391
  around it; the deny layer matches TEXT, not parsed verbs.
@@ -12,6 +12,8 @@ run **before the coordinator's retro and before the PR exists.**
12
12
  each; the coordinator judged them all and formed beliefs doing it. You arrive with none of that, which is
13
13
  why you can see the seams between sessions that no session's own verification structurally covers.
14
14
 
15
+ **Find the format first.** This repository writes its formats (the doc header and status words, where an effort is filed, its rails) in the sections `rg -n '^#+ .*\(repository-owned\)' docs/_meta/` lists; read the one a step names before that step writes. This skill names the section, never the format.
16
+
15
17
  Two documents come out of a pipeline's close and they are deliberately different: **the retro looks
16
18
  inward, and you look in from outside.** They are independent grades, and reconciling them is somebody
17
19
  else's job.
@@ -33,7 +35,9 @@ else's job.
33
35
  Your seed carries **no beliefs about the pipeline, and that is deliberate** — but it should carry an
34
36
  **inventory**, because you cannot review what you cannot find:
35
37
 
36
- - the **topic**, the **branch**, and the **diff range** (`main...<branch>`);
38
+ - the **topic**, the **branch**, and the **diff range** (`{base}...<branch>` — `{base}` is the base branch
39
+ `docs/_meta/engineering-loop.md` § This repository's rails (repository-owned) names, or the remote's
40
+ default when it names none);
37
41
  - **every numbered spec path** — you read them as claims, not facts;
38
42
  - the **`sessions/`** folder, holding each build seat's `plan.md` + `tldr.md`;
39
43
  - the **working docs for this effort, named explicitly** — `docs/working/` is a shared queue and you must
@@ -54,7 +58,7 @@ That is testimony; record it and treat it as a claim.
54
58
  3. **The working docs** in `docs/working/` — the narrative: what was tried, what failed, what changed.
55
59
  4. **The tracker** — `project-status.md`, the coordinator's journal: rulings and their reasoning, recon
56
60
  corrections, incidents.
57
- 5. **THEN the diff** — `git diff main...<pipeline-branch>`, plus `git log --oneline main..<pipeline-branch>`.
61
+ 5. **THEN the diff** — `git diff {base}...<pipeline-branch>`, plus `git log --oneline {base}..<pipeline-branch>`.
58
62
 
59
63
  **Read the artifacts before the code, and the code before you conclude.** Reading the diff first makes
60
64
  you review a patch; reading the claims first makes you review whether the pipeline did what it said.
@@ -144,7 +148,7 @@ finding seen twice.
144
148
  - **A receipt is false** — a TLDR, spec, or doc asserts something proven that the code does not do. This
145
149
  matters as much as a bug, because the next pipeline builds on it.
146
150
 
147
- **(b) Leaving it costs more than a session costs** — it reaches main, propagates to the next pipeline, or a
151
+ **(b) Leaving it costs more than a session costs** — it reaches the base branch, propagates to the next pipeline, or a
148
152
  human discovers it live.
149
153
 
150
154
  Everything else is a finding with a home, and you name the home:
@@ -197,8 +201,8 @@ not a chat transcript, and it archives with the effort.
197
201
  # Code + session review — {topic}, pipeline {id}
198
202
 
199
203
  > The outside look, written before the retro and before the PR. Two independent passes, consolidated.
200
- > Reviewed `main..{sha}` across N sessions. **This seat did not read the coordinator's retrospective.**
201
- <!-- meta: type=session; status=final; verified={date}; lineage=N -->
204
+ > Reviewed `{base}..{sha}` across N sessions. **This seat did not read the coordinator's retrospective.**
205
+ {the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for `shipped`}
202
206
 
203
207
  ## The verdict, first
204
208
  {The one thing a reader must not miss. If the pipeline ships something untrue, say it here in bold.}
@@ -238,7 +242,7 @@ that does not exist yet (an unauthored row, an unwritten charter) is NOT homed:
238
242
  ## What I could not verify
239
243
  {Stated, not omitted.}
240
244
 
241
- ## Lineage
245
+ ## Lineage _(where § Doc metadata (repository-owned) keeps the footer)_
242
246
  - **Pipeline** — the tracker · the umbrella · sessions 02..NN
243
247
  - **Counterpart** — `parent-retrospective.md` (the inside look; written after this)
244
248
  ```
@@ -33,6 +33,8 @@ the review row into the umbrella its own charter excluded and offered the docs-p
33
33
  it must never run. One base class, three wrong instructions. The contract and the job are therefore two
34
34
  files.
35
35
 
36
+ **Find the format first.** This repository writes its formats (the doc header and status words, where an effort is filed, its rails) in the sections `rg -n '^#+ .*\(repository-owned\)' docs/_meta/` lists; read the one a step names before that step writes. This skill names the section, never the format.
37
+
36
38
  ## What you hold (and what you never touch)
37
39
 
38
40
  - **Yours:** your one assignment; the recon; the plan; the work; your artifacts; your own explicit-path
@@ -52,8 +54,8 @@ Do this immediately, whatever your assignment turns out to be. It needs no role
52
54
  | Doc | What it gives you |
53
55
  |---|---|
54
56
  | `docs/_meta/doc-index.md` | the menu — which living doc answers which question |
55
- | `docs/_meta/docs-workflow.md` | the folders, the templates, the two loops |
56
- | `docs/_meta/engineering-loop.md` | the seats, the ceremony, and **the comms protocol you follow** |
57
+ | `docs/_meta/docs-workflow.md` | the folders, the templates, the two loops — and, in its sections marked (repository-owned), this repository's doc header and where an effort is filed |
58
+ | `docs/_meta/engineering-loop.md` | the seats, the ceremony, **the comms protocol you follow** — and this repository's rails (repository-owned) |
57
59
 
58
60
  Then recon the tree: `git rev-parse HEAD`, `git status --porcelain`, `git branch --show-current`.
59
61
 
@@ -79,9 +81,9 @@ prompt's `EXTRA DOCS` line names this repository's own.** Read every one that ex
79
81
  is absent was not detected by `init` — `docs/_meta/doc-index.md` § Role reading lists carries a row
80
82
  saying so — and is not a missing-file finding.
81
83
 
82
- A doc marked `status=draft` in its meta line was scanned by `init`, not decided by the repo's owners —
83
- read it as the starting point it is, and treat a rule it states without a cited source as a question for
84
- your PLAN TLDR, not a law.
84
+ A doc whose header carries the word for `draft` (§ Doc metadata (repository-owned)) was scanned by `init`,
85
+ not decided by the repo's owners — read it as the starting point it is, and treat a rule it states without
86
+ a cited source as a question for your PLAN TLDR, not a law.
85
87
 
86
88
  Then report your orientation and beliefs, and ask ONE question: **your assignment.** Stop.
87
89
 
@@ -347,6 +349,9 @@ doorbell with no follow-through**; if you receive one bare, retry anyway and say
347
349
 
348
350
  ## Git shapes
349
351
 
352
+ - **This repository's rails add to these shapes.** `docs/_meta/engineering-loop.md` § This repository's
353
+ rails (repository-owned) names the gates a commit passes, the suite before a PR, the PR route and any
354
+ stricter staging — follow them beside these, never instead of them.
350
355
  - **Explicit staging only, never `git add -A`.** Additive-only; never amend pushed commits.
351
356
  - **VERIFY EVERY STAGE BEFORE THE COMMIT — `git add` skips gitignored paths under a directory with
352
357
  exit 0 and NO output, and `git status` cannot show the loss** (an ignored file is neither staged nor
@@ -43,7 +43,7 @@
43
43
 
44
44
  | Doc | When to read |
45
45
  |-----|-------------|
46
- | [docs-workflow](docs-workflow.md) | The loop, folder rules, templates (the `<!-- meta -->` header + `## Lineage` footer convention and the spec-doc template), the `docs-*` knowledge-loop + `spec-*` execution-loop skills — read before running `docs-write` / `docs-process` / `spec-pipeline` |
46
+ | [docs-workflow](docs-workflow.md) | The loop, folder rules, templates (the doc header + `## Lineage` footer convention, the sections marked repository-owned, and the spec-doc template), the `docs-*` knowledge-loop + `spec-*` execution-loop skills — read before running `docs-write` / `docs-process` / `spec-pipeline` |
47
47
  | [engineering-loop](engineering-loop.md) | The quick intro to how work gets built: the two loops (`docs-*` knowledge / `spec-*` execution, both hand-crankable), the seats, the ceremony, the blackboard + the question lane, and where a rule lives (policy / procedure / task) |
48
48
 
49
49
  ## Role reading lists
@@ -2,7 +2,7 @@
2
2
 
3
3
  > What runs outside a request in this repository — workers, queues, schedulers — and how long-running,
4
4
  > multi-stage work is orchestrated. Last verified {today}.
5
- <!-- meta: type=infrastructure; status=draft; verified={today}; lineage=0 -->
5
+ <!-- init: write here the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for draft; where that section keeps no date in the blockquote, drop the `Last verified {today}.` above. The loop's own header is the one-line meta comment: type=infrastructure; status=draft; verified={today}; lineage=0 -->
6
6
  <!-- naswerks-loop: draft; scanned={files} -->
7
7
 
8
8
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  > How server-side events reach the client in this repository, and the poke/refetch pattern that keeps
4
4
  > the two consistent. Last verified {today}.
5
- <!-- meta: type=infrastructure; status=draft; verified={today}; lineage=0 -->
5
+ <!-- init: write here the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for draft; where that section keeps no date in the blockquote, drop the `Last verified {today}.` above. The loop's own header is the one-line meta comment: type=infrastructure; status=draft; verified={today}; lineage=0 -->
6
6
  <!-- naswerks-loop: draft; scanned={files} -->
7
7
 
8
8
  ---
@@ -1,7 +1,7 @@
1
1
  # Backend Patterns
2
2
 
3
3
  > Rules and conventions for the server side of this repository. Last verified {today}.
4
- <!-- meta: type=pattern; status=draft; verified={today}; lineage=0 -->
4
+ <!-- init: write here the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for draft; where that section keeps no date in the blockquote, drop the `Last verified {today}.` above. The loop's own header is the one-line meta comment: type=pattern; status=draft; verified={today}; lineage=0 -->
5
5
  <!-- naswerks-loop: draft; scanned={files} -->
6
6
 
7
7
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Where the code lives and where a new piece goes — the folder shape this repository actually uses, per
4
4
  > stack, and the checklist for adding a feature to it. Last verified {today}.
5
- <!-- meta: type=pattern; status=draft; verified={today}; lineage=0 -->
5
+ <!-- init: write here the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for draft; where that section keeps no date in the blockquote, drop the `Last verified {today}.` above. The loop's own header is the one-line meta comment: type=pattern; status=draft; verified={today}; lineage=0 -->
6
6
  <!-- naswerks-loop: draft; scanned={files} -->
7
7
 
8
8
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  > What is generated in this repository, from what, by which command — and the one rule: never hand-write
4
4
  > a generated file. Last verified {today}.
5
- <!-- meta: type=pattern; status=draft; verified={today}; lineage=0 -->
5
+ <!-- init: write here the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for draft; where that section keeps no date in the blockquote, drop the `Last verified {today}.` above. The loop's own header is the one-line meta comment: type=pattern; status=draft; verified={today}; lineage=0 -->
6
6
  <!-- naswerks-loop: draft; scanned={files} -->
7
7
 
8
8
  ---
@@ -3,7 +3,7 @@
3
3
  > The token authority: which colour, spacing and typography tokens exist, what each means, and how
4
4
  > component CSS consumes them. Component usage (which class for which job) lives in
5
5
  > [ui-style-guide](ui-style-guide.md). Last verified {today}.
6
- <!-- meta: type=pattern; status=draft; verified={today}; lineage=0 -->
6
+ <!-- init: write here the machine header docs/_meta/docs-workflow.md § Doc metadata (repository-owned) prescribes, at the word for draft; where that section keeps no date in the blockquote, drop the `Last verified {today}.` above. The loop's own header is the one-line meta comment: type=pattern; status=draft; verified={today}; lineage=0 -->
7
7
  <!-- naswerks-loop: draft; scanned={files} -->
8
8
 
9
9
  ---