aiwcli 0.15.5 → 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,262 @@
1
+ /**
2
+ * TmuxMultiplexer — unified tmux backend.
3
+ * Consolidates TmuxLauncher + launchInTmuxSession + command building from pane-driver.
4
+ */
5
+ import { execSync } from 'node:child_process';
6
+ import * as fs from 'node:fs';
7
+ import { cleanClaudeEnv, getLastLine, spawnAttached, splitFlagFromDimensions } from '../mux-utils.js';
8
+ import { isNonWindowsPlatform, isWindowsPlatform } from '../runtime/platform-adapter.js';
9
+ import { cleanupSentinelIpc, createSentinelIpcPaths } from '../runtime/sentinel-ipc.js';
10
+ import { execFileAsync, findExecutable } from '../runtime/subprocess-utils.js';
11
+ import { wrapSentinelSh } from '../sentinel-wrapper.js';
12
+ import { findBestSplit, listPanes } from '../tmux-pane-placement.js';
13
+ import { quoteForSh, toMsysPosixPath } from '../tmux-primitives.js';
14
+ import { buildShellCommand, buildTmuxRuntimeBootstrapCommands } from '../tmux-session.js';
15
+ /** @internal */
16
+ export function buildEnvPrefix(env) {
17
+ return Object.entries(env)
18
+ .map(([key, value]) => `${key}=${quoteForSh(value)}`)
19
+ .join(' ');
20
+ }
21
+ /** @internal */
22
+ export function buildShToolCommand(params) {
23
+ const { toolPath, args, env, mode, promptPath, promptText } = params;
24
+ const envPrefix = buildEnvPrefix(env);
25
+ const commandArgs = buildCommandArgs(args, mode, promptText);
26
+ const argPart = commandArgs.map((arg) => quoteForSh(arg)).join(' ');
27
+ const base = [envPrefix, quoteForSh(toolPath), argPart]
28
+ .filter(Boolean)
29
+ .join(' ');
30
+ if (mode === 'exec' && promptPath) {
31
+ return `${base} < ${quoteForSh(promptPath)}`;
32
+ }
33
+ return base;
34
+ }
35
+ /** @internal */
36
+ export function buildCommandArgs(args, mode, promptText) {
37
+ if (mode !== 'repl' || promptText === undefined)
38
+ return args;
39
+ return [...args, promptText];
40
+ }
41
+ /** @internal */
42
+ export function withWindowsTmuxBootstrap(command, platform = process.platform) {
43
+ if (platform !== 'win32')
44
+ return command;
45
+ const bootstrap = buildTmuxRuntimeBootstrapCommands(platform).join('; ');
46
+ return `${bootstrap}; ${command}`;
47
+ }
48
+ async function resolveToolForBash(toolName) {
49
+ const bash = findExecutable('bash');
50
+ if (!bash)
51
+ return null;
52
+ const result = await execFileAsync(bash, ['-lc', `command -v ${toolName}`], {
53
+ timeout: 3000,
54
+ env: { ...process.env, MSYS_NO_PATHCONV: '1' },
55
+ });
56
+ return result.exitCode === 0 ? result.stdout.trim() || null : null;
57
+ }
58
+ async function resolveAutoSplit(tmuxPath, splitTarget) {
59
+ const explicitTarget = splitTarget?.trim();
60
+ if (explicitTarget) {
61
+ const size = await execFileAsync(tmuxPath, ['display-message', '-p', '-t', explicitTarget, '#{pane_width} #{pane_height}'], { timeout: 3000 });
62
+ if (size.exitCode === 0) {
63
+ const parts = size.stdout.trim().split(/\s+/);
64
+ if (parts.length >= 2) {
65
+ const width = Number.parseInt(parts[0] ?? '', 10);
66
+ const height = Number.parseInt(parts[1] ?? '', 10);
67
+ if (Number.isFinite(width) && Number.isFinite(height)) {
68
+ return {
69
+ splitFlag: splitFlagFromDimensions(width, height),
70
+ splitTarget: explicitTarget,
71
+ };
72
+ }
73
+ }
74
+ }
75
+ return { splitFlag: '-h', splitTarget: explicitTarget };
76
+ }
77
+ const panes = await listPanes(tmuxPath);
78
+ const placement = findBestSplit(panes);
79
+ if (!placement)
80
+ return { splitFlag: '-h' };
81
+ return {
82
+ splitFlag: placement.splitFlag,
83
+ splitTarget: placement.targetPane,
84
+ };
85
+ }
86
+ /** @internal */
87
+ export function buildTmuxSplitWindowArgs(params) {
88
+ const args = ['split-window', params.splitFlag, '-P', '-F', '#{pane_id}'];
89
+ if (params.cwd) {
90
+ args.push('-c', params.cwd);
91
+ }
92
+ if (params.splitTarget) {
93
+ args.push('-t', params.splitTarget);
94
+ }
95
+ args.push(params.command);
96
+ return args;
97
+ }
98
+ /** @internal */
99
+ export function buildTmuxCreateSessionArgs(params) {
100
+ const args = ['new-session'];
101
+ if (params.reattach)
102
+ args.push('-A');
103
+ args.push('-c', params.cwd, '-s', params.sessionName, params.shellCommand);
104
+ return args;
105
+ }
106
+ export class TmuxMultiplexer {
107
+ backend = 'tmux';
108
+ tmuxPath;
109
+ constructor(tmuxPath) {
110
+ this.tmuxPath = tmuxPath;
111
+ }
112
+ static create() {
113
+ const tmuxPath = findExecutable('tmux');
114
+ if (!tmuxPath)
115
+ return null;
116
+ return new TmuxMultiplexer(tmuxPath);
117
+ }
118
+ async createSession(options) {
119
+ const { sessionName, reattach } = options;
120
+ if (!isNonWindowsPlatform()) {
121
+ return { exitCode: -1, usedMux: false, reason: 'tmux not available on this platform' };
122
+ }
123
+ // Set default-terminal BEFORE session creation (batched into single invocation)
124
+ try {
125
+ execSync(String.raw `tmux start-server \; set -g default-terminal "tmux-256color"`, { stdio: 'ignore', timeout: 3000 });
126
+ }
127
+ catch {
128
+ try {
129
+ execSync(String.raw `tmux start-server \; set -g default-terminal "screen-256color"`, { stdio: 'ignore', timeout: 3000 });
130
+ }
131
+ catch { /* best-effort */ }
132
+ }
133
+ const shellCommand = buildShellCommand({
134
+ sessionName,
135
+ toolPath: options.toolPath,
136
+ toolArgs: options.toolArgs,
137
+ promptText: options.promptText,
138
+ enableMouse: options.enableMouse ?? true,
139
+ });
140
+ const args = buildTmuxCreateSessionArgs({
141
+ sessionName,
142
+ cwd: process.cwd(),
143
+ shellCommand,
144
+ reattach,
145
+ });
146
+ return spawnAttached('tmux', args, cleanClaudeEnv(), this.backend);
147
+ }
148
+ isInsideSession() {
149
+ return Boolean(process.env.TMUX);
150
+ }
151
+ async kill(paneId) {
152
+ if (!paneId)
153
+ return;
154
+ await execFileAsync(this.tmuxPath, ['kill-pane', '-t', paneId], { timeout: 3000 });
155
+ }
156
+ async splitPane(options) {
157
+ const mode = options.mode ?? 'repl';
158
+ const args = options.args ?? [];
159
+ const envVars = options.env ?? {};
160
+ const cwd = options.cwd ?? process.cwd();
161
+ // Resolve tool path
162
+ const toolPath = findExecutable(options.toolName);
163
+ if (!toolPath) {
164
+ return { launched: false, backend: this.backend, reason: `${options.toolName} not found on PATH` };
165
+ }
166
+ // On Windows with tmux backend, resolve tool path from bash's perspective
167
+ let effectiveToolPath = toolPath;
168
+ if (isWindowsPlatform()) {
169
+ const bashPath = await resolveToolForBash(options.toolName);
170
+ if (bashPath) {
171
+ effectiveToolPath = bashPath;
172
+ }
173
+ else {
174
+ return { launched: false, backend: this.backend, reason: `${options.toolName} not found in bash PATH (required for tmux pane)` };
175
+ }
176
+ }
177
+ // Sentinel IPC for exit code tracking
178
+ const useSentinel = options.sentinel !== false;
179
+ const sentinel = useSentinel ? createSentinelIpcPaths(`aiwcli-pane-${options.toolName}`) : null;
180
+ // Inject COLORTERM=truecolor for tmux
181
+ const effectiveEnvVars = { COLORTERM: 'truecolor', ...envVars };
182
+ try {
183
+ const promptText = mode === 'repl' && options.promptPath
184
+ ? fs.readFileSync(options.promptPath, 'utf8')
185
+ : undefined;
186
+ const baseCommand = buildShToolCommand({
187
+ toolPath: effectiveToolPath,
188
+ args,
189
+ env: effectiveEnvVars,
190
+ mode,
191
+ promptText,
192
+ ...(options.promptPath ? { promptPath: options.promptPath } : {}),
193
+ });
194
+ let paneCommand = baseCommand;
195
+ if (sentinel) {
196
+ const holdMessage = options.holdMessage ?? '[aiwcli] Driver exited. Pane held open.';
197
+ paneCommand = wrapSentinelSh({
198
+ command: baseCommand,
199
+ sentinelPath: sentinel.sentinelPath,
200
+ autoClose: Boolean(options.autoClose),
201
+ holdPane: Boolean(options.holdPane),
202
+ holdMessage,
203
+ });
204
+ }
205
+ // Resolve split direction
206
+ const splitDirection = options.split ?? 'auto';
207
+ let splitFlag;
208
+ let splitTarget;
209
+ if (splitDirection === 'auto') {
210
+ try {
211
+ const resolved = await resolveAutoSplit(this.tmuxPath, options.splitTarget);
212
+ splitFlag = resolved.splitFlag;
213
+ splitTarget = resolved.splitTarget;
214
+ }
215
+ catch {
216
+ splitFlag = '-h';
217
+ splitTarget = options.splitTarget?.trim();
218
+ }
219
+ }
220
+ else {
221
+ splitFlag = splitDirection === 'v' ? '-v' : '-h';
222
+ splitTarget = options.splitTarget?.trim();
223
+ }
224
+ // Build tmux split-window command
225
+ const cwdPath = cwd ? (process.platform === 'win32' ? toMsysPosixPath(cwd) : cwd) : undefined;
226
+ const bootstrappedCommand = withWindowsTmuxBootstrap(paneCommand);
227
+ const tmuxArgs = buildTmuxSplitWindowArgs({
228
+ splitFlag,
229
+ command: `bash -lc ${quoteForSh(bootstrappedCommand)}`,
230
+ cwd: cwdPath,
231
+ splitTarget,
232
+ });
233
+ const split = await execFileAsync(this.tmuxPath, tmuxArgs, { timeout: 5000 });
234
+ if (split.exitCode !== 0) {
235
+ if (sentinel)
236
+ cleanupSentinelIpc(sentinel);
237
+ return {
238
+ launched: false,
239
+ backend: this.backend,
240
+ reason: 'tmux split-window failed',
241
+ stderr: split.stderr.trim() || undefined,
242
+ };
243
+ }
244
+ const paneId = getLastLine(split.stdout) || undefined;
245
+ return {
246
+ launched: true,
247
+ backend: this.backend,
248
+ paneId,
249
+ sentinelPath: sentinel?.sentinelPath,
250
+ };
251
+ }
252
+ catch (error) {
253
+ if (sentinel)
254
+ cleanupSentinelIpc(sentinel);
255
+ return {
256
+ launched: false,
257
+ backend: this.backend,
258
+ reason: `pane launch failed: ${String(error)}`,
259
+ };
260
+ }
261
+ }
262
+ }
@@ -0,0 +1,5 @@
1
+ import type { CreateSessionResult } from './multiplexer.js';
2
+ export declare function getLastLine(text: string): string;
3
+ export declare function spawnAttached(command: string, args: string[], env: NodeJS.ProcessEnv | undefined, backendLabel: string): Promise<CreateSessionResult>;
4
+ export declare function splitFlagFromDimensions(width: number, height: number): '-h' | '-v';
5
+ export declare function cleanClaudeEnv(extra?: Record<string, string>): NodeJS.ProcessEnv;
@@ -0,0 +1,42 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { getInternalSubprocessEnv } from './runtime/subprocess-utils.js';
3
+ const CELL_ASPECT_RATIO = 2;
4
+ export function getLastLine(text) {
5
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
6
+ return lines.at(-1) ?? '';
7
+ }
8
+ export function spawnAttached(command, args, env, backendLabel) {
9
+ return new Promise((resolve) => {
10
+ let child;
11
+ try {
12
+ child = spawn(command, args, { stdio: 'inherit', env: env ?? process.env });
13
+ }
14
+ catch (error) {
15
+ resolve({ exitCode: -1, usedMux: false, reason: error instanceof Error ? error.message : String(error) });
16
+ return;
17
+ }
18
+ child.on('error', (error) => {
19
+ resolve({ exitCode: -1, usedMux: false, reason: error.message });
20
+ });
21
+ child.on('close', (code) => {
22
+ if (code === 0) {
23
+ resolve({ exitCode: 0, usedMux: true });
24
+ }
25
+ else {
26
+ resolve({ exitCode: code ?? 1, usedMux: false, reason: `${backendLabel} exited with code ${code ?? 1}` });
27
+ }
28
+ });
29
+ });
30
+ }
31
+ export function splitFlagFromDimensions(width, height) {
32
+ return width >= height * CELL_ASPECT_RATIO ? '-h' : '-v';
33
+ }
34
+ export function cleanClaudeEnv(extra) {
35
+ const env = {
36
+ ...getInternalSubprocessEnv(),
37
+ ...extra,
38
+ };
39
+ delete env['CLAUDECODE'];
40
+ delete env['CLAUDE_CODE_ENTRYPOINT'];
41
+ return env;
42
+ }
@@ -13,14 +13,14 @@ export declare function expandPath(inputPath: string): string;
13
13
  /**
14
14
  * Convert a path to Unix-style forward slashes.
15
15
  * Useful for cross-platform logging or display.
16
- * @param inputPath - Path with any separator style
16
+ * @param inputPath - Path with unknown separator style
17
17
  * @returns Path with forward slashes only
18
18
  */
19
19
  export declare function toUnixPath(inputPath: string): string;
20
20
  /**
21
21
  * Convert a path to Windows-style backslashes.
22
22
  * Useful for cross-platform logging or display.
23
- * @param inputPath - Path with any separator style
23
+ * @param inputPath - Path with unknown separator style
24
24
  * @returns Path with backslashes only
25
25
  */
26
26
  export declare function toWindowsPath(inputPath: string): string;
package/dist/lib/paths.js CHANGED
@@ -31,7 +31,7 @@ export function expandPath(inputPath) {
31
31
  /**
32
32
  * Convert a path to Unix-style forward slashes.
33
33
  * Useful for cross-platform logging or display.
34
- * @param inputPath - Path with any separator style
34
+ * @param inputPath - Path with unknown separator style
35
35
  * @returns Path with forward slashes only
36
36
  */
37
37
  export function toUnixPath(inputPath) {
@@ -40,7 +40,7 @@ export function toUnixPath(inputPath) {
40
40
  /**
41
41
  * Convert a path to Windows-style backslashes.
42
42
  * Useful for cross-platform logging or display.
43
- * @param inputPath - Path with any separator style
43
+ * @param inputPath - Path with unknown separator style
44
44
  * @returns Path with backslashes only
45
45
  */
46
46
  export function toWindowsPath(inputPath) {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Platform-specific hook command adaptation.
3
+ *
4
+ * Claude Code on Windows uses cmd.exe to execute hook commands (Node.js
5
+ * child_process.exec defaults to process.env.ComSpec). Template commands use
6
+ * bash syntax (~/ expansion, env-var prefixes) that cmd.exe can't parse.
7
+ *
8
+ * This module rewrites commands at settings-reconstruction time so they work
9
+ * in cmd.exe while remaining valid in bash on Unix (where it's a no-op).
10
+ */
11
+ /**
12
+ * Resolver path token that appears in all template hook commands.
13
+ * Shared between templates and adapter — changing this requires updating
14
+ * all template settings.json files AND tests.
15
+ */
16
+ export declare const RESOLVER_TOKEN = ".aiwcli/_core/scripts/resolve-run.ts";
17
+ /**
18
+ * Adapt a hook command for the current platform.
19
+ * On Windows: strips bash env prefixes, quotes the resolver path.
20
+ * On Unix: no-op (returns input unchanged).
21
+ */
22
+ export declare function adaptHookCommand(command: string): string;
23
+ /**
24
+ * Post-reconstruction validation. On Windows, asserts no command contains
25
+ * bash-only syntax. Throws with actionable message if validation fails.
26
+ */
27
+ export declare function validateCommandsForPlatform(commands: string[]): void;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Platform-specific hook command adaptation.
3
+ *
4
+ * Claude Code on Windows uses cmd.exe to execute hook commands (Node.js
5
+ * child_process.exec defaults to process.env.ComSpec). Template commands use
6
+ * bash syntax (~/ expansion, env-var prefixes) that cmd.exe can't parse.
7
+ *
8
+ * This module rewrites commands at settings-reconstruction time so they work
9
+ * in cmd.exe while remaining valid in bash on Unix (where it's a no-op).
10
+ */
11
+ /**
12
+ * Resolver path token that appears in all template hook commands.
13
+ * Shared between templates and adapter — changing this requires updating
14
+ * all template settings.json files AND tests.
15
+ */
16
+ export const RESOLVER_TOKEN = '.aiwcli/_core/scripts/resolve-run.ts';
17
+ /** Matches leading bash-style env-var prefixes: KEY=VALUE KEY2= ... */
18
+ const BASH_ENV_PREFIX_RE = /^(?:[A-Z_]+=\S*\s+)+/;
19
+ /**
20
+ * Adapt a hook command for the current platform.
21
+ * On Windows: strips bash env prefixes, quotes the resolver path.
22
+ * On Unix: no-op (returns input unchanged).
23
+ */
24
+ export function adaptHookCommand(command) {
25
+ if (process.platform !== 'win32')
26
+ return command;
27
+ const quotedResolver = `"${RESOLVER_TOKEN}"`;
28
+ let result = command.replace(BASH_ENV_PREFIX_RE, '');
29
+ result = result.replace(RESOLVER_TOKEN, quotedResolver);
30
+ return result;
31
+ }
32
+ /**
33
+ * Post-reconstruction validation. On Windows, asserts no command contains
34
+ * bash-only syntax. Throws with actionable message if validation fails.
35
+ */
36
+ export function validateCommandsForPlatform(commands) {
37
+ if (process.platform !== 'win32')
38
+ return;
39
+ for (const cmd of commands) {
40
+ if (cmd.includes('~/')) {
41
+ throw new Error(`Hook command contains unexpanded ~/: "${cmd}". ` +
42
+ 'All hook commands must use RESOLVER_TOKEN from platform-commands.ts.');
43
+ }
44
+ if (BASH_ENV_PREFIX_RE.test(cmd)) {
45
+ throw new Error(`Hook command contains bash env prefix: "${cmd}". ` +
46
+ 'Move env vars into resolve-run.ts spawn env instead.');
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Helper to discover and invoke the `aiw` CLI binary from template scripts.
3
+ *
4
+ * Reads the resolved binary path from `.aiwcli/.aiw-bin-path` (written by `aiw init`).
5
+ * Falls back to `aiw` on PATH if the file is missing.
6
+ */
7
+ export interface AiwLaunchOptions {
8
+ /** Launch codex instead of claude. */
9
+ codex?: boolean;
10
+ /** Working directory. */
11
+ cwd?: string;
12
+ /** Extra env vars to inject. */
13
+ env?: Record<string, string>;
14
+ /** Return JSON output. */
15
+ json?: boolean;
16
+ /** Path to prompt file. */
17
+ promptPath?: string;
18
+ /** Split direction: auto, h, or v. */
19
+ split?: "auto" | "h" | "v";
20
+ /** Timeout in ms (only relevant with --wait). */
21
+ timeoutMs?: number;
22
+ /** Block until pane exits. */
23
+ wait?: boolean;
24
+ }
25
+ export interface AiwLaunchResult {
26
+ backend: string;
27
+ exitCode: null | number;
28
+ launched: boolean;
29
+ paneId: null | string;
30
+ reason: null | string;
31
+ sentinelPath: null | string;
32
+ }
33
+ /**
34
+ * Shell out to `aiw launch` with structured options.
35
+ * Returns parsed JSON result when --json is used.
36
+ */
37
+ export declare function aiwLaunch(options: AiwLaunchOptions): Promise<AiwLaunchResult>;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Helper to discover and invoke the `aiw` CLI binary from template scripts.
3
+ *
4
+ * Reads the resolved binary path from `.aiwcli/.aiw-bin-path` (written by `aiw init`).
5
+ * Falls back to `aiw` on PATH if the file is missing.
6
+ */
7
+ import * as fs from "node:fs";
8
+ import path from "node:path";
9
+ import { getProjectRoot } from "./constants.js";
10
+ import { execFileAsync } from "./subprocess-utils.js";
11
+ function resolveAiwBin(cwd) {
12
+ const projectRoot = getProjectRoot(cwd ?? process.cwd());
13
+ const binPathFile = path.join(projectRoot, ".aiwcli", ".aiw-bin-path");
14
+ try {
15
+ const binPath = fs.readFileSync(binPathFile, "utf8").trim();
16
+ if (binPath && fs.existsSync(binPath))
17
+ return binPath;
18
+ }
19
+ catch {
20
+ // Fall through to PATH lookup
21
+ }
22
+ return "aiw";
23
+ }
24
+ /**
25
+ * Shell out to `aiw launch` with structured options.
26
+ * Returns parsed JSON result when --json is used.
27
+ */
28
+ export async function aiwLaunch(options) {
29
+ const bin = resolveAiwBin(options.cwd);
30
+ const args = ["launch"];
31
+ if (options.codex)
32
+ args.push("--codex");
33
+ if (options.wait)
34
+ args.push("--wait");
35
+ args.push("--json");
36
+ if (options.split)
37
+ args.push("--split", options.split);
38
+ if (options.env && Object.keys(options.env).length > 0) {
39
+ args.push("--env", JSON.stringify(options.env));
40
+ }
41
+ if (options.promptPath)
42
+ args.push("--prompt-path", options.promptPath);
43
+ const result = await execFileAsync(bin, args, {
44
+ timeout: options.timeoutMs ?? 14_400_000,
45
+ env: process.env,
46
+ shell: process.platform === "win32",
47
+ });
48
+ return parseJsonResult(result);
49
+ }
50
+ function parseJsonResult(result) {
51
+ try {
52
+ const lines = result.stdout.trim().split(/\r?\n/);
53
+ const lastLine = lines.at(-1) ?? "";
54
+ const parsed = JSON.parse(lastLine);
55
+ return {
56
+ launched: Boolean(parsed.launched),
57
+ backend: String(parsed.backend ?? "exec"),
58
+ paneId: parsed.paneId ?? null,
59
+ sentinelPath: parsed.sentinelPath ?? null,
60
+ exitCode: typeof parsed.exitCode === "number" ? parsed.exitCode : null,
61
+ reason: parsed.reason ?? null,
62
+ };
63
+ }
64
+ catch {
65
+ return {
66
+ launched: false,
67
+ backend: "exec",
68
+ paneId: null,
69
+ sentinelPath: null,
70
+ exitCode: result.exitCode,
71
+ reason: `Failed to parse aiw launch output: ${result.stderr || result.stdout}`,
72
+ };
73
+ }
74
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Cross-platform atomic file writes with security.
3
+ * Crash-safe writes by writing to temp file then renaming.
4
+ * NOT for concurrent access — assumes single-session-per-context.
5
+ * See SPEC.md §4
6
+ */
7
+ /**
8
+ * Write file atomically with retry logic.
9
+ * Creates temp file, writes, fsyncs, renames.
10
+ * Returns [success, error].
11
+ * See SPEC.md §4.2
12
+ */
13
+ export declare function atomicWrite(filePath: string, content: string, maxAttempts?: number, backoffMs?: number[], fsync?: boolean): [boolean, null | string];
14
+ /**
15
+ * Append to file with retry logic.
16
+ * For JSONL files where each line is independent.
17
+ * See SPEC.md §4.3
18
+ */
19
+ export declare function atomicAppend(filePath: string, content: string, maxAttempts?: number, backoffMs?: number[], fsync?: boolean): [boolean, null | string];
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Cross-platform atomic file writes with security.
3
+ * Crash-safe writes by writing to temp file then renaming.
4
+ * NOT for concurrent access — assumes single-session-per-context.
5
+ * See SPEC.md §4
6
+ */
7
+ import * as crypto from "node:crypto";
8
+ import * as fs from "node:fs";
9
+ import path from "node:path";
10
+ /**
11
+ * Write file atomically with retry logic.
12
+ * Creates temp file, writes, fsyncs, renames.
13
+ * Returns [success, error].
14
+ * See SPEC.md §4.2
15
+ */
16
+ export function atomicWrite(filePath, content, maxAttempts = 2, backoffMs = [500, 1000], fsync = true) {
17
+ // Ensure parent directory exists
18
+ const dir = path.dirname(filePath);
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ const stem = path.basename(filePath, path.extname(filePath));
21
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
22
+ const tmpName = `.${stem}_${crypto.randomBytes(4).toString("hex")}.tmp`;
23
+ const tmpPath = path.join(dir, tmpName);
24
+ try {
25
+ // Write to temp file
26
+ const fd = fs.openSync(tmpPath, "w");
27
+ try {
28
+ fs.writeSync(fd, content, undefined, "utf8");
29
+ if (fsync)
30
+ fs.fsyncSync(fd);
31
+ }
32
+ finally {
33
+ fs.closeSync(fd);
34
+ }
35
+ // Set restrictive permissions (best-effort)
36
+ try {
37
+ fs.chmodSync(tmpPath, 0o600);
38
+ }
39
+ catch {
40
+ // May fail on some filesystems
41
+ }
42
+ // Atomic rename (cross-platform on modern Node/Bun)
43
+ fs.renameSync(tmpPath, filePath);
44
+ return [true, null];
45
+ }
46
+ catch (error) {
47
+ // Clean up temp file
48
+ try {
49
+ fs.unlinkSync(tmpPath);
50
+ }
51
+ catch {
52
+ // Best-effort cleanup
53
+ }
54
+ if (attempt < maxAttempts - 1) {
55
+ const waitMs = backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? backoffMs.at(-1) ?? 500;
56
+ sleepSync(waitMs);
57
+ }
58
+ else {
59
+ const errType = error?.constructor?.name ?? "Error";
60
+ const errMsg = String(error).split("\n")[0]?.slice(0, 200) ?? "";
61
+ return [false, `${errType}: ${errMsg}`];
62
+ }
63
+ }
64
+ }
65
+ return [false, "Max retry attempts exceeded"];
66
+ }
67
+ /**
68
+ * Append to file with retry logic.
69
+ * For JSONL files where each line is independent.
70
+ * See SPEC.md §4.3
71
+ */
72
+ export function atomicAppend(filePath, content, maxAttempts = 2, backoffMs = [500, 1000], fsync = true) {
73
+ // Ensure parent directory exists
74
+ const dir = path.dirname(filePath);
75
+ fs.mkdirSync(dir, { recursive: true });
76
+ const isNewFile = !fs.existsSync(filePath);
77
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
78
+ try {
79
+ const fd = fs.openSync(filePath, "a");
80
+ try {
81
+ fs.writeSync(fd, content, undefined, "utf8");
82
+ if (fsync)
83
+ fs.fsyncSync(fd);
84
+ }
85
+ finally {
86
+ fs.closeSync(fd);
87
+ }
88
+ // Set permissions on newly created files (best-effort)
89
+ if (isNewFile) {
90
+ try {
91
+ fs.chmodSync(filePath, 0o600);
92
+ }
93
+ catch {
94
+ // May fail on some filesystems
95
+ }
96
+ }
97
+ return [true, null];
98
+ }
99
+ catch (error) {
100
+ if (attempt < maxAttempts - 1) {
101
+ const waitMs = backoffMs[Math.min(attempt, backoffMs.length - 1)] ?? backoffMs.at(-1) ?? 500;
102
+ sleepSync(waitMs);
103
+ }
104
+ else {
105
+ const errType = error?.constructor?.name ?? "Error";
106
+ const errMsg = String(error).split("\n")[0]?.slice(0, 200) ?? "";
107
+ return [false, `${errType}: ${errMsg}`];
108
+ }
109
+ }
110
+ }
111
+ return [false, "Max retry attempts exceeded"];
112
+ }
113
+ /**
114
+ * Synchronous sleep for retry backoff.
115
+ * Uses Atomics.wait() for CPU-friendly blocking instead of busy-wait.
116
+ */
117
+ function sleepSync(ms) {
118
+ const sab = new SharedArrayBuffer(4);
119
+ const i32 = new Int32Array(sab);
120
+ Atomics.wait(i32, 0, 0, ms);
121
+ }