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.
Files changed (66) hide show
  1. package/dist/pi-gsd-hooks.js +1533 -0
  2. package/dist/pi-gsd-tools.js +53 -52
  3. package/package.json +3 -5
  4. package/.gsd/extensions/pi-gsd-hooks.ts +0 -973
  5. package/src/cli.ts +0 -644
  6. package/src/commands/base.ts +0 -67
  7. package/src/commands/commit.ts +0 -22
  8. package/src/commands/config.ts +0 -71
  9. package/src/commands/frontmatter.ts +0 -51
  10. package/src/commands/index.ts +0 -76
  11. package/src/commands/init.ts +0 -43
  12. package/src/commands/milestone.ts +0 -37
  13. package/src/commands/phase.ts +0 -92
  14. package/src/commands/progress.ts +0 -71
  15. package/src/commands/roadmap.ts +0 -40
  16. package/src/commands/scaffold.ts +0 -19
  17. package/src/commands/state.ts +0 -102
  18. package/src/commands/template.ts +0 -52
  19. package/src/commands/verify.ts +0 -70
  20. package/src/commands/workstream.ts +0 -98
  21. package/src/commands/wxp.ts +0 -65
  22. package/src/lib/commands.ts +0 -1040
  23. package/src/lib/config.ts +0 -385
  24. package/src/lib/core.ts +0 -1167
  25. package/src/lib/frontmatter.ts +0 -462
  26. package/src/lib/init.ts +0 -517
  27. package/src/lib/milestone.ts +0 -290
  28. package/src/lib/model-profiles.ts +0 -272
  29. package/src/lib/phase.ts +0 -1012
  30. package/src/lib/profile-output.ts +0 -237
  31. package/src/lib/profile-pipeline.ts +0 -556
  32. package/src/lib/roadmap.ts +0 -378
  33. package/src/lib/schemas.ts +0 -290
  34. package/src/lib/security.ts +0 -176
  35. package/src/lib/state.ts +0 -1175
  36. package/src/lib/template.ts +0 -246
  37. package/src/lib/uat.ts +0 -289
  38. package/src/lib/verify.ts +0 -879
  39. package/src/lib/workstream.ts +0 -524
  40. package/src/output.ts +0 -45
  41. package/src/schemas/pi-gsd-settings.schema.json +0 -80
  42. package/src/schemas/wxp.xsd +0 -619
  43. package/src/schemas/wxp.zod.ts +0 -318
  44. package/src/wxp/__tests__/arguments.test.ts +0 -86
  45. package/src/wxp/__tests__/conditions.test.ts +0 -106
  46. package/src/wxp/__tests__/executor.test.ts +0 -95
  47. package/src/wxp/__tests__/helpers.ts +0 -26
  48. package/src/wxp/__tests__/integration.test.ts +0 -166
  49. package/src/wxp/__tests__/new-features.test.ts +0 -222
  50. package/src/wxp/__tests__/parser.test.ts +0 -159
  51. package/src/wxp/__tests__/paste.test.ts +0 -66
  52. package/src/wxp/__tests__/schema.test.ts +0 -120
  53. package/src/wxp/__tests__/security.test.ts +0 -87
  54. package/src/wxp/__tests__/shell.test.ts +0 -85
  55. package/src/wxp/__tests__/string-ops.test.ts +0 -25
  56. package/src/wxp/__tests__/variables.test.ts +0 -65
  57. package/src/wxp/arguments.ts +0 -89
  58. package/src/wxp/conditions.ts +0 -78
  59. package/src/wxp/executor.ts +0 -191
  60. package/src/wxp/index.ts +0 -191
  61. package/src/wxp/parser.ts +0 -198
  62. package/src/wxp/paste.ts +0 -51
  63. package/src/wxp/security.ts +0 -102
  64. package/src/wxp/shell.ts +0 -81
  65. package/src/wxp/string-ops.ts +0 -44
  66. package/src/wxp/variables.ts +0 -109
@@ -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
- }