@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.
Files changed (115) hide show
  1. package/CHANGELOG.md +115 -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 +7 -3
  23. package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
  24. package/deliver-great-systems/bin/lib/fast-routing.cjs +199 -0
  25. package/deliver-great-systems/bin/lib/fast-routing.test.cjs +108 -0
  26. package/deliver-great-systems/bin/lib/final-commit-precondition.test.cjs +87 -0
  27. package/deliver-great-systems/bin/lib/fixtures/package-scan/bundler-audit-gemfile.json +21 -0
  28. package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-expected.md +186 -0
  29. package/deliver-great-systems/bin/lib/fixtures/package-scan/gate-parity-runresult.json +235 -0
  30. package/deliver-great-systems/bin/lib/fixtures/package-scan/govulncheck-import.json +3 -0
  31. package/deliver-great-systems/bin/lib/fixtures/package-scan/npm-audit-v10.json +37 -0
  32. package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-clean.json +3 -0
  33. package/deliver-great-systems/bin/lib/fixtures/package-scan/osv-vulns.json +77 -0
  34. package/deliver-great-systems/bin/lib/fixtures/package-scan/pip-audit-requirements.json +28 -0
  35. package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-lodash.json +30 -0
  36. package/deliver-great-systems/bin/lib/fixtures/package-scan/snyk-workspaces.json +55 -0
  37. package/deliver-great-systems/bin/lib/frontmatter.cjs +1 -1
  38. package/deliver-great-systems/bin/lib/governance.cjs +211 -0
  39. package/deliver-great-systems/bin/lib/governance.test.cjs +339 -0
  40. package/deliver-great-systems/bin/lib/health-untracked-phase.test.cjs +269 -0
  41. package/deliver-great-systems/bin/lib/init.cjs +56 -27
  42. package/deliver-great-systems/bin/lib/init.test.cjs +212 -5
  43. package/deliver-great-systems/bin/lib/jobs.cjs +7 -4
  44. package/deliver-great-systems/bin/lib/milestone.cjs +101 -3
  45. package/deliver-great-systems/bin/lib/milestone.test.cjs +203 -0
  46. package/deliver-great-systems/bin/lib/package-adapters.cjs +530 -0
  47. package/deliver-great-systems/bin/lib/package-adapters.test.cjs +618 -0
  48. package/deliver-great-systems/bin/lib/package-ecosystems.cjs +350 -0
  49. package/deliver-great-systems/bin/lib/package-ecosystems.test.cjs +348 -0
  50. package/deliver-great-systems/bin/lib/package-runner.cjs +199 -0
  51. package/deliver-great-systems/bin/lib/package-runner.test.cjs +198 -0
  52. package/deliver-great-systems/bin/lib/package-scan-provenance.cjs +56 -0
  53. package/deliver-great-systems/bin/lib/package-scan-provenance.test.cjs +103 -0
  54. package/deliver-great-systems/bin/lib/package-scan-report.cjs +1140 -0
  55. package/deliver-great-systems/bin/lib/package-scan-report.test.cjs +1963 -0
  56. package/deliver-great-systems/bin/lib/package-scan-skill.cjs +96 -0
  57. package/deliver-great-systems/bin/lib/package-scan-skill.test.cjs +136 -0
  58. package/deliver-great-systems/bin/lib/package-scan.cjs +919 -0
  59. package/deliver-great-systems/bin/lib/package-scan.test.cjs +2147 -0
  60. package/deliver-great-systems/bin/lib/phase.cjs +18 -1
  61. package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
  62. package/deliver-great-systems/bin/lib/projects.cjs +38 -3
  63. package/deliver-great-systems/bin/lib/projects.test.cjs +112 -2
  64. package/deliver-great-systems/bin/lib/quick.cjs +178 -23
  65. package/deliver-great-systems/bin/lib/quick.test.cjs +138 -4
  66. package/deliver-great-systems/bin/lib/repos.cjs +12 -12
  67. package/deliver-great-systems/bin/lib/review.cjs +1821 -0
  68. package/deliver-great-systems/bin/lib/state.cjs +7 -3
  69. package/deliver-great-systems/bin/lib/summary-frontmatter.cjs +54 -0
  70. package/deliver-great-systems/bin/lib/summary-frontmatter.test.cjs +78 -0
  71. package/deliver-great-systems/bin/lib/sweep-scope.test.cjs +263 -0
  72. package/deliver-great-systems/bin/lib/verify.cjs +118 -6
  73. package/deliver-great-systems/bin/lib/verify.test.cjs +82 -0
  74. package/deliver-great-systems/bin/lib/wave-0-template-rename.test.cjs +40 -0
  75. package/deliver-great-systems/bin/lib/worktrees.cjs +27 -1
  76. package/deliver-great-systems/bin/lib/worktrees.test.cjs +76 -0
  77. package/deliver-great-systems/references/agent-step-reliability.md +60 -0
  78. package/deliver-great-systems/references/conflict-resolution.md +4 -0
  79. package/deliver-great-systems/references/context-tiers.md +4 -0
  80. package/deliver-great-systems/references/package-scan-config.md +151 -0
  81. package/deliver-great-systems/references/questioning.md +0 -30
  82. package/deliver-great-systems/references/spec-review-loop.md +1 -2
  83. package/deliver-great-systems/references/workflow-conventions.md +29 -0
  84. package/deliver-great-systems/skills/dgs-tests/package-scan.md +44 -0
  85. package/deliver-great-systems/templates/REVIEW.md +35 -0
  86. package/deliver-great-systems/templates/VALIDATION.md +1 -1
  87. package/deliver-great-systems/templates/claude-md.md +11 -0
  88. package/deliver-great-systems/templates/package-scan-report.md +108 -0
  89. package/deliver-great-systems/templates/project.md +6 -170
  90. package/deliver-great-systems/templates/summary.md +3 -1
  91. package/deliver-great-systems/workflows/add-phase.md +5 -0
  92. package/deliver-great-systems/workflows/audit-milestone.md +66 -10
  93. package/deliver-great-systems/workflows/cancel-job.md +1 -1
  94. package/deliver-great-systems/workflows/codereview.md +103 -9
  95. package/deliver-great-systems/workflows/complete-milestone.md +26 -7
  96. package/deliver-great-systems/workflows/complete-quick.md +40 -2
  97. package/deliver-great-systems/workflows/discuss-phase.md +3 -2
  98. package/deliver-great-systems/workflows/execute-phase.md +89 -2
  99. package/deliver-great-systems/workflows/execute-plan.md +10 -1
  100. package/deliver-great-systems/workflows/help.md +51 -18
  101. package/deliver-great-systems/workflows/import-spec.md +65 -7
  102. package/deliver-great-systems/workflows/init-product.md +46 -152
  103. package/deliver-great-systems/workflows/new-milestone.md +115 -14
  104. package/deliver-great-systems/workflows/new-project.md +60 -331
  105. package/deliver-great-systems/workflows/package-scan.md +59 -0
  106. package/deliver-great-systems/workflows/plan-phase.md +79 -1
  107. package/deliver-great-systems/workflows/quick-complete.md +40 -2
  108. package/deliver-great-systems/workflows/quick.md +183 -10
  109. package/deliver-great-systems/workflows/research-idea.md +80 -142
  110. package/deliver-great-systems/workflows/run-job.md +21 -35
  111. package/deliver-great-systems/workflows/settings.md +13 -77
  112. package/deliver-great-systems/workflows/write-spec.md +9 -11
  113. package/hooks/dist/dgs-enforce-discipline.js +196 -0
  114. package/package.json +1 -1
  115. 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: ctx.root ? pathExistsInternal(cwd, path.join(ctx.root, 'PROJECT.md')) : pathExistsInternal(cwd, path.join(planRootRel, 'PROJECT.md')),
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: 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'),
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: ctx.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
- const slug = cleanDescription ? generateSlugInternal(cleanDescription)?.substring(0, 40).replace(/-+$/, '') : null;
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
- // 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.
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 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;
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