mandrel 2.25.0 → 2.26.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/.agents/agents/acceptance-critic.md +10 -6
- package/.agents/audit-checklists/baselines.md +21 -0
- package/.agents/docs/quality-gates.md +80 -18
- package/.agents/docs/workflows.md +3 -1
- package/.agents/instructions.md +1 -1
- package/.agents/schemas/audit-rules.json +15 -0
- package/.agents/schemas/baselines/audit-baselines-envelope.schema.json +242 -0
- package/.agents/schemas/baselines/baseline-envelope.schema.json +4 -0
- package/.agents/schemas/baselines/crap.schema.json +8 -0
- package/.agents/schemas/model-attribution.schema.json +4 -0
- package/.agents/scripts/acceptance-eval.js +89 -6
- package/.agents/scripts/audit-baselines.js +136 -0
- package/.agents/scripts/check-arch-cycles.js +12 -93
- package/.agents/scripts/check-baseline-drift.js +16 -3
- package/.agents/scripts/check-baselines.js +19 -3
- package/.agents/scripts/check-cyclomatic.js +214 -0
- package/.agents/scripts/check-schema-references.js +392 -0
- package/.agents/scripts/check-test-temp-hygiene.js +38 -1
- package/.agents/scripts/check-workflow-timeouts.js +291 -0
- package/.agents/scripts/diagnose-friction.js +85 -19
- package/.agents/scripts/lib/audit-baselines/engine.js +177 -0
- package/.agents/scripts/lib/audit-baselines/gate-surface.js +63 -0
- package/.agents/scripts/lib/audit-baselines/headroom.js +72 -0
- package/.agents/scripts/lib/audit-baselines/hotspots.js +69 -0
- package/.agents/scripts/lib/audit-baselines/kinds.js +313 -0
- package/.agents/scripts/lib/audit-baselines/outliers.js +100 -0
- package/.agents/scripts/lib/audit-baselines/read.js +87 -0
- package/.agents/scripts/lib/audit-baselines/staleness.js +123 -0
- package/.agents/scripts/lib/audit-baselines/surface-entry.js +106 -0
- package/.agents/scripts/lib/audit-baselines/trend.js +125 -0
- package/.agents/scripts/lib/audit-baselines/weights.js +193 -0
- package/.agents/scripts/lib/audit-suite/index.js +0 -5
- package/.agents/scripts/lib/audit-suite/selector.js +9 -62
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +1 -0
- package/.agents/scripts/lib/baseline-schema-registry.js +13 -1
- package/.agents/scripts/lib/baselines/diff-scope-cli.js +22 -160
- package/.agents/scripts/lib/baselines/duplication-scanner.js +27 -0
- package/.agents/scripts/lib/baselines/git-base.js +26 -4
- package/.agents/scripts/lib/baselines/kinds/crap.js +112 -15
- package/.agents/scripts/lib/baselines/refresh-service.js +69 -11
- package/.agents/scripts/lib/baselines/scope.js +39 -90
- package/.agents/scripts/lib/baselines/writer.js +16 -11
- package/.agents/scripts/lib/changed-files.js +8 -1
- package/.agents/scripts/lib/cli-args.js +115 -1
- package/.agents/scripts/lib/close-validation/runner.js +70 -25
- package/.agents/scripts/lib/crap-engine.js +32 -13
- package/.agents/scripts/lib/crap-method-identity.js +153 -0
- package/.agents/scripts/lib/crap-utils.js +13 -0
- package/.agents/scripts/lib/cyclomatic-ceiling.js +265 -0
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +0 -2
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +0 -2
- package/.agents/scripts/lib/git-utils.js +136 -80
- package/.agents/scripts/lib/import-graph.js +156 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +17 -2
- package/.agents/scripts/lib/observability/source-classifier.js +175 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +17 -12
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +36 -6
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +5 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +12 -1
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +10 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +39 -3
- package/.agents/scripts/lib/orchestration/plan-context.js +119 -66
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +31 -5
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +209 -109
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +48 -12
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +79 -22
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +51 -20
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +70 -74
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +231 -0
- package/.agents/scripts/lib/orchestration/resolve-stories.js +18 -17
- package/.agents/scripts/lib/orchestration/run-epilogue.js +12 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +29 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +6 -6
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +42 -38
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +245 -140
- package/.agents/scripts/lib/orchestration/spec-budget.js +16 -5
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +182 -95
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +22 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +5 -11
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +4 -4
- package/.agents/scripts/lib/story-adjacency.js +3 -3
- package/.agents/scripts/lib/test-runner-contract.js +134 -0
- package/.agents/scripts/lib/test-tiers.js +11 -2
- package/.agents/scripts/lib/util/concurrent-map.js +17 -0
- package/.agents/scripts/lib/util/parse-id-list.js +103 -0
- package/.agents/scripts/lib/wave-runner/live-probe.js +24 -14
- package/.agents/scripts/lib/wave-runner/ready-set.js +189 -42
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +4 -10
- package/.agents/scripts/lib/workers/crap-worker.js +2 -10
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +4 -10
- package/.agents/scripts/lib/workers/maintainability-worker.js +4 -10
- package/.agents/scripts/lib/workers/serve-worker-messages.js +35 -0
- package/.agents/scripts/lib/worktree/git-hooks.js +206 -0
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +6 -0
- package/.agents/scripts/lib/worktree-manager.js +14 -0
- package/.agents/scripts/plan-run-epilogue.js +17 -5
- package/.agents/scripts/providers/github/tickets.js +33 -10
- package/.agents/scripts/provision-git-hooks.js +85 -0
- package/.agents/scripts/quality-preview.js +112 -28
- package/.agents/scripts/resolve-stories.js +4 -1
- package/.agents/scripts/run-coverage.js +86 -35
- package/.agents/scripts/run-lint.js +20 -0
- package/.agents/scripts/run-tests.js +26 -36
- package/.agents/scripts/single-story-close.js +28 -2
- package/.agents/scripts/single-story-confirm-merge.js +22 -6
- package/.agents/scripts/stories-wave-tick.js +214 -38
- package/.agents/scripts/update-coverage-baseline.js +34 -4
- package/.agents/scripts/update-duplication-baseline.js +209 -83
- package/.agents/scripts/validate-docs-freshness.js +1 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +4 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +17 -11
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/workflows/audit-baselines.md +289 -0
- package/.agents/workflows/audit-navigability.md +5 -4
- package/.agents/workflows/deliver.md +13 -4
- package/.agents/workflows/helpers/acceptance-self-eval.md +47 -10
- package/.agents/workflows/helpers/code-quality-guardrails.md +9 -2
- package/.agents/workflows/helpers/deliver-digest.md +41 -21
- package/.agents/workflows/helpers/deliver-reference.md +77 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +47 -6
- package/.agents/workflows/helpers/plan-reference.md +15 -5
- package/.agents/workflows/memory-consolidate.md +116 -0
- package/.agents/workflows/plan.md +3 -0
- package/README.md +13 -6
- package/docs/CHANGELOG.md +64 -0
- package/package.json +9 -4
- package/.agents/schemas/friction-event.schema.json +0 -56
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +0 -707
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit the committed baseline surface — dead instruments, stale baselines, cross-gate hotspot clusters, trend drift, and floor-tightening headroom — and emit findings whose remediation burns the measured debt down and tightens the ratchet behind it.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Baseline & Ratchet Audit
|
|
6
|
+
|
|
7
|
+
You are a Principal Engineer and Quality-Systems Owner auditing this
|
|
8
|
+
repository's **committed baseline surface**: the ratchet artifacts under
|
|
9
|
+
`baselines/` and the gate floors under `delivery.quality.gates` in
|
|
10
|
+
`.agentrc.json`. Those instruments only prevent **regression** — nothing owns
|
|
11
|
+
the loop that burns the measured debt **down** and tightens the floors behind
|
|
12
|
+
it, so a repo can hold a floor it cleared years ago and never notice. This lens
|
|
13
|
+
is that loop's read-only entry point. The shared lens machinery — read-only
|
|
14
|
+
constraint, scope interpretation, report envelope + finding-block skeleton,
|
|
15
|
+
severity scale, self-cross-check, and execution strategy — lives in
|
|
16
|
+
[`helpers/audit-lens-core.md`](helpers/audit-lens-core.md). Write the report to
|
|
17
|
+
`{{auditOutputDir}}/audit-baselines-results.md`. Dimension values:
|
|
18
|
+
`Dead Instrument | Staleness | Hotspot Cluster | Trend Drift | Tightening Headroom`.
|
|
19
|
+
|
|
20
|
+
> **Value-free titles (mandatory).** A finding title MUST NOT embed a measured
|
|
21
|
+
> number — write ``### `baselines/crap.json` — crap floor holds unused slack``,
|
|
22
|
+
> not ``… — crap floor 13 vs measured 8``. Every re-run re-measures, so a title
|
|
23
|
+
> carrying the reading changes each pass, its fingerprint changes with it, and
|
|
24
|
+
> `/audit-to-stories` files a duplicate instead of deduping against the open
|
|
25
|
+
> Story. Numbers belong in Current State, never in the title.
|
|
26
|
+
|
|
27
|
+
## Scope
|
|
28
|
+
|
|
29
|
+
Interpret this lens's change-set fence per the core's Scope interpretation:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
{{changedFiles}}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
When the fence resolves to a file list, keep only Hotspot Cluster findings
|
|
36
|
+
whose cluster key is in that list. The other four dimensions are properties of
|
|
37
|
+
the instrument set as a whole rather than of any changed file, so report them
|
|
38
|
+
only in codebase-wide mode.
|
|
39
|
+
|
|
40
|
+
## Constraint (lens-specific carve-out)
|
|
41
|
+
|
|
42
|
+
This lens **refines** the core's read-only constraint; it never relaxes it.
|
|
43
|
+
|
|
44
|
+
- The only command it runs is the read-only engine in Step 0. It never runs a
|
|
45
|
+
test, coverage, mutation, duplication, or lint suite.
|
|
46
|
+
- It never writes under `baselines/` and never edits `.agentrc.json`. It never
|
|
47
|
+
invokes an `update-*-baseline` script. **Regeneration is a finding, never an
|
|
48
|
+
in-run step** — the remediation Story owns every write to the surface.
|
|
49
|
+
- Reading committed baseline rows is explicitly permitted and required: citing
|
|
50
|
+
an already-computed metric is analysis, not measurement.
|
|
51
|
+
|
|
52
|
+
## Execution strategy
|
|
53
|
+
|
|
54
|
+
Run this lens as a single `subagent_type: auditor` dispatch returning the report
|
|
55
|
+
path + Executive Summary; sequential inline execution is the fallback (see the
|
|
56
|
+
core's Execution strategy).
|
|
57
|
+
|
|
58
|
+
## Step 0: Run the engine (mandatory — measure before you judge)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
node .agents/scripts/audit-baselines.js --out temp/audit-baselines/envelope.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Optional flags: `--cwd` (repository root), `--top-n` (outlier rows per gate,
|
|
65
|
+
default 20), `--hotspot-limit` (clusters emitted, default 50), `--trend-depth`
|
|
66
|
+
(baseline commits sampled per kind, default 5). Exit 0 means evidence was
|
|
67
|
+
assembled **including every degraded input**; exit 1 means the envelope could
|
|
68
|
+
not be built or written — report that and stop, because no evidence base
|
|
69
|
+
exists to author against.
|
|
70
|
+
|
|
71
|
+
The envelope is validated against
|
|
72
|
+
`.agents/schemas/baselines/audit-baselines-envelope.schema.json` and is this
|
|
73
|
+
lens's sole evidence base. Cite its fields by name:
|
|
74
|
+
|
|
75
|
+
| Section | Fields you cite |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| root | `generatedAt`, `cwd`, `topN`, `configError`, `degradations` |
|
|
78
|
+
| `gateSurface[]` | `kind`, `surface`, `baselinePath`, `configured`, `baselineExists`, `stub`, `rowCount`, `measured` (`unit` plus `value`), `generatedAt`, `staleDays`, `staleCommits`, `surfaceStale`, `deadIgnoreGlobs`, `parseError` |
|
|
79
|
+
| `hotspots[]` | `path`, `gates` (each `kind`, `metric`, `value`, `rowCount`, `severityWeight`), `gateKinds`, `gateCount`, `severityWeight`, `churnWeight`, `centralityWeight`, `frictionWeight`, `rank` |
|
|
80
|
+
| `trend[]` | `kind`, `baselinePath`, `sampleCount`, `from`, `to` (each a `sha` plus `committedAt`), `deltas` |
|
|
81
|
+
| `headroom[]` | `kind`, `axis`, `floor`, `measured`, `direction`, `headroom` |
|
|
82
|
+
|
|
83
|
+
`surface` is `gate` (a closed `delivery.quality.gates` kind) or `ratchet` (an
|
|
84
|
+
out-of-band baseline the CI baselines job owns). `direction` is `gte` or `lte`.
|
|
85
|
+
`rowCount` counts rows **after** per-file aggregation; `measured` is the
|
|
86
|
+
quantity the instrument reports, in its own unit. Cite `measured` when the two
|
|
87
|
+
disagree — 589 dead-export symbols sit in 187 files.
|
|
88
|
+
|
|
89
|
+
Two envelope-level reads come **before** any finding:
|
|
90
|
+
|
|
91
|
+
- **`configError` non-null.** Floors, target directories, and ignore globs
|
|
92
|
+
were unavailable and the engine fell back to default baseline paths. Every
|
|
93
|
+
Tightening Headroom finding would be unfounded this run: file the config
|
|
94
|
+
failure itself as one `Dead Instrument` finding and skip that dimension.
|
|
95
|
+
- **`degradations`.** Each of `gitHistory`, `importGraph`, `frictionLedger`
|
|
96
|
+
reading `true` collapsed its rank multiplier to exactly 1.0, so the hotspot
|
|
97
|
+
ordering is weaker evidence. Name the degraded inputs in the Executive
|
|
98
|
+
Summary; never present a degraded rank as a churn-informed one.
|
|
99
|
+
|
|
100
|
+
## Step 1: Evaluation Dimensions
|
|
101
|
+
|
|
102
|
+
1. **Dead Instruments.** An instrument that cannot fail is worse than none: it
|
|
103
|
+
reads green forever and the surface it names looks governed. Four shapes,
|
|
104
|
+
read straight off `gateSurface[]`:
|
|
105
|
+
- `stub` is `true` — zero rows **and** an all-zero rollup, so the gate
|
|
106
|
+
passes vacuously. The engine requires both halves, so a ratchet with
|
|
107
|
+
genuinely nothing to report is never mistaken for a dead one.
|
|
108
|
+
- `configured` is `false` on a `gate` row — a baseline is committed but no
|
|
109
|
+
`delivery.quality.gates` block enforces it, so nothing reads it.
|
|
110
|
+
- `baselineExists` is `false`, or `parseError` is non-null — the instrument
|
|
111
|
+
cannot be read at all.
|
|
112
|
+
- `deadIgnoreGlobs` is non-empty — a configured ignore pattern matches zero
|
|
113
|
+
files. It protects nothing today and silently exempts the next file that
|
|
114
|
+
happens to match it.
|
|
115
|
+
|
|
116
|
+
Grade a stub or unenforced gate **Medium**, a `parseError` on an enforced
|
|
117
|
+
gate **High** (delivery reads that file every run), a dead glob **Low**.
|
|
118
|
+
|
|
119
|
+
2. **Staleness.** Two clocks. `staleDays` is whole days since the baseline's
|
|
120
|
+
own `generatedAt`; `staleCommits` is commits touching the measured surface
|
|
121
|
+
since the baseline was last committed, with `surfaceStale` its boolean. A
|
|
122
|
+
`null` on either is never a fabricated zero — the stamp is unreadable, git
|
|
123
|
+
cannot answer, or the rows are not file paths — and is itself the finding.
|
|
124
|
+
**`surfaceStale` with `staleDays: 0` is still stale:** refreshed recently in
|
|
125
|
+
wall time, already behind the surface it scores. Grade an enforced gate
|
|
126
|
+
stale beyond roughly a month or `surfaceStale` **Medium**, a `null` stamp
|
|
127
|
+
**Medium**, an unenforced kind **Low** or **Info**.
|
|
128
|
+
|
|
129
|
+
**Regeneration is the remediation, never an in-run step.** The Agent Prompt
|
|
130
|
+
names the matching script and its one-shot acknowledgment:
|
|
131
|
+
|
|
132
|
+
| Kind | Regeneration script | Acknowledgment |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `coverage` | `npm run coverage:reanchor` | `COVERAGE_REFRESH=1` |
|
|
135
|
+
| `crap` | `npm run crap:reanchor` | `CRAP_REFRESH=1` |
|
|
136
|
+
| `duplication` | `npm run duplication:reanchor` | `DUPLICATION_REFRESH=1` |
|
|
137
|
+
| `maintainability` | `npm run maintainability:reanchor` | `MAINTAINABILITY_REFRESH=1` |
|
|
138
|
+
|
|
139
|
+
**Prescribe the `:reanchor` script, never the bare `:update` one.** Every
|
|
140
|
+
updater defaults to a **diff-scoped** refresh — only files changed in
|
|
141
|
+
`origin/main..HEAD` are re-scored, and everything else is preserved
|
|
142
|
+
verbatim. That is the right default for "I changed code, re-score what I
|
|
143
|
+
touched", and it is exactly wrong here: a baseline is stale because the
|
|
144
|
+
*world* moved (a scorer bump, a coverage-shape change, months of unrelated
|
|
145
|
+
drift), so a diff-scoped run leaves almost every stale row untouched and
|
|
146
|
+
the staleness finding re-fires on the next sweep. `:reanchor` is the same
|
|
147
|
+
script with `--full-scope`, which re-scores every file in every target
|
|
148
|
+
dir. Confirm the flag on any kind you are unsure of with that script's
|
|
149
|
+
`--help`.
|
|
150
|
+
|
|
151
|
+
Expect a re-anchor to touch far more rows than a code change would — that
|
|
152
|
+
breadth is the point, but say so in the finding so a reviewer can tell a
|
|
153
|
+
re-anchor from a mass regression.
|
|
154
|
+
|
|
155
|
+
The acknowledgment is the kind upper-snaked. It demotes that kind's
|
|
156
|
+
head-vs-base regressions to unchanged **for one run only** — floors stay
|
|
157
|
+
enforced, so a genuine breach is still caught. The durable equivalent is a
|
|
158
|
+
commit in the compared range whose subject carries the gate's `refreshTag`
|
|
159
|
+
(default `baseline-refresh:`) **and** whose diff touches that kind's
|
|
160
|
+
baseline file. Confirm both against
|
|
161
|
+
`node .agents/scripts/check-baselines.js --help` before writing the prompt,
|
|
162
|
+
and never invent an acknowledgment for a kind that ships no regeneration
|
|
163
|
+
script — there, the remediation is to add one, not to hand-edit rows.
|
|
164
|
+
|
|
165
|
+
3. **Hotspot Clusters.** `hotspots[]` is already the cross-gate join, one entry
|
|
166
|
+
per cluster key, ranked highest first. **Emit one finding per cluster —
|
|
167
|
+
never one per metric row.** A file that is a CRAP outlier and a
|
|
168
|
+
maintainability outlier is one debt item with two symptoms; splitting it
|
|
169
|
+
files two Stories that fight over the same refactor. The cluster key is a
|
|
170
|
+
repository file path for every kind except `lighthouse` (a route) and
|
|
171
|
+
`bundle-size` (a bundle name) — say which it is when it is not a file.
|
|
172
|
+
|
|
173
|
+
Quote `rank` with the four factors behind it — `severityWeight`,
|
|
174
|
+
`churnWeight`, `centralityWeight`, `frictionWeight` — and the per-gate rows
|
|
175
|
+
under `gates`. Grade by breadth first: three or more entries in `gateKinds`
|
|
176
|
+
is **High**, two is **Medium**, one is **Low** unless its `severityWeight`
|
|
177
|
+
alone is extreme.
|
|
178
|
+
|
|
179
|
+
4. **Trend Drift.** `trend[]` carries newest-versus-previous rollup deltas per
|
|
180
|
+
kind, bracketed by the commits in `from` and `to`. A delta moving **away**
|
|
181
|
+
from the floor is the finding; one moving toward it is headroom the next
|
|
182
|
+
dimension owns. Read the axis's `direction` in `headroom[]` before assigning
|
|
183
|
+
a sign — lower is not universally better. An entry needs `sampleCount` of at
|
|
184
|
+
least 2 to mean anything, and an empty `trend[]` means no readable history:
|
|
185
|
+
record that as **Info** rather than inferring a flat trend from silence.
|
|
186
|
+
Each `deltas` key **names its unit** — `symbols`, `bytes`, `filesTracked` —
|
|
187
|
+
so quote the axis with the number, never a bare delta.
|
|
188
|
+
|
|
189
|
+
5. **Tightening Headroom.** `headroom[]` is what this lens exists for. Positive
|
|
190
|
+
headroom is slack the floor could be tightened into; negative headroom means
|
|
191
|
+
the floor is already breached — grade that **High** and route it as a
|
|
192
|
+
regression, not an opportunity. File a tightening finding only when the
|
|
193
|
+
slack is **durable**: the same kind's trend is flat or improving. A one-run
|
|
194
|
+
dip tightened into a floor turns the next honest change red for no defect.
|
|
195
|
+
Grade durable multi-point slack **Medium**, marginal slack **Low**.
|
|
196
|
+
|
|
197
|
+
## Step 2: Hotspot budget and the dropped log
|
|
198
|
+
|
|
199
|
+
Cap the Detailed Findings at the **top 8 hotspot clusters by `rank`**. The
|
|
200
|
+
engine emits up to `--hotspot-limit` clusters, and the point of the lens is a
|
|
201
|
+
ranked actionable batch, not an exhaustive dump nobody schedules.
|
|
202
|
+
|
|
203
|
+
A silent truncation reads as full coverage, so the report MUST carry a
|
|
204
|
+
**Dropped Hotspots** section naming every cluster the cap excluded with its
|
|
205
|
+
cluster key, `rank`, and `gateKinds`. Write `_None dropped._` when the cap did
|
|
206
|
+
not bite; the section's absence is itself a defect. This budget log is separate
|
|
207
|
+
from — and additional to — the core's self-cross-check `kept / dropped` line,
|
|
208
|
+
which counts evidence-bar drops rather than budget drops. State the cap in the
|
|
209
|
+
Executive Summary and change it only on an explicit operator instruction.
|
|
210
|
+
|
|
211
|
+
## Step 3: The floor-tightening contract (mandatory)
|
|
212
|
+
|
|
213
|
+
A remediation Story that only burns debt down leaves the floor where it was,
|
|
214
|
+
and the reclaimed slack is silently re-spent by the next change — the loop
|
|
215
|
+
runs and the ratchet never moves. So **every Hotspot Cluster and Tightening
|
|
216
|
+
Headroom finding's Agent Prompt MUST** end the remediation with the ratchet
|
|
217
|
+
tightened and gate-enforced:
|
|
218
|
+
|
|
219
|
+
1. Lower the floor under `delivery.quality.gates` in `.agentrc.json` to the
|
|
220
|
+
newly measured level, **or** delete the burnt-down rows from that kind's
|
|
221
|
+
file under `baselines/`.
|
|
222
|
+
2. Carry `node .agents/scripts/check-baselines.js --gate <kind>` in the
|
|
223
|
+
remediation Story's `verify[]`, so the tightened floor is enforced by the
|
|
224
|
+
gate that already exists at that Story's delivery time rather than by prose
|
|
225
|
+
nobody runs.
|
|
226
|
+
|
|
227
|
+
Use these two Agent Prompt templates verbatim, substituting the envelope's own
|
|
228
|
+
values for the angle-bracketed slots:
|
|
229
|
+
|
|
230
|
+
- **Hotspot Cluster template:**
|
|
231
|
+
`Burn down the measured debt in <hotspots.path>, an outlier across <gateKinds>. Refactor and add tests until its rows leave that kind's file under baselines/, then regenerate that baseline with the matching update-*-baseline script in a commit whose subject carries the baseline-refresh: tag. Finish by TIGHTENING the ratchet in the same Story — lower the kind's floor under delivery.quality.gates in .agentrc.json to the new measured level, or delete the burnt-down rows — and carry node .agents/scripts/check-baselines.js --gate <kind> in this Story's verify[] so the tightened floor is enforced at delivery.`
|
|
232
|
+
- **Tightening Headroom template:**
|
|
233
|
+
`The <kind> gate's <axis> floor sits at <floor> while the measured rollup is <measured> (headroom <headroom>, direction <direction>), and that kind's trend is flat or improving. Tighten it: set that axis under delivery.quality.gates in .agentrc.json to the measured level so no slack remains for the next change to re-spend, and carry node .agents/scripts/check-baselines.js --gate <kind> in this Story's verify[] so the new floor is enforced. Regenerate no baseline in this Story — the floor edit is the whole change.`
|
|
234
|
+
|
|
235
|
+
Staleness, Dead Instrument, and Trend Drift findings do **not** carry the
|
|
236
|
+
tightening clause: there is no measured slack to claim until the instrument is
|
|
237
|
+
alive and current again.
|
|
238
|
+
|
|
239
|
+
## Step 4: Cadence (host-owned — documented, never scheduled)
|
|
240
|
+
|
|
241
|
+
This lens ships **no scheduler**, and building one is out of scope; cadence
|
|
242
|
+
belongs to the host that invokes it. Document the intent and let the operator
|
|
243
|
+
or the host's own timer drive it: **monthly** for a codebase-wide pass (long
|
|
244
|
+
enough for `trend[]` to hold signal, short enough to catch a stale instrument
|
|
245
|
+
before a release leans on it); **after a large refactor lands**, when headroom
|
|
246
|
+
appears and is most likely to be silently re-spent; and **before any floor is
|
|
247
|
+
raised**, so the raise is argued against measured headroom rather than
|
|
248
|
+
convenience. Nothing here self-triggers.
|
|
249
|
+
|
|
250
|
+
## Step 5: Hand off to `/audit-to-stories`
|
|
251
|
+
|
|
252
|
+
The report is the deliverable. Hand it to the converter, which parses the
|
|
253
|
+
shared finding skeleton, fingerprints each finding for dedupe, and groups the
|
|
254
|
+
batch:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
node .agents/scripts/audit-to-stories.js --scan --glob temp/audits/audit-baselines-results.md --out temp/audits/audit-to-stories-plan.json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Report the plan path and the group count; the converter owns everything
|
|
261
|
+
downstream, including whether a finding becomes a Story at all.
|
|
262
|
+
|
|
263
|
+
## Report additions
|
|
264
|
+
|
|
265
|
+
Beyond the shared skeleton (Executive Summary + Detailed Findings from the
|
|
266
|
+
core), this report carries its own title, a Gate Surface Health table, a
|
|
267
|
+
Tightening Ledger, and the Dropped Hotspots budget log:
|
|
268
|
+
|
|
269
|
+
```markdown
|
|
270
|
+
# Baseline & Ratchet Audit Report
|
|
271
|
+
|
|
272
|
+
## Gate Surface Health
|
|
273
|
+
|
|
274
|
+
| Kind | Surface | Configured | Rows | Measured | Stale (days) | Stale (commits) | Verdict |
|
|
275
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
276
|
+
| [kind] | [gate / ratchet] | [yes / no] | [rowCount] | [measured.value measured.unit] | [staleDays or `null`] | [staleCommits or `null`] | [Live / Stub / Unenforced / Unreadable] |
|
|
277
|
+
|
|
278
|
+
## Tightening Ledger
|
|
279
|
+
|
|
280
|
+
| Kind | Axis | Floor | Measured | Headroom | Trend | Proposed floor |
|
|
281
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
282
|
+
| [kind] | [axis] | [floor] | [measured] | [headroom] | [improving / flat / worsening] | [value] |
|
|
283
|
+
|
|
284
|
+
## Dropped Hotspots
|
|
285
|
+
|
|
286
|
+
| Cluster key | Rank | Gates |
|
|
287
|
+
| --- | --- | --- |
|
|
288
|
+
| [key] | [rank] | [gateKinds] |
|
|
289
|
+
```
|
|
@@ -42,10 +42,11 @@ touched only one route file. Reachability is a global property: adding one
|
|
|
42
42
|
route can orphan it, but removing or renaming a route elsewhere can also break
|
|
43
43
|
a nav href that the change set never touched.
|
|
44
44
|
|
|
45
|
-
Because of this, the navigability lens
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
Because of this, the navigability lens declares `"scope": "global"` in
|
|
46
|
+
[`audit-rules.json`](../schemas/audit-rules.json) — the single source of truth
|
|
47
|
+
`resolveLensTier` in
|
|
48
|
+
[`lib/audit-suite/selector.js`](../scripts/lib/audit-suite/selector.js) reads —
|
|
49
|
+
and is **exempt from the cross-epic-leak guard** that narrows every other lens's
|
|
49
50
|
evidence to the change set's `changedFiles`. The exemption is scoped to this
|
|
50
51
|
lens only — the guard is **not** weakened for any other lens, and it never lets
|
|
51
52
|
a foreign change set leak into a scoped lens.
|
|
@@ -35,12 +35,15 @@ you read:
|
|
|
35
35
|
| `/deliver` | bare | List the open `agent::ready` Stories and ask which to deliver. Deliver nothing until answered. |
|
|
36
36
|
| `/deliver 4712` | ids | One Story via `helpers/deliver-story.md`, **inline in this session** — no `story-worker` spawn. |
|
|
37
37
|
| `/deliver 4712 4713 …` | ids | Resolve the set, sequence by the discovered graph via `stories-wave-tick.js`, dispatch sub-agents. |
|
|
38
|
+
| `/deliver 4712 - 4716` | ids | A **range** — every id in the inclusive span. |
|
|
38
39
|
| `/deliver add a --json flag to doctor` | prompt | Unplanned work: gate, author a receipt Story, land it — [`helpers/deliver-light.md`](helpers/deliver-light.md). |
|
|
39
40
|
|
|
40
|
-
**The discriminator is lexical and total.**
|
|
41
|
-
`^#?\d+$`
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
**The discriminator is lexical and total.** An argument matching `^#?\d+$` is an
|
|
42
|
+
id, and `^#?\d+\s*[-–—]\s*#?\d+$` an inclusive **range** — pass one on as a
|
|
43
|
+
single unspaced token, never hand-expanded (reference § Ranges). Either shape
|
|
44
|
+
means ids; anything else means a prompt. A **mixed** invocation (ids *and*
|
|
45
|
+
prose) is a **hard error** — refuse it and ask which was meant. A ticket not
|
|
46
|
+
`type::story`, or carrying an `Epic: #N` footer, is a hard error too.
|
|
44
47
|
|
|
45
48
|
## Saying what you want
|
|
46
49
|
|
|
@@ -103,6 +106,12 @@ it to an operator or add it to an attended run.
|
|
|
103
106
|
`node .agents/scripts/plan-run-epilogue.js --stories 101,102`. N=1 skips it
|
|
104
107
|
([reference § Per-run epilogue](helpers/deliver-reference.md)).
|
|
105
108
|
|
|
109
|
+
5. **Correct what the change invalidated.** If a memory you recalled this
|
|
110
|
+
session is now wrong — a trap this landed, a budget it moved — fix that
|
|
111
|
+
entry now, while both the old belief and the new fact are in context, and
|
|
112
|
+
say so when you report. No memory substrate → skip silently. Sweeping the
|
|
113
|
+
whole pool is [`/memory-consolidate`](memory-consolidate.md), not this step.
|
|
114
|
+
|
|
106
115
|
## Closing what the workers hand back {#tail}
|
|
107
116
|
|
|
108
117
|
**The tail is the orchestrator's, not the worker's.** A dispatched
|
|
@@ -40,8 +40,8 @@ mid-delivery, and evaluates the actual work product.
|
|
|
40
40
|
`resolveCeremonyForRisk`). **Never run both**, and never run a
|
|
41
41
|
preliminary self-assessment pass before dispatching the fresh critic —
|
|
42
42
|
the redundant pre-pass buys no measurable quality and roughly triples
|
|
43
|
-
the acceptance-block cost. Step
|
|
44
|
-
of the one
|
|
43
|
+
the acceptance-block cost. Step 3's gate is the deterministic **scorer**
|
|
44
|
+
of the one merged verdict, not a second (or third) pass over the
|
|
45
45
|
criteria.
|
|
46
46
|
|
|
47
47
|
> **Sub-agent type + derived-level ceremony.** When
|
|
@@ -134,20 +134,56 @@ mid-delivery, and evaluates the actual work product.
|
|
|
134
134
|
fresh — a false-fresh coverage record without `coverage-final.json`
|
|
135
135
|
silently weakens the floor. Limit the evidence-share to `lint` and
|
|
136
136
|
`typecheck`.
|
|
137
|
-
- Emits a verdict file under `temp/` conforming to
|
|
137
|
+
- Emits a **cluster** verdict file under `temp/` conforming to
|
|
138
138
|
[`acceptance-eval-verdict.schema.json`](../../schemas/acceptance-eval-verdict.schema.json):
|
|
139
139
|
one `{ index, criterion, verdict: met|partial|unmet, evidence,
|
|
140
|
-
verifyEvidence[] }` record per acceptance item
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
verifyEvidence[] }` record per acceptance item **in that cluster**, each
|
|
141
|
+
`index` being the item's position in the Story's full `acceptance[]`
|
|
142
|
+
array. A fresh critic **returns that path to you** rather than calling the
|
|
143
|
+
gate itself.
|
|
144
|
+
2. **Dispatch the round's clusters in parallel, then merge into one verdict.**
|
|
145
|
+
The clusters of a round are independent, so dispatch **all** of the round's
|
|
146
|
+
fresh critics as N `Agent` calls **in a single assistant turn** —
|
|
147
|
+
[`parallel-tooling.md`](parallel-tooling.md) **Rule 3** — never serially,
|
|
148
|
+
and never one round per cluster. Clusters routed `inline` are authored in
|
|
149
|
+
the same round alongside them.
|
|
150
|
+
|
|
151
|
+
Then **merge** the cluster verdicts into **one** verdict file under `temp/`:
|
|
152
|
+
concatenate every cluster's `criteria[]` records and order the merged array
|
|
153
|
+
by `index`, so it holds exactly one record per `acceptance[]` item in
|
|
154
|
+
**acceptance-array order**, under a single top-level `storyId`,
|
|
155
|
+
`schemaVersion`, `round` and `commitSha`. The verdict schema deliberately
|
|
156
|
+
carries **no `clusterId`** — the round's artifact is the merged verdict, and
|
|
157
|
+
which critic scored which record is not part of the contract.
|
|
158
|
+
|
|
159
|
+
> **Why one gate call and not N.** The round counter is **Story-scoped** —
|
|
160
|
+
> derived by counting `acceptance-eval` signals in the Story's
|
|
161
|
+
> `signals.ndjson` — and each cluster verdict has a distinct fingerprint, so
|
|
162
|
+
> the replay guard never collapses them. A gate call per cluster would spend
|
|
163
|
+
> one of the (default 2) rounds *per cluster*, so a Story with more than 8
|
|
164
|
+
> acceptance criteria would exhaust its redraft budget on cluster arithmetic
|
|
165
|
+
> alone; N concurrent calls would also race that same ledger. Cluster-scoped
|
|
166
|
+
> round counting exists in
|
|
167
|
+
> [`acceptance-eval-decision.js`](../../scripts/lib/orchestration/acceptance-eval-decision.js)
|
|
168
|
+
> but requires an integer `epicId`, which v2 pins `null` — it is not a way
|
|
169
|
+
> around the merge.
|
|
170
|
+
3. **Decide — exactly one gate call per round.** Run the gate against the
|
|
171
|
+
**merged** verdict (the caller's Step 1a names the exact invocation — omit
|
|
172
|
+
`--epic`). The gate **scores the single verdict the round produced** —
|
|
173
|
+
schema validation, round cap, decision — and never re-scores the criteria
|
|
174
|
+
itself:
|
|
145
175
|
|
|
146
176
|
```bash
|
|
147
177
|
node <main-repo>/.agents/scripts/acceptance-eval.js \
|
|
148
|
-
--story <storyId> --verdict <verdict-path>
|
|
178
|
+
--story <storyId> --verdict <merged-verdict-path> \
|
|
179
|
+
--expected-criteria <number of acceptance[] items>
|
|
149
180
|
```
|
|
150
181
|
|
|
182
|
+
Pass `--expected-criteria` from the `acceptance[]` count you already read
|
|
183
|
+
off the Story body: a verdict whose `criteria[]` length differs — a single
|
|
184
|
+
cluster's verdict handed over unmerged — is rejected **before scoring**,
|
|
185
|
+
with an error naming the merge contract and consuming **no round**.
|
|
186
|
+
|
|
151
187
|
The gate validates the verdict against the schema, applies the round cap,
|
|
152
188
|
emits the per-criterion `acceptance-eval` signal into the retro / feedback
|
|
153
189
|
substrate, prints a JSON envelope, and exits with one of three decisions:
|
|
@@ -161,4 +197,5 @@ mid-delivery, and evaluates the actual work product.
|
|
|
161
197
|
(transition to `agent::blocked`) and post a `friction` comment naming the
|
|
162
198
|
unmet criteria and their evidence. Never silently proceed to close.
|
|
163
199
|
|
|
164
|
-
Write the verdict
|
|
200
|
+
Write both the per-cluster verdicts and the merged verdict under `temp/` only —
|
|
201
|
+
they are scratch artifacts.
|
|
@@ -35,8 +35,15 @@ thresholds, sourced from
|
|
|
35
35
|
| CC range | Action |
|
|
36
36
|
| --- | --- |
|
|
37
37
|
| ≤ 8 | Pass — no annotation required. |
|
|
38
|
-
| > 8 (default `cyclomaticFlag`) | **Flag**
|
|
39
|
-
| > 12 (default `cyclomaticMustFix`) | **Must-fix**:
|
|
38
|
+
| > 8 (default `cyclomaticFlag`) | **Flag** — `quality:preview` counts the function in its `new-method count over c=<flag>` column. The function is allowed to land but the report names it. |
|
|
39
|
+
| > 12 (default `cyclomaticMustFix`) | **Must-fix**: `check-cyclomatic.js` fails when a file gains a function above the ceiling, or when its worst function gets worse than the recorded baseline. |
|
|
40
|
+
|
|
41
|
+
`check-cyclomatic.js` is a **ratchet**, not a cliff: `baselines/cyclomatic.json`
|
|
42
|
+
records the over-ceiling functions a repository already carries, so adopting
|
|
43
|
+
the gate never demands a mass refactor. Burning a recorded breach down is
|
|
44
|
+
always allowed and re-records itself on the next `--update`; adding one is
|
|
45
|
+
what fails. It runs in the same required-check slot as `check-arch-cycles.js`
|
|
46
|
+
and `check-dead-exports.js`.
|
|
40
47
|
|
|
41
48
|
A common refactor that pulls a 13-CC function under 8 is extracting the early-
|
|
42
49
|
return guard chain into a named predicate, then collapsing the remaining
|
|
@@ -10,10 +10,9 @@ description: >-
|
|
|
10
10
|
# Deliver digest (read once per session)
|
|
11
11
|
|
|
12
12
|
> **Bundle, not a procedure.** [`deliver-story.md`](deliver-story.md) is still
|
|
13
|
-
> the steps
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
> budgets, CI remediation) stays on demand in
|
|
13
|
+
> the steps; this file is the material they reference, bundled so one read
|
|
14
|
+
> covers the happy path. Situational material (lease preflight, recovery
|
|
15
|
+
> routers, merge-wait budgets, CI remediation) stays on demand in
|
|
17
16
|
> [`deliver-story-reference.md`](deliver-story-reference.md) and
|
|
18
17
|
> [`deliver-reference.md`](deliver-reference.md); read those **only** when an
|
|
19
18
|
> envelope or a failure routes you there.
|
|
@@ -28,10 +27,10 @@ rule produces it:
|
|
|
28
27
|
whatever its shape — sub-agent isolation is load-bearing only against a
|
|
29
28
|
*concurrent* sibling racing the same checkout, and a one-Story run has none.
|
|
30
29
|
2. **Every other run is `subagent`.** A multi-Story run dispatches every Story
|
|
31
|
-
as a sub-agent however trivial its shape
|
|
32
|
-
second session for a sibling
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
as a sub-agent however trivial its shape — a lite body does not conjure a
|
|
31
|
+
second session for a sibling, and the wave tick may hand you the whole set
|
|
32
|
+
on one beat. Shape still sets ceremony; the `route::lite` label is a
|
|
33
|
+
human-visible hint, never the control signal.
|
|
35
34
|
|
|
36
35
|
`inline` removes model-side fan-out only — no `story-worker` boot, no fresh
|
|
37
36
|
acceptance-critic spawn. **`subagent` and `inline` run the same engine**: same
|
|
@@ -54,21 +53,32 @@ the only sanctioned landing. A silent local build is not a delivery.
|
|
|
54
53
|
## 3. Change set — computed once, handed to everyone
|
|
55
54
|
|
|
56
55
|
One enumeration per Story. A critic that re-runs its own `git diff`
|
|
57
|
-
can score a different set than the one that routed it
|
|
56
|
+
can score a different set than the one that routed it. Both routing calls take
|
|
57
|
+
a single options object and are **total — they never throw**, so a wrong-shaped
|
|
58
|
+
argument is silently absorbed into the `null` fail-safe:
|
|
58
59
|
|
|
59
60
|
```bash
|
|
60
61
|
node --input-type=module -e '
|
|
61
|
-
|
|
62
|
+
const lib = "<main-repo>/.agents/scripts/lib/orchestration";
|
|
63
|
+
const { computeChangeSet } = await import(`${lib}/change-set.js`);
|
|
64
|
+
const { deriveChangeLevel } = await import(`${lib}/review-depth.js`);
|
|
65
|
+
const { resolveCeremonyForRisk } = await import(`${lib}/ceremony-routing.js`);
|
|
62
66
|
const { files } = computeChangeSet({ baseRef: "main", headRef: "story-<storyId>" });
|
|
63
|
-
|
|
67
|
+
// deriveChangeLevel({ changedFiles, injectedRules?, selectSensitivePathClassesFn? })
|
|
68
|
+
// -> { level, classes } — an OBJECT, never a bare level.
|
|
69
|
+
const { level, classes } = deriveChangeLevel({ changedFiles: files });
|
|
70
|
+
// resolveCeremonyForRisk({ derivedLevel, clusterIndex?, freshCriticSampleRate?,
|
|
71
|
+
// ceremonyProfile? }) -> { mode, reason, sampled, profile, verdictOwner }.
|
|
72
|
+
// derivedLevel is that level STRING. Handing it the object above matches no
|
|
73
|
+
// tier, so it routes to the null fail-safe: a fresh critic, silently.
|
|
74
|
+
const ceremony = resolveCeremonyForRisk({ derivedLevel: level, clusterIndex: 0 });
|
|
75
|
+
console.log(JSON.stringify({ files, level, classes, ...ceremony }));
|
|
64
76
|
'
|
|
65
77
|
```
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
→ `low`, an unenumerable diff (`files === null`) → `null`. Resolve
|
|
71
|
-
fresh-vs-inline critics with `resolveCeremonyForRisk`
|
|
79
|
+
Level rules ([`review-depth.js`](../../scripts/lib/orchestration/review-depth.js)):
|
|
80
|
+
a sensitive path registered in `audit-rules.json` → `high`, none → `low`, an
|
|
81
|
+
unenumerable diff (`files === null`) → `null`. Ceremony rules
|
|
72
82
|
([`ceremony-routing.js`](../../scripts/lib/orchestration/ceremony-routing.js)):
|
|
73
83
|
`minimal` → always inline, `strict` → always fresh, `standard` → `high`/`null`
|
|
74
84
|
→ fresh and `low` → inline unless the `freshCriticSampleRate` floor forces
|
|
@@ -78,16 +88,26 @@ fresh. An `inline` dispatch mode overrides all of it to inline critics. Close's
|
|
|
78
88
|
## 4. Acceptance self-eval (Step 1a, required)
|
|
79
89
|
|
|
80
90
|
**One verdict-owner per cluster** — the fresh critic *or* the inline
|
|
81
|
-
self-eval, named by `verdictOwner`, never both and never a warm-up pass.
|
|
82
|
-
scores
|
|
83
|
-
output as evidence. Bounded by `delivery.acceptanceEval.maxRounds`
|
|
84
|
-
|
|
91
|
+
self-eval, named by `verdictOwner`, never both and never a warm-up pass. Each
|
|
92
|
+
scores its cluster's `acceptance[]` items against the change set above, with
|
|
93
|
+
`verify[]` output as evidence. Bounded by `delivery.acceptanceEval.maxRounds`
|
|
94
|
+
(default 2).
|
|
95
|
+
|
|
96
|
+
**One round = N cluster critics → ONE merged verdict → ONE gate call.** Merge
|
|
97
|
+
every cluster's records into a single `criteria[]` in `acceptance[]` order, one
|
|
98
|
+
per acceptance item, and score that once. A gate call per cluster spends a
|
|
99
|
+
round *per cluster* and races the round ledger:
|
|
85
100
|
|
|
86
101
|
```bash
|
|
87
102
|
node <main-repo>/.agents/scripts/acceptance-eval.js \
|
|
88
|
-
--story <storyId> --verdict <verdict-path>
|
|
103
|
+
--story <storyId> --verdict <merged-verdict-path> \
|
|
104
|
+
--expected-criteria <acceptance[] count>
|
|
89
105
|
```
|
|
90
106
|
|
|
107
|
+
Pass `--expected-criteria` — **without it the coverage assertion is inert**, so
|
|
108
|
+
an unmerged cluster verdict scores a fraction of the criteria and still reports
|
|
109
|
+
`proceed`. A mismatch is rejected before scoring and costs no round.
|
|
110
|
+
|
|
91
111
|
`proceed` → close. `redraft` → one more round inside the cap. `block` → **do
|
|
92
112
|
not close**: post a `friction` comment and flip `agent::blocked`.
|
|
93
113
|
Per-round mechanics: [`acceptance-self-eval.md`](acceptance-self-eval.md).
|