@a-canary/pi-upskill 1.0.0
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/README.md +143 -0
- package/extension/index.ts +308 -0
- package/package.json +21 -0
- package/skills/analyze/SKILL.md +187 -0
- package/skills/backfill/SKILL.md +142 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# pi-upskill
|
|
2
|
+
|
|
3
|
+
Learn from failures. Reduce token waste. Improve automatically.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
pi-upskill tracks corrections (failures → fixes) and generates skills/rules to prevent future mistakes.
|
|
8
|
+
|
|
9
|
+
**Core flow:**
|
|
10
|
+
1. Log corrections during conversation (`upskill-log` tool)
|
|
11
|
+
2. Backfill from past sessions (`/upskill-backfill`)
|
|
12
|
+
3. At threshold, analyze and generate ONE high-impact edit (`/upskill-analyze`)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# From git (recommended)
|
|
18
|
+
pi install git:github.com/a-canary/pi-upskill
|
|
19
|
+
|
|
20
|
+
# Or from npm after publishing
|
|
21
|
+
pi install npm:pi-upskill
|
|
22
|
+
|
|
23
|
+
# Try without installing
|
|
24
|
+
pi -e git:github.com/a-canary/pi-upskill
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
After install, reload: `/reload`
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### During conversation: log corrections
|
|
32
|
+
|
|
33
|
+
The agent uses the `upskill-log` tool:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Agent: [uses upskill-log tool]
|
|
37
|
+
failure: "Committed without running tests"
|
|
38
|
+
correction: "Always run tests before commit"
|
|
39
|
+
strength: "strong"
|
|
40
|
+
tokens_wasted: 3000
|
|
41
|
+
|
|
42
|
+
Result: Logged correction #5 to .pi/corrections.jsonl
|
|
43
|
+
Progress: 5/20 corrections
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Strength levels:**
|
|
47
|
+
- `strong` — User said "always/never/remember" → single occurrence = skill
|
|
48
|
+
- `pattern` — Self-correction or repeated issue → needs 3x occurrences
|
|
49
|
+
|
|
50
|
+
### One-time: scan past sessions
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
/upskill-backfill
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Scans session files from pi, claude, opencode, codex. Extracts corrections for review.
|
|
57
|
+
|
|
58
|
+
### At threshold: analyze and improve
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
/upskill-analyze
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When 20+ corrections logged, triggers background analysis:
|
|
65
|
+
1. LLM reviews all corrections
|
|
66
|
+
2. Selects ONE edit for maximum token impact
|
|
67
|
+
3. Applies surgical edit (skill/AGENTS.md/MEMORY.md)
|
|
68
|
+
4. Removes addressed corrections
|
|
69
|
+
|
|
70
|
+
### Check progress
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
/upskill-status
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Shows: count, threshold, strong vs pattern, total tokens wasted.
|
|
77
|
+
|
|
78
|
+
## Data Format
|
|
79
|
+
|
|
80
|
+
`.pi/corrections.jsonl` — one JSON object per line:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{"timestamp":"2025-03-13T01:30:00Z","failure":"Committed without tests","correction":"Always run tests first","context":"User reminder after broken CI","tokens_wasted":3000,"source":"user","strength":"strong"}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Required fields (max 30 words each):**
|
|
87
|
+
- `timestamp` — ISO 8601
|
|
88
|
+
- `failure` — What went wrong
|
|
89
|
+
- `correction` — How to fix / what to do instead
|
|
90
|
+
- `source` — "user" or "self"
|
|
91
|
+
- `strength` — "strong" or "pattern"
|
|
92
|
+
|
|
93
|
+
**Optional:**
|
|
94
|
+
- `context` — Relevant context
|
|
95
|
+
- `tokens_wasted` — Estimated tokens
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
`.pi/settings.json`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"upskill": {
|
|
104
|
+
"threshold": 20,
|
|
105
|
+
"autoAnalyze": false,
|
|
106
|
+
"lookbackDays": 7
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Architecture
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
~/pi-upskill/
|
|
115
|
+
├── CHOICES.md # Decision record
|
|
116
|
+
├── PLAN.md # Implementation phases
|
|
117
|
+
├── README.md # This file
|
|
118
|
+
├── extension/
|
|
119
|
+
│ └── index.ts # upskill-log tool, commands
|
|
120
|
+
└── skills/
|
|
121
|
+
├── analyze/SKILL.md # Pattern analysis workflow
|
|
122
|
+
└── backfill/SKILL.md # Historical scan workflow
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Hybrid interface:**
|
|
126
|
+
- Extension provides `upskill-log` tool (inline during conversation)
|
|
127
|
+
- Skills provide `/upskill-backfill` and `/upskill-analyze` commands
|
|
128
|
+
|
|
129
|
+
## Key Decisions
|
|
130
|
+
|
|
131
|
+
See [CHOICES.md](CHOICES.md) for full decision record.
|
|
132
|
+
|
|
133
|
+
| ID | Decision |
|
|
134
|
+
|----|----------|
|
|
135
|
+
| UX-0001 | User corrections with "always/never/remember" → immediate skill |
|
|
136
|
+
| UX-0002 | Self-corrections → need 3x pattern before skill |
|
|
137
|
+
| F-0003 | At 20 corrections → background analysis, ONE edit for max impact |
|
|
138
|
+
| D-0003 | Processed corrections removed after edit applied |
|
|
139
|
+
|
|
140
|
+
## Inspiration
|
|
141
|
+
|
|
142
|
+
- [upskill.md](https://github.com/claude-admin/cc-plugins) — Pattern extraction from memory
|
|
143
|
+
- [pi-reflect](https://github.com/jo-inc/pi-reflect) — Iterative self-improvement
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-upskill — Learn from failures, reduce token waste
|
|
3
|
+
*
|
|
4
|
+
* Tools:
|
|
5
|
+
* upskill-log — Log a correction during conversation
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* /upskill-status — Show corrections count and threshold progress
|
|
9
|
+
* /upskill-analyze — Trigger pattern analysis (runs in background)
|
|
10
|
+
*
|
|
11
|
+
* Configuration (.pi/settings.json):
|
|
12
|
+
* {
|
|
13
|
+
* "upskill": {
|
|
14
|
+
* "threshold": 20,
|
|
15
|
+
* "autoAnalyze": false
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
21
|
+
import { Type } from "@sinclair/typebox";
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
import { spawn } from "node:child_process";
|
|
25
|
+
|
|
26
|
+
// ── Types ────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
interface Correction {
|
|
29
|
+
timestamp: string;
|
|
30
|
+
failure: string;
|
|
31
|
+
correction: string;
|
|
32
|
+
context?: string;
|
|
33
|
+
tokens_wasted?: number;
|
|
34
|
+
source: "user" | "self";
|
|
35
|
+
strength: "strong" | "pattern";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface UpskillSettings {
|
|
39
|
+
threshold: number;
|
|
40
|
+
autoAnalyze: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const DEFAULT_SETTINGS: UpskillSettings = {
|
|
44
|
+
threshold: 20,
|
|
45
|
+
autoAnalyze: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ── Helpers ──────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function getCorrectionsPath(cwd: string): string {
|
|
51
|
+
return path.join(cwd, ".pi", "corrections.jsonl");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function loadCorrections(filepath: string): Correction[] {
|
|
55
|
+
if (!fs.existsSync(filepath)) return [];
|
|
56
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
57
|
+
return content
|
|
58
|
+
.trim()
|
|
59
|
+
.split("\n")
|
|
60
|
+
.filter((line) => line.trim())
|
|
61
|
+
.map((line) => JSON.parse(line));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function appendCorrection(filepath: string, correction: Correction): void {
|
|
65
|
+
const dir = path.dirname(filepath);
|
|
66
|
+
if (!fs.existsSync(dir)) {
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
fs.appendFileSync(filepath, JSON.stringify(correction) + "\n", "utf-8");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getSettings(ctx: any): UpskillSettings {
|
|
73
|
+
const projectSettings = ctx.projectSettings?.upskill || {};
|
|
74
|
+
return { ...DEFAULT_SETTINGS, ...projectSettings };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function countWords(text: string): number {
|
|
78
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Extension ────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
export default function (pi: ExtensionAPI) {
|
|
84
|
+
// ── upskill-log Tool ───────────────────────────
|
|
85
|
+
|
|
86
|
+
pi.registerTool({
|
|
87
|
+
name: "upskill-log",
|
|
88
|
+
label: "Log Correction",
|
|
89
|
+
description: `Log a failure → correction to .pi/corrections.jsonl. Use when:
|
|
90
|
+
1. User corrects you with "always", "never", or "remember" (strength: strong)
|
|
91
|
+
2. You self-correct after multiple failed attempts (strength: pattern, needs 3x)
|
|
92
|
+
|
|
93
|
+
After logging, check if threshold reached (default 20 entries). If so, suggest running /upskill-analyze.
|
|
94
|
+
|
|
95
|
+
Each field must be 30 words or less. Estimate tokens_wasted based on conversation length from mistake to correction.`,
|
|
96
|
+
|
|
97
|
+
parameters: Type.Object({
|
|
98
|
+
failure: Type.String({
|
|
99
|
+
description: "What went wrong (max 30 words)",
|
|
100
|
+
maxLength: 300,
|
|
101
|
+
}),
|
|
102
|
+
correction: Type.String({
|
|
103
|
+
description: "How it was fixed / what to do instead (max 30 words)",
|
|
104
|
+
maxLength: 300,
|
|
105
|
+
}),
|
|
106
|
+
context: Type.Optional(
|
|
107
|
+
Type.String({
|
|
108
|
+
description: "Relevant context (max 30 words)",
|
|
109
|
+
maxLength: 300,
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
tokens_wasted: Type.Optional(
|
|
113
|
+
Type.Number({
|
|
114
|
+
description: "Estimated tokens wasted (in + out) from mistake to correction",
|
|
115
|
+
}),
|
|
116
|
+
),
|
|
117
|
+
strength: Type.Optional(
|
|
118
|
+
Type.String({
|
|
119
|
+
description: "strong = always/never/remember (single occurrence sufficient), pattern = needs 3x",
|
|
120
|
+
enum: ["strong", "pattern"],
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
|
|
125
|
+
async execute(toolCallId, params, _signal, _onUpdate, ctx) {
|
|
126
|
+
const { failure, correction, context, tokens_wasted, strength = "pattern" } = params;
|
|
127
|
+
|
|
128
|
+
// Validate word counts
|
|
129
|
+
const failureWords = countWords(failure);
|
|
130
|
+
const correctionWords = countWords(correction);
|
|
131
|
+
const contextWords = context ? countWords(context) : 0;
|
|
132
|
+
|
|
133
|
+
if (failureWords > 30 || correctionWords > 30 || contextWords > 30) {
|
|
134
|
+
return {
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: "text",
|
|
138
|
+
text: `Error: Fields must be 30 words or less. Got: failure=${failureWords}, correction=${correctionWords}, context=${contextWords}`,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
isError: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const entry: Correction = {
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
failure,
|
|
148
|
+
correction,
|
|
149
|
+
context,
|
|
150
|
+
tokens_wasted,
|
|
151
|
+
source: "user", // Could be inferred from context
|
|
152
|
+
strength: strength as "strong" | "pattern",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const correctionsPath = getCorrectionsPath(ctx.cwd);
|
|
156
|
+
appendCorrection(correctionsPath, entry);
|
|
157
|
+
|
|
158
|
+
const corrections = loadCorrections(correctionsPath);
|
|
159
|
+
const settings = getSettings(ctx);
|
|
160
|
+
const count = corrections.length;
|
|
161
|
+
|
|
162
|
+
let message = `Logged correction #${count} to .pi/corrections.jsonl`;
|
|
163
|
+
|
|
164
|
+
if (count >= settings.threshold) {
|
|
165
|
+
message += `\n\n**Threshold reached!** (${count}/${settings.threshold})\nRun /upskill-analyze to generate skills from patterns.`;
|
|
166
|
+
} else {
|
|
167
|
+
message += `\n\nProgress: ${count}/${settings.threshold} corrections`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
content: [{ type: "text", text: message }],
|
|
172
|
+
details: { count, threshold: settings.threshold },
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
renderCall(args, theme) {
|
|
177
|
+
const strength = args.strength || "pattern";
|
|
178
|
+
const strengthColor = strength === "strong" ? "warning" : "muted";
|
|
179
|
+
return theme.fg("toolTitle", "upskill-log ") + theme.fg(strengthColor, `[${strength}]`);
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
renderResult(result, _options, theme) {
|
|
183
|
+
const details = result.details as { count: number; threshold: number } | undefined;
|
|
184
|
+
if (!details) {
|
|
185
|
+
const text = result.content[0];
|
|
186
|
+
return theme.fg("success", text?.type === "text" ? text.text : "Logged");
|
|
187
|
+
}
|
|
188
|
+
const pct = Math.round((details.count / details.threshold) * 100);
|
|
189
|
+
const bar = "█".repeat(Math.min(10, Math.floor(pct / 10))) + "░".repeat(10 - Math.min(10, Math.floor(pct / 10)));
|
|
190
|
+
return theme.fg("success", `✓ Logged #${details.count} `) + theme.fg("dim", `[${bar}] ${details.count}/${details.threshold}`);
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ── /upskill-status Command ───────────────────
|
|
195
|
+
|
|
196
|
+
pi.registerCommand("upskill-status", {
|
|
197
|
+
description: "Show corrections count and threshold progress",
|
|
198
|
+
handler: async (_args, ctx) => {
|
|
199
|
+
const correctionsPath = getCorrectionsPath(ctx.cwd);
|
|
200
|
+
const corrections = loadCorrections(correctionsPath);
|
|
201
|
+
const settings = getSettings(ctx);
|
|
202
|
+
const count = corrections.length;
|
|
203
|
+
|
|
204
|
+
const strong = corrections.filter((c) => c.strength === "strong").length;
|
|
205
|
+
const pattern = corrections.filter((c) => c.strength === "pattern").length;
|
|
206
|
+
const totalTokens = corrections.reduce((sum, c) => sum + (c.tokens_wasted || 0), 0);
|
|
207
|
+
|
|
208
|
+
const status =
|
|
209
|
+
count >= settings.threshold
|
|
210
|
+
? "🟢 Ready to analyze"
|
|
211
|
+
: `🟡 ${settings.threshold - count} more needed`;
|
|
212
|
+
|
|
213
|
+
ctx.ui.notify(
|
|
214
|
+
`**Upskill Status**\n\n` +
|
|
215
|
+
`Corrections: ${count}/${settings.threshold}\n` +
|
|
216
|
+
`Strong: ${strong} | Pattern: ${pattern}\n` +
|
|
217
|
+
`Tokens wasted (est): ${totalTokens.toLocaleString()}\n` +
|
|
218
|
+
`Status: ${status}\n\n` +
|
|
219
|
+
`File: ${correctionsPath}`,
|
|
220
|
+
"info",
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ── /upskill-analyze Command ──────────────────
|
|
226
|
+
|
|
227
|
+
pi.registerCommand("upskill-analyze", {
|
|
228
|
+
description: "Analyze corrections and generate skills (runs in background)",
|
|
229
|
+
handler: async (_args, ctx) => {
|
|
230
|
+
const correctionsPath = getCorrectionsPath(ctx.cwd);
|
|
231
|
+
const corrections = loadCorrections(correctionsPath);
|
|
232
|
+
|
|
233
|
+
if (corrections.length === 0) {
|
|
234
|
+
ctx.ui.notify("No corrections logged. Use upskill-log tool or /upskill-backfill first.", "warning");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const settings = getSettings(ctx);
|
|
239
|
+
|
|
240
|
+
if (corrections.length < settings.threshold) {
|
|
241
|
+
const proceed = await ctx.ui.confirm(
|
|
242
|
+
"Below threshold",
|
|
243
|
+
`Only ${corrections.length} corrections (threshold: ${settings.threshold}). Analyze anyway?`,
|
|
244
|
+
);
|
|
245
|
+
if (!proceed) return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Read the analyze skill prompt
|
|
249
|
+
const skillPath = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "skills", "analyze", "SKILL.md");
|
|
250
|
+
|
|
251
|
+
let analyzePrompt = "";
|
|
252
|
+
try {
|
|
253
|
+
const skillContent = fs.readFileSync(skillPath, "utf-8");
|
|
254
|
+
// Extract content after frontmatter
|
|
255
|
+
const match = skillContent.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
256
|
+
if (match) analyzePrompt = match[1].trim();
|
|
257
|
+
} catch {
|
|
258
|
+
analyzePrompt = "Analyze the corrections and propose one high-impact edit.";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Build the prompt with corrections
|
|
262
|
+
const correctionsBlock = corrections.map((c, i) => {
|
|
263
|
+
const strength = c.strength === "strong" ? "⬤" : "○";
|
|
264
|
+
const tokens = c.tokens_wasted ? ` [${c.tokens_wasted} tokens]` : "";
|
|
265
|
+
return `${strength} [${i + 1}] ${c.failure} → ${c.correction}${tokens}\n Context: ${c.context || "none"}`;
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const fullPrompt =
|
|
269
|
+
`${analyzePrompt}\n\n` +
|
|
270
|
+
`## Corrections to Analyze (${corrections.length} entries)\n\n` +
|
|
271
|
+
`Legend: ⬤ = strong (single occurrence), ○ = pattern (needs 3x)\n\n` +
|
|
272
|
+
correctionsBlock.join("\n") +
|
|
273
|
+
`\n\n## Instructions\n\n` +
|
|
274
|
+
`1. Identify patterns in the corrections\n` +
|
|
275
|
+
`2. Select ONE edit with the largest impact on token usage\n` +
|
|
276
|
+
`3. Apply the surgical edit to the appropriate file\n` +
|
|
277
|
+
`4. Report what was changed and which corrections it addresses\n` +
|
|
278
|
+
`5. DO NOT remove the corrections file — user will review`;
|
|
279
|
+
|
|
280
|
+
// Spawn background process
|
|
281
|
+
const logPath = path.join(ctx.cwd, ".pi", "upskill-analysis.log");
|
|
282
|
+
const args = [
|
|
283
|
+
"-p",
|
|
284
|
+
"--no-session",
|
|
285
|
+
"--model",
|
|
286
|
+
ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : "anthropic/claude-sonnet-4-5",
|
|
287
|
+
fullPrompt,
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
ctx.ui.notify(`Spawning background analysis...\nLog: ${logPath}`, "info");
|
|
291
|
+
|
|
292
|
+
const proc = spawn("pi", args, {
|
|
293
|
+
detached: true,
|
|
294
|
+
stdio: ["ignore", fs.openSync(logPath, "w"), fs.openSync(logPath, "a")],
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
proc.unref();
|
|
298
|
+
|
|
299
|
+
ctx.ui.notify(
|
|
300
|
+
`Background analysis started.\n` +
|
|
301
|
+
`- Corrections: ${corrections.length}\n` +
|
|
302
|
+
`- Log: ${logPath}\n\n` +
|
|
303
|
+
`Check log for results. Use /upskill-status to see progress.`,
|
|
304
|
+
"info",
|
|
305
|
+
);
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@a-canary/pi-upskill",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Learn from failures, reduce token waste, improve automatically",
|
|
5
|
+
"keywords": ["pi-package"],
|
|
6
|
+
"author": "a-canary",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/a-canary/pi-upskill.git"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
14
|
+
"@mariozechner/pi-tui": "*",
|
|
15
|
+
"@sinclair/typebox": "*"
|
|
16
|
+
},
|
|
17
|
+
"pi": {
|
|
18
|
+
"extensions": ["./extension"],
|
|
19
|
+
"skills": ["./skills"]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analyze
|
|
3
|
+
description: Analyze logged corrections and generate one high-impact skill or rule edit. Use when threshold reached or user requests improvement.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /upskill-analyze — Pattern Analysis & Skill Generation
|
|
7
|
+
|
|
8
|
+
Review corrections, find patterns, apply ONE surgical edit for maximum token savings.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Threshold reached (default: 20 corrections)
|
|
13
|
+
- User explicitly requests analysis
|
|
14
|
+
- After backfill to process historical corrections
|
|
15
|
+
|
|
16
|
+
## Process
|
|
17
|
+
|
|
18
|
+
### Step 1: Read Corrections
|
|
19
|
+
|
|
20
|
+
Load all entries from `.pi/corrections.jsonl`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cat .pi/corrections.jsonl
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Step 2: Identify Patterns
|
|
27
|
+
|
|
28
|
+
Group corrections by:
|
|
29
|
+
|
|
30
|
+
**Type:**
|
|
31
|
+
- Same failure, same correction → strong pattern
|
|
32
|
+
- Same failure, different correction → needs synthesis
|
|
33
|
+
- Related failures → cluster by keyword/theme
|
|
34
|
+
|
|
35
|
+
**Priority:**
|
|
36
|
+
1. `strength: "strong"` entries — single occurrence sufficient
|
|
37
|
+
2. High `tokens_wasted` — prioritize for impact
|
|
38
|
+
3. Frequency — 3+ pattern entries = solid pattern
|
|
39
|
+
|
|
40
|
+
### Step 3: Select ONE Edit
|
|
41
|
+
|
|
42
|
+
Ask: "Which single edit would have the largest impact on token usage?"
|
|
43
|
+
|
|
44
|
+
Consider:
|
|
45
|
+
- How many corrections would this prevent?
|
|
46
|
+
- What's the total tokens wasted across those corrections?
|
|
47
|
+
- Is this a new skill or an edit to existing file?
|
|
48
|
+
|
|
49
|
+
**Edit targets (in order):**
|
|
50
|
+
1. `.pi/skills/` — Add new skill for workflow/pattern
|
|
51
|
+
2. `AGENTS.md` — Strengthen or add rule
|
|
52
|
+
3. `MEMORY.md` — Add durable fact
|
|
53
|
+
4. Existing skill — Strengthen wording
|
|
54
|
+
|
|
55
|
+
### Step 4: Generate Edit
|
|
56
|
+
|
|
57
|
+
For new skills, create `.pi/skills/{name}/SKILL.md`:
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
---
|
|
61
|
+
name: {name}
|
|
62
|
+
description: {when to use}
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# /{name} — {purpose}
|
|
66
|
+
|
|
67
|
+
## Pattern Source
|
|
68
|
+
|
|
69
|
+
{why this skill exists}
|
|
70
|
+
|
|
71
|
+
## Process
|
|
72
|
+
|
|
73
|
+
1. {step}
|
|
74
|
+
2. {step}
|
|
75
|
+
|
|
76
|
+
## Verification
|
|
77
|
+
|
|
78
|
+
{testable gate}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For existing files, propose surgical edit:
|
|
82
|
+
- Add bullet point to existing section
|
|
83
|
+
- Strengthen wording with "ALWAYS" / "NEVER"
|
|
84
|
+
- Add concrete example
|
|
85
|
+
|
|
86
|
+
### Step 5: Apply Edit
|
|
87
|
+
|
|
88
|
+
Use `edit` tool to apply the change. Record:
|
|
89
|
+
- File edited
|
|
90
|
+
- Lines changed
|
|
91
|
+
- Corrections addressed
|
|
92
|
+
|
|
93
|
+
### Step 6: Clean Up Corrections
|
|
94
|
+
|
|
95
|
+
Remove the corrections that contributed to this edit:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Keep only unprocessed corrections
|
|
99
|
+
# (those not matching the pattern we just addressed)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Leave a marker comment for traceability:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{"timestamp":"2025-03-13T02:00:00Z","event":"upskill","action":"added_skill","name":"run-tests-first","corrections_addressed":3,"tokens_saved":9000}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Step 7: Report Results
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Analysis complete.
|
|
112
|
+
|
|
113
|
+
**Edit Applied:**
|
|
114
|
+
- File: .pi/skills/run-tests-first/SKILL.md (created)
|
|
115
|
+
- Action: Added new skill
|
|
116
|
+
|
|
117
|
+
**Impact:**
|
|
118
|
+
- Corrections addressed: 3
|
|
119
|
+
- Tokens to save: ~9,000 per occurrence
|
|
120
|
+
|
|
121
|
+
**Removed from corrections.jsonl:**
|
|
122
|
+
- #4 "Committed without tests"
|
|
123
|
+
- #7 "Forgot to run tests"
|
|
124
|
+
- #12 "Skipped test step"
|
|
125
|
+
|
|
126
|
+
Remaining: 17 corrections
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Example Analysis Prompt
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
You are analyzing logged corrections to improve agent behavior.
|
|
133
|
+
|
|
134
|
+
## Corrections (12 entries)
|
|
135
|
+
|
|
136
|
+
⬤ [1] Committed without running tests → Always run tests first
|
|
137
|
+
Context: User reminder after broken CI
|
|
138
|
+
[3000 tokens]
|
|
139
|
+
|
|
140
|
+
○ [2] Used deprecated API → Check docs for current method
|
|
141
|
+
Context: Self-corrected after error
|
|
142
|
+
[1500 tokens]
|
|
143
|
+
|
|
144
|
+
○ [5] Used deprecated API → Check docs for current method
|
|
145
|
+
Context: Same pattern as #2
|
|
146
|
+
[2000 tokens]
|
|
147
|
+
|
|
148
|
+
...
|
|
149
|
+
|
|
150
|
+
Legend: ⬤ = strong (single = skill), ○ = pattern (needs 3x)
|
|
151
|
+
|
|
152
|
+
## Task
|
|
153
|
+
|
|
154
|
+
1. Find patterns in these corrections
|
|
155
|
+
2. Select ONE edit with largest token impact
|
|
156
|
+
3. Apply surgical edit to appropriate file
|
|
157
|
+
4. Report what was changed
|
|
158
|
+
5. List which corrections this addresses
|
|
159
|
+
|
|
160
|
+
Constraints:
|
|
161
|
+
- Only ONE edit
|
|
162
|
+
- Maximum impact on future token usage
|
|
163
|
+
- Prefer adding skills for workflows
|
|
164
|
+
- Prefer strengthening AGENTS.md for behavioral rules
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Verification
|
|
168
|
+
|
|
169
|
+
After edit:
|
|
170
|
+
```bash
|
|
171
|
+
# Skill exists and loads
|
|
172
|
+
pi --skill .pi/skills/{name} -p "help"
|
|
173
|
+
|
|
174
|
+
# Corrections reduced
|
|
175
|
+
cat .pi/corrections.jsonl | wc -l
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Configuration
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"upskill": {
|
|
183
|
+
"maxEditsPerAnalysis": 1,
|
|
184
|
+
"removeAddressedCorrections": true
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backfill
|
|
3
|
+
description: Scan past sessions and extract corrections to .pi/corrections.jsonl. Use for one-time historical analysis of failures.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /upskill-backfill — Historical Correction Extraction
|
|
7
|
+
|
|
8
|
+
Scan past conversation sessions to identify and log failures → corrections.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- First-time setup: extract patterns from past sessions
|
|
13
|
+
- After realizing a pattern of mistakes
|
|
14
|
+
- User requests historical analysis
|
|
15
|
+
|
|
16
|
+
## Process
|
|
17
|
+
|
|
18
|
+
### Step 1: Locate Session Files
|
|
19
|
+
|
|
20
|
+
Search for session files in common locations:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Pi sessions
|
|
24
|
+
ls ~/.pi/agent/sessions/**/*.jsonl
|
|
25
|
+
|
|
26
|
+
# Claude sessions (if available)
|
|
27
|
+
ls ~/.claude/sessions/**/*.jsonl 2>/dev/null
|
|
28
|
+
|
|
29
|
+
# OpenCode sessions
|
|
30
|
+
ls ~/.opencode/sessions/**/* 2>/dev/null
|
|
31
|
+
|
|
32
|
+
# Codex sessions
|
|
33
|
+
ls ~/.codex/sessions/**/* 2>/dev/null
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Step 2: Filter by Date
|
|
37
|
+
|
|
38
|
+
Default lookback: 7 days. Ask user to confirm or adjust.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Find files from last N days
|
|
42
|
+
find ~/.pi/agent/sessions -name "*.jsonl" -mtime -7
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 3: Extract Corrections
|
|
46
|
+
|
|
47
|
+
For each session file, look for:
|
|
48
|
+
|
|
49
|
+
**Explicit user corrections:**
|
|
50
|
+
- "no", "not that", "wrong", "actually", "I meant"
|
|
51
|
+
- "stop", "don't", "never", "always"
|
|
52
|
+
- "remember", "make sure to", "from now on"
|
|
53
|
+
|
|
54
|
+
**Strong signals (always/never/remember):**
|
|
55
|
+
- Mark as `strength: "strong"` — single occurrence sufficient for skill
|
|
56
|
+
|
|
57
|
+
**Self-corrections:**
|
|
58
|
+
- Multiple tool calls attempting the same thing
|
|
59
|
+
- Agent says "let me try again" or "that didn't work"
|
|
60
|
+
- Mark as `strength: "pattern"` — needs 3x for skill
|
|
61
|
+
|
|
62
|
+
### Step 4: Estimate Token Waste
|
|
63
|
+
|
|
64
|
+
For each correction:
|
|
65
|
+
1. Find where the mistake started
|
|
66
|
+
2. Find where correction was applied
|
|
67
|
+
3. Count messages/turns between
|
|
68
|
+
4. Rough estimate: ~500-2000 tokens per turn (varies by model)
|
|
69
|
+
|
|
70
|
+
### Step 5: Present Candidates
|
|
71
|
+
|
|
72
|
+
Show extracted corrections for review:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Found 12 potential corrections:
|
|
76
|
+
|
|
77
|
+
[1] STRONG: "Always run tests before committing"
|
|
78
|
+
Context: User reminded after broken commit
|
|
79
|
+
Tokens: ~3000
|
|
80
|
+
|
|
81
|
+
[2] PATTERN: "Don't use deprecated API"
|
|
82
|
+
Context: Self-corrected after error
|
|
83
|
+
Tokens: ~1500
|
|
84
|
+
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
Review? [y/n/select]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Step 6: Log Approved Corrections
|
|
91
|
+
|
|
92
|
+
For each approved correction, use the `upskill-log` tool or write directly:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{"timestamp":"2025-03-13T00:00:00Z","failure":"Committed without tests","correction":"Always run tests first","context":"User reminder after broken CI","tokens_wasted":3000,"source":"user","strength":"strong"}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Step 7: Report Summary
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Backfill complete:
|
|
102
|
+
- Sessions scanned: 47
|
|
103
|
+
- Corrections found: 12
|
|
104
|
+
- Logged: 8 (4 skipped)
|
|
105
|
+
- Total tokens wasted: ~15,000
|
|
106
|
+
|
|
107
|
+
Run /upskill-status to see progress.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Output
|
|
111
|
+
|
|
112
|
+
Appends to `.pi/corrections.jsonl`.
|
|
113
|
+
|
|
114
|
+
## Verification
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
cat .pi/corrections.jsonl | wc -l
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Session Parsing Details
|
|
121
|
+
|
|
122
|
+
### Pi JSONL Format
|
|
123
|
+
|
|
124
|
+
Each line is a JSON object. Look for:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{"type":"message","message":{"role":"user","content":[{"type":"text","text":"no, that's wrong"}]}}
|
|
128
|
+
{"type":"message","message":{"role":"assistant","content":[{"type":"text","text":"Let me try again..."}]}}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
User messages with corrections → extract failure/correction pair.
|
|
132
|
+
|
|
133
|
+
### Other Formats
|
|
134
|
+
|
|
135
|
+
Claude/OpenCode/Codex: Parse similarly, looking for user correction signals and agent self-corrections.
|
|
136
|
+
|
|
137
|
+
## Options
|
|
138
|
+
|
|
139
|
+
User can specify:
|
|
140
|
+
- `--lookback N` — Days to look back (default: 7)
|
|
141
|
+
- `--source pi|claude|all` — Which session sources to scan
|
|
142
|
+
- `--auto` — Skip review, log all found corrections
|