pi-skill-shiori 0.6.10 → 0.6.12

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 CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [0.6.12] - 2026-09-30
6
+
7
+ ### Changed
8
+
9
+ - Update `@earendil-works/pi-*` dependencies to `0.99.1`.
10
+
11
+ ## [0.6.11] - 2026-09-21
12
+
13
+ ### Fixed
14
+
15
+ - Rank curated trigger matches ahead of heuristic description matches when enforcing candidate limits. Derived single-token query variants can score a perfect `1.00` from substring matches while trigger matches are fixed at `0.95`, so a genuine trigger match could be dropped by the candidate cap. The same ordering now applies at both cap sites, because the per-variant cap runs before expanded retrieval merges variants and a match dropped there can never be restored.
16
+ - Apply the original query's `exclude` triggers across merged variants in expanded retrieval. A derived single-token variant could drop the context that made an exclude match (`do not use auth` becomes `auth`), which reintroduced an excluded skill.
17
+
5
18
  ## [0.6.10] - 2026-09-21
6
19
 
7
20
  ### Fixed
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.10` 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.
22
+ `0.6.12` 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
 
@@ -37,7 +37,7 @@ Pi Skill Shiori is a [Pi](https://pi.dev/) package for vaults or projects with m
37
37
  ### Global install from npm
38
38
 
39
39
  ```bash
40
- pi install npm:pi-skill-shiori@0.6.10
40
+ pi install npm:pi-skill-shiori@0.6.12
41
41
  ```
42
42
 
43
43
  Without a version pin:
@@ -49,7 +49,7 @@ pi install npm:pi-skill-shiori
49
49
  ### Project-local install from npm
50
50
 
51
51
  ```bash
52
- pi install -l npm:pi-skill-shiori@0.6.10
52
+ pi install -l npm:pi-skill-shiori@0.6.12
53
53
  ```
54
54
 
55
55
  Without a version pin:
@@ -61,7 +61,7 @@ pi install -l npm:pi-skill-shiori
61
61
  ### Install from GitHub
62
62
 
63
63
  ```bash
64
- pi install git:github.com/eiei114/pi-skill-shiori@v0.6.10
64
+ pi install git:github.com/eiei114/pi-skill-shiori@v0.6.12
65
65
  pi install -l git:github.com/eiei114/pi-skill-shiori
66
66
  ```
67
67
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-skill-shiori",
3
- "version": "0.6.10",
3
+ "version": "0.6.12",
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",
@@ -54,9 +54,9 @@
54
54
  "typebox": "*"
55
55
  },
56
56
  "devDependencies": {
57
- "@earendil-works/pi-ai": "^0.85.0",
58
- "@earendil-works/pi-coding-agent": "^0.84.1",
59
- "@earendil-works/pi-tui": "^0.85.1",
57
+ "@earendil-works/pi-ai": "^0.99.1",
58
+ "@earendil-works/pi-coding-agent": "^0.99.1",
59
+ "@earendil-works/pi-tui": "^0.99.1",
60
60
  "@types/node": "^26.0.0",
61
61
  "tsx": "^4.22.4",
62
62
  "typebox": "latest",
@@ -1,5 +1,5 @@
1
1
  import { buildRetrievalQueries } from "./query.js";
2
- import { retrieveCandidates } from "./retrieval.js";
2
+ import { compareCandidates, excludedSkillNames, retrieveCandidates } from "./retrieval.js";
3
3
  import type { ShioriPolicy, SkillCandidate, SkillRecord } from "./types.js";
4
4
 
5
5
  interface RetrievalIndex {
@@ -14,17 +14,21 @@ export function retrieveCandidatesExpanded(
14
14
  ): SkillCandidate[] {
15
15
  const byName = new Map<string, SkillCandidate>();
16
16
  const max = policy.candidateInjection.maxCandidates;
17
+ // Apply the original query's excludes across the merged set. A derived
18
+ // single-token variant can drop the context that made an exclude match
19
+ // ("do not use auth" becomes "auth"), which would otherwise let an excluded
20
+ // skill back in once variants are merged.
21
+ const excluded = excludedSkillNames(query, skills, policy);
17
22
 
18
23
  for (const variant of buildRetrievalQueries(query)) {
19
24
  for (const candidate of retrieveCandidates(variant, skills, policy, index)) {
25
+ if (excluded.has(candidate.skill.name)) continue;
20
26
  const previous = byName.get(candidate.skill.name);
21
27
  byName.set(candidate.skill.name, preferExpandedCandidate(previous, candidate));
22
28
  }
23
29
  }
24
30
 
25
- return [...byName.values()]
26
- .sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name))
27
- .slice(0, max);
31
+ return [...byName.values()].sort(compareCandidates).slice(0, max);
28
32
  }
29
33
 
30
34
  /** Keep trigger badges when expanded query variants also score the description higher. */
package/src/retrieval.ts CHANGED
@@ -34,10 +34,58 @@ export function retrieveCandidates(
34
34
  }
35
35
 
36
36
  return [...candidatesByName.values()]
37
- .sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name))
37
+ .sort(compareCandidates)
38
38
  .slice(0, policy.candidateInjection.maxCandidates);
39
39
  }
40
40
 
41
+ /**
42
+ * Order candidates so curated trigger matches rank ahead of heuristic
43
+ * description matches.
44
+ *
45
+ * Trigger matches carry a fixed score (0.95, see `evaluateSkill`), while
46
+ * description matches can reach a higher score from short query tokens that
47
+ * happen to appear as substrings — "is" in "history", "fix" in "prefix".
48
+ * Sorting by score alone therefore lets that noise push a genuine trigger match
49
+ * past a candidate cap, where it is dropped without a trace. A trigger is an
50
+ * explicit statement that the skill is relevant, so it outranks a heuristic
51
+ * substring match even when the heuristic scored higher.
52
+ *
53
+ * Every cap site must use this comparator: the per-variant cap inside
54
+ * `retrieveCandidates` runs before expanded retrieval merges variants, so a
55
+ * trigger match dropped there can never be restored downstream.
56
+ */
57
+ export function compareCandidates(a: SkillCandidate, b: SkillCandidate): number {
58
+ const triggerDelta = Number(b.reason === "trigger") - Number(a.reason === "trigger");
59
+ if (triggerDelta !== 0) return triggerDelta;
60
+ return b.score - a.score || a.skill.name.localeCompare(b.skill.name);
61
+ }
62
+
63
+ /**
64
+ * Skill names the original query excludes via policy exclude triggers.
65
+ *
66
+ * `retrieveCandidates` evaluates excludes against whichever query string it
67
+ * receives. Expanded retrieval passes derived variants, and a derived variant can
68
+ * drop the surrounding context that made an exclude match ("do not use auth"
69
+ * becomes "auth"), so the exclusion no longer applies and the skill leaks back
70
+ * in. Callers that merge variants should apply the original query's excludes
71
+ * across the merged set.
72
+ */
73
+ export function excludedSkillNames(
74
+ query: string,
75
+ skills: SkillRecord[],
76
+ policy: ShioriPolicy,
77
+ ): Set<string> {
78
+ const normalizedQuery = normalize(query);
79
+ const excluded = new Set<string>();
80
+ for (const skill of skills) {
81
+ const excludes = policy.skills[skill.name]?.triggers?.exclude ?? [];
82
+ if (excludes.some((trigger) => includesNormalized(normalizedQuery, trigger))) {
83
+ excluded.add(skill.name);
84
+ }
85
+ }
86
+ return excluded;
87
+ }
88
+
41
89
  function evaluateSkill(
42
90
  skill: SkillRecord,
43
91
  normalizedQuery: string,