@aeondave/pi-skill-optimizer 3.1.0 → 3.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/CHANGELOG.md +14 -0
- package/README.md +5 -1
- package/docs/ARCHITECTURE.md +11 -0
- package/package.json +2 -2
- package/src/index.ts +8 -5
- package/src/optimize.ts +19 -19
- package/src/skills.ts +1 -1
- package/src/usage.ts +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes are documented here.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## 3.1.1 - 2026-10-08
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Optimize the native Pi transcript before provider conversion, keeping worker notifications, shell
|
|
12
|
+
output and summaries out of human-intent routing and prefetch insertion.
|
|
13
|
+
- Preserve structured system sections and tool declarations; respect provider scoping when the
|
|
14
|
+
request has no selected model.
|
|
15
|
+
- Label prefetch as catalog metadata, not loaded instructions or usage evidence. Emit its actual
|
|
16
|
+
catalog fingerprint and report selected skills only when an overlay was inserted.
|
|
17
|
+
- Recognize native Pi skill-activation calls without double-counting `skill_search` usage.
|
|
18
|
+
|
|
5
19
|
## 3.1.0 - 2026-10-03
|
|
6
20
|
|
|
7
21
|
### Breaking
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ That's enough to start. Optionally run `/skill-optimizer init` to generate routi
|
|
|
37
37
|
| Need | Command |
|
|
38
38
|
| --- | --- |
|
|
39
39
|
| Install only in this project | `pi install -l npm:@aeondave/pi-skill-optimizer` |
|
|
40
|
-
| Pin the current release | `pi install npm:@aeondave/pi-skill-optimizer@3.1.
|
|
40
|
+
| Pin the current release | `pi install npm:@aeondave/pi-skill-optimizer@3.1.1` |
|
|
41
41
|
| Update an unpinned install | `pi update npm:@aeondave/pi-skill-optimizer` |
|
|
42
42
|
|
|
43
43
|
Restart after updating. Pinned installs stay pinned until you explicitly install another version.
|
|
@@ -61,6 +61,10 @@ Say you have hundreds of skills, but you're asking Pi to fix a database migratio
|
|
|
61
61
|
|
|
62
62
|
AUTO keeps a compact, stable index of **all eligible names**, adds relevant descriptions beside your latest request, and exposes `skill_search` for loading complete instructions. The stable index does not change when your question changes.
|
|
63
63
|
|
|
64
|
+
Prefetch is a catalog, not loaded instructions or evidence that a skill was used. Worker updates,
|
|
65
|
+
extension notifications and summaries do not replace your request as the routing signal or receive
|
|
66
|
+
its overlay; there is no need to acknowledge the catalog.
|
|
67
|
+
|
|
64
68
|
| Area | What AUTO does | What stays available |
|
|
65
69
|
| --- | --- | --- |
|
|
66
70
|
| Skills | Shortens catalog descriptions; prefetches relevant ones | Every eligible name and exact full-instruction loading |
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -20,8 +20,19 @@ The index contains no filesystem paths. With the same catalog, profile, and conf
|
|
|
20
20
|
|
|
21
21
|
AUTO extracts intent only from genuine human text in Anthropic, OpenAI Chat and Responses, Gemini, Mistral, and Pi's structured `pi-messages` request shapes. A catalog may live in a system or developer block, but neither is treated as human intent. Assistant text, tool or function results, injected context, image metadata, and empty input cannot create a ranking signal.
|
|
22
22
|
|
|
23
|
+
At runtime, catalog optimization and usage extraction run in Pi's `context_with_system` hook,
|
|
24
|
+
before provider conversion erases custom-message provenance. Native `custom`, `bashExecution`,
|
|
25
|
+
`branchSummary`, `compactionSummary` and `toolResult` messages remain intact and cannot become
|
|
26
|
+
the query or overlay target. Structured system sections and tool declarations are preserved;
|
|
27
|
+
request-local clones do not modify persisted conversation entries.
|
|
28
|
+
|
|
23
29
|
Ranking uses exact matching, BM25F, bounded name and alias typo recovery, reciprocal-rank fusion, diversity, profile evidence, and decayed usage evidence. It scores full source descriptions, then appends a bounded set of verbatim skill names and descriptions (not the full `SKILL.md` instructions) to the latest human block. The overlay is separate from the stable prefix, fingerprinted, idempotent, and omitted when there is no valid target.
|
|
24
30
|
|
|
31
|
+
The resolver labels prefetch as discovery metadata, not loaded instructions or usage evidence.
|
|
32
|
+
Diagnostics count only overlays actually inserted. Usage follows explicit human mentions and skill
|
|
33
|
+
activations; the `skill_search` executor records its own loads/resources, so transcript scanning
|
|
34
|
+
does not count those calls twice. Catalog selections and assistant acknowledgments are not usage.
|
|
35
|
+
|
|
25
36
|
Optional init-generated activation tags distinguish specialists, workflow methods
|
|
26
37
|
and behavioral discipline. An admitted specialist can bring one activity-matched
|
|
27
38
|
supporting skill into the ordinary prefetch budget; this does not recursively
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeondave/pi-skill-optimizer",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Less repeated context for Pi: local skill routing, on-demand tools, and recoverable output reduction.",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"extensions": [
|
|
82
82
|
"./src/index.ts"
|
|
83
83
|
],
|
|
84
|
-
"image": "https://raw.githubusercontent.com/AeonDave/pi-skill-optimizer/v3.1.
|
|
84
|
+
"image": "https://raw.githubusercontent.com/AeonDave/pi-skill-optimizer/v3.1.1/assets/banner.png"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/src/index.ts
CHANGED
|
@@ -1084,13 +1084,14 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1084
1084
|
}
|
|
1085
1085
|
});
|
|
1086
1086
|
|
|
1087
|
-
pi.on("
|
|
1087
|
+
pi.on("context_with_system", (event, ctx) => {
|
|
1088
1088
|
if (isDisabled(ctx.cwd)) return;
|
|
1089
1089
|
const config = getRuntimeConfig(ctx);
|
|
1090
1090
|
const providers = config.providers.length > 0 ? config.providers : undefined;
|
|
1091
|
-
if (providers && ctx.model
|
|
1091
|
+
if (providers && (!ctx.model || !providers.includes(ctx.model.provider))) return;
|
|
1092
1092
|
const state = ensureSnapshot(ctx);
|
|
1093
|
-
const
|
|
1093
|
+
const payload = { messages: event.messages };
|
|
1094
|
+
const transformed = optimizePayload(payload, {
|
|
1094
1095
|
...state.config,
|
|
1095
1096
|
profile: state.profile,
|
|
1096
1097
|
usagePrior: buildUsagePrior(state.usage, {
|
|
@@ -1109,7 +1110,7 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1109
1110
|
addPending(getStatsFilePath(ctx.cwd), { skills: transformed.removedChars, tools: 0, output: 0 });
|
|
1110
1111
|
}
|
|
1111
1112
|
if (registry) {
|
|
1112
|
-
const evidence = collectSkillUsageEvidence(
|
|
1113
|
+
const evidence = collectSkillUsageEvidence(payload, registry.skills.map((skill) => skill.name)).slice(-MAX_SEEN_EVIDENCE);
|
|
1113
1114
|
const names: string[] = [];
|
|
1114
1115
|
for (const item of evidence) {
|
|
1115
1116
|
if (seenEvidence.has(item.key)) continue;
|
|
@@ -1124,7 +1125,9 @@ export default function skillOptimizer(pi: ExtensionAPI) {
|
|
|
1124
1125
|
}
|
|
1125
1126
|
maybeFlush(ctx);
|
|
1126
1127
|
updateStatus(ctx);
|
|
1127
|
-
return transformed.next ===
|
|
1128
|
+
return transformed.next === payload
|
|
1129
|
+
? undefined
|
|
1130
|
+
: { messages: (transformed.next as typeof payload).messages };
|
|
1128
1131
|
});
|
|
1129
1132
|
|
|
1130
1133
|
pi.on("context", (event, ctx) => {
|
package/src/optimize.ts
CHANGED
|
@@ -361,25 +361,24 @@ export function optimizePayload<T>(
|
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
let addedPrefetchChars = 0;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
364
|
+
let injectedSkills: string[] = [];
|
|
365
|
+
const prefetchPlan = planSkillPrefetch(catalogSkills, query, {
|
|
366
|
+
profile: config.profile,
|
|
367
|
+
usagePrior: config.usagePrior,
|
|
368
|
+
always: uniqueInOrder([
|
|
369
|
+
...(config.profile?.critical ?? []),
|
|
370
|
+
...(config.alwaysSkills ?? []),
|
|
371
|
+
]),
|
|
372
|
+
never: config.excludeSkills,
|
|
373
|
+
targetTopK: config.prefetchTarget,
|
|
374
|
+
minTopK: config.prefetchMin,
|
|
375
|
+
maxTopK: config.prefetchMax,
|
|
376
|
+
fullRenderBudgetChars: config.prefetchBudgetChars,
|
|
377
|
+
fuzzyCandidateLimit: config.fuzzyCandidateLimit,
|
|
378
|
+
});
|
|
379
|
+
const selectedSkills = prefetchPlan.selected;
|
|
379
380
|
if (selectedSkills.length > 0) {
|
|
380
|
-
const appendix = renderSkillPrefetch(
|
|
381
|
-
selected: selectedSkills,
|
|
382
|
-
} as Parameters<typeof renderSkillPrefetch>[0]);
|
|
381
|
+
const appendix = renderSkillPrefetch(prefetchPlan);
|
|
383
382
|
if (appendix) {
|
|
384
383
|
const withPrefetch = appendToLatestHumanInput(
|
|
385
384
|
next,
|
|
@@ -389,6 +388,7 @@ export function optimizePayload<T>(
|
|
|
389
388
|
if (withPrefetch !== next) {
|
|
390
389
|
addedPrefetchChars = serializedLength(withPrefetch) - serializedLength(next);
|
|
391
390
|
next = withPrefetch;
|
|
391
|
+
injectedSkills = selectedSkills.map((skill) => skill.name);
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
394
|
}
|
|
@@ -416,7 +416,7 @@ export function optimizePayload<T>(
|
|
|
416
416
|
removedChars: originalLength - finalLength,
|
|
417
417
|
baseRemovedChars,
|
|
418
418
|
addedPrefetchChars: Math.max(0, addedPrefetchChars),
|
|
419
|
-
selected:
|
|
419
|
+
selected: injectedSkills,
|
|
420
420
|
catalogNames: uniqueInOrder(catalogNames),
|
|
421
421
|
fingerprints: uniqueInOrder(fingerprints),
|
|
422
422
|
budget,
|
package/src/skills.ts
CHANGED
|
@@ -1058,7 +1058,7 @@ export function renderSkillPrefetch(plan: SkillPrefetchPlan): string {
|
|
|
1058
1058
|
if (plan.selected.length === 0) return "";
|
|
1059
1059
|
return [
|
|
1060
1060
|
`<skill_prefetch catalog_sha256="${plan.fingerprint}">`,
|
|
1061
|
-
" <skill_resolver>
|
|
1061
|
+
" <skill_resolver>Catalog metadata only, not loaded instructions or usage evidence. Load a relevant full skill with skill_search(action=load, name); no acknowledgment is needed.</skill_resolver>",
|
|
1062
1062
|
...plan.selected.map(renderFullSkill),
|
|
1063
1063
|
"</skill_prefetch>",
|
|
1064
1064
|
].join("\n");
|
package/src/usage.ts
CHANGED
|
@@ -91,16 +91,18 @@ export function collectSkillUsageEvidence(payload: unknown, selectedSkills: read
|
|
|
91
91
|
const rawMessage = rawMessages[messageIndex];
|
|
92
92
|
if (!rawMessage || typeof rawMessage !== "object") continue;
|
|
93
93
|
const message = rawMessage as Record<string, unknown>;
|
|
94
|
+
if (message.role !== "assistant") continue;
|
|
94
95
|
if (!Array.isArray(message.content)) continue;
|
|
95
96
|
for (let blockIndex = 0; blockIndex < message.content.length; blockIndex++) {
|
|
96
97
|
const rawBlock = message.content[blockIndex];
|
|
97
98
|
if (!rawBlock || typeof rawBlock !== "object") continue;
|
|
98
99
|
const block = rawBlock as Record<string, unknown>;
|
|
99
|
-
if (block.type !== "tool_use" || typeof block.name !== "string") continue;
|
|
100
|
+
if ((block.type !== "tool_use" && block.type !== "toolCall") || typeof block.name !== "string") continue;
|
|
100
101
|
const toolName = block.name.toLowerCase();
|
|
101
102
|
if (toolName !== "skill" && toolName !== "activate_skill") continue;
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
const rawInput = block.type === "toolCall" ? block.arguments : block.input;
|
|
104
|
+
if (!rawInput || typeof rawInput !== "object") continue;
|
|
105
|
+
const input = rawInput as Record<string, unknown>;
|
|
104
106
|
let serialized = "";
|
|
105
107
|
try { serialized = JSON.stringify(input); } catch { serialized = String(input); }
|
|
106
108
|
const sourceId = objectId(block);
|