@naswerks/engineering-loop 0.2.0 → 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 +78 -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 +13 -6
  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
@@ -5,8 +5,10 @@ description: Patch the living docs from a session doc's "Living Docs to Update"
5
5
 
6
6
  # docs-process
7
7
 
8
- The **second** half of the bookend. Take a finished session doc (in `docs/working/` or its topic
9
- folder under `docs/archive/`) and bring the living docs up to current state.
8
+ The **second** half of the bookend. Take a finished session doc (in `docs/working/`, or already filed
9
+ in its archive home) and bring the living docs up to current state.
10
+
11
+ **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.
10
12
 
11
13
  ## Running as a pipeline row (`docs-process` seat)
12
14
 
@@ -31,8 +33,8 @@ branch, which is the truth for that PR, and you say so at close.
31
33
  1. **Read the session doc(s) for the effort** from `docs/working/` — that's where `docs-write` leaves
32
34
  them (it does not archive). A folder's `README.md` is never a queue entry. One effort may be a **SEQUENCE of working docs** across sessions (each
33
35
  `Builds on` the prior, all sharing one `Source:` research doc); treat the sequence as one unit. The
34
- combined **Living Docs to Update** sections are your checklist; the `## Archive` line names the topic
35
- folder you'll file everything into in the last step. Run this when the effort is done (it's the
36
+ combined **Living Docs to Update** sections are your checklist; the `## Archive` line names the archive
37
+ home you'll file everything into in the last step. Run this when the effort is done (it's the
36
38
  finalizer) — mid-effort, just keep adding working docs and continue.
37
39
 
38
40
  **`docs/working/` is a SHARED QUEUE — it may hold other efforts' docs.** Invoked by hand you scope
@@ -58,28 +60,31 @@ branch, which is the truth for that PR, and you say so at close.
58
60
  - Read the living doc and the session doc's description of what changed.
59
61
  - **Patch the stale sections** — don't rewrite. Preserve everything still accurate.
60
62
  - **Verify against code** — open the actual source file for each claimed change. Code is truth.
61
- - **Leave the "Last verified" date alone.** You verified the *specific* checklist changes against
62
- code — but you did NOT re-read the whole doc against the whole codebase, and "Last verified" is a
63
- *whole-doc* claim. Stamping it for a targeted patch (rename or substantive alike) overclaims and
64
- hides real staleness in the untouched sections from the next `docs-status` scan. That date is
65
- `docs-audit-feature`'s to set (it does the full doc-vs-code pass — see its anti-fabrication rule).
66
- **Exception:** a doc you *create from scratch* here by reading the code is verified-today by
67
- definition — set its date.
63
+ - **Leave the verified date alone.** You verified the *specific* checklist changes against
64
+ code — but you did NOT re-read the whole doc against the whole codebase, and the verified date (wherever
65
+ § Doc metadata (repository-owned) says it lives) is a *whole-doc* claim. Stamping it for a targeted patch
66
+ (rename or substantive alike) overclaims and hides real staleness in the untouched sections from the next
67
+ `docs-status` scan. That date is `docs-audit-feature`'s to set (it does the full doc-vs-code pass — see
68
+ its anti-fabrication rule). **Exception:** a doc you *create from scratch* here by reading the code is
69
+ verified-today by definition — set its date.
68
70
  - **Update the Key Files table** if files were renamed, created, or deleted.
69
- - **Write / update the meta-comment + `## Lineage`** (see "Doc metadata" in `docs-workflow.md`):
70
- - *Creating a doc from scratch* here: emit the `<!-- meta: type=…; status=current; verified={today};
71
- lineage=N -->` line (its `verified` = today, matching the blockquote you just set) **and** a
72
- `## Lineage` footer. You already know the `Source:` research doc (the `Idea` row), any spec pipeline
73
- (the `Spec` row), and the working-doc sequence / `## Archive` target (the `Built` row); derive `Related` from
74
- existing peer links. Set `lineage=N` to the link count. **Keep `Spec` and `Built` symmetric** —
75
- list **every per-slice spec that a `Built` session implemented** (the umbrella + `00-operating-frame`
76
- go in `Idea`, not `Spec`); a `Built` session whose spec is missing from `Spec` is the smell (see the
77
- Lineage rules in `docs-workflow.md`).
78
- - *Patching an existing doc*: bump `status` if it changed and recompute `lineage=N`; append any new
79
- `Built`/`Spec` rows for this effort. **Do NOT restamp `verified`** — same anti-fabrication rule as the
80
- `Last verified` date above (a targeted patch is not a whole-doc re-verify).
81
- - **Migrate `## Related Docs`.** If the doc still carries a `## Related Docs` section, fold its links into
82
- the `## Lineage` `Related` row and remove the old section (the convention supersedes it).
71
+ - **Write / update the header + `## Lineage`** — the header is the one `docs/_meta/docs-workflow.md`
72
+ § Doc metadata (repository-owned) prescribes, in its words, and `## Lineage` is written where that
73
+ section keeps it:
74
+ - *Creating a doc from scratch* here: write the header at the word for `current`, its verified date
75
+ today, **and** a `## Lineage` footer. You already know the `Source:` research doc (the `Idea` row), any
76
+ spec pipeline (the `Spec` row), and the working-doc sequence / `## Archive` target (the `Built` row);
77
+ derive `Related` from existing peer links. Compute any field the header derives from those links the
78
+ way the section says. **Keep `Spec` and `Built` symmetric** — list **every per-slice spec that a
79
+ `Built` session implemented** (the umbrella + `00-operating-frame` go in `Idea`, not `Spec`); a `Built`
80
+ session whose spec is missing from `Spec` is the smell (the `## Lineage` rules § Doc metadata
81
+ (repository-owned) gives).
82
+ - *Patching an existing doc*: update the status if it changed and recompute any field the header
83
+ derives; append any new `Built`/`Spec` rows for this effort. **Do NOT restamp the verified date** — same
84
+ anti-fabrication rule as above (a targeted patch is not a whole-doc re-verify).
85
+ - **Migrate `## Related Docs`.** If the doc still carries a `## Related Docs` section and the section
86
+ keeps `## Lineage`, fold its links into the `## Lineage` `Related` row and remove the old section (the
87
+ convention supersedes it).
83
88
  - **Flag code risks you spot.** If you notice a genuine bug / risk / tech-debt *outside* what you're
84
89
  documenting — while verifying against code, or as a still-unresolved item in the working doc's
85
90
  **Watch-Outs** — add a dated one-line bullet (with `file:line`) to that doc's `## Open Issues`
@@ -88,52 +93,44 @@ branch, which is the truth for that PR, and you say so at close.
88
93
  surfaces these so they aren't lost.
89
94
 
90
95
  3. **New feature?** If the work added a feature, ensure it has a doc in `docs/features/` (one doc per
91
- feature) and add it to the menu in `docs/_meta/doc-index.md`.
96
+ feature) and add it to the menu in `docs/_meta/doc-index.md` — unless `docs-workflow.md` § Doc metadata
97
+ (repository-owned) says a repository tool generates the menu, in which case leave it alone.
92
98
 
93
99
  4. **Closing grep** — if the session doc notes deletions or renames, grep all living
94
100
  docs (`guides/ patterns/ infrastructure/ features/ _meta/`) for stale references and fix them. Do
95
- NOT touch `archive/` — that's frozen history.
96
-
97
- 5. **Archive the effort — research/spec docs AND working doc(s), together.** Now that the living docs are
98
- patched and verified, file the whole effort into one **topic folder** under `docs/archive/{topic}/`
99
- (use the `## Archive` line; reuse the folder if it exists, create it if new; descriptive names, **no
100
- numeric prefixes on working docs** — `Builds on` is the ordering):
101
-
102
- **FIRST, RECONCILE THE DESTINATIONS — `rg '^\s*`?docs/archive/' <every working doc in the effort>`.**
103
- This step says *"one topic folder"* and *"use the `## Archive` line"* — a singular read over a PLURAL
101
+ NOT touch a filed doc — wherever § The archive convention (repository-owned) files them, they are frozen
102
+ history.
103
+
104
+ 5. **File the effort — research/spec docs AND working doc(s), together.** Now that the living docs are
105
+ patched and verified, file the whole effort where `docs/_meta/docs-workflow.md` § The archive convention
106
+ (repository-owned) says, in the shape it gives for this kind of effort (a small effort, a spec pipeline,
107
+ a follow-on round). That section holds the destination, the shape and the links a move repairs; this
108
+ step holds the procedure, which is the same everywhere. A pipeline whose umbrella carries an `Archive:`
109
+ line files there.
110
+
111
+ **FIRST, RECONCILE THE DESTINATIONS — `rg -A1 '^## Archive' <every working doc in the effort>`, plus
112
+ the umbrella's `Archive:` line when there is a pipeline.**
113
+ This step says *"one archive home"* and *"use the `## Archive` line"* — a singular read over a PLURAL
104
114
  source, so the set is checked before any move. **If the docs disagree, STOP and ask.** Do not pick the
105
115
  majority, do not pick the first, and do not pick the one you happen to be reading. The failure is not
106
- recoverable by you: one working doc naming a different, populated effort's folder is enough to sweep
107
- this effort's docs into a neighbour's archive, and `archive/` is frozen history. No seat can see it
116
+ recoverable by you: one working doc naming a different, populated effort's home is enough to sweep
117
+ this effort's docs into a neighbour's, and a filed effort is frozen history. No seat can see it
108
118
  alone — each wrote one working doc and each line is individually plausible; the divergence exists only
109
119
  in the SET, which is why the check belongs here and not upstream.
110
- - Move **every working doc** in the effort from `docs/working/` into the topic folder.
111
- - Move the **`Source:`** research/spec docs from `docs/research/` into the **same** topic folder — the
112
- idea and the work that delivered it are filed together. Do NOT delete and do NOT leave in `research/`
113
- (that's what kept shipped ideas cluttering the running tab).
114
- - **Pick the folder shape** (see "Two topic-folder shapes" in `docs-workflow.md`):
115
- - **Flat** (no spec pipeline — the default for small/single-session efforts): working doc(s) + any
116
- research doc at the topic-folder root.
117
- - **Split** (the effort used `spec-pipeline`, so a numbered spec set exists): `git mv` the numbered
118
- specs + umbrella into **`specs/`**, the working docs into **`implementation/`**, and leave
119
- **`project-status.md` at the topic-folder root** as the index, plus any `live-checks.md`; a
120
- `sessions/` folder (**two files per session — the filed `plan.md` + `tldr.md`**; older archives may
121
- also hold `questions.md` / `status-after-*.md`, which is historical record, not the convention)
122
- moves wholesale alongside them. `git mv` STAGES immediately — in a shared working tree, another
123
- session's commit can silently sweep your staged renames. Commit promptly after the moves, or
124
- announce the staged state before yielding the tree.
125
- - **Follow-on round** (the effort continues an already-archived family — e.g. a round-2 pipeline for
126
- a shipped feature): its own **sibling** topic folder with the full split shape, named
127
- `{family-prefix}-{subtopic}` — **never file into the frozen prior-round folder**. The family
128
- index is the living feature doc's `## Lineage` (append this round's Idea/Spec/Built rows beside
129
- the prior round's); the new round's back-links to the old archive stay as-is (historical
130
- cross-effort links). See the section named `Follow-on pipelines` in `docs-workflow.md`.
131
- - **Repair the intra-effort links the move breaks** (relative depths change). On a split, fix: each
132
- working doc's `Source:` becomes `../specs/NN-*.md`; the spec / `project-status` links; and the **live feature
133
- doc's `## Lineage`** rows (`Spec` to `…/specs/…`, `Built` to `…/implementation/…`). On a flat archive, a
134
- working doc's `Source: ../research/{topic}/x.md` becomes the now-sibling `x.md`. **Leave pre-existing
135
- cross-effort links** (to *other* topic folders / research folders) as historical record. Verify with a
136
- link-resolution pass before finishing.
120
+ - Move **every working doc** in the effort from `docs/working/` into that home, in the section's shape.
121
+ - Move the **`Source:`** research/spec docs from `docs/research/` into the **same** home — the idea and
122
+ the work that delivered it are filed together. Do NOT delete and do NOT leave in `research/` (that's
123
+ what kept shipped ideas cluttering the running tab).
124
+ - **`git mv` STAGES immediately** — in a shared working tree, another session's commit can silently sweep
125
+ your staged renames. Commit promptly after the moves, or announce the staged state before yielding
126
+ the tree.
127
+ - **A `sessions/` folder moves wholesale and byte-untouched** — it is testimony, and no link repair
128
+ reaches into it.
129
+ - **Never file into a frozen effort.** An effort that continues one already filed (a follow-on round)
130
+ goes where the section puts a follow-on — never inside the earlier round's home.
131
+ - **Repair the links the move breaks** — the ones the section names (relative depths change), inside the
132
+ effort and in the live feature doc that points at it. **Leave pre-existing cross-effort links** as
133
+ historical record. Verify with a link-resolution pass before finishing.
137
134
  This clears both the `working/` queue and the research idea. After this the docs are frozen history.
138
135
 
139
136
  6. **Summary** — list which living docs were updated, what changed in each, which source files you read
@@ -141,6 +138,6 @@ branch, which is the truth for that PR, and you say so at close.
141
138
 
142
139
  ## Notes
143
140
  - Skip doc updates for pure bug fixes that restored behavior the doc already described correctly.
144
- - `docs-write` leaves the doc in `working/`; **this skill is what archives it** (step 5) — only after the
141
+ - `docs-write` leaves the doc in `working/`; **this skill is what files it** (step 5) — only after the
145
142
  living docs are current. That keeps `working/` an honest queue of unprocessed sessions.
146
143
  - No releases/changelog step — git history covers that.
@@ -8,6 +8,8 @@ description: Read-only orientation at session start — what's recently archived
8
8
  A quick, read-only pulse of the docs pipeline. No files created. Report by name with context, not
9
9
  just counts.
10
10
 
11
+ **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
+
11
13
  ## Steps
12
14
 
13
15
  1. **`working/` — in flight.** List each doc in `docs/working/` with a one-line "what it's about /
@@ -16,20 +18,24 @@ just counts.
16
18
  2. **`research/` — waiting ideas.** List each doc in `docs/research/` (and topic subfolders) by name
17
19
  with a one-line hook. This is the backlog of things to pick up next.
18
20
 
19
- 3. **`archive/` — recently done.** List the most recently modified topic folders / session docs in
20
- `docs/archive/` (a handful, newest first) so the user can see what just shipped.
21
-
22
- 4. **Meta sweep — the condensed status table.** Run `rg -o '<!-- meta:.*-->' docs/` and parse each
23
- `type · status · verified · lineage` into a compact one-line-per-doc table (group by folder). This is the
24
- headline pulse — richer than the old date-only scan. Read-only; do not write anything. Flag, in order of
25
- concern:
26
- - **missing-meta** — a living doc (`guides/ patterns/ infrastructure/ features/`) with no `<!-- meta:`
27
- comment. Read its blockquote `Last verified` instead (back-compat: read either, **prefer the
28
- comment**) and list it as a gap to fill.
29
- - **stale** — `verified` older than ~14 days, a candidate for `docs-audit-feature` (the existing nudge;
30
- note the harder 90-day line the future `docs_freshness` tool will use).
31
- - **dead lineage** — a `## Lineage` link whose target file no longer exists (a broken breadcrumb).
32
- - **low-lineage feature** — `lineage=0` on a `type=feature` doc = ungrounded; worth wiring its lineage.
21
+ 3. **Recently done.** List the most recently modified filed efforts in the archive home
22
+ `docs/_meta/docs-workflow.md` § The archive convention (repository-owned) names (a handful, newest first)
23
+ so the user can see what just shipped.
24
+
25
+ 4. **Meta sweep — the condensed status table.** Run the sweep command `docs/_meta/docs-workflow.md` § Doc
26
+ metadata (repository-owned) gives — it is spelled there for whichever header this repository writes.
27
+ Parse each doc's status and verified date (and any other field that header carries) into a compact
28
+ one-line-per-doc table (group by folder), in that section's status words. This is the headline pulse —
29
+ richer than the old date-only scan. Read-only; do not write anything. Flag, in order of concern:
30
+ - **missing header** — a living doc (`guides/ patterns/ infrastructure/ features/`) the sweep does not
31
+ list. Read its verified date from wherever else the section says it may live, **prefer the header**,
32
+ and list it as a gap to fill.
33
+ - **stale** — a verified date older than ~14 days, a candidate for `docs-audit-feature` (the existing
34
+ nudge; note the harder 90-day line the future `docs_freshness` tool will use).
35
+ - **dead lineage** — where the section keeps `## Lineage`: a link whose target file no longer exists (a
36
+ broken breadcrumb).
37
+ - **low-lineage feature** — where the section keeps `## Lineage`: a feature doc with no Lineage rows =
38
+ ungrounded; worth wiring its lineage.
33
39
 
34
40
  5. **Open issues.** Grep living docs for an `## Open Issues` section; list which docs/features carry
35
41
  unresolved, code-verified problems (and how many) so known risks surface at orientation instead of
@@ -44,4 +50,4 @@ just counts.
44
50
 
45
51
  ## Notes
46
52
  - Output is a conversational summary only. There is no backlog doc and no release doc to read —
47
- `working/`, `research/`, and `archive/` ARE the status.
53
+ `working/`, `research/`, and the archive home ARE the status.
@@ -11,13 +11,15 @@ does NOT archive. Both happen in `docs-process`, once the living docs are patche
11
11
  code. Leaving the doc in `working/` is what makes `working/` a real queue (`docs-status` reads it) and
12
12
  keeps the doc editable until its living-doc obligations are discharged.
13
13
 
14
+ **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.
15
+
14
16
  ## Steps
15
17
 
16
18
  1. **Review what happened** — prefer conversation memory (you just did the work). Use `git diff --stat` only as a fallback to confirm file paths.
17
19
 
18
20
  2. **Write/finish the working doc.** If a doc for this effort already exists in `docs/working/`, finish it; otherwise create `docs/working/{topic}-{description}.md`. Use the session-doc template in `docs/_meta/docs-workflow.md`. Required sections:
19
21
  - Title + one-line summary; completion date; branch
20
- - **Meta-comment** — directly below the blockquote, emit one line: `<!-- meta: type=working; status={wip|shipped}; verified={today}; lineage=N -->` (see "Doc metadata" in `docs-workflow.md`). `lineage` = the count of provenance links the doc carries (`Source:` + `Builds on` + any `## Lineage` rows).
22
+ - **Header** — the machine header `docs/_meta/docs-workflow.md` § Doc metadata (repository-owned) prescribes, in that section's own words: the doc's type is working, its status the word for `wip` while the effort continues or the word for `shipped` on its last session, its verified date today, and any field that section derives (a count of the doc's provenance links, say) computed the way it says.
21
23
  - **Session-comment** — next line: `<!-- session: claude-session-id={the harness's session id} -->` (Claude Code: `$CLAUDE_CODE_SESSION_ID` / `$env:CLAUDE_CODE_SESSION_ID`). Applies to EVERY session that writes a working doc — solo sessions with no pipeline, no coordinator, and no research doc included; the id alone is the point. Append `; name={...}` only when the session actually has a human-given window name (pipeline seats do; most solo sessions won't — omit it, never invent one). The harness's transcript store is ephemeral (a retention window, and the format moves between releases); this line is the durable link from the work to its conversation, so audit-grade sessions can be found later.
22
24
  - **Builds on** — if this continues prior work, link the prior doc: `Builds on [prior](prior.md)`
23
25
  - **Source** — if idea-driven, link the `research/` doc
@@ -29,15 +31,16 @@ keeps the doc editable until its living-doc obligations are discharged.
29
31
  - **Watch-Outs** — edge cases, gotchas
30
32
  - **Living Docs to Update** — the bridge to `docs-process`. For each stale living doc, name it and say what changed and in what form. Be specific ("references `OldEntity`, should be `NewEntity`"), not vague.
31
33
 
32
- 3. **Record the archive target — don't move the file.** Add an `## Archive` line to the doc naming the intended **topic folder** under `docs/archive/{topic}/` (reuse an existing topic folder; descriptive name, **no numeric prefixes** — the `Builds on` link is the ordering). `docs-process` files it there later. The doc itself **stays in `docs/working/`**.
34
+ 3. **Record the archive target — don't move the file.** Add an `## Archive` line to the doc, in the form the session template gives it (`docs/_meta/docs-workflow.md` § Templates (repository-owned)), naming where § The archive convention (repository-owned) files this effort — reuse what that section says to reuse, and name it the way that section names things. When the effort has a spec pipeline whose umbrella carries an `Archive:` line, that path is the target. `docs-process` files it there later. The doc itself **stays in `docs/working/`**.
33
35
 
34
- 4. **Stop.** The finished doc sits in `docs/working/` as the queue. Do NOT patch living docs, do NOT grep for stale references, do NOT move it to `archive/` — all of that is `docs-process`.
36
+ 4. **Stop.** The finished doc sits in `docs/working/` as the queue. Do NOT patch living docs, do NOT grep for stale references, do NOT file it — all of that is `docs-process`.
35
37
 
36
38
  ## Notes
37
39
  - **Provenance carries into the living docs.** The `Source:` (research) and `Builds on` (prior working) links
38
40
  in the blockquote already *are* lineage edges — at `docs-process` time they populate the living doc's
39
- `## Lineage` (`Idea` / `Built` rows). Optionally also render a `## Lineage` block in the working doc itself
40
- for symmetry, but the blockquote links are the load-bearing record.
41
+ `## Lineage` (`Idea` / `Built` rows), where § Doc metadata (repository-owned) keeps that footer. Optionally
42
+ also render a `## Lineage` block in the working doc itself for symmetry, but the blockquote links are the
43
+ load-bearing record.
41
44
  - **Write for future us, not for review.** Record **what we did vs. what we thought we were going to do**, **what we tried that didn't work**, and **the decisions we made (and why)**. Those three are the load-bearing content — the commit already captures the final file state; the doc captures the reasoning, the reversals, and the dead-ends so the next session doesn't repeat them.
42
- - The doc becomes frozen history only when `docs-process` files it into `docs/archive/` (after the living docs are patched). Until then it's editable in `working/`.
45
+ - The doc becomes frozen history only when `docs-process` files it into its archive home (after the living docs are patched). Until then it's editable in `working/`.
43
46
  - If the work was pure research/exploration with no code change, use `docs-backlog` instead.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: init
3
- description: Make a repository able to run the loop — create the docs/ shape, the two method docs, a scanned first draft of every doc the loop's skills read, the doc-index menu with the repo's own slots, the Start-here stanza and the plugin pin; then say what is still missing. Run init in a repository that has none of this; run init doctor to check one that does. A second init writes nothing and prints the doctor report.
3
+ description: Make a repository able to run the loop — create the docs/ shape, the two method docs, a scanned first draft of every doc the loop's skills read, the doc-index menu with the repo's own slots, the Start-here stanza and the plugin pin; then say what is still missing. Run init in a repository that has none of this; run init doctor to check one that does; run init refresh to bring a repository's copies of the method docs up to a newer pack (it writes only their repository-owned sections and the stamp). A second init writes nothing and prints the doctor report.
4
4
  ---
5
5
 
6
6
  # init
@@ -11,8 +11,11 @@ control plane admits a spec folder only when the repository carries `docs/_meta/
11
11
  `init` creates all of that in a repository that has none of it — as REAL first drafts scanned off the
12
12
  repo, never as empty headings — and `init doctor` says, per path, whether the loop can run.
13
13
 
14
- **Two invocations:** `init` (create what is missing, then the report) · `init doctor` (the report only).
15
- A second `init` writes nothing and IS the doctor report.
14
+ **Three invocations:** `init` (create what is missing, then the report) · `init doctor` (the report only) ·
15
+ `init refresh` (a newer pack's repository-owned sections into the copies that exist — see § refresh). A
16
+ second `init` writes nothing and IS the doctor report.
17
+
18
+ **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
19
 
17
20
  **The law of the draft:** a section is filled only from files the scan opened, and every drafted doc
18
21
  names them in its provenance line. Stack, trees, commands, config keys and Key Files are copied verbatim.
@@ -32,8 +35,9 @@ block read, never the exit code alone; a pin must be able to fail.
32
35
 
33
36
  ```
34
37
  docs/
35
- _meta/docs-workflow.md a byte copy of the pack's references/docs-workflow.md (stamped)
36
- _meta/engineering-loop.md a byte copy of references/engineering-loop.md (stamped)
38
+ _meta/docs-workflow.md a copy of the pack's references/docs-workflow.md (stamped); its sections marked
39
+ (repository-owned) are the repository's to rewrite to its own conventions
40
+ _meta/engineering-loop.md a copy of references/engineering-loop.md (stamped), under the same rule
37
41
  _meta/doc-index.md the menu, written LAST — every doc that exists + three repo slots:
38
42
  ## Role reading lists · ## Apps and their build commands · ## The evidence tools
39
43
  research/ working/ archive/ guides/ patterns/ infrastructure/ features/ each with a README.md
@@ -90,7 +94,8 @@ what it says` — and cite from it. A draft written mid-scan is a draft written
90
94
  `.gitlab-ci.yml`) — the jobs are the lanes · app roots (`angular.json` projects, `apps/*`,
91
95
  `packages/*`, `web/*` and `services/*` under `src/`, `cmd/*`, an executable `*.csproj`) · `generated/` folders
92
96
  and codegen configs (`codegen.ts`, `codegen.yml`, `openapi*`, `*.proto`, `tgconfig.json`, `prisma/`) ·
93
- the folder layout two deep · `git log --oneline -20` (the message convention) · a realtime library
97
+ the folder layout two deep · `git log --oneline -20` (the message convention) · the remote's default
98
+ branch (`git symbolic-ref refs/remotes/origin/HEAD`) · a realtime library
94
99
  (signalr, socket.io, ws, sse, pusher, ably) · a state library (ngxs, ngrx, redux, zustand, pinia,
95
100
  mobx) · a UI library or tokens file (tailwind, daisyui, mui, chakra, bootstrap, shadcn) · a queue,
96
101
  worker or scheduler (a service bus, RabbitMQ, SQS, BullMQ, Hangfire, Quartz, Celery, sidekiq).
@@ -108,8 +113,20 @@ not. `init` never overwrites, never reorders, never deletes.
108
113
 
109
114
  1. The seven folders, each with its `README.md` from `templates/docs/readme-{folder}.md` — the README is
110
115
  the only file init writes into a folder that already exists.
111
- 2. `docs/_meta/docs-workflow.md` and `docs/_meta/engineering-loop.md` — byte copies of the pack's
112
- `references/*` (their first line is the version stamp; copy it with the file).
116
+ 2. `docs/_meta/docs-workflow.md` and `docs/_meta/engineering-loop.md` — copies of the pack's
117
+ `references/*` (their first line is the version stamp; copy it with the file). **If the repository
118
+ already carries a copy, read its repository-owned sections before writing any draft:** the header every
119
+ draft below carries is the one that copy's § Doc metadata (repository-owned) prescribes, not the
120
+ templates' own line. An existing copy is never rewritten by `init`; `init refresh` is the one path that
121
+ writes into a copy, and only into its repository-owned sections. When the repository's own gate requires
122
+ frontmatter on every file under `docs/`, the block may sit above the stamp; the doctor reads the stamp
123
+ after it.
124
+
125
+ **When `init` writes the copies itself, it drafts § This repository's rails (repository-owned)** in the
126
+ new `engineering-loop.md` from the scan, under the law of the draft: each item carries only what a file
127
+ the scan opened states — CONTRIBUTING, `CLAUDE.md` / `AGENTS.md`, `.husky/` / `.githooks/`, the CI
128
+ workflows, a pull-request template, CODEOWNERS, the remote's default branch — and cites it, or keeps
129
+ its absent-line.
113
130
  3. The four floor drafts (`code-organization`, `backend-patterns`, `frontend-patterns`, `testing`) from
114
131
  `templates/docs/patterns/*.md`, filled as below.
115
132
  4. The standard-set drafts — `codegen`, `state-management`, `ui-style-guide`, `design-tokens`,
@@ -139,14 +156,21 @@ the scan opened for that doc.
139
156
 
140
157
  The shape every drafted doc keeps — because it is the shape the seats know how to read:
141
158
 
142
- 1. the H1, the one-line blockquote ending `Last verified {today}.`, the `<!-- meta: … status=draft … -->`
143
- line, the `<!-- naswerks-loop: draft; scanned=… -->` line;
159
+ 1. the H1, the one-line blockquote, the machine header `docs/_meta/docs-workflow.md` § Doc metadata
160
+ (repository-owned) prescribes at the word for `draft` — placed and spelled as that section says, the
161
+ verified date included wherever it keeps one (the templates carry the loop's own header, and each
162
+ template's init instruction says what to drop when this repository's differs) — then the
163
+ `<!-- naswerks-loop: draft; scanned=… -->` line;
144
164
  2. Overview, then Stack, then the shape (a tree copied from a REAL folder), then one section per
145
165
  concept in the micro-grammar *prose rule, a REAL example, `### Rules`, `### Gotcha:`*;
146
166
  3. decision tables that decide, checklists whose items are real paths, a `Legacy (never use)` column
147
167
  where the repo has one;
148
168
  4. exact commands in fenced blocks; identifiers in backticks;
149
- 5. `## Key Files`, `## Lineage`.
169
+ 5. `## Key Files`, and `## Lineage` where § Doc metadata (repository-owned) keeps it.
170
+
171
+ **The templates speak the loop's own formats** — the header, the status words in the Start-here stanza and
172
+ the folder READMEs, the archive home in the `archive/` and `working/` READMEs and in the doc-index. When
173
+ this repository's copy has replaced the section a line echoes, write that line in the copy's words instead.
150
174
 
151
175
  A section the scan can only half-fill says which half. A rule with no cited source is not written.
152
176
 
@@ -159,10 +183,12 @@ FAIL, so a CI job or a control plane can call it.
159
183
  | Row | FAIL | DEGRADE | ok |
160
184
  |---|---|---|---|
161
185
  | each of the eight folders | missing | README missing | present |
162
- | `_meta/docs-workflow.md`, `_meta/engineering-loop.md` | missing, or no stamp on line 1 | stamp behind the pack's version | stamp equals it |
186
+ | `_meta/docs-workflow.md`, `_meta/engineering-loop.md` | missing, or no stamp on line 1 (or on the first line after a frontmatter block) | stamp behind the pack's version (`init refresh` restamps) | stamp equals it |
187
+ | each repository-owned heading, in its file | — | unmarked (an earlier spelling) or missing — `init refresh` marks or adds it | present |
188
+ | each item of § This repository's rails (repository-owned) | — | missing, or still its absent-line | recorded |
163
189
  | `_meta/doc-index.md` and its three slot sections | missing | a slot carries only the absent-line | filled |
164
- | the four floor docs | missing | `status=draft`, or an `<!-- init: -->` left in | `status=current` |
165
- | the seven standard docs | absent AND no doc-index row saying `not detected` | `status=draft` when present; absent with its row | present and current, or absent with its row |
190
+ | the four floor docs | missing | the word `draft` (read from either header form), or an `<!-- init: -->` left in | a decided status |
191
+ | the seven standard docs | absent AND no doc-index row saying `not detected` | the word `draft` when present; absent with its row | present and decided, or absent with its row |
166
192
  | every path a § Role reading lists row names | the row's path does not exist | — | resolves |
167
193
  | `CLAUDE.md` / `AGENTS.md` stanza · `.claude/settings.json` pin | — | missing | present |
168
194
  | `verify-staged` | — | not on PATH (the row carries the fallback) | resolvable |
@@ -171,11 +197,42 @@ FAIL, so a CI job or a control plane can call it.
171
197
  | anything else under `docs/` | — | — | "outside the loop's shape — not touched" |
172
198
 
173
199
  **If `node` is absent**, walk the same rows by hand with your file tools, in that order, and print the
174
- same table: the checks are file existence, a first-line stamp, a heading grep, a `status=` read, and the
175
- brief's two sections.
200
+ same table: the checks are file existence, a first-line stamp, a heading grep, a `status` read in either
201
+ header form, and the brief's two sections.
176
202
 
177
203
  A repository fresh from `init` reads DEGRADE on every draft and ok on the shape. That is the intended
178
- state: the owner promotes each draft to `status=current` as they confirm it, section by section.
204
+ state: the owner promotes each draft to the word for `current` as they confirm it, section by section.
205
+
206
+ The doctor never grades the loop text of the two method docs, and never their repository-owned sections'
207
+ content: a repository rewrites both as it sees fit. It reads the stamp, the owned headings, and the rails
208
+ items' absent-lines.
209
+
210
+ ## refresh
211
+
212
+ `node ${CLAUDE_SKILL_DIR}/scripts/refresh.mjs [repo-root]` is a dry run; add `--write` to apply it. Node 22
213
+ or later, no dependencies. **What it does, in the line it prints first: it writes only the repository-owned
214
+ sections and the stamp; every other line is left as it is.** A repository updating the pack is taking new
215
+ skills, not new prose — the loop text in its copies is its own, whatever it has done to it, and refresh
216
+ never touches it.
217
+
218
+ It reads the pack's `references/` for what each repository-owned section is, and prints one row per section:
219
+
220
+ | Row | When | What `--write` does |
221
+ |---|---|---|
222
+ | **kept** | the copy has the section under its exact heading | nothing — the section is the repository's, byte for byte |
223
+ | **heading marked** | the copy has it under an earlier spelling (`## The archive convention`, `## Doc metadata (header + lineage)`, `## Templates`) | rewrites that one heading line and nothing else |
224
+ | **added** | the copy lacks the section | inserts the pack's default before the first following section the copy also has |
225
+ | **item added** | § This repository's rails (repository-owned) lacks an item this pack names | appends that item's absent-line to the end of the section |
226
+ | **stamp** | the stamp is behind the pack | restamps (after any frontmatter the stamp sits under) |
227
+
228
+ Then, for each section, what this pack's skills read from it and which loop passages echo it (from the
229
+ pack's § The repository-owned sections), so the owner can check that a rewritten section still answers each
230
+ item. It never deletes, never reorders, keeps every line's own ending, and a second run writes nothing.
231
+
232
+ **Run it this way:** the dry run first; show the human its table; `--write` only on their word. After the
233
+ write, offer — and ask before doing either — to fill each new absent-line from the scan as `init` would
234
+ (the law of the draft), and to move into its new item any fact the repository had parked in its loop text
235
+ (a paragraph naming its own skills, say). Then run `init doctor`.
179
236
 
180
237
  ## Notes
181
238
 
@@ -5,6 +5,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
5
5
  import { join, resolve, dirname } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { execFileSync } from 'node:child_process';
8
+ import { OWNED, compareVersions, linesOf, bare, h2Index, findSection, railsItems } from './owned.mjs';
8
9
 
9
10
  const repo = resolve(process.argv[2] || process.cwd());
10
11
  const packRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
@@ -27,17 +28,54 @@ for (const f of FOLDERS) {
27
28
  }
28
29
 
29
30
  // ── 2. the two method docs and their stamp ──────────────────────────────────────────────────────
31
+ // A repository rewrites the repository-owned sections of these two files to its own conventions, and the
32
+ // loop text around them is its copy from whenever it was taken — the doctor grades neither. It reads the
33
+ // version stamp (line 1, or the first line after a leading frontmatter block when the repository's own gate
34
+ // requires one on every file under docs/) and whether each repository-owned heading is where a skill looks.
30
35
  const STAMP = /^<!-- naswerks-loop: version=([0-9]+\.[0-9]+\.[0-9]+[^ ]*) -->/m;
31
36
  for (const name of ['docs-workflow.md', 'engineering-loop.md']) {
32
37
  const p = join(repo, 'docs', '_meta', name);
33
38
  const text = read(p);
34
39
  if (text === null) { row('FAIL', `docs/_meta/${name}`, 'missing'); continue; }
35
- const m = STAMP.exec(text.split('\n', 1)[0]);
36
- if (!m) { row('FAIL', `docs/_meta/${name}`, 'no naswerks-loop stamp on line 1 — an unstamped copy cannot be told from a stale one'); continue; }
40
+ const m = STAMP.exec(firstBodyLine(text));
41
+ if (!m) { row('FAIL', `docs/_meta/${name}`, 'no naswerks-loop stamp on line 1 (or on the first line after a frontmatter block) — an unstamped copy cannot be told from a stale one'); continue; }
37
42
  if (!packVersion) { row('DEGRADE', `docs/_meta/${name}`, `stamped ${m[1]}; the pack's own version could not be read`); continue; }
38
43
  const cmp = compareVersions(m[1], packVersion);
39
- if (cmp < 0) row('DEGRADE', `docs/_meta/${name}`, `stamp ${m[1]} is behind the pack ${packVersion} — diff against the pack's references/${name}`);
44
+ if (cmp < 0) row('DEGRADE', `docs/_meta/${name}`, `stamp ${m[1]} is behind the pack ${packVersion} — run init refresh: it adds this version's repository-owned sections and restamps, and never touches the loop text`);
40
45
  else row('ok', `docs/_meta/${name}`, `stamp ${m[1]}`);
46
+
47
+ // ── 2b. the repository-owned sections: present under the heading a skill looks them up by ─────────
48
+ const lines = linesOf(text);
49
+ const headings = h2Index(lines).map((h) => h.heading);
50
+ for (const space of OWNED.filter((o) => o.file === name)) {
51
+ if (headings.includes(space.heading)) row('ok', `docs/_meta/${name} ${space.heading}`, 'present');
52
+ else {
53
+ const was = space.earlier.find((h) => headings.includes(h));
54
+ row('DEGRADE', `docs/_meta/${name} ${space.heading}`, was
55
+ ? `unmarked (the copy says "${was}") — a skill finds this section by its heading; init refresh marks it`
56
+ : 'missing — a skill finds this section by its heading; init refresh adds it');
57
+ }
58
+ }
59
+
60
+ // ── 2c. the rails: every item the pack names is present, and none still carries its absent-line ───
61
+ const rails = OWNED.find((o) => o.file === name && o.heading.includes('rails'));
62
+ const sec = rails ? findSection(lines, rails.heading) : null;
63
+ if (sec) {
64
+ const section = lines.slice(sec.start, sec.end);
65
+ const items = railsItems(section);
66
+ const packItems = (() => {
67
+ const t = read(join(packRoot, 'references', name));
68
+ if (t === null) return [];
69
+ const pl = linesOf(t);
70
+ const ps = findSection(pl, rails.heading);
71
+ return ps ? railsItems(pl.slice(ps.start, ps.end)).map((it) => it.name) : [];
72
+ })();
73
+ for (const n of packItems.filter((n) => !items.some((it) => it.name.toLowerCase() === n.toLowerCase()))) {
74
+ row('DEGRADE', `rails: ${n}`, 'missing — init refresh adds it as its absent-line');
75
+ }
76
+ for (const it of items) row(/fill this in/i.test(it.text) ? 'DEGRADE' : 'ok', `rails: ${it.name}`, /fill this in/i.test(it.text) ? 'still the absent-line — fill it in (init drafts it from the scan)' : 'recorded');
77
+ if (!items.length && /fill this in/i.test(section.map(bare).join('\n'))) row('DEGRADE', 'rails', 'carries only the absent-line');
78
+ }
41
79
  }
42
80
 
43
81
  // ── 3. doc-index and its three repo slots ───────────────────────────────────────────────────────
@@ -56,6 +94,9 @@ for (const name of ['docs-workflow.md', 'engineering-loop.md']) {
56
94
  }
57
95
 
58
96
  // ── 4. the reading floor and the conditional docs ───────────────────────────────────────────────
97
+ // A doc's status is read from whichever header the repository writes: the loop's own <!-- meta --> line,
98
+ // or a YAML frontmatter block with a status key. The one word the doctor needs is `draft`, which both forms
99
+ // spell the same way.
59
100
  const FLOOR = ['patterns/code-organization.md', 'patterns/backend-patterns.md', 'patterns/frontend-patterns.md', 'patterns/testing.md'];
60
101
  const STANDARD = ['patterns/codegen.md', 'patterns/state-management.md', 'patterns/ui-style-guide.md', 'patterns/design-tokens.md', 'patterns/long-running-workflows.md', 'infrastructure/realtime-events.md', 'infrastructure/background-work.md'];
61
102
  const roleRows = (() => { const t = read(join(repo, 'docs', '_meta', 'doc-index.md')); return t === null ? null : sectionOf(t, '## Role reading lists'); })();
@@ -70,10 +111,10 @@ function docRow(relPath, required) {
70
111
  else row('FAIL', `docs/${relPath}`, 'absent with no doc-index row saying it was not detected — a seat meeting this path has no answer');
71
112
  return;
72
113
  }
73
- const meta = /<!-- meta:[^>]*status=([a-z]+)/.exec(text);
74
- if (!meta) row('DEGRADE', `docs/${relPath}`, 'no <!-- meta --> line');
75
- else if (meta[1] === 'draft') row('DEGRADE', `docs/${relPath}`, 'status=draft — scanned, not decided');
76
- else row('ok', `docs/${relPath}`, `status=${meta[1]}`);
114
+ const status = statusOf(text);
115
+ if (status === null) row('DEGRADE', `docs/${relPath}`, 'no status in either header form (<!-- meta --> line or frontmatter status key)');
116
+ else if (status.value === 'draft') row('DEGRADE', `docs/${relPath}`, `status=draft (${status.layer}) — scanned, not decided`);
117
+ else row('ok', `docs/${relPath}`, `status=${status.value} (${status.layer})`);
77
118
  if (/<!-- init:/.test(text)) row('DEGRADE', `docs/${relPath}`, 'an <!-- init: --> instruction was left in the file');
78
119
  }
79
120
 
@@ -187,8 +228,29 @@ function sectionOf(text, heading) {
187
228
  for (let i = start + 1; i < lines.length; i++) { if (lines[i].startsWith('## ')) break; body.push(lines[i]); }
188
229
  return body.join('\n');
189
230
  }
190
- function compareVersions(a, b) {
191
- const pa = a.split(/[.-]/).map((x) => parseInt(x, 10) || 0), pb = b.split(/[.-]/).map((x) => parseInt(x, 10) || 0);
192
- for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); }
193
- return 0;
231
+ // A doc's status from whichever header it carries: the <!-- meta --> line first, then a frontmatter
232
+ // `status:` key. Returns { value, layer } or null.
233
+ function statusOf(text) {
234
+ const m = /<!-- meta:[^>]*\bstatus=([A-Za-z-]+)/.exec(text);
235
+ if (m) return { value: m[1], layer: 'meta line' };
236
+ const fm = frontmatterOf(text);
237
+ if (fm && fm.status !== undefined) return { value: fm.status, layer: 'frontmatter' };
238
+ return null;
239
+ }
240
+ // The leading YAML frontmatter block as flat key: value pairs (scalars only — that is all the doctor reads).
241
+ function frontmatterOf(text) {
242
+ if (!text.startsWith('---\n')) return null;
243
+ const end = text.indexOf('\n---', 4);
244
+ if (end < 0) return null;
245
+ const out = {};
246
+ for (const line of text.slice(4, end).split('\n')) {
247
+ const m = /^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/.exec(line);
248
+ if (m) out[m[1]] = m[2].trim().replace(/^["']|["']$/g, '');
249
+ }
250
+ return out;
251
+ }
252
+ function firstBodyLine(text) {
253
+ let body = text;
254
+ if (text.startsWith('---\n')) { const end = text.indexOf('\n---', 4); if (end >= 0) body = text.slice(end + 4).replace(/^\n+/, ''); }
255
+ return body.split('\n', 1)[0];
194
256
  }