@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.
- package/CHANGELOG.md +197 -0
- package/README.md +34 -2
- package/agents/dgs-executor.md +124 -3
- package/agents/dgs-idea-researcher.md +447 -0
- package/agents/dgs-plan-checker.md +61 -3
- package/agents/dgs-planner.md +51 -8
- package/bin/install.js +44 -0
- package/commands/dgs/abandon-quick.md +28 -0
- package/commands/dgs/add-tests.md +2 -2
- package/commands/dgs/audit-milestone.md +4 -3
- package/commands/dgs/capture-principle.md +11 -11
- package/commands/dgs/cleanup.md +2 -2
- package/commands/dgs/complete-milestone.md +11 -11
- package/commands/dgs/complete-quick.md +28 -0
- package/commands/dgs/create-milestone-job.md +2 -2
- package/commands/dgs/debug.md +3 -3
- package/commands/dgs/develop-idea.md +1 -1
- package/commands/dgs/diff-report.md +124 -0
- package/commands/dgs/fast.md +3 -1
- package/commands/dgs/health.md +1 -1
- package/commands/dgs/map-codebase.md +6 -6
- package/commands/dgs/new-milestone.md +5 -5
- package/commands/dgs/new-project.md +8 -21
- package/commands/dgs/package-scan.md +43 -0
- package/commands/dgs/plan-milestone-gaps.md +1 -1
- package/commands/dgs/progress.md +3 -3
- package/commands/dgs/quick-abandon.md +8 -0
- package/commands/dgs/quick-complete.md +8 -0
- package/commands/dgs/quick.md +10 -3
- package/commands/dgs/research-idea.md +3 -2
- package/commands/dgs/research-phase.md +3 -3
- package/commands/dgs/switch-project.md +14 -1
- package/commands/dgs/write-spec.md +3 -3
- package/deliver-great-systems/bin/dgs-tools.cjs +401 -32
- package/deliver-great-systems/bin/lib/audit-tolerance.cjs +77 -0
- package/deliver-great-systems/bin/lib/audit-tolerance.test.cjs +101 -0
- package/deliver-great-systems/bin/lib/commands.cjs +626 -46
- package/deliver-great-systems/bin/lib/commands.test.cjs +451 -0
- package/deliver-great-systems/bin/lib/commit-verify.test.cjs +236 -0
- package/deliver-great-systems/bin/lib/config.cjs +80 -6
- package/deliver-great-systems/bin/lib/config.test.cjs +309 -0
- package/deliver-great-systems/bin/lib/context.cjs +120 -0
- package/deliver-great-systems/bin/lib/core.cjs +35 -14
- package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
- package/deliver-great-systems/bin/lib/execution.cjs +49 -17
- package/deliver-great-systems/bin/lib/fast-routing.cjs +199 -0
- package/deliver-great-systems/bin/lib/fast-routing.test.cjs +108 -0
- package/deliver-great-systems/bin/lib/final-commit-precondition.test.cjs +87 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/bundler-audit-gemfile.json +21 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-expected.md +186 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-runresult.json +235 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/govulncheck-import.json +3 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/npm-audit-v10.json +37 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-clean.json +3 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-vulns.json +77 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/pip-audit-requirements.json +28 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-lodash.json +30 -0
- package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-workspaces.json +55 -0
- package/deliver-great-systems/bin/lib/flat-migration.test.cjs +396 -0
- package/deliver-great-systems/bin/lib/frontmatter.cjs +1 -1
- package/deliver-great-systems/bin/lib/governance.cjs +211 -0
- package/deliver-great-systems/bin/lib/governance.test.cjs +339 -0
- package/deliver-great-systems/bin/lib/health-untracked-phase.test.cjs +269 -0
- package/deliver-great-systems/bin/lib/ideas.cjs +206 -91
- package/deliver-great-systems/bin/lib/ideas.test.cjs +244 -1
- package/deliver-great-systems/bin/lib/init.cjs +357 -61
- package/deliver-great-systems/bin/lib/init.test.cjs +625 -8
- package/deliver-great-systems/bin/lib/jobs.cjs +131 -25
- package/deliver-great-systems/bin/lib/jobs.test.cjs +193 -74
- package/deliver-great-systems/bin/lib/migration.cjs +409 -1
- package/deliver-great-systems/bin/lib/migration.test.cjs +158 -1
- package/deliver-great-systems/bin/lib/milestone.cjs +154 -31
- package/deliver-great-systems/bin/lib/milestone.test.cjs +203 -0
- package/deliver-great-systems/bin/lib/package-adapters.cjs +530 -0
- package/deliver-great-systems/bin/lib/package-adapters.test.cjs +618 -0
- package/deliver-great-systems/bin/lib/package-ecosystems.cjs +350 -0
- package/deliver-great-systems/bin/lib/package-ecosystems.test.cjs +348 -0
- package/deliver-great-systems/bin/lib/package-runner.cjs +199 -0
- package/deliver-great-systems/bin/lib/package-runner.test.cjs +198 -0
- package/deliver-great-systems/bin/lib/package-scan-provenance.cjs +56 -0
- package/deliver-great-systems/bin/lib/package-scan-provenance.test.cjs +103 -0
- package/deliver-great-systems/bin/lib/package-scan-report.cjs +1140 -0
- package/deliver-great-systems/bin/lib/package-scan-report.test.cjs +1963 -0
- package/deliver-great-systems/bin/lib/package-scan-skill.cjs +96 -0
- package/deliver-great-systems/bin/lib/package-scan-skill.test.cjs +136 -0
- package/deliver-great-systems/bin/lib/package-scan.cjs +919 -0
- package/deliver-great-systems/bin/lib/package-scan.test.cjs +2147 -0
- package/deliver-great-systems/bin/lib/phase.cjs +146 -3
- package/deliver-great-systems/bin/lib/phase.test.cjs +420 -0
- package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
- package/deliver-great-systems/bin/lib/projects.cjs +65 -10
- package/deliver-great-systems/bin/lib/projects.test.cjs +198 -2
- package/deliver-great-systems/bin/lib/quick.cjs +739 -0
- package/deliver-great-systems/bin/lib/quick.test.cjs +730 -0
- package/deliver-great-systems/bin/lib/repos.cjs +37 -13
- package/deliver-great-systems/bin/lib/review.cjs +1821 -0
- package/deliver-great-systems/bin/lib/roadmap.cjs +34 -13
- package/deliver-great-systems/bin/lib/specs.cjs +3 -81
- package/deliver-great-systems/bin/lib/state-transition-gate.test.cjs +160 -0
- package/deliver-great-systems/bin/lib/state.cjs +147 -55
- package/deliver-great-systems/bin/lib/summary-frontmatter.cjs +54 -0
- package/deliver-great-systems/bin/lib/summary-frontmatter.test.cjs +78 -0
- package/deliver-great-systems/bin/lib/sweep-scope.test.cjs +263 -0
- package/deliver-great-systems/bin/lib/sync.cjs +75 -0
- package/deliver-great-systems/bin/lib/verify.cjs +198 -7
- package/deliver-great-systems/bin/lib/verify.test.cjs +82 -0
- package/deliver-great-systems/bin/lib/wave-0-template-rename.test.cjs +40 -0
- package/deliver-great-systems/bin/lib/worktrees.cjs +790 -0
- package/deliver-great-systems/bin/lib/worktrees.test.cjs +963 -0
- package/deliver-great-systems/references/agent-step-reliability.md +60 -0
- package/deliver-great-systems/references/conflict-resolution.md +4 -0
- package/deliver-great-systems/references/context-tiers.md +4 -0
- package/deliver-great-systems/references/package-scan-config.md +151 -0
- package/deliver-great-systems/references/questioning.md +0 -30
- package/deliver-great-systems/references/spec-review-loop.md +1 -2
- package/deliver-great-systems/references/workflow-conventions.md +29 -0
- package/deliver-great-systems/skills/dgs-tests/package-scan.md +44 -0
- package/deliver-great-systems/templates/REVIEW.md +35 -0
- package/deliver-great-systems/templates/VALIDATION.md +1 -1
- package/deliver-great-systems/templates/claude-md.md +27 -0
- package/deliver-great-systems/templates/package-scan-report.md +108 -0
- package/deliver-great-systems/templates/project.md +6 -170
- package/deliver-great-systems/templates/summary.md +3 -1
- package/deliver-great-systems/workflows/abandon-quick.md +89 -0
- package/deliver-great-systems/workflows/add-idea.md +3 -3
- package/deliver-great-systems/workflows/add-phase.md +5 -0
- package/deliver-great-systems/workflows/add-tests.md +14 -0
- package/deliver-great-systems/workflows/add-todo.md +1 -0
- package/deliver-great-systems/workflows/approve-spec.md +25 -4
- package/deliver-great-systems/workflows/audit-milestone.md +66 -10
- package/deliver-great-systems/workflows/audit-phase.md +15 -5
- package/deliver-great-systems/workflows/cancel-job.md +2 -2
- package/deliver-great-systems/workflows/check-todos.md +2 -3
- package/deliver-great-systems/workflows/codereview.md +103 -9
- package/deliver-great-systems/workflows/complete-milestone.md +218 -24
- package/deliver-great-systems/workflows/complete-quick.md +106 -0
- package/deliver-great-systems/workflows/consolidate-ideas.md +1 -1
- package/deliver-great-systems/workflows/create-milestone-job.md +4 -4
- package/deliver-great-systems/workflows/develop-idea.md +11 -11
- package/deliver-great-systems/workflows/diagnose-issues.md +14 -0
- package/deliver-great-systems/workflows/discuss-idea.md +1 -1
- package/deliver-great-systems/workflows/discuss-phase.md +3 -2
- package/deliver-great-systems/workflows/execute-phase.md +209 -33
- package/deliver-great-systems/workflows/execute-plan.md +22 -22
- package/deliver-great-systems/workflows/help.md +53 -20
- package/deliver-great-systems/workflows/import-spec.md +65 -7
- package/deliver-great-systems/workflows/init-product.md +45 -167
- package/deliver-great-systems/workflows/new-milestone.md +140 -33
- package/deliver-great-systems/workflows/new-project.md +60 -331
- package/deliver-great-systems/workflows/package-scan.md +59 -0
- package/deliver-great-systems/workflows/plan-phase.md +79 -1
- package/deliver-great-systems/workflows/progress-all.md +133 -0
- package/deliver-great-systems/workflows/quick-abandon.md +89 -0
- package/deliver-great-systems/workflows/quick-complete.md +106 -0
- package/deliver-great-systems/workflows/quick.md +328 -26
- package/deliver-great-systems/workflows/refine-spec.md +1 -1
- package/deliver-great-systems/workflows/research-idea.md +77 -139
- package/deliver-great-systems/workflows/resume-project.md +2 -2
- package/deliver-great-systems/workflows/run-job.md +29 -43
- package/deliver-great-systems/workflows/settings.md +13 -77
- package/deliver-great-systems/workflows/validate-phase.md +39 -1
- package/deliver-great-systems/workflows/verify-work.md +14 -0
- package/deliver-great-systems/workflows/write-spec.md +11 -13
- package/hooks/dist/dgs-enforce-discipline.js +196 -0
- package/package.json +1 -1
- 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
|
-
|
|
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:
|
|
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
|
-
//
|
|
490
|
-
const headingMatch = cleaned.match(
|
|
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
|
-
|
|
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
|
|
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
|
|
692
|
-
return fs.existsSync(path.join(projectsDir, e.name, '
|
|
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
|
|
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 {
|
|
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;
|