gsd-opencode 1.33.3 → 1.35.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 +23 -0
- package/agents/gsd-ai-researcher.md +142 -0
- package/agents/gsd-code-fixer.md +523 -0
- package/agents/gsd-code-reviewer.md +361 -0
- package/agents/gsd-debugger.md +14 -1
- package/agents/gsd-domain-researcher.md +162 -0
- package/agents/gsd-eval-auditor.md +170 -0
- package/agents/gsd-eval-planner.md +161 -0
- package/agents/gsd-executor.md +70 -7
- package/agents/gsd-framework-selector.md +167 -0
- package/agents/gsd-intel-updater.md +320 -0
- package/agents/gsd-phase-researcher.md +26 -0
- package/agents/gsd-plan-checker.md +12 -0
- package/agents/gsd-planner.md +16 -6
- package/agents/gsd-project-researcher.md +23 -0
- package/agents/gsd-ui-researcher.md +23 -0
- package/agents/gsd-verifier.md +55 -1
- package/commands/gsd/gsd-ai-integration-phase.md +36 -0
- package/commands/gsd/gsd-audit-fix.md +33 -0
- package/commands/gsd/gsd-autonomous.md +1 -0
- package/commands/gsd/gsd-code-review-fix.md +52 -0
- package/commands/gsd/gsd-code-review.md +55 -0
- package/commands/gsd/gsd-eval-review.md +32 -0
- package/commands/gsd/gsd-explore.md +27 -0
- package/commands/gsd/gsd-from-gsd2.md +45 -0
- package/commands/gsd/gsd-import.md +36 -0
- package/commands/gsd/gsd-intel.md +183 -0
- package/commands/gsd/gsd-next.md +2 -0
- package/commands/gsd/gsd-reapply-patches.md +58 -3
- package/commands/gsd/gsd-review.md +4 -2
- package/commands/gsd/gsd-scan.md +26 -0
- package/commands/gsd/gsd-undo.md +34 -0
- package/commands/gsd/gsd-workstreams.md +6 -6
- package/get-shit-done/bin/gsd-tools.cjs +143 -5
- package/get-shit-done/bin/lib/commands.cjs +10 -2
- package/get-shit-done/bin/lib/config.cjs +71 -37
- package/get-shit-done/bin/lib/core.cjs +70 -8
- package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
- package/get-shit-done/bin/lib/init.cjs +20 -6
- package/get-shit-done/bin/lib/intel.cjs +660 -0
- package/get-shit-done/bin/lib/learnings.cjs +378 -0
- package/get-shit-done/bin/lib/milestone.cjs +25 -15
- package/get-shit-done/bin/lib/model-profiles.cjs +17 -17
- package/get-shit-done/bin/lib/phase.cjs +148 -112
- package/get-shit-done/bin/lib/roadmap.cjs +12 -5
- package/get-shit-done/bin/lib/security.cjs +119 -0
- package/get-shit-done/bin/lib/state.cjs +283 -221
- package/get-shit-done/bin/lib/template.cjs +8 -4
- package/get-shit-done/bin/lib/verify.cjs +42 -5
- package/get-shit-done/references/ai-evals.md +156 -0
- package/get-shit-done/references/ai-frameworks.md +186 -0
- package/get-shit-done/references/common-bug-patterns.md +114 -0
- package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
- package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
- package/get-shit-done/references/gates.md +70 -0
- package/get-shit-done/references/ios-scaffold.md +123 -0
- package/get-shit-done/references/model-profile-resolution.md +6 -7
- package/get-shit-done/references/model-profiles.md +20 -14
- package/get-shit-done/references/planning-config.md +237 -0
- package/get-shit-done/references/thinking-models-debug.md +44 -0
- package/get-shit-done/references/thinking-models-execution.md +50 -0
- package/get-shit-done/references/thinking-models-planning.md +62 -0
- package/get-shit-done/references/thinking-models-research.md +50 -0
- package/get-shit-done/references/thinking-models-verification.md +55 -0
- package/get-shit-done/references/thinking-partner.md +96 -0
- package/get-shit-done/references/universal-anti-patterns.md +6 -1
- package/get-shit-done/references/verification-overrides.md +227 -0
- package/get-shit-done/templates/AI-SPEC.md +246 -0
- package/get-shit-done/workflows/add-tests.md +3 -0
- package/get-shit-done/workflows/add-todo.md +2 -0
- package/get-shit-done/workflows/ai-integration-phase.md +284 -0
- package/get-shit-done/workflows/audit-fix.md +154 -0
- package/get-shit-done/workflows/autonomous.md +33 -2
- package/get-shit-done/workflows/check-todos.md +2 -0
- package/get-shit-done/workflows/cleanup.md +2 -0
- package/get-shit-done/workflows/code-review-fix.md +497 -0
- package/get-shit-done/workflows/code-review.md +515 -0
- package/get-shit-done/workflows/complete-milestone.md +40 -15
- package/get-shit-done/workflows/diagnose-issues.md +1 -1
- package/get-shit-done/workflows/discovery-phase.md +3 -1
- package/get-shit-done/workflows/discuss-phase-assumptions.md +1 -1
- package/get-shit-done/workflows/discuss-phase.md +21 -7
- package/get-shit-done/workflows/do.md +2 -0
- package/get-shit-done/workflows/docs-update.md +2 -0
- package/get-shit-done/workflows/eval-review.md +155 -0
- package/get-shit-done/workflows/execute-phase.md +307 -57
- package/get-shit-done/workflows/execute-plan.md +64 -93
- package/get-shit-done/workflows/explore.md +136 -0
- package/get-shit-done/workflows/help.md +1 -1
- package/get-shit-done/workflows/import.md +273 -0
- package/get-shit-done/workflows/inbox.md +387 -0
- package/get-shit-done/workflows/manager.md +4 -10
- package/get-shit-done/workflows/new-milestone.md +3 -1
- package/get-shit-done/workflows/new-project.md +2 -0
- package/get-shit-done/workflows/new-workspace.md +2 -0
- package/get-shit-done/workflows/next.md +56 -0
- package/get-shit-done/workflows/note.md +2 -0
- package/get-shit-done/workflows/plan-phase.md +97 -17
- package/get-shit-done/workflows/plant-seed.md +3 -0
- package/get-shit-done/workflows/pr-branch.md +41 -13
- package/get-shit-done/workflows/profile-user.md +4 -2
- package/get-shit-done/workflows/quick.md +99 -4
- package/get-shit-done/workflows/remove-workspace.md +2 -0
- package/get-shit-done/workflows/review.md +53 -6
- package/get-shit-done/workflows/scan.md +98 -0
- package/get-shit-done/workflows/secure-phase.md +2 -0
- package/get-shit-done/workflows/settings.md +18 -3
- package/get-shit-done/workflows/ship.md +3 -0
- package/get-shit-done/workflows/ui-phase.md +10 -2
- package/get-shit-done/workflows/ui-review.md +2 -0
- package/get-shit-done/workflows/undo.md +314 -0
- package/get-shit-done/workflows/update.md +2 -0
- package/get-shit-done/workflows/validate-phase.md +2 -0
- package/get-shit-done/workflows/verify-phase.md +83 -0
- package/get-shit-done/workflows/verify-work.md +12 -1
- package/package.json +1 -1
- package/skills/gsd-code-review/SKILL.md +48 -0
- package/skills/gsd-code-review-fix/SKILL.md +44 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gsd2-import — Reverse migration from GSD-2 (.gsd/) to GSD v1 (.planning/)
|
|
5
|
+
*
|
|
6
|
+
* Reads a GSD-2 project directory structure and produces a complete
|
|
7
|
+
* .planning/ artifact tree in GSD v1 format.
|
|
8
|
+
*
|
|
9
|
+
* GSD-2 hierarchy: Milestone → Slice → task
|
|
10
|
+
* GSD v1 hierarchy: Milestone (in ROADMAP.md) → Phase → Plan
|
|
11
|
+
*
|
|
12
|
+
* Mapping rules:
|
|
13
|
+
* - Slices are numbered sequentially across all milestones (01, 02, …)
|
|
14
|
+
* - Tasks within a slice become plans (01-01, 01-02, …)
|
|
15
|
+
* - Completed slices ([x] in ROADMAP) → [x] phases in ROADMAP.md
|
|
16
|
+
* - Tasks with a SUMMARY file → SUMMARY.md written
|
|
17
|
+
* - Slice RESEARCH.md → phase XX-RESEARCH.md
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('node:fs');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
|
|
23
|
+
// ─── Utilities ──────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function readOptional(filePath) {
|
|
26
|
+
try { return fs.readFileSync(filePath, 'utf8'); } catch { return null; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function zeroPad(n, width = 2) {
|
|
30
|
+
return String(n).padStart(width, '0');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function slugify(title) {
|
|
34
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── GSD-2 Parser ───────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Find the .gsd/ directory starting from a project root.
|
|
41
|
+
* Returns the absolute path or null if not found.
|
|
42
|
+
*/
|
|
43
|
+
function findGsd2Root(startPath) {
|
|
44
|
+
if (path.basename(startPath) === '.gsd' && fs.existsSync(startPath)) {
|
|
45
|
+
return startPath;
|
|
46
|
+
}
|
|
47
|
+
const candidate = path.join(startPath, '.gsd');
|
|
48
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
49
|
+
return candidate;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse the ## Slices section from a GSD-2 milestone ROADMAP.md.
|
|
56
|
+
* Each slice entry looks like:
|
|
57
|
+
* - [x] **S01: Title** `risk:medium` `depends:[S00]`
|
|
58
|
+
*/
|
|
59
|
+
function parseSlicesFromRoadmap(content) {
|
|
60
|
+
const slices = [];
|
|
61
|
+
const sectionMatch = content.match(/## Slices\n([\s\S]*?)(?:\n## |\n# |$)/);
|
|
62
|
+
if (!sectionMatch) return slices;
|
|
63
|
+
|
|
64
|
+
for (const line of sectionMatch[1].split('\n')) {
|
|
65
|
+
const m = line.match(/^- \[([x ])\]\s+\*\*(\w+):\s*([^*]+)\*\*/);
|
|
66
|
+
if (!m) continue;
|
|
67
|
+
slices.push({ done: m[1] === 'x', id: m[2].trim(), title: m[3].trim() });
|
|
68
|
+
}
|
|
69
|
+
return slices;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse the milestone title from the first heading in a GSD-2 ROADMAP.md.
|
|
74
|
+
* Format: # M001: Title
|
|
75
|
+
*/
|
|
76
|
+
function parseMilestoneTitle(content) {
|
|
77
|
+
const m = content.match(/^# \w+:\s*(.+)/m);
|
|
78
|
+
return m ? m[1].trim() : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse a task title from a GSD-2 T##-PLAN.md.
|
|
83
|
+
* Format: # T01: Title
|
|
84
|
+
*/
|
|
85
|
+
function parseTaskTitle(content, fallback) {
|
|
86
|
+
const m = content.match(/^# \w+:\s*(.+)/m);
|
|
87
|
+
return m ? m[1].trim() : fallback;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse the ## Description body from a GSD-2 task plan.
|
|
92
|
+
*/
|
|
93
|
+
function parseTaskDescription(content) {
|
|
94
|
+
const m = content.match(/## Description\n+([\s\S]+?)(?:\n## |\n# |$)/);
|
|
95
|
+
return m ? m[1].trim() : '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse ## Must-Haves items from a GSD-2 task plan.
|
|
100
|
+
*/
|
|
101
|
+
function parseTaskMustHaves(content) {
|
|
102
|
+
const m = content.match(/## Must-Haves\n+([\s\S]+?)(?:\n## |\n# |$)/);
|
|
103
|
+
if (!m) return [];
|
|
104
|
+
return m[1].split('\n')
|
|
105
|
+
.map(l => l.match(/^- \[[ x]\]\s*(.+)/))
|
|
106
|
+
.filter(Boolean)
|
|
107
|
+
.map(match => match[1].trim());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* read all task plan files from a GSD-2 tasks/ directory.
|
|
112
|
+
*/
|
|
113
|
+
function readTasksDir(tasksDir) {
|
|
114
|
+
if (!fs.existsSync(tasksDir)) return [];
|
|
115
|
+
|
|
116
|
+
return fs.readdirSync(tasksDir)
|
|
117
|
+
.filter(f => f.endsWith('-PLAN.md'))
|
|
118
|
+
.sort()
|
|
119
|
+
.map(tf => {
|
|
120
|
+
const tid = tf.replace('-PLAN.md', '');
|
|
121
|
+
const plan = readOptional(path.join(tasksDir, tf));
|
|
122
|
+
const summary = readOptional(path.join(tasksDir, `${tid}-SUMMARY.md`));
|
|
123
|
+
return {
|
|
124
|
+
id: tid,
|
|
125
|
+
title: plan ? parseTaskTitle(plan, tid) : tid,
|
|
126
|
+
description: plan ? parseTaskDescription(plan) : '',
|
|
127
|
+
mustHaves: plan ? parseTaskMustHaves(plan) : [],
|
|
128
|
+
plan,
|
|
129
|
+
summary,
|
|
130
|
+
done: !!summary,
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Parse a complete GSD-2 .gsd/ directory into a structured representation.
|
|
137
|
+
*/
|
|
138
|
+
function parseGsd2(gsdDir) {
|
|
139
|
+
const data = {
|
|
140
|
+
projectContent: readOptional(path.join(gsdDir, 'PROJECT.md')),
|
|
141
|
+
requirements: readOptional(path.join(gsdDir, 'REQUIREMENTS.md')),
|
|
142
|
+
milestones: [],
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const milestonesBase = path.join(gsdDir, 'milestones');
|
|
146
|
+
if (!fs.existsSync(milestonesBase)) return data;
|
|
147
|
+
|
|
148
|
+
const milestoneIds = fs.readdirSync(milestonesBase)
|
|
149
|
+
.filter(d => fs.statSync(path.join(milestonesBase, d)).isDirectory())
|
|
150
|
+
.sort();
|
|
151
|
+
|
|
152
|
+
for (const mid of milestoneIds) {
|
|
153
|
+
const mDir = path.join(milestonesBase, mid);
|
|
154
|
+
const roadmapContent = readOptional(path.join(mDir, `${mid}-ROADMAP.md`));
|
|
155
|
+
const slicesDir = path.join(mDir, 'slices');
|
|
156
|
+
|
|
157
|
+
const sliceInfos = roadmapContent ? parseSlicesFromRoadmap(roadmapContent) : [];
|
|
158
|
+
|
|
159
|
+
const slices = sliceInfos.map(info => {
|
|
160
|
+
const sDir = path.join(slicesDir, info.id);
|
|
161
|
+
const hasSDir = fs.existsSync(sDir);
|
|
162
|
+
return {
|
|
163
|
+
id: info.id,
|
|
164
|
+
title: info.title,
|
|
165
|
+
done: info.done,
|
|
166
|
+
plan: hasSDir ? readOptional(path.join(sDir, `${info.id}-PLAN.md`)) : null,
|
|
167
|
+
summary: hasSDir ? readOptional(path.join(sDir, `${info.id}-SUMMARY.md`)) : null,
|
|
168
|
+
research: hasSDir ? readOptional(path.join(sDir, `${info.id}-RESEARCH.md`)) : null,
|
|
169
|
+
context: hasSDir ? readOptional(path.join(sDir, `${info.id}-CONTEXT.md`)) : null,
|
|
170
|
+
tasks: hasSDir ? readTasksDir(path.join(sDir, 'tasks')) : [],
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
data.milestones.push({
|
|
175
|
+
id: mid,
|
|
176
|
+
title: roadmapContent ? (parseMilestoneTitle(roadmapContent) ?? mid) : mid,
|
|
177
|
+
research: readOptional(path.join(mDir, `${mid}-RESEARCH.md`)),
|
|
178
|
+
slices,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return data;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── Artifact Builders ──────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build a GSD v1 PLAN.md from a GSD-2 task.
|
|
189
|
+
*/
|
|
190
|
+
function buildPlanMd(task, phasePrefix, planPrefix, phaseSlug, milestoneTitle) {
|
|
191
|
+
const lines = [
|
|
192
|
+
'---',
|
|
193
|
+
`phase: "${phasePrefix}"`,
|
|
194
|
+
`plan: "${planPrefix}"`,
|
|
195
|
+
'type: "implementation"',
|
|
196
|
+
'---',
|
|
197
|
+
'',
|
|
198
|
+
'<objective>',
|
|
199
|
+
task.title,
|
|
200
|
+
'</objective>',
|
|
201
|
+
'',
|
|
202
|
+
'<context>',
|
|
203
|
+
`Phase: ${phasePrefix} (${phaseSlug}) — Milestone: ${milestoneTitle}`,
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
if (task.description) {
|
|
207
|
+
lines.push('', task.description);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
lines.push('</context>');
|
|
211
|
+
|
|
212
|
+
if (task.mustHaves.length > 0) {
|
|
213
|
+
lines.push('', '<must_haves>');
|
|
214
|
+
for (const mh of task.mustHaves) {
|
|
215
|
+
lines.push(`- ${mh}`);
|
|
216
|
+
}
|
|
217
|
+
lines.push('</must_haves>');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return lines.join('\n') + '\n';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Build a GSD v1 SUMMARY.md from a GSD-2 task summary.
|
|
225
|
+
* Strips the GSD-2 frontmatter and preserves the body.
|
|
226
|
+
*/
|
|
227
|
+
function buildSummaryMd(task, phasePrefix, planPrefix) {
|
|
228
|
+
const raw = task.summary || '';
|
|
229
|
+
// Strip GSD-2 frontmatter block (--- ... ---) if present
|
|
230
|
+
const bodyMatch = raw.match(/^---[\s\S]*?---\n+([\s\S]*)$/);
|
|
231
|
+
const body = bodyMatch ? bodyMatch[1].trim() : raw.trim();
|
|
232
|
+
|
|
233
|
+
return [
|
|
234
|
+
'---',
|
|
235
|
+
`phase: "${phasePrefix}"`,
|
|
236
|
+
`plan: "${planPrefix}"`,
|
|
237
|
+
'---',
|
|
238
|
+
'',
|
|
239
|
+
body || 'task completed (migrated from GSD-2).',
|
|
240
|
+
'',
|
|
241
|
+
].join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Build a GSD v1 XX-CONTEXT.md from a GSD-2 slice.
|
|
246
|
+
*/
|
|
247
|
+
function buildContextMd(slice, phasePrefix) {
|
|
248
|
+
const lines = [
|
|
249
|
+
`# Phase ${phasePrefix} Context`,
|
|
250
|
+
'',
|
|
251
|
+
`Migrated from GSD-2 slice ${slice.id}: ${slice.title}`,
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const extra = slice.context || '';
|
|
255
|
+
if (extra.trim()) {
|
|
256
|
+
lines.push('', extra.trim());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return lines.join('\n') + '\n';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Build the GSD v1 ROADMAP.md with milestone-sectioned format.
|
|
264
|
+
*/
|
|
265
|
+
function buildRoadmapMd(milestones, phaseMap) {
|
|
266
|
+
const lines = ['# Roadmap', ''];
|
|
267
|
+
|
|
268
|
+
for (const milestone of milestones) {
|
|
269
|
+
lines.push(`## ${milestone.id}: ${milestone.title}`, '');
|
|
270
|
+
const mPhases = phaseMap.filter(p => p.milestoneId === milestone.id);
|
|
271
|
+
for (const { slice, phaseNum } of mPhases) {
|
|
272
|
+
const prefix = zeroPad(phaseNum);
|
|
273
|
+
const slug = slugify(slice.title);
|
|
274
|
+
const check = slice.done ? 'x' : ' ';
|
|
275
|
+
lines.push(`- [${check}] **Phase ${prefix}: ${slug}** — ${slice.title}`);
|
|
276
|
+
}
|
|
277
|
+
lines.push('');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return lines.join('\n');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Build the GSD v1 STATE.md reflecting the current position in the project.
|
|
285
|
+
*/
|
|
286
|
+
function buildStateMd(phaseMap) {
|
|
287
|
+
const currentEntry = phaseMap.find(p => !p.slice.done);
|
|
288
|
+
const totalPhases = phaseMap.length;
|
|
289
|
+
const donePhases = phaseMap.filter(p => p.slice.done).length;
|
|
290
|
+
const pct = totalPhases > 0 ? Math.round((donePhases / totalPhases) * 100) : 0;
|
|
291
|
+
|
|
292
|
+
const currentPhaseNum = currentEntry ? zeroPad(currentEntry.phaseNum) : zeroPad(totalPhases);
|
|
293
|
+
const currentSlug = currentEntry ? slugify(currentEntry.slice.title) : 'complete';
|
|
294
|
+
const status = currentEntry ? 'Ready to plan' : 'All phases complete';
|
|
295
|
+
|
|
296
|
+
const filled = Math.round(pct / 10);
|
|
297
|
+
const bar = `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}]`;
|
|
298
|
+
const today = new Date().toISOString().split('T')[0];
|
|
299
|
+
|
|
300
|
+
return [
|
|
301
|
+
'# Project State',
|
|
302
|
+
'',
|
|
303
|
+
'## Project Reference',
|
|
304
|
+
'',
|
|
305
|
+
'See: .planning/PROJECT.md',
|
|
306
|
+
'',
|
|
307
|
+
`**Current focus:** Phase ${currentPhaseNum} (${currentSlug})`,
|
|
308
|
+
'',
|
|
309
|
+
'## Current Position',
|
|
310
|
+
'',
|
|
311
|
+
`Phase: ${currentPhaseNum} of ${zeroPad(totalPhases)} (${currentSlug})`,
|
|
312
|
+
`Status: ${status}`,
|
|
313
|
+
`Last activity: ${today} — Migrated from GSD-2`,
|
|
314
|
+
'',
|
|
315
|
+
`Progress: ${bar} ${pct}%`,
|
|
316
|
+
'',
|
|
317
|
+
'## Accumulated Context',
|
|
318
|
+
'',
|
|
319
|
+
'### Decisions',
|
|
320
|
+
'',
|
|
321
|
+
'Migrated from GSD-2. Review PROJECT.md for key decisions.',
|
|
322
|
+
'',
|
|
323
|
+
'### Blockers/Concerns',
|
|
324
|
+
'',
|
|
325
|
+
'None.',
|
|
326
|
+
'',
|
|
327
|
+
'## Session Continuity',
|
|
328
|
+
'',
|
|
329
|
+
`Last session: ${today}`,
|
|
330
|
+
'Stopped at: Migration from GSD-2 completed',
|
|
331
|
+
'Resume file: None',
|
|
332
|
+
'',
|
|
333
|
+
].join('\n');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ─── Transformer ─────────────────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Convert parsed GSD-2 data into a map of relative path → file content.
|
|
340
|
+
* All paths are relative to the .planning/ root.
|
|
341
|
+
*/
|
|
342
|
+
function buildPlanningArtifacts(gsd2Data) {
|
|
343
|
+
const artifacts = new Map();
|
|
344
|
+
|
|
345
|
+
// Passthrough files
|
|
346
|
+
artifacts.set('PROJECT.md', gsd2Data.projectContent || '# Project\n\n(Migrated from GSD-2)\n');
|
|
347
|
+
if (gsd2Data.requirements) {
|
|
348
|
+
artifacts.set('REQUIREMENTS.md', gsd2Data.requirements);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Minimal valid v1 config
|
|
352
|
+
artifacts.set('config.json', JSON.stringify({ version: 1 }, null, 2) + '\n');
|
|
353
|
+
|
|
354
|
+
// Build sequential phase map: flatten Milestones → Slices into numbered phases
|
|
355
|
+
const phaseMap = [];
|
|
356
|
+
let phaseNum = 1;
|
|
357
|
+
for (const milestone of gsd2Data.milestones) {
|
|
358
|
+
for (const slice of milestone.slices) {
|
|
359
|
+
phaseMap.push({ milestoneId: milestone.id, milestoneTitle: milestone.title, slice, phaseNum });
|
|
360
|
+
phaseNum++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
artifacts.set('ROADMAP.md', buildRoadmapMd(gsd2Data.milestones, phaseMap));
|
|
365
|
+
artifacts.set('STATE.md', buildStateMd(phaseMap));
|
|
366
|
+
|
|
367
|
+
for (const { slice, phaseNum, milestoneTitle } of phaseMap) {
|
|
368
|
+
const prefix = zeroPad(phaseNum);
|
|
369
|
+
const slug = slugify(slice.title);
|
|
370
|
+
const dir = `phases/${prefix}-${slug}`;
|
|
371
|
+
|
|
372
|
+
artifacts.set(`${dir}/${prefix}-CONTEXT.md`, buildContextMd(slice, prefix));
|
|
373
|
+
|
|
374
|
+
if (slice.research) {
|
|
375
|
+
artifacts.set(`${dir}/${prefix}-RESEARCH.md`, slice.research);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for (let i = 0; i < slice.tasks.length; i++) {
|
|
379
|
+
const task = slice.tasks[i];
|
|
380
|
+
const planPrefix = zeroPad(i + 1);
|
|
381
|
+
|
|
382
|
+
artifacts.set(
|
|
383
|
+
`${dir}/${prefix}-${planPrefix}-PLAN.md`,
|
|
384
|
+
buildPlanMd(task, prefix, planPrefix, slug, milestoneTitle)
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
if (task.done && task.summary) {
|
|
388
|
+
artifacts.set(
|
|
389
|
+
`${dir}/${prefix}-${planPrefix}-SUMMARY.md`,
|
|
390
|
+
buildSummaryMd(task, prefix, planPrefix)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return artifacts;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ─── Preview ─────────────────────────────────────────────────────────────────
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Format a dry-run preview string for display before writing.
|
|
403
|
+
*/
|
|
404
|
+
function buildPreview(gsd2Data, artifacts) {
|
|
405
|
+
const lines = ['Preview — files that will be created in .planning/:'];
|
|
406
|
+
|
|
407
|
+
for (const rel of artifacts.keys()) {
|
|
408
|
+
lines.push(` ${rel}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const totalSlices = gsd2Data.milestones.reduce((s, m) => s + m.slices.length, 0);
|
|
412
|
+
const doneSlices = gsd2Data.milestones.reduce((s, m) => s + m.slices.filter(sl => sl.done).length, 0);
|
|
413
|
+
const allTasks = gsd2Data.milestones.flatMap(m => m.slices.flatMap(sl => sl.tasks));
|
|
414
|
+
const doneTasks = allTasks.filter(t => t.done).length;
|
|
415
|
+
|
|
416
|
+
lines.push('');
|
|
417
|
+
lines.push(`Milestones: ${gsd2Data.milestones.length}`);
|
|
418
|
+
lines.push(`Phases (slices): ${totalSlices} (${doneSlices} completed)`);
|
|
419
|
+
lines.push(`Plans (tasks): ${allTasks.length} (${doneTasks} completed)`);
|
|
420
|
+
lines.push('');
|
|
421
|
+
lines.push('Cannot migrate automatically:');
|
|
422
|
+
lines.push(' - GSD-2 cost/token ledger (no v1 equivalent)');
|
|
423
|
+
lines.push(' - GSD-2 database state (rebuilt from files on first /gsd-health)');
|
|
424
|
+
lines.push(' - VS Code extension state');
|
|
425
|
+
|
|
426
|
+
return lines.join('\n');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ─── Writer ───────────────────────────────────────────────────────────────────
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* write all artifacts to the .planning/ directory.
|
|
433
|
+
*/
|
|
434
|
+
function writePlanningDir(artifacts, planningRoot) {
|
|
435
|
+
for (const [rel, content] of artifacts) {
|
|
436
|
+
const absPath = path.join(planningRoot, rel);
|
|
437
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
438
|
+
fs.writeFileSync(absPath, content, 'utf8');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ─── Command Handler ──────────────────────────────────────────────────────────
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Entry point called from gsd-tools.cjs.
|
|
446
|
+
* Supports: --force, --dry-run, --path <dir>
|
|
447
|
+
*/
|
|
448
|
+
function cmdFromGsd2(args, cwd, raw) {
|
|
449
|
+
const { output, error } = require('./core.cjs');
|
|
450
|
+
|
|
451
|
+
const force = args.includes('--force');
|
|
452
|
+
const dryRun = args.includes('--dry-run');
|
|
453
|
+
|
|
454
|
+
const pathIdx = args.indexOf('--path');
|
|
455
|
+
const projectDir = pathIdx >= 0 && args[pathIdx + 1]
|
|
456
|
+
? path.resolve(cwd, args[pathIdx + 1])
|
|
457
|
+
: cwd;
|
|
458
|
+
|
|
459
|
+
const gsdDir = findGsd2Root(projectDir);
|
|
460
|
+
if (!gsdDir) {
|
|
461
|
+
return output({ success: false, error: `No .gsd/ directory found in ${projectDir}` }, raw);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const planningRoot = path.join(path.dirname(gsdDir), '.planning');
|
|
465
|
+
if (fs.existsSync(planningRoot) && !force) {
|
|
466
|
+
return output({
|
|
467
|
+
success: false,
|
|
468
|
+
error: `.planning/ already exists at ${planningRoot}. Pass --force to overwrite.`,
|
|
469
|
+
}, raw);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const gsd2Data = parseGsd2(gsdDir);
|
|
473
|
+
const artifacts = buildPlanningArtifacts(gsd2Data);
|
|
474
|
+
const preview = buildPreview(gsd2Data, artifacts);
|
|
475
|
+
|
|
476
|
+
if (dryRun) {
|
|
477
|
+
return output({ success: true, dryRun: true, preview }, raw);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
writePlanningDir(artifacts, planningRoot);
|
|
481
|
+
|
|
482
|
+
return output({
|
|
483
|
+
success: true,
|
|
484
|
+
planningDir: planningRoot,
|
|
485
|
+
filesWritten: artifacts.size,
|
|
486
|
+
milestones: gsd2Data.milestones.length,
|
|
487
|
+
preview,
|
|
488
|
+
}, raw);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
module.exports = {
|
|
492
|
+
findGsd2Root,
|
|
493
|
+
parseGsd2,
|
|
494
|
+
buildPlanningArtifacts,
|
|
495
|
+
buildPreview,
|
|
496
|
+
writePlanningDir,
|
|
497
|
+
cmdFromGsd2,
|
|
498
|
+
// Exported for unit tests
|
|
499
|
+
parseSlicesFromRoadmap,
|
|
500
|
+
parseMilestoneTitle,
|
|
501
|
+
parseTaskTitle,
|
|
502
|
+
parseTaskDescription,
|
|
503
|
+
parseTaskMustHaves,
|
|
504
|
+
buildPlanMd,
|
|
505
|
+
buildSummaryMd,
|
|
506
|
+
buildContextMd,
|
|
507
|
+
buildRoadmapMd,
|
|
508
|
+
buildStateMd,
|
|
509
|
+
slugify,
|
|
510
|
+
zeroPad,
|
|
511
|
+
};
|
|
@@ -870,6 +870,23 @@ function cmdInitManager(cwd, raw) {
|
|
|
870
870
|
const phasesDir = paths.phases;
|
|
871
871
|
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
|
872
872
|
|
|
873
|
+
// Pre-compute directory listing once (avoids O(N) readdirSync per phase)
|
|
874
|
+
const _phaseDirEntries = (() => {
|
|
875
|
+
try {
|
|
876
|
+
return fs.readdirSync(phasesDir, { withFileTypes: true })
|
|
877
|
+
.filter(e => e.isDirectory())
|
|
878
|
+
.map(e => e.name);
|
|
879
|
+
} catch { return []; }
|
|
880
|
+
})();
|
|
881
|
+
|
|
882
|
+
// Pre-extract all checkbox states in a single pass (avoids O(N) regex per phase)
|
|
883
|
+
const _checkboxStates = new Map();
|
|
884
|
+
const _cbPattern = /-\s*\[(x| )\]\s*.*Phase\s+(\d+[A-Z]?(?:\.\d+)*)[:\s]/gi;
|
|
885
|
+
let _cbMatch;
|
|
886
|
+
while ((_cbMatch = _cbPattern.exec(content)) !== null) {
|
|
887
|
+
_checkboxStates.set(_cbMatch[2], _cbMatch[1].toLowerCase() === 'x');
|
|
888
|
+
}
|
|
889
|
+
|
|
873
890
|
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
|
874
891
|
const phases = [];
|
|
875
892
|
let match;
|
|
@@ -900,8 +917,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
900
917
|
let isActive = false;
|
|
901
918
|
|
|
902
919
|
try {
|
|
903
|
-
const
|
|
904
|
-
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).filter(isDirInMilestone);
|
|
920
|
+
const dirs = _phaseDirEntries.filter(isDirInMilestone);
|
|
905
921
|
const dirMatch = dirs.find(d => phaseTokenMatches(d, normalized));
|
|
906
922
|
|
|
907
923
|
if (dirMatch) {
|
|
@@ -935,10 +951,8 @@ function cmdInitManager(cwd, raw) {
|
|
|
935
951
|
}
|
|
936
952
|
} catch { /* intentionally empty */ }
|
|
937
953
|
|
|
938
|
-
// Check ROADMAP checkbox status
|
|
939
|
-
const
|
|
940
|
-
const checkboxMatch = content.match(checkboxPattern);
|
|
941
|
-
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
|
|
954
|
+
// Check ROADMAP checkbox status (pre-extracted above the loop)
|
|
955
|
+
const roadmapComplete = _checkboxStates.get(phaseNum) || false;
|
|
942
956
|
if (roadmapComplete && diskStatus !== 'complete') {
|
|
943
957
|
diskStatus = 'complete';
|
|
944
958
|
}
|