mandrel 2.0.0 → 2.1.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 (311) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +41 -46
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +51 -44
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +32 -56
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +14 -16
  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/git-conventions-reference.md +42 -51
  14. package/.agents/schemas/agentrc.schema.json +34 -45
  15. package/.agents/schemas/audit-rules.json +59 -1
  16. package/.agents/schemas/audit-rules.schema.json +33 -1
  17. package/.agents/schemas/lifecycle/README.md +1 -2
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  20. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  21. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  22. package/.agents/schemas/signal-event.schema.json +3 -3
  23. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  24. package/.agents/schemas/validation-evidence.schema.json +1 -1
  25. package/.agents/scripts/acceptance-eval.js +22 -66
  26. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  27. package/.agents/scripts/bootstrap.js +3 -3
  28. package/.agents/scripts/check-dead-exports.js +43 -104
  29. package/.agents/scripts/check-doc-links.js +2 -2
  30. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  31. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  32. package/.agents/scripts/deliver-recover.js +122 -0
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/evidence-gate.js +20 -50
  35. package/.agents/scripts/generate-skills-index.js +17 -1
  36. package/.agents/scripts/generate-workflows-doc.js +4 -4
  37. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  38. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  39. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  40. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  41. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  42. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  43. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  44. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  45. package/.agents/scripts/lib/checks/index.js +1 -1
  46. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  47. package/.agents/scripts/lib/checks/state.js +17 -248
  48. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  49. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  50. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  51. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  52. package/.agents/scripts/lib/cli-args.js +23 -2
  53. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  54. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  55. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  56. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  57. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  58. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  59. package/.agents/scripts/lib/config/explain.js +10 -16
  60. package/.agents/scripts/lib/config/github.js +7 -5
  61. package/.agents/scripts/lib/config/limits.js +15 -25
  62. package/.agents/scripts/lib/config/quality.js +11 -14
  63. package/.agents/scripts/lib/config/runners.js +8 -21
  64. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  65. package/.agents/scripts/lib/config-settings-schema-delivery.js +31 -13
  66. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  67. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  68. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  69. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  70. package/.agents/scripts/lib/duplicate-search.js +38 -7
  71. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  72. package/.agents/scripts/lib/format-generated-json.js +97 -0
  73. package/.agents/scripts/lib/framework-version.js +19 -189
  74. package/.agents/scripts/lib/gh-exec.js +8 -0
  75. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  76. package/.agents/scripts/lib/git-utils.js +0 -14
  77. package/.agents/scripts/lib/json-utils.js +1 -2
  78. package/.agents/scripts/lib/label-constants.js +0 -15
  79. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  80. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  81. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  82. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  83. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  84. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  85. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  86. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  87. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  88. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  89. package/.agents/scripts/lib/orchestration/code-review.js +58 -168
  90. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  91. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  92. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  93. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  94. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  95. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  96. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  97. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  98. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  99. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  100. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  101. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  102. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  103. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  104. package/.agents/scripts/lib/orchestration/plan-context.js +114 -24
  105. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  106. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
  107. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  108. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  109. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  110. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  111. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -75
  112. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  113. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  114. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  115. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  116. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  118. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  119. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  120. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  122. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  124. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  125. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  126. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +3 -12
  131. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  132. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  138. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  139. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  140. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  141. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -32
  142. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  143. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  144. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  145. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  146. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  147. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  148. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  149. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  150. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  151. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  152. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  153. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  154. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  155. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  156. package/.agents/scripts/lib/planning-corpus.js +12 -286
  157. package/.agents/scripts/lib/preflight-runner.js +2 -2
  158. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  159. package/.agents/scripts/lib/signals/index.js +4 -17
  160. package/.agents/scripts/lib/signals/read.js +35 -35
  161. package/.agents/scripts/lib/signals/schema.js +8 -11
  162. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  163. package/.agents/scripts/lib/signals/write.js +0 -1
  164. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  165. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  166. package/.agents/scripts/lib/story-adjacency.js +8 -7
  167. package/.agents/scripts/lib/story-body/story-body.js +6 -5
  168. package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
  169. package/.agents/scripts/lib/test-env.js +14 -1
  170. package/.agents/scripts/lib/test-tiers.js +0 -3
  171. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  172. package/.agents/scripts/lib/validation-evidence.js +31 -59
  173. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  174. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  175. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  176. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  177. package/.agents/scripts/plan-context.js +38 -6
  178. package/.agents/scripts/plan-persist.js +145 -35
  179. package/.agents/scripts/plan-run-epilogue.js +83 -38
  180. package/.agents/scripts/post-structured-comment.js +0 -38
  181. package/.agents/scripts/pr-watch-with-update.js +43 -22
  182. package/.agents/scripts/providers/github/compose.js +0 -1
  183. package/.agents/scripts/providers/github/errors.js +0 -19
  184. package/.agents/scripts/providers/github/issues.js +1 -11
  185. package/.agents/scripts/providers/github/mappers.js +5 -0
  186. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  187. package/.agents/scripts/providers/github/tickets.js +33 -153
  188. package/.agents/scripts/providers/github.js +17 -6
  189. package/.agents/scripts/resolve-stories.js +236 -0
  190. package/.agents/scripts/run-coverage.js +4 -1
  191. package/.agents/scripts/run-lint.js +2 -2
  192. package/.agents/scripts/run-verify.js +31 -2
  193. package/.agents/scripts/signals-view.js +9 -10
  194. package/.agents/scripts/single-story-close.js +173 -18
  195. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  196. package/.agents/scripts/single-story-init.js +6 -10
  197. package/.agents/scripts/stories-wave-tick.js +79 -4
  198. package/.agents/scripts/story-plan.js +3 -3
  199. package/.agents/scripts/update-ticket-state.js +8 -50
  200. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  201. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  202. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  203. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  204. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  205. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  206. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  207. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  208. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  209. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  210. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  211. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  212. package/.agents/skills/skills.index.json +2 -12
  213. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  214. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  215. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  216. package/.agents/workflows/audit-architecture.md +3 -4
  217. package/.agents/workflows/audit-clean-code.md +4 -4
  218. package/.agents/workflows/audit-documentation.md +4 -5
  219. package/.agents/workflows/audit-lighthouse.md +8 -0
  220. package/.agents/workflows/audit-navigability.md +10 -0
  221. package/.agents/workflows/audit-performance.md +2 -3
  222. package/.agents/workflows/audit-quality.md +8 -9
  223. package/.agents/workflows/audit-security.md +1 -2
  224. package/.agents/workflows/audit-seo.md +10 -0
  225. package/.agents/workflows/audit-ux-ui.md +7 -0
  226. package/.agents/workflows/deliver.md +98 -45
  227. package/.agents/workflows/git-cleanup.md +2 -2
  228. package/.agents/workflows/git-deliver.md +1 -1
  229. package/.agents/workflows/helpers/acceptance-self-eval.md +21 -13
  230. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  231. package/.agents/workflows/helpers/code-review.md +12 -10
  232. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  233. package/.agents/workflows/helpers/deliver-story.md +193 -118
  234. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  235. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  236. package/.agents/workflows/plan.md +184 -19
  237. package/.agents/workflows/qa-assist.md +6 -6
  238. package/.agents/workflows/qa-explore.md +3 -3
  239. package/.agents/workflows/qa-run.md +1 -5
  240. package/bin/mandrel.js +12 -1
  241. package/docs/CHANGELOG.md +40 -0
  242. package/lib/cli/registry.js +262 -19
  243. package/lib/cli/sync-agents.js +157 -0
  244. package/lib/cli/sync-commands.js +115 -6
  245. package/lib/cli/sync.js +168 -6
  246. package/lib/cli/update.js +105 -8
  247. package/lib/cli/version-helpers.js +131 -0
  248. package/lib/migrations/README.md +7 -5
  249. package/lib/migrations/index.js +12 -9
  250. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  251. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  252. package/package.json +1 -1
  253. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  254. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  255. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  256. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  257. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  258. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  259. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  260. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  261. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  262. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  263. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  264. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  265. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  266. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  268. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  270. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  271. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  273. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  274. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  275. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  277. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  278. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  279. package/.agents/schemas/risk-verdict.schema.json +0 -53
  280. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  281. package/.agents/scripts/analyze-execution.js +0 -444
  282. package/.agents/scripts/check-prepush-recovery.js +0 -90
  283. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  284. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  285. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  286. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  287. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  288. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  289. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  290. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  291. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  292. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  293. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  295. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  296. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  297. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  298. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  299. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  300. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  301. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  302. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  303. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  304. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  305. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  306. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  307. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  308. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  309. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  310. package/.agents/scripts/resolve-plan-run.js +0 -117
  311. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -5,7 +5,7 @@
5
5
  * phase is removed, every plan CLI invocation stamps an entry/exit record so
6
6
  * the current 12-phase baseline is captured on disk. Each record is one
7
7
  * newline-terminated JSON line appended to
8
- * `temp/epic-<id>/plan-metrics.json` (per-Epic plan CLIs) or
8
+ * `temp/run-<id>/plan-metrics.json` (per-Epic plan CLIs) or
9
9
  * `temp/standalone/plan-metrics.json` (the standalone `story-plan.js` path
10
10
  * and Epic-less healthcheck runs — same standalone routing the friction
11
11
  * ledger uses).
@@ -61,7 +61,7 @@ import path from 'node:path';
61
61
 
62
62
  import {
63
63
  anchorTempRoot,
64
- epicArtifactPath,
64
+ runArtifactPath,
65
65
  tempRootFrom,
66
66
  } from '../config/temp-paths.js';
67
67
  import { Logger } from '../Logger.js';
@@ -95,7 +95,7 @@ export function planMetricsPath(epicId, config) {
95
95
  PLAN_METRICS_BASENAME,
96
96
  );
97
97
  }
98
- return epicArtifactPath(epicId, PLAN_METRICS_BASENAME, config);
98
+ return runArtifactPath(epicId, PLAN_METRICS_BASENAME, config);
99
99
  }
100
100
 
101
101
  /**
@@ -314,7 +314,8 @@ export async function readPlanMetrics(epicId, config) {
314
314
 
315
315
  /**
316
316
  * Roll a read ledger up into the compact summary surfaced by the persist
317
- * summary and `analyze-execution.js`. Returns `null` when there is nothing
317
+ * summary. (Story #4545 deleted its second consumer, `analyze-execution.js`,
318
+ * with the execution-analysis surface.) Returns `null` when there is nothing
318
319
  * to summarize (missing ledger or zero parseable entries).
319
320
  *
320
321
  * Critic-skip records (kind: 'critic-skip') are counted separately from
@@ -322,7 +323,15 @@ export async function readPlanMetrics(epicId, config) {
322
323
  * them down, so the skip-audit trail is visible in the persist summary
323
324
  * without inflating the turns-per-plan proxy.
324
325
  *
326
+ * Pass `opts.since` (an ISO-8601 instant) to scope the roll-up to one plan
327
+ * run (Story #4541). The Epic-less ledger at `temp/standalone/` is shared by
328
+ * every plan the repo has ever run, so an unfiltered summary reported
329
+ * lifetime totals under a line the reader takes to describe the invocation
330
+ * in front of them. Records are timestamped `startedAt` (invocations) or
331
+ * `at` (critic skips); either at-or-after `since` is in scope.
332
+ *
325
333
  * @param {{ entries: object[], malformedLines?: number }} ledger
334
+ * @param {{ since?: string|null }} [opts]
326
335
  * @returns {{
327
336
  * invocations: number,
328
337
  * failures: number,
@@ -337,8 +346,31 @@ export async function readPlanMetrics(epicId, config) {
337
346
  * malformedLines: number,
338
347
  * }|null}
339
348
  */
340
- export function summarizePlanMetrics(ledger) {
341
- const entries = ledger?.entries ?? [];
349
+ /**
350
+ * Timestamp a ledger record is ordered by: `startedAt` for invocation
351
+ * records, `at` for critic-skip records.
352
+ *
353
+ * @param {object} entry
354
+ * @returns {string|null}
355
+ */
356
+ function recordTimestamp(entry) {
357
+ const stamp =
358
+ entry?.kind === PLAN_METRICS_KIND_CRITIC_SKIP ? entry.at : entry.startedAt;
359
+ return typeof stamp === 'string' ? stamp : null;
360
+ }
361
+
362
+ export function summarizePlanMetrics(ledger, opts = {}) {
363
+ const all = ledger?.entries ?? [];
364
+ const since = typeof opts.since === 'string' ? opts.since : null;
365
+ // ISO-8601 UTC strings sort lexicographically in time order, so a string
366
+ // compare is a correct (and allocation-free) instant compare here.
367
+ const entries =
368
+ since === null
369
+ ? all
370
+ : all.filter((e) => {
371
+ const stamp = recordTimestamp(e);
372
+ return stamp !== null && stamp >= since;
373
+ });
342
374
  if (entries.length === 0) return null;
343
375
  const byCli = {};
344
376
  const byMode = {};
@@ -6,7 +6,11 @@
6
6
  */
7
7
 
8
8
  import { Logger } from '../../Logger.js';
9
- import { renderHardConflictError } from '../ticket-validator-conflicts.js';
9
+ import {
10
+ renderFanOutEvidence,
11
+ renderFanOutRemedy,
12
+ renderHardConflictError,
13
+ } from '../ticket-validator-conflicts.js';
10
14
 
11
15
  /**
12
16
  * @param {object[]} findings
@@ -24,8 +28,9 @@ export function enforceFanOutGate(
24
28
  for (const f of fanOut) {
25
29
  Logger.warn(
26
30
  `[${tag}] Persisting a large-fan-out deletion: ` +
27
- `Task "${f.taskSlug}" deletes "${f.path}" with ${f.callSiteCount} ` +
28
- `call site(s) (threshold ${f.threshold}). Operator override --allow-large-fan-out.`,
31
+ `Story "${f.storySlug}" deletes "${f.path}" with ${f.callSiteCount} ` +
32
+ `importer(s) (threshold ${f.threshold}). Operator override --allow-large-fan-out.` +
33
+ renderFanOutEvidence(f),
29
34
  );
30
35
  }
31
36
  return;
@@ -33,13 +38,18 @@ export function enforceFanOutGate(
33
38
  const lines = fanOut
34
39
  .map(
35
40
  (f) =>
36
- ` - Task "${f.taskSlug}" (Story "${f.storySlug}") deletes "${f.path}" — ${f.callSiteCount} call site(s) (threshold ${f.threshold})`,
41
+ ` - Story "${f.storySlug}" deletes "${f.path}" — ` +
42
+ `${f.callSiteCount} importer(s) (threshold ${f.threshold})` +
43
+ renderFanOutEvidence(f),
37
44
  )
38
45
  .join('\n');
46
+ // Each finding carries its own remedy — a rename-shaped deletion has no
47
+ // subsystems to split across, so a blanket "split it up" footer would be
48
+ // wrong advice for it (Story #4547).
49
+ const remedies = [...new Set(fanOut.map((f) => renderFanOutRemedy(f)))];
39
50
  throw new Error(
40
51
  `[${tag}] ${fanOut.length} Task(s) declare large-fan-out deletions:\n${lines}\n\n` +
41
- `Split each deletion into a subsystem-by-subsystem migration across multiple Stories, ` +
42
- `or rerun --allow-large-fan-out after confirming the deletion is intentional.`,
52
+ remedies.join('\n\n'),
43
53
  );
44
54
  }
45
55
 
@@ -2,16 +2,19 @@
2
2
  * persist-helpers.js — pure helper surface for the flat Story `/plan` persist.
3
3
  *
4
4
  * Exports:
5
+ * - `resolveBaseBranchRef(config)` — the one place the persist gates learn
6
+ * which ref to probe.
5
7
  * - `validateTickets(tickets, config)` — runs the cross-link, model-capacity,
6
8
  * freshness, and task-body validators in one pass. Capacity settings are
7
9
  * explicit inputs so the validator and decomposer share one live delivery
8
10
  * envelope instead of silently falling back to framework defaults.
9
- * - `makeDefaultFanOutCounter({ baseBranchRef, cwd })` — production
11
+ * - `makeDefaultFanOutCounter({ baseBranchRef, cwd, git })` — production
10
12
  * fan-out probe used by the conflict policy.
11
13
  *
12
14
  * @module lib/orchestration/plan-persist/persist-helpers
13
15
  */
14
16
 
17
+ import posix from 'node:path/posix';
15
18
  import { resolveListValue } from '../../config/shared.js';
16
19
  import { gitSpawn } from '../../git-utils.js';
17
20
  import { validateTaskBodies } from '../task-body-validator.js';
@@ -19,32 +22,156 @@ import { validateAndNormalizeTickets } from '../ticket-validator.js';
19
22
  import { DEFAULT_REGISTRY_PATTERNS } from '../ticket-validator-conflicts.js';
20
23
 
21
24
  /**
22
- * Default fan-out counter counts distinct files at `baseBranchRef` that
23
- * reference the basename (without extension) of the deleted path. Uses
24
- * `git grep -l` for a streaming-friendly probe; an empty grep returns
25
- * exit code 1 which we map to a count of 0.
25
+ * Extensions an import specifier may elide. Probed longest-path-first when
26
+ * resolving an extensionless specifier back onto a concrete repo path.
27
+ */
28
+ const RESOLVABLE_EXTENSIONS = ['', '.js', '.mjs', '.cjs', '.jsx', '.json'];
29
+
30
+ /** Every quoted string on a candidate line — the specifier lives in one. */
31
+ const QUOTED_RE = /['"]([^'"\n]+)['"]/g;
32
+
33
+ function escapeRegExp(value) {
34
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
35
+ }
36
+
37
+ /**
38
+ * The specifier tails an importer of `path` could plausibly write as the
39
+ * final segment of its quoted specifier: the basename, the basename minus
40
+ * its extension, and — for a directory-index module — the directory name
41
+ * (`./foo` resolving to `foo/index.js`).
42
+ *
43
+ * This is a *candidate* net only. It is deliberately generous because the
44
+ * resolution pass below re-checks every hit against the real path; a tail
45
+ * that over-matches costs one extra resolve, a tail that under-matches
46
+ * loses a genuine importer.
47
+ */
48
+ function specifierTails(path) {
49
+ const base = posix.basename(path);
50
+ const ext = posix.extname(base);
51
+ const stem = ext ? base.slice(0, -ext.length) : base;
52
+ const tails = new Set([base, stem]);
53
+ if (stem === 'index') {
54
+ const dir = posix.basename(posix.dirname(path));
55
+ if (dir && dir !== '.') tails.add(dir);
56
+ }
57
+ return [...tails].filter((t) => t.length > 0);
58
+ }
59
+
60
+ /**
61
+ * ERE matching a line that carries an `import` / `export … from` /
62
+ * `require(` / dynamic `import(` whose quoted specifier *ends* at one of
63
+ * `tails`. The `(^|/)`-equivalent guard (`([^'"]*\/)?`) is what keeps
64
+ * `notification.js` from matching `push-notification.js`.
65
+ */
66
+ function buildProbePattern(tails) {
67
+ const alt = tails.map(escapeRegExp).join('|');
68
+ return `(from|require|import)[[:space:]]*\\(?[[:space:]]*['"]([^'"]*/)?(${alt})['"]`;
69
+ }
70
+
71
+ /**
72
+ * Resolve a relative specifier written in `importerPath` back onto a repo
73
+ * path and report whether it names `deletedPath`.
74
+ *
75
+ * **Known boundary:** only relative (`./`, `../`) specifiers resolve. A
76
+ * consumer repo that imports its own modules through bare specifiers or a
77
+ * path alias (`#lib/x`, `@app/x`, a `tsconfig` `paths` entry) would
78
+ * under-count, because resolving those needs the resolver config this probe
79
+ * deliberately does not read. Mandrel's own internal imports are all
80
+ * relative. Under-counting is the *quiet* failure direction — it argues for
81
+ * a deletion rather than against one — so if alias-importing consumers
82
+ * appear, this is the place to teach the probe their resolver.
83
+ */
84
+ function specifierResolvesTo(importerPath, specifier, deletedPath) {
85
+ if (!specifier.startsWith('./') && !specifier.startsWith('../')) return false;
86
+ const resolved = posix.normalize(
87
+ posix.join(posix.dirname(importerPath), specifier),
88
+ );
89
+ for (const ext of RESOLVABLE_EXTENSIONS) {
90
+ if (`${resolved}${ext}` === deletedPath) return true;
91
+ if (ext && `${resolved}/index${ext}` === deletedPath) return true;
92
+ }
93
+ return false;
94
+ }
95
+
96
+ /**
97
+ * Quote one argv entry so the reported probe is **runnable as emitted**.
26
98
  *
27
- * Story #2962. Injected via opts in tests; this default runs in production.
99
+ * The probe is the operator's route to checking the number, so it has to
100
+ * survive a paste into a shell. Unquoted, the ERE's `(`, `|`, `[[:space:]]`
101
+ * and `?` are glob/grouping metacharacters: zsh fails the paste with
102
+ * `no matches found` *and exits 0*, which reads as "zero importers" — the
103
+ * gate's own audit trail would then argue for the deletion it is meant to
104
+ * question (Story #4547).
28
105
  */
29
- export function makeDefaultFanOutCounter({ baseBranchRef, cwd }) {
106
+ function shellQuote(value) {
107
+ if (/^[A-Za-z0-9_./-]+$/.test(value)) return value;
108
+ return `'${value.replaceAll("'", "'\\''")}'`;
109
+ }
110
+
111
+ /**
112
+ * Parse one `git grep -n` output line of the form `<ref>:<path>:<lineno>:<text>`.
113
+ */
114
+ function parseGrepLine(line, baseBranchRef) {
115
+ const prefix = `${baseBranchRef}:`;
116
+ if (!line.startsWith(prefix)) return null;
117
+ const rest = line.slice(prefix.length);
118
+ const pathEnd = rest.indexOf(':');
119
+ if (pathEnd === -1) return null;
120
+ const path = rest.slice(0, pathEnd);
121
+ const afterPath = rest.slice(pathEnd + 1);
122
+ const lineEnd = afterPath.indexOf(':');
123
+ if (lineEnd === -1) return null;
124
+ return { path, text: afterPath.slice(lineEnd + 1) };
125
+ }
126
+
127
+ /**
128
+ * Default fan-out probe — resolves the *importers* of the deleted module at
129
+ * `baseBranchRef`, and reports the referencing files alongside the exact
130
+ * probe that produced them.
131
+ *
132
+ * Two-stage, because accuracy and cost pull in opposite directions:
133
+ *
134
+ * 1. `git grep -n -E` narrows the tree to lines whose quoted import /
135
+ * require specifier could name the module (final-segment match).
136
+ * 2. Each candidate specifier is resolved against its own importer's
137
+ * directory and compared to the deleted path. Only a real resolution
138
+ * counts.
139
+ *
140
+ * The predecessor (Story #2962) grepped the basename stem as a bare word
141
+ * across the whole tree, so a module named `notification` or `options`
142
+ * reported dozens of call sites drawn from prose, schemas, and unrelated
143
+ * modules — and the gate that fired on that number told the operator to
144
+ * split a migration that did not exist. It also returned 0 without probing
145
+ * for any stem under three characters, under-reporting in silence. Both are
146
+ * gone: coupling is measured by resolution, not vocabulary (Story #4547).
147
+ *
148
+ * @returns {(arg: { path: string }) => { count: number, files: string[], probe: string }}
149
+ */
150
+ export function makeDefaultFanOutCounter({ baseBranchRef, cwd, git } = {}) {
151
+ const spawn = git?.gitSpawn ?? gitSpawn;
30
152
  return ({ path }) => {
31
- const lastSlash = path.lastIndexOf('/');
32
- const base = lastSlash === -1 ? path : path.slice(lastSlash + 1);
33
- const dotIdx = base.lastIndexOf('.');
34
- const stem = dotIdx > 0 ? base.slice(0, dotIdx) : base;
35
- if (stem.length < 3) return 0;
36
- const result = gitSpawn(
37
- cwd ?? process.cwd(),
38
- 'grep',
39
- '-l',
40
- '--fixed-strings',
41
- stem,
42
- baseBranchRef,
43
- );
44
- if (result.status !== 0) return 0;
45
- const lines = result.stdout.split('\n').filter((l) => l.trim().length > 0);
46
- // Exclude the deleted file itself from the call-site count.
47
- return lines.filter((l) => !l.endsWith(`:${path}`)).length;
153
+ const tails = specifierTails(path);
154
+ const pattern = buildProbePattern(tails);
155
+ const args = ['grep', '-n', '-E', '--full-name', pattern, baseBranchRef];
156
+ const probe = `git ${args.map(shellQuote).join(' ')}`;
157
+ const result = spawn(cwd ?? process.cwd(), ...args);
158
+ // git grep exits 1 on "no matches" — an empty result, not a failure.
159
+ if (result.status !== 0) return { count: 0, files: [], probe };
160
+ const files = new Set();
161
+ for (const line of result.stdout.split('\n')) {
162
+ if (line.trim().length === 0) continue;
163
+ const hit = parseGrepLine(line, baseBranchRef);
164
+ // The deleted module's own self-references are not call sites.
165
+ if (!hit || hit.path === path) continue;
166
+ for (const match of hit.text.matchAll(QUOTED_RE)) {
167
+ if (specifierResolvesTo(hit.path, match[1], path)) {
168
+ files.add(hit.path);
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ const sorted = [...files].sort();
174
+ return { count: sorted.length, files: sorted, probe };
48
175
  };
49
176
  }
50
177
 
@@ -72,8 +199,29 @@ function resolveConflictPolicy(cfg) {
72
199
  return policy;
73
200
  }
74
201
 
202
+ /**
203
+ * Resolve the ref the persist gates probe against.
204
+ *
205
+ * The canonical resolved config carries the base branch at
206
+ * `project.baseBranch` (`lib/config-resolver.js` defaults it to `main`).
207
+ * This helper used to read `config.baseBranch` — a key the resolver never
208
+ * produces — so every freshness / file-assumption / fan-out probe silently
209
+ * targeted the literal `main` regardless of configuration. Benign in a repo
210
+ * whose base branch *is* `main`; wrong for any consumer that configured
211
+ * something else (Story #4541).
212
+ *
213
+ * The flat `config.baseBranch` fallback is retained for the legacy
214
+ * `settings`-bag callers that pass `{ baseBranch, paths, planning }`.
215
+ *
216
+ * @param {object} [config] Resolved config, or a legacy settings bag.
217
+ * @returns {string}
218
+ */
219
+ export function resolveBaseBranchRef(config) {
220
+ return config?.project?.baseBranch ?? config?.baseBranch ?? 'main';
221
+ }
222
+
75
223
  export function validateTickets(tickets, config, opts = {}) {
76
- const baseBranchRef = config?.baseBranch ?? 'main';
224
+ const baseBranchRef = resolveBaseBranchRef(config);
77
225
  const conflictPolicy = resolveConflictPolicy(config);
78
226
  if (typeof opts.fanOutCounter === 'function') {
79
227
  conflictPolicy.fanOutCounter = opts.fanOutCounter;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * plan-context-source.js — locate and read the `plan-context.js` envelope so
3
+ * persist can derive the `/plan --tickets` source ids from the run that
4
+ * actually fetched them (Story #4554).
5
+ *
6
+ * This lives beside the persist ops rather than inside `plan-persist.js` so
7
+ * the discovery + failure policy is directly testable: the CLI is a thin
8
+ * `parseArgs` shell, and the interesting behaviour here is exactly the part
9
+ * that decides whether a `--tickets` run can quietly lose its source set.
10
+ *
11
+ * @module lib/orchestration/plan-persist/plan-context-source
12
+ */
13
+
14
+ import { readFile } from 'node:fs/promises';
15
+ import path from 'node:path';
16
+
17
+ import { Logger } from '../../Logger.js';
18
+
19
+ /**
20
+ * Filename the `/plan` interrogate step writes its envelope to inside
21
+ * `--plan-dir` (`plan-context.js --out <plan-dir>/plan-context.json`).
22
+ */
23
+ export const PLAN_CONTEXT_FILENAME = 'plan-context.json';
24
+
25
+ /**
26
+ * Decide where to look for the envelope.
27
+ *
28
+ * An explicit `--plan-context` path wins; otherwise the conventional file
29
+ * inside `--plan-dir`. With neither, there is nothing to read — the caller
30
+ * gets `null` and must warn (see `loadPlanContextEnvelope`).
31
+ *
32
+ * @param {string|null|undefined} explicitPath `--plan-context`.
33
+ * @param {string|null|undefined} planDir `--plan-dir`.
34
+ * @returns {{ path: string, explicit: boolean }|null}
35
+ */
36
+ export function resolvePlanContextPath(explicitPath, planDir) {
37
+ if (explicitPath) {
38
+ return { path: path.resolve(explicitPath), explicit: true };
39
+ }
40
+ if (planDir) {
41
+ return {
42
+ path: path.join(path.resolve(planDir), PLAN_CONTEXT_FILENAME),
43
+ explicit: false,
44
+ };
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * The advice printed whenever persist has no envelope to derive ids from.
51
+ * Single-homed so the two no-envelope paths cannot drift apart.
52
+ */
53
+ const CAPTURE_HINT =
54
+ 'Re-run step 1 with `node .agents/scripts/plan-context.js … --out ' +
55
+ '<plan-dir>/plan-context.json` and pass --plan-dir, or pass ' +
56
+ '--source-tickets explicitly.';
57
+
58
+ /**
59
+ * Read the `plan-context.js` envelope.
60
+ *
61
+ * Failure policy — the point is that a `--tickets` run can never *quietly*
62
+ * lose its source set, so every no-envelope path is audible:
63
+ *
64
+ * - **No path at all** (neither `--plan-dir` nor `--plan-context`): warn.
65
+ * Persist cannot tell a legitimate `--seed` run from a `--tickets` run
66
+ * whose envelope was never captured, so it says so rather than returning a
67
+ * silent `null`.
68
+ * - **Explicit `--plan-context` missing**: throw. The operator named a file
69
+ * and meant it.
70
+ * - **Auto-discovered file simply absent**: warn and degrade to
71
+ * `--source-tickets`. A `--seed` run legitimately has no envelope, so
72
+ * absence alone is not fatal.
73
+ * - **Present but unparseable**: throw either way. A corrupt envelope is not
74
+ * the same as no envelope, and reading it as "no source tickets" is exactly
75
+ * the vacuous pass this module exists to prevent.
76
+ *
77
+ * @param {{ path: string, explicit: boolean }|null} planContext
78
+ * @returns {Promise<object|null>} Parsed envelope, or null when absent.
79
+ */
80
+ export async function loadPlanContextEnvelope(planContext) {
81
+ if (!planContext) {
82
+ Logger.warn(
83
+ '[plan-persist] no --plan-dir or --plan-context given, so no ' +
84
+ 'plan-context envelope was read. If this was a `/plan --tickets` ' +
85
+ 'run, its source tickets can only come from --source-tickets and ' +
86
+ `will NOT be closed otherwise. ${CAPTURE_HINT}`,
87
+ );
88
+ return null;
89
+ }
90
+
91
+ let raw;
92
+ try {
93
+ raw = await readFile(planContext.path, 'utf8');
94
+ } catch (err) {
95
+ if (err?.code === 'ENOENT' && !planContext.explicit) {
96
+ Logger.warn(
97
+ `[plan-persist] no plan-context envelope at ${planContext.path} — ` +
98
+ 'source tickets can only come from --source-tickets. ' +
99
+ CAPTURE_HINT,
100
+ );
101
+ return null;
102
+ }
103
+ throw new Error(
104
+ `Cannot read plan-context envelope ${planContext.path}: ${err.message}`,
105
+ );
106
+ }
107
+
108
+ try {
109
+ return JSON.parse(raw);
110
+ } catch (err) {
111
+ throw new Error(
112
+ `Failed to parse plan-context envelope "${planContext.path}" as JSON: ` +
113
+ `${err.message}. ${CAPTURE_HINT}`,
114
+ );
115
+ }
116
+ }