mandrel 1.57.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 (843) hide show
  1. package/.agents/README.md +954 -0
  2. package/.agents/docs/SDLC.md +1420 -0
  3. package/.agents/docs/agentrc-reference.json +278 -0
  4. package/.agents/docs/configuration.md +1040 -0
  5. package/.agents/docs/workflows.md +59 -0
  6. package/.agents/instructions.md +384 -0
  7. package/.agents/personas/architect.md +107 -0
  8. package/.agents/personas/devops-engineer.md +36 -0
  9. package/.agents/personas/engineer-mobile.md +119 -0
  10. package/.agents/personas/engineer-web.md +110 -0
  11. package/.agents/personas/engineer.md +90 -0
  12. package/.agents/personas/product.md +88 -0
  13. package/.agents/personas/project-manager.md +110 -0
  14. package/.agents/personas/qa-engineer.md +91 -0
  15. package/.agents/personas/refactorer.md +110 -0
  16. package/.agents/personas/security-engineer.md +112 -0
  17. package/.agents/personas/sre.md +86 -0
  18. package/.agents/personas/technical-writer.md +100 -0
  19. package/.agents/personas/ux-designer.md +95 -0
  20. package/.agents/rules/api-conventions.md +75 -0
  21. package/.agents/rules/changelog-style.md +238 -0
  22. package/.agents/rules/gherkin-standards.md +146 -0
  23. package/.agents/rules/git-conventions.md +146 -0
  24. package/.agents/rules/orchestration-error-handling.md +35 -0
  25. package/.agents/rules/security-baseline.md +92 -0
  26. package/.agents/rules/shell-conventions.md +70 -0
  27. package/.agents/rules/test-seams.md +59 -0
  28. package/.agents/rules/testing-standards.md +177 -0
  29. package/.agents/runtime-deps.json +18 -0
  30. package/.agents/schemas/acceptance-eval-verdict.schema.json +93 -0
  31. package/.agents/schemas/agentrc.schema.json +1583 -0
  32. package/.agents/schemas/audit-results.schema.json +69 -0
  33. package/.agents/schemas/audit-rules.json +134 -0
  34. package/.agents/schemas/audit-rules.schema.json +69 -0
  35. package/.agents/schemas/baselines/baseline-envelope.schema.json +44 -0
  36. package/.agents/schemas/baselines/bundle-size.schema.json +47 -0
  37. package/.agents/schemas/baselines/coverage.schema.json +50 -0
  38. package/.agents/schemas/baselines/crap.schema.json +52 -0
  39. package/.agents/schemas/baselines/duplication.schema.json +62 -0
  40. package/.agents/schemas/baselines/lighthouse.schema.json +59 -0
  41. package/.agents/schemas/baselines/lint.schema.json +47 -0
  42. package/.agents/schemas/baselines/maintainability.schema.json +71 -0
  43. package/.agents/schemas/baselines/mutation.schema.json +52 -0
  44. package/.agents/schemas/crap-baseline.schema.json +57 -0
  45. package/.agents/schemas/crap-report.schema.json +102 -0
  46. package/.agents/schemas/dispatch-manifest.json +232 -0
  47. package/.agents/schemas/epic-perf-report.schema.json +89 -0
  48. package/.agents/schemas/epic-spec.schema.json +183 -0
  49. package/.agents/schemas/friction-event.schema.json +56 -0
  50. package/.agents/schemas/lifecycle/README.md +18 -0
  51. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +13 -0
  52. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +13 -0
  53. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +13 -0
  54. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +12 -0
  55. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +13 -0
  56. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +13 -0
  57. package/.agents/schemas/lifecycle/close-validate.end.schema.json +18 -0
  58. package/.agents/schemas/lifecycle/close-validate.start.schema.json +13 -0
  59. package/.agents/schemas/lifecycle/code-review.end.schema.json +30 -0
  60. package/.agents/schemas/lifecycle/code-review.start.schema.json +12 -0
  61. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +14 -0
  62. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +13 -0
  63. package/.agents/schemas/lifecycle/epic.blocked.schema.json +13 -0
  64. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +12 -0
  65. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +12 -0
  66. package/.agents/schemas/lifecycle/epic.close.end.schema.json +12 -0
  67. package/.agents/schemas/lifecycle/epic.complete.schema.json +13 -0
  68. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +13 -0
  69. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +12 -0
  70. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +13 -0
  71. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +14 -0
  72. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +17 -0
  73. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +15 -0
  74. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +18 -0
  75. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +12 -0
  76. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +16 -0
  77. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +12 -0
  78. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +28 -0
  79. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +16 -0
  80. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +15 -0
  81. package/.agents/schemas/lifecycle/ledger-record.schema.json +59 -0
  82. package/.agents/schemas/lifecycle/notification.emitted.schema.json +18 -0
  83. package/.agents/schemas/lifecycle/pr.created.schema.json +14 -0
  84. package/.agents/schemas/lifecycle/retro.end.schema.json +16 -0
  85. package/.agents/schemas/lifecycle/retro.start.schema.json +12 -0
  86. package/.agents/schemas/lifecycle/story.blocked.schema.json +13 -0
  87. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +17 -0
  88. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +15 -0
  89. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +20 -0
  90. package/.agents/schemas/lifecycle/story.merged.schema.json +13 -0
  91. package/.agents/schemas/mi-report.schema.json +58 -0
  92. package/.agents/schemas/model-attribution.schema.json +49 -0
  93. package/.agents/schemas/qa-finding.schema.json +133 -0
  94. package/.agents/schemas/qa-ledger.schema.json +89 -0
  95. package/.agents/schemas/risk-verdict.schema.json +53 -0
  96. package/.agents/schemas/signal-event.schema.json +58 -0
  97. package/.agents/schemas/skill.schema.json +31 -0
  98. package/.agents/schemas/skills-index.schema.json +81 -0
  99. package/.agents/schemas/story-perf-summary.schema.json +73 -0
  100. package/.agents/schemas/validation-evidence.schema.json +78 -0
  101. package/.agents/scripts/README.md +93 -0
  102. package/.agents/scripts/acceptance-eval.js +284 -0
  103. package/.agents/scripts/acceptance-spec-reconciler.js +556 -0
  104. package/.agents/scripts/agents-bootstrap-github.js +634 -0
  105. package/.agents/scripts/analyze-execution.js +369 -0
  106. package/.agents/scripts/assert-branch.js +83 -0
  107. package/.agents/scripts/audit-labels-bootstrap.js +253 -0
  108. package/.agents/scripts/audit-to-stories.js +257 -0
  109. package/.agents/scripts/bootstrap.js +1378 -0
  110. package/.agents/scripts/check-baselines.js +81 -0
  111. package/.agents/scripts/check-dead-exports.js +311 -0
  112. package/.agents/scripts/check-doc-links.js +401 -0
  113. package/.agents/scripts/check-gherkin-placeholders.js +663 -0
  114. package/.agents/scripts/check-lifecycle-doc-drift.js +402 -0
  115. package/.agents/scripts/check-lifecycle-lint.js +379 -0
  116. package/.agents/scripts/check-prepush-recovery.js +90 -0
  117. package/.agents/scripts/check-windows-git-perf.js +138 -0
  118. package/.agents/scripts/cleanup-repo-test-temp.js +67 -0
  119. package/.agents/scripts/coverage-capture.js +112 -0
  120. package/.agents/scripts/detect-merges.js +111 -0
  121. package/.agents/scripts/diagnose-friction.js +257 -0
  122. package/.agents/scripts/diagnose.js +240 -0
  123. package/.agents/scripts/dispatcher.js +295 -0
  124. package/.agents/scripts/drain-pending-cleanup.js +147 -0
  125. package/.agents/scripts/epic-audit-prepare.js +419 -0
  126. package/.agents/scripts/epic-audit-recheck.js +241 -0
  127. package/.agents/scripts/epic-deliver-note-intervention.js +192 -0
  128. package/.agents/scripts/epic-deliver-preflight.js +407 -0
  129. package/.agents/scripts/epic-deliver-prepare.js +383 -0
  130. package/.agents/scripts/epic-execute-record-wave.js +463 -0
  131. package/.agents/scripts/epic-plan-clarity.js +201 -0
  132. package/.agents/scripts/epic-plan-decompose.js +79 -0
  133. package/.agents/scripts/epic-plan-healthcheck.js +363 -0
  134. package/.agents/scripts/epic-plan-spec-validate.js +111 -0
  135. package/.agents/scripts/epic-plan-spec.js +198 -0
  136. package/.agents/scripts/epic-reconcile.js +637 -0
  137. package/.agents/scripts/evidence-gate.js +235 -0
  138. package/.agents/scripts/generate-config-docs.js +516 -0
  139. package/.agents/scripts/generate-lifecycle-docs.js +224 -0
  140. package/.agents/scripts/generate-skills-index.js +252 -0
  141. package/.agents/scripts/generate-workflows-doc.js +168 -0
  142. package/.agents/scripts/git-cleanup.js +124 -0
  143. package/.agents/scripts/git-pr-quality-gate.js +203 -0
  144. package/.agents/scripts/git-rebase-and-resolve.js +234 -0
  145. package/.agents/scripts/hierarchy-gate.js +176 -0
  146. package/.agents/scripts/hydrate-context.js +179 -0
  147. package/.agents/scripts/install-matrix-assert.js +282 -0
  148. package/.agents/scripts/lib/Graph.js +326 -0
  149. package/.agents/scripts/lib/ITicketingProvider.js +349 -0
  150. package/.agents/scripts/lib/Logger.js +194 -0
  151. package/.agents/scripts/lib/audit-suite/cli.js +64 -0
  152. package/.agents/scripts/lib/audit-suite/findings.js +164 -0
  153. package/.agents/scripts/lib/audit-suite/frontmatter-lint.js +32 -0
  154. package/.agents/scripts/lib/audit-suite/frontmatter.js +110 -0
  155. package/.agents/scripts/lib/audit-suite/index.js +22 -0
  156. package/.agents/scripts/lib/audit-suite/runner.js +233 -0
  157. package/.agents/scripts/lib/audit-suite/selector.js +235 -0
  158. package/.agents/scripts/lib/audit-suite/substitutions.js +124 -0
  159. package/.agents/scripts/lib/audit-suite/workflow-loader.js +49 -0
  160. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +130 -0
  161. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +114 -0
  162. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +93 -0
  163. package/.agents/scripts/lib/audit-to-stories/group-findings.js +265 -0
  164. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +246 -0
  165. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +160 -0
  166. package/.agents/scripts/lib/auto-refresh-baselines.js +308 -0
  167. package/.agents/scripts/lib/baseline-loader.js +0 -0
  168. package/.agents/scripts/lib/baseline-schema-registry.js +69 -0
  169. package/.agents/scripts/lib/baseline-snapshot.js +716 -0
  170. package/.agents/scripts/lib/baselines/component-matcher.js +21 -0
  171. package/.agents/scripts/lib/baselines/components.js +126 -0
  172. package/.agents/scripts/lib/baselines/diff-scope-cli.js +203 -0
  173. package/.agents/scripts/lib/baselines/duplication-scanner.js +220 -0
  174. package/.agents/scripts/lib/baselines/env-overrides.js +129 -0
  175. package/.agents/scripts/lib/baselines/envelope.js +368 -0
  176. package/.agents/scripts/lib/baselines/exit-codes.js +89 -0
  177. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  178. package/.agents/scripts/lib/baselines/kernel.js +111 -0
  179. package/.agents/scripts/lib/baselines/kinds/_shared-metric.js +220 -0
  180. package/.agents/scripts/lib/baselines/kinds/bundle-size.js +157 -0
  181. package/.agents/scripts/lib/baselines/kinds/coverage.js +194 -0
  182. package/.agents/scripts/lib/baselines/kinds/crap.js +555 -0
  183. package/.agents/scripts/lib/baselines/kinds/duplication.js +197 -0
  184. package/.agents/scripts/lib/baselines/kinds/lighthouse.js +185 -0
  185. package/.agents/scripts/lib/baselines/kinds/lint.js +172 -0
  186. package/.agents/scripts/lib/baselines/kinds/maintainability.js +340 -0
  187. package/.agents/scripts/lib/baselines/kinds/mutation.js +153 -0
  188. package/.agents/scripts/lib/baselines/path-canon.js +279 -0
  189. package/.agents/scripts/lib/baselines/preview-gates.js +298 -0
  190. package/.agents/scripts/lib/baselines/reader.js +321 -0
  191. package/.agents/scripts/lib/baselines/refresh-service.js +733 -0
  192. package/.agents/scripts/lib/baselines/scope.js +291 -0
  193. package/.agents/scripts/lib/baselines/writer.js +312 -0
  194. package/.agents/scripts/lib/bdd-runner-detect.js +417 -0
  195. package/.agents/scripts/lib/bdd-scenario-scanner.js +310 -0
  196. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +202 -0
  197. package/.agents/scripts/lib/bootstrap/branch-protection.js +222 -0
  198. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +171 -0
  199. package/.agents/scripts/lib/bootstrap/commit-push.js +146 -0
  200. package/.agents/scripts/lib/bootstrap/gh-list.js +153 -0
  201. package/.agents/scripts/lib/bootstrap/gh-preflight.js +306 -0
  202. package/.agents/scripts/lib/bootstrap/hitl-confirm.js +89 -0
  203. package/.agents/scripts/lib/bootstrap/install-ledger.js +174 -0
  204. package/.agents/scripts/lib/bootstrap/manifest.js +272 -0
  205. package/.agents/scripts/lib/bootstrap/merge-methods.js +108 -0
  206. package/.agents/scripts/lib/bootstrap/preflight.js +195 -0
  207. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +801 -0
  208. package/.agents/scripts/lib/bootstrap/prompt.js +480 -0
  209. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +370 -0
  210. package/.agents/scripts/lib/bootstrap/summary.js +75 -0
  211. package/.agents/scripts/lib/bootstrap/workflow-audit.js +256 -0
  212. package/.agents/scripts/lib/branch-name-guard.js +98 -0
  213. package/.agents/scripts/lib/c8-cli-path.js +21 -0
  214. package/.agents/scripts/lib/changed-files.js +184 -0
  215. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +104 -0
  216. package/.agents/scripts/lib/checks/core-bare-clean.js +48 -0
  217. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +54 -0
  218. package/.agents/scripts/lib/checks/index.js +288 -0
  219. package/.agents/scripts/lib/checks/push-hook-parity.js +106 -0
  220. package/.agents/scripts/lib/checks/stale-origin-epic.js +49 -0
  221. package/.agents/scripts/lib/checks/state.js +558 -0
  222. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +186 -0
  223. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +182 -0
  224. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +92 -0
  225. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +81 -0
  226. package/.agents/scripts/lib/checks/worktree-residue-biome.js +55 -0
  227. package/.agents/scripts/lib/cli/parse-numeric.js +60 -0
  228. package/.agents/scripts/lib/cli/standard-args.js +351 -0
  229. package/.agents/scripts/lib/cli-args.js +286 -0
  230. package/.agents/scripts/lib/cli-utils.js +69 -0
  231. package/.agents/scripts/lib/close-validation/projections/head-sha.js +44 -0
  232. package/.agents/scripts/lib/close-validation/projections/inputs.js +86 -0
  233. package/.agents/scripts/lib/close-validation/projections/maintainability.js +286 -0
  234. package/.agents/scripts/lib/close-validation.js +897 -0
  235. package/.agents/scripts/lib/codebase-snapshot.js +513 -0
  236. package/.agents/scripts/lib/command-header.js +33 -0
  237. package/.agents/scripts/lib/config/acceptance-eval.js +95 -0
  238. package/.agents/scripts/lib/config/baselines.js +60 -0
  239. package/.agents/scripts/lib/config/ci.js +30 -0
  240. package/.agents/scripts/lib/config/commands.js +36 -0
  241. package/.agents/scripts/lib/config/defaults.js +119 -0
  242. package/.agents/scripts/lib/config/explain.js +348 -0
  243. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +23 -0
  244. package/.agents/scripts/lib/config/gates/coverage.schema.js +18 -0
  245. package/.agents/scripts/lib/config/gates/crap.schema.js +33 -0
  246. package/.agents/scripts/lib/config/gates/duplication.schema.js +26 -0
  247. package/.agents/scripts/lib/config/gates/index.js +36 -0
  248. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +23 -0
  249. package/.agents/scripts/lib/config/gates/lint.schema.js +9 -0
  250. package/.agents/scripts/lib/config/gates/maintainability.schema.js +20 -0
  251. package/.agents/scripts/lib/config/gates/mutation.schema.js +12 -0
  252. package/.agents/scripts/lib/config/gates/shared.js +117 -0
  253. package/.agents/scripts/lib/config/github.js +122 -0
  254. package/.agents/scripts/lib/config/lifecycle.js +40 -0
  255. package/.agents/scripts/lib/config/limits.js +211 -0
  256. package/.agents/scripts/lib/config/paths.js +73 -0
  257. package/.agents/scripts/lib/config/preflight.js +58 -0
  258. package/.agents/scripts/lib/config/quality.js +665 -0
  259. package/.agents/scripts/lib/config/retro.js +77 -0
  260. package/.agents/scripts/lib/config/runners.js +105 -0
  261. package/.agents/scripts/lib/config/runtime.js +167 -0
  262. package/.agents/scripts/lib/config/shared.js +46 -0
  263. package/.agents/scripts/lib/config/sync-agentrc.js +243 -0
  264. package/.agents/scripts/lib/config/temp-paths.js +373 -0
  265. package/.agents/scripts/lib/config/validate-orchestration.js +81 -0
  266. package/.agents/scripts/lib/config/worktree-isolation.js +80 -0
  267. package/.agents/scripts/lib/config-resolver.js +298 -0
  268. package/.agents/scripts/lib/config-schema-shared.js +32 -0
  269. package/.agents/scripts/lib/config-schema.js +20 -0
  270. package/.agents/scripts/lib/config-settings-schema-delivery.js +332 -0
  271. package/.agents/scripts/lib/config-settings-schema-quality.js +165 -0
  272. package/.agents/scripts/lib/config-settings-schema.js +420 -0
  273. package/.agents/scripts/lib/coverage-baseline.js +352 -0
  274. package/.agents/scripts/lib/coverage-capture.js +195 -0
  275. package/.agents/scripts/lib/coverage-utils.js +239 -0
  276. package/.agents/scripts/lib/cpu-pool.js +223 -0
  277. package/.agents/scripts/lib/crap-engine.js +119 -0
  278. package/.agents/scripts/lib/crap-utils.js +479 -0
  279. package/.agents/scripts/lib/degraded-mode.js +69 -0
  280. package/.agents/scripts/lib/dependency-parser.js +129 -0
  281. package/.agents/scripts/lib/duplicate-search.js +189 -0
  282. package/.agents/scripts/lib/dynamic-workflow/architecture-report-contract.js +70 -0
  283. package/.agents/scripts/lib/dynamic-workflow/audit-orchestrator.js +197 -0
  284. package/.agents/scripts/lib/dynamic-workflow/capability.js +396 -0
  285. package/.agents/scripts/lib/dynamic-workflow/clean-code-report-contract.js +80 -0
  286. package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +72 -0
  287. package/.agents/scripts/lib/dynamic-workflow/quality-report-contract.js +90 -0
  288. package/.agents/scripts/lib/dynamic-workflow/report-contract-core.js +43 -0
  289. package/.agents/scripts/lib/dynamic-workflow/security-report-contract.js +83 -0
  290. package/.agents/scripts/lib/env-loader.js +52 -0
  291. package/.agents/scripts/lib/epic-merge-lock.js +239 -0
  292. package/.agents/scripts/lib/epic-plan-clarity.js +142 -0
  293. package/.agents/scripts/lib/epic-plan-ideation.js +228 -0
  294. package/.agents/scripts/lib/error-redactor.js +125 -0
  295. package/.agents/scripts/lib/errors/index.js +67 -0
  296. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +230 -0
  297. package/.agents/scripts/lib/feedback-loop/code-review-graduator.js +207 -0
  298. package/.agents/scripts/lib/feedback-loop/graduator-core.js +421 -0
  299. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +480 -0
  300. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +229 -0
  301. package/.agents/scripts/lib/findings/classify-finding.js +195 -0
  302. package/.agents/scripts/lib/findings/promote-finding.js +353 -0
  303. package/.agents/scripts/lib/findings/route-finding.js +283 -0
  304. package/.agents/scripts/lib/findings/semantic-issue-search.js +179 -0
  305. package/.agents/scripts/lib/findings/severity.js +102 -0
  306. package/.agents/scripts/lib/gates/baseline-store.js +106 -0
  307. package/.agents/scripts/lib/gates/friction.js +43 -0
  308. package/.agents/scripts/lib/gh-exec.js +553 -0
  309. package/.agents/scripts/lib/git/cached-fetch.js +0 -0
  310. package/.agents/scripts/lib/git/sync-from-base.js +162 -0
  311. package/.agents/scripts/lib/git-branch-cleanup.js +213 -0
  312. package/.agents/scripts/lib/git-branch-lifecycle.js +353 -0
  313. package/.agents/scripts/lib/git-merge-orchestrator.js +261 -0
  314. package/.agents/scripts/lib/git-utils.js +363 -0
  315. package/.agents/scripts/lib/github-url.js +29 -0
  316. package/.agents/scripts/lib/install-cmd-parser.js +51 -0
  317. package/.agents/scripts/lib/issue-link-parser.js +74 -0
  318. package/.agents/scripts/lib/json-utils.js +60 -0
  319. package/.agents/scripts/lib/label-constants.js +169 -0
  320. package/.agents/scripts/lib/label-taxonomy.js +200 -0
  321. package/.agents/scripts/lib/maintainability-engine.js +164 -0
  322. package/.agents/scripts/lib/maintainability-utils.js +343 -0
  323. package/.agents/scripts/lib/mandrel-catalog.js +170 -0
  324. package/.agents/scripts/lib/mutation/baseline-snapshot.js +238 -0
  325. package/.agents/scripts/lib/mutation/config-detector.js +119 -0
  326. package/.agents/scripts/lib/mutation/stryker-runner.js +306 -0
  327. package/.agents/scripts/lib/mutation/survivor-report.js +160 -0
  328. package/.agents/scripts/lib/notifications/notifier.js +75 -0
  329. package/.agents/scripts/lib/observability/active-story-env.js +182 -0
  330. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +221 -0
  331. package/.agents/scripts/lib/observability/perf-aggregator.js +887 -0
  332. package/.agents/scripts/lib/observability/perf-report-readers.js +319 -0
  333. package/.agents/scripts/lib/observability/perf-report-render.js +182 -0
  334. package/.agents/scripts/lib/observability/signals-writer.js +296 -0
  335. package/.agents/scripts/lib/observability/source-classifier.js +103 -0
  336. package/.agents/scripts/lib/observability/tool-trace-hook.js +417 -0
  337. package/.agents/scripts/lib/onboard/detect-stack.js +300 -0
  338. package/.agents/scripts/lib/onboard/scaffold-docs.js +128 -0
  339. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +173 -0
  340. package/.agents/scripts/lib/orchestration/cascade-grouping.js +275 -0
  341. package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +131 -0
  342. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +80 -0
  343. package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +132 -0
  344. package/.agents/scripts/lib/orchestration/check-baselines/phases/friction.js +142 -0
  345. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +149 -0
  346. package/.agents/scripts/lib/orchestration/check-baselines/phases/pipeline.js +158 -0
  347. package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +56 -0
  348. package/.agents/scripts/lib/orchestration/code-review.js +652 -0
  349. package/.agents/scripts/lib/orchestration/column-sync.js +286 -0
  350. package/.agents/scripts/lib/orchestration/context-envelope.js +280 -0
  351. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +581 -0
  352. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +88 -0
  353. package/.agents/scripts/lib/orchestration/detectors-phase.js +188 -0
  354. package/.agents/scripts/lib/orchestration/dispatch-engine.js +144 -0
  355. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +206 -0
  356. package/.agents/scripts/lib/orchestration/doc-reader.js +94 -0
  357. package/.agents/scripts/lib/orchestration/epic-cleanup.js +473 -0
  358. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +310 -0
  359. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/cli.js +167 -0
  360. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +151 -0
  361. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +74 -0
  362. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +78 -0
  363. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +72 -0
  364. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +155 -0
  365. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +321 -0
  366. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +75 -0
  367. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +86 -0
  368. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +235 -0
  369. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +197 -0
  370. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +48 -0
  371. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +94 -0
  372. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +414 -0
  373. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +55 -0
  374. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +105 -0
  375. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +235 -0
  376. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +120 -0
  377. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +118 -0
  378. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +295 -0
  379. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +186 -0
  380. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +50 -0
  381. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +146 -0
  382. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +110 -0
  383. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +392 -0
  384. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +217 -0
  385. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +235 -0
  386. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +69 -0
  387. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +32 -0
  388. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +291 -0
  389. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +175 -0
  390. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +37 -0
  391. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +127 -0
  392. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +400 -0
  393. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +285 -0
  394. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +66 -0
  395. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +797 -0
  396. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +619 -0
  397. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +335 -0
  398. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +230 -0
  399. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +363 -0
  400. package/.agents/scripts/lib/orchestration/error-journal.js +139 -0
  401. package/.agents/scripts/lib/orchestration/file-assumption-enum.js +31 -0
  402. package/.agents/scripts/lib/orchestration/file-assumptions.js +506 -0
  403. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +116 -0
  404. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +241 -0
  405. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +489 -0
  406. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +88 -0
  407. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-reap.js +219 -0
  408. package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +309 -0
  409. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +99 -0
  410. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +123 -0
  411. package/.agents/scripts/lib/orchestration/git-cleanup/phases/filters.js +57 -0
  412. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +114 -0
  413. package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +426 -0
  414. package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +84 -0
  415. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +365 -0
  416. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prompts.js +72 -0
  417. package/.agents/scripts/lib/orchestration/git-cleanup/phases/prune.js +69 -0
  418. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +214 -0
  419. package/.agents/scripts/lib/orchestration/git-cleanup/phases/stashes.js +137 -0
  420. package/.agents/scripts/lib/orchestration/label-transitions.js +43 -0
  421. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +309 -0
  422. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +147 -0
  423. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +155 -0
  424. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +226 -0
  425. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +69 -0
  426. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +378 -0
  427. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +248 -0
  428. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +527 -0
  429. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +259 -0
  430. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +278 -0
  431. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +355 -0
  432. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +647 -0
  433. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +331 -0
  434. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +140 -0
  435. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +421 -0
  436. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +168 -0
  437. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +668 -0
  438. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +322 -0
  439. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +114 -0
  440. package/.agents/scripts/lib/orchestration/manifest-builder.js +216 -0
  441. package/.agents/scripts/lib/orchestration/model-attribution.js +390 -0
  442. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +147 -0
  443. package/.agents/scripts/lib/orchestration/phase-runner.js +87 -0
  444. package/.agents/scripts/lib/orchestration/plan-review-routing.js +63 -0
  445. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +86 -0
  446. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +212 -0
  447. package/.agents/scripts/lib/orchestration/planning-context-budget.js +213 -0
  448. package/.agents/scripts/lib/orchestration/planning-risk.js +155 -0
  449. package/.agents/scripts/lib/orchestration/planning-state-manager.js +318 -0
  450. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +56 -0
  451. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +33 -0
  452. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +78 -0
  453. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +68 -0
  454. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +118 -0
  455. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +396 -0
  456. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +205 -0
  457. package/.agents/scripts/lib/orchestration/pr-base-guard.js +47 -0
  458. package/.agents/scripts/lib/orchestration/preflight-cache.js +164 -0
  459. package/.agents/scripts/lib/orchestration/reassert-status-column.js +202 -0
  460. package/.agents/scripts/lib/orchestration/reconciler.js +137 -0
  461. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +152 -0
  462. package/.agents/scripts/lib/orchestration/recut.js +56 -0
  463. package/.agents/scripts/lib/orchestration/resolves-token.js +127 -0
  464. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +94 -0
  465. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +448 -0
  466. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +335 -0
  467. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +133 -0
  468. package/.agents/scripts/lib/orchestration/retro-heuristics.js +57 -0
  469. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +275 -0
  470. package/.agents/scripts/lib/orchestration/retro-proposals.js +395 -0
  471. package/.agents/scripts/lib/orchestration/retro-runner.js +171 -0
  472. package/.agents/scripts/lib/orchestration/review-depth.js +93 -0
  473. package/.agents/scripts/lib/orchestration/review-providers/codex.js +363 -0
  474. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +205 -0
  475. package/.agents/scripts/lib/orchestration/review-providers/native.js +805 -0
  476. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +73 -0
  477. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +396 -0
  478. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +373 -0
  479. package/.agents/scripts/lib/orchestration/review-providers/types.js +89 -0
  480. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +107 -0
  481. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +159 -0
  482. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +194 -0
  483. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +81 -0
  484. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +190 -0
  485. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +70 -0
  486. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +106 -0
  487. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +42 -0
  488. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +73 -0
  489. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +225 -0
  490. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +315 -0
  491. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +149 -0
  492. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +110 -0
  493. package/.agents/scripts/lib/orchestration/spec-freshness.js +320 -0
  494. package/.agents/scripts/lib/orchestration/spec-renderer.js +456 -0
  495. package/.agents/scripts/lib/orchestration/spec-section-validator.js +80 -0
  496. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +797 -0
  497. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +163 -0
  498. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +152 -0
  499. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +387 -0
  500. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +266 -0
  501. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +48 -0
  502. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +67 -0
  503. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +161 -0
  504. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +117 -0
  505. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +86 -0
  506. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +147 -0
  507. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +142 -0
  508. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +62 -0
  509. package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +221 -0
  510. package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +123 -0
  511. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +216 -0
  512. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +636 -0
  513. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +198 -0
  514. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +105 -0
  515. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +222 -0
  516. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +220 -0
  517. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +291 -0
  518. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +234 -0
  519. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +110 -0
  520. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +86 -0
  521. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +112 -0
  522. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +157 -0
  523. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +434 -0
  524. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +290 -0
  525. package/.agents/scripts/lib/orchestration/story-close-recovery.js +643 -0
  526. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +67 -0
  527. package/.agents/scripts/lib/orchestration/task-body-validator.js +391 -0
  528. package/.agents/scripts/lib/orchestration/ticket-lease.js +358 -0
  529. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +783 -0
  530. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +367 -0
  531. package/.agents/scripts/lib/orchestration/ticket-validator.js +691 -0
  532. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +723 -0
  533. package/.agents/scripts/lib/orchestration/ticketing/reads.js +474 -0
  534. package/.agents/scripts/lib/orchestration/ticketing/state.js +559 -0
  535. package/.agents/scripts/lib/orchestration/ticketing.js +55 -0
  536. package/.agents/scripts/lib/orchestration/wave-marker.js +28 -0
  537. package/.agents/scripts/lib/orchestration/wave-record-io.js +277 -0
  538. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +189 -0
  539. package/.agents/scripts/lib/orchestration/wave-record-projection.js +423 -0
  540. package/.agents/scripts/lib/path-security.js +25 -0
  541. package/.agents/scripts/lib/plan-phase-cleanup.js +125 -0
  542. package/.agents/scripts/lib/preflight-runner.js +196 -0
  543. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +95 -0
  544. package/.agents/scripts/lib/presentation/manifest-builder.js +245 -0
  545. package/.agents/scripts/lib/presentation/manifest-formatter.js +243 -0
  546. package/.agents/scripts/lib/presentation/manifest-helpers.js +213 -0
  547. package/.agents/scripts/lib/presentation/manifest-persistence.js +262 -0
  548. package/.agents/scripts/lib/presentation/manifest-procedures.js +55 -0
  549. package/.agents/scripts/lib/presentation/manifest-render-waves.js +252 -0
  550. package/.agents/scripts/lib/presentation/manifest-renderer.js +188 -0
  551. package/.agents/scripts/lib/presentation/manifest-story-views.js +119 -0
  552. package/.agents/scripts/lib/provider-factory.js +80 -0
  553. package/.agents/scripts/lib/push-epic-retry.js +209 -0
  554. package/.agents/scripts/lib/qa/console-allowlist.js +151 -0
  555. package/.agents/scripts/lib/qa/coverage-report.js +181 -0
  556. package/.agents/scripts/lib/qa/coverage-verdict.js +296 -0
  557. package/.agents/scripts/lib/qa/propose-missing-test.js +95 -0
  558. package/.agents/scripts/lib/qa/qa-context-hydrator.js +296 -0
  559. package/.agents/scripts/lib/qa/qa-session.js +197 -0
  560. package/.agents/scripts/lib/qa/redact-evidence.js +245 -0
  561. package/.agents/scripts/lib/qa/resolve-qa-contract.js +190 -0
  562. package/.agents/scripts/lib/qa/resolve-selection.js +373 -0
  563. package/.agents/scripts/lib/runtime-deps/ensure-installed.js +100 -0
  564. package/.agents/scripts/lib/runtime-deps/manifest.js +96 -0
  565. package/.agents/scripts/lib/runtime-deps/preflight.js +78 -0
  566. package/.agents/scripts/lib/runtime-deps/scan-imports.js +202 -0
  567. package/.agents/scripts/lib/signals/detectors/common.js +36 -0
  568. package/.agents/scripts/lib/signals/detectors/hotspot.js +298 -0
  569. package/.agents/scripts/lib/signals/detectors/index.js +14 -0
  570. package/.agents/scripts/lib/signals/detectors/retry.js +289 -0
  571. package/.agents/scripts/lib/signals/detectors/rework.js +204 -0
  572. package/.agents/scripts/lib/signals/index.js +39 -0
  573. package/.agents/scripts/lib/signals/read.js +268 -0
  574. package/.agents/scripts/lib/signals/schema.js +225 -0
  575. package/.agents/scripts/lib/signals/span-tree.js +290 -0
  576. package/.agents/scripts/lib/signals/write.js +19 -0
  577. package/.agents/scripts/lib/single-story/confirm-merge.js +201 -0
  578. package/.agents/scripts/lib/single-story/story-merged-notify.js +126 -0
  579. package/.agents/scripts/lib/single-story-sweep/protection.js +274 -0
  580. package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +169 -0
  581. package/.agents/scripts/lib/single-story-sweep.js +329 -0
  582. package/.agents/scripts/lib/skills/parse-skill.js +202 -0
  583. package/.agents/scripts/lib/skills/walk-skill-files.js +56 -0
  584. package/.agents/scripts/lib/spec/index.js +36 -0
  585. package/.agents/scripts/lib/spec/loader.js +425 -0
  586. package/.agents/scripts/lib/spec/state.js +217 -0
  587. package/.agents/scripts/lib/story-body/story-body.js +743 -0
  588. package/.agents/scripts/lib/story-init/blocker-validator.js +68 -0
  589. package/.agents/scripts/lib/story-init/branch-initializer.js +422 -0
  590. package/.agents/scripts/lib/story-init/context-resolver.js +92 -0
  591. package/.agents/scripts/lib/story-init/donor-precheck.js +207 -0
  592. package/.agents/scripts/lib/story-init/hierarchy-tracer.js +36 -0
  593. package/.agents/scripts/lib/story-init/state-transitioner.js +80 -0
  594. package/.agents/scripts/lib/story-init/task-graph-builder.js +114 -0
  595. package/.agents/scripts/lib/story-init/transition-summary.js +34 -0
  596. package/.agents/scripts/lib/story-lifecycle.js +186 -0
  597. package/.agents/scripts/lib/story-plan.js +246 -0
  598. package/.agents/scripts/lib/task-utils.js +26 -0
  599. package/.agents/scripts/lib/templates/decomposer-prompts.js +168 -0
  600. package/.agents/scripts/lib/test-env.js +30 -0
  601. package/.agents/scripts/lib/test-isolate/env-snapshot-loader.js +52 -0
  602. package/.agents/scripts/lib/test-isolate/list-files.js +90 -0
  603. package/.agents/scripts/lib/test-isolate/parse-tap.js +75 -0
  604. package/.agents/scripts/lib/test-isolate/runner.js +483 -0
  605. package/.agents/scripts/lib/test-profile/parse-tap.js +136 -0
  606. package/.agents/scripts/lib/test-profile/render-report.js +45 -0
  607. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +35 -0
  608. package/.agents/scripts/lib/test-tiers.js +94 -0
  609. package/.agents/scripts/lib/util/concurrent-map.js +59 -0
  610. package/.agents/scripts/lib/util/phase-timer-state.js +72 -0
  611. package/.agents/scripts/lib/util/phase-timer.js +163 -0
  612. package/.agents/scripts/lib/util/poll-loop.js +86 -0
  613. package/.agents/scripts/lib/util/with-timeout.js +32 -0
  614. package/.agents/scripts/lib/validation-evidence.js +323 -0
  615. package/.agents/scripts/lib/wave-runner/tick.js +665 -0
  616. package/.agents/scripts/lib/wave-runner/wave-checkpoint.js +91 -0
  617. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +19 -0
  618. package/.agents/scripts/lib/workers/crap-worker.js +197 -0
  619. package/.agents/scripts/lib/workers/maintainability-report-worker.js +137 -0
  620. package/.agents/scripts/lib/workers/maintainability-worker.js +79 -0
  621. package/.agents/scripts/lib/workspace-provisioner.js +189 -0
  622. package/.agents/scripts/lib/worktree/bootstrapper.js +48 -0
  623. package/.agents/scripts/lib/worktree/inspector.js +140 -0
  624. package/.agents/scripts/lib/worktree/lifecycle/creation.js +118 -0
  625. package/.agents/scripts/lib/worktree/lifecycle/drift-detection.js +62 -0
  626. package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +276 -0
  627. package/.agents/scripts/lib/worktree/lifecycle/gc.js +49 -0
  628. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +178 -0
  629. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +264 -0
  630. package/.agents/scripts/lib/worktree/lifecycle/precheck.js +100 -0
  631. package/.agents/scripts/lib/worktree/lifecycle/reap.js +588 -0
  632. package/.agents/scripts/lib/worktree/lifecycle/registry-sync.js +124 -0
  633. package/.agents/scripts/lib/worktree/lifecycle/shared.js +26 -0
  634. package/.agents/scripts/lib/worktree/lifecycle-manager.js +40 -0
  635. package/.agents/scripts/lib/worktree/node-modules-strategy.js +349 -0
  636. package/.agents/scripts/lib/worktree-manager.js +243 -0
  637. package/.agents/scripts/lifecycle-diff.js +206 -0
  638. package/.agents/scripts/lifecycle-emit-story-dispatch.js +194 -0
  639. package/.agents/scripts/lifecycle-emit.js +479 -0
  640. package/.agents/scripts/lint-baseline.js +507 -0
  641. package/.agents/scripts/lint-label-vocabulary.js +237 -0
  642. package/.agents/scripts/loc-delta.js +205 -0
  643. package/.agents/scripts/notify.js +307 -0
  644. package/.agents/scripts/package.json +3 -0
  645. package/.agents/scripts/post-structured-comment.js +127 -0
  646. package/.agents/scripts/pr-watch-with-update.js +152 -0
  647. package/.agents/scripts/providers/github/auth.js +65 -0
  648. package/.agents/scripts/providers/github/board-add.js +63 -0
  649. package/.agents/scripts/providers/github/branch-protection.js +186 -0
  650. package/.agents/scripts/providers/github/cache.js +72 -0
  651. package/.agents/scripts/providers/github/comments.js +131 -0
  652. package/.agents/scripts/providers/github/compose.js +111 -0
  653. package/.agents/scripts/providers/github/errors.js +242 -0
  654. package/.agents/scripts/providers/github/issues.js +242 -0
  655. package/.agents/scripts/providers/github/labels.js +179 -0
  656. package/.agents/scripts/providers/github/mappers.js +126 -0
  657. package/.agents/scripts/providers/github/merge-methods.js +82 -0
  658. package/.agents/scripts/providers/github/project-board.js +47 -0
  659. package/.agents/scripts/providers/github/projects-v2-graphql.js +472 -0
  660. package/.agents/scripts/providers/github/prs.js +103 -0
  661. package/.agents/scripts/providers/github/request-helpers.js +110 -0
  662. package/.agents/scripts/providers/github/sub-issues.js +369 -0
  663. package/.agents/scripts/providers/github/tickets.js +381 -0
  664. package/.agents/scripts/providers/github/transient-retry.js +62 -0
  665. package/.agents/scripts/providers/github.js +157 -0
  666. package/.agents/scripts/quality-preview.js +327 -0
  667. package/.agents/scripts/quality-watch.js +223 -0
  668. package/.agents/scripts/render-manifest.js +143 -0
  669. package/.agents/scripts/resync-status-column.js +176 -0
  670. package/.agents/scripts/retro-run.js +167 -0
  671. package/.agents/scripts/run-audit-suite.js +97 -0
  672. package/.agents/scripts/run-coverage.js +103 -0
  673. package/.agents/scripts/run-lint.js +94 -0
  674. package/.agents/scripts/run-test-profile.js +126 -0
  675. package/.agents/scripts/run-tests.js +185 -0
  676. package/.agents/scripts/run-verify.js +56 -0
  677. package/.agents/scripts/select-audits.js +155 -0
  678. package/.agents/scripts/signals-view.js +294 -0
  679. package/.agents/scripts/single-story-close.js +83 -0
  680. package/.agents/scripts/single-story-confirm-merge.js +183 -0
  681. package/.agents/scripts/single-story-init.js +692 -0
  682. package/.agents/scripts/stories-wave-tick.js +415 -0
  683. package/.agents/scripts/story-close.js +246 -0
  684. package/.agents/scripts/story-deliver-prepare.js +267 -0
  685. package/.agents/scripts/story-init.js +516 -0
  686. package/.agents/scripts/story-phase.js +327 -0
  687. package/.agents/scripts/story-plan.js +284 -0
  688. package/.agents/scripts/sync-agentrc.js +71 -0
  689. package/.agents/scripts/sync-branch-from-base.js +138 -0
  690. package/.agents/scripts/sync-claude-commands.js +151 -0
  691. package/.agents/scripts/test-isolate.js +222 -0
  692. package/.agents/scripts/test-wrapper.js +108 -0
  693. package/.agents/scripts/update-coverage-baseline.js +129 -0
  694. package/.agents/scripts/update-crap-baseline.js +177 -0
  695. package/.agents/scripts/update-duplication-baseline.js +134 -0
  696. package/.agents/scripts/update-maintainability-baseline.js +183 -0
  697. package/.agents/scripts/update-mutation-baseline.js +189 -0
  698. package/.agents/scripts/update-ticket-state.js +107 -0
  699. package/.agents/scripts/validate-docs-freshness.js +259 -0
  700. package/.agents/scripts/validate-skills.js +278 -0
  701. package/.agents/scripts/wave-tick.js +335 -0
  702. package/.agents/skills/core/analyze-execution/SKILL.md +98 -0
  703. package/.agents/skills/core/api-and-interface-design/SKILL.md +327 -0
  704. package/.agents/skills/core/baseline-refresh/SKILL.md +181 -0
  705. package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +352 -0
  706. package/.agents/skills/core/ci-cd-and-automation/SKILL.md +274 -0
  707. package/.agents/skills/core/ci-cd-and-automation/examples.md +211 -0
  708. package/.agents/skills/core/code-review-and-quality/SKILL.md +421 -0
  709. package/.agents/skills/core/code-simplification/SKILL.md +389 -0
  710. package/.agents/skills/core/context-engineering/SKILL.md +309 -0
  711. package/.agents/skills/core/context-engineering/examples.md +58 -0
  712. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +338 -0
  713. package/.agents/skills/core/deprecation-and-migration/SKILL.md +250 -0
  714. package/.agents/skills/core/diagnose-friction/SKILL.md +79 -0
  715. package/.agents/skills/core/documentation-and-adrs/SKILL.md +323 -0
  716. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +145 -0
  717. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +425 -0
  718. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +393 -0
  719. package/.agents/skills/core/frontend-ui-engineering/SKILL.md +357 -0
  720. package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +352 -0
  721. package/.agents/skills/core/hydrate-context/SKILL.md +118 -0
  722. package/.agents/skills/core/idea-refinement/SKILL.md +317 -0
  723. package/.agents/skills/core/idea-refinement/examples.md +437 -0
  724. package/.agents/skills/core/idea-refinement/frameworks.md +135 -0
  725. package/.agents/skills/core/idea-refinement/refinement-criteria.md +155 -0
  726. package/.agents/skills/core/idea-refinement/scripts/idea-refine.sh +15 -0
  727. package/.agents/skills/core/incremental-implementation/SKILL.md +271 -0
  728. package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +213 -0
  729. package/.agents/skills/core/knowledge-transfer/SKILL.md +175 -0
  730. package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +117 -0
  731. package/.agents/skills/core/performance-optimization/SKILL.md +314 -0
  732. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +277 -0
  733. package/.agents/skills/core/property-based-testing/SKILL.md +148 -0
  734. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +105 -0
  735. package/.agents/skills/core/refactoring-discipline/SKILL.md +111 -0
  736. package/.agents/skills/core/scope-triage/SKILL.md +127 -0
  737. package/.agents/skills/core/security-and-hardening/SKILL.md +400 -0
  738. package/.agents/skills/core/shipping-and-launch/SKILL.md +328 -0
  739. package/.agents/skills/core/spec-driven-development/SKILL.md +252 -0
  740. package/.agents/skills/core/test-driven-development/SKILL.md +475 -0
  741. package/.agents/skills/core/using-agent-skills/SKILL.md +232 -0
  742. package/.agents/skills/skills.index.json +596 -0
  743. package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +31 -0
  744. package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +51 -0
  745. package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +48 -0
  746. package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +31 -0
  747. package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +33 -0
  748. package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +31 -0
  749. package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +51 -0
  750. package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +54 -0
  751. package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +29 -0
  752. package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +30 -0
  753. package/.agents/skills/stack/backend/stripe-integration/SKILL.md +57 -0
  754. package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +9 -0
  755. package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +48 -0
  756. package/.agents/skills/stack/frontend/astro/SKILL.md +62 -0
  757. package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +30 -0
  758. package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +29 -0
  759. package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +50 -0
  760. package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +58 -0
  761. package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +34 -0
  762. package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +51 -0
  763. package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +257 -0
  764. package/.agents/skills/stack/qa/gherkin-authoring/examples/invoice-issue.feature +41 -0
  765. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +199 -0
  766. package/.agents/skills/stack/qa/playwright/SKILL.md +50 -0
  767. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +188 -0
  768. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +142 -0
  769. package/.agents/skills/stack/qa/qa-harness/SKILL.md +220 -0
  770. package/.agents/skills/stack/qa/vitest/SKILL.md +51 -0
  771. package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +68 -0
  772. package/.agents/starter-agentrc.json +22 -0
  773. package/.agents/templates/agent-protocol.md +72 -0
  774. package/.agents/templates/docs/architecture.md +30 -0
  775. package/.agents/templates/docs/decisions.md +24 -0
  776. package/.agents/templates/epic-from-idea.md +21 -0
  777. package/.agents/templates/single-story-body.md +17 -0
  778. package/.agents/workflows/agents-update.md +415 -0
  779. package/.agents/workflows/audit-architecture.md +312 -0
  780. package/.agents/workflows/audit-clean-code.md +179 -0
  781. package/.agents/workflows/audit-dependencies.md +91 -0
  782. package/.agents/workflows/audit-devops.md +110 -0
  783. package/.agents/workflows/audit-lighthouse.md +260 -0
  784. package/.agents/workflows/audit-performance.md +161 -0
  785. package/.agents/workflows/audit-privacy.md +104 -0
  786. package/.agents/workflows/audit-quality.md +191 -0
  787. package/.agents/workflows/audit-security.md +156 -0
  788. package/.agents/workflows/audit-seo.md +118 -0
  789. package/.agents/workflows/audit-sre.md +139 -0
  790. package/.agents/workflows/audit-to-stories.md +257 -0
  791. package/.agents/workflows/audit-ux-ui.md +102 -0
  792. package/.agents/workflows/epic-deliver.md +864 -0
  793. package/.agents/workflows/epic-plan.md +998 -0
  794. package/.agents/workflows/explain.md +118 -0
  795. package/.agents/workflows/git-cleanup.md +250 -0
  796. package/.agents/workflows/git-commit-all.md +15 -0
  797. package/.agents/workflows/git-merge-pr.md +377 -0
  798. package/.agents/workflows/git-pr-all.md +278 -0
  799. package/.agents/workflows/git-push.md +60 -0
  800. package/.agents/workflows/helpers/_merge-conflict-template.md +54 -0
  801. package/.agents/workflows/helpers/acceptance-self-eval.md +74 -0
  802. package/.agents/workflows/helpers/agents-sync-config.md +129 -0
  803. package/.agents/workflows/helpers/code-quality-guardrails.md +101 -0
  804. package/.agents/workflows/helpers/code-review.md +370 -0
  805. package/.agents/workflows/helpers/diagnose.md +117 -0
  806. package/.agents/workflows/helpers/epic-audit.md +295 -0
  807. package/.agents/workflows/helpers/epic-deliver-story.md +370 -0
  808. package/.agents/workflows/helpers/epic-plan-decompose.md +199 -0
  809. package/.agents/workflows/helpers/epic-plan-spec.md +184 -0
  810. package/.agents/workflows/helpers/epic-testing.md +125 -0
  811. package/.agents/workflows/helpers/parallel-tooling.md +88 -0
  812. package/.agents/workflows/helpers/signals.md +112 -0
  813. package/.agents/workflows/helpers/single-story-deliver.md +636 -0
  814. package/.agents/workflows/helpers/worktree-lifecycle.md +317 -0
  815. package/.agents/workflows/onboard.md +207 -0
  816. package/.agents/workflows/qa-assist.md +293 -0
  817. package/.agents/workflows/qa-explore.md +350 -0
  818. package/.agents/workflows/qa-run-harness.md +288 -0
  819. package/.agents/workflows/story-deliver.md +327 -0
  820. package/.agents/workflows/story-plan.md +233 -0
  821. package/LICENSE +21 -0
  822. package/README.md +193 -0
  823. package/bin/mandrel.js +56 -0
  824. package/bin/postinstall.js +195 -0
  825. package/lib/cli/__tests__/migrate.test.js +268 -0
  826. package/lib/cli/__tests__/sync-local-zone.test.js +247 -0
  827. package/lib/cli/__tests__/sync.test.js +372 -0
  828. package/lib/cli/__tests__/update-major.test.js +217 -0
  829. package/lib/cli/__tests__/update.test.js +696 -0
  830. package/lib/cli/__tests__/version-check.test.js +398 -0
  831. package/lib/cli/doctor.js +124 -0
  832. package/lib/cli/explain.js +107 -0
  833. package/lib/cli/migrate.js +260 -0
  834. package/lib/cli/registry.js +830 -0
  835. package/lib/cli/sync-commands.js +50 -0
  836. package/lib/cli/sync.js +200 -0
  837. package/lib/cli/uninstall.js +795 -0
  838. package/lib/cli/update.js +854 -0
  839. package/lib/cli/version-check.js +206 -0
  840. package/lib/migrations/README.md +69 -0
  841. package/lib/migrations/__tests__/index.test.js +216 -0
  842. package/lib/migrations/index.js +164 -0
  843. package/package.json +105 -0
@@ -0,0 +1,1378 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * bootstrap.js — single-command consumer setup for Mandrel.
4
+ *
5
+ * The sole bootstrap orchestrator (Story #3690 collapsed the temporary
6
+ * bootstrap-new.js fork into this file). Key behaviours:
7
+ * - No config profiles — `.agentrc.json` always seeds from the bundled
8
+ * `.agents/starter-agentrc.json` starter reference.
9
+ * - Runs even when the directory is NOT a git repo yet (preflight detects
10
+ * git state instead of failing on it).
11
+ * - Adds a Projects V2 permission check to preflight (warns rather than
12
+ * failing when classic token scopes cannot be read, e.g. fine-grained
13
+ * PATs).
14
+ * - Uses a plain summary + confirm loop (interactive runs can go back and
15
+ * re-answer) instead of a phased-approval manifest.
16
+ * - Provisions the missing pieces of a cold start: initializes the local
17
+ * git repo (with a first commit) when absent, creates the GitHub repo
18
+ * (linking + pushing the local tree), and creates the Projects V2 board
19
+ * from a typed name — capturing its number for the rest of the run.
20
+ *
21
+ * Usage:
22
+ * node .agents/scripts/bootstrap.js [flags]
23
+ *
24
+ * Flags:
25
+ * --owner <name> GitHub owner (default: parsed from origin remote)
26
+ * --repo <name> GitHub repo (default: parsed from origin remote)
27
+ * --visibility <v> Visibility for a newly created repo:
28
+ * private | public | internal (default: private)
29
+ * --operator-handle <name> GitHub handle for github.operatorHandle
30
+ * --base-branch <name> Base branch (default: origin/HEAD or 'main')
31
+ * --project-number <n> Projects V2 number/name (optional)
32
+ * --assume-yes Accept every default + approve GitHub-admin
33
+ * mutations. A non-TTY run requires this (or
34
+ * --approve-github-admin) — there is no operator
35
+ * to confirm the summary.
36
+ * --approve-github-admin Consent to the irreversible GitHub-admin phase
37
+ * (labels, Projects V2, branch protection, merge
38
+ * methods) without accepting every other default.
39
+ * --skip-github Skip the GitHub-side bootstrap entirely
40
+ * --skip-quality Skip the quality-gates bootstrap
41
+ * --dry-run Collect info and print the plan; change nothing
42
+ * --reap-conflicting-workflows Delete Projects V2 built-in workflows that
43
+ * race against the orchestrator (destructive)
44
+ * --help Print this help
45
+ */
46
+
47
+ import { spawnSync } from 'node:child_process';
48
+ import fs from 'node:fs';
49
+ import path from 'node:path';
50
+ import readline from 'node:readline/promises';
51
+ import { fileURLToPath } from 'node:url';
52
+
53
+ // Reused bootstrap library helpers (unchanged).
54
+ import {
55
+ buildManualInstructions,
56
+ COMMIT_SUBJECT,
57
+ resolveStagePaths,
58
+ stageBootstrapFiles,
59
+ } from './lib/bootstrap/commit-push.js';
60
+ import { listProjects, listRepos } from './lib/bootstrap/gh-list.js';
61
+ import {
62
+ buildLedgerRecord,
63
+ writeInstallLedger,
64
+ } from './lib/bootstrap/install-ledger.js';
65
+ import {
66
+ buildMutationManifest,
67
+ PHASE_GROUPS,
68
+ } from './lib/bootstrap/manifest.js';
69
+ import { runPreflight } from './lib/bootstrap/preflight.js';
70
+ import { applyProjectBootstrap } from './lib/bootstrap/project-bootstrap.js';
71
+ import {
72
+ collectAnswers,
73
+ inferDefaults,
74
+ parseFlags,
75
+ } from './lib/bootstrap/prompt.js';
76
+ import { runAsCli } from './lib/cli-utils.js';
77
+ import { exec, GhNotFoundError } from './lib/gh-exec.js';
78
+ import { Logger } from './lib/Logger.js';
79
+
80
+ const HELP = `bootstrap.js — single-command consumer setup for Mandrel.
81
+
82
+ Usage: node .agents/scripts/bootstrap.js [flags]
83
+
84
+ Flags:
85
+ --owner <name> GitHub owner (default: parsed from origin remote)
86
+ --repo <name> GitHub repo (default: parsed from origin remote)
87
+ --visibility <v> Visibility for a newly created repo:
88
+ private | public | internal (default: private)
89
+ --operator-handle <name> GitHub handle for github.operatorHandle
90
+ --base-branch <name> Base branch (default: origin/HEAD or 'main')
91
+ --project-number <n> Projects V2 number/name (optional)
92
+ --assume-yes Accept every default + approve GitHub-admin
93
+ mutations. A non-TTY run requires this (or
94
+ --approve-github-admin) — there is no operator
95
+ to confirm the summary.
96
+ --approve-github-admin Consent to the irreversible GitHub-admin phase
97
+ (labels, Projects V2, branch protection, merge
98
+ methods) without accepting every other default.
99
+ --skip-github Skip the GitHub-side bootstrap entirely
100
+ --skip-quality Skip the quality-gates bootstrap
101
+ --dry-run Collect info and print the plan; change nothing
102
+ --reap-conflicting-workflows Delete Projects V2 built-in workflows that
103
+ race against the orchestrator (destructive)
104
+ --help Print this help
105
+ `;
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Small helpers
109
+ // ---------------------------------------------------------------------------
110
+
111
+ /** Strip an `owner/` prefix off a repo slug, leaving the bare repo name. */
112
+ function bareRepoName(slug) {
113
+ const slash = slug.indexOf('/');
114
+ return slash === -1 ? slug : slug.slice(slash + 1);
115
+ }
116
+
117
+ /**
118
+ * Normalize an operator handle to the bare login the starter template expects.
119
+ *
120
+ * The starter `.agentrc.json` carries `"operatorHandle": "@[USERNAME]"` and the
121
+ * seed step substitutes `[USERNAME]` with this answer — so the answer MUST be
122
+ * the bare handle (no leading `@`), or the seeded value becomes `@@foo`. The
123
+ * interactive validator already rejects a leading `@`, but the
124
+ * `--operator-handle @x` flag and `GH_OPERATOR_HANDLE=@x` env paths skip that
125
+ * validator (Story #3700). Stripping a single leading `@` here closes that gap
126
+ * for every resolution path. Idempotent: a bare handle is returned unchanged,
127
+ * so a re-run never re-strips or re-accumulates.
128
+ *
129
+ * @param {string|undefined|null} handle
130
+ * @returns {string|undefined|null} the input with a single leading `@` removed
131
+ */
132
+ export function normalizeHandleAnswer(handle) {
133
+ if (typeof handle !== 'string') return handle;
134
+ return handle.replace(/^@/, '');
135
+ }
136
+
137
+ /** Run a list-producing fn, returning [] on any throw. */
138
+ function safeList(fn) {
139
+ try {
140
+ return fn() ?? [];
141
+ } catch {
142
+ return [];
143
+ }
144
+ }
145
+
146
+ /** Resolve the GitHub owner for the pickers: flag → env → inferred default. */
147
+ export function resolveOwnerForPicker(defaults, flags, env = process.env) {
148
+ if (typeof flags?.owner === 'string' && flags.owner.length > 0) {
149
+ return flags.owner;
150
+ }
151
+ if (typeof env?.GH_OWNER === 'string' && env.GH_OWNER.length > 0) {
152
+ return env.GH_OWNER;
153
+ }
154
+ if (typeof defaults?.owner === 'string' && defaults.owner.length > 0) {
155
+ return defaults.owner;
156
+ }
157
+ return null;
158
+ }
159
+
160
+ /** Ask a yes/no question. Non-interactive runs auto-accept (return true). */
161
+ async function confirmYesNo(message, interactive) {
162
+ if (!interactive) return true;
163
+ const rl = readline.createInterface({
164
+ input: process.stdin,
165
+ output: process.stdout,
166
+ });
167
+ try {
168
+ const raw = (await rl.question(`${message} [Y/n]: `)).trim().toLowerCase();
169
+ return raw === '' || raw === 'y' || raw === 'yes';
170
+ } finally {
171
+ rl.close();
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Run a git command in `cwd`. Returns the normalized
177
+ * `{ ok, status, stdout, stderr, error }` shape (mirroring the bootstrap
178
+ * preflight/gh-list runners) so callers branch on `ok` without juggling
179
+ * spawnSync internals.
180
+ */
181
+ function runGit(args, cwd) {
182
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
183
+ return {
184
+ ok: !result.error && result.status === 0,
185
+ status: result.status,
186
+ stdout: typeof result.stdout === 'string' ? result.stdout.trim() : '',
187
+ stderr: typeof result.stderr === 'string' ? result.stderr.trim() : '',
188
+ error: result.error,
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Surface a `gh`/exec failure with the same detail the GitHub bootstrap
194
+ * step prints — message plus the real gh stderr/stdout/args carried on a
195
+ * `GhExecError` — so a bare "gh exited with code 1" is actually diagnosable.
196
+ */
197
+ function logGhError(label, err) {
198
+ Logger.error(`[bootstrap] ${label} failed: ${err.message}`);
199
+ if (err.stderr)
200
+ Logger.error(`[bootstrap] gh stderr: ${String(err.stderr).trim()}`);
201
+ if (err.stdout)
202
+ Logger.error(`[bootstrap] gh stdout: ${String(err.stdout).trim()}`);
203
+ if (Array.isArray(err.args)) {
204
+ Logger.error(`[bootstrap] gh args: ${err.args.join(' ')}`);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Per-command git identity args. The first commit fails when neither a repo-
210
+ * nor global-level `user.name`/`user.email` is configured, which is common on
211
+ * a freshly provisioned machine. When either is missing we supply a
212
+ * non-persistent identity via `-c` (derived from the operator handle) so the
213
+ * commit succeeds without mutating the operator's git config.
214
+ */
215
+ function gitIdentityArgs(cwd, answers) {
216
+ const haveName = runGit(['config', 'user.name'], cwd).ok;
217
+ const haveEmail = runGit(['config', 'user.email'], cwd).ok;
218
+ if (haveName && haveEmail) return [];
219
+ const handle = answers.operatorHandle || answers.owner || 'mandrel';
220
+ return [
221
+ '-c',
222
+ `user.name=${handle}`,
223
+ '-c',
224
+ `user.email=${handle}@users.noreply.github.com`,
225
+ ];
226
+ }
227
+
228
+ /**
229
+ * Initialize the local git repo when one is not already present, and ensure
230
+ * at least one commit exists so `gh repo create --source=. --push` has
231
+ * something to push. Idempotent: a repo that already resolves `HEAD` is left
232
+ * untouched. Returns `{ ok, initialized, committed }` (or `{ ok:false, error }`
233
+ * on failure).
234
+ */
235
+ function ensureGitInitialized(state) {
236
+ const cwd = state.projectRoot;
237
+ const branch = state.answers.baseBranch || 'main';
238
+ let initialized = false;
239
+ if (!state.gitInitialized) {
240
+ // `git init -b <branch>` (git ≥ 2.28) sets the initial branch directly;
241
+ // fall back to a plain init + symbolic-ref for older git.
242
+ let init = runGit(['init', '-b', branch], cwd);
243
+ if (!init.ok) {
244
+ init = runGit(['init'], cwd);
245
+ if (!init.ok)
246
+ return { ok: false, error: init.stderr || 'git init failed' };
247
+ runGit(['symbolic-ref', 'HEAD', `refs/heads/${branch}`], cwd);
248
+ }
249
+ initialized = true;
250
+ state.gitInitialized = true;
251
+ Logger.info(
252
+ `[bootstrap] Initialized git repo (branch ${branch}) at ${cwd}.`,
253
+ );
254
+ }
255
+
256
+ // A push needs a commit; create one only when HEAD does not resolve yet.
257
+ //
258
+ // SECURITY (Story #3894): do NOT `git add -A` here. `.gitignore` seeding
259
+ // (`ensureGitignore`) runs two phases later in the pipeline, so at this
260
+ // point a cold-start folder may still contain secret-bearing files
261
+ // (`.env`, `.mcp.json`). Staging the whole tree before any gitignore
262
+ // exists — followed immediately by `gh repo create --push` — would push
263
+ // those secrets to a brand-new (often public) remote with no per-file
264
+ // consent, violating `security-baseline.md` § Secrets Management. The
265
+ // push only needs a commit to *exist*, so we create an empty one; the
266
+ // operator's own first content commit lands after the gitignore phase has
267
+ // already excluded the secret-bearing paths.
268
+ let committed = false;
269
+ if (!runGit(['rev-parse', '--verify', 'HEAD'], cwd).ok) {
270
+ const commit = runGit(
271
+ [
272
+ ...gitIdentityArgs(cwd, state.answers),
273
+ 'commit',
274
+ '--allow-empty',
275
+ '-m',
276
+ 'Initial commit',
277
+ ],
278
+ cwd,
279
+ );
280
+ if (!commit.ok) {
281
+ return { ok: false, error: commit.stderr || 'git commit failed' };
282
+ }
283
+ committed = true;
284
+ Logger.info('[bootstrap] Created initial commit.');
285
+ }
286
+ return { ok: true, initialized, committed };
287
+ }
288
+
289
+ /**
290
+ * Wire the local `origin` remote to owner/repo when it is missing, so the
291
+ * GitHub bootstrap — which infers the target repo from the local remote —
292
+ * can run. This is the companion to `createGithubRepo`: that path wires
293
+ * `origin` itself via `--remote origin`, but a repo that already exists (or
294
+ * a re-run after a partial failure) leaves the local folder unlinked. Only
295
+ * acts when the repo actually exists on GitHub; pushes the base branch to
296
+ * set upstream, downgrading a rejected push to a warning since the bootstrap
297
+ * only needs the remote to resolve (content sync is the operator's to settle).
298
+ */
299
+ async function ensureGitRemote(state, execImpl = exec) {
300
+ const cwd = state.projectRoot;
301
+ const { owner, repo } = state.answers;
302
+ const branch = state.answers.baseBranch || 'main';
303
+ if (runGit(['remote', 'get-url', 'origin'], cwd).ok) return;
304
+ if (!(await repoExists(owner, repo, execImpl))) {
305
+ Logger.warn(
306
+ `[bootstrap] No 'origin' remote and ${owner}/${repo} does not exist on GitHub — skipping remote wiring.`,
307
+ );
308
+ return;
309
+ }
310
+ const url = `https://github.com/${owner}/${repo}.git`;
311
+ const add = runGit(['remote', 'add', 'origin', url], cwd);
312
+ if (!add.ok) {
313
+ Logger.warn(`[bootstrap] Could not add 'origin' remote: ${add.stderr}`);
314
+ return;
315
+ }
316
+ Logger.info(`[bootstrap] Wired 'origin' → ${url}.`);
317
+ const push = runGit(['push', '-u', 'origin', branch], cwd);
318
+ if (!push.ok) {
319
+ Logger.warn(
320
+ `[bootstrap] 'origin' is set but push of '${branch}' failed (resolve manually, e.g. \`git pull --rebase origin ${branch}\`): ${push.stderr}`,
321
+ );
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Authoritatively check whether `owner/repo` exists, via `gh repo view`.
327
+ * Returns false on a not-found (so the repo can be created), true when it
328
+ * resolves, and true on any other error (auth/network/etc.) so a transient
329
+ * failure never triggers a spurious create attempt. Used instead of an
330
+ * `is it in the repo-list?` heuristic, which mis-fires for a brand-new
331
+ * account whose `gh repo list` is empty.
332
+ */
333
+ async function repoExists(owner, repo, execImpl = exec) {
334
+ try {
335
+ await execImpl({
336
+ args: ['repo', 'view', `${owner}/${repo}`, '--json', 'name'],
337
+ });
338
+ return true;
339
+ } catch (err) {
340
+ if (err instanceof GhNotFoundError) return false;
341
+ return true;
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Link the repo to the Projects V2 board (`gh project link`) so issues and
347
+ * PRs from the repo surface on the project. Runs whenever both a numeric
348
+ * project number and a repo are resolved — a freshly created project or an
349
+ * existing one picked from the list. Non-fatal and re-run-safe: an
350
+ * already-linked repo or a transient hiccup is downgraded to a warning so it
351
+ * never fails the bootstrap.
352
+ */
353
+ async function ensureProjectLinked(state, execImpl = exec) {
354
+ const { owner, repo } = state.answers;
355
+ const pn = String(state.answers.projectNumber ?? '');
356
+ if (!/^\d+$/.test(pn) || !repo) return;
357
+ try {
358
+ await execImpl({
359
+ args: ['project', 'link', pn, '--owner', owner, '--repo', repo],
360
+ });
361
+ Logger.info(
362
+ `[bootstrap] Linked repo ${owner}/${repo} to Project V2 #${pn}.`,
363
+ );
364
+ } catch (err) {
365
+ Logger.warn(
366
+ `[bootstrap] Could not link repo ${owner}/${repo} to Project V2 #${pn} (continuing): ${err.message}`,
367
+ );
368
+ }
369
+ }
370
+
371
+ /** Visibilities `gh repo create` accepts; each maps to a `--<v>` flag. */
372
+ export const REPO_VISIBILITIES = Object.freeze([
373
+ 'private',
374
+ 'public',
375
+ 'internal',
376
+ ]);
377
+
378
+ /**
379
+ * Resolve the new-repo visibility from `--visibility` (default `private`).
380
+ * Case-insensitive. Returns `null` for an unrecognized value so the caller
381
+ * can reject it with a clear message instead of silently defaulting.
382
+ */
383
+ export function resolveRepoVisibility(flags = {}) {
384
+ const raw = flags.visibility;
385
+ if (typeof raw !== 'string' || raw.length === 0) return 'private';
386
+ const value = raw.trim().toLowerCase();
387
+ return REPO_VISIBILITIES.includes(value) ? value : null;
388
+ }
389
+
390
+ /**
391
+ * Create the GitHub repo from the resolved owner/repo. `--source` links the
392
+ * existing local repo, `--remote origin` wires the remote, and `--push`
393
+ * uploads the current branch — so the local tree and the new remote stay in
394
+ * lockstep and Step 1's auto-detection works on a re-run. Visibility comes
395
+ * from `--visibility` (default private). Throws GhExecError on failure
396
+ * (surfaced by the caller).
397
+ */
398
+ async function createGithubRepo(state, execImpl = exec) {
399
+ const { owner, repo } = state.answers;
400
+ const slug = `${owner}/${repo}`;
401
+ const visibility = resolveRepoVisibility(state.flags);
402
+ await execImpl({
403
+ args: [
404
+ 'repo',
405
+ 'create',
406
+ slug,
407
+ `--${visibility}`,
408
+ '--source',
409
+ state.projectRoot,
410
+ '--remote',
411
+ 'origin',
412
+ '--push',
413
+ ],
414
+ });
415
+ Logger.info(
416
+ `[bootstrap] Created GitHub repo ${slug} (${visibility}) and pushed.`,
417
+ );
418
+ }
419
+
420
+ /**
421
+ * Find an existing Projects V2 board owned by `owner` whose title matches
422
+ * `title` exactly (case-insensitive, trimmed), returning its numeric id or
423
+ * `null` when none matches. Lists through the injected `execImpl` seam (the
424
+ * same `gh project list --owner X --format json` shape `gh-list` parses) so
425
+ * the dedupe is unit-testable without spawning a real `gh`. Any non-zero
426
+ * exit, spawn error, or unparseable payload degrades to `null` (no match) so
427
+ * a transient list failure never blocks creation.
428
+ *
429
+ * @param {string} owner
430
+ * @param {string} title
431
+ * @param {typeof exec} execImpl
432
+ * @returns {Promise<number|null>}
433
+ */
434
+ async function findExistingProjectNumber(owner, title, execImpl) {
435
+ const wanted = title.trim().toLowerCase();
436
+ if (wanted.length === 0) return null;
437
+ let res;
438
+ try {
439
+ res = await execImpl({
440
+ args: ['project', 'list', '--owner', owner, '--format', 'json'],
441
+ });
442
+ } catch {
443
+ return null;
444
+ }
445
+ let parsed;
446
+ try {
447
+ parsed = JSON.parse(res?.stdout ?? '');
448
+ } catch {
449
+ return null;
450
+ }
451
+ const projects = Array.isArray(parsed)
452
+ ? parsed
453
+ : Array.isArray(parsed?.projects)
454
+ ? parsed.projects
455
+ : [];
456
+ for (const item of projects) {
457
+ if (!item || typeof item !== 'object') continue;
458
+ if (!Number.isInteger(item.number)) continue;
459
+ const itemTitle =
460
+ typeof item.title === 'string' ? item.title.trim().toLowerCase() : '';
461
+ if (itemTitle === wanted) return item.number;
462
+ }
463
+ return null;
464
+ }
465
+
466
+ /**
467
+ * Resolve the typed Projects V2 name into a numeric id and rewrite
468
+ * `state.answers.projectNumber` so the downstream persist + GitHub bootstrap
469
+ * steps treat it as an existing project (and never create a duplicate). Before
470
+ * creating, it **dedupes against the owner's existing project titles**: if a
471
+ * board with the same title already exists, that board is adopted instead of
472
+ * running `gh project create` — so a re-run that re-types the same name never
473
+ * spawns a second same-titled board (Story #3896 / review Finding B.3). Throws
474
+ * on create failure or when gh returns no number.
475
+ */
476
+ async function createGithubProject(state, execImpl = exec) {
477
+ const { owner } = state.answers;
478
+ const title = String(state.answers.projectNumber);
479
+ const existing = await findExistingProjectNumber(owner, title, execImpl);
480
+ if (Number.isInteger(existing)) {
481
+ state.answers.projectNumber = String(existing);
482
+ Logger.info(
483
+ `[bootstrap] Reusing existing GitHub Project V2 "${title}" (#${existing}) — no duplicate created.`,
484
+ );
485
+ return existing;
486
+ }
487
+ // `gh project create` uses `--format json` (not `--json`), so exec returns
488
+ // the raw `{ stdout }` envelope — parse the number ourselves.
489
+ const res = await execImpl({
490
+ args: [
491
+ 'project',
492
+ 'create',
493
+ '--owner',
494
+ owner,
495
+ '--title',
496
+ title,
497
+ '--format',
498
+ 'json',
499
+ ],
500
+ });
501
+ let number = null;
502
+ try {
503
+ number = JSON.parse(res.stdout)?.number ?? null;
504
+ } catch {
505
+ /* fall through to the guard below */
506
+ }
507
+ if (!Number.isInteger(number)) {
508
+ throw new Error(
509
+ `gh project create returned no numeric project number (stdout: ${res.stdout?.trim() ?? ''})`,
510
+ );
511
+ }
512
+ state.answers.projectNumber = String(number);
513
+ Logger.info(`[bootstrap] Created GitHub Project V2 "${title}" (#${number}).`);
514
+ return number;
515
+ }
516
+
517
+ // ---------------------------------------------------------------------------
518
+ // Question list (Step 3).
519
+ // ---------------------------------------------------------------------------
520
+
521
+ /**
522
+ * Build the Step 3 question list. `silentAccept` keys (owner/repo/baseBranch/
523
+ * operatorHandle) are git-inferred and accepted without prompting unless an
524
+ * override is supplied. The `operatorHandle` and `projectNumber` defaults
525
+ * track the repo owner / repo name respectively (see post-processing in
526
+ * `collectAndConfirm`).
527
+ */
528
+ export function buildQuestions(defaults, flags, env = process.env, lists = {}) {
529
+ const owner = resolveOwnerForPicker(defaults, flags, env);
530
+ // Pre-fetched lists (shared with the summary display) are a fast path used
531
+ // only when populated. They're empty when the owner is unknown up front
532
+ // (a folder with no git remote), so the pickers fall back to a live fetch
533
+ // keyed off the owner the operator just typed (`answers.owner`).
534
+ const reposList = lists.reposList;
535
+ const projectsList = lists.projectsList;
536
+ const pickerOwner = (answers) => answers?.owner || owner;
537
+ return [
538
+ {
539
+ key: 'owner',
540
+ flag: 'owner',
541
+ env: 'GH_OWNER',
542
+ message: 'Github repo owner',
543
+ default: defaults.owner,
544
+ required: true,
545
+ validate: (v) =>
546
+ /^[A-Za-z0-9][A-Za-z0-9-]*$/.test(v) ? null : 'Invalid GitHub owner',
547
+ },
548
+ {
549
+ key: 'operatorHandle',
550
+ flag: 'operator-handle',
551
+ env: 'GH_OPERATOR_HANDLE',
552
+ message: 'Github username/handle (without the @)',
553
+ // Default tracks the repo owner; resolved post-collect if left blank.
554
+ default: defaults.owner,
555
+ required: false,
556
+ validate: (v) =>
557
+ v.length === 0 || /^[A-Za-z0-9-]+$/.test(v)
558
+ ? null
559
+ : 'Invalid GitHub handle',
560
+ },
561
+ {
562
+ key: 'repo',
563
+ flag: 'repo',
564
+ env: 'GH_REPO',
565
+ message:
566
+ 'Github repo name - Select from the list or enter a new name to create one',
567
+ default: defaults.repo,
568
+ required: true,
569
+ picker: {
570
+ list: (answers) => {
571
+ if (Array.isArray(reposList) && reposList.length > 0)
572
+ return reposList;
573
+ const o = pickerOwner(answers);
574
+ return o ? listRepos({ owner: o }).map(bareRepoName) : [];
575
+ },
576
+ },
577
+ validate: (v) =>
578
+ /^[A-Za-z0-9._-]+$/.test(v) ? null : 'Invalid GitHub repo name',
579
+ },
580
+ {
581
+ key: 'baseBranch',
582
+ flag: 'base-branch',
583
+ env: 'GH_BASE_BRANCH',
584
+ message: 'Base branch',
585
+ default: defaults.baseBranch || 'main',
586
+ required: true,
587
+ validate: (v) => (v.length > 0 ? null : 'Base branch is required'),
588
+ },
589
+ {
590
+ key: 'projectNumber',
591
+ flag: 'project-number',
592
+ env: 'GH_PROJECT_NUMBER',
593
+ message:
594
+ 'Github Project V2 name - Select from the list or enter a new name to create one',
595
+ // Prefer the already-stored numeric project number (an
596
+ // already-provisioned project on a re-run) so `--assume-yes` resolves a
597
+ // numeric answer that `detectCreation` treats as existing — never a
598
+ // duplicate board (Story #3896). Falls back to the repo name only on a
599
+ // genuine first run where nothing is stored yet.
600
+ default: defaults.projectNumber || defaults.repo,
601
+ required: false,
602
+ picker: {
603
+ list: (answers) => {
604
+ if (Array.isArray(projectsList) && projectsList.length > 0) {
605
+ return projectsList;
606
+ }
607
+ const o = pickerOwner(answers);
608
+ return o ? listProjects({ owner: o }) : [];
609
+ },
610
+ },
611
+ // Accept blank (skip), an existing project number, or a new project
612
+ // name (letters/digits/space/._-).
613
+ validate: (v) =>
614
+ v.length === 0 || /^\d+$/.test(v) || /^[A-Za-z0-9 ._-]+$/.test(v)
615
+ ? null
616
+ : 'Invalid project name',
617
+ },
618
+ ];
619
+ }
620
+
621
+ const INFERRED_KEYS = Object.freeze([
622
+ 'owner',
623
+ 'repo',
624
+ 'baseBranch',
625
+ 'operatorHandle',
626
+ ]);
627
+ const FLAG_BY_KEY = Object.freeze({
628
+ owner: 'owner',
629
+ repo: 'repo',
630
+ baseBranch: 'base-branch',
631
+ operatorHandle: 'operator-handle',
632
+ });
633
+ const ENV_BY_KEY = Object.freeze({
634
+ owner: 'GH_OWNER',
635
+ repo: 'GH_REPO',
636
+ baseBranch: 'GH_BASE_BRANCH',
637
+ operatorHandle: 'GH_OPERATOR_HANDLE',
638
+ });
639
+
640
+ /** Keys whose git-inferred default is accepted without prompting. */
641
+ export function resolveSilentAccept(defaults, flags, env = process.env) {
642
+ const out = [];
643
+ for (const key of INFERRED_KEYS) {
644
+ const value = defaults?.[key];
645
+ if (typeof value !== 'string' || value.length === 0) continue;
646
+ if (typeof flags?.[FLAG_BY_KEY[key]] === 'string') continue;
647
+ if (typeof env?.[ENV_BY_KEY[key]] === 'string') continue;
648
+ out.push(key);
649
+ }
650
+ return out;
651
+ }
652
+
653
+ // ---------------------------------------------------------------------------
654
+ // GitHub-side bootstrap (Step 6) — same wiring as bootstrap.js.
655
+ // ---------------------------------------------------------------------------
656
+
657
+ async function runGithubBootstrap(answers, opts) {
658
+ const { runBootstrap, preflightGh, preflightRuntimeDeps } = await import(
659
+ './agents-bootstrap-github.js'
660
+ );
661
+ await preflightGh();
662
+ await preflightRuntimeDeps();
663
+ const { resolveConfig, validateOrchestrationConfig } = await import(
664
+ './lib/config-resolver.js'
665
+ );
666
+ // `resolveConfig` reads github.projectNumber from .agentrc.json, which the
667
+ // persistProjectNumber step writes BEFORE this runs — so the provider reuses
668
+ // the existing project instead of creating a new one (Bug: gave #8, created
669
+ // #12 because the number never reached the provider config).
670
+ const config = resolveConfig();
671
+ validateOrchestrationConfig(config);
672
+ return runBootstrap(config, {
673
+ project: config.project,
674
+ github: config.github,
675
+ assumeYes: opts.assumeYes,
676
+ baseBranch: answers.baseBranch,
677
+ // Real consent signal threaded from `parseAndValidate` (Story #3897):
678
+ // interactive operator confirmation, `--assume-yes`, or
679
+ // `--approve-github-admin`. Default-deny at the boundary gate when absent.
680
+ githubAdminApproved: opts.githubAdminApproved === true,
681
+ // Opt-in: delete the Projects V2 built-in workflows that race against the
682
+ // orchestrator's ColumnSync (e.g. "Pull request merged"). Off by default.
683
+ reapConflictingWorkflows: Boolean(opts.reapConflictingWorkflows),
684
+ });
685
+ }
686
+
687
+ /** True only when every github-admin sub-mutation that ran succeeded. */
688
+ function githubSubMutationsSucceeded(gh) {
689
+ if (gh.branchProtection?.status === 'failed') return false;
690
+ if (gh.mergeMethods?.status === 'failed') return false;
691
+ return true;
692
+ }
693
+
694
+ /** Phase groups whose mutations actually landed, for the install ledger. */
695
+ function resolveAppliedGroups(approvedGroups, report) {
696
+ const applied = new Set();
697
+ for (const group of approvedGroups ?? []) {
698
+ if (group === PHASE_GROUPS.GITHUB_ADMIN) {
699
+ const gh = report?.github;
700
+ if (gh && !gh.error && !gh.skipped && githubSubMutationsSucceeded(gh)) {
701
+ applied.add(group);
702
+ }
703
+ continue;
704
+ }
705
+ applied.add(group);
706
+ }
707
+ return applied;
708
+ }
709
+
710
+ // ---------------------------------------------------------------------------
711
+ // Pipeline phases
712
+ // ---------------------------------------------------------------------------
713
+
714
+ /**
715
+ * Step 1 — Parse argv, handle `--help`, and enforce the non-TTY contract.
716
+ *
717
+ * Consent contract (Story #3897). A non-TTY run has no operator to confirm
718
+ * the summary loop in `collectAndConfirm`, so the irreversible GitHub-admin
719
+ * mutations cannot ride on a real confirmation — they need an explicit
720
+ * up-front signal. The gate therefore requires **either** `--assume-yes`
721
+ * **or** `--approve-github-admin` on any non-TTY run (matching the
722
+ * `--help` text), and computes `githubAdminApproved` once for the whole run:
723
+ *
724
+ * - **interactive (TTY)** → consent is the operator's `Is this correct?`
725
+ * confirmation in `collectAndConfirm`, so the run is approved.
726
+ * - **non-TTY** → consent is `--assume-yes` or `--approve-github-admin`;
727
+ * without one of those the run halts before any mutation.
728
+ *
729
+ * `githubAdminApproved` flows down to `runGithubBootstrap`, which forwards it
730
+ * to the boundary gate in `agents-bootstrap-github.js#runBootstrap`. That
731
+ * gate is default-deny, so a non-approved value makes the GitHub-admin phase
732
+ * a verified no-op instead of a silent mutation.
733
+ */
734
+ export function parseAndValidate(argv, opts = {}) {
735
+ const stdout = opts.stdout ?? process.stdout;
736
+ const stdin = opts.stdin ?? process.stdin;
737
+ const flags = parseFlags(argv);
738
+ if (flags.help) {
739
+ stdout.write(HELP);
740
+ return { ok: false, exit: 0 };
741
+ }
742
+ const interactive = Boolean(stdin.isTTY) && !flags['assume-yes'];
743
+ const assumeYes = Boolean(flags['assume-yes']);
744
+ const approveGithubAdmin = Boolean(flags['approve-github-admin']);
745
+ // A non-TTY run cannot collect operator consent interactively, so it MUST
746
+ // carry an explicit consent signal. This restores parity with the --help
747
+ // text, which has always claimed --assume-yes is required for non-TTY runs.
748
+ // (owner/repo are resolved from flags/env/git-remote downstream — a consent
749
+ // signal alone is sufficient to advance, exactly as the pre-Story #3897
750
+ // `--assume-yes` path did.)
751
+ if (!interactive && !assumeYes && !approveGithubAdmin) {
752
+ Logger.error(
753
+ '[bootstrap] non-TTY run requires --assume-yes or --approve-github-admin ' +
754
+ '(no operator is present to confirm the GitHub-admin mutations).',
755
+ );
756
+ return { ok: false, exit: 1 };
757
+ }
758
+ // Real GitHub-admin consent: an interactive run confirms it in
759
+ // `collectAndConfirm`; a non-TTY run signals it via flag (above).
760
+ const githubAdminApproved = interactive || assumeYes || approveGithubAdmin;
761
+ if (resolveRepoVisibility(flags) === null) {
762
+ Logger.error(
763
+ `[bootstrap] invalid --visibility "${flags.visibility}". ` +
764
+ `Expected one of: ${REPO_VISIBILITIES.join(', ')}.`,
765
+ );
766
+ return { ok: false, exit: 1 };
767
+ }
768
+ return {
769
+ ok: true,
770
+ payload: { flags, interactive, assumeYes, githubAdminApproved },
771
+ };
772
+ }
773
+
774
+ /**
775
+ * Step 1b — Resolve paths, infer defaults from git, and echo the detected
776
+ * values back to the operator (the "share found values" requirement).
777
+ */
778
+ export function prepareContext(state, opts = {}) {
779
+ const scriptUrl = opts.scriptUrl ?? import.meta.url;
780
+ const here = path.dirname(fileURLToPath(scriptUrl));
781
+ const projectRoot = opts.projectRoot ?? process.cwd();
782
+ const agentRoot = path.resolve(here, '..');
783
+ const defaults = inferDefaults(projectRoot);
784
+ const silentAccept = resolveSilentAccept(defaults, state.flags);
785
+
786
+ Logger.info('[bootstrap] Detected from local git:');
787
+ Logger.info(` owner ${defaults.owner ?? '(none)'}`);
788
+ Logger.info(` repo ${defaults.repo ?? '(none)'}`);
789
+ Logger.info(` base branch ${defaults.baseBranch ?? '(none)'}`);
790
+ Logger.info(` username ${defaults.operatorHandle ?? '(none)'}`);
791
+
792
+ return {
793
+ ok: true,
794
+ payload: { projectRoot, agentRoot, defaults, silentAccept },
795
+ };
796
+ }
797
+
798
+ /**
799
+ * Step 2 — Preflight. Work-tree check is informational (does not fail the
800
+ * gate); adds the Projects V2 permission check. Prints a pass/fail line for
801
+ * every check.
802
+ */
803
+ export async function runPreflightPhase(state, opts = {}) {
804
+ const run = opts.run ?? runPreflight;
805
+ const skipGithub = Boolean(state.flags['skip-github']);
806
+ const result = await run({
807
+ skipGithub,
808
+ requireWorkTree: false,
809
+ checkProjectScope: !skipGithub,
810
+ });
811
+
812
+ for (const check of result.checks) {
813
+ if (check.ok) {
814
+ Logger.info(
815
+ `[bootstrap] ✓ ${check.name}${check.detail ? ` — ${check.detail}` : ''}`,
816
+ );
817
+ } else {
818
+ Logger.error(`[bootstrap] ✗ ${check.name}: ${check.remedy}`);
819
+ }
820
+ }
821
+
822
+ if (!result.ok) {
823
+ Logger.error(
824
+ '[bootstrap] Preflight failed. Resolve the issues above and re-run.',
825
+ );
826
+ return { ok: false, exit: 1 };
827
+ }
828
+
829
+ Logger.info(
830
+ `[bootstrap] git initialized: ${result.gitInitialized ? 'yes' : 'no'}`,
831
+ );
832
+ return {
833
+ ok: true,
834
+ payload: { preflight: result, gitInitialized: result.gitInitialized },
835
+ };
836
+ }
837
+
838
+ /** Render the resolved answers as a human-readable summary block. */
839
+ function renderAnswerSummary(
840
+ answers,
841
+ creation,
842
+ project,
843
+ gitInitialized,
844
+ visibility,
845
+ ) {
846
+ const newRepoNote = creation.newRepo
847
+ ? ` (NEW — will be created, ${visibility})`
848
+ : '';
849
+ const lines = [
850
+ '\n=== Review your answers ===',
851
+ ` Repo owner ${answers.owner}`,
852
+ ` Username/handle ${answers.operatorHandle || '(none)'}`,
853
+ ` Repo name ${answers.repo}${newRepoNote}`,
854
+ ` Base branch ${answers.baseBranch}`,
855
+ ` Project V2 name ${project.name}${creation.newProject ? ' (NEW — will be created)' : ''}`,
856
+ ` Project V2 # ${project.number}`,
857
+ ` Local git ${gitInitialized ? 'initialized' : 'will be initialized'}`,
858
+ ];
859
+ return lines.join('\n');
860
+ }
861
+
862
+ /**
863
+ * Determine whether the answers ask for resources that do not exist yet.
864
+ * The repo is "new" when `gh repo view owner/repo` reports it does not
865
+ * exist — an authoritative per-repo probe rather than an "is it in the
866
+ * repo-list?" check, which mis-fired for a brand-new account whose
867
+ * `gh repo list` is empty (it then assumed the repo already existed and
868
+ * skipped creation). A non-numeric project answer (a typed name, not a
869
+ * picked number) is "new". When GitHub is skipped there is nothing to
870
+ * create, so detection is bypassed.
871
+ */
872
+ async function detectCreation(answers, skipGithub) {
873
+ const creation = { newRepo: false, newProject: false };
874
+ if (skipGithub) return creation;
875
+ if (answers.repo && answers.owner) {
876
+ creation.newRepo = !(await repoExists(answers.owner, answers.repo));
877
+ }
878
+ const pn = answers.projectNumber;
879
+ if (typeof pn === 'string' && pn.length > 0 && !/^\d+$/.test(pn)) {
880
+ creation.newProject = true;
881
+ }
882
+ return creation;
883
+ }
884
+
885
+ /**
886
+ * Resolve the Projects V2 answer into a `{ name, number }` pair for the
887
+ * summary. The picker stores only the numeric value, so for an existing
888
+ * project (numeric answer) we look the name up in the owner's project list.
889
+ * A typed answer (non-numeric) is a new project name with no number yet.
890
+ */
891
+ function resolveProjectDisplay(answers, skipGithub, projectsList) {
892
+ const pn = answers.projectNumber;
893
+ if (!pn) return { name: '(skip)', number: '(skip)' };
894
+ if (/^\d+$/.test(pn)) {
895
+ let name = '(unknown)';
896
+ if (!skipGithub) {
897
+ const projects =
898
+ projectsList ?? safeList(() => listProjects({ owner: answers.owner }));
899
+ const match = projects.find((p) => p.value === pn);
900
+ if (match) {
901
+ const m = /^(.*)\s+\(#\d+\)$/.exec(match.label);
902
+ name = m ? m[1] : match.label;
903
+ }
904
+ }
905
+ return { name, number: pn };
906
+ }
907
+ // Typed name → new project; number is assigned at creation time.
908
+ return { name: pn, number: '(new)' };
909
+ }
910
+
911
+ /**
912
+ * Steps 3 + 4 — Collect answers, show a summary, and confirm. Interactive
913
+ * runs that answer "no" loop back and re-ask. Non-interactive runs
914
+ * auto-accept. Then collect creation approval when a new repo/project was
915
+ * requested.
916
+ */
917
+ export async function collectAndConfirm(state) {
918
+ const skipGithub = Boolean(state.flags['skip-github']);
919
+ const owner = resolveOwnerForPicker(state.defaults, state.flags);
920
+ // Fetch the owner's repos + projects ONCE and reuse for the pickers and
921
+ // the summary display — so the resolved project name never depends on a
922
+ // second (flaky) `gh` call. (Repo existence for the creation check is a
923
+ // separate, authoritative `gh repo view` probe in `detectCreation`.)
924
+ const reposList =
925
+ !skipGithub && owner
926
+ ? safeList(() => listRepos({ owner }).map(bareRepoName))
927
+ : [];
928
+ const projectsList =
929
+ !skipGithub && owner ? safeList(() => listProjects({ owner })) : [];
930
+
931
+ let silentAccept = state.silentAccept;
932
+ // Loop until the operator confirms the summary (or we auto-accept).
933
+ for (;;) {
934
+ const { answers, missing } = await collectAnswers({
935
+ questions: buildQuestions(state.defaults, state.flags, process.env, {
936
+ reposList,
937
+ projectsList,
938
+ }),
939
+ flags: state.flags,
940
+ interactive: state.interactive,
941
+ assumeYes: state.assumeYes,
942
+ silentAccept,
943
+ });
944
+ if (missing.length > 0) {
945
+ Logger.error(
946
+ `[bootstrap] missing required answers: ${missing.join(', ')}`,
947
+ );
948
+ return { ok: false, exit: 1 };
949
+ }
950
+ // Defaults that track another answer: handle ⇐ owner, project ⇐ repo.
951
+ if (!answers.operatorHandle) answers.operatorHandle = answers.owner;
952
+ // Strip a single leading `@` so the starter template's `@[USERNAME]`
953
+ // substitution yields `@foo`, not `@@foo` (Story #3700). The flag/env
954
+ // paths bypass the interactive validator that already rejects a leading
955
+ // `@`, so normalize uniformly here.
956
+ answers.operatorHandle = normalizeHandleAnswer(answers.operatorHandle);
957
+
958
+ const creation = await detectCreation(answers, skipGithub);
959
+ const project = resolveProjectDisplay(answers, skipGithub, projectsList);
960
+ Logger.info(
961
+ renderAnswerSummary(
962
+ answers,
963
+ creation,
964
+ project,
965
+ state.gitInitialized,
966
+ resolveRepoVisibility(state.flags),
967
+ ),
968
+ );
969
+ const correct = await confirmYesNo('Is this correct?', state.interactive);
970
+ if (!correct) {
971
+ Logger.info('[bootstrap] Okay — let’s try again.');
972
+ // Re-prompt everything on the next pass (drop silent-accept).
973
+ silentAccept = [];
974
+ continue;
975
+ }
976
+
977
+ // In --dry-run we only collect/confirm info, so never ask to create.
978
+ if (!state.flags['dry-run'] && (creation.newRepo || creation.newProject)) {
979
+ const approved = await confirmYesNo(
980
+ 'Create the new GitHub repo/project listed above?',
981
+ state.interactive,
982
+ );
983
+ if (!approved) {
984
+ Logger.error(
985
+ '[bootstrap] Creation declined — cannot continue without the repo/project. Exiting.',
986
+ );
987
+ return { ok: false, exit: 1 };
988
+ }
989
+ }
990
+ return { ok: true, payload: { answers, creation } };
991
+ }
992
+ }
993
+
994
+ /**
995
+ * --dry-run gate — print the resolved answers and the full mutation plan,
996
+ * then halt BEFORE any file write, GitHub change, or label creation. Runs
997
+ * after collect/confirm so the operator sees exactly what would happen.
998
+ */
999
+ /** Render the dry-run plan as a per-section layout (no mutations happen). */
1000
+ function renderDryRunPlan(state) {
1001
+ const a = state.answers ?? {};
1002
+ const c = state.creation ?? {};
1003
+ const flagList = Object.entries(state.flags ?? {}).map(([k, v]) =>
1004
+ v === true ? k : `${k}=${v}`,
1005
+ );
1006
+ return [
1007
+ '\n=== Dry run — nothing will be changed ===',
1008
+ 'Values',
1009
+ ` owner ${a.owner ?? '(none)'}`,
1010
+ ` operator handle ${a.operatorHandle ?? '(none)'}`,
1011
+ ` repo ${a.repo ?? '(none)'}`,
1012
+ ` base branch ${a.baseBranch ?? '(none)'}`,
1013
+ ` project number ${a.projectNumber || '(skip)'}`,
1014
+ '',
1015
+ 'Creation',
1016
+ ` git init ${state.gitInitialized ? 'no' : 'yes'}`,
1017
+ ` new repo ${c.newRepo ? `yes (${resolveRepoVisibility(state.flags)})` : 'no'}`,
1018
+ ` new project ${c.newProject ? 'yes' : 'no'}`,
1019
+ '',
1020
+ 'Flags',
1021
+ ` ${flagList.length ? flagList.join(', ') : '(none)'}`,
1022
+ ].join('\n');
1023
+ }
1024
+
1025
+ export function dryRunPlan(state) {
1026
+ if (!state.flags['dry-run']) return { ok: true, payload: {} };
1027
+ Logger.info(
1028
+ '[bootstrap] --dry-run: no files, GitHub settings, or labels will be changed.',
1029
+ );
1030
+ Logger.info(renderDryRunPlan(state));
1031
+ return { ok: false, exit: 0 };
1032
+ }
1033
+
1034
+ /**
1035
+ * Step 5 — Provision the missing pieces of a cold start, in dependency order:
1036
+ *
1037
+ * 1. Local git — `git init` + an initial commit when the folder is not a
1038
+ * repo yet (so the repo create below has something to push).
1039
+ * 2. GitHub repo — `gh repo create --source --remote --push` when the repo
1040
+ * does not exist for the owner; otherwise wire the `origin` remote to the
1041
+ * existing repo when the local folder is not yet linked. Either way the
1042
+ * GitHub bootstrap can resolve the target from the local remote.
1043
+ * 3. GitHub Project V2 — `gh project create` from the typed name when the
1044
+ * project answer is a name rather than an existing number; the assigned
1045
+ * number is written back onto `state.answers.projectNumber`.
1046
+ * 4. Link — `gh project link` ties the repo to the project board so its
1047
+ * issues/PRs surface there (non-fatal; safe to re-run).
1048
+ *
1049
+ * Every action is idempotent and guarded by the detection done in
1050
+ * `collectAndConfirm`, so a re-run on an already-provisioned project is a
1051
+ * no-op. `--skip-github` suppresses the GitHub mutations but still runs the
1052
+ * local git init. `--dry-run` never reaches this step (it halts earlier).
1053
+ *
1054
+ * `deps.exec` injects the `gh-exec` seam so the GitHub-touching branches
1055
+ * (`gh repo create`, `gh project create`, `gh project link`) are unit-testable
1056
+ * without spawning a real `gh`; it defaults to the module's `exec`.
1057
+ */
1058
+ export async function provisionResources(state, deps = {}) {
1059
+ const execImpl = deps.exec ?? exec;
1060
+ const skipGithub = Boolean(state.flags['skip-github']);
1061
+
1062
+ // 1. Local git — initialize + first commit when missing (idempotent).
1063
+ const git = ensureGitInitialized(state);
1064
+ if (!git.ok) {
1065
+ Logger.error(`[bootstrap] git initialization failed: ${git.error}`);
1066
+ return { ok: false, exit: 1 };
1067
+ }
1068
+ if (!git.initialized && !git.committed) {
1069
+ Logger.info('[bootstrap] git already initialized — leaving as-is.');
1070
+ }
1071
+
1072
+ const { newRepo, newProject } = state.creation;
1073
+ if (skipGithub) {
1074
+ if (newRepo || newProject) {
1075
+ Logger.info(
1076
+ '[bootstrap] --skip-github set; not creating the GitHub repo/project.',
1077
+ );
1078
+ }
1079
+ return { ok: true, payload: {} };
1080
+ }
1081
+
1082
+ // 2. GitHub repo — create + link + push when it does not exist yet;
1083
+ // otherwise ensure the local `origin` remote points at the existing repo
1084
+ // so the GitHub bootstrap can resolve the target (idempotent re-runs and
1085
+ // pre-created repos would otherwise leave the folder unlinked).
1086
+ if (newRepo) {
1087
+ try {
1088
+ await createGithubRepo(state, execImpl);
1089
+ } catch (err) {
1090
+ logGhError('repo create', err);
1091
+ return { ok: false, exit: 1 };
1092
+ }
1093
+ } else {
1094
+ await ensureGitRemote(state, execImpl);
1095
+ }
1096
+
1097
+ // 3. GitHub Project V2 — create from the typed name; capture its number so
1098
+ // the persist + GitHub bootstrap steps reuse it instead of duplicating.
1099
+ if (newProject) {
1100
+ try {
1101
+ await createGithubProject(state, execImpl);
1102
+ // It now exists with a real number; downstream treats it as existing.
1103
+ state.creation.newProject = false;
1104
+ } catch (err) {
1105
+ logGhError('project create', err);
1106
+ return { ok: false, exit: 1 };
1107
+ }
1108
+ }
1109
+
1110
+ if (!newRepo && !newProject) {
1111
+ Logger.info('[bootstrap] No new GitHub resources needed.');
1112
+ }
1113
+
1114
+ // 4. Link the repo to the project board so issues/PRs surface on it
1115
+ // (idempotent + non-fatal; runs for both freshly created and existing
1116
+ // repo/project pairs).
1117
+ await ensureProjectLinked(state, execImpl);
1118
+
1119
+ return { ok: true, payload: {} };
1120
+ }
1121
+
1122
+ /**
1123
+ * Step 6a — Project-side bootstrap. With phased approval removed, all
1124
+ * project-side phase groups are treated as approved.
1125
+ */
1126
+ export async function executeBootstrap(state) {
1127
+ Logger.info(
1128
+ `[bootstrap] Starting project bootstrap at ${state.projectRoot} (owner=${state.answers.owner} repo=${state.answers.repo} base=${state.answers.baseBranch})`,
1129
+ );
1130
+ const approvedGroups = new Set(Object.values(PHASE_GROUPS));
1131
+ const report = await applyProjectBootstrap({
1132
+ projectRoot: state.projectRoot,
1133
+ agentRoot: state.agentRoot,
1134
+ answers: state.answers,
1135
+ approvedGroups,
1136
+ skipQuality: Boolean(state.flags['skip-quality']),
1137
+ });
1138
+ return { ok: true, payload: { report, approvedGroups } };
1139
+ }
1140
+
1141
+ /**
1142
+ * Step 6 (between project + GitHub) — Persist the chosen Projects V2 number
1143
+ * into .agentrc.json's github block so it is the stored source of truth that
1144
+ * resolveConfig (and the orchestrator) read back. Runs AFTER the project-side
1145
+ * bootstrap has ensured .agentrc.json exists and BEFORE the GitHub bootstrap,
1146
+ * so the provider reuses the existing project instead of creating a new one.
1147
+ * Merges into an existing file (ensureAgentrc never overwrites one). Stored as
1148
+ * an integer per the schema; a blank/new-project answer stores nothing.
1149
+ */
1150
+ export function persistProjectNumber(state) {
1151
+ const pn = String(state.answers.projectNumber ?? '');
1152
+ if (!/^\d+$/.test(pn)) {
1153
+ return { ok: true, payload: {} };
1154
+ }
1155
+ const target = path.join(state.projectRoot, '.agentrc.json');
1156
+ let config;
1157
+ try {
1158
+ config = JSON.parse(fs.readFileSync(target, 'utf8'));
1159
+ } catch (err) {
1160
+ Logger.error(
1161
+ `[bootstrap] Could not read ${target} to store projectNumber: ${err.message}`,
1162
+ );
1163
+ return { ok: true, payload: {} };
1164
+ }
1165
+ config.github = config.github ?? {};
1166
+ // Minimal-write contract (Story #3700): only re-serialize `.agentrc.json`
1167
+ // when the stored number actually changes. When the value is already present
1168
+ // and equal, leave the file byte-for-byte untouched — a re-run must not churn
1169
+ // the consumer's hand-formatting or whitespace.
1170
+ if (config.github.projectNumber === Number(pn)) {
1171
+ return { ok: true, payload: {} };
1172
+ }
1173
+ config.github.projectNumber = Number(pn);
1174
+ fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
1175
+ Logger.info(`[bootstrap] Stored github.projectNumber=${pn} in .agentrc.json`);
1176
+ return { ok: true, payload: {} };
1177
+ }
1178
+
1179
+ /** Step 6b — GitHub-side bootstrap. Honours `--skip-github`. */
1180
+ export async function executeGithubBootstrap(state) {
1181
+ if (state.flags['skip-github']) {
1182
+ Logger.info('[bootstrap] --skip-github set; skipping GitHub bootstrap.');
1183
+ return { ok: true, payload: {} };
1184
+ }
1185
+ try {
1186
+ state.report.github = await runGithubBootstrap(state.answers, {
1187
+ assumeYes: state.assumeYes,
1188
+ githubAdminApproved: state.githubAdminApproved === true,
1189
+ reapConflictingWorkflows: Boolean(
1190
+ state.flags['reap-conflicting-workflows'],
1191
+ ),
1192
+ });
1193
+ } catch (err) {
1194
+ // GhExecError carries the real gh stderr/stdout/exit code — surface it so
1195
+ // a generic "gh exited with code 1" is actually diagnosable.
1196
+ logGhError('GitHub bootstrap', err);
1197
+ state.report.github = { error: err.message };
1198
+ }
1199
+ return { ok: true, payload: {} };
1200
+ }
1201
+
1202
+ /** Step 6c — Record the install ledger for a future uninstall. */
1203
+ export function recordLedger(state) {
1204
+ const appliedGroups = resolveAppliedGroups(
1205
+ state.approvedGroups,
1206
+ state.report,
1207
+ );
1208
+ const manifestCtx = {
1209
+ answers: state.answers,
1210
+ skipGithub: Boolean(state.flags['skip-github']),
1211
+ skipQuality: Boolean(state.flags['skip-quality']),
1212
+ };
1213
+ const entries = buildMutationManifest(manifestCtx).filter((e) =>
1214
+ appliedGroups.has(e.phaseGroup),
1215
+ );
1216
+ if (entries.length === 0) {
1217
+ state.report.ledger = { written: false, reason: 'no-mutations-applied' };
1218
+ return { ok: true, payload: {} };
1219
+ }
1220
+ const record = buildLedgerRecord({
1221
+ entries,
1222
+ approvedGroups: appliedGroups,
1223
+ answers: state.answers,
1224
+ // The live execution report lets the ledger record `already-present` vs
1225
+ // `seeded` per entry, so uninstall never deletes a pre-existing
1226
+ // `.agentrc.json` the install merely left in place (Story #3895).
1227
+ report: state.report,
1228
+ });
1229
+ const result = writeInstallLedger(state.projectRoot, record);
1230
+ state.report.ledger = { ...result, approvedGroups: [...appliedGroups] };
1231
+ return { ok: true, payload: {} };
1232
+ }
1233
+
1234
+ /**
1235
+ * Step 7 — Offer to commit + push the bootstrap wiring (Story #3899).
1236
+ *
1237
+ * Story delivery runs in git worktrees that check out **tracked files only**,
1238
+ * so an uncommitted `.agents/` tree means every Story sub-agent breaks. This
1239
+ * step closes that "worked in my checkout, broke in delivery" trap by offering
1240
+ * the commit + push at the end of the run.
1241
+ *
1242
+ * Ordering: this phase runs LAST in the pipeline, after `executeBootstrap`
1243
+ * (which seeds the secret-safe `.gitignore`) and `recordLedger`. The stage
1244
+ * step also uses an explicit allowlist and refuses to stage `.env` /
1245
+ * `.mcp.json` / `.agentrc.local.json` regardless of `.gitignore` state, so the
1246
+ * commit never carries a secret even before the gitignore-ordering Story
1247
+ * (#3894) lands.
1248
+ *
1249
+ * Behaviour:
1250
+ * - `--dry-run` → no-op (the dry-run gate already halted earlier; this is a
1251
+ * belt-and-braces guard for direct calls).
1252
+ * - Interactive + accept → stage the allowlist, commit with a conventional
1253
+ * subject, push the base branch.
1254
+ * - Interactive + decline → print the exact manual commands; no git mutation.
1255
+ * - Non-interactive (`--assume-yes` / no TTY) → the defined safe path is to
1256
+ * print the manual commands and make NO git mutation, so a CI run never
1257
+ * surprises the operator with a push it did not ask for.
1258
+ *
1259
+ * `deps.runGit` injects the git seam and `deps.confirm` the yes/no prompt seam
1260
+ * for unit testing; both default to the module's implementations.
1261
+ */
1262
+ export async function offerCommitPush(state, deps = {}) {
1263
+ if (state.flags['dry-run']) return { ok: true, payload: {} };
1264
+ const runGitImpl = deps.runGit ?? runGit;
1265
+ const confirmImpl = deps.confirm ?? confirmYesNo;
1266
+ const cwd = state.projectRoot;
1267
+ const branch = state.answers.baseBranch || 'main';
1268
+ const stagePaths = resolveStagePaths(cwd);
1269
+ const instructions = buildManualInstructions({
1270
+ stagePaths,
1271
+ baseBranch: branch,
1272
+ });
1273
+
1274
+ // Non-interactive (--assume-yes / no TTY): never push unprompted. Print the
1275
+ // exact commands and leave the working tree untouched.
1276
+ if (!state.interactive) {
1277
+ Logger.info(`\n[bootstrap] ${instructions}`);
1278
+ return { ok: true, payload: { commitPush: { action: 'instructed' } } };
1279
+ }
1280
+
1281
+ const accepted = await confirmImpl(
1282
+ 'Commit and push the Mandrel setup?',
1283
+ state.interactive,
1284
+ );
1285
+ if (!accepted) {
1286
+ Logger.info(`\n[bootstrap] ${instructions}`);
1287
+ return { ok: true, payload: { commitPush: { action: 'declined' } } };
1288
+ }
1289
+
1290
+ const staged = stageBootstrapFiles({ projectRoot: cwd, runGit: runGitImpl });
1291
+ if (!staged.ok) {
1292
+ Logger.warn(`[bootstrap] Could not stage the wiring: ${staged.error}`);
1293
+ Logger.info(`\n[bootstrap] ${instructions}`);
1294
+ return { ok: true, payload: { commitPush: { action: 'stage-failed' } } };
1295
+ }
1296
+ const commit = runGitImpl(
1297
+ [...gitIdentityArgs(cwd, state.answers), 'commit', '-m', COMMIT_SUBJECT],
1298
+ cwd,
1299
+ );
1300
+ if (!commit.ok) {
1301
+ // A "nothing to commit" exit is benign — the wiring is already committed.
1302
+ Logger.warn(
1303
+ `[bootstrap] git commit did not create a commit (already committed?): ${commit.stderr || commit.stdout}`,
1304
+ );
1305
+ Logger.info(`\n[bootstrap] ${instructions}`);
1306
+ return { ok: true, payload: { commitPush: { action: 'commit-skipped' } } };
1307
+ }
1308
+ Logger.info('[bootstrap] Committed the Mandrel wiring.');
1309
+ const push = runGitImpl(['push', '-u', 'origin', branch], cwd);
1310
+ if (!push.ok) {
1311
+ Logger.warn(
1312
+ `[bootstrap] Commit landed but push of '${branch}' failed (push it manually with \`git push -u origin ${branch}\`): ${push.stderr}`,
1313
+ );
1314
+ return { ok: true, payload: { commitPush: { action: 'push-failed' } } };
1315
+ }
1316
+ Logger.info(`[bootstrap] Pushed '${branch}' to origin.`);
1317
+ return { ok: true, payload: { commitPush: { action: 'committed-pushed' } } };
1318
+ }
1319
+
1320
+ /** Pipeline driver — threads accumulated state through each phase. */
1321
+ export async function runPipeline(phases) {
1322
+ let state = {};
1323
+ for (const phase of phases) {
1324
+ const result = await phase(state);
1325
+ if (!result.ok) return { ok: false, exit: result.exit, state };
1326
+ state = { ...state, ...(result.payload ?? {}) };
1327
+ }
1328
+ return { ok: true, state };
1329
+ }
1330
+
1331
+ export async function main(argv = process.argv.slice(2), deps = {}) {
1332
+ // `deps.phases` lets tests inject a substitute pipeline so the
1333
+ // post-pipeline GitHub-failure detection can be exercised
1334
+ // deterministically without spawning `gh` (Story #3898).
1335
+ const phases = deps.phases ?? [
1336
+ () => parseAndValidate(argv),
1337
+ (s) => prepareContext(s),
1338
+ (s) => runPreflightPhase(s),
1339
+ (s) => collectAndConfirm(s),
1340
+ (s) => dryRunPlan(s),
1341
+ (s) => provisionResources(s),
1342
+ (s) => executeBootstrap(s),
1343
+ (s) => persistProjectNumber(s),
1344
+ (s) => executeGithubBootstrap(s),
1345
+ (s) => recordLedger(s),
1346
+ (s) => offerCommitPush(s),
1347
+ ];
1348
+ const result = await runPipeline(phases);
1349
+ if (!result.ok) return result.exit;
1350
+
1351
+ // GitHub-side bootstrap failures are non-fatal to the pipeline (so the
1352
+ // ledger still records the project-side mutations that already landed —
1353
+ // the failure is surfaced, not silently rolled back), but they MUST NOT
1354
+ // exit 0. `executeGithubBootstrap` records `report.github.error` instead
1355
+ // of throwing; detect it here and exit non-zero with a distinct final
1356
+ // status line so `create-mandrel` and CI see the failure (Story #3898).
1357
+ const githubError = result.state?.report?.github?.error;
1358
+ if (githubError) {
1359
+ Logger.error(
1360
+ `\n[bootstrap] GitHub bootstrap failed: ${githubError}. ` +
1361
+ 'Project-side setup (labels are GitHub-side; the local .agentrc.json / ' +
1362
+ 'quality-gate / workflow files that were applied are recorded in the ' +
1363
+ 'install ledger) completed, but the GitHub label/board/views/protection ' +
1364
+ 'setup did not. Resolve the cause above (commonly `gh auth login` or a ' +
1365
+ 'missing repo/project scope) and re-run `mandrel bootstrap` — the run is ' +
1366
+ 'idempotent and will skip what already succeeded.',
1367
+ );
1368
+ return 1;
1369
+ }
1370
+
1371
+ Logger.info('\n[bootstrap] Done.');
1372
+ return 0;
1373
+ }
1374
+
1375
+ runAsCli(import.meta.url, main, {
1376
+ source: 'Bootstrap',
1377
+ propagateExitCode: true,
1378
+ });