@naswerks/engineering-loop 0.1.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 +15 -0
- package/.claude-plugin/plugin.json +8 -0
- package/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/verify-staged.mjs +247 -0
- package/package.json +35 -0
- package/references/docs-workflow.md +417 -0
- package/references/engineering-loop.md +298 -0
- package/skills/docs-audit-feature/SKILL.md +88 -0
- package/skills/docs-audit-full/SKILL.md +54 -0
- package/skills/docs-backlog/SKILL.md +41 -0
- package/skills/docs-process/SKILL.md +146 -0
- package/skills/docs-status/SKILL.md +47 -0
- package/skills/docs-write/SKILL.md +43 -0
- package/skills/init/SKILL.md +163 -0
- package/skills/init/scripts/doctor.mjs +176 -0
- package/skills/spec-child/SKILL.md +149 -0
- package/skills/spec-ignite/SKILL.md +277 -0
- package/skills/spec-parent/SKILL.md +634 -0
- package/skills/spec-pipeline/SKILL.md +453 -0
- package/skills/spec-retro/SKILL.md +398 -0
- package/skills/spec-review/SKILL.md +266 -0
- package/skills/spec-seat/SKILL.md +512 -0
- package/skills/spec-witness/SKILL.md +222 -0
- package/templates/claude-md-stanza.md +13 -0
- package/templates/docs/_meta/doc-index.md +90 -0
- package/templates/docs/infrastructure/background-work.md +53 -0
- package/templates/docs/infrastructure/realtime-events.md +54 -0
- package/templates/docs/patterns/backend-patterns.md +96 -0
- package/templates/docs/patterns/codegen.md +69 -0
- package/templates/docs/patterns/frontend-patterns.md +92 -0
- package/templates/docs/patterns/state-management.md +73 -0
- package/templates/docs/patterns/testing.md +103 -0
- package/templates/docs/patterns/ui-style-guide.md +76 -0
- package/templates/docs/patterns/vertical-slice-anatomy.md +61 -0
- package/templates/docs/readme-archive.md +9 -0
- package/templates/docs/readme-features.md +7 -0
- package/templates/docs/readme-guides.md +7 -0
- package/templates/docs/readme-infrastructure.md +7 -0
- package/templates/docs/readme-patterns.md +9 -0
- package/templates/docs/readme-research.md +9 -0
- package/templates/docs/readme-working.md +9 -0
- package/templates/settings-pin.json +10 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs-status
|
|
3
|
+
description: Read-only orientation at session start — what's recently archived, what's in progress in working/, and what ideas are waiting in research/. Use at session start or when the user asks for docs status, or runs docs-status.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# docs-status
|
|
7
|
+
|
|
8
|
+
A quick, read-only pulse of the docs pipeline. No files created. Report by name with context, not
|
|
9
|
+
just counts.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. **`working/` — in flight.** List each doc in `docs/working/` with a one-line "what it's about /
|
|
14
|
+
how far along." A folder's `README.md` is never a queue entry — skip it. These are active efforts; a non-empty queue means living docs may be going stale.
|
|
15
|
+
|
|
16
|
+
2. **`research/` — waiting ideas.** List each doc in `docs/research/` (and topic subfolders) by name
|
|
17
|
+
with a one-line hook. This is the backlog of things to pick up next.
|
|
18
|
+
|
|
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.
|
|
33
|
+
|
|
34
|
+
5. **Open issues.** Grep living docs for an `## Open Issues` section; list which docs/features carry
|
|
35
|
+
unresolved, code-verified problems (and how many) so known risks surface at orientation instead of
|
|
36
|
+
rotting silently. These are candidates to fix, or to graduate into a `working/` effort.
|
|
37
|
+
|
|
38
|
+
6. **Recommend a next action, escalating by scale:**
|
|
39
|
+
- Working queue non-empty: "process the working doc X" first.
|
|
40
|
+
- Clear: "pick up research idea Y" (with a one-line why).
|
|
41
|
+
- A few stale docs: `docs-audit-feature` on those specific docs.
|
|
42
|
+
- **Many stale docs, or a long absence (~30+ days of no doc activity)**: recommend `docs-audit-full`
|
|
43
|
+
to get a prioritized re-verify plan instead of one-off audits.
|
|
44
|
+
|
|
45
|
+
## Notes
|
|
46
|
+
- 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.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs-write
|
|
3
|
+
description: Finalize the working doc at the end of an implementation session and leave it in docs/working/ as the queue for docs-process to patch + file. Use at the end of a coding session, when the user says to write up the work, document the session, book-end it, or run docs-write.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# docs-write
|
|
7
|
+
|
|
8
|
+
Capture an implementation session into a finished session doc that **stays in `docs/working/`** as the
|
|
9
|
+
queue for `docs-process`. This is the **first** half of the bookend — it does NOT patch living docs and
|
|
10
|
+
does NOT archive. Both happen in `docs-process`, once the living docs are patched and verified against
|
|
11
|
+
code. Leaving the doc in `working/` is what makes `working/` a real queue (`docs-status` reads it) and
|
|
12
|
+
keeps the doc editable until its living-doc obligations are discharged.
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
|
|
16
|
+
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
|
+
|
|
18
|
+
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
|
+
- 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).
|
|
21
|
+
- **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
|
+
- **Builds on** — if this continues prior work, link the prior doc: `Builds on [prior](prior.md)`
|
|
23
|
+
- **Source** — if idea-driven, link the `research/` doc
|
|
24
|
+
- **The Problem** / **The Target**
|
|
25
|
+
- **Research & Decisions** — options, verdicts, rationale (table). Write this for **future us**: capture **what we actually did vs. what we set out to do** (where the plan changed and why), **what we tried that didn't work** (dead-ends, gotchas, abandoned approaches — so nobody re-walks them), and **every decision we made** with its reasoning. The point of a session doc is the narrative a later session can't reconstruct from the diff: the diff shows the final state, not the choices or the things that failed on the way there.
|
|
26
|
+
- **Implementation** — what changed, before/after where useful
|
|
27
|
+
- **Change shape** — orientation, NOT a file-by-file inventory (the commit is the inventory). Name the key new/moved pieces and the structural facts a later session needs; defer the full list to the commit. A file-by-file dump makes the doc read like a review.
|
|
28
|
+
- **Verification** — build/test results; what's tested, what isn't
|
|
29
|
+
- **Watch-Outs** — edge cases, gotchas
|
|
30
|
+
- **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
|
+
|
|
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/`**.
|
|
33
|
+
|
|
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`.
|
|
35
|
+
|
|
36
|
+
## Notes
|
|
37
|
+
- **Provenance carries into the living docs.** The `Source:` (research) and `Builds on` (prior working) links
|
|
38
|
+
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
|
+
- **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/`.
|
|
43
|
+
- If the work was pure research/exploration with no code change, use `docs-backlog` instead.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# init
|
|
7
|
+
|
|
8
|
+
Every seat the loop arms reads `docs/_meta/*` and a floor of `patterns/*` docs before it writes a line;
|
|
9
|
+
the coordinator keeps a tracker in `docs/research/{topic}/`; `docs-write` files into `docs/working/`; the
|
|
10
|
+
control plane admits a spec folder only when the repository carries `docs/_meta/docs-workflow.md`.
|
|
11
|
+
`init` creates all of that in a repository that has none of it — as REAL first drafts scanned off the
|
|
12
|
+
repo, never as empty headings — and `init doctor` says, per path, whether the loop can run.
|
|
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.
|
|
16
|
+
|
|
17
|
+
**The law of the draft:** a section is filled only from files the scan opened, and every drafted doc
|
|
18
|
+
names them in its provenance line. Stack, trees, commands, config keys and Key Files are copied verbatim.
|
|
19
|
+
**Rules and gotchas are never invented** — a rule is written only when the repository already wrote it
|
|
20
|
+
down somewhere (a CONTRIBUTING, an analyzer config, a lint rule, a base-class docstring), and the bullet
|
|
21
|
+
cites that file. A section with nothing to fill carries the literal absent-line and nothing else:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
No {X} detected — fill this in: {what the section must answer}.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The loop itself supplies exactly three seed rules that need no source: never hand-write a generated
|
|
28
|
+
folder (when one exists); suites run serially with the output captured to a UTF-8 file and the summary
|
|
29
|
+
block read, never the exit code alone; a pin must be able to fail.
|
|
30
|
+
|
|
31
|
+
## What init creates — the closed set
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
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)
|
|
37
|
+
_meta/doc-index.md the menu, written LAST — every doc that exists + three repo slots:
|
|
38
|
+
## Role reading lists · ## Apps and their build commands · ## The evidence tools
|
|
39
|
+
research/ working/ archive/ guides/ patterns/ infrastructure/ features/ each with a README.md
|
|
40
|
+
patterns/vertical-slice-anatomy.md patterns/backend-patterns.md patterns/frontend-patterns.md
|
|
41
|
+
patterns/testing.md patterns/codegen.md the floor — always drafted
|
|
42
|
+
patterns/state-management.md patterns/ui-style-guide.md
|
|
43
|
+
infrastructure/realtime-events.md infrastructure/background-work.md only when the scan finds the concern
|
|
44
|
+
CLAUDE.md the Start-here stanza appended (created with only the stanza if absent)
|
|
45
|
+
AGENTS.md the same stanza, only when the file already exists
|
|
46
|
+
.claude/settings.json the plugin pin merged in (never clobbers existing keys)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Eight folders, and the set is closed. Anything else already under `docs/` is left alone and reported as
|
|
50
|
+
"outside the loop's shape — not touched" — never an error, never a queue entry.
|
|
51
|
+
|
|
52
|
+
**Every template lives at the pack root under `templates/`** (`templates/docs/patterns/*.md`,
|
|
53
|
+
`templates/docs/infrastructure/*.md`, `templates/docs/_meta/doc-index.md`, `templates/docs/readme-*.md`,
|
|
54
|
+
`templates/claude-md-stanza.md`, `templates/settings-pin.json`). Under Claude Code the pack root is
|
|
55
|
+
`${CLAUDE_PLUGIN_ROOT}` and this skill's own folder is `${CLAUDE_SKILL_DIR}`; in another harness, the
|
|
56
|
+
pack root is the folder that holds `.claude-plugin/plugin.json` — two levels above this SKILL.md.
|
|
57
|
+
|
|
58
|
+
## The scan — read everything first, write nothing yet
|
|
59
|
+
|
|
60
|
+
Run all three passes with your file tools before the first write. Keep a table in your turn — `file ·
|
|
61
|
+
what it says` — and cite from it. A draft written mid-scan is a draft written from memory.
|
|
62
|
+
|
|
63
|
+
1. **Refuse outside a git repository**: `git rev-parse --show-toplevel` (the refusal is
|
|
64
|
+
`init-needs-a-git-repository`). Everything below is relative to that root.
|
|
65
|
+
2. **The stacks.** `*.sln`, `*.slnx`, `*.csproj`, `Directory.Build.props`, `Directory.Packages.props` ·
|
|
66
|
+
`package.json` (+ lockfile, `workspaces`), `pnpm-workspace.yaml` · `pyproject.toml`, `setup.cfg`,
|
|
67
|
+
`requirements*.txt` · `go.mod` · `Cargo.toml` · `Gemfile` · `pom.xml`, `build.gradle*`. Frameworks and
|
|
68
|
+
versions by dependency name.
|
|
69
|
+
3. **The commands and the shape.** Test runners and scripts (`package.json` `scripts`, `Makefile`,
|
|
70
|
+
`justfile`, `scripts/*`) · CI workflow files (`.github/workflows/*.yml`, `azure-pipelines.yml`,
|
|
71
|
+
`.gitlab-ci.yml`) — the jobs are the lanes · app roots (`angular.json` projects, `apps/*`,
|
|
72
|
+
`packages/*`, `web/*` and `services/*` under `src/`, `cmd/*`, an executable `*.csproj`) · `generated/` folders
|
|
73
|
+
and codegen configs (`codegen.ts`, `codegen.yml`, `openapi*`, `*.proto`, `tgconfig.json`, `prisma/`) ·
|
|
74
|
+
the folder layout two deep · `git log --oneline -20` (the message convention) · a realtime library
|
|
75
|
+
(signalr, socket.io, ws, sse, pusher, ably) · a state library (ngxs, ngrx, redux, zustand, pinia,
|
|
76
|
+
mobx) · a UI library or tokens file (tailwind, daisyui, mui, chakra, bootstrap, shadcn) · a queue,
|
|
77
|
+
worker or scheduler (a service bus, RabbitMQ, SQS, BullMQ, Hangfire, Quartz, Celery, sidekiq).
|
|
78
|
+
4. **The rules the repository already wrote down.** `CONTRIBUTING.md`, `CLAUDE.md`, `AGENTS.md`, a
|
|
79
|
+
README "Conventions" section · `.editorconfig`, eslint / biome / prettier / stylelint configs ·
|
|
80
|
+
analyzer configs (`.stylecop`, `.globalconfig`, `Directory.Build.props` rules such as
|
|
81
|
+
`TreatWarningsAsErrors`, `Nullable`, banned-API lists) · `tsconfig*.json` strictness ·
|
|
82
|
+
`BannedSymbols.txt`, `FLAKY-TESTS.md` · CODEOWNERS · `.husky/`, `.githooks/` · PR templates · the
|
|
83
|
+
existing `docs/` tree · `.claude/settings.json`.
|
|
84
|
+
|
|
85
|
+
## The write order — create-if-missing at every step
|
|
86
|
+
|
|
87
|
+
An existing file always wins and is reported `repo-owned`, whether it is a draft or not, loop-shaped or
|
|
88
|
+
not. `init` never overwrites, never reorders, never deletes.
|
|
89
|
+
|
|
90
|
+
1. The seven folders, each with its `README.md` from `templates/docs/readme-{folder}.md` — the README is
|
|
91
|
+
the only file init writes into a folder that already exists.
|
|
92
|
+
2. `docs/_meta/docs-workflow.md` and `docs/_meta/engineering-loop.md` — byte copies of the pack's
|
|
93
|
+
`references/*` (their first line is the version stamp; copy it with the file).
|
|
94
|
+
3. The five floor drafts from `templates/docs/patterns/*.md`, filled as below.
|
|
95
|
+
4. The conditional drafts — `state-management`, `ui-style-guide`, `realtime-events`, `background-work` —
|
|
96
|
+
only where pass 3 found the concern.
|
|
97
|
+
5. `docs/_meta/doc-index.md` from its template, LAST, listing every doc that now exists under the four
|
|
98
|
+
living folders (drafted or repo-owned), the three repo slots filled from the scan.
|
|
99
|
+
6. The Start-here stanza (`templates/claude-md-stanza.md`) appended to `CLAUDE.md` (created if absent)
|
|
100
|
+
and to `AGENTS.md` (only if present); skipped when the opening marker
|
|
101
|
+
`<!-- naswerks-loop:start-here -->` is already there.
|
|
102
|
+
7. `.claude/settings.json` — merge the two keys from `templates/settings-pin.json`; create the file if
|
|
103
|
+
absent; never remove or reorder existing keys.
|
|
104
|
+
|
|
105
|
+
Then the report: one table, one row per path — `created` · `repo-owned` · `skipped: marker present` —
|
|
106
|
+
followed by the doctor verdict line.
|
|
107
|
+
|
|
108
|
+
## Filling a template
|
|
109
|
+
|
|
110
|
+
Every template section carries an `<!-- init: scan … fill … absent … -->` instruction with three parts:
|
|
111
|
+
**scan** (what to read), **fill** (what a real fill looks like — a fenced command, a tree, a table row),
|
|
112
|
+
**absent** (the exact line to write when nothing was found). Replace the comment with the fill or the
|
|
113
|
+
absent-line; **never leave an `<!-- init: -->` comment in the output** (`doctor` reads one as DEGRADE).
|
|
114
|
+
Replace `{today}` with today's date and `{files}` in the provenance line with the comma-separated files
|
|
115
|
+
the scan opened for that doc.
|
|
116
|
+
|
|
117
|
+
The shape every drafted doc keeps — because it is the shape the seats know how to read:
|
|
118
|
+
|
|
119
|
+
1. the H1, the one-line blockquote ending `Last verified {today}.`, the `<!-- meta: … status=draft … -->`
|
|
120
|
+
line, the `<!-- naswerks-loop: draft; scanned=… -->` line;
|
|
121
|
+
2. Overview, then Stack, then the shape (a tree copied from a REAL folder), then one section per
|
|
122
|
+
concept in the micro-grammar *prose rule, a REAL example, `### Rules`, `### Gotcha:`*;
|
|
123
|
+
3. decision tables that decide, checklists whose items are real paths, a `Legacy (never use)` column
|
|
124
|
+
where the repo has one;
|
|
125
|
+
4. exact commands in fenced blocks; identifiers in backticks;
|
|
126
|
+
5. `## Key Files`, `## Lineage`.
|
|
127
|
+
|
|
128
|
+
A section the scan can only half-fill says which half. A rule with no cited source is not written.
|
|
129
|
+
|
|
130
|
+
## doctor
|
|
131
|
+
|
|
132
|
+
`node ${CLAUDE_SKILL_DIR}/scripts/doctor.mjs [repo-root]` — the `scripts/` folder beside this SKILL.md;
|
|
133
|
+
Node 22 or later, no dependencies. It prints one row per thing the skills read and exits non-zero on any
|
|
134
|
+
FAIL, so a CI job or a control plane can call it.
|
|
135
|
+
|
|
136
|
+
| Row | FAIL | DEGRADE | ok |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| each of the eight folders | missing | README missing | present |
|
|
139
|
+
| `_meta/docs-workflow.md`, `_meta/engineering-loop.md` | missing, or no stamp on line 1 | stamp behind the pack's version | stamp equals it |
|
|
140
|
+
| `_meta/doc-index.md` and its three slot sections | missing | a slot carries only the absent-line | filled |
|
|
141
|
+
| the five floor docs | missing | `status=draft`, or an `<!-- init: -->` left in | `status=current` |
|
|
142
|
+
| the four conditional docs | — | `status=draft` when present | present and current, or absent |
|
|
143
|
+
| `CLAUDE.md` / `AGENTS.md` stanza · `.claude/settings.json` pin | — | missing | present |
|
|
144
|
+
| `verify-staged` | — | not on PATH (the row carries the fallback) | resolvable |
|
|
145
|
+
| 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 |
|
|
146
|
+
| every `docs/working/*.md` | — | lacks `## Living Docs to Update` or `## Archive` | carries both |
|
|
147
|
+
| anything else under `docs/` | — | — | "outside the loop's shape — not touched" |
|
|
148
|
+
|
|
149
|
+
**If `node` is absent**, walk the same rows by hand with your file tools, in that order, and print the
|
|
150
|
+
same table: the checks are file existence, a first-line stamp, a heading grep, a `status=` read, and the
|
|
151
|
+
brief's two sections.
|
|
152
|
+
|
|
153
|
+
A repository fresh from `init` reads DEGRADE on every draft and ok on the shape. That is the intended
|
|
154
|
+
state: the owner promotes each draft to `status=current` as they confirm it, section by section.
|
|
155
|
+
|
|
156
|
+
## Notes
|
|
157
|
+
|
|
158
|
+
- `init` is the front door of the pack; `docs-status` is the pulse after it. Run `docs-status` once the
|
|
159
|
+
drafts are promoted to see the queue the way a session will.
|
|
160
|
+
- The floor is always drafted, even in a repository with no frontend or no codegen — the reading floor
|
|
161
|
+
must RESOLVE for every seat kind, and "No frontend detected — fill this in" is an honest doc.
|
|
162
|
+
- Counterpart skills: `spec-seat` (reads the floor), `spec-child` (reads the role rows), `spec-pipeline`
|
|
163
|
+
(writes the brief `doctor` checks), `docs-write` / `docs-process` (the queue `doctor` reads).
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// doctor — can the loop run in this repository? One row per thing the skills read, a verdict per row,
|
|
3
|
+
// non-zero exit on any FAIL. Node >= 22, no dependencies. Usage: node doctor.mjs [repo-root]
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
5
|
+
import { join, resolve, dirname } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const repo = resolve(process.argv[2] || process.cwd());
|
|
10
|
+
const packRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
11
|
+
const rows = [];
|
|
12
|
+
const row = (verdict, subject, detail = '') => rows.push({ verdict, subject, detail });
|
|
13
|
+
const read = (p) => (existsSync(p) ? readFileSync(p, 'utf8').replace(/\r\n/g, '\n') : null);
|
|
14
|
+
const rel = (p) => p.slice(repo.length + 1).replace(/\\/g, '/');
|
|
15
|
+
|
|
16
|
+
// ── the pack's own version, for the stamp check ─────────────────────────────────────────────────
|
|
17
|
+
let packVersion = null;
|
|
18
|
+
try { packVersion = JSON.parse(readFileSync(join(packRoot, '.claude-plugin', 'plugin.json'), 'utf8')).version; } catch { /* reported below */ }
|
|
19
|
+
|
|
20
|
+
// ── 1. the eight folders ────────────────────────────────────────────────────────────────────────
|
|
21
|
+
const FOLDERS = ['_meta', 'research', 'working', 'archive', 'guides', 'patterns', 'infrastructure', 'features'];
|
|
22
|
+
if (!existsSync(join(repo, 'docs'))) row('FAIL', 'docs/', 'missing — run init');
|
|
23
|
+
for (const f of FOLDERS) {
|
|
24
|
+
const p = join(repo, 'docs', f);
|
|
25
|
+
if (!existsSync(p)) row('FAIL', `docs/${f}/`, 'missing');
|
|
26
|
+
else if (f !== '_meta') row(existsSync(join(p, 'README.md')) ? 'ok' : 'DEGRADE', `docs/${f}/README.md`, existsSync(join(p, 'README.md')) ? '' : 'missing (init writes it)');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── 2. the two method docs and their stamp ──────────────────────────────────────────────────────
|
|
30
|
+
const STAMP = /^<!-- naswerks-loop: version=([0-9]+\.[0-9]+\.[0-9]+[^ ]*) -->/m;
|
|
31
|
+
for (const name of ['docs-workflow.md', 'engineering-loop.md']) {
|
|
32
|
+
const p = join(repo, 'docs', '_meta', name);
|
|
33
|
+
const text = read(p);
|
|
34
|
+
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; }
|
|
37
|
+
if (!packVersion) { row('DEGRADE', `docs/_meta/${name}`, `stamped ${m[1]}; the pack's own version could not be read`); continue; }
|
|
38
|
+
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}`);
|
|
40
|
+
else row('ok', `docs/_meta/${name}`, `stamp ${m[1]}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── 3. doc-index and its three repo slots ───────────────────────────────────────────────────────
|
|
44
|
+
{
|
|
45
|
+
const p = join(repo, 'docs', '_meta', 'doc-index.md');
|
|
46
|
+
const text = read(p);
|
|
47
|
+
if (text === null) row('FAIL', 'docs/_meta/doc-index.md', 'missing');
|
|
48
|
+
else {
|
|
49
|
+
for (const heading of ['## Role reading lists', '## Apps and their build commands', '## The evidence tools']) {
|
|
50
|
+
const section = sectionOf(text, heading);
|
|
51
|
+
if (section === null) row('FAIL', `doc-index.md ${heading}`, 'section missing');
|
|
52
|
+
else if (/fill this in|\(none detected\)/i.test(section)) row('DEGRADE', `doc-index.md ${heading}`, 'present, carries only the absent-line');
|
|
53
|
+
else row('ok', `doc-index.md ${heading}`, '');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── 4. the reading floor and the conditional docs ───────────────────────────────────────────────
|
|
59
|
+
const FLOOR = ['patterns/vertical-slice-anatomy.md', 'patterns/backend-patterns.md', 'patterns/frontend-patterns.md', 'patterns/testing.md', 'patterns/codegen.md'];
|
|
60
|
+
const CONDITIONAL = ['patterns/state-management.md', 'patterns/ui-style-guide.md', 'infrastructure/realtime-events.md', 'infrastructure/background-work.md'];
|
|
61
|
+
for (const d of FLOOR) docRow(d, true);
|
|
62
|
+
for (const d of CONDITIONAL) docRow(d, false);
|
|
63
|
+
function docRow(relPath, required) {
|
|
64
|
+
const text = read(join(repo, 'docs', relPath));
|
|
65
|
+
if (text === null) { row(required ? 'FAIL' : 'ok', `docs/${relPath}`, required ? 'missing' : 'absent (drafted only when the scan finds the concern)'); return; }
|
|
66
|
+
const meta = /<!-- meta:[^>]*status=([a-z]+)/.exec(text);
|
|
67
|
+
if (!meta) row('DEGRADE', `docs/${relPath}`, 'no <!-- meta --> line');
|
|
68
|
+
else if (meta[1] === 'draft') row('DEGRADE', `docs/${relPath}`, 'status=draft — scanned, not decided');
|
|
69
|
+
else row('ok', `docs/${relPath}`, `status=${meta[1]}`);
|
|
70
|
+
if (/<!-- init:/.test(text)) row('DEGRADE', `docs/${relPath}`, 'an <!-- init: --> instruction was left in the file');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── 5. CLAUDE.md / AGENTS.md stanza, settings pin ───────────────────────────────────────────────
|
|
74
|
+
for (const name of ['CLAUDE.md', 'AGENTS.md']) {
|
|
75
|
+
const text = read(join(repo, name));
|
|
76
|
+
if (text === null) { if (name === 'CLAUDE.md') row('DEGRADE', name, 'missing (init creates it with the stanza)'); continue; }
|
|
77
|
+
row(text.includes('<!-- naswerks-loop:start-here -->') ? 'ok' : 'DEGRADE', `${name} stanza`, text.includes('<!-- naswerks-loop:start-here -->') ? '' : 'the Start-here stanza is not present');
|
|
78
|
+
}
|
|
79
|
+
{
|
|
80
|
+
const p = join(repo, '.claude', 'settings.json');
|
|
81
|
+
const text = read(p);
|
|
82
|
+
if (text === null) row('DEGRADE', '.claude/settings.json', 'missing (init writes the plugin pin)');
|
|
83
|
+
else {
|
|
84
|
+
try {
|
|
85
|
+
const j = JSON.parse(text);
|
|
86
|
+
const mk = j.extraKnownMarketplaces && j.extraKnownMarketplaces['engineering-loop'];
|
|
87
|
+
const en = j.enabledPlugins && j.enabledPlugins['naswerks@engineering-loop'] === true;
|
|
88
|
+
row(mk && en ? 'ok' : 'DEGRADE', '.claude/settings.json pin', mk && en ? '' : `${mk ? '' : 'extraKnownMarketplaces.engineering-loop missing; '}${en ? '' : 'enabledPlugins["naswerks@engineering-loop"] missing'}`);
|
|
89
|
+
} catch { row('DEGRADE', '.claude/settings.json', 'not valid JSON'); }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── 6. verify-staged resolvable ─────────────────────────────────────────────────────────────────
|
|
94
|
+
{
|
|
95
|
+
let ok = false;
|
|
96
|
+
for (const [cmd, args] of [['verify-staged', ['--help']], ['npx', ['--no-install', 'verify-staged', '--help']]]) {
|
|
97
|
+
try { execFileSync(cmd, args, { stdio: 'ignore', shell: process.platform === 'win32', timeout: 15000 }); ok = true; break; } catch { /* next */ }
|
|
98
|
+
}
|
|
99
|
+
const packBin = join(packRoot, 'bin', 'verify-staged.mjs');
|
|
100
|
+
if (ok) row('ok', 'verify-staged', 'on PATH');
|
|
101
|
+
else if (existsSync(packBin)) row('DEGRADE', 'verify-staged', `not on PATH; the skills' fallback is: node ${packBin.replace(/\\/g, '/')} {list}`);
|
|
102
|
+
else row('DEGRADE', 'verify-staged', 'not on PATH and the pack bin was not found — install the pack (npm i -g @naswerks/engineering-loop)');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── 7. every ignition brief, checked the way the control plane checks it ────────────────────────
|
|
106
|
+
const KINDS = new Set(['coordinator', 'build', 'fixit', 'review', 'retro', 'witness', 'docs-process']);
|
|
107
|
+
const researchDir = join(repo, 'docs', 'research');
|
|
108
|
+
if (existsSync(researchDir)) {
|
|
109
|
+
for (const topic of readdirSync(researchDir)) {
|
|
110
|
+
const briefPath = join(researchDir, topic, '00-ignition-brief.md');
|
|
111
|
+
if (!statSync(join(researchDir, topic)).isDirectory() || !existsSync(briefPath)) continue;
|
|
112
|
+
const text = read(briefPath);
|
|
113
|
+
const problems = [];
|
|
114
|
+
const kick = sectionOf(text, '## The kick');
|
|
115
|
+
if (kick === null) problems.push('the brief has no `## The kick` section, so the task text could not be read');
|
|
116
|
+
else if (!kick.split('\n').some((l) => l.trimStart().startsWith('>'))) problems.push('the `## The kick` section carries no blockquote; its whole text stands in for the task');
|
|
117
|
+
const seq = sectionOf(text, '## The sequence');
|
|
118
|
+
if (seq === null) problems.push('the brief has no `## The sequence` section, so no seats could be read');
|
|
119
|
+
else {
|
|
120
|
+
const tableRows = seq.split('\n').filter((l) => l.trim().startsWith('|'));
|
|
121
|
+
if (tableRows.length === 0) problems.push('the `## The sequence` section holds no table, so no seats could be read');
|
|
122
|
+
for (const line of tableRows.slice(1)) {
|
|
123
|
+
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim().replace(/^`|`$/g, '').trim());
|
|
124
|
+
if (cells.every((c) => /^[-: ]*$/.test(c))) continue;
|
|
125
|
+
if (!cells.some((c) => KINDS.has(c))) { problems.push(`a sequence row names no seat kind and composes nothing: \`${line.trim()}\``); continue; }
|
|
126
|
+
const spec = cells.map((c) => /\b(\d{2}-[A-Za-z0-9._-]*?\.md)\b/.exec(c)).find(Boolean);
|
|
127
|
+
if (spec && !existsSync(join(researchDir, topic, spec[1]))) problems.push(`the sequence names \`${spec[1]}\` and it is missing from disk`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (problems.length) for (const pr of problems) row('FAIL', `docs/research/${topic}/00-ignition-brief.md`, pr);
|
|
131
|
+
else row('ok', `docs/research/${topic}/00-ignition-brief.md`, 'the kick and the sequence read clean');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── 8. the working queue's shape ────────────────────────────────────────────────────────────────
|
|
136
|
+
const workingDir = join(repo, 'docs', 'working');
|
|
137
|
+
if (existsSync(workingDir)) {
|
|
138
|
+
for (const f of readdirSync(workingDir).filter((f) => f.endsWith('.md') && f !== 'README.md')) {
|
|
139
|
+
const text = read(join(workingDir, f));
|
|
140
|
+
const missing = ['## Living Docs to Update', '## Archive'].filter((h) => !text.includes(h));
|
|
141
|
+
row(missing.length ? 'DEGRADE' : 'ok', `docs/working/${f}`, missing.length ? `lacks ${missing.join(' and ')}` : 'carries its checklist and its archive target');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── 9. anything else under docs/ ────────────────────────────────────────────────────────────────
|
|
146
|
+
if (existsSync(join(repo, 'docs'))) {
|
|
147
|
+
for (const entry of readdirSync(join(repo, 'docs'))) {
|
|
148
|
+
if (FOLDERS.includes(entry)) continue;
|
|
149
|
+
row('ok', `docs/${entry}`, 'outside the loop\'s shape — not touched');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── report ──────────────────────────────────────────────────────────────────────────────────────
|
|
154
|
+
const width = Math.max(...rows.map((r) => r.subject.length), 10);
|
|
155
|
+
console.log(`doctor — ${repo}${packVersion ? ` — pack ${packVersion}` : ''}\n`);
|
|
156
|
+
console.log(`| verdict | subject${' '.repeat(width - 7)} | detail |`);
|
|
157
|
+
console.log(`|---------|${'-'.repeat(width + 2)}|--------|`);
|
|
158
|
+
for (const r of rows) console.log(`| ${r.verdict.padEnd(7)} | ${r.subject.padEnd(width)} | ${r.detail} |`);
|
|
159
|
+
const count = (v) => rows.filter((r) => r.verdict === v).length;
|
|
160
|
+
console.log(`\nFAIL ${count('FAIL')} · DEGRADE ${count('DEGRADE')} · ok ${count('ok')}`);
|
|
161
|
+
process.exit(count('FAIL') ? 1 : 0);
|
|
162
|
+
|
|
163
|
+
// ── helpers ─────────────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
function sectionOf(text, heading) {
|
|
165
|
+
const lines = text.split('\n');
|
|
166
|
+
const start = lines.findIndex((l) => l.trimEnd().toLowerCase().startsWith(heading.toLowerCase()));
|
|
167
|
+
if (start < 0) return null;
|
|
168
|
+
const body = [];
|
|
169
|
+
for (let i = start + 1; i < lines.length; i++) { if (lines[i].startsWith('## ')) break; body.push(lines[i]); }
|
|
170
|
+
return body.join('\n');
|
|
171
|
+
}
|
|
172
|
+
function compareVersions(a, b) {
|
|
173
|
+
const pa = a.split(/[.-]/).map((x) => parseInt(x, 10) || 0), pb = b.split(/[.-]/).map((x) => parseInt(x, 10) || 0);
|
|
174
|
+
for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); }
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-child
|
|
3
|
+
description: The BUILD JOB for a pipeline seat — one spec, the role's reading list, the pin taxonomy, the suite protocol, and the fixit variant. Composed AFTER spec-seat, which carries the seat contract (blackboard, question lane, verdicts, FINAL TLDR, close-out). Use when a prompt says you are a build or fixit session, or run spec-child {role} {spec-path}. Roles: backend | frontend | mixed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# spec-child
|
|
7
|
+
|
|
8
|
+
**Your seat contract is `spec-seat` — invoke it first if you have not.** It carries the blackboard, the
|
|
9
|
+
question lane, the parked-call discriminator, the verdict vocabulary, the BELIEFS sha, git shapes, the
|
|
10
|
+
six-section FINAL TLDR, and the close-out order. **This file is the BUILD JOB only**, and it refines that
|
|
11
|
+
contract — where the two genuinely conflict, that is a finding to report, not a fork to resolve quietly.
|
|
12
|
+
|
|
13
|
+
You build exactly one spec. The coordinator judges and rules; you recon, plan, build, and report honestly.
|
|
14
|
+
Hand-cranked, a human relays messages; hosted inside a pipeline, your coordinator drives you itself and
|
|
15
|
+
the relay is machine-carried — **your contract is identical either way.**
|
|
16
|
+
|
|
17
|
+
Invoked as `spec-child {role} {spec-path}`, or told "you are a build session" with the spec path.
|
|
18
|
+
|
|
19
|
+
## Step 1 — with your role and spec in hand
|
|
20
|
+
|
|
21
|
+
**If your prompt handed you a MANIFEST, that manifest is your reading list and this section does not
|
|
22
|
+
apply** (`spec-seat`, Step 0). This list is for a build seat working from a spec.
|
|
23
|
+
|
|
24
|
+
1. **Your spec** — fully, INCLUDING any Addendum sections. It is self-contained.
|
|
25
|
+
2. **The umbrella** (`01-{topic}.md`) — settled decisions; don't relitigate them.
|
|
26
|
+
3. **The tracker** — `docs/research/{topic}/project-status.md`. The coordinator's live journal and
|
|
27
|
+
READ-ONLY to you. Prior session rows, deferred findings, and process incidents are your contract: build
|
|
28
|
+
against what ACTUALLY shipped, not the spec's guesses. **If anything in it disagrees with your spec or
|
|
29
|
+
doesn't make sense, say so in your PLAN TLDR** — asking is always allowed.
|
|
30
|
+
4. **Your role's adds** (below) + any extra docs the prompt names.
|
|
31
|
+
5. **Grep the living docs' `## Open Issues` for your subject.** One command, and it is the step efforts
|
|
32
|
+
keep skipping: a defect can sit dated and described in a living doc while several seats check the code
|
|
33
|
+
and conclude it is new.
|
|
34
|
+
|
|
35
|
+
## Role — the adds, on top of spec-seat's Step 0 floor
|
|
36
|
+
|
|
37
|
+
| Role | Adds |
|
|
38
|
+
|---|---|
|
|
39
|
+
| **frontend** | `patterns/state-management.md` · `patterns/ui-style-guide.md` · `infrastructure/realtime-events.md` (the poke/refetch concept trips sessions up — read it even for "pure UI" work) · plus your role's rows in `docs/_meta/doc-index.md` § Role reading lists |
|
|
40
|
+
| **backend** | `infrastructure/realtime-events.md` (the poke/refetch concept trips sessions up — read it even for "pure API" work) · `infrastructure/background-work.md` as the task warrants · plus your role's rows in `docs/_meta/doc-index.md` § Role reading lists |
|
|
41
|
+
| **mixed** | **NOT every set by default** — read the adds for the surfaces your spec ACTUALLY touches, decided at recon once you have read the spec. **This is the default role and it covers every surface with no role of its own** — a surface the repo names only in `docs/_meta/doc-index.md` § Role reading lists (a sidecar, a CLI, a worker) reads the row there; docs work needs nothing beyond the Step 0 floor |
|
|
42
|
+
|
|
43
|
+
A role is *which docs does this build session read* — nothing else. Jobs are SEAT KINDS, not roles:
|
|
44
|
+
`docs-process` is a session type of its own, and a spec that touches a surface with no role of its own is
|
|
45
|
+
`mixed` with that surface's row. A doc-index row that says "no X detected" is an honest answer from `init`:
|
|
46
|
+
that concern is not in this repo yet, and there is nothing to read for it.
|
|
47
|
+
|
|
48
|
+
## Recon — the build seat's first act
|
|
49
|
+
|
|
50
|
+
**Re-verify EVERY anchor your spec cites against HEAD** — specs age and prior sessions move line numbers,
|
|
51
|
+
so **report drift, don't trust**. **Recon's first act is to test whether the slice is already satisfied**:
|
|
52
|
+
specs do get overtaken between authoring and build, and discovering that at recon closes a session in
|
|
53
|
+
minutes while assuming the work is pending rebuilds what already exists.
|
|
54
|
+
|
|
55
|
+
Run `git status`; identify which dirty files are yours-to-be vs NOT yours (coordinator-owned docs appear
|
|
56
|
+
mid-run — never stage them). Answer any STANDING RECON QUESTION your spec or prompt carries.
|
|
57
|
+
|
|
58
|
+
**Measure your own baseline for any lane you touch, BEFORE you edit.** A same-day coordinator-verified
|
|
59
|
+
baseline is an acceptable substitute when cited, but measuring your own is the norm — and a number you
|
|
60
|
+
cannot reproduce is a finding.
|
|
61
|
+
|
|
62
|
+
## Build
|
|
63
|
+
|
|
64
|
+
State your HEAD sha again in your FIRST implementation message (the coordinator diffs your beliefs
|
|
65
|
+
against its ledger — stale-tree catches come from this).
|
|
66
|
+
|
|
67
|
+
**If HEAD moves mid-build**: disclose FIRST in your next report — old sha to new sha, clean-ancestor or
|
|
68
|
+
not, `git diff --stat` overlap with YOUR files, and proof of coexistence if any overlap (marker greps +
|
|
69
|
+
suite on the combined tree). **Absorb-and-prove, never silently rebase your beliefs.**
|
|
70
|
+
|
|
71
|
+
**Before you delete a pin, ask what it encodes.** A pin that describes the **MECHANISM** you are removing
|
|
72
|
+
dies with it ("this verb is seat evidence"). A pin that encodes a **DECISION** — a boundary verdict, an
|
|
73
|
+
accepted residual, a known-positive control — dies with nothing, and removing it vacates a ruling nobody
|
|
74
|
+
made. **When in doubt it is a DECISION: say so and ask.**
|
|
75
|
+
|
|
76
|
+
**If ANY existing test requires modification to pass, STOP and report.** A test needing an edit is the
|
|
77
|
+
signal that a VERDICT changed, not that the test is stale.
|
|
78
|
+
|
|
79
|
+
## The suite protocol
|
|
80
|
+
|
|
81
|
+
Run the suite lanes **SERIALLY** — the lanes and their exact invocations are `patterns/testing.md`
|
|
82
|
+
§ Running tests; concurrent runs flake.
|
|
83
|
+
|
|
84
|
+
**Capture FULL runner output to a UTF-8 file and search failures by name** — redirect to a file, then
|
|
85
|
+
search `^failed ` / `Failed!`. Piping through a "last N lines" filter destroys failure names.
|
|
86
|
+
|
|
87
|
+
### USE `rg`, NOT `grep`, FOR ANYTHING THAT MIGHT NOT BE ASCII — AND NEVER TRUST A LOCAL TEST
|
|
88
|
+
|
|
89
|
+
**Two instruments return `0` for text that is present.** Both are silent: they exit clean and read as
|
|
90
|
+
"not found", which is indistinguishable from a correct negative.
|
|
91
|
+
|
|
92
|
+
| instrument | the lie | measured |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `grep` on a non-ASCII pattern | **locale-dependent** — the SAME binary on the SAME file | `LANG` unset: 64 · `LC_ALL=C`: 64 · `LANG=en_US.UTF-8`: 0 |
|
|
95
|
+
| PowerShell `Get-Content -Raw` | reads UTF-8 as the ANSI codepage | with `-Encoding utf8`: 83 · without: 0 |
|
|
96
|
+
|
|
97
|
+
**THE LOCALE ONE IS WHY "IT WORKS FOR ME" IS NOT EVIDENCE.** Whether your shell exports `LANG` is not
|
|
98
|
+
something you chose or can see, two seats on one machine can disagree, and an ASCII control passes in both
|
|
99
|
+
— so a test that "proves" grep is fine proves only that your environment is the lucky one. `rg` is
|
|
100
|
+
unaffected under every locale above.
|
|
101
|
+
|
|
102
|
+
**So:** `rg` for content search, always. `Get-Content` only with `-Encoding utf8`. And author regexes with
|
|
103
|
+
your file tools, never through a shell heredoc — a heredoc can collapse `\\s` to `\s` and break both a pin
|
|
104
|
+
and that pin's own control, so neither can announce the other.
|
|
105
|
+
|
|
106
|
+
**PowerShell 5.1 `Tee-Object` writes UTF-16** — `rg` detects the BOM and reads it; plain `grep` finds
|
|
107
|
+
nothing. Prefer a UTF-8 path anyway so the file is legible to everything.
|
|
108
|
+
|
|
109
|
+
**Read the build's exit code BEFORE trusting a green summary** — a stale binary passes every assertion
|
|
110
|
+
in it. And **require the summary block itself**: a run that printed no totals ran nothing, whatever its
|
|
111
|
+
exit code said. Disclose every red run WITH its rerun; never present a flake as clean.
|
|
112
|
+
|
|
113
|
+
**Any session that touched frontend code ALSO runs the production build** — a test transform does NOT
|
|
114
|
+
catch what the strict production compiler rejects: duplicate class members, cross-session member
|
|
115
|
+
collisions, private-in-template. A green test run can sit over a broken build. The build receipt goes in
|
|
116
|
+
FINAL TLDR section (b) alongside the suite.
|
|
117
|
+
|
|
118
|
+
**`cd` INTO THE APP YOU TOUCHED — a repo can hold several, and the wrong one builds green.** The apps
|
|
119
|
+
and their test and production-build commands are `docs/_meta/doc-index.md` § Apps and their build
|
|
120
|
+
commands. An instruction that named ONE app for years sent seats on other surfaces to run a production
|
|
121
|
+
build over a tree they had not touched, and they got a **passing receipt that proved nothing** — the
|
|
122
|
+
false-green genus, embedded in the manual instead of in a test. Name the app you edited and run its
|
|
123
|
+
commands from its root.
|
|
124
|
+
|
|
125
|
+
## Fix-it variant (`spec-child fixit`)
|
|
126
|
+
|
|
127
|
+
No spec — your contract is the prompt + the coordinator's ledger (READ-ONLY). **STEP 0 — INVESTIGATE,
|
|
128
|
+
REPORT, STOP:** (1) reproduce/characterize cheaply, (2) find the cheapest reliable RECEIPT for the fixed
|
|
129
|
+
state, (3) report the verdict as plain text — root cause, evidence, proposed fix shape + blast radius —
|
|
130
|
+
then STOP for the coordinator's go. After the go: the minimal fix + MAKE THE FAILURE LOUD (log + trace row
|
|
131
|
+
— never silent) + a regression pin + an E2E receipt re-running the original failing scenario
|
|
132
|
+
+ **SWEEP THE RECEIPTS YOUR FIX INVALIDATED** (`spec-seat`'s close-out step, and a fixit is the diff
|
|
133
|
+
most likely to need it — you are changing behaviour something already made a claim about). A fixit that
|
|
134
|
+
repairs a pane silently re-makes a `superseded` live-check TRUE, so its "do not run this" marker becomes
|
|
135
|
+
false. **Grep `live-checks.md`, the tracker and the working docs for every behaviour you changed**,
|
|
136
|
+
correct what is yours in the same commit, and hand back what is not.
|
|
137
|
+
|
|
138
|
+
**A fixit chartered from a review's findings inherits no escape hatch** — every numbered spec carries one
|
|
139
|
+
because `spec-pipeline` requires it, and you have no spec. If the charter you were handed did not state
|
|
140
|
+
what a truthful partial looks like per named item, **ask for it** before you start designing.
|
|
141
|
+
|
|
142
|
+
## Notes
|
|
143
|
+
|
|
144
|
+
- **The spec's anchors are hypotheses; HEAD is the truth.** The biggest catches — stale trees, false
|
|
145
|
+
greens, dropped rulings — are all belief-vs-ledger mismatches. Your beliefs block exists so the
|
|
146
|
+
coordinator can catch them cheaply.
|
|
147
|
+
- Counterpart skills: `spec-seat` (your contract) · `spec-parent` (the seat that arms you) ·
|
|
148
|
+
`spec-pipeline` (how your spec came to exist). The hosted version of you runs THIS same pair — the
|
|
149
|
+
control plane's seed names both skills, in this order.
|