@askdkc/kiokuko 0.1.17 → 0.1.20

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 (539) hide show
  1. package/README.ja.md +91 -112
  2. package/README.ko.md +90 -102
  3. package/README.md +103 -262
  4. package/README.zh-CN.md +88 -102
  5. package/dist/agent-file/atomic-write.d.ts +90 -3
  6. package/dist/agent-file/atomic-write.d.ts.map +1 -1
  7. package/dist/agent-file/atomic-write.js +990 -14
  8. package/dist/agent-file/atomic-write.js.map +1 -1
  9. package/dist/agent-file/managed-block.d.ts +12 -1
  10. package/dist/agent-file/managed-block.d.ts.map +1 -1
  11. package/dist/agent-file/managed-block.js +49 -4
  12. package/dist/agent-file/managed-block.js.map +1 -1
  13. package/dist/agent-file/render.d.ts +1 -1
  14. package/dist/agent-file/render.d.ts.map +1 -1
  15. package/dist/agent-file/render.js +10 -7
  16. package/dist/agent-file/render.js.map +1 -1
  17. package/dist/akinator/agent-task.d.ts +11 -22
  18. package/dist/akinator/agent-task.d.ts.map +1 -1
  19. package/dist/akinator/agent-task.js +454 -99
  20. package/dist/akinator/agent-task.js.map +1 -1
  21. package/dist/akinator/capabilities.d.ts +18 -6
  22. package/dist/akinator/capabilities.d.ts.map +1 -1
  23. package/dist/akinator/capabilities.js +72 -35
  24. package/dist/akinator/capabilities.js.map +1 -1
  25. package/dist/akinator/capability-binding.d.ts +11 -0
  26. package/dist/akinator/capability-binding.d.ts.map +1 -0
  27. package/dist/akinator/capability-binding.js +56 -0
  28. package/dist/akinator/capability-binding.js.map +1 -0
  29. package/dist/akinator/domain.js +2 -2
  30. package/dist/akinator/domain.js.map +1 -1
  31. package/dist/akinator/knowledge-path.d.ts.map +1 -1
  32. package/dist/akinator/knowledge-path.js +409 -39
  33. package/dist/akinator/knowledge-path.js.map +1 -1
  34. package/dist/akinator/orchestrator.d.ts +3 -4
  35. package/dist/akinator/orchestrator.d.ts.map +1 -1
  36. package/dist/akinator/orchestrator.js +1 -4
  37. package/dist/akinator/orchestrator.js.map +1 -1
  38. package/dist/akinator/service.d.ts +0 -4
  39. package/dist/akinator/service.d.ts.map +1 -1
  40. package/dist/akinator/service.js +19 -35
  41. package/dist/akinator/service.js.map +1 -1
  42. package/dist/akinator/skill-discovery-attempt.d.ts +20 -0
  43. package/dist/akinator/skill-discovery-attempt.d.ts.map +1 -0
  44. package/dist/akinator/skill-discovery-attempt.js +390 -0
  45. package/dist/akinator/skill-discovery-attempt.js.map +1 -0
  46. package/dist/akinator/store.d.ts.map +1 -1
  47. package/dist/akinator/store.js +107 -52
  48. package/dist/akinator/store.js.map +1 -1
  49. package/dist/akinator/types.d.ts +0 -12
  50. package/dist/akinator/types.d.ts.map +1 -1
  51. package/dist/cli.d.ts +2 -0
  52. package/dist/cli.d.ts.map +1 -1
  53. package/dist/cli.js +560 -150
  54. package/dist/cli.js.map +1 -1
  55. package/dist/client/server-client.d.ts +5 -3
  56. package/dist/client/server-client.d.ts.map +1 -1
  57. package/dist/client/server-client.js +486 -181
  58. package/dist/client/server-client.js.map +1 -1
  59. package/dist/commands/agent.d.ts +2 -0
  60. package/dist/commands/agent.d.ts.map +1 -1
  61. package/dist/commands/agent.js +861 -86
  62. package/dist/commands/agent.js.map +1 -1
  63. package/dist/commands/backup.d.ts +2 -1
  64. package/dist/commands/backup.d.ts.map +1 -1
  65. package/dist/commands/backup.js +71 -8
  66. package/dist/commands/backup.js.map +1 -1
  67. package/dist/commands/doctor.d.ts +5 -1
  68. package/dist/commands/doctor.d.ts.map +1 -1
  69. package/dist/commands/doctor.js +70 -26
  70. package/dist/commands/doctor.js.map +1 -1
  71. package/dist/commands/export.d.ts +0 -4
  72. package/dist/commands/export.d.ts.map +1 -1
  73. package/dist/commands/export.js +191 -32
  74. package/dist/commands/export.js.map +1 -1
  75. package/dist/commands/import.d.ts +10 -1
  76. package/dist/commands/import.d.ts.map +1 -1
  77. package/dist/commands/import.js +592 -113
  78. package/dist/commands/import.js.map +1 -1
  79. package/dist/commands/init.d.ts +19 -1
  80. package/dist/commands/init.d.ts.map +1 -1
  81. package/dist/commands/init.js +569 -22
  82. package/dist/commands/init.js.map +1 -1
  83. package/dist/commands/purge.d.ts.map +1 -1
  84. package/dist/commands/purge.js +3 -0
  85. package/dist/commands/purge.js.map +1 -1
  86. package/dist/commands/setup.d.ts +37 -8
  87. package/dist/commands/setup.d.ts.map +1 -1
  88. package/dist/commands/setup.js +448 -86
  89. package/dist/commands/setup.js.map +1 -1
  90. package/dist/commands/skills.d.ts +9 -0
  91. package/dist/commands/skills.d.ts.map +1 -0
  92. package/dist/commands/skills.js +317 -0
  93. package/dist/commands/skills.js.map +1 -0
  94. package/dist/commands/use.d.ts +10 -2
  95. package/dist/commands/use.d.ts.map +1 -1
  96. package/dist/commands/use.js +851 -73
  97. package/dist/commands/use.js.map +1 -1
  98. package/dist/config/paths.d.ts +11 -1
  99. package/dist/config/paths.d.ts.map +1 -1
  100. package/dist/config/paths.js +64 -29
  101. package/dist/config/paths.js.map +1 -1
  102. package/dist/config/project-config.d.ts +1 -0
  103. package/dist/config/project-config.d.ts.map +1 -1
  104. package/dist/config/project-config.js +70 -32
  105. package/dist/config/project-config.js.map +1 -1
  106. package/dist/context/broker.d.ts +28 -14
  107. package/dist/context/broker.d.ts.map +1 -1
  108. package/dist/context/broker.js +604 -145
  109. package/dist/context/broker.js.map +1 -1
  110. package/dist/context/delivery.d.ts +6 -21
  111. package/dist/context/delivery.d.ts.map +1 -1
  112. package/dist/context/delivery.js +196 -147
  113. package/dist/context/delivery.js.map +1 -1
  114. package/dist/context/feedback.d.ts +11 -12
  115. package/dist/context/feedback.d.ts.map +1 -1
  116. package/dist/context/feedback.js +229 -261
  117. package/dist/context/feedback.js.map +1 -1
  118. package/dist/context/ranking.d.ts +1 -12
  119. package/dist/context/ranking.d.ts.map +1 -1
  120. package/dist/context/ranking.js +13 -4
  121. package/dist/context/ranking.js.map +1 -1
  122. package/dist/context/recommendations.d.ts +0 -5
  123. package/dist/context/recommendations.d.ts.map +1 -1
  124. package/dist/context/recommendations.js +0 -1
  125. package/dist/context/recommendations.js.map +1 -1
  126. package/dist/context/run-state.d.ts +26 -0
  127. package/dist/context/run-state.d.ts.map +1 -0
  128. package/dist/context/run-state.js +399 -0
  129. package/dist/context/run-state.js.map +1 -0
  130. package/dist/context/scoped-broker.d.ts +17 -5
  131. package/dist/context/scoped-broker.d.ts.map +1 -1
  132. package/dist/context/scoped-broker.js +512 -106
  133. package/dist/context/scoped-broker.js.map +1 -1
  134. package/dist/context/selection-state.d.ts +19 -0
  135. package/dist/context/selection-state.d.ts.map +1 -0
  136. package/dist/context/selection-state.js +250 -0
  137. package/dist/context/selection-state.js.map +1 -0
  138. package/dist/db/adapter.d.ts +5 -3
  139. package/dist/db/adapter.d.ts.map +1 -1
  140. package/dist/db/adapter.js +7 -3
  141. package/dist/db/adapter.js.map +1 -1
  142. package/dist/db/capabilities.d.ts.map +1 -1
  143. package/dist/db/capabilities.js +34 -16
  144. package/dist/db/capabilities.js.map +1 -1
  145. package/dist/db/connection.d.ts +8 -0
  146. package/dist/db/connection.d.ts.map +1 -1
  147. package/dist/db/connection.js +55 -26
  148. package/dist/db/connection.js.map +1 -1
  149. package/dist/db/migrate.d.ts +28 -7
  150. package/dist/db/migrate.d.ts.map +1 -1
  151. package/dist/db/migrate.js +106 -78
  152. package/dist/db/migrate.js.map +1 -1
  153. package/dist/db/sqlite-retry.d.ts +11 -0
  154. package/dist/db/sqlite-retry.d.ts.map +1 -0
  155. package/dist/db/sqlite-retry.js +66 -0
  156. package/dist/db/sqlite-retry.js.map +1 -0
  157. package/dist/db/transaction.d.ts +13 -0
  158. package/dist/db/transaction.d.ts.map +1 -1
  159. package/dist/db/transaction.js +53 -24
  160. package/dist/db/transaction.js.map +1 -1
  161. package/dist/db/upgrade-backup.d.ts +58 -2
  162. package/dist/db/upgrade-backup.d.ts.map +1 -1
  163. package/dist/db/upgrade-backup.js +758 -28
  164. package/dist/db/upgrade-backup.js.map +1 -1
  165. package/dist/errors.d.ts +4 -0
  166. package/dist/errors.d.ts.map +1 -1
  167. package/dist/errors.js +29 -1
  168. package/dist/errors.js.map +1 -1
  169. package/dist/gateway/agent-service.d.ts +4 -1
  170. package/dist/gateway/agent-service.d.ts.map +1 -1
  171. package/dist/gateway/agent-service.js +71 -58
  172. package/dist/gateway/agent-service.js.map +1 -1
  173. package/dist/gateway/checkpoint-service.d.ts +1 -0
  174. package/dist/gateway/checkpoint-service.d.ts.map +1 -1
  175. package/dist/gateway/checkpoint-service.js +47 -24
  176. package/dist/gateway/checkpoint-service.js.map +1 -1
  177. package/dist/ledger/archive.d.ts +1 -4
  178. package/dist/ledger/archive.d.ts.map +1 -1
  179. package/dist/ledger/archive.js +77 -39
  180. package/dist/ledger/archive.js.map +1 -1
  181. package/dist/ledger/maintenance.d.ts +0 -11
  182. package/dist/ledger/maintenance.d.ts.map +1 -1
  183. package/dist/ledger/maintenance.js +24 -40
  184. package/dist/ledger/maintenance.js.map +1 -1
  185. package/dist/ledger/projection.d.ts +0 -1
  186. package/dist/ledger/projection.d.ts.map +1 -1
  187. package/dist/ledger/projection.js.map +1 -1
  188. package/dist/ledger/promotion.d.ts +0 -9
  189. package/dist/ledger/promotion.d.ts.map +1 -1
  190. package/dist/ledger/promotion.js.map +1 -1
  191. package/dist/ledger/query.d.ts +1 -15
  192. package/dist/ledger/query.d.ts.map +1 -1
  193. package/dist/ledger/query.js.map +1 -1
  194. package/dist/ledger/store.d.ts +0 -1
  195. package/dist/ledger/store.d.ts.map +1 -1
  196. package/dist/ledger/store.js +11 -8
  197. package/dist/ledger/store.js.map +1 -1
  198. package/dist/ledger/types.d.ts +0 -4
  199. package/dist/ledger/types.d.ts.map +1 -1
  200. package/dist/ledger/types.js.map +1 -1
  201. package/dist/ledger/validate.d.ts.map +1 -1
  202. package/dist/ledger/validate.js +0 -5
  203. package/dist/ledger/validate.js.map +1 -1
  204. package/dist/mcp/server.d.ts +4 -0
  205. package/dist/mcp/server.d.ts.map +1 -1
  206. package/dist/mcp/server.js +105 -54
  207. package/dist/mcp/server.js.map +1 -1
  208. package/dist/memory/audit.d.ts +0 -1
  209. package/dist/memory/audit.d.ts.map +1 -1
  210. package/dist/memory/audit.js +0 -1
  211. package/dist/memory/audit.js.map +1 -1
  212. package/dist/memory/curator.d.ts.map +1 -1
  213. package/dist/memory/curator.js +172 -96
  214. package/dist/memory/curator.js.map +1 -1
  215. package/dist/memory/entries.d.ts +12 -2
  216. package/dist/memory/entries.d.ts.map +1 -1
  217. package/dist/memory/entries.js +199 -115
  218. package/dist/memory/entries.js.map +1 -1
  219. package/dist/memory/federated-retrieval.d.ts +12 -1
  220. package/dist/memory/federated-retrieval.d.ts.map +1 -1
  221. package/dist/memory/federated-retrieval.js +183 -70
  222. package/dist/memory/federated-retrieval.js.map +1 -1
  223. package/dist/memory/hybrid-retrieval.d.ts +5 -11
  224. package/dist/memory/hybrid-retrieval.d.ts.map +1 -1
  225. package/dist/memory/hybrid-retrieval.js +78 -58
  226. package/dist/memory/hybrid-retrieval.js.map +1 -1
  227. package/dist/memory/lifecycle.d.ts.map +1 -1
  228. package/dist/memory/lifecycle.js +17 -13
  229. package/dist/memory/lifecycle.js.map +1 -1
  230. package/dist/memory/rebuild-search.d.ts +5 -0
  231. package/dist/memory/rebuild-search.d.ts.map +1 -1
  232. package/dist/memory/rebuild-search.js +101 -76
  233. package/dist/memory/rebuild-search.js.map +1 -1
  234. package/dist/memory/retrieval-query.d.ts +0 -5
  235. package/dist/memory/retrieval-query.d.ts.map +1 -1
  236. package/dist/memory/retrieval-query.js +0 -5
  237. package/dist/memory/retrieval-query.js.map +1 -1
  238. package/dist/memory/retrieval.d.ts.map +1 -1
  239. package/dist/memory/retrieval.js +25 -131
  240. package/dist/memory/retrieval.js.map +1 -1
  241. package/dist/memory/revisions.d.ts +76 -1
  242. package/dist/memory/revisions.d.ts.map +1 -1
  243. package/dist/memory/revisions.js +540 -36
  244. package/dist/memory/revisions.js.map +1 -1
  245. package/dist/memory/scoped-memory.d.ts +16 -1
  246. package/dist/memory/scoped-memory.d.ts.map +1 -1
  247. package/dist/memory/scoped-memory.js +240 -107
  248. package/dist/memory/scoped-memory.js.map +1 -1
  249. package/dist/memory/secrets.d.ts +2 -0
  250. package/dist/memory/secrets.d.ts.map +1 -1
  251. package/dist/memory/secrets.js +36 -0
  252. package/dist/memory/secrets.js.map +1 -1
  253. package/dist/memory/structured-memory.d.ts +3 -2
  254. package/dist/memory/structured-memory.d.ts.map +1 -1
  255. package/dist/memory/structured-memory.js +54 -22
  256. package/dist/memory/structured-memory.js.map +1 -1
  257. package/dist/memory/workspaces.d.ts +9 -0
  258. package/dist/memory/workspaces.d.ts.map +1 -1
  259. package/dist/memory/workspaces.js +67 -0
  260. package/dist/memory/workspaces.js.map +1 -1
  261. package/dist/package-version.d.ts +2 -0
  262. package/dist/package-version.d.ts.map +1 -0
  263. package/dist/package-version.js +7 -0
  264. package/dist/package-version.js.map +1 -0
  265. package/dist/repository/binding.d.ts +5 -0
  266. package/dist/repository/binding.d.ts.map +1 -1
  267. package/dist/repository/binding.js +117 -17
  268. package/dist/repository/binding.js.map +1 -1
  269. package/dist/repository/framework-version.d.ts +8 -0
  270. package/dist/repository/framework-version.d.ts.map +1 -0
  271. package/dist/repository/framework-version.js +29 -0
  272. package/dist/repository/framework-version.js.map +1 -0
  273. package/dist/repository/identity-value.d.ts +4 -0
  274. package/dist/repository/identity-value.d.ts.map +1 -0
  275. package/dist/repository/identity-value.js +24 -0
  276. package/dist/repository/identity-value.js.map +1 -0
  277. package/dist/repository/identity.d.ts.map +1 -1
  278. package/dist/repository/identity.js +2 -0
  279. package/dist/repository/identity.js.map +1 -1
  280. package/dist/repository/project-fingerprint.d.ts +14 -2
  281. package/dist/repository/project-fingerprint.d.ts.map +1 -1
  282. package/dist/repository/project-fingerprint.js +312 -33
  283. package/dist/repository/project-fingerprint.js.map +1 -1
  284. package/dist/repository/remote-url.d.ts +0 -1
  285. package/dist/repository/remote-url.d.ts.map +1 -1
  286. package/dist/repository/remote-url.js +0 -1
  287. package/dist/repository/remote-url.js.map +1 -1
  288. package/dist/runtime-version.d.ts +2 -1
  289. package/dist/runtime-version.d.ts.map +1 -1
  290. package/dist/runtime-version.js +21 -4
  291. package/dist/runtime-version.js.map +1 -1
  292. package/dist/serialization/boundary-json.d.ts +20 -0
  293. package/dist/serialization/boundary-json.d.ts.map +1 -0
  294. package/dist/serialization/boundary-json.js +129 -0
  295. package/dist/serialization/boundary-json.js.map +1 -0
  296. package/dist/serialization/envelope.js +1 -1
  297. package/dist/serialization/envelope.js.map +1 -1
  298. package/dist/serialization/validate.d.ts +7 -0
  299. package/dist/serialization/validate.d.ts.map +1 -1
  300. package/dist/serialization/validate.js +53 -13
  301. package/dist/serialization/validate.js.map +1 -1
  302. package/dist/server/app.js +1 -1
  303. package/dist/server/app.js.map +1 -1
  304. package/dist/server/http.d.ts +4 -3
  305. package/dist/server/http.d.ts.map +1 -1
  306. package/dist/server/http.js +90 -50
  307. package/dist/server/http.js.map +1 -1
  308. package/dist/server/idempotency.d.ts +0 -6
  309. package/dist/server/idempotency.d.ts.map +1 -1
  310. package/dist/server/idempotency.js.map +1 -1
  311. package/dist/server/router.d.ts.map +1 -1
  312. package/dist/server/router.js +21 -4
  313. package/dist/server/router.js.map +1 -1
  314. package/dist/server/routes/agent-capability-gate.d.ts +40 -0
  315. package/dist/server/routes/agent-capability-gate.d.ts.map +1 -0
  316. package/dist/server/routes/agent-capability-gate.js +182 -0
  317. package/dist/server/routes/agent-capability-gate.js.map +1 -0
  318. package/dist/server/routes/agent-events.d.ts.map +1 -1
  319. package/dist/server/routes/agent-events.js +8 -1
  320. package/dist/server/routes/agent-events.js.map +1 -1
  321. package/dist/server/routes/agent-intake.d.ts.map +1 -1
  322. package/dist/server/routes/agent-intake.js +10 -2
  323. package/dist/server/routes/agent-intake.js.map +1 -1
  324. package/dist/server/routes/agent-promotions.d.ts.map +1 -1
  325. package/dist/server/routes/agent-promotions.js +5 -2
  326. package/dist/server/routes/agent-promotions.js.map +1 -1
  327. package/dist/server/routes/agent-runs.d.ts.map +1 -1
  328. package/dist/server/routes/agent-runs.js +18 -4
  329. package/dist/server/routes/agent-runs.js.map +1 -1
  330. package/dist/server/routes/request-binding.d.ts +5 -0
  331. package/dist/server/routes/request-binding.d.ts.map +1 -0
  332. package/dist/server/routes/request-binding.js +49 -0
  333. package/dist/server/routes/request-binding.js.map +1 -0
  334. package/dist/server/routes/task5-support.d.ts +1 -8
  335. package/dist/server/routes/task5-support.d.ts.map +1 -1
  336. package/dist/server/routes/task5-support.js +1 -17
  337. package/dist/server/routes/task5-support.js.map +1 -1
  338. package/dist/server/routes/task5.d.ts.map +1 -1
  339. package/dist/server/routes/task5.js +106 -36
  340. package/dist/server/routes/task5.js.map +1 -1
  341. package/dist/server/write-queue.js +2 -2
  342. package/dist/server/write-queue.js.map +1 -1
  343. package/dist/setup/claude-config.d.ts +4 -1
  344. package/dist/setup/claude-config.d.ts.map +1 -1
  345. package/dist/setup/claude-config.js +62 -4
  346. package/dist/setup/claude-config.js.map +1 -1
  347. package/dist/setup/client-detection.d.ts +7 -2
  348. package/dist/setup/client-detection.d.ts.map +1 -1
  349. package/dist/setup/client-detection.js +27 -9
  350. package/dist/setup/client-detection.js.map +1 -1
  351. package/dist/setup/hermes-config.d.ts +4 -1
  352. package/dist/setup/hermes-config.d.ts.map +1 -1
  353. package/dist/setup/hermes-config.js +71 -17
  354. package/dist/setup/hermes-config.js.map +1 -1
  355. package/dist/setup/legacy-client-cleanup.d.ts +6 -0
  356. package/dist/setup/legacy-client-cleanup.d.ts.map +1 -0
  357. package/dist/setup/legacy-client-cleanup.js +152 -0
  358. package/dist/setup/legacy-client-cleanup.js.map +1 -0
  359. package/dist/setup/managed-text.d.ts.map +1 -1
  360. package/dist/setup/managed-text.js +11 -1
  361. package/dist/setup/managed-text.js.map +1 -1
  362. package/dist/setup/mcp-conflict.d.ts +4 -0
  363. package/dist/setup/mcp-conflict.d.ts.map +1 -0
  364. package/dist/setup/mcp-conflict.js +15 -0
  365. package/dist/setup/mcp-conflict.js.map +1 -0
  366. package/dist/setup/opencode-config.d.ts +4 -1
  367. package/dist/setup/opencode-config.d.ts.map +1 -1
  368. package/dist/setup/opencode-config.js +65 -5
  369. package/dist/setup/opencode-config.js.map +1 -1
  370. package/dist/setup/render.d.ts +4 -1
  371. package/dist/setup/render.d.ts.map +1 -1
  372. package/dist/setup/render.js +168 -18
  373. package/dist/setup/render.js.map +1 -1
  374. package/dist/setup/standard-skills.d.ts +16 -2
  375. package/dist/setup/standard-skills.d.ts.map +1 -1
  376. package/dist/setup/standard-skills.js +37 -13
  377. package/dist/setup/standard-skills.js.map +1 -1
  378. package/dist/setup/strict-json.d.ts +7 -0
  379. package/dist/setup/strict-json.d.ts.map +1 -0
  380. package/dist/setup/strict-json.js +98 -0
  381. package/dist/setup/strict-json.js.map +1 -0
  382. package/dist/setup/strict-toml.d.ts +18 -0
  383. package/dist/setup/strict-toml.d.ts.map +1 -0
  384. package/dist/setup/strict-toml.js +491 -0
  385. package/dist/setup/strict-toml.js.map +1 -0
  386. package/dist/skills/cache.d.ts +15 -0
  387. package/dist/skills/cache.d.ts.map +1 -0
  388. package/dist/skills/cache.js +19 -0
  389. package/dist/skills/cache.js.map +1 -0
  390. package/dist/skills/candidate-ranking.d.ts +15 -0
  391. package/dist/skills/candidate-ranking.d.ts.map +1 -0
  392. package/dist/skills/candidate-ranking.js +52 -0
  393. package/dist/skills/candidate-ranking.js.map +1 -0
  394. package/dist/skills/chunking.d.ts +11 -0
  395. package/dist/skills/chunking.d.ts.map +1 -0
  396. package/dist/skills/chunking.js +291 -0
  397. package/dist/skills/chunking.js.map +1 -0
  398. package/dist/skills/config.d.ts +19 -0
  399. package/dist/skills/config.d.ts.map +1 -0
  400. package/dist/skills/config.js +103 -0
  401. package/dist/skills/config.js.map +1 -0
  402. package/dist/skills/discovery-service.d.ts +26 -0
  403. package/dist/skills/discovery-service.d.ts.map +1 -0
  404. package/dist/skills/discovery-service.js +921 -0
  405. package/dist/skills/discovery-service.js.map +1 -0
  406. package/dist/skills/external-transport.d.ts +2 -0
  407. package/dist/skills/external-transport.d.ts.map +1 -0
  408. package/dist/skills/external-transport.js +30 -0
  409. package/dist/skills/external-transport.js.map +1 -0
  410. package/dist/skills/find.d.ts +16 -0
  411. package/dist/skills/find.d.ts.map +1 -0
  412. package/dist/skills/find.js +58 -0
  413. package/dist/skills/find.js.map +1 -0
  414. package/dist/skills/gap-detection.d.ts +23 -0
  415. package/dist/skills/gap-detection.d.ts.map +1 -0
  416. package/dist/skills/gap-detection.js +62 -0
  417. package/dist/skills/gap-detection.js.map +1 -0
  418. package/dist/skills/import-preparation.d.ts +4 -0
  419. package/dist/skills/import-preparation.d.ts.map +1 -0
  420. package/dist/skills/import-preparation.js +20 -0
  421. package/dist/skills/import-preparation.js.map +1 -0
  422. package/dist/skills/materialization-authority.d.ts +11 -0
  423. package/dist/skills/materialization-authority.d.ts.map +1 -0
  424. package/dist/skills/materialization-authority.js +92 -0
  425. package/dist/skills/materialization-authority.js.map +1 -0
  426. package/dist/skills/materialization-service.d.ts +22 -0
  427. package/dist/skills/materialization-service.d.ts.map +1 -0
  428. package/dist/skills/materialization-service.js +101 -0
  429. package/dist/skills/materialization-service.js.map +1 -0
  430. package/dist/skills/official-catalog.d.ts +31 -0
  431. package/dist/skills/official-catalog.d.ts.map +1 -0
  432. package/dist/skills/official-catalog.js +110 -0
  433. package/dist/skills/official-catalog.js.map +1 -0
  434. package/dist/skills/providers/schema.d.ts +30 -0
  435. package/dist/skills/providers/schema.d.ts.map +1 -0
  436. package/dist/skills/providers/schema.js +354 -0
  437. package/dist/skills/providers/schema.js.map +1 -0
  438. package/dist/skills/providers/skills-sh-compat.d.ts +17 -0
  439. package/dist/skills/providers/skills-sh-compat.d.ts.map +1 -0
  440. package/dist/skills/providers/skills-sh-compat.js +71 -0
  441. package/dist/skills/providers/skills-sh-compat.js.map +1 -0
  442. package/dist/skills/providers/skills-sh-v1.d.ts +23 -0
  443. package/dist/skills/providers/skills-sh-v1.d.ts.map +1 -0
  444. package/dist/skills/providers/skills-sh-v1.js +134 -0
  445. package/dist/skills/providers/skills-sh-v1.js.map +1 -0
  446. package/dist/skills/query-builder.d.ts +18 -0
  447. package/dist/skills/query-builder.d.ts.map +1 -0
  448. package/dist/skills/query-builder.js +93 -0
  449. package/dist/skills/query-builder.js.map +1 -0
  450. package/dist/skills/source/errors.d.ts +9 -0
  451. package/dist/skills/source/errors.d.ts.map +1 -0
  452. package/dist/skills/source/errors.js +15 -0
  453. package/dist/skills/source/errors.js.map +1 -0
  454. package/dist/skills/source/frontmatter.d.ts +9 -0
  455. package/dist/skills/source/frontmatter.d.ts.map +1 -0
  456. package/dist/skills/source/frontmatter.js +77 -0
  457. package/dist/skills/source/frontmatter.js.map +1 -0
  458. package/dist/skills/source/github-fetcher.d.ts +20 -0
  459. package/dist/skills/source/github-fetcher.d.ts.map +1 -0
  460. package/dist/skills/source/github-fetcher.js +292 -0
  461. package/dist/skills/source/github-fetcher.js.map +1 -0
  462. package/dist/skills/source/snapshot-validator.d.ts +28 -0
  463. package/dist/skills/source/snapshot-validator.d.ts.map +1 -0
  464. package/dist/skills/source/snapshot-validator.js +186 -0
  465. package/dist/skills/source/snapshot-validator.js.map +1 -0
  466. package/dist/skills/store.d.ts +181 -0
  467. package/dist/skills/store.d.ts.map +1 -0
  468. package/dist/skills/store.js +1710 -0
  469. package/dist/skills/store.js.map +1 -0
  470. package/dist/skills/types.d.ts +169 -0
  471. package/dist/skills/types.d.ts.map +1 -0
  472. package/dist/skills/types.js +2 -0
  473. package/dist/skills/types.js.map +1 -0
  474. package/dist/web/i18n.d.ts +32 -0
  475. package/dist/web/i18n.d.ts.map +1 -1
  476. package/dist/web/i18n.js +128 -0
  477. package/dist/web/i18n.js.map +1 -1
  478. package/dist/web/server.d.ts.map +1 -1
  479. package/dist/web/server.js +386 -38
  480. package/dist/web/server.js.map +1 -1
  481. package/dist/web/ui.d.ts +4 -0
  482. package/dist/web/ui.d.ts.map +1 -1
  483. package/dist/web/ui.js +382 -6
  484. package/dist/web/ui.js.map +1 -1
  485. package/migrations/009_external_skill_discovery.sql +126 -0
  486. package/package.json +6 -3
  487. package/skills/kiokuko-single-purpose-functions/SKILL.md +288 -0
  488. package/skills/kiokuko-single-purpose-functions/references/kiokuko-patterns.md +344 -0
  489. package/skills/kiokuko-single-purpose-functions/references/review-checklist.md +97 -0
  490. package/skills/kiokuko-ui-design-soul/SKILL.md +1186 -28
  491. package/templates/AGENTS.md +8 -7
  492. package/dist/client/runtime-discovery.d.ts +0 -15
  493. package/dist/client/runtime-discovery.d.ts.map +0 -1
  494. package/dist/client/runtime-discovery.js +0 -45
  495. package/dist/client/runtime-discovery.js.map +0 -1
  496. package/dist/commands/call.d.ts +0 -7
  497. package/dist/commands/call.d.ts.map +0 -1
  498. package/dist/commands/call.js +0 -10
  499. package/dist/commands/call.js.map +0 -1
  500. package/dist/commands/link.d.ts +0 -6
  501. package/dist/commands/link.d.ts.map +0 -1
  502. package/dist/commands/link.js +0 -6
  503. package/dist/commands/link.js.map +0 -1
  504. package/dist/commands/promote.d.ts +0 -6
  505. package/dist/commands/promote.d.ts.map +0 -1
  506. package/dist/commands/promote.js +0 -6
  507. package/dist/commands/promote.js.map +0 -1
  508. package/dist/commands/read.d.ts +0 -6
  509. package/dist/commands/read.d.ts.map +0 -1
  510. package/dist/commands/read.js +0 -4
  511. package/dist/commands/read.js.map +0 -1
  512. package/dist/commands/recall.d.ts +0 -6
  513. package/dist/commands/recall.d.ts.map +0 -1
  514. package/dist/commands/recall.js +0 -4
  515. package/dist/commands/recall.js.map +0 -1
  516. package/dist/commands/record.d.ts +0 -6
  517. package/dist/commands/record.d.ts.map +0 -1
  518. package/dist/commands/record.js +0 -4
  519. package/dist/commands/record.js.map +0 -1
  520. package/dist/commands/search.d.ts +0 -6
  521. package/dist/commands/search.d.ts.map +0 -1
  522. package/dist/commands/search.js +0 -4
  523. package/dist/commands/search.js.map +0 -1
  524. package/dist/commands/supersede.d.ts +0 -6
  525. package/dist/commands/supersede.d.ts.map +0 -1
  526. package/dist/commands/supersede.js +0 -6
  527. package/dist/commands/supersede.js.map +0 -1
  528. package/dist/knowledge/sources.d.ts +0 -72
  529. package/dist/knowledge/sources.d.ts.map +0 -1
  530. package/dist/knowledge/sources.js +0 -388
  531. package/dist/knowledge/sources.js.map +0 -1
  532. package/dist/setup/opencode-evidence.d.ts +0 -37
  533. package/dist/setup/opencode-evidence.d.ts.map +0 -1
  534. package/dist/setup/opencode-evidence.js +0 -58
  535. package/dist/setup/opencode-evidence.js.map +0 -1
  536. package/dist/setup/opencode-loop-guard.d.ts +0 -9
  537. package/dist/setup/opencode-loop-guard.d.ts.map +0 -1
  538. package/dist/setup/opencode-loop-guard.js +0 -248
  539. package/dist/setup/opencode-loop-guard.js.map +0 -1
@@ -0,0 +1,1710 @@
1
+ import { rollbackFailedTransaction, withImmediateTransaction } from '../db/transaction.js';
2
+ import { canonicalContentHash, canonicalEntryRevisionContentHash, canonicalJson, compareCanonicalStrings } from '../serialization/validate.js';
3
+ import { KiokukoError } from '../errors.js';
4
+ import { buildStructuredScope, validateApplicability, validateSignals } from '../memory/structured-memory.js';
5
+ import { readEntry, recordEntryInTransaction, updateManagedExternalEntryInTransaction } from '../memory/entries.js';
6
+ import { cacheKey, parseSkillCandidates, SkillProviderError } from './providers/schema.js';
7
+ import { requirementForOfficialSkill, reviewedCatalogSkill } from './official-catalog.js';
8
+ import { SkillSourceError } from './source/errors.js';
9
+ import { canonicalSkillSlugFromPrimaryPath, parseGitHubSource, revalidateSkillSnapshot, validateSkillCandidate } from './source/snapshot-validator.js';
10
+ import { documentsFromSkillSnapshot } from './import-preparation.js';
11
+ import { findSecret } from '../memory/secrets.js';
12
+ import { validSkillFrontmatterName } from './source/frontmatter.js';
13
+ import { claimSkillMaterializationAuthorization } from './materialization-authority.js';
14
+ export function externalSkillSourceFetchRequest(detail) {
15
+ const materialized = detail.skill.sourceCommit !== null || detail.skill.snapshotHash !== null;
16
+ if (!materialized) {
17
+ if (detail.entries.length !== 0)
18
+ integrity('Unmaterialized external skill has stored mappings');
19
+ return { purpose: 'discovery' };
20
+ }
21
+ if (detail.skill.sourceCommit === null || detail.skill.snapshotHash === null)
22
+ integrity('External skill snapshot identity is incomplete');
23
+ const primary = detail.entries.filter((entry) => entry.primary);
24
+ if (primary.length !== 1)
25
+ integrity('External skill primary mapping is invalid');
26
+ return { purpose: 'refresh', expectedPrimaryPath: primary[0].sourcePath };
27
+ }
28
+ function invalidSkillDiscoveryCache() {
29
+ throw new KiokukoError('INTEGRITY_ERROR', 'Skill discovery cache is invalid');
30
+ }
31
+ function canonicalStoredJson(value, onInvalid) {
32
+ try {
33
+ return canonicalJson(value);
34
+ }
35
+ catch (error) {
36
+ if (error instanceof RangeError
37
+ || (error instanceof KiokukoError && error.code === 'VALIDATION_ERROR'))
38
+ onInvalid();
39
+ throw error;
40
+ }
41
+ }
42
+ function canonicalIsoTimestamp(value) {
43
+ if (typeof value !== 'string')
44
+ return false;
45
+ const timestamp = Date.parse(value);
46
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
47
+ }
48
+ function validateStoredSkillSearchCacheRow(value) {
49
+ if (typeof value.cache_key !== 'string' || !/^[0-9a-f]{64}$/u.test(value.cache_key)
50
+ || typeof value.provider !== 'string' || typeof value.query_text !== 'string'
51
+ || value.owner !== null && typeof value.owner !== 'string'
52
+ || value.mode !== 'official' && value.mode !== 'community'
53
+ || typeof value.response_json !== 'string' || typeof value.outcome !== 'string'
54
+ || !['success', 'empty', 'rate_limited', 'unavailable'].includes(value.outcome)
55
+ || !canonicalIsoTimestamp(value.fetched_at) || !canonicalIsoTimestamp(value.expires_at)
56
+ || value.fetched_at > value.expires_at)
57
+ invalidSkillDiscoveryCache();
58
+ let expectedKey;
59
+ try {
60
+ expectedKey = cacheKey({ provider: value.provider, query: value.query_text, owner: value.owner, mode: value.mode });
61
+ }
62
+ catch (error) {
63
+ if (error instanceof SkillProviderError || error instanceof KiokukoError)
64
+ invalidSkillDiscoveryCache();
65
+ throw error;
66
+ }
67
+ if (value.cache_key !== expectedKey)
68
+ invalidSkillDiscoveryCache();
69
+ let parsed;
70
+ try {
71
+ parsed = JSON.parse(value.response_json);
72
+ }
73
+ catch (error) {
74
+ if (error instanceof SyntaxError || error instanceof RangeError)
75
+ invalidSkillDiscoveryCache();
76
+ throw error;
77
+ }
78
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
79
+ invalidSkillDiscoveryCache();
80
+ const object = parsed;
81
+ const allowedFields = new Set(['provider', 'experimental', 'candidates', 'failureCode']);
82
+ if (Object.keys(object).some((field) => !allowedFields.has(field))
83
+ || canonicalStoredJson(object, invalidSkillDiscoveryCache) !== value.response_json
84
+ || object.provider !== value.provider || typeof object.experimental !== 'boolean'
85
+ || object.failureCode !== undefined && object.failureCode !== 'registry_authentication_failed')
86
+ invalidSkillDiscoveryCache();
87
+ let candidates;
88
+ try {
89
+ candidates = parseSkillCandidates(object.candidates, value.provider, value.query_text === '__curated__' ? 'curated' : 'search');
90
+ }
91
+ catch (error) {
92
+ if (error instanceof SkillProviderError && error.code === 'registry_invalid_response')
93
+ invalidSkillDiscoveryCache();
94
+ throw error;
95
+ }
96
+ if ((value.outcome === 'success') !== (candidates.length > 0)
97
+ || object.failureCode === 'registry_authentication_failed' && (value.outcome !== 'unavailable' || candidates.length !== 0)) {
98
+ invalidSkillDiscoveryCache();
99
+ }
100
+ }
101
+ function storedSourceFailureCacheIdentity(value) {
102
+ if (value.source_type !== 'github' || typeof value.source_locator !== 'string' || typeof value.slug !== 'string'
103
+ || !/^[A-Za-z0-9_.\-/]{1,240}$/u.test(value.slug)
104
+ || value.slug.split('/').some((part) => part === '' || part === '.' || part === '..')
105
+ || findSecret(`${value.source_locator}/${value.slug}`) !== undefined)
106
+ invalidSkillDiscoveryCache();
107
+ let sourceLocator;
108
+ try {
109
+ sourceLocator = parseGitHubSource(value.source_locator).source;
110
+ }
111
+ catch (error) {
112
+ if (error instanceof SkillSourceError)
113
+ invalidSkillDiscoveryCache();
114
+ throw error;
115
+ }
116
+ if (sourceLocator !== value.source_locator)
117
+ invalidSkillDiscoveryCache();
118
+ const sourceType = value.source_type;
119
+ const slug = value.slug;
120
+ return { key: canonicalContentHash({ sourceType, sourceLocator, slug }), sourceType, sourceLocator, slug };
121
+ }
122
+ function validateStoredSkillSourceFailureCacheRow(value) {
123
+ const identity = storedSourceFailureCacheIdentity(value);
124
+ if (value.cache_key !== identity.key || typeof value.outcome !== 'string'
125
+ || !['source_rate_limited', 'source_unavailable'].includes(value.outcome)
126
+ || !canonicalIsoTimestamp(value.fetched_at) || !canonicalIsoTimestamp(value.expires_at)
127
+ || value.fetched_at > value.expires_at)
128
+ invalidSkillDiscoveryCache();
129
+ }
130
+ function validateStoredSkillAuditFailureCacheRow(value) {
131
+ const source = storedSourceFailureCacheIdentity(value);
132
+ if (typeof value.provider !== 'string' || !/^[A-Za-z0-9_.-]{1,100}$/u.test(value.provider)
133
+ || findSecret(value.provider) !== undefined
134
+ || value.cache_key !== canonicalContentHash({ provider: value.provider, sourceType: source.sourceType, sourceLocator: source.sourceLocator, slug: source.slug })
135
+ || typeof value.outcome !== 'string' || !['registry_rate_limited', 'registry_unavailable'].includes(value.outcome)
136
+ || !canonicalIsoTimestamp(value.fetched_at) || !canonicalIsoTimestamp(value.expires_at)
137
+ || value.fetched_at > value.expires_at)
138
+ invalidSkillDiscoveryCache();
139
+ }
140
+ function storedSearchCacheRow(database, input, key) {
141
+ const rows = database.prepare(`
142
+ SELECT cache_key, provider, query_text, owner, mode, response_json, outcome, fetched_at, expires_at
143
+ FROM skill_discovery_cache
144
+ WHERE cache_key = ?
145
+ OR (provider = ? AND query_text = ? AND owner IS ? AND mode = ?)
146
+ `).all(key, input.provider, input.query, input.owner ?? null, input.mode);
147
+ if (rows.length > 1)
148
+ invalidSkillDiscoveryCache();
149
+ const value = rows[0];
150
+ if (value !== undefined)
151
+ validateStoredSkillSearchCacheRow(value);
152
+ return value;
153
+ }
154
+ function storedSourceFailureCacheRow(database, identity) {
155
+ const rows = database.prepare(`
156
+ SELECT cache_key, source_type, source_locator, slug, outcome, fetched_at, expires_at
157
+ FROM skill_source_failure_cache
158
+ WHERE cache_key = ?
159
+ OR (source_type = ? AND source_locator = ? AND slug = ?)
160
+ `).all(identity.key, identity.sourceType, identity.sourceLocator, identity.slug);
161
+ if (rows.length > 1)
162
+ invalidSkillDiscoveryCache();
163
+ const value = rows[0];
164
+ if (value !== undefined)
165
+ validateStoredSkillSourceFailureCacheRow(value);
166
+ return value;
167
+ }
168
+ function storedAuditFailureCacheRow(database, identity) {
169
+ const rows = database.prepare(`
170
+ SELECT cache_key, provider, source_type, source_locator, slug, outcome, fetched_at, expires_at
171
+ FROM skill_audit_failure_cache
172
+ WHERE cache_key = ?
173
+ OR (provider = ? AND source_type = ? AND source_locator = ? AND slug = ?)
174
+ `).all(identity.key, identity.provider, identity.sourceType, identity.sourceLocator, identity.slug);
175
+ if (rows.length > 1)
176
+ invalidSkillDiscoveryCache();
177
+ const value = rows[0];
178
+ if (value !== undefined)
179
+ validateStoredSkillAuditFailureCacheRow(value);
180
+ return value;
181
+ }
182
+ export function readPersistentSkillSearchCache(database, input) {
183
+ const now = input.now ?? new Date().toISOString();
184
+ if (!canonicalIsoTimestamp(now))
185
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill discovery cache time is invalid');
186
+ const key = cacheKey(input);
187
+ const value = storedSearchCacheRow(database, input, key);
188
+ if (!value)
189
+ return null;
190
+ if (value.provider !== input.provider || value.query_text !== input.query || value.owner !== (input.owner ?? null) || value.mode !== input.mode
191
+ || typeof value.response_json !== 'string' || typeof value.outcome !== 'string'
192
+ || !canonicalIsoTimestamp(value.fetched_at) || !canonicalIsoTimestamp(value.expires_at)
193
+ || value.fetched_at > value.expires_at)
194
+ invalidSkillDiscoveryCache();
195
+ let parsed;
196
+ try {
197
+ parsed = JSON.parse(value.response_json);
198
+ }
199
+ catch (error) {
200
+ if (error instanceof SyntaxError || error instanceof RangeError)
201
+ invalidSkillDiscoveryCache();
202
+ throw error;
203
+ }
204
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
205
+ invalidSkillDiscoveryCache();
206
+ const object = parsed;
207
+ const allowedFields = new Set(['provider', 'experimental', 'candidates', 'failureCode']);
208
+ if (Object.keys(object).some((field) => !allowedFields.has(field))
209
+ || canonicalStoredJson(object, invalidSkillDiscoveryCache) !== value.response_json)
210
+ invalidSkillDiscoveryCache();
211
+ if (object.provider !== input.provider || typeof object.experimental !== 'boolean')
212
+ invalidSkillDiscoveryCache();
213
+ if (!['success', 'empty', 'rate_limited', 'unavailable'].includes(value.outcome))
214
+ invalidSkillDiscoveryCache();
215
+ if (object.failureCode !== undefined && object.failureCode !== 'registry_authentication_failed')
216
+ invalidSkillDiscoveryCache();
217
+ let candidates;
218
+ try {
219
+ candidates = parseSkillCandidates(object.candidates, input.provider, input.query === '__curated__' ? 'curated' : 'search');
220
+ }
221
+ catch (error) {
222
+ if (error instanceof SkillProviderError && error.code === 'registry_invalid_response')
223
+ invalidSkillDiscoveryCache();
224
+ throw error;
225
+ }
226
+ if ((value.outcome === 'success') !== (candidates.length > 0))
227
+ invalidSkillDiscoveryCache();
228
+ if (object.failureCode === 'registry_authentication_failed' && (value.outcome !== 'unavailable' || candidates.length !== 0))
229
+ invalidSkillDiscoveryCache();
230
+ if (value.expires_at <= now)
231
+ return null;
232
+ return {
233
+ provider: input.provider,
234
+ experimental: object.experimental,
235
+ candidates,
236
+ cacheOutcome: value.outcome,
237
+ ...(object.failureCode === 'registry_authentication_failed' ? { failureCode: object.failureCode } : {}),
238
+ };
239
+ }
240
+ export function writePersistentSkillSearchCache(database, input) {
241
+ if (input.result.provider !== input.provider || typeof input.result.experimental !== 'boolean'
242
+ || Object.prototype.hasOwnProperty.call(input.result, 'cached')
243
+ || !['success', 'empty', 'rate_limited', 'unavailable'].includes(input.outcome)) {
244
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill search cache result is invalid');
245
+ }
246
+ let candidates;
247
+ try {
248
+ candidates = parseSkillCandidates(input.result.candidates, input.provider, input.query === '__curated__' ? 'curated' : 'search');
249
+ }
250
+ catch (error) {
251
+ if (error instanceof SkillProviderError && error.code === 'registry_invalid_response') {
252
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill search cache result is invalid');
253
+ }
254
+ throw error;
255
+ }
256
+ if (input.failureCode !== undefined && input.failureCode !== 'registry_authentication_failed'
257
+ || (input.outcome === 'success') !== (candidates.length > 0)
258
+ || input.failureCode === 'registry_authentication_failed' && (input.outcome !== 'unavailable' || candidates.length !== 0)) {
259
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill search cache failure is invalid');
260
+ }
261
+ const now = input.now ?? new Date().toISOString();
262
+ if (!canonicalIsoTimestamp(now) || !Number.isSafeInteger(input.ttlMs) || input.ttlMs < 1)
263
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill discovery cache time is invalid');
264
+ const expiresAt = Date.parse(now) + input.ttlMs;
265
+ if (!Number.isFinite(expiresAt) || Math.abs(expiresAt) > 8.64e15)
266
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill discovery cache time is invalid');
267
+ const key = cacheKey(input);
268
+ const expires = new Date(expiresAt).toISOString();
269
+ storedSearchCacheRow(database, input, key);
270
+ const response = { provider: input.result.provider, experimental: input.result.experimental, candidates, ...(input.failureCode === undefined ? {} : { failureCode: input.failureCode }) };
271
+ database.prepare('INSERT INTO skill_discovery_cache (cache_key, provider, query_text, owner, mode, outcome, response_json, fetched_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(cache_key) DO UPDATE SET outcome = excluded.outcome, response_json = excluded.response_json, fetched_at = excluded.fetched_at, expires_at = excluded.expires_at').run(key, input.provider, input.query, input.owner ?? null, input.mode, input.outcome, canonicalJson(response), now, expires);
272
+ }
273
+ function sourceFailureCacheIdentity(candidate) {
274
+ validateCandidateForStorage(candidate);
275
+ const sourceType = candidate.sourceType.normalize('NFKC').toLowerCase();
276
+ const sourceLocator = candidate.source.normalize('NFKC').toLowerCase();
277
+ // Repository names are canonicalized, but repository paths are
278
+ // case-sensitive. `Foo/SKILL.md` and `foo/SKILL.md` are distinct sources.
279
+ const slug = candidate.slug;
280
+ return { key: canonicalContentHash({ sourceType, sourceLocator, slug }), sourceType, sourceLocator, slug };
281
+ }
282
+ export function readPersistentSkillSourceFailure(database, candidate, now = new Date().toISOString()) {
283
+ if (!canonicalIsoTimestamp(now))
284
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill source cache time is invalid');
285
+ const identity = sourceFailureCacheIdentity(candidate);
286
+ const value = storedSourceFailureCacheRow(database, identity);
287
+ if (value === undefined)
288
+ return null;
289
+ if (value.source_type !== identity.sourceType || value.source_locator !== identity.sourceLocator || value.slug !== identity.slug
290
+ || typeof value.outcome !== 'string' || !['source_rate_limited', 'source_unavailable'].includes(value.outcome)
291
+ || !canonicalIsoTimestamp(value.fetched_at) || !canonicalIsoTimestamp(value.expires_at)
292
+ || value.fetched_at > value.expires_at) {
293
+ throw new KiokukoError('INTEGRITY_ERROR', 'Skill source failure cache is invalid');
294
+ }
295
+ if (value.expires_at <= now)
296
+ return null;
297
+ return { code: value.outcome, fetchedAt: value.fetched_at, expiresAt: value.expires_at };
298
+ }
299
+ export function writePersistentSkillSourceFailure(database, candidate, code, ttlMs, now = new Date().toISOString()) {
300
+ const identity = sourceFailureCacheIdentity(candidate);
301
+ if (!['source_rate_limited', 'source_unavailable'].includes(code)
302
+ || !canonicalIsoTimestamp(now) || !Number.isSafeInteger(ttlMs) || ttlMs < 1) {
303
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill source failure cache is invalid');
304
+ }
305
+ const expiresAt = Date.parse(now) + ttlMs;
306
+ if (!Number.isFinite(expiresAt) || Math.abs(expiresAt) > 8.64e15)
307
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill source cache time is invalid');
308
+ storedSourceFailureCacheRow(database, identity);
309
+ database.prepare(`
310
+ INSERT INTO skill_source_failure_cache
311
+ (cache_key, source_type, source_locator, slug, outcome, fetched_at, expires_at)
312
+ VALUES (?, ?, ?, ?, ?, ?, ?)
313
+ ON CONFLICT(cache_key) DO UPDATE SET
314
+ source_type = excluded.source_type,
315
+ source_locator = excluded.source_locator,
316
+ slug = excluded.slug,
317
+ outcome = excluded.outcome,
318
+ fetched_at = excluded.fetched_at,
319
+ expires_at = excluded.expires_at
320
+ `).run(identity.key, identity.sourceType, identity.sourceLocator, identity.slug, code, now, new Date(expiresAt).toISOString());
321
+ }
322
+ export function clearPersistentSkillSourceFailure(database, candidate) {
323
+ const identity = sourceFailureCacheIdentity(candidate);
324
+ storedSourceFailureCacheRow(database, identity);
325
+ database.prepare('DELETE FROM skill_source_failure_cache WHERE cache_key = ?').run(identity.key);
326
+ }
327
+ function auditFailureCacheIdentity(provider, candidate) {
328
+ const source = sourceFailureCacheIdentity(candidate);
329
+ if (!/^[A-Za-z0-9_.-]{1,100}$/u.test(provider) || findSecret(provider) !== undefined) {
330
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill audit failure cache is invalid');
331
+ }
332
+ return {
333
+ key: canonicalContentHash({ provider, sourceType: source.sourceType, sourceLocator: source.sourceLocator, slug: source.slug }),
334
+ provider,
335
+ sourceType: source.sourceType,
336
+ sourceLocator: source.sourceLocator,
337
+ slug: source.slug,
338
+ };
339
+ }
340
+ export function readPersistentSkillAuditFailure(database, provider, candidate, now = new Date().toISOString()) {
341
+ if (!canonicalIsoTimestamp(now))
342
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill audit cache time is invalid');
343
+ const identity = auditFailureCacheIdentity(provider, candidate);
344
+ const value = storedAuditFailureCacheRow(database, identity);
345
+ if (value === undefined)
346
+ return null;
347
+ if (value.provider !== identity.provider || value.source_type !== identity.sourceType
348
+ || value.source_locator !== identity.sourceLocator || value.slug !== identity.slug
349
+ || typeof value.outcome !== 'string' || !['registry_rate_limited', 'registry_unavailable'].includes(value.outcome)
350
+ || !canonicalIsoTimestamp(value.fetched_at) || !canonicalIsoTimestamp(value.expires_at)
351
+ || value.fetched_at > value.expires_at) {
352
+ throw new KiokukoError('INTEGRITY_ERROR', 'Skill audit failure cache is invalid');
353
+ }
354
+ if (value.expires_at <= now)
355
+ return null;
356
+ return { code: value.outcome, fetchedAt: value.fetched_at, expiresAt: value.expires_at };
357
+ }
358
+ export function writePersistentSkillAuditFailure(database, provider, candidate, code, ttlMs, now = new Date().toISOString()) {
359
+ const identity = auditFailureCacheIdentity(provider, candidate);
360
+ if (!['registry_rate_limited', 'registry_unavailable'].includes(code)
361
+ || !canonicalIsoTimestamp(now) || !Number.isSafeInteger(ttlMs) || ttlMs < 1) {
362
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill audit failure cache is invalid');
363
+ }
364
+ const expiresAt = Date.parse(now) + ttlMs;
365
+ if (!Number.isFinite(expiresAt) || Math.abs(expiresAt) > 8.64e15)
366
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill audit cache time is invalid');
367
+ storedAuditFailureCacheRow(database, identity);
368
+ database.prepare(`
369
+ INSERT INTO skill_audit_failure_cache
370
+ (cache_key, provider, source_type, source_locator, slug, outcome, fetched_at, expires_at)
371
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
372
+ ON CONFLICT(cache_key) DO UPDATE SET
373
+ provider = excluded.provider,
374
+ source_type = excluded.source_type,
375
+ source_locator = excluded.source_locator,
376
+ slug = excluded.slug,
377
+ outcome = excluded.outcome,
378
+ fetched_at = excluded.fetched_at,
379
+ expires_at = excluded.expires_at
380
+ `).run(identity.key, identity.provider, identity.sourceType, identity.sourceLocator, identity.slug, code, now, new Date(expiresAt).toISOString());
381
+ }
382
+ export function clearPersistentSkillAuditFailure(database, provider, candidate) {
383
+ const identity = auditFailureCacheIdentity(provider, candidate);
384
+ storedAuditFailureCacheRow(database, identity);
385
+ database.prepare('DELETE FROM skill_audit_failure_cache WHERE cache_key = ?').run(identity.key);
386
+ }
387
+ export function pruneExternalSkillCaches(database, now = new Date().toISOString()) {
388
+ if (!canonicalIsoTimestamp(now))
389
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill cache prune time is invalid');
390
+ return withImmediateTransaction(database, () => {
391
+ const discoveryRows = database.prepare(`
392
+ SELECT cache_key, provider, query_text, owner, mode, response_json, outcome, fetched_at, expires_at
393
+ FROM skill_discovery_cache
394
+ ORDER BY cache_key
395
+ `).all();
396
+ const sourceRows = database.prepare(`
397
+ SELECT cache_key, source_type, source_locator, slug, outcome, fetched_at, expires_at
398
+ FROM skill_source_failure_cache
399
+ ORDER BY cache_key
400
+ `).all();
401
+ const auditRows = database.prepare(`
402
+ SELECT cache_key, provider, source_type, source_locator, slug, outcome, fetched_at, expires_at
403
+ FROM skill_audit_failure_cache
404
+ ORDER BY cache_key
405
+ `).all();
406
+ discoveryRows.forEach(validateStoredSkillSearchCacheRow);
407
+ sourceRows.forEach(validateStoredSkillSourceFailureCacheRow);
408
+ auditRows.forEach(validateStoredSkillAuditFailureCacheRow);
409
+ database.prepare('DELETE FROM skill_discovery_cache WHERE expires_at <= ?').run(now);
410
+ const discovery = Number(database.prepare('SELECT changes() AS count').get()?.count ?? 0);
411
+ database.prepare('DELETE FROM skill_source_failure_cache WHERE expires_at <= ?').run(now);
412
+ const sourceFailures = Number(database.prepare('SELECT changes() AS count').get()?.count ?? 0);
413
+ database.prepare('DELETE FROM skill_audit_failure_cache WHERE expires_at <= ?').run(now);
414
+ const auditFailures = Number(database.prepare('SELECT changes() AS count').get()?.count ?? 0);
415
+ if (!Number.isSafeInteger(discovery) || discovery < 0 || !Number.isSafeInteger(sourceFailures) || sourceFailures < 0
416
+ || !Number.isSafeInteger(auditFailures) || auditFailures < 0) {
417
+ invalidSkillDiscoveryCache();
418
+ }
419
+ return { discovery, sourceFailures, auditFailures, total: discovery + sourceFailures + auditFailures };
420
+ });
421
+ }
422
+ function safePart(value) { return value.replace(/[^A-Za-z0-9_.-]/gu, '-'); }
423
+ export function externalSkillWorkspace(candidate) {
424
+ const prefix = `external-skills:${candidate.sourceType}:`;
425
+ const source = safePart(candidate.source);
426
+ const available = 240 - prefix.length;
427
+ const suffix = canonicalContentHash({ sourceType: candidate.sourceType, source: candidate.source.toLowerCase() }).slice(0, 16);
428
+ const maxSource = available - suffix.length - 1;
429
+ return `${prefix}${source.slice(0, maxSource)}-${suffix}`;
430
+ }
431
+ function sourceReference(candidate, commit, path) { return `https://github.com/${candidate.source}/blob/${commit}/${path.split('/').map(encodeURIComponent).join('/')}`; }
432
+ function candidateId(candidate) { return `${candidate.sourceType}:${candidate.source.toLowerCase()}:${candidate.slug}`; }
433
+ function sameSkillSource(record, candidate) {
434
+ return record.sourceType.toLowerCase() === candidate.sourceType.toLowerCase()
435
+ && record.sourceLocator.toLowerCase() === candidate.source.toLowerCase();
436
+ }
437
+ export function parseExternalSkillLocator(identifier) {
438
+ if (identifier !== identifier.trim() || identifier.includes('?') || identifier.includes('#') || identifier.includes('\\')) {
439
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill identifier must be an owner/repository/skill path');
440
+ }
441
+ if (identifier.includes('://'))
442
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill identifier must be an owner/repository/skill path');
443
+ const parts = identifier.split('/');
444
+ if (parts.length < 3 || parts.some((part) => part.length === 0)) {
445
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill identifier must be owner/repository/skill');
446
+ }
447
+ let source;
448
+ try {
449
+ source = parseGitHubSource(`${parts[0]}/${parts[1]}`).source;
450
+ }
451
+ catch (error) {
452
+ if (error instanceof SkillSourceError)
453
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill identifier contains an unsafe path');
454
+ throw error;
455
+ }
456
+ const slug = parts.slice(2).join('/');
457
+ if (!/^[A-Za-z0-9_.\-/]{1,240}$/u.test(slug)
458
+ || slug.split('/').some((part) => part === '.' || part === '..')) {
459
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill identifier contains an unsafe path');
460
+ }
461
+ return { source, slug };
462
+ }
463
+ export function resolveExternalSkillIdentifier(database, identifier) {
464
+ const exact = row(database, identifier);
465
+ if (exact !== undefined)
466
+ return exact;
467
+ if (identifier.includes('://')) {
468
+ throw new KiokukoError('VALIDATION_ERROR', 'Skill identifier must be an owner/repository/skill path');
469
+ }
470
+ if (identifier.includes(':')) {
471
+ throw new KiokukoError('NOT_FOUND', 'External skill not found');
472
+ }
473
+ const locator = parseExternalSkillLocator(identifier);
474
+ const matches = database.prepare(`SELECT skill_id FROM external_skills WHERE source_locator = ? AND slug = ? ORDER BY skill_id`)
475
+ .all(locator.source, locator.slug);
476
+ if (matches.length === 0)
477
+ throw new KiokukoError('NOT_FOUND', 'External skill not found');
478
+ if (matches.length > 1)
479
+ throw new KiokukoError('CONFLICT', 'External skill identifier is ambiguous; use the internal skill ID');
480
+ const resolved = row(database, matches[0].skill_id);
481
+ if (resolved === undefined)
482
+ throw new KiokukoError('INTEGRITY_ERROR', 'External skill row disappeared during identifier resolution');
483
+ return resolved;
484
+ }
485
+ function validateCandidateForStorage(candidate) {
486
+ let validated;
487
+ try {
488
+ validated = validateSkillCandidate(candidate);
489
+ }
490
+ catch (error) {
491
+ if (error instanceof SkillSourceError)
492
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill candidate is invalid');
493
+ throw error;
494
+ }
495
+ // The source validator canonicalizes repository casing for fetches. Durable
496
+ // rows must already contain that canonical value rather than accepting an
497
+ // alternate spelling that later resolves to the same repository.
498
+ if (validated.source !== candidate.source) {
499
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill candidate is invalid');
500
+ }
501
+ }
502
+ function integrity(message) {
503
+ throw new KiokukoError('INTEGRITY_ERROR', message);
504
+ }
505
+ function assertExternalSkillGenerationClock(database) {
506
+ const clock = database.prepare('SELECT value FROM external_skill_generation_clock WHERE singleton = 1')
507
+ .get();
508
+ const tokens = database.prepare('SELECT COUNT(*) AS count, MAX(generation) AS maximum FROM external_skill_generation_tokens')
509
+ .get();
510
+ const sequence = database.prepare("SELECT seq FROM sqlite_sequence WHERE name = 'external_skill_generation_tokens'")
511
+ .get();
512
+ const live = database.prepare('SELECT COUNT(*) AS count, MAX(generation) AS maximum FROM external_skills')
513
+ .get();
514
+ const missingTokens = database.prepare(`
515
+ SELECT COUNT(*) AS count
516
+ FROM external_skills AS skill
517
+ LEFT JOIN external_skill_generation_tokens AS token ON token.generation = skill.generation
518
+ WHERE token.generation IS NULL
519
+ `).get()?.count;
520
+ const unreferencedTokens = database.prepare(`
521
+ SELECT COUNT(*) AS count
522
+ FROM external_skill_generation_tokens AS token
523
+ LEFT JOIN external_skills AS skill ON skill.generation = token.generation
524
+ WHERE skill.generation IS NULL
525
+ `).get()?.count;
526
+ const tokenMaximum = tokens?.maximum === null ? 0 : tokens?.maximum;
527
+ const sequenceValue = sequence === undefined ? 0 : sequence.seq;
528
+ const liveMaximum = live?.maximum === null ? 0 : live?.maximum;
529
+ if (clock === undefined || typeof clock.value !== 'number' || !Number.isSafeInteger(clock.value)
530
+ || clock.value < 0 || clock.value >= Number.MAX_SAFE_INTEGER
531
+ || tokens === undefined || typeof tokens.count !== 'number' || !Number.isSafeInteger(tokens.count) || tokens.count < 0
532
+ || typeof tokenMaximum !== 'number' || !Number.isSafeInteger(tokenMaximum) || tokenMaximum < 0
533
+ || typeof sequenceValue !== 'number' || !Number.isSafeInteger(sequenceValue) || sequenceValue < 0
534
+ || live === undefined || typeof live.count !== 'number' || !Number.isSafeInteger(live.count) || live.count < 0
535
+ || typeof liveMaximum !== 'number' || !Number.isSafeInteger(liveMaximum) || liveMaximum < 0
536
+ || typeof missingTokens !== 'number' || missingTokens !== 0
537
+ || typeof unreferencedTokens !== 'number' || unreferencedTokens !== 0
538
+ || tokens.count !== live.count || tokenMaximum > clock.value || liveMaximum > clock.value
539
+ || sequenceValue !== clock.value) {
540
+ integrity('External skill generation allocator is invalid or exhausted');
541
+ }
542
+ return clock.value;
543
+ }
544
+ /** Read the durable mutation token used to bind external-skill list snapshots. */
545
+ export function externalSkillListVersion(database) {
546
+ return assertExternalSkillGenerationClock(database);
547
+ }
548
+ function requiredMetadataText(value) {
549
+ if (typeof value !== 'string' || value.trim().length < 1 || value.length > 500
550
+ || /[\p{Cc}\p{Cf}]/u.test(value) || findSecret(value) !== undefined) {
551
+ integrity('External skill metadata is invalid');
552
+ }
553
+ const normalized = value.normalize('NFKC').trim();
554
+ if (normalized !== value)
555
+ integrity('External skill metadata is invalid');
556
+ return normalized;
557
+ }
558
+ function requirementAliasesMetadata(value) {
559
+ if (!Array.isArray(value) || value.length < 1 || value.length > 100
560
+ || value.some((item) => typeof item !== 'string' || item.trim().length < 1 || item.length > 500 || /[\p{Cc}\p{Cf}]/u.test(item) || findSecret(item) !== undefined)) {
561
+ integrity('External skill metadata is invalid');
562
+ }
563
+ const normalized = [...new Set(value.map((item) => String(item).normalize('NFKC').trim()))]
564
+ .sort(compareCanonicalStrings);
565
+ if (normalized.length < 1 || normalized.length !== value.length
566
+ || normalized.some((item, index) => item !== value[index]))
567
+ integrity('External skill metadata is invalid');
568
+ return normalized;
569
+ }
570
+ function currentMappingsMetadata(value) {
571
+ if (!Array.isArray(value) || value.length < 1 || value.length > 64)
572
+ integrity('External skill metadata is invalid');
573
+ const seen = new Set();
574
+ return value.map((item) => {
575
+ if (typeof item !== 'object' || item === null || Array.isArray(item))
576
+ integrity('External skill metadata is invalid');
577
+ const mapping = item;
578
+ if (Object.keys(mapping).some((key) => key !== 'sourcePath' && key !== 'chunkIndex')
579
+ || typeof mapping.sourcePath !== 'string' || mapping.sourcePath.length < 1 || mapping.sourcePath.length > 2_000
580
+ || mapping.sourcePath.startsWith('/') || mapping.sourcePath.includes('\\') || mapping.sourcePath.includes('\0')
581
+ || mapping.sourcePath.split('/').some((part) => part === '' || part === '.' || part === '..')
582
+ || typeof mapping.chunkIndex !== 'number' || !Number.isSafeInteger(mapping.chunkIndex) || mapping.chunkIndex < 0) {
583
+ integrity('External skill metadata is invalid');
584
+ }
585
+ const key = `${mapping.sourcePath}\u0000${mapping.chunkIndex}`;
586
+ if (seen.has(key))
587
+ integrity('External skill metadata is invalid');
588
+ seen.add(key);
589
+ return { sourcePath: mapping.sourcePath, chunkIndex: mapping.chunkIndex };
590
+ });
591
+ }
592
+ function materializedSkillMetadata(metadata) {
593
+ const fields = new Set(['applicability', 'auditStatus', 'currentMappings', 'documents', 'frontmatter', 'officialStatus', 'requirementAliases', 'signals', 'technology']);
594
+ const keys = Object.keys(metadata);
595
+ if (keys.length !== fields.size || keys.some((key) => !fields.has(key)))
596
+ integrity('External skill metadata is invalid');
597
+ const auditStatus = metadata.auditStatus;
598
+ if (typeof auditStatus !== 'string' || !['not-required', 'passed', 'failed', 'unavailable'].includes(auditStatus))
599
+ integrity('External skill metadata is invalid');
600
+ const officialStatus = metadata.officialStatus;
601
+ if (typeof officialStatus !== 'string' || !['curated', 'catalog-verified', 'owner-verified', 'registry-only', 'unknown'].includes(officialStatus))
602
+ integrity('External skill metadata is invalid');
603
+ let applicability;
604
+ let signals;
605
+ try {
606
+ applicability = validateApplicability(metadata.applicability);
607
+ signals = validateSignals(metadata.signals);
608
+ }
609
+ catch (error) {
610
+ if (error instanceof KiokukoError && (error.code === 'VALIDATION_ERROR' || error.code === 'SECURITY_REJECTION'))
611
+ integrity('External skill metadata is invalid');
612
+ throw error;
613
+ }
614
+ const currentMappings = currentMappingsMetadata(metadata.currentMappings);
615
+ const rawFrontmatter = metadata.frontmatter;
616
+ if (typeof rawFrontmatter !== 'object' || rawFrontmatter === null || Array.isArray(rawFrontmatter))
617
+ integrity('External skill frontmatter is invalid');
618
+ const frontmatterObject = rawFrontmatter;
619
+ if (Object.keys(frontmatterObject).length !== 3
620
+ || Object.keys(frontmatterObject).some((key) => !['name', 'description', 'disableModelInvocation'].includes(key))
621
+ || !validSkillFrontmatterName(frontmatterObject.name) || findSecret(frontmatterObject.name) !== undefined
622
+ || frontmatterObject.description !== null && (typeof frontmatterObject.description !== 'string' || frontmatterObject.description.length > 2_000
623
+ || /[\p{Cc}\p{Cf}\p{Cs}\uFFFD]/u.test(frontmatterObject.description) || findSecret(frontmatterObject.description) !== undefined)
624
+ || frontmatterObject.disableModelInvocation !== false)
625
+ integrity('External skill frontmatter is invalid');
626
+ const frontmatter = {
627
+ name: frontmatterObject.name,
628
+ description: frontmatterObject.description,
629
+ disableModelInvocation: false,
630
+ };
631
+ const documents = metadata.documents;
632
+ if (typeof documents !== 'number' || !Number.isSafeInteger(documents) || documents < 1 || documents > 64 || documents !== currentMappings.length) {
633
+ integrity('External skill metadata is invalid');
634
+ }
635
+ if (!Object.values(applicability).some((value) => Array.isArray(value) && value.length > 0))
636
+ integrity('External skill applicability is missing');
637
+ if (canonicalContentHash(applicability) !== canonicalContentHash(metadata.applicability)
638
+ || canonicalContentHash(signals) !== canonicalContentHash(metadata.signals))
639
+ integrity('External skill metadata is invalid');
640
+ return {
641
+ documents,
642
+ frontmatter,
643
+ technology: requiredMetadataText(metadata.technology),
644
+ requirementAliases: requirementAliasesMetadata(metadata.requirementAliases),
645
+ applicability,
646
+ signals,
647
+ currentMappings,
648
+ auditStatus: auditStatus,
649
+ officialStatus: officialStatus,
650
+ };
651
+ }
652
+ function storedText(value) {
653
+ if (typeof value !== 'string' || value.length < 1)
654
+ integrity('External skill row is invalid');
655
+ return value;
656
+ }
657
+ function decodeExternalSkillRow(value) {
658
+ if (typeof value.metadata_json !== 'string')
659
+ integrity('External skill metadata is invalid');
660
+ let metadata;
661
+ try {
662
+ metadata = JSON.parse(value.metadata_json);
663
+ }
664
+ catch (error) {
665
+ if (error instanceof SyntaxError || error instanceof RangeError)
666
+ integrity('External skill metadata is invalid');
667
+ throw error;
668
+ }
669
+ if (typeof metadata !== 'object' || metadata === null || Array.isArray(metadata))
670
+ integrity('External skill metadata is invalid');
671
+ if (canonicalStoredJson(metadata, () => integrity('External skill metadata is invalid')) !== value.metadata_json) {
672
+ integrity('External skill metadata is not canonical');
673
+ }
674
+ const sourceCommit = value.source_commit === null ? null : storedText(value.source_commit);
675
+ const snapshotHash = value.snapshot_hash === null ? null : storedText(value.snapshot_hash);
676
+ if ((sourceCommit === null) !== (snapshotHash === null))
677
+ integrity('External skill snapshot identity is invalid');
678
+ if (sourceCommit !== null && (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(sourceCommit) || !/^[0-9a-f]{64}$/u.test(snapshotHash))) {
679
+ integrity('External skill snapshot identity is invalid');
680
+ }
681
+ if (sourceCommit === null && Object.keys(metadata).length !== 0)
682
+ integrity('External skill metadata is invalid');
683
+ const materialized = sourceCommit === null ? undefined : materializedSkillMetadata(metadata);
684
+ const storedSkillId = storedText(value.skill_id);
685
+ const generation = value.generation;
686
+ if (typeof generation !== 'number' || !Number.isSafeInteger(generation) || generation < 1) {
687
+ integrity('External skill generation is invalid');
688
+ }
689
+ const provider = storedText(value.provider);
690
+ const sourceType = storedText(value.source_type);
691
+ const sourceLocator = storedText(value.source_locator);
692
+ const slug = storedText(value.slug);
693
+ const name = storedText(value.name);
694
+ const installUrl = value.install_url === null ? null : storedText(value.install_url);
695
+ const officialStatus = storedText(value.official_status);
696
+ if (typeof value.duplicate !== 'number' || ![0, 1].includes(value.duplicate)
697
+ || typeof value.installs !== 'number' || !Number.isSafeInteger(value.installs))
698
+ integrity('External skill row is invalid');
699
+ const state = storedText(value.state);
700
+ const sourceWorkspace = storedText(value.source_workspace);
701
+ const disabledAt = value.disabled_at === null ? null : storedText(value.disabled_at);
702
+ const firstSeenAt = storedText(value.first_seen_at);
703
+ const lastSeenAt = storedText(value.last_seen_at);
704
+ const lastCheckedAt = storedText(value.last_checked_at);
705
+ if (!canonicalIsoTimestamp(firstSeenAt) || !canonicalIsoTimestamp(lastSeenAt) || !canonicalIsoTimestamp(lastCheckedAt)
706
+ || disabledAt !== null && !canonicalIsoTimestamp(disabledAt)
707
+ || firstSeenAt > lastSeenAt || firstSeenAt > lastCheckedAt
708
+ || disabledAt !== null && disabledAt < firstSeenAt) {
709
+ integrity('External skill lifecycle timestamps are invalid');
710
+ }
711
+ const candidate = {
712
+ id: `${provider}:${sourceLocator}:${slug}`,
713
+ provider,
714
+ name,
715
+ slug,
716
+ source: sourceLocator,
717
+ sourceType: sourceType,
718
+ installUrl,
719
+ installs: value.installs,
720
+ duplicate: value.duplicate === 1,
721
+ officialStatus: officialStatus,
722
+ ...(materialized === undefined ? {} : { auditStatus: materialized.auditStatus }),
723
+ };
724
+ try {
725
+ validateCandidateForStorage(candidate);
726
+ }
727
+ catch (error) {
728
+ if (error instanceof KiokukoError && error.code === 'VALIDATION_ERROR')
729
+ integrity('External skill row is invalid');
730
+ throw error;
731
+ }
732
+ if (materialized !== undefined && materialized.auditStatus !== (reviewedCatalogSkill(candidate) === undefined ? 'passed' : 'not-required')) {
733
+ integrity('External skill materialization trust is invalid');
734
+ }
735
+ if (storedSkillId !== candidateId(candidate) || sourceWorkspace !== externalSkillWorkspace(candidate)
736
+ || !['discovered', 'imported', 'disabled', 'stale', 'blocked'].includes(state)
737
+ || (sourceCommit === null && (state === 'imported' || state === 'disabled' || disabledAt !== null))
738
+ || (sourceCommit !== null && !['imported', 'disabled', 'stale', 'blocked'].includes(state))
739
+ || (state === 'imported' && disabledAt !== null)
740
+ || (state === 'disabled' && disabledAt === null)
741
+ || (materialized !== undefined && (materialized.officialStatus !== officialStatus || materialized.frontmatter.name !== name))) {
742
+ integrity('External skill row is invalid');
743
+ }
744
+ const auditStatus = materialized?.auditStatus
745
+ ?? (reviewedCatalogSkill(candidate) === undefined ? 'unavailable' : 'not-required');
746
+ return { skillId: storedSkillId, provider, sourceType, sourceLocator, slug, name, installUrl, officialStatus, duplicate: value.duplicate === 1, installs: value.installs, state, sourceWorkspace, sourceCommit, snapshotHash, metadata, auditStatus, generation, firstSeenAt, lastSeenAt, lastCheckedAt, disabledAt };
747
+ }
748
+ function row(database, skillId) {
749
+ assertExternalSkillGenerationClock(database);
750
+ const value = database.prepare('SELECT * FROM external_skills WHERE skill_id = ?').get(skillId);
751
+ if (!value)
752
+ return undefined;
753
+ return decodeExternalSkillRow(value);
754
+ }
755
+ /** Capture the exact row generation required by refresh and discovery CAS operations. */
756
+ export function externalSkillRefreshExpectation(record) {
757
+ return {
758
+ generation: record.generation,
759
+ sourceCommit: record.sourceCommit,
760
+ snapshotHash: record.snapshotHash,
761
+ state: record.state,
762
+ lastCheckedAt: record.lastCheckedAt,
763
+ };
764
+ }
765
+ function mergedStrings(left, right) {
766
+ const values = [...new Set([...(left ?? []), ...(right ?? [])])].sort(compareCanonicalStrings);
767
+ return values.length === 0 ? undefined : values;
768
+ }
769
+ function mergeSkillRequirements(left, right) {
770
+ const frameworks = [...new Map([...(left.applicability.frameworks ?? []), ...(right.applicability.frameworks ?? [])]
771
+ .map((item) => [`${item.name}\u0000${item.version ?? ''}`, item])).values()]
772
+ .sort((a, b) => compareCanonicalStrings(`${a.name}\u0000${a.version ?? ''}`, `${b.name}\u0000${b.version ?? ''}`));
773
+ const applicability = validateApplicability({
774
+ ...(mergedStrings(left.applicability.languages, right.applicability.languages) === undefined ? {} : { languages: mergedStrings(left.applicability.languages, right.applicability.languages) }),
775
+ ...(frameworks.length === 0 ? {} : { frameworks }),
776
+ ...(mergedStrings(left.applicability.databases, right.applicability.databases) === undefined ? {} : { databases: mergedStrings(left.applicability.databases, right.applicability.databases) }),
777
+ ...(mergedStrings(left.applicability.runtimes, right.applicability.runtimes) === undefined ? {} : { runtimes: mergedStrings(left.applicability.runtimes, right.applicability.runtimes) }),
778
+ ...(mergedStrings(left.applicability.tools, right.applicability.tools) === undefined ? {} : { tools: mergedStrings(left.applicability.tools, right.applicability.tools) }),
779
+ });
780
+ const packages = mergedStrings(left.signals.packages, right.signals.packages);
781
+ const [technology] = [left.technology, right.technology].sort(compareCanonicalStrings);
782
+ return {
783
+ id: technology, technology: technology,
784
+ aliases: mergedStrings([left.id, left.technology, ...left.aliases], [right.id, right.technology, ...right.aliases]) ?? [technology],
785
+ queries: mergedStrings(left.queries, right.queries) ?? [technology],
786
+ owners: mergedStrings(left.owners, right.owners) ?? [],
787
+ repositories: mergedStrings(left.repositories, right.repositories) ?? [],
788
+ applicability,
789
+ signals: packages === undefined ? {} : { packages },
790
+ reason: 'Merged applicability for one managed external skill.',
791
+ };
792
+ }
793
+ function requirementScopeIdentity(requirement, identities) {
794
+ return requirement === undefined ? null : canonicalContentHash({ technology: requirement.technology, identities, applicability: requirement.applicability, signals: requirement.signals });
795
+ }
796
+ function managedRequirementScopeHash(requirement) {
797
+ if (requirement === undefined)
798
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill applicability is required');
799
+ const identities = [...new Set([requirement.id, requirement.technology, ...requirement.aliases])]
800
+ .sort(compareCanonicalStrings);
801
+ return requirementScopeIdentity(requirement, identities);
802
+ }
803
+ function requirementFromMaterializedSkill(skill, metadata) {
804
+ return {
805
+ id: metadata.technology,
806
+ technology: metadata.technology,
807
+ aliases: metadata.requirementAliases,
808
+ queries: [metadata.technology],
809
+ owners: [skill.sourceLocator.split('/')[0] ?? skill.sourceLocator],
810
+ repositories: [skill.sourceLocator],
811
+ applicability: metadata.applicability,
812
+ signals: metadata.signals,
813
+ reason: 'Applicability persisted with the imported external skill snapshot.',
814
+ };
815
+ }
816
+ /** Read the complete applicability persisted at the materialization boundary. */
817
+ export function externalSkillRequirement(database, skillId) {
818
+ const skill = row(database, skillId);
819
+ if (!skill || skill.sourceCommit === null)
820
+ return undefined;
821
+ assertCurrentManagedSnapshot(database, skill);
822
+ const requirement = requirementFromMaterializedSkill(skill, materializedSkillMetadata(skill.metadata));
823
+ return requirement;
824
+ }
825
+ function normalizedSkillRequirement(requirement) {
826
+ const text = (value) => {
827
+ if (typeof value !== 'string' || value.length < 1 || value.length > 500 || value !== value.normalize('NFKC').trim()
828
+ || /[\p{Cc}\p{Cf}]/u.test(value) || findSecret(value) !== undefined) {
829
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill requirement is invalid');
830
+ }
831
+ return value;
832
+ };
833
+ if (!Array.isArray(requirement.aliases) || requirement.aliases.length < 1 || requirement.aliases.length > 100) {
834
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill requirement is invalid');
835
+ }
836
+ const normalized = {
837
+ ...requirement,
838
+ id: text(requirement.id),
839
+ technology: text(requirement.technology),
840
+ aliases: [...new Set(requirement.aliases.map(text))].sort(compareCanonicalStrings),
841
+ applicability: validateApplicability(requirement.applicability),
842
+ signals: validateSignals(requirement.signals),
843
+ };
844
+ if (!Object.values(normalized.applicability).some((value) => Array.isArray(value) && value.length > 0)) {
845
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill applicability is required');
846
+ }
847
+ return normalized;
848
+ }
849
+ function allocateExternalSkillGeneration(database) {
850
+ const previous = assertExternalSkillGenerationClock(database);
851
+ const allocated = database.prepare('INSERT INTO external_skill_generation_tokens DEFAULT VALUES RETURNING generation')
852
+ .get();
853
+ if (allocated === undefined || typeof allocated.generation !== 'number'
854
+ || !Number.isSafeInteger(allocated.generation) || allocated.generation !== previous + 1) {
855
+ integrity('External skill generation allocator is invalid or exhausted');
856
+ }
857
+ database.prepare('UPDATE external_skill_generation_clock SET value = ? WHERE singleton = 1 AND value = ?')
858
+ .run(allocated.generation, previous);
859
+ const changed = database.prepare('SELECT changes() AS count').get()?.count;
860
+ if (changed !== 1)
861
+ integrity('External skill generation allocator is invalid or exhausted');
862
+ return allocated.generation;
863
+ }
864
+ function pruneExternalSkillGenerationTokens(database) {
865
+ database.prepare('DELETE FROM external_skill_generation_tokens WHERE generation NOT IN (SELECT generation FROM external_skills)').run();
866
+ }
867
+ function upsertCandidate(database, candidate, now) {
868
+ validateCandidateForStorage(candidate);
869
+ const skillId = candidateId(candidate);
870
+ const workspace = externalSkillWorkspace(candidate);
871
+ // Validate an existing row before the upsert can overwrite and accidentally
872
+ // conceal corrupted identity or materialization metadata.
873
+ row(database, skillId);
874
+ const generation = allocateExternalSkillGeneration(database);
875
+ database.prepare(`
876
+ INSERT INTO external_skills (
877
+ skill_id, generation, provider, source_type, source_locator, slug, name,
878
+ install_url, official_status, duplicate, installs, state,
879
+ source_workspace, first_seen_at, last_seen_at, last_checked_at
880
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'discovered', ?, ?, ?, ?)
881
+ ON CONFLICT(skill_id) DO UPDATE SET
882
+ generation = excluded.generation,
883
+ provider = CASE WHEN external_skills.source_commit IS NULL THEN excluded.provider ELSE external_skills.provider END,
884
+ source_type = CASE WHEN external_skills.source_commit IS NULL THEN excluded.source_type ELSE external_skills.source_type END,
885
+ source_locator = CASE WHEN external_skills.source_commit IS NULL THEN excluded.source_locator ELSE external_skills.source_locator END,
886
+ slug = CASE WHEN external_skills.source_commit IS NULL THEN excluded.slug ELSE external_skills.slug END,
887
+ name = CASE WHEN external_skills.source_commit IS NULL THEN excluded.name ELSE external_skills.name END,
888
+ install_url = CASE WHEN external_skills.source_commit IS NULL THEN excluded.install_url ELSE external_skills.install_url END,
889
+ official_status = CASE WHEN external_skills.source_commit IS NULL THEN excluded.official_status ELSE external_skills.official_status END,
890
+ duplicate = CASE WHEN external_skills.source_commit IS NULL THEN excluded.duplicate ELSE external_skills.duplicate END,
891
+ installs = CASE WHEN external_skills.source_commit IS NULL THEN excluded.installs ELSE external_skills.installs END,
892
+ source_workspace = CASE WHEN external_skills.source_commit IS NULL THEN excluded.source_workspace ELSE external_skills.source_workspace END,
893
+ last_seen_at = excluded.last_seen_at,
894
+ last_checked_at = CASE WHEN external_skills.source_commit IS NULL THEN excluded.last_checked_at ELSE external_skills.last_checked_at END
895
+ `).run(skillId, generation, candidate.provider, candidate.sourceType, candidate.source, candidate.slug, candidate.name, candidate.installUrl, candidate.officialStatus, candidate.duplicate ? 1 : 0, candidate.installs, workspace, now, now, now);
896
+ pruneExternalSkillGenerationTokens(database);
897
+ return skillId;
898
+ }
899
+ function authorizedPreparedSkillImport(input, authorization) {
900
+ return { ...input, skill: claimSkillMaterializationAuthorization(input.skill, authorization) };
901
+ }
902
+ function inputForDocument(input, document, now) {
903
+ const scope = buildStructuredScope({ visibility: 'project', retrievalScope: 'ecosystem', memoryClass: 'reference', applicability: input.requirement?.applicability ?? {}, signals: input.requirement?.signals ?? {} });
904
+ const provenance = {
905
+ type: 'external_skill',
906
+ reference: sourceReference(input.skill, input.sourceCommit, document.sourcePath),
907
+ externalSkillId: candidateId(input.skill),
908
+ requirementScopeHash: managedRequirementScopeHash(input.requirement),
909
+ sourceRepositoryId: `github:${input.skill.source}`,
910
+ sourceWorkspace: input.sourceWorkspace,
911
+ sourceCommit: input.sourceCommit,
912
+ sourcePath: document.sourcePath,
913
+ sourceChunkIndex: document.chunkIndex,
914
+ timestamp: now,
915
+ };
916
+ const tags = [...new Set(['external:skill', `provider:${input.skill.provider}`, `source:${input.skill.source}`, `skill:${input.skill.slug.split('/').at(-1) ?? input.skill.slug}`, `official:${input.skill.officialStatus}`, `technology:${input.requirement?.technology ?? input.skill.name}`])]
917
+ .sort(compareCanonicalStrings);
918
+ return { workspace: input.sourceWorkspace, kind: 'reference', status: 'candidate', title: document.title, body: document.body, summary: document.summary, scope, provenance, trustLevel: 'untrusted', confidence: 0.7, tags, createdBy: 'kiokuko-skill-discovery', actor: 'kiokuko-skill-discovery' };
919
+ }
920
+ function validatePreparedFrontmatter(input) {
921
+ const value = input.frontmatter;
922
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
923
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill frontmatter is invalid');
924
+ const object = value;
925
+ if (Object.keys(object).length !== 3 || Object.keys(object).some((key) => !['name', 'description', 'disableModelInvocation'].includes(key))
926
+ || object.name !== input.skill.name
927
+ || !validSkillFrontmatterName(object.name) || findSecret(object.name) !== undefined
928
+ || object.description !== null && (typeof object.description !== 'string' || object.description.length > 2_000 || /[\p{Cc}\p{Cf}\p{Cs}\uFFFD]/u.test(object.description) || findSecret(object.description) !== undefined)
929
+ || object.disableModelInvocation !== false
930
+ || input.documents.some((document) => document.summary !== object.description)) {
931
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill frontmatter is invalid');
932
+ }
933
+ const primaryPath = input.documents.find((document) => document.primary)?.sourcePath;
934
+ if (primaryPath === 'SKILL.md' && (input.skill.slug.includes('/') || input.skill.slug !== object.name || input.skill.name !== object.name)) {
935
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill root identity is invalid');
936
+ }
937
+ }
938
+ function managedRecordContentHash(input) {
939
+ return canonicalEntryRevisionContentHash({
940
+ kind: input.kind,
941
+ title: input.title,
942
+ body: input.body,
943
+ summary: input.summary ?? null,
944
+ scope: input.scope,
945
+ provenance: input.provenance,
946
+ tags: input.tags,
947
+ });
948
+ }
949
+ function candidateFromMaterializedSkill(skill, metadata) {
950
+ return {
951
+ id: `${skill.provider}:${skill.sourceLocator}:${skill.slug}`,
952
+ provider: skill.provider,
953
+ name: skill.name,
954
+ slug: skill.slug,
955
+ source: skill.sourceLocator,
956
+ sourceType: 'github',
957
+ installUrl: skill.installUrl,
958
+ installs: skill.installs,
959
+ duplicate: skill.duplicate,
960
+ officialStatus: metadata.officialStatus,
961
+ auditStatus: metadata.auditStatus,
962
+ };
963
+ }
964
+ function canonicalStoredSkillSlug(candidate, primaryPath) {
965
+ const reviewed = reviewedCatalogSkill(candidate);
966
+ if (reviewed !== undefined) {
967
+ if (!reviewed.primaryPaths.includes(primaryPath))
968
+ throw new SkillSourceError('skill_validation_failed');
969
+ return reviewed.slug;
970
+ }
971
+ return primaryPath === 'SKILL.md' ? candidate.slug : canonicalSkillSlugFromPrimaryPath(primaryPath);
972
+ }
973
+ function assertManagedEntryIdentity(database, entry, mapping, expected, requireCreatedAt = false) {
974
+ const revision = database.prepare(`
975
+ SELECT created_by, created_at
976
+ FROM entry_revisions
977
+ WHERE entry_id = ? AND workspace = ? AND revision = ?
978
+ `).get(entry.id, expected.workspace, mapping.entry_revision);
979
+ const expectedHash = managedRecordContentHash(expected);
980
+ if (entry.workspace !== expected.workspace
981
+ || entry.kind !== 'reference' || entry.status !== 'candidate'
982
+ || entry.trustLevel !== 'untrusted' || entry.confidence !== 0.7
983
+ || entry.title !== expected.title || entry.body !== expected.body || entry.summary !== (expected.summary ?? null)
984
+ || canonicalContentHash(entry.scope) !== canonicalContentHash(expected.scope)
985
+ || canonicalContentHash(entry.provenance) !== canonicalContentHash(expected.provenance)
986
+ || canonicalContentHash(entry.tags) !== canonicalContentHash(expected.tags)
987
+ || entry.contentHash !== expectedHash || entry.revision !== mapping.entry_revision
988
+ || entry.supersededBy !== null || entry.createdBy !== 'kiokuko-skill-discovery' || entry.verifiedAt !== null
989
+ || entry.updatedAt !== mapping.imported_at || (requireCreatedAt && entry.createdAt !== mapping.imported_at)
990
+ || revision?.created_by !== 'kiokuko-skill-discovery' || revision.created_at !== mapping.imported_at) {
991
+ integrity('Managed external skill entry is invalid');
992
+ }
993
+ }
994
+ function assertManagedEntryLifecycle(entry, mapping) {
995
+ if (entry.kind !== 'reference' || entry.status !== 'candidate' || entry.trustLevel !== 'untrusted'
996
+ || entry.confidence !== 0.7 || entry.createdBy !== 'kiokuko-skill-discovery'
997
+ || entry.verifiedAt !== null || entry.supersededBy !== null
998
+ || entry.revision !== mapping.entry_revision || entry.contentHash !== mapping.content_hash) {
999
+ integrity('Managed external skill entry is invalid');
1000
+ }
1001
+ }
1002
+ function assertManagedEntryParentIdentity(database, skill, candidate, entry, mapping) {
1003
+ const provenance = entry.provenance;
1004
+ const fields = new Set([
1005
+ 'type', 'reference', 'externalSkillId', 'requirementScopeHash', 'sourceRepositoryId',
1006
+ 'sourceWorkspace', 'sourceCommit', 'sourcePath',
1007
+ 'sourceChunkIndex', 'timestamp',
1008
+ ]);
1009
+ const sourceCommit = provenance.sourceCommit;
1010
+ const revision = database.prepare(`
1011
+ SELECT created_by, created_at
1012
+ FROM entry_revisions
1013
+ WHERE entry_id = ? AND workspace = ? AND revision = ?
1014
+ `).get(entry.id, skill.sourceWorkspace, mapping.entry_revision);
1015
+ if (Object.keys(provenance).length !== fields.size || Object.keys(provenance).some((field) => !fields.has(field))
1016
+ || provenance.type !== 'external_skill'
1017
+ || provenance.externalSkillId !== skill.skillId
1018
+ || provenance.requirementScopeHash === undefined || typeof provenance.requirementScopeHash !== 'string'
1019
+ || !/^[0-9a-f]{64}$/u.test(provenance.requirementScopeHash)
1020
+ || provenance.sourceRepositoryId !== `github:${skill.sourceLocator}`
1021
+ || provenance.sourceWorkspace !== skill.sourceWorkspace
1022
+ || typeof sourceCommit !== 'string' || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(sourceCommit)
1023
+ || provenance.sourcePath !== mapping.source_path
1024
+ || provenance.sourceChunkIndex !== mapping.chunk_index
1025
+ || provenance.reference !== sourceReference(candidate, sourceCommit, mapping.source_path)
1026
+ || provenance.timestamp !== mapping.imported_at
1027
+ || entry.updatedAt !== mapping.imported_at
1028
+ || revision?.created_by !== 'kiokuko-skill-discovery' || revision.created_at !== mapping.imported_at) {
1029
+ integrity('Managed external skill entry parent identity is invalid');
1030
+ }
1031
+ }
1032
+ function readMappedEntry(database, workspace, entryId) {
1033
+ try {
1034
+ return readEntry(database, { workspace, entryId });
1035
+ }
1036
+ catch (error) {
1037
+ if (error instanceof KiokukoError && error.code === 'NOT_FOUND')
1038
+ integrity('Managed external skill entry is missing');
1039
+ throw error;
1040
+ }
1041
+ }
1042
+ function storedExternalSkillMappings(database, skillId) {
1043
+ const mappings = database.prepare(`
1044
+ SELECT source_path, chunk_index, entry_id, entry_revision, content_hash,
1045
+ primary_document, active, imported_at
1046
+ FROM external_skill_entries
1047
+ WHERE skill_id = ?
1048
+ ORDER BY source_path, chunk_index
1049
+ `).all(skillId);
1050
+ const entryIds = new Set();
1051
+ return mappings.map((value) => {
1052
+ if (typeof value.source_path !== 'string' || value.source_path.length < 1 || value.source_path.length > 2_000
1053
+ || !/^[A-Za-z0-9_.\-/]+$/u.test(value.source_path) || value.source_path.startsWith('/')
1054
+ || value.source_path.split('/').some((part) => part === '' || part === '.' || part === '..')
1055
+ || typeof value.chunk_index !== 'number' || !Number.isSafeInteger(value.chunk_index) || value.chunk_index < 0
1056
+ || typeof value.entry_id !== 'string' || value.entry_id.length < 1 || value.entry_id.length > 500 || /[\p{Cc}\p{Cf}\p{Cs}\uFFFD]/u.test(value.entry_id)
1057
+ || typeof value.entry_revision !== 'number' || !Number.isSafeInteger(value.entry_revision) || value.entry_revision < 1
1058
+ || typeof value.content_hash !== 'string' || !/^[0-9a-f]{64}$/u.test(value.content_hash)
1059
+ || typeof value.primary_document !== 'number' || ![0, 1].includes(value.primary_document)
1060
+ || typeof value.active !== 'number' || ![0, 1].includes(value.active)
1061
+ || typeof value.imported_at !== 'string' || !canonicalIsoTimestamp(value.imported_at)
1062
+ || entryIds.has(value.entry_id)) {
1063
+ integrity('External skill mapping is invalid');
1064
+ }
1065
+ entryIds.add(value.entry_id);
1066
+ return value;
1067
+ });
1068
+ }
1069
+ function assertCurrentManagedSnapshot(database, skill, expectedDocuments) {
1070
+ if (skill.sourceCommit === null || skill.snapshotHash === null)
1071
+ integrity('External skill snapshot identity is invalid');
1072
+ const metadata = materializedSkillMetadata(skill.metadata);
1073
+ const candidate = candidateFromMaterializedSkill(skill, metadata);
1074
+ if (skill.sourceType !== 'github' || skill.sourceWorkspace !== externalSkillWorkspace(candidate))
1075
+ integrity('External skill snapshot identity is invalid');
1076
+ if (!['imported', 'disabled', 'stale', 'blocked'].includes(skill.state)
1077
+ || (skill.state === 'imported' && skill.disabledAt !== null)
1078
+ || (skill.state === 'disabled' && skill.disabledAt === null)) {
1079
+ integrity('External skill lifecycle is invalid');
1080
+ }
1081
+ const mappings = storedExternalSkillMappings(database, skill.skillId);
1082
+ const mappingByKey = new Map(mappings.map((mapping) => [`${mapping.source_path}\u0000${mapping.chunk_index}`, mapping]));
1083
+ const currentKeys = new Set(metadata.currentMappings.map((mapping) => `${mapping.sourcePath}\u0000${mapping.chunkIndex}`));
1084
+ const current = metadata.currentMappings.map((mapping) => mappingByKey.get(`${mapping.sourcePath}\u0000${mapping.chunkIndex}`));
1085
+ if (current.some((mapping) => mapping === undefined)
1086
+ || current.filter((mapping) => mapping?.primary_document === 1).length !== 1
1087
+ || current.some((mapping) => mapping?.primary_document !== 0 && mapping?.primary_document !== 1)
1088
+ || mappings.some((mapping) => !currentKeys.has(`${mapping.source_path}\u0000${mapping.chunk_index}`) && mapping.active !== 0)) {
1089
+ integrity('External skill current mapping is invalid');
1090
+ }
1091
+ const entriesByMapping = new Map();
1092
+ for (const mapping of mappings) {
1093
+ const entry = readMappedEntry(database, skill.sourceWorkspace, mapping.entry_id);
1094
+ assertManagedEntryLifecycle(entry, mapping);
1095
+ assertManagedEntryParentIdentity(database, skill, candidate, entry, mapping);
1096
+ entriesByMapping.set(`${mapping.source_path}\u0000${mapping.chunk_index}`, entry);
1097
+ }
1098
+ const expectedActive = skill.state === 'imported' ? 1 : 0;
1099
+ if (current.some((mapping) => mapping?.active !== expectedActive))
1100
+ integrity('External skill current mapping is invalid');
1101
+ const primaryMapping = current.find((mapping) => mapping?.primary_document === 1);
1102
+ try {
1103
+ if (canonicalStoredSkillSlug(candidate, primaryMapping.source_path) !== skill.slug)
1104
+ integrity('External skill current mapping is invalid');
1105
+ }
1106
+ catch (error) {
1107
+ if (error instanceof SkillSourceError || error instanceof KiokukoError && error.code === 'VALIDATION_ERROR')
1108
+ integrity('External skill current mapping is invalid');
1109
+ throw error;
1110
+ }
1111
+ if (expectedDocuments !== undefined) {
1112
+ const expectedByKey = new Map(expectedDocuments.map((document) => [`${document.sourcePath}\u0000${document.chunkIndex}`, document]));
1113
+ if (expectedByKey.size !== currentKeys.size || [...currentKeys].some((key) => !expectedByKey.has(key)))
1114
+ integrity('External skill current mapping is invalid');
1115
+ for (const mapping of current) {
1116
+ const document = expectedByKey.get(`${mapping.source_path}\u0000${mapping.chunk_index}`);
1117
+ if (mapping.primary_document !== (document.primary ? 1 : 0))
1118
+ integrity('External skill current mapping is invalid');
1119
+ }
1120
+ }
1121
+ const requirement = requirementFromMaterializedSkill(skill, metadata);
1122
+ const seenEntries = new Set();
1123
+ for (const mapping of current) {
1124
+ if (typeof mapping.source_path !== 'string' || typeof mapping.entry_id !== 'string' || typeof mapping.imported_at !== 'string'
1125
+ || !Number.isSafeInteger(mapping.chunk_index) || mapping.chunk_index < 0
1126
+ || !Number.isSafeInteger(mapping.entry_revision) || mapping.entry_revision < 1
1127
+ || !/^[0-9a-f]{64}$/u.test(mapping.content_hash)
1128
+ || seenEntries.has(mapping.entry_id)) {
1129
+ integrity('External skill current mapping is invalid');
1130
+ }
1131
+ seenEntries.add(mapping.entry_id);
1132
+ const entry = entriesByMapping.get(`${mapping.source_path}\u0000${mapping.chunk_index}`);
1133
+ const documentHash = canonicalContentHash({ title: entry.title, body: entry.body, sourcePath: mapping.source_path });
1134
+ const expectedDocument = expectedDocuments?.find((document) => document.sourcePath === mapping.source_path && document.chunkIndex === mapping.chunk_index);
1135
+ if (mapping.content_hash !== entry.contentHash
1136
+ || entry.summary !== metadata.frontmatter.description
1137
+ || expectedDocument !== undefined && (expectedDocument.contentHash !== documentHash
1138
+ || expectedDocument.title !== entry.title || expectedDocument.body !== entry.body
1139
+ || expectedDocument.summary !== entry.summary)) {
1140
+ integrity('External skill current mapping is invalid');
1141
+ }
1142
+ const expected = inputForDocument({
1143
+ skill: candidate,
1144
+ sourceWorkspace: skill.sourceWorkspace,
1145
+ sourceCommit: skill.sourceCommit,
1146
+ snapshotHash: skill.snapshotHash,
1147
+ frontmatter: metadata.frontmatter,
1148
+ documents: [],
1149
+ requirement,
1150
+ }, {
1151
+ sourcePath: mapping.source_path,
1152
+ chunkIndex: mapping.chunk_index,
1153
+ title: entry.title,
1154
+ body: entry.body,
1155
+ summary: entry.summary,
1156
+ contentHash: documentHash,
1157
+ primary: mapping.primary_document === 1,
1158
+ }, mapping.imported_at);
1159
+ assertManagedEntryIdentity(database, entry, mapping, expected);
1160
+ }
1161
+ }
1162
+ function validatePreparedDocuments(documents) {
1163
+ if (documents.length < 1 || documents.length > 64 || documents.filter((document) => document.primary).length !== 1)
1164
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill documents are invalid');
1165
+ const primaryPath = documents.find((document) => document.primary).sourcePath;
1166
+ if (primaryPath !== 'SKILL.md' && !primaryPath.endsWith('/SKILL.md'))
1167
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill documents are invalid');
1168
+ const root = primaryPath === 'SKILL.md' ? '' : primaryPath.slice(0, -'/SKILL.md'.length);
1169
+ const mappings = new Set();
1170
+ for (const document of documents) {
1171
+ const allowedPath = document.sourcePath === primaryPath
1172
+ || document.sourcePath.startsWith(root === '' ? 'references/' : `${root}/references/`) && /\.(?:md|txt)$/u.test(document.sourcePath)
1173
+ || document.sourcePath.startsWith(root === '' ? 'docs/' : `${root}/docs/`) && /\.md$/u.test(document.sourcePath);
1174
+ if (!Number.isSafeInteger(document.chunkIndex) || document.chunkIndex < 0
1175
+ || document.sourcePath.length < 1 || document.sourcePath.length > 2_000 || !/^[A-Za-z0-9_.\-/]+$/u.test(document.sourcePath) || document.sourcePath.startsWith('/') || document.sourcePath.split('/').some((part) => part === '' || part === '.' || part === '..')
1176
+ || !allowedPath
1177
+ || !/^[0-9a-f]{64}$/u.test(document.contentHash)
1178
+ || document.contentHash !== canonicalContentHash({ title: document.title, body: document.body, sourcePath: document.sourcePath }))
1179
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill documents are invalid');
1180
+ const key = `${document.sourcePath}\u0000${document.chunkIndex}`;
1181
+ if (mappings.has(key))
1182
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill document mapping is duplicated');
1183
+ mappings.add(key);
1184
+ }
1185
+ }
1186
+ function validatePreparedSkillIdentity(input) {
1187
+ if (input.sourceWorkspace !== externalSkillWorkspace(input.skill))
1188
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill import identity is invalid');
1189
+ const primary = input.documents.find((document) => document.primary);
1190
+ if (primary === undefined)
1191
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill import identity is invalid');
1192
+ let slug;
1193
+ try {
1194
+ slug = canonicalStoredSkillSlug(input.skill, primary.sourcePath);
1195
+ }
1196
+ catch (error) {
1197
+ if (error instanceof SkillSourceError)
1198
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill import identity is invalid');
1199
+ throw error;
1200
+ }
1201
+ if (input.skill.slug !== slug || input.skill.id !== `${input.skill.provider}:${input.skill.source}:${slug}`) {
1202
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill import identity is invalid');
1203
+ }
1204
+ }
1205
+ function persistSkillImportInTransaction(database, input, now = new Date().toISOString()) {
1206
+ validatePreparedSkillIdentity(input);
1207
+ validatePreparedFrontmatter(input);
1208
+ if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(input.sourceCommit)
1209
+ || !/^[0-9a-f]{64}$/u.test(input.snapshotHash))
1210
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill import identity is invalid');
1211
+ validatePreparedDocuments(input.documents);
1212
+ const previous = row(database, candidateId(input.skill));
1213
+ const skillId = upsertCandidate(database, input.skill, now);
1214
+ const current = row(database, skillId);
1215
+ if (!current)
1216
+ throw new KiokukoError('INTEGRITY_ERROR', 'External skill row disappeared during import');
1217
+ // disabled_at is the durable user intent. A failed refresh records stale/blocked
1218
+ // in state, but must not allow the next successful refresh to re-enable the skill.
1219
+ const preserveDisabled = current.disabledAt !== null;
1220
+ const storedMappingCount = Number(database.prepare('SELECT COUNT(*) AS count FROM external_skill_entries WHERE skill_id = ?').get(skillId)?.count ?? 0);
1221
+ if (current.sourceCommit === null && storedMappingCount !== 0)
1222
+ integrity('Unmaterialized external skill has stored mappings');
1223
+ const storedRequirement = current.sourceCommit === null
1224
+ ? undefined
1225
+ : requirementFromMaterializedSkill(current, materializedSkillMetadata(current.metadata));
1226
+ const requirementInput = input.requirement ?? storedRequirement ?? requirementForOfficialSkill(input.skill);
1227
+ if (requirementInput === undefined)
1228
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill applicability is required');
1229
+ const requestedRequirement = normalizedSkillRequirement(requirementInput);
1230
+ const effectiveRequirement = storedRequirement === undefined ? requestedRequirement
1231
+ : mergeSkillRequirements(storedRequirement, requestedRequirement);
1232
+ const storedRequirementIdentities = storedRequirement === undefined ? [] : requirementAliasesMetadata(current.metadata.requirementAliases);
1233
+ const effectiveRequirementIdentities = [...new Set([
1234
+ ...storedRequirementIdentities,
1235
+ effectiveRequirement.id,
1236
+ effectiveRequirement.technology,
1237
+ ...effectiveRequirement.aliases,
1238
+ ])].sort(compareCanonicalStrings);
1239
+ const effectiveInput = { ...input, requirement: effectiveRequirement };
1240
+ const exactReplay = current.snapshotHash === input.snapshotHash && current.state === 'imported'
1241
+ && requirementScopeIdentity(storedRequirement, storedRequirementIdentities) === requirementScopeIdentity(effectiveRequirement, effectiveRequirementIdentities);
1242
+ if (previous !== undefined && previous.sourceCommit !== null) {
1243
+ assertCurrentManagedSnapshot(database, previous, exactReplay ? input.documents : undefined);
1244
+ }
1245
+ if (exactReplay) {
1246
+ const entries = database.prepare('SELECT entry_id FROM external_skill_entries WHERE skill_id = ? AND active = 1 ORDER BY source_path, chunk_index').all(skillId).map(({ entry_id }) => readMappedEntry(database, input.sourceWorkspace, entry_id));
1247
+ return { skillId, imported: 0, updated: false, snapshotHash: input.snapshotHash, sourceWorkspace: input.sourceWorkspace, entries };
1248
+ }
1249
+ const currentMappingKeys = current.sourceCommit === null
1250
+ ? new Set()
1251
+ : new Set(materializedSkillMetadata(current.metadata).currentMappings.map((mapping) => `${mapping.sourcePath}\u0000${mapping.chunkIndex}`));
1252
+ const previousMappings = new Map(database.prepare(`
1253
+ SELECT source_path, chunk_index, entry_id, entry_revision, content_hash,
1254
+ primary_document, active, imported_at
1255
+ FROM external_skill_entries
1256
+ WHERE skill_id = ?
1257
+ `).all(skillId).map((mapping) => [`${mapping.source_path}\u0000${mapping.chunk_index}`, mapping]));
1258
+ database.prepare('UPDATE external_skill_entries SET active = 0 WHERE skill_id = ?').run(skillId);
1259
+ const entries = [];
1260
+ for (const document of input.documents) {
1261
+ const key = `${document.sourcePath}\u0000${document.chunkIndex}`;
1262
+ const recordInput = inputForDocument(effectiveInput, document, now);
1263
+ const previous = previousMappings.get(key);
1264
+ let entry;
1265
+ let mappingImportedAt = now;
1266
+ if (previous !== undefined) {
1267
+ const existingEntry = readMappedEntry(database, input.sourceWorkspace, previous.entry_id);
1268
+ assertManagedEntryLifecycle(existingEntry, previous);
1269
+ const reusableInput = inputForDocument(effectiveInput, document, previous.imported_at);
1270
+ if (currentMappingKeys.has(key) && previous.content_hash === managedRecordContentHash(reusableInput)) {
1271
+ assertManagedEntryIdentity(database, existingEntry, previous, reusableInput);
1272
+ entry = existingEntry;
1273
+ mappingImportedAt = previous.imported_at;
1274
+ }
1275
+ else {
1276
+ entry = updateManagedExternalEntryInTransaction(database, {
1277
+ workspace: input.sourceWorkspace,
1278
+ entryId: previous.entry_id,
1279
+ expectedRevision: previous.entry_revision,
1280
+ kind: recordInput.kind,
1281
+ title: recordInput.title,
1282
+ body: recordInput.body,
1283
+ summary: recordInput.summary,
1284
+ scope: recordInput.scope,
1285
+ provenance: recordInput.provenance,
1286
+ tags: recordInput.tags,
1287
+ createdBy: 'kiokuko-skill-discovery',
1288
+ actor: 'kiokuko-skill-discovery',
1289
+ now,
1290
+ });
1291
+ assertManagedEntryIdentity(database, entry, { entry_revision: entry.revision, imported_at: now }, recordInput);
1292
+ }
1293
+ }
1294
+ else {
1295
+ entry = recordEntryInTransaction(database, recordInput, { now });
1296
+ assertManagedEntryIdentity(database, entry, { entry_revision: entry.revision, imported_at: now }, recordInput, true);
1297
+ }
1298
+ const active = preserveDisabled ? 0 : 1;
1299
+ database.prepare(`INSERT INTO external_skill_entries (skill_id, source_path, chunk_index, entry_id, entry_revision, content_hash, primary_document, active, imported_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(skill_id, source_path, chunk_index) DO UPDATE SET entry_id = excluded.entry_id, entry_revision = excluded.entry_revision, content_hash = excluded.content_hash, primary_document = excluded.primary_document, active = excluded.active, imported_at = excluded.imported_at`).run(skillId, document.sourcePath, document.chunkIndex, entry.id, entry.revision, entry.contentHash, document.primary ? 1 : 0, active, mappingImportedAt);
1300
+ entries.push(entry);
1301
+ }
1302
+ const metadata = {
1303
+ documents: input.documents.length,
1304
+ frontmatter: input.frontmatter,
1305
+ currentMappings: input.documents.map((document) => ({ sourcePath: document.sourcePath, chunkIndex: document.chunkIndex })),
1306
+ technology: effectiveRequirement.technology,
1307
+ requirementAliases: effectiveRequirementIdentities,
1308
+ auditStatus: input.skill.auditStatus ?? current.auditStatus,
1309
+ officialStatus: input.skill.officialStatus,
1310
+ applicability: effectiveRequirement.applicability,
1311
+ signals: effectiveRequirement.signals,
1312
+ };
1313
+ const generation = allocateExternalSkillGeneration(database);
1314
+ database.prepare(`
1315
+ UPDATE external_skills
1316
+ SET generation = ?,
1317
+ provider = ?,
1318
+ source_type = ?,
1319
+ source_locator = ?,
1320
+ slug = ?,
1321
+ name = ?,
1322
+ install_url = ?,
1323
+ official_status = ?,
1324
+ duplicate = ?,
1325
+ installs = ?,
1326
+ state = ?,
1327
+ source_commit = ?,
1328
+ snapshot_hash = ?,
1329
+ source_workspace = ?,
1330
+ last_checked_at = ?,
1331
+ metadata_json = ?
1332
+ WHERE skill_id = ?
1333
+ `).run(generation, input.skill.provider, input.skill.sourceType, input.skill.source, input.skill.slug, input.skill.name, input.skill.installUrl, input.skill.officialStatus, input.skill.duplicate ? 1 : 0, input.skill.installs, preserveDisabled ? 'disabled' : 'imported', input.sourceCommit, input.snapshotHash, input.sourceWorkspace, now, canonicalJson(metadata), skillId);
1334
+ pruneExternalSkillGenerationTokens(database);
1335
+ const stored = row(database, skillId);
1336
+ if (stored === undefined)
1337
+ integrity('External skill row disappeared during import');
1338
+ assertCurrentManagedSnapshot(database, stored, input.documents);
1339
+ return { skillId, imported: entries.length, updated: current?.snapshotHash !== null && current?.snapshotHash !== input.snapshotHash, snapshotHash: input.snapshotHash, sourceWorkspace: input.sourceWorkspace, entries };
1340
+ }
1341
+ /** Create a materialized skill only when its canonical identity is absent under the write lock. */
1342
+ export function persistNewSkillImportInTransaction(database, input, now = new Date().toISOString(), authorization) {
1343
+ validatePreparedSkillIdentity(input);
1344
+ const expectedSkillId = candidateId(input.skill);
1345
+ const identityOwner = database.prepare(`
1346
+ SELECT skill_id
1347
+ FROM external_skills
1348
+ WHERE source_type = ? AND source_locator = ? AND slug = ?
1349
+ `).get(input.skill.sourceType, input.skill.source, input.skill.slug);
1350
+ if (row(database, expectedSkillId) !== undefined || identityOwner !== undefined) {
1351
+ if (identityOwner !== undefined && identityOwner.skill_id !== expectedSkillId) {
1352
+ // A valid row's key is derived from this tuple. Surface corruption rather
1353
+ // than disguising it as an ordinary create-only conflict.
1354
+ row(database, identityOwner.skill_id);
1355
+ integrity('External skill identity owner is invalid');
1356
+ }
1357
+ throw new KiokukoError('CONFLICT', 'External skill already exists; use refresh');
1358
+ }
1359
+ return persistSkillImportInTransaction(database, authorizedPreparedSkillImport(input, authorization), now);
1360
+ }
1361
+ export function persistSkillImport(database, input, now, authorization) {
1362
+ return withImmediateTransaction(database, () => persistNewSkillImportInTransaction(database, input, now, authorization));
1363
+ }
1364
+ /** Persist already-validated prepared documents only through an explicit materialized-row CAS. */
1365
+ export function persistPreparedSkillRefresh(database, input, expected, now = new Date().toISOString(), authorization) {
1366
+ return withImmediateTransaction(database, () => {
1367
+ validatePreparedSkillIdentity(input);
1368
+ const current = row(database, candidateId(input.skill));
1369
+ if (current === undefined || current.sourceCommit === null || current.snapshotHash === null
1370
+ || current.generation !== expected.generation || current.sourceCommit !== expected.sourceCommit
1371
+ || current.snapshotHash !== expected.snapshotHash || current.state !== expected.state
1372
+ || current.lastCheckedAt !== expected.lastCheckedAt) {
1373
+ throw new KiokukoError('CONFLICT', 'External skill snapshot changed during refresh');
1374
+ }
1375
+ return persistSkillImportInTransaction(database, authorizedPreparedSkillImport(input, authorization), now);
1376
+ });
1377
+ }
1378
+ /** Import through an existing unmaterialized discovery identity with a generation check. */
1379
+ export function persistExistingSkillImportInTransaction(database, skillId, input, expected, now = new Date().toISOString(), canonicalTarget, authorization) {
1380
+ validatePreparedSkillIdentity(input);
1381
+ const current = row(database, skillId);
1382
+ if (!current)
1383
+ throw new KiokukoError('NOT_FOUND', 'External skill not found');
1384
+ if (current.generation !== expected.generation || current.sourceCommit !== expected.sourceCommit || current.snapshotHash !== expected.snapshotHash
1385
+ || current.state !== expected.state || current.lastCheckedAt !== expected.lastCheckedAt) {
1386
+ throw new KiokukoError('CONFLICT', 'External skill snapshot changed during discovery');
1387
+ }
1388
+ const mappingCount = database.prepare('SELECT COUNT(*) AS count FROM external_skill_entries WHERE skill_id = ?').get(skillId)?.count ?? 0;
1389
+ if (current.sourceCommit !== null || current.snapshotHash !== null || Number(mappingCount) !== 0)
1390
+ throw new KiokukoError('CONFLICT', 'Verified external skills must use refresh');
1391
+ if (current.disabledAt !== null || current.state === 'blocked' || !['discovered', 'stale'].includes(current.state))
1392
+ throw new KiokukoError('CONFLICT', 'External skill alias has protected state');
1393
+ if (!sameSkillSource(current, input.skill))
1394
+ throw new KiokukoError('CONFLICT', 'External skill source identity changed during discovery');
1395
+ const authorizedInput = authorizedPreparedSkillImport(input, authorization);
1396
+ const canonicalId = candidateId(input.skill);
1397
+ if (canonicalId !== skillId) {
1398
+ const canonical = row(database, canonicalId);
1399
+ if (canonical !== undefined) {
1400
+ if (canonicalTarget === undefined || canonicalTarget.skillId !== canonicalId
1401
+ || canonical.generation !== canonicalTarget.expected.generation
1402
+ || canonical.sourceCommit !== canonicalTarget.expected.sourceCommit || canonical.snapshotHash !== canonicalTarget.expected.snapshotHash
1403
+ || canonical.state !== canonicalTarget.expected.state || canonical.lastCheckedAt !== canonicalTarget.expected.lastCheckedAt) {
1404
+ throw new KiokukoError('CONFLICT', 'External skill canonical identity is ambiguous');
1405
+ }
1406
+ if (canonical.disabledAt !== null || canonical.state === 'disabled' || canonical.state === 'blocked')
1407
+ throw new KiokukoError('CONFLICT', 'External skill canonical identity has protected state');
1408
+ const currentPrimary = database.prepare('SELECT source_path FROM external_skill_entries WHERE skill_id = ? AND primary_document = 1 ORDER BY source_path LIMIT 1').get(canonicalId)?.source_path;
1409
+ const incomingPrimary = input.documents.find((document) => document.primary)?.sourcePath;
1410
+ if (currentPrimary !== undefined && currentPrimary !== incomingPrimary)
1411
+ throw new KiokukoError('CONFLICT', 'External skill canonical primary path changed');
1412
+ allocateExternalSkillGeneration(database);
1413
+ database.prepare('DELETE FROM external_skills WHERE skill_id = ?').run(skillId);
1414
+ pruneExternalSkillGenerationTokens(database);
1415
+ }
1416
+ else {
1417
+ if (canonicalTarget !== undefined)
1418
+ throw new KiokukoError('CONFLICT', 'External skill canonical identity changed during discovery');
1419
+ const generation = allocateExternalSkillGeneration(database);
1420
+ database.prepare('UPDATE external_skills SET generation = ?, skill_id = ?, provider = ?, slug = ?, source_workspace = ? WHERE skill_id = ?')
1421
+ .run(generation, canonicalId, input.skill.provider, input.skill.slug, externalSkillWorkspace(input.skill), skillId);
1422
+ pruneExternalSkillGenerationTokens(database);
1423
+ }
1424
+ }
1425
+ return persistSkillImportInTransaction(database, authorizedInput, now);
1426
+ }
1427
+ export function persistExistingSkillImport(database, skillId, input, expected, now = new Date().toISOString(), canonicalTarget, authorization) {
1428
+ return withImmediateTransaction(database, () => persistExistingSkillImportInTransaction(database, skillId, input, expected, now, canonicalTarget, authorization));
1429
+ }
1430
+ export function recordDiscoveredSkillInTransaction(database, candidate, now = new Date().toISOString()) { const id = upsertCandidate(database, candidate, now); const result = row(database, id); if (!result)
1431
+ throw new KiokukoError('INTEGRITY_ERROR', 'External skill row disappeared during discovery'); return result; }
1432
+ export function recordDiscoveredSkill(database, candidate, now) { const at = now ?? new Date().toISOString(); return withImmediateTransaction(database, () => recordDiscoveredSkillInTransaction(database, candidate, at)); }
1433
+ const EXTERNAL_SKILL_STATES = new Set(['discovered', 'imported', 'blocked', 'stale', 'disabled']);
1434
+ const MAX_EXTERNAL_SKILL_LIST_SIZE = 1_000;
1435
+ const MAX_EXTERNAL_SKILL_PAGE_SIZE = 200;
1436
+ function externalSkillListState(value) {
1437
+ if (value === undefined)
1438
+ return undefined;
1439
+ if (!EXTERNAL_SKILL_STATES.has(value)) {
1440
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill list state is invalid');
1441
+ }
1442
+ return value;
1443
+ }
1444
+ function validateExternalSkillListAnchor(anchor) {
1445
+ let canonicalSource;
1446
+ try {
1447
+ canonicalSource = parseGitHubSource(anchor.sourceLocator).source;
1448
+ }
1449
+ catch (error) {
1450
+ if (error instanceof SkillSourceError)
1451
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill list anchor is invalid');
1452
+ throw error;
1453
+ }
1454
+ if (canonicalSource !== anchor.sourceLocator
1455
+ || !/^[A-Za-z0-9_.\-/]{1,240}$/u.test(anchor.slug)
1456
+ || anchor.slug.split('/').some((part) => part === '' || part === '.' || part === '..')
1457
+ || !/^[A-Za-z0-9_.-]{1,50}$/u.test(anchor.provider)
1458
+ || typeof anchor.skillId !== 'string' || anchor.skillId.length < 1 || anchor.skillId.length > 2_000
1459
+ || /[\p{Cc}\p{Cf}]/u.test(anchor.skillId)) {
1460
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill list anchor is invalid');
1461
+ }
1462
+ }
1463
+ function withExternalSkillReadTransaction(database, operation) {
1464
+ database.exec('BEGIN DEFERRED');
1465
+ try {
1466
+ const result = operation();
1467
+ database.exec('COMMIT');
1468
+ return result;
1469
+ }
1470
+ catch (error) {
1471
+ rollbackFailedTransaction(database, error);
1472
+ }
1473
+ }
1474
+ /**
1475
+ * Read one strict, keyset-ordered page bound to the durable external-skill mutation token.
1476
+ * Continuations must provide both the prior version and the last row's anchor.
1477
+ */
1478
+ export function listExternalSkillsPage(database, input) {
1479
+ const state = externalSkillListState(input.state);
1480
+ if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > MAX_EXTERNAL_SKILL_PAGE_SIZE
1481
+ || (input.after === undefined) !== (input.expectedVersion === undefined)
1482
+ || input.expectedVersion !== undefined && (!Number.isSafeInteger(input.expectedVersion) || input.expectedVersion < 0 || input.expectedVersion >= Number.MAX_SAFE_INTEGER)) {
1483
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill list page is invalid');
1484
+ }
1485
+ if (input.after !== undefined)
1486
+ validateExternalSkillListAnchor(input.after);
1487
+ return withExternalSkillReadTransaction(database, () => {
1488
+ const version = assertExternalSkillGenerationClock(database);
1489
+ if (input.expectedVersion !== undefined && input.expectedVersion !== version) {
1490
+ throw new KiokukoError('CONFLICT', 'External skill list changed; restart pagination');
1491
+ }
1492
+ if (input.after !== undefined) {
1493
+ const anchorValue = database.prepare('SELECT * FROM external_skills WHERE skill_id = ?')
1494
+ .get(input.after.skillId);
1495
+ if (anchorValue === undefined)
1496
+ throw new KiokukoError('CONFLICT', 'External skill list anchor is missing; restart pagination');
1497
+ const anchor = decodeExternalSkillRow(anchorValue);
1498
+ if (anchor.sourceLocator !== input.after.sourceLocator || anchor.slug !== input.after.slug || anchor.provider !== input.after.provider
1499
+ || state !== undefined && anchor.state !== state) {
1500
+ throw new KiokukoError('CONFLICT', 'External skill list anchor does not match this snapshot and filter');
1501
+ }
1502
+ }
1503
+ const clauses = [];
1504
+ const parameters = [];
1505
+ if (state !== undefined) {
1506
+ clauses.push('state = ?');
1507
+ parameters.push(state);
1508
+ }
1509
+ if (input.after !== undefined) {
1510
+ clauses.push('(source_locator, slug, provider, skill_id) > (?, ?, ?, ?)');
1511
+ parameters.push(input.after.sourceLocator, input.after.slug, input.after.provider, input.after.skillId);
1512
+ }
1513
+ const statement = `SELECT * FROM external_skills${clauses.length === 0 ? '' : ` WHERE ${clauses.join(' AND ')}`} ORDER BY source_locator, slug, provider, skill_id LIMIT ?`;
1514
+ parameters.push(input.limit + 1);
1515
+ const decoded = database.prepare(statement).all(...parameters).map(decodeExternalSkillRow);
1516
+ if (assertExternalSkillGenerationClock(database) !== version) {
1517
+ throw new KiokukoError('CONFLICT', 'External skill list changed; restart pagination');
1518
+ }
1519
+ return { skills: decoded.slice(0, input.limit), version, truncated: decoded.length > input.limit };
1520
+ });
1521
+ }
1522
+ /** Strict management list with a hard 1,000-row safety bound. */
1523
+ export function listExternalSkills(database, options = {}) {
1524
+ const state = externalSkillListState(options.state);
1525
+ if (options.limit !== undefined && (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > MAX_EXTERNAL_SKILL_LIST_SIZE)) {
1526
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill list limit is invalid');
1527
+ }
1528
+ const version = assertExternalSkillGenerationClock(database);
1529
+ const requested = options.limit ?? MAX_EXTERNAL_SKILL_LIST_SIZE;
1530
+ const parameters = [];
1531
+ let statement = 'SELECT * FROM external_skills';
1532
+ if (state !== undefined) {
1533
+ statement += ' WHERE state = ?';
1534
+ parameters.push(state);
1535
+ }
1536
+ statement += ' ORDER BY source_locator, slug, provider, skill_id LIMIT ?';
1537
+ parameters.push(requested + (options.limit === undefined ? 1 : 0));
1538
+ const values = database.prepare(statement).all(...parameters).map(decodeExternalSkillRow);
1539
+ if (assertExternalSkillGenerationClock(database) !== version) {
1540
+ throw new KiokukoError('CONFLICT', 'External skill list changed during read');
1541
+ }
1542
+ if (options.limit === undefined && values.length > MAX_EXTERNAL_SKILL_LIST_SIZE) {
1543
+ throw new KiokukoError('BACKPRESSURE', 'External skill list exceeds the 1,000-row management bound');
1544
+ }
1545
+ return values;
1546
+ }
1547
+ export function readExternalSkill(database, skillId) {
1548
+ const version = assertExternalSkillGenerationClock(database);
1549
+ const skill = row(database, skillId);
1550
+ if (!skill) {
1551
+ if (assertExternalSkillGenerationClock(database) !== version)
1552
+ throw new KiokukoError('CONFLICT', 'External skill changed during read');
1553
+ return undefined;
1554
+ }
1555
+ const entries = storedExternalSkillMappings(database, skillId).map((entry) => ({ entryId: entry.entry_id, revision: entry.entry_revision, sourcePath: entry.source_path, chunkIndex: entry.chunk_index, primary: entry.primary_document === 1, active: entry.active === 1 }));
1556
+ if (skill.sourceCommit === null) {
1557
+ if (entries.length !== 0)
1558
+ integrity('Unmaterialized external skill has stored mappings');
1559
+ }
1560
+ else {
1561
+ assertCurrentManagedSnapshot(database, skill);
1562
+ }
1563
+ if (assertExternalSkillGenerationClock(database) !== version) {
1564
+ throw new KiokukoError('CONFLICT', 'External skill changed during read');
1565
+ }
1566
+ return { skill, entries };
1567
+ }
1568
+ function activateCurrentMappings(database, skill) {
1569
+ assertCurrentManagedSnapshot(database, skill);
1570
+ database.prepare('UPDATE external_skill_entries SET active = 0 WHERE skill_id = ?').run(skill.skillId);
1571
+ for (const mapping of materializedSkillMetadata(skill.metadata).currentMappings) {
1572
+ database.prepare('UPDATE external_skill_entries SET active = 1 WHERE skill_id = ? AND source_path = ? AND chunk_index = ?').run(skill.skillId, mapping.sourcePath, mapping.chunkIndex);
1573
+ const changed = database.prepare('SELECT changes() AS count').get();
1574
+ if (Number(changed?.count ?? 0) !== 1)
1575
+ throw new KiokukoError('INTEGRITY_ERROR', 'External skill current mapping is missing');
1576
+ }
1577
+ }
1578
+ export function setExternalSkillState(database, skillId, state, now = new Date().toISOString()) {
1579
+ return withImmediateTransaction(database, () => {
1580
+ const current = row(database, skillId);
1581
+ if (!current)
1582
+ throw new KiokukoError('NOT_FOUND', 'External skill not found');
1583
+ if (state === 'imported' && (current.state !== 'disabled' || current.sourceCommit === null || current.snapshotHash === null))
1584
+ throw new KiokukoError('CONFLICT', 'Only a disabled skill with a verified snapshot can be enabled');
1585
+ if (state === 'disabled' && current.state !== 'imported')
1586
+ throw new KiokukoError('CONFLICT', 'Only an imported skill can be disabled');
1587
+ if (state === 'disabled')
1588
+ database.prepare('UPDATE external_skill_entries SET active = 0 WHERE skill_id = ?').run(skillId);
1589
+ else
1590
+ activateCurrentMappings(database, current);
1591
+ const generation = allocateExternalSkillGeneration(database);
1592
+ database.prepare('UPDATE external_skills SET generation = ?, state = ?, disabled_at = ? WHERE skill_id = ?').run(generation, state, state === 'disabled' ? now : null, skillId);
1593
+ pruneExternalSkillGenerationTokens(database);
1594
+ const result = row(database, skillId);
1595
+ if (!result)
1596
+ throw new KiokukoError('INTEGRITY_ERROR', 'External skill row disappeared during update');
1597
+ assertCurrentManagedSnapshot(database, result);
1598
+ return result;
1599
+ });
1600
+ }
1601
+ export function markExternalSkillRefreshFailureInTransaction(database, skillId, state, expected, now = new Date().toISOString()) {
1602
+ const current = row(database, skillId);
1603
+ if (!current)
1604
+ throw new KiokukoError('NOT_FOUND', 'External skill not found');
1605
+ if (current.generation !== expected.generation || current.sourceCommit !== expected.sourceCommit || current.snapshotHash !== expected.snapshotHash
1606
+ || current.state !== expected.state || current.lastCheckedAt !== expected.lastCheckedAt) {
1607
+ throw new KiokukoError('CONFLICT', 'External skill snapshot changed during refresh');
1608
+ }
1609
+ if (current.state === 'blocked' && state === 'stale')
1610
+ throw new KiokukoError('CONFLICT', 'A blocked external skill cannot be downgraded to stale');
1611
+ if (current.sourceCommit === null) {
1612
+ const mappingCount = database.prepare('SELECT COUNT(*) AS count FROM external_skill_entries WHERE skill_id = ?').get(skillId)?.count;
1613
+ if (mappingCount !== 0)
1614
+ integrity('Unmaterialized external skill has stored mappings');
1615
+ }
1616
+ else {
1617
+ assertCurrentManagedSnapshot(database, current);
1618
+ }
1619
+ const generation = allocateExternalSkillGeneration(database);
1620
+ database.prepare('UPDATE external_skills SET generation = ?, state = ?, last_checked_at = ? WHERE skill_id = ?').run(generation, state, now, skillId);
1621
+ database.prepare('UPDATE external_skill_entries SET active = 0 WHERE skill_id = ?').run(skillId);
1622
+ pruneExternalSkillGenerationTokens(database);
1623
+ const result = row(database, skillId);
1624
+ if (!result)
1625
+ throw new KiokukoError('INTEGRITY_ERROR', 'External skill row disappeared during update');
1626
+ if (result.sourceCommit === null) {
1627
+ const mappingCount = database.prepare('SELECT COUNT(*) AS count FROM external_skill_entries WHERE skill_id = ?').get(skillId)?.count;
1628
+ if (mappingCount !== 0)
1629
+ integrity('Unmaterialized external skill has stored mappings');
1630
+ }
1631
+ else {
1632
+ assertCurrentManagedSnapshot(database, result);
1633
+ }
1634
+ return result;
1635
+ }
1636
+ export function markExternalSkillRefreshFailure(database, skillId, state, expected, now = new Date().toISOString()) {
1637
+ return withImmediateTransaction(database, () => markExternalSkillRefreshFailureInTransaction(database, skillId, state, expected, now));
1638
+ }
1639
+ export function isExternalSkillReference(entry) {
1640
+ return entry.createdBy === 'kiokuko-skill-discovery'
1641
+ || entry.tags.includes('external:skill')
1642
+ || entry.provenance.type === 'external_skill';
1643
+ }
1644
+ function verifiedSnapshotDocuments(snapshot, documents) {
1645
+ const verifiedSnapshot = revalidateSkillSnapshot(snapshot);
1646
+ const verifiedDocuments = documentsFromSkillSnapshot(verifiedSnapshot);
1647
+ if (canonicalContentHash(documents) !== canonicalContentHash(verifiedDocuments))
1648
+ throw new KiokukoError('VALIDATION_ERROR', 'External skill documents do not match the verified snapshot');
1649
+ return { snapshot: verifiedSnapshot, documents: verifiedDocuments };
1650
+ }
1651
+ export function importSkillSnapshot(database, snapshot, documents, requirement, now, authorization) {
1652
+ const verified = verifiedSnapshotDocuments(snapshot, documents);
1653
+ return persistSkillImport(database, { skill: verified.snapshot.candidate, sourceWorkspace: externalSkillWorkspace(verified.snapshot.candidate), sourceCommit: verified.snapshot.sourceCommit, snapshotHash: verified.snapshot.snapshotHash, frontmatter: verified.snapshot.frontmatter, documents: verified.documents, ...(requirement ? { requirement } : {}) }, now, authorization);
1654
+ }
1655
+ /** Refresh inside an existing write transaction and report every committed lifecycle transition. */
1656
+ export function refreshExternalSkillSnapshotInTransaction(database, skillId, snapshot, documents, requirement, expected, now = new Date().toISOString(), authorization) {
1657
+ const verified = verifiedSnapshotDocuments(snapshot, documents);
1658
+ snapshot = verified.snapshot;
1659
+ documents = verified.documents;
1660
+ const detail = readExternalSkill(database, skillId);
1661
+ if (!detail)
1662
+ throw new KiokukoError('NOT_FOUND', 'External skill not found');
1663
+ if (detail.skill.generation !== expected.generation || detail.skill.sourceCommit !== expected.sourceCommit || detail.skill.snapshotHash !== expected.snapshotHash
1664
+ || detail.skill.state !== expected.state || detail.skill.lastCheckedAt !== expected.lastCheckedAt) {
1665
+ throw new KiokukoError('CONFLICT', 'External skill snapshot changed during refresh');
1666
+ }
1667
+ const unmaterialized = detail.skill.sourceCommit === null;
1668
+ if (unmaterialized) {
1669
+ if (!['discovered', 'stale'].includes(detail.skill.state)) {
1670
+ throw new KiokukoError('CONFLICT', 'Only discovered or stale unmaterialized skills can be refreshed');
1671
+ }
1672
+ if (detail.entries.length !== 0)
1673
+ integrity('Unmaterialized external skill has stored mappings');
1674
+ }
1675
+ else {
1676
+ assertCurrentManagedSnapshot(database, detail.skill);
1677
+ }
1678
+ const primary = documents.find((document) => document.primary);
1679
+ const expectedPrimary = unmaterialized ? undefined : detail.entries.find((entry) => entry.primary);
1680
+ const identityChanged = snapshot.candidate.sourceType !== detail.skill.sourceType
1681
+ || snapshot.candidate.source !== detail.skill.sourceLocator
1682
+ || snapshot.candidate.slug !== detail.skill.slug
1683
+ || (expectedPrimary !== undefined && primary?.sourcePath !== expectedPrimary.sourcePath);
1684
+ if (identityChanged) {
1685
+ const generation = allocateExternalSkillGeneration(database);
1686
+ database.prepare("UPDATE external_skills SET generation = ?, state = 'stale', last_checked_at = ? WHERE skill_id = ?").run(generation, now, skillId);
1687
+ database.prepare('UPDATE external_skill_entries SET active = 0 WHERE skill_id = ?').run(skillId);
1688
+ pruneExternalSkillGenerationTokens(database);
1689
+ const staled = row(database, skillId);
1690
+ if (staled === undefined)
1691
+ integrity('External skill row disappeared during refresh');
1692
+ return { kind: 'staled', skill: staled };
1693
+ }
1694
+ const authorizedCandidate = claimSkillMaterializationAuthorization(snapshot.candidate, authorization);
1695
+ const result = persistSkillImportInTransaction(database, {
1696
+ skill: authorizedCandidate,
1697
+ sourceWorkspace: externalSkillWorkspace(snapshot.candidate),
1698
+ sourceCommit: snapshot.sourceCommit,
1699
+ snapshotHash: snapshot.snapshotHash,
1700
+ frontmatter: snapshot.frontmatter,
1701
+ documents,
1702
+ ...(requirement === undefined ? {} : { requirement }),
1703
+ }, now);
1704
+ return { kind: 'refreshed', result };
1705
+ }
1706
+ /** Refresh an existing managed skill without allowing stale fetches or moved sources to replace its current snapshot. */
1707
+ export function refreshExternalSkillSnapshot(database, skillId, snapshot, documents, requirement, expected, now = new Date().toISOString(), authorization) {
1708
+ return withImmediateTransaction(database, () => refreshExternalSkillSnapshotInTransaction(database, skillId, snapshot, documents, requirement, expected, now, authorization));
1709
+ }
1710
+ //# sourceMappingURL=store.js.map