cohorte 2.7.0 → 2.9.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 +118 -0
- package/README.md +15 -10
- package/bin/cli.js +15 -2
- package/core/agents/review.md +4 -2
- package/core/commands/cohorte-brainstorm.md +5 -1
- package/core/commands/cohorte-build.md +3 -1
- package/core/commands/cohorte-doctor.md +5 -3
- package/core/commands/cohorte-fix.md +6 -5
- package/core/commands/cohorte-fleet.md +104 -0
- package/core/commands/cohorte-intake.md +92 -0
- package/core/commands/cohorte-patch.md +6 -1
- package/core/commands/cohorte-retro.md +85 -0
- package/core/commands/cohorte-review.md +85 -23
- package/core/commands/cohorte-ship.md +2 -1
- package/core/commands/cohorte-spec.md +1 -1
- package/core/hooks/gate.py +10 -4
- package/core/workflows/audit.js +14 -2
- package/core/workflows/loop.js +641 -0
- package/core/workflows/refactor.js +21 -8
- package/core/workflows/review.js +92 -12
- package/dashboard/dist/assets/{index-D1rsbLat.js → index-vtFc6Gyc.js} +12 -12
- package/dashboard/dist/index.html +1 -1
- package/dashboard/server/doctor.js +8 -2
- package/dashboard/server/index.js +6 -1
- package/dashboard/server/kanban.js +15 -4
- package/dashboard/server/metrics.js +8 -1
- package/dashboard/server/runtime.js +20 -1
- package/install.ps1 +16 -333
- package/install.sh +27 -297
- package/package.json +1 -1
- package/profile/SCHEMA.md +45 -14
- package/profile/cohorte.config.template.yaml +1 -1
- package/scripts/kanban-move.sh +15 -5
- package/scripts/new-feature.sh.template +8 -1
- package/scripts/preflight.sh +10 -2
- package/scripts/remove-feature.sh.template +3 -1
- package/scripts/test-dashboard.mjs +53 -5
- package/scripts/test-gate.mjs +6 -0
- package/scripts/test-workflows.mjs +415 -6
- package/scripts/validate-core.mjs +68 -34
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Mine the accumulated review findings across features for repeating patterns, and turn the ones the human ratifies into PIPELINE.md §Conventions rules the next build already follows.
|
|
3
|
+
argument-hint: [last <n> | all] (default: every feature with a report on disk)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are running the **retrospective**. Every review this pipeline has run left structured
|
|
7
|
+
residue on disk — verdicts, Remediation rounds, deferred backlog items. A finding that shows up
|
|
8
|
+
once is a bug; the same finding shape across features is a **missing rule**, and the pipeline
|
|
9
|
+
has a place for rules the implementers actually read: `PIPELINE.md` §Conventions, baked into
|
|
10
|
+
each surface agent at render time. This command closes that loop: findings → patterns →
|
|
11
|
+
ratified rules → re-rendered agents. The next build then never produces the finding again —
|
|
12
|
+
which is cheaper than any number of review rounds catching it.
|
|
13
|
+
|
|
14
|
+
> Read `PIPELINE.md` §`pipeline-profile` (`surfaces`, and skim the existing §Conventions
|
|
15
|
+
> stanzas — a rule that already exists is a finding about *enforcement*, not a missing rule)
|
|
16
|
+
> and `specs/_decisions.md` §Live. _Skip the re-read if already in context and unmodified._
|
|
17
|
+
|
|
18
|
+
## 1. Collect — mechanical, redirected, no judgment yet
|
|
19
|
+
|
|
20
|
+
Scope: `all` (default), or `last <n>` features by spec mtime. In as few Bash calls as possible,
|
|
21
|
+
**always redirected to `specs/reports/retro-scan.txt` and grepped — never into your context**:
|
|
22
|
+
|
|
23
|
+
- `specs/reports/*.verdict.json` — per feature: `blocking_items` (normalized identities),
|
|
24
|
+
`severity`, per-surface counts. The freshest machine record, one per feature.
|
|
25
|
+
- Every non-`_` spec's `## Remediation` section — the **persistent** history (verdict.json is
|
|
26
|
+
overwritten per round; Remediation accumulates, and collapsed rounds still carry their
|
|
27
|
+
count line). Grep the item lines: `- [x?] <SEVERITY> · <file:line> · <kind> · <fix>`.
|
|
28
|
+
- `specs/refactor-backlog.md` — the `deferred:<id>` tagged items: debt reviews kept finding
|
|
29
|
+
but no feature owned.
|
|
30
|
+
|
|
31
|
+
A repo with fewer than two features' worth of residue ⇒ say the retro has nothing statistical
|
|
32
|
+
to stand on yet, name what exists, and stop — one feature's findings are that feature's story,
|
|
33
|
+
not a pattern.
|
|
34
|
+
|
|
35
|
+
## 2. Patterns — what repeats, with the evidence attached
|
|
36
|
+
|
|
37
|
+
A **pattern** is a finding shape that recurs where recurrence means something:
|
|
38
|
+
|
|
39
|
+
- same `kind` × same surface across **≥ 2 features** (e.g. `security` findings on `backend`
|
|
40
|
+
twice running — the strongest signal there is);
|
|
41
|
+
- the same file/module named by findings from **≥ 2 features** (a hotspot no single fix loop
|
|
42
|
+
owns);
|
|
43
|
+
- the same *fix wording* family recurring (e.g. three "add the authz check" fixes = one
|
|
44
|
+
missing authorization convention);
|
|
45
|
+
- a `deferred:` cluster in one domain — debt the reviews keep re-discovering because no rule
|
|
46
|
+
makes the implementers avoid adding to it.
|
|
47
|
+
|
|
48
|
+
For each pattern, produce: the evidence lines **verbatim** (`<feature> · <severity> · <file> ·
|
|
49
|
+
<problem/fix>`), the count, and ONE drafted convention rule — **rule-shaped**: a sentence an
|
|
50
|
+
implementer can follow and a reviewer can test a diff against, placed under `### Shared` or the
|
|
51
|
+
owning `### Surface: <key>`. Not advice ("be careful with auth") — a rule ("every route under
|
|
52
|
+
`apps/api/src/routes/` calls `authorize()` before its handler; no exceptions without a spec
|
|
53
|
+
§5 note"). Skip any pattern the existing §Conventions already covers — report those separately
|
|
54
|
+
as **enforcement gaps** (the rule exists and reviews keep finding violations: that is input for
|
|
55
|
+
the human, not a new rule).
|
|
56
|
+
|
|
57
|
+
## 3. Ratify — the human picks; nothing lands without them
|
|
58
|
+
|
|
59
|
+
Present a compact table: pattern → evidence count → drafted rule → target stanza. Then **stop
|
|
60
|
+
and ask** which to adopt (all / some / none — none is a fine outcome and says the rulebook fits
|
|
61
|
+
the codebase). Rules are standing law every future dispatch pays tokens to carry: the human
|
|
62
|
+
decides what becomes law, exactly as they freeze specs.
|
|
63
|
+
|
|
64
|
+
## 4. Apply — and keep the baked slices honest
|
|
65
|
+
|
|
66
|
+
For each adopted rule, in this order:
|
|
67
|
+
|
|
68
|
+
1. Append it to `PIPELINE.md` §Conventions under its stanza (create the `### Surface: <key>`
|
|
69
|
+
stanza if absent; keep it rule-shaped, one line each).
|
|
70
|
+
2. **Re-render every affected surface agent** per SCHEMA.md §Rendering step 2 — the
|
|
71
|
+
`<SURFACE_CONVENTIONS>` slice is **baked at render time**, so a §Conventions edit without a
|
|
72
|
+
re-render produces the worst outcome available: reviewers (who read the prose live) enforce
|
|
73
|
+
a rule implementers (who carry the stale bake) have never seen, and every future review
|
|
74
|
+
round re-finds the pattern this retro just paid to close. If you cannot re-render on this
|
|
75
|
+
runtime, say so and route to `/cohorte-update-pipeline` (its reconcile step 2 re-renders) —
|
|
76
|
+
but then the rule is **pending**, and you say that too.
|
|
77
|
+
3. Append ONE line per adopted rule to `specs/_decisions.md` §Live (SCHEMA.md §Decisions):
|
|
78
|
+
`- <date> · conventions · <rule, compressed> — because <kind>×<n> across <features> · retro`.
|
|
79
|
+
A retro rule is a standing decision; the journal is where the next `/cohorte-spec` learns it
|
|
80
|
+
without re-mining the reports.
|
|
81
|
+
|
|
82
|
+
In chat print ONLY: patterns found / adopted / skipped (one line each), enforcement gaps, which
|
|
83
|
+
agents were re-rendered (or the pending route), and the decisions lines appended. The evidence
|
|
84
|
+
stays in `specs/reports/retro-scan.txt`. **Recommend a `/clear`** — everything that matters is
|
|
85
|
+
now in `PIPELINE.md`, the rendered agents, and the journal.
|
|
@@ -12,7 +12,8 @@ You are the **lead**. Dispatch the review for feature **$ARGUMENTS**.
|
|
|
12
12
|
> **Kanban** (SCHEMA.md §Kanban): run
|
|
13
13
|
> `<core>/pipeline/scripts/kanban-move.sh auto $ARGUMENTS review`. `auto` resolves the board from the config itself and
|
|
14
14
|
> exits 0 with a `kanban: <reason>` line when there is none — so **never decide "no board is
|
|
15
|
-
> configured" without running it**.
|
|
15
|
+
> configured" without running it**. _(PR mode — `--pr` below — skips this entirely: an incoming
|
|
16
|
+
> PR is not a pipeline feature and has no card.)_
|
|
16
17
|
>
|
|
17
18
|
<!-- cohorte:if workflows -->
|
|
18
19
|
> **Workflow variant** (opt-in — SCHEMA.md §Workflows): on Claude Code ≥ 2.1.154 with workflows
|
|
@@ -20,6 +21,57 @@ You are the **lead**. Dispatch the review for feature **$ARGUMENTS**.
|
|
|
20
21
|
> This conversational path stays the default and the fallback; `/cohorte-doctor` shows which is available.
|
|
21
22
|
<!-- cohorte:endif -->
|
|
22
23
|
|
|
24
|
+
## PR mode — review an INCOMING pull request (`--pr <num>` or a PR URL)
|
|
25
|
+
|
|
26
|
+
If `$ARGUMENTS` contains `--pr <num>` (or a GitHub PR URL — extract the number), this is not a
|
|
27
|
+
pipeline feature: it is **someone else's work arriving**, reviewed with the same reviewers and the
|
|
28
|
+
same report format, without pretending it went through the pipeline. What that changes, explicitly:
|
|
29
|
+
**no spec** (nothing to check conformance against — reviewers run in audit mode: conventions +
|
|
30
|
+
correctness + security + TDD coverage only), **no kanban move**, **no verdict.json**, **no DoD
|
|
31
|
+
tick, no freshness stamp, no metrics line** — those certify pipeline features, and this is not one.
|
|
32
|
+
Requires `vcs.host: github` + `gh`; otherwise say so and stop.
|
|
33
|
+
|
|
34
|
+
1. **Fetch into a throwaway worktree — never touch the current checkout.** One Bash call:
|
|
35
|
+
`gh pr view <num> --json title,baseRefName,headRefName,author` (relay the one-line summary), then
|
|
36
|
+
`git fetch -f origin pull/<num>/head:cohorte-pr-<num> <baseRefName> && git worktree add ../<repo>-pr-<num> cohorte-pr-<num>`
|
|
37
|
+
— `-f` so a branch left behind by a crashed earlier run updates instead of failing, and the
|
|
38
|
+
**base ref is fetched too**: the diff below is against `origin/<baseRefName>`, and a stale
|
|
39
|
+
local base blames the PR for every commit it merely hasn't rebased onto yet.
|
|
40
|
+
The human's tree, branch and stamps stay untouched; every later step runs `-C` that worktree.
|
|
41
|
+
2. **Mechanical checks inside the worktree — the commands directly, NEVER `preflight.sh`.** The
|
|
42
|
+
script's whole point is stamping `<state>/preflight.ok` in the MAIN checkout (it resolves
|
|
43
|
+
through `git-common-dir` on purpose), so running it here would overwrite the human's stamp
|
|
44
|
+
with the PR tree's digest — greening pipeline review dispatches of a tree nobody preflighted,
|
|
45
|
+
then blocking them once the worktree is gone. Instead: install dependencies first
|
|
46
|
+
(`commands.install` — a bare `worktree add` has no `node_modules`), then run the profile's
|
|
47
|
+
typecheck/lint-quiet/test-quiet yourself, each appended to the worktree's
|
|
48
|
+
`specs/reports/pr-<num>.preflight.txt`, stopping at the first failure. **Red does NOT get
|
|
49
|
+
reviewers** — same economics as §0 (a reviewer on code that doesn't compile rediscovers what
|
|
50
|
+
`tsc` printed for free) — but unlike §0 it is not an abort: the mechanical failures ARE the
|
|
51
|
+
review (severity CRITICAL, kind `quality`, the failing command + last lines) — jump to
|
|
52
|
+
step 4. An *environmental* failure (install itself failed, a missing service) is reported as
|
|
53
|
+
"not verifiable here", never as a finding against the PR.
|
|
54
|
+
3. **Stage + dispatch — §1's staging procedure and §2's dispatch, minus everything spec-shaped**
|
|
55
|
+
(there is no `specs/<id>.md` to confirm and no small-diff fast path — an unknown author's
|
|
56
|
+
diff always gets a full reviewer), with three substitutions: the diff is
|
|
57
|
+
`git diff origin/<baseRefName>...HEAD` (**three-dot** — only the PR's own commits, not the
|
|
58
|
+
base's drift), staged diffs and file paths are **absolute paths into the worktree**
|
|
59
|
+
(reviewers are stateless — they must not read the main checkout), and the dispatch template's
|
|
60
|
+
spec/contract slots read
|
|
61
|
+
`spec: none (incoming PR — audit mode: PIPELINE.md conventions + quality/security/TDD only,
|
|
62
|
+
skip spec conformance)` · `contract: none`. Roll call + merge per §3 (retry once, `unreviewed`
|
|
63
|
+
forbids a clean verdict), same capped REVIEW REPORT.
|
|
64
|
+
4. **Stage the report to the MAIN checkout's `specs/reports/pr-<num>.md`** (it must outlive the
|
|
65
|
+
worktree), print the verdict + severity table + CRITICAL/security digests in chat. Then —
|
|
66
|
+
**always ask the human first, posting is outward-facing** — offer:
|
|
67
|
+
`gh pr comment <num> --body-file specs/reports/pr-<num>.md` (prefix the body with one line:
|
|
68
|
+
`Automated review — cohorte pipeline (advisory)`). Never post without the explicit go-ahead;
|
|
69
|
+
"no" leaves the report on disk and that is a complete outcome.
|
|
70
|
+
5. **Teardown, always** (also after a red preflight or a dead reviewer):
|
|
71
|
+
`git worktree remove --force ../<repo>-pr-<num> && git branch -D cohorte-pr-<num>`.
|
|
72
|
+
|
|
73
|
+
Everything below this line is the normal pipeline-feature flow.
|
|
74
|
+
|
|
23
75
|
## 0. Deterministic pre-flight — no agents while red
|
|
24
76
|
|
|
25
77
|
Run the profile's mechanical gates in ONE Bash call via the shipped script
|
|
@@ -136,9 +188,11 @@ between the pipeline and any automated driver, which parses no prose:
|
|
|
136
188
|
|
|
137
189
|
- **`blocking` = CRITICAL findings + `security` findings, deduplicated** (a finding that is both
|
|
138
190
|
counts once). That is exactly the agent's existing verdict rule restated as a number, so
|
|
139
|
-
`blocking == 0` ⟺ `verdict == SHIP`. HIGH/MEDIUM/LOW quality findings are **not** blocking
|
|
140
|
-
|
|
141
|
-
-
|
|
191
|
+
`blocking == 0` ⟺ `verdict == SHIP`. HIGH/MEDIUM/LOW quality findings are **not** blocking and
|
|
192
|
+
must never cost a driver an iteration — a *human* routes them in the verdict branch below
|
|
193
|
+
(a `/cohorte-fix` pass, or a deliberate park in the backlog); a driver ignores them.
|
|
194
|
+
- **`blocking_items`** — one normalized string per **distinct** blocking finding (deduplicated —
|
|
195
|
+
two findings sharing surface, file and problem head are one identity), `<surface>|<file>|<problem>`:
|
|
142
196
|
the file path **without the `:line`** (a fix that inserts lines shifts every line below it — a
|
|
143
197
|
line-bearing identity would change every pass and the drift detection would never fire), and the
|
|
144
198
|
**problem**, not the fix, cut to its first 8 words, lowercased, every run of non-alphanumerics
|
|
@@ -181,25 +235,33 @@ In chat print ONLY: the verdict, the severity-count table, a one-line digest of
|
|
|
181
235
|
finding, and `Full report: specs/reports/$ARGUMENTS.md` — never echo the findings body into chat (it
|
|
182
236
|
would sit in this session's history, re-sent every turn). Then:
|
|
183
237
|
|
|
184
|
-
- **SHIP** → only reachable with `unreviewed` empty (the roll call above forbids it otherwise).
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
238
|
+
- **SHIP** → only reachable with `unreviewed` empty (the roll call above forbids it otherwise).
|
|
239
|
+
**Split on what survived — the DoD tick and the freshness stamp are earned by a clean bill, not
|
|
240
|
+
by the verdict alone** (the workflow variant enforces the same rule; certifying surviving
|
|
241
|
+
HIGH/MEDIUM findings for `/cohorte-ship` would ship known defects):
|
|
242
|
+
- **Nothing above LOW survived** — the clean bill. **Tick the DoD**: in `specs/$ARGUMENTS.md`
|
|
243
|
+
§`Acceptance criteria / DoD`, flip each `- [ ]` → `- [x]` for the criteria the pipeline has
|
|
244
|
+
actually verified — spec conformance + `ui_language` copy (this review), tests · lint ·
|
|
245
|
+
typecheck (a green `/cohorte-build`), mobile-first as far as the code shows it (this review).
|
|
246
|
+
**Leave `- [ ]` (and say which) any item no stage actually verified** — nothing in the pipeline
|
|
247
|
+
*runs* the feature, so any criterion that needs the app up (runtime flows, a visual check
|
|
248
|
+
against the design) stays open unless the human says they exercised it by hand and it held.
|
|
249
|
+
Ticking is the lead's job (the reviewer is read-only). **Then stamp the freshness gate** so
|
|
250
|
+
`/cohorte-ship` can refuse to ship code edited after this verdict: compute
|
|
251
|
+
`BASE=$(git merge-base <default_branch> HEAD)` and write into the spec front-matter
|
|
252
|
+
`reviewed_base: $BASE` plus `reviewed_digest: $(git diff $BASE -- . ':(exclude)specs/' | sha256sum | cut -c1-16)`
|
|
253
|
+
(`shasum -a 256` then the first 16 hex chars where there is no `sha256sum` — macOS) — the
|
|
254
|
+
fingerprint of exactly the source you just reviewed (specs excluded, so DoD ticks + the ship
|
|
255
|
+
status flip don't trip it). Park the leftover LOWs through §3.5's exact route (the backlog,
|
|
256
|
+
under their surface's domain heading, tagged `deferred:$ARGUMENTS` — never as open
|
|
257
|
+
`## Remediation` items, which would re-trigger the fix loop). Then tell the human they can
|
|
258
|
+
`/cohorte-ship` — **recommend a `/clear` first**, the handoff is fully on disk.
|
|
259
|
+
- **HIGH or MEDIUM findings survived** — the verdict stays SHIP (they are not blocking), but
|
|
260
|
+
**no DoD tick and no freshness stamp**. Route them: `/cohorte-fix $ARGUMENTS` for what should
|
|
261
|
+
be fixed now; MEDIUMs the human *explicitly* chooses to live with go through §3.5's park
|
|
262
|
+
instead — and once everything left is LOW, apply the clean-bill branch above (tick, stamp,
|
|
263
|
+
ship). The stamp is the pipeline certifying what ships; it is never written over known
|
|
264
|
+
HIGH/MEDIUM defects.
|
|
203
265
|
- **REVISE / BLOCK**, or any CRITICAL/HIGH/security finding → tell the human to run
|
|
204
266
|
**`/cohorte-fix $ARGUMENTS`** — it appends the report to the spec's `## Remediation` and re-dispatches ONLY
|
|
205
267
|
the surfaces with findings. The full path (`/cohorte-spec` Mode B then `/cohorte-build`) remains for findings that
|
|
@@ -23,7 +23,8 @@ You are the **lead**. Ship feature **$ARGUMENTS**. This is the outward-facing ga
|
|
|
23
23
|
verdict was REVISE/BLOCK, stop and say so.
|
|
24
24
|
- **Freshness gate** — the reviewed code must be exactly what ships. If the spec front-matter carries
|
|
25
25
|
`reviewed_base` + `reviewed_digest`, recompute
|
|
26
|
-
`git diff <reviewed_base> -- . ':(exclude)specs/' | sha256sum | cut -c1-16`
|
|
26
|
+
`git diff <reviewed_base> -- . ':(exclude)specs/' | sha256sum | cut -c1-16` (`shasum -a 256`
|
|
27
|
+
then the first 16 hex chars where there is no `sha256sum` — macOS) and compare to
|
|
27
28
|
`reviewed_digest`. **Match** ⇒ source unchanged since the SHIP verdict, proceed. **Mismatch** ⇒ source
|
|
28
29
|
(or the contract) was edited after review — the verdict is **stale**: stop and tell the human to re-run
|
|
29
30
|
`/cohorte-review $ARGUMENTS` before shipping. Missing fields (spec predates the gate) ⇒ skip, don't block.
|
|
@@ -91,7 +91,7 @@ Detect the mode from the pasted content:
|
|
|
91
91
|
`specs/reports/<id>.md`, where `/cohorte-review` stages its last report. Identify `feature_id` from its
|
|
92
92
|
header; open `specs/<id>.md`.
|
|
93
93
|
2. Append each finding to the spec's **`## Remediation`**, one per line:
|
|
94
|
-
`- [ ] <severity> · <file:line> · <spec-violation|quality|security> · <concrete fix>`
|
|
94
|
+
`- [ ] <severity> · <file:line> · <spec-violation|quality|security|complexity> · <concrete fix>`
|
|
95
95
|
(Keep prior items; add the new round under a dated/numbered subheading.)
|
|
96
96
|
3. If a finding implies the **contract** must change, update §5 and flag it so the lead re-authors the
|
|
97
97
|
contract file.
|
package/core/hooks/gate.py
CHANGED
|
@@ -454,13 +454,19 @@ def check_bash(payload: dict, cfg: dict) -> int:
|
|
|
454
454
|
branch_cache[directory] = branch is None or branch == default
|
|
455
455
|
return branch_cache[directory]
|
|
456
456
|
|
|
457
|
-
for raw in SPLIT.split(command)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
457
|
+
segments = [s for s in (norm(raw) for raw in SPLIT.split(command)) if s]
|
|
458
|
+
|
|
459
|
+
# Deny wins over ask across the WHOLE chain, not just in segment order. Scanning
|
|
460
|
+
# segment-by-segment returned on the first match, so `git commit -m x && node ace
|
|
461
|
+
# migration:fresh` surfaced only the benign `git commit` ask — and the human's one
|
|
462
|
+
# confirmation ran the hard-denied command behind it. Scan every segment for deny
|
|
463
|
+
# patterns first; only a fully deny-free chain gets an ask.
|
|
464
|
+
for seg in segments:
|
|
461
465
|
for pat in deny:
|
|
462
466
|
if norm(pat) in seg:
|
|
463
467
|
return decide("deny", f"`{pat}` is forbidden by the project's PIPELINE.md gate.")
|
|
468
|
+
|
|
469
|
+
for seg in segments:
|
|
464
470
|
for pat in ask:
|
|
465
471
|
if norm(pat) in seg:
|
|
466
472
|
return decide(ask_decision,
|
package/core/workflows/audit.js
CHANGED
|
@@ -157,8 +157,15 @@ const gates = await agent(
|
|
|
157
157
|
'the file and return each failure as file/line/kind/one-line summary, capped at 40 (set overflow for the rest).',
|
|
158
158
|
{ model: 'haiku', label: 'gates', schema: GATES, effort: 'low' },
|
|
159
159
|
)
|
|
160
|
+
// A dead gates agent is NOT "zero mechanical failures" — silence would read as the
|
|
161
|
+
// cleanest possible inventory from a phase that never ran (SCHEMA.md §Dead agents).
|
|
162
|
+
// The domain auditors still run (they read the staged gates file, which then simply
|
|
163
|
+
// isn't there), but the run must say the mechanical sweep is uncovered.
|
|
164
|
+
const gatesCovered = gates != null
|
|
160
165
|
const mech = (gates && gates.failures) || []
|
|
161
|
-
log(
|
|
166
|
+
log(gatesCovered
|
|
167
|
+
? `Mechanical failures: ${mech.length}${gates.overflow ? ` (+${gates.overflow} overflow)` : ''}`
|
|
168
|
+
: 'the gates agent died — mechanical checks NOT covered this run')
|
|
162
169
|
|
|
163
170
|
// ── Phase 2 — one auditor per domain, concurrent ─────────────────────────────
|
|
164
171
|
// Domains = every surface + `shared` (contract package + anything outside the
|
|
@@ -195,6 +202,10 @@ if (deadDomains.length) {
|
|
|
195
202
|
body.push('', `> ⚠ NOT audited (the auditor died): ${deadDomains.join(', ')} — absence of items below`,
|
|
196
203
|
'> for those domains means "not looked at", not "clean". Re-run the audit for them.')
|
|
197
204
|
}
|
|
205
|
+
if (!gatesCovered) {
|
|
206
|
+
body.push('', '> ⚠ Mechanical gates NOT run (the gates agent died) — lint/typecheck/test failures',
|
|
207
|
+
'> are uncounted below. Re-run the audit for the mechanical sweep.')
|
|
208
|
+
}
|
|
198
209
|
let total = 0
|
|
199
210
|
for (const d of perDomain) {
|
|
200
211
|
const items = [...d.items].sort((a, b) => SEV[a.severity] - SEV[b.severity])
|
|
@@ -214,7 +225,8 @@ const backlogOk = written != null && /done/i.test(String(written))
|
|
|
214
225
|
return {
|
|
215
226
|
backlog: backlogOk ? 'specs/refactor-backlog.md' : '(NOT written — the backlog writer died)',
|
|
216
227
|
notAudited: deadDomains, // absence of findings here means "not looked at"
|
|
217
|
-
mechanicalFailures: mech.length,
|
|
228
|
+
mechanicalFailures: gatesCovered ? mech.length : null, // null = gates agent died, NOT zero
|
|
229
|
+
gatesCovered,
|
|
218
230
|
domains: Object.fromEntries(perDomain.map(d => [d.key, d.items.length + (d.overflow || 0)])),
|
|
219
231
|
total,
|
|
220
232
|
top: perDomain.flatMap(d => d.items.map(it => ({ ...it, domain: d.key })))
|