@tekmidian/pai 0.15.0 → 0.17.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 (251) hide show
  1. package/README.md +11 -3
  2. package/dist/{auto-route-CLYcToZ5.mjs → auto-route-CdvjEWs5.mjs} +4 -4
  3. package/dist/{auto-route-CLYcToZ5.mjs.map → auto-route-CdvjEWs5.mjs.map} +1 -1
  4. package/dist/auto-route-DkIRampF.mjs +86 -0
  5. package/dist/auto-route-DkIRampF.mjs.map +1 -0
  6. package/dist/{kg-extraction-C8DEUHTS.mjs → checkpoint-block-Bl0HD3U2.mjs} +442 -13
  7. package/dist/checkpoint-block-Bl0HD3U2.mjs.map +1 -0
  8. package/dist/checkpoint-block-C05sK_HK.mjs +1056 -0
  9. package/dist/checkpoint-block-C05sK_HK.mjs.map +1 -0
  10. package/dist/checkpoint-block-CUCG10Uh.mjs +1022 -0
  11. package/dist/checkpoint-block-CUCG10Uh.mjs.map +1 -0
  12. package/dist/checkpoint-block-D5IyLLTr.mjs +1062 -0
  13. package/dist/checkpoint-block-D5IyLLTr.mjs.map +1 -0
  14. package/dist/cli/index.mjs +14 -12
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/cli/program.d.mts.map +1 -1
  17. package/dist/cli/program.mjs +31 -16
  18. package/dist/cli/program.mjs.map +1 -1
  19. package/dist/{clusters-BYPw7vfW.mjs → clusters-CKWDwcMX.mjs} +2 -2
  20. package/dist/{clusters-BYPw7vfW.mjs.map → clusters-CKWDwcMX.mjs.map} +1 -1
  21. package/dist/{config-C8m-tPhP.mjs → config-DGOfoGfm.mjs} +1 -1
  22. package/dist/{config-C8m-tPhP.mjs.map → config-DGOfoGfm.mjs.map} +1 -1
  23. package/dist/daemon/index.mjs +18 -16
  24. package/dist/daemon/index.mjs.map +1 -1
  25. package/dist/{daemon-COSvHTNu.mjs → daemon-1rKbN-fd.mjs} +31 -30
  26. package/dist/{daemon-COSvHTNu.mjs.map → daemon-1rKbN-fd.mjs.map} +1 -1
  27. package/dist/daemon-9AL6Pwh-.mjs +1581 -0
  28. package/dist/daemon-9AL6Pwh-.mjs.map +1 -0
  29. package/dist/daemon-Bbk-_rV6.mjs +1581 -0
  30. package/dist/daemon-Bbk-_rV6.mjs.map +1 -0
  31. package/dist/daemon-BvcaMk4g.mjs +1581 -0
  32. package/dist/daemon-BvcaMk4g.mjs.map +1 -0
  33. package/dist/daemon-CTDEfwcq.mjs +1576 -0
  34. package/dist/daemon-CTDEfwcq.mjs.map +1 -0
  35. package/dist/daemon-ClbfaF60.mjs +1576 -0
  36. package/dist/daemon-ClbfaF60.mjs.map +1 -0
  37. package/dist/daemon-DDQNDgYF.mjs +1576 -0
  38. package/dist/daemon-DDQNDgYF.mjs.map +1 -0
  39. package/dist/daemon-DU8yDWlQ.mjs +1576 -0
  40. package/dist/daemon-DU8yDWlQ.mjs.map +1 -0
  41. package/dist/daemon-NJr4iSW3.mjs +1576 -0
  42. package/dist/daemon-NJr4iSW3.mjs.map +1 -0
  43. package/dist/daemon-mcp/index.mjs +2 -2
  44. package/dist/db-BtuN768f.mjs +206 -0
  45. package/dist/db-BtuN768f.mjs.map +1 -0
  46. package/dist/{db-CmYbAVCD.mjs → db-CYmBWcjh.mjs} +1 -1
  47. package/dist/{db-CmYbAVCD.mjs.map → db-CYmBWcjh.mjs.map} +1 -1
  48. package/dist/{detect-KjycLtXM.mjs → detect-Bf2z-oKB.mjs} +1 -1
  49. package/dist/{detect-KjycLtXM.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
  50. package/dist/{detector-ZiOhHszd.mjs → detector-Bwzm3E_x.mjs} +2 -2
  51. package/dist/{detector-ZiOhHszd.mjs.map → detector-Bwzm3E_x.mjs.map} +1 -1
  52. package/dist/detector-rq9JGTmS.mjs +74 -0
  53. package/dist/detector-rq9JGTmS.mjs.map +1 -0
  54. package/dist/embeddings-Bn86ssxR.mjs +119 -0
  55. package/dist/embeddings-Bn86ssxR.mjs.map +1 -0
  56. package/dist/{embeddings-BJPOcbik.mjs → embeddings-DGRAPAYb.mjs} +1 -1
  57. package/dist/{embeddings-BJPOcbik.mjs.map → embeddings-DGRAPAYb.mjs.map} +1 -1
  58. package/dist/{factory-Q88X1bAN.mjs → factory-0-57Ac-7.mjs} +8 -5
  59. package/dist/{factory-Q88X1bAN.mjs.map → factory-0-57Ac-7.mjs.map} +1 -1
  60. package/dist/factory-HcQsQqh0.mjs +72 -0
  61. package/dist/factory-HcQsQqh0.mjs.map +1 -0
  62. package/dist/factory-PDXQdkLO.mjs +72 -0
  63. package/dist/factory-PDXQdkLO.mjs.map +1 -0
  64. package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
  65. package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
  66. package/dist/hooks/capture-all-events.mjs.map +1 -1
  67. package/dist/hooks/capture-session-summary.mjs.map +1 -1
  68. package/dist/hooks/capture-tool-output.mjs.map +1 -1
  69. package/dist/hooks/cleanup-session-files.mjs.map +1 -1
  70. package/dist/hooks/context-compression-hook.mjs +336 -48
  71. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  72. package/dist/hooks/initialize-session.mjs.map +1 -1
  73. package/dist/hooks/inject-observations.mjs.map +1 -1
  74. package/dist/hooks/load-core-context.mjs.map +1 -1
  75. package/dist/hooks/load-project-context.mjs +188 -6
  76. package/dist/hooks/load-project-context.mjs.map +4 -4
  77. package/dist/hooks/observe.mjs.map +1 -1
  78. package/dist/hooks/post-compact-inject.mjs.map +1 -1
  79. package/dist/hooks/security-validator.mjs.map +1 -1
  80. package/dist/hooks/session-commands.mjs.map +1 -1
  81. package/dist/hooks/stop-hook.mjs +331 -43
  82. package/dist/hooks/stop-hook.mjs.map +4 -4
  83. package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
  84. package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
  85. package/dist/hooks/update-tab-on-action.mjs.map +1 -1
  86. package/dist/hooks/update-tab-titles.mjs.map +1 -1
  87. package/dist/hooks/whisper-rules.mjs.map +1 -1
  88. package/dist/index.d.mts +0 -9
  89. package/dist/index.d.mts.map +1 -1
  90. package/dist/index.mjs +10 -9
  91. package/dist/indexer-AEcT8wHf.mjs +1 -0
  92. package/dist/{indexer-backend-Cm5RS7y0.mjs → indexer-backend-BQZXoarl.mjs} +3 -3
  93. package/dist/{indexer-backend-Cm5RS7y0.mjs.map → indexer-backend-BQZXoarl.mjs.map} +1 -1
  94. package/dist/indexer-backend-BmaS2VcD.mjs +299 -0
  95. package/dist/indexer-backend-BmaS2VcD.mjs.map +1 -0
  96. package/dist/indexer-backend-COrJ5rjS.mjs +299 -0
  97. package/dist/indexer-backend-COrJ5rjS.mjs.map +1 -0
  98. package/dist/indexer-backend-NwD7TBGh.mjs +301 -0
  99. package/dist/indexer-backend-NwD7TBGh.mjs.map +1 -0
  100. package/dist/{ipc-client-CoyUHPod.mjs → ipc-client-vO2GV325.mjs} +1 -1
  101. package/dist/{ipc-client-CoyUHPod.mjs.map → ipc-client-vO2GV325.mjs.map} +1 -1
  102. package/dist/{kg-entity-LXblD7LZ.mjs → kg-entity-DbOMPdF9.mjs} +1 -1
  103. package/dist/{kg-entity-LXblD7LZ.mjs.map → kg-entity-DbOMPdF9.mjs.map} +1 -1
  104. package/dist/{latent-ideas-RyC6eyqI.mjs → latent-ideas-BwM3WdBp.mjs} +4 -4
  105. package/dist/{latent-ideas-RyC6eyqI.mjs.map → latent-ideas-BwM3WdBp.mjs.map} +1 -1
  106. package/dist/latent-ideas-C6IFFT0-.mjs +191 -0
  107. package/dist/latent-ideas-C6IFFT0-.mjs.map +1 -0
  108. package/dist/{main-resolver-uFxNiDy7.mjs → main-resolver-DimnS2qP.mjs} +22 -22
  109. package/dist/{main-resolver-uFxNiDy7.mjs.map → main-resolver-DimnS2qP.mjs.map} +1 -1
  110. package/dist/{migrate-B02wDDgS.mjs → migrate-fLD6rAdO.mjs} +2 -2
  111. package/dist/{migrate-B02wDDgS.mjs.map → migrate-fLD6rAdO.mjs.map} +1 -1
  112. package/dist/{neighborhood-CklGIB8r.mjs → neighborhood-CzRNG2Oy.mjs} +2 -2
  113. package/dist/{neighborhood-CklGIB8r.mjs.map → neighborhood-CzRNG2Oy.mjs.map} +1 -1
  114. package/dist/neighborhood-DZY0wkpE.mjs +135 -0
  115. package/dist/neighborhood-DZY0wkpE.mjs.map +1 -0
  116. package/dist/{note-context-qvZlVXVO.mjs → note-context-CrfMbr4R.mjs} +1 -1
  117. package/dist/{note-context-qvZlVXVO.mjs.map → note-context-CrfMbr4R.mjs.map} +1 -1
  118. package/dist/{pai-marker-HVBwwBW-.mjs → pai-marker-B20KqhA8.mjs} +44 -2
  119. package/dist/pai-marker-B20KqhA8.mjs.map +1 -0
  120. package/dist/pick-B0q5cy79.mjs +11785 -0
  121. package/dist/pick-B0q5cy79.mjs.map +1 -0
  122. package/dist/{pick-r95zvydr.mjs → pick-B3zEYjin.mjs} +2288 -497
  123. package/dist/pick-B3zEYjin.mjs.map +1 -0
  124. package/dist/pick-BHE9_iA0.mjs +11947 -0
  125. package/dist/pick-BHE9_iA0.mjs.map +1 -0
  126. package/dist/pick-BaTjoNqM.mjs +11544 -0
  127. package/dist/pick-BaTjoNqM.mjs.map +1 -0
  128. package/dist/pick-C6JbZfN4.mjs +11893 -0
  129. package/dist/pick-C6JbZfN4.mjs.map +1 -0
  130. package/dist/pick-CK-bJyjp.mjs +11883 -0
  131. package/dist/pick-CK-bJyjp.mjs.map +1 -0
  132. package/dist/pick-CLBkGHyI.mjs +11826 -0
  133. package/dist/pick-CLBkGHyI.mjs.map +1 -0
  134. package/dist/pick-CTOif21h.mjs +11544 -0
  135. package/dist/pick-CTOif21h.mjs.map +1 -0
  136. package/dist/pick-Ck2cdQgD.mjs +11893 -0
  137. package/dist/pick-Ck2cdQgD.mjs.map +1 -0
  138. package/dist/pick-Cr4nJzHA.mjs +11914 -0
  139. package/dist/pick-Cr4nJzHA.mjs.map +1 -0
  140. package/dist/pick-D4dFfKAu.mjs +11893 -0
  141. package/dist/pick-D4dFfKAu.mjs.map +1 -0
  142. package/dist/pick-D7-lYiGX.mjs +11543 -0
  143. package/dist/pick-D7-lYiGX.mjs.map +1 -0
  144. package/dist/pick-DMjCY0np.mjs +11875 -0
  145. package/dist/pick-DMjCY0np.mjs.map +1 -0
  146. package/dist/pick-DbX4OrCP.mjs +11543 -0
  147. package/dist/pick-DbX4OrCP.mjs.map +1 -0
  148. package/dist/pick-iCiPjlnC.mjs +11893 -0
  149. package/dist/pick-iCiPjlnC.mjs.map +1 -0
  150. package/dist/pick-sF9GnQUS.mjs +11893 -0
  151. package/dist/pick-sF9GnQUS.mjs.map +1 -0
  152. package/dist/pick-yHKUrnfG.mjs +11793 -0
  153. package/dist/pick-yHKUrnfG.mjs.map +1 -0
  154. package/dist/postgres-BJxiqtqg.mjs +891 -0
  155. package/dist/postgres-BJxiqtqg.mjs.map +1 -0
  156. package/dist/{postgres-B451Hnkm.mjs → postgres-zjKqPl4X.mjs} +2 -2
  157. package/dist/{postgres-B451Hnkm.mjs.map → postgres-zjKqPl4X.mjs.map} +1 -1
  158. package/dist/{query-feedback-fapbqRgh.mjs → query-feedback-BoY8_Dbb.mjs} +1 -1
  159. package/dist/{query-feedback-fapbqRgh.mjs.map → query-feedback-BoY8_Dbb.mjs.map} +1 -1
  160. package/dist/{reranker-C08R99zn.mjs → reranker-CMNZcfVx.mjs} +1 -1
  161. package/dist/{reranker-C08R99zn.mjs.map → reranker-CMNZcfVx.mjs.map} +1 -1
  162. package/dist/{search-HcdKtMla.mjs → search-CpTv1I24.mjs} +3 -3
  163. package/dist/{search-HcdKtMla.mjs.map → search-CpTv1I24.mjs.map} +1 -1
  164. package/dist/search-i2nlQ-JM.mjs +298 -0
  165. package/dist/search-i2nlQ-JM.mjs.map +1 -0
  166. package/dist/skills/End/SKILL.md +78 -0
  167. package/dist/skills/Pause/SKILL.md +84 -0
  168. package/dist/sqlite-CZ0LFili.mjs +271 -0
  169. package/dist/sqlite-CZ0LFili.mjs.map +1 -0
  170. package/dist/{sqlite-DQpY1Esi.mjs → sqlite-ChSCYwiC.mjs} +3 -3
  171. package/dist/{sqlite-DQpY1Esi.mjs.map → sqlite-ChSCYwiC.mjs.map} +1 -1
  172. package/dist/sqlite-Nw8ZW5Rj.mjs +271 -0
  173. package/dist/sqlite-Nw8ZW5Rj.mjs.map +1 -0
  174. package/dist/{state-BIlxNRUn.mjs → state-BXIdbxDs.mjs} +1 -1
  175. package/dist/{state-BIlxNRUn.mjs.map → state-BXIdbxDs.mjs.map} +1 -1
  176. package/dist/{stop-words-BwplsQ3z.mjs → stop-words-BaMEGVeY.mjs} +1 -1
  177. package/dist/{stop-words-BwplsQ3z.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
  178. package/dist/{sync-uPR4g438.mjs → sync--BoxBBok.mjs} +6 -205
  179. package/dist/sync--BoxBBok.mjs.map +1 -0
  180. package/dist/sync-CmBKOL3K.mjs +310 -0
  181. package/dist/sync-CmBKOL3K.mjs.map +1 -0
  182. package/dist/{themes-Cg8oG9Ra.mjs → themes-BBOlGXAg.mjs} +3 -3
  183. package/dist/{themes-Cg8oG9Ra.mjs.map → themes-BBOlGXAg.mjs.map} +1 -1
  184. package/dist/themes-nvRM8v84.mjs +148 -0
  185. package/dist/themes-nvRM8v84.mjs.map +1 -0
  186. package/dist/tools-B6BmaAvz.mjs +1939 -0
  187. package/dist/tools-B6BmaAvz.mjs.map +1 -0
  188. package/dist/tools-CAIY8TAb.mjs +1939 -0
  189. package/dist/tools-CAIY8TAb.mjs.map +1 -0
  190. package/dist/{tools-Op3C2Nm6.mjs → tools-CznjzfYz.mjs} +26 -26
  191. package/dist/{tools-Op3C2Nm6.mjs.map → tools-CznjzfYz.mjs.map} +1 -1
  192. package/dist/{trace-CLK-NPkb.mjs → trace-B8oz1ok0.mjs} +1 -1
  193. package/dist/{trace-CLK-NPkb.mjs.map → trace-B8oz1ok0.mjs.map} +1 -1
  194. package/dist/{utils-CqqgB0dH.mjs → utils-BAxjW3j8.mjs} +1 -1
  195. package/dist/{utils-CqqgB0dH.mjs.map → utils-BAxjW3j8.mjs.map} +1 -1
  196. package/dist/{vault-indexer-Dn_BccVK.mjs → vault-indexer-DrttVeEk.mjs} +2 -2
  197. package/dist/{vault-indexer-Dn_BccVK.mjs.map → vault-indexer-DrttVeEk.mjs.map} +1 -1
  198. package/dist/{work-queue-worker-BwouhVkb.mjs → work-queue-worker-BPXLkEik.mjs} +47 -30
  199. package/dist/work-queue-worker-BPXLkEik.mjs.map +1 -0
  200. package/dist/work-queue-worker-BoHIH70K.mjs +1856 -0
  201. package/dist/work-queue-worker-BoHIH70K.mjs.map +1 -0
  202. package/dist/work-queue-worker-CZGcQoLw.mjs +1856 -0
  203. package/dist/work-queue-worker-CZGcQoLw.mjs.map +1 -0
  204. package/dist/work-queue-worker-D05Ze9e9.mjs +1856 -0
  205. package/dist/work-queue-worker-D05Ze9e9.mjs.map +1 -0
  206. package/dist/work-queue-worker-DdXFINfH.mjs +1856 -0
  207. package/dist/work-queue-worker-DdXFINfH.mjs.map +1 -0
  208. package/dist/{zettelkasten-NwBD1NsT.mjs → zettelkasten-CTlt1dGv.mjs} +4 -4
  209. package/dist/{zettelkasten-NwBD1NsT.mjs.map → zettelkasten-CTlt1dGv.mjs.map} +1 -1
  210. package/dist/zettelkasten-D0N_A_OE.mjs +1063 -0
  211. package/dist/zettelkasten-D0N_A_OE.mjs.map +1 -0
  212. package/docs/commands/README.md +18 -0
  213. package/docs/commands/backup.md +1 -1
  214. package/docs/commands/clear-names.md +1 -1
  215. package/docs/commands/daemon.md +1 -1
  216. package/docs/commands/db.md +1 -1
  217. package/docs/commands/end.md +4 -1
  218. package/docs/commands/help.md +1 -1
  219. package/docs/commands/kg.md +1 -1
  220. package/docs/commands/mcp.md +1 -1
  221. package/docs/commands/memory.md +1 -1
  222. package/docs/commands/notify.md +1 -1
  223. package/docs/commands/observation.md +1 -1
  224. package/docs/commands/obsidian.md +1 -1
  225. package/docs/commands/pause.md +5 -2
  226. package/docs/commands/project.md +1 -1
  227. package/docs/commands/projects.md +1 -1
  228. package/docs/commands/registry.md +18 -1
  229. package/docs/commands/restore.md +1 -1
  230. package/docs/commands/session.md +295 -0
  231. package/docs/commands/sessions.md +1 -1
  232. package/docs/commands/setup.md +1 -1
  233. package/docs/commands/shell-init.md +1 -1
  234. package/docs/commands/skill.md +1 -1
  235. package/docs/commands/task.md +1 -1
  236. package/docs/commands/topic.md +1 -1
  237. package/docs/commands/update.md +1 -1
  238. package/docs/commands/zettel.md +1 -1
  239. package/package.json +1 -1
  240. package/plugins/context-preservation/hooks/hooks.json +12 -1
  241. package/scripts/build-hooks.mjs +33 -3
  242. package/src/hooks/session-autosave.sh +69 -0
  243. package/src/hooks/session-stop.sh +32 -1
  244. package/src/hooks/ts/lib/project-utils/todo.test.ts +132 -0
  245. package/src/hooks/ts/lib/project-utils/todo.ts +60 -34
  246. package/src/hooks/ts/session-start/load-project-context.ts +44 -0
  247. package/dist/kg-extraction-C8DEUHTS.mjs.map +0 -1
  248. package/dist/pai-marker-HVBwwBW-.mjs.map +0 -1
  249. package/dist/pick-r95zvydr.mjs.map +0 -1
  250. package/dist/sync-uPR4g438.mjs.map +0 -1
  251. package/dist/work-queue-worker-BwouhVkb.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/hooks/ts/session-start/inject-observations.ts", "../../src/hooks/ts/lib/project-utils/paths.ts", "../../src/hooks/ts/lib/pai-paths.ts"],
3
+ "sources": ["../../../src/hooks/ts/session-start/inject-observations.ts", "../../../src/hooks/ts/lib/project-utils/paths.ts", "../../../src/hooks/ts/lib/pai-paths.ts"],
4
4
  "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * inject-observations.ts\n *\n * SessionStart hook that injects recent project observation context into Claude's\n * session as a <system-reminder>. Provides progressive disclosure of recent activity\n * so Claude has immediate awareness of what has been happening in this project.\n *\n * Flow:\n * 1. Read session data (session_id, cwd) from stdin\n * 2. Call daemon via IPC: observation_recent with { cwd, limit: 25 }\n * (daemon resolves project_id from cwd internally via registry lookup)\n * 3. Format as progressive disclosure context block\n * 4. Output to stdout as <system-reminder> (injected into session by Claude Code)\n *\n * Silent on any failure \u2014 never blocks session start.\n */\n\nimport { connect } from 'net';\nimport { randomUUID } from 'crypto';\nimport { isProbeSession } from '../lib/project-utils.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HookData {\n session_id?: string;\n cwd?: string;\n hook_event_name?: string;\n}\n\ninterface ObservationRow {\n id: number;\n session_id: string;\n project_id: number | null;\n project_slug: string | null;\n type: string;\n title: string;\n narrative: string | null;\n created_at: string; // ISO string after JSON serialization\n}\n\ninterface ObservationRecentResult {\n rows: ObservationRow[];\n project_slug?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Inline IPC client \u2014 mirrors the pattern in observe.ts\n// Hooks can't import from src/daemon/ at runtime, so we inline this.\n// ---------------------------------------------------------------------------\n\nasync function callDaemon(method: string, params: Record<string, unknown>): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => reject(new Error('timeout')), 5000);\n let buffer = '';\n\n try {\n const socket = connect('/tmp/pai.sock', () => {\n socket.write(JSON.stringify({ id: randomUUID(), method, params }) + '\\n');\n });\n\n socket.on('data', (chunk: Buffer) => {\n buffer += chunk.toString();\n const nl = buffer.indexOf('\\n');\n if (nl !== -1) {\n clearTimeout(timeout);\n try {\n const response = JSON.parse(buffer.slice(0, nl));\n socket.destroy();\n if (response.ok) resolve(response.result);\n else reject(new Error(response.error ?? 'daemon error'));\n } catch (e) {\n socket.destroy();\n reject(e);\n }\n }\n });\n\n socket.on('error', () => {\n clearTimeout(timeout);\n reject(new Error('daemon unavailable'));\n });\n } catch {\n clearTimeout(timeout);\n reject(new Error('daemon unavailable'));\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Time formatting\n// ---------------------------------------------------------------------------\n\nfunction timeAgo(date: Date): string {\n const diffMs = Date.now() - date.getTime();\n const diffSec = Math.floor(diffMs / 1000);\n const diffMin = Math.floor(diffSec / 60);\n const diffHour = Math.floor(diffMin / 60);\n const diffDay = Math.floor(diffHour / 24);\n\n if (diffSec < 60) return 'just now';\n if (diffMin < 60) return `${diffMin}m ago`;\n if (diffHour < 24) return `${diffHour}h ago`;\n if (diffDay < 7) return `${diffDay}d ago`;\n\n // Older than a week: show date string\n return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });\n}\n\n// ---------------------------------------------------------------------------\n// Type label\n// ---------------------------------------------------------------------------\n\nfunction typeLabel(type: string): string {\n switch (type) {\n case 'change': return '[change]';\n case 'discovery': return '[discovery]';\n case 'decision': return '[decision]';\n case 'bugfix': return '[bugfix]';\n case 'feature': return '[feature]';\n case 'refactor': return '[refactor]';\n default: return `[${type}]`;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Truncate\n// ---------------------------------------------------------------------------\n\nfunction truncate(s: string, maxLen: number): string {\n return s.length > maxLen ? s.slice(0, maxLen - 1) + '\\u2026' : s;\n}\n\n// ---------------------------------------------------------------------------\n// Format observations as progressive disclosure context\n// ---------------------------------------------------------------------------\n\nfunction formatContext(\n projectSlug: string,\n observations: ObservationRow[]\n): string {\n if (observations.length === 0) return '';\n\n // Count distinct sessions\n const sessionSet = new Set(observations.map(o => o.session_id));\n const sessionCount = sessionSet.size;\n\n // Most recent observation\n const newest = new Date(observations[0].created_at);\n const lastActivity = timeAgo(newest);\n\n // Timeline: show most recent 15, keep titles to 80 chars\n const timelineObs = observations.slice(0, 15);\n const timeline = timelineObs\n .map(o => {\n const t = timeAgo(new Date(o.created_at));\n const label = typeLabel(o.type);\n const title = truncate(o.title, 80);\n return `- [${t}] ${label} ${title}`;\n })\n .join('\\n');\n\n const showingNote = observations.length > 15\n ? `(showing most recent 15 of ${observations.length}, use observation_search for more)`\n : `(showing ${observations.length} observation${observations.length !== 1 ? 's' : ''})`;\n\n const lines: string[] = [\n `<system-reminder>`,\n `OBSERVATION CONTEXT (auto-injected)`,\n ``,\n `## Recent Activity (${projectSlug})`,\n `${observations.length} observations across ${sessionCount} session${sessionCount !== 1 ? 's' : ''} | Last activity: ${lastActivity}`,\n ``,\n `### Recent Timeline`,\n timeline,\n showingNote,\n `</system-reminder>`,\n ];\n\n return lines.join('\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nasync function main() {\n try {\n // Skip probe/health-check sessions\n if (isProbeSession()) {\n process.exit(0);\n }\n\n // Skip subagent sessions \u2014 they don't need observation context\n const claudeProjectDir = process.env.CLAUDE_PROJECT_DIR || '';\n const isSubagent = claudeProjectDir.includes('/.claude/agents/') ||\n process.env.CLAUDE_AGENT_TYPE !== undefined;\n if (isSubagent) {\n process.exit(0);\n }\n\n // Read hook data from stdin\n let hookData: HookData = {};\n try {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk as Buffer);\n }\n const raw = Buffer.concat(chunks).toString('utf-8').trim();\n if (raw) {\n hookData = JSON.parse(raw);\n }\n } catch {\n // Non-fatal \u2014 fall back to process.cwd()\n }\n\n const cwd = hookData.cwd || process.cwd();\n\n // Fetch recent observations for this cwd \u2014 daemon resolves the project internally\n let observations: ObservationRow[];\n let projectSlug: string;\n\n try {\n const result = await callDaemon('observation_recent', { cwd, limit: 25 }) as ObservationRecentResult;\n observations = result?.rows ?? [];\n projectSlug = result?.project_slug ?? '';\n } catch {\n // Daemon unavailable or Postgres not configured \u2014 silent exit\n process.exit(0);\n }\n\n if (!observations || observations.length === 0 || !projectSlug) {\n // No data or no matching project \u2014 nothing to inject\n process.exit(0);\n }\n\n // Format and output\n const context = formatContext(projectSlug, observations);\n if (context) {\n // Output to stdout \u2014 Claude Code captures this and injects into session context\n console.log(context);\n }\n\n process.exit(0);\n } catch {\n // Never block session start\n process.exit(0);\n }\n}\n\nmain();\n", "/**\n * Path utilities \u2014 encoding, Notes/Sessions directory discovery and creation.\n */\n\nimport { existsSync, mkdirSync, readdirSync, renameSync } from 'fs';\nimport { join, basename } from 'path';\nimport { PAI_DIR } from '../pai-paths.js';\n\n// Re-export PAI_DIR for consumers\nexport { PAI_DIR };\nexport const PROJECTS_DIR = join(PAI_DIR, 'projects');\n\n/**\n * Directories known to be automated health-check / probe sessions.\n * Hooks should exit early for these to avoid registry clutter and wasted work.\n */\nconst PROBE_CWD_PATTERNS = [\n '/CodexBar/ClaudeProbe',\n '/ClaudeProbe',\n];\n\n/**\n * Check if the current working directory belongs to a probe/health-check session.\n * Returns true if hooks should skip this session entirely.\n */\nexport function isProbeSession(cwd?: string): boolean {\n const dir = cwd || process.cwd();\n return PROBE_CWD_PATTERNS.some(pattern => dir.includes(pattern));\n}\n\n/**\n * Encode a path the same way Claude Code does:\n * - Replace / with -\n * - Replace . with -\n * - Replace space with -\n */\nexport function encodePath(path: string): string {\n return path\n .replace(/\\//g, '-')\n .replace(/\\./g, '-')\n .replace(/ /g, '-');\n}\n\n/** Get the project directory for a given working directory. */\nexport function getProjectDir(cwd: string): string {\n const encoded = encodePath(cwd);\n return join(PROJECTS_DIR, encoded);\n}\n\n/** Get the Notes directory for a project (central location). */\nexport function getNotesDir(cwd: string): string {\n return join(getProjectDir(cwd), 'Notes');\n}\n\n/**\n * Find Notes directory \u2014 checks local first, falls back to central.\n * Does NOT create the directory.\n */\nexport function findNotesDir(cwd: string): { path: string; isLocal: boolean } {\n const cwdBasename = basename(cwd).toLowerCase();\n if (cwdBasename === 'notes' && existsSync(cwd)) {\n return { path: cwd, isLocal: true };\n }\n\n const localPaths = [\n join(cwd, 'Notes'),\n join(cwd, 'notes'),\n join(cwd, '.claude', 'Notes'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) {\n return { path, isLocal: true };\n }\n }\n\n return { path: getNotesDir(cwd), isLocal: false };\n}\n\n/** Get the sessions/ directory for a project (stores .jsonl transcripts). */\nexport function getSessionsDir(cwd: string): string {\n return join(getProjectDir(cwd), 'sessions');\n}\n\n/** Get the sessions/ directory from a project directory path. */\nexport function getSessionsDirFromProjectDir(projectDir: string): string {\n return join(projectDir, 'sessions');\n}\n\n// ---------------------------------------------------------------------------\n// Directory creation helpers\n// ---------------------------------------------------------------------------\n\n/** Ensure the Notes directory exists for a project. @deprecated Use ensureNotesDirSmart() */\nexport function ensureNotesDir(cwd: string): string {\n const notesDir = getNotesDir(cwd);\n if (!existsSync(notesDir)) {\n mkdirSync(notesDir, { recursive: true });\n console.error(`Created Notes directory: ${notesDir}`);\n }\n return notesDir;\n}\n\n/**\n * Smart Notes directory handling:\n * - If local Notes/ exists \u2192 use it (don't create anything new)\n * - If no local Notes/ \u2192 ensure central exists and use that\n */\nexport function ensureNotesDirSmart(cwd: string): { path: string; isLocal: boolean } {\n const found = findNotesDir(cwd);\n if (found.isLocal) return found;\n if (!existsSync(found.path)) {\n mkdirSync(found.path, { recursive: true });\n console.error(`Created central Notes directory: ${found.path}`);\n }\n return found;\n}\n\n/** Ensure the sessions/ directory exists for a project. */\nexport function ensureSessionsDir(cwd: string): string {\n const sessionsDir = getSessionsDir(cwd);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/** Ensure the sessions/ directory exists (from project dir path). */\nexport function ensureSessionsDirFromProjectDir(projectDir: string): string {\n const sessionsDir = getSessionsDirFromProjectDir(projectDir);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/**\n * Move all .jsonl session files from project root to sessions/ subdirectory.\n * Returns the number of files moved.\n */\nexport function moveSessionFilesToSessionsDir(\n projectDir: string,\n excludeFile?: string,\n silent = false\n): number {\n const sessionsDir = ensureSessionsDirFromProjectDir(projectDir);\n\n if (!existsSync(projectDir)) return 0;\n\n const files = readdirSync(projectDir);\n let movedCount = 0;\n\n for (const file of files) {\n if (file.endsWith('.jsonl') && file !== excludeFile) {\n const sourcePath = join(projectDir, file);\n const destPath = join(sessionsDir, file);\n try {\n renameSync(sourcePath, destPath);\n if (!silent) console.error(`Moved ${file} \u2192 sessions/`);\n movedCount++;\n } catch (error) {\n if (!silent) console.error(`Could not move ${file}: ${error}`);\n }\n }\n }\n\n return movedCount;\n}\n\n// ---------------------------------------------------------------------------\n// CLAUDE.md / TODO.md discovery\n// ---------------------------------------------------------------------------\n\n/** Find TODO.md \u2014 check local first, fallback to central. */\nexport function findTodoPath(cwd: string): string {\n const localPaths = [\n join(cwd, 'TODO.md'),\n join(cwd, 'notes', 'TODO.md'),\n join(cwd, 'Notes', 'TODO.md'),\n join(cwd, '.claude', 'TODO.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) return path;\n }\n\n return join(getNotesDir(cwd), 'TODO.md');\n}\n\n/** Find CLAUDE.md \u2014 returns the FIRST found path. */\nexport function findClaudeMdPath(cwd: string): string | null {\n const paths = findAllClaudeMdPaths(cwd);\n return paths.length > 0 ? paths[0] : null;\n}\n\n/**\n * Find ALL CLAUDE.md files in local locations in priority order.\n */\nexport function findAllClaudeMdPaths(cwd: string): string[] {\n const foundPaths: string[] = [];\n\n const localPaths = [\n join(cwd, '.claude', 'CLAUDE.md'),\n join(cwd, 'CLAUDE.md'),\n join(cwd, 'Notes', 'CLAUDE.md'),\n join(cwd, 'notes', 'CLAUDE.md'),\n join(cwd, 'Prompts', 'CLAUDE.md'),\n join(cwd, 'prompts', 'CLAUDE.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) foundPaths.push(path);\n }\n\n return foundPaths;\n}\n", "/**\n * PAI Path Resolution - Single Source of Truth\n *\n * This module provides consistent path resolution across all PAI hooks.\n * It handles PAI_DIR detection whether set explicitly or defaulting to ~/.claude\n *\n * ALSO loads .env file from PAI_DIR so all hooks get environment variables\n * without relying on Claude Code's settings.json injection.\n *\n * Usage in hooks:\n * import { PAI_DIR, HOOKS_DIR, SKILLS_DIR } from './lib/pai-paths';\n */\n\nimport { homedir } from 'os';\nimport { resolve, join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\n\n/**\n * Load .env file and inject into process.env\n * Must run BEFORE PAI_DIR resolution so .env can set PAI_DIR if needed\n */\nfunction loadEnvFile(): void {\n // Check common locations for .env\n const possiblePaths = [\n resolve(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\n/**\n * Smart PAI_DIR detection with fallback\n * Priority:\n * 1. PAI_DIR environment variable (if set)\n * 2. ~/.claude (standard location)\n */\nexport const PAI_DIR = process.env.PAI_DIR\n ? resolve(process.env.PAI_DIR)\n : resolve(homedir(), '.claude');\n\n/**\n * Common PAI directories\n */\nexport const HOOKS_DIR = join(PAI_DIR, 'Hooks');\nexport const SKILLS_DIR = join(PAI_DIR, 'Skills');\nexport const AGENTS_DIR = join(PAI_DIR, 'Agents');\nexport const HISTORY_DIR = join(PAI_DIR, 'History');\nexport const COMMANDS_DIR = join(PAI_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(PAI_DIR)) {\n console.error(`PAI_DIR does not exist: ${PAI_DIR}`);\n console.error(` Expected ~/.claude or set PAI_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 PAI_DIR may be misconfigured`);\n console.error(` Current PAI_DIR: ${PAI_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 * 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(HISTORY_DIR, subdir, `${year}-${month}`, filename);\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["join", "join", "resolve"]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/hooks/ts/session-start/load-core-context.ts", "../../src/hooks/ts/lib/pai-paths.ts", "../../src/hooks/ts/lib/project-utils/paths.ts"],
3
+ "sources": ["../../../src/hooks/ts/session-start/load-core-context.ts", "../../../src/hooks/ts/lib/pai-paths.ts", "../../../src/hooks/ts/lib/project-utils/paths.ts"],
4
4
  "sourcesContent": ["#!/usr/bin/env node\n\n/**\n * load-core-context.ts\n *\n * Automatically loads your CORE skill context at session start by reading and injecting\n * the CORE SKILL.md file contents directly into Claude's context as a system-reminder.\n *\n * Purpose:\n * - Read CORE SKILL.md file content\n * - Output content as system-reminder for Claude to process\n * - Ensure complete context (contacts, preferences, security, identity) available at session start\n * - Bypass skill activation logic by directly injecting context\n *\n * Setup:\n * 1. Customize your ${PAI_DIR}/skills/CORE/SKILL.md with your personal context\n * 2. Add this hook to settings.json SessionStart hooks\n * 3. Ensure PAI_DIR environment variable is set (defaults to $HOME/.claude)\n *\n * How it works:\n * - Runs at the start of every Claude Code session\n * - Skips execution for subagent sessions (they don't need CORE context)\n * - Reads your CORE SKILL.md file\n * - Injects content as <system-reminder> which Claude processes automatically\n * - Gives your AI immediate access to your complete personal context\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport { PAI_DIR, SKILLS_DIR } from '../lib/pai-paths';\nimport { isProbeSession } from '../lib/project-utils';\n\nasync function main() {\n try {\n // Check if this is a subagent session - if so, exit silently\n const claudeProjectDir = process.env.CLAUDE_PROJECT_DIR || '';\n const isSubagent = claudeProjectDir.includes('/.claude/agents/') ||\n process.env.CLAUDE_AGENT_TYPE !== undefined;\n\n if (isSubagent) {\n // Subagent sessions don't need CORE context loading\n console.error('Subagent session - skipping CORE context loading');\n process.exit(0);\n }\n\n // Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)\n if (isProbeSession()) {\n console.error('Probe session detected - skipping CORE context loading');\n process.exit(0);\n }\n\n // Get CORE skill path using PAI paths library\n const coreSkillPath = join(SKILLS_DIR, 'CORE/SKILL.md');\n\n // Verify CORE skill file exists\n if (!existsSync(coreSkillPath)) {\n console.error(`CORE skill not found at: ${coreSkillPath}`);\n console.error(`Ensure CORE/SKILL.md exists or check PAI_DIR environment variable`);\n process.exit(1);\n }\n\n console.error('Reading CORE context from skill file...');\n\n // Read the CORE SKILL.md file content\n let coreContent = readFileSync(coreSkillPath, 'utf-8');\n\n // Perform Dynamic Variable Substitution\n // This allows SKILL.md to be generic while the session is personalized\n const daName = process.env.DA || 'PAI';\n const daColor = process.env.DA_COLOR || 'blue';\n const engineerName = process.env.ENGINEER_NAME || '';\n\n // Replace placeholders {{DA}}, {{DA_COLOR}}, {{ENGINEER_NAME}}\n coreContent = coreContent\n .replace(/\\{\\{DA\\}\\}/g, daName)\n .replace(/\\{\\{DA_COLOR\\}\\}/g, daColor)\n .replace(/\\{\\{ENGINEER_NAME\\}\\}/g, engineerName);\n\n console.error(`Read ${coreContent.length} characters from CORE SKILL.md (Personalized for ${engineerName} & ${daName})`);\n\n // Determine the local timezone dynamically\n const localTimeZone = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone;\n\n // Output the CORE content as a system-reminder\n // This will be injected into Claude's context at session start\n const message = `<system-reminder>\nPAI CORE CONTEXT (Auto-loaded at Session Start)\n\nCURRENT DATE/TIME: ${new Date().toLocaleString('en-US', { timeZone: localTimeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, timeZoneName: 'short' })}\n\nThe following context has been loaded from ${coreSkillPath}:\n\n---\n${coreContent}\n---\n\nThis context is now active for this session. Follow all instructions, preferences, and guidelines contained above.\n</system-reminder>`;\n\n // Write to stdout (will be captured by Claude Code)\n console.log(message);\n\n console.error('CORE context injected into session');\n process.exit(0);\n } catch (error) {\n console.error('Error in load-core-context hook:', error);\n process.exit(1);\n }\n}\n\nmain();\n", "/**\n * PAI Path Resolution - Single Source of Truth\n *\n * This module provides consistent path resolution across all PAI hooks.\n * It handles PAI_DIR detection whether set explicitly or defaulting to ~/.claude\n *\n * ALSO loads .env file from PAI_DIR so all hooks get environment variables\n * without relying on Claude Code's settings.json injection.\n *\n * Usage in hooks:\n * import { PAI_DIR, HOOKS_DIR, SKILLS_DIR } from './lib/pai-paths';\n */\n\nimport { homedir } from 'os';\nimport { resolve, join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\n\n/**\n * Load .env file and inject into process.env\n * Must run BEFORE PAI_DIR resolution so .env can set PAI_DIR if needed\n */\nfunction loadEnvFile(): void {\n // Check common locations for .env\n const possiblePaths = [\n resolve(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\n/**\n * Smart PAI_DIR detection with fallback\n * Priority:\n * 1. PAI_DIR environment variable (if set)\n * 2. ~/.claude (standard location)\n */\nexport const PAI_DIR = process.env.PAI_DIR\n ? resolve(process.env.PAI_DIR)\n : resolve(homedir(), '.claude');\n\n/**\n * Common PAI directories\n */\nexport const HOOKS_DIR = join(PAI_DIR, 'Hooks');\nexport const SKILLS_DIR = join(PAI_DIR, 'Skills');\nexport const AGENTS_DIR = join(PAI_DIR, 'Agents');\nexport const HISTORY_DIR = join(PAI_DIR, 'History');\nexport const COMMANDS_DIR = join(PAI_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(PAI_DIR)) {\n console.error(`PAI_DIR does not exist: ${PAI_DIR}`);\n console.error(` Expected ~/.claude or set PAI_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 PAI_DIR may be misconfigured`);\n console.error(` Current PAI_DIR: ${PAI_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 * 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(HISTORY_DIR, subdir, `${year}-${month}`, filename);\n}\n", "/**\n * Path utilities \u2014 encoding, Notes/Sessions directory discovery and creation.\n */\n\nimport { existsSync, mkdirSync, readdirSync, renameSync } from 'fs';\nimport { join, basename } from 'path';\nimport { PAI_DIR } from '../pai-paths.js';\n\n// Re-export PAI_DIR for consumers\nexport { PAI_DIR };\nexport const PROJECTS_DIR = join(PAI_DIR, 'projects');\n\n/**\n * Directories known to be automated health-check / probe sessions.\n * Hooks should exit early for these to avoid registry clutter and wasted work.\n */\nconst PROBE_CWD_PATTERNS = [\n '/CodexBar/ClaudeProbe',\n '/ClaudeProbe',\n];\n\n/**\n * Check if the current working directory belongs to a probe/health-check session.\n * Returns true if hooks should skip this session entirely.\n */\nexport function isProbeSession(cwd?: string): boolean {\n const dir = cwd || process.cwd();\n return PROBE_CWD_PATTERNS.some(pattern => dir.includes(pattern));\n}\n\n/**\n * Encode a path the same way Claude Code does:\n * - Replace / with -\n * - Replace . with -\n * - Replace space with -\n */\nexport function encodePath(path: string): string {\n return path\n .replace(/\\//g, '-')\n .replace(/\\./g, '-')\n .replace(/ /g, '-');\n}\n\n/** Get the project directory for a given working directory. */\nexport function getProjectDir(cwd: string): string {\n const encoded = encodePath(cwd);\n return join(PROJECTS_DIR, encoded);\n}\n\n/** Get the Notes directory for a project (central location). */\nexport function getNotesDir(cwd: string): string {\n return join(getProjectDir(cwd), 'Notes');\n}\n\n/**\n * Find Notes directory \u2014 checks local first, falls back to central.\n * Does NOT create the directory.\n */\nexport function findNotesDir(cwd: string): { path: string; isLocal: boolean } {\n const cwdBasename = basename(cwd).toLowerCase();\n if (cwdBasename === 'notes' && existsSync(cwd)) {\n return { path: cwd, isLocal: true };\n }\n\n const localPaths = [\n join(cwd, 'Notes'),\n join(cwd, 'notes'),\n join(cwd, '.claude', 'Notes'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) {\n return { path, isLocal: true };\n }\n }\n\n return { path: getNotesDir(cwd), isLocal: false };\n}\n\n/** Get the sessions/ directory for a project (stores .jsonl transcripts). */\nexport function getSessionsDir(cwd: string): string {\n return join(getProjectDir(cwd), 'sessions');\n}\n\n/** Get the sessions/ directory from a project directory path. */\nexport function getSessionsDirFromProjectDir(projectDir: string): string {\n return join(projectDir, 'sessions');\n}\n\n// ---------------------------------------------------------------------------\n// Directory creation helpers\n// ---------------------------------------------------------------------------\n\n/** Ensure the Notes directory exists for a project. @deprecated Use ensureNotesDirSmart() */\nexport function ensureNotesDir(cwd: string): string {\n const notesDir = getNotesDir(cwd);\n if (!existsSync(notesDir)) {\n mkdirSync(notesDir, { recursive: true });\n console.error(`Created Notes directory: ${notesDir}`);\n }\n return notesDir;\n}\n\n/**\n * Smart Notes directory handling:\n * - If local Notes/ exists \u2192 use it (don't create anything new)\n * - If no local Notes/ \u2192 ensure central exists and use that\n */\nexport function ensureNotesDirSmart(cwd: string): { path: string; isLocal: boolean } {\n const found = findNotesDir(cwd);\n if (found.isLocal) return found;\n if (!existsSync(found.path)) {\n mkdirSync(found.path, { recursive: true });\n console.error(`Created central Notes directory: ${found.path}`);\n }\n return found;\n}\n\n/** Ensure the sessions/ directory exists for a project. */\nexport function ensureSessionsDir(cwd: string): string {\n const sessionsDir = getSessionsDir(cwd);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/** Ensure the sessions/ directory exists (from project dir path). */\nexport function ensureSessionsDirFromProjectDir(projectDir: string): string {\n const sessionsDir = getSessionsDirFromProjectDir(projectDir);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/**\n * Move all .jsonl session files from project root to sessions/ subdirectory.\n * Returns the number of files moved.\n */\nexport function moveSessionFilesToSessionsDir(\n projectDir: string,\n excludeFile?: string,\n silent = false\n): number {\n const sessionsDir = ensureSessionsDirFromProjectDir(projectDir);\n\n if (!existsSync(projectDir)) return 0;\n\n const files = readdirSync(projectDir);\n let movedCount = 0;\n\n for (const file of files) {\n if (file.endsWith('.jsonl') && file !== excludeFile) {\n const sourcePath = join(projectDir, file);\n const destPath = join(sessionsDir, file);\n try {\n renameSync(sourcePath, destPath);\n if (!silent) console.error(`Moved ${file} \u2192 sessions/`);\n movedCount++;\n } catch (error) {\n if (!silent) console.error(`Could not move ${file}: ${error}`);\n }\n }\n }\n\n return movedCount;\n}\n\n// ---------------------------------------------------------------------------\n// CLAUDE.md / TODO.md discovery\n// ---------------------------------------------------------------------------\n\n/** Find TODO.md \u2014 check local first, fallback to central. */\nexport function findTodoPath(cwd: string): string {\n const localPaths = [\n join(cwd, 'TODO.md'),\n join(cwd, 'notes', 'TODO.md'),\n join(cwd, 'Notes', 'TODO.md'),\n join(cwd, '.claude', 'TODO.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) return path;\n }\n\n return join(getNotesDir(cwd), 'TODO.md');\n}\n\n/** Find CLAUDE.md \u2014 returns the FIRST found path. */\nexport function findClaudeMdPath(cwd: string): string | null {\n const paths = findAllClaudeMdPaths(cwd);\n return paths.length > 0 ? paths[0] : null;\n}\n\n/**\n * Find ALL CLAUDE.md files in local locations in priority order.\n */\nexport function findAllClaudeMdPaths(cwd: string): string[] {\n const foundPaths: string[] = [];\n\n const localPaths = [\n join(cwd, '.claude', 'CLAUDE.md'),\n join(cwd, 'CLAUDE.md'),\n join(cwd, 'Notes', 'CLAUDE.md'),\n join(cwd, 'notes', 'CLAUDE.md'),\n join(cwd, 'Prompts', 'CLAUDE.md'),\n join(cwd, 'prompts', 'CLAUDE.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) foundPaths.push(path);\n }\n\n return foundPaths;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["readFileSync", "existsSync", "join", "join", "join", "join", "existsSync", "readFileSync"]
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/hooks/ts/session-start/load-project-context.ts
4
- import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync } from "fs";
4
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
5
5
  import { join as join6, basename as basename4, dirname, resolve as resolve2 } from "path";
6
6
  import { homedir as homedir4 } from "os";
7
7
  import { execSync } from "child_process";
8
8
 
9
9
  // src/memory/wakeup.ts
10
- import { existsSync, readdirSync, readFileSync } from "node:fs";
10
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
11
11
  import { join, basename } from "node:path";
12
12
  import { homedir } from "node:os";
13
13
  var L1_TOKEN_BUDGET = 800;
@@ -68,10 +68,26 @@ function findSessionNotes(notesDir) {
68
68
  }
69
69
  };
70
70
  scanDir(notesDir);
71
+ const dateOf = (p) => basename(p).match(/(\d{4}-\d{2}-\d{2})/)?.[1] ?? "";
72
+ const numberOf = (p) => parseInt(basename(p).match(/^(\d+)/)?.[1] ?? "0", 10);
73
+ const mtimeOf = (p) => {
74
+ try {
75
+ return statSync(p).mtimeMs;
76
+ } catch {
77
+ return 0;
78
+ }
79
+ };
71
80
  result.sort((a, b) => {
72
- const numA = parseInt(basename(a).match(/^(\d+)/)?.[1] ?? "0", 10);
73
- const numB = parseInt(basename(b).match(/^(\d+)/)?.[1] ?? "0", 10);
74
- return numB - numA;
81
+ const dateA = dateOf(a);
82
+ const dateB = dateOf(b);
83
+ if (dateA !== dateB) {
84
+ if (!dateA) return 1;
85
+ if (!dateB) return -1;
86
+ return dateB.localeCompare(dateA);
87
+ }
88
+ const byNumber = numberOf(b) - numberOf(a);
89
+ if (byNumber !== 0) return byNumber;
90
+ return mtimeOf(b) - mtimeOf(a);
75
91
  });
76
92
  return result;
77
93
  }
@@ -161,6 +177,141 @@ ${essentialStory}`);
161
177
  return parts.join("\n\n");
162
178
  }
163
179
 
180
+ // src/session/checkpoint-block.ts
181
+ var MARKER_OPEN = "<!-- pai:checkpoint";
182
+ var MARKER_CLOSE = "<!-- /pai:checkpoint -->";
183
+ var CONTINUE_HEADING = "## Continue";
184
+ function parseMarker(line) {
185
+ const trimmed = line.trim();
186
+ if (!trimmed.startsWith(MARKER_OPEN)) return null;
187
+ const attrs = {};
188
+ for (const m of trimmed.matchAll(/([a-zA-Z][\w-]*)="([^"]*)"/g)) {
189
+ attrs[m[1]] = m[2];
190
+ }
191
+ return {
192
+ authored: attrs.authored === "model" ? "model" : "auto",
193
+ session: attrs.session || void 0,
194
+ sessionId: attrs["session-id"] || void 0,
195
+ ts: attrs.ts || void 0
196
+ };
197
+ }
198
+ var BOILERPLATE_PATTERNS = [
199
+ /^##\s+Continue$/,
200
+ /^<!--\s*\/?pai:checkpoint/,
201
+ /^>\s*\*\*Last session:\*\*/,
202
+ /^>\s*\*\*Paused at:\*\*/,
203
+ /^>\s*Working directory:/,
204
+ /^>\s*Resume with:/,
205
+ /^>\s*_No checkpoint body was recorded/,
206
+ /^-{3,}$/
207
+ ];
208
+ var BLANK_LINE = /^>?\s*$/;
209
+ function extractSectionContent(lines) {
210
+ const kept = [];
211
+ for (const line of lines) {
212
+ const t = line.trim();
213
+ if (BOILERPLATE_PATTERNS.some((re) => re.test(t))) continue;
214
+ kept.push(line);
215
+ }
216
+ return trimBlankEdges(collapseBlankRuns(kept)).join("\n");
217
+ }
218
+ function trimBlankEdges(lines) {
219
+ let start = 0;
220
+ let end = lines.length;
221
+ while (start < end && BLANK_LINE.test(lines[start].trim())) start += 1;
222
+ while (end > start && BLANK_LINE.test(lines[end - 1].trim())) end -= 1;
223
+ return lines.slice(start, end);
224
+ }
225
+ function collapseBlankRuns(lines) {
226
+ const out = [];
227
+ let lastWasBlank = false;
228
+ for (const line of lines) {
229
+ const isBlank = BLANK_LINE.test(line.trim());
230
+ if (isBlank && lastWasBlank) continue;
231
+ out.push(line);
232
+ lastWasBlank = isBlank;
233
+ }
234
+ return out;
235
+ }
236
+ function locateContinue(content) {
237
+ const lines = content.split("\n");
238
+ const startIdx = lines.findIndex((l) => l.trim() === CONTINUE_HEADING);
239
+ if (startIdx === -1) return null;
240
+ let meta = null;
241
+ let markerIdx = -1;
242
+ for (let i = startIdx + 1; i < Math.min(startIdx + 6, lines.length); i++) {
243
+ const parsed = parseMarker(lines[i]);
244
+ if (parsed) {
245
+ meta = parsed;
246
+ markerIdx = i;
247
+ break;
248
+ }
249
+ if (lines[i].trim() !== "") break;
250
+ }
251
+ let endIdx = lines.length;
252
+ if (markerIdx !== -1) {
253
+ const closeIdx = lines.findIndex(
254
+ (l, i) => i > markerIdx && l.trim() === MARKER_CLOSE
255
+ );
256
+ if (closeIdx !== -1) {
257
+ endIdx = closeIdx + 1;
258
+ } else {
259
+ endIdx = heuristicEnd(lines, startIdx);
260
+ }
261
+ } else {
262
+ endIdx = heuristicEnd(lines, startIdx);
263
+ }
264
+ let trailingEnd = endIdx;
265
+ while (trailingEnd < lines.length && lines[trailingEnd].trim() === "") {
266
+ trailingEnd += 1;
267
+ }
268
+ if (trailingEnd < lines.length && lines[trailingEnd].trim() === "---") {
269
+ trailingEnd += 1;
270
+ } else {
271
+ trailingEnd = endIdx;
272
+ }
273
+ return {
274
+ startIdx,
275
+ endIdx: trailingEnd,
276
+ meta,
277
+ lines: lines.slice(startIdx, trailingEnd)
278
+ };
279
+ }
280
+ function heuristicEnd(lines, startIdx) {
281
+ for (let i = startIdx + 1; i < lines.length; i++) {
282
+ const trimmed = lines[i].trim();
283
+ if (trimmed === "---" || /^##(?!#)/.test(trimmed) && trimmed !== CONTINUE_HEADING) {
284
+ return i;
285
+ }
286
+ }
287
+ return lines.length;
288
+ }
289
+ function readContinueCheckpoint(content) {
290
+ const found = locateContinue(content);
291
+ if (!found) return null;
292
+ const body = found.meta ? extractMarkedBody(found.lines) : extractSectionContent(found.lines);
293
+ if (!body) return null;
294
+ return { meta: found.meta, body, raw: found.lines.join("\n") };
295
+ }
296
+ function extractMarkedBody(lines) {
297
+ const markerIdx = lines.findIndex((l) => parseMarker(l) !== null);
298
+ if (markerIdx === -1) return extractSectionContent(lines);
299
+ const closeIdx = lines.findIndex(
300
+ (l, i) => i > markerIdx && l.trim() === MARKER_CLOSE
301
+ );
302
+ if (closeIdx === -1) return extractSectionContent(lines);
303
+ let bodyStart = markerIdx + 1;
304
+ while (bodyStart < closeIdx) {
305
+ const t = lines[bodyStart].trim();
306
+ if (BLANK_LINE.test(t) || t.startsWith(">")) {
307
+ bodyStart += 1;
308
+ continue;
309
+ }
310
+ break;
311
+ }
312
+ return trimBlankEdges(lines.slice(bodyStart, closeIdx)).join("\n");
313
+ }
314
+
164
315
  // src/hooks/ts/lib/project-utils/paths.ts
165
316
  import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, renameSync } from "fs";
166
317
  import { join as join3, basename as basename2 } from "path";
@@ -573,7 +724,7 @@ async function main() {
573
724
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => ({
574
725
  name: f,
575
726
  path: join6(projectDir, f),
576
- mtime: statSync(join6(projectDir, f)).mtime.getTime()
727
+ mtime: statSync2(join6(projectDir, f)).mtime.getTime()
577
728
  })).sort((a, b) => b.mtime - a.mtime);
578
729
  if (jsonlFiles.length > 1) {
579
730
  const { mkdirSync: mkdirSync3, renameSync: renameSync3 } = await import("fs");
@@ -750,6 +901,37 @@ Session Commands:
750
901
  </system-reminder>
751
902
  `;
752
903
  console.log(reminder);
904
+ if (existsSync6(todoPath)) {
905
+ try {
906
+ const checkpoint = readContinueCheckpoint(readFileSync5(todoPath, "utf-8"));
907
+ if (checkpoint) {
908
+ const from = checkpoint.meta?.session ? `
909
+ From session: ${checkpoint.meta.session}` : "";
910
+ const when = checkpoint.meta?.ts ? `
911
+ Paused at: ${checkpoint.meta.ts}` : "";
912
+ const resume = checkpoint.meta?.sessionId ? `
913
+ Resume that session with: claude --resume ${checkpoint.meta.sessionId}` : "";
914
+ console.log(`
915
+ <system-reminder>
916
+ HANDOVER FROM THE PREVIOUS SESSION
917
+
918
+ Source: ${todoPath} (## Continue)${from}${when}${resume}
919
+
920
+ ${checkpoint.body}
921
+
922
+ ---
923
+ This is recorded state, not a new instruction. Do not start acting on it
924
+ unprompted. If the user says "go", "continue", or "weiter", resume from here.
925
+ </system-reminder>
926
+ `);
927
+ console.error(`Injected pause checkpoint (${checkpoint.body.length} chars)`);
928
+ } else {
929
+ console.error("No pause checkpoint body in TODO.md \u2014 nothing to hand over");
930
+ }
931
+ } catch (error) {
932
+ console.error("Checkpoint injection failed:", error);
933
+ }
934
+ }
753
935
  for (const { path, content } of claudeMdContents) {
754
936
  const claudeMdReminder = `
755
937
  <system-reminder>