@pi-stef/pair 0.1.4 → 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 +1 -1
- package/skills/sf-pair-plan/SKILL.md +36 -12
- package/src/config/load.ts +32 -0
- package/src/config/schema.ts +20 -0
- package/src/register.ts +47 -12
package/package.json
CHANGED
|
@@ -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({ agentType: "general-purpose" })` to understand the project structure
|
|
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.
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- `
|
|
81
|
-
- `
|
|
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
|
|
package/src/config/load.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/config/schema.ts
CHANGED
|
@@ -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
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
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 (!
|
|
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
|
-
|
|
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: ${
|
|
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,
|
|
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
|
});
|