gsd-antigravity-kit 2.0.1 → 2.1.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/.agent/skills/gsd/SKILL.md +26 -4
- package/.agent/skills/gsd/VERSION +1 -1
- package/.agent/skills/gsd/assets/templates/AI-SPEC.md +246 -0
- package/.agent/skills/gsd/assets/templates/DEBUG.md +7 -2
- package/.agent/skills/gsd/assets/templates/config.json +56 -48
- package/.agent/skills/gsd/assets/templates/research.md +40 -0
- package/.agent/skills/gsd/assets/templates/spec.md +307 -0
- package/.agent/skills/gsd/assets/templates/state.md +8 -0
- package/.agent/skills/gsd/bin/gsd-tools.cjs +212 -11
- package/.agent/skills/gsd/bin/help-manifest.json +8 -2
- package/.agent/skills/gsd/bin/hooks/gsd-check-update-worker.js +108 -0
- package/.agent/skills/gsd/bin/hooks/gsd-check-update.js +14 -89
- package/.agent/skills/gsd/bin/hooks/gsd-context-monitor.js +34 -5
- package/.agent/skills/gsd/bin/hooks/gsd-phase-boundary.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-prompt-guard.js +1 -1
- package/.agent/skills/gsd/bin/hooks/gsd-read-guard.js +6 -1
- package/.agent/skills/gsd/bin/hooks/gsd-session-state.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-statusline.js +150 -16
- package/.agent/skills/gsd/bin/hooks/gsd-validate-commit.sh +1 -0
- package/.agent/skills/gsd/bin/hooks/gsd-workflow-guard.js +1 -1
- package/.agent/skills/gsd/bin/lib/audit.cjs +757 -0
- package/.agent/skills/gsd/bin/lib/commands.cjs +17 -7
- package/.agent/skills/gsd/bin/lib/config.cjs +66 -20
- package/.agent/skills/gsd/bin/lib/core.cjs +212 -12
- package/.agent/skills/gsd/bin/lib/frontmatter.cjs +6 -8
- package/.agent/skills/gsd/bin/lib/graphify.cjs +494 -0
- package/.agent/skills/gsd/bin/lib/gsd2-import.cjs +511 -0
- package/.agent/skills/gsd/bin/lib/init.cjs +371 -18
- package/.agent/skills/gsd/bin/lib/intel.cjs +9 -30
- package/.agent/skills/gsd/bin/lib/milestone.cjs +18 -17
- package/.agent/skills/gsd/bin/lib/model-profiles.cjs +1 -0
- package/.agent/skills/gsd/bin/lib/phase.cjs +225 -98
- package/.agent/skills/gsd/bin/lib/profile-output.cjs +17 -5
- package/.agent/skills/gsd/bin/lib/roadmap.cjs +12 -5
- package/.agent/skills/gsd/bin/lib/state.cjs +394 -129
- package/.agent/skills/gsd/bin/lib/template.cjs +8 -4
- package/.agent/skills/gsd/bin/lib/uat.cjs +2 -1
- package/.agent/skills/gsd/bin/lib/verify.cjs +111 -42
- package/.agent/skills/gsd/migration_report.md +2 -2
- package/.agent/skills/gsd/references/agents/gsd-advisor-researcher.md +23 -0
- package/.agent/skills/gsd/references/agents/gsd-ai-researcher.md +133 -0
- package/.agent/skills/gsd/references/agents/gsd-code-fixer.md +11 -10
- package/.agent/skills/gsd/references/agents/gsd-code-reviewer.md +2 -2
- package/.agent/skills/gsd/references/agents/gsd-codebase-mapper.md +13 -2
- package/.agent/skills/gsd/references/agents/gsd-debug-session-manager.md +314 -0
- package/.agent/skills/gsd/references/agents/gsd-debugger.md +147 -76
- package/.agent/skills/gsd/references/agents/gsd-doc-verifier.md +1 -1
- package/.agent/skills/gsd/references/agents/gsd-doc-writer.md +615 -602
- package/.agent/skills/gsd/references/agents/gsd-domain-researcher.md +153 -0
- package/.agent/skills/gsd/references/agents/gsd-eval-auditor.md +175 -0
- package/.agent/skills/gsd/references/agents/gsd-eval-planner.md +154 -0
- package/.agent/skills/gsd/references/agents/gsd-executor.md +108 -38
- package/.agent/skills/gsd/references/agents/gsd-framework-selector.md +160 -0
- package/.agent/skills/gsd/references/agents/gsd-integration-checker.md +454 -443
- package/.agent/skills/gsd/references/agents/gsd-intel-updater.md +40 -20
- package/.agent/skills/gsd/references/agents/gsd-nyquist-auditor.md +187 -176
- package/.agent/skills/gsd/references/agents/gsd-pattern-mapper.md +335 -0
- package/.agent/skills/gsd/references/agents/gsd-phase-researcher.md +112 -13
- package/.agent/skills/gsd/references/agents/gsd-plan-checker.md +104 -10
- package/.agent/skills/gsd/references/agents/gsd-planner.md +125 -167
- package/.agent/skills/gsd/references/agents/gsd-project-researcher.md +25 -2
- package/.agent/skills/gsd/references/agents/gsd-research-synthesizer.md +3 -3
- package/.agent/skills/gsd/references/agents/gsd-roadmapper.md +12 -1
- package/.agent/skills/gsd/references/agents/gsd-security-auditor.md +139 -128
- package/.agent/skills/gsd/references/agents/gsd-ui-auditor.md +3 -3
- package/.agent/skills/gsd/references/agents/gsd-ui-checker.md +11 -2
- package/.agent/skills/gsd/references/agents/gsd-ui-researcher.md +27 -4
- package/.agent/skills/gsd/references/agents/gsd-verifier.md +13 -19
- package/.agent/skills/gsd/references/commands/atomic/add-todo.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/check-todos.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/cleanup.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/do.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/help.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/join-discord.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/note.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/session-report.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/ship.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/stats.md +2 -2
- package/.agent/skills/gsd/references/commands/atomic/thread.md +141 -41
- package/.agent/skills/gsd/references/commands/atomic/undo.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/add-backlog.md +15 -12
- package/.agent/skills/gsd/references/commands/milestone/audit-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/complete-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/milestone-summary.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/new-milestone.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/plan-milestone-gaps.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/plant-seed.md +2 -2
- package/.agent/skills/gsd/references/commands/milestone/review-backlog.md +4 -4
- package/.agent/skills/gsd/references/commands/misc/ai-integration-phase.md +38 -0
- package/.agent/skills/gsd/references/commands/misc/audit-fix.md +2 -2
- package/.agent/skills/gsd/references/commands/misc/audit-uat.md +2 -2
- package/.agent/skills/gsd/references/commands/misc/eval-review.md +34 -0
- package/.agent/skills/gsd/references/commands/misc/extract_learnings.md +24 -0
- package/.agent/skills/gsd/references/commands/misc/from-gsd2.md +49 -0
- package/.agent/skills/gsd/references/commands/misc/graphify.md +203 -0
- package/.agent/skills/gsd/references/commands/misc/inbox.md +40 -0
- package/.agent/skills/gsd/references/commands/misc/next.md +5 -3
- package/.agent/skills/gsd/references/commands/misc/progress.md +4 -3
- package/.agent/skills/gsd/references/commands/misc/sketch-wrap-up.md +33 -0
- package/.agent/skills/gsd/references/commands/misc/sketch.md +47 -0
- package/.agent/skills/gsd/references/commands/misc/spec-phase.md +64 -0
- package/.agent/skills/gsd/references/commands/misc/spike-wrap-up.md +33 -0
- package/.agent/skills/gsd/references/commands/misc/spike.md +43 -0
- package/.agent/skills/gsd/references/commands/misc/verify-work.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/add-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/add-tests.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/discuss-phase.md +5 -5
- package/.agent/skills/gsd/references/commands/phase/execute-phase.md +4 -4
- package/.agent/skills/gsd/references/commands/phase/insert-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/list-phase-assumptions.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/plan-phase.md +3 -3
- package/.agent/skills/gsd/references/commands/phase/remove-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/research-phase.md +5 -5
- package/.agent/skills/gsd/references/commands/phase/secure-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/ui-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/ui-review.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/validate-phase.md +2 -2
- package/.agent/skills/gsd/references/commands/phase/workstreams.md +9 -9
- package/.agent/skills/gsd/references/commands/project/analyze-dependencies.md +2 -2
- package/.agent/skills/gsd/references/commands/project/explore.md +2 -2
- package/.agent/skills/gsd/references/commands/project/import.md +2 -2
- package/.agent/skills/gsd/references/commands/project/intel.md +10 -10
- package/.agent/skills/gsd/references/commands/project/list-workspaces.md +2 -2
- package/.agent/skills/gsd/references/commands/project/map-codebase.md +2 -2
- package/.agent/skills/gsd/references/commands/project/new-project.md +2 -2
- package/.agent/skills/gsd/references/commands/project/new-workspace.md +2 -2
- package/.agent/skills/gsd/references/commands/project/remove-workspace.md +2 -2
- package/.agent/skills/gsd/references/commands/project/scan.md +2 -2
- package/.agent/skills/gsd/references/commands/system/autonomous.md +4 -3
- package/.agent/skills/gsd/references/commands/system/code-review-fix.md +3 -3
- package/.agent/skills/gsd/references/commands/system/code-review.md +3 -3
- package/.agent/skills/gsd/references/commands/system/debug.md +177 -100
- package/.agent/skills/gsd/references/commands/system/docs-update.md +2 -2
- package/.agent/skills/gsd/references/commands/system/fast.md +2 -2
- package/.agent/skills/gsd/references/commands/system/forensics.md +2 -2
- package/.agent/skills/gsd/references/commands/system/gsd-tools.md +153 -6
- package/.agent/skills/gsd/references/commands/system/health.md +2 -2
- package/.agent/skills/gsd/references/commands/system/manager.md +3 -3
- package/.agent/skills/gsd/references/commands/system/pause-work.md +2 -2
- package/.agent/skills/gsd/references/commands/system/pr-branch.md +2 -2
- package/.agent/skills/gsd/references/commands/system/profile-user.md +2 -2
- package/.agent/skills/gsd/references/commands/system/quick.md +127 -3
- package/.agent/skills/gsd/references/commands/system/reapply-patches.md +45 -6
- package/.agent/skills/gsd/references/commands/system/resume-work.md +2 -2
- package/.agent/skills/gsd/references/commands/system/review.md +6 -4
- package/.agent/skills/gsd/references/commands/system/set-profile.md +3 -3
- package/.agent/skills/gsd/references/commands/system/settings.md +2 -2
- package/.agent/skills/gsd/references/commands/system/update.md +2 -2
- package/.agent/skills/gsd/references/docs/ai-evals.md +156 -0
- package/.agent/skills/gsd/references/docs/ai-frameworks.md +186 -0
- package/.agent/skills/gsd/references/docs/artifact-types.md +18 -0
- package/.agent/skills/gsd/references/docs/autonomous-smart-discuss.md +277 -0
- package/.agent/skills/gsd/references/docs/checkpoints.md +30 -0
- package/.agent/skills/gsd/references/docs/common-bug-patterns.md +49 -49
- package/.agent/skills/gsd/references/docs/continuation-format.md +11 -7
- package/.agent/skills/gsd/references/docs/debugger-philosophy.md +76 -0
- package/.agent/skills/gsd/references/docs/decimal-phase-calculation.md +64 -64
- package/.agent/skills/gsd/references/docs/executor-examples.md +110 -0
- package/.agent/skills/gsd/references/docs/git-integration.md +4 -4
- package/.agent/skills/gsd/references/docs/git-planning-commit.md +40 -38
- package/.agent/skills/gsd/references/docs/ios-scaffold.md +123 -0
- package/.agent/skills/gsd/references/docs/mandatory-initial-read.md +2 -0
- package/.agent/skills/gsd/references/docs/phase-argument-parsing.md +61 -61
- package/.agent/skills/gsd/references/docs/planner-antipatterns.md +89 -0
- package/.agent/skills/gsd/references/docs/planner-revision.md +87 -87
- package/.agent/skills/gsd/references/docs/planner-source-audit.md +73 -0
- package/.agent/skills/gsd/references/docs/planning-config.md +33 -8
- package/.agent/skills/gsd/references/docs/project-skills-discovery.md +19 -0
- package/.agent/skills/gsd/references/docs/sketch-interactivity.md +41 -0
- package/.agent/skills/gsd/references/docs/sketch-theme-system.md +94 -0
- package/.agent/skills/gsd/references/docs/sketch-tooling.md +45 -0
- package/.agent/skills/gsd/references/docs/sketch-variant-patterns.md +81 -0
- package/.agent/skills/gsd/references/docs/tdd.md +67 -0
- package/.agent/skills/gsd/references/docs/universal-anti-patterns.md +5 -0
- package/.agent/skills/gsd/references/docs/workstream-flag.md +11 -11
- package/.agent/skills/gsd/references/mapping.md +1 -1
- package/.agent/skills/gsd/references/workflows/add-phase.md +112 -112
- package/.agent/skills/gsd/references/workflows/add-tests.md +6 -3
- package/.agent/skills/gsd/references/workflows/add-todo.md +5 -3
- package/.agent/skills/gsd/references/workflows/ai-integration-phase.md +284 -0
- package/.agent/skills/gsd/references/workflows/audit-fix.md +157 -157
- package/.agent/skills/gsd/references/workflows/audit-milestone.md +340 -340
- package/.agent/skills/gsd/references/workflows/audit-uat.md +109 -109
- package/.agent/skills/gsd/references/workflows/autonomous.md +20 -288
- package/.agent/skills/gsd/references/workflows/check-todos.md +4 -2
- package/.agent/skills/gsd/references/workflows/cleanup.md +3 -1
- package/.agent/skills/gsd/references/workflows/code-review-fix.md +497 -497
- package/.agent/skills/gsd/references/workflows/code-review.md +515 -515
- package/.agent/skills/gsd/references/workflows/complete-milestone.md +97 -24
- package/.agent/skills/gsd/references/workflows/diagnose-issues.md +238 -238
- package/.agent/skills/gsd/references/workflows/discovery-phase.md +2 -0
- package/.agent/skills/gsd/references/workflows/discuss-phase-assumptions.md +11 -11
- package/.agent/skills/gsd/references/workflows/discuss-phase.md +143 -19
- package/.agent/skills/gsd/references/workflows/do.md +8 -2
- package/.agent/skills/gsd/references/workflows/docs-update.md +5 -3
- package/.agent/skills/gsd/references/workflows/eval-review.md +155 -0
- package/.agent/skills/gsd/references/workflows/execute-phase.md +338 -54
- package/.agent/skills/gsd/references/workflows/execute-plan.md +80 -104
- package/.agent/skills/gsd/references/workflows/explore.md +3 -1
- package/.agent/skills/gsd/references/workflows/extract_learnings.md +232 -0
- package/.agent/skills/gsd/references/workflows/forensics.md +3 -3
- package/.agent/skills/gsd/references/workflows/health.md +2 -2
- package/.agent/skills/gsd/references/workflows/help.md +59 -1
- package/.agent/skills/gsd/references/workflows/import.md +3 -1
- package/.agent/skills/gsd/references/workflows/inbox.md +387 -384
- package/.agent/skills/gsd/references/workflows/insert-phase.md +130 -130
- package/.agent/skills/gsd/references/workflows/list-workspaces.md +56 -56
- package/.agent/skills/gsd/references/workflows/manager.md +5 -3
- package/.agent/skills/gsd/references/workflows/map-codebase.md +19 -5
- package/.agent/skills/gsd/references/workflows/milestone-summary.md +6 -6
- package/.agent/skills/gsd/references/workflows/new-milestone.md +63 -9
- package/.agent/skills/gsd/references/workflows/new-project.md +126 -22
- package/.agent/skills/gsd/references/workflows/new-workspace.md +6 -4
- package/.agent/skills/gsd/references/workflows/next.md +220 -153
- package/.agent/skills/gsd/references/workflows/note.md +2 -0
- package/.agent/skills/gsd/references/workflows/pause-work.md +11 -7
- package/.agent/skills/gsd/references/workflows/plan-milestone-gaps.md +273 -273
- package/.agent/skills/gsd/references/workflows/plan-phase.md +281 -62
- package/.agent/skills/gsd/references/workflows/plant-seed.md +4 -1
- package/.agent/skills/gsd/references/workflows/pr-branch.md +41 -13
- package/.agent/skills/gsd/references/workflows/profile-user.md +15 -13
- package/.agent/skills/gsd/references/workflows/progress.md +133 -21
- package/.agent/skills/gsd/references/workflows/quick.md +67 -27
- package/.agent/skills/gsd/references/workflows/remove-phase.md +155 -155
- package/.agent/skills/gsd/references/workflows/remove-workspace.md +4 -2
- package/.agent/skills/gsd/references/workflows/research-phase.md +3 -3
- package/.agent/skills/gsd/references/workflows/resume-project.md +3 -3
- package/.agent/skills/gsd/references/workflows/review.md +71 -8
- package/.agent/skills/gsd/references/workflows/scan.md +102 -102
- package/.agent/skills/gsd/references/workflows/secure-phase.md +7 -5
- package/.agent/skills/gsd/references/workflows/settings.md +24 -7
- package/.agent/skills/gsd/references/workflows/ship.md +71 -6
- package/.agent/skills/gsd/references/workflows/sketch-wrap-up.md +283 -0
- package/.agent/skills/gsd/references/workflows/sketch.md +263 -0
- package/.agent/skills/gsd/references/workflows/spec-phase.md +262 -0
- package/.agent/skills/gsd/references/workflows/spike-wrap-up.md +273 -0
- package/.agent/skills/gsd/references/workflows/spike.md +270 -0
- package/.agent/skills/gsd/references/workflows/stats.md +60 -60
- package/.agent/skills/gsd/references/workflows/transition.md +671 -671
- package/.agent/skills/gsd/references/workflows/ui-phase.md +33 -12
- package/.agent/skills/gsd/references/workflows/ui-review.md +6 -4
- package/.agent/skills/gsd/references/workflows/undo.md +3 -1
- package/.agent/skills/gsd/references/workflows/update.md +113 -2
- package/.agent/skills/gsd/references/workflows/validate-phase.md +7 -5
- package/.agent/skills/gsd/references/workflows/verify-phase.md +93 -10
- package/.agent/skills/gsd/references/workflows/verify-work.md +50 -10
- package/.agent/skills/gsd-converter/references/mapping.md +1 -1
- package/.agent/skills/gsd-converter/scripts/convert.py +36 -17
- package/.agent/skills/gsd-converter/scripts/regression_test.py +68 -33
- package/README.md +3 -2
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { escapeRegex, getMilestonePhaseFilter, extractOneLinerFromBody, normalizeMd, planningPaths, output, error } = require('./core.cjs');
|
|
7
|
+
const { escapeRegex, getMilestonePhaseFilter, extractOneLinerFromBody, normalizeMd, planningPaths, output, error, atomicWriteFileSync } = require('./core.cjs');
|
|
8
8
|
const { extractFrontmatter } = require('./frontmatter.cjs');
|
|
9
9
|
const { writeStateMd, stateReplaceFieldWithFallback } = require('./state.cjs');
|
|
10
10
|
|
|
@@ -41,29 +41,30 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
41
41
|
const reqEscaped = escapeRegex(reqId);
|
|
42
42
|
|
|
43
43
|
// Update checkbox: - [ ] **REQ-ID** → - [x] **REQ-ID**
|
|
44
|
+
// Use replace() directly and compare — avoids test()+replace() global regex
|
|
45
|
+
// lastIndex bug where test() advances state and replace() misses matches.
|
|
44
46
|
const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi');
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
const afterCheckbox = reqContent.replace(checkboxPattern, '$1x$2');
|
|
48
|
+
if (afterCheckbox !== reqContent) {
|
|
49
|
+
reqContent = afterCheckbox;
|
|
47
50
|
found = true;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
// Update traceability table: | REQ-ID | Phase N | Pending | → | REQ-ID | Phase N | Complete |
|
|
51
54
|
const tablePattern = new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi');
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
reqContent =
|
|
55
|
-
new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'),
|
|
56
|
-
'$1 Complete $2'
|
|
57
|
-
);
|
|
55
|
+
const afterTable = reqContent.replace(tablePattern, '$1 Complete $2');
|
|
56
|
+
if (afterTable !== reqContent) {
|
|
57
|
+
reqContent = afterTable;
|
|
58
58
|
found = true;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (found) {
|
|
62
62
|
updated.push(reqId);
|
|
63
63
|
} else {
|
|
64
|
-
// Check if already complete before declaring not_found
|
|
65
|
-
|
|
66
|
-
const
|
|
64
|
+
// Check if already complete before declaring not_found.
|
|
65
|
+
// Non-global flag is fine here — we only need to know if a match exists.
|
|
66
|
+
const doneCheckbox = new RegExp(`-\\s*\\[x\\]\\s*\\*\\*${reqEscaped}\\*\\*`, 'i');
|
|
67
|
+
const doneTable = new RegExp(`\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|\\s*Complete\\s*\\|`, 'i');
|
|
67
68
|
if (doneCheckbox.test(reqContent) || doneTable.test(reqContent)) {
|
|
68
69
|
alreadyComplete.push(reqId);
|
|
69
70
|
} else {
|
|
@@ -73,7 +74,7 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
if (updated.length > 0) {
|
|
76
|
-
|
|
77
|
+
atomicWriteFileSync(reqPath, reqContent);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
output({
|
|
@@ -177,21 +178,21 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
177
178
|
const existing = fs.readFileSync(milestonesPath, 'utf-8');
|
|
178
179
|
if (!existing.trim()) {
|
|
179
180
|
// Empty file — treat like new
|
|
180
|
-
|
|
181
|
+
atomicWriteFileSync(milestonesPath, normalizeMd(`# Milestones\n\n${milestoneEntry}`));
|
|
181
182
|
} else {
|
|
182
183
|
// Insert after the header line(s) for reverse chronological order (newest first)
|
|
183
184
|
const headerMatch = existing.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
|
|
184
185
|
if (headerMatch) {
|
|
185
186
|
const header = headerMatch[1];
|
|
186
187
|
const rest = existing.slice(header.length);
|
|
187
|
-
|
|
188
|
+
atomicWriteFileSync(milestonesPath, normalizeMd(header + milestoneEntry + rest));
|
|
188
189
|
} else {
|
|
189
190
|
// No recognizable header — prepend the entry
|
|
190
|
-
|
|
191
|
+
atomicWriteFileSync(milestonesPath, normalizeMd(milestoneEntry + existing));
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
} else {
|
|
194
|
-
|
|
195
|
+
atomicWriteFileSync(milestonesPath, normalizeMd(`# Milestones\n\n${milestoneEntry}`));
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
// Update STATE.md — use shared helpers that handle both **bold:** and plain Field: formats
|
|
@@ -19,6 +19,7 @@ const MODEL_PROFILES = {
|
|
|
19
19
|
'gsd-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
|
|
20
20
|
'gsd-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
|
|
21
21
|
'gsd-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
|
|
22
|
+
'gsd-pattern-mapper': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
|
|
22
23
|
'gsd-ui-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku', adaptive: 'sonnet' },
|
|
23
24
|
'gsd-ui-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
|
|
24
25
|
'gsd-ui-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { escapeRegex, loadConfig, normalizePhaseName, comparePhaseNum, findPhaseInternal, getArchivedPhaseDirs, generateSlugInternal, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, toPosixPath, planningDir, withPlanningLock, output, error, readSubdirectories, phaseTokenMatches } = require('./core.cjs');
|
|
7
|
+
const { escapeRegex, loadConfig, normalizePhaseName, comparePhaseNum, findPhaseInternal, getArchivedPhaseDirs, generateSlugInternal, getMilestonePhaseFilter, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, toPosixPath, planningDir, withPlanningLock, output, error, readSubdirectories, phaseTokenMatches, atomicWriteFileSync } = require('./core.cjs');
|
|
8
8
|
const { extractFrontmatter } = require('./frontmatter.cjs');
|
|
9
|
-
const { writeStateMd, stateExtractField, stateReplaceField, stateReplaceFieldWithFallback, updatePerformanceMetricsSection } = require('./state.cjs');
|
|
9
|
+
const { writeStateMd, readModifyWriteStateMd, stateExtractField, stateReplaceField, stateReplaceFieldWithFallback, updatePerformanceMetricsSection } = require('./state.cjs');
|
|
10
10
|
|
|
11
11
|
function cmdPhasesList(cwd, options, raw) {
|
|
12
12
|
const phasesDir = path.join(planningDir(cwd), 'phases');
|
|
@@ -88,50 +88,49 @@ function cmdPhaseNextDecimal(cwd, basePhase, raw) {
|
|
|
88
88
|
const phasesDir = path.join(planningDir(cwd), 'phases');
|
|
89
89
|
const normalized = normalizePhaseName(basePhase);
|
|
90
90
|
|
|
91
|
-
// Check if phases directory exists
|
|
92
|
-
if (!fs.existsSync(phasesDir)) {
|
|
93
|
-
output(
|
|
94
|
-
{
|
|
95
|
-
found: false,
|
|
96
|
-
base_phase: normalized,
|
|
97
|
-
next: `${normalized}.1`,
|
|
98
|
-
existing: [],
|
|
99
|
-
},
|
|
100
|
-
raw,
|
|
101
|
-
`${normalized}.1`
|
|
102
|
-
);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
91
|
try {
|
|
107
|
-
|
|
108
|
-
const
|
|
92
|
+
let baseExists = false;
|
|
93
|
+
const decimalSet = new Set();
|
|
109
94
|
|
|
110
|
-
//
|
|
111
|
-
|
|
95
|
+
// Scan directory names for existing decimal phases
|
|
96
|
+
if (fs.existsSync(phasesDir)) {
|
|
97
|
+
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
98
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
99
|
+
baseExists = dirs.some(d => phaseTokenMatches(d, normalized));
|
|
112
100
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
101
|
+
const dirPattern = new RegExp(`^(?:[A-Z]{1,6}-)?${escapeRegex(normalized)}\\.(\\d+)`);
|
|
102
|
+
for (const dir of dirs) {
|
|
103
|
+
const match = dir.match(dirPattern);
|
|
104
|
+
if (match) decimalSet.add(parseInt(match[1], 10));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
116
107
|
|
|
117
|
-
for
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
// Also scan ROADMAP.md for phase entries that may not have directories yet
|
|
109
|
+
const roadmapPath = path.join(planningDir(cwd), 'ROADMAP.md');
|
|
110
|
+
if (fs.existsSync(roadmapPath)) {
|
|
111
|
+
try {
|
|
112
|
+
const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
113
|
+
const phasePattern = new RegExp(
|
|
114
|
+
`#{2,4}\\s*Phase\\s+0*${escapeRegex(normalized)}\\.(\\d+)\\s*:`, 'gi'
|
|
115
|
+
);
|
|
116
|
+
let pm;
|
|
117
|
+
while ((pm = phasePattern.exec(roadmapContent)) !== null) {
|
|
118
|
+
decimalSet.add(parseInt(pm[1], 10));
|
|
121
119
|
}
|
|
120
|
+
} catch { /* ROADMAP.md read failure is non-fatal */ }
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
//
|
|
125
|
-
existingDecimals
|
|
123
|
+
// Build sorted list of existing decimals
|
|
124
|
+
const existingDecimals = Array.from(decimalSet)
|
|
125
|
+
.sort((a, b) => a - b)
|
|
126
|
+
.map(n => `${normalized}.${n}`);
|
|
126
127
|
|
|
127
128
|
// Calculate next decimal
|
|
128
129
|
let nextDecimal;
|
|
129
|
-
if (
|
|
130
|
+
if (decimalSet.size === 0) {
|
|
130
131
|
nextDecimal = `${normalized}.1`;
|
|
131
132
|
} else {
|
|
132
|
-
|
|
133
|
-
const lastNum = parseInt(lastDecimal.split('.')[1], 10);
|
|
134
|
-
nextDecimal = `${normalized}.${lastNum + 1}`;
|
|
133
|
+
nextDecimal = `${normalized}.${Math.max(...decimalSet) + 1}`;
|
|
135
134
|
}
|
|
136
135
|
|
|
137
136
|
output(
|
|
@@ -341,15 +340,34 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
|
|
|
341
340
|
if (!_newPhaseId) error('--id required when phase_naming is "custom"');
|
|
342
341
|
_dirName = `${prefix}${_newPhaseId}-${slug}`;
|
|
343
342
|
} else {
|
|
344
|
-
// Sequential mode: find highest integer phase number
|
|
343
|
+
// Sequential mode: find highest integer phase number from two sources:
|
|
344
|
+
// 1. ROADMAP.md (current milestone only)
|
|
345
|
+
// 2. .planning/phases/ on disk (orphan directories not tracked in roadmap)
|
|
346
|
+
// Skip 999.x backlog phases — they live outside the active sequence
|
|
345
347
|
const phasePattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*:/gi;
|
|
346
348
|
let maxPhase = 0;
|
|
347
349
|
let m;
|
|
348
350
|
while ((m = phasePattern.exec(content)) !== null) {
|
|
349
351
|
const num = parseInt(m[1], 10);
|
|
352
|
+
if (num >= 999) continue; // backlog phases use 999.x numbering
|
|
350
353
|
if (num > maxPhase) maxPhase = num;
|
|
351
354
|
}
|
|
352
355
|
|
|
356
|
+
// Also scan .planning/phases/ for orphan directories not tracked in ROADMAP.
|
|
357
|
+
// Directory names follow: [PREFIX-]NN-slug (e.g. 03-api or CK-05-old-feature).
|
|
358
|
+
// Strip the optional project_code prefix before extracting the leading integer.
|
|
359
|
+
const phasesOnDisk = path.join(planningDir(cwd), 'phases');
|
|
360
|
+
if (fs.existsSync(phasesOnDisk)) {
|
|
361
|
+
const dirNumPattern = /^(?:[A-Z][A-Z0-9]*-)?(\d+)-/;
|
|
362
|
+
for (const entry of fs.readdirSync(phasesOnDisk)) {
|
|
363
|
+
const match = entry.match(dirNumPattern);
|
|
364
|
+
if (!match) continue;
|
|
365
|
+
const num = parseInt(match[1], 10);
|
|
366
|
+
if (num >= 999) continue; // skip backlog orphans
|
|
367
|
+
if (num > maxPhase) maxPhase = num;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
353
371
|
_newPhaseId = maxPhase + 1;
|
|
354
372
|
const paddedNum = String(_newPhaseId).padStart(2, '0');
|
|
355
373
|
_dirName = `${prefix}${paddedNum}-${slug}`;
|
|
@@ -374,7 +392,7 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
|
|
|
374
392
|
updatedContent = rawContent + phaseEntry;
|
|
375
393
|
}
|
|
376
394
|
|
|
377
|
-
|
|
395
|
+
atomicWriteFileSync(roadmapPath, updatedContent);
|
|
378
396
|
return { newPhaseId: _newPhaseId, dirName: _dirName };
|
|
379
397
|
});
|
|
380
398
|
|
|
@@ -390,6 +408,76 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
|
|
|
390
408
|
output(result, raw, result.padded);
|
|
391
409
|
}
|
|
392
410
|
|
|
411
|
+
function cmdPhaseAddBatch(cwd, descriptions, raw) {
|
|
412
|
+
if (!Array.isArray(descriptions) || descriptions.length === 0) {
|
|
413
|
+
error('descriptions array required for phase add-batch');
|
|
414
|
+
}
|
|
415
|
+
const config = loadConfig(cwd);
|
|
416
|
+
const roadmapPath = path.join(planningDir(cwd), 'ROADMAP.md');
|
|
417
|
+
if (!fs.existsSync(roadmapPath)) { error('ROADMAP.md not found'); }
|
|
418
|
+
const projectCode = config.project_code || '';
|
|
419
|
+
const prefix = projectCode ? `${projectCode}-` : '';
|
|
420
|
+
|
|
421
|
+
const results = withPlanningLock(cwd, () => {
|
|
422
|
+
let rawContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
423
|
+
const content = extractCurrentMilestone(rawContent, cwd);
|
|
424
|
+
let maxPhase = 0;
|
|
425
|
+
if (config.phase_naming !== 'custom') {
|
|
426
|
+
const phasePattern = /#{2,4}\s*Phase\s+(\d+)[A-Z]?(?:\.\d+)*:/gi;
|
|
427
|
+
let m;
|
|
428
|
+
while ((m = phasePattern.exec(content)) !== null) {
|
|
429
|
+
const num = parseInt(m[1], 10);
|
|
430
|
+
if (num >= 999) continue;
|
|
431
|
+
if (num > maxPhase) maxPhase = num;
|
|
432
|
+
}
|
|
433
|
+
const phasesOnDisk = path.join(planningDir(cwd), 'phases');
|
|
434
|
+
if (fs.existsSync(phasesOnDisk)) {
|
|
435
|
+
const dirNumPattern = /^(?:[A-Z][A-Z0-9]*-)?(\d+)-/;
|
|
436
|
+
for (const entry of fs.readdirSync(phasesOnDisk)) {
|
|
437
|
+
const match = entry.match(dirNumPattern);
|
|
438
|
+
if (!match) continue;
|
|
439
|
+
const num = parseInt(match[1], 10);
|
|
440
|
+
if (num >= 999) continue;
|
|
441
|
+
if (num > maxPhase) maxPhase = num;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const added = [];
|
|
446
|
+
for (const description of descriptions) {
|
|
447
|
+
const slug = generateSlugInternal(description);
|
|
448
|
+
let newPhaseId, dirName;
|
|
449
|
+
if (config.phase_naming === 'custom') {
|
|
450
|
+
newPhaseId = slug.toUpperCase().replace(/-/g, '-');
|
|
451
|
+
dirName = `${prefix}${newPhaseId}-${slug}`;
|
|
452
|
+
} else {
|
|
453
|
+
maxPhase += 1;
|
|
454
|
+
newPhaseId = maxPhase;
|
|
455
|
+
dirName = `${prefix}${String(newPhaseId).padStart(2, '0')}-${slug}`;
|
|
456
|
+
}
|
|
457
|
+
const dirPath = path.join(planningDir(cwd), 'phases', dirName);
|
|
458
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
459
|
+
fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
|
|
460
|
+
const dependsOn = config.phase_naming === 'custom' ? '' : `\n**Depends on:** Phase ${typeof newPhaseId === 'number' ? newPhaseId - 1 : 'TBD'}`;
|
|
461
|
+
const phaseEntry = `\n### Phase ${newPhaseId}: ${description}\n\n**Goal:** [To be planned]\n**Requirements**: TBD${dependsOn}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd-plan-phase ${newPhaseId} to break down)\n`;
|
|
462
|
+
const lastSeparator = rawContent.lastIndexOf('\n---');
|
|
463
|
+
rawContent = lastSeparator > 0
|
|
464
|
+
? rawContent.slice(0, lastSeparator) + phaseEntry + rawContent.slice(lastSeparator)
|
|
465
|
+
: rawContent + phaseEntry;
|
|
466
|
+
added.push({
|
|
467
|
+
phase_number: typeof newPhaseId === 'number' ? newPhaseId : String(newPhaseId),
|
|
468
|
+
padded: typeof newPhaseId === 'number' ? String(newPhaseId).padStart(2, '0') : String(newPhaseId),
|
|
469
|
+
name: description,
|
|
470
|
+
slug,
|
|
471
|
+
directory: toPosixPath(path.join(path.relative(cwd, planningDir(cwd)), 'phases', dirName)),
|
|
472
|
+
naming_mode: config.phase_naming,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
atomicWriteFileSync(roadmapPath, rawContent);
|
|
476
|
+
return added;
|
|
477
|
+
});
|
|
478
|
+
output({ phases: results, count: results.length }, raw);
|
|
479
|
+
}
|
|
480
|
+
|
|
393
481
|
function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
394
482
|
if (!afterPhase || !description) {
|
|
395
483
|
error('after-phase and description required for phase insert');
|
|
@@ -416,22 +504,31 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
|
416
504
|
error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
417
505
|
}
|
|
418
506
|
|
|
419
|
-
// Calculate next decimal
|
|
507
|
+
// Calculate next decimal by scanning both directories AND ROADMAP.md entries
|
|
420
508
|
const phasesDir = path.join(planningDir(cwd), 'phases');
|
|
421
509
|
const normalizedBase = normalizePhaseName(afterPhase);
|
|
422
|
-
|
|
510
|
+
const decimalSet = new Set();
|
|
423
511
|
|
|
424
512
|
try {
|
|
425
513
|
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
426
514
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
427
|
-
const decimalPattern = new RegExp(`^(?:[A-Z]{1,6}-)?${normalizedBase}\\.(\\d+)`);
|
|
515
|
+
const decimalPattern = new RegExp(`^(?:[A-Z]{1,6}-)?${escapeRegex(normalizedBase)}\\.(\\d+)`);
|
|
428
516
|
for (const dir of dirs) {
|
|
429
517
|
const dm = dir.match(decimalPattern);
|
|
430
|
-
if (dm)
|
|
518
|
+
if (dm) decimalSet.add(parseInt(dm[1], 10));
|
|
431
519
|
}
|
|
432
520
|
} catch { /* intentionally empty */ }
|
|
433
521
|
|
|
434
|
-
|
|
522
|
+
// Also scan ROADMAP.md content (already loaded) for decimal entries
|
|
523
|
+
const rmPhasePattern = new RegExp(
|
|
524
|
+
`#{2,4}\\s*Phase\\s+0*${escapeRegex(normalizedBase)}\\.(\\d+)\\s*:`, 'gi'
|
|
525
|
+
);
|
|
526
|
+
let rmMatch;
|
|
527
|
+
while ((rmMatch = rmPhasePattern.exec(rawContent)) !== null) {
|
|
528
|
+
decimalSet.add(parseInt(rmMatch[1], 10));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const nextDecimal = decimalSet.size === 0 ? 1 : Math.max(...decimalSet) + 1;
|
|
435
532
|
const _decimalPhase = `${normalizedBase}.${nextDecimal}`;
|
|
436
533
|
// Optional project code prefix
|
|
437
534
|
const insertConfig = loadConfig(cwd);
|
|
@@ -466,7 +563,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
|
466
563
|
}
|
|
467
564
|
|
|
468
565
|
const updatedContent = rawContent.slice(0, insertIdx) + phaseEntry + rawContent.slice(insertIdx);
|
|
469
|
-
|
|
566
|
+
atomicWriteFileSync(roadmapPath, updatedContent);
|
|
470
567
|
return { decimalPhase: _decimalPhase, dirName: _dirName };
|
|
471
568
|
});
|
|
472
569
|
|
|
@@ -488,10 +585,12 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
|
488
585
|
*/
|
|
489
586
|
function renameDecimalPhases(phasesDir, baseInt, removedDecimal) {
|
|
490
587
|
const renamedDirs = [], renamedFiles = [];
|
|
491
|
-
|
|
588
|
+
// Capture the zero-padded prefix (e.g. "06" from "06.3-slug") so the renamed
|
|
589
|
+
// directory preserves the original padding format.
|
|
590
|
+
const decPattern = new RegExp(`^(0*${baseInt})\\.(\\d+)-(.+)$`);
|
|
492
591
|
const dirs = readSubdirectories(phasesDir, true);
|
|
493
592
|
const toRename = dirs
|
|
494
|
-
.map(dir => { const m = dir.match(decPattern); return m ? { dir, oldDecimal: parseInt(m[
|
|
593
|
+
.map(dir => { const m = dir.match(decPattern); return m ? { dir, prefix: m[1], oldDecimal: parseInt(m[2], 10), slug: m[3] } : null; })
|
|
495
594
|
.filter(item => item && item.oldDecimal > removedDecimal)
|
|
496
595
|
.sort((a, b) => b.oldDecimal - a.oldDecimal); // descending to avoid conflicts
|
|
497
596
|
|
|
@@ -499,7 +598,7 @@ function renameDecimalPhases(phasesDir, baseInt, removedDecimal) {
|
|
|
499
598
|
const newDecimal = item.oldDecimal - 1;
|
|
500
599
|
const oldPhaseId = `${baseInt}.${item.oldDecimal}`;
|
|
501
600
|
const newPhaseId = `${baseInt}.${newDecimal}`;
|
|
502
|
-
const newDirName = `${
|
|
601
|
+
const newDirName = `${item.prefix}.${newDecimal}-${item.slug}`;
|
|
503
602
|
fs.renameSync(path.join(phasesDir, item.dir), path.join(phasesDir, newDirName));
|
|
504
603
|
renamedDirs.push({ from: item.dir, to: newDirName });
|
|
505
604
|
for (const f of fs.readdirSync(path.join(phasesDir, newDirName))) {
|
|
@@ -580,7 +679,7 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
|
|
|
580
679
|
}
|
|
581
680
|
}
|
|
582
681
|
|
|
583
|
-
|
|
682
|
+
atomicWriteFileSync(roadmapPath, content);
|
|
584
683
|
});
|
|
585
684
|
}
|
|
586
685
|
|
|
@@ -615,7 +714,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
615
714
|
let renamedDirs = [], renamedFiles = [];
|
|
616
715
|
try {
|
|
617
716
|
const renamed = isDecimal
|
|
618
|
-
? renameDecimalPhases(phasesDir, normalized.split('.')[0], parseInt(normalized.split('.')[1], 10))
|
|
717
|
+
? renameDecimalPhases(phasesDir, parseInt(normalized.split('.')[0], 10), parseInt(normalized.split('.')[1], 10))
|
|
619
718
|
: renameIntegerPhases(phasesDir, parseInt(normalized, 10));
|
|
620
719
|
renamedDirs = renamed.renamedDirs;
|
|
621
720
|
renamedFiles = renamed.renamedFiles;
|
|
@@ -624,19 +723,20 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
624
723
|
// Update ROADMAP.md
|
|
625
724
|
updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, parseInt(normalized, 10), cwd);
|
|
626
725
|
|
|
627
|
-
// Update STATE.md phase count
|
|
726
|
+
// Update STATE.md phase count atomically (#P4.4)
|
|
628
727
|
const statePath = path.join(planningDir(cwd), 'STATE.md');
|
|
629
728
|
if (fs.existsSync(statePath)) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
729
|
+
readModifyWriteStateMd(statePath, (stateContent) => {
|
|
730
|
+
const totalRaw = stateExtractField(stateContent, 'Total Phases');
|
|
731
|
+
if (totalRaw) {
|
|
633
732
|
stateContent = stateReplaceField(stateContent, 'Total Phases', String(parseInt(totalRaw, 10) - 1)) || stateContent;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
733
|
+
}
|
|
734
|
+
const ofMatch = stateContent.match(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i);
|
|
735
|
+
if (ofMatch) {
|
|
637
736
|
stateContent = stateContent.replace(/(\bof\s+)(\d+)(\s*(?:\(|phases?))/i, `$1${parseInt(ofMatch[2], 10) - 1}$3`);
|
|
638
|
-
|
|
639
|
-
|
|
737
|
+
}
|
|
738
|
+
return stateContent;
|
|
739
|
+
}, cwd);
|
|
640
740
|
}
|
|
641
741
|
|
|
642
742
|
output({
|
|
@@ -701,7 +801,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
701
801
|
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${escapeRegex(phaseNum)}[:\\s][^\\n]*)`,
|
|
702
802
|
'i'
|
|
703
803
|
);
|
|
704
|
-
roadmapContent =
|
|
804
|
+
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
705
805
|
|
|
706
806
|
// Progress table: update Status to Complete, add date (handles 4 or 5 column tables)
|
|
707
807
|
const phaseEscaped = escapeRegex(phaseNum);
|
|
@@ -725,13 +825,20 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
725
825
|
return '|' + cells.join('|') + '|';
|
|
726
826
|
});
|
|
727
827
|
|
|
728
|
-
// Update plan count in phase section
|
|
828
|
+
// Update plan count in phase section.
|
|
829
|
+
// Use direct .replace() rather than replaceInCurrentMilestone() so this
|
|
830
|
+
// works when the current milestone section is itself inside a <details>
|
|
831
|
+
// block (the standard /gsd-new-project layout). replaceInCurrentMilestone
|
|
832
|
+
// scopes to content after the last </details>, which misses content inside
|
|
833
|
+
// the current milestone's own <details> wrapper (#2005).
|
|
834
|
+
// The phase-scoped heading pattern is specific enough to avoid matching
|
|
835
|
+
// archived phases (which belong to different milestones).
|
|
729
836
|
const planCountPattern = new RegExp(
|
|
730
837
|
`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
|
|
731
838
|
'i'
|
|
732
839
|
);
|
|
733
|
-
roadmapContent =
|
|
734
|
-
|
|
840
|
+
roadmapContent = roadmapContent.replace(
|
|
841
|
+
planCountPattern,
|
|
735
842
|
`$1${summaryCount}/${planCount} plans complete`
|
|
736
843
|
);
|
|
737
844
|
|
|
@@ -748,7 +855,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
748
855
|
roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
|
|
749
856
|
}
|
|
750
857
|
|
|
751
|
-
|
|
858
|
+
atomicWriteFileSync(roadmapPath, roadmapContent);
|
|
752
859
|
|
|
753
860
|
// Update REQUIREMENTS.md traceability for this phase's requirements
|
|
754
861
|
const reqPath = path.join(planningDir(cwd), 'REQUIREMENTS.md');
|
|
@@ -781,7 +888,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
781
888
|
);
|
|
782
889
|
}
|
|
783
890
|
|
|
784
|
-
|
|
891
|
+
atomicWriteFileSync(reqPath, reqContent);
|
|
785
892
|
requirementsUpdated = true;
|
|
786
893
|
}
|
|
787
894
|
}
|
|
@@ -803,9 +910,11 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
803
910
|
.sort((a, b) => comparePhaseNum(a, b));
|
|
804
911
|
|
|
805
912
|
// Find the next phase directory after current
|
|
913
|
+
// Skip backlog phases (999.x) — they are parked ideas, not sequential work (#2129)
|
|
806
914
|
for (const dir of dirs) {
|
|
807
915
|
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)-?(.*)/i);
|
|
808
916
|
if (dm) {
|
|
917
|
+
if (/^999(?:\.|$)/.test(dm[1])) continue;
|
|
809
918
|
if (comparePhaseNum(dm[1], phaseNum) > 0) {
|
|
810
919
|
nextPhaseNum = dm[1];
|
|
811
920
|
nextPhaseName = dm[2] || null;
|
|
@@ -834,72 +943,88 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
834
943
|
} catch { /* intentionally empty */ }
|
|
835
944
|
}
|
|
836
945
|
|
|
837
|
-
// Update STATE.md —
|
|
946
|
+
// Update STATE.md atomically — hold lock across read-modify-write (#P4.4).
|
|
947
|
+
// Previously read outside the lock; a crash between the ROADMAP update
|
|
948
|
+
// (locked above) and this write left ROADMAP/STATE inconsistent.
|
|
838
949
|
if (fs.existsSync(statePath)) {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const existingPhaseField = stateExtractField(stateContent, 'Current Phase')
|
|
950
|
+
readModifyWriteStateMd(statePath, (stateContent) => {
|
|
951
|
+
// Update Current Phase — preserve "X of Y (Name)" compound format
|
|
952
|
+
const phaseValue = nextPhaseNum || phaseNum;
|
|
953
|
+
const existingPhaseField = stateExtractField(stateContent, 'Current Phase')
|
|
844
954
|
|| stateExtractField(stateContent, 'Phase');
|
|
845
|
-
|
|
846
|
-
|
|
955
|
+
let newPhaseValue = String(phaseValue);
|
|
956
|
+
if (existingPhaseField) {
|
|
847
957
|
const totalMatch = existingPhaseField.match(/of\s+(\d+)/);
|
|
848
958
|
const nameMatch = existingPhaseField.match(/\(([^)]+)\)/);
|
|
849
959
|
if (totalMatch) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
960
|
+
const total = totalMatch[1];
|
|
961
|
+
const nameStr = nextPhaseName ? ` (${nextPhaseName.replace(/-/g, ' ')})` : (nameMatch ? ` (${nameMatch[1]})` : '');
|
|
962
|
+
newPhaseValue = `${phaseValue} of ${total}${nameStr}`;
|
|
853
963
|
}
|
|
854
|
-
|
|
855
|
-
|
|
964
|
+
}
|
|
965
|
+
stateContent = stateReplaceFieldWithFallback(stateContent, 'Current Phase', 'Phase', newPhaseValue);
|
|
856
966
|
|
|
857
|
-
|
|
858
|
-
|
|
967
|
+
// Update Current Phase Name
|
|
968
|
+
if (nextPhaseName) {
|
|
859
969
|
stateContent = stateReplaceFieldWithFallback(stateContent, 'Current Phase Name', null, nextPhaseName.replace(/-/g, ' '));
|
|
860
|
-
|
|
970
|
+
}
|
|
861
971
|
|
|
862
|
-
|
|
863
|
-
|
|
972
|
+
// Update Status
|
|
973
|
+
stateContent = stateReplaceFieldWithFallback(stateContent, 'Status', null,
|
|
864
974
|
isLastPhase ? 'Milestone complete' : 'Ready to plan');
|
|
865
975
|
|
|
866
|
-
|
|
867
|
-
|
|
976
|
+
// Update Current Plan
|
|
977
|
+
stateContent = stateReplaceFieldWithFallback(stateContent, 'Current Plan', 'Plan', 'Not started');
|
|
868
978
|
|
|
869
|
-
|
|
870
|
-
|
|
979
|
+
// Update Last Activity
|
|
980
|
+
stateContent = stateReplaceFieldWithFallback(stateContent, 'Last Activity', 'Last activity', today);
|
|
871
981
|
|
|
872
|
-
|
|
873
|
-
|
|
982
|
+
// Update Last Activity Description
|
|
983
|
+
stateContent = stateReplaceFieldWithFallback(stateContent, 'Last Activity Description', null,
|
|
874
984
|
`Phase ${phaseNum} complete${nextPhaseNum ? `, transitioned to Phase ${nextPhaseNum}` : ''}`);
|
|
875
985
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
986
|
+
// Increment Completed Phases counter (#956)
|
|
987
|
+
const completedRaw = stateExtractField(stateContent, 'Completed Phases');
|
|
988
|
+
if (completedRaw) {
|
|
879
989
|
const newCompleted = parseInt(completedRaw, 10) + 1;
|
|
880
990
|
stateContent = stateReplaceField(stateContent, 'Completed Phases', String(newCompleted)) || stateContent;
|
|
881
991
|
|
|
882
992
|
// Recalculate percent based on completed / total (#956)
|
|
883
993
|
const totalRaw = stateExtractField(stateContent, 'Total Phases');
|
|
884
994
|
if (totalRaw) {
|
|
885
|
-
|
|
886
|
-
|
|
995
|
+
const totalPhases = parseInt(totalRaw, 10);
|
|
996
|
+
if (totalPhases > 0) {
|
|
887
997
|
const newPercent = Math.round((newCompleted / totalPhases) * 100);
|
|
888
998
|
stateContent = stateReplaceField(stateContent, 'Progress', `${newPercent}%`) || stateContent;
|
|
889
|
-
// Also update percent field if it exists separately
|
|
890
999
|
stateContent = stateContent.replace(
|
|
891
|
-
|
|
892
|
-
|
|
1000
|
+
/(percent:\s*)\d+/,
|
|
1001
|
+
`$1${newPercent}`
|
|
893
1002
|
);
|
|
1003
|
+
}
|
|
894
1004
|
}
|
|
895
1005
|
}
|
|
896
|
-
}
|
|
897
1006
|
|
|
898
|
-
|
|
899
|
-
|
|
1007
|
+
// Gate 4: Update Performance Metrics section (#1627)
|
|
1008
|
+
stateContent = updatePerformanceMetricsSection(stateContent, cwd, phaseNum, planCount, summaryCount);
|
|
900
1009
|
|
|
901
|
-
|
|
1010
|
+
return stateContent;
|
|
1011
|
+
}, cwd);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Auto-prune STATE.md on phase boundary when configured (#2087)
|
|
1015
|
+
let autoPruned = false;
|
|
1016
|
+
try {
|
|
1017
|
+
const configPath = path.join(planningDir(cwd), 'config.json');
|
|
1018
|
+
if (fs.existsSync(configPath)) {
|
|
1019
|
+
const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1020
|
+
const autoPruneEnabled = rawConfig.workflow && rawConfig.workflow.auto_prune_state === true;
|
|
1021
|
+
if (autoPruneEnabled && fs.existsSync(statePath)) {
|
|
1022
|
+
const { cmdStatePrune } = require('./state.cjs');
|
|
1023
|
+
cmdStatePrune(cwd, { keepRecent: '3', dryRun: false, silent: true }, true);
|
|
1024
|
+
autoPruned = true;
|
|
1025
|
+
}
|
|
902
1026
|
}
|
|
1027
|
+
} catch { /* intentionally empty — auto-prune is best-effort */ }
|
|
903
1028
|
|
|
904
1029
|
const result = {
|
|
905
1030
|
completed_phase: phaseNum,
|
|
@@ -912,6 +1037,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
912
1037
|
roadmap_updated: fs.existsSync(roadmapPath),
|
|
913
1038
|
state_updated: fs.existsSync(statePath),
|
|
914
1039
|
requirements_updated: requirementsUpdated,
|
|
1040
|
+
auto_pruned: autoPruned,
|
|
915
1041
|
warnings,
|
|
916
1042
|
has_warnings: warnings.length > 0,
|
|
917
1043
|
};
|
|
@@ -925,6 +1051,7 @@ module.exports = {
|
|
|
925
1051
|
cmdFindPhase,
|
|
926
1052
|
cmdPhasePlanIndex,
|
|
927
1053
|
cmdPhaseAdd,
|
|
1054
|
+
cmdPhaseAddBatch,
|
|
928
1055
|
cmdPhaseInsert,
|
|
929
1056
|
cmdPhaseRemove,
|
|
930
1057
|
cmdPhaseComplete,
|