@pi-stef/pair 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stefano Fiorini
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,53 @@
1
+ ---
2
+ description: Plan/Implementation Reviewer
3
+ tools: read, grep, find, ls
4
+ model: {{REVIEWER_MODEL}}
5
+ thinking: high
6
+ max_turns: 30
7
+ isolated: true
8
+ ---
9
+
10
+ You are a code reviewer. Your job is to review plans and implementation diffs for correctness, completeness, and risk.
11
+
12
+ When reviewing a plan:
13
+ - Check that milestones are well-defined with clear acceptance criteria
14
+ - Check that stories are bite-sized (2-5 min each)
15
+ - Check that the plan is detailed enough for a less intelligent model to follow
16
+ - Check for missing edge cases or error handling
17
+
18
+ When reviewing an implementation:
19
+ - Check that the diff matches the plan
20
+ - Check for bugs, security issues, and missing error handling
21
+ - Check that tests cover the changes
22
+ - Check that verification (lint/typecheck/tests) passes
23
+
24
+ Return exactly this structure:
25
+
26
+ ## Summary
27
+ [One paragraph summary of the review]
28
+
29
+ ## Findings
30
+
31
+ ### P0
32
+ - None.
33
+
34
+ ### P1
35
+ - None.
36
+
37
+ ### P2
38
+ - None.
39
+
40
+ ### P3
41
+ - None.
42
+
43
+ ## Verdict
44
+ VERDICT: APPROVED
45
+
46
+ Rules:
47
+ - P0 = total blocker (must fix)
48
+ - P1 = major risk (must fix)
49
+ - P2 = must-fix before approval
50
+ - P3 = cosmetic / nice-to-have (non-blocking)
51
+ - Use `- None.` when a severity has no findings
52
+ - VERDICT: APPROVED is valid only when no P0, P1, or P2 findings remain
53
+ - Order findings from highest to lowest severity
@@ -0,0 +1,6 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { registerSfPair } from "../src/register";
3
+
4
+ export default function pairExtension(pi: ExtensionAPI): void {
5
+ registerSfPair(pi);
6
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@pi-stef/pair",
3
+ "version": "0.1.1",
4
+ "description": "Pi extension for plan/review/implement workflows using pi-subagents for reviewer spawning.",
5
+ "type": "module",
6
+ "pi": {
7
+ "extensions": [
8
+ "./extensions"
9
+ ]
10
+ },
11
+ "peerDependencies": {
12
+ "@earendil-works/pi-coding-agent": "*",
13
+ "@earendil-works/pi-tui": "*",
14
+ "@tintinweb/pi-subagents": "*"
15
+ },
16
+ "dependencies": {
17
+ "@sinclair/typebox": "*",
18
+ "@pi-stef/paths": "0.3.3"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "*",
22
+ "vitest": "*"
23
+ },
24
+ "files": [
25
+ "src/",
26
+ "extensions/",
27
+ "skills/",
28
+ "templates/",
29
+ "agents/"
30
+ ],
31
+ "keywords": [
32
+ "pi-package",
33
+ "pi-extension",
34
+ "pair",
35
+ "plan",
36
+ "implement",
37
+ "review"
38
+ ],
39
+ "license": "MIT",
40
+ "scripts": {
41
+ "test": "vitest run",
42
+ "typecheck": "tsc --noEmit"
43
+ }
44
+ }
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: sf-pair-implement
3
+ description: Use when a plan folder created by sf-pair-plan must be executed with milestone verification, reviewer gates, and automatic worktree management.
4
+ ---
5
+
6
+ # sf-pair-implement
7
+
8
+ Execute an existing plan milestone-by-milestone in a git worktree, with reviewer approval gates and automatic worktree lifecycle.
9
+
10
+ ## Prerequisites
11
+
12
+ - pi-subagents extension installed
13
+ - A plan folder under `ai_plan/`
14
+ - Reviewer model configured
15
+ - Obra Superpowers skills available to pi: `verification-before-completion`, `finishing-a-development-branch` (install from https://github.com/obra/superpowers)
16
+
17
+ ## Input Resolution
18
+
19
+ The tool receives a `path` parameter. Resolve it:
20
+
21
+ 1. If path starts with `ai_plan/` → use as-is
22
+ 2. Otherwise → treat as slug, resolve to `ai_plan/YYYY-MM-DD-<slug>/`
23
+ 3. If multiple matches, list them and ask user to pick
24
+
25
+ ## Process
26
+
27
+ ### Phase 1: Locate Plan
28
+
29
+ 1. Read `continuation-runbook.md` first
30
+ 2. Read `story-tracker.md` to identify resume state
31
+ 3. Read `milestone-plan.md` for the implementation spec
32
+
33
+ ### Phase 2: Resolve Reviewer
34
+
35
+ Verify `.pi/agents/reviewer.md` exists. If not, stop and ask for model.
36
+
37
+ ### Phase 3: Set Up Worktree
38
+
39
+ Create a git worktree:
40
+ - Branch: `pair/<slug>`
41
+ - Base: HEAD
42
+ - Install deps if missing (detect pnpm/npm/yarn)
43
+
44
+ Use the worktree helpers from `src/worktree/create.ts` via the tool's execute function. The skill instructs the agent to:
45
+
46
+ 1. Run `git worktree add -b pair/<slug> <path> HEAD`
47
+ 2. Run package manager install in the worktree directory
48
+ 3. Switch to the worktree directory for implementation
49
+
50
+ ### Phase 4: Execute Milestones
51
+
52
+ Do not stop between milestones. For each milestone:
53
+
54
+ 1. Mark stories `in-dev` in `story-tracker.md`
55
+ 2. Implement each story following the plan to the letter
56
+ 3. Mark stories `completed` with commit hash in notes
57
+ 4. Run verification (lint/typecheck/tests) for changed files
58
+ 5. Spawn reviewer on milestone diff:
59
+ ```
60
+ Agent({
61
+ subagent_type: "reviewer",
62
+ prompt: "Review the milestone implementation. [include diff and verification output]",
63
+ description: "Review milestone M<N>"
64
+ })
65
+ ```
66
+ 6. If APPROVED → commit locally, mark milestone `approved`
67
+ 7. If REVISE → fix findings, re-review
68
+
69
+ ### Phase 5: Finalization
70
+
71
+ Once all milestones are approved and reviewed:
72
+
73
+ 1. Switch to base branch
74
+ 2. Merge worktree branch: `git merge --ff-only pair/<slug>`
75
+ 3. Remove worktree: `git worktree remove <worktree-path>`
76
+ 4. Delete branch: `git branch -d pair/<slug>`
77
+ 5. Stop for user's final review
78
+
79
+ ### Phase 6: Telegram Notification
80
+
81
+ If configured, send completion summary.
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: sf-pair-plan
3
+ description: Use when a user asks to create a structured implementation plan with milestones, stories, and reviewer approval using pi-subagents.
4
+ ---
5
+
6
+ # sf-pair-plan
7
+
8
+ Create a multi-milestone implementation plan with iterative reviewer approval.
9
+
10
+ ## Prerequisites
11
+
12
+ - pi-subagents extension installed
13
+ - Reviewer model configured (via config, env, or prompt)
14
+ - Obra Superpowers skills available to pi: `brainstorming`, `writing-plans` (install from https://github.com/obra/superpowers)
15
+
16
+ ## Process
17
+
18
+ ### Phase 1: Analyze
19
+
20
+ Explore the codebase and existing patterns. Use exploration agents to understand the project structure.
21
+
22
+ ### Phase 2: Gather Requirements
23
+
24
+ Ask questions one at a time using `AskUserQuestion` until the scope is clear. Confirm constraints, success criteria, dependencies, and what is out of scope.
25
+
26
+ ### Phase 3: Resolve Reviewer Model
27
+
28
+ The tool has already resolved the reviewer model and written `.pi/agents/reviewer.md`. Verify it exists:
29
+
30
+ ```
31
+ test -f .pi/agents/reviewer.md
32
+ ```
33
+
34
+ If it doesn't exist, stop and ask the user for a reviewer model.
35
+
36
+ ### Phase 4: Design
37
+
38
+ Load `brainstorming` skill. Present 2-3 approaches and recommend one. Resolve open design questions before the milestone breakdown.
39
+
40
+ ### Phase 5: Plan
41
+
42
+ Load `writing-plans` skill. Break the work into milestones and bite-sized stories (2-5 min each). Story IDs use `S-101`, `S-102` style.
43
+
44
+ Each story must be detailed enough for a less intelligent model to follow to the letter.
45
+
46
+ ### Phase 6: Iterative Plan Review
47
+
48
+ #### Step 1: Write plan to temp file
49
+
50
+ Write the complete plan to `/tmp/pair-plan-{REVIEW_ID}.md` where REVIEW_ID is a random UUID.
51
+
52
+ #### Step 2: Spawn reviewer
53
+
54
+ ```
55
+ Agent({
56
+ subagent_type: "reviewer",
57
+ prompt: "Review the implementation plan at /tmp/pair-plan-{REVIEW_ID}.md. Return exactly the required ## Summary, ## Findings (P0-P3), and ## Verdict structure.",
58
+ description: "Review plan round N"
59
+ })
60
+ ```
61
+
62
+ #### Step 3: Parse verdict
63
+
64
+ Scan the response for `VERDICT: APPROVED` (case-insensitive, line must start with `VERDICT:`).
65
+
66
+ - If found AND no P0/P1/P2 findings → proceed to Phase 7
67
+ - If not found OR P0/P1/P2 present → extract findings, fix, rewrite /tmp file, goto Step 2
68
+ - If max rounds (10) reached → stop, present outcome to user
69
+
70
+ ### Phase 7: Generate Plan Files
71
+
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
82
+
83
+ ### Phase 8: Telegram Notification
84
+
85
+ If `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` are configured, send a completion summary via the Telegram notifier helper.
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: sf-pair-task
3
+ description: Execute a single task end-to-end with plan review, implementation review, verification, and one persistent task-plan artifact.
4
+ ---
5
+
6
+ # sf-pair-task
7
+
8
+ Execute a single user task end-to-end: clarify, plan, review, implement, verify, review, commit.
9
+
10
+ ## Prerequisites
11
+
12
+ - pi-subagents extension installed
13
+ - Reviewer model configured
14
+ - Obra Superpowers skills available to pi: `brainstorming`, `test-driven-development`, `verification-before-completion`, `finishing-a-development-branch` (install from https://github.com/obra/superpowers)
15
+
16
+ ## Process
17
+
18
+ ### Phase 1: Preflight
19
+
20
+ 1. Verify repo: `git rev-parse --is-inside-work-tree`
21
+ 2. Ensure `ai_plan/` exists in `.gitignore`
22
+ 3. Verify `.pi/agents/reviewer.md` exists
23
+
24
+ ### Phase 2: Parse Prompt And Clarify
25
+
26
+ 1. Capture the user's prompt verbatim
27
+ 2. Ask 1-3 clarifying questions one at a time using `AskUserQuestion`
28
+ 3. Load `brainstorming` for behavior-changing work
29
+
30
+ ### Phase 3: Initialize task-plan.md
31
+
32
+ 1. Compute `ai_plan/YYYY-MM-DD-<slug>/`
33
+ 2. Write `task-plan.md` from template
34
+ 3. Fill all sections: Metadata, Prompt, Interpretation, Assumptions, Files, Approach, TDD Approach, Acceptance Criteria, Verification, Rollback
35
+ 4. Set `Status: draft`
36
+
37
+ ### Phase 4: Plan Review
38
+
39
+ 1. Write task-plan content to `/tmp/pair-task-plan-{REVIEW_ID}.md`
40
+ 2. Spawn reviewer:
41
+ ```
42
+ Agent({
43
+ subagent_type: "reviewer",
44
+ prompt: "Review the task plan at /tmp/pair-task-plan-{REVIEW_ID}.md",
45
+ description: "Review task plan"
46
+ })
47
+ ```
48
+ 3. Fix P0/P1/P2 findings until APPROVED
49
+ 4. Set `Status: plan-approved`
50
+
51
+ ### Phase 5: Execute
52
+
53
+ 1. Set `Status: implementation-in-progress`
54
+ 2. Load `test-driven-development` for behavior-changing edits
55
+ 3. Implement following the plan to the letter
56
+ 4. Update `task-plan.md` as acceptance criteria are completed
57
+
58
+ ### Phase 6: Verification Gate
59
+
60
+ 1. Load `verification-before-completion`
61
+ 2. Run verification commands from task-plan.md
62
+ 3. Fix failures until green
63
+
64
+ ### Phase 7: Implementation Review
65
+
66
+ 1. Build review payload from approved plan + diff + verification output
67
+ 2. Spawn reviewer:
68
+ ```
69
+ Agent({
70
+ subagent_type: "reviewer",
71
+ prompt: "Review the implementation. [include diff and verification]",
72
+ description: "Review implementation"
73
+ })
74
+ ```
75
+ 3. Fix P0/P1/P2 findings until APPROVED
76
+ 4. Set `Status: implementation-approved`
77
+
78
+ ### Phase 8: Commit And Push
79
+
80
+ 1. Load `finishing-a-development-branch`
81
+ 2. Stage intended files
82
+ 3. Create one commit
83
+ 4. Ask whether to push or keep local
84
+
85
+ ### Phase 9: Telegram Notification
86
+
87
+ If configured, send completion summary.
@@ -0,0 +1,123 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { Value } from "@sinclair/typebox/value";
4
+ import { globalConfig, projectConfig } from "@pi-stef/paths";
5
+ import {
6
+ ConfigSchema,
7
+ DEFAULT_CONFIG,
8
+ type PairConfig,
9
+ type ResolvedPairConfig,
10
+ } from "./schema";
11
+
12
+ export class ConfigValidationError extends Error {
13
+ constructor(
14
+ public readonly filePath: string,
15
+ public readonly pointer: string,
16
+ message: string
17
+ ) {
18
+ super(`Config validation error in ${filePath} at ${pointer}: ${message}`);
19
+ this.name = "ConfigValidationError";
20
+ }
21
+ }
22
+
23
+ async function loadFile(filePath: string): Promise<PairConfig> {
24
+ const raw = await readFile(filePath, "utf8");
25
+ const parsed = JSON.parse(raw);
26
+ const errors = [...Value.Errors(ConfigSchema, parsed)];
27
+ if (errors.length > 0) {
28
+ const first = errors[0];
29
+ throw new ConfigValidationError(
30
+ filePath,
31
+ first.path,
32
+ first.message
33
+ );
34
+ }
35
+ return parsed as PairConfig;
36
+ }
37
+
38
+ /** Load file, returning null if not found (ENOENT), throwing on parse/validation errors. */
39
+ async function loadFileOrNull(filePath: string): Promise<PairConfig | null> {
40
+ try {
41
+ return await loadFile(filePath);
42
+ } catch (err: unknown) {
43
+ if (
44
+ err &&
45
+ typeof err === "object" &&
46
+ "code" in err &&
47
+ (err as { code: string }).code === "ENOENT"
48
+ ) {
49
+ return null;
50
+ }
51
+ throw err;
52
+ }
53
+ }
54
+
55
+ export async function loadConfig(
56
+ repoRoot: string,
57
+ opts: { homeDir?: string } = {}
58
+ ): Promise<PairConfig> {
59
+ const homeDir = opts.homeDir ?? homedir();
60
+ const globalPath = globalConfig("pair", homeDir);
61
+ const projectPath = projectConfig("pair", repoRoot);
62
+
63
+ const global = (await loadFileOrNull(globalPath)) ?? {};
64
+ const project = (await loadFileOrNull(projectPath)) ?? {};
65
+
66
+ // Deep merge: project wins on conflicts, but nested objects are merged field-by-field
67
+ return {
68
+ reviewer: {
69
+ ...(global.reviewer ?? {}),
70
+ ...(project.reviewer ?? {}),
71
+ },
72
+ };
73
+ }
74
+
75
+ export function resolveDefaults(loaded: PairConfig = {}): ResolvedPairConfig {
76
+ return {
77
+ reviewer: {
78
+ model: loaded.reviewer?.model ?? DEFAULT_CONFIG.reviewer.model,
79
+ },
80
+ };
81
+ }
82
+
83
+ export async function loadAndResolveDefaults(
84
+ repoRoot: string,
85
+ opts: { homeDir?: string; notify?: (msg: string, level: string) => void } = {}
86
+ ): Promise<ResolvedPairConfig> {
87
+ try {
88
+ const loaded = await loadConfig(repoRoot, { homeDir: opts.homeDir });
89
+ return resolveDefaults(loaded);
90
+ } catch (err) {
91
+ const detail = err instanceof Error ? err.message : String(err);
92
+ opts.notify?.(
93
+ `sf-pair config: ${detail} — falling back to built-in defaults.`,
94
+ "warning"
95
+ );
96
+ return resolveDefaults({});
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Resolve reviewer model from the 4-step chain:
102
+ * 1. Prompt argument (parsed by caller)
103
+ * 2. Project/global config
104
+ * 3. Environment variable SF_PAIR_REVIEWER_MODEL
105
+ * 4. Ask user (returns null, caller must prompt)
106
+ */
107
+ export function resolveReviewerModel(
108
+ promptArg: string | undefined,
109
+ config: ResolvedPairConfig
110
+ ): string | null {
111
+ // 1. Prompt argument
112
+ if (promptArg) return promptArg;
113
+
114
+ // 2. Config file
115
+ if (config.reviewer.model) return config.reviewer.model;
116
+
117
+ // 3. Environment variable
118
+ const envModel = process.env.SF_PAIR_REVIEWER_MODEL;
119
+ if (envModel) return envModel;
120
+
121
+ // 4. Not found — caller must ask user
122
+ return null;
123
+ }
@@ -0,0 +1,35 @@
1
+ import { Type, type Static } from "@sinclair/typebox";
2
+
3
+ export const ConfigSchema = Type.Object(
4
+ {
5
+ reviewer: Type.Optional(
6
+ Type.Object(
7
+ {
8
+ model: Type.Optional(
9
+ Type.String({
10
+ minLength: 1,
11
+ description:
12
+ "Model for the reviewer agent (e.g. 'anthropic/sonnet-4-6')",
13
+ })
14
+ ),
15
+ },
16
+ { additionalProperties: false }
17
+ )
18
+ ),
19
+ },
20
+ { additionalProperties: false }
21
+ );
22
+
23
+ export type PairConfig = Static<typeof ConfigSchema>;
24
+
25
+ export interface ResolvedPairConfig {
26
+ reviewer: {
27
+ model: string | null;
28
+ };
29
+ }
30
+
31
+ export const DEFAULT_CONFIG: ResolvedPairConfig = {
32
+ reviewer: {
33
+ model: null, // null = not configured, must ask user
34
+ },
35
+ };