pi-gsd 2.0.1 → 2.0.3
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 +1533 -0
- package/dist/pi-gsd-tools.js +53 -52
- 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/milestone.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* milestone.ts - Milestone and requirements lifecycle operations.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import path from "path";
|
|
7
|
-
import {
|
|
8
|
-
escapeRegex,
|
|
9
|
-
extractOneLinerFromBody,
|
|
10
|
-
getMilestonePhaseFilter,
|
|
11
|
-
gsdError,
|
|
12
|
-
normalizeMd,
|
|
13
|
-
output,
|
|
14
|
-
planningPaths,
|
|
15
|
-
} from "./core.js";
|
|
16
|
-
import { extractFrontmatter, asStr } from "./frontmatter.js";
|
|
17
|
-
import { stateReplaceFieldWithFallback, writeStateMd } from "./state.js";
|
|
18
|
-
|
|
19
|
-
export function cmdRequirementsMarkComplete(
|
|
20
|
-
cwd: string,
|
|
21
|
-
reqIdsRaw: string[],
|
|
22
|
-
raw: boolean,
|
|
23
|
-
): void {
|
|
24
|
-
if (!reqIdsRaw || reqIdsRaw.length === 0)
|
|
25
|
-
gsdError("requirement IDs required.");
|
|
26
|
-
const reqIds = reqIdsRaw
|
|
27
|
-
.join(" ")
|
|
28
|
-
.replace(/[[\]]/g, "")
|
|
29
|
-
.split(/[,\s]+/)
|
|
30
|
-
.map((r) => r.trim())
|
|
31
|
-
.filter(Boolean);
|
|
32
|
-
if (reqIds.length === 0) gsdError("no valid requirement IDs found");
|
|
33
|
-
const reqPath = planningPaths(cwd).requirements;
|
|
34
|
-
if (!fs.existsSync(reqPath)) {
|
|
35
|
-
output(
|
|
36
|
-
{ updated: false, reason: "REQUIREMENTS.md not found", ids: reqIds },
|
|
37
|
-
raw,
|
|
38
|
-
"no requirements file",
|
|
39
|
-
);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
let reqContent = fs.readFileSync(reqPath, "utf-8");
|
|
43
|
-
const updated: string[] = [],
|
|
44
|
-
alreadyComplete: string[] = [],
|
|
45
|
-
notFound: string[] = [];
|
|
46
|
-
for (const reqId of reqIds) {
|
|
47
|
-
let found = false;
|
|
48
|
-
const esc = escapeRegex(reqId);
|
|
49
|
-
const checkboxPattern = new RegExp(
|
|
50
|
-
`(-\\s*\\[)[ ](\\]\\s*\\*\\*${esc}\\*\\*)`,
|
|
51
|
-
"gi",
|
|
52
|
-
);
|
|
53
|
-
if (checkboxPattern.test(reqContent)) {
|
|
54
|
-
reqContent = reqContent.replace(
|
|
55
|
-
new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${esc}\\*\\*)`, "gi"),
|
|
56
|
-
"$1x$2",
|
|
57
|
-
);
|
|
58
|
-
found = true;
|
|
59
|
-
}
|
|
60
|
-
const tablePattern = new RegExp(
|
|
61
|
-
`(\\|\\s*${esc}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`,
|
|
62
|
-
"gi",
|
|
63
|
-
);
|
|
64
|
-
if (tablePattern.test(reqContent)) {
|
|
65
|
-
reqContent = reqContent.replace(
|
|
66
|
-
new RegExp(`(\\|\\s*${esc}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, "gi"),
|
|
67
|
-
"$1 Complete $2",
|
|
68
|
-
);
|
|
69
|
-
found = true;
|
|
70
|
-
}
|
|
71
|
-
if (found) {
|
|
72
|
-
updated.push(reqId);
|
|
73
|
-
} else if (
|
|
74
|
-
new RegExp(`-\\s*\\[x\\]\\s*\\*\\*${esc}\\*\\*`, "gi").test(reqContent) ||
|
|
75
|
-
new RegExp(`\\|\\s*${esc}\\s*\\|[^|]+\\|\\s*Complete\\s*\\|`, "gi").test(
|
|
76
|
-
reqContent,
|
|
77
|
-
)
|
|
78
|
-
) {
|
|
79
|
-
alreadyComplete.push(reqId);
|
|
80
|
-
} else {
|
|
81
|
-
notFound.push(reqId);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (updated.length > 0) fs.writeFileSync(reqPath, reqContent, "utf-8");
|
|
85
|
-
output(
|
|
86
|
-
{
|
|
87
|
-
updated: updated.length > 0,
|
|
88
|
-
marked_complete: updated,
|
|
89
|
-
already_complete: alreadyComplete,
|
|
90
|
-
not_found: notFound,
|
|
91
|
-
total: reqIds.length,
|
|
92
|
-
},
|
|
93
|
-
raw,
|
|
94
|
-
`${updated.length}/${reqIds.length} requirements marked complete`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function cmdMilestoneComplete(
|
|
99
|
-
cwd: string,
|
|
100
|
-
version: string | undefined,
|
|
101
|
-
options: { name?: string | null; archivePhases?: boolean },
|
|
102
|
-
raw: boolean,
|
|
103
|
-
): void {
|
|
104
|
-
if (!version)
|
|
105
|
-
gsdError("version required for milestone complete (e.g., v1.0)");
|
|
106
|
-
const roadmapPath = planningPaths(cwd).roadmap;
|
|
107
|
-
const reqPath = planningPaths(cwd).requirements;
|
|
108
|
-
const statePath = planningPaths(cwd).state;
|
|
109
|
-
const milestonesPath = path.join(cwd, ".planning", "MILESTONES.md");
|
|
110
|
-
const archiveDir = path.join(cwd, ".planning", "milestones");
|
|
111
|
-
const phasesDir = planningPaths(cwd).phases;
|
|
112
|
-
const today = new Date().toISOString().split("T")[0];
|
|
113
|
-
const milestoneName = options.name || version;
|
|
114
|
-
fs.mkdirSync(archiveDir, { recursive: true });
|
|
115
|
-
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
|
116
|
-
let phaseCount = 0,
|
|
117
|
-
totalPlans = 0,
|
|
118
|
-
totalTasks = 0;
|
|
119
|
-
const accomplishments: string[] = [];
|
|
120
|
-
try {
|
|
121
|
-
const dirs = fs
|
|
122
|
-
.readdirSync(phasesDir, { withFileTypes: true })
|
|
123
|
-
.filter((e) => e.isDirectory())
|
|
124
|
-
.map((e) => e.name)
|
|
125
|
-
.sort();
|
|
126
|
-
for (const dir of dirs) {
|
|
127
|
-
if (!isDirInMilestone(dir)) continue;
|
|
128
|
-
phaseCount++;
|
|
129
|
-
const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
|
|
130
|
-
const plans = phaseFiles.filter(
|
|
131
|
-
(f) => f.endsWith("-PLAN.md") || f === "PLAN.md",
|
|
132
|
-
);
|
|
133
|
-
const summaries = phaseFiles.filter(
|
|
134
|
-
(f) => f.endsWith("-SUMMARY.md") || f === "SUMMARY.md",
|
|
135
|
-
);
|
|
136
|
-
totalPlans += plans.length;
|
|
137
|
-
for (const s of summaries) {
|
|
138
|
-
try {
|
|
139
|
-
const content = fs.readFileSync(
|
|
140
|
-
path.join(phasesDir, dir, s),
|
|
141
|
-
"utf-8",
|
|
142
|
-
);
|
|
143
|
-
const fm = extractFrontmatter(content);
|
|
144
|
-
const oneLiner = asStr(fm["one-liner"]) ?? extractOneLinerFromBody(content);
|
|
145
|
-
if (oneLiner) accomplishments.push(oneLiner);
|
|
146
|
-
const tasksFieldMatch = content.match(/\*\*Tasks:\*\*\s*(\d+)/);
|
|
147
|
-
if (tasksFieldMatch) {
|
|
148
|
-
totalTasks += parseInt(tasksFieldMatch[1], 10);
|
|
149
|
-
} else {
|
|
150
|
-
const xmlTaskMatches = content.match(/<task[\s>]/gi) || [];
|
|
151
|
-
const mdTaskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
|
|
152
|
-
totalTasks += xmlTaskMatches.length || mdTaskMatches.length;
|
|
153
|
-
}
|
|
154
|
-
} catch {
|
|
155
|
-
/* ok */
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
} catch {
|
|
160
|
-
/* ok */
|
|
161
|
-
}
|
|
162
|
-
if (fs.existsSync(roadmapPath))
|
|
163
|
-
fs.writeFileSync(
|
|
164
|
-
path.join(archiveDir, `${version}-ROADMAP.md`),
|
|
165
|
-
fs.readFileSync(roadmapPath, "utf-8"),
|
|
166
|
-
"utf-8",
|
|
167
|
-
);
|
|
168
|
-
if (fs.existsSync(reqPath))
|
|
169
|
-
fs.writeFileSync(
|
|
170
|
-
path.join(archiveDir, `${version}-REQUIREMENTS.md`),
|
|
171
|
-
`# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\n---\n\n` +
|
|
172
|
-
fs.readFileSync(reqPath, "utf-8"),
|
|
173
|
-
"utf-8",
|
|
174
|
-
);
|
|
175
|
-
const auditFile = path.join(
|
|
176
|
-
cwd,
|
|
177
|
-
".planning",
|
|
178
|
-
`${version}-MILESTONE-AUDIT.md`,
|
|
179
|
-
);
|
|
180
|
-
if (fs.existsSync(auditFile))
|
|
181
|
-
fs.renameSync(
|
|
182
|
-
auditFile,
|
|
183
|
-
path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`),
|
|
184
|
-
);
|
|
185
|
-
const accomplishmentsList = accomplishments.map((a) => `- ${a}`).join("\n");
|
|
186
|
-
const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || "- (none recorded)"}\n\n---\n\n`;
|
|
187
|
-
if (fs.existsSync(milestonesPath)) {
|
|
188
|
-
const existing = fs.readFileSync(milestonesPath, "utf-8");
|
|
189
|
-
if (!existing.trim()) {
|
|
190
|
-
fs.writeFileSync(
|
|
191
|
-
milestonesPath,
|
|
192
|
-
normalizeMd(`# Milestones\n\n${milestoneEntry}`),
|
|
193
|
-
"utf-8",
|
|
194
|
-
);
|
|
195
|
-
} else {
|
|
196
|
-
const headerMatch = existing.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
|
|
197
|
-
if (headerMatch)
|
|
198
|
-
fs.writeFileSync(
|
|
199
|
-
milestonesPath,
|
|
200
|
-
normalizeMd(
|
|
201
|
-
headerMatch[1] +
|
|
202
|
-
milestoneEntry +
|
|
203
|
-
existing.slice(headerMatch[1].length),
|
|
204
|
-
),
|
|
205
|
-
"utf-8",
|
|
206
|
-
);
|
|
207
|
-
else
|
|
208
|
-
fs.writeFileSync(
|
|
209
|
-
milestonesPath,
|
|
210
|
-
normalizeMd(milestoneEntry + existing),
|
|
211
|
-
"utf-8",
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
} else {
|
|
215
|
-
fs.writeFileSync(
|
|
216
|
-
milestonesPath,
|
|
217
|
-
normalizeMd(`# Milestones\n\n${milestoneEntry}`),
|
|
218
|
-
"utf-8",
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
if (fs.existsSync(statePath)) {
|
|
222
|
-
let stateContent = fs.readFileSync(statePath, "utf-8");
|
|
223
|
-
stateContent = stateReplaceFieldWithFallback(
|
|
224
|
-
stateContent,
|
|
225
|
-
"Status",
|
|
226
|
-
null,
|
|
227
|
-
`${version} milestone complete`,
|
|
228
|
-
);
|
|
229
|
-
stateContent = stateReplaceFieldWithFallback(
|
|
230
|
-
stateContent,
|
|
231
|
-
"Last Activity",
|
|
232
|
-
"Last activity",
|
|
233
|
-
today,
|
|
234
|
-
);
|
|
235
|
-
stateContent = stateReplaceFieldWithFallback(
|
|
236
|
-
stateContent,
|
|
237
|
-
"Last Activity Description",
|
|
238
|
-
null,
|
|
239
|
-
`${version} milestone completed and archived`,
|
|
240
|
-
);
|
|
241
|
-
writeStateMd(statePath, stateContent, cwd);
|
|
242
|
-
}
|
|
243
|
-
let phasesArchived = false;
|
|
244
|
-
if (options.archivePhases) {
|
|
245
|
-
try {
|
|
246
|
-
const phaseArchiveDir = path.join(archiveDir, `${version}-phases`);
|
|
247
|
-
fs.mkdirSync(phaseArchiveDir, { recursive: true });
|
|
248
|
-
const dirs = fs
|
|
249
|
-
.readdirSync(phasesDir, { withFileTypes: true })
|
|
250
|
-
.filter((e) => e.isDirectory())
|
|
251
|
-
.map((e) => e.name);
|
|
252
|
-
let count = 0;
|
|
253
|
-
for (const dir of dirs) {
|
|
254
|
-
if (!isDirInMilestone(dir)) continue;
|
|
255
|
-
fs.renameSync(
|
|
256
|
-
path.join(phasesDir, dir),
|
|
257
|
-
path.join(phaseArchiveDir, dir),
|
|
258
|
-
);
|
|
259
|
-
count++;
|
|
260
|
-
}
|
|
261
|
-
phasesArchived = count > 0;
|
|
262
|
-
} catch {
|
|
263
|
-
/* ok */
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
output(
|
|
267
|
-
{
|
|
268
|
-
version,
|
|
269
|
-
name: milestoneName,
|
|
270
|
-
date: today,
|
|
271
|
-
phases: phaseCount,
|
|
272
|
-
plans: totalPlans,
|
|
273
|
-
tasks: totalTasks,
|
|
274
|
-
accomplishments,
|
|
275
|
-
archived: {
|
|
276
|
-
roadmap: fs.existsSync(path.join(archiveDir, `${version}-ROADMAP.md`)),
|
|
277
|
-
requirements: fs.existsSync(
|
|
278
|
-
path.join(archiveDir, `${version}-REQUIREMENTS.md`),
|
|
279
|
-
),
|
|
280
|
-
audit: fs.existsSync(
|
|
281
|
-
path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`),
|
|
282
|
-
),
|
|
283
|
-
phases: phasesArchived,
|
|
284
|
-
},
|
|
285
|
-
milestones_updated: true,
|
|
286
|
-
state_updated: fs.existsSync(statePath),
|
|
287
|
-
},
|
|
288
|
-
raw,
|
|
289
|
-
);
|
|
290
|
-
}
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* model-profiles.ts - MODEL_PROFILES data + resolution + markdown generation.
|
|
3
|
-
*
|
|
4
|
-
* This is the single source of truth for model profiles. The markdown reference
|
|
5
|
-
* file at `gsd/references/model-profiles.md (pi) or get-shit-done/references/model-profiles.md (other harnesses)` is auto-generated from
|
|
6
|
-
* this data via:
|
|
7
|
-
*
|
|
8
|
-
* node dist/gsd-tools.js generate-model-profiles-md [--harness <name>]
|
|
9
|
-
*
|
|
10
|
-
* Do NOT edit `references/model-profiles.md` by hand - changes will be overwritten.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export type ModelAlias = "opus" | "sonnet" | "haiku" | "inherit";
|
|
14
|
-
export type ProfileKey = "quality" | "balanced" | "budget";
|
|
15
|
-
export type AgentName = string;
|
|
16
|
-
|
|
17
|
-
export interface AgentProfile {
|
|
18
|
-
quality: ModelAlias;
|
|
19
|
-
balanced: ModelAlias;
|
|
20
|
-
budget: ModelAlias;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const MODEL_PROFILES: Record<AgentName, AgentProfile> = {
|
|
24
|
-
"gsd-planner": { quality: "opus", balanced: "opus", budget: "sonnet" },
|
|
25
|
-
"gsd-roadmapper": { quality: "opus", balanced: "sonnet", budget: "sonnet" },
|
|
26
|
-
"gsd-executor": { quality: "opus", balanced: "sonnet", budget: "sonnet" },
|
|
27
|
-
"gsd-phase-researcher": {
|
|
28
|
-
quality: "opus",
|
|
29
|
-
balanced: "sonnet",
|
|
30
|
-
budget: "haiku",
|
|
31
|
-
},
|
|
32
|
-
"gsd-project-researcher": {
|
|
33
|
-
quality: "opus",
|
|
34
|
-
balanced: "sonnet",
|
|
35
|
-
budget: "haiku",
|
|
36
|
-
},
|
|
37
|
-
"gsd-research-synthesizer": {
|
|
38
|
-
quality: "sonnet",
|
|
39
|
-
balanced: "sonnet",
|
|
40
|
-
budget: "haiku",
|
|
41
|
-
},
|
|
42
|
-
"gsd-debugger": { quality: "opus", balanced: "sonnet", budget: "sonnet" },
|
|
43
|
-
"gsd-codebase-mapper": {
|
|
44
|
-
quality: "sonnet",
|
|
45
|
-
balanced: "haiku",
|
|
46
|
-
budget: "haiku",
|
|
47
|
-
},
|
|
48
|
-
"gsd-verifier": { quality: "sonnet", balanced: "sonnet", budget: "haiku" },
|
|
49
|
-
"gsd-plan-checker": {
|
|
50
|
-
quality: "sonnet",
|
|
51
|
-
balanced: "sonnet",
|
|
52
|
-
budget: "haiku",
|
|
53
|
-
},
|
|
54
|
-
"gsd-integration-checker": {
|
|
55
|
-
quality: "sonnet",
|
|
56
|
-
balanced: "sonnet",
|
|
57
|
-
budget: "haiku",
|
|
58
|
-
},
|
|
59
|
-
"gsd-nyquist-auditor": {
|
|
60
|
-
quality: "sonnet",
|
|
61
|
-
balanced: "sonnet",
|
|
62
|
-
budget: "haiku",
|
|
63
|
-
},
|
|
64
|
-
"gsd-ui-researcher": { quality: "opus", balanced: "sonnet", budget: "haiku" },
|
|
65
|
-
"gsd-ui-checker": { quality: "sonnet", balanced: "sonnet", budget: "haiku" },
|
|
66
|
-
"gsd-ui-auditor": { quality: "sonnet", balanced: "sonnet", budget: "haiku" },
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export const VALID_PROFILES: ProfileKey[] = Object.keys(
|
|
70
|
-
MODEL_PROFILES["gsd-planner"],
|
|
71
|
-
) as ProfileKey[];
|
|
72
|
-
|
|
73
|
-
// ─── Pi harness constants (pi-only) ─────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
const PI_CMD_PREFIX = "/gsd-";
|
|
76
|
-
const PI_RUNTIME = "pi";
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
export function getAgentToModelMapForProfile(
|
|
80
|
-
profile: ProfileKey,
|
|
81
|
-
): Record<AgentName, ModelAlias> {
|
|
82
|
-
const result: Record<AgentName, ModelAlias> = {};
|
|
83
|
-
for (const [agent, profiles] of Object.entries(MODEL_PROFILES)) {
|
|
84
|
-
result[agent] = profiles[profile];
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Formats an agent→model map as a human-readable table string.
|
|
91
|
-
*/
|
|
92
|
-
export function formatAgentToModelMapAsTable(
|
|
93
|
-
map: Record<AgentName, ModelAlias>,
|
|
94
|
-
): string {
|
|
95
|
-
const agentWidth = Math.max(
|
|
96
|
-
"Agent".length,
|
|
97
|
-
...Object.keys(map).map((a) => a.length),
|
|
98
|
-
);
|
|
99
|
-
const modelWidth = Math.max(
|
|
100
|
-
"Model".length,
|
|
101
|
-
...Object.values(map).map((m) => m.length),
|
|
102
|
-
);
|
|
103
|
-
const sep = "─".repeat(agentWidth + 2) + "┼" + "─".repeat(modelWidth + 2);
|
|
104
|
-
const header =
|
|
105
|
-
" " + "Agent".padEnd(agentWidth) + " │ " + "Model".padEnd(modelWidth);
|
|
106
|
-
let table = header + "\n" + sep + "\n";
|
|
107
|
-
for (const [agent, model] of Object.entries(map)) {
|
|
108
|
-
table +=
|
|
109
|
-
" " + agent.padEnd(agentWidth) + " │ " + model.padEnd(modelWidth) + "\n";
|
|
110
|
-
}
|
|
111
|
-
return table;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Generate the full `references/model-profiles.md` content.
|
|
116
|
-
*/
|
|
117
|
-
export function generateModelProfilesMd(): string {
|
|
118
|
-
const runtimeName = PI_RUNTIME;
|
|
119
|
-
const cmdPrefix = PI_CMD_PREFIX;
|
|
120
|
-
|
|
121
|
-
const profiles = VALID_PROFILES;
|
|
122
|
-
const agents = Object.keys(MODEL_PROFILES);
|
|
123
|
-
|
|
124
|
-
const headerCols = [
|
|
125
|
-
"Agent",
|
|
126
|
-
...profiles.map((p) => "`" + p + "`"),
|
|
127
|
-
"`inherit`",
|
|
128
|
-
];
|
|
129
|
-
const headerRow = "| " + headerCols.join(" | ") + " |";
|
|
130
|
-
const sepRow =
|
|
131
|
-
"|" + headerCols.map((col) => "-".repeat(col.length + 2)).join("|") + "|";
|
|
132
|
-
const tableRows = agents.map((agent) => {
|
|
133
|
-
const vals = profiles.map((p) => MODEL_PROFILES[agent][p]);
|
|
134
|
-
return "| " + [agent, ...vals, "inherit"].join(" | ") + " |";
|
|
135
|
-
});
|
|
136
|
-
const profileTable = [headerRow, sepRow, ...tableRows].join("\n");
|
|
137
|
-
|
|
138
|
-
const settingsCmd = `${cmdPrefix}settings`;
|
|
139
|
-
const setProfileCmd = `${cmdPrefix}set-profile <profile>`;
|
|
140
|
-
|
|
141
|
-
return `<!-- AUTO-GENERATED - do not edit by hand.
|
|
142
|
-
Source of truth: src/lib/model-profiles.ts
|
|
143
|
-
Regenerate with: pi-gsd-tools generate-model-profiles-md
|
|
144
|
-
-->
|
|
145
|
-
# Model Profiles
|
|
146
|
-
|
|
147
|
-
Model profiles control which ${runtimeName} model each GSD agent uses. This allows balancing quality vs token spend, or inheriting the currently selected session model.
|
|
148
|
-
|
|
149
|
-
## Profile Definitions
|
|
150
|
-
|
|
151
|
-
${profileTable}
|
|
152
|
-
|
|
153
|
-
## Profile Philosophy
|
|
154
|
-
|
|
155
|
-
**quality** - Maximum reasoning power
|
|
156
|
-
- Opus for all decision-making agents
|
|
157
|
-
- Sonnet for read-only verification
|
|
158
|
-
- Use when: quota available, critical architecture work
|
|
159
|
-
|
|
160
|
-
**balanced** (default) - Smart allocation
|
|
161
|
-
- Opus only for planning (where architecture decisions happen)
|
|
162
|
-
- Sonnet for execution and research (follows explicit instructions)
|
|
163
|
-
- Sonnet for verification (needs reasoning, not just pattern matching)
|
|
164
|
-
- Use when: normal development, good balance of quality and cost
|
|
165
|
-
|
|
166
|
-
**budget** - Minimal Opus usage
|
|
167
|
-
- Sonnet for anything that writes code
|
|
168
|
-
- Haiku for research and verification
|
|
169
|
-
- Use when: conserving quota, high-volume work, less critical phases
|
|
170
|
-
|
|
171
|
-
**inherit** - Follow the current session model
|
|
172
|
-
- All agents resolve to \`inherit\`
|
|
173
|
-
- Best when you switch models interactively (for example OpenCode \`/model\`)
|
|
174
|
-
- **Required when using non-Anthropic providers** (OpenRouter, local models, etc.) - otherwise GSD may call Anthropic models directly, incurring unexpected costs
|
|
175
|
-
- Use when: you want GSD to follow your currently selected runtime model
|
|
176
|
-
|
|
177
|
-
## Using pi with Other Providers
|
|
178
|
-
|
|
179
|
-
pi supports any provider via its settings. Set the \`inherit\` profile when using non-default models to ensure GSD routes subagents through your active session model.
|
|
180
|
-
|
|
181
|
-
To assign different models to different agents, add \`model_overrides\` with model IDs your runtime recognizes:
|
|
182
|
-
|
|
183
|
-
\`\`\`json
|
|
184
|
-
{
|
|
185
|
-
"resolve_model_ids": "omit",
|
|
186
|
-
"model_overrides": {
|
|
187
|
-
"gsd-planner": "o3",
|
|
188
|
-
"gsd-executor": "o4-mini",
|
|
189
|
-
"gsd-debugger": "o3",
|
|
190
|
-
"gsd-codebase-mapper": "o4-mini"
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
\`\`\`
|
|
194
|
-
|
|
195
|
-
The same tiering logic applies: stronger models for planning and debugging, cheaper models for execution and mapping.
|
|
196
|
-
|
|
197
|
-
## Using Non-Default Providers (OpenRouter, Local)
|
|
198
|
-
|
|
199
|
-
If you're using pi with OpenRouter or a local model, set the \`inherit\` profile to prevent GSD from calling specific model APIs directly:
|
|
200
|
-
|
|
201
|
-
\`\`\`bash
|
|
202
|
-
# Via settings command
|
|
203
|
-
${settingsCmd}
|
|
204
|
-
# → Select "Inherit" for model profile
|
|
205
|
-
|
|
206
|
-
# Or manually in .planning/config.json
|
|
207
|
-
{
|
|
208
|
-
"model_profile": "inherit"
|
|
209
|
-
}
|
|
210
|
-
\`\`\`
|
|
211
|
-
|
|
212
|
-
Without \`inherit\`, GSD's default \`balanced\` profile spawns specific Anthropic models (\`opus\`, \`sonnet\`, \`haiku\`) for each agent type, which can result in additional API costs through your non-Anthropic provider.
|
|
213
|
-
|
|
214
|
-
## Resolution Logic
|
|
215
|
-
|
|
216
|
-
Orchestrators resolve model before spawning:
|
|
217
|
-
|
|
218
|
-
\`\`\`
|
|
219
|
-
1. Read .planning/config.json
|
|
220
|
-
2. Check model_overrides for agent-specific override
|
|
221
|
-
3. If no override, look up agent in profile table
|
|
222
|
-
4. Pass model parameter to Task call
|
|
223
|
-
\`\`\`
|
|
224
|
-
|
|
225
|
-
## Per-Agent Overrides
|
|
226
|
-
|
|
227
|
-
Override specific agents without changing the entire profile:
|
|
228
|
-
|
|
229
|
-
\`\`\`json
|
|
230
|
-
{
|
|
231
|
-
"model_profile": "balanced",
|
|
232
|
-
"model_overrides": {
|
|
233
|
-
"gsd-executor": "opus",
|
|
234
|
-
"gsd-planner": "haiku"
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
\`\`\`
|
|
238
|
-
|
|
239
|
-
Overrides take precedence over the profile. Valid values: \`opus\`, \`sonnet\`, \`haiku\`, \`inherit\`, or any fully-qualified model ID (e.g., \`"o3"\`, \`"openai/o3"\`, \`"google/gemini-2.5-pro"\`).
|
|
240
|
-
|
|
241
|
-
## Switching Profiles
|
|
242
|
-
|
|
243
|
-
Runtime: \`${setProfileCmd}\`
|
|
244
|
-
|
|
245
|
-
Per-project default: Set in \`.planning/config.json\`:
|
|
246
|
-
\`\`\`json
|
|
247
|
-
{
|
|
248
|
-
"model_profile": "balanced"
|
|
249
|
-
}
|
|
250
|
-
\`\`\`
|
|
251
|
-
|
|
252
|
-
## Design Rationale
|
|
253
|
-
|
|
254
|
-
**Why Opus for gsd-planner?**
|
|
255
|
-
Planning involves architecture decisions, goal decomposition, and task design. This is where model quality has the highest impact.
|
|
256
|
-
|
|
257
|
-
**Why Sonnet for gsd-executor?**
|
|
258
|
-
Executors follow explicit PLAN.md instructions. The plan already contains the reasoning; execution is implementation.
|
|
259
|
-
|
|
260
|
-
**Why Sonnet (not Haiku) for verifiers in balanced?**
|
|
261
|
-
Verification requires goal-backward reasoning - checking if code *delivers* what the phase promised, not just pattern matching. Sonnet handles this well; Haiku may miss subtle gaps.
|
|
262
|
-
|
|
263
|
-
**Why Haiku for gsd-codebase-mapper?**
|
|
264
|
-
Read-only exploration and pattern extraction. No reasoning required, just structured output from file contents.
|
|
265
|
-
|
|
266
|
-
**Why \`inherit\` instead of passing \`opus\` directly?**
|
|
267
|
-
pi's \`"opus"\` alias maps to a specific model version. Organizations may block older opus versions while allowing newer ones. GSD returns \`"inherit"\` for opus-tier agents, causing them to use whatever opus version the user has configured in their session. This avoids version conflicts and silent fallbacks to Sonnet.
|
|
268
|
-
|
|
269
|
-
**Why \`inherit\` profile?**
|
|
270
|
-
Some runtimes (including OpenCode) let users switch models at runtime (\`/model\`). The \`inherit\` profile keeps all GSD subagents aligned to that live selection.
|
|
271
|
-
`;
|
|
272
|
-
}
|