@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
|
@@ -142,9 +142,9 @@ describe('context load-tier none', () => {
|
|
|
142
142
|
beforeEach(() => {
|
|
143
143
|
resetTierCache();
|
|
144
144
|
fixture = createFixture({
|
|
145
|
-
'
|
|
146
|
-
'
|
|
147
|
-
'
|
|
145
|
+
'config.json': JSON.stringify({}),
|
|
146
|
+
'STATE.md': '# State',
|
|
147
|
+
'PROJECT.md': '# Project',
|
|
148
148
|
});
|
|
149
149
|
});
|
|
150
150
|
|
|
@@ -154,18 +154,18 @@ describe('context load-tier none', () => {
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
it('returns empty files array', () => {
|
|
157
|
-
const result = loadTierInternal('none',
|
|
157
|
+
const result = loadTierInternal('none', fixture.cwd, { cwd: fixture.cwd });
|
|
158
158
|
assert.ok(Array.isArray(result.files), 'files should be array');
|
|
159
159
|
assert.equal(result.files.length, 0, 'none tier should have 0 files');
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
it('returns tier name in output', () => {
|
|
163
|
-
const result = loadTierInternal('none',
|
|
163
|
+
const result = loadTierInternal('none', fixture.cwd, { cwd: fixture.cwd });
|
|
164
164
|
assert.equal(result.tier, 'none', 'tier should be "none"');
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
it('returns empty scope', () => {
|
|
168
|
-
const result = loadTierInternal('none',
|
|
168
|
+
const result = loadTierInternal('none', fixture.cwd, { cwd: fixture.cwd });
|
|
169
169
|
assert.deepEqual(result.scope, {}, 'scope should be empty');
|
|
170
170
|
});
|
|
171
171
|
});
|
|
@@ -186,12 +186,12 @@ describe('context load-tier lite', () => {
|
|
|
186
186
|
|
|
187
187
|
it('returns files with correct paths for existing files', () => {
|
|
188
188
|
fixture = createFixture({
|
|
189
|
-
'
|
|
190
|
-
'
|
|
191
|
-
'
|
|
189
|
+
'config.json': JSON.stringify({}),
|
|
190
|
+
'STATE.md': '# State',
|
|
191
|
+
'PROJECT.md': '# Project',
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
-
const result = loadTierInternal('lite',
|
|
194
|
+
const result = loadTierInternal('lite', fixture.cwd, { cwd: fixture.cwd });
|
|
195
195
|
assert.equal(result.tier, 'lite');
|
|
196
196
|
assert.ok(result.files.length > 0, 'should have some files');
|
|
197
197
|
|
|
@@ -204,12 +204,12 @@ describe('context load-tier lite', () => {
|
|
|
204
204
|
|
|
205
205
|
it('returns category labels for each file', () => {
|
|
206
206
|
fixture = createFixture({
|
|
207
|
-
'
|
|
208
|
-
'
|
|
209
|
-
'
|
|
207
|
+
'config.json': JSON.stringify({}),
|
|
208
|
+
'STATE.md': '# State',
|
|
209
|
+
'PROJECT.md': '# Project',
|
|
210
210
|
});
|
|
211
211
|
|
|
212
|
-
const result = loadTierInternal('lite',
|
|
212
|
+
const result = loadTierInternal('lite', fixture.cwd, { cwd: fixture.cwd });
|
|
213
213
|
|
|
214
214
|
for (const file of result.files) {
|
|
215
215
|
assert.ok(file.category, `file ${file.path} should have category`);
|
|
@@ -224,11 +224,11 @@ describe('context load-tier lite', () => {
|
|
|
224
224
|
it('omits missing files silently', () => {
|
|
225
225
|
// No STATE.md in this fixture
|
|
226
226
|
fixture = createFixture({
|
|
227
|
-
'
|
|
228
|
-
'
|
|
227
|
+
'config.json': JSON.stringify({}),
|
|
228
|
+
'PROJECT.md': '# Project',
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
-
const result = loadTierInternal('lite',
|
|
231
|
+
const result = loadTierInternal('lite', fixture.cwd, { cwd: fixture.cwd });
|
|
232
232
|
const filePaths = result.files.map(f => f.path);
|
|
233
233
|
assert.ok(!filePaths.some(p => p.includes('STATE.md')), 'STATE.md should be omitted when missing');
|
|
234
234
|
assert.ok(filePaths.some(p => p.includes('PROJECT.md')), 'PROJECT.md should still be present');
|
|
@@ -237,12 +237,12 @@ describe('context load-tier lite', () => {
|
|
|
237
237
|
|
|
238
238
|
it('all files have exists: true', () => {
|
|
239
239
|
fixture = createFixture({
|
|
240
|
-
'
|
|
241
|
-
'
|
|
242
|
-
'
|
|
240
|
+
'config.json': JSON.stringify({}),
|
|
241
|
+
'STATE.md': '# State',
|
|
242
|
+
'PROJECT.md': '# Project',
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
const result = loadTierInternal('lite',
|
|
245
|
+
const result = loadTierInternal('lite', fixture.cwd, { cwd: fixture.cwd });
|
|
246
246
|
for (const file of result.files) {
|
|
247
247
|
assert.equal(file.exists, true, `file ${file.path} should have exists: true`);
|
|
248
248
|
}
|
|
@@ -265,15 +265,15 @@ describe('context load-tier planning', () => {
|
|
|
265
265
|
|
|
266
266
|
it('includes all lite tier files plus planning files', () => {
|
|
267
267
|
fixture = createFixture({
|
|
268
|
-
'
|
|
269
|
-
'
|
|
270
|
-
'
|
|
271
|
-
'
|
|
272
|
-
'
|
|
273
|
-
'
|
|
268
|
+
'config.json': JSON.stringify({}),
|
|
269
|
+
'STATE.md': '# State',
|
|
270
|
+
'PROJECT.md': '# Project',
|
|
271
|
+
'ROADMAP.md': '# Roadmap',
|
|
272
|
+
'REQUIREMENTS.md': '# Requirements',
|
|
273
|
+
'REPOS.md': '# Repos',
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
-
const result = loadTierInternal('planning',
|
|
276
|
+
const result = loadTierInternal('planning', fixture.cwd, { cwd: fixture.cwd });
|
|
277
277
|
const filePaths = result.files.map(f => f.path);
|
|
278
278
|
|
|
279
279
|
// Lite tier files
|
|
@@ -289,14 +289,14 @@ describe('context load-tier planning', () => {
|
|
|
289
289
|
|
|
290
290
|
it('includes codebase docs via dynamic glob', () => {
|
|
291
291
|
fixture = createFixture({
|
|
292
|
-
'
|
|
293
|
-
'
|
|
294
|
-
'
|
|
295
|
-
'
|
|
296
|
-
'
|
|
292
|
+
'config.json': JSON.stringify({}),
|
|
293
|
+
'PROJECT.md': '# Project',
|
|
294
|
+
'ROADMAP.md': '# Roadmap',
|
|
295
|
+
'codebase/CONVENTIONS.md': '# Conventions',
|
|
296
|
+
'codebase/TESTING.md': '# Testing',
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
-
const result = loadTierInternal('planning',
|
|
299
|
+
const result = loadTierInternal('planning', fixture.cwd, { cwd: fixture.cwd });
|
|
300
300
|
const filePaths = result.files.map(f => f.path);
|
|
301
301
|
|
|
302
302
|
assert.ok(filePaths.some(p => p.includes('CONVENTIONS.md')), 'should include CONVENTIONS.md');
|
|
@@ -309,14 +309,14 @@ describe('context load-tier planning', () => {
|
|
|
309
309
|
|
|
310
310
|
it('missing REPOS.md is silently omitted', () => {
|
|
311
311
|
fixture = createFixture({
|
|
312
|
-
'
|
|
313
|
-
'
|
|
314
|
-
'
|
|
315
|
-
'
|
|
312
|
+
'config.json': JSON.stringify({}),
|
|
313
|
+
'PROJECT.md': '# Project',
|
|
314
|
+
'ROADMAP.md': '# Roadmap',
|
|
315
|
+
'REQUIREMENTS.md': '# Requirements',
|
|
316
316
|
// No REPOS.md
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
-
const result = loadTierInternal('planning',
|
|
319
|
+
const result = loadTierInternal('planning', fixture.cwd, { cwd: fixture.cwd });
|
|
320
320
|
const filePaths = result.files.map(f => f.path);
|
|
321
321
|
assert.ok(!filePaths.some(p => p.includes('REPOS.md')), 'REPOS.md should be omitted when missing');
|
|
322
322
|
assert.ok(filePaths.some(p => p.includes('ROADMAP.md')), 'ROADMAP.md should be present');
|
|
@@ -339,17 +339,17 @@ describe('context load-tier with scope flags', () => {
|
|
|
339
339
|
|
|
340
340
|
it('--phase adds phase-specific files to output', () => {
|
|
341
341
|
fixture = createFixture({
|
|
342
|
-
'
|
|
343
|
-
'
|
|
344
|
-
'
|
|
345
|
-
'
|
|
346
|
-
'
|
|
347
|
-
'
|
|
348
|
-
'
|
|
349
|
-
'
|
|
342
|
+
'config.json': JSON.stringify({}),
|
|
343
|
+
'PROJECT.md': '# Project',
|
|
344
|
+
'STATE.md': '# State',
|
|
345
|
+
'ROADMAP.md': '# Roadmap\n\n## Phases\n\n### Phase 1: Test Phase\n',
|
|
346
|
+
'phases/01-test-phase/01-CONTEXT.md': '# Context',
|
|
347
|
+
'phases/01-test-phase/01-RESEARCH.md': '# Research',
|
|
348
|
+
'phases/01-test-phase/01-01-PLAN.md': '---\nphase: 01\nplan: 01\n---\n# Plan',
|
|
349
|
+
'phases/01-test-phase/01-01-SUMMARY.md': '---\nphase: 01\nplan: 01\n---\n# Summary',
|
|
350
350
|
});
|
|
351
351
|
|
|
352
|
-
const result = loadTierInternal('execution',
|
|
352
|
+
const result = loadTierInternal('execution', fixture.cwd, {
|
|
353
353
|
cwd: fixture.cwd,
|
|
354
354
|
phase: '1',
|
|
355
355
|
});
|
|
@@ -363,14 +363,14 @@ describe('context load-tier with scope flags', () => {
|
|
|
363
363
|
|
|
364
364
|
it('--idea adds idea file and docs to output', () => {
|
|
365
365
|
fixture = createFixture({
|
|
366
|
-
'
|
|
367
|
-
'
|
|
368
|
-
'
|
|
369
|
-
'
|
|
370
|
-
'
|
|
366
|
+
'config.json': JSON.stringify({}),
|
|
367
|
+
'PROJECT.md': '# Project',
|
|
368
|
+
'ROADMAP.md': '# Roadmap',
|
|
369
|
+
'ideas/pending/005-better-caching.md': '# Better Caching',
|
|
370
|
+
'ideas/pending/005-better-caching/docs/research.md': '# Research on caching',
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
-
const result = loadTierInternal('planning',
|
|
373
|
+
const result = loadTierInternal('planning', fixture.cwd, {
|
|
374
374
|
cwd: fixture.cwd,
|
|
375
375
|
idea: '5',
|
|
376
376
|
});
|
|
@@ -383,14 +383,14 @@ describe('context load-tier with scope flags', () => {
|
|
|
383
383
|
|
|
384
384
|
it('--spec adds spec file and docs to output', () => {
|
|
385
385
|
fixture = createFixture({
|
|
386
|
-
'
|
|
387
|
-
'
|
|
388
|
-
'
|
|
389
|
-
'
|
|
390
|
-
'
|
|
386
|
+
'config.json': JSON.stringify({}),
|
|
387
|
+
'PROJECT.md': '# Project',
|
|
388
|
+
'ROADMAP.md': '# Roadmap',
|
|
389
|
+
'specs/dgs-improved-context-spec.md': '---\nid: dgs-improved-context\nstatus: draft\n---\n# Improved Context',
|
|
390
|
+
'specs/dgs-improved-context-spec/docs/analysis.md': '# Analysis',
|
|
391
391
|
});
|
|
392
392
|
|
|
393
|
-
const result = loadTierInternal('planning',
|
|
393
|
+
const result = loadTierInternal('planning', fixture.cwd, {
|
|
394
394
|
cwd: fixture.cwd,
|
|
395
395
|
spec: 'dgs-improved-context',
|
|
396
396
|
});
|
|
@@ -403,15 +403,15 @@ describe('context load-tier with scope flags', () => {
|
|
|
403
403
|
|
|
404
404
|
it('missing scope targets silently return no extra files', () => {
|
|
405
405
|
fixture = createFixture({
|
|
406
|
-
'
|
|
407
|
-
'
|
|
408
|
-
'
|
|
409
|
-
'
|
|
410
|
-
'
|
|
406
|
+
'config.json': JSON.stringify({}),
|
|
407
|
+
'PROJECT.md': '# Project',
|
|
408
|
+
'STATE.md': '# State',
|
|
409
|
+
'ROADMAP.md': '# Roadmap\n\n## Phases\n\n### Phase 1: Test Phase\n',
|
|
410
|
+
'phases/01-test-phase/01-01-PLAN.md': '# Plan',
|
|
411
411
|
});
|
|
412
412
|
|
|
413
413
|
// Phase 999 does not exist
|
|
414
|
-
const result = loadTierInternal('execution',
|
|
414
|
+
const result = loadTierInternal('execution', fixture.cwd, {
|
|
415
415
|
cwd: fixture.cwd,
|
|
416
416
|
phase: '999',
|
|
417
417
|
});
|
|
@@ -426,13 +426,13 @@ describe('context load-tier with scope flags', () => {
|
|
|
426
426
|
|
|
427
427
|
it('approved spec is marked as truncated', () => {
|
|
428
428
|
fixture = createFixture({
|
|
429
|
-
'
|
|
430
|
-
'
|
|
431
|
-
'
|
|
432
|
-
'
|
|
429
|
+
'config.json': JSON.stringify({}),
|
|
430
|
+
'PROJECT.md': '# Project',
|
|
431
|
+
'ROADMAP.md': '# Roadmap',
|
|
432
|
+
'specs/my-spec.md': '---\nid: my-spec\nstatus: approved\n---\n# My Spec\n\n## Section 1\nContent\n\n## Section 2\nContent\n\n## Section 3\nContent\n\n## Section 4\nShould be truncated',
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
-
const result = loadTierInternal('planning',
|
|
435
|
+
const result = loadTierInternal('planning', fixture.cwd, {
|
|
436
436
|
cwd: fixture.cwd,
|
|
437
437
|
spec: 'my-spec',
|
|
438
438
|
});
|
|
@@ -458,13 +458,13 @@ describe('graceful degradation', () => {
|
|
|
458
458
|
resetTierCache();
|
|
459
459
|
});
|
|
460
460
|
|
|
461
|
-
it('empty
|
|
461
|
+
it('empty directory returns empty files for lite tier', () => {
|
|
462
462
|
fixture = createFixture({
|
|
463
|
-
'
|
|
463
|
+
'config.json': JSON.stringify({}),
|
|
464
464
|
// No STATE.md, PROJECT.md etc.
|
|
465
465
|
});
|
|
466
466
|
|
|
467
|
-
const result = loadTierInternal('lite',
|
|
467
|
+
const result = loadTierInternal('lite', fixture.cwd, { cwd: fixture.cwd });
|
|
468
468
|
assert.equal(result.tier, 'lite');
|
|
469
469
|
// config.json exists but PROJECT.md and STATE.md don't
|
|
470
470
|
assert.ok(result.files.length <= 1, 'should have at most config.json');
|
|
@@ -472,10 +472,10 @@ describe('graceful degradation', () => {
|
|
|
472
472
|
|
|
473
473
|
it('non-existent tier name returns error in result', () => {
|
|
474
474
|
fixture = createFixture({
|
|
475
|
-
'
|
|
475
|
+
'config.json': JSON.stringify({}),
|
|
476
476
|
});
|
|
477
477
|
|
|
478
|
-
const result = loadTierInternal('invalid-name',
|
|
478
|
+
const result = loadTierInternal('invalid-name', fixture.cwd, { cwd: fixture.cwd });
|
|
479
479
|
assert.ok(result.error, 'should have error field');
|
|
480
480
|
assert.ok(result.error.includes('Unknown tier'), 'error should mention unknown tier');
|
|
481
481
|
assert.equal(result.files.length, 0, 'should have empty files');
|
|
@@ -483,7 +483,7 @@ describe('graceful degradation', () => {
|
|
|
483
483
|
|
|
484
484
|
it('invalid tier via CLI produces error on stderr', () => {
|
|
485
485
|
fixture = createFixture({
|
|
486
|
-
'
|
|
486
|
+
'config.json': JSON.stringify({}),
|
|
487
487
|
});
|
|
488
488
|
|
|
489
489
|
const stderr = runContextError(fixture.cwd, 'load-tier invalid-name');
|
|
@@ -491,14 +491,14 @@ describe('graceful degradation', () => {
|
|
|
491
491
|
assert.ok(stderr.includes('invalid tier'), 'error should mention invalid tier');
|
|
492
492
|
});
|
|
493
493
|
|
|
494
|
-
it('missing
|
|
494
|
+
it('missing config.json falls back gracefully', () => {
|
|
495
495
|
fixture = createFixture({
|
|
496
|
-
'
|
|
497
|
-
'
|
|
496
|
+
'PROJECT.md': '# Project',
|
|
497
|
+
'STATE.md': '# State',
|
|
498
498
|
});
|
|
499
499
|
|
|
500
500
|
// The config.json from tier definitions won't be found but that's fine
|
|
501
|
-
const result = loadTierInternal('lite',
|
|
501
|
+
const result = loadTierInternal('lite', fixture.cwd, { cwd: fixture.cwd });
|
|
502
502
|
assert.equal(result.tier, 'lite');
|
|
503
503
|
// PROJECT.md and STATE.md should be found
|
|
504
504
|
const filePaths = result.files.map(f => f.path);
|
|
@@ -523,9 +523,9 @@ describe('CLI integration', () => {
|
|
|
523
523
|
|
|
524
524
|
it('context load-tier lite returns JSON with correct structure', () => {
|
|
525
525
|
fixture = createFixture({
|
|
526
|
-
'
|
|
527
|
-
'
|
|
528
|
-
'
|
|
526
|
+
'config.json': JSON.stringify({}),
|
|
527
|
+
'STATE.md': '# State',
|
|
528
|
+
'PROJECT.md': '# Project',
|
|
529
529
|
});
|
|
530
530
|
|
|
531
531
|
const result = runContext(fixture.cwd, 'load-tier lite');
|
|
@@ -536,7 +536,7 @@ describe('CLI integration', () => {
|
|
|
536
536
|
|
|
537
537
|
it('context load-tier none returns empty files via CLI', () => {
|
|
538
538
|
fixture = createFixture({
|
|
539
|
-
'
|
|
539
|
+
'config.json': JSON.stringify({}),
|
|
540
540
|
});
|
|
541
541
|
|
|
542
542
|
const result = runContext(fixture.cwd, 'load-tier none');
|
|
@@ -546,11 +546,11 @@ describe('CLI integration', () => {
|
|
|
546
546
|
|
|
547
547
|
it('context load-tier planning includes inherited lite files via CLI', () => {
|
|
548
548
|
fixture = createFixture({
|
|
549
|
-
'
|
|
550
|
-
'
|
|
551
|
-
'
|
|
552
|
-
'
|
|
553
|
-
'
|
|
549
|
+
'config.json': JSON.stringify({}),
|
|
550
|
+
'STATE.md': '# State',
|
|
551
|
+
'PROJECT.md': '# Project',
|
|
552
|
+
'ROADMAP.md': '# Roadmap',
|
|
553
|
+
'REQUIREMENTS.md': '# Requirements',
|
|
554
554
|
});
|
|
555
555
|
|
|
556
556
|
const result = runContext(fixture.cwd, 'load-tier planning');
|
|
@@ -561,13 +561,13 @@ describe('CLI integration', () => {
|
|
|
561
561
|
|
|
562
562
|
it('context load-tier with --phase flag works via CLI', () => {
|
|
563
563
|
fixture = createFixture({
|
|
564
|
-
'
|
|
565
|
-
'
|
|
566
|
-
'
|
|
567
|
-
'
|
|
568
|
-
'
|
|
569
|
-
'
|
|
570
|
-
'
|
|
564
|
+
'config.json': JSON.stringify({}),
|
|
565
|
+
'PROJECT.md': '# Project',
|
|
566
|
+
'STATE.md': '# State',
|
|
567
|
+
'ROADMAP.md': '# Roadmap\n\n## Phases\n\n### Phase 1: Test Phase\n',
|
|
568
|
+
'REQUIREMENTS.md': '# Requirements',
|
|
569
|
+
'phases/01-test-phase/01-CONTEXT.md': '# Context',
|
|
570
|
+
'phases/01-test-phase/01-01-PLAN.md': '---\nphase: 01\nplan: 01\n---\n# Plan',
|
|
571
571
|
});
|
|
572
572
|
|
|
573
573
|
const result = runContext(fixture.cwd, 'load-tier execution --phase 1');
|
|
@@ -578,7 +578,7 @@ describe('CLI integration', () => {
|
|
|
578
578
|
|
|
579
579
|
it('context without subcommand produces error', () => {
|
|
580
580
|
fixture = createFixture({
|
|
581
|
-
'
|
|
581
|
+
'config.json': JSON.stringify({}),
|
|
582
582
|
});
|
|
583
583
|
|
|
584
584
|
const stderr = runContextError(fixture.cwd, '');
|
|
@@ -589,7 +589,7 @@ describe('CLI integration', () => {
|
|
|
589
589
|
|
|
590
590
|
it('context load-tier without tier name produces usage error', () => {
|
|
591
591
|
fixture = createFixture({
|
|
592
|
-
'
|
|
592
|
+
'config.json': JSON.stringify({}),
|
|
593
593
|
});
|
|
594
594
|
|
|
595
595
|
const stderr = runContextError(fixture.cwd, 'load-tier');
|
|
@@ -600,7 +600,7 @@ describe('CLI integration', () => {
|
|
|
600
600
|
|
|
601
601
|
it('context unknown-subcommand produces error', () => {
|
|
602
602
|
fixture = createFixture({
|
|
603
|
-
'
|
|
603
|
+
'config.json': JSON.stringify({}),
|
|
604
604
|
});
|
|
605
605
|
|
|
606
606
|
const stderr = runContextError(fixture.cwd, 'bogus');
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
|
-
const { getPlanningRoot, PROJECTS_DIR } = require('./paths.cjs');
|
|
8
|
+
const { getPlanningRoot, isV2Install, PROJECTS_DIR } = require('./paths.cjs');
|
|
9
9
|
|
|
10
10
|
// ─── Path helpers ────────────────────────────────────────────────────────────
|
|
11
11
|
|
|
@@ -102,17 +102,7 @@ function loadConfig(cwd) {
|
|
|
102
102
|
}
|
|
103
103
|
} catch { /* ignore */ }
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
let hasNewConfig = Object.keys(shared).length > 0 || Object.keys(local).length > 0;
|
|
107
|
-
if (!hasNewConfig) {
|
|
108
|
-
const legacyPath = path.join(root, 'dgs.config.json');
|
|
109
|
-
try {
|
|
110
|
-
if (fs.existsSync(legacyPath)) {
|
|
111
|
-
shared = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
|
|
112
|
-
hasNewConfig = true;
|
|
113
|
-
}
|
|
114
|
-
} catch { /* ignore */ }
|
|
115
|
-
}
|
|
105
|
+
const hasNewConfig = Object.keys(shared).length > 0 || Object.keys(local).length > 0;
|
|
116
106
|
|
|
117
107
|
if (!hasNewConfig) {
|
|
118
108
|
return { ...defaults, current_project: null, v2_hint_shown: false, sync_hint_shown: false };
|
|
@@ -182,8 +172,7 @@ function isGitIgnored(cwd, targetPath) {
|
|
|
182
172
|
try {
|
|
183
173
|
// --no-index checks .gitignore rules regardless of whether the file is tracked.
|
|
184
174
|
// Without it, git check-ignore returns "not ignored" for tracked files even when
|
|
185
|
-
// .gitignore explicitly lists them —
|
|
186
|
-
// was committed before being added to .gitignore.
|
|
175
|
+
// .gitignore explicitly lists them — ensure planning files are not accidentally ignored.
|
|
187
176
|
execSync('git check-ignore -q --no-index -- ' + targetPath.replace(/[^a-zA-Z0-9._\-/]/g, ''), {
|
|
188
177
|
cwd,
|
|
189
178
|
stdio: 'pipe',
|
|
@@ -308,12 +297,12 @@ function findPhaseInternal(cwd, phase) {
|
|
|
308
297
|
|
|
309
298
|
const normalized = normalizePhaseName(phase);
|
|
310
299
|
|
|
311
|
-
// Resolve phases directory:
|
|
300
|
+
// Resolve phases directory: project root or '.' fallback
|
|
312
301
|
let projectRoot;
|
|
313
302
|
try {
|
|
314
303
|
projectRoot = getProjectRoot(cwd);
|
|
315
304
|
} catch {
|
|
316
|
-
projectRoot = '.
|
|
305
|
+
projectRoot = '.';
|
|
317
306
|
}
|
|
318
307
|
const phasesRel = path.join(projectRoot, 'phases');
|
|
319
308
|
const phasesDir = path.join(cwd, phasesRel);
|
|
@@ -393,12 +382,12 @@ function getArchivedPhaseDirs(cwd) {
|
|
|
393
382
|
|
|
394
383
|
function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
395
384
|
if (!phaseNum) return null;
|
|
396
|
-
//
|
|
385
|
+
// Look in project root (projects/<slug>/)
|
|
397
386
|
let projectRoot;
|
|
398
387
|
try {
|
|
399
388
|
projectRoot = getProjectRoot(cwd);
|
|
400
389
|
} catch {
|
|
401
|
-
projectRoot = path.relative(cwd, getPlanningRoot(cwd)) || '.
|
|
390
|
+
projectRoot = path.relative(cwd, getPlanningRoot(cwd)) || '.';
|
|
402
391
|
}
|
|
403
392
|
const roadmapPath = path.join(cwd, projectRoot, 'ROADMAP.md');
|
|
404
393
|
if (!fs.existsSync(roadmapPath)) return null;
|
|
@@ -554,8 +543,8 @@ function getMilestonePhaseFilter(cwd) {
|
|
|
554
543
|
// ─── v2 Path Resolution ──────────────────────────────────────────────────────
|
|
555
544
|
|
|
556
545
|
/**
|
|
557
|
-
* System directories that
|
|
558
|
-
* Kept as a safety net — getProjectFolders()
|
|
546
|
+
* System directories that are NOT project folders.
|
|
547
|
+
* Kept as a safety net — getProjectFolders() scans projects/ directly
|
|
559
548
|
* and no longer relies on this set for filtering. The 'projects' entry prevents
|
|
560
549
|
* the projects container directory itself from being mistaken for a project.
|
|
561
550
|
*/
|
|
@@ -564,32 +553,6 @@ const SYSTEM_DIRS = new Set([
|
|
|
564
553
|
'debug', 'milestones', 'research', PROJECTS_DIR,
|
|
565
554
|
]);
|
|
566
555
|
|
|
567
|
-
/**
|
|
568
|
-
* Detect if this is a v2 (multi-project) installation.
|
|
569
|
-
* Requires both file existence AND valid DGS-generated content.
|
|
570
|
-
* PROJECTS.md must start with "# Projects", REPOS.md with "# Repos".
|
|
571
|
-
*
|
|
572
|
-
* @param {string} cwd - Working directory
|
|
573
|
-
* @returns {boolean}
|
|
574
|
-
*/
|
|
575
|
-
function isV2Install(cwd) {
|
|
576
|
-
const checks = [
|
|
577
|
-
{ file: 'PROJECTS.md', header: '# Projects' },
|
|
578
|
-
{ file: 'REPOS.md', header: '# Repos' },
|
|
579
|
-
];
|
|
580
|
-
const planRoot = getPlanningRoot(cwd);
|
|
581
|
-
for (const { file, header } of checks) {
|
|
582
|
-
const filePath = path.join(planRoot, file);
|
|
583
|
-
try {
|
|
584
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
585
|
-
if (content.startsWith(header)) return true;
|
|
586
|
-
} catch {
|
|
587
|
-
// File doesn't exist or unreadable — not a marker
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
556
|
/**
|
|
594
557
|
* Construct the absolute path to a project directory.
|
|
595
558
|
*
|
|
@@ -606,9 +569,9 @@ function getProjectDir(cwd, slug) {
|
|
|
606
569
|
|
|
607
570
|
/**
|
|
608
571
|
* Get the project root directory (relative path).
|
|
609
|
-
* - v2 with current_project set: returns
|
|
572
|
+
* - v2 with current_project set: returns projects/<project>
|
|
610
573
|
* - v2 without current_project: throws Error('NO_CURRENT_PROJECT_V2')
|
|
611
|
-
* -
|
|
574
|
+
* - No v2 markers: returns '.'
|
|
612
575
|
*
|
|
613
576
|
* @param {string} cwd - Working directory
|
|
614
577
|
* @returns {string} Relative path to project root
|
|
@@ -618,10 +581,8 @@ function getProjectRoot(cwd) {
|
|
|
618
581
|
const config = loadConfig(cwd);
|
|
619
582
|
const currentProject = (config.current_project || '').trim();
|
|
620
583
|
|
|
621
|
-
//
|
|
622
|
-
const
|
|
623
|
-
const isRootLayout = (planRoot === path.resolve(cwd));
|
|
624
|
-
const planRootRel = isRootLayout ? '.' : '.planning';
|
|
584
|
+
// Root layout: planning root IS the repo root
|
|
585
|
+
const planRootRel = '.';
|
|
625
586
|
|
|
626
587
|
if (currentProject) {
|
|
627
588
|
// Validate project name — must be a simple slug, no path traversal
|
|
@@ -649,7 +610,7 @@ function getProjectRoot(cwd) {
|
|
|
649
610
|
throw new Error('NO_CURRENT_PROJECT_V2');
|
|
650
611
|
}
|
|
651
612
|
|
|
652
|
-
//
|
|
613
|
+
// No v2 markers — return root
|
|
653
614
|
return planRootRel;
|
|
654
615
|
}
|
|
655
616
|
|
|
@@ -674,8 +635,7 @@ function requireProjectRoot(cwd) {
|
|
|
674
635
|
|
|
675
636
|
/**
|
|
676
637
|
* Resolve a project-scoped relative path.
|
|
677
|
-
* e.g., resolveProjectPath(cwd, 'STATE.md') → '
|
|
678
|
-
* resolveProjectPath(cwd, 'STATE.md') → '.planning/STATE.md' (v1)
|
|
638
|
+
* e.g., resolveProjectPath(cwd, 'STATE.md') → 'projects/auth-overhaul/STATE.md'
|
|
679
639
|
* resolveProjectPath(cwd) → project root
|
|
680
640
|
*
|
|
681
641
|
* @param {string} cwd - Working directory
|
|
@@ -692,7 +652,7 @@ function resolveProjectPath(cwd, relativePath) {
|
|
|
692
652
|
*
|
|
693
653
|
* This is intentionally lightweight — reads STATUS line from STATE.md using
|
|
694
654
|
* safeReadFile (already in core.cjs) to avoid circular dependency on projects.cjs.
|
|
695
|
-
* Reads from
|
|
655
|
+
* Reads from projects/<slug>/STATE.md.
|
|
696
656
|
*
|
|
697
657
|
* @param {string} cwd - Working directory
|
|
698
658
|
* @param {string} slug - Project slug
|
|
@@ -711,7 +671,7 @@ function isProjectCompleted(cwd, slug) {
|
|
|
711
671
|
|
|
712
672
|
/**
|
|
713
673
|
* List project folders in a v2 install.
|
|
714
|
-
* Scans
|
|
674
|
+
* Scans projects/ directly and returns directories that:
|
|
715
675
|
* - Don't start with '.'
|
|
716
676
|
* - Contain a STATE.md file (qualifying marker for project folders)
|
|
717
677
|
*
|