@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
|
@@ -224,7 +224,12 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
224
224
|
output(result, raw);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Internal helper: performs the ROADMAP.md plan-progress update for a single
|
|
229
|
+
* phase and returns { result, roadmapPath, rawValue } without emitting/exiting.
|
|
230
|
+
* External callers (cmdRoadmapUpdatePlanProgress, cmdPlanFinalize) handle output.
|
|
231
|
+
*/
|
|
232
|
+
function roadmapUpdatePlanProgressInternal(cwd, phaseNum) {
|
|
228
233
|
if (!phaseNum) {
|
|
229
234
|
error('phase number required for roadmap update-plan-progress');
|
|
230
235
|
}
|
|
@@ -240,8 +245,11 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
240
245
|
const summaryCount = phaseInfo.summaries.length;
|
|
241
246
|
|
|
242
247
|
if (planCount === 0) {
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
return {
|
|
249
|
+
result: { updated: false, reason: 'No plans found', plan_count: 0, summary_count: 0 },
|
|
250
|
+
roadmapPath,
|
|
251
|
+
rawValue: 'no plans',
|
|
252
|
+
};
|
|
245
253
|
}
|
|
246
254
|
|
|
247
255
|
const isComplete = summaryCount >= planCount;
|
|
@@ -249,8 +257,11 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
249
257
|
const today = new Date().toISOString().split('T')[0];
|
|
250
258
|
|
|
251
259
|
if (!fs.existsSync(roadmapPath)) {
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
return {
|
|
261
|
+
result: { updated: false, reason: 'ROADMAP.md not found', plan_count: planCount, summary_count: summaryCount },
|
|
262
|
+
roadmapPath,
|
|
263
|
+
rawValue: 'no roadmap',
|
|
264
|
+
};
|
|
254
265
|
}
|
|
255
266
|
|
|
256
267
|
let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
@@ -288,18 +299,28 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
288
299
|
|
|
289
300
|
fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
|
|
290
301
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
302
|
+
return {
|
|
303
|
+
result: {
|
|
304
|
+
updated: true,
|
|
305
|
+
phase: phaseNum,
|
|
306
|
+
plan_count: planCount,
|
|
307
|
+
summary_count: summaryCount,
|
|
308
|
+
status,
|
|
309
|
+
complete: isComplete,
|
|
310
|
+
},
|
|
311
|
+
roadmapPath,
|
|
312
|
+
rawValue: `${summaryCount}/${planCount} ${status}`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
317
|
+
const { result, rawValue } = roadmapUpdatePlanProgressInternal(cwd, phaseNum);
|
|
318
|
+
output(result, raw, rawValue);
|
|
299
319
|
}
|
|
300
320
|
|
|
301
321
|
module.exports = {
|
|
302
322
|
cmdRoadmapGetPhase,
|
|
303
323
|
cmdRoadmapAnalyze,
|
|
304
324
|
cmdRoadmapUpdatePlanProgress,
|
|
325
|
+
roadmapUpdatePlanProgressInternal,
|
|
305
326
|
};
|
|
@@ -774,13 +774,13 @@ function cmdSpecsFinalize(cwd, idOrFilename, raw, author) {
|
|
|
774
774
|
const content = buildSpecContent(spec.frontmatter, spec.body);
|
|
775
775
|
fs.writeFileSync(spec.path, content, 'utf-8');
|
|
776
776
|
|
|
777
|
-
//
|
|
777
|
+
// Set source ideas to done via frontmatter edit (no file moves)
|
|
778
|
+
const { setIdeaStatus } = require('./ideas.cjs');
|
|
778
779
|
const ideasMoved = [];
|
|
779
780
|
const ideasFailed = [];
|
|
780
781
|
const sourceIdeas = spec.frontmatter.source_ideas || [];
|
|
781
782
|
|
|
782
783
|
for (const ideaFilename of sourceIdeas) {
|
|
783
|
-
// Extract idea ID from filename (e.g., "001-slug.md" -> "001")
|
|
784
784
|
const idMatch = ideaFilename.match(/^(\d+)/);
|
|
785
785
|
if (!idMatch) {
|
|
786
786
|
ideasFailed.push({ idea: ideaFilename, reason: 'could not parse ID' });
|
|
@@ -789,90 +789,13 @@ function cmdSpecsFinalize(cwd, idOrFilename, raw, author) {
|
|
|
789
789
|
const ideaId = idMatch[1];
|
|
790
790
|
|
|
791
791
|
try {
|
|
792
|
-
|
|
793
|
-
// so we use the internal helpers directly instead)
|
|
794
|
-
// Instead, do the move manually following the same pattern
|
|
795
|
-
const { findIdeaFile, ensureIdeasDirs } = require('./ideas.cjs');
|
|
796
|
-
const idea = findIdeaFile(cwd, ideaId);
|
|
797
|
-
|
|
798
|
-
if (!idea) {
|
|
799
|
-
ideasFailed.push({ idea: ideaFilename, reason: 'idea not found' });
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
if (idea.state === 'done') {
|
|
804
|
-
// Already done, skip
|
|
805
|
-
ideasMoved.push(ideaFilename);
|
|
806
|
-
continue;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
if (idea.state === 'consolidated') {
|
|
810
|
-
// Consolidated ideas cannot be used as spec sources
|
|
811
|
-
ideasFailed.push({ idea: ideaFilename, reason: 'idea is consolidated' });
|
|
812
|
-
continue;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
ensureIdeasDirs(cwd);
|
|
816
|
-
|
|
817
|
-
const planRootRel = path.relative(cwd, getPlanningRoot(cwd)) || '.';
|
|
818
|
-
const fromRel = path.join(planRootRel, 'ideas', idea.state, idea.filename);
|
|
819
|
-
const toRel = path.join(planRootRel, 'ideas', 'done', idea.filename);
|
|
820
|
-
const mvResult = execGit(cwd, ['mv', fromRel, toRel]);
|
|
821
|
-
|
|
822
|
-
if (mvResult.exitCode !== 0) {
|
|
823
|
-
ideasFailed.push({ idea: ideaFilename, reason: mvResult.stderr || 'git mv failed' });
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
|
|
792
|
+
setIdeaStatus(cwd, ideaId, 'done');
|
|
827
793
|
ideasMoved.push(ideaFilename);
|
|
828
794
|
} catch (err) {
|
|
829
795
|
ideasFailed.push({ idea: ideaFilename, reason: err.message || 'unknown error' });
|
|
830
796
|
}
|
|
831
797
|
}
|
|
832
798
|
|
|
833
|
-
// Move associated research documents and update links in idea files
|
|
834
|
-
const researchDocsMoved = [];
|
|
835
|
-
|
|
836
|
-
for (const ideaFilename of ideasMoved) {
|
|
837
|
-
try {
|
|
838
|
-
// Extract slug from idea filename: "001-my-idea.md" -> "my-idea"
|
|
839
|
-
const slug = ideaFilename.replace(/^\d+-/, '').replace(/\.md$/, '');
|
|
840
|
-
const researchDocName = `${slug}-research.md`;
|
|
841
|
-
const specPlanRoot = getPlanningRoot(cwd);
|
|
842
|
-
const specPlanRootRel = path.relative(cwd, specPlanRoot) || '.';
|
|
843
|
-
const sourcePath = path.join(specPlanRoot, 'docs', 'ideas', 'pending', researchDocName);
|
|
844
|
-
|
|
845
|
-
// Skip if no research doc exists (graceful handling)
|
|
846
|
-
if (!fs.existsSync(sourcePath)) continue;
|
|
847
|
-
|
|
848
|
-
// Ensure done directory exists
|
|
849
|
-
fs.mkdirSync(path.join(specPlanRoot, 'docs', 'ideas', 'done'), { recursive: true });
|
|
850
|
-
|
|
851
|
-
const sourceRel = path.join(specPlanRootRel, 'docs', 'ideas', 'pending', researchDocName);
|
|
852
|
-
const targetRel = path.join(specPlanRootRel, 'docs', 'ideas', 'done', researchDocName);
|
|
853
|
-
const mvResult = execGit(cwd, ['mv', sourceRel, targetRel]);
|
|
854
|
-
|
|
855
|
-
if (mvResult.exitCode !== 0) continue;
|
|
856
|
-
|
|
857
|
-
researchDocsMoved.push(researchDocName);
|
|
858
|
-
|
|
859
|
-
// Update documentLink paths in the idea file (now in done/)
|
|
860
|
-
const ideaDonePath = path.join(getPlanningRoot(cwd), 'ideas', 'done', ideaFilename);
|
|
861
|
-
if (fs.existsSync(ideaDonePath)) {
|
|
862
|
-
let ideaContent = fs.readFileSync(ideaDonePath, 'utf-8');
|
|
863
|
-
// Replace pending/ paths with done/ for this research doc
|
|
864
|
-
const pendingPattern = `docs/ideas/pending/${researchDocName}`;
|
|
865
|
-
const doneReplacement = `docs/ideas/done/${researchDocName}`;
|
|
866
|
-
if (ideaContent.includes(pendingPattern)) {
|
|
867
|
-
ideaContent = ideaContent.split(pendingPattern).join(doneReplacement);
|
|
868
|
-
fs.writeFileSync(ideaDonePath, ideaContent, 'utf-8');
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
} catch {
|
|
872
|
-
// Graceful handling: skip on any error
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
799
|
// Auto-commit the spec status change and idea moves
|
|
877
800
|
execGit(cwd, ['add', '-A']);
|
|
878
801
|
const title = spec.frontmatter.title || spec.frontmatter.id;
|
|
@@ -884,7 +807,6 @@ function cmdSpecsFinalize(cwd, idOrFilename, raw, author) {
|
|
|
884
807
|
status: 'final',
|
|
885
808
|
ideas_moved: ideasMoved,
|
|
886
809
|
ideas_failed: ideasFailed,
|
|
887
|
-
research_docs_moved: researchDocsMoved,
|
|
888
810
|
};
|
|
889
811
|
output(result, raw);
|
|
890
812
|
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GATE tests: State Transition Invariant
|
|
3
|
+
*
|
|
4
|
+
* Enforces that no code path in ideas, todos, jobs, or specs uses file moves
|
|
5
|
+
* (git mv, renameSync, unlinkSync) for state transitions. All state changes
|
|
6
|
+
* must use frontmatter edits (setIdeaStatus, setTodoStatus, setJobStatus).
|
|
7
|
+
*
|
|
8
|
+
* This GATE was introduced in Phase 133 (v20.0) after rewriting all 9 file-move
|
|
9
|
+
* state transitions to frontmatter edits.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { describe, it } = require('node:test');
|
|
13
|
+
const assert = require('node:assert/strict');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const LIB_DIR = path.join(__dirname);
|
|
18
|
+
|
|
19
|
+
// State subdirectory patterns that indicate directory-based state management
|
|
20
|
+
const STATE_DIR_PATTERNS = [
|
|
21
|
+
/ideas\/pending\//,
|
|
22
|
+
/ideas\/done\//,
|
|
23
|
+
/ideas\/rejected\//,
|
|
24
|
+
/ideas\/consolidated\//,
|
|
25
|
+
/todos\/pending\//,
|
|
26
|
+
/todos\/completed\//,
|
|
27
|
+
/jobs\/pending\//,
|
|
28
|
+
/jobs\/in-progress\//,
|
|
29
|
+
/jobs\/completed\//,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Move/rename/delete operations that are prohibited in state transition contexts
|
|
33
|
+
const MOVE_PATTERNS = [
|
|
34
|
+
{ pattern: /execGit\([^)]*\[\s*['"]mv['"]/, label: 'git mv' },
|
|
35
|
+
{ pattern: /renameSync\(/, label: 'fs.renameSync' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Files to scan (source files only, not tests)
|
|
39
|
+
const TARGET_FILES = ['ideas.cjs', 'commands.cjs', 'jobs.cjs', 'specs.cjs'];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Scan a file for lines containing both a state directory pattern AND a move operation.
|
|
43
|
+
* Returns violations as { line: number, text: string, stateDir: string, operation: string }.
|
|
44
|
+
*/
|
|
45
|
+
function scanForViolations(filePath) {
|
|
46
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
47
|
+
const lines = content.split('\n');
|
|
48
|
+
const violations = [];
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < lines.length; i++) {
|
|
51
|
+
const line = lines[i];
|
|
52
|
+
|
|
53
|
+
// Skip comments
|
|
54
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*')) continue;
|
|
55
|
+
|
|
56
|
+
// Check each move pattern
|
|
57
|
+
for (const { pattern: movePattern, label: moveLabel } of MOVE_PATTERNS) {
|
|
58
|
+
if (!movePattern.test(line)) continue;
|
|
59
|
+
|
|
60
|
+
// Check if this line or nearby lines reference state directories
|
|
61
|
+
// Check a window of +/-5 lines for state directory context
|
|
62
|
+
const windowStart = Math.max(0, i - 5);
|
|
63
|
+
const windowEnd = Math.min(lines.length - 1, i + 5);
|
|
64
|
+
const window = lines.slice(windowStart, windowEnd + 1).join('\n');
|
|
65
|
+
|
|
66
|
+
for (const statePattern of STATE_DIR_PATTERNS) {
|
|
67
|
+
if (statePattern.test(window)) {
|
|
68
|
+
violations.push({
|
|
69
|
+
line: i + 1,
|
|
70
|
+
text: line.trim(),
|
|
71
|
+
stateDir: statePattern.source,
|
|
72
|
+
operation: moveLabel,
|
|
73
|
+
});
|
|
74
|
+
break; // One violation per line is enough
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return violations;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
describe('state transition gate: no file moves for state changes', () => {
|
|
84
|
+
|
|
85
|
+
for (const fileName of TARGET_FILES) {
|
|
86
|
+
it(`GATE: zero file-move state transitions in ${fileName}`, () => {
|
|
87
|
+
const filePath = path.join(LIB_DIR, fileName);
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(filePath)) {
|
|
90
|
+
// File doesn't exist — not a violation, just skip
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const violations = scanForViolations(filePath);
|
|
95
|
+
|
|
96
|
+
if (violations.length > 0) {
|
|
97
|
+
const details = violations.map(v =>
|
|
98
|
+
` Line ${v.line}: ${v.operation} near ${v.stateDir}\n ${v.text}`
|
|
99
|
+
).join('\n');
|
|
100
|
+
|
|
101
|
+
assert.fail(
|
|
102
|
+
`Found ${violations.length} file-move state transition(s) in ${fileName}:\n${details}\n\n` +
|
|
103
|
+
'All state transitions must use frontmatter edits (setIdeaStatus, setTodoStatus, setJobStatus).\n' +
|
|
104
|
+
'Replace git mv / renameSync with the appropriate set*Status helper.'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
assert.equal(violations.length, 0,
|
|
109
|
+
`${fileName} should have zero file-move state transitions`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
it('GATE: cmdTodoComplete has no unlinkSync (copy+delete pattern)', () => {
|
|
114
|
+
const filePath = path.join(LIB_DIR, 'commands.cjs');
|
|
115
|
+
if (!fs.existsSync(filePath)) return;
|
|
116
|
+
|
|
117
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
118
|
+
|
|
119
|
+
// Find cmdTodoComplete function body
|
|
120
|
+
const fnStart = content.indexOf('function cmdTodoComplete');
|
|
121
|
+
if (fnStart === -1) return; // Function not found
|
|
122
|
+
|
|
123
|
+
// Find next function definition or module.exports as boundary
|
|
124
|
+
const afterFn = content.slice(fnStart + 1);
|
|
125
|
+
const fnEnd = afterFn.search(/\nfunction\s|\nmodule\.exports/);
|
|
126
|
+
const fnBody = fnEnd > 0 ? afterFn.slice(0, fnEnd) : afterFn;
|
|
127
|
+
|
|
128
|
+
if (fnBody.includes('unlinkSync')) {
|
|
129
|
+
assert.fail(
|
|
130
|
+
'cmdTodoComplete contains unlinkSync — this is a copy+delete state transition pattern.\n' +
|
|
131
|
+
'Use setTodoStatus to edit frontmatter instead of copying and deleting files.'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('GATE: cmdSpecsFinalize has no research doc moving', () => {
|
|
137
|
+
const filePath = path.join(LIB_DIR, 'specs.cjs');
|
|
138
|
+
if (!fs.existsSync(filePath)) return;
|
|
139
|
+
|
|
140
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
141
|
+
|
|
142
|
+
// Find cmdSpecsFinalize function body
|
|
143
|
+
const fnStart = content.indexOf('function cmdSpecsFinalize');
|
|
144
|
+
if (fnStart === -1) return;
|
|
145
|
+
|
|
146
|
+
const afterFn = content.slice(fnStart + 1);
|
|
147
|
+
const fnEnd = afterFn.search(/\nfunction\s|\nmodule\.exports/);
|
|
148
|
+
const fnBody = fnEnd > 0 ? afterFn.slice(0, fnEnd) : afterFn;
|
|
149
|
+
|
|
150
|
+
// Check for research doc directory references
|
|
151
|
+
const hasResearchDocMove = /docs\/ideas\/(pending|done|rejected|consolidated)/.test(fnBody);
|
|
152
|
+
|
|
153
|
+
if (hasResearchDocMove) {
|
|
154
|
+
assert.fail(
|
|
155
|
+
'cmdSpecsFinalize still references research doc state directories.\n' +
|
|
156
|
+
'Research doc flattening is handled by Phase 134 — remove from finalize.'
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { loadConfig, getMilestoneInfo, output, error, getProjectRoot } = require('./core.cjs');
|
|
7
|
+
const { loadConfig, getMilestoneInfo, getMilestonePhaseFilter, output, error, getProjectRoot } = require('./core.cjs');
|
|
8
8
|
const { extractFrontmatter, reconstructFrontmatter } = require('./frontmatter.cjs');
|
|
9
9
|
const { getPlanningRoot } = require('./paths.cjs');
|
|
10
10
|
|
|
@@ -69,9 +69,6 @@ function cmdStateLoad(cwd, raw) {
|
|
|
69
69
|
const lines = [
|
|
70
70
|
`model_profile=${c.model_profile}`,
|
|
71
71
|
`commit_docs=${c.commit_docs}`,
|
|
72
|
-
`branching_strategy=${c.branching_strategy}`,
|
|
73
|
-
`phase_branch_template=${c.phase_branch_template}`,
|
|
74
|
-
`milestone_branch_template=${c.milestone_branch_template}`,
|
|
75
72
|
`base_branch=${c.base_branch}`,
|
|
76
73
|
`parallelization=${c.parallelization}`,
|
|
77
74
|
`research=${c.research}`,
|
|
@@ -282,11 +279,19 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
282
279
|
}
|
|
283
280
|
}
|
|
284
281
|
|
|
285
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Internal helper: recalculates the progress bar from disk and updates STATE.md.
|
|
284
|
+
* Returns { updated, percent, completed, total, bar, reason, statePath, rawValue }
|
|
285
|
+
* WITHOUT calling output()/exit. Callers handle output emission themselves.
|
|
286
|
+
* `rawValue` is the string emitted by the CLI in raw mode (progressStr or 'false').
|
|
287
|
+
*/
|
|
288
|
+
function stateUpdateProgressInternal(cwd) {
|
|
286
289
|
let projectRoot;
|
|
287
290
|
try { projectRoot = getProjectRoot(cwd); } catch { projectRoot = path.relative(cwd, getPlanningRoot(cwd)) || '.'; }
|
|
288
291
|
const statePath = path.join(cwd, projectRoot, 'STATE.md');
|
|
289
|
-
if (!fs.existsSync(statePath)) {
|
|
292
|
+
if (!fs.existsSync(statePath)) {
|
|
293
|
+
return { result: { error: 'STATE.md not found' }, statePath, rawValue: undefined };
|
|
294
|
+
}
|
|
290
295
|
|
|
291
296
|
let content = fs.readFileSync(statePath, 'utf-8');
|
|
292
297
|
|
|
@@ -317,13 +322,35 @@ function cmdStateUpdateProgress(cwd, raw) {
|
|
|
317
322
|
if (boldProgressPattern.test(content)) {
|
|
318
323
|
content = content.replace(boldProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
|
|
319
324
|
writeStateMd(statePath, content, cwd);
|
|
320
|
-
|
|
325
|
+
return {
|
|
326
|
+
result: { updated: true, percent, completed: totalSummaries, total: totalPlans, bar: progressStr },
|
|
327
|
+
statePath,
|
|
328
|
+
rawValue: progressStr,
|
|
329
|
+
};
|
|
321
330
|
} else if (plainProgressPattern.test(content)) {
|
|
322
331
|
content = content.replace(plainProgressPattern, (_match, prefix) => `${prefix}${progressStr}`);
|
|
323
332
|
writeStateMd(statePath, content, cwd);
|
|
324
|
-
|
|
333
|
+
return {
|
|
334
|
+
result: { updated: true, percent, completed: totalSummaries, total: totalPlans, bar: progressStr },
|
|
335
|
+
statePath,
|
|
336
|
+
rawValue: progressStr,
|
|
337
|
+
};
|
|
325
338
|
} else {
|
|
326
|
-
|
|
339
|
+
return {
|
|
340
|
+
result: { updated: false, reason: 'Progress field not found in STATE.md' },
|
|
341
|
+
statePath,
|
|
342
|
+
rawValue: 'false',
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function cmdStateUpdateProgress(cwd, raw) {
|
|
348
|
+
const { result, rawValue } = stateUpdateProgressInternal(cwd);
|
|
349
|
+
if (rawValue === undefined) {
|
|
350
|
+
// STATE.md missing branch — original used `output({ error: ... }, raw)`
|
|
351
|
+
output(result, raw);
|
|
352
|
+
} else {
|
|
353
|
+
output(result, raw, rawValue);
|
|
327
354
|
}
|
|
328
355
|
}
|
|
329
356
|
|
|
@@ -564,40 +591,6 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
564
591
|
|
|
565
592
|
// ─── State Frontmatter Sync ──────────────────────────────────────────────────
|
|
566
593
|
|
|
567
|
-
/**
|
|
568
|
-
* Build a milestone phase filter from ROADMAP.md.
|
|
569
|
-
* Returns a function that checks if a phase directory belongs to the current milestone.
|
|
570
|
-
*/
|
|
571
|
-
function getMilestonePhaseFilter(cwd) {
|
|
572
|
-
const milestonePhaseNums = new Set();
|
|
573
|
-
try {
|
|
574
|
-
const roadmap = fs.readFileSync(path.join(getPlanningRoot(cwd), 'ROADMAP.md'), 'utf-8');
|
|
575
|
-
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
|
|
576
|
-
let m;
|
|
577
|
-
while ((m = phasePattern.exec(roadmap)) !== null) {
|
|
578
|
-
milestonePhaseNums.add(m[1]);
|
|
579
|
-
}
|
|
580
|
-
} catch {}
|
|
581
|
-
|
|
582
|
-
if (milestonePhaseNums.size === 0) {
|
|
583
|
-
const passAll = () => true;
|
|
584
|
-
passAll.phaseCount = 0;
|
|
585
|
-
return passAll;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const normalized = new Set(
|
|
589
|
-
[...milestonePhaseNums].map(n => (n.replace(/^0+/, '') || '0').toLowerCase())
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
function isDirInMilestone(dirName) {
|
|
593
|
-
const m = dirName.match(/^0*(\d+[A-Za-z]?(?:\.\d+)*)/);
|
|
594
|
-
if (!m) return false;
|
|
595
|
-
return normalized.has(m[1].toLowerCase());
|
|
596
|
-
}
|
|
597
|
-
isDirInMilestone.phaseCount = milestonePhaseNums.size;
|
|
598
|
-
return isDirInMilestone;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
594
|
/**
|
|
602
595
|
* Extract machine-readable fields from STATE.md markdown body and build
|
|
603
596
|
* a YAML frontmatter object. Allows hooks and scripts to read state
|
|
@@ -632,7 +625,13 @@ function buildStateFrontmatter(bodyContent, cwd) {
|
|
|
632
625
|
|
|
633
626
|
if (cwd) {
|
|
634
627
|
try {
|
|
635
|
-
|
|
628
|
+
let phasesDir;
|
|
629
|
+
try {
|
|
630
|
+
const projectRoot = getProjectRoot(cwd);
|
|
631
|
+
phasesDir = path.join(cwd, projectRoot, 'phases');
|
|
632
|
+
} catch {
|
|
633
|
+
phasesDir = path.join(getPlanningRoot(cwd), 'phases');
|
|
634
|
+
}
|
|
636
635
|
if (fs.existsSync(phasesDir)) {
|
|
637
636
|
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
|
638
637
|
const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
@@ -679,7 +678,10 @@ function buildStateFrontmatter(bodyContent, cwd) {
|
|
|
679
678
|
normalizedStatus = 'discussing';
|
|
680
679
|
} else if (statusLower.includes('verif')) {
|
|
681
680
|
normalizedStatus = 'verifying';
|
|
682
|
-
} else if (statusLower
|
|
681
|
+
} else if (statusLower === 'completed' || statusLower === 'done' || statusLower === 'project completed') {
|
|
682
|
+
// Only exact matches trigger 'completed' — "Phase X execution complete" or
|
|
683
|
+
// "Milestone shipped" should NOT mark the project as completed.
|
|
684
|
+
// Project completion is a manual action via /dgs:complete-project.
|
|
683
685
|
normalizedStatus = 'completed';
|
|
684
686
|
} else if (statusLower.includes('ready to execute')) {
|
|
685
687
|
normalizedStatus = 'executing';
|
|
@@ -751,11 +753,12 @@ function cmdStateJson(cwd, raw) {
|
|
|
751
753
|
|
|
752
754
|
// ─── Quick Task Archival ─────────────────────────────────────────────────────
|
|
753
755
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
+
// Internal helper: archive the Quick Tasks Completed section from a single
|
|
757
|
+
// STATE.md file. Returns a status object describing the outcome; does not
|
|
758
|
+
// emit output directly so the caller can aggregate across multiple files.
|
|
759
|
+
function _archiveSingleStateFile(statePath, cwd) {
|
|
756
760
|
if (!fs.existsSync(statePath)) {
|
|
757
|
-
|
|
758
|
-
return;
|
|
761
|
+
return { archived: false, state_path: statePath, reason: 'not_found', row_count: 0 };
|
|
759
762
|
}
|
|
760
763
|
|
|
761
764
|
let content = fs.readFileSync(statePath, 'utf-8');
|
|
@@ -765,8 +768,7 @@ function cmdStateArchiveQuickTasks(cwd, raw) {
|
|
|
765
768
|
const sectionMatch = content.match(sectionPattern);
|
|
766
769
|
|
|
767
770
|
if (!sectionMatch) {
|
|
768
|
-
|
|
769
|
-
return;
|
|
771
|
+
return { archived: false, state_path: statePath, reason: 'no_section', row_count: 0 };
|
|
770
772
|
}
|
|
771
773
|
|
|
772
774
|
const sectionBody = sectionMatch[2];
|
|
@@ -800,8 +802,7 @@ function cmdStateArchiveQuickTasks(cwd, raw) {
|
|
|
800
802
|
}
|
|
801
803
|
|
|
802
804
|
if (dataRows.length <= 20) {
|
|
803
|
-
|
|
804
|
-
return;
|
|
805
|
+
return { archived: false, state_path: statePath, reason: 'under_threshold', row_count: dataRows.length };
|
|
805
806
|
}
|
|
806
807
|
|
|
807
808
|
// Archive down to 15 rows (buffer below the 20 threshold)
|
|
@@ -818,7 +819,8 @@ function cmdStateArchiveQuickTasks(cwd, raw) {
|
|
|
818
819
|
content = content.replace(sectionPattern, (_match, header) => `${header}${rebuiltSection}`);
|
|
819
820
|
writeStateMd(statePath, content, cwd);
|
|
820
821
|
|
|
821
|
-
// Build HISTORY.md path
|
|
822
|
+
// Build HISTORY.md path — naturally lands beside the STATE.md being archived
|
|
823
|
+
// (product → planning-root quick/HISTORY.md; project → projects/<p>/quick/HISTORY.md).
|
|
822
824
|
const historyPath = path.join(path.dirname(statePath), 'quick', 'HISTORY.md');
|
|
823
825
|
|
|
824
826
|
// Ensure quick/ directory exists
|
|
@@ -869,7 +871,94 @@ Archived quick/fast task records from STATE.md.
|
|
|
869
871
|
|
|
870
872
|
fs.writeFileSync(historyPath, historyContent, 'utf-8');
|
|
871
873
|
|
|
872
|
-
|
|
874
|
+
return { archived: true, state_path: statePath, count: archiveCount, remaining: 15, history_path: historyPath };
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Dual-scan archival: quick tasks can land in either the product-level
|
|
878
|
+
// STATE.md (planning root, used by `/dgs:quick --main` and product-mode
|
|
879
|
+
// quicks) or the project-level STATE.md (milestone-context quicks). Both
|
|
880
|
+
// files can accumulate tracking rows independently, so archival has to
|
|
881
|
+
// scan both and archive each over-threshold section into its own
|
|
882
|
+
// sibling quick/HISTORY.md file.
|
|
883
|
+
function cmdStateArchiveQuickTasks(cwd, raw) {
|
|
884
|
+
const productStatePath = path.join(getPlanningRoot(cwd), 'STATE.md');
|
|
885
|
+
const projectStatePath = resolveStatePath(cwd);
|
|
886
|
+
|
|
887
|
+
// Deduplicate — root layout without a project subdir resolves both to the
|
|
888
|
+
// same file, so only scan once to avoid double-processing.
|
|
889
|
+
const candidates = [productStatePath];
|
|
890
|
+
if (path.resolve(projectStatePath) !== path.resolve(productStatePath)) {
|
|
891
|
+
candidates.push(projectStatePath);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const results = candidates.map((p) => _archiveSingleStateFile(p, cwd));
|
|
895
|
+
const anyArchived = results.some((r) => r.archived);
|
|
896
|
+
|
|
897
|
+
output({ archived: anyArchived, results, scanned: candidates }, raw);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// ─── Milestone Completion ────────────────────────────────────────────────────
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Mark the current milestone as complete in STATE.md.
|
|
904
|
+
* Updates frontmatter status to "complete", progress.percent to 100,
|
|
905
|
+
* last_updated to current ISO timestamp, and adds completed_date.
|
|
906
|
+
*
|
|
907
|
+
* Called by the complete-milestone workflow after all code repos have
|
|
908
|
+
* successfully merged.
|
|
909
|
+
*
|
|
910
|
+
* @param {string} cwd - Planning root directory
|
|
911
|
+
* @returns {{ success: boolean, milestone: string, completed_date: string }}
|
|
912
|
+
*/
|
|
913
|
+
function markMilestoneComplete(cwd) {
|
|
914
|
+
const statePath = resolveStatePath(cwd);
|
|
915
|
+
if (!fs.existsSync(statePath)) {
|
|
916
|
+
error('STATE.md not found');
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
let content = fs.readFileSync(statePath, 'utf-8');
|
|
920
|
+
const fm = extractFrontmatter(content);
|
|
921
|
+
const milestone = fm.milestone || 'unknown';
|
|
922
|
+
const today = new Date().toISOString().split('T')[0];
|
|
923
|
+
const now = new Date().toISOString();
|
|
924
|
+
|
|
925
|
+
// Update frontmatter fields — milestone complete, NOT project complete
|
|
926
|
+
// Project completion is a separate manual action via /dgs:complete-project
|
|
927
|
+
fm.status = 'milestone_shipped';
|
|
928
|
+
if (!fm.progress) fm.progress = {};
|
|
929
|
+
fm.progress.percent = 100;
|
|
930
|
+
fm.last_updated = now;
|
|
931
|
+
fm.completed_date = today;
|
|
932
|
+
|
|
933
|
+
// Reconstruct frontmatter and preserve body
|
|
934
|
+
let body = content.replace(/^---\n[\s\S]*?\n---\n*/, '');
|
|
935
|
+
|
|
936
|
+
// Update markdown body to reflect completion
|
|
937
|
+
const milestoneName = fm.milestone_name || fm.milestone || 'unknown';
|
|
938
|
+
const totalPhases = (fm.progress && fm.progress.total_phases) || '?';
|
|
939
|
+
const totalPlans = (fm.progress && fm.progress.total_plans) || '?';
|
|
940
|
+
const lastPhase = (fm.progress && fm.progress.completed_phases) || totalPhases;
|
|
941
|
+
|
|
942
|
+
// Update progress bar
|
|
943
|
+
body = body.replace(/Progress:\s*\[[^\]]*\]\s*\d+%/, 'Progress: [██████████] 100%');
|
|
944
|
+
// Update current focus
|
|
945
|
+
body = body.replace(/\*\*Current focus:\*\*\s*.+/, `**Current focus:** Milestone ${milestone} complete — shipped ${today}`);
|
|
946
|
+
// Update status line in Current Position
|
|
947
|
+
body = body.replace(/Status:\s*.+/, `Status: Milestone ${milestone} shipped ${today}`);
|
|
948
|
+
// Update last activity in Current Position
|
|
949
|
+
body = body.replace(/(Last activity:\s*).+/, `$1${today} -- Milestone ${milestone} shipped (${totalPhases} phases, ${totalPlans} plans)`);
|
|
950
|
+
|
|
951
|
+
const yamlStr = reconstructFrontmatter(fm);
|
|
952
|
+
content = `---\n${yamlStr}\n---\n\n${body}`;
|
|
953
|
+
|
|
954
|
+
fs.writeFileSync(statePath, content, 'utf-8');
|
|
955
|
+
|
|
956
|
+
return { success: true, milestone: milestone, completed_date: today };
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function cmdMarkMilestoneComplete(cwd, raw) {
|
|
960
|
+
const result = markMilestoneComplete(cwd);
|
|
961
|
+
output(result, raw);
|
|
873
962
|
}
|
|
874
963
|
|
|
875
964
|
module.exports = {
|
|
@@ -883,6 +972,7 @@ module.exports = {
|
|
|
883
972
|
cmdStateAdvancePlan,
|
|
884
973
|
cmdStateRecordMetric,
|
|
885
974
|
cmdStateUpdateProgress,
|
|
975
|
+
stateUpdateProgressInternal,
|
|
886
976
|
cmdStateAddDecision,
|
|
887
977
|
cmdStateAddBlocker,
|
|
888
978
|
cmdStateResolveBlocker,
|
|
@@ -890,4 +980,6 @@ module.exports = {
|
|
|
890
980
|
cmdStateSnapshot,
|
|
891
981
|
cmdStateJson,
|
|
892
982
|
cmdStateArchiveQuickTasks,
|
|
983
|
+
markMilestoneComplete,
|
|
984
|
+
cmdMarkMilestoneComplete,
|
|
893
985
|
};
|