@wrongstack/core 0.313.0 → 0.316.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 (343) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/legacy-journal-import.d.ts +0 -6
  3. package/dist/chronicle/metrics-ingest.d.ts +14 -0
  4. package/dist/chronicle/metrics-schema.d.ts +10 -1
  5. package/dist/chronicle/metrics-store.d.ts +20 -2
  6. package/dist/chronicle/project-server.js +98 -4874
  7. package/dist/chronicle/sink.d.ts +13 -0
  8. package/dist/chronicle/sqlite-journal.d.ts +20 -2
  9. package/dist/chunk-25LSLC3G.js +334 -0
  10. package/dist/chunk-27QIURLN.js +1073 -0
  11. package/dist/chunk-2FK4WTFF.js +207 -0
  12. package/dist/chunk-2YU4WVBW.js +134 -0
  13. package/dist/chunk-3AGWVSS7.js +4608 -0
  14. package/dist/chunk-3BDV7RL3.js +211 -0
  15. package/dist/chunk-3ECENVHL.js +325 -0
  16. package/dist/chunk-3XPVB7BA.js +135 -0
  17. package/dist/chunk-45WI72ZB.js +16884 -0
  18. package/dist/chunk-4BMVPOHO.js +3748 -0
  19. package/dist/chunk-4I7HGVVA.js +363 -0
  20. package/dist/chunk-4LCZVJVF.js +317 -0
  21. package/dist/chunk-4SJNS7OB.js +217 -0
  22. package/dist/chunk-4VWO3ZXL.js +342 -0
  23. package/dist/chunk-57GEWK5X.js +26 -0
  24. package/dist/chunk-5BMIE7QG.js +46 -0
  25. package/dist/chunk-5S437PRC.js +50 -0
  26. package/dist/chunk-5XDUGTKN.js +511 -0
  27. package/dist/chunk-5YPSXQXM.js +1443 -0
  28. package/dist/chunk-6FHWXLPT.js +10 -0
  29. package/dist/chunk-6H5GEOET.js +2406 -0
  30. package/dist/chunk-6NWEVFEL.js +151 -0
  31. package/dist/chunk-AYW5YINX.js +58 -0
  32. package/dist/chunk-B4AMLZPB.js +9 -0
  33. package/dist/chunk-B7F4WVP4.js +65 -0
  34. package/dist/chunk-BJHVULHO.js +55 -0
  35. package/dist/chunk-BPBKKBUK.js +2085 -0
  36. package/dist/chunk-BYD345OX.js +589 -0
  37. package/dist/chunk-C27KDLOX.js +279 -0
  38. package/dist/chunk-CU546ESW.js +597 -0
  39. package/dist/chunk-DLHHXUAM.js +172 -0
  40. package/dist/chunk-DOTHQSJ2.js +429 -0
  41. package/dist/chunk-DYLPMV4Q.js +711 -0
  42. package/dist/chunk-EODDVJBM.js +82 -0
  43. package/dist/chunk-F3JWNST7.js +361 -0
  44. package/dist/chunk-FC2ZGRF3.js +140 -0
  45. package/dist/chunk-FCBTO76Z.js +5786 -0
  46. package/dist/chunk-FHZVFYSL.js +396 -0
  47. package/dist/chunk-FNXVT7HF.js +52 -0
  48. package/dist/chunk-FWNLEZYG.js +259 -0
  49. package/dist/chunk-FXQLIX3K.js +211 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GDQ7CZRQ.js +116 -0
  52. package/dist/chunk-GJJGJB2R.js +1374 -0
  53. package/dist/chunk-GTCU6DZ3.js +1 -0
  54. package/dist/chunk-HA4ZYA6T.js +178 -0
  55. package/dist/chunk-HI3JGZR2.js +188 -0
  56. package/dist/chunk-HMLIGAU7.js +395 -0
  57. package/dist/chunk-HXKZ7OBQ.js +303 -0
  58. package/dist/chunk-I7DALG7J.js +292 -0
  59. package/dist/chunk-IRUTEHCE.js +960 -0
  60. package/dist/chunk-IYFBFX34.js +86 -0
  61. package/dist/chunk-J2WRWR37.js +1104 -0
  62. package/dist/chunk-JFPJ6LLQ.js +129 -0
  63. package/dist/chunk-JMULFFLN.js +9 -0
  64. package/dist/chunk-JO4MGB6Z.js +1264 -0
  65. package/dist/chunk-JVKB2TBL.js +48 -0
  66. package/dist/chunk-JX7VBZDK.js +31 -0
  67. package/dist/chunk-K6BXGITA.js +931 -0
  68. package/dist/chunk-KDQJCT2J.js +45 -0
  69. package/dist/chunk-KJ2ZTQSL.js +47 -0
  70. package/dist/chunk-KZMSZV32.js +187 -0
  71. package/dist/chunk-LA7CLDRW.js +129 -0
  72. package/dist/chunk-LHOA5DWA.js +28 -0
  73. package/dist/chunk-LSMMOS2Z.js +119 -0
  74. package/dist/chunk-LX5QWLBL.js +27 -0
  75. package/dist/chunk-LXWCMQNJ.js +789 -0
  76. package/dist/chunk-M27LIEKV.js +2829 -0
  77. package/dist/chunk-MJPHJRAL.js +663 -0
  78. package/dist/chunk-MTL4O5YR.js +228 -0
  79. package/dist/chunk-NDBIPFNY.js +102 -0
  80. package/dist/chunk-NEMIWEXC.js +1199 -0
  81. package/dist/chunk-NHJ75HXY.js +1 -0
  82. package/dist/chunk-NIOAGFPB.js +1510 -0
  83. package/dist/chunk-NS52ISWS.js +355 -0
  84. package/dist/chunk-NSOFPKGT.js +5352 -0
  85. package/dist/chunk-NVIZG63R.js +757 -0
  86. package/dist/chunk-O37BQ65S.js +21 -0
  87. package/dist/chunk-OOYZPOXM.js +308 -0
  88. package/dist/chunk-OSGLYW5J.js +28 -0
  89. package/dist/chunk-PFCMSSUF.js +1640 -0
  90. package/dist/chunk-PFZO3C6H.js +14 -0
  91. package/dist/chunk-PGRVXIMY.js +158 -0
  92. package/dist/chunk-PHH7LD45.js +113 -0
  93. package/dist/chunk-PISBFBJN.js +109 -0
  94. package/dist/chunk-POOOIWE5.js +9 -0
  95. package/dist/chunk-PQNS3U7Z.js +1240 -0
  96. package/dist/chunk-QSTSJOCK.js +402 -0
  97. package/dist/chunk-RFQMOKWK.js +282 -0
  98. package/dist/chunk-RJIZEJ42.js +30 -0
  99. package/dist/chunk-RTOTNK3P.js +484 -0
  100. package/dist/chunk-RXKSDUAU.js +1172 -0
  101. package/dist/chunk-S45JPKQN.js +3317 -0
  102. package/dist/chunk-SAFHACFA.js +152 -0
  103. package/dist/chunk-SQJVFEBV.js +41 -0
  104. package/dist/chunk-SVT4RNTR.js +49 -0
  105. package/dist/chunk-SWUFN7XM.js +43 -0
  106. package/dist/chunk-SXMVYC5H.js +174 -0
  107. package/dist/chunk-SZ6AYUIZ.js +23 -0
  108. package/dist/chunk-TBRL2RCC.js +9 -0
  109. package/dist/chunk-TCCHINEF.js +655 -0
  110. package/dist/chunk-THEGTRAG.js +316 -0
  111. package/dist/chunk-TR4LQE7L.js +2256 -0
  112. package/dist/chunk-TRT2HRWZ.js +18 -0
  113. package/dist/chunk-TWJMGXCI.js +900 -0
  114. package/dist/chunk-U45AXT5D.js +1023 -0
  115. package/dist/chunk-U4FWBSO3.js +509 -0
  116. package/dist/chunk-UBGDX6FI.js +147 -0
  117. package/dist/chunk-UJILKYES.js +75 -0
  118. package/dist/chunk-ULMAQV3T.js +234 -0
  119. package/dist/chunk-VVBP6KGT.js +9 -0
  120. package/dist/chunk-W5VITDM2.js +168 -0
  121. package/dist/chunk-WAHQVNWT.js +194 -0
  122. package/dist/chunk-WBPVKEEE.js +227 -0
  123. package/dist/chunk-WGGYRBXH.js +5434 -0
  124. package/dist/chunk-WLYKU45P.js +5770 -0
  125. package/dist/chunk-WP7BVANJ.js +53 -0
  126. package/dist/chunk-WQQON3SV.js +473 -0
  127. package/dist/chunk-WQTOVMGV.js +119 -0
  128. package/dist/chunk-XBDP4QPW.js +7 -0
  129. package/dist/chunk-XEBHSJLT.js +103 -0
  130. package/dist/chunk-XF4VUMKW.js +231 -0
  131. package/dist/chunk-XQ2ECNK3.js +1 -0
  132. package/dist/chunk-Y5YXDM7D.js +74 -0
  133. package/dist/chunk-YOPAUVU4.js +2588 -0
  134. package/dist/chunk-YPT4A46F.js +362 -0
  135. package/dist/chunk-YWXT3JNM.js +425 -0
  136. package/dist/chunk-YY6APYUM.js +49 -0
  137. package/dist/chunk-Z2IJUO45.js +691 -0
  138. package/dist/chunk-ZHZ2Y2DH.js +1643 -0
  139. package/dist/chunk-ZT7JNIGX.js +152 -0
  140. package/dist/coordination/agent-monitor.d.ts +9 -1
  141. package/dist/coordination/agent-subagent-runner.d.ts +1 -1
  142. package/dist/coordination/agents/agent-prompts.d.ts +8 -0
  143. package/dist/coordination/agents/index.d.ts +33 -1
  144. package/dist/coordination/agents/index.js +184 -6269
  145. package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
  146. package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
  147. package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
  148. package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
  149. package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
  150. package/dist/coordination/agents/types.d.ts +13 -2
  151. package/dist/coordination/brain-monitor.d.ts +30 -7
  152. package/dist/coordination/collab-debug.d.ts +1 -1
  153. package/dist/coordination/collab-pause.d.ts +1 -1
  154. package/dist/coordination/delegate-tool.d.ts +7 -5
  155. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
  156. package/dist/coordination/director-options.d.ts +7 -0
  157. package/dist/coordination/director-spawn-model.d.ts +17 -0
  158. package/dist/coordination/director-tools.d.ts +2 -2
  159. package/dist/coordination/director.d.ts +12 -0
  160. package/dist/coordination/dispatcher.d.ts +8 -0
  161. package/dist/coordination/fleet-bus.d.ts +35 -4
  162. package/dist/coordination/fleet-supervisor.d.ts +10 -2
  163. package/dist/coordination/fleet.d.ts +16 -7
  164. package/dist/coordination/index.d.ts +4 -0
  165. package/dist/coordination/index.js +402 -34324
  166. package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
  167. package/dist/coordination/mailbox-http-router.d.ts +4 -3
  168. package/dist/coordination/mailbox-http-validation.d.ts +1 -1
  169. package/dist/coordination/mailbox-project-server.js +48 -3276
  170. package/dist/coordination/mailbox-types.d.ts +24 -6
  171. package/dist/coordination/model-tier-leader.d.ts +122 -0
  172. package/dist/coordination/model-tier.d.ts +122 -0
  173. package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
  174. package/dist/coordination/origin-session.d.ts +16 -0
  175. package/dist/coordination/provider-status-tracker.d.ts +41 -5
  176. package/dist/coordination/session-note-hub.d.ts +44 -0
  177. package/dist/coordination/session-note-tool.d.ts +10 -0
  178. package/dist/core/agent.d.ts +15 -2
  179. package/dist/core/context.d.ts +22 -3
  180. package/dist/core/index.d.ts +2 -0
  181. package/dist/core/index.js +239 -12360
  182. package/dist/core/model-ref.js +5 -23
  183. package/dist/core/provider-runner.d.ts +36 -1
  184. package/dist/core/request-conversation-binding.d.ts +37 -0
  185. package/dist/core/session-notes.d.ts +25 -0
  186. package/dist/core/system-prompt-builder.d.ts +11 -2
  187. package/dist/core/system-prompt-variants.d.ts +78 -0
  188. package/dist/design/index.js +50 -1087
  189. package/dist/execution/auto-compaction-middleware.d.ts +11 -0
  190. package/dist/execution/autonomy-brain.d.ts +2 -1
  191. package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
  192. package/dist/execution/compaction-core.d.ts +4 -4
  193. package/dist/execution/eternal-autonomy.d.ts +1 -1
  194. package/dist/execution/index.d.ts +1 -1
  195. package/dist/execution/index.js +1615 -21895
  196. package/dist/execution/model-runtime.d.ts +0 -7
  197. package/dist/execution/parallel-eternal-engine.d.ts +7 -7
  198. package/dist/execution/prompt-enhancer.js +16 -535
  199. package/dist/execution/retry-policy.d.ts +31 -1
  200. package/dist/extension/index.js +7 -311
  201. package/dist/goal/index.js +33 -3435
  202. package/dist/hooks/index.js +14 -1138
  203. package/dist/hq/index.js +114 -5139
  204. package/dist/hq/persistence/event-log.d.ts +0 -1
  205. package/dist/hq/persistence.d.ts +4 -4
  206. package/dist/hq/protocol.js +21 -668
  207. package/dist/hq/publisher.d.ts +2 -0
  208. package/dist/index.d.ts +14 -11
  209. package/dist/index.js +1502 -92655
  210. package/dist/infrastructure/index.d.ts +4 -4
  211. package/dist/infrastructure/index.js +504 -5230
  212. package/dist/infrastructure/token-counter.d.ts +9 -0
  213. package/dist/kernel/events/agent-events.d.ts +77 -3
  214. package/dist/kernel/events/brain-events.d.ts +12 -0
  215. package/dist/kernel/events/provider-events.d.ts +22 -1
  216. package/dist/kernel/events/session-events.d.ts +14 -0
  217. package/dist/kernel/events/tool-events.d.ts +13 -0
  218. package/dist/kernel/index.js +14 -992
  219. package/dist/mailbox-attach.d.ts +1 -1
  220. package/dist/models/index.js +41 -1117
  221. package/dist/notifications/index.js +3 -108
  222. package/dist/observability/event-bridge.d.ts +25 -5
  223. package/dist/observability/index.d.ts +9 -8
  224. package/dist/observability/index.js +477 -631
  225. package/dist/observability/tool-usage-source.d.ts +65 -0
  226. package/dist/plugin/index.d.ts +5 -0
  227. package/dist/plugin/index.js +600 -14835
  228. package/dist/plugins/auto-review-plugin.d.ts +3 -2
  229. package/dist/plugins/review-finding-commands.d.ts +0 -8
  230. package/dist/plugins/review-finding-store.js +22 -0
  231. package/dist/plugins/review-finding-types.js +19 -0
  232. package/dist/plugins/review-report-integration.js +21 -0
  233. package/dist/plugins/review-report-store.js +20 -0
  234. package/dist/plugins/review-report-types.js +13 -0
  235. package/dist/prompts/index.js +14 -948
  236. package/dist/registry/index.js +9 -822
  237. package/dist/registry/tool-registry.d.ts +59 -9
  238. package/dist/registry-7N4O3E7W.js +14 -0
  239. package/dist/replay/index.js +6 -84
  240. package/dist/security/capabilities.d.ts +6 -0
  241. package/dist/security/file-permissions.d.ts +1 -1
  242. package/dist/security/index.js +161 -2235
  243. package/dist/security/permission-policy.d.ts +12 -1
  244. package/dist/security/secret-scrubber.d.ts +15 -0
  245. package/dist/session-catalog/index.d.ts +1 -0
  246. package/dist/session-catalog/index.js +41 -2103
  247. package/dist/session-catalog/project-server.js +59 -1435
  248. package/dist/session-catalog/protocol.d.ts +32 -1
  249. package/dist/session-catalog/registry.d.ts +17 -1
  250. package/dist/session-catalog/session-agents.d.ts +56 -0
  251. package/dist/session-catalog/store-rebuild.d.ts +14 -0
  252. package/dist/session-catalog/store-schema.d.ts +26 -0
  253. package/dist/session-catalog/store.d.ts +27 -1
  254. package/dist/session-note-attach.d.ts +9 -0
  255. package/dist/skills/index.js +35 -1578
  256. package/dist/statusline/index.d.ts +43 -0
  257. package/dist/statusline/index.js +102 -0
  258. package/dist/storage/board-store-port.d.ts +2 -2
  259. package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
  260. package/dist/storage/config-loader.d.ts +3 -2
  261. package/dist/storage/file-session-writer.d.ts +4 -2
  262. package/dist/storage/goal-coordination.d.ts +6 -1
  263. package/dist/storage/goal-kanban.d.ts +4 -1
  264. package/dist/storage/index.d.ts +12 -4
  265. package/dist/storage/index.js +198 -15601
  266. package/dist/storage/input-history-store.d.ts +2 -4
  267. package/dist/storage/memory-curator.d.ts +133 -0
  268. package/dist/storage/session-agent-attribution.d.ts +39 -0
  269. package/dist/storage/session-doctor.d.ts +109 -0
  270. package/dist/storage/session-event-bridge.d.ts +5 -7
  271. package/dist/storage/session-outcome.d.ts +42 -0
  272. package/dist/storage/session-recovery.d.ts +55 -0
  273. package/dist/storage/session-store/load-session-data.d.ts +11 -0
  274. package/dist/storage/session-store/prune-helpers.d.ts +1 -1
  275. package/dist/storage/session-store/resume-session.d.ts +0 -2
  276. package/dist/storage/session-store/types.d.ts +8 -0
  277. package/dist/storage/session-store.d.ts +3 -3
  278. package/dist/storage/session-summary-tracker.d.ts +8 -1
  279. package/dist/storage/session-write-buffer.d.ts +18 -0
  280. package/dist/storage/session-writer-truncate.d.ts +2 -0
  281. package/dist/tasking/index.js +10 -676
  282. package/dist/tools/fallback-manage-tools.d.ts +6 -4
  283. package/dist/tools/index.js +52 -9590
  284. package/dist/tools/model-tier-set-tool.d.ts +66 -0
  285. package/dist/types/agent-bridge.d.ts +2 -1
  286. package/dist/types/config/autonomy.d.ts +5 -0
  287. package/dist/types/config/mcp-features.d.ts +7 -0
  288. package/dist/types/config/model-tiers.d.ts +148 -0
  289. package/dist/types/config/root.d.ts +9 -0
  290. package/dist/types/config/tools.d.ts +73 -0
  291. package/dist/types/config/ui.d.ts +1 -1
  292. package/dist/types/config.d.ts +1 -0
  293. package/dist/types/context.d.ts +2 -0
  294. package/dist/types/default-config.d.ts +6 -0
  295. package/dist/types/errors.d.ts +20 -1
  296. package/dist/types/index.d.ts +11 -9
  297. package/dist/types/index.js +131 -1342
  298. package/dist/types/multi-agent.d.ts +43 -0
  299. package/dist/types/plugin.d.ts +1 -1
  300. package/dist/types/provider-runner.d.ts +8 -0
  301. package/dist/types/provider.d.ts +15 -0
  302. package/dist/types/runtime-capability-manifest.d.ts +1 -1
  303. package/dist/types/secret-scrubber.d.ts +15 -0
  304. package/dist/types/session-markers.d.ts +37 -10
  305. package/dist/types/session-markers.js +8 -144
  306. package/dist/types/session-timeline.d.ts +197 -0
  307. package/dist/types/session-timeline.js +12 -0
  308. package/dist/types/session.d.ts +172 -11
  309. package/dist/types/system-prompt.d.ts +27 -0
  310. package/dist/utils/config-backup.d.ts +9 -1
  311. package/dist/utils/error.js +3 -4
  312. package/dist/utils/expect-defined.js +3 -9
  313. package/dist/utils/heap-watchdog.js +6 -706
  314. package/dist/utils/index.js +271 -5685
  315. package/dist/utils/message-invariants.d.ts +4 -1
  316. package/dist/utils/regex-guard.d.ts +1 -1
  317. package/dist/utils/sage-output-block.js +5 -42
  318. package/dist/utils/socket-path.d.ts +1 -1
  319. package/dist/utils/token-estimate.d.ts +6 -2
  320. package/dist/utils/wstack-paths.d.ts +2 -0
  321. package/dist/wiring/proxy-rewrite.d.ts +46 -0
  322. package/dist/wiring/proxy-rewrite.js +24 -121
  323. package/dist/worktree/index.js +6 -887
  324. package/instructions/agents/explore-companion.md +45 -18
  325. package/instructions/agents/memory-curator.md +34 -22
  326. package/instructions/coordination/director-preamble.md +12 -7
  327. package/instructions/coordination/subagent-baseline.md +26 -5
  328. package/instructions/leader-after-task.md +4 -1
  329. package/instructions/llm/chimera-review.md +30 -9
  330. package/instructions/llm/memory-curator.md +31 -0
  331. package/instructions/sections/tool/delegation-compact.md +2 -0
  332. package/instructions/sections/tool/delegation-full.md +5 -1
  333. package/instructions/sections/tool/mailbox-compact.md +5 -1
  334. package/instructions/sections/tool/mailbox-full.md +3 -0
  335. package/instructions/sections/tool/session-note-compact.md +3 -0
  336. package/instructions/sections/tool/session-note-full.md +15 -0
  337. package/instructions/system-lite.md +9 -4
  338. package/instructions/system-pro.md +66 -143
  339. package/instructions/system.md +73 -175
  340. package/package.json +32 -4
  341. package/dist/coordination/agents.d.ts +0 -8
  342. package/dist/coordination/transport.d.ts +0 -2
  343. package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
@@ -0,0 +1,1172 @@
1
+ import {
2
+ buildWin32CmdShimInvocation
3
+ } from "./chunk-LHOA5DWA.js";
4
+ import {
5
+ SKILL_LIMITS
6
+ } from "./chunk-XEBHSJLT.js";
7
+ import {
8
+ atomicWrite
9
+ } from "./chunk-OSGLYW5J.js";
10
+ import {
11
+ isValidSkillNameFormat,
12
+ parseSkillFrontmatter,
13
+ validateSkillName
14
+ } from "./chunk-FC2ZGRF3.js";
15
+ import {
16
+ expectDefined
17
+ } from "./chunk-PFZO3C6H.js";
18
+ import {
19
+ ERROR_CODES,
20
+ FetchError,
21
+ FsError,
22
+ ParseError,
23
+ WrongStackError
24
+ } from "./chunk-25LSLC3G.js";
25
+ import {
26
+ toErrorMessage
27
+ } from "./chunk-TBRL2RCC.js";
28
+
29
+ // src/skills/registry/github-direct-adapter.ts
30
+ var githubDirectAdapter = {
31
+ id: "github",
32
+ displayName: "GitHub (direct)",
33
+ async search(_query, _opts = {}) {
34
+ return { adapterId: "github", results: [], hasMore: false };
35
+ },
36
+ resolveInstallRef(registryId) {
37
+ const trimmed = registryId.trim();
38
+ if (!trimmed.includes("/")) {
39
+ return trimmed;
40
+ }
41
+ return trimmed;
42
+ }
43
+ };
44
+
45
+ // src/skills/registry/skills-sh-adapter.ts
46
+ var DEFAULT_SKILLS_SH_URL = "https://skills.sh";
47
+ var SEARCH_TIMEOUT_MS = 15e3;
48
+ var MAX_PAGE_SIZE = 50;
49
+ var defaultFetcher = async (url) => {
50
+ let res;
51
+ try {
52
+ res = await fetch(url, {
53
+ signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS),
54
+ headers: {
55
+ Accept: "application/json",
56
+ "User-Agent": "wrongstack-skill-installer"
57
+ },
58
+ redirect: "follow"
59
+ });
60
+ } catch (err) {
61
+ throw new FetchError({
62
+ message: `Network error querying skill registry: ${err instanceof Error ? err.message : String(err)}`,
63
+ status: 0,
64
+ context: { url, op: "skills.sh.search" },
65
+ cause: err
66
+ });
67
+ }
68
+ if (!res.ok) {
69
+ throw new FetchError({
70
+ message: `Skill registry returned ${res.status} ${res.statusText}`,
71
+ status: res.status,
72
+ context: { url, op: "skills.sh.search" }
73
+ });
74
+ }
75
+ try {
76
+ return await res.json();
77
+ } catch (err) {
78
+ throw new ParseError({
79
+ message: `Skill registry response was not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
80
+ source: "skills.sh.search",
81
+ context: { url },
82
+ cause: err
83
+ });
84
+ }
85
+ };
86
+ function createSkillsShAdapter(opts = {}) {
87
+ const baseUrl = (opts.baseUrl ?? DEFAULT_SKILLS_SH_URL).replace(/\/+$/, "");
88
+ const fetcher = opts.fetcher ?? defaultFetcher;
89
+ const id = "skills.sh";
90
+ return {
91
+ id,
92
+ displayName: "skills.sh",
93
+ async search(query, sopts = {}) {
94
+ const page = Math.max(1, sopts.page ?? 1);
95
+ const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, sopts.pageSize ?? 20));
96
+ const q = query.trim();
97
+ if (!q) return { adapterId: id, results: [], hasMore: false };
98
+ const url = `${baseUrl}/api/skills?query=${encodeURIComponent(q)}&page=${page}&pageSize=${pageSize}&sortBy=installs`;
99
+ const raw = await fetcher(url);
100
+ const results = parseResults(raw, url);
101
+ return {
102
+ adapterId: id,
103
+ results,
104
+ hasMore: results.length === pageSize
105
+ };
106
+ },
107
+ resolveInstallRef(registryId) {
108
+ const trimmed = registryId.trim();
109
+ if (!trimmed) {
110
+ throw new ParseError({
111
+ message: "Empty skills.sh registry id.",
112
+ source: "skills.sh.resolveInstallRef"
113
+ });
114
+ }
115
+ const atIdx = trimmed.indexOf("@");
116
+ const repoPart = atIdx > 0 ? trimmed.slice(0, atIdx) : trimmed;
117
+ const segs = repoPart.split("/").filter(Boolean);
118
+ if (segs.length !== 2) {
119
+ throw new ParseError({
120
+ message: `Invalid skills.sh id "${registryId}". Expected "<owner>/<repo>" or "<owner>/<repo>@<ref>".`,
121
+ source: "skills.sh.resolveInstallRef",
122
+ context: { registryId }
123
+ });
124
+ }
125
+ return trimmed;
126
+ }
127
+ };
128
+ }
129
+ function parseResults(raw, url) {
130
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
131
+ throw new ParseError({
132
+ message: "Skill registry response was not a JSON object.",
133
+ source: "skills.sh.search",
134
+ context: { url }
135
+ });
136
+ }
137
+ const results = raw.results;
138
+ if (!Array.isArray(results)) {
139
+ return [];
140
+ }
141
+ const out = [];
142
+ for (let i = 0; i < results.length; i++) {
143
+ const entry = results[i];
144
+ if (!entry || typeof entry !== "object") continue;
145
+ const name = strField(entry, "name") ?? strField(entry, "slug");
146
+ const description = strField(entry, "description") ?? "";
147
+ const owner = strField(entry, "owner") ?? strField(entry, "author");
148
+ const repo = strField(entry, "repo") ?? strField(entry, "repository");
149
+ if (!name || !owner || !repo) continue;
150
+ const ref = strField(entry, "ref") ?? strField(entry, "branch") ?? strField(entry, "tag");
151
+ const installRef = ref ? `${owner}/${repo}@${ref}` : `${owner}/${repo}`;
152
+ out.push({
153
+ id: strField(entry, "id") ?? `${owner}/${repo}`,
154
+ name,
155
+ description,
156
+ author: owner,
157
+ installs: numField(entry, "installs") ?? numField(entry, "installCount"),
158
+ stars: numField(entry, "stars") ?? numField(entry, "starCount"),
159
+ securityScore: numField(entry, "securityScore") ?? numField(entry, "score"),
160
+ updatedAt: strField(entry, "updatedAt") ?? strField(entry, "updated_at"),
161
+ installRef
162
+ });
163
+ }
164
+ return out;
165
+ }
166
+ function strField(rec, key) {
167
+ const v = rec[key];
168
+ return typeof v === "string" && v.length > 0 ? v : void 0;
169
+ }
170
+ function numField(rec, key) {
171
+ const v = rec[key];
172
+ if (typeof v === "number" && Number.isFinite(v)) return v;
173
+ if (typeof v === "string" && /^\d+(\.\d+)?$/.test(v.trim())) {
174
+ const n = Number(v);
175
+ if (Number.isFinite(n)) return n;
176
+ }
177
+ return void 0;
178
+ }
179
+
180
+ // src/skills/skill-generator.ts
181
+ import { spawn } from "node:child_process";
182
+ import * as fs from "node:fs/promises";
183
+ import * as path from "node:path";
184
+ async function validateSkillNameAvailable(name, loader) {
185
+ const formatViolations = validateSkillName(name);
186
+ const conflicts = loader ? (await loader.listEntries()).filter((e) => e.name === name) : [];
187
+ const formatOk = formatViolations.length === 0;
188
+ const collisionBlocks = conflicts.some((c) => c.source === "project" || c.source === "user");
189
+ return {
190
+ ok: formatOk && !collisionBlocks,
191
+ formatViolations,
192
+ conflicts
193
+ };
194
+ }
195
+ function generateSkillSkeleton(opts) {
196
+ const name = opts.name.trim();
197
+ if (!isValidSkillNameFormat(name)) {
198
+ throw new WrongStackError({
199
+ message: `Cannot generate skeleton: invalid skill name "${name}". Names must be kebab-case (a-z0-9 and hyphens), \u226464 chars.`,
200
+ code: ERROR_CODES.VALIDATION_ERROR,
201
+ subsystem: "general",
202
+ context: { name }
203
+ });
204
+ }
205
+ const description = opts.description.trim();
206
+ const triggers = (opts.triggerKeywords ?? []).map((k) => k.trim()).filter(Boolean).map((k) => `"${k}"`).join(", ");
207
+ const triggerLine = triggers ? `
208
+ Triggers: user says ${triggers}.` : "";
209
+ const version = opts.version?.trim() || "1.0.0";
210
+ const title = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
211
+ return `---
212
+ name: ${name}
213
+ description: |
214
+ ${description || `Use this skill when <trigger situation>.`}${triggerLine}
215
+ version: ${version}
216
+ ---
217
+
218
+ # ${title}
219
+
220
+ ## Overview
221
+
222
+ ${description || "One-line description of what this skill does."}
223
+
224
+ ## Rules
225
+
226
+ 1. Rule one
227
+ 2. Rule two
228
+
229
+ ## Patterns
230
+
231
+ ### Do
232
+
233
+ \`\`\`ts
234
+ // good example
235
+ \`\`\`
236
+
237
+ ### Don't
238
+
239
+ \`\`\`ts
240
+ // bad example
241
+ \`\`\`
242
+
243
+ ## Skills in scope
244
+
245
+ - \`other-skill\` \u2014 for delegation when this skill needs help
246
+ `;
247
+ }
248
+ function extractSkillFromPrompt(prompt) {
249
+ const text = prompt.trim();
250
+ if (!text) {
251
+ return { suggestedName: "", description: "", body: "", triggerKeywords: [] };
252
+ }
253
+ const headingMatch = text.match(/^#{1,6}\s+(.+?)\s*$/m);
254
+ const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim() ?? text;
255
+ const titleSource = headingMatch?.[1] ?? firstLine.replace(/^#+\s*/, "");
256
+ const suggestedName = toKebab(titleSource).slice(0, SKILL_LIMITS.SKILL_NAME_MAX_LEN);
257
+ const paragraphs = text.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean);
258
+ const description = paragraphs[0]?.replace(/^#{1,6}\s+/m, "").trim() ?? titleSource;
259
+ const body = paragraphs.slice(1).join("\n\n");
260
+ const quoted = [...text.matchAll(/"([^"]{2,40})"/g)].map((m) => m[1]).filter((q) => typeof q === "string").map((q) => q.toLowerCase());
261
+ const titleWords = titleSource.split(/\W+/).map((w) => w.toLowerCase()).filter((w) => w.length > 3 && !STOPWORDS.has(w));
262
+ const triggerKeywords = dedupe([...quoted, ...titleWords]).slice(0, 8);
263
+ return { suggestedName, description, body, triggerKeywords };
264
+ }
265
+ async function openInEditor(filePath, env = process.env) {
266
+ const editor = env["VISUAL"]?.trim() && env["VISUAL"] || env["EDITOR"]?.trim() && env["EDITOR"] || defaultEditor();
267
+ if (!editor) {
268
+ throw new WrongStackError({
269
+ message: "No editor found. Set the EDITOR or VISUAL environment variable (e.g. `export EDITOR=code` or `export EDITOR=vim`).",
270
+ code: ERROR_CODES.VALIDATION_ERROR,
271
+ subsystem: "general",
272
+ context: { filePath }
273
+ });
274
+ }
275
+ const shell = process.platform === "win32";
276
+ const parts = editor.split(/\s+/).filter(Boolean);
277
+ if (parts.length === 0) {
278
+ throw new WrongStackError({
279
+ message: "Resolved editor command is empty.",
280
+ code: ERROR_CODES.VALIDATION_ERROR,
281
+ subsystem: "general",
282
+ context: { filePath }
283
+ });
284
+ }
285
+ const editorArgs = [...parts.slice(1), filePath];
286
+ const child = shell ? (() => {
287
+ const inv = buildWin32CmdShimInvocation(parts[0], editorArgs);
288
+ return spawn(inv.command, inv.args, {
289
+ stdio: "ignore",
290
+ detached: true,
291
+ windowsVerbatimArguments: inv.windowsVerbatimArguments,
292
+ // Suppresses the console flash the cmd.exe wrapper would otherwise
293
+ // show before the editor appears. Repo convention — see
294
+ // `core/tests/architecture/spawn-convention.test.ts`.
295
+ windowsHide: true
296
+ });
297
+ })() : spawn(parts[0], editorArgs, {
298
+ stdio: "ignore",
299
+ detached: true,
300
+ windowsHide: true
301
+ });
302
+ child.unref();
303
+ }
304
+ async function writeSkeletonSkill(skillsDir, body, opts = {}) {
305
+ const fm = parseSkillFrontmatter(body);
306
+ const name = fm.name;
307
+ if (!name || !isValidSkillNameFormat(name)) {
308
+ throw new WrongStackError({
309
+ message: "Skeleton body has no valid `name` in its frontmatter.",
310
+ code: ERROR_CODES.VALIDATION_ERROR,
311
+ subsystem: "general"
312
+ });
313
+ }
314
+ const skillDir = path.join(skillsDir, name);
315
+ const skillFile = path.join(skillDir, "SKILL.md");
316
+ if (!opts.overwrite) {
317
+ try {
318
+ await fs.access(skillFile);
319
+ throw new WrongStackError({
320
+ message: `A skill already exists at ${skillFile}. Use --force to overwrite.`,
321
+ code: ERROR_CODES.VALIDATION_ERROR,
322
+ subsystem: "general",
323
+ context: { skillFile }
324
+ });
325
+ } catch (err) {
326
+ if (err instanceof WrongStackError) throw err;
327
+ }
328
+ }
329
+ await fs.mkdir(skillDir, { recursive: true });
330
+ await fs.writeFile(skillFile, body, "utf8");
331
+ return skillFile;
332
+ }
333
+ function bodyLineAdvisory(body) {
334
+ const lines = body.split("\n").length;
335
+ return { lines, over: lines > SKILL_LIMITS.SKILL_BODY_LINE_LIMIT };
336
+ }
337
+ var STOPWORDS = /* @__PURE__ */ new Set([
338
+ "the",
339
+ "and",
340
+ "for",
341
+ "with",
342
+ "that",
343
+ "this",
344
+ "from",
345
+ "your",
346
+ "have",
347
+ "will",
348
+ "are",
349
+ "was",
350
+ "were",
351
+ "been",
352
+ "into",
353
+ "when",
354
+ "use",
355
+ "skill",
356
+ "about"
357
+ ]);
358
+ function toKebab(s) {
359
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, SKILL_LIMITS.SKILL_NAME_MAX_LEN) || "skill";
360
+ }
361
+ function dedupe(arr) {
362
+ return [...new Set(arr)];
363
+ }
364
+ function defaultEditor() {
365
+ if (process.platform === "win32") return "notepad";
366
+ if (process.platform === "darwin") return "open";
367
+ return void 0;
368
+ }
369
+
370
+ // src/skills/github-fetcher.ts
371
+ import { createWriteStream } from "node:fs";
372
+ import * as fs2 from "node:fs/promises";
373
+ import * as os from "node:os";
374
+ import * as path2 from "node:path";
375
+ import { Readable } from "node:stream";
376
+ import { pipeline } from "node:stream/promises";
377
+ import { createGunzip } from "node:zlib";
378
+ function parseSkillRef(input) {
379
+ const trimmed = input.trim().replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "");
380
+ const atIdx = trimmed.indexOf("@");
381
+ let refPath;
382
+ let ref;
383
+ if (atIdx > 0) {
384
+ refPath = trimmed.slice(0, atIdx);
385
+ ref = trimmed.slice(atIdx + 1);
386
+ } else {
387
+ refPath = trimmed;
388
+ ref = "main";
389
+ }
390
+ const parts = refPath.split("/").filter(Boolean);
391
+ if (parts.length < 2) {
392
+ throw new WrongStackError({
393
+ message: `Invalid skill reference "${input}". Expected format: user/repo or user/repo@ref`,
394
+ code: ERROR_CODES.UNKNOWN,
395
+ subsystem: "general",
396
+ context: { input }
397
+ });
398
+ }
399
+ return { owner: expectDefined(parts[0]), repo: expectDefined(parts[1]), ref };
400
+ }
401
+ async function writeBoundedGzipStream(source, outputPath, maxBytes, maxCompressedBytes = SKILL_LIMITS.MAX_TARBALL_SIZE) {
402
+ let compressedBytes = 0;
403
+ let writtenBytes = 0;
404
+ await pipeline(
405
+ source,
406
+ async function* (chunks) {
407
+ for await (const chunk of chunks) {
408
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
409
+ compressedBytes += buffer.byteLength;
410
+ if (compressedBytes > maxCompressedBytes) {
411
+ throw new WrongStackError({
412
+ message: `Compressed tarball too large. Max: ${maxCompressedBytes / 1024 / 1024}MB`,
413
+ code: ERROR_CODES.UNKNOWN,
414
+ subsystem: "general",
415
+ context: { compressedBytes, maxBytes: maxCompressedBytes }
416
+ });
417
+ }
418
+ yield buffer;
419
+ }
420
+ },
421
+ createGunzip(),
422
+ async function* (chunks) {
423
+ for await (const chunk of chunks) {
424
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
425
+ writtenBytes += buffer.byteLength;
426
+ if (writtenBytes > maxBytes) {
427
+ throw new WrongStackError({
428
+ message: `Uncompressed tarball too large. Max: ${maxBytes / 1024 / 1024}MB`,
429
+ code: ERROR_CODES.UNKNOWN,
430
+ subsystem: "general",
431
+ context: { uncompressedBytes: writtenBytes, maxBytes }
432
+ });
433
+ }
434
+ yield buffer;
435
+ }
436
+ },
437
+ createWriteStream(outputPath, { flags: "wx" })
438
+ );
439
+ return writtenBytes;
440
+ }
441
+ function resolveGitHubToken(env = process.env) {
442
+ const raw = env["WRONGSTACK_GITHUB_TOKEN"] ?? env["GITHUB_TOKEN"] ?? env["GH_TOKEN"];
443
+ if (!raw) return void 0;
444
+ const trimmed = raw.trim();
445
+ return trimmed.length > 0 ? trimmed : void 0;
446
+ }
447
+ async function downloadGitHubTarball(parsed) {
448
+ const url = `https://api.github.com/repos/${parsed.owner}/${parsed.repo}/tarball/${parsed.ref}`;
449
+ const token = resolveGitHubToken();
450
+ const headers = {
451
+ Accept: "application/vnd.github+json",
452
+ "User-Agent": "wrongstack-skill-installer"
453
+ };
454
+ if (token) headers["Authorization"] = `Bearer ${token}`;
455
+ let response;
456
+ try {
457
+ response = await fetch(url, {
458
+ signal: AbortSignal.timeout(3e4),
459
+ headers,
460
+ redirect: "follow"
461
+ });
462
+ } catch (err) {
463
+ throw new FetchError({
464
+ message: `Network error fetching ${parsed.owner}/${parsed.repo}: ${err instanceof Error ? err.message : String(err)}`,
465
+ status: 0,
466
+ context: { owner: parsed.owner, repo: parsed.repo, ref: parsed.ref, op: "tarball" },
467
+ cause: err
468
+ });
469
+ }
470
+ if (!response.ok) {
471
+ if (response.status === 404) {
472
+ throw new WrongStackError({
473
+ message: `Repository not found: ${parsed.owner}/${parsed.repo}` + (parsed.ref !== "main" ? ` (ref: ${parsed.ref})` : "") + (token ? "" : ". If this is a private repo, set GITHUB_TOKEN (or GH_TOKEN)."),
474
+ code: ERROR_CODES.UNKNOWN,
475
+ subsystem: "general",
476
+ context: { owner: parsed.owner, repo: parsed.repo, ref: parsed.ref, status: 404 }
477
+ });
478
+ }
479
+ if (response.status === 403) {
480
+ const remaining = response.headers.get("x-ratelimit-remaining");
481
+ const isRateLimited = remaining === "0";
482
+ throw new WrongStackError({
483
+ message: isRateLimited ? `GitHub API rate limit exceeded. Set GITHUB_TOKEN (or GH_TOKEN) to use the higher authenticated limit.` : `Access denied: ${parsed.owner}/${parsed.repo}. The repository may be private (set GITHUB_TOKEN to install private repos) or rate-limited.`,
484
+ code: ERROR_CODES.UNKNOWN,
485
+ subsystem: "general",
486
+ context: {
487
+ owner: parsed.owner,
488
+ repo: parsed.repo,
489
+ status: 403,
490
+ rateLimited: isRateLimited,
491
+ hasToken: Boolean(token)
492
+ }
493
+ });
494
+ }
495
+ throw new WrongStackError({
496
+ message: `GitHub API error (${response.status}): ${response.statusText}`,
497
+ code: ERROR_CODES.UNKNOWN,
498
+ subsystem: "general",
499
+ context: { owner: parsed.owner, repo: parsed.repo, status: response.status }
500
+ });
501
+ }
502
+ const contentLength = response.headers.get("content-length");
503
+ if (contentLength && Number.parseInt(contentLength, 10) > SKILL_LIMITS.MAX_TARBALL_SIZE) {
504
+ throw new WrongStackError({
505
+ message: `Tarball too large (${(Number.parseInt(contentLength, 10) / 1024 / 1024).toFixed(1)}MB). Max: ${SKILL_LIMITS.MAX_TARBALL_SIZE / 1024 / 1024}MB`,
506
+ code: ERROR_CODES.UNKNOWN,
507
+ subsystem: "general",
508
+ context: {
509
+ owner: parsed.owner,
510
+ repo: parsed.repo,
511
+ contentLengthBytes: Number.parseInt(contentLength, 10),
512
+ maxBytes: SKILL_LIMITS.MAX_TARBALL_SIZE
513
+ }
514
+ });
515
+ }
516
+ const tempDir = await fs2.mkdtemp(path2.join(os.tmpdir(), "wskill-"));
517
+ try {
518
+ if (!response.body) {
519
+ throw new WrongStackError({
520
+ message: "Empty response body from GitHub API",
521
+ code: ERROR_CODES.UNKNOWN,
522
+ subsystem: "general",
523
+ context: { owner: parsed.owner, repo: parsed.repo }
524
+ });
525
+ }
526
+ const nodeStream = Readable.fromWeb(response.body);
527
+ const tarPath = path2.join(tempDir, ".wrongstack-download.tar");
528
+ await writeBoundedGzipStream(nodeStream, tarPath, SKILL_LIMITS.MAX_UNCOMPRESSED_TARBALL_SIZE);
529
+ const tarBuf = await fs2.readFile(tarPath);
530
+ try {
531
+ await extractTar(tarBuf, tempDir);
532
+ } finally {
533
+ await fs2.rm(tarPath, { force: true });
534
+ }
535
+ return { tempDir };
536
+ } catch (err) {
537
+ await fs2.rm(tempDir, { recursive: true, force: true }).catch(() => {
538
+ });
539
+ throw err;
540
+ }
541
+ }
542
+ function readTarSize(buf, start) {
543
+ const first = buf[start] ?? 0;
544
+ if ((first & 128) !== 0) {
545
+ if (first === 255) {
546
+ throw new Error("tar: negative base-256 size field");
547
+ }
548
+ let value = first & 127;
549
+ for (let i = start + 1; i < start + 12; i++) {
550
+ value = value * 256 + (buf[i] ?? 0);
551
+ if (!Number.isSafeInteger(value)) {
552
+ throw new Error("tar: base-256 size exceeds the safe integer range");
553
+ }
554
+ }
555
+ return value;
556
+ }
557
+ const text = readTarString(buf, start, 12).trim();
558
+ if (text === "") return 0;
559
+ if (!/^[0-7]+$/.test(text)) {
560
+ throw new Error(`tar: unparseable size field ${JSON.stringify(text)}`);
561
+ }
562
+ const parsed = Number.parseInt(text, 8);
563
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
564
+ throw new Error("tar: size field out of range");
565
+ }
566
+ return parsed;
567
+ }
568
+ function readTarString(buf, start, maxLen) {
569
+ let end = start;
570
+ while (end < start + maxLen && end < buf.length && buf[end] !== 0) {
571
+ end++;
572
+ }
573
+ return buf.subarray(start, end).toString("utf8");
574
+ }
575
+ function stripTopDir(p) {
576
+ const idx = p.indexOf("/");
577
+ if (idx === -1) return "";
578
+ return p.slice(idx + 1);
579
+ }
580
+ async function extractTar(buf, destDir) {
581
+ let offset = 0;
582
+ while (offset + 512 <= buf.length) {
583
+ const header = buf.subarray(offset, offset + 512);
584
+ if (header.every((b) => b === 0)) break;
585
+ const name = readTarString(buf, offset, 100);
586
+ const prefix = readTarString(buf, offset + 345, 155);
587
+ const size = readTarSize(buf, offset + 124);
588
+ const typeflag = buf[offset + 156] ?? 0;
589
+ const fullPath = prefix ? `${prefix}/${name}` : name;
590
+ const relPath = stripTopDir(fullPath);
591
+ if (relPath && relPath !== "." && relPath !== "..") {
592
+ const destPath = path2.join(destDir, relPath);
593
+ const resolvedDest = path2.resolve(destPath);
594
+ const resolvedRoot = path2.resolve(destDir);
595
+ if (resolvedDest !== resolvedRoot && !resolvedDest.startsWith(resolvedRoot + path2.sep)) {
596
+ offset += 512 + Math.ceil(size / 512) * 512;
597
+ continue;
598
+ }
599
+ if (typeflag === 53 || typeflag === 0) {
600
+ if (relPath.endsWith("/") || typeflag === 53) {
601
+ await fs2.mkdir(destPath, { recursive: true });
602
+ }
603
+ }
604
+ if ((typeflag === 48 || typeflag === 0 || typeflag === 0) && size > 0) {
605
+ const dir = path2.dirname(destPath);
606
+ await fs2.mkdir(dir, { recursive: true });
607
+ const dataStart = offset + 512;
608
+ const dataEnd = dataStart + size;
609
+ if (dataEnd > buf.length) break;
610
+ await fs2.writeFile(destPath, buf.subarray(dataStart, dataEnd));
611
+ }
612
+ }
613
+ offset += 512 + Math.ceil(size / 512) * 512;
614
+ }
615
+ }
616
+
617
+ // src/skills/manifest-store.ts
618
+ import * as fs3 from "node:fs/promises";
619
+ import * as path3 from "node:path";
620
+ var SkillManifestStore = class {
621
+ manifestPath;
622
+ cache;
623
+ constructor(manifestPath) {
624
+ this.manifestPath = manifestPath;
625
+ }
626
+ async read() {
627
+ if (this.cache) return this.cache;
628
+ try {
629
+ const raw = await fs3.readFile(this.manifestPath, "utf8");
630
+ const data = JSON.parse(raw);
631
+ if (!Array.isArray(data.skills)) {
632
+ this.cache = { skills: [] };
633
+ } else {
634
+ this.cache = data;
635
+ }
636
+ } catch {
637
+ this.cache = { skills: [] };
638
+ }
639
+ return this.cache;
640
+ }
641
+ async write(data) {
642
+ const dir = path3.dirname(this.manifestPath);
643
+ await fs3.mkdir(dir, { recursive: true });
644
+ await atomicWrite(this.manifestPath, JSON.stringify(data, null, 2) + "\n");
645
+ this.cache = data;
646
+ }
647
+ async addEntry(entry) {
648
+ const data = await this.read();
649
+ data.skills = data.skills.filter((s) => !(s.name === entry.name && s.scope === entry.scope));
650
+ data.skills.push(entry);
651
+ await this.write(data);
652
+ }
653
+ async removeEntry(name, scope) {
654
+ const data = await this.read();
655
+ const before = data.skills.length;
656
+ data.skills = data.skills.filter((s) => !(s.name === name && s.scope === scope));
657
+ if (data.skills.length === before) return false;
658
+ await this.write(data);
659
+ return true;
660
+ }
661
+ async findByName(name) {
662
+ const data = await this.read();
663
+ return data.skills.filter((s) => s.name === name);
664
+ }
665
+ async findBySource(source) {
666
+ const data = await this.read();
667
+ return data.skills.filter((s) => s.source === source);
668
+ }
669
+ async listAll() {
670
+ const data = await this.read();
671
+ return data.skills;
672
+ }
673
+ /** Invalidate the in-memory cache (e.g. after external file changes). */
674
+ invalidateCache() {
675
+ this.cache = void 0;
676
+ }
677
+ };
678
+
679
+ // src/skills/skill-installer.ts
680
+ import * as fs4 from "node:fs/promises";
681
+ import * as path4 from "node:path";
682
+ function isInside(resolved, destDir) {
683
+ const root = path4.resolve(destDir);
684
+ return resolved === root || resolved.startsWith(root + path4.sep);
685
+ }
686
+ var MAX_SKILL_FILE_SIZE = SKILL_LIMITS.MAX_SKILL_FILE_SIZE;
687
+ var SkillInstaller = class {
688
+ opts;
689
+ manifest;
690
+ adapters;
691
+ constructor(opts) {
692
+ this.opts = opts;
693
+ this.manifest = new SkillManifestStore(opts.manifestPath);
694
+ this.adapters = opts.registryAdapters?.length ? opts.registryAdapters : [githubDirectAdapter];
695
+ }
696
+ /**
697
+ * Install skills from a skill reference.
698
+ *
699
+ * Accepts two ref formats:
700
+ * - `user/repo[@ref]` — direct GitHub install (original path)
701
+ * - `<adapterId>:<registryId>` — registry-resolved (e.g.
702
+ * `skills.sh:owner/repo@v1`); the
703
+ * adapter resolves it to a `user/repo`
704
+ * ref and the install proceeds as above.
705
+ *
706
+ * Supports both single-skill repos (SKILL.md at root) and multi-skill repos
707
+ * (skills/ subdirectory). The manifest records the GitHub source as
708
+ * `github:owner/repo` (so `/skill-update` keeps working) plus the originating
709
+ * registry in `registryFrom` when the install came through a registry.
710
+ */
711
+ async install(refInput, opts) {
712
+ const resolved = this.resolveRef(refInput);
713
+ const parsed = parseSkillRef(resolved.installRef);
714
+ const scope = opts?.global ? "user" : "project";
715
+ const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
716
+ const source = `github:${parsed.owner}/${parsed.repo}`;
717
+ this.opts.log?.(
718
+ resolved.fromRegistry ? `Resolving ${refInput} \u2192 ${resolved.installRef} (${resolved.adapterId}), downloading...` : `Downloading ${parsed.owner}/${parsed.repo}@${parsed.ref}...`
719
+ );
720
+ const { tempDir } = await downloadGitHubTarball(parsed);
721
+ try {
722
+ const skills = await this.detectSkills(tempDir);
723
+ if (skills.length === 0) {
724
+ throw new WrongStackError({
725
+ message: "No skills found in repository. Expected SKILL.md at root or skills/ subdirectory.",
726
+ code: ERROR_CODES.VALIDATION_ERROR,
727
+ subsystem: "general",
728
+ context: { owner: parsed.owner, repo: parsed.repo, ref: parsed.ref }
729
+ });
730
+ }
731
+ const results = [];
732
+ for (const skill of skills) {
733
+ const existing = await this.manifest.findByName(skill.name);
734
+ const existingInScope = existing.find((e) => e.scope === scope);
735
+ if (existingInScope) {
736
+ this.opts.log?.(`Overwriting existing skill "${skill.name}" (${scope})...`);
737
+ await this.removeSkillFiles(skill.name, scope);
738
+ }
739
+ const destDir = path4.join(targetDir, skill.name);
740
+ await fs4.mkdir(destDir, { recursive: true });
741
+ const copiedFiles = [];
742
+ for (const file of skill.files) {
743
+ const srcPath = path4.join(skill.baseDir, file);
744
+ const destPath = path4.join(destDir, file);
745
+ const resolved2 = path4.resolve(destPath);
746
+ if (!isInside(resolved2, destDir)) {
747
+ throw new FsError({
748
+ message: `Path traversal detected in skill file: ${file}`,
749
+ code: ERROR_CODES.FS_DELETE_FAILED,
750
+ path: destPath,
751
+ context: { reason: "path_traversal", skillName: skill.name }
752
+ });
753
+ }
754
+ const stat2 = await fs4.stat(srcPath);
755
+ if (stat2.size > MAX_SKILL_FILE_SIZE) {
756
+ throw new FsError({
757
+ message: `Skill file "${file}" is too large (${(stat2.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`,
758
+ code: ERROR_CODES.FS_WRITE_FAILED,
759
+ path: srcPath,
760
+ context: { skillName: skill.name, fileSize: stat2.size, maxSize: MAX_SKILL_FILE_SIZE }
761
+ });
762
+ }
763
+ await fs4.mkdir(path4.dirname(destPath), { recursive: true });
764
+ await fs4.copyFile(srcPath, destPath);
765
+ copiedFiles.push(file);
766
+ }
767
+ const entry = {
768
+ name: skill.name,
769
+ source,
770
+ ref: parsed.ref,
771
+ scope,
772
+ projectHash: scope === "project" ? this.opts.projectHash : void 0,
773
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
774
+ files: copiedFiles,
775
+ // When the install came through a registry (e.g. skills.sh), record
776
+ // which adapter + registry id resolved to this GitHub repo, so the
777
+ // source is traceable. `source` stays `github:owner/repo` for
778
+ // backward compat with `/skill-update`.
779
+ ...resolved.fromRegistry ? { registryFrom: { adapterId: resolved.adapterId, registryId: resolved.registryId } } : {}
780
+ };
781
+ await this.manifest.addEntry(entry);
782
+ results.push({
783
+ name: skill.name,
784
+ path: destDir,
785
+ scope,
786
+ source,
787
+ ref: parsed.ref,
788
+ skillCount: 1
789
+ });
790
+ }
791
+ this.invalidateLoaderCache();
792
+ return results;
793
+ } finally {
794
+ await fs4.rm(tempDir, { recursive: true, force: true }).catch(() => {
795
+ });
796
+ }
797
+ }
798
+ /**
799
+ * Import skills from a local directory (e.g. `.claude/skills`) into the
800
+ * project or user skills dir, optionally as symlinks. Used by `/skill-import`
801
+ * to take ownership of foreign skills so they can be edited/committed.
802
+ * Each direct subdirectory containing a valid `SKILL.md` is copied verbatim.
803
+ */
804
+ async importFromDir(srcDir, opts) {
805
+ const scope = opts?.global ? "user" : "project";
806
+ const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
807
+ let entries;
808
+ try {
809
+ entries = await fs4.readdir(srcDir, { withFileTypes: true });
810
+ } catch {
811
+ throw new WrongStackError({
812
+ message: `Source directory not found or not readable: ${srcDir}`,
813
+ code: ERROR_CODES.VALIDATION_ERROR,
814
+ subsystem: "general",
815
+ context: { srcDir }
816
+ });
817
+ }
818
+ const results = [];
819
+ for (const e of entries) {
820
+ if (!await entryIsDirectory(srcDir, e)) continue;
821
+ const skillMdPath = path4.join(srcDir, e.name, "SKILL.md");
822
+ let content;
823
+ try {
824
+ content = await fs4.readFile(skillMdPath, "utf8");
825
+ } catch {
826
+ continue;
827
+ }
828
+ const fm = parseSkillFrontmatter(content);
829
+ if (!fm.name || !fm.description || !isValidSkillNameFormat(fm.name)) continue;
830
+ const existing = await this.manifest.findByName(fm.name);
831
+ if (existing.find((x) => x.scope === scope)) {
832
+ await this.removeSkillFiles(fm.name, scope);
833
+ }
834
+ const destDir = path4.join(targetDir, fm.name);
835
+ await fs4.mkdir(destDir, { recursive: true });
836
+ const srcSkillDir = path4.join(srcDir, e.name);
837
+ const files = await collectFiles(srcSkillDir, srcSkillDir);
838
+ const copiedFiles = [];
839
+ for (const file of files) {
840
+ const srcPath = path4.join(srcSkillDir, file);
841
+ const destPath = path4.join(destDir, file);
842
+ const resolved = path4.resolve(destPath);
843
+ if (!isInside(resolved, destDir)) {
844
+ throw new FsError({
845
+ message: `Path traversal detected in skill file: ${file}`,
846
+ code: ERROR_CODES.FS_DELETE_FAILED,
847
+ path: destPath,
848
+ context: { reason: "path_traversal", skillName: fm.name }
849
+ });
850
+ }
851
+ await fs4.mkdir(path4.dirname(destPath), { recursive: true });
852
+ if (opts?.link) {
853
+ try {
854
+ await fs4.symlink(srcPath, destPath);
855
+ } catch (err) {
856
+ await fs4.copyFile(srcPath, destPath);
857
+ this.opts.log?.(`symlink failed for ${file} (${toErrorMessage(err)}); copied instead`);
858
+ }
859
+ } else {
860
+ await fs4.copyFile(srcPath, destPath);
861
+ }
862
+ copiedFiles.push(file);
863
+ }
864
+ await this.manifest.addEntry({
865
+ name: fm.name,
866
+ source: `import:${srcDir}`,
867
+ ref: "-",
868
+ scope,
869
+ projectHash: scope === "project" ? this.opts.projectHash : void 0,
870
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
871
+ files: copiedFiles
872
+ });
873
+ results.push({
874
+ name: fm.name,
875
+ path: destDir,
876
+ scope,
877
+ source: `import:${srcDir}`,
878
+ ref: "-",
879
+ skillCount: 1
880
+ });
881
+ }
882
+ this.invalidateLoaderCache();
883
+ return results;
884
+ }
885
+ /**
886
+ * Update installed skills.
887
+ * - No args: update all
888
+ * - Name: update that specific skill
889
+ * - Name + newRef: update to a different ref
890
+ */
891
+ async update(nameOrRef, _opts) {
892
+ const result = { updated: [], unchanged: [], errors: [] };
893
+ const allEntries = await this.manifest.listAll();
894
+ let targets;
895
+ if (nameOrRef) {
896
+ const byName = allEntries.filter((e) => e.name === nameOrRef);
897
+ if (byName.length > 0) {
898
+ targets = byName;
899
+ } else {
900
+ try {
901
+ const parsed = parseSkillRef(nameOrRef);
902
+ const source = `github:${parsed.owner}/${parsed.repo}`;
903
+ targets = allEntries.filter((e) => e.source === source);
904
+ if (targets.length === 0) {
905
+ result.errors.push({
906
+ name: nameOrRef,
907
+ error: `No installed skills found matching "${nameOrRef}"`
908
+ });
909
+ return result;
910
+ }
911
+ } catch {
912
+ result.errors.push({
913
+ name: nameOrRef,
914
+ error: `Invalid reference: ${nameOrRef}`
915
+ });
916
+ return result;
917
+ }
918
+ }
919
+ } else {
920
+ targets = allEntries;
921
+ }
922
+ const bySource = /* @__PURE__ */ new Map();
923
+ for (const entry of targets) {
924
+ const key = `${entry.source}@${entry.ref}`;
925
+ if (!bySource.has(key)) bySource.set(key, []);
926
+ bySource.get(key)?.push(entry);
927
+ }
928
+ for (const [, entries] of bySource) {
929
+ const first = expectDefined(entries[0]);
930
+ const scope = first.scope;
931
+ const isGlobal = scope === "user";
932
+ try {
933
+ const sourceRepo = first.source.replace("github:", "");
934
+ let refToInstall = first.ref;
935
+ if (nameOrRef && !allEntries.find((e) => e.name === nameOrRef)) {
936
+ try {
937
+ const parsed = parseSkillRef(nameOrRef);
938
+ refToInstall = parsed.ref;
939
+ } catch {
940
+ }
941
+ }
942
+ this.opts.log?.(`Updating ${first.source}@${refToInstall}...`);
943
+ const results = await this.install(`${sourceRepo}@${refToInstall}`, { global: isGlobal });
944
+ for (const r of results) {
945
+ result.updated.push({
946
+ name: r.name,
947
+ oldRef: first.ref,
948
+ newRef: refToInstall
949
+ });
950
+ }
951
+ } catch (err) {
952
+ const msg = toErrorMessage(err);
953
+ for (const entry of entries) {
954
+ result.errors.push({ name: entry.name, error: msg });
955
+ }
956
+ }
957
+ }
958
+ return result;
959
+ }
960
+ /**
961
+ * Uninstall a skill by name.
962
+ */
963
+ async uninstall(name, opts) {
964
+ const scope = opts?.global ? "user" : "project";
965
+ const entries = await this.manifest.findByName(name);
966
+ const entry = entries.find((e) => e.scope === scope);
967
+ if (!entry) {
968
+ throw new WrongStackError({
969
+ message: `Skill "${name}" is not installed${scope === "user" ? " (global)" : ""}.`,
970
+ code: ERROR_CODES.VALIDATION_ERROR,
971
+ subsystem: "general",
972
+ context: { skillName: name, scope }
973
+ });
974
+ }
975
+ await this.removeSkillFiles(name, scope);
976
+ await this.manifest.removeEntry(name, scope);
977
+ this.invalidateLoaderCache();
978
+ }
979
+ /**
980
+ * List all installed skills from the manifest.
981
+ */
982
+ async listInstalled() {
983
+ return this.manifest.listAll();
984
+ }
985
+ /**
986
+ * Search across all configured registry adapters. Results from each adapter
987
+ * are merged (deduplicated by `installRef`, adapters earlier in the list win
988
+ * conflicts). Adapters that don't support search (e.g. github-direct)
989
+ * contribute nothing.
990
+ */
991
+ async search(query, opts) {
992
+ const settled = await Promise.allSettled(this.adapters.map((a) => a.search(query, opts)));
993
+ const perAdapter = [];
994
+ const errors = [];
995
+ settled.forEach((s, i) => {
996
+ if (s.status === "fulfilled") perAdapter.push(s.value);
997
+ else {
998
+ const id = this.adapters[i]?.id ?? "?";
999
+ errors.push(`${id}: ${toErrorMessage(s.reason)}`);
1000
+ }
1001
+ });
1002
+ if (errors.length > 0) {
1003
+ this.opts.log?.(`Some registries failed during search: ${errors.join("; ")}`);
1004
+ }
1005
+ return dedupeSearchResults(perAdapter);
1006
+ }
1007
+ // ── Private helpers ──────────────────────────────────────────────
1008
+ /**
1009
+ * Detect skills in an extracted repository.
1010
+ * Returns an array of detected skills with their files.
1011
+ */
1012
+ async detectSkills(baseDir) {
1013
+ const results = [];
1014
+ const rootSkillMd = path4.join(baseDir, "SKILL.md");
1015
+ try {
1016
+ await fs4.access(rootSkillMd);
1017
+ const content = await fs4.readFile(rootSkillMd, "utf8");
1018
+ const fm = parseSkillFrontmatter(content);
1019
+ if (fm.name && fm.description && isValidSkillNameFormat(fm.name)) {
1020
+ results.push({
1021
+ name: fm.name,
1022
+ baseDir,
1023
+ files: ["SKILL.md"]
1024
+ });
1025
+ return results;
1026
+ }
1027
+ } catch {
1028
+ }
1029
+ const skillsDir = path4.join(baseDir, "skills");
1030
+ try {
1031
+ const entries = await fs4.readdir(skillsDir, { withFileTypes: true });
1032
+ for (const entry of entries) {
1033
+ if (!entry.isDirectory()) continue;
1034
+ const skillFile = path4.join(skillsDir, entry.name, "SKILL.md");
1035
+ try {
1036
+ const content = await fs4.readFile(skillFile, "utf8");
1037
+ const fm = parseSkillFrontmatter(content);
1038
+ if (fm.name && fm.description && isValidSkillNameFormat(fm.name)) {
1039
+ const skillDir = path4.join(skillsDir, entry.name);
1040
+ const files = await collectFiles(skillDir, skillDir);
1041
+ results.push({
1042
+ name: fm.name,
1043
+ baseDir: skillDir,
1044
+ files
1045
+ });
1046
+ }
1047
+ } catch {
1048
+ }
1049
+ }
1050
+ } catch {
1051
+ }
1052
+ return results;
1053
+ }
1054
+ /**
1055
+ * Remove all files for an installed skill.
1056
+ */
1057
+ /**
1058
+ * Delete an installed skill's directory.
1059
+ *
1060
+ * The containment check is not ceremonial: this is a `recursive: true,
1061
+ * force: true` delete whose target is built from a skill NAME taken from
1062
+ * manifest frontmatter. `path.join` normalises `..`, so a crafted name
1063
+ * escapes `targetDir` and takes the recursive delete with it. This file
1064
+ * already had `isInside` and applied it on the copy path but not here.
1065
+ *
1066
+ * The strict-subdirectory requirement matters separately: `isInside` allows
1067
+ * equality by design, and an empty or `.` name resolves `skillDir` to
1068
+ * `targetDir` itself — deleting every installed skill (WS-097).
1069
+ */
1070
+ async removeSkillFiles(name, scope) {
1071
+ const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
1072
+ const root = path4.resolve(targetDir);
1073
+ const skillDir = path4.resolve(path4.join(targetDir, name));
1074
+ if (skillDir === root || !isInside(skillDir, root)) {
1075
+ throw new FsError({
1076
+ message: `Refusing to delete skill files outside the skills directory: ${name}`,
1077
+ code: ERROR_CODES.FS_DELETE_FAILED,
1078
+ path: skillDir,
1079
+ context: { reason: "path_traversal", skillName: name, scope }
1080
+ });
1081
+ }
1082
+ await fs4.rm(skillDir, { recursive: true, force: true });
1083
+ }
1084
+ /**
1085
+ * Invalidate the skill loader's cache so newly installed skills appear.
1086
+ */
1087
+ invalidateLoaderCache() {
1088
+ const loader = this.opts.skillLoader;
1089
+ if (loader && typeof loader.invalidateCache === "function") {
1090
+ loader.invalidateCache();
1091
+ }
1092
+ }
1093
+ /**
1094
+ * Resolve an install ref, dispatching registry-prefixed refs to the matching
1095
+ * adapter. Returns the concrete `user/repo[@ref]` install ref plus provenance
1096
+ * (which adapter resolved it, if any).
1097
+ */
1098
+ resolveRef(refInput) {
1099
+ const trimmed = refInput.trim();
1100
+ const colonIdx = trimmed.indexOf(":");
1101
+ if (colonIdx > 0) {
1102
+ const maybeAdapterId = trimmed.slice(0, colonIdx);
1103
+ const adapter = this.adapters.find((a) => a.id === maybeAdapterId);
1104
+ if (adapter) {
1105
+ const registryId = trimmed.slice(colonIdx + 1);
1106
+ const installRef = adapter.resolveInstallRef(registryId);
1107
+ return { installRef, fromRegistry: true, adapterId: adapter.id, registryId };
1108
+ }
1109
+ }
1110
+ return { installRef: trimmed, fromRegistry: false, adapterId: "", registryId: "" };
1111
+ }
1112
+ };
1113
+ async function entryIsDirectory(dir, entry) {
1114
+ if (entry.isDirectory()) return true;
1115
+ if (entry.isSymbolicLink()) {
1116
+ try {
1117
+ return (await fs4.stat(path4.join(dir, entry.name))).isDirectory();
1118
+ } catch {
1119
+ return false;
1120
+ }
1121
+ }
1122
+ return false;
1123
+ }
1124
+ async function collectFiles(dir, baseDir) {
1125
+ const results = [];
1126
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
1127
+ for (const entry of entries) {
1128
+ const fullPath = path4.join(dir, entry.name);
1129
+ const relPath = path4.relative(baseDir, fullPath);
1130
+ if (entry.isDirectory()) {
1131
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
1132
+ results.push(...await collectFiles(fullPath, baseDir));
1133
+ } else {
1134
+ results.push(relPath);
1135
+ }
1136
+ }
1137
+ return results;
1138
+ }
1139
+ function dedupeSearchResults(perAdapter) {
1140
+ const seen = /* @__PURE__ */ new Set();
1141
+ const out = [];
1142
+ for (const block of perAdapter) {
1143
+ const kept = [];
1144
+ for (const r of block.results) {
1145
+ const key = r.installRef;
1146
+ if (seen.has(key)) continue;
1147
+ seen.add(key);
1148
+ kept.push(r);
1149
+ }
1150
+ if (kept.length > 0 || block.results.length > 0) {
1151
+ out.push({ ...block, results: kept });
1152
+ }
1153
+ }
1154
+ return out;
1155
+ }
1156
+
1157
+ export {
1158
+ githubDirectAdapter,
1159
+ DEFAULT_SKILLS_SH_URL,
1160
+ createSkillsShAdapter,
1161
+ validateSkillNameAvailable,
1162
+ generateSkillSkeleton,
1163
+ extractSkillFromPrompt,
1164
+ openInEditor,
1165
+ writeSkeletonSkill,
1166
+ bodyLineAdvisory,
1167
+ parseSkillRef,
1168
+ downloadGitHubTarball,
1169
+ SkillManifestStore,
1170
+ SkillInstaller
1171
+ };
1172
+ //# sourceMappingURL=chunk-RXKSDUAU.js.map