@caupulican/pi-adaptative 0.81.37 → 0.81.39

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 (451) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +1 -1
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +131 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +196 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +308 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +22 -2
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +585 -24
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  13. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +749 -27
  14. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
  15. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
  16. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
  17. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
  18. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
  19. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
  20. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
  21. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
  22. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
  23. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
  24. package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
  25. package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
  26. package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
  27. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
  28. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
  29. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
  30. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
  31. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +5 -5
  32. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
  33. package/dist/cli/args.d.ts +3 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +15 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/core/agent-paths.d.ts +49 -0
  38. package/dist/core/agent-paths.d.ts.map +1 -0
  39. package/dist/core/agent-paths.js +107 -0
  40. package/dist/core/agent-paths.js.map +1 -0
  41. package/dist/core/agent-session-services.d.ts.map +1 -1
  42. package/dist/core/agent-session-services.js +3 -3
  43. package/dist/core/agent-session-services.js.map +1 -1
  44. package/dist/core/agent-session.d.ts +234 -17
  45. package/dist/core/agent-session.d.ts.map +1 -1
  46. package/dist/core/agent-session.js +484 -62
  47. package/dist/core/agent-session.js.map +1 -1
  48. package/dist/core/autonomy/contracts.d.ts +9 -0
  49. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  50. package/dist/core/autonomy/contracts.js.map +1 -1
  51. package/dist/core/autonomy/lane-tracker.d.ts +10 -1
  52. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  53. package/dist/core/autonomy/lane-tracker.js +5 -1
  54. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  55. package/dist/core/background-lane-controller.d.ts +122 -6
  56. package/dist/core/background-lane-controller.d.ts.map +1 -1
  57. package/dist/core/background-lane-controller.js +447 -90
  58. package/dist/core/background-lane-controller.js.map +1 -1
  59. package/dist/core/bash-execution-controller.d.ts +4 -0
  60. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  61. package/dist/core/bash-execution-controller.js +7 -1
  62. package/dist/core/bash-execution-controller.js.map +1 -1
  63. package/dist/core/compaction-support.d.ts +13 -3
  64. package/dist/core/compaction-support.d.ts.map +1 -1
  65. package/dist/core/compaction-support.js +43 -7
  66. package/dist/core/compaction-support.js.map +1 -1
  67. package/dist/core/context/context-audit.d.ts +33 -1
  68. package/dist/core/context/context-audit.d.ts.map +1 -1
  69. package/dist/core/context/context-audit.js +31 -5
  70. package/dist/core/context/context-audit.js.map +1 -1
  71. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  72. package/dist/core/context/sqlite-runtime-index.js +13 -7
  73. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  74. package/dist/core/context-gc.d.ts.map +1 -1
  75. package/dist/core/context-gc.js +16 -1
  76. package/dist/core/context-gc.js.map +1 -1
  77. package/dist/core/context-pipeline.d.ts +26 -4
  78. package/dist/core/context-pipeline.d.ts.map +1 -1
  79. package/dist/core/context-pipeline.js +137 -14
  80. package/dist/core/context-pipeline.js.map +1 -1
  81. package/dist/core/cost-guard.d.ts +19 -3
  82. package/dist/core/cost-guard.d.ts.map +1 -1
  83. package/dist/core/cost-guard.js +18 -3
  84. package/dist/core/cost-guard.js.map +1 -1
  85. package/dist/core/delegation/session-worker-result.d.ts +34 -4
  86. package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
  87. package/dist/core/delegation/session-worker-result.js +64 -4
  88. package/dist/core/delegation/session-worker-result.js.map +1 -1
  89. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  90. package/dist/core/delegation/worker-actions.js +1 -1
  91. package/dist/core/delegation/worker-actions.js.map +1 -1
  92. package/dist/core/delegation/worker-result.d.ts +42 -1
  93. package/dist/core/delegation/worker-result.d.ts.map +1 -1
  94. package/dist/core/delegation/worker-result.js +68 -0
  95. package/dist/core/delegation/worker-result.js.map +1 -1
  96. package/dist/core/extensions/loader.d.ts.map +1 -1
  97. package/dist/core/extensions/loader.js +23 -7
  98. package/dist/core/extensions/loader.js.map +1 -1
  99. package/dist/core/extensions/runner.d.ts.map +1 -1
  100. package/dist/core/extensions/runner.js +1 -0
  101. package/dist/core/extensions/runner.js.map +1 -1
  102. package/dist/core/extensions/types.d.ts +51 -0
  103. package/dist/core/extensions/types.d.ts.map +1 -1
  104. package/dist/core/extensions/types.js.map +1 -1
  105. package/dist/core/goal-loop-controller.d.ts +17 -1
  106. package/dist/core/goal-loop-controller.d.ts.map +1 -1
  107. package/dist/core/goal-loop-controller.js +79 -11
  108. package/dist/core/goal-loop-controller.js.map +1 -1
  109. package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
  110. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  111. package/dist/core/goals/goal-continuation-controller.js +77 -0
  112. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  113. package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
  114. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
  115. package/dist/core/goals/goal-continuation-defaults.js +42 -0
  116. package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
  117. package/dist/core/goals/goal-continuation-prompt.d.ts +4 -0
  118. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  119. package/dist/core/goals/goal-continuation-prompt.js +51 -10
  120. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  121. package/dist/core/goals/goal-runtime-snapshot.d.ts +101 -2
  122. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  123. package/dist/core/goals/goal-runtime-snapshot.js +87 -4
  124. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  125. package/dist/core/goals/goal-state.d.ts +89 -1
  126. package/dist/core/goals/goal-state.d.ts.map +1 -1
  127. package/dist/core/goals/goal-state.js +71 -5
  128. package/dist/core/goals/goal-state.js.map +1 -1
  129. package/dist/core/goals/goal-tool-core.d.ts +56 -2
  130. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  131. package/dist/core/goals/goal-tool-core.js +111 -5
  132. package/dist/core/goals/goal-tool-core.js.map +1 -1
  133. package/dist/core/goals/session-goal-state.d.ts +11 -2
  134. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  135. package/dist/core/goals/session-goal-state.js +32 -17
  136. package/dist/core/goals/session-goal-state.js.map +1 -1
  137. package/dist/core/keybindings.d.ts +10 -0
  138. package/dist/core/keybindings.d.ts.map +1 -1
  139. package/dist/core/keybindings.js +10 -2
  140. package/dist/core/keybindings.js.map +1 -1
  141. package/dist/core/learning/observation-store.d.ts +12 -3
  142. package/dist/core/learning/observation-store.d.ts.map +1 -1
  143. package/dist/core/learning/observation-store.js +30 -15
  144. package/dist/core/learning/observation-store.js.map +1 -1
  145. package/dist/core/learning/skill-curator.d.ts +5 -1
  146. package/dist/core/learning/skill-curator.d.ts.map +1 -1
  147. package/dist/core/learning/skill-curator.js +21 -19
  148. package/dist/core/learning/skill-curator.js.map +1 -1
  149. package/dist/core/local-runtime-controller.d.ts +65 -3
  150. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  151. package/dist/core/local-runtime-controller.js +186 -26
  152. package/dist/core/local-runtime-controller.js.map +1 -1
  153. package/dist/core/memory/providers/file-store.d.ts +1 -1
  154. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  155. package/dist/core/memory/providers/file-store.js +6 -6
  156. package/dist/core/memory/providers/file-store.js.map +1 -1
  157. package/dist/core/model-capability.d.ts +34 -0
  158. package/dist/core/model-capability.d.ts.map +1 -1
  159. package/dist/core/model-capability.js +42 -1
  160. package/dist/core/model-capability.js.map +1 -1
  161. package/dist/core/model-router/tool-escalation.d.ts +15 -0
  162. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  163. package/dist/core/model-router/tool-escalation.js +23 -1
  164. package/dist/core/model-router/tool-escalation.js.map +1 -1
  165. package/dist/core/model-router-controller.d.ts +34 -7
  166. package/dist/core/model-router-controller.d.ts.map +1 -1
  167. package/dist/core/model-router-controller.js +95 -16
  168. package/dist/core/model-router-controller.js.map +1 -1
  169. package/dist/core/models/adaptation-store.d.ts +7 -0
  170. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  171. package/dist/core/models/adaptation-store.js +47 -11
  172. package/dist/core/models/adaptation-store.js.map +1 -1
  173. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  174. package/dist/core/models/default-model-suggestions.js +17 -0
  175. package/dist/core/models/default-model-suggestions.js.map +1 -1
  176. package/dist/core/models/fitness-store.d.ts +3 -0
  177. package/dist/core/models/fitness-store.d.ts.map +1 -1
  178. package/dist/core/models/fitness-store.js +11 -2
  179. package/dist/core/models/fitness-store.js.map +1 -1
  180. package/dist/core/models/llamacpp-runtime.d.ts +180 -0
  181. package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
  182. package/dist/core/models/llamacpp-runtime.js +475 -0
  183. package/dist/core/models/llamacpp-runtime.js.map +1 -0
  184. package/dist/core/models/local-registration.d.ts +40 -0
  185. package/dist/core/models/local-registration.d.ts.map +1 -1
  186. package/dist/core/models/local-registration.js +94 -5
  187. package/dist/core/models/local-registration.js.map +1 -1
  188. package/dist/core/models/local-runtime.d.ts.map +1 -1
  189. package/dist/core/models/local-runtime.js +9 -7
  190. package/dist/core/models/local-runtime.js.map +1 -1
  191. package/dist/core/models/model-ref.d.ts +7 -0
  192. package/dist/core/models/model-ref.d.ts.map +1 -1
  193. package/dist/core/models/model-ref.js +26 -0
  194. package/dist/core/models/model-ref.js.map +1 -1
  195. package/dist/core/models/needle-runtime.d.ts +257 -0
  196. package/dist/core/models/needle-runtime.d.ts.map +1 -0
  197. package/dist/core/models/needle-runtime.js +519 -0
  198. package/dist/core/models/needle-runtime.js.map +1 -0
  199. package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
  200. package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
  201. package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
  202. package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
  203. package/dist/core/package-manager.d.ts.map +1 -1
  204. package/dist/core/package-manager.js +11 -10
  205. package/dist/core/package-manager.js.map +1 -1
  206. package/dist/core/process-matrix/codes.d.ts +72 -0
  207. package/dist/core/process-matrix/codes.d.ts.map +1 -0
  208. package/dist/core/process-matrix/codes.js +15 -0
  209. package/dist/core/process-matrix/codes.js.map +1 -0
  210. package/dist/core/process-matrix/runtime.d.ts +63 -0
  211. package/dist/core/process-matrix/runtime.d.ts.map +1 -0
  212. package/dist/core/process-matrix/runtime.js +310 -0
  213. package/dist/core/process-matrix/runtime.js.map +1 -0
  214. package/dist/core/process-matrix/store.d.ts +22 -0
  215. package/dist/core/process-matrix/store.d.ts.map +1 -0
  216. package/dist/core/process-matrix/store.js +80 -0
  217. package/dist/core/process-matrix/store.js.map +1 -0
  218. package/dist/core/process-matrix/supervisor.d.ts +72 -0
  219. package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
  220. package/dist/core/process-matrix/supervisor.js +130 -0
  221. package/dist/core/process-matrix/supervisor.js.map +1 -0
  222. package/dist/core/profile-registry.d.ts +7 -0
  223. package/dist/core/profile-registry.d.ts.map +1 -1
  224. package/dist/core/profile-registry.js +20 -0
  225. package/dist/core/profile-registry.js.map +1 -1
  226. package/dist/core/python-runtime.d.ts.map +1 -1
  227. package/dist/core/python-runtime.js +3 -3
  228. package/dist/core/python-runtime.js.map +1 -1
  229. package/dist/core/reflection-controller.d.ts +29 -5
  230. package/dist/core/reflection-controller.d.ts.map +1 -1
  231. package/dist/core/reflection-controller.js +215 -126
  232. package/dist/core/reflection-controller.js.map +1 -1
  233. package/dist/core/reload-blockers.d.ts +36 -0
  234. package/dist/core/reload-blockers.d.ts.map +1 -1
  235. package/dist/core/reload-blockers.js +44 -0
  236. package/dist/core/reload-blockers.js.map +1 -1
  237. package/dist/core/resource-loader.d.ts.map +1 -1
  238. package/dist/core/resource-loader.js +8 -7
  239. package/dist/core/resource-loader.js.map +1 -1
  240. package/dist/core/runtime-builder.d.ts +58 -5
  241. package/dist/core/runtime-builder.d.ts.map +1 -1
  242. package/dist/core/runtime-builder.js +209 -25
  243. package/dist/core/runtime-builder.js.map +1 -1
  244. package/dist/core/scout-controller.d.ts +6 -0
  245. package/dist/core/scout-controller.d.ts.map +1 -1
  246. package/dist/core/scout-controller.js +66 -52
  247. package/dist/core/scout-controller.js.map +1 -1
  248. package/dist/core/sdk.d.ts.map +1 -1
  249. package/dist/core/sdk.js +3 -3
  250. package/dist/core/sdk.js.map +1 -1
  251. package/dist/core/session-role.d.ts +31 -0
  252. package/dist/core/session-role.d.ts.map +1 -0
  253. package/dist/core/session-role.js +52 -0
  254. package/dist/core/session-role.js.map +1 -0
  255. package/dist/core/settings-manager.d.ts +42 -3
  256. package/dist/core/settings-manager.d.ts.map +1 -1
  257. package/dist/core/settings-manager.js +94 -60
  258. package/dist/core/settings-manager.js.map +1 -1
  259. package/dist/core/system-prompt-builder.d.ts +12 -0
  260. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  261. package/dist/core/system-prompt-builder.js +6 -0
  262. package/dist/core/system-prompt-builder.js.map +1 -1
  263. package/dist/core/system-prompt.d.ts +14 -1
  264. package/dist/core/system-prompt.d.ts.map +1 -1
  265. package/dist/core/system-prompt.js +20 -3
  266. package/dist/core/system-prompt.js.map +1 -1
  267. package/dist/core/tasks/session-task-state.d.ts +11 -2
  268. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  269. package/dist/core/tasks/session-task-state.js +27 -11
  270. package/dist/core/tasks/session-task-state.js.map +1 -1
  271. package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
  272. package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
  273. package/dist/core/tasks/task-contract-monitor.js +56 -0
  274. package/dist/core/tasks/task-contract-monitor.js.map +1 -0
  275. package/dist/core/tasks/task-state.d.ts +8 -0
  276. package/dist/core/tasks/task-state.d.ts.map +1 -1
  277. package/dist/core/tasks/task-state.js +28 -1
  278. package/dist/core/tasks/task-state.js.map +1 -1
  279. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  280. package/dist/core/tool-gate-controller.js +5 -0
  281. package/dist/core/tool-gate-controller.js.map +1 -1
  282. package/dist/core/tool-recovery-log-records.d.ts +7 -0
  283. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  284. package/dist/core/tool-recovery-log-records.js +14 -6
  285. package/dist/core/tool-recovery-log-records.js.map +1 -1
  286. package/dist/core/tool-selection/promotion.d.ts +54 -0
  287. package/dist/core/tool-selection/promotion.d.ts.map +1 -0
  288. package/dist/core/tool-selection/promotion.js +81 -0
  289. package/dist/core/tool-selection/promotion.js.map +1 -0
  290. package/dist/core/tool-selection/tool-performance-store.d.ts +37 -0
  291. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  292. package/dist/core/tool-selection/tool-performance-store.js +87 -3
  293. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  294. package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
  295. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  296. package/dist/core/tool-selection/tool-selection-controller.js +96 -0
  297. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  298. package/dist/core/tools/bash.d.ts +18 -0
  299. package/dist/core/tools/bash.d.ts.map +1 -1
  300. package/dist/core/tools/bash.js +97 -18
  301. package/dist/core/tools/bash.js.map +1 -1
  302. package/dist/core/tools/delegate-status.d.ts +14 -0
  303. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  304. package/dist/core/tools/delegate-status.js +82 -7
  305. package/dist/core/tools/delegate-status.js.map +1 -1
  306. package/dist/core/tools/delegate.d.ts.map +1 -1
  307. package/dist/core/tools/delegate.js +25 -8
  308. package/dist/core/tools/delegate.js.map +1 -1
  309. package/dist/core/tools/find.d.ts.map +1 -1
  310. package/dist/core/tools/find.js +52 -44
  311. package/dist/core/tools/find.js.map +1 -1
  312. package/dist/core/tools/goal.d.ts +94 -3
  313. package/dist/core/tools/goal.d.ts.map +1 -1
  314. package/dist/core/tools/goal.js +165 -15
  315. package/dist/core/tools/goal.js.map +1 -1
  316. package/dist/core/tools/grep.d.ts.map +1 -1
  317. package/dist/core/tools/grep.js +5 -4
  318. package/dist/core/tools/grep.js.map +1 -1
  319. package/dist/core/tools/model-fitness.d.ts +7 -0
  320. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  321. package/dist/core/tools/model-fitness.js +2 -2
  322. package/dist/core/tools/model-fitness.js.map +1 -1
  323. package/dist/core/tools/render-utils.d.ts.map +1 -1
  324. package/dist/core/tools/render-utils.js +1 -1
  325. package/dist/core/tools/render-utils.js.map +1 -1
  326. package/dist/core/tools/shell-contract-router.d.ts +6 -1
  327. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  328. package/dist/core/tools/shell-contract-router.js +69 -13
  329. package/dist/core/tools/shell-contract-router.js.map +1 -1
  330. package/dist/core/tools/shell-session.d.ts +89 -0
  331. package/dist/core/tools/shell-session.d.ts.map +1 -0
  332. package/dist/core/tools/shell-session.js +432 -0
  333. package/dist/core/tools/shell-session.js.map +1 -0
  334. package/dist/core/tools/task-steps.d.ts +4 -0
  335. package/dist/core/tools/task-steps.d.ts.map +1 -1
  336. package/dist/core/tools/task-steps.js +63 -8
  337. package/dist/core/tools/task-steps.js.map +1 -1
  338. package/dist/core/tools/tmux-dispatch.d.ts +86 -0
  339. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
  340. package/dist/core/tools/tmux-dispatch.js +91 -0
  341. package/dist/core/tools/tmux-dispatch.js.map +1 -0
  342. package/dist/core/tools/windows-shell-engine.d.ts +42 -0
  343. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
  344. package/dist/core/tools/windows-shell-engine.js +153 -0
  345. package/dist/core/tools/windows-shell-engine.js.map +1 -0
  346. package/dist/core/tools/windows-shell-state.d.ts +40 -0
  347. package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
  348. package/dist/core/tools/windows-shell-state.js +59 -0
  349. package/dist/core/tools/windows-shell-state.js.map +1 -0
  350. package/dist/core/tools/worktree-sync.d.ts +24 -0
  351. package/dist/core/tools/worktree-sync.d.ts.map +1 -0
  352. package/dist/core/tools/worktree-sync.js +338 -0
  353. package/dist/core/tools/worktree-sync.js.map +1 -0
  354. package/dist/core/trust-manager.d.ts +4 -1
  355. package/dist/core/trust-manager.d.ts.map +1 -1
  356. package/dist/core/trust-manager.js +20 -2
  357. package/dist/core/trust-manager.js.map +1 -1
  358. package/dist/core/util/atomic-file.d.ts +55 -0
  359. package/dist/core/util/atomic-file.d.ts.map +1 -0
  360. package/dist/core/util/atomic-file.js +255 -0
  361. package/dist/core/util/atomic-file.js.map +1 -0
  362. package/dist/core/util/minimatch-cache.d.ts +33 -0
  363. package/dist/core/util/minimatch-cache.d.ts.map +1 -0
  364. package/dist/core/util/minimatch-cache.js +0 -0
  365. package/dist/core/util/minimatch-cache.js.map +1 -0
  366. package/dist/core/worktree-sync/codes.d.ts +227 -0
  367. package/dist/core/worktree-sync/codes.d.ts.map +1 -0
  368. package/dist/core/worktree-sync/codes.js +14 -0
  369. package/dist/core/worktree-sync/codes.js.map +1 -0
  370. package/dist/core/worktree-sync/git-engine.d.ts +156 -0
  371. package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
  372. package/dist/core/worktree-sync/git-engine.js +1191 -0
  373. package/dist/core/worktree-sync/git-engine.js.map +1 -0
  374. package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
  375. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
  376. package/dist/core/worktree-sync/lane-gate.js +360 -0
  377. package/dist/core/worktree-sync/lane-gate.js.map +1 -0
  378. package/dist/core/worktree-sync/runtime.d.ts +47 -0
  379. package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
  380. package/dist/core/worktree-sync/runtime.js +96 -0
  381. package/dist/core/worktree-sync/runtime.js.map +1 -0
  382. package/dist/core/worktree-sync/store.d.ts +69 -0
  383. package/dist/core/worktree-sync/store.d.ts.map +1 -0
  384. package/dist/core/worktree-sync/store.js +247 -0
  385. package/dist/core/worktree-sync/store.js.map +1 -0
  386. package/dist/core/worktree-sync/watcher.d.ts +29 -0
  387. package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
  388. package/dist/core/worktree-sync/watcher.js +93 -0
  389. package/dist/core/worktree-sync/watcher.js.map +1 -0
  390. package/dist/main.d.ts.map +1 -1
  391. package/dist/main.js +94 -0
  392. package/dist/main.js.map +1 -1
  393. package/dist/migrations.d.ts +9 -0
  394. package/dist/migrations.d.ts.map +1 -1
  395. package/dist/migrations.js +38 -0
  396. package/dist/migrations.js.map +1 -1
  397. package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
  398. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  399. package/dist/modes/interactive/auto-learn-controller.js +50 -8
  400. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  401. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  402. package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
  403. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  404. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  405. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  406. package/dist/modes/interactive/interactive-mode.js +4 -0
  407. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  408. package/dist/modes/interactive/local-model-commands.d.ts +43 -0
  409. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  410. package/dist/modes/interactive/local-model-commands.js +290 -3
  411. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  412. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  413. package/dist/modes/interactive/session-flow-commands.js +24 -4
  414. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  415. package/dist/utils/fs-watch.d.ts +11 -0
  416. package/dist/utils/fs-watch.d.ts.map +1 -1
  417. package/dist/utils/fs-watch.js +20 -2
  418. package/dist/utils/fs-watch.js.map +1 -1
  419. package/dist/utils/highlight-js-languages.d.ts +4 -0
  420. package/dist/utils/highlight-js-languages.d.ts.map +1 -0
  421. package/dist/utils/highlight-js-languages.js +573 -0
  422. package/dist/utils/highlight-js-languages.js.map +1 -0
  423. package/dist/utils/shell.d.ts +7 -1
  424. package/dist/utils/shell.d.ts.map +1 -1
  425. package/dist/utils/shell.js +39 -9
  426. package/dist/utils/shell.js.map +1 -1
  427. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  428. package/dist/utils/syntax-highlight.js +53 -5
  429. package/dist/utils/syntax-highlight.js.map +1 -1
  430. package/dist/utils/tools-manager.d.ts.map +1 -1
  431. package/dist/utils/tools-manager.js +112 -1
  432. package/dist/utils/tools-manager.js.map +1 -1
  433. package/docs/development.md +2 -0
  434. package/docs/packages.md +1 -1
  435. package/docs/process-matrix.md +120 -0
  436. package/docs/settings.md +5 -2
  437. package/docs/tmux-agent-manager.md +85 -2
  438. package/docs/windows.md +52 -3
  439. package/docs/work-directory.md +29 -0
  440. package/docs/worktree-sync.md +250 -0
  441. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  442. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  443. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  444. package/examples/extensions/sandbox/package-lock.json +2 -2
  445. package/examples/extensions/sandbox/package.json +1 -1
  446. package/examples/extensions/with-deps/package-lock.json +2 -2
  447. package/examples/extensions/with-deps/package.json +1 -1
  448. package/npm-shrinkwrap.json +12 -12
  449. package/package.json +10 -4
  450. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
  451. package/docs/integration-sweep-resume-2026-07-09.md +0 -407
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-file.d.ts","sourceRoot":"","sources":["../../../src/core/util/atomic-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiEH,MAAM,WAAW,qBAAqB;IACrC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sGAAsG;IACtG,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AA2ED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,CAAC,CAcrG;AAED,0GAA0G;AAC1G,wBAAsB,YAAY,CAAC,CAAC,EACnC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EACxB,OAAO,CAAC,EAAE,qBAAqB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAiBZ;AA6BD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAY3E;AAED,wDAAwD;AACxD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYtF","sourcesContent":["/**\n * Shared atomic-file helper: a single lock+tmp+rename implementation reused by every\n * on-disk store that does a read-modify-write against a JSON/text file. Modeled on the pattern\n * already proven in `memory/providers/file-store.ts` (proper-lockfile advisory lock + write-tmp-\n * then-rename); before this helper existed the pattern was copy-pasted per store, and most copies\n * were missing either the lock, the atomic write, or both.\n *\n * Two call shapes:\n * - `withFileLock(Sync)` — hold an exclusive advisory lock across an arbitrary read-modify-write\n * callback. The lock spans BOTH the read and the write, closing the classic RMW race where two\n * writers each read the old content before either writes back.\n * - `writeFileAtomic(Sync)` — write-tmp-then-rename. Used INSIDE a `withFileLock` callback (or\n * standalone, for a pure overwrite that has no read step to race).\n *\n * Sync and async variants are both exported. Most existing stores expose a synchronous public API\n * called from hot, non-async paths (e.g. a per-token-stream perf sample), so the sync variant lets\n * them gain locking without forcing an async ripple through their callers; the async variant is for\n * call sites that are already async.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { promises as fsPromises, mkdirSync, renameSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\n\n/**\n * Bounded rename retry budget for the win32-only transient-rename handling below (see\n * {@link isTransientRenameErrorOnWin32}). Backoff doubles each attempt starting at\n * {@link RENAME_RETRY_MIN_TIMEOUT_MS} and capped at {@link RENAME_RETRY_MAX_TIMEOUT_MS}, mirroring\n * the doubling-backoff shape used for lock acquisition above. With {@link RENAME_RETRY_ATTEMPTS}\n * extra attempts (10, 20, 40, 80, ... capped at 200) the worst-case total wait is ~1.03s\n * (10+20+40+80+160+200+200+200+200 ~= 1110ms across 9 gaps), which comfortably covers the\n * millisecond-scale window Defender/the Windows Search indexer hold a freshly-written file open\n * without FILE_SHARE_DELETE before releasing it.\n */\nconst RENAME_RETRY_ATTEMPTS = 9;\nconst RENAME_RETRY_MIN_TIMEOUT_MS = 10;\nconst RENAME_RETRY_MAX_TIMEOUT_MS = 200;\n\n/**\n * On win32, antivirus (e.g. Windows Defender's real-time scanner) and the Windows Search indexer\n * routinely open a freshly-written file for a brief scan without `FILE_SHARE_DELETE`, which makes a\n * rename over/of that file fail transiently with EPERM/EACCES/EBUSY for a few milliseconds. This is\n * a well-documented platform semantic, not a bug in our write path — it's exactly why the\n * `graceful-fs` package ships its own win32 rename-retry wrapper. POSIX platforms never exhibit this\n * transient (a POSIX rename either succeeds or fails for a real, non-transient reason), so retry is\n * gated strictly to win32 to keep POSIX behavior byte-identical to a bare rename.\n */\nfunction isTransientRenameErrorOnWin32(err: unknown): boolean {\n\tif (process.platform !== \"win32\") return false;\n\tif (typeof err !== \"object\" || err === null) return false;\n\tconst code = (err as { code?: string }).code;\n\treturn code === \"EPERM\" || code === \"EACCES\" || code === \"EBUSY\";\n}\n\n/** Sync counterpart of the rename-retry policy; see {@link isTransientRenameErrorOnWin32}. */\nfunction renameSyncWithRetry(tmpPath: string, filePath: string): void {\n\tfor (let attempt = 0; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {\n\t\ttry {\n\t\t\trenameSync(tmpPath, filePath);\n\t\t\treturn;\n\t\t} catch (err) {\n\t\t\tif (!isTransientRenameErrorOnWin32(err) || attempt === RENAME_RETRY_ATTEMPTS) throw err;\n\t\t\tconst backoffMs = Math.min(RENAME_RETRY_MIN_TIMEOUT_MS * 2 ** attempt, RENAME_RETRY_MAX_TIMEOUT_MS);\n\t\t\tblockingSleepMs(backoffMs);\n\t\t}\n\t}\n}\n\n/** Async counterpart of the rename-retry policy; see {@link isTransientRenameErrorOnWin32}. */\nasync function renameWithRetry(tmpPath: string, filePath: string): Promise<void> {\n\tfor (let attempt = 0; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {\n\t\ttry {\n\t\t\tawait fsPromises.rename(tmpPath, filePath);\n\t\t\treturn;\n\t\t} catch (err) {\n\t\t\tif (!isTransientRenameErrorOnWin32(err) || attempt === RENAME_RETRY_ATTEMPTS) throw err;\n\t\t\tconst backoffMs = Math.min(RENAME_RETRY_MIN_TIMEOUT_MS * 2 ** attempt, RENAME_RETRY_MAX_TIMEOUT_MS);\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, backoffMs));\n\t\t}\n\t}\n}\n\nexport interface AtomicFileLockOptions {\n\t/**\n\t * Bounded retry attempts while waiting for a lock already held elsewhere. Both variants use a\n\t * SHORT, capped backoff (see {@link RETRY_MIN_TIMEOUT_MS}/{@link RETRY_MAX_TIMEOUT_MS}) — these\n\t * stores' critical sections are sub-millisecond reads+writes of small JSON/text files, so\n\t * contention should clear in milliseconds, not the multi-second-to-31-second worst case\n\t * proper-lockfile's OWN default backoff produces for a bare numeric `retries` (its default\n\t * `minTimeout` is 1000ms with factor 2 — see node_modules/retry/lib/retry.js). Passing a bare\n\t * number straight through would turn brief contention into a multi-second stall on a hot path\n\t * (e.g. a per-token-stream perf sample), so both variants instead build an explicit short-backoff\n\t * `retry` options object.\n\t * - Async (`withFileLock`): forwarded as `{retries, minTimeout, maxTimeout}` to proper-lockfile.\n\t * - Sync (`withFileLockSync`): proper-lockfile's sync API REJECTS `retries > 0` outright (it\n\t * requires the whole acquire flow to be synchronous — see proper-lockfile/lib/adapter.js\n\t * `toSyncOptions`, which throws `ESYNC`). So the sync path implements its own bounded retry\n\t * around single `lockfile.lockSync` attempts, blocking briefly between them (Atomics.wait) —\n\t * callers are already fully synchronous fs code, so a short blocking wait on contention matches\n\t * the existing execution model rather than introducing a new one.\n\t */\n\tretries?: number;\n\t/** Resolve symlinks before locking (proper-lockfile `realpath`); false matches file-store.ts. */\n\trealpath?: boolean;\n\t/** Lock staleness window in ms (proper-lockfile `stale`); omitted = proper-lockfile's own default. */\n\tstale?: number;\n}\n\nconst DEFAULT_RETRIES = 10;\nconst DEFAULT_REALPATH = false;\n/**\n * Capped doubling backoff shared by both variants — see {@link AtomicFileLockOptions.retries}.\n * With the current defaults (10 retries, 25ms floor, 500ms cap) the total worst-case contention\n * budget is ~3.3s (25+50+100+200+400+500+500+500+500+500), comfortably over the >= 2s floor this\n * needs to survive a loaded CI runner (e.g. the Windows CI mkdir/rmdir lock-directory churn under\n * two-real-OS-thread contention).\n */\nconst RETRY_MIN_TIMEOUT_MS = 25;\nconst RETRY_MAX_TIMEOUT_MS = 500;\nfunction ensureLockDirSync(filePath: string): void {\n\tmkdirSync(dirname(filePath), { recursive: true });\n}\n\nasync function ensureLockDir(filePath: string): Promise<void> {\n\tawait fsPromises.mkdir(dirname(filePath), { recursive: true });\n}\n\nfunction isLockedError(err: unknown): boolean {\n\tif (typeof err !== \"object\" || err === null) return false;\n\tconst code = (err as { code?: string }).code;\n\tif (code === \"ELOCKED\") return true;\n\t// On win32, mkdir-ing the lock directory can transiently surface EPERM (rather than the\n\t// expected EEXIST) when a previous incarnation of that directory is concurrently being\n\t// rmdir'd by the racing releaser — the mkdir lands mid-teardown and the OS reports \"operation\n\t// not permitted\" instead of \"already exists\". This is contention, not a real permissions\n\t// failure, so treat it identically to ELOCKED: retry with the existing backoff rather than\n\t// letting it escape as fatal. POSIX platforms don't exhibit this transient and keep surfacing\n\t// real EPERM (e.g. an actually unwritable directory) as fatal.\n\tif (code === \"EPERM\" && process.platform === \"win32\") return true;\n\treturn false;\n}\n\n/** Block the calling thread for `ms` without spinning the CPU (Atomics.wait on a private buffer). */\nfunction blockingSleepMs(ms: number): void {\n\tAtomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\n/**\n * Acquire a proper-lockfile sync lock with bounded retry on `ELOCKED` (proper-lockfile's sync API\n * itself forbids `retries > 0`; see {@link AtomicFileLockOptions.retries}).\n *\n * Backoff doubles each attempt (mirroring the `retry` module's own `factor: 2` default that the\n * async path gets for free from proper-lockfile), starting at {@link RETRY_MIN_TIMEOUT_MS} and\n * capped at {@link RETRY_MAX_TIMEOUT_MS}.\n *\n * This is budget PARITY between two implementations of the same lock-acquisition contract, not\n * flake-masking: `options.retries` (a count of RETRIES, i.e. attempts after the first) means the\n * same thing on both paths, so both must make `retries + 1` total acquisition attempts with the\n * same per-gap backoff schedule. The async path gets `retries + 1` attempts for free from the\n * `retry` module (it retries `options.retries` times after its initial attempt); the sync path\n * used to loop exactly `attempts` times total (one fewer gap, and one fewer attempt than the\n * async path for the same `retries` value), which under-budgeted it relative to its async sibling\n * and surfaced as spurious ELOCKED failures under real contention (e.g. two OS threads hammering\n * the same file, or mkdir/rmdir lock-directory churn on a loaded Windows CI runner).\n */\nfunction lockSyncWithRetry(filePath: string, options: AtomicFileLockOptions): () => void {\n\tconst attempts = Math.max(1, options.retries ?? DEFAULT_RETRIES) + 1;\n\tconst lockOptions = { realpath: options.realpath ?? DEFAULT_REALPATH, stale: options.stale };\n\tfor (let attempt = 1; attempt <= attempts; attempt++) {\n\t\ttry {\n\t\t\treturn lockfile.lockSync(filePath, lockOptions);\n\t\t} catch (err) {\n\t\t\tif (!isLockedError(err) || attempt === attempts) throw err;\n\t\t\tconst backoffMs = Math.min(RETRY_MIN_TIMEOUT_MS * 2 ** (attempt - 1), RETRY_MAX_TIMEOUT_MS);\n\t\t\tblockingSleepMs(backoffMs);\n\t\t}\n\t}\n\t// Unreachable (the loop always returns or throws), but keeps the function's return type honest.\n\tthrow new Error(`Failed to acquire lock for ${filePath}`);\n}\n\n/**\n * Hold an exclusive advisory lock on `filePath` for the duration of `fn` (sync). Always releases,\n * including when `fn` throws.\n */\nexport function withFileLockSync<T>(filePath: string, fn: () => T, options?: AtomicFileLockOptions): T {\n\tensureLockDirSync(filePath);\n\tconst release = lockSyncWithRetry(filePath, options ?? {});\n\ttry {\n\t\treturn fn();\n\t} finally {\n\t\t// A lock-cleanup failure must never mask fn()'s result (or replace fn()'s own thrown error) —\n\t\t// by this point fn() has already durably committed or failed on its own terms.\n\t\ttry {\n\t\t\trelease();\n\t\t} catch {\n\t\t\t// best-effort cleanup; a stale lock self-expires via proper-lockfile's `stale` window\n\t\t}\n\t}\n}\n\n/** Async counterpart of {@link withFileLockSync}. Always releases, including when `fn` throws/rejects. */\nexport async function withFileLock<T>(\n\tfilePath: string,\n\tfn: () => Promise<T> | T,\n\toptions?: AtomicFileLockOptions,\n): Promise<T> {\n\tawait ensureLockDir(filePath);\n\tconst release = await lockfile.lock(filePath, {\n\t\trealpath: options?.realpath ?? DEFAULT_REALPATH,\n\t\tretries: {\n\t\t\tretries: options?.retries ?? DEFAULT_RETRIES,\n\t\t\tminTimeout: RETRY_MIN_TIMEOUT_MS,\n\t\t\tmaxTimeout: RETRY_MAX_TIMEOUT_MS,\n\t\t},\n\t\tstale: options?.stale,\n\t});\n\ttry {\n\t\treturn await fn();\n\t} finally {\n\t\t// See {@link withFileLockSync} — cleanup failures must not mask fn()'s outcome.\n\t\tawait release().catch(() => {});\n\t}\n}\n\n/**\n * Write `content` to `filePath` via write-tmp-then-rename (sync); the rename is atomic on the same\n * filesystem, so a concurrent reader never observes a partially-written file. Does NOT itself lock —\n * call from inside {@link withFileLockSync} when the write follows a read that must not race another\n * writer's read+write; call standalone for an unconditional overwrite with no read step.\n */\nfunction temporaryPath(filePath: string): string {\n\treturn `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n}\n\n/** Remove one invocation's temporary path without masking the original write/rename failure. */\nfunction removeTemporaryPathSync(tmpPath: string): void {\n\ttry {\n\t\tunlinkSync(tmpPath);\n\t} catch {\n\t\t// The rename may have consumed the path, or another failure may have prevented its creation.\n\t}\n}\n\nasync function removeTemporaryPath(tmpPath: string): Promise<void> {\n\ttry {\n\t\tawait fsPromises.unlink(tmpPath);\n\t} catch {\n\t\t// The rename may have consumed the path, or another failure may have prevented its creation.\n\t}\n}\n\nexport function writeFileAtomicSync(filePath: string, content: string): void {\n\tmkdirSync(dirname(filePath), { recursive: true });\n\tconst tmpPath = temporaryPath(filePath);\n\tlet renamed = false;\n\ttry {\n\t\t// `wx` makes a nonce collision harmless: never truncate another writer's temporary file.\n\t\twriteFileSync(tmpPath, content, { encoding: \"utf-8\", flag: \"wx\" });\n\t\trenameSyncWithRetry(tmpPath, filePath);\n\t\trenamed = true;\n\t} finally {\n\t\tif (!renamed) removeTemporaryPathSync(tmpPath);\n\t}\n}\n\n/** Async counterpart of {@link writeFileAtomicSync}. */\nexport async function writeFileAtomic(filePath: string, content: string): Promise<void> {\n\tawait fsPromises.mkdir(dirname(filePath), { recursive: true });\n\tconst tmpPath = temporaryPath(filePath);\n\tlet renamed = false;\n\ttry {\n\t\t// `wx` makes a nonce collision harmless: never truncate another writer's temporary file.\n\t\tawait fsPromises.writeFile(tmpPath, content, { encoding: \"utf-8\", flag: \"wx\" });\n\t\tawait renameWithRetry(tmpPath, filePath);\n\t\trenamed = true;\n\t} finally {\n\t\tif (!renamed) await removeTemporaryPath(tmpPath);\n\t}\n}\n"]}
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Shared atomic-file helper: a single lock+tmp+rename implementation reused by every
3
+ * on-disk store that does a read-modify-write against a JSON/text file. Modeled on the pattern
4
+ * already proven in `memory/providers/file-store.ts` (proper-lockfile advisory lock + write-tmp-
5
+ * then-rename); before this helper existed the pattern was copy-pasted per store, and most copies
6
+ * were missing either the lock, the atomic write, or both.
7
+ *
8
+ * Two call shapes:
9
+ * - `withFileLock(Sync)` — hold an exclusive advisory lock across an arbitrary read-modify-write
10
+ * callback. The lock spans BOTH the read and the write, closing the classic RMW race where two
11
+ * writers each read the old content before either writes back.
12
+ * - `writeFileAtomic(Sync)` — write-tmp-then-rename. Used INSIDE a `withFileLock` callback (or
13
+ * standalone, for a pure overwrite that has no read step to race).
14
+ *
15
+ * Sync and async variants are both exported. Most existing stores expose a synchronous public API
16
+ * called from hot, non-async paths (e.g. a per-token-stream perf sample), so the sync variant lets
17
+ * them gain locking without forcing an async ripple through their callers; the async variant is for
18
+ * call sites that are already async.
19
+ */
20
+ import { randomUUID } from "node:crypto";
21
+ import { promises as fsPromises, mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
22
+ import { dirname } from "node:path";
23
+ import lockfile from "proper-lockfile";
24
+ /**
25
+ * Bounded rename retry budget for the win32-only transient-rename handling below (see
26
+ * {@link isTransientRenameErrorOnWin32}). Backoff doubles each attempt starting at
27
+ * {@link RENAME_RETRY_MIN_TIMEOUT_MS} and capped at {@link RENAME_RETRY_MAX_TIMEOUT_MS}, mirroring
28
+ * the doubling-backoff shape used for lock acquisition above. With {@link RENAME_RETRY_ATTEMPTS}
29
+ * extra attempts (10, 20, 40, 80, ... capped at 200) the worst-case total wait is ~1.03s
30
+ * (10+20+40+80+160+200+200+200+200 ~= 1110ms across 9 gaps), which comfortably covers the
31
+ * millisecond-scale window Defender/the Windows Search indexer hold a freshly-written file open
32
+ * without FILE_SHARE_DELETE before releasing it.
33
+ */
34
+ const RENAME_RETRY_ATTEMPTS = 9;
35
+ const RENAME_RETRY_MIN_TIMEOUT_MS = 10;
36
+ const RENAME_RETRY_MAX_TIMEOUT_MS = 200;
37
+ /**
38
+ * On win32, antivirus (e.g. Windows Defender's real-time scanner) and the Windows Search indexer
39
+ * routinely open a freshly-written file for a brief scan without `FILE_SHARE_DELETE`, which makes a
40
+ * rename over/of that file fail transiently with EPERM/EACCES/EBUSY for a few milliseconds. This is
41
+ * a well-documented platform semantic, not a bug in our write path — it's exactly why the
42
+ * `graceful-fs` package ships its own win32 rename-retry wrapper. POSIX platforms never exhibit this
43
+ * transient (a POSIX rename either succeeds or fails for a real, non-transient reason), so retry is
44
+ * gated strictly to win32 to keep POSIX behavior byte-identical to a bare rename.
45
+ */
46
+ function isTransientRenameErrorOnWin32(err) {
47
+ if (process.platform !== "win32")
48
+ return false;
49
+ if (typeof err !== "object" || err === null)
50
+ return false;
51
+ const code = err.code;
52
+ return code === "EPERM" || code === "EACCES" || code === "EBUSY";
53
+ }
54
+ /** Sync counterpart of the rename-retry policy; see {@link isTransientRenameErrorOnWin32}. */
55
+ function renameSyncWithRetry(tmpPath, filePath) {
56
+ for (let attempt = 0; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {
57
+ try {
58
+ renameSync(tmpPath, filePath);
59
+ return;
60
+ }
61
+ catch (err) {
62
+ if (!isTransientRenameErrorOnWin32(err) || attempt === RENAME_RETRY_ATTEMPTS)
63
+ throw err;
64
+ const backoffMs = Math.min(RENAME_RETRY_MIN_TIMEOUT_MS * 2 ** attempt, RENAME_RETRY_MAX_TIMEOUT_MS);
65
+ blockingSleepMs(backoffMs);
66
+ }
67
+ }
68
+ }
69
+ /** Async counterpart of the rename-retry policy; see {@link isTransientRenameErrorOnWin32}. */
70
+ async function renameWithRetry(tmpPath, filePath) {
71
+ for (let attempt = 0; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {
72
+ try {
73
+ await fsPromises.rename(tmpPath, filePath);
74
+ return;
75
+ }
76
+ catch (err) {
77
+ if (!isTransientRenameErrorOnWin32(err) || attempt === RENAME_RETRY_ATTEMPTS)
78
+ throw err;
79
+ const backoffMs = Math.min(RENAME_RETRY_MIN_TIMEOUT_MS * 2 ** attempt, RENAME_RETRY_MAX_TIMEOUT_MS);
80
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
81
+ }
82
+ }
83
+ }
84
+ const DEFAULT_RETRIES = 10;
85
+ const DEFAULT_REALPATH = false;
86
+ /**
87
+ * Capped doubling backoff shared by both variants — see {@link AtomicFileLockOptions.retries}.
88
+ * With the current defaults (10 retries, 25ms floor, 500ms cap) the total worst-case contention
89
+ * budget is ~3.3s (25+50+100+200+400+500+500+500+500+500), comfortably over the >= 2s floor this
90
+ * needs to survive a loaded CI runner (e.g. the Windows CI mkdir/rmdir lock-directory churn under
91
+ * two-real-OS-thread contention).
92
+ */
93
+ const RETRY_MIN_TIMEOUT_MS = 25;
94
+ const RETRY_MAX_TIMEOUT_MS = 500;
95
+ function ensureLockDirSync(filePath) {
96
+ mkdirSync(dirname(filePath), { recursive: true });
97
+ }
98
+ async function ensureLockDir(filePath) {
99
+ await fsPromises.mkdir(dirname(filePath), { recursive: true });
100
+ }
101
+ function isLockedError(err) {
102
+ if (typeof err !== "object" || err === null)
103
+ return false;
104
+ const code = err.code;
105
+ if (code === "ELOCKED")
106
+ return true;
107
+ // On win32, mkdir-ing the lock directory can transiently surface EPERM (rather than the
108
+ // expected EEXIST) when a previous incarnation of that directory is concurrently being
109
+ // rmdir'd by the racing releaser — the mkdir lands mid-teardown and the OS reports "operation
110
+ // not permitted" instead of "already exists". This is contention, not a real permissions
111
+ // failure, so treat it identically to ELOCKED: retry with the existing backoff rather than
112
+ // letting it escape as fatal. POSIX platforms don't exhibit this transient and keep surfacing
113
+ // real EPERM (e.g. an actually unwritable directory) as fatal.
114
+ if (code === "EPERM" && process.platform === "win32")
115
+ return true;
116
+ return false;
117
+ }
118
+ /** Block the calling thread for `ms` without spinning the CPU (Atomics.wait on a private buffer). */
119
+ function blockingSleepMs(ms) {
120
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
121
+ }
122
+ /**
123
+ * Acquire a proper-lockfile sync lock with bounded retry on `ELOCKED` (proper-lockfile's sync API
124
+ * itself forbids `retries > 0`; see {@link AtomicFileLockOptions.retries}).
125
+ *
126
+ * Backoff doubles each attempt (mirroring the `retry` module's own `factor: 2` default that the
127
+ * async path gets for free from proper-lockfile), starting at {@link RETRY_MIN_TIMEOUT_MS} and
128
+ * capped at {@link RETRY_MAX_TIMEOUT_MS}.
129
+ *
130
+ * This is budget PARITY between two implementations of the same lock-acquisition contract, not
131
+ * flake-masking: `options.retries` (a count of RETRIES, i.e. attempts after the first) means the
132
+ * same thing on both paths, so both must make `retries + 1` total acquisition attempts with the
133
+ * same per-gap backoff schedule. The async path gets `retries + 1` attempts for free from the
134
+ * `retry` module (it retries `options.retries` times after its initial attempt); the sync path
135
+ * used to loop exactly `attempts` times total (one fewer gap, and one fewer attempt than the
136
+ * async path for the same `retries` value), which under-budgeted it relative to its async sibling
137
+ * and surfaced as spurious ELOCKED failures under real contention (e.g. two OS threads hammering
138
+ * the same file, or mkdir/rmdir lock-directory churn on a loaded Windows CI runner).
139
+ */
140
+ function lockSyncWithRetry(filePath, options) {
141
+ const attempts = Math.max(1, options.retries ?? DEFAULT_RETRIES) + 1;
142
+ const lockOptions = { realpath: options.realpath ?? DEFAULT_REALPATH, stale: options.stale };
143
+ for (let attempt = 1; attempt <= attempts; attempt++) {
144
+ try {
145
+ return lockfile.lockSync(filePath, lockOptions);
146
+ }
147
+ catch (err) {
148
+ if (!isLockedError(err) || attempt === attempts)
149
+ throw err;
150
+ const backoffMs = Math.min(RETRY_MIN_TIMEOUT_MS * 2 ** (attempt - 1), RETRY_MAX_TIMEOUT_MS);
151
+ blockingSleepMs(backoffMs);
152
+ }
153
+ }
154
+ // Unreachable (the loop always returns or throws), but keeps the function's return type honest.
155
+ throw new Error(`Failed to acquire lock for ${filePath}`);
156
+ }
157
+ /**
158
+ * Hold an exclusive advisory lock on `filePath` for the duration of `fn` (sync). Always releases,
159
+ * including when `fn` throws.
160
+ */
161
+ export function withFileLockSync(filePath, fn, options) {
162
+ ensureLockDirSync(filePath);
163
+ const release = lockSyncWithRetry(filePath, options ?? {});
164
+ try {
165
+ return fn();
166
+ }
167
+ finally {
168
+ // A lock-cleanup failure must never mask fn()'s result (or replace fn()'s own thrown error) —
169
+ // by this point fn() has already durably committed or failed on its own terms.
170
+ try {
171
+ release();
172
+ }
173
+ catch {
174
+ // best-effort cleanup; a stale lock self-expires via proper-lockfile's `stale` window
175
+ }
176
+ }
177
+ }
178
+ /** Async counterpart of {@link withFileLockSync}. Always releases, including when `fn` throws/rejects. */
179
+ export async function withFileLock(filePath, fn, options) {
180
+ await ensureLockDir(filePath);
181
+ const release = await lockfile.lock(filePath, {
182
+ realpath: options?.realpath ?? DEFAULT_REALPATH,
183
+ retries: {
184
+ retries: options?.retries ?? DEFAULT_RETRIES,
185
+ minTimeout: RETRY_MIN_TIMEOUT_MS,
186
+ maxTimeout: RETRY_MAX_TIMEOUT_MS,
187
+ },
188
+ stale: options?.stale,
189
+ });
190
+ try {
191
+ return await fn();
192
+ }
193
+ finally {
194
+ // See {@link withFileLockSync} — cleanup failures must not mask fn()'s outcome.
195
+ await release().catch(() => { });
196
+ }
197
+ }
198
+ /**
199
+ * Write `content` to `filePath` via write-tmp-then-rename (sync); the rename is atomic on the same
200
+ * filesystem, so a concurrent reader never observes a partially-written file. Does NOT itself lock —
201
+ * call from inside {@link withFileLockSync} when the write follows a read that must not race another
202
+ * writer's read+write; call standalone for an unconditional overwrite with no read step.
203
+ */
204
+ function temporaryPath(filePath) {
205
+ return `${filePath}.${process.pid}.${randomUUID()}.tmp`;
206
+ }
207
+ /** Remove one invocation's temporary path without masking the original write/rename failure. */
208
+ function removeTemporaryPathSync(tmpPath) {
209
+ try {
210
+ unlinkSync(tmpPath);
211
+ }
212
+ catch {
213
+ // The rename may have consumed the path, or another failure may have prevented its creation.
214
+ }
215
+ }
216
+ async function removeTemporaryPath(tmpPath) {
217
+ try {
218
+ await fsPromises.unlink(tmpPath);
219
+ }
220
+ catch {
221
+ // The rename may have consumed the path, or another failure may have prevented its creation.
222
+ }
223
+ }
224
+ export function writeFileAtomicSync(filePath, content) {
225
+ mkdirSync(dirname(filePath), { recursive: true });
226
+ const tmpPath = temporaryPath(filePath);
227
+ let renamed = false;
228
+ try {
229
+ // `wx` makes a nonce collision harmless: never truncate another writer's temporary file.
230
+ writeFileSync(tmpPath, content, { encoding: "utf-8", flag: "wx" });
231
+ renameSyncWithRetry(tmpPath, filePath);
232
+ renamed = true;
233
+ }
234
+ finally {
235
+ if (!renamed)
236
+ removeTemporaryPathSync(tmpPath);
237
+ }
238
+ }
239
+ /** Async counterpart of {@link writeFileAtomicSync}. */
240
+ export async function writeFileAtomic(filePath, content) {
241
+ await fsPromises.mkdir(dirname(filePath), { recursive: true });
242
+ const tmpPath = temporaryPath(filePath);
243
+ let renamed = false;
244
+ try {
245
+ // `wx` makes a nonce collision harmless: never truncate another writer's temporary file.
246
+ await fsPromises.writeFile(tmpPath, content, { encoding: "utf-8", flag: "wx" });
247
+ await renameWithRetry(tmpPath, filePath);
248
+ renamed = true;
249
+ }
250
+ finally {
251
+ if (!renamed)
252
+ await removeTemporaryPath(tmpPath);
253
+ }
254
+ }
255
+ //# sourceMappingURL=atomic-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-file.js","sourceRoot":"","sources":["../../../src/core/util/atomic-file.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared atomic-file helper: a single lock+tmp+rename implementation reused by every\n * on-disk store that does a read-modify-write against a JSON/text file. Modeled on the pattern\n * already proven in `memory/providers/file-store.ts` (proper-lockfile advisory lock + write-tmp-\n * then-rename); before this helper existed the pattern was copy-pasted per store, and most copies\n * were missing either the lock, the atomic write, or both.\n *\n * Two call shapes:\n * - `withFileLock(Sync)` — hold an exclusive advisory lock across an arbitrary read-modify-write\n * callback. The lock spans BOTH the read and the write, closing the classic RMW race where two\n * writers each read the old content before either writes back.\n * - `writeFileAtomic(Sync)` — write-tmp-then-rename. Used INSIDE a `withFileLock` callback (or\n * standalone, for a pure overwrite that has no read step to race).\n *\n * Sync and async variants are both exported. Most existing stores expose a synchronous public API\n * called from hot, non-async paths (e.g. a per-token-stream perf sample), so the sync variant lets\n * them gain locking without forcing an async ripple through their callers; the async variant is for\n * call sites that are already async.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { promises as fsPromises, mkdirSync, renameSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\n\n/**\n * Bounded rename retry budget for the win32-only transient-rename handling below (see\n * {@link isTransientRenameErrorOnWin32}). Backoff doubles each attempt starting at\n * {@link RENAME_RETRY_MIN_TIMEOUT_MS} and capped at {@link RENAME_RETRY_MAX_TIMEOUT_MS}, mirroring\n * the doubling-backoff shape used for lock acquisition above. With {@link RENAME_RETRY_ATTEMPTS}\n * extra attempts (10, 20, 40, 80, ... capped at 200) the worst-case total wait is ~1.03s\n * (10+20+40+80+160+200+200+200+200 ~= 1110ms across 9 gaps), which comfortably covers the\n * millisecond-scale window Defender/the Windows Search indexer hold a freshly-written file open\n * without FILE_SHARE_DELETE before releasing it.\n */\nconst RENAME_RETRY_ATTEMPTS = 9;\nconst RENAME_RETRY_MIN_TIMEOUT_MS = 10;\nconst RENAME_RETRY_MAX_TIMEOUT_MS = 200;\n\n/**\n * On win32, antivirus (e.g. Windows Defender's real-time scanner) and the Windows Search indexer\n * routinely open a freshly-written file for a brief scan without `FILE_SHARE_DELETE`, which makes a\n * rename over/of that file fail transiently with EPERM/EACCES/EBUSY for a few milliseconds. This is\n * a well-documented platform semantic, not a bug in our write path — it's exactly why the\n * `graceful-fs` package ships its own win32 rename-retry wrapper. POSIX platforms never exhibit this\n * transient (a POSIX rename either succeeds or fails for a real, non-transient reason), so retry is\n * gated strictly to win32 to keep POSIX behavior byte-identical to a bare rename.\n */\nfunction isTransientRenameErrorOnWin32(err: unknown): boolean {\n\tif (process.platform !== \"win32\") return false;\n\tif (typeof err !== \"object\" || err === null) return false;\n\tconst code = (err as { code?: string }).code;\n\treturn code === \"EPERM\" || code === \"EACCES\" || code === \"EBUSY\";\n}\n\n/** Sync counterpart of the rename-retry policy; see {@link isTransientRenameErrorOnWin32}. */\nfunction renameSyncWithRetry(tmpPath: string, filePath: string): void {\n\tfor (let attempt = 0; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {\n\t\ttry {\n\t\t\trenameSync(tmpPath, filePath);\n\t\t\treturn;\n\t\t} catch (err) {\n\t\t\tif (!isTransientRenameErrorOnWin32(err) || attempt === RENAME_RETRY_ATTEMPTS) throw err;\n\t\t\tconst backoffMs = Math.min(RENAME_RETRY_MIN_TIMEOUT_MS * 2 ** attempt, RENAME_RETRY_MAX_TIMEOUT_MS);\n\t\t\tblockingSleepMs(backoffMs);\n\t\t}\n\t}\n}\n\n/** Async counterpart of the rename-retry policy; see {@link isTransientRenameErrorOnWin32}. */\nasync function renameWithRetry(tmpPath: string, filePath: string): Promise<void> {\n\tfor (let attempt = 0; attempt <= RENAME_RETRY_ATTEMPTS; attempt++) {\n\t\ttry {\n\t\t\tawait fsPromises.rename(tmpPath, filePath);\n\t\t\treturn;\n\t\t} catch (err) {\n\t\t\tif (!isTransientRenameErrorOnWin32(err) || attempt === RENAME_RETRY_ATTEMPTS) throw err;\n\t\t\tconst backoffMs = Math.min(RENAME_RETRY_MIN_TIMEOUT_MS * 2 ** attempt, RENAME_RETRY_MAX_TIMEOUT_MS);\n\t\t\tawait new Promise((resolve) => setTimeout(resolve, backoffMs));\n\t\t}\n\t}\n}\n\nexport interface AtomicFileLockOptions {\n\t/**\n\t * Bounded retry attempts while waiting for a lock already held elsewhere. Both variants use a\n\t * SHORT, capped backoff (see {@link RETRY_MIN_TIMEOUT_MS}/{@link RETRY_MAX_TIMEOUT_MS}) — these\n\t * stores' critical sections are sub-millisecond reads+writes of small JSON/text files, so\n\t * contention should clear in milliseconds, not the multi-second-to-31-second worst case\n\t * proper-lockfile's OWN default backoff produces for a bare numeric `retries` (its default\n\t * `minTimeout` is 1000ms with factor 2 — see node_modules/retry/lib/retry.js). Passing a bare\n\t * number straight through would turn brief contention into a multi-second stall on a hot path\n\t * (e.g. a per-token-stream perf sample), so both variants instead build an explicit short-backoff\n\t * `retry` options object.\n\t * - Async (`withFileLock`): forwarded as `{retries, minTimeout, maxTimeout}` to proper-lockfile.\n\t * - Sync (`withFileLockSync`): proper-lockfile's sync API REJECTS `retries > 0` outright (it\n\t * requires the whole acquire flow to be synchronous — see proper-lockfile/lib/adapter.js\n\t * `toSyncOptions`, which throws `ESYNC`). So the sync path implements its own bounded retry\n\t * around single `lockfile.lockSync` attempts, blocking briefly between them (Atomics.wait) —\n\t * callers are already fully synchronous fs code, so a short blocking wait on contention matches\n\t * the existing execution model rather than introducing a new one.\n\t */\n\tretries?: number;\n\t/** Resolve symlinks before locking (proper-lockfile `realpath`); false matches file-store.ts. */\n\trealpath?: boolean;\n\t/** Lock staleness window in ms (proper-lockfile `stale`); omitted = proper-lockfile's own default. */\n\tstale?: number;\n}\n\nconst DEFAULT_RETRIES = 10;\nconst DEFAULT_REALPATH = false;\n/**\n * Capped doubling backoff shared by both variants — see {@link AtomicFileLockOptions.retries}.\n * With the current defaults (10 retries, 25ms floor, 500ms cap) the total worst-case contention\n * budget is ~3.3s (25+50+100+200+400+500+500+500+500+500), comfortably over the >= 2s floor this\n * needs to survive a loaded CI runner (e.g. the Windows CI mkdir/rmdir lock-directory churn under\n * two-real-OS-thread contention).\n */\nconst RETRY_MIN_TIMEOUT_MS = 25;\nconst RETRY_MAX_TIMEOUT_MS = 500;\nfunction ensureLockDirSync(filePath: string): void {\n\tmkdirSync(dirname(filePath), { recursive: true });\n}\n\nasync function ensureLockDir(filePath: string): Promise<void> {\n\tawait fsPromises.mkdir(dirname(filePath), { recursive: true });\n}\n\nfunction isLockedError(err: unknown): boolean {\n\tif (typeof err !== \"object\" || err === null) return false;\n\tconst code = (err as { code?: string }).code;\n\tif (code === \"ELOCKED\") return true;\n\t// On win32, mkdir-ing the lock directory can transiently surface EPERM (rather than the\n\t// expected EEXIST) when a previous incarnation of that directory is concurrently being\n\t// rmdir'd by the racing releaser — the mkdir lands mid-teardown and the OS reports \"operation\n\t// not permitted\" instead of \"already exists\". This is contention, not a real permissions\n\t// failure, so treat it identically to ELOCKED: retry with the existing backoff rather than\n\t// letting it escape as fatal. POSIX platforms don't exhibit this transient and keep surfacing\n\t// real EPERM (e.g. an actually unwritable directory) as fatal.\n\tif (code === \"EPERM\" && process.platform === \"win32\") return true;\n\treturn false;\n}\n\n/** Block the calling thread for `ms` without spinning the CPU (Atomics.wait on a private buffer). */\nfunction blockingSleepMs(ms: number): void {\n\tAtomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\n/**\n * Acquire a proper-lockfile sync lock with bounded retry on `ELOCKED` (proper-lockfile's sync API\n * itself forbids `retries > 0`; see {@link AtomicFileLockOptions.retries}).\n *\n * Backoff doubles each attempt (mirroring the `retry` module's own `factor: 2` default that the\n * async path gets for free from proper-lockfile), starting at {@link RETRY_MIN_TIMEOUT_MS} and\n * capped at {@link RETRY_MAX_TIMEOUT_MS}.\n *\n * This is budget PARITY between two implementations of the same lock-acquisition contract, not\n * flake-masking: `options.retries` (a count of RETRIES, i.e. attempts after the first) means the\n * same thing on both paths, so both must make `retries + 1` total acquisition attempts with the\n * same per-gap backoff schedule. The async path gets `retries + 1` attempts for free from the\n * `retry` module (it retries `options.retries` times after its initial attempt); the sync path\n * used to loop exactly `attempts` times total (one fewer gap, and one fewer attempt than the\n * async path for the same `retries` value), which under-budgeted it relative to its async sibling\n * and surfaced as spurious ELOCKED failures under real contention (e.g. two OS threads hammering\n * the same file, or mkdir/rmdir lock-directory churn on a loaded Windows CI runner).\n */\nfunction lockSyncWithRetry(filePath: string, options: AtomicFileLockOptions): () => void {\n\tconst attempts = Math.max(1, options.retries ?? DEFAULT_RETRIES) + 1;\n\tconst lockOptions = { realpath: options.realpath ?? DEFAULT_REALPATH, stale: options.stale };\n\tfor (let attempt = 1; attempt <= attempts; attempt++) {\n\t\ttry {\n\t\t\treturn lockfile.lockSync(filePath, lockOptions);\n\t\t} catch (err) {\n\t\t\tif (!isLockedError(err) || attempt === attempts) throw err;\n\t\t\tconst backoffMs = Math.min(RETRY_MIN_TIMEOUT_MS * 2 ** (attempt - 1), RETRY_MAX_TIMEOUT_MS);\n\t\t\tblockingSleepMs(backoffMs);\n\t\t}\n\t}\n\t// Unreachable (the loop always returns or throws), but keeps the function's return type honest.\n\tthrow new Error(`Failed to acquire lock for ${filePath}`);\n}\n\n/**\n * Hold an exclusive advisory lock on `filePath` for the duration of `fn` (sync). Always releases,\n * including when `fn` throws.\n */\nexport function withFileLockSync<T>(filePath: string, fn: () => T, options?: AtomicFileLockOptions): T {\n\tensureLockDirSync(filePath);\n\tconst release = lockSyncWithRetry(filePath, options ?? {});\n\ttry {\n\t\treturn fn();\n\t} finally {\n\t\t// A lock-cleanup failure must never mask fn()'s result (or replace fn()'s own thrown error) —\n\t\t// by this point fn() has already durably committed or failed on its own terms.\n\t\ttry {\n\t\t\trelease();\n\t\t} catch {\n\t\t\t// best-effort cleanup; a stale lock self-expires via proper-lockfile's `stale` window\n\t\t}\n\t}\n}\n\n/** Async counterpart of {@link withFileLockSync}. Always releases, including when `fn` throws/rejects. */\nexport async function withFileLock<T>(\n\tfilePath: string,\n\tfn: () => Promise<T> | T,\n\toptions?: AtomicFileLockOptions,\n): Promise<T> {\n\tawait ensureLockDir(filePath);\n\tconst release = await lockfile.lock(filePath, {\n\t\trealpath: options?.realpath ?? DEFAULT_REALPATH,\n\t\tretries: {\n\t\t\tretries: options?.retries ?? DEFAULT_RETRIES,\n\t\t\tminTimeout: RETRY_MIN_TIMEOUT_MS,\n\t\t\tmaxTimeout: RETRY_MAX_TIMEOUT_MS,\n\t\t},\n\t\tstale: options?.stale,\n\t});\n\ttry {\n\t\treturn await fn();\n\t} finally {\n\t\t// See {@link withFileLockSync} — cleanup failures must not mask fn()'s outcome.\n\t\tawait release().catch(() => {});\n\t}\n}\n\n/**\n * Write `content` to `filePath` via write-tmp-then-rename (sync); the rename is atomic on the same\n * filesystem, so a concurrent reader never observes a partially-written file. Does NOT itself lock —\n * call from inside {@link withFileLockSync} when the write follows a read that must not race another\n * writer's read+write; call standalone for an unconditional overwrite with no read step.\n */\nfunction temporaryPath(filePath: string): string {\n\treturn `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n}\n\n/** Remove one invocation's temporary path without masking the original write/rename failure. */\nfunction removeTemporaryPathSync(tmpPath: string): void {\n\ttry {\n\t\tunlinkSync(tmpPath);\n\t} catch {\n\t\t// The rename may have consumed the path, or another failure may have prevented its creation.\n\t}\n}\n\nasync function removeTemporaryPath(tmpPath: string): Promise<void> {\n\ttry {\n\t\tawait fsPromises.unlink(tmpPath);\n\t} catch {\n\t\t// The rename may have consumed the path, or another failure may have prevented its creation.\n\t}\n}\n\nexport function writeFileAtomicSync(filePath: string, content: string): void {\n\tmkdirSync(dirname(filePath), { recursive: true });\n\tconst tmpPath = temporaryPath(filePath);\n\tlet renamed = false;\n\ttry {\n\t\t// `wx` makes a nonce collision harmless: never truncate another writer's temporary file.\n\t\twriteFileSync(tmpPath, content, { encoding: \"utf-8\", flag: \"wx\" });\n\t\trenameSyncWithRetry(tmpPath, filePath);\n\t\trenamed = true;\n\t} finally {\n\t\tif (!renamed) removeTemporaryPathSync(tmpPath);\n\t}\n}\n\n/** Async counterpart of {@link writeFileAtomicSync}. */\nexport async function writeFileAtomic(filePath: string, content: string): Promise<void> {\n\tawait fsPromises.mkdir(dirname(filePath), { recursive: true });\n\tconst tmpPath = temporaryPath(filePath);\n\tlet renamed = false;\n\ttry {\n\t\t// `wx` makes a nonce collision harmless: never truncate another writer's temporary file.\n\t\tawait fsPromises.writeFile(tmpPath, content, { encoding: \"utf-8\", flag: \"wx\" });\n\t\tawait renameWithRetry(tmpPath, filePath);\n\t\trenamed = true;\n\t} finally {\n\t\tif (!renamed) await removeTemporaryPath(tmpPath);\n\t}\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared compiled-pattern cache (C2): `matchesAnyPattern` in `package-manager.ts` and
3
+ * `matchesResourceProfilePattern` in `settings-manager.ts` each test up to 6 candidate strings
4
+ * (relative path, basename, absolute path, and the same 3 for the parent directory) against every
5
+ * configured glob pattern, per file, on every reload/profile switch. The functional `minimatch(p,
6
+ * pattern, options)` re-parses `pattern` into a `Minimatch` instance on EVERY call — so the same
7
+ * pattern was being compiled up to 6x per file per call site.
8
+ *
9
+ * `new Minimatch(pattern, options).match(p)` is exactly what the functional form does internally
10
+ * (see `minimatch`'s own implementation: `assertValidPattern(pattern)` then
11
+ * `new Minimatch(pattern, options).match(p)`, with comment/empty-pattern short-circuits handled
12
+ * inside the class itself), so precompiling and reusing the `Minimatch` instance is behaviorally
13
+ * identical to calling the functional form each time — only the redundant re-parsing is removed.
14
+ *
15
+ * Both call sites invoke `minimatch(candidate, pattern)` with NO options argument (always the
16
+ * library default `{}`), so the cache key is the pattern string alone. If a future caller needs
17
+ * non-default options, extend the key to include a serialized options object rather than reusing
18
+ * this cache blind — a `Minimatch` compiled under different options is not interchangeable.
19
+ *
20
+ * The cache is a plain, unbounded module-level `Map`. That is safe here because patterns are not
21
+ * arbitrary/untrusted input: they come from a small, operator-controlled set (package resource
22
+ * patterns in settings, `.gitignore`-style profile overrides) that is bounded by how much a human
23
+ * hand-authors into settings/profile files, not by file count or request volume. It never grows
24
+ * per-file or per-scan.
25
+ */
26
+ import { type MinimatchOptions } from "minimatch";
27
+ /**
28
+ * Drop-in replacement for `minimatch(candidate, pattern, options)` that compiles each unique
29
+ * `(pattern, options)` pair once and reuses it. Match results are identical to the functional
30
+ * form; only the redundant glob-compilation work is eliminated.
31
+ */
32
+ export declare function matchesCompiledPattern(candidate: string, pattern: string, options?: MinimatchOptions): boolean;
33
+ //# sourceMappingURL=minimatch-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"minimatch-cache.d.ts","sourceRoot":"","sources":["../../../src/core/util/minimatch-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAgB7D;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAE9G","sourcesContent":["/**\n * Shared compiled-pattern cache (C2): `matchesAnyPattern` in `package-manager.ts` and\n * `matchesResourceProfilePattern` in `settings-manager.ts` each test up to 6 candidate strings\n * (relative path, basename, absolute path, and the same 3 for the parent directory) against every\n * configured glob pattern, per file, on every reload/profile switch. The functional `minimatch(p,\n * pattern, options)` re-parses `pattern` into a `Minimatch` instance on EVERY call — so the same\n * pattern was being compiled up to 6x per file per call site.\n *\n * `new Minimatch(pattern, options).match(p)` is exactly what the functional form does internally\n * (see `minimatch`'s own implementation: `assertValidPattern(pattern)` then\n * `new Minimatch(pattern, options).match(p)`, with comment/empty-pattern short-circuits handled\n * inside the class itself), so precompiling and reusing the `Minimatch` instance is behaviorally\n * identical to calling the functional form each time — only the redundant re-parsing is removed.\n *\n * Both call sites invoke `minimatch(candidate, pattern)` with NO options argument (always the\n * library default `{}`), so the cache key is the pattern string alone. If a future caller needs\n * non-default options, extend the key to include a serialized options object rather than reusing\n * this cache blind — a `Minimatch` compiled under different options is not interchangeable.\n *\n * The cache is a plain, unbounded module-level `Map`. That is safe here because patterns are not\n * arbitrary/untrusted input: they come from a small, operator-controlled set (package resource\n * patterns in settings, `.gitignore`-style profile overrides) that is bounded by how much a human\n * hand-authors into settings/profile files, not by file count or request volume. It never grows\n * per-file or per-scan.\n */\n\nimport { Minimatch, type MinimatchOptions } from \"minimatch\";\n\nconst compiledPatternCache = new Map<string, Minimatch>();\n\nfunction getCompiledPattern(pattern: string, options?: MinimatchOptions): Minimatch {\n\t// Both current call sites omit `options`, so this collapses to a single default-options\n\t// instance per unique pattern string in practice.\n\tconst key = options ? `${JSON.stringify(options)}\u0000${pattern}` : pattern;\n\tlet compiled = compiledPatternCache.get(key);\n\tif (!compiled) {\n\t\tcompiled = new Minimatch(pattern, options);\n\t\tcompiledPatternCache.set(key, compiled);\n\t}\n\treturn compiled;\n}\n\n/**\n * Drop-in replacement for `minimatch(candidate, pattern, options)` that compiles each unique\n * `(pattern, options)` pair once and reuses it. Match results are identical to the functional\n * form; only the redundant glob-compilation work is eliminated.\n */\nexport function matchesCompiledPattern(candidate: string, pattern: string, options?: MinimatchOptions): boolean {\n\treturn getCompiledPattern(pattern, options).match(candidate);\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"minimatch-cache.js","sourceRoot":"","sources":["../../../src/core/util/minimatch-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,SAAS,EAAyB,MAAM,WAAW,CAAC;AAE7D,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAqB,CAAC;AAE1D,SAAS,kBAAkB,CAAC,OAAe,EAAE,OAA0B,EAAa;IACnF,wFAAwF;IACxF,kDAAkD;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IACxE,IAAI,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,QAAQ,GAAG,IAAI,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3C,oBAAoB,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB,EAAE,OAAe,EAAE,OAA0B,EAAW;IAC/G,OAAO,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAAA,CAC7D","sourcesContent":["/**\n * Shared compiled-pattern cache (C2): `matchesAnyPattern` in `package-manager.ts` and\n * `matchesResourceProfilePattern` in `settings-manager.ts` each test up to 6 candidate strings\n * (relative path, basename, absolute path, and the same 3 for the parent directory) against every\n * configured glob pattern, per file, on every reload/profile switch. The functional `minimatch(p,\n * pattern, options)` re-parses `pattern` into a `Minimatch` instance on EVERY call — so the same\n * pattern was being compiled up to 6x per file per call site.\n *\n * `new Minimatch(pattern, options).match(p)` is exactly what the functional form does internally\n * (see `minimatch`'s own implementation: `assertValidPattern(pattern)` then\n * `new Minimatch(pattern, options).match(p)`, with comment/empty-pattern short-circuits handled\n * inside the class itself), so precompiling and reusing the `Minimatch` instance is behaviorally\n * identical to calling the functional form each time — only the redundant re-parsing is removed.\n *\n * Both call sites invoke `minimatch(candidate, pattern)` with NO options argument (always the\n * library default `{}`), so the cache key is the pattern string alone. If a future caller needs\n * non-default options, extend the key to include a serialized options object rather than reusing\n * this cache blind — a `Minimatch` compiled under different options is not interchangeable.\n *\n * The cache is a plain, unbounded module-level `Map`. That is safe here because patterns are not\n * arbitrary/untrusted input: they come from a small, operator-controlled set (package resource\n * patterns in settings, `.gitignore`-style profile overrides) that is bounded by how much a human\n * hand-authors into settings/profile files, not by file count or request volume. It never grows\n * per-file or per-scan.\n */\n\nimport { Minimatch, type MinimatchOptions } from \"minimatch\";\n\nconst compiledPatternCache = new Map<string, Minimatch>();\n\nfunction getCompiledPattern(pattern: string, options?: MinimatchOptions): Minimatch {\n\t// Both current call sites omit `options`, so this collapses to a single default-options\n\t// instance per unique pattern string in practice.\n\tconst key = options ? `${JSON.stringify(options)}\u0000${pattern}` : pattern;\n\tlet compiled = compiledPatternCache.get(key);\n\tif (!compiled) {\n\t\tcompiled = new Minimatch(pattern, options);\n\t\tcompiledPatternCache.set(key, compiled);\n\t}\n\treturn compiled;\n}\n\n/**\n * Drop-in replacement for `minimatch(candidate, pattern, options)` that compiles each unique\n * `(pattern, options)` pair once and reuses it. Match results are identical to the functional\n * form; only the redundant glob-compilation work is eliminated.\n */\nexport function matchesCompiledPattern(candidate: string, pattern: string, options?: MinimatchOptions): boolean {\n\treturn getCompiledPattern(pattern, options).match(candidate);\n}\n"]}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Worktree-sync SSOT for tagged result codes and shared record types.
3
+ *
4
+ * Every worktree-sync operation reports its outcome through one of these string-literal codes in
5
+ * a typed result -- callers branch on `code`, NEVER on message/stderr substrings (the
6
+ * `classifyDispatchError` substring compromise is deliberately not repeated here). Git stderr is
7
+ * carried alongside as evidence for humans, not as a branch condition.
8
+ */
9
+ /** Cap on `WorktreeSyncEpoch.changedPaths`; beyond it the list is truncated and overlap checks
10
+ * treat the land as conservatively overlapping EVERYTHING (deterministic, never optimistic). */
11
+ export declare const EPOCH_CHANGED_PATHS_CAP = 500;
12
+ /** Branch namespace for lane branches: `pi/wt/<laneKey>`. */
13
+ export declare const LANE_BRANCH_PREFIX = "pi/wt/";
14
+ export type WorktreeSyncCode = "ok" | "disabled" | "not_a_git_repo" | "default_branch_unresolved" | "hub_missing" | "invalid_lane_key" | "lane_exists" | "lane_not_found" | "max_lanes_reached" | "lane_dirty" | "hub_dirty" | "stale_lane" | "sync_required" | "sync_clean" | "sync_conflicts" | "rebase_in_progress" | "no_rebase_in_progress" | "conflict_markers_present" | "gate_command_unset" | "gate_failed" | "lane_changed_during_gate" | "ff_failed" | "lock_busy" | "lock_takeover" | "nothing_to_land" | "main_mutation_refused" | "lane_unlanded_work" | "worktree_missing" | "released" | "reconciled" | "git_error" | "role_forbidden" | "path_outside_lane" | "lane_owner_conflict";
15
+ /**
16
+ * A refusal/failure outcome. `message` is deterministic text assembled from facts (safe to show a
17
+ * model verbatim); `gitStderr` is bounded raw evidence; `paths` names offending files where the
18
+ * refusal is about specific files (dirty files, conflict markers); `holder` identifies the
19
+ * integration-lock holder on `lock_busy`.
20
+ */
21
+ export interface WorktreeSyncRefusal<C extends WorktreeSyncCode = WorktreeSyncCode> {
22
+ code: C;
23
+ message: string;
24
+ gitStderr?: string;
25
+ paths?: string[];
26
+ holder?: IntegrationLockOwner;
27
+ }
28
+ /** Staleness-propagation policy (D9). `on_land_mandatory` is the default: every land marks every
29
+ * other active lane sync_required. `overlap_mandatory` requires it only on changed-file overlap.
30
+ * `land_time_only` keeps staleness advisory until the land gate (G3 always enforces). */
31
+ export type WorktreeSyncPolicy = "on_land_mandatory" | "overlap_mandatory" | "land_time_only";
32
+ /** One lane registration record (`lanes/<laneKey>.json`). Identity/binding fields ONLY -- git
33
+ * truths (freshness, dirtiness, ahead/behind) are always re-derived live, never stored here. */
34
+ export interface LaneRegistration {
35
+ laneKey: string;
36
+ branch: string;
37
+ worktreePath: string;
38
+ status: "active" | "released" | "orphaned";
39
+ createdAt: string;
40
+ updatedAt: string;
41
+ goalId?: string;
42
+ requirementId?: string;
43
+ /** Host `LaneRecord.laneId` after dispatch correlation (tmux/in-process worker binding). */
44
+ boundLaneId?: string;
45
+ ownerPid?: number;
46
+ ownerSessionId?: string;
47
+ }
48
+ /** `epoch.json` -- the integration epoch. `epoch` is monotonic messaging/audit sugar; correctness
49
+ * always derives from git shas (`merge-base --is-ancestor`), never from this counter. */
50
+ export interface WorktreeSyncEpoch {
51
+ epoch: number;
52
+ mainSha: string;
53
+ previousMainSha?: string;
54
+ landedLaneKey?: string;
55
+ landedAt?: string;
56
+ changedPaths: string[];
57
+ changedPathsTruncated: boolean;
58
+ }
59
+ /** Live, git-derived facts about one lane. Computed fresh per call -- there is no cached copy to
60
+ * go stale, which is the point. */
61
+ export interface LaneFacts {
62
+ laneKey: string;
63
+ branch: string;
64
+ worktreePath: string;
65
+ registrationStatus: LaneRegistration["status"];
66
+ /** Lane branch tip sha; undefined when the branch no longer exists. */
67
+ branchSha?: string;
68
+ /** True iff current main is an ancestor of the lane tip (`merge-base --is-ancestor`). */
69
+ fresh: boolean;
70
+ /** True when `status --porcelain` in the lane worktree is non-empty. */
71
+ dirty: boolean;
72
+ rebaseInProgress: boolean;
73
+ aheadOfMain: number;
74
+ behindMain: number;
75
+ worktreePresent: boolean;
76
+ /** Undefined when the lane has no recorded owner pid. */
77
+ ownerAlive?: boolean;
78
+ }
79
+ export interface IntegrationLockOwner {
80
+ pid: number;
81
+ hostname: string;
82
+ /** Random acquisition identity; PID/session are diagnostic only and never authorize release. */
83
+ token: string;
84
+ sessionId?: string;
85
+ laneKey?: string;
86
+ acquiredAt: string;
87
+ }
88
+ export type IntegrationLockAcquisition = {
89
+ acquired: true;
90
+ takeover: boolean;
91
+ token: string;
92
+ } | {
93
+ acquired: false;
94
+ holder?: IntegrationLockOwner;
95
+ holderAlive: boolean;
96
+ };
97
+ /** One conflicted file in a sync stopped on conflicts. */
98
+ export interface ConflictWorklistFile {
99
+ path: string;
100
+ /** Both-modified is the common case; add/add and modify/delete surface as their index states. */
101
+ kind: "both_modified" | "both_added" | "deleted_by_us" | "deleted_by_them" | "unknown";
102
+ /** True when git rerere already replayed a recorded resolution for this file -- review, not re-resolve. */
103
+ resolvedByRerere: boolean;
104
+ }
105
+ /** Structured conflict worklist returned when a sync/continue stops on conflicts. */
106
+ export interface ConflictWorklist {
107
+ /** e.g. "2/5" -- rebase progress (stopped step / total steps). */
108
+ step: string;
109
+ stoppedAtCommit?: {
110
+ sha: string;
111
+ subject: string;
112
+ };
113
+ files: ConflictWorklistFile[];
114
+ }
115
+ export type BindLaneWorkerResult = {
116
+ code: "bound";
117
+ laneKey: string;
118
+ laneId: string;
119
+ } | {
120
+ code: "already_bound";
121
+ laneKey: string;
122
+ laneId: string;
123
+ } | {
124
+ code: "binding_conflict";
125
+ laneKey: string;
126
+ boundLaneId: string;
127
+ } | {
128
+ code: "lane_not_found";
129
+ message: string;
130
+ } | {
131
+ code: "lane_not_active";
132
+ message: string;
133
+ } | {
134
+ code: "store_error";
135
+ message: string;
136
+ };
137
+ export type CreateLaneResult = {
138
+ code: "ok";
139
+ lane: LaneRegistration;
140
+ } | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "invalid_lane_key" | "lane_exists" | "max_lanes_reached" | "git_error">;
141
+ export type ReleaseLaneResult = {
142
+ code: "released";
143
+ laneKey: string;
144
+ } | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "lane_not_found" | "lane_unlanded_work" | "lane_owner_conflict" | "git_error">;
145
+ export interface LandingTransaction {
146
+ laneKey: string;
147
+ priorMainSha: string;
148
+ testedTipSha: string;
149
+ changedPaths: string[];
150
+ changedPathsTruncated: boolean;
151
+ lockToken: string;
152
+ gate: "passed" | "off";
153
+ stage: "ready_to_merge" | "main_moved" | "epoch_written" | "audit_logged";
154
+ }
155
+ export interface ReconcileSummary {
156
+ code: "reconciled";
157
+ orphanedLaneKeys: string[];
158
+ reRegisteredLaneKeys: string[];
159
+ ownerClearedLaneKeys: string[];
160
+ staleLockReleased: boolean;
161
+ }
162
+ export type ReconcileResult = ReconcileSummary | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "git_error">;
163
+ export type SyncLaneResult = {
164
+ code: "sync_clean";
165
+ laneKey: string;
166
+ alreadyFresh: boolean;
167
+ autoContinued: number;
168
+ } | {
169
+ code: "sync_conflicts";
170
+ laneKey: string;
171
+ worklist: ConflictWorklist;
172
+ } | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "lane_not_found" | "worktree_missing" | "lane_dirty" | "rebase_in_progress" | "git_error">;
173
+ export type ContinueSyncResult = {
174
+ code: "sync_clean";
175
+ laneKey: string;
176
+ autoContinued: number;
177
+ } | {
178
+ code: "sync_conflicts";
179
+ laneKey: string;
180
+ worklist: ConflictWorklist;
181
+ } | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "lane_not_found" | "worktree_missing" | "no_rebase_in_progress" | "conflict_markers_present" | "git_error">;
182
+ export type AbortSyncResult = {
183
+ code: "ok";
184
+ laneKey: string;
185
+ } | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "lane_not_found" | "no_rebase_in_progress" | "git_error">;
186
+ export type LandResult = {
187
+ code: "ok";
188
+ laneKey: string;
189
+ epoch: number;
190
+ mainSha: string;
191
+ gate: "passed" | "off";
192
+ } | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "lane_not_found" | "worktree_missing" | "rebase_in_progress" | "lane_dirty" | "stale_lane" | "hub_missing" | "hub_dirty" | "gate_command_unset" | "gate_failed" | "lane_changed_during_gate" | "ff_failed" | "lock_busy" | "nothing_to_land" | "lane_owner_conflict" | "git_error">;
193
+ /** One lane's full status entry: live git facts plus registry identity plus policy derivation. */
194
+ export interface LaneStatusEntry extends LaneFacts {
195
+ goalId?: string;
196
+ requirementId?: string;
197
+ boundLaneId?: string;
198
+ /** Not fresh: main moved past this lane's base. Derived, never stored. */
199
+ stale: boolean;
200
+ /** Stale AND the active policy makes syncing mandatory NOW (G8 gates on this). */
201
+ syncRequired: boolean;
202
+ /** Lane-changed files intersecting the last land's changed files (conservatively the whole
203
+ * lane-changed list when the epoch's changedPaths were truncated). */
204
+ overlapWithLastLand: string[];
205
+ }
206
+ /** The deterministic full picture (`status` action): everything an agent would otherwise infer
207
+ * probabilistically from porcelain output, computed mechanically. */
208
+ export interface SyncStatus {
209
+ code: "ok";
210
+ mainBranch: string;
211
+ mainSha: string;
212
+ epoch: number;
213
+ hub?: {
214
+ path: string;
215
+ clean: boolean;
216
+ };
217
+ lock: {
218
+ held: boolean;
219
+ holder?: IntegrationLockOwner;
220
+ holderAlive?: boolean;
221
+ };
222
+ lanes: LaneStatusEntry[];
223
+ /** One deterministic advisory sentence assembled from the codes above; never model-generated. */
224
+ advice?: string;
225
+ }
226
+ export type SyncStatusResult = SyncStatus | WorktreeSyncRefusal<"not_a_git_repo" | "default_branch_unresolved" | "git_error">;
227
+ //# sourceMappingURL=codes.d.ts.map