aiwcli 0.15.4 → 0.15.7

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 (299) hide show
  1. package/README.md +6 -3
  2. package/dist/capabilities/branch/adapters.d.ts +2 -0
  3. package/dist/capabilities/branch/adapters.js +21 -0
  4. package/dist/capabilities/branch/contracts.d.ts +57 -0
  5. package/dist/capabilities/branch/contracts.js +1 -0
  6. package/dist/capabilities/branch/control-plane.d.ts +2 -0
  7. package/dist/capabilities/branch/control-plane.js +343 -0
  8. package/dist/capabilities/branch/runtime-core.d.ts +5 -0
  9. package/dist/capabilities/branch/runtime-core.js +36 -0
  10. package/dist/capabilities/installation/control-plane/clean-command.d.ts +41 -0
  11. package/dist/capabilities/installation/control-plane/clean-command.js +196 -0
  12. package/dist/capabilities/installation/control-plane/clear-command.d.ts +160 -0
  13. package/dist/capabilities/installation/control-plane/clear-command.js +1220 -0
  14. package/dist/capabilities/installation/control-plane/init-command.d.ts +81 -0
  15. package/dist/capabilities/installation/control-plane/init-command.js +449 -0
  16. package/dist/capabilities/launch/contracts.d.ts +51 -0
  17. package/dist/capabilities/launch/contracts.js +1 -0
  18. package/dist/capabilities/launch/control-plane/execute-launch.d.ts +2 -0
  19. package/dist/capabilities/launch/control-plane/execute-launch.js +222 -0
  20. package/dist/capabilities/launch/runtime-core/launch-options.d.ts +14 -0
  21. package/dist/capabilities/launch/runtime-core/launch-options.js +69 -0
  22. package/dist/cli/base-command.d.ts +18 -0
  23. package/dist/cli/base-command.js +55 -0
  24. package/dist/commands/branch.d.ts +0 -20
  25. package/dist/commands/branch.js +24 -416
  26. package/dist/commands/clean.d.ts +1 -41
  27. package/dist/commands/clean.js +1 -196
  28. package/dist/commands/clear.d.ts +1 -161
  29. package/dist/commands/clear.js +1 -1121
  30. package/dist/commands/init/index.d.ts +1 -98
  31. package/dist/commands/init/index.js +4 -478
  32. package/dist/commands/launch.d.ts +36 -11
  33. package/dist/commands/launch.js +135 -159
  34. package/dist/lib/base-command.d.ts +1 -114
  35. package/dist/lib/base-command.js +1 -153
  36. package/dist/lib/claude-settings-types.d.ts +31 -19
  37. package/dist/lib/context/context-formatter.d.ts +74 -0
  38. package/dist/lib/context/context-formatter.js +493 -0
  39. package/dist/lib/context/context-selector.d.ts +42 -0
  40. package/dist/lib/context/context-selector.js +451 -0
  41. package/dist/lib/context/context-store.d.ts +100 -0
  42. package/dist/lib/context/context-store.js +618 -0
  43. package/dist/lib/context/plan-manager.d.ts +54 -0
  44. package/dist/lib/context/plan-manager.js +282 -0
  45. package/dist/lib/context/task-tracker.d.ts +44 -0
  46. package/dist/lib/context/task-tracker.js +146 -0
  47. package/dist/lib/core-ide-base.d.ts +4 -0
  48. package/dist/lib/core-ide-base.js +77 -0
  49. package/dist/lib/core-installer.d.ts +5 -0
  50. package/dist/lib/core-installer.js +54 -0
  51. package/dist/lib/git-exclude-manager.d.ts +2 -2
  52. package/dist/lib/git-exclude-manager.js +3 -3
  53. package/dist/lib/hooks/hook-utils.d.ts +143 -0
  54. package/dist/lib/hooks/hook-utils.js +609 -0
  55. package/dist/lib/hooks/session-end-logic.d.ts +5 -0
  56. package/dist/lib/hooks/session-end-logic.js +63 -0
  57. package/dist/lib/hooks-merger.js +25 -19
  58. package/dist/lib/ide-path-resolver.d.ts +19 -7
  59. package/dist/lib/ide-path-resolver.js +25 -9
  60. package/dist/lib/install-state.d.ts +34 -0
  61. package/dist/lib/install-state.js +161 -0
  62. package/dist/lib/launch-options.d.ts +1 -0
  63. package/dist/lib/launch-options.js +1 -0
  64. package/dist/lib/lsp-patch.d.ts +12 -0
  65. package/dist/lib/lsp-patch.js +156 -0
  66. package/dist/lib/multiplexer.d.ts +57 -0
  67. package/dist/lib/multiplexer.js +19 -0
  68. package/dist/lib/multiplexers/psmux.d.ts +75 -0
  69. package/dist/lib/multiplexers/psmux.js +384 -0
  70. package/dist/lib/multiplexers/tmux.d.ts +44 -0
  71. package/dist/lib/multiplexers/tmux.js +262 -0
  72. package/dist/lib/mux-utils.d.ts +5 -0
  73. package/dist/lib/mux-utils.js +42 -0
  74. package/dist/lib/paths.d.ts +2 -2
  75. package/dist/lib/paths.js +2 -2
  76. package/dist/lib/platform-commands.d.ts +27 -0
  77. package/dist/lib/platform-commands.js +49 -0
  78. package/dist/lib/runtime/aiw-cli.d.ts +37 -0
  79. package/dist/lib/runtime/aiw-cli.js +74 -0
  80. package/dist/lib/runtime/atomic-write.d.ts +19 -0
  81. package/dist/lib/runtime/atomic-write.js +121 -0
  82. package/dist/lib/runtime/cli-args.d.ts +55 -0
  83. package/dist/lib/runtime/cli-args.js +185 -0
  84. package/dist/lib/runtime/constants.d.ts +56 -0
  85. package/dist/lib/runtime/constants.js +230 -0
  86. package/dist/lib/runtime/executable-policy.d.ts +16 -0
  87. package/dist/lib/runtime/executable-policy.js +57 -0
  88. package/dist/lib/runtime/git-state.d.ts +9 -0
  89. package/dist/lib/runtime/git-state.js +59 -0
  90. package/dist/lib/runtime/inference.d.ts +37 -0
  91. package/dist/lib/runtime/inference.js +262 -0
  92. package/dist/lib/runtime/lint-dispatch.d.ts +40 -0
  93. package/dist/lib/runtime/lint-dispatch.js +285 -0
  94. package/dist/lib/runtime/logger.d.ts +66 -0
  95. package/dist/lib/runtime/logger.js +201 -0
  96. package/dist/lib/runtime/models.d.ts +14 -0
  97. package/dist/lib/runtime/models.js +14 -0
  98. package/dist/lib/runtime/platform-adapter.d.ts +7 -0
  99. package/dist/lib/runtime/platform-adapter.js +21 -0
  100. package/dist/lib/runtime/preflight.d.ts +24 -0
  101. package/dist/lib/runtime/preflight.js +65 -0
  102. package/dist/lib/runtime/sentinel-ipc.d.ts +14 -0
  103. package/dist/lib/runtime/sentinel-ipc.js +67 -0
  104. package/dist/lib/runtime/state-io.d.ts +30 -0
  105. package/dist/lib/runtime/state-io.js +174 -0
  106. package/dist/lib/runtime/stop-words.d.ts +20 -0
  107. package/dist/lib/runtime/stop-words.js +150 -0
  108. package/dist/lib/runtime/subprocess-utils.d.ts +29 -0
  109. package/dist/lib/runtime/subprocess-utils.js +96 -0
  110. package/dist/lib/runtime/tmux-preflight.d.ts +13 -0
  111. package/dist/lib/runtime/tmux-preflight.js +78 -0
  112. package/dist/lib/runtime/utils.d.ts +54 -0
  113. package/dist/lib/runtime/utils.js +162 -0
  114. package/dist/lib/sentinel-wrapper.d.ts +9 -0
  115. package/dist/lib/sentinel-wrapper.js +20 -0
  116. package/dist/lib/shell-quoting.d.ts +5 -0
  117. package/dist/lib/shell-quoting.js +17 -0
  118. package/dist/lib/spawn-errors.d.ts +6 -0
  119. package/dist/lib/spawn-errors.js +15 -0
  120. package/dist/lib/spawn.js +5 -11
  121. package/dist/lib/template-installer.d.ts +4 -5
  122. package/dist/lib/template-installer.js +36 -34
  123. package/dist/lib/template-resolver.d.ts +6 -7
  124. package/dist/lib/template-resolver.js +26 -21
  125. package/dist/lib/template-settings-reconstructor.d.ts +7 -2
  126. package/dist/lib/template-settings-reconstructor.js +76 -45
  127. package/dist/lib/terminal-strategy.d.ts +11 -0
  128. package/dist/lib/terminal-strategy.js +49 -0
  129. package/dist/lib/terminal.d.ts +28 -0
  130. package/dist/lib/terminal.js +162 -112
  131. package/dist/lib/tmux-pane-placement.d.ts +17 -0
  132. package/dist/lib/tmux-pane-placement.js +58 -0
  133. package/dist/lib/tmux-primitives.d.ts +5 -0
  134. package/dist/lib/tmux-primitives.js +15 -0
  135. package/dist/lib/tmux-session.d.ts +32 -0
  136. package/dist/lib/tmux-session.js +86 -0
  137. package/dist/lib/tty-detection.js +1 -1
  138. package/dist/lib/types.d.ts +168 -0
  139. package/dist/lib/types.js +6 -0
  140. package/dist/lib/version.d.ts +1 -1
  141. package/dist/lib/version.js +1 -1
  142. package/dist/platform/launch.d.ts +10 -0
  143. package/dist/platform/launch.js +10 -0
  144. package/dist/templates/CLAUDE.md +31 -40
  145. package/dist/templates/cc-native/.claude/settings.json +27 -27
  146. package/dist/templates/cc-native/CC-NATIVE-README.md +1 -1
  147. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +10 -9
  148. package/dist/templates/cc-native/_cc-native/CLAUDE.md +18 -18
  149. package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +3 -3
  150. package/dist/templates/cc-native/_cc-native/artifacts/lib/format.ts +14 -14
  151. package/dist/templates/cc-native/_cc-native/artifacts/lib/tracker.ts +1 -1
  152. package/dist/templates/cc-native/_cc-native/artifacts/lib/write.ts +3 -3
  153. package/dist/templates/cc-native/_cc-native/cc-native.config.json +3 -3
  154. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +16 -15
  155. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +3 -3
  156. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +2 -2
  157. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +2 -2
  158. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +3 -3
  159. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +2 -2
  160. package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +3 -3
  161. package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +8 -8
  162. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +1 -1
  163. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +4 -4
  164. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +1 -1
  165. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +1 -1
  166. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +1 -1
  167. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +1 -1
  168. package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +2 -2
  169. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +1 -1
  170. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +2 -2
  171. package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +1 -1
  172. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +8 -8
  173. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +3 -3
  174. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +2 -2
  175. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +3 -3
  176. package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +3 -1
  177. package/dist/templates/cc-native/_cc-native/plan-review/lib/__tests__/agent-selection.test.ts +345 -0
  178. package/dist/templates/cc-native/_cc-native/plan-review/lib/__tests__/preflight.test.ts +344 -0
  179. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +37 -15
  180. package/dist/templates/cc-native/_cc-native/plan-review/lib/corroboration.ts +16 -69
  181. package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +1 -1
  182. package/dist/templates/cc-native/_cc-native/plan-review/lib/output-builder.ts +1 -1
  183. package/dist/templates/cc-native/_cc-native/plan-review/lib/plan-questions.ts +2 -2
  184. package/dist/templates/cc-native/_cc-native/plan-review/lib/preflight.ts +56 -26
  185. package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +7 -7
  186. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/agent.ts +4 -4
  187. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +3 -3
  188. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/index.ts +1 -1
  189. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/claude-agent.ts +2 -2
  190. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/codex-agent.ts +4 -4
  191. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/gemini-agent.ts +1 -1
  192. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +5 -6
  193. package/dist/templates/core/.codex/workflows/codex.md +17 -0
  194. package/dist/templates/core/.codex/workflows/handoff.md +5 -0
  195. package/dist/templates/core/.codex/workflows/meta-plan.md +7 -0
  196. package/dist/templates/core/.cognition/AGENTS.md +5 -0
  197. package/dist/templates/core/.cognition/config.json +12 -0
  198. package/dist/templates/{_shared → core}/.windsurf/workflows/handoff.md +1 -1
  199. package/dist/templates/{_shared → core}/.windsurf/workflows/meta-plan.md +1 -1
  200. package/dist/templates/core/hooks-ts/_utils/git-state.ts +2 -0
  201. package/dist/templates/{_shared → core}/hooks-ts/archive_plan.ts +14 -23
  202. package/dist/templates/core/hooks-ts/codex_explorer.ts +160 -0
  203. package/dist/templates/{_shared → core}/hooks-ts/context_monitor.ts +23 -55
  204. package/dist/templates/{_shared → core}/hooks-ts/file-suggestion.ts +4 -3
  205. package/dist/templates/{_shared → core}/hooks-ts/lint_after_edit.ts +7 -9
  206. package/dist/templates/{_shared → core}/hooks-ts/pre_compact.ts +5 -5
  207. package/dist/templates/{_shared → core}/hooks-ts/session_end.ts +38 -78
  208. package/dist/templates/{_shared → core}/hooks-ts/session_start.ts +5 -5
  209. package/dist/templates/core/hooks-ts/task_create_capture.ts +32 -0
  210. package/dist/templates/{_shared → core}/hooks-ts/task_update_capture.ts +9 -24
  211. package/dist/templates/core/hooks-ts/user_prompt_submit.ts +46 -0
  212. package/dist/templates/{_shared → core}/lib-ts/CLAUDE.md +27 -16
  213. package/dist/templates/{_shared → core}/lib-ts/agent-exec/backends/headless.ts +3 -2
  214. package/dist/templates/{_shared → core}/lib-ts/agent-exec/backends/tmux.ts +44 -15
  215. package/dist/templates/{_shared → core}/lib-ts/agent-exec/base-agent.ts +6 -4
  216. package/dist/templates/{_shared → core}/lib-ts/agent-exec/execution-backend.ts +1 -1
  217. package/dist/templates/{_shared → core}/lib-ts/agent-exec/index.ts +2 -2
  218. package/dist/templates/{_shared → core}/lib-ts/agent-exec/structured-output.ts +4 -5
  219. package/dist/templates/{_shared → core}/lib-ts/context/CLAUDE.md +9 -6
  220. package/dist/templates/{_shared → core}/lib-ts/context/context-formatter.ts +16 -21
  221. package/dist/templates/{_shared → core}/lib-ts/context/context-selector.ts +8 -6
  222. package/dist/templates/{_shared → core}/lib-ts/context/context-store.ts +32 -20
  223. package/dist/templates/{_shared → core}/lib-ts/context/plan-manager.ts +19 -15
  224. package/dist/templates/{_shared → core}/lib-ts/context/task-tracker.ts +3 -3
  225. package/dist/templates/core/lib-ts/hooks/context-monitor-logic.ts +32 -0
  226. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/hooks}/hook-utils.ts +168 -41
  227. package/dist/templates/core/lib-ts/hooks/prompt-binding-logic.ts +80 -0
  228. package/dist/templates/core/lib-ts/hooks/session-end-logic.ts +93 -0
  229. package/dist/templates/core/lib-ts/package.json +19 -0
  230. package/dist/templates/core/lib-ts/runtime/agent-launcher.ts +295 -0
  231. package/dist/templates/core/lib-ts/runtime/aiw-cli.ts +106 -0
  232. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/atomic-write.ts +12 -7
  233. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/cli-args.ts +8 -6
  234. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/constants.ts +326 -324
  235. package/dist/templates/core/lib-ts/runtime/executable-policy.ts +89 -0
  236. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/git-state.ts +6 -4
  237. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/inference.ts +59 -10
  238. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/lint-dispatch.ts +25 -23
  239. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/logger.ts +32 -29
  240. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/models.ts +2 -2
  241. package/dist/templates/core/lib-ts/runtime/platform-adapter.ts +33 -0
  242. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/preflight.ts +4 -3
  243. package/dist/templates/core/lib-ts/runtime/sentinel-ipc.ts +91 -0
  244. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/state-io.ts +11 -7
  245. package/dist/templates/core/lib-ts/runtime/stop-words.ts +185 -0
  246. package/dist/templates/core/lib-ts/runtime/subprocess-utils.ts +147 -0
  247. package/dist/templates/core/lib-ts/runtime/tmux-preflight.ts +93 -0
  248. package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/utils.ts +4 -3
  249. package/dist/templates/{_shared → core}/lib-ts/templates/formatters.ts +7 -5
  250. package/dist/templates/{_shared → core}/lib-ts/templates/plan-context.ts +2 -1
  251. package/dist/templates/{_shared → core}/lib-ts/tsconfig.json +3 -1
  252. package/dist/templates/{_shared → core}/lib-ts/types.ts +78 -77
  253. package/dist/templates/core/scripts/resolve-run.ts +61 -0
  254. package/dist/templates/{_shared → core}/scripts/resolve_context.ts +3 -3
  255. package/dist/templates/{_shared → core}/scripts/status_line.ts +25 -20
  256. package/dist/templates/core/skills/codex/CLAUDE.md +78 -0
  257. package/dist/templates/{_shared → core}/skills/codex/SKILL.md +21 -18
  258. package/dist/templates/{_shared → core}/skills/codex/lib/codex-watcher.ts +76 -103
  259. package/dist/templates/{_shared → core}/skills/codex/scripts/launch-codex.ts +119 -133
  260. package/dist/templates/{_shared → core}/skills/codex/scripts/watch-codex.ts +6 -4
  261. package/dist/templates/core/skills/devin/CLAUDE.md +65 -0
  262. package/dist/templates/core/skills/devin/SKILL.md +73 -0
  263. package/dist/templates/core/skills/devin/lib/devin-watcher.ts +280 -0
  264. package/dist/templates/core/skills/devin/scripts/launch-devin.ts +257 -0
  265. package/dist/templates/{_shared → core}/skills/handoff-system/CLAUDE.md +436 -433
  266. package/dist/templates/{_shared → core}/skills/handoff-system/lib/document-generator.ts +9 -7
  267. package/dist/templates/{_shared → core}/skills/handoff-system/lib/handoff-reader.ts +6 -4
  268. package/dist/templates/{_shared → core}/skills/handoff-system/scripts/resume_handoff.ts +10 -8
  269. package/dist/templates/{_shared → core}/skills/handoff-system/scripts/save_handoff.ts +12 -10
  270. package/dist/templates/{_shared → core}/skills/handoff-system/workflows/handoff-resume.md +2 -2
  271. package/dist/templates/{_shared → core}/skills/handoff-system/workflows/handoff.md +6 -5
  272. package/dist/templates/{_shared → core}/skills/meta-plan/CLAUDE.md +2 -1
  273. package/dist/templates/{_shared → core}/skills/meta-plan/workflows/meta-plan.md +8 -7
  274. package/oclif.manifest.json +89 -13
  275. package/package.json +13 -12
  276. package/dist/templates/_shared/.claude/settings.json +0 -120
  277. package/dist/templates/_shared/.claude/skills/codex/SKILL.md +0 -35
  278. package/dist/templates/_shared/.claude/skills/handoff/SKILL.md +0 -13
  279. package/dist/templates/_shared/.claude/skills/handoff-resume/SKILL.md +0 -13
  280. package/dist/templates/_shared/.claude/skills/meta-plan/SKILL.md +0 -43
  281. package/dist/templates/_shared/.codex/workflows/codex.md +0 -11
  282. package/dist/templates/_shared/.codex/workflows/handoff.md +0 -226
  283. package/dist/templates/_shared/.codex/workflows/meta-plan.md +0 -347
  284. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +0 -2
  285. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +0 -48
  286. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +0 -93
  287. package/dist/templates/_shared/lib-ts/base/launchers/tmux-launcher.ts +0 -173
  288. package/dist/templates/_shared/lib-ts/base/launchers/window-launcher.ts +0 -93
  289. package/dist/templates/_shared/lib-ts/base/launchers/wt-launcher.ts +0 -64
  290. package/dist/templates/_shared/lib-ts/base/pane-launcher.ts +0 -55
  291. package/dist/templates/_shared/lib-ts/base/sentinel-ipc.ts +0 -87
  292. package/dist/templates/_shared/lib-ts/base/stop-words.ts +0 -184
  293. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +0 -249
  294. package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +0 -341
  295. package/dist/templates/_shared/lib-ts/base/tmux-pane-placement.ts +0 -78
  296. package/dist/templates/_shared/lib-ts/package.json +0 -20
  297. package/dist/templates/_shared/scripts/resolve-run.ts +0 -62
  298. package/dist/templates/_shared/skills/codex/CLAUDE.md +0 -70
  299. /package/dist/templates/{_shared → core}/lib-ts/agent-exec/backends/index.ts +0 -0
@@ -0,0 +1,609 @@
1
+ /**
2
+ * Common utilities for hook scripts.
3
+ * Standardized boilerplate for JSON parsing, validation, error handling.
4
+ * See SPEC.md §5
5
+ */
6
+ import * as fs from "node:fs";
7
+ import { getContextBySessionId, maybeActivate } from "../context/context-store.js";
8
+ import { getProjectRoot } from "../runtime/constants.js";
9
+ import { logDebug, logWarn, hookLog, setSessionId, getContextPath as _getContextPath } from "../runtime/logger.js";
10
+ // Re-export logger functions for convenience (matches Python hook_utils re-exports)
11
+ // Context window baseline: tokens not visible in hook data §5.9
12
+ export const CONTEXT_BASELINE_TOKENS = 22_600;
13
+ export const DEFAULT_CONTEXT_WINDOW_SIZE = 200_000;
14
+ // Event metadata stash — populated by loadHookInput(), read by runHook()
15
+ let _lastHookEvent = null;
16
+ let _lastToolName = null;
17
+ let _cachedHookName = null;
18
+ // Pre-fetched input stash
19
+ let _prefetchedInput = null;
20
+ function readStringField(value, key) {
21
+ const field = value[key];
22
+ return typeof field === "string" ? field : null;
23
+ }
24
+ /**
25
+ * Load the current payload, resolve its project root, and require an already-bound session.
26
+ * Returns null when the hook should quietly no-op.
27
+ */
28
+ export function requireBoundSession(hookName, prefetchedPayload) {
29
+ const payload = prefetchedPayload ?? loadHookInput();
30
+ if (!payload)
31
+ return null;
32
+ const sessionId = payload.session_id;
33
+ if (!sessionId) {
34
+ logDebug(hookName, "No session_id available");
35
+ return null;
36
+ }
37
+ const projectRoot = getProjectRoot(payload.cwd);
38
+ const state = getContextBySessionId(sessionId, projectRoot);
39
+ if (!state) {
40
+ logDebug(hookName, `No bound context for session ${sessionId.slice(0, 8)}`);
41
+ return null;
42
+ }
43
+ return { payload, projectRoot, sessionId, state };
44
+ }
45
+ /**
46
+ * Require a bound session plus tool_input for PostToolUse task persistence hooks.
47
+ */
48
+ export function requirePersistenceContext(expectedTool, hookName) {
49
+ const bound = requireBoundSession(hookName);
50
+ if (!bound)
51
+ return null;
52
+ const { payload } = bound;
53
+ if (payload.hook_event_name !== "PostToolUse") {
54
+ return null;
55
+ }
56
+ if (payload.tool_name !== expectedTool) {
57
+ return null;
58
+ }
59
+ if (checkSkipPersistence(payload, hookName)) {
60
+ return null;
61
+ }
62
+ const toolInput = getToolInput(payload);
63
+ if (!toolInput) {
64
+ logWarn(hookName, `${expectedTool} missing tool_input payload`);
65
+ return null;
66
+ }
67
+ return { ...bound, toolInput };
68
+ }
69
+ /**
70
+ * Require a tool_input payload for tool-oriented hooks without needing a bound session.
71
+ */
72
+ export function requireToolInput(hookName) {
73
+ const payload = loadHookInput();
74
+ if (!payload)
75
+ return null;
76
+ const toolInput = getToolInput(payload);
77
+ if (!toolInput) {
78
+ logDebug(hookName, "No tool_input payload");
79
+ return null;
80
+ }
81
+ return { payload, toolInput };
82
+ }
83
+ /**
84
+ * Best-effort wrapper around maybeActivate. Hooks should never fail closed on activation.
85
+ */
86
+ export function safeMaybeActivate(contextId, permissionMode, projectRoot, hookName = "hook") {
87
+ try {
88
+ return maybeActivate(contextId, permissionMode, projectRoot, hookName);
89
+ }
90
+ catch (error) {
91
+ logWarn(hookName, `maybeActivate failed for ${contextId}: ${error}`);
92
+ return false;
93
+ }
94
+ }
95
+ /**
96
+ * Load and parse JSON from stdin (or return prefetched input if set).
97
+ * Returns null if stdin is empty or invalid JSON.
98
+ * See SPEC.md §5.1
99
+ */
100
+ export function loadHookInput() {
101
+ if (_prefetchedInput !== null) {
102
+ const result = _prefetchedInput;
103
+ _prefetchedInput = null; // consume once
104
+ if (result && typeof result === "object") {
105
+ _lastHookEvent = readStringField(result, "hook_event_name");
106
+ _lastToolName = readStringField(result, "tool_name");
107
+ }
108
+ return result;
109
+ }
110
+ try {
111
+ // Read entire stdin using fd 0 (cross-platform, works on Windows)
112
+ const inputData = fs.readFileSync(0, "utf8").trim();
113
+ if (!inputData)
114
+ return null;
115
+ const result = JSON.parse(inputData);
116
+ if (result && typeof result === "object") {
117
+ _lastHookEvent = readStringField(result, "hook_event_name");
118
+ _lastToolName = readStringField(result, "tool_name");
119
+ }
120
+ return result;
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ }
126
+ /**
127
+ * Validate hook event type and optional tool name.
128
+ * See SPEC.md §5.2
129
+ */
130
+ export function validateHookEvent(payload, expectedEvent, expectedTool) {
131
+ if (payload.hook_event_name !== expectedEvent)
132
+ return false;
133
+ if (expectedTool && payload.tool_name !== expectedTool)
134
+ return false;
135
+ return true;
136
+ }
137
+ /**
138
+ * Extract and validate tool_input from payload.
139
+ * See SPEC.md §5.3
140
+ */
141
+ export function getToolInput(payload) {
142
+ const toolInput = payload.tool_input;
143
+ return toolInput && typeof toolInput === "object" ? toolInput : null;
144
+ }
145
+ /**
146
+ * Check if persistence should be skipped based on metadata flags.
147
+ * See SPEC.md §5.4
148
+ */
149
+ export function checkSkipPersistence(payload, hookName = "hook") {
150
+ const toolInput = getToolInput(payload);
151
+ if (!toolInput)
152
+ return false;
153
+ const { metadata } = toolInput;
154
+ if (metadata &&
155
+ typeof metadata === "object" &&
156
+ Boolean(metadata.skip_persistence)) {
157
+ logDebug(hookName, "Skipping persistence (skip_persistence flag set)");
158
+ return true;
159
+ }
160
+ return false;
161
+ }
162
+ /**
163
+ * Emit hookSpecificOutput with additionalContext to stdout.
164
+ * hookEventName is required by Claude Code's Zod validator (discriminated union).
165
+ * Auto-detected from stdin payload (set by loadHookInput/runHook).
166
+ *
167
+ * SubagentStop and Stop events use top-level systemMessage field instead of hookSpecificOutput.
168
+ * See SPEC.md §5.5
169
+ */
170
+ export function emitContext(additionalContext) {
171
+ const eventName = _lastHookEvent ?? undefined;
172
+ const tool = _lastToolName;
173
+ // SubagentStop and Stop use top-level systemMessage field
174
+ if (eventName === "SubagentStop" || eventName === "Stop") {
175
+ const out = { systemMessage: additionalContext };
176
+ process.stdout.write(JSON.stringify(out) + "\n");
177
+ _logEmit("systemMessage", additionalContext.length, { event: eventName ?? "unknown", systemMessage: additionalContext });
178
+ return;
179
+ }
180
+ // All other events use hookSpecificOutput
181
+ const out = {
182
+ hookSpecificOutput: {
183
+ ...(eventName ? { hookEventName: eventName } : {}),
184
+ additionalContext,
185
+ },
186
+ };
187
+ const json = JSON.stringify(out);
188
+ const eventDesc = tool ? `${eventName}:${tool}` : eventName ?? "unknown";
189
+ _logEmit("context", additionalContext.length, { event: eventDesc, additionalContext });
190
+ process.stdout.write(json + "\n");
191
+ }
192
+ /**
193
+ * Emit hookSpecificOutput that denies the tool call with context and reason.
194
+ * hookEventName is required by Claude Code's Zod validator (discriminated union).
195
+ * Auto-detected from stdin payload (set by loadHookInput/runHook).
196
+ * See SPEC.md §5.6
197
+ */
198
+ export function emitContextAndBlock(additionalContext, reason) {
199
+ const eventName = _lastHookEvent ?? undefined;
200
+ if (eventName && eventName !== "PreToolUse") {
201
+ logWarn(_cachedHookName ?? "unknown", `emitContextAndBlock() called from ${eventName} — permissionDecision only works for PreToolUse. ` +
202
+ `Use emitBlock() or the event-specific function instead.`);
203
+ }
204
+ const tool = _lastToolName;
205
+ const out = {
206
+ hookSpecificOutput: {
207
+ ...(eventName ? { hookEventName: eventName } : {}),
208
+ additionalContext,
209
+ permissionDecision: "deny",
210
+ permissionDecisionReason: reason,
211
+ },
212
+ };
213
+ const json = JSON.stringify(out);
214
+ const eventDesc = tool ? `${eventName}:${tool}` : eventName ?? "unknown";
215
+ _logEmit("block", additionalContext.length, { event: eventDesc, additionalContext, blockReason: reason });
216
+ process.stdout.write(json + "\n");
217
+ }
218
+ /** Log hook output (context, systemMessage, or block) to hook-log.jsonl for visibility. */
219
+ function _logEmit(type, chars, payload) {
220
+ const hook = _cachedHookName ?? "unknown";
221
+ const event = payload.event ?? "unknown";
222
+ const mechanism = payload.mechanism ? ` via ${payload.mechanism}` : "";
223
+ const msg = type === "block"
224
+ ? `HOOK_OUTPUT [${type}] ${event} ${chars} chars${mechanism}, reason="${String(payload.blockReason ?? "").slice(0, 80)}"`
225
+ : `HOOK_OUTPUT [${type}] ${event} ${chars} chars`;
226
+ hookLog("info", hook, msg, { data: payload });
227
+ }
228
+ /**
229
+ * Block a user prompt submission with a reason.
230
+ * Only works for UserPromptSubmit hooks.
231
+ * Output: top-level { decision: "block", reason } + optional hookSpecificOutput.additionalContext
232
+ */
233
+ export function emitBlockPrompt(reason, context) {
234
+ const eventName = _lastHookEvent ?? undefined;
235
+ if (eventName && eventName !== "UserPromptSubmit") {
236
+ logWarn(_cachedHookName ?? "unknown", `emitBlockPrompt() called from ${eventName} — only works for UserPromptSubmit`);
237
+ }
238
+ const out = {
239
+ decision: "block",
240
+ reason,
241
+ ...(context ? {
242
+ hookSpecificOutput: {
243
+ ...(eventName ? { hookEventName: eventName } : {}),
244
+ additionalContext: context,
245
+ }
246
+ } : {}),
247
+ };
248
+ _logEmit("block", context?.length ?? 0, { event: eventName ?? "unknown", additionalContext: context, blockReason: reason });
249
+ process.stdout.write(JSON.stringify(out) + "\n");
250
+ }
251
+ /**
252
+ * Block via exit code 2 + stderr feedback.
253
+ * Works for PostToolUse, PostToolUseFailure.
254
+ * The reason becomes the stderr message (fed to Claude as system-reminder).
255
+ * If context is provided, it's prepended to the stderr message for richer feedback.
256
+ * NOTE: Exit 2 causes Claude Code to ignore all JSON stdout — only stderr matters.
257
+ */
258
+ export function emitBlockViaExit(reason, context) {
259
+ const stderrMessage = context ? `${context}\n\n${reason}` : reason;
260
+ _logEmit("block", stderrMessage.length, {
261
+ event: _lastHookEvent ?? "unknown",
262
+ blockReason: reason,
263
+ mechanism: "exit2",
264
+ });
265
+ process.stderr.write(stderrMessage + "\n");
266
+ throw new Error("SystemExit:2");
267
+ }
268
+ /**
269
+ * Block via top-level { decision: "block", reason }.
270
+ * Works for Stop and SubagentStop events.
271
+ * These events do NOT support additionalContext — only reason is available.
272
+ */
273
+ export function emitBlockTopLevel(reason) {
274
+ const eventName = _lastHookEvent ?? undefined;
275
+ if (eventName && eventName !== "Stop" && eventName !== "SubagentStop") {
276
+ logWarn(_cachedHookName ?? "unknown", `emitBlockTopLevel() called from ${eventName} — only works for Stop/SubagentStop`);
277
+ }
278
+ const out = { decision: "block", reason };
279
+ _logEmit("block", reason.length, {
280
+ event: eventName ?? "unknown",
281
+ blockReason: reason,
282
+ mechanism: "topLevelDecision",
283
+ });
284
+ process.stdout.write(JSON.stringify(out) + "\n");
285
+ }
286
+ /**
287
+ * Respond to a PermissionRequest with allow/deny.
288
+ * Only works for PermissionRequest hooks.
289
+ */
290
+ export function emitPermissionDecision(behavior, opts) {
291
+ const out = {
292
+ decision: {
293
+ behavior,
294
+ ...(opts?.message ? { message: opts.message } : {}),
295
+ ...(opts?.updatedInput ? { updatedInput: opts.updatedInput } : {}),
296
+ ...(opts?.updatedPermissions ? { updatedPermissions: opts.updatedPermissions } : {}),
297
+ },
298
+ };
299
+ _logEmit("block", 0, {
300
+ event: _lastHookEvent ?? "unknown",
301
+ blockReason: `permission:${behavior}`,
302
+ mechanism: "permissionRequest",
303
+ });
304
+ process.stdout.write(JSON.stringify(out) + "\n");
305
+ }
306
+ /**
307
+ * Unified block dispatcher — auto-detects the correct blocking mechanism
308
+ * based on the current hook event type.
309
+ *
310
+ * PreToolUse → permissionDecision: "deny" (via emitContextAndBlock)
311
+ * UserPromptSubmit → top-level decision: "block" (via emitBlockPrompt)
312
+ * PostToolUse/PostToolUseFailure → exit(2) + stderr (via emitBlockViaExit)
313
+ * Stop/SubagentStop → top-level { decision: "block", reason } (via emitBlockTopLevel)
314
+ * PermissionRequest → decision: { behavior: "deny" } (via emitPermissionDecision)
315
+ * SessionStart/Notification/SubagentStart/SessionEnd/etc. → warn and no-op
316
+ *
317
+ * This is the RECOMMENDED universal blocking API. Hook authors should use
318
+ * emitBlock() and let the library handle event-specific dispatch.
319
+ */
320
+ export function emitBlock(reason, context) {
321
+ const event = _lastHookEvent;
322
+ switch (event) {
323
+ case "PermissionRequest": {
324
+ emitPermissionDecision("deny", { message: reason });
325
+ break;
326
+ }
327
+ case "PostToolUse":
328
+ case "PostToolUseFailure": {
329
+ emitBlockViaExit(reason, context);
330
+ break;
331
+ }
332
+ case "PreToolUse": {
333
+ emitContextAndBlock(context ?? reason, reason);
334
+ break;
335
+ }
336
+ case "Stop":
337
+ case "SubagentStop": {
338
+ emitBlockTopLevel(reason);
339
+ break;
340
+ }
341
+ case "UserPromptSubmit": {
342
+ emitBlockPrompt(reason, context);
343
+ break;
344
+ }
345
+ default: {
346
+ logWarn(_cachedHookName ?? "unknown", `emitBlock() called from ${event ?? "unknown"} — no blocking mechanism exists for this event type, ignoring`);
347
+ break;
348
+ }
349
+ }
350
+ }
351
+ /**
352
+ * Auto-detect template origin from the hook script path.
353
+ */
354
+ function detectTemplate(scriptPath = "") {
355
+ const p = (scriptPath || (process.argv[1] ?? "")).replaceAll('\\', "/");
356
+ if (p.includes("/_core/hooks-ts/") ||
357
+ p.startsWith("_core/hooks-ts/") ||
358
+ p.includes("/core/hooks-ts/") ||
359
+ p.startsWith("core/hooks-ts/")) {
360
+ return "core";
361
+ }
362
+ const match = p.match(/_([a-z][a-z0-9-]*)\/hooks\//);
363
+ if (match?.[1])
364
+ return match[1]; // e.g., "cc-native"
365
+ return "unknown";
366
+ }
367
+ /**
368
+ * Parse context window from hook input.
369
+ * Returns [tokensUsed, maxTokens] or [null, null].
370
+ * See SPEC.md §5.9
371
+ */
372
+ export function parseContextWindow(hookInput) {
373
+ const contextWindow = hookInput.context_window;
374
+ if (!contextWindow)
375
+ return [null, null];
376
+ const currentUsage = contextWindow.current_usage;
377
+ if (!currentUsage)
378
+ return [null, null];
379
+ const cacheRead = currentUsage.cache_read_input_tokens ?? 0;
380
+ const inputTokens = currentUsage.input_tokens ?? 0;
381
+ const cacheCreation = currentUsage.cache_creation_input_tokens ?? 0;
382
+ const outputTokens = currentUsage.output_tokens ?? 0;
383
+ const contentTokens = cacheRead + inputTokens + cacheCreation + outputTokens;
384
+ const tokensUsed = contentTokens + CONTEXT_BASELINE_TOKENS;
385
+ const maxTokens = contextWindow.context_window_size ?? DEFAULT_CONTEXT_WINDOW_SIZE;
386
+ return [tokensUsed, maxTokens];
387
+ }
388
+ /**
389
+ * Get context percentage remaining with fallback.
390
+ * Returns [percentRemaining, tokensUsed, maxTokens] or [null, null, null].
391
+ * See SPEC.md §5.9
392
+ */
393
+ export function getContextPercentRemaining(hookInput) {
394
+ const [tokensUsed, maxTokens] = parseContextWindow(hookInput);
395
+ if (tokensUsed !== null && maxTokens !== null && maxTokens > 0) {
396
+ const remaining = maxTokens - tokensUsed;
397
+ const percentRemaining = Math.max(0, Math.min(100, Math.round((remaining / maxTokens) * 100)));
398
+ return [percentRemaining, tokensUsed, maxTokens];
399
+ }
400
+ // Source 2: context.json fallback (written by status_line.py)
401
+ try {
402
+ const sessionId = hookInput.session_id;
403
+ if (sessionId) {
404
+ const projectRoot = getProjectRoot(hookInput.cwd);
405
+ const context = getContextBySessionId(sessionId, projectRoot);
406
+ if (context?.last_session?.context_remaining_pct !== undefined) {
407
+ return [context.last_session.context_remaining_pct, null, null];
408
+ }
409
+ }
410
+ }
411
+ catch {
412
+ // Fallback failed — degrade gracefully
413
+ }
414
+ return [null, null, null];
415
+ }
416
+ /**
417
+ * Read stdin early and extract session_id + event metadata.
418
+ * Stashes parsed input for loadHookInput() to consume later.
419
+ */
420
+ function _earlyReadInput(prefetchedInput) {
421
+ if (prefetchedInput !== undefined) {
422
+ _prefetchedInput = prefetchedInput;
423
+ }
424
+ // If we already have prefetched input, extract metadata from it
425
+ if (_prefetchedInput && typeof _prefetchedInput === "object") {
426
+ _lastHookEvent = readStringField(_prefetchedInput, "hook_event_name");
427
+ _lastToolName = readStringField(_prefetchedInput, "tool_name");
428
+ const sessionId = readStringField(_prefetchedInput, "session_id");
429
+ if (sessionId) {
430
+ setSessionId(sessionId);
431
+ }
432
+ return;
433
+ }
434
+ // Read stdin now so HOOK_START can include sid
435
+ try {
436
+ const inputData = fs.readFileSync(0, "utf8").trim();
437
+ if (inputData) {
438
+ const parsed = JSON.parse(inputData);
439
+ if (parsed && typeof parsed === "object") {
440
+ _prefetchedInput = parsed;
441
+ _lastHookEvent = readStringField(parsed, "hook_event_name");
442
+ _lastToolName = readStringField(parsed, "tool_name");
443
+ const sessionId = readStringField(parsed, "session_id");
444
+ if (sessionId) {
445
+ setSessionId(sessionId);
446
+ }
447
+ }
448
+ }
449
+ }
450
+ catch {
451
+ // Non-fatal — loadHookInput will return null
452
+ }
453
+ }
454
+ /**
455
+ * Standard hook entry point with lifecycle logging.
456
+ * See SPEC.md §5.7
457
+ */
458
+ export function runHook(mainFunc, hookName = "unknown", prefetchedInput) {
459
+ _earlyReadInput(prefetchedInput);
460
+ _cachedHookName = hookName;
461
+ // Ensure cwd is project root so relative paths in hooks resolve correctly,
462
+ // even when cwd has drifted via `cd` in a Bash tool call.
463
+ try {
464
+ const cwd = _prefetchedInput ? readStringField(_prefetchedInput, "cwd") ?? undefined : undefined;
465
+ const projectRoot = getProjectRoot(cwd);
466
+ if (process.cwd() !== projectRoot)
467
+ process.chdir(projectRoot);
468
+ }
469
+ catch { /* non-fatal — proceed with current cwd */ }
470
+ const startTime = performance.now();
471
+ const template = detectTemplate();
472
+ const event = _lastHookEvent ?? "unknown";
473
+ const tool = _lastToolName;
474
+ const startData = {
475
+ lifecycle: "start",
476
+ template,
477
+ event,
478
+ };
479
+ if (tool)
480
+ startData.tool = tool;
481
+ hookLog("info", hookName, "HOOK_START", { data: startData });
482
+ let exitCode = 0;
483
+ let status = "success";
484
+ let errorInfo = null;
485
+ try {
486
+ const result = mainFunc();
487
+ exitCode = typeof result === "number" ? result : 0;
488
+ status = exitCode !== 0 ? "blocked" : "success";
489
+ }
490
+ catch (error) {
491
+ if (error instanceof Error && error.message.startsWith("SystemExit:")) {
492
+ const code = parseInt(error.message.slice(11), 10);
493
+ exitCode = isNaN(code) ? (error.message.slice(11) ? 1 : 0) : code;
494
+ status = exitCode !== 0 ? "blocked" : "success";
495
+ }
496
+ else {
497
+ exitCode = 0; // Non-blocking
498
+ status = "error";
499
+ const stack = error instanceof Error ? error.stack ?? "" : "";
500
+ errorInfo = [error instanceof Error ? error : new Error(String(error)), stack];
501
+ }
502
+ }
503
+ _emitHookEnd(hookName, startTime, exitCode, status, errorInfo, startData, event, tool, template);
504
+ process.exit(exitCode);
505
+ }
506
+ /**
507
+ * Async variant of runHook for hooks that need await (e.g., AI inference).
508
+ * Provides identical structured JSONL lifecycle logging as runHook.
509
+ * See SPEC.md §5.7
510
+ */
511
+ export function runHookAsync(mainFunc, hookName = "unknown", prefetchedInput) {
512
+ _earlyReadInput(prefetchedInput);
513
+ _cachedHookName = hookName;
514
+ // Ensure cwd is project root so relative paths in hooks resolve correctly,
515
+ // even when cwd has drifted via `cd` in a Bash tool call.
516
+ try {
517
+ const cwd = _prefetchedInput ? readStringField(_prefetchedInput, "cwd") ?? undefined : undefined;
518
+ const projectRoot = getProjectRoot(cwd);
519
+ if (process.cwd() !== projectRoot)
520
+ process.chdir(projectRoot);
521
+ }
522
+ catch { /* non-fatal — proceed with current cwd */ }
523
+ const startTime = performance.now();
524
+ const template = detectTemplate();
525
+ const event = _lastHookEvent ?? "unknown";
526
+ const tool = _lastToolName;
527
+ const startData = {
528
+ lifecycle: "start",
529
+ template,
530
+ event,
531
+ };
532
+ if (tool)
533
+ startData.tool = tool;
534
+ hookLog("info", hookName, "HOOK_START", { data: startData });
535
+ mainFunc()
536
+ .then((result) => {
537
+ const exitCode = typeof result === "number" ? result : 0;
538
+ _emitHookEnd(hookName, startTime, exitCode, exitCode !== 0 ? "blocked" : "success", null, startData, event, tool, template);
539
+ _drainAndExit(exitCode);
540
+ })
541
+ .catch((error) => {
542
+ let exitCode = 0;
543
+ let status = "error";
544
+ let errorInfo = null;
545
+ if (error instanceof Error && error.message.startsWith("SystemExit:")) {
546
+ const code = parseInt(error.message.slice(11), 10);
547
+ exitCode = isNaN(code) ? (error.message.slice(11) ? 1 : 0) : code;
548
+ status = exitCode !== 0 ? "blocked" : "success";
549
+ }
550
+ else {
551
+ exitCode = 0; // Non-blocking (fail open)
552
+ const stack = error instanceof Error ? error.stack ?? "" : "";
553
+ errorInfo = [error instanceof Error ? error : new Error(String(error)), stack];
554
+ }
555
+ _emitHookEnd(hookName, startTime, exitCode, status, errorInfo, startData, event, tool, template);
556
+ _drainAndExit(exitCode);
557
+ });
558
+ }
559
+ /** Shared HOOK_END logic for runHook and runHookAsync */
560
+ function _emitHookEnd(hookName, startTime, exitCode, status, errorInfo, startData, event, tool, template) {
561
+ // Retroactive HOOK_START to per-context log (context_path resolved after main runs)
562
+ const resolvedAfter = _getContextPath();
563
+ if (resolvedAfter && fs.existsSync(resolvedAfter)) {
564
+ hookLog("info", hookName, "HOOK_START", { data: startData });
565
+ }
566
+ const durationMs = Math.round((performance.now() - startTime) * 10) / 10;
567
+ const endEvent = _lastHookEvent ?? event;
568
+ const endTool = _lastToolName ?? tool;
569
+ const endData = {
570
+ lifecycle: "end",
571
+ status,
572
+ duration_ms: durationMs,
573
+ exit_code: exitCode,
574
+ template,
575
+ event: endEvent,
576
+ };
577
+ if (endTool)
578
+ endData.tool = endTool;
579
+ if (errorInfo) {
580
+ const [err, tb] = errorInfo;
581
+ endData.error_type = err.constructor.name;
582
+ hookLog("error", hookName, `[${endEvent}] ${err.constructor.name}: ${String(err).replaceAll(/[\n\r]/g, " ").slice(0, 200)}`, { traceback_str: tb });
583
+ hookLog("error", hookName, `HOOK_END: ${err}`, { data: endData, traceback_str: tb });
584
+ }
585
+ else if (status === "blocked") {
586
+ hookLog("warn", hookName, "HOOK_END", { data: endData });
587
+ }
588
+ else {
589
+ hookLog("info", hookName, "HOOK_END", { data: endData });
590
+ }
591
+ }
592
+ /**
593
+ * Drain stdout before exiting to ensure pipe consumers receive all data.
594
+ * On Windows, stdout to a pipe is fully buffered — process.exit() can
595
+ * discard unflushed data. This waits for the write buffer to drain.
596
+ */
597
+ function _drainAndExit(code) {
598
+ // If stdout is already finished or not writable, exit immediately
599
+ if (!process.stdout.writable || process.stdout.writableFinished) {
600
+ process.exit(code);
601
+ }
602
+ // Attempt to end stdout and wait for drain
603
+ const timeout = setTimeout(() => process.exit(code), 1000); // safety fallback
604
+ process.stdout.end(() => {
605
+ clearTimeout(timeout);
606
+ process.exit(code);
607
+ });
608
+ }
609
+ export { hookLog, logBlocking, logDebug, logDiagnostic, logError, logHookError, logInfo, logWarn, setContextPath, setSessionId } from "../runtime/logger.js";
@@ -0,0 +1,5 @@
1
+ import type { ContextState, GitState, LastSession } from "../types.js";
2
+ export declare function computePlanFallback(state: ContextState, planContent: string): Partial<ContextState>;
3
+ export declare function shouldStage(state: ContextState, permissionMode: string): boolean;
4
+ export declare function buildSessionMetadata(sessionId: string, source: string, transcriptPath: string | undefined, gitState?: GitState): LastSession;
5
+ export declare function generateArchiveFilename(sessionId: string, date: Date, existingNames: Iterable<string>): string;
@@ -0,0 +1,63 @@
1
+ import * as crypto from "node:crypto";
2
+ import { extractPlanAnchors, generatePlanId, normalizePlanContent, } from "../context/plan-manager.js";
3
+ import { nowIso } from "../runtime/utils.js";
4
+ function formatArchiveTimestamp(date) {
5
+ return (`${date.getFullYear()}-` +
6
+ `${String(date.getMonth() + 1).padStart(2, "0")}-` +
7
+ `${String(date.getDate()).padStart(2, "0")}-` +
8
+ `${String(date.getHours()).padStart(2, "0")}` +
9
+ `${String(date.getMinutes()).padStart(2, "0")}`);
10
+ }
11
+ function resolveArtifactType(state) {
12
+ if (state.next_artifact_type)
13
+ return state.next_artifact_type;
14
+ const hasPlan = Boolean(state.plan_path && state.plan_hash);
15
+ const hasHandoff = Boolean(state.handoff_path);
16
+ if (hasPlan && hasHandoff)
17
+ return "plan";
18
+ if (hasPlan)
19
+ return "plan";
20
+ if (hasHandoff)
21
+ return "handoff";
22
+ return null;
23
+ }
24
+ export function computePlanFallback(state, planContent) {
25
+ const normalized = normalizePlanContent(planContent);
26
+ const planHash = crypto
27
+ .createHash("sha256")
28
+ .update(normalized, "utf8")
29
+ .digest("hex")
30
+ .slice(0, 12);
31
+ return {
32
+ plan_hash: planHash,
33
+ plan_signature: planContent.slice(0, 200),
34
+ plan_id: generatePlanId(),
35
+ plan_anchors: extractPlanAnchors(planContent),
36
+ work_consumed: state.work_consumed ?? false,
37
+ };
38
+ }
39
+ export function shouldStage(state, permissionMode) {
40
+ const artifactType = resolveArtifactType(state);
41
+ const canStage = state.mode === "active" || permissionMode === "plan";
42
+ return Boolean(artifactType && canStage && !state.work_consumed);
43
+ }
44
+ export function buildSessionMetadata(sessionId, source, transcriptPath, gitState) {
45
+ return {
46
+ session_id: sessionId,
47
+ save_reason: source,
48
+ saved_at: nowIso(),
49
+ ...(transcriptPath ? { transcript_path: transcriptPath } : {}),
50
+ git_state: gitState ?? {},
51
+ };
52
+ }
53
+ export function generateArchiveFilename(sessionId, date, existingNames) {
54
+ const existing = new Set(existingNames);
55
+ const timestamp = formatArchiveTimestamp(date);
56
+ let archiveName = `${timestamp}-${sessionId}.jsonl`;
57
+ let counter = 2;
58
+ while (existing.has(archiveName)) {
59
+ archiveName = `${timestamp}-${sessionId}-${counter}.jsonl`;
60
+ counter += 1;
61
+ }
62
+ return archiveName;
63
+ }