@pi-stef/pair 0.1.3 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-stef/pair",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Pi extension for plan/review/implement workflows using pi-subagents for reviewer spawning.",
5
5
  "type": "module",
6
6
  "pi": {
@@ -17,7 +17,14 @@ Create a multi-milestone implementation plan with iterative reviewer approval.
17
17
 
18
18
  ### Phase 1: Analyze
19
19
 
20
- Explore the codebase and existing patterns. Use `Agent()` to understand the project structure — this inherits the current session model. Do NOT specify a model or agentType unless explicitly asked.
20
+ Explore the codebase and existing patterns. Use `Agent({ agentType: "general-purpose" })` to understand the project structure.
21
+
22
+ If an explorer model was configured (via prompt, config, or env), use it:
23
+ ```
24
+ Agent({ agentType: "general-purpose", model: "<explorer_model>" })
25
+ ```
26
+
27
+ If no explorer model is configured, omit the `model` parameter to inherit the current session model. Do NOT use the default `Explore` agent (it uses Haiku).
21
28
 
22
29
  ### Phase 2: Gather Requirements
23
30
 
@@ -35,7 +42,7 @@ If it doesn't exist, stop and ask the user for a reviewer model.
35
42
 
36
43
  ### Phase 4: Design
37
44
 
38
- Load `brainstorming` skill. Present 2-3 approaches and recommend one. Resolve open design questions before the milestone breakdown.
45
+ Load `brainstorming` skill. Present 2-3 approaches and recommend one. Use `AskUserQuestion` to get the user's approval on the chosen approach before proceeding. Do NOT move to Phase 5 until the user confirms the design.
39
46
 
40
47
  ### Phase 5: Plan
41
48
 
@@ -69,16 +76,33 @@ Scan the response for `VERDICT: APPROVED` (case-insensitive, line must start wit
69
76
 
70
77
  ### Phase 7: Generate Plan Files
71
78
 
72
- Once the plan is approved:
73
-
74
- 1. Ensure `ai_plan/` exists in `.gitignore`
75
- 2. Create `ai_plan/YYYY-MM-DD-<slug>/`
76
- 3. Write using templates from this package's `templates/` directory:
77
- - `original-plan.md` raw approved plan
78
- - `final-transcript.md` — conversation log
79
- - `milestone-plan.md` from template
80
- - `story-tracker.md` — from template
81
- - `continuation-runbook.md` — from template
79
+ **CRITICAL: The plan is NOT complete until ALL files are created in `ai_plan/`. Do NOT stop or ask the user how to proceed.**
80
+
81
+ Once the plan is approved by the reviewer, you MUST:
82
+
83
+ 1. **Check `.gitignore`**: Run `grep -qx '/ai_plan/' .gitignore || echo '/ai_plan/' >> .gitignore`
84
+ 2. **Create the plan folder**: `mkdir -p ai_plan/YYYY-MM-DD-<slug>/` (use today's date and a descriptive slug)
85
+ 3. **Read the templates**: Read the 3 templates from the `templates/` directory (relative to this skill: `../../templates/`). Note: `task-plan.md` is for the task skill only ignore it here.
86
+ 4. **Write ALL 5 files** (use the same slug throughout):
87
+ - `ai_plan/YYYY-MM-DD-<slug>/original-plan.md` — the raw approved plan from the review
88
+ - `ai_plan/YYYY-MM-DD-<slug>/final-transcript.md` — conversation log of the planning session
89
+ - `ai_plan/YYYY-MM-DD-<slug>/milestone-plan.md` — filled from `milestone-plan.md` template
90
+ - `ai_plan/YYYY-MM-DD-<slug>/story-tracker.md` — filled from `story-tracker.md` template
91
+ - `ai_plan/YYYY-MM-DD-<slug>/continuation-runbook.md` — filled from `continuation-runbook.md` template
92
+
93
+ 5. **Verify all files exist**:
94
+ ```
95
+ ls -la ai_plan/YYYY-MM-DD-<slug>/
96
+ ```
97
+ If any file is missing, create it immediately.
98
+
99
+ 6. **Present the plan folder to the user**: Show the file list and confirm the plan is ready for implementation.
100
+
101
+ **DO NOT:**
102
+ - Say "Plan complete" without creating the ai_plan/ folder
103
+ - Ask the user how to proceed with implementation
104
+ - Save the plan to any other location (docs/plans/, etc.)
105
+ - Skip any of the 5 required files
82
106
 
83
107
  ### Phase 8: Telegram Notification
84
108
 
@@ -69,6 +69,10 @@ export async function loadConfig(
69
69
  ...(global.reviewer ?? {}),
70
70
  ...(project.reviewer ?? {}),
71
71
  },
72
+ explorer: {
73
+ ...(global.explorer ?? {}),
74
+ ...(project.explorer ?? {}),
75
+ },
72
76
  };
73
77
  }
74
78
 
@@ -77,6 +81,9 @@ export function resolveDefaults(loaded: PairConfig = {}): ResolvedPairConfig {
77
81
  reviewer: {
78
82
  model: loaded.reviewer?.model ?? DEFAULT_CONFIG.reviewer.model,
79
83
  },
84
+ explorer: {
85
+ model: loaded.explorer?.model ?? DEFAULT_CONFIG.explorer.model,
86
+ },
80
87
  };
81
88
  }
82
89
 
@@ -121,3 +128,28 @@ export function resolveReviewerModel(
121
128
  // 4. Not found — caller must ask user
122
129
  return null;
123
130
  }
131
+
132
+ /**
133
+ * Resolve explorer model from the 3-step chain:
134
+ * 1. Prompt argument (parsed by caller)
135
+ * 2. Project/global config
136
+ * 3. Environment variable SF_PAIR_EXPLORER_MODEL
137
+ * 4. Returns null to inherit parent model (no need to ask user)
138
+ */
139
+ export function resolveExplorerModel(
140
+ promptArg: string | undefined,
141
+ config: ResolvedPairConfig
142
+ ): string | null {
143
+ // 1. Prompt argument
144
+ if (promptArg) return promptArg;
145
+
146
+ // 2. Config file
147
+ if (config.explorer.model) return config.explorer.model;
148
+
149
+ // 3. Environment variable
150
+ const envModel = process.env.SF_PAIR_EXPLORER_MODEL;
151
+ if (envModel) return envModel;
152
+
153
+ // 4. Not found — inherit parent model
154
+ return null;
155
+ }
@@ -16,6 +16,20 @@ export const ConfigSchema = Type.Object(
16
16
  { additionalProperties: false }
17
17
  )
18
18
  ),
19
+ explorer: Type.Optional(
20
+ Type.Object(
21
+ {
22
+ model: Type.Optional(
23
+ Type.String({
24
+ minLength: 1,
25
+ description:
26
+ "Model for the explorer agent (e.g. 'anthropic/sonnet-4-6'). Falls back to parent model if not set.",
27
+ })
28
+ ),
29
+ },
30
+ { additionalProperties: false }
31
+ )
32
+ ),
19
33
  },
20
34
  { additionalProperties: false }
21
35
  );
@@ -26,10 +40,16 @@ export interface ResolvedPairConfig {
26
40
  reviewer: {
27
41
  model: string | null;
28
42
  };
43
+ explorer: {
44
+ model: string | null;
45
+ };
29
46
  }
30
47
 
31
48
  export const DEFAULT_CONFIG: ResolvedPairConfig = {
32
49
  reviewer: {
33
50
  model: null, // null = not configured, must ask user
34
51
  },
52
+ explorer: {
53
+ model: null, // null = not configured, inherit parent model
54
+ },
35
55
  };
package/src/register.ts CHANGED
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import {
7
7
  loadAndResolveDefaults,
8
8
  resolveReviewerModel,
9
+ resolveExplorerModel,
9
10
  } from "./config/load";
10
11
 
11
12
  export const PAIR_TOOL_NAMES = [
@@ -54,16 +55,36 @@ function extractReviewerModelFromPrompt(prompt: string): string | undefined {
54
55
  return undefined;
55
56
  }
56
57
 
58
+ /**
59
+ * Extract explorer model from prompt string.
60
+ * Looks for patterns like "use <model> as explorer" or "explorer: <model>"
61
+ */
62
+ function extractExplorerModelFromPrompt(prompt: string): string | undefined {
63
+ const patterns = [
64
+ /use\s+([\w/.-]+)\s+as\s+explorer/i,
65
+ /explorer[:\s]+([\w/.-]+)/i,
66
+ /explore\s+with\s+([\w/.-]+)/i,
67
+ ];
68
+ for (const pattern of patterns) {
69
+ const match = prompt.match(pattern);
70
+ if (match) return match[1];
71
+ }
72
+ return undefined;
73
+ }
74
+
57
75
  export function registerSfPair(pi: ExtensionAPI): void {
58
76
  // Register plan tool
59
77
  const planSchema = Type.Object(
60
78
  {
61
79
  prompt: Type.Optional(
62
- Type.String({ description: "The task to plan. May include reviewer model override." })
80
+ Type.String({ description: "The task to plan. May include reviewer/explorer model overrides." })
63
81
  ),
64
82
  reviewer_model: Type.Optional(
65
83
  Type.String({ description: "Override reviewer model (e.g. 'anthropic/sonnet-4-6')" })
66
84
  ),
85
+ explorer_model: Type.Optional(
86
+ Type.String({ description: "Override explorer model (e.g. 'anthropic/sonnet-4-6'). Falls back to parent model if not set.", minLength: 1 })
87
+ ),
67
88
  },
68
89
  { additionalProperties: false }
69
90
  );
@@ -77,13 +98,16 @@ export function registerSfPair(pi: ExtensionAPI): void {
77
98
  execute: async (_id, params, _signal, _onUpdate, ctx) => {
78
99
  const repoRoot = ctx.cwd ?? process.cwd();
79
100
  const defaults = await loadAndResolveDefaults(repoRoot);
80
- const promptModel = extractReviewerModelFromPrompt((params as any).prompt ?? "");
81
- const model = resolveReviewerModel(
82
- (params as any).reviewer_model ?? promptModel,
101
+ const prompt = (params as any).prompt ?? "";
102
+
103
+ // Resolve reviewer model
104
+ const promptReviewerModel = extractReviewerModelFromPrompt(prompt);
105
+ const reviewerModel = resolveReviewerModel(
106
+ (params as any).reviewer_model ?? promptReviewerModel,
83
107
  defaults
84
108
  );
85
109
 
86
- if (!model) {
110
+ if (!reviewerModel) {
87
111
  return {
88
112
  content: [
89
113
  {
@@ -95,16 +119,27 @@ export function registerSfPair(pi: ExtensionAPI): void {
95
119
  };
96
120
  }
97
121
 
98
- await writeReviewerAgent(repoRoot, model);
122
+ // Resolve explorer model (optional, falls back to parent)
123
+ const promptExplorerModel = extractExplorerModelFromPrompt(prompt);
124
+ const explorerModel = resolveExplorerModel(
125
+ (params as any).explorer_model ?? promptExplorerModel,
126
+ defaults
127
+ );
128
+
129
+ await writeReviewerAgent(repoRoot, reviewerModel);
130
+
131
+ const explorerInfo = explorerModel
132
+ ? `Explorer model: ${explorerModel}`
133
+ : "Explorer model: inherits from parent (not configured)";
99
134
 
100
135
  return {
101
136
  content: [
102
137
  {
103
138
  type: "text" as const,
104
- text: `Reviewer configured with model: ${model}\nAgent file written to ${REVIEWER_AGENT_PATH}\n\nNow load and follow the plan skill.`,
139
+ text: `Reviewer configured with model: ${reviewerModel}\n${explorerInfo}\nAgent file written to ${REVIEWER_AGENT_PATH}\n\nNow load and follow the sf-pair-plan skill.`,
105
140
  },
106
141
  ],
107
- details: { configured: true, model },
142
+ details: { configured: true, reviewerModel, explorerModel },
108
143
  };
109
144
  },
110
145
  });
@@ -152,10 +187,10 @@ export function registerSfPair(pi: ExtensionAPI): void {
152
187
  content: [
153
188
  {
154
189
  type: "text" as const,
155
- text: `Reviewer configured with model: ${model}\nPlan path: ${(params as any).path}\nAgent file written to ${REVIEWER_AGENT_PATH}\n\nNow load and follow the implement skill.`,
190
+ text: `Reviewer configured with model: ${model}\nPlan path: ${(params as any).path}\nAgent file written to ${REVIEWER_AGENT_PATH}\n\nNow load and follow the sf-pair-implement skill.`,
156
191
  },
157
192
  ],
158
- details: { configured: true, model, path: (params as any).path },
193
+ details: { configured: true, reviewerModel: model, path: (params as any).path },
159
194
  };
160
195
  },
161
196
  });
@@ -206,10 +241,10 @@ export function registerSfPair(pi: ExtensionAPI): void {
206
241
  content: [
207
242
  {
208
243
  type: "text" as const,
209
- text: `Reviewer configured with model: ${model}\nTask: ${(params as any).prompt}\nAgent file written to ${REVIEWER_AGENT_PATH}\n\nNow load and follow the task skill.`,
244
+ text: `Reviewer configured with model: ${model}\nTask: ${(params as any).prompt}\nAgent file written to ${REVIEWER_AGENT_PATH}\n\nNow load and follow the sf-pair-task skill.`,
210
245
  },
211
246
  ],
212
- details: { configured: true, model, prompt: (params as any).prompt },
247
+ details: { configured: true, reviewerModel: model, prompt: (params as any).prompt },
213
248
  };
214
249
  },
215
250
  });