@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
|
@@ -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
|
-
|
|
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,
|
|
328
|
+
assert.equal(config.execution.active_context, result.slug);
|
|
274
329
|
|
|
275
330
|
// Verify worktree entry exists
|
|
276
|
-
const entry = config.projects.tp.worktrees[
|
|
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
|
|
1118
|
-
|
|
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
|
|