@ktpartners/dgs-platform 2.8.0 → 3.0.4

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 (94) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +41 -13
  3. package/agents/dgs-plan-checker.md +29 -3
  4. package/agents/dgs-planner.md +10 -0
  5. package/commands/dgs/abandon-quick.md +28 -0
  6. package/commands/dgs/add-tests.md +2 -2
  7. package/commands/dgs/audit-milestone.md +2 -2
  8. package/commands/dgs/capture-principle.md +11 -11
  9. package/commands/dgs/cleanup.md +2 -2
  10. package/commands/dgs/complete-milestone.md +11 -11
  11. package/commands/dgs/complete-quick.md +28 -0
  12. package/commands/dgs/create-milestone-job.md +2 -2
  13. package/commands/dgs/debug.md +3 -3
  14. package/commands/dgs/develop-idea.md +1 -1
  15. package/commands/dgs/fast.md +3 -1
  16. package/commands/dgs/health.md +1 -1
  17. package/commands/dgs/map-codebase.md +6 -6
  18. package/commands/dgs/new-milestone.md +5 -5
  19. package/commands/dgs/new-project.md +6 -6
  20. package/commands/dgs/plan-milestone-gaps.md +1 -1
  21. package/commands/dgs/progress.md +3 -3
  22. package/commands/dgs/quick-abandon.md +8 -0
  23. package/commands/dgs/quick-complete.md +8 -0
  24. package/commands/dgs/quick.md +10 -3
  25. package/commands/dgs/research-idea.md +2 -2
  26. package/commands/dgs/research-phase.md +3 -3
  27. package/commands/dgs/switch-project.md +1 -1
  28. package/commands/dgs/write-spec.md +3 -3
  29. package/deliver-great-systems/bin/dgs-tools.cjs +284 -30
  30. package/deliver-great-systems/bin/lib/commands.cjs +316 -31
  31. package/deliver-great-systems/bin/lib/commands.test.cjs +336 -0
  32. package/deliver-great-systems/bin/lib/config.cjs +39 -6
  33. package/deliver-great-systems/bin/lib/context.cjs +120 -0
  34. package/deliver-great-systems/bin/lib/core.cjs +28 -11
  35. package/deliver-great-systems/bin/lib/execution.cjs +49 -17
  36. package/deliver-great-systems/bin/lib/flat-migration.test.cjs +396 -0
  37. package/deliver-great-systems/bin/lib/ideas.cjs +206 -91
  38. package/deliver-great-systems/bin/lib/ideas.test.cjs +244 -1
  39. package/deliver-great-systems/bin/lib/init.cjs +306 -39
  40. package/deliver-great-systems/bin/lib/init.test.cjs +416 -6
  41. package/deliver-great-systems/bin/lib/jobs.cjs +124 -21
  42. package/deliver-great-systems/bin/lib/jobs.test.cjs +193 -74
  43. package/deliver-great-systems/bin/lib/migration.cjs +409 -1
  44. package/deliver-great-systems/bin/lib/migration.test.cjs +158 -1
  45. package/deliver-great-systems/bin/lib/milestone.cjs +54 -29
  46. package/deliver-great-systems/bin/lib/phase.cjs +128 -2
  47. package/deliver-great-systems/bin/lib/phase.test.cjs +420 -0
  48. package/deliver-great-systems/bin/lib/projects.cjs +28 -8
  49. package/deliver-great-systems/bin/lib/projects.test.cjs +86 -0
  50. package/deliver-great-systems/bin/lib/quick.cjs +584 -0
  51. package/deliver-great-systems/bin/lib/quick.test.cjs +596 -0
  52. package/deliver-great-systems/bin/lib/repos.cjs +25 -1
  53. package/deliver-great-systems/bin/lib/roadmap.cjs +34 -13
  54. package/deliver-great-systems/bin/lib/specs.cjs +3 -81
  55. package/deliver-great-systems/bin/lib/state-transition-gate.test.cjs +160 -0
  56. package/deliver-great-systems/bin/lib/state.cjs +142 -54
  57. package/deliver-great-systems/bin/lib/sync.cjs +75 -0
  58. package/deliver-great-systems/bin/lib/verify.cjs +80 -1
  59. package/deliver-great-systems/bin/lib/worktrees.cjs +764 -0
  60. package/deliver-great-systems/bin/lib/worktrees.test.cjs +887 -0
  61. package/deliver-great-systems/templates/claude-md.md +16 -0
  62. package/deliver-great-systems/workflows/abandon-quick.md +89 -0
  63. package/deliver-great-systems/workflows/add-idea.md +3 -3
  64. package/deliver-great-systems/workflows/add-tests.md +14 -0
  65. package/deliver-great-systems/workflows/add-todo.md +1 -0
  66. package/deliver-great-systems/workflows/approve-spec.md +25 -4
  67. package/deliver-great-systems/workflows/audit-phase.md +15 -5
  68. package/deliver-great-systems/workflows/cancel-job.md +1 -1
  69. package/deliver-great-systems/workflows/check-todos.md +2 -3
  70. package/deliver-great-systems/workflows/complete-milestone.md +197 -22
  71. package/deliver-great-systems/workflows/complete-quick.md +68 -0
  72. package/deliver-great-systems/workflows/consolidate-ideas.md +1 -1
  73. package/deliver-great-systems/workflows/create-milestone-job.md +4 -4
  74. package/deliver-great-systems/workflows/develop-idea.md +11 -11
  75. package/deliver-great-systems/workflows/diagnose-issues.md +14 -0
  76. package/deliver-great-systems/workflows/discuss-idea.md +1 -1
  77. package/deliver-great-systems/workflows/execute-phase.md +121 -32
  78. package/deliver-great-systems/workflows/execute-plan.md +12 -21
  79. package/deliver-great-systems/workflows/help.md +33 -29
  80. package/deliver-great-systems/workflows/init-product.md +2 -18
  81. package/deliver-great-systems/workflows/new-milestone.md +40 -24
  82. package/deliver-great-systems/workflows/new-project.md +22 -680
  83. package/deliver-great-systems/workflows/progress-all.md +133 -0
  84. package/deliver-great-systems/workflows/quick-abandon.md +89 -0
  85. package/deliver-great-systems/workflows/quick-complete.md +68 -0
  86. package/deliver-great-systems/workflows/quick.md +152 -23
  87. package/deliver-great-systems/workflows/refine-spec.md +1 -1
  88. package/deliver-great-systems/workflows/research-idea.md +8 -8
  89. package/deliver-great-systems/workflows/resume-project.md +2 -2
  90. package/deliver-great-systems/workflows/run-job.md +8 -8
  91. package/deliver-great-systems/workflows/validate-phase.md +39 -1
  92. package/deliver-great-systems/workflows/verify-work.md +14 -0
  93. package/deliver-great-systems/workflows/write-spec.md +2 -2
  94. package/package.json +1 -1
@@ -10,7 +10,7 @@ const path = require('path');
10
10
  const { execSync } = require('child_process');
11
11
  const { createTempDir, cleanupDir, createTempProject, initGitRepo } = require('./test-helpers.cjs');
12
12
  const { resetPaths } = require('./paths.cjs');
13
- const { findIdeaFile, parseIdeaFrontmatter, ensureIdeasDirs, cmdIdeasCreate, loadManifest, IDEA_STATES, buildIdeaContent, cmdIdeasConsolidate, cmdIdeasFindRelated, cmdIdeasUndoConsolidation } = require('./ideas.cjs');
13
+ const { findIdeaFile, parseIdeaFrontmatter, ensureIdeasDirs, cmdIdeasCreate, cmdIdeasList, loadManifest, IDEA_STATES, IDEA_STATUSES, setIdeaStatus, buildIdeaContent, cmdIdeasConsolidate, cmdIdeasFindRelated, cmdIdeasUndoConsolidation } = require('./ideas.cjs');
14
14
 
15
15
  // ─── Helpers ──────────────────────────────────────────────────────────────────
16
16
 
@@ -1416,3 +1416,246 @@ describe('cmdIdeasUndoConsolidation', () => {
1416
1416
  assert.strictEqual(json.undone_idea_id, 4);
1417
1417
  });
1418
1418
  });
1419
+
1420
+ // ─── IDEA_STATUSES constant ─────────────────────────────────────────────────
1421
+
1422
+ describe('IDEA_STATUSES constant', () => {
1423
+ it('exports correct values', () => {
1424
+ assert.deepStrictEqual(IDEA_STATUSES, ['pending', 'done', 'rejected', 'consolidated']);
1425
+ });
1426
+
1427
+ it('IDEA_STATES aliases IDEA_STATUSES', () => {
1428
+ assert.strictEqual(IDEA_STATES, IDEA_STATUSES);
1429
+ });
1430
+ });
1431
+
1432
+ // ─── buildIdeaContent round-trip ─────────────────────────────────────────────
1433
+
1434
+ describe('buildIdeaContent round-trip', () => {
1435
+ it('preserves status and unknown fields', () => {
1436
+ const original = '---\nid: 42\ntitle: "Round Trip Test"\nstatus: pending\ntags: ["test"]\ncreated: 2026-01-01\nupdated: 2026-01-02\nauthor: "Test"\ncustom_field: preserved_value\n---\n\nBody content here.\n';
1437
+ const parsed = parseIdeaFrontmatter(original);
1438
+ const rebuilt = buildIdeaContent(parsed.frontmatter, parsed.body, parsed.notes, parsed.discussionLog, parsed.researchLog);
1439
+ const reparsed = parseIdeaFrontmatter(rebuilt);
1440
+
1441
+ assert.strictEqual(reparsed.frontmatter.id, 42);
1442
+ assert.strictEqual(reparsed.frontmatter.title, 'Round Trip Test');
1443
+ assert.strictEqual(reparsed.frontmatter.status, 'pending');
1444
+ assert.deepStrictEqual(reparsed.frontmatter.tags, ['test']);
1445
+ assert.strictEqual(reparsed.frontmatter.custom_field, 'preserved_value');
1446
+ assert.ok(reparsed.body.includes('Body content here.'));
1447
+ });
1448
+ });
1449
+
1450
+ // ─── setIdeaStatus ───────────────────────────────────────────────────────────
1451
+
1452
+ describe('setIdeaStatus', () => {
1453
+ it('throws on invalid status', () => {
1454
+ assert.throws(() => {
1455
+ setIdeaStatus('/nonexistent', '001', 'invalid-status');
1456
+ }, (err) => {
1457
+ assert.ok(err.message.includes('Invalid status'));
1458
+ assert.ok(err.message.includes('pending'));
1459
+ assert.ok(err.message.includes('done'));
1460
+ assert.ok(err.message.includes('rejected'));
1461
+ assert.ok(err.message.includes('consolidated'));
1462
+ return true;
1463
+ });
1464
+ });
1465
+
1466
+ it('throws when idea not found', () => {
1467
+ const os = require('os');
1468
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dgs-test-'));
1469
+ assert.throws(() => {
1470
+ setIdeaStatus(tmpDir, '999', 'done');
1471
+ }, (err) => {
1472
+ assert.ok(err.message.includes('not found'));
1473
+ return true;
1474
+ });
1475
+ fs.rmSync(tmpDir, { recursive: true });
1476
+ });
1477
+ });
1478
+
1479
+ // ─── findIdeaFile flat-first scanning ───────────────────────────────────────
1480
+
1481
+ /**
1482
+ * Creates an idea file in the flat ideas/ directory (not a subdirectory).
1483
+ */
1484
+ function createFlatIdea(cwd, id, title, status) {
1485
+ const paddedId = String(id).padStart(3, '0');
1486
+ const slug = title.toLowerCase().replace(/\s+/g, '-');
1487
+ const filename = `${paddedId}-${slug}.md`;
1488
+ const content = `---\nid: ${id}\ntitle: "${title}"\nstatus: ${status}\n---\n\nIdea body.\n`;
1489
+ const dir = path.join(cwd, 'ideas');
1490
+ fs.mkdirSync(dir, { recursive: true });
1491
+ fs.writeFileSync(path.join(dir, filename), content, 'utf-8');
1492
+ return filename;
1493
+ }
1494
+
1495
+ describe('findIdeaFile flat-first scanning', () => {
1496
+ let tmpDir;
1497
+
1498
+ beforeEach(() => {
1499
+ tmpDir = createTempDir('ideas-flat-test-');
1500
+ tmpDir = fs.realpathSync(tmpDir);
1501
+ initGitRepo(tmpDir);
1502
+ });
1503
+
1504
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
1505
+
1506
+ it('finds idea in flat ideas/ directory', () => {
1507
+ createFlatIdea(tmpDir, 1, 'Test Flat', 'done');
1508
+ const idea = findIdeaFile(tmpDir, '1');
1509
+ assert.ok(idea, 'findIdeaFile should return a result');
1510
+ assert.strictEqual(idea.status, 'done');
1511
+ assert.strictEqual(idea.state, 'done');
1512
+ assert.ok(idea.path.endsWith('ideas/001-test-flat.md'), 'Should be in flat ideas/ directory');
1513
+ assert.ok(!idea.path.includes('ideas/done/'), 'Should NOT be in a subdirectory');
1514
+ });
1515
+
1516
+ it('falls back to legacy subdirectory with warning', () => {
1517
+ createIdeaInState(tmpDir, 'pending', 2, 'Legacy Idea');
1518
+ // Capture stderr
1519
+ const origWrite = process.stderr.write;
1520
+ let stderrOutput = '';
1521
+ process.stderr.write = (msg) => { stderrOutput += msg; };
1522
+ try {
1523
+ const idea = findIdeaFile(tmpDir, '2');
1524
+ assert.ok(idea, 'findIdeaFile should return a result');
1525
+ assert.ok(stderrOutput.includes('[DGS] Warning:'), 'Should emit legacy warning');
1526
+ assert.ok(stderrOutput.includes('legacy'), 'Warning should mention legacy');
1527
+ } finally {
1528
+ process.stderr.write = origWrite;
1529
+ }
1530
+ });
1531
+
1532
+ it('warns when frontmatter status disagrees with directory', () => {
1533
+ // Create idea in done/ directory but with frontmatter status: pending
1534
+ const dir = path.join(tmpDir, 'ideas', 'done');
1535
+ fs.mkdirSync(dir, { recursive: true });
1536
+ fs.writeFileSync(path.join(dir, '003-mismatch.md'), '---\nid: 3\ntitle: "Mismatch"\nstatus: pending\n---\n\nBody.\n', 'utf-8');
1537
+ const origWrite = process.stderr.write;
1538
+ let stderrOutput = '';
1539
+ process.stderr.write = (msg) => { stderrOutput += msg; };
1540
+ try {
1541
+ const idea = findIdeaFile(tmpDir, '3');
1542
+ assert.ok(idea, 'findIdeaFile should return a result');
1543
+ assert.strictEqual(idea.status, 'pending', 'Frontmatter should win');
1544
+ assert.ok(stderrOutput.includes('disagrees with directory'), 'Should warn about disagreement');
1545
+ } finally {
1546
+ process.stderr.write = origWrite;
1547
+ }
1548
+ });
1549
+
1550
+ it('prefers flat directory over legacy subdirectory', () => {
1551
+ // Create in BOTH flat and legacy
1552
+ createFlatIdea(tmpDir, 4, 'Both Locations', 'done');
1553
+ createIdeaInState(tmpDir, 'pending', 4, 'Both Locations');
1554
+ const idea = findIdeaFile(tmpDir, '4');
1555
+ assert.ok(idea, 'findIdeaFile should return a result');
1556
+ assert.ok(idea.path.endsWith('ideas/004-both-locations.md'), 'Flat should win');
1557
+ assert.strictEqual(idea.status, 'done');
1558
+ });
1559
+
1560
+ it('returns status and state fields', () => {
1561
+ createFlatIdea(tmpDir, 5, 'Both Fields', 'rejected');
1562
+ const idea = findIdeaFile(tmpDir, '5');
1563
+ assert.ok(idea, 'findIdeaFile should return a result');
1564
+ assert.strictEqual(idea.status, 'rejected');
1565
+ assert.strictEqual(idea.state, 'rejected');
1566
+ });
1567
+ });
1568
+
1569
+ // ─── cmdIdeasList frontmatter status grouping ───────────────────────────────
1570
+
1571
+ /**
1572
+ * Capture JSON output from cmdIdeasList.
1573
+ */
1574
+ function captureListOutput(fn) {
1575
+ const logs = [];
1576
+ const origLog = console.log;
1577
+ console.log = (...args) => logs.push(args.join(' '));
1578
+ try {
1579
+ fn();
1580
+ } finally {
1581
+ console.log = origLog;
1582
+ }
1583
+ return logs.length ? JSON.parse(logs[0]) : null;
1584
+ }
1585
+
1586
+ describe('cmdIdeasList frontmatter status grouping', () => {
1587
+ let tmpDir;
1588
+
1589
+ beforeEach(() => {
1590
+ tmpDir = createTempDir('ideas-list-flat-test-');
1591
+ tmpDir = fs.realpathSync(tmpDir);
1592
+ initGitRepo(tmpDir);
1593
+ });
1594
+
1595
+ afterEach(() => { resetPaths(); cleanupDir(tmpDir); });
1596
+
1597
+ it('groups flat ideas by frontmatter status', () => {
1598
+ createFlatIdea(tmpDir, 1, 'Pending Idea', 'pending');
1599
+ createFlatIdea(tmpDir, 2, 'Done Idea', 'done');
1600
+ const result = captureListOutput(() => {
1601
+ cmdIdeasList(tmpDir, { state: null, tag: null, include_orphan_check: false, include_consolidated: false, show_all: true }, true);
1602
+ });
1603
+ assert.ok(result, 'Should return output');
1604
+ assert.strictEqual(result.ideas.length, 2);
1605
+ const pendingIdea = result.ideas.find(i => i.id === 1);
1606
+ const doneIdea = result.ideas.find(i => i.id === 2);
1607
+ assert.strictEqual(pendingIdea.status, 'pending');
1608
+ assert.strictEqual(pendingIdea.state, 'pending');
1609
+ assert.strictEqual(doneIdea.status, 'done');
1610
+ assert.strictEqual(doneIdea.state, 'done');
1611
+ });
1612
+
1613
+ it('handles mixed layout without duplicates', () => {
1614
+ createFlatIdea(tmpDir, 1, 'Flat One', 'pending');
1615
+ createIdeaInState(tmpDir, 'done', 2, 'Legacy Two');
1616
+ // Suppress stderr warnings
1617
+ const origWrite = process.stderr.write;
1618
+ process.stderr.write = () => {};
1619
+ try {
1620
+ const result = captureListOutput(() => {
1621
+ cmdIdeasList(tmpDir, { state: null, tag: null, include_orphan_check: false, include_consolidated: false, show_all: true }, true);
1622
+ });
1623
+ assert.ok(result, 'Should return output');
1624
+ assert.strictEqual(result.ideas.length, 2, 'No duplicates');
1625
+ assert.strictEqual(result.counts.total, 2);
1626
+ } finally {
1627
+ process.stderr.write = origWrite;
1628
+ }
1629
+ });
1630
+
1631
+ it('filters by --state option using frontmatter status', () => {
1632
+ createFlatIdea(tmpDir, 1, 'Done One', 'done');
1633
+ createFlatIdea(tmpDir, 2, 'Pending One', 'pending');
1634
+ const doneResult = captureListOutput(() => {
1635
+ cmdIdeasList(tmpDir, { state: 'done', tag: null, include_orphan_check: false, include_consolidated: false, show_all: false }, true);
1636
+ });
1637
+ assert.strictEqual(doneResult.ideas.length, 1);
1638
+ assert.strictEqual(doneResult.ideas[0].status, 'done');
1639
+
1640
+ const pendingResult = captureListOutput(() => {
1641
+ cmdIdeasList(tmpDir, { state: 'pending', tag: null, include_orphan_check: false, include_consolidated: false, show_all: false }, true);
1642
+ });
1643
+ assert.strictEqual(pendingResult.ideas.length, 1);
1644
+ assert.strictEqual(pendingResult.ideas[0].status, 'pending');
1645
+ });
1646
+
1647
+ it('emits warning for legacy ideas in list', () => {
1648
+ createIdeaInState(tmpDir, 'pending', 1, 'Legacy Listed');
1649
+ const origWrite = process.stderr.write;
1650
+ let stderrOutput = '';
1651
+ process.stderr.write = (msg) => { stderrOutput += msg; };
1652
+ try {
1653
+ captureListOutput(() => {
1654
+ cmdIdeasList(tmpDir, { state: null, tag: null, include_orphan_check: false, include_consolidated: false, show_all: false }, true);
1655
+ });
1656
+ assert.ok(stderrOutput.includes('[DGS] Warning:'), 'Should emit legacy warning in list');
1657
+ } finally {
1658
+ process.stderr.write = origWrite;
1659
+ }
1660
+ });
1661
+ });