berry-agent 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/.api-emit.stamp +1 -0
  4. package/dist/.build-meta.json +6 -0
  5. package/dist/agent/events.js +1 -0
  6. package/dist/agent/index.js +2 -0
  7. package/dist/agent/loop.js +139 -0
  8. package/dist/agent/queue.js +119 -0
  9. package/dist/agent/stream.js +62 -0
  10. package/dist/agent/tools-batch.js +221 -0
  11. package/dist/agent/types.js +8 -0
  12. package/dist/api/berry-agent-llm.d.ts +18 -0
  13. package/dist/api/berry-agent.d.ts +9 -0
  14. package/dist/api/surface.json +1632 -0
  15. package/dist/api/tsconfig.paths.json +12 -0
  16. package/dist/api/typebox-compile.d.ts +2 -0
  17. package/dist/api/typebox-value.d.ts +2 -0
  18. package/dist/api/typebox.d.ts +2 -0
  19. package/dist/browser/cdp.js +256 -0
  20. package/dist/browser/codes.js +31 -0
  21. package/dist/browser/discover.js +107 -0
  22. package/dist/browser/engine.js +169 -0
  23. package/dist/browser/index.js +17 -0
  24. package/dist/browser/install.js +342 -0
  25. package/dist/browser/page.js +321 -0
  26. package/dist/browser/service.js +158 -0
  27. package/dist/browser/tools.js +198 -0
  28. package/dist/browser/types.js +81 -0
  29. package/dist/channels/ask-queue.js +82 -0
  30. package/dist/channels/commands.js +129 -0
  31. package/dist/channels/engine/cell.js +143 -0
  32. package/dist/channels/engine/diff.js +143 -0
  33. package/dist/channels/engine/engine.js +416 -0
  34. package/dist/channels/engine/index.js +8 -0
  35. package/dist/channels/engine/input-keys.js +86 -0
  36. package/dist/channels/engine/input.js +596 -0
  37. package/dist/channels/engine/memory-io.js +71 -0
  38. package/dist/channels/engine/process-io.js +38 -0
  39. package/dist/channels/engine/types.js +17 -0
  40. package/dist/channels/engine/width.js +122 -0
  41. package/dist/channels/index.js +15 -0
  42. package/dist/channels/registry.js +56 -0
  43. package/dist/channels/sdk/admit.js +29 -0
  44. package/dist/channels/sdk/backend.js +102 -0
  45. package/dist/channels/sdk/cursor.js +56 -0
  46. package/dist/channels/sdk/index.js +20 -0
  47. package/dist/channels/sdk/jsonl.js +208 -0
  48. package/dist/channels/sdk/protocol.js +24 -0
  49. package/dist/channels/sdk/schema.js +91 -0
  50. package/dist/channels/sdk/wire-core.js +446 -0
  51. package/dist/channels/service.js +169 -0
  52. package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
  53. package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
  54. package/dist/channels/tui/autocomplete/popup.js +148 -0
  55. package/dist/channels/tui/autocomplete/provider.js +7 -0
  56. package/dist/channels/tui/autocomplete/token.js +68 -0
  57. package/dist/channels/tui/backend/ansi-rows.js +211 -0
  58. package/dist/channels/tui/backend/main-screen.js +207 -0
  59. package/dist/channels/tui/backend/osc.js +91 -0
  60. package/dist/channels/tui/backend/transcript.js +230 -0
  61. package/dist/channels/tui/backend/tui-backend.js +915 -0
  62. package/dist/channels/tui/editor/editor-model.js +543 -0
  63. package/dist/channels/tui/editor/editor-view.js +140 -0
  64. package/dist/channels/tui/editor/editor.js +275 -0
  65. package/dist/channels/tui/editor/undo-stack.js +35 -0
  66. package/dist/channels/tui/editor/visual-lines.js +126 -0
  67. package/dist/channels/tui/editor/word-nav.js +106 -0
  68. package/dist/channels/tui/history/history-viewer.js +403 -0
  69. package/dist/channels/tui/index.js +3 -0
  70. package/dist/channels/tui/layout.js +167 -0
  71. package/dist/channels/tui/markdown/blocks.js +111 -0
  72. package/dist/channels/tui/markdown/inline.js +75 -0
  73. package/dist/channels/tui/markdown/markdown.js +182 -0
  74. package/dist/channels/tui/memory/memory-viewer.js +540 -0
  75. package/dist/channels/tui/overlay/alt-screen.js +105 -0
  76. package/dist/channels/tui/overlay/overlay.js +57 -0
  77. package/dist/channels/tui/overlay/select-confirm.js +156 -0
  78. package/dist/channels/tui/panels/todo-panel.js +54 -0
  79. package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
  80. package/dist/channels/tui/scroll/scroll-view.js +254 -0
  81. package/dist/channels/tui/status/status-line.js +76 -0
  82. package/dist/channels/tui/text.js +38 -0
  83. package/dist/channels/tui/theme.js +41 -0
  84. package/dist/channels/types.js +11 -0
  85. package/dist/channels/ui-core.js +209 -0
  86. package/dist/checkpoint/capture.js +45 -0
  87. package/dist/checkpoint/codes.js +33 -0
  88. package/dist/checkpoint/command.js +82 -0
  89. package/dist/checkpoint/gate.js +48 -0
  90. package/dist/checkpoint/index.js +20 -0
  91. package/dist/checkpoint/restore.js +172 -0
  92. package/dist/checkpoint/store.js +247 -0
  93. package/dist/checkpoint/types.js +4 -0
  94. package/dist/checkpoint/walk.js +146 -0
  95. package/dist/compaction/ccr-tools.js +112 -0
  96. package/dist/compaction/ccr.js +84 -0
  97. package/dist/compaction/codes.js +21 -0
  98. package/dist/compaction/index.js +18 -0
  99. package/dist/compaction/policy.js +200 -0
  100. package/dist/compaction/service.js +422 -0
  101. package/dist/compaction/slots.js +121 -0
  102. package/dist/compaction/types.js +10 -0
  103. package/dist/context/codes.js +40 -0
  104. package/dist/context/events.js +160 -0
  105. package/dist/context/index.js +13 -0
  106. package/dist/context/logger.js +136 -0
  107. package/dist/context/scope.js +182 -0
  108. package/dist/context/workspace.js +104 -0
  109. package/dist/contracts/agent-events.d.ts +73 -0
  110. package/dist/contracts/agent-events.js +16 -0
  111. package/dist/contracts/api.d.ts +249 -0
  112. package/dist/contracts/api.js +299 -0
  113. package/dist/contracts/approval.d.ts +37 -0
  114. package/dist/contracts/approval.js +9 -0
  115. package/dist/contracts/env-ref.d.ts +41 -0
  116. package/dist/contracts/env-ref.js +32 -0
  117. package/dist/contracts/errors.d.ts +56 -0
  118. package/dist/contracts/errors.js +266 -0
  119. package/dist/contracts/events.d.ts +70 -0
  120. package/dist/contracts/events.js +329 -0
  121. package/dist/contracts/index.d.ts +32 -0
  122. package/dist/contracts/index.js +26 -0
  123. package/dist/contracts/llm.d.ts +275 -0
  124. package/dist/contracts/llm.js +12 -0
  125. package/dist/contracts/messages.d.ts +63 -0
  126. package/dist/contracts/messages.js +65 -0
  127. package/dist/contracts/redact.d.ts +46 -0
  128. package/dist/contracts/redact.js +223 -0
  129. package/dist/contracts/tools.d.ts +197 -0
  130. package/dist/contracts/tools.js +33 -0
  131. package/dist/contracts/types.d.ts +302 -0
  132. package/dist/contracts/types.js +50 -0
  133. package/dist/contracts/ui.d.ts +122 -0
  134. package/dist/contracts/ui.js +15 -0
  135. package/dist/conversation/agent-service.js +61 -0
  136. package/dist/conversation/approval-wiring.js +82 -0
  137. package/dist/conversation/backoff.js +38 -0
  138. package/dist/conversation/codes.js +47 -0
  139. package/dist/conversation/control-tools.js +102 -0
  140. package/dist/conversation/control.js +210 -0
  141. package/dist/conversation/driver.js +922 -0
  142. package/dist/conversation/index.js +12 -0
  143. package/dist/conversation/model-visible.js +169 -0
  144. package/dist/conversation/open-tools.js +126 -0
  145. package/dist/conversation/reseed.js +132 -0
  146. package/dist/conversation/sessions.js +215 -0
  147. package/dist/conversation/todo.js +203 -0
  148. package/dist/conversation/types.js +35 -0
  149. package/dist/conversation/wiring.js +204 -0
  150. package/dist/credentials/codes.js +59 -0
  151. package/dist/credentials/commands.js +186 -0
  152. package/dist/credentials/env-ref.js +34 -0
  153. package/dist/credentials/index.js +28 -0
  154. package/dist/credentials/migration.js +18 -0
  155. package/dist/credentials/oauth.js +266 -0
  156. package/dist/credentials/refresh.js +152 -0
  157. package/dist/credentials/secrets.js +118 -0
  158. package/dist/credentials/types.js +30 -0
  159. package/dist/exec/bash.js +279 -0
  160. package/dist/exec/codes.js +37 -0
  161. package/dist/exec/env.js +99 -0
  162. package/dist/exec/environment.js +39 -0
  163. package/dist/exec/git-guard.js +623 -0
  164. package/dist/exec/index.js +24 -0
  165. package/dist/exec/registry.js +120 -0
  166. package/dist/exec/spawn.js +281 -0
  167. package/dist/exec/tail.js +77 -0
  168. package/dist/exec/types.js +5 -0
  169. package/dist/goal/codes.js +33 -0
  170. package/dist/goal/command.js +175 -0
  171. package/dist/goal/fold.js +121 -0
  172. package/dist/goal/gates.js +122 -0
  173. package/dist/goal/index.js +22 -0
  174. package/dist/goal/migration.js +48 -0
  175. package/dist/goal/service.js +501 -0
  176. package/dist/goal/todo-tool.js +174 -0
  177. package/dist/goal/types.js +1 -0
  178. package/dist/goal/update-tool.js +46 -0
  179. package/dist/host/approval-cmd.js +276 -0
  180. package/dist/host/assembly.js +1146 -0
  181. package/dist/host/boot-failures.js +104 -0
  182. package/dist/host/budget-advisory.js +74 -0
  183. package/dist/host/budget-broadcast.js +50 -0
  184. package/dist/host/builtins.js +54 -0
  185. package/dist/host/cli.js +544 -0
  186. package/dist/host/codes.js +120 -0
  187. package/dist/host/config-schema.js +195 -0
  188. package/dist/host/conversation-stack.js +654 -0
  189. package/dist/host/core-plugins.js +1685 -0
  190. package/dist/host/credentials-cmd.js +42 -0
  191. package/dist/host/disclosure.js +30 -0
  192. package/dist/host/dispatch.js +105 -0
  193. package/dist/host/doors-cmd.js +154 -0
  194. package/dist/host/dump-config.js +44 -0
  195. package/dist/host/hook-dispatch-guard.js +27 -0
  196. package/dist/host/import-gate.js +153 -0
  197. package/dist/host/index.js +54 -0
  198. package/dist/host/issue-session.js +241 -0
  199. package/dist/host/loader.js +372 -0
  200. package/dist/host/main.js +161 -0
  201. package/dist/host/manifest.js +381 -0
  202. package/dist/host/mcp-entry.js +53 -0
  203. package/dist/host/plugin-boot.js +952 -0
  204. package/dist/host/plugin-context.js +672 -0
  205. package/dist/host/plugin-install.js +480 -0
  206. package/dist/host/plugin-reload.js +124 -0
  207. package/dist/host/plugin-store.js +462 -0
  208. package/dist/host/plugin-tools.js +359 -0
  209. package/dist/host/plugin-uninstall.js +257 -0
  210. package/dist/host/plugins-cmd.js +302 -0
  211. package/dist/host/plugins-command.js +130 -0
  212. package/dist/host/plugins-config.js +195 -0
  213. package/dist/host/prompt-sections.js +133 -0
  214. package/dist/host/run-entry.js +544 -0
  215. package/dist/host/runtime.js +209 -0
  216. package/dist/host/scheduler-tick.js +294 -0
  217. package/dist/host/serve-daemon.js +501 -0
  218. package/dist/host/serve-entry.js +279 -0
  219. package/dist/host/session-anchor.js +41 -0
  220. package/dist/host/sessions-cmd.js +219 -0
  221. package/dist/host/sessions-face.js +47 -0
  222. package/dist/host/settings-store.js +112 -0
  223. package/dist/host/signals.js +69 -0
  224. package/dist/host/single-instance.js +105 -0
  225. package/dist/host/subagent-factory.js +291 -0
  226. package/dist/host/testkit/harness.js +283 -0
  227. package/dist/host/testkit/index.js +14 -0
  228. package/dist/host/testkit/matrix.js +302 -0
  229. package/dist/host/tool-policy-store.js +177 -0
  230. package/dist/host/triggers.js +212 -0
  231. package/dist/host/tui-entry.js +216 -0
  232. package/dist/host/webui-bridge.js +165 -0
  233. package/dist/issue/codes.js +38 -0
  234. package/dist/issue/filter.js +146 -0
  235. package/dist/issue/github.js +116 -0
  236. package/dist/issue/index.js +17 -0
  237. package/dist/issue/mount.js +70 -0
  238. package/dist/issue/poll.js +58 -0
  239. package/dist/issue/service.js +341 -0
  240. package/dist/issue/tools.js +70 -0
  241. package/dist/issue/types.js +34 -0
  242. package/dist/issue/webhook.js +126 -0
  243. package/dist/llm/codes.js +77 -0
  244. package/dist/llm/complete.js +196 -0
  245. package/dist/llm/events.js +15 -0
  246. package/dist/llm/index.js +24 -0
  247. package/dist/llm/inflight.js +65 -0
  248. package/dist/llm/model-id.js +58 -0
  249. package/dist/llm/provider-face.d.ts +32 -0
  250. package/dist/llm/provider-face.js +34 -0
  251. package/dist/llm/recovery.js +146 -0
  252. package/dist/llm/runtime.js +46 -0
  253. package/dist/llm/stream-fn.js +147 -0
  254. package/dist/lsp/codes.js +31 -0
  255. package/dist/lsp/connection.js +147 -0
  256. package/dist/lsp/frame.js +110 -0
  257. package/dist/lsp/index.js +16 -0
  258. package/dist/lsp/inject.js +58 -0
  259. package/dist/lsp/instance.js +264 -0
  260. package/dist/lsp/service.js +522 -0
  261. package/dist/lsp/tools.js +53 -0
  262. package/dist/lsp/types.js +201 -0
  263. package/dist/mcp/bridge.js +222 -0
  264. package/dist/mcp/codes.js +21 -0
  265. package/dist/mcp/index.js +19 -0
  266. package/dist/mcp/jsonrpc.js +230 -0
  267. package/dist/mcp/service.js +125 -0
  268. package/dist/mcp/tools.js +145 -0
  269. package/dist/mcp/types.js +108 -0
  270. package/dist/memory/cite.js +131 -0
  271. package/dist/memory/codes.js +50 -0
  272. package/dist/memory/command.js +71 -0
  273. package/dist/memory/consolidate.js +219 -0
  274. package/dist/memory/cycle.js +109 -0
  275. package/dist/memory/dao.js +878 -0
  276. package/dist/memory/diff.js +238 -0
  277. package/dist/memory/extract.js +181 -0
  278. package/dist/memory/fts.js +58 -0
  279. package/dist/memory/index.js +42 -0
  280. package/dist/memory/inject.js +354 -0
  281. package/dist/memory/merge.js +178 -0
  282. package/dist/memory/migration.js +132 -0
  283. package/dist/memory/pollution.js +59 -0
  284. package/dist/memory/port.js +385 -0
  285. package/dist/memory/review.js +201 -0
  286. package/dist/memory/scan.js +89 -0
  287. package/dist/memory/tools.js +508 -0
  288. package/dist/memory/types.js +140 -0
  289. package/dist/obs/codes.js +38 -0
  290. package/dist/obs/db.js +115 -0
  291. package/dist/obs/index.js +20 -0
  292. package/dist/obs/rollup.js +61 -0
  293. package/dist/obs/service.js +363 -0
  294. package/dist/obs/session-tools.js +282 -0
  295. package/dist/obs/session-view.js +225 -0
  296. package/dist/obs/tool.js +97 -0
  297. package/dist/obs/types.js +1 -0
  298. package/dist/persist/audit.js +63 -0
  299. package/dist/persist/aux.js +39 -0
  300. package/dist/persist/codes.js +35 -0
  301. package/dist/persist/index.js +30 -0
  302. package/dist/persist/load-history.js +50 -0
  303. package/dist/persist/migrations.js +37 -0
  304. package/dist/persist/paths.js +108 -0
  305. package/dist/persist/persistence.js +229 -0
  306. package/dist/persist/schema.js +99 -0
  307. package/dist/persist/secret-box.js +95 -0
  308. package/dist/persist/store.js +706 -0
  309. package/dist/persist/write-behind.js +238 -0
  310. package/dist/safety/approval.js +171 -0
  311. package/dist/safety/bwrap.js +123 -0
  312. package/dist/safety/codes.js +57 -0
  313. package/dist/safety/danger.js +505 -0
  314. package/dist/safety/gate.js +226 -0
  315. package/dist/safety/index.js +24 -0
  316. package/dist/safety/presets.js +73 -0
  317. package/dist/safety/roots.js +139 -0
  318. package/dist/safety/sandbox.js +214 -0
  319. package/dist/safety/seatbelt.js +88 -0
  320. package/dist/safety/sensitive.js +34 -0
  321. package/dist/safety/tool-policy.js +203 -0
  322. package/dist/safety/types.js +13 -0
  323. package/dist/scheduler/codes.js +54 -0
  324. package/dist/scheduler/cron-backend.js +135 -0
  325. package/dist/scheduler/engine.js +280 -0
  326. package/dist/scheduler/gates.js +60 -0
  327. package/dist/scheduler/index.js +25 -0
  328. package/dist/scheduler/migration.js +22 -0
  329. package/dist/scheduler/runner.js +171 -0
  330. package/dist/scheduler/schedule.js +193 -0
  331. package/dist/scheduler/service.js +283 -0
  332. package/dist/scheduler/tick.js +139 -0
  333. package/dist/scheduler/types.js +1 -0
  334. package/dist/sdk/http.js +865 -0
  335. package/dist/sdk/index.js +22 -0
  336. package/dist/sdk/mcp.js +289 -0
  337. package/dist/sdk/plugin-route-registry.js +126 -0
  338. package/dist/sdk/plugin-routes.js +149 -0
  339. package/dist/sdk/security.js +116 -0
  340. package/dist/sdk/types.js +32 -0
  341. package/dist/session/budget.js +159 -0
  342. package/dist/session/codes.js +40 -0
  343. package/dist/session/derive.js +180 -0
  344. package/dist/session/event-data.js +1 -0
  345. package/dist/session/fork.js +72 -0
  346. package/dist/session/import-gates.js +148 -0
  347. package/dist/session/index.js +18 -0
  348. package/dist/session/recover.js +103 -0
  349. package/dist/session/session.js +279 -0
  350. package/dist/session/snapshot.js +107 -0
  351. package/dist/skills/agents.js +242 -0
  352. package/dist/skills/codes.js +51 -0
  353. package/dist/skills/discovery.js +266 -0
  354. package/dist/skills/frontmatter.js +215 -0
  355. package/dist/skills/index.js +28 -0
  356. package/dist/skills/load.js +118 -0
  357. package/dist/skills/manage.js +201 -0
  358. package/dist/skills/registry.js +138 -0
  359. package/dist/skills/render.js +124 -0
  360. package/dist/skills/sections.js +171 -0
  361. package/dist/skills/types.js +21 -0
  362. package/dist/subagent/codes.js +50 -0
  363. package/dist/subagent/declarative.js +55 -0
  364. package/dist/subagent/index.js +19 -0
  365. package/dist/subagent/notify.js +51 -0
  366. package/dist/subagent/provide.js +7 -0
  367. package/dist/subagent/registry.js +149 -0
  368. package/dist/subagent/service.js +338 -0
  369. package/dist/subagent/surface.js +44 -0
  370. package/dist/subagent/tool.js +153 -0
  371. package/dist/subagent/types.js +25 -0
  372. package/dist/tools/apply-patch.js +164 -0
  373. package/dist/tools/codes.js +109 -0
  374. package/dist/tools/fs.js +501 -0
  375. package/dist/tools/index.js +17 -0
  376. package/dist/tools/observed.js +99 -0
  377. package/dist/tools/pipeline.js +176 -0
  378. package/dist/tools/protected-read.js +54 -0
  379. package/dist/tools/registry.js +250 -0
  380. package/dist/tools/search.js +451 -0
  381. package/dist/tools/worktree.js +327 -0
  382. package/dist/web/codes.js +37 -0
  383. package/dist/web/gate.js +43 -0
  384. package/dist/web/hygiene.js +195 -0
  385. package/dist/web/index.js +16 -0
  386. package/dist/web/service.js +207 -0
  387. package/dist/web/ssrf-guard.js +44 -0
  388. package/dist/web/tool.js +76 -0
  389. package/dist/web/types.js +10 -0
  390. package/dist/webui/assets/index-B-t2O8gh.js +22 -0
  391. package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
  392. package/dist/webui/index.html +14 -0
  393. package/dist/webui/index.js +14 -0
  394. package/dist/webui/server.js +549 -0
  395. package/dist/webui/types.js +38 -0
  396. package/examples/README.md +26 -0
  397. package/examples/minimal-code-plugin/entry.js +43 -0
  398. package/examples/minimal-code-plugin/package.json +12 -0
  399. package/examples/pure-skill-pack/package.json +14 -0
  400. package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
  401. package/package.json +86 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * 声明式子代理解析层(06 §11.6——解析层住 core:skills、机器住 core:subagent)。
3
+ *
4
+ * agents/*.md = frontmatter(name/description/tools/requires/skills/model)+ 正文即
5
+ * 系统提示。发现位置镜像技能位 1-4:project `.agents/agents`(需目录信任)>
6
+ * user `<dataDir>/agents` > 跨库 `~/.agents/agents` 与 `~/.claude/agents` >
7
+ * 插件声明载荷;位 5 CLI/动态不纳入、位 6 无出厂位。层序即信任序,
8
+ * first-wins 同名压制。坏文件 warning 跳过不炸装配(同技能纪律)。
9
+ *
10
+ * 本件只产纯数据 def(SubagentDef——contracts 归位);yaml 解析复用技能
11
+ * frontmatter 机器(yaml 裸导入白名单不扩——subagent 收 def 零 yaml)。
12
+ */
13
+ import { readdir, readFile } from 'node:fs/promises';
14
+ import { existsSync } from 'node:fs';
15
+ import { realpathSync } from 'node:fs';
16
+ import { basename, join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { canonicalWorkspaceRoot } from '../context/index.js';
19
+ import { parseSkillFrontmatter, validateSkillName, validateSkillRefList } from './frontmatter.js';
20
+ import { SKILL_DESCRIPTION_MAX } from './types.js';
21
+ /**
22
+ * 从 agents/*.md 全文解析声明式子代理 def。
23
+ *
24
+ * 处置逐条(06 §11.6 + §11.2 技能纪律镜像):
25
+ * - frontmatter 坏形(无开闭 --- 等)→ 拒;
26
+ * - description 非字符串或空白 → 拒(唯一硬条件——披露段清单行的唯一依据);
27
+ * - description 超 1024 → 截断装载(清单行帽同技能律);
28
+ * - name 缺席 → 回落文件基名(去 .md);提供时须与基名一致(身份键纪律:
29
+ * 静态工具名 agent_<name> 与文件名漂移即双名混乱)且过技能名词法
30
+ * (物化为工具名的词法安全面)——违例拒(name 是注册键不宽容);
31
+ * - tools/requires/skills 非字符串数组 → 拒;model 非字符串 → 拒;
32
+ * skills 项数帽 8/项词法/重复项拒(06 §11.6 形状护栏——存在性归工厂复验);
33
+ * - 正文空(trim 后)→ 拒(正文即系统提示——空正文无装载面);
34
+ * - 未采用字段(license 等生态超集)→ 静默忽略(生态宽容——CC 形可装载)。
35
+ */
36
+ export function parseAgentDef(raw, context) {
37
+ const parsed = parseSkillFrontmatter(raw);
38
+ if ('error' in parsed) {
39
+ return { error: parsed.error };
40
+ }
41
+ const frontmatter = parsed.frontmatter;
42
+ // description:必填(模型选择依据——声明式工具 description 直传此值)
43
+ const rawDescription = frontmatter['description'];
44
+ if (typeof rawDescription !== 'string' || rawDescription.trim() === '') {
45
+ return { error: 'description 缺席或为空(必填——披露段清单行的唯一依据)' };
46
+ }
47
+ const description = rawDescription.length > SKILL_DESCRIPTION_MAX ? rawDescription.slice(0, SKILL_DESCRIPTION_MAX) : rawDescription;
48
+ // name:缺席回落文件基名(去扩展名);提供须一致 + 技能名词法(工具名安全面)
49
+ const stem = basename(context.filePath).replace(/\.md$/i, '');
50
+ const rawName = frontmatter['name'];
51
+ const name = typeof rawName === 'string' && rawName !== '' ? rawName : stem;
52
+ if (name !== stem) {
53
+ return { error: `name(${name})与文件基名(${stem})不一致——agents 文件约定 = 同名文件承载同名子代理` };
54
+ }
55
+ const nameErrors = validateSkillName(name);
56
+ if (nameErrors.length > 0) {
57
+ return { error: nameErrors.join(';') };
58
+ }
59
+ // tools/requires/skills:可选字符串数组(白名单/前置要求/技能注入三键正交——语义执法在机器侧)
60
+ const tools = parseStringList(frontmatter['tools'], 'tools');
61
+ if ('error' in tools)
62
+ return { error: tools.error };
63
+ const requires = parseStringList(frontmatter['requires'], 'requires');
64
+ if ('error' in requires)
65
+ return { error: requires.error };
66
+ // skills 键形状三验在解析层(帽 8/技能名词法/重复拒——06 §11.6 声明面低宽容);
67
+ // 存在性不在此查(装载序晚于装配期物化——存在性归工厂 spawn 时点复验)
68
+ const skills = parseStringList(frontmatter['skills'], 'skills');
69
+ if ('error' in skills)
70
+ return { error: skills.error };
71
+ const skillRefErrors = skills.value !== undefined ? validateSkillRefList(skills.value) : [];
72
+ if (skillRefErrors.length > 0) {
73
+ return { error: skillRefErrors.join(';') };
74
+ }
75
+ // model:可选字符串(模型覆盖——启动参数直传工厂)
76
+ const rawModel = frontmatter['model'];
77
+ if (rawModel !== undefined && typeof rawModel !== 'string') {
78
+ return { error: 'model 须为字符串(模型覆盖名)' };
79
+ }
80
+ // 正文即系统提示(保留原始缩进——frontmatter 闭合 --- 之后全文)
81
+ if (parsed.body.trim() === '') {
82
+ return { error: '正文空(正文即系统提示——空正文无装载面)' };
83
+ }
84
+ return {
85
+ name,
86
+ description,
87
+ ...(tools.value !== undefined ? { tools: tools.value } : {}),
88
+ ...(requires.value !== undefined ? { requires: requires.value } : {}),
89
+ ...(skills.value !== undefined ? { skills: skills.value } : {}),
90
+ ...(rawModel !== undefined ? { model: rawModel } : {}),
91
+ systemPrompt: parsed.body,
92
+ filePath: context.filePath,
93
+ };
94
+ }
95
+ /** 字符串数组字段解析(undefined 放行;非数组/含非字符串 → 拒) */
96
+ function parseStringList(value, field) {
97
+ if (value === undefined)
98
+ return {};
99
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
100
+ return { error: `${field} 须为字符串数组` };
101
+ }
102
+ return { value: value };
103
+ }
104
+ /**
105
+ * 扁平目录层 provider 工厂:每根目录直扫 `*.md`(不递归——agents 是平铺
106
+ * 单文件形,非技能目录树形;无 gitignore 机器——树形剪枝纪律不适用平铺面)。
107
+ * trusted=false → 空扫描 + 单诊断(镜像技能层语义:不入清单即零注入面)。
108
+ */
109
+ export function createAgentLayerProvider(options) {
110
+ return {
111
+ id: options.id,
112
+ roots: options.roots,
113
+ ...(options.trusted !== undefined ? { trusted: options.trusted } : {}),
114
+ scan: async () => {
115
+ const defs = [];
116
+ const diagnostics = [];
117
+ if (options.trusted === false) {
118
+ return {
119
+ defs,
120
+ diagnostics: [
121
+ {
122
+ type: 'warning',
123
+ message: 'project 子代理目录未获目录信任锚——整层跳过(04 目录信任条:装载不激活)',
124
+ path: options.roots[0],
125
+ },
126
+ ],
127
+ };
128
+ }
129
+ const seen = options.seenRealPaths ?? new Set();
130
+ const seenNames = new Set(); // 同层同名(跨文件声明 name 撞)——后到跳过带诊断
131
+ for (const root of options.roots) {
132
+ if (!existsSync(root))
133
+ continue;
134
+ let entries;
135
+ try {
136
+ entries = await readdir(root, { withFileTypes: true });
137
+ }
138
+ catch {
139
+ continue; // 读失败(权限/竞态消失)——跳过继续
140
+ }
141
+ // 文件名排序——同层装载序确定(readdir 序不保证)
142
+ const files = entries
143
+ .filter((entry) => entry.isFile() && /\.md$/i.test(entry.name))
144
+ .map((entry) => entry.name)
145
+ .sort();
146
+ for (const fileName of files) {
147
+ const filePath = join(root, fileName);
148
+ let realPath;
149
+ try {
150
+ realPath = realpathSync(filePath);
151
+ }
152
+ catch {
153
+ continue; // 断链符号链——跳过
154
+ }
155
+ if (seen.has(realPath))
156
+ continue; // 同文件双发现去重(静默——跨层 symlink 别名)
157
+ let raw;
158
+ try {
159
+ raw = await readFile(realPath, 'utf8');
160
+ }
161
+ catch (error) {
162
+ diagnostics.push({
163
+ type: 'warning',
164
+ message: `agents 文件读取失败:${error instanceof Error ? error.message : String(error)}`,
165
+ path: filePath,
166
+ });
167
+ continue;
168
+ }
169
+ seen.add(realPath);
170
+ const parsed = parseAgentDef(raw, { filePath });
171
+ if ('error' in parsed) {
172
+ // 坏文件 warning 跳过不炸装配(06 §11.6——诊断语义同技能坏文件)
173
+ diagnostics.push({ type: 'invalid-metadata', message: parsed.error, path: filePath });
174
+ continue;
175
+ }
176
+ if (seenNames.has(parsed.name)) {
177
+ diagnostics.push({
178
+ type: 'collision',
179
+ message: `同层子代理撞名「${parsed.name}」——后到文件跳过(first-wins)`,
180
+ path: filePath,
181
+ });
182
+ continue;
183
+ }
184
+ seenNames.add(parsed.name);
185
+ defs.push(parsed);
186
+ }
187
+ }
188
+ return { defs, diagnostics };
189
+ },
190
+ };
191
+ }
192
+ /**
193
+ * 标准发现层(06 §11.6 位 1-4 镜像技能位):project > user > 跨库 > 插件。
194
+ * 位 5 CLI/动态不纳入、位 6 无出厂位(与技能差异两处——无 skill_manage
195
+ * 写面故无 writable 标记、无宿主出厂 agents)。
196
+ */
197
+ export function createStandardAgentLayers(options) {
198
+ const home = options.homeDir ?? homedir();
199
+ const seen = options.seenRealPaths ?? new Set();
200
+ return [
201
+ // 位 1:project `.agents/agents`(canonical 工作区根锚——需目录信任)
202
+ createAgentLayerProvider({
203
+ id: 'project',
204
+ roots: [join(canonicalWorkspaceRoot(options.cwd ?? process.cwd()), '.agents', 'agents')],
205
+ trusted: options.trustedProject ?? true,
206
+ seenRealPaths: seen,
207
+ }),
208
+ // 位 2:user `<dataDir>/agents`(无需信任——dataDir 缺席跳位,:memory: 形)
209
+ ...(options.dataDir !== undefined
210
+ ? [createAgentLayerProvider({ id: 'user', roots: [join(options.dataDir, 'agents')], seenRealPaths: seen })]
211
+ : []),
212
+ // 位 3:跨库 `~/.agents/agents` 与 `~/.claude/agents`(生态复用——CC 形可装载)
213
+ createAgentLayerProvider({
214
+ id: 'cross-repo',
215
+ roots: [join(home, '.agents', 'agents'), join(home, '.claude', 'agents')],
216
+ seenRealPaths: seen,
217
+ }),
218
+ // 位 4:插件声明载荷层(信任序低于主人位——first-wins 全序表达)
219
+ ...(options.pluginLayers ?? []).map((layer) => createAgentLayerProvider({ ...layer, seenRealPaths: seen })),
220
+ ];
221
+ }
222
+ /**
223
+ * 按层序合并(first-wins 同名压制——层序即信任序,project 恒压 user)。
224
+ * 后层同名静默压制(主人位既定,后者诊断只作装载日志不作冲突面——
225
+ * 与同层撞名的 collision 诊断分立)。
226
+ */
227
+ export async function collectAgentDefs(providers) {
228
+ const defs = [];
229
+ const diagnostics = [];
230
+ const seenNames = new Set();
231
+ for (const provider of providers) {
232
+ const scan = await provider.scan();
233
+ diagnostics.push(...scan.diagnostics);
234
+ for (const def of scan.defs) {
235
+ if (seenNames.has(def.name))
236
+ continue; // 跨层同名——先到主人位胜出(first-wins)
237
+ seenNames.add(def.name);
238
+ defs.push(def);
239
+ }
240
+ }
241
+ return { defs, diagnostics };
242
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * skills 域错误码注册(06 篇 §11 技能机制 + §12.1 skill_manage——SKILLS_ 前缀族)。
3
+ *
4
+ * 前缀族在 contracts/errors.ts ERROR_CODE_PREFIXES 已注册(02 §5.3 #1);八码全为
5
+ * 落码批定名(规范具名面只有机制语义无码名——06 §11.2 校验族 / §12.1 create 亮拒·
6
+ * patch 三拒 / provider 链装配期撞名)。本文件由模块公开面 index.ts 引入
7
+ * (注册纪律:import 发生才注册)。
8
+ */
9
+ import { registerErrorCodes } from '../contracts/index.js';
10
+ registerErrorCodes([
11
+ {
12
+ code: 'SKILLS_NAME_INVALID',
13
+ module: 'skills',
14
+ description: '技能名词法/长度违例(≤64、^[a-z0-9-]+$、首尾与连续连字符禁——skill_manage create 载荷校验面;装载面同规则仅警告不拒)',
15
+ },
16
+ {
17
+ code: 'SKILLS_NAME_EXISTS',
18
+ module: 'skills',
19
+ description: 'skill_manage create 同名亮拒不覆写——判据 = 注册表在册名 ∪ 盘上文件在场(坏 frontmatter 文件对 get() 隐身,只查在册名会静默毁文件,06 §12.1)',
20
+ },
21
+ {
22
+ code: 'SKILLS_CONTENT_INVALID',
23
+ module: 'skills',
24
+ description: 'skill_manage create 载荷校验拒——description 非空且 ≤1024 / 正文非空(06 §12.1)',
25
+ },
26
+ {
27
+ code: 'SKILLS_NOT_FOUND',
28
+ module: 'skills',
29
+ description: '注册表查无该名——skill_manage patch 目标不在册(先刷新后改;盘上坏文件不入册者同报,修坏文件走人面)与 load_skill name 未命中(指路 <available_skills> 清单)两消费面(06 §12.1/§11.5)',
30
+ },
31
+ {
32
+ code: 'SKILLS_LAYER_READONLY',
33
+ module: 'skills',
34
+ description: 'skill_manage patch 仅 project 层可改——user/package 层拒并指路人面(06 §12.1 patch 域条款)',
35
+ },
36
+ {
37
+ code: 'SKILLS_PATCH_MATCH_INVALID',
38
+ module: 'skills',
39
+ description: 'skill_manage patch find 串零匹配或多匹配均拒(不猜改点——与节级寻址歧义报错不猜同律,06 §11.3/§12.1)',
40
+ },
41
+ {
42
+ code: 'SKILLS_WRITE_ROOT_DENIED',
43
+ module: 'skills',
44
+ description: 'skill_manage create/patch 写点前置可写根断言拒——物理写(含 mkdir)前走装配注入的可写根推导,read-only 档空根即拒(06 §12.1;绕过 fence 的直写即缺陷)',
45
+ },
46
+ {
47
+ code: 'SKILLS_PROVIDER_CONFLICT',
48
+ module: 'skills',
49
+ description: '技能 provider 撞名拒——同 id 两方注册属装配期错误 fail-loud(03 skills_change 载荷 = provider id 清单,id 即身份)',
50
+ },
51
+ ]);
@@ -0,0 +1,266 @@
1
+ /**
2
+ * 技能发现层(06 §11.4 发现位置与优先级——六位序列的位 1/2/3/4/6 落码形态;
3
+ * 位 5 CLI/动态注入不纳入 v1)。
4
+ *
5
+ * 发现规则(pi 蓝本同律):目录含 SKILL.md 即为技能根不再递归;否则递归子目录。
6
+ * 尊重 .gitignore——前缀化锚定判据与检索族(tools/search.ts)/快照遍历三副本
7
+ * 同笔同判(06 §11.4 括注:模式体去尾随目录标记斜杠后含斜杠或带前导 / = 锚定
8
+ * 本层精确匹配;纯 basename 模式前缀化插目录通配前缀(`**`+`/`)保本层与
9
+ * symlink 目录跟随(realpath 去重防环——与 pi 同律;检索族防环不跟随是遍历
10
+ * 面差异:技能面文件量小且 realpath 集合天然断环)。
11
+ *
12
+ * 信任语义(04 目录信任条):project 层需目录信任锚——trusted=false 时整层跳过
13
+ * 扫描并落诊断(装载不激活的 v1 实现:不入清单即零注入面);user/跨库/出厂层
14
+ * 无需信任(出厂 = 与官方插件同源分发恒扫描)。
15
+ */
16
+ import { readdir, readFile, stat } from 'node:fs/promises';
17
+ import { existsSync, realpathSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { dirname, join, relative, resolve, sep } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import ignore from 'ignore';
22
+ import { canonicalWorkspaceRoot } from '../context/index.js';
23
+ import { loadSkillFromText } from './frontmatter.js';
24
+ /** 遍历常量剪枝(与检索族同表语义:依赖目录与 git 元数据) */
25
+ const PRUNE_DIRS = new Set(['node_modules', '.git']);
26
+ /** 平台路径分隔符归一为 /(gitignore 模式语义在 posix 路径上) */
27
+ function toPosix(p) {
28
+ return p.split(sep).join('/');
29
+ }
30
+ /**
31
+ * 单行 gitignore 模式前缀化(06 §11.4 锚定判据——与检索族/快照遍历三副本同笔
32
+ * 同判;实现与 tools/search.ts 逐分支对齐)。注释/空行丢弃;`!` 否定与 `\!`/
33
+ * `\#` 转义保留;纯 basename 模式前缀化插目录通配前缀(`**`+`/`)保深层同配。
34
+ */
35
+ function prefixIgnorePattern(line, prefix) {
36
+ const trimmed = line.trim();
37
+ if (!trimmed)
38
+ return null;
39
+ if (trimmed.startsWith('#') && !trimmed.startsWith('\\#'))
40
+ return null;
41
+ let pattern = line;
42
+ let negated = false;
43
+ if (pattern.startsWith('!')) {
44
+ negated = true;
45
+ pattern = pattern.slice(1);
46
+ }
47
+ else if (pattern.startsWith('\\!')) {
48
+ pattern = pattern.slice(1);
49
+ }
50
+ // 前导 / = 显式锚定标记(先记下再去掉;锚定形相对本目录精确匹配)
51
+ const rooted = pattern.startsWith('/');
52
+ if (rooted)
53
+ pattern = pattern.slice(1);
54
+ if (!prefix) {
55
+ // 根 .gitignore 锚定形保留前导 /(git 语义:前导分隔符 = 锚定本层——剥掉
56
+ // 透传会把锚定降级成任意层匹配;检索族同笔同判同修)
57
+ const passed = rooted ? `/${pattern}` : pattern;
58
+ return negated ? `!${passed}` : passed;
59
+ }
60
+ const body = pattern.endsWith('/') ? pattern.slice(0, -1) : pattern;
61
+ const anchored = rooted || body.includes('/');
62
+ const prefixed = anchored ? `${prefix}${pattern}` : `${prefix}**/${pattern}`;
63
+ return negated ? `!${prefixed}` : prefixed;
64
+ }
65
+ /** 读 dir 下 .gitignore 并按所在目录前缀化挂上匹配器(嵌套规则只作用本子树) */
66
+ async function addIgnoreRules(matcher, dir, rootDir) {
67
+ let content;
68
+ try {
69
+ content = await readFile(join(dir, '.gitignore'), 'utf8');
70
+ }
71
+ catch {
72
+ return; // 无 .gitignore 或读失败——本目录无额外规则
73
+ }
74
+ const prefix = toPosix(relative(rootDir, dir));
75
+ const prefixed = prefix ? `${prefix}/` : '';
76
+ const patterns = content
77
+ .split(/\r?\n/)
78
+ .map((line) => prefixIgnorePattern(line, prefixed))
79
+ .filter((line) => line !== null);
80
+ if (patterns.length > 0)
81
+ matcher.add(patterns);
82
+ }
83
+ /**
84
+ * 扫描一个技能层根目录(06 §11.4「目录含 SKILL.md 即为技能根不再递归」)。
85
+ *
86
+ * 缺席目录 = 空结果不诊断(跨库目录 ~/.claude/skills 等常态缺席)。
87
+ * realpath 去重集由调用方传入(跨根共享——同文件经符号链双发现的去重锚)。
88
+ */
89
+ export async function scanSkillsDir(dir, options = { providerId: 'unknown' }) {
90
+ const seen = options.seenRealPaths ?? new Set();
91
+ const result = await scanDirInternal(resolve(dir), resolve(dir), options.providerId, ignore(), seen);
92
+ return result;
93
+ }
94
+ /** 实际递归(每层目录同一规则:本目录含 SKILL.md 即技能根不再下钻;否则递归子目录) */
95
+ async function scanDirInternal(dir, root, providerId, matcher, seen) {
96
+ const skills = [];
97
+ const diagnostics = [];
98
+ if (!existsSync(dir))
99
+ return { skills, diagnostics };
100
+ await addIgnoreRules(matcher, dir, root);
101
+ let entries;
102
+ try {
103
+ entries = await readdir(dir, { withFileTypes: true });
104
+ }
105
+ catch {
106
+ return { skills, diagnostics }; // 读失败(权限/竞态消失)——跳过继续
107
+ }
108
+ // 第一遍:本目录含 SKILL.md 即技能根(不递归)——发现即返回
109
+ const skillMd = entries.find((e) => e.name === 'SKILL.md');
110
+ if (skillMd !== undefined) {
111
+ const filePath = join(dir, 'SKILL.md');
112
+ if (!ignoredPath(matcher, root, filePath)) {
113
+ const loaded = await loadSkillFile(filePath, providerId, seen);
114
+ skills.push(...loaded.skills);
115
+ diagnostics.push(...loaded.diagnostics);
116
+ }
117
+ return { skills, diagnostics };
118
+ }
119
+ // 第二遍:递归子目录(点目录与依赖目录剪枝;符号链目录跟随——realpath 断环)
120
+ for (const entry of entries) {
121
+ if (entry.name.startsWith('.') || PRUNE_DIRS.has(entry.name))
122
+ continue;
123
+ const fullPath = join(dir, entry.name);
124
+ let isDirectory = entry.isDirectory();
125
+ if (entry.isSymbolicLink()) {
126
+ try {
127
+ isDirectory = (await stat(fullPath)).isDirectory();
128
+ }
129
+ catch {
130
+ continue; // 断链——跳过
131
+ }
132
+ }
133
+ if (!isDirectory)
134
+ continue; // 技能面只认 <名>/SKILL.md 目录形(散 .md 非技能)
135
+ if (ignoredPath(matcher, root, fullPath, true))
136
+ continue;
137
+ const sub = await scanDirInternal(fullPath, root, providerId, matcher, seen);
138
+ skills.push(...sub.skills);
139
+ diagnostics.push(...sub.diagnostics);
140
+ }
141
+ return { skills, diagnostics };
142
+ }
143
+ /** 路径 gitignore 判定(目录双测带/不带尾斜杠——兼容 `dir/` 与 `dir` 两种写法) */
144
+ function ignoredPath(matcher, root, fullPath, isDir = false) {
145
+ const rel = toPosix(relative(root, fullPath));
146
+ if (rel === '')
147
+ return false; // 根自身不判
148
+ return isDir ? matcher.ignores(rel) || matcher.ignores(`${rel}/`) : matcher.ignores(rel);
149
+ }
150
+ /** 单 SKILL.md 装载(读失败/realpath 已见 → 跳过;校验诊断透传) */
151
+ async function loadSkillFile(filePath, providerId, seen) {
152
+ const diagnostics = [];
153
+ let realPath;
154
+ try {
155
+ realPath = realpathSync(filePath);
156
+ }
157
+ catch {
158
+ return { skills: [], diagnostics }; // 断链符号链——跳过
159
+ }
160
+ if (seen.has(realPath))
161
+ return { skills: [], diagnostics }; // 同文件双发现去重(静默)
162
+ let raw;
163
+ try {
164
+ raw = await readFile(realPath, 'utf8');
165
+ }
166
+ catch (error) {
167
+ diagnostics.push({
168
+ type: 'warning',
169
+ message: `SKILL.md 读取失败:${error instanceof Error ? error.message : String(error)}`,
170
+ path: filePath,
171
+ });
172
+ return { skills: [], diagnostics };
173
+ }
174
+ seen.add(realPath);
175
+ const loaded = loadSkillFromText(raw, { filePath, providerId });
176
+ return { skills: loaded.skill !== undefined ? [loaded.skill] : [], diagnostics: [...loaded.diagnostics] };
177
+ }
178
+ /**
179
+ * 目录扫描 provider 工厂(标准层与插件声明载荷层共用——03 §6.1 mount 即注册)。
180
+ *
181
+ * trusted=false → 空扫描 + 单诊断(装载不激活:不入清单即零注入面——信任授予
182
+ * 是用户动作,模型不可自授)。
183
+ */
184
+ export function createDirProvider(options) {
185
+ return {
186
+ id: options.id,
187
+ roots: options.roots,
188
+ ...(options.writable !== undefined ? { writable: options.writable } : {}),
189
+ ...(options.trusted !== undefined ? { trusted: options.trusted } : {}),
190
+ // passSeen = 本轮扫描 pass 的去重集(registry.refresh 铸新集透传——层间
191
+ // 同文件去重;缺省独立集 = provider 自身多根跨根去重)。不持有跨 refresh
192
+ // 持久集(曾致二次 refresh 起 realpath 全「已见」整册静默清空)
193
+ scan: async (passSeen) => {
194
+ if (options.trusted === false) {
195
+ return {
196
+ skills: [],
197
+ diagnostics: [
198
+ {
199
+ type: 'warning',
200
+ message: 'project 技能目录未获目录信任锚——整层跳过(04 目录信任条:装载不激活)',
201
+ path: options.roots[0],
202
+ },
203
+ ],
204
+ };
205
+ }
206
+ const skills = [];
207
+ const diagnostics = [];
208
+ const seen = passSeen ?? new Set();
209
+ for (const root of options.roots) {
210
+ const scanned = await scanSkillsDir(root, { providerId: options.id, seenRealPaths: seen });
211
+ skills.push(...scanned.skills);
212
+ diagnostics.push(...scanned.diagnostics);
213
+ }
214
+ return { skills, diagnostics };
215
+ },
216
+ };
217
+ }
218
+ /**
219
+ * 出厂技能目录定位(06 §11.4 位 6:`<包根>/skills/`——包根 = 发现模块位置上推
220
+ * 两级;源码 src/skills/* 与产物 dist/skills/* 双形态同构,上推两级皆包根)。
221
+ */
222
+ export function resolveFactorySkillsDir(moduleUrl = import.meta.url) {
223
+ // dist/skills/discovery.js → dirname 两次 = 包根(src 形同理)
224
+ const moduleDir = dirname(fileURLToPath(moduleUrl));
225
+ return resolve(moduleDir, '..', '..', 'skills');
226
+ }
227
+ /**
228
+ * 构造六位序列标准层(注册序即优先序——project > user > 跨库 > 插件 > 出厂;
229
+ * provider 注册序即优先序 06 §11.3 护栏②,同名 first-wins 由扫描序表达)。
230
+ *
231
+ * realpath 去重集 pass 域律:各层不持有跨 refresh 持久去重集——层间同文件
232
+ * 去重由注册表 refresh 时铸新集经 scan(passSeen) 透传(曾因共享持久集致
233
+ * 第二次 refresh 起 realpath 全「已见」整册静默清空——装配尾 resync 与
234
+ * skill_manage 写后刷新共用径全灭,回归锁在 discovery.test.ts)。
235
+ */
236
+ export function createStandardLayers(options) {
237
+ const home = options.homeDir ?? homedir();
238
+ const layers = [
239
+ // 位 1:project `.agents/skills`(canonical 工作区根锚——与 memory owner_key/
240
+ // project 域键同源一处实现;需目录信任、可改写)
241
+ createDirProvider({
242
+ id: 'project',
243
+ roots: [join(canonicalWorkspaceRoot(options.cwd ?? process.cwd()), '.agents', 'skills')],
244
+ writable: true,
245
+ trusted: options.trustedProject ?? true,
246
+ }),
247
+ // 位 2:user `<dataDir>/skills`(无需信任)——缺席跳过(:memory: 形无
248
+ // 用户技能面,不造空根目录)
249
+ ...(options.dataDir !== undefined
250
+ ? [createDirProvider({ id: 'user', roots: [join(options.dataDir, 'skills')] })]
251
+ : []),
252
+ // 位 3:跨库 `~/.agents/skills` 与 `~/.claude/skills`(agentskills.io 生态复用)
253
+ createDirProvider({
254
+ id: 'cross-repo',
255
+ roots: [join(home, '.agents', 'skills'), join(home, '.claude', 'skills')],
256
+ }),
257
+ // 位 4:插件声明载荷层(信任序低于主人位——first-wins 全序表达)
258
+ ...(options.pluginLayers ?? []).map((layer) => createDirProvider({ ...layer })),
259
+ // 位 6:宿主出厂 `<包根>/skills/`(恒扫描、末位——用户/project 同名恒压出厂件)
260
+ createDirProvider({
261
+ id: 'factory',
262
+ roots: [options.factoryDir ?? resolveFactorySkillsDir()],
263
+ }),
264
+ ];
265
+ return layers;
266
+ }