@ktpartners/dgs-platform 2.7.4 → 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 +25 -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 +6 -7
- package/deliver-great-systems/workflows/run-job.md +56 -0
- package/deliver-great-systems/workflows/settings.md +30 -0
- package/package.json +5 -1
|
@@ -8,13 +8,12 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const os = require('os');
|
|
10
10
|
|
|
11
|
-
const { createTempDir, cleanupDir } = require('./test-helpers.cjs');
|
|
11
|
+
const { createTempDir, cleanupDir , initGitRepo } = require('./test-helpers.cjs');
|
|
12
|
+
const { resetPaths } = require('./paths.cjs');
|
|
12
13
|
|
|
13
|
-
// Helper to create
|
|
14
|
+
// Helper to create REPOS.md with given content at root
|
|
14
15
|
function writeReposFile(cwd, content) {
|
|
15
|
-
|
|
16
|
-
fs.mkdirSync(planningDir, { recursive: true });
|
|
17
|
-
fs.writeFileSync(path.join(planningDir, 'REPOS.md'), content);
|
|
16
|
+
fs.writeFileSync(path.join(cwd, 'REPOS.md'), content);
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
const VALID_REPOS_MD = `# Repos
|
|
@@ -45,8 +44,8 @@ const {
|
|
|
45
44
|
|
|
46
45
|
describe('parseReposMd', () => {
|
|
47
46
|
let tmpDir;
|
|
48
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
49
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
47
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
48
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
50
49
|
|
|
51
50
|
it('returns structured array from valid REPOS.md', () => {
|
|
52
51
|
writeReposFile(tmpDir, VALID_REPOS_MD);
|
|
@@ -60,7 +59,7 @@ describe('parseReposMd', () => {
|
|
|
60
59
|
});
|
|
61
60
|
|
|
62
61
|
it('returns null when file does not exist', () => {
|
|
63
|
-
|
|
62
|
+
|
|
64
63
|
const result = parseReposMd(tmpDir);
|
|
65
64
|
assert.strictEqual(result, null);
|
|
66
65
|
});
|
|
@@ -147,18 +146,18 @@ Registered repositories.
|
|
|
147
146
|
|
|
148
147
|
describe('writeReposMd', () => {
|
|
149
148
|
let tmpDir;
|
|
150
|
-
beforeEach(() => { tmpDir = createTempDir(); fs.
|
|
151
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
149
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
150
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
152
151
|
|
|
153
152
|
it('creates file with # Repos header', () => {
|
|
154
153
|
writeReposMd(tmpDir, [{ name: 'app', path: './app', url: '', description: 'App' }]);
|
|
155
|
-
const content = fs.readFileSync(path.join(tmpDir, '
|
|
154
|
+
const content = fs.readFileSync(path.join(tmpDir, 'REPOS.md'), 'utf-8');
|
|
156
155
|
assert.ok(content.startsWith('# Repos'));
|
|
157
156
|
});
|
|
158
157
|
|
|
159
158
|
it('includes explanatory text', () => {
|
|
160
159
|
writeReposMd(tmpDir, []);
|
|
161
|
-
const content = fs.readFileSync(path.join(tmpDir, '
|
|
160
|
+
const content = fs.readFileSync(path.join(tmpDir, 'REPOS.md'), 'utf-8');
|
|
162
161
|
assert.ok(content.includes('Managed by DGS'));
|
|
163
162
|
});
|
|
164
163
|
|
|
@@ -166,7 +165,7 @@ describe('writeReposMd', () => {
|
|
|
166
165
|
writeReposMd(tmpDir, [
|
|
167
166
|
{ name: 'web', path: './web', url: 'https://github.com/org/web', description: 'Frontend' },
|
|
168
167
|
]);
|
|
169
|
-
const content = fs.readFileSync(path.join(tmpDir, '
|
|
168
|
+
const content = fs.readFileSync(path.join(tmpDir, 'REPOS.md'), 'utf-8');
|
|
170
169
|
assert.ok(content.includes('| Name | Path | GitHub URL | Description |'));
|
|
171
170
|
assert.ok(content.includes('|------|------|------------|-------------|'));
|
|
172
171
|
assert.ok(content.includes('| web | ./web | https://github.com/org/web | Frontend |'));
|
|
@@ -174,16 +173,16 @@ describe('writeReposMd', () => {
|
|
|
174
173
|
|
|
175
174
|
it('handles empty repos array', () => {
|
|
176
175
|
writeReposMd(tmpDir, []);
|
|
177
|
-
const content = fs.readFileSync(path.join(tmpDir, '
|
|
176
|
+
const content = fs.readFileSync(path.join(tmpDir, 'REPOS.md'), 'utf-8');
|
|
178
177
|
assert.ok(content.includes('| Name | Path | GitHub URL | Description |'));
|
|
179
178
|
// Should NOT have any data rows
|
|
180
179
|
const lines = content.split('\n').filter(l => l.startsWith('|'));
|
|
181
180
|
assert.strictEqual(lines.length, 2); // header + separator only
|
|
182
181
|
});
|
|
183
182
|
|
|
184
|
-
it('writes
|
|
183
|
+
it('writes REPOS.md at root', () => {
|
|
185
184
|
writeReposMd(tmpDir, []);
|
|
186
|
-
assert.ok(fs.existsSync(path.join(tmpDir, '
|
|
185
|
+
assert.ok(fs.existsSync(path.join(tmpDir, 'REPOS.md')));
|
|
187
186
|
});
|
|
188
187
|
});
|
|
189
188
|
|
|
@@ -227,8 +226,8 @@ describe('resolveFileToRepo', () => {
|
|
|
227
226
|
assert.strictEqual(result, null);
|
|
228
227
|
});
|
|
229
228
|
|
|
230
|
-
it('returns null for
|
|
231
|
-
const result = resolveFileToRepo('
|
|
229
|
+
it('returns null for STATE.md', () => {
|
|
230
|
+
const result = resolveFileToRepo('STATE.md', repos);
|
|
232
231
|
assert.strictEqual(result, null);
|
|
233
232
|
});
|
|
234
233
|
|
|
@@ -281,8 +280,8 @@ describe('resolveFileToRepo', () => {
|
|
|
281
280
|
|
|
282
281
|
describe('discoverRepos', () => {
|
|
283
282
|
let tmpDir;
|
|
284
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
285
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
283
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
284
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
286
285
|
|
|
287
286
|
it('finds directories containing .git/', () => {
|
|
288
287
|
fs.mkdirSync(path.join(tmpDir, 'my-repo', '.git'), { recursive: true });
|
|
@@ -299,8 +298,8 @@ describe('discoverRepos', () => {
|
|
|
299
298
|
assert.strictEqual(result[0].name, 'real-repo');
|
|
300
299
|
});
|
|
301
300
|
|
|
302
|
-
it('excludes
|
|
303
|
-
fs.mkdirSync(path.join(tmpDir, '.
|
|
301
|
+
it('excludes system directories', () => {
|
|
302
|
+
fs.mkdirSync(path.join(tmpDir, '.git'), { recursive: true });
|
|
304
303
|
const result = discoverRepos(tmpDir);
|
|
305
304
|
assert.strictEqual(result.length, 0);
|
|
306
305
|
});
|
|
@@ -361,8 +360,8 @@ describe('discoverRepos', () => {
|
|
|
361
360
|
|
|
362
361
|
describe('discoverSiblingRepos', () => {
|
|
363
362
|
let tmpDir;
|
|
364
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
365
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
363
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
364
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
366
365
|
|
|
367
366
|
it('finds sibling directories containing .git/', () => {
|
|
368
367
|
// tmpDir acts as parent; product-root and sibling-repo are siblings
|
|
@@ -427,8 +426,8 @@ describe('discoverSiblingRepos', () => {
|
|
|
427
426
|
|
|
428
427
|
describe('syncGitignore', () => {
|
|
429
428
|
let tmpDir;
|
|
430
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
431
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
429
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
430
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
432
431
|
|
|
433
432
|
it('creates .gitignore with DGS section when file does not exist', () => {
|
|
434
433
|
syncGitignore(tmpDir, ['./web-app', './server']);
|
|
@@ -515,8 +514,8 @@ describe('syncGitignore', () => {
|
|
|
515
514
|
|
|
516
515
|
describe('validateRepoPaths', () => {
|
|
517
516
|
let tmpDir;
|
|
518
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
519
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
517
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
518
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
520
519
|
|
|
521
520
|
it('returns valid and missing arrays', () => {
|
|
522
521
|
fs.mkdirSync(path.join(tmpDir, 'existing'));
|
|
@@ -589,25 +588,25 @@ describe('hasPathConflict', () => {
|
|
|
589
588
|
|
|
590
589
|
describe('writeProjectsMd', () => {
|
|
591
590
|
let tmpDir;
|
|
592
|
-
beforeEach(() => { tmpDir = createTempDir(); fs.
|
|
593
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
591
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
592
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
594
593
|
|
|
595
594
|
it('creates file with # Projects header', () => {
|
|
596
595
|
writeProjectsMd(tmpDir, []);
|
|
597
|
-
const content = fs.readFileSync(path.join(tmpDir, '
|
|
596
|
+
const content = fs.readFileSync(path.join(tmpDir, 'PROJECTS.md'), 'utf-8');
|
|
598
597
|
assert.ok(content.startsWith('# Projects'));
|
|
599
598
|
});
|
|
600
599
|
|
|
601
600
|
it('includes Active and Completed sections', () => {
|
|
602
601
|
writeProjectsMd(tmpDir, []);
|
|
603
|
-
const content = fs.readFileSync(path.join(tmpDir, '
|
|
602
|
+
const content = fs.readFileSync(path.join(tmpDir, 'PROJECTS.md'), 'utf-8');
|
|
604
603
|
assert.ok(content.includes('## Active'));
|
|
605
604
|
assert.ok(content.includes('## Completed'));
|
|
606
605
|
});
|
|
607
606
|
|
|
608
|
-
it('writes
|
|
607
|
+
it('writes PROJECTS.md at root', () => {
|
|
609
608
|
writeProjectsMd(tmpDir, []);
|
|
610
|
-
assert.ok(fs.existsSync(path.join(tmpDir, '
|
|
609
|
+
assert.ok(fs.existsSync(path.join(tmpDir, 'PROJECTS.md')));
|
|
611
610
|
});
|
|
612
611
|
});
|
|
613
612
|
|
|
@@ -615,8 +614,8 @@ describe('writeProjectsMd', () => {
|
|
|
615
614
|
|
|
616
615
|
describe('writeReposMd -> parseReposMd roundtrip', () => {
|
|
617
616
|
let tmpDir;
|
|
618
|
-
beforeEach(() => { tmpDir = createTempDir(); fs.
|
|
619
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
617
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
618
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
620
619
|
|
|
621
620
|
it('write then parse produces identical data', () => {
|
|
622
621
|
const repos = [
|
|
@@ -1379,10 +1378,12 @@ describe('cmdReposAdd ../name format enforcement', () => {
|
|
|
1379
1378
|
const { execSync } = require('child_process');
|
|
1380
1379
|
|
|
1381
1380
|
beforeEach(() => {
|
|
1382
|
-
|
|
1383
|
-
|
|
1381
|
+
resetPaths();
|
|
1382
|
+
tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir);
|
|
1383
|
+
// Create a flat layout: product-root with its own git repo and sibling repos
|
|
1384
1384
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1385
|
-
fs.mkdirSync(
|
|
1385
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1386
|
+
initGitRepo(productRoot);
|
|
1386
1387
|
// Write initial REPOS.md
|
|
1387
1388
|
const reposMd = `# Repos
|
|
1388
1389
|
|
|
@@ -1391,7 +1392,8 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1391
1392
|
| Name | Path | GitHub URL | Description |
|
|
1392
1393
|
|------|------|------------|-------------|
|
|
1393
1394
|
`;
|
|
1394
|
-
fs.writeFileSync(path.join(productRoot, '
|
|
1395
|
+
fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
|
|
1396
|
+
execSync('git add REPOS.md && git commit -m "add repos"', { cwd: productRoot, stdio: 'pipe' });
|
|
1395
1397
|
// Create sibling repo with .git
|
|
1396
1398
|
const siblingRepo = path.join(tmpDir, 'my-repo');
|
|
1397
1399
|
fs.mkdirSync(path.join(siblingRepo, '.git'), { recursive: true });
|
|
@@ -1399,7 +1401,7 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1399
1401
|
const noGitRepo = path.join(tmpDir, 'no-git-repo');
|
|
1400
1402
|
fs.mkdirSync(noGitRepo, { recursive: true });
|
|
1401
1403
|
});
|
|
1402
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
1404
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
1403
1405
|
|
|
1404
1406
|
// Helper to test cmdReposAdd via subprocess (it calls error() which does process.exit)
|
|
1405
1407
|
function runCmdReposAdd(cwd, repoPath, opts) {
|
|
@@ -1491,9 +1493,11 @@ describe('cmdReposAdd trailing slash normalization', () => {
|
|
|
1491
1493
|
const { execSync } = require('child_process');
|
|
1492
1494
|
|
|
1493
1495
|
beforeEach(() => {
|
|
1494
|
-
|
|
1496
|
+
resetPaths();
|
|
1497
|
+
tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir);
|
|
1495
1498
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1496
|
-
fs.mkdirSync(
|
|
1499
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1500
|
+
initGitRepo(productRoot);
|
|
1497
1501
|
const reposMd = `# Repos
|
|
1498
1502
|
|
|
1499
1503
|
Registered repositories for this product. Managed by DGS — manual edits may be overwritten.
|
|
@@ -1501,12 +1505,13 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1501
1505
|
| Name | Path | GitHub URL | Description |
|
|
1502
1506
|
|------|------|------------|-------------|
|
|
1503
1507
|
`;
|
|
1504
|
-
fs.writeFileSync(path.join(productRoot, '
|
|
1508
|
+
fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
|
|
1509
|
+
execSync('git add REPOS.md && git commit -m "add repos"', { cwd: productRoot, stdio: 'pipe' });
|
|
1505
1510
|
// Create sibling repo with .git
|
|
1506
1511
|
const siblingRepo = path.join(tmpDir, 'sibling-repo');
|
|
1507
1512
|
fs.mkdirSync(path.join(siblingRepo, '.git'), { recursive: true });
|
|
1508
1513
|
});
|
|
1509
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
1514
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
1510
1515
|
|
|
1511
1516
|
function runCmdReposAdd(cwd, repoPath, opts) {
|
|
1512
1517
|
const reposPath = require.resolve('./repos.cjs');
|
|
@@ -1541,7 +1546,7 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1541
1546
|
}
|
|
1542
1547
|
|
|
1543
1548
|
function getStoredPath(productRoot) {
|
|
1544
|
-
const content = fs.readFileSync(path.join(productRoot, '
|
|
1549
|
+
const content = fs.readFileSync(path.join(productRoot, 'REPOS.md'), 'utf-8');
|
|
1545
1550
|
const lines = content.split('\n').filter(l => l.startsWith('|') && !l.includes('---') && !l.includes('Name'));
|
|
1546
1551
|
if (lines.length === 0) return null;
|
|
1547
1552
|
const cols = lines[0].split('|').map(c => c.trim()).filter(Boolean);
|
|
@@ -1577,12 +1582,13 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1577
1582
|
|
|
1578
1583
|
describe('validateReposMdEager', () => {
|
|
1579
1584
|
let tmpDir;
|
|
1580
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
1581
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
1585
|
+
beforeEach(() => { resetPaths(); tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); });
|
|
1586
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
1582
1587
|
|
|
1583
1588
|
function setupFlatLayout(tmpDir, entries) {
|
|
1584
1589
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1585
|
-
fs.mkdirSync(
|
|
1590
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1591
|
+
initGitRepo(productRoot);
|
|
1586
1592
|
|
|
1587
1593
|
let reposMd = `# Repos
|
|
1588
1594
|
|
|
@@ -1600,7 +1606,7 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1600
1606
|
fs.mkdirSync(path.join(tmpDir, entry.name, '.git'), { recursive: true });
|
|
1601
1607
|
}
|
|
1602
1608
|
}
|
|
1603
|
-
fs.writeFileSync(path.join(productRoot, '
|
|
1609
|
+
fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
|
|
1604
1610
|
return productRoot;
|
|
1605
1611
|
}
|
|
1606
1612
|
|
|
@@ -1627,7 +1633,9 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1627
1633
|
const repoDir = path.join(tmpDir, 'shared');
|
|
1628
1634
|
fs.mkdirSync(path.join(repoDir, '.git'), { recursive: true });
|
|
1629
1635
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1630
|
-
fs.mkdirSync(
|
|
1636
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1637
|
+
initGitRepo(productRoot);
|
|
1638
|
+
resetPaths();
|
|
1631
1639
|
|
|
1632
1640
|
const reposMd = `# Repos
|
|
1633
1641
|
|
|
@@ -1638,7 +1646,7 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1638
1646
|
| shared-a | ../shared | | First ref |
|
|
1639
1647
|
| shared-b | ../shared | | Duplicate ref |
|
|
1640
1648
|
`;
|
|
1641
|
-
fs.writeFileSync(path.join(productRoot, '
|
|
1649
|
+
fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
|
|
1642
1650
|
const result = validateReposMdEager(productRoot);
|
|
1643
1651
|
assert.ok(result.errors.length > 0);
|
|
1644
1652
|
assert.ok(result.errors[0].includes('Duplicate path'));
|
|
@@ -1656,7 +1664,9 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1656
1664
|
|
|
1657
1665
|
it('rejects ./repo path format', () => {
|
|
1658
1666
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1659
|
-
fs.mkdirSync(
|
|
1667
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1668
|
+
initGitRepo(productRoot);
|
|
1669
|
+
resetPaths();
|
|
1660
1670
|
fs.mkdirSync(path.join(productRoot, 'local-repo', '.git'), { recursive: true });
|
|
1661
1671
|
|
|
1662
1672
|
const reposMd = `# Repos
|
|
@@ -1665,7 +1675,7 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1665
1675
|
|------|------|------------|-------------|
|
|
1666
1676
|
| local-repo | ./local-repo | | Local repo |
|
|
1667
1677
|
`;
|
|
1668
|
-
fs.writeFileSync(path.join(productRoot, '
|
|
1678
|
+
fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
|
|
1669
1679
|
const result = validateReposMdEager(productRoot);
|
|
1670
1680
|
assert.ok(result.errors.length > 0);
|
|
1671
1681
|
assert.ok(result.errors[0].includes('Repo paths must use ../name format'));
|
|
@@ -1676,8 +1686,8 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1676
1686
|
|
|
1677
1687
|
describe('validateRepoPaths with ../repo paths', () => {
|
|
1678
1688
|
let tmpDir;
|
|
1679
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
1680
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
1689
|
+
beforeEach(() => { tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); initGitRepo(tmpDir); });
|
|
1690
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
1681
1691
|
|
|
1682
1692
|
it('validates ../repo paths that exist on disk', () => {
|
|
1683
1693
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
@@ -1707,15 +1717,15 @@ describe('syncGitignore skip for flat layout', () => {
|
|
|
1707
1717
|
let tmpDir;
|
|
1708
1718
|
const { execSync } = require('child_process');
|
|
1709
1719
|
|
|
1710
|
-
beforeEach(() => { tmpDir = createTempDir(); });
|
|
1711
|
-
afterEach(() => { cleanupDir(tmpDir); });
|
|
1720
|
+
beforeEach(() => { resetPaths(); tmpDir = createTempDir(); tmpDir = fs.realpathSync(tmpDir); });
|
|
1721
|
+
afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
|
|
1712
1722
|
|
|
1713
|
-
it('cmdReposInitProduct
|
|
1714
|
-
// Set up a product root with
|
|
1715
|
-
// discoverRepos will find nothing -> localPaths is empty -> syncGitignore not called
|
|
1723
|
+
it('cmdReposInitProduct creates root layout with .gitignore', () => {
|
|
1724
|
+
// Set up a product root with its own git repo (root layout)
|
|
1716
1725
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1717
|
-
fs.mkdirSync(
|
|
1718
|
-
|
|
1726
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1727
|
+
initGitRepo(productRoot);
|
|
1728
|
+
// Create sibling repos (outside product root -- discoverRepos won't find them)
|
|
1719
1729
|
fs.mkdirSync(path.join(tmpDir, 'sibling-app', '.git'), { recursive: true });
|
|
1720
1730
|
|
|
1721
1731
|
const reposPath = require.resolve('./repos.cjs');
|
|
@@ -1745,20 +1755,18 @@ describe('syncGitignore skip for flat layout', () => {
|
|
|
1745
1755
|
}).trim();
|
|
1746
1756
|
const parsed = JSON.parse(result);
|
|
1747
1757
|
assert.strictEqual(parsed.initialized, true);
|
|
1748
|
-
assert.strictEqual(parsed.
|
|
1758
|
+
assert.strictEqual(parsed.layout, 'root');
|
|
1749
1759
|
|
|
1750
|
-
// .gitignore should
|
|
1760
|
+
// .gitignore should exist with root-layout DGS markers
|
|
1751
1761
|
const gitignorePath = path.join(productRoot, '.gitignore');
|
|
1752
|
-
|
|
1753
|
-
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
1754
|
-
assert.ok(!content.includes('# DGS managed repos'), '.gitignore should not contain DGS markers');
|
|
1755
|
-
}
|
|
1762
|
+
assert.ok(fs.existsSync(gitignorePath), '.gitignore should exist');
|
|
1756
1763
|
});
|
|
1757
1764
|
|
|
1758
1765
|
it('cmdReposAdd with ../path does NOT modify .gitignore', () => {
|
|
1759
|
-
// Set up product root with
|
|
1766
|
+
// Set up product root with its own git repo and a sibling repo
|
|
1760
1767
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1761
|
-
fs.mkdirSync(
|
|
1768
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1769
|
+
initGitRepo(productRoot);
|
|
1762
1770
|
const reposMd = `# Repos
|
|
1763
1771
|
|
|
1764
1772
|
Registered repositories for this product. Managed by DGS — manual edits may be overwritten.
|
|
@@ -1766,7 +1774,7 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1766
1774
|
| Name | Path | GitHub URL | Description |
|
|
1767
1775
|
|------|------|------------|-------------|
|
|
1768
1776
|
`;
|
|
1769
|
-
fs.writeFileSync(path.join(productRoot, '
|
|
1777
|
+
fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
|
|
1770
1778
|
// Create sibling repo with .git
|
|
1771
1779
|
fs.mkdirSync(path.join(tmpDir, 'sibling-repo', '.git'), { recursive: true });
|
|
1772
1780
|
|
|
@@ -1803,9 +1811,10 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1803
1811
|
});
|
|
1804
1812
|
|
|
1805
1813
|
it('cmdReposRemove with ../path does NOT modify .gitignore', () => {
|
|
1806
|
-
// Set up product root with REPOS.md containing a ../repo entry
|
|
1814
|
+
// Set up product root with its own git repo and REPOS.md containing a ../repo entry
|
|
1807
1815
|
const productRoot = path.join(tmpDir, 'product-root');
|
|
1808
|
-
fs.mkdirSync(
|
|
1816
|
+
fs.mkdirSync(productRoot, { recursive: true });
|
|
1817
|
+
initGitRepo(productRoot);
|
|
1809
1818
|
const reposMd = `# Repos
|
|
1810
1819
|
|
|
1811
1820
|
Registered repositories for this product. Managed by DGS — manual edits may be overwritten.
|
|
@@ -1814,7 +1823,7 @@ Registered repositories for this product. Managed by DGS — manual edits may be
|
|
|
1814
1823
|
|------|------|------------|-------------|
|
|
1815
1824
|
| sibling-repo | ../sibling-repo | | Test repo |
|
|
1816
1825
|
`;
|
|
1817
|
-
fs.writeFileSync(path.join(productRoot, '
|
|
1826
|
+
fs.writeFileSync(path.join(productRoot, 'REPOS.md'), reposMd);
|
|
1818
1827
|
// Create sibling repo with .git
|
|
1819
1828
|
fs.mkdirSync(path.join(tmpDir, 'sibling-repo', '.git'), { recursive: true });
|
|
1820
1829
|
|
|
@@ -161,7 +161,7 @@ function parseFrontmatter(content) {
|
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
163
|
* Scan ideas directories for searchable idea files.
|
|
164
|
-
* Scans
|
|
164
|
+
* Scans ideas/{pending,done,rejected}/ for .md files.
|
|
165
165
|
*
|
|
166
166
|
* @param {string} cwd - Working directory
|
|
167
167
|
* @param {object} options - { include_rejected, tags }
|
|
@@ -218,7 +218,7 @@ function scanIdeas(cwd, options) {
|
|
|
218
218
|
|
|
219
219
|
/**
|
|
220
220
|
* Scan specs directory for searchable spec files.
|
|
221
|
-
* Scans
|
|
221
|
+
* Scans specs/ for .md files.
|
|
222
222
|
*
|
|
223
223
|
* @param {string} cwd - Working directory
|
|
224
224
|
* @returns {Array<{ type: string, id: string, title: string, filePath: string, status: string, author: string, content: string }>}
|
|
@@ -258,8 +258,8 @@ function scanSpecs(cwd) {
|
|
|
258
258
|
|
|
259
259
|
/**
|
|
260
260
|
* Scan docs directories for searchable documents.
|
|
261
|
-
* Scans
|
|
262
|
-
*
|
|
261
|
+
* Scans docs/ (product), ideas/{state}/{idea-slug}/docs/ (idea-scoped),
|
|
262
|
+
* specs/{spec-slug}/docs/ (spec-scoped).
|
|
263
263
|
* Uses .extracted.txt sidecars when available.
|
|
264
264
|
*
|
|
265
265
|
* @param {string} cwd - Working directory
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides the data model and operations for the spec lifecycle:
|
|
5
5
|
* create, list, update-status, append-review-history, and finalize.
|
|
6
6
|
* Specs are stored as markdown files with YAML frontmatter in
|
|
7
|
-
*
|
|
7
|
+
* specs/ (flat directory, no subdirectories).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
@@ -32,7 +32,7 @@ function normalizeStatus(status) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Ensure
|
|
35
|
+
* Ensure specs/ directory exists.
|
|
36
36
|
*
|
|
37
37
|
* @param {string} cwd - Working directory
|
|
38
38
|
*/
|
|
@@ -140,7 +140,7 @@ function classifyError(result, operation, repoName, repoPath) {
|
|
|
140
140
|
function collectSyncRepos(cwd) {
|
|
141
141
|
const repos = [];
|
|
142
142
|
|
|
143
|
-
// First entry: the planning repo itself (cwd is the product root
|
|
143
|
+
// First entry: the planning repo itself (cwd is the product root)
|
|
144
144
|
const resolvedCwd = path.resolve(cwd);
|
|
145
145
|
const basename = path.basename(resolvedCwd);
|
|
146
146
|
|
|
@@ -144,9 +144,9 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
|
|
|
144
144
|
'- **Output:** [Concrete deliverable]',
|
|
145
145
|
'',
|
|
146
146
|
'## Context',
|
|
147
|
-
'
|
|
148
|
-
'
|
|
149
|
-
'
|
|
147
|
+
'@PROJECT.md',
|
|
148
|
+
'@ROADMAP.md',
|
|
149
|
+
'@STATE.md',
|
|
150
150
|
'',
|
|
151
151
|
'## Tasks',
|
|
152
152
|
'',
|