@zhushanwen/pi-exec-skills 0.2.1 → 0.2.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-exec-skills",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Shared skill acquisition library for Pi extensions — disk-scanning discovery over pi's four skill roots (settings overrides + plan-exec frontmatter marker + name dedup) and the pi-registry execution gate (resolveSkills). Shared library, not a Pi extension.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"typecheck": "npx tsc --noEmit",
|
|
38
|
-
"test": "vitest run"
|
|
38
|
+
"test": "vitest run --pool=threads"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -326,9 +326,9 @@ describe("isEnabledByOverrides(overrides 三级优先级,pi 同构)", () =
|
|
|
326
326
|
});
|
|
327
327
|
|
|
328
328
|
// globToRegExp 未测分支(minimatch 子集重实现,与 pi 求值的偏差域):? 单字符 /
|
|
329
|
-
// [...] 字符类(含取反)/
|
|
329
|
+
// [...] 字符类(含取反)/ 未结束 ] 字面量 / 正则元字符转义——经公开入口锁语义,
|
|
330
330
|
// 任一分支回归即红(false = 禁用命中,true = 默认启用未命中)。
|
|
331
|
-
it("? / 字符类 / 取反 /
|
|
331
|
+
it("? / 字符类 / 取反 / 未结束 ] / 元字符转义分支", () => {
|
|
332
332
|
const baseDir = join(world.root, "base");
|
|
333
333
|
const filePath = join(baseDir, "skills", "demo", "SKILL.md");
|
|
334
334
|
// ? = 单字符且不跨 /:dem? 命中 o;demo? 差一字符不命中;skills?demo 不跨 / 不命中
|
|
@@ -341,7 +341,7 @@ describe("isEnabledByOverrides(overrides 三级优先级,pi 同构)", () =
|
|
|
341
341
|
// 取反类:[!a-z] 排除小写字母 → d 不命中;[!0-9] 排除数字 → d 命中
|
|
342
342
|
expect(isEnabledByOverrides(filePath, ["!skills/[!a-z]emo/SKILL.md"], baseDir)).toBe(true);
|
|
343
343
|
expect(isEnabledByOverrides(filePath, ["!skills/[!0-9]emo/SKILL.md"], baseDir)).toBe(false);
|
|
344
|
-
//
|
|
344
|
+
// 未结束 ]:[ 按字面量处理(不吞后续段),demo 目录无 [ → 不命中
|
|
345
345
|
expect(isEnabledByOverrides(filePath, ["!skills/demo[/SKILL.md"], baseDir)).toBe(true);
|
|
346
346
|
// 字符类单字:dem[o] 命中(dem 后是 o);dem[w] 不命中(dem 后非 w)
|
|
347
347
|
expect(isEnabledByOverrides(filePath, ["!skills/dem[o]/SKILL.md"], baseDir)).toBe(false);
|
|
@@ -371,7 +371,7 @@ describe("pi 实装对拍(loadSkillsFromDir 语义锚)", () => {
|
|
|
371
371
|
* - beta:无 frontmatter name → 目录名 fallback(⑥)
|
|
372
372
|
* - no-desc:无 description → ③ 必填门排除
|
|
373
373
|
* - nested/deep/gamma:递归遍历
|
|
374
|
-
* - loose.md:根级散 .md(loadSkillsFromDir
|
|
374
|
+
* - loose.md:根级散 .md(loadSkillsFromDir 收集,⑦登记清单内)
|
|
375
375
|
* - ignored/:.gitignore 排除(①)
|
|
376
376
|
* - node_modules 与 .hidden:loader 内建排除
|
|
377
377
|
*/
|
|
@@ -396,7 +396,7 @@ describe("pi 实装对拍(loadSkillsFromDir 语义锚)", () => {
|
|
|
396
396
|
const { root, agentDir } = makeAnchorRoot();
|
|
397
397
|
try {
|
|
398
398
|
const piResult = loadSkillsFromDir({ dir: join(agentDir, "skills"), source: "anchor" });
|
|
399
|
-
// pi 实装语义快照——pi bump
|
|
399
|
+
// pi 实装语义快照——pi bump 改单根规则时此处红灯(检查边界 = 单根规则漂移)
|
|
400
400
|
expect(piResult.skills.map((s) => s.name).sort()).toEqual(["beta", "custom-alpha", "gamma", "loose"]);
|
|
401
401
|
expect(piResult.skills.some((s) => s.name === "no-desc")).toBe(false); // ③ description 必填门
|
|
402
402
|
expect(piResult.skills.some((s) => s.filePath.includes("ignored"))).toBe(false); // ① gitignore
|
|
@@ -430,7 +430,7 @@ describe("pi 实装对拍(loadSkillsFromDir 语义锚)", () => {
|
|
|
430
430
|
}
|
|
431
431
|
});
|
|
432
432
|
|
|
433
|
-
it("散 .md 形态:skillEntryPath =
|
|
433
|
+
it("散 .md 形态:skillEntryPath = 文件路径本身(指引路径不悬空,⑦登记清单内)", () => {
|
|
434
434
|
const { root, agentDir } = makeAnchorRoot();
|
|
435
435
|
try {
|
|
436
436
|
const detected = detectExecSkills({
|
|
@@ -7,7 +7,7 @@ import { normalizeSkillName, resolveSkills } from "../resolve-skills.js";
|
|
|
7
7
|
/**
|
|
8
8
|
* fake skill 命令条目(SlashCommandInfo 形态:source === 'skill' + sourceInfo.path)。
|
|
9
9
|
* name 带 pi 命名空间前缀(`skill:<name>`)——真实 pi.getCommands() 枚举 skill 类
|
|
10
|
-
*
|
|
10
|
+
* 命令即此形态,归一化比对的检查目标。
|
|
11
11
|
*/
|
|
12
12
|
const SKILL_COMMANDS = [
|
|
13
13
|
{ name: "skill:tech-design", source: "skill", sourceInfo: { path: "/skills/tech-design/SKILL.md" } },
|
|
@@ -36,7 +36,7 @@ describe("normalizeSkillName(skill: 命名空间前缀剥离)", () => {
|
|
|
36
36
|
|
|
37
37
|
describe("resolveSkills(执行门禁:pi 注册表枚举比对,双向剥 skill: 前缀归一)", () => {
|
|
38
38
|
it("resolves prefixed enum entries from bare-name input (short name in resolved)", () => {
|
|
39
|
-
//
|
|
39
|
+
// 无前缀输入命中带前缀枚举(输入范围:用户用自然技能名)
|
|
40
40
|
const resolution = resolveSkills(makePi(), ["tech-design", "code review"]);
|
|
41
41
|
expect(resolution.ok).toBe(true);
|
|
42
42
|
if (resolution.ok) {
|
package/src/exec-skills.ts
CHANGED
|
@@ -14,11 +14,11 @@ import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
|
14
14
|
const logger = getLogger("pi-exec-skills");
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* 技能目录扫描共享能力(ADR-
|
|
17
|
+
* 技能目录扫描共享能力(ADR-0096 落地包):项目内一切「发现可执行 skill」的磁盘
|
|
18
18
|
* 扫描单源,禁止各扩展自扫目录或直用 pi 注册表快照形成第二实现(pi 的
|
|
19
19
|
* getCommands 返回 resource loader 启动加载集——reload 才刷新的滞后快照)。
|
|
20
20
|
*
|
|
21
|
-
* 与 ./resolve-skills.js 的两段消费语义闭环(ADR-
|
|
21
|
+
* 与 ./resolve-skills.js 的两段消费语义闭环(ADR-0096):
|
|
22
22
|
* - 本模块 = 「发现」:自研扫描直读磁盘,技能安装即时可见;用于向用户提议
|
|
23
23
|
* (表单列出可用执行方式)。
|
|
24
24
|
* - resolve-skills = 「执行门禁」:注入前经 pi 注册表确认 pi 实际认得该技能。
|
|
@@ -26,7 +26,7 @@ const logger = getLogger("pi-exec-skills");
|
|
|
26
26
|
* 检测语义 = 「向用户提议的执行 skill」,以 pi 本体加载集为基线做意图/信任对齐:
|
|
27
27
|
* - 单根枚举复用 pi 公开导出 loadSkillsFromDir——单根内横切规则(ignore / description
|
|
28
28
|
* 必填门 / name 派生 / 递归遍历 / symlink / node_modules / 隐藏文件)随 pi 升级
|
|
29
|
-
*
|
|
29
|
+
* 自动同步,零漂移风险(对照表①③⑥⑦ 的 SKILL.md 目录形态侧);
|
|
30
30
|
* - 跨根组装自实现(pi 的四根拼装逻辑未导出,对照表②④⑤):根序四枚按 pi 本体
|
|
31
31
|
* `package-manager.js addAutoDiscoveredResources`——`.pi/skills` → 祖先链
|
|
32
32
|
* `.agents/skills`(近→远,git root 级含)→ `<agentDir>/skills` → `~/.agents/skills`
|
|
@@ -45,7 +45,7 @@ const logger = getLogger("pi-exec-skills");
|
|
|
45
45
|
* plan-exec skill,出现时补第五根)。
|
|
46
46
|
*
|
|
47
47
|
* 降级规格(检测失败最坏后果 = skill 选项空集,绝不允许炸消费方的核心交互闭环;
|
|
48
|
-
* pi 对坏 skill 也只产 diagnostics 不中断加载):每根 existsSync
|
|
48
|
+
* pi 对坏 skill 也只产 diagnostics 不中断加载):每根 existsSync 检查 + 整根
|
|
49
49
|
* try/catch(EACCES 等任何 fs 错 → 跳过该根 + warn);单 skill frontmatter 读/解析
|
|
50
50
|
* 失败 → 跳过该项 + warn;settings 读取/解析失败 → 视为无 overrides。
|
|
51
51
|
*/
|
package/src/index.ts
CHANGED
package/src/resolve-skills.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* 技能执行门禁共享能力(ADR-
|
|
4
|
+
* 技能执行门禁共享能力(ADR-0096 落地包):steer/注入前的「pi 实际认得」确认,
|
|
5
5
|
* 走 pi 注册表(pi.getCommands)。与同包 exec-skills 模块构成两段消费语义闭环——
|
|
6
6
|
* 扫描(发现,自研直读磁盘,技能热装即时可见)≠ 本模块(执行门禁,pi 注册表
|
|
7
7
|
* 是执行权威);pi 不认得的技能只提示不注入。
|
|
@@ -25,7 +25,7 @@ export interface SkillRef { // oe-exempt:20261001:framework:exec-skills 共享
|
|
|
25
25
|
/**
|
|
26
26
|
* 技能名归一:剥掉 pi 命令命名空间前导 `skill:` 前缀,得到自然技能名。
|
|
27
27
|
* pi.getCommands() 枚举 skill 类命令时 name 带 `skill:` 前缀(如 `skill:tech-design`),
|
|
28
|
-
* 这是 pi
|
|
28
|
+
* 这是 pi 的实现细节,不得泄漏到输入范围——slash `--skills` 与消费方工具的 skills 参数
|
|
29
29
|
* 都用自然技能名。枚举侧与输入侧双向剥前缀后比对,兼容两种形态。
|
|
30
30
|
*/
|
|
31
31
|
export function normalizeSkillName(name: string): string {
|
|
@@ -41,7 +41,7 @@ export function normalizeSkillName(name: string): string {
|
|
|
41
41
|
* available + 空 missing——available 正是调用方报错文案的恢复动作输入(错误 →
|
|
42
42
|
* 可用清单 → 重试闭环),missing 空让报错器走「没有给技能名」分支。自 plan 包
|
|
43
43
|
* enter.ts 迁入(plan-mode-audit-remediation 批次 4②b):该分支是 slash 空
|
|
44
|
-
* `--skills`
|
|
44
|
+
* `--skills` 报错复用单源的活契约,非永不命中的防御机制;既有调用方先判非空才调的,行为不受影响。
|
|
45
45
|
*/
|
|
46
46
|
export function resolveSkills(pi: ExtensionAPI, requested: string[]): SkillResolution {
|
|
47
47
|
const skillCommands = pi.getCommands().filter((c) => c.source === "skill");
|