@ktpartners/dgs-platform 2.6.2

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 (256) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +851 -0
  3. package/agents/dgs-codebase-cross-analyzer.md +183 -0
  4. package/agents/dgs-codebase-mapper.md +782 -0
  5. package/agents/dgs-codebase-synthesizer.md +156 -0
  6. package/agents/dgs-debugger.md +1256 -0
  7. package/agents/dgs-executor.md +550 -0
  8. package/agents/dgs-integration-checker.md +481 -0
  9. package/agents/dgs-nyquist-auditor.md +178 -0
  10. package/agents/dgs-phase-researcher.md +563 -0
  11. package/agents/dgs-phase-verifier.md +450 -0
  12. package/agents/dgs-plan-checker.md +708 -0
  13. package/agents/dgs-planner.md +1324 -0
  14. package/agents/dgs-project-researcher.md +631 -0
  15. package/agents/dgs-research-synthesizer.md +249 -0
  16. package/agents/dgs-roadmapper.md +652 -0
  17. package/agents/dgs-verifier.md +607 -0
  18. package/bin/install.js +2073 -0
  19. package/commands/dgs/add-doc.md +45 -0
  20. package/commands/dgs/add-idea.md +38 -0
  21. package/commands/dgs/add-phase.md +43 -0
  22. package/commands/dgs/add-repo.md +54 -0
  23. package/commands/dgs/add-tests.md +41 -0
  24. package/commands/dgs/add-todo.md +47 -0
  25. package/commands/dgs/approve-spec.md +38 -0
  26. package/commands/dgs/audit-milestone.md +36 -0
  27. package/commands/dgs/audit-phase.md +37 -0
  28. package/commands/dgs/cancel-job.md +23 -0
  29. package/commands/dgs/capture-principle.md +143 -0
  30. package/commands/dgs/check-todos.md +45 -0
  31. package/commands/dgs/cleanup.md +18 -0
  32. package/commands/dgs/complete-milestone.md +136 -0
  33. package/commands/dgs/complete-project.md +70 -0
  34. package/commands/dgs/consolidate-ideas.md +50 -0
  35. package/commands/dgs/create-milestone-job.md +37 -0
  36. package/commands/dgs/debug.md +164 -0
  37. package/commands/dgs/develop-idea.md +53 -0
  38. package/commands/dgs/discuss-idea.md +41 -0
  39. package/commands/dgs/discuss-phase.md +83 -0
  40. package/commands/dgs/execute-phase.md +41 -0
  41. package/commands/dgs/fast.md +38 -0
  42. package/commands/dgs/find-related-ideas.md +43 -0
  43. package/commands/dgs/health.md +28 -0
  44. package/commands/dgs/help.md +22 -0
  45. package/commands/dgs/import-spec.md +36 -0
  46. package/commands/dgs/init-product.md +28 -0
  47. package/commands/dgs/insert-phase.md +32 -0
  48. package/commands/dgs/join-discord.md +18 -0
  49. package/commands/dgs/list-docs.md +40 -0
  50. package/commands/dgs/list-ideas.md +42 -0
  51. package/commands/dgs/list-jobs.md +22 -0
  52. package/commands/dgs/list-phase-assumptions.md +46 -0
  53. package/commands/dgs/list-projects.md +57 -0
  54. package/commands/dgs/list-specs.md +40 -0
  55. package/commands/dgs/map-codebase.md +92 -0
  56. package/commands/dgs/new-milestone.md +44 -0
  57. package/commands/dgs/new-project.md +42 -0
  58. package/commands/dgs/node-repair.md +26 -0
  59. package/commands/dgs/overlap-check.md +20 -0
  60. package/commands/dgs/pause-work.md +38 -0
  61. package/commands/dgs/plan-milestone-gaps.md +34 -0
  62. package/commands/dgs/plan-phase.md +44 -0
  63. package/commands/dgs/progress.md +24 -0
  64. package/commands/dgs/quick.md +41 -0
  65. package/commands/dgs/reactivate-project.md +70 -0
  66. package/commands/dgs/reapply-patches.md +110 -0
  67. package/commands/dgs/refine-spec.md +38 -0
  68. package/commands/dgs/reject-idea.md +43 -0
  69. package/commands/dgs/remove-doc.md +44 -0
  70. package/commands/dgs/remove-phase.md +31 -0
  71. package/commands/dgs/remove-repo.md +69 -0
  72. package/commands/dgs/research-idea.md +43 -0
  73. package/commands/dgs/research-phase.md +189 -0
  74. package/commands/dgs/restore-idea.md +45 -0
  75. package/commands/dgs/resume-work.md +40 -0
  76. package/commands/dgs/rollback-job.md +24 -0
  77. package/commands/dgs/run-job.md +35 -0
  78. package/commands/dgs/search.md +40 -0
  79. package/commands/dgs/set-profile.md +34 -0
  80. package/commands/dgs/settings.md +38 -0
  81. package/commands/dgs/switch-project.md +58 -0
  82. package/commands/dgs/undo-consolidation.md +42 -0
  83. package/commands/dgs/update-idea.md +44 -0
  84. package/commands/dgs/update.md +37 -0
  85. package/commands/dgs/validate-phase.md +35 -0
  86. package/commands/dgs/verify-work.md +39 -0
  87. package/commands/dgs/write-spec.md +49 -0
  88. package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-01-SUMMARY.md +84 -0
  89. package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-02-SUMMARY.md +86 -0
  90. package/deliver-great-systems/.planning/phases/10-v1-to-v2-migration-flow/10-01-SUMMARY.md +85 -0
  91. package/deliver-great-systems/bin/dgs-tools.cjs +1444 -0
  92. package/deliver-great-systems/bin/lib/auto-test.cjs +1365 -0
  93. package/deliver-great-systems/bin/lib/commands.cjs +570 -0
  94. package/deliver-great-systems/bin/lib/config.cjs +417 -0
  95. package/deliver-great-systems/bin/lib/conflict-agent.cjs +1063 -0
  96. package/deliver-great-systems/bin/lib/conflict-agent.test.cjs +554 -0
  97. package/deliver-great-systems/bin/lib/context.cjs +929 -0
  98. package/deliver-great-systems/bin/lib/context.test.cjs +693 -0
  99. package/deliver-great-systems/bin/lib/core.cjs +744 -0
  100. package/deliver-great-systems/bin/lib/core.test.cjs +822 -0
  101. package/deliver-great-systems/bin/lib/docs.cjs +919 -0
  102. package/deliver-great-systems/bin/lib/docs.test.cjs +211 -0
  103. package/deliver-great-systems/bin/lib/execution.cjs +705 -0
  104. package/deliver-great-systems/bin/lib/execution.test.cjs +1472 -0
  105. package/deliver-great-systems/bin/lib/frontmatter.cjs +324 -0
  106. package/deliver-great-systems/bin/lib/ideas.cjs +1406 -0
  107. package/deliver-great-systems/bin/lib/ideas.test.cjs +1417 -0
  108. package/deliver-great-systems/bin/lib/identity.cjs +125 -0
  109. package/deliver-great-systems/bin/lib/init.cjs +1114 -0
  110. package/deliver-great-systems/bin/lib/init.test.cjs +1271 -0
  111. package/deliver-great-systems/bin/lib/jobs.cjs +2015 -0
  112. package/deliver-great-systems/bin/lib/jobs.test.cjs +2619 -0
  113. package/deliver-great-systems/bin/lib/merge-conflicts.cjs +654 -0
  114. package/deliver-great-systems/bin/lib/merge-conflicts.test.cjs +370 -0
  115. package/deliver-great-systems/bin/lib/migration.cjs +352 -0
  116. package/deliver-great-systems/bin/lib/migration.test.cjs +582 -0
  117. package/deliver-great-systems/bin/lib/milestone.cjs +243 -0
  118. package/deliver-great-systems/bin/lib/overlap.cjs +437 -0
  119. package/deliver-great-systems/bin/lib/overlap.test.cjs +747 -0
  120. package/deliver-great-systems/bin/lib/path-audit.test.cjs +384 -0
  121. package/deliver-great-systems/bin/lib/paths.cjs +144 -0
  122. package/deliver-great-systems/bin/lib/paths.test.cjs +486 -0
  123. package/deliver-great-systems/bin/lib/phase.cjs +910 -0
  124. package/deliver-great-systems/bin/lib/projects.cjs +691 -0
  125. package/deliver-great-systems/bin/lib/projects.test.cjs +871 -0
  126. package/deliver-great-systems/bin/lib/repos.cjs +1432 -0
  127. package/deliver-great-systems/bin/lib/repos.test.cjs +1882 -0
  128. package/deliver-great-systems/bin/lib/roadmap.cjs +305 -0
  129. package/deliver-great-systems/bin/lib/search.cjs +570 -0
  130. package/deliver-great-systems/bin/lib/specs.cjs +1303 -0
  131. package/deliver-great-systems/bin/lib/state.cjs +893 -0
  132. package/deliver-great-systems/bin/lib/template.cjs +228 -0
  133. package/deliver-great-systems/bin/lib/test-helpers.cjs +291 -0
  134. package/deliver-great-systems/bin/lib/verify.cjs +796 -0
  135. package/deliver-great-systems/references/checkpoints.md +776 -0
  136. package/deliver-great-systems/references/conflict-resolution.md +66 -0
  137. package/deliver-great-systems/references/context-tiers.md +166 -0
  138. package/deliver-great-systems/references/continuation-format.md +249 -0
  139. package/deliver-great-systems/references/decimal-phase-calculation.md +67 -0
  140. package/deliver-great-systems/references/git-integration.md +250 -0
  141. package/deliver-great-systems/references/git-planning-commit.md +40 -0
  142. package/deliver-great-systems/references/model-profile-resolution.md +36 -0
  143. package/deliver-great-systems/references/model-profiles.md +95 -0
  144. package/deliver-great-systems/references/phase-argument-parsing.md +61 -0
  145. package/deliver-great-systems/references/planning-config.md +224 -0
  146. package/deliver-great-systems/references/questioning.md +162 -0
  147. package/deliver-great-systems/references/spec-review-loop.md +177 -0
  148. package/deliver-great-systems/references/tdd.md +265 -0
  149. package/deliver-great-systems/references/ui-brand.md +160 -0
  150. package/deliver-great-systems/references/verification-patterns.md +612 -0
  151. package/deliver-great-systems/templates/DEBUG.md +166 -0
  152. package/deliver-great-systems/templates/UAT.md +251 -0
  153. package/deliver-great-systems/templates/VALIDATION.md +95 -0
  154. package/deliver-great-systems/templates/claude-md.md +74 -0
  155. package/deliver-great-systems/templates/codebase/architecture.md +257 -0
  156. package/deliver-great-systems/templates/codebase/concerns.md +312 -0
  157. package/deliver-great-systems/templates/codebase/conventions.md +309 -0
  158. package/deliver-great-systems/templates/codebase/integrations.md +282 -0
  159. package/deliver-great-systems/templates/codebase/stack.md +188 -0
  160. package/deliver-great-systems/templates/codebase/structure.md +287 -0
  161. package/deliver-great-systems/templates/codebase/testing.md +482 -0
  162. package/deliver-great-systems/templates/config.json +38 -0
  163. package/deliver-great-systems/templates/context.md +354 -0
  164. package/deliver-great-systems/templates/continue-here.md +80 -0
  165. package/deliver-great-systems/templates/debug-subagent-prompt.md +93 -0
  166. package/deliver-great-systems/templates/discovery.md +148 -0
  167. package/deliver-great-systems/templates/milestone-archive.md +125 -0
  168. package/deliver-great-systems/templates/milestone.md +117 -0
  169. package/deliver-great-systems/templates/phase-prompt.md +615 -0
  170. package/deliver-great-systems/templates/planner-subagent-prompt.md +119 -0
  171. package/deliver-great-systems/templates/project.md +186 -0
  172. package/deliver-great-systems/templates/requirements.md +233 -0
  173. package/deliver-great-systems/templates/research-project/ARCHITECTURE.md +206 -0
  174. package/deliver-great-systems/templates/research-project/FEATURES.md +149 -0
  175. package/deliver-great-systems/templates/research-project/PITFALLS.md +202 -0
  176. package/deliver-great-systems/templates/research-project/STACK.md +122 -0
  177. package/deliver-great-systems/templates/research-project/SUMMARY.md +172 -0
  178. package/deliver-great-systems/templates/research.md +554 -0
  179. package/deliver-great-systems/templates/retrospective.md +54 -0
  180. package/deliver-great-systems/templates/roadmap.md +204 -0
  181. package/deliver-great-systems/templates/state.md +178 -0
  182. package/deliver-great-systems/templates/summary-complex.md +59 -0
  183. package/deliver-great-systems/templates/summary-minimal.md +41 -0
  184. package/deliver-great-systems/templates/summary-standard.md +48 -0
  185. package/deliver-great-systems/templates/summary.md +253 -0
  186. package/deliver-great-systems/templates/user-setup.md +313 -0
  187. package/deliver-great-systems/templates/verification-report.md +324 -0
  188. package/deliver-great-systems/workflows/add-doc.md +151 -0
  189. package/deliver-great-systems/workflows/add-idea.md +96 -0
  190. package/deliver-great-systems/workflows/add-phase.md +120 -0
  191. package/deliver-great-systems/workflows/add-tests.md +359 -0
  192. package/deliver-great-systems/workflows/add-todo.md +162 -0
  193. package/deliver-great-systems/workflows/approve-spec.md +194 -0
  194. package/deliver-great-systems/workflows/audit-milestone.md +364 -0
  195. package/deliver-great-systems/workflows/audit-phase.md +462 -0
  196. package/deliver-great-systems/workflows/cancel-job.md +108 -0
  197. package/deliver-great-systems/workflows/check-todos.md +181 -0
  198. package/deliver-great-systems/workflows/cleanup.md +247 -0
  199. package/deliver-great-systems/workflows/codereview.md +526 -0
  200. package/deliver-great-systems/workflows/complete-milestone.md +1298 -0
  201. package/deliver-great-systems/workflows/consolidate-ideas.md +365 -0
  202. package/deliver-great-systems/workflows/create-milestone-job.md +177 -0
  203. package/deliver-great-systems/workflows/develop-idea.md +544 -0
  204. package/deliver-great-systems/workflows/diagnose-issues.md +231 -0
  205. package/deliver-great-systems/workflows/discovery-phase.md +301 -0
  206. package/deliver-great-systems/workflows/discuss-idea.md +263 -0
  207. package/deliver-great-systems/workflows/discuss-phase.md +733 -0
  208. package/deliver-great-systems/workflows/execute-phase.md +571 -0
  209. package/deliver-great-systems/workflows/execute-plan.md +592 -0
  210. package/deliver-great-systems/workflows/find-related-ideas.md +271 -0
  211. package/deliver-great-systems/workflows/health.md +173 -0
  212. package/deliver-great-systems/workflows/help.md +997 -0
  213. package/deliver-great-systems/workflows/import-spec.md +381 -0
  214. package/deliver-great-systems/workflows/init-product.md +767 -0
  215. package/deliver-great-systems/workflows/insert-phase.md +138 -0
  216. package/deliver-great-systems/workflows/list-docs.md +119 -0
  217. package/deliver-great-systems/workflows/list-ideas.md +154 -0
  218. package/deliver-great-systems/workflows/list-jobs.md +89 -0
  219. package/deliver-great-systems/workflows/list-phase-assumptions.md +192 -0
  220. package/deliver-great-systems/workflows/list-specs.md +101 -0
  221. package/deliver-great-systems/workflows/map-codebase.md +621 -0
  222. package/deliver-great-systems/workflows/new-milestone.md +591 -0
  223. package/deliver-great-systems/workflows/new-project.md +1113 -0
  224. package/deliver-great-systems/workflows/node-repair.md +94 -0
  225. package/deliver-great-systems/workflows/overlap-check.md +86 -0
  226. package/deliver-great-systems/workflows/pause-work.md +134 -0
  227. package/deliver-great-systems/workflows/plan-milestone-gaps.md +306 -0
  228. package/deliver-great-systems/workflows/plan-phase.md +698 -0
  229. package/deliver-great-systems/workflows/progress.md +386 -0
  230. package/deliver-great-systems/workflows/quick.md +845 -0
  231. package/deliver-great-systems/workflows/refine-spec.md +275 -0
  232. package/deliver-great-systems/workflows/reject-idea.md +109 -0
  233. package/deliver-great-systems/workflows/remove-doc.md +117 -0
  234. package/deliver-great-systems/workflows/remove-phase.md +163 -0
  235. package/deliver-great-systems/workflows/research-idea.md +325 -0
  236. package/deliver-great-systems/workflows/research-phase.md +81 -0
  237. package/deliver-great-systems/workflows/restore-idea.md +101 -0
  238. package/deliver-great-systems/workflows/resume-project.md +311 -0
  239. package/deliver-great-systems/workflows/rollback-job.md +130 -0
  240. package/deliver-great-systems/workflows/run-job.md +498 -0
  241. package/deliver-great-systems/workflows/search.md +130 -0
  242. package/deliver-great-systems/workflows/set-profile.md +83 -0
  243. package/deliver-great-systems/workflows/settings.md +470 -0
  244. package/deliver-great-systems/workflows/transition.md +563 -0
  245. package/deliver-great-systems/workflows/undo-consolidation.md +155 -0
  246. package/deliver-great-systems/workflows/update-idea.md +157 -0
  247. package/deliver-great-systems/workflows/update.md +242 -0
  248. package/deliver-great-systems/workflows/validate-phase.md +177 -0
  249. package/deliver-great-systems/workflows/verify-phase.md +253 -0
  250. package/deliver-great-systems/workflows/verify-work.md +671 -0
  251. package/deliver-great-systems/workflows/write-spec.md +450 -0
  252. package/hooks/dist/dgs-check-update.js +62 -0
  253. package/hooks/dist/dgs-context-monitor.js +141 -0
  254. package/hooks/dist/dgs-statusline.js +115 -0
  255. package/package.json +60 -0
  256. package/scripts/build-hooks.js +43 -0
@@ -0,0 +1,1271 @@
1
+ /**
2
+ * Tests for init.cjs v2 project-qualified path resolution
3
+ *
4
+ * Uses Node.js built-in test runner (node:test) and assert (node:assert).
5
+ * Tests invoke dgs-tools.cjs CLI via execSync against isolated temp fixtures
6
+ * to verify init commands output correct paths in v1 and v2 modes.
7
+ */
8
+
9
+ const { describe, it, beforeEach, afterEach } = require('node:test');
10
+ const assert = require('node:assert/strict');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const os = require('os');
14
+ const { execSync } = require('child_process');
15
+
16
+ const { createFixture } = require('./test-helpers.cjs');
17
+
18
+ // Path to CLI entry point
19
+ const CLI = path.resolve(__dirname, '..', 'dgs-tools.cjs');
20
+
21
+ /**
22
+ * Run a dgs-tools init command in a fixture directory and return parsed JSON.
23
+ */
24
+ function runInit(cwd, command) {
25
+ const stdout = execSync(`node "${CLI}" init ${command}`, {
26
+ cwd,
27
+ encoding: 'utf-8',
28
+ stdio: ['pipe', 'pipe', 'pipe'],
29
+ });
30
+ return JSON.parse(stdout.trim());
31
+ }
32
+
33
+ // ─── Standard v1 fixture ─────────────────────────────────────────────────────
34
+
35
+ function v1Fixture() {
36
+ return createFixture({
37
+ '.planning/config.json': JSON.stringify({}),
38
+ '.planning/STATE.md': '# State',
39
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n- [ ] **Phase 1: Test Phase** - A test\n',
40
+ '.planning/REQUIREMENTS.md': '# Requirements',
41
+ '.planning/PROJECT.md': '# Project',
42
+ '.planning/phases/01-test-phase/01-CONTEXT.md': '# Context',
43
+ '.planning/phases/01-test-phase/01-01-PLAN.md': '---\nphase: 01-test-phase\nplan: 01\n---\n# Plan',
44
+ });
45
+ }
46
+
47
+ // ─── Standard v2 fixture with current project ────────────────────────────────
48
+
49
+ function v2FixtureWithProject() {
50
+ return createFixture({
51
+ '.planning/config.json': JSON.stringify({ current_project: 'test-project' }),
52
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n|---------|--------|\n| test-project | Active |\n',
53
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n|------|------|\n',
54
+ '.planning/projects/test-project/STATE.md': '# State',
55
+ '.planning/projects/test-project/ROADMAP.md': '# Roadmap\n\n## Phases\n\n- [ ] **Phase 1: Test Phase** - A test\n',
56
+ '.planning/projects/test-project/REQUIREMENTS.md': '# Requirements',
57
+ '.planning/projects/test-project/PROJECT.md': '# Project',
58
+ '.planning/projects/test-project/phases/01-test-phase/01-CONTEXT.md': '# Context',
59
+ '.planning/projects/test-project/phases/01-test-phase/01-01-PLAN.md': '---\nphase: 01-test-phase\nplan: 01\n---\n# Plan',
60
+ });
61
+ }
62
+
63
+ // ─── v2 fixture without current_project (for guard tests) ────────────────────
64
+
65
+ function v2FixtureOneProject() {
66
+ return createFixture({
67
+ '.planning/config.json': JSON.stringify({}),
68
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
69
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n',
70
+ '.planning/projects/test-project/STATE.md': '# State',
71
+ });
72
+ }
73
+
74
+ function v2FixtureZeroProjects() {
75
+ return createFixture({
76
+ '.planning/config.json': JSON.stringify({}),
77
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
78
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n',
79
+ });
80
+ }
81
+
82
+ function v2FixtureMultipleProjects() {
83
+ return createFixture({
84
+ '.planning/config.json': JSON.stringify({}),
85
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
86
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n',
87
+ '.planning/projects/alpha-project/STATE.md': '# State',
88
+ '.planning/projects/beta-project/STATE.md': '# State',
89
+ });
90
+ }
91
+
92
+ // ─── v1 Mode: plan-phase ─────────────────────────────────────────────────────
93
+
94
+ describe('v1 mode: init plan-phase', () => {
95
+ let fixture;
96
+ let result;
97
+
98
+ beforeEach(() => {
99
+ fixture = v1Fixture();
100
+ result = runInit(fixture.cwd, 'plan-phase 1');
101
+ });
102
+
103
+ afterEach(() => {
104
+ fixture.cleanup();
105
+ });
106
+
107
+ it('returns state_path as .planning/STATE.md', () => {
108
+ assert.equal(result.state_path, '.planning/STATE.md');
109
+ });
110
+
111
+ it('returns roadmap_path as .planning/ROADMAP.md', () => {
112
+ assert.equal(result.roadmap_path, '.planning/ROADMAP.md');
113
+ });
114
+
115
+ it('returns requirements_path as .planning/REQUIREMENTS.md', () => {
116
+ assert.equal(result.requirements_path, '.planning/REQUIREMENTS.md');
117
+ });
118
+
119
+ it('returns dgs_mode v1', () => {
120
+ assert.equal(result.dgs_mode, 'v1');
121
+ });
122
+
123
+ it('returns current_project null', () => {
124
+ assert.equal(result.current_project, null);
125
+ });
126
+
127
+ it('returns guard null', () => {
128
+ assert.equal(result.guard, null);
129
+ });
130
+
131
+ it('returns project_root as .planning', () => {
132
+ assert.equal(result.project_root, '.planning');
133
+ });
134
+
135
+ it('preserves phase_found', () => {
136
+ assert.equal(result.phase_found, true);
137
+ });
138
+
139
+ it('preserves phase_number', () => {
140
+ assert.equal(result.phase_number, '01');
141
+ });
142
+ });
143
+
144
+ // ─── v1 Mode: execute-phase ──────────────────────────────────────────────────
145
+
146
+ describe('v1 mode: init execute-phase', () => {
147
+ let fixture;
148
+ let result;
149
+
150
+ beforeEach(() => {
151
+ fixture = v1Fixture();
152
+ result = runInit(fixture.cwd, 'execute-phase 1');
153
+ });
154
+
155
+ afterEach(() => {
156
+ fixture.cleanup();
157
+ });
158
+
159
+ it('returns state_path as .planning/STATE.md', () => {
160
+ assert.equal(result.state_path, '.planning/STATE.md');
161
+ });
162
+
163
+ it('returns roadmap_path as .planning/ROADMAP.md', () => {
164
+ assert.equal(result.roadmap_path, '.planning/ROADMAP.md');
165
+ });
166
+
167
+ it('returns config_path as .planning/dgs.config.json (always product-level)', () => {
168
+ assert.equal(result.config_path, '.planning/dgs.config.json');
169
+ });
170
+
171
+ it('returns project_path as .planning/PROJECT.md', () => {
172
+ assert.equal(result.project_path, '.planning/PROJECT.md');
173
+ });
174
+
175
+ it('returns debug_dir as .planning/debug', () => {
176
+ assert.equal(result.debug_dir, '.planning/debug');
177
+ });
178
+
179
+ it('returns dgs_mode v1', () => {
180
+ assert.equal(result.dgs_mode, 'v1');
181
+ });
182
+
183
+ it('returns current_project null', () => {
184
+ assert.equal(result.current_project, null);
185
+ });
186
+
187
+ it('returns base_branch defaulting to main', () => {
188
+ assert.equal(result.base_branch, 'main');
189
+ });
190
+ });
191
+
192
+ // ─── v1 Mode: init new-project ────────────────────────────────────────────────
193
+
194
+ describe('v1 mode: init new-project', () => {
195
+ let fixture;
196
+ let result;
197
+
198
+ beforeEach(() => {
199
+ fixture = v1Fixture();
200
+ result = runInit(fixture.cwd, 'new-project');
201
+ });
202
+
203
+ afterEach(() => {
204
+ fixture.cleanup();
205
+ });
206
+
207
+ it('returns state_path as .planning/STATE.md', () => {
208
+ assert.equal(result.state_path, '.planning/STATE.md');
209
+ });
210
+
211
+ it('returns roadmap_path as .planning/ROADMAP.md', () => {
212
+ assert.equal(result.roadmap_path, '.planning/ROADMAP.md');
213
+ });
214
+
215
+ it('returns requirements_path as .planning/REQUIREMENTS.md', () => {
216
+ assert.equal(result.requirements_path, '.planning/REQUIREMENTS.md');
217
+ });
218
+
219
+ it('returns research_dir as .planning/research', () => {
220
+ assert.equal(result.research_dir, '.planning/research');
221
+ });
222
+
223
+ it('returns project_path as .planning/PROJECT.md', () => {
224
+ assert.equal(result.project_path, '.planning/PROJECT.md');
225
+ });
226
+
227
+ it('returns dgs_mode v1', () => {
228
+ assert.equal(result.dgs_mode, 'v1');
229
+ });
230
+ });
231
+
232
+ // ─── v1 Mode: other init commands ────────────────────────────────────────────
233
+
234
+ describe('v1 mode: init progress', () => {
235
+ let fixture;
236
+ let result;
237
+
238
+ beforeEach(() => {
239
+ fixture = v1Fixture();
240
+ result = runInit(fixture.cwd, 'progress');
241
+ });
242
+
243
+ afterEach(() => {
244
+ fixture.cleanup();
245
+ });
246
+
247
+ it('returns v1 file paths', () => {
248
+ assert.equal(result.state_path, '.planning/STATE.md');
249
+ assert.equal(result.roadmap_path, '.planning/ROADMAP.md');
250
+ assert.equal(result.project_path, '.planning/PROJECT.md');
251
+ assert.equal(result.config_path, '.planning/dgs.config.json');
252
+ });
253
+
254
+ it('returns dgs_mode v1', () => {
255
+ assert.equal(result.dgs_mode, 'v1');
256
+ });
257
+ });
258
+
259
+ describe('v1 mode: init todos', () => {
260
+ let fixture;
261
+ let result;
262
+
263
+ beforeEach(() => {
264
+ fixture = v1Fixture();
265
+ result = runInit(fixture.cwd, 'todos');
266
+ });
267
+
268
+ afterEach(() => {
269
+ fixture.cleanup();
270
+ });
271
+
272
+ it('returns v1 pending_dir path', () => {
273
+ assert.equal(result.pending_dir, '.planning/todos/pending');
274
+ });
275
+
276
+ it('returns v1 completed_dir path', () => {
277
+ assert.equal(result.completed_dir, '.planning/todos/completed');
278
+ });
279
+
280
+ it('returns dgs_mode v1', () => {
281
+ assert.equal(result.dgs_mode, 'v1');
282
+ });
283
+ });
284
+
285
+ describe('v1 mode: init milestone-op', () => {
286
+ let fixture;
287
+ let result;
288
+
289
+ beforeEach(() => {
290
+ fixture = v1Fixture();
291
+ result = runInit(fixture.cwd, 'milestone-op');
292
+ });
293
+
294
+ afterEach(() => {
295
+ fixture.cleanup();
296
+ });
297
+
298
+ it('returns v1 file paths', () => {
299
+ assert.equal(result.project_path, '.planning/PROJECT.md');
300
+ assert.equal(result.roadmap_path, '.planning/ROADMAP.md');
301
+ assert.equal(result.state_path, '.planning/STATE.md');
302
+ });
303
+
304
+ it('returns dgs_mode v1', () => {
305
+ assert.equal(result.dgs_mode, 'v1');
306
+ });
307
+
308
+ it('returns base_branch defaulting to main', () => {
309
+ assert.equal(result.base_branch, 'main');
310
+ });
311
+ });
312
+
313
+ describe('v1 mode: init map-codebase', () => {
314
+ let fixture;
315
+ let result;
316
+
317
+ beforeEach(() => {
318
+ fixture = v1Fixture();
319
+ result = runInit(fixture.cwd, 'map-codebase');
320
+ });
321
+
322
+ afterEach(() => {
323
+ fixture.cleanup();
324
+ });
325
+
326
+ it('keeps codebase_dir product-level (not project-qualified)', () => {
327
+ assert.equal(result.codebase_dir, '.planning/codebase');
328
+ });
329
+
330
+ it('returns dgs_mode v1', () => {
331
+ assert.equal(result.dgs_mode, 'v1');
332
+ });
333
+ });
334
+
335
+ // ─── v2 Mode with current_project ────────────────────────────────────────────
336
+
337
+ describe('v2 mode with project: init plan-phase', () => {
338
+ let fixture;
339
+ let result;
340
+
341
+ beforeEach(() => {
342
+ fixture = v2FixtureWithProject();
343
+ result = runInit(fixture.cwd, 'plan-phase 1');
344
+ });
345
+
346
+ afterEach(() => {
347
+ fixture.cleanup();
348
+ });
349
+
350
+ it('returns project-qualified state_path', () => {
351
+ assert.equal(result.state_path, path.join('.planning', 'projects', 'test-project', 'STATE.md'));
352
+ });
353
+
354
+ it('returns project-qualified roadmap_path', () => {
355
+ assert.equal(result.roadmap_path, path.join('.planning', 'projects', 'test-project', 'ROADMAP.md'));
356
+ });
357
+
358
+ it('returns project-qualified requirements_path', () => {
359
+ assert.equal(result.requirements_path, path.join('.planning', 'projects', 'test-project', 'REQUIREMENTS.md'));
360
+ });
361
+
362
+ it('returns dgs_mode v2', () => {
363
+ assert.equal(result.dgs_mode, 'v2');
364
+ });
365
+
366
+ it('returns current_project', () => {
367
+ assert.equal(result.current_project, 'test-project');
368
+ });
369
+
370
+ it('returns project_root', () => {
371
+ assert.equal(result.project_root, path.join('.planning', 'projects', 'test-project'));
372
+ });
373
+
374
+ it('returns guard null (project is set)', () => {
375
+ assert.equal(result.guard, null);
376
+ });
377
+
378
+ it('finds phase in project-scoped phases directory', () => {
379
+ assert.equal(result.phase_found, true);
380
+ assert.equal(result.phase_number, '01');
381
+ assert.equal(result.phase_dir, path.join('.planning', 'projects', 'test-project', 'phases', '01-test-phase'));
382
+ });
383
+ });
384
+
385
+ describe('v2 mode with project: init execute-phase', () => {
386
+ let fixture;
387
+ let result;
388
+
389
+ beforeEach(() => {
390
+ fixture = v2FixtureWithProject();
391
+ result = runInit(fixture.cwd, 'execute-phase 1');
392
+ });
393
+
394
+ afterEach(() => {
395
+ fixture.cleanup();
396
+ });
397
+
398
+ it('returns project-qualified state_path', () => {
399
+ assert.equal(result.state_path, path.join('.planning', 'projects', 'test-project', 'STATE.md'));
400
+ });
401
+
402
+ it('returns project-qualified roadmap_path', () => {
403
+ assert.equal(result.roadmap_path, path.join('.planning', 'projects', 'test-project', 'ROADMAP.md'));
404
+ });
405
+
406
+ it('config_path stays product-level', () => {
407
+ assert.equal(result.config_path, '.planning/dgs.config.json');
408
+ });
409
+
410
+ it('returns project-qualified project_path', () => {
411
+ assert.equal(result.project_path, path.join('.planning', 'projects', 'test-project', 'PROJECT.md'));
412
+ });
413
+
414
+ it('returns project-qualified debug_dir', () => {
415
+ assert.equal(result.debug_dir, path.join('.planning', 'projects', 'test-project', 'debug'));
416
+ });
417
+
418
+ it('returns dgs_mode v2', () => {
419
+ assert.equal(result.dgs_mode, 'v2');
420
+ });
421
+
422
+ it('finds phase in project-scoped phases directory', () => {
423
+ assert.equal(result.phase_found, true);
424
+ assert.equal(result.phase_number, '01');
425
+ assert.equal(result.phase_dir, path.join('.planning', 'projects', 'test-project', 'phases', '01-test-phase'));
426
+ });
427
+ });
428
+
429
+ describe('v2 mode with project: init new-project', () => {
430
+ let fixture;
431
+ let result;
432
+
433
+ beforeEach(() => {
434
+ fixture = v2FixtureWithProject();
435
+ result = runInit(fixture.cwd, 'new-project');
436
+ });
437
+
438
+ afterEach(() => {
439
+ fixture.cleanup();
440
+ });
441
+
442
+ it('returns project-qualified state_path', () => {
443
+ assert.equal(result.state_path, path.join('.planning', 'projects', 'test-project', 'STATE.md'));
444
+ });
445
+
446
+ it('returns project-qualified roadmap_path', () => {
447
+ assert.equal(result.roadmap_path, path.join('.planning', 'projects', 'test-project', 'ROADMAP.md'));
448
+ });
449
+
450
+ it('returns project-qualified requirements_path', () => {
451
+ assert.equal(result.requirements_path, path.join('.planning', 'projects', 'test-project', 'REQUIREMENTS.md'));
452
+ });
453
+
454
+ it('returns project-qualified research_dir', () => {
455
+ assert.equal(result.research_dir, path.join('.planning', 'projects', 'test-project', 'research'));
456
+ });
457
+
458
+ it('returns project-qualified project_path', () => {
459
+ assert.equal(result.project_path, path.join('.planning', 'projects', 'test-project', 'PROJECT.md'));
460
+ });
461
+
462
+ it('returns dgs_mode v2', () => {
463
+ assert.equal(result.dgs_mode, 'v2');
464
+ });
465
+ });
466
+
467
+ describe('v2 mode with project: init progress', () => {
468
+ let fixture;
469
+ let result;
470
+
471
+ beforeEach(() => {
472
+ fixture = v2FixtureWithProject();
473
+ result = runInit(fixture.cwd, 'progress');
474
+ });
475
+
476
+ afterEach(() => {
477
+ fixture.cleanup();
478
+ });
479
+
480
+ it('returns project-qualified state_path', () => {
481
+ assert.equal(result.state_path, path.join('.planning', 'projects', 'test-project', 'STATE.md'));
482
+ });
483
+
484
+ it('config_path stays product-level', () => {
485
+ assert.equal(result.config_path, '.planning/dgs.config.json');
486
+ });
487
+
488
+ it('phase directory is project-qualified', () => {
489
+ if (result.phases.length > 0) {
490
+ assert.ok(result.phases[0].directory.startsWith(path.join('.planning', 'projects', 'test-project', 'phases')));
491
+ }
492
+ });
493
+ });
494
+
495
+ describe('v2 mode with project: init todos', () => {
496
+ let fixture;
497
+ let result;
498
+
499
+ beforeEach(() => {
500
+ fixture = v2FixtureWithProject();
501
+ result = runInit(fixture.cwd, 'todos');
502
+ });
503
+
504
+ afterEach(() => {
505
+ fixture.cleanup();
506
+ });
507
+
508
+ it('returns project-qualified pending_dir', () => {
509
+ assert.equal(result.pending_dir, path.join('.planning', 'projects', 'test-project', 'todos', 'pending'));
510
+ });
511
+
512
+ it('returns project-qualified completed_dir', () => {
513
+ assert.equal(result.completed_dir, path.join('.planning', 'projects', 'test-project', 'todos', 'completed'));
514
+ });
515
+
516
+ it('returns dgs_mode v2', () => {
517
+ assert.equal(result.dgs_mode, 'v2');
518
+ });
519
+ });
520
+
521
+ describe('v2 mode with project: init milestone-op', () => {
522
+ let fixture;
523
+ let result;
524
+
525
+ beforeEach(() => {
526
+ fixture = v2FixtureWithProject();
527
+ result = runInit(fixture.cwd, 'milestone-op');
528
+ });
529
+
530
+ afterEach(() => {
531
+ fixture.cleanup();
532
+ });
533
+
534
+ it('returns project-qualified file paths', () => {
535
+ assert.equal(result.project_path, path.join('.planning', 'projects', 'test-project', 'PROJECT.md'));
536
+ assert.equal(result.roadmap_path, path.join('.planning', 'projects', 'test-project', 'ROADMAP.md'));
537
+ assert.equal(result.state_path, path.join('.planning', 'projects', 'test-project', 'STATE.md'));
538
+ });
539
+
540
+ it('returns dgs_mode v2', () => {
541
+ assert.equal(result.dgs_mode, 'v2');
542
+ });
543
+ });
544
+
545
+ describe('v2 mode with project: init map-codebase', () => {
546
+ let fixture;
547
+ let result;
548
+
549
+ beforeEach(() => {
550
+ fixture = v2FixtureWithProject();
551
+ result = runInit(fixture.cwd, 'map-codebase');
552
+ });
553
+
554
+ afterEach(() => {
555
+ fixture.cleanup();
556
+ });
557
+
558
+ it('keeps codebase_dir product-level (shared, not project-qualified)', () => {
559
+ assert.equal(result.codebase_dir, '.planning/codebase');
560
+ });
561
+
562
+ it('returns dgs_mode v2', () => {
563
+ assert.equal(result.dgs_mode, 'v2');
564
+ });
565
+ });
566
+
567
+ // ─── init map-codebase: multi-repo fields ────────────────────────────────────
568
+
569
+ describe('init map-codebase: multi-repo with valid repos', () => {
570
+ let fixture;
571
+ let repoDir;
572
+
573
+ beforeEach(() => {
574
+ // Create a sibling repo directory with .git
575
+ repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-'));
576
+ fs.mkdirSync(path.join(repoDir, '.git'));
577
+
578
+ // Use a fixture with REPOS.md containing the sibling repo via relative path
579
+ fixture = createFixture({
580
+ '.planning/config.json': JSON.stringify({}),
581
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories for this product. Managed by DGS — manual edits may be overwritten.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| testrepo | ../${path.basename(repoDir)} | | Test repo |\n`,
582
+ '.planning/STATE.md': '# State',
583
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
584
+ });
585
+ });
586
+
587
+ afterEach(() => {
588
+ fixture.cleanup();
589
+ fs.rmSync(repoDir, { recursive: true, force: true });
590
+ });
591
+
592
+ it('returns multi_repo true when repos are registered', () => {
593
+ const result = runInit(fixture.cwd, 'map-codebase');
594
+ assert.equal(result.multi_repo, true);
595
+ });
596
+
597
+ it('returns repos array with resolved absolute paths', () => {
598
+ const result = runInit(fixture.cwd, 'map-codebase');
599
+ assert.ok(Array.isArray(result.repos));
600
+ assert.equal(result.repos.length, 1);
601
+ assert.equal(result.repos[0].name, 'testrepo');
602
+ assert.ok(path.isAbsolute(result.repos[0].path), 'Repo path should be absolute');
603
+ });
604
+
605
+ it('returns valid_repos array with repo names', () => {
606
+ const result = runInit(fixture.cwd, 'map-codebase');
607
+ assert.deepEqual(result.valid_repos, ['testrepo']);
608
+ });
609
+
610
+ it('returns empty existing_repo_maps when no per-repo codebase dirs exist', () => {
611
+ const result = runInit(fixture.cwd, 'map-codebase');
612
+ assert.deepEqual(result.existing_repo_maps, {});
613
+ });
614
+
615
+ it('preserves backward-compatible codebase_dir field', () => {
616
+ const result = runInit(fixture.cwd, 'map-codebase');
617
+ assert.equal(result.codebase_dir, '.planning/codebase');
618
+ });
619
+ });
620
+
621
+ describe('init map-codebase: no repos registered', () => {
622
+ let fixture;
623
+
624
+ beforeEach(() => {
625
+ fixture = createFixture({
626
+ '.planning/config.json': JSON.stringify({}),
627
+ '.planning/STATE.md': '# State',
628
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
629
+ });
630
+ });
631
+
632
+ afterEach(() => {
633
+ fixture.cleanup();
634
+ });
635
+
636
+ it('returns multi_repo false when no REPOS.md exists', () => {
637
+ const result = runInit(fixture.cwd, 'map-codebase');
638
+ assert.equal(result.multi_repo, false);
639
+ });
640
+
641
+ it('returns empty repos array when no REPOS.md exists', () => {
642
+ const result = runInit(fixture.cwd, 'map-codebase');
643
+ assert.deepEqual(result.repos, []);
644
+ });
645
+
646
+ it('returns empty valid_repos when no REPOS.md exists', () => {
647
+ const result = runInit(fixture.cwd, 'map-codebase');
648
+ assert.deepEqual(result.valid_repos, []);
649
+ });
650
+ });
651
+
652
+ describe('init map-codebase: empty REPOS.md table', () => {
653
+ let fixture;
654
+
655
+ beforeEach(() => {
656
+ fixture = createFixture({
657
+ '.planning/config.json': JSON.stringify({}),
658
+ '.planning/REPOS.md': '# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n',
659
+ '.planning/STATE.md': '# State',
660
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
661
+ });
662
+ });
663
+
664
+ afterEach(() => {
665
+ fixture.cleanup();
666
+ });
667
+
668
+ it('returns multi_repo false when REPOS.md has no data rows', () => {
669
+ const result = runInit(fixture.cwd, 'map-codebase');
670
+ assert.equal(result.multi_repo, false);
671
+ });
672
+
673
+ it('returns empty repos array when REPOS.md has no data rows', () => {
674
+ const result = runInit(fixture.cwd, 'map-codebase');
675
+ assert.deepEqual(result.repos, []);
676
+ });
677
+ });
678
+
679
+ describe('init map-codebase: existing per-repo maps detected', () => {
680
+ let fixture;
681
+ let repoDir;
682
+
683
+ beforeEach(() => {
684
+ repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-'));
685
+ fs.mkdirSync(path.join(repoDir, '.git'));
686
+
687
+ fixture = createFixture({
688
+ '.planning/config.json': JSON.stringify({}),
689
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| myrepo | ../${path.basename(repoDir)} | | My repo |\n`,
690
+ '.planning/STATE.md': '# State',
691
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
692
+ '.planning/codebase/myrepo/STACK.md': '# Stack\n',
693
+ '.planning/codebase/myrepo/ARCHITECTURE.md': '# Architecture\n',
694
+ });
695
+ });
696
+
697
+ afterEach(() => {
698
+ fixture.cleanup();
699
+ fs.rmSync(repoDir, { recursive: true, force: true });
700
+ });
701
+
702
+ it('returns existing_repo_maps with md files for repos that have them', () => {
703
+ const result = runInit(fixture.cwd, 'map-codebase');
704
+ assert.ok(result.existing_repo_maps.myrepo, 'Should have myrepo entry');
705
+ assert.ok(result.existing_repo_maps.myrepo.includes('STACK.md'));
706
+ assert.ok(result.existing_repo_maps.myrepo.includes('ARCHITECTURE.md'));
707
+ assert.equal(result.existing_repo_maps.myrepo.length, 2);
708
+ });
709
+ });
710
+
711
+ describe('init map-codebase: invalid repo paths produce errors', () => {
712
+ let fixture;
713
+
714
+ beforeEach(() => {
715
+ fixture = createFixture({
716
+ '.planning/config.json': JSON.stringify({}),
717
+ '.planning/REPOS.md': '# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| badrepo | ../nonexistent-path-nowhere | | Bad repo |\n',
718
+ '.planning/STATE.md': '# State',
719
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
720
+ });
721
+ });
722
+
723
+ afterEach(() => {
724
+ fixture.cleanup();
725
+ });
726
+
727
+ it('returns multi_repo false when all repos are invalid', () => {
728
+ const result = runInit(fixture.cwd, 'map-codebase');
729
+ assert.equal(result.multi_repo, false);
730
+ });
731
+
732
+ it('returns empty repos array when all repos are invalid', () => {
733
+ const result = runInit(fixture.cwd, 'map-codebase');
734
+ assert.deepEqual(result.repos, []);
735
+ });
736
+
737
+ it('returns repo_errors with validation messages', () => {
738
+ const result = runInit(fixture.cwd, 'map-codebase');
739
+ assert.ok(result.repo_errors.length > 0, 'Should have validation errors');
740
+ });
741
+ });
742
+
743
+ // ─── init map-codebase: --only flag ───────────────────────────────────────────
744
+
745
+ describe('init map-codebase: --only with valid repo name returns only that repo', () => {
746
+ let fixture;
747
+ let repoDirA;
748
+ let repoDirB;
749
+
750
+ beforeEach(() => {
751
+ repoDirA = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-a-'));
752
+ fs.mkdirSync(path.join(repoDirA, '.git'));
753
+ repoDirB = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-b-'));
754
+ fs.mkdirSync(path.join(repoDirB, '.git'));
755
+
756
+ fixture = createFixture({
757
+ '.planning/config.json': JSON.stringify({}),
758
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| repo-a | ../${path.basename(repoDirA)} | | Repo A |\n| repo-b | ../${path.basename(repoDirB)} | | Repo B |\n`,
759
+ '.planning/STATE.md': '# State',
760
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
761
+ });
762
+ });
763
+
764
+ afterEach(() => {
765
+ fixture.cleanup();
766
+ fs.rmSync(repoDirA, { recursive: true, force: true });
767
+ fs.rmSync(repoDirB, { recursive: true, force: true });
768
+ });
769
+
770
+ it('returns only_repo set to the targeted repo', () => {
771
+ const result = runInit(fixture.cwd, 'map-codebase --only repo-a');
772
+ assert.equal(result.only_repo, 'repo-a');
773
+ });
774
+
775
+ it('returns mode as update', () => {
776
+ const result = runInit(fixture.cwd, 'map-codebase --only repo-a');
777
+ assert.equal(result.mode, 'update');
778
+ });
779
+
780
+ it('returns valid_repos with only the targeted repo', () => {
781
+ const result = runInit(fixture.cwd, 'map-codebase --only repo-a');
782
+ assert.equal(result.valid_repos.length, 1);
783
+ assert.equal(result.valid_repos[0], 'repo-a');
784
+ });
785
+
786
+ it('returns repos array with only the targeted repo', () => {
787
+ const result = runInit(fixture.cwd, 'map-codebase --only repo-a');
788
+ assert.equal(result.repos.length, 1);
789
+ assert.equal(result.repos[0].name, 'repo-a');
790
+ });
791
+ });
792
+
793
+ describe('init map-codebase: --only with invalid repo name throws error', () => {
794
+ let fixture;
795
+ let repoDirA;
796
+
797
+ beforeEach(() => {
798
+ repoDirA = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-a-'));
799
+ fs.mkdirSync(path.join(repoDirA, '.git'));
800
+
801
+ fixture = createFixture({
802
+ '.planning/config.json': JSON.stringify({}),
803
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| repo-a | ../${path.basename(repoDirA)} | | Repo A |\n`,
804
+ '.planning/STATE.md': '# State',
805
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
806
+ });
807
+ });
808
+
809
+ afterEach(() => {
810
+ fixture.cleanup();
811
+ fs.rmSync(repoDirA, { recursive: true, force: true });
812
+ });
813
+
814
+ it('errors with message containing the invalid name', () => {
815
+ assert.throws(() => {
816
+ runInit(fixture.cwd, 'map-codebase --only nonexistent');
817
+ }, (err) => {
818
+ assert.ok(err.stderr.includes("'nonexistent' not found in REPOS.md"), 'Should mention invalid repo name');
819
+ return true;
820
+ });
821
+ });
822
+
823
+ it('error message includes available repo names', () => {
824
+ assert.throws(() => {
825
+ runInit(fixture.cwd, 'map-codebase --only nonexistent');
826
+ }, (err) => {
827
+ assert.ok(err.stderr.includes('repo-a'), 'Should list available repos');
828
+ return true;
829
+ });
830
+ });
831
+ });
832
+
833
+ describe('init map-codebase: without --only returns refresh mode', () => {
834
+ let fixture;
835
+ let repoDirA;
836
+ let repoDirB;
837
+
838
+ beforeEach(() => {
839
+ repoDirA = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-a-'));
840
+ fs.mkdirSync(path.join(repoDirA, '.git'));
841
+ repoDirB = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-b-'));
842
+ fs.mkdirSync(path.join(repoDirB, '.git'));
843
+
844
+ fixture = createFixture({
845
+ '.planning/config.json': JSON.stringify({}),
846
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| repo-a | ../${path.basename(repoDirA)} | | Repo A |\n| repo-b | ../${path.basename(repoDirB)} | | Repo B |\n`,
847
+ '.planning/STATE.md': '# State',
848
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
849
+ });
850
+ });
851
+
852
+ afterEach(() => {
853
+ fixture.cleanup();
854
+ fs.rmSync(repoDirA, { recursive: true, force: true });
855
+ fs.rmSync(repoDirB, { recursive: true, force: true });
856
+ });
857
+
858
+ it('returns only_repo as null', () => {
859
+ const result = runInit(fixture.cwd, 'map-codebase');
860
+ assert.equal(result.only_repo, null);
861
+ });
862
+
863
+ it('returns mode as refresh', () => {
864
+ const result = runInit(fixture.cwd, 'map-codebase');
865
+ assert.equal(result.mode, 'refresh');
866
+ });
867
+
868
+ it('returns all valid repos', () => {
869
+ const result = runInit(fixture.cwd, 'map-codebase');
870
+ assert.equal(result.valid_repos.length, 2);
871
+ assert.ok(result.valid_repos.includes('repo-a'));
872
+ assert.ok(result.valid_repos.includes('repo-b'));
873
+ });
874
+ });
875
+
876
+ describe('init map-codebase: --only without repo name argument throws error', () => {
877
+ let fixture;
878
+ let repoDirA;
879
+
880
+ beforeEach(() => {
881
+ repoDirA = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-a-'));
882
+ fs.mkdirSync(path.join(repoDirA, '.git'));
883
+
884
+ fixture = createFixture({
885
+ '.planning/config.json': JSON.stringify({}),
886
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| repo-a | ../${path.basename(repoDirA)} | | Repo A |\n`,
887
+ '.planning/STATE.md': '# State',
888
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
889
+ });
890
+ });
891
+
892
+ afterEach(() => {
893
+ fixture.cleanup();
894
+ fs.rmSync(repoDirA, { recursive: true, force: true });
895
+ });
896
+
897
+ it('errors when --only is provided without a following argument', () => {
898
+ assert.throws(() => {
899
+ runInit(fixture.cwd, 'map-codebase --only');
900
+ }, (err) => {
901
+ assert.ok(err.stderr.includes('--only requires a repo name'), 'Should mention missing argument');
902
+ return true;
903
+ });
904
+ });
905
+ });
906
+
907
+ // ─── init map-codebase: positional argument (shorthand for --only) ─────────
908
+ //
909
+ // Positional argument parsing happens in dgs-tools.cjs: when the first
910
+ // non-flag argument after "map-codebase" is not prefixed with "--", it is
911
+ // treated as a repo name and passed to cmdInitMapCodebase as onlyRepo.
912
+ // This converges with the --only flag path at the same function call, so
913
+ // the existing --only tests above also validate the downstream behavior
914
+ // of cmdInitMapCodebase when a repo name is supplied.
915
+
916
+ describe('init map-codebase: positional argument with valid repo name', () => {
917
+ let fixture;
918
+ let repoDirA;
919
+ let repoDirB;
920
+
921
+ beforeEach(() => {
922
+ repoDirA = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-a-'));
923
+ fs.mkdirSync(path.join(repoDirA, '.git'));
924
+ repoDirB = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-b-'));
925
+ fs.mkdirSync(path.join(repoDirB, '.git'));
926
+
927
+ fixture = createFixture({
928
+ '.planning/config.json': JSON.stringify({}),
929
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| repo-a | ../${path.basename(repoDirA)} | | Repo A |\n| repo-b | ../${path.basename(repoDirB)} | | Repo B |\n`,
930
+ '.planning/STATE.md': '# State',
931
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
932
+ });
933
+ });
934
+
935
+ afterEach(() => {
936
+ fixture.cleanup();
937
+ fs.rmSync(repoDirA, { recursive: true, force: true });
938
+ fs.rmSync(repoDirB, { recursive: true, force: true });
939
+ });
940
+
941
+ it('returns only_repo set to the positional repo name', () => {
942
+ // Positional: "map-codebase repo-a" instead of "map-codebase --only repo-a"
943
+ const result = runInit(fixture.cwd, 'map-codebase repo-a');
944
+ assert.equal(result.only_repo, 'repo-a');
945
+ });
946
+
947
+ it('returns mode as update (same as --only)', () => {
948
+ const result = runInit(fixture.cwd, 'map-codebase repo-a');
949
+ assert.equal(result.mode, 'update');
950
+ });
951
+
952
+ it('returns valid_repos with only the targeted repo', () => {
953
+ const result = runInit(fixture.cwd, 'map-codebase repo-a');
954
+ assert.equal(result.valid_repos.length, 1);
955
+ assert.equal(result.valid_repos[0], 'repo-a');
956
+ });
957
+
958
+ it('produces identical output to --only flag', () => {
959
+ const positional = runInit(fixture.cwd, 'map-codebase repo-a');
960
+ const flagged = runInit(fixture.cwd, 'map-codebase --only repo-a');
961
+ assert.equal(positional.only_repo, flagged.only_repo);
962
+ assert.equal(positional.mode, flagged.mode);
963
+ assert.deepEqual(positional.valid_repos, flagged.valid_repos);
964
+ });
965
+ });
966
+
967
+ describe('init map-codebase: positional argument with invalid repo name', () => {
968
+ let fixture;
969
+ let repoDirA;
970
+
971
+ beforeEach(() => {
972
+ repoDirA = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-a-'));
973
+ fs.mkdirSync(path.join(repoDirA, '.git'));
974
+
975
+ fixture = createFixture({
976
+ '.planning/config.json': JSON.stringify({}),
977
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| repo-a | ../${path.basename(repoDirA)} | | Repo A |\n`,
978
+ '.planning/STATE.md': '# State',
979
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
980
+ });
981
+ });
982
+
983
+ afterEach(() => {
984
+ fixture.cleanup();
985
+ fs.rmSync(repoDirA, { recursive: true, force: true });
986
+ });
987
+
988
+ it('errors with message listing valid repos when positional name is invalid', () => {
989
+ assert.throws(() => {
990
+ runInit(fixture.cwd, 'map-codebase nonexistent');
991
+ }, (err) => {
992
+ assert.ok(err.stderr.includes("'nonexistent' not found in REPOS.md"), 'Should mention invalid repo name');
993
+ assert.ok(err.stderr.includes('repo-a'), 'Should list available repos');
994
+ return true;
995
+ });
996
+ });
997
+ });
998
+
999
+ describe('init map-codebase: argument starting with -- is not treated as positional repo name', () => {
1000
+ let fixture;
1001
+ let repoDirA;
1002
+
1003
+ beforeEach(() => {
1004
+ repoDirA = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-repo-a-'));
1005
+ fs.mkdirSync(path.join(repoDirA, '.git'));
1006
+
1007
+ fixture = createFixture({
1008
+ '.planning/config.json': JSON.stringify({}),
1009
+ '.planning/REPOS.md': `# Repos\n\nRegistered repositories.\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| repo-a | ../${path.basename(repoDirA)} | | Repo A |\n`,
1010
+ '.planning/STATE.md': '# State',
1011
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n',
1012
+ });
1013
+ });
1014
+
1015
+ afterEach(() => {
1016
+ fixture.cleanup();
1017
+ fs.rmSync(repoDirA, { recursive: true, force: true });
1018
+ });
1019
+
1020
+ it('treats --raw as a flag, not a repo name (refresh mode)', () => {
1021
+ const result = runInit(fixture.cwd, 'map-codebase --raw');
1022
+ // --raw is not a repo name, so onlyRepo stays null -> refresh mode
1023
+ assert.equal(result.only_repo, null);
1024
+ assert.equal(result.mode, 'refresh');
1025
+ });
1026
+ });
1027
+
1028
+ // ─── v2 Guard Tests ──────────────────────────────────────────────────────────
1029
+
1030
+ describe('v2 guard: one project, no current_project (auto-select)', () => {
1031
+ let fixture;
1032
+ let result;
1033
+
1034
+ beforeEach(() => {
1035
+ fixture = v2FixtureOneProject();
1036
+ result = runInit(fixture.cwd, 'progress');
1037
+ });
1038
+
1039
+ afterEach(() => {
1040
+ fixture.cleanup();
1041
+ });
1042
+
1043
+ it('guard action is auto_selected', () => {
1044
+ assert.ok(result.guard);
1045
+ assert.equal(result.guard.action, 'auto_selected');
1046
+ });
1047
+
1048
+ it('guard includes the auto-selected project name', () => {
1049
+ assert.equal(result.guard.project, 'test-project');
1050
+ });
1051
+
1052
+ it('current_project is set to the auto-selected project', () => {
1053
+ assert.equal(result.current_project, 'test-project');
1054
+ });
1055
+
1056
+ it('project_root is set to the auto-selected project root', () => {
1057
+ assert.equal(result.project_root, path.join('.planning', 'projects', 'test-project'));
1058
+ });
1059
+
1060
+ it('dgs_mode is v2', () => {
1061
+ assert.equal(result.dgs_mode, 'v2');
1062
+ });
1063
+ });
1064
+
1065
+ describe('v2 guard: zero projects (create guidance)', () => {
1066
+ let fixture;
1067
+ let result;
1068
+
1069
+ beforeEach(() => {
1070
+ fixture = v2FixtureZeroProjects();
1071
+ result = runInit(fixture.cwd, 'progress');
1072
+ });
1073
+
1074
+ afterEach(() => {
1075
+ fixture.cleanup();
1076
+ });
1077
+
1078
+ it('guard action is create', () => {
1079
+ assert.ok(result.guard);
1080
+ assert.equal(result.guard.action, 'create');
1081
+ });
1082
+
1083
+ it('guard message mentions /dgs:new-project', () => {
1084
+ assert.ok(result.guard.message.includes('/dgs:new-project'));
1085
+ });
1086
+
1087
+ it('project_root is null', () => {
1088
+ assert.equal(result.project_root, null);
1089
+ });
1090
+
1091
+ it('dgs_mode is v2', () => {
1092
+ assert.equal(result.dgs_mode, 'v2');
1093
+ });
1094
+ });
1095
+
1096
+ describe('v2 guard: multiple projects (prompt)', () => {
1097
+ let fixture;
1098
+ let result;
1099
+
1100
+ beforeEach(() => {
1101
+ fixture = v2FixtureMultipleProjects();
1102
+ result = runInit(fixture.cwd, 'progress');
1103
+ });
1104
+
1105
+ afterEach(() => {
1106
+ fixture.cleanup();
1107
+ });
1108
+
1109
+ it('guard action is prompt', () => {
1110
+ assert.ok(result.guard);
1111
+ assert.equal(result.guard.action, 'prompt');
1112
+ });
1113
+
1114
+ it('guard includes project list', () => {
1115
+ assert.ok(Array.isArray(result.guard.projects));
1116
+ assert.equal(result.guard.projects.length, 2);
1117
+ assert.ok(result.guard.projects.includes('alpha-project'));
1118
+ assert.ok(result.guard.projects.includes('beta-project'));
1119
+ });
1120
+
1121
+ it('project_root is null (no project selected)', () => {
1122
+ assert.equal(result.project_root, null);
1123
+ });
1124
+
1125
+ it('current_project is null', () => {
1126
+ assert.equal(result.current_project, null);
1127
+ });
1128
+
1129
+ it('dgs_mode is v2', () => {
1130
+ assert.equal(result.dgs_mode, 'v2');
1131
+ });
1132
+ });
1133
+
1134
+ // ─── branch_name with {project} resolution ──────────────────────────────────
1135
+
1136
+ describe('branch_name with {project} resolution', () => {
1137
+ it('v1 execute-phase branch_name resolves {project} from current_project', () => {
1138
+ const fixture = createFixture({
1139
+ '.planning/config.json': JSON.stringify({ current_project: 'myapp', git: { branching_strategy: 'phase' } }),
1140
+ '.planning/STATE.md': '# State',
1141
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n- [ ] **Phase 3: Auth** - Auth\n',
1142
+ '.planning/REQUIREMENTS.md': '# Requirements',
1143
+ '.planning/PROJECT.md': '# Project',
1144
+ '.planning/phases/03-auth/03-01-PLAN.md': '---\nphase: 03-auth\nplan: 01\n---\n# Plan',
1145
+ });
1146
+ try {
1147
+ const result = runInit(fixture.cwd, 'execute-phase 3');
1148
+ assert.equal(result.branch_name, 'dgs/myapp/phase-03-auth');
1149
+ } finally {
1150
+ fixture.cleanup();
1151
+ }
1152
+ });
1153
+
1154
+ it('v2 execute-phase branch_name resolves {project} from current_project', () => {
1155
+ const fixture = createFixture({
1156
+ '.planning/config.json': JSON.stringify({ current_project: 'checkout', git: { branching_strategy: 'phase' } }),
1157
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n|---------|--------|\n| checkout | Active |\n',
1158
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n|------|------|\n',
1159
+ '.planning/projects/checkout/STATE.md': '# State',
1160
+ '.planning/projects/checkout/ROADMAP.md': '# Roadmap\n\n## Phases\n\n- [ ] **Phase 1: Init** - Init\n',
1161
+ '.planning/projects/checkout/REQUIREMENTS.md': '# Requirements',
1162
+ '.planning/projects/checkout/PROJECT.md': '# Project',
1163
+ '.planning/projects/checkout/phases/01-init/01-01-PLAN.md': '---\nphase: 01-init\nplan: 01\n---\n# Plan',
1164
+ });
1165
+ try {
1166
+ const result = runInit(fixture.cwd, 'execute-phase 1');
1167
+ assert.equal(result.branch_name, 'dgs/checkout/phase-01-init');
1168
+ } finally {
1169
+ fixture.cleanup();
1170
+ }
1171
+ });
1172
+
1173
+ it('branch_name with custom template without {project} is unchanged', () => {
1174
+ const fixture = createFixture({
1175
+ '.planning/config.json': JSON.stringify({
1176
+ current_project: 'myapp',
1177
+ git: { branching_strategy: 'phase', phase_branch_template: 'feature/phase-{phase}-{slug}' }
1178
+ }),
1179
+ '.planning/STATE.md': '# State',
1180
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n- [ ] **Phase 3: Auth** - Auth\n',
1181
+ '.planning/REQUIREMENTS.md': '# Requirements',
1182
+ '.planning/PROJECT.md': '# Project',
1183
+ '.planning/phases/03-auth/03-01-PLAN.md': '---\nphase: 03-auth\nplan: 01\n---\n# Plan',
1184
+ });
1185
+ try {
1186
+ const result = runInit(fixture.cwd, 'execute-phase 3');
1187
+ assert.equal(result.branch_name, 'feature/phase-03-auth');
1188
+ } finally {
1189
+ fixture.cleanup();
1190
+ }
1191
+ });
1192
+
1193
+ it('branch_name errors when {project} in template but current_project is null', () => {
1194
+ const fixture = createFixture({
1195
+ '.planning/config.json': JSON.stringify({ git: { branching_strategy: 'phase' } }),
1196
+ '.planning/STATE.md': '# State',
1197
+ '.planning/ROADMAP.md': '# Roadmap\n\n## Phases\n\n- [ ] **Phase 3: Auth** - Auth\n',
1198
+ '.planning/REQUIREMENTS.md': '# Requirements',
1199
+ '.planning/PROJECT.md': '# Project',
1200
+ '.planning/phases/03-auth/03-01-PLAN.md': '---\nphase: 03-auth\nplan: 01\n---\n# Plan',
1201
+ });
1202
+ try {
1203
+ const result = runInit(fixture.cwd, 'execute-phase 3');
1204
+ assert.equal(result.branch_name, 'current_project not set. Run /dgs:settings to configure.');
1205
+ } finally {
1206
+ fixture.cleanup();
1207
+ }
1208
+ });
1209
+ });
1210
+
1211
+ // ─── Backward Compatibility ──────────────────────────────────────────────────
1212
+
1213
+ describe('backward compatibility: v1 output is superset of old shape', () => {
1214
+ let fixture;
1215
+
1216
+ beforeEach(() => {
1217
+ fixture = v1Fixture();
1218
+ });
1219
+
1220
+ afterEach(() => {
1221
+ fixture.cleanup();
1222
+ });
1223
+
1224
+ it('plan-phase output includes all pre-v2 fields', () => {
1225
+ const result = runInit(fixture.cwd, 'plan-phase 1');
1226
+ // Pre-v2 fields that must still exist
1227
+ assert.ok('researcher_model' in result);
1228
+ assert.ok('planner_model' in result);
1229
+ assert.ok('checker_model' in result);
1230
+ assert.ok('research_enabled' in result);
1231
+ assert.ok('plan_checker_enabled' in result);
1232
+ assert.ok('phase_found' in result);
1233
+ assert.ok('phase_dir' in result);
1234
+ assert.ok('phase_number' in result);
1235
+ assert.ok('planning_exists' in result);
1236
+ assert.ok('roadmap_exists' in result);
1237
+ assert.ok('state_path' in result);
1238
+ assert.ok('roadmap_path' in result);
1239
+ // New v2 fields that are additive
1240
+ assert.ok('dgs_mode' in result);
1241
+ assert.ok('current_project' in result);
1242
+ assert.ok('project_root' in result);
1243
+ assert.ok('guard' in result);
1244
+ });
1245
+
1246
+ it('execute-phase output includes all pre-v2 fields', () => {
1247
+ const result = runInit(fixture.cwd, 'execute-phase 1');
1248
+ assert.ok('executor_model' in result);
1249
+ assert.ok('verifier_model' in result);
1250
+ assert.ok('phase_found' in result);
1251
+ assert.ok('plans' in result);
1252
+ assert.ok('summaries' in result);
1253
+ assert.ok('state_exists' in result);
1254
+ assert.ok('roadmap_exists' in result);
1255
+ assert.ok('state_path' in result);
1256
+ assert.ok('config_path' in result);
1257
+ assert.ok('dgs_mode' in result);
1258
+ assert.ok('current_project' in result);
1259
+ });
1260
+
1261
+ it('progress output includes all pre-v2 fields', () => {
1262
+ const result = runInit(fixture.cwd, 'progress');
1263
+ assert.ok('phases' in result);
1264
+ assert.ok('phase_count' in result);
1265
+ assert.ok('completed_count' in result);
1266
+ assert.ok('state_path' in result);
1267
+ assert.ok('roadmap_path' in result);
1268
+ assert.ok('config_path' in result);
1269
+ assert.ok('dgs_mode' in result);
1270
+ });
1271
+ });