@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
@@ -33,6 +33,7 @@ const { execSync } = require('child_process');
33
33
  const { output, error, getProjectRoot } = require('./core.cjs');
34
34
  const { getPlanningRoot } = require('./paths.cjs');
35
35
  const { requireGitIdentity, formatAuthorString } = require('./identity.cjs');
36
+ const { extractFrontmatter, spliceFrontmatter, reconstructFrontmatter } = require('./frontmatter.cjs');
36
37
 
37
38
  // ─── Constants ──────────────────────────────────────────────────────────────
38
39
 
@@ -48,6 +49,7 @@ const KNOWN_COMMANDS = new Set([
48
49
  'plan-milestone-gaps',
49
50
  'discuss-phase',
50
51
  'research-phase',
52
+ 'validate-phase',
51
53
  'verify-phase',
52
54
  ]);
53
55
 
@@ -92,6 +94,8 @@ const STATUS_TO_MARKER = {
92
94
  'pending': ' ',
93
95
  };
94
96
 
97
+ const JOB_STATUSES = ['pending', 'in-progress', 'completed', 'failed', 'rolled_back'];
98
+
95
99
  // ─── Internal Helpers ───────────────────────────────────────────────────────
96
100
 
97
101
  /**
@@ -374,13 +378,31 @@ function moveJobFile(filePath, targetDir) {
374
378
  }
375
379
 
376
380
  /**
377
- * Locate a job file by milestone version, checking directories in priority order.
381
+ * Read status from a job file: YAML frontmatter first, then bold-key header fallback.
378
382
  *
379
- * Search order: in-progress/ (resume case), pending/ (new job), completed/ (inspection).
383
+ * @param {string} content - File content
384
+ * @returns {string|null} Status value or null
385
+ */
386
+ function _readJobStatus(content) {
387
+ // Try YAML frontmatter first
388
+ const fmMatch = content.match(/^---\n([\s\S]+?)\n---/);
389
+ if (fmMatch) {
390
+ const statusMatch = fmMatch[1].match(/^status:\s*(.+)$/m);
391
+ if (statusMatch) return statusMatch[1].trim();
392
+ }
393
+ // Fall back to bold-key header: **Status:** value
394
+ const headerMatch = content.match(/\*\*Status:\*\*\s*(.+)/);
395
+ if (headerMatch) return headerMatch[1].trim().toLowerCase();
396
+ return null;
397
+ }
398
+
399
+ /**
400
+ * Locate a job file by milestone version.
401
+ * Scans flat jobs/ directory first (frontmatter status), then falls back to legacy subdirectories.
380
402
  *
381
403
  * @param {string} cwd - Working directory
382
404
  * @param {string} version - Milestone version (e.g., "v6.0" or "6.0")
383
- * @returns {{ found: boolean, path?: string, directory?: string }}
405
+ * @returns {{ found: boolean, path?: string, directory?: string, status?: string }}
384
406
  */
385
407
  function findJobFile(cwd, version) {
386
408
  // Normalize version: ensure 'v' prefix
@@ -388,25 +410,56 @@ function findJobFile(cwd, version) {
388
410
  const fileName = `milestone-${normalized}.md`;
389
411
  const jobsDir = path.join(getPlanningRoot(cwd), 'jobs');
390
412
 
391
- // Search order: in-progress first (resume case), then pending, then completed (inspection)
413
+ // Flat-first scan: check jobs/ root directory
414
+ const flatPath = path.join(jobsDir, fileName);
415
+ if (fs.existsSync(flatPath)) {
416
+ const content = fs.readFileSync(flatPath, 'utf-8');
417
+ const status = _readJobStatus(content);
418
+ return { found: true, path: flatPath, directory: null, status: status || 'pending' };
419
+ }
420
+
421
+ // Legacy subdirectory scan
392
422
  for (const dir of ['in-progress', 'pending', 'completed']) {
393
423
  const filePath = path.join(jobsDir, dir, fileName);
394
424
  if (fs.existsSync(filePath)) {
395
- return { found: true, path: filePath, directory: dir };
425
+ // Legacy fallback warning
426
+ process.stderr.write(`[DGS] Warning: job '${fileName}' found in legacy ${dir}/ directory. Run migration to flatten.\n`);
427
+
428
+ // Read frontmatter status if available
429
+ const content = fs.readFileSync(filePath, 'utf-8');
430
+ const fmStatus = _readJobStatus(content);
431
+
432
+ if (fmStatus && fmStatus !== dir && fmStatus !== dir.replace(/-/g, '_')) {
433
+ process.stderr.write(`[DGS] Warning: job '${fileName}' frontmatter status '${fmStatus}' disagrees with directory '${dir}'. Frontmatter wins.\n`);
434
+ }
435
+
436
+ const effectiveStatus = fmStatus || dir;
437
+ return { found: true, path: filePath, directory: dir, status: effectiveStatus };
396
438
  }
397
439
  }
398
440
 
399
- // Search project subdirectories: projects/*/jobs/
441
+ // Search project subdirectories: projects/*/jobs/ (flat first, then legacy)
400
442
  const projectsDir = path.join(getPlanningRoot(cwd), 'projects');
401
443
  if (fs.existsSync(projectsDir)) {
402
444
  const projects = fs.readdirSync(projectsDir).filter(d =>
403
445
  fs.statSync(path.join(projectsDir, d)).isDirectory()
404
446
  );
405
447
  for (const project of projects) {
448
+ // Flat first
449
+ const projFlatPath = path.join(projectsDir, project, 'jobs', fileName);
450
+ if (fs.existsSync(projFlatPath)) {
451
+ const content = fs.readFileSync(projFlatPath, 'utf-8');
452
+ const status = _readJobStatus(content);
453
+ return { found: true, path: projFlatPath, directory: null, status: status || 'pending' };
454
+ }
455
+ // Legacy subdirectory
406
456
  for (const dir of ['in-progress', 'pending', 'completed']) {
407
457
  const filePath = path.join(projectsDir, project, 'jobs', dir, fileName);
408
458
  if (fs.existsSync(filePath)) {
409
- return { found: true, path: filePath, directory: dir };
459
+ process.stderr.write(`[DGS] Warning: job '${fileName}' found in legacy ${dir}/ directory. Run migration to flatten.\n`);
460
+ const content = fs.readFileSync(filePath, 'utf-8');
461
+ const fmStatus = _readJobStatus(content);
462
+ return { found: true, path: filePath, directory: dir, status: fmStatus || dir };
410
463
  }
411
464
  }
412
465
  }
@@ -628,6 +681,53 @@ function insertGapFixSection(filePath, afterStepIndex, cycleNumber, newPhases, v
628
681
  * @param {string} phasesJson - JSON string of phases array [{number, name}]
629
682
  * @param {boolean} raw - Raw output mode
630
683
  */
684
+ /**
685
+ * Set a job's status in both YAML frontmatter and bold-key header (internal helper — throws on error).
686
+ *
687
+ * Dual-write strategy: YAML frontmatter is added/updated as the source of truth,
688
+ * and the existing **Status:** markdown header is kept in sync for human readability.
689
+ *
690
+ * @param {string} cwd - Working directory
691
+ * @param {string} version - Milestone version (e.g., "v20.0")
692
+ * @param {string} status - Target status (must be in JOB_STATUSES)
693
+ * @returns {{ version: string, previous_status: string, status: string, path: string }}
694
+ * @throws {Error} If status is invalid or job not found
695
+ */
696
+ function setJobStatus(cwd, version, status) {
697
+ if (!JOB_STATUSES.includes(status)) {
698
+ throw new Error(`Invalid status: ${status}. Allowed: ${JOB_STATUSES.join(', ')}`);
699
+ }
700
+
701
+ const found = findJobFile(cwd, version);
702
+ if (!found.found) {
703
+ throw new Error(`Job not found: ${version}`);
704
+ }
705
+
706
+ let content = fs.readFileSync(found.path, 'utf-8');
707
+ const previousHeader = parseHeader(content);
708
+ const previousStatus = previousHeader.status;
709
+
710
+ // 1. Update bold-key markdown header (existing mechanism)
711
+ updateJobHeader(found.path, 'Status', status);
712
+
713
+ // 2. Update or add YAML frontmatter
714
+ content = fs.readFileSync(found.path, 'utf-8');
715
+ const fm = extractFrontmatter(content);
716
+ fm.status = status;
717
+
718
+ if (content.match(/^---\n/)) {
719
+ // Has frontmatter — splice it
720
+ content = spliceFrontmatter(content, fm);
721
+ } else {
722
+ // No frontmatter — add it at the very top
723
+ const yamlStr = reconstructFrontmatter({ status });
724
+ content = `---\n${yamlStr}\n---\n\n${content}`;
725
+ }
726
+ fs.writeFileSync(found.path, content, 'utf-8');
727
+
728
+ return { version, previous_status: previousStatus, status, path: found.path };
729
+ }
730
+
631
731
  function cmdJobsInsertGapFixSection(cwd, file, afterIndexStr, cycleStr, version, phasesJson, raw) {
632
732
  if (!file || afterIndexStr === undefined || cycleStr === undefined || !version || !phasesJson) {
633
733
  error('Usage: jobs insert-gap-fix-section <file> <after-index> <cycle> <version> <phases-json>');
@@ -956,9 +1056,11 @@ function generateMilestoneSteps(phases, options) {
956
1056
  steps.push({ command: 'map-codebase', args: `${num} --auto` });
957
1057
  steps.push({ command: 'plan-phase', args: `${num} --non-interactive` });
958
1058
  steps.push({ command: 'execute-phase', args: `${num} --non-interactive` });
1059
+ steps.push({ command: 'validate-phase', args: `${num} --scaffold` });
959
1060
  steps.push({ command: 'audit-phase', args: `${num}` });
960
1061
  } else if (NEEDS_EXECUTION.has(phase.disk_status)) {
961
1062
  steps.push({ command: 'execute-phase', args: `${num} --non-interactive` });
1063
+ steps.push({ command: 'validate-phase', args: `${num} --scaffold` });
962
1064
  steps.push({ command: 'audit-phase', args: `${num}` });
963
1065
  }
964
1066
  }
@@ -966,7 +1068,6 @@ function generateMilestoneSteps(phases, options) {
966
1068
  // Append audit + complete steps if check is enabled
967
1069
  if (check) {
968
1070
  steps.push({ command: 'audit-milestone', args: version });
969
- steps.push({ command: 'complete-milestone', args: version });
970
1071
  }
971
1072
 
972
1073
  return steps;
@@ -984,7 +1085,10 @@ function generateMilestoneSteps(phases, options) {
984
1085
  function buildJobFileContent(version, check, steps, createdBy) {
985
1086
  const created = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
986
1087
 
987
- let content = `# Milestone Job: ${version}\n\n`;
1088
+ // YAML frontmatter (source of truth for status)
1089
+ let content = `---\nstatus: pending\n---\n\n`;
1090
+
1091
+ content += `# Milestone Job: ${version}\n\n`;
988
1092
  content += `**Version:** ${version}\n`;
989
1093
  content += `**Created:** ${created}\n`;
990
1094
  if (createdBy) {
@@ -1219,11 +1323,11 @@ function cmdJobsCreateMilestone(cwd, version, check, raw) {
1219
1323
  // Build file content
1220
1324
  const content = buildJobFileContent(resolvedVersion, check, steps, createdBy);
1221
1325
 
1222
- // Write to pending directory
1223
- const pendingDir = path.join(getPlanningRoot(cwd), 'jobs', 'pending');
1224
- fs.mkdirSync(pendingDir, { recursive: true });
1326
+ // Write to flat jobs/ directory (status tracked in frontmatter)
1327
+ const jobsDir = path.join(getPlanningRoot(cwd), 'jobs');
1328
+ fs.mkdirSync(jobsDir, { recursive: true });
1225
1329
  const fileName = `milestone-${resolvedVersion}.md`;
1226
- const filePath = path.join(pendingDir, fileName);
1330
+ const filePath = path.join(jobsDir, fileName);
1227
1331
  fs.writeFileSync(filePath, content, 'utf-8');
1228
1332
 
1229
1333
  // Count phases that contribute at least one step
@@ -1234,7 +1338,7 @@ function cmdJobsCreateMilestone(cwd, version, check, raw) {
1234
1338
  const result = {
1235
1339
  created: true,
1236
1340
  version: resolvedVersion,
1237
- file: path.join(path.relative(cwd, getPlanningRoot(cwd)) || '.', 'jobs', 'pending', fileName),
1341
+ file: path.join(path.relative(cwd, getPlanningRoot(cwd)) || '.', 'jobs', fileName),
1238
1342
  step_count: steps.length,
1239
1343
  check,
1240
1344
  phase_count: phasesWithSteps.length,
@@ -1397,17 +1501,14 @@ function cancelJob(cwd, version) {
1397
1501
  }
1398
1502
  }
1399
1503
 
1400
- // Update Status header to pending
1401
- updateJobHeader(filePath, 'Status', 'pending');
1402
-
1403
- // Move to pending/
1404
- const pendingDir = path.join(getPlanningRoot(cwd), 'jobs', 'pending');
1405
- const moveResult = moveJobFile(filePath, pendingDir);
1504
+ // Set job status to pending via frontmatter edit (no file move)
1505
+ // setJobStatus handles both markdown header and frontmatter update
1506
+ setJobStatus(cwd, version, 'pending');
1406
1507
 
1407
1508
  return {
1408
1509
  cancelled: true,
1409
1510
  version: version.startsWith('v') ? version : 'v' + version,
1410
- path: moveResult.to,
1511
+ path: filePath,
1411
1512
  steps_reset: stepsReset,
1412
1513
  };
1413
1514
  }
@@ -1983,6 +2084,8 @@ module.exports = {
1983
2084
  insertJobSteps,
1984
2085
  buildGapFixSteps,
1985
2086
  buildPhaseGapFixSteps,
2087
+ JOB_STATUSES,
2088
+ setJobStatus,
1986
2089
  insertGapFixSection,
1987
2090
  insertPhaseGapFixSection,
1988
2091
  updatePhaseFixCycleHeader,