mandrel 2.25.0 → 2.27.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.
Files changed (132) hide show
  1. package/.agents/agents/acceptance-critic.md +10 -6
  2. package/.agents/audit-checklists/baselines.md +21 -0
  3. package/.agents/docs/quality-gates.md +80 -18
  4. package/.agents/docs/workflows.md +3 -1
  5. package/.agents/instructions.md +1 -1
  6. package/.agents/schemas/audit-rules.json +15 -0
  7. package/.agents/schemas/baselines/audit-baselines-envelope.schema.json +242 -0
  8. package/.agents/schemas/baselines/baseline-envelope.schema.json +4 -0
  9. package/.agents/schemas/baselines/crap.schema.json +8 -0
  10. package/.agents/schemas/model-attribution.schema.json +4 -0
  11. package/.agents/scripts/acceptance-eval.js +89 -6
  12. package/.agents/scripts/audit-baselines.js +136 -0
  13. package/.agents/scripts/check-arch-cycles.js +12 -93
  14. package/.agents/scripts/check-baseline-drift.js +16 -3
  15. package/.agents/scripts/check-baselines.js +19 -3
  16. package/.agents/scripts/check-cyclomatic.js +214 -0
  17. package/.agents/scripts/check-schema-references.js +392 -0
  18. package/.agents/scripts/check-test-temp-hygiene.js +38 -1
  19. package/.agents/scripts/check-workflow-timeouts.js +291 -0
  20. package/.agents/scripts/diagnose-friction.js +85 -19
  21. package/.agents/scripts/lib/audit-baselines/engine.js +177 -0
  22. package/.agents/scripts/lib/audit-baselines/gate-surface.js +63 -0
  23. package/.agents/scripts/lib/audit-baselines/headroom.js +72 -0
  24. package/.agents/scripts/lib/audit-baselines/hotspots.js +69 -0
  25. package/.agents/scripts/lib/audit-baselines/kinds.js +313 -0
  26. package/.agents/scripts/lib/audit-baselines/outliers.js +100 -0
  27. package/.agents/scripts/lib/audit-baselines/read.js +87 -0
  28. package/.agents/scripts/lib/audit-baselines/staleness.js +123 -0
  29. package/.agents/scripts/lib/audit-baselines/surface-entry.js +106 -0
  30. package/.agents/scripts/lib/audit-baselines/trend.js +125 -0
  31. package/.agents/scripts/lib/audit-baselines/weights.js +193 -0
  32. package/.agents/scripts/lib/audit-suite/index.js +0 -5
  33. package/.agents/scripts/lib/audit-suite/selector.js +9 -62
  34. package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +1 -0
  35. package/.agents/scripts/lib/baseline-schema-registry.js +13 -1
  36. package/.agents/scripts/lib/baselines/diff-scope-cli.js +22 -160
  37. package/.agents/scripts/lib/baselines/duplication-scanner.js +27 -0
  38. package/.agents/scripts/lib/baselines/git-base.js +26 -4
  39. package/.agents/scripts/lib/baselines/kinds/crap.js +112 -15
  40. package/.agents/scripts/lib/baselines/reader.js +52 -38
  41. package/.agents/scripts/lib/baselines/refresh-service.js +69 -11
  42. package/.agents/scripts/lib/baselines/scope.js +39 -90
  43. package/.agents/scripts/lib/baselines/writer.js +16 -11
  44. package/.agents/scripts/lib/changed-files.js +8 -1
  45. package/.agents/scripts/lib/cli-args.js +115 -1
  46. package/.agents/scripts/lib/close-validation/runner.js +70 -25
  47. package/.agents/scripts/lib/crap-engine.js +32 -13
  48. package/.agents/scripts/lib/crap-method-identity.js +153 -0
  49. package/.agents/scripts/lib/crap-utils.js +13 -0
  50. package/.agents/scripts/lib/cyclomatic-ceiling.js +265 -0
  51. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -2
  52. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +0 -2
  53. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +0 -2
  54. package/.agents/scripts/lib/git-utils.js +136 -80
  55. package/.agents/scripts/lib/import-graph.js +156 -0
  56. package/.agents/scripts/lib/observability/runtime-friction.js +17 -2
  57. package/.agents/scripts/lib/observability/source-classifier.js +175 -2
  58. package/.agents/scripts/lib/orchestration/ceremony-routing.js +17 -12
  59. package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +36 -6
  60. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +5 -0
  61. package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +12 -1
  62. package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
  63. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +10 -5
  64. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +39 -3
  65. package/.agents/scripts/lib/orchestration/plan-context.js +119 -66
  66. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +31 -5
  67. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +209 -109
  68. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +48 -12
  69. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +79 -22
  70. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +51 -20
  71. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +70 -74
  72. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +231 -0
  73. package/.agents/scripts/lib/orchestration/resolve-stories.js +18 -17
  74. package/.agents/scripts/lib/orchestration/run-epilogue.js +12 -0
  75. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +29 -3
  76. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +6 -6
  77. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +42 -38
  78. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -1
  79. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +245 -140
  80. package/.agents/scripts/lib/orchestration/spec-budget.js +16 -5
  81. package/.agents/scripts/lib/orchestration/story-follow-ups.js +182 -95
  82. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +22 -0
  83. package/.agents/scripts/lib/orchestration/ticket-validator.js +5 -11
  84. package/.agents/scripts/lib/orchestration/ticketing/reads.js +4 -4
  85. package/.agents/scripts/lib/story-adjacency.js +3 -3
  86. package/.agents/scripts/lib/test-runner-contract.js +134 -0
  87. package/.agents/scripts/lib/test-tiers.js +11 -2
  88. package/.agents/scripts/lib/util/concurrent-map.js +17 -0
  89. package/.agents/scripts/lib/util/parse-id-list.js +103 -0
  90. package/.agents/scripts/lib/wave-runner/live-probe.js +24 -14
  91. package/.agents/scripts/lib/wave-runner/ready-set.js +189 -42
  92. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +4 -10
  93. package/.agents/scripts/lib/workers/crap-worker.js +2 -10
  94. package/.agents/scripts/lib/workers/maintainability-report-worker.js +4 -10
  95. package/.agents/scripts/lib/workers/maintainability-worker.js +4 -10
  96. package/.agents/scripts/lib/workers/serve-worker-messages.js +35 -0
  97. package/.agents/scripts/lib/worktree/git-hooks.js +206 -0
  98. package/.agents/scripts/lib/worktree/lifecycle/creation.js +6 -0
  99. package/.agents/scripts/lib/worktree-manager.js +14 -0
  100. package/.agents/scripts/plan-run-epilogue.js +17 -5
  101. package/.agents/scripts/providers/github/tickets.js +33 -10
  102. package/.agents/scripts/provision-git-hooks.js +85 -0
  103. package/.agents/scripts/quality-preview.js +112 -28
  104. package/.agents/scripts/resolve-stories.js +4 -1
  105. package/.agents/scripts/run-coverage.js +86 -35
  106. package/.agents/scripts/run-lint.js +20 -0
  107. package/.agents/scripts/run-tests.js +26 -36
  108. package/.agents/scripts/single-story-close.js +28 -2
  109. package/.agents/scripts/single-story-confirm-merge.js +22 -6
  110. package/.agents/scripts/stories-wave-tick.js +214 -38
  111. package/.agents/scripts/update-coverage-baseline.js +34 -4
  112. package/.agents/scripts/update-duplication-baseline.js +209 -83
  113. package/.agents/scripts/validate-docs-freshness.js +1 -0
  114. package/.agents/skills/core/diagnose-friction/SKILL.md +4 -1
  115. package/.agents/skills/core/gates-and-baselines/SKILL.md +17 -11
  116. package/.agents/skills/skills.index.json +2 -2
  117. package/.agents/workflows/audit-baselines.md +289 -0
  118. package/.agents/workflows/audit-navigability.md +5 -4
  119. package/.agents/workflows/deliver.md +13 -4
  120. package/.agents/workflows/helpers/acceptance-self-eval.md +47 -10
  121. package/.agents/workflows/helpers/code-quality-guardrails.md +9 -2
  122. package/.agents/workflows/helpers/deliver-digest.md +41 -21
  123. package/.agents/workflows/helpers/deliver-reference.md +77 -1
  124. package/.agents/workflows/helpers/deliver-story-reference.md +47 -6
  125. package/.agents/workflows/helpers/plan-reference.md +15 -5
  126. package/.agents/workflows/memory-consolidate.md +116 -0
  127. package/.agents/workflows/plan.md +3 -0
  128. package/README.md +13 -6
  129. package/docs/CHANGELOG.md +71 -0
  130. package/package.json +9 -4
  131. package/.agents/schemas/friction-event.schema.json +0 -56
  132. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +0 -707
@@ -115,10 +115,13 @@ For each acceptance item in your cluster:
115
115
 
116
116
  ## Verdict schema (MUST)
117
117
 
118
- Emit a verdict file under `temp/` conforming to
118
+ Write a verdict file under `temp/` at a **cluster-unique path** (e.g.
119
+ `temp/acceptance-verdict-<storyId>-r<round>-c<clusterIndex>.json`) so parallel
120
+ sibling critics cannot overwrite each other, conforming to
119
121
  [`acceptance-eval-verdict.schema.json`](../schemas/acceptance-eval-verdict.schema.json):
120
122
  one `criteria[]` record per acceptance item in your cluster, in acceptance-array
121
- order.
123
+ order. Each `index` is the criterion's position in the Story's **full**
124
+ `acceptance[]` array, not within your cluster — the caller merges on it.
122
125
 
123
126
  ```json
124
127
  {
@@ -146,10 +149,11 @@ order.
146
149
  - `partial` — partially addressed, or addressed without the required evidence.
147
150
  - `unmet` — not addressed, or the evidence contradicts the claim.
148
151
 
149
- Hand the verdict path to the caller's `acceptance-eval.js` gate, which applies
150
- the round cap and emits the per-criterion `acceptance-eval` signal; the
151
- **proceed / redraft / block** decision is the gate's, not yours. You score; the
152
- gate decides.
152
+ **Return the verdict file's absolute path to your caller never invoke
153
+ `acceptance-eval.js` yourself.** The caller merges every cluster's records into
154
+ one verdict and calls the gate **once** per round; a per-cluster call would burn
155
+ a Story-level round per cluster. The **proceed / redraft / block** decision is
156
+ the gate's, not yours. You score; the gate decides.
153
157
 
154
158
  ## Boundaries
155
159
 
@@ -0,0 +1,21 @@
1
+ <!-- GENERATED FILE — do not edit by hand.
2
+ Source of truth: .agents/workflows/audit-baselines.md
3
+ Regenerate: node .agents/scripts/generate-lens-checklists.js
4
+ Drift is gated by: npm run docs:check
5
+ -->
6
+
7
+ # Baseline & Ratchet Audit — authoring checklist
8
+
9
+ > 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.
10
+
11
+ Self-check your change against this lens's concerns before you ship:
12
+
13
+ - [ ] `configError` non-null.
14
+ - [ ] `degradations`.
15
+ - [ ] Dead Instruments.
16
+ - [ ] Staleness.
17
+ - [ ] Hotspot Clusters.
18
+ - [ ] Trend Drift.
19
+ - [ ] Tightening Headroom.
20
+ - [ ] Hotspot Cluster template
21
+ - [ ] Tightening Headroom template
@@ -102,13 +102,18 @@ npm run coverage:update # writes baselines/coverage.json from the run
102
102
  runners that orchestrate coverage capture separately).
103
103
 
104
104
  The files-out-of-scope list is declared in [`.c8rc.cjs`](../../.c8rc.cjs) —
105
- thin CLI shells (e.g. `agents-bootstrap-github.js`, `plan-context.js`,
106
- `plan-persist.js`) plus the larger Story #1702 carve-out of
107
- top-level/orchestration/git CLIs and `lib/*` glue, each with a per-entry
108
- rationale in the `.c8rc.cjs` header comment (the authoritative list). Every
109
- excluded file also carries `/* node:coverage ignore file */` at the top of its
110
- source as a second line of defence; the header comment MUST be updated when the
111
- list changes.
105
+ thin CLI shells plus the larger Story #1702 carve-out of
106
+ top-level/orchestration/git CLIs and `lib/*` glue. The `exclude[]` array is
107
+ the **single** declaration: each entry carries its rationale as an inline
108
+ comment on the line above it. Story #4922 removed the prose inventory the
109
+ header used to duplicate two copies of one list in one file, 27 files
110
+ apart by the time it was measured. Do not reintroduce one. Every excluded
111
+ file also carries `/* node:coverage ignore file */` at the top of its source
112
+ as a second line of defence.
113
+
114
+ `.c8rc.cjs`'s `include` globs and `delivery.quality.gates.coverage.targetDirs`
115
+ in [`.agentrc.json`](../../.agentrc.json) MUST name the same roots — the gate
116
+ scores what c8 measures. `tests/c8rc-scope.test.js` asserts both invariants.
112
117
 
113
118
  ---
114
119
 
@@ -123,11 +128,30 @@ touched it:
123
128
 
124
129
  | Metric | Floor | Scope |
125
130
  | --- | --- | --- |
126
- | Coverage — lines | ≥ 90 % | per file |
127
- | Coverage — branches | ≥ 85 % | per file |
128
- | Coverage — functions | ≥ 90 % | per file |
129
- | Maintainability Index | ≥ 70 | per file |
130
- | CRAP | ≤ 20 | per method |
131
+ | Coverage — lines | ≥ 94 % | repo rollup |
132
+ | Coverage — branches | ≥ 85 % | repo rollup |
133
+ | Coverage — functions | ≥ 87 % | repo rollup |
134
+ | Maintainability Index | ≥ 70 | repo rollup |
135
+ | CRAP — methods above 20 | ≤ 13 | repo rollup |
136
+
137
+ Floors are enforced against the baseline's `rollup` components — the
138
+ `applyFloors` phase compares `rollup["*"]` (and any named component), never
139
+ individual rows. Story #4922 corrected this table, which previously read
140
+ "per file" and quoted 90/85/90 for coverage; those numbers came from the
141
+ example in `.agents/docs/agentrc-reference.json`, which is validated only
142
+ against itself, and the coverage gate was not configured at all.
143
+
144
+ The live coverage floors are derived from the measurement in
145
+ [`baselines/coverage.json`](../../baselines/coverage.json) — a full-tier run
146
+ scored 95.65 / 86.16 / 88.52, and each floor sits ~1–1.7 points under its
147
+ axis. Re-derive them, do not invent them, whenever the baseline is
148
+ regenerated wholesale.
149
+
150
+ The coverage gate deliberately declares **no `tolerance`**, so its
151
+ head-vs-base ratchet arm reports regressions without failing the build (the
152
+ same shape the `crap` gate uses). Story #4922's scope was making the
153
+ instrument honest; arming the ratchet belongs with the debt burn-down that
154
+ the widened measurement newly exposes.
131
155
 
132
156
  The floors are declared in [`.agentrc.json`](../../.agentrc.json) under
133
157
  `delivery.quality.gates.<gate>.floors.*` (defaults baked into the helper
@@ -173,11 +197,11 @@ The floor gate is only as strict as its scope, so the `exclude` list in
173
197
  [`.c8rc.cjs`](../../.c8rc.cjs) carries three hard requirements that are
174
198
  enforced by review (and partially by the audit suite):
175
199
 
176
- 1. **One-line rationale per entry.** Every file in `exclude[]` MUST have
177
- a bulleted justification in the `.c8rc.cjs` header comment naming
178
- *why* it is excluded — typically "thin CLI shell, meaningful logic
179
- lives in `lib/<X>` and is unit-tested there." A bare path with no
180
- rationale is a review-block.
200
+ 1. **One-line rationale per entry.** Every file in `exclude[]` MUST carry
201
+ an inline comment on the line(s) directly above it naming *why* it is
202
+ excluded — typically "thin CLI shell, meaningful logic lives in
203
+ `lib/<X>` and is unit-tested there." A bare path with no rationale is a
204
+ review-block, and `tests/c8rc-scope.test.js` fails on one.
181
205
  2. **`/* node:coverage ignore file */` pragma at source.** Every
182
206
  excluded file MUST carry the Node coverage pragma at the top of its
183
207
  own source. This is the second line of defence: when `c8 report` and
@@ -260,6 +284,42 @@ default and the deep-merge extender form).
260
284
 
261
285
  ---
262
286
 
287
+ ## Cyclomatic ceiling ratchet
288
+
289
+ `delivery.quality.codingGuardrails.cyclomaticMustFix` (default `12`) is the
290
+ per-function complexity ceiling, enforced by `check-cyclomatic.js`. It is a
291
+ **standalone ratchet** — the same slot as `check-arch-cycles.js`,
292
+ `check-dead-exports.js`, and `check-context-budget.js` — not a
293
+ `delivery.quality.gates` kind, so it needs no gate block and no floor.
294
+
295
+ ```bash
296
+ node .agents/scripts/check-cyclomatic.js # the gate
297
+ node .agents/scripts/check-cyclomatic.js --update # re-record the breaches
298
+ ```
299
+
300
+ `baselines/cyclomatic.json` records, per file, how many functions currently
301
+ sit above the ceiling and how bad the worst one is. The gate fails when a
302
+ file's over-ceiling count rises (including `0 → 1`, a brand-new breach) or
303
+ when its worst function gets worse than recorded. Shrinking and disappearing
304
+ are the success signals and never fail.
305
+
306
+ Recording existing breaches is what makes the ceiling adoptable: a repository
307
+ with dozens of over-ceiling functions can turn the gate on today and burn them
308
+ down on its own schedule, instead of disabling a gate that fails on the first
309
+ commit. Re-run `--update` after a deliberate refactor; that is the only motion
310
+ allowed to raise a recorded count, and it shows up in review as a baseline
311
+ diff.
312
+
313
+ The scan reuses `delivery.quality.gates.maintainability.targetDirs` /
314
+ `ignoreGlobs` — both instruments read the same coverage-free escomplex
315
+ surface, so a separate scope declaration could only ever restate it.
316
+
317
+ `cyclomaticFlag` (default `8`) is the softer half of the pair: it is not
318
+ gated, and names the ceiling `quality:preview` counts new methods against in
319
+ its `new-method count over c=<flag>` column.
320
+
321
+ ---
322
+
263
323
  ## CRAP gate — Consumer onboarding
264
324
 
265
325
  > Baseline envelope, axes, and component model: see the
@@ -436,7 +496,9 @@ are not comparable to rows scored by this one, and neither `kernelVersion` nor
436
496
  therefore carries a `scoringSemantics` stamp; `check-baselines` fails closed on
437
497
  a mismatch with the exact re-baseline command rather than comparing across the
438
498
  boundary. Bump the stamp whenever the coverage join, the line coordinate
439
- system, or the unresolved-method policy changes.
499
+ system, the unresolved-method policy, or the method identity rule changes
500
+ Story #4969 bumped it for the last of these, replacing escomplex's positional
501
+ `<anon method-N>` label with an enclosing-scope-path identity.
440
502
 
441
503
  ---
442
504
 
@@ -32,12 +32,13 @@ by `node .agents/scripts/generate-workflows-doc.js`; `npm run docs:check`
32
32
  fails when it drifts from the on-disk workflow set. To change a command’s
33
33
  description, edit the workflow file’s front-matter and regenerate.
34
34
 
35
- ## Commands (25)
35
+ ## Commands (27)
36
36
 
37
37
  | Command | Description |
38
38
  | --- | --- |
39
39
  | `/audit-accessibility` | Audit WCAG accessibility conformance (static-first) with an optional runtime verification pass, and produce a structured findings report |
40
40
  | `/audit-architecture` | Audit architectural boundaries, module coupling, layering violations, and shipped-but-uncalled seams; emit a structured findings report keyed to the canonical severity scale. |
41
+ | `/audit-baselines` | 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. |
41
42
  | `/audit-clean-code` | Audit code smells, dead code, complexity hotspots, and maintainability-index outliers; emit a structured findings report. |
42
43
  | `/audit-data-model` | Audit the persistence layer as a first-class artifact — model↔migration↔seed drift, constraint completeness, migration hygiene, type fidelity, and access-pattern fit; gated by a persistence-layer applicability probe so DB-less repos skip cleanly. |
43
44
  | `/audit-dependencies` | Audit `package.json` for unused, outdated, and major-version-stale dependencies; surface Node-engine drift and propose upgrade batches. |
@@ -56,6 +57,7 @@ description, edit the workflow file’s front-matter and regenerate.
56
57
  | `/git-cleanup` | Tidy the local checkout in four phases: fast-forward `main`, prune stale remote-tracking refs, sweep merged branches (squash-aware), and triage `git stash` entries — each step gated by operator confirmation. |
57
58
  | `/git-deliver` | Single ad-hoc delivery command for working-tree changes. Detects the git setup and escalates to the right terminal step — commit only, commit + push, or commit + push + open a PR with native auto-merge — picking the default from observable state and letting flags pin any level explicitly. Replaces the retired git-commit-all, git-push, and git-pr-all trio. |
58
59
  | `/mandrel-update` | npm-era upgrade wraparound for a Mandrel consumer. Runs `npx mandrel update` (resolve newest published version → install → re-materialize `.agents/` → migrate → doctor → surface changelog) as the single mechanical step, then walks the operator through the judgment wraparound the CLI deliberately leaves unowned: reconcile `.agentrc.json`, install the stabilized quality-gate surface, refresh the harness permission allowlist, reconcile the consumer's `AGENTS.md` / runbooks against the surfaced changelog, and stage + commit the staged lockfile bump. |
60
+ | `/memory-consolidate` | Attended consolidation pass over this project's agent memory pool — merge duplicates, verify claims against the current tree, prune with operator confirmation, rewrite the index, and stamp the pool so the /plan advisory goes quiet. |
59
61
  | `/plan` | Unified planning entry point. Interrogate → author → persist. Emits one Story by default; splits into N>1 only under the default-single split policy. |
60
62
  | `/prototype` | Operator-invoked UI prototype pass. Discovers the consumer's design-system SSOT first, then — only after the operator confirms — writes exactly one self-contained HTML file under the gitignored workspace-root temp tree, so a layout can be reviewed before its UI acceptance criteria are authored. |
61
63
  | `/qa-assist` | Human-led QA assist loop — set up, then ride a rolling multi-observation intake session. The operator reports observations in any order; the agent enriches each (repro + root-cause file:line + coverage verdict for bugs; analysis + options + recommendation for enhancements), asks clarifying questions only when ambiguous, and appends a redacted ledger item — recording, never planning — to a persistent, resumable session under temp/qa/. Only when the operator says they are done does it review the full ledger and hand off to /plan. |
@@ -79,7 +79,7 @@ present, else the **Tech Stack** section of `docs/architecture.md`.
79
79
 
80
80
  You MUST log operational friction (repeated tool errors, unrecoverable
81
81
  command failures, self-corrected ambiguity, automatable boilerplate):
82
- `node .agents/scripts/diagnose-friction.js --story [STORY_ID] --cmd [FAILED_COMMAND]`
82
+ `node .agents/scripts/diagnose-friction.js --story [STORY_ID] --cmd <cmd> <args...>`
83
83
  — a **local NDJSON signal** (not posted to the ticket). Schema and the
84
84
  `AGENT_LOG_LEVEL` table:
85
85
  [`docs/execution-reference.md`](docs/execution-reference.md#friction-telemetry).
@@ -167,6 +167,21 @@
167
167
  "scope": "cumulative",
168
168
  "substitutionKeys": []
169
169
  },
170
+ "audit-baselines": {
171
+ "triggers": {
172
+ "gates": ["gate3"],
173
+ "keywords": [
174
+ "baseline",
175
+ "ratchet",
176
+ "floor",
177
+ "headroom",
178
+ "quality gate"
179
+ ],
180
+ "filePatterns": ["baselines/**", ".agentrc.json"]
181
+ },
182
+ "scope": "cumulative",
183
+ "substitutionKeys": []
184
+ },
170
185
  "audit-documentation": {
171
186
  "triggers": {
172
187
  "gates": ["gate1", "gate3"],
@@ -0,0 +1,242 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://mandrel/.agents/schemas/baselines/audit-baselines-envelope.schema.json",
4
+ "title": "AuditBaselinesEnvelope",
5
+ "description": "Evidence envelope emitted by `.agents/scripts/audit-baselines.js` (Story #4902). Unlike its siblings in this directory it does NOT describe a committed baseline — it describes an engine's read-only report ABOUT the baseline surface, so it deliberately does not extend baseline-envelope.schema.json. Four sections: gateSurface (instrument health per kind), hotspots (ranked per-file clusters joining top-N outliers across gates), trend (rollup deltas from each baseline file's git history), and headroom (configured floor vs measured rollup).",
6
+ "type": "object",
7
+ "required": [
8
+ "kind",
9
+ "schemaVersion",
10
+ "generatedAt",
11
+ "cwd",
12
+ "topN",
13
+ "configError",
14
+ "degradations",
15
+ "gateSurface",
16
+ "hotspots",
17
+ "trend",
18
+ "headroom"
19
+ ],
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "kind": { "const": "audit-baselines-envelope" },
23
+ "schemaVersion": { "type": "string", "minLength": 1 },
24
+ "generatedAt": { "type": "string", "format": "date-time" },
25
+ "cwd": {
26
+ "type": "string",
27
+ "minLength": 1,
28
+ "description": "Repository root the engine ran against."
29
+ },
30
+ "topN": {
31
+ "type": "integer",
32
+ "minimum": 0,
33
+ "description": "Per-gate bound on extracted outlier rows. The envelope never embeds a full baseline; crap.json alone is ~650KB of per-method rows."
34
+ },
35
+ "configError": {
36
+ "type": ["string", "null"],
37
+ "description": "Message from a failed config resolution. Non-null means floors, targetDirs, and ignoreGlobs were unavailable and the engine fell back to default baseline paths."
38
+ },
39
+ "degradations": {
40
+ "type": "object",
41
+ "description": "Which optional inputs were unavailable. Each degradation collapses its rank multiplier to exactly 1.0 and never changes the exit code.",
42
+ "required": ["gitHistory", "importGraph", "frictionLedger"],
43
+ "additionalProperties": false,
44
+ "properties": {
45
+ "gitHistory": { "type": "boolean" },
46
+ "importGraph": { "type": "boolean" },
47
+ "frictionLedger": { "type": "boolean" }
48
+ }
49
+ },
50
+ "gateSurface": {
51
+ "type": "array",
52
+ "description": "One entry per kind across BOTH halves of the surface: the closed delivery.quality.gates kinds and the out-of-band ratchet baselines the CI baselines job owns.",
53
+ "items": {
54
+ "type": "object",
55
+ "required": [
56
+ "kind",
57
+ "surface",
58
+ "baselinePath",
59
+ "configured",
60
+ "baselineExists",
61
+ "stub",
62
+ "rowCount",
63
+ "measured",
64
+ "generatedAt",
65
+ "staleDays",
66
+ "staleCommits",
67
+ "surfaceStale",
68
+ "deadIgnoreGlobs",
69
+ "parseError"
70
+ ],
71
+ "additionalProperties": false,
72
+ "properties": {
73
+ "kind": { "type": "string", "minLength": 1 },
74
+ "surface": { "enum": ["gate", "ratchet"] },
75
+ "baselinePath": { "type": "string", "minLength": 1 },
76
+ "configured": {
77
+ "type": "boolean",
78
+ "description": "A gate block for this kind is present in the resolved config."
79
+ },
80
+ "baselineExists": { "type": "boolean" },
81
+ "stub": {
82
+ "type": "boolean",
83
+ "description": "Zero rows AND an all-zero rollup — an instrument that passes every run vacuously. Both halves are required, so a ratchet baseline with nothing to report (e.g. zero import cycles) is never mistaken for a dead instrument."
84
+ },
85
+ "rowCount": {
86
+ "type": "integer",
87
+ "minimum": 0,
88
+ "description": "Rows AFTER the kind's per-file aggregation, so it is a file count for kinds whose on-disk grain is finer (dead exports are one row per {file, symbol}). Read `measured` for the quantity the instrument reports."
89
+ },
90
+ "measured": {
91
+ "type": ["object", "null"],
92
+ "description": "The whole-repo quantity in the unit the instrument measures — symbols for dead exports, bytes for context budget. Null when the baseline is absent or unreadable. Sits beside rowCount because the two legitimately disagree.",
93
+ "required": ["unit", "value"],
94
+ "additionalProperties": false,
95
+ "properties": {
96
+ "unit": { "type": "string", "minLength": 1 },
97
+ "value": { "type": "number" }
98
+ }
99
+ },
100
+ "generatedAt": { "type": ["string", "null"] },
101
+ "staleDays": {
102
+ "type": ["integer", "null"],
103
+ "description": "Whole days since generatedAt. Null when the stamp is absent or unparseable — never a fabricated 0. Wall time only: a baseline can read 0 here and still predate the surface it scores, which is what staleCommits catches."
104
+ },
105
+ "staleCommits": {
106
+ "type": ["integer", "null"],
107
+ "description": "Commits touching the measured surface (the gate's targetDirs, else the baseline's own row paths) since the baseline was last committed. Null when git cannot answer or the surface is not path-keyed — an unknown count, never a reassuring 0.",
108
+ "minimum": 0
109
+ },
110
+ "surfaceStale": {
111
+ "type": ["boolean", "null"],
112
+ "description": "staleCommits > 0 — the baseline is older than the surface it scores, independent of wall-clock age. Null when staleCommits is unknown."
113
+ },
114
+ "deadIgnoreGlobs": {
115
+ "type": "array",
116
+ "description": "Configured ignoreGlobs matching zero files on disk.",
117
+ "items": { "type": "string" }
118
+ },
119
+ "parseError": { "type": ["string", "null"] }
120
+ }
121
+ }
122
+ },
123
+ "hotspots": {
124
+ "type": "array",
125
+ "description": "Per-file clusters, highest rank first. Severity sums across gate memberships, so a file that is an outlier in two gates outranks a single-gate outlier of equal per-gate severity.",
126
+ "items": {
127
+ "type": "object",
128
+ "required": [
129
+ "path",
130
+ "gates",
131
+ "gateKinds",
132
+ "gateCount",
133
+ "severityWeight",
134
+ "churnWeight",
135
+ "centralityWeight",
136
+ "frictionWeight",
137
+ "rank"
138
+ ],
139
+ "additionalProperties": false,
140
+ "properties": {
141
+ "path": {
142
+ "type": "string",
143
+ "minLength": 1,
144
+ "description": "Cluster key. A repository file path for every kind except lighthouse (route) and bundle-size (bundle name)."
145
+ },
146
+ "gates": {
147
+ "type": "array",
148
+ "minItems": 1,
149
+ "items": {
150
+ "type": "object",
151
+ "required": [
152
+ "kind",
153
+ "metric",
154
+ "value",
155
+ "rowCount",
156
+ "severityWeight"
157
+ ],
158
+ "additionalProperties": false,
159
+ "properties": {
160
+ "kind": { "type": "string", "minLength": 1 },
161
+ "metric": { "type": "string", "minLength": 1 },
162
+ "value": { "type": "number" },
163
+ "rowCount": { "type": "integer", "minimum": 1 },
164
+ "severityWeight": { "type": "number", "minimum": 0 }
165
+ }
166
+ }
167
+ },
168
+ "gateKinds": { "type": "array", "items": { "type": "string" } },
169
+ "gateCount": { "type": "integer", "minimum": 1 },
170
+ "severityWeight": { "type": "number", "minimum": 0 },
171
+ "churnWeight": { "type": "number", "minimum": 1 },
172
+ "centralityWeight": { "type": "number", "minimum": 1 },
173
+ "frictionWeight": { "type": "number", "minimum": 1 },
174
+ "rank": { "type": "number", "minimum": 0 }
175
+ }
176
+ }
177
+ },
178
+ "trend": {
179
+ "type": "array",
180
+ "description": "Newest-vs-previous rollup deltas parsed from each baseline file's git history. Empty when no history is readable.",
181
+ "items": {
182
+ "type": "object",
183
+ "required": [
184
+ "kind",
185
+ "baselinePath",
186
+ "sampleCount",
187
+ "from",
188
+ "to",
189
+ "deltas"
190
+ ],
191
+ "additionalProperties": false,
192
+ "properties": {
193
+ "kind": { "type": "string", "minLength": 1 },
194
+ "baselinePath": { "type": "string", "minLength": 1 },
195
+ "sampleCount": { "type": "integer", "minimum": 2 },
196
+ "from": { "$ref": "#/definitions/commitRef" },
197
+ "to": { "$ref": "#/definitions/commitRef" },
198
+ "deltas": {
199
+ "type": "object",
200
+ "description": "Axis-by-axis `to - from`. Each key NAMES THE UNIT the delta is denominated in — `symbols` for dead exports, `bytes` for context budget, `filesTracked` where the metric is a non-additive score with no honest whole-repo sum. A kind carrying its own rollup keeps that rollup's axes.",
201
+ "additionalProperties": { "type": "number" }
202
+ }
203
+ }
204
+ }
205
+ },
206
+ "headroom": {
207
+ "type": "array",
208
+ "description": "Configured floor vs measured rollup per axis. Positive headroom is slack the floor could be tightened into; negative means the floor is already breached.",
209
+ "items": {
210
+ "type": "object",
211
+ "required": [
212
+ "kind",
213
+ "axis",
214
+ "floor",
215
+ "measured",
216
+ "direction",
217
+ "headroom"
218
+ ],
219
+ "additionalProperties": false,
220
+ "properties": {
221
+ "kind": { "type": "string", "minLength": 1 },
222
+ "axis": { "type": "string", "minLength": 1 },
223
+ "floor": { "type": "number" },
224
+ "measured": { "type": ["number", "null"] },
225
+ "direction": { "enum": ["gte", "lte"] },
226
+ "headroom": { "type": ["number", "null"] }
227
+ }
228
+ }
229
+ }
230
+ },
231
+ "definitions": {
232
+ "commitRef": {
233
+ "type": "object",
234
+ "required": ["sha", "committedAt"],
235
+ "additionalProperties": false,
236
+ "properties": {
237
+ "sha": { "type": "string", "minLength": 7 },
238
+ "committedAt": { "type": "string", "minLength": 1 }
239
+ }
240
+ }
241
+ }
242
+ }
@@ -29,6 +29,10 @@
29
29
  "type": "string",
30
30
  "description": "Optional per-kind stamp identifying the TypeScript transpiler that produced the sourcemap these rows' line coordinates were resolved through (Story #4866). Only meaningful for kinds whose rows carry a line coordinate derived from transpiled sources; a transpiler change can move every such coordinate, and for kinds that key rows on a line that coordinate is half the row identity."
31
31
  },
32
+ "provenanceStamped": {
33
+ "type": "boolean",
34
+ "description": "Optional per-kind POSITIVE assertion that the writer which produced these rows recorded each row's coordinate provenance (Story #4901). Only meaningful for kinds whose rows carry a line coordinate that may be transpiled. Absence is the only evidence a baseline written before provenance existed leaves behind — such a baseline claims by omission that every row is an original-source coordinate — so the kind's compat axis fails it closed with re-seed guidance rather than letting the comparator key rows against a coordinate space they are not in."
35
+ },
32
36
  "rollup": {
33
37
  "type": "object",
34
38
  "description": "Aggregate metrics keyed by component. The '*' key is reserved for the whole-repo rollup and is REQUIRED; additional keys MAY be present, one per declared component.",
@@ -10,6 +10,10 @@
10
10
  "type": "string",
11
11
  "description": "Identifier for the per-method coverage-join semantics that produced these rows (Story #4775). Deliberately NOT constrained to the current value here: an old-semantics baseline must reach the gate's compat axis, which fails it closed with an explicit re-baseline command, rather than being rejected with a raw schema error."
12
12
  },
13
+ "provenanceStamped": {
14
+ "type": "boolean",
15
+ "description": "Positive assertion that the writer which produced these rows recorded coordinate provenance per row (Story #4901). Absence is the ONLY evidence a pre-Story-#4866 baseline leaves — such a baseline claims by omission that every row is an original-source coordinate while some are transpiled. Deliberately optional and unconstrained here for the same reason as scoringSemantics: an unstamped baseline must reach the `provenance-unstamped` compat axis, which fails it closed with the re-seed command when it also carries transpiled-source rows, rather than dying on a raw schema error. A pure-JavaScript baseline never needs it."
16
+ },
13
17
  "rollup": {
14
18
  "type": "object",
15
19
  "required": ["*"],
@@ -49,6 +53,10 @@
49
53
  "method": { "type": "string" },
50
54
  "startLine": { "type": "integer", "minimum": 1 },
51
55
  "crap": { "type": "number", "minimum": 0 },
56
+ "anonymous": {
57
+ "enum": [true],
58
+ "description": "Marks `method` as a DERIVED identity rather than a name the source carries (Story #4969). escomplex labels an unnamed function `<anon method-N>`, where N is a per-module counter, so inserting one anonymous function renumbers every later one and re-keys 34.6% of this baseline's rows; the derived identity is the method's enclosing-scope path plus its parameter list, which is invariant under edits elsewhere in the file. Written ONLY when true, mirroring `coordinateSystem`: a named row keeps the exact four-key shape it always had. Absence on a row whose `method` still looks anonymous is the signature of a half-migrated baseline — rows preserved verbatim by a diff-scoped refresh under an envelope stamped with the current semantics — which the `anon-identity-unstamped` compat axis fails closed."
59
+ },
52
60
  "coordinateSystem": {
53
61
  "enum": ["transpiled"],
54
62
  "description": "Coordinate provenance of startLine (Story #4866). Written ONLY when the row kept transpiled coordinates because its sourcemap lookup did not resolve — the absent-means-'original' default keeps a pure-JavaScript baseline byte-identical, since JavaScript coordinates already ARE original-source coordinates. A row carrying this key is not comparable to one without it: the compare refuses to resolve the two through its nearest-line drift heuristic rather than manufacturing a regression from the gap between two coordinate systems."
@@ -3,6 +3,10 @@
3
3
  "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/model-attribution.schema.json",
4
4
  "title": "ModelAttribution",
5
5
  "description": "Payload of the <!-- structured:model-attribution --> comment upserted onto a Task ticket at the moment it transitions to agent::executing (Story #2813). One entry per Task. Story- and Epic-level breakdowns are derived at query time from the child Tasks' attribution comments — there is no Story/Epic-scope emission.",
6
+ "x-mandrel-uncompiled": {
7
+ "reason": "Deliberate: this document is the SSOT for the shape, but no AJV instance compiles it. The framework does not pull AJV into the structured-comment path — that path uses hand-rolled shape guards — so the runtime gate below mirrors this document by hand. Keep the two in step: an edit here is only real once the validator enforces it. Declared in-file per Story #4938 so a reader is never left inferring authority from the file's mere existence.",
8
+ "runtimeGate": ".agents/scripts/lib/orchestration/model-attribution.js#validateModelAttributionPayload"
9
+ },
6
10
  "type": "object",
7
11
  "additionalProperties": false,
8
12
  "required": ["kind", "ticketId", "model", "source", "recordedAt"],