@ktpartners/dgs-platform 2.9.0 → 3.3.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.
Files changed (166) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/README.md +34 -2
  3. package/agents/dgs-executor.md +124 -3
  4. package/agents/dgs-idea-researcher.md +447 -0
  5. package/agents/dgs-plan-checker.md +61 -3
  6. package/agents/dgs-planner.md +51 -8
  7. package/bin/install.js +44 -0
  8. package/commands/dgs/abandon-quick.md +28 -0
  9. package/commands/dgs/add-tests.md +2 -2
  10. package/commands/dgs/audit-milestone.md +4 -3
  11. package/commands/dgs/capture-principle.md +11 -11
  12. package/commands/dgs/cleanup.md +2 -2
  13. package/commands/dgs/complete-milestone.md +11 -11
  14. package/commands/dgs/complete-quick.md +28 -0
  15. package/commands/dgs/create-milestone-job.md +2 -2
  16. package/commands/dgs/debug.md +3 -3
  17. package/commands/dgs/develop-idea.md +1 -1
  18. package/commands/dgs/diff-report.md +124 -0
  19. package/commands/dgs/fast.md +3 -1
  20. package/commands/dgs/health.md +1 -1
  21. package/commands/dgs/map-codebase.md +6 -6
  22. package/commands/dgs/new-milestone.md +5 -5
  23. package/commands/dgs/new-project.md +8 -21
  24. package/commands/dgs/package-scan.md +43 -0
  25. package/commands/dgs/plan-milestone-gaps.md +1 -1
  26. package/commands/dgs/progress.md +3 -3
  27. package/commands/dgs/quick-abandon.md +8 -0
  28. package/commands/dgs/quick-complete.md +8 -0
  29. package/commands/dgs/quick.md +10 -3
  30. package/commands/dgs/research-idea.md +3 -2
  31. package/commands/dgs/research-phase.md +3 -3
  32. package/commands/dgs/switch-project.md +14 -1
  33. package/commands/dgs/write-spec.md +3 -3
  34. package/deliver-great-systems/bin/dgs-tools.cjs +401 -32
  35. package/deliver-great-systems/bin/lib/audit-tolerance.cjs +77 -0
  36. package/deliver-great-systems/bin/lib/audit-tolerance.test.cjs +101 -0
  37. package/deliver-great-systems/bin/lib/commands.cjs +626 -46
  38. package/deliver-great-systems/bin/lib/commands.test.cjs +451 -0
  39. package/deliver-great-systems/bin/lib/commit-verify.test.cjs +236 -0
  40. package/deliver-great-systems/bin/lib/config.cjs +80 -6
  41. package/deliver-great-systems/bin/lib/config.test.cjs +309 -0
  42. package/deliver-great-systems/bin/lib/context.cjs +120 -0
  43. package/deliver-great-systems/bin/lib/core.cjs +35 -14
  44. package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
  45. package/deliver-great-systems/bin/lib/execution.cjs +49 -17
  46. package/deliver-great-systems/bin/lib/fast-routing.cjs +199 -0
  47. package/deliver-great-systems/bin/lib/fast-routing.test.cjs +108 -0
  48. package/deliver-great-systems/bin/lib/final-commit-precondition.test.cjs +87 -0
  49. package/deliver-great-systems/bin/lib/fixtures/package-scan/bundler-audit-gemfile.json +21 -0
  50. package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-expected.md +186 -0
  51. package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-runresult.json +235 -0
  52. package/deliver-great-systems/bin/lib/fixtures/package-scan/govulncheck-import.json +3 -0
  53. package/deliver-great-systems/bin/lib/fixtures/package-scan/npm-audit-v10.json +37 -0
  54. package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-clean.json +3 -0
  55. package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-vulns.json +77 -0
  56. package/deliver-great-systems/bin/lib/fixtures/package-scan/pip-audit-requirements.json +28 -0
  57. package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-lodash.json +30 -0
  58. package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-workspaces.json +55 -0
  59. package/deliver-great-systems/bin/lib/flat-migration.test.cjs +396 -0
  60. package/deliver-great-systems/bin/lib/frontmatter.cjs +1 -1
  61. package/deliver-great-systems/bin/lib/governance.cjs +211 -0
  62. package/deliver-great-systems/bin/lib/governance.test.cjs +339 -0
  63. package/deliver-great-systems/bin/lib/health-untracked-phase.test.cjs +269 -0
  64. package/deliver-great-systems/bin/lib/ideas.cjs +206 -91
  65. package/deliver-great-systems/bin/lib/ideas.test.cjs +244 -1
  66. package/deliver-great-systems/bin/lib/init.cjs +357 -61
  67. package/deliver-great-systems/bin/lib/init.test.cjs +625 -8
  68. package/deliver-great-systems/bin/lib/jobs.cjs +131 -25
  69. package/deliver-great-systems/bin/lib/jobs.test.cjs +193 -74
  70. package/deliver-great-systems/bin/lib/migration.cjs +409 -1
  71. package/deliver-great-systems/bin/lib/migration.test.cjs +158 -1
  72. package/deliver-great-systems/bin/lib/milestone.cjs +154 -31
  73. package/deliver-great-systems/bin/lib/milestone.test.cjs +203 -0
  74. package/deliver-great-systems/bin/lib/package-adapters.cjs +530 -0
  75. package/deliver-great-systems/bin/lib/package-adapters.test.cjs +618 -0
  76. package/deliver-great-systems/bin/lib/package-ecosystems.cjs +350 -0
  77. package/deliver-great-systems/bin/lib/package-ecosystems.test.cjs +348 -0
  78. package/deliver-great-systems/bin/lib/package-runner.cjs +199 -0
  79. package/deliver-great-systems/bin/lib/package-runner.test.cjs +198 -0
  80. package/deliver-great-systems/bin/lib/package-scan-provenance.cjs +56 -0
  81. package/deliver-great-systems/bin/lib/package-scan-provenance.test.cjs +103 -0
  82. package/deliver-great-systems/bin/lib/package-scan-report.cjs +1140 -0
  83. package/deliver-great-systems/bin/lib/package-scan-report.test.cjs +1963 -0
  84. package/deliver-great-systems/bin/lib/package-scan-skill.cjs +96 -0
  85. package/deliver-great-systems/bin/lib/package-scan-skill.test.cjs +136 -0
  86. package/deliver-great-systems/bin/lib/package-scan.cjs +919 -0
  87. package/deliver-great-systems/bin/lib/package-scan.test.cjs +2147 -0
  88. package/deliver-great-systems/bin/lib/phase.cjs +146 -3
  89. package/deliver-great-systems/bin/lib/phase.test.cjs +420 -0
  90. package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
  91. package/deliver-great-systems/bin/lib/projects.cjs +65 -10
  92. package/deliver-great-systems/bin/lib/projects.test.cjs +198 -2
  93. package/deliver-great-systems/bin/lib/quick.cjs +739 -0
  94. package/deliver-great-systems/bin/lib/quick.test.cjs +730 -0
  95. package/deliver-great-systems/bin/lib/repos.cjs +37 -13
  96. package/deliver-great-systems/bin/lib/review.cjs +1821 -0
  97. package/deliver-great-systems/bin/lib/roadmap.cjs +34 -13
  98. package/deliver-great-systems/bin/lib/specs.cjs +3 -81
  99. package/deliver-great-systems/bin/lib/state-transition-gate.test.cjs +160 -0
  100. package/deliver-great-systems/bin/lib/state.cjs +147 -55
  101. package/deliver-great-systems/bin/lib/summary-frontmatter.cjs +54 -0
  102. package/deliver-great-systems/bin/lib/summary-frontmatter.test.cjs +78 -0
  103. package/deliver-great-systems/bin/lib/sweep-scope.test.cjs +263 -0
  104. package/deliver-great-systems/bin/lib/sync.cjs +75 -0
  105. package/deliver-great-systems/bin/lib/verify.cjs +198 -7
  106. package/deliver-great-systems/bin/lib/verify.test.cjs +82 -0
  107. package/deliver-great-systems/bin/lib/wave-0-template-rename.test.cjs +40 -0
  108. package/deliver-great-systems/bin/lib/worktrees.cjs +790 -0
  109. package/deliver-great-systems/bin/lib/worktrees.test.cjs +963 -0
  110. package/deliver-great-systems/references/agent-step-reliability.md +60 -0
  111. package/deliver-great-systems/references/conflict-resolution.md +4 -0
  112. package/deliver-great-systems/references/context-tiers.md +4 -0
  113. package/deliver-great-systems/references/package-scan-config.md +151 -0
  114. package/deliver-great-systems/references/questioning.md +0 -30
  115. package/deliver-great-systems/references/spec-review-loop.md +1 -2
  116. package/deliver-great-systems/references/workflow-conventions.md +29 -0
  117. package/deliver-great-systems/skills/dgs-tests/package-scan.md +44 -0
  118. package/deliver-great-systems/templates/REVIEW.md +35 -0
  119. package/deliver-great-systems/templates/VALIDATION.md +1 -1
  120. package/deliver-great-systems/templates/claude-md.md +27 -0
  121. package/deliver-great-systems/templates/package-scan-report.md +108 -0
  122. package/deliver-great-systems/templates/project.md +6 -170
  123. package/deliver-great-systems/templates/summary.md +3 -1
  124. package/deliver-great-systems/workflows/abandon-quick.md +89 -0
  125. package/deliver-great-systems/workflows/add-idea.md +3 -3
  126. package/deliver-great-systems/workflows/add-phase.md +5 -0
  127. package/deliver-great-systems/workflows/add-tests.md +14 -0
  128. package/deliver-great-systems/workflows/add-todo.md +1 -0
  129. package/deliver-great-systems/workflows/approve-spec.md +25 -4
  130. package/deliver-great-systems/workflows/audit-milestone.md +66 -10
  131. package/deliver-great-systems/workflows/audit-phase.md +15 -5
  132. package/deliver-great-systems/workflows/cancel-job.md +2 -2
  133. package/deliver-great-systems/workflows/check-todos.md +2 -3
  134. package/deliver-great-systems/workflows/codereview.md +103 -9
  135. package/deliver-great-systems/workflows/complete-milestone.md +218 -24
  136. package/deliver-great-systems/workflows/complete-quick.md +106 -0
  137. package/deliver-great-systems/workflows/consolidate-ideas.md +1 -1
  138. package/deliver-great-systems/workflows/create-milestone-job.md +4 -4
  139. package/deliver-great-systems/workflows/develop-idea.md +11 -11
  140. package/deliver-great-systems/workflows/diagnose-issues.md +14 -0
  141. package/deliver-great-systems/workflows/discuss-idea.md +1 -1
  142. package/deliver-great-systems/workflows/discuss-phase.md +3 -2
  143. package/deliver-great-systems/workflows/execute-phase.md +209 -33
  144. package/deliver-great-systems/workflows/execute-plan.md +22 -22
  145. package/deliver-great-systems/workflows/help.md +53 -20
  146. package/deliver-great-systems/workflows/import-spec.md +65 -7
  147. package/deliver-great-systems/workflows/init-product.md +45 -167
  148. package/deliver-great-systems/workflows/new-milestone.md +140 -33
  149. package/deliver-great-systems/workflows/new-project.md +60 -331
  150. package/deliver-great-systems/workflows/package-scan.md +59 -0
  151. package/deliver-great-systems/workflows/plan-phase.md +79 -1
  152. package/deliver-great-systems/workflows/progress-all.md +133 -0
  153. package/deliver-great-systems/workflows/quick-abandon.md +89 -0
  154. package/deliver-great-systems/workflows/quick-complete.md +106 -0
  155. package/deliver-great-systems/workflows/quick.md +328 -26
  156. package/deliver-great-systems/workflows/refine-spec.md +1 -1
  157. package/deliver-great-systems/workflows/research-idea.md +77 -139
  158. package/deliver-great-systems/workflows/resume-project.md +2 -2
  159. package/deliver-great-systems/workflows/run-job.md +29 -43
  160. package/deliver-great-systems/workflows/settings.md +13 -77
  161. package/deliver-great-systems/workflows/validate-phase.md +39 -1
  162. package/deliver-great-systems/workflows/verify-work.md +14 -0
  163. package/deliver-great-systems/workflows/write-spec.md +11 -13
  164. package/hooks/dist/dgs-enforce-discipline.js +196 -0
  165. package/package.json +1 -1
  166. package/scripts/build-hooks.js +1 -0
@@ -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
  /**
@@ -339,6 +343,9 @@ function updateJobStep(filePath, stepIndex, status, options) {
339
343
  }
340
344
 
341
345
  /**
346
+ * @deprecated Use setJobStatus() for state transitions instead of physical file moves.
347
+ * Kept for backward compatibility with legacy directory-based job layouts.
348
+ *
342
349
  * Move a job file from its current location to a target directory.
343
350
  *
344
351
  * @param {string} filePath - Absolute path to the job file
@@ -374,13 +381,31 @@ function moveJobFile(filePath, targetDir) {
374
381
  }
375
382
 
376
383
  /**
377
- * Locate a job file by milestone version, checking directories in priority order.
384
+ * Read status from a job file: YAML frontmatter first, then bold-key header fallback.
378
385
  *
379
- * Search order: in-progress/ (resume case), pending/ (new job), completed/ (inspection).
386
+ * @param {string} content - File content
387
+ * @returns {string|null} Status value or null
388
+ */
389
+ function _readJobStatus(content) {
390
+ // Try YAML frontmatter first
391
+ const fmMatch = content.match(/^---\n([\s\S]+?)\n---/);
392
+ if (fmMatch) {
393
+ const statusMatch = fmMatch[1].match(/^status:\s*(.+)$/m);
394
+ if (statusMatch) return statusMatch[1].trim();
395
+ }
396
+ // Fall back to bold-key header: **Status:** value
397
+ const headerMatch = content.match(/\*\*Status:\*\*\s*(.+)/);
398
+ if (headerMatch) return headerMatch[1].trim().toLowerCase();
399
+ return null;
400
+ }
401
+
402
+ /**
403
+ * Locate a job file by milestone version.
404
+ * Scans flat jobs/ directory first (frontmatter status), then falls back to legacy subdirectories.
380
405
  *
381
406
  * @param {string} cwd - Working directory
382
407
  * @param {string} version - Milestone version (e.g., "v6.0" or "6.0")
383
- * @returns {{ found: boolean, path?: string, directory?: string }}
408
+ * @returns {{ found: boolean, path?: string, directory?: string, status?: string }}
384
409
  */
385
410
  function findJobFile(cwd, version) {
386
411
  // Normalize version: ensure 'v' prefix
@@ -388,25 +413,56 @@ function findJobFile(cwd, version) {
388
413
  const fileName = `milestone-${normalized}.md`;
389
414
  const jobsDir = path.join(getPlanningRoot(cwd), 'jobs');
390
415
 
391
- // Search order: in-progress first (resume case), then pending, then completed (inspection)
416
+ // Flat-first scan: check jobs/ root directory
417
+ const flatPath = path.join(jobsDir, fileName);
418
+ if (fs.existsSync(flatPath)) {
419
+ const content = fs.readFileSync(flatPath, 'utf-8');
420
+ const status = _readJobStatus(content);
421
+ return { found: true, path: flatPath, directory: null, status: status || 'pending' };
422
+ }
423
+
424
+ // Legacy subdirectory scan
392
425
  for (const dir of ['in-progress', 'pending', 'completed']) {
393
426
  const filePath = path.join(jobsDir, dir, fileName);
394
427
  if (fs.existsSync(filePath)) {
395
- return { found: true, path: filePath, directory: dir };
428
+ // Legacy fallback warning
429
+ process.stderr.write(`[DGS] Warning: job '${fileName}' found in legacy ${dir}/ directory. Run migration to flatten.\n`);
430
+
431
+ // Read frontmatter status if available
432
+ const content = fs.readFileSync(filePath, 'utf-8');
433
+ const fmStatus = _readJobStatus(content);
434
+
435
+ if (fmStatus && fmStatus !== dir && fmStatus !== dir.replace(/-/g, '_')) {
436
+ process.stderr.write(`[DGS] Warning: job '${fileName}' frontmatter status '${fmStatus}' disagrees with directory '${dir}'. Frontmatter wins.\n`);
437
+ }
438
+
439
+ const effectiveStatus = fmStatus || dir;
440
+ return { found: true, path: filePath, directory: dir, status: effectiveStatus };
396
441
  }
397
442
  }
398
443
 
399
- // Search project subdirectories: projects/*/jobs/
444
+ // Search project subdirectories: projects/*/jobs/ (flat first, then legacy)
400
445
  const projectsDir = path.join(getPlanningRoot(cwd), 'projects');
401
446
  if (fs.existsSync(projectsDir)) {
402
447
  const projects = fs.readdirSync(projectsDir).filter(d =>
403
448
  fs.statSync(path.join(projectsDir, d)).isDirectory()
404
449
  );
405
450
  for (const project of projects) {
451
+ // Flat first
452
+ const projFlatPath = path.join(projectsDir, project, 'jobs', fileName);
453
+ if (fs.existsSync(projFlatPath)) {
454
+ const content = fs.readFileSync(projFlatPath, 'utf-8');
455
+ const status = _readJobStatus(content);
456
+ return { found: true, path: projFlatPath, directory: null, status: status || 'pending' };
457
+ }
458
+ // Legacy subdirectory
406
459
  for (const dir of ['in-progress', 'pending', 'completed']) {
407
460
  const filePath = path.join(projectsDir, project, 'jobs', dir, fileName);
408
461
  if (fs.existsSync(filePath)) {
409
- return { found: true, path: filePath, directory: dir };
462
+ process.stderr.write(`[DGS] Warning: job '${fileName}' found in legacy ${dir}/ directory. Run migration to flatten.\n`);
463
+ const content = fs.readFileSync(filePath, 'utf-8');
464
+ const fmStatus = _readJobStatus(content);
465
+ return { found: true, path: filePath, directory: dir, status: fmStatus || dir };
410
466
  }
411
467
  }
412
468
  }
@@ -628,6 +684,53 @@ function insertGapFixSection(filePath, afterStepIndex, cycleNumber, newPhases, v
628
684
  * @param {string} phasesJson - JSON string of phases array [{number, name}]
629
685
  * @param {boolean} raw - Raw output mode
630
686
  */
687
+ /**
688
+ * Set a job's status in both YAML frontmatter and bold-key header (internal helper — throws on error).
689
+ *
690
+ * Dual-write strategy: YAML frontmatter is added/updated as the source of truth,
691
+ * and the existing **Status:** markdown header is kept in sync for human readability.
692
+ *
693
+ * @param {string} cwd - Working directory
694
+ * @param {string} version - Milestone version (e.g., "v20.0")
695
+ * @param {string} status - Target status (must be in JOB_STATUSES)
696
+ * @returns {{ version: string, previous_status: string, status: string, path: string }}
697
+ * @throws {Error} If status is invalid or job not found
698
+ */
699
+ function setJobStatus(cwd, version, status) {
700
+ if (!JOB_STATUSES.includes(status)) {
701
+ throw new Error(`Invalid status: ${status}. Allowed: ${JOB_STATUSES.join(', ')}`);
702
+ }
703
+
704
+ const found = findJobFile(cwd, version);
705
+ if (!found.found) {
706
+ throw new Error(`Job not found: ${version}`);
707
+ }
708
+
709
+ let content = fs.readFileSync(found.path, 'utf-8');
710
+ const previousHeader = parseHeader(content);
711
+ const previousStatus = previousHeader.status;
712
+
713
+ // 1. Update bold-key markdown header (existing mechanism)
714
+ updateJobHeader(found.path, 'Status', status);
715
+
716
+ // 2. Update or add YAML frontmatter
717
+ content = fs.readFileSync(found.path, 'utf-8');
718
+ const fm = extractFrontmatter(content);
719
+ fm.status = status;
720
+
721
+ if (content.match(/^---\n/)) {
722
+ // Has frontmatter — splice it
723
+ content = spliceFrontmatter(content, fm);
724
+ } else {
725
+ // No frontmatter — add it at the very top
726
+ const yamlStr = reconstructFrontmatter({ status });
727
+ content = `---\n${yamlStr}\n---\n\n${content}`;
728
+ }
729
+ fs.writeFileSync(found.path, content, 'utf-8');
730
+
731
+ return { version, previous_status: previousStatus, status, path: found.path };
732
+ }
733
+
631
734
  function cmdJobsInsertGapFixSection(cwd, file, afterIndexStr, cycleStr, version, phasesJson, raw) {
632
735
  if (!file || afterIndexStr === undefined || cycleStr === undefined || !version || !phasesJson) {
633
736
  error('Usage: jobs insert-gap-fix-section <file> <after-index> <cycle> <version> <phases-json>');
@@ -956,9 +1059,11 @@ function generateMilestoneSteps(phases, options) {
956
1059
  steps.push({ command: 'map-codebase', args: `${num} --auto` });
957
1060
  steps.push({ command: 'plan-phase', args: `${num} --non-interactive` });
958
1061
  steps.push({ command: 'execute-phase', args: `${num} --non-interactive` });
1062
+ steps.push({ command: 'validate-phase', args: `${num} --scaffold` });
959
1063
  steps.push({ command: 'audit-phase', args: `${num}` });
960
1064
  } else if (NEEDS_EXECUTION.has(phase.disk_status)) {
961
1065
  steps.push({ command: 'execute-phase', args: `${num} --non-interactive` });
1066
+ steps.push({ command: 'validate-phase', args: `${num} --scaffold` });
962
1067
  steps.push({ command: 'audit-phase', args: `${num}` });
963
1068
  }
964
1069
  }
@@ -966,7 +1071,6 @@ function generateMilestoneSteps(phases, options) {
966
1071
  // Append audit + complete steps if check is enabled
967
1072
  if (check) {
968
1073
  steps.push({ command: 'audit-milestone', args: version });
969
- steps.push({ command: 'complete-milestone', args: version });
970
1074
  }
971
1075
 
972
1076
  return steps;
@@ -984,7 +1088,10 @@ function generateMilestoneSteps(phases, options) {
984
1088
  function buildJobFileContent(version, check, steps, createdBy) {
985
1089
  const created = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
986
1090
 
987
- let content = `# Milestone Job: ${version}\n\n`;
1091
+ // YAML frontmatter (source of truth for status)
1092
+ let content = `---\nstatus: pending\n---\n\n`;
1093
+
1094
+ content += `# Milestone Job: ${version}\n\n`;
988
1095
  content += `**Version:** ${version}\n`;
989
1096
  content += `**Created:** ${created}\n`;
990
1097
  if (createdBy) {
@@ -1219,11 +1326,11 @@ function cmdJobsCreateMilestone(cwd, version, check, raw) {
1219
1326
  // Build file content
1220
1327
  const content = buildJobFileContent(resolvedVersion, check, steps, createdBy);
1221
1328
 
1222
- // Write to pending directory
1223
- const pendingDir = path.join(getPlanningRoot(cwd), 'jobs', 'pending');
1224
- fs.mkdirSync(pendingDir, { recursive: true });
1329
+ // Write to flat jobs/ directory (status tracked in frontmatter)
1330
+ const jobsDir = path.join(getPlanningRoot(cwd), 'jobs');
1331
+ fs.mkdirSync(jobsDir, { recursive: true });
1225
1332
  const fileName = `milestone-${resolvedVersion}.md`;
1226
- const filePath = path.join(pendingDir, fileName);
1333
+ const filePath = path.join(jobsDir, fileName);
1227
1334
  fs.writeFileSync(filePath, content, 'utf-8');
1228
1335
 
1229
1336
  // Count phases that contribute at least one step
@@ -1234,7 +1341,7 @@ function cmdJobsCreateMilestone(cwd, version, check, raw) {
1234
1341
  const result = {
1235
1342
  created: true,
1236
1343
  version: resolvedVersion,
1237
- file: path.join(path.relative(cwd, getPlanningRoot(cwd)) || '.', 'jobs', 'pending', fileName),
1344
+ file: path.join(path.relative(cwd, getPlanningRoot(cwd)) || '.', 'jobs', fileName),
1238
1345
  step_count: steps.length,
1239
1346
  check,
1240
1347
  phase_count: phasesWithSteps.length,
@@ -1357,7 +1464,7 @@ function listJobs(cwd) {
1357
1464
  }
1358
1465
 
1359
1466
  /**
1360
- * Cancel an in-progress job: reset [>] steps to [ ], set Status to pending, move to pending/.
1467
+ * Cancel an in-progress job: reset [>] steps to [ ], set Status to pending.
1361
1468
  *
1362
1469
  * @param {string} cwd - Working directory
1363
1470
  * @param {string} version - Milestone version (e.g., "v6.0")
@@ -1368,7 +1475,7 @@ function cancelJob(cwd, version) {
1368
1475
  if (!found.found) {
1369
1476
  return { cancelled: false, reason: 'not_found' };
1370
1477
  }
1371
- if (found.directory !== 'in-progress') {
1478
+ if (found.status !== 'in-progress') {
1372
1479
  return { cancelled: false, reason: 'not_in_progress' };
1373
1480
  }
1374
1481
 
@@ -1397,17 +1504,14 @@ function cancelJob(cwd, version) {
1397
1504
  }
1398
1505
  }
1399
1506
 
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);
1507
+ // Set job status to pending via frontmatter edit (no file move)
1508
+ // setJobStatus handles both markdown header and frontmatter update
1509
+ setJobStatus(cwd, version, 'pending');
1406
1510
 
1407
1511
  return {
1408
1512
  cancelled: true,
1409
1513
  version: version.startsWith('v') ? version : 'v' + version,
1410
- path: moveResult.to,
1514
+ path: filePath,
1411
1515
  steps_reset: stepsReset,
1412
1516
  };
1413
1517
  }
@@ -1713,8 +1817,8 @@ function recordStartShas(cwd, jobFilePath) {
1713
1817
  if (name === 'Name' || name.startsWith('-')) continue;
1714
1818
  if (!repoPath || repoPath.startsWith('-')) continue;
1715
1819
 
1716
- // Resolve absolute path: paths in REPOS.md are relative to planning root's parent
1717
- const absRepoPath = path.resolve(path.dirname(planningRoot), repoPath);
1820
+ // Resolve absolute path: paths in REPOS.md are relative to the planning root
1821
+ const absRepoPath = path.resolve(planningRoot, repoPath);
1718
1822
  if (!fs.existsSync(absRepoPath)) continue;
1719
1823
 
1720
1824
  try {
@@ -1983,6 +2087,8 @@ module.exports = {
1983
2087
  insertJobSteps,
1984
2088
  buildGapFixSteps,
1985
2089
  buildPhaseGapFixSteps,
2090
+ JOB_STATUSES,
2091
+ setJobStatus,
1986
2092
  insertGapFixSection,
1987
2093
  insertPhaseGapFixSection,
1988
2094
  updatePhaseFixCycleHeader,