mandrel 2.30.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 (253) 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/ci-remediation.md +44 -1
  10. package/.agents/rules/git-conventions-reference.md +27 -27
  11. package/.agents/rules/git-conventions.md +4 -2
  12. package/.agents/rules/known-tooling-behavior.md +66 -30
  13. package/.agents/rules/testing-standards.md +35 -71
  14. package/.agents/runtime-deps.json +0 -1
  15. package/.agents/schemas/agentrc.schema.json +1939 -1400
  16. package/.agents/schemas/lifecycle/README.md +21 -14
  17. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  18. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  19. package/.agents/scripts/README.md +7 -29
  20. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  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 +136 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  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 +0 -1
  38. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  39. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  40. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  41. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  42. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  43. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  44. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  45. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  46. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  47. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  48. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  49. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  50. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  51. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  52. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  53. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  54. package/.agents/scripts/lib/child-exec.js +193 -0
  55. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  56. package/.agents/scripts/lib/cli-args.js +64 -0
  57. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  58. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  59. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  60. package/.agents/scripts/lib/config/commands.js +3 -5
  61. package/.agents/scripts/lib/config/explain.js +5 -7
  62. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  63. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  64. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  65. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  66. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  67. package/.agents/scripts/lib/config/gates/index.js +5 -2
  68. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  69. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  70. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  71. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  72. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  73. package/.agents/scripts/lib/config/github.js +0 -1
  74. package/.agents/scripts/lib/config/limits.js +3 -48
  75. package/.agents/scripts/lib/config/qa.js +105 -0
  76. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  77. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  78. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  79. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  80. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  81. package/.agents/scripts/lib/crap-utils.js +84 -520
  82. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  83. package/.agents/scripts/lib/degraded-mode.js +2 -2
  84. package/.agents/scripts/lib/doc-tiers.js +3 -3
  85. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  86. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  87. package/.agents/scripts/lib/fs-walk.js +52 -0
  88. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  89. package/.agents/scripts/lib/git-utils.js +16 -36
  90. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  91. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  92. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  93. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  94. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  95. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  96. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  97. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  98. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  99. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  100. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  101. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  102. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  103. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  104. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  105. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  106. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  107. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  108. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  109. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  110. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  111. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  112. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  113. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  124. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
  125. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  126. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  127. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  128. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  129. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  130. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  131. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  132. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  133. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  134. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  135. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  136. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  137. package/.agents/scripts/lib/signals/index.js +8 -6
  138. package/.agents/scripts/lib/signals/schema.js +20 -25
  139. package/.agents/scripts/lib/signals/write.js +8 -8
  140. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  141. package/.agents/scripts/lib/temp-retention.js +1 -1
  142. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  143. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  144. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  145. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  146. package/.agents/scripts/lib/worktree-manager.js +2 -3
  147. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  148. package/.agents/scripts/pr-watch-with-update.js +7 -5
  149. package/.agents/scripts/providers/github/cache.js +2 -2
  150. package/.agents/scripts/providers/github/comments.js +6 -28
  151. package/.agents/scripts/providers/github/compose.js +0 -15
  152. package/.agents/scripts/providers/github/errors.js +10 -27
  153. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  154. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  155. package/.agents/scripts/providers/github.js +4 -7
  156. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  157. package/.agents/scripts/resolve-stories.js +0 -2
  158. package/.agents/scripts/run-lint.js +61 -61
  159. package/.agents/scripts/run-test-profile.js +6 -6
  160. package/.agents/scripts/run-verify.js +48 -30
  161. package/.agents/scripts/single-story-close.js +20 -0
  162. package/.agents/scripts/single-story-init.js +12 -35
  163. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  164. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  165. package/.agents/skills/skills.index.json +2 -12
  166. package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
  167. package/.agents/workflows/audit-documentation.md +5 -6
  168. package/.agents/workflows/audit-to-stories.md +2 -2
  169. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  170. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  171. package/.agents/workflows/helpers/code-review.md +3 -8
  172. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  173. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  174. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  175. package/.agents/workflows/mandrel-update.md +10 -10
  176. package/.agents/workflows/qa-assist.md +15 -20
  177. package/.agents/workflows/qa-explore.md +9 -8
  178. package/README.md +1 -1
  179. package/docs/CHANGELOG.md +49 -0
  180. package/lib/migrations/index.js +2 -0
  181. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  182. package/package.json +12 -3
  183. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  184. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  185. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  186. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  187. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  188. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  189. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  190. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  191. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  192. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  193. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  194. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  195. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  196. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  197. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  198. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  199. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  200. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  201. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  202. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  203. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  204. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  205. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  206. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  207. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  208. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  209. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  210. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  211. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  212. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  213. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  214. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  215. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  216. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  217. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  218. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  219. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  220. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  221. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  222. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  223. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  224. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  225. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  226. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  227. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  228. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  229. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  230. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  231. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  232. package/.agents/scripts/lib/planning-corpus.js +0 -37
  233. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  234. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  235. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  236. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  237. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  238. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  239. package/.agents/scripts/lib/signals/read.js +0 -268
  240. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  241. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  242. package/.agents/scripts/lib/story-plan.js +0 -379
  243. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  244. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  245. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  246. package/.agents/scripts/lint-baseline.js +0 -507
  247. package/.agents/scripts/providers/github/prs.js +0 -103
  248. package/.agents/scripts/signals-view.js +0 -309
  249. package/.agents/scripts/story-plan.js +0 -370
  250. package/.agents/scripts/sync-branch-from-base.js +0 -149
  251. package/.agents/scripts/validate-docs-freshness.js +0 -314
  252. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  253. package/.agents/workflows/helpers/signals.md +0 -112
@@ -2,12 +2,44 @@
2
2
 
3
3
  import Ajv from 'ajv';
4
4
 
5
+ import { COMMANDS_DEFAULTS } from './config/commands.js';
6
+ import {
7
+ BRANCH_PROTECTION_DEFAULTS,
8
+ DEFAULT_REQUIRED_CHECKS,
9
+ MERGE_METHODS_DEFAULTS,
10
+ NOTIFICATIONS_DEFAULTS,
11
+ } from './config/github.js';
12
+ import { PATHS_DEFAULTS } from './config/paths.js';
5
13
  import { SHELL_INJECTION_PATTERN_STRING } from './config-schema-shared.js';
6
14
  // `delivery.*` sub-schemas were extracted to a sibling module (refs #3457)
7
15
  // to keep this aggregate module above the maintainability floor. The
8
16
  // resolved AGENTRC_SCHEMA is unchanged.
9
17
  import { DELIVERY_SCHEMA } from './config-settings-schema-delivery.js';
10
18
 
19
+ /**
20
+ * Annotation contract (Story #5007). These schema literals are the SINGLE
21
+ * annotated source for the whole `.agentrc.json` surface:
22
+ *
23
+ * - `description` — the operator-facing gloss. `generate-config-docs.js`
24
+ * serializes it into the shipped JSON-Schema mirror
25
+ * (`.agents/schemas/agentrc.schema.json`, which every consumer config
26
+ * points `$schema` at) and into the `configuration.md` key table.
27
+ * - `default` — the value that appears in the generated defaults
28
+ * inventory `.agents/docs/agentrc-reference.json` (the SSOT
29
+ * `lib/config/defaults.js` reads for `mandrel explain` and the
30
+ * sync-agentrc redundancy advisory). Import the matching runtime
31
+ * `*_DEFAULTS` constant rather than restating a literal wherever one
32
+ * exists, so the annotation and the resolver cannot drift.
33
+ *
34
+ * A key with a runtime default but no `default` annotation is deliberately
35
+ * out of the inventory — annotating it would change what `mandrel explain`
36
+ * reports and what sync-agentrc flags redundant, which is a behaviour
37
+ * change, not a representation one.
38
+ *
39
+ * Nothing here is hand-mirrored any more: run `npm run docs:gen` after
40
+ * editing, and `npm run docs:check` fails closed on drift.
41
+ */
42
+
11
43
  const SAFE_STRING = {
12
44
  type: 'string',
13
45
  not: { pattern: SHELL_INJECTION_PATTERN_STRING },
@@ -65,39 +97,100 @@ export const AGENT_SETTINGS_STRING_FIELDS = Object.freeze([]);
65
97
  */
66
98
  const PATHS_SCHEMA = {
67
99
  type: 'object',
100
+ description:
101
+ 'The three required filesystem roots. Every `${dir}Root` the framework needs is derived at runtime as `${agentRoot}/<dir>`, and the audit output dir as `${tempRoot}/audits`.',
68
102
  required: ['agentRoot', 'docsRoot', 'tempRoot'],
69
103
  properties: {
70
- agentRoot: { ...SAFE_STRING, minLength: 1 },
71
- docsRoot: { ...SAFE_STRING, minLength: 1 },
72
- tempRoot: { ...SAFE_STRING, minLength: 1 },
104
+ agentRoot: {
105
+ ...SAFE_STRING,
106
+ minLength: 1,
107
+ description:
108
+ 'Repo-relative root of the materialized framework tree (`mandrel sync` writes here).',
109
+ default: PATHS_DEFAULTS.agentRoot,
110
+ },
111
+ docsRoot: {
112
+ ...SAFE_STRING,
113
+ minLength: 1,
114
+ description:
115
+ 'Repo-relative root of the project documentation the planner reads for context.',
116
+ default: PATHS_DEFAULTS.docsRoot,
117
+ },
118
+ tempRoot: {
119
+ ...SAFE_STRING,
120
+ minLength: 1,
121
+ description:
122
+ 'Repo-relative gitignored scratch root. Every temporary artifact — gate transcripts, audit reports, plan authoring dirs — lands under it.',
123
+ default: PATHS_DEFAULTS.tempRoot,
124
+ },
73
125
  },
74
126
  additionalProperties: false,
75
127
  };
76
128
 
77
129
  /**
78
- * `project.commands` — names of the lint/test/typecheck/format commands the
130
+ * `project.commands` — names of the test/typecheck/format commands the
79
131
  * close-validation chain spawns. `typecheck` accepts `null` to mean
80
132
  * "disabled". `validate` and `build` were dropped (no production consumers).
81
133
  */
82
134
  const COMMANDS_SCHEMA = {
83
135
  type: 'object',
136
+ description:
137
+ 'Shell commands the close-validation chain spawns. Each is run from the repo root.',
84
138
  properties: {
85
- lintBaseline: { ...SAFE_STRING, minLength: 1 },
86
- test: { ...SAFE_STRING, minLength: 1 },
87
- typecheck: NULLABLE_NONEMPTY_SAFE_STRING,
88
- formatCheck: { ...SAFE_STRING, minLength: 1 },
89
- formatWrite: { ...SAFE_STRING, minLength: 1 },
139
+ test: {
140
+ ...SAFE_STRING,
141
+ minLength: 1,
142
+ description: 'Full test-suite command run by the close-validation chain.',
143
+ default: COMMANDS_DEFAULTS.test,
144
+ },
145
+ typecheck: {
146
+ ...NULLABLE_NONEMPTY_SAFE_STRING,
147
+ description:
148
+ 'Static type-check command. `null` disables the gate for projects with no type layer; the empty string is rejected so a typo cannot silently disable it.',
149
+ default: COMMANDS_DEFAULTS.typecheck,
150
+ },
151
+ formatCheck: {
152
+ ...SAFE_STRING,
153
+ minLength: 1,
154
+ description:
155
+ 'Non-mutating format verification run as a close-validation gate.',
156
+ default: COMMANDS_DEFAULTS.formatCheck,
157
+ },
158
+ formatWrite: {
159
+ ...SAFE_STRING,
160
+ minLength: 1,
161
+ description:
162
+ 'Mutating format command the close-time format-autofix step spawns.',
163
+ default: COMMANDS_DEFAULTS.formatWrite,
164
+ },
90
165
  },
91
166
  additionalProperties: false,
92
167
  };
93
168
 
94
169
  const PROJECT_SCHEMA = {
95
170
  type: 'object',
171
+ description:
172
+ 'Project identity, filesystem roots, planner docs context, and the commands the close-validation chain spawns.',
96
173
  required: ['paths'],
97
174
  properties: {
98
- baseBranch: SAFE_STRING,
175
+ baseBranch: {
176
+ ...SAFE_STRING,
177
+ description:
178
+ 'Branch every `story-<id>` branch is seeded from and every Story PR targets.',
179
+ default: 'main',
180
+ },
99
181
  paths: PATHS_SCHEMA,
100
- docsContextFiles: { type: 'array', items: { type: 'string' } },
182
+ docsContextFiles: {
183
+ type: 'array',
184
+ items: { type: 'string' },
185
+ description:
186
+ 'Files under `paths.docsRoot` the planner treats as standing context. Read digest-first — the docs digest names the file and the line range, and only the named section is pulled.',
187
+ default: [
188
+ 'architecture.md',
189
+ 'data-dictionary.md',
190
+ 'decisions.md',
191
+ 'patterns.md',
192
+ ],
193
+ },
101
194
  commands: COMMANDS_SCHEMA,
102
195
  },
103
196
  additionalProperties: false,
@@ -110,10 +203,19 @@ const PROJECT_SCHEMA = {
110
203
  /**
111
204
  * Curated webhook event vocabulary. The webhook channel is gated by an
112
205
  * explicit allowlist of event names — the vocabulary mirrors the events the
113
- * v2 runtime actually emits through `notify()` (Story transitions, merge
114
- * outcomes, loop lifecycle beats).
206
+ * v2 runtime actually emits through `notify()` (Story transitions and merge
207
+ * outcomes).
208
+ *
209
+ * `loop.tick` was retired here on the same rule (Story #5024). Its only
210
+ * producer was `emit-loop-tick.js`, which published to the lifecycle bus and
211
+ * never called `notify()` at all — and the bus had no production caller, so
212
+ * the event could not reach a webhook by any path. The notify CLI cannot
213
+ * substitute: it hardcodes `event: 'operator-message'` and exposes no
214
+ * `--event` flag, so a consumer could not dispatch it either. It shipped in
215
+ * `NOTIFICATIONS_DEFAULTS`, which meant every consumer was subscribed by
216
+ * default to something that could never fire.
115
217
  *
116
- * `story.heartbeat` was retired here (A22): the vocabulary's contract is
218
+ * `story.heartbeat` was retired here first (A22): the vocabulary's contract is
117
219
  * "events the runtime actually emits", and nothing could emit this one. Its
118
220
  * emitter (`emit-story-heartbeat.js`) demanded an `epicId >= 1` while the
119
221
  * sole call path (`single-story-init.js` → `setActiveStoryEnv`) passed
@@ -131,7 +233,6 @@ export const WEBHOOK_EVENT_NAMES = Object.freeze([
131
233
  'operator-message',
132
234
  'merge.unlanded',
133
235
  'merge.flip-failed',
134
- 'loop.tick',
135
236
  ]);
136
237
 
137
238
  /**
@@ -142,11 +243,17 @@ export const WEBHOOK_EVENT_NAMES = Object.freeze([
142
243
  * is ticket scope, not importance. A comment is written *onto a Story
143
244
  * issue*, so only events that are about one Story, and whose message reads
144
245
  * as narrative an operator wants durably on the ticket, belong here. The
145
- * webhook-only remainder — `merge.unlanded`, `merge.flip-failed`,
146
- * `loop.tick` — are run-scoped or firehose beats;
147
- * mirroring them onto the ticket would bury the narrative under machine
148
- * chatter, and `notify()` drops a comment for any dispatch without a
149
- * resolvable ticket id regardless.
246
+ * webhook-only remainder — `merge.unlanded` and `merge.flip-failed` — are
247
+ * run-scoped beats; mirroring them onto the ticket would bury the narrative
248
+ * under machine chatter, and `notify()` drops a comment for any dispatch
249
+ * without a resolvable ticket id regardless.
250
+ *
251
+ * Note both webhook-only names are allowlistable but have no `notify()`
252
+ * dispatcher today — they reach the run ledger via `appendLedgerEvent`, not
253
+ * the notify path. That is a wiring gap, deliberately left alone by Story
254
+ * #5024 (which only removed `loop.tick`, whose producer went with the bus):
255
+ * unlike `loop.tick` these two have a live producer, so whether to wire the
256
+ * dispatch or drop the allowlist entries is an open decision, not dead code.
150
257
  *
151
258
  * `story-closing` IS in scope by that rule (Story-scoped, `level: 'story'`,
152
259
  * human-readable — the same shape as `story-merged`) and its earlier
@@ -165,17 +272,30 @@ export const COMMENT_EVENT_NAMES = Object.freeze([
165
272
 
166
273
  const NOTIFICATIONS_SCHEMA = {
167
274
  type: 'object',
275
+ description:
276
+ "Allowlist-gated notification channels. An event fires on a channel only when it is named in that channel's array.",
168
277
  properties: {
169
- mentionOperator: { type: 'boolean' },
278
+ mentionOperator: {
279
+ type: 'boolean',
280
+ description:
281
+ 'When true, `github.operatorHandle` is @-mentioned in the comments the notifier posts.',
282
+ default: NOTIFICATIONS_DEFAULTS.mentionOperator,
283
+ },
170
284
  commentEvents: {
171
285
  type: 'array',
172
286
  items: { type: 'string', enum: [...COMMENT_EVENT_NAMES] },
173
287
  uniqueItems: true,
288
+ description:
289
+ 'Events mirrored onto the Story issue as a comment. Deliberately narrower than `webhookEvents`: only Story-scoped events whose message reads as narrative an operator wants durably on the ticket belong here.',
290
+ default: [...NOTIFICATIONS_DEFAULTS.commentEvents],
174
291
  },
175
292
  webhookEvents: {
176
293
  type: 'array',
177
294
  items: { type: 'string', enum: [...WEBHOOK_EVENT_NAMES] },
178
295
  uniqueItems: true,
296
+ description:
297
+ 'Events dispatched to the configured webhook. The vocabulary is the allowlist the webhook channel gates on; `merge.unlanded` and `merge.flip-failed` are allowlistable but reach the run ledger rather than `notify()` today.',
298
+ default: [...NOTIFICATIONS_DEFAULTS.webhookEvents],
179
299
  },
180
300
  },
181
301
  additionalProperties: false,
@@ -183,13 +303,21 @@ const NOTIFICATIONS_SCHEMA = {
183
303
 
184
304
  const BRANCH_PROTECTION_CHECK_SCHEMA = {
185
305
  type: 'object',
306
+ description:
307
+ 'One required status check: the context name GitHub gates the merge on, plus the argv the framework runs locally to reproduce it.',
186
308
  required: ['name', 'cmd'],
187
309
  properties: {
188
- name: { type: 'string', minLength: 1 },
310
+ name: {
311
+ type: 'string',
312
+ minLength: 1,
313
+ description: 'Required status-check context name as GitHub reports it.',
314
+ },
189
315
  cmd: {
190
316
  type: 'array',
191
317
  minItems: 1,
192
318
  items: { type: 'string', minLength: 1 },
319
+ description:
320
+ 'argv array (never a shell string) the local pre-push validation runs to reproduce the check.',
193
321
  },
194
322
  },
195
323
  additionalProperties: false,
@@ -197,11 +325,24 @@ const BRANCH_PROTECTION_CHECK_SCHEMA = {
197
325
 
198
326
  const BRANCH_PROTECTION_SCHEMA = {
199
327
  type: 'object',
328
+ description:
329
+ 'Branch-protection stance applied to `project.baseBranch` by the GitHub bootstrap, and reproduced locally before every push.',
200
330
  properties: {
201
- enforce: { type: 'boolean' },
331
+ enforce: {
332
+ type: 'boolean',
333
+ description:
334
+ 'When true, the GitHub bootstrap writes the required-check ruleset. False leaves the remote stance alone.',
335
+ default: BRANCH_PROTECTION_DEFAULTS.enforce,
336
+ },
202
337
  requiredChecks: {
203
338
  type: 'array',
204
339
  items: BRANCH_PROTECTION_CHECK_SCHEMA,
340
+ description:
341
+ 'Checks that must pass before a Story PR merges. Each entry carries both the remote context name and the local argv.',
342
+ default: DEFAULT_REQUIRED_CHECKS.map((c) => ({
343
+ name: c.name,
344
+ cmd: [...c.cmd],
345
+ })),
205
346
  },
206
347
  },
207
348
  additionalProperties: false,
@@ -209,26 +350,79 @@ const BRANCH_PROTECTION_SCHEMA = {
209
350
 
210
351
  const MERGE_METHODS_SCHEMA = {
211
352
  type: 'object',
353
+ description:
354
+ 'Repository merge-method stance the GitHub bootstrap enforces. The framework ships squash-only with auto-merge on, which is what the one-PR-per-Story model needs for release-please to parse each landed subject.',
212
355
  properties: {
213
- allow_squash_merge: { type: 'boolean' },
214
- allow_rebase_merge: { type: 'boolean' },
215
- allow_merge_commit: { type: 'boolean' },
216
- allow_auto_merge: { type: 'boolean' },
217
- delete_branch_on_merge: { type: 'boolean' },
356
+ allow_squash_merge: {
357
+ type: 'boolean',
358
+ default: MERGE_METHODS_DEFAULTS.allow_squash_merge,
359
+ },
360
+ allow_rebase_merge: {
361
+ type: 'boolean',
362
+ default: MERGE_METHODS_DEFAULTS.allow_rebase_merge,
363
+ },
364
+ allow_merge_commit: {
365
+ type: 'boolean',
366
+ default: MERGE_METHODS_DEFAULTS.allow_merge_commit,
367
+ },
368
+ allow_auto_merge: {
369
+ type: 'boolean',
370
+ default: MERGE_METHODS_DEFAULTS.allow_auto_merge,
371
+ },
372
+ delete_branch_on_merge: {
373
+ type: 'boolean',
374
+ default: MERGE_METHODS_DEFAULTS.delete_branch_on_merge,
375
+ },
218
376
  },
219
377
  additionalProperties: false,
220
378
  };
221
379
 
222
380
  const GITHUB_SCHEMA = {
223
381
  type: 'object',
382
+ description:
383
+ 'GitHub provider identity plus the remote stance the bootstrap enforces. `owner`, `repo`, and `operatorHandle` are operator identity — the shipped values are placeholders, not usable defaults.',
224
384
  required: ['owner', 'repo', 'operatorHandle'],
225
385
  properties: {
226
- owner: { type: 'string', minLength: 1 },
227
- repo: { type: 'string', minLength: 1 },
228
- projectNumber: { type: ['integer', 'null'], minimum: 1 },
229
- projectOwner: { type: ['string', 'null'], minLength: 1 },
230
- operatorHandle: { type: 'string', pattern: '^@.+' },
231
- defaultTimeoutMs: { type: 'integer', minimum: 1000 },
386
+ owner: {
387
+ type: 'string',
388
+ minLength: 1,
389
+ description: 'GitHub owner (user or org) that hosts the repository.',
390
+ default: '[OWNER]',
391
+ },
392
+ repo: {
393
+ type: 'string',
394
+ minLength: 1,
395
+ description: 'Repository name under `owner`.',
396
+ default: '[REPO]',
397
+ },
398
+ projectNumber: {
399
+ type: ['integer', 'null'],
400
+ minimum: 1,
401
+ description:
402
+ 'Projects V2 board number the orchestrator syncs Story status onto. `null` disables board sync.',
403
+ default: null,
404
+ },
405
+ projectOwner: {
406
+ type: ['string', 'null'],
407
+ minLength: 1,
408
+ description:
409
+ 'Owner of the Projects V2 board when it lives outside `owner` (an org board fed by a user repo). `null` means the board shares `owner`.',
410
+ default: null,
411
+ },
412
+ operatorHandle: {
413
+ type: 'string',
414
+ pattern: '^@.+',
415
+ description:
416
+ 'The human the framework escalates to, `@`-prefixed. Used for HITL @-mentions on `agent::blocked`.',
417
+ default: '@[USERNAME]',
418
+ },
419
+ defaultTimeoutMs: {
420
+ type: 'integer',
421
+ minimum: 1000,
422
+ description:
423
+ 'Default `timeoutMs` applied to every `gh` subprocess the provider facade spawns, so a stalled socket or long-poll cannot hang an orchestration indefinitely. A `GhExecTimeoutError` from a hit ceiling is classified `transient` and retried by `withTransientRetry`. Story #2860.',
424
+ default: 60000,
425
+ },
232
426
  branchProtection: BRANCH_PROTECTION_SCHEMA,
233
427
  mergeMethods: MERGE_METHODS_SCHEMA,
234
428
  notifications: NOTIFICATIONS_SCHEMA,
@@ -260,8 +454,21 @@ const GITHUB_SCHEMA = {
260
454
 
261
455
  const PLANNING_SCHEMA = {
262
456
  type: 'object',
457
+ description:
458
+ 'Inputs to `/plan`: risk escalation heuristics, ceremony-lite routing, and the cross-Story conflict-finding severity gates.',
263
459
  properties: {
264
- riskHeuristics: LIST_OR_EXTENDER_OF_STRINGS,
460
+ riskHeuristics: {
461
+ ...LIST_OR_EXTENDER_OF_STRINGS,
462
+ description:
463
+ 'Prose heuristics the planner escalates a Story against. A plain array replaces the framework list; the `{ append, prepend }` extender form deep-merges with it.',
464
+ default: [
465
+ 'Destructive or irreversible data mutations (dropping tables, deleting rows without soft-delete or backup, truncating production state).',
466
+ 'Modifications to shared security or auth infrastructure (IAM policies, auth middleware, session or token handling, secret rotation).',
467
+ 'Changes to CI/CD, deployment pipelines, or release gating that could disable safety checks or ship unverified code to production.',
468
+ 'Monorepo-wide AST or text replacements touching overlapping files in parallel (catastrophic merge-conflict risk across concurrent agents).',
469
+ 'Schema migrations that rewrite existing rows or drop columns without a backfill or rollback plan.',
470
+ ],
471
+ },
265
472
  // Story #4722 (superseding #4683's word-count gate) — shape-derived
266
473
  // ceremony-lite routing. Complexity routes on the objective shape of the
267
474
  // authored work (changes[] count, acceptance count, creates-vs-refactors
@@ -303,11 +510,13 @@ const PLANNING_SCHEMA = {
303
510
  type: 'boolean',
304
511
  description:
305
512
  'When true, upgrade shared-editor conflict findings to hard errors (default false — advisory soft findings only).',
513
+ default: false,
306
514
  },
307
515
  requireExplicitCrossStoryDeps: {
308
516
  type: 'boolean',
309
517
  description:
310
518
  'When true, upgrade implicit cross-Story dependency findings to hard errors (default false — advisory soft findings only).',
519
+ default: false,
311
520
  },
312
521
  // Cross-cutting registry conflict knobs consumed by
313
522
  // `ticket-validator-conflicts.js` (wired through
@@ -321,22 +530,35 @@ const PLANNING_SCHEMA = {
321
530
  ...LIST_OR_EXTENDER_OF_STRINGS,
322
531
  description:
323
532
  'Registry path patterns whose concurrent edits across Stories are flagged as conflicts. Defaults to the framework listener/handler index patterns when omitted.',
533
+ // Mirrors DEFAULT_REGISTRY_PATTERNS in
534
+ // `lib/orchestration/ticket-validator-conflicts.js`. Restated rather
535
+ // than imported: that module pulls in the story-body parser and the
536
+ // reachability walker, which have no business loading behind a schema
537
+ // declaration. The rewritten parity suite asserts the two agree.
538
+ default: [
539
+ 'lib/orchestration/lifecycle/listeners/index.js',
540
+ '**/listeners/index.js',
541
+ '**/handlers/index.js',
542
+ ],
324
543
  },
325
544
  failOnRegistryConflicts: {
326
545
  type: 'boolean',
327
546
  description:
328
547
  'When true, upgrade cross-cutting registry conflict findings to hard errors (default false).',
548
+ default: false,
329
549
  },
330
550
  failOnLargeFanOut: {
331
551
  type: 'boolean',
332
552
  description:
333
553
  'When true, upgrade fan-out-warning findings (delete blast radius) to hard errors (default false — soft advisory).',
554
+ default: false,
334
555
  },
335
556
  largeFanOutThreshold: {
336
557
  type: 'integer',
337
558
  minimum: 0,
338
559
  description:
339
560
  'Call-site count above which a Story that deletes a module emits a fan-out-warning. Counts base-branch references to the deleted path basename. Soft by default; does not size or reject Stories. Default 10.',
561
+ default: 10,
340
562
  },
341
563
  // Navigability-reachability config consumed by the plan-persist draft
342
564
  // reachability gate (Epic #4131 F7; demoted into persist by #4474 PR6).
@@ -351,12 +573,14 @@ const PLANNING_SCHEMA = {
351
573
  items: { type: 'string' },
352
574
  description:
353
575
  'Glob patterns (e.g. pages/**, app/**/route.ts) marking paths that add a user-facing route.',
576
+ default: [],
354
577
  },
355
578
  navRegistry: {
356
579
  type: 'array',
357
580
  items: { type: 'string' },
358
581
  description:
359
582
  'Tokens identifying the nav-registry SSOT a route-adding Story is expected to reference.',
583
+ default: [],
360
584
  },
361
585
  },
362
586
  additionalProperties: false,
@@ -426,6 +650,13 @@ const QA_SIGN_IN_SEAM_SCHEMA = {
426
650
  // for `skill`/credential seams where per-persona material is genuinely
427
651
  // consulted. The resolver normalizes both to one canonical internal form.
428
652
  const QA_PERSONAS_SCHEMA = {
653
+ description:
654
+ 'Personas the QA-harness sign-in seam accepts. Two accepted shapes: (1) a plain array of persona names — the honest shape for a `urlTemplate` dev-impersonation seam, where the persona name is the sole input the workflow consumes; (2) the object-map form keyed by persona name, where each entry carries per-persona auth material (`credentialRef` or `signInSkill`) consulted only under a skill-based or credential-based seam.',
655
+ // Inventory value: an illustrative map showing both per-persona shapes.
656
+ default: {
657
+ admin: { credentialRef: 'QA_ADMIN_CREDENTIAL' },
658
+ member: { signInSkill: 'stack/qa/sign-in-member' },
659
+ },
429
660
  oneOf: [
430
661
  {
431
662
  type: 'array',
@@ -470,6 +701,23 @@ const QA_PERSONAS_SCHEMA = {
470
701
  // `.agents/rules/git-conventions.md` § Contract Cutovers).
471
702
  const QA_ENVIRONMENTS_SCHEMA = {
472
703
  type: 'object',
704
+ description:
705
+ 'Deployment targets the QA harness can run against (Epic #4326). A map keyed by environment name (e.g. `local`, `staging`), each carrying its own `baseUrl`, its own per-environment sign-in seam (the same url-template/skill union as the top-level seam), and an optional `allowWrites` gate. resolveQaEnvironment selects one environment per invocation by name or by raw-URL origin match against `baseUrl`; `allowWrites` defaults to true only for the `local` environment. Replaces the retired top-level single `signInSeam`.',
706
+ // Inventory value: an illustrative two-environment map, not a resolvable
707
+ // default. The QA harness is opt-in and every value here is
708
+ // project-specific; the entry exists so `mandrel explain` can show the
709
+ // expected shape.
710
+ default: {
711
+ local: {
712
+ baseUrl: 'http://localhost:3000',
713
+ signInSeam: { urlTemplate: '/dev/sign-in-as/{persona}' },
714
+ },
715
+ staging: {
716
+ baseUrl: 'https://staging.example.test',
717
+ signInSeam: { skill: 'stack/qa/sign-in' },
718
+ allowWrites: false,
719
+ },
720
+ },
473
721
  minProperties: 1,
474
722
  additionalProperties: {
475
723
  type: 'object',
@@ -483,18 +731,113 @@ const QA_ENVIRONMENTS_SCHEMA = {
483
731
  },
484
732
  };
485
733
 
734
+ // `gherkinLint` is the static corpus gate's contract (Story #5013). It is
735
+ // deliberately its own sub-block rather than more top-level `qa` keys: the
736
+ // gate is opt-in as a whole, so presence of the block IS the opt-in signal,
737
+ // and `check-gherkin-corpus.js` needs exactly one thing to test for. `scopes`
738
+ // is a map rather than an array because a scope's name appears verbatim in
739
+ // every finding, and a map makes naming it mandatory. Defaults for the two
740
+ // escape hatches live in `lib/config/qa.js` (GHERKIN_LINT_DEFAULTS).
741
+ const QA_GHERKIN_LINT_SCHEMA = {
742
+ type: 'object',
743
+ description:
744
+ 'Static Gherkin corpus gate (Story #5013). Optional; the gate runs only when this block is present, so an upgrade never reddens the lint of a consumer that never asked the framework to police its `.feature` files. Inside the opt-in it fails closed: an unresolvable `@cucumber/gherkin` parser, or a scope resolving zero step definitions, exits 1 rather than reporting a clean run.',
745
+ // Inventory value: an illustrative single-scope map, not a resolvable
746
+ // default. Every path here is project-specific; the entry exists so
747
+ // `mandrel explain` can show the expected shape.
748
+ default: {
749
+ scopes: {
750
+ web: {
751
+ featureRoots: ['apps/web/tests/features'],
752
+ stepRoots: ['apps/web/tests/steps'],
753
+ },
754
+ },
755
+ exemptionTags: ['@skip'],
756
+ stepWaivers: [],
757
+ },
758
+ properties: {
759
+ scopes: {
760
+ type: 'object',
761
+ description:
762
+ 'Binding scopes, keyed by name. Each scope resolves its own features against its own step definitions only — pooling every step root into one matcher list is what makes a cross-app false bind possible, where a step defined solely in app B silently vouches for app A. The scope name appears verbatim in every unbound finding.',
763
+ minProperties: 1,
764
+ additionalProperties: {
765
+ type: 'object',
766
+ properties: {
767
+ featureRoots: {
768
+ type: 'array',
769
+ minItems: 1,
770
+ items: { ...SAFE_STRING, minLength: 1 },
771
+ description:
772
+ 'Directories holding the `.feature` files of this scope, walked recursively.',
773
+ },
774
+ stepRoots: {
775
+ type: 'array',
776
+ minItems: 1,
777
+ items: { ...SAFE_STRING, minLength: 1 },
778
+ description:
779
+ 'Directories holding the step definitions of this scope, walked recursively. Resolving zero definitions here is a fail-closed error, not a clean run.',
780
+ },
781
+ },
782
+ required: ['featureRoots', 'stepRoots'],
783
+ additionalProperties: false,
784
+ },
785
+ },
786
+ exemptionTags: {
787
+ type: 'array',
788
+ items: { ...SAFE_STRING, minLength: 1 },
789
+ description:
790
+ 'Tags marking a scenario as intentionally non-binding, so must-bind skips it. Never an escape from must-compile: a parse error in the file still fails the run. Default: ["@skip"].',
791
+ default: ['@skip'],
792
+ },
793
+ stepWaivers: {
794
+ type: 'array',
795
+ items: { type: 'string', minLength: 1 },
796
+ description:
797
+ 'Exact step texts must-bind never reports as unbound. The step index is a source scan and therefore heuristic while the parser is exact, so a false unbound must always have an escape that does not require switching the gate off. Default: [].',
798
+ default: [],
799
+ },
800
+ },
801
+ required: ['scopes'],
802
+ additionalProperties: false,
803
+ };
804
+
486
805
  export const QA_SCHEMA = {
487
806
  type: 'object',
807
+ description:
808
+ 'Agent-driven QA harness contract (Epic #3214; environment-keyed by Epic #4326). Optional top-level block. All filesystem-pointer fields (featureRoot, fixturesManifest, designTokens) carry safeString guards rejecting shell-injection metacharacters. environments is a map of named deployment targets (each with a baseUrl, a per-environment url-template/skill sign-in seam, and an optional allowWrites gate); personas resolve to a stored credential reference or a sign-in skill, never an inline secret.',
488
809
  properties: {
489
- featureRoot: { ...SAFE_STRING, minLength: 1 },
490
- fixturesManifest: { ...SAFE_STRING, minLength: 1 },
810
+ featureRoot: {
811
+ ...SAFE_STRING,
812
+ minLength: 1,
813
+ description:
814
+ 'Directory holding the Gherkin feature files the QA sweep drives.',
815
+ default: 'tests/features',
816
+ },
817
+ fixturesManifest: {
818
+ ...SAFE_STRING,
819
+ minLength: 1,
820
+ description:
821
+ 'Path to the persona/fixture manifest the harness seeds from.',
822
+ default: 'tests/fixtures/personas.json',
823
+ },
491
824
  environments: QA_ENVIRONMENTS_SCHEMA,
492
825
  personas: QA_PERSONAS_SCHEMA,
826
+ gherkinLint: QA_GHERKIN_LINT_SCHEMA,
493
827
  consoleAllowlist: {
494
828
  type: 'array',
495
829
  items: { ...SAFE_STRING, minLength: 1 },
830
+ description:
831
+ 'Console-message substrings the QA run tolerates instead of reporting as a finding (framework dev-mode chatter).',
832
+ default: ['Download the React DevTools', '[HMR]'],
833
+ },
834
+ designTokens: {
835
+ ...SAFE_STRING,
836
+ minLength: 1,
837
+ description:
838
+ 'Path to the design-token SSOT the UX/UI lens checks rendered styles against.',
839
+ default: 'src/styles/tokens.css',
496
840
  },
497
- designTokens: { ...SAFE_STRING, minLength: 1 },
498
841
  },
499
842
  additionalProperties: false,
500
843
  };
@@ -503,7 +846,11 @@ export const AGENTRC_SCHEMA = {
503
846
  type: 'object',
504
847
  required: ['project'],
505
848
  properties: {
506
- $schema: { type: 'string' },
849
+ $schema: {
850
+ type: 'string',
851
+ description:
852
+ 'Editor pointer at the shipped JSON-Schema mirror. Not read by the runtime.',
853
+ },
507
854
  project: PROJECT_SCHEMA,
508
855
  github: GITHUB_SCHEMA,
509
856
  planning: PLANNING_SCHEMA,