pi-gsd 2.0.1 → 2.0.2
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/dist/pi-gsd-hooks.js +1532 -0
- package/package.json +3 -5
- package/.gsd/extensions/pi-gsd-hooks.ts +0 -973
- package/src/cli.ts +0 -644
- package/src/commands/base.ts +0 -67
- package/src/commands/commit.ts +0 -22
- package/src/commands/config.ts +0 -71
- package/src/commands/frontmatter.ts +0 -51
- package/src/commands/index.ts +0 -76
- package/src/commands/init.ts +0 -43
- package/src/commands/milestone.ts +0 -37
- package/src/commands/phase.ts +0 -92
- package/src/commands/progress.ts +0 -71
- package/src/commands/roadmap.ts +0 -40
- package/src/commands/scaffold.ts +0 -19
- package/src/commands/state.ts +0 -102
- package/src/commands/template.ts +0 -52
- package/src/commands/verify.ts +0 -70
- package/src/commands/workstream.ts +0 -98
- package/src/commands/wxp.ts +0 -65
- package/src/lib/commands.ts +0 -1040
- package/src/lib/config.ts +0 -385
- package/src/lib/core.ts +0 -1167
- package/src/lib/frontmatter.ts +0 -462
- package/src/lib/init.ts +0 -517
- package/src/lib/milestone.ts +0 -290
- package/src/lib/model-profiles.ts +0 -272
- package/src/lib/phase.ts +0 -1012
- package/src/lib/profile-output.ts +0 -237
- package/src/lib/profile-pipeline.ts +0 -556
- package/src/lib/roadmap.ts +0 -378
- package/src/lib/schemas.ts +0 -290
- package/src/lib/security.ts +0 -176
- package/src/lib/state.ts +0 -1175
- package/src/lib/template.ts +0 -246
- package/src/lib/uat.ts +0 -289
- package/src/lib/verify.ts +0 -879
- package/src/lib/workstream.ts +0 -524
- package/src/output.ts +0 -45
- package/src/schemas/pi-gsd-settings.schema.json +0 -80
- package/src/schemas/wxp.xsd +0 -619
- package/src/schemas/wxp.zod.ts +0 -318
- package/src/wxp/__tests__/arguments.test.ts +0 -86
- package/src/wxp/__tests__/conditions.test.ts +0 -106
- package/src/wxp/__tests__/executor.test.ts +0 -95
- package/src/wxp/__tests__/helpers.ts +0 -26
- package/src/wxp/__tests__/integration.test.ts +0 -166
- package/src/wxp/__tests__/new-features.test.ts +0 -222
- package/src/wxp/__tests__/parser.test.ts +0 -159
- package/src/wxp/__tests__/paste.test.ts +0 -66
- package/src/wxp/__tests__/schema.test.ts +0 -120
- package/src/wxp/__tests__/security.test.ts +0 -87
- package/src/wxp/__tests__/shell.test.ts +0 -85
- package/src/wxp/__tests__/string-ops.test.ts +0 -25
- package/src/wxp/__tests__/variables.test.ts +0 -65
- package/src/wxp/arguments.ts +0 -89
- package/src/wxp/conditions.ts +0 -78
- package/src/wxp/executor.ts +0 -191
- package/src/wxp/index.ts +0 -191
- package/src/wxp/parser.ts +0 -198
- package/src/wxp/paste.ts +0 -51
- package/src/wxp/security.ts +0 -102
- package/src/wxp/shell.ts +0 -81
- package/src/wxp/string-ops.ts +0 -44
- package/src/wxp/variables.ts +0 -109
package/src/lib/roadmap.ts
DELETED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* roadmap.ts - Roadmap parsing and update operations.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import {
|
|
8
|
-
comparePhaseNum,
|
|
9
|
-
escapeRegex,
|
|
10
|
-
extractCurrentMilestone,
|
|
11
|
-
findPhaseInternal,
|
|
12
|
-
gsdError,
|
|
13
|
-
loadConfig,
|
|
14
|
-
normalizePhaseName,
|
|
15
|
-
output,
|
|
16
|
-
planningPaths,
|
|
17
|
-
replaceInCurrentMilestone,
|
|
18
|
-
stripShippedMilestones,
|
|
19
|
-
} from "./core.js";
|
|
20
|
-
import { extractFrontmatter } from "./frontmatter.js";
|
|
21
|
-
|
|
22
|
-
export function cmdRoadmapGetPhase(
|
|
23
|
-
cwd: string,
|
|
24
|
-
phaseNum: string | undefined,
|
|
25
|
-
raw: boolean,
|
|
26
|
-
): void {
|
|
27
|
-
const roadmapPath = planningPaths(cwd).roadmap;
|
|
28
|
-
if (!fs.existsSync(roadmapPath)) {
|
|
29
|
-
output({ found: false, error: "ROADMAP.md not found" }, raw, "");
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const content = extractCurrentMilestone(
|
|
34
|
-
fs.readFileSync(roadmapPath, "utf-8"),
|
|
35
|
-
cwd,
|
|
36
|
-
);
|
|
37
|
-
const escapedPhase = escapeRegex(phaseNum ?? "");
|
|
38
|
-
const phasePattern = new RegExp(
|
|
39
|
-
`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
|
|
40
|
-
"i",
|
|
41
|
-
);
|
|
42
|
-
const headerMatch = content.match(phasePattern);
|
|
43
|
-
if (!headerMatch) {
|
|
44
|
-
const checklistPattern = new RegExp(
|
|
45
|
-
`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
|
|
46
|
-
"i",
|
|
47
|
-
);
|
|
48
|
-
const checklistMatch = content.match(checklistPattern);
|
|
49
|
-
if (checklistMatch) {
|
|
50
|
-
output(
|
|
51
|
-
{
|
|
52
|
-
found: false,
|
|
53
|
-
phase_number: phaseNum,
|
|
54
|
-
phase_name: checklistMatch[1].trim(),
|
|
55
|
-
error: "malformed_roadmap",
|
|
56
|
-
message: `Phase ${phaseNum} exists in summary list but missing detail section.`,
|
|
57
|
-
},
|
|
58
|
-
raw,
|
|
59
|
-
"",
|
|
60
|
-
);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
output({ found: false, phase_number: phaseNum }, raw, "");
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const phaseName = headerMatch[1].trim();
|
|
67
|
-
const headerIndex = headerMatch.index!;
|
|
68
|
-
const restOfContent = content.slice(headerIndex);
|
|
69
|
-
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
70
|
-
const sectionEnd = nextHeaderMatch
|
|
71
|
-
? headerIndex + nextHeaderMatch.index!
|
|
72
|
-
: content.length;
|
|
73
|
-
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
74
|
-
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
75
|
-
const criteriaMatch = section.match(
|
|
76
|
-
/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i,
|
|
77
|
-
);
|
|
78
|
-
const success_criteria = criteriaMatch
|
|
79
|
-
? criteriaMatch[1]
|
|
80
|
-
.trim()
|
|
81
|
-
.split("\n")
|
|
82
|
-
.map((l) => l.replace(/^\s*\d+\.\s*/, "").trim())
|
|
83
|
-
.filter(Boolean)
|
|
84
|
-
: [];
|
|
85
|
-
output(
|
|
86
|
-
{
|
|
87
|
-
found: true,
|
|
88
|
-
phase_number: phaseNum,
|
|
89
|
-
phase_name: phaseName,
|
|
90
|
-
goal: goalMatch ? goalMatch[1].trim() : null,
|
|
91
|
-
success_criteria,
|
|
92
|
-
section,
|
|
93
|
-
},
|
|
94
|
-
raw,
|
|
95
|
-
section,
|
|
96
|
-
);
|
|
97
|
-
} catch (e) {
|
|
98
|
-
gsdError("Failed to read ROADMAP.md: " + (e as Error).message);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
interface RoadmapPhaseItem {
|
|
103
|
-
number: string;
|
|
104
|
-
name: string;
|
|
105
|
-
goal: string | null;
|
|
106
|
-
depends_on: string | null;
|
|
107
|
-
plan_count: number;
|
|
108
|
-
summary_count: number;
|
|
109
|
-
has_context: boolean;
|
|
110
|
-
has_research: boolean;
|
|
111
|
-
disk_status: string;
|
|
112
|
-
roadmap_complete: boolean;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function cmdRoadmapAnalyze(cwd: string, raw: boolean): void {
|
|
116
|
-
const roadmapPath = planningPaths(cwd).roadmap;
|
|
117
|
-
if (!fs.existsSync(roadmapPath)) {
|
|
118
|
-
output(
|
|
119
|
-
{
|
|
120
|
-
error: "ROADMAP.md not found",
|
|
121
|
-
milestones: [],
|
|
122
|
-
phases: [],
|
|
123
|
-
current_phase: null,
|
|
124
|
-
},
|
|
125
|
-
raw,
|
|
126
|
-
);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
const rawContent = fs.readFileSync(roadmapPath, "utf-8");
|
|
130
|
-
const content = extractCurrentMilestone(rawContent, cwd);
|
|
131
|
-
const phasesDir = planningPaths(cwd).phases;
|
|
132
|
-
|
|
133
|
-
const phasePattern =
|
|
134
|
-
/#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
|
135
|
-
const phases: RoadmapPhaseItem[] = [];
|
|
136
|
-
let match: RegExpExecArray | null;
|
|
137
|
-
|
|
138
|
-
while ((match = phasePattern.exec(content)) !== null) {
|
|
139
|
-
const phaseNum = match[1],
|
|
140
|
-
phaseName = match[2].replace(/\(INSERTED\)/i, "").trim();
|
|
141
|
-
const sectionStart = match.index;
|
|
142
|
-
const nextHeader = content
|
|
143
|
-
.slice(sectionStart)
|
|
144
|
-
.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
145
|
-
const sectionEnd = nextHeader
|
|
146
|
-
? sectionStart + nextHeader.index!
|
|
147
|
-
: content.length;
|
|
148
|
-
const section = content.slice(sectionStart, sectionEnd);
|
|
149
|
-
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
150
|
-
const dependsMatch = section.match(
|
|
151
|
-
/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i,
|
|
152
|
-
);
|
|
153
|
-
const normalized = normalizePhaseName(phaseNum);
|
|
154
|
-
let diskStatus = "no_directory",
|
|
155
|
-
planCount = 0,
|
|
156
|
-
summaryCount = 0,
|
|
157
|
-
hasContext = false,
|
|
158
|
-
hasResearch = false;
|
|
159
|
-
try {
|
|
160
|
-
const dirs = fs
|
|
161
|
-
.readdirSync(phasesDir, { withFileTypes: true })
|
|
162
|
-
.filter((e) => e.isDirectory())
|
|
163
|
-
.map((e) => e.name);
|
|
164
|
-
const dirMatch = dirs.find(
|
|
165
|
-
(d) => d.startsWith(normalized + "-") || d === normalized,
|
|
166
|
-
);
|
|
167
|
-
if (dirMatch) {
|
|
168
|
-
const phaseFiles = fs.readdirSync(path.join(phasesDir, dirMatch));
|
|
169
|
-
planCount = phaseFiles.filter(
|
|
170
|
-
(f) => f.endsWith("-PLAN.md") || f === "PLAN.md",
|
|
171
|
-
).length;
|
|
172
|
-
summaryCount = phaseFiles.filter(
|
|
173
|
-
(f) => f.endsWith("-SUMMARY.md") || f === "SUMMARY.md",
|
|
174
|
-
).length;
|
|
175
|
-
hasContext = phaseFiles.some(
|
|
176
|
-
(f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md",
|
|
177
|
-
);
|
|
178
|
-
hasResearch = phaseFiles.some(
|
|
179
|
-
(f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md",
|
|
180
|
-
);
|
|
181
|
-
if (summaryCount >= planCount && planCount > 0) diskStatus = "complete";
|
|
182
|
-
else if (summaryCount > 0) diskStatus = "partial";
|
|
183
|
-
else if (planCount > 0) diskStatus = "planned";
|
|
184
|
-
else if (hasResearch) diskStatus = "researched";
|
|
185
|
-
else if (hasContext) diskStatus = "discussed";
|
|
186
|
-
else diskStatus = "empty";
|
|
187
|
-
}
|
|
188
|
-
} catch {
|
|
189
|
-
/* ok */
|
|
190
|
-
}
|
|
191
|
-
const checkboxPattern = new RegExp(
|
|
192
|
-
`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${escapeRegex(phaseNum)}[:\\s]`,
|
|
193
|
-
"i",
|
|
194
|
-
);
|
|
195
|
-
const checkboxMatch = content.match(checkboxPattern);
|
|
196
|
-
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === "x" : false;
|
|
197
|
-
if (roadmapComplete && diskStatus !== "complete") diskStatus = "complete";
|
|
198
|
-
phases.push({
|
|
199
|
-
number: phaseNum,
|
|
200
|
-
name: phaseName,
|
|
201
|
-
goal: goalMatch ? goalMatch[1].trim() : null,
|
|
202
|
-
depends_on: dependsMatch ? dependsMatch[1].trim() : null,
|
|
203
|
-
plan_count: planCount,
|
|
204
|
-
summary_count: summaryCount,
|
|
205
|
-
has_context: hasContext,
|
|
206
|
-
has_research: hasResearch,
|
|
207
|
-
disk_status: diskStatus,
|
|
208
|
-
roadmap_complete: roadmapComplete,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const milestones: Array<{ heading: string; version: string }> = [];
|
|
213
|
-
const milestonePattern = /##\s*(.*v(\d+(?:\.\d+)+)[^(\n]*)/gi;
|
|
214
|
-
let mMatch: RegExpExecArray | null;
|
|
215
|
-
while ((mMatch = milestonePattern.exec(content)) !== null)
|
|
216
|
-
milestones.push({ heading: mMatch[1].trim(), version: "v" + mMatch[2] });
|
|
217
|
-
|
|
218
|
-
const currentPhase =
|
|
219
|
-
phases.find(
|
|
220
|
-
(p) => p.disk_status === "planned" || p.disk_status === "partial",
|
|
221
|
-
) ?? null;
|
|
222
|
-
const nextPhase =
|
|
223
|
-
phases.find((p) =>
|
|
224
|
-
["empty", "no_directory", "discussed", "researched"].includes(
|
|
225
|
-
p.disk_status,
|
|
226
|
-
),
|
|
227
|
-
) ?? null;
|
|
228
|
-
const totalPlans = phases.reduce(
|
|
229
|
-
(s: number, p: { plan_count: number }) => s + p.plan_count,
|
|
230
|
-
0,
|
|
231
|
-
);
|
|
232
|
-
const totalSummaries = phases.reduce(
|
|
233
|
-
(s: number, p: { summary_count: number }) => s + p.summary_count,
|
|
234
|
-
0,
|
|
235
|
-
);
|
|
236
|
-
const completedPhases = phases.filter(
|
|
237
|
-
(p: { disk_status: string }) => p.disk_status === "complete",
|
|
238
|
-
).length;
|
|
239
|
-
|
|
240
|
-
const checklistPattern = /-\s*\[[ x]\]\s*\*\*Phase\s+(\d+[A-Z]?(?:\.\d+)*)/gi;
|
|
241
|
-
const checklistPhases = new Set<string>();
|
|
242
|
-
let clm: RegExpExecArray | null;
|
|
243
|
-
while ((clm = checklistPattern.exec(content)) !== null)
|
|
244
|
-
checklistPhases.add(clm[1]);
|
|
245
|
-
const detailPhases = new Set(phases.map((p: { number: string }) => p.number));
|
|
246
|
-
const missingDetails = [...checklistPhases].filter(
|
|
247
|
-
(p) => !detailPhases.has(p),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
output(
|
|
251
|
-
{
|
|
252
|
-
milestones,
|
|
253
|
-
phases,
|
|
254
|
-
phase_count: phases.length,
|
|
255
|
-
completed_phases: completedPhases,
|
|
256
|
-
total_plans: totalPlans,
|
|
257
|
-
total_summaries: totalSummaries,
|
|
258
|
-
progress_percent:
|
|
259
|
-
totalPlans > 0
|
|
260
|
-
? Math.min(100, Math.round((totalSummaries / totalPlans) * 100))
|
|
261
|
-
: 0,
|
|
262
|
-
current_phase: currentPhase ? currentPhase.number : null,
|
|
263
|
-
next_phase: nextPhase ? nextPhase.number : null,
|
|
264
|
-
missing_phase_details: missingDetails.length > 0 ? missingDetails : null,
|
|
265
|
-
},
|
|
266
|
-
raw,
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export function cmdRoadmapUpdatePlanProgress(
|
|
271
|
-
cwd: string,
|
|
272
|
-
phaseNum: string | undefined,
|
|
273
|
-
raw: boolean,
|
|
274
|
-
): void {
|
|
275
|
-
if (!phaseNum)
|
|
276
|
-
gsdError("phase number required for roadmap update-plan-progress");
|
|
277
|
-
const roadmapPath = planningPaths(cwd).roadmap;
|
|
278
|
-
const phaseInfo = findPhaseInternal(cwd, phaseNum!);
|
|
279
|
-
if (!phaseInfo) gsdError(`Phase ${phaseNum} not found`);
|
|
280
|
-
const planCount = phaseInfo!.plans.length,
|
|
281
|
-
summaryCount = phaseInfo!.summaries.length;
|
|
282
|
-
if (planCount === 0) {
|
|
283
|
-
output(
|
|
284
|
-
{
|
|
285
|
-
updated: false,
|
|
286
|
-
reason: "No plans found",
|
|
287
|
-
plan_count: 0,
|
|
288
|
-
summary_count: 0,
|
|
289
|
-
},
|
|
290
|
-
raw,
|
|
291
|
-
"no plans",
|
|
292
|
-
);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const isComplete = summaryCount >= planCount;
|
|
296
|
-
const status = isComplete
|
|
297
|
-
? "Complete"
|
|
298
|
-
: summaryCount > 0
|
|
299
|
-
? "In Progress"
|
|
300
|
-
: "Planned";
|
|
301
|
-
const today = new Date().toISOString().split("T")[0];
|
|
302
|
-
if (!fs.existsSync(roadmapPath)) {
|
|
303
|
-
output(
|
|
304
|
-
{
|
|
305
|
-
updated: false,
|
|
306
|
-
reason: "ROADMAP.md not found",
|
|
307
|
-
plan_count: planCount,
|
|
308
|
-
summary_count: summaryCount,
|
|
309
|
-
},
|
|
310
|
-
raw,
|
|
311
|
-
"no roadmap",
|
|
312
|
-
);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
let roadmapContent = fs.readFileSync(roadmapPath, "utf-8");
|
|
316
|
-
const phaseEscaped = escapeRegex(phaseNum!);
|
|
317
|
-
const tableRowPattern = new RegExp(
|
|
318
|
-
`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
|
|
319
|
-
"im",
|
|
320
|
-
);
|
|
321
|
-
const dateField = isComplete ? ` ${today} ` : " ";
|
|
322
|
-
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
|
|
323
|
-
const cells = fullRow.split("|").slice(1, -1);
|
|
324
|
-
if (cells.length === 5) {
|
|
325
|
-
cells[2] = ` ${summaryCount}/${planCount} `;
|
|
326
|
-
cells[3] = ` ${status.padEnd(11)}`;
|
|
327
|
-
cells[4] = dateField;
|
|
328
|
-
} else if (cells.length === 4) {
|
|
329
|
-
cells[1] = ` ${summaryCount}/${planCount} `;
|
|
330
|
-
cells[2] = ` ${status.padEnd(11)}`;
|
|
331
|
-
cells[3] = dateField;
|
|
332
|
-
}
|
|
333
|
-
return "|" + cells.join("|") + "|";
|
|
334
|
-
});
|
|
335
|
-
const planCountPattern = new RegExp(
|
|
336
|
-
`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
|
|
337
|
-
"i",
|
|
338
|
-
);
|
|
339
|
-
roadmapContent = replaceInCurrentMilestone(
|
|
340
|
-
roadmapContent,
|
|
341
|
-
planCountPattern,
|
|
342
|
-
`$1${isComplete ? `${summaryCount}/${planCount} plans complete` : `${summaryCount}/${planCount} plans executed`}`,
|
|
343
|
-
);
|
|
344
|
-
if (isComplete) {
|
|
345
|
-
const checkboxPattern = new RegExp(
|
|
346
|
-
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
|
|
347
|
-
"i",
|
|
348
|
-
);
|
|
349
|
-
roadmapContent = replaceInCurrentMilestone(
|
|
350
|
-
roadmapContent,
|
|
351
|
-
checkboxPattern,
|
|
352
|
-
`$1x$2 (completed ${today})`,
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
for (const summaryFile of phaseInfo!.summaries) {
|
|
356
|
-
const planId = summaryFile
|
|
357
|
-
.replace("-SUMMARY.md", "")
|
|
358
|
-
.replace("SUMMARY.md", "");
|
|
359
|
-
if (!planId) continue;
|
|
360
|
-
roadmapContent = roadmapContent.replace(
|
|
361
|
-
new RegExp(`(-\\s*\\[) (\\]\\s*${escapeRegex(planId)})`, "i"),
|
|
362
|
-
"$1x$2",
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
fs.writeFileSync(roadmapPath, roadmapContent, "utf-8");
|
|
366
|
-
output(
|
|
367
|
-
{
|
|
368
|
-
updated: true,
|
|
369
|
-
phase: phaseNum,
|
|
370
|
-
plan_count: planCount,
|
|
371
|
-
summary_count: summaryCount,
|
|
372
|
-
status,
|
|
373
|
-
complete: isComplete,
|
|
374
|
-
},
|
|
375
|
-
raw,
|
|
376
|
-
`${summaryCount}/${planCount} ${status}`,
|
|
377
|
-
);
|
|
378
|
-
}
|
package/src/lib/schemas.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* schemas.ts - Zod schemas for all .planning/ file structures.
|
|
3
|
-
*
|
|
4
|
-
* Covers: STATE.md frontmatter, ROADMAP.md phase entries, PLAN.md frontmatter,
|
|
5
|
-
* UAT.md checkpoint structure, and .planning/config.json.
|
|
6
|
-
*
|
|
7
|
-
* All schemas are permissive (use .passthrough()) to accept 100% of valid
|
|
8
|
-
* GSD v1.30.0 .planning/ files without error, even if they carry extra fields.
|
|
9
|
-
*
|
|
10
|
-
* TypeScript types are exported via z.infer<>.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { z } from "zod";
|
|
14
|
-
|
|
15
|
-
// ─── STATE.md Frontmatter ─────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Schema for STATE.md frontmatter key-value fields.
|
|
19
|
-
*
|
|
20
|
-
* STATE.md uses a bespoke "key: value" format (not strict YAML), parsed via
|
|
21
|
-
* stateExtractField(). All fields are optional strings - the file may contain
|
|
22
|
-
* only a subset depending on project state.
|
|
23
|
-
*/
|
|
24
|
-
export const StateFrontmatterSchema = z
|
|
25
|
-
.object({
|
|
26
|
-
/** Active milestone version (e.g. "1.0.0") */
|
|
27
|
-
milestone: z.string().optional(),
|
|
28
|
-
/** Human-readable milestone name */
|
|
29
|
-
milestone_name: z.string().optional(),
|
|
30
|
-
/** Current phase number (e.g. "3", "3.1") */
|
|
31
|
-
current_phase: z.string().optional(),
|
|
32
|
-
/** Descriptive name of the current phase */
|
|
33
|
-
current_phase_name: z.string().optional(),
|
|
34
|
-
/** Current plan filename (e.g. "001-PLAN.md") */
|
|
35
|
-
current_plan: z.string().optional(),
|
|
36
|
-
/** Total number of phases in the milestone */
|
|
37
|
-
total_phases: z.coerce.number().int().nonnegative().optional(),
|
|
38
|
-
/** Total number of plans within the current phase */
|
|
39
|
-
total_plans_in_phase: z.coerce.number().int().nonnegative().optional(),
|
|
40
|
-
/**
|
|
41
|
-
* Workflow status - free-text field.
|
|
42
|
-
* Common values: "In Progress", "Paused", "Stopped", "Complete".
|
|
43
|
-
*/
|
|
44
|
-
status: z.string().optional(),
|
|
45
|
-
/** Percentage completion (e.g. "42%") */
|
|
46
|
-
progress: z.string().optional(),
|
|
47
|
-
/** ISO-8601 timestamp or human-readable last-activity string */
|
|
48
|
-
last_activity: z.string().optional(),
|
|
49
|
-
/** ISO-8601 or human-readable timestamp when work was paused */
|
|
50
|
-
paused_at: z.string().optional(),
|
|
51
|
-
/** ISO-8601 or human-readable timestamp when work was stopped */
|
|
52
|
-
stopped_at: z.string().optional(),
|
|
53
|
-
})
|
|
54
|
-
.passthrough();
|
|
55
|
-
|
|
56
|
-
export type StateFrontmatter = z.infer<typeof StateFrontmatterSchema>;
|
|
57
|
-
|
|
58
|
-
// ─── ROADMAP.md Phase Entry ───────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Schema for a single parsed phase entry from ROADMAP.md.
|
|
62
|
-
*
|
|
63
|
-
* This represents the structured data returned by cmdRoadmapGetPhase() rather
|
|
64
|
-
* than raw markdown - the roadmap file itself is free-form markdown.
|
|
65
|
-
*/
|
|
66
|
-
export const RoadmapPhaseEntrySchema = z
|
|
67
|
-
.object({
|
|
68
|
-
/** Indicates a successful lookup */
|
|
69
|
-
found: z.literal(true),
|
|
70
|
-
/** Phase identifier, e.g. "3", "3.1", "10B" */
|
|
71
|
-
phase_number: z.string(),
|
|
72
|
-
/** Human-readable phase title */
|
|
73
|
-
phase_name: z.string(),
|
|
74
|
-
/** One-line goal statement extracted from **Goal:** line (null if absent) */
|
|
75
|
-
goal: z.string().nullable(),
|
|
76
|
-
/** Ordered success criteria items */
|
|
77
|
-
success_criteria: z.array(z.string()).default([]),
|
|
78
|
-
/** Raw markdown section text (for debugging/display) */
|
|
79
|
-
section: z.string().optional(),
|
|
80
|
-
})
|
|
81
|
-
.passthrough();
|
|
82
|
-
|
|
83
|
-
export type RoadmapPhaseEntry = z.infer<typeof RoadmapPhaseEntrySchema>;
|
|
84
|
-
|
|
85
|
-
// ─── PLAN.md Frontmatter ──────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Schema for PLAN.md YAML frontmatter (written by /gsd-plan-phase).
|
|
89
|
-
*
|
|
90
|
-
* GSD v1.30.0 requires these 8 fields. Additional fields may be present
|
|
91
|
-
* (handled by .passthrough()).
|
|
92
|
-
*/
|
|
93
|
-
export const PlanFrontmatterSchema = z
|
|
94
|
-
.object({
|
|
95
|
-
/** Phase number this plan belongs to */
|
|
96
|
-
phase: z.union([z.string(), z.number()]),
|
|
97
|
-
/** Plan slug / short identifier (e.g. "001") */
|
|
98
|
-
plan: z.union([z.string(), z.number()]),
|
|
99
|
-
/**
|
|
100
|
-
* Plan type.
|
|
101
|
-
* Common values: "implementation", "research", "verification", "ui".
|
|
102
|
-
*/
|
|
103
|
-
type: z.string(),
|
|
104
|
-
/**
|
|
105
|
-
* Execution wave for parallelization (e.g. 1, 2, "1").
|
|
106
|
-
* Wave 1 plans may run in parallel; later waves depend on earlier ones.
|
|
107
|
-
*/
|
|
108
|
-
wave: z.union([z.string(), z.number()]),
|
|
109
|
-
/**
|
|
110
|
-
* Dependency list - plan slugs that must complete before this plan runs.
|
|
111
|
-
* Can be an empty string, a comma-separated list, or an array.
|
|
112
|
-
*/
|
|
113
|
-
depends_on: z.union([z.string(), z.array(z.string())]),
|
|
114
|
-
/**
|
|
115
|
-
* Files this plan intends to create or modify.
|
|
116
|
-
* Can be an empty string, comma-separated list, or array.
|
|
117
|
-
*/
|
|
118
|
-
files_modified: z.union([z.string(), z.array(z.string())]),
|
|
119
|
-
/** Whether this plan can run without human intervention */
|
|
120
|
-
autonomous: z.union([z.boolean(), z.string()]),
|
|
121
|
-
/**
|
|
122
|
-
* Must-have deliverables / acceptance items.
|
|
123
|
-
* Can be an empty string, a comma-separated list, or an array.
|
|
124
|
-
*/
|
|
125
|
-
must_haves: z.union([z.string(), z.array(z.string())]),
|
|
126
|
-
})
|
|
127
|
-
.passthrough();
|
|
128
|
-
|
|
129
|
-
export type PlanFrontmatter = z.infer<typeof PlanFrontmatterSchema>;
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Schema for SUMMARY.md frontmatter (produced after phase execution).
|
|
133
|
-
*/
|
|
134
|
-
export const SummaryFrontmatterSchema = z
|
|
135
|
-
.object({
|
|
136
|
-
/** Phase number (string or numeric) */
|
|
137
|
-
phase: z.union([z.string(), z.number()]),
|
|
138
|
-
/** Plan slug */
|
|
139
|
-
plan: z.union([z.string(), z.number()]),
|
|
140
|
-
/** Subsystem or area this plan affected */
|
|
141
|
-
subsystem: z.string(),
|
|
142
|
-
/** Comma-separated tags or array */
|
|
143
|
-
tags: z.union([z.string(), z.array(z.string())]),
|
|
144
|
-
/** Human-readable duration string (e.g. "45m", "2h") */
|
|
145
|
-
duration: z.string(),
|
|
146
|
-
/** ISO-8601 completion timestamp or human-readable date */
|
|
147
|
-
completed: z.string(),
|
|
148
|
-
})
|
|
149
|
-
.passthrough();
|
|
150
|
-
|
|
151
|
-
export type SummaryFrontmatter = z.infer<typeof SummaryFrontmatterSchema>;
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Schema for VERIFICATION.md frontmatter.
|
|
155
|
-
*/
|
|
156
|
-
export const VerificationFrontmatterSchema = z
|
|
157
|
-
.object({
|
|
158
|
-
/** Phase number */
|
|
159
|
-
phase: z.union([z.string(), z.number()]),
|
|
160
|
-
/** Whether verification passed */
|
|
161
|
-
verified: z.union([z.boolean(), z.string()]),
|
|
162
|
-
/** Verification status label (e.g. "pass", "fail", "partial") */
|
|
163
|
-
status: z.string(),
|
|
164
|
-
/** Numeric score (0–100) */
|
|
165
|
-
score: z.union([z.number(), z.string()]),
|
|
166
|
-
})
|
|
167
|
-
.passthrough();
|
|
168
|
-
|
|
169
|
-
export type VerificationFrontmatter = z.infer<
|
|
170
|
-
typeof VerificationFrontmatterSchema
|
|
171
|
-
>;
|
|
172
|
-
|
|
173
|
-
// ─── UAT.md Checkpoint Structure ─────────────────────────────────────────────
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Schema for a single UAT checkpoint item parsed from a -UAT.md file.
|
|
177
|
-
*
|
|
178
|
-
* Matches the UatItem interface used by cmdAuditUat().
|
|
179
|
-
*/
|
|
180
|
-
export const UatCheckpointSchema = z
|
|
181
|
-
.object({
|
|
182
|
-
/** Optional numeric test index (1-based) */
|
|
183
|
-
test: z.number().int().positive().optional(),
|
|
184
|
-
/** Human-readable test name */
|
|
185
|
-
name: z.string(),
|
|
186
|
-
/** Expected outcome description */
|
|
187
|
-
expected: z.string().optional(),
|
|
188
|
-
/** Actual result ("pass", "fail", "blocked", "pending", etc.) */
|
|
189
|
-
result: z.string(),
|
|
190
|
-
/** Grouping category for this test */
|
|
191
|
-
category: z.string(),
|
|
192
|
-
/** Optional reason if test failed or is blocked */
|
|
193
|
-
reason: z.string().optional(),
|
|
194
|
-
/** Optional reference to blocking issue/phase */
|
|
195
|
-
blocked_by: z.string().optional(),
|
|
196
|
-
})
|
|
197
|
-
.passthrough();
|
|
198
|
-
|
|
199
|
-
export type UatCheckpoint = z.infer<typeof UatCheckpointSchema>;
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Schema for the full structured UAT audit result (as returned by pi-gsd-tools).
|
|
203
|
-
*/
|
|
204
|
-
export const UatAuditResultSchema = z
|
|
205
|
-
.object({
|
|
206
|
-
phase: z.string(),
|
|
207
|
-
phase_dir: z.string(),
|
|
208
|
-
file: z.string(),
|
|
209
|
-
file_path: z.string(),
|
|
210
|
-
type: z.literal("uat"),
|
|
211
|
-
status: z.string(),
|
|
212
|
-
items: z.array(UatCheckpointSchema),
|
|
213
|
-
})
|
|
214
|
-
.passthrough();
|
|
215
|
-
|
|
216
|
-
export type UatAuditResult = z.infer<typeof UatAuditResultSchema>;
|
|
217
|
-
|
|
218
|
-
// ─── .planning/config.json ────────────────────────────────────────────────────
|
|
219
|
-
|
|
220
|
-
const GitConfigSchema = z
|
|
221
|
-
.object({
|
|
222
|
-
branching_strategy: z
|
|
223
|
-
.enum(["none", "phase", "milestone", "workstream"])
|
|
224
|
-
.default("none"),
|
|
225
|
-
phase_branch_template: z.string().default("gsd/phase-{phase}-{slug}"),
|
|
226
|
-
milestone_branch_template: z.string().default("gsd/{milestone}-{slug}"),
|
|
227
|
-
quick_branch_template: z.string().nullable().default(null),
|
|
228
|
-
})
|
|
229
|
-
.passthrough();
|
|
230
|
-
|
|
231
|
-
const WorkflowConfigSchema = z
|
|
232
|
-
.object({
|
|
233
|
-
research: z.boolean().default(true),
|
|
234
|
-
plan_check: z.boolean().default(true),
|
|
235
|
-
verifier: z.boolean().default(true),
|
|
236
|
-
nyquist_validation: z.boolean().default(true),
|
|
237
|
-
auto_advance: z.boolean().default(false),
|
|
238
|
-
node_repair: z.boolean().default(true),
|
|
239
|
-
node_repair_budget: z.number().int().nonnegative().default(2),
|
|
240
|
-
auto_retry_audit: z.boolean().default(true),
|
|
241
|
-
auto_retry_audit_budget: z.number().int().nonnegative().default(1),
|
|
242
|
-
auto_retry_tech_debt: z.boolean().default(true),
|
|
243
|
-
auto_retry_tech_debt_budget: z.number().int().nonnegative().default(1),
|
|
244
|
-
ui_phase: z.boolean().default(true),
|
|
245
|
-
ui_safety_gate: z.boolean().default(true),
|
|
246
|
-
text_mode: z.boolean().default(false),
|
|
247
|
-
research_before_questions: z.boolean().default(false),
|
|
248
|
-
discuss_mode: z.string().default("discuss"),
|
|
249
|
-
skip_discuss: z.boolean().default(false),
|
|
250
|
-
_auto_chain_active: z.boolean().default(false),
|
|
251
|
-
})
|
|
252
|
-
.passthrough();
|
|
253
|
-
|
|
254
|
-
const HooksConfigSchema = z
|
|
255
|
-
.object({
|
|
256
|
-
context_warnings: z.boolean().default(true),
|
|
257
|
-
workflow_guard: z.boolean().default(false),
|
|
258
|
-
})
|
|
259
|
-
.passthrough();
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Schema for .planning/config.json.
|
|
263
|
-
*
|
|
264
|
-
* Generated by /gsd-new-project (cmdConfigNewProject) and persisted for the
|
|
265
|
-
* lifetime of the project. The outer object uses .passthrough() so that
|
|
266
|
-
* user-added agent_skills entries and future GSD keys are accepted.
|
|
267
|
-
*/
|
|
268
|
-
export const PlanningConfigSchema = z
|
|
269
|
-
.object({
|
|
270
|
-
model_profile: z
|
|
271
|
-
.enum(["quality", "balanced", "budget", "inherit"])
|
|
272
|
-
.default("balanced"),
|
|
273
|
-
commit_docs: z.boolean().default(true),
|
|
274
|
-
parallelization: z.boolean().default(true),
|
|
275
|
-
search_gitignored: z.boolean().default(false),
|
|
276
|
-
brave_search: z.boolean().default(false),
|
|
277
|
-
firecrawl: z.boolean().default(false),
|
|
278
|
-
exa_search: z.boolean().default(false),
|
|
279
|
-
git: GitConfigSchema.default({}),
|
|
280
|
-
workflow: WorkflowConfigSchema.default({}),
|
|
281
|
-
hooks: HooksConfigSchema.default({}),
|
|
282
|
-
/** Map of agent-type → enabled flag or config object */
|
|
283
|
-
agent_skills: z.record(z.string(), z.unknown()).default({}),
|
|
284
|
-
})
|
|
285
|
-
.passthrough();
|
|
286
|
-
|
|
287
|
-
export type PlanningConfig = z.infer<typeof PlanningConfigSchema>;
|
|
288
|
-
export type GitConfig = z.infer<typeof GitConfigSchema>;
|
|
289
|
-
export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>;
|
|
290
|
-
export type HooksConfig = z.infer<typeof HooksConfigSchema>;
|