@x-otto/skill 0.0.1-alpha.2 → 0.0.1-alpha.4

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 CHANGED
@@ -8,23 +8,23 @@ Manages discovery, parsing, matching, and registration of SKILL.md skill files.
8
8
 
9
9
  | Module | Function |
10
10
  | -------------- | -------------------------------------------- |
11
- | SkillRegistry | Skill registration/discovery/lifecycle events |
11
+ | SkillRegistry | Skill registration/discovery/lifecycle events + usage recording |
12
12
  | SkillDiscovery | Scans .otto/skills/ + ~/.otto/skills/ |
13
13
  | SkillParser | Parses SKILL.md YAML frontmatter + Markdown |
14
- | SkillMatcher | Multi-factor scoring (name 0.3 + description 0.5 + tags 0.2) |
14
+ | SkillUsageStore | Per-skill execution usage aggregation (count / success-fail / duration) |
15
15
 
16
16
  ## Directory Overview
17
17
 
18
18
  ```
19
19
  src/
20
- types.ts # core types
21
- skill-registry.ts # registry
22
- skill-discovery.ts # auto-discovery
23
- skill-parser.ts # parser
24
- skill-matcher.ts # matcher
25
- skill-context.ts # execution context
20
+ types.ts # core types
21
+ skill-registry.ts # registry
22
+ skill-discovery.ts # auto-discovery
23
+ skill-parser.ts # parser
24
+ skill-usage.ts # per-skill execution usage aggregation
25
+ registry-skill-provider.ts # SkillProvider adapter (load/execute bridge for upper layers)
26
26
  index.ts
27
- tests/ # 3 test files
27
+ tests/ # 5 test files
28
28
  ```
29
29
 
30
30
  ## Development Commands
package/dist/index.d.ts CHANGED
@@ -1,5 +1,53 @@
1
1
  import { TypedEventEmitter } from "@x-otto/shared";
2
2
 
3
+ //#region src/skill-usage.d.ts
4
+ /**
5
+ * skill-usage.ts —— RFC-327 M3-01:skill 执行 usage 的计数存储与写入端口。
6
+ *
7
+ * RFC-318 技能回路的抽取侧此前只有 `skill.executed` 事件(emit 即弃,零落库)。
8
+ * 本模块补上 usage 落库:
9
+ * - `SkillUsageRecorder` 是写入端口——SkillRegistry 经此落库;宿主可注入自定义
10
+ * 实现(转发到 EvolutionSignalStore / 磁盘持久化等)。
11
+ * - `SkillUsageStore` 是默认进程内聚合计数实现(执行次数 / 成功失败 / 累计时长)。
12
+ *
13
+ * 确定性纪律:本层**不直呼 Date.now**——时间戳 `at` 由调用方(SkillRegistry,
14
+ * 经注入时钟端口)传入。
15
+ */
16
+ interface SkillUsageRecord {
17
+ /** skill 名(`definition.metadata.name`)。 */
18
+ name: string;
19
+ /** 本次执行是否成功(失败 = 未找到 / 已禁用等未实际执行路径)。 */
20
+ success: boolean;
21
+ /** 本次执行耗时(ms)。失败路径为 0(未实际执行)。 */
22
+ durationMs: number;
23
+ /** 执行时刻——调用方注入时钟刻度(确定性纪律,本层不直呼 Date.now)。 */
24
+ at: number;
25
+ }
26
+ /** usage 写入端口——SkillRegistry 经此落库。 */
27
+ interface SkillUsageRecorder {
28
+ record(usage: SkillUsageRecord): void;
29
+ }
30
+ /** 单 skill 的聚合计数(RFC-327 M3-01 的"执行次数/成功失败/时长"三要素)。 */
31
+ interface SkillUsageAggregate {
32
+ name: string;
33
+ /** 总执行次数(成功 + 失败)。 */
34
+ executions: number;
35
+ successes: number;
36
+ failures: number;
37
+ /** 累计耗时(ms)。 */
38
+ totalDurationMs: number;
39
+ /** 最近一次执行时刻(注入时钟刻度)。 */
40
+ lastExecutedAt: number;
41
+ }
42
+ /** 进程内聚合计数实现——SkillRegistry 缺省落库点。 */
43
+ declare class SkillUsageStore implements SkillUsageRecorder {
44
+ private readonly aggregates;
45
+ record(usage: SkillUsageRecord): void;
46
+ get(name: string): SkillUsageAggregate | undefined;
47
+ all(): SkillUsageAggregate[];
48
+ get size(): number;
49
+ }
50
+ //#endregion
3
51
  //#region src/types.d.ts
4
52
  interface SkillMetadata {
5
53
  /** kebab-case,例如 "test-driven-development"。 */
@@ -7,7 +55,6 @@ interface SkillMetadata {
7
55
  /** 描述触发条件而非 skill 做什么;建议以 "Use when..." 开头,最长 500 字符。 */
8
56
  description: string;
9
57
  version?: string;
10
- author?: string;
11
58
  tags?: string[];
12
59
  dependencies?: string[];
13
60
  /** auto = 根据 description 自动匹配;manual = 仅显式调用。 */
@@ -50,14 +97,17 @@ interface SkillLibraryConfig {
50
97
  id: string;
51
98
  dir: string;
52
99
  }>;
53
- /** 禁用的 skill 名称列表 */
54
- disabled?: string[];
55
100
  }
56
101
  interface SkillExecutionContext {
57
102
  /** skill 名称 */
58
103
  skillName: string;
59
104
  /** 工作目录 */
60
105
  workspaceDir: string;
106
+ /**
107
+ * 会话归属(RFC-385 §11 授权扩展,向后兼容可选)——宿主侧注入当前 sessionId,
108
+ * 供 skill.usage 进化信号按会话切片(第④层 turn 级质量标注联结)。不传 = 无会话归属。
109
+ */
110
+ sessionId?: string;
61
111
  }
62
112
  interface SkillExecutionResult {
63
113
  success: boolean;
@@ -67,26 +117,17 @@ interface SkillExecutionResult {
67
117
  error?: string;
68
118
  }
69
119
  interface SkillEvents {
70
- 'skill.discovered': (info: {
71
- source: SkillSource;
72
- count: number;
73
- }) => void;
74
- 'skill.loaded': (info: {
75
- name: string;
76
- source: SkillSource;
77
- }) => void;
78
- 'skill.unloaded': (info: {
79
- name: string;
80
- }) => void;
81
- 'skill.error': (info: {
82
- name: string;
83
- error: string;
84
- }) => void;
120
+ /** skill 执行完成(成功路径 emit)——供 usage/观测消费。 */
85
121
  'skill.executed': (info: {
86
122
  name: string;
87
123
  success: boolean;
88
124
  durationMs: number;
89
125
  }) => void;
126
+ /** RFC review D7:隐式调用命中(bash 命令里脚本执行/文档读取被识别为 skill 引用)。 */
127
+ 'skill.invoked': (info: {
128
+ name: string;
129
+ kind: 'script' | 'document';
130
+ }) => void;
90
131
  }
91
132
  /**
92
133
  * 上层(如 App)通过此接口注入 skill 能力。
@@ -98,8 +139,8 @@ interface SkillProvider {
98
139
  name?: string;
99
140
  error?: string;
100
141
  }>;
101
- /** 按名解析 → 注入正文(resolve(name)→body),无模板/参数语义。 */
102
- execute(name: string): Promise<SkillExecutionResult>;
142
+ /** 按名解析 → 注入正文(resolve(name)→body),无模板/参数语义。sessionId 可选(RFC-385 M1:skill.usage 信号会话归属)。 */
143
+ execute(name: string, sessionId?: string): Promise<SkillExecutionResult>;
103
144
  }
104
145
  //#endregion
105
146
  //#region src/skill-registry.d.ts
@@ -110,6 +151,36 @@ interface SkillRegistryOptions {
110
151
  library?: SkillLibraryConfig;
111
152
  /** 禁用的 skill 名称列表 */
112
153
  disabled?: string[];
154
+ /**
155
+ * RFC-327 M3-01:skill 执行 usage 的写入端口(落库点)。缺省用进程内
156
+ * `SkillUsageStore`(执行次数/成功失败/时长聚合),宿主可注入自定义实现
157
+ * (转发到 EvolutionSignalStore / 磁盘持久化)。
158
+ */
159
+ usageStore?: SkillUsageRecorder;
160
+ /**
161
+ * 时钟端口——usage 时间戳与 execute 计时走注入时钟(确定性纪律,不直呼
162
+ * Date.now)。缺省回退 Date.now(宿主未注入时向后兼容)。
163
+ */
164
+ now?: () => number;
165
+ /**
166
+ * RFC-383 M1(第③层进化链):进化信号统一出口(可选)。注入后 execute 的
167
+ * recordUsage 旁路 append skill.usage 信号(覆盖成功/未找到/禁用三出口)——
168
+ * 供内化决策器(M2)与第④层消费。不走 usageStore 注入(避免 instanceof 门控
169
+ * 使聚合读取静默失效);本端口是旁路,默认 SkillUsageStore 聚合保持原样。
170
+ *
171
+ * 形状 = EvolutionSignalSink(@x-otto/memory)的结构子集——skill 是叶子包,
172
+ * 不依赖 memory(避免引入 ai/persistence 依赖链);宿主注入 memory 的
173
+ * EvolutionSignalStore 时结构兼容(duck typing)。
174
+ */
175
+ evolutionSignals?: {
176
+ append(record: {
177
+ kind: 'skill.usage';
178
+ key: string;
179
+ value: number; /** 会话归属(RFC-385 §11 透传,可选)——execute 路径有 context.sessionId 即带。 */
180
+ sessionId?: string;
181
+ detail?: Record<string, unknown>;
182
+ }): void;
183
+ };
113
184
  }
114
185
  declare class SkillRegistry extends TypedEventEmitter<SkillEvents> {
115
186
  private skills;
@@ -118,10 +189,25 @@ declare class SkillRegistry extends TypedEventEmitter<SkillEvents> {
118
189
  private disabledSet;
119
190
  /** 可变的 plugin 目录列表(reloadCapabilities 更新为新安装 plugin 的 dirs) */
120
191
  private pluginDirs?;
192
+ /**
193
+ * RFC-396 M1:上一轮 discover **实际写入 map** 的 name 集(不含被「磁盘不覆盖手动
194
+ * 注册」跳过的名)。reconcile 用它算差集——只清理「上轮 discover 写入、本轮未写入」
195
+ * 的条目,手动注册(inline via loadSkill / computed via axis handler,不经 discover)
196
+ * 永不进集、永不被清理。绝不按 source.type 判别(computed 用 type:'plugin',会误删)。
197
+ */
198
+ private discoverRegisteredNames;
199
+ /** RFC-327 M3-01:usage 落库端口(缺省 = 内部 SkillUsageStore)。 */
200
+ private readonly usageStore;
201
+ /** RFC-383 M1:进化信号旁路(可选,见构造 options 注释)。 */
202
+ private readonly evolutionSignals?;
203
+ private readonly now;
121
204
  constructor({
122
205
  workspaceDir,
123
206
  library,
124
- disabled
207
+ disabled,
208
+ usageStore,
209
+ now,
210
+ evolutionSignals
125
211
  }: SkillRegistryOptions);
126
212
  /**
127
213
  * 更新 plugin 贡献的 skill 目录列表(用于 reloadCapabilities 热刷新)。
@@ -132,8 +218,14 @@ declare class SkillRegistry extends TypedEventEmitter<SkillEvents> {
132
218
  dir: string;
133
219
  }>): void;
134
220
  /**
135
- * 发现并注册所有可用 skill
136
- * 扫描项目本地 + 全局目录
221
+ * 发现并注册所有可用 skill,差集 reconcile(RFC-396 M1)。
222
+ *
223
+ * 扫描项目本地 + 全局 + 插件目录,以本轮扫描为真源:
224
+ * - 新增/仍在的磁盘 skill → 注册(同名不覆盖手动注册的 inline/computed);
225
+ * - 上一轮 discover 写入、本轮未再写入的 → unregister(修「删目录/卸插件后残留」)。
226
+ *
227
+ * 手动注册(loadSkill 的 inline、axis handler 的 computed)不经本方法,永不进
228
+ * `discoverRegisteredNames`,永不被 reconcile 清理。
137
229
  */
138
230
  discover(): Promise<void>;
139
231
  /**
@@ -157,6 +249,15 @@ declare class SkillRegistry extends TypedEventEmitter<SkillEvents> {
157
249
  */
158
250
  get(name: string): RegisteredSkill | undefined;
159
251
  execute(context: SkillExecutionContext): SkillExecutionResult;
252
+ /**
253
+ * RFC-327 M3-01 / RFC-396 M2:读取单 skill 的 usage 聚合(执行次数/成功失败/时长)。
254
+ * duck-typing:注入的 usageStore 若实现了 `get`(如默认 SkillUsageStore 或宿主的
255
+ * 组合 recorder),即读取;否则(纯 `{record}` 写端口)返回 undefined。去 instanceof
256
+ * 门控——避免注入实现了聚合读取的自定义 recorder 后读取被静默门死(内化决策器 M2 消费)。
257
+ */
258
+ getSkillUsage(name: string): SkillUsageAggregate | undefined;
259
+ /** RFC-327 M3-01 / RFC-396 M2:全部 skill 的 usage 聚合(duck-typing:usageStore 实现 `all` 即读,否则空数组)。 */
260
+ getAllSkillUsage(): SkillUsageAggregate[];
160
261
  /**
161
262
  * 禁用 skill
162
263
  */
@@ -186,6 +287,20 @@ declare class SkillRegistry extends TypedEventEmitter<SkillEvents> {
186
287
  * CATALOG_DESCRIPTION_MAX_CHARS——渐进披露:目录常驻、正文按需取回,不应本末倒置)
187
288
  */
188
289
  buildCatalog(): string;
290
+ /**
291
+ * RFC review D7:命令隐式 skill 调用检测(codex invocation.rs 移植)。
292
+ *
293
+ * 把 shell 命令里出现的「skill 脚本执行」或「skill 文档读取」路径与已注册 skill 匹配:
294
+ * - script:路径落在 skill 的 scripts/ 子目录;
295
+ * - document:路径指向 skill 的 SKILL.md(目录自身或文件路径)。
296
+ *
297
+ * 命中即返回该 skill(并 emit 'skill.invoked' + 记录 usage success/duration=0——
298
+ * 隐式调用是「发现」,不是真实执行,仅作观测信号供技能回路/进化观测消费)。
299
+ * 多命中取第一个;未命中返回 undefined。
300
+ */
301
+ detectImplicitInvocation(command: string, cwd: string): RegisteredSkill | undefined;
302
+ /** RFC review D7:隐式调用记录——usage 落库(duration 0,观测信号)+ 事件。 */
303
+ private recordInvocation;
189
304
  /**
190
305
  * 获取 skill 总数
191
306
  */
@@ -201,12 +316,18 @@ declare function parseSkillContent(content: string, filePath: string, directory:
201
316
  //#endregion
202
317
  //#region src/skill-discovery.d.ts
203
318
  /**
204
- * 扫描指定目录列表,发现所有有效 skill
319
+ * discovery 产出的单条目:解析成功的 definition + source,或(M3)解析失败时带 `error`
320
+ * 的占位条目(definition.metadata.name 为防撞占位名、body 空,供 discover 注册 error 态)。
205
321
  */
206
- declare function discoverSkills(config: SkillLibraryConfig, workspaceDir: string): Promise<Map<string, {
322
+ type DiscoveredEntry = {
207
323
  definition: SkillDefinition;
208
324
  source: SkillSource;
209
- }>>;
325
+ error?: string;
326
+ };
327
+ /**
328
+ * 扫描指定目录列表,发现所有有效 skill
329
+ */
330
+ declare function discoverSkills(config: SkillLibraryConfig, workspaceDir: string): Promise<Map<string, DiscoveredEntry>>;
210
331
  /**
211
332
  * 获取默认的全局 skill 目录列表。
212
333
  *
@@ -218,5 +339,36 @@ declare function getDefaultGlobalSkillDirs(): string[];
218
339
  //#region src/registry-skill-provider.d.ts
219
340
  declare function createRegistrySkillProvider(registry: SkillRegistry, workspaceDir: string): SkillProvider;
220
341
  //#endregion
221
- export { type RegisteredSkill, type SkillDefinition, type SkillEvents, type SkillExecutionContext, type SkillExecutionResult, type SkillLibraryConfig, type SkillMetadata, type SkillProvider, SkillRegistry, type SkillRegistryOptions, type SkillSource, type SkillSourceType, type SkillStatus, createRegistrySkillProvider, createSkillRegistry, discoverSkills, getDefaultGlobalSkillDirs, parseSkillContent };
342
+ //#region src/skill-invocation.d.ts
343
+ /**
344
+ * skill-invocation.ts —— RFC review D7:命令隐式 skill 调用检测(纯函数)。
345
+ *
346
+ * 参照 codex `ext/skills/src/invocation.rs`(`implicit_skill_accesses_for_command`):
347
+ * shell 命令里「运行 skill scripts/ 下的脚本」或「读取 skill 的 SKILL.md 文档」都被
348
+ * 视为隐式 skill 调用——不需模型显式走 skill 工具,用户/模型直接在 bash 里引用即命中。
349
+ *
350
+ * 纯函数、无 IO、不读时钟(符合引擎确定性约束);分词不引第三方 shlex(最小引号/转义
351
+ * tokenizer,覆盖 bash 常见形态;无法完美分词的退化空白切分与 codex 同语义)。
352
+ */
353
+ /** 一次命令中识别出的 skill 文件访问(脚本执行或文档读取)。 */
354
+ type ImplicitSkillAccessKind = 'script' | 'document';
355
+ interface ImplicitSkillAccess {
356
+ kind: ImplicitSkillAccessKind;
357
+ /** 命令中出现的路径原文(相对 cwd)。 */
358
+ path: string;
359
+ }
360
+ /**
361
+ * 最小 shlex 式分词:处理单/双引号与反斜杠转义;引号不闭合时按剩余原文收尾。
362
+ * 退化空白切分与 codex shlex::split 失败路径同语义。
363
+ */
364
+ declare function tokenizeCommand(command: string): string[];
365
+ /**
366
+ * 识别一次 shell 命令中静态可判定的 skill 访问(脚本执行 + 文档读取)。
367
+ *
368
+ * @param command 完整命令串。
369
+ * @returns 访问列表(脚本执行优先;去重保持首次出现顺序)。
370
+ */
371
+ declare function implicitSkillAccessesForCommand(command: string): ImplicitSkillAccess[];
372
+ //#endregion
373
+ export { type ImplicitSkillAccess, type ImplicitSkillAccessKind, type RegisteredSkill, type SkillDefinition, type SkillEvents, type SkillExecutionContext, type SkillExecutionResult, type SkillLibraryConfig, type SkillMetadata, type SkillProvider, SkillRegistry, type SkillRegistryOptions, type SkillSource, type SkillSourceType, type SkillStatus, type SkillUsageAggregate, type SkillUsageRecord, type SkillUsageRecorder, SkillUsageStore, createRegistrySkillProvider, createSkillRegistry, discoverSkills, getDefaultGlobalSkillDirs, implicitSkillAccessesForCommand, parseSkillContent, tokenizeCommand };
222
374
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/skill-registry.ts","../src/skill-parser.ts","../src/skill-discovery.ts","../src/registry-skill-provider.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/skill-usage.ts","../src/types.ts","../src/skill-registry.ts","../src/skill-parser.ts","../src/skill-discovery.ts","../src/registry-skill-provider.ts","../src/skill-invocation.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- import{TypedEventEmitter as e,createLogger as t}from"@x-otto/shared";import*as n from"node:fs";import*as r from"node:path";import{isClaudeCompatEnabled as i}from"@x-otto/env";import{parse as a}from"yaml";const o=t(`@x-otto/skill:parser`),s=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;function c(e,t,n,r=[]){let i=s.exec(e.trim());if(!i)throw Error(`SKILL.md at "${t}" has no valid YAML frontmatter`);let[,o,c]=i,u;try{u=a(o)}catch(e){throw Error(`Failed to parse YAML frontmatter in "${t}": ${e.message}`)}if(!u||typeof u!=`object`)throw Error(`YAML frontmatter in "${t}" is not a valid object`);return{metadata:l(u,t),body:(c??``).trim(),directory:n,filePath:t,supportingFiles:r}}function l(e,t){let n=e.name;if(typeof n!=`string`||n.length===0)throw Error(`SKILL.md at "${t}" missing required "name" field in frontmatter`);/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(n)||o.warn(`Skill name "%s" in "%s" does not follow kebab-case convention`,n,t);let r=e.description;if(typeof r!=`string`||r.length===0)throw Error(`SKILL.md at "${t}" missing required "description" field in frontmatter`);let i=`${n}\n${r}`;return i.length>1024&&o.warn(`Skill "%s" frontmatter exceeds 1024 character limit (%d chars)`,n,i.length),{name:n,description:r,version:typeof e.version==`string`?e.version:void 0,author:typeof e.author==`string`?e.author:void 0,tags:Array.isArray(e.tags)?e.tags.filter(e=>typeof e==`string`):void 0,dependencies:Array.isArray(e.dependencies)?e.dependencies.filter(e=>typeof e==`string`):void 0,trigger:e.trigger===`manual`?`manual`:`auto`,priority:typeof e.priority==`number`?e.priority:100}}const u=t(`@x-otto/skill:discovery`),d=`SKILL.md`;async function f(e,t){let n=new Map,i=e.projectDirs??[`.otto/skills`];for(let e of i){let i=r.resolve(t,e),a=await p(i,{type:`project`,root:i});for(let[e,t]of a)n.set(e,t)}let a=e.globalDirs??[];for(let e of a){let t=await p(e,{type:`global`,root:e});for(let[e,r]of t)n.has(e)||n.set(e,r)}let o=e.pluginDirs??[];for(let{id:e,dir:t}of o){let r=await p(t,{type:`plugin`,root:t,id:e});for(let[e,t]of r)n.has(e)||n.set(e,t)}return u.info(`Discovered %d skills from %d directories`,n.size,i.length+a.length+o.length),n}async function p(e,t){let i=new Map;if(!n.existsSync(e))return u.debug(`Skill directory does not exist: %s`,e),i;let a;try{a=n.readdirSync(e,{withFileTypes:!0})}catch{return u.warn(`Cannot read skill directory: %s`,e),i}for(let o of a){if(!o.isDirectory()){if(o.name===d){let n=await m(e,t);n&&i.set(n.definition.metadata.name,n)}continue}let a=r.join(e,o.name),s=r.join(a,d);if(!n.existsSync(s))continue;let c=await m(a,t);c&&i.set(c.definition.metadata.name,c)}return i}async function m(e,t){let i=r.join(e,d);try{let r=c(n.readFileSync(i,`utf-8`),i,e,h(e));return u.debug(`Parsed skill "%s" from %s`,r.metadata.name,i),{definition:r,source:t}}catch(e){return u.warn(`Failed to parse skill at "%s": %s`,i,e.message),null}}function h(e){try{return n.readdirSync(e).filter(e=>e!==d).map(t=>r.join(e,t))}catch{return[]}}function g(){return i()?[`.claude/skills`,`.otto/skills`]:[`.otto/skills`]}function _(){let e=process.env.HOME??process.env.USERPROFILE??``;if(!e)return[];let t=[r.join(e,`.otto`,`skills`)];return i()&&t.push(r.join(e,`.claude`,`skills`)),t}const v=t(`@x-otto/skill:registry`);var y=class t extends e{skills=new Map;workspaceDir;library;disabledSet;pluginDirs;constructor({workspaceDir:e,library:t,disabled:n}){super(),this.workspaceDir=e,this.library=t,this.disabledSet=new Set(n??t?.disabled??[]),this.pluginDirs=t?.pluginDirs}updatePluginDirs(e){this.pluginDirs=e}async discover(){let e=await f({projectDirs:this.library?.projectDirs??g(),globalDirs:this.library?.globalDirs??_(),pluginDirs:this.pluginDirs??this.library?.pluginDirs,disabled:[...this.disabledSet]},this.workspaceDir);for(let[t,{definition:n,source:r}]of e){let e=this.disabledSet.has(t)?`disabled`:`available`;this.skills.set(t,{definition:n,source:r,status:e}),this.emit(`skill.discovered`,{source:r,count:1})}v.info(`Skill registry initialized: %d skills (%d available, %d disabled)`,this.skills.size,this.getAvailable().length,[...this.skills.values()].filter(e=>e.status===`disabled`).length)}register(e,t){let{name:n}=e.metadata,r=this.disabledSet.has(n)?`disabled`:`available`;this.skills.set(n,{definition:e,source:t,status:r}),v.debug(`Registered skill "%s" from %s`,n,t.type)}unregister(e){let t=this.skills.delete(e);return t&&this.emit(`skill.unloaded`,{name:e}),t}getAll(){return this.skills}getAvailable(){return[...this.skills.values()].filter(e=>e.status===`available`||e.status===`loaded`)}get(e){return this.skills.get(e)}execute(e){let t=Date.now(),n=this.skills.get(e.skillName);if(!n)return{success:!1,error:`Skill "${e.skillName}" not found`};if(n.status===`disabled`)return{success:!1,error:`Skill "${e.skillName}" is disabled`};n.status===`available`&&(n.status=`loaded`,n.loadedAt=Date.now());let r=Date.now()-t;return this.emit(`skill.executed`,{name:e.skillName,success:!0,durationMs:r}),{success:!0,content:n.definition.body}}disable(e){this.disabledSet.add(e);let t=this.skills.get(e);t&&(t.status=`disabled`)}enable(e){this.disabledSet.delete(e);let t=this.skills.get(e);t&&(t.status=`available`)}static CATALOG_DESCRIPTION_MAX_CHARS=200;static truncateDescription(e){let n=t.CATALOG_DESCRIPTION_MAX_CHARS,r=Array.from(e);return r.length<=n?e:`${r.slice(0,n).join(``)}…`}buildCatalog(){let e=this.getAvailable();if(e.length===0)return``;let n=[`## Available Skills`,``];for(let{definition:r}of e){let{name:e,description:i,trigger:a}=r.metadata,o=a===`manual`?` [manual]`:``;n.push(`- **${e}**${o}: ${t.truncateDescription(i)}`)}return n.join(`
2
- `)}get size(){return this.skills.size}};function b(e){return new y(e)}function x(e){return e.startsWith(`.claude/`)&&!e.includes(`skills/`)?`.claude/skills/`+e.slice(8):null}function S(e,t){return{async load(n){try{let{readFile:r}=await import(`node:fs/promises`),{isAbsolute:i,resolve:a,dirname:o}=await import(`node:path`),s=async e=>{let n=i(e)?e:a(t,e);try{return await r(n,`utf-8`)}catch(e){if(e.code===`ENOENT`)return null;throw e}},l=await s(n),u=n;if(l===null){let e=x(n);if(e){let t=await s(e);t!==null&&(l=t,u=e)}}if(l===null)return{success:!1,error:`ENOENT: no such file, open '${n}'`};let d=i(u)?u:a(t,u),f=c(l,d,o(d));return e.register(f,{type:`inline`,root:o(d)}),{success:!0,name:f.metadata.name}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}},async execute(n){return e.execute({skillName:n,workspaceDir:t})}}}export{y as SkillRegistry,S as createRegistrySkillProvider,b as createSkillRegistry,f as discoverSkills,_ as getDefaultGlobalSkillDirs,c as parseSkillContent};
1
+ import{TypedEventEmitter as e,createLogger as t}from"@x-otto/shared";import*as n from"node:fs";import*as r from"node:path";import{isClaudeCompatEnabled as i}from"@x-otto/env";import{parse as a}from"yaml";const o=t(`@x-otto/skill:parser`),s=/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;function c(e,t,n,r=[]){let i=s.exec(e.trim());if(!i)throw Error(`SKILL.md at "${t}" has no valid YAML frontmatter`);let[,o,c]=i,u;try{u=a(o)}catch(e){throw Error(`Failed to parse YAML frontmatter in "${t}": ${e.message}`)}if(!u||typeof u!=`object`)throw Error(`YAML frontmatter in "${t}" is not a valid object`);return{metadata:l(u,t),body:(c??``).trim(),directory:n,filePath:t,supportingFiles:r}}function l(e,t){let n=e.name;if(typeof n!=`string`||n.length===0)throw Error(`SKILL.md at "${t}" missing required "name" field in frontmatter`);/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(n)||o.warn(`Skill name "%s" in "%s" does not follow kebab-case convention`,n,t);let r=e.description;if(typeof r!=`string`||r.length===0)throw Error(`SKILL.md at "${t}" missing required "description" field in frontmatter`);let i=`${n}\n${r}`;return i.length>1024&&o.warn(`Skill "%s" frontmatter exceeds 1024 character limit (%d chars)`,n,i.length),{name:n,description:r,version:typeof e.version==`string`?e.version:void 0,tags:Array.isArray(e.tags)?e.tags.filter(e=>typeof e==`string`):void 0,dependencies:Array.isArray(e.dependencies)?e.dependencies.filter(e=>typeof e==`string`):void 0,trigger:e.trigger===`manual`?`manual`:`auto`,priority:typeof e.priority==`number`?e.priority:100}}const u=t(`@x-otto/skill:discovery`),d=`SKILL.md`;async function f(e,t){let n=new Map,i=e.projectDirs??[`.otto/skills`];for(let e of i){let i=r.resolve(t,e);p(n,await m(i,{type:`project`,root:i}),`overwrite`)}let a=e.globalDirs??[];for(let e of a)p(n,await m(e,{type:`global`,root:e}),`keep`);let o=e.pluginDirs??[];for(let{id:e,dir:t}of o)p(n,await m(t,{type:`plugin`,root:t,id:e}),`keep`);return u.info(`Discovered %d skills from %d directories`,n.size,i.length+a.length+o.length),n}function p(e,t,n){for(let[r,i]of t)(n===`overwrite`||!e.has(r))&&e.set(r,i)}async function m(e,t){let i=new Map;if(!n.existsSync(e))return u.debug(`Skill directory does not exist: %s`,e),i;let a;try{a=n.readdirSync(e,{withFileTypes:!0})}catch{return u.warn(`Cannot read skill directory: %s`,e),i}for(let o of a){if(!o.isDirectory()){if(o.name===d){let n=await h(e,t);i.set(n.definition.metadata.name,n)}continue}let a=r.join(e,o.name),s=r.join(a,d);if(!n.existsSync(s))continue;let c=await h(a,t);i.set(c.definition.metadata.name,c)}return i}async function h(e,t){let i=r.join(e,d);try{let r=c(n.readFileSync(i,`utf-8`),i,e,g(e));return u.debug(`Parsed skill "%s" from %s`,r.metadata.name,i),{definition:r,source:t}}catch(n){let r=n.message;u.warn(`Failed to parse skill at "%s": %s`,i,r);let a=t.id?`${t.id}:`:``;return{definition:{metadata:{name:`error:${t.type}:${a}${i}`,description:`Failed to parse: ${r}`},body:``,directory:e,filePath:i,supportingFiles:[]},source:t,error:r}}}function g(e){try{return n.readdirSync(e).filter(e=>e!==d).map(t=>r.join(e,t))}catch{return[]}}function _(){return i()?[`.claude/skills`,`.otto/skills`]:[`.otto/skills`]}function v(){let e=process.env.HOME??process.env.USERPROFILE??``;if(!e)return[];let t=[r.join(e,`.otto`,`skills`)];return i()&&t.push(r.join(e,`.claude`,`skills`)),t}const y=new Set([`python`,`python3`,`bash`,`zsh`,`sh`,`node`,`deno`,`ruby`,`perl`,`pwsh`,`python.exe`,`node.exe`]),b=new Set([`.py`,`.sh`,`.js`,`.ts`,`.rb`,`.pl`,`.ps1`,`.mjs`,`.cjs`]),x=new Set([`cat`,`less`,`more`,`head`,`tail`,`type`,`get-content`,`gc`,`sed`,`awk`,`grep`]);function S(e){let t=[],n=``,r=null,i=!1,a=!1;for(let o of e){if(i){n+=o,i=!1,a=!0;continue}if(o===`\\`&&r!==`'`){i=!0,a=!0;continue}if(r!==null){o===r?r=null:n+=o,a=!0;continue}if(o===`"`||o===`'`){r=o,a=!0;continue}if(o===` `||o===` `||o===`
2
+ `){a&&=(t.push(n),n=``,!1);continue}n+=o,a=!0}return a&&t.push(n),t}function C(e){let t=e.split(`/`);return t[t.length-1]??e}function w(e){let t=e.replace(/\\/g,`/`).split(`/`);return t[t.length-1]??e}function T(e){let t=w(e).toLowerCase();return[...b].some(e=>t.endsWith(e))}function E(e){let t=S(e);if(t.length===0)return[];let n=[],r=C(t[0]??``).toLowerCase();if(y.has(r))for(let e=1;e<t.length;e++){let r=t[e];if(!r.startsWith(`-`)&&T(r)){n.push({kind:`script`,path:r});break}}if(x.has(r))for(let e=1;e<t.length;e++){let r=t[e];if(!r.startsWith(`-`)){if(r===`|`||r===`||`||r===`&&`||r===`;`||r===`>`)break;n.push({kind:`document`,path:r})}}return n}var D=class{aggregates=new Map;record(e){let t=this.aggregates.get(e.name)??{name:e.name,executions:0,successes:0,failures:0,totalDurationMs:0,lastExecutedAt:e.at};t.executions+=1,e.success?t.successes+=1:t.failures+=1,t.totalDurationMs+=e.durationMs,t.lastExecutedAt=e.at,this.aggregates.set(e.name,t)}get(e){return this.aggregates.get(e)}all(){return[...this.aggregates.values()]}get size(){return this.aggregates.size}};const O=t(`@x-otto/skill:registry`);var k=class t extends e{skills=new Map;workspaceDir;library;disabledSet;pluginDirs;discoverRegisteredNames=new Set;usageStore;evolutionSignals;now;constructor({workspaceDir:e,library:t,disabled:n,usageStore:r,now:i,evolutionSignals:a}){super(),this.workspaceDir=e,this.library=t,this.disabledSet=new Set(n??[]),this.pluginDirs=t?.pluginDirs,this.usageStore=r??new D,this.evolutionSignals=a,this.now=i??(()=>Date.now())}updatePluginDirs(e){this.pluginDirs=e}async discover(){let e=await f({projectDirs:this.library?.projectDirs??_(),globalDirs:this.library?.globalDirs??v(),pluginDirs:this.pluginDirs??this.library?.pluginDirs},this.workspaceDir),t=new Set;for(let[n,{definition:r,source:i,error:a}]of e){if(this.skills.get(n)!==void 0&&!this.discoverRegisteredNames.has(n))continue;let e=a?`error`:this.disabledSet.has(n)?`disabled`:`available`;this.skills.set(n,a?{definition:r,source:i,status:e,error:a}:{definition:r,source:i,status:e}),t.add(n)}for(let e of this.discoverRegisteredNames)t.has(e)||this.skills.delete(e);this.discoverRegisteredNames=t,O.info(`Skill registry reconciled: %d skills (%d available, %d disabled, %d from discover)`,this.skills.size,this.getAvailable().length,[...this.skills.values()].filter(e=>e.status===`disabled`).length,t.size)}register(e,t){let{name:n}=e.metadata,r=this.disabledSet.has(n)?`disabled`:`available`;this.skills.set(n,{definition:e,source:t,status:r}),O.debug(`Registered skill "%s" from %s`,n,t.type)}unregister(e){return this.skills.delete(e)}getAll(){return this.skills}getAvailable(){return[...this.skills.values()].filter(e=>e.status===`available`||e.status===`loaded`)}get(e){return this.skills.get(e)}execute(e){let t=this.now(),r=t,i=this.skills.get(e.skillName),a=(n,r)=>{this.usageStore.record({name:e.skillName,success:n,durationMs:r,at:t}),this.evolutionSignals?.append({kind:`skill.usage`,key:e.skillName,value:1,...e.sessionId===void 0?{}:{sessionId:e.sessionId},detail:{success:n,durationMs:r}})};if(!i)return a(!1,0),{success:!1,error:`Skill "${e.skillName}" not found`};if(i.status===`disabled`)return a(!1,0),{success:!1,error:`Skill "${e.skillName}" is disabled`};if(i.status===`error`)return a(!1,0),{success:!1,error:i.error??`Skill "${e.skillName}" failed to parse`};i.status===`available`&&(i.status=`loaded`,i.loadedAt=this.now());let{source:o,definition:s}=i,l=o.type===`project`||o.type===`global`||o.type===`plugin`&&!s.filePath.includes(`#computed:`),u=s.body;if(l)try{let e=c(n.readFileSync(s.filePath,`utf-8`),s.filePath,s.directory,s.supportingFiles);u=e.body,s.body=e.body}catch(t){O.warn(`Skill "%s" body reread failed, falling back to in-memory body: %s`,e.skillName,t.message)}let d=this.now()-r;return this.emit(`skill.executed`,{name:e.skillName,success:!0,durationMs:d}),a(!0,d),{success:!0,content:u}}getSkillUsage(e){let t=this.usageStore;return typeof t.get==`function`?t.get(e):void 0}getAllSkillUsage(){let e=this.usageStore;return typeof e.all==`function`?e.all():[]}disable(e){this.disabledSet.add(e);let t=this.skills.get(e);t&&t.status!==`error`&&(t.status=`disabled`)}enable(e){this.disabledSet.delete(e);let t=this.skills.get(e);t&&t.status===`disabled`&&(t.status=`available`)}static CATALOG_DESCRIPTION_MAX_CHARS=200;static truncateDescription(e){let n=t.CATALOG_DESCRIPTION_MAX_CHARS,r=Array.from(e);return r.length<=n?e:`${r.slice(0,n).join(``)}…`}buildCatalog(){let e=this.getAvailable();if(e.length===0)return``;let n=[`## Available Skills`,``];for(let{definition:r}of e){let{name:e,description:i,trigger:a}=r.metadata,o=a===`manual`?` [manual]`:``;n.push(`- **${e}**${o}: ${t.truncateDescription(i)}`)}return n.join(`
3
+ `)}detectImplicitInvocation(e,t){let n=E(e);if(n.length!==0)for(let e of n)for(let n of this.skills.values()){if(n.status!==`available`)continue;let i=r.resolve(n.definition.directory),a=r.resolve(t,e.path);if(e.kind===`script`&&(e=>{let t=r.relative(r.join(i,e),a);return!t.startsWith(`..`)&&!r.isAbsolute(t)})(`scripts`))return this.recordInvocation(n.definition.metadata.name,`script`),n;if(e.kind===`document`){let e=r.basename(a);if((a===i||a===r.join(i,`SKILL.md`)||e===`SKILL.md`)&&r.dirname(a)===i)return this.recordInvocation(n.definition.metadata.name,`document`),n}}}recordInvocation(e,t){this.usageStore.record({name:e,success:!0,durationMs:0,at:this.now()}),this.evolutionSignals?.append({kind:`skill.usage`,key:e,value:1,detail:{success:!0,durationMs:0,implicit:!0}}),this.emit(`skill.invoked`,{name:e,kind:t})}get size(){return this.skills.size}};function A(e){return new k(e)}function j(e){return e.startsWith(`.claude/`)&&!e.includes(`skills/`)?`.claude/skills/`+e.slice(8):null}function M(e,t){return{async load(n){try{let{readFile:r}=await import(`node:fs/promises`),{isAbsolute:i,resolve:a,dirname:o}=await import(`node:path`),s=async e=>{let n=i(e)?e:a(t,e);try{return await r(n,`utf-8`)}catch(e){if(e.code===`ENOENT`)return null;throw e}},l=await s(n),u=n;if(l===null){let e=j(n);if(e){let t=await s(e);t!==null&&(l=t,u=e)}}if(l===null)return{success:!1,error:`ENOENT: no such file, open '${n}'`};let d=i(u)?u:a(t,u),f=c(l,d,o(d));return e.register(f,{type:`inline`,root:o(d)}),{success:!0,name:f.metadata.name}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}},async execute(n,r){return e.execute({skillName:n,workspaceDir:t,sessionId:r})}}}export{k as SkillRegistry,D as SkillUsageStore,M as createRegistrySkillProvider,A as createSkillRegistry,f as discoverSkills,v as getDefaultGlobalSkillDirs,E as implicitSkillAccessesForCommand,c as parseSkillContent,S as tokenizeCommand};
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["logger","parseYaml","logger"],"sources":["../src/skill-parser.ts","../src/skill-discovery.ts","../src/skill-registry.ts","../src/registry-skill-provider.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml'\nimport { createLogger } from '@x-otto/shared'\nimport type { SkillMetadata, SkillDefinition } from './types'\n\nconst logger = createLogger('@x-otto/skill:parser')\n\nconst FRONTMATTER_RE = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n?([\\s\\S]*)$/\n\n/**\n * 从 SKILL.md 原始文本解析出 SkillDefinition\n */\nexport function parseSkillContent(\n content: string,\n filePath: string,\n directory: string,\n supportingFiles: string[] = [],\n): SkillDefinition {\n const match = FRONTMATTER_RE.exec(content.trim())\n\n if (!match) {\n throw new Error(`SKILL.md at \"${filePath}\" has no valid YAML frontmatter`)\n }\n\n const [, yamlStr, body] = match\n let raw: Record<string, unknown>\n\n try {\n raw = parseYaml(yamlStr!) as Record<string, unknown>\n } catch (err) {\n throw new Error(`Failed to parse YAML frontmatter in \"${filePath}\": ${(err as Error).message}`)\n }\n\n if (!raw || typeof raw !== 'object') {\n throw new Error(`YAML frontmatter in \"${filePath}\" is not a valid object`)\n }\n\n const metadata = extractMetadata(raw, filePath)\n\n return {\n metadata,\n body: (body ?? '').trim(),\n directory,\n filePath,\n supportingFiles,\n }\n}\n\n/**\n * 从 raw YAML 提取并验证 SkillMetadata\n */\nfunction extractMetadata(raw: Record<string, unknown>, filePath: string): SkillMetadata {\n const name = raw['name']\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error(`SKILL.md at \"${filePath}\" missing required \"name\" field in frontmatter`)\n }\n\n if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {\n logger.warn('Skill name \"%s\" in \"%s\" does not follow kebab-case convention', name, filePath)\n }\n\n const description = raw['description']\n if (typeof description !== 'string' || description.length === 0) {\n throw new Error(`SKILL.md at \"${filePath}\" missing required \"description\" field in frontmatter`)\n }\n\n const frontmatterText = `${name}\\n${description}`\n if (frontmatterText.length > 1024) {\n logger.warn(\n 'Skill \"%s\" frontmatter exceeds 1024 character limit (%d chars)',\n name,\n frontmatterText.length,\n )\n }\n\n return {\n name,\n description,\n version: typeof raw['version'] === 'string' ? raw['version'] : undefined,\n author: typeof raw['author'] === 'string' ? raw['author'] : undefined,\n tags: Array.isArray(raw['tags'])\n ? raw['tags'].filter((t): t is string => typeof t === 'string')\n : undefined,\n dependencies: Array.isArray(raw['dependencies'])\n ? raw['dependencies'].filter((d): d is string => typeof d === 'string')\n : undefined,\n trigger: raw['trigger'] === 'manual' ? 'manual' : 'auto',\n priority: typeof raw['priority'] === 'number' ? raw['priority'] : 100,\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { createLogger } from '@x-otto/shared'\nimport { isClaudeCompatEnabled } from '@x-otto/env'\nimport { parseSkillContent } from './skill-parser'\nimport type { SkillDefinition, SkillSource, SkillLibraryConfig } from './types'\n\nconst logger = createLogger('@x-otto/skill:discovery')\n\nconst SKILL_FILENAME = 'SKILL.md'\n\n/**\n * 扫描指定目录列表,发现所有有效 skill\n */\nexport async function discoverSkills(\n config: SkillLibraryConfig,\n workspaceDir: string,\n): Promise<Map<string, { definition: SkillDefinition; source: SkillSource }>> {\n const results = new Map<string, { definition: SkillDefinition; source: SkillSource }>()\n\n const projectDirs = config.projectDirs ?? ['.otto/skills']\n for (const relDir of projectDirs) {\n const absDir = path.resolve(workspaceDir, relDir)\n const source: SkillSource = { type: 'project', root: absDir }\n const skills = await scanDirectory(absDir, source)\n\n for (const [name, entry] of skills) {\n results.set(name, entry)\n }\n }\n\n const globalDirs = config.globalDirs ?? []\n for (const absDir of globalDirs) {\n const source: SkillSource = { type: 'global', root: absDir }\n const skills = await scanDirectory(absDir, source)\n\n for (const [name, entry] of skills) {\n if (!results.has(name)) {\n results.set(name, entry)\n }\n }\n }\n\n const pluginDirs = config.pluginDirs ?? []\n for (const { id, dir } of pluginDirs) {\n const source: SkillSource = { type: 'plugin', root: dir, id }\n const skills = await scanDirectory(dir, source)\n\n for (const [name, entry] of skills) {\n if (!results.has(name)) {\n results.set(name, entry)\n }\n }\n }\n\n logger.info(\n 'Discovered %d skills from %d directories',\n results.size,\n projectDirs.length + globalDirs.length + pluginDirs.length,\n )\n\n return results\n}\n\n/**\n * 扫描单个目录下的所有 skill 子目录\n *\n * 目录结构:\n * skills/\n * skill-name/\n * SKILL.md\n * supporting-file.ts\n * another-skill/\n * SKILL.md\n */\nasync function scanDirectory(\n dir: string,\n source: SkillSource,\n): Promise<Map<string, { definition: SkillDefinition; source: SkillSource }>> {\n const results = new Map<string, { definition: SkillDefinition; source: SkillSource }>()\n\n if (!fs.existsSync(dir)) {\n logger.debug('Skill directory does not exist: %s', dir)\n return results\n }\n\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true })\n } catch {\n logger.warn('Cannot read skill directory: %s', dir)\n return results\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) {\n if (entry.name === SKILL_FILENAME) {\n const skill = await tryParseSkill(dir, source)\n if (skill) {\n results.set(skill.definition.metadata.name, skill)\n }\n }\n continue\n }\n\n const skillDir = path.join(dir, entry.name)\n const skillFile = path.join(skillDir, SKILL_FILENAME)\n\n if (!fs.existsSync(skillFile)) {\n continue\n }\n\n const skill = await tryParseSkill(skillDir, source)\n if (skill) {\n results.set(skill.definition.metadata.name, skill)\n }\n }\n\n return results\n}\n\n/**\n * 尝试读取并解析单个 SKILL.md\n */\nasync function tryParseSkill(\n skillDir: string,\n source: SkillSource,\n): Promise<{ definition: SkillDefinition; source: SkillSource } | null> {\n const skillFile = path.join(skillDir, SKILL_FILENAME)\n\n try {\n const content = fs.readFileSync(skillFile, 'utf-8')\n const supportingFiles = listSupportingFiles(skillDir)\n const definition = parseSkillContent(content, skillFile, skillDir, supportingFiles)\n\n logger.debug('Parsed skill \"%s\" from %s', definition.metadata.name, skillFile)\n\n return { definition, source }\n } catch (err) {\n logger.warn('Failed to parse skill at \"%s\": %s', skillFile, (err as Error).message)\n return null\n }\n}\n\n/**\n * 列出 skill 目录下的附属文件(排除 SKILL.md 自身)\n */\nfunction listSupportingFiles(skillDir: string): string[] {\n try {\n return fs\n .readdirSync(skillDir)\n .filter((name) => name !== SKILL_FILENAME)\n .map((name) => path.join(skillDir, name))\n } catch {\n return []\n }\n}\n\n/**\n * 默认项目级 skill 目录。\n *\n * 顺序即优先级:`discoverSkills` 的 project 循环对同名是 **last-set wins**,\n * 故 `.otto/skills` 排在 `.claude/skills` 之后 = otto 原生覆盖 .claude(RFC-032)。\n */\nexport function getDefaultProjectSkillDirs(): string[] {\n return isClaudeCompatEnabled() ? ['.claude/skills', '.otto/skills'] : ['.otto/skills']\n}\n\n/**\n * 获取默认的全局 skill 目录列表。\n *\n * 顺序即优先级:`discoverSkills` 的 global 循环对同名是 **first-win**(`!has` 才填),\n * 故 `~/.otto/skills` 排在 `~/.claude/skills` 之前 = otto 原生覆盖 .claude(RFC-032)。\n */\nexport function getDefaultGlobalSkillDirs(): string[] {\n const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? ''\n if (!home) {\n return []\n }\n\n const dirs = [path.join(home, '.otto', 'skills')]\n if (isClaudeCompatEnabled()) {\n dirs.push(path.join(home, '.claude', 'skills'))\n }\n return dirs\n}\n","import { TypedEventEmitter, createLogger } from '@x-otto/shared'\nimport {\n discoverSkills,\n getDefaultGlobalSkillDirs,\n getDefaultProjectSkillDirs,\n} from './skill-discovery'\nimport type {\n SkillDefinition,\n SkillLibraryConfig,\n SkillSource,\n RegisteredSkill,\n SkillStatus,\n SkillExecutionContext,\n SkillExecutionResult,\n SkillEvents,\n} from './types'\n\nconst logger = createLogger('@x-otto/skill:registry')\n\nexport interface SkillRegistryOptions {\n /** 工作目录 */\n workspaceDir: string\n /** skill 库配置 */\n library?: SkillLibraryConfig\n /** 禁用的 skill 名称列表 */\n disabled?: string[]\n}\n\nexport class SkillRegistry extends TypedEventEmitter<SkillEvents> {\n private skills = new Map<string, RegisteredSkill>()\n private readonly workspaceDir: string\n private readonly library?: SkillLibraryConfig\n private disabledSet: Set<string>\n /** 可变的 plugin 目录列表(reloadCapabilities 更新为新安装 plugin 的 dirs) */\n private pluginDirs?: Array<{ id: string; dir: string }>\n\n constructor({ workspaceDir, library, disabled }: SkillRegistryOptions) {\n super()\n this.workspaceDir = workspaceDir\n this.library = library\n this.disabledSet = new Set(disabled ?? library?.disabled ?? [])\n this.pluginDirs = library?.pluginDirs\n }\n\n /**\n * 更新 plugin 贡献的 skill 目录列表(用于 reloadCapabilities 热刷新)。\n * 下次 discover() 将扫描新的 plugin 目录。\n */\n updatePluginDirs(dirs: Array<{ id: string; dir: string }>): void {\n this.pluginDirs = dirs\n }\n\n /**\n * 发现并注册所有可用 skill\n * 扫描项目本地 + 全局目录\n */\n async discover(): Promise<void> {\n const config: SkillLibraryConfig = {\n projectDirs: this.library?.projectDirs ?? getDefaultProjectSkillDirs(),\n globalDirs: this.library?.globalDirs ?? getDefaultGlobalSkillDirs(),\n pluginDirs: this.pluginDirs ?? this.library?.pluginDirs,\n disabled: [...this.disabledSet],\n }\n\n const discovered = await discoverSkills(config, this.workspaceDir)\n\n for (const [name, { definition, source }] of discovered) {\n const status: SkillStatus = this.disabledSet.has(name) ? 'disabled' : 'available'\n\n this.skills.set(name, {\n definition,\n source,\n status,\n })\n\n this.emit('skill.discovered', { source, count: 1 })\n }\n\n logger.info(\n 'Skill registry initialized: %d skills (%d available, %d disabled)',\n this.skills.size,\n this.getAvailable().length,\n [...this.skills.values()].filter((s) => s.status === 'disabled').length,\n )\n }\n\n /**\n * 手动注册一个 skill(用于内联/插件 skill)\n */\n register(definition: SkillDefinition, source: SkillSource): void {\n const { name } = definition.metadata\n const status: SkillStatus = this.disabledSet.has(name) ? 'disabled' : 'available'\n\n this.skills.set(name, { definition, source, status })\n logger.debug('Registered skill \"%s\" from %s', name, source.type)\n }\n\n /**\n * 注销 skill\n */\n unregister(name: string): boolean {\n const had = this.skills.delete(name)\n if (had) {\n this.emit('skill.unloaded', { name })\n }\n return had\n }\n\n /**\n * 获取所有已注册 skill\n */\n getAll(): ReadonlyMap<string, RegisteredSkill> {\n return this.skills\n }\n\n /**\n * 获取所有可用(非 disabled/error)skill\n */\n getAvailable(): RegisteredSkill[] {\n return [...this.skills.values()].filter(\n (s) => s.status === 'available' || s.status === 'loaded',\n )\n }\n\n /**\n * 按名称获取 skill\n */\n get(name: string): RegisteredSkill | undefined {\n return this.skills.get(name)\n }\n\n execute(context: SkillExecutionContext): SkillExecutionResult {\n const startTime = Date.now()\n const registered = this.skills.get(context.skillName)\n\n if (!registered) {\n return { success: false, error: `Skill \"${context.skillName}\" not found` }\n }\n\n if (registered.status === 'disabled') {\n return { success: false, error: `Skill \"${context.skillName}\" is disabled` }\n }\n\n if (registered.status === 'available') {\n registered.status = 'loaded'\n registered.loadedAt = Date.now()\n }\n\n const durationMs = Date.now() - startTime\n this.emit('skill.executed', { name: context.skillName, success: true, durationMs })\n\n return {\n success: true,\n content: registered.definition.body,\n }\n }\n\n /**\n * 禁用 skill\n */\n disable(name: string): void {\n this.disabledSet.add(name)\n const registered = this.skills.get(name)\n if (registered) {\n registered.status = 'disabled'\n }\n }\n\n /**\n * 启用 skill\n */\n enable(name: string): void {\n this.disabledSet.delete(name)\n const registered = this.skills.get(name)\n if (registered) {\n registered.status = 'available'\n }\n }\n\n /**\n * catalog 里每条 skill description 的最大字符数(渐进披露的正确边界——完整正文本就该\n * 靠 skill 工具按需取回,不该把全文塞进常驻的 system prompt catalog)。\n *\n * 取值依据:本仓真实 skill(rfc-todos)description 长度 1339 字符,若装 10+ 个同量级\n * skill,catalog 本身就会膨胀到与基座 prompt 同量级。200 字符能保留\"这个 skill 是干\n * 什么的、什么时候用\"的判断信息,同时把单条上限压到基座 prompt 的 1/10 量级。\n */\n private static readonly CATALOG_DESCRIPTION_MAX_CHARS = 200\n\n /**\n * 按字符边界截断 description(不切断多字节字符),超长时追加省略号标记。\n * 截断是**渲染层**行为——`definition.metadata.description` 原文不变,`skill` 工具\n * 取全文时仍拿到完整内容,catalog 只是给模型的\"目录索引\"而非替代正文。\n */\n private static truncateDescription(description: string): string {\n const max = SkillRegistry.CATALOG_DESCRIPTION_MAX_CHARS\n const chars = Array.from(description)\n if (chars.length <= max) return description\n return `${chars.slice(0, max).join('')}…`\n }\n\n /**\n * 构建 skill catalog(用于 system prompt 注入)\n * 格式:简短列表,仅 name + description(description 截断到固定上限,见\n * CATALOG_DESCRIPTION_MAX_CHARS——渐进披露:目录常驻、正文按需取回,不应本末倒置)\n */\n buildCatalog(): string {\n const available = this.getAvailable()\n if (available.length === 0) {\n return ''\n }\n\n const lines = ['## Available Skills', '']\n for (const { definition } of available) {\n const { name, description, trigger } = definition.metadata\n const triggerTag = trigger === 'manual' ? ' [manual]' : ''\n lines.push(`- **${name}**${triggerTag}: ${SkillRegistry.truncateDescription(description)}`)\n }\n\n return lines.join('\\n')\n }\n\n /**\n * 获取 skill 总数\n */\n get size(): number {\n return this.skills.size\n }\n}\n\nexport function createSkillRegistry(options: SkillRegistryOptions): SkillRegistry {\n return new SkillRegistry(options)\n}\n","import { parseSkillContent } from './skill-parser'\n\nimport type { SkillRegistry } from './skill-registry'\nimport type { SkillProvider } from './types'\n\n/**\n * 常见路径遗漏:`.claude/rfc-to-todos/SKILL.md` → `.claude/skills/rfc-to-todos/SKILL.md`。\n * 仅处理以 `.claude/` 开头、不含 `skills/` 的相对路径。返回 null 表示无需补全。\n */\nfunction resolveClaudeSkillsPath(path: string): string | null {\n if (path.startsWith('.claude/') && !path.includes('skills/')) {\n return '.claude/skills/' + path.slice('.claude/'.length)\n }\n return null\n}\n\nexport function createRegistrySkillProvider(\n registry: SkillRegistry,\n workspaceDir: string,\n): SkillProvider {\n return {\n async load(path) {\n try {\n const { readFile } = await import('node:fs/promises')\n const { isAbsolute, resolve, dirname: dir } = await import('node:path')\n\n const tryPath = async (p: string): Promise<string | null> => {\n const filePath = isAbsolute(p) ? p : resolve(workspaceDir, p)\n try {\n return await readFile(filePath, 'utf-8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw err\n }\n }\n\n let content = await tryPath(path)\n let usedPath = path\n\n if (content === null) {\n const altPath = resolveClaudeSkillsPath(path)\n if (altPath) {\n const altContent = await tryPath(altPath)\n if (altContent !== null) {\n content = altContent\n usedPath = altPath\n }\n }\n }\n\n if (content === null) {\n return { success: false, error: `ENOENT: no such file, open '${path}'` }\n }\n\n const filePath = isAbsolute(usedPath) ? usedPath : resolve(workspaceDir, usedPath)\n const definition = parseSkillContent(content, filePath, dir(filePath))\n\n registry.register(definition, { type: 'inline', root: dir(filePath) })\n return { success: true, name: definition.metadata.name }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n },\n\n async execute(name) {\n return registry.execute({ skillName: name, workspaceDir })\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["logger","parseYaml","logger"],"sources":["../src/skill-parser.ts","../src/skill-discovery.ts","../src/skill-invocation.ts","../src/skill-usage.ts","../src/skill-registry.ts","../src/registry-skill-provider.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml'\nimport { createLogger } from '@x-otto/shared'\nimport type { SkillMetadata, SkillDefinition } from './types'\n\nconst logger = createLogger('@x-otto/skill:parser')\n\nconst FRONTMATTER_RE = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n?([\\s\\S]*)$/\n\n/**\n * 从 SKILL.md 原始文本解析出 SkillDefinition\n */\nexport function parseSkillContent(\n content: string,\n filePath: string,\n directory: string,\n supportingFiles: string[] = [],\n): SkillDefinition {\n const match = FRONTMATTER_RE.exec(content.trim())\n\n if (!match) {\n throw new Error(`SKILL.md at \"${filePath}\" has no valid YAML frontmatter`)\n }\n\n const [, yamlStr, body] = match\n let raw: Record<string, unknown>\n\n try {\n raw = parseYaml(yamlStr!) as Record<string, unknown>\n } catch (err) {\n throw new Error(`Failed to parse YAML frontmatter in \"${filePath}\": ${(err as Error).message}`)\n }\n\n if (!raw || typeof raw !== 'object') {\n throw new Error(`YAML frontmatter in \"${filePath}\" is not a valid object`)\n }\n\n const metadata = extractMetadata(raw, filePath)\n\n return {\n metadata,\n body: (body ?? '').trim(),\n directory,\n filePath,\n supportingFiles,\n }\n}\n\n/**\n * 从 raw YAML 提取并验证 SkillMetadata\n */\nfunction extractMetadata(raw: Record<string, unknown>, filePath: string): SkillMetadata {\n const name = raw['name']\n if (typeof name !== 'string' || name.length === 0) {\n throw new Error(`SKILL.md at \"${filePath}\" missing required \"name\" field in frontmatter`)\n }\n\n if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name)) {\n logger.warn('Skill name \"%s\" in \"%s\" does not follow kebab-case convention', name, filePath)\n }\n\n const description = raw['description']\n if (typeof description !== 'string' || description.length === 0) {\n throw new Error(`SKILL.md at \"${filePath}\" missing required \"description\" field in frontmatter`)\n }\n\n const frontmatterText = `${name}\\n${description}`\n if (frontmatterText.length > 1024) {\n logger.warn(\n 'Skill \"%s\" frontmatter exceeds 1024 character limit (%d chars)',\n name,\n frontmatterText.length,\n )\n }\n\n return {\n name,\n description,\n version: typeof raw['version'] === 'string' ? raw['version'] : undefined,\n tags: Array.isArray(raw['tags'])\n ? raw['tags'].filter((t): t is string => typeof t === 'string')\n : undefined,\n dependencies: Array.isArray(raw['dependencies'])\n ? raw['dependencies'].filter((d): d is string => typeof d === 'string')\n : undefined,\n trigger: raw['trigger'] === 'manual' ? 'manual' : 'auto',\n priority: typeof raw['priority'] === 'number' ? raw['priority'] : 100,\n }\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { createLogger } from '@x-otto/shared'\nimport { isClaudeCompatEnabled } from '@x-otto/env'\nimport { parseSkillContent } from './skill-parser'\nimport type { SkillDefinition, SkillSource, SkillLibraryConfig } from './types'\n\nconst logger = createLogger('@x-otto/skill:discovery')\n\nconst SKILL_FILENAME = 'SKILL.md'\n\n/**\n * discovery 产出的单条目:解析成功的 definition + source,或(M3)解析失败时带 `error`\n * 的占位条目(definition.metadata.name 为防撞占位名、body 空,供 discover 注册 error 态)。\n */\ntype DiscoveredEntry = { definition: SkillDefinition; source: SkillSource; error?: string }\n\n/**\n * 扫描指定目录列表,发现所有有效 skill\n */\nexport async function discoverSkills(\n config: SkillLibraryConfig,\n workspaceDir: string,\n): Promise<Map<string, DiscoveredEntry>> {\n const results = new Map<string, DiscoveredEntry>()\n\n const projectDirs = config.projectDirs ?? ['.otto/skills']\n for (const relDir of projectDirs) {\n const absDir = path.resolve(workspaceDir, relDir)\n const skills = await scanDirectory(absDir, { type: 'project', root: absDir })\n // project 层:last-set wins(`.otto/skills` 排 `.claude/skills` 之后 = otto 覆盖,RFC-032)。\n mergeSkills(results, skills, 'overwrite')\n }\n\n const globalDirs = config.globalDirs ?? []\n for (const absDir of globalDirs) {\n const skills = await scanDirectory(absDir, { type: 'global', root: absDir })\n // global 层:first-win(`~/.otto/skills` 排 `~/.claude/skills` 之前 = otto 覆盖,RFC-032)。\n mergeSkills(results, skills, 'keep')\n }\n\n const pluginDirs = config.pluginDirs ?? []\n for (const { id, dir } of pluginDirs) {\n const skills = await scanDirectory(dir, { type: 'plugin', root: dir, id })\n // plugin 层:first-win + 最低优先级(不夺已有 project/global 名,RFC-044 R7)。\n mergeSkills(results, skills, 'keep')\n }\n\n logger.info(\n 'Discovered %d skills from %d directories',\n results.size,\n projectDirs.length + globalDirs.length + pluginDirs.length,\n )\n\n return results\n}\n\n/**\n * 把一批扫描结果合并进累积 map,按策略处理同名冲突(收敛原三处循环的重复合并逻辑)。\n *\n * - `'overwrite'`:同名后写覆盖(project 层 last-set wins,实现 otto 覆盖 .claude)。\n * - `'keep'`:同名保留已有(global/plugin 层 first-win,不夺更高优先级层已占的名)。\n */\nfunction mergeSkills(\n target: Map<string, DiscoveredEntry>,\n incoming: Map<string, DiscoveredEntry>,\n onConflict: 'overwrite' | 'keep',\n): void {\n for (const [name, entry] of incoming) {\n if (onConflict === 'overwrite' || !target.has(name)) {\n target.set(name, entry)\n }\n }\n}\n\n/**\n * 扫描单个目录下的所有 skill 子目录\n *\n * 目录结构:\n * skills/\n * skill-name/\n * SKILL.md\n * supporting-file.ts\n * another-skill/\n * SKILL.md\n */\nasync function scanDirectory(\n dir: string,\n source: SkillSource,\n): Promise<Map<string, DiscoveredEntry>> {\n const results = new Map<string, DiscoveredEntry>()\n\n if (!fs.existsSync(dir)) {\n logger.debug('Skill directory does not exist: %s', dir)\n return results\n }\n\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true })\n } catch {\n logger.warn('Cannot read skill directory: %s', dir)\n return results\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory()) {\n if (entry.name === SKILL_FILENAME) {\n const skill = await tryParseSkill(dir, source)\n results.set(skill.definition.metadata.name, skill)\n }\n continue\n }\n\n const skillDir = path.join(dir, entry.name)\n const skillFile = path.join(skillDir, SKILL_FILENAME)\n\n if (!fs.existsSync(skillFile)) {\n continue\n }\n\n const skill = await tryParseSkill(skillDir, source)\n results.set(skill.definition.metadata.name, skill)\n }\n\n return results\n}\n\n/**\n * 尝试读取并解析单个 SKILL.md。\n *\n * 成功 → 返回解析结果;失败 → 返回 **error 占位条目**(RFC-396 M3)而非静默 null,\n * 让 discover 能注册 error 态供 TUI 展示(解析失败的 skill 用户可见、可排查)。\n * 占位 name 用 `error:<type>:<id或root>:<相对路径>` 保证跨源/多插件不撞名。\n */\nasync function tryParseSkill(\n skillDir: string,\n source: SkillSource,\n): Promise<DiscoveredEntry> {\n const skillFile = path.join(skillDir, SKILL_FILENAME)\n\n try {\n const content = fs.readFileSync(skillFile, 'utf-8')\n const supportingFiles = listSupportingFiles(skillDir)\n const definition = parseSkillContent(content, skillFile, skillDir, supportingFiles)\n\n logger.debug('Parsed skill \"%s\" from %s', definition.metadata.name, skillFile)\n\n return { definition, source }\n } catch (err) {\n const message = (err as Error).message\n logger.warn('Failed to parse skill at \"%s\": %s', skillFile, message)\n // error 占位名:`error:` 前缀(含冒号,与 kebab-case 正常 skill 名空间隔离)\n // + type + [插件 id] + **绝对 filePath**(全局唯一)。用绝对路径而非 basename(root)——\n // project/global 的 root 就是 skills 目录本身,compat 模式下 `.claude/skills` 与\n // `.otto/skills` 的 basename 都是 'skills' 会撞名(RFC-396 D3 评审修订)。绝对\n // skillFile 路径全局唯一,保证跨源/多目录/多插件的坏 skill 各占独立占位名。\n const idPart = source.id ? `${source.id}:` : ''\n const errorName = `error:${source.type}:${idPart}${skillFile}`\n return {\n definition: {\n metadata: { name: errorName, description: `Failed to parse: ${message}` },\n body: '',\n directory: skillDir,\n filePath: skillFile,\n supportingFiles: [],\n },\n source,\n error: message,\n }\n }\n}\n\n/**\n * 列出 skill 目录下的附属文件(排除 SKILL.md 自身)\n */\nfunction listSupportingFiles(skillDir: string): string[] {\n try {\n return fs\n .readdirSync(skillDir)\n .filter((name) => name !== SKILL_FILENAME)\n .map((name) => path.join(skillDir, name))\n } catch {\n return []\n }\n}\n\n/**\n * 默认项目级 skill 目录。\n *\n * 顺序即优先级:`discoverSkills` 的 project 循环对同名是 **last-set wins**,\n * 故 `.otto/skills` 排在 `.claude/skills` 之后 = otto 原生覆盖 .claude(RFC-032)。\n */\nexport function getDefaultProjectSkillDirs(): string[] {\n return isClaudeCompatEnabled() ? ['.claude/skills', '.otto/skills'] : ['.otto/skills']\n}\n\n/**\n * 获取默认的全局 skill 目录列表。\n *\n * 顺序即优先级:`discoverSkills` 的 global 循环对同名是 **first-win**(`!has` 才填),\n * 故 `~/.otto/skills` 排在 `~/.claude/skills` 之前 = otto 原生覆盖 .claude(RFC-032)。\n */\nexport function getDefaultGlobalSkillDirs(): string[] {\n const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? ''\n if (!home) {\n return []\n }\n\n const dirs = [path.join(home, '.otto', 'skills')]\n if (isClaudeCompatEnabled()) {\n dirs.push(path.join(home, '.claude', 'skills'))\n }\n return dirs\n}\n","/**\n * skill-invocation.ts —— RFC review D7:命令隐式 skill 调用检测(纯函数)。\n *\n * 参照 codex `ext/skills/src/invocation.rs`(`implicit_skill_accesses_for_command`):\n * shell 命令里「运行 skill scripts/ 下的脚本」或「读取 skill 的 SKILL.md 文档」都被\n * 视为隐式 skill 调用——不需模型显式走 skill 工具,用户/模型直接在 bash 里引用即命中。\n *\n * 纯函数、无 IO、不读时钟(符合引擎确定性约束);分词不引第三方 shlex(最小引号/转义\n * tokenizer,覆盖 bash 常见形态;无法完美分词的退化空白切分与 codex 同语义)。\n */\n\n/** 一次命令中识别出的 skill 文件访问(脚本执行或文档读取)。 */\nexport type ImplicitSkillAccessKind = 'script' | 'document'\n\nexport interface ImplicitSkillAccess {\n kind: ImplicitSkillAccessKind\n /** 命令中出现的路径原文(相对 cwd)。 */\n path: string\n}\n\n/** 常见脚本 runner(codex RUNNERS 同款 + 常见补充)。 */\nconst SCRIPT_RUNNERS = new Set([\n 'python', 'python3', 'bash', 'zsh', 'sh', 'node', 'deno', 'ruby', 'perl', 'pwsh',\n 'python.exe', 'node.exe',\n])\n\n/** 常见脚本扩展名(codex SCRIPT_EXTENSIONS 同款)。 */\nconst SCRIPT_EXTENSIONS = new Set(['.py', '.sh', '.js', '.ts', '.rb', '.pl', '.ps1', '.mjs', '.cjs'])\n\n/** 常见文档读取命令(codex ParsedCommand::Read 同款 + PowerShell 别名)。 */\nconst READ_COMMANDS = new Set([\n 'cat', 'less', 'more', 'head', 'tail', 'type', 'get-content', 'gc', 'sed', 'awk', 'grep',\n])\n\n/**\n * 最小 shlex 式分词:处理单/双引号与反斜杠转义;引号不闭合时按剩余原文收尾。\n * 退化空白切分与 codex shlex::split 失败路径同语义。\n */\nexport function tokenizeCommand(command: string): string[] {\n const tokens: string[] = []\n let current = ''\n let quote: '\"' | \"'\" | null = null\n let escaped = false\n let hasToken = false\n\n for (const ch of command) {\n if (escaped) {\n current += ch\n escaped = false\n hasToken = true\n continue\n }\n if (ch === '\\\\' && quote !== \"'\") {\n escaped = true\n hasToken = true\n continue\n }\n if (quote !== null) {\n if (ch === quote) {\n quote = null\n } else {\n current += ch\n }\n hasToken = true\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n hasToken = true\n continue\n }\n if (ch === ' ' || ch === '\\t' || ch === '\\n') {\n if (hasToken) {\n tokens.push(current)\n current = ''\n hasToken = false\n }\n continue\n }\n current += ch\n hasToken = true\n }\n if (hasToken) tokens.push(current)\n return tokens\n}\n\nfunction commandBasename(token: string): string {\n const parts = token.split('/')\n const last = parts[parts.length - 1] ?? token\n return last\n}\n\nfunction basename(token: string): string {\n const parts = token.replace(/\\\\/g, '/').split('/')\n return parts[parts.length - 1] ?? token\n}\n\nfunction hasScriptExtension(token: string): boolean {\n const b = basename(token).toLowerCase()\n return [...SCRIPT_EXTENSIONS].some((ext) => b.endsWith(ext))\n}\n\n/**\n * 识别一次 shell 命令中静态可判定的 skill 访问(脚本执行 + 文档读取)。\n *\n * @param command 完整命令串。\n * @returns 访问列表(脚本执行优先;去重保持首次出现顺序)。\n */\nexport function implicitSkillAccessesForCommand(command: string): ImplicitSkillAccess[] {\n const tokens = tokenizeCommand(command)\n if (tokens.length === 0) return []\n\n const accesses: ImplicitSkillAccess[] = []\n\n // 1) 脚本执行:runner + 脚本扩展名(`python scripts/foo.py` / `bash skill.sh`)\n const runner = commandBasename(tokens[0] ?? '').toLowerCase()\n if (SCRIPT_RUNNERS.has(runner)) {\n for (let i = 1; i < tokens.length; i++) {\n const token = tokens[i]!\n if (token.startsWith('-')) continue // 跳过 runner 旗标(-u 等)\n if (hasScriptExtension(token)) {\n accesses.push({ kind: 'script', path: token })\n break\n }\n }\n }\n\n // 2) 文档读取:read 命令 + 路径参数(`cat path/SKILL.md` / `Get-Content skill/SKILL.md`)\n if (READ_COMMANDS.has(runner)) {\n for (let i = 1; i < tokens.length; i++) {\n const token = tokens[i]!\n if (token.startsWith('-')) continue // 跳过旗标(-n 等);旗标带参的简化处理:跳过值不精确,但命中仍保守(false positive 无害)\n // 管道/重定向等 shell 语法 token 不当作路径\n if (token === '|' || token === '||' || token === '&&' || token === ';' || token === '>') break\n accesses.push({ kind: 'document', path: token })\n }\n }\n\n return accesses\n}\n","/**\n * skill-usage.ts —— RFC-327 M3-01:skill 执行 usage 的计数存储与写入端口。\n *\n * RFC-318 技能回路的抽取侧此前只有 `skill.executed` 事件(emit 即弃,零落库)。\n * 本模块补上 usage 落库:\n * - `SkillUsageRecorder` 是写入端口——SkillRegistry 经此落库;宿主可注入自定义\n * 实现(转发到 EvolutionSignalStore / 磁盘持久化等)。\n * - `SkillUsageStore` 是默认进程内聚合计数实现(执行次数 / 成功失败 / 累计时长)。\n *\n * 确定性纪律:本层**不直呼 Date.now**——时间戳 `at` 由调用方(SkillRegistry,\n * 经注入时钟端口)传入。\n */\n\nexport interface SkillUsageRecord {\n /** skill 名(`definition.metadata.name`)。 */\n name: string\n /** 本次执行是否成功(失败 = 未找到 / 已禁用等未实际执行路径)。 */\n success: boolean\n /** 本次执行耗时(ms)。失败路径为 0(未实际执行)。 */\n durationMs: number\n /** 执行时刻——调用方注入时钟刻度(确定性纪律,本层不直呼 Date.now)。 */\n at: number\n}\n\n/** usage 写入端口——SkillRegistry 经此落库。 */\nexport interface SkillUsageRecorder {\n record(usage: SkillUsageRecord): void\n}\n\n/** 单 skill 的聚合计数(RFC-327 M3-01 的\"执行次数/成功失败/时长\"三要素)。 */\nexport interface SkillUsageAggregate {\n name: string\n /** 总执行次数(成功 + 失败)。 */\n executions: number\n successes: number\n failures: number\n /** 累计耗时(ms)。 */\n totalDurationMs: number\n /** 最近一次执行时刻(注入时钟刻度)。 */\n lastExecutedAt: number\n}\n\n/** 进程内聚合计数实现——SkillRegistry 缺省落库点。 */\nexport class SkillUsageStore implements SkillUsageRecorder {\n private readonly aggregates = new Map<string, SkillUsageAggregate>()\n\n record(usage: SkillUsageRecord): void {\n const existing = this.aggregates.get(usage.name)\n const aggregate: SkillUsageAggregate = existing ?? {\n name: usage.name,\n executions: 0,\n successes: 0,\n failures: 0,\n totalDurationMs: 0,\n lastExecutedAt: usage.at,\n }\n aggregate.executions += 1\n if (usage.success) {\n aggregate.successes += 1\n } else {\n aggregate.failures += 1\n }\n aggregate.totalDurationMs += usage.durationMs\n aggregate.lastExecutedAt = usage.at\n this.aggregates.set(usage.name, aggregate)\n }\n\n get(name: string): SkillUsageAggregate | undefined {\n return this.aggregates.get(name)\n }\n\n all(): SkillUsageAggregate[] {\n return [...this.aggregates.values()]\n }\n\n get size(): number {\n return this.aggregates.size\n }\n}\n","import { TypedEventEmitter, createLogger } from '@x-otto/shared'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport {\n discoverSkills,\n getDefaultGlobalSkillDirs,\n getDefaultProjectSkillDirs,\n} from './skill-discovery'\nimport { implicitSkillAccessesForCommand } from './skill-invocation'\nimport { parseSkillContent } from './skill-parser'\nimport { SkillUsageStore } from './skill-usage'\n\nimport type { SkillUsageAggregate, SkillUsageRecorder } from './skill-usage'\nimport type {\n SkillDefinition,\n SkillLibraryConfig,\n SkillSource,\n RegisteredSkill,\n SkillStatus,\n SkillExecutionContext,\n SkillExecutionResult,\n SkillEvents,\n} from './types'\n\nconst logger = createLogger('@x-otto/skill:registry')\n\nexport interface SkillRegistryOptions {\n /** 工作目录 */\n workspaceDir: string\n /** skill 库配置 */\n library?: SkillLibraryConfig\n /** 禁用的 skill 名称列表 */\n disabled?: string[]\n /**\n * RFC-327 M3-01:skill 执行 usage 的写入端口(落库点)。缺省用进程内\n * `SkillUsageStore`(执行次数/成功失败/时长聚合),宿主可注入自定义实现\n * (转发到 EvolutionSignalStore / 磁盘持久化)。\n */\n usageStore?: SkillUsageRecorder\n /**\n * 时钟端口——usage 时间戳与 execute 计时走注入时钟(确定性纪律,不直呼\n * Date.now)。缺省回退 Date.now(宿主未注入时向后兼容)。\n */\n now?: () => number\n /**\n * RFC-383 M1(第③层进化链):进化信号统一出口(可选)。注入后 execute 的\n * recordUsage 旁路 append skill.usage 信号(覆盖成功/未找到/禁用三出口)——\n * 供内化决策器(M2)与第④层消费。不走 usageStore 注入(避免 instanceof 门控\n * 使聚合读取静默失效);本端口是旁路,默认 SkillUsageStore 聚合保持原样。\n *\n * 形状 = EvolutionSignalSink(@x-otto/memory)的结构子集——skill 是叶子包,\n * 不依赖 memory(避免引入 ai/persistence 依赖链);宿主注入 memory 的\n * EvolutionSignalStore 时结构兼容(duck typing)。\n */\n evolutionSignals?: {\n append(record: {\n kind: 'skill.usage'\n key: string\n value: number\n /** 会话归属(RFC-385 §11 透传,可选)——execute 路径有 context.sessionId 即带。 */\n sessionId?: string\n detail?: Record<string, unknown>\n }): void\n }\n}\n\nexport class SkillRegistry extends TypedEventEmitter<SkillEvents> {\n private skills = new Map<string, RegisteredSkill>()\n private readonly workspaceDir: string\n private readonly library?: SkillLibraryConfig\n private disabledSet: Set<string>\n /** 可变的 plugin 目录列表(reloadCapabilities 更新为新安装 plugin 的 dirs) */\n private pluginDirs?: Array<{ id: string; dir: string }>\n /**\n * RFC-396 M1:上一轮 discover **实际写入 map** 的 name 集(不含被「磁盘不覆盖手动\n * 注册」跳过的名)。reconcile 用它算差集——只清理「上轮 discover 写入、本轮未写入」\n * 的条目,手动注册(inline via loadSkill / computed via axis handler,不经 discover)\n * 永不进集、永不被清理。绝不按 source.type 判别(computed 用 type:'plugin',会误删)。\n */\n private discoverRegisteredNames = new Set<string>()\n /** RFC-327 M3-01:usage 落库端口(缺省 = 内部 SkillUsageStore)。 */\n private readonly usageStore: SkillUsageRecorder\n /** RFC-383 M1:进化信号旁路(可选,见构造 options 注释)。 */\n private readonly evolutionSignals?: {\n append(record: {\n kind: 'skill.usage'\n key: string\n value: number\n /** 会话归属(RFC-385 §11 透传,可选)。 */\n sessionId?: string\n detail?: Record<string, unknown>\n }): void\n }\n private readonly now: () => number\n\n constructor({ workspaceDir, library, disabled, usageStore, now, evolutionSignals }: SkillRegistryOptions) {\n super()\n this.workspaceDir = workspaceDir\n this.library = library\n // RFC-396 M3:disabled 只从 options.disabled 取(library.disabled 死通道已删)。\n // 生产真值由 app-lifecycle 在 ensureSettingsLoaded 之后经 disable() 事后应用——\n // 构造期读 settings 恒空(app.ts 时序陷阱),故构造期 disabled 仅测试/惰性注入用。\n this.disabledSet = new Set(disabled ?? [])\n this.pluginDirs = library?.pluginDirs\n this.usageStore = usageStore ?? new SkillUsageStore()\n this.evolutionSignals = evolutionSignals\n this.now = now ?? (() => Date.now())\n }\n\n /**\n * 更新 plugin 贡献的 skill 目录列表(用于 reloadCapabilities 热刷新)。\n * 下次 discover() 将扫描新的 plugin 目录。\n */\n updatePluginDirs(dirs: Array<{ id: string; dir: string }>): void {\n this.pluginDirs = dirs\n }\n\n /**\n * 发现并注册所有可用 skill,差集 reconcile(RFC-396 M1)。\n *\n * 扫描项目本地 + 全局 + 插件目录,以本轮扫描为真源:\n * - 新增/仍在的磁盘 skill → 注册(同名不覆盖手动注册的 inline/computed);\n * - 上一轮 discover 写入、本轮未再写入的 → unregister(修「删目录/卸插件后残留」)。\n *\n * 手动注册(loadSkill 的 inline、axis handler 的 computed)不经本方法,永不进\n * `discoverRegisteredNames`,永不被 reconcile 清理。\n */\n async discover(): Promise<void> {\n const config: SkillLibraryConfig = {\n projectDirs: this.library?.projectDirs ?? getDefaultProjectSkillDirs(),\n globalDirs: this.library?.globalDirs ?? getDefaultGlobalSkillDirs(),\n pluginDirs: this.pluginDirs ?? this.library?.pluginDirs,\n }\n\n const discovered = await discoverSkills(config, this.workspaceDir)\n\n // 本轮 discover 实际写入 map 的 name(不含被「磁盘不覆盖手动注册」跳过的)。\n const nextDiscoverNames = new Set<string>()\n\n for (const [name, { definition, source, error }] of discovered) {\n const existing = this.skills.get(name)\n // 磁盘不覆盖手动注册(inline/computed 是显式意图,优先级更高,RFC-396 D1)——\n // 「手动注册」= 已存在且不在上一轮 discover 集内的条目。\n const isManualRegistration =\n existing !== undefined && !this.discoverRegisteredNames.has(name)\n if (isManualRegistration) {\n continue\n }\n\n // RFC-396 M3:解析失败的条目注册 error 态(供 TUI 展示,不进 catalog/不可 execute);\n // 正常条目按 disabled 列表定 available/disabled。\n const status: SkillStatus = error\n ? 'error'\n : this.disabledSet.has(name)\n ? 'disabled'\n : 'available'\n this.skills.set(name, error ? { definition, source, status, error } : { definition, source, status })\n nextDiscoverNames.add(name)\n }\n\n // 差集清理:上一轮 discover 写入、本轮未再写入的条目 → unregister(删目录/卸插件后残留)。\n for (const name of this.discoverRegisteredNames) {\n if (!nextDiscoverNames.has(name)) {\n this.skills.delete(name)\n }\n }\n\n this.discoverRegisteredNames = nextDiscoverNames\n\n logger.info(\n 'Skill registry reconciled: %d skills (%d available, %d disabled, %d from discover)',\n this.skills.size,\n this.getAvailable().length,\n [...this.skills.values()].filter((s) => s.status === 'disabled').length,\n nextDiscoverNames.size,\n )\n }\n\n /**\n * 手动注册一个 skill(用于内联/插件 skill)\n */\n register(definition: SkillDefinition, source: SkillSource): void {\n const { name } = definition.metadata\n const status: SkillStatus = this.disabledSet.has(name) ? 'disabled' : 'available'\n\n this.skills.set(name, { definition, source, status })\n logger.debug('Registered skill \"%s\" from %s', name, source.type)\n }\n\n /**\n * 注销 skill\n */\n unregister(name: string): boolean {\n return this.skills.delete(name)\n }\n\n /**\n * 获取所有已注册 skill\n */\n getAll(): ReadonlyMap<string, RegisteredSkill> {\n return this.skills\n }\n\n /**\n * 获取所有可用(非 disabled/error)skill\n */\n getAvailable(): RegisteredSkill[] {\n return [...this.skills.values()].filter(\n (s) => s.status === 'available' || s.status === 'loaded',\n )\n }\n\n /**\n * 按名称获取 skill\n */\n get(name: string): RegisteredSkill | undefined {\n return this.skills.get(name)\n }\n\n execute(context: SkillExecutionContext): SkillExecutionResult {\n const at = this.now()\n const startTime = at\n const registered = this.skills.get(context.skillName)\n // RFC-327 M3-01:三条出口都落 usage(成功 + 失败路径),时间戳走注入时钟。\n const recordUsage = (success: boolean, durationMs: number): void => {\n this.usageStore.record({ name: context.skillName, success, durationMs, at })\n // RFC-383 M1:进化信号旁路——覆盖全部出口(成功/未找到/禁用),\n // detail 带 success/durationMs 供差分(R5 数据形态)。\n // RFC-385 M1:透传 context.sessionId(可选,向后兼容——不传则 append 不带该字段)。\n this.evolutionSignals?.append({\n kind: 'skill.usage',\n key: context.skillName,\n value: 1,\n ...(context.sessionId !== undefined ? { sessionId: context.sessionId } : {}),\n detail: { success, durationMs },\n })\n }\n\n if (!registered) {\n recordUsage(false, 0)\n return { success: false, error: `Skill \"${context.skillName}\" not found` }\n }\n\n if (registered.status === 'disabled') {\n recordUsage(false, 0)\n return { success: false, error: `Skill \"${context.skillName}\" is disabled` }\n }\n\n // RFC-396 M3 / R3:error 态(解析失败占位)不可执行——fail-closed,不进模型面。\n if (registered.status === 'error') {\n recordUsage(false, 0)\n return { success: false, error: registered.error ?? `Skill \"${context.skillName}\" failed to parse` }\n }\n\n if (registered.status === 'available') {\n registered.status = 'loaded'\n registered.loadedAt = this.now()\n }\n\n // RFC-396 M4:磁盘来源 skill 按需重读 body(修热编辑不生效)——execute 是低频操作,\n // 同步 readFileSync 读小文件的阻塞可忽略,与 discovery 全同步 IO 风格一致。\n //\n // 磁盘来源判别用 source.type(非 existsSync)——这与 M1 reconcile「禁用 source.type」\n // 无冲突:M1 是删除决策(computed 用 type:plugin 会被误删),此处是「读 body 从哪来」\n // 的来源选择。inline(loadSkill 快照)/ computed(合成 filePath)用内存 body(RFC D5);\n // project/global/真实 plugin 磁盘 skill 重读磁盘。\n //\n // 失败路径(评审修订 v4):重读失败 → **优雅降级用内存 body**(discover 时已解析的有效\n // 内容),execute 仍成功、不删条目。execute 是读操作,一次读盘抖动(文件被编辑器临时\n // 移动/权限抖动)不该销毁 registry 条目——删除是 discover+reconcile(M1) 以磁盘为真源\n // 时的职责,不该由 execute 副作用承担(否则内化/catalog 持有者会因 execute 抖动被清)。\n const { source, definition } = registered\n const isDiskSourced =\n source.type === 'project' ||\n source.type === 'global' ||\n (source.type === 'plugin' && !definition.filePath.includes('#computed:'))\n let content = definition.body\n if (isDiskSourced) {\n try {\n const raw = fs.readFileSync(definition.filePath, 'utf-8')\n const fresh = parseSkillContent(raw, definition.filePath, definition.directory, definition.supportingFiles)\n content = fresh.body\n // 同步内存态 body,使后续 catalog/内化摘要也反映最新(不重算 metadata 避免改名歧义)。\n definition.body = fresh.body\n } catch (err) {\n // 重读失败(文件删/改名/权限抖动)→ 降级用内存 body(保持 execute 成功、不删条目)。\n logger.warn(\n 'Skill \"%s\" body reread failed, falling back to in-memory body: %s',\n context.skillName,\n (err as Error).message,\n )\n }\n }\n\n const durationMs = this.now() - startTime\n this.emit('skill.executed', { name: context.skillName, success: true, durationMs })\n recordUsage(true, durationMs)\n\n return {\n success: true,\n content,\n }\n }\n\n /**\n * RFC-327 M3-01 / RFC-396 M2:读取单 skill 的 usage 聚合(执行次数/成功失败/时长)。\n * duck-typing:注入的 usageStore 若实现了 `get`(如默认 SkillUsageStore 或宿主的\n * 组合 recorder),即读取;否则(纯 `{record}` 写端口)返回 undefined。去 instanceof\n * 门控——避免注入实现了聚合读取的自定义 recorder 后读取被静默门死(内化决策器 M2 消费)。\n */\n getSkillUsage(name: string): SkillUsageAggregate | undefined {\n const store = this.usageStore as Partial<Pick<SkillUsageStore, 'get'>>\n return typeof store.get === 'function' ? store.get(name) : undefined\n }\n\n /** RFC-327 M3-01 / RFC-396 M2:全部 skill 的 usage 聚合(duck-typing:usageStore 实现 `all` 即读,否则空数组)。 */\n getAllSkillUsage(): SkillUsageAggregate[] {\n const store = this.usageStore as Partial<Pick<SkillUsageStore, 'all'>>\n return typeof store.all === 'function' ? store.all() : []\n }\n\n /**\n * 禁用 skill\n */\n disable(name: string): void {\n this.disabledSet.add(name)\n const registered = this.skills.get(name)\n // RFC-396 M3/R3:error 态(解析失败占位)不参与 enable/disable 状态机——它不是\n // 「可用但被关」的正常 skill,disabledSet 记录禁用意图但不覆盖 error 状态(否则\n // disable→enable 循环会把 error 复活成 available,绕过 R3 fail-closed)。\n if (registered && registered.status !== 'error') {\n registered.status = 'disabled'\n }\n }\n\n /**\n * 启用 skill\n */\n enable(name: string): void {\n this.disabledSet.delete(name)\n const registered = this.skills.get(name)\n // RFC-396 M3/R3:error 态不可被 enable 复活为 available——只允许 disabled→available。\n if (registered && registered.status === 'disabled') {\n registered.status = 'available'\n }\n }\n\n /**\n * catalog 里每条 skill description 的最大字符数(渐进披露的正确边界——完整正文本就该\n * 靠 skill 工具按需取回,不该把全文塞进常驻的 system prompt catalog)。\n *\n * 取值依据:本仓真实 skill(rfc-todos)description 长度 1339 字符,若装 10+ 个同量级\n * skill,catalog 本身就会膨胀到与基座 prompt 同量级。200 字符能保留\"这个 skill 是干\n * 什么的、什么时候用\"的判断信息,同时把单条上限压到基座 prompt 的 1/10 量级。\n */\n private static readonly CATALOG_DESCRIPTION_MAX_CHARS = 200\n\n /**\n * 按字符边界截断 description(不切断多字节字符),超长时追加省略号标记。\n * 截断是**渲染层**行为——`definition.metadata.description` 原文不变,`skill` 工具\n * 取全文时仍拿到完整内容,catalog 只是给模型的\"目录索引\"而非替代正文。\n */\n private static truncateDescription(description: string): string {\n const max = SkillRegistry.CATALOG_DESCRIPTION_MAX_CHARS\n const chars = Array.from(description)\n if (chars.length <= max) return description\n return `${chars.slice(0, max).join('')}…`\n }\n\n /**\n * 构建 skill catalog(用于 system prompt 注入)\n * 格式:简短列表,仅 name + description(description 截断到固定上限,见\n * CATALOG_DESCRIPTION_MAX_CHARS——渐进披露:目录常驻、正文按需取回,不应本末倒置)\n */\n buildCatalog(): string {\n const available = this.getAvailable()\n if (available.length === 0) {\n return ''\n }\n\n const lines = ['## Available Skills', '']\n for (const { definition } of available) {\n const { name, description, trigger } = definition.metadata\n const triggerTag = trigger === 'manual' ? ' [manual]' : ''\n lines.push(`- **${name}**${triggerTag}: ${SkillRegistry.truncateDescription(description)}`)\n }\n\n return lines.join('\\n')\n }\n\n /**\n * RFC review D7:命令隐式 skill 调用检测(codex invocation.rs 移植)。\n *\n * 把 shell 命令里出现的「skill 脚本执行」或「skill 文档读取」路径与已注册 skill 匹配:\n * - script:路径落在 skill 的 scripts/ 子目录;\n * - document:路径指向 skill 的 SKILL.md(目录自身或文件路径)。\n *\n * 命中即返回该 skill(并 emit 'skill.invoked' + 记录 usage success/duration=0——\n * 隐式调用是「发现」,不是真实执行,仅作观测信号供技能回路/进化观测消费)。\n * 多命中取第一个;未命中返回 undefined。\n */\n detectImplicitInvocation(command: string, cwd: string): RegisteredSkill | undefined {\n const accesses = implicitSkillAccessesForCommand(command)\n if (accesses.length === 0) return undefined\n\n for (const access of accesses) {\n for (const skill of this.skills.values()) {\n if (skill.status !== 'available') continue\n const dir = path.resolve(skill.definition.directory)\n const target = path.resolve(cwd, access.path)\n const inside = (sub: string): boolean => {\n const subRel = path.relative(path.join(dir, sub), target)\n return !subRel.startsWith('..') && !path.isAbsolute(subRel)\n }\n if (access.kind === 'script' && inside('scripts')) {\n this.recordInvocation(skill.definition.metadata.name, 'script')\n return skill\n }\n if (access.kind === 'document') {\n const base = path.basename(target)\n const docHit =\n target === dir || target === path.join(dir, 'SKILL.md') || base === 'SKILL.md'\n if (docHit && path.dirname(target) === dir) {\n this.recordInvocation(skill.definition.metadata.name, 'document')\n return skill\n }\n }\n }\n }\n return undefined\n }\n\n /** RFC review D7:隐式调用记录——usage 落库(duration 0,观测信号)+ 事件。 */\n private recordInvocation(name: string, kind: 'script' | 'document'): void {\n this.usageStore.record({ name, success: true, durationMs: 0, at: this.now() })\n // RFC-383 §9 修订 2 / RFC-385 M1 前置项(方案 X):隐式调用同样投影到 evolutionSignals,\n // 与 recordUsage 投影同形——detail.implicit=true 区分「发现」而非真实执行。\n // 否则隐式调用(TUI 高频路径)只涨 usage 聚合、不产 skill.usage 信号,训练质量标注系统性低估。\n // 隐式检测无 SkillExecutionContext,故此处不带 sessionId(向后兼容,无会话归属)。\n this.evolutionSignals?.append({\n kind: 'skill.usage',\n key: name,\n value: 1,\n detail: { success: true, durationMs: 0, implicit: true },\n })\n this.emit('skill.invoked', { name, kind })\n }\n\n /**\n * 获取 skill 总数\n */\n get size(): number {\n return this.skills.size\n }\n}\n\nexport function createSkillRegistry(options: SkillRegistryOptions): SkillRegistry {\n return new SkillRegistry(options)\n}\n","import { parseSkillContent } from './skill-parser'\n\nimport type { SkillRegistry } from './skill-registry'\nimport type { SkillProvider } from './types'\n\n/**\n * 常见路径遗漏:`.claude/rfc-to-todos/SKILL.md` → `.claude/skills/rfc-to-todos/SKILL.md`。\n * 仅处理以 `.claude/` 开头、不含 `skills/` 的相对路径。返回 null 表示无需补全。\n */\nfunction resolveClaudeSkillsPath(path: string): string | null {\n if (path.startsWith('.claude/') && !path.includes('skills/')) {\n return '.claude/skills/' + path.slice('.claude/'.length)\n }\n return null\n}\n\nexport function createRegistrySkillProvider(\n registry: SkillRegistry,\n workspaceDir: string,\n): SkillProvider {\n return {\n async load(path) {\n try {\n const { readFile } = await import('node:fs/promises')\n const { isAbsolute, resolve, dirname: dir } = await import('node:path')\n\n const tryPath = async (p: string): Promise<string | null> => {\n const filePath = isAbsolute(p) ? p : resolve(workspaceDir, p)\n try {\n return await readFile(filePath, 'utf-8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw err\n }\n }\n\n let content = await tryPath(path)\n let usedPath = path\n\n if (content === null) {\n const altPath = resolveClaudeSkillsPath(path)\n if (altPath) {\n const altContent = await tryPath(altPath)\n if (altContent !== null) {\n content = altContent\n usedPath = altPath\n }\n }\n }\n\n if (content === null) {\n return { success: false, error: `ENOENT: no such file, open '${path}'` }\n }\n\n const filePath = isAbsolute(usedPath) ? usedPath : resolve(workspaceDir, usedPath)\n const definition = parseSkillContent(content, filePath, dir(filePath))\n\n registry.register(definition, { type: 'inline', root: dir(filePath) })\n return { success: true, name: definition.metadata.name }\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : String(error),\n }\n }\n },\n\n async execute(name, sessionId) {\n return registry.execute({ skillName: name, workspaceDir, sessionId })\n },\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-otto/skill",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.4",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "yaml": "2.8.3",
18
- "@x-otto/shared": "0.1.0-alpha.6",
19
- "@x-otto/env": "0.1.0-alpha.6"
18
+ "@x-otto/env": "0.1.0-alpha.7",
19
+ "@x-otto/shared": "0.1.0-alpha.7"
20
20
  },
21
21
  "private": false,
22
22
  "publishConfig": {