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
@@ -1,268 +0,0 @@
1
- // lib/cli/__tests__/migrate.test.js
2
- /**
3
- * Unit tests for lib/cli/migrate.js — the standalone `mandrel migrate`
4
- * subcommand (Story #3505, Epic #3437).
5
- *
6
- * Every test drives runMigrate through injectable seams (argv, runMigrations,
7
- * registry, ctx, write, writeErr, exit). No real filesystem I/O, no real
8
- * network call, and no shared mutable module state occur (testing-standards
9
- * § Unit: all external I/O MUST be mocked; pure-logic assertions only).
10
- *
11
- * Coverage contract (Story #3505 AC):
12
- * - Module shape: runMigrate named export + default function export.
13
- * - A live run parses --from/--to out of argv and forwards them to
14
- * runMigrations.
15
- * - --dry-run reports the steps that WOULD run and invokes no step's apply
16
- * and no runMigrations call (writes nothing to disk).
17
- * - Missing --from or --to is a usage error (non-zero exit).
18
- */
19
-
20
- import assert from 'node:assert/strict';
21
- import { describe, it } from 'node:test';
22
-
23
- import migrate, { runMigrate } from '../migrate.js';
24
-
25
- // ---------------------------------------------------------------------------
26
- // Capture + seam helpers
27
- // ---------------------------------------------------------------------------
28
-
29
- /** Capture stdout/stderr writes and the exit code. */
30
- function makeCapture() {
31
- const out = [];
32
- const err = [];
33
- let exitCode = null;
34
- return {
35
- out,
36
- err,
37
- get exitCode() {
38
- return exitCode;
39
- },
40
- write: (s) => out.push(s),
41
- writeErr: (s) => err.push(s),
42
- exit: (code) => {
43
- exitCode = code;
44
- },
45
- };
46
- }
47
-
48
- /**
49
- * Build a fixture registry with `detect`/`apply` recorders so the dry-run
50
- * (detect only, no apply) and live (detect → apply) paths are both
51
- * observable. `appliedNeeded` lists the versions whose detect returns true
52
- * (still needs applying); every other step is treated as already-present.
53
- */
54
- function makeFixtureRegistry({ appliedNeeded = ['1.4.0', '1.5.0'] } = {}) {
55
- const calls = [];
56
- const registry = [
57
- { version: '1.3.0', description: 'pre-range step' },
58
- { version: '1.4.0', description: 'rename foo to bar' },
59
- { version: '1.5.0', description: 'move baseline file' },
60
- { version: '1.6.0', description: 'post-range step' },
61
- ].map((step) => ({
62
- ...step,
63
- detect: (_ctx) => {
64
- calls.push(`detect:${step.version}`);
65
- return appliedNeeded.includes(step.version);
66
- },
67
- apply: (_ctx) => {
68
- calls.push(`apply:${step.version}`);
69
- },
70
- }));
71
- return { registry, calls };
72
- }
73
-
74
- // ---------------------------------------------------------------------------
75
- // Module shape
76
- // ---------------------------------------------------------------------------
77
-
78
- describe('migrate module exports', () => {
79
- it('exports runMigrate as a named export', () => {
80
- assert.equal(typeof runMigrate, 'function');
81
- });
82
-
83
- it('exports a default function for bin/mandrel.js dispatch', () => {
84
- assert.equal(typeof migrate, 'function');
85
- });
86
- });
87
-
88
- // ---------------------------------------------------------------------------
89
- // AC — live run forwards parsed --from/--to to runMigrations
90
- // ---------------------------------------------------------------------------
91
-
92
- describe('runMigrate — live run', () => {
93
- it('parses --from/--to and forwards them to runMigrations', () => {
94
- const cap = makeCapture();
95
- const calls = [];
96
- const runMigrations = ({ fromVersion, toVersion }) => {
97
- calls.push(`runMigrations:${fromVersion}->${toVersion}`);
98
- return { applied: ['1.4.0'], skipped: [] };
99
- };
100
-
101
- const result = runMigrate({
102
- argv: ['--from', '1.3.0', '--to', '1.5.0'],
103
- runMigrations,
104
- registry: [],
105
- write: cap.write,
106
- writeErr: cap.writeErr,
107
- exit: cap.exit,
108
- });
109
-
110
- assert.deepEqual(calls, ['runMigrations:1.3.0->1.5.0']);
111
- assert.equal(result.ok, true);
112
- assert.equal(result.action, 'migrated');
113
- assert.deepEqual(result.applied, ['1.4.0']);
114
- assert.equal(cap.exitCode, null);
115
- assert.match(cap.out.join(''), /Applied 1 migration/);
116
- });
117
-
118
- it('accepts the --from=value / --to=value spelling', () => {
119
- const cap = makeCapture();
120
- const calls = [];
121
- const runMigrations = ({ fromVersion, toVersion }) => {
122
- calls.push(`runMigrations:${fromVersion}->${toVersion}`);
123
- return { applied: [], skipped: [] };
124
- };
125
-
126
- runMigrate({
127
- argv: ['--from=1.2.0', '--to=1.9.0'],
128
- runMigrations,
129
- registry: [],
130
- write: cap.write,
131
- writeErr: cap.writeErr,
132
- exit: cap.exit,
133
- });
134
-
135
- assert.deepEqual(calls, ['runMigrations:1.2.0->1.9.0']);
136
- });
137
-
138
- it('reports a no-op when no migrations applied', () => {
139
- const cap = makeCapture();
140
- const runMigrations = () => ({ applied: [], skipped: ['1.4.0'] });
141
-
142
- const result = runMigrate({
143
- argv: ['--from', '1.3.0', '--to', '1.5.0'],
144
- runMigrations,
145
- registry: [],
146
- write: cap.write,
147
- writeErr: cap.writeErr,
148
- exit: cap.exit,
149
- });
150
-
151
- assert.equal(result.ok, true);
152
- assert.deepEqual(result.applied, []);
153
- assert.match(cap.out.join(''), /no migrations to apply/);
154
- });
155
- });
156
-
157
- // ---------------------------------------------------------------------------
158
- // AC — --dry-run reports the plan, invokes no apply, writes nothing
159
- // ---------------------------------------------------------------------------
160
-
161
- describe('runMigrate — --dry-run', () => {
162
- it('reports the in-range steps that would apply / be skipped and never applies', () => {
163
- const cap = makeCapture();
164
- const { registry, calls } = makeFixtureRegistry({
165
- // 1.5.0 is in range but already present (detect → false ⇒ would skip).
166
- appliedNeeded: ['1.4.0'],
167
- });
168
- let runnerCalled = false;
169
-
170
- const result = runMigrate({
171
- argv: ['--from', '1.3.0', '--to', '1.5.0', '--dry-run'],
172
- runMigrations: () => {
173
- runnerCalled = true;
174
- return { applied: [], skipped: [] };
175
- },
176
- registry,
177
- write: cap.write,
178
- writeErr: cap.writeErr,
179
- exit: cap.exit,
180
- });
181
-
182
- // Range filter is fromVersion < v <= toVersion → only 1.4.0 and 1.5.0.
183
- assert.deepEqual(result.wouldApply, ['1.4.0']);
184
- assert.deepEqual(result.wouldSkip, ['1.5.0']);
185
- assert.equal(result.action, 'dry-run');
186
- assert.equal(result.ok, true);
187
-
188
- // Dry-run probes detect on in-range steps only — never apply, never the
189
- // live runner.
190
- assert.deepEqual(calls, ['detect:1.4.0', 'detect:1.5.0']);
191
- assert.equal(runnerCalled, false);
192
- assert.ok(!calls.some((c) => c.startsWith('apply:')));
193
-
194
- // Operator-facing plan output.
195
- const stdout = cap.out.join('');
196
- assert.match(stdout, /dry run v1\.3\.0 → v1\.5\.0/);
197
- assert.match(stdout, /would apply {2}1\.4\.0: rename foo to bar/);
198
- assert.match(stdout, /would skip {3}1\.5\.0: move baseline file/);
199
- assert.match(stdout, /no migrations applied, nothing written/);
200
- assert.equal(cap.exitCode, null);
201
- });
202
-
203
- it('reports an empty plan when no steps fall in range', () => {
204
- const cap = makeCapture();
205
- const { registry, calls } = makeFixtureRegistry();
206
-
207
- const result = runMigrate({
208
- // 1.6.0 < v <= 1.6.0 leaves only the 1.6.0 step out (exclusive lower);
209
- // 9.9.0 → 9.9.0 range catches nothing.
210
- argv: ['--from', '9.9.0', '--to', '9.9.0', '--dry-run'],
211
- runMigrations: () => ({ applied: [], skipped: [] }),
212
- registry,
213
- write: cap.write,
214
- writeErr: cap.writeErr,
215
- exit: cap.exit,
216
- });
217
-
218
- assert.deepEqual(result.wouldApply, []);
219
- assert.deepEqual(result.wouldSkip, []);
220
- assert.deepEqual(calls, []);
221
- assert.match(cap.out.join(''), /no migration steps in range/);
222
- });
223
- });
224
-
225
- // ---------------------------------------------------------------------------
226
- // AC — missing bounds are a usage error
227
- // ---------------------------------------------------------------------------
228
-
229
- describe('runMigrate — usage validation', () => {
230
- it('exits non-zero when --to is missing', () => {
231
- const cap = makeCapture();
232
- let runnerCalled = false;
233
-
234
- const result = runMigrate({
235
- argv: ['--from', '1.3.0'],
236
- runMigrations: () => {
237
- runnerCalled = true;
238
- return { applied: [], skipped: [] };
239
- },
240
- registry: [],
241
- write: cap.write,
242
- writeErr: cap.writeErr,
243
- exit: cap.exit,
244
- });
245
-
246
- assert.equal(result.ok, false);
247
- assert.equal(result.action, 'usage-error');
248
- assert.equal(cap.exitCode, 1);
249
- assert.equal(runnerCalled, false);
250
- assert.match(cap.err.join(''), /both --from .* and --to .* are required/);
251
- });
252
-
253
- it('exits non-zero when --from is missing', () => {
254
- const cap = makeCapture();
255
-
256
- const result = runMigrate({
257
- argv: ['--to', '1.5.0'],
258
- runMigrations: () => ({ applied: [], skipped: [] }),
259
- registry: [],
260
- write: cap.write,
261
- writeErr: cap.writeErr,
262
- exit: cap.exit,
263
- });
264
-
265
- assert.equal(result.action, 'usage-error');
266
- assert.equal(cap.exitCode, 1);
267
- });
268
- });
@@ -1,247 +0,0 @@
1
- // lib/cli/__tests__/sync-local-zone.test.js
2
- /**
3
- * Unit tests for the `.agents/local/` sync-exempt local-additions zone
4
- * (Story #3498, f-drift-local-zone).
5
- *
6
- * Contract under test (Story #3498 AC):
7
- * 1. runSync skips any path under `.agents/local/` when copying the
8
- * package payload — proven by pre-populating a consumer-authored
9
- * `.agents/local/custom.md` in the destination via an injected fs
10
- * seam and asserting it is left byte-identical after a sync.
11
- * 2. `mandrel sync` writes no file into `.agents/local/` from the package
12
- * payload — proven by seeding a (hypothetical) payload file under the
13
- * source `.agents/local/` and asserting it is never enumerated, never
14
- * copied, and never appears in the dry-run plan.
15
- *
16
- * Every test drives runSync through the same injectable seams used by
17
- * sync.test.js (resolvePackageRoot, fs, cwd, write, writeErr, exit) backed
18
- * by an in-memory filesystem fake, so no real package resolution and no
19
- * real disk I/O occur (testing-standards § Unit: all filesystem I/O MUST
20
- * be mocked; sync.js injectable-seam style — no real child processes).
21
- */
22
-
23
- import assert from 'node:assert/strict';
24
- import path from 'node:path';
25
- import { describe, it } from 'node:test';
26
-
27
- import { runSync } from '../sync.js';
28
-
29
- // ---------------------------------------------------------------------------
30
- // In-memory filesystem fake (mirrors sync.test.js)
31
- // ---------------------------------------------------------------------------
32
-
33
- /**
34
- * Build an in-memory fs whose `seed` maps absolute file paths → contents.
35
- * Directories are inferred from the seeded file paths.
36
- *
37
- * Tracks writes (copyFileSync) and guards against symlink creation.
38
- */
39
- function makeFs(seed = {}) {
40
- const files = new Map(Object.entries(seed));
41
- const symlinkCalls = [];
42
-
43
- function dirEntries(dir) {
44
- const norm = dir.endsWith(path.sep) ? dir.slice(0, -1) : dir;
45
- const children = new Map(); // name → isDir
46
- for (const abs of files.keys()) {
47
- if (!abs.startsWith(norm + path.sep)) continue;
48
- const rest = abs.slice(norm.length + 1);
49
- const segments = rest.split(path.sep);
50
- const name = segments[0];
51
- children.set(name, segments.length > 1);
52
- }
53
- return [...children.entries()].map(([name, isDir]) => ({
54
- name,
55
- isDirectory: () => isDir,
56
- }));
57
- }
58
-
59
- return {
60
- files,
61
- symlinkCalls,
62
- readdirSync(dir, _opts) {
63
- return dirEntries(dir);
64
- },
65
- existsSync(p) {
66
- const norm = p.endsWith(path.sep) ? p.slice(0, -1) : p;
67
- if (files.has(norm)) return true;
68
- for (const abs of files.keys()) {
69
- if (abs.startsWith(norm + path.sep)) return true;
70
- }
71
- return false;
72
- },
73
- mkdirSync(_dir, _opts) {
74
- // No-op: directories are implied by file paths in this fake.
75
- },
76
- copyFileSync(src, dest) {
77
- if (!files.has(src)) {
78
- throw new Error(`copyFileSync: source missing ${src}`);
79
- }
80
- files.set(dest, files.get(src));
81
- },
82
- symlinkSync(target, p) {
83
- symlinkCalls.push({ target, path: p });
84
- },
85
- };
86
- }
87
-
88
- /** Capture stdout/stderr writes and the exit code. */
89
- function makeCapture() {
90
- const out = [];
91
- const err = [];
92
- let exitCode = null;
93
- return {
94
- out,
95
- err,
96
- get exitCode() {
97
- return exitCode;
98
- },
99
- write: (s) => out.push(s),
100
- writeErr: (s) => err.push(s),
101
- exit: (code) => {
102
- exitCode = code;
103
- },
104
- };
105
- }
106
-
107
- const PROJECT = path.join(path.sep, 'proj');
108
- const PKG_ROOT = path.join(PROJECT, 'node_modules', 'mandrel');
109
- const SRC_AGENTS = path.join(PKG_ROOT, '.agents');
110
- const resolveToPkg = () => PKG_ROOT;
111
-
112
- /** Seed a normal package payload of two non-local files under <pkg>/.agents/. */
113
- function seedPackagePayload() {
114
- return {
115
- [path.join(SRC_AGENTS, 'instructions.md')]: '# instructions\n',
116
- [path.join(SRC_AGENTS, 'rules', 'security-baseline.md')]: '# security\n',
117
- };
118
- }
119
-
120
- const baseOpts = (fs, cap) => ({
121
- argv: [],
122
- resolvePackageRoot: resolveToPkg,
123
- fs,
124
- cwd: () => PROJECT,
125
- write: cap.write,
126
- writeErr: cap.writeErr,
127
- exit: cap.exit,
128
- });
129
-
130
- // ---------------------------------------------------------------------------
131
- // AC1 — pre-existing consumer .agents/local/ additions survive sync
132
- // ---------------------------------------------------------------------------
133
-
134
- describe('runSync — .agents/local/ consumer additions survive', () => {
135
- it('leaves a pre-existing .agents/local/custom.md untouched', () => {
136
- // Arrange: a normal payload plus a consumer-authored file already living
137
- // in the destination's local zone.
138
- const localAddition = path.join(PROJECT, '.agents', 'local', 'custom.md');
139
- const fs = makeFs({
140
- ...seedPackagePayload(),
141
- [localAddition]: '# my custom local note\n',
142
- });
143
- const cap = makeCapture();
144
-
145
- // Act
146
- const result = runSync(baseOpts(fs, cap));
147
-
148
- // Assert: the local addition is byte-identical and the regular payload
149
- // still materialized.
150
- assert.equal(fs.files.get(localAddition), '# my custom local note\n');
151
- assert.equal(
152
- fs.files.get(path.join(PROJECT, '.agents', 'instructions.md')),
153
- '# instructions\n',
154
- );
155
- assert.equal(result.copied, 2);
156
- assert.equal(cap.exitCode, null);
157
- });
158
-
159
- it('never writes into the destination .agents/local/ subtree', () => {
160
- const localAddition = path.join(PROJECT, '.agents', 'local', 'custom.md');
161
- const fs = makeFs({
162
- ...seedPackagePayload(),
163
- [localAddition]: '# my custom local note\n',
164
- });
165
- const cap = makeCapture();
166
-
167
- runSync(baseOpts(fs, cap));
168
-
169
- // The only destination file under .agents/local/ is the one the consumer
170
- // authored; sync added nothing there.
171
- const localPrefix = path.join(PROJECT, '.agents', 'local') + path.sep;
172
- const localDestFiles = [...fs.files.keys()].filter((k) =>
173
- k.startsWith(localPrefix),
174
- );
175
- assert.deepEqual(localDestFiles, [localAddition]);
176
- });
177
- });
178
-
179
- // ---------------------------------------------------------------------------
180
- // AC2 — a payload file under .agents/local/ is never materialized
181
- // ---------------------------------------------------------------------------
182
-
183
- describe('runSync — payload .agents/local/ is skipped on copy', () => {
184
- it('does not copy a source .agents/local/ file into the destination', () => {
185
- // Arrange: a payload that (hypothetically) ships a file under local/.
186
- // The published payload ships none, but the skip must hold defensively.
187
- const srcLocal = path.join(SRC_AGENTS, 'local', 'should-not-copy.md');
188
- const fs = makeFs({
189
- ...seedPackagePayload(),
190
- [srcLocal]: '# payload local file\n',
191
- });
192
- const cap = makeCapture();
193
-
194
- // Act
195
- const result = runSync(baseOpts(fs, cap));
196
-
197
- // Assert: the local payload file was never copied to the destination.
198
- const destLocal = path.join(
199
- PROJECT,
200
- '.agents',
201
- 'local',
202
- 'should-not-copy.md',
203
- );
204
- assert.equal(fs.files.has(destLocal), false);
205
- // Only the two non-local payload files were materialized.
206
- assert.equal(result.copied, 2);
207
- });
208
-
209
- it('omits .agents/local/ paths from the --dry-run plan', () => {
210
- const srcLocal = path.join(SRC_AGENTS, 'local', 'should-not-copy.md');
211
- const fs = makeFs({
212
- ...seedPackagePayload(),
213
- [srcLocal]: '# payload local file\n',
214
- });
215
- const cap = makeCapture();
216
-
217
- const result = runSync({ ...baseOpts(fs, cap), argv: ['--dry-run'] });
218
-
219
- const joined = cap.out.join('');
220
- assert.doesNotMatch(joined, /local/);
221
- assert.match(joined, /Dry run: 2 file\(s\)/);
222
- assert.equal(result.planned, 2);
223
- });
224
-
225
- it('still materializes a deeper non-top-level directory named local', () => {
226
- // The skip is scoped to the top-level .agents/local/ only — a nested
227
- // rules/local/ must still copy.
228
- const nestedLocal = path.join(SRC_AGENTS, 'rules', 'local', 'note.md');
229
- const fs = makeFs({
230
- ...seedPackagePayload(),
231
- [nestedLocal]: '# nested local note\n',
232
- });
233
- const cap = makeCapture();
234
-
235
- const result = runSync(baseOpts(fs, cap));
236
-
237
- const destNested = path.join(
238
- PROJECT,
239
- '.agents',
240
- 'rules',
241
- 'local',
242
- 'note.md',
243
- );
244
- assert.equal(fs.files.get(destNested), '# nested local note\n');
245
- assert.equal(result.copied, 3);
246
- });
247
- });