@ktpartners/dgs-platform 2.7.5 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/agents/dgs-executor.md +0 -52
  3. package/deliver-great-systems/bin/dgs-tools.cjs +66 -10
  4. package/deliver-great-systems/bin/lib/commands.cjs +1 -8
  5. package/deliver-great-systems/bin/lib/config.cjs +9 -90
  6. package/deliver-great-systems/bin/lib/context.cjs +2 -2
  7. package/deliver-great-systems/bin/lib/context.test.cjs +100 -100
  8. package/deliver-great-systems/bin/lib/core.cjs +17 -57
  9. package/deliver-great-systems/bin/lib/core.test.cjs +166 -170
  10. package/deliver-great-systems/bin/lib/docs.cjs +3 -3
  11. package/deliver-great-systems/bin/lib/docs.test.cjs +14 -7
  12. package/deliver-great-systems/bin/lib/execution.cjs +2 -2
  13. package/deliver-great-systems/bin/lib/execution.test.cjs +65 -67
  14. package/deliver-great-systems/bin/lib/ideas.cjs +4 -4
  15. package/deliver-great-systems/bin/lib/ideas.test.cjs +45 -44
  16. package/deliver-great-systems/bin/lib/init.cjs +9 -4
  17. package/deliver-great-systems/bin/lib/init.test.cjs +242 -175
  18. package/deliver-great-systems/bin/lib/jobs.cjs +1 -1
  19. package/deliver-great-systems/bin/lib/jobs.test.cjs +203 -202
  20. package/deliver-great-systems/bin/lib/migration.cjs +256 -281
  21. package/deliver-great-systems/bin/lib/migration.test.cjs +385 -440
  22. package/deliver-great-systems/bin/lib/milestone.cjs +1 -1
  23. package/deliver-great-systems/bin/lib/overlap.cjs +4 -4
  24. package/deliver-great-systems/bin/lib/overlap.test.cjs +45 -44
  25. package/deliver-great-systems/bin/lib/path-audit.test.cjs +16 -22
  26. package/deliver-great-systems/bin/lib/paths.cjs +60 -59
  27. package/deliver-great-systems/bin/lib/paths.test.cjs +192 -225
  28. package/deliver-great-systems/bin/lib/phase.cjs +5 -4
  29. package/deliver-great-systems/bin/lib/projects.cjs +8 -8
  30. package/deliver-great-systems/bin/lib/projects.test.cjs +75 -74
  31. package/deliver-great-systems/bin/lib/repos.cjs +94 -230
  32. package/deliver-great-systems/bin/lib/repos.test.cjs +84 -75
  33. package/deliver-great-systems/bin/lib/search.cjs +4 -4
  34. package/deliver-great-systems/bin/lib/specs.cjs +2 -2
  35. package/deliver-great-systems/bin/lib/sync.cjs +1 -1
  36. package/deliver-great-systems/bin/lib/template.cjs +3 -3
  37. package/deliver-great-systems/bin/lib/test-helpers.cjs +59 -162
  38. package/deliver-great-systems/bin/lib/verify.cjs +3 -3
  39. package/deliver-great-systems/references/planning-config.md +7 -8
  40. package/deliver-great-systems/workflows/add-tests.md +1 -1
  41. package/deliver-great-systems/workflows/approve-spec.md +1 -11
  42. package/deliver-great-systems/workflows/complete-milestone.md +2 -2
  43. package/deliver-great-systems/workflows/consolidate-ideas.md +1 -1
  44. package/deliver-great-systems/workflows/create-milestone-job.md +2 -2
  45. package/deliver-great-systems/workflows/discuss-phase.md +2 -2
  46. package/deliver-great-systems/workflows/execute-phase.md +63 -4
  47. package/deliver-great-systems/workflows/execute-plan.md +0 -51
  48. package/deliver-great-systems/workflows/find-related-ideas.md +1 -1
  49. package/deliver-great-systems/workflows/help.md +25 -58
  50. package/deliver-great-systems/workflows/init-product.md +14 -451
  51. package/deliver-great-systems/workflows/map-codebase.md +109 -0
  52. package/deliver-great-systems/workflows/new-project.md +0 -1
  53. package/deliver-great-systems/workflows/quick.md +2 -2
  54. package/deliver-great-systems/workflows/run-job.md +56 -0
  55. package/package.json +1 -1
@@ -4,9 +4,9 @@
4
4
  * Provides the data model and operations for document management:
5
5
  * add, list, remove, move, and INDEX.md maintenance.
6
6
  * Documents are stored in scope-specific docs/ directories:
7
- * - Product-level: .planning/docs/
8
- * - Idea-scoped: .planning/ideas/{state}/{idea-slug}/docs/
9
- * - Spec-scoped: .planning/specs/{spec-slug}/docs/
7
+ * - Product-level: docs/
8
+ * - Idea-scoped: ideas/{state}/{idea-slug}/docs/
9
+ * - Spec-scoped: specs/{spec-slug}/docs/
10
10
  * Text extraction produces .extracted.txt sidecars for PDF, XLSX, CSV, DOCX.
11
11
  * Large extractions also generate .summary.txt sidecars.
12
12
  */
@@ -13,6 +13,9 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
15
 
16
+ const { initGitRepo } = require('./test-helpers.cjs');
17
+ const { resetPaths } = require('./paths.cjs');
18
+
16
19
  const {
17
20
  resolveDocsDir,
18
21
  scanDocsDir,
@@ -29,15 +32,18 @@ describe('docs', () => {
29
32
 
30
33
  beforeEach(() => {
31
34
  tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-resolve-docs-'));
35
+ tmpDir = fs.realpathSync(tmpDir);
36
+ initGitRepo(tmpDir);
32
37
  });
33
38
 
34
39
  afterEach(() => {
40
+ resetPaths();
35
41
  fs.rmSync(tmpDir, { recursive: true, force: true });
36
42
  });
37
43
 
38
- it('product scope returns .planning/docs/product', () => {
44
+ it('product scope returns docs/product at root', () => {
39
45
  const result = resolveDocsDir(tmpDir, 'product');
40
- assert.equal(result, path.join(tmpDir, '.planning', 'docs', 'product'));
46
+ assert.equal(result, path.join(tmpDir, 'docs', 'product'));
41
47
  });
42
48
 
43
49
  it('product scope path ends with /docs/product', () => {
@@ -47,11 +53,12 @@ describe('docs', () => {
47
53
  });
48
54
 
49
55
  it('idea scope returns docs path for idea found in pending', () => {
50
- // Create the fixture directory that resolveDocsDir searches for
51
- fs.mkdirSync(path.join(tmpDir, '.planning', 'ideas', 'pending', 'my-idea'), { recursive: true });
52
- const result = resolveDocsDir(tmpDir, 'idea', 'my-idea');
53
- assert.ok(result.includes(path.join('ideas', 'pending', 'my-idea', 'docs')),
54
- `Expected ideas/pending/my-idea/docs in path, got: ${result}`);
56
+ // Create the idea file that resolveDocsDir searches for
57
+ fs.mkdirSync(path.join(tmpDir, 'ideas', 'pending'), { recursive: true });
58
+ fs.writeFileSync(path.join(tmpDir, 'ideas', 'pending', '001-my-idea.md'), '---\nid: 1\ntitle: "My Idea"\n---\n');
59
+ const result = resolveDocsDir(tmpDir, 'idea', '1');
60
+ assert.ok(result.includes(path.join('ideas', 'pending', '001-my-idea', 'docs')),
61
+ `Expected ideas/pending/001-my-idea/docs in path, got: ${result}`);
55
62
  });
56
63
 
57
64
  it('spec scope returns correct path', () => {
@@ -420,7 +420,7 @@ function createRepoBranches(cwd, repoNames, branchName, config, baseBranch) {
420
420
  reason: 'base_branch_missing',
421
421
  error: `Base branch '${baseBranch}' does not exist in repo '${name}'. ` +
422
422
  `Checked both local and remote (origin/${baseBranch}). ` +
423
- `Configure git.base_branch in .planning/config.json or create the branch first.`,
423
+ `Configure git.base_branch in config.json or create the branch first.`,
424
424
  repo: name,
425
425
  };
426
426
  }
@@ -578,7 +578,7 @@ function updateRepoStatus(cwd, projectSlug, repoResults) {
578
578
  }
579
579
 
580
580
  /**
581
- * Get project slugs from .planning directory.
581
+ * Get project slugs from projects directory.
582
582
  * Looks for subdirectories that contain STATE.md (project folders).
583
583
  *
584
584
  * @param {string} cwd - Product root
@@ -11,15 +11,13 @@ const os = require('os');
11
11
  const { execSync } = require('child_process');
12
12
 
13
13
  const { createTempDir, cleanupDir, initGitRepo } = require('./test-helpers.cjs');
14
+ const { resetPaths } = require('./paths.cjs');
14
15
 
15
16
  /**
16
- * Helper: create a product root with .planning/REPOS.md and git repos.
17
+ * Helper: create a product root with REPOS.md and git repos.
17
18
  */
18
19
  function setupProductRoot(tmpDir, repoNames) {
19
- // Create .planning with REPOS.md
20
- const planningDir = path.join(tmpDir, '.planning');
21
- fs.mkdirSync(planningDir, { recursive: true });
22
-
20
+ // Create REPOS.md at root
23
21
  let reposMd = '# Repos\n\n';
24
22
  reposMd += '| Name | Path | GitHub URL | Description |\n';
25
23
  reposMd += '|------|------|------------|-------------|\n';
@@ -33,7 +31,7 @@ function setupProductRoot(tmpDir, repoNames) {
33
31
  execSync('git commit -m "add readme"', { cwd: path.join(tmpDir, name), stdio: 'pipe' });
34
32
  }
35
33
 
36
- fs.writeFileSync(path.join(planningDir, 'REPOS.md'), reposMd);
34
+ fs.writeFileSync(path.join(tmpDir, 'REPOS.md'), reposMd);
37
35
  return tmpDir;
38
36
  }
39
37
 
@@ -57,8 +55,8 @@ const {
57
55
 
58
56
  describe('resolveRepoRelativePath', () => {
59
57
  let tmpDir;
60
- beforeEach(() => { tmpDir = createTempDir(); });
61
- afterEach(() => { cleanupDir(tmpDir); });
58
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
59
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
62
60
 
63
61
  it('resolves src/index.ts in repo web-app to correct absolute path', () => {
64
62
  setupProductRoot(tmpDir, ['web-app']);
@@ -76,17 +74,17 @@ describe('resolveRepoRelativePath', () => {
76
74
  });
77
75
 
78
76
  it('works with ../repo paths in REPOS.md (sibling layout)', () => {
79
- // Create a sibling-layout product root
77
+ // Create a sibling-layout product root with its own git repo
80
78
  const productRoot = path.join(tmpDir, 'product-root');
81
- const planningDir = path.join(productRoot, '.planning');
82
- fs.mkdirSync(planningDir, { recursive: true });
79
+ initGitRepo(productRoot);
80
+ resetPaths();
83
81
 
84
82
  let reposMd = '# Repos\n\n';
85
83
  reposMd += '| Name | Path | GitHub URL | Description |\n';
86
84
  reposMd += '|------|------|------------|-------------|\n';
87
85
  reposMd += '| api-service | ../api-service | | Test repo |\n';
88
86
 
89
- fs.writeFileSync(path.join(planningDir, 'REPOS.md'), reposMd);
87
+ fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
90
88
  fs.mkdirSync(path.join(tmpDir, 'api-service'), { recursive: true });
91
89
 
92
90
  const result = resolveRepoRelativePath(productRoot, 'api-service', 'src/server.ts');
@@ -115,8 +113,8 @@ describe('resolveRepoRelativePath', () => {
115
113
 
116
114
  describe('preflightCheck', () => {
117
115
  let tmpDir;
118
- beforeEach(() => { tmpDir = createTempDir(); });
119
- afterEach(() => { cleanupDir(tmpDir); });
116
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
117
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
120
118
 
121
119
  it('all repos clean — passed: true', () => {
122
120
  setupProductRoot(tmpDir, ['repo-a', 'repo-b']);
@@ -166,7 +164,7 @@ describe('preflightCheck', () => {
166
164
 
167
165
  it('SUMMARY.md with status: partial — passed: false, partial_execution set', () => {
168
166
  setupProductRoot(tmpDir, ['repo-a']);
169
- const phaseDir = path.join(tmpDir, '.planning', 'phases', '04-test');
167
+ const phaseDir = path.join(tmpDir, 'phases', '04-test');
170
168
  fs.mkdirSync(phaseDir, { recursive: true });
171
169
  fs.writeFileSync(path.join(phaseDir, '04-01-SUMMARY.md'),
172
170
  '---\nphase: 04-test\nplan: 01\nstatus: partial\n---\n# Summary\n');
@@ -177,7 +175,7 @@ describe('preflightCheck', () => {
177
175
 
178
176
  it('SUMMARY.md with status: complete — passed: true', () => {
179
177
  setupProductRoot(tmpDir, ['repo-a']);
180
- const phaseDir = path.join(tmpDir, '.planning', 'phases', '04-test');
178
+ const phaseDir = path.join(tmpDir, 'phases', '04-test');
181
179
  fs.mkdirSync(phaseDir, { recursive: true });
182
180
  fs.writeFileSync(path.join(phaseDir, '04-01-SUMMARY.md'),
183
181
  '---\nphase: 04-test\nplan: 01\nstatus: complete\n---\n# Summary\n');
@@ -191,8 +189,8 @@ describe('preflightCheck', () => {
191
189
 
192
190
  describe('commitPerRepo', () => {
193
191
  let tmpDir;
194
- beforeEach(() => { tmpDir = createTempDir(); });
195
- afterEach(() => { cleanupDir(tmpDir); });
192
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
193
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
196
194
 
197
195
  const options = {
198
196
  type: 'feat',
@@ -329,8 +327,8 @@ describe('buildPlanningCommitBody', () => {
329
327
 
330
328
  describe('detectRepoChanges', () => {
331
329
  let tmpDir;
332
- beforeEach(() => { tmpDir = createTempDir(); });
333
- afterEach(() => { cleanupDir(tmpDir); });
330
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
331
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
334
332
 
335
333
  it('repo with modified file — listed', () => {
336
334
  setupProductRoot(tmpDir, ['repo-a']);
@@ -388,8 +386,8 @@ describe('detectRepoChanges', () => {
388
386
 
389
387
  describe('preflightCheck edge cases', () => {
390
388
  let tmpDir;
391
- beforeEach(() => { tmpDir = createTempDir(); });
392
- afterEach(() => { cleanupDir(tmpDir); });
389
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
390
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
393
391
 
394
392
  it('empty repoNames array — passed: true (vacuously)', () => {
395
393
  setupProductRoot(tmpDir, ['repo-a']);
@@ -409,8 +407,8 @@ describe('preflightCheck edge cases', () => {
409
407
 
410
408
  describe('commitPerRepo edge cases', () => {
411
409
  let tmpDir;
412
- beforeEach(() => { tmpDir = createTempDir(); });
413
- afterEach(() => { cleanupDir(tmpDir); });
410
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
411
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
414
412
 
415
413
  const options = {
416
414
  type: 'feat',
@@ -429,8 +427,8 @@ describe('commitPerRepo edge cases', () => {
429
427
 
430
428
  describe('detectRepoChanges edge cases', () => {
431
429
  let tmpDir;
432
- beforeEach(() => { tmpDir = createTempDir(); });
433
- afterEach(() => { cleanupDir(tmpDir); });
430
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
431
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
434
432
 
435
433
  it('repo with deleted file — listed', () => {
436
434
  setupProductRoot(tmpDir, ['repo-a']);
@@ -444,8 +442,8 @@ describe('detectRepoChanges edge cases', () => {
444
442
 
445
443
  describe('roundtrip integration', () => {
446
444
  let tmpDir;
447
- beforeEach(() => { tmpDir = createTempDir(); });
448
- afterEach(() => { cleanupDir(tmpDir); });
445
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
446
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
449
447
 
450
448
  it('detectRepoChanges -> commitPerRepo -> buildPlanningCommitBody produces coherent result', () => {
451
449
  setupProductRoot(tmpDir, ['repo-a', 'repo-b']);
@@ -481,8 +479,8 @@ describe('roundtrip integration', () => {
481
479
 
482
480
  describe('createRepoBranches', () => {
483
481
  let tmpDir;
484
- beforeEach(() => { tmpDir = createTempDir(); });
485
- afterEach(() => { cleanupDir(tmpDir); });
482
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
483
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
486
484
 
487
485
  it('branching_strategy none — created: false', () => {
488
486
  setupProductRoot(tmpDir, ['repo-a']);
@@ -529,8 +527,8 @@ describe('createRepoBranches', () => {
529
527
 
530
528
  describe('createRepoBranches prefix collision', () => {
531
529
  let tmpDir;
532
- beforeEach(() => { tmpDir = createTempDir(); });
533
- afterEach(() => { cleanupDir(tmpDir); });
530
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
531
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
534
532
 
535
533
  it('detects prefix collision when creating similar branch name', () => {
536
534
  setupProductRoot(tmpDir, ['repo-a']);
@@ -587,8 +585,8 @@ describe('createRepoBranches prefix collision', () => {
587
585
 
588
586
  describe('createRepoBranches project-scoped prefix collision', () => {
589
587
  let tmpDir;
590
- beforeEach(() => { tmpDir = createTempDir(); });
591
- afterEach(() => { cleanupDir(tmpDir); });
588
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
589
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
592
590
 
593
591
  it('no collision when project slugs differ by / delimiter (checkout vs checkout-v2)', () => {
594
592
  setupProductRoot(tmpDir, ['repo-a']);
@@ -646,8 +644,8 @@ describe('createRepoBranches project-scoped prefix collision', () => {
646
644
 
647
645
  describe('createRepoBranches base_branch', () => {
648
646
  let tmpDir;
649
- beforeEach(() => { tmpDir = createTempDir(); });
650
- afterEach(() => { cleanupDir(tmpDir); });
647
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
648
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
651
649
 
652
650
  it('without baseBranch creates branch from HEAD (backwards compat)', () => {
653
651
  setupProductRoot(tmpDir, ['repo-a']);
@@ -737,8 +735,8 @@ describe('createRepoBranches base_branch', () => {
737
735
 
738
736
  describe('reportBranchesToMerge', () => {
739
737
  let tmpDir;
740
- beforeEach(() => { tmpDir = createTempDir(); });
741
- afterEach(() => { cleanupDir(tmpDir); });
738
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
739
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
742
740
 
743
741
  it('repos with the branch — listed', () => {
744
742
  setupProductRoot(tmpDir, ['repo-a']);
@@ -765,16 +763,16 @@ describe('reportBranchesToMerge', () => {
765
763
 
766
764
  describe('updateRepoStatus', () => {
767
765
  let tmpDir;
768
- beforeEach(() => { tmpDir = createTempDir(); });
769
- afterEach(() => { cleanupDir(tmpDir); });
766
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
767
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
770
768
 
771
769
  it('creates Repo Status section in STATE.md', () => {
772
770
  setupProductRoot(tmpDir, ['repo-a']);
773
771
  // Create initial STATE.md
774
- fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# Project State\n\n## Current Position\nPhase: 4\n');
772
+ fs.writeFileSync(path.join(tmpDir, 'STATE.md'), '# Project State\n\n## Current Position\nPhase: 4\n');
775
773
  const repoResults = [{ repo: 'repo-a', status: 'complete', sha: 'abc1234' }];
776
774
  updateRepoStatus(tmpDir, null, repoResults);
777
- const content = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
775
+ const content = fs.readFileSync(path.join(tmpDir, 'STATE.md'), 'utf-8');
778
776
  assert.ok(content.includes('## Repo Status'));
779
777
  assert.ok(content.includes('repo-a'));
780
778
  assert.ok(content.includes('abc1234'));
@@ -782,11 +780,11 @@ describe('updateRepoStatus', () => {
782
780
 
783
781
  it('updates existing Repo Status section (replace, not duplicate)', () => {
784
782
  setupProductRoot(tmpDir, ['repo-a']);
785
- fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'),
783
+ fs.writeFileSync(path.join(tmpDir, 'STATE.md'),
786
784
  '# State\n\n## Repo Status\n\n| Repo | Branch | Last Commit | Touched By |\n|------|--------|-------------|------------|\n| old | main | old123 | - |\n\n## Other\n');
787
785
  const repoResults = [{ repo: 'repo-a', status: 'complete', sha: 'new456' }];
788
786
  updateRepoStatus(tmpDir, null, repoResults);
789
- const content = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
787
+ const content = fs.readFileSync(path.join(tmpDir, 'STATE.md'), 'utf-8');
790
788
  assert.ok(content.includes('new456'));
791
789
  assert.ok(!content.includes('old123'));
792
790
  // Should only have one Repo Status section
@@ -796,20 +794,20 @@ describe('updateRepoStatus', () => {
796
794
 
797
795
  it('handles repo with no branch (shows current branch)', () => {
798
796
  setupProductRoot(tmpDir, ['repo-a']);
799
- fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n');
797
+ fs.writeFileSync(path.join(tmpDir, 'STATE.md'), '# State\n');
800
798
  const repoResults = [{ repo: 'repo-a', status: 'complete', sha: 'abc1234' }];
801
799
  updateRepoStatus(tmpDir, null, repoResults);
802
- const content = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
800
+ const content = fs.readFileSync(path.join(tmpDir, 'STATE.md'), 'utf-8');
803
801
  // Should show 'main' or 'master' as current branch
804
802
  assert.ok(content.includes('main') || content.includes('master'));
805
803
  });
806
804
 
807
805
  it('works when STATE.md has no existing Repo Status section', () => {
808
806
  setupProductRoot(tmpDir, ['repo-a']);
809
- fs.writeFileSync(path.join(tmpDir, '.planning', 'STATE.md'), '# State\n\n## Current Position\nPhase: 4\n');
807
+ fs.writeFileSync(path.join(tmpDir, 'STATE.md'), '# State\n\n## Current Position\nPhase: 4\n');
810
808
  const repoResults = [{ repo: 'repo-a', status: 'complete', sha: 'abc1234' }];
811
809
  updateRepoStatus(tmpDir, null, repoResults);
812
- const content = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
810
+ const content = fs.readFileSync(path.join(tmpDir, 'STATE.md'), 'utf-8');
813
811
  assert.ok(content.includes('## Repo Status'));
814
812
  });
815
813
  });
@@ -818,8 +816,8 @@ describe('updateRepoStatus', () => {
818
816
 
819
817
  describe('CLI integration: commit preflight', () => {
820
818
  let tmpDir;
821
- beforeEach(() => { tmpDir = createTempDir(); });
822
- afterEach(() => { cleanupDir(tmpDir); });
819
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
820
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
823
821
 
824
822
  it('commit preflight with clean repos — returns passed: true via CLI', () => {
825
823
  setupProductRoot(tmpDir, ['repo-a', 'repo-b']);
@@ -862,8 +860,8 @@ describe('CLI integration: commit preflight', () => {
862
860
 
863
861
  describe('CLI integration: commit --multi-repo', () => {
864
862
  let tmpDir;
865
- beforeEach(() => { tmpDir = createTempDir(); });
866
- afterEach(() => { cleanupDir(tmpDir); });
863
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
864
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
867
865
 
868
866
  it('commit --multi-repo with changes in 2 repos — both committed', () => {
869
867
  setupProductRoot(tmpDir, ['repo-a', 'repo-b']);
@@ -938,8 +936,8 @@ describe('CLI integration: commit --multi-repo', () => {
938
936
 
939
937
  describe('retryCommitPerRepo', () => {
940
938
  let tmpDir;
941
- beforeEach(() => { tmpDir = createTempDir(); });
942
- afterEach(() => { cleanupDir(tmpDir); });
939
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
940
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
943
941
 
944
942
  const options = {
945
943
  type: 'feat',
@@ -1141,8 +1139,8 @@ describe('retryCommitPerRepo', () => {
1141
1139
 
1142
1140
  describe('detectManualResolution', () => {
1143
1141
  let tmpDir;
1144
- beforeEach(() => { tmpDir = createTempDir(); });
1145
- afterEach(() => { cleanupDir(tmpDir); });
1142
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
1143
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
1146
1144
 
1147
1145
  it('clean working tree with all expected files present -- resolved: true, reason: manually_resolved', () => {
1148
1146
  setupProductRoot(tmpDir, ['repo-a']);
@@ -1200,8 +1198,8 @@ describe('detectManualResolution', () => {
1200
1198
 
1201
1199
  describe('cmdCommitMultiRepo advisory preflight', () => {
1202
1200
  let tmpDir;
1203
- beforeEach(() => { tmpDir = createTempDir(); });
1204
- afterEach(() => { cleanupDir(tmpDir); });
1201
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
1202
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
1205
1203
 
1206
1204
  it('dirty repo produces warning but commit proceeds for other repos', () => {
1207
1205
  setupProductRoot(tmpDir, ['repo-a', 'repo-b']);
@@ -1249,7 +1247,7 @@ describe('cmdCommitMultiRepo advisory preflight', () => {
1249
1247
  setupProductRoot(tmpDir, ['repo-a']);
1250
1248
 
1251
1249
  // Create a phase dir with a partial SUMMARY.md
1252
- const phaseDir = path.join(tmpDir, '.planning', 'phases', '04-test');
1250
+ const phaseDir = path.join(tmpDir, 'phases', '04-test');
1253
1251
  fs.mkdirSync(phaseDir, { recursive: true });
1254
1252
  fs.writeFileSync(path.join(phaseDir, '04-01-SUMMARY.md'),
1255
1253
  '---\nphase: 04-test\nplan: 01\nstatus: partial\n---\n# Summary\n');
@@ -1314,10 +1312,10 @@ describe('cmdCommitMultiRepo advisory preflight', () => {
1314
1312
  * Returns { rootDir, productRoot } where productRoot is the planning repo cwd.
1315
1313
  */
1316
1314
  function setupFlatLayout(tmpDir, repoNames) {
1317
- // Create planning repo (product root)
1315
+ // Create planning repo (product root) with its own git repo
1318
1316
  const productRoot = path.join(tmpDir, 'planning-repo');
1319
- const planningDir = path.join(productRoot, '.planning');
1320
- fs.mkdirSync(planningDir, { recursive: true });
1317
+ initGitRepo(productRoot);
1318
+ resetPaths();
1321
1319
 
1322
1320
  let reposMd = '# Repos\n\n';
1323
1321
  reposMd += '| Name | Path | GitHub URL | Description |\n';
@@ -1333,7 +1331,7 @@ function setupFlatLayout(tmpDir, repoNames) {
1333
1331
  execSync('git commit -m "add readme"', { cwd: path.join(tmpDir, name), stdio: 'pipe' });
1334
1332
  }
1335
1333
 
1336
- fs.writeFileSync(path.join(planningDir, 'REPOS.md'), reposMd);
1334
+ fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
1337
1335
  return { rootDir: tmpDir, productRoot };
1338
1336
  }
1339
1337
 
@@ -1341,8 +1339,8 @@ function setupFlatLayout(tmpDir, repoNames) {
1341
1339
 
1342
1340
  describe('resolveRepoPath with ../repo paths', () => {
1343
1341
  let tmpDir;
1344
- beforeEach(() => { tmpDir = createTempDir(); });
1345
- afterEach(() => { cleanupDir(tmpDir); });
1342
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
1343
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
1346
1344
 
1347
1345
  it('resolves ../api-service to absolute path of sibling directory', () => {
1348
1346
  const { productRoot } = setupFlatLayout(tmpDir, ['api-service']);
@@ -1380,8 +1378,8 @@ describe('resolveRepoPath with ../repo paths', () => {
1380
1378
 
1381
1379
  describe('multi-repo execution with ../repo paths', () => {
1382
1380
  let tmpDir;
1383
- beforeEach(() => { tmpDir = createTempDir(); });
1384
- afterEach(() => { cleanupDir(tmpDir); });
1381
+ beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
1382
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
1385
1383
 
1386
1384
  it('preflightCheck passes for clean sibling repos', () => {
1387
1385
  const { productRoot } = setupFlatLayout(tmpDir, ['repo-a', 'repo-b']);
@@ -7,7 +7,7 @@
7
7
  * discuss-save, research-save, appendDiscussionEntry, and appendResearchEntry.
8
8
  *
9
9
  * Ideas are stored as markdown files with YAML frontmatter in
10
- * .planning/ideas/{pending,done,rejected}/ directories.
10
+ * ideas/{pending,done,rejected}/ directories.
11
11
  * A manifest.json tracks the next available sequential ID (never reused).
12
12
  *
13
13
  * Section order: Body > ## Notes > ## Discussion Log > ## Research Log
@@ -23,7 +23,7 @@ const { extractNameFromAuthor } = require('./identity.cjs');
23
23
  // ─── Manifest Management ─────────────────────────────────────────────────────
24
24
 
25
25
  /**
26
- * Load the ideas manifest from .planning/ideas/manifest.json.
26
+ * Load the ideas manifest from ideas/manifest.json.
27
27
  * Returns { next_id: N }. If missing, returns { next_id: 1 }.
28
28
  *
29
29
  * @param {string} cwd - Working directory
@@ -42,7 +42,7 @@ function loadManifest(cwd) {
42
42
  }
43
43
 
44
44
  /**
45
- * Save the ideas manifest to .planning/ideas/manifest.json.
45
+ * Save the ideas manifest to ideas/manifest.json.
46
46
  *
47
47
  * @param {string} cwd - Working directory
48
48
  * @param {{ next_id: number }} manifest
@@ -75,7 +75,7 @@ function allocateId(cwd) {
75
75
  const IDEA_STATES = ['pending', 'done', 'rejected', 'consolidated'];
76
76
 
77
77
  /**
78
- * Ensure .planning/ideas/{pending,done,rejected}/ directories exist.
78
+ * Ensure ideas/{pending,done,rejected}/ directories exist.
79
79
  *
80
80
  * @param {string} cwd - Working directory
81
81
  */