mandrel 1.58.0 → 1.60.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 (319) hide show
  1. package/.agents/README.md +100 -98
  2. package/.agents/docs/SDLC.md +140 -141
  3. package/.agents/docs/configuration.md +16 -16
  4. package/.agents/docs/workflows.md +7 -8
  5. package/.agents/instructions.md +12 -11
  6. package/.agents/personas/architect.md +1 -1
  7. package/.agents/personas/product.md +1 -1
  8. package/.agents/personas/project-manager.md +14 -14
  9. package/.agents/personas/technical-writer.md +1 -1
  10. package/.agents/rules/changelog-style.md +5 -5
  11. package/.agents/rules/git-conventions.md +3 -3
  12. package/.agents/schemas/agentrc.schema.json +3 -3
  13. package/.agents/schemas/audit-rules.json +20 -0
  14. package/.agents/schemas/dispatch-manifest.json +4 -4
  15. package/.agents/schemas/epic-spec.schema.json +15 -45
  16. package/.agents/schemas/lifecycle/README.md +1 -1
  17. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
  18. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
  20. package/.agents/schemas/validation-evidence.schema.json +1 -1
  21. package/.agents/scripts/README.md +1 -1
  22. package/.agents/scripts/acceptance-eval.js +21 -4
  23. package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
  24. package/.agents/scripts/analyze-execution.js +2 -2
  25. package/.agents/scripts/assert-branch.js +1 -3
  26. package/.agents/scripts/audit-to-stories.js +1 -1
  27. package/.agents/scripts/bootstrap.js +1 -1
  28. package/.agents/scripts/check-arch-cycles.js +360 -0
  29. package/.agents/scripts/check-doc-links.js +2 -3
  30. package/.agents/scripts/coverage-capture.js +24 -3
  31. package/.agents/scripts/diagnose-friction.js +1 -1
  32. package/.agents/scripts/dispatcher.js +2 -2
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/epic-audit-prepare.js +3 -3
  35. package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
  36. package/.agents/scripts/epic-deliver-preflight.js +11 -9
  37. package/.agents/scripts/epic-deliver-prepare.js +13 -5
  38. package/.agents/scripts/epic-execute-record-wave.js +5 -5
  39. package/.agents/scripts/epic-plan-healthcheck.js +6 -10
  40. package/.agents/scripts/epic-plan-spec-validate.js +1 -1
  41. package/.agents/scripts/epic-reconcile.js +11 -29
  42. package/.agents/scripts/evidence-gate.js +2 -2
  43. package/.agents/scripts/generate-workflows-doc.js +1 -1
  44. package/.agents/scripts/git-rebase-and-resolve.js +1 -1
  45. package/.agents/scripts/hierarchy-gate.js +40 -24
  46. package/.agents/scripts/lib/ITicketingProvider.js +1 -1
  47. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  48. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
  49. package/.agents/scripts/lib/baseline-snapshot.js +7 -7
  50. package/.agents/scripts/lib/baselines/kinds/coverage.js +33 -149
  51. package/.agents/scripts/lib/baselines/kinds/duplication.js +27 -116
  52. package/.agents/scripts/lib/baselines/kinds/kind-factory.js +192 -0
  53. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +34 -133
  54. package/.agents/scripts/lib/baselines/kinds/maintainability.js +31 -124
  55. package/.agents/scripts/lib/baselines/kinds/mutation.js +25 -111
  56. package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +59 -0
  57. package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +37 -0
  58. package/.agents/scripts/lib/baselines/writer.js +1 -1
  59. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  60. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  61. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
  62. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  63. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
  64. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  65. package/.agents/scripts/lib/close-validation/commands.js +188 -0
  66. package/.agents/scripts/lib/close-validation/gates.js +235 -0
  67. package/.agents/scripts/lib/close-validation/process.js +101 -0
  68. package/.agents/scripts/lib/close-validation/projections/maintainability.js +1 -1
  69. package/.agents/scripts/lib/close-validation/runner.js +325 -0
  70. package/.agents/scripts/lib/close-validation/telemetry.js +70 -0
  71. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  72. package/.agents/scripts/lib/config/explain.js +1 -1
  73. package/.agents/scripts/lib/config/quality.js +6 -6
  74. package/.agents/scripts/lib/config/runners.js +2 -2
  75. package/.agents/scripts/lib/config/runtime.js +1 -1
  76. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  77. package/.agents/scripts/lib/config-resolver.js +2 -5
  78. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  79. package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
  80. package/.agents/scripts/lib/config-settings-schema.js +3 -3
  81. package/.agents/scripts/lib/coverage-capture.js +147 -4
  82. package/.agents/scripts/lib/cpu-pool.js +14 -0
  83. package/.agents/scripts/lib/crap-utils.js +6 -11
  84. package/.agents/scripts/lib/duplicate-search.js +1 -1
  85. package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
  86. package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +87 -0
  87. package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
  88. package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
  89. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
  90. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
  91. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  92. package/.agents/scripts/lib/findings/promote-finding.js +10 -10
  93. package/.agents/scripts/lib/git-utils.js +24 -22
  94. package/.agents/scripts/lib/label-constants.js +3 -4
  95. package/.agents/scripts/lib/label-taxonomy.js +3 -8
  96. package/.agents/scripts/lib/maintainability-engine.js +1 -1
  97. package/.agents/scripts/lib/maintainability-utils.js +4 -187
  98. package/.agents/scripts/lib/observability/perf-report-readers.js +32 -23
  99. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +81 -7
  100. package/.agents/scripts/lib/orchestration/code-review.js +95 -82
  101. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
  102. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
  103. package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
  104. package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
  105. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +14 -37
  106. package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
  107. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +22 -22
  108. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
  109. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
  110. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
  111. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +2 -2
  112. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +206 -58
  113. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +1 -1
  114. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +27 -3
  115. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
  116. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +28 -8
  117. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
  118. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
  119. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
  120. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
  121. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +13 -41
  122. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
  123. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +2 -3
  124. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -8
  125. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
  126. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +103 -0
  127. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +22 -64
  128. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +38 -76
  129. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
  130. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +10 -10
  131. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +8 -20
  132. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
  133. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
  134. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
  135. package/.agents/scripts/lib/orchestration/file-assumptions.js +6 -5
  136. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
  137. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
  138. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
  139. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +144 -0
  140. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
  141. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +3 -3
  142. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
  143. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
  144. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
  145. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
  146. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
  147. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  148. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
  149. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
  150. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +8 -8
  151. package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
  152. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
  153. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
  154. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +3 -3
  155. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
  156. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +7 -7
  157. package/.agents/scripts/lib/orchestration/preflight-cache.js +36 -13
  158. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
  159. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
  160. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
  161. package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
  162. package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
  163. package/.agents/scripts/lib/orchestration/review-providers/codex.js +5 -60
  164. package/.agents/scripts/lib/orchestration/review-providers/native.js +7 -6
  165. package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +105 -0
  166. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +7 -59
  167. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +2 -4
  168. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +1 -1
  169. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
  170. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -4
  171. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +32 -35
  172. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +1 -2
  173. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  174. package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
  175. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  176. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +451 -503
  177. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +8 -2
  178. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +47 -2
  179. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +2 -2
  180. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
  181. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +358 -54
  182. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +1 -1
  183. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +3 -2
  184. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +32 -5
  185. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +5 -18
  186. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +3 -3
  187. package/.agents/scripts/lib/orchestration/story-close-recovery.js +33 -16
  188. package/.agents/scripts/lib/orchestration/story-reachability.js +47 -0
  189. package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
  190. package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
  191. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +4 -35
  192. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
  193. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
  194. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +44 -73
  195. package/.agents/scripts/lib/orchestration/ticketing/reads.js +16 -7
  196. package/.agents/scripts/lib/orchestration/ticketing/state.js +53 -439
  197. package/.agents/scripts/lib/orchestration/ticketing/transition.js +471 -0
  198. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  199. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +3 -3
  200. package/.agents/scripts/lib/orchestration/wave-record-projection.js +2 -8
  201. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  202. package/.agents/scripts/lib/preflight-runner.js +1 -1
  203. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
  204. package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
  205. package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
  206. package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
  207. package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
  208. package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
  209. package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
  210. package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
  211. package/.agents/scripts/lib/project-root.js +17 -0
  212. package/.agents/scripts/lib/signals/schema.js +1 -1
  213. package/.agents/scripts/lib/spec/index.js +1 -1
  214. package/.agents/scripts/lib/spec/loader.js +2 -2
  215. package/.agents/scripts/lib/spec/state.js +7 -16
  216. package/.agents/scripts/lib/story-adjacency.js +76 -0
  217. package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
  218. package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
  219. package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
  220. package/.agents/scripts/lib/story-lifecycle.js +9 -9
  221. package/.agents/scripts/lib/story-plan.js +1 -1
  222. package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
  223. package/.agents/scripts/lib/transpile.js +93 -0
  224. package/.agents/scripts/lib/wave-runner/tick.js +4 -153
  225. package/.agents/scripts/lib/workers/crap-worker.js +1 -1
  226. package/.agents/scripts/lib/workers/maintainability-report-worker.js +1 -1
  227. package/.agents/scripts/lib/worktree/lifecycle/creation.js +20 -2
  228. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +90 -0
  229. package/.agents/scripts/lib/worktree/lifecycle/reap.js +26 -8
  230. package/.agents/scripts/lib/worktree/node-modules-strategy.js +74 -0
  231. package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
  232. package/.agents/scripts/lifecycle-emit.js +1 -1
  233. package/.agents/scripts/providers/github/board-add.js +1 -1
  234. package/.agents/scripts/providers/github/errors.js +1 -1
  235. package/.agents/scripts/providers/github/mappers.js +2 -2
  236. package/.agents/scripts/providers/github/tickets.js +114 -10
  237. package/.agents/scripts/resync-status-column.js +1 -1
  238. package/.agents/scripts/retro-run.js +2 -2
  239. package/.agents/scripts/run-lint.js +10 -1
  240. package/.agents/scripts/run-tests.js +24 -4
  241. package/.agents/scripts/single-story-init.js +1 -1
  242. package/.agents/scripts/stories-wave-tick.js +13 -10
  243. package/.agents/scripts/story-close.js +1 -1
  244. package/.agents/scripts/story-init.js +162 -26
  245. package/.agents/scripts/story-phase.js +5 -5
  246. package/.agents/scripts/story-plan.js +3 -3
  247. package/.agents/scripts/sync-branch-from-base.js +2 -2
  248. package/.agents/scripts/validate-docs-freshness.js +1 -1
  249. package/.agents/scripts/wave-tick.js +1 -1
  250. package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
  251. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
  252. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
  253. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
  254. package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
  255. package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
  256. package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
  257. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
  258. package/.agents/skills/core/scope-triage/SKILL.md +9 -10
  259. package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
  260. package/.agents/skills/skills.index.json +7 -7
  261. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +1 -1
  262. package/.agents/templates/agent-protocol.md +2 -2
  263. package/.agents/workflows/agents-update.md +2 -2
  264. package/.agents/workflows/audit-architecture.md +2 -2
  265. package/.agents/workflows/audit-clean-code.md +2 -2
  266. package/.agents/workflows/audit-dependencies.md +1 -1
  267. package/.agents/workflows/audit-devops.md +1 -1
  268. package/.agents/workflows/audit-documentation.md +226 -0
  269. package/.agents/workflows/audit-lighthouse.md +1 -1
  270. package/.agents/workflows/audit-performance.md +2 -2
  271. package/.agents/workflows/audit-privacy.md +1 -1
  272. package/.agents/workflows/audit-quality.md +2 -2
  273. package/.agents/workflows/audit-security.md +2 -2
  274. package/.agents/workflows/audit-seo.md +1 -1
  275. package/.agents/workflows/audit-sre.md +1 -1
  276. package/.agents/workflows/audit-to-stories.md +10 -10
  277. package/.agents/workflows/audit-ux-ui.md +1 -1
  278. package/.agents/workflows/deliver.md +85 -0
  279. package/.agents/workflows/explain.md +3 -3
  280. package/.agents/workflows/git-merge-pr.md +1 -1
  281. package/.agents/workflows/git-pr-all.md +13 -10
  282. package/.agents/workflows/git-push.md +6 -3
  283. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  284. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  285. package/.agents/workflows/helpers/code-review.md +5 -5
  286. package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +59 -66
  287. package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
  288. package/.agents/workflows/helpers/diagnose.md +1 -1
  289. package/.agents/workflows/helpers/epic-audit.md +6 -6
  290. package/.agents/workflows/helpers/epic-deliver-story.md +28 -39
  291. package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
  292. package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
  293. package/.agents/workflows/helpers/epic-testing.md +3 -3
  294. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  295. package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
  296. package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
  297. package/.agents/workflows/helpers/signals.md +1 -1
  298. package/.agents/workflows/helpers/single-story-deliver.md +12 -11
  299. package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
  300. package/.agents/workflows/onboard.md +21 -20
  301. package/.agents/workflows/plan.md +89 -0
  302. package/.agents/workflows/qa-explore.md +1 -1
  303. package/.agents/workflows/qa-run-harness.md +1 -1
  304. package/README.md +17 -20
  305. package/docs/CHANGELOG.md +1149 -0
  306. package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
  307. package/lib/cli/__tests__/update-reexec.test.js +513 -0
  308. package/lib/cli/init.js +338 -0
  309. package/lib/cli/update.js +413 -52
  310. package/package.json +3 -1
  311. package/.agents/scripts/lib/auto-refresh-baselines.js +0 -308
  312. package/.agents/scripts/lib/close-validation.js +0 -897
  313. package/.agents/scripts/lib/orchestration/cascade-grouping.js +0 -275
  314. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +0 -69
  315. package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
  316. package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +0 -221
  317. package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +0 -123
  318. package/.agents/scripts/lib/task-utils.js +0 -26
  319. package/.agents/scripts/story-deliver-prepare.js +0 -267
@@ -1,396 +1,78 @@
1
1
  /**
2
- * lib/orchestration/ticketing/state.js — Per-ticket state mutators.
3
- *
4
- * Owns the one-ticket-at-a-time mutation surface: state-label
5
- * transitions, tasklist checkbox toggling, and structured-comment
6
- * post/upsert. Pulled out of `../ticketing.js` under Story #1848 so
7
- * the read-side (`./reads.js`) and the cascade/bulk side (`./bulk.js`)
8
- * each live behind a narrower import contract.
9
- *
10
- * Note on the cycle: `transitionTicketState` fires `cascadeCompletion`
11
- * from `./bulk.js` whenever a ticket flips to `agent::done` with the
12
- * default `cascade: true`. `./bulk.js` in turn imports
13
- * `transitionTicketState` to walk the parent chain. ESM tolerates the
14
- * cycle because neither side dereferences the other at
15
- * module-evaluation time both bindings are resolved at call-time
16
- * once both modules have completed evaluation.
17
- *
18
- * Story #3661 `_columnSyncRegistry` is a module-level WeakMap that
19
- * retains one `ColumnSync` instance per provider for the lifetime of the
20
- * process. `ColumnSync` already caches the project metadata (projectId,
21
- * fieldId, option ids) inside the instance after the first GraphQL fetch
22
- * (`this._meta`), but that cache was discarded on every label transition
23
- * because `syncProjectStatusColumn` constructed a fresh instance each
24
- * call. The registry lets the instance — and therefore its `_meta`
25
- * cache — survive across transitions, so the invariant metadata is
26
- * resolved exactly once per run rather than once per label flip.
2
+ * lib/orchestration/ticketing/state.js — Ticket state-mutation facade.
3
+ *
4
+ * Owns the structured-comment upsert (`upsertStructuredComment`) and the
5
+ * Storyless direct transition (`transitionStoryDirect`), and re-exports
6
+ * the single-ticket mutators that now live in `./transition.js`
7
+ * (`transitionTicketState`, `toggleTasklistCheckbox`,
8
+ * `postStructuredComment`, `_resetColumnSyncCache`). Pulled out of
9
+ * `../ticketing.js` under Story #1848 so the read-side (`./reads.js`) and
10
+ * the cascade/bulk side (`./bulk.js`) each live behind a narrower import
11
+ * contract.
12
+ *
13
+ * Story #3995 the single-ticket mutators were extracted into the leaf
14
+ * `./transition.js` to break the `state.js bulk.js` import cycle.
15
+ * `bulk.js`'s cascade walk depends on those primitives, and the primitives
16
+ * fire the upward cascade; lifting them into a leaf lets `state.js` and
17
+ * `bulk.js` both depend **downward** on `transition.js`. This module is
18
+ * the wiring point that injects `bulk.js`'s cascade pair
19
+ * (`cascadeParentState` / `logCascadePartialFailures`) into
20
+ * `transition.js` via `registerCascadeRunner`, so importing `state.js`
21
+ * (or the `../ticketing.js` facade that re-exports through it) always
22
+ * loads `bulk.js` and arms the real cascade before any transition runs.
27
23
  */
28
24
 
29
- import { extractEpicIdFromBody } from '../../dependency-parser.js';
30
25
  import { Logger } from '../../Logger.js';
31
- import {
32
- eventSeverity,
33
- renderTransitionMessage,
34
- } from '../../notifications/notifier.js';
35
- import { ColumnSync } from '../column-sync.js';
36
- // Story #1848 — cascade primitives live in `./bulk.js`. The ESM cycle
37
- // between state.js ↔ bulk.js is safe because neither side dereferences
38
- // the imported bindings at module-evaluation time — both are invoked at
39
- // call-time once the cycle has fully resolved. Story #2676 — the entry
40
- // point for upward propagation is now `cascadeParentState`, which
41
- // delegates `agent::done` transitions to `cascadeCompletion` internally.
42
26
  import { cascadeParentState, logCascadePartialFailures } from './bulk.js';
43
27
  import {
44
- ALL_STATES,
45
28
  assertValidStructuredCommentType,
46
29
  findStructuredComment,
47
30
  getProviderCommentCache,
48
31
  invalidateRawCommentsCache,
49
- STATE_LABELS,
50
32
  structuredCommentCacheKey,
51
33
  structuredCommentMarker,
52
34
  } from './reads.js';
53
-
54
- /**
55
- * One `ColumnSync` instance per provider, retained for the process
56
- * lifetime so the invariant project metadata (`projectId`, `fieldId`,
57
- * option ids) is fetched exactly once per run regardless of how many
58
- * label transitions fire. The `ColumnSync` instance already caches
59
- * the metadata internally (`_meta`), but that cache was thrown away on
60
- * every call to `syncProjectStatusColumn` because the function
61
- * constructed a fresh instance each time. Story #3661.
62
- *
63
- * `WeakMap` keys are GC-friendly: when a provider is collected the
64
- * entry is automatically removed without a manual eviction step.
65
- */
66
- const _columnSyncRegistry = new WeakMap();
67
-
68
- /**
69
- * Drop the cached `ColumnSync` for a given provider. Exposed as a
70
- * named export so unit tests that swap providers between assertions can
71
- * reset the registry without reloading the module.
72
- *
73
- * @param {object} provider
74
- */
75
- export function _resetColumnSyncCache(provider) {
76
- _columnSyncRegistry.delete(provider);
77
- }
78
-
79
- /**
80
- * Guard the inputs to {@link transitionTicketState}. Extracted from the
81
- * outer function so that the per-method cyclomatic complexity of
82
- * `transitionTicketState` lands below the CRAP-12 ceiling required by
83
- * Story #1848 (was CRAP 16 prior to the split — see baselines/crap.json).
84
- *
85
- * Currently a single label-membership predicate, but extracting it as a
86
- * named function lets future input guards (e.g. provider-shape checks,
87
- * concurrency-token validation) accrete here without re-inflating the
88
- * caller's complexity.
89
- *
90
- * @param {string} newState - Target `agent::*` label.
91
- * @returns {string} The validated newState, returned for fluent reuse.
92
- * @throws {Error} when `newState` is not a recognised state label.
93
- */
94
- function validateTransitionInputs(newState) {
95
- if (!ALL_STATES.includes(newState)) {
96
- throw new Error(`Invalid state: ${newState}`);
97
- }
98
- return newState;
99
- }
100
-
101
- /**
102
- * Resolve the pre-transition ticket snapshot that drives the notify
103
- * payload and the provider's label-merge path. Honors the caller-supplied
104
- * `opts.ticketSnapshot` (Story #1795) when present; otherwise issues a
105
- * best-effort `getTicket` and returns `null` on transient failure.
106
- *
107
- * @param {object} provider
108
- * @param {{ notify?: Function, ticketSnapshot?: object|null }} opts
109
- * @param {number} ticketId
110
- * @returns {Promise<object|null>}
111
- */
112
- async function loadTicketSnapshot(provider, opts, ticketId) {
113
- if (opts.ticketSnapshot) return opts.ticketSnapshot;
114
- if (!opts.notify || typeof provider.getTicket !== 'function') return null;
115
- try {
116
- return await provider.getTicket(ticketId);
117
- } catch (err) {
118
- Logger.debug(
119
- `[Ticketing] fromState lookup failed for #${ticketId}: ${err.message ?? err}`,
120
- );
121
- return null;
122
- }
123
- }
124
-
125
- /**
126
- * Mirror the post-flip label set onto the GitHub Projects v2 Status
127
- * column. Story #2548 — wiring this here makes every caller of
128
- * `transitionTicketState` (story-init, story-close, story-phase,
129
- * the LabelTransitioner lifecycle listener, the update-ticket-state CLI,
130
- * batch transitions) update the board automatically. Prior to #2548 the
131
- * sync was only wired from the epic-runner against the Epic ticket, so
132
- * Stories and Tasks never had their `agent::executing` /
133
- * `agent::blocked` flips reflected on the board.
134
- *
135
- * Best-effort: a project-board misconfig, missing scope, or transient
136
- * GraphQL failure MUST NOT block the label transition itself. Errors
137
- * surface via `Logger.warn` and the function resolves cleanly.
138
- *
139
- * The `_makeColumnSync` default param is a DIP seam: production callers
140
- * accept the default (which constructs a real `ColumnSync`); tests inject
141
- * a factory stub that avoids the GraphQL dependency without mocking the
142
- * module. Story #3645.
143
- *
144
- * Story #3661 — when the caller uses the default factory (i.e. did not
145
- * inject a stub), the function looks up or creates a `ColumnSync`
146
- * instance in `_columnSyncRegistry` keyed by `provider`. This keeps the
147
- * instance — and therefore its `_meta` cache — alive across transitions
148
- * so the invariant project metadata is fetched exactly once per run.
149
- * Test-injected factories bypass the registry entirely; their synthetic
150
- * stubs are never stored in `_columnSyncRegistry`.
151
- *
152
- * @param {object} provider
153
- * @param {number} ticketId
154
- * @param {string} newState
155
- * @param {(opts: object) => { sync: (id: number, labels: string[]) => Promise<object> }} [_makeColumnSync]
156
- */
157
- async function syncProjectStatusColumn(
158
- provider,
159
- ticketId,
160
- newState,
161
- _makeColumnSync,
162
- ) {
163
- try {
164
- let sync;
165
- if (_makeColumnSync) {
166
- // Test-injected factory: bypass the registry so stubs are never
167
- // accidentally cached and reused in subsequent calls.
168
- sync = _makeColumnSync({ provider, logger: Logger });
169
- } else {
170
- // Production path: look up or create the per-provider instance.
171
- // The instance's `_meta` cache survives across label transitions
172
- // so the invariant project metadata (projectId, fieldId, options)
173
- // is only fetched once per process run. Story #3661.
174
- if (!_columnSyncRegistry.has(provider)) {
175
- _columnSyncRegistry.set(
176
- provider,
177
- new ColumnSync({ provider, logger: Logger }),
178
- );
179
- }
180
- sync = _columnSyncRegistry.get(provider);
181
- }
182
- await sync.sync(ticketId, [newState]);
183
- } catch (err) {
184
- Logger.warn(
185
- `[Ticketing] column sync failed for #${ticketId} → ${newState}: ${err?.message ?? err}`,
186
- );
187
- }
188
- }
189
-
190
- /**
191
- * Dispatch the state-transition notification once the label flip has
192
- * landed. Pulled out of `transitionTicketState` so the outer function
193
- * stays below the CRAP-12 ceiling: this is where the severity gating,
194
- * the ticket-type derivation, the level mapping, and the fire-and-forget
195
- * dispatch all live.
196
- *
197
- * @param {{
198
- * notify: Function,
199
- * ticketId: number,
200
- * ticketSnapshot: object|null,
201
- * fromState: string|null,
202
- * newState: string,
203
- * }} args
204
- */
205
- function dispatchTransitionNotification(args) {
206
- const { notify, ticketId, ticketSnapshot, fromState, newState } = args;
207
- const typeLabel =
208
- ticketSnapshot?.labels?.find((l) => l.startsWith('type::')) ?? '';
209
- const ticketType = typeLabel.replace(/^type::/, '') || 'ticket';
210
- const epicId = extractEpicIdFromBody(ticketSnapshot?.body) ?? null;
211
- const event = {
212
- kind: 'state-transition',
213
- ticket: {
214
- id: ticketId,
215
- title: ticketSnapshot?.title,
216
- type: ticketType,
217
- },
218
- fromState,
219
- toState: newState,
220
- };
221
- const severity = eventSeverity(event);
222
- // Suppress the dispatch entirely for low-severity transitions (task-
223
- // level, or non-terminal story / epic flips). Pre-migration the
224
- // comment channel filtered these out via `commentMinLevel: medium`;
225
- // post-migration the channel is event-allowlist gated and would
226
- // surface every transition equally, so the noise filter moves to
227
- // the emit point.
228
- if (severity === 'low') return;
229
- const message = renderTransitionMessage(event);
230
- // Post to the epic so operators get a single timeline feed; fall back
231
- // to the transitioned ticket itself when no epic reference is present.
232
- // The dispatch is fire-and-forget by design (a failed notification must
233
- // not block the state transition itself), but surfacing the failure via
234
- // the logger preserves operator visibility — the previous empty-handler
235
- // .catch swallowed network blips and webhook 5xxs without any signal.
236
- const targetId = epicId ?? ticketId;
237
- const level =
238
- ticketType === 'epic' || ticketType === 'wave' || ticketType === 'story'
239
- ? ticketType
240
- : 'task';
241
- Promise.resolve(
242
- notify(targetId, {
243
- severity,
244
- message,
245
- event: 'state-transition',
246
- level,
247
- epicId: epicId ?? undefined,
248
- }),
249
- ).catch((err) => {
250
- Logger.warn(
251
- `[Ticketing] notify dispatch failed for #${targetId}: ${err?.message ?? err}`,
252
- );
253
- });
254
- }
255
-
256
- /**
257
- * Transitions a ticket's label to the new state.
258
- * Removes other agent:: state labels.
259
- *
260
- * @param {import('../../ITicketingProvider.js').ITicketingProvider} provider
261
- * @param {number} ticketId
262
- * @param {string} newState - Must be one of STATE_LABELS.
263
- * @param {{ notify?: Function, cascade?: boolean, ticketSnapshot?: object, _makeColumnSync?: Function }} [opts]
264
- * Optional notify function (the exported `notify(ticketId, payload, opts)`
265
- * from `notify.js`, or any stub matching its shape). When provided, a
266
- * state-transition notification fires after a successful transition.
267
- * Story/Epic → `agent::done` events are dispatched as `medium`; all other
268
- * transitions are `low` and filtered out at the default `medium` channel
269
- * thresholds. The dispatched payload carries the typed envelope fields
270
- * (`event: 'state-transition'`, `level: 'task'|'story'|'wave'|'epic'`,
271
- * `epicId`) for routable webhook subscribers.
272
- *
273
- * `cascade` (default `true`) controls whether a `done` transition fans the
274
- * `cascadeCompletion` upward to parents. Per-Task closes invoked mid-Story
275
- * from the retired per-Task progress writer (4-tier era, removed under
276
- * #3157) passed `cascade: false` so the Story/Epic only flipped to
277
- * `agent::done` at story-close (after the merge lands), not when the
278
- * last Task commit landed on the still-unmerged Story branch. The
279
- * parameter is preserved for callers that still suppress cascade
280
- * explicitly (e.g. batch-transition helpers).
281
- *
282
- * `ticketSnapshot` (Story #1795 / Epic #1788) is an optional pre-fetched
283
- * ticket object. When the caller already holds the ticket (e.g.
284
- * `batchTransitionTickets`, which loops over a list it just hydrated),
285
- * passing the snapshot eliminates the two `getTicket` round-trips that
286
- * `transitionTicketState` would otherwise issue — one for the notify
287
- * `fromState` lookup and one inside `provider.updateTicket`'s label
288
- * merge path. Backwards compatible: when omitted, behaviour is unchanged.
289
- *
290
- * `_makeColumnSync` (Story #3645 DIP seam) — factory for the board-sync
291
- * object. Production callers omit it (the default constructs a real
292
- * `ColumnSync`); tests inject a stub to avoid GraphQL calls without
293
- * module-level mocking.
294
- */
295
- export async function transitionTicketState(
296
- provider,
297
- ticketId,
298
- newState,
299
- opts = {},
300
- ) {
301
- validateTransitionInputs(newState);
302
-
303
- const toRemove = ALL_STATES.filter((state) => state !== newState);
304
-
305
- // Snapshot prior state for the notification payload (best-effort; skip on
306
- // error). A transient read failure MUST NOT block a label transition —
307
- // the transition itself is idempotent and `fromState: null` is a valid
308
- // payload value.
309
- //
310
- // Story #1795 — when the caller threads `opts.ticketSnapshot` we reuse
311
- // it as the notify snapshot without issuing a fresh `getTicket`. The
312
- // snapshot is also forwarded to `provider.updateTicket` so the label
313
- // merge path skips its own `getTicket` call (the second of the two
314
- // round-trips this seam eliminates).
315
- const ticketSnapshot = await loadTicketSnapshot(provider, opts, ticketId);
316
- const fromState =
317
- ticketSnapshot?.labels?.find((l) => ALL_STATES.includes(l)) ?? null;
318
-
319
- // Closing/reopening mirrors the label state so GitHub shows the correct
320
- // issue state without requiring a separate manual close step.
321
- const isDone = newState === STATE_LABELS.DONE;
322
-
323
- await provider.updateTicket(ticketId, {
324
- labels: {
325
- add: [newState],
326
- remove: toRemove,
327
- },
328
- state: isDone ? 'closed' : 'open',
329
- state_reason: isDone ? 'completed' : null,
330
- // Internal-only escape hatch threaded through `provider.updateTicket`
331
- // to `_applyLabelMutations`. Honored by `providers/github.js`; ignored
332
- // by providers that don't recognise it. Underscore-prefixed to mark
333
- // it as a provider-internal contract rather than part of the public
334
- // `mutations` shape.
335
- _ticketSnapshot: ticketSnapshot,
35
+ import {
36
+ _resetColumnSyncCache,
37
+ postStructuredComment,
38
+ registerCascadeRunner,
39
+ toggleTasklistCheckbox,
40
+ transitionTicketState,
41
+ } from './transition.js';
42
+
43
+ // Re-export the single-ticket mutators that moved to `./transition.js`
44
+ // (Story #3995) so existing consumers that import them from `./state.js`
45
+ // and the `../ticketing.js` facade keep working without an import
46
+ // path change.
47
+ export {
48
+ _resetColumnSyncCache,
49
+ postStructuredComment,
50
+ toggleTasklistCheckbox,
51
+ transitionTicketState,
52
+ };
53
+
54
+ // Story #3995 — inject `bulk.js`'s upward-cascade pair into
55
+ // `transition.js`. `transition.js` stays a leaf (it does not import
56
+ // `bulk.js`); this wiring runs once at module-evaluation time and is the
57
+ // single home for the legacy `transitionTicketState → cascadeParentState`
58
+ // behaviour, preserving the `cascade` flag and the partial-failure log.
59
+ registerCascadeRunner(async (provider, ticketId, opts) => {
60
+ const cascade = await cascadeParentState(provider, ticketId, {
61
+ notify: opts.notify,
336
62
  });
337
-
338
- // Story #2548 — mirror the new state onto the Projects v2 Status
339
- // column. Best-effort; never blocks the transition.
340
- // Story #3645 — thread the DIP seam so callers can inject a stub.
341
- await syncProjectStatusColumn(
342
- provider,
343
- ticketId,
344
- newState,
345
- opts._makeColumnSync,
346
- );
347
-
348
- // Automatically trigger upward cascade on every transition (Story
349
- // #2676). The unified entry point is `cascadeParentState`, which:
350
- // - delegates `agent::done` transitions to the legacy
351
- // `cascadeCompletion` (preserving tasklist-checkbox toggling, the
352
- // "All child tickets completed" progress comment, and the Epic
353
- // close-exclusion);
354
- // - for every other `agent::*` transition (`executing`, `blocked`,
355
- // `closing`, …) walks the parent chain and updates each parent to
356
- // the state derived from its children's current composition. This
357
- // keeps the GitHub Project board accurate when work begins on a
358
- // Task ("In Progress" surfaces up to the Story and Epic) or when a
359
- // child enters the HITL pause state.
360
- //
361
- // Callers that intentionally suppress propagation (historically the
362
- // per-Task progress writer, which closed Tasks at commit-time but
363
- // deferred the Story flip to story-close after the branch was merged)
364
- // opt out by passing `cascade: false`.
365
- if (opts.cascade !== false) {
366
- const cascade = await cascadeParentState(provider, ticketId, {
367
- notify: opts.notify,
368
- });
369
- logCascadePartialFailures(ticketId, cascade);
370
- }
371
-
372
- // Fire the state-transition notification (fire-and-forget).
373
- if (typeof opts.notify === 'function') {
374
- dispatchTransitionNotification({
375
- notify: opts.notify,
376
- ticketId,
377
- ticketSnapshot,
378
- fromState,
379
- newState,
380
- });
381
- }
382
- }
63
+ logCascadePartialFailures(ticketId, cascade);
64
+ });
383
65
 
384
66
  /**
385
67
  * Transition a Story ticket directly to a new `agent::*` state without
386
68
  * walking a Task cascade. Story #3097 (Wave-0 additive, Epic #3078
387
- * Strategy B) — in the 3-tier hierarchy a Story has no Task children, so
69
+ * Strategy B) — in the 2-tier hierarchy a Story has no Task children, so
388
70
  * the canonical `transitionTicketState` upward-cascade path
389
71
  * (`cascadeParentState`) is the only meaningful walk. This helper is a
390
- * thin wrapper that pins `cascade: true` (so the parent Feature/Epic
72
+ * thin wrapper that pins `cascade: true` (so the parent Epic
391
73
  * still receives derived-state updates) and is intentionally a no-op
392
74
  * difference from `transitionTicketState` in 4-tier mode — the helper
393
- * exists so 3-tier callers can opt into a name that documents intent
75
+ * exists so 2-tier callers can opt into a name that documents intent
394
76
  * (and so F8 can pivot the implementation to skip the now-impossible
395
77
  * Task-fan-in without rewriting call sites). The wrapper preserves every
396
78
  * `opts` field the caller supplies; only `cascade` defaults to `true`
@@ -411,74 +93,6 @@ export async function transitionStoryDirect(
411
93
  await transitionTicketState(provider, storyId, newState, merged);
412
94
  }
413
95
 
414
- /**
415
- * Mutates the tasklist checkbox in the parent's body.
416
- * E.g., `- [ ] #123` to `- [x] #123`
417
- *
418
- * Story #3645 — positional `checked` boolean replaced with a named
419
- * `{ checked }` options bag to eliminate the boolean-trap smell (SRP /
420
- * naming clarity audit finding). All call sites updated in the same PR
421
- * per the No-Shim cutover rule.
422
- *
423
- * @param {import('../../ITicketingProvider.js').ITicketingProvider} provider
424
- * @param {number} ticketId - ID of parent ticket
425
- * @param {number} subIssueId - ID of child ticket
426
- * @param {{ checked: boolean }} opts
427
- */
428
- export async function toggleTasklistCheckbox(
429
- provider,
430
- ticketId,
431
- subIssueId,
432
- { checked },
433
- ) {
434
- const ticket = await provider.getTicket(ticketId);
435
- const body = ticket.body || '';
436
-
437
- if (!body.includes(`#${subIssueId}`)) {
438
- return; // sub-issue not directly referenced in body
439
- }
440
-
441
- const targetBox = checked ? '- [x]' : '- [ ]';
442
-
443
- let newBody = body;
444
-
445
- if (checked) {
446
- // replace `- [ ] #123` or `- [] #123` with `- [x] #123`
447
- const re = new RegExp(`-\\s*\\[\\s*\\]\\s+#${subIssueId}\\b`, 'g');
448
- newBody = newBody.replace(re, `${targetBox} #${subIssueId}`);
449
- } else {
450
- // replace `- [x] #123` or `- [X] #123` with `- [ ] #123`
451
- const re = new RegExp(`-\\s*\\[[xX]\\]\\s+#${subIssueId}\\b`, 'g');
452
- newBody = newBody.replace(re, `${targetBox} #${subIssueId}`);
453
- }
454
-
455
- if (newBody !== body) {
456
- await provider.updateTicket(ticketId, {
457
- body: newBody,
458
- });
459
- }
460
- }
461
-
462
- /**
463
- * Post a structured comment to a ticket.
464
- *
465
- * @param {import('../../ITicketingProvider.js').ITicketingProvider} provider
466
- * @param {number} ticketId
467
- * @param {'progress'|'friction'|'notification'} type
468
- * @param {string} payload
469
- */
470
- export async function postStructuredComment(provider, ticketId, type, payload) {
471
- assertValidStructuredCommentType(type);
472
- await provider.postComment(ticketId, {
473
- type,
474
- body: payload,
475
- });
476
- // Story #2465 — evict the raw-comments cache entry so the next
477
- // `findStructuredComment` against this ticket re-fetches and sees the
478
- // freshly-posted comment.
479
- invalidateRawCommentsCache(provider, ticketId);
480
- }
481
-
482
96
  /**
483
97
  * Idempotently post a structured comment identified by an embedded HTML
484
98
  * marker. If an existing comment with the same `type` marker (and matching