@tekmidian/pai 0.42.5 → 0.43.0

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 (297) hide show
  1. package/ARCHITECTURE.md +9 -9
  2. package/README.md +26 -8
  3. package/dist/{aibroker-client-BP74gGwD.mjs → aibroker-client-Bn9SnGMa.mjs} +3 -3
  4. package/dist/{aibroker-client-BP74gGwD.mjs.map → aibroker-client-Bn9SnGMa.mjs.map} +1 -1
  5. package/dist/{aibroker-client-C6m71p6O.mjs → aibroker-client-D5NT0S1N.mjs} +1 -1
  6. package/dist/{auto-route-_5zzJz5q.mjs → auto-route-CcD0OWWB.mjs} +4 -4
  7. package/dist/{auto-route-_5zzJz5q.mjs.map → auto-route-CcD0OWWB.mjs.map} +1 -1
  8. package/dist/{chain-DTII7xeL.mjs → chain-CEC1a5mX.mjs} +5 -4
  9. package/dist/{chain-DTII7xeL.mjs.map → chain-CEC1a5mX.mjs.map} +1 -1
  10. package/dist/cli/index.mjs +23 -21
  11. package/dist/cli/index.mjs.map +1 -1
  12. package/dist/cli/program.d.mts.map +1 -1
  13. package/dist/cli/program.mjs +23 -21
  14. package/dist/{clusters-j0ZuNYp9.mjs → clusters-Cw_P6lO-.mjs} +2 -2
  15. package/dist/{clusters-j0ZuNYp9.mjs.map → clusters-Cw_P6lO-.mjs.map} +1 -1
  16. package/dist/{config-B64vFg14.mjs → config-DCu8THKi.mjs} +41 -9
  17. package/dist/{config-B64vFg14.mjs.map → config-DCu8THKi.mjs.map} +1 -1
  18. package/dist/config-DeqFqa1N.mjs +3 -0
  19. package/dist/config-DxMZjpvY.mjs +1289 -0
  20. package/dist/config-DxMZjpvY.mjs.map +1 -0
  21. package/dist/context-handover-cache-B9NKSjwR.mjs +3242 -0
  22. package/dist/context-handover-cache-B9NKSjwR.mjs.map +1 -0
  23. package/dist/daemon/index.mjs +23 -22
  24. package/dist/daemon/index.mjs.map +1 -1
  25. package/dist/{daemon-B8N1egl0.mjs → daemon-BhldpFvO.mjs} +52 -38
  26. package/dist/daemon-BhldpFvO.mjs.map +1 -0
  27. package/dist/daemon-QIvCXB5f.mjs +24 -0
  28. package/dist/daemon-mcp/index.mjs +144 -117
  29. package/dist/daemon-mcp/index.mjs.map +1 -1
  30. package/dist/db-BOBfewFH.mjs +3 -0
  31. package/dist/{db-a1ixZQjr.mjs → db-DJ8rk0VM.mjs} +15 -6
  32. package/dist/{db-a1ixZQjr.mjs.map → db-DJ8rk0VM.mjs.map} +1 -1
  33. package/dist/db-DbY2O8QO.mjs +3 -0
  34. package/dist/{db-Ca5qfsMC.mjs → db-Ddns80yf.mjs} +15 -6
  35. package/dist/{db-Ca5qfsMC.mjs.map → db-Ddns80yf.mjs.map} +1 -1
  36. package/dist/{detect-CdaA48EI.mjs → detect-Bf2z-oKB.mjs} +1 -1
  37. package/dist/{detect-CdaA48EI.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
  38. package/dist/detector-CkwkXuGh.mjs +5 -0
  39. package/dist/{detector-C73Nxm94.mjs → detector-DWXkeTlk.mjs} +2 -2
  40. package/dist/{detector-C73Nxm94.mjs.map → detector-DWXkeTlk.mjs.map} +1 -1
  41. package/dist/{embeddings-CEBGrzwu.mjs → embeddings-ClqKtLZ8.mjs} +1 -1
  42. package/dist/{embeddings-DOLZnT1X.mjs → embeddings-DvRKcQyt.mjs} +1 -1
  43. package/dist/{embeddings-DOLZnT1X.mjs.map → embeddings-DvRKcQyt.mjs.map} +1 -1
  44. package/dist/factory-47Qf_df7.mjs +3 -0
  45. package/dist/{factory-DEb9NmHJ.mjs → factory-DNYAqjH0.mjs} +8 -8
  46. package/dist/{factory-DEb9NmHJ.mjs.map → factory-DNYAqjH0.mjs.map} +1 -1
  47. package/dist/{fallback-oJl3BaIN.mjs → fallback-BQJ8PbCK.mjs} +185 -123
  48. package/dist/fallback-BQJ8PbCK.mjs.map +1 -0
  49. package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
  50. package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
  51. package/dist/hooks/capture-all-events.mjs +117 -34
  52. package/dist/hooks/capture-all-events.mjs.map +4 -4
  53. package/dist/hooks/capture-session-summary.mjs +110 -33
  54. package/dist/hooks/capture-session-summary.mjs.map +4 -4
  55. package/dist/hooks/capture-tool-output.mjs +105 -28
  56. package/dist/hooks/capture-tool-output.mjs.map +4 -4
  57. package/dist/hooks/cleanup-session-files.mjs +54 -13
  58. package/dist/hooks/cleanup-session-files.mjs.map +2 -2
  59. package/dist/hooks/context-compression-hook.mjs +7565 -122
  60. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  61. package/dist/hooks/initialize-session.mjs +55 -14
  62. package/dist/hooks/initialize-session.mjs.map +2 -2
  63. package/dist/hooks/inject-observations.mjs +79 -18
  64. package/dist/hooks/inject-observations.mjs.map +3 -3
  65. package/dist/hooks/load-core-context.mjs +55 -14
  66. package/dist/hooks/load-core-context.mjs.map +2 -2
  67. package/dist/hooks/load-project-context.mjs +207 -122
  68. package/dist/hooks/load-project-context.mjs.map +4 -4
  69. package/dist/hooks/mcp-deferred-gate.mjs +54 -13
  70. package/dist/hooks/mcp-deferred-gate.mjs.map +2 -2
  71. package/dist/hooks/mcp-deferred-reminder.mjs +9 -0
  72. package/dist/hooks/mcp-deferred-reminder.mjs.map +3 -3
  73. package/dist/hooks/observe.mjs +54 -13
  74. package/dist/hooks/observe.mjs.map +2 -2
  75. package/dist/hooks/post-compact-inject.mjs +7419 -16
  76. package/dist/hooks/post-compact-inject.mjs.map +4 -4
  77. package/dist/hooks/route-agents-to-worker.mjs +7778 -171
  78. package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
  79. package/dist/hooks/route-edits-to-worker.mjs +90 -0
  80. package/dist/hooks/route-edits-to-worker.mjs.map +7 -0
  81. package/dist/hooks/security-validator.mjs +147 -6
  82. package/dist/hooks/security-validator.mjs.map +4 -4
  83. package/dist/hooks/session-commands.mjs +9 -0
  84. package/dist/hooks/session-commands.mjs.map +3 -3
  85. package/dist/hooks/stop-hook.mjs +182 -105
  86. package/dist/hooks/stop-hook.mjs.map +4 -4
  87. package/dist/hooks/subagent-stop-hook.mjs +3 -0
  88. package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
  89. package/dist/hooks/sync-todo-to-md.mjs +54 -13
  90. package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
  91. package/dist/hooks/update-tab-on-action.mjs +3 -0
  92. package/dist/hooks/update-tab-on-action.mjs.map +1 -1
  93. package/dist/hooks/update-tab-titles.mjs +3 -0
  94. package/dist/hooks/update-tab-titles.mjs.map +1 -1
  95. package/dist/hooks/whisper-reinject.mjs +11 -0
  96. package/dist/hooks/whisper-reinject.mjs.map +3 -3
  97. package/dist/hooks/whisper-rules.mjs +134 -29
  98. package/dist/hooks/whisper-rules.mjs.map +4 -4
  99. package/dist/hooks/worker-proxy.mjs +7737 -117
  100. package/dist/hooks/worker-proxy.mjs.map +4 -4
  101. package/dist/hooks/worker-status-line.mjs +7883 -169
  102. package/dist/hooks/worker-status-line.mjs.map +4 -4
  103. package/dist/hooks/worker-supervision.mjs +7721 -115
  104. package/dist/hooks/worker-supervision.mjs.map +4 -4
  105. package/dist/index.d.mts +4 -2
  106. package/dist/index.d.mts.map +1 -1
  107. package/dist/index.mjs +10 -10
  108. package/dist/{indexer-backend-BPTzKFZU.mjs → indexer-backend-CdvCJCkD.mjs} +3 -3
  109. package/dist/{indexer-backend-BPTzKFZU.mjs.map → indexer-backend-CdvCJCkD.mjs.map} +1 -1
  110. package/dist/{ipc-client-BmypMNYk.mjs → ipc-client-C6NryMMb.mjs} +2 -2
  111. package/dist/{ipc-client-BmypMNYk.mjs.map → ipc-client-C6NryMMb.mjs.map} +1 -1
  112. package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-BSjM5xZh.mjs} +1 -1
  113. package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-BSjM5xZh.mjs.map} +1 -1
  114. package/dist/{latent-ideas-CtSca4qG.mjs → latent-ideas-BfL4nCQ3.mjs} +4 -4
  115. package/dist/{latent-ideas-CtSca4qG.mjs.map → latent-ideas-BfL4nCQ3.mjs.map} +1 -1
  116. package/dist/{link-boost-Bucnz3n8.mjs → link-boost-C5PaNdzm.mjs} +1 -1
  117. package/dist/{link-boost-Bucnz3n8.mjs.map → link-boost-C5PaNdzm.mjs.map} +1 -1
  118. package/dist/main-resolver-CnfnWa8C.mjs +7 -0
  119. package/dist/{main-resolver-DnkxPYkr.mjs → main-resolver-DvYu9jbl.mjs} +21 -11
  120. package/dist/main-resolver-DvYu9jbl.mjs.map +1 -0
  121. package/dist/{migrate-Cjzeefn9.mjs → migrate-CfERwXhY.mjs} +2 -2
  122. package/dist/{migrate-Cjzeefn9.mjs.map → migrate-CfERwXhY.mjs.map} +1 -1
  123. package/dist/{neighborhood-CM7Jgb51.mjs → neighborhood-CETR_P4y.mjs} +2 -2
  124. package/dist/{neighborhood-CM7Jgb51.mjs.map → neighborhood-CETR_P4y.mjs.map} +1 -1
  125. package/dist/{note-context-DCwE2A-M.mjs → note-context-DCcRWBoN.mjs} +1 -1
  126. package/dist/{note-context-DCwE2A-M.mjs.map → note-context-DCcRWBoN.mjs.map} +1 -1
  127. package/dist/pai-home-ChAOupZc.mjs +164 -0
  128. package/dist/pai-home-ChAOupZc.mjs.map +1 -0
  129. package/dist/{pai-marker-CHtbJMwJ.mjs → pai-marker-B20KqhA8.mjs} +1 -1
  130. package/dist/{pai-marker-CHtbJMwJ.mjs.map → pai-marker-B20KqhA8.mjs.map} +1 -1
  131. package/dist/{planner-D5Ui7DAm.mjs → planner-BlKGgpWI.mjs} +7 -6
  132. package/dist/{planner-D5Ui7DAm.mjs.map → planner-BlKGgpWI.mjs.map} +1 -1
  133. package/dist/{postgres-CtOzfoQU.mjs → postgres-CUSXWgTG.mjs} +2 -2
  134. package/dist/{postgres-CtOzfoQU.mjs.map → postgres-CUSXWgTG.mjs.map} +1 -1
  135. package/dist/{program-zpoYnJGz.mjs → program-C5jRQ1kg.mjs} +8880 -5913
  136. package/dist/program-C5jRQ1kg.mjs.map +1 -0
  137. package/dist/query-feedback-C7rSfiXU.mjs +3 -0
  138. package/dist/{query-feedback-Dqz-HC__.mjs → query-feedback-CYyOGi3y.mjs} +20 -6
  139. package/dist/query-feedback-CYyOGi3y.mjs.map +1 -0
  140. package/dist/{reranker-CwTCNsgA.mjs → reranker-B5qUTamW.mjs} +1 -1
  141. package/dist/{reranker-xPm04PXx.mjs → reranker-HHmhZk6v.mjs} +1 -1
  142. package/dist/{reranker-xPm04PXx.mjs.map → reranker-HHmhZk6v.mjs.map} +1 -1
  143. package/dist/router-CremHKTI.mjs +3 -0
  144. package/dist/{router-DD6D4j31.mjs → router-DBAZfE7X.mjs} +2 -2
  145. package/dist/{router-DD6D4j31.mjs.map → router-DBAZfE7X.mjs.map} +1 -1
  146. package/dist/{run-D7K5UALn.mjs → run-ByBKowN5.mjs} +980 -247
  147. package/dist/run-ByBKowN5.mjs.map +1 -0
  148. package/dist/{runtime-paths-rni52zHX.mjs → runtime-paths-B0P1TvUr.mjs} +1 -1
  149. package/dist/{runtime-paths-rni52zHX.mjs.map → runtime-paths-B0P1TvUr.mjs.map} +1 -1
  150. package/dist/{search-Rpk1cSBC.mjs → search-B69cQwDB.mjs} +3 -3
  151. package/dist/{search-Rpk1cSBC.mjs.map → search-B69cQwDB.mjs.map} +1 -1
  152. package/dist/{search-CfPpJAWQ.mjs → search-fb-5ovj4.mjs} +2 -2
  153. package/dist/{server-D4vAieF4.mjs → server-Ch99xt5K.mjs} +15 -14
  154. package/dist/server-Ch99xt5K.mjs.map +1 -0
  155. package/dist/skills/Advisor/SKILL.md +3 -3
  156. package/dist/skills/Providers/SKILL.md +30 -0
  157. package/dist/skills/Reconstruct/SKILL.md +1 -1
  158. package/dist/skills/VaultContext/SKILL.md +1 -1
  159. package/dist/skills/Whisper/SKILL.md +3 -3
  160. package/dist/skills/Worker/SKILL.md +1 -1
  161. package/dist/{sources-CS43CpuS.mjs → sources-KBGEA_E6.mjs} +2 -2
  162. package/dist/{sources-CS43CpuS.mjs.map → sources-KBGEA_E6.mjs.map} +1 -1
  163. package/dist/{sqlite-Cq3gJcq9.mjs → sqlite-By6r2YhZ.mjs} +3 -3
  164. package/dist/{sqlite-Cq3gJcq9.mjs.map → sqlite-By6r2YhZ.mjs.map} +1 -1
  165. package/dist/{state-CY6I-Hnc.mjs → state-DnYGRmS7.mjs} +1 -1
  166. package/dist/{state-BRdB-Bf1.mjs → state-PJrgfDLW.mjs} +1 -1
  167. package/dist/{state-BRdB-Bf1.mjs.map → state-PJrgfDLW.mjs.map} +1 -1
  168. package/dist/{stop-words-Hfu8u22w.mjs → stop-words-BaMEGVeY.mjs} +1 -1
  169. package/dist/{stop-words-Hfu8u22w.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
  170. package/dist/{sync-BWbe8JTg.mjs → sync-Btcb6xA4.mjs} +3 -3
  171. package/dist/{sync-BWbe8JTg.mjs.map → sync-Btcb6xA4.mjs.map} +1 -1
  172. package/dist/{themes-B7h9YDiy.mjs → themes-BVoAnLZj.mjs} +3 -3
  173. package/dist/{themes-B7h9YDiy.mjs.map → themes-BVoAnLZj.mjs.map} +1 -1
  174. package/dist/{tools-BW3NLMPk.mjs → tools-CAG2sUjf.mjs} +4 -3
  175. package/dist/{tools-LlzcV_eK.mjs → tools-DiRC5glA.mjs} +30 -233
  176. package/dist/tools-DiRC5glA.mjs.map +1 -0
  177. package/dist/{trace-u4hT-pLD.mjs → trace-Cy8quQTm.mjs} +1 -1
  178. package/dist/{trace-u4hT-pLD.mjs.map → trace-Cy8quQTm.mjs.map} +1 -1
  179. package/dist/{utils-DhMex3Ox.mjs → utils-BD8g8oSf.mjs} +1 -1
  180. package/dist/{utils-9Err2RBW.mjs → utils-CJymHkYm.mjs} +1 -1
  181. package/dist/{utils-9Err2RBW.mjs.map → utils-CJymHkYm.mjs.map} +1 -1
  182. package/dist/{vault-indexer-mata9S7b.mjs → vault-indexer-C5i-OAyr.mjs} +2 -2
  183. package/dist/{vault-indexer-mata9S7b.mjs.map → vault-indexer-C5i-OAyr.mjs.map} +1 -1
  184. package/dist/wakeup-BfElduEU.mjs +231 -0
  185. package/dist/wakeup-BfElduEU.mjs.map +1 -0
  186. package/dist/work-queue-worker-BA5IDBwQ.mjs +567 -0
  187. package/dist/work-queue-worker-BA5IDBwQ.mjs.map +1 -0
  188. package/dist/work-queue-worker-DLM2wlqM.mjs +14 -0
  189. package/dist/{zettelkasten-F0D_XfQ4.mjs → zettelkasten-BBjA7GcP.mjs} +4 -4
  190. package/dist/{zettelkasten-F0D_XfQ4.mjs.map → zettelkasten-BBjA7GcP.mjs.map} +1 -1
  191. package/docs/commands/README.md +32 -3
  192. package/docs/commands/audit.md +113 -0
  193. package/docs/commands/backup.md +2 -2
  194. package/docs/commands/clear-names.md +1 -1
  195. package/docs/commands/config.md +59 -0
  196. package/docs/commands/daemon.md +1 -1
  197. package/docs/commands/db.md +1 -1
  198. package/docs/commands/end.md +1 -1
  199. package/docs/commands/help.md +1 -1
  200. package/docs/commands/identity.md +1 -1
  201. package/docs/commands/kg.md +1 -1
  202. package/docs/commands/launch.md +45 -0
  203. package/docs/commands/mcp.md +1 -1
  204. package/docs/commands/memory.md +3 -3
  205. package/docs/commands/notify.md +1 -1
  206. package/docs/commands/observation.md +1 -1
  207. package/docs/commands/obsidian.md +1 -1
  208. package/docs/commands/pause.md +1 -1
  209. package/docs/commands/project.md +46 -1
  210. package/docs/commands/projects.md +46 -1
  211. package/docs/commands/registry.md +1 -1
  212. package/docs/commands/restore.md +1 -1
  213. package/docs/commands/session.md +1 -1
  214. package/docs/commands/sessions.md +1 -1
  215. package/docs/commands/setup.md +1 -1
  216. package/docs/commands/shell-init.md +1 -1
  217. package/docs/commands/skill.md +1 -1
  218. package/docs/commands/task.md +1 -1
  219. package/docs/commands/topic.md +1 -1
  220. package/docs/commands/update.md +1 -1
  221. package/docs/commands/worker.md +97 -6
  222. package/docs/commands/zettel.md +1 -1
  223. package/docs/provider-abstraction.md +1 -1
  224. package/docs/provider-independence-details.md +8 -5
  225. package/docs/task-bus.md +1 -1
  226. package/docs/worker.md +136 -83
  227. package/docs/workers-config.md +446 -0
  228. package/package.json +4 -2
  229. package/scripts/build-hooks.mjs +58 -3
  230. package/src/hooks/pre-compact.sh +5 -2
  231. package/src/hooks/session-stop.sh +29 -4
  232. package/src/hooks/ts/capture-all-events.ts +3 -3
  233. package/src/hooks/ts/lib/advisor-budget.test.ts +98 -0
  234. package/src/hooks/ts/lib/advisor-budget.ts +90 -0
  235. package/src/hooks/ts/lib/agent-gate.test.ts +101 -0
  236. package/src/hooks/ts/lib/agent-gate.ts +112 -0
  237. package/src/hooks/ts/lib/context-fill.ts +2 -2
  238. package/src/hooks/ts/lib/edit-gate.test.ts +52 -0
  239. package/src/hooks/ts/lib/edit-gate.ts +51 -0
  240. package/src/hooks/ts/lib/mcp-deferred-reminder.test.ts +8 -0
  241. package/src/hooks/ts/lib/pai-paths.test.ts +93 -0
  242. package/src/hooks/ts/lib/pai-paths.ts +168 -24
  243. package/src/hooks/ts/lib/project-utils/index.ts +1 -0
  244. package/src/hooks/ts/lib/project-utils/paths.ts +5 -4
  245. package/src/hooks/ts/lib/project-utils.ts +1 -0
  246. package/src/hooks/ts/post-tool-use/capture-tool-output.ts +2 -2
  247. package/src/hooks/ts/post-tool-use/whisper-reinject.test.ts +55 -0
  248. package/src/hooks/ts/post-tool-use/whisper-reinject.ts +3 -0
  249. package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
  250. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +46 -99
  251. package/src/hooks/ts/pre-tool-use/route-edits-to-worker.test.ts +47 -0
  252. package/src/hooks/ts/pre-tool-use/route-edits-to-worker.ts +96 -0
  253. package/src/hooks/ts/pre-tool-use/security-validator.ts +2 -1
  254. package/src/hooks/ts/session-end/capture-session-summary.ts +4 -4
  255. package/src/hooks/ts/session-start/initialize-session.ts +3 -3
  256. package/src/hooks/ts/session-start/inject-observations.test.ts +56 -0
  257. package/src/hooks/ts/session-start/inject-observations.ts +28 -8
  258. package/src/hooks/ts/session-start/load-core-context.ts +4 -4
  259. package/src/hooks/ts/session-start/load-project-context.ts +2 -2
  260. package/src/hooks/ts/session-start/mcp-deferred-reminder.ts +2 -0
  261. package/src/hooks/ts/session-start/session-commands.ts +4 -0
  262. package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +36 -0
  263. package/src/hooks/ts/stop/stop-hook.ts +6 -11
  264. package/src/hooks/ts/user-prompt/whisper-rules.test.ts +49 -0
  265. package/src/hooks/ts/user-prompt/whisper-rules.ts +88 -47
  266. package/src/hooks/ztk-auto.sh +28 -0
  267. package/src/hooks/ztk-auto.test.ts +72 -0
  268. package/src/hooks/ztk-passthrough.mjs +71 -0
  269. package/statusline-command.sh +277 -116
  270. package/templates/agent-prefs.example.md +8 -8
  271. package/templates/claude-md.template.md +1 -1
  272. package/templates/voices.example.json +3 -3
  273. package/dist/config-C_ErGddD.mjs +0 -3
  274. package/dist/config-_lQI_W08.mjs +0 -640
  275. package/dist/config-_lQI_W08.mjs.map +0 -1
  276. package/dist/context-handover-cache-pNFdBNrU.mjs +0 -1548
  277. package/dist/context-handover-cache-pNFdBNrU.mjs.map +0 -1
  278. package/dist/daemon-B8N1egl0.mjs.map +0 -1
  279. package/dist/daemon-DHfyoTlk.mjs +0 -23
  280. package/dist/db-O-cyAPfS.mjs +0 -3
  281. package/dist/db-XEwJbuGO.mjs +0 -3
  282. package/dist/detector-C-7KRi42.mjs +0 -5
  283. package/dist/factory-Dbo8yNPi.mjs +0 -3
  284. package/dist/fallback-oJl3BaIN.mjs.map +0 -1
  285. package/dist/main-resolver-BAfOMppW.mjs +0 -7
  286. package/dist/main-resolver-DnkxPYkr.mjs.map +0 -1
  287. package/dist/program-zpoYnJGz.mjs.map +0 -1
  288. package/dist/query-feedback-DGoVRaWD.mjs +0 -3
  289. package/dist/query-feedback-Dqz-HC__.mjs.map +0 -1
  290. package/dist/router-BTIGZR2_.mjs +0 -3
  291. package/dist/run-D7K5UALn.mjs.map +0 -1
  292. package/dist/server-D4vAieF4.mjs.map +0 -1
  293. package/dist/tools-LlzcV_eK.mjs.map +0 -1
  294. package/dist/work-queue-worker-AEmfVE0F.mjs +0 -14
  295. package/dist/work-queue-worker-DIT-VLSh.mjs +0 -2136
  296. package/dist/work-queue-worker-DIT-VLSh.mjs.map +0 -1
  297. /package/dist/{indexer-D7MvSQPY.mjs → indexer-C5Le7XE7.mjs} +0 -0
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __paiCreateRequire } from 'node:module';
3
+ const require = __paiCreateRequire(import.meta.url);
4
+ if (!process.env.PAI_QUIET_NOTICES) process.env.PAI_QUIET_NOTICES = '1';
5
+
6
+ // src/hooks/ts/pre-tool-use/route-edits-to-worker.ts
7
+ import { existsSync } from "node:fs";
8
+ import { dirname, join, resolve, sep } from "node:path";
9
+ import { homedir, tmpdir } from "node:os";
10
+
11
+ // src/hooks/ts/lib/edit-gate.ts
12
+ import { basename, extname } from "node:path";
13
+ var PROSE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt"]);
14
+ function isProseEdit(filePath) {
15
+ const ext = extname(filePath).toLowerCase();
16
+ if (PROSE_EXTENSIONS.has(ext)) return true;
17
+ return filePath.split(/[\\/]/).some((segment) => segment === "Notes" || segment === "notes");
18
+ }
19
+ function decideEditGate(ctx) {
20
+ if (ctx.isPaiWorker) return { decision: "allow" };
21
+ if (ctx.isExemptPrefix) return { decision: "allow" };
22
+ if (isProseEdit(ctx.filePath)) return { decision: "allow" };
23
+ if (!ctx.isGitWorkTree) return { decision: "allow" };
24
+ const label = basename(ctx.filePath).slice(0, 60) || "edit";
25
+ return {
26
+ decision: "block",
27
+ reason: `main session does not edit code: run a worker instead \u2014 pai worker run --label "${label}" --class implement --output-format json -p <spec> (target: ${ctx.filePath})`
28
+ };
29
+ }
30
+
31
+ // src/hooks/ts/pre-tool-use/route-edits-to-worker.ts
32
+ function isUnder(p, prefix) {
33
+ return p === prefix || p.startsWith(prefix + sep);
34
+ }
35
+ function inGitWorkTree(p) {
36
+ let dir = resolve(p);
37
+ for (; ; ) {
38
+ if (existsSync(join(dir, ".git"))) return true;
39
+ const parent = dirname(dir);
40
+ if (parent === dir) return false;
41
+ dir = parent;
42
+ }
43
+ }
44
+ function exemptPrefixes() {
45
+ const home = homedir();
46
+ return [
47
+ join(home, ".claude"),
48
+ join(home, "colibri", "Notes"),
49
+ join(home, "colibri", "tasks"),
50
+ join(home, "colibri", "scratchpad"),
51
+ tmpdir(),
52
+ "/tmp"
53
+ ];
54
+ }
55
+ function allow() {
56
+ process.exit(0);
57
+ }
58
+ function block(reason) {
59
+ process.stdout.write(JSON.stringify({ decision: "block", reason }) + "\n");
60
+ process.exit(0);
61
+ }
62
+ async function main() {
63
+ let text = "";
64
+ try {
65
+ for await (const chunk of process.stdin) text += chunk;
66
+ } catch {
67
+ return allow();
68
+ }
69
+ if (!text.trim()) return allow();
70
+ let input;
71
+ try {
72
+ input = JSON.parse(text);
73
+ } catch {
74
+ return allow();
75
+ }
76
+ const filePath = input.tool_input && typeof input.tool_input.file_path === "string" ? input.tool_input.file_path : "";
77
+ if (!filePath) return allow();
78
+ const abs = resolve(input.cwd ?? process.cwd(), filePath);
79
+ const ctx = {
80
+ filePath: abs,
81
+ isPaiWorker: process.env.PAI_WORKER === "1",
82
+ isExemptPrefix: exemptPrefixes().some((p) => isUnder(abs, p)),
83
+ isGitWorkTree: inGitWorkTree(abs)
84
+ };
85
+ const decision = decideEditGate(ctx);
86
+ if (decision.decision === "block") return block(decision.reason);
87
+ return allow();
88
+ }
89
+ main().catch(() => process.exit(0));
90
+ //# sourceMappingURL=route-edits-to-worker.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/hooks/ts/pre-tool-use/route-edits-to-worker.ts", "../../../src/hooks/ts/lib/edit-gate.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * route-edits-to-worker.ts \u2014 PreToolUse (Edit|MultiEdit|Write)\n *\n * The outer orchestrator session does not edit code files; code changes go\n * through `pai worker run`. Allowed through silently:\n * - worker sessions (the runner sets PAI_WORKER=1)\n * - paths outside any git work tree\n * - the exempt scratch prefixes (.claude incl. memory dirs, the local\n * notes/tasks/scratchpad dirs, the OS temp dir)\n * - prose: notes, session logs, handovers \u2014 see lib/edit-gate.ts isProseEdit\n * Everything else inside a git work tree is blocked with the worker command.\n *\n * This file is only I/O: read stdin, resolve the path, ask lib/edit-gate for\n * the decision, print it. The decision itself lives in lib/edit-gate.ts and\n * is tested there.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { dirname, join, resolve, sep } from \"node:path\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { decideEditGate, type EditGateContext } from \"../lib/edit-gate.js\";\n\nfunction isUnder(p: string, prefix: string): boolean {\n return p === prefix || p.startsWith(prefix + sep);\n}\n\n// A .git entry in any ancestor marks a git work tree (.git is a directory in\n// a normal repo, a file in a linked worktree \u2014 existsSync covers both).\nfunction inGitWorkTree(p: string): boolean {\n let dir = resolve(p);\n for (;;) {\n if (existsSync(join(dir, \".git\"))) return true;\n const parent = dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n\nfunction exemptPrefixes(): string[] {\n const home = homedir();\n return [\n join(home, \".claude\"),\n join(home, \"colibri\", \"Notes\"),\n join(home, \"colibri\", \"tasks\"),\n join(home, \"colibri\", \"scratchpad\"),\n tmpdir(),\n \"/tmp\",\n ];\n}\n\nfunction allow(): void {\n process.exit(0);\n}\n\nfunction block(reason: string): void {\n process.stdout.write(JSON.stringify({ decision: \"block\", reason }) + \"\\n\");\n process.exit(0);\n}\n\nasync function main(): Promise<void> {\n let text = \"\";\n try {\n for await (const chunk of process.stdin) text += chunk;\n } catch {\n return allow();\n }\n if (!text.trim()) return allow();\n\n let input: { cwd?: string; tool_input?: { file_path?: string } };\n try {\n input = JSON.parse(text);\n } catch {\n return allow();\n }\n\n const filePath =\n input.tool_input && typeof input.tool_input.file_path === \"string\" ? input.tool_input.file_path : \"\";\n if (!filePath) return allow();\n\n const abs = resolve(input.cwd ?? process.cwd(), filePath);\n\n const ctx: EditGateContext = {\n filePath: abs,\n isPaiWorker: process.env.PAI_WORKER === \"1\",\n isExemptPrefix: exemptPrefixes().some((p) => isUnder(abs, p)),\n isGitWorkTree: inGitWorkTree(abs),\n };\n\n const decision = decideEditGate(ctx);\n if (decision.decision === \"block\") return block(decision.reason);\n return allow();\n}\n\nmain().catch(() => process.exit(0));\n", "/**\n * edit-gate.ts \u2014 the decision behind the PreToolUse edit-routing gate.\n *\n * Pure: no stdin, no filesystem, no process.env read of its own. The hook\n * entry (pre-tool-use/route-edits-to-worker.ts) resolves paths and checks the\n * filesystem/env, then hands this module the already-computed facts to\n * decide on \u2014 same split as lib/agent-gate.ts.\n */\n\nimport { basename, extname } from \"node:path\";\n\nexport interface EditGateContext {\n /** Absolute, already-resolved target path. */\n filePath: string;\n isPaiWorker: boolean;\n isExemptPrefix: boolean;\n isGitWorkTree: boolean;\n}\n\nexport type EditGateDecision = { decision: \"allow\" } | { decision: \"block\"; reason: string };\n\n/** Extensions that are prose regardless of which directory they live in. */\nconst PROSE_EXTENSIONS = new Set([\".md\", \".markdown\", \".txt\"]);\n\n/**\n * True for notes, session logs, handovers and other prose the outer session\n * writes directly instead of routing through a worker \u2014 a `Notes`/`notes`\n * directory segment anywhere in the path, or a prose file extension.\n * Extension check runs first: a source file that happens to live under a\n * `notes/` directory is still code, but `src/foo.md` is still prose.\n */\nexport function isProseEdit(filePath: string): boolean {\n const ext = extname(filePath).toLowerCase();\n if (PROSE_EXTENSIONS.has(ext)) return true;\n return filePath.split(/[\\\\/]/).some((segment) => segment === \"Notes\" || segment === \"notes\");\n}\n\nexport function decideEditGate(ctx: EditGateContext): EditGateDecision {\n if (ctx.isPaiWorker) return { decision: \"allow\" };\n if (ctx.isExemptPrefix) return { decision: \"allow\" };\n if (isProseEdit(ctx.filePath)) return { decision: \"allow\" };\n if (!ctx.isGitWorkTree) return { decision: \"allow\" };\n\n const label = basename(ctx.filePath).slice(0, 60) || \"edit\";\n return {\n decision: \"block\",\n reason:\n `main session does not edit code: run a worker instead \u2014 ` +\n `pai worker run --label \"${label}\" --class implement --output-format json -p <spec> (target: ${ctx.filePath})`,\n };\n}\n"],
5
+ "mappings": ";;;;;;AAmBA,SAAS,kBAAkB;AAC3B,SAAS,SAAS,MAAM,SAAS,WAAW;AAC5C,SAAS,SAAS,cAAc;;;ACZhC,SAAS,UAAU,eAAe;AAalC,IAAM,mBAAmB,oBAAI,IAAI,CAAC,OAAO,aAAa,MAAM,CAAC;AAStD,SAAS,YAAY,UAA2B;AACrD,QAAM,MAAM,QAAQ,QAAQ,EAAE,YAAY;AAC1C,MAAI,iBAAiB,IAAI,GAAG,EAAG,QAAO;AACtC,SAAO,SAAS,MAAM,OAAO,EAAE,KAAK,CAAC,YAAY,YAAY,WAAW,YAAY,OAAO;AAC7F;AAEO,SAAS,eAAe,KAAwC;AACrE,MAAI,IAAI,YAAa,QAAO,EAAE,UAAU,QAAQ;AAChD,MAAI,IAAI,eAAgB,QAAO,EAAE,UAAU,QAAQ;AACnD,MAAI,YAAY,IAAI,QAAQ,EAAG,QAAO,EAAE,UAAU,QAAQ;AAC1D,MAAI,CAAC,IAAI,cAAe,QAAO,EAAE,UAAU,QAAQ;AAEnD,QAAM,QAAQ,SAAS,IAAI,QAAQ,EAAE,MAAM,GAAG,EAAE,KAAK;AACrD,SAAO;AAAA,IACL,UAAU;AAAA,IACV,QACE,wFAC2B,KAAK,+DAA+D,IAAI,QAAQ;AAAA,EAC/G;AACF;;;AD1BA,SAAS,QAAQ,GAAW,QAAyB;AACnD,SAAO,MAAM,UAAU,EAAE,WAAW,SAAS,GAAG;AAClD;AAIA,SAAS,cAAc,GAAoB;AACzC,MAAI,MAAM,QAAQ,CAAC;AACnB,aAAS;AACP,QAAI,WAAW,KAAK,KAAK,MAAM,CAAC,EAAG,QAAO;AAC1C,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAEA,SAAS,iBAA2B;AAClC,QAAM,OAAO,QAAQ;AACrB,SAAO;AAAA,IACL,KAAK,MAAM,SAAS;AAAA,IACpB,KAAK,MAAM,WAAW,OAAO;AAAA,IAC7B,KAAK,MAAM,WAAW,OAAO;AAAA,IAC7B,KAAK,MAAM,WAAW,YAAY;AAAA,IAClC,OAAO;AAAA,IACP;AAAA,EACF;AACF;AAEA,SAAS,QAAc;AACrB,UAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,MAAM,QAAsB;AACnC,UAAQ,OAAO,MAAM,KAAK,UAAU,EAAE,UAAU,SAAS,OAAO,CAAC,IAAI,IAAI;AACzE,UAAQ,KAAK,CAAC;AAChB;AAEA,eAAe,OAAsB;AACnC,MAAI,OAAO;AACX,MAAI;AACF,qBAAiB,SAAS,QAAQ,MAAO,SAAQ;AAAA,EACnD,QAAQ;AACN,WAAO,MAAM;AAAA,EACf;AACA,MAAI,CAAC,KAAK,KAAK,EAAG,QAAO,MAAM;AAE/B,MAAI;AACJ,MAAI;AACF,YAAQ,KAAK,MAAM,IAAI;AAAA,EACzB,QAAQ;AACN,WAAO,MAAM;AAAA,EACf;AAEA,QAAM,WACJ,MAAM,cAAc,OAAO,MAAM,WAAW,cAAc,WAAW,MAAM,WAAW,YAAY;AACpG,MAAI,CAAC,SAAU,QAAO,MAAM;AAE5B,QAAM,MAAM,QAAQ,MAAM,OAAO,QAAQ,IAAI,GAAG,QAAQ;AAExD,QAAM,MAAuB;AAAA,IAC3B,UAAU;AAAA,IACV,aAAa,QAAQ,IAAI,eAAe;AAAA,IACxC,gBAAgB,eAAe,EAAE,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,IAC5D,eAAe,cAAc,GAAG;AAAA,EAClC;AAEA,QAAM,WAAW,eAAe,GAAG;AACnC,MAAI,SAAS,aAAa,QAAS,QAAO,MAAM,SAAS,MAAM;AAC/D,SAAO,MAAM;AACf;AAEA,KAAK,EAAE,MAAM,MAAM,QAAQ,KAAK,CAAC,CAAC;",
6
+ "names": []
7
+ }
@@ -1,8 +1,149 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as __paiCreateRequire } from 'node:module';
3
+ const require = __paiCreateRequire(import.meta.url);
4
+ if (!process.env.PAI_QUIET_NOTICES) process.env.PAI_QUIET_NOTICES = '1';
5
+
6
+ // src/hooks/ts/pre-tool-use/security-validator.ts
7
+ import { appendFileSync, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
8
+ import { dirname as dirname2 } from "path";
9
+
10
+ // src/hooks/ts/lib/pai-paths.ts
11
+ import { homedir as homedir2 } from "os";
12
+ import { resolve, join as join2 } from "path";
13
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
14
+
15
+ // src/config/pai-home.ts
16
+ import { existsSync, mkdirSync, readFileSync, copyFileSync, renameSync, readdirSync, statSync } from "node:fs";
17
+ import { homedir } from "node:os";
18
+ import { join, dirname } from "node:path";
19
+ function paiHomeDir() {
20
+ return process.env.PAI_HOME || join(homedir(), ".claude", "pai");
21
+ }
22
+ function paiHomePath(...segments) {
23
+ return join(paiHomeDir(), ...segments);
24
+ }
25
+ var noticesPrinted = /* @__PURE__ */ new Set();
26
+ function resolvePaiFile(newPath, oldCandidates, migrateHint) {
27
+ if (existsSync(newPath)) return newPath;
28
+ for (const old of oldCandidates) {
29
+ if (existsSync(old)) {
30
+ if (!noticesPrinted.has(newPath)) {
31
+ noticesPrinted.add(newPath);
32
+ process.stderr.write(
33
+ `pai: ${old} is at an old location \u2014 run \`${migrateHint}\` to move it to ${newPath}
34
+ `
35
+ );
36
+ }
37
+ return old;
38
+ }
39
+ }
40
+ return newPath;
41
+ }
42
+
43
+ // src/hooks/ts/lib/pai-paths.ts
44
+ function loadEnvFile() {
45
+ const possiblePaths = [
46
+ resolve(process.env.ADAPTER_DIR || process.env.PAI_DIR || "", ".env"),
47
+ resolve(homedir2(), ".claude", ".env")
48
+ ];
49
+ for (const envPath of possiblePaths) {
50
+ if (existsSync2(envPath)) {
51
+ try {
52
+ const content = readFileSync2(envPath, "utf-8");
53
+ for (const line of content.split("\n")) {
54
+ const trimmed = line.trim();
55
+ if (!trimmed || trimmed.startsWith("#")) continue;
56
+ const eqIndex = trimmed.indexOf("=");
57
+ if (eqIndex > 0) {
58
+ const key = trimmed.substring(0, eqIndex).trim();
59
+ let value = trimmed.substring(eqIndex + 1).trim();
60
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
61
+ value = value.slice(1, -1);
62
+ }
63
+ value = value.replace(/\$HOME/g, homedir2());
64
+ value = value.replace(/^~(?=\/|$)/, homedir2());
65
+ if (process.env[key] === void 0) {
66
+ process.env[key] = value;
67
+ }
68
+ }
69
+ }
70
+ break;
71
+ } catch {
72
+ }
73
+ }
74
+ }
75
+ }
76
+ loadEnvFile();
77
+ function defaultAdapterDir() {
78
+ return resolve(homedir2(), ".claude");
79
+ }
80
+ function suppressDeprecationNotices() {
81
+ if (process.env.PAI_QUIET_NOTICES === "1") return true;
82
+ const idx = process.argv.indexOf("--output-format");
83
+ return idx !== -1 && process.argv[idx + 1] === "json";
84
+ }
85
+ function warnPaiDirDeprecatedOnce(message) {
86
+ const g = globalThis;
87
+ if (g.__paiDirNoticePrinted || suppressDeprecationNotices()) return;
88
+ g.__paiDirNoticePrinted = true;
89
+ process.stderr.write(`pai: ${message}
90
+ `);
91
+ }
92
+ function resolveAdapterDir() {
93
+ if (process.env.ADAPTER_DIR) {
94
+ const adapterDir = resolve(process.env.ADAPTER_DIR);
95
+ if (process.env.PAI_DIR) {
96
+ const paiDir = resolve(process.env.PAI_DIR);
97
+ if (paiDir !== adapterDir) {
98
+ warnPaiDirDeprecatedOnce(
99
+ `ADAPTER_DIR (${adapterDir}) and PAI_DIR (${paiDir}) are both set and differ \u2014 ADAPTER_DIR wins. PAI_DIR still works for one release.`
100
+ );
101
+ }
102
+ }
103
+ return adapterDir;
104
+ }
105
+ if (process.env.PAI_DIR) {
106
+ const paiDir = resolve(process.env.PAI_DIR);
107
+ if (paiDir !== defaultAdapterDir()) {
108
+ warnPaiDirDeprecatedOnce(
109
+ "PAI_DIR is deprecated \u2014 use ADAPTER_DIR for the harness adapter root (~/.claude). PAI_DIR still works for one release."
110
+ );
111
+ }
112
+ return paiDir;
113
+ }
114
+ return defaultAdapterDir();
115
+ }
116
+ var ADAPTER_DIR = resolveAdapterDir();
117
+ var HOOKS_DIR = join2(ADAPTER_DIR, "Hooks");
118
+ var SKILLS_DIR = join2(ADAPTER_DIR, "Skills");
119
+ var AGENTS_DIR = join2(ADAPTER_DIR, "Agents");
120
+ var COMMANDS_DIR = join2(ADAPTER_DIR, "Commands");
121
+ function validatePAIStructure() {
122
+ if (!existsSync2(ADAPTER_DIR)) {
123
+ console.error(`ADAPTER_DIR does not exist: ${ADAPTER_DIR}`);
124
+ console.error(` Expected ~/.claude or set ADAPTER_DIR environment variable`);
125
+ process.exit(1);
126
+ }
127
+ if (!existsSync2(HOOKS_DIR)) {
128
+ console.error(`PAI hooks directory not found: ${HOOKS_DIR}`);
129
+ console.error(` Your ADAPTER_DIR may be misconfigured`);
130
+ console.error(` Current ADAPTER_DIR: ${ADAPTER_DIR}`);
131
+ process.exit(1);
132
+ }
133
+ }
134
+ validatePAIStructure();
135
+ function oldSecurityEventsPath() {
136
+ return join2(ADAPTER_DIR, "history", "security", "security-events.jsonl");
137
+ }
138
+ function securityEventsPath() {
139
+ return resolvePaiFile(
140
+ paiHomePath("History", "security", "security-events.jsonl"),
141
+ [oldSecurityEventsPath()],
142
+ "pai config migrate --history"
143
+ );
144
+ }
2
145
 
3
146
  // src/hooks/ts/pre-tool-use/security-validator.ts
4
- import { appendFileSync, mkdirSync, existsSync } from "fs";
5
- import { dirname } from "path";
6
147
  var REVERSE_SHELL_PATTERNS = [
7
148
  /\/dev\/(tcp|udp)\/[0-9]/,
8
149
  // Bash TCP/UDP device
@@ -67,12 +208,12 @@ function detectAttack(content) {
67
208
  return { blocked: false };
68
209
  }
69
210
  function logSecurityEvent(event) {
70
- const logPath = `${process.env.PAI_DIR || "~/.claude"}/history/security/security-events.jsonl`;
211
+ const logPath = securityEventsPath();
71
212
  const entry = JSON.stringify({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...event }) + "\n";
72
213
  try {
73
- const dir = dirname(logPath);
74
- if (!existsSync(dir)) {
75
- mkdirSync(dir, { recursive: true });
214
+ const dir = dirname2(logPath);
215
+ if (!existsSync3(dir)) {
216
+ mkdirSync2(dir, { recursive: true });
76
217
  }
77
218
  appendFileSync(logPath, entry);
78
219
  } catch {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/ts/pre-tool-use/security-validator.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * security-validator.ts - PreToolUse Security Validation Hook\n *\n * Fast pattern-based security validation for Bash commands.\n * Blocks commands matching known attack patterns before execution.\n *\n * Design Principles:\n * - Fast path: Most commands allowed with minimal processing\n * - Pre-compiled regex patterns at module load\n * - Only log/block on high-confidence attack detection\n * - Fail open on errors (don't break legitimate work)\n *\n * CUSTOMIZATION REQUIRED:\n * This template includes basic examples. Add your own security patterns\n * based on your threat model and environment.\n */\n\nimport { appendFileSync, mkdirSync, existsSync } from 'fs';\nimport { dirname } from 'path';\n\n// ============================================================================\n// ATTACK PATTERNS - CUSTOMIZE THESE FOR YOUR ENVIRONMENT\n// ============================================================================\n\n// Example: Reverse Shell Patterns (BLOCK - rarely legitimate)\nconst REVERSE_SHELL_PATTERNS: RegExp[] = [\n /\\/dev\\/(tcp|udp)\\/[0-9]/, // Bash TCP/UDP device\n /bash\\s+-i\\s+>&?\\s*\\/dev\\//, // Interactive bash redirect\n // Add your own reverse shell patterns here\n];\n\n// Example: Instruction Override (BLOCK - prompt injection)\nconst INSTRUCTION_OVERRIDE_PATTERNS: RegExp[] = [\n /ignore\\s+(all\\s+)?previous\\s+instructions?/i,\n /disregard\\s+(all\\s+)?(prior|previous)\\s+(instructions?|rules?)/i,\n // Add your own prompt injection patterns here\n];\n\n// Example: Catastrophic Deletion Patterns (BLOCK - filesystem destruction)\nconst CATASTROPHIC_DELETION_PATTERNS: RegExp[] = [\n // Trailing tilde bypass\n /\\s+~\\/?(\\s*$|\\s+)/, // Space then ~/ at end\n /\\brm\\s+(-[rfivd]+\\s+)*\\S+\\s+~\\/?/, // rm something ~/\n\n // Relative path recursive deletion\n /\\brm\\s+(-[rfivd]+\\s+)*\\.\\/\\s*$/, // rm -rf ./\n /\\brm\\s+(-[rfivd]+\\s+)*\\.\\.\\/\\s*$/, // rm -rf ../\n\n // Add your own dangerous deletion patterns here\n];\n\n// Example: Dangerous File Operations (BLOCK - data destruction)\nconst DANGEROUS_FILE_OPS_PATTERNS: RegExp[] = [\n /\\bchmod\\s+(-R\\s+)?0{3,}/, // chmod 000\n // Add your own dangerous file operation patterns here\n];\n\n// OPTIONAL: Operations that require confirmation instead of blocking\nconst DANGEROUS_GIT_PATTERNS: RegExp[] = [\n /\\bgit\\s+push\\s+.*(-f\\b|--force)/i, // git push --force\n /\\bgit\\s+reset\\s+--hard/i, // git reset --hard\n // Add your own git safety patterns here\n];\n\n// Combined patterns for fast iteration\nconst ALL_BLOCK_PATTERNS: { category: string; patterns: RegExp[] }[] = [\n { category: 'reverse_shell', patterns: REVERSE_SHELL_PATTERNS },\n { category: 'instruction_override', patterns: INSTRUCTION_OVERRIDE_PATTERNS },\n { category: 'catastrophic_deletion', patterns: CATASTROPHIC_DELETION_PATTERNS },\n { category: 'dangerous_file_ops', patterns: DANGEROUS_FILE_OPS_PATTERNS },\n];\n\nconst CONFIRM_PATTERNS: { category: string; patterns: RegExp[] }[] = [\n { category: 'dangerous_git', patterns: DANGEROUS_GIT_PATTERNS },\n];\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface HookInput {\n session_id: string;\n tool_name: string;\n tool_input: Record<string, unknown> | string;\n}\n\ninterface HookOutput {\n permissionDecision: 'allow' | 'deny';\n additionalContext?: string;\n feedback?: string;\n}\n\n// ============================================================================\n// DETECTION LOGIC\n// ============================================================================\n\ninterface DetectionResult {\n blocked: boolean;\n requiresConfirmation?: boolean;\n category?: string;\n pattern?: string;\n}\n\nfunction detectAttack(content: string): DetectionResult {\n // First check for hard blocks\n for (const { category, patterns } of ALL_BLOCK_PATTERNS) {\n for (const pattern of patterns) {\n if (pattern.test(content)) {\n return { blocked: true, category, pattern: pattern.source };\n }\n }\n }\n\n // Then check for confirmation-required patterns\n for (const { category, patterns } of CONFIRM_PATTERNS) {\n for (const pattern of patterns) {\n if (pattern.test(content)) {\n return { blocked: false, requiresConfirmation: true, category, pattern: pattern.source };\n }\n }\n }\n\n return { blocked: false };\n}\n\n// ============================================================================\n// ASYNC LOGGING (fire-and-forget on block only)\n// ============================================================================\n\nfunction logSecurityEvent(event: Record<string, unknown>): void {\n const logPath = `${process.env.PAI_DIR || '~/.claude'}/history/security/security-events.jsonl`;\n const entry = JSON.stringify({ timestamp: new Date().toISOString(), ...event }) + '\\n';\n\n try {\n const dir = dirname(logPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n appendFileSync(logPath, entry);\n } catch {\n // Silently fail - logging should never break the hook\n }\n}\n\n// ============================================================================\n// MAIN HOOK LOGIC\n// ============================================================================\n\nasync function main(): Promise<void> {\n let input: HookInput;\n\n try {\n // Read stdin\n const chunks: Buffer[] = [];\n const timeoutPromise = new Promise<Buffer[]>((_, reject) =>\n setTimeout(() => reject(new Error('timeout')), 100)\n );\n const readPromise = (async () => {\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks;\n })();\n\n let text = '';\n try {\n const result = await Promise.race([readPromise, timeoutPromise]);\n text = Buffer.concat(result).toString('utf-8');\n } catch {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n if (!text.trim()) {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n input = JSON.parse(text);\n } catch {\n // Parse error or timeout - fail open\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Only validate Bash commands\n if (input.tool_name !== 'Bash') {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Extract command string\n const command = typeof input.tool_input === 'string'\n ? input.tool_input\n : (input.tool_input?.command as string) || '';\n\n if (!command) {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Check all patterns\n const result = detectAttack(command);\n\n if (result.blocked) {\n // Log and block\n logSecurityEvent({\n type: 'attack_blocked',\n category: result.category,\n pattern: result.pattern,\n command: command.slice(0, 200), // Truncate for log\n session_id: input.session_id,\n });\n\n const output: HookOutput = {\n permissionDecision: 'deny',\n additionalContext: `SECURITY: Blocked ${result.category} pattern`,\n feedback: `This command matched a security pattern (${result.category}). If this is legitimate, please rephrase the command.`,\n };\n\n console.log(JSON.stringify(output));\n process.exit(2); // Exit 2 = blocking error\n }\n\n if (result.requiresConfirmation) {\n // Log warning and require confirmation\n logSecurityEvent({\n type: 'confirmation_required',\n category: result.category,\n pattern: result.pattern,\n command: command.slice(0, 200),\n session_id: input.session_id,\n });\n\n const output: HookOutput = {\n permissionDecision: 'deny',\n additionalContext: `DANGEROUS: ${result.category} operation requires confirmation`,\n feedback: `This is a dangerous operation (${command.slice(0, 50)}...). This can cause data loss. If you're sure, explicitly confirm this command.`,\n };\n\n console.log(JSON.stringify(output));\n process.exit(2); // Exit 2 = requires user confirmation\n }\n\n // Allow - no logging, immediate exit\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n}\n\n// ============================================================================\n// RUN\n// ============================================================================\n\nmain().catch(() => {\n // On any error, fail open\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n});\n"],
5
- "mappings": "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",
6
- "names": ["result"]
3
+ "sources": ["../../../src/hooks/ts/pre-tool-use/security-validator.ts", "../../../src/hooks/ts/lib/pai-paths.ts", "../../../src/config/pai-home.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * security-validator.ts - PreToolUse Security Validation Hook\n *\n * Fast pattern-based security validation for Bash commands.\n * Blocks commands matching known attack patterns before execution.\n *\n * Design Principles:\n * - Fast path: Most commands allowed with minimal processing\n * - Pre-compiled regex patterns at module load\n * - Only log/block on high-confidence attack detection\n * - Fail open on errors (don't break legitimate work)\n *\n * CUSTOMIZATION REQUIRED:\n * This template includes basic examples. Add your own security patterns\n * based on your threat model and environment.\n */\n\nimport { appendFileSync, mkdirSync, existsSync } from 'fs';\nimport { dirname } from 'path';\nimport { securityEventsPath } from '../lib/pai-paths.js';\n\n// ============================================================================\n// ATTACK PATTERNS - CUSTOMIZE THESE FOR YOUR ENVIRONMENT\n// ============================================================================\n\n// Example: Reverse Shell Patterns (BLOCK - rarely legitimate)\nconst REVERSE_SHELL_PATTERNS: RegExp[] = [\n /\\/dev\\/(tcp|udp)\\/[0-9]/, // Bash TCP/UDP device\n /bash\\s+-i\\s+>&?\\s*\\/dev\\//, // Interactive bash redirect\n // Add your own reverse shell patterns here\n];\n\n// Example: Instruction Override (BLOCK - prompt injection)\nconst INSTRUCTION_OVERRIDE_PATTERNS: RegExp[] = [\n /ignore\\s+(all\\s+)?previous\\s+instructions?/i,\n /disregard\\s+(all\\s+)?(prior|previous)\\s+(instructions?|rules?)/i,\n // Add your own prompt injection patterns here\n];\n\n// Example: Catastrophic Deletion Patterns (BLOCK - filesystem destruction)\nconst CATASTROPHIC_DELETION_PATTERNS: RegExp[] = [\n // Trailing tilde bypass\n /\\s+~\\/?(\\s*$|\\s+)/, // Space then ~/ at end\n /\\brm\\s+(-[rfivd]+\\s+)*\\S+\\s+~\\/?/, // rm something ~/\n\n // Relative path recursive deletion\n /\\brm\\s+(-[rfivd]+\\s+)*\\.\\/\\s*$/, // rm -rf ./\n /\\brm\\s+(-[rfivd]+\\s+)*\\.\\.\\/\\s*$/, // rm -rf ../\n\n // Add your own dangerous deletion patterns here\n];\n\n// Example: Dangerous File Operations (BLOCK - data destruction)\nconst DANGEROUS_FILE_OPS_PATTERNS: RegExp[] = [\n /\\bchmod\\s+(-R\\s+)?0{3,}/, // chmod 000\n // Add your own dangerous file operation patterns here\n];\n\n// OPTIONAL: Operations that require confirmation instead of blocking\nconst DANGEROUS_GIT_PATTERNS: RegExp[] = [\n /\\bgit\\s+push\\s+.*(-f\\b|--force)/i, // git push --force\n /\\bgit\\s+reset\\s+--hard/i, // git reset --hard\n // Add your own git safety patterns here\n];\n\n// Combined patterns for fast iteration\nconst ALL_BLOCK_PATTERNS: { category: string; patterns: RegExp[] }[] = [\n { category: 'reverse_shell', patterns: REVERSE_SHELL_PATTERNS },\n { category: 'instruction_override', patterns: INSTRUCTION_OVERRIDE_PATTERNS },\n { category: 'catastrophic_deletion', patterns: CATASTROPHIC_DELETION_PATTERNS },\n { category: 'dangerous_file_ops', patterns: DANGEROUS_FILE_OPS_PATTERNS },\n];\n\nconst CONFIRM_PATTERNS: { category: string; patterns: RegExp[] }[] = [\n { category: 'dangerous_git', patterns: DANGEROUS_GIT_PATTERNS },\n];\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface HookInput {\n session_id: string;\n tool_name: string;\n tool_input: Record<string, unknown> | string;\n}\n\ninterface HookOutput {\n permissionDecision: 'allow' | 'deny';\n additionalContext?: string;\n feedback?: string;\n}\n\n// ============================================================================\n// DETECTION LOGIC\n// ============================================================================\n\ninterface DetectionResult {\n blocked: boolean;\n requiresConfirmation?: boolean;\n category?: string;\n pattern?: string;\n}\n\nfunction detectAttack(content: string): DetectionResult {\n // First check for hard blocks\n for (const { category, patterns } of ALL_BLOCK_PATTERNS) {\n for (const pattern of patterns) {\n if (pattern.test(content)) {\n return { blocked: true, category, pattern: pattern.source };\n }\n }\n }\n\n // Then check for confirmation-required patterns\n for (const { category, patterns } of CONFIRM_PATTERNS) {\n for (const pattern of patterns) {\n if (pattern.test(content)) {\n return { blocked: false, requiresConfirmation: true, category, pattern: pattern.source };\n }\n }\n }\n\n return { blocked: false };\n}\n\n// ============================================================================\n// ASYNC LOGGING (fire-and-forget on block only)\n// ============================================================================\n\nfunction logSecurityEvent(event: Record<string, unknown>): void {\n const logPath = securityEventsPath();\n const entry = JSON.stringify({ timestamp: new Date().toISOString(), ...event }) + '\\n';\n\n try {\n const dir = dirname(logPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n appendFileSync(logPath, entry);\n } catch {\n // Silently fail - logging should never break the hook\n }\n}\n\n// ============================================================================\n// MAIN HOOK LOGIC\n// ============================================================================\n\nasync function main(): Promise<void> {\n let input: HookInput;\n\n try {\n // Read stdin\n const chunks: Buffer[] = [];\n const timeoutPromise = new Promise<Buffer[]>((_, reject) =>\n setTimeout(() => reject(new Error('timeout')), 100)\n );\n const readPromise = (async () => {\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return chunks;\n })();\n\n let text = '';\n try {\n const result = await Promise.race([readPromise, timeoutPromise]);\n text = Buffer.concat(result).toString('utf-8');\n } catch {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n if (!text.trim()) {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n input = JSON.parse(text);\n } catch {\n // Parse error or timeout - fail open\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Only validate Bash commands\n if (input.tool_name !== 'Bash') {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Extract command string\n const command = typeof input.tool_input === 'string'\n ? input.tool_input\n : (input.tool_input?.command as string) || '';\n\n if (!command) {\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n return;\n }\n\n // Check all patterns\n const result = detectAttack(command);\n\n if (result.blocked) {\n // Log and block\n logSecurityEvent({\n type: 'attack_blocked',\n category: result.category,\n pattern: result.pattern,\n command: command.slice(0, 200), // Truncate for log\n session_id: input.session_id,\n });\n\n const output: HookOutput = {\n permissionDecision: 'deny',\n additionalContext: `SECURITY: Blocked ${result.category} pattern`,\n feedback: `This command matched a security pattern (${result.category}). If this is legitimate, please rephrase the command.`,\n };\n\n console.log(JSON.stringify(output));\n process.exit(2); // Exit 2 = blocking error\n }\n\n if (result.requiresConfirmation) {\n // Log warning and require confirmation\n logSecurityEvent({\n type: 'confirmation_required',\n category: result.category,\n pattern: result.pattern,\n command: command.slice(0, 200),\n session_id: input.session_id,\n });\n\n const output: HookOutput = {\n permissionDecision: 'deny',\n additionalContext: `DANGEROUS: ${result.category} operation requires confirmation`,\n feedback: `This is a dangerous operation (${command.slice(0, 50)}...). This can cause data loss. If you're sure, explicitly confirm this command.`,\n };\n\n console.log(JSON.stringify(output));\n process.exit(2); // Exit 2 = requires user confirmation\n }\n\n // Allow - no logging, immediate exit\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n}\n\n// ============================================================================\n// RUN\n// ============================================================================\n\nmain().catch(() => {\n // On any error, fail open\n console.log(JSON.stringify({ permissionDecision: 'allow' }));\n});\n", "/**\n * PAI Path Resolution - Single Source of Truth\n *\n * This module provides consistent path resolution across all PAI hooks.\n *\n * Two different things live here, and they must not be confused:\n *\n * - ADAPTER_DIR (~/.claude by default) is the Claude Code harness adapter \u2014\n * fixed, hardcoded paths the harness itself loads from (Hooks/, Skills/,\n * Agents/, Commands/, settings.json, statusline-command.sh,\n * tab-color-command.sh). It is harness-specific: a future harness would\n * need its own adapter directory with its own conventions.\n * - PAI_HOME (~/.claude/pai by default \u2014 see ../../../config/pai-home.ts) is\n * where PAI's own state lives: everything hooks WRITE (History/,\n * agent-sessions.json, session-routing.json, ...) resolves there, with a\n * fallback to the pre-2026-09-19 ADAPTER_DIR location and a one-time\n * stderr notice, exactly like every other PAI_HOME file.\n *\n * ALSO loads .env file from ADAPTER_DIR so all hooks get environment\n * variables without relying on Claude Code's settings.json injection.\n *\n * Usage in hooks:\n * import { ADAPTER_DIR, HOOKS_DIR, SKILLS_DIR, historyDir } from './lib/pai-paths';\n */\n\nimport { homedir } from 'os';\nimport { resolve, join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\nimport {\n paiHomePath,\n resolvePaiFile,\n migratePaiFile,\n migratePaiDir,\n type MigrateFileResult,\n type MigrateDirResult,\n} from '../../../config/pai-home.js';\n\n/**\n * Load .env file and inject into process.env\n * Must run BEFORE ADAPTER_DIR resolution so .env can set ADAPTER_DIR/PAI_DIR if needed\n */\nfunction loadEnvFile(): void {\n // Check common locations for .env\n const possiblePaths = [\n resolve(process.env.ADAPTER_DIR || process.env.PAI_DIR || '', '.env'),\n resolve(homedir(), '.claude', '.env'),\n ];\n\n for (const envPath of possiblePaths) {\n if (existsSync(envPath)) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n // Skip comments and empty lines\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex > 0) {\n const key = trimmed.substring(0, eqIndex).trim();\n let value = trimmed.substring(eqIndex + 1).trim();\n\n // Remove surrounding quotes if present\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n\n // Expand $HOME and ~ in values\n value = value.replace(/\\$HOME/g, homedir());\n value = value.replace(/^~(?=\\/|$)/, homedir());\n\n // Only set if not already defined (env vars take precedence)\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\n }\n // Found and loaded, don't check other paths\n break;\n } catch {\n // Silently continue if .env can't be read\n }\n }\n }\n}\n\n// Load .env FIRST, before any other initialization\nloadEnvFile();\n\nfunction defaultAdapterDir(): string {\n return resolve(homedir(), '.claude');\n}\n\n/**\n * Never print when stdout/stderr is a data channel, not a human: hook\n * bundles and worker-status-line.mjs set PAI_QUIET_NOTICES=1 via their\n * esbuild banner (scripts/build-hooks.mjs), and a `pai worker run\n * --output-format json` invocation is detected directly off argv since its\n * env can't be set before this module's static imports resolve.\n */\nfunction suppressDeprecationNotices(): boolean {\n if (process.env.PAI_QUIET_NOTICES === '1') return true;\n const idx = process.argv.indexOf('--output-format');\n return idx !== -1 && process.argv[idx + 1] === 'json';\n}\n\n/**\n * At most once per process \u2014 a `globalThis` flag rather than a module-level\n * `let` because hooks and the CLI can end up with more than one instance of\n * this module loaded into the same process (separate bundles), each with its\n * own module scope; only a property on the shared global survives that.\n */\nfunction warnPaiDirDeprecatedOnce(message: string): void {\n const g = globalThis as typeof globalThis & { __paiDirNoticePrinted?: boolean };\n if (g.__paiDirNoticePrinted || suppressDeprecationNotices()) return;\n g.__paiDirNoticePrinted = true;\n process.stderr.write(`pai: ${message}\\n`);\n}\n\n/**\n * Smart ADAPTER_DIR detection with fallback\n * Priority:\n * 1. ADAPTER_DIR environment variable (if set) \u2014 warns once if PAI_DIR is\n * ALSO set and resolves to a different path (naming both).\n * 2. PAI_DIR environment variable (deprecated alias, one release) \u2014 warns\n * once only when it differs from the default adapter root; PAI_DIR set to\n * the same value as the default is the operator's current, correct\n * setting and stays silent.\n * 3. ~/.claude (standard location)\n */\nfunction resolveAdapterDir(): string {\n if (process.env.ADAPTER_DIR) {\n const adapterDir = resolve(process.env.ADAPTER_DIR);\n if (process.env.PAI_DIR) {\n const paiDir = resolve(process.env.PAI_DIR);\n if (paiDir !== adapterDir) {\n warnPaiDirDeprecatedOnce(\n `ADAPTER_DIR (${adapterDir}) and PAI_DIR (${paiDir}) are both set and differ \u2014 ADAPTER_DIR wins. PAI_DIR still works for one release.`\n );\n }\n }\n return adapterDir;\n }\n if (process.env.PAI_DIR) {\n const paiDir = resolve(process.env.PAI_DIR);\n if (paiDir !== defaultAdapterDir()) {\n warnPaiDirDeprecatedOnce(\n 'PAI_DIR is deprecated \u2014 use ADAPTER_DIR for the harness adapter root (~/.claude). PAI_DIR still works for one release.'\n );\n }\n return paiDir;\n }\n return defaultAdapterDir();\n}\n\n/** The Claude Code harness adapter root \u2014 NOT PAI's state home. See module doc above. */\nexport const ADAPTER_DIR = resolveAdapterDir();\n\n/** @deprecated alias for ADAPTER_DIR, kept for one release. Prefer ADAPTER_DIR. */\nexport const PAI_DIR = ADAPTER_DIR;\n\n/**\n * Adapter directories \u2014 fixed paths the Claude Code harness itself loads\n * from. These stay under ADAPTER_DIR; they are not PAI state.\n */\nexport const HOOKS_DIR = join(ADAPTER_DIR, 'Hooks');\nexport const SKILLS_DIR = join(ADAPTER_DIR, 'Skills');\nexport const AGENTS_DIR = join(ADAPTER_DIR, 'Agents');\nexport const COMMANDS_DIR = join(ADAPTER_DIR, 'Commands');\n\n/**\n * Validate PAI directory structure on first import\n * This fails fast with a clear error if PAI is misconfigured\n */\nfunction validatePAIStructure(): void {\n if (!existsSync(ADAPTER_DIR)) {\n console.error(`ADAPTER_DIR does not exist: ${ADAPTER_DIR}`);\n console.error(` Expected ~/.claude or set ADAPTER_DIR environment variable`);\n process.exit(1);\n }\n\n if (!existsSync(HOOKS_DIR)) {\n console.error(`PAI hooks directory not found: ${HOOKS_DIR}`);\n console.error(` Your ADAPTER_DIR may be misconfigured`);\n console.error(` Current ADAPTER_DIR: ${ADAPTER_DIR}`);\n process.exit(1);\n }\n}\n\n// Run validation on module import\n// This ensures any hook that imports this module will fail fast if paths are wrong\nvalidatePAIStructure();\n\n// ---------------------------------------------------------------------------\n// PAI state written by hooks \u2014 resolves under PAI_HOME, falling back to the\n// pre-2026-09-19 ADAPTER_DIR location (one-time stderr notice) until\n// `pai config migrate --history` moves it. Actively written on every hook\n// event across every session, so unlike most PAI_HOME files the live move is\n// deliberately NOT automatic \u2014 see `pai config migrate --history`.\n// ---------------------------------------------------------------------------\n\nfunction oldHistoryDir(): string {\n return join(ADAPTER_DIR, 'History');\n}\n\n/** Read/write location for hook-captured history: PAI_HOME/History if\n * present, else the old ADAPTER_DIR/History (one-time stderr notice). */\nexport function historyDir(): string {\n return resolvePaiFile(paiHomePath('History'), [oldHistoryDir()], 'pai config migrate --history');\n}\n\nexport function migrateHistoryDir(opts: { dryRun?: boolean } = {}): MigrateDirResult {\n return migratePaiDir(paiHomePath('History'), [oldHistoryDir()], opts);\n}\n\nfunction oldAgentSessionsPath(): string {\n return join(ADAPTER_DIR, 'agent-sessions.json');\n}\n\nexport function agentSessionsPath(): string {\n return resolvePaiFile(paiHomePath('agent-sessions.json'), [oldAgentSessionsPath()], 'pai config migrate --history');\n}\n\nexport function migrateAgentSessions(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath('agent-sessions.json'), [oldAgentSessionsPath()], opts);\n}\n\nfunction oldSecurityEventsPath(): string {\n return join(ADAPTER_DIR, 'history', 'security', 'security-events.jsonl');\n}\n\nexport function securityEventsPath(): string {\n return resolvePaiFile(\n paiHomePath('History', 'security', 'security-events.jsonl'),\n [oldSecurityEventsPath()],\n 'pai config migrate --history'\n );\n}\n\nexport function migrateSecurityEvents(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath('History', 'security', 'security-events.jsonl'), [oldSecurityEventsPath()], opts);\n}\n\nfunction oldSessionRoutingPath(): string {\n return join(ADAPTER_DIR, 'session-routing.json');\n}\n\nexport function sessionRoutingPath(): string {\n return resolvePaiFile(paiHomePath('session-routing.json'), [oldSessionRoutingPath()], 'pai config migrate --history');\n}\n\nexport function migrateSessionRouting(opts: { dryRun?: boolean } = {}): MigrateFileResult {\n return migratePaiFile(paiHomePath('session-routing.json'), [oldSessionRoutingPath()], opts);\n}\n\n/**\n * Helper to get history file path with date-based organization\n */\nexport function getHistoryFilePath(subdir: string, filename: string): string {\n const now = new Date();\n const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone;\n const localDate = new Date(now.toLocaleString('en-US', { timeZone: tz }));\n const year = localDate.getFullYear();\n const month = String(localDate.getMonth() + 1).padStart(2, '0');\n\n return join(historyDir(), subdir, `${year}-${month}`, filename);\n}\n", "/**\n * pai-home.ts \u2014 the PAI_HOME namespace directory and generic per-user file\n * migration into it.\n *\n * Every PAI-owned per-user file (workers.yaml, config.json, whisper-rules.md,\n * advisor-mode.json, ...) lives under PAI_HOME (~/.claude/pai by default) so\n * nothing PAI writes can ever collide with a file Claude Code itself\n * introduces under ~/.claude. Decided 2026-09-19 \u2014 see docs/workers-config.md.\n */\n\nimport { existsSync, mkdirSync, readFileSync, copyFileSync, renameSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, dirname } from \"node:path\";\nimport { execFileSync } from \"node:child_process\";\n\n/**\n * Byte-identical check that works for files of any size. `cmp` compares by\n * streaming rather than loading either file into memory, unlike a\n * readFileSync + Buffer.compare \u2014 which throws (\"File size is greater than\n * 2 GiB\") on anything past Node's 2GiB single-read ceiling, a real limit hit\n * migrating a multi-gigabyte federation.db.\n */\nfunction filesByteIdentical(a: string, b: string): boolean {\n try {\n execFileSync(\"cmp\", [\"-s\", a, b], { stdio: \"pipe\" });\n return true;\n } catch (e) {\n if (e && typeof e === \"object\" && \"status\" in e && (e as { status: number }).status === 1) return false;\n // cmp missing or errored for an unrelated reason \u2014 fall back to an\n // in-memory compare (fine for the small files this path normally sees).\n return Buffer.compare(readFileSync(a), readFileSync(b)) === 0;\n }\n}\n\n/** PAI_HOME (test isolation, power users) overrides the default namespace dir. */\nexport function paiHomeDir(): string {\n return process.env.PAI_HOME || join(homedir(), \".claude\", \"pai\");\n}\n\n/** A path under PAI_HOME, e.g. `paiHomePath(\"config.json\")`. */\nexport function paiHomePath(...segments: string[]): string {\n return join(paiHomeDir(), ...segments);\n}\n\nconst noticesPrinted = new Set<string>();\n\n/**\n * Resolve a per-user PAI file: the new PAI_HOME path if it exists, else the\n * first existing entry in `oldCandidates` (checked in order \u2014 most recent\n * old location first), else the new path (the target a first write\n * creates). Prints one stderr notice per process per new-path when a\n * fallback location is actually used.\n */\nexport function resolvePaiFile(newPath: string, oldCandidates: string[], migrateHint: string): string {\n if (existsSync(newPath)) return newPath;\n for (const old of oldCandidates) {\n if (existsSync(old)) {\n if (!noticesPrinted.has(newPath)) {\n noticesPrinted.add(newPath);\n process.stderr.write(\n `pai: ${old} is at an old location \u2014 run \\`${migrateHint}\\` to move it to ${newPath}\\n`\n );\n }\n return old;\n }\n }\n return newPath;\n}\n\nexport class PaiFileMigrationError extends Error {}\n\nexport interface MigrateFileResult {\n fromPath: string | null;\n toPath: string;\n dryRun: boolean;\n note?: string;\n}\n\n/**\n * Move a per-user PAI file to its new PAI_HOME location: copy, verify\n * byte-identical, then rename the source to `<name>.migrated-<YYYYMMDD>`\n * (never deleted). Idempotent: if the new path already holds bytes\n * identical to the found source, the source is just renamed aside; if it\n * differs, this refuses rather than overwrite silently.\n */\nexport function migratePaiFile(\n newPath: string,\n oldCandidates: string[],\n opts: { dryRun?: boolean } = {}\n): MigrateFileResult {\n const fromPath = oldCandidates.find((p) => existsSync(p)) ?? null;\n if (!fromPath) {\n const note = existsSync(newPath) ? \"already at new location\" : \"nothing to migrate \u2014 file does not exist yet\";\n return { fromPath: null, toPath: newPath, dryRun: !!opts.dryRun, note };\n }\n if (opts.dryRun) return { fromPath, toPath: newPath, dryRun: true };\n\n const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n\n if (existsSync(newPath)) {\n if (filesByteIdentical(fromPath, newPath)) {\n renameSync(fromPath, `${fromPath}.migrated-${stamp}`);\n return { fromPath, toPath: newPath, dryRun: false, note: \"identical \u2014 old file renamed aside\" };\n }\n throw new PaiFileMigrationError(\n `${newPath} already exists and differs from ${fromPath} \u2014 resolve manually, nothing changed`\n );\n }\n\n const dir = dirname(newPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n copyFileSync(fromPath, newPath);\n\n if (!filesByteIdentical(fromPath, newPath)) {\n throw new PaiFileMigrationError(\n `${newPath}: copy did not match ${fromPath} byte-for-byte \u2014 aborting, old file left in place`\n );\n }\n\n renameSync(fromPath, `${fromPath}.migrated-${stamp}`);\n return { fromPath, toPath: newPath, dryRun: false };\n}\n\nexport interface MigrateDirResult {\n fromDir: string | null;\n toDir: string;\n dryRun: boolean;\n movedCount?: number;\n note?: string;\n}\n\n/**\n * Move a per-user PAI directory (queries/, session-state/, ...) into\n * PAI_HOME: move every entry from the first found old candidate into the\n * new dir (never overwriting an existing entry there), then rename the now-\n * empty old dir aside to `<name>.migrated-<YYYYMMDD>` (never deleted).\n * Idempotent \u2014 an already-migrated dir has nothing left to find.\n */\nexport function migratePaiDir(\n newDir: string,\n oldCandidates: string[],\n opts: { dryRun?: boolean } = {}\n): MigrateDirResult {\n const fromDir = oldCandidates.find((p) => existsSync(p) && statSync(p).isDirectory()) ?? null;\n if (!fromDir) {\n const note = existsSync(newDir) ? \"already at new location\" : \"nothing to migrate \u2014 directory does not exist yet\";\n return { fromDir: null, toDir: newDir, dryRun: !!opts.dryRun, note };\n }\n\n const entries = readdirSync(fromDir);\n if (opts.dryRun) return { fromDir, toDir: newDir, dryRun: true, movedCount: entries.length };\n\n if (!existsSync(newDir)) mkdirSync(newDir, { recursive: true });\n\n let moved = 0;\n const collided: string[] = [];\n for (const entry of entries) {\n const src = join(fromDir, entry);\n const dst = join(newDir, entry);\n if (existsSync(dst)) {\n collided.push(entry);\n continue;\n }\n renameSync(src, dst);\n moved++;\n }\n\n const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, \"\");\n const remaining = readdirSync(fromDir);\n const note = collided.length\n ? `${collided.length} entrie(s) left in ${fromDir} \u2014 name already existed in ${newDir}`\n : undefined;\n\n if (remaining.length === 0) {\n renameSync(fromDir, `${fromDir}.migrated-${stamp}`);\n }\n\n return { fromDir, toDir: newDir, dryRun: false, movedCount: moved, note };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["mkdirSync", "existsSync", "dirname", "homedir", "join", "existsSync", "readFileSync", "homedir", "existsSync", "readFileSync", "join", "existsSync", "join", "dirname", "existsSync", "mkdirSync", "result"]
7
7
  }
@@ -1,6 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire as __paiCreateRequire } from 'node:module';
3
+ const require = __paiCreateRequire(import.meta.url);
4
+ if (!process.env.PAI_QUIET_NOTICES) process.env.PAI_QUIET_NOTICES = '1';
5
+
6
+ // src/hooks/ts/lib/worker-session.ts
7
+ function isWorkerSession(env = process.env) {
8
+ return env.PAI_WORKER === "1";
9
+ }
2
10
 
3
11
  // src/hooks/ts/session-start/session-commands.ts
12
+ if (isWorkerSession()) process.exit(0);
4
13
  var commands = `<system-reminder>
5
14
  SESSION COMMANDS (always available):
6
15
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/hooks/ts/session-start/session-commands.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * session-commands.ts\n *\n * Tiny SessionStart hook that injects critical session commands inline.\n * Stays under 2KB to avoid persisted-output truncation.\n *\n * This runs BEFORE load-core-context so these commands are always available\n * even when the full CORE skill gets truncated.\n */\n\nconst commands = `<system-reminder>\nSESSION COMMANDS (always available):\n\n- \"go\" / \"continue\" / \"weiter\" \u2192 Read Notes/TODO.md, find ## Continue section, resume work\n- \"pause session\" \u2192 Save state to TODO.md, update session note, stop (no compact)\n- \"end session\" \u2192 Finalize note, commit if needed, start fresh next time\n- \"cpp\" \u2192 Release workflow: version bump \u2192 build \u2192 npm publish \u2192 git add ALL \u2192 ONE commit \u2192 git push\n\nWhen user says \"go\" with no other context, ALWAYS check TODO.md first.\n\nIMPORTANT: Full PAI context may be truncated due to size. For complete instructions (response format, workflows, principles), READ: ~/.claude/Skills/CORE/SKILL.md\n</system-reminder>`;\n\nconsole.log(commands);\n"],
5
- "mappings": ";;;AAYA,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAajB,QAAQ,IAAI,QAAQ;",
3
+ "sources": ["../../../src/hooks/ts/lib/worker-session.ts", "../../../src/hooks/ts/session-start/session-commands.ts"],
4
+ "sourcesContent": ["/**\n * Worker-session detection.\n *\n * A disposable headless worker (a `claude -p` run started by an orchestrating\n * session, possibly against a different model provider with a different\n * context window) shares the project directory and the hook configuration\n * with the real session that spawned it. Left alone, the hooks treat it as a\n * session in its own right: they inject project context into it, create and\n * rename a numbered session note for it, autosave it, and enqueue a\n * model-written handover for it. Its compactions also land in the project's\n * transcript folder, where they are indistinguishable from a real session's\n * and drag the measured compaction trigger down (observed 2026-09-17: two\n * workers compacting at ~151k tokens pulled a project's trigger from ~784k\n * to ~151k, and the real session's handover fired at ~50k tokens).\n *\n * The launcher marks such sessions with `PAI_WORKER=1`. Every hook that does\n * per-session bookkeeping returns immediately when this predicate is true.\n * Deliberately NOT guarded: the security validator (a worker's shell\n * commands must still be checked) and observability capture.\n */\nexport function isWorkerSession(env: NodeJS.ProcessEnv = process.env): boolean {\n return env.PAI_WORKER === \"1\";\n}\n", "#!/usr/bin/env node\n\n/**\n * session-commands.ts\n *\n * Tiny SessionStart hook that injects critical session commands inline.\n * Stays under 2KB to avoid persisted-output truncation.\n *\n * This runs BEFORE load-core-context so these commands are always available\n * even when the full CORE skill gets truncated.\n */\n\nimport { isWorkerSession } from \"../lib/worker-session.js\";\n\nif (isWorkerSession()) process.exit(0); // disposable worker: no per-session bookkeeping\n\nconst commands = `<system-reminder>\nSESSION COMMANDS (always available):\n\n- \"go\" / \"continue\" / \"weiter\" \u2192 Read Notes/TODO.md, find ## Continue section, resume work\n- \"pause session\" \u2192 Save state to TODO.md, update session note, stop (no compact)\n- \"end session\" \u2192 Finalize note, commit if needed, start fresh next time\n- \"cpp\" \u2192 Release workflow: version bump \u2192 build \u2192 npm publish \u2192 git add ALL \u2192 ONE commit \u2192 git push\n\nWhen user says \"go\" with no other context, ALWAYS check TODO.md first.\n\nIMPORTANT: Full PAI context may be truncated due to size. For complete instructions (response format, workflows, principles), READ: ~/.claude/Skills/CORE/SKILL.md\n</system-reminder>`;\n\nconsole.log(commands);\n"],
5
+ "mappings": ";;;;;;AAoBO,SAAS,gBAAgB,MAAyB,QAAQ,KAAc;AAC7E,SAAO,IAAI,eAAe;AAC5B;;;ACRA,IAAI,gBAAgB,EAAG,SAAQ,KAAK,CAAC;AAErC,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAajB,QAAQ,IAAI,QAAQ;",
6
6
  "names": []
7
7
  }