@pi-unipi/unipi 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (359) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +8 -11
  3. package/docs/prefix-cache-architecture.md +14 -0
  4. package/package.json +22 -24
  5. package/packages/ask-user/README.md +55 -107
  6. package/packages/ask-user/ask-ui.ts +306 -732
  7. package/packages/ask-user/clipboard.ts +50 -0
  8. package/packages/ask-user/config.ts +74 -78
  9. package/packages/ask-user/package.json +2 -2
  10. package/packages/ask-user/questions.ts +151 -0
  11. package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
  12. package/packages/ask-user/tools.ts +198 -432
  13. package/packages/autocomplete/package.json +1 -1
  14. package/packages/autocomplete/src/constants.ts +38 -43
  15. package/packages/autocomplete/src/sorting.ts +8 -0
  16. package/packages/background-tasks/package.json +2 -2
  17. package/packages/background-tasks/src/config.ts +0 -73
  18. package/packages/background-tasks/src/index.ts +20 -16
  19. package/packages/background-tasks/src/registry.ts +1 -171
  20. package/packages/background-tasks/src/types.ts +0 -32
  21. package/packages/btw/README.md +13 -56
  22. package/packages/btw/extensions/btw.ts +403 -1796
  23. package/packages/btw/package.json +5 -2
  24. package/packages/btw/skills/btw/SKILL.md +7 -143
  25. package/packages/compactor/README.md +56 -287
  26. package/packages/compactor/package.json +5 -9
  27. package/packages/compactor/skills/compactor/SKILL.md +13 -20
  28. package/packages/compactor/src/card.ts +90 -0
  29. package/packages/compactor/src/commands/index.ts +73 -130
  30. package/packages/compactor/src/compaction/compact-args.ts +5 -3
  31. package/packages/compactor/src/compaction/filter-noise.ts +1 -1
  32. package/packages/compactor/src/compaction/hooks.ts +487 -311
  33. package/packages/compactor/src/compaction/source.ts +178 -0
  34. package/packages/compactor/src/compaction/summarize.ts +586 -52
  35. package/packages/compactor/src/config/manager.ts +130 -127
  36. package/packages/compactor/src/config/schema.ts +32 -48
  37. package/packages/compactor/src/index.ts +40 -431
  38. package/packages/compactor/src/info-screen.ts +44 -190
  39. package/packages/compactor/src/session/recall-blocks.ts +1 -1
  40. package/packages/compactor/src/stats.ts +76 -0
  41. package/packages/compactor/src/tools/context-budget.ts +17 -34
  42. package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
  43. package/packages/compactor/src/tools/register.ts +29 -269
  44. package/packages/compactor/src/types.ts +47 -186
  45. package/packages/core/README.md +25 -0
  46. package/packages/core/child-agent.ts +1 -0
  47. package/packages/core/command-echo.ts +63 -0
  48. package/packages/core/compaction-context.ts +41 -0
  49. package/packages/core/constants.ts +8 -38
  50. package/packages/core/events.ts +0 -53
  51. package/packages/core/fusion-status.ts +4 -0
  52. package/packages/core/global.d.ts +10 -0
  53. package/packages/core/harness-messages.ts +392 -0
  54. package/packages/core/index.ts +14 -0
  55. package/packages/core/kanboard-status.ts +49 -0
  56. package/packages/core/long-horizon-owner-status.ts +46 -0
  57. package/packages/core/model-cache.ts +81 -7
  58. package/packages/core/package.json +6 -3
  59. package/packages/core/src/attach/detect.ts +163 -0
  60. package/packages/core/src/child-agent/child-args.ts +31 -0
  61. package/packages/core/src/child-agent/delivery.ts +41 -0
  62. package/packages/core/src/child-agent/index.ts +5 -0
  63. package/packages/core/src/child-agent/readers.ts +203 -0
  64. package/packages/core/src/child-agent/runtime.ts +584 -0
  65. package/packages/core/src/evidence.ts +78 -0
  66. package/packages/core/src/hints/browser.ts +278 -0
  67. package/packages/core/src/hints/crab-data.ts +19 -0
  68. package/packages/core/src/hints/index.ts +865 -0
  69. package/packages/core/src/hints/lines.ts +607 -0
  70. package/packages/core/src/hints/store.ts +120 -0
  71. package/packages/core/src/jev/chatter.ts +24 -0
  72. package/packages/core/src/jev/settings.ts +177 -14
  73. package/packages/core/src/package-colors.ts +2 -0
  74. package/packages/core/src/settings/catalog.ts +39 -15
  75. package/packages/core/src/settings/engine.ts +48 -2
  76. package/packages/core/src/settings/hub.ts +83 -20
  77. package/packages/core/src/settings/open.ts +49 -0
  78. package/packages/core/src/settings/schema.ts +27 -7
  79. package/packages/core/src/tui/hub-kit.ts +18 -5
  80. package/packages/core/src/tui/kit.ts +198 -0
  81. package/packages/core/src/tui/progress.ts +73 -0
  82. package/packages/core/src/turn/arbiter.ts +290 -0
  83. package/packages/footer/README.md +4 -6
  84. package/packages/footer/package.json +3 -3
  85. package/packages/footer/src/glance-editor.ts +19 -11
  86. package/packages/footer/src/help.ts +1 -1
  87. package/packages/footer/src/index.ts +36 -3
  88. package/packages/footer/src/presets.ts +3 -4
  89. package/packages/footer/src/rendering/icons.ts +0 -20
  90. package/packages/footer/src/segments/compactor.ts +52 -142
  91. package/packages/footer/src/segments/status-ext.ts +0 -1
  92. package/packages/fusion/README.md +14 -4
  93. package/packages/fusion/package.json +4 -3
  94. package/packages/fusion/src/child-args.ts +1 -0
  95. package/packages/fusion/src/index.ts +161 -8
  96. package/packages/fusion/src/pi-settings.ts +57 -0
  97. package/packages/fusion/src/picker.ts +186 -51
  98. package/packages/fusion/src/preset.ts +56 -2
  99. package/packages/fusion/src/prompts.ts +1 -1
  100. package/packages/fusion/src/sidekick-runtime.ts +1 -420
  101. package/packages/fusion/src/tools.ts +107 -128
  102. package/packages/fusion/src/transcript.ts +27 -85
  103. package/packages/info-screen/package.json +2 -2
  104. package/packages/input-shortcuts/README.md +18 -30
  105. package/packages/input-shortcuts/package.json +2 -2
  106. package/packages/input-shortcuts/src/burst.ts +88 -0
  107. package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
  108. package/packages/input-shortcuts/src/cleared-input.ts +97 -0
  109. package/packages/input-shortcuts/src/index.ts +99 -193
  110. package/packages/input-shortcuts/src/last-response.ts +40 -0
  111. package/packages/input-shortcuts/src/registers.ts +8 -23
  112. package/packages/input-shortcuts/src/types.ts +4 -16
  113. package/packages/input-shortcuts/src/undo-redo.ts +59 -47
  114. package/packages/kanboard/README.md +51 -47
  115. package/packages/kanboard/index.ts +293 -55
  116. package/packages/kanboard/package.json +8 -8
  117. package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
  118. package/packages/kanboard/src/badges.ts +93 -0
  119. package/packages/kanboard/src/bin.ts +4 -2
  120. package/packages/kanboard/src/commands.ts +218 -84
  121. package/packages/kanboard/src/debug.ts +20 -0
  122. package/packages/kanboard/src/guard.ts +267 -109
  123. package/packages/kanboard/src/monitor.ts +330 -0
  124. package/packages/kanboard/src/notice-buffer.ts +74 -0
  125. package/packages/kanboard/src/progress.ts +27 -0
  126. package/packages/kanboard/src/reminders.ts +234 -0
  127. package/packages/kanboard/src/settings.ts +69 -15
  128. package/packages/kanboard/src/shapes.ts +5 -0
  129. package/packages/long-horizon/index.ts +190 -13
  130. package/packages/long-horizon/package.json +3 -3
  131. package/packages/long-horizon/src/commands.ts +103 -7
  132. package/packages/long-horizon/src/compaction-brief.ts +35 -0
  133. package/packages/long-horizon/src/engine/continuation.ts +84 -15
  134. package/packages/long-horizon/src/engine/goal-state.ts +64 -13
  135. package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
  136. package/packages/long-horizon/src/engine/ralph.ts +3 -2
  137. package/packages/long-horizon/src/engine/runaway.ts +68 -11
  138. package/packages/long-horizon/src/engine/verifier.ts +19 -0
  139. package/packages/long-horizon/src/gate.ts +37 -12
  140. package/packages/long-horizon/src/judge/resolve.ts +1 -0
  141. package/packages/long-horizon/src/owner.ts +35 -2
  142. package/packages/long-horizon/src/progress.ts +87 -0
  143. package/packages/long-horizon/src/prompts/goal.ts +1 -1
  144. package/packages/long-horizon/src/runtime.ts +172 -18
  145. package/packages/long-horizon/src/settings.ts +54 -39
  146. package/packages/long-horizon/src/tools/goal.ts +6 -0
  147. package/packages/long-horizon/src/tools/graph.ts +1 -1
  148. package/packages/long-horizon/src/tools/swarm.ts +3 -3
  149. package/packages/mcp/package.json +2 -2
  150. package/packages/memory/commands.ts +7 -2
  151. package/packages/memory/index.ts +210 -32
  152. package/packages/memory/package.json +3 -3
  153. package/packages/memory/save-session.ts +264 -0
  154. package/packages/memory/settings.ts +17 -0
  155. package/packages/memory/tools.ts +253 -203
  156. package/packages/notify/package.json +2 -2
  157. package/packages/skill-registry/README.md +53 -16
  158. package/packages/skill-registry/index.ts +230 -0
  159. package/packages/skill-registry/package.json +19 -8
  160. package/packages/skill-registry/src/editor.ts +206 -0
  161. package/packages/skill-registry/src/judge.ts +289 -0
  162. package/packages/skill-registry/src/registry.ts +87 -0
  163. package/packages/skill-registry/src/settings.ts +259 -0
  164. package/packages/skill-registry/src/vault.ts +80 -0
  165. package/packages/subagents/README.md +39 -119
  166. package/packages/subagents/package.json +6 -13
  167. package/packages/subagents/skills/subagents/SKILL.md +18 -42
  168. package/packages/subagents/src/agents.ts +213 -0
  169. package/packages/subagents/src/cards.ts +182 -0
  170. package/packages/subagents/src/index.ts +609 -1084
  171. package/packages/subagents/src/manager.ts +429 -0
  172. package/packages/subagents/src/profiles.ts +130 -0
  173. package/packages/subagents/src/transcript.ts +154 -0
  174. package/packages/subagents/src/ui.ts +389 -0
  175. package/packages/unipi/bundled.js +39756 -45569
  176. package/packages/unipi/index.ts +55 -10
  177. package/packages/updater/package.json +2 -2
  178. package/packages/utility/README.md +76 -69
  179. package/packages/utility/package.json +2 -2
  180. package/packages/utility/skills/image/SKILL.md +37 -0
  181. package/packages/utility/skills/utility/SKILL.md +17 -60
  182. package/packages/utility/src/answer/extract.ts +128 -0
  183. package/packages/utility/src/answer/index.ts +213 -0
  184. package/packages/utility/src/answer/reply.ts +133 -0
  185. package/packages/utility/src/answer/web.ts +167 -0
  186. package/packages/utility/src/attach/index.ts +205 -0
  187. package/packages/utility/src/commands.ts +38 -142
  188. package/packages/utility/src/diagnostics/engine.ts +50 -5
  189. package/packages/utility/src/image/index.ts +33 -0
  190. package/packages/utility/src/image/models.ts +221 -0
  191. package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
  192. package/packages/utility/src/image/settings.ts +178 -0
  193. package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
  194. package/packages/utility/src/image/tools.ts +168 -0
  195. package/packages/utility/src/image/transports.ts +186 -0
  196. package/packages/utility/src/index.ts +68 -389
  197. package/packages/utility/src/lifecycle/cleanup.ts +116 -311
  198. package/packages/utility/src/rename/gate.ts +102 -0
  199. package/packages/utility/src/rename/index.ts +191 -0
  200. package/packages/utility/src/rename/session.ts +170 -0
  201. package/packages/utility/src/render/delegated.ts +285 -0
  202. package/packages/utility/src/render/harness.ts +683 -0
  203. package/packages/utility/src/render/parse.ts +156 -0
  204. package/packages/utility/src/render/pi-settings.ts +38 -0
  205. package/packages/utility/src/render/reply-bg.ts +307 -0
  206. package/packages/utility/src/render/simple.ts +724 -0
  207. package/packages/utility/src/render/spacing.ts +235 -0
  208. package/packages/utility/src/render/styled.ts +251 -0
  209. package/packages/utility/src/render/tools.ts +353 -0
  210. package/packages/utility/src/settings.ts +166 -233
  211. package/packages/utility/src/types.ts +1 -222
  212. package/packages/watchdog/index.ts +17 -6
  213. package/packages/watchdog/package.json +3 -3
  214. package/packages/watchdog/src/config.ts +3 -2
  215. package/packages/web-api/package.json +2 -2
  216. package/packages/workflow/README.md +3 -0
  217. package/packages/workflow/index.ts +0 -2
  218. package/packages/workflow/package.json +2 -2
  219. package/packages/workflow/src/permission/decide.ts +6 -4
  220. package/packages/workflow/src/permission/index.ts +17 -7
  221. package/packages/workflow/src/permission/settings.ts +39 -4
  222. package/packages/workflow/src/plan/index.ts +19 -4
  223. package/packages/background-tasks/extensions/delegate-child.ts +0 -1
  224. package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
  225. package/packages/background-tasks/src/context-token-budget.ts +0 -903
  226. package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
  227. package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
  228. package/packages/background-tasks/src/delegate/budget.ts +0 -415
  229. package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
  230. package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
  231. package/packages/background-tasks/src/delegate/launch.ts +0 -484
  232. package/packages/background-tasks/src/delegate/result-package.ts +0 -459
  233. package/packages/background-tasks/src/delegate/runner.ts +0 -430
  234. package/packages/background-tasks/src/delegate/seed.ts +0 -423
  235. package/packages/background-tasks/src/delegate/types.ts +0 -323
  236. package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
  237. package/packages/background-tasks/src/delegate-extension.ts +0 -654
  238. package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
  239. package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
  240. package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
  241. package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
  242. package/packages/compactor/src/compaction/build-sections.ts +0 -76
  243. package/packages/compactor/src/compaction/extract/goals.ts +0 -36
  244. package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
  245. package/packages/compactor/src/compaction/format.ts +0 -53
  246. package/packages/compactor/src/compaction/merge.ts +0 -143
  247. package/packages/compactor/src/config/presets.ts +0 -160
  248. package/packages/compactor/src/executor/executor.ts +0 -233
  249. package/packages/compactor/src/executor/runtime.ts +0 -115
  250. package/packages/compactor/src/security/evaluator.ts +0 -89
  251. package/packages/compactor/src/security/policy.ts +0 -96
  252. package/packages/compactor/src/security/scanner.ts +0 -65
  253. package/packages/compactor/src/session/auto-inject.ts +0 -60
  254. package/packages/compactor/src/session/db.ts +0 -325
  255. package/packages/compactor/src/session/extract.ts +0 -144
  256. package/packages/compactor/src/session/resume-inject.ts +0 -64
  257. package/packages/compactor/src/session/snapshot.ts +0 -327
  258. package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
  259. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
  260. package/packages/compactor/src/tools/ctx-execute.ts +0 -23
  261. package/packages/compactor/src/tools/ctx-stats.ts +0 -96
  262. package/packages/image/README.md +0 -99
  263. package/packages/image/package.json +0 -61
  264. package/packages/image/skills/image/SKILL.md +0 -76
  265. package/packages/image/src/generate.ts +0 -256
  266. package/packages/image/src/index.ts +0 -135
  267. package/packages/image/src/models.ts +0 -519
  268. package/packages/image/src/openai-images-api.ts +0 -282
  269. package/packages/image/src/register-providers.ts +0 -209
  270. package/packages/image/src/settings.ts +0 -173
  271. package/packages/image/src/tools.ts +0 -326
  272. package/packages/input-shortcuts/src/clipboard.ts +0 -119
  273. package/packages/kanboard/src/runner.ts +0 -817
  274. package/packages/skill-registry/skills/auto/SKILL.md +0 -465
  275. package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
  276. package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
  277. package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
  278. package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
  279. package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
  280. package/packages/skill-registry/skills/debug/SKILL.md +0 -228
  281. package/packages/skill-registry/skills/document/SKILL.md +0 -120
  282. package/packages/skill-registry/skills/fix/SKILL.md +0 -212
  283. package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
  284. package/packages/skill-registry/skills/plan/SKILL.md +0 -231
  285. package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
  286. package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
  287. package/packages/skill-registry/skills/research/SKILL.md +0 -251
  288. package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
  289. package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
  290. package/packages/skill-registry/skills/work/SKILL.md +0 -218
  291. package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
  292. package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
  293. package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
  294. package/packages/subagents/agents/delegate.md +0 -14
  295. package/packages/subagents/agents/oracle.md +0 -78
  296. package/packages/subagents/agents/researcher.md +0 -52
  297. package/packages/subagents/agents/reviewer.md +0 -80
  298. package/packages/subagents/agents/scout.md +0 -50
  299. package/packages/subagents/agents/worker.md +0 -59
  300. package/packages/subagents/prompts/council.md +0 -48
  301. package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
  302. package/packages/subagents/prompts/parallel-cleanup.md +0 -59
  303. package/packages/subagents/prompts/parallel-research.md +0 -50
  304. package/packages/subagents/prompts/parallel-review.md +0 -54
  305. package/packages/subagents/prompts/review-loop.md +0 -43
  306. package/packages/subagents/skills/council-mode/SKILL.md +0 -230
  307. package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
  308. package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
  309. package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
  310. package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
  311. package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
  312. package/packages/subagents/src/acceptance.ts +0 -331
  313. package/packages/subagents/src/agent-manager.ts +0 -405
  314. package/packages/subagents/src/agent-memory.ts +0 -300
  315. package/packages/subagents/src/agent-overrides.ts +0 -183
  316. package/packages/subagents/src/agent-runner.ts +0 -271
  317. package/packages/subagents/src/async-runner.ts +0 -422
  318. package/packages/subagents/src/authority-policy.ts +0 -47
  319. package/packages/subagents/src/budgets.ts +0 -285
  320. package/packages/subagents/src/child-safety.ts +0 -140
  321. package/packages/subagents/src/config.ts +0 -249
  322. package/packages/subagents/src/conversation-viewer.ts +0 -281
  323. package/packages/subagents/src/core-compat.ts +0 -94
  324. package/packages/subagents/src/custom-agents.ts +0 -343
  325. package/packages/subagents/src/file-system-retry.ts +0 -50
  326. package/packages/subagents/src/fleet-data.ts +0 -105
  327. package/packages/subagents/src/fleet-view.ts +0 -322
  328. package/packages/subagents/src/foreground-detach.ts +0 -59
  329. package/packages/subagents/src/fork-context.ts +0 -285
  330. package/packages/subagents/src/global.d.ts +0 -7
  331. package/packages/subagents/src/guide.ts +0 -129
  332. package/packages/subagents/src/mission-state.ts +0 -133
  333. package/packages/subagents/src/mission-store.ts +0 -436
  334. package/packages/subagents/src/output-limits.ts +0 -142
  335. package/packages/subagents/src/parity-types.ts +0 -315
  336. package/packages/subagents/src/pi-args.ts +0 -167
  337. package/packages/subagents/src/result-files.ts +0 -232
  338. package/packages/subagents/src/result-watcher.ts +0 -221
  339. package/packages/subagents/src/retained-children.ts +0 -176
  340. package/packages/subagents/src/run-fanout-budget.ts +0 -356
  341. package/packages/subagents/src/scheduled-runs.ts +0 -349
  342. package/packages/subagents/src/schemas.ts +0 -263
  343. package/packages/subagents/src/slash-commands.ts +0 -80
  344. package/packages/subagents/src/supervisor-channel.ts +0 -249
  345. package/packages/subagents/src/tool-handler.ts +0 -1144
  346. package/packages/subagents/src/types.ts +0 -238
  347. package/packages/subagents/src/widget.ts +0 -457
  348. package/packages/subagents/src/workflow-script.ts +0 -737
  349. package/packages/subagents/src/workflow-worker.ts +0 -384
  350. package/packages/subagents/src/worktree.ts +0 -614
  351. package/packages/utility/src/analytics/collector.ts +0 -293
  352. package/packages/utility/src/info-screen.ts +0 -80
  353. package/packages/utility/src/lifecycle/process.ts +0 -162
  354. package/packages/utility/src/prefix-cache.ts +0 -263
  355. package/packages/utility/src/skill-discovery.ts +0 -542
  356. package/packages/utility/src/tools/env.ts +0 -113
  357. package/packages/utility/src/tui/name-badge-state.ts +0 -315
  358. package/packages/utility/src/tui/name-badge.ts +0 -109
  359. /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
@@ -1,25 +1,62 @@
1
1
  # @pi-unipi/skill-registry
2
2
 
3
- **Placeholder package.** It ships no extension code — only skills — and exists so the
4
- bundled skill set keeps loading through the `@pi-unipi/unipi` umbrella manifest.
3
+ Decides which skills the agent sees: per-project on/off, a vault of extra skills, and jev-judged exposure.
5
4
 
6
- The twenty skills that used to live in `@pi-unipi/workflow` moved here when that
7
- package was repurposed for **plan mode** and **permission modes**. They are plain
8
- `SKILL.md` instruction files; the slash commands that used to dispatch them
9
- (`/unipi:brainstorm`, `/unipi:work`, `/unipi:review-work`, `/unipi:auto`, …) were
10
- removed — plan mode (`/unipi:plan`, `Alt+P`) and ordinary prompting replace them. Every skill is still
11
- loadable by name via `/skill:<name>` or by reading its `SKILL.md`, and jev skill
12
- judging still sees them.
5
+ ## Commands
13
6
 
14
- ## Roadmap
7
+ | Command | Description |
8
+ |---------|-------------|
9
+ | `/unipi:skills` | Opens `/unipi:settings` on Skills (proxy, exposure, and the **Skill settings…** row) |
15
10
 
16
- This package becomes the real skill registry: discovery, install/update of third-party
17
- skill packs, and the metadata (capability tags, model hints) that jev skill judging and
18
- the `/unipi:settings` hub will consume. Until then it is a mount point, deliberately
19
- code-free.
11
+ ## Skill settings
20
12
 
21
- ## Layout
13
+ `/unipi:settings` → Skills → **Skill settings…** opens a grid of every skill pi loaded, like Fusion's preset editor:
22
14
 
23
15
  ```
24
- skills/<name>/SKILL.md # one directory per skill
16
+ Skill settings · 42 skills · editing global · proxy on
17
+ E M skill source description
18
+ › [x] [ ] api-contract user Check REST/JSON API changes ...
19
+ [ ] [ ] aws-deploy vault Deploy services to AWS with ...
20
+
21
+ E enabled — the model sees it (judged first when exposure is on); off removes it and blocks /skill:name
22
+ M must show — always listed, even when judging would hide it
23
+ bright = set in global · dim = inherited
25
24
  ```
25
+
26
+ | Key | Action |
27
+ |-----|--------|
28
+ | `↑` `↓` | Move between skills (PgUp/PgDn page) |
29
+ | `←` `→` | Move between the E / M columns |
30
+ | `space` | Toggle the cell |
31
+ | `d` | Clear the cell in the edited scope (inherit again) |
32
+ | `g` | Edit the global or the project scope |
33
+ | `p` | Skill proxy on/off |
34
+ | type | Filter by name, source or description (Esc clears) |
35
+ | `enter` / `esc` | Save / cancel |
36
+
37
+ Cells show the effective value: project over global over the default (vault skills off, everything else on). Only the cells you changed are written, each option on its own, so a project can override just one.
38
+
39
+ ## Skill proxy
40
+
41
+ Off by default. While off, pi's skills pass through untouched (exposure judging still applies), vault skills stay hidden, and your choices are saved but not applied. Turn it on and:
42
+
43
+ - Skills turned **off** are removed from the session, including `/skill:name`.
44
+ - The **vault**, `~/.unipi/skill-vault/`, takes part. Keep as many skills there as you like; they stay off until a scope turns them on, so a project only gets the ones it needs. Changes apply on the next prompt, no reload needed. Any folder with a `SKILL.md`, up to three levels deep (`vault/<skill>/` or `vault/<pack>/<skill>/`).
45
+
46
+ ## Exposure
47
+
48
+ Settings → Skills → Exposure:
49
+
50
+ - **judged** (default) — when more skills are available than *Max skills listed* (12), the first real request of the session picks the listed set, and it stays fixed for the session so the system prompt never changes (prefix cache intact). Greetings don't count.
51
+ 1. **Named skills are always kept.** A skill whose name appears in the request (`agent-browser`, "grill me") or a word only its name contains ("ssh into **coffee**" → `coffee-sandbox`) is listed without asking a model.
52
+ 2. **jev scores the rest** against the request; those above the threshold are listed best first, up to *Max skills listed* in total.
53
+ 3. **Hidden skills are still named.** The system prompt gets a short section listing every hidden skill by folder, so the agent knows it exists and can read its `SKILL.md`.
54
+ 4. **Later requests** that name a hidden skill (or, with *recheck*, that jev finds it relevant to) get a message pointing at it. The system prompt itself never changes.
55
+ - **all** — every skill is listed.
56
+ - **off** — UniPi's bundled skills are removed from the list.
57
+
58
+ jev uses the shared Decision Model (Settings → Skills → Decision model: inherit or custom). If jev is unavailable, every skill is listed.
59
+
60
+ ## License
61
+
62
+ MIT
@@ -0,0 +1,230 @@
1
+ /**
2
+ * @pi-unipi/skill-registry — Extension entry
3
+ *
4
+ * Per turn (before_agent_start), on pi's discovered skills:
5
+ * 1. registry (proxy on): drop skills turned off — removed from the session,
6
+ * /skill:name included; the vault is mounted via resources_discover and
7
+ * stays off until a scope turns a skill on.
8
+ * 2. exposure: off (bundled stripped) | all | judged (see src/judge.ts).
9
+ * Plus /unipi:skills (the hub opened on Skills), the "Skill settings…" overlay
10
+ * (per-skill Enabled / Must show, src/editor.ts), and the
11
+ * reveal event kanboard uses to surface a skill mid-session.
12
+ */
13
+
14
+ import { existsSync } from "node:fs";
15
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
16
+ import { emitEvent, getPackageVersion, harnessMetadata, HUB_OVERLAY_OPTIONS, MODULES, openSettingsHub, registerCommandRunner, setSettings, UNIPI_EVENTS } from "@pi-unipi/core";
17
+ import { dirname } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import { applyRegistry, isBundledSkillLocation, isUnderDir, skillCommandName, skillDir, skillSource, type CatalogSkill } from "./src/registry.js";
20
+ import { readSkillsSettings, readStateLayers, writeStateLayers } from "./src/settings.js";
21
+ import { SkillEditor, type EditorResult } from "./src/editor.js";
22
+ import { listVaultSkills, vaultDir } from "./src/vault.js";
23
+ import {
24
+ decideTurn,
25
+ emptyState,
26
+ HIDDEN_SECTION,
27
+ restoreState,
28
+ revealMessage,
29
+ SKILLS_JUDGED_ENTRY,
30
+ SKILLS_REVEALED_ENTRY,
31
+ toEntry,
32
+ type SessionSkillsState,
33
+ } from "./src/judge.js";
34
+
35
+ export { SKILLS_JUDGED_ENTRY, SKILLS_REVEALED_ENTRY } from "./src/judge.js";
36
+
37
+ const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
38
+
39
+ /** Event another module emits to reveal a skill by name (append-only). */
40
+ export const SKILL_REVEAL_EVENT = "unipi:skills:reveal";
41
+
42
+ export default function skillRegistry(pi: ExtensionAPI) {
43
+ let state: SessionSkillsState | null = null;
44
+ let lastCatalog: CatalogSkill[] = [];
45
+ let disabled = new Set<string>();
46
+
47
+ const sessionState = (ctx: ExtensionContext): SessionSkillsState => {
48
+ const id = ctx.sessionManager.getSessionId();
49
+ if (!state || state.sessionId !== id) state = emptyState(id);
50
+ return state;
51
+ };
52
+
53
+ /** Everything the manager can show: last catalog ∪ pi's skill commands ∪ vault. */
54
+ const allSkills = (): CatalogSkill[] => {
55
+ const byName = new Map<string, CatalogSkill>();
56
+ for (const s of lastCatalog) byName.set(s.name, s);
57
+ try {
58
+ for (const c of pi.getCommands()) {
59
+ if (c.source !== "skill") continue;
60
+ const name = c.name.replace(/^skill:/, "");
61
+ if (!byName.has(name)) byName.set(name, { name, description: c.description ?? "", filePath: c.sourceInfo?.path });
62
+ }
63
+ } catch {
64
+ // getCommands unavailable (tests) — catalog + vault only.
65
+ }
66
+ for (const v of listVaultSkills()) if (!byName.has(v.name)) byName.set(v.name, v);
67
+ return [...byName.values()];
68
+ };
69
+
70
+ const invalidateJudgement = () => {
71
+ if (state) state.judged = false;
72
+ };
73
+
74
+ /** "Skill settings…" — the per-skill E/M overlay. */
75
+ const openEditor = async (ctx: ExtensionContext) => {
76
+ if (!ctx.hasUI) return;
77
+ const cwd = ctx.cwd ?? process.cwd();
78
+ const vault = vaultDir();
79
+ const skills = allSkills()
80
+ .map((sk) => ({ name: sk.name, description: sk.description, source: skillSource(sk, cwd, vault) }))
81
+ .sort((a, b) => a.name.localeCompare(b.name));
82
+ const before = readStateLayers(cwd);
83
+ const settings = readSkillsSettings(cwd);
84
+ const hasProject = Object.keys(before.project).length > 0;
85
+ const result = await ctx.ui.custom<EditorResult>(
86
+ (tui, theme, _kb, done) =>
87
+ new SkillEditor({
88
+ skills,
89
+ layers: before,
90
+ proxy: settings.proxy,
91
+ initialScope: hasProject ? "project" : "global",
92
+ theme: { fg: (c, text) => theme.fg(c as never, text), bold: (text) => theme.bold(text) },
93
+ onDone: done,
94
+ onRenderRequest: () => tui.requestRender(),
95
+ }),
96
+ HUB_OVERLAY_OPTIONS,
97
+ );
98
+ if (result.type !== "saved") return;
99
+ const writes = writeStateLayers(cwd, before, result.layers);
100
+ if (result.proxy !== settings.proxy) setSettings("skills", { proxy: result.proxy }, "global", cwd);
101
+ if (writes > 0 || result.proxy !== settings.proxy) {
102
+ invalidateJudgement();
103
+ ctx.ui.notify(`Skill settings saved (${writes} change${writes === 1 ? "" : "s"}${result.proxy !== settings.proxy ? `, proxy ${result.proxy ? "on" : "off"}` : ""}) — applies from the next prompt`, "info");
104
+ }
105
+ };
106
+ registerCommandRunner("unipi:skills-editor", async (raw: unknown) => openEditor(raw as ExtensionContext));
107
+
108
+ pi.registerCommand("unipi:skills", {
109
+ description: "Skill settings — proxy, exposure, and per-skill Enabled / Must show (opens /unipi:settings on Skills)",
110
+ handler: async (_args, ctx) => {
111
+ await openSettingsHub(ctx, {
112
+ filter: "skills",
113
+ onChanged: (ns) => {
114
+ if (ns === "skills") invalidateJudgement();
115
+ },
116
+ });
117
+ },
118
+ });
119
+
120
+ // The vault is always mounted so toggles apply without /reload; while the
121
+ // proxy is off, vault skills are filtered out below (and /skill:name blocked).
122
+ pi.on("resources_discover", () => {
123
+ try {
124
+ const vault = vaultDir();
125
+ return existsSync(vault) ? { skillPaths: [vault] } : undefined;
126
+ } catch {
127
+ return undefined;
128
+ }
129
+ });
130
+
131
+ pi.on("session_start", (_event, ctx) => {
132
+ try {
133
+ state = restoreState(ctx.sessionManager.getSessionId(), ctx.sessionManager.getEntries());
134
+ emitEvent(pi, UNIPI_EVENTS.MODULE_READY, { name: MODULES.SKILL_REGISTRY, version: VERSION, commands: ["unipi:skills"], tools: [] });
135
+ } catch {
136
+ // never block startup
137
+ }
138
+ });
139
+
140
+ // A skill turned off is gone for the session — /skill:name included.
141
+ pi.on("input", (event, ctx) => {
142
+ try {
143
+ const name = skillCommandName(event.text ?? "");
144
+ if (!name || !disabled.has(name)) return undefined;
145
+ ctx.ui?.notify?.(`Skill "${name}" is turned off here. Turn it on in /unipi:skills.`, "warning");
146
+ return { action: "handled" as const };
147
+ } catch {
148
+ return undefined;
149
+ }
150
+ });
151
+
152
+ pi.events?.on?.(SKILL_REVEAL_EVENT, (payload: unknown) => {
153
+ try {
154
+ const data = payload as { names?: unknown } | undefined;
155
+ if (!Array.isArray(data?.names) || !state) return;
156
+ const names = data.names.map(String).filter((n) => !state!.revealed.has(n));
157
+ const entries = state.hidden.filter((h) => names.includes(h.name));
158
+ if (entries.length === 0) return;
159
+ for (const e of entries) state.revealed.add(e.name);
160
+ pi.appendEntry(SKILLS_REVEALED_ENTRY, { names: entries.map((e) => e.name) });
161
+ pi.sendMessage({ customType: "unipi-skills-revealed", content: revealMessage(entries), display: true, details: { unipiHarness: harnessMetadata({ source: "Skills", title: "Skill reveal", synopsis: `${String(entries.length)} relevant skill(s)` }, "direct") } }, { triggerTurn: false });
162
+ } catch {
163
+ // ignore
164
+ }
165
+ });
166
+
167
+ pi.on("before_agent_start", async (event, ctx) => {
168
+ try {
169
+ const cwd = ctx.cwd ?? process.cwd();
170
+ const options = event.systemPromptOptions;
171
+ const catalog = (options.skills ?? []) as unknown as CatalogSkill[];
172
+ lastCatalog = catalog;
173
+ const settings = readSkillsSettings(cwd);
174
+
175
+ const vault = vaultDir();
176
+ let pool = catalog;
177
+ let mustShow = new Set<string>();
178
+ disabled = new Set();
179
+ if (settings.proxy) {
180
+ const result = applyRegistry(catalog, settings.states, cwd, vault);
181
+ pool = result.listed;
182
+ disabled = result.disabled;
183
+ mustShow = result.mustShow;
184
+ } else {
185
+ pool = catalog.filter((s) => {
186
+ const inVault = isUnderDir(skillDir(s), vault);
187
+ if (inVault) disabled.add(s.name);
188
+ return !inVault;
189
+ });
190
+ }
191
+
192
+ const setSkills = (list: CatalogSkill[]) => {
193
+ options.skills = list as unknown as typeof options.skills;
194
+ };
195
+ if (settings.exposure.mode !== "judged") {
196
+ setSkills(settings.exposure.mode === "off" ? pool.filter((s) => mustShow.has(s.name) || !isBundledSkillLocation(skillDir(s))) : pool);
197
+ delete options.sections[HIDDEN_SECTION];
198
+ return undefined;
199
+ }
200
+
201
+ const out = await decideTurn({
202
+ prompt: event.prompt,
203
+ catalog: pool.map(toEntry),
204
+ settings: settings.exposure,
205
+ cwd,
206
+ state: sessionState(ctx),
207
+ mustShow,
208
+ });
209
+ setSkills(pool.filter((s) => out.listed.has(s.name)));
210
+ if (out.index) options.sections[HIDDEN_SECTION] = out.index;
211
+ else delete options.sections[HIDDEN_SECTION];
212
+ if (out.freeze) pi.appendEntry(SKILLS_JUDGED_ENTRY, out.freeze);
213
+ if (out.status && ctx.hasUI) ctx.ui.setStatus("skills", out.status);
214
+ if (out.reveal.length > 0) {
215
+ pi.appendEntry(SKILLS_REVEALED_ENTRY, { names: out.reveal.map((r) => r.name) });
216
+ return {
217
+ message: {
218
+ customType: "unipi-skills-revealed",
219
+ content: revealMessage(out.reveal),
220
+ display: true,
221
+ details: { unipiHarness: harnessMetadata({ source: "Skills", title: "Skill reveal", synopsis: `${String(out.reveal.length)} relevant skill(s)` }, "before_agent_start") },
222
+ },
223
+ };
224
+ }
225
+ return undefined;
226
+ } catch {
227
+ return undefined; // never abort a turn
228
+ }
229
+ });
230
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/skill-registry",
3
- "version": "3.0.0-alpha.2",
4
- "description": "Placeholder for the upcoming unipi skill registry — hosts the bundled workflow skills",
3
+ "version": "3.0.0-alpha.21",
4
+ "description": "Skill registry for pi — per-project skill on/off, a skill vault, and jev-judged skill exposure",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Neuron Mr White",
@@ -12,23 +12,34 @@
12
12
  },
13
13
  "keywords": [
14
14
  "pi-package",
15
- "pi-skills",
16
15
  "unipi",
17
16
  "skill-registry"
18
17
  ],
19
18
  "files": [
20
- "skills/**/*",
21
- "README.md"
19
+ "index.ts",
20
+ "src/**/*.ts",
21
+ "README.md",
22
+ "!src/**/*.test.ts"
22
23
  ],
23
24
  "publishConfig": {
24
25
  "access": "public"
25
26
  },
26
27
  "pi": {
27
- "extensions": [],
28
- "skills": [
29
- "./skills"
28
+ "extensions": [
29
+ "./index.ts"
30
30
  ],
31
31
  "prompts": [],
32
32
  "themes": []
33
+ },
34
+ "main": "index.ts",
35
+ "dependencies": {
36
+ "@pi-unipi/core": "3.0.0-alpha.21"
37
+ },
38
+ "peerDependencies": {
39
+ "@earendil-works/pi-coding-agent": "^0.87.1",
40
+ "@earendil-works/pi-tui": "^0.87.1"
41
+ },
42
+ "scripts": {
43
+ "test": "npx tsx --test tests/*.test.ts"
33
44
  }
34
45
  }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * @pi-unipi/skill-registry — "Skill settings…" overlay (like Fusion's preset editor)
3
+ *
4
+ * Skill settings · 47 skills · editing project · proxy on
5
+ * Search: (type to filter)
6
+ * ────────────────────────────────────────────────────────────
7
+ * E M skill source
8
+ * › [x] [ ] sql-review user Review SQL migrations…
9
+ * [ ] [ ] aws-deploy vault Deploy services to AWS…
10
+ *
11
+ * E enabled — the model sees it (judged first when exposure is on); off removes it and blocks /skill:name
12
+ * M must show — always listed, even when judging would hide it
13
+ * ↑↓ skill · ←→ column · space toggle · d inherit · g scope · p proxy · enter save · esc cancel · type to filter
14
+ *
15
+ * Each cell shows the EFFECTIVE value; a value set in the layer being edited
16
+ * is bright, an inherited one dim. Changes are held until Enter.
17
+ */
18
+
19
+ import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
20
+ import { frameOverlay } from "@pi-unipi/core";
21
+ import type { SkillSource } from "./registry.js";
22
+ import type { SkillState } from "./settings.js";
23
+
24
+ export type Column = "enabled" | "mustShow";
25
+ export const COLUMNS: readonly Column[] = ["enabled", "mustShow"];
26
+ const LETTER: Record<Column, string> = { enabled: "E", mustShow: "M" };
27
+
28
+ export interface EditorSkill {
29
+ name: string;
30
+ description: string;
31
+ source: SkillSource;
32
+ }
33
+
34
+ export type Scope = "global" | "project";
35
+ export type Layers = Record<Scope, Record<string, SkillState>>;
36
+
37
+ export interface EditorResult {
38
+ type: "saved" | "cancelled";
39
+ /** The layers as edited (only meaningful when saved). */
40
+ layers: Layers;
41
+ proxy: boolean;
42
+ }
43
+
44
+ export interface EditorOptions {
45
+ skills: readonly EditorSkill[];
46
+ layers: Layers;
47
+ proxy: boolean;
48
+ initialScope: Scope;
49
+ theme: { fg: (color: string, text: string) => string; bold: (text: string) => string };
50
+ onDone: (result: EditorResult) => void;
51
+ onRenderRequest?: () => void;
52
+ visibleRows?: number;
53
+ }
54
+
55
+ function defaultFor(column: Column, source: SkillSource): boolean {
56
+ if (column === "enabled") return source !== "vault";
57
+ return false;
58
+ }
59
+
60
+ /** Effective value of one cell: project over global over the source default. */
61
+ export function cellValue(layers: Layers, skill: EditorSkill, column: Column): { value: boolean; from: Scope | "default" } {
62
+ const p = layers.project[skill.name]?.[column];
63
+ if (typeof p === "boolean") return { value: p, from: "project" };
64
+ const g = layers.global[skill.name]?.[column];
65
+ if (typeof g === "boolean") return { value: g, from: "global" };
66
+ return { value: defaultFor(column, skill.source), from: "default" };
67
+ }
68
+
69
+ function printable(data: string): string | undefined {
70
+ if (data.length !== 1) return undefined;
71
+ const code = data.charCodeAt(0);
72
+ return code < 32 || code === 127 ? undefined : data;
73
+ }
74
+
75
+ export class SkillEditor {
76
+ private readonly opts: EditorOptions;
77
+ private readonly layers: Layers;
78
+ private proxy: boolean;
79
+ private scope: Scope;
80
+ private search = "";
81
+ private selected = 0;
82
+ private column = 0;
83
+ private done = false;
84
+
85
+ constructor(opts: EditorOptions) {
86
+ this.opts = opts;
87
+ this.layers = { global: structuredClone(opts.layers.global), project: structuredClone(opts.layers.project) };
88
+ this.proxy = opts.proxy;
89
+ this.scope = opts.initialScope;
90
+ }
91
+
92
+ private filtered(): EditorSkill[] {
93
+ const q = this.search.toLowerCase();
94
+ const list = q ? this.opts.skills.filter((s) => `${s.name} ${s.source} ${s.description}`.toLowerCase().includes(q)) : [...this.opts.skills];
95
+ return list;
96
+ }
97
+
98
+ handleInput(data: string): void {
99
+ if (this.done) return;
100
+ const list = this.filtered();
101
+ const cur = list[this.selected];
102
+ const col = COLUMNS[this.column]!;
103
+ if (matchesKey(data, Key.escape)) {
104
+ if (this.search) {
105
+ this.search = "";
106
+ this.selected = 0;
107
+ } else {
108
+ this.done = true;
109
+ this.opts.onDone({ type: "cancelled", layers: this.layers, proxy: this.proxy });
110
+ return;
111
+ }
112
+ } else if (matchesKey(data, Key.enter) || data === "\r") {
113
+ this.done = true;
114
+ this.opts.onDone({ type: "saved", layers: this.layers, proxy: this.proxy });
115
+ return;
116
+ } else if (matchesKey(data, Key.up)) this.selected = Math.max(0, this.selected - 1);
117
+ else if (matchesKey(data, Key.down)) this.selected = Math.min(Math.max(0, list.length - 1), this.selected + 1);
118
+ else if (matchesKey(data, Key.left) || matchesKey(data, "shift+tab")) this.column = (this.column + COLUMNS.length - 1) % COLUMNS.length;
119
+ else if (matchesKey(data, Key.right) || matchesKey(data, Key.tab)) this.column = (this.column + 1) % COLUMNS.length;
120
+ else if (matchesKey(data, Key.pageUp)) this.selected = Math.max(0, this.selected - this.window());
121
+ else if (matchesKey(data, Key.pageDown)) this.selected = Math.min(Math.max(0, list.length - 1), this.selected + this.window());
122
+ else if ((data === " " || matchesKey(data, Key.space)) && cur) {
123
+ const next = !cellValue(this.layers, cur, col).value;
124
+ const layer = this.layers[this.scope];
125
+ layer[cur.name] = { ...layer[cur.name], [col]: next };
126
+ } else if (!this.search && data === "d" && cur) {
127
+ const entry = this.layers[this.scope][cur.name];
128
+ if (entry) {
129
+ delete entry[col];
130
+ if (Object.keys(entry).length === 0) delete this.layers[this.scope][cur.name];
131
+ }
132
+ } else if (!this.search && data === "g") this.scope = this.scope === "global" ? "project" : "global";
133
+ else if (!this.search && data === "p") this.proxy = !this.proxy;
134
+ else if (matchesKey(data, Key.backspace) || data === "\x7f") {
135
+ this.search = this.search.slice(0, -1);
136
+ this.selected = 0;
137
+ } else {
138
+ const ch = printable(data);
139
+ if (ch === undefined || ch === " ") return;
140
+ this.search += ch;
141
+ this.selected = 0;
142
+ }
143
+ this.opts.onRenderRequest?.();
144
+ }
145
+
146
+ private window(): number {
147
+ return this.opts.visibleRows ?? Math.max(5, Math.min(18, (process.stdout.rows ?? 40) - 16));
148
+ }
149
+
150
+ invalidate(): void {}
151
+
152
+ render(width: number): string[] {
153
+ return frameOverlay(this.body(Math.max(20, width - 2)), width, { title: "Skill settings" });
154
+ }
155
+
156
+ private body(width: number): string[] {
157
+ const t = this.opts.theme;
158
+ const list = this.filtered();
159
+ if (this.selected >= list.length) this.selected = Math.max(0, list.length - 1);
160
+ const fit = (s: string) => truncateToWidth(s, Math.max(1, width - 1));
161
+ const lines: string[] = [];
162
+ lines.push(fit(`${t.fg("accent", t.bold("Skill settings"))} ${t.fg("dim", `· ${list.length} skills · editing `)}${t.fg("accent", this.scope)}${t.fg("dim", " · proxy ")}${this.proxy ? t.fg("success", "on") : t.fg("warning", "off — saved but not applied (p)")}`));
163
+ lines.push(fit(`${t.fg("dim", "Search:")} ${this.search ? t.fg("text", this.search) : t.fg("dim", "(type to filter)")}`));
164
+ lines.push(t.fg("dim", "─".repeat(Math.max(1, width - 2))));
165
+
166
+ const nameW = Math.min(28, Math.max(10, ...list.map((s) => s.name.length)) + 1);
167
+ const head = COLUMNS.map((c, i) => (i === this.column ? t.fg("accent", t.bold(` ${LETTER[c]} `)) : t.fg("dim", ` ${LETTER[c]} `))).join(" ");
168
+ lines.push(fit(` ${head} ${t.fg("dim", "skill".padEnd(nameW))} ${t.fg("dim", "source ")} ${t.fg("dim", "description")}`));
169
+
170
+ const win = this.window();
171
+ const start = Math.max(0, Math.min(this.selected - Math.floor(win / 2), list.length - win));
172
+ const end = Math.min(list.length, start + win);
173
+ if (list.length === 0) lines.push(t.fg("warning", " No skills match."));
174
+ if (start > 0) lines.push(t.fg("dim", ` ↑ ${start} more`));
175
+ for (let i = start; i < end; i++) {
176
+ const s = list[i]!;
177
+ const hl = i === this.selected;
178
+ const cells = COLUMNS.map((c, ci) => {
179
+ const { value, from } = cellValue(this.layers, s, c);
180
+ const glyph = value ? "[x]" : "[ ]";
181
+ if (hl && ci === this.column) return t.fg("accent", t.bold(glyph));
182
+ if (from === this.scope) return value ? t.fg("success", glyph) : t.fg("text", glyph);
183
+ return t.fg("dim", glyph);
184
+ }).join(" ");
185
+ const name = s.name.length > nameW - 1 ? `${s.name.slice(0, nameW - 2)}…` : s.name;
186
+ const label = hl ? t.fg("accent", name.padEnd(nameW)) : t.fg("text", name.padEnd(nameW));
187
+ const src = t.fg(s.source === "vault" ? "warning" : "dim", s.source.padEnd(7));
188
+ const desc = t.fg("dim", s.description.replace(/\s+/g, " "));
189
+ lines.push(fit(` ${hl ? t.fg("accent", "›") : " "} ${cells} ${label} ${src} ${desc}`));
190
+ }
191
+ if (end < list.length) lines.push(t.fg("dim", ` ↓ ${list.length - end} more`));
192
+
193
+ lines.push("");
194
+ const legend: Array<[Column, string]> = [
195
+ ["enabled", "enabled — the model sees it (judged first when exposure is on); off removes it and blocks /skill:name"],
196
+ ["mustShow", "must show — always listed, even when judging would hide it"],
197
+ ];
198
+ for (const [c, text] of legend) {
199
+ const active = COLUMNS[this.column] === c;
200
+ lines.push(fit(`${active ? t.fg("accent", t.bold(LETTER[c])) : t.fg("dim", LETTER[c])} ${active ? t.fg("text", text) : t.fg("dim", text)}`));
201
+ }
202
+ lines.push(fit(t.fg("dim", `bright = set in ${this.scope} · dim = inherited`)));
203
+ lines.push(fit(t.fg("dim", "↑↓ skill · ←→ column · space toggle · d inherit · g scope · p proxy · enter save · esc cancel · type to filter")));
204
+ return lines.map((l) => (visibleWidth(l) > width ? truncateToWidth(l, width) : l));
205
+ }
206
+ }