task-pipeline-skill 1.14.0 → 1.15.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 +139 -0
- package/CONTRIBUTING.md +11 -1
- package/README.md +6 -2
- package/SKILL-CARD.md +3 -3
- package/cursor/rules/task-pipeline.mdc +10 -2
- package/evals/RESULTS.md +2 -1
- package/package.json +1 -1
- package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/pipeline.example.json +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/documentation.md +66 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/knowledge-graph.md +65 -5
- package/plugins/task-pipeline/skills/task-pipeline/references/knowledge-sources.md +5 -2
- package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +10 -2
- package/plugins/task-pipeline/skills/task-pipeline/templates/brief.md +3 -2
- package/plugins/task-pipeline/skills/task-pipeline/templates/hygiene.sh +46 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,144 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.15.0 — 2026-08-06
|
|
4
|
+
|
|
5
|
+
### Added — the ten canons: what makes a document evidence
|
|
6
|
+
|
|
7
|
+
The doctrine carried the mechanisms and never stated the standard they serve.
|
|
8
|
+
`references/documentation.md` now opens with ten laws — a claim carries its address,
|
|
9
|
+
numbers are computed rather than restated, one home per fact, a reference resolves from
|
|
10
|
+
where the document is *read*, green nobody watched turn red is not evidence, a check
|
|
11
|
+
proves its scope and nothing beyond it, silence is not a pass, an estimate is never
|
|
12
|
+
announced as a measurement, what was not checked is printed beside what was, and the
|
|
13
|
+
document ships in the change that made it true.
|
|
14
|
+
|
|
15
|
+
Each canon **names where it is enforced** instead of restating the mechanism, and the
|
|
16
|
+
boundary against `learned.md` is written down: the canons are epistemic (what makes a
|
|
17
|
+
claim documentation), `learned.md` is operational (what to do at a trigger). Two
|
|
18
|
+
undifferentiated rule lists would be the duplication canon 3 forbids.
|
|
19
|
+
|
|
20
|
+
Guarded four ways: the list exists, all ten laws are present, every canon names an
|
|
21
|
+
enforcement, and the boundary is stated. The enforcement check counts **per canon**
|
|
22
|
+
rather than in total — a total threshold only fires once most of them are gone, which
|
|
23
|
+
is a check proving less than it claims, canon 6 applied to itself.
|
|
24
|
+
|
|
25
|
+
**Found while writing them, by this repository's own guard:** canon 9 cited
|
|
26
|
+
`audit.md → Ratchets`, a section that lives in `gates.md`. The file resolved and the
|
|
27
|
+
section did not — the shape a link checker cannot catch and a reader believes.
|
|
28
|
+
|
|
29
|
+
### Added — a build date is the graph's reply about itself, not a measurement of it
|
|
30
|
+
|
|
31
|
+
Stage 0 reads the code graph before it reads anything else, and until now it recorded
|
|
32
|
+
that graph's freshness as `built YYYY-MM-DD`. That is true, self-reported, and silent
|
|
33
|
+
about the only thing the harvest needs to know: whether the graph describes the tree
|
|
34
|
+
this run is about to change. Twelve commits later the row still reads `built
|
|
35
|
+
2026-08-05` — and still reads *fresh*, because a date with nothing subtracted from it
|
|
36
|
+
is a fact with no scale. It is the class `references/gates.md` named one release ago:
|
|
37
|
+
an actor's own reply, standing in for evidence about the world.
|
|
38
|
+
|
|
39
|
+
The occasion was this repository's own. `docs/superpowers/specs/` holds a brief, a
|
|
40
|
+
carry-over ledger and an acceptance document for v1.11.0, v1.12.0 and v1.13.0 — and
|
|
41
|
+
none for v1.14.0 or v1.14.1. Those two releases never opened stage 9, so nothing
|
|
42
|
+
refreshed the graph, and the next harvest would have read a two-release-old index
|
|
43
|
+
behind a date that looked current.
|
|
44
|
+
|
|
45
|
+
- **`references/knowledge-graph.md` gains *Measure the lag***: three commands
|
|
46
|
+
(`git rev-parse --verify`, `git rev-list --count`, `git log -1 --format=%ct`) so
|
|
47
|
+
the number comes from git rather than from judgement, and **three states** —
|
|
48
|
+
`built_at_commit` exact, file `mtime` approximate, and `unresolvable` — because
|
|
49
|
+
`graph.json` carries the commit stamp **only when the caller passed it** and
|
|
50
|
+
`graphify update` from the CLI does not. With one state, "could not measure" would
|
|
51
|
+
print like "fresh", which is the failure the section cites `gates.md` for. Zero is
|
|
52
|
+
stated out loud for the same reason.
|
|
53
|
+
- **No threshold, deliberately.** `continuity.md` refused a context-budget number on
|
|
54
|
+
the same grounds: an unmeasurable threshold becomes unconditional doctrine, not
|
|
55
|
+
config. One commit that moved the function this task is about outweighs fifty that
|
|
56
|
+
touched a README, so anything but `current` carries `⚠ not trusted for reach until
|
|
57
|
+
refreshed` — a marker, not a block. The graph is recommended everywhere else in this
|
|
58
|
+
bundle; a blocking staleness check would make *no graph* cheaper than a week-old one.
|
|
59
|
+
- **The cadence is untouched.** Stage 9 already required the refresh unconditionally,
|
|
60
|
+
and an `always | major | manual` mode was rejected rather than deferred: `major`
|
|
61
|
+
schedules exactly the state the doctrine warns about — a graph confidently wrong
|
|
62
|
+
between releases, read first by the next run.
|
|
63
|
+
|
|
64
|
+
### Added — hygiene check 7: a blank line inside a table
|
|
65
|
+
|
|
66
|
+
A GFM table ends at the first blank line, so a blank line left mid-table silently
|
|
67
|
+
demotes every row below it to pipe-delimited prose. The file stays well-formed, every
|
|
68
|
+
row is still present, and a diff showing only added lines shows nothing wrong. It
|
|
69
|
+
happened **twice inside this run** — a carry-over ledger and the brief's decision
|
|
70
|
+
table — which is `audit.md`'s threshold for a mechanism rather than a third ledger
|
|
71
|
+
row. On its first armed pass the check found **three more** in the carry-over ledgers
|
|
72
|
+
of v1.12.0 and v1.13.0, which had been rendering broken since the day they were
|
|
73
|
+
written. All three are fixed rather than baselined behind a floor.
|
|
74
|
+
|
|
75
|
+
The check shipped with `HYGIENE_FLOOR_7` undeclared, so `judge()` compared against an
|
|
76
|
+
empty string and printed `ok: check 7 … 3 (floor )` over three real hits — the gate
|
|
77
|
+
reporting a pass it never computed, on the release about exactly that. An undeclared
|
|
78
|
+
floor is now a failure, not a zero, and the reason is written beside it.
|
|
79
|
+
|
|
80
|
+
### Guards — 95 → 104, by extending a sibling rather than copying it
|
|
81
|
+
|
|
82
|
+
`test/validate.py` already enforced that the code-graph doctrine be named in **both**
|
|
83
|
+
halves of stage 9 — the config gate the orchestrator verifies and the section an agent
|
|
84
|
+
reads — because a file that "reads as law while the run never does it" is an inert
|
|
85
|
+
gate. Standing instruction R-003 requires running a fixed defect's definition against
|
|
86
|
+
its siblings, and stage 0 is the sibling: same doctrine file, one duty reading the
|
|
87
|
+
graph where the other refreshes it. So the existing guard was **extended**, not
|
|
88
|
+
duplicated.
|
|
89
|
+
|
|
90
|
+
At stage 0 the word *graph* is not the test — it was already there. The guard requires
|
|
91
|
+
the **measured lag**, and three more checks keep the cited section honest: the commands
|
|
92
|
+
must survive, all three states must survive, and `templates/brief.md` may not ship the
|
|
93
|
+
superseded bare-date row to every project scaffolded from it.
|
|
94
|
+
|
|
95
|
+
The repository's own drift guard caught the release mid-flight **three times**:
|
|
96
|
+
`SKILL-CARD.md` and `evals/RESULTS.md` said *95*, then *100*, then *102*. Its message
|
|
97
|
+
has always offered two fixes — *derive the number or delete it* — and three
|
|
98
|
+
hand-corrections in one run is the answer to which one was right. **The prose no
|
|
99
|
+
longer states a count at all**; it names the command that prints one. The guard's own
|
|
100
|
+
negative self-test was rewritten in the same move: its plant used to edit the
|
|
101
|
+
restated number in place, so it broke the moment the number went away — a test
|
|
102
|
+
coupled to the defect it was written against rather than to the rule. It now
|
|
103
|
+
*introduces* a count into a document that has none, which is what the guard actually
|
|
104
|
+
forbids.
|
|
105
|
+
|
|
106
|
+
### Fixed — one marker, one spelling
|
|
107
|
+
|
|
108
|
+
The distrust marker this release introduces was written **four different ways inside
|
|
109
|
+
the release that introduced it**: the doctrine's own three-state table omitted it
|
|
110
|
+
entirely, its `unresolvable` row invented *"treat as stale until refreshed"*, and the
|
|
111
|
+
Cursor rule and the config both dropped the sigil. Review caught the first; standing
|
|
112
|
+
instruction R-003 — run a fixed defect's definition against its siblings — found the
|
|
113
|
+
other three. `audit.md` says a class seen twice becomes a mechanism rather than a
|
|
114
|
+
third ledger row, so a guard now requires the canonical string and rejects the second
|
|
115
|
+
spelling. Greppability is the marker's only property: a ledger row is prose, and the
|
|
116
|
+
marker is the one string a later reader can search for.
|
|
117
|
+
|
|
118
|
+
That guard shipped green for the wrong reason and was caught by probing it rather
|
|
119
|
+
than by reading it. It compared **per line**, and this doctrine wraps at ~80 columns —
|
|
120
|
+
so in `README.md` and `stages.md`, where the marker is split across two lines, it
|
|
121
|
+
matched nothing and reported a pass. It now normalises whitespace before counting.
|
|
122
|
+
Two releases running, the defect found inside the release was an instance of the
|
|
123
|
+
class the release was about.
|
|
124
|
+
|
|
125
|
+
## v1.14.1 — 2026-08-05
|
|
126
|
+
|
|
127
|
+
### Fixed — a guard below the verdict block is dead code shaped like a guard
|
|
128
|
+
|
|
129
|
+
The fourteen guards v1.14.0 added were first appended to the *end* of
|
|
130
|
+
`test/validate.py`, below `if errors: … sys.exit(1)`. On a clean run they executed
|
|
131
|
+
after `PASS` was printed; on a corrupted one `sys.exit()` fired first and they never
|
|
132
|
+
executed at all. Every one was green for the single reason that cannot be argued
|
|
133
|
+
with: it never ran. The negatives runner caught it because it requires positive
|
|
134
|
+
evidence (`OK:` in stdout) rather than a non-zero exit — this release's own subject,
|
|
135
|
+
committed by the release that names it.
|
|
136
|
+
|
|
137
|
+
A guard now reads the validator's own source and rejects any `fail(` after the
|
|
138
|
+
verdict. It shipped with the same defect it checks for — `find()` matched the literal
|
|
139
|
+
inside the guard's own body — and its negative self-test caught that within one run;
|
|
140
|
+
`rfind`, with the reason written beside it. Guards 94 → 95.
|
|
141
|
+
|
|
3
142
|
## v1.14.0 — 2026-08-05
|
|
4
143
|
|
|
5
144
|
### Added — false success: the failure mode that removes the reason to look
|
package/CONTRIBUTING.md
CHANGED
|
@@ -225,7 +225,17 @@ 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.
|
|
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. Every invariant above names the guard that enforces it, and that guard exists.**
|
|
229
239
|
This list claims to be *what the validator enforces*; it was eight guards behind when
|
|
230
240
|
an audit measured it. A claim of enforcement is now checked like any other claim.
|
|
231
241
|
*(guard: `whose message does not appear in`)* — and a cited literal must lie inside
|
package/README.md
CHANGED
|
@@ -238,8 +238,12 @@ then, in the project root:
|
|
|
238
238
|
|
|
239
239
|
**Stage 0 asks it what grep can't** — `graphify query "how does session reach the
|
|
240
240
|
API layer"`, `graphify affected "AuthModule"`, `graphify god-nodes` — and records it
|
|
241
|
-
in the source ledger **with its
|
|
242
|
-
a doc.
|
|
241
|
+
in the source ledger **with its measured lag**, because a graph goes stale exactly
|
|
242
|
+
like a doc. Not a build date: `built 2026-08-05` is the graph's own reply about
|
|
243
|
+
itself, true and self-reported and silent about whether it describes the tree you
|
|
244
|
+
are about to change. The row carries `N commits / M days behind HEAD`, the signal
|
|
245
|
+
that measured it — and, on anything but `current`, `⚠ not trusted for reach until
|
|
246
|
+
refreshed`. It points; the code decides.
|
|
243
247
|
|
|
244
248
|
**Stage 9 closes three artifacts, not two.** Docs, wiki, **and the graph** — in the
|
|
245
249
|
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.
|
|
15
|
+
| **Version** | 1.15.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` —
|
|
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.**
|
|
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
|
|
90
|
-
|
|
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
|
|
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.
|
|
3
|
+
"version": "1.15.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.
|
|
5
|
+
"version": "1.15.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
8
8
|
"url": "https://x.com/sshlg93"
|
|
@@ -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
|
|
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
|
|
82
|
-
`graphify-out/graph.json`, what it said about this task, how
|
|
83
|
-
whether stage 9 owes it a refresh. A source consulted
|
|
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
|
|
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
|
|
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
|
|
@@ -73,6 +73,7 @@ a row pointing outside the bundle is the defect this file exists to catch.
|
|
|
73
73
|
| How the host project's own conventions are read | `references/conventions.md` |
|
|
74
74
|
| Which companions exist, what is required, self-currency | `references/companion-skills.md` |
|
|
75
75
|
| The code graph: queries, refresh, the graph↔docs divergence | `references/knowledge-graph.md` |
|
|
76
|
+
| How the graph's staleness is measured and stated in the ledger | `references/knowledge-graph.md` |
|
|
76
77
|
| Model policy — tier not id, ask once at preflight | `references/model-tiering.md` |
|
|
77
78
|
| This boundary | `references/portability.md` |
|
|
78
79
|
|
|
@@ -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
|
|
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")**, **
|
|
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
|
|
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
|
|
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
|
|
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
|