@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.
- package/CHANGELOG.md +16 -0
- package/agents/dgs-executor.md +0 -52
- package/deliver-great-systems/bin/dgs-tools.cjs +66 -10
- package/deliver-great-systems/bin/lib/commands.cjs +1 -8
- package/deliver-great-systems/bin/lib/config.cjs +9 -90
- package/deliver-great-systems/bin/lib/context.cjs +2 -2
- package/deliver-great-systems/bin/lib/context.test.cjs +100 -100
- package/deliver-great-systems/bin/lib/core.cjs +17 -57
- package/deliver-great-systems/bin/lib/core.test.cjs +166 -170
- package/deliver-great-systems/bin/lib/docs.cjs +3 -3
- package/deliver-great-systems/bin/lib/docs.test.cjs +14 -7
- package/deliver-great-systems/bin/lib/execution.cjs +2 -2
- package/deliver-great-systems/bin/lib/execution.test.cjs +65 -67
- package/deliver-great-systems/bin/lib/ideas.cjs +4 -4
- package/deliver-great-systems/bin/lib/ideas.test.cjs +45 -44
- package/deliver-great-systems/bin/lib/init.cjs +9 -4
- package/deliver-great-systems/bin/lib/init.test.cjs +242 -175
- package/deliver-great-systems/bin/lib/jobs.cjs +1 -1
- package/deliver-great-systems/bin/lib/jobs.test.cjs +203 -202
- package/deliver-great-systems/bin/lib/migration.cjs +256 -281
- package/deliver-great-systems/bin/lib/migration.test.cjs +385 -440
- package/deliver-great-systems/bin/lib/milestone.cjs +1 -1
- package/deliver-great-systems/bin/lib/overlap.cjs +4 -4
- package/deliver-great-systems/bin/lib/overlap.test.cjs +45 -44
- package/deliver-great-systems/bin/lib/path-audit.test.cjs +16 -22
- package/deliver-great-systems/bin/lib/paths.cjs +60 -59
- package/deliver-great-systems/bin/lib/paths.test.cjs +192 -225
- package/deliver-great-systems/bin/lib/phase.cjs +5 -4
- package/deliver-great-systems/bin/lib/projects.cjs +8 -8
- package/deliver-great-systems/bin/lib/projects.test.cjs +75 -74
- package/deliver-great-systems/bin/lib/repos.cjs +94 -230
- package/deliver-great-systems/bin/lib/repos.test.cjs +84 -75
- package/deliver-great-systems/bin/lib/search.cjs +4 -4
- package/deliver-great-systems/bin/lib/specs.cjs +2 -2
- package/deliver-great-systems/bin/lib/sync.cjs +1 -1
- package/deliver-great-systems/bin/lib/template.cjs +3 -3
- package/deliver-great-systems/bin/lib/test-helpers.cjs +59 -162
- package/deliver-great-systems/bin/lib/verify.cjs +3 -3
- package/deliver-great-systems/references/planning-config.md +7 -8
- package/deliver-great-systems/workflows/add-tests.md +1 -1
- package/deliver-great-systems/workflows/approve-spec.md +1 -11
- package/deliver-great-systems/workflows/complete-milestone.md +2 -2
- package/deliver-great-systems/workflows/consolidate-ideas.md +1 -1
- package/deliver-great-systems/workflows/create-milestone-job.md +2 -2
- package/deliver-great-systems/workflows/discuss-phase.md +2 -2
- package/deliver-great-systems/workflows/execute-phase.md +63 -4
- package/deliver-great-systems/workflows/execute-plan.md +0 -51
- package/deliver-great-systems/workflows/find-related-ideas.md +1 -1
- package/deliver-great-systems/workflows/help.md +25 -58
- package/deliver-great-systems/workflows/init-product.md +14 -451
- package/deliver-great-systems/workflows/map-codebase.md +109 -0
- package/deliver-great-systems/workflows/new-project.md +0 -1
- package/deliver-great-systems/workflows/quick.md +2 -2
- package/deliver-great-systems/workflows/run-job.md +56 -0
- 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:
|
|
8
|
-
* - Idea-scoped:
|
|
9
|
-
* - Spec-scoped:
|
|
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
|
|
44
|
+
it('product scope returns docs/product at root', () => {
|
|
39
45
|
const result = resolveDocsDir(tmpDir, 'product');
|
|
40
|
-
assert.equal(result, path.join(tmpDir, '
|
|
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
|
|
51
|
-
fs.mkdirSync(path.join(tmpDir, '
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
|
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
|
|
17
|
+
* Helper: create a product root with REPOS.md and git repos.
|
|
17
18
|
*/
|
|
18
19
|
function setupProductRoot(tmpDir, repoNames) {
|
|
19
|
-
// Create .
|
|
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(
|
|
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
|
-
|
|
82
|
-
|
|
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(
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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
|
-
|
|
1320
|
-
|
|
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(
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
78
|
+
* Ensure ideas/{pending,done,rejected}/ directories exist.
|
|
79
79
|
*
|
|
80
80
|
* @param {string} cwd - Working directory
|
|
81
81
|
*/
|