@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// deliver-great-systems/bin/lib/fast-routing.cjs
|
|
2
|
+
//
|
|
3
|
+
// REL-05/06: Fast-mode commit routing helpers for /dgs:fast.
|
|
4
|
+
//
|
|
5
|
+
// Exports:
|
|
6
|
+
// surveyDirty(planningRoot) -> { planningRoot: { dirty, paths }, subRepos: [{ name, path, dirty, paths }], submodules: [{ path, dirty }] }
|
|
7
|
+
// decideRouting(survey) -> { action: 'route-to-planning-root'|'route-to-sub-repo'|'fail', repoCwd?, exitCode?, warnings?: [string], message?: string }
|
|
8
|
+
// enumerateSubmodules(repoPath) -> [{ path, dirty }]
|
|
9
|
+
//
|
|
10
|
+
// REL-05: decideRouting routes commits to the correct repo on multi-repo products.
|
|
11
|
+
// REL-06: surveyDirty + a thin pre-edit caller (in dgs-tools dispatcher) implements pre-edit dirt check.
|
|
12
|
+
//
|
|
13
|
+
// Both helpers honour the fail-loudly contract: surveyDirty returns structured data;
|
|
14
|
+
// decideRouting names the exit-code label (`multi-repo-dirt`) and includes a 1-3 line
|
|
15
|
+
// remediation message suitable for direct display.
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { execGit } = require('./core.cjs');
|
|
20
|
+
|
|
21
|
+
// Inline REPOS.md parser — does NOT use repos.cjs's parseReposMd because that
|
|
22
|
+
// helper goes through getPlanningRoot() which has a per-process cache that
|
|
23
|
+
// breaks tests using multiple temp planning roots in the same process.
|
|
24
|
+
function parseReposMdLocal(planningRoot) {
|
|
25
|
+
const filePath = path.join(planningRoot, 'REPOS.md');
|
|
26
|
+
let content;
|
|
27
|
+
try {
|
|
28
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
29
|
+
} catch {
|
|
30
|
+
return { repos: [] };
|
|
31
|
+
}
|
|
32
|
+
if (!content || !content.startsWith('# Repos')) return { repos: [] };
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
let tableStart = -1;
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
if (lines[i].startsWith('|') && lines[i].includes('Name')) { tableStart = i; break; }
|
|
37
|
+
}
|
|
38
|
+
if (tableStart === -1) return { repos: [] };
|
|
39
|
+
const repos = [];
|
|
40
|
+
for (let i = tableStart + 2; i < lines.length; i++) {
|
|
41
|
+
const line = lines[i].trim();
|
|
42
|
+
if (!line.startsWith('|')) break;
|
|
43
|
+
const cells = line.split('|').map(c => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
|
|
44
|
+
if (cells.length < 2) continue;
|
|
45
|
+
const [name, p] = cells;
|
|
46
|
+
if (!name || !p) continue;
|
|
47
|
+
repos.push({ name, path: p });
|
|
48
|
+
}
|
|
49
|
+
return { repos };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readConfigLocalJson(planningRoot) {
|
|
53
|
+
const cfgPath = path.join(planningRoot, 'config.local.json');
|
|
54
|
+
if (!fs.existsSync(cfgPath)) return {};
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function enumerateRegisteredRepos(planningRoot) {
|
|
63
|
+
const seen = new Map(); // absolute path -> { name, path }
|
|
64
|
+
|
|
65
|
+
// Source 1: REPOS.md (inline parser, bypassing repos.cjs cache)
|
|
66
|
+
const reposMd = parseReposMdLocal(planningRoot);
|
|
67
|
+
for (const r of (reposMd.repos || [])) {
|
|
68
|
+
const abs = path.resolve(planningRoot, r.path);
|
|
69
|
+
if (!seen.has(abs)) seen.set(abs, { name: r.name, path: abs });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Source 2: config.local.json projects.*.worktrees.*.repos (map of name -> absolute path)
|
|
73
|
+
const cfg = readConfigLocalJson(planningRoot);
|
|
74
|
+
const projects = (cfg.projects || {});
|
|
75
|
+
for (const proj of Object.values(projects)) {
|
|
76
|
+
const wts = (proj.worktrees || {});
|
|
77
|
+
for (const wt of Object.values(wts)) {
|
|
78
|
+
const repos = (wt.repos || {});
|
|
79
|
+
for (const [name, abs] of Object.entries(repos)) {
|
|
80
|
+
if (typeof abs !== 'string') continue;
|
|
81
|
+
const resolved = path.resolve(abs);
|
|
82
|
+
if (!seen.has(resolved)) seen.set(resolved, { name, path: resolved });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return Array.from(seen.values());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isGitRepo(p) {
|
|
91
|
+
try {
|
|
92
|
+
const res = execGit(p, ['rev-parse', '--is-inside-work-tree']);
|
|
93
|
+
if (!res || res.exitCode !== 0) return false;
|
|
94
|
+
return typeof res.stdout === 'string' && res.stdout.trim() === 'true';
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function porcelainPaths(repoPath) {
|
|
101
|
+
if (!isGitRepo(repoPath)) return null;
|
|
102
|
+
let out = '';
|
|
103
|
+
try {
|
|
104
|
+
const res = execGit(repoPath, ['status', '--porcelain']);
|
|
105
|
+
out = (res && res.stdout) || '';
|
|
106
|
+
} catch {
|
|
107
|
+
out = '';
|
|
108
|
+
}
|
|
109
|
+
// git status --porcelain format: "XY <path>" where XY is a 2-char status code
|
|
110
|
+
// Strip the 2-char status + 1 space; trim and filter empties.
|
|
111
|
+
return out
|
|
112
|
+
.split('\n')
|
|
113
|
+
.map(l => l.replace(/^.{2,3}/, '').trim())
|
|
114
|
+
.filter(Boolean);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function enumerateSubmodules(repoPath) {
|
|
118
|
+
if (!isGitRepo(repoPath)) return [];
|
|
119
|
+
let out = '';
|
|
120
|
+
try {
|
|
121
|
+
const res = execGit(repoPath, ['submodule', 'status']);
|
|
122
|
+
out = (res && res.stdout) || '';
|
|
123
|
+
} catch {
|
|
124
|
+
out = '';
|
|
125
|
+
}
|
|
126
|
+
return out
|
|
127
|
+
.split('\n')
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.map(line => {
|
|
130
|
+
// Format: ` <sha> <path> (<ref>)` — leading char `+` (mismatch),
|
|
131
|
+
// `-` (uninitialised), ` ` (clean), `U` (conflict)
|
|
132
|
+
const lead = line[0];
|
|
133
|
+
const match = line.trim().match(/^[+\- U]?\w+\s+(\S+)/);
|
|
134
|
+
return {
|
|
135
|
+
path: match ? match[1] : line.trim(),
|
|
136
|
+
dirty: Boolean(lead && lead !== ' '),
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function surveyDirty(planningRoot) {
|
|
142
|
+
const planningPaths = porcelainPaths(planningRoot) || [];
|
|
143
|
+
const planningRootEntry = { dirty: planningPaths.length > 0, paths: planningPaths };
|
|
144
|
+
|
|
145
|
+
const subRepos = [];
|
|
146
|
+
for (const repo of enumerateRegisteredRepos(planningRoot)) {
|
|
147
|
+
if (path.resolve(repo.path) === path.resolve(planningRoot)) continue; // skip if same dir
|
|
148
|
+
const paths = porcelainPaths(repo.path) || [];
|
|
149
|
+
subRepos.push({ name: repo.name, path: repo.path, dirty: paths.length > 0, paths });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const submodules = enumerateSubmodules(planningRoot);
|
|
153
|
+
|
|
154
|
+
return { planningRoot: planningRootEntry, subRepos, submodules };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function decideRouting(survey) {
|
|
158
|
+
const warnings = [];
|
|
159
|
+
const dirtySubmodules = (survey.submodules || []).filter(s => s.dirty);
|
|
160
|
+
for (const sm of dirtySubmodules) {
|
|
161
|
+
warnings.push(
|
|
162
|
+
`submodule changes detected at ${sm.path}; auto-routing not supported for submodules — commit manually or use /dgs:quick`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const planningRoot = survey.planningRoot || { dirty: false };
|
|
167
|
+
const subRepos = survey.subRepos || [];
|
|
168
|
+
const dirtySubRepos = subRepos.filter(r => r.dirty);
|
|
169
|
+
|
|
170
|
+
// Case (c): multiple sub-repos dirty OR planning + at least one sub-repo dirty
|
|
171
|
+
if (dirtySubRepos.length > 1 || (dirtySubRepos.length >= 1 && planningRoot.dirty)) {
|
|
172
|
+
return {
|
|
173
|
+
action: 'fail',
|
|
174
|
+
exitCode: 'multi-repo-dirt',
|
|
175
|
+
message:
|
|
176
|
+
'Multiple repos have dirty changes — fast-mode cannot auto-route. Review state with `git status` in each repo, run `/dgs:fast --repo <name>` per repo, or use `/dgs:quick` for multi-repo work.',
|
|
177
|
+
warnings,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Case (a): exactly one sub-repo dirty + planning clean
|
|
182
|
+
if (dirtySubRepos.length === 1 && !planningRoot.dirty) {
|
|
183
|
+
return {
|
|
184
|
+
action: 'route-to-sub-repo',
|
|
185
|
+
repoCwd: dirtySubRepos[0].path,
|
|
186
|
+
warnings,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Case (b): only planning root dirty (or nothing dirty — same code path)
|
|
191
|
+
return { action: 'route-to-planning-root', warnings };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
surveyDirty,
|
|
196
|
+
decideRouting,
|
|
197
|
+
enumerateSubmodules,
|
|
198
|
+
enumerateRegisteredRepos,
|
|
199
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// deliver-great-systems/bin/lib/fast-routing.test.cjs
|
|
2
|
+
// REL-05/06 regression test scaffold — initially RED. Turns GREEN after plan 03
|
|
3
|
+
// implements bin/lib/fast-routing.cjs with surveyDirty + decideRouting helpers.
|
|
4
|
+
|
|
5
|
+
const test = require('node:test');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const os = require('node:os');
|
|
10
|
+
const { execSync } = require('node:child_process');
|
|
11
|
+
|
|
12
|
+
function getModule() {
|
|
13
|
+
try {
|
|
14
|
+
return require('./fast-routing.cjs');
|
|
15
|
+
} catch (err) {
|
|
16
|
+
assert.fail('REL-05/06 module not yet implemented: bin/lib/fast-routing.cjs (plan 03 task)');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setupMultiRepoFixture() {
|
|
21
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fast-routing-'));
|
|
22
|
+
execSync('git init -q', { cwd: root });
|
|
23
|
+
execSync('git config user.email test@test', { cwd: root });
|
|
24
|
+
execSync('git config user.name test', { cwd: root });
|
|
25
|
+
const subRepo = path.join(root, '..', `fast-routing-sub-${path.basename(root)}`);
|
|
26
|
+
fs.mkdirSync(subRepo);
|
|
27
|
+
execSync('git init -q', { cwd: subRepo });
|
|
28
|
+
execSync('git config user.email test@test', { cwd: subRepo });
|
|
29
|
+
execSync('git config user.name test', { cwd: subRepo });
|
|
30
|
+
fs.writeFileSync(
|
|
31
|
+
path.join(root, 'REPOS.md'),
|
|
32
|
+
`# Repos\n\n| Name | Path | GitHub URL | Description |\n|------|------|------------|-------------|\n| sub | ${subRepo} | git@x:y.git | test sub-repo |\n`
|
|
33
|
+
);
|
|
34
|
+
// Commit something so the planning-root tree isn't entirely empty
|
|
35
|
+
execSync('git add REPOS.md', { cwd: root });
|
|
36
|
+
execSync('git commit -q -m init', { cwd: root });
|
|
37
|
+
return { root, subRepo };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('REL-06: surveyDirty detects pre-existing dirt in planning root (pre-existing-dirt)', () => {
|
|
41
|
+
const m = getModule();
|
|
42
|
+
const { root } = setupMultiRepoFixture();
|
|
43
|
+
fs.writeFileSync(path.join(root, 'dirty.txt'), 'pre-existing');
|
|
44
|
+
const survey = m.surveyDirty(root);
|
|
45
|
+
assert.ok(survey.planningRoot.dirty, 'planning root should be flagged dirty');
|
|
46
|
+
assert.ok((survey.planningRoot.paths || []).some(p => /dirty\.txt/.test(p)), 'dirty.txt must appear in paths');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('REL-06: surveyDirty detects pre-existing dirt in registered sub-repo', () => {
|
|
50
|
+
const m = getModule();
|
|
51
|
+
const { root, subRepo } = setupMultiRepoFixture();
|
|
52
|
+
fs.writeFileSync(path.join(subRepo, 'dirty.txt'), 'pre-existing');
|
|
53
|
+
const survey = m.surveyDirty(root);
|
|
54
|
+
const subEntry = (survey.subRepos || []).find(r => r.name === 'sub');
|
|
55
|
+
assert.ok(subEntry && subEntry.dirty, 'sub-repo should be flagged dirty');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('REL-05: decideRouting returns route-to-sub-repo for single-sub-repo dirty case', () => {
|
|
59
|
+
const m = getModule();
|
|
60
|
+
const survey = {
|
|
61
|
+
planningRoot: { dirty: false, paths: [] },
|
|
62
|
+
subRepos: [{ name: 'sub', path: '/tmp/sub', dirty: true, paths: ['file.txt'] }],
|
|
63
|
+
submodules: [],
|
|
64
|
+
};
|
|
65
|
+
const decision = m.decideRouting(survey);
|
|
66
|
+
assert.strictEqual(decision.action, 'route-to-sub-repo');
|
|
67
|
+
assert.strictEqual(decision.repoCwd, '/tmp/sub');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('REL-05: decideRouting returns route-to-planning-root for planning-only dirty case', () => {
|
|
71
|
+
const m = getModule();
|
|
72
|
+
const survey = {
|
|
73
|
+
planningRoot: { dirty: true, paths: ['a.md'] },
|
|
74
|
+
subRepos: [{ name: 'sub', path: '/tmp/sub', dirty: false, paths: [] }],
|
|
75
|
+
submodules: [],
|
|
76
|
+
};
|
|
77
|
+
const decision = m.decideRouting(survey);
|
|
78
|
+
assert.strictEqual(decision.action, 'route-to-planning-root');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('REL-05: decideRouting fails with multi-repo-dirt for multiple-dirty case', () => {
|
|
82
|
+
const m = getModule();
|
|
83
|
+
const survey = {
|
|
84
|
+
planningRoot: { dirty: true, paths: ['a.md'] },
|
|
85
|
+
subRepos: [{ name: 'sub', path: '/tmp/sub', dirty: true, paths: ['b.txt'] }],
|
|
86
|
+
submodules: [],
|
|
87
|
+
};
|
|
88
|
+
const decision = m.decideRouting(survey);
|
|
89
|
+
assert.strictEqual(decision.action, 'fail');
|
|
90
|
+
assert.strictEqual(decision.exitCode, 'multi-repo-dirt');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('REL-05: decideRouting warns on submodules but does not auto-route them', () => {
|
|
94
|
+
const m = getModule();
|
|
95
|
+
const survey = {
|
|
96
|
+
planningRoot: { dirty: false, paths: [] },
|
|
97
|
+
subRepos: [{ name: 'sub', path: '/tmp/sub', dirty: true, paths: ['file.txt'] }],
|
|
98
|
+
submodules: [{ path: 'vendor/lib', dirty: true }],
|
|
99
|
+
};
|
|
100
|
+
const decision = m.decideRouting(survey);
|
|
101
|
+
assert.strictEqual(decision.action, 'route-to-sub-repo');
|
|
102
|
+
assert.ok(
|
|
103
|
+
Array.isArray(decision.warnings) && decision.warnings.some(w => /submodule/i.test(w)),
|
|
104
|
+
'submodule warning must be present in decision.warnings'
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// REL-05/06 sentinel — flag this file as a Wave-0 RED scaffold for plan 03.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// deliver-great-systems/bin/lib/final-commit-precondition.test.cjs
|
|
2
|
+
// REL-08 regression test scaffold — initially RED. Turns GREEN after plan 02
|
|
3
|
+
// adds the pre-commit precondition gate to executor's <final_commit> step.
|
|
4
|
+
|
|
5
|
+
const test = require('node:test');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const os = require('node:os');
|
|
10
|
+
const { execSync } = require('node:child_process');
|
|
11
|
+
|
|
12
|
+
// Resolve dgs-tools.cjs CLI: __dirname = .../deliver-great-systems/bin/lib
|
|
13
|
+
// CLI lives at .../deliver-great-systems/bin/dgs-tools.cjs
|
|
14
|
+
const CLI = path.resolve(__dirname, '..', 'dgs-tools.cjs');
|
|
15
|
+
|
|
16
|
+
function setupFixture({ planRequirements, summaryRequirementsCompleted }) {
|
|
17
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rel08-'));
|
|
18
|
+
execSync('git init -q', { cwd: tmpDir });
|
|
19
|
+
execSync('git config user.email test@test', { cwd: tmpDir });
|
|
20
|
+
execSync('git config user.name test', { cwd: tmpDir });
|
|
21
|
+
fs.writeFileSync(
|
|
22
|
+
path.join(tmpDir, 'PLAN.md'),
|
|
23
|
+
`---\nphase: test\nplan: 01\nrequirements:\n${planRequirements.map(r => ` - ${r}`).join('\n')}\n---\n`
|
|
24
|
+
);
|
|
25
|
+
fs.writeFileSync(
|
|
26
|
+
path.join(tmpDir, 'SUMMARY.md'),
|
|
27
|
+
`---\nphase: test\nplan: 01\nrequirements_completed: [${summaryRequirementsCompleted.join(', ')}]\n---\n`
|
|
28
|
+
);
|
|
29
|
+
return tmpDir;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('REL-08: precondition aborts with summary-frontmatter-mismatch when PLAN non-empty AND SUMMARY empty', () => {
|
|
33
|
+
const fixture = setupFixture({ planRequirements: ['TEST-01'], summaryRequirementsCompleted: [] });
|
|
34
|
+
|
|
35
|
+
let exitCode = 0;
|
|
36
|
+
let combined = '';
|
|
37
|
+
try {
|
|
38
|
+
execSync(`node ${CLI} final-commit-precondition --plan ${fixture}/PLAN.md --summary ${fixture}/SUMMARY.md`, { stdio: 'pipe' });
|
|
39
|
+
} catch (err) {
|
|
40
|
+
exitCode = err.status;
|
|
41
|
+
combined = (err.stderr ? err.stderr.toString() : '') + (err.stdout ? err.stdout.toString() : '');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// RED: the CLI subcommand doesn't exist yet, so execSync throws with a different error
|
|
45
|
+
// (CLI says "Unknown command: final-commit-precondition" or similar — does NOT include the literal label).
|
|
46
|
+
assert.notStrictEqual(exitCode, 0, 'should exit non-zero on mismatch');
|
|
47
|
+
assert.match(combined, /summary-frontmatter-mismatch/, 'stderr must include the exit-code label');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('REL-08: precondition leaves working tree unchanged on abort (REL-08 fail-loudly contract)', () => {
|
|
51
|
+
const fixture = setupFixture({ planRequirements: ['TEST-01'], summaryRequirementsCompleted: [] });
|
|
52
|
+
// Snapshot working-tree state BEFORE precondition runs
|
|
53
|
+
const before = execSync('git status --porcelain', { cwd: fixture }).toString();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
execSync(`node ${CLI} final-commit-precondition --plan ${fixture}/PLAN.md --summary ${fixture}/SUMMARY.md`, { stdio: 'pipe' });
|
|
57
|
+
} catch (_) { /* expected non-zero */ }
|
|
58
|
+
|
|
59
|
+
const after = execSync('git status --porcelain', { cwd: fixture }).toString();
|
|
60
|
+
assert.strictEqual(after, before, 'working tree must be unchanged after precondition abort');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('REL-08: precondition is a no-op (exit 0) when PLAN requirements is empty', () => {
|
|
64
|
+
const fixture = setupFixture({ planRequirements: [], summaryRequirementsCompleted: [] });
|
|
65
|
+
|
|
66
|
+
let exitCode = 0;
|
|
67
|
+
try {
|
|
68
|
+
execSync(`node ${CLI} final-commit-precondition --plan ${fixture}/PLAN.md --summary ${fixture}/SUMMARY.md`, { stdio: 'pipe' });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
exitCode = err.status;
|
|
71
|
+
}
|
|
72
|
+
assert.strictEqual(exitCode, 0, 'precondition is no-op when PLAN requirements is empty');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('REL-08: precondition succeeds (exit 0) when SUMMARY requirements_completed matches PLAN', () => {
|
|
76
|
+
const fixture = setupFixture({ planRequirements: ['TEST-01'], summaryRequirementsCompleted: ['TEST-01'] });
|
|
77
|
+
|
|
78
|
+
let exitCode = 0;
|
|
79
|
+
try {
|
|
80
|
+
execSync(`node ${CLI} final-commit-precondition --plan ${fixture}/PLAN.md --summary ${fixture}/SUMMARY.md`, { stdio: 'pipe' });
|
|
81
|
+
} catch (err) {
|
|
82
|
+
exitCode = err.status;
|
|
83
|
+
}
|
|
84
|
+
assert.strictEqual(exitCode, 0, 'precondition exits 0 when SUMMARY matches PLAN');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// REL-08 sentinel — flag this file as a Wave-0 RED scaffold for plan 02.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"results": [
|
|
3
|
+
{
|
|
4
|
+
"type": "unpatched_gem",
|
|
5
|
+
"advisory": {
|
|
6
|
+
"id": "CVE-2020-8164",
|
|
7
|
+
"url": "https://groups.google.com/g/rubyonrails-security/c/f6ioe4sdpbY",
|
|
8
|
+
"title": "Possible Strong Parameters Bypass in ActionPack",
|
|
9
|
+
"description": "There is a strong parameters bypass vector in ActionPack versions 5.2.4.3 and below.",
|
|
10
|
+
"cvss_v3": 7.5,
|
|
11
|
+
"cvss_v2": 5.0,
|
|
12
|
+
"criticality": "high",
|
|
13
|
+
"solution": "Upgrade to activerecord >= 5.2.4.3"
|
|
14
|
+
},
|
|
15
|
+
"gem": {
|
|
16
|
+
"name": "activerecord",
|
|
17
|
+
"version": "5.2.3"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: "package-scan"
|
|
3
|
+
date: "2026-04-18"
|
|
4
|
+
tool: "mixed"
|
|
5
|
+
snyk_org: null
|
|
6
|
+
repos_scanned: 3
|
|
7
|
+
critical: 1
|
|
8
|
+
high: 1
|
|
9
|
+
medium: 2
|
|
10
|
+
low: 0
|
|
11
|
+
duration: 5
|
|
12
|
+
findings:
|
|
13
|
+
- id: "pkg-001"
|
|
14
|
+
test_source: "package-scan"
|
|
15
|
+
gap_type: "dependency-security"
|
|
16
|
+
severity: "high"
|
|
17
|
+
resource_id: "lodash@4.17.15"
|
|
18
|
+
repo: "api"
|
|
19
|
+
manifest_path: "packages/api/package.json"
|
|
20
|
+
title: "Command Injection in lodash"
|
|
21
|
+
description: "Versions of lodash prior to 4.17.21 are vulnerable to Command Injection."
|
|
22
|
+
remediation: "upgrade to lodash@4.17.21"
|
|
23
|
+
reference: "https://snyk.io/vuln/SNYK-JS-LODASH-1040724"
|
|
24
|
+
cve: "CVE-2021-23337"
|
|
25
|
+
cvss: 7.2
|
|
26
|
+
dependency_chain:
|
|
27
|
+
- "api@1.0.0"
|
|
28
|
+
- "lodash@4.17.15"
|
|
29
|
+
chain_available: true
|
|
30
|
+
direct_or_transitive: "direct"
|
|
31
|
+
tool: "snyk"
|
|
32
|
+
introduced_in_commit: null
|
|
33
|
+
introduced_in_plan: null
|
|
34
|
+
- id: "pkg-002"
|
|
35
|
+
test_source: "package-scan"
|
|
36
|
+
gap_type: "dependency-security"
|
|
37
|
+
severity: "medium"
|
|
38
|
+
resource_id: "gpl-licensed-dep@2.0.0"
|
|
39
|
+
repo: "api"
|
|
40
|
+
manifest_path: "packages/api/package.json"
|
|
41
|
+
title: "Prototype Pollution in gpl-licensed-dep"
|
|
42
|
+
description: |-
|
|
43
|
+
Multi-line
|
|
44
|
+
description with
|
|
45
|
+
embedded newlines.
|
|
46
|
+
remediation: null
|
|
47
|
+
reference: "https://example.com/advisory"
|
|
48
|
+
cve: null
|
|
49
|
+
cvss: null
|
|
50
|
+
dependency_chain: null
|
|
51
|
+
chain_available: false
|
|
52
|
+
direct_or_transitive: "transitive"
|
|
53
|
+
tool: "snyk"
|
|
54
|
+
introduced_in_commit: null
|
|
55
|
+
introduced_in_plan: null
|
|
56
|
+
- id: "pkg-002-lic"
|
|
57
|
+
test_source: "package-scan"
|
|
58
|
+
gap_type: "dependency-licence"
|
|
59
|
+
severity: "high"
|
|
60
|
+
resource_id: "gpl-licensed-dep@2.0.0"
|
|
61
|
+
repo: "api"
|
|
62
|
+
manifest_path: "packages/api/package.json"
|
|
63
|
+
title: "Restrictive licence: GPL-3.0"
|
|
64
|
+
description: "Package gpl-licensed-dep@2.0.0 is licensed under GPL-3.0. Using this dependency may impose copyleft obligations on your project."
|
|
65
|
+
remediation: "Review licence compatibility or replace with a permissive-licensed alternative."
|
|
66
|
+
reference: null
|
|
67
|
+
cve: null
|
|
68
|
+
cvss: null
|
|
69
|
+
dependency_chain: null
|
|
70
|
+
chain_available: false
|
|
71
|
+
direct_or_transitive: "transitive"
|
|
72
|
+
tool: "snyk"
|
|
73
|
+
introduced_in_commit: null
|
|
74
|
+
introduced_in_plan: null
|
|
75
|
+
- id: "pkg-003"
|
|
76
|
+
test_source: "package-scan"
|
|
77
|
+
gap_type: "dependency-security"
|
|
78
|
+
severity: "medium"
|
|
79
|
+
resource_id: "requests@2.25.0"
|
|
80
|
+
repo: "worker"
|
|
81
|
+
manifest_path: null
|
|
82
|
+
title: "Unintended leak of Proxy-Authorization header"
|
|
83
|
+
description: "Requests is a HTTP library."
|
|
84
|
+
remediation: "pip install requests==2.31.0"
|
|
85
|
+
reference: null
|
|
86
|
+
cve: "CVE-2023-32681"
|
|
87
|
+
cvss: null
|
|
88
|
+
dependency_chain: null
|
|
89
|
+
chain_available: false
|
|
90
|
+
direct_or_transitive: null
|
|
91
|
+
tool: "pip-audit"
|
|
92
|
+
introduced_in_commit: null
|
|
93
|
+
introduced_in_plan: null
|
|
94
|
+
- id: "pkg-004"
|
|
95
|
+
test_source: "package-scan"
|
|
96
|
+
gap_type: "dependency-security"
|
|
97
|
+
severity: "critical"
|
|
98
|
+
resource_id: "express"
|
|
99
|
+
repo: "_product_root"
|
|
100
|
+
manifest_path: null
|
|
101
|
+
title: "express Critical vulnerability"
|
|
102
|
+
description: null
|
|
103
|
+
remediation: null
|
|
104
|
+
reference: "https://github.com/advisories/GHSA-xxxx"
|
|
105
|
+
cve: null
|
|
106
|
+
cvss: 9.8
|
|
107
|
+
dependency_chain: null
|
|
108
|
+
chain_available: false
|
|
109
|
+
direct_or_transitive: "direct"
|
|
110
|
+
tool: "npm-audit"
|
|
111
|
+
introduced_in_commit: null
|
|
112
|
+
introduced_in_plan: null
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
# Package Scan Report
|
|
116
|
+
|
|
117
|
+
## Summary
|
|
118
|
+
|
|
119
|
+
| Repo | Ecosystem | Tool | .snyk policy | Critical | High | Medium | Low | Status |
|
|
120
|
+
|------|-----------|------|--------------|----------|------|--------|-----|--------|
|
|
121
|
+
| api | node | snyk | — | 0 | 1 | 1 | 0 | ok |
|
|
122
|
+
| worker | python | pip-audit | — | 0 | 0 | 1 | 0 | ok |
|
|
123
|
+
| _product_root | node | npm-audit | — | 1 | 0 | 0 | 0 | ok |
|
|
124
|
+
|
|
125
|
+
## Licence Compliance
|
|
126
|
+
|
|
127
|
+
> Licence scan incomplete -- use Snyk for full coverage.
|
|
128
|
+
|
|
129
|
+
## Critical
|
|
130
|
+
|
|
131
|
+
### _product_root: express — express Critical vulnerability
|
|
132
|
+
- **CVE:** unavailable
|
|
133
|
+
- **CVSS:** 9.8
|
|
134
|
+
- **Tool:** npm-audit
|
|
135
|
+
- **Manifest:** repo root
|
|
136
|
+
- **Direct/Transitive:** direct
|
|
137
|
+
- **Dependency chain:** unavailable (chain_available: false — recommend Snyk for full chain analysis)
|
|
138
|
+
- **Fix:** no upgrade path available — manual review required
|
|
139
|
+
- **Reference:** https://github.com/advisories/GHSA-xxxx
|
|
140
|
+
- **Introduced in:** unknown
|
|
141
|
+
|
|
142
|
+
## High
|
|
143
|
+
|
|
144
|
+
### api: lodash@4.17.15 — Command Injection in lodash
|
|
145
|
+
- **CVE:** CVE-2021-23337
|
|
146
|
+
- **CVSS:** 7.2
|
|
147
|
+
- **Tool:** snyk
|
|
148
|
+
- **Manifest:** `packages/api/package.json`
|
|
149
|
+
- **Direct/Transitive:** direct
|
|
150
|
+
- **Dependency chain:** api@1.0.0 → lodash@4.17.15
|
|
151
|
+
- **Fix:** upgrade to lodash@4.17.21
|
|
152
|
+
- **Reference:** https://snyk.io/vuln/SNYK-JS-LODASH-1040724
|
|
153
|
+
- **Introduced in:** unknown
|
|
154
|
+
|
|
155
|
+
> Versions of lodash prior to 4.17.21 are vulnerable to Command Injection.
|
|
156
|
+
|
|
157
|
+
## Medium
|
|
158
|
+
|
|
159
|
+
### api: gpl-licensed-dep@2.0.0 — Prototype Pollution in gpl-licensed-dep
|
|
160
|
+
- **CVE:** unavailable
|
|
161
|
+
- **CVSS:** unavailable
|
|
162
|
+
- **Tool:** snyk
|
|
163
|
+
- **Manifest:** `packages/api/package.json`
|
|
164
|
+
- **Direct/Transitive:** transitive
|
|
165
|
+
- **Dependency chain:** unavailable (chain_available: false — recommend Snyk for full chain analysis)
|
|
166
|
+
- **Fix:** no upgrade path available — manual review required
|
|
167
|
+
- **Reference:** https://example.com/advisory
|
|
168
|
+
- **Introduced in:** unknown
|
|
169
|
+
|
|
170
|
+
> Multi-line
|
|
171
|
+
> description with
|
|
172
|
+
> embedded newlines.
|
|
173
|
+
|
|
174
|
+
### worker: requests@2.25.0 — Unintended leak of Proxy-Authorization header
|
|
175
|
+
- **CVE:** CVE-2023-32681
|
|
176
|
+
- **CVSS:** unavailable
|
|
177
|
+
- **Tool:** pip-audit
|
|
178
|
+
- **Manifest:** repo root
|
|
179
|
+
- **Direct/Transitive:** unknown
|
|
180
|
+
- **Dependency chain:** unavailable (chain_available: false — recommend Snyk for full chain analysis)
|
|
181
|
+
- **Fix:** pip install requests==2.31.0
|
|
182
|
+
- **Reference:** unavailable
|
|
183
|
+
- **Introduced in:** unknown
|
|
184
|
+
|
|
185
|
+
> Requests is a HTTP library.
|
|
186
|
+
|