mandrel 1.59.0 → 1.61.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 (267) hide show
  1. package/.agents/README.md +86 -44
  2. package/.agents/docs/SDLC.md +135 -141
  3. package/.agents/docs/configuration.md +77 -20
  4. package/.agents/docs/quality-gates.md +796 -0
  5. package/.agents/docs/workflows.md +6 -9
  6. package/.agents/instructions.md +12 -11
  7. package/.agents/personas/architect.md +1 -1
  8. package/.agents/personas/product.md +1 -1
  9. package/.agents/personas/project-manager.md +14 -14
  10. package/.agents/personas/technical-writer.md +1 -1
  11. package/.agents/rules/changelog-style.md +5 -5
  12. package/.agents/rules/git-conventions.md +3 -3
  13. package/.agents/runtime-deps.json +2 -2
  14. package/.agents/schemas/agentrc.schema.json +3 -3
  15. package/.agents/schemas/dispatch-manifest.json +4 -4
  16. package/.agents/schemas/epic-spec.schema.json +15 -45
  17. package/.agents/schemas/lifecycle/README.md +1 -1
  18. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
  20. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
  21. package/.agents/schemas/validation-evidence.schema.json +1 -1
  22. package/.agents/scripts/README.md +2 -2
  23. package/.agents/scripts/acceptance-eval.js +1 -1
  24. package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
  25. package/.agents/scripts/agents-bootstrap-github.js +23 -119
  26. package/.agents/scripts/analyze-execution.js +2 -2
  27. package/.agents/scripts/audit-to-stories.js +1 -1
  28. package/.agents/scripts/check-doc-links.js +2 -3
  29. package/.agents/scripts/diagnose-friction.js +1 -1
  30. package/.agents/scripts/dispatcher.js +2 -2
  31. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  32. package/.agents/scripts/epic-audit-prepare.js +3 -3
  33. package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
  34. package/.agents/scripts/epic-deliver-preflight.js +6 -6
  35. package/.agents/scripts/epic-deliver-prepare.js +1 -1
  36. package/.agents/scripts/epic-execute-record-wave.js +4 -4
  37. package/.agents/scripts/epic-plan-healthcheck.js +6 -10
  38. package/.agents/scripts/epic-plan-spec-validate.js +1 -1
  39. package/.agents/scripts/epic-reconcile.js +11 -29
  40. package/.agents/scripts/evidence-gate.js +1 -1
  41. package/.agents/scripts/generate-workflows-doc.js +1 -1
  42. package/.agents/scripts/hierarchy-gate.js +7 -11
  43. package/.agents/scripts/lib/ITicketingProvider.js +1 -1
  44. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  45. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
  46. package/.agents/scripts/lib/baseline-snapshot.js +7 -7
  47. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  48. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  49. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
  50. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  51. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +47 -1
  52. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  53. package/.agents/scripts/lib/bootstrap/gh-preflight.js +7 -9
  54. package/.agents/scripts/lib/bootstrap/manifest.js +21 -1
  55. package/.agents/scripts/lib/bootstrap/merge-methods.js +31 -16
  56. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +32 -11
  57. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  58. package/.agents/scripts/lib/config/explain.js +1 -1
  59. package/.agents/scripts/lib/config/runners.js +2 -2
  60. package/.agents/scripts/lib/config/runtime.js +1 -1
  61. package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
  62. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  63. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  64. package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
  65. package/.agents/scripts/lib/config-settings-schema.js +3 -3
  66. package/.agents/scripts/lib/detect-package-manager.js +72 -0
  67. package/.agents/scripts/lib/duplicate-search.js +1 -1
  68. package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
  69. package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
  70. package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
  71. package/.agents/scripts/lib/errors/index.js +4 -4
  72. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
  73. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
  74. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  75. package/.agents/scripts/lib/findings/promote-finding.js +10 -10
  76. package/.agents/scripts/lib/label-constants.js +3 -4
  77. package/.agents/scripts/lib/label-taxonomy.js +5 -10
  78. package/.agents/scripts/lib/onboard/detect-stack.js +10 -10
  79. package/.agents/scripts/lib/onboard/init-tail.js +218 -0
  80. package/.agents/scripts/lib/onboard/scaffold-docs.js +18 -3
  81. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
  82. package/.agents/scripts/lib/orchestration/code-review.js +5 -5
  83. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
  84. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
  85. package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
  86. package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
  87. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +9 -25
  88. package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
  89. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +8 -8
  90. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
  91. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
  92. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
  93. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +26 -13
  94. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +1 -1
  95. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
  96. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +2 -2
  97. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
  98. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
  99. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
  100. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
  101. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +6 -21
  102. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
  103. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +1 -1
  104. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -2
  105. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
  106. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
  107. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +8 -8
  108. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +4 -4
  109. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
  110. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
  111. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
  112. package/.agents/scripts/lib/orchestration/file-assumptions.js +2 -2
  113. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
  114. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
  115. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
  116. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -3
  117. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
  118. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +1 -1
  119. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
  120. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
  121. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
  122. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
  123. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
  124. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  125. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
  126. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
  127. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +1 -1
  128. package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
  129. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
  130. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
  131. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
  132. package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
  133. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
  134. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
  135. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
  136. package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
  137. package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
  139. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  140. package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
  141. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  142. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
  143. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +2 -2
  144. package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
  145. package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
  146. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +2 -2
  147. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
  148. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
  149. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -12
  150. package/.agents/scripts/lib/orchestration/ticketing/reads.js +8 -8
  151. package/.agents/scripts/lib/orchestration/ticketing/state.js +3 -3
  152. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +2 -2
  153. package/.agents/scripts/lib/orchestration/wave-record-projection.js +1 -1
  154. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  155. package/.agents/scripts/lib/preflight-runner.js +1 -1
  156. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
  157. package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
  158. package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
  159. package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
  160. package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
  161. package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
  162. package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
  163. package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
  164. package/.agents/scripts/lib/runtime-deps/preflight.js +6 -6
  165. package/.agents/scripts/lib/signals/schema.js +1 -1
  166. package/.agents/scripts/lib/spec/index.js +1 -1
  167. package/.agents/scripts/lib/spec/loader.js +2 -2
  168. package/.agents/scripts/lib/spec/state.js +7 -16
  169. package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
  170. package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
  171. package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
  172. package/.agents/scripts/lib/story-lifecycle.js +8 -8
  173. package/.agents/scripts/lib/story-plan.js +1 -1
  174. package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
  175. package/.agents/scripts/lib/wave-runner/tick.js +1 -1
  176. package/.agents/scripts/lib/worktree/node-modules-strategy.js +5 -2
  177. package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
  178. package/.agents/scripts/lifecycle-emit.js +1 -1
  179. package/.agents/scripts/providers/github/board-add.js +1 -1
  180. package/.agents/scripts/providers/github/errors.js +1 -1
  181. package/.agents/scripts/providers/github/mappers.js +2 -2
  182. package/.agents/scripts/providers/github/tickets.js +4 -4
  183. package/.agents/scripts/resync-status-column.js +1 -1
  184. package/.agents/scripts/retro-run.js +2 -2
  185. package/.agents/scripts/run-lint.js +1 -1
  186. package/.agents/scripts/single-story-init.js +1 -1
  187. package/.agents/scripts/stories-wave-tick.js +5 -5
  188. package/.agents/scripts/story-close.js +1 -1
  189. package/.agents/scripts/story-init.js +13 -16
  190. package/.agents/scripts/story-phase.js +5 -5
  191. package/.agents/scripts/story-plan.js +3 -3
  192. package/.agents/scripts/sync-branch-from-base.js +1 -1
  193. package/.agents/scripts/validate-docs-freshness.js +1 -1
  194. package/.agents/scripts/wave-tick.js +1 -1
  195. package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
  196. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
  197. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
  198. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
  199. package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
  200. package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
  201. package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
  202. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
  203. package/.agents/skills/core/scope-triage/SKILL.md +9 -10
  204. package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
  205. package/.agents/skills/skills.index.json +7 -7
  206. package/.agents/templates/agent-protocol.md +2 -2
  207. package/.agents/workflows/agents-update.md +16 -31
  208. package/.agents/workflows/audit-architecture.md +2 -2
  209. package/.agents/workflows/audit-clean-code.md +2 -2
  210. package/.agents/workflows/audit-dependencies.md +1 -1
  211. package/.agents/workflows/audit-devops.md +1 -1
  212. package/.agents/workflows/audit-documentation.md +2 -2
  213. package/.agents/workflows/audit-lighthouse.md +1 -1
  214. package/.agents/workflows/audit-performance.md +2 -2
  215. package/.agents/workflows/audit-privacy.md +1 -1
  216. package/.agents/workflows/audit-quality.md +2 -2
  217. package/.agents/workflows/audit-security.md +2 -2
  218. package/.agents/workflows/audit-seo.md +1 -1
  219. package/.agents/workflows/audit-sre.md +1 -1
  220. package/.agents/workflows/audit-to-stories.md +10 -10
  221. package/.agents/workflows/audit-ux-ui.md +1 -1
  222. package/.agents/workflows/deliver.md +85 -0
  223. package/.agents/workflows/explain.md +3 -3
  224. package/.agents/workflows/git-merge-pr.md +1 -1
  225. package/.agents/workflows/git-pr-all.md +13 -10
  226. package/.agents/workflows/git-push.md +6 -3
  227. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  228. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  229. package/.agents/workflows/helpers/agents-sync-config.md +3 -2
  230. package/.agents/workflows/helpers/code-review.md +5 -5
  231. package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +43 -43
  232. package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
  233. package/.agents/workflows/helpers/diagnose.md +1 -1
  234. package/.agents/workflows/helpers/epic-audit.md +6 -6
  235. package/.agents/workflows/helpers/epic-deliver-story.md +13 -13
  236. package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
  237. package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
  238. package/.agents/workflows/helpers/epic-testing.md +3 -3
  239. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  240. package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
  241. package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
  242. package/.agents/workflows/helpers/signals.md +1 -1
  243. package/.agents/workflows/helpers/single-story-deliver.md +11 -11
  244. package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
  245. package/.agents/workflows/plan.md +131 -0
  246. package/.agents/workflows/qa-explore.md +1 -1
  247. package/.agents/workflows/qa-run-harness.md +1 -1
  248. package/README.md +19 -39
  249. package/bin/mandrel.js +235 -16
  250. package/docs/CHANGELOG.md +1173 -0
  251. package/lib/cli/doctor.js +45 -3
  252. package/lib/cli/init.js +97 -36
  253. package/lib/cli/registry.js +41 -145
  254. package/lib/cli/sync.js +122 -23
  255. package/lib/cli/uninstall.js +42 -7
  256. package/lib/cli/update.js +524 -210
  257. package/lib/cli/version-helpers.js +59 -0
  258. package/package.json +7 -6
  259. package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
  260. package/.agents/workflows/onboard.md +0 -208
  261. package/lib/cli/__tests__/migrate.test.js +0 -268
  262. package/lib/cli/__tests__/sync-local-zone.test.js +0 -247
  263. package/lib/cli/__tests__/sync.test.js +0 -372
  264. package/lib/cli/__tests__/update-major.test.js +0 -217
  265. package/lib/cli/__tests__/update.test.js +0 -696
  266. package/lib/cli/__tests__/version-check.test.js +0 -398
  267. package/lib/migrations/__tests__/index.test.js +0 -216
@@ -205,7 +205,7 @@ const GITHUB_SCHEMA = {
205
205
  };
206
206
 
207
207
  // ---------------------------------------------------------------------------
208
- // planning.* — inputs to /epic-plan
208
+ // planning.* — inputs to /plan
209
209
  // ---------------------------------------------------------------------------
210
210
 
211
211
  /**
@@ -224,7 +224,7 @@ const PLANNING_CONTEXT_SCHEMA = {
224
224
 
225
225
  /**
226
226
  * Story #2634 — `planning.codebaseSnapshot` controls the structural
227
- * view of the consumer repo threaded into `/epic-plan` Phase 7 spec
227
+ * view of the consumer repo threaded into `/plan` Phase 7 spec
228
228
  * authoring. Absent / partial entries resolve to defaults inside
229
229
  * `lib/codebase-snapshot.js#resolveSnapshotConfig` — the schema only
230
230
  * enforces shape (correct enum value, well-formed glob arrays).
@@ -287,7 +287,7 @@ const PLANNING_SCHEMA = {
287
287
  };
288
288
 
289
289
  // ---------------------------------------------------------------------------
290
- // delivery.* — /epic-deliver + story-deliver consume. The full block of
290
+ // delivery.* — /deliver + story-deliver consume. The full block of
291
291
  // per-key sub-schemas lives in `config-settings-schema-delivery.js` (refs
292
292
  // #3457); DELIVERY_SCHEMA is imported above and referenced unchanged below.
293
293
  // ---------------------------------------------------------------------------
@@ -0,0 +1,72 @@
1
+ /**
2
+ * detect-package-manager — shared lockfile-probe helper (Story #4048 B3).
3
+ *
4
+ * Five independent copies of this lockfile probe existed across the codebase:
5
+ * - `lib/cli/update.js#detectPackageManager`
6
+ * - `lib/bootstrap/project-bootstrap.js#detectPackageManager`
7
+ * - `lib/runtime-deps/preflight.js#detectPackageManager`
8
+ * - `lib/onboard/detect-stack.js#detectPackageManager`
9
+ * - `lib/worktree/node-modules-strategy.js#selectInstallCommand` (inline)
10
+ *
11
+ * This module is the single authoritative implementation. It uses the
12
+ * strictest semantics from the prior copies: detects `bun` in addition to
13
+ * pnpm/yarn/npm, returns `null` when the directory has no Node manifest at
14
+ * all (not even `package.json`), and optionally reports `workspaceRoot` for
15
+ * pnpm (the `update.js` caller's unique requirement).
16
+ *
17
+ * All callers must handle `null` explicitly — it means the directory carries
18
+ * no recognizable Node toolchain, so callers that need a concrete fallback
19
+ * should coerce: `detectPm(root) ?? 'npm'`.
20
+ *
21
+ * Injectable seams: the `exists` parameter replaces `fs.existsSync` so
22
+ * callers can drive the function with an in-memory fixture in unit tests.
23
+ *
24
+ * Builtins only — this module runs before third-party packages are
25
+ * guaranteed to be present and is also imported from the worktree and
26
+ * runtime-deps preflight guards.
27
+ */
28
+
29
+ import fs from 'node:fs';
30
+ import path from 'node:path';
31
+
32
+ /**
33
+ * Detect the package manager from lockfile and manifest presence.
34
+ *
35
+ * Precedence: `pnpm-lock.yaml` > `yarn.lock` > `bun.lockb` >
36
+ * `package-lock.json` > `package.json` (npm without a lockfile yet) > `null`.
37
+ *
38
+ * @param {string} root - Absolute directory to probe (consumer project root).
39
+ * @param {(p: string) => boolean} [exists=fs.existsSync] - Path existence probe.
40
+ * @returns {'pnpm'|'yarn'|'bun'|'npm'|null}
41
+ */
42
+ export function detectPackageManager(root, exists = fs.existsSync) {
43
+ if (exists(path.join(root, 'pnpm-lock.yaml'))) return 'pnpm';
44
+ if (exists(path.join(root, 'yarn.lock'))) return 'yarn';
45
+ if (exists(path.join(root, 'bun.lockb'))) return 'bun';
46
+ if (exists(path.join(root, 'package-lock.json'))) return 'npm';
47
+ if (exists(path.join(root, 'package.json'))) return 'npm';
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * Detect the package manager and whether the directory is a pnpm workspace
53
+ * root. The `workspaceRoot` flag is `true` only for pnpm when
54
+ * `pnpm-workspace.yaml` is present alongside the lockfile — the signal that
55
+ * `pnpm add` must carry `-w` to target the workspace-root manifest.
56
+ *
57
+ * Used by `lib/cli/update.js` which needs both pieces of information to
58
+ * construct the correct install command.
59
+ *
60
+ * @param {string} root - Absolute directory to probe.
61
+ * @param {(p: string) => boolean} [exists=fs.existsSync] - Path existence probe.
62
+ * @returns {{ packageManager: 'pnpm'|'yarn'|'bun'|'npm', workspaceRoot: boolean }}
63
+ */
64
+ export function detectPackageManagerWithWorkspace(
65
+ root,
66
+ exists = fs.existsSync,
67
+ ) {
68
+ const pm = detectPackageManager(root, exists) ?? 'npm';
69
+ const workspaceRoot =
70
+ pm === 'pnpm' && exists(path.join(root, 'pnpm-workspace.yaml'));
71
+ return { packageManager: pm, workspaceRoot };
72
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * duplicate-search.js — Cross-Epic Duplicate Detection
3
3
  *
4
- * Used by `/epic-plan` Phase 2 (s-plan-ideation) to surface open Epics
4
+ * Used by `/plan` Phase 2 (s-plan-ideation) to surface open Epics
5
5
  * whose scope overlaps with a sharpened one-pager before a new Epic is
6
6
  * created. Returns ranked candidates with an overlap score and URL so
7
7
  * the host LLM can pause for HITL confirmation.
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * Both paths MUST emit the identical per-lens report contract
28
28
  * (`{{auditOutputDir}}/<lens>-results.md`), so downstream consumers
29
- * (`/epic-deliver` Phase 4 epic-audit, `audit-to-stories`) are agnostic to
29
+ * (`/deliver` Phase 4 epic-audit, `audit-to-stories`) are agnostic to
30
30
  * which path produced it.
31
31
  *
32
32
  * ## Why this is capability-degradation, not a contract shim
@@ -10,7 +10,7 @@
10
10
  * Verdict rule: `clear` requires **both** (a) ≥ 4 of 5 canonical sections
11
11
  * present, **and** (b) the **Acceptance Criteria** section present. The
12
12
  * Acceptance-Criteria requirement is load-bearing: a downstream
13
- * `/epic-deliver` start gate and the close-time acceptance-spec reconciler
13
+ * `/deliver` start gate and the close-time acceptance-spec reconciler
14
14
  * both assume the Epic carries acceptance criteria, so a gate that passed an
15
15
  * Epic with no Acceptance Criteria (the pre-Story-#3910 `≥ 4 of 5` behaviour)
16
16
  * advertised a clarity guarantee it did not provide. AC is now a required
@@ -1,5 +1,5 @@
1
1
  /**
2
- * epic-plan-ideation.js — Phase 3/4 helpers for /epic-plan
2
+ * epic-plan-ideation.js — Phase 3/4 helpers for /plan
3
3
  *
4
4
  * Phase 3: render an Epic body from a sharpened ideation one-pager
5
5
  * using the canonical template at `.agents/templates/epic-from-idea.md`.
@@ -52,10 +52,10 @@ export class GhVersionError extends Error {
52
52
  /**
53
53
  * Raised when a framework runtime dependency (e.g. `ajv`) cannot be
54
54
  * resolved from the consumer's `node_modules/`. Surfaces during the
55
- * `/agents-bootstrap-github` preflight to redirect operators to
56
- * `/agents-bootstrap-project`, which is the workflow responsible for
57
- * merging and installing the framework's runtime deps. `missing` carries
58
- * the package specifiers that failed to resolve so the CLI can render an
55
+ * `agents-bootstrap-github` preflight to redirect operators to the
56
+ * correct remediation (`mandrel init` for new projects, or
57
+ * `npm install mandrel` for existing ones). `missing` carries the
58
+ * package specifiers that failed to resolve so the CLI can render an
59
59
  * actionable hint.
60
60
  */
61
61
  export class MissingRuntimeDepsError extends Error {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * memory-freshness.js — walker + verifier for the `/epic-plan` Phase 0
2
+ * memory-freshness.js — walker + verifier for the `/plan` Phase 0
3
3
  * memory-freshness pre-flight. Story #2557 / Epic #2547. Tech Spec #2550.
4
4
  *
5
5
  * Walks every `.md` file under a memory directory (typically
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * prior-feedback-fetcher.js — gh-CLI-backed fetcher for open meta feedback
3
- * issues that feed the `/epic-plan` Phase 0 planner context.
3
+ * issues that feed the `/plan` Phase 0 planner context.
4
4
  *
5
5
  * Story #2554 / Epic #2547. Tech Spec #2550 specifies that the fetcher MUST
6
6
  * return open issues carrying the `meta::framework-gap` and
@@ -51,7 +51,7 @@ export const FINDING_CLASSES = Object.freeze([
51
51
  /**
52
52
  * Class → label-set routing table. Each class maps to exactly one label set.
53
53
  * `tooling-dx` is the framework-gap path: it carries `meta::framework-gap` so
54
- * the `/epic-plan` Phase 0 feedback fetcher surfaces it to the planner.
54
+ * the `/plan` Phase 0 feedback fetcher surfaces it to the planner.
55
55
  */
56
56
  const CLASS_TO_LABELS = Object.freeze({
57
57
  'product-bug': [FOCUS_LABELS.PRODUCT],
@@ -5,8 +5,8 @@
5
5
  * tail of the exploratory-QA Triage path: once an operator has dispositioned a
6
6
  * session's ledger items (see `.agents/schemas/qa-ledger.schema.json` and
7
7
  * `lib/qa/qa-session.js`), the still-untriaged backlog is clustered and each
8
- * cluster is promoted to a follow-up ticket — a single Story (via `/story-plan`)
9
- * for a tight, one-deliverable cluster, or an Epic (via `/epic-plan --idea`) for
8
+ * cluster is promoted to a follow-up ticket — a single Story (via `/plan`)
9
+ * for a tight, one-deliverable cluster, or an Epic (via `/plan --idea`) for
10
10
  * a broad cluster that spans multiple coverage surfaces. Each contributing
11
11
  * ledger item then has the resulting `routedTo` issue link written back onto it
12
12
  * so a resume run sees the item as filed rather than re-promoting it.
@@ -23,7 +23,7 @@
23
23
  * Pure orchestration: **no network I/O lives here.** Every GitHub side-effect
24
24
  * (issue search, ticket creation) flows through INJECTED PORTS so the unit test
25
25
  * runs with no network. Production wires the ports to the GitHub provider /
26
- * `/story-plan` / `/epic-plan` surfaces; tests pass in-memory stubs.
26
+ * `/plan` / `/plan` surfaces; tests pass in-memory stubs.
27
27
  */
28
28
 
29
29
  import { fingerprintFinding, routeFinding } from './route-finding.js';
@@ -40,8 +40,8 @@ export const PROMOTION_TARGETS = Object.freeze({
40
40
 
41
41
  /**
42
42
  * A cluster of more than this many distinct coverage surfaces is broad enough
43
- * to warrant an Epic (`/epic-plan --idea`) rather than a single Story
44
- * (`/story-plan`). One or two surfaces is a tight, single-deliverable cluster.
43
+ * to warrant an Epic (`/plan --idea`) rather than a single Story
44
+ * (`/plan`). One or two surfaces is a tight, single-deliverable cluster.
45
45
  */
46
46
  const EPIC_COVERAGE_THRESHOLD = 2;
47
47
 
@@ -159,8 +159,8 @@ export function clusterLedgerItems(items) {
159
159
  /**
160
160
  * Decide a cluster's promotion target. A cluster that spans more than
161
161
  * {@link EPIC_COVERAGE_THRESHOLD} distinct coverage surfaces is broad enough to
162
- * warrant an Epic (`/epic-plan --idea`); otherwise it is a single-deliverable
163
- * Story (`/story-plan`).
162
+ * warrant an Epic (`/plan --idea`); otherwise it is a single-deliverable
163
+ * Story (`/plan`).
164
164
  *
165
165
  * @param {{ coverages: string[] }} cluster
166
166
  * @returns {'story'|'epic'}
@@ -233,7 +233,7 @@ function routedToLink(issue, kind) {
233
233
  * shared `routeFinding` against existing Issues (via the injected search
234
234
  * port). This dedups against work already filed.
235
235
  * 2. On a `new` decision, open the follow-up ticket through the injected
236
- * `createStory` (`/story-plan`) or `createEpic` (`/epic-plan --idea`) port,
236
+ * `createStory` (`/plan`) or `createEpic` (`/plan --idea`) port,
237
237
  * chosen by {@link targetForCluster}. On any other decision, link back to
238
238
  * the matched Issue rather than creating a duplicate.
239
239
  * 3. Stamp the resolved `routedTo` link onto every contributing ledger item
@@ -250,9 +250,9 @@ function routedToLink(issue, kind) {
250
250
  * @param {(finding: object) => Promise<Array<{ number: number, state: string, title?: string, body?: string }>>} [ports.searchCandidates]
251
251
  * Optional semantic candidate search, forwarded to `routeFinding`.
252
252
  * @param {(cluster: object) => Promise<{ number: number, url?: string }>} ports.createStory
253
- * Opens a single Story (`/story-plan`) for a tight cluster.
253
+ * Opens a single Story (`/plan`) for a tight cluster.
254
254
  * @param {(cluster: object) => Promise<{ number: number, url?: string }>} ports.createEpic
255
- * Opens an Epic (`/epic-plan --idea`) for a broad cluster.
255
+ * Opens an Epic (`/plan --idea`) for a broad cluster.
256
256
  * @returns {Promise<{
257
257
  * promotions: Array<{
258
258
  * clusterKey: string,
@@ -76,7 +76,6 @@ export function isValidTransition(fromState, toState) {
76
76
 
77
77
  export const TYPE_LABELS = {
78
78
  EPIC: 'type::epic',
79
- FEATURE: 'type::feature',
80
79
  STORY: 'type::story',
81
80
  };
82
81
 
@@ -104,7 +103,7 @@ export const CONTEXT_LABELS = {
104
103
  export const CONTEXT_ACCEPTANCE_SPEC = CONTEXT_LABELS.ACCEPTANCE_SPEC;
105
104
 
106
105
  /**
107
- * Acceptance-axis labels for opt-out signalling on Stories and Features that
106
+ * Acceptance-axis labels for opt-out signalling on Stories that
108
107
  * intentionally have no acceptance-spec coverage. Separate namespace from
109
108
  * `context::` because it expresses absence rather than a linked context
110
109
  * ticket.
@@ -120,7 +119,7 @@ export const ACCEPTANCE_NA = ACCEPTANCE_LABELS.N_A;
120
119
  * loop). `meta::framework-gap` is applied to issues that surface a defect or
121
120
  * missing capability in the framework itself; `meta::consumer-improvement`
122
121
  * is applied to issues that surface improvements to a consumer project
123
- * (workflow tweaks, ergonomic asks, doc polish). The `/epic-plan` Phase 0
122
+ * (workflow tweaks, ergonomic asks, doc polish). The `/plan` Phase 0
124
123
  * fetcher (see `lib/feedback-loop/prior-feedback-fetcher.js`) reads open
125
124
  * issues carrying either label and surfaces them to the planner so retro
126
125
  * signals are routed into durable substrates rather than lost in chat.
@@ -133,7 +132,7 @@ export const META_LABELS = {
133
132
  /**
134
133
  * Planning-axis labels (Epic #2880 F7). Currently scoped to the
135
134
  * `planning::healthcheck-waived` operator-applied waiver, which is the
136
- * documented escape hatch for the `/epic-plan` Phase 10 readiness
135
+ * documented escape hatch for the `/plan` Phase 10 readiness
137
136
  * healthcheck (`epic-plan-healthcheck.js`). The persist half of
138
137
  * `epic-plan-decompose.js` refuses to flip an Epic to `agent::ready`
139
138
  * when the healthcheck returned `ok: false` unless this label is
@@ -53,15 +53,10 @@ export const LABEL_TAXONOMY = [
53
53
  color: LABEL_COLORS.TYPE,
54
54
  description: 'Epic-level work item',
55
55
  },
56
- {
57
- name: TYPE_LABELS.FEATURE,
58
- color: LABEL_COLORS.TYPE,
59
- description: 'Feature under an Epic',
60
- },
61
56
  {
62
57
  name: TYPE_LABELS.STORY,
63
58
  color: LABEL_COLORS.TYPE,
64
- description: 'User story under a Feature',
59
+ description: 'User story under an Epic',
65
60
  },
66
61
 
67
62
  // Agent State
@@ -75,7 +70,7 @@ export const LABEL_TAXONOMY = [
75
70
  name: AGENT_LABELS.READY,
76
71
  color: LABEL_COLORS.AGENT,
77
72
  description:
78
- 'Parking state — frozen dispatch manifest exists; awaiting local /epic-deliver',
73
+ 'Parking state — frozen dispatch manifest exists; awaiting local /deliver',
79
74
  },
80
75
  {
81
76
  name: AGENT_LABELS.EXECUTING,
@@ -120,7 +115,7 @@ export const LABEL_TAXONOMY = [
120
115
  description: 'Acceptance Specification (Gherkin scenarios)',
121
116
  },
122
117
 
123
- // Acceptance axis — explicit opt-out signal for Stories/Features that
118
+ // Acceptance axis — explicit opt-out signal for Stories that
124
119
  // intentionally have no acceptance-spec coverage.
125
120
  {
126
121
  name: ACCEPTANCE_LABELS.N_A,
@@ -169,8 +164,8 @@ export const STATUS_FIELD_OPTIONS = ['Todo', 'In Progress', 'Done'];
169
164
  *
170
165
  * GitHub's GraphQL surface does not expose a public `createProjectV2View`
171
166
  * mutation, so bootstrap creates these via the REST Projects V2 views
172
- * endpoint best-effort and falls back to documenting the filter strings in
173
- * `docs/project-board.md` when the endpoint is unavailable.
167
+ * endpoint best-effort; when the endpoint is unavailable the views must be
168
+ * configured manually in the GitHub Projects UI.
174
169
  *
175
170
  * @type {Array<{ name: string, filter: string, groupBy: string,
176
171
  * layout?: 'table'|'board'|'roadmap' }>}
@@ -1,11 +1,12 @@
1
1
  /**
2
- * detect-stack.js — Consumer stack detection for `/onboard`
2
+ * detect-stack.js — Consumer stack detection for `mandrel init`
3
3
  *
4
4
  * Inspects a consumer repository root and reports the package manager,
5
5
  * test runner, and primary language it can infer from on-disk signals
6
6
  * (lockfiles, `package.json` contents, and source-file extensions). The
7
- * guided `/onboard` flow (Feature #3514) uses this to tell the operator
8
- * what it found before scaffolding missing `docsContextFiles`.
7
+ * `mandrel init` configure-path tail (Feature #3514, Story #4045) uses
8
+ * this to tell the operator what it found before scaffolding missing
9
+ * `docsContextFiles`.
9
10
  *
10
11
  * The detection functions are seam-injectable: each takes an injected
11
12
  * filesystem facade (`exists` / `readFile` / `listExtensions`) so they
@@ -19,6 +20,7 @@
19
20
 
20
21
  import fs from 'node:fs';
21
22
  import path from 'node:path';
23
+ import { detectPackageManager as detectPm } from '../detect-package-manager.js';
22
24
 
23
25
  /**
24
26
  * Filesystem facade. Pure detection logic talks to disk only through
@@ -163,18 +165,16 @@ export const defaultFsFacade = {
163
165
  * when no lockfile is found but a `package.json` exists, and `null` when
164
166
  * the repo has no Node manifest at all.
165
167
  *
168
+ * Delegates to the shared `detectPackageManager` helper
169
+ * (Story #4048 B3 — one implementation per concept). The `fsFacade.exists`
170
+ * seam is forwarded directly.
171
+ *
166
172
  * @param {string} root - Repository root.
167
173
  * @param {FsFacade} [fsFacade=defaultFsFacade]
168
174
  * @returns {'pnpm'|'yarn'|'bun'|'npm'|null}
169
175
  */
170
176
  export function detectPackageManager(root, fsFacade = defaultFsFacade) {
171
- const { exists } = fsFacade;
172
- if (exists(path.join(root, 'pnpm-lock.yaml'))) return 'pnpm';
173
- if (exists(path.join(root, 'yarn.lock'))) return 'yarn';
174
- if (exists(path.join(root, 'bun.lockb'))) return 'bun';
175
- if (exists(path.join(root, 'package-lock.json'))) return 'npm';
176
- if (exists(path.join(root, 'package.json'))) return 'npm';
177
- return null;
177
+ return detectPm(root, fsFacade.exists);
178
178
  }
179
179
 
180
180
  /**
@@ -0,0 +1,218 @@
1
+ /**
2
+ * init-tail.js — post-bootstrap onboarding tail for `mandrel init`.
3
+ *
4
+ * Called by `mandrel init` after `bootstrap.js` completes successfully on
5
+ * the "configure now" path. Sequences the four phases that walk an operator
6
+ * from a freshly bootstrapped project to a ready-to-plan workspace:
7
+ *
8
+ * Phase 1 — Detect the consumer stack (lib/onboard/detect-stack.js).
9
+ * Phase 2 — Offer to scaffold missing docsContextFiles (scaffold-docs.js).
10
+ * Phase 3 — Run `mandrel doctor` as a readiness gate.
11
+ * Phase 4 — Print the /plan handoff next-step text.
12
+ *
13
+ * The whole tail is idempotent: re-running after an already-onboarded project
14
+ * re-detects, re-checks, and re-offers scaffolding without duplicating stubs
15
+ * (the scaffolder only writes genuinely absent files) and without modifying
16
+ * anything (doctor is read-only).
17
+ *
18
+ * Injectable seams: `runDoctor`, `stdout`, `confirmScaffold`, and `isTTY`
19
+ * allow the unit suite to drive every branch without real I/O.
20
+ *
21
+ * Story #4045 (refs #4045).
22
+ */
23
+
24
+ import { spawnSync as defaultSpawnSync } from 'node:child_process';
25
+ import fs from 'node:fs';
26
+ import path from 'node:path';
27
+
28
+ import { detectStack } from './detect-stack.js';
29
+ import { STUB_MARKER, scaffoldDocs } from './scaffold-docs.js';
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Phase text constants
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Text printed at the end of the init tail to hand the operator off to /plan.
37
+ *
38
+ * @type {string}
39
+ */
40
+ export const PLAN_HANDOFF_TEXT =
41
+ '\n✅ Mandrel is ready. Start your first Epic:\n\n' +
42
+ ' /plan --idea "<one-line description of what you want to build>"\n\n' +
43
+ 'Or, if you already have a `type::epic` Issue open:\n\n' +
44
+ ' /plan <epicId>\n';
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Internal helpers
48
+ // ---------------------------------------------------------------------------
49
+
50
+ /**
51
+ * Format a stack-detection result as a human-readable report line.
52
+ *
53
+ * @param {{ packageManager: string|null, testRunner: string|null, primaryLanguage: string|null }} stack
54
+ * @returns {string}
55
+ */
56
+ function formatStackReport(stack) {
57
+ const pm = stack.packageManager ?? '(unknown)';
58
+ const runner = stack.testRunner ?? '(unknown)';
59
+ const lang = stack.primaryLanguage ?? '(unknown)';
60
+ return (
61
+ '\n[init] Stack detection:\n' +
62
+ ` Package manager : ${pm}\n` +
63
+ ` Test runner : ${runner}\n` +
64
+ ` Primary language: ${lang}\n`
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Format a list of missing docs as a human-readable report (no prompt).
70
+ *
71
+ * @param {string[]} missing
72
+ * @returns {string}
73
+ */
74
+ function formatMissingList(missing) {
75
+ if (missing.length === 0) return '';
76
+ const list = missing.map((f) => ` • ${f}`).join('\n');
77
+ return (
78
+ '\n[init] The following docsContextFiles are missing — agents load\n' +
79
+ 'these before every task:\n' +
80
+ `${list}\n`
81
+ );
82
+ }
83
+
84
+ /** Prompt text shown only on a TTY when asking to scaffold. */
85
+ const SCAFFOLD_PROMPT = '\nScaffold stubs now? [y/N]: ';
86
+
87
+ /**
88
+ * Synchronous y/N read from stdin (fd 0). Returns `false` on any read error
89
+ * or when the user enters something other than `y` / `yes`.
90
+ *
91
+ * @returns {boolean}
92
+ */
93
+ function syncConfirm() {
94
+ let answer = '';
95
+ try {
96
+ const buf = fs.readFileSync(0, 'utf8');
97
+ answer = buf.split('\n', 1)[0].trim().toLowerCase();
98
+ } catch {
99
+ answer = '';
100
+ }
101
+ return answer === 'y' || answer === 'yes';
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Public API
106
+ // ---------------------------------------------------------------------------
107
+
108
+ /**
109
+ * Run the post-bootstrap init tail.
110
+ *
111
+ * @param {object} [opts]
112
+ * @param {string} [opts.root] - Project root (defaults to `process.cwd()`).
113
+ * @param {(msg: string) => void} [opts.stdout] - Output sink (defaults to
114
+ * `process.stdout.write` bound to `process.stdout`).
115
+ * @param {() => boolean} [opts.confirmScaffold] - Read the operator's y/N
116
+ * answer for the scaffold offer. Returns `true` to scaffold. In non-TTY
117
+ * contexts defaults to `false` (no write without operator confirmation).
118
+ * @param {(extraArgs?: string[]) => { status: number|null }} [opts.runDoctor]
119
+ * - Run `mandrel doctor`; injectable for tests.
120
+ * @param {boolean} [opts.isTTY] - Whether stdin is a TTY (defaults to
121
+ * `Boolean(process.stdin.isTTY)`).
122
+ * @returns {{
123
+ * stack: { packageManager: string|null, testRunner: string|null, primaryLanguage: string|null },
124
+ * scaffoldResult: object,
125
+ * doctorStatus: number,
126
+ * ok: boolean,
127
+ * }}
128
+ */
129
+ export function runInitTail({
130
+ root,
131
+ stdout = (s) => process.stdout.write(s),
132
+ confirmScaffold,
133
+ runDoctor,
134
+ isTTY,
135
+ } = {}) {
136
+ const projectRoot = root ?? process.cwd();
137
+ const tty = isTTY ?? Boolean(process.stdin.isTTY);
138
+
139
+ // When confirmScaffold is explicitly injected (e.g. from tests), always use
140
+ // it. When using the default, auto-decline on non-TTY so the scaffolder
141
+ // never writes unattended.
142
+ const usingDefaultConfirm = confirmScaffold == null;
143
+ const confirmFn = confirmScaffold ?? (() => syncConfirm());
144
+
145
+ // Default doctor runner — spawns `mandrel doctor` via the locally installed
146
+ // bin; inherits stdio so the report streams to the terminal.
147
+ const mandrelBin = path.join(
148
+ projectRoot,
149
+ 'node_modules',
150
+ 'mandrel',
151
+ 'bin',
152
+ 'mandrel.js',
153
+ );
154
+ const defaultRunDoctor = (extraArgs = []) =>
155
+ defaultSpawnSync(process.execPath, [mandrelBin, 'doctor', ...extraArgs], {
156
+ cwd: projectRoot,
157
+ stdio: 'inherit',
158
+ });
159
+
160
+ const doctorFn = runDoctor ?? defaultRunDoctor;
161
+
162
+ // --- Phase 1: Detect the stack -------------------------------------------
163
+ let stack;
164
+ try {
165
+ stack = detectStack(projectRoot);
166
+ } catch {
167
+ stack = { packageManager: null, testRunner: null, primaryLanguage: null };
168
+ }
169
+ stdout(formatStackReport(stack));
170
+
171
+ // --- Phase 2: Offer to scaffold missing docsContextFiles -----------------
172
+ const preview = scaffoldDocs({ root: projectRoot, write: false });
173
+ let scaffoldResult = preview;
174
+
175
+ if (preview.missing.length === 0) {
176
+ stdout('\n[init] All docsContextFiles are present.\n');
177
+ } else {
178
+ stdout(formatMissingList(preview.missing));
179
+ // On non-TTY without an injected confirm, auto-decline so the scaffolder
180
+ // never writes unattended. On TTY (or with an injected confirm seam), show
181
+ // the prompt and consult the confirm function.
182
+ const canPrompt = tty || !usingDefaultConfirm;
183
+ if (canPrompt) stdout(SCAFFOLD_PROMPT);
184
+ const accepted = canPrompt ? confirmFn() : false;
185
+ if (accepted) {
186
+ scaffoldResult = scaffoldDocs({ root: projectRoot, write: true });
187
+ if (scaffoldResult.created.length > 0) {
188
+ stdout(
189
+ `[init] Scaffolded ${scaffoldResult.created.length} stub(s). ` +
190
+ `Each carries a \`${STUB_MARKER}\` marker — replace placeholder ` +
191
+ 'content before planning.\n',
192
+ );
193
+ }
194
+ } else {
195
+ stdout(
196
+ '[init] Scaffolding declined. docsContextFiles are still missing — ' +
197
+ 'agents will load degraded context until you create them.\n',
198
+ );
199
+ }
200
+ }
201
+
202
+ // --- Phase 3: Readiness gate (mandrel doctor) ----------------------------
203
+ stdout('\n[init] Running mandrel doctor…\n');
204
+ const doctorResult = doctorFn();
205
+ const doctorStatus = doctorResult?.status ?? 1;
206
+
207
+ if (doctorStatus !== 0) {
208
+ stdout(
209
+ '\n[init] ❌ Doctor check failed. Resolve the remedies above and\n' +
210
+ 'then re-run: mandrel init\n',
211
+ );
212
+ return { stack, scaffoldResult, doctorStatus, ok: false };
213
+ }
214
+
215
+ // --- Phase 4: Handoff to /plan -------------------------------------------
216
+ stdout(PLAN_HANDOFF_TEXT);
217
+ return { stack, scaffoldResult, doctorStatus, ok: true };
218
+ }
@@ -24,6 +24,15 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
24
  const AGENT_ROOT = path.resolve(__dirname, '../../..');
25
25
  const DOCS_TEMPLATE_DIR = path.join(AGENT_ROOT, 'templates', 'docs');
26
26
 
27
+ /**
28
+ * Deterministic marker written into every scaffolded stub. The `/plan`
29
+ * first-run preflight (and any tooling that wants to detect unedited stubs)
30
+ * keys off this exact string — do not change it without a hard cutover.
31
+ *
32
+ * @type {string}
33
+ */
34
+ export const STUB_MARKER = '<!-- MANDREL:STUB -->';
35
+
27
36
  /**
28
37
  * Build the generic placeholder stub for a docsContextFile that has no
29
38
  * dedicated template. Derives a human-readable title from the filename.
@@ -39,8 +48,9 @@ function genericStub(fileName) {
39
48
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
40
49
  .join(' ');
41
50
  return (
51
+ `${STUB_MARKER}\n` +
42
52
  `# ${title}\n\n` +
43
- '> **Stub generated by guided onboarding.** This file is one of the\n' +
53
+ '> **Stub generated by `mandrel init`.** This file is one of the\n' +
44
54
  '> `project.docsContextFiles` mandatory-reads — agents load it before\n' +
45
55
  '> every task. Replace this placeholder with real content.\n'
46
56
  );
@@ -48,7 +58,9 @@ function genericStub(fileName) {
48
58
 
49
59
  /**
50
60
  * Read the dedicated template body for a docsContextFile, or fall back to the
51
- * generic stub when no template ships for that name.
61
+ * generic stub when no template ships for that name. Either path prepends the
62
+ * {@link STUB_MARKER} so the `/plan` first-run preflight can detect un-edited
63
+ * stubs regardless of whether a dedicated template was used.
52
64
  *
53
65
  * @param {string} fileName
54
66
  * @param {import('node:fs')} fsImpl
@@ -57,7 +69,10 @@ function genericStub(fileName) {
57
69
  function stubContentFor(fileName, fsImpl) {
58
70
  const templatePath = path.join(DOCS_TEMPLATE_DIR, fileName);
59
71
  if (fsImpl.existsSync(templatePath)) {
60
- return fsImpl.readFileSync(templatePath, 'utf8');
72
+ const body = fsImpl.readFileSync(templatePath, 'utf8');
73
+ // Prepend the marker only when absent (idempotent; the template files
74
+ // themselves are kept marker-free so they read cleanly as documentation).
75
+ return body.startsWith(STUB_MARKER) ? body : `${STUB_MARKER}\n${body}`;
61
76
  }
62
77
  return genericStub(fileName);
63
78
  }
@@ -152,7 +152,7 @@ export function decideAcceptanceEval({ verdict, maxRounds, round: roundIn }) {
152
152
  /**
153
153
  * Build the per-criterion acceptance-eval signal payload for the retro /
154
154
  * feedback substrate. Carries which acceptance items needed rework and the
155
- * round count so `/epic-plan` Phase 0 feedback fetch and the retro can
155
+ * round count so `/plan` Phase 0 feedback fetch and the retro can
156
156
  * surface acceptance churn. PII-free by construction — it carries only
157
157
  * acceptance-item indices, verdicts, and the terminal decision.
158
158
  *