@ktpartners/dgs-platform 3.0.4 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +8 -1
  3. package/agents/dgs-executor.md +124 -3
  4. package/agents/dgs-idea-researcher.md +447 -0
  5. package/agents/dgs-plan-checker.md +32 -0
  6. package/agents/dgs-planner.md +41 -8
  7. package/bin/install.js +44 -0
  8. package/commands/dgs/audit-milestone.md +2 -1
  9. package/commands/dgs/diff-report.md +124 -0
  10. package/commands/dgs/new-project.md +8 -21
  11. package/commands/dgs/package-scan.md +43 -0
  12. package/commands/dgs/research-idea.md +1 -0
  13. package/commands/dgs/switch-project.md +13 -0
  14. package/deliver-great-systems/bin/dgs-tools.cjs +120 -5
  15. package/deliver-great-systems/bin/lib/audit-tolerance.cjs +77 -0
  16. package/deliver-great-systems/bin/lib/audit-tolerance.test.cjs +101 -0
  17. package/deliver-great-systems/bin/lib/commands.cjs +311 -16
  18. package/deliver-great-systems/bin/lib/commands.test.cjs +115 -0
  19. package/deliver-great-systems/bin/lib/commit-verify.test.cjs +236 -0
  20. package/deliver-great-systems/bin/lib/config.cjs +41 -0
  21. package/deliver-great-systems/bin/lib/config.test.cjs +309 -0
  22. package/deliver-great-systems/bin/lib/core.cjs +9 -9
  23. package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
  24. package/deliver-great-systems/bin/lib/docs.cjs +22 -12
  25. package/deliver-great-systems/bin/lib/fast-routing.cjs +199 -0
  26. package/deliver-great-systems/bin/lib/fast-routing.test.cjs +108 -0
  27. package/deliver-great-systems/bin/lib/final-commit-precondition.test.cjs +87 -0
  28. package/deliver-great-systems/bin/lib/fixtures/package-scan/bundler-audit-gemfile.json +21 -0
  29. package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-expected.md +186 -0
  30. package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-runresult.json +235 -0
  31. package/deliver-great-systems/bin/lib/fixtures/package-scan/govulncheck-import.json +3 -0
  32. package/deliver-great-systems/bin/lib/fixtures/package-scan/npm-audit-v10.json +37 -0
  33. package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-clean.json +3 -0
  34. package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-vulns.json +77 -0
  35. package/deliver-great-systems/bin/lib/fixtures/package-scan/pip-audit-requirements.json +28 -0
  36. package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-lodash.json +30 -0
  37. package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-workspaces.json +55 -0
  38. package/deliver-great-systems/bin/lib/frontmatter.cjs +1 -1
  39. package/deliver-great-systems/bin/lib/governance.cjs +211 -0
  40. package/deliver-great-systems/bin/lib/governance.test.cjs +339 -0
  41. package/deliver-great-systems/bin/lib/health-untracked-phase.test.cjs +269 -0
  42. package/deliver-great-systems/bin/lib/init.cjs +107 -37
  43. package/deliver-great-systems/bin/lib/init.test.cjs +212 -5
  44. package/deliver-great-systems/bin/lib/jobs.cjs +7 -4
  45. package/deliver-great-systems/bin/lib/milestone.cjs +101 -3
  46. package/deliver-great-systems/bin/lib/milestone.test.cjs +203 -0
  47. package/deliver-great-systems/bin/lib/package-adapters.cjs +530 -0
  48. package/deliver-great-systems/bin/lib/package-adapters.test.cjs +618 -0
  49. package/deliver-great-systems/bin/lib/package-ecosystems.cjs +350 -0
  50. package/deliver-great-systems/bin/lib/package-ecosystems.test.cjs +348 -0
  51. package/deliver-great-systems/bin/lib/package-runner.cjs +199 -0
  52. package/deliver-great-systems/bin/lib/package-runner.test.cjs +198 -0
  53. package/deliver-great-systems/bin/lib/package-scan-provenance.cjs +56 -0
  54. package/deliver-great-systems/bin/lib/package-scan-provenance.test.cjs +103 -0
  55. package/deliver-great-systems/bin/lib/package-scan-report.cjs +1140 -0
  56. package/deliver-great-systems/bin/lib/package-scan-report.test.cjs +1963 -0
  57. package/deliver-great-systems/bin/lib/package-scan-skill.cjs +96 -0
  58. package/deliver-great-systems/bin/lib/package-scan-skill.test.cjs +136 -0
  59. package/deliver-great-systems/bin/lib/package-scan.cjs +919 -0
  60. package/deliver-great-systems/bin/lib/package-scan.test.cjs +2147 -0
  61. package/deliver-great-systems/bin/lib/phase.cjs +18 -1
  62. package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
  63. package/deliver-great-systems/bin/lib/projects.cjs +38 -3
  64. package/deliver-great-systems/bin/lib/projects.test.cjs +112 -2
  65. package/deliver-great-systems/bin/lib/quick.cjs +178 -23
  66. package/deliver-great-systems/bin/lib/quick.test.cjs +138 -4
  67. package/deliver-great-systems/bin/lib/repos.cjs +12 -12
  68. package/deliver-great-systems/bin/lib/review.cjs +1821 -0
  69. package/deliver-great-systems/bin/lib/state.cjs +7 -3
  70. package/deliver-great-systems/bin/lib/summary-frontmatter.cjs +54 -0
  71. package/deliver-great-systems/bin/lib/summary-frontmatter.test.cjs +78 -0
  72. package/deliver-great-systems/bin/lib/sweep-scope.test.cjs +263 -0
  73. package/deliver-great-systems/bin/lib/sync.cjs +2 -6
  74. package/deliver-great-systems/bin/lib/verify.cjs +120 -7
  75. package/deliver-great-systems/bin/lib/verify.test.cjs +82 -0
  76. package/deliver-great-systems/bin/lib/wave-0-template-rename.test.cjs +40 -0
  77. package/deliver-great-systems/bin/lib/worktrees.cjs +27 -1
  78. package/deliver-great-systems/bin/lib/worktrees.test.cjs +76 -0
  79. package/deliver-great-systems/references/agent-step-reliability.md +60 -0
  80. package/deliver-great-systems/references/conflict-resolution.md +4 -0
  81. package/deliver-great-systems/references/context-tiers.md +4 -0
  82. package/deliver-great-systems/references/package-scan-config.md +151 -0
  83. package/deliver-great-systems/references/questioning.md +0 -30
  84. package/deliver-great-systems/references/spec-review-loop.md +1 -2
  85. package/deliver-great-systems/references/workflow-conventions.md +29 -0
  86. package/deliver-great-systems/skills/dgs-tests/package-scan.md +44 -0
  87. package/deliver-great-systems/templates/REVIEW.md +35 -0
  88. package/deliver-great-systems/templates/VALIDATION.md +1 -1
  89. package/deliver-great-systems/templates/claude-md.md +11 -0
  90. package/deliver-great-systems/templates/package-scan-report.md +108 -0
  91. package/deliver-great-systems/templates/project.md +6 -170
  92. package/deliver-great-systems/templates/summary.md +3 -1
  93. package/deliver-great-systems/workflows/add-phase.md +5 -0
  94. package/deliver-great-systems/workflows/audit-milestone.md +66 -10
  95. package/deliver-great-systems/workflows/cancel-job.md +1 -1
  96. package/deliver-great-systems/workflows/codereview.md +103 -9
  97. package/deliver-great-systems/workflows/complete-milestone.md +26 -7
  98. package/deliver-great-systems/workflows/complete-quick.md +40 -2
  99. package/deliver-great-systems/workflows/discuss-phase.md +3 -2
  100. package/deliver-great-systems/workflows/execute-phase.md +89 -2
  101. package/deliver-great-systems/workflows/execute-plan.md +10 -1
  102. package/deliver-great-systems/workflows/help.md +51 -18
  103. package/deliver-great-systems/workflows/import-spec.md +65 -7
  104. package/deliver-great-systems/workflows/init-product.md +46 -152
  105. package/deliver-great-systems/workflows/new-milestone.md +115 -14
  106. package/deliver-great-systems/workflows/new-project.md +60 -331
  107. package/deliver-great-systems/workflows/package-scan.md +59 -0
  108. package/deliver-great-systems/workflows/plan-phase.md +79 -1
  109. package/deliver-great-systems/workflows/quick-complete.md +40 -2
  110. package/deliver-great-systems/workflows/quick.md +183 -10
  111. package/deliver-great-systems/workflows/research-idea.md +80 -142
  112. package/deliver-great-systems/workflows/run-job.md +21 -35
  113. package/deliver-great-systems/workflows/settings.md +13 -77
  114. package/deliver-great-systems/workflows/write-spec.md +9 -11
  115. package/hooks/dist/dgs-enforce-discipline.js +196 -0
  116. package/package.json +1 -1
  117. 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
  /**
@@ -88,6 +88,54 @@ function applySyncPull(cwd, workflowName, result) {
88
88
  result.needs_pull = false;
89
89
  }
90
90
 
91
+ // ─── Brownfield detection ───────────────────────────────────────────────────
92
+
93
+ /**
94
+ * Detect whether the directory tree rooted at cwd contains any source
95
+ * files in a small set of common languages, ignoring node_modules and .git,
96
+ * with a maximum recursion depth of 3 (cwd itself is depth 0).
97
+ *
98
+ * Returns true on first match (early termination).
99
+ * Returns false on any error (e.g. permission denied on top-level dir).
100
+ *
101
+ * Pure Node — replaces the prior `find ... | grep ... | head -5` shell
102
+ * pipeline that does not exist on Windows.
103
+ *
104
+ * @param {string} cwd - Directory to scan.
105
+ * @returns {boolean}
106
+ */
107
+ function hasCodeFiles(cwd) {
108
+ const CODE_EXTENSIONS = new Set(['.ts', '.js', '.py', '.go', '.rs', '.swift', '.java']);
109
+ const SKIP_DIRS = new Set(['node_modules', '.git']);
110
+ const MAX_DEPTH = 3;
111
+
112
+ function walk(dir, depth) {
113
+ if (depth > MAX_DEPTH) return false;
114
+ let entries;
115
+ try {
116
+ entries = fs.readdirSync(dir, { withFileTypes: true });
117
+ } catch {
118
+ return false;
119
+ }
120
+ for (const entry of entries) {
121
+ if (entry.isDirectory()) {
122
+ if (SKIP_DIRS.has(entry.name)) continue;
123
+ if (walk(path.join(dir, entry.name), depth + 1)) return true;
124
+ } else if (entry.isFile()) {
125
+ const ext = path.extname(entry.name).toLowerCase();
126
+ if (CODE_EXTENSIONS.has(ext)) return true;
127
+ }
128
+ }
129
+ return false;
130
+ }
131
+
132
+ try {
133
+ return walk(cwd, 0);
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+
91
139
  // ─── v2 Project Context Resolution ──────────────────────────────────────────
92
140
 
93
141
  /**
@@ -384,28 +432,36 @@ function cmdInitPlanPhase(cwd, phase, raw) {
384
432
  output(result, raw);
385
433
  }
386
434
 
387
- function cmdInitNewProject(cwd, raw) {
435
+ function cmdInitNewProject(cwd, slugArg, raw) {
388
436
  const config = loadConfig(cwd);
389
437
  const ctx = resolveProjectContext(cwd);
390
438
  const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
391
439
  const milestone = getMilestoneInfo(cwd);
392
440
 
441
+ // Resolve slug override (optional positional arg from CLI).
442
+ // When supplied AND we're on a v2 install, override the per-slug paths
443
+ // so `init new-project <slug>` reflects whether THAT slug exists,
444
+ // independent of the currently active project. v1 mode silently falls
445
+ // back to ctx.root behaviour because v1 has no projects/<slug>/ layout.
446
+ let effectiveRoot = ctx.root;
447
+ let slugOverride = null;
448
+ if (slugArg && typeof slugArg === 'string' && slugArg.trim()) {
449
+ const candidate = generateSlugInternal(slugArg.trim());
450
+ if (candidate && isV2Install(cwd)) {
451
+ slugOverride = candidate;
452
+ effectiveRoot = path.join(planRootRel, 'projects', slugOverride);
453
+ }
454
+ }
455
+ const rootForPaths = effectiveRoot;
456
+
393
457
  // Detect Brave Search API key availability
394
458
  const homedir = require('os').homedir();
395
459
  const braveKeyFile = path.join(homedir, '.dgs', 'brave_api_key');
396
460
  const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
397
461
 
398
- // Detect existing code
399
- let hasCode = false;
462
+ // Detect existing code (pure-Node walk, Windows-safe)
463
+ const hasCode = hasCodeFiles(cwd);
400
464
  let hasPackageFile = false;
401
- try {
402
- const files = execSync('find . -maxdepth 3 \\( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.swift" -o -name "*.java" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5', {
403
- cwd,
404
- encoding: 'utf-8',
405
- stdio: ['pipe', 'pipe', 'pipe'],
406
- });
407
- hasCode = files.trim().length > 0;
408
- } catch {}
409
465
 
410
466
  hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
411
467
  pathExistsInternal(cwd, 'requirements.txt') ||
@@ -426,8 +482,8 @@ function cmdInitNewProject(cwd, raw) {
426
482
  cadence_pull: getCadence('new-project').pull,
427
483
  cadence_push: getCadence('new-project').push,
428
484
 
429
- // Existing state (project-qualified)
430
- project_exists: ctx.root ? pathExistsInternal(cwd, path.join(ctx.root, 'PROJECT.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'PROJECT.md')),
485
+ // Existing state (project-qualified, slug-override aware)
486
+ project_exists: rootForPaths ? pathExistsInternal(cwd, path.join(rootForPaths, 'PROJECT.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'PROJECT.md')),
431
487
  has_codebase_map: pathExistsInternal(cwd, path.join(planRootRel, 'codebase')),
432
488
  planning_exists: pathExistsInternal(cwd, planRootRel),
433
489
 
@@ -446,12 +502,12 @@ function cmdInitNewProject(cwd, raw) {
446
502
  // Enhanced search
447
503
  brave_search_available: hasBraveSearch,
448
504
 
449
- // File paths (project-qualified)
450
- project_path: ctx.root ? path.join(ctx.root, 'PROJECT.md') : path.join(planRootRel, 'PROJECT.md'),
451
- state_path: ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
452
- roadmap_path: ctx.root ? path.join(ctx.root, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
453
- requirements_path: ctx.root ? path.join(ctx.root, 'REQUIREMENTS.md') : path.join(planRootRel, 'REQUIREMENTS.md'),
454
- research_dir: ctx.root ? path.join(ctx.root, 'research') : path.join(planRootRel, 'research'),
505
+ // File paths (project-qualified, slug-override aware)
506
+ project_path: rootForPaths ? path.join(rootForPaths, 'PROJECT.md') : path.join(planRootRel, 'PROJECT.md'),
507
+ state_path: rootForPaths ? path.join(rootForPaths, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
508
+ roadmap_path: rootForPaths ? path.join(rootForPaths, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
509
+ requirements_path: rootForPaths ? path.join(rootForPaths, 'REQUIREMENTS.md') : path.join(planRootRel, 'REQUIREMENTS.md'),
510
+ research_dir: rootForPaths ? path.join(rootForPaths, 'research') : path.join(planRootRel, 'research'),
455
511
 
456
512
  // Milestone info (product-level, shared across projects)
457
513
  milestone_version: milestone.version,
@@ -460,7 +516,13 @@ function cmdInitNewProject(cwd, raw) {
460
516
  // v2 context
461
517
  dgs_mode: ctx.dgs_mode,
462
518
  current_project: ctx.current_project,
463
- project_root: ctx.root,
519
+ // project_root reflects override when a slug arg is supplied (so the
520
+ // workflow can write directly to the requested project's folder), otherwise
521
+ // it still reports the active project root.
522
+ project_root: rootForPaths,
523
+ // requested_slug: the normalized slug arg (or null when no slug arg / v1 fallback).
524
+ // The workflow uses this to distinguish "active project" from "requested project".
525
+ requested_slug: slugOverride,
464
526
  guard: ctx.guard,
465
527
  v2_hint: ctx.v2_hint || null,
466
528
  };
@@ -503,6 +565,10 @@ function cmdInitNewMilestone(cwd, raw) {
503
565
  roadmap_path: ctx.root ? path.join(ctx.root, 'ROADMAP.md') : path.join(planRootRel, 'ROADMAP.md'),
504
566
  state_path: ctx.root ? path.join(ctx.root, 'STATE.md') : path.join(planRootRel, 'STATE.md'),
505
567
 
568
+ // Product-level MILESTONES.md (authoritative for global version/phase continuity across projects)
569
+ product_milestones_path: path.join(planRootRel, 'MILESTONES.md'),
570
+ product_milestones_exists: pathExistsInternal(cwd, path.join(planRootRel, 'MILESTONES.md')),
571
+
506
572
  // Author
507
573
  author: resolveAuthorSafe(cwd),
508
574
 
@@ -527,30 +593,33 @@ function cmdInitQuick(cwd, description, raw) {
527
593
  // Parse --main flag out of description string
528
594
  const forceMain = /\s*--main\s*/.test(description || '');
529
595
  const cleanDescription = (description || '').replace(/\s*--main\s*/g, ' ').trim() || null;
530
- const slug = cleanDescription ? generateSlugInternal(cleanDescription)?.substring(0, 40).replace(/-+$/, '') : null;
596
+
597
+ // Hoist getActiveQuick lookup so both `slug` and `quickId` derive from
598
+ // the same canonical source (the worktree slug stored in config.local.json).
599
+ // The worktree slug has already been truncated to 50 chars by worktrees.cjs;
600
+ // re-sanitising the description here would produce a divergent (51-char)
601
+ // descSlug, leaving an orphan directory on disk after every quick task.
602
+ // See: bug 260428-k7f.
603
+ const activeQuick = getActiveQuick(cwd);
604
+ const hasActiveQuickSlug = activeQuick && /^\d{6}-[a-z0-9]{3}-/.test(activeQuick.slug);
605
+
606
+ const slug = hasActiveQuickSlug
607
+ ? activeQuick.slug.slice(11) // strip "{quickId}-" prefix (10 chars + 1 dash)
608
+ : (cleanDescription ? generateSlugInternal(cleanDescription)?.substring(0, 40).replace(/-+$/, '') : null);
531
609
 
532
610
  // Detect quick mode: product-level vs milestone-context
533
611
  const quickMode = detectQuickMode(cwd, forceMain);
534
612
 
535
- // Generate collision-resistant quick task ID: YYMMDD-xxx
536
- // xxx = 2-second precision blocks since midnight, encoded as 3-char Base36 (lowercase)
537
- // Range: 000 (00:00:00) to xbz (23:59:58), guaranteed 3 chars for any time of day.
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.
613
+ // Quick task ID: YYMMDD-xxx. For product-level quicks with an active worktree,
614
+ // extract the quickId from the worktree slug (startProductQuick embeds it as the
615
+ // prefix). For fast mode and milestone-context, generate a fresh one.
542
616
  const isProductMode = quickMode.mode === 'product';
543
617
  const quickBase = isProductMode
544
618
  ? path.join(planRootRel, 'quick')
545
619
  : (ctx.root ? path.join(ctx.root, 'quick') : path.join(planRootRel, 'quick'));
546
- const yy = String(now.getFullYear()).slice(-2);
547
- const mm = String(now.getMonth() + 1).padStart(2, '0');
548
- const dd = String(now.getDate()).padStart(2, '0');
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;
620
+ const quickId = hasActiveQuickSlug
621
+ ? activeQuick.slug.slice(0, 10)
622
+ : generateQuickId(now);
554
623
 
555
624
  // Auto-create product-level STATE.md on demand (matches project STATE.md
556
625
  // auto-creation behavior elsewhere). Keep skeleton minimal — the /dgs:quick
@@ -1518,4 +1587,5 @@ module.exports = {
1518
1587
  cmdInitProgress,
1519
1588
  cmdInitProgressAll,
1520
1589
  applySyncPull,
1590
+ hasCodeFiles,
1521
1591
  };