mandrel 2.31.0 → 2.33.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 (267) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +12 -9
  4. package/.agents/docs/agentrc-reference.json +63 -58
  5. package/.agents/docs/configuration.md +275 -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 +2 -2
  14. package/.agents/schemas/agentrc.schema.json +1955 -1410
  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 +16 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/audit-to-stories.js +160 -41
  21. package/.agents/scripts/bootstrap.js +28 -26
  22. package/.agents/scripts/check-baseline-drift.js +73 -13
  23. package/.agents/scripts/check-baseline-scope.js +362 -0
  24. package/.agents/scripts/check-dead-exports.js +9 -1
  25. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  26. package/.agents/scripts/check-knip-entries.js +159 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +108 -124
  28. package/.agents/scripts/check-schema-references.js +1 -1
  29. package/.agents/scripts/diagnose-friction.js +7 -4
  30. package/.agents/scripts/generate-config-docs.js +263 -171
  31. package/.agents/scripts/install-matrix-assert.js +0 -1
  32. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  33. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  34. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  35. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  36. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  37. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +81 -35
  38. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
  39. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  40. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  41. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  42. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  43. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  44. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  45. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  46. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  47. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  48. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  49. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  50. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  51. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  52. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  53. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  54. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  55. package/.agents/scripts/lib/child-exec.js +193 -0
  56. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  57. package/.agents/scripts/lib/cli-args.js +64 -0
  58. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  59. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  60. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  61. package/.agents/scripts/lib/config/commands.js +3 -5
  62. package/.agents/scripts/lib/config/explain.js +5 -7
  63. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  64. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  65. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  66. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  67. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  68. package/.agents/scripts/lib/config/gates/index.js +5 -2
  69. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  70. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  71. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  72. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  73. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  74. package/.agents/scripts/lib/config/github.js +0 -1
  75. package/.agents/scripts/lib/config/limits.js +3 -48
  76. package/.agents/scripts/lib/config/qa.js +105 -0
  77. package/.agents/scripts/lib/config/runners.js +38 -16
  78. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  79. package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -56
  80. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  81. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  82. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  83. package/.agents/scripts/lib/crap-utils.js +84 -520
  84. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  85. package/.agents/scripts/lib/degraded-mode.js +2 -2
  86. package/.agents/scripts/lib/dependency-parser.js +20 -7
  87. package/.agents/scripts/lib/doc-tiers.js +3 -3
  88. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  89. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  90. package/.agents/scripts/lib/findings/provenance-field.js +135 -0
  91. package/.agents/scripts/lib/findings/route-finding.js +57 -8
  92. package/.agents/scripts/lib/fs-walk.js +52 -0
  93. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  94. package/.agents/scripts/lib/git-utils.js +16 -36
  95. package/.agents/scripts/lib/knip-config-resolver.js +181 -0
  96. package/.agents/scripts/lib/knip-entry-sync.js +508 -0
  97. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  98. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  99. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  100. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  101. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  102. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  103. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  104. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  105. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  106. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  107. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  108. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  109. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  110. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  111. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  112. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +7 -26
  114. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
  115. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
  116. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
  117. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  118. package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
  119. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  120. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  124. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  126. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  131. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  132. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  133. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  134. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  135. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  136. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  137. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  138. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
  139. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  140. package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
  141. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  142. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  143. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  144. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  145. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  146. package/.agents/scripts/lib/signals/index.js +8 -6
  147. package/.agents/scripts/lib/signals/schema.js +20 -25
  148. package/.agents/scripts/lib/signals/write.js +8 -8
  149. package/.agents/scripts/lib/story-body/footer-block.js +97 -0
  150. package/.agents/scripts/lib/story-body/story-body.js +18 -81
  151. package/.agents/scripts/lib/temp-retention.js +1 -1
  152. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  153. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  154. package/.agents/scripts/lib/wave-runner/footprint.js +306 -0
  155. package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
  156. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  157. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  158. package/.agents/scripts/lib/worktree-manager.js +2 -3
  159. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  160. package/.agents/scripts/pr-watch-with-update.js +7 -5
  161. package/.agents/scripts/providers/github/blocked-by-add.js +25 -10
  162. package/.agents/scripts/providers/github/cache.js +2 -2
  163. package/.agents/scripts/providers/github/comments.js +6 -28
  164. package/.agents/scripts/providers/github/compose.js +0 -15
  165. package/.agents/scripts/providers/github/errors.js +10 -27
  166. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  167. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  168. package/.agents/scripts/providers/github.js +4 -7
  169. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  170. package/.agents/scripts/resolve-stories.js +21 -7
  171. package/.agents/scripts/run-lint.js +61 -61
  172. package/.agents/scripts/run-test-profile.js +6 -6
  173. package/.agents/scripts/run-verify.js +48 -30
  174. package/.agents/scripts/single-story-close.js +20 -0
  175. package/.agents/scripts/single-story-init.js +12 -35
  176. package/.agents/scripts/stories-wave-tick.js +192 -9
  177. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  178. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  179. package/.agents/skills/skills.index.json +1 -11
  180. package/.agents/workflows/audit-documentation.md +5 -6
  181. package/.agents/workflows/audit-to-stories.md +28 -2
  182. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  183. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  184. package/.agents/workflows/helpers/code-review.md +3 -8
  185. package/.agents/workflows/helpers/deliver-reference.md +30 -2
  186. package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
  187. package/.agents/workflows/helpers/plan-reference.md +76 -0
  188. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  189. package/.agents/workflows/mandrel-update.md +10 -10
  190. package/.agents/workflows/qa-assist.md +15 -20
  191. package/.agents/workflows/qa-explore.md +9 -8
  192. package/README.md +1 -1
  193. package/docs/CHANGELOG.md +58 -0
  194. package/lib/migrations/index.js +2 -0
  195. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  196. package/package.json +14 -5
  197. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  198. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  199. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  200. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  201. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  202. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  203. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  204. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  205. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  206. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  207. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  208. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  209. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  210. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  211. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  212. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  213. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  214. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  215. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  216. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  217. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  218. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  219. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  220. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  221. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  222. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  223. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  224. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  225. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  226. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  227. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  228. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  229. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  230. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  231. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  232. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  233. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  234. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  235. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  236. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  237. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  238. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  239. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  240. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  241. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  242. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  243. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  244. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  245. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  246. package/.agents/scripts/lib/planning-corpus.js +0 -37
  247. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  248. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  249. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  250. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  251. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  252. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  253. package/.agents/scripts/lib/signals/read.js +0 -268
  254. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  255. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  256. package/.agents/scripts/lib/story-plan.js +0 -379
  257. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  258. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  259. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  260. package/.agents/scripts/lint-baseline.js +0 -507
  261. package/.agents/scripts/providers/github/prs.js +0 -103
  262. package/.agents/scripts/signals-view.js +0 -309
  263. package/.agents/scripts/story-plan.js +0 -370
  264. package/.agents/scripts/sync-branch-from-base.js +0 -149
  265. package/.agents/scripts/validate-docs-freshness.js +0 -314
  266. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  267. package/.agents/workflows/helpers/signals.md +0 -112
@@ -109,6 +109,59 @@ function tolerantMergeWatchMode(value) {
109
109
  }
110
110
  }
111
111
 
112
+ /**
113
+ * Shortest override reason that can plausibly name a rejected finding. Below
114
+ * this, the flag is being used to silence the gate rather than to record a
115
+ * judgement, which is the failure mode the reason requirement exists to stop.
116
+ */
117
+ const MIN_OVERRIDE_REASON_LENGTH = 12;
118
+
119
+ /**
120
+ * Parse `--override-review-block <reason>` — the sanctioned,
121
+ * logged override of a Story-scope code-review critical blocker.
122
+ *
123
+ * The reason is **mandatory and validated**, because the alternative it
124
+ * replaces is not "no override" but `gh pr merge` run by hand: before this
125
+ * flag, a review blocker the operator had reviewed and rejected left bypassing
126
+ * the gate entirely as the only way to land, and that bypass wrote nothing
127
+ * down anywhere. An override that records why is strictly more auditable than
128
+ * the hand-merge it displaces; an override that records nothing is not, so a
129
+ * bare or blank `--override-review-block` fails closed here — before any phase
130
+ * runs, at no mutation cost — rather than arming a silent one.
131
+ *
132
+ * @param {unknown} value
133
+ * @returns {string|undefined} the trimmed reason, or `undefined` when absent.
134
+ */
135
+ export function parseOverrideReviewBlock(value) {
136
+ if (value == null) return undefined;
137
+ // `parseArgs` with `strict: false` yields `true` for a bare string flag.
138
+ const reason = typeof value === 'string' ? value.trim() : '';
139
+ if (reason.length >= MIN_OVERRIDE_REASON_LENGTH) return reason;
140
+ throw new Error(
141
+ '--override-review-block requires a reason of at least ' +
142
+ `${MIN_OVERRIDE_REASON_LENGTH} characters naming the finding you reviewed ` +
143
+ `and rejected (got ${JSON.stringify(value)}). The reason is posted to the ` +
144
+ 'PR and the Story and recorded as friction telemetry.',
145
+ );
146
+ }
147
+
148
+ /**
149
+ * {@link parseOverrideReviewBlock} degraded to absent instead of throwing, for
150
+ * the tolerant reporting parse. Same contract as
151
+ * {@link tolerantMergeWatchMode}: safe only because a tolerant parse runs no
152
+ * phase.
153
+ *
154
+ * @param {unknown} value
155
+ * @returns {string|undefined}
156
+ */
157
+ function tolerantOverrideReviewBlock(value) {
158
+ try {
159
+ return parseOverrideReviewBlock(value);
160
+ } catch {
161
+ return undefined;
162
+ }
163
+ }
164
+
112
165
  /**
113
166
  * Standardized CLI argument parser for sprint scripts.
114
167
  * Supports options like --epic, --story, --dry-run, --skip-dashboard.
@@ -147,6 +200,11 @@ export function parseSprintArgs(
147
200
  // Absent means "use the config"; see `parseMergeWatchMode` for why an
148
201
  // unrecognized value fails closed instead of degrading to absent.
149
202
  'merge-watch-mode': { type: 'string' },
203
+ // Sanctioned override of a code-review critical blocker.
204
+ // Absent means "the blocker blocks"; see `parseOverrideReviewBlock` for
205
+ // why a bare or too-short reason fails closed instead of arming a silent
206
+ // override.
207
+ 'override-review-block': { type: 'string' },
150
208
  executor: { type: 'string' },
151
209
  cwd: { type: 'string' },
152
210
  'recut-of': { type: 'string' },
@@ -183,6 +241,12 @@ export function parseSprintArgs(
183
241
  mergeWatchMode: tolerant
184
242
  ? tolerantMergeWatchMode(values['merge-watch-mode'])
185
243
  : parseMergeWatchMode(values['merge-watch-mode']),
244
+ // The operator's recorded reason for overriding a review
245
+ // blocker. `undefined` when the flag is absent, which is what keeps the
246
+ // blocker blocking by default.
247
+ overrideReviewBlock: tolerant
248
+ ? tolerantOverrideReviewBlock(values['override-review-block'])
249
+ : parseOverrideReviewBlock(values['override-review-block']),
186
250
  executor: values.executor ?? null,
187
251
  // Resolve worktree cwd from flag or env. Empty string/whitespace → null.
188
252
  cwd:
@@ -327,8 +327,8 @@ export function buildDefaultGates({
327
327
  ...buildTestGateEntry(coverageCaptureActive),
328
328
  {
329
329
  // Gate name kept generic ("format") so the close-orchestrator log line
330
- // and the per-gate phase-timer key don't shift when a repo swaps biome
331
- // for Prettier / dprint via `project.commands.formatCheck`. The
330
+ // doesn't shift when a repo swaps biome for Prettier / dprint via
331
+ // `project.commands.formatCheck`. The
332
332
  // actual command and the remediation hint resolve from config.
333
333
  name: 'format',
334
334
  cmd: formatCmd,
@@ -87,9 +87,9 @@ function applyChangedFileScope({ gate, spawnCwd, log }) {
87
87
  * `<tempRoot>/standalone/stories/story-<id>/validation-evidence.json`
88
88
  * keyspace. v2.0.0 removed the Epic tier and its Epic-keyed keyspace.
89
89
  *
90
- * `onGateStart` is invoked immediately before each gate's runner spawn.
91
- * story-close uses it to drive `phaseTimer.mark(...)` for per-gate
92
- * wall-clock telemetry. Errors thrown from the hook propagate.
90
+ * `onGateStart` is invoked immediately before each gate's runner spawn, so a
91
+ * caller can record per-gate wall-clock telemetry. Errors thrown from the
92
+ * hook propagate.
93
93
  *
94
94
  * Projection advisories (Story #4776): when `baseBranch` and `storyBranch`
95
95
  * are both supplied and every gate passed, the maintainability and CRAP
@@ -38,7 +38,6 @@
38
38
  */
39
39
  export const ACCEPTANCE_EVAL_DEFAULTS = Object.freeze({
40
40
  maxRounds: 2,
41
- clusterCeiling: 4,
42
41
  });
43
42
 
44
43
  /**
@@ -51,22 +50,6 @@ export const ACCEPTANCE_EVAL_DEFAULTS = Object.freeze({
51
50
  */
52
51
  export const ACCEPTANCE_EVAL_MAX_ROUNDS_CEILING = 5;
53
52
 
54
- /**
55
- * Hard, undisableable ceiling on `clusterCeiling` — the max ACs one
56
- * single-delivery acceptance critic scores in a single fresh-context pass
57
- * (Epic #4475, M4-B, design §S2a). It is the **acceptance-dilution guard**:
58
- * single delivery collapses the whole Epic into one session, so the only
59
- * acceptance coverage left is the per-AC-cluster critic fan-out
60
- * (`ceil(totalACs / clusterCeiling)` independent maker-blind passes). A
61
- * pathologically large `clusterCeiling` would collapse that fan-out to a
62
- * single critic scoring every AC at once — exactly the dilution the design
63
- * forecloses — so a configured value above this ceiling is clamped down to
64
- * it. Kept small on purpose.
65
- *
66
- * @type {number}
67
- */
68
- export const ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX = 8;
69
-
70
53
  /**
71
54
  * Clamp a candidate round count into the inviolable `[1, ceiling]` range.
72
55
  * Non-integer / non-finite inputs fall back to the documented default.
@@ -85,43 +68,19 @@ function clampRounds(value, fallback) {
85
68
  return candidate;
86
69
  }
87
70
 
88
- /**
89
- * Clamp a candidate cluster ceiling into the inviolable
90
- * `[1, ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX]` range. Non-integer / non-finite
91
- * inputs fall back to the documented default. Mirrors `clampRounds` — the
92
- * anti-dilution guard cannot be disabled (`clusterCeiling: 0` or a negative
93
- * clamps up to 1; an over-max value clamps down to the hard cap).
94
- *
95
- * @param {unknown} value
96
- * @param {number} fallback
97
- * @returns {number}
98
- */
99
- function clampClusterCeiling(value, fallback) {
100
- const candidate =
101
- typeof value === 'number' && Number.isInteger(value) ? value : fallback;
102
- if (candidate < 1) return 1;
103
- if (candidate > ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX) {
104
- return ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX;
105
- }
106
- return candidate;
107
- }
108
-
109
71
  /**
110
72
  * Read the merged acceptance-eval block. Returns the canonical shape:
111
73
  *
112
74
  * {
113
- * maxRounds: number, // clamped into [1, roundsCeiling]
114
- * ceiling: number, // the undisableable hard cap on rounds
115
- * clusterCeiling: number, // clamped into [1, clusterCeilingMax]
116
- * clusterCeilingMax: number // the undisableable hard cap on cluster size
75
+ * maxRounds: number, // clamped into [1, ceiling]
76
+ * ceiling: number // the undisableable hard cap on rounds
117
77
  * }
118
78
  *
119
- * `maxRounds` is always a positive integer no greater than `ceiling`;
120
- * `clusterCeiling` is always a positive integer no greater than
121
- * `clusterCeilingMax`, regardless of what the resolved config carried.
79
+ * `maxRounds` is always a positive integer no greater than `ceiling`,
80
+ * regardless of what the resolved config carried.
122
81
  *
123
82
  * @param {object | null | undefined} config
124
- * @returns {{ maxRounds: number, ceiling: number, clusterCeiling: number, clusterCeilingMax: number }}
83
+ * @returns {{ maxRounds: number, ceiling: number }}
125
84
  */
126
85
  export function getAcceptanceEval(config) {
127
86
  const user = config?.delivery?.acceptanceEval ?? {};
@@ -129,14 +88,8 @@ export function getAcceptanceEval(config) {
129
88
  user.maxRounds,
130
89
  ACCEPTANCE_EVAL_DEFAULTS.maxRounds,
131
90
  );
132
- const clusterCeiling = clampClusterCeiling(
133
- user.clusterCeiling,
134
- ACCEPTANCE_EVAL_DEFAULTS.clusterCeiling,
135
- );
136
91
  return {
137
92
  maxRounds,
138
93
  ceiling: ACCEPTANCE_EVAL_MAX_ROUNDS_CEILING,
139
- clusterCeiling,
140
- clusterCeilingMax: ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX,
141
94
  };
142
95
  }
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * `project.commands` accessor (Epic #1720 Story #1739 — top-level reshape).
3
3
  *
4
- * The surviving five command keys are `lintBaseline`, `test`,
5
- * `typecheck`, `formatCheck`, `formatWrite`.
4
+ * The surviving four command keys are `test`, `typecheck`,
5
+ * `formatCheck`, `formatWrite`.
6
6
  */
7
7
 
8
8
  export const COMMANDS_DEFAULTS = Object.freeze({
9
- lintBaseline: 'npx eslint . --format json',
10
9
  test: 'npm test',
11
10
  typecheck: null,
12
11
  formatCheck: 'npx biome format .',
@@ -19,12 +18,11 @@ export const COMMANDS_DEFAULTS = Object.freeze({
19
18
  * a bare `{ project }` bag.
20
19
  *
21
20
  * @param {object | null | undefined} config
22
- * @returns {{ lintBaseline: string, test: string, typecheck: string|null, formatCheck: string, formatWrite: string }}
21
+ * @returns {{ test: string, typecheck: string|null, formatCheck: string, formatWrite: string }}
23
22
  */
24
23
  export function getCommands(config) {
25
24
  const commands = config?.project?.commands ?? {};
26
25
  return {
27
- lintBaseline: commands.lintBaseline ?? COMMANDS_DEFAULTS.lintBaseline,
28
26
  test: commands.test ?? COMMANDS_DEFAULTS.test,
29
27
  typecheck:
30
28
  commands.typecheck === undefined
@@ -72,8 +72,6 @@ const KEY_MEANINGS = Object.freeze({
72
72
  'Root for gitignored scratch output (per-run state, mirrors, logs).',
73
73
  'project.docsContextFiles':
74
74
  'Authoritative files an agent must read before starting any task.',
75
- 'project.commands.lintBaseline':
76
- 'Lint command the close-validation chain runs.',
77
75
  'project.commands.test': 'Test command the close-validation chain runs.',
78
76
  'project.commands.typecheck':
79
77
  'Typecheck command (null disables the typecheck gate).',
@@ -136,8 +134,6 @@ const KEY_MEANINGS = Object.freeze({
136
134
  // delivery.*
137
135
  'delivery.execution.timeoutMs':
138
136
  'Per-execution timeout for orchestrated delivery steps.',
139
- 'delivery.lease.ttlMs':
140
- 'Time-to-live for the delivery lease before a stale claim is reclaimable.',
141
137
  'delivery.ci.watch.pollIntervalMs':
142
138
  'Poll cadence (ms) for the merge/CI watch loop.',
143
139
  'delivery.ci.watch.maxPolls':
@@ -147,7 +143,7 @@ const KEY_MEANINGS = Object.freeze({
147
143
  'delivery.ci.autoMerge':
148
144
  'Merge posture: trust-ci merges on green checks; strict also requires a clean review gate.',
149
145
  'delivery.docsFreshness.paths':
150
- 'Docs whose freshness is checked at delivery time.',
146
+ 'Docs the audit-documentation lens folds into its target set.',
151
147
  'delivery.deliverRunner.concurrencyCap':
152
148
  'Maximum Stories dispatched in parallel within one wave. Default 3 — conservative by design to keep host-quota consumption predictable. Operators running wide waves with adequate parallel-agent quota should raise this to reduce wall-clock time proportionally.',
153
149
  'delivery.worktreeIsolation.enabled':
@@ -202,8 +198,6 @@ const KEY_MEANINGS = Object.freeze({
202
198
  'Whether a dedicated refactor stage runs during delivery.',
203
199
  'delivery.acceptanceEval.maxRounds':
204
200
  'Redraft rounds the per-Story acceptance self-eval loop runs before escalating to agent::blocked (default 2; clamped to a hard ceiling that cannot be disabled).',
205
- 'delivery.acceptanceEval.clusterCeiling':
206
- 'Max acceptance criteria one single-delivery acceptance critic scores per fresh-context pass; the Story ACs are split into ceil(totalACs / clusterCeiling) maker-blind critic clusters (default 4; clamped to [1, 8]).',
207
201
 
208
202
  // qa.*
209
203
  'qa.featureRoot': 'Root directory holding the QA harness .feature files.',
@@ -257,6 +251,10 @@ const PREFIX_MEANINGS = Object.freeze([
257
251
  'QA harness deployment target (baseUrl, per-environment sign-in seam, allowWrites gate).',
258
252
  ],
259
253
  ['qa.personas', 'QA harness persona / credential mapping.'],
254
+ [
255
+ 'qa.gherkinLint',
256
+ 'Static Gherkin corpus gate: per-scope feature/step roots plus the exemption-tag and step-waiver escapes.',
257
+ ],
260
258
  ]);
261
259
 
262
260
  /**
@@ -1,23 +1,49 @@
1
1
  /* node:coverage ignore file -- AJV schema declaration (data-as-code) */
2
2
 
3
- import { GATE_BASE } from './shared.js';
3
+ import { gateBase } from './shared.js';
4
4
 
5
5
  const BUNDLE_DECLARATION = {
6
6
  type: 'object',
7
+ description: 'One built artifact the bundle-size gate weighs.',
7
8
  required: ['name', 'path', 'limit'],
8
9
  properties: {
9
- name: { type: 'string', minLength: 1 },
10
- path: { type: 'string', minLength: 1 },
11
- limit: { type: 'string', minLength: 1 },
10
+ name: {
11
+ type: 'string',
12
+ minLength: 1,
13
+ description: 'Label the rollup row is keyed by.',
14
+ },
15
+ path: {
16
+ type: 'string',
17
+ minLength: 1,
18
+ description: 'Glob or path of the built artifact to measure.',
19
+ },
20
+ limit: {
21
+ type: 'string',
22
+ minLength: 1,
23
+ description: 'Human size ceiling (e.g. `"180 kB"`).',
24
+ },
12
25
  },
13
26
  additionalProperties: false,
14
27
  };
15
28
 
16
29
  export const BUNDLE_SIZE_GATE = {
17
30
  type: 'object',
31
+ description:
32
+ 'Built-artifact size ratchet for web targets. Off by default — it needs declared bundles.',
18
33
  properties: {
19
- ...GATE_BASE,
20
- bundles: { type: 'array', items: BUNDLE_DECLARATION },
34
+ ...gateBase({
35
+ enabled: false,
36
+ baselinePath: 'baselines/bundle-size.json',
37
+ tolerance: { kind: 'percent', value: 0 },
38
+ floors: { '*': {} },
39
+ }),
40
+ bundles: {
41
+ type: 'array',
42
+ items: BUNDLE_DECLARATION,
43
+ description:
44
+ 'The artifacts to weigh. Empty means the gate has nothing to do.',
45
+ default: [],
46
+ },
21
47
  },
22
48
  additionalProperties: false,
23
49
  };
@@ -1,18 +1,38 @@
1
1
  /* node:coverage ignore file -- AJV schema declaration (data-as-code) */
2
2
 
3
- import { GATE_BASE, SAFE_STRING } from './shared.js';
3
+ import { COVERAGE_GATE_DEFAULTS } from '../quality.js';
4
+ import { gateBase, SAFE_STRING } from './shared.js';
4
5
 
5
6
  export const COVERAGE_GATE = {
6
7
  type: 'object',
8
+ description:
9
+ 'Line/branch/function coverage ratchet, read from the Istanbul JSON summary the project test run emits.',
7
10
  properties: {
8
- ...GATE_BASE,
9
- coveragePath: { ...SAFE_STRING, minLength: 1 },
11
+ ...gateBase({
12
+ enabled: COVERAGE_GATE_DEFAULTS.enabled,
13
+ baselinePath: COVERAGE_GATE_DEFAULTS.baselinePath,
14
+ tolerance: COVERAGE_GATE_DEFAULTS.tolerance,
15
+ floors: COVERAGE_GATE_DEFAULTS.floors,
16
+ }),
17
+ coveragePath: {
18
+ ...SAFE_STRING,
19
+ minLength: 1,
20
+ description:
21
+ 'Repo-relative path to the Istanbul `coverage-final.json` the capture step writes and the gate reads.',
22
+ default: COVERAGE_GATE_DEFAULTS.coveragePath,
23
+ },
10
24
  // Story #2136 / Task #2142 — bounded timeout for `npm run test:coverage`.
11
25
  // Wired into `runCapture` via `spawnSync({ timeout, killSignal })`. A
12
26
  // SIGKILL fired by the timeout is translated to exit code 124 (the GNU
13
27
  // `timeout` convention) so close-validation can branch on "hang" vs.
14
- // "test failed". Default 600000 (10 min) resolved in `config/quality.js`.
15
- timeoutMs: { type: 'integer', minimum: 1 },
28
+ // "test failed".
29
+ timeoutMs: {
30
+ type: 'integer',
31
+ minimum: 1,
32
+ description:
33
+ 'Bounded timeout (ms) for the `npm run test:coverage` capture spawn. A SIGKILL at the budget boundary maps to exit 124 so close-validation can tell a hang from a test failure.',
34
+ default: COVERAGE_GATE_DEFAULTS.timeoutMs,
35
+ },
16
36
  },
17
37
  additionalProperties: false,
18
38
  };
@@ -18,9 +18,19 @@
18
18
  */
19
19
  export const INCREMENTAL_COVERAGE_SCHEMA = {
20
20
  type: 'object',
21
+ description:
22
+ 'Story #4981 — opt-in incremental coverage-capture + CRAP-join scoping. Default (key absent) preserves today’s full-repo behaviour byte-for-byte. When `enabled: true`, `coverage-capture.js` scopes `npm run test:coverage` to the files changed against `baseRef` (default: the gate’s own `--ref` / `main`), and the CRAP join treats a method in a file the diff did not touch as resolved by its committed baseline row instead of requiring fresh coverage for it.',
21
23
  properties: {
22
- enabled: { type: 'boolean' },
23
- baseRef: { type: 'string', minLength: 1 },
24
+ enabled: {
25
+ type: 'boolean',
26
+ description: 'Master switch for incremental capture + join scoping.',
27
+ },
28
+ baseRef: {
29
+ type: 'string',
30
+ minLength: 1,
31
+ description:
32
+ 'Git ref the changed-file set is computed against. Omitted falls back to the gate’s own `--ref` (`main`).',
33
+ },
24
34
  },
25
35
  additionalProperties: false,
26
36
  };
@@ -1,41 +1,86 @@
1
1
  /* node:coverage ignore file -- AJV schema declaration (data-as-code) */
2
2
 
3
+ import { CRAP_GATE_DEFAULTS } from '../quality.js';
3
4
  import { INCREMENTAL_COVERAGE_SCHEMA } from './crap-incremental-coverage.schema.js';
4
5
  import {
5
- GATE_BASE,
6
+ gateBase,
6
7
  LIST_OR_EXTENDER_OF_STRINGS,
7
8
  SAFE_STRING,
8
9
  } from './shared.js';
9
10
 
10
11
  export const CRAP_GATE = {
11
12
  type: 'object',
13
+ description:
14
+ 'CRAP (Change Risk Anti-Pattern) ratchet — per-method cyclomatic complexity joined against per-method coverage.',
12
15
  properties: {
13
- ...GATE_BASE,
14
- targetDirs: LIST_OR_EXTENDER_OF_STRINGS,
15
- newMethodCeiling: { type: 'integer', minimum: 1 },
16
- requireCoverage: { type: 'boolean' },
17
- // Story #4775 — fail-closed floor on the per-method coverage JOIN: the
18
- // fraction of methods that must resolve a coverage entry, counted only
19
- // over files that HAVE one, before `update-crap-baseline.js` will
20
- // persist. A broken join is silent by construction (unresolved methods
21
- // are simply absent), so the updater refuses rather than writing a thin
22
- // baseline and logging it as success. Default 0.75.
23
- minMethodResolutionRate: { type: 'number', minimum: 0, maximum: 1 },
16
+ ...gateBase({
17
+ enabled: CRAP_GATE_DEFAULTS.enabled,
18
+ baselinePath: CRAP_GATE_DEFAULTS.baselinePath,
19
+ tolerance: CRAP_GATE_DEFAULTS.tolerance,
20
+ floors: CRAP_GATE_DEFAULTS.floors,
21
+ }),
22
+ targetDirs: {
23
+ ...LIST_OR_EXTENDER_OF_STRINGS,
24
+ description:
25
+ "Directories whose JS sources the CRAP gate scores. Mandrel ships a `src/`-centric default; projects whose executable code lives elsewhere (e.g. this repo's `.agents/scripts/`) override here. The framework default is intentionally not auto-discovered, so an override is the explicit, auditable signal.",
26
+ default: [...CRAP_GATE_DEFAULTS.targetDirs],
27
+ },
28
+ newMethodCeiling: {
29
+ type: 'integer',
30
+ minimum: 1,
31
+ description:
32
+ 'Hard CRAP ceiling applied to a method the diff introduces. A new method above it fails the gate regardless of the baseline.',
33
+ default: CRAP_GATE_DEFAULTS.newMethodCeiling,
34
+ },
35
+ requireCoverage: {
36
+ type: 'boolean',
37
+ description:
38
+ 'When true, the gate refuses to score without a coverage artifact rather than silently reporting complexity-only rows.',
39
+ default: CRAP_GATE_DEFAULTS.requireCoverage,
40
+ },
41
+ // Story #4775 — fail-closed floor on the per-method coverage JOIN.
42
+ minMethodResolutionRate: {
43
+ type: 'number',
44
+ minimum: 0,
45
+ maximum: 1,
46
+ description:
47
+ 'Fail-closed floor on the per-method coverage JOIN (Story #4775): the fraction of methods that must resolve a coverage entry, counted only over files that HAVE one, before `update-crap-baseline.js` will persist. A broken join is silent by construction — unresolved methods are simply absent — so the updater refuses rather than writing a thin baseline and logging it as success. Not enforced below 25 joinable methods, where a diff-scoped run’s rate is noise. Default 0.75; a healthy repo resolves ~98%.',
48
+ },
24
49
  friction: {
25
50
  type: 'object',
26
- properties: { markerKey: { type: 'string', minLength: 1 } },
51
+ description:
52
+ 'Friction-signal wiring for a CRAP baseline regression, so a recurring one can reach the actionable threshold.',
53
+ properties: {
54
+ markerKey: {
55
+ type: 'string',
56
+ minLength: 1,
57
+ description: 'Signal marker key the regression is recorded under.',
58
+ },
59
+ },
27
60
  additionalProperties: false,
61
+ default: { ...CRAP_GATE_DEFAULTS.friction },
62
+ },
63
+ refreshTag: {
64
+ ...SAFE_STRING,
65
+ minLength: 1,
66
+ description:
67
+ 'Commit-subject substring that acknowledges a deliberate CRAP baseline refresh in the compared range. A range commit carrying it that also touches the baseline file demotes head-vs-base regressions; floors stay enforced.',
68
+ default: CRAP_GATE_DEFAULTS.refreshTag,
69
+ },
70
+ refreshTimeoutMs: {
71
+ type: 'integer',
72
+ minimum: 1,
73
+ description:
74
+ 'Bounded timeout (ms) for `npm run crap:update` spawned by the baseline-attribution refresh path. Mirrors `coverage.timeoutMs`: a SIGKILL fired at the budget boundary maps to exit 124 so the close orchestrator can flip the Story to `agent::blocked`. Default 60000 (Story #2165).',
75
+ default: CRAP_GATE_DEFAULTS.refreshTimeoutMs,
76
+ },
77
+ ignoreGlobs: {
78
+ type: 'array',
79
+ items: { type: 'string', minLength: 1 },
80
+ description:
81
+ 'Minimatch glob patterns matched against the canonicalised repo-relative path of each discovered file. Files matching any pattern are excluded from CRAP discovery before scoring. Orthogonal to `components` (grouping) — a file excluded here never appears in any component bucket. Absent or empty preserves the existing IGNORED_DIRS-only behaviour (Story #3217).',
82
+ default: [...CRAP_GATE_DEFAULTS.ignoreGlobs],
28
83
  },
29
- refreshTag: { ...SAFE_STRING, minLength: 1 },
30
- // Story #2165 — bounded timeout for `npm run crap:update` spawned by the
31
- // baseline-attribution refresh path. Mirrors `coverage.timeoutMs` (Story
32
- // #2142): a SIGKILL fired at the budget boundary maps to exit code 124
33
- // so the close orchestrator can flip the Story to `agent::blocked`.
34
- refreshTimeoutMs: { type: 'integer', minimum: 1 },
35
- // Story #3217 — glob patterns matched against canonicalised repo-relative
36
- // paths to exclude files from CRAP discovery before scoring. Orthogonal
37
- // to `components` (grouping). Absent/empty preserves existing behaviour.
38
- ignoreGlobs: { type: 'array', items: { type: 'string', minLength: 1 } },
39
84
  incrementalCoverage: INCREMENTAL_COVERAGE_SCHEMA,
40
85
  },
41
86
  additionalProperties: false,
@@ -1,26 +1,38 @@
1
1
  /* node:coverage ignore file -- AJV schema declaration (data-as-code) */
2
2
 
3
- import { GATE_BASE, LIST_OR_EXTENDER_OF_STRINGS } from './shared.js';
3
+ import { gateBase, LIST_OR_EXTENDER_OF_STRINGS } from './shared.js';
4
4
 
5
- /**
6
- * `delivery.quality.gates.duplication` — code-duplication (DRY) gate
7
- * (Story #3664). Shares the four-field `GATE_BASE` (enabled, baselinePath,
8
- * tolerance, floors, components) and adds the scan-scope extras: the
9
- * `targetDirs` the duplication scanner walks, a bounded refresh timeout
10
- * mirroring crap/MI, and `ignoreGlobs` to exclude files from the scan.
11
- */
12
5
  export const DUPLICATION_GATE = {
13
6
  type: 'object',
7
+ description:
8
+ 'Code-duplication (DRY) gate (Story #3664). Shares the gate base and adds the scan-scope extras: the `targetDirs` the duplication scanner walks, a bounded refresh timeout mirroring crap/MI, and `ignoreGlobs` to exclude files from the scan.',
14
9
  properties: {
15
- ...GATE_BASE,
16
- targetDirs: LIST_OR_EXTENDER_OF_STRINGS,
17
- // Bounded timeout (ms) for the `npm run duplication:update` scanner spawn.
18
- // Mirrors `crap.refreshTimeoutMs` / `maintainability.refreshTimeoutMs`.
19
- refreshTimeoutMs: { type: 'integer', minimum: 1 },
20
- // Glob patterns matched against canonicalised repo-relative paths to
21
- // exclude files from duplication discovery before scoring. Orthogonal to
22
- // `components` (grouping). Absent/empty preserves existing behaviour.
23
- ignoreGlobs: { type: 'array', items: { type: 'string', minLength: 1 } },
10
+ ...gateBase({
11
+ enabled: false,
12
+ baselinePath: 'baselines/duplication.json',
13
+ tolerance: { kind: 'absolute', value: 1 },
14
+ floors: { '*': { percentage: 25 } },
15
+ }),
16
+ targetDirs: {
17
+ ...LIST_OR_EXTENDER_OF_STRINGS,
18
+ description:
19
+ "Directories whose JS sources the duplication (DRY) gate scans for copy-paste clones. Mandrel ships a `src/`-centric default; projects whose executable code lives elsewhere (e.g. this repo's `.agents/scripts/`) override here. The framework default is intentionally not auto-discovered, so an override is the explicit, auditable signal (Story #3664).",
20
+ default: ['src'],
21
+ },
22
+ refreshTimeoutMs: {
23
+ type: 'integer',
24
+ minimum: 1,
25
+ description:
26
+ 'Bounded timeout (ms) for `npm run duplication:update` spawned by the baseline-attribution refresh path. Mirrors `crap.refreshTimeoutMs` / `coverage.timeoutMs`: a SIGKILL fired at the budget boundary maps to exit 124. Default 60000 (Story #3664).',
27
+ default: 60000,
28
+ },
29
+ ignoreGlobs: {
30
+ type: 'array',
31
+ items: { type: 'string', minLength: 1 },
32
+ description:
33
+ 'Minimatch glob patterns matched against the canonicalised repo-relative path of each discovered file. Files matching any pattern are excluded from duplication discovery before scanning. Orthogonal to `components` (grouping). Absent or empty preserves the existing behaviour (Story #3664).',
34
+ default: [],
35
+ },
24
36
  },
25
37
  additionalProperties: false,
26
38
  };
@@ -14,14 +14,17 @@ import { MUTATION_GATE } from './mutation.schema.js';
14
14
  *
15
15
  * Split from the former `lib/config-gates-schema.js` aggregate
16
16
  * (Story #2987) into per-gate sub-schema files under
17
- * `lib/config/gates/`. Shared fragments (`GATE_BASE`, `SAFE_STRING`,
17
+ * `lib/config/gates/`. Shared fragments (`gateBase()`, `SAFE_STRING`,
18
18
  * `TOLERANCE_SCHEMA`, `FLOORS_SCHEMA`, `COMPONENTS_SCHEMA`,
19
19
  * `LIST_OR_EXTENDER_OF_STRINGS`, `NULLABLE_NONEMPTY_SAFE_STRING`) live
20
20
  * in `./shared.js`. Each per-gate file exports one sub-schema literal
21
- * with the gate-specific extras layered on top of `GATE_BASE`.
21
+ * with the gate-specific extras layered on top of `gateBase()`, passing
22
+ * that gate's own base-field defaults (Story #5007).
22
23
  */
23
24
  export const GATES_SCHEMA = {
24
25
  type: 'object',
26
+ description:
27
+ 'The eight quality gates, each sharing the `{ enabled, baselinePath, tolerance, floors, components }` base.',
25
28
  properties: {
26
29
  lint: LINT_GATE,
27
30
  coverage: COVERAGE_GATE,
@@ -1,23 +1,51 @@
1
1
  /* node:coverage ignore file -- AJV schema declaration (data-as-code) */
2
2
 
3
- import { GATE_BASE, NULLABLE_NONEMPTY_SAFE_STRING } from './shared.js';
3
+ import { gateBase, NULLABLE_NONEMPTY_SAFE_STRING } from './shared.js';
4
4
 
5
5
  const LIGHTHOUSE_ROUTE = {
6
6
  type: 'object',
7
+ description: 'One route the Lighthouse run scores.',
7
8
  required: ['path'],
8
9
  properties: {
9
- path: { type: 'string', minLength: 1 },
10
- formFactor: { type: 'string', enum: ['mobile', 'desktop'] },
10
+ path: {
11
+ type: 'string',
12
+ minLength: 1,
13
+ description: 'Route path appended to `baseUrl`.',
14
+ },
15
+ formFactor: {
16
+ type: 'string',
17
+ enum: ['mobile', 'desktop'],
18
+ description: 'Emulated form factor for this route.',
19
+ },
11
20
  },
12
21
  additionalProperties: false,
13
22
  };
14
23
 
15
24
  export const LIGHTHOUSE_GATE = {
16
25
  type: 'object',
26
+ description:
27
+ 'Lighthouse category-score ratchet for web targets. Off by default — it needs a running deployment.',
17
28
  properties: {
18
- ...GATE_BASE,
19
- baseUrl: NULLABLE_NONEMPTY_SAFE_STRING,
20
- routes: { type: 'array', items: LIGHTHOUSE_ROUTE },
29
+ ...gateBase({
30
+ enabled: false,
31
+ baselinePath: 'baselines/lighthouse.json',
32
+ tolerance: { kind: 'absolute', value: 0 },
33
+ floors: {
34
+ '*': { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 },
35
+ },
36
+ }),
37
+ baseUrl: {
38
+ ...NULLABLE_NONEMPTY_SAFE_STRING,
39
+ description:
40
+ 'Origin the routes are resolved against. `null` leaves the gate unusable, which is why it ships disabled.',
41
+ default: null,
42
+ },
43
+ routes: {
44
+ type: 'array',
45
+ items: LIGHTHOUSE_ROUTE,
46
+ description: 'Routes to score on each run.',
47
+ default: [],
48
+ },
21
49
  },
22
50
  additionalProperties: false,
23
51
  };