@ktpartners/dgs-platform 3.0.4 → 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 +115 -0
- package/README.md +8 -1
- package/agents/dgs-executor.md +124 -3
- package/agents/dgs-idea-researcher.md +447 -0
- package/agents/dgs-plan-checker.md +32 -0
- package/agents/dgs-planner.md +41 -8
- package/bin/install.js +44 -0
- package/commands/dgs/audit-milestone.md +2 -1
- package/commands/dgs/diff-report.md +124 -0
- package/commands/dgs/new-project.md +8 -21
- package/commands/dgs/package-scan.md +43 -0
- package/commands/dgs/research-idea.md +1 -0
- package/commands/dgs/switch-project.md +13 -0
- package/deliver-great-systems/bin/dgs-tools.cjs +120 -5
- 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 +311 -16
- package/deliver-great-systems/bin/lib/commands.test.cjs +115 -0
- package/deliver-great-systems/bin/lib/commit-verify.test.cjs +236 -0
- package/deliver-great-systems/bin/lib/config.cjs +41 -0
- package/deliver-great-systems/bin/lib/config.test.cjs +309 -0
- package/deliver-great-systems/bin/lib/core.cjs +7 -3
- package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
- 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/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/init.cjs +56 -27
- package/deliver-great-systems/bin/lib/init.test.cjs +212 -5
- package/deliver-great-systems/bin/lib/jobs.cjs +7 -4
- package/deliver-great-systems/bin/lib/milestone.cjs +101 -3
- 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 +18 -1
- package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
- package/deliver-great-systems/bin/lib/projects.cjs +38 -3
- package/deliver-great-systems/bin/lib/projects.test.cjs +112 -2
- package/deliver-great-systems/bin/lib/quick.cjs +178 -23
- package/deliver-great-systems/bin/lib/quick.test.cjs +138 -4
- package/deliver-great-systems/bin/lib/repos.cjs +12 -12
- package/deliver-great-systems/bin/lib/review.cjs +1821 -0
- package/deliver-great-systems/bin/lib/state.cjs +7 -3
- 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/verify.cjs +118 -6
- 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 +27 -1
- package/deliver-great-systems/bin/lib/worktrees.test.cjs +76 -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 +11 -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/add-phase.md +5 -0
- package/deliver-great-systems/workflows/audit-milestone.md +66 -10
- package/deliver-great-systems/workflows/cancel-job.md +1 -1
- package/deliver-great-systems/workflows/codereview.md +103 -9
- package/deliver-great-systems/workflows/complete-milestone.md +26 -7
- package/deliver-great-systems/workflows/complete-quick.md +40 -2
- package/deliver-great-systems/workflows/discuss-phase.md +3 -2
- package/deliver-great-systems/workflows/execute-phase.md +89 -2
- package/deliver-great-systems/workflows/execute-plan.md +10 -1
- package/deliver-great-systems/workflows/help.md +51 -18
- package/deliver-great-systems/workflows/import-spec.md +65 -7
- package/deliver-great-systems/workflows/init-product.md +46 -152
- package/deliver-great-systems/workflows/new-milestone.md +115 -14
- 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/quick-complete.md +40 -2
- package/deliver-great-systems/workflows/quick.md +183 -10
- package/deliver-great-systems/workflows/research-idea.md +80 -142
- package/deliver-great-systems/workflows/run-job.md +21 -35
- package/deliver-great-systems/workflows/settings.md +13 -77
- package/deliver-great-systems/workflows/write-spec.md +9 -11
- package/hooks/dist/dgs-enforce-discipline.js +196 -0
- package/package.json +1 -1
- package/scripts/build-hooks.js +1 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RED test scaffold for REL-11 (Phase 156 plan 04).
|
|
3
|
+
*
|
|
4
|
+
* The cmdValidateHealth extension is implemented in plan 04; running
|
|
5
|
+
* this file before plan 04 lands MUST produce 6 failed tests with
|
|
6
|
+
* 'not yet implemented — REL-11' style messages.
|
|
7
|
+
*
|
|
8
|
+
* Behaviour under test:
|
|
9
|
+
* - untracked PLAN.md detection: warning entry references the file
|
|
10
|
+
* - untracked CONTEXT.md, RESEARCH.md, UAT.md, VERIFICATION.md: a
|
|
11
|
+
* warning entry that lists all four file paths
|
|
12
|
+
* - clean phase dir: zero warnings about untracked-phase-artifacts
|
|
13
|
+
* - multiple phase dirs: walks all phases under projects/<project>/phases/
|
|
14
|
+
* - non-artifact files (README.md, .DS_Store): not reported
|
|
15
|
+
* - fix message references a remediation command (dgs-tools commit
|
|
16
|
+
* OR /dgs:health --repair)
|
|
17
|
+
*
|
|
18
|
+
* Conventions:
|
|
19
|
+
* - Uses node:test runner + node:assert (matches state-transition-gate.test.cjs)
|
|
20
|
+
* - Each test creates and tears down its own temp planning root via os.tmpdir()
|
|
21
|
+
* - Until plan 04 lands, every test fails with 'not yet implemented'
|
|
22
|
+
* so the file is RED in a controlled way (no parse errors).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const test = require('node:test');
|
|
26
|
+
const assert = require('node:assert');
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const os = require('os');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
const { execSync } = require('child_process');
|
|
31
|
+
|
|
32
|
+
const NOT_IMPL = 'untracked-phase-artifacts check not yet implemented — REL-11';
|
|
33
|
+
|
|
34
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function tryRequireVerify() {
|
|
37
|
+
try {
|
|
38
|
+
return require('./verify.cjs');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function makePlanningRoot() {
|
|
45
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'health-rel11-test-'));
|
|
46
|
+
execSync('git init --quiet', { cwd: dir });
|
|
47
|
+
execSync('git config user.email test@example.com', { cwd: dir });
|
|
48
|
+
execSync('git config user.name "Test User"', { cwd: dir });
|
|
49
|
+
// Minimum required scaffolding so existing checks don't error out
|
|
50
|
+
fs.mkdirSync(path.join(dir, 'phases'), { recursive: true });
|
|
51
|
+
fs.writeFileSync(path.join(dir, 'PROJECT.md'), '# project\n## What This Is\n## Core Value\n## Requirements\n');
|
|
52
|
+
fs.writeFileSync(path.join(dir, 'ROADMAP.md'), '# roadmap\n');
|
|
53
|
+
fs.writeFileSync(path.join(dir, 'STATE.md'), '# state\n');
|
|
54
|
+
fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify({ commit_docs: true }, null, 2));
|
|
55
|
+
// Initial commit so HEAD exists
|
|
56
|
+
execSync('git add -A', { cwd: dir });
|
|
57
|
+
execSync('git commit --quiet -m "seed"', { cwd: dir });
|
|
58
|
+
return dir;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function cleanupRoot(dir) {
|
|
62
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function writeAndOptionallyTrack(root, rel, content, track) {
|
|
66
|
+
const abs = path.join(root, rel);
|
|
67
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
68
|
+
fs.writeFileSync(abs, content);
|
|
69
|
+
if (track) {
|
|
70
|
+
execSync(`git add "${rel}"`, { cwd: root });
|
|
71
|
+
execSync(`git commit --quiet -m "track ${rel}"`, { cwd: root });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function callValidateHealth(root) {
|
|
76
|
+
const verify = tryRequireVerify();
|
|
77
|
+
if (!verify || typeof verify.cmdValidateHealth !== 'function') return null;
|
|
78
|
+
// Capture the result by stubbing output() through raw mode. Since
|
|
79
|
+
// cmdValidateHealth calls output() which calls process.exit, we run
|
|
80
|
+
// it in a child process and capture stdout JSON.
|
|
81
|
+
//
|
|
82
|
+
// We write the shim to a tmp file rather than passing via -e, because
|
|
83
|
+
// shell-escaping a path that may contain spaces or quotes plus the
|
|
84
|
+
// raw arg is fragile. A tmp .cjs file is unambiguous.
|
|
85
|
+
const verifyPath = path.resolve(__dirname, 'verify.cjs');
|
|
86
|
+
const shim = [
|
|
87
|
+
`const v = require(${JSON.stringify(verifyPath)});`,
|
|
88
|
+
`v.cmdValidateHealth(${JSON.stringify(root)}, { raw: true }, true);`,
|
|
89
|
+
].join('\n');
|
|
90
|
+
const shimPath = path.join(os.tmpdir(), `rel11-shim-${process.pid}-${Date.now()}.cjs`);
|
|
91
|
+
fs.writeFileSync(shimPath, shim);
|
|
92
|
+
try {
|
|
93
|
+
const stdout = execSync(`node ${JSON.stringify(shimPath)}`, { encoding: 'utf-8' });
|
|
94
|
+
try { return JSON.parse(stdout); } catch { return { stdout }; }
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return { error: err.message, stdout: err.stdout && err.stdout.toString() };
|
|
97
|
+
} finally {
|
|
98
|
+
try { fs.unlinkSync(shimPath); } catch { /* ignore */ }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── Test 1: untracked PLAN.md detection ──────────────────────────────────
|
|
103
|
+
|
|
104
|
+
test('REL-11 untracked PLAN.md detection: returns untracked-phase-artifacts entry listing the file', () => {
|
|
105
|
+
const verify = tryRequireVerify();
|
|
106
|
+
if (!verify || typeof verify.cmdValidateHealth !== 'function') {
|
|
107
|
+
assert.fail(NOT_IMPL);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const root = makePlanningRoot();
|
|
111
|
+
try {
|
|
112
|
+
// tracked CONTEXT.md, untracked PLAN.md
|
|
113
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-CONTEXT.md', '# ctx\n', true);
|
|
114
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-01-PLAN.md', '# plan\n', false);
|
|
115
|
+
const result = callValidateHealth(root);
|
|
116
|
+
if (!result || !Array.isArray(result.warnings)) {
|
|
117
|
+
assert.fail(NOT_IMPL);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const hit = result.warnings.find(w =>
|
|
121
|
+
(w.message || '').includes('untracked-phase-artifacts') &&
|
|
122
|
+
(w.message || '').includes('100-01-PLAN.md')
|
|
123
|
+
);
|
|
124
|
+
assert.ok(hit, 'expected an untracked-phase-artifacts warning referencing 100-01-PLAN.md');
|
|
125
|
+
} finally {
|
|
126
|
+
cleanupRoot(root);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ─── Test 2: untracked CONTEXT/RESEARCH/UAT/VERIFICATION ─────────────────
|
|
131
|
+
|
|
132
|
+
test('REL-11 untracked CONTEXT.md, RESEARCH.md, UAT.md, VERIFICATION.md detection (one of each)', () => {
|
|
133
|
+
const verify = tryRequireVerify();
|
|
134
|
+
if (!verify || typeof verify.cmdValidateHealth !== 'function') {
|
|
135
|
+
assert.fail(NOT_IMPL);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const root = makePlanningRoot();
|
|
139
|
+
try {
|
|
140
|
+
// All four artifacts untracked
|
|
141
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-CONTEXT.md', 'a\n', false);
|
|
142
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-RESEARCH.md', 'b\n', false);
|
|
143
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-UAT.md', 'c\n', false);
|
|
144
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-VERIFICATION.md', 'd\n', false);
|
|
145
|
+
const result = callValidateHealth(root);
|
|
146
|
+
if (!result || !Array.isArray(result.warnings)) {
|
|
147
|
+
assert.fail(NOT_IMPL);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const hit = result.warnings.find(w => (w.message || '').includes('untracked-phase-artifacts'));
|
|
151
|
+
assert.ok(hit, 'expected an untracked-phase-artifacts warning');
|
|
152
|
+
for (const f of ['100-CONTEXT.md', '100-RESEARCH.md', '100-UAT.md', '100-VERIFICATION.md']) {
|
|
153
|
+
assert.ok((hit.message || '').includes(f), `warning message must include ${f}`);
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
cleanupRoot(root);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ─── Test 3: clean phase dir → no warning ─────────────────────────────────
|
|
161
|
+
|
|
162
|
+
test('REL-11 clean phase dir: all artifacts tracked yields no untracked-phase-artifacts entry', () => {
|
|
163
|
+
const verify = tryRequireVerify();
|
|
164
|
+
if (!verify || typeof verify.cmdValidateHealth !== 'function') {
|
|
165
|
+
assert.fail(NOT_IMPL);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const root = makePlanningRoot();
|
|
169
|
+
try {
|
|
170
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-CONTEXT.md', 'a\n', true);
|
|
171
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-01-PLAN.md', 'b\n', true);
|
|
172
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-RESEARCH.md', 'c\n', true);
|
|
173
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-UAT.md', 'd\n', true);
|
|
174
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-VERIFICATION.md', 'e\n', true);
|
|
175
|
+
const result = callValidateHealth(root);
|
|
176
|
+
if (!result || !Array.isArray(result.warnings)) {
|
|
177
|
+
assert.fail(NOT_IMPL);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const hit = result.warnings.find(w => (w.message || '').includes('untracked-phase-artifacts'));
|
|
181
|
+
assert.strictEqual(hit, undefined, 'no untracked-phase-artifacts warning expected when all tracked');
|
|
182
|
+
} finally {
|
|
183
|
+
cleanupRoot(root);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ─── Test 4: multiple phase dirs ──────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
test('REL-11 multiple phase dirs: walks all phases under projects/<current>/phases/', () => {
|
|
190
|
+
const verify = tryRequireVerify();
|
|
191
|
+
if (!verify || typeof verify.cmdValidateHealth !== 'function') {
|
|
192
|
+
assert.fail(NOT_IMPL);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const root = makePlanningRoot();
|
|
196
|
+
try {
|
|
197
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-01-PLAN.md', 'a\n', false);
|
|
198
|
+
writeAndOptionallyTrack(root, 'phases/101-bar/101-CONTEXT.md', 'b\n', false);
|
|
199
|
+
writeAndOptionallyTrack(root, 'phases/102-baz/102-VERIFICATION.md', 'c\n', false);
|
|
200
|
+
const result = callValidateHealth(root);
|
|
201
|
+
if (!result || !Array.isArray(result.warnings)) {
|
|
202
|
+
assert.fail(NOT_IMPL);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const hit = result.warnings.find(w => (w.message || '').includes('untracked-phase-artifacts'));
|
|
206
|
+
assert.ok(hit, 'expected an untracked-phase-artifacts warning');
|
|
207
|
+
assert.ok((hit.message || '').includes('100-01-PLAN.md'));
|
|
208
|
+
assert.ok((hit.message || '').includes('101-CONTEXT.md'));
|
|
209
|
+
assert.ok((hit.message || '').includes('102-VERIFICATION.md'));
|
|
210
|
+
} finally {
|
|
211
|
+
cleanupRoot(root);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ─── Test 5: non-artifact files not reported ──────────────────────────────
|
|
216
|
+
|
|
217
|
+
test('REL-11 non-artifact files (e.g., a .DS_Store or some-other.md inside phases/100/) are NOT reported', () => {
|
|
218
|
+
const verify = tryRequireVerify();
|
|
219
|
+
if (!verify || typeof verify.cmdValidateHealth !== 'function') {
|
|
220
|
+
assert.fail(NOT_IMPL);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const root = makePlanningRoot();
|
|
224
|
+
try {
|
|
225
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-CONTEXT.md', 'a\n', true);
|
|
226
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/README.md', 'r\n', false);
|
|
227
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/.DS_Store', 'x\n', false);
|
|
228
|
+
const result = callValidateHealth(root);
|
|
229
|
+
if (!result || !Array.isArray(result.warnings)) {
|
|
230
|
+
assert.fail(NOT_IMPL);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const hit = result.warnings.find(w => (w.message || '').includes('untracked-phase-artifacts'));
|
|
234
|
+
if (hit) {
|
|
235
|
+
assert.ok(!(hit.message || '').includes('README.md'), 'README.md must NOT be reported');
|
|
236
|
+
assert.ok(!(hit.message || '').includes('.DS_Store'), '.DS_Store must NOT be reported');
|
|
237
|
+
}
|
|
238
|
+
// If no untracked-phase-artifacts warning at all, that's also acceptable
|
|
239
|
+
// (only PLAN/CONTEXT/RESEARCH/UAT/VERIFICATION are checked).
|
|
240
|
+
} finally {
|
|
241
|
+
cleanupRoot(root);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// ─── Test 6: fix message references a remediation command ────────────────
|
|
246
|
+
|
|
247
|
+
test('REL-11 fix message includes a remediation command', () => {
|
|
248
|
+
const verify = tryRequireVerify();
|
|
249
|
+
if (!verify || typeof verify.cmdValidateHealth !== 'function') {
|
|
250
|
+
assert.fail(NOT_IMPL);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const root = makePlanningRoot();
|
|
254
|
+
try {
|
|
255
|
+
writeAndOptionallyTrack(root, 'phases/100-foo/100-01-PLAN.md', 'a\n', false);
|
|
256
|
+
const result = callValidateHealth(root);
|
|
257
|
+
if (!result || !Array.isArray(result.warnings)) {
|
|
258
|
+
assert.fail(NOT_IMPL);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const hit = result.warnings.find(w => (w.message || '').includes('untracked-phase-artifacts'));
|
|
262
|
+
assert.ok(hit, 'expected an untracked-phase-artifacts warning');
|
|
263
|
+
const fix = hit.fix || '';
|
|
264
|
+
const refs = fix.includes('dgs-tools commit') || fix.includes('/dgs:health --repair');
|
|
265
|
+
assert.ok(refs, `fix message should reference 'dgs-tools commit' OR '/dgs:health --repair' (got: ${fix})`);
|
|
266
|
+
} finally {
|
|
267
|
+
cleanupRoot(root);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
@@ -10,7 +10,7 @@ const { requireGitIdentity, formatAuthorString } = require('./identity.cjs');
|
|
|
10
10
|
const { getPlanningRoot, PROJECTS_DIR } = require('./paths.cjs');
|
|
11
11
|
const { parseReposMd, validateReposMdEager } = require('./repos.cjs');
|
|
12
12
|
const { getCadence, pullAll } = require('./sync.cjs');
|
|
13
|
-
const { detectQuickMode } = require('./quick.cjs');
|
|
13
|
+
const { detectQuickMode, generateQuickId, getActiveQuick } = require('./quick.cjs');
|
|
14
14
|
const { listProjectsReadonly } = require('./projects.cjs');
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -384,12 +384,28 @@ function cmdInitPlanPhase(cwd, phase, raw) {
|
|
|
384
384
|
output(result, raw);
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
function cmdInitNewProject(cwd, raw) {
|
|
387
|
+
function cmdInitNewProject(cwd, slugArg, raw) {
|
|
388
388
|
const config = loadConfig(cwd);
|
|
389
389
|
const ctx = resolveProjectContext(cwd);
|
|
390
390
|
const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
|
|
391
391
|
const milestone = getMilestoneInfo(cwd);
|
|
392
392
|
|
|
393
|
+
// Resolve slug override (optional positional arg from CLI).
|
|
394
|
+
// When supplied AND we're on a v2 install, override the per-slug paths
|
|
395
|
+
// so `init new-project <slug>` reflects whether THAT slug exists,
|
|
396
|
+
// independent of the currently active project. v1 mode silently falls
|
|
397
|
+
// back to ctx.root behaviour because v1 has no projects/<slug>/ layout.
|
|
398
|
+
let effectiveRoot = ctx.root;
|
|
399
|
+
let slugOverride = null;
|
|
400
|
+
if (slugArg && typeof slugArg === 'string' && slugArg.trim()) {
|
|
401
|
+
const candidate = generateSlugInternal(slugArg.trim());
|
|
402
|
+
if (candidate && isV2Install(cwd)) {
|
|
403
|
+
slugOverride = candidate;
|
|
404
|
+
effectiveRoot = path.join(planRootRel, 'projects', slugOverride);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const rootForPaths = effectiveRoot;
|
|
408
|
+
|
|
393
409
|
// Detect Brave Search API key availability
|
|
394
410
|
const homedir = require('os').homedir();
|
|
395
411
|
const braveKeyFile = path.join(homedir, '.dgs', 'brave_api_key');
|
|
@@ -426,8 +442,8 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
426
442
|
cadence_pull: getCadence('new-project').pull,
|
|
427
443
|
cadence_push: getCadence('new-project').push,
|
|
428
444
|
|
|
429
|
-
// Existing state (project-qualified)
|
|
430
|
-
project_exists:
|
|
445
|
+
// Existing state (project-qualified, slug-override aware)
|
|
446
|
+
project_exists: rootForPaths ? pathExistsInternal(cwd, path.join(rootForPaths, 'PROJECT.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'PROJECT.md')),
|
|
431
447
|
has_codebase_map: pathExistsInternal(cwd, path.join(planRootRel, 'codebase')),
|
|
432
448
|
planning_exists: pathExistsInternal(cwd, planRootRel),
|
|
433
449
|
|
|
@@ -446,12 +462,12 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
446
462
|
// Enhanced search
|
|
447
463
|
brave_search_available: hasBraveSearch,
|
|
448
464
|
|
|
449
|
-
// File paths (project-qualified)
|
|
450
|
-
project_path:
|
|
451
|
-
state_path:
|
|
452
|
-
roadmap_path:
|
|
453
|
-
requirements_path:
|
|
454
|
-
research_dir:
|
|
465
|
+
// File paths (project-qualified, slug-override aware)
|
|
466
|
+
project_path: rootForPaths ? path.join(rootForPaths, 'PROJECT.md') : path.join(planRootRel, 'PROJECT.md'),
|
|
467
|
+
state_path: rootForPaths ? path.join(rootForPaths, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
|
|
468
|
+
roadmap_path: rootForPaths ? path.join(rootForPaths, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
|
|
469
|
+
requirements_path: rootForPaths ? path.join(rootForPaths, 'REQUIREMENTS.md') : path.join(planRootRel, 'REQUIREMENTS.md'),
|
|
470
|
+
research_dir: rootForPaths ? path.join(rootForPaths, 'research') : path.join(planRootRel, 'research'),
|
|
455
471
|
|
|
456
472
|
// Milestone info (product-level, shared across projects)
|
|
457
473
|
milestone_version: milestone.version,
|
|
@@ -460,7 +476,13 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
460
476
|
// v2 context
|
|
461
477
|
dgs_mode: ctx.dgs_mode,
|
|
462
478
|
current_project: ctx.current_project,
|
|
463
|
-
project_root
|
|
479
|
+
// project_root reflects override when a slug arg is supplied (so the
|
|
480
|
+
// workflow can write directly to the requested project's folder), otherwise
|
|
481
|
+
// it still reports the active project root.
|
|
482
|
+
project_root: rootForPaths,
|
|
483
|
+
// requested_slug: the normalized slug arg (or null when no slug arg / v1 fallback).
|
|
484
|
+
// The workflow uses this to distinguish "active project" from "requested project".
|
|
485
|
+
requested_slug: slugOverride,
|
|
464
486
|
guard: ctx.guard,
|
|
465
487
|
v2_hint: ctx.v2_hint || null,
|
|
466
488
|
};
|
|
@@ -503,6 +525,10 @@ function cmdInitNewMilestone(cwd, raw) {
|
|
|
503
525
|
roadmap_path: ctx.root ? path.join(ctx.root, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
|
|
504
526
|
state_path: ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
|
|
505
527
|
|
|
528
|
+
// Product-level MILESTONES.md (authoritative for global version/phase continuity across projects)
|
|
529
|
+
product_milestones_path: path.join(planRootRel, 'MILESTONES.md'),
|
|
530
|
+
product_milestones_exists: pathExistsInternal(cwd, path.join(planRootRel, 'MILESTONES.md')),
|
|
531
|
+
|
|
506
532
|
// Author
|
|
507
533
|
author: resolveAuthorSafe(cwd),
|
|
508
534
|
|
|
@@ -527,30 +553,33 @@ function cmdInitQuick(cwd, description, raw) {
|
|
|
527
553
|
// Parse --main flag out of description string
|
|
528
554
|
const forceMain = /\s*--main\s*/.test(description || '');
|
|
529
555
|
const cleanDescription = (description || '').replace(/\s*--main\s*/g, ' ').trim() || null;
|
|
530
|
-
|
|
556
|
+
|
|
557
|
+
// Hoist getActiveQuick lookup so both `slug` and `quickId` derive from
|
|
558
|
+
// the same canonical source (the worktree slug stored in config.local.json).
|
|
559
|
+
// The worktree slug has already been truncated to 50 chars by worktrees.cjs;
|
|
560
|
+
// re-sanitising the description here would produce a divergent (51-char)
|
|
561
|
+
// descSlug, leaving an orphan directory on disk after every quick task.
|
|
562
|
+
// See: bug 260428-k7f.
|
|
563
|
+
const activeQuick = getActiveQuick(cwd);
|
|
564
|
+
const hasActiveQuickSlug = activeQuick && /^\d{6}-[a-z0-9]{3}-/.test(activeQuick.slug);
|
|
565
|
+
|
|
566
|
+
const slug = hasActiveQuickSlug
|
|
567
|
+
? activeQuick.slug.slice(11) // strip "{quickId}-" prefix (10 chars + 1 dash)
|
|
568
|
+
: (cleanDescription ? generateSlugInternal(cleanDescription)?.substring(0, 40).replace(/-+$/, '') : null);
|
|
531
569
|
|
|
532
570
|
// Detect quick mode: product-level vs milestone-context
|
|
533
571
|
const quickMode = detectQuickMode(cwd, forceMain);
|
|
534
572
|
|
|
535
|
-
//
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
// Provides ~2s uniqueness window per user — practically collision-free across a team.
|
|
539
|
-
// Branch path resolution by quickMode: product-level quicks (including --main
|
|
540
|
-
// override) land at the planning root; milestone-context quicks stay under the
|
|
541
|
-
// active project. This keeps product-level work out of project state.
|
|
573
|
+
// Quick task ID: YYMMDD-xxx. For product-level quicks with an active worktree,
|
|
574
|
+
// extract the quickId from the worktree slug (startProductQuick embeds it as the
|
|
575
|
+
// prefix). For fast mode and milestone-context, generate a fresh one.
|
|
542
576
|
const isProductMode = quickMode.mode === 'product';
|
|
543
577
|
const quickBase = isProductMode
|
|
544
578
|
? path.join(planRootRel, 'quick')
|
|
545
579
|
: (ctx.root ? path.join(ctx.root, 'quick') : path.join(planRootRel, 'quick'));
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const dateStr = yy + mm + dd;
|
|
550
|
-
const secondsSinceMidnight = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
|
|
551
|
-
const timeBlocks = Math.floor(secondsSinceMidnight / 2);
|
|
552
|
-
const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
|
|
553
|
-
const quickId = dateStr + '-' + timeEncoded;
|
|
580
|
+
const quickId = hasActiveQuickSlug
|
|
581
|
+
? activeQuick.slug.slice(0, 10)
|
|
582
|
+
: generateQuickId(now);
|
|
554
583
|
|
|
555
584
|
// Auto-create product-level STATE.md on demand (matches project STATE.md
|
|
556
585
|
// auto-creation behavior elsewhere). Keep skeleton minimal — the /dgs:quick
|