mandrel 2.0.0 → 2.2.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 (323) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +20 -9
  3. package/.agents/agents/story-worker.md +45 -48
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +60 -46
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +33 -57
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +17 -19
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/gherkin-standards.md +10 -0
  14. package/.agents/rules/git-conventions-reference.md +42 -51
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
  16. package/.agents/schemas/agentrc.schema.json +35 -46
  17. package/.agents/schemas/audit-rules.json +59 -1
  18. package/.agents/schemas/audit-rules.schema.json +33 -1
  19. package/.agents/schemas/lifecycle/README.md +1 -2
  20. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  21. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  22. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  23. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  24. package/.agents/schemas/signal-event.schema.json +3 -3
  25. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  26. package/.agents/schemas/validation-evidence.schema.json +1 -1
  27. package/.agents/scripts/acceptance-eval.js +24 -68
  28. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  29. package/.agents/scripts/bootstrap.js +3 -3
  30. package/.agents/scripts/check-dead-exports.js +43 -104
  31. package/.agents/scripts/check-doc-links.js +2 -2
  32. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  33. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  34. package/.agents/scripts/deliver-recover.js +122 -0
  35. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  36. package/.agents/scripts/evidence-gate.js +20 -50
  37. package/.agents/scripts/generate-skills-index.js +17 -1
  38. package/.agents/scripts/generate-workflows-doc.js +4 -4
  39. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  40. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  41. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  42. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  43. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  44. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  45. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  46. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  47. package/.agents/scripts/lib/checks/index.js +1 -1
  48. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  49. package/.agents/scripts/lib/checks/state.js +17 -248
  50. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  51. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  52. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  53. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  54. package/.agents/scripts/lib/cli-args.js +23 -2
  55. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  56. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  57. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  58. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  59. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  60. package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
  61. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  62. package/.agents/scripts/lib/config/explain.js +10 -16
  63. package/.agents/scripts/lib/config/github.js +7 -5
  64. package/.agents/scripts/lib/config/limits.js +15 -25
  65. package/.agents/scripts/lib/config/quality.js +11 -14
  66. package/.agents/scripts/lib/config/runners.js +8 -21
  67. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  68. package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
  69. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  70. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  71. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  72. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  73. package/.agents/scripts/lib/duplicate-search.js +38 -7
  74. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  75. package/.agents/scripts/lib/format-generated-json.js +97 -0
  76. package/.agents/scripts/lib/framework-version.js +19 -189
  77. package/.agents/scripts/lib/gh-exec.js +8 -0
  78. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  79. package/.agents/scripts/lib/git-utils.js +0 -14
  80. package/.agents/scripts/lib/json-utils.js +1 -2
  81. package/.agents/scripts/lib/label-constants.js +0 -15
  82. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  83. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  84. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  85. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  86. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  87. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  88. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  89. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  91. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  92. package/.agents/scripts/lib/orchestration/change-set.js +103 -0
  93. package/.agents/scripts/lib/orchestration/code-review.js +70 -191
  94. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  95. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  96. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  97. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  98. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  99. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  100. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  101. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  102. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  103. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  105. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  106. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  107. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  108. package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
  109. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
  110. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
  111. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  112. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  114. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  115. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
  116. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  117. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  118. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  119. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
  120. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  121. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
  122. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  123. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  124. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  125. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  126. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  127. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  128. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  129. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  130. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  131. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  132. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  139. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  140. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  141. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  142. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  143. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  144. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  145. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  146. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  147. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
  148. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
  149. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
  150. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  151. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  152. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  153. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  154. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  155. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  156. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  157. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  158. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  159. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  160. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  161. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  162. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  163. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  164. package/.agents/scripts/lib/planning-corpus.js +12 -286
  165. package/.agents/scripts/lib/preflight-runner.js +2 -2
  166. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  167. package/.agents/scripts/lib/signals/index.js +4 -17
  168. package/.agents/scripts/lib/signals/read.js +35 -35
  169. package/.agents/scripts/lib/signals/schema.js +8 -11
  170. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  171. package/.agents/scripts/lib/signals/write.js +0 -1
  172. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  173. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  174. package/.agents/scripts/lib/story-adjacency.js +8 -7
  175. package/.agents/scripts/lib/story-body/story-body.js +81 -13
  176. package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
  177. package/.agents/scripts/lib/test-env.js +14 -1
  178. package/.agents/scripts/lib/test-tiers.js +0 -3
  179. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  180. package/.agents/scripts/lib/validation-evidence.js +31 -59
  181. package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
  182. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  183. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  184. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  185. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  186. package/.agents/scripts/plan-context.js +38 -7
  187. package/.agents/scripts/plan-critics.js +203 -0
  188. package/.agents/scripts/plan-persist.js +145 -35
  189. package/.agents/scripts/plan-run-epilogue.js +83 -38
  190. package/.agents/scripts/post-structured-comment.js +0 -38
  191. package/.agents/scripts/pr-watch-with-update.js +43 -22
  192. package/.agents/scripts/providers/github/compose.js +0 -1
  193. package/.agents/scripts/providers/github/errors.js +0 -19
  194. package/.agents/scripts/providers/github/issues.js +1 -11
  195. package/.agents/scripts/providers/github/mappers.js +5 -0
  196. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  197. package/.agents/scripts/providers/github/tickets.js +33 -153
  198. package/.agents/scripts/providers/github.js +17 -6
  199. package/.agents/scripts/quality-preview.js +13 -6
  200. package/.agents/scripts/resolve-stories.js +236 -0
  201. package/.agents/scripts/run-coverage.js +4 -1
  202. package/.agents/scripts/run-lint.js +2 -2
  203. package/.agents/scripts/run-verify.js +31 -2
  204. package/.agents/scripts/signals-view.js +9 -10
  205. package/.agents/scripts/single-story-close.js +173 -18
  206. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  207. package/.agents/scripts/single-story-init.js +6 -10
  208. package/.agents/scripts/stories-wave-tick.js +380 -53
  209. package/.agents/scripts/story-plan.js +3 -3
  210. package/.agents/scripts/update-ticket-state.js +8 -50
  211. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  212. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  213. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  214. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  215. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  216. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  217. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  218. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  219. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  220. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  221. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  222. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  223. package/.agents/skills/skills.index.json +2 -12
  224. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  225. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  226. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  227. package/.agents/workflows/audit-architecture.md +3 -4
  228. package/.agents/workflows/audit-clean-code.md +4 -4
  229. package/.agents/workflows/audit-documentation.md +4 -5
  230. package/.agents/workflows/audit-lighthouse.md +8 -0
  231. package/.agents/workflows/audit-navigability.md +10 -0
  232. package/.agents/workflows/audit-performance.md +2 -3
  233. package/.agents/workflows/audit-quality.md +8 -9
  234. package/.agents/workflows/audit-security.md +1 -2
  235. package/.agents/workflows/audit-seo.md +10 -0
  236. package/.agents/workflows/audit-ux-ui.md +7 -0
  237. package/.agents/workflows/deliver.md +133 -45
  238. package/.agents/workflows/git-cleanup.md +2 -2
  239. package/.agents/workflows/git-deliver.md +1 -1
  240. package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
  241. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
  242. package/.agents/workflows/helpers/code-review.md +14 -12
  243. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  244. package/.agents/workflows/helpers/deliver-story.md +209 -118
  245. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  246. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  247. package/.agents/workflows/plan.md +239 -19
  248. package/.agents/workflows/qa-assist.md +6 -6
  249. package/.agents/workflows/qa-explore.md +3 -3
  250. package/.agents/workflows/qa-run.md +1 -5
  251. package/bin/mandrel.js +12 -1
  252. package/docs/CHANGELOG.md +62 -0
  253. package/lib/cli/registry.js +262 -19
  254. package/lib/cli/sync-agents.js +157 -0
  255. package/lib/cli/sync-commands.js +115 -6
  256. package/lib/cli/sync.js +168 -6
  257. package/lib/cli/update.js +105 -8
  258. package/lib/cli/version-helpers.js +131 -0
  259. package/lib/migrations/README.md +7 -5
  260. package/lib/migrations/index.js +17 -9
  261. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  262. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  263. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
  264. package/package.json +2 -2
  265. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  266. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  268. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  270. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  271. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  273. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  274. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  275. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  277. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  278. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  279. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  280. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  281. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  282. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  283. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  284. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  285. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  286. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  287. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  288. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  289. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  290. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  291. package/.agents/schemas/risk-verdict.schema.json +0 -53
  292. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  293. package/.agents/scripts/analyze-execution.js +0 -444
  294. package/.agents/scripts/check-prepush-recovery.js +0 -90
  295. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  296. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  297. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  298. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  299. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  300. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  301. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  302. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  303. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  304. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  305. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  306. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  307. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  308. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  309. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  310. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  311. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  312. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  313. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  314. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  315. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  316. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  317. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  318. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  319. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  320. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  321. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  322. package/.agents/scripts/resolve-plan-run.js +0 -117
  323. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -22,10 +22,6 @@ import fs from 'node:fs/promises';
22
22
  import { parseArgs } from 'node:util';
23
23
  import { runAsCli } from './lib/cli-utils.js';
24
24
  import { resolveConfig } from './lib/config-resolver.js';
25
- import {
26
- outboxPathFor,
27
- postCommentOrBuffer,
28
- } from './lib/orchestration/bookkeeping-outbox.js';
29
25
  import {
30
26
  assertValidStructuredCommentType,
31
27
  upsertStructuredComment,
@@ -41,11 +37,6 @@ Flags:
41
37
  retro, epic-run-state, wave-0-start) (required).
42
38
  --body-file Path to a file containing the markdown body (required).
43
39
  --provider Provider name (default: inferred from .agentrc.json github block).
44
- --buffer Headless mode: buffer this upsert to the per-Epic bookkeeping
45
- outbox instead of posting live. Requires --epic. Drained to
46
- GitHub once at finalize by bookkeeping-reconcile.js (Epic
47
- #4476). Omit for attended runs (posts live, unchanged).
48
- --epic <id> Epic id owning the outbox (required with --buffer).
49
40
  --help Show this message.
50
41
  `;
51
42
 
@@ -73,8 +64,6 @@ export function parseArgv(argv) {
73
64
  marker: { type: 'string' },
74
65
  'body-file': { type: 'string' },
75
66
  provider: { type: 'string' },
76
- buffer: { type: 'boolean' },
77
- epic: { type: 'string' },
78
67
  help: { type: 'boolean' },
79
68
  },
80
69
  strict: false,
@@ -126,33 +115,6 @@ export async function main(argv = process.argv.slice(2)) {
126
115
  : config;
127
116
  const provider = createProvider(effectiveConfig);
128
117
 
129
- // Headless buffering (Epic #4476 M5): route the upsert to the per-Epic
130
- // outbox instead of a live GitHub round-trip. Requires --epic to locate the
131
- // outbox; finalize's bookkeeping-reconcile.js drains it once. Attended runs
132
- // (no --buffer) are unchanged.
133
- if (values.buffer) {
134
- const epicId = Number.parseInt(values.epic ?? '', 10);
135
- if (!Number.isInteger(epicId) || epicId <= 0) {
136
- process.stderr.write(
137
- '[post-structured-comment] --buffer requires --epic <id>.\n',
138
- );
139
- process.exit(2);
140
- }
141
- assertValidStructuredCommentType(values.marker);
142
- const { buffered } = await postCommentOrBuffer({
143
- provider,
144
- ticketId,
145
- marker: values.marker,
146
- body,
147
- headless: true,
148
- outboxPath: outboxPathFor(epicId, config),
149
- });
150
- process.stdout.write(
151
- `${JSON.stringify({ success: true, ticketId, type: values.marker, buffered })}\n`,
152
- );
153
- return;
154
- }
155
-
156
118
  const envelope = await runPostStructuredComment({
157
119
  ticketId,
158
120
  type: values.marker,
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * pr-watch-with-update.js — the single CI-watch mechanism for BOTH the
4
- * Epic Phase 8 path (`deliver-epic.md`) and the standalone single-Story
5
- * Step 4 path (`single-story-deliver.md`). Story #4358 retired the bare
6
- * `gh pr checks --watch` from the single-Story path so both drive this
7
- * one CLI.
3
+ * pr-watch-with-update.js — the single CI-watch mechanism for the Story
4
+ * delivery path (`helpers/deliver-story.md` Step 4). Story #4358 retired
5
+ * the bare `gh pr checks --watch` so every caller drives this one CLI.
8
6
  *
9
7
  * Polls the PR's required checks to a terminal state and auto-recovers
10
8
  * from `mergeStateStatus: BEHIND` (via bounded `gh pr update-branch`
@@ -17,10 +15,13 @@
17
15
  * - GREEN — every required check terminal + green → exit 0.
18
16
  * - RED — one or more required checks genuinely failed → exit 1
19
17
  * IMMEDIATELY, consuming no resume budget. On red the CLI
20
- * writes `temp/epic-<id>-ci-digest.{json,md}` (failing check
18
+ * writes `temp/story-<id>-ci-digest.{json,md}` (failing check
21
19
  * name, run id, a `gh run view --log-failed` tail, and a
22
20
  * coarse classification) and prints the red-green
23
- * remediation handoff.
21
+ * remediation handoff. The digest is scoped by filename, so
22
+ * it requires `--story` (Story #4539: the digest was
23
+ * Epic-scoped and therefore never written on the only
24
+ * delivery path v2 has).
24
25
  * - STILL-RUNNING — the poll cap fired with checks still pending and
25
26
  * none failed; the watcher re-armed up to
26
27
  * `delivery.ci.watch.maxResumes` times, then returned a
@@ -35,8 +36,8 @@
35
36
  * CLI flags override config; config overrides the framework fallback.
36
37
  *
37
38
  * Usage:
38
- * node .agents/scripts/pr-watch-with-update.js --pr <n> [--repo owner/repo]
39
- * [--epic <id>] [--max-updates N] [--poll-interval-ms MS]
39
+ * node .agents/scripts/pr-watch-with-update.js --pr <n> --story <id>
40
+ * [--repo owner/repo] [--max-updates N] [--poll-interval-ms MS]
40
41
  * [--max-polls N] [--max-resumes N]
41
42
  */
42
43
  import { spawnSync } from 'node:child_process';
@@ -161,13 +162,33 @@ function resolveRunId({ prRef, checkName, cwd, spawnFn = spawnSync }) {
161
162
  }
162
163
  }
163
164
 
165
+ /**
166
+ * Resolve which ticket the digest is keyed to. Story #4539: the digest used
167
+ * to be Epic-scoped by filename and returned `null` without an epic id — so
168
+ * on the v2 Story path (which has no Epic and invokes the watch with `--pr`
169
+ * alone) a red check wrote no digest at all, despite the module header
170
+ * advertising one. v2.0.0 removed the Epic tier; Story scope is the only
171
+ * scope.
172
+ *
173
+ * @param {{ storyId?: number|string|null }} opts
174
+ * @returns {{ kind: 'story', id: number } | null}
175
+ */
176
+ export function resolveDigestScope({ storyId = null } = {}) {
177
+ if (storyId == null || String(storyId).length === 0) return null;
178
+ const parsed = Number.parseInt(String(storyId), 10);
179
+ return Number.isInteger(parsed) && parsed > 0
180
+ ? { kind: 'story', id: parsed }
181
+ : null;
182
+ }
183
+
164
184
  /**
165
185
  * Write the CI failure digest (`.json` + `.md`) for a red watch. Returns
166
- * the two paths written (or `null` when no epic id was supplied the
167
- * digest is Epic-scoped by filename). Exported for tests.
186
+ * the two paths written, or `null` when no story id was supplied (the
187
+ * digest is scoped by filename and has nothing to key on). Exported for
188
+ * tests.
168
189
  *
169
190
  * @param {object} opts
170
- * @param {number|string|null} opts.epicId
191
+ * @param {number|string|null} [opts.storyId] The v2 delivery scope.
171
192
  * @param {number} opts.prNumber
172
193
  * @param {Array<{name:string, outcome:string}>} opts.failures
173
194
  * @param {string} opts.tempRoot
@@ -178,7 +199,7 @@ function resolveRunId({ prRef, checkName, cwd, spawnFn = spawnSync }) {
178
199
  * @returns {{ jsonPath: string, mdPath: string } | null}
179
200
  */
180
201
  export function writeCiDigest({
181
- epicId,
202
+ storyId = null,
182
203
  prNumber,
183
204
  failures,
184
205
  tempRoot,
@@ -187,13 +208,14 @@ export function writeCiDigest({
187
208
  runIdFn = resolveRunId,
188
209
  logTailFn = ghRunLogTail,
189
210
  }) {
190
- if (epicId == null || String(epicId).length === 0) return null;
211
+ const scope = resolveDigestScope({ storyId });
212
+ if (!scope) return null;
191
213
  const primary = failures[0] ?? { name: 'unknown', outcome: 'failure' };
192
214
  const runId = runIdFn({ prRef, checkName: primary.name, cwd });
193
215
  const logTail = logTailFn({ runId, cwd });
194
216
  const classification = classifyFailure(primary.name);
195
217
  const digest = {
196
- epicId: Number.parseInt(String(epicId), 10),
218
+ storyId: scope.id,
197
219
  prNumber,
198
220
  failingCheck: primary.name,
199
221
  failingOutcome: primary.outcome,
@@ -205,12 +227,12 @@ export function writeCiDigest({
205
227
  };
206
228
  const dir = path.isAbsolute(tempRoot) ? tempRoot : path.join(cwd, tempRoot);
207
229
  mkdirSync(dir, { recursive: true });
208
- const base = `epic-${digest.epicId}-ci-digest`;
230
+ const base = `${scope.kind}-${scope.id}-ci-digest`;
209
231
  const jsonPath = path.join(dir, `${base}.json`);
210
232
  const mdPath = path.join(dir, `${base}.md`);
211
233
  writeFileSync(jsonPath, `${JSON.stringify(digest, null, 2)}\n`);
212
234
  const md = [
213
- `# CI failure digest — Epic #${digest.epicId} (PR #${prNumber})`,
235
+ `# CI failure digest — Story #${scope.id} (PR #${prNumber})`,
214
236
  '',
215
237
  `- **Failing check:** \`${digest.failingCheck}\` (${digest.failingOutcome})`,
216
238
  `- **Run id:** ${runId ?? 'unresolved'}`,
@@ -247,7 +269,6 @@ export function writeCiDigest({
247
269
  * @param {object} opts
248
270
  * @param {number} opts.prNumber
249
271
  * @param {string|null} [opts.repo]
250
- * @param {number|string|null} [opts.epicId] Epic id for the red-path digest.
251
272
  * @param {number|string} [opts.maxUpdates]
252
273
  * @param {number|string} [opts.pollIntervalMs]
253
274
  * @param {number|string} [opts.maxPolls]
@@ -266,7 +287,7 @@ export function writeCiDigest({
266
287
  export async function runPrWatch({
267
288
  prNumber,
268
289
  repo = null,
269
- epicId = null,
290
+ storyId = null,
270
291
  maxUpdates,
271
292
  pollIntervalMs,
272
293
  maxPolls,
@@ -377,7 +398,7 @@ export async function runPrWatch({
377
398
  let digestPaths = null;
378
399
  try {
379
400
  digestPaths = writeDigestFn({
380
- epicId,
401
+ storyId,
381
402
  prNumber,
382
403
  failures,
383
404
  tempRoot: effectiveTempRoot,
@@ -415,7 +436,7 @@ async function main() {
415
436
  options: {
416
437
  pr: { type: 'string' },
417
438
  repo: { type: 'string' },
418
- epic: { type: 'string' },
439
+ story: { type: 'string' },
419
440
  'max-updates': { type: 'string' },
420
441
  'poll-interval-ms': { type: 'string' },
421
442
  'max-polls': { type: 'string' },
@@ -426,7 +447,7 @@ async function main() {
426
447
  return runPrWatch({
427
448
  prNumber: Number.parseInt(values.pr ?? '', 10),
428
449
  repo: values.repo ?? null,
429
- epicId: values.epic ?? null,
450
+ storyId: values.story ?? null,
430
451
  maxUpdates: values['max-updates'],
431
452
  pollIntervalMs: values['poll-interval-ms'],
432
453
  maxPolls: values['max-polls'],
@@ -42,7 +42,6 @@ export function composeGateways(provider) {
42
42
  ...ghDeps,
43
43
  cache: p._cache,
44
44
  hooks: {
45
- addSubIssue: (a, b) => p.addSubIssue(a, b),
46
45
  addItemToProject,
47
46
  getProjectNumber,
48
47
  },
@@ -272,22 +272,3 @@ export const REMOVE_SUB_ISSUE_MUTATION = `
272
272
  subIssue { number }
273
273
  }
274
274
  }`;
275
-
276
- // Story #2982 — native parent lookup for the cascade upward walk. The
277
- // inverse of `SUB_ISSUES_QUERY`: given a child Issue node, fetch the
278
- // `parent` field exposed by the Sub-Issues feature so `cascadeCompletion`
279
- // can resolve a parent even when the child body lacks the `parent: #N`
280
- // orchestrator footer.
281
- export const PARENT_ISSUE_QUERY = `query($id: ID!) {
282
- node(id: $id) {
283
- ... on Issue {
284
- parent {
285
- number
286
- databaseId
287
- id
288
- title
289
- state
290
- }
291
- }
292
- }
293
- }`;
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Owns the remaining read-side surface that did not belong to any of the
5
5
  * earlier six gateways: the raw GraphQL shim (`graphql` / `_ghGraphql`),
6
- * epic enumeration (`getEpics`, `getEpic`), repository-wide label scans
6
+ * epic reads (`getEpic`), repository-wide label scans
7
7
  * (`listIssuesByLabel`), branch existence probes (`branchExists`), and the
8
8
  * three-strategy sub-ticket aggregator (`getSubTickets`).
9
9
  *
@@ -151,16 +151,6 @@ export class IssuesGateway {
151
151
  }));
152
152
  }
153
153
 
154
- /**
155
- * Historical Epic enumeration surface. Stage 5 removed the Epic type from
156
- * the taxonomy; callers that have not yet collapsed their planning corpus
157
- * reads receive an empty list rather than querying a deleted label.
158
- */
159
- /* node:coverage ignore next */
160
- async getEpics(_filters = {}) {
161
- return [];
162
- }
163
-
164
154
  /**
165
155
  * @field-manifest /repos/{owner}/{repo}/issues/{n}: number, id, node_id,
166
156
  * title, body, labels, state
@@ -35,6 +35,11 @@ export function issueToTicket(issue) {
35
35
  labelSet: new Set(labels),
36
36
  assignees: (issue.assignees ?? []).map((a) => a.login),
37
37
  state: issue.state,
38
+ // `completed` | `not_planned` | `reopened` | null. `state` alone cannot
39
+ // tell a Story that landed from one closed as superseded — both read
40
+ // `closed` — so the close path needs this to avoid reporting a
41
+ // never-merged Story as landed.
42
+ stateReason: issue.state_reason ?? null,
38
43
  };
39
44
  }
40
45
 
@@ -30,7 +30,6 @@ import { concurrentMap } from '../../lib/util/concurrent-map.js';
30
30
  import {
31
31
  ADD_SUB_ISSUE_MUTATION,
32
32
  classifyGithubError as defaultClassifyGithubError,
33
- PARENT_ISSUE_QUERY,
34
33
  REMOVE_SUB_ISSUE_MUTATION,
35
34
  SUB_ISSUES_QUERY,
36
35
  withTransientRetry,
@@ -134,52 +133,6 @@ export class SubIssueGateway {
134
133
  return childIds;
135
134
  }
136
135
 
137
- /**
138
- * Strategy 2 — native GitHub Sub-Issues, inverse direction. Given a
139
- * child Issue node, return the parent's issue number (or `null` when
140
- * the child has no native parent link, or the feature is disabled on
141
- * this repo). Story #2982: used by `cascadeCompletion` as a third
142
- * fallback so a Story whose body lost the `parent: #N` orchestrator
143
- * footer still cascades upward to its true Sub-Issue parent.
144
- *
145
- * @param {string} childNodeId GraphQL node ID of the child Issue.
146
- * @param {number} childNumber Issue number, for log context only.
147
- * @returns {Promise<number|null>}
148
- */
149
- async getNativeParent(childNodeId, childNumber) {
150
- if (!childNodeId) return null;
151
- try {
152
- const data = await withTransientRetry(
153
- () =>
154
- this._ghGraphql(
155
- PARENT_ISSUE_QUERY,
156
- { id: childNodeId },
157
- { headers: { 'GraphQL-Features': 'sub_issues' } },
158
- ),
159
- {
160
- label: `getNativeParent child=#${childNumber}`,
161
- classify: this._classify,
162
- onRetry: defaultRetryWarn,
163
- },
164
- );
165
- const parent = data?.node?.parent;
166
- if (!parent || typeof parent.number !== 'number') return null;
167
- return parent.number;
168
- } catch (err) {
169
- const category = this._classify(err);
170
- if (category === 'feature-disabled') {
171
- Logger.warn(
172
- `[GitHubProvider] sub-issues parent lookup unavailable (child #${childNumber})`,
173
- );
174
- return null;
175
- }
176
- Logger.error(
177
- `[GitHubProvider] native parent lookup failed (child #${childNumber}, category=${category}): ${err.message}`,
178
- );
179
- throw err;
180
- }
181
- }
182
-
183
136
  /**
184
137
  * Establish the native sub-issue link between `parentNumber` and the
185
138
  * child identified by `childNodeId`. Retries on transient errors with
@@ -8,13 +8,16 @@
8
8
  *
9
9
  * The gateway is constructed with a `{ gh, owner, repo, hooks }` object so
10
10
  * cross-gateway concerns can be threaded in without bloating the constructor
11
- * signature. Today the only hooks the ticket surface reaches for are
12
- * `addSubIssue` (from the future SubIssueGateway, used by `createTicket` to
13
- * link a freshly-created child as a native sub-issue) and `addItemToProject`
14
- * (from the projects-v2 shim, used by `createTicket` to add the new issue
15
- * to the configured Project V2).
11
+ * signature. The `addItemToProject` hook (from the projects-v2 shim) adds a
12
+ * newly-created issue to the configured Project V2.
16
13
  *
17
- * Public surface: `GitHubProvider.createTicket / createIssue / getTicket /
14
+ * Story #4545 deleted `createTicket` and its `composeStoryBody` helper — the
15
+ * Epic-hierarchy write surface. It composed the exact `Epic: #N` footer that
16
+ * `pr-base-guard.js` hard-refuses at delivery, so the framework retained the
17
+ * ability to generate work it would then reject; it had no production caller
18
+ * (`/plan` persists through the bare `createIssue` below).
19
+ *
20
+ * Public surface: `GitHubProvider.createIssue / getTicket /
18
21
  * getTickets / updateTicket / getTicketDependencies / primeTicketCache /
19
22
  * invalidateTicket` all delegate to the same-named methods on this class.
20
23
  *
@@ -22,9 +25,7 @@
22
25
  */
23
26
 
24
27
  import { parseBlockedBy, parseBlocks } from '../../lib/dependency-parser.js';
25
- import { stampFrameworkVersion } from '../../lib/framework-version.js';
26
28
  import { Logger } from '../../lib/Logger.js';
27
- import { TYPE_LABELS } from '../../lib/label-constants.js';
28
29
  import { addIssueToBoard } from './board-add.js';
29
30
  import { createInlineTicketCache } from './cache.js';
30
31
  import { withTransientRetry } from './errors.js';
@@ -43,70 +44,6 @@ import {
43
44
  */
44
45
  const SEARCH_PAGE_CAP = 10;
45
46
 
46
- /**
47
- * Compose the final markdown body for a created ticket. Under the 2-tier
48
- * hierarchy (Epic → Story), `body` is always a string supplied
49
- * by the caller (the decomposer, the spec planner, or the reconciler-apply
50
- * engine). This helper appends the canonical orchestrator footer
51
- * (`parent: #<n>` / `Epic: #<m>` / `blocked by #<x>`) byte-stable with the
52
- * format consumers (manifest, close-gate, dispatcher) parse.
53
- *
54
- * Story #3186 — inlined here when the Task-tier body-renderer helpers
55
- * were retired. The structured-body (object) path is gone: Stories carry
56
- * inline `acceptance[]` / `verify[]`
57
- * arrays on the Story body authored by the decomposer; there is no
58
- * server-side rendering of a four-section payload at create time.
59
- *
60
- * Story #4382 — this is also where a Story body is stamped, once, with the
61
- * running Mandrel framework version and authoring date (hidden `mandrel_version`
62
- * / `authored_at` meta field + a visible `> 🏷️ Authored with Mandrel …`
63
- * marker) via {@link stampFrameworkVersion}. The stamp is immutable: a body
64
- * that already carries a version (e.g. a reconciler re-create) is preserved
65
- * verbatim. The `stamp` override exists for deterministic tests; production
66
- * callers omit it so the running version and today's date are used.
67
- *
68
- * @param {{
69
- * body: string,
70
- * parentId: number,
71
- * epicId?: number,
72
- * dependencies?: number[],
73
- * stamp?: { version?: string, authoredAt?: string } | false,
74
- * }} opts
75
- * @returns {string}
76
- *
77
- * Story #3958 — this is the single owner of the `blocked by #N` footer.
78
- * Callers (the reconciler-apply create path, the spec planner) pass
79
- * resolved dependency issue numbers via `dependencies` and supply a body
80
- * WITHOUT any pre-appended footer. A caller that also appends a
81
- * `blocked by` block to `body` would double every dependency line.
82
- */
83
- export function composeStoryBody({
84
- body,
85
- parentId,
86
- epicId,
87
- dependencies = [],
88
- stamp,
89
- }) {
90
- const rawHead = typeof body === 'string' ? body : '';
91
- // `stamp === false` → footer-only recomposition: the caller (the reconciler
92
- // UPDATE/diff path) owns stamp preservation itself and must NOT introduce a
93
- // fresh authoring stamp, which would churn or bump the version on every
94
- // reconcile. Every other call is a create — stamp once (immutably).
95
- const head =
96
- stamp === false ? rawHead : stampFrameworkVersion(rawHead, stamp ?? {});
97
- const lines = ['---', `parent: #${parentId}`];
98
- if (epicId !== undefined && epicId !== null) {
99
- lines.push(`Epic: #${epicId}`);
100
- }
101
- if (dependencies.length > 0) {
102
- lines.push('');
103
- for (const dep of dependencies) {
104
- lines.push(`blocked by #${dep}`);
105
- }
106
- }
107
- return `${head}\n\n${lines.join('\n')}`;
108
- }
109
-
110
47
  export class TicketGateway {
111
48
  /**
112
49
  * @param {{
@@ -114,7 +51,6 @@ export class TicketGateway {
114
51
  * owner: string,
115
52
  * repo: string,
116
53
  * hooks?: {
117
- * addSubIssue?: (parentNumber: number, childNodeId: string) => Promise<unknown>,
118
54
  * addItemToProject?: (nodeId: string) => Promise<unknown>,
119
55
  * getProjectNumber?: () => number|null,
120
56
  * },
@@ -314,81 +250,10 @@ export class TicketGateway {
314
250
  // ---------------------------------------------------------------------------
315
251
 
316
252
  /**
317
- * Create a new issue. Renders the body via the inline `composeStoryBody`
318
- * helper so the `parent: #N` / `Epic: #M` / `blocked by #X` footer is
319
- * consistent across creators.
320
- *
321
- * After the POST, opportunistically link the child as a native sub-issue
322
- * (via the `addSubIssue` hook — retried internally on the sub-issue
323
- * gateway) and add it to the configured Project V2 (best-effort —
324
- * failures warn but do not fail the create).
325
- *
326
- * @field-manifest POST /repos/{owner}/{repo}/issues: number, id, node_id,
327
- * html_url
328
- */
329
- /* node:coverage ignore next */
330
- async createTicket(parentId, ticketData) {
331
- const epicId = ticketData.epicId || parentId;
332
- const renderedBody = composeStoryBody({
333
- body: ticketData.body ?? '',
334
- parentId,
335
- epicId,
336
- dependencies: ticketData.dependencies ?? [],
337
- });
338
-
339
- // Inject TYPE_LABELS.STORY so a spec that omits the labels array cannot
340
- // produce an unlabeled, undispatchable Story. Dedupe to avoid duplicates
341
- // when the caller already carries the label.
342
- const callerLabels = ticketData.labels ?? [];
343
- const labels = callerLabels.includes(TYPE_LABELS.STORY)
344
- ? callerLabels
345
- : [TYPE_LABELS.STORY, ...callerLabels];
346
- const result = await this._gh.api({
347
- method: 'POST',
348
- endpoint: `/repos/${this.owner}/${this.repo}/issues`,
349
- body: {
350
- title: ticketData.title,
351
- body: renderedBody,
352
- labels,
353
- },
354
- });
355
- const issue = parseApiJson(result);
356
- this._listCache.clear();
357
-
358
- let subIssueLinked = false;
359
- let subIssueError = null;
360
- try {
361
- if (typeof this._hooks.addSubIssue === 'function') {
362
- await this._hooks.addSubIssue(parentId, issue.node_id);
363
- subIssueLinked = true;
364
- }
365
- } catch (err) {
366
- subIssueError = err;
367
- }
368
-
369
- await addIssueToBoard({
370
- nodeId: issue.node_id,
371
- issueNumber: issue.number,
372
- getProjectNumber: this._hooks.getProjectNumber,
373
- addItemToProject: this._hooks.addItemToProject,
374
- });
375
-
376
- return {
377
- id: issue.number,
378
- internalId: issue.id,
379
- nodeId: issue.node_id,
380
- url: issue.html_url,
381
- subIssueLinked,
382
- subIssueError,
383
- };
384
- }
385
-
386
- /**
387
- * Create a **bare** issue — no `parent: #N` footer composition and no
388
- * sub-issue link. Serves the standalone create paths that bypass
389
- * `createTicket`'s Story-shaped body rendering: the `/plan`
390
- * persist step and the `/plan` Phase 4 Epic open
391
- * (`openEpicFromOnePager`'s `createIssue` port).
253
+ * Create a **bare** issue no footer composition and no sub-issue link.
254
+ * Since Story #4545 this is the *only* create path: `/plan` persist and the
255
+ * `/plan` Phase 4 Epic open (`openEpicFromOnePager`'s `createIssue` port)
256
+ * both route through it.
392
257
  *
393
258
  * After the POST, the new issue is added to the configured Project V2
394
259
  * board via the shared `addIssueToBoard` helper (Story #3822) —
@@ -396,6 +261,17 @@ export class TicketGateway {
396
261
  * so board membership never depends on GitHub's "Auto-add to project"
397
262
  * built-in workflow.
398
263
  *
264
+ * The POST is wrapped in `withTransientRetry` (Story #4541) so it absorbs
265
+ * the same 502/429/ECONNRESET blips the read surfaces already do. It used
266
+ * to post bare, which made a single transient failure at story *k* of *N*
267
+ * abort `/plan` persist with `1..k-1` already live on the tracker.
268
+ *
269
+ * Retry alone is not sufficient for that failure mode — a POST whose
270
+ * response is lost would double-create on the retry — so the caller
271
+ * (`plan-persist`'s `createStoryIssues`) carries the idempotency half via
272
+ * a plan fingerprint it looks up before creating. Retry narrows the
273
+ * window; the fingerprint closes it.
274
+ *
399
275
  * @field-manifest POST /repos/{owner}/{repo}/issues: number, id, node_id,
400
276
  * html_url
401
277
  *
@@ -410,11 +286,15 @@ export class TicketGateway {
410
286
  * }>}
411
287
  */
412
288
  async createIssue({ title, body, labels = [] }) {
413
- const result = await this._gh.api({
414
- method: 'POST',
415
- endpoint: `/repos/${this.owner}/${this.repo}/issues`,
416
- body: { title, body, labels },
417
- });
289
+ const result = await withTransientRetry(
290
+ () =>
291
+ this._gh.api({
292
+ method: 'POST',
293
+ endpoint: `/repos/${this.owner}/${this.repo}/issues`,
294
+ body: { title, body, labels },
295
+ }),
296
+ { label: `createIssue "${title}"`, onRetry: defaultRetryWarn },
297
+ );
418
298
  const issue = parseApiJson(result);
419
299
  this._listCache.clear();
420
300
 
@@ -80,9 +80,23 @@ export class GitHubProvider extends ITicketingProvider {
80
80
  return this._memoizedToken;
81
81
  }
82
82
 
83
- /* node:coverage ignore next */
84
- async listIssues(filters = {}) {
85
- return this.getEpics(filters);
83
+ /**
84
+ * The identifiers a native dependency-edge writer needs, handed over as an
85
+ * explicit interface (Story #4544).
86
+ *
87
+ * `providers/github/blocked-by-add.js` talks to the dependencies REST API
88
+ * directly, so it needs the `gh` facade plus `owner`/`repo` — none of which
89
+ * the `ITicketingProvider` surface exposes. Its caller lives in the
90
+ * orchestration layer (`plan-persist/story-ops.js`), and the alternative was
91
+ * for that caller to reach through `provider._gh` — a private-by-convention
92
+ * field — from outside this module. Naming the hand-off here keeps the
93
+ * coupling declared and greppable instead of incidental: the provider
94
+ * decides what it lends out, and the field stays private.
95
+ *
96
+ * @returns {{ gh: object, owner: string, repo: string }}
97
+ */
98
+ getDependencyWriteContext() {
99
+ return { gh: this._gh, owner: this.owner, repo: this.repo };
86
100
  }
87
101
 
88
102
  static isInsufficientScopes(err) {
@@ -100,7 +114,6 @@ const DELEGATIONS = [
100
114
  ['graphql', 'issues.ghGraphql'],
101
115
  ['searchIssues', 'issues.searchIssues'],
102
116
  ['listIssuesByLabel', 'issues.listIssuesByLabel'],
103
- ['getEpics', 'issues.getEpics'],
104
117
  ['getEpic', 'issues.getEpic'],
105
118
  ['branchExists', 'issues.branchExists'],
106
119
  ['getSubTickets', 'issues.getSubTickets'],
@@ -108,12 +121,10 @@ const DELEGATIONS = [
108
121
  ['getTickets', 'tickets.getTickets'],
109
122
  ['getTicket', 'tickets.getTicket'],
110
123
  ['getTicketDependencies', 'tickets.getTicketDependencies'],
111
- ['createTicket', 'tickets.createTicket'],
112
124
  ['createIssue', 'tickets.createIssue'],
113
125
  ['updateTicket', 'tickets.updateTicket'],
114
126
  ['_applyLabelMutations', 'tickets._applyLabelMutations'],
115
127
  ['_getNativeSubIssues', 'subIssues.getNativeSubIssues'],
116
- ['_getNativeParent', 'subIssues.getNativeParent'],
117
128
  ['addSubIssue', 'subIssues.addSubIssue'],
118
129
  ['removeSubIssue', 'subIssues.removeSubIssue'],
119
130
  ['reconcileSubIssueLinks', 'subIssues.reconcileSubIssueLinks'],
@@ -35,18 +35,25 @@ import {
35
35
  * present without a value. Returns `null` when the flag is absent so callers
36
36
  * can fall through to the gate scripts' own diff defaults.
37
37
  *
38
+ * **Last occurrence wins** (Story #4603). `npm run <alias> -- --changed-since <base>`
39
+ * appends the operator's flag *after* any flag baked into the npm script, so a
40
+ * first-wins scan silently discarded the operator's base and compared against
41
+ * the script's hardcoded one instead — reporting a false green for a branch the
42
+ * gate had never actually scored. Last-wins matches the convention every
43
+ * mainstream CLI parser follows for repeated scalar flags, and makes the
44
+ * npm-alias passthrough behave the way its callers already assume.
45
+ *
38
46
  * @param {string[]} argv
39
47
  * @returns {string | null}
40
48
  */
41
49
  export function parseChangedSinceArg(argv) {
50
+ let resolved = null;
42
51
  for (let i = 0; i < argv.length; i += 1) {
43
- if (argv[i] === '--changed-since') {
44
- const next = argv[i + 1];
45
- if (next && !next.startsWith('--')) return next;
46
- return 'HEAD';
47
- }
52
+ if (argv[i] !== '--changed-since') continue;
53
+ const next = argv[i + 1];
54
+ resolved = next && !next.startsWith('--') ? next : 'HEAD';
48
55
  }
49
- return null;
56
+ return resolved;
50
57
  }
51
58
 
52
59
  /**