@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
@@ -150,8 +150,11 @@ describe('detectQuickMode', () => {
150
150
  });
151
151
 
152
152
  it('returns milestone-context when active_context points to milestone worktree', () => {
153
+ const wtPath = path.join(env.tmpDir, 'code-repo--tp-v1-0');
154
+ fs.mkdirSync(wtPath, { recursive: true });
155
+
153
156
  const config = readLocalConfig(env.planDir);
154
- config.projects = { tp: { worktrees: { 'v1-0': { type: 'milestone', repos: {} } } } };
157
+ config.projects = { tp: { worktrees: { 'v1-0': { type: 'milestone', repos: { 'code-repo': wtPath } } } } };
155
158
  config.execution = { active_context: 'v1-0' };
156
159
  writeLocalConfig(env.planDir, config);
157
160
 
@@ -162,6 +165,56 @@ describe('detectQuickMode', () => {
162
165
  assert.equal(result.activeMilestone, 'v1-0');
163
166
  });
164
167
 
168
+ it('returns product mode when milestone entry has no repos (stale)', () => {
169
+ const config = readLocalConfig(env.planDir);
170
+ config.projects = { tp: { worktrees: { 'v1-0': { type: 'milestone', repos: {} } } } };
171
+ config.execution = { active_context: 'v1-0' };
172
+ writeLocalConfig(env.planDir, config);
173
+
174
+ const { detectQuickMode } = require('./quick.cjs');
175
+ const result = detectQuickMode(env.planDir, false);
176
+ assert.equal(result.mode, 'product');
177
+ });
178
+
179
+ it('returns product mode when milestone entry repos point to non-existent path (stale)', () => {
180
+ // Regression test for 260507-pdp: a stale milestone entry left over from
181
+ // an interrupted /dgs:complete-milestone (or aborted `worktrees remove`)
182
+ // silently re-routed every subsequent /dgs:quick to milestone-context.
183
+ // Pre-fix this returned 'milestone-context' — post-fix it falls through.
184
+ const config = readLocalConfig(env.planDir);
185
+ config.projects = { tp: { worktrees: { 'v1-0': { type: 'milestone', repos: { 'code-repo': '/nonexistent/path/v1-0' } } } } };
186
+ config.execution = { active_context: 'v1-0' };
187
+ writeLocalConfig(env.planDir, config);
188
+
189
+ const { detectQuickMode } = require('./quick.cjs');
190
+ const result = detectQuickMode(env.planDir, false);
191
+ assert.equal(result.mode, 'product');
192
+ });
193
+
194
+ it('does NOT auto-clear stale milestone entries from config', () => {
195
+ // Asymmetric to getActiveQuick by design: milestone state is heavier than
196
+ // quick state and may carry context worth inspecting; user clears manually
197
+ // via `dgs-tools worktrees remove <slug>`.
198
+ const config = readLocalConfig(env.planDir);
199
+ config.projects = { tp: { worktrees: { 'v1-0': { type: 'milestone', repos: { 'code-repo': '/nonexistent/path/v1-0' } } } } };
200
+ config.execution = { active_context: 'v1-0' };
201
+ writeLocalConfig(env.planDir, config);
202
+
203
+ const { detectQuickMode } = require('./quick.cjs');
204
+ const result = detectQuickMode(env.planDir, false);
205
+ assert.equal(result.mode, 'product');
206
+
207
+ // Re-read config and assert the milestone entry is STILL present
208
+ const updatedConfig = readLocalConfig(env.planDir);
209
+ const entry = updatedConfig.projects && updatedConfig.projects.tp
210
+ && updatedConfig.projects.tp.worktrees
211
+ && updatedConfig.projects.tp.worktrees['v1-0'];
212
+ assert.ok(entry, 'Stale milestone entry should NOT be auto-cleared');
213
+ assert.equal(entry.type, 'milestone');
214
+ assert.equal(updatedConfig.execution.active_context, 'v1-0',
215
+ 'active_context should also still point to the stale milestone');
216
+ });
217
+
165
218
  it('returns product mode when active_context points to a quick worktree', () => {
166
219
  // Create a real quick worktree directory so it's not auto-cleared
167
220
  const wtPath = path.join(env.tmpDir, 'code-repo--tp-fix-bug');
@@ -266,17 +319,49 @@ describe('startProductQuick', () => {
266
319
  const { startProductQuick } = require('./quick.cjs');
267
320
  const result = startProductQuick(env.planDir, 'fix token bug', null);
268
321
  assert.equal(result.success, true);
269
- assert.equal(result.slug, 'fix-token-bug');
322
+ // Slug now includes quickId prefix: YYMMDD-xxx-fix-token-bug
323
+ assert.ok(/^\d{6}-[a-z0-9]{3}-fix-token-bug$/.test(result.slug),
324
+ 'Slug should have quickId prefix: ' + result.slug);
270
325
 
271
326
  // Verify active_context was set
272
327
  const config = readLocalConfig(env.planDir);
273
- assert.equal(config.execution.active_context, 'fix-token-bug');
328
+ assert.equal(config.execution.active_context, result.slug);
274
329
 
275
330
  // Verify worktree entry exists
276
- const entry = config.projects.tp.worktrees['fix-token-bug'];
331
+ const entry = config.projects.tp.worktrees[result.slug];
277
332
  assert.ok(entry, 'Worktree entry should exist');
278
333
  assert.equal(entry.type, 'quick');
279
334
  });
335
+
336
+ it('returns canonical slug that matches worktrees[] lookup key for long descriptions (regression: slug truncation)', () => {
337
+ // Regression for 260507-kq9: a title whose descSlug fills the 40-char cap
338
+ // produces a 51-char raw slug (10-char quickId + '-' + 40-char descSlug).
339
+ // cmdWorktreesCreate re-sanitises with a 50-char cap, so the worktrees[]
340
+ // entry is keyed under the 50-char canonical slug. Pre-fix, startProductQuick
341
+ // read back at the 51-char key → undefined → returned `repos: {}`.
342
+ // The workflow then injected no <worktree_context> and the executor
343
+ // committed to main of the registered repo instead of the quick branch.
344
+ const { startProductQuick } = require('./quick.cjs');
345
+ const longTitle = 'fix slug truncation mismatch causing empty repos and main commits';
346
+ const result = startProductQuick(env.planDir, longTitle, null);
347
+
348
+ assert.equal(result.success, true,
349
+ 'startProductQuick should succeed; got error: ' + (result.error || '<none>'));
350
+ assert.ok(result.slug.length <= 50,
351
+ 'Returned slug must respect canonical 50-char cap; got ' + result.slug.length + ' chars: ' + result.slug);
352
+ assert.ok(Object.keys(result.repos).length > 0,
353
+ 'Returned repos must be populated (regression: was {} when slug exceeded 50 chars). repos: ' + JSON.stringify(result.repos));
354
+
355
+ // The slug-as-returned must equal the slug-as-keyed in config.local.json.
356
+ const config = readLocalConfig(env.planDir);
357
+ assert.equal(config.execution.active_context, result.slug,
358
+ 'active_context should equal returned slug; active_context=' + config.execution.active_context + ' slug=' + result.slug);
359
+ const entry = config.projects.tp.worktrees[result.slug];
360
+ assert.ok(entry,
361
+ 'worktrees[result.slug] must exist; available keys: ' +
362
+ JSON.stringify(Object.keys(config.projects.tp.worktrees || {})));
363
+ assert.equal(entry.type, 'quick');
364
+ });
280
365
  });
281
366
 
282
367
  // ─── quickComplete ───────────────────────────────────────────────────────────
@@ -593,4 +678,53 @@ describe('cmdQuickFinalize', () => {
593
678
  assert.equal(second.parsed.committed, false);
594
679
  assert.equal(second.parsed.commit_reason, 'nothing_to_commit');
595
680
  });
681
+
682
+ it('commits both flat PLAN.md and numbered {quickId}-01-SUMMARY.md (regression: idea #18)', () => {
683
+ env = createFinalizeEnv();
684
+ // Planner writes flat PLAN, executor writes numbered SUMMARY (the 260410-ckl shape)
685
+ fs.writeFileSync(path.join(env.taskDir, env.quickId + '-PLAN.md'), '# plan\n');
686
+ fs.writeFileSync(path.join(env.taskDir, env.quickId + '-01-SUMMARY.md'), '# summary\n');
687
+ fs.writeFileSync(env.statePath, '# state\n');
688
+
689
+ const res = runFinalize(env.repoDir, [
690
+ env.quickId,
691
+ '--quick-dir', env.quickDir,
692
+ '--state-path', env.statePath,
693
+ '--description', 'idea-18 regression',
694
+ ]);
695
+ assert.equal(res.exitCode, 0, 'stderr: ' + res.stderr);
696
+ assert.ok(res.parsed, 'expected parsed JSON, got: ' + res.stdout);
697
+ assert.equal(res.parsed.committed, true);
698
+ assert.equal(res.parsed.commit_reason, 'committed');
699
+ // 3 files: flat PLAN + numbered SUMMARY + STATE
700
+ assert.equal(res.parsed.files_committed.length, 3, 'files: ' + JSON.stringify(res.parsed.files_committed));
701
+
702
+ // Both file basenames must appear in the actual commit
703
+ const committedFiles = execSync(
704
+ 'git log -1 --name-only --format=',
705
+ { cwd: env.repoDir, encoding: 'utf-8' }
706
+ ).trim().split('\n').filter(Boolean);
707
+ const hasFlat = committedFiles.some(f => f.endsWith(env.quickId + '-PLAN.md'));
708
+ const hasNumberedSummary = committedFiles.some(f => f.endsWith(env.quickId + '-01-SUMMARY.md'));
709
+ assert.ok(hasFlat, 'flat PLAN.md should be in commit, got: ' + JSON.stringify(committedFiles));
710
+ assert.ok(hasNumberedSummary, 'numbered SUMMARY should be in commit, got: ' + JSON.stringify(committedFiles));
711
+ });
712
+
713
+ it('commits fully numbered shape ({quickId}-01-PLAN.md + {quickId}-01-SUMMARY.md)', () => {
714
+ env = createFinalizeEnv();
715
+ fs.writeFileSync(path.join(env.taskDir, env.quickId + '-01-PLAN.md'), '# plan\n');
716
+ fs.writeFileSync(path.join(env.taskDir, env.quickId + '-01-SUMMARY.md'), '# summary\n');
717
+ fs.writeFileSync(env.statePath, '# state\n');
718
+
719
+ const res = runFinalize(env.repoDir, [
720
+ env.quickId,
721
+ '--quick-dir', env.quickDir,
722
+ '--state-path', env.statePath,
723
+ '--description', 'fully numbered shape',
724
+ ]);
725
+ assert.equal(res.exitCode, 0, 'stderr: ' + res.stderr);
726
+ assert.ok(res.parsed);
727
+ assert.equal(res.parsed.committed, true);
728
+ assert.equal(res.parsed.files_committed.length, 3);
729
+ });
596
730
  });
@@ -1101,10 +1101,6 @@ function cmdReposInitProduct(cwd, options, raw) {
1101
1101
  // Check if already initialized (idempotency)
1102
1102
  if (isV2Install(cwd)) {
1103
1103
  // Silently ensure v3.0 directories exist (backfill for upgrades)
1104
- const ideasStates = ['pending', 'rejected', 'done'];
1105
- for (const st of ideasStates) {
1106
- fs.mkdirSync(path.join(getPlanningRoot(cwd), 'ideas', st), { recursive: true });
1107
- }
1108
1104
  fs.mkdirSync(path.join(getPlanningRoot(cwd), 'specs'), { recursive: true });
1109
1105
  fs.mkdirSync(path.join(getPlanningRoot(cwd), 'docs', 'product'), { recursive: true });
1110
1106
  fs.mkdirSync(path.join(getPlanningRoot(cwd), 'quick'), { recursive: true });
@@ -1114,28 +1110,31 @@ function cmdReposInitProduct(cwd, options, raw) {
1114
1110
  // Planning root is cwd (root layout)
1115
1111
  fs.mkdirSync(getPlanningRoot(cwd), { recursive: true });
1116
1112
 
1117
- // Create ideas, specs, and docs directories for v3.0 features
1118
- const ideasStates = ['pending', 'rejected', 'done'];
1119
- for (const state of ideasStates) {
1120
- fs.mkdirSync(path.join(getPlanningRoot(cwd), 'ideas', state), { recursive: true });
1121
- }
1113
+ // Create specs and docs directories for v3.0 features
1114
+ // (ideas/ is created on demand by `dgs-tools ideas create`; flat-status frontmatter replaces directory-based state per idea #009)
1122
1115
  fs.mkdirSync(path.join(getPlanningRoot(cwd), 'specs'), { recursive: true });
1123
1116
  fs.mkdirSync(path.join(getPlanningRoot(cwd), 'docs', 'product'), { recursive: true });
1124
1117
  fs.mkdirSync(path.join(getPlanningRoot(cwd), 'quick'), { recursive: true });
1125
1118
 
1126
1119
  // Add .gitkeep files to empty directories so they survive git commit
1127
1120
  const gitkeepPaths = [
1128
- path.join(getPlanningRoot(cwd), 'ideas', 'pending', '.gitkeep'),
1129
- path.join(getPlanningRoot(cwd), 'ideas', 'rejected', '.gitkeep'),
1130
- path.join(getPlanningRoot(cwd), 'ideas', 'done', '.gitkeep'),
1131
1121
  path.join(getPlanningRoot(cwd), 'specs', '.gitkeep'),
1132
1122
  path.join(getPlanningRoot(cwd), 'docs', 'product', '.gitkeep'),
1133
1123
  path.join(getPlanningRoot(cwd), 'quick', '.gitkeep'),
1134
1124
  ];
1125
+ // AGENT-13 contract: scaffolded_files lists all files this command writes/guarantees,
1126
+ // derived from actual write list (never hardcoded). Workflows MUST consume this array
1127
+ // and pass it to a subsequent commit step's --files argument. See
1128
+ // references/workflow-conventions.md and references/agent-step-reliability.md.
1129
+ const scaffoldedFiles = [];
1135
1130
  for (const gk of gitkeepPaths) {
1136
1131
  if (!fs.existsSync(gk)) {
1137
1132
  fs.writeFileSync(gk, '', 'utf-8');
1138
1133
  }
1134
+ // Always include in scaffolded_files: contract is "files this command guarantees on
1135
+ // disk that the caller MUST commit", not "files this run newly created" — fresh
1136
+ // clones need them tracked even if a previous local run already wrote them.
1137
+ scaffoldedFiles.push(path.relative(cwd, gk));
1139
1138
  }
1140
1139
 
1141
1140
  // Scaffold review-keys.json with empty template
@@ -1203,6 +1202,7 @@ function cmdReposInitProduct(cwd, options, raw) {
1203
1202
  docs_dir_created: true,
1204
1203
  bootstrapped_repos: bootstrappedRepos,
1205
1204
  files_created: ['config.json', 'config.local.json', 'REPOS.md', 'PROJECTS.md', 'ideas/', 'specs/', 'docs/', 'quick/', '.gitignore', 'review-keys.json'],
1205
+ scaffolded_files: scaffoldedFiles,
1206
1206
  }, raw);
1207
1207
  }
1208
1208