@ktpartners/dgs-platform 2.9.0 → 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 +197 -0
- package/README.md +34 -2
- package/agents/dgs-executor.md +124 -3
- package/agents/dgs-idea-researcher.md +447 -0
- package/agents/dgs-plan-checker.md +61 -3
- package/agents/dgs-planner.md +51 -8
- package/bin/install.js +44 -0
- package/commands/dgs/abandon-quick.md +28 -0
- package/commands/dgs/add-tests.md +2 -2
- package/commands/dgs/audit-milestone.md +4 -3
- package/commands/dgs/capture-principle.md +11 -11
- package/commands/dgs/cleanup.md +2 -2
- package/commands/dgs/complete-milestone.md +11 -11
- package/commands/dgs/complete-quick.md +28 -0
- package/commands/dgs/create-milestone-job.md +2 -2
- package/commands/dgs/debug.md +3 -3
- package/commands/dgs/develop-idea.md +1 -1
- package/commands/dgs/diff-report.md +124 -0
- package/commands/dgs/fast.md +3 -1
- package/commands/dgs/health.md +1 -1
- package/commands/dgs/map-codebase.md +6 -6
- package/commands/dgs/new-milestone.md +5 -5
- package/commands/dgs/new-project.md +8 -21
- package/commands/dgs/package-scan.md +43 -0
- package/commands/dgs/plan-milestone-gaps.md +1 -1
- package/commands/dgs/progress.md +3 -3
- package/commands/dgs/quick-abandon.md +8 -0
- package/commands/dgs/quick-complete.md +8 -0
- package/commands/dgs/quick.md +10 -3
- package/commands/dgs/research-idea.md +3 -2
- package/commands/dgs/research-phase.md +3 -3
- package/commands/dgs/switch-project.md +14 -1
- package/commands/dgs/write-spec.md +3 -3
- package/deliver-great-systems/bin/dgs-tools.cjs +401 -32
- 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 +626 -46
- package/deliver-great-systems/bin/lib/commands.test.cjs +451 -0
- package/deliver-great-systems/bin/lib/commit-verify.test.cjs +236 -0
- package/deliver-great-systems/bin/lib/config.cjs +80 -6
- package/deliver-great-systems/bin/lib/config.test.cjs +309 -0
- package/deliver-great-systems/bin/lib/context.cjs +120 -0
- package/deliver-great-systems/bin/lib/core.cjs +35 -14
- package/deliver-great-systems/bin/lib/core.test.cjs +79 -1
- package/deliver-great-systems/bin/lib/execution.cjs +49 -17
- 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/flat-migration.test.cjs +396 -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/ideas.cjs +206 -91
- package/deliver-great-systems/bin/lib/ideas.test.cjs +244 -1
- package/deliver-great-systems/bin/lib/init.cjs +357 -61
- package/deliver-great-systems/bin/lib/init.test.cjs +625 -8
- package/deliver-great-systems/bin/lib/jobs.cjs +131 -25
- package/deliver-great-systems/bin/lib/jobs.test.cjs +193 -74
- package/deliver-great-systems/bin/lib/migration.cjs +409 -1
- package/deliver-great-systems/bin/lib/migration.test.cjs +158 -1
- package/deliver-great-systems/bin/lib/milestone.cjs +154 -31
- 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 +146 -3
- package/deliver-great-systems/bin/lib/phase.test.cjs +420 -0
- package/deliver-great-systems/bin/lib/plan-number-validity.test.cjs +48 -0
- package/deliver-great-systems/bin/lib/projects.cjs +65 -10
- package/deliver-great-systems/bin/lib/projects.test.cjs +198 -2
- package/deliver-great-systems/bin/lib/quick.cjs +739 -0
- package/deliver-great-systems/bin/lib/quick.test.cjs +730 -0
- package/deliver-great-systems/bin/lib/repos.cjs +37 -13
- package/deliver-great-systems/bin/lib/review.cjs +1821 -0
- package/deliver-great-systems/bin/lib/roadmap.cjs +34 -13
- package/deliver-great-systems/bin/lib/specs.cjs +3 -81
- package/deliver-great-systems/bin/lib/state-transition-gate.test.cjs +160 -0
- package/deliver-great-systems/bin/lib/state.cjs +147 -55
- 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/sync.cjs +75 -0
- package/deliver-great-systems/bin/lib/verify.cjs +198 -7
- 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 +790 -0
- package/deliver-great-systems/bin/lib/worktrees.test.cjs +963 -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 +27 -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/abandon-quick.md +89 -0
- package/deliver-great-systems/workflows/add-idea.md +3 -3
- package/deliver-great-systems/workflows/add-phase.md +5 -0
- package/deliver-great-systems/workflows/add-tests.md +14 -0
- package/deliver-great-systems/workflows/add-todo.md +1 -0
- package/deliver-great-systems/workflows/approve-spec.md +25 -4
- package/deliver-great-systems/workflows/audit-milestone.md +66 -10
- package/deliver-great-systems/workflows/audit-phase.md +15 -5
- package/deliver-great-systems/workflows/cancel-job.md +2 -2
- package/deliver-great-systems/workflows/check-todos.md +2 -3
- package/deliver-great-systems/workflows/codereview.md +103 -9
- package/deliver-great-systems/workflows/complete-milestone.md +218 -24
- package/deliver-great-systems/workflows/complete-quick.md +106 -0
- package/deliver-great-systems/workflows/consolidate-ideas.md +1 -1
- package/deliver-great-systems/workflows/create-milestone-job.md +4 -4
- package/deliver-great-systems/workflows/develop-idea.md +11 -11
- package/deliver-great-systems/workflows/diagnose-issues.md +14 -0
- package/deliver-great-systems/workflows/discuss-idea.md +1 -1
- package/deliver-great-systems/workflows/discuss-phase.md +3 -2
- package/deliver-great-systems/workflows/execute-phase.md +209 -33
- package/deliver-great-systems/workflows/execute-plan.md +22 -22
- package/deliver-great-systems/workflows/help.md +53 -20
- package/deliver-great-systems/workflows/import-spec.md +65 -7
- package/deliver-great-systems/workflows/init-product.md +45 -167
- package/deliver-great-systems/workflows/new-milestone.md +140 -33
- 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/progress-all.md +133 -0
- package/deliver-great-systems/workflows/quick-abandon.md +89 -0
- package/deliver-great-systems/workflows/quick-complete.md +106 -0
- package/deliver-great-systems/workflows/quick.md +328 -26
- package/deliver-great-systems/workflows/refine-spec.md +1 -1
- package/deliver-great-systems/workflows/research-idea.md +77 -139
- package/deliver-great-systems/workflows/resume-project.md +2 -2
- package/deliver-great-systems/workflows/run-job.md +29 -43
- package/deliver-great-systems/workflows/settings.md +13 -77
- package/deliver-great-systems/workflows/validate-phase.md +39 -1
- package/deliver-great-systems/workflows/verify-work.md +14 -0
- package/deliver-great-systems/workflows/write-spec.md +11 -13
- package/hooks/dist/dgs-enforce-discipline.js +196 -0
- package/package.json +1 -1
- package/scripts/build-hooks.js +1 -0
|
@@ -4,32 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { escapeRegex, getMilestonePhaseFilter, output, error } = require('./core.cjs');
|
|
7
|
+
const { escapeRegex, getMilestonePhaseFilter, getProjectRoot, output, error, loadConfig } = require('./core.cjs');
|
|
8
8
|
const { extractFrontmatter } = require('./frontmatter.cjs');
|
|
9
9
|
const { getPlanningRoot } = require('./paths.cjs');
|
|
10
10
|
const { writeStateMd } = require('./state.cjs');
|
|
11
|
+
const { getContributors, checkFourEyes } = require('./governance.cjs');
|
|
12
|
+
const { requireGitIdentity, formatAuthorString } = require('./identity.cjs');
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.join(' ')
|
|
20
|
-
.replace(/[\[\]]/g, '')
|
|
21
|
-
.split(/[,\s]+/)
|
|
22
|
-
.map(r => r.trim())
|
|
23
|
-
.filter(Boolean);
|
|
24
|
-
|
|
25
|
-
if (reqIds.length === 0) {
|
|
14
|
+
/**
|
|
15
|
+
* Internal helper: marks requirement IDs complete in REQUIREMENTS.md.
|
|
16
|
+
* Accepts an already-parsed array of IDs. Returns { result, reqPath, rawValue }
|
|
17
|
+
* WITHOUT calling output()/exit.
|
|
18
|
+
*/
|
|
19
|
+
function requirementsMarkCompleteInternal(cwd, reqIds) {
|
|
20
|
+
if (!Array.isArray(reqIds) || reqIds.length === 0) {
|
|
26
21
|
error('no valid requirement IDs found');
|
|
27
22
|
}
|
|
28
23
|
|
|
29
|
-
const
|
|
24
|
+
const projectRootRel = getProjectRoot(cwd);
|
|
25
|
+
const reqPath = path.join(getPlanningRoot(cwd), projectRootRel, 'REQUIREMENTS.md');
|
|
30
26
|
if (!fs.existsSync(reqPath)) {
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
return {
|
|
28
|
+
result: { updated: false, reason: 'REQUIREMENTS.md not found', ids: reqIds },
|
|
29
|
+
reqPath,
|
|
30
|
+
rawValue: 'no requirements file',
|
|
31
|
+
};
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
let reqContent = fs.readFileSync(reqPath, 'utf-8');
|
|
@@ -69,12 +68,37 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
69
68
|
fs.writeFileSync(reqPath, reqContent, 'utf-8');
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
return {
|
|
72
|
+
result: {
|
|
73
|
+
updated: updated.length > 0,
|
|
74
|
+
marked_complete: updated,
|
|
75
|
+
not_found: notFound,
|
|
76
|
+
total: reqIds.length,
|
|
77
|
+
},
|
|
78
|
+
reqPath,
|
|
79
|
+
rawValue: `${updated.length}/${reqIds.length} requirements marked complete`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
84
|
+
if (!reqIdsRaw || reqIdsRaw.length === 0) {
|
|
85
|
+
error('requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Accept comma-separated, space-separated, or bracket-wrapped: [REQ-01, REQ-02]
|
|
89
|
+
const reqIds = reqIdsRaw
|
|
90
|
+
.join(' ')
|
|
91
|
+
.replace(/[\[\]]/g, '')
|
|
92
|
+
.split(/[,\s]+/)
|
|
93
|
+
.map(r => r.trim())
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
|
|
96
|
+
if (reqIds.length === 0) {
|
|
97
|
+
error('no valid requirement IDs found');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { result, rawValue } = requirementsMarkCompleteInternal(cwd, reqIds);
|
|
101
|
+
output(result, raw, rawValue);
|
|
78
102
|
}
|
|
79
103
|
|
|
80
104
|
function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
@@ -83,12 +107,14 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
const planRoot = getPlanningRoot(cwd);
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
110
|
+
const projectRootRel = getProjectRoot(cwd);
|
|
111
|
+
const projectRoot = path.join(planRoot, projectRootRel);
|
|
112
|
+
const roadmapPath = path.join(projectRoot, 'ROADMAP.md');
|
|
113
|
+
const reqPath = path.join(projectRoot, 'REQUIREMENTS.md');
|
|
114
|
+
const statePath = path.join(projectRoot, 'STATE.md');
|
|
89
115
|
const milestonesPath = path.join(planRoot, 'MILESTONES.md');
|
|
90
116
|
const archiveDir = path.join(planRoot, 'milestones');
|
|
91
|
-
const phasesDir = path.join(
|
|
117
|
+
const phasesDir = path.join(projectRoot, 'phases');
|
|
92
118
|
const today = new Date().toISOString().split('T')[0];
|
|
93
119
|
const milestoneName = options.name || version;
|
|
94
120
|
|
|
@@ -135,6 +161,72 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
135
161
|
}
|
|
136
162
|
} catch {}
|
|
137
163
|
|
|
164
|
+
// ── Four-Eyes Gate (GATE-01) ───────────────────────────────────────────────
|
|
165
|
+
const config = loadConfig(cwd);
|
|
166
|
+
const rawConfig = (() => {
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(fs.readFileSync(path.join(planRoot, 'config.json'), 'utf-8'));
|
|
169
|
+
} catch { return {}; }
|
|
170
|
+
})();
|
|
171
|
+
const fourEyesMode = (rawConfig.workflow && rawConfig.workflow.four_eyes) || 'off';
|
|
172
|
+
let governanceNote = null;
|
|
173
|
+
|
|
174
|
+
if (fourEyesMode !== 'off') {
|
|
175
|
+
// Resolve current user identity
|
|
176
|
+
let currentUserStr = '';
|
|
177
|
+
try {
|
|
178
|
+
const identity = requireGitIdentity(cwd);
|
|
179
|
+
currentUserStr = formatAuthorString(identity);
|
|
180
|
+
} catch {
|
|
181
|
+
// Identity gate already ran — this is a safety fallback
|
|
182
|
+
currentUserStr = '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Get contributors for this milestone
|
|
186
|
+
const contribResult = getContributors(cwd);
|
|
187
|
+
const contributors = contribResult.contributors || [];
|
|
188
|
+
|
|
189
|
+
// Run four-eyes check
|
|
190
|
+
const feResult = checkFourEyes(contributors, currentUserStr, fourEyesMode);
|
|
191
|
+
|
|
192
|
+
// Display contributor list (SHR-02 — always when not off)
|
|
193
|
+
const contribNames = contributors.length > 0
|
|
194
|
+
? contributors.join(', ')
|
|
195
|
+
: '(none detected)';
|
|
196
|
+
|
|
197
|
+
if (feResult.passed) {
|
|
198
|
+
// Four-eyes satisfied — display and continue
|
|
199
|
+
process.stderr.write('Contributors: ' + contribNames + ' \u2014 \u2714 Four-eyes satisfied\n');
|
|
200
|
+
} else {
|
|
201
|
+
// Four-eyes failed — display contributor list first
|
|
202
|
+
process.stderr.write('Contributors: ' + contribNames + '\n');
|
|
203
|
+
|
|
204
|
+
// Extract current user name for display
|
|
205
|
+
let displayName = currentUserStr;
|
|
206
|
+
try {
|
|
207
|
+
const identity = requireGitIdentity(cwd);
|
|
208
|
+
displayName = identity.name;
|
|
209
|
+
} catch { /* use full string */ }
|
|
210
|
+
|
|
211
|
+
if (fourEyesMode === 'warn') {
|
|
212
|
+
// GATE-04: warn mode — display warning, proceed, log
|
|
213
|
+
process.stderr.write('\u26A0 You (' + displayName + ') contributed to this milestone. Completing anyway (warn mode).\n');
|
|
214
|
+
governanceNote = 'Four-eyes warning: ' + displayName + ' completed while also a contributor. Contributors: ' + contribNames;
|
|
215
|
+
} else if (fourEyesMode === 'enforce') {
|
|
216
|
+
if (options.force) {
|
|
217
|
+
// GATE-06: --force bypasses enforce
|
|
218
|
+
process.stderr.write('\u26A0 Forced: you (' + displayName + ') are the only contributor. Override logged.\n');
|
|
219
|
+
governanceNote = 'Four-eyes override: ' + displayName + ' force-completed. Contributors: ' + contribNames;
|
|
220
|
+
} else {
|
|
221
|
+
// GATE-05: enforce mode — block completion
|
|
222
|
+
error('\u2718 Blocked: you (' + displayName + ') are the only contributor. Use --force to override.');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Off mode: no check, no output, no performance impact (GATE-03)
|
|
229
|
+
|
|
138
230
|
// Archive ROADMAP.md
|
|
139
231
|
if (fs.existsSync(roadmapPath)) {
|
|
140
232
|
const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
@@ -149,14 +241,15 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
149
241
|
}
|
|
150
242
|
|
|
151
243
|
// Archive audit file if exists
|
|
152
|
-
const auditFile = path.join(
|
|
244
|
+
const auditFile = path.join(projectRoot, `${version}-MILESTONE-AUDIT.md`);
|
|
153
245
|
if (fs.existsSync(auditFile)) {
|
|
154
246
|
fs.renameSync(auditFile, path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
|
|
155
247
|
}
|
|
156
248
|
|
|
157
249
|
// Create/append MILESTONES.md entry
|
|
158
250
|
const accomplishmentsList = accomplishments.map(a => `- ${a}`).join('\n');
|
|
159
|
-
const
|
|
251
|
+
const governanceLine = governanceNote ? `\n**Governance:** ${governanceNote}\n` : '';
|
|
252
|
+
const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}${governanceLine}\n\n---\n\n`;
|
|
160
253
|
|
|
161
254
|
if (fs.existsSync(milestonesPath)) {
|
|
162
255
|
const existing = fs.readFileSync(milestonesPath, 'utf-8');
|
|
@@ -190,9 +283,12 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
190
283
|
/(\*\*Last Activity:\*\*\s*).*/,
|
|
191
284
|
`$1${today}`
|
|
192
285
|
);
|
|
286
|
+
const activitySuffix = governanceNote
|
|
287
|
+
? ` (four-eyes: ${fourEyesMode === 'warn' ? 'warn' : 'force'} override by ${(() => { try { return requireGitIdentity(cwd).name; } catch { return 'unknown'; } })()})`
|
|
288
|
+
: '';
|
|
193
289
|
stateContent = stateContent.replace(
|
|
194
290
|
/(\*\*Last Activity Description:\*\*\s*).*/,
|
|
195
|
-
`$1${version} milestone completed and archived`
|
|
291
|
+
`$1${version} milestone completed and archived${activitySuffix}`
|
|
196
292
|
);
|
|
197
293
|
writeStateMd(statePath, stateContent, cwd);
|
|
198
294
|
}
|
|
@@ -216,6 +312,31 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
216
312
|
} catch {}
|
|
217
313
|
}
|
|
218
314
|
|
|
315
|
+
// Archive completed quick task directories
|
|
316
|
+
let quickArchived = false;
|
|
317
|
+
const quickDir = path.join(projectRoot, 'quick');
|
|
318
|
+
if (fs.existsSync(quickDir)) {
|
|
319
|
+
try {
|
|
320
|
+
const quickArchiveDir = path.join(archiveDir, `${version}-quick`);
|
|
321
|
+
const quickEntries = fs.readdirSync(quickDir, { withFileTypes: true });
|
|
322
|
+
const quickDirNames = quickEntries.filter(e => e.isDirectory()).map(e => e.name);
|
|
323
|
+
let archivedCount = 0;
|
|
324
|
+
for (const dir of quickDirNames) {
|
|
325
|
+
// Check if this quick dir has a SUMMARY file (completed)
|
|
326
|
+
const dirPath = path.join(quickDir, dir);
|
|
327
|
+
const dirFiles = fs.readdirSync(dirPath);
|
|
328
|
+
const hasSummary = dirFiles.some(f => f.endsWith('-SUMMARY.md'));
|
|
329
|
+
if (!hasSummary) continue;
|
|
330
|
+
if (!fs.existsSync(quickArchiveDir)) {
|
|
331
|
+
fs.mkdirSync(quickArchiveDir, { recursive: true });
|
|
332
|
+
}
|
|
333
|
+
fs.renameSync(dirPath, path.join(quickArchiveDir, dir));
|
|
334
|
+
archivedCount++;
|
|
335
|
+
}
|
|
336
|
+
quickArchived = archivedCount > 0;
|
|
337
|
+
} catch {}
|
|
338
|
+
}
|
|
339
|
+
|
|
219
340
|
const result = {
|
|
220
341
|
version,
|
|
221
342
|
name: milestoneName,
|
|
@@ -229,6 +350,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
229
350
|
requirements: fs.existsSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`)),
|
|
230
351
|
audit: fs.existsSync(path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
|
|
231
352
|
phases: phasesArchived,
|
|
353
|
+
quick: quickArchived,
|
|
232
354
|
},
|
|
233
355
|
milestones_updated: true,
|
|
234
356
|
state_updated: fs.existsSync(statePath),
|
|
@@ -239,5 +361,6 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
239
361
|
|
|
240
362
|
module.exports = {
|
|
241
363
|
cmdRequirementsMarkComplete,
|
|
364
|
+
requirementsMarkCompleteInternal,
|
|
242
365
|
cmdMilestoneComplete,
|
|
243
366
|
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for milestone.cjs — quick dir archival in cmdMilestoneComplete
|
|
3
|
+
*
|
|
4
|
+
* Uses Node.js built-in test runner (node:test) and assert (node:assert).
|
|
5
|
+
* Each test creates an isolated temp directory fixture and cleans up after.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { describe, it, beforeEach, afterEach } = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const { createFixture, writeFile } = require('./test-helpers.cjs');
|
|
14
|
+
const { cmdMilestoneComplete } = require('./milestone.cjs');
|
|
15
|
+
|
|
16
|
+
// Helper: capture stdout from cmdMilestoneComplete (which calls output())
|
|
17
|
+
function captureResult(fn) {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
const origWrite = process.stdout.write;
|
|
20
|
+
process.stdout.write = function (chunk) {
|
|
21
|
+
chunks.push(String(chunk));
|
|
22
|
+
};
|
|
23
|
+
// Also suppress stderr (governance / state messages)
|
|
24
|
+
const origStderrWrite = process.stderr.write;
|
|
25
|
+
process.stderr.write = function () {};
|
|
26
|
+
// Prevent process.exit from actually exiting during tests
|
|
27
|
+
const origExit = process.exit;
|
|
28
|
+
process.exit = function () {};
|
|
29
|
+
try {
|
|
30
|
+
fn();
|
|
31
|
+
} finally {
|
|
32
|
+
process.stdout.write = origWrite;
|
|
33
|
+
process.stderr.write = origStderrWrite;
|
|
34
|
+
process.exit = origExit;
|
|
35
|
+
}
|
|
36
|
+
const raw = chunks.join('');
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(raw);
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a root-layout fixture suitable for cmdMilestoneComplete.
|
|
46
|
+
* Root layout: no PROJECTS.md/REPOS.md (avoids v2 detection).
|
|
47
|
+
* Includes config.local.json with planningRoot, STATE.md, ROADMAP.md, phases/.
|
|
48
|
+
*/
|
|
49
|
+
function createMilestoneFixture(extras) {
|
|
50
|
+
const base = {
|
|
51
|
+
'config.json': JSON.stringify({}),
|
|
52
|
+
'config.local.json': JSON.stringify({ planningRoot: '.' }),
|
|
53
|
+
'STATE.md': '# Project State\n\nPhase: 1\nStatus: Ready\nProgress: [----------] 0%\n',
|
|
54
|
+
'ROADMAP.md': '# Roadmap\n\n## Phase 1: Test\n',
|
|
55
|
+
'phases/': null,
|
|
56
|
+
};
|
|
57
|
+
if (extras) {
|
|
58
|
+
Object.assign(base, extras);
|
|
59
|
+
}
|
|
60
|
+
return createFixture(base);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Quick Dir Archival Tests ────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
describe('cmdMilestoneComplete quick dir archival', () => {
|
|
66
|
+
let fixture;
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
if (fixture) fixture.cleanup();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('archives completed quick dirs (with SUMMARY.md) to milestones/v1.0-quick/', () => {
|
|
73
|
+
fixture = createMilestoneFixture({
|
|
74
|
+
'quick/260101-abc-some-task/260101-abc-PLAN.md': '# Plan',
|
|
75
|
+
'quick/260101-abc-some-task/260101-abc-SUMMARY.md': '# Summary',
|
|
76
|
+
});
|
|
77
|
+
const cwd = fixture.cwd;
|
|
78
|
+
|
|
79
|
+
const result = captureResult(() => {
|
|
80
|
+
cmdMilestoneComplete(cwd, 'v1.0', {}, false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Quick dir should be moved to milestones/v1.0-quick/
|
|
84
|
+
const archivedDir = path.join(cwd, 'milestones', 'v1.0-quick', '260101-abc-some-task');
|
|
85
|
+
assert.ok(fs.existsSync(archivedDir), 'Quick dir should be archived to milestones/v1.0-quick/');
|
|
86
|
+
assert.ok(!fs.existsSync(path.join(cwd, 'quick', '260101-abc-some-task')), 'Quick dir should be removed from quick/');
|
|
87
|
+
assert.ok(result, 'Should return result JSON');
|
|
88
|
+
assert.equal(result.archived.quick, true, 'archived.quick should be true');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('does not move HISTORY.md from quick/', () => {
|
|
92
|
+
fixture = createMilestoneFixture({
|
|
93
|
+
'quick/HISTORY.md': '# History\n',
|
|
94
|
+
'quick/260102-def-other/260102-def-SUMMARY.md': '# Summary',
|
|
95
|
+
});
|
|
96
|
+
const cwd = fixture.cwd;
|
|
97
|
+
|
|
98
|
+
const result = captureResult(() => {
|
|
99
|
+
cmdMilestoneComplete(cwd, 'v1.0', {}, false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// HISTORY.md should remain in quick/
|
|
103
|
+
assert.ok(fs.existsSync(path.join(cwd, 'quick', 'HISTORY.md')), 'HISTORY.md should stay in quick/');
|
|
104
|
+
// The completed dir should be archived
|
|
105
|
+
assert.ok(fs.existsSync(path.join(cwd, 'milestones', 'v1.0-quick', '260102-def-other')), 'Completed dir should be archived');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('skips plain files (non-directories) in quick/', () => {
|
|
109
|
+
fixture = createMilestoneFixture({
|
|
110
|
+
'quick/some-notes.txt': 'random notes',
|
|
111
|
+
'quick/260103-ghi-task/260103-ghi-SUMMARY.md': '# Summary',
|
|
112
|
+
});
|
|
113
|
+
const cwd = fixture.cwd;
|
|
114
|
+
|
|
115
|
+
const result = captureResult(() => {
|
|
116
|
+
cmdMilestoneComplete(cwd, 'v1.0', {}, false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// File should remain
|
|
120
|
+
assert.ok(fs.existsSync(path.join(cwd, 'quick', 'some-notes.txt')), 'Plain files should remain in quick/');
|
|
121
|
+
// Dir should be archived
|
|
122
|
+
assert.ok(fs.existsSync(path.join(cwd, 'milestones', 'v1.0-quick', '260103-ghi-task')), 'Completed dir should be archived');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('does NOT move quick dirs without SUMMARY.md (incomplete)', () => {
|
|
126
|
+
fixture = createMilestoneFixture({
|
|
127
|
+
'quick/260104-jkl-wip/260104-jkl-PLAN.md': '# Plan',
|
|
128
|
+
});
|
|
129
|
+
const cwd = fixture.cwd;
|
|
130
|
+
|
|
131
|
+
const result = captureResult(() => {
|
|
132
|
+
cmdMilestoneComplete(cwd, 'v1.0', {}, false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Should NOT be moved
|
|
136
|
+
assert.ok(fs.existsSync(path.join(cwd, 'quick', '260104-jkl-wip')), 'Incomplete quick dir should stay in quick/');
|
|
137
|
+
assert.ok(!fs.existsSync(path.join(cwd, 'milestones', 'v1.0-quick')), 'No quick archive dir should be created');
|
|
138
|
+
assert.ok(result, 'Should return result JSON');
|
|
139
|
+
assert.equal(result.archived.quick, false, 'archived.quick should be false when nothing archived');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('gracefully handles when quick/ does not exist', () => {
|
|
143
|
+
fixture = createMilestoneFixture();
|
|
144
|
+
const cwd = fixture.cwd;
|
|
145
|
+
|
|
146
|
+
// No quick/ directory at all
|
|
147
|
+
const result = captureResult(() => {
|
|
148
|
+
cmdMilestoneComplete(cwd, 'v1.0', {}, false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
assert.ok(result, 'Should return result JSON without error');
|
|
152
|
+
assert.equal(result.archived.quick, false, 'archived.quick should be false when no quick/ dir');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns quickArchived=false when quick/ has no completed dirs', () => {
|
|
156
|
+
fixture = createMilestoneFixture({
|
|
157
|
+
'quick/HISTORY.md': '# History',
|
|
158
|
+
'quick/260105-mno-wip/260105-mno-PLAN.md': '# Plan',
|
|
159
|
+
});
|
|
160
|
+
const cwd = fixture.cwd;
|
|
161
|
+
|
|
162
|
+
const result = captureResult(() => {
|
|
163
|
+
cmdMilestoneComplete(cwd, 'v1.0', {}, false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
assert.equal(result.archived.quick, false, 'archived.quick should be false');
|
|
167
|
+
assert.ok(!fs.existsSync(path.join(cwd, 'milestones', 'v1.0-quick')), 'No quick archive dir should be created');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('creates milestones/v{X.Y}-quick/ only when there are dirs to archive', () => {
|
|
171
|
+
fixture = createMilestoneFixture({
|
|
172
|
+
'quick/260106-pqr-first/260106-pqr-SUMMARY.md': '# Summary',
|
|
173
|
+
'quick/260107-stu-second/260107-stu-SUMMARY.md': '# Summary',
|
|
174
|
+
'quick/260108-vwx-wip/260108-vwx-PLAN.md': '# Plan',
|
|
175
|
+
});
|
|
176
|
+
const cwd = fixture.cwd;
|
|
177
|
+
|
|
178
|
+
const result = captureResult(() => {
|
|
179
|
+
cmdMilestoneComplete(cwd, 'v2.0', {}, false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Both completed dirs should be archived
|
|
183
|
+
assert.ok(fs.existsSync(path.join(cwd, 'milestones', 'v2.0-quick', '260106-pqr-first')), 'First completed dir archived');
|
|
184
|
+
assert.ok(fs.existsSync(path.join(cwd, 'milestones', 'v2.0-quick', '260107-stu-second')), 'Second completed dir archived');
|
|
185
|
+
// Incomplete dir should remain
|
|
186
|
+
assert.ok(fs.existsSync(path.join(cwd, 'quick', '260108-vwx-wip')), 'Incomplete dir should stay');
|
|
187
|
+
assert.equal(result.archived.quick, true, 'archived.quick should be true');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('result JSON includes archived.quick field', () => {
|
|
191
|
+
fixture = createMilestoneFixture();
|
|
192
|
+
const cwd = fixture.cwd;
|
|
193
|
+
|
|
194
|
+
const result = captureResult(() => {
|
|
195
|
+
cmdMilestoneComplete(cwd, 'v1.0', {}, false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
assert.ok(result, 'Should return result');
|
|
199
|
+
assert.ok('archived' in result, 'Result should have archived object');
|
|
200
|
+
assert.ok('quick' in result.archived, 'archived should have quick field');
|
|
201
|
+
assert.equal(typeof result.archived.quick, 'boolean', 'archived.quick should be boolean');
|
|
202
|
+
});
|
|
203
|
+
});
|