gsd-opencode 1.22.1 → 1.33.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/agents/gsd-advisor-researcher.md +112 -0
- package/agents/gsd-assumptions-analyzer.md +110 -0
- package/agents/gsd-codebase-mapper.md +0 -2
- package/agents/gsd-debugger.md +117 -2
- package/agents/gsd-doc-verifier.md +207 -0
- package/agents/gsd-doc-writer.md +608 -0
- package/agents/gsd-executor.md +45 -4
- package/agents/gsd-integration-checker.md +0 -2
- package/agents/gsd-nyquist-auditor.md +0 -2
- package/agents/gsd-phase-researcher.md +191 -5
- package/agents/gsd-plan-checker.md +152 -5
- package/agents/gsd-planner.md +131 -157
- package/agents/gsd-project-researcher.md +28 -3
- package/agents/gsd-research-synthesizer.md +0 -2
- package/agents/gsd-roadmapper.md +29 -2
- package/agents/gsd-security-auditor.md +129 -0
- package/agents/gsd-ui-auditor.md +485 -0
- package/agents/gsd-ui-checker.md +305 -0
- package/agents/gsd-ui-researcher.md +368 -0
- package/agents/gsd-user-profiler.md +173 -0
- package/agents/gsd-verifier.md +207 -22
- package/commands/gsd/gsd-add-backlog.md +76 -0
- package/commands/gsd/gsd-analyze-dependencies.md +34 -0
- package/commands/gsd/gsd-audit-uat.md +24 -0
- package/commands/gsd/gsd-autonomous.md +45 -0
- package/commands/gsd/gsd-cleanup.md +5 -0
- package/commands/gsd/gsd-debug.md +29 -21
- package/commands/gsd/gsd-discuss-phase.md +15 -36
- package/commands/gsd/gsd-do.md +30 -0
- package/commands/gsd/gsd-docs-update.md +48 -0
- package/commands/gsd/gsd-execute-phase.md +24 -2
- package/commands/gsd/gsd-fast.md +30 -0
- package/commands/gsd/gsd-forensics.md +56 -0
- package/commands/gsd/gsd-help.md +2 -0
- package/commands/gsd/gsd-join-discord.md +2 -1
- package/commands/gsd/gsd-list-workspaces.md +19 -0
- package/commands/gsd/gsd-manager.md +40 -0
- package/commands/gsd/gsd-milestone-summary.md +51 -0
- package/commands/gsd/gsd-new-project.md +4 -0
- package/commands/gsd/gsd-new-workspace.md +44 -0
- package/commands/gsd/gsd-next.md +24 -0
- package/commands/gsd/gsd-note.md +34 -0
- package/commands/gsd/gsd-plan-phase.md +8 -1
- package/commands/gsd/gsd-plant-seed.md +28 -0
- package/commands/gsd/gsd-pr-branch.md +25 -0
- package/commands/gsd/gsd-profile-user.md +46 -0
- package/commands/gsd/gsd-quick.md +7 -3
- package/commands/gsd/gsd-reapply-patches.md +178 -45
- package/commands/gsd/gsd-remove-workspace.md +26 -0
- package/commands/gsd/gsd-research-phase.md +7 -12
- package/commands/gsd/gsd-review-backlog.md +62 -0
- package/commands/gsd/gsd-review.md +38 -0
- package/commands/gsd/gsd-secure-phase.md +35 -0
- package/commands/gsd/gsd-session-report.md +19 -0
- package/commands/gsd/gsd-set-profile.md +24 -23
- package/commands/gsd/gsd-ship.md +23 -0
- package/commands/gsd/gsd-stats.md +18 -0
- package/commands/gsd/gsd-thread.md +127 -0
- package/commands/gsd/gsd-ui-phase.md +34 -0
- package/commands/gsd/gsd-ui-review.md +32 -0
- package/commands/gsd/gsd-workstreams.md +71 -0
- package/get-shit-done/bin/gsd-tools.cjs +450 -90
- package/get-shit-done/bin/lib/commands.cjs +489 -24
- package/get-shit-done/bin/lib/config.cjs +329 -48
- package/get-shit-done/bin/lib/core.cjs +1143 -102
- package/get-shit-done/bin/lib/docs.cjs +267 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +125 -43
- package/get-shit-done/bin/lib/init.cjs +918 -106
- package/get-shit-done/bin/lib/milestone.cjs +65 -33
- package/get-shit-done/bin/lib/model-profiles.cjs +70 -0
- package/get-shit-done/bin/lib/phase.cjs +434 -404
- package/get-shit-done/bin/lib/profile-output.cjs +1048 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/roadmap.cjs +156 -101
- package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
- package/get-shit-done/bin/lib/security.cjs +384 -0
- package/get-shit-done/bin/lib/state.cjs +711 -79
- package/get-shit-done/bin/lib/template.cjs +2 -2
- package/get-shit-done/bin/lib/uat.cjs +282 -0
- package/get-shit-done/bin/lib/verify.cjs +254 -42
- package/get-shit-done/bin/lib/workstream.cjs +495 -0
- package/get-shit-done/references/agent-contracts.md +79 -0
- package/get-shit-done/references/artifact-types.md +113 -0
- package/get-shit-done/references/checkpoints.md +12 -10
- package/get-shit-done/references/context-budget.md +49 -0
- package/get-shit-done/references/continuation-format.md +15 -15
- package/get-shit-done/references/decimal-phase-calculation.md +2 -3
- package/get-shit-done/references/domain-probes.md +125 -0
- package/get-shit-done/references/gate-prompts.md +100 -0
- package/get-shit-done/references/git-integration.md +47 -0
- package/get-shit-done/references/model-profile-resolution.md +2 -0
- package/get-shit-done/references/model-profiles.md +62 -16
- package/get-shit-done/references/phase-argument-parsing.md +2 -2
- package/get-shit-done/references/planner-gap-closure.md +62 -0
- package/get-shit-done/references/planner-reviews.md +39 -0
- package/get-shit-done/references/planner-revision.md +87 -0
- package/get-shit-done/references/planning-config.md +18 -1
- package/get-shit-done/references/revision-loop.md +97 -0
- package/get-shit-done/references/ui-brand.md +2 -2
- package/get-shit-done/references/universal-anti-patterns.md +58 -0
- package/get-shit-done/references/user-profiling.md +681 -0
- package/get-shit-done/references/workstream-flag.md +111 -0
- package/get-shit-done/templates/SECURITY.md +61 -0
- package/get-shit-done/templates/UAT.md +21 -3
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +3 -3
- package/get-shit-done/templates/claude-md.md +145 -0
- package/get-shit-done/templates/config.json +14 -3
- package/get-shit-done/templates/context.md +61 -6
- package/get-shit-done/templates/debug-subagent-prompt.md +2 -6
- package/get-shit-done/templates/dev-preferences.md +21 -0
- package/get-shit-done/templates/discussion-log.md +63 -0
- package/get-shit-done/templates/phase-prompt.md +46 -5
- package/get-shit-done/templates/planner-subagent-prompt.md +2 -10
- package/get-shit-done/templates/project.md +2 -0
- package/get-shit-done/templates/state.md +2 -2
- package/get-shit-done/templates/user-profile.md +146 -0
- package/get-shit-done/workflows/add-phase.md +4 -4
- package/get-shit-done/workflows/add-tests.md +4 -4
- package/get-shit-done/workflows/add-todo.md +4 -4
- package/get-shit-done/workflows/analyze-dependencies.md +96 -0
- package/get-shit-done/workflows/audit-milestone.md +20 -16
- package/get-shit-done/workflows/audit-uat.md +109 -0
- package/get-shit-done/workflows/autonomous.md +1036 -0
- package/get-shit-done/workflows/check-todos.md +4 -4
- package/get-shit-done/workflows/cleanup.md +4 -4
- package/get-shit-done/workflows/complete-milestone.md +22 -10
- package/get-shit-done/workflows/diagnose-issues.md +21 -7
- package/get-shit-done/workflows/discovery-phase.md +2 -2
- package/get-shit-done/workflows/discuss-phase-assumptions.md +671 -0
- package/get-shit-done/workflows/discuss-phase-power.md +291 -0
- package/get-shit-done/workflows/discuss-phase.md +558 -47
- package/get-shit-done/workflows/do.md +104 -0
- package/get-shit-done/workflows/docs-update.md +1093 -0
- package/get-shit-done/workflows/execute-phase.md +741 -58
- package/get-shit-done/workflows/execute-plan.md +77 -12
- package/get-shit-done/workflows/fast.md +105 -0
- package/get-shit-done/workflows/forensics.md +265 -0
- package/get-shit-done/workflows/health.md +28 -6
- package/get-shit-done/workflows/help.md +127 -7
- package/get-shit-done/workflows/insert-phase.md +4 -4
- package/get-shit-done/workflows/list-phase-assumptions.md +2 -2
- package/get-shit-done/workflows/list-workspaces.md +56 -0
- package/get-shit-done/workflows/manager.md +363 -0
- package/get-shit-done/workflows/map-codebase.md +83 -44
- package/get-shit-done/workflows/milestone-summary.md +223 -0
- package/get-shit-done/workflows/new-milestone.md +133 -25
- package/get-shit-done/workflows/new-project.md +216 -54
- package/get-shit-done/workflows/new-workspace.md +237 -0
- package/get-shit-done/workflows/next.md +97 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/note.md +156 -0
- package/get-shit-done/workflows/pause-work.md +132 -15
- package/get-shit-done/workflows/plan-milestone-gaps.md +6 -7
- package/get-shit-done/workflows/plan-phase.md +513 -62
- package/get-shit-done/workflows/plant-seed.md +169 -0
- package/get-shit-done/workflows/pr-branch.md +129 -0
- package/get-shit-done/workflows/profile-user.md +450 -0
- package/get-shit-done/workflows/progress.md +154 -29
- package/get-shit-done/workflows/quick.md +285 -111
- package/get-shit-done/workflows/remove-phase.md +2 -2
- package/get-shit-done/workflows/remove-workspace.md +90 -0
- package/get-shit-done/workflows/research-phase.md +13 -9
- package/get-shit-done/workflows/resume-project.md +37 -18
- package/get-shit-done/workflows/review.md +281 -0
- package/get-shit-done/workflows/secure-phase.md +154 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/set-profile.md +2 -2
- package/get-shit-done/workflows/settings.md +91 -11
- package/get-shit-done/workflows/ship.md +237 -0
- package/get-shit-done/workflows/stats.md +60 -0
- package/get-shit-done/workflows/transition.md +150 -23
- package/get-shit-done/workflows/ui-phase.md +292 -0
- package/get-shit-done/workflows/ui-review.md +183 -0
- package/get-shit-done/workflows/update.md +262 -30
- package/get-shit-done/workflows/validate-phase.md +14 -17
- package/get-shit-done/workflows/verify-phase.md +143 -11
- package/get-shit-done/workflows/verify-work.md +141 -39
- package/package.json +1 -1
- package/skills/gsd-audit-milestone/SKILL.md +29 -0
- package/skills/gsd-cleanup/SKILL.md +19 -0
- package/skills/gsd-complete-milestone/SKILL.md +131 -0
- package/skills/gsd-discuss-phase/SKILL.md +54 -0
- package/skills/gsd-execute-phase/SKILL.md +49 -0
- package/skills/gsd-plan-phase/SKILL.md +37 -0
- package/skills/gsd-ui-phase/SKILL.md +24 -0
- package/skills/gsd-ui-review/SKILL.md +24 -0
- package/skills/gsd-verify-work/SKILL.md +30 -0
|
@@ -4,10 +4,76 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { escapeRegex, normalizePhaseName, output, error, findPhaseInternal } = require('./core.cjs');
|
|
7
|
+
const { escapeRegex, normalizePhaseName, planningPaths, withPlanningLock, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, phaseTokenMatches } = require('./core.cjs');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Search for a phase header (and its section) within the given content string.
|
|
11
|
+
* Returns a result object if found (either a full match or a malformed_roadmap
|
|
12
|
+
* checklist-only match), or null if the phase is not present at all.
|
|
13
|
+
*/
|
|
14
|
+
function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
|
15
|
+
// Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
|
|
16
|
+
const phasePattern = new RegExp(
|
|
17
|
+
`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
|
|
18
|
+
'i'
|
|
19
|
+
);
|
|
20
|
+
const headerMatch = content.match(phasePattern);
|
|
21
|
+
|
|
22
|
+
if (!headerMatch) {
|
|
23
|
+
// Fallback: check if phase exists in summary list but missing detail section
|
|
24
|
+
const checklistPattern = new RegExp(
|
|
25
|
+
`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
|
|
26
|
+
'i'
|
|
27
|
+
);
|
|
28
|
+
const checklistMatch = content.match(checklistPattern);
|
|
29
|
+
|
|
30
|
+
if (checklistMatch) {
|
|
31
|
+
return {
|
|
32
|
+
found: false,
|
|
33
|
+
phase_number: phaseNum,
|
|
34
|
+
phase_name: checklistMatch[1].trim(),
|
|
35
|
+
error: 'malformed_roadmap',
|
|
36
|
+
message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const phaseName = headerMatch[1].trim();
|
|
44
|
+
const headerIndex = headerMatch.index;
|
|
45
|
+
|
|
46
|
+
// Find the end of this section (next ## or ### phase header, or end of file)
|
|
47
|
+
const restOfContent = content.slice(headerIndex);
|
|
48
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
49
|
+
const sectionEnd = nextHeaderMatch
|
|
50
|
+
? headerIndex + nextHeaderMatch.index
|
|
51
|
+
: content.length;
|
|
52
|
+
|
|
53
|
+
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
54
|
+
|
|
55
|
+
// Extract goal if present (supports both **Goal:** and **Goal**: formats)
|
|
56
|
+
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
57
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
58
|
+
|
|
59
|
+
// Extract success criteria as structured array
|
|
60
|
+
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
|
61
|
+
const success_criteria = criteriaMatch
|
|
62
|
+
? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
|
|
63
|
+
: [];
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
found: true,
|
|
67
|
+
phase_number: phaseNum,
|
|
68
|
+
phase_name: phaseName,
|
|
69
|
+
goal,
|
|
70
|
+
success_criteria,
|
|
71
|
+
section,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
8
74
|
|
|
9
75
|
function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
10
|
-
const roadmapPath =
|
|
76
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
11
77
|
|
|
12
78
|
if (!fs.existsSync(roadmapPath)) {
|
|
13
79
|
output({ found: false, error: 'ROADMAP.md not found' }, raw, '');
|
|
@@ -15,91 +81,48 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
15
81
|
}
|
|
16
82
|
|
|
17
83
|
try {
|
|
18
|
-
const
|
|
84
|
+
const rawContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
85
|
+
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
19
86
|
|
|
20
87
|
// Escape special regex chars in phase number, handle decimal
|
|
21
88
|
const escapedPhase = escapeRegex(phaseNum);
|
|
22
89
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// Fallback: check if phase exists in summary list but missing detail section
|
|
32
|
-
const checklistPattern = new RegExp(
|
|
33
|
-
`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
|
|
34
|
-
'i'
|
|
35
|
-
);
|
|
36
|
-
const checklistMatch = content.match(checklistPattern);
|
|
37
|
-
|
|
38
|
-
if (checklistMatch) {
|
|
39
|
-
// Phase exists in summary but missing detail section - malformed ROADMAP
|
|
40
|
-
output({
|
|
41
|
-
found: false,
|
|
42
|
-
phase_number: phaseNum,
|
|
43
|
-
phase_name: checklistMatch[1].trim(),
|
|
44
|
-
error: 'malformed_roadmap',
|
|
45
|
-
message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
|
|
46
|
-
}, raw, '');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
90
|
+
// Search the current milestone slice first, then fall back to full roadmap.
|
|
91
|
+
// A malformed_roadmap result (checklist-only) from the milestone should not
|
|
92
|
+
// block finding a full header match in the wider roadmap content.
|
|
93
|
+
const fullContent = stripShippedMilestones(rawContent);
|
|
94
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, escapedPhase, phaseNum);
|
|
95
|
+
const result = (milestoneResult && !milestoneResult.error)
|
|
96
|
+
? milestoneResult
|
|
97
|
+
: searchPhaseInContent(fullContent, escapedPhase, phaseNum) || milestoneResult;
|
|
49
98
|
|
|
99
|
+
if (!result) {
|
|
50
100
|
output({ found: false, phase_number: phaseNum }, raw, '');
|
|
51
101
|
return;
|
|
52
102
|
}
|
|
53
103
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const restOfContent = content.slice(headerIndex);
|
|
59
|
-
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
60
|
-
const sectionEnd = nextHeaderMatch
|
|
61
|
-
? headerIndex + nextHeaderMatch.index
|
|
62
|
-
: content.length;
|
|
63
|
-
|
|
64
|
-
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
65
|
-
|
|
66
|
-
// Extract goal if present
|
|
67
|
-
const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
|
|
68
|
-
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
69
|
-
|
|
70
|
-
// Extract success criteria as structured array
|
|
71
|
-
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
|
72
|
-
const success_criteria = criteriaMatch
|
|
73
|
-
? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
|
|
74
|
-
: [];
|
|
104
|
+
if (result.error) {
|
|
105
|
+
output(result, raw, '');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
75
108
|
|
|
76
|
-
output(
|
|
77
|
-
{
|
|
78
|
-
found: true,
|
|
79
|
-
phase_number: phaseNum,
|
|
80
|
-
phase_name: phaseName,
|
|
81
|
-
goal,
|
|
82
|
-
success_criteria,
|
|
83
|
-
section,
|
|
84
|
-
},
|
|
85
|
-
raw,
|
|
86
|
-
section
|
|
87
|
-
);
|
|
109
|
+
output(result, raw, result.section);
|
|
88
110
|
} catch (e) {
|
|
89
111
|
error('Failed to read ROADMAP.md: ' + e.message);
|
|
90
112
|
}
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
function cmdRoadmapAnalyze(cwd, raw) {
|
|
94
|
-
const roadmapPath =
|
|
116
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
95
117
|
|
|
96
118
|
if (!fs.existsSync(roadmapPath)) {
|
|
97
119
|
output({ error: 'ROADMAP.md not found', milestones: [], phases: [], current_phase: null }, raw);
|
|
98
120
|
return;
|
|
99
121
|
}
|
|
100
122
|
|
|
101
|
-
const
|
|
102
|
-
const
|
|
123
|
+
const rawContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
124
|
+
const content = extractCurrentMilestone(rawContent, cwd);
|
|
125
|
+
const phasesDir = planningPaths(cwd).phases;
|
|
103
126
|
|
|
104
127
|
// Extract all phase headings: ## Phase N: Name or ### Phase N: Name
|
|
105
128
|
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
|
@@ -117,10 +140,10 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
117
140
|
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
118
141
|
const section = content.slice(sectionStart, sectionEnd);
|
|
119
142
|
|
|
120
|
-
const goalMatch = section.match(/\*\*Goal
|
|
143
|
+
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
121
144
|
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
122
145
|
|
|
123
|
-
const dependsMatch = section.match(/\*\*Depends on
|
|
146
|
+
const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
124
147
|
const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
|
|
125
148
|
|
|
126
149
|
// Check completion on disk
|
|
@@ -134,7 +157,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
134
157
|
try {
|
|
135
158
|
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
136
159
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
137
|
-
const dirMatch = dirs.find(d =>
|
|
160
|
+
const dirMatch = dirs.find(d => phaseTokenMatches(d, normalized));
|
|
138
161
|
|
|
139
162
|
if (dirMatch) {
|
|
140
163
|
const phaseFiles = fs.readdirSync(path.join(phasesDir, dirMatch));
|
|
@@ -150,13 +173,20 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
150
173
|
else if (hasContext) diskStatus = 'discussed';
|
|
151
174
|
else diskStatus = 'empty';
|
|
152
175
|
}
|
|
153
|
-
} catch {}
|
|
176
|
+
} catch { /* intentionally empty */ }
|
|
154
177
|
|
|
155
178
|
// Check ROADMAP checkbox status
|
|
156
|
-
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${escapeRegex(phaseNum)}`, 'i');
|
|
179
|
+
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${escapeRegex(phaseNum)}[:\\s]`, 'i');
|
|
157
180
|
const checkboxMatch = content.match(checkboxPattern);
|
|
158
181
|
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
|
|
159
182
|
|
|
183
|
+
// If roadmap marks phase complete, trust that over disk file structure.
|
|
184
|
+
// Phases completed before GSD tracking (or via external tools) may lack
|
|
185
|
+
// the standard PLAN/SUMMARY pairs but are still done.
|
|
186
|
+
if (roadmapComplete && diskStatus !== 'complete') {
|
|
187
|
+
diskStatus = 'complete';
|
|
188
|
+
}
|
|
189
|
+
|
|
160
190
|
phases.push({
|
|
161
191
|
number: phaseNum,
|
|
162
192
|
name: phaseName,
|
|
@@ -173,7 +203,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
173
203
|
|
|
174
204
|
// Extract milestone info
|
|
175
205
|
const milestones = [];
|
|
176
|
-
const milestonePattern = /##\s*(.*v(\d
|
|
206
|
+
const milestonePattern = /##\s*(.*v(\d+(?:\.\d+)+)[^(\n]*)/gi;
|
|
177
207
|
let mMatch;
|
|
178
208
|
while ((mMatch = milestonePattern.exec(content)) !== null) {
|
|
179
209
|
milestones.push({
|
|
@@ -222,7 +252,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
222
252
|
error('phase number required for roadmap update-plan-progress');
|
|
223
253
|
}
|
|
224
254
|
|
|
225
|
-
const roadmapPath =
|
|
255
|
+
const roadmapPath = planningPaths(cwd).roadmap;
|
|
226
256
|
|
|
227
257
|
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
228
258
|
if (!phaseInfo) {
|
|
@@ -246,41 +276,66 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
246
276
|
return;
|
|
247
277
|
}
|
|
248
278
|
|
|
249
|
-
|
|
250
|
-
|
|
279
|
+
// Wrap entire read-modify-write in lock to prevent concurrent corruption
|
|
280
|
+
withPlanningLock(cwd, () => {
|
|
281
|
+
let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
282
|
+
const phaseEscaped = escapeRegex(phaseNum);
|
|
251
283
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
284
|
+
// Progress table row: update Plans/Status/Date columns (handles 4 or 5 column tables)
|
|
285
|
+
const tableRowPattern = new RegExp(
|
|
286
|
+
`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
|
|
287
|
+
'im'
|
|
288
|
+
);
|
|
289
|
+
const dateField = isComplete ? ` ${today} ` : ' ';
|
|
290
|
+
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
|
|
291
|
+
const cells = fullRow.split('|').slice(1, -1); // drop leading/trailing empty from split
|
|
292
|
+
if (cells.length === 5) {
|
|
293
|
+
// 5-col: Phase | Milestone | Plans | Status | Completed
|
|
294
|
+
cells[2] = ` ${summaryCount}/${planCount} `;
|
|
295
|
+
cells[3] = ` ${status.padEnd(11)}`;
|
|
296
|
+
cells[4] = dateField;
|
|
297
|
+
} else if (cells.length === 4) {
|
|
298
|
+
// 4-col: Phase | Plans | Status | Completed
|
|
299
|
+
cells[1] = ` ${summaryCount}/${planCount} `;
|
|
300
|
+
cells[2] = ` ${status.padEnd(11)}`;
|
|
301
|
+
cells[3] = dateField;
|
|
302
|
+
}
|
|
303
|
+
return '|' + cells.join('|') + '|';
|
|
304
|
+
});
|
|
262
305
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
'i'
|
|
267
|
-
);
|
|
268
|
-
const planCountText = isComplete
|
|
269
|
-
? `${summaryCount}/${planCount} plans complete`
|
|
270
|
-
: `${summaryCount}/${planCount} plans executed`;
|
|
271
|
-
roadmapContent = roadmapContent.replace(planCountPattern, `$1${planCountText}`);
|
|
272
|
-
|
|
273
|
-
// If complete: check checkbox
|
|
274
|
-
if (isComplete) {
|
|
275
|
-
const checkboxPattern = new RegExp(
|
|
276
|
-
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
|
|
306
|
+
// Update plan count in phase detail section
|
|
307
|
+
const planCountPattern = new RegExp(
|
|
308
|
+
`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
|
|
277
309
|
'i'
|
|
278
310
|
);
|
|
279
|
-
|
|
280
|
-
|
|
311
|
+
const planCountText = isComplete
|
|
312
|
+
? `${summaryCount}/${planCount} plans complete`
|
|
313
|
+
: `${summaryCount}/${planCount} plans executed`;
|
|
314
|
+
roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, `$1${planCountText}`);
|
|
315
|
+
|
|
316
|
+
// If complete: check checkbox
|
|
317
|
+
if (isComplete) {
|
|
318
|
+
const checkboxPattern = new RegExp(
|
|
319
|
+
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
|
|
320
|
+
'i'
|
|
321
|
+
);
|
|
322
|
+
roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
|
|
323
|
+
}
|
|
281
324
|
|
|
282
|
-
|
|
325
|
+
// Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md", "- [ ] 50-01:", or "- [ ] **50-01**")
|
|
326
|
+
for (const summaryFile of phaseInfo.summaries) {
|
|
327
|
+
const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
328
|
+
if (!planId) continue;
|
|
329
|
+
const planEscaped = escapeRegex(planId);
|
|
330
|
+
const planCheckboxPattern = new RegExp(
|
|
331
|
+
`(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`,
|
|
332
|
+
'i'
|
|
333
|
+
);
|
|
334
|
+
roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
|
|
335
|
+
}
|
|
283
336
|
|
|
337
|
+
fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
|
|
338
|
+
});
|
|
284
339
|
output({
|
|
285
340
|
updated: true,
|
|
286
341
|
phase: phaseNum,
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Drift Detection — Detects schema-relevant file changes and verifies
|
|
3
|
+
* that the appropriate database push command was executed during a phase.
|
|
4
|
+
*
|
|
5
|
+
* Prevents false-positive verification when schema files change but no push
|
|
6
|
+
* occurs — TypeScript types come from config, not the live database, so
|
|
7
|
+
* build/types pass on a broken state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
// ─── ORM Patterns ────────────────────────────────────────────────────────────
|
|
13
|
+
//
|
|
14
|
+
// Each entry maps a glob-like pattern to an ORM name. Patterns use forward
|
|
15
|
+
// slashes internally — Windows backslash paths are normalized before matching.
|
|
16
|
+
|
|
17
|
+
const SCHEMA_PATTERNS = [
|
|
18
|
+
// Payload CMS
|
|
19
|
+
{ pattern: /^src\/collections\/.*\.ts$/, orm: 'payload' },
|
|
20
|
+
{ pattern: /^src\/globals\/.*\.ts$/, orm: 'payload' },
|
|
21
|
+
|
|
22
|
+
// Prisma
|
|
23
|
+
{ pattern: /^prisma\/schema\.prisma$/, orm: 'prisma' },
|
|
24
|
+
{ pattern: /^prisma\/schema\/.*\.prisma$/, orm: 'prisma' },
|
|
25
|
+
|
|
26
|
+
// Drizzle
|
|
27
|
+
{ pattern: /^drizzle\/schema\.ts$/, orm: 'drizzle' },
|
|
28
|
+
{ pattern: /^src\/db\/schema\.ts$/, orm: 'drizzle' },
|
|
29
|
+
{ pattern: /^drizzle\/.*\.ts$/, orm: 'drizzle' },
|
|
30
|
+
|
|
31
|
+
// Supabase
|
|
32
|
+
{ pattern: /^supabase\/migrations\/.*\.sql$/, orm: 'supabase' },
|
|
33
|
+
|
|
34
|
+
// TypeORM
|
|
35
|
+
{ pattern: /^src\/entities\/.*\.ts$/, orm: 'typeorm' },
|
|
36
|
+
{ pattern: /^src\/migrations\/.*\.ts$/, orm: 'typeorm' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// ─── Push Commands & Evidence Patterns ───────────────────────────────────────
|
|
40
|
+
//
|
|
41
|
+
// For each ORM, the push command that agents should run, plus regex patterns
|
|
42
|
+
// that indicate the push was actually executed (matched against execution logs,
|
|
43
|
+
// SUMMARY.md content, and git commit messages).
|
|
44
|
+
|
|
45
|
+
const ORM_INFO = {
|
|
46
|
+
payload: {
|
|
47
|
+
pushCommand: 'npx payload migrate',
|
|
48
|
+
envHint: 'CI=true PAYLOAD_MIGRATING=true npx payload migrate',
|
|
49
|
+
interactiveWarning: 'Payload migrate may require interactive prompts — use CI=true PAYLOAD_MIGRATING=true to suppress',
|
|
50
|
+
evidencePatterns: [
|
|
51
|
+
/payload\s+migrate/i,
|
|
52
|
+
/PAYLOAD_MIGRATING/,
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
prisma: {
|
|
56
|
+
pushCommand: 'npx prisma db push',
|
|
57
|
+
envHint: 'npx prisma db push --accept-data-loss (if destructive changes are intended)',
|
|
58
|
+
interactiveWarning: 'Prisma db push may prompt for confirmation on destructive changes — use --accept-data-loss to bypass',
|
|
59
|
+
evidencePatterns: [
|
|
60
|
+
/prisma\s+db\s+push/i,
|
|
61
|
+
/prisma\s+migrate\s+deploy/i,
|
|
62
|
+
/prisma\s+migrate\s+dev/i,
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
drizzle: {
|
|
66
|
+
pushCommand: 'npx drizzle-kit push',
|
|
67
|
+
envHint: 'npx drizzle-kit push',
|
|
68
|
+
interactiveWarning: null,
|
|
69
|
+
evidencePatterns: [
|
|
70
|
+
/drizzle-kit\s+push/i,
|
|
71
|
+
/drizzle-kit\s+migrate/i,
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
supabase: {
|
|
75
|
+
pushCommand: 'supabase db push',
|
|
76
|
+
envHint: 'supabase db push',
|
|
77
|
+
interactiveWarning: 'Supabase db push may require authentication — ensure SUPABASE_ACCESS_TOKEN is set',
|
|
78
|
+
evidencePatterns: [
|
|
79
|
+
/supabase\s+db\s+push/i,
|
|
80
|
+
/supabase\s+migration\s+up/i,
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
typeorm: {
|
|
84
|
+
pushCommand: 'npx typeorm migration:run',
|
|
85
|
+
envHint: 'npx typeorm migration:run -d src/data-source.ts',
|
|
86
|
+
interactiveWarning: null,
|
|
87
|
+
evidencePatterns: [
|
|
88
|
+
/typeorm\s+migration:run/i,
|
|
89
|
+
/typeorm\s+schema:sync/i,
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Detect schema-relevant files in a list of file paths.
|
|
98
|
+
*
|
|
99
|
+
* @param {string[]} files - List of file paths (relative to project root)
|
|
100
|
+
* @returns {{ detected: boolean, matches: string[], orms: string[] }}
|
|
101
|
+
*/
|
|
102
|
+
function detectSchemaFiles(files) {
|
|
103
|
+
const matches = [];
|
|
104
|
+
const orms = new Set();
|
|
105
|
+
|
|
106
|
+
for (const rawFile of files) {
|
|
107
|
+
// Normalize Windows backslash paths
|
|
108
|
+
const file = rawFile.replace(/\\/g, '/');
|
|
109
|
+
|
|
110
|
+
for (const { pattern, orm } of SCHEMA_PATTERNS) {
|
|
111
|
+
if (pattern.test(file)) {
|
|
112
|
+
matches.push(rawFile);
|
|
113
|
+
orms.add(orm);
|
|
114
|
+
break; // One match per file is enough
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
detected: matches.length > 0,
|
|
121
|
+
matches,
|
|
122
|
+
orms: Array.from(orms),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get ORM-specific push command info.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} ormName - ORM identifier (payload, prisma, drizzle, supabase, typeorm)
|
|
130
|
+
* @returns {{ pushCommand: string, envHint: string, interactiveWarning: string|null, evidencePatterns: RegExp[] } | null}
|
|
131
|
+
*/
|
|
132
|
+
function detectSchemaOrm(ormName) {
|
|
133
|
+
return ORM_INFO[ormName] || null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check for schema drift: schema files changed but no push evidence found.
|
|
138
|
+
*
|
|
139
|
+
* @param {string[]} changedFiles - Files changed during the phase
|
|
140
|
+
* @param {string} executionLog - Combined text from SUMMARY.md, commit messages, and execution logs
|
|
141
|
+
* @param {{ skipCheck?: boolean }} [options] - Options
|
|
142
|
+
* @returns {{ driftDetected: boolean, blocking: boolean, schemaFiles: string[], orms: string[], unpushedOrms: string[], message: string, skipped?: boolean }}
|
|
143
|
+
*/
|
|
144
|
+
function checkSchemaDrift(changedFiles, executionLog, options = {}) {
|
|
145
|
+
const { skipCheck = false } = options;
|
|
146
|
+
|
|
147
|
+
const detection = detectSchemaFiles(changedFiles);
|
|
148
|
+
|
|
149
|
+
if (!detection.detected) {
|
|
150
|
+
return {
|
|
151
|
+
driftDetected: false,
|
|
152
|
+
blocking: false,
|
|
153
|
+
schemaFiles: [],
|
|
154
|
+
orms: [],
|
|
155
|
+
unpushedOrms: [],
|
|
156
|
+
message: '',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check which ORMs have push evidence in the execution log
|
|
161
|
+
const pushedOrms = new Set();
|
|
162
|
+
const unpushedOrms = [];
|
|
163
|
+
|
|
164
|
+
for (const orm of detection.orms) {
|
|
165
|
+
const info = ORM_INFO[orm];
|
|
166
|
+
if (!info) continue;
|
|
167
|
+
|
|
168
|
+
const hasPushEvidence = info.evidencePatterns.some(p => p.test(executionLog));
|
|
169
|
+
if (hasPushEvidence) {
|
|
170
|
+
pushedOrms.add(orm);
|
|
171
|
+
} else {
|
|
172
|
+
unpushedOrms.push(orm);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const driftDetected = unpushedOrms.length > 0;
|
|
177
|
+
|
|
178
|
+
if (!driftDetected) {
|
|
179
|
+
return {
|
|
180
|
+
driftDetected: false,
|
|
181
|
+
blocking: false,
|
|
182
|
+
schemaFiles: detection.matches,
|
|
183
|
+
orms: detection.orms,
|
|
184
|
+
unpushedOrms: [],
|
|
185
|
+
message: '',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Build actionable message
|
|
190
|
+
const pushCommands = unpushedOrms
|
|
191
|
+
.map(orm => {
|
|
192
|
+
const info = ORM_INFO[orm];
|
|
193
|
+
return info ? ` ${orm}: ${info.envHint || info.pushCommand}` : null;
|
|
194
|
+
})
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.join('\n');
|
|
197
|
+
|
|
198
|
+
const message = [
|
|
199
|
+
'Schema drift detected: schema-relevant files changed but no database push was executed.',
|
|
200
|
+
'',
|
|
201
|
+
`Schema files changed: ${detection.matches.join(', ')}`,
|
|
202
|
+
`ORMs requiring push: ${unpushedOrms.join(', ')}`,
|
|
203
|
+
'',
|
|
204
|
+
'Required push commands:',
|
|
205
|
+
pushCommands,
|
|
206
|
+
'',
|
|
207
|
+
'Run the appropriate push command, or set GSD_SKIP_SCHEMA_CHECK=true to bypass this gate.',
|
|
208
|
+
].join('\n');
|
|
209
|
+
|
|
210
|
+
if (skipCheck) {
|
|
211
|
+
return {
|
|
212
|
+
driftDetected: true,
|
|
213
|
+
blocking: false,
|
|
214
|
+
skipped: true,
|
|
215
|
+
schemaFiles: detection.matches,
|
|
216
|
+
orms: detection.orms,
|
|
217
|
+
unpushedOrms,
|
|
218
|
+
message: 'Schema drift detected but check was skipped (GSD_SKIP_SCHEMA_CHECK=true).',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
driftDetected: true,
|
|
224
|
+
blocking: true,
|
|
225
|
+
schemaFiles: detection.matches,
|
|
226
|
+
orms: detection.orms,
|
|
227
|
+
unpushedOrms,
|
|
228
|
+
message,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
SCHEMA_PATTERNS,
|
|
234
|
+
ORM_INFO,
|
|
235
|
+
detectSchemaFiles,
|
|
236
|
+
detectSchemaOrm,
|
|
237
|
+
checkSchemaDrift,
|
|
238
|
+
};
|