task-pipeline-skill 1.11.0 → 1.12.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 +54 -0
- package/CONTRIBUTING.md +7 -0
- package/README.md +1 -1
- package/SKILL-CARD.md +3 -3
- package/cursor/rules/task-pipeline.mdc +9 -1
- package/evals/RESULTS.md +4 -1
- package/package.json +1 -1
- package/plugins/task-pipeline/.claude-plugin/plugin.json +1 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/artifacts.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/build.md +25 -1
- package/plugins/task-pipeline/skills/task-pipeline/references/portability.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/references/stages.md +6 -3
- package/plugins/task-pipeline/skills/task-pipeline/templates/README.md +1 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/hygiene.sh +236 -0
- package/plugins/task-pipeline/skills/task-pipeline/templates/retro.md +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.12.0 — 2026-08-05
|
|
4
|
+
|
|
5
|
+
### Added — a gate for the defects an agent leaves behind
|
|
6
|
+
|
|
7
|
+
Every other check in this repository asks whether the work is *right*. None of
|
|
8
|
+
them asked whether the text is *intact* — whether a merge was left half-resolved,
|
|
9
|
+
a stub outlived its task, a generation stopped mid-fence, a file was "shortened"
|
|
10
|
+
while being rewritten, a retried edit duplicated a block instead of replacing it,
|
|
11
|
+
or a section was opened and abandoned. That class has an author, and the author is
|
|
12
|
+
an agent.
|
|
13
|
+
|
|
14
|
+
`templates/hygiene.sh` is the answer: a seeded, executable gate, sibling of
|
|
15
|
+
`docgate.sh`, carrying the same contracts — a `SCOPE:` header that names its own
|
|
16
|
+
false-positive surfaces, a VERDICT block that must be last, portability to macOS
|
|
17
|
+
bash 3.2, progressive arming, and a final line of computed numbers. It runs in two
|
|
18
|
+
modes: the run's diff at **zero tolerance**, and the whole tree behind per-check
|
|
19
|
+
**ratchet floors**, so an existing repository can adopt it without starting red
|
|
20
|
+
while nothing new is forgiven.
|
|
21
|
+
|
|
22
|
+
**It never edits.** None of the six defects is safely machine-fixable — deleting a
|
|
23
|
+
"duplicated block" sometimes deletes a legitimate repetition, and deleting a `TODO`
|
|
24
|
+
erases a reminder instead of discharging it. `references/build.md` makes fixing the
|
|
25
|
+
agent's obligation, at the point it is cheapest: after each task reports `DONE`,
|
|
26
|
+
before the reviewer sees it. Found one task later, a defect costs a re-dispatch;
|
|
27
|
+
found eight tasks later it is fixed by an agent that no longer remembers the code.
|
|
28
|
+
|
|
29
|
+
**Two of the six definitions were wrong until they were measured**, which is the
|
|
30
|
+
point of the rule that says measure a detector before shipping it. Matching the
|
|
31
|
+
word `TODO` anywhere found 33 hits here, 28 of them ordinary English — this is a
|
|
32
|
+
repository whose own doctrine is *"a ratchet, never a TODO"*. Anchored as a
|
|
33
|
+
line-leading marker: 0. Flagging a heading followed by any heading found 62
|
|
34
|
+
legitimate nestings; restricted to same-or-higher level it found 4, and the
|
|
35
|
+
detector still had a bug — fenced lines were skipped entirely, so a section whose
|
|
36
|
+
body is one code block looked empty. Counting fenced content as body: 2, both real.
|
|
37
|
+
Check 4 then repeated check 2's mistake in its sibling and was caught the same way.
|
|
38
|
+
|
|
39
|
+
Those last 2 were fixed rather than absorbed into a floor, so all six ship at 0.
|
|
40
|
+
One of them was a shape mistake in the retro template every project copies: a
|
|
41
|
+
one-line retirement record written as a heading, when a heading promises a section.
|
|
42
|
+
|
|
43
|
+
### Fixed — a guard that had been decorative since it shipped
|
|
44
|
+
|
|
45
|
+
Writing the first probe for the VERDICT-last contract revealed that the guard split
|
|
46
|
+
each gate script on the *word* `VERDICT`, which also appears in the header sentence
|
|
47
|
+
forbidding anything after it. Its "tail after the verdict" was therefore most of the
|
|
48
|
+
script, exit lines included, and it passed on anything. It now splits on the
|
|
49
|
+
`# ---------- VERDICT` marker and requires that marker to exist. A green from a
|
|
50
|
+
check nobody has watched fail is not evidence; this one was green for nine releases.
|
|
51
|
+
|
|
52
|
+
The template contract guards now iterate a `GATE_SCRIPTS` list instead of naming one
|
|
53
|
+
file, so a third gate costs one line rather than a copied block that drifts.
|
|
54
|
+
|
|
55
|
+
Negative self-tests: 68 → 75.
|
|
56
|
+
|
|
3
57
|
## v1.11.0 — 2026-08-04
|
|
4
58
|
|
|
5
59
|
### Added — run continuity: the pacing rules the operator was repeating by hand
|
package/CONTRIBUTING.md
CHANGED
|
@@ -117,6 +117,13 @@ found**, and run a minimum of live checks — because every section can go `dorm
|
|
|
117
117
|
and a gate blind to a shape passes exactly like one that reads it. Change a template
|
|
118
118
|
→ run `npm test`, not just your eyes.
|
|
119
119
|
|
|
120
|
+
**The same law now covers `templates/hygiene.sh`.** It is run over a clean scratch
|
|
121
|
+
project, must exit `0`, and must report all six of its check counts — exit `0` alone
|
|
122
|
+
proves nothing, because every check can go `dormant` and dormant is green. Both gate
|
|
123
|
+
scripts are validated by one iterated block, not two copies, and both must keep a
|
|
124
|
+
`# ---------- VERDICT` marker with nothing after it.
|
|
125
|
+
*(guard: `the VERDICT block must be last and must `)*
|
|
126
|
+
|
|
120
127
|
**11. Every reference over 100 lines carries a `## Contents` list**, and the list is
|
|
121
128
|
compared against that file's own `##` headings. The guidance asks for it because a
|
|
122
129
|
long file gets previewed with a partial read; the comparison is because a hand-kept
|
package/README.md
CHANGED
|
@@ -776,7 +776,7 @@ recommendation, so you arm the whole run in one exchange. Detail:
|
|
|
776
776
|
| [`references/knowledge-graph.md`](plugins/task-pipeline/skills/task-pipeline/references/knowledge-graph.md) | the code graph: install line, stage-0 reach queries, the stage-9 refresh, the graph↔docs divergence check |
|
|
777
777
|
| [`references/retrospective.md`](plugins/task-pipeline/skills/task-pipeline/references/retrospective.md) | the project retro: the three grades of fix, the mandatory prune, the cap of ten |
|
|
778
778
|
| [`references/model-tiering.md`](plugins/task-pipeline/skills/task-pipeline/references/model-tiering.md) | model policy, the `/model` reminder, overrides |
|
|
779
|
-
| [`templates/`](plugins/task-pipeline/skills/task-pipeline/templates/README.md) | brief, carry-over ledger, `CONTEXT.md` and ADR skeletons, the doc map, both registers, the retro and its archive, the seeded `docgate.sh`, a worked hook |
|
|
779
|
+
| [`templates/`](plugins/task-pipeline/skills/task-pipeline/templates/README.md) | brief, carry-over ledger, `CONTEXT.md` and ADR skeletons, the doc map, both registers, the retro and its archive, the seeded `docgate.sh` and `hygiene.sh`, a worked hook |
|
|
780
780
|
| [`references/adoption.md`](plugins/task-pipeline/skills/task-pipeline/references/adoption.md) | the first run in a project: greenfield seeding, and the brownfield walkthrough |
|
|
781
781
|
| [`references/setup.md`](plugins/task-pipeline/skills/task-pipeline/references/setup.md) | the entry audit: seven passes over the docs a project already has, offered once, output as a fix plan |
|
|
782
782
|
| [`references/portability.md`](plugins/task-pipeline/skills/task-pipeline/references/portability.md) | the manifest of workflow decisions and their homes in the bundle, and the boundary against a project's own answers |
|
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.12.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` — 76 guards, each with 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.** 76 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.
|
|
@@ -207,7 +207,15 @@ not authorize an outward, irreversible action — stage 7 stops and asks.
|
|
|
207
207
|
commit), then a review with **all three** verdicts — spec compliance, **REQ
|
|
208
208
|
satisfied** (judged against the requirement's own statement, not the task's
|
|
209
209
|
instructions), and code quality. Findings loop back to the implementer (max 5
|
|
210
|
-
rounds, then adjudicate in writing); minor findings are recorded, never blocking.
|
|
210
|
+
rounds, then adjudicate in writing); minor findings are recorded, never blocking.
|
|
211
|
+
**After each task reports done, before the review, scan what that task changed
|
|
212
|
+
for the defects an agent leaves behind** — a half-resolved merge, a stub that
|
|
213
|
+
outlived the task, a code fence left open, a file "shortened" while being
|
|
214
|
+
rewritten, a block duplicated by a retried edit, a section opened and abandoned.
|
|
215
|
+
Zero tolerance on a diff: that task wrote it, that task fixes it. A subagent's own
|
|
216
|
+
report is not evidence about the text it left behind, and a duplicated block eight
|
|
217
|
+
tasks later is repaired by an agent that no longer remembers the code.
|
|
218
|
+
Keep a progress ledger in
|
|
211
219
|
a file so a lost context can resume instead of redoing finished work. Tasks in
|
|
212
220
|
the same parallel group run concurrently **only** if each worker has its own
|
|
213
221
|
worktree — otherwise sequential. Close the stage by integrating: sync with the
|
package/evals/RESULTS.md
CHANGED
|
@@ -37,13 +37,16 @@ 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 76 structural guards that check the
|
|
41
41
|
*form*; these are the only checks that would speak to the *behaviour*, and they have
|
|
42
42
|
not been run yet. Printed here so "63 of 63 green" is never read as "the skill is
|
|
43
43
|
known to work".
|
|
44
44
|
|
|
45
45
|
## Runs
|
|
46
46
|
|
|
47
|
+
Newest first. Each entry states the model, the task, and how the evidence was
|
|
48
|
+
obtained — a self-observed run and a blind one are not the same claim.
|
|
49
|
+
|
|
47
50
|
## 2026-08-03 · opus · self-observed, not a blind run
|
|
48
51
|
|
|
49
52
|
**Scope of this evidence.** The author ran the skill on a real task
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-pipeline-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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.12.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
8
8
|
"url": "https://x.com/sshlg93"
|
|
@@ -156,6 +156,7 @@ plugins/task-pipeline/
|
|
|
156
156
|
conventions.md artifacts.md # host conventions, this layout
|
|
157
157
|
companion-skills.md # optional companions + preflight
|
|
158
158
|
templates/ # skeletons seeded into a host project
|
|
159
|
+
hygiene.sh # -> scripts/check-hygiene.sh (stages 5, 6, 9)
|
|
159
160
|
README.md brief.md carryover.md context.md adr.md retro.md
|
|
160
161
|
cursor/rules/task-pipeline.mdc # Cursor channel (self-contained rule)
|
|
161
162
|
bin/task-pipeline.js # npx installer (package task-pipeline-skill)
|
|
@@ -247,11 +247,35 @@ physical: **two implementers writing one working tree corrupt each other's state
|
|
|
247
247
|
|
|
248
248
|
| Status | Action |
|
|
249
249
|
|---|---|
|
|
250
|
-
| `DONE` |
|
|
250
|
+
| `DONE` | **Run the hygiene gate in diff mode first** (below), then build the review package and dispatch the task review ([`review.md`](review.md)). |
|
|
251
251
|
| `DONE_WITH_CONCERNS` | Read the concerns first. Correctness or scope → resolve before review. Observations ("this file is getting large") → **append to the carry-over ledger**, then proceed. A concern that stays only in the report dies with the workspace. |
|
|
252
252
|
| `NEEDS_CONTEXT` | Supply exactly what's missing, re-dispatch. |
|
|
253
253
|
| `BLOCKED` | Diagnose: missing context → re-dispatch with it; needs more reasoning → a more capable model; too large → split the task; the plan itself is wrong → escalate to the operator. |
|
|
254
254
|
|
|
255
|
+
### The hygiene gate — after every task, before the review
|
|
256
|
+
|
|
257
|
+
A subagent's own report is not evidence about the text it left behind. Run the
|
|
258
|
+
seeded gate over **what this task changed**, before the reviewer sees it:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
HYGIENE_BASE=<the commit recorded before dispatching> bash scripts/check-hygiene.sh
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Diff mode has **no floor**: this task wrote it, this task fixes it. Six checks — a
|
|
265
|
+
half-resolved merge, a stub that outlived the task, a fence left open, a file
|
|
266
|
+
"shortened" while being rewritten, a **block duplicated by a retried edit**, and a
|
|
267
|
+
section opened and abandoned.
|
|
268
|
+
|
|
269
|
+
The fifth is why the gate runs here rather than only at stage 6. A batch of edits
|
|
270
|
+
where one applied twice, or never applied at all, is the incident behind standing
|
|
271
|
+
instruction R-002 — and it is invisible in a status report. Found one task later it
|
|
272
|
+
costs a re-dispatch; found eight tasks later it is fixed by an agent that no longer
|
|
273
|
+
remembers the code.
|
|
274
|
+
|
|
275
|
+
**The gate never edits. Fixing is yours.** A finding is repaired inside the same
|
|
276
|
+
task, or it becomes a carry-over row with a reason — never a silent pass. A gate
|
|
277
|
+
whose findings nobody acts on is a slower way of ignoring them.
|
|
278
|
+
|
|
255
279
|
**Never** ignore an escalation, and never re-dispatch the same model with the same
|
|
256
280
|
prompt after a BLOCKED. If the implementer says it's stuck, something must change.
|
|
257
281
|
If the implementer asks a question — before or mid-task — answer it completely; do
|
|
@@ -57,6 +57,7 @@ a row pointing outside the bundle is the defect this file exists to catch.
|
|
|
57
57
|
| Rules earned by failure | `references/learned.md` |
|
|
58
58
|
| **The routing default and its boundary** | `templates/routing-rule.md` |
|
|
59
59
|
| The seeded doc map, registers and gate | `templates/docmap.md`, `templates/decisions.md`, `templates/open-questions.md`, `templates/docgate.sh` |
|
|
60
|
+
| Which agent-introduced defects are found, and that the agent fixes them rather than the script | `templates/hygiene.sh`, `references/build.md` |
|
|
60
61
|
| What a stage reads, and which host files bind it | `references/artifacts.md` |
|
|
61
62
|
| The design conversation, its hard gate, UI detection, user paths | `references/brainstorm.md` |
|
|
62
63
|
| Cutting a platform into modules, brick criteria, build order | `references/decomposition.md` |
|
|
@@ -296,7 +296,9 @@ never that the work was skipped quietly.
|
|
|
296
296
|
on the result, land it the project's way (merge, or a PR — outward, so it needs a
|
|
297
297
|
go), remove the worktree. Stages 7–9 act on the integrated result, so a branch the
|
|
298
298
|
operator chose to leave unmerged is recorded as such.
|
|
299
|
-
- **GATE (auto):**
|
|
299
|
+
- **GATE (auto):** **the hygiene gate green in diff mode after every task**
|
|
300
|
+
(`references/build.md`) — six checks over what that task changed, no floor, and a
|
|
301
|
+
finding fixed in-task or carried over with a reason; all plan tasks DONE (three review verdicts per task: spec
|
|
300
302
|
compliance, **REQ satisfied**, code quality); every finding fixed or parked with a
|
|
301
303
|
ruling; **every parked finding and implementer concern harvested into the
|
|
302
304
|
carry-over ledger** — nothing stays only in the scratch workspace, which is
|
|
@@ -311,7 +313,8 @@ never that the work was skipped quietly.
|
|
|
311
313
|
- **Invoke:** the host test runner (see `conventions.md` → *Lint + test*); the
|
|
312
314
|
built-in [`tdd.md`](tdd.md) cycle for any uncovered gap — failing test first,
|
|
313
315
|
same as stage 5.
|
|
314
|
-
- **GATE (auto):** the
|
|
316
|
+
- **GATE (auto):** **the hygiene gate green over the whole tree**, its six counts
|
|
317
|
+
printed beside their floors; the **full** suite is green (not just the new tests); new/changed code
|
|
315
318
|
is covered; no `skip`/`xfail` smuggling a red suite past the gate. Never advance
|
|
316
319
|
to deploy on a red or partial run. **The carry-over count is printed beside this
|
|
317
320
|
verdict** — a ratchet nobody prints is a TODO with a better name
|
|
@@ -381,7 +384,7 @@ never that the work was skipped quietly.
|
|
|
381
384
|
- **Docs living in another repository** are outward: propose the edit, get an
|
|
382
385
|
explicit go, then open a PR there. No go → the exact edit goes in the carry-over
|
|
383
386
|
ledger.
|
|
384
|
-
- **GATE (auto):** **the propagation matrix walked for every change type this run
|
|
387
|
+
- **GATE (auto):** **the hygiene gate green**, counts printed; **the propagation matrix walked for every change type this run
|
|
385
388
|
produced**, with every settled thing recorded under an id and every answered
|
|
386
389
|
question resolved; **the documentation gate green, its ratchet counts printed and
|
|
387
390
|
any skip stated** — this is what replaced the unfalsifiable *"docs in sync with
|
|
@@ -17,6 +17,7 @@ from `super-ux`.
|
|
|
17
17
|
| `decisions.md` | `docs/DECISIONS.md` — the decision register | 0 seeds it, the Doc Loop appends |
|
|
18
18
|
| `open-questions.md` | `docs/OPEN_QUESTIONS.md` | 0 seeds it, the Doc Loop resolves rows |
|
|
19
19
|
| `docgate.sh` | `scripts/check-docs.sh` | 0 seeds it · 9 runs it · 10 proves it |
|
|
20
|
+
| `hygiene.sh` | `scripts/check-hygiene.sh` | 0 seeds it · **5 runs it after every task** · 6 and 9 run it · 10 proves it |
|
|
20
21
|
| `hooks.example.json` | the project's `.claude/settings.json` | 0 — offered, never installed silently |
|
|
21
22
|
| `routing-rule.md` | the operator's `CLAUDE.md` — **offered by `setup`, never written silently** | 0 / `setup` |
|
|
22
23
|
| `retro.md` | `docs/superpowers/retro.md` — **one per project, not per run** | 10 writes (prune → stamp → entry), 0 reads it in full |
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hygiene.sh — the artifact hygiene gate for <project>.
|
|
3
|
+
#
|
|
4
|
+
# Seeded by task-pipeline (references/gates.md). IT IS YOURS NOW: extend it here,
|
|
5
|
+
# section by section. Each section is independent and removable.
|
|
6
|
+
#
|
|
7
|
+
# WHAT IT IS FOR: the defect class an AGENT produces and no other check looks for —
|
|
8
|
+
# a half-resolved merge, a stub that outlived its task, a generation cut off in the
|
|
9
|
+
# middle, a file "shortened" while being rewritten, a batch of edits where one
|
|
10
|
+
# applied twice, a section opened and abandoned.
|
|
11
|
+
#
|
|
12
|
+
# SCOPE: walks tracked files. In diff mode it walks only what this run changed; in
|
|
13
|
+
# tree mode it walks everything, behind per-check floors.
|
|
14
|
+
# It does NOT check: prose meaning, code correctness, style, formatting, spelling,
|
|
15
|
+
# another repository, or generated output. It is not a linter — your language has
|
|
16
|
+
# one, and this is not it.
|
|
17
|
+
# KNOWN FALSE-POSITIVE SURFACES, one per check, because a gate that hides them
|
|
18
|
+
# gets disabled the first time it is wrong:
|
|
19
|
+
# 1 conflict markers — a document explaining merge conflicts
|
|
20
|
+
# 2 placeholders — only a LINE-LEADING marker counts. The word TODO in the
|
|
21
|
+
# middle of a sentence is prose, not a stub. This
|
|
22
|
+
# distinction is the whole check: measured on the project
|
|
23
|
+
# that ships it, 28 of 33 hits were ordinary English.
|
|
24
|
+
# 3 unterminated fence — a fence deliberately shown unclosed as an example
|
|
25
|
+
# 4 truncation stubs — a document quoting one as an example of what not to do
|
|
26
|
+
# 5 duplicated block — a legitimately repeated stanza 3+ lines long
|
|
27
|
+
# 6 empty section — a heading used as a one-line record. If you have those,
|
|
28
|
+
# they want to be list items: a heading promises a section.
|
|
29
|
+
# Read this header before quoting a green from here as evidence.
|
|
30
|
+
#
|
|
31
|
+
# IT NEVER EDITS. It reports file:line and exits non-zero. None of the six is safely
|
|
32
|
+
# machine-fixable: deleting a "duplicated block" sometimes deletes a legitimate
|
|
33
|
+
# repetition, and deleting a TODO erases a reminder instead of discharging it.
|
|
34
|
+
# Fixing is the agent's job, and task-pipeline's references/build.md makes it one.
|
|
35
|
+
#
|
|
36
|
+
# EXIT CODE IS THE OUTPUT: non-zero on any failure. Nothing may run after the
|
|
37
|
+
# VERDICT block at the bottom — a gate that appended a check after its verdict
|
|
38
|
+
# printed FAIL and returned 0, and CI was green over it for an unknown period.
|
|
39
|
+
#
|
|
40
|
+
# PORTABLE to macOS bash 3.2: no grep -P, no sed -i, no readarray, no mapfile.
|
|
41
|
+
#
|
|
42
|
+
# PROGRESSIVE ARMING: a check whose input does not exist prints
|
|
43
|
+
# "dormant: … — no <input>" and does NOT fail. Dormant is visible so it is not
|
|
44
|
+
# forgotten, and green so a freshly seeded project does not start red.
|
|
45
|
+
#
|
|
46
|
+
# TWO MODES, AND THE DIFFERENCE IS THE POINT:
|
|
47
|
+
# diff mode (HYGIENE_BASE set) — ZERO tolerance. This run wrote it, this run fixes
|
|
48
|
+
# it. There is no floor, because a floor for work you just did is a licence.
|
|
49
|
+
# tree mode (default) — per-check RATCHET FLOORS. A count at or below its
|
|
50
|
+
# floor passes; above it fails. A floor may only fall. This is what lets an
|
|
51
|
+
# existing repository adopt the gate without starting red, while forgiving
|
|
52
|
+
# nothing new.
|
|
53
|
+
|
|
54
|
+
set -u
|
|
55
|
+
|
|
56
|
+
FAIL=0
|
|
57
|
+
HYGIENE_BASE=${HYGIENE_BASE:-}
|
|
58
|
+
HYGIENE_EXCLUDE=${HYGIENE_EXCLUDE:-graphify-out/}
|
|
59
|
+
HYGIENE_FLOOR_1=${HYGIENE_FLOOR_1:-0}
|
|
60
|
+
HYGIENE_FLOOR_2=${HYGIENE_FLOOR_2:-0}
|
|
61
|
+
HYGIENE_FLOOR_3=${HYGIENE_FLOOR_3:-0}
|
|
62
|
+
HYGIENE_FLOOR_4=${HYGIENE_FLOOR_4:-0}
|
|
63
|
+
HYGIENE_FLOOR_5=${HYGIENE_FLOOR_5:-0}
|
|
64
|
+
HYGIENE_FLOOR_6=${HYGIENE_FLOOR_6:-0}
|
|
65
|
+
|
|
66
|
+
TMP=${TMPDIR:-/tmp}/hygiene.$$
|
|
67
|
+
mkdir -p "$TMP" || exit 2
|
|
68
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
69
|
+
|
|
70
|
+
err() { echo " FAIL: $*"; FAIL=1; }
|
|
71
|
+
ok() { echo " ok: $*"; }
|
|
72
|
+
dormant() { echo " dormant: $*"; }
|
|
73
|
+
|
|
74
|
+
# ---------- the file list ----------
|
|
75
|
+
# git is the source of truth for "authored": generated and ignored files are not
|
|
76
|
+
# this gate's business, and scanning them is how a gate becomes noise.
|
|
77
|
+
if git rev-parse --git-dir >/dev/null 2>&1; then
|
|
78
|
+
if [ -n "$HYGIENE_BASE" ]; then
|
|
79
|
+
MODE=diff
|
|
80
|
+
{ git diff --name-only "$HYGIENE_BASE" 2>/dev/null
|
|
81
|
+
git diff --name-only 2>/dev/null
|
|
82
|
+
git diff --name-only --cached 2>/dev/null
|
|
83
|
+
} | sort -u > "$TMP/all"
|
|
84
|
+
else
|
|
85
|
+
MODE=tree
|
|
86
|
+
git ls-files > "$TMP/all"
|
|
87
|
+
fi
|
|
88
|
+
else
|
|
89
|
+
MODE=tree
|
|
90
|
+
find . -type f -not -path './.git/*' | sed 's|^\./||' > "$TMP/all"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
: > "$TMP/files"
|
|
94
|
+
while read -r f; do
|
|
95
|
+
[ -n "$f" ] || continue
|
|
96
|
+
[ -f "$f" ] || continue
|
|
97
|
+
case "$f" in
|
|
98
|
+
$HYGIENE_EXCLUDE*) continue ;;
|
|
99
|
+
esac
|
|
100
|
+
echo "$f" >> "$TMP/files"
|
|
101
|
+
done < "$TMP/all"
|
|
102
|
+
|
|
103
|
+
NFILES=$(wc -l < "$TMP/files" | tr -d ' ')
|
|
104
|
+
: > "$TMP/md"
|
|
105
|
+
while read -r f; do
|
|
106
|
+
case "$f" in *.md) echo "$f" >> "$TMP/md" ;; esac
|
|
107
|
+
done < "$TMP/files"
|
|
108
|
+
NMD=$(wc -l < "$TMP/md" | tr -d ' ')
|
|
109
|
+
|
|
110
|
+
echo "hygiene gate — mode $MODE · $NFILES file(s), $NMD markdown"
|
|
111
|
+
|
|
112
|
+
# The floor a check is judged against. Diff mode has none: zero tolerance.
|
|
113
|
+
floor_for() {
|
|
114
|
+
if [ "$MODE" = diff ]; then echo 0; return; fi
|
|
115
|
+
eval "echo \${HYGIENE_FLOOR_$1}"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
judge() { # judge <n> <count> <label>
|
|
119
|
+
_f=$(floor_for "$1")
|
|
120
|
+
if [ "$2" -gt "$_f" ]; then
|
|
121
|
+
err "check $1 — $3: $2 finding(s), floor $_f"
|
|
122
|
+
sed 's/^/ /' "$TMP/hits$1"
|
|
123
|
+
else
|
|
124
|
+
ok "check $1 — $3: $2 (floor $_f)"
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if [ "$NFILES" -eq 0 ]; then
|
|
129
|
+
dormant "every check — no files in scope"
|
|
130
|
+
C1=0; C2=0; C3=0; C4=0; C5=0; C6=0
|
|
131
|
+
else
|
|
132
|
+
|
|
133
|
+
# ---------- 1. conflict markers ----------
|
|
134
|
+
: > "$TMP/hits1"
|
|
135
|
+
while read -r f; do
|
|
136
|
+
grep -n -E '^(<<<<<<< |=======$|>>>>>>> )' "$f" 2>/dev/null |
|
|
137
|
+
sed "s|^|$f:|" >> "$TMP/hits1"
|
|
138
|
+
done < "$TMP/files"
|
|
139
|
+
C1=$(wc -l < "$TMP/hits1" | tr -d ' ')
|
|
140
|
+
judge 1 "$C1" "conflict markers"
|
|
141
|
+
|
|
142
|
+
# ---------- 2. surviving placeholders ----------
|
|
143
|
+
# A LINE-LEADING marker, optionally behind a comment introducer. Not the word.
|
|
144
|
+
: > "$TMP/hits2"
|
|
145
|
+
while read -r f; do
|
|
146
|
+
grep -n -E '^[[:space:]]*([#/*<!-]+[[:space:]]*)*(TODO|TBD|FIXME|XXX)\b' "$f" 2>/dev/null |
|
|
147
|
+
sed "s|^|$f:|" >> "$TMP/hits2"
|
|
148
|
+
done < "$TMP/files"
|
|
149
|
+
C2=$(wc -l < "$TMP/hits2" | tr -d ' ')
|
|
150
|
+
judge 2 "$C2" "surviving placeholders"
|
|
151
|
+
|
|
152
|
+
# ---------- 3. unterminated code fence ----------
|
|
153
|
+
: > "$TMP/hits3"
|
|
154
|
+
if [ "$NMD" -eq 0 ]; then
|
|
155
|
+
dormant "check 3 — unterminated fence: no markdown in scope"
|
|
156
|
+
C3=0
|
|
157
|
+
else
|
|
158
|
+
while read -r f; do
|
|
159
|
+
# grep -c exits 1 on zero matches, so a `|| echo 0` would append a SECOND zero
|
|
160
|
+
# and make the test below see "0\n0". Swallow the status instead.
|
|
161
|
+
n=$(grep -c '^[[:space:]]*```' "$f" 2>/dev/null) || n=0
|
|
162
|
+
[ -z "$n" ] && n=0
|
|
163
|
+
if [ "$n" -gt 0 ] && [ "$(expr "$n" % 2)" -ne 0 ]; then
|
|
164
|
+
echo "$f:1: odd fence count ($n) — a code block is not closed" >> "$TMP/hits3"
|
|
165
|
+
fi
|
|
166
|
+
done < "$TMP/md"
|
|
167
|
+
C3=$(wc -l < "$TMP/hits3" | tr -d ' ')
|
|
168
|
+
judge 3 "$C3" "unterminated fence"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# ---------- 4. truncation stubs ----------
|
|
172
|
+
# LINE-LEADING, exactly like check 2, and for exactly the same reason: the first
|
|
173
|
+
# version matched the phrase anywhere and fired three times on the very documents
|
|
174
|
+
# that DEFINE these patterns. A stub is a line standing in for content; a phrase in
|
|
175
|
+
# the middle of a sentence is prose about stubs.
|
|
176
|
+
: > "$TMP/hits4"
|
|
177
|
+
while read -r f; do
|
|
178
|
+
grep -n -E '^[[:space:]]*([#/*<!-]+[[:space:]]*)*(\.\.\. existing code|\[TRUNC|rest of (the )?file unchanged|unchanged\.\.\.)' "$f" 2>/dev/null |
|
|
179
|
+
sed "s|^|$f:|" >> "$TMP/hits4"
|
|
180
|
+
done < "$TMP/files"
|
|
181
|
+
C4=$(wc -l < "$TMP/hits4" | tr -d ' ')
|
|
182
|
+
judge 4 "$C4" "truncation stubs"
|
|
183
|
+
|
|
184
|
+
# ---------- 5. duplicated adjacent block ----------
|
|
185
|
+
# The mechanical form of "re-verify every edit in a batch": a retry that duplicated
|
|
186
|
+
# instead of replacing leaves two identical blocks back to back. Three lines is the
|
|
187
|
+
# floor because two-line repeats occur legitimately (table rows, list pairs).
|
|
188
|
+
: > "$TMP/hits5"
|
|
189
|
+
while read -r f; do
|
|
190
|
+
awk -v F="$f" '
|
|
191
|
+
NF { blk = blk "\n" $0; n++; next }
|
|
192
|
+
{
|
|
193
|
+
if (n >= 3) { if (blk == prev) print F ":" NR ": duplicated " n "-line block"; prev = blk }
|
|
194
|
+
else prev = ""
|
|
195
|
+
blk = ""; n = 0
|
|
196
|
+
}
|
|
197
|
+
END { if (n >= 3 && blk == prev) print F ":" NR ": duplicated " n "-line block at end of file" }
|
|
198
|
+
' "$f" >> "$TMP/hits5" 2>/dev/null
|
|
199
|
+
done < "$TMP/files"
|
|
200
|
+
C5=$(wc -l < "$TMP/hits5" | tr -d ' ')
|
|
201
|
+
judge 5 "$C5" "duplicated adjacent block"
|
|
202
|
+
|
|
203
|
+
# ---------- 6. empty section ----------
|
|
204
|
+
# A heading of level N followed by a heading of level <= N with no body between.
|
|
205
|
+
# FENCED CONTENT COUNTS AS BODY — the first version of this check skipped fenced
|
|
206
|
+
# lines entirely and so reported every section whose body is one code block.
|
|
207
|
+
: > "$TMP/hits6"
|
|
208
|
+
if [ "$NMD" -eq 0 ]; then
|
|
209
|
+
dormant "check 6 — empty section: no markdown in scope"
|
|
210
|
+
C6=0
|
|
211
|
+
else
|
|
212
|
+
while read -r f; do
|
|
213
|
+
awk -v F="$f" '
|
|
214
|
+
/^[[:space:]]*(```|~~~)/ { fence = !fence; prev = 0; next }
|
|
215
|
+
fence { prev = 0; next }
|
|
216
|
+
/^#{1,6} / {
|
|
217
|
+
match($0, /^#+/); lvl = RLENGTH
|
|
218
|
+
if (prev && lvl <= prev) print F ":" NR ": empty section — h" prev " then h" lvl " with no body"
|
|
219
|
+
prev = lvl; next
|
|
220
|
+
}
|
|
221
|
+
NF { prev = 0 }
|
|
222
|
+
' "$f" >> "$TMP/hits6" 2>/dev/null
|
|
223
|
+
done < "$TMP/md"
|
|
224
|
+
C6=$(wc -l < "$TMP/hits6" | tr -d ' ')
|
|
225
|
+
judge 6 "$C6" "empty section"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# ---------- VERDICT — nothing may run after this block ----------
|
|
231
|
+
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}"
|
|
233
|
+
exit 1
|
|
234
|
+
fi
|
|
235
|
+
echo "OK: hygiene gate — mode $MODE · $NFILES file(s) · conflict ${C1} · placeholder ${C2} · fence ${C3} · truncation ${C4} · duplicate ${C5} · empty-section ${C6}"
|
|
236
|
+
exit 0
|
|
@@ -48,7 +48,13 @@ stays short precisely so that reading it in full at stage 0 stays cheap.
|
|
|
48
48
|
- **Upstream?** … (a lesson true in any repo belongs in the skill's own
|
|
49
49
|
`references/learned.md` — open an issue and say so here)
|
|
50
50
|
|
|
51
|
-
###
|
|
51
|
+
### Retirements
|
|
52
|
+
|
|
53
|
+
One line each, newest first. A retirement is a record, not a section — so it is a
|
|
54
|
+
list item. A heading promises a body, and a heading with nothing under it is the
|
|
55
|
+
shape the hygiene gate's check 6 exists to find.
|
|
56
|
+
|
|
57
|
+
- 2026-05-04 · retired R-000 — became a check (`npm run lint:paths`) · `<sha>`
|
|
52
58
|
|
|
53
59
|
## Run stamps
|
|
54
60
|
|