mandrel 2.31.0 → 2.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +4 -4
  4. package/.agents/docs/agentrc-reference.json +61 -57
  5. package/.agents/docs/configuration.md +274 -227
  6. package/.agents/docs/execution-reference.md +13 -14
  7. package/.agents/docs/quality-gates.md +195 -23
  8. package/.agents/instructions.md +2 -5
  9. package/.agents/rules/git-conventions-reference.md +27 -27
  10. package/.agents/rules/git-conventions.md +4 -2
  11. package/.agents/rules/known-tooling-behavior.md +66 -30
  12. package/.agents/rules/testing-standards.md +35 -71
  13. package/.agents/runtime-deps.json +0 -1
  14. package/.agents/schemas/agentrc.schema.json +1939 -1400
  15. package/.agents/schemas/lifecycle/README.md +21 -14
  16. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  17. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  18. package/.agents/scripts/README.md +7 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/bootstrap.js +28 -26
  21. package/.agents/scripts/check-baseline-drift.js +73 -13
  22. package/.agents/scripts/check-baseline-scope.js +362 -0
  23. package/.agents/scripts/check-dead-exports.js +9 -1
  24. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  25. package/.agents/scripts/check-knip-entries.js +136 -0
  26. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  27. package/.agents/scripts/check-schema-references.js +1 -1
  28. package/.agents/scripts/diagnose-friction.js +7 -4
  29. package/.agents/scripts/generate-config-docs.js +263 -171
  30. package/.agents/scripts/install-matrix-assert.js +0 -1
  31. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  32. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  33. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  34. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  35. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  36. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  37. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  38. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  39. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  40. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  41. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  42. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  43. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  44. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  45. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  46. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  47. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  48. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  49. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  50. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  51. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  52. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  53. package/.agents/scripts/lib/child-exec.js +193 -0
  54. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  55. package/.agents/scripts/lib/cli-args.js +64 -0
  56. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  57. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  58. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  59. package/.agents/scripts/lib/config/commands.js +3 -5
  60. package/.agents/scripts/lib/config/explain.js +5 -7
  61. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  62. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  63. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  64. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  65. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  66. package/.agents/scripts/lib/config/gates/index.js +5 -2
  67. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  68. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  69. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  70. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  71. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  72. package/.agents/scripts/lib/config/github.js +0 -1
  73. package/.agents/scripts/lib/config/limits.js +3 -48
  74. package/.agents/scripts/lib/config/qa.js +105 -0
  75. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  76. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  77. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  78. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  79. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  80. package/.agents/scripts/lib/crap-utils.js +84 -520
  81. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  82. package/.agents/scripts/lib/degraded-mode.js +2 -2
  83. package/.agents/scripts/lib/doc-tiers.js +3 -3
  84. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  85. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  86. package/.agents/scripts/lib/fs-walk.js +52 -0
  87. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  88. package/.agents/scripts/lib/git-utils.js +16 -36
  89. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  90. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  91. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  92. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  93. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  94. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  95. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  96. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  97. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  98. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  99. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  100. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  101. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  102. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  103. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  104. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  105. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  106. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  107. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  108. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  109. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  110. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  111. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  112. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  122. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  123. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  124. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  125. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  126. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  127. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  128. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  129. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  130. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  131. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  132. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  133. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  134. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  135. package/.agents/scripts/lib/signals/index.js +8 -6
  136. package/.agents/scripts/lib/signals/schema.js +20 -25
  137. package/.agents/scripts/lib/signals/write.js +8 -8
  138. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  139. package/.agents/scripts/lib/temp-retention.js +1 -1
  140. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  141. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  142. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  143. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  144. package/.agents/scripts/lib/worktree-manager.js +2 -3
  145. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  146. package/.agents/scripts/pr-watch-with-update.js +7 -5
  147. package/.agents/scripts/providers/github/cache.js +2 -2
  148. package/.agents/scripts/providers/github/comments.js +6 -28
  149. package/.agents/scripts/providers/github/compose.js +0 -15
  150. package/.agents/scripts/providers/github/errors.js +10 -27
  151. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  152. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  153. package/.agents/scripts/providers/github.js +4 -7
  154. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  155. package/.agents/scripts/resolve-stories.js +0 -2
  156. package/.agents/scripts/run-lint.js +61 -61
  157. package/.agents/scripts/run-test-profile.js +6 -6
  158. package/.agents/scripts/run-verify.js +48 -30
  159. package/.agents/scripts/single-story-close.js +20 -0
  160. package/.agents/scripts/single-story-init.js +12 -35
  161. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  162. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  163. package/.agents/skills/skills.index.json +1 -11
  164. package/.agents/workflows/audit-documentation.md +5 -6
  165. package/.agents/workflows/audit-to-stories.md +2 -2
  166. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  167. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  168. package/.agents/workflows/helpers/code-review.md +3 -8
  169. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  170. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  171. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  172. package/.agents/workflows/mandrel-update.md +10 -10
  173. package/.agents/workflows/qa-assist.md +15 -20
  174. package/.agents/workflows/qa-explore.md +9 -8
  175. package/README.md +1 -1
  176. package/docs/CHANGELOG.md +42 -0
  177. package/lib/migrations/index.js +2 -0
  178. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  179. package/package.json +12 -3
  180. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  181. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  182. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  183. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  184. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  185. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  186. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  187. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  188. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  189. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  190. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  191. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  192. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  193. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  194. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  195. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  196. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  197. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  198. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  199. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  200. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  201. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  202. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  203. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  204. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  205. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  206. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  207. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  208. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  209. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  210. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  211. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  212. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  213. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  214. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  215. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  216. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  217. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  218. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  219. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  220. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  221. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  222. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  223. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  224. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  225. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  226. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  227. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  228. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  229. package/.agents/scripts/lib/planning-corpus.js +0 -37
  230. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  231. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  232. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  233. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  234. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  235. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  236. package/.agents/scripts/lib/signals/read.js +0 -268
  237. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  238. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  239. package/.agents/scripts/lib/story-plan.js +0 -379
  240. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  241. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  242. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  243. package/.agents/scripts/lint-baseline.js +0 -507
  244. package/.agents/scripts/providers/github/prs.js +0 -103
  245. package/.agents/scripts/signals-view.js +0 -309
  246. package/.agents/scripts/story-plan.js +0 -370
  247. package/.agents/scripts/sync-branch-from-base.js +0 -149
  248. package/.agents/scripts/validate-docs-freshness.js +0 -314
  249. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  250. package/.agents/workflows/helpers/signals.md +0 -112
@@ -109,7 +109,7 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
109
109
  **Slug format**: \`^[a-z0-9][a-z0-9-]*$\` — hyphen-case only. Underscores are rejected by the validator.
110
110
 
111
111
  ### STORY BODY SCHEMA (REQUIRED FOR EVERY STORY):
112
- \`body\` is either the serialized markdown **string** (the section format below) or a **structured object** carrying the same fields (\`goal\`, optional \`slicing\` / \`spec\`, \`changes\`, optional \`non_goals\` / \`wide\` / \`reason_to_exist\` / \`estimated_test_files\`) — persist parses either shape and serializes the canonical markdown itself, so you never need to read \`story-body.js\` or hand-assemble the markdown (the \`stories.template.json\` file emitted next to the plan-context envelope is a ready-to-fill structured-object skeleton). Stories are consumed by non-interactive sub-agents that must self-verify from the Story ticket alone — so the ticket must carry everything an agent needs to execute and self-verify.
112
+ \`body\` is either the serialized markdown **string** (the section format below) or a **structured object** carrying the same fields (\`goal\`, optional \`slicing\` / \`spec\`, \`changes\`, optional \`non_goals\` / \`wide\` / \`reason_to_exist\`) — persist parses either shape and serializes the canonical markdown itself, so you never need to read \`story-body.js\` or hand-assemble the markdown (the \`stories.template.json\` file emitted next to the plan-context envelope is a ready-to-fill structured-object skeleton). Stories are consumed by non-interactive sub-agents that must self-verify from the Story ticket alone — so the ticket must carry everything an agent needs to execute and self-verify.
113
113
 
114
114
  The \`acceptance[]\` and \`verify[]\` arrays live at the **top level** of the Story ticket object — that is the machine contract the validator reads. Author each list **once, at top level**, and **omit** the \`## Acceptance\` / \`## Verify\` sections from the authored \`body\` string: persist syncs the top-level arrays into those sections so the GitHub issue stays a complete executable document. The validator resolves both fields from the top level, so an omitted section is the expected shape, not a violation.
115
115
 
@@ -151,7 +151,6 @@ The **persisted** \`body\` renders these markdown sections (in order) — you au
151
151
  - **acceptance** (top-level array on the ticket object): Items MUST be observable from outside the agent. Acceptable shapes: a specific command exits 0, a file exists at a given path, a snapshot test matches, a \`data-testid\` resolves under a given selector, a row count in a fixture matches. UNACCEPTABLE: "verify by reading the diff", "looks good", "matches the spec" — push these down into a \`verify\` command instead.
152
152
  - **verify** (top-level array on the ticket object): Each entry MUST name a testing tier in parentheses, drawn from \`unit\` / \`contract\` / \`e2e\` / \`validate\`. Example: \`npm run test -- src/x.test.ts (unit)\`, \`npm run validate (validate)\`. Stories with zero verify entries SHOULD fail validation; if a story is genuinely unverifiable in isolation (e.g., a copy edit auditor will eyeball), the literal entry \`manual:<reason>\` is allowed so the absence is intentional, not lazy. Manual entries without a reason are rejected.
153
153
  - **reason to exist** (REQUIRED, encoded as the \`reason_to_exist\` field of the \`<!-- meta: {...} -->\` comment appended to the serialized body string — NOT a top-level ticket field): One sentence stating the single coherent reason this Story exists, distinct from its broader \`## Goal\` prose. Every Story MUST carry a non-empty \`reason_to_exist\`; it is the machine-checkable form of the cohesion rule (**one Story = one coherent change with one reason to exist**) and the \`epic-plan-consolidate\` critic flags any Story whose body carries no non-empty reason to exist. Encode it as \`<!-- meta: {"reason_to_exist": "..."} -->\`.
154
- - **estimated_test_files** (optional, encoded in the \`<!-- meta: {...} -->\` comment appended to the serialized body string — NOT a top-level ticket field): Integer estimate of how many test files this Story creates or modifies. Omit when the number is not estimable. Informational only — it does not gate the decompose.
155
154
  - **Observed-behavior claims open with \`Current state (verified <date>)\`.** Any Spec claim about how the codebase behaves today MUST open with that preamble (e.g. \`Current state (verified 2026-07-17): …\`) so a reader can tell a verified observation from an assumption, and can tell when the observation went stale.
156
155
  - **Intent-then-proxy acceptance shape.** When an acceptance item verifies through a proxy check (a grep, a file-exists probe, an exit-code test), state the intent clause before the proxy check — what outcome the check stands in for — so the proxy never becomes the goal (e.g. "the workflow names hygiene findings as re-author input: \`grep -n "textHygiene" …\` exits 0").
157
156
  - **Slicing checkpoints are one line each.** Each \`## Slicing\` checkpoint is a single line naming the checkpoint; implementation detail lives in \`## Spec\`, never duplicated into Slicing. A Slicing section outweighing its Spec is a defect the text-hygiene lint flags.
@@ -171,7 +170,7 @@ The Story is executed by a frontier-model deliverer that reads the codebase itse
171
170
 
172
171
  #### DETERMINISTIC BODY-FORMAT LINTS — author lint-clean by construction:
173
172
 
174
- Persist enforces the deterministic body-format rules below and **rejects** an authored body that violates any of them. Author every Story to satisfy all of them on the FIRST draft — each rule is stated example-first so there is nothing to discover by trial-and-error. The two auto-fixable rules (\`changes-path-entry-shape\`, \`verify-tier-suffix\`) also emit the corrected form in the dry-run failure output, but authoring them right up front avoids the round-trip entirely.
173
+ Persist enforces the deterministic body-format rules below and **rejects** an authored body that violates any of them. Author every Story to satisfy all of them on the FIRST draft — each rule is stated example-first so there is nothing to discover by trial-and-error. \`verify-tier-suffix\` is the one rule persist repairs for you: when the tier is unambiguously inferable from the command, persist appends it and proceeds; when it is not, the entry is still rejected and you must choose the tier. \`changes-path-entry-shape\` emits the corrected form in the dry-run failure output but is never applied for you.
175
174
 
176
175
  ${bodyFormatLintChecklist}
177
176
 
@@ -231,35 +230,38 @@ Declaring \`wide\` with a non-empty reason **lifts the hard session-mass rejecti
231
230
 
232
231
  #### UI / TESTID INVARIANCE (per CLAUDE.md safety rule):
233
232
 
234
- - Stories that touch UI (\`*.tsx\`, \`*.astro\`, \`*.svelte\`, \`*.vue\`, components folders) MUST end \`changes\` with one of:
235
- - \`data-testid invariance: <list of testids that MUST be preserved>\`, or
236
- - \`data-testid changes: <old> -> <new>\` paired with a corresponding \`tests/e2e/*.spec.ts\` edit in the same story or a depends_on Story.
233
+ Every \`changes[]\` entry is a \`{ path, assumption }\` object a prose bullet there is rejected by the parser, so the testid contract is carried where prose belongs:
234
+
235
+ - Stories that touch UI (\`*.tsx\`, \`*.astro\`, \`*.svelte\`, \`*.vue\`, components folders) MUST carry the testid contract as a top-level \`acceptance[]\` item, one of:
236
+ - \`"data-testid invariance: <list of testids that MUST be preserved>"\`, or
237
+ - \`"data-testid changes: <old> -> <new>, with the matching tests/e2e/*.spec.ts selector updated"\` — paired with that \`tests/e2e/*.spec.ts\` file in \`changes[]\`, in the same Story or a depends_on Story.
238
+ - State the preserved-testid set in \`## Non-Goals\` prose as well when the Story deliberately renames nothing.
237
239
  - Renaming a testid without the matching e2e edit is FORBIDDEN.
238
240
 
239
241
  #### BRAND / COPY / STYLE WORK:
240
242
 
241
- - Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of \`docs/style-guide.md\` in \`acceptance\` (e.g. \`"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]\`). If \`docs/style-guide.md\` does not exist or has no relevant section, state that explicitly: \`"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in the Epic body"]\`. Silence on style sourcing is a smell.
243
+ - Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of \`docs/style-guide.md\` in \`acceptance\` (e.g. \`"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]\`). If \`docs/style-guide.md\` does not exist or has no relevant section, state that explicitly: \`"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in the plan seed"]\`. Silence on style sourcing is a smell.
242
244
 
243
- ### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when the Acceptance Spec has \`new\`-disposition rows):
244
- The Acceptance Spec's AC table (columns \`AC ID | Outcome | Feature File | Scenario | Disposition\`) tags each row's \`Disposition\` with one of \`new | updated | unchanged\`. A \`new\` row names a \`.feature\` file + scenario that does NOT yet exist on \`main\`. The framework is features-first: implementing Stories reference those \`.feature\` paths in their \`verify[]\` lines, so the files MUST already exist when those Stories run — otherwise verification fails mid-delivery on a missing file. (These Gherkin \`.feature\` files are BDD artifacts, unrelated to any ticket tier.)
245
+ ### WAVE-0 BDD SCAFFOLD STORY (features-first; emit when your plan verifies against a scenario that does not exist yet):
246
+ The plan-context envelope's \`bddScenarios\` field is the index of Gherkin scenarios that **already exist on \`main\`** one row per scenario, carrying its \`.feature\` file path, line, scenario title and tags. It is the live signal for this rule: a \`.feature\` path + scenario your plan needs but that appears in no \`bddScenarios\` row does not exist yet. The framework is features-first: implementing Stories reference those \`.feature\` paths in their \`verify[]\` lines, so the files MUST already exist when those Stories run — otherwise verification fails mid-delivery on a missing file.
245
247
 
246
- When the Acceptance Spec contains **one or more \`Disposition: new\` rows**, you MUST emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create the \`.feature\` files with \`@skip\`-tagged scenarios BEFORE any implementation Story runs:
248
+ When **one or more** \`.feature\` scenarios your plan verifies against are absent from \`bddScenarios\`, you MUST emit **exactly one** dedicated wave-0 scaffold Story whose sole job is to create those \`.feature\` files with \`@skip\`-tagged scenarios BEFORE any implementation Story runs:
247
249
 
248
250
  - **goal**: contains the literal token \`bdd-scaffold\` (e.g. "bdd-scaffold: create the @skip-tagged feature files the implementation Stories verify against").
249
251
  - **depends_on**: EMPTY (\`[]\`) — it runs first, in wave 0.
250
- - **changes**: one entry per distinct \`.feature\` file named in a \`new\` row, each \`{ "path": "<feature file path>", "assumption": "creates" }\`.
252
+ - **changes**: one entry per distinct absent \`.feature\` file, each \`{ "path": "<feature file path>", "assumption": "creates" }\`.
251
253
  - **acceptance**: MUST assert (a) every new \`.feature\` file exists AND (b) every new scenario within them carries an \`@skip\` tag. Keep these observable (a grep/validate command exits 0, a file exists at a path).
252
254
  - **verify**: a grep/validate command (tier \`validate\`), NOT an e2e runner — verifying that a file exists with the required tags needs no browser/playwright run. Example: \`grep -rL '@skip' tests/features/<area>/*.feature (validate)\` paired with an existence check.
253
255
  - Each implementation Story whose \`verify[]\` references one of these scaffolded \`.feature\` paths MUST \`depends_on\` the scaffold Story (so the scaffold lands in an earlier wave). Omitting the link trips the soft \`missing-bdd-scaffold\` validator finding.
254
256
 
255
- When the Acceptance Spec contains **zero \`new\`-disposition rows** (every row is \`updated\` or \`unchanged\`), do NOT emit a scaffold Story — there is nothing to create.
257
+ When every scenario your plan verifies against is already present in \`bddScenarios\`, do NOT emit a scaffold Story — there is nothing to create.
256
258
 
257
259
  ### SCOPE-OVERLAP FLAGGING (docs/runbook downstream of config work):
258
- When a "docs update" / "runbook" / "README" Story appears downstream of an earlier Story in the same Epic whose AC already covers updating the same document (e.g. a "config + runbook" Story followed by a "docs" Story touching the same runbook), the downstream Story's deliverable may be fully absorbed by the earlier Story. Flag the risk directly in the Story's top-level \`acceptance\` array by appending an item of the form:
260
+ When a "docs update" / "runbook" / "README" Story appears downstream of an earlier Story in the same plan whose AC already covers updating the same document (e.g. a "config + runbook" Story followed by a "docs" Story touching the same runbook), the downstream Story's deliverable may be fully absorbed by the earlier Story. Flag the risk directly in the Story's top-level \`acceptance\` array by appending an item of the form:
259
261
  "Scope verification note: this story's deliverable may already be satisfied by Story #<slug-or-id>'s AC — before implementing, \`git diff main -- <path>\` against the upstream Story branch and confirm whether a substantive edit is still required, or whether only a cross-reference remains."
260
262
  This prevents the executing agent from redoing work the upstream Story already merged.
261
263
 
262
- CRITICAL: Dependencies should follow execution blockers. Stories attach directly to the Epic — never emit a 'parent_slug' field.
264
+ CRITICAL: Dependencies should follow execution blockers. There is no parent ticket — never emit a 'parent_slug' field.
263
265
  IMPORTANT DEPENDENCY RULE: Story-to-Story dependencies are expressed via \`depends_on\` (one Story depends_on another Story's slug). Use this to express execution ordering across the plan.
264
266
 
265
267
  ### REVIEWABILITY BUDGET (Story #2798):
@@ -53,11 +53,10 @@ export const TICKET_BODY_SECTIONS = Object.freeze({
53
53
  export const ACCEPTANCE_TABLE_HEADING = '## Acceptance Table';
54
54
 
55
55
  /**
56
- * Regex matching the Tech Spec's required opening heading (same variants
57
- * `spec-section-validator.js` accepts). Exported so it is the single
58
- * source of truth for this pattern — `spec-section-validator.js`,
59
- * `consolidation-precondition.js` all import
60
- * it rather than each carrying their own copy.
56
+ * Regex matching the Tech Spec's `## Delivery Slicing` heading, tolerating
57
+ * the `Delivery ` qualifier and casing variants. Exported so it is the single
58
+ * source of truth for this pattern — `consolidation-precondition.js` imports
59
+ * it rather than carrying its own copy.
61
60
  */
62
61
  export const DELIVERY_SLICING_RE = /^##\s+(?:Delivery\s+)?Slicing\s*$/im;
63
62
 
@@ -3,23 +3,25 @@
3
3
  *
4
4
  * The "is the worktree's work integrated upstream?" half of
5
5
  * `isSafeToRemove`. Runs the two-phase reachability gate the parent
6
- * documents: primary `merge-base --is-ancestor HEAD epicRef`, and a
7
- * fallback `git log --grep=resolves #<storyId>` against the Epic ref when
8
- * the ancestry check returns "not an ancestor".
6
+ * documents: primary `merge-base --is-ancestor HEAD baseRef`, and a
7
+ * `git cherry` patch-equivalence fallback when the ancestry check returns
8
+ * "not an ancestor".
9
9
  *
10
10
  * The fallback exists because a post-merge rebase or force-push can drop
11
- * the local branch ref off the merged tip the `(resolves #N)` token on
12
- * the Epic's `--no-ff` merge commit (emitted by
13
- * `story-close/merge-runner.js`) is the durable proof the Story was
14
- * integrated.
11
+ * the local branch ref off the merged tip, so SHA reachability alone
12
+ * under-reports integration.
13
+ *
14
+ * A third phase used to sit between the two: a `git log --merges
15
+ * --grep=resolves #<storyId>` probe for the `(resolves #N)` token on the
16
+ * Epic's `--no-ff` merge commit. Story #5006 removed it — v2 lands every
17
+ * Story as a **squash** merge onto `main`, which never writes that token,
18
+ * and the `--no-ff` emitter (`story-close/merge-runner.js`) went with the
19
+ * Epic pipeline. The probe could only ever return `false`.
15
20
  *
16
21
  * Pure with respect to the supplied `ctx` bag; the only side effects are
17
22
  * the `gitSpawn` calls.
18
23
  */
19
24
 
20
- import { parseStoryBranch } from '../../git-utils.js';
21
- import { resolvesGrepArgs } from '../../orchestration/resolves-token.js';
22
-
23
25
  /**
24
26
  * Resolve a worktree's `HEAD` to a full commit SHA via
25
27
  * `git rev-parse HEAD` (run inside the worktree). Returns
@@ -70,37 +72,6 @@ export function checkHeadAncestor(ctx, headSha, epicRef) {
70
72
  };
71
73
  }
72
74
 
73
- /**
74
- * Predicate: did the Epic ref accumulate a `--no-ff` merge commit whose
75
- * subject names this Story (e.g. `... (resolves #1851)`)? Returns `true`
76
- * when the grep finds at least one matching merge commit, `false` when it
77
- * returns empty or fails.
78
- *
79
- * Returns `false` for branches that do not match the canonical
80
- * `story-<id>` shape — the merge-commit subject contract is only
81
- * guaranteed for story branches.
82
- *
83
- * @param {object} ctx
84
- * @param {string} branch Worktree branch (e.g. `story-1851`).
85
- * @param {string} epicRef Epic branch ref (e.g. `epic/1831`).
86
- * @returns {boolean}
87
- */
88
- export function hasMergeCommitForStory(ctx, branch, epicRef) {
89
- const storyId = parseStoryBranch(branch);
90
- if (storyId === null) return false;
91
- const grep = ctx.git.gitSpawn(
92
- ctx.repoRoot,
93
- 'log',
94
- epicRef,
95
- '--merges',
96
- '-n',
97
- '1',
98
- '--pretty=%H',
99
- ...resolvesGrepArgs(storyId),
100
- );
101
- return grep.status === 0 && grep.stdout.trim().length > 0;
102
- }
103
-
104
75
  /**
105
76
  * Predicate: are every commit on `branch` patch-equivalent to a commit
106
77
  * already on `epicRef`? Runs `git cherry <epicRef> <branch>` and returns
@@ -140,7 +111,7 @@ export function hasRebasedEquivalents(ctx, branch, epicRef) {
140
111
  }
141
112
 
142
113
  /**
143
- * Run the full three-phase merge-reachability gate. Returns the same
114
+ * Run the full two-phase merge-reachability gate. Returns the same
144
115
  * `{ safe, reason }` envelope `isSafeToRemove` does, so callers can chain
145
116
  * the verdict directly into the parent return value.
146
117
  *
@@ -165,9 +136,6 @@ export async function checkMergeReachability(ctx, wtPath, branch, epicRef) {
165
136
  };
166
137
  }
167
138
 
168
- if (hasMergeCommitForStory(ctx, branch, epicRef)) {
169
- return { safe: true, reason: 'merge-commit-reachable' };
170
- }
171
139
  if (hasRebasedEquivalents(ctx, branch, epicRef)) {
172
140
  return { safe: true, reason: 'rebased-equivalents' };
173
141
  }
@@ -47,11 +47,10 @@ const WINDOWS_CWD_RE =
47
47
  * (branch advanced to a SHA still reachable from the Epic merge commit).
48
48
  *
49
49
  * When HEAD is no longer an ancestor (force-push that drops or rewrites
50
- * the merged tip), the function falls back to a `merge-commit-reachable`
51
- * check: search the Epic ref for a `--no-ff` merge commit whose subject
52
- * carries this Story's `(resolves #<id>)` token. Such a merge commit
53
- * proves the Story branch was integrated even though the current HEAD
54
- * has diverged, so the worktree is still safe to reap.
50
+ * the merged tip), the function falls back to a `rebased-equivalents`
51
+ * check: `git cherry` proves every commit on the branch is already
52
+ * upstream by patch-id even though the current HEAD has diverged, so the
53
+ * worktree is still safe to reap.
55
54
  *
56
55
  * `opts.epicBranch` is the integration / base ref the Story must already
57
56
  * be merged into (e.g. `main` or a plan-run branch).
@@ -54,9 +54,8 @@ export class WorktreeManager {
54
54
  * @param {(phase: 'worktree-create'|'bootstrap'|'install') => void} [opts.onPhase]
55
55
  * Optional phase-boundary callback fired from `ensure()` just before each
56
56
  * internal phase (git worktree add, bootstrap-file copy, dependency
57
- * install). Consumed by `story-init` to drive `phase-timer.mark()`
58
- * so `[phase-timing]` log lines attribute wall-clock correctly to the
59
- * sub-phases of worktree provisioning.
57
+ * install), so a caller can attribute wall-clock to the sub-phases of
58
+ * worktree provisioning.
60
59
  */
61
60
  constructor({
62
61
  repoRoot,
@@ -38,8 +38,8 @@
38
38
  import { readdirSync, readFileSync } from 'node:fs';
39
39
  import path from 'node:path';
40
40
  import { fileURLToPath } from 'node:url';
41
-
42
41
  import { runAsCli } from './lib/cli-utils.js';
42
+ import { walkFilesByExtension } from './lib/fs-walk.js';
43
43
 
44
44
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
45
45
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
@@ -71,28 +71,6 @@ export const KNOWN_AXES = Object.freeze([
71
71
  'planning',
72
72
  ]);
73
73
 
74
- /**
75
- * Walk a directory tree synchronously, yielding absolute paths of files
76
- * matching `.md`. Mirrors the walker shape in `check-lifecycle-lint.js`.
77
- */
78
- function* walkMd(dir) {
79
- let entries;
80
- try {
81
- entries = readdirSync(dir, { withFileTypes: true });
82
- } catch (err) {
83
- if (err.code === 'ENOENT') return;
84
- throw err;
85
- }
86
- for (const entry of entries) {
87
- const p = path.join(dir, entry.name);
88
- if (entry.isDirectory()) {
89
- yield* walkMd(p);
90
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
91
- yield p;
92
- }
93
- }
94
- }
95
-
96
74
  /**
97
75
  * Resolve a mixed list of file/directory paths into the flat set of
98
76
  * markdown files to scan. Missing paths are silently skipped (the
@@ -116,7 +94,7 @@ function* resolveTargets(targets) {
116
94
  for (const entry of stat) {
117
95
  const p = path.join(target, entry.name);
118
96
  if (entry.isDirectory()) {
119
- yield* walkMd(p);
97
+ yield* walkFilesByExtension(p, '.md');
120
98
  } else if (entry.isFile() && entry.name.endsWith('.md')) {
121
99
  yield p;
122
100
  }
@@ -6,10 +6,12 @@
6
6
  *
7
7
  * Polls the PR's required checks to a terminal state and auto-recovers
8
8
  * from `mergeStateStatus: BEHIND` (via bounded `gh pr update-branch`
9
- * calls) by delegating to the shared `watchPrToTerminal` primitive in
10
- * the lifecycle `Watcher` the SAME loop the listener runs, so the CLI
11
- * and the bus path are byte-for-byte equivalent. No lifecycle bus is
12
- * created; this is a direct, synchronous watch with a real exit code.
9
+ * calls) by delegating to the `watchPrToTerminal` primitive in
10
+ * `lib/orchestration/pr-watch.js`. That primitive was shared with the
11
+ * `Watcher` bus listener until Story #5006 deleted it (nothing emitted at
12
+ * it); this CLI is now its only caller. Story #5024 retired the bus
13
+ * outright, so there is no bus to create — this is a direct, synchronous
14
+ * watch with a real exit code.
13
15
  *
14
16
  * Slow-vs-failed semantics (Story #4358):
15
17
  * - GREEN — every required check terminal + green → exit 0, unless the
@@ -100,7 +102,7 @@ import {
100
102
  retireCiDigest,
101
103
  writeCiDigest,
102
104
  } from './lib/orchestration/ci-rerun-guard.js';
103
- import { watchPrToTerminal } from './lib/orchestration/lifecycle/listeners/watcher.js';
105
+ import { watchPrToTerminal } from './lib/orchestration/pr-watch.js';
104
106
  import { enableAutoMergeWith } from './lib/orchestration/single-story-close/phases/auto-merge.js';
105
107
  import { sleep as defaultSleep } from './lib/util/poll-loop.js';
106
108
 
@@ -5,8 +5,8 @@
5
5
  * single `GitHubProvider`, shared by dispatcher, reconciler, and cascade.
6
6
  * The outer TTL wrapper is deliberately omitted because `peekFresh` already
7
7
  * bounds entries by a caller-supplied `maxAgeMs`, and every other reader
8
- * trusts the ticket mutators (`updateTicket` / `postComment` /
9
- * `addSubIssue` / `removeSubIssue`) to call `invalidate` explicitly.
8
+ * trusts the ticket mutators (`updateTicket` / `postComment`) to call
9
+ * `invalidate` explicitly.
10
10
  *
11
11
  * Surface is intentionally narrow: only the methods the provider itself
12
12
  * reaches for live here (`has` / `peek` / `peekFresh` / `set` /
@@ -7,20 +7,16 @@
7
7
  * the upstream `upsertStructuredComment` ticketing helper before the body
8
8
  * lands here.
9
9
  *
10
- * Extracted from `../github.js` in Story #2462 / Task #2480. Public
11
- * surface on `GitHubProvider` is unchanged — `postComment`,
12
- * `getRecentComments`, `getTicketComments`, and `deleteComment` all
13
- * delegate here.
10
+ * Extracted from `../github.js` in Story #2462 / Task #2480. `postComment`,
11
+ * `getTicketComments`, and `deleteComment` on `GitHubProvider` all delegate
12
+ * here. Story #5008 dropped the repo-wide `getRecentComments` feed — the
13
+ * telemetry dashboard that read it was retired and nothing else polls a
14
+ * cross-issue comment stream.
14
15
  *
15
16
  * @see Story #2462 — Split GitHubProvider god class into seven composed gateways.
16
17
  */
17
18
 
18
- import { withTransientRetry } from './errors.js';
19
- import {
20
- defaultRetryWarn,
21
- paginateRest,
22
- parseApiJson,
23
- } from './request-helpers.js';
19
+ import { paginateRest, parseApiJson } from './request-helpers.js';
24
20
 
25
21
  // Structured-comment badge — preserved verbatim from the legacy
26
22
  // `./github/comments.js`. The upstream `upsertStructuredComment` ticketing
@@ -51,24 +47,6 @@ export class CommentGateway {
51
47
  this._hooks = hooks;
52
48
  }
53
49
 
54
- /**
55
- * Recent comments across all issues in the repo (sorted newest first).
56
- *
57
- * @field-manifest /repos/{owner}/{repo}/issues/comments?sort=created:
58
- * id, body, created_at, user, issue_url
59
- */
60
- async getRecentComments(limit = 100) {
61
- const result = await withTransientRetry(
62
- () =>
63
- this._gh.api({
64
- method: 'GET',
65
- endpoint: `/repos/${this.owner}/${this.repo}/issues/comments?sort=created&direction=desc&per_page=${limit}`,
66
- }),
67
- { label: 'getRecentComments', onRetry: defaultRetryWarn },
68
- );
69
- return parseApiJson(result) ?? [];
70
- }
71
-
72
50
  /**
73
51
  * All comments on a single ticket. Used by the upstream
74
52
  * `findStructuredComment` ticketing helper, which greps each comment
@@ -23,7 +23,6 @@ import { LabelGateway } from './labels.js';
23
23
  import { MergeMethodsGateway } from './merge-methods.js';
24
24
  import { ProjectBoardGateway } from './project-board.js';
25
25
  import * as projects from './projects-v2-graphql.js';
26
- import { PullRequestGateway } from './prs.js';
27
26
  import { SubIssueGateway } from './sub-issues.js';
28
27
  import { TicketGateway } from './tickets.js';
29
28
 
@@ -50,12 +49,6 @@ export function composeGateways(provider) {
50
49
  ghGraphql: (q, v, o) => p.graphql(q, v, o),
51
50
  cache: p._cache,
52
51
  classifyGithubError,
53
- hooks: {
54
- getTicket: (id, o) => p.getTicket(id, o),
55
- getTickets: (id) => p.getTickets(id),
56
- primeTicketCache: (t) => p.primeTicketCache(t),
57
- invalidateTicket: (id) => p.invalidateTicket(id),
58
- },
59
52
  });
60
53
  p.comments = new CommentGateway({
61
54
  ...ghDeps,
@@ -64,14 +57,6 @@ export function composeGateways(provider) {
64
57
  p.labels = new LabelGateway(ghDeps);
65
58
  p.branchProtection = new BranchProtectionGateway(ghDeps);
66
59
  p.mergeMethods = new MergeMethodsGateway(ghDeps);
67
- p.pullRequests = new PullRequestGateway({
68
- gh: p._gh,
69
- hooks: {
70
- getTicket: (id) => p.getTicket(id),
71
- addItemToProject,
72
- getProjectNumber,
73
- },
74
- });
75
60
  p.issues = new IssuesGateway({
76
61
  ...ghDeps,
77
62
  hooks: {
@@ -1,16 +1,16 @@
1
1
  /**
2
- * GitHub Provider — error classifier + sub-issues GraphQL shapes.
2
+ * GitHub Provider — error classifier + sub-issues GraphQL shape.
3
3
  *
4
4
  * `classifyGithubError` buckets `gh-exec`-thrown errors into 4 categories
5
5
  * (`feature-disabled` / `permission` / `transient` / `permanent`) so the
6
- * sub-issues fallback and the addSubIssue retry loop have a deterministic
6
+ * sub-issues fallback and the shared retry loop have a deterministic
7
7
  * switch. Rate-limit detection wins over the 401/403 → permission rule
8
8
  * because GitHub's secondary rate limit is delivered as HTTP 403 with a
9
9
  * known message; if we bucketed it as 'permission' it would never be
10
10
  * retried.
11
11
  *
12
- * `SUB_ISSUES_QUERY` / `ADD_SUB_ISSUE_MUTATION` / `REMOVE_SUB_ISSUE_MUTATION`
13
- * are the three GraphQL shapes the sub-issues feature reads/writes.
12
+ * `SUB_ISSUES_QUERY` is the GraphQL shape the sub-issues read path uses.
13
+ * Story #5008 removed the add/remove mutations with the write surface.
14
14
  *
15
15
  * Extracted from `../github.js` in Story #1846 / Task #1857.
16
16
  */
@@ -153,11 +153,11 @@ export function classifyGithubError(err) {
153
153
  // transient network/connectivity errors — so every former consumer of either
154
154
  // module keeps (or gains) its prior retry coverage with no shim.
155
155
  //
156
- // Mirrors the addSubIssue retry contract in `sub-issues.js` so read-path
157
- // callers (paginateRest, getTicket, getNativeSubIssues, …) absorb the same
158
- // jittered exponential backoff on transient GitHub errors instead of
159
- // bubbling a one-shot 502/429/ECONNRESET that kills a longer pipeline
160
- // (e.g. the /deliver Phase E retro). The network consumers repointed here
156
+ // Read-path callers (paginateRest, getTicket, getNativeSubIssues, …) all
157
+ // absorb the same jittered exponential backoff on transient GitHub errors
158
+ // through this one primitive, instead of bubbling a one-shot
159
+ // 502/429/ECONNRESET that kills a longer pipeline (e.g. the /deliver
160
+ // Phase E retro). The network consumers repointed here
161
161
  // (branch-protection, labels, projects-v2-graphql) call with no opts, so
162
162
  // they adopt these defaults; their retry *classes* (the network blips) are
163
163
  // preserved via the unified classifier above.
@@ -174,8 +174,7 @@ export const TRANSIENT_RETRY_DEFAULTS = Object.freeze({
174
174
  * `classify` (defaults to `classifyGithubError`) decides whether each
175
175
  * failure is retry-eligible: only `'transient'` retries; every other
176
176
  * bucket (`feature-disabled` / `permission` / `permanent`) bubbles on the
177
- * first failure. The retry shape matches `addSubIssue` in
178
- * `sub-issues.js:119-167`.
177
+ * first failure.
179
178
  *
180
179
  * `sleep` and `random` are injectable so tests can drive deterministic
181
180
  * retry paths without real-world timing.
@@ -256,19 +255,3 @@ export const SUB_ISSUES_QUERY = `query($id: ID!, $cursor: String) {
256
255
  }
257
256
  }
258
257
  }`;
259
-
260
- export const ADD_SUB_ISSUE_MUTATION = `
261
- mutation($parentId: ID!, $subIssueId: ID!, $replaceParent: Boolean) {
262
- addSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId, replaceParent: $replaceParent }) {
263
- issue { number }
264
- subIssue { number }
265
- }
266
- }`;
267
-
268
- export const REMOVE_SUB_ISSUE_MUTATION = `
269
- mutation($parentId: ID!, $subIssueId: ID!) {
270
- removeSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {
271
- issue { number }
272
- subIssue { number }
273
- }
274
- }`;
@@ -9,8 +9,7 @@
9
9
  * 1. **Transient retry** — each underlying `gh.api({...})` call inside
10
10
  * `paginateRest` is wrapped in `withTransientRetry` (from `./errors.js`),
11
11
  * so a single 502 / 429 / ECONNRESET on page N of M does not lose
12
- * pages 1..N-1 of work. The retry shape matches the existing
13
- * `addSubIssue` mutation retry contract in `sub-issues.js`.
12
+ * pages 1..N-1 of work.
14
13
  * 2. **Page cap** — `paginateRest` enforces a hard ceiling (default
15
14
  * `DEFAULT_PAGE_CAP = 50` → 5000 items at `per_page=100`). Exceeding
16
15
  * the cap throws a clear error naming the endpoint, cap, and items