@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,822 @@
1
+ /**
2
+ * Tests for core.cjs v2 path resolution functions
3
+ *
4
+ * Uses Node.js built-in test runner (node:test) and assert (node:assert).
5
+ * Each test creates an isolated temp directory fixture and cleans up after.
6
+ */
7
+
8
+ const { describe, it, beforeEach, afterEach } = require('node:test');
9
+ const assert = require('node:assert/strict');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+
14
+ const { createFixture, createTempProject } = require('./test-helpers.cjs');
15
+
16
+ // Import the functions under test
17
+ const {
18
+ resolveProjectPath,
19
+ getProjectRoot,
20
+ requireProjectRoot,
21
+ isV2Install,
22
+ getProjectFolders,
23
+ getV2Hint,
24
+ loadConfig,
25
+ isProjectCompleted,
26
+ getProjectDir,
27
+ } = require('./core.cjs');
28
+
29
+ // ─── v1 Mode Tests ────────────────────────────────────────────────────────────
30
+
31
+ describe('v1 mode (no v2 markers)', () => {
32
+ let fixture;
33
+
34
+ beforeEach(() => {
35
+ fixture = createFixture({
36
+ '.planning/config.json': JSON.stringify({}),
37
+ '.planning/STATE.md': '# State',
38
+ '.planning/ROADMAP.md': '# Roadmap',
39
+ '.planning/phases/': null,
40
+ });
41
+ });
42
+
43
+ afterEach(() => {
44
+ fixture.cleanup();
45
+ });
46
+
47
+ it('getProjectRoot returns .planning', () => {
48
+ const result = getProjectRoot(fixture.cwd);
49
+ assert.equal(result, '.planning');
50
+ });
51
+
52
+ it('resolveProjectPath with relative path returns .planning/<path>', () => {
53
+ const result = resolveProjectPath(fixture.cwd, 'STATE.md');
54
+ assert.equal(result, path.join('.planning', 'STATE.md'));
55
+ });
56
+
57
+ it('resolveProjectPath with nested path returns correct path', () => {
58
+ const result = resolveProjectPath(fixture.cwd, 'phases/01-foo');
59
+ assert.equal(result, path.join('.planning', 'phases', '01-foo'));
60
+ });
61
+
62
+ it('resolveProjectPath without second arg returns .planning', () => {
63
+ const result = resolveProjectPath(fixture.cwd);
64
+ assert.equal(result, '.planning');
65
+ });
66
+
67
+ it('isV2Install returns false', () => {
68
+ const result = isV2Install(fixture.cwd);
69
+ assert.equal(result, false);
70
+ });
71
+ });
72
+
73
+ // ─── v2 Mode with current_project Tests ───────────────────────────────────────
74
+
75
+ describe('v2 mode with current_project', () => {
76
+ let fixture;
77
+
78
+ beforeEach(() => {
79
+ fixture = createFixture({
80
+ '.planning/config.json': JSON.stringify({ current_project: 'auth-overhaul' }),
81
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
82
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n',
83
+ '.planning/projects/auth-overhaul/STATE.md': '# State',
84
+ '.planning/projects/auth-overhaul/ROADMAP.md': '# Roadmap',
85
+ '.planning/projects/auth-overhaul/phases/': null,
86
+ });
87
+ });
88
+
89
+ afterEach(() => {
90
+ fixture.cleanup();
91
+ });
92
+
93
+ it('getProjectRoot returns .planning/projects/<project>', () => {
94
+ const result = getProjectRoot(fixture.cwd);
95
+ assert.equal(result, path.join('.planning', 'projects', 'auth-overhaul'));
96
+ });
97
+
98
+ it('resolveProjectPath with STATE.md returns project-qualified path', () => {
99
+ const result = resolveProjectPath(fixture.cwd, 'STATE.md');
100
+ assert.equal(result, path.join('.planning', 'projects', 'auth-overhaul', 'STATE.md'));
101
+ });
102
+
103
+ it('resolveProjectPath with nested path returns project-qualified path', () => {
104
+ const result = resolveProjectPath(fixture.cwd, 'phases/01-foo');
105
+ assert.equal(result, path.join('.planning', 'projects', 'auth-overhaul', 'phases', '01-foo'));
106
+ });
107
+ });
108
+
109
+ // ─── v2 Mode without current_project (guard trigger) Tests ────────────────────
110
+
111
+ describe('v2 mode without current_project (guard trigger)', () => {
112
+ it('throws NO_CURRENT_PROJECT_V2 when PROJECTS.md exists with valid header', () => {
113
+ const fixture = createFixture({
114
+ '.planning/config.json': JSON.stringify({}),
115
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
116
+ });
117
+
118
+ try {
119
+ assert.throws(
120
+ () => getProjectRoot(fixture.cwd),
121
+ (err) => err.message === 'NO_CURRENT_PROJECT_V2'
122
+ );
123
+ } finally {
124
+ fixture.cleanup();
125
+ }
126
+ });
127
+
128
+ it('throws NO_CURRENT_PROJECT_V2 when REPOS.md exists with valid header', () => {
129
+ const fixture = createFixture({
130
+ '.planning/config.json': JSON.stringify({}),
131
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n',
132
+ });
133
+
134
+ try {
135
+ assert.throws(
136
+ () => getProjectRoot(fixture.cwd),
137
+ (err) => err.message === 'NO_CURRENT_PROJECT_V2'
138
+ );
139
+ } finally {
140
+ fixture.cleanup();
141
+ }
142
+ });
143
+ });
144
+
145
+ // ─── Strict v2 Marker Validation Tests ────────────────────────────────────────
146
+
147
+ describe('strict v2 marker validation', () => {
148
+ it('isV2Install returns false when PROJECTS.md has non-DGS content', () => {
149
+ const fixture = createFixture({
150
+ '.planning/config.json': JSON.stringify({}),
151
+ '.planning/PROJECTS.md': 'Shopping List\n- apples\n- bananas',
152
+ });
153
+
154
+ try {
155
+ assert.equal(isV2Install(fixture.cwd), false);
156
+ } finally {
157
+ fixture.cleanup();
158
+ }
159
+ });
160
+
161
+ it('isV2Install returns true when PROJECTS.md starts with # Projects', () => {
162
+ const fixture = createFixture({
163
+ '.planning/config.json': JSON.stringify({}),
164
+ '.planning/PROJECTS.md': '# Projects\n\n## Active\n',
165
+ });
166
+
167
+ try {
168
+ assert.equal(isV2Install(fixture.cwd), true);
169
+ } finally {
170
+ fixture.cleanup();
171
+ }
172
+ });
173
+
174
+ it('isV2Install returns true when REPOS.md starts with # Repos', () => {
175
+ const fixture = createFixture({
176
+ '.planning/config.json': JSON.stringify({}),
177
+ '.planning/REPOS.md': '# Repos\n\n| Name | Path |\n',
178
+ });
179
+
180
+ try {
181
+ assert.equal(isV2Install(fixture.cwd), true);
182
+ } finally {
183
+ fixture.cleanup();
184
+ }
185
+ });
186
+
187
+ it('isV2Install returns false when neither file exists', () => {
188
+ const fixture = createFixture({
189
+ '.planning/config.json': JSON.stringify({}),
190
+ });
191
+
192
+ try {
193
+ assert.equal(isV2Install(fixture.cwd), false);
194
+ } finally {
195
+ fixture.cleanup();
196
+ }
197
+ });
198
+ });
199
+
200
+ // ─── Project Directory Validation Tests ───────────────────────────────────────
201
+
202
+ describe('project directory validation', () => {
203
+ it('throws PROJECT_NOT_FOUND when current_project dir does not exist', () => {
204
+ const fixture = createFixture({
205
+ '.planning/config.json': JSON.stringify({ current_project: 'ghost-project' }),
206
+ '.planning/PROJECTS.md': '# Projects\n',
207
+ });
208
+
209
+ try {
210
+ assert.throws(
211
+ () => getProjectRoot(fixture.cwd),
212
+ (err) => err.message.includes('PROJECT_NOT_FOUND')
213
+ );
214
+ } finally {
215
+ fixture.cleanup();
216
+ }
217
+ });
218
+ });
219
+
220
+ // ─── Completed Project Guards Tests ───────────────────────────────────────────
221
+
222
+ describe('completed project guards', () => {
223
+ it('getProjectRoot throws PROJECT_COMPLETED when project STATUS is completed', () => {
224
+ const fixture = createFixture({
225
+ '.planning/config.json': JSON.stringify({ current_project: 'done-proj' }),
226
+ '.planning/PROJECTS.md': '# Projects\n',
227
+ '.planning/projects/done-proj/STATE.md': '# Project State\n\nStatus: completed\nCompleted: 2026-02-20\n',
228
+ });
229
+
230
+ try {
231
+ assert.throws(
232
+ () => getProjectRoot(fixture.cwd),
233
+ (err) => err.message.includes('PROJECT_COMPLETED')
234
+ );
235
+ } finally {
236
+ fixture.cleanup();
237
+ }
238
+ });
239
+
240
+ it('getProjectRoot throws PROJECT_COMPLETED for case-insensitive status (Completed)', () => {
241
+ const fixture = createFixture({
242
+ '.planning/config.json': JSON.stringify({ current_project: 'done-proj' }),
243
+ '.planning/PROJECTS.md': '# Projects\n',
244
+ '.planning/projects/done-proj/STATE.md': '# Project State\n\nStatus: Completed\n',
245
+ });
246
+
247
+ try {
248
+ assert.throws(
249
+ () => getProjectRoot(fixture.cwd),
250
+ (err) => err.message.includes('PROJECT_COMPLETED')
251
+ );
252
+ } finally {
253
+ fixture.cleanup();
254
+ }
255
+ });
256
+
257
+ it('getProjectRoot does NOT throw for active project', () => {
258
+ const fixture = createFixture({
259
+ '.planning/config.json': JSON.stringify({ current_project: 'active-proj' }),
260
+ '.planning/PROJECTS.md': '# Projects\n',
261
+ '.planning/projects/active-proj/STATE.md': '# Project State\n\nStatus: Active\n',
262
+ });
263
+
264
+ try {
265
+ const result = getProjectRoot(fixture.cwd);
266
+ assert.equal(result, path.join('.planning', 'projects', 'active-proj'));
267
+ } finally {
268
+ fixture.cleanup();
269
+ }
270
+ });
271
+
272
+ it('getProjectRoot does NOT throw when STATE.md is missing', () => {
273
+ const fixture = createFixture({
274
+ '.planning/config.json': JSON.stringify({ current_project: 'no-state-proj' }),
275
+ '.planning/PROJECTS.md': '# Projects\n',
276
+ '.planning/projects/no-state-proj/': null,
277
+ });
278
+
279
+ try {
280
+ const result = getProjectRoot(fixture.cwd);
281
+ assert.equal(result, path.join('.planning', 'projects', 'no-state-proj'));
282
+ } finally {
283
+ fixture.cleanup();
284
+ }
285
+ });
286
+
287
+ it('requireProjectRoot propagates PROJECT_COMPLETED', () => {
288
+ const fixture = createFixture({
289
+ '.planning/config.json': JSON.stringify({ current_project: 'done-proj' }),
290
+ '.planning/PROJECTS.md': '# Projects\n',
291
+ '.planning/projects/done-proj/STATE.md': '# Project State\n\nStatus: completed\n',
292
+ });
293
+
294
+ try {
295
+ assert.throws(
296
+ () => requireProjectRoot(fixture.cwd),
297
+ (err) => err.message.includes('PROJECT_COMPLETED')
298
+ );
299
+ } finally {
300
+ fixture.cleanup();
301
+ }
302
+ });
303
+
304
+ it('resolveProjectPath throws PROJECT_COMPLETED for completed project', () => {
305
+ const fixture = createFixture({
306
+ '.planning/config.json': JSON.stringify({ current_project: 'done-proj' }),
307
+ '.planning/PROJECTS.md': '# Projects\n',
308
+ '.planning/projects/done-proj/STATE.md': '# Project State\n\nStatus: completed\n',
309
+ });
310
+
311
+ try {
312
+ assert.throws(
313
+ () => resolveProjectPath(fixture.cwd, 'STATE.md'),
314
+ (err) => err.message.includes('PROJECT_COMPLETED')
315
+ );
316
+ } finally {
317
+ fixture.cleanup();
318
+ }
319
+ });
320
+
321
+ it('isProjectCompleted returns true for completed project', () => {
322
+ const fixture = createFixture({
323
+ '.planning/config.json': JSON.stringify({}),
324
+ '.planning/projects/done-proj/STATE.md': '# Project State\n\nStatus: completed\n',
325
+ });
326
+
327
+ try {
328
+ assert.equal(isProjectCompleted(fixture.cwd, 'done-proj'), true);
329
+ } finally {
330
+ fixture.cleanup();
331
+ }
332
+ });
333
+
334
+ it('isProjectCompleted returns false for active project', () => {
335
+ const fixture = createFixture({
336
+ '.planning/config.json': JSON.stringify({}),
337
+ '.planning/projects/active-proj/STATE.md': '# Project State\n\nStatus: Active\n',
338
+ });
339
+
340
+ try {
341
+ assert.equal(isProjectCompleted(fixture.cwd, 'active-proj'), false);
342
+ } finally {
343
+ fixture.cleanup();
344
+ }
345
+ });
346
+
347
+ it('isProjectCompleted returns false when STATE.md missing', () => {
348
+ const fixture = createFixture({
349
+ '.planning/config.json': JSON.stringify({}),
350
+ '.planning/projects/no-state-proj/': null,
351
+ });
352
+
353
+ try {
354
+ assert.equal(isProjectCompleted(fixture.cwd, 'no-state-proj'), false);
355
+ } finally {
356
+ fixture.cleanup();
357
+ }
358
+ });
359
+ });
360
+
361
+ // ─── getProjectFolders Tests ──────────────────────────────────────────────────
362
+
363
+ describe('getProjectFolders', () => {
364
+ it('returns empty array for v1 install', () => {
365
+ const fixture = createFixture({
366
+ '.planning/config.json': JSON.stringify({}),
367
+ '.planning/STATE.md': '# State',
368
+ '.planning/phases/': null,
369
+ });
370
+
371
+ try {
372
+ const result = getProjectFolders(fixture.cwd);
373
+ assert.deepEqual(result, []);
374
+ } finally {
375
+ fixture.cleanup();
376
+ }
377
+ });
378
+
379
+ it('returns project folder names from projects/ directory', () => {
380
+ const fixture = createFixture({
381
+ '.planning/config.json': JSON.stringify({}),
382
+ '.planning/PROJECTS.md': '# Projects\n',
383
+ '.planning/projects/auth-overhaul/STATE.md': '# State',
384
+ '.planning/projects/dashboard-v2/STATE.md': '# State',
385
+ '.planning/phases/': null,
386
+ '.planning/codebase/': null,
387
+ '.planning/archive/': null,
388
+ '.planning/todos/': null,
389
+ });
390
+
391
+ try {
392
+ const result = getProjectFolders(fixture.cwd);
393
+ assert.deepEqual(result, ['auth-overhaul', 'dashboard-v2']);
394
+ } finally {
395
+ fixture.cleanup();
396
+ }
397
+ });
398
+
399
+ it('only includes directories containing STATE.md', () => {
400
+ const fixture = createFixture({
401
+ '.planning/config.json': JSON.stringify({}),
402
+ '.planning/PROJECTS.md': '# Projects\n',
403
+ '.planning/projects/valid-project/STATE.md': '# State',
404
+ '.planning/projects/empty-folder/': null,
405
+ });
406
+
407
+ try {
408
+ const result = getProjectFolders(fixture.cwd);
409
+ assert.deepEqual(result, ['valid-project']);
410
+ } finally {
411
+ fixture.cleanup();
412
+ }
413
+ });
414
+
415
+ it('excludes dot-directories', () => {
416
+ const fixture = createFixture({
417
+ '.planning/config.json': JSON.stringify({}),
418
+ '.planning/projects/.hidden/STATE.md': '# State',
419
+ '.planning/projects/real-project/STATE.md': '# State',
420
+ });
421
+
422
+ try {
423
+ const result = getProjectFolders(fixture.cwd);
424
+ assert.deepEqual(result, ['real-project']);
425
+ } finally {
426
+ fixture.cleanup();
427
+ }
428
+ });
429
+
430
+ it('returns empty array when .planning does not exist', () => {
431
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-test-'));
432
+ try {
433
+ const result = getProjectFolders(cwd);
434
+ assert.deepEqual(result, []);
435
+ } finally {
436
+ fs.rmSync(cwd, { recursive: true, force: true });
437
+ }
438
+ });
439
+
440
+ it('returns empty array when projects/ directory does not exist', () => {
441
+ const fixture = createFixture({
442
+ '.planning/config.json': JSON.stringify({}),
443
+ '.planning/PROJECTS.md': '# Projects\n',
444
+ '.planning/phases/': null,
445
+ });
446
+
447
+ try {
448
+ const result = getProjectFolders(fixture.cwd);
449
+ assert.deepEqual(result, []);
450
+ } finally {
451
+ fixture.cleanup();
452
+ }
453
+ });
454
+ });
455
+
456
+ // ─── getV2Hint Tests ──────────────────────────────────────────────────────────
457
+
458
+ describe('getV2Hint', () => {
459
+ it('returns hint text when v2_hint_shown is falsy', () => {
460
+ const config = {};
461
+ const result = getV2Hint(null, config);
462
+ assert.equal(typeof result, 'string');
463
+ assert.ok(result.includes('v2'));
464
+ assert.ok(result.includes('/dgs:init-product'));
465
+ });
466
+
467
+ it('returns null when v2_hint_shown is true', () => {
468
+ const config = { v2_hint_shown: true };
469
+ const result = getV2Hint(null, config);
470
+ assert.equal(result, null);
471
+ });
472
+ });
473
+
474
+ // ─── Edge Cases ───────────────────────────────────────────────────────────────
475
+
476
+ describe('edge cases', () => {
477
+ it('resolveProjectPath with empty string returns project root', () => {
478
+ const fixture = createFixture({
479
+ '.planning/config.json': JSON.stringify({}),
480
+ });
481
+
482
+ try {
483
+ const result = resolveProjectPath(fixture.cwd, '');
484
+ assert.equal(result, '.planning');
485
+ } finally {
486
+ fixture.cleanup();
487
+ }
488
+ });
489
+
490
+ it('getProjectRoot handles missing .planning gracefully', () => {
491
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-test-'));
492
+ try {
493
+ // Should not crash — returns .planning (v1 fallback since no markers)
494
+ const result = getProjectRoot(cwd);
495
+ assert.equal(result, '.planning');
496
+ } finally {
497
+ fs.rmSync(cwd, { recursive: true, force: true });
498
+ }
499
+ });
500
+
501
+ it('treats empty string current_project as unset', () => {
502
+ const fixture = createFixture({
503
+ '.planning/config.json': JSON.stringify({ current_project: '' }),
504
+ });
505
+
506
+ try {
507
+ // Should return .planning (v1 fallback), not .planning/
508
+ const result = getProjectRoot(fixture.cwd);
509
+ assert.equal(result, '.planning');
510
+ } finally {
511
+ fixture.cleanup();
512
+ }
513
+ });
514
+
515
+ it('rejects current_project with path traversal (..)', () => {
516
+ const fixture = createFixture({
517
+ '.planning/config.json': JSON.stringify({ current_project: '../etc' }),
518
+ '.planning/PROJECTS.md': '# Projects\n',
519
+ });
520
+
521
+ try {
522
+ assert.throws(
523
+ () => getProjectRoot(fixture.cwd),
524
+ (err) => err.message.includes('INVALID_PROJECT_NAME')
525
+ );
526
+ } finally {
527
+ fixture.cleanup();
528
+ }
529
+ });
530
+
531
+ it('rejects current_project with forward slash', () => {
532
+ const fixture = createFixture({
533
+ '.planning/config.json': JSON.stringify({ current_project: 'foo/bar' }),
534
+ '.planning/PROJECTS.md': '# Projects\n',
535
+ });
536
+
537
+ try {
538
+ assert.throws(
539
+ () => getProjectRoot(fixture.cwd),
540
+ (err) => err.message.includes('INVALID_PROJECT_NAME')
541
+ );
542
+ } finally {
543
+ fixture.cleanup();
544
+ }
545
+ });
546
+
547
+ it('rejects current_project with backslash', () => {
548
+ const fixture = createFixture({
549
+ '.planning/config.json': JSON.stringify({ current_project: 'foo\\bar' }),
550
+ '.planning/PROJECTS.md': '# Projects\n',
551
+ });
552
+
553
+ try {
554
+ assert.throws(
555
+ () => getProjectRoot(fixture.cwd),
556
+ (err) => err.message.includes('INVALID_PROJECT_NAME')
557
+ );
558
+ } finally {
559
+ fixture.cleanup();
560
+ }
561
+ });
562
+ });
563
+
564
+ // ─── requireProjectRoot Tests ─────────────────────────────────────────────────
565
+
566
+ describe('requireProjectRoot', () => {
567
+ it('returns .planning/projects/<project> when v2 install has current_project set and directory exists', () => {
568
+ const fixture = createTempProject({ version: 2, project: 'my-app' });
569
+
570
+ try {
571
+ const result = requireProjectRoot(fixture.cwd);
572
+ assert.equal(result, path.join('.planning', 'projects', 'my-app'));
573
+ } finally {
574
+ fixture.cleanup();
575
+ }
576
+ });
577
+
578
+ it('throws NO_CURRENT_PROJECT_V2 when v2 install has no current_project', () => {
579
+ const fixture = createFixture({
580
+ '.planning/config.json': JSON.stringify({}),
581
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
582
+ });
583
+
584
+ try {
585
+ assert.throws(
586
+ () => requireProjectRoot(fixture.cwd),
587
+ (err) => err.message === 'NO_CURRENT_PROJECT_V2'
588
+ );
589
+ } finally {
590
+ fixture.cleanup();
591
+ }
592
+ });
593
+
594
+ it('throws NO_CURRENT_PROJECT_V2 when v2 install has empty string current_project', () => {
595
+ const fixture = createFixture({
596
+ '.planning/config.json': JSON.stringify({ current_project: '' }),
597
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
598
+ });
599
+
600
+ try {
601
+ assert.throws(
602
+ () => requireProjectRoot(fixture.cwd),
603
+ (err) => err.message === 'NO_CURRENT_PROJECT_V2'
604
+ );
605
+ } finally {
606
+ fixture.cleanup();
607
+ }
608
+ });
609
+
610
+ it('throws NO_CURRENT_PROJECT_V2 when v2 install has whitespace-only current_project', () => {
611
+ const fixture = createFixture({
612
+ '.planning/config.json': JSON.stringify({ current_project: ' ' }),
613
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
614
+ });
615
+
616
+ try {
617
+ assert.throws(
618
+ () => requireProjectRoot(fixture.cwd),
619
+ (err) => err.message === 'NO_CURRENT_PROJECT_V2'
620
+ );
621
+ } finally {
622
+ fixture.cleanup();
623
+ }
624
+ });
625
+
626
+ it('throws PROJECT_NOT_FOUND when v2 install has current_project pointing to nonexistent directory', () => {
627
+ const fixture = createTempProject({ version: 2, project: 'real-project' });
628
+
629
+ // Manually update config to point at a nonexistent project
630
+ fs.writeFileSync(
631
+ path.join(fixture.cwd, '.planning', 'config.json'),
632
+ JSON.stringify({ current_project: 'ghost-project' })
633
+ );
634
+
635
+ try {
636
+ assert.throws(
637
+ () => requireProjectRoot(fixture.cwd),
638
+ (err) => err.message.includes('PROJECT_NOT_FOUND')
639
+ );
640
+ } finally {
641
+ fixture.cleanup();
642
+ }
643
+ });
644
+
645
+ it('throws INVALID_PROJECT_NAME when current_project contains ..', () => {
646
+ const fixture = createTempProject({ version: 2, project: 'real-project' });
647
+
648
+ fs.writeFileSync(
649
+ path.join(fixture.cwd, '.planning', 'config.json'),
650
+ JSON.stringify({ current_project: '../etc' })
651
+ );
652
+
653
+ try {
654
+ assert.throws(
655
+ () => requireProjectRoot(fixture.cwd),
656
+ (err) => err.message.includes('INVALID_PROJECT_NAME')
657
+ );
658
+ } finally {
659
+ fixture.cleanup();
660
+ }
661
+ });
662
+
663
+ it('returns .planning for v1 install (backward compatible)', () => {
664
+ const fixture = createTempProject({ version: 1 });
665
+
666
+ try {
667
+ const result = requireProjectRoot(fixture.cwd);
668
+ assert.equal(result, '.planning');
669
+ } finally {
670
+ fixture.cleanup();
671
+ }
672
+ });
673
+
674
+ it('resolveProjectPath throws NO_CURRENT_PROJECT_V2 on v2 install without current_project (guard propagates)', () => {
675
+ const fixture = createFixture({
676
+ '.planning/config.json': JSON.stringify({}),
677
+ '.planning/PROJECTS.md': '# Projects\n\n| Project | Status |\n',
678
+ });
679
+
680
+ try {
681
+ assert.throws(
682
+ () => resolveProjectPath(fixture.cwd, 'STATE.md'),
683
+ (err) => err.message === 'NO_CURRENT_PROJECT_V2'
684
+ );
685
+ } finally {
686
+ fixture.cleanup();
687
+ }
688
+ });
689
+ });
690
+
691
+ // ─── Root Layout Tests ───────────────────────────────────────────────────────
692
+
693
+ describe('root layout', () => {
694
+ let fixture;
695
+
696
+ afterEach(() => {
697
+ if (fixture) fixture.cleanup();
698
+ fixture = null;
699
+ });
700
+
701
+ it('loadConfig reads from root-layout dgs.config.json', () => {
702
+ fixture = createTempProject({ layout: 'root', withConfig: { model_profile: 'quality' } });
703
+ const config = loadConfig(fixture.cwd);
704
+ assert.equal(config.model_profile, 'quality');
705
+ });
706
+
707
+ it('loadConfig returns defaults when root-layout dgs.config.json has no model_profile', () => {
708
+ fixture = createTempProject({ layout: 'root' });
709
+ const config = loadConfig(fixture.cwd);
710
+ assert.equal(config.model_profile, 'balanced');
711
+ });
712
+
713
+ it('getProjectRoot returns . for v1 root-layout', () => {
714
+ // Create a root-layout v1 fixture (no v2 markers with valid headers)
715
+ fixture = createFixture({
716
+ 'dgs.config.json': JSON.stringify({ planningRoot: '.' }),
717
+ 'PROJECT.md': '# Project\n',
718
+ 'STATE.md': '# State\n',
719
+ 'ROADMAP.md': '# Roadmap\n',
720
+ 'phases/': null,
721
+ });
722
+ const result = getProjectRoot(fixture.cwd);
723
+ assert.equal(result, '.');
724
+ });
725
+
726
+ it('isV2Install detects v2 markers in root-layout', () => {
727
+ fixture = createTempProject({ layout: 'root' });
728
+ const result = isV2Install(fixture.cwd);
729
+ assert.equal(result, true);
730
+ });
731
+
732
+ it('isV2Install returns false for root-layout v1', () => {
733
+ fixture = createFixture({
734
+ 'dgs.config.json': JSON.stringify({ planningRoot: '.' }),
735
+ 'PROJECT.md': '# Project\n',
736
+ 'STATE.md': '# State\n',
737
+ });
738
+ const result = isV2Install(fixture.cwd);
739
+ assert.equal(result, false);
740
+ });
741
+
742
+ it('getProjectFolders works in root-layout v2', () => {
743
+ fixture = createFixture({
744
+ 'dgs.config.json': JSON.stringify({ planningRoot: '.', current_project: 'proj-a' }),
745
+ 'PROJECTS.md': '# Projects\n',
746
+ 'REPOS.md': '# Repos\n',
747
+ 'projects/proj-a/STATE.md': '# State\n',
748
+ 'projects/proj-a/phases/': null,
749
+ });
750
+ const result = getProjectFolders(fixture.cwd);
751
+ assert.ok(result.includes('proj-a'));
752
+ });
753
+
754
+ it('loadConfig reads legacy config.json fallback in root-layout', () => {
755
+ // Root layout with only config.json (no dgs.config.json) + PROJECT.md auto-detect
756
+ fixture = createFixture({
757
+ 'PROJECT.md': '# Project\n',
758
+ 'config.json': JSON.stringify({ model_profile: 'budget' }),
759
+ 'STATE.md': '# State\n',
760
+ });
761
+ const config = loadConfig(fixture.cwd);
762
+ assert.equal(config.model_profile, 'budget');
763
+ });
764
+ });
765
+
766
+ // ─── getProjectDir Tests ─────────────────────────────────────────────────────
767
+
768
+ describe('getProjectDir', () => {
769
+ it('returns absolute path under .planning/projects/<slug> for v2 install', () => {
770
+ const fixture = createTempProject({ version: 2, project: 'my-app' });
771
+
772
+ try {
773
+ const result = getProjectDir(fixture.cwd, 'my-app');
774
+ assert.ok(path.isAbsolute(result), 'Expected absolute path');
775
+ assert.equal(result, path.join(fixture.cwd, '.planning', 'projects', 'my-app'));
776
+ } finally {
777
+ fixture.cleanup();
778
+ }
779
+ });
780
+
781
+ it('returns absolute path for any slug even if directory does not exist', () => {
782
+ const fixture = createFixture({
783
+ '.planning/config.json': JSON.stringify({}),
784
+ });
785
+
786
+ try {
787
+ const result = getProjectDir(fixture.cwd, 'nonexistent');
788
+ assert.ok(path.isAbsolute(result), 'Expected absolute path');
789
+ assert.ok(result.endsWith(path.join('projects', 'nonexistent')));
790
+ } finally {
791
+ fixture.cleanup();
792
+ }
793
+ });
794
+ });
795
+
796
+ // ─── Config Dual-Read Tests ─────────────────────────────────────────────────
797
+
798
+ describe('config dual-read', () => {
799
+ let fixture;
800
+
801
+ afterEach(() => {
802
+ if (fixture) fixture.cleanup();
803
+ fixture = null;
804
+ });
805
+
806
+ it('loadConfig reads dgs.config.json when both exist in .planning/', () => {
807
+ fixture = createFixture({
808
+ '.planning/dgs.config.json': JSON.stringify({ model_profile: 'quality' }),
809
+ '.planning/config.json': JSON.stringify({ model_profile: 'speed' }),
810
+ });
811
+ const config = loadConfig(fixture.cwd);
812
+ assert.equal(config.model_profile, 'quality');
813
+ });
814
+
815
+ it('loadConfig falls back to config.json when no dgs.config.json in .planning/', () => {
816
+ fixture = createFixture({
817
+ '.planning/config.json': JSON.stringify({ model_profile: 'speed' }),
818
+ });
819
+ const config = loadConfig(fixture.cwd);
820
+ assert.equal(config.model_profile, 'speed');
821
+ });
822
+ });