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
package/bin/mandrel.js CHANGED
@@ -2,35 +2,252 @@
2
2
  // bin/mandrel.js — mandrel CLI entry point
3
3
 
4
4
  /**
5
- * Convention-based subcommand dispatcher.
5
+ * Allowlist-based subcommand dispatcher.
6
6
  *
7
- * Resolves `process.argv[2]` to `lib/cli/<name>.js` and dynamically
8
- * imports it so the subcommand surface can grow without touching this
9
- * file. Each subcommand module must export a default function `run(argv)`.
7
+ * Only modules listed in SUBCOMMANDS are dispatchable. Each entry declares the
8
+ * name, a one-line description for help output, and the set of known flags so
9
+ * the dispatcher can reject unknown flags before loading the subcommand.
10
+ *
11
+ * Supported top-level flags:
12
+ * --help / -h Print subcommand list and exit 0.
13
+ * --version Print installed version and exit 0.
14
+ *
15
+ * Each subcommand module must export a default function `run(argv)`.
10
16
  */
11
17
 
18
+ import { createRequire } from 'node:module';
12
19
  import path from 'node:path';
13
20
  import { fileURLToPath, pathToFileURL } from 'node:url';
14
21
 
15
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
23
 
17
- function usage(badSub) {
18
- if (badSub) {
19
- process.stderr.write(
20
- `mandrel: unknown subcommand '${badSub}'\n\nUsage: mandrel <subcommand> [args]\n`,
21
- );
22
- } else {
23
- process.stderr.write('Usage: mandrel <subcommand> [args]\n');
24
+ // ---------------------------------------------------------------------------
25
+ // Subcommand registry — the ONLY allowed dispatch targets
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /**
29
+ * @typedef {{ description: string, knownFlags: Set<string> }} SubcommandMeta
30
+ * @type {Map<string, SubcommandMeta>}
31
+ */
32
+ const SUBCOMMANDS = new Map([
33
+ [
34
+ 'init',
35
+ {
36
+ description: 'install and configure mandrel in the current project',
37
+ knownFlags: new Set([
38
+ '--assume-yes',
39
+ '--skip-github',
40
+ '--owner',
41
+ '--repo',
42
+ '--base-branch',
43
+ '--project-number',
44
+ '--operator-handle',
45
+ '--dry-run',
46
+ ]),
47
+ },
48
+ ],
49
+ [
50
+ 'sync',
51
+ {
52
+ description: 'materialize .agents/ payload from installed package',
53
+ knownFlags: new Set(['--dry-run']),
54
+ },
55
+ ],
56
+ [
57
+ 'sync-commands',
58
+ {
59
+ description: 'regenerate .claude/commands/ from .agents/workflows/',
60
+ knownFlags: new Set(['--dry-run']),
61
+ },
62
+ ],
63
+ [
64
+ 'doctor',
65
+ {
66
+ description: 'run readiness checks and report remedies',
67
+ knownFlags: new Set([]),
68
+ },
69
+ ],
70
+ [
71
+ 'update',
72
+ {
73
+ description: 'upgrade mandrel to the newest published version',
74
+ knownFlags: new Set(['--dry-run', '--install-cmd']),
75
+ },
76
+ ],
77
+ [
78
+ 'migrate',
79
+ {
80
+ description: 'apply version-keyed migrations for a version range',
81
+ knownFlags: new Set(['--from', '--to', '--dry-run']),
82
+ },
83
+ ],
84
+ [
85
+ 'explain',
86
+ {
87
+ description: 'print resolved config values and their sources',
88
+ knownFlags: new Set(['--json']),
89
+ },
90
+ ],
91
+ [
92
+ 'uninstall',
93
+ {
94
+ description: 'reverse a recorded install using the install ledger',
95
+ knownFlags: new Set(['--include-github', '--dry-run']),
96
+ },
97
+ ],
98
+ ]);
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Helpers
102
+ // ---------------------------------------------------------------------------
103
+
104
+ /**
105
+ * Read the installed mandrel version from the root package.json.
106
+ *
107
+ * @returns {string}
108
+ */
109
+ function installedVersion() {
110
+ const req = createRequire(import.meta.url);
111
+ const manifest = req('../package.json');
112
+ return String(manifest.version);
113
+ }
114
+
115
+ /**
116
+ * Print the help screen listing all subcommands with descriptions.
117
+ *
118
+ * @param {(s: string) => void} [write]
119
+ */
120
+ function printHelp(write = (s) => process.stdout.write(s)) {
121
+ const lines = ['Usage: mandrel <subcommand> [args]\n', '\nSubcommands:\n'];
122
+ for (const [name, meta] of SUBCOMMANDS) {
123
+ const pad = ' '.repeat(Math.max(1, 16 - name.length));
124
+ lines.push(` ${name}${pad}${meta.description}\n`);
125
+ }
126
+ lines.push(
127
+ '\nFlags:\n',
128
+ ' --help, -h Print this help message\n',
129
+ ' --version Print the installed version\n',
130
+ '\nRun `mandrel <subcommand> --help` for subcommand-specific flags.\n',
131
+ );
132
+ write(lines.join(''));
133
+ }
134
+
135
+ /**
136
+ * Suggest the closest known subcommand name for a typo (Levenshtein-1).
137
+ *
138
+ * @param {string} input
139
+ * @returns {string | undefined}
140
+ */
141
+ function suggest(input) {
142
+ for (const name of SUBCOMMANDS.keys()) {
143
+ if (levenshtein(input, name) <= 2) return name;
144
+ }
145
+ return undefined;
146
+ }
147
+
148
+ /**
149
+ * Compute Levenshtein edit distance between two strings (capped at 3 for
150
+ * performance — we only care about small distances).
151
+ *
152
+ * @param {string} a
153
+ * @param {string} b
154
+ * @returns {number}
155
+ */
156
+ function levenshtein(a, b) {
157
+ if (a === b) return 0;
158
+ if (Math.abs(a.length - b.length) > 3) return 4;
159
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
160
+ for (let i = 0; i < a.length; i++) {
161
+ const curr = [i + 1];
162
+ for (let j = 0; j < b.length; j++) {
163
+ curr[j + 1] = Math.min(
164
+ curr[j] + 1,
165
+ prev[j + 1] + 1,
166
+ prev[j] + (a[i] === b[j] ? 0 : 1),
167
+ );
168
+ }
169
+ prev.splice(0, prev.length, ...curr);
170
+ }
171
+ return prev[b.length];
172
+ }
173
+
174
+ /**
175
+ * Universal flags that all subcommands accept and that bypass per-subcommand
176
+ * flag validation. Subcommands may handle these themselves internally.
177
+ */
178
+ const UNIVERSAL_FLAGS = new Set(['--help', '-h']);
179
+
180
+ /**
181
+ * Validate the argv array against the known flags for a subcommand.
182
+ * Returns null when all flags are known; an error message string when an
183
+ * unknown flag is detected. Values following `=` or the next positional are
184
+ * allowed — only the flag name prefix is checked.
185
+ *
186
+ * Universal flags (--help, -h) are always allowed and bypass this check.
187
+ *
188
+ * @param {string} subName
189
+ * @param {Set<string>} knownFlags
190
+ * @param {string[]} argv
191
+ * @returns {string | null}
192
+ */
193
+ function findUnknownFlag(subName, knownFlags, argv) {
194
+ for (const arg of argv) {
195
+ if (!arg.startsWith('-')) continue;
196
+ // Strip value portion for `--flag=value` form.
197
+ const flagName = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
198
+ if (UNIVERSAL_FLAGS.has(flagName)) continue;
199
+ if (!knownFlags.has(flagName)) {
200
+ const names = [...knownFlags].sort().join(', ');
201
+ const hint = names.length > 0 ? ` Known flags: ${names}\n` : '';
202
+ return (
203
+ `mandrel: unknown flag '${flagName}' for subcommand '${subName}'\n` +
204
+ hint
205
+ );
206
+ }
24
207
  }
208
+ return null;
25
209
  }
26
210
 
27
- const sub = process.argv[2];
211
+ // ---------------------------------------------------------------------------
212
+ // Dispatch
213
+ // ---------------------------------------------------------------------------
214
+
215
+ const args = process.argv.slice(2);
216
+ const sub = args[0];
217
+
218
+ // --- Top-level flags (no subcommand) ---
219
+ if (!sub || sub === '--help' || sub === '-h') {
220
+ printHelp();
221
+ process.exit(0);
222
+ }
28
223
 
29
- if (!sub) {
30
- usage();
224
+ if (sub === '--version') {
225
+ process.stdout.write(`${installedVersion()}\n`);
226
+ process.exit(0);
227
+ }
228
+
229
+ // --- Subcommand lookup ---
230
+ const meta = SUBCOMMANDS.get(sub);
231
+ if (!meta) {
232
+ const subcommandList = [...SUBCOMMANDS.keys()].join(', ');
233
+ const hint = suggest(sub);
234
+ const didYouMean = hint ? `\n Did you mean '${hint}'?` : '';
235
+ process.stderr.write(
236
+ `mandrel: unknown subcommand '${sub}'${didYouMean}\n` +
237
+ ` Available subcommands: ${subcommandList}\n`,
238
+ );
239
+ process.exit(1);
240
+ }
241
+
242
+ // --- Unknown-flag rejection ---
243
+ const subArgv = args.slice(1);
244
+ const unknownFlagError = findUnknownFlag(sub, meta.knownFlags, subArgv);
245
+ if (unknownFlagError) {
246
+ process.stderr.write(unknownFlagError);
31
247
  process.exit(1);
32
248
  }
33
249
 
250
+ // --- Load and dispatch ---
34
251
  const subFile = path.resolve(__dirname, '..', 'lib', 'cli', `${sub}.js`);
35
252
  const subFileUrl = pathToFileURL(subFile).href;
36
253
 
@@ -39,7 +256,9 @@ try {
39
256
  mod = await import(subFileUrl);
40
257
  } catch (err) {
41
258
  if (err.code === 'ERR_MODULE_NOT_FOUND' && err.message.includes(subFile)) {
42
- usage(sub);
259
+ process.stderr.write(
260
+ `mandrel: subcommand '${sub}' module not found — this is a bug\n`,
261
+ );
43
262
  process.exit(1);
44
263
  }
45
264
  // Re-throw broken-module errors so they are visible rather than masked.
@@ -53,4 +272,4 @@ if (typeof mod.default !== 'function') {
53
272
  process.exit(1);
54
273
  }
55
274
 
56
- await mod.default(process.argv.slice(3));
275
+ await mod.default(subArgv);