@askdkc/kiokuko 0.1.16 → 0.1.19

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 (549) hide show
  1. package/README.ja.md +91 -112
  2. package/README.ko.md +90 -102
  3. package/README.md +103 -260
  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 +14 -24
  18. package/dist/akinator/agent-task.d.ts.map +1 -1
  19. package/dist/akinator/agent-task.js +460 -103
  20. package/dist/akinator/agent-task.js.map +1 -1
  21. package/dist/akinator/capabilities.d.ts +52 -7
  22. package/dist/akinator/capabilities.d.ts.map +1 -1
  23. package/dist/akinator/capabilities.js +226 -28
  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/bin/kiokuko.js +0 -0
  52. package/dist/cli.d.ts +2 -0
  53. package/dist/cli.d.ts.map +1 -1
  54. package/dist/cli.js +561 -144
  55. package/dist/cli.js.map +1 -1
  56. package/dist/client/server-client.d.ts +5 -3
  57. package/dist/client/server-client.d.ts.map +1 -1
  58. package/dist/client/server-client.js +486 -181
  59. package/dist/client/server-client.js.map +1 -1
  60. package/dist/commands/agent.d.ts +2 -0
  61. package/dist/commands/agent.d.ts.map +1 -1
  62. package/dist/commands/agent.js +861 -86
  63. package/dist/commands/agent.js.map +1 -1
  64. package/dist/commands/backup.d.ts +2 -1
  65. package/dist/commands/backup.d.ts.map +1 -1
  66. package/dist/commands/backup.js +64 -8
  67. package/dist/commands/backup.js.map +1 -1
  68. package/dist/commands/doctor.d.ts +5 -1
  69. package/dist/commands/doctor.d.ts.map +1 -1
  70. package/dist/commands/doctor.js +70 -26
  71. package/dist/commands/doctor.js.map +1 -1
  72. package/dist/commands/export.d.ts +0 -4
  73. package/dist/commands/export.d.ts.map +1 -1
  74. package/dist/commands/export.js +191 -32
  75. package/dist/commands/export.js.map +1 -1
  76. package/dist/commands/import.d.ts +10 -1
  77. package/dist/commands/import.d.ts.map +1 -1
  78. package/dist/commands/import.js +592 -113
  79. package/dist/commands/import.js.map +1 -1
  80. package/dist/commands/init.d.ts +19 -1
  81. package/dist/commands/init.d.ts.map +1 -1
  82. package/dist/commands/init.js +569 -22
  83. package/dist/commands/init.js.map +1 -1
  84. package/dist/commands/purge.d.ts.map +1 -1
  85. package/dist/commands/purge.js +3 -0
  86. package/dist/commands/purge.js.map +1 -1
  87. package/dist/commands/setup.d.ts +34 -8
  88. package/dist/commands/setup.d.ts.map +1 -1
  89. package/dist/commands/setup.js +418 -86
  90. package/dist/commands/setup.js.map +1 -1
  91. package/dist/commands/skills.d.ts +9 -0
  92. package/dist/commands/skills.d.ts.map +1 -0
  93. package/dist/commands/skills.js +317 -0
  94. package/dist/commands/skills.js.map +1 -0
  95. package/dist/commands/use.d.ts +10 -2
  96. package/dist/commands/use.d.ts.map +1 -1
  97. package/dist/commands/use.js +851 -73
  98. package/dist/commands/use.js.map +1 -1
  99. package/dist/config/paths.d.ts +11 -1
  100. package/dist/config/paths.d.ts.map +1 -1
  101. package/dist/config/paths.js +64 -29
  102. package/dist/config/paths.js.map +1 -1
  103. package/dist/config/project-config.d.ts +1 -0
  104. package/dist/config/project-config.d.ts.map +1 -1
  105. package/dist/config/project-config.js +70 -32
  106. package/dist/config/project-config.js.map +1 -1
  107. package/dist/context/broker.d.ts +30 -14
  108. package/dist/context/broker.d.ts.map +1 -1
  109. package/dist/context/broker.js +628 -150
  110. package/dist/context/broker.js.map +1 -1
  111. package/dist/context/delivery.d.ts +8 -22
  112. package/dist/context/delivery.d.ts.map +1 -1
  113. package/dist/context/delivery.js +204 -152
  114. package/dist/context/delivery.js.map +1 -1
  115. package/dist/context/feedback.d.ts +11 -12
  116. package/dist/context/feedback.d.ts.map +1 -1
  117. package/dist/context/feedback.js +250 -268
  118. package/dist/context/feedback.js.map +1 -1
  119. package/dist/context/origin.d.ts +9 -0
  120. package/dist/context/origin.d.ts.map +1 -0
  121. package/dist/context/origin.js +15 -0
  122. package/dist/context/origin.js.map +1 -0
  123. package/dist/context/ranking.d.ts +4 -13
  124. package/dist/context/ranking.d.ts.map +1 -1
  125. package/dist/context/ranking.js +21 -5
  126. package/dist/context/ranking.js.map +1 -1
  127. package/dist/context/recommendations.d.ts +0 -5
  128. package/dist/context/recommendations.d.ts.map +1 -1
  129. package/dist/context/recommendations.js +0 -1
  130. package/dist/context/recommendations.js.map +1 -1
  131. package/dist/context/run-state.d.ts +26 -0
  132. package/dist/context/run-state.d.ts.map +1 -0
  133. package/dist/context/run-state.js +399 -0
  134. package/dist/context/run-state.js.map +1 -0
  135. package/dist/context/scoped-broker.d.ts +19 -6
  136. package/dist/context/scoped-broker.d.ts.map +1 -1
  137. package/dist/context/scoped-broker.js +530 -131
  138. package/dist/context/scoped-broker.js.map +1 -1
  139. package/dist/context/selection-state.d.ts +19 -0
  140. package/dist/context/selection-state.d.ts.map +1 -0
  141. package/dist/context/selection-state.js +250 -0
  142. package/dist/context/selection-state.js.map +1 -0
  143. package/dist/db/adapter.d.ts +5 -3
  144. package/dist/db/adapter.d.ts.map +1 -1
  145. package/dist/db/adapter.js +6 -3
  146. package/dist/db/adapter.js.map +1 -1
  147. package/dist/db/capabilities.d.ts.map +1 -1
  148. package/dist/db/capabilities.js +34 -16
  149. package/dist/db/capabilities.js.map +1 -1
  150. package/dist/db/connection.d.ts +8 -0
  151. package/dist/db/connection.d.ts.map +1 -1
  152. package/dist/db/connection.js +55 -26
  153. package/dist/db/connection.js.map +1 -1
  154. package/dist/db/migrate.d.ts +28 -7
  155. package/dist/db/migrate.d.ts.map +1 -1
  156. package/dist/db/migrate.js +106 -78
  157. package/dist/db/migrate.js.map +1 -1
  158. package/dist/db/sqlite-retry.d.ts +11 -0
  159. package/dist/db/sqlite-retry.d.ts.map +1 -0
  160. package/dist/db/sqlite-retry.js +66 -0
  161. package/dist/db/sqlite-retry.js.map +1 -0
  162. package/dist/db/transaction.d.ts +13 -0
  163. package/dist/db/transaction.d.ts.map +1 -1
  164. package/dist/db/transaction.js +53 -24
  165. package/dist/db/transaction.js.map +1 -1
  166. package/dist/db/upgrade-backup.d.ts +58 -2
  167. package/dist/db/upgrade-backup.d.ts.map +1 -1
  168. package/dist/db/upgrade-backup.js +757 -28
  169. package/dist/db/upgrade-backup.js.map +1 -1
  170. package/dist/errors.js +1 -1
  171. package/dist/errors.js.map +1 -1
  172. package/dist/gateway/agent-service.d.ts +4 -1
  173. package/dist/gateway/agent-service.d.ts.map +1 -1
  174. package/dist/gateway/agent-service.js +71 -58
  175. package/dist/gateway/agent-service.js.map +1 -1
  176. package/dist/gateway/checkpoint-service.d.ts +1 -0
  177. package/dist/gateway/checkpoint-service.d.ts.map +1 -1
  178. package/dist/gateway/checkpoint-service.js +47 -24
  179. package/dist/gateway/checkpoint-service.js.map +1 -1
  180. package/dist/ledger/archive.d.ts +1 -4
  181. package/dist/ledger/archive.d.ts.map +1 -1
  182. package/dist/ledger/archive.js +134 -54
  183. package/dist/ledger/archive.js.map +1 -1
  184. package/dist/ledger/maintenance.d.ts +0 -11
  185. package/dist/ledger/maintenance.d.ts.map +1 -1
  186. package/dist/ledger/maintenance.js +41 -44
  187. package/dist/ledger/maintenance.js.map +1 -1
  188. package/dist/ledger/projection.d.ts +0 -1
  189. package/dist/ledger/projection.d.ts.map +1 -1
  190. package/dist/ledger/projection.js.map +1 -1
  191. package/dist/ledger/promotion.d.ts +0 -9
  192. package/dist/ledger/promotion.d.ts.map +1 -1
  193. package/dist/ledger/promotion.js.map +1 -1
  194. package/dist/ledger/query.d.ts +1 -15
  195. package/dist/ledger/query.d.ts.map +1 -1
  196. package/dist/ledger/query.js.map +1 -1
  197. package/dist/ledger/store.d.ts +0 -1
  198. package/dist/ledger/store.d.ts.map +1 -1
  199. package/dist/ledger/store.js +11 -8
  200. package/dist/ledger/store.js.map +1 -1
  201. package/dist/ledger/types.d.ts +0 -4
  202. package/dist/ledger/types.d.ts.map +1 -1
  203. package/dist/ledger/types.js.map +1 -1
  204. package/dist/ledger/validate.d.ts.map +1 -1
  205. package/dist/ledger/validate.js +0 -5
  206. package/dist/ledger/validate.js.map +1 -1
  207. package/dist/mcp/bounded-stdio-transport.d.ts +23 -0
  208. package/dist/mcp/bounded-stdio-transport.d.ts.map +1 -0
  209. package/dist/mcp/bounded-stdio-transport.js +109 -0
  210. package/dist/mcp/bounded-stdio-transport.js.map +1 -0
  211. package/dist/mcp/server.d.ts +4 -0
  212. package/dist/mcp/server.d.ts.map +1 -1
  213. package/dist/mcp/server.js +111 -75
  214. package/dist/mcp/server.js.map +1 -1
  215. package/dist/memory/audit.d.ts +0 -1
  216. package/dist/memory/audit.d.ts.map +1 -1
  217. package/dist/memory/audit.js +0 -1
  218. package/dist/memory/audit.js.map +1 -1
  219. package/dist/memory/curator.d.ts +45 -1
  220. package/dist/memory/curator.d.ts.map +1 -1
  221. package/dist/memory/curator.js +290 -68
  222. package/dist/memory/curator.js.map +1 -1
  223. package/dist/memory/entries.d.ts +12 -2
  224. package/dist/memory/entries.d.ts.map +1 -1
  225. package/dist/memory/entries.js +198 -114
  226. package/dist/memory/entries.js.map +1 -1
  227. package/dist/memory/federated-retrieval.d.ts +78 -0
  228. package/dist/memory/federated-retrieval.d.ts.map +1 -0
  229. package/dist/memory/federated-retrieval.js +441 -0
  230. package/dist/memory/federated-retrieval.js.map +1 -0
  231. package/dist/memory/hybrid-retrieval.d.ts +5 -11
  232. package/dist/memory/hybrid-retrieval.d.ts.map +1 -1
  233. package/dist/memory/hybrid-retrieval.js +78 -58
  234. package/dist/memory/hybrid-retrieval.js.map +1 -1
  235. package/dist/memory/lifecycle.d.ts.map +1 -1
  236. package/dist/memory/lifecycle.js +17 -13
  237. package/dist/memory/lifecycle.js.map +1 -1
  238. package/dist/memory/portability.d.ts +10 -0
  239. package/dist/memory/portability.d.ts.map +1 -0
  240. package/dist/memory/portability.js +44 -0
  241. package/dist/memory/portability.js.map +1 -0
  242. package/dist/memory/rebuild-search.d.ts +5 -0
  243. package/dist/memory/rebuild-search.d.ts.map +1 -1
  244. package/dist/memory/rebuild-search.js +101 -76
  245. package/dist/memory/rebuild-search.js.map +1 -1
  246. package/dist/memory/retrieval-query.d.ts +0 -5
  247. package/dist/memory/retrieval-query.d.ts.map +1 -1
  248. package/dist/memory/retrieval-query.js +0 -5
  249. package/dist/memory/retrieval-query.js.map +1 -1
  250. package/dist/memory/retrieval.d.ts +11 -0
  251. package/dist/memory/retrieval.d.ts.map +1 -1
  252. package/dist/memory/retrieval.js +34 -120
  253. package/dist/memory/retrieval.js.map +1 -1
  254. package/dist/memory/revisions.d.ts +76 -1
  255. package/dist/memory/revisions.d.ts.map +1 -1
  256. package/dist/memory/revisions.js +539 -35
  257. package/dist/memory/revisions.js.map +1 -1
  258. package/dist/memory/scoped-memory.d.ts +22 -13
  259. package/dist/memory/scoped-memory.d.ts.map +1 -1
  260. package/dist/memory/scoped-memory.js +248 -139
  261. package/dist/memory/scoped-memory.js.map +1 -1
  262. package/dist/memory/secrets.d.ts +2 -0
  263. package/dist/memory/secrets.d.ts.map +1 -1
  264. package/dist/memory/secrets.js +36 -0
  265. package/dist/memory/secrets.js.map +1 -1
  266. package/dist/memory/structured-memory.d.ts +8 -1
  267. package/dist/memory/structured-memory.d.ts.map +1 -1
  268. package/dist/memory/structured-memory.js +74 -20
  269. package/dist/memory/structured-memory.js.map +1 -1
  270. package/dist/memory/workspaces.d.ts +9 -0
  271. package/dist/memory/workspaces.d.ts.map +1 -1
  272. package/dist/memory/workspaces.js +67 -0
  273. package/dist/memory/workspaces.js.map +1 -1
  274. package/dist/package-version.d.ts +2 -0
  275. package/dist/package-version.d.ts.map +1 -0
  276. package/dist/package-version.js +7 -0
  277. package/dist/package-version.js.map +1 -0
  278. package/dist/repository/binding.d.ts +5 -0
  279. package/dist/repository/binding.d.ts.map +1 -1
  280. package/dist/repository/binding.js +117 -17
  281. package/dist/repository/binding.js.map +1 -1
  282. package/dist/repository/framework-version.d.ts +8 -0
  283. package/dist/repository/framework-version.d.ts.map +1 -0
  284. package/dist/repository/framework-version.js +29 -0
  285. package/dist/repository/framework-version.js.map +1 -0
  286. package/dist/repository/identity-value.d.ts +4 -0
  287. package/dist/repository/identity-value.d.ts.map +1 -0
  288. package/dist/repository/identity-value.js +24 -0
  289. package/dist/repository/identity-value.js.map +1 -0
  290. package/dist/repository/identity.d.ts.map +1 -1
  291. package/dist/repository/identity.js +2 -0
  292. package/dist/repository/identity.js.map +1 -1
  293. package/dist/repository/project-fingerprint.d.ts +33 -0
  294. package/dist/repository/project-fingerprint.d.ts.map +1 -0
  295. package/dist/repository/project-fingerprint.js +429 -0
  296. package/dist/repository/project-fingerprint.js.map +1 -0
  297. package/dist/repository/remote-url.d.ts +0 -1
  298. package/dist/repository/remote-url.d.ts.map +1 -1
  299. package/dist/repository/remote-url.js +0 -1
  300. package/dist/repository/remote-url.js.map +1 -1
  301. package/dist/runtime-version.d.ts +2 -1
  302. package/dist/runtime-version.d.ts.map +1 -1
  303. package/dist/runtime-version.js +21 -4
  304. package/dist/runtime-version.js.map +1 -1
  305. package/dist/serialization/boundary-json.d.ts +20 -0
  306. package/dist/serialization/boundary-json.d.ts.map +1 -0
  307. package/dist/serialization/boundary-json.js +129 -0
  308. package/dist/serialization/boundary-json.js.map +1 -0
  309. package/dist/serialization/envelope.js +1 -1
  310. package/dist/serialization/envelope.js.map +1 -1
  311. package/dist/serialization/validate.d.ts +7 -0
  312. package/dist/serialization/validate.d.ts.map +1 -1
  313. package/dist/serialization/validate.js +53 -13
  314. package/dist/serialization/validate.js.map +1 -1
  315. package/dist/server/app.js +1 -1
  316. package/dist/server/app.js.map +1 -1
  317. package/dist/server/http.d.ts +4 -3
  318. package/dist/server/http.d.ts.map +1 -1
  319. package/dist/server/http.js +90 -50
  320. package/dist/server/http.js.map +1 -1
  321. package/dist/server/idempotency.d.ts +0 -6
  322. package/dist/server/idempotency.d.ts.map +1 -1
  323. package/dist/server/idempotency.js.map +1 -1
  324. package/dist/server/router.d.ts.map +1 -1
  325. package/dist/server/router.js +21 -4
  326. package/dist/server/router.js.map +1 -1
  327. package/dist/server/routes/agent-capability-gate.d.ts +40 -0
  328. package/dist/server/routes/agent-capability-gate.d.ts.map +1 -0
  329. package/dist/server/routes/agent-capability-gate.js +182 -0
  330. package/dist/server/routes/agent-capability-gate.js.map +1 -0
  331. package/dist/server/routes/agent-events.d.ts.map +1 -1
  332. package/dist/server/routes/agent-events.js +8 -1
  333. package/dist/server/routes/agent-events.js.map +1 -1
  334. package/dist/server/routes/agent-intake.d.ts.map +1 -1
  335. package/dist/server/routes/agent-intake.js +10 -2
  336. package/dist/server/routes/agent-intake.js.map +1 -1
  337. package/dist/server/routes/agent-promotions.d.ts.map +1 -1
  338. package/dist/server/routes/agent-promotions.js +5 -2
  339. package/dist/server/routes/agent-promotions.js.map +1 -1
  340. package/dist/server/routes/agent-runs.d.ts.map +1 -1
  341. package/dist/server/routes/agent-runs.js +18 -4
  342. package/dist/server/routes/agent-runs.js.map +1 -1
  343. package/dist/server/routes/request-binding.d.ts +5 -0
  344. package/dist/server/routes/request-binding.d.ts.map +1 -0
  345. package/dist/server/routes/request-binding.js +49 -0
  346. package/dist/server/routes/request-binding.js.map +1 -0
  347. package/dist/server/routes/task5-support.d.ts +1 -8
  348. package/dist/server/routes/task5-support.d.ts.map +1 -1
  349. package/dist/server/routes/task5-support.js +1 -17
  350. package/dist/server/routes/task5-support.js.map +1 -1
  351. package/dist/server/routes/task5.d.ts.map +1 -1
  352. package/dist/server/routes/task5.js +106 -36
  353. package/dist/server/routes/task5.js.map +1 -1
  354. package/dist/server/write-queue.js +2 -2
  355. package/dist/server/write-queue.js.map +1 -1
  356. package/dist/setup/claude-config.d.ts +2 -1
  357. package/dist/setup/claude-config.d.ts.map +1 -1
  358. package/dist/setup/claude-config.js +54 -4
  359. package/dist/setup/claude-config.js.map +1 -1
  360. package/dist/setup/client-detection.d.ts +7 -2
  361. package/dist/setup/client-detection.d.ts.map +1 -1
  362. package/dist/setup/client-detection.js +27 -9
  363. package/dist/setup/client-detection.js.map +1 -1
  364. package/dist/setup/hermes-config.d.ts +2 -1
  365. package/dist/setup/hermes-config.d.ts.map +1 -1
  366. package/dist/setup/hermes-config.js +53 -12
  367. package/dist/setup/hermes-config.js.map +1 -1
  368. package/dist/setup/legacy-client-cleanup.d.ts +6 -0
  369. package/dist/setup/legacy-client-cleanup.d.ts.map +1 -0
  370. package/dist/setup/legacy-client-cleanup.js +152 -0
  371. package/dist/setup/legacy-client-cleanup.js.map +1 -0
  372. package/dist/setup/managed-text.d.ts.map +1 -1
  373. package/dist/setup/managed-text.js +11 -1
  374. package/dist/setup/managed-text.js.map +1 -1
  375. package/dist/setup/opencode-config.d.ts +2 -1
  376. package/dist/setup/opencode-config.d.ts.map +1 -1
  377. package/dist/setup/opencode-config.js +57 -5
  378. package/dist/setup/opencode-config.js.map +1 -1
  379. package/dist/setup/render.d.ts +2 -1
  380. package/dist/setup/render.d.ts.map +1 -1
  381. package/dist/setup/render.js +91 -17
  382. package/dist/setup/render.js.map +1 -1
  383. package/dist/setup/standard-skills.d.ts +16 -2
  384. package/dist/setup/standard-skills.d.ts.map +1 -1
  385. package/dist/setup/standard-skills.js +37 -13
  386. package/dist/setup/standard-skills.js.map +1 -1
  387. package/dist/setup/strict-json.d.ts +7 -0
  388. package/dist/setup/strict-json.d.ts.map +1 -0
  389. package/dist/setup/strict-json.js +98 -0
  390. package/dist/setup/strict-json.js.map +1 -0
  391. package/dist/setup/strict-toml.d.ts +7 -0
  392. package/dist/setup/strict-toml.d.ts.map +1 -0
  393. package/dist/setup/strict-toml.js +470 -0
  394. package/dist/setup/strict-toml.js.map +1 -0
  395. package/dist/skills/cache.d.ts +15 -0
  396. package/dist/skills/cache.d.ts.map +1 -0
  397. package/dist/skills/cache.js +19 -0
  398. package/dist/skills/cache.js.map +1 -0
  399. package/dist/skills/candidate-ranking.d.ts +15 -0
  400. package/dist/skills/candidate-ranking.d.ts.map +1 -0
  401. package/dist/skills/candidate-ranking.js +52 -0
  402. package/dist/skills/candidate-ranking.js.map +1 -0
  403. package/dist/skills/chunking.d.ts +11 -0
  404. package/dist/skills/chunking.d.ts.map +1 -0
  405. package/dist/skills/chunking.js +291 -0
  406. package/dist/skills/chunking.js.map +1 -0
  407. package/dist/skills/config.d.ts +19 -0
  408. package/dist/skills/config.d.ts.map +1 -0
  409. package/dist/skills/config.js +103 -0
  410. package/dist/skills/config.js.map +1 -0
  411. package/dist/skills/discovery-service.d.ts +26 -0
  412. package/dist/skills/discovery-service.d.ts.map +1 -0
  413. package/dist/skills/discovery-service.js +921 -0
  414. package/dist/skills/discovery-service.js.map +1 -0
  415. package/dist/skills/external-transport.d.ts +2 -0
  416. package/dist/skills/external-transport.d.ts.map +1 -0
  417. package/dist/skills/external-transport.js +30 -0
  418. package/dist/skills/external-transport.js.map +1 -0
  419. package/dist/skills/find.d.ts +16 -0
  420. package/dist/skills/find.d.ts.map +1 -0
  421. package/dist/skills/find.js +58 -0
  422. package/dist/skills/find.js.map +1 -0
  423. package/dist/skills/gap-detection.d.ts +23 -0
  424. package/dist/skills/gap-detection.d.ts.map +1 -0
  425. package/dist/skills/gap-detection.js +62 -0
  426. package/dist/skills/gap-detection.js.map +1 -0
  427. package/dist/skills/import-preparation.d.ts +4 -0
  428. package/dist/skills/import-preparation.d.ts.map +1 -0
  429. package/dist/skills/import-preparation.js +20 -0
  430. package/dist/skills/import-preparation.js.map +1 -0
  431. package/dist/skills/materialization-authority.d.ts +11 -0
  432. package/dist/skills/materialization-authority.d.ts.map +1 -0
  433. package/dist/skills/materialization-authority.js +92 -0
  434. package/dist/skills/materialization-authority.js.map +1 -0
  435. package/dist/skills/materialization-service.d.ts +22 -0
  436. package/dist/skills/materialization-service.d.ts.map +1 -0
  437. package/dist/skills/materialization-service.js +101 -0
  438. package/dist/skills/materialization-service.js.map +1 -0
  439. package/dist/skills/official-catalog.d.ts +31 -0
  440. package/dist/skills/official-catalog.d.ts.map +1 -0
  441. package/dist/skills/official-catalog.js +110 -0
  442. package/dist/skills/official-catalog.js.map +1 -0
  443. package/dist/skills/providers/schema.d.ts +30 -0
  444. package/dist/skills/providers/schema.d.ts.map +1 -0
  445. package/dist/skills/providers/schema.js +354 -0
  446. package/dist/skills/providers/schema.js.map +1 -0
  447. package/dist/skills/providers/skills-sh-compat.d.ts +17 -0
  448. package/dist/skills/providers/skills-sh-compat.d.ts.map +1 -0
  449. package/dist/skills/providers/skills-sh-compat.js +71 -0
  450. package/dist/skills/providers/skills-sh-compat.js.map +1 -0
  451. package/dist/skills/providers/skills-sh-v1.d.ts +23 -0
  452. package/dist/skills/providers/skills-sh-v1.d.ts.map +1 -0
  453. package/dist/skills/providers/skills-sh-v1.js +134 -0
  454. package/dist/skills/providers/skills-sh-v1.js.map +1 -0
  455. package/dist/skills/query-builder.d.ts +18 -0
  456. package/dist/skills/query-builder.d.ts.map +1 -0
  457. package/dist/skills/query-builder.js +93 -0
  458. package/dist/skills/query-builder.js.map +1 -0
  459. package/dist/skills/source/errors.d.ts +9 -0
  460. package/dist/skills/source/errors.d.ts.map +1 -0
  461. package/dist/skills/source/errors.js +15 -0
  462. package/dist/skills/source/errors.js.map +1 -0
  463. package/dist/skills/source/frontmatter.d.ts +9 -0
  464. package/dist/skills/source/frontmatter.d.ts.map +1 -0
  465. package/dist/skills/source/frontmatter.js +77 -0
  466. package/dist/skills/source/frontmatter.js.map +1 -0
  467. package/dist/skills/source/github-fetcher.d.ts +20 -0
  468. package/dist/skills/source/github-fetcher.d.ts.map +1 -0
  469. package/dist/skills/source/github-fetcher.js +292 -0
  470. package/dist/skills/source/github-fetcher.js.map +1 -0
  471. package/dist/skills/source/snapshot-validator.d.ts +28 -0
  472. package/dist/skills/source/snapshot-validator.d.ts.map +1 -0
  473. package/dist/skills/source/snapshot-validator.js +186 -0
  474. package/dist/skills/source/snapshot-validator.js.map +1 -0
  475. package/dist/skills/store.d.ts +181 -0
  476. package/dist/skills/store.d.ts.map +1 -0
  477. package/dist/skills/store.js +1710 -0
  478. package/dist/skills/store.js.map +1 -0
  479. package/dist/skills/types.d.ts +169 -0
  480. package/dist/skills/types.d.ts.map +1 -0
  481. package/dist/skills/types.js +2 -0
  482. package/dist/skills/types.js.map +1 -0
  483. package/dist/web/i18n.d.ts +56 -8
  484. package/dist/web/i18n.d.ts.map +1 -1
  485. package/dist/web/i18n.js +224 -32
  486. package/dist/web/i18n.js.map +1 -1
  487. package/dist/web/server.d.ts.map +1 -1
  488. package/dist/web/server.js +458 -42
  489. package/dist/web/server.js.map +1 -1
  490. package/dist/web/ui.d.ts +4 -0
  491. package/dist/web/ui.d.ts.map +1 -1
  492. package/dist/web/ui.js +697 -61
  493. package/dist/web/ui.js.map +1 -1
  494. package/migrations/008_federated_memory.sql +46 -0
  495. package/migrations/009_external_skill_discovery.sql +126 -0
  496. package/package.json +6 -3
  497. package/skills/kiokuko-single-purpose-functions/SKILL.md +288 -0
  498. package/skills/kiokuko-single-purpose-functions/references/kiokuko-patterns.md +344 -0
  499. package/skills/kiokuko-single-purpose-functions/references/review-checklist.md +97 -0
  500. package/skills/kiokuko-ui-design-soul/SKILL.md +1186 -28
  501. package/templates/AGENTS.md +8 -7
  502. package/dist/client/runtime-discovery.d.ts +0 -15
  503. package/dist/client/runtime-discovery.d.ts.map +0 -1
  504. package/dist/client/runtime-discovery.js +0 -45
  505. package/dist/client/runtime-discovery.js.map +0 -1
  506. package/dist/commands/call.d.ts +0 -7
  507. package/dist/commands/call.d.ts.map +0 -1
  508. package/dist/commands/call.js +0 -10
  509. package/dist/commands/call.js.map +0 -1
  510. package/dist/commands/link.d.ts +0 -6
  511. package/dist/commands/link.d.ts.map +0 -1
  512. package/dist/commands/link.js +0 -6
  513. package/dist/commands/link.js.map +0 -1
  514. package/dist/commands/promote.d.ts +0 -6
  515. package/dist/commands/promote.d.ts.map +0 -1
  516. package/dist/commands/promote.js +0 -6
  517. package/dist/commands/promote.js.map +0 -1
  518. package/dist/commands/read.d.ts +0 -6
  519. package/dist/commands/read.d.ts.map +0 -1
  520. package/dist/commands/read.js +0 -4
  521. package/dist/commands/read.js.map +0 -1
  522. package/dist/commands/recall.d.ts +0 -6
  523. package/dist/commands/recall.d.ts.map +0 -1
  524. package/dist/commands/recall.js +0 -4
  525. package/dist/commands/recall.js.map +0 -1
  526. package/dist/commands/record.d.ts +0 -6
  527. package/dist/commands/record.d.ts.map +0 -1
  528. package/dist/commands/record.js +0 -4
  529. package/dist/commands/record.js.map +0 -1
  530. package/dist/commands/search.d.ts +0 -6
  531. package/dist/commands/search.d.ts.map +0 -1
  532. package/dist/commands/search.js +0 -4
  533. package/dist/commands/search.js.map +0 -1
  534. package/dist/commands/supersede.d.ts +0 -6
  535. package/dist/commands/supersede.d.ts.map +0 -1
  536. package/dist/commands/supersede.js +0 -6
  537. package/dist/commands/supersede.js.map +0 -1
  538. package/dist/knowledge/sources.d.ts +0 -72
  539. package/dist/knowledge/sources.d.ts.map +0 -1
  540. package/dist/knowledge/sources.js +0 -388
  541. package/dist/knowledge/sources.js.map +0 -1
  542. package/dist/setup/opencode-evidence.d.ts +0 -37
  543. package/dist/setup/opencode-evidence.d.ts.map +0 -1
  544. package/dist/setup/opencode-evidence.js +0 -58
  545. package/dist/setup/opencode-evidence.js.map +0 -1
  546. package/dist/setup/opencode-loop-guard.d.ts +0 -9
  547. package/dist/setup/opencode-loop-guard.d.ts.map +0 -1
  548. package/dist/setup/opencode-loop-guard.js +0 -248
  549. package/dist/setup/opencode-loop-guard.js.map +0 -1
package/dist/web/ui.js CHANGED
@@ -10,6 +10,93 @@ const WEB_I18N_CONFIG = JSON.stringify({
10
10
  .replaceAll('>', '\\u003e')
11
11
  .replaceAll('\u2028', '\\u2028')
12
12
  .replaceAll('\u2029', '\\u2029');
13
+ /** Strict client-boundary decoder for the bounded external-skill list shape. */
14
+ export function externalSkillListItemIsValid(value) {
15
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
16
+ return false;
17
+ const skill = value;
18
+ const fields = new Set([
19
+ 'skillId', 'provider', 'sourceType', 'sourceLocator', 'slug', 'name', 'installUrl',
20
+ 'officialStatus', 'duplicate', 'installs', 'state', 'sourceWorkspace', 'sourceCommit',
21
+ 'snapshotHash', 'metadata', 'auditStatus', 'generation', 'firstSeenAt', 'lastSeenAt',
22
+ 'lastCheckedAt', 'disabledAt',
23
+ ]);
24
+ const text = (item, maximum) => typeof item === 'string'
25
+ && item.length >= 1 && item.length <= maximum && item === item.trim() && !/[\u0000-\u001f\u007f]/u.test(item);
26
+ const timestamp = (item) => {
27
+ if (typeof item !== 'string')
28
+ return false;
29
+ const parsed = Date.parse(item);
30
+ return Number.isFinite(parsed) && new Date(parsed).toISOString() === item;
31
+ };
32
+ const metadata = skill.metadata;
33
+ const sourceLocator = skill.sourceLocator;
34
+ const slug = skill.slug;
35
+ const sourceCommit = skill.sourceCommit;
36
+ const snapshotHash = skill.snapshotHash;
37
+ const disabledAt = skill.disabledAt;
38
+ const officialStatus = skill.officialStatus;
39
+ const state = skill.state;
40
+ const auditStatus = skill.auditStatus;
41
+ if (Object.keys(skill).length !== fields.size || Object.keys(skill).some((field) => !fields.has(field))
42
+ || !text(skill.skillId, 500) || !text(skill.provider, 50) || !/^[A-Za-z0-9_.-]+$/u.test(skill.provider)
43
+ || skill.sourceType !== 'github'
44
+ || typeof sourceLocator !== 'string' || !/^[a-z0-9_.-]{1,100}\/[a-z0-9_.-]{1,100}$/u.test(sourceLocator)
45
+ || typeof slug !== 'string' || !/^[A-Za-z0-9_.\-/]{1,240}$/u.test(slug)
46
+ || slug.split('/').some((part) => part === '' || part === '.' || part === '..')
47
+ || skill.skillId !== `github:${sourceLocator}:${slug}`
48
+ || !text(skill.name, 500)
49
+ || skill.installUrl !== null && skill.installUrl !== `https://github.com/${sourceLocator}`
50
+ || typeof officialStatus !== 'string'
51
+ || !['curated', 'catalog-verified', 'owner-verified', 'registry-only', 'unknown'].includes(officialStatus)
52
+ || typeof skill.duplicate !== 'boolean'
53
+ || typeof skill.installs !== 'number' || !Number.isSafeInteger(skill.installs) || skill.installs < 0
54
+ || typeof state !== 'string'
55
+ || !['discovered', 'imported', 'blocked', 'stale', 'disabled'].includes(state)
56
+ || !text(skill.sourceWorkspace, 240)
57
+ || sourceCommit !== null && (typeof sourceCommit !== 'string' || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(sourceCommit))
58
+ || snapshotHash !== null && (typeof snapshotHash !== 'string' || !/^[0-9a-f]{64}$/u.test(snapshotHash))
59
+ || (sourceCommit === null) !== (snapshotHash === null)
60
+ || typeof metadata !== 'object' || metadata === null || Array.isArray(metadata)
61
+ || Object.keys(metadata).length !== 2 || !Object.hasOwn(metadata, 'documents') || !Object.hasOwn(metadata, 'technology')
62
+ || typeof metadata.documents !== 'number'
63
+ || !Number.isSafeInteger(metadata.documents)
64
+ || metadata.documents < 0
65
+ || metadata.documents > 64
66
+ || metadata.technology !== null && !text(metadata.technology, 500)
67
+ || typeof auditStatus !== 'string'
68
+ || !['not-required', 'passed', 'failed', 'unavailable'].includes(auditStatus)
69
+ || typeof skill.generation !== 'number' || !Number.isSafeInteger(skill.generation) || skill.generation < 1
70
+ || !timestamp(skill.firstSeenAt) || !timestamp(skill.lastSeenAt) || !timestamp(skill.lastCheckedAt)
71
+ || disabledAt !== null && !timestamp(disabledAt)
72
+ || skill.firstSeenAt > skill.lastSeenAt
73
+ || skill.firstSeenAt > skill.lastCheckedAt
74
+ || disabledAt !== null && disabledAt < skill.firstSeenAt
75
+ || sourceCommit === null && (['imported', 'disabled'].includes(state) || disabledAt !== null)
76
+ || sourceCommit !== null && !['imported', 'disabled', 'stale', 'blocked'].includes(state)
77
+ || state === 'imported' && disabledAt !== null
78
+ || state === 'disabled' && disabledAt === null
79
+ || sourceCommit === null && (metadata.documents !== 0 || metadata.technology !== null)
80
+ || sourceCommit !== null && (metadata.documents === 0 || !text(metadata.technology, 500)))
81
+ return false;
82
+ return true;
83
+ }
84
+ /** Strict client-boundary decoder for one external-skill mapping summary. */
85
+ export function externalSkillEntrySummaryIsValid(value) {
86
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
87
+ return false;
88
+ const entry = value;
89
+ const fields = new Set(['entryId', 'revision', 'sourcePath', 'chunkIndex', 'primary', 'active']);
90
+ return Object.keys(entry).length === fields.size
91
+ && Object.keys(entry).every((field) => fields.has(field))
92
+ && typeof entry.entryId === 'string' && entry.entryId.length >= 1 && entry.entryId.length <= 500 && entry.entryId === entry.entryId.trim()
93
+ && typeof entry.revision === 'number' && Number.isSafeInteger(entry.revision) && entry.revision >= 1
94
+ && typeof entry.sourcePath === 'string' && entry.sourcePath.length >= 1 && entry.sourcePath.length <= 2_000
95
+ && /^[A-Za-z0-9_.\-/]+$/u.test(entry.sourcePath) && !entry.sourcePath.startsWith('/')
96
+ && !entry.sourcePath.split('/').some((part) => part === '' || part === '.' || part === '..')
97
+ && typeof entry.chunkIndex === 'number' && Number.isSafeInteger(entry.chunkIndex) && entry.chunkIndex >= 0
98
+ && typeof entry.primary === 'boolean' && typeof entry.active === 'boolean';
99
+ }
13
100
  export const WEB_HTML = String.raw `<!doctype html>
14
101
  <html lang="en">
15
102
  <head>
@@ -65,11 +152,18 @@ export const WEB_HTML = String.raw `<!doctype html>
65
152
  .entry-card:hover,.entry-card.selected { border-color:#93c5fd; box-shadow:0 8px 20px rgba(37,99,235,.10); }
66
153
  .entry-meta { display:flex; gap:7px; align-items:center; flex-wrap:wrap; color:var(--muted); font-size:12px; }
67
154
  .badge { border-radius:999px; padding:3px 8px; background:#f1f5f9; color:#475569; font-weight:700; }
155
+ .badge.origin-project { background:#dbeafe; color:#1d4ed8; }
156
+ .badge.origin-ecosystem { background:#dcfce7; color:#166534; }
157
+ .badge.origin-global { background:#fef3c7; color:#92400e; }
68
158
  .badge.verified { background:#dcfce7; color:#166534; }
69
159
  .badge.candidate { background:#fef3c7; color:#92400e; }
70
160
  .badge.superseded { background:#fee2e2; color:#991b1b; }
71
161
  .entry-card h3 { margin:9px 0 6px; font-size:16px; }
72
162
  .snippet { margin:0; color:var(--muted); display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; white-space:pre-wrap; }
163
+ .federated-card { border-left:4px solid #22c55e; }
164
+ .federated-card.global { border-left-color:#f59e0b; }
165
+ .federated-source,.selection-reasons { margin-top:8px; color:var(--muted); font-size:12px; white-space:pre-wrap; overflow-wrap:anywhere; }
166
+ .federated-detail { display:grid; gap:12px; }
73
167
  .tags { display:flex; gap:6px; flex-wrap:wrap; margin-top:10px; }
74
168
  .tag { border:0; background:transparent; color:var(--accent); font-size:12px; padding:0; }
75
169
  .tag:hover { text-decoration:underline; }
@@ -106,19 +200,44 @@ export const WEB_HTML = String.raw `<!doctype html>
106
200
  .curator-close { display:grid; place-items:center; flex:0 0 auto; width:44px; height:44px; border:1px solid var(--line); border-radius:12px; background:var(--panel); color:var(--muted); font-size:26px; line-height:1; }
107
201
  .curator-close:hover { border-color:#93c5fd; background:var(--accent-soft); color:var(--accent); }
108
202
  .curator-status { margin:0 0 12px; }
203
+ .curator-filters { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:8px; margin:0 0 14px; padding:12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); }
204
+ .curator-filter { display:grid; gap:4px; color:var(--muted); font-size:11px; font-weight:800; }
205
+ .curator-filter-check { display:flex; align-items:center; gap:6px; color:var(--muted); font-size:12px; font-weight:700; }
206
+ .curator-filter-check input { width:16px; height:16px; }
207
+ .curator-filter-tags { grid-column:1 / -1; display:flex; flex-wrap:wrap; gap:7px; padding-top:4px; }
208
+ .curator-filter-tags .tag.active { font-weight:800; text-decoration:underline; }
109
209
  .curator-list { display:grid; gap:12px; }
110
210
  .curator-card { border:1px solid var(--line); border-radius:14px; padding:16px; background:var(--panel); }
111
- .curator-card.selected { border-color:#93c5fd; box-shadow:0 8px 20px rgba(37,99,235,.10); }
112
- .curator-select { display:flex; gap:8px; align-items:center; color:var(--muted); font-size:13px; font-weight:700; }
113
- .curator-select input { width:17px; height:17px; accent-color:#2563eb; }
114
211
  .curator-workspace { overflow-wrap:anywhere; }
115
212
  .curator-card h3 { margin:8px 0; font-size:17px; }
116
213
  .curator-overview { margin:8px 0 12px; color:var(--muted); white-space:pre-wrap; line-height:1.55; }
214
+ .curator-card-actions { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 0; }
215
+ .curator-review-details { margin-top:12px; }
117
216
  .curator-draft { margin:12px 0; padding:12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); }
118
217
  .curator-draft h4 { margin:0 0 10px; font-size:14px; }
119
218
  .curator-draft-label { margin:10px 0 4px; color:var(--muted); font-size:12px; font-weight:700; }
120
219
  .curator-draft-value { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; font:inherit; font-size:13px; line-height:1.5; }
121
220
  .curator-reasons { color:var(--muted); font-size:12px; white-space:pre-wrap; }
221
+ .curator-confirm-modal { position:absolute; inset:0; z-index:2; display:grid; place-items:center; padding:24px; background:rgba(15,23,42,.58); }
222
+ .curator-confirm-dialog { width:min(680px,calc(100vw - 32px)); max-height:min(760px,calc(100vh - 48px)); display:flex; flex-direction:column; min-height:0; background:var(--panel); box-shadow:0 28px 90px rgba(15,23,42,.34); }
223
+ .curator-confirm-dialog .panel-body { min-height:0; overflow-y:auto; }
224
+ .curator-confirm-grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin:0 0 14px; }
225
+ .curator-confirm-field { min-width:0; padding:11px 12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); }
226
+ .curator-confirm-field dt { margin:0 0 5px; color:var(--muted); font-size:12px; font-weight:800; }
227
+ .curator-confirm-field dd { margin:0; overflow-wrap:anywhere; white-space:pre-wrap; }
228
+ .curator-confirm-draft { max-height:280px; overflow:auto; margin:0; padding:12px; border:1px solid var(--line); border-radius:12px; background:var(--surface); white-space:pre-wrap; overflow-wrap:anywhere; font:inherit; font-size:13px; line-height:1.5; }
229
+ .curator-confirm-actions { display:flex; justify-content:flex-end; gap:8px; margin-top:14px; }
230
+ .skill-dialog { width:min(1040px,calc(100vw - 32px)); max-height:min(860px,calc(100vh - 48px)); display:flex; flex-direction:column; min-height:0; background:rgba(255,255,255,.96); box-shadow:0 28px 90px rgba(15,23,42,.28); }
231
+ .skill-dialog .panel-body { min-height:0; overflow-y:auto; }
232
+ .skill-list { display:grid; gap:12px; }
233
+ .skill-card { border:1px solid var(--line); border-radius:14px; padding:16px; background:var(--panel); }
234
+ .skill-card h3 { margin:8px 0; font-size:17px; overflow-wrap:anywhere; }
235
+ .skill-card-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:8px; margin-top:12px; }
236
+ .skill-field { min-width:0; padding:10px 11px; border:1px solid var(--line); border-radius:10px; background:var(--surface); }
237
+ .skill-field dt { margin:0 0 4px; color:var(--muted); font-size:11px; font-weight:800; }
238
+ .skill-field dd { margin:0; overflow-wrap:anywhere; white-space:pre-wrap; font-size:13px; }
239
+ .skill-card-actions { display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; }
240
+ .skill-source-link { color:var(--accent); overflow-wrap:anywhere; }
122
241
  @keyframes curator-backdrop-in { from { opacity:0; } to { opacity:1; } }
123
242
  @keyframes curator-backdrop-out { from { opacity:1; } to { opacity:0; } }
124
243
  @keyframes curator-dialog-in { from { opacity:0; transform:translateY(10px) scale(.98); } to { opacity:1; transform:translateY(0) scale(1); } }
@@ -128,7 +247,7 @@ export const WEB_HTML = String.raw `<!doctype html>
128
247
  }
129
248
  @media (max-width:680px) { .operator-grid { grid-template-columns:1fr; } }
130
249
  @media (max-width:1050px) { .layout { grid-template-columns:180px minmax(280px,1fr); } .editor-panel { grid-column:1 / -1; } }
131
- @media (max-width:680px) { .shell { padding:16px; } .topbar { display:block; position:relative; } .brand { padding-right:56px; } .topbar-side { margin-top:16px; align-self:auto; } .language-picker { position:absolute; top:0; right:0; } .toolbar { justify-content:stretch; margin-top:0; } .topbar-side .control,.topbar-side .search { flex:1 1 100%; width:100%; } .search { min-width:0; } .layout { grid-template-columns:1fr; } .genres-panel { order:0; } .list-panel { order:1; } .editor-panel { order:2; } .entry-list { max-height:none; } .curator-dialog-head { flex-wrap:wrap; } .curator-header-actions { flex:1 1 100%; width:100%; margin-left:0; } .curator-actions { justify-content:flex-end; } }
250
+ @media (max-width:680px) { .shell { padding:16px; } .topbar { display:block; position:relative; } .brand { padding-right:56px; } .topbar-side { margin-top:16px; align-self:auto; } .language-picker { position:absolute; top:0; right:0; } .toolbar { justify-content:stretch; margin-top:0; } .topbar-side .control,.topbar-side .search { flex:1 1 100%; width:100%; } .search { min-width:0; } .layout { grid-template-columns:1fr; } .genres-panel { order:0; } .list-panel { order:1; } .editor-panel { order:2; } .entry-list { max-height:none; } .curator-dialog-head { flex-wrap:wrap; } .curator-header-actions { flex:1 1 100%; width:100%; margin-left:0; } .curator-actions { justify-content:flex-end; } .curator-confirm-grid { grid-template-columns:1fr; } .curator-confirm-actions { flex-direction:column-reverse; } .curator-confirm-actions .button { width:100%; } }
132
251
  </style>
133
252
  </head>
134
253
  <body>
@@ -150,6 +269,7 @@ export const WEB_HTML = String.raw `<!doctype html>
150
269
  <select id="workspace" class="control" aria-label="Workspace" data-i18n-aria-label="workspaceLabel"></select>
151
270
  <input id="search" class="search" type="search" placeholder="Search memory…" aria-label="Search memory…" data-i18n-placeholder="searchPlaceholder" data-i18n-aria-label="searchPlaceholder">
152
271
  <button id="curator-button" class="button" type="button" data-i18n="curator">Curator</button>
272
+ <button id="external-skills-button" class="button" type="button" data-i18n="openExternalSkills">External Skills</button>
153
273
  <button id="refresh" class="button" type="button" data-i18n="refresh">Refresh</button>
154
274
  </div>
155
275
  </div>
@@ -188,14 +308,52 @@ export const WEB_HTML = String.raw `<!doctype html>
188
308
  </div>
189
309
  <div class="panel-body">
190
310
  <p id="curator-description" class="subtitle" data-i18n="curatorDescription">Review reusable knowledge candidates and add them to global memory.</p>
191
- <div id="curator-selection-count" class="curator-selection-count curator-selection-summary">0 selected</div>
311
+ <div id="curator-filters" class="curator-filters" aria-label="Curator filters"></div>
312
+ <div class="curator-selection-summary"><span id="curator-result-count" class="curator-selection-count">0 candidates</span></div>
192
313
  <div id="curator-status" class="status" role="status" aria-live="polite"></div>
193
314
  <div id="curator-list" class="curator-list"></div>
194
315
  </div>
195
316
  </section>
317
+ <div id="curator-confirm-modal" class="curator-confirm-modal" hidden>
318
+ <section id="curator-confirm-panel" class="panel curator-confirm-dialog" role="alertdialog" aria-modal="true" aria-labelledby="curator-confirm-title" aria-describedby="curator-confirm-description">
319
+ <div class="panel-head">
320
+ <h2 id="curator-confirm-title" data-i18n="curatorConfirmTitle">Confirm Global memory addition</h2>
321
+ </div>
322
+ <div class="panel-body">
323
+ <p id="curator-confirm-description" class="subtitle" data-i18n="curatorConfirmDescription">Verify the source, applicability, and complete regenerated draft.</p>
324
+ <dl class="curator-confirm-grid">
325
+ <div class="curator-confirm-field"><dt data-i18n="curatorConfirmSource">Source project</dt><dd id="curator-confirm-source"></dd></div>
326
+ <div class="curator-confirm-field"><dt data-i18n="curatorConfirmSkill">Skill</dt><dd id="curator-confirm-skill"></dd></div>
327
+ <div class="curator-confirm-field"><dt data-i18n="curatorConfirmApplicability">Applicability</dt><dd id="curator-confirm-applicability"></dd></div>
328
+ </dl>
329
+ <h3 data-i18n="curatorConfirmDraft">Draft to add</h3>
330
+ <pre id="curator-confirm-draft" class="curator-confirm-draft"></pre>
331
+ <div id="curator-confirm-status" class="status curator-status" role="status" aria-live="polite"></div>
332
+ <div class="curator-confirm-actions">
333
+ <button id="curator-confirm-cancel" class="button" type="button" data-i18n="curatorCancel">Cancel</button>
334
+ <button id="curator-confirm-submit" class="button primary" type="button" data-i18n="curatorConfirmAction">Add this draft to Global</button>
335
+ </div>
336
+ </div>
337
+ </section>
338
+ </div>
339
+ </div>
340
+ <div id="external-skills-modal" class="curator-modal" hidden>
341
+ <section id="external-skills-panel" class="panel skill-dialog" role="dialog" aria-modal="true" aria-labelledby="external-skills-title" aria-describedby="external-skills-description">
342
+ <div class="panel-head curator-dialog-head">
343
+ <div class="curator-heading"><h2 id="external-skills-title" data-i18n="externalSkills">External Skills</h2><span class="badge" data-i18n="trustBadge">stored data is untrusted / non-actionable</span></div>
344
+ <div class="curator-header-actions"><button id="external-skills-refresh" class="button" type="button" data-i18n="externalSkillsRefresh">Refresh</button><button id="external-skills-close" class="curator-close" type="button" aria-label="Close External Skills" data-i18n-aria-label="externalSkillsClose"><span aria-hidden="true">×</span></button></div>
345
+ </div>
346
+ <div class="panel-body">
347
+ <p id="external-skills-description" class="subtitle" data-i18n="externalSkillsDescription">Review fetched skills as untrusted reference data. Nothing here installs software or runs scripts.</p>
348
+ <div id="external-skills-status" class="status" role="status" aria-live="polite"></div>
349
+ <div id="external-skills-list" class="skill-list"></div>
350
+ </div>
351
+ </section>
196
352
  </div>
197
353
  <script>
198
354
  const i18n = ${WEB_I18N_CONFIG};
355
+ const validExternalSkillListItem = (${externalSkillListItemIsValid.toString()});
356
+ const validExternalSkillEntrySummary = (${externalSkillEntrySummaryIsValid.toString()});
199
357
  const localeStorageKey = 'kiokuko.web.locale';
200
358
  const normalizeLocale = (value) => {
201
359
  if (typeof value !== 'string') return null;
@@ -207,7 +365,14 @@ export const WEB_HTML = String.raw `<!doctype html>
207
365
  if (language === 'zh' && (parts.length === 1 || parts.includes('hans') || parts.includes('cn') || parts.includes('sg'))) return 'zh-CN';
208
366
  return null;
209
367
  };
210
- const readStoredLocale = () => { try { return localStorage.getItem(localeStorageKey); } catch { return null; } };
368
+ const isStorageAccessError = (error) => typeof DOMException !== 'undefined' && error instanceof DOMException;
369
+ const readStoredLocale = () => {
370
+ try { return localStorage.getItem(localeStorageKey); }
371
+ catch (error) {
372
+ if (!isStorageAccessError(error)) throw error;
373
+ return null;
374
+ }
375
+ };
211
376
  const browserLocales = Array.isArray(navigator.languages) ? navigator.languages : [navigator.language];
212
377
  const localeCandidates = [new URLSearchParams(location.search).get('lang'), readStoredLocale(), ...browserLocales];
213
378
  const initialLocale = localeCandidates.map(normalizeLocale).find(Boolean) || i18n.defaultLocale;
@@ -217,7 +382,8 @@ export const WEB_HTML = String.raw `<!doctype html>
217
382
  const botModes = [
218
383
  ['bot:common', 'bot.common'], ['bot:researcher', 'bot.researcher'], ['bot:builder', 'bot.builder'], ['bot:reviewer', 'bot.reviewer'], ['bot:devops', 'bot.devops'], ['bot:writer', 'bot.writer'], ['bot:analyst', 'bot.analyst']
219
384
  ];
220
- const state = { locale: initialLocale, workspace: '', kind: 'all', tag: '', query: '', entries: [], tags: [], selected: null, runs: [], selectedRun: null, curatorCandidates: [], curatorSelected: new Set(), curatorGlobalized: new Set(), curatorOpen: false, curatorPreviousFocus: null, localizedStatus: null };
385
+ const curatorUrl = new URLSearchParams(location.search);
386
+ const state = { locale: initialLocale, workspace: '', kind: 'all', tag: '', query: '', entries: [], recallItems: [], tags: [], selected: null, selectedRecall: null, runs: [], selectedRun: null, curatorCandidates: [], curatorGlobalized: new Set(), curatorNextCursor: null, curatorTotalApproximate: 0, curatorOpen: false, curatorPreviousFocus: null, curatorPendingCandidate: null, curatorConfirmationTrigger: null, curatorGlobalizeBusy: false, externalSkills: [], externalSkillsOpen: false, externalSkillsPreviousFocus: null, externalSkillsBusy: false, externalSkillsPendingDisable: null, localizedStatus: null, curatorSearch: curatorUrl.get('search') || '', curatorProject: curatorUrl.get('project') || '', curatorTags: new Set(curatorUrl.getAll('tag')), curatorFramework: curatorUrl.get('framework') || '', curatorLanguage: curatorUrl.get('language') || '', curatorMemoryClass: curatorUrl.get('memoryClass') || '', curatorTier: curatorUrl.get('tier') || '', curatorReady: curatorUrl.get('ready') === 'true', curatorIncludeGlobalized: curatorUrl.get('includeGlobalized') === 'true', curatorFacets: null };
221
387
  const $ = (id) => document.getElementById(id);
222
388
  const t = (key, parameters = {}) => {
223
389
  const template = i18n.messages[state.locale]?.[key] ?? i18n.messages[i18n.defaultLocale]?.[key] ?? key;
@@ -230,6 +396,7 @@ export const WEB_HTML = String.raw `<!doctype html>
230
396
  const setI18nText = (element, key) => { element.dataset.i18n = key; element.textContent = t(key); return element; };
231
397
  const labelForKind = (kind) => { const item = kinds.find(([value]) => value === kind); return item ? t(item[1]) : kind; };
232
398
  const labelForStatus = (status) => i18n.messages[state.locale]?.['status.' + status] ?? status;
399
+ const labelForOrigin = (origin) => t(origin === 'ecosystem' ? 'originEcosystem' : origin === 'global' ? 'originGlobal' : 'originProject');
233
400
  const updateEditorState = () => {
234
401
  const element = $('editor-state');
235
402
  if (state.selected) { delete element.dataset.i18n; element.dataset.i18nStatus = state.selected.status; element.textContent = labelForStatus(state.selected.status); }
@@ -264,9 +431,10 @@ export const WEB_HTML = String.raw `<!doctype html>
264
431
  };
265
432
  const selectLocale = (value) => {
266
433
  state.locale = normalizeLocale(value) || i18n.defaultLocale;
267
- try { localStorage.setItem(localeStorageKey, state.locale); } catch {}
434
+ try { localStorage.setItem(localeStorageKey, state.locale); }
435
+ catch (error) { if (!isStorageAccessError(error)) throw error; }
268
436
  setLanguageMenuOpen(false, true);
269
- applyTranslations(); renderFilters(); renderEntries(); renderRuns(); renderRunDetail(); renderCurator(); updateEditorState();
437
+ applyTranslations(); renderFilters(); renderEntries(); renderRuns(); renderRunDetail(); renderCurator(); renderExternalSkills(); updateEditorState();
270
438
  };
271
439
  const applyTranslations = () => {
272
440
  document.documentElement.lang = state.locale;
@@ -280,10 +448,24 @@ export const WEB_HTML = String.raw `<!doctype html>
280
448
  renderLanguageMenu();
281
449
  if (state.localizedStatus) showStatus(state.localizedStatus.plural ? tp(state.localizedStatus.key, state.localizedStatus.count) : t(state.localizedStatus.key, state.localizedStatus.parameters), state.localizedStatus.error);
282
450
  };
451
+ const apiError = (value) => {
452
+ const rawCode = value?.error?.code;
453
+ const rawMessage = value?.error?.message;
454
+ const code = typeof rawCode === 'string' && /^[A-Z][A-Z0-9_]{0,63}$/.test(rawCode) ? rawCode : 'REQUEST_FAILED';
455
+ const normalizedMessage = typeof rawMessage === 'string' ? rawMessage.replace(/[\u0000-\u001f\u007f]+/g, ' ').trim().slice(0, 300) : '';
456
+ const error = new Error(normalizedMessage || t('requestFailed'));
457
+ error.code = code;
458
+ return error;
459
+ };
460
+ const apiErrorText = (error) => {
461
+ const code = error instanceof Error && typeof error.code === 'string' && /^[A-Z][A-Z0-9_]{0,63}$/.test(error.code) ? error.code : 'REQUEST_FAILED';
462
+ const normalizedMessage = error instanceof Error ? error.message.replace(/[\u0000-\u001f\u007f]+/g, ' ').trim().slice(0, 300) : '';
463
+ return '[' + code + '] ' + (normalizedMessage || t('requestFailed'));
464
+ };
283
465
  const api = async (path, options) => {
284
466
  const response = await fetch(path, options);
285
467
  const value = await response.json();
286
- if (!response.ok) throw new Error(value.error?.message || t('requestFailed'));
468
+ if (!response.ok) throw apiError(value);
287
469
  return value;
288
470
  };
289
471
  const escapeTags = (value) => value.split(',').map((item) => item.trim()).filter(Boolean);
@@ -298,6 +480,55 @@ export const WEB_HTML = String.raw `<!doctype html>
298
480
  if (blocked) appMain.setAttribute('aria-hidden', 'true');
299
481
  else appMain.removeAttribute('aria-hidden');
300
482
  };
483
+ function restoreCuratorUrl() {
484
+ const params = new URLSearchParams(location.search);
485
+ state.curatorSearch = params.get('search') || '';
486
+ state.curatorProject = params.get('project') || '';
487
+ state.curatorTags = new Set(params.getAll('tag'));
488
+ state.curatorFramework = params.get('framework') || '';
489
+ state.curatorLanguage = params.get('language') || '';
490
+ state.curatorMemoryClass = params.get('memoryClass') || '';
491
+ state.curatorTier = params.get('tier') || '';
492
+ state.curatorReady = params.get('ready') === 'true';
493
+ state.curatorIncludeGlobalized = params.get('includeGlobalized') === 'true';
494
+ }
495
+ function syncCuratorUrl(push = false) {
496
+ const params = new URLSearchParams(location.search);
497
+ for (const key of ['search', 'project', 'framework', 'language', 'memoryClass', 'tier', 'ready', 'includeGlobalized', 'tag']) params.delete(key);
498
+ if (state.curatorSearch) params.set('search', state.curatorSearch);
499
+ if (state.curatorProject) params.set('project', state.curatorProject);
500
+ if (state.curatorFramework) params.set('framework', state.curatorFramework);
501
+ if (state.curatorLanguage) params.set('language', state.curatorLanguage);
502
+ if (state.curatorMemoryClass) params.set('memoryClass', state.curatorMemoryClass);
503
+ if (state.curatorTier) params.set('tier', state.curatorTier);
504
+ if (state.curatorReady) params.set('ready', 'true');
505
+ if (state.curatorIncludeGlobalized) params.set('includeGlobalized', 'true');
506
+ for (const tag of state.curatorTags) params.append('tag', tag);
507
+ const nextUrl = location.pathname + (params.toString() ? '?' + params.toString() : '');
508
+ if (push) history.pushState(null, '', nextUrl); else history.replaceState(null, '', nextUrl);
509
+ }
510
+ function curatorFilterSelect(label, values, value, onChange) {
511
+ const wrapper = document.createElement('label'); wrapper.className = 'curator-filter';
512
+ const text = document.createElement('span'); text.textContent = label;
513
+ const select = document.createElement('select');
514
+ const all = document.createElement('option'); all.value = ''; all.textContent = 'All'; select.append(all);
515
+ for (const item of values || []) { const option = document.createElement('option'); option.value = item.value || item.workspace; option.textContent = item.name || item.value || item.workspace; select.append(option); }
516
+ select.value = value || ''; select.addEventListener('change', () => onChange(select.value)); wrapper.append(text, select); return wrapper;
517
+ }
518
+ function renderCuratorFilters() {
519
+ const root = $('curator-filters'); if (!root) return;
520
+ const facets = state.curatorFacets || {};
521
+ const search = document.createElement('input'); search.type = 'search'; search.placeholder = 'Search candidates'; search.value = state.curatorSearch; search.addEventListener('input', () => { state.curatorSearch = search.value.trim(); clearTimeout(state.curatorSearchTimer); state.curatorSearchTimer = setTimeout(() => { syncCuratorUrl(true); loadCurator(); }, 180); });
522
+ const project = curatorFilterSelect('Project', facets.projects, state.curatorProject, (value) => { state.curatorProject = value; syncCuratorUrl(true); loadCurator(); });
523
+ const framework = curatorFilterSelect('Framework', facets.frameworks, state.curatorFramework, (value) => { state.curatorFramework = value; syncCuratorUrl(true); loadCurator(); });
524
+ const language = curatorFilterSelect('Language', facets.languages, state.curatorLanguage, (value) => { state.curatorLanguage = value; syncCuratorUrl(true); loadCurator(); });
525
+ const memoryClass = curatorFilterSelect('Memory class', facets.memoryClasses, state.curatorMemoryClass, (value) => { state.curatorMemoryClass = value; syncCuratorUrl(true); loadCurator(); });
526
+ const readiness = curatorFilterSelect('Readiness', [{ value: 'portable', name: 'Portable' }, { value: 'repeated', name: 'Repeated' }, { value: 'observed', name: 'Observed' }], state.curatorTier, (value) => { state.curatorTier = value; syncCuratorUrl(true); loadCurator(); });
527
+ const ready = document.createElement('label'); ready.className = 'curator-filter-check'; const readyInput = document.createElement('input'); readyInput.type = 'checkbox'; readyInput.checked = state.curatorReady; readyInput.addEventListener('change', () => { state.curatorReady = readyInput.checked; syncCuratorUrl(true); loadCurator(); }); ready.append(readyInput, document.createTextNode('Skill-ready'));
528
+ const globalized = document.createElement('label'); globalized.className = 'curator-filter-check'; const globalizedInput = document.createElement('input'); globalizedInput.type = 'checkbox'; globalizedInput.checked = state.curatorIncludeGlobalized; globalizedInput.addEventListener('change', () => { state.curatorIncludeGlobalized = globalizedInput.checked; syncCuratorUrl(true); loadCurator(); }); globalized.append(globalizedInput, document.createTextNode('Show already globalized'));
529
+ const tags = document.createElement('div'); tags.className = 'curator-filter-tags'; for (const facet of facets.tags || []) { const button = document.createElement('button'); button.type = 'button'; button.className = 'tag' + (state.curatorTags.has(facet.value) ? ' active' : ''); button.textContent = '#' + facet.value + ' (' + facet.count + ')'; button.addEventListener('click', () => { if (state.curatorTags.has(facet.value)) state.curatorTags.delete(facet.value); else state.curatorTags.add(facet.value); syncCuratorUrl(true); loadCurator(); }); tags.append(button); }
530
+ root.replaceChildren(search, project, framework, language, memoryClass, readiness, ready, globalized, tags);
531
+ }
301
532
  function openCurator() {
302
533
  if (state.curatorOpen) { $('curator-close')?.focus(); return; }
303
534
  state.curatorPreviousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -309,6 +540,8 @@ export const WEB_HTML = String.raw `<!doctype html>
309
540
  requestAnimationFrame(() => $('curator-close')?.focus());
310
541
  }
311
542
  function closeCurator() {
543
+ if (state.curatorGlobalizeBusy) return;
544
+ if (state.curatorPendingCandidate) closeCuratorConfirmation(false);
312
545
  const modal = $('curator-modal');
313
546
  if (modal.hidden) return;
314
547
  state.curatorOpen = false;
@@ -340,6 +573,242 @@ export const WEB_HTML = String.raw `<!doctype html>
340
573
  else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
341
574
  }
342
575
 
576
+ const setExternalSkillsStatus = (message, error = false) => {
577
+ const element = $('external-skills-status');
578
+ element.textContent = message;
579
+ element.className = error ? 'status notice error curator-status' : 'status curator-status';
580
+ };
581
+ function openExternalSkills() {
582
+ if (state.externalSkillsOpen) { $('external-skills-close')?.focus(); return; }
583
+ state.externalSkillsPreviousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
584
+ state.externalSkillsOpen = true;
585
+ const modal = $('external-skills-modal');
586
+ modal.classList.remove('is-closing');
587
+ setCuratorBackgroundBlocked(true);
588
+ modal.hidden = false;
589
+ requestAnimationFrame(() => $('external-skills-close')?.focus());
590
+ }
591
+ function closeExternalSkills() {
592
+ if (state.externalSkillsBusy) return;
593
+ const modal = $('external-skills-modal');
594
+ if (modal.hidden) return;
595
+ state.externalSkillsOpen = false;
596
+ state.externalSkillsPendingDisable = null;
597
+ modal.classList.add('is-closing');
598
+ let finished = false;
599
+ const finish = () => {
600
+ if (finished) return;
601
+ finished = true;
602
+ modal.hidden = true;
603
+ modal.classList.remove('is-closing');
604
+ setCuratorBackgroundBlocked(false);
605
+ const previousFocus = state.externalSkillsPreviousFocus;
606
+ state.externalSkillsPreviousFocus = null;
607
+ if (previousFocus?.isConnected) previousFocus.focus(); else $('external-skills-button')?.focus();
608
+ };
609
+ modal.addEventListener('animationend', (event) => { if (event.target === modal) finish(); }, { once: true });
610
+ window.setTimeout(finish, 220);
611
+ }
612
+ function handleExternalSkillsKeydown(event) {
613
+ if (event.key === 'Escape') { event.preventDefault(); closeExternalSkills(); return; }
614
+ if (event.key !== 'Tab') return;
615
+ const modal = $('external-skills-modal');
616
+ const focusable = [...modal.querySelectorAll('button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])')];
617
+ if (!focusable.length) { event.preventDefault(); return; }
618
+ const first = focusable[0]; const last = focusable.at(-1);
619
+ if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
620
+ else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
621
+ }
622
+ function skillField(labelKey, value) {
623
+ const field = document.createElement('div'); field.className = 'skill-field';
624
+ const label = document.createElement('dt'); setI18nText(label, labelKey);
625
+ const content = document.createElement('dd'); content.textContent = value || t('unknown');
626
+ field.append(label, content); return field;
627
+ }
628
+ function externalSkillButton(attribute, skillId) {
629
+ return [...document.querySelectorAll('[' + attribute + ']')].find((element) => element.getAttribute(attribute) === skillId);
630
+ }
631
+ function externalSkillSourceUrl(skill) {
632
+ return /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}$/.test(skill.sourceLocator) ? 'https://github.com/' + skill.sourceLocator : null;
633
+ }
634
+ function renderExternalSkills() {
635
+ if (!state.externalSkillsOpen) return;
636
+ const root = $('external-skills-list');
637
+ if (!state.externalSkills.length) { const empty = document.createElement('div'); empty.className = 'editor-empty'; setI18nText(empty, 'externalSkillsEmpty'); root.replaceChildren(empty); return; }
638
+ root.replaceChildren(...state.externalSkills.map((skill) => {
639
+ const card = document.createElement('article'); card.className = 'skill-card';
640
+ const meta = document.createElement('div'); meta.className = 'entry-meta';
641
+ const stateBadge = document.createElement('span'); stateBadge.className = 'badge ' + (skill.state === 'disabled' ? 'superseded' : skill.state === 'imported' ? 'verified' : 'candidate'); stateBadge.textContent = skill.state;
642
+ const official = document.createElement('span'); official.className = 'badge'; official.textContent = skill.officialStatus;
643
+ meta.append(stateBadge, official);
644
+ const heading = document.createElement('h3'); heading.textContent = skill.name;
645
+ const fields = document.createElement('dl'); fields.className = 'skill-card-grid';
646
+ fields.append(
647
+ skillField('externalSkillsSource', skill.sourceLocator + '/' + skill.slug),
648
+ skillField('externalSkillsCommit', skill.sourceCommit ? skill.sourceCommit.slice(0, 12) : null),
649
+ skillField('externalSkillsSnapshot', skill.snapshotHash ? skill.snapshotHash.slice(0, 12) : null),
650
+ skillField('externalSkillsDocuments', String(skill.metadata?.documents ?? 0)),
651
+ skillField('externalSkillsFirstSeen', skill.firstSeenAt),
652
+ skillField('externalSkillsLastChecked', skill.lastCheckedAt),
653
+ skillField('externalSkillsTechnology', skill.metadata?.technology ?? skill.name),
654
+ skillField('externalSkillsAudit', skill.auditStatus),
655
+ skillField('externalSkillsState', skill.state),
656
+ );
657
+ const notice = document.createElement('div'); notice.className = 'notice'; setI18nText(notice, 'externalSkillsUntrusted');
658
+ const actions = document.createElement('div'); actions.className = 'skill-card-actions';
659
+ const sourceUrl = externalSkillSourceUrl(skill);
660
+ if (sourceUrl) { const source = document.createElement('a'); source.className = 'button'; source.href = sourceUrl; source.target = '_blank'; source.rel = 'noopener noreferrer'; setI18nText(source, 'externalSkillsSourceUrl'); actions.append(source); }
661
+ const refresh = document.createElement('button'); refresh.type = 'button'; refresh.className = 'button'; refresh.setAttribute('data-external-skill-refresh', skill.skillId); setI18nText(refresh, 'externalSkillsRefresh'); refresh.disabled = state.externalSkillsBusy; refresh.addEventListener('click', () => externalSkillAction(skill.skillId, 'refresh')); actions.append(refresh);
662
+ const action = skill.state === 'disabled' ? 'enable' : skill.state === 'imported' ? 'disable' : null;
663
+ if (action === 'disable' && state.externalSkillsPendingDisable === skill.skillId) {
664
+ const confirmation = document.createElement('div'); confirmation.className = 'notice'; confirmation.textContent = t('externalSkillsDisableConfirm');
665
+ const confirm = document.createElement('button'); confirm.type = 'button'; confirm.className = 'button primary'; confirm.setAttribute('data-external-skill-confirm', skill.skillId); setI18nText(confirm, 'externalSkillsDisable'); confirm.disabled = state.externalSkillsBusy; confirm.addEventListener('click', () => externalSkillAction(skill.skillId, action, true));
666
+ const cancel = document.createElement('button'); cancel.type = 'button'; cancel.className = 'button'; setI18nText(cancel, 'curatorCancel'); cancel.disabled = state.externalSkillsBusy; cancel.addEventListener('click', () => { state.externalSkillsPendingDisable = null; renderExternalSkills(); requestAnimationFrame(() => externalSkillButton('data-external-skill-toggle', skill.skillId)?.focus()); });
667
+ actions.append(confirmation, confirm, cancel);
668
+ } else if (action !== null) {
669
+ const toggle = document.createElement('button'); toggle.type = 'button'; toggle.className = 'button primary'; toggle.setAttribute('data-external-skill-toggle', skill.skillId); setI18nText(toggle, action === 'enable' ? 'externalSkillsEnable' : 'externalSkillsDisable'); toggle.disabled = state.externalSkillsBusy; toggle.addEventListener('click', () => externalSkillAction(skill.skillId, action)); actions.append(toggle);
670
+ }
671
+ const mapping = document.createElement('div'); mapping.className = 'detail-text'; mapping.hidden = true;
672
+ const viewEntries = document.createElement('button'); viewEntries.type = 'button'; viewEntries.className = 'button'; viewEntries.setAttribute('aria-expanded', 'false'); setI18nText(viewEntries, 'externalSkillsViewEntries'); viewEntries.disabled = state.externalSkillsBusy; viewEntries.addEventListener('click', async () => {
673
+ viewEntries.disabled = true;
674
+ try {
675
+ const detail = externalSkillDetail(await api('/api/skills/' + encodeURIComponent(skill.skillId)), skill.skillId);
676
+ if (detail === null) throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
677
+ const entries = detail.entries;
678
+ mapping.textContent = entries.length ? entries.map((entry) => entry.sourcePath + '#' + entry.chunkIndex + ' → ' + entry.entryId + '@' + entry.revision + (entry.active ? '' : ' (inactive)')).join('\n') : t('externalSkillsNoEntries');
679
+ mapping.hidden = false;
680
+ viewEntries.setAttribute('aria-expanded', 'true');
681
+ } catch {
682
+ mapping.textContent = '';
683
+ mapping.hidden = true;
684
+ viewEntries.setAttribute('aria-expanded', 'false');
685
+ setExternalSkillsStatus(t('externalSkillsFailure'), true);
686
+ }
687
+ finally { viewEntries.disabled = state.externalSkillsBusy; }
688
+ });
689
+ actions.append(viewEntries);
690
+ card.append(meta, heading, fields, notice, actions, mapping); return card;
691
+ }));
692
+ }
693
+ async function reloadExternalSkills() {
694
+ const skills = [];
695
+ const skillIds = new Set();
696
+ const cursors = new Set();
697
+ let cursor = null;
698
+ for (let page = 0; page < 100; page += 1) {
699
+ const result = await api('/api/skills?limit=200' + (cursor === null ? '' : '&cursor=' + encodeURIComponent(cursor)));
700
+ if (!result || typeof result !== 'object' || Array.isArray(result)
701
+ || Object.keys(result).length !== 5
702
+ || result.untrusted !== true
703
+ || !Array.isArray(result.skills) || result.skills.length > 200
704
+ || !Number.isSafeInteger(result.count) || result.count !== result.skills.length
705
+ || typeof result.truncated !== 'boolean'
706
+ || result.nextCursor !== null && typeof result.nextCursor !== 'string') throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
707
+ if (skills.length + result.skills.length > 20_000) throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
708
+ for (const skill of result.skills) {
709
+ if (!validExternalSkillListItem(skill) || skillIds.has(skill.skillId)) throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
710
+ skillIds.add(skill.skillId); skills.push(skill);
711
+ }
712
+ if (!result.truncated) {
713
+ if (result.nextCursor !== null) throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
714
+ state.externalSkills = skills;
715
+ renderExternalSkills();
716
+ return;
717
+ }
718
+ if (result.skills.length === 0 || typeof result.nextCursor !== 'string' || result.nextCursor.length === 0 || cursors.has(result.nextCursor)) throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
719
+ cursors.add(result.nextCursor); cursor = result.nextCursor;
720
+ }
721
+ throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
722
+ }
723
+ function externalSkillDetail(result, expectedSkillId) {
724
+ if (!result || typeof result !== 'object' || Array.isArray(result)
725
+ || Object.keys(result).length !== 4 || result.untrusted !== true
726
+ || !validExternalSkillListItem(result.skill) || result.skill.skillId !== expectedSkillId
727
+ || !Array.isArray(result.entries) || result.entries.length > 200
728
+ || typeof result.entriesTruncated !== 'boolean'
729
+ || result.entriesTruncated && result.entries.length !== 200
730
+ || result.skill.sourceCommit === null && result.entries.length !== 0) return null;
731
+ const entryIds = new Set(); const mappingIds = new Set();
732
+ for (const entry of result.entries) {
733
+ const mappingId = validExternalSkillEntrySummary(entry) ? entry.sourcePath + '\u0000' + entry.chunkIndex : null;
734
+ if (mappingId === null || entryIds.has(entry.entryId) || mappingIds.has(mappingId)) return null;
735
+ entryIds.add(entry.entryId); mappingIds.add(mappingId);
736
+ }
737
+ return { skill: result.skill, entries: result.entries, entriesTruncated: result.entriesTruncated };
738
+ }
739
+ async function reloadExternalSkill(skillId) {
740
+ const detail = externalSkillDetail(await api('/api/skills/' + encodeURIComponent(skillId)), skillId);
741
+ if (detail === null) throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
742
+ const index = state.externalSkills.findIndex((skill) => skill.skillId === skillId);
743
+ if (index < 0) throw apiError({ error: { code: 'INVALID_RESPONSE', message: t('requestFailed') } });
744
+ state.externalSkills[index] = detail.skill;
745
+ renderExternalSkills();
746
+ }
747
+ async function externalSkillAction(skillId, action, confirmed = false) {
748
+ if (state.externalSkillsBusy) return;
749
+ if (action === 'disable' && !confirmed) { state.externalSkillsPendingDisable = skillId; renderExternalSkills(); requestAnimationFrame(() => externalSkillButton('data-external-skill-confirm', skillId)?.focus()); return; }
750
+ state.externalSkillsPendingDisable = null;
751
+ state.externalSkillsBusy = true;
752
+ $('external-skills-refresh').disabled = true;
753
+ $('external-skills-panel').setAttribute('aria-busy', 'true');
754
+ setExternalSkillsStatus(action === 'refresh' ? t('externalSkillsRefreshing') : t('externalSkillsLoading'));
755
+ renderExternalSkills();
756
+ try {
757
+ let actionError = null;
758
+ try {
759
+ await api('/api/skills/' + encodeURIComponent(skillId) + '/' + action, { method: 'POST' });
760
+ } catch (error) {
761
+ actionError = error;
762
+ }
763
+ let reloadError = null;
764
+ try {
765
+ await reloadExternalSkill(skillId);
766
+ } catch (error) {
767
+ reloadError = error;
768
+ }
769
+ if (reloadError) {
770
+ state.externalSkills = [];
771
+ state.externalSkillsPendingDisable = null;
772
+ }
773
+ if (actionError) {
774
+ const reloadFailure = reloadError ? ' / ' + apiErrorText(reloadError) : '';
775
+ setExternalSkillsStatus(apiErrorText(actionError) + reloadFailure, true);
776
+ } else if (reloadError) {
777
+ setExternalSkillsStatus(apiErrorText(reloadError), true);
778
+ } else {
779
+ setExternalSkillsStatus(t(action === 'disable' ? 'externalSkillsDisabled' : action === 'enable' ? 'externalSkillsEnabled' : 'externalSkillsRefreshed'));
780
+ }
781
+ } finally {
782
+ state.externalSkillsBusy = false;
783
+ $('external-skills-refresh').disabled = false;
784
+ $('external-skills-panel').removeAttribute('aria-busy');
785
+ renderExternalSkills();
786
+ requestAnimationFrame(() => externalSkillButton(action === 'refresh' ? 'data-external-skill-refresh' : 'data-external-skill-toggle', skillId)?.focus());
787
+ }
788
+ }
789
+ async function loadExternalSkills(open = true) {
790
+ if (state.externalSkillsBusy) return;
791
+ if (open) openExternalSkills();
792
+ state.externalSkills = [];
793
+ state.externalSkillsPendingDisable = null;
794
+ state.externalSkillsBusy = true;
795
+ $('external-skills-refresh').disabled = true;
796
+ renderExternalSkills();
797
+ setExternalSkillsStatus(t('externalSkillsLoading'));
798
+ $('external-skills-panel').setAttribute('aria-busy', 'true');
799
+ try {
800
+ await reloadExternalSkills();
801
+ setExternalSkillsStatus('');
802
+ } catch (error) {
803
+ setExternalSkillsStatus(t('externalSkillsOffline') + ' ' + apiErrorText(error), true);
804
+ } finally {
805
+ $('external-skills-panel').removeAttribute('aria-busy');
806
+ state.externalSkillsBusy = false;
807
+ $('external-skills-refresh').disabled = false;
808
+ renderExternalSkills();
809
+ }
810
+ }
811
+
343
812
  function filterButton(key, label, count, active, onClick) {
344
813
  const button = document.createElement('button');
345
814
  button.type = 'button';
@@ -369,10 +838,34 @@ export const WEB_HTML = String.raw `<!doctype html>
369
838
  if (tagButtons.length > 0) root.append(filterGroup(t('crossTagFilterTitle'), tagButtons));
370
839
  }
371
840
 
841
+ function renderFederatedItems() {
842
+ const list = $('entry-list');
843
+ list.replaceChildren(...state.recallItems.map((item) => {
844
+ const card = document.createElement('article'); card.className = 'entry-card federated-card ' + (item.origin === 'global' ? 'global' : '');
845
+ const meta = document.createElement('div'); meta.className = 'entry-meta';
846
+ const origin = document.createElement('span'); origin.className = 'badge origin-' + item.origin; origin.textContent = labelForOrigin(item.origin);
847
+ const kind = document.createElement('span'); kind.className = 'badge'; kind.textContent = labelForKind(item.kind);
848
+ const status = document.createElement('span'); status.className = 'badge ' + item.status; status.textContent = labelForStatus(item.status);
849
+ meta.append(origin, kind, status);
850
+ const title = document.createElement('h3'); title.textContent = item.title;
851
+ const snippet = document.createElement('p'); snippet.className = 'snippet'; snippet.textContent = item.snippet;
852
+ const source = document.createElement('div'); source.className = 'federated-source';
853
+ const sourceLines = [];
854
+ if (item.sourceWorkspace) sourceLines.push(t('sourceWorkspace', { workspace: item.sourceWorkspace }));
855
+ if (item.sourceProject) sourceLines.push(t('sourceProject', { project: item.sourceProject }));
856
+ source.textContent = sourceLines.join('\n');
857
+ const tags = document.createElement('div'); tags.className = 'tags'; item.tags.forEach((tag) => { const tagItem = document.createElement('span'); tagItem.className = 'tag'; tagItem.textContent = '#' + tag; tags.append(tagItem); });
858
+ const reasons = document.createElement('div'); reasons.className = 'selection-reasons'; reasons.textContent = [t('selectionReasons'), ...item.selectionReasons.map((reason) => '• ' + reason)].join('\n');
859
+ card.append(meta, title, snippet, source, tags, reasons); card.addEventListener('click', () => { state.selected = null; state.selectedRecall = item; renderEntries(); renderEditor(); }); return card;
860
+ }));
861
+ }
862
+
372
863
  function renderEntries() {
373
- $('result-count').textContent = tp('entryCount', state.entries.length);
374
- $('list-title').textContent = state.tag ? '#' + state.tag : (state.kind === 'all' ? t('entriesTitle') : labelForKind(state.kind));
864
+ const displayed = state.recallItems.length > 0 ? state.recallItems.length : state.entries.length;
865
+ $('result-count').textContent = tp('entryCount', displayed);
866
+ $('list-title').textContent = state.recallItems.length > 0 ? t('entriesTitle') : (state.tag ? '#' + state.tag : (state.kind === 'all' ? t('entriesTitle') : labelForKind(state.kind)));
375
867
  const list = $('entry-list');
868
+ if (state.recallItems.length > 0) { renderFederatedItems(); return; }
376
869
  if (!state.entries.length) {
377
870
  const empty = document.createElement('div'); empty.className = 'editor-empty'; setI18nText(empty, 'noEntries'); list.replaceChildren(empty); return;
378
871
  }
@@ -390,7 +883,26 @@ export const WEB_HTML = String.raw `<!doctype html>
390
883
  }));
391
884
  }
392
885
 
886
+ function renderFederatedDetail() {
887
+ const editor = $('editor'); const item = state.selectedRecall;
888
+ if (!item) return;
889
+ const origin = document.createElement('span'); origin.className = 'badge origin-' + item.origin; origin.textContent = labelForOrigin(item.origin);
890
+ const heading = document.createElement('h3'); heading.textContent = item.title;
891
+ const meta = document.createElement('div'); meta.className = 'entry-meta'; meta.append(origin);
892
+ if (item.sourceWorkspace) { const source = document.createElement('span'); source.className = 'badge'; source.textContent = t('sourceWorkspace', { workspace: item.sourceWorkspace }); meta.append(source); }
893
+ if (item.sourceProject) { const source = document.createElement('span'); source.className = 'badge'; source.textContent = t('sourceProject', { project: item.sourceProject }); meta.append(source); }
894
+ const notice = document.createElement('div'); notice.className = 'notice'; notice.textContent = t('federatedReadOnly');
895
+ const detail = document.createElement('div'); detail.className = 'federated-detail';
896
+ const snippet = document.createElement('div'); snippet.className = 'detail-text'; snippet.textContent = item.snippet;
897
+ const reasons = detailBlock('selectionReasons', item.selectionReasons.join('\n'));
898
+ const tags = detailBlock('commaSeparatedTags', item.tags.map((tag) => '#' + tag).join(' '));
899
+ detail.append(snippet, reasons, tags);
900
+ const stateBadge = $('editor-state'); delete stateBadge.dataset.i18n; delete stateBadge.dataset.i18nStatus; stateBadge.textContent = labelForOrigin(item.origin);
901
+ editor.replaceChildren(meta, heading, notice, detail);
902
+ }
903
+
393
904
  function renderEditor() {
905
+ if (state.selectedRecall) { renderFederatedDetail(); return; }
394
906
  const editor = $('editor');
395
907
  const entry = state.selected;
396
908
  updateEditorState();
@@ -418,34 +930,106 @@ export const WEB_HTML = String.raw `<!doctype html>
418
930
  }); editor.replaceChildren(form);
419
931
  }
420
932
 
421
- async function selectEntry(id) { try { const result = await api('/api/entries/' + encodeURIComponent(id) + '?workspace=' + encodeURIComponent(state.workspace)); state.selected = result.entry; renderEntries(); renderEditor(); } catch (error) { setStatus(error.message, true); } }
933
+ async function selectEntry(id) { try { const result = await api('/api/entries/' + encodeURIComponent(id) + '?workspace=' + encodeURIComponent(state.workspace)); state.selectedRecall = null; state.selected = result.entry; renderEntries(); renderEditor(); } catch (error) { setStatus(error.message, true); } }
422
934
 
423
935
  const curatorCandidateKey = (candidate) => candidate.workspace + '\u0000' + candidate.entryId;
424
936
 
425
- async function applySelectedCurator() {
426
- const selected = state.curatorCandidates.filter((candidate) => state.curatorSelected.has(curatorCandidateKey(candidate)) && !state.curatorGlobalized.has(curatorCandidateKey(candidate)));
427
- if (!selected.length) return;
428
- let added = 0;
429
- let failed = 0;
430
- for (const candidate of selected) {
431
- try {
432
- await api('/api/curator/globalize?workspace=' + encodeURIComponent(candidate.workspace), {
433
- method: 'POST',
434
- headers: { 'content-type': 'application/json' },
435
- body: JSON.stringify({ workspace: candidate.workspace, entryId: candidate.entryId, expectedRevision: candidate.revision }),
436
- });
437
- state.curatorGlobalized.add(curatorCandidateKey(candidate));
438
- added += 1;
439
- } catch (error) {
440
- failed += 1;
441
- setStatus(error.message, true);
442
- }
937
+ function setCuratorConfirmationStatus(message, error = false) {
938
+ const element = $('curator-confirm-status');
939
+ element.textContent = message;
940
+ element.className = error ? 'status notice error curator-status' : 'status curator-status';
941
+ }
942
+
943
+ function openCuratorConfirmation(candidate, trigger) {
944
+ if (state.curatorGlobalizeBusy || state.curatorGlobalized.has(curatorCandidateKey(candidate))) return;
945
+ state.curatorPendingCandidate = candidate;
946
+ state.curatorConfirmationTrigger = trigger instanceof HTMLElement ? trigger : null;
947
+ $('curator-confirm-source').textContent = candidate.projectName || candidate.workspace;
948
+ $('curator-confirm-skill').textContent = candidate.skillName;
949
+ $('curator-confirm-applicability').textContent = candidate.applicability ? JSON.stringify(candidate.applicability, null, 2) : t('curatorApplicabilityNone');
950
+ $('curator-confirm-draft').textContent = [
951
+ t('curatorDraftTitle') + '\n' + candidate.draft.title,
952
+ t('curatorDraftSummary') + '\n' + candidate.draft.summary,
953
+ t('curatorDraftBody') + '\n' + candidate.draft.body,
954
+ t('curatorDraftVersion') + '\n' + candidate.draft.version,
955
+ ].join('\n\n');
956
+ setCuratorConfirmationStatus('');
957
+ const panel = $('curator-panel');
958
+ panel.inert = true;
959
+ panel.setAttribute('aria-hidden', 'true');
960
+ const submit = $('curator-confirm-submit');
961
+ submit.disabled = false;
962
+ setI18nText(submit, 'curatorConfirmAction');
963
+ $('curator-confirm-cancel').disabled = false;
964
+ $('curator-confirm-modal').hidden = false;
965
+ requestAnimationFrame(() => submit.focus());
966
+ }
967
+
968
+ function closeCuratorConfirmation(restoreFocus = true) {
969
+ if (state.curatorGlobalizeBusy) return;
970
+ $('curator-confirm-modal').hidden = true;
971
+ const panel = $('curator-panel');
972
+ panel.inert = false;
973
+ panel.removeAttribute('aria-hidden');
974
+ const trigger = state.curatorConfirmationTrigger;
975
+ state.curatorPendingCandidate = null;
976
+ state.curatorConfirmationTrigger = null;
977
+ setCuratorConfirmationStatus('');
978
+ if (restoreFocus) requestAnimationFrame(() => {
979
+ if (trigger?.isConnected) trigger.focus();
980
+ else $('curator-close')?.focus();
981
+ });
982
+ }
983
+
984
+ function handleCuratorConfirmationKeydown(event) {
985
+ event.stopPropagation();
986
+ if (event.key === 'Escape') { event.preventDefault(); closeCuratorConfirmation(); return; }
987
+ if (event.key !== 'Tab') return;
988
+ const panel = $('curator-confirm-panel');
989
+ const focusable = [...panel.querySelectorAll('button:not([disabled]), [tabindex]:not([tabindex="-1"])')];
990
+ if (!focusable.length) { event.preventDefault(); return; }
991
+ const first = focusable[0];
992
+ const last = focusable.at(-1);
993
+ if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
994
+ else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
995
+ }
996
+
997
+ async function globalizePendingCurator() {
998
+ const candidate = state.curatorPendingCandidate;
999
+ if (!candidate || state.curatorGlobalizeBusy) return;
1000
+ state.curatorGlobalizeBusy = true;
1001
+ const panel = $('curator-confirm-panel');
1002
+ const cancel = $('curator-confirm-cancel');
1003
+ const submit = $('curator-confirm-submit');
1004
+ panel.setAttribute('aria-busy', 'true');
1005
+ cancel.disabled = true;
1006
+ submit.disabled = true;
1007
+ setI18nText(submit, 'curatorGlobalizing');
1008
+ setCuratorConfirmationStatus(t('curatorGlobalizing'));
1009
+ try {
1010
+ await api('/api/curator/globalize?workspace=' + encodeURIComponent(candidate.workspace), {
1011
+ method: 'POST',
1012
+ headers: { 'content-type': 'application/json' },
1013
+ body: JSON.stringify({ workspace: candidate.workspace, entryId: candidate.entryId, expectedRevision: candidate.revision }),
1014
+ });
1015
+ state.curatorGlobalized.add(curatorCandidateKey(candidate));
1016
+ state.curatorGlobalizeBusy = false;
1017
+ closeCuratorConfirmation(false);
1018
+ await loadCurator();
1019
+ await loadEntries();
1020
+ setLocalizedStatus('curatorAdded');
1021
+ setCuratorStatus(t('curatorAdded'));
1022
+ requestAnimationFrame(() => $('curator-close')?.focus());
1023
+ } catch (error) {
1024
+ const message = error instanceof Error ? error.message : t('curatorGlobalizeFailed');
1025
+ setCuratorConfirmationStatus(t('curatorGlobalizeFailed') + ' ' + message, true);
1026
+ } finally {
1027
+ state.curatorGlobalizeBusy = false;
1028
+ panel.removeAttribute('aria-busy');
1029
+ cancel.disabled = false;
1030
+ submit.disabled = false;
1031
+ setI18nText(submit, 'curatorConfirmAction');
443
1032
  }
444
- state.curatorSelected.clear();
445
- await loadCurator();
446
- await loadEntries();
447
- if (added > 0 && failed === 0) { setLocalizedStatus('curatorBatchAdded', { count: added }); setCuratorStatus(t('curatorBatchAdded', { count: added })); }
448
- else if (added > 0) { setLocalizedStatus('curatorBatchPartial', { added, failed }, true); setCuratorStatus(t('curatorBatchPartial', { added, failed }), true); }
449
1033
  }
450
1034
 
451
1035
  function renderCurator() {
@@ -453,25 +1037,16 @@ export const WEB_HTML = String.raw `<!doctype html>
453
1037
  const modal = $('curator-modal');
454
1038
  modal.hidden = false;
455
1039
  const root = $('curator-list');
456
- const selectable = state.curatorCandidates.filter((candidate) => !state.curatorGlobalized.has(curatorCandidateKey(candidate)));
457
- $('curator-selection-count').textContent = t('curatorSelected', { count: state.curatorSelected.size });
1040
+ $('curator-result-count').textContent = t('curatorResultCount', { count: state.curatorTotalApproximate || state.curatorCandidates.length });
458
1041
  const actionRoot = $('curator-actions'); actionRoot.replaceChildren();
459
- const selectAll = document.createElement('button'); selectAll.type = 'button'; selectAll.className = 'button'; setI18nText(selectAll, 'curatorSelectAll'); selectAll.disabled = selectable.length === 0;
460
- selectAll.addEventListener('click', () => { selectable.forEach((candidate) => state.curatorSelected.add(curatorCandidateKey(candidate))); renderCurator(); });
461
- const clearSelection = document.createElement('button'); clearSelection.type = 'button'; clearSelection.className = 'button'; setI18nText(clearSelection, 'curatorClearSelection'); clearSelection.disabled = state.curatorSelected.size === 0;
462
- clearSelection.addEventListener('click', () => { state.curatorSelected.clear(); renderCurator(); });
463
- const apply = document.createElement('button'); apply.type = 'button'; apply.className = 'button primary'; setI18nText(apply, 'curatorApplySelected'); apply.disabled = state.curatorSelected.size === 0;
464
- apply.addEventListener('click', () => applySelectedCurator());
465
- actionRoot.append(selectAll, clearSelection, apply);
1042
+ if (state.curatorNextCursor) {
1043
+ const more = document.createElement('button'); more.type = 'button'; more.className = 'button'; setI18nText(more, 'curatorLoadMore'); more.addEventListener('click', () => loadCurator(true)); actionRoot.append(more);
1044
+ }
466
1045
  if (!state.curatorCandidates.length) { const empty = document.createElement('div'); empty.className = 'editor-empty'; setI18nText(empty, 'noCuratorCandidates'); root.replaceChildren(empty); return; }
467
- const cards = state.curatorCandidates.map((candidate) => {
1046
+ const cards = state.curatorCandidates.map((candidate, index) => {
468
1047
  const card = document.createElement('article'); card.className = 'curator-card';
469
1048
  const candidateKey = curatorCandidateKey(candidate);
470
1049
  const done = state.curatorGlobalized.has(candidateKey);
471
- const selectLabel = document.createElement('label'); selectLabel.className = 'curator-select';
472
- const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = state.curatorSelected.has(candidateKey); checkbox.disabled = done;
473
- checkbox.addEventListener('change', () => { if (checkbox.checked) state.curatorSelected.add(candidateKey); else state.curatorSelected.delete(candidateKey); renderCurator(); });
474
- const selectText = document.createElement('span'); selectText.textContent = done ? t('globalized') : t('curatorSelectSkill'); selectLabel.append(checkbox, selectText);
475
1050
  const meta = document.createElement('div'); meta.className = 'entry-meta';
476
1051
  const kind = document.createElement('span'); kind.className = 'badge'; kind.textContent = labelForKind(candidate.kind);
477
1052
  const score = document.createElement('span'); score.className = 'badge'; score.textContent = t('curatorScore', { score: candidate.score });
@@ -480,6 +1055,7 @@ export const WEB_HTML = String.raw `<!doctype html>
480
1055
  if (candidate.knowledge && candidate.knowledge.skillReady) { const ready = document.createElement('span'); ready.className = 'badge'; setI18nText(ready, 'curatorSkillReady'); meta.append(ready); }
481
1056
  const heading = document.createElement('h3'); heading.textContent = candidate.skillName;
482
1057
  const overview = document.createElement('div'); overview.className = 'curator-overview'; overview.textContent = candidate.overview.join('\n');
1058
+ const tags = document.createElement('div'); tags.className = 'tags'; (candidate.tags || []).forEach((tag) => { const tagItem = document.createElement('span'); tagItem.className = 'tag'; tagItem.textContent = '#' + tag; tags.append(tagItem); });
483
1059
  const knowledge = document.createElement('div'); knowledge.className = 'curator-reasons'; knowledge.textContent = candidate.knowledge ? [
484
1060
  t('curatorEvidence', { hits: candidate.knowledge.qualifiedHits, runs: candidate.knowledge.independentRuns, workspaces: candidate.knowledge.independentWorkspaces }),
485
1061
  t('curatorSilo', { value: candidate.knowledge.averageCompleteness }),
@@ -506,22 +1082,52 @@ export const WEB_HTML = String.raw `<!doctype html>
506
1082
  draft.append(label, content);
507
1083
  }
508
1084
  const reasons = document.createElement('div'); reasons.className = 'curator-reasons'; reasons.textContent = [...(candidate.reasons || []), ...(candidate.warnings || []).map((warning) => t('curatorWarning', { warning }))].join('\n');
509
- card.classList.toggle('selected', checkbox.checked);
510
- card.append(selectLabel, meta, heading, overview, knowledge, draft, reasons); return card;
1085
+ const details = document.createElement('div'); details.id = 'curator-review-' + index; details.className = 'curator-review-details'; details.hidden = true; details.append(knowledge, draft, reasons);
1086
+ const actions = document.createElement('div'); actions.className = 'curator-card-actions';
1087
+ const review = document.createElement('button'); review.type = 'button'; review.className = 'button'; review.setAttribute('aria-controls', details.id); review.setAttribute('aria-expanded', 'false'); setI18nText(review, 'curatorReview');
1088
+ review.addEventListener('click', () => { details.hidden = !details.hidden; review.setAttribute('aria-expanded', String(!details.hidden)); setI18nText(review, details.hidden ? 'curatorReview' : 'curatorHideReview'); });
1089
+ const globalize = document.createElement('button'); globalize.type = 'button'; globalize.className = 'button primary'; globalize.disabled = done; setI18nText(globalize, done ? 'globalized' : 'curatorGlobalizeCandidate');
1090
+ globalize.addEventListener('click', () => openCuratorConfirmation(candidate, globalize));
1091
+ actions.append(review, globalize);
1092
+ card.append(meta, heading, overview, tags, actions, details); return card;
511
1093
  });
512
- root.replaceChildren(toolbar, ...cards);
1094
+ root.replaceChildren(...cards);
513
1095
  }
514
1096
 
515
- async function loadCurator() {
516
- openCurator();
1097
+ async function loadCurator(append = false) {
1098
+ // Keep the documented legacy URL shape recognizable for clients: /api/curator/candidates?limit=50
1099
+ if (!append) {
1100
+ openCurator();
1101
+ state.curatorCandidates = [];
1102
+ state.curatorNextCursor = null;
1103
+ state.curatorTotalApproximate = 0;
1104
+ } else if (!state.curatorNextCursor) return;
517
1105
  const panel = $('curator-panel');
518
1106
  panel.setAttribute('aria-busy', 'true');
519
1107
  setCuratorStatus(t('curatorLoading'));
520
1108
  try {
521
- const result = await api('/api/curator/candidates?limit=50');
522
- state.curatorCandidates = result.candidates || [];
523
- const available = new Set(state.curatorCandidates.map((candidate) => curatorCandidateKey(candidate)));
524
- state.curatorSelected = new Set([...state.curatorSelected].filter((key) => available.has(key)));
1109
+ if (!append) {
1110
+ const facetParams = new URLSearchParams({ workspace: 'all' });
1111
+ if (state.curatorIncludeGlobalized) facetParams.set('includeGlobalized', 'true');
1112
+ const facetResult = await api('/api/curator/facets?' + facetParams);
1113
+ state.curatorFacets = facetResult.facets || {};
1114
+ renderCuratorFilters();
1115
+ }
1116
+ const params = new URLSearchParams({ workspace: 'all', limit: '50' });
1117
+ if (state.curatorProject) params.set('project', state.curatorProject);
1118
+ if (state.curatorSearch) params.set('search', state.curatorSearch);
1119
+ if (state.curatorFramework) params.set('framework', state.curatorFramework);
1120
+ if (state.curatorLanguage) params.set('language', state.curatorLanguage);
1121
+ if (state.curatorMemoryClass) params.set('memoryClass', state.curatorMemoryClass);
1122
+ if (state.curatorTier) params.set('tier', state.curatorTier);
1123
+ if (state.curatorReady) params.set('skillReadyOnly', 'true');
1124
+ if (state.curatorIncludeGlobalized) params.set('includeGlobalized', 'true');
1125
+ for (const tag of state.curatorTags) params.append('tag', tag);
1126
+ if (append && state.curatorNextCursor) params.set('cursor', state.curatorNextCursor);
1127
+ const result = await api('/api/curator/candidates?' + params);
1128
+ state.curatorCandidates = append ? [...state.curatorCandidates, ...(result.candidates || [])] : (result.candidates || []);
1129
+ state.curatorNextCursor = result.nextCursor || null;
1130
+ state.curatorTotalApproximate = Number(result.totalApproximate || state.curatorCandidates.length);
525
1131
  setCuratorStatus('');
526
1132
  renderCurator();
527
1133
  }
@@ -582,7 +1188,29 @@ export const WEB_HTML = String.raw `<!doctype html>
582
1188
  } catch (error) { setStatus(error.message, true); }
583
1189
  }
584
1190
 
585
- async function loadEntries() { if (!state.workspace) return; try { const params = new URLSearchParams({ workspace: state.workspace }); if (state.kind !== 'all') params.set('kind', state.kind); if (state.tag) params.set('tag', state.tag); if (state.query) params.set('q', state.query); const result = await api('/api/entries?' + params); state.entries = result.entries; if (state.selected && !state.entries.some((entry) => entry.id === state.selected.id)) state.selected = null; renderEntries(); renderFilters(); if (!state.selected && state.entries[0]) await selectEntry(state.entries[0].id); else renderEditor(); setLocalizedCountStatus('displayedCount', result.entries.length); } catch (error) { setStatus(error.message, true); } }
1191
+ async function loadEntries() {
1192
+ if (!state.workspace) return;
1193
+ try {
1194
+ const params = new URLSearchParams({ workspace: state.workspace });
1195
+ if (state.kind !== 'all') params.set('kind', state.kind);
1196
+ if (state.tag) params.set('tag', state.tag);
1197
+ if (state.query) params.set('q', state.query);
1198
+ const result = await api('/api/entries?' + params);
1199
+ state.entries = result.entries;
1200
+ state.recallItems = [];
1201
+ state.selectedRecall = null;
1202
+ if (state.query && state.kind === 'all' && !state.tag) {
1203
+ const recallParams = new URLSearchParams({ workspace: state.workspace, q: state.query, limit: '100' });
1204
+ const recalled = await api('/api/memory/recall?' + recallParams);
1205
+ state.recallItems = recalled.combined?.items || [];
1206
+ state.selected = null;
1207
+ } else if (state.selected && !state.entries.some((entry) => entry.id === state.selected.id)) state.selected = null;
1208
+ renderEntries(); renderFilters();
1209
+ if (!state.recallItems.length && !state.selected && state.entries[0]) await selectEntry(state.entries[0].id);
1210
+ else renderEditor();
1211
+ setLocalizedCountStatus('displayedCount', state.recallItems.length || result.entries.length);
1212
+ } catch (error) { setStatus(error.message, true); }
1213
+ }
586
1214
  async function loadTags() { if (!state.workspace) return; try { const result = await api('/api/tags?workspace=' + encodeURIComponent(state.workspace)); state.tags = result.tags; renderFilters(); } catch (error) { setStatus(error.message, true); } }
587
1215
  async function loadWorkspaces() { try { const result = await api('/api/workspaces'); const select = $('workspace'); select.replaceChildren(...result.workspaces.map((item) => { const option = document.createElement('option'); option.value = item.workspace; option.textContent = (item.displayName || item.workspace) + ' (' + item.count + ')'; return option; })); if (!state.workspace && result.workspaces[0]) state.workspace = result.workspaces[0].workspace; select.value = state.workspace; if (state.workspace) { await loadTags(); await loadEntries(); await loadRuns(); } if (state.curatorOpen) await loadCurator(); else if (!state.workspace) setLocalizedStatus('noWorkspace', {}, true); } catch (error) { setStatus(error.message, true); } }
588
1216
  $('language-toggle').addEventListener('click', () => {
@@ -606,10 +1234,18 @@ export const WEB_HTML = String.raw `<!doctype html>
606
1234
  });
607
1235
  document.addEventListener('click', (event) => { if (!$('language-picker')?.contains(event.target)) setLanguageMenuOpen(false); });
608
1236
  document.addEventListener('focusin', (event) => { if (!$('language-picker')?.contains(event.target)) setLanguageMenuOpen(false); });
609
- $('workspace').addEventListener('change', (event) => { if (state.curatorOpen) closeCurator(); state.workspace = event.target.value; state.selected = null; state.selectedRun = null; state.runs = []; state.tag = ''; state.curatorCandidates = []; state.curatorSelected = new Set(); state.curatorGlobalized = new Set(); state.curatorOpen = false; loadTags().then(loadEntries).then(loadRuns); });
1237
+ $('workspace').addEventListener('change', (event) => { if (state.curatorOpen) closeCurator(); if (state.externalSkillsOpen) closeExternalSkills(); state.workspace = event.target.value; state.selected = null; state.selectedRecall = null; state.selectedRun = null; state.runs = []; state.tag = ''; state.curatorCandidates = []; state.curatorGlobalized = new Set(); state.curatorNextCursor = null; state.curatorTotalApproximate = 0; state.curatorOpen = false; loadTags().then(loadEntries).then(loadRuns); });
610
1238
  $('curator-button').addEventListener('click', () => loadCurator());
611
1239
  $('curator-close').addEventListener('click', () => closeCurator());
1240
+ $('external-skills-button').addEventListener('click', () => loadExternalSkills());
1241
+ $('external-skills-close').addEventListener('click', () => closeExternalSkills());
1242
+ $('external-skills-refresh').addEventListener('click', () => loadExternalSkills(false));
1243
+ $('curator-confirm-cancel').addEventListener('click', () => closeCuratorConfirmation());
1244
+ $('curator-confirm-submit').addEventListener('click', () => globalizePendingCurator());
1245
+ $('curator-confirm-modal').addEventListener('keydown', handleCuratorConfirmationKeydown);
612
1246
  $('curator-modal').addEventListener('keydown', handleCuratorKeydown);
1247
+ $('external-skills-modal').addEventListener('keydown', handleExternalSkillsKeydown);
1248
+ window.addEventListener('popstate', () => { restoreCuratorUrl(); if (state.curatorOpen) loadCurator(); });
613
1249
  $('refresh').addEventListener('click', () => loadWorkspaces());
614
1250
  let searchTimer; $('search').addEventListener('input', (event) => { clearTimeout(searchTimer); state.query = event.target.value.trim(); searchTimer = setTimeout(() => loadEntries(), 180); });
615
1251
  applyTranslations();