task-pipeline-skill 1.14.1 → 1.16.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/CHANGELOG.md CHANGED
@@ -1,5 +1,155 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.16.0 — 2026-08-06
4
+
5
+ ### Added — `evidence-docs`: a second skill in this plugin, and the router row it fills
6
+
7
+ The canons landed in v1.15.0 with no way to reach them except through the pipeline. The
8
+ global router already reserved a name for the question *"what is this proved by?"* —
9
+ `evidence-docs` — and nothing resolved it. A routed name that resolves to nothing is the
10
+ shape `learned.md` rule 14 forbids, in the routing table itself.
11
+
12
+ `skills/evidence-docs/SKILL.md` is a **navigator, not a second copy**: the ten canons as
13
+ a one-line index, a pointer to their one home in `documentation.md` → *The canons*, and a
14
+ table of where to go next — set docs up from nothing, record a decision, avoid orphaning
15
+ docs on a change, build a check that cannot lie, trust a mechanism that reports success,
16
+ audit docs a project already has, seed a gate. It states its own boundary ("it will be
17
+ read as true"), what is explicitly out (drafts, chat, commit messages, code comments) and
18
+ its refusal phrase.
19
+
20
+ Shipping it as a second skill **in the same plugin** rather than a separate repository is
21
+ what keeps SSOT: one set of files, one release, and no copy to drift. `super-ux` already
22
+ ships six skills from one plugin, so the shape is the family's own.
23
+
24
+ ### Guards — 108 → 112
25
+
26
+ The index is held to the doctrine's own canon list, the pointer to the one home is
27
+ required, the frontmatter is checked against the Agent Skills spec, and every relative
28
+ link is resolved **from the navigator's own directory** — it sits one level over from
29
+ everything it names, which is canon 4 enforced in the file that publishes canon 4.
30
+
31
+ ## v1.15.0 — 2026-08-06
32
+
33
+ ### Added — the ten canons: what makes a document evidence
34
+
35
+ The doctrine carried the mechanisms and never stated the standard they serve.
36
+ `references/documentation.md` now opens with ten laws — a claim carries its address,
37
+ numbers are computed rather than restated, one home per fact, a reference resolves from
38
+ where the document is *read*, green nobody watched turn red is not evidence, a check
39
+ proves its scope and nothing beyond it, silence is not a pass, an estimate is never
40
+ announced as a measurement, what was not checked is printed beside what was, and the
41
+ document ships in the change that made it true.
42
+
43
+ Each canon **names where it is enforced** instead of restating the mechanism, and the
44
+ boundary against `learned.md` is written down: the canons are epistemic (what makes a
45
+ claim documentation), `learned.md` is operational (what to do at a trigger). Two
46
+ undifferentiated rule lists would be the duplication canon 3 forbids.
47
+
48
+ Guarded four ways: the list exists, all ten laws are present, every canon names an
49
+ enforcement, and the boundary is stated. The enforcement check counts **per canon**
50
+ rather than in total — a total threshold only fires once most of them are gone, which
51
+ is a check proving less than it claims, canon 6 applied to itself.
52
+
53
+ **Found while writing them, by this repository's own guard:** canon 9 cited
54
+ `audit.md → Ratchets`, a section that lives in `gates.md`. The file resolved and the
55
+ section did not — the shape a link checker cannot catch and a reader believes.
56
+
57
+ ### Added — a build date is the graph's reply about itself, not a measurement of it
58
+
59
+ Stage 0 reads the code graph before it reads anything else, and until now it recorded
60
+ that graph's freshness as `built YYYY-MM-DD`. That is true, self-reported, and silent
61
+ about the only thing the harvest needs to know: whether the graph describes the tree
62
+ this run is about to change. Twelve commits later the row still reads `built
63
+ 2026-08-05` — and still reads *fresh*, because a date with nothing subtracted from it
64
+ is a fact with no scale. It is the class `references/gates.md` named one release ago:
65
+ an actor's own reply, standing in for evidence about the world.
66
+
67
+ The occasion was this repository's own. `docs/superpowers/specs/` holds a brief, a
68
+ carry-over ledger and an acceptance document for v1.11.0, v1.12.0 and v1.13.0 — and
69
+ none for v1.14.0 or v1.14.1. Those two releases never opened stage 9, so nothing
70
+ refreshed the graph, and the next harvest would have read a two-release-old index
71
+ behind a date that looked current.
72
+
73
+ - **`references/knowledge-graph.md` gains *Measure the lag***: three commands
74
+ (`git rev-parse --verify`, `git rev-list --count`, `git log -1 --format=%ct`) so
75
+ the number comes from git rather than from judgement, and **three states** —
76
+ `built_at_commit` exact, file `mtime` approximate, and `unresolvable` — because
77
+ `graph.json` carries the commit stamp **only when the caller passed it** and
78
+ `graphify update` from the CLI does not. With one state, "could not measure" would
79
+ print like "fresh", which is the failure the section cites `gates.md` for. Zero is
80
+ stated out loud for the same reason.
81
+ - **No threshold, deliberately.** `continuity.md` refused a context-budget number on
82
+ the same grounds: an unmeasurable threshold becomes unconditional doctrine, not
83
+ config. One commit that moved the function this task is about outweighs fifty that
84
+ touched a README, so anything but `current` carries `⚠ not trusted for reach until
85
+ refreshed` — a marker, not a block. The graph is recommended everywhere else in this
86
+ bundle; a blocking staleness check would make *no graph* cheaper than a week-old one.
87
+ - **The cadence is untouched.** Stage 9 already required the refresh unconditionally,
88
+ and an `always | major | manual` mode was rejected rather than deferred: `major`
89
+ schedules exactly the state the doctrine warns about — a graph confidently wrong
90
+ between releases, read first by the next run.
91
+
92
+ ### Added — hygiene check 7: a blank line inside a table
93
+
94
+ A GFM table ends at the first blank line, so a blank line left mid-table silently
95
+ demotes every row below it to pipe-delimited prose. The file stays well-formed, every
96
+ row is still present, and a diff showing only added lines shows nothing wrong. It
97
+ happened **twice inside this run** — a carry-over ledger and the brief's decision
98
+ table — which is `audit.md`'s threshold for a mechanism rather than a third ledger
99
+ row. On its first armed pass the check found **three more** in the carry-over ledgers
100
+ of v1.12.0 and v1.13.0, which had been rendering broken since the day they were
101
+ written. All three are fixed rather than baselined behind a floor.
102
+
103
+ The check shipped with `HYGIENE_FLOOR_7` undeclared, so `judge()` compared against an
104
+ empty string and printed `ok: check 7 … 3 (floor )` over three real hits — the gate
105
+ reporting a pass it never computed, on the release about exactly that. An undeclared
106
+ floor is now a failure, not a zero, and the reason is written beside it.
107
+
108
+ ### Guards — 95 → 104, by extending a sibling rather than copying it
109
+
110
+ `test/validate.py` already enforced that the code-graph doctrine be named in **both**
111
+ halves of stage 9 — the config gate the orchestrator verifies and the section an agent
112
+ reads — because a file that "reads as law while the run never does it" is an inert
113
+ gate. Standing instruction R-003 requires running a fixed defect's definition against
114
+ its siblings, and stage 0 is the sibling: same doctrine file, one duty reading the
115
+ graph where the other refreshes it. So the existing guard was **extended**, not
116
+ duplicated.
117
+
118
+ At stage 0 the word *graph* is not the test — it was already there. The guard requires
119
+ the **measured lag**, and three more checks keep the cited section honest: the commands
120
+ must survive, all three states must survive, and `templates/brief.md` may not ship the
121
+ superseded bare-date row to every project scaffolded from it.
122
+
123
+ The repository's own drift guard caught the release mid-flight **three times**:
124
+ `SKILL-CARD.md` and `evals/RESULTS.md` said *95*, then *100*, then *102*. Its message
125
+ has always offered two fixes — *derive the number or delete it* — and three
126
+ hand-corrections in one run is the answer to which one was right. **The prose no
127
+ longer states a count at all**; it names the command that prints one. The guard's own
128
+ negative self-test was rewritten in the same move: its plant used to edit the
129
+ restated number in place, so it broke the moment the number went away — a test
130
+ coupled to the defect it was written against rather than to the rule. It now
131
+ *introduces* a count into a document that has none, which is what the guard actually
132
+ forbids.
133
+
134
+ ### Fixed — one marker, one spelling
135
+
136
+ The distrust marker this release introduces was written **four different ways inside
137
+ the release that introduced it**: the doctrine's own three-state table omitted it
138
+ entirely, its `unresolvable` row invented *"treat as stale until refreshed"*, and the
139
+ Cursor rule and the config both dropped the sigil. Review caught the first; standing
140
+ instruction R-003 — run a fixed defect's definition against its siblings — found the
141
+ other three. `audit.md` says a class seen twice becomes a mechanism rather than a
142
+ third ledger row, so a guard now requires the canonical string and rejects the second
143
+ spelling. Greppability is the marker's only property: a ledger row is prose, and the
144
+ marker is the one string a later reader can search for.
145
+
146
+ That guard shipped green for the wrong reason and was caught by probing it rather
147
+ than by reading it. It compared **per line**, and this doctrine wraps at ~80 columns —
148
+ so in `README.md` and `stages.md`, where the marker is split across two lines, it
149
+ matched nothing and reported a pass. It now normalises whitespace before counting.
150
+ Two releases running, the defect found inside the release was an instance of the
151
+ class the release was about.
152
+
3
153
  ## v1.14.1 — 2026-08-05
4
154
 
5
155
  ### Fixed — a guard below the verdict block is dead code shaped like a guard
package/CONTRIBUTING.md CHANGED
@@ -225,7 +225,25 @@ side; `references/review.md` rates an effect asserted without one as **Important
225
225
  not Minor. A finding that never blocks is a finding the fix loop never sees.
226
226
  *(guard: `the report no longer requires verified-by lines`)*
227
227
 
228
- **30. Every invariant above names the guard that enforces it, and that guard exists.**
228
+ **30. The code graph's ledger row states a measured lag, never a build date.**
229
+ Stage 0 reads the graph first, so its freshness is the one claim a whole run rests
230
+ on — and `built 2026-08-05` is the graph's own reply about itself, true and silent
231
+ about whether it describes the tree the run is about to change. `stages.md`'s stage-0
232
+ section and the config's stage-0 gate must both require the measurement, exactly as
233
+ they do for the stage-9 refresh, and `references/knowledge-graph.md` must keep the
234
+ commands and all three signal states — with a state missing, a graph that could not
235
+ be measured prints like a fresh one.
236
+ *(guard: `never requires it — a run passes intake quoting a`)*
237
+
238
+ **31. The evidence-docs navigator indexes the canons and never copies them.**
239
+ `skills/evidence-docs/SKILL.md` is a second skill in the same plugin: the ten canons as
240
+ a one-line index, a pointer to their one home, and a table of where to go next. The
241
+ guard holds the index to the doctrine's own list, requires the pointer, and resolves
242
+ every relative link **from the navigator's directory** — it sits one level over from
243
+ everything it names, which is canon 4 in the file that publishes canon 4.
244
+ *(guard: `index that has drifted from its doctrine`)*
245
+
246
+ **32. Every invariant above names the guard that enforces it, and that guard exists.**
229
247
  This list claims to be *what the validator enforces*; it was eight guards behind when
230
248
  an audit measured it. A claim of enforcement is now checked like any other claim.
231
249
  *(guard: `whose message does not appear in`)* — and a cited literal must lie inside
package/README.md CHANGED
@@ -116,6 +116,7 @@ until it is installed.
116
116
 
117
117
  | Stage | Built-in doctrine |
118
118
  |---|---|
119
+ | **any claim read as true** | [`evidence-docs`](plugins/task-pipeline/skills/evidence-docs/SKILL.md) — **a second skill in this plugin**: the ten canons as an index, and where each is defined, enforced and seeded |
119
120
  | 0 Knowledge harvest | [`knowledge-sources.md`](plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md) — source list, the wiki, the ledger, the stage-9 loop-back |
120
121
  | 0 Intake grill | [`grill.md`](plugins/task-pipeline/skills/task-pipeline/references/grill.md) — interview loop, domain awareness, autonomy sweep |
121
122
  | 2 Brainstorm | [`brainstorm.md`](plugins/task-pipeline/skills/task-pipeline/references/brainstorm.md) — approaches, YAGNI, the no-code-before-approval gate |
@@ -238,8 +239,12 @@ then, in the project root:
238
239
 
239
240
  **Stage 0 asks it what grep can't** — `graphify query "how does session reach the
240
241
  API layer"`, `graphify affected "AuthModule"`, `graphify god-nodes` — and records it
241
- in the source ledger **with its build date**, because a graph goes stale exactly like
242
- a doc. It points; the code decides.
242
+ in the source ledger **with its measured lag**, because a graph goes stale exactly
243
+ like a doc. Not a build date: `built 2026-08-05` is the graph's own reply about
244
+ itself, true and self-reported and silent about whether it describes the tree you
245
+ are about to change. The row carries `N commits / M days behind HEAD`, the signal
246
+ that measured it — and, on anything but `current`, `⚠ not trusted for reach until
247
+ refreshed`. It points; the code decides.
243
248
 
244
249
  **Stage 9 closes three artifacts, not two.** Docs, wiki, **and the graph** — in the
245
250
  agent, so the documents this stage just edited are re-extracted too:
package/SKILL-CARD.md CHANGED
@@ -12,7 +12,7 @@ harmless.
12
12
  |---|---|
13
13
  | **Purpose** | Runs a substantial task through ten gated delivery stages — intake grill, docs study, brainstorm, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs+registers, acceptance — refusing to advance until each gate passes |
14
14
  | **Owner** | ssheleg ([github.com/ssheleg/task-pipeline](https://github.com/ssheleg/task-pipeline)) |
15
- | **Version** | 1.14.1 |
15
+ | **Version** | 1.16.0 |
16
16
  | **Surface** | Claude Code (filesystem skill + plugin) and the vercel `skills` CLI. **Not** uploaded to the Skills API; custom Skills do not sync across surfaces |
17
17
  | **Dependencies** | None required. Optional: `context7` (MCP), `figma` (MCP), super-ux, agent-sync, graphify, obsidian-wiki. Every stage's doctrine ships in-repo; the one conditional requirement is super-ux for the stage-3 UX track on a user-facing task |
18
18
  | **Evaluation status** | Suite authored (15 evals, 5 categories). **Never executed** — see [`evals/RESULTS.md`](evals/RESULTS.md) |
@@ -38,7 +38,7 @@ apply.
38
38
  instruction surface, and every one is linked directly from `SKILL.md`.
39
39
  2. Read `templates/docgate.sh` before seeding it; it is the only shipped script a
40
40
  host project will run on its own repository.
41
- 3. Run `npm run test:all` — 95 guards, each with a negative self-test that plants a
41
+ 3. Run `npm run test:all` — every guard has a negative self-test that plants a
42
42
  defect and requires rejection.
43
43
  4. Run `python3 evals/run.py` for the behavioural protocol, and read
44
44
  `evals/RESULTS.md` for what has actually been observed.
@@ -54,7 +54,7 @@ apply.
54
54
  - **Versions are pinned by git tag** and mirrored into `sshlg-skills`'s catalogue.
55
55
  Rollback is `git checkout v<previous>` or pinning the previous plugin version;
56
56
  the previous version is never deleted.
57
- - **Behavioural evidence is missing, not merely thin.** 95 structural guards prove
57
+ - **Behavioural evidence is missing, not merely thin.** The structural guards prove
58
58
  the skill is well-formed. Until `evals/RESULTS.md` carries a dated run, nothing in
59
59
  this repository proves it *behaves* — triggers correctly, stays quiet on a
60
60
  question, or performs the steps it documents.
@@ -86,8 +86,16 @@ you invented — it counts because the project names it). Query each by *this ta
86
86
  nouns; it is retrieval, not a full read; stop when the terms return nothing new.
87
87
  Write a short **source ledger** into the brief — source, what it says about this
88
88
  task, how fresh, and whether this run makes it stale. `none found` is a valid row;
89
- the graph's row carries its build date, because a graph goes stale like any other
90
- source, and it **points while the code decides** never the tiebreaker.
89
+ the graph's row carries its **measured lag**, not a build date run
90
+ `git rev-list --count <built_at_commit>..HEAD` and record `N commits / M days behind
91
+ HEAD` plus the signal it came from (`built_at_commit` exact, file `mtime`
92
+ approximate when the graph carries no stamp, or `unresolvable` when the stamp does
93
+ not resolve here), ending in `⚠ not trusted for reach until refreshed` on anything
94
+ but `current` — that exact string, so one marker is greppable across every
95
+ ledger. A date is the graph's own reply about itself; the lag is the only part
96
+ that says whether it describes the tree you are about to change. A graph goes stale
97
+ like any other source, and it **points while the code decides** — never the
98
+ tiebreaker.
91
99
  **Read `docs/superpowers/retro.md` in full if it exists** — its *standing
92
100
  instructions* are what previous runs of this pipeline got wrong in this project, and
93
101
  they bind this run (they are capped at ten, so reading them all is cheap). Stamp
package/evals/RESULTS.md CHANGED
@@ -37,7 +37,8 @@ or it belongs in a check.
37
37
  | Dated runs recorded | **0** | 2026-08-03 |
38
38
 
39
39
  The bottom two numbers are the honest state of this skill's behavioural evidence.
40
- Everything else in this repository is proven by 95 structural guards that check the
40
+ Everything else in this repository is proven by structural guards the count is
41
+ whatever `npm run test:all` prints, deliberately not restated here — that check the
41
42
  *form*; these are the only checks that would speak to the *behaviour*, and they have
42
43
  not been run yet. Printed here so "63 of 63 green" is never read as "the skill is
43
44
  known to work".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "task-pipeline-skill",
3
- "version": "1.14.1",
3
+ "version": "1.16.0",
4
4
  "description": "Full-cycle delivery pipeline for coding agents: a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine ships inside the skill — no companion plugin required. This package is the installer CLI.",
5
5
  "bin": {
6
6
  "task-pipeline": "bin/task-pipeline.js"
@@ -2,7 +2,7 @@
2
2
  "name": "task-pipeline",
3
3
  "displayName": "Task Pipeline",
4
4
  "description": "Runs a substantial task through a mandatory built-in intake grill, then 10 gated stages (docs, brainstorm+decompose, spec, plan, subagent build, tests, lint/deploy, post-deploy, docs/wiki, acceptance). Every stage's doctrine is built into the skill — no companion plugin required — with typed auto/manual gates, a frozen requirement spine that must close with evidence, a loop guard that breaks churn, one provider-agnostic model confirmed up front, and an optional super-ux UX track for user-facing work.",
5
- "version": "1.14.1",
5
+ "version": "1.16.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: evidence-docs
3
+ description: Applies the ten canons of evidence-backed documentation — what makes a claim documentation rather than an assertion — and routes to the doctrine that enforces each one. Use when writing or reviewing anything that will be read as true: a decision record, a README, an acceptance report, a runbook, a changelog entry, an audit finding, or any claim that something was verified. Also use when a project needs a documentation gate, a decision register, a propagation matrix, or a retrospective that outlives its author. Not for drafts, chat answers, commit messages or code comments.
4
+ ---
5
+
6
+ # Evidence-backed documentation
7
+
8
+ **A claim is documentation only when it carries the means to check it.** This skill is
9
+ the standard and the map: ten canons, and where each is defined, enforced and seeded.
10
+
11
+ It is a **navigator, not a second copy**. Every law below has exactly one home — that is
12
+ canon 3, and a navigator that restated the doctrine would break the rule it is indexing.
13
+ The full statement of each canon, its rationale and its enforcement live in
14
+ [`documentation.md`](../task-pipeline/references/documentation.md) → *The canons*.
15
+
16
+ ## The ten canons
17
+
18
+ 1. **A claim carries its address** — `file:line`, a command with its output, a test name; a lesson names its commit.
19
+ 2. **Numbers are computed, never restated.**
20
+ 3. **Every fact has exactly one home** — others link, never restate.
21
+ 4. **A reference resolves from where the document is read** — not from where it lives.
22
+ 5. **Green nobody watched turn red is not evidence.**
23
+ 6. **A check proves its scope and nothing beyond it.**
24
+ 7. **Silence is not a pass** — ask what a mechanism prints when it did not look.
25
+ 8. **An estimate is never announced as a measurement** — a rule states its evidence condition.
26
+ 9. **What was not checked is printed beside what was.**
27
+ 10. **The document ships in the change that made it true** — and a correction is appended, never written over.
28
+
29
+ They are **epistemic**: what makes a claim documentation. The operational layer — what to
30
+ do at a given trigger, with a check and an exit criterion — is
31
+ [`learned.md`](../task-pipeline/references/learned.md). When the two seem to say the same
32
+ thing, the canon is the *why* and the rule is the *how*.
33
+
34
+ ## Where next
35
+
36
+ | You are about to… | Read | Because |
37
+ |---|---|---|
38
+ | set a project's documentation up from nothing | [`documentation.md`](../task-pipeline/references/documentation.md) → *The inventory* | four questions answered before the first line of work |
39
+ | record a decision so it survives its author | *Registers and ids* + [`templates/decisions.md`](../task-pipeline/templates/decisions.md) | append-only ids, edge markers, one decision home |
40
+ | change something and not orphan the docs | *The Doc Loop* + *The propagation matrix* | which documents a change owes, starting with the meta-row |
41
+ | decide where a fact belongs | *Single source of truth* | two homes disagree the day one of them is updated |
42
+ | build a check that cannot lie | [`gates.md`](../task-pipeline/references/gates.md) | three axes, the enforcement ladder, progressive arming, probing |
43
+ | trust a mechanism that reports success | [`gates.md`](../task-pipeline/references/gates.md) → *False success* | the failure that removes the reason to look |
44
+ | wire a check into the agent's own tooling | [`hooks.md`](../task-pipeline/references/hooks.md) | the hook contract, and why a crashed guard **allows** the action |
45
+ | audit documentation a project already has | [`setup.md`](../task-pipeline/references/setup.md) | seven passes, cheapest first, output is a fix plan |
46
+ | carry a lesson to the next run | [`retrospective.md`](../task-pipeline/references/retrospective.md) | prune first, cap of ten, every lesson names its commit |
47
+ | seed a gate into a host project | [`templates/docgate.sh`](../task-pipeline/templates/docgate.sh) | it seeds **green**: dormant where there is no input yet |
48
+ | take a whole change through to acceptance | [`task-pipeline`](../task-pipeline/SKILL.md) | this skill is the standard; that one is how a change reaches the repository |
49
+
50
+ ## When this applies
51
+
52
+ **The boundary is "it will be read as true."** A decision record, a README, an acceptance
53
+ report, a runbook, a changelog for users, an audit finding, a claim that something was
54
+ verified.
55
+
56
+ **Not through this skill:** a draft, thinking out loud, an answer in chat, a commit
57
+ message, a code comment. Demanding a `file:line` for "let me check that" is the fastest
58
+ way to teach an agent to route around the rule where it actually protects something.
59
+
60
+ **Refusal phrase — "без доков" / "on my word".** It works on a task that would otherwise
61
+ pass through here: do it directly and **say out loud** that the claim is unbacked, rather
62
+ than presenting an estimate as a measurement (canon 8).
63
+
64
+ ## The one test
65
+
66
+ Before a document ships, read it for the sentence that would embarrass you if someone
67
+ asked *"how do you know?"* — and either give that sentence its address, or delete it.
68
+ Everything above is that question, made mechanical.
@@ -47,6 +47,7 @@ gate stops until it is installed.
47
47
  | Stage | Built-in doctrine |
48
48
  |---|---|
49
49
  | 0 + 9 + any settled decision · The documentation system | [`references/documentation.md`](references/documentation.md) |
50
+ | any claim that will be read as true · The canons, and where each is enforced | [`evidence-docs`](../evidence-docs/SKILL.md) — the sibling skill in this plugin |
50
51
  | 6–10 + any check you write · Gates | [`references/gates.md`](references/gates.md) |
51
52
  | any agent-time enforcement · Hooks | [`references/hooks.md`](references/hooks.md) |
52
53
  | 0 Knowledge harvest (pre-grill) | [`references/knowledge-sources.md`](references/knowledge-sources.md) |
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "gate": {
20
20
  "type": "manual",
21
- "check": "MANDATORY stage — never skipped (only sanctioned bypass: the entry-from-super-ux short-circuit). PHASE 1, before the first question: harvest the knowledge sources (references/knowledge-sources.md) — code, THE CODE GRAPH when one is built (references/knowledge-graph.md: graphify query/affected/god-nodes answer reach, which grep cannot; detect graphify-out/graph.json — recommended, never required), CLAUDE.md/AGENTS.md, CONTEXT.md + docs/adr, docs/ + docs/ux, past pipeline briefs and carry-over ledgers, THE RETRO'S STANDING INSTRUCTIONS (docs/superpowers/retro.md — read IN FULL, not queried: they are capped at ten and they BIND this run; stamp each one the moment it fires, since that date is the only evidence behind stage 10's cold-retirement rule — references/retrospective.md), the knowledge wiki when installed (obsidian-wiki — recommended, never required; detect ~/.obsidian-wiki/config), and any other repo or hosted doc system the project names as its docs — queried by this task's own terms, with the SOURCE LEDGER written into the brief (a row per source consulted, or an explicit 'none found'; the graph's row carries its build date, because a graph goes stale like any other source). PHASE 2, the grill, built into the skill (references/grill.md) — no companion to install. Per its contract: one question at a time, a recommended answer with each, explore the codebase/docs before asking, depth-first, contradictions reconciled; EVERY answer that touches a harvested source is validated against that source — the operator outranks any document, but only out loud, and the losing side is logged for the stage-9 doc update; domain awareness applied (terms challenged against CONTEXT.md, ADRs recorded for hard-to-reverse calls). The autonomy sweep is covered — every stage 1-10 has its blockers pre-resolved (docs sources, branch/tracker policy, test + lint commands, deploy target and authorization, log/health locations, docs+wiki targets, and for UI tasks the design surface: Figma on or text-only, is the Figma MCP connected, and if it is not — ship text-only or stop and connect it, since the UX chain degrades on its own and never blocks; plus, with Figma on, the DESIGN DESTINATION — which team/org by name and which file (the recorded one, a URL the operator gives, or creation in that named team with the creation explicitly authorized), written into the project's canonical record before the first frame, and never created while a recorded file resolves — an unreachable recorded file means stop and ask, never make a replacement) or is explicitly marked 'stop and ask here'. UI verdict recorded (arms super-ux); model decision recorded. All of it locked into a committed task brief the operator confirms before stage 1. The REQ table is written — one row per independently verifiable deliverable, each naming how it is verified — and frozen: adding later is free, removing or narrowing needs the operator's explicit agreement. The carry-over ledger is seeded. PHASE 1b, THE DOCUMENTATION INVENTORY (references/documentation.md): four questions answered into docs/DOCMAP.md before the interview — where settled things live (the DECISION HOME, and there is exactly one per project: an existing docs/adr/ IS the register and is recorded as such, never duplicated), what each fact's single home is, what a change of type X obliges (THE PROPAGATION MATRIX, non-empty, every row naming the check that enforces it or the word 'review' with a one-line reason), and what proves it (the gate command). A project with no answers gets them seeded — registers, matrix and scripts/check-docs.sh from the skill's templates — and the seeding is recorded as the register's first entry; the seeded gate must exit 0 on its own seeds, because a project that starts red teaches everyone on day one that the gate is noise. The regime is recorded. PHASE 1c, RECONCILE: git says how it should be, the run record says how it turned out — read both for the area about to be touched and resolve every divergence (the document is stale, the record is wrong, or they genuinely disagree and that is a decision), because starting on an unresolved divergence means building against a system that does not exist. The retro's in-force sections are read IN FULL and its archive is QUERIED by the task's nouns."
21
+ "check": "MANDATORY stage — never skipped (only sanctioned bypass: the entry-from-super-ux short-circuit). PHASE 1, before the first question: harvest the knowledge sources (references/knowledge-sources.md) — code, THE CODE GRAPH when one is built (references/knowledge-graph.md: graphify query/affected/god-nodes answer reach, which grep cannot; detect graphify-out/graph.json — recommended, never required), CLAUDE.md/AGENTS.md, CONTEXT.md + docs/adr, docs/ + docs/ux, past pipeline briefs and carry-over ledgers, THE RETRO'S STANDING INSTRUCTIONS (docs/superpowers/retro.md — read IN FULL, not queried: they are capped at ten and they BIND this run; stamp each one the moment it fires, since that date is the only evidence behind stage 10's cold-retirement rule — references/retrospective.md), the knowledge wiki when installed (obsidian-wiki — recommended, never required; detect ~/.obsidian-wiki/config), and any other repo or hosted doc system the project names as its docs — queried by this task's own terms, with the SOURCE LEDGER written into the brief (a row per source consulted, or an explicit 'none found'; THE GRAPH'S ROW CARRIES ITS MEASURED LAG — commits and days behind HEAD, the signal it was measured with (built_at_commit exact / mtime approximate / unresolvable), and the marker ' not trusted for reach until refreshed' — that exact string, so one marker is greppable across every ledger — on anything but current. A bare build date does NOT satisfy this: it is the graph's own reply about itself, true and self-reported and silent about whether the graph describes the tree this run is about to change — references/knowledge-graph.md -> Measure the lag, and references/gates.md -> False success for the class). PHASE 2, the grill, built into the skill (references/grill.md) — no companion to install. Per its contract: one question at a time, a recommended answer with each, explore the codebase/docs before asking, depth-first, contradictions reconciled; EVERY answer that touches a harvested source is validated against that source — the operator outranks any document, but only out loud, and the losing side is logged for the stage-9 doc update; domain awareness applied (terms challenged against CONTEXT.md, ADRs recorded for hard-to-reverse calls). The autonomy sweep is covered — every stage 1-10 has its blockers pre-resolved (docs sources, branch/tracker policy, test + lint commands, deploy target and authorization, log/health locations, docs+wiki targets, and for UI tasks the design surface: Figma on or text-only, is the Figma MCP connected, and if it is not — ship text-only or stop and connect it, since the UX chain degrades on its own and never blocks; plus, with Figma on, the DESIGN DESTINATION — which team/org by name and which file (the recorded one, a URL the operator gives, or creation in that named team with the creation explicitly authorized), written into the project's canonical record before the first frame, and never created while a recorded file resolves — an unreachable recorded file means stop and ask, never make a replacement) or is explicitly marked 'stop and ask here'. UI verdict recorded (arms super-ux); model decision recorded. All of it locked into a committed task brief the operator confirms before stage 1. The REQ table is written — one row per independently verifiable deliverable, each naming how it is verified — and frozen: adding later is free, removing or narrowing needs the operator's explicit agreement. The carry-over ledger is seeded. PHASE 1b, THE DOCUMENTATION INVENTORY (references/documentation.md): four questions answered into docs/DOCMAP.md before the interview — where settled things live (the DECISION HOME, and there is exactly one per project: an existing docs/adr/ IS the register and is recorded as such, never duplicated), what each fact's single home is, what a change of type X obliges (THE PROPAGATION MATRIX, non-empty, every row naming the check that enforces it or the word 'review' with a one-line reason), and what proves it (the gate command). A project with no answers gets them seeded — registers, matrix and scripts/check-docs.sh from the skill's templates — and the seeding is recorded as the register's first entry; the seeded gate must exit 0 on its own seeds, because a project that starts red teaches everyone on day one that the gate is noise. The regime is recorded. PHASE 1c, RECONCILE: git says how it should be, the run record says how it turned out — read both for the area about to be touched and resolve every divergence (the document is stale, the record is wrong, or they genuinely disagree and that is a decision), because starting on an unresolved divergence means building against a system that does not exist. The retro's in-force sections are read IN FULL and its archive is QUERIED by the task's nouns."
22
22
  }
23
23
  },
24
24
  {
@@ -18,6 +18,7 @@ being written twice; write it once, here.
18
18
 
19
19
  ## Contents
20
20
 
21
+ - The canons — what makes a document evidence
21
22
  - The inventory — four questions, answered before the first line of work
22
23
  - Registers and ids
23
24
  - Single source of truth
@@ -30,6 +31,71 @@ being written twice; write it once, here.
30
31
  - Where this binds in the pipeline
31
32
  - Rationalizations
32
33
 
34
+ ## The canons — what makes a document evidence
35
+
36
+ Ten laws. Everything else in this file, and the mechanisms in [`gates.md`](gates.md),
37
+ [`learned.md`](learned.md) and [`retrospective.md`](retrospective.md), exist to serve
38
+ them. Where a canon is already enforced by something, the enforcement is **named, not
39
+ restated** — a canon that repeats its own mechanism is the second home this system
40
+ exists to prevent.
41
+
42
+ **1. A claim carries its address.** Every fact that lands in a document names where it
43
+ can be checked: `file:line`, a command with its output, a test name. A lesson names the
44
+ commit that earned it. *"We verified it" is the sentence that passes every review and
45
+ proves nothing.* → the retro's SHA-resolution guard; the finding shape in
46
+ [`setup.md`](setup.md).
47
+
48
+ **2. Numbers are computed, never restated.** A count in prose is a number that was true
49
+ once. Derive it at check time and compare the stated one against the computed one as the
50
+ same object. → [`learned.md`](learned.md) rule 8.
51
+
52
+ **3. Every fact has exactly one home.** Other documents link to it; they never restate
53
+ it. Two homes do not disagree on the day they are written — they disagree on the day one
54
+ of them is updated. → *Single source of truth*, below.
55
+
56
+ **4. A reference resolves from where the document is read.** Not from where it lives. A
57
+ link correct in `templates/` and broken everywhere the template is seeded stays green
58
+ under every link checker, because the checker resolves from the file's home. → invariant
59
+ 27; the seeded-template guard.
60
+
61
+ **5. Green nobody watched turn red is not evidence.** A check must be seen rejecting a
62
+ planted defect before its pass means anything — and the plant must be proven to have
63
+ landed in the text the check actually parses. → [`gates.md`](gates.md) → *Probing*;
64
+ [`learned.md`](learned.md) rules 4 and 5.
65
+
66
+ **6. A check proves its scope and nothing beyond it.** Every gate carries what it does
67
+ **not** cover, and quoting it wider is how "the gate is green" becomes a false statement
68
+ made in good faith. "The docs are in sync" is a command with an exit code, never a
69
+ sentence at the end of a report. → [`gates.md`](gates.md) → *Before you run a check*.
70
+
71
+ **7. Silence is not a pass.** Ask of any mechanism: what does it print when it did not
72
+ look? If that is indistinguishable from what it prints when it looked and found nothing
73
+ wrong, it is not evidence. → [`gates.md`](gates.md) → *False success*.
74
+
75
+ **8. An estimate is never announced as a measurement.** A rule that fires on a judgement
76
+ states its **evidence condition** — the observable signal that licenses it. A false
77
+ alarm does not cost one interruption; it costs the alarm. → [`continuity.md`](continuity.md)
78
+ → *The context budget*.
79
+
80
+ **9. What was not checked is printed beside what was.** Absence is a finding with one
81
+ side, so it never surfaces by comparison. Carry it as a named, counted set next to every
82
+ verdict — a ratchet, never a TODO — so `PASS` reads as *"green, and here is what nobody
83
+ looked at"*. → [`learned.md`](learned.md) rule 7; [`gates.md`](gates.md) → *Ratchets*.
84
+
85
+ **10. The document ships in the change that made it true.** Not in the next ticket —
86
+ documentation deferred is documentation that describes a system nobody is running. A
87
+ correction is **appended**, never edited over: a register that is rewritten loses the
88
+ fact that it was ever wrong, which is usually the useful part. → *The Doc Loop*, below.
89
+
90
+ ### What these are not
91
+
92
+ They are **epistemic**, not operational: they say what makes a claim documentation, not
93
+ what to do at a trigger. The operational layer is [`learned.md`](learned.md) — each rule
94
+ there carries a trigger, a check and an exit criterion. When the two seem to say the same
95
+ thing, the canon is the *why* and the rule is the *how*; edit the rule, cite the canon.
96
+
97
+ ---
98
+
33
99
  ## The inventory — four questions, answered before the first line of work
34
100
 
35
101
  Stage 0 answers these before the interview, and writes the answers to
@@ -19,6 +19,7 @@ harvest simply runs on the sources it has
19
19
 
20
20
  - Detect it, and install it once
21
21
  - Stage 0 — query the graph before you ask the person
22
+ - Measure the lag — a build date is a reply, not a measurement
22
23
  - Stage 9 — the close-out has three artifacts, not two
23
24
  - The divergence check — the graph against the docs
24
25
  - Rationalizations
@@ -78,10 +79,68 @@ Two rules keep it honest:
78
79
  belongs in the retrieval order right after the code, and never as the tiebreaker.
79
80
  Precedence, reflowed so it reads in one direction: code first, then host docs and
80
81
  ADRs, then the graph, then the wiki, then anyone's memory.
81
- - **Record it in the ledger with its build date** — source
82
- `graphify-out/graph.json`, what it said about this task, how fresh, and therefore
83
- whether stage 9 owes it a refresh. A source consulted but not recorded is a source
84
- nobody will update.
82
+ - **Record it in the ledger with its measured lag** — source
83
+ `graphify-out/graph.json`, what it said about this task, **how far behind `HEAD` it
84
+ actually is**, and therefore whether stage 9 owes it a refresh. A source consulted
85
+ but not recorded is a source nobody will update. How to measure it: next section.
86
+
87
+ ## Measure the lag — a build date is a reply, not a measurement
88
+
89
+ [`gates.md`](gates.md) → *False success* gives the law — *an actor's own reply is not
90
+ evidence about the world* — and the test: **what does it print when it did not look?**
91
+
92
+ A build date is that kind of reply. `built 2026-08-05` is true, self-reported, and
93
+ says nothing about whether the graph describes the tree this run is about to change.
94
+ Twelve commits later it still reads `built 2026-08-05`, and it still reads *fresh* —
95
+ a date with nothing subtracted from it is a fact with no scale. So the `Fresh?` cell
96
+ of the graph's ledger row carries a **measured distance from `HEAD`**, the **signal it
97
+ was measured with**, and — while that distance is not zero — a refusal to lean on it.
98
+
99
+ **Compute it; never type it** ([`learned.md`](learned.md) rule 8). Three commands, and
100
+ the first one's failure *is* a state rather than an error to handle:
101
+
102
+ ```bash
103
+ git rev-parse --verify -q "<built_at_commit>^{commit}" # which state applies
104
+ git rev-list --count "<built_at_commit>..HEAD" # commits behind
105
+ git log -1 --format=%ct "<built_at_commit>" # its timestamp → days behind
106
+ ```
107
+
108
+ `<built_at_commit>` is read from the top level of `graph.json`. **It is written only
109
+ when the caller passed it**, and `graphify update .` from the CLI does not — so the
110
+ absence of a stamp is normal, not a defect, and it gets its own state instead of
111
+ silence. Three states, each naming its signal — the same reason
112
+ [`gates.md`](gates.md) → *Progressive arming* gives a dormant check a word of its own:
113
+
114
+ | State | Condition | The `Fresh?` cell reads |
115
+ |---|---|---|
116
+ | **exact** | stamp present and resolves here | ``built `3944593` — 12 commits / 2d behind HEAD, signal: built_at_commit (exact) — ⚠ not trusted for reach until refreshed`` |
117
+ | **approximate** | no stamp in `graph.json` | ``built ≤ 2026-08-05T22:47Z — signal: file mtime (approximate; no commit stamp, so this is a lower bound) — ⚠ not trusted for reach until refreshed`` |
118
+ | **unresolvable** | stamp present, does not resolve here (rebase, squash, shallow clone) | ``built `3944593` — UNRESOLVABLE in this checkout, signal: none — ⚠ not trusted for reach until refreshed`` |
119
+
120
+ Every row above is a **non-current** graph, so every row ends with the marker. The
121
+ cell is quoted whole on purpose: a worked example is what gets copied, and an example
122
+ that omits the thing it exists to demonstrate teaches the omission.
123
+
124
+ **State zero out loud.** A current graph reads ``built `3944593` — current (0 commits
125
+ behind), signal: built_at_commit (exact)``. Printing nothing when the graph is fresh
126
+ is what makes freshness indistinguishable from a harvest that never looked, which is
127
+ the failure this section cites `gates.md` for.
128
+
129
+ **Anything but `current` ends the cell with `⚠ not trusted for reach until
130
+ refreshed`.** The marker does not block — stage 0's gate is `manual` and this is a
131
+ ledger row, not a gate. It tells the harvest what it may lean on; the run continues,
132
+ it just stops quoting the graph as though it were current.
133
+
134
+ **No threshold, deliberately.** [`continuity.md`](continuity.md) refused a
135
+ context-budget number for the same reason: an unmeasurable threshold becomes
136
+ unconditional doctrine, not config. "Ten commits is fine, eleven is not" is a number
137
+ nobody can defend, and a number nobody can defend is one every run argues its way
138
+ under — one commit that moved the function this task is about outweighs fifty that
139
+ touched a README.
140
+
141
+ **Promote it when it breaks, not before** ([`gates.md`](gates.md) → *Axis B*): this
142
+ rule sits at rung 2, a criterion in stage 0's gate. Promote it to a script the first
143
+ time a run is observed passing stage 0 with an unmeasured graph row.
85
144
 
86
145
  ## Stage 9 — the close-out has three artifacts, not two
87
146
 
@@ -160,7 +219,8 @@ sweep on every commit turns an audit axis into noise and it stops being read.
160
219
  | Excuse | Reality |
161
220
  |---|---|
162
221
  | "I'll just grep, it's faster" | Grep answers "where is this name". The questions that stop a run are "what reaches this" and "what breaks if it moves" — that is one query against a graph and an afternoon with grep. |
163
- | "The graph is probably stale" | Then it has a build date and you can say so. Stale-and-dated is a finding; stale-and-unknown is what you get by not building one. |
222
+ | "The graph is probably stale" | Then measure it and say how stale — three commands, above. *Probably* is the word a run uses right before leaning on it anyway; `12 commits / 2d behind HEAD` is a finding, and `built 2026-08-05` on its own is only a date. |
223
+ | "It says built today, that's fresh enough" | Today's date and today's `HEAD` are different claims. The date is the graph's own reply about itself; the lag is the one thing that says whether it describes the tree you are about to change. |
164
224
  | "Docs and wiki are updated, we're done" | The graph is the source the *next* harvest reads first. Leaving it behind is leaving a false premise where a machine will quote it back. |
165
225
  | "The divergence check found nothing, skip it next time" | It found nothing **on this axis, this pass**. Rotate the axis; that is the rule this check belongs to, not an exemption from it. |
166
226
  | "A hub with no doc is fine, everyone knows it" | Everyone currently on the team. The graph found it in one command; the person who joins next month will find it in a postmortem. |
@@ -94,8 +94,11 @@ the directory doesn't (installed, not built).
94
94
 
95
95
  - **Built → query it** during the harvest: `graphify query "<the task, as a
96
96
  question>"`, `graphify affected "<the thing being changed>"`, `graphify god-nodes`.
97
- Record the row **with the graph's build date**, because a graph is a source and
98
- goes stale like one.
97
+ Record the row **with the graph's measured lag** how far behind `HEAD` it is and
98
+ which signal said so — because a graph is a source and goes stale like one, and a
99
+ build date is the graph's own reply about itself rather than a measurement of it.
100
+ The three commands and the three states:
101
+ [`knowledge-graph.md`](knowledge-graph.md) → *Measure the lag*.
99
102
  - **Not installed → recommend it once**, in the preflight block, with the lines:
100
103
 
101
104
  ```bash
@@ -67,12 +67,14 @@ a row pointing outside the bundle is the defect this file exists to catch.
67
67
  | Workspace isolation, the subagent loop, who may write the register | `references/build.md` |
68
68
  | The review rubric, diff packages, the three verdicts | `references/review.md` |
69
69
  | **False success** — the class, its known shapes and its two rules | `references/gates.md` |
70
+ | **The canons** — what makes a claim documentation, and the index that routes to each | `references/documentation.md`, `../evidence-docs/SKILL.md` |
70
71
  | **Effect verification** — the `verified-by:` contract and the rubric item that blocks | `references/build.md`, `references/review.md` |
71
72
  | The TDD iron law and the suite gate | `references/tdd.md` |
72
73
  | The REQ coverage table, evidence rules, the closing question | `references/acceptance.md` |
73
74
  | How the host project's own conventions are read | `references/conventions.md` |
74
75
  | Which companions exist, what is required, self-currency | `references/companion-skills.md` |
75
76
  | The code graph: queries, refresh, the graph↔docs divergence | `references/knowledge-graph.md` |
77
+ | How the graph's staleness is measured and stated in the ledger | `references/knowledge-graph.md` |
76
78
  | Model policy — tier not id, ask once at preflight | `references/model-tiering.md` |
77
79
  | This boundary | `references/portability.md` |
78
80
 
@@ -93,7 +93,11 @@ never that the work was skipped quietly.
93
93
  ([obsidian-wiki](https://github.com/ar9av/obsidian-wiki) — recommended,
94
94
  never required), and any **other repo or hosted doc system the project names as
95
95
  its docs**. Write the **source ledger** into the brief (a row per source, or an
96
- explicit "none found"; the graph's row carries its build date). It is retrieval
96
+ explicit "none found"; **the graph's row carries its measured lag commits and
97
+ days behind `HEAD`, the signal that measured it, and `⚠ not trusted for reach
98
+ until refreshed` on anything but `current`**
99
+ ([`knowledge-graph.md`](knowledge-graph.md) → *Measure the lag*; a build date is
100
+ the graph's own reply about itself, not a measurement of it). It is retrieval
97
101
  scoped by the task's own nouns, not a read of everything — and it is what makes
98
102
  phase 2's answers checkable instead of merely confident.
99
103
  - **Phase 1b — the documentation inventory**
@@ -152,7 +156,11 @@ never that the work was skipped quietly.
152
156
  an updated `CONTEXT.md` (terms written as they resolved) and any ADRs under
153
157
  `docs/adr/` — see `grill.md` → *Domain awareness*.
154
158
  - **GATE (manual):** shared understanding reached — **the source ledger is written
155
- (every source consulted, or an explicit "none found")**, **the documentation
159
+ (every source consulted, or an explicit "none found")**, **where a code graph
160
+ exists its row carries the measured lag and the signal it was measured with — a
161
+ bare build date does not satisfy this, because it is the graph's own reply rather
162
+ than a measurement of it** ([`knowledge-graph.md`](knowledge-graph.md) →
163
+ *Measure the lag*), **the documentation
156
164
  inventory is answered into `docs/DOCMAP.md`** with its registers, single homes,
157
165
  a non-empty propagation matrix and the gate command, **the regime is recorded**,
158
166
  **intent and as-built are reconciled with every divergence resolved**, the retro's
@@ -31,7 +31,7 @@ premise if the run leaves it wrong.
31
31
  | Source | What it says about this task | Fresh? | Authority | Stale after this run? |
32
32
  |---|---|---|---|---|
33
33
  | `docs/adr/NNNN-….md` | … | YYYY-MM | decision | no |
34
- | `graphify-out/graph.json` | reach: what calls it, what breaks if it moves | built YYYY-MM-DD | index | **yes — refresh at stage 9** |
34
+ | `graphify-out/graph.json` | reach: what calls it, what breaks if it moves | built `<sha>` — N commits / M days behind HEAD, signal: built_at_commit (exact) — ⚠ not trusted for reach until refreshed | index | **yes — refresh at stage 9** |
35
35
  | wiki: `projects/…/concepts/…` | … | YYYY-MM | context | **yes — update at stage 9** |
36
36
  | `CLAUDE.md` | test/lint/deploy commands, house rules | current | convention | no |
37
37
 
@@ -66,7 +66,8 @@ source is a recorded decision, an unquoted one is an undetected divergence.
66
66
  what it returned: … (or `nothing`)
67
67
  - **Code graph:** built / installed-not-built / not installed
68
68
  ([graphify](https://github.com/Graphify-Labs/graphify); recommended, never a gate —
69
- built → its row above carries the build date and stage 9 refreshes it)
69
+ built → its row above carries the **measured lag and the signal it was measured
70
+ with**, and stage 9 refreshes it)
70
71
 
71
72
  ## Scope
72
73
 
@@ -7,7 +7,8 @@
7
7
  # WHAT IT IS FOR: the defect class an AGENT produces and no other check looks for —
8
8
  # a half-resolved merge, a stub that outlived its task, a generation cut off in the
9
9
  # middle, a file "shortened" while being rewritten, a batch of edits where one
10
- # applied twice, a section opened and abandoned.
10
+ # applied twice, a section opened and abandoned, a blank line left in a table so
11
+ # every row below it silently stops being one.
11
12
  #
12
13
  # SCOPE: walks tracked files. In diff mode it walks only what this run changed; in
13
14
  # tree mode it walks everything, behind per-check floors.
@@ -26,9 +27,11 @@
26
27
  # 5 duplicated block — a legitimately repeated stanza 3+ lines long
27
28
  # 6 empty section — a heading used as a one-line record. If you have those,
28
29
  # they want to be list items: a heading promises a section.
30
+ # 7 split table — a document deliberately showing a broken table as an
31
+ # example of the defect.
29
32
  # Read this header before quoting a green from here as evidence.
30
33
  #
31
- # IT NEVER EDITS. It reports file:line and exits non-zero. None of the six is safely
34
+ # IT NEVER EDITS. It reports file:line and exits non-zero. None of them is safely
32
35
  # machine-fixable: deleting a "duplicated block" sometimes deletes a legitimate
33
36
  # repetition, and deleting a TODO erases a reminder instead of discharging it.
34
37
  # Fixing is the agent's job, and task-pipeline's references/build.md makes it one.
@@ -36,6 +39,8 @@
36
39
  # EXIT CODE IS THE OUTPUT: non-zero on any failure. Nothing may run after the
37
40
  # VERDICT block at the bottom — a gate that appended a check after its verdict
38
41
  # printed FAIL and returned 0, and CI was green over it for an unknown period.
42
+ # For the same reason, an UNDECLARED FLOOR is a failure, not a zero: check 7
43
+ # shipped without HYGIENE_FLOOR_7 and printed "ok … (floor )" over 3 real hits.
39
44
  #
40
45
  # PORTABLE to macOS bash 3.2: no grep -P, no sed -i, no readarray, no mapfile.
41
46
  #
@@ -62,6 +67,7 @@ HYGIENE_FLOOR_3=${HYGIENE_FLOOR_3:-0}
62
67
  HYGIENE_FLOOR_4=${HYGIENE_FLOOR_4:-0}
63
68
  HYGIENE_FLOOR_5=${HYGIENE_FLOOR_5:-0}
64
69
  HYGIENE_FLOOR_6=${HYGIENE_FLOOR_6:-0}
70
+ HYGIENE_FLOOR_7=${HYGIENE_FLOOR_7:-0}
65
71
 
66
72
  TMP=${TMPDIR:-/tmp}/hygiene.$$
67
73
  mkdir -p "$TMP" || exit 2
@@ -117,6 +123,12 @@ floor_for() {
117
123
 
118
124
  judge() { # judge <n> <count> <label>
119
125
  _f=$(floor_for "$1")
126
+ # An undeclared floor made this print "ok … (floor )" while the count was 3 — the
127
+ # gate reporting a pass it never computed. Refuse rather than default to zero: a
128
+ # missing floor is a bug in the gate, and a bug in the gate is not a passing file.
129
+ case "$_f" in
130
+ ''|*[!0-9]*) err "check $1 — $3: no floor declared (HYGIENE_FLOOR_$1) — the gate cannot judge $2 finding(s)"; return ;;
131
+ esac
120
132
  if [ "$2" -gt "$_f" ]; then
121
133
  err "check $1 — $3: $2 finding(s), floor $_f"
122
134
  sed 's/^/ /' "$TMP/hits$1"
@@ -127,7 +139,7 @@ judge() { # judge <n> <count> <label>
127
139
 
128
140
  if [ "$NFILES" -eq 0 ]; then
129
141
  dormant "every check — no files in scope"
130
- C1=0; C2=0; C3=0; C4=0; C5=0; C6=0
142
+ C1=0; C2=0; C3=0; C4=0; C5=0; C6=0; C7=0
131
143
  else
132
144
 
133
145
  # ---------- 1. conflict markers ----------
@@ -225,12 +237,41 @@ else
225
237
  judge 6 "$C6" "empty section"
226
238
  fi
227
239
 
240
+ # ---------- 7. a blank line inside a table ----------
241
+ # A GFM table ends at the first blank line. An agent appending rows to a ledger or a
242
+ # decision register routinely leaves one behind, and the rows after it silently stop
243
+ # being a table: they render as pipe-delimited prose with no header. Nothing else
244
+ # here sees it — the file is well-formed markdown, every row is present, and the
245
+ # defect is invisible in a diff that shows only the added lines.
246
+ # Found twice in one run (a carry-over ledger and a brief's decision table), which is
247
+ # what turns a finding into a check rather than a third ledger row.
248
+ : > "$TMP/hits7"
249
+ if [ "$NMD" -eq 0 ]; then
250
+ dormant "check 7 — blank line inside a table: no markdown in scope"
251
+ C7=0
252
+ else
253
+ while read -r f; do
254
+ awk -v F="$f" '
255
+ /^[[:space:]]*(```|~~~)/ { fence = !fence; next }
256
+ fence { next }
257
+ { line[NR] = $0 }
258
+ END {
259
+ for (i = 2; i < NR; i++)
260
+ if (line[i] == "" && line[i-1] ~ /^\|/ && line[i+1] ~ /^\|/)
261
+ print F ":" i ": blank line inside a table — the rows below it stop being a table"
262
+ }
263
+ ' "$f" >> "$TMP/hits7" 2>/dev/null
264
+ done < "$TMP/md"
265
+ C7=$(wc -l < "$TMP/hits7" | tr -d ' ')
266
+ judge 7 "$C7" "blank line inside a table"
267
+ fi
268
+
228
269
  fi
229
270
 
230
271
  # ---------- VERDICT — nothing may run after this block ----------
231
272
  if [ "$FAIL" -ne 0 ]; then
232
- echo "FAIL: hygiene gate — mode $MODE · conflict ${C1} · placeholder ${C2} · fence ${C3} · truncation ${C4} · duplicate ${C5} · empty-section ${C6}"
273
+ echo "FAIL: hygiene gate — mode $MODE · conflict ${C1} · placeholder ${C2} · fence ${C3} · truncation ${C4} · duplicate ${C5} · empty-section ${C6} · split-table ${C7}"
233
274
  exit 1
234
275
  fi
235
- echo "OK: hygiene gate — mode $MODE · $NFILES file(s) · conflict ${C1} · placeholder ${C2} · fence ${C3} · truncation ${C4} · duplicate ${C5} · empty-section ${C6}"
276
+ echo "OK: hygiene gate — mode $MODE · $NFILES file(s) · conflict ${C1} · placeholder ${C2} · fence ${C3} · truncation ${C4} · duplicate ${C5} · empty-section ${C6} · split-table ${C7}"
236
277
  exit 0