mandrel 2.31.0 → 2.32.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 (250) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +4 -4
  4. package/.agents/docs/agentrc-reference.json +61 -57
  5. package/.agents/docs/configuration.md +274 -227
  6. package/.agents/docs/execution-reference.md +13 -14
  7. package/.agents/docs/quality-gates.md +195 -23
  8. package/.agents/instructions.md +2 -5
  9. package/.agents/rules/git-conventions-reference.md +27 -27
  10. package/.agents/rules/git-conventions.md +4 -2
  11. package/.agents/rules/known-tooling-behavior.md +66 -30
  12. package/.agents/rules/testing-standards.md +35 -71
  13. package/.agents/runtime-deps.json +0 -1
  14. package/.agents/schemas/agentrc.schema.json +1939 -1400
  15. package/.agents/schemas/lifecycle/README.md +21 -14
  16. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  17. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  18. package/.agents/scripts/README.md +7 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/bootstrap.js +28 -26
  21. package/.agents/scripts/check-baseline-drift.js +73 -13
  22. package/.agents/scripts/check-baseline-scope.js +362 -0
  23. package/.agents/scripts/check-dead-exports.js +9 -1
  24. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  25. package/.agents/scripts/check-knip-entries.js +136 -0
  26. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  27. package/.agents/scripts/check-schema-references.js +1 -1
  28. package/.agents/scripts/diagnose-friction.js +7 -4
  29. package/.agents/scripts/generate-config-docs.js +263 -171
  30. package/.agents/scripts/install-matrix-assert.js +0 -1
  31. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  32. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  33. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  34. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  35. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  36. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  37. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  38. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  39. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  40. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  41. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  42. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  43. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  44. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  45. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  46. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  47. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  48. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  49. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  50. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  51. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  52. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  53. package/.agents/scripts/lib/child-exec.js +193 -0
  54. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  55. package/.agents/scripts/lib/cli-args.js +64 -0
  56. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  57. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  58. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  59. package/.agents/scripts/lib/config/commands.js +3 -5
  60. package/.agents/scripts/lib/config/explain.js +5 -7
  61. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  62. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  63. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  64. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  65. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  66. package/.agents/scripts/lib/config/gates/index.js +5 -2
  67. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  68. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  69. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  70. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  71. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  72. package/.agents/scripts/lib/config/github.js +0 -1
  73. package/.agents/scripts/lib/config/limits.js +3 -48
  74. package/.agents/scripts/lib/config/qa.js +105 -0
  75. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  76. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  77. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  78. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  79. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  80. package/.agents/scripts/lib/crap-utils.js +84 -520
  81. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  82. package/.agents/scripts/lib/degraded-mode.js +2 -2
  83. package/.agents/scripts/lib/doc-tiers.js +3 -3
  84. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  85. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  86. package/.agents/scripts/lib/fs-walk.js +52 -0
  87. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  88. package/.agents/scripts/lib/git-utils.js +16 -36
  89. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  90. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  91. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  92. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  93. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  94. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  95. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  96. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  97. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  98. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  99. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  100. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  101. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  102. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  103. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  104. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  105. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  106. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  107. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  108. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  109. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  110. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  111. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  112. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  122. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  123. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  124. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  125. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  126. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  127. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  128. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  129. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  130. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  131. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  132. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  133. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  134. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  135. package/.agents/scripts/lib/signals/index.js +8 -6
  136. package/.agents/scripts/lib/signals/schema.js +20 -25
  137. package/.agents/scripts/lib/signals/write.js +8 -8
  138. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  139. package/.agents/scripts/lib/temp-retention.js +1 -1
  140. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  141. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  142. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  143. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  144. package/.agents/scripts/lib/worktree-manager.js +2 -3
  145. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  146. package/.agents/scripts/pr-watch-with-update.js +7 -5
  147. package/.agents/scripts/providers/github/cache.js +2 -2
  148. package/.agents/scripts/providers/github/comments.js +6 -28
  149. package/.agents/scripts/providers/github/compose.js +0 -15
  150. package/.agents/scripts/providers/github/errors.js +10 -27
  151. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  152. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  153. package/.agents/scripts/providers/github.js +4 -7
  154. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  155. package/.agents/scripts/resolve-stories.js +0 -2
  156. package/.agents/scripts/run-lint.js +61 -61
  157. package/.agents/scripts/run-test-profile.js +6 -6
  158. package/.agents/scripts/run-verify.js +48 -30
  159. package/.agents/scripts/single-story-close.js +20 -0
  160. package/.agents/scripts/single-story-init.js +12 -35
  161. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  162. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  163. package/.agents/skills/skills.index.json +1 -11
  164. package/.agents/workflows/audit-documentation.md +5 -6
  165. package/.agents/workflows/audit-to-stories.md +2 -2
  166. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  167. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  168. package/.agents/workflows/helpers/code-review.md +3 -8
  169. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  170. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  171. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  172. package/.agents/workflows/mandrel-update.md +10 -10
  173. package/.agents/workflows/qa-assist.md +15 -20
  174. package/.agents/workflows/qa-explore.md +9 -8
  175. package/README.md +1 -1
  176. package/docs/CHANGELOG.md +42 -0
  177. package/lib/migrations/index.js +2 -0
  178. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  179. package/package.json +12 -3
  180. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  181. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  182. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  183. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  184. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  185. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  186. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  187. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  188. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  189. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  190. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  191. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  192. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  193. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  194. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  195. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  196. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  197. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  198. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  199. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  200. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  201. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  202. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  203. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  204. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  205. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  206. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  207. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  208. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  209. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  210. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  211. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  212. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  213. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  214. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  215. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  216. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  217. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  218. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  219. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  220. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  221. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  222. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  223. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  224. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  225. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  226. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  227. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  228. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  229. package/.agents/scripts/lib/planning-corpus.js +0 -37
  230. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  231. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  232. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  233. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  234. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  235. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  236. package/.agents/scripts/lib/signals/read.js +0 -268
  237. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  238. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  239. package/.agents/scripts/lib/story-plan.js +0 -379
  240. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  241. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  242. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  243. package/.agents/scripts/lint-baseline.js +0 -507
  244. package/.agents/scripts/providers/github/prs.js +0 -103
  245. package/.agents/scripts/signals-view.js +0 -309
  246. package/.agents/scripts/story-plan.js +0 -370
  247. package/.agents/scripts/sync-branch-from-base.js +0 -149
  248. package/.agents/scripts/validate-docs-freshness.js +0 -314
  249. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  250. package/.agents/workflows/helpers/signals.md +0 -112
@@ -1,18 +1,25 @@
1
1
  # Lifecycle event schemas
2
2
 
3
- JSON Schemas for the lifecycle event taxonomy consumed by the
4
- `/deliver` lifecycle bus
5
- (`lib/orchestration/lifecycle/bus.js`). The bus validates every
6
- emit payload against one of these schemas before invoking
7
- listeners; a schema mismatch fails the emit and propagates the
8
- throw.
3
+ JSON Schemas for the two events a delivery run can append to its per-run
4
+ lifecycle ledger. `appendLedgerEvent`
5
+ (`lib/orchestration/lifecycle/emit-ledger-event.js`) validates every payload
6
+ against one of these before the write; a schema mismatch throws and nothing is
7
+ appended.
9
8
 
10
- Each event in the Tech Spec taxonomy has a `<event>.schema.json`
11
- file. The ledger record (`emitted | completed | failed` union)
12
- lives in `ledger-record.schema.json` and is consumed by
13
- `ledger-writer.js`.
9
+ `ledger-record.schema.json` is the NDJSON record envelope
10
+ (`emitted | completed | failed`), not an event. Only `emitted` has a writer —
11
+ see the schema's own description for why the other two kinds remain.
14
12
 
15
- Schemas are intentionally permissive (`additionalProperties: true`)
16
- on inner objects whose shape is dictated by upstream tooling (e.g.
17
- `gh pr view` JSON, `checkOutcomes`). The required-key set is the
18
- contract.
13
+ **A schema belongs here only while code emits its event.** Story #4545 applied
14
+ that rule to the `epic.*` and `acceptance.reconcile.*` families; Story #5024
15
+ applied it to the remaining fifteen when it retired the lifecycle bus that had
16
+ been their only publish path. `tests/lifecycle/schema-registry.test.js` enforces
17
+ it in both directions, so a schema file for an event nobody emits fails the
18
+ suite rather than reading green.
19
+
20
+ Schemas are intentionally permissive (`additionalProperties: true`) on inner
21
+ objects whose shape is dictated by upstream tooling (e.g. `gh pr view` JSON).
22
+ The required-key set is the contract.
23
+
24
+ Full reference:
25
+ [`docs/LIFECYCLE.md`](https://github.com/dsj1984/mandrel/blob/main/docs/LIFECYCLE.md).
@@ -2,22 +2,41 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/lifecycle/ledger-record.schema.json",
4
4
  "title": "Lifecycle ledger record (emitted | completed | failed)",
5
- "description": "Append-only NDJSON record shape for temp/run-<id>/lifecycle.ndjson. Three discriminated kinds; consumers (LedgerWriter, TraceLogger) discriminate on `kind`.",
5
+ "description": "Append-only NDJSON record shape for the per-run lifecycle ledger. Three discriminated kinds keyed on `kind`. Only `emitted` has a writer: Story #5024 retired the lifecycle bus, so the `completed` / `failed` kinds are a READ contract for ledgers archived from the bus era (`failed` requires a `listener`, which only a listener chain could supply). Reading a ledger never consults this schema; validation happens on the write path only.",
6
6
  "oneOf": [
7
- { "$ref": "#/$defs/emitted" },
8
- { "$ref": "#/$defs/completed" },
9
- { "$ref": "#/$defs/failed" }
7
+ {
8
+ "$ref": "#/$defs/emitted"
9
+ },
10
+ {
11
+ "$ref": "#/$defs/completed"
12
+ },
13
+ {
14
+ "$ref": "#/$defs/failed"
15
+ }
10
16
  ],
11
17
  "$defs": {
12
18
  "emitted": {
13
19
  "type": "object",
14
20
  "required": ["kind", "seqId", "ts", "event", "payload"],
15
21
  "properties": {
16
- "kind": { "const": "emitted" },
17
- "seqId": { "type": "integer", "minimum": 1 },
18
- "ts": { "type": "string", "format": "date-time" },
19
- "event": { "type": "string", "minLength": 1 },
20
- "payload": { "type": "object" }
22
+ "kind": {
23
+ "const": "emitted"
24
+ },
25
+ "seqId": {
26
+ "type": "integer",
27
+ "minimum": 1
28
+ },
29
+ "ts": {
30
+ "type": "string",
31
+ "format": "date-time"
32
+ },
33
+ "event": {
34
+ "type": "string",
35
+ "minLength": 1
36
+ },
37
+ "payload": {
38
+ "type": "object"
39
+ }
21
40
  },
22
41
  "additionalProperties": false
23
42
  },
@@ -25,11 +44,25 @@
25
44
  "type": "object",
26
45
  "required": ["kind", "seqId", "ts", "event"],
27
46
  "properties": {
28
- "kind": { "const": "completed" },
29
- "seqId": { "type": "integer", "minimum": 1 },
30
- "ts": { "type": "string", "format": "date-time" },
31
- "event": { "type": "string", "minLength": 1 },
32
- "listener": { "type": "string", "minLength": 1 }
47
+ "kind": {
48
+ "const": "completed"
49
+ },
50
+ "seqId": {
51
+ "type": "integer",
52
+ "minimum": 1
53
+ },
54
+ "ts": {
55
+ "type": "string",
56
+ "format": "date-time"
57
+ },
58
+ "event": {
59
+ "type": "string",
60
+ "minLength": 1
61
+ },
62
+ "listener": {
63
+ "type": "string",
64
+ "minLength": 1
65
+ }
33
66
  },
34
67
  "additionalProperties": false
35
68
  },
@@ -37,18 +70,39 @@
37
70
  "type": "object",
38
71
  "required": ["kind", "seqId", "ts", "event", "listener", "error"],
39
72
  "properties": {
40
- "kind": { "const": "failed" },
41
- "seqId": { "type": "integer", "minimum": 1 },
42
- "ts": { "type": "string", "format": "date-time" },
43
- "event": { "type": "string", "minLength": 1 },
44
- "listener": { "type": "string", "minLength": 1 },
73
+ "kind": {
74
+ "const": "failed"
75
+ },
76
+ "seqId": {
77
+ "type": "integer",
78
+ "minimum": 1
79
+ },
80
+ "ts": {
81
+ "type": "string",
82
+ "format": "date-time"
83
+ },
84
+ "event": {
85
+ "type": "string",
86
+ "minLength": 1
87
+ },
88
+ "listener": {
89
+ "type": "string",
90
+ "minLength": 1
91
+ },
45
92
  "error": {
46
93
  "type": "object",
47
94
  "required": ["name", "message"],
48
95
  "properties": {
49
- "name": { "type": "string", "minLength": 1 },
50
- "message": { "type": "string" },
51
- "stack": { "type": "string" }
96
+ "name": {
97
+ "type": "string",
98
+ "minLength": 1
99
+ },
100
+ "message": {
101
+ "type": "string"
102
+ },
103
+ "stack": {
104
+ "type": "string"
105
+ }
52
106
  },
53
107
  "additionalProperties": false
54
108
  }
@@ -73,10 +73,10 @@
73
73
  },
74
74
  "gates": {
75
75
  "type": "object",
76
- "description": "Outcome of each close gate this invocation was responsible for. A gate the run skipped (--skip-validation, or a phase it never reached) reports \"skipped\" rather than being omitted, so a missing gate is never mistaken for a passing one.",
76
+ "description": "Outcome of each close gate this invocation was responsible for. A gate the run skipped (--skip-validation, or a phase it never reached) reports \"skipped\" rather than being omitted, so a missing gate is never mistaken for a passing one. \"overridden\" is distinct from both: the gate ran, it FAILED, and an operator authorized delivery anyway via --override-review-block with a recorded reason. Reporting that as \"passed\" would erase the one fact a reader of this envelope most needs, and reporting it as \"skipped\" would claim the gate never ran.",
77
77
  "additionalProperties": {
78
78
  "type": "string",
79
- "enum": ["passed", "failed", "skipped"]
79
+ "enum": ["passed", "failed", "skipped", "overridden"]
80
80
  }
81
81
  },
82
82
  "tail": {
@@ -5,35 +5,13 @@ invoked indirectly by `npm run …`, slash-command workflows
5
5
  (`.agents/workflows/*.md`), or Husky / GitHub Actions hooks; you rarely
6
6
  need to call them by hand.
7
7
 
8
- This file is **not** an exhaustive index of the ~90 top-level entrypoints.
9
- It documents the **operator-facing scripts** that operators may want to
10
- run by hand and that are **not** wired into the standard quality / CI
11
- surface. For everything else, search `package.json` scripts and
12
- `.agents/workflows/` first.
13
-
14
- ## Operator Scripts
15
-
16
- These scripts are kept in the distributed product but are intentionally
17
- not invoked by `npm test`, `npm run verify`, CI, or any Husky hook. They
18
- are optional operator tools; run them by hand when you need them.
19
-
20
- ### `validate-docs-freshness.js`
21
-
22
- **Purpose.** Per-Epic documentation freshness gate. For each doc in
23
- `delivery.docsFreshness.paths` + `project.docsContextFiles`, asserts
24
- that the file was meaningfully updated during this Epic's lifecycle
25
- (commit message references `#<epicId>` or the file body does).
26
-
27
- **When to run.** Optional. Useful as a pre-merge spot check when an
28
- Epic should have produced documentation updates; the standard
29
- `/deliver` flow does **not** invoke this gate today.
30
-
31
- **Usage.**
32
-
33
- ```bash
34
- node .agents/scripts/validate-docs-freshness.js --epic <id> \
35
- [--base main] [--docs <comma-separated>] [--json]
36
- ```
8
+ This file is **not** an exhaustive index of the ~90 top-level entrypoints
9
+ it is the orientation pointer for the directory. Every script documents
10
+ its own flags under `--help`, and each is reachable from a real caller:
11
+ search `package.json` scripts, `.agents/workflows/`, and the Husky /
12
+ GitHub Actions surfaces first. `check-knip-entries.js` derives that
13
+ caller set mechanically, so a CLI no invoker names is dead, not
14
+ operator-only.
37
15
 
38
16
  ## See Also
39
17
 
@@ -11,63 +11,56 @@
11
11
  * had no test so it silently drifted when the two helper signatures moved, and
12
12
  * it could not be invoked or dry-run independently.
13
13
  *
14
- * This script runs the two Epic #1386 quality-gate installs in order against
15
- * the consumer repo root:
14
+ * The script runs one install against the consumer repo root:
15
+ * `applyQualityBootstrap` — copies the code-quality-guardrails helper,
16
+ * installs the `.husky/pre-commit` quality:preview line, backfills the
17
+ * `quality:preview` / `quality:watch` npm scripts, seeds the
18
+ * `delivery.quality.{codingGuardrails,autoRefresh}` defaults, and prunes a
19
+ * committed pre-v2 `baselines/epic/` tree.
16
20
  *
17
- * 1. `applyQualityBootstrap` copies the code-quality-guardrails helper,
18
- * installs the `.husky/pre-commit` quality:preview line, backfills the
19
- * `quality:preview` / `quality:watch` npm scripts, and seeds the
20
- * `delivery.quality.{codingGuardrails,autoRefresh}` defaults.
21
- * 2. `migrateBaselinesLayout` relocates per-Epic baseline snapshots into
22
- * the `temp/epic/<id>/baselines/` namespace.
21
+ * Story #5007 retired the second step. `migrateBaselinesLayout` relocated
22
+ * per-Epic ratchet snapshots into `temp/epic/<id>/baselines/` on the contract
23
+ * that `/deliver` reaps that namespace on merge — a mechanism the Story-only
24
+ * v2 model deleted, so the migration moved dead data into a namespace no code
25
+ * path writes, reads, or reaps. Its one residual hygiene value (getting the
26
+ * committed `baselines/epic/` tree out of version control) survives as
27
+ * `pruneLegacyEpicBaselines`, the quality install's fifth step.
23
28
  *
24
- * Both helpers are idempotent by contract — a second run reports `no-change`
25
- * on every install path — so this wrapper is safe to re-run. It prints the
26
- * **same JSON result shape** the heredoc did: `{ quality, baselines }` to
27
- * stdout, so any tooling that parsed the old output keeps working.
29
+ * The helper is idempotent by contract — a second run reports `no-change` /
30
+ * `already-present` / `absent` on every install path — so this wrapper is safe
31
+ * to re-run. It prints `{ quality }` JSON to stdout.
28
32
  *
29
33
  * The effectful work is a thin pure function (`applyBootstrapAndMigration`)
30
- * that takes the two helpers and the project root, so the test suite can
31
- * drive it against a tmp directory without spawning a child process. The CLI
32
- * wrapper wires the real helpers and `process.cwd()`.
34
+ * that takes the helper and the project root, so the test suite can drive it
35
+ * against a tmp directory without spawning a child process. The CLI wrapper
36
+ * wires the real helper and `process.cwd()`.
33
37
  */
34
38
 
35
- import path from 'node:path';
36
- import { migrateBaselinesLayout } from './lib/bootstrap/baselines-layout-migration.js';
37
39
  import { applyQualityBootstrap } from './lib/bootstrap/quality-bootstrap.js';
38
40
  import { runAsCli } from './lib/cli-utils.js';
39
41
 
40
42
  /**
41
- * Run the quality-bootstrap install and the baselines-layout migration
42
- * against `projectRoot`, returning the combined `{ quality, baselines }`
43
- * envelope. Pure relative to its injected helpers: the default helpers touch
44
- * the filesystem under `projectRoot`, but tests can pass stubs to exercise
45
- * the composition in isolation.
43
+ * Run the quality-bootstrap install against `projectRoot`, returning the
44
+ * `{ quality }` envelope. Pure relative to its injected helper: the default
45
+ * helper touches the filesystem under `projectRoot`, but tests can pass a stub
46
+ * to exercise the composition in isolation.
46
47
  *
47
48
  * @param {object} options
48
49
  * @param {string} options.projectRoot Absolute consumer repo root.
49
50
  * @param {typeof applyQualityBootstrap} [options.applyQualityBootstrap]
50
- * @param {typeof migrateBaselinesLayout} [options.migrateBaselinesLayout]
51
- * @returns {{ quality: object, baselines: object }}
51
+ * @returns {{ quality: object }}
52
52
  */
53
53
  export function applyBootstrapAndMigration({
54
54
  projectRoot,
55
55
  applyQualityBootstrap: applyQuality = applyQualityBootstrap,
56
- migrateBaselinesLayout: migrateBaselines = migrateBaselinesLayout,
57
56
  }) {
58
- const quality = applyQuality({ projectRoot });
59
- const baselines = migrateBaselines({
60
- baselinesDir: path.join(projectRoot, 'baselines'),
61
- repoRoot: projectRoot,
62
- });
63
- return { quality, baselines };
57
+ return { quality: applyQuality({ projectRoot }) };
64
58
  }
65
59
 
66
60
  async function main() {
67
61
  const projectRoot = process.cwd();
68
62
  const result = applyBootstrapAndMigration({ projectRoot });
69
- // Mirror the retired heredoc's output: pretty-printed `{ quality, baselines }`
70
- // to stdout. Use process.stdout.write (not console.log) per the no-console
63
+ // Use process.stdout.write (not console.log) per the no-console
71
64
  // enforcement boundary.
72
65
  process.stdout.write(`${JSON.stringify(result)}\n`);
73
66
  return 0;
@@ -79,7 +72,7 @@ runAsCli(import.meta.url, main, {
79
72
  usage: {
80
73
  invocation: 'node .agents/scripts/apply-quality-bootstrap.js',
81
74
  summary:
82
- 'Install the quality-gate surface into the consumer repo (guardrails helper, pre-commit line, npm scripts, config defaults) and migrate the baselines layout. Idempotent; prints { quality, baselines } JSON to stdout.',
75
+ 'Install the quality-gate surface into the consumer repo (guardrails helper, pre-commit line, npm scripts, config defaults, legacy baselines/epic prune). Idempotent; prints { quality } JSON to stdout.',
83
76
  flags: [],
84
77
  },
85
78
  });
@@ -726,19 +726,25 @@ function githubSubMutationsSucceeded(gh) {
726
726
  return true;
727
727
  }
728
728
 
729
- /** Phase groups whose mutations actually landed, for the install ledger. */
730
- function resolveAppliedGroups(approvedGroups, report) {
731
- const applied = new Set();
732
- for (const group of approvedGroups ?? []) {
733
- if (group === PHASE_GROUPS.GITHUB_ADMIN) {
734
- const gh = report?.github;
735
- if (gh && !gh.error && !gh.skipped && githubSubMutationsSucceeded(gh)) {
736
- applied.add(group);
737
- }
738
- continue;
739
- }
740
- applied.add(group);
741
- }
729
+ /**
730
+ * Phase groups whose mutations actually landed, for the install ledger.
731
+ *
732
+ * Every project-side group runs on every install (Story #3690 replaced the
733
+ * consent-first phased-approval install with a plain summary+confirm loop),
734
+ * so the only variable is whether the irreversible GitHub-admin mutations
735
+ * landed. Story #5007 collapsed the set-threading this used to carry down to
736
+ * that one boolean.
737
+ *
738
+ * @param {object|undefined} report — the live execution report.
739
+ * @returns {Set<string>}
740
+ */
741
+ function resolveAppliedGroups(report) {
742
+ const applied = new Set(Object.values(PHASE_GROUPS));
743
+ const gh = report?.github;
744
+ const githubApplied = Boolean(
745
+ gh && !gh.error && !gh.skipped && githubSubMutationsSucceeded(gh),
746
+ );
747
+ if (!githubApplied) applied.delete(PHASE_GROUPS.GITHUB_ADMIN);
742
748
  return applied;
743
749
  }
744
750
 
@@ -1210,23 +1216,22 @@ export async function provisionResources(state, deps = {}) {
1210
1216
  }
1211
1217
 
1212
1218
  /**
1213
- * Step 6a — Project-side bootstrap. With phased approval removed, all
1214
- * project-side phase groups are treated as approved.
1219
+ * Step 6a — Project-side bootstrap. Every project-side phase runs; the
1220
+ * install ledger decides afterwards which phase groups actually landed
1221
+ * ({@link resolveAppliedGroups}).
1215
1222
  */
1216
1223
  export async function executeBootstrap(state) {
1217
1224
  Logger.info(
1218
1225
  `[Bootstrap] Starting project bootstrap at ${state.projectRoot} (owner=${state.answers.owner} repo=${state.answers.repo} base=${state.answers.baseBranch})`,
1219
1226
  );
1220
- const approvedGroups = new Set(Object.values(PHASE_GROUPS));
1221
1227
  const report = await applyProjectBootstrap({
1222
1228
  projectRoot: state.projectRoot,
1223
1229
  agentRoot: state.agentRoot,
1224
1230
  answers: state.answers,
1225
- approvedGroups,
1226
1231
  withQuality: state.withQuality === true,
1227
1232
  withIssueForms: state.withIssueForms === true,
1228
1233
  });
1229
- return { ok: true, payload: { report, approvedGroups } };
1234
+ return { ok: true, payload: { report } };
1230
1235
  }
1231
1236
 
1232
1237
  /**
@@ -1293,22 +1298,19 @@ export async function executeGithubBootstrap(state) {
1293
1298
 
1294
1299
  /** Step 6c — Record the install ledger for a future uninstall. */
1295
1300
  export function recordLedger(state) {
1296
- const appliedGroups = resolveAppliedGroups(
1297
- state.approvedGroups,
1298
- state.report,
1299
- );
1301
+ const appliedGroups = resolveAppliedGroups(state.report);
1300
1302
  const manifestCtx = {
1301
1303
  answers: state.answers,
1302
1304
  skipGithub: Boolean(state.flags['skip-github']),
1303
1305
  withQuality: state.withQuality === true,
1304
1306
  };
1307
+ // The manifest always carries the unconditional ide-wiring / repo-config
1308
+ // entries and `appliedGroups` always contains those groups, so the filter
1309
+ // never empties. (Story #5007 removed the phased-approval gate that was the
1310
+ // only way to reach an empty set.)
1305
1311
  const entries = buildMutationManifest(manifestCtx).filter((e) =>
1306
1312
  appliedGroups.has(e.phaseGroup),
1307
1313
  );
1308
- if (entries.length === 0) {
1309
- state.report.ledger = { written: false, reason: 'no-mutations-applied' };
1310
- return { ok: true, payload: {} };
1311
- }
1312
1314
  const record = buildLedgerRecord({
1313
1315
  entries,
1314
1316
  approvedGroups: appliedGroups,
@@ -13,13 +13,23 @@
13
13
  // directory through the same scorer that writes the baseline, prints a per-row
14
14
  // before/after table for everything that moved beyond the gate's tolerance,
15
15
  // and exits non-zero when it finds any — so a consumer can wire it as a
16
- // scheduled CI job without wrapping it in verdict-parsing glue. Wiring it is
17
- // deliberately consumer-side work; nothing in this repo schedules it.
16
+ // scheduled CI job without wrapping it in verdict-parsing glue. This repo
17
+ // schedules the maintainability kind in `.github/workflows/baseline-drift.yml`;
18
+ // a consumer materializing `.agents/` still owns its own schedule.
19
+ //
20
+ // `--require-scored` exists because the default skip-is-green contract below
21
+ // is a fail-open trap for exactly that scheduled use. Measured on this repo:
22
+ // `check-baseline-drift.js --gate crap` with no `coverage/coverage-final.json`
23
+ // present prints "✅ No baseline drift detected" and exits 0 — a nightly job
24
+ // wired that way reports green while having measured nothing. The flag turns
25
+ // every skip into exit 2, so a job that asked for a kind and did not get it
26
+ // reds instead.
18
27
  //
19
28
  // Exit codes:
20
29
  // 0 — no drift (or every kind skipped: disabled gate, no baseline, no scorer)
21
30
  // 1 — drift detected in at least one kind
22
- // 2 — the check itself could not run
31
+ // 2 — the check itself could not run (including: a requested kind was
32
+ // skipped and `--require-scored` was passed)
23
33
 
24
34
  // Fail-fast if the framework's runtime deps are not installed — must be the
25
35
  // first import so the check runs before any third-party-importing sibling
@@ -42,6 +52,10 @@ diff-scoped gates structurally cannot see.
42
52
  Options:
43
53
  --gate <kind> Restrict to one kind (repeatable). Default: ${DRIFT_KINDS.join(', ')}.
44
54
  --tolerance <n> Override the per-gate absolute tolerance.
55
+ --require-scored Treat a skipped kind (gate disabled, no baseline, no
56
+ scorer, nothing scored) as a failure rather than a pass.
57
+ Use this in scheduled jobs: without it a kind that could
58
+ not be scored at all reports green.
45
59
  --json Emit the machine-readable report instead of the table.
46
60
  -h, --help Show this help.
47
61
 
@@ -55,16 +69,21 @@ commit the result with a \`baseline-refresh:\` tagged subject (non-empty body).`
55
69
  * cannot silently widen a scheduled job's scope.
56
70
  *
57
71
  * @param {string[]} argv
58
- * @returns {{ kinds: string[], tolerance: number|null, json: boolean }}
72
+ * @returns {{ kinds: string[], tolerance: number|null, json: boolean, requireScored: boolean }}
59
73
  */
60
74
  export function parseArgs(argv = []) {
61
75
  const kinds = [];
62
76
  let tolerance = null;
63
77
  let json = false;
64
- for (let i = 0; i < argv.length; i += 1) {
65
- const arg = argv[i];
66
- if (arg === '--gate' && argv[i + 1]) {
67
- const kind = argv[i + 1];
78
+ // Lifted out of the loop rather than added to its else-if chain: the chain
79
+ // is the file's most complex method already, and a valueless boolean flag
80
+ // needs no positional handling to be recognised.
81
+ const requireScored = argv.includes('--require-scored');
82
+ const rest = argv.filter((a) => a !== '--require-scored');
83
+ for (let i = 0; i < rest.length; i += 1) {
84
+ const arg = rest[i];
85
+ if (arg === '--gate' && rest[i + 1]) {
86
+ const kind = rest[i + 1];
68
87
  if (!DRIFT_KINDS.includes(kind)) {
69
88
  throw new Error(
70
89
  `[drift] unknown --gate "${kind}"; expected one of ${DRIFT_KINDS.join(', ')}`,
@@ -72,11 +91,11 @@ export function parseArgs(argv = []) {
72
91
  }
73
92
  kinds.push(kind);
74
93
  i += 1;
75
- } else if (arg === '--tolerance' && argv[i + 1]) {
76
- const value = Number(argv[i + 1]);
94
+ } else if (arg === '--tolerance' && rest[i + 1]) {
95
+ const value = Number(rest[i + 1]);
77
96
  if (!Number.isFinite(value)) {
78
97
  throw new Error(
79
- `[drift] --tolerance must be a number (got ${argv[i + 1]})`,
98
+ `[drift] --tolerance must be a number (got ${rest[i + 1]})`,
80
99
  );
81
100
  }
82
101
  tolerance = value;
@@ -91,9 +110,44 @@ export function parseArgs(argv = []) {
91
110
  kinds: kinds.length > 0 ? kinds : [...DRIFT_KINDS],
92
111
  tolerance,
93
112
  json,
113
+ requireScored,
94
114
  };
95
115
  }
96
116
 
117
+ /**
118
+ * Render the `--require-scored` verdict for a completed run.
119
+ *
120
+ * A skip is the detector's honest answer to "I could not score this kind" —
121
+ * `{ ok: true, skipped: '<reason>' }` — and the default contract maps that to
122
+ * a pass so an ad-hoc run does not red because coverage happened to be absent.
123
+ * Under `--require-scored` the same answer is a failure: the caller named the
124
+ * kinds it wanted measured, and a kind that was not measured is a gap, not a
125
+ * clean bill of health.
126
+ *
127
+ * Returns `null` when the flag is off or nothing was skipped, so the caller
128
+ * keeps the run's own 0/1 verdict untouched. Both "off" and "nothing skipped"
129
+ * are answered here rather than at the call site: the caller is the file's
130
+ * ratcheted entry point, and this is the branch's natural home anyway.
131
+ *
132
+ * @param {{ results?: Array<{ kind: string, skipped?: string }> }} run
133
+ * @param {boolean} requireScored Whether `--require-scored` was passed.
134
+ * @returns {string|null} the operator-facing failure note, or null.
135
+ */
136
+ function requireScoredFailure(run, requireScored) {
137
+ if (!requireScored) return null;
138
+ const skipped = (run?.results ?? []).filter((r) => r?.skipped);
139
+ if (skipped.length === 0) return null;
140
+ const detail = skipped.map((r) => `${r.kind} (${r.skipped})`).join(', ');
141
+ return (
142
+ `[drift] ❌ --require-scored: ${skipped.length} requested kind(s) were ` +
143
+ `not scored — ${detail}. The verdict above covers only the kinds that ` +
144
+ 'DID score, which is why it can read clean. A scheduled run that cannot ' +
145
+ 'measure a kind is not a clean run; fix the cause (a missing coverage ' +
146
+ 'artifact, a missing baseline, a disabled gate) or drop the kind from ' +
147
+ 'the invocation.'
148
+ );
149
+ }
150
+
97
151
  /**
98
152
  * Run the drift check and render it. Returns the process exit code rather
99
153
  * than exiting, so the whole path is unit-testable.
@@ -112,10 +166,16 @@ export async function runCheckBaselineDrift({
112
166
  cwd,
113
167
  tolerance: args.tolerance,
114
168
  });
115
- const output = args.json
169
+ const base = args.json
116
170
  ? JSON.stringify({ schemaVersion: '1', ...run }, null, 2)
117
171
  : formatDriftReport(run);
118
- return { exitCode: run.ok ? 0 : 1, output };
172
+ // The note is appended rather than substituted: the drift table is still the
173
+ // useful half of the report for whichever kinds DID score. A note present at
174
+ // all means a requested kind went unmeasured, which is exit 2 regardless of
175
+ // what the measured kinds reported.
176
+ const unscored = requireScoredFailure(run, args.requireScored);
177
+ if (unscored) return { exitCode: 2, output: `${base}\n${unscored}` };
178
+ return { exitCode: run.ok ? 0 : 1, output: base };
119
179
  }
120
180
 
121
181
  /**