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
@@ -1,9 +1,18 @@
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
  export const LINT_GATE = {
6
6
  type: 'object',
7
- properties: { ...GATE_BASE },
7
+ description:
8
+ 'Lint-count ratchet. Floors are absolute error/warning counts; the baseline pins the current count so a regression is visible even while the floor is not yet met.',
9
+ properties: {
10
+ ...gateBase({
11
+ enabled: true,
12
+ baselinePath: 'baselines/lint.json',
13
+ tolerance: { kind: 'absolute', value: 0 },
14
+ floors: { '*': { errorCount: 0 } },
15
+ }),
16
+ },
8
17
  additionalProperties: false,
9
18
  };
@@ -1,29 +1,51 @@
1
1
  /* node:coverage ignore file -- AJV schema declaration (data-as-code) */
2
2
 
3
+ import { MAINTAINABILITY_GATE_DEFAULTS } from '../quality.js';
3
4
  import {
4
- GATE_BASE,
5
+ gateBase,
5
6
  LIST_OR_EXTENDER_OF_STRINGS,
6
7
  SAFE_STRING,
7
8
  } from './shared.js';
8
9
 
9
10
  export const MAINTAINABILITY_GATE = {
10
11
  type: 'object',
12
+ description:
13
+ 'Maintainability-index ratchet. Scores per file as the average over its methods, so deleting a small high-MI method can legitimately lower a file’s score.',
11
14
  properties: {
12
- ...GATE_BASE,
13
- targetDirs: LIST_OR_EXTENDER_OF_STRINGS,
15
+ ...gateBase({
16
+ enabled: MAINTAINABILITY_GATE_DEFAULTS.enabled,
17
+ baselinePath: MAINTAINABILITY_GATE_DEFAULTS.baselinePath,
18
+ tolerance: MAINTAINABILITY_GATE_DEFAULTS.tolerance,
19
+ floors: MAINTAINABILITY_GATE_DEFAULTS.floors,
20
+ }),
21
+ targetDirs: {
22
+ ...LIST_OR_EXTENDER_OF_STRINGS,
23
+ description:
24
+ "Directories whose JS sources the maintainability gate scores. Mandrel ships a `src/`-centric default; projects whose executable code lives elsewhere (e.g. this repo's `.agents/scripts/` plus `tests/`) override here. The framework default is intentionally not auto-discovered, so an override is the explicit, auditable signal.",
25
+ default: [...MAINTAINABILITY_GATE_DEFAULTS.targetDirs],
26
+ },
14
27
  // Story #4731 — commit-subject substring that acknowledges a deliberate
15
- // maintainability baseline refresh in the compared range. Mirrors the CRAP
16
- // gate's `refreshTag`; a range commit carrying it that touches the baseline
17
- // file demotes head-vs-base regressions (floors still enforced).
18
- refreshTag: { ...SAFE_STRING, minLength: 1 },
19
- // Story #2165 — bounded timeout for `npm run maintainability:update`
20
- // spawned by the baseline-attribution refresh path. Mirrors
21
- // `coverage.timeoutMs` (Story #2142).
22
- refreshTimeoutMs: { type: 'integer', minimum: 1 },
23
- // Story #3217 — glob patterns matched against canonicalised repo-relative
24
- // paths to exclude files from MI discovery before scoring. Orthogonal to
25
- // `components` (grouping). Absent/empty preserves existing behaviour.
26
- ignoreGlobs: { type: 'array', items: { type: 'string', minLength: 1 } },
28
+ // maintainability baseline refresh in the compared range.
29
+ refreshTag: {
30
+ ...SAFE_STRING,
31
+ minLength: 1,
32
+ description:
33
+ "Commit-subject substring that acknowledges a deliberate maintainability baseline refresh in the compared range. Mirrors the CRAP gate's `refreshTag`; a range commit carrying it that touches the baseline file demotes head-vs-base regressions (floors still enforced).",
34
+ },
35
+ refreshTimeoutMs: {
36
+ type: 'integer',
37
+ minimum: 1,
38
+ description:
39
+ 'Bounded timeout (ms) for `npm run maintainability: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).',
40
+ default: MAINTAINABILITY_GATE_DEFAULTS.refreshTimeoutMs,
41
+ },
42
+ ignoreGlobs: {
43
+ type: 'array',
44
+ items: { type: 'string', minLength: 1 },
45
+ description:
46
+ 'Minimatch glob patterns matched against the canonicalised repo-relative path of each discovered file. Files matching any pattern are excluded from MI 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).',
47
+ default: [...MAINTAINABILITY_GATE_DEFAULTS.ignoreGlobs],
48
+ },
27
49
  },
28
50
  additionalProperties: false,
29
51
  };
@@ -1,12 +1,24 @@
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
  export const MUTATION_GATE = {
6
6
  type: 'object',
7
+ description:
8
+ 'Stryker mutation-score ratchet. Off in practice for most consumers — the baseline kind is registered but no framework path runs Stryker.',
7
9
  properties: {
8
- ...GATE_BASE,
9
- strykerConfigPath: NULLABLE_NONEMPTY_SAFE_STRING,
10
+ ...gateBase({
11
+ enabled: true,
12
+ baselinePath: 'baselines/mutation.json',
13
+ tolerance: { kind: 'percent', value: 0 },
14
+ floors: { '*': { score: 60 } },
15
+ }),
16
+ strykerConfigPath: {
17
+ ...NULLABLE_NONEMPTY_SAFE_STRING,
18
+ description:
19
+ 'Repo-relative path to the Stryker config. `null` lets Stryker resolve its own default.',
20
+ default: null,
21
+ },
10
22
  },
11
23
  additionalProperties: false,
12
24
  };
@@ -48,10 +48,21 @@ export const LIST_OR_EXTENDER_OF_STRINGS = {
48
48
  /** Object-shaped tolerance: `{ kind: 'absolute' | 'percent', value: number }`. */
49
49
  export const TOLERANCE_SCHEMA = {
50
50
  type: 'object',
51
+ description:
52
+ 'How much a rollup may drift from the committed baseline before the gate reports a regression.',
51
53
  required: ['kind', 'value'],
52
54
  properties: {
53
- kind: { type: 'string', enum: ['absolute', 'percent'] },
54
- value: { type: 'number', minimum: 0 },
55
+ kind: {
56
+ type: 'string',
57
+ enum: ['absolute', 'percent'],
58
+ description:
59
+ 'Whether `value` is read as raw metric units (`absolute`) or as a percentage of the baseline (`percent`).',
60
+ },
61
+ value: {
62
+ type: 'number',
63
+ minimum: 0,
64
+ description: 'The tolerance magnitude. 0 means no drift is allowed.',
65
+ },
55
66
  },
56
67
  additionalProperties: false,
57
68
  };
@@ -84,6 +95,8 @@ export const TOLERANCE_SCHEMA = {
84
95
  */
85
96
  export const FLOORS_SCHEMA = {
86
97
  type: 'object',
98
+ description:
99
+ 'Workspace-keyed absolute floors: `{ "<workspace>": { "<metric>": number } }`. `"*"` is the project-wide catch-all; the metric keyset is open so per-rollup keys flow through without each gate enumerating them. Floors are absolute — unlike `tolerance`, they are enforced regardless of the baseline.',
87
100
  additionalProperties: {
88
101
  type: 'object',
89
102
  additionalProperties: { type: 'number' },
@@ -102,16 +115,52 @@ export const FLOORS_SCHEMA = {
102
115
  */
103
116
  export const COMPONENTS_SCHEMA = {
104
117
  type: 'object',
118
+ description:
119
+ 'Per-gate component map — component name to the glob list whose files roll up under it. Defaults to `{ "*": ["**"] }` at the resolver layer.',
105
120
  additionalProperties: {
106
121
  type: 'array',
107
122
  items: { type: 'string', minLength: 1 },
108
123
  },
109
124
  };
110
125
 
111
- export const GATE_BASE = {
112
- enabled: { type: 'boolean' },
113
- baselinePath: { ...SAFE_STRING, minLength: 1 },
114
- tolerance: TOLERANCE_SCHEMA,
115
- floors: FLOORS_SCHEMA,
116
- components: COMPONENTS_SCHEMA,
117
- };
126
+ /**
127
+ * Build the shared four-field gate base with per-gate `default` annotations
128
+ * layered on (Story #5007). The base fields are identical in shape across
129
+ * every gate but their defaults are not — each gate ships its own
130
+ * `baselinePath`, tolerance, and floors — so the defaults are supplied by the
131
+ * calling gate module rather than baked in here.
132
+ *
133
+ * @param {{ enabled?: boolean, baselinePath?: string,
134
+ * tolerance?: object, floors?: object }} [defaults]
135
+ * @returns {object} A fresh `properties` fragment; never a shared reference.
136
+ */
137
+ export function gateBase(defaults = {}) {
138
+ return {
139
+ enabled: {
140
+ type: 'boolean',
141
+ description:
142
+ 'When false, the checker exits 0 with a skip line and the gate is reported as `skipped`, never omitted.',
143
+ ...(defaults.enabled === undefined ? {} : { default: defaults.enabled }),
144
+ },
145
+ baselinePath: {
146
+ ...SAFE_STRING,
147
+ minLength: 1,
148
+ description:
149
+ "Repo-root-relative path to the gate's committed baseline artifact.",
150
+ ...(defaults.baselinePath === undefined
151
+ ? {}
152
+ : { default: defaults.baselinePath }),
153
+ },
154
+ tolerance: {
155
+ ...TOLERANCE_SCHEMA,
156
+ ...(defaults.tolerance === undefined
157
+ ? {}
158
+ : { default: defaults.tolerance }),
159
+ },
160
+ floors: {
161
+ ...FLOORS_SCHEMA,
162
+ ...(defaults.floors === undefined ? {} : { default: defaults.floors }),
163
+ },
164
+ components: COMPONENTS_SCHEMA,
165
+ };
166
+ }
@@ -61,7 +61,6 @@ export const NOTIFICATIONS_DEFAULTS = Object.freeze({
61
61
  'operator-message',
62
62
  'merge.unlanded',
63
63
  'merge.flip-failed',
64
- 'loop.tick',
65
64
  ]),
66
65
  });
67
66
 
@@ -6,7 +6,6 @@
6
6
  * `planning.*` and `delivery.*`:
7
7
  *
8
8
  * - `delivery.execution.timeoutMs` (per-process execution timeout)
9
- * - `delivery.lease.ttlMs` (assignee-as-lease staleness window — Story #3480)
10
9
  * - `delivery.signals.{rework, retry}` (performance-signal detector
11
10
  * thresholds — `hotspot` retired with Epic #4406; `churn`/`idle` dropped)
12
11
  *
@@ -14,7 +13,9 @@
14
13
  * - `maxTickets` — decomposer reviewability budget (Story #4163)
15
14
  *
16
15
  * Dropped entirely: `maxInstructionSteps`, `friction.*`, `executionMaxBuffer`,
17
- * `signals.{churn, idle}`, `delivery.preflight`, `delivery.maxTokenBudget`
16
+ * `signals.{churn, idle}`, `delivery.preflight`, `delivery.lease.ttlMs`
17
+ * (Story #5006 deleted the lease TTL: with no heartbeat source every foreign
18
+ * claim read live, so the window decided nothing), `delivery.maxTokenBudget`
18
19
  * (planning no longer sizes against a token-budget envelope; session-mass
19
20
  * ceilings are absolute in `DEFAULT_MODEL_CAPACITY`), and
20
21
  * `planning.context.{maxBytes, summaryMode}` (Story #4541 — the `applyBudget`
@@ -39,26 +40,12 @@ export const SIGNALS_DEFAULTS = Object.freeze({
39
40
  retry: Object.freeze({ repeatCount: 3 }),
40
41
  });
41
42
 
42
- /**
43
- * Default TTL for the assignee-as-lease primitive (Story #3480). A claim
44
- * whose owner's last heartbeat is older than this window is considered stale
45
- * and may be reclaimed by another operator.
46
- *
47
- * Note: no shipped caller supplies a real heartbeat — the emitter was inert
48
- * and was deleted (A22), so the guards anchor liveness to `now` and every
49
- * foreign claim reads live (fail-closed; clear a stranded claim with
50
- * `--steal`). This value is therefore only consulted by a caller that threads
51
- * its own `heartbeatAt`, and is kept as the documented default for that seam.
52
- */
53
- export const LEASE_TTL_MS_DEFAULT = 900000;
54
-
55
43
  /**
56
44
  * Framework defaults for the surviving limits surface.
57
45
  */
58
46
  export const LIMITS_DEFAULTS = Object.freeze({
59
47
  maxTickets: 80,
60
48
  executionTimeoutMs: 600000,
61
- leaseTtlMs: LEASE_TTL_MS_DEFAULT,
62
49
  signals: SIGNALS_DEFAULTS,
63
50
  });
64
51
 
@@ -94,7 +81,6 @@ function mergeSignals(userSignals) {
94
81
  * @returns {{
95
82
  * maxTickets: number,
96
83
  * executionTimeoutMs: number,
97
- * leaseTtlMs: number,
98
84
  * signals: ReturnType<typeof mergeSignals>,
99
85
  * }}
100
86
  */
@@ -107,13 +93,10 @@ export function resolveLimits(config) {
107
93
  delivery.execution && typeof delivery.execution === 'object'
108
94
  ? delivery.execution
109
95
  : {};
110
- const lease =
111
- delivery.lease && typeof delivery.lease === 'object' ? delivery.lease : {};
112
96
  return {
113
97
  maxTickets: LIMITS_DEFAULTS.maxTickets,
114
98
  executionTimeoutMs:
115
99
  execution.timeoutMs ?? LIMITS_DEFAULTS.executionTimeoutMs,
116
- leaseTtlMs: lease.ttlMs ?? LIMITS_DEFAULTS.leaseTtlMs,
117
100
  signals: mergeSignals(delivery.signals),
118
101
  };
119
102
  }
@@ -141,31 +124,3 @@ export function getLimits(config) {
141
124
  export function getSignals(config) {
142
125
  return getLimits(config).signals;
143
126
  }
144
-
145
- /**
146
- * Resolve the assignee-as-lease TTL in milliseconds (Story #3480). Standalone
147
- * accessor so the `ticket-lease` module can import it without dragging the
148
- * whole limits surface into its bundle. Precedence:
149
- *
150
- * 1. An explicit `override` (a positive finite number) — lets a caller or
151
- * test pin the TTL directly.
152
- * 2. `delivery.lease.ttlMs` from the resolved config.
153
- * 3. `LEASE_TTL_MS_DEFAULT`.
154
- *
155
- * A non-positive or non-finite override is ignored (falls through to config /
156
- * default) so a stray `0` can never collapse every claim to instantly-stale.
157
- *
158
- * @param {object | null | undefined} config
159
- * @param {number} [override]
160
- * @returns {number}
161
- */
162
- export function resolveLeaseTtlMs(config, override) {
163
- if (
164
- typeof override === 'number' &&
165
- Number.isFinite(override) &&
166
- override > 0
167
- ) {
168
- return override;
169
- }
170
- return getLimits(config).leaseTtlMs;
171
- }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * `qa.gherkinLint` accessor — the static Gherkin corpus gate's contract.
3
+ *
4
+ * The gate is **opt-in**: absent this block, `check-gherkin-corpus.js` reports
5
+ * that it is not configured and exits 0. That is not politeness. `.agents/`
6
+ * reaches a consumer by plain file copy, so every framework upgrade lands this
7
+ * gate in every project at once; a default-on corpus lint would redden the
8
+ * `lint` required check of every repo that happens to own a `.feature` file it
9
+ * never asked the framework to police.
10
+ *
11
+ * Inside the opt-in the posture inverts and the gate fails **closed** — an
12
+ * unresolvable parser or a scope that resolves no step definitions is an
13
+ * error, never a quiet pass. Both are the same blackout the gate exists to
14
+ * catch, wearing a different costume.
15
+ *
16
+ * Scopes are the load-bearing shape. Pooling every step root into one matcher
17
+ * list makes a cross-app false bind possible: a step defined only in app B's
18
+ * suite silently vouches for app A's feature, and the gate reports green on a
19
+ * corpus that cannot generate. Resolving each feature against **its own**
20
+ * scope's step roots makes that structurally impossible rather than merely
21
+ * unlikely.
22
+ */
23
+
24
+ /**
25
+ * Framework defaults for the two escape hatches. `@skip` is the conventional
26
+ * "not meant to run" tag across cucumber-js and playwright-bdd, so it is the
27
+ * default exemption; the waiver list starts empty because every entry is a
28
+ * project-specific admission that the step index guessed wrong.
29
+ */
30
+ const GHERKIN_LINT_DEFAULTS = Object.freeze({
31
+ exemptionTags: Object.freeze(['@skip']),
32
+ stepWaivers: Object.freeze([]),
33
+ });
34
+
35
+ /** Coerce a value to an array of non-empty strings. */
36
+ function stringArray(value, fallback) {
37
+ if (!Array.isArray(value)) return [...fallback];
38
+ return value.filter((item) => typeof item === 'string' && item.length > 0);
39
+ }
40
+
41
+ /**
42
+ * Normalize one scope entry into `{ name, featureRoots, stepRoots }`.
43
+ *
44
+ * @param {string} name
45
+ * @param {object} raw
46
+ * @returns {{ name: string, featureRoots: string[], stepRoots: string[] }}
47
+ */
48
+ function normalizeScope(name, raw) {
49
+ return {
50
+ name,
51
+ featureRoots: stringArray(raw?.featureRoots, []),
52
+ stepRoots: stringArray(raw?.stepRoots, []),
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Normalize a raw `qa.gherkinLint` block.
58
+ *
59
+ * @param {object | null | undefined} raw
60
+ * @returns {{
61
+ * scopes: Array<{ name: string, featureRoots: string[], stepRoots: string[] }>,
62
+ * exemptionTags: string[],
63
+ * stepWaivers: string[],
64
+ * } | null} `null` when the block is absent or not an object
65
+ */
66
+ function resolveGherkinLint(raw) {
67
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
68
+ const scopesRaw =
69
+ raw.scopes && typeof raw.scopes === 'object' && !Array.isArray(raw.scopes)
70
+ ? raw.scopes
71
+ : {};
72
+ return {
73
+ scopes: Object.keys(scopesRaw)
74
+ .sort()
75
+ .map((name) => normalizeScope(name, scopesRaw[name])),
76
+ exemptionTags: stringArray(
77
+ raw.exemptionTags,
78
+ GHERKIN_LINT_DEFAULTS.exemptionTags,
79
+ ),
80
+ stepWaivers: stringArray(
81
+ raw.stepWaivers,
82
+ GHERKIN_LINT_DEFAULTS.stepWaivers,
83
+ ),
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Read the normalized `qa.gherkinLint` contract off a resolved config.
89
+ *
90
+ * @param {object | null | undefined} config
91
+ * @returns {ReturnType<typeof resolveGherkinLint>}
92
+ */
93
+ export function getGherkinLint(config) {
94
+ return resolveGherkinLint(config?.qa?.gherkinLint);
95
+ }
96
+
97
+ /**
98
+ * Module-private surface the suite drives directly, bundled behind one export
99
+ * for the same reason as `bdd-step-index.js`: `getGherkinLint` is the whole
100
+ * public API, and the normalizer and its defaults are how it is built.
101
+ */
102
+ export const __testing = Object.freeze({
103
+ GHERKIN_LINT_DEFAULTS,
104
+ resolveGherkinLint,
105
+ });
@@ -30,9 +30,19 @@ export const DEFAULT_DECOMPOSER = Object.freeze({
30
30
  * `verifyWaveResults` loop it claimed to bound never existed in the tree, and
31
31
  * its only reader was the retired execution-analysis CLI, which echoed the
32
32
  * number into a report rather than bounding anything.
33
+ *
34
+ * **Serialization tradeoff — `footprintGuard`.** `enforce` is the default and
35
+ * stays it: the file-overlap guard encodes delivery-time-only knowledge (which
36
+ * implementation windows are open, which Stories a foreign lease holds) that no
37
+ * plan-time `depends_on` edge can carry, so demoting it by default would trade
38
+ * a real merge-conflict class for throughput nobody asked for. `advisory`
39
+ * detects collisions and reports every would-be withhold but lets dispatch
40
+ * follow the declared edges alone — for runs whose ordering is fully declared
41
+ * (Story #5044).
33
42
  */
34
43
  const DEFAULT_DELIVER_RUNNER = Object.freeze({
35
44
  concurrencyCap: 3,
45
+ footprintGuard: 'enforce',
36
46
  });
37
47
 
38
48
  /**
@@ -51,28 +61,40 @@ export const DEFAULT_CODE_REVIEW = Object.freeze({
51
61
  *
52
62
  * @param {object | null | undefined} config
53
63
  * @returns {{
54
- * deliverRunner: { concurrencyCap: number },
64
+ * deliverRunner: { concurrencyCap: number, footprintGuard: 'enforce'|'advisory' },
55
65
  * codeReview: { maxFixAttempts: number, maxFixScopeFiles: number, autoFixSeverity: 'high'|'medium' },
56
66
  * decomposer: { concurrencyCap: number },
57
67
  * }}
58
68
  */
59
69
  export function getRunners(config) {
60
- const deliverRunnerUser = config?.delivery?.deliverRunner ?? {};
61
- const codeReviewUser = config?.delivery?.codeReview ?? {};
62
70
  return {
63
- deliverRunner: {
64
- concurrencyCap:
65
- deliverRunnerUser.concurrencyCap ??
66
- DEFAULT_DELIVER_RUNNER.concurrencyCap,
67
- },
68
- codeReview: {
69
- maxFixAttempts:
70
- codeReviewUser.maxFixAttempts ?? DEFAULT_CODE_REVIEW.maxFixAttempts,
71
- maxFixScopeFiles:
72
- codeReviewUser.maxFixScopeFiles ?? DEFAULT_CODE_REVIEW.maxFixScopeFiles,
73
- autoFixSeverity:
74
- codeReviewUser.autoFixSeverity ?? DEFAULT_CODE_REVIEW.autoFixSeverity,
75
- },
71
+ deliverRunner: withDefaults(
72
+ DEFAULT_DELIVER_RUNNER,
73
+ config?.delivery?.deliverRunner,
74
+ ),
75
+ codeReview: withDefaults(DEFAULT_CODE_REVIEW, config?.delivery?.codeReview),
76
76
  decomposer: DEFAULT_DECOMPOSER,
77
77
  };
78
78
  }
79
+
80
+ /**
81
+ * Overlay an operator's block onto the framework defaults — the per-key `??`
82
+ * fallback these accessors have always applied, written once.
83
+ *
84
+ * Iterating the **defaults'** keys rather than the user's is what keeps the
85
+ * returned shape closed: a key the framework does not define cannot reach a
86
+ * consumer through here even if one somehow survived AJV, so a typo degrades to
87
+ * the default rather than to an undefined a caller would read as configuration.
88
+ *
89
+ * @template {Record<string, unknown>} T
90
+ * @param {T} defaults Frozen framework defaults.
91
+ * @param {object|null|undefined} user Operator block from `.agentrc`.
92
+ * @returns {T} A fresh object; the frozen defaults are never mutated.
93
+ */
94
+ function withDefaults(defaults, user) {
95
+ const out = { ...defaults };
96
+ for (const key of Object.keys(defaults)) {
97
+ if (user?.[key] != null) out[key] = user[key];
98
+ }
99
+ return out;
100
+ }
@@ -13,7 +13,7 @@
13
13
  * ├─ techspec.md
14
14
  * ├─ manifest.md (dispatch manifest)
15
15
  * ├─ retro.md (mirror of GitHub retro at Epic close)
16
- * ├─ lifecycle.ndjson (lifecycle bus ledger)
16
+ * ├─ lifecycle.ndjson (lifecycle ledger)
17
17
  * ├─ checkpoints/... (pre-v2 epic-runner state store; retained layout)
18
18
  * ├─ <name> (runArtifactPath escape hatch)
19
19
  * └─ stories/
@@ -124,7 +124,7 @@ export function _clearMainCheckoutRootCache() {
124
124
  *
125
125
  * The shared test bootstrap (`lib/test-env.js`) sets this to a fresh
126
126
  * `os.tmpdir()` directory before spawning the test runner, so any test that
127
- * reaches a writer (`signals-writer`, the lifecycle `LedgerWriter`, etc.)
127
+ * reaches a writer (`signals-writer`, `appendLedgerEvent`, etc.)
128
128
  * *without* explicitly injecting an absolute tempRoot still resolves under
129
129
  * scratch instead of the repo's real `temp/` tree. This is the single
130
130
  * injection seam: because every path helper funnels a relative root through
@@ -573,9 +573,10 @@ function storyArtifactPath(eid, sid, name, config) {
573
573
  }
574
574
 
575
575
  /**
576
- * `temp/run-<eid>/lifecycle.ndjson` — append-only lifecycle bus ledger
577
- * (Story #2510). The LedgerWriter persists every emitted/completed/failed
578
- * record here; the TraceLogger renders the companion markdown from it.
576
+ * `temp/run-<eid>/lifecycle.ndjson` — append-only lifecycle ledger
577
+ * (Story #2510). `appendLedgerEvent` persists one `emitted` record here per
578
+ * merge-terminal outcome; Story #5024 retired the `LedgerWriter` listener and
579
+ * the `TraceLogger` markdown companion that preceded it.
579
580
  *
580
581
  * The path is also the canonical input the standalone `lifecycle-emit`
581
582
  * CLI feeds to `buildDefaultListenerChain` when assembling the default