@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
@@ -911,6 +911,125 @@ function cmdContextLoadTier(cwd, tierName, args, raw) {
911
911
  output(result, raw, result.files.map(f => f.path).join('\n'));
912
912
  }
913
913
 
914
+ // ─── Code Context Resolution ──────────────────────────────────────────────
915
+
916
+ // Lazy requires to avoid circular dependencies (repos.cjs requires core.cjs)
917
+ function _getRepos() { return require('./repos.cjs'); }
918
+ const { getLocalConfigPath } = require('./config.cjs');
919
+
920
+ /**
921
+ * Resolve the active code context for a repo.
922
+ *
923
+ * Returns the directory where code operations should target:
924
+ * - Main checkout when no active context
925
+ * - Milestone worktree when milestone context active
926
+ * - Quick worktree when quick context active
927
+ *
928
+ * Handles stale contexts (missing directories) by clearing
929
+ * active_context and falling back to main with a warning.
930
+ *
931
+ * @param {string} cwd - Working directory (for config resolution)
932
+ * @param {string} repoName - Name of the repo (as in REPOS.md)
933
+ * @returns {{ type: string, directory: string, slug?: string, mode?: string }}
934
+ */
935
+ function resolveCodeContext(cwd, repoName) {
936
+ const localPath = getLocalConfigPath(cwd);
937
+ let local;
938
+ try {
939
+ local = JSON.parse(fs.readFileSync(localPath, 'utf-8'));
940
+ } catch {
941
+ local = {};
942
+ }
943
+
944
+ const activeContext = local && local.execution && local.execution.active_context;
945
+ if (!activeContext) {
946
+ // No active context -- resolve to main checkout
947
+ return _resolveMainCheckout(cwd, repoName);
948
+ }
949
+
950
+ // Look up the current project
951
+ const project = local.current_project;
952
+ if (!project) {
953
+ // No project set -- fall back to main
954
+ return _resolveMainCheckout(cwd, repoName);
955
+ }
956
+
957
+ // Look up worktree entry
958
+ const worktreeEntry = local.projects
959
+ && local.projects[project]
960
+ && local.projects[project].worktrees
961
+ && local.projects[project].worktrees[activeContext];
962
+
963
+ if (!worktreeEntry) {
964
+ // Stale context -- entry missing from config
965
+ process.stderr.write(
966
+ 'Warning: Active context \'' + activeContext + '\' not found in worktree state. Falling back to main.\n'
967
+ );
968
+ _clearActiveContext(localPath, local);
969
+ return _resolveMainCheckout(cwd, repoName);
970
+ }
971
+
972
+ // Look up repo directory in worktree entry
973
+ const worktreeDir = worktreeEntry.repos && worktreeEntry.repos[repoName];
974
+ if (!worktreeDir) {
975
+ // Repo not in this worktree -- fall back to main
976
+ return _resolveMainCheckout(cwd, repoName);
977
+ }
978
+
979
+ // Validate directory exists on disk
980
+ if (!fs.existsSync(worktreeDir)) {
981
+ // Stale context -- directory missing
982
+ process.stderr.write(
983
+ 'Warning: Active context \'' + activeContext + '\' is stale -- worktree directory no longer exists at ' + worktreeDir + '. Falling back to main.\n'
984
+ );
985
+ _clearActiveContext(localPath, local);
986
+ return _resolveMainCheckout(cwd, repoName);
987
+ }
988
+
989
+ return {
990
+ type: worktreeEntry.type || 'milestone',
991
+ directory: worktreeDir,
992
+ slug: activeContext,
993
+ mode: worktreeEntry.mode || null,
994
+ };
995
+ }
996
+
997
+ /**
998
+ * Resolve main checkout path for a repo.
999
+ * @private
1000
+ */
1001
+ function _resolveMainCheckout(cwd, repoName) {
1002
+ try {
1003
+ const parsed = _getRepos().parseReposMd(cwd);
1004
+ if (parsed && parsed.repos) {
1005
+ const repo = parsed.repos.find(function(r) { return r.name === repoName; });
1006
+ if (repo && repo.path) {
1007
+ const root = getPlanningRoot(cwd);
1008
+ const absPath = path.resolve(root, repo.path);
1009
+ return { type: 'main', directory: absPath };
1010
+ }
1011
+ }
1012
+ } catch {
1013
+ // REPOS.md not found or parse error
1014
+ }
1015
+ // Fallback: return cwd
1016
+ return { type: 'main', directory: cwd };
1017
+ }
1018
+
1019
+ /**
1020
+ * Clear active_context in config.local.json.
1021
+ * @private
1022
+ */
1023
+ function _clearActiveContext(localPath, localData) {
1024
+ try {
1025
+ if (!localData.execution) localData.execution = {};
1026
+ localData.execution.active_context = null;
1027
+ fs.writeFileSync(localPath, JSON.stringify(localData, null, 2) + '\n', 'utf-8');
1028
+ } catch {
1029
+ // Best effort -- don't crash on write failure
1030
+ }
1031
+ }
1032
+
914
1033
  // ─── Exports ────────────────────────────────────────────────────────────────
915
1034
 
916
1035
  module.exports = {
@@ -918,6 +1037,7 @@ module.exports = {
918
1037
  cmdContextLoadTier,
919
1038
  truncateApprovedSpec,
920
1039
  resetTierCache,
1040
+ resolveCodeContext,
921
1041
  // Internal exports for testing
922
1042
  parseTierDefinitions,
923
1043
  parseSimpleYaml,
@@ -22,12 +22,14 @@ const MODEL_PROFILES = {
22
22
  'dgs-executor': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
23
23
  'dgs-phase-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
24
24
  'dgs-project-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
25
+ 'dgs-idea-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku' },
25
26
  'dgs-research-synthesizer': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
26
27
  'dgs-debugger': { quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
27
28
  'dgs-codebase-mapper': { quality: 'sonnet', balanced: 'haiku', budget: 'haiku' },
28
29
  'dgs-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
29
30
  'dgs-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
30
31
  'dgs-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
32
+ 'dgs-review-analyst': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
31
33
  };
32
34
 
33
35
  // ─── Output helpers ───────────────────────────────────────────────────────────
@@ -71,9 +73,6 @@ function loadConfig(cwd) {
71
73
  model_profile: 'balanced',
72
74
  commit_docs: true,
73
75
  search_gitignored: false,
74
- branching_strategy: 'none',
75
- phase_branch_template: 'dgs/{project}/phase-{phase}-{slug}',
76
- milestone_branch_template: 'dgs/{project}/{milestone}-{slug}',
77
76
  base_branch: 'main',
78
77
  sync_push: 'off',
79
78
  sync_pull: 'off',
@@ -144,9 +143,6 @@ function loadConfig(cwd) {
144
143
  model_profile: get('model_profile') ?? defaults.model_profile,
145
144
  commit_docs: get('commit_docs', { section: 'planning', field: 'commit_docs' }) ?? defaults.commit_docs,
146
145
  search_gitignored: get('search_gitignored', { section: 'planning', field: 'search_gitignored' }) ?? defaults.search_gitignored,
147
- branching_strategy: get('branching_strategy', { section: 'git', field: 'branching_strategy' }) ?? defaults.branching_strategy,
148
- phase_branch_template: get('phase_branch_template', { section: 'git', field: 'phase_branch_template' }) ?? defaults.phase_branch_template,
149
- milestone_branch_template: get('milestone_branch_template', { section: 'git', field: 'milestone_branch_template' }) ?? defaults.milestone_branch_template,
150
146
  base_branch: get('base_branch', { section: 'git', field: 'base_branch' }) ?? defaults.base_branch,
151
147
  sync_push: get('sync_push', { section: 'git', field: 'sync_push' }) ?? defaults.sync_push,
152
148
  sync_pull: get('sync_pull', { section: 'git', field: 'sync_pull' }) ?? defaults.sync_pull,
@@ -472,7 +468,14 @@ function generateSlugInternal(text) {
472
468
 
473
469
  function getMilestoneInfo(cwd) {
474
470
  try {
475
- const roadmap = fs.readFileSync(path.join(getPlanningRoot(cwd), 'ROADMAP.md'), 'utf-8');
471
+ // Try project-scoped ROADMAP first, then planning root
472
+ let roadmap;
473
+ try {
474
+ const projectRoot = getProjectRoot(cwd);
475
+ roadmap = fs.readFileSync(path.join(cwd, projectRoot, 'ROADMAP.md'), 'utf-8');
476
+ } catch {
477
+ roadmap = fs.readFileSync(path.join(getPlanningRoot(cwd), 'ROADMAP.md'), 'utf-8');
478
+ }
476
479
 
477
480
  // First: check for list-format roadmaps using 🚧 (in-progress) marker
478
481
  // e.g. "- 🚧 **v2.1 Belgium** — Phases 24-28 (in progress)"
@@ -484,10 +487,20 @@ function getMilestoneInfo(cwd) {
484
487
  };
485
488
  }
486
489
 
487
- // Second: heading-format roadmaps — strip shipped milestones in <details> blocks
490
+ // Second: bullet-list format with "(in progress)" marker
491
+ // e.g. "- v19.0 Git Worktrees -- Phases 124-129 (in progress)"
492
+ const bulletMatch = roadmap.match(/^- v(\d+\.\d+)\s+(.+?)\s+--\s+Phases\s+\S+\s+\(in progress\)/m);
493
+ if (bulletMatch) {
494
+ return {
495
+ version: 'v' + bulletMatch[1],
496
+ name: bulletMatch[2].trim(),
497
+ };
498
+ }
499
+
500
+ // Third: heading-format roadmaps — strip shipped milestones in <details> blocks
488
501
  const cleaned = roadmap.replace(/<details>[\s\S]*?<\/details>/gi, '');
489
- // Extract version and name from the same ## heading for consistency
490
- const headingMatch = cleaned.match(/## .*v(\d+\.\d+)[:\s]+([^\n(]+)/);
502
+ // e.g. "### v19.0 Git Worktrees (In Progress)"
503
+ const headingMatch = cleaned.match(/#{2,3}\s+v(\d+\.\d+)[:\s]+([^\n(]+)/);
491
504
  if (headingMatch) {
492
505
  return {
493
506
  version: 'v' + headingMatch[1],
@@ -513,7 +526,13 @@ function getMilestoneInfo(cwd) {
513
526
  function getMilestonePhaseFilter(cwd) {
514
527
  const milestonePhaseNums = new Set();
515
528
  try {
516
- const roadmap = fs.readFileSync(path.join(getPlanningRoot(cwd), 'ROADMAP.md'), 'utf-8');
529
+ let roadmap;
530
+ try {
531
+ const projectRoot = getProjectRoot(cwd);
532
+ roadmap = fs.readFileSync(path.join(cwd, projectRoot, 'ROADMAP.md'), 'utf-8');
533
+ } catch {
534
+ roadmap = fs.readFileSync(path.join(getPlanningRoot(cwd), 'ROADMAP.md'), 'utf-8');
535
+ }
517
536
  const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
518
537
  let m;
519
538
  while ((m = phasePattern.exec(roadmap)) !== null) {
@@ -673,7 +692,9 @@ function isProjectCompleted(cwd, slug) {
673
692
  * List project folders in a v2 install.
674
693
  * Scans projects/ directly and returns directories that:
675
694
  * - Don't start with '.'
676
- * - Contain a STATE.md file (qualifying marker for project folders)
695
+ * - Contain a PROJECT.md file (canonical project marker; STATE.md is milestone state
696
+ * and may be absent for thin-skeleton projects created by /dgs:new-project before
697
+ * /dgs:new-milestone has run)
677
698
  *
678
699
  * Returns empty array if the projects/ directory does not exist, for v1 installs, or on any error.
679
700
  *
@@ -688,8 +709,8 @@ function getProjectFolders(cwd) {
688
709
  .filter(e => e.isDirectory())
689
710
  .filter(e => !e.name.startsWith('.'))
690
711
  .filter(e => {
691
- // Must contain STATE.md to qualify as a project
692
- return fs.existsSync(path.join(projectsDir, e.name, 'STATE.md'));
712
+ // Must contain PROJECT.md to qualify as a project
713
+ return fs.existsSync(path.join(projectsDir, e.name, 'PROJECT.md'));
693
714
  })
694
715
  .map(e => e.name)
695
716
  .sort();
@@ -24,6 +24,8 @@ const {
24
24
  loadConfig,
25
25
  isProjectCompleted,
26
26
  getProjectDir,
27
+ resolveModelInternal,
28
+ MODEL_PROFILES,
27
29
  } = require('./core.cjs');
28
30
 
29
31
  // ─── Root layout (no v2 markers) Tests ───────────────────────────────────────
@@ -388,7 +390,9 @@ describe('getProjectFolders', () => {
388
390
  const fixture = createFixture({
389
391
  'config.json': JSON.stringify({}),
390
392
  'PROJECTS.md': '# Projects\n',
393
+ 'projects/auth-overhaul/PROJECT.md': '# Project\n',
391
394
  'projects/auth-overhaul/STATE.md': '# State',
395
+ 'projects/dashboard-v2/PROJECT.md': '# Project\n',
392
396
  'projects/dashboard-v2/STATE.md': '# State',
393
397
  'phases/': null,
394
398
  'codebase/': null,
@@ -404,10 +408,11 @@ describe('getProjectFolders', () => {
404
408
  }
405
409
  });
406
410
 
407
- it('only includes directories containing STATE.md', () => {
411
+ it('only includes directories containing PROJECT.md', () => {
408
412
  const fixture = createFixture({
409
413
  'config.json': JSON.stringify({}),
410
414
  'PROJECTS.md': '# Projects\n',
415
+ 'projects/valid-project/PROJECT.md': '# Project\n',
411
416
  'projects/valid-project/STATE.md': '# State',
412
417
  'projects/empty-folder/': null,
413
418
  });
@@ -420,10 +425,43 @@ describe('getProjectFolders', () => {
420
425
  }
421
426
  });
422
427
 
428
+ it('returns thin-skeleton project folder containing PROJECT.md but no STATE.md', () => {
429
+ const fixture = createFixture({
430
+ 'config.json': JSON.stringify({}),
431
+ 'PROJECTS.md': '# Projects\n',
432
+ 'projects/word-gen/PROJECT.md': '# Project: Word Gen\n',
433
+ });
434
+
435
+ try {
436
+ const result = getProjectFolders(fixture.cwd);
437
+ assert.deepEqual(result, ['word-gen']);
438
+ } finally {
439
+ fixture.cleanup();
440
+ }
441
+ });
442
+
443
+ it('excludes directory with neither PROJECT.md nor STATE.md', () => {
444
+ const fixture = createFixture({
445
+ 'config.json': JSON.stringify({}),
446
+ 'PROJECTS.md': '# Projects\n',
447
+ 'projects/empty-folder/': null,
448
+ 'projects/real-project/PROJECT.md': '# Project\n',
449
+ });
450
+
451
+ try {
452
+ const result = getProjectFolders(fixture.cwd);
453
+ assert.deepEqual(result, ['real-project']);
454
+ } finally {
455
+ fixture.cleanup();
456
+ }
457
+ });
458
+
423
459
  it('excludes dot-directories', () => {
424
460
  const fixture = createFixture({
425
461
  'config.json': JSON.stringify({}),
462
+ 'projects/.hidden/PROJECT.md': '# Project\n',
426
463
  'projects/.hidden/STATE.md': '# State',
464
+ 'projects/real-project/PROJECT.md': '# Project\n',
427
465
  'projects/real-project/STATE.md': '# State',
428
466
  });
429
467
 
@@ -745,6 +783,7 @@ describe('root layout', () => {
745
783
  'config.json': JSON.stringify({}),
746
784
  'PROJECTS.md': '# Projects\n',
747
785
  'REPOS.md': '# Repos\n',
786
+ 'projects/proj-a/PROJECT.md': '# Project\n',
748
787
  'projects/proj-a/STATE.md': '# State\n',
749
788
  'projects/proj-a/phases/': null,
750
789
  });
@@ -837,3 +876,42 @@ describe('config two-file merge', () => {
837
876
  });
838
877
 
839
878
  });
879
+
880
+ // ─── MODEL_PROFILES dgs-idea-researcher tiering ──────────────────────────────
881
+
882
+ describe('MODEL_PROFILES dgs-idea-researcher tiering', () => {
883
+ let fixture;
884
+
885
+ afterEach(() => {
886
+ if (fixture) fixture.cleanup();
887
+ fixture = null;
888
+ });
889
+
890
+ it('MODEL_PROFILES table contains dgs-idea-researcher with quality/balanced/budget tiers', () => {
891
+ const profile = MODEL_PROFILES['dgs-idea-researcher'];
892
+ assert.ok(profile, 'MODEL_PROFILES[\'dgs-idea-researcher\'] should be defined');
893
+ assert.equal(typeof profile.quality, 'string', 'quality tier present');
894
+ assert.equal(typeof profile.balanced, 'string', 'balanced tier present');
895
+ assert.equal(typeof profile.budget, 'string', 'budget tier present');
896
+ });
897
+
898
+ it('resolveModelInternal returns inherit for dgs-idea-researcher on quality profile', () => {
899
+ fixture = createTempProject({ withConfig: { model_profile: 'quality' } });
900
+ const resolved = resolveModelInternal(fixture.cwd, 'dgs-idea-researcher');
901
+ // Per core.cjs: when the resolved tier is 'opus', the function returns 'inherit'.
902
+ assert.equal(resolved, 'inherit');
903
+ });
904
+
905
+ it('resolveModelInternal returns sonnet for dgs-idea-researcher on balanced profile', () => {
906
+ fixture = createTempProject({ withConfig: { model_profile: 'balanced' } });
907
+ const resolved = resolveModelInternal(fixture.cwd, 'dgs-idea-researcher');
908
+ assert.equal(resolved, 'sonnet');
909
+ });
910
+
911
+ it('resolveModelInternal returns haiku for dgs-idea-researcher on budget profile', () => {
912
+ fixture = createTempProject({ withConfig: { model_profile: 'budget' } });
913
+ const resolved = resolveModelInternal(fixture.cwd, 'dgs-idea-researcher');
914
+ assert.equal(resolved, 'haiku');
915
+ });
916
+
917
+ });
@@ -9,11 +9,15 @@
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
  const { execGit, safeReadFile, loadConfig, output, error } = require('./core.cjs');
12
- const { getPlanningRoot } = require('./paths.cjs');
12
+ const { getPlanningRoot, PROJECTS_DIR } = require('./paths.cjs');
13
13
  const { parseReposMd } = require('./repos.cjs');
14
14
  const { extractFrontmatter } = require('./frontmatter.cjs');
15
15
  const { scanProjectReposTags } = require('./projects.cjs');
16
16
 
17
+ // Lazy require to avoid circular dependency (context.cjs does not require execution.cjs,
18
+ // but keeping it lazy is a safety practice for future refactoring).
19
+ function _getResolveCodeContext() { return require('./context.cjs').resolveCodeContext; }
20
+
17
21
  // ─── Repo Path Resolution ───────────────────────────────────────────────────
18
22
 
19
23
  /**
@@ -24,7 +28,26 @@ const { scanProjectReposTags } = require('./projects.cjs');
24
28
  * @param {Array} repos - Parsed repos array (optional, loads from REPOS.md if not provided)
25
29
  * @returns {{ absPath: string, repo: Object } | null}
26
30
  */
27
- function resolveRepoPath(cwd, repoName, repos) {
31
+ function resolveRepoPath(cwd, repoName, repos, useActiveContext) {
32
+ // If active context requested, resolve to worktree directory
33
+ if (useActiveContext) {
34
+ try {
35
+ const ctx = _getResolveCodeContext()(cwd, repoName);
36
+ if (ctx && ctx.type !== 'main') {
37
+ // Resolve the repo object for metadata (still need repo.name etc.)
38
+ if (!repos) {
39
+ const parsed = parseReposMd(cwd);
40
+ if (parsed) repos = parsed.repos;
41
+ }
42
+ const repo = repos ? repos.find(r => r.name === repoName) : null;
43
+ return { absPath: ctx.directory, repo: repo || { name: repoName, path: ctx.directory } };
44
+ }
45
+ } catch {
46
+ // Fall through to standard resolution
47
+ }
48
+ }
49
+
50
+ // Standard resolution via REPOS.md
28
51
  if (!repos) {
29
52
  const parsed = parseReposMd(cwd);
30
53
  if (!parsed) return null;
@@ -48,8 +71,8 @@ function resolveRepoPath(cwd, repoName, repos) {
48
71
  * @param {Array} repos - Parsed repos array (optional, loads from REPOS.md if not provided)
49
72
  * @returns {{ absFilePath: string, repoAbsPath: string, repo: Object } | null}
50
73
  */
51
- function resolveRepoRelativePath(cwd, repoName, relativePath, repos) {
52
- const resolved = resolveRepoPath(cwd, repoName, repos);
74
+ function resolveRepoRelativePath(cwd, repoName, relativePath, repos, useActiveContext) {
75
+ const resolved = resolveRepoPath(cwd, repoName, repos, useActiveContext);
53
76
  if (!resolved) return null;
54
77
  const absFilePath = path.join(resolved.absPath, relativePath);
55
78
  return { absFilePath, repoAbsPath: resolved.absPath, repo: resolved.repo };
@@ -65,14 +88,14 @@ function resolveRepoRelativePath(cwd, repoName, relativePath, repos) {
65
88
  * @param {string} [phaseDir] - Optional phase directory to check for partial SUMMARY.md
66
89
  * @returns {{ passed: boolean, dirty_repos: Array, partial_execution: Object|null, missing_repos: string[] }}
67
90
  */
68
- function preflightCheck(cwd, repoNames, phaseDir) {
91
+ function preflightCheck(cwd, repoNames, phaseDir, useActiveContext) {
69
92
  const parsed = parseReposMd(cwd);
70
93
  const repos = parsed ? parsed.repos : [];
71
94
  const dirty_repos = [];
72
95
  const missing_repos = [];
73
96
 
74
97
  for (const name of repoNames) {
75
- const resolved = resolveRepoPath(cwd, name, repos);
98
+ const resolved = resolveRepoPath(cwd, name, repos, useActiveContext);
76
99
  if (!resolved) {
77
100
  missing_repos.push(name);
78
101
  continue;
@@ -316,13 +339,13 @@ function buildPlanningCommitBody(repoResults) {
316
339
  * @param {string[]} repoNames - Repo names to check
317
340
  * @returns {Array<{repoName: string, repoPath: string, files: string[]}>}
318
341
  */
319
- function detectRepoChanges(cwd, repoNames) {
342
+ function detectRepoChanges(cwd, repoNames, useActiveContext) {
320
343
  const parsed = parseReposMd(cwd);
321
344
  const repos = parsed ? parsed.repos : [];
322
345
  const changes = [];
323
346
 
324
347
  for (const name of repoNames) {
325
- const resolved = resolveRepoPath(cwd, name, repos);
348
+ const resolved = resolveRepoPath(cwd, name, repos, useActiveContext);
326
349
  if (!resolved) continue;
327
350
  if (!fs.existsSync(resolved.absPath)) continue;
328
351
 
@@ -347,7 +370,7 @@ function detectRepoChanges(cwd, repoNames) {
347
370
  if (files.length > 0) {
348
371
  changes.push({
349
372
  repoName: name,
350
- repoPath: resolved.repo.path,
373
+ repoPath: useActiveContext ? resolved.absPath : resolved.repo.path,
351
374
  files,
352
375
  });
353
376
  }
@@ -360,7 +383,6 @@ function detectRepoChanges(cwd, repoNames) {
360
383
 
361
384
  /**
362
385
  * Create branches in each repo for multi-repo execution.
363
- * Respects branching_strategy config — if 'none', skips branch creation.
364
386
  * Reuses existing branch if it already exists.
365
387
  * Detects branch prefix collisions and warns about potential ambiguity.
366
388
  *
@@ -375,15 +397,12 @@ function detectRepoChanges(cwd, repoNames) {
375
397
  * @param {string} cwd - Product root
376
398
  * @param {string[]} repoNames - Repo names
377
399
  * @param {string} branchName - Branch name (e.g., dgs/project/phase-slug)
378
- * @param {{ branching_strategy?: string }} config - Config object
400
+ * @param {object} [config] - Config object (legacy parameter, kept for backward compatibility)
379
401
  * @param {string|null} [baseBranch=null] - Base branch to checkout before creating new branch.
380
402
  * When null/undefined, creates branch from wherever HEAD is (backwards-compatible behavior).
381
403
  * @returns {{ created: boolean, reason?: string, error?: string, repo?: string, branches?: Array<{repo: string, branch: string, action: string}>, warnings?: Array }}
382
404
  */
383
405
  function createRepoBranches(cwd, repoNames, branchName, config, baseBranch) {
384
- if (config && config.branching_strategy === 'none') {
385
- return { created: false, reason: 'branching_disabled' };
386
- }
387
406
 
388
407
  const parsed = parseReposMd(cwd);
389
408
  const repos = parsed ? parsed.repos : [];
@@ -521,7 +540,7 @@ function updateRepoStatus(cwd, projectSlug, repoResults) {
521
540
  let statePath;
522
541
  const planRoot = getPlanningRoot(cwd);
523
542
  if (projectSlug) {
524
- statePath = path.join(planRoot, projectSlug, 'STATE.md');
543
+ statePath = path.join(planRoot, PROJECTS_DIR, projectSlug, 'STATE.md');
525
544
  }
526
545
  if (!statePath || !fs.existsSync(statePath)) {
527
546
  statePath = path.join(planRoot, 'STATE.md');
@@ -622,8 +641,21 @@ function cmdCommitMultiRepo(cwd, options, raw) {
622
641
 
623
642
  const warnings = [];
624
643
 
644
+ // Determine whether to resolve repos against active worktree
645
+ let useActiveContext = false;
646
+ try {
647
+ const { getLocalConfigPath } = require('./config.cjs');
648
+ const localPath = getLocalConfigPath(cwd);
649
+ if (fs.existsSync(localPath)) {
650
+ const localCfg = JSON.parse(fs.readFileSync(localPath, 'utf-8'));
651
+ if (localCfg.execution && localCfg.execution.active_context) {
652
+ useActiveContext = true;
653
+ }
654
+ }
655
+ } catch { /* fall back to standard resolution */ }
656
+
625
657
  // Run preflight
626
- const preflight = preflightCheck(cwd, repoNames, options.phaseDir || null);
658
+ const preflight = preflightCheck(cwd, repoNames, options.phaseDir || null, useActiveContext);
627
659
 
628
660
  // Missing repos is a hard error — block execution
629
661
  if (preflight.missing_repos && preflight.missing_repos.length > 0) {
@@ -649,7 +681,7 @@ function cmdCommitMultiRepo(cwd, options, raw) {
649
681
  }
650
682
 
651
683
  // Detect changes
652
- const changes = detectRepoChanges(cwd, repoNames);
684
+ const changes = detectRepoChanges(cwd, repoNames, useActiveContext);
653
685
  if (changes.length === 0) {
654
686
  output({ success: true, message: 'No changes detected', commits: [], warnings }, raw);
655
687
  return;