@naswerks/engineering-loop 0.4.0 → 0.5.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.
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "naswerks",
12
12
  "description": "The engineering loop: docs-* knowledge loop, spec-* execution loop, and init for a fresh repository.",
13
- "version": "0.4.0",
13
+ "version": "0.5.0",
14
14
  "source": "./"
15
15
  }
16
16
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naswerks",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "The engineering loop: a knowledge loop (docs-*) and an execution loop (spec-*) for repositories driven by hosted agent sessions, plus init to make a repository ready for both.",
5
5
  "author": { "name": "naswerks" },
6
6
  "license": "MIT",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ version is the one in `package.json` and `.claude-plugin/plugin.json`, which mus
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.5.0] - 2026-09-24
9
+
10
+ ### Added
11
+
12
+ - **The menu declares its shape.** `docs/_meta/doc-index.md` carries `<!-- naswerks-loop: menu=all -->` (it
13
+ lists every living doc: the repository has no other index; the default, and what a menu with no such line
14
+ means) or `<!-- naswerks-loop: menu=curated; index={path} -->` (the repository already indexes every doc in
15
+ `{path}`; the menu lists the docs its seats read). `docs-workflow.md` § The repository-owned sections
16
+ explains both, and the loop's standard for a doc on a curated menu: a header at a decided status and a
17
+ `## Key Files` section. `init` writes `menu=curated` when its scan finds an index the repository already
18
+ keeps; `docs-process` registers a new doc where the shape says; `docs-status` and `docs-audit-full` flag a
19
+ menu that has drifted; `init doctor` reads the shape, FAILs a doc the menu names that does not exist,
20
+ flags a living doc an all-menu misses, and holds every doc a curated menu names to the standard.
21
+ `pins/menu.test.mjs` holds both shapes.
22
+
23
+ ### Changed
24
+
25
+ - The map's Doc metadata row no longer asks whether the menu is kept by hand; the menu's shape line says
26
+ what the menu is.
27
+ - `docs-workflow.md` § The repository-owned sections no longer says which words this repository writes for
28
+ the loop's states; that is § Doc metadata's to say, so a repository that pastes the section verbatim no
29
+ longer inherits a line that is untrue for it.
30
+ - README: taking a new release is `claude plugin update`; the pins and the release path read as they are.
31
+
8
32
  ## [0.4.0] - 2026-09-23
9
33
 
10
34
  ### Changed
package/README.md CHANGED
@@ -20,7 +20,8 @@ claude plugin install naswerks@engineering-loop
20
20
 
21
21
  The marketplace is this repository, and the plugin is sourced from it: no registry sits between you and a
22
22
  release. Plugins load at session start, so start a new session (or run `/reload-plugins`) after installing.
23
- To pick up a new release later: `claude plugin marketplace update engineering-loop`, then the install line again.
23
+ To pick up a new release later: `claude plugin marketplace update engineering-loop`, then
24
+ `claude plugin update naswerks@engineering-loop` (an install over an existing install does nothing).
24
25
 
25
26
  Skills are namespaced by the plugin: `/naswerks:docs-write`, `/naswerks:spec-pipeline`, `/naswerks:init`.
26
27
 
@@ -48,6 +49,11 @@ sections lists what each must answer. One grep lists the four:
48
49
  rg -n '^#+ .*\(repository-owned\)' docs/_meta/
49
50
  ```
50
51
 
52
+ If the repository already keeps an index of every doc, declare the loop's menu curated — put
53
+ `<!-- naswerks-loop: menu=curated; index=docs/INDEX.md -->` (your index's path) in `docs/_meta/doc-index.md`
54
+ — and the menu lists only the docs your seats read, each held to the loop's standard; every other doc stays
55
+ in your own index. A repository with no index of its own keeps the default, a menu of every living doc.
56
+
51
57
  Every skill names the section it needs and never states the format itself, so the pack stays generic and
52
58
  your conventions live in your repository. **Taking a newer pack:** `/naswerks:init refresh` writes only
53
59
  those four sections and the version stamp into your copies — a dry run first, then `--write` on your word —
@@ -70,7 +76,7 @@ and never touches the rest of the text.
70
76
  | `spec-retro` | spec | the coordinator's close-out audit |
71
77
  | `spec-witness` | spec | a read-only verifier |
72
78
  | `spec-ignite` | spec | the ignition copilot for a hosted run |
73
- | `init` | — | make a repository ready for both loops |
79
+ | `init` | — | make a repository ready for both loops; `init doctor` checks one, `init refresh` takes a newer pack's repository-owned sections |
74
80
 
75
81
  `references/` carries the two method documents a repository copies into `docs/_meta/` and then
76
82
  rewrites where they say (repository-owned);
@@ -84,19 +90,26 @@ or the child may resolve `docs/` paths against the parent session's workspace.
84
90
 
85
91
  ## Does it work
86
92
 
87
- `npm test` runs the pins: one version across the package, the plugin manifest and the marketplace
88
- entry; every skill directory carries a `SKILL.md` named for its folder; no byte-order marks; the
89
- method documents carry the version stamp; the vocabulary censuses. `node bin/verify-staged.mjs
90
- --self-test` drives the receipt against a throwaway repository and reports each case.
93
+ `npm test` runs the pins: one version across the package, the plugin manifest, the marketplace entry and
94
+ both method documents' stamps; every skill directory carries a `SKILL.md` named for its folder; no
95
+ byte-order marks; the vocabulary and content censuses; no skill states a format a repository owns, and
96
+ every skill that needs one carries the same find-format line; exactly four repository-owned sections, found
97
+ by the one grep; the loop's own preferences stay stated in the method documents; the menu's two shapes; `init refresh` leaves
98
+ every line outside those sections byte for byte. `node bin/verify-staged.mjs --self-test` drives the
99
+ receipt against a throwaway repository and reports each case.
91
100
 
92
101
  ## Releasing
93
102
 
94
- The version lives in `package.json` and `.claude-plugin/plugin.json` (they must agree). A `v*` tag
95
- runs the pins and publishes to npm through trusted publishing; the GitHub release notes are the
96
- CHANGELOG section for that version. The marketplace serves the plugin from this repository at whatever
97
- `main` holds, so a CLI install never waits on npm; npm is the runner lane, and a refused publish leaves
98
- runners one version behind until the trusted publisher on npmjs.com matches this repository and
99
- `release.yml` (the workflow prints that remedy when it happens).
103
+ The version lives in `package.json`, `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`
104
+ (they must agree), in both method documents' stamps, and in a CHANGELOG section. The marketplace serves
105
+ the plugin from this repository at whatever `main` holds, so a CLI install never waits on npm; npm is the
106
+ runner lane.
107
+
108
+ To release: push the release commit to `main`, publish it to npm from the repository root (`npm login`,
109
+ then `npm publish --access public`), then push its `v*` tag. The tag's workflow runs the pins, finds the
110
+ version already on the registry and skips its own publish, and creates the GitHub release from the
111
+ CHANGELOG section. With a trusted publisher linked on npmjs.com for this repository and `release.yml`,
112
+ pushing the tag alone publishes; the workflow prints that remedy when a publish is refused.
100
113
 
101
114
  ## License
102
115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naswerks/engineering-loop",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "The engineering loop as a Claude Code plugin: a knowledge loop (docs-*), an execution loop (spec-*), and init for a fresh repository. The package root is the plugin root.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- <!-- naswerks-loop: version=0.4.0 -->
1
+ <!-- naswerks-loop: version=0.5.0 -->
2
2
  # Docs Workflow
3
3
 
4
4
  > How knowledge flows through `docs/`: the loop, the folder rules, the templates, and the `docs-*` skills.
@@ -84,12 +84,12 @@ that replaces the section rewrites alongside it):
84
84
  | Section | What the skills read from it | Also echoed in |
85
85
  |---------|------------------------------|----------------|
86
86
  | **The archive convention** (this file) | the archive home for each kind of effort (hand-cranked, spec pipeline, follow-on round) and for audit reports · the shape inside it · what a working doc's `## Archive` line says, and whether an umbrella carries an `Archive:` line · the links a move repairs · what is frozen once filed | The Loop (the diagram and its prose) · Folders (`research/`, `working/`, `archive/`) · "Living docs vs session docs" · the skills table (`docs-process`) · Conventions (incident receipts, archived docs) · `engineering-loop.md` "The loop at a glance" |
87
- | **Doc metadata** (this file) | the header — its form and its place · the sweep: one command, one line per doc · the word for each loop state (below) · where the verified date lives, and the rule that moves it · whether `## Lineage` stays · the gates a doc passes, and the folders they exempt · whether `doc-index.md` is kept by hand or generated | the skills table (`docs-status`) · Conventions (the machine layer; dates in the header) · `engineering-loop.md`'s own header line and its `## Lineage` |
87
+ | **Doc metadata** (this file) | the header — its form and its place · the sweep: one command, one line per doc · the word for each loop state (below) · where the verified date lives, and the rule that moves it · whether `## Lineage` stays · the gates a doc passes, and the folders they exempt | the skills table (`docs-status`) · Conventions (the machine layer; dates in the header) · `engineering-loop.md`'s own header line and its `## Lineage` |
88
88
  | **Templates** (this file) | each template's header lines, at the state its writer names | — |
89
89
  | **This repository's rails** (`engineering-loop.md`) | the policy document, and how a seat reaches it · the gates a commit passes · the suite before a PR · the base branch, the PR target, the branch names and how a PR is opened · the staging rule · the repository's own skills a seat meets | the POLICY row of "Where a rule lives" · the Hard rules, which the rails add to and never subtract from |
90
90
 
91
91
  **The loop's states.** The skills speak in four states, and § Doc metadata says which word this repository
92
- writes for each. Here the words are the states themselves.
92
+ writes for each.
93
93
 
94
94
  | State | Written by | Means |
95
95
  |-------|------------|-------|
@@ -100,8 +100,21 @@ writes for each. Here the words are the states themselves.
100
100
 
101
101
  *Stale* is a date, not a state: `docs-status` computes it from the verified date, and no seat writes it.
102
102
 
103
- **Silence.** A section that says nothing about an item leaves the loop's default: the menu is kept by hand
104
- (`docs-process` adds a row for a new feature doc), no gate runs on a commit, the base branch is the remote's
103
+ **The menu.** `doc-index.md` is the menu the seats read, and the whole file is the repository's: `init` writes
104
+ it once and never again. A line near its top says which of two shapes it has, and one grep reads it:
105
+ `rg -n 'naswerks-loop: menu=' docs/_meta/doc-index.md`.
106
+
107
+ | Shape line | The menu lists | A new living doc goes |
108
+ |------------|----------------|-----------------------|
109
+ | `<!-- naswerks-loop: menu=all -->` | every living doc — the repository has no other index | onto the menu |
110
+ | `<!-- naswerks-loop: menu=curated; index={path} -->` | the docs this repository's seats read; `{path}` indexes every doc | into `{path}`, the way that index is kept, and onto the menu only when it meets the loop's standard and a seat should read it |
111
+
112
+ **The loop's standard for a doc on a curated menu:** it exists, it carries the header § Doc metadata
113
+ prescribes at a decided status (not the word for `draft`), and it has a `## Key Files` section. `init doctor`
114
+ checks exactly that, for every doc a curated menu names.
115
+
116
+ **Silence.** A section that says nothing about an item leaves the loop's default: a menu with no shape line
117
+ lists every living doc (`menu=all`), no gate runs on a commit, the base branch is the remote's
105
118
  default (`git symbolic-ref refs/remotes/origin/HEAD`), and a seat with no PR route asks the human once and
106
119
  suggests recording the answer in the rails. Any other unanswered item is a question for the human, never a
107
120
  guess.
@@ -131,6 +144,9 @@ Keep what the skills need to find, change the words to yours:
131
144
  `spec-pipeline` then records the resolved path in the umbrella's blockquote as an `Archive:` line.
132
145
  - **Your rails.** Fill each item of `engineering-loop.md` § This repository's rails from what the
133
146
  repository already wrote down — a policy document, hooks, CI, a pull-request template.
147
+ - **Your menu.** A repository that already indexes its docs declares its menu curated and names that index:
148
+ the menu then lists only the docs its seats read, each meeting the loop's standard, and every other doc
149
+ stays in the repository's own index, kept the way it already is.
134
150
 
135
151
  **The version stamp** on line 1 names the pack version whose repository-owned sections this copy carries.
136
152
  The loop text around them is this repository's copy from whenever it was taken: `init doctor` never grades
@@ -1,4 +1,4 @@
1
- <!-- naswerks-loop: version=0.4.0 -->
1
+ <!-- naswerks-loop: version=0.5.0 -->
2
2
  # The Engineering Loop — flow, seats, skills, and the ceremony
3
3
 
4
4
  > The quick intro to how work gets built with this loop: the two loops (`docs-*` knowledge, `spec-*`
@@ -18,7 +18,10 @@ what to audit next.
18
18
  oldest-first.
19
19
 
20
20
  2. **Feature completeness** — quick `docs/features/` folder pass (per `docs-audit-feature`'s
21
- folder mode): any features in code with no doc, or docs whose feature is gone.
21
+ folder mode): any features in code with no doc, or docs whose feature is gone. Then the menu against its
22
+ shape line (`rg -n 'naswerks-loop: menu=' docs/_meta/doc-index.md`; none means `menu=all`): on
23
+ `menu=all`, living docs it does not name; on `menu=curated`, docs it names that fall short of the loop's
24
+ standard (`docs/_meta/docs-workflow.md` § The repository-owned sections).
22
25
 
23
26
  3. **Convention spot-check** — sample 2-3 source files per major convention against the
24
27
  `docs/patterns/` docs; note drift.
@@ -92,9 +92,13 @@ branch, which is the truth for that PR, and you say so at close.
92
92
  speculation. A sizable design idea goes to `docs-backlog` (research/) instead. `docs-status`
93
93
  surfaces these so they aren't lost.
94
94
 
95
- 3. **New feature?** If the work added a feature, ensure it has a doc in `docs/features/` (one doc per
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.
95
+ 3. **A new living doc?** If the work added a feature, ensure it has a doc in `docs/features/` (one doc per
96
+ feature). Then register every living doc this run created where the menu's shape line says
97
+ (`rg -n 'naswerks-loop: menu=' docs/_meta/doc-index.md`; `docs/_meta/docs-workflow.md` § The
98
+ repository-owned sections explains the two shapes). **`menu=all`, or no shape line:** add its row to the
99
+ menu in `docs/_meta/doc-index.md`. **`menu=curated; index={path}`:** add it to `{path}` the way that
100
+ index is kept — a generated index is never edited by hand — and add a menu row only when the doc meets
101
+ the loop's standard and a seat should read it; say which you did in the summary.
98
102
 
99
103
  4. **Closing grep** — if the session doc notes deletions or renames, grep all living
100
104
  docs (`guides/ patterns/ infrastructure/ features/ _meta/`) for stale references and fix them. Do
@@ -36,6 +36,10 @@ just counts.
36
36
  broken breadcrumb).
37
37
  - **low-lineage feature** — where the section keeps `## Lineage`: a feature doc with no Lineage rows =
38
38
  ungrounded; worth wiring its lineage.
39
+ - **menu drift** — read the menu's shape line (`rg -n 'naswerks-loop: menu=' docs/_meta/doc-index.md`;
40
+ none means `menu=all`). On `menu=all`: a living doc the menu does not name. On `menu=curated`: a doc the
41
+ menu names that falls short of the loop's standard (`docs/_meta/docs-workflow.md` § The
42
+ repository-owned sections — a header at a decided status and a `## Key Files` section).
39
43
 
40
44
  5. **Open issues.** Grep living docs for an `## Open Issues` section; list which docs/features carry
41
45
  unresolved, code-verified problems (and how many) so known risks surface at orientation instead of
@@ -38,7 +38,8 @@ docs/
38
38
  _meta/docs-workflow.md a copy of the pack's references/docs-workflow.md (stamped); its sections marked
39
39
  (repository-owned) are the repository's to rewrite to its own conventions
40
40
  _meta/engineering-loop.md a copy of references/engineering-loop.md (stamped), under the same rule
41
- _meta/doc-index.md the menu, written LAST — every doc that exists + three repo slots:
41
+ _meta/doc-index.md the menu, written LAST — its shape line (menu=all: every doc that exists;
42
+ menu=curated: the docs init drafted, when the repository indexes its own) + three repo slots:
42
43
  ## Role reading lists · ## Apps and their build commands · ## The evidence tools
43
44
  research/ working/ archive/ guides/ patterns/ infrastructure/ features/ each with a README.md
44
45
  patterns/code-organization.md patterns/backend-patterns.md
@@ -104,7 +105,9 @@ what it says` — and cite from it. A draft written mid-scan is a draft written
104
105
  analyzer configs (`.stylecop`, `.globalconfig`, `Directory.Build.props` rules such as
105
106
  `TreatWarningsAsErrors`, `Nullable`, banned-API lists) · `tsconfig*.json` strictness ·
106
107
  `BannedSymbols.txt`, `FLAKY-TESTS.md` · CODEOWNERS · `.husky/`, `.githooks/` · PR templates · the
107
- existing `docs/` tree · `.claude/settings.json`.
108
+ existing `docs/` tree · `.claude/settings.json` · an index the repository already keeps of every doc (a
109
+ `docs/INDEX.md` or `docs/index.md` that lists them, a `docs/SUMMARY.md`, a `mkdocs.yml` nav, a
110
+ docusaurus or vitepress sidebar).
108
111
 
109
112
  ## The write order — create-if-missing at every step
110
113
 
@@ -134,8 +137,12 @@ not. `init` never overwrites, never reorders, never deletes.
134
137
  Evidence per concern: a `generated/` folder or codegen config; a state library; a UI library; a tokens
135
138
  file or token layer; a saga / workflow / state-machine mechanism; a realtime library on either side; a
136
139
  queue, worker or scheduler.
137
- 5. `docs/_meta/doc-index.md` from its template, LAST, listing every doc that now exists under the four
138
- living folders (drafted or repo-owned), the three repo slots filled from the scan.
140
+ 5. `docs/_meta/doc-index.md` from its template, LAST, with its shape line (`docs/_meta/docs-workflow.md`
141
+ § The repository-owned sections, the menu). **`menu=all`** when the repository keeps no other index: the
142
+ menu lists every doc that now exists under the four living folders (drafted or repo-owned). **`menu=curated;
143
+ index={path}`** when pass 4 found an index the repository already keeps of every doc: the menu lists only
144
+ the docs `init` drafted, and the report says the owner adds each further doc that meets the loop's
145
+ standard. Either way, the three repo slots are filled from the scan.
139
146
  6. The Start-here stanza (`templates/claude-md-stanza.md`) appended to `CLAUDE.md` (created if absent)
140
147
  and to `AGENTS.md` (only if present); skipped when the opening marker
141
148
  `<!-- naswerks-loop:start-here -->` is already there.
@@ -187,9 +194,11 @@ FAIL, so a CI job or a control plane can call it.
187
194
  | each repository-owned heading, in its file | — | unmarked (an earlier spelling) or missing — `init refresh` marks or adds it | present |
188
195
  | each item of § This repository's rails (repository-owned) | — | missing, or still its absent-line | recorded |
189
196
  | `_meta/doc-index.md` and its three slot sections | missing | a slot carries only the absent-line | filled |
197
+ | the menu's shape line | an unknown shape | a curated menu names no index, or an index that does not exist | `menu=all` (also a menu with no shape line), or `menu=curated` with its index |
198
+ | every living doc a `menu=all` menu should list | — | a doc under the four living folders the menu does not name | named |
199
+ | every doc a menu names | the path does not exist (a row saying `not detected` is exempt) | on a curated menu: no status in either header form, the word `draft`, or no `## Key Files` | meets the loop's standard |
190
200
  | the four floor docs | missing | the word `draft` (read from either header form), or an `<!-- init: -->` left in | a decided status |
191
201
  | 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 |
192
- | every path a § Role reading lists row names | the row's path does not exist | — | resolves |
193
202
  | `CLAUDE.md` / `AGENTS.md` stanza · `.claude/settings.json` pin | — | missing | present |
194
203
  | `verify-staged` | — | not on PATH (the row carries the fallback) | resolvable |
195
204
  | every `docs/research/*/00-ignition-brief.md` | the five problems the control plane's parser emits (no `## The kick`; the kick has no blockquote; no `## The sequence`; the sequence holds no table; a row names no kind from `coordinator build fixit review retro witness docs-process`), or a spec a row names is missing from disk | — | clean |
@@ -118,13 +118,46 @@ function docRow(relPath, required) {
118
118
  if (/<!-- init:/.test(text)) row('DEGRADE', `docs/${relPath}`, 'an <!-- init: --> instruction was left in the file');
119
119
  }
120
120
 
121
- // ── 4b. every path a Role reading lists row names resolves ─────────────────────────────────────
122
- if (roleRows !== null) {
123
- for (const line of roleRows.split('\n').filter((l) => l.trim().startsWith('|') && !/^\|\s*-/.test(l.trim()) && !/^\|\s*Doc\s*\|/i.test(l.trim()))) {
124
- for (const m of line.matchAll(/`((?:patterns|infrastructure|guides|features)\/[A-Za-z0-9_.-]+\.md)`|\]\((?:\.\.\/)?((?:patterns|infrastructure|guides|features)\/[A-Za-z0-9_.-]+\.md)\)/g)) {
125
- const p = m[1] || m[2];
126
- if (/not detected/i.test(line)) continue;
127
- row(existsSync(join(repo, 'docs', p)) ? 'ok' : 'FAIL', `doc-index row: docs/${p}`, existsSync(join(repo, 'docs', p)) ? '' : 'the row names a path that does not exist');
121
+ // ── 4b. the menu: its shape, and every doc it names ────────────────────────────────────────────
122
+ // doc-index.md declares one of two shapes (docs-workflow.md § The repository-owned sections): menu=all lists
123
+ // every living doc; menu=curated lists the docs the seats read and names the index that lists every doc. A
124
+ // menu with no shape line is menu=all. Every doc a menu names must exist (a row saying `not detected` is
125
+ // exempt); a curated menu's docs must also meet the loop's standard: a status in either header form, not
126
+ // draft, and a `## Key Files` section.
127
+ {
128
+ const menu = read(join(repo, 'docs', '_meta', 'doc-index.md'));
129
+ if (menu !== null) {
130
+ const line = /<!-- naswerks-loop: menu=([A-Za-z-]+)(?:;\s*index=(\S+?))?\s*-->/.exec(menu);
131
+ const shape = line ? line[1] : 'all';
132
+ if (!['all', 'curated'].includes(shape)) row('FAIL', 'menu shape', `menu=${shape} is not a shape the loop knows (all | curated)`);
133
+ else if (shape === 'curated') {
134
+ const index = line[2];
135
+ if (!index) row('DEGRADE', 'menu shape', 'menu=curated names no index of every doc, so docs-process cannot say where a new doc is registered');
136
+ else if (!existsSync(join(repo, index))) row('DEGRADE', 'menu shape', `menu=curated; its index ${index} does not exist`);
137
+ else row('ok', 'menu shape', `menu=curated; index=${index}`);
138
+ } else row('ok', 'menu shape', line ? 'menu=all' : 'menu=all (no shape line: the default)');
139
+
140
+ const named = new Set();
141
+ for (const l of menu.split('\n')) {
142
+ if (/not detected/i.test(l)) continue;
143
+ for (const m of l.matchAll(/(?:\]\(|`)(?:\.\.\/)?((?:guides|patterns|infrastructure|features)\/[A-Za-z0-9_./-]+\.md)/g)) named.add(m[1]);
144
+ }
145
+ const missing = [...named].filter((p) => !existsSync(join(repo, 'docs', p))).sort();
146
+ for (const p of missing) row('FAIL', `menu: docs/${p}`, 'the menu names a doc that does not exist');
147
+ if (shape === 'curated') {
148
+ for (const p of [...named].filter((q) => !missing.includes(q)).sort()) {
149
+ const text = read(join(repo, 'docs', p));
150
+ const short = [];
151
+ const st = statusOf(text);
152
+ if (st === null) short.push('no status in either header form');
153
+ else if (st.value === 'draft') short.push('status draft');
154
+ if (!/^## Key Files\b/m.test(text)) short.push('no ## Key Files');
155
+ row(short.length ? 'DEGRADE' : 'ok', `menu: docs/${p}`, short.length ? `below the loop's standard: ${short.join(', ')}` : "meets the loop's standard");
156
+ }
157
+ } else if (shape === 'all') {
158
+ const unnamed = livingDocs().filter((p) => !named.has(p) && !menu.includes(p));
159
+ for (const p of unnamed) row('DEGRADE', `menu: docs/${p}`, 'a living doc the menu does not name (menu=all) — add its row, or declare the menu curated');
160
+ if (!unnamed.length && !missing.length) row('ok', 'menu entries', `names every living doc (${named.size}), each present`);
128
161
  }
129
162
  }
130
163
  }
@@ -249,6 +282,24 @@ function frontmatterOf(text) {
249
282
  }
250
283
  return out;
251
284
  }
285
+ // The living docs a menu=all menu lists: every .md directly under the four living folders, and each
286
+ // feature folder's own docs one level down (a filed effort's notes sit deeper and are history, not living).
287
+ function livingDocs() {
288
+ const out = [];
289
+ for (const f of ['guides', 'patterns', 'infrastructure', 'features']) {
290
+ const dir = join(repo, 'docs', f);
291
+ if (!existsSync(dir)) continue;
292
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
293
+ if (e.isFile() && e.name.endsWith('.md') && e.name !== 'README.md') out.push(`${f}/${e.name}`);
294
+ else if (f === 'features' && e.isDirectory()) {
295
+ for (const g of readdirSync(join(dir, e.name), { withFileTypes: true })) {
296
+ if (g.isFile() && g.name.endsWith('.md') && g.name !== 'README.md') out.push(`features/${e.name}/${g.name}`);
297
+ }
298
+ }
299
+ }
300
+ }
301
+ return out.sort();
302
+ }
252
303
  function firstBodyLine(text) {
253
304
  let body = text;
254
305
  if (text.startsWith('---\n')) { const end = text.indexOf('\n---', 4); if (end >= 0) body = text.slice(end + 4).replace(/^\n+/, ''); }
@@ -53,7 +53,7 @@ Do this immediately, whatever your assignment turns out to be. It needs no role
53
53
 
54
54
  | Doc | What it gives you |
55
55
  |---|---|
56
- | `docs/_meta/doc-index.md` | the menu — which living doc answers which question |
56
+ | `docs/_meta/doc-index.md` | the menu — which living doc answers which question: every living doc, or on a curated menu the ones meant for seats (its shape line says which) |
57
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
58
  | `docs/_meta/engineering-loop.md` | the seats, the ceremony, **the comms protocol you follow** — and this repository's rails (repository-owned) |
59
59
 
@@ -2,6 +2,12 @@
2
2
 
3
3
  > The menu. Read this at session start, pick the 2-3 docs that match your task, read those for context.
4
4
  > This is the curated short list — the compiled layer you read *before* diving into source.
5
+ <!-- naswerks-loop: menu=all -->
6
+ <!-- init: the shape line above stays in the output. It says menu=all unless the scan found an index the
7
+ repository already keeps of every doc (a docs/INDEX.md or docs/index.md that lists them, a
8
+ docs/SUMMARY.md, a mkdocs.yml nav, a docusaurus or vitepress sidebar); then it says
9
+ menu=curated; index={that path}, and the tables below list only the docs init drafted plus the Meta
10
+ rows. The owner adds each further doc that meets the loop's standard. -->
5
11
 
6
12
  ---
7
13