@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.
- package/.claude-plugin/marketplace.json +2 -6
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +70 -0
- package/README.md +29 -4
- package/package.json +1 -1
- package/references/docs-workflow.md +103 -10
- package/references/engineering-loop.md +27 -2
- package/skills/docs-audit-feature/SKILL.md +24 -20
- package/skills/docs-audit-full/SKILL.md +13 -8
- package/skills/docs-backlog/SKILL.md +6 -3
- package/skills/docs-process/SKILL.md +62 -65
- package/skills/docs-status/SKILL.md +21 -15
- package/skills/docs-write/SKILL.md +9 -6
- package/skills/init/SKILL.md +74 -17
- package/skills/init/scripts/doctor.mjs +73 -11
- package/skills/init/scripts/owned.mjs +96 -0
- package/skills/init/scripts/refresh.mjs +129 -0
- package/skills/spec-child/SKILL.md +1 -1
- package/skills/spec-parent/SKILL.md +20 -9
- package/skills/spec-pipeline/SKILL.md +23 -13
- package/skills/spec-retro/SKILL.md +5 -2
- package/skills/spec-review/SKILL.md +10 -6
- package/skills/spec-seat/SKILL.md +10 -5
- package/templates/docs/_meta/doc-index.md +1 -1
- package/templates/docs/infrastructure/background-work.md +1 -1
- package/templates/docs/infrastructure/realtime-events.md +1 -1
- package/templates/docs/patterns/backend-patterns.md +1 -1
- package/templates/docs/patterns/code-organization.md +1 -1
- package/templates/docs/patterns/codegen.md +1 -1
- package/templates/docs/patterns/design-tokens.md +1 -1
- package/templates/docs/patterns/frontend-patterns.md +1 -1
- package/templates/docs/patterns/long-running-workflows.md +1 -1
- package/templates/docs/patterns/state-management.md +1 -1
- package/templates/docs/patterns/testing.md +1 -1
- 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
|
|
9
|
-
|
|
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
|
|
35
|
-
|
|
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
|
|
62
|
-
code — but you did NOT re-read the whole doc against the whole codebase, and
|
|
63
|
-
*whole-doc* claim. Stamping it for a targeted patch
|
|
64
|
-
hides real staleness in the untouched sections from the next
|
|
65
|
-
`docs-audit-feature`'s to set (it does the full doc-vs-code pass — see
|
|
66
|
-
**Exception:** a doc you *create from scratch* here by reading the code is
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
107
|
-
this effort's docs into a neighbour's
|
|
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
|
|
111
|
-
- Move the **`Source:`** research/spec docs from `docs/research/` into the **same**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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.
|
|
20
|
-
`docs/archive
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- **
|
|
32
|
-
|
|
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
|
|
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
|
-
- **
|
|
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
|
|
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
|
|
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)
|
|
40
|
-
for symmetry, but the blockquote links are the
|
|
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
|
|
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.
|
package/skills/init/SKILL.md
CHANGED
|
@@ -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
|
-
**
|
|
15
|
-
|
|
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
|
|
36
|
-
|
|
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) ·
|
|
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` —
|
|
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
|
|
143
|
-
|
|
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 | `
|
|
165
|
-
| the seven standard docs | absent AND no doc-index row saying `not detected` | `
|
|
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
|
|
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 `
|
|
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
|
|
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} —
|
|
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
|
|
74
|
-
if (
|
|
75
|
-
else if (
|
|
76
|
-
else row('ok', `docs/${relPath}`, `status=${
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
}
|