@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
@@ -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, generateQuickId, getActiveQuick } = 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
@@ -395,12 +384,28 @@ function cmdInitPlanPhase(cwd, phase, raw) {
395
384
  output(result, raw);
396
385
  }
397
386
 
398
- function cmdInitNewProject(cwd, raw) {
387
+ function cmdInitNewProject(cwd, slugArg, raw) {
399
388
  const config = loadConfig(cwd);
400
389
  const ctx = resolveProjectContext(cwd);
401
390
  const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
402
391
  const milestone = getMilestoneInfo(cwd);
403
392
 
393
+ // Resolve slug override (optional positional arg from CLI).
394
+ // When supplied AND we're on a v2 install, override the per-slug paths
395
+ // so `init new-project <slug>` reflects whether THAT slug exists,
396
+ // independent of the currently active project. v1 mode silently falls
397
+ // back to ctx.root behaviour because v1 has no projects/<slug>/ layout.
398
+ let effectiveRoot = ctx.root;
399
+ let slugOverride = null;
400
+ if (slugArg && typeof slugArg === 'string' && slugArg.trim()) {
401
+ const candidate = generateSlugInternal(slugArg.trim());
402
+ if (candidate && isV2Install(cwd)) {
403
+ slugOverride = candidate;
404
+ effectiveRoot = path.join(planRootRel, 'projects', slugOverride);
405
+ }
406
+ }
407
+ const rootForPaths = effectiveRoot;
408
+
404
409
  // Detect Brave Search API key availability
405
410
  const homedir = require('os').homedir();
406
411
  const braveKeyFile = path.join(homedir, '.dgs', 'brave_api_key');
@@ -437,8 +442,8 @@ function cmdInitNewProject(cwd, raw) {
437
442
  cadence_pull: getCadence('new-project').pull,
438
443
  cadence_push: getCadence('new-project').push,
439
444
 
440
- // Existing state (project-qualified)
441
- project_exists: ctx.root ? pathExistsInternal(cwd, path.join(ctx.root, 'PROJECT.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'PROJECT.md')),
445
+ // Existing state (project-qualified, slug-override aware)
446
+ project_exists: rootForPaths ? pathExistsInternal(cwd, path.join(rootForPaths, 'PROJECT.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'PROJECT.md')),
442
447
  has_codebase_map: pathExistsInternal(cwd, path.join(planRootRel, 'codebase')),
443
448
  planning_exists: pathExistsInternal(cwd, planRootRel),
444
449
 
@@ -457,12 +462,12 @@ function cmdInitNewProject(cwd, raw) {
457
462
  // Enhanced search
458
463
  brave_search_available: hasBraveSearch,
459
464
 
460
- // File paths (project-qualified)
461
- project_path: ctx.root ? path.join(ctx.root, 'PROJECT.md') : path.join(planRootRel, 'PROJECT.md'),
462
- state_path: ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
463
- roadmap_path: ctx.root ? path.join(ctx.root, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
464
- requirements_path: ctx.root ? path.join(ctx.root, 'REQUIREMENTS.md') : path.join(planRootRel, 'REQUIREMENTS.md'),
465
- research_dir: ctx.root ? path.join(ctx.root, 'research') : path.join(planRootRel, 'research'),
465
+ // File paths (project-qualified, slug-override aware)
466
+ project_path: rootForPaths ? path.join(rootForPaths, 'PROJECT.md') : path.join(planRootRel, 'PROJECT.md'),
467
+ state_path: rootForPaths ? path.join(rootForPaths, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
468
+ roadmap_path: rootForPaths ? path.join(rootForPaths, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
469
+ requirements_path: rootForPaths ? path.join(rootForPaths, 'REQUIREMENTS.md') : path.join(planRootRel, 'REQUIREMENTS.md'),
470
+ research_dir: rootForPaths ? path.join(rootForPaths, 'research') : path.join(planRootRel, 'research'),
466
471
 
467
472
  // Milestone info (product-level, shared across projects)
468
473
  milestone_version: milestone.version,
@@ -471,7 +476,13 @@ function cmdInitNewProject(cwd, raw) {
471
476
  // v2 context
472
477
  dgs_mode: ctx.dgs_mode,
473
478
  current_project: ctx.current_project,
474
- project_root: ctx.root,
479
+ // project_root reflects override when a slug arg is supplied (so the
480
+ // workflow can write directly to the requested project's folder), otherwise
481
+ // it still reports the active project root.
482
+ project_root: rootForPaths,
483
+ // requested_slug: the normalized slug arg (or null when no slug arg / v1 fallback).
484
+ // The workflow uses this to distinguish "active project" from "requested project".
485
+ requested_slug: slugOverride,
475
486
  guard: ctx.guard,
476
487
  v2_hint: ctx.v2_hint || null,
477
488
  };
@@ -514,6 +525,10 @@ function cmdInitNewMilestone(cwd, raw) {
514
525
  roadmap_path: ctx.root ? path.join(ctx.root, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
515
526
  state_path: ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
516
527
 
528
+ // Product-level MILESTONES.md (authoritative for global version/phase continuity across projects)
529
+ product_milestones_path: path.join(planRootRel, 'MILESTONES.md'),
530
+ product_milestones_exists: pathExistsInternal(cwd, path.join(planRootRel, 'MILESTONES.md')),
531
+
517
532
  // Author
518
533
  author: resolveAuthorSafe(cwd),
519
534
 
@@ -533,22 +548,50 @@ function cmdInitQuick(cwd, description, raw) {
533
548
  const config = loadConfig(cwd);
534
549
  const ctx = resolveProjectContext(cwd);
535
550
  const now = new Date();
536
- const slug = description ? generateSlugInternal(description)?.substring(0, 40) : null;
537
551
  const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
538
552
 
539
- // Generate collision-resistant quick task ID: YYMMDD-xxx
540
- // xxx = 2-second precision blocks since midnight, encoded as 3-char Base36 (lowercase)
541
- // Range: 000 (00:00:00) to xbz (23:59:58), guaranteed 3 chars for any time of day.
542
- // 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');
544
- const yy = String(now.getFullYear()).slice(-2);
545
- const mm = String(now.getMonth() + 1).padStart(2, '0');
546
- const dd = String(now.getDate()).padStart(2, '0');
547
- const dateStr = yy + mm + dd;
548
- const secondsSinceMidnight = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
549
- const timeBlocks = Math.floor(secondsSinceMidnight / 2);
550
- const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
551
- const quickId = dateStr + '-' + timeEncoded;
553
+ // Parse --main flag out of description string
554
+ const forceMain = /\s*--main\s*/.test(description || '');
555
+ const cleanDescription = (description || '').replace(/\s*--main\s*/g, ' ').trim() || null;
556
+
557
+ // Hoist getActiveQuick lookup so both `slug` and `quickId` derive from
558
+ // the same canonical source (the worktree slug stored in config.local.json).
559
+ // The worktree slug has already been truncated to 50 chars by worktrees.cjs;
560
+ // re-sanitising the description here would produce a divergent (51-char)
561
+ // descSlug, leaving an orphan directory on disk after every quick task.
562
+ // See: bug 260428-k7f.
563
+ const activeQuick = getActiveQuick(cwd);
564
+ const hasActiveQuickSlug = activeQuick && /^\d{6}-[a-z0-9]{3}-/.test(activeQuick.slug);
565
+
566
+ const slug = hasActiveQuickSlug
567
+ ? activeQuick.slug.slice(11) // strip "{quickId}-" prefix (10 chars + 1 dash)
568
+ : (cleanDescription ? generateSlugInternal(cleanDescription)?.substring(0, 40).replace(/-+$/, '') : null);
569
+
570
+ // Detect quick mode: product-level vs milestone-context
571
+ const quickMode = detectQuickMode(cwd, forceMain);
572
+
573
+ // Quick task ID: YYMMDD-xxx. For product-level quicks with an active worktree,
574
+ // extract the quickId from the worktree slug (startProductQuick embeds it as the
575
+ // prefix). For fast mode and milestone-context, generate a fresh one.
576
+ const isProductMode = quickMode.mode === 'product';
577
+ const quickBase = isProductMode
578
+ ? path.join(planRootRel, 'quick')
579
+ : (ctx.root ? path.join(ctx.root, 'quick') : path.join(planRootRel, 'quick'));
580
+ const quickId = hasActiveQuickSlug
581
+ ? activeQuick.slug.slice(0, 10)
582
+ : generateQuickId(now);
583
+
584
+ // Auto-create product-level STATE.md on demand (matches project STATE.md
585
+ // auto-creation behavior elsewhere). Keep skeleton minimal — the /dgs:quick
586
+ // workflow adds ### Quick Tasks Completed on first use; we only need the
587
+ // ### Blockers/Concerns anchor so later inserts succeed.
588
+ if (isProductMode) {
589
+ const absoluteStatePath = path.resolve(cwd, planRootRel, 'STATE.md');
590
+ if (!fs.existsSync(absoluteStatePath)) {
591
+ 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';
592
+ fs.writeFileSync(absoluteStatePath, skeleton, 'utf-8');
593
+ }
594
+ }
552
595
 
553
596
  const result = {
554
597
  // Models
@@ -567,7 +610,7 @@ function cmdInitQuick(cwd, description, raw) {
567
610
  // Quick task info
568
611
  quick_id: quickId,
569
612
  slug: slug,
570
- description: description || null,
613
+ description: cleanDescription,
571
614
 
572
615
  // Timestamps
573
616
  date: now.toISOString().split('T')[0],
@@ -576,9 +619,14 @@ function cmdInitQuick(cwd, description, raw) {
576
619
  // Paths (project-qualified)
577
620
  quick_dir: quickBase,
578
621
  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'),
622
+ state_path: isProductMode
623
+ ? path.join(planRootRel, 'STATE.md')
624
+ : (ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md')),
580
625
 
581
626
  // File existence
627
+ // roadmap_exists stays project-scoped for both modes: the quick workflow
628
+ // only reads ROADMAP.md for project context (phase/milestone labels), and
629
+ // product-level quicks still run against the currently active project.
582
630
  roadmap_exists: ctx.root ? pathExistsInternal(cwd, path.join(ctx.root, 'ROADMAP.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'ROADMAP.md')),
583
631
  planning_exists: pathExistsInternal(cwd, planRootRel),
584
632
 
@@ -844,17 +892,23 @@ function cmdInitTodos(cwd, area, raw, workflow) {
844
892
  const now = new Date();
845
893
  const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
846
894
 
847
- // List todos (reuse existing logic) project-qualified
848
- const pendingBase = path.join(planRootRel, 'todos', 'pending');
849
- const pendingDir = path.join(cwd, pendingBase);
895
+ // List todos — flat directory (status in frontmatter), with legacy fallback
896
+ const todosBase = path.join(planRootRel, 'todos');
897
+ const todosDir = path.join(cwd, todosBase);
850
898
  let count = 0;
851
899
  const todos = [];
900
+ const seenFiles = new Set();
852
901
 
902
+ // Flat directory scan (new layout — status in frontmatter)
853
903
  try {
854
- const files = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md'));
904
+ const files = fs.readdirSync(todosDir).filter(f => f.endsWith('.md'));
855
905
  for (const file of files) {
856
906
  try {
857
- const content = fs.readFileSync(path.join(pendingDir, file), 'utf-8');
907
+ const content = fs.readFileSync(path.join(todosDir, file), 'utf-8');
908
+ const statusMatch = content.match(/^status:\s*(.+)$/m);
909
+ const todoStatus = statusMatch ? statusMatch[1].trim() : 'pending';
910
+ if (todoStatus !== 'pending') continue; // Only show pending todos
911
+
858
912
  const createdMatch = content.match(/^created:\s*(.+)$/m);
859
913
  const titleMatch = content.match(/^title:\s*(.+)$/m);
860
914
  const areaMatch = content.match(/^area:\s*(.+)$/m);
@@ -863,19 +917,46 @@ function cmdInitTodos(cwd, area, raw, workflow) {
863
917
  if (area && todoArea !== area) continue;
864
918
 
865
919
  count++;
920
+ seenFiles.add(file);
866
921
  todos.push({
867
922
  file,
868
923
  created: createdMatch ? createdMatch[1].trim() : 'unknown',
869
924
  title: titleMatch ? titleMatch[1].trim() : 'Untitled',
870
925
  area: todoArea,
871
- path: pendingBase + '/' + file,
926
+ path: todosBase + '/' + file,
927
+ });
928
+ } catch {}
929
+ }
930
+ } catch {}
931
+
932
+ // Legacy fallback: check todos/pending/
933
+ const legacyPendingDir = path.join(cwd, todosBase, 'pending');
934
+ try {
935
+ const files = fs.readdirSync(legacyPendingDir).filter(f => f.endsWith('.md'));
936
+ for (const file of files) {
937
+ if (seenFiles.has(file)) continue;
938
+ try {
939
+ const content = fs.readFileSync(path.join(legacyPendingDir, file), 'utf-8');
940
+ const createdMatch = content.match(/^created:\s*(.+)$/m);
941
+ const titleMatch = content.match(/^title:\s*(.+)$/m);
942
+ const areaMatch = content.match(/^area:\s*(.+)$/m);
943
+ const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
944
+
945
+ if (area && todoArea !== area) continue;
946
+
947
+ count++;
948
+ todos.push({
949
+ file,
950
+ created: createdMatch ? createdMatch[1].trim() : 'unknown',
951
+ title: titleMatch ? titleMatch[1].trim() : 'Untitled',
952
+ area: todoArea,
953
+ path: todosBase + '/pending/' + file,
872
954
  });
873
955
  } catch {}
874
956
  }
875
957
  } catch {}
876
958
 
877
959
  const completedBase = path.join(planRootRel, 'todos', 'completed');
878
- const todosBase = path.join(planRootRel, 'todos');
879
960
 
880
961
  const result = {
881
962
  // Config
@@ -894,14 +975,14 @@ function cmdInitTodos(cwd, area, raw, workflow) {
894
975
  todos,
895
976
  area_filter: area || null,
896
977
 
897
- // Paths (project-qualified)
898
- pending_dir: pendingBase,
978
+ // Paths (project-qualified) — pending_dir now points to flat todos/
979
+ pending_dir: todosBase,
899
980
  completed_dir: completedBase,
900
981
 
901
982
  // File existence
902
983
  planning_exists: pathExistsInternal(cwd, planRootRel),
903
984
  todos_dir_exists: pathExistsInternal(cwd, todosBase),
904
- pending_dir_exists: pathExistsInternal(cwd, pendingBase),
985
+ pending_dir_exists: pathExistsInternal(cwd, todosBase),
905
986
 
906
987
  // Author
907
988
  author: resolveAuthorSafe(cwd),
@@ -959,9 +1040,7 @@ function cmdInitMilestoneOp(cwd, raw, workflow) {
959
1040
  // Config
960
1041
  commit_docs: config.commit_docs,
961
1042
  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,
1043
+ branching_strategy: 'milestone',
965
1044
  sync_push: config.sync_push,
966
1045
  sync_pull: config.sync_pull,
967
1046
  cadence_pull: cadence.pull,
@@ -1236,6 +1315,222 @@ function cmdInitProgress(cwd, raw) {
1236
1315
  output(result, raw);
1237
1316
  }
1238
1317
 
1318
+ // ─── cmdInitProgressAll — Product-level dashboard ────────────────────────────
1319
+
1320
+ /**
1321
+ * Parse the product-level STATE.md "Quick Tasks Completed" table and return
1322
+ * the most recent rows (up to `limit`). Returns [] if STATE.md or the table
1323
+ * is missing.
1324
+ */
1325
+ function parseProductQuickTasks(planningRoot, limit) {
1326
+ const statePath = path.join(planningRoot, 'STATE.md');
1327
+ const content = safeReadFile(statePath);
1328
+ if (!content) return [];
1329
+
1330
+ const lines = content.split('\n');
1331
+ const rows = [];
1332
+ let inTable = false;
1333
+ let headerSeen = false;
1334
+
1335
+ for (const line of lines) {
1336
+ if (!inTable) {
1337
+ if (/^###\s+Quick Tasks Completed/i.test(line)) {
1338
+ inTable = true;
1339
+ }
1340
+ continue;
1341
+ }
1342
+ // Inside the Quick Tasks section
1343
+ if (!line.startsWith('|')) {
1344
+ if (rows.length > 0 || headerSeen) break; // end of table
1345
+ continue;
1346
+ }
1347
+ // It's a table row
1348
+ if (!headerSeen) {
1349
+ if (/^\|\s*#\s*\|\s*Description/i.test(line)) {
1350
+ headerSeen = true;
1351
+ }
1352
+ continue;
1353
+ }
1354
+ // Skip separator row
1355
+ if (/^\|[\s-]+\|/.test(line)) continue;
1356
+ // Data row
1357
+ const cells = line.split('|').map(c => c.trim());
1358
+ // cells[0] is '' before first pipe; real cells are cells[1..n-1]
1359
+ if (cells.length < 6) continue;
1360
+ rows.push({
1361
+ id: cells[1],
1362
+ description: cells[2],
1363
+ date: cells[3],
1364
+ commit: cells[4],
1365
+ status: cells[5],
1366
+ directory: cells[6] || '',
1367
+ });
1368
+ }
1369
+
1370
+ // Keep order as in file; take last `limit` (most recent appended last)
1371
+ return rows.slice(-limit);
1372
+ }
1373
+
1374
+ /**
1375
+ * Parse the product-level MILESTONES.md for shipped milestone headers.
1376
+ * Returns at most `limit` milestones in file order (top = most recent).
1377
+ */
1378
+ function parseProductShippedMilestones(planningRoot, limit) {
1379
+ const mPath = path.join(planningRoot, 'MILESTONES.md');
1380
+ const content = safeReadFile(mPath);
1381
+ if (!content) return [];
1382
+
1383
+ const regex = /^##\s+(v[\d.]+)\s+(.+?)\s+\(Shipped:\s+(\d{4}-\d{2}-\d{2})\)/gm;
1384
+ const entries = [];
1385
+ let m;
1386
+ while ((m = regex.exec(content)) !== null) {
1387
+ entries.push({ version: m[1], name: m[2].trim(), shipped_date: m[3] });
1388
+ if (entries.length >= limit) break;
1389
+ }
1390
+ return entries;
1391
+ }
1392
+
1393
+ /**
1394
+ * Parse milestone info from an arbitrary ROADMAP.md file (project-scoped).
1395
+ * Uses the same regexes as core.getMilestoneInfo but reads any path.
1396
+ * Returns { milestone_version, milestone_name } or both nulls if none found.
1397
+ */
1398
+ function parseProjectMilestone(roadmapPath) {
1399
+ const roadmap = safeReadFile(roadmapPath);
1400
+ if (!roadmap) return { milestone_version: null, milestone_name: null };
1401
+
1402
+ // 🚧 marker: "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
1403
+ const inProgressMatch = roadmap.match(/🚧\s*\*\*v(\d+\.\d+)\s+([^*]+)\*\*/);
1404
+ if (inProgressMatch) {
1405
+ return { milestone_version: 'v' + inProgressMatch[1], milestone_name: inProgressMatch[2].trim() };
1406
+ }
1407
+
1408
+ // Bullet list: "- v19.0 Git Worktrees -- Phases 124-129 (in progress)"
1409
+ const bulletMatch = roadmap.match(/^- v(\d+\.\d+)\s+(.+?)\s+--\s+Phases\s+\S+\s+\(in progress\)/m);
1410
+ if (bulletMatch) {
1411
+ return { milestone_version: 'v' + bulletMatch[1], milestone_name: bulletMatch[2].trim() };
1412
+ }
1413
+
1414
+ // Heading format (strip shipped <details> blocks)
1415
+ const cleaned = roadmap.replace(/<details>[\s\S]*?<\/details>/gi, '');
1416
+ const headingMatch = cleaned.match(/#{2,3}\s+v(\d+\.\d+)[:\s]+([^\n(]+)/);
1417
+ if (headingMatch) {
1418
+ return { milestone_version: 'v' + headingMatch[1], milestone_name: headingMatch[2].trim() };
1419
+ }
1420
+
1421
+ return { milestone_version: null, milestone_name: null };
1422
+ }
1423
+
1424
+ /**
1425
+ * Count .md files in a directory (non-recursive). Returns 0 on any error
1426
+ * (missing dir, permissions, etc).
1427
+ */
1428
+ function countMdFiles(dir, excludePredicate) {
1429
+ try {
1430
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1431
+ let count = 0;
1432
+ for (const e of entries) {
1433
+ if (!e.isFile()) continue;
1434
+ if (!e.name.endsWith('.md')) continue;
1435
+ if (excludePredicate && excludePredicate(e.name)) continue;
1436
+ count++;
1437
+ }
1438
+ return count;
1439
+ } catch {
1440
+ return 0;
1441
+ }
1442
+ }
1443
+
1444
+ /**
1445
+ * Count .md files in a flat directory that have a specific frontmatter status.
1446
+ * Used for ideas/todos/jobs that use frontmatter-based status instead of subdirectories.
1447
+ */
1448
+ function countMdFilesByStatus(dir, status) {
1449
+ try {
1450
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1451
+ let count = 0;
1452
+ for (const e of entries) {
1453
+ if (!e.isFile()) continue;
1454
+ if (!e.name.endsWith('.md')) continue;
1455
+ if (e.name === 'manifest.json') continue;
1456
+ try {
1457
+ const content = fs.readFileSync(path.join(dir, e.name), 'utf-8');
1458
+ const statusMatch = content.match(/^---\n[\s\S]*?^status:\s*(.+)/m);
1459
+ if (statusMatch && statusMatch[1].trim() === status) count++;
1460
+ } catch { /* skip unreadable files */ }
1461
+ }
1462
+ return count;
1463
+ } catch {
1464
+ return 0;
1465
+ }
1466
+ }
1467
+
1468
+ function cmdInitProgressAll(cwd, raw) {
1469
+ const config = loadConfig(cwd);
1470
+ const planningRoot = getPlanningRoot(cwd);
1471
+
1472
+ // listProjectsReadonly handles ghost projects internally via warnings
1473
+ const { projects: allProjects, warnings } = listProjectsReadonly(cwd);
1474
+
1475
+ const activeProjects = allProjects.filter(p => !p.status.toLowerCase().includes('completed'));
1476
+ const completedProjects = allProjects.filter(p => p.status.toLowerCase().includes('completed'));
1477
+
1478
+ // Enrich each active project with milestone info from its ROADMAP.md
1479
+ const enrichedProjects = activeProjects.map(p => {
1480
+ const roadmapPath = path.join(planningRoot, 'projects', p.name, 'ROADMAP.md');
1481
+ const { milestone_version, milestone_name } = parseProjectMilestone(roadmapPath);
1482
+ return {
1483
+ name: p.name,
1484
+ status: p.status,
1485
+ current_phase: p.current_phase,
1486
+ progress: p.progress,
1487
+ repos_touched: p.repos_touched,
1488
+ milestone_version,
1489
+ milestone_name,
1490
+ };
1491
+ });
1492
+
1493
+ // Product-level aggregates
1494
+ const quickTasksRecent = parseProductQuickTasks(planningRoot, 5);
1495
+ const shippedMilestonesRecent = parseProductShippedMilestones(planningRoot, 5);
1496
+
1497
+ const backlog = {
1498
+ todos: countMdFilesByStatus(path.join(planningRoot, 'todos'), 'pending'),
1499
+ ideas: countMdFilesByStatus(path.join(planningRoot, 'ideas'), 'pending'),
1500
+ specs: countMdFiles(path.join(planningRoot, 'specs')),
1501
+ debug_active: countMdFiles(path.join(planningRoot, 'debug'), name => name.includes('resolved')),
1502
+ };
1503
+
1504
+ const result = {
1505
+ planner_model: resolveModelInternal(cwd, 'dgs-planner'),
1506
+ executor_model: resolveModelInternal(cwd, 'dgs-executor'),
1507
+ commit_docs: config.commit_docs,
1508
+ sync_push: config.sync_push,
1509
+ sync_pull: config.sync_pull,
1510
+ cadence_pull: getCadence('progress').pull,
1511
+ cadence_push: getCadence('progress').push,
1512
+
1513
+ product: {
1514
+ product_name: config.product_name || null,
1515
+ active_count: activeProjects.length,
1516
+ completed_count: completedProjects.length,
1517
+ quick_tasks_recent: quickTasksRecent,
1518
+ shipped_milestones_recent: shippedMilestonesRecent,
1519
+ backlog,
1520
+ },
1521
+
1522
+ projects: enrichedProjects,
1523
+ warnings,
1524
+
1525
+ author: resolveAuthorSafe(cwd),
1526
+ dgs_mode: isV2Install(cwd) ? 'v2' : 'v1',
1527
+ planning_root: path.relative(cwd, planningRoot) || '.',
1528
+ };
1529
+
1530
+ applySyncPull(cwd, 'progress', result);
1531
+ output(result, raw);
1532
+ }
1533
+
1239
1534
  module.exports = {
1240
1535
  cmdInitExecutePhase,
1241
1536
  cmdInitPlanPhase,
@@ -1250,5 +1545,6 @@ module.exports = {
1250
1545
  cmdInitMilestoneOp,
1251
1546
  cmdInitMapCodebase,
1252
1547
  cmdInitProgress,
1548
+ cmdInitProgressAll,
1253
1549
  applySyncPull,
1254
1550
  };