pi-skill-shiori 0.6.12 → 0.6.13
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 +6 -0
- package/Docs/usage.md +11 -0
- package/README.md +5 -4
- package/package.json +2 -2
- package/src/index.ts +22 -5
- package/src/metrics.ts +65 -1
- package/src/types.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## [0.6.13] - 2026-10-05
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add session-local `/shiori:stats` timing diagnostics for inventory refresh and skill retrieval, with additive JSON counters and no prompt, skill-body, or cross-session analytics storage.
|
|
10
|
+
|
|
5
11
|
## [0.6.12] - 2026-09-30
|
|
6
12
|
|
|
7
13
|
### Changed
|
package/Docs/usage.md
CHANGED
|
@@ -167,6 +167,17 @@ command: offers=1, loaded=2, zeroHits=1
|
|
|
167
167
|
top skills: auth-helper(1/2), deploy-kit(1/1)
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## Timing diagnostics
|
|
171
|
+
|
|
172
|
+
Run `/shiori:stats` to see a compact timing line alongside the existing recommendation feedback summary. The line separates inventory refresh cost from retrieval cost; the JSON payload exposes the same machine-readable counters under `timing.inventoryRefresh` and `timing.retrieval`:
|
|
173
|
+
|
|
174
|
+
- `count` — successful operations recorded in the current session
|
|
175
|
+
- `lastMs` — duration of the most recent successful operation
|
|
176
|
+
- `totalMs` — cumulative duration of successful operations
|
|
177
|
+
- `maxMs` — longest successful operation
|
|
178
|
+
|
|
179
|
+
Inventory refresh timing covers the successful session-start, manual `/shiori:reload`, and automatic refresh rebuilds. Retrieval timing covers successful auto-injection, `shiori_recommend`, and `/shiori:recommend` retrievals. These are in-memory, session-local diagnostics: Shiori does not store prompt text, full queries, skill bodies, or cross-session analytics.
|
|
180
|
+
|
|
170
181
|
## Agent tools
|
|
171
182
|
|
|
172
183
|
Shiori registers two tools the agent can call after candidates appear:
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ For maintenance priorities and the phased plan, see [`ROADMAP.md`](ROADMAP.md).
|
|
|
19
19
|
|
|
20
20
|
Pi Skill Shiori is a [Pi](https://pi.dev/) package for vaults or projects with many Agent Skills where the default catalog becomes noisy and expensive. It hides the normal skill catalog when safe, recommends triggerable skills with compact summaries, and loads full `SKILL.md` bodies on demand.
|
|
21
21
|
|
|
22
|
-
`0.6.
|
|
22
|
+
`0.6.13` is the current working release. Prompt-boundary suppression is intentionally conservative: if Shiori cannot safely recognize a catalog boundary, it leaves the prompt untouched and warns instead of deleting too much.
|
|
23
23
|
|
|
24
24
|
## Features
|
|
25
25
|
|
|
@@ -31,13 +31,14 @@ Pi Skill Shiori is a [Pi](https://pi.dev/) package for vaults or projects with m
|
|
|
31
31
|
- **SQLite FTS retrieval** — indexes skill names, descriptions, and policy triggers with `node:sqlite` + FTS5, with token-match fallback.
|
|
32
32
|
- **Recommendation reason badges** — compact `[trigger]`, `[description]`, and `[low match]` labels on surfaced skills.
|
|
33
33
|
- **Session-local feedback metrics** — `/shiori:stats` tracks offers, loads, and follow-through without storing prompts.
|
|
34
|
+
- **Timing diagnostics** — `/shiori:stats` separates inventory refresh and retrieval cost without storing prompts, skill bodies, or cross-session analytics.
|
|
34
35
|
|
|
35
36
|
## Install
|
|
36
37
|
|
|
37
38
|
### Global install from npm
|
|
38
39
|
|
|
39
40
|
```bash
|
|
40
|
-
pi install npm:pi-skill-shiori@0.6.
|
|
41
|
+
pi install npm:pi-skill-shiori@0.6.13
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Without a version pin:
|
|
@@ -49,7 +50,7 @@ pi install npm:pi-skill-shiori
|
|
|
49
50
|
### Project-local install from npm
|
|
50
51
|
|
|
51
52
|
```bash
|
|
52
|
-
pi install -l npm:pi-skill-shiori@0.6.
|
|
53
|
+
pi install -l npm:pi-skill-shiori@0.6.13
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
Without a version pin:
|
|
@@ -61,7 +62,7 @@ pi install -l npm:pi-skill-shiori
|
|
|
61
62
|
### Install from GitHub
|
|
62
63
|
|
|
63
64
|
```bash
|
|
64
|
-
pi install git:github.com/eiei114/pi-skill-shiori@v0.6.
|
|
65
|
+
pi install git:github.com/eiei114/pi-skill-shiori@v0.6.13
|
|
65
66
|
pi install -l git:github.com/eiei114/pi-skill-shiori
|
|
66
67
|
```
|
|
67
68
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-skill-shiori",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.13",
|
|
4
4
|
"description": "Pi extension that reduces Agent Skill catalog tokens with policy-based skill retrieval and on-demand skill loading.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
]
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"yaml": "^2.
|
|
48
|
+
"yaml": "^2.9.1"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@earendil-works/pi-ai": "*",
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { findDuplicates, formatInventoryRoots, isAutoRefreshEnabled, isInventory
|
|
|
7
7
|
import type { SkillIndex } from "./indexer.js";
|
|
8
8
|
import { loadRecommendedSkills } from "./load-skills.js";
|
|
9
9
|
import { formatPlanningKickoffMessage, isPlanningIntent } from "./planning-kickoff.js";
|
|
10
|
-
import { createStats } from "./metrics.js";
|
|
10
|
+
import { createStats, formatTimingSummary, measureTiming } from "./metrics.js";
|
|
11
11
|
import { createRecommendationFeedback } from "./recommendation-feedback.js";
|
|
12
12
|
import { getPolicyPath, loadPolicyWithSource, loadedPolicySignature, type LoadedPolicy } from "./policy.js";
|
|
13
13
|
import {
|
|
@@ -48,7 +48,10 @@ export default function piSkillShiori(pi: ExtensionAPI) {
|
|
|
48
48
|
async function reload(cwd: string, currentPolicy?: LoadedPolicy): Promise<SkillIndex> {
|
|
49
49
|
currentPolicy ??= await loadPolicyWithSource(cwd);
|
|
50
50
|
const policy = currentPolicy.policy;
|
|
51
|
-
const inventory = await
|
|
51
|
+
const inventory = await measureTiming(
|
|
52
|
+
stats.timing.inventoryRefresh,
|
|
53
|
+
() => refreshSkillInventory(cwd, policy, index),
|
|
54
|
+
);
|
|
52
55
|
index = inventory.index;
|
|
53
56
|
inventoryFingerprint = inventory.fingerprint;
|
|
54
57
|
inventoryRoots = inventory.roots;
|
|
@@ -139,7 +142,10 @@ export default function piSkillShiori(pi: ExtensionAPI) {
|
|
|
139
142
|
ctx.ui.notify("Pi Skill Shiori: Skill Catalog suppression pattern not found; leaving system prompt unchanged.", "warning");
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
const candidates =
|
|
145
|
+
const candidates = await measureTiming(
|
|
146
|
+
stats.timing.retrieval,
|
|
147
|
+
() => retrieveCandidatesExpanded(event.prompt, current.skills, current.policy, current),
|
|
148
|
+
);
|
|
143
149
|
if (candidates.length === 0) {
|
|
144
150
|
stats.zeroCandidateCount += 1;
|
|
145
151
|
recommendationFeedback.recordZeroCandidates("auto-inject");
|
|
@@ -174,7 +180,10 @@ export default function piSkillShiori(pi: ExtensionAPI) {
|
|
|
174
180
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
175
181
|
const current = await ensureIndex(ctx.cwd);
|
|
176
182
|
const policy = withRecommendLimits(current);
|
|
177
|
-
const candidates =
|
|
183
|
+
const candidates = await measureTiming(
|
|
184
|
+
stats.timing.retrieval,
|
|
185
|
+
() => retrieveCandidatesExpanded(params.task, current.skills, policy, current),
|
|
186
|
+
);
|
|
178
187
|
const candidateNames = candidates.map((candidate) => candidate.skill.name);
|
|
179
188
|
const loaded = await loadRecommendedSkills(candidates);
|
|
180
189
|
recommendationFeedback.recordRecommendation(
|
|
@@ -340,7 +349,10 @@ export default function piSkillShiori(pi: ExtensionAPI) {
|
|
|
340
349
|
}
|
|
341
350
|
|
|
342
351
|
const policy = withRecommendLimits(current);
|
|
343
|
-
const candidates =
|
|
352
|
+
const candidates = await measureTiming(
|
|
353
|
+
stats.timing.retrieval,
|
|
354
|
+
() => retrieveCandidatesExpanded(query, current.skills, policy, current),
|
|
355
|
+
);
|
|
344
356
|
const candidateNames = candidates.map((candidate) => candidate.skill.name);
|
|
345
357
|
const planning = isPlanningIntent(query);
|
|
346
358
|
const reviewOnly = action === RECOMMEND_REVIEW_ONLY;
|
|
@@ -417,11 +429,16 @@ export default function piSkillShiori(pi: ExtensionAPI) {
|
|
|
417
429
|
handler: async (_args, ctx) => {
|
|
418
430
|
const payload = {
|
|
419
431
|
...stats,
|
|
432
|
+
timing: {
|
|
433
|
+
inventoryRefresh: { ...stats.timing.inventoryRefresh },
|
|
434
|
+
retrieval: { ...stats.timing.retrieval },
|
|
435
|
+
},
|
|
420
436
|
recommendationFeedback: recommendationFeedback.summarize(),
|
|
421
437
|
};
|
|
422
438
|
ctx.ui.notify(
|
|
423
439
|
[
|
|
424
440
|
recommendationFeedback.formatSummary(),
|
|
441
|
+
formatTimingSummary(stats.timing),
|
|
425
442
|
"",
|
|
426
443
|
JSON.stringify(payload, null, 2),
|
|
427
444
|
].join("\n"),
|
package/src/metrics.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import type { ShioriStats, ShioriTiming, TimingCounter } from "./types.js";
|
|
3
|
+
|
|
4
|
+
const TIMING_DECIMAL_PLACES = 2;
|
|
5
|
+
const TIMING_SCALE = 10 ** TIMING_DECIMAL_PLACES;
|
|
6
|
+
|
|
7
|
+
type MonotonicClock = () => number;
|
|
8
|
+
|
|
9
|
+
export function createTimingCounter(): TimingCounter {
|
|
10
|
+
return {
|
|
11
|
+
lastMs: 0,
|
|
12
|
+
totalMs: 0,
|
|
13
|
+
maxMs: 0,
|
|
14
|
+
count: 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createTiming(): ShioriTiming {
|
|
19
|
+
return {
|
|
20
|
+
inventoryRefresh: createTimingCounter(),
|
|
21
|
+
retrieval: createTimingCounter(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
2
24
|
|
|
3
25
|
export function createStats(): ShioriStats {
|
|
4
26
|
return {
|
|
@@ -12,5 +34,47 @@ export function createStats(): ShioriStats {
|
|
|
12
34
|
suppressionStatus: "disabled",
|
|
13
35
|
inventoryRefreshCount: 0,
|
|
14
36
|
inventoryAutoRefreshCount: 0,
|
|
37
|
+
timing: createTiming(),
|
|
15
38
|
};
|
|
16
39
|
}
|
|
40
|
+
|
|
41
|
+
export function recordTiming(counter: TimingCounter, elapsedMs: number): void {
|
|
42
|
+
const duration = roundMilliseconds(elapsedMs);
|
|
43
|
+
counter.lastMs = duration;
|
|
44
|
+
counter.totalMs = roundMilliseconds(counter.totalMs + duration);
|
|
45
|
+
counter.maxMs = Math.max(counter.maxMs, duration);
|
|
46
|
+
counter.count += 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function measureTiming<T>(
|
|
50
|
+
counter: TimingCounter,
|
|
51
|
+
operation: () => T | PromiseLike<T>,
|
|
52
|
+
now: MonotonicClock = () => performance.now(),
|
|
53
|
+
): Promise<T> {
|
|
54
|
+
const startedAt = now();
|
|
55
|
+
const result = await operation();
|
|
56
|
+
recordTiming(counter, now() - startedAt);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatTimingSummary(timing: ShioriTiming): string {
|
|
61
|
+
return [
|
|
62
|
+
"Timing (session-local)",
|
|
63
|
+
`inventory refresh: ${formatTimingCounter(timing.inventoryRefresh)}`,
|
|
64
|
+
`retrieval: ${formatTimingCounter(timing.retrieval)}`,
|
|
65
|
+
].join(" | ");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatTimingCounter(counter: TimingCounter): string {
|
|
69
|
+
const count = Number.isFinite(counter.count) && counter.count > 0 ? Math.trunc(counter.count) : 0;
|
|
70
|
+
return `count=${count} last=${formatMilliseconds(counter.lastMs)} total=${formatMilliseconds(counter.totalMs)} max=${formatMilliseconds(counter.maxMs)}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatMilliseconds(value: number): string {
|
|
74
|
+
return `${roundMilliseconds(value).toFixed(TIMING_DECIMAL_PLACES)}ms`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function roundMilliseconds(value: number): number {
|
|
78
|
+
if (!Number.isFinite(value) || value <= 0) return 0;
|
|
79
|
+
return Math.round((value + Number.EPSILON) * TIMING_SCALE) / TIMING_SCALE;
|
|
80
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -48,6 +48,18 @@ export interface SkillCandidate {
|
|
|
48
48
|
reason: RecommendationReasonKind | null;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface TimingCounter {
|
|
52
|
+
lastMs: number;
|
|
53
|
+
totalMs: number;
|
|
54
|
+
maxMs: number;
|
|
55
|
+
count: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ShioriTiming {
|
|
59
|
+
inventoryRefresh: TimingCounter;
|
|
60
|
+
retrieval: TimingCounter;
|
|
61
|
+
}
|
|
62
|
+
|
|
51
63
|
export interface ShioriStats {
|
|
52
64
|
inventoryCount: number;
|
|
53
65
|
duplicateCount: number;
|
|
@@ -60,4 +72,5 @@ export interface ShioriStats {
|
|
|
60
72
|
lastReloadAt?: string;
|
|
61
73
|
inventoryRefreshCount: number;
|
|
62
74
|
inventoryAutoRefreshCount: number;
|
|
75
|
+
timing: ShioriTiming;
|
|
63
76
|
}
|