@ktpartners/dgs-platform 2.9.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 (93) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +26 -1
  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 +2 -2
  80. package/deliver-great-systems/workflows/init-product.md +2 -18
  81. package/deliver-great-systems/workflows/new-milestone.md +30 -24
  82. package/deliver-great-systems/workflows/progress-all.md +133 -0
  83. package/deliver-great-systems/workflows/quick-abandon.md +89 -0
  84. package/deliver-great-systems/workflows/quick-complete.md +68 -0
  85. package/deliver-great-systems/workflows/quick.md +152 -23
  86. package/deliver-great-systems/workflows/refine-spec.md +1 -1
  87. package/deliver-great-systems/workflows/research-idea.md +8 -8
  88. package/deliver-great-systems/workflows/resume-project.md +2 -2
  89. package/deliver-great-systems/workflows/run-job.md +8 -8
  90. package/deliver-great-systems/workflows/validate-phase.md +39 -1
  91. package/deliver-great-systems/workflows/verify-work.md +14 -0
  92. package/deliver-great-systems/workflows/write-spec.md +2 -2
  93. package/package.json +1 -1
@@ -5,11 +5,13 @@
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
  const { execSync } = require('child_process');
8
- const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error, resolveProjectPath, getProjectRoot, isV2Install, getProjectFolders, getV2Hint } = require('./core.cjs');
8
+ const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error, resolveProjectPath, getProjectRoot, isV2Install, getProjectFolders, getV2Hint, safeReadFile } = require('./core.cjs');
9
9
  const { requireGitIdentity, formatAuthorString } = require('./identity.cjs');
10
10
  const { getPlanningRoot, PROJECTS_DIR } = require('./paths.cjs');
11
11
  const { parseReposMd, validateReposMdEager } = require('./repos.cjs');
12
12
  const { getCadence, pullAll } = require('./sync.cjs');
13
+ const { detectQuickMode } = require('./quick.cjs');
14
+ const { listProjectsReadonly } = require('./projects.cjs');
13
15
 
14
16
  /**
15
17
  * Safely resolve the current git author string.
@@ -221,9 +223,7 @@ function cmdInitExecutePhase(cwd, phase, raw) {
221
223
  // Config flags
222
224
  commit_docs: config.commit_docs,
223
225
  parallelization: config.parallelization,
224
- branching_strategy: config.branching_strategy,
225
- phase_branch_template: config.phase_branch_template,
226
- milestone_branch_template: config.milestone_branch_template,
226
+ branching_strategy: 'milestone',
227
227
  verifier_enabled: config.verifier,
228
228
  base_branch: config.base_branch,
229
229
  sync_push: config.sync_push,
@@ -246,25 +246,14 @@ function cmdInitExecutePhase(cwd, phase, raw) {
246
246
  plan_count: phaseInfo?.plans?.length || 0,
247
247
  incomplete_count: phaseInfo?.incomplete_plans?.length || 0,
248
248
 
249
- // Branch name (pre-computed with {project} resolution)
249
+ // Branch name (milestone worktree model — always uses milestone template)
250
250
  branch_name: (() => {
251
- let template, resolved;
252
- if (config.branching_strategy === 'phase' && phaseInfo) {
253
- template = config.phase_branch_template;
254
- resolved = resolveProjectInTemplate(template, ctx.current_project);
255
- if (resolved.error) return resolved.error;
256
- return resolved.value
257
- .replace('{phase}', phaseInfo.phase_number)
258
- .replace('{slug}', phaseInfo.phase_slug || 'phase');
259
- } else if (config.branching_strategy === 'milestone') {
260
- template = config.milestone_branch_template;
261
- resolved = resolveProjectInTemplate(template, ctx.current_project);
262
- if (resolved.error) return resolved.error;
263
- return resolved.value
264
- .replace('{milestone}', milestone.version)
265
- .replace('{slug}', generateSlugInternal(milestone.name) || 'milestone');
266
- }
267
- return null;
251
+ const template = 'dgs/{project}/{milestone}-{slug}';
252
+ const resolved = resolveProjectInTemplate(template, ctx.current_project);
253
+ if (resolved.error) return resolved.error;
254
+ return resolved.value
255
+ .replace('{milestone}', milestone.version)
256
+ .replace('{slug}', generateSlugInternal(milestone.name) || 'milestone');
268
257
  })(),
269
258
 
270
259
  // Milestone info
@@ -533,14 +522,27 @@ function cmdInitQuick(cwd, description, raw) {
533
522
  const config = loadConfig(cwd);
534
523
  const ctx = resolveProjectContext(cwd);
535
524
  const now = new Date();
536
- const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
537
525
  const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
538
526
 
527
+ // Parse --main flag out of description string
528
+ const forceMain = /\s*--main\s*/.test(description || '');
529
+ const cleanDescription = (description || '').replace(/\s*--main\s*/g, ' ').trim() || null;
530
+ const slug = cleanDescription ? generateSlugInternal(cleanDescription)?.substring(0, 40).replace(/-+$/, '') : null;
531
+
532
+ // Detect quick mode: product-level vs milestone-context
533
+ const quickMode = detectQuickMode(cwd, forceMain);
534
+
539
535
  // Generate collision-resistant quick task ID: YYMMDD-xxx
540
536
  // xxx = 2-second precision blocks since midnight, encoded as 3-char Base36 (lowercase)
541
537
  // Range: 000 (00:00:00) to xbz (23:59:58), guaranteed 3 chars for any time of day.
542
538
  // Provides ~2s uniqueness window per user — practically collision-free across a team.
543
- const quickBase = ctx.root ? path.join(ctx.root, 'quick') : path.join(planRootRel, 'quick');
539
+ // Branch path resolution by quickMode: product-level quicks (including --main
540
+ // override) land at the planning root; milestone-context quicks stay under the
541
+ // active project. This keeps product-level work out of project state.
542
+ const isProductMode = quickMode.mode === 'product';
543
+ const quickBase = isProductMode
544
+ ? path.join(planRootRel, 'quick')
545
+ : (ctx.root ? path.join(ctx.root, 'quick') : path.join(planRootRel, 'quick'));
544
546
  const yy = String(now.getFullYear()).slice(-2);
545
547
  const mm = String(now.getMonth() + 1).padStart(2, '0');
546
548
  const dd = String(now.getDate()).padStart(2, '0');
@@ -550,6 +552,18 @@ function cmdInitQuick(cwd, description, raw) {
550
552
  const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
551
553
  const quickId = dateStr + '-' + timeEncoded;
552
554
 
555
+ // Auto-create product-level STATE.md on demand (matches project STATE.md
556
+ // auto-creation behavior elsewhere). Keep skeleton minimal — the /dgs:quick
557
+ // workflow adds ### Quick Tasks Completed on first use; we only need the
558
+ // ### Blockers/Concerns anchor so later inserts succeed.
559
+ if (isProductMode) {
560
+ const absoluteStatePath = path.resolve(cwd, planRootRel, 'STATE.md');
561
+ if (!fs.existsSync(absoluteStatePath)) {
562
+ const skeleton = '# Project State\n\n## Current Position\n\n(Product-level state — quick tasks tracked here.)\n\n## Accumulated Context\n\n### Blockers/Concerns\n\nNone yet.\n';
563
+ fs.writeFileSync(absoluteStatePath, skeleton, 'utf-8');
564
+ }
565
+ }
566
+
553
567
  const result = {
554
568
  // Models
555
569
  planner_model: resolveModelInternal(cwd, 'dgs-planner'),
@@ -567,7 +581,7 @@ function cmdInitQuick(cwd, description, raw) {
567
581
  // Quick task info
568
582
  quick_id: quickId,
569
583
  slug: slug,
570
- description: description || null,
584
+ description: cleanDescription,
571
585
 
572
586
  // Timestamps
573
587
  date: now.toISOString().split('T')[0],
@@ -576,9 +590,14 @@ function cmdInitQuick(cwd, description, raw) {
576
590
  // Paths (project-qualified)
577
591
  quick_dir: quickBase,
578
592
  task_dir: slug ? path.join(quickBase, `${quickId}-${slug}`) : null,
579
- state_path: ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
593
+ state_path: isProductMode
594
+ ? path.join(planRootRel, 'STATE.md')
595
+ : (ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md')),
580
596
 
581
597
  // File existence
598
+ // roadmap_exists stays project-scoped for both modes: the quick workflow
599
+ // only reads ROADMAP.md for project context (phase/milestone labels), and
600
+ // product-level quicks still run against the currently active project.
582
601
  roadmap_exists: ctx.root ? pathExistsInternal(cwd, path.join(ctx.root, 'ROADMAP.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'ROADMAP.md')),
583
602
  planning_exists: pathExistsInternal(cwd, planRootRel),
584
603
 
@@ -844,17 +863,23 @@ function cmdInitTodos(cwd, area, raw, workflow) {
844
863
  const now = new Date();
845
864
  const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
846
865
 
847
- // List todos (reuse existing logic) project-qualified
848
- const pendingBase = path.join(planRootRel, 'todos', 'pending');
849
- const pendingDir = path.join(cwd, pendingBase);
866
+ // List todos — flat directory (status in frontmatter), with legacy fallback
867
+ const todosBase = path.join(planRootRel, 'todos');
868
+ const todosDir = path.join(cwd, todosBase);
850
869
  let count = 0;
851
870
  const todos = [];
871
+ const seenFiles = new Set();
852
872
 
873
+ // Flat directory scan (new layout — status in frontmatter)
853
874
  try {
854
- const files = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md'));
875
+ const files = fs.readdirSync(todosDir).filter(f => f.endsWith('.md'));
855
876
  for (const file of files) {
856
877
  try {
857
- const content = fs.readFileSync(path.join(pendingDir, file), 'utf-8');
878
+ const content = fs.readFileSync(path.join(todosDir, file), 'utf-8');
879
+ const statusMatch = content.match(/^status:\s*(.+)$/m);
880
+ const todoStatus = statusMatch ? statusMatch[1].trim() : 'pending';
881
+ if (todoStatus !== 'pending') continue; // Only show pending todos
882
+
858
883
  const createdMatch = content.match(/^created:\s*(.+)$/m);
859
884
  const titleMatch = content.match(/^title:\s*(.+)$/m);
860
885
  const areaMatch = content.match(/^area:\s*(.+)$/m);
@@ -863,19 +888,46 @@ function cmdInitTodos(cwd, area, raw, workflow) {
863
888
  if (area && todoArea !== area) continue;
864
889
 
865
890
  count++;
891
+ seenFiles.add(file);
866
892
  todos.push({
867
893
  file,
868
894
  created: createdMatch ? createdMatch[1].trim() : 'unknown',
869
895
  title: titleMatch ? titleMatch[1].trim() : 'Untitled',
870
896
  area: todoArea,
871
- path: pendingBase + '/' + file,
897
+ path: todosBase + '/' + file,
898
+ });
899
+ } catch {}
900
+ }
901
+ } catch {}
902
+
903
+ // Legacy fallback: check todos/pending/
904
+ const legacyPendingDir = path.join(cwd, todosBase, 'pending');
905
+ try {
906
+ const files = fs.readdirSync(legacyPendingDir).filter(f => f.endsWith('.md'));
907
+ for (const file of files) {
908
+ if (seenFiles.has(file)) continue;
909
+ try {
910
+ const content = fs.readFileSync(path.join(legacyPendingDir, file), 'utf-8');
911
+ const createdMatch = content.match(/^created:\s*(.+)$/m);
912
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
913
+ const areaMatch = content.match(/^area:\s*(.+)$/m);
914
+ const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
915
+
916
+ if (area && todoArea !== area) continue;
917
+
918
+ count++;
919
+ todos.push({
920
+ file,
921
+ created: createdMatch ? createdMatch[1].trim() : 'unknown',
922
+ title: titleMatch ? titleMatch[1].trim() : 'Untitled',
923
+ area: todoArea,
924
+ path: todosBase + '/pending/' + file,
872
925
  });
873
926
  } catch {}
874
927
  }
875
928
  } catch {}
876
929
 
877
930
  const completedBase = path.join(planRootRel, 'todos', 'completed');
878
- const todosBase = path.join(planRootRel, 'todos');
879
931
 
880
932
  const result = {
881
933
  // Config
@@ -894,14 +946,14 @@ function cmdInitTodos(cwd, area, raw, workflow) {
894
946
  todos,
895
947
  area_filter: area || null,
896
948
 
897
- // Paths (project-qualified)
898
- pending_dir: pendingBase,
949
+ // Paths (project-qualified) — pending_dir now points to flat todos/
950
+ pending_dir: todosBase,
899
951
  completed_dir: completedBase,
900
952
 
901
953
  // File existence
902
954
  planning_exists: pathExistsInternal(cwd, planRootRel),
903
955
  todos_dir_exists: pathExistsInternal(cwd, todosBase),
904
- pending_dir_exists: pathExistsInternal(cwd, pendingBase),
956
+ pending_dir_exists: pathExistsInternal(cwd, todosBase),
905
957
 
906
958
  // Author
907
959
  author: resolveAuthorSafe(cwd),
@@ -959,9 +1011,7 @@ function cmdInitMilestoneOp(cwd, raw, workflow) {
959
1011
  // Config
960
1012
  commit_docs: config.commit_docs,
961
1013
  base_branch: config.base_branch,
962
- branching_strategy: config.branching_strategy,
963
- phase_branch_template: config.phase_branch_template,
964
- milestone_branch_template: config.milestone_branch_template,
1014
+ branching_strategy: 'milestone',
965
1015
  sync_push: config.sync_push,
966
1016
  sync_pull: config.sync_pull,
967
1017
  cadence_pull: cadence.pull,
@@ -1236,6 +1286,222 @@ function cmdInitProgress(cwd, raw) {
1236
1286
  output(result, raw);
1237
1287
  }
1238
1288
 
1289
+ // ─── cmdInitProgressAll — Product-level dashboard ────────────────────────────
1290
+
1291
+ /**
1292
+ * Parse the product-level STATE.md "Quick Tasks Completed" table and return
1293
+ * the most recent rows (up to `limit`). Returns [] if STATE.md or the table
1294
+ * is missing.
1295
+ */
1296
+ function parseProductQuickTasks(planningRoot, limit) {
1297
+ const statePath = path.join(planningRoot, 'STATE.md');
1298
+ const content = safeReadFile(statePath);
1299
+ if (!content) return [];
1300
+
1301
+ const lines = content.split('\n');
1302
+ const rows = [];
1303
+ let inTable = false;
1304
+ let headerSeen = false;
1305
+
1306
+ for (const line of lines) {
1307
+ if (!inTable) {
1308
+ if (/^###\s+Quick Tasks Completed/i.test(line)) {
1309
+ inTable = true;
1310
+ }
1311
+ continue;
1312
+ }
1313
+ // Inside the Quick Tasks section
1314
+ if (!line.startsWith('|')) {
1315
+ if (rows.length > 0 || headerSeen) break; // end of table
1316
+ continue;
1317
+ }
1318
+ // It's a table row
1319
+ if (!headerSeen) {
1320
+ if (/^\|\s*#\s*\|\s*Description/i.test(line)) {
1321
+ headerSeen = true;
1322
+ }
1323
+ continue;
1324
+ }
1325
+ // Skip separator row
1326
+ if (/^\|[\s-]+\|/.test(line)) continue;
1327
+ // Data row
1328
+ const cells = line.split('|').map(c => c.trim());
1329
+ // cells[0] is '' before first pipe; real cells are cells[1..n-1]
1330
+ if (cells.length < 6) continue;
1331
+ rows.push({
1332
+ id: cells[1],
1333
+ description: cells[2],
1334
+ date: cells[3],
1335
+ commit: cells[4],
1336
+ status: cells[5],
1337
+ directory: cells[6] || '',
1338
+ });
1339
+ }
1340
+
1341
+ // Keep order as in file; take last `limit` (most recent appended last)
1342
+ return rows.slice(-limit);
1343
+ }
1344
+
1345
+ /**
1346
+ * Parse the product-level MILESTONES.md for shipped milestone headers.
1347
+ * Returns at most `limit` milestones in file order (top = most recent).
1348
+ */
1349
+ function parseProductShippedMilestones(planningRoot, limit) {
1350
+ const mPath = path.join(planningRoot, 'MILESTONES.md');
1351
+ const content = safeReadFile(mPath);
1352
+ if (!content) return [];
1353
+
1354
+ const regex = /^##\s+(v[\d.]+)\s+(.+?)\s+\(Shipped:\s+(\d{4}-\d{2}-\d{2})\)/gm;
1355
+ const entries = [];
1356
+ let m;
1357
+ while ((m = regex.exec(content)) !== null) {
1358
+ entries.push({ version: m[1], name: m[2].trim(), shipped_date: m[3] });
1359
+ if (entries.length >= limit) break;
1360
+ }
1361
+ return entries;
1362
+ }
1363
+
1364
+ /**
1365
+ * Parse milestone info from an arbitrary ROADMAP.md file (project-scoped).
1366
+ * Uses the same regexes as core.getMilestoneInfo but reads any path.
1367
+ * Returns { milestone_version, milestone_name } or both nulls if none found.
1368
+ */
1369
+ function parseProjectMilestone(roadmapPath) {
1370
+ const roadmap = safeReadFile(roadmapPath);
1371
+ if (!roadmap) return { milestone_version: null, milestone_name: null };
1372
+
1373
+ // 🚧 marker: "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
1374
+ const inProgressMatch = roadmap.match(/🚧\s*\*\*v(\d+\.\d+)\s+([^*]+)\*\*/);
1375
+ if (inProgressMatch) {
1376
+ return { milestone_version: 'v' + inProgressMatch[1], milestone_name: inProgressMatch[2].trim() };
1377
+ }
1378
+
1379
+ // Bullet list: "- v19.0 Git Worktrees -- Phases 124-129 (in progress)"
1380
+ const bulletMatch = roadmap.match(/^- v(\d+\.\d+)\s+(.+?)\s+--\s+Phases\s+\S+\s+\(in progress\)/m);
1381
+ if (bulletMatch) {
1382
+ return { milestone_version: 'v' + bulletMatch[1], milestone_name: bulletMatch[2].trim() };
1383
+ }
1384
+
1385
+ // Heading format (strip shipped <details> blocks)
1386
+ const cleaned = roadmap.replace(/<details>[\s\S]*?<\/details>/gi, '');
1387
+ const headingMatch = cleaned.match(/#{2,3}\s+v(\d+\.\d+)[:\s]+([^\n(]+)/);
1388
+ if (headingMatch) {
1389
+ return { milestone_version: 'v' + headingMatch[1], milestone_name: headingMatch[2].trim() };
1390
+ }
1391
+
1392
+ return { milestone_version: null, milestone_name: null };
1393
+ }
1394
+
1395
+ /**
1396
+ * Count .md files in a directory (non-recursive). Returns 0 on any error
1397
+ * (missing dir, permissions, etc).
1398
+ */
1399
+ function countMdFiles(dir, excludePredicate) {
1400
+ try {
1401
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1402
+ let count = 0;
1403
+ for (const e of entries) {
1404
+ if (!e.isFile()) continue;
1405
+ if (!e.name.endsWith('.md')) continue;
1406
+ if (excludePredicate && excludePredicate(e.name)) continue;
1407
+ count++;
1408
+ }
1409
+ return count;
1410
+ } catch {
1411
+ return 0;
1412
+ }
1413
+ }
1414
+
1415
+ /**
1416
+ * Count .md files in a flat directory that have a specific frontmatter status.
1417
+ * Used for ideas/todos/jobs that use frontmatter-based status instead of subdirectories.
1418
+ */
1419
+ function countMdFilesByStatus(dir, status) {
1420
+ try {
1421
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1422
+ let count = 0;
1423
+ for (const e of entries) {
1424
+ if (!e.isFile()) continue;
1425
+ if (!e.name.endsWith('.md')) continue;
1426
+ if (e.name === 'manifest.json') continue;
1427
+ try {
1428
+ const content = fs.readFileSync(path.join(dir, e.name), 'utf-8');
1429
+ const statusMatch = content.match(/^---\n[\s\S]*?^status:\s*(.+)/m);
1430
+ if (statusMatch && statusMatch[1].trim() === status) count++;
1431
+ } catch { /* skip unreadable files */ }
1432
+ }
1433
+ return count;
1434
+ } catch {
1435
+ return 0;
1436
+ }
1437
+ }
1438
+
1439
+ function cmdInitProgressAll(cwd, raw) {
1440
+ const config = loadConfig(cwd);
1441
+ const planningRoot = getPlanningRoot(cwd);
1442
+
1443
+ // listProjectsReadonly handles ghost projects internally via warnings
1444
+ const { projects: allProjects, warnings } = listProjectsReadonly(cwd);
1445
+
1446
+ const activeProjects = allProjects.filter(p => !p.status.toLowerCase().includes('completed'));
1447
+ const completedProjects = allProjects.filter(p => p.status.toLowerCase().includes('completed'));
1448
+
1449
+ // Enrich each active project with milestone info from its ROADMAP.md
1450
+ const enrichedProjects = activeProjects.map(p => {
1451
+ const roadmapPath = path.join(planningRoot, 'projects', p.name, 'ROADMAP.md');
1452
+ const { milestone_version, milestone_name } = parseProjectMilestone(roadmapPath);
1453
+ return {
1454
+ name: p.name,
1455
+ status: p.status,
1456
+ current_phase: p.current_phase,
1457
+ progress: p.progress,
1458
+ repos_touched: p.repos_touched,
1459
+ milestone_version,
1460
+ milestone_name,
1461
+ };
1462
+ });
1463
+
1464
+ // Product-level aggregates
1465
+ const quickTasksRecent = parseProductQuickTasks(planningRoot, 5);
1466
+ const shippedMilestonesRecent = parseProductShippedMilestones(planningRoot, 5);
1467
+
1468
+ const backlog = {
1469
+ todos: countMdFilesByStatus(path.join(planningRoot, 'todos'), 'pending'),
1470
+ ideas: countMdFilesByStatus(path.join(planningRoot, 'ideas'), 'pending'),
1471
+ specs: countMdFiles(path.join(planningRoot, 'specs')),
1472
+ debug_active: countMdFiles(path.join(planningRoot, 'debug'), name => name.includes('resolved')),
1473
+ };
1474
+
1475
+ const result = {
1476
+ planner_model: resolveModelInternal(cwd, 'dgs-planner'),
1477
+ executor_model: resolveModelInternal(cwd, 'dgs-executor'),
1478
+ commit_docs: config.commit_docs,
1479
+ sync_push: config.sync_push,
1480
+ sync_pull: config.sync_pull,
1481
+ cadence_pull: getCadence('progress').pull,
1482
+ cadence_push: getCadence('progress').push,
1483
+
1484
+ product: {
1485
+ product_name: config.product_name || null,
1486
+ active_count: activeProjects.length,
1487
+ completed_count: completedProjects.length,
1488
+ quick_tasks_recent: quickTasksRecent,
1489
+ shipped_milestones_recent: shippedMilestonesRecent,
1490
+ backlog,
1491
+ },
1492
+
1493
+ projects: enrichedProjects,
1494
+ warnings,
1495
+
1496
+ author: resolveAuthorSafe(cwd),
1497
+ dgs_mode: isV2Install(cwd) ? 'v2' : 'v1',
1498
+ planning_root: path.relative(cwd, planningRoot) || '.',
1499
+ };
1500
+
1501
+ applySyncPull(cwd, 'progress', result);
1502
+ output(result, raw);
1503
+ }
1504
+
1239
1505
  module.exports = {
1240
1506
  cmdInitExecutePhase,
1241
1507
  cmdInitPlanPhase,
@@ -1250,5 +1516,6 @@ module.exports = {
1250
1516
  cmdInitMilestoneOp,
1251
1517
  cmdInitMapCodebase,
1252
1518
  cmdInitProgress,
1519
+ cmdInitProgressAll,
1253
1520
  applySyncPull,
1254
1521
  };