ashlr-plugin 1.36.4

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 (499) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +39 -0
  3. package/.claude-plugin/plugin.json +36 -0
  4. package/.codex/agents/README.md +9 -0
  5. package/.codex/agents/ashlr-explorer.md +36 -0
  6. package/.codex/agents/ashlr-worker.md +38 -0
  7. package/.codex-plugin/plugin.json +45 -0
  8. package/.mcp.json +14 -0
  9. package/LICENSE +21 -0
  10. package/README.md +311 -0
  11. package/agents/ashlr-code.md +69 -0
  12. package/agents/ashlr-explore.md +44 -0
  13. package/agents/ashlr-plan.md +54 -0
  14. package/bench/fixtures/notebook-large.ipynb +660 -0
  15. package/bench/fixtures/notebook-small.ipynb +39 -0
  16. package/bench/fixtures/tasklist-closed.json +62 -0
  17. package/bench/fixtures/tasklist-large-mixed.json +27 -0
  18. package/bench/fixtures/tasklist-sprint-open.json +79 -0
  19. package/bench/fixtures/websearch-bun-runtime.json +32 -0
  20. package/bench/fixtures/websearch-react-hooks-patterns.json +32 -0
  21. package/bench/fixtures/websearch-typescript-generics.json +38 -0
  22. package/bench/refs/node-sdk/.refrev +1 -0
  23. package/bench/refs/node-sdk/README.md +17 -0
  24. package/bench/refs/node-sdk/src/agent/agent.ts +163 -0
  25. package/bench/refs/node-sdk/src/agent/create-agent-ui-stream.ts +83 -0
  26. package/bench/refs/node-sdk/src/agent/tool-loop-agent.ts +238 -0
  27. package/bench/refs/node-sdk/src/embed/embed-many.ts +409 -0
  28. package/bench/refs/node-sdk/src/embed/embed-result.ts +50 -0
  29. package/bench/refs/node-sdk/src/embed/embed.ts +257 -0
  30. package/bench/refs/node-sdk/src/error/invalid-argument-error.ts +34 -0
  31. package/bench/refs/node-sdk/src/error/invalid-tool-input-error.ts +33 -0
  32. package/bench/refs/node-sdk/src/error/no-object-generated-error.ts +70 -0
  33. package/bench/refs/node-sdk/src/generate-image/generate-image-result.ts +42 -0
  34. package/bench/refs/node-sdk/src/generate-image/generate-image.ts +362 -0
  35. package/bench/refs/node-sdk/src/generate-object/generate-object-result.ts +67 -0
  36. package/bench/refs/node-sdk/src/generate-object/generate-object.ts +501 -0
  37. package/bench/refs/node-sdk/src/generate-object/inject-json-instruction.ts +30 -0
  38. package/bench/refs/node-sdk/src/generate-object/output-strategy.ts +415 -0
  39. package/bench/refs/node-sdk/src/generate-object/repair-text.ts +12 -0
  40. package/bench/refs/node-sdk/src/generate-object/stream-object.ts +960 -0
  41. package/bench/refs/node-sdk/src/generate-speech/generate-speech.ts +191 -0
  42. package/bench/refs/node-sdk/src/generate-text/generate-text.ts +1484 -0
  43. package/bench/refs/node-sdk/src/generate-text/stream-text.ts +2708 -0
  44. package/bench/refs/node-sdk/src/generate-text/tool-call.ts +59 -0
  45. package/bench/refs/node-sdk/src/generate-text/tool-result.ts +36 -0
  46. package/bench/refs/node-sdk/src/telemetry/create-telemetry-dispatcher.ts +146 -0
  47. package/bench/refs/node-sdk/src/telemetry/telemetry-registry.ts +15 -0
  48. package/bench/refs/node-sdk/src/telemetry/telemetry.ts +223 -0
  49. package/bench/refs/node-sdk/src/util/async-iterable-stream.ts +94 -0
  50. package/bench/refs/node-sdk/src/util/consume-stream.ts +31 -0
  51. package/bench/refs/node-sdk/src/util/create-stitchable-stream.ts +112 -0
  52. package/bench/refs/node-sdk/src/util/download/download.ts +74 -0
  53. package/bench/refs/node-sdk/src/util/error-handler.ts +1 -0
  54. package/bench/refs/node-sdk/src/util/fix-json.ts +401 -0
  55. package/bench/refs/node-sdk/src/util/merge-objects.ts +79 -0
  56. package/bench/refs/python-lib/.refrev +1 -0
  57. package/bench/refs/python-lib/README.md +18 -0
  58. package/bench/refs/python-lib/pandas/core/_numba/executor.py +249 -0
  59. package/bench/refs/python-lib/pandas/core/_numba/extensions.py +586 -0
  60. package/bench/refs/python-lib/pandas/core/_numba/kernels/mean_.py +198 -0
  61. package/bench/refs/python-lib/pandas/core/_numba/kernels/min_max_.py +178 -0
  62. package/bench/refs/python-lib/pandas/core/_numba/kernels/sum_.py +255 -0
  63. package/bench/refs/python-lib/pandas/core/_numba/kernels/var_.py +251 -0
  64. package/bench/refs/python-lib/pandas/core/accessor.py +520 -0
  65. package/bench/refs/python-lib/pandas/core/api.py +138 -0
  66. package/bench/refs/python-lib/pandas/core/array_algos/masked_accumulations.py +97 -0
  67. package/bench/refs/python-lib/pandas/core/array_algos/masked_reductions.py +252 -0
  68. package/bench/refs/python-lib/pandas/core/array_algos/putmask.py +150 -0
  69. package/bench/refs/python-lib/pandas/core/array_algos/quantile.py +232 -0
  70. package/bench/refs/python-lib/pandas/core/array_algos/replace.py +159 -0
  71. package/bench/refs/python-lib/pandas/core/array_algos/take.py +609 -0
  72. package/bench/refs/python-lib/pandas/core/arraylike.py +534 -0
  73. package/bench/refs/python-lib/pandas/core/arrays/_arrow_string_mixins.py +434 -0
  74. package/bench/refs/python-lib/pandas/core/arrays/_mixins.py +630 -0
  75. package/bench/refs/python-lib/pandas/core/arrays/_ranges.py +276 -0
  76. package/bench/refs/python-lib/pandas/core/arrays/_utils.py +77 -0
  77. package/bench/refs/python-lib/pandas/core/arrays/arrow/accessors.py +521 -0
  78. package/bench/refs/python-lib/pandas/core/arrays/arrow/extension_types.py +174 -0
  79. package/bench/refs/python-lib/pandas/core/arrays/boolean.py +454 -0
  80. package/bench/refs/python-lib/pandas/core/arrays/floating.py +192 -0
  81. package/bench/refs/python-lib/pandas/core/arrays/integer.py +296 -0
  82. package/bench/refs/python-lib/pandas/core/arrays/masked.py +2140 -0
  83. package/bench/refs/python-lib/pandas/core/arrays/numeric.py +305 -0
  84. package/bench/refs/python-lib/pandas/core/arrays/numpy_.py +701 -0
  85. package/bench/refs/python-lib/pandas/core/arrays/period.py +1586 -0
  86. package/bench/refs/python-lib/pandas/core/arrays/sparse/accessor.py +505 -0
  87. package/bench/refs/python-lib/pandas/core/arrays/sparse/scipy_sparse.py +208 -0
  88. package/bench/refs/rust-project/.refrev +1 -0
  89. package/bench/refs/rust-project/README.md +17 -0
  90. package/bench/refs/rust-project/tokio/src/doc/os.rs +68 -0
  91. package/bench/refs/rust-project/tokio/src/fs/dir_builder.rs +129 -0
  92. package/bench/refs/rust-project/tokio/src/fs/file.rs +1113 -0
  93. package/bench/refs/rust-project/tokio/src/fs/mocks.rs +176 -0
  94. package/bench/refs/rust-project/tokio/src/fs/mod.rs +324 -0
  95. package/bench/refs/rust-project/tokio/src/fs/open_options.rs +856 -0
  96. package/bench/refs/rust-project/tokio/src/fs/read.rs +80 -0
  97. package/bench/refs/rust-project/tokio/src/fs/read_dir.rs +357 -0
  98. package/bench/refs/rust-project/tokio/src/fs/write.rs +99 -0
  99. package/bench/refs/rust-project/tokio/src/future/maybe_done.rs +123 -0
  100. package/bench/refs/rust-project/tokio/src/future/try_join.rs +82 -0
  101. package/bench/refs/rust-project/tokio/src/io/async_buf_read.rs +117 -0
  102. package/bench/refs/rust-project/tokio/src/io/async_read.rs +133 -0
  103. package/bench/refs/rust-project/tokio/src/io/async_seek.rs +96 -0
  104. package/bench/refs/rust-project/tokio/src/io/async_write.rs +402 -0
  105. package/bench/refs/rust-project/tokio/src/io/blocking.rs +339 -0
  106. package/bench/refs/rust-project/tokio/src/io/interest.rs +345 -0
  107. package/bench/refs/rust-project/tokio/src/io/join.rs +130 -0
  108. package/bench/refs/rust-project/tokio/src/io/mod.rs +308 -0
  109. package/bench/refs/rust-project/tokio/src/io/poll_evented.rs +319 -0
  110. package/bench/refs/rust-project/tokio/src/io/read_buf.rs +344 -0
  111. package/bench/refs/rust-project/tokio/src/io/ready.rs +322 -0
  112. package/bench/refs/rust-project/tokio/src/io/split.rs +166 -0
  113. package/bench/refs/rust-project/tokio/src/io/stderr.rs +148 -0
  114. package/bench/refs/rust-project/tokio/src/io/stdin.rs +98 -0
  115. package/bench/refs/rust-project/tokio/src/io/stdio_common.rs +222 -0
  116. package/bench/refs/rust-project/tokio/src/io/stdout.rs +197 -0
  117. package/bench/refs/rust-project/tokio/src/io/util/buf_reader.rs +311 -0
  118. package/bench/refs/rust-project/tokio/src/io/util/buf_stream.rs +207 -0
  119. package/bench/refs/rust-project/tokio/src/io/util/buf_writer.rs +310 -0
  120. package/bench/refs/rust-project/tokio/src/io/util/chain.rs +144 -0
  121. package/commands/_spawn-patterns.json +32 -0
  122. package/commands/ashlr-allow.md +21 -0
  123. package/commands/ashlr-benchmark.md +53 -0
  124. package/commands/ashlr-brief.md +97 -0
  125. package/commands/ashlr-budget.md +76 -0
  126. package/commands/ashlr-compact.md +54 -0
  127. package/commands/ashlr-dashboard.md +37 -0
  128. package/commands/ashlr-doctor.md +56 -0
  129. package/commands/ashlr-eco-mode.md +92 -0
  130. package/commands/ashlr-genome-init.md +24 -0
  131. package/commands/ashlr-genome-keygen.md +36 -0
  132. package/commands/ashlr-genome-loop.md +118 -0
  133. package/commands/ashlr-genome-push.md +36 -0
  134. package/commands/ashlr-genome-rewrap.md +48 -0
  135. package/commands/ashlr-genome-team-init.md +67 -0
  136. package/commands/ashlr-handoff.md +23 -0
  137. package/commands/ashlr-help.md +86 -0
  138. package/commands/ashlr-hook-timings.md +30 -0
  139. package/commands/ashlr-leaderboard.md +50 -0
  140. package/commands/ashlr-ollama-setup.md +39 -0
  141. package/commands/ashlr-orchestrate-status.md +72 -0
  142. package/commands/ashlr-orchestrate.md +83 -0
  143. package/commands/ashlr-parallelize.md +63 -0
  144. package/commands/ashlr-report-crash.md +33 -0
  145. package/commands/ashlr-resume.md +72 -0
  146. package/commands/ashlr-savings.md +24 -0
  147. package/commands/ashlr-settings.md +50 -0
  148. package/commands/ashlr-spawn.md +61 -0
  149. package/commands/ashlr-start.md +60 -0
  150. package/commands/ashlr-status.md +64 -0
  151. package/commands/ashlr-team-invite.md +47 -0
  152. package/commands/ashlr-tier.md +122 -0
  153. package/commands/ashlr-tour.md +60 -0
  154. package/commands/ashlr-update.md +155 -0
  155. package/commands/ashlr-upgrade.md +96 -0
  156. package/commands/goal.md +58 -0
  157. package/commands/loop.md +40 -0
  158. package/docs/CNAME +1 -0
  159. package/docs/PRO_TIER.md +104 -0
  160. package/docs/README.md +21 -0
  161. package/docs/architecture.md +415 -0
  162. package/docs/ashlr-pipe.md +161 -0
  163. package/docs/assets/logo.svg +19 -0
  164. package/docs/assets/og.png +0 -0
  165. package/docs/assets/og.svg +41 -0
  166. package/docs/benchmarks-v2.json +355 -0
  167. package/docs/benchmarks.json +102 -0
  168. package/docs/benchmarks.md +211 -0
  169. package/docs/billing.md +185 -0
  170. package/docs/cloud-genome.md +155 -0
  171. package/docs/competitive-wozcode.md +92 -0
  172. package/docs/deploy.md +325 -0
  173. package/docs/email-templates.md +113 -0
  174. package/docs/embed-tuning.md +103 -0
  175. package/docs/genome.md +120 -0
  176. package/docs/github-oauth-onboarding.md +127 -0
  177. package/docs/hero-video-script.md +129 -0
  178. package/docs/index.html +2262 -0
  179. package/docs/install-prompt.md +78 -0
  180. package/docs/install-windows.md +132 -0
  181. package/docs/install.ps1 +171 -0
  182. package/docs/install.sh +146 -0
  183. package/docs/launch/README.md +114 -0
  184. package/docs/launch/hn.md +49 -0
  185. package/docs/launch/reddit.md +45 -0
  186. package/docs/launch/x.md +65 -0
  187. package/docs/launch-post.md +112 -0
  188. package/docs/legal.md +78 -0
  189. package/docs/llm-providers.md +99 -0
  190. package/docs/multi-host-mcp.md +217 -0
  191. package/docs/multi-turn-architecture.md +108 -0
  192. package/docs/operations/deployment.md +108 -0
  193. package/docs/operations/error-monitoring.md +70 -0
  194. package/docs/operations/weekly-digest.md +70 -0
  195. package/docs/operations.md +494 -0
  196. package/docs/platform-support.md +133 -0
  197. package/docs/policy-packs.md +188 -0
  198. package/docs/press-kit/fact-sheet.md +78 -0
  199. package/docs/press-kit/founder-bio.md +3 -0
  200. package/docs/press-kit/logo.svg +33 -0
  201. package/docs/press-kit/screenshots.md +146 -0
  202. package/docs/pricing.md +251 -0
  203. package/docs/pro-backend-architecture.md +523 -0
  204. package/docs/producthunt.md +52 -0
  205. package/docs/router-migration.md +110 -0
  206. package/docs/session-log-schema.md +68 -0
  207. package/docs/social.md +95 -0
  208. package/docs/team-genome.md +528 -0
  209. package/docs/telemetry.md +194 -0
  210. package/docs/test-isolation.md +100 -0
  211. package/docs/upgrade.md +159 -0
  212. package/hooks/_hook-errors.ts +87 -0
  213. package/hooks/_hook-perf-emit.ts +113 -0
  214. package/hooks/_hook-timing-ledger.ts +1133 -0
  215. package/hooks/_nudge-throttle.ts +127 -0
  216. package/hooks/_recent-blocks.ts +127 -0
  217. package/hooks/audit-upload.ts +86 -0
  218. package/hooks/codex-hooks.json +171 -0
  219. package/hooks/commit-attribution.ts +179 -0
  220. package/hooks/edit-batching-nudge.ts +154 -0
  221. package/hooks/genome-scribe-hook.ts +219 -0
  222. package/hooks/hooks.json +294 -0
  223. package/hooks/orient-nudge-hook.ts +242 -0
  224. package/hooks/policy-enforce.ts +214 -0
  225. package/hooks/post-tool-use-embedding.ts +93 -0
  226. package/hooks/post-tool-use-genome.sh +37 -0
  227. package/hooks/post-tool-use-genome.ts +69 -0
  228. package/hooks/posttooluse-correlate.ts +122 -0
  229. package/hooks/posttooluse-dedupe-output.ts +241 -0
  230. package/hooks/posttooluse-genome-refresh.ts +223 -0
  231. package/hooks/posttooluse-native-nudge.ts +205 -0
  232. package/hooks/posttooluse-stale-result.ts +277 -0
  233. package/hooks/pretooluse-bash.ts +63 -0
  234. package/hooks/pretooluse-budget-guard.ts +318 -0
  235. package/hooks/pretooluse-common.ts +985 -0
  236. package/hooks/pretooluse-eco-router.ts +196 -0
  237. package/hooks/pretooluse-edit.sh +93 -0
  238. package/hooks/pretooluse-edit.ts +197 -0
  239. package/hooks/pretooluse-glob.ts +70 -0
  240. package/hooks/pretooluse-grep.sh +85 -0
  241. package/hooks/pretooluse-grep.ts +96 -0
  242. package/hooks/pretooluse-notebookedit.ts +121 -0
  243. package/hooks/pretooluse-read.sh +118 -0
  244. package/hooks/pretooluse-read.ts +118 -0
  245. package/hooks/pretooluse-task.ts +130 -0
  246. package/hooks/pretooluse-webfetch.ts +66 -0
  247. package/hooks/pretooluse-websearch.ts +85 -0
  248. package/hooks/pretooluse-write.ts +128 -0
  249. package/hooks/pulse-emit.ts +197 -0
  250. package/hooks/session-end-consolidate.sh +35 -0
  251. package/hooks/session-end-consolidate.ts +188 -0
  252. package/hooks/session-end-stats-gc.ts +80 -0
  253. package/hooks/session-log-append.sh +94 -0
  254. package/hooks/session-log-append.ts +116 -0
  255. package/hooks/session-start.ts +993 -0
  256. package/hooks/sessionend-hook-health-nudge.ts +451 -0
  257. package/hooks/sessionend-savings-nudge.ts +318 -0
  258. package/hooks/sessionend-status-line-nudge.ts +296 -0
  259. package/hooks/sessionstart-brief.ts +140 -0
  260. package/hooks/sessionstart-cost-refactor.ts +95 -0
  261. package/hooks/sessionstart-efficient.ts +110 -0
  262. package/hooks/sessionstart-genome-author.ts +93 -0
  263. package/hooks/sessionstart-lean-tools.ts +98 -0
  264. package/hooks/sessionstart-search.ts +97 -0
  265. package/hooks/stop-accounting.ts +152 -0
  266. package/hooks/subagent-stop-rollup.ts +162 -0
  267. package/hooks/userpromptsubmit-brief-trigger.ts +156 -0
  268. package/package.json +56 -0
  269. package/ports/README.md +108 -0
  270. package/ports/cursor/mcp.json +11 -0
  271. package/ports/goose/recipe.yaml +26 -0
  272. package/scripts/ashlr-mcp.ts +40 -0
  273. package/scripts/auto-update.ts +177 -0
  274. package/scripts/baseline-scan.ts +831 -0
  275. package/scripts/bench-orchestrate.ts +377 -0
  276. package/scripts/benchmark-refs.ts +292 -0
  277. package/scripts/bootstrap.mjs +100 -0
  278. package/scripts/brief-eval.ts +234 -0
  279. package/scripts/bump-version.ts +82 -0
  280. package/scripts/bun-resolve.d.mts +11 -0
  281. package/scripts/bun-resolve.mjs +59 -0
  282. package/scripts/calibrate-grep.ts +467 -0
  283. package/scripts/capture-grep-workload.ts +260 -0
  284. package/scripts/check-tier.ts +61 -0
  285. package/scripts/check-version-sync.ts +97 -0
  286. package/scripts/cli-orchestrate-status.ts +386 -0
  287. package/scripts/cli-orchestrate.ts +303 -0
  288. package/scripts/cli.ts +478 -0
  289. package/scripts/cloud-smoke-test.ts +313 -0
  290. package/scripts/coach-report.ts +373 -0
  291. package/scripts/context-db-migrate.ts +137 -0
  292. package/scripts/context-status.ts +43 -0
  293. package/scripts/demo-run.ts +255 -0
  294. package/scripts/deploy-smoke.sh +140 -0
  295. package/scripts/doctor.ts +992 -0
  296. package/scripts/embed-file-worker.ts +104 -0
  297. package/scripts/embed-tune.ts +308 -0
  298. package/scripts/errors-report.ts +428 -0
  299. package/scripts/find-test-leak.ts +69 -0
  300. package/scripts/generate-badge.ts +357 -0
  301. package/scripts/genome-auto-consolidate.ts +533 -0
  302. package/scripts/genome-auto-propose.ts +578 -0
  303. package/scripts/genome-cloud-pull.ts +363 -0
  304. package/scripts/genome-cloud-push.ts +382 -0
  305. package/scripts/genome-cloud-sync.ts +587 -0
  306. package/scripts/genome-commit-watcher.ts +295 -0
  307. package/scripts/genome-init.ts +986 -0
  308. package/scripts/genome-key.ts +233 -0
  309. package/scripts/genome-keygen.ts +168 -0
  310. package/scripts/genome-link.ts +59 -0
  311. package/scripts/genome-refresh-worker.ts +364 -0
  312. package/scripts/genome-rewrap.ts +90 -0
  313. package/scripts/genome-synthesizer.ts +679 -0
  314. package/scripts/genome-team-init.ts +312 -0
  315. package/scripts/handoff-pack.ts +438 -0
  316. package/scripts/hook-bootstrap.mjs +75 -0
  317. package/scripts/hook-timing-reader.ts +371 -0
  318. package/scripts/hook-timings-report.ts +554 -0
  319. package/scripts/install-genome-hooks.ts +196 -0
  320. package/scripts/install-onnx-model.ts +154 -0
  321. package/scripts/install-permissions.ts +276 -0
  322. package/scripts/install-status-line.ts +150 -0
  323. package/scripts/mcp-entrypoint.sh +78 -0
  324. package/scripts/mcp-entrypoint.ts +130 -0
  325. package/scripts/mdx-angle-bracket-lint.ts +152 -0
  326. package/scripts/measure-cold-start.ts +126 -0
  327. package/scripts/migrate-stats-to-sqlite.ts +86 -0
  328. package/scripts/ollama-setup.ts +498 -0
  329. package/scripts/onboarding-wizard.ts +1691 -0
  330. package/scripts/orchestrate-emit-telemetry.ts +177 -0
  331. package/scripts/orchestrate-expand.ts +517 -0
  332. package/scripts/orchestrate-run.ts +744 -0
  333. package/scripts/print-legend.ts +50 -0
  334. package/scripts/product-metadata.ts +38 -0
  335. package/scripts/prune-old-memories.ts +208 -0
  336. package/scripts/publish.sh +88 -0
  337. package/scripts/read-calibration.ts +122 -0
  338. package/scripts/report-crash.ts +226 -0
  339. package/scripts/run-benchmark.ts +750 -0
  340. package/scripts/run-root-tests.ts +28 -0
  341. package/scripts/savings-dashboard.ts +1258 -0
  342. package/scripts/savings-report-extras.ts +447 -0
  343. package/scripts/savings-status-line.ts +873 -0
  344. package/scripts/session-event-emit.ts +266 -0
  345. package/scripts/session-greet.ts +686 -0
  346. package/scripts/session-log-report.ts +551 -0
  347. package/scripts/session-resume.ts +614 -0
  348. package/scripts/set-hook-mode.ts +83 -0
  349. package/scripts/smoke-cross-platform.ts +117 -0
  350. package/scripts/smoke-realtime.ts +257 -0
  351. package/scripts/smoke-tools.ts +21 -0
  352. package/scripts/stats-cloud-pull.ts +213 -0
  353. package/scripts/stats-cloud-sync.ts +261 -0
  354. package/scripts/telemetry-flush.ts +160 -0
  355. package/scripts/telemetry-status.ts +266 -0
  356. package/scripts/timing-budget.ts +35 -0
  357. package/scripts/ui-animation.ts +120 -0
  358. package/scripts/ui.ts +431 -0
  359. package/scripts/upgrade-flow.ts +775 -0
  360. package/scripts/weekly-digest.ts +394 -0
  361. package/servers/_accounting.ts +78 -0
  362. package/servers/_ask-router.ts +84 -0
  363. package/servers/_ast-chunker.ts +434 -0
  364. package/servers/_ast-helpers.ts +183 -0
  365. package/servers/_ast-languages.ts +153 -0
  366. package/servers/_ast-refactor/_shared.ts +221 -0
  367. package/servers/_ast-refactor/cross-file-rename.ts +560 -0
  368. package/servers/_ast-refactor/extract-function.ts +357 -0
  369. package/servers/_ast-refactor/file-local-rename.ts +172 -0
  370. package/servers/_ast-refactor.ts +41 -0
  371. package/servers/_ast-skeleton.ts +107 -0
  372. package/servers/_bash-summarizers-registry.ts +1096 -0
  373. package/servers/_crash-dump.ts +210 -0
  374. package/servers/_cwd-clamp.ts +342 -0
  375. package/servers/_edit-log.ts +28 -0
  376. package/servers/_edit-match.ts +236 -0
  377. package/servers/_edit-validate.ts +196 -0
  378. package/servers/_embed-calibration.ts +123 -0
  379. package/servers/_embedding-cache.ts +401 -0
  380. package/servers/_embedding-model.ts +329 -0
  381. package/servers/_events.ts +113 -0
  382. package/servers/_genome-cache.ts +154 -0
  383. package/servers/_genome-commits.ts +172 -0
  384. package/servers/_genome-crypto-v2.ts +242 -0
  385. package/servers/_genome-crypto.ts +259 -0
  386. package/servers/_genome-discoveries.ts +140 -0
  387. package/servers/_genome-embed-populator.ts +216 -0
  388. package/servers/_genome-freshness.ts +134 -0
  389. package/servers/_genome-issues.ts +171 -0
  390. package/servers/_genome-live.ts +263 -0
  391. package/servers/_genome-prs.ts +188 -0
  392. package/servers/_genome-search.ts +479 -0
  393. package/servers/_genome-sync.ts +297 -0
  394. package/servers/_history-tracker.ts +279 -0
  395. package/servers/_http-helpers.ts +151 -0
  396. package/servers/_identity-hash.ts +151 -0
  397. package/servers/_llm-providers/anthropic.ts +132 -0
  398. package/servers/_llm-providers/cloud.ts +195 -0
  399. package/servers/_llm-providers/index.ts +350 -0
  400. package/servers/_llm-providers/local.ts +127 -0
  401. package/servers/_llm-providers/onnx.ts +393 -0
  402. package/servers/_llm-providers/types.ts +27 -0
  403. package/servers/_manifest-v2.ts +447 -0
  404. package/servers/_mcp-host.ts +93 -0
  405. package/servers/_nudge-events.ts +451 -0
  406. package/servers/_orchestrate-executor.ts +316 -0
  407. package/servers/_prefetch.ts +435 -0
  408. package/servers/_pricing.ts +256 -0
  409. package/servers/_pro.ts +381 -0
  410. package/servers/_read-cache.ts +44 -0
  411. package/servers/_router-handlers.ts +46 -0
  412. package/servers/_router.ts +55 -0
  413. package/servers/_run-with-timeout.ts +173 -0
  414. package/servers/_savings-render.ts +214 -0
  415. package/servers/_stats-sqlite.ts +640 -0
  416. package/servers/_stats.ts +1183 -0
  417. package/servers/_status-line-cells.ts +469 -0
  418. package/servers/_streaks.ts +162 -0
  419. package/servers/_summarize.ts +208 -0
  420. package/servers/_task-graph-render.ts +217 -0
  421. package/servers/_task-graph.ts +545 -0
  422. package/servers/_telemetry.ts +719 -0
  423. package/servers/_test-parsers.ts +530 -0
  424. package/servers/_test-watch.ts +626 -0
  425. package/servers/_text-helpers.ts +37 -0
  426. package/servers/_token-measure.ts +160 -0
  427. package/servers/_tool-base.ts +248 -0
  428. package/servers/_weekly-digest.ts +391 -0
  429. package/servers/ask-server-handlers.ts +49 -0
  430. package/servers/ask-server.ts +174 -0
  431. package/servers/bash-server-handlers.ts +138 -0
  432. package/servers/bash-server.ts +1136 -0
  433. package/servers/bench.ts +186 -0
  434. package/servers/diff-semantic-server-handlers.ts +39 -0
  435. package/servers/diff-semantic-server.ts +566 -0
  436. package/servers/diff-server-handlers.ts +317 -0
  437. package/servers/diff-server.ts +20 -0
  438. package/servers/edit-server-handlers.ts +31 -0
  439. package/servers/edit-server.ts +209 -0
  440. package/servers/edit-structural-server-handlers.ts +322 -0
  441. package/servers/efficiency-server-handlers.ts +161 -0
  442. package/servers/efficiency-server.ts +1135 -0
  443. package/servers/flush-server-handlers.ts +21 -0
  444. package/servers/flush-server.ts +19 -0
  445. package/servers/genome-server-handlers.ts +118 -0
  446. package/servers/genome-server.ts +397 -0
  447. package/servers/github-server-handlers.ts +187 -0
  448. package/servers/github-server.ts +856 -0
  449. package/servers/glob-server-handlers.ts +233 -0
  450. package/servers/glob-server.ts +20 -0
  451. package/servers/grep-server-handlers.ts +61 -0
  452. package/servers/grep-server.ts +564 -0
  453. package/servers/http-server-handlers.ts +36 -0
  454. package/servers/http-server.ts +149 -0
  455. package/servers/logs-server-handlers.ts +42 -0
  456. package/servers/logs-server.ts +359 -0
  457. package/servers/ls-server-handlers.ts +166 -0
  458. package/servers/ls-server.ts +20 -0
  459. package/servers/multi-edit-server-handlers.ts +51 -0
  460. package/servers/multi-edit-server.ts +422 -0
  461. package/servers/notebook-edit-server-handlers.ts +34 -0
  462. package/servers/notebook-edit-server.ts +181 -0
  463. package/servers/orient-server-handlers.ts +52 -0
  464. package/servers/orient-server.ts +436 -0
  465. package/servers/pipe-server-handlers.ts +68 -0
  466. package/servers/pipe-server.ts +300 -0
  467. package/servers/read-server-handlers.ts +42 -0
  468. package/servers/read-server.ts +206 -0
  469. package/servers/rename-file-server-handlers.ts +67 -0
  470. package/servers/rename-file-server.ts +699 -0
  471. package/servers/savings-server-handlers.ts +27 -0
  472. package/servers/savings-server.ts +54 -0
  473. package/servers/search-replace-regex-server-handlers.ts +96 -0
  474. package/servers/search-replace-regex-server.ts +751 -0
  475. package/servers/sql-server-handlers.ts +58 -0
  476. package/servers/sql-server.ts +505 -0
  477. package/servers/task-server-handlers.ts +358 -0
  478. package/servers/task-server.ts +24 -0
  479. package/servers/test-server-handlers.ts +351 -0
  480. package/servers/test-server.ts +20 -0
  481. package/servers/tree-server-handlers.ts +503 -0
  482. package/servers/tree-server.ts +20 -0
  483. package/servers/webfetch-server-handlers.ts +166 -0
  484. package/servers/webfetch-server.ts +20 -0
  485. package/servers/websearch-server-handlers.ts +215 -0
  486. package/servers/websearch-server.ts +19 -0
  487. package/servers/write-server-handlers.ts +27 -0
  488. package/servers/write-server.ts +97 -0
  489. package/skills/ashlr-brief/SKILL.md +80 -0
  490. package/skills/ashlr-codex-doctor/SKILL.md +21 -0
  491. package/skills/ashlr-codex-efficient-tools/SKILL.md +17 -0
  492. package/skills/ashlr-codex-genome-refresh/SKILL.md +20 -0
  493. package/skills/ashlr-codex-parallel-explore/SKILL.md +20 -0
  494. package/skills/ashlr-codex-savings/SKILL.md +17 -0
  495. package/skills/ashlr-cost-refactor/SKILL.md +50 -0
  496. package/skills/ashlr-efficient/SKILL.md +64 -0
  497. package/skills/ashlr-genome-author/SKILL.md +43 -0
  498. package/skills/ashlr-lean-tools/SKILL.md +43 -0
  499. package/skills/ashlr-search/SKILL.md +46 -0
@@ -0,0 +1,1133 @@
1
+ import { randomUUID } from "crypto";
2
+ import {
3
+ chmodSync,
4
+ closeSync,
5
+ constants,
6
+ existsSync,
7
+ fsyncSync,
8
+ mkdirSync,
9
+ openSync,
10
+ readFileSync,
11
+ readdirSync,
12
+ readSync,
13
+ renameSync,
14
+ rmSync,
15
+ statSync,
16
+ writeFileSync,
17
+ } from "fs";
18
+ import { basename, dirname, join } from "path";
19
+
20
+ export const HOOK_TIMING_FILE_MAX_BYTES = 16 * 1024 * 1024;
21
+ export const HOOK_TIMING_BATCH_MAX_BYTES = 256 * 1024;
22
+
23
+ const LOCK_STALE_MS = 30_000;
24
+ const LOCK_HARD_STALE_MS = 5 * 60_000;
25
+ const LOCK_FUTURE_SKEW_MS = 5_000;
26
+ const LOCK_WAIT_MS = 500;
27
+ const LOCK_POLL_MS = 5;
28
+
29
+ interface LockOwner {
30
+ token: string;
31
+ pid: number;
32
+ createdAt: string;
33
+ }
34
+
35
+ interface DropMetadata {
36
+ schemaVersion: 1;
37
+ droppedThrough: string | null;
38
+ updatedAt: string;
39
+ }
40
+
41
+ export type HookTimingWriteResult = "written" | "contended" | "failed";
42
+
43
+ function sleepSync(ms: number): void {
44
+ const signal = new Int32Array(new SharedArrayBuffer(4));
45
+ Atomics.wait(signal, 0, 0, ms);
46
+ }
47
+
48
+ function hardenDirectory(path: string): void {
49
+ mkdirSync(path, { recursive: true, mode: 0o700 });
50
+ chmodSync(path, 0o700);
51
+ }
52
+
53
+ function fsyncDirectory(path: string): void {
54
+ let fd: number | null = null;
55
+ try {
56
+ fd = openSync(path, constants.O_RDONLY);
57
+ fsyncSync(fd);
58
+ } catch {
59
+ /* Some platforms/filesystems do not support directory fsync. */
60
+ } finally {
61
+ if (fd !== null) {
62
+ try { closeSync(fd); } catch { /* best effort */ }
63
+ }
64
+ }
65
+ }
66
+
67
+ function atomicReplace(path: string, write: (fd: number) => void): void {
68
+ const tempPath = join(
69
+ dirname(path),
70
+ `.${basename(path)}.tmp-${process.pid}-${randomUUID()}`,
71
+ );
72
+ let fd: number | null = null;
73
+ try {
74
+ fd = openSync(
75
+ tempPath,
76
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
77
+ 0o600,
78
+ );
79
+ write(fd);
80
+ fsyncSync(fd);
81
+ closeSync(fd);
82
+ fd = null;
83
+ chmodSync(tempPath, 0o600);
84
+ renameSync(tempPath, path);
85
+ chmodSync(path, 0o600);
86
+ fsyncDirectory(dirname(path));
87
+ } finally {
88
+ if (fd !== null) {
89
+ try { closeSync(fd); } catch { /* fail open */ }
90
+ }
91
+ try { rmSync(tempPath, { force: true }); } catch { /* fail open */ }
92
+ }
93
+ }
94
+
95
+ function atomicWrite(path: string, data: string | Buffer | readonly Buffer[]): void {
96
+ atomicReplace(path, (fd) => {
97
+ if (typeof data !== "string" && !Buffer.isBuffer(data)) {
98
+ for (const chunk of data) writeFileSync(fd, chunk);
99
+ } else {
100
+ writeFileSync(fd, data);
101
+ }
102
+ });
103
+ }
104
+
105
+ function atomicCopyRange(path: string, sourceFd: number, start: number, end: number): void {
106
+ atomicReplace(path, (targetFd) => {
107
+ const chunk = Buffer.allocUnsafe(SCAN_CHUNK_BYTES);
108
+ let position = start;
109
+ while (position < end) {
110
+ const count = readSync(
111
+ sourceFd,
112
+ chunk,
113
+ 0,
114
+ Math.min(chunk.length, end - position),
115
+ position,
116
+ );
117
+ if (count === 0) throw new Error("Unexpected EOF while copying timing ledger");
118
+ writeFileSync(targetFd, chunk.subarray(0, count));
119
+ position += count;
120
+ }
121
+ });
122
+ }
123
+
124
+ function readOwner(lockPath: string): LockOwner | null {
125
+ try {
126
+ const value = JSON.parse(readFileSync(join(lockPath, "owner.json"), "utf8"));
127
+ if (
128
+ typeof value?.token !== "string" ||
129
+ !Number.isSafeInteger(value?.pid) ||
130
+ typeof value?.createdAt !== "string"
131
+ ) return null;
132
+ return value as LockOwner;
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ function processIsAlive(pid: number): boolean {
139
+ try {
140
+ process.kill(pid, 0);
141
+ return true;
142
+ } catch (error) {
143
+ return (error as NodeJS.ErrnoException).code !== "ESRCH";
144
+ }
145
+ }
146
+
147
+ interface LockIdentity {
148
+ lockDev: number;
149
+ lockIno: number;
150
+ lockCtimeMs: number;
151
+ ownerToken: string | null;
152
+ ownerDev: number | null;
153
+ ownerIno: number | null;
154
+ ownerCtimeMs: number | null;
155
+ }
156
+
157
+ let testBeforeStaleQuarantine: ((lockPath: string) => void) | null = null;
158
+ let testAfterReaperPrecheck: ((lockPath: string) => void) | null = null;
159
+
160
+ /** @internal Test seam for deterministic stale-lock replacement races. */
161
+ export function __setBeforeStaleQuarantineForTest(
162
+ callback: ((lockPath: string) => void) | null,
163
+ ): void {
164
+ testBeforeStaleQuarantine = callback;
165
+ }
166
+
167
+ /** @internal Test seam for a writer paused between guard check and lock create. */
168
+ export function __setAfterReaperPrecheckForTest(
169
+ callback: ((lockPath: string) => void) | null,
170
+ ): void {
171
+ testAfterReaperPrecheck = callback;
172
+ }
173
+
174
+ function readLockIdentity(lockPath: string): { identity: LockIdentity; owner: LockOwner | null; mtimeMs: number } | null {
175
+ try {
176
+ const lockBefore = statSync(lockPath);
177
+ let ownerStat: ReturnType<typeof statSync> | null = null;
178
+ try { ownerStat = statSync(join(lockPath, "owner.json")); } catch { /* invalid owner */ }
179
+ const owner = readOwner(lockPath);
180
+ const lockAfter = statSync(lockPath);
181
+ if (lockBefore.dev !== lockAfter.dev || lockBefore.ino !== lockAfter.ino) return null;
182
+ return {
183
+ identity: {
184
+ lockDev: lockAfter.dev,
185
+ lockIno: lockAfter.ino,
186
+ lockCtimeMs: lockAfter.ctimeMs,
187
+ ownerToken: owner?.token ?? null,
188
+ ownerDev: ownerStat?.dev ?? null,
189
+ ownerIno: ownerStat?.ino ?? null,
190
+ ownerCtimeMs: ownerStat?.ctimeMs ?? null,
191
+ },
192
+ owner,
193
+ mtimeMs: lockAfter.mtimeMs,
194
+ };
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+
200
+ function sameLockIdentity(actual: LockIdentity, expected: LockIdentity): boolean {
201
+ return actual.lockDev === expected.lockDev &&
202
+ actual.lockIno === expected.lockIno &&
203
+ actual.ownerToken === expected.ownerToken &&
204
+ actual.ownerDev === expected.ownerDev &&
205
+ actual.ownerIno === expected.ownerIno &&
206
+ actual.ownerCtimeMs === expected.ownerCtimeMs;
207
+ }
208
+
209
+ function inspectStaleLock(lockPath: string): LockIdentity | null {
210
+ try {
211
+ const inspected = readLockIdentity(lockPath);
212
+ if (!inspected) return null;
213
+ const { identity, owner, mtimeMs } = inspected;
214
+ const parsedCreated = owner ? Date.parse(owner.createdAt) : Number.NaN;
215
+ const now = Date.now();
216
+ const created = Number.isFinite(parsedCreated) && parsedCreated <= now + LOCK_FUTURE_SKEW_MS
217
+ ? parsedCreated
218
+ : mtimeMs;
219
+ const age = Math.max(0, now - Math.min(created, mtimeMs));
220
+ if (age > LOCK_HARD_STALE_MS) return identity;
221
+ if (age <= LOCK_STALE_MS) return null;
222
+ return owner === null || !processIsAlive(owner.pid) ? identity : null;
223
+ } catch {
224
+ return null;
225
+ }
226
+ }
227
+
228
+ function quarantineStaleLock(lockPath: string, expected: LockIdentity): boolean {
229
+ const quarantine = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
230
+ try {
231
+ renameSync(lockPath, quarantine);
232
+ } catch {
233
+ return false;
234
+ }
235
+ const renamed = readLockIdentity(quarantine);
236
+ if (!renamed || !sameLockIdentity(renamed.identity, expected)) {
237
+ try {
238
+ if (!existsSync(lockPath)) renameSync(quarantine, lockPath);
239
+ } catch {
240
+ /* Never delete an identity-mismatched lock. */
241
+ }
242
+ fsyncDirectory(dirname(lockPath));
243
+ return false;
244
+ }
245
+ try { chmodSync(quarantine, 0o700); } catch { /* best effort */ }
246
+ try { rmSync(quarantine, { recursive: true, force: true }); } catch { /* fail open */ }
247
+ fsyncDirectory(dirname(lockPath));
248
+ return true;
249
+ }
250
+
251
+ function createLockOwner(): LockOwner {
252
+ return {
253
+ token: randomUUID(),
254
+ pid: process.pid,
255
+ createdAt: new Date().toISOString(),
256
+ };
257
+ }
258
+
259
+ function tryAcquireReaperGuard(lockPath: string): { path: string; owner: LockOwner } | null {
260
+ const owner = createLockOwner();
261
+ const path = `${lockPath}.reap-${owner.token}`;
262
+ let created = false;
263
+ try {
264
+ mkdirSync(path, { mode: 0o700 });
265
+ created = true;
266
+ chmodSync(path, 0o700);
267
+ atomicWrite(join(path, "owner.json"), JSON.stringify(owner) + "\n");
268
+ return { path, owner };
269
+ } catch {
270
+ if (created) {
271
+ try { rmSync(path, { recursive: true, force: true }); } catch { /* fail open */ }
272
+ }
273
+ return null;
274
+ }
275
+ }
276
+
277
+ function listReaperGuards(lockPath: string): string[] {
278
+ const directory = dirname(lockPath);
279
+ const prefix = `${basename(lockPath)}.reap-`;
280
+ try {
281
+ return readdirSync(directory)
282
+ .filter((name) => name.startsWith(prefix))
283
+ .map((name) => join(directory, name));
284
+ } catch {
285
+ return [];
286
+ }
287
+ }
288
+
289
+ function hasActiveReaperGuard(lockPath: string): boolean {
290
+ for (const path of listReaperGuards(lockPath)) {
291
+ const stale = inspectStaleLock(path);
292
+ if (stale) quarantineStaleLock(path, stale);
293
+ }
294
+ return listReaperGuards(lockPath).length > 0;
295
+ }
296
+
297
+ function releaseOwnedDirectory(path: string, owner: LockOwner): void {
298
+ try {
299
+ if (readOwner(path)?.token !== owner.token) return;
300
+ rmSync(path, { recursive: true, force: true });
301
+ fsyncDirectory(dirname(path));
302
+ } catch {
303
+ /* telemetry is fail open */
304
+ }
305
+ }
306
+
307
+ function reapStaleLock(lockPath: string, expected: LockIdentity): boolean {
308
+ const guard = tryAcquireReaperGuard(lockPath);
309
+ if (!guard) return false;
310
+ try {
311
+ testBeforeStaleQuarantine?.(lockPath);
312
+ const current = inspectStaleLock(lockPath);
313
+ if (!current || !sameLockIdentity(current, expected)) return false;
314
+ return quarantineStaleLock(lockPath, expected);
315
+ } finally {
316
+ releaseOwnedDirectory(guard.path, guard.owner);
317
+ }
318
+ }
319
+
320
+ function acquireLock(
321
+ lockPath: string,
322
+ waitMs: number,
323
+ ): { owner: LockOwner | null; result: HookTimingWriteResult } {
324
+ const deadline = Date.now() + waitMs;
325
+ let firstAttempt = true;
326
+ while (firstAttempt || Date.now() <= deadline) {
327
+ firstAttempt = false;
328
+ if (hasActiveReaperGuard(lockPath)) {
329
+ if (Date.now() < deadline) sleepSync(Math.min(LOCK_POLL_MS, deadline - Date.now()));
330
+ continue;
331
+ }
332
+ testAfterReaperPrecheck?.(lockPath);
333
+ const owner = createLockOwner();
334
+ let created = false;
335
+ try {
336
+ mkdirSync(lockPath, { mode: 0o700 });
337
+ created = true;
338
+ chmodSync(lockPath, 0o700);
339
+ atomicWrite(join(lockPath, "owner.json"), JSON.stringify(owner) + "\n");
340
+ // A reaper may have appeared after the precheck. Relinquish this lock
341
+ // before entering the ledger transaction so quarantine remains exclusive.
342
+ if (hasActiveReaperGuard(lockPath)) {
343
+ releaseOwnedDirectory(lockPath, owner);
344
+ if (Date.now() < deadline) sleepSync(Math.min(LOCK_POLL_MS, deadline - Date.now()));
345
+ continue;
346
+ }
347
+ return { owner, result: "written" };
348
+ } catch (error) {
349
+ const code = (error as NodeJS.ErrnoException).code;
350
+ if (code !== "EEXIST") {
351
+ if (created) {
352
+ try { rmSync(lockPath, { recursive: true, force: true }); } catch { /* fail open */ }
353
+ }
354
+ return { owner: null, result: "failed" };
355
+ }
356
+ const staleIdentity = inspectStaleLock(lockPath);
357
+ if (staleIdentity) {
358
+ reapStaleLock(lockPath, staleIdentity);
359
+ }
360
+ else if (Date.now() < deadline) sleepSync(Math.min(LOCK_POLL_MS, deadline - Date.now()));
361
+ }
362
+ }
363
+ return { owner: null, result: "contended" };
364
+ }
365
+
366
+ function releaseLock(lockPath: string, owner: LockOwner): void {
367
+ try {
368
+ if (readOwner(lockPath)?.token !== owner.token) return;
369
+ rmSync(lockPath, { recursive: true, force: true });
370
+ fsyncDirectory(dirname(lockPath));
371
+ } catch {
372
+ /* telemetry is fail open */
373
+ }
374
+ }
375
+
376
+ const SCAN_CHUNK_BYTES = 64 * 1024;
377
+ const OVERSIZED_LINE_PREFIX_BYTES = 64 * 1024;
378
+ const FIELD_TS = Buffer.from('{"ts":');
379
+ const FIELD_HOOK = Buffer.from(',"hook":');
380
+ const FIELD_TOOL = Buffer.from(',"tool":');
381
+ const FIELD_DURATION = Buffer.from(',"durationMs":');
382
+ const FIELD_OUTCOME = Buffer.from(',"outcome":');
383
+ const JSON_NULL = Buffer.from("null");
384
+
385
+ function consumeLiteral(input: Buffer, offset: number, literal: Buffer): number {
386
+ if (offset + literal.length > input.length) return -1;
387
+ for (let i = 0; i < literal.length; i++) {
388
+ if (input[offset + i] !== literal[i]) return -1;
389
+ }
390
+ return offset + literal.length;
391
+ }
392
+
393
+ function consumeJsonString(input: Buffer, offset: number): number {
394
+ if (input[offset] !== 0x22) return -1;
395
+ for (let i = offset + 1; i < input.length; i++) {
396
+ const byte = input[i]!;
397
+ if (byte === 0x22) return i + 1;
398
+ if (byte < 0x20) return -1;
399
+ if (byte !== 0x5c) continue;
400
+ const escaped = input[++i];
401
+ if (escaped === undefined) return -1;
402
+ if (escaped === 0x75) {
403
+ for (let hex = 0; hex < 4; hex++) {
404
+ const value = input[++i];
405
+ if (value === undefined || !(
406
+ (value >= 0x30 && value <= 0x39) ||
407
+ (value >= 0x41 && value <= 0x46) ||
408
+ (value >= 0x61 && value <= 0x66)
409
+ )) return -1;
410
+ }
411
+ } else if (![0x22, 0x5c, 0x2f, 0x62, 0x66, 0x6e, 0x72, 0x74].includes(escaped)) {
412
+ return -1;
413
+ }
414
+ }
415
+ return -1;
416
+ }
417
+
418
+ function consumeJsonNumber(input: Buffer, offset: number): number {
419
+ let i = offset;
420
+ if (input[i] === 0x2d) i++;
421
+ if (input[i] === 0x30) i++;
422
+ else {
423
+ if (input[i] === undefined || input[i]! < 0x31 || input[i]! > 0x39) return -1;
424
+ while (input[i] !== undefined && input[i]! >= 0x30 && input[i]! <= 0x39) i++;
425
+ }
426
+ if (input[i] === 0x2e) {
427
+ i++;
428
+ const start = i;
429
+ while (input[i] !== undefined && input[i]! >= 0x30 && input[i]! <= 0x39) i++;
430
+ if (i === start) return -1;
431
+ }
432
+ if (input[i] === 0x65 || input[i] === 0x45) {
433
+ i++;
434
+ if (input[i] === 0x2b || input[i] === 0x2d) i++;
435
+ const start = i;
436
+ while (input[i] !== undefined && input[i]! >= 0x30 && input[i]! <= 0x39) i++;
437
+ if (i === start) return -1;
438
+ }
439
+ return i;
440
+ }
441
+
442
+ function isCanonicalTimingRow(input: Buffer): boolean {
443
+ let offset = consumeLiteral(input, 0, FIELD_TS);
444
+ offset = consumeJsonString(input, offset);
445
+ offset = consumeLiteral(input, offset, FIELD_HOOK);
446
+ offset = consumeJsonString(input, offset);
447
+ offset = consumeLiteral(input, offset, FIELD_TOOL);
448
+ const afterNull = consumeLiteral(input, offset, JSON_NULL);
449
+ offset = afterNull >= 0 ? afterNull : consumeJsonString(input, offset);
450
+ offset = consumeLiteral(input, offset, FIELD_DURATION);
451
+ offset = consumeJsonNumber(input, offset);
452
+ offset = consumeLiteral(input, offset, FIELD_OUTCOME);
453
+ offset = consumeJsonString(input, offset);
454
+ return offset === input.length - 1 && input[offset] === 0x7d;
455
+ }
456
+
457
+ interface RowEntry {
458
+ data: Buffer;
459
+ ts: string | null;
460
+ }
461
+
462
+ interface RowWindow {
463
+ entries: RowEntry[];
464
+ head: number;
465
+ bytes: number;
466
+ limit: number;
467
+ droppedThrough: string | null;
468
+ hadDropped: boolean;
469
+ }
470
+
471
+ function advanceTimestamp(current: string | null, candidate: string | null): string | null {
472
+ if (!candidate) return current;
473
+ const candidateTime = Date.parse(candidate);
474
+ if (!Number.isFinite(candidateTime)) return current;
475
+ if (!current) return candidate;
476
+ const currentTime = Date.parse(current);
477
+ return !Number.isFinite(currentTime) || candidateTime > currentTime ? candidate : current;
478
+ }
479
+
480
+ function timestampFromRecord(record: Record<string, unknown>): string | null {
481
+ return typeof record.ts === "string" && Number.isFinite(Date.parse(record.ts))
482
+ ? record.ts
483
+ : null;
484
+ }
485
+
486
+ function timestampFromOversizedPrefix(prefix: Buffer): string | null {
487
+ const match = prefix.toString("utf8").match(/"ts"\s*:\s*("(?:\\.|[^"\\])*")/);
488
+ if (!match) return null;
489
+ try {
490
+ const value = JSON.parse(match[1]!) as unknown;
491
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) ? value : null;
492
+ } catch {
493
+ return null;
494
+ }
495
+ }
496
+
497
+ function createWindow(limit: number): RowWindow {
498
+ return {
499
+ entries: [],
500
+ head: 0,
501
+ bytes: 0,
502
+ limit,
503
+ droppedThrough: null,
504
+ hadDropped: false,
505
+ };
506
+ }
507
+
508
+ function noteDropped(window: RowWindow, ts: string | null): void {
509
+ window.hadDropped = true;
510
+ window.droppedThrough = advanceTimestamp(window.droppedThrough, ts);
511
+ }
512
+
513
+ function compactWindow(window: RowWindow): void {
514
+ if (window.head < 4096 || window.head * 2 < window.entries.length) return;
515
+ window.entries = window.entries.slice(window.head);
516
+ window.head = 0;
517
+ }
518
+
519
+ function pushValidRow(window: RowWindow, entry: RowEntry): void {
520
+ if (window.limit === 0 || entry.data.length > Math.min(window.limit, HOOK_TIMING_FILE_MAX_BYTES)) {
521
+ noteDropped(window, entry.ts);
522
+ return;
523
+ }
524
+ window.entries.push(entry);
525
+ window.bytes += entry.data.length;
526
+ while (window.bytes > window.limit && window.head < window.entries.length) {
527
+ const droppedIndex = window.head++;
528
+ const dropped = window.entries[droppedIndex]!;
529
+ window.entries[droppedIndex] = { data: Buffer.alloc(0), ts: null };
530
+ window.bytes -= dropped.data.length;
531
+ noteDropped(window, dropped.ts);
532
+ }
533
+ compactWindow(window);
534
+ }
535
+
536
+ function consumeCompleteLine(window: RowWindow, line: Buffer): void {
537
+ try {
538
+ const parsed = JSON.parse(line.subarray(0, -1).toString("utf8")) as unknown;
539
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
540
+ noteDropped(window, null);
541
+ return;
542
+ }
543
+ pushValidRow(window, {
544
+ data: line,
545
+ ts: timestampFromRecord(parsed as Record<string, unknown>),
546
+ });
547
+ } catch {
548
+ noteDropped(window, null);
549
+ }
550
+ }
551
+
552
+ function appendPrefix(prefix: Buffer, source: Buffer): Buffer {
553
+ const remaining = OVERSIZED_LINE_PREFIX_BYTES - prefix.length;
554
+ if (remaining <= 0) return prefix;
555
+ return Buffer.concat([prefix, source.subarray(0, remaining)]);
556
+ }
557
+
558
+ function findNewline(buffer: Buffer, start: number, end: number): number {
559
+ const relative = buffer.subarray(start, end).indexOf(0x0a);
560
+ return relative < 0 ? -1 : start + relative;
561
+ }
562
+
563
+ /** Stream complete JSONL rows, retaining only the newest `limit` bytes. */
564
+ function scanJsonlFile(path: string, limit: number): RowWindow {
565
+ const window = createWindow(limit);
566
+ let fd: number | null = null;
567
+ let pending: Buffer = Buffer.alloc(0);
568
+ let discardingOversized = false;
569
+ let oversizedPrefix: Buffer = Buffer.alloc(0);
570
+ try {
571
+ fd = openSync(path, constants.O_RDONLY);
572
+ const chunk = Buffer.allocUnsafe(SCAN_CHUNK_BYTES);
573
+ let position = 0;
574
+ for (;;) {
575
+ const count = readSync(fd, chunk, 0, chunk.length, position);
576
+ if (count === 0) break;
577
+ position += count;
578
+ let offset = 0;
579
+ while (offset < count) {
580
+ if (discardingOversized) {
581
+ const newline = findNewline(chunk, offset, count);
582
+ const end = newline < 0 ? count : newline + 1;
583
+ oversizedPrefix = appendPrefix(oversizedPrefix, chunk.subarray(offset, end));
584
+ if (newline < 0) break;
585
+ noteDropped(window, timestampFromOversizedPrefix(oversizedPrefix));
586
+ oversizedPrefix = Buffer.alloc(0);
587
+ discardingOversized = false;
588
+ offset = newline + 1;
589
+ continue;
590
+ }
591
+
592
+ const newline = findNewline(chunk, offset, count);
593
+ if (newline < 0) {
594
+ const remainder = chunk.subarray(offset, count);
595
+ if (pending.length + remainder.length > HOOK_TIMING_FILE_MAX_BYTES) {
596
+ oversizedPrefix = appendPrefix(oversizedPrefix, pending);
597
+ oversizedPrefix = appendPrefix(oversizedPrefix, remainder);
598
+ pending = Buffer.alloc(0);
599
+ discardingOversized = true;
600
+ } else {
601
+ pending = Buffer.concat([pending, remainder]);
602
+ }
603
+ break;
604
+ }
605
+
606
+ const fragment = chunk.subarray(offset, newline + 1);
607
+ if (pending.length + fragment.length > HOOK_TIMING_FILE_MAX_BYTES) {
608
+ oversizedPrefix = appendPrefix(oversizedPrefix, pending);
609
+ oversizedPrefix = appendPrefix(oversizedPrefix, fragment);
610
+ noteDropped(window, timestampFromOversizedPrefix(oversizedPrefix));
611
+ oversizedPrefix = Buffer.alloc(0);
612
+ pending = Buffer.alloc(0);
613
+ } else {
614
+ const line = pending.length === 0
615
+ ? Buffer.from(fragment)
616
+ : Buffer.concat([pending, fragment]);
617
+ pending = Buffer.alloc(0);
618
+ consumeCompleteLine(window, line);
619
+ }
620
+ offset = newline + 1;
621
+ }
622
+ }
623
+ if (pending.length > 0) {
624
+ try {
625
+ const parsed = JSON.parse(pending.toString("utf8")) as unknown;
626
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
627
+ pushValidRow(window, {
628
+ data: Buffer.concat([pending, Buffer.from("\n")]),
629
+ ts: timestampFromRecord(parsed as Record<string, unknown>),
630
+ });
631
+ } else {
632
+ noteDropped(window, null);
633
+ }
634
+ } catch {
635
+ noteDropped(window, null);
636
+ }
637
+ } else if (discardingOversized) {
638
+ noteDropped(window, null);
639
+ }
640
+ } catch (error) {
641
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") noteDropped(window, null);
642
+ } finally {
643
+ if (fd !== null) {
644
+ try { closeSync(fd); } catch { /* fail open */ }
645
+ }
646
+ }
647
+ compactWindow(window);
648
+ return window;
649
+ }
650
+
651
+ interface ScratchEvidence {
652
+ validBytes: number;
653
+ validatedRows: number;
654
+ hadDropped: boolean;
655
+ droppedThrough: string | null;
656
+ onMemorySample?: (bytes: number) => void;
657
+ }
658
+
659
+ function noteScratchDrop(evidence: ScratchEvidence, ts: string | null): void {
660
+ evidence.hadDropped = true;
661
+ evidence.droppedThrough = advanceTimestamp(evidence.droppedThrough, ts);
662
+ }
663
+
664
+ function comparableMemoryFootprint(): number {
665
+ const unsafe = Bun.unsafe as typeof Bun.unsafe & {
666
+ memoryFootprint?: () => number | undefined;
667
+ };
668
+ const native = unsafe.memoryFootprint?.();
669
+ if (typeof native === "number") return native;
670
+ if (process.platform === "linux") {
671
+ try {
672
+ const match = readFileSync("/proc/self/smaps_rollup", "utf8").match(/^Pss:\s+(\d+)\s+kB$/m);
673
+ if (match) return Number(match[1]) * 1024;
674
+ } catch {
675
+ /* Fall through to portable RSS. */
676
+ }
677
+ }
678
+ return process.memoryUsage().rss;
679
+ }
680
+
681
+ function writeScratchLine(
682
+ scratchFd: number,
683
+ evidence: ScratchEvidence,
684
+ line: Buffer,
685
+ hasNewline: boolean,
686
+ ): void {
687
+ try {
688
+ const json = hasNewline ? line.subarray(0, -1) : line;
689
+ if (!isCanonicalTimingRow(json)) {
690
+ const text = json.toString("utf8");
691
+ const parsed = JSON.parse(text) as unknown;
692
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
693
+ noteScratchDrop(evidence, null);
694
+ return;
695
+ }
696
+ }
697
+ writeFileSync(scratchFd, line);
698
+ evidence.validBytes += line.length;
699
+ evidence.validatedRows++;
700
+ if (!hasNewline) {
701
+ writeFileSync(scratchFd, "\n");
702
+ evidence.validBytes += 1;
703
+ }
704
+ // JSON.parse creates one short-lived object per legacy row. Force periodic
705
+ // collection so small-row ledgers stay bounded under Linux/JSC as well as
706
+ // macOS; this path runs only during one-time oversized migration.
707
+ if (evidence.validatedRows % 16_384 === 0) {
708
+ Bun.gc(true);
709
+ evidence.onMemorySample?.(comparableMemoryFootprint());
710
+ }
711
+ } catch {
712
+ noteScratchDrop(evidence, null);
713
+ }
714
+ }
715
+
716
+ /** Validate a legacy ledger into a disk-backed, complete-JSONL scratch file. */
717
+ function streamJsonlToScratch(
718
+ path: string,
719
+ scratchFd: number,
720
+ onMemorySample?: (bytes: number) => void,
721
+ ): ScratchEvidence {
722
+ const evidence: ScratchEvidence = {
723
+ validBytes: 0,
724
+ validatedRows: 0,
725
+ hadDropped: false,
726
+ droppedThrough: null,
727
+ onMemorySample,
728
+ };
729
+ let sourceFd: number | null = null;
730
+ let pending: Buffer = Buffer.alloc(0);
731
+ let discardingOversized = false;
732
+ let oversizedPrefix: Buffer = Buffer.alloc(0);
733
+ try {
734
+ sourceFd = openSync(path, constants.O_RDONLY);
735
+ const chunk = Buffer.allocUnsafe(SCAN_CHUNK_BYTES);
736
+ let position = 0;
737
+ for (;;) {
738
+ const count = readSync(sourceFd, chunk, 0, chunk.length, position);
739
+ if (count === 0) break;
740
+ position += count;
741
+ let offset = 0;
742
+ while (offset < count) {
743
+ if (discardingOversized) {
744
+ const newline = findNewline(chunk, offset, count);
745
+ const end = newline < 0 ? count : newline + 1;
746
+ oversizedPrefix = appendPrefix(oversizedPrefix, chunk.subarray(offset, end));
747
+ if (newline < 0) break;
748
+ noteScratchDrop(evidence, timestampFromOversizedPrefix(oversizedPrefix));
749
+ oversizedPrefix = Buffer.alloc(0);
750
+ discardingOversized = false;
751
+ offset = newline + 1;
752
+ continue;
753
+ }
754
+
755
+ const newline = findNewline(chunk, offset, count);
756
+ if (newline < 0) {
757
+ const remainder = chunk.subarray(offset, count);
758
+ if (pending.length + remainder.length > HOOK_TIMING_FILE_MAX_BYTES) {
759
+ oversizedPrefix = appendPrefix(oversizedPrefix, pending);
760
+ oversizedPrefix = appendPrefix(oversizedPrefix, remainder);
761
+ pending = Buffer.alloc(0);
762
+ discardingOversized = true;
763
+ } else {
764
+ pending = Buffer.concat([pending, remainder]);
765
+ }
766
+ break;
767
+ }
768
+
769
+ const fragment = chunk.subarray(offset, newline + 1);
770
+ if (pending.length + fragment.length > HOOK_TIMING_FILE_MAX_BYTES) {
771
+ oversizedPrefix = appendPrefix(oversizedPrefix, pending);
772
+ oversizedPrefix = appendPrefix(oversizedPrefix, fragment);
773
+ noteScratchDrop(evidence, timestampFromOversizedPrefix(oversizedPrefix));
774
+ oversizedPrefix = Buffer.alloc(0);
775
+ pending = Buffer.alloc(0);
776
+ } else if (pending.length === 0) {
777
+ writeScratchLine(scratchFd, evidence, fragment, true);
778
+ } else {
779
+ const line = Buffer.concat([pending, fragment]);
780
+ pending = Buffer.alloc(0);
781
+ writeScratchLine(scratchFd, evidence, line, true);
782
+ }
783
+ offset = newline + 1;
784
+ }
785
+ }
786
+
787
+ if (pending.length > 0) writeScratchLine(scratchFd, evidence, pending, false);
788
+ else if (discardingOversized) noteScratchDrop(evidence, null);
789
+ } catch (error) {
790
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") noteScratchDrop(evidence, null);
791
+ } finally {
792
+ if (sourceFd !== null) {
793
+ try { closeSync(sourceFd); } catch { /* fail open */ }
794
+ }
795
+ }
796
+ return evidence;
797
+ }
798
+
799
+ function findTailStart(fd: number, end: number, maxBytes: number): number {
800
+ if (end <= maxBytes) return 0;
801
+ const target = end - maxBytes;
802
+ const previous = Buffer.allocUnsafe(1);
803
+ if (readSync(fd, previous, 0, 1, target - 1) === 1 && previous[0] === 0x0a) {
804
+ return target;
805
+ }
806
+ const chunk = Buffer.allocUnsafe(SCAN_CHUNK_BYTES);
807
+ let position = target;
808
+ while (position < end) {
809
+ const count = readSync(fd, chunk, 0, Math.min(chunk.length, end - position), position);
810
+ if (count === 0) break;
811
+ const newline = findNewline(chunk, 0, count);
812
+ if (newline >= 0) return position + newline + 1;
813
+ position += count;
814
+ }
815
+ return end;
816
+ }
817
+
818
+ function newestTimestampInRange(fd: number, start: number, end: number): string | null {
819
+ let newest: string | null = null;
820
+ let pending: Buffer = Buffer.alloc(0);
821
+ const chunk = Buffer.allocUnsafe(SCAN_CHUNK_BYTES);
822
+ let position = start;
823
+ while (position < end) {
824
+ const count = readSync(fd, chunk, 0, Math.min(chunk.length, end - position), position);
825
+ if (count === 0) break;
826
+ position += count;
827
+ let offset = 0;
828
+ while (offset < count) {
829
+ const newline = findNewline(chunk, offset, count);
830
+ if (newline < 0) {
831
+ pending = Buffer.concat([pending, chunk.subarray(offset, count)]);
832
+ break;
833
+ }
834
+ const fragment = chunk.subarray(offset, newline + 1);
835
+ const line = pending.length === 0 ? fragment : Buffer.concat([pending, fragment]);
836
+ pending = Buffer.alloc(0);
837
+ newest = advanceTimestamp(newest, timestampFromOversizedPrefix(line));
838
+ offset = newline + 1;
839
+ }
840
+ }
841
+ return newest;
842
+ }
843
+
844
+ function cleanupMigrationScratch(path: string): void {
845
+ const directory = dirname(path);
846
+ const prefix = `.${basename(path)}.migrate-`;
847
+ try {
848
+ for (const name of readdirSync(directory)) {
849
+ if (name.startsWith(prefix) && name.endsWith(".tmp")) {
850
+ try { rmSync(join(directory, name), { force: true }); } catch { /* fail open */ }
851
+ }
852
+ }
853
+ fsyncDirectory(directory);
854
+ } catch {
855
+ /* fail open */
856
+ }
857
+ }
858
+
859
+ function parseBatch(input: Buffer): RowEntry[] | null {
860
+ const entries: RowEntry[] = [];
861
+ let offset = 0;
862
+ while (offset < input.length) {
863
+ const newline = input.indexOf(0x0a, offset);
864
+ if (newline < 0) return null;
865
+ const line = input.subarray(offset, newline + 1);
866
+ try {
867
+ const parsed = JSON.parse(line.subarray(0, -1).toString("utf8")) as unknown;
868
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
869
+ entries.push({
870
+ data: line,
871
+ ts: timestampFromRecord(parsed as Record<string, unknown>),
872
+ });
873
+ } catch {
874
+ return null;
875
+ }
876
+ offset = newline + 1;
877
+ }
878
+ return entries;
879
+ }
880
+
881
+ function currentEntries(window: RowWindow): RowEntry[] {
882
+ return window.entries.slice(window.head);
883
+ }
884
+
885
+ function timestampOfEntries(entries: readonly RowEntry[], initial: string | null = null): string | null {
886
+ let newest = initial;
887
+ for (const entry of entries) newest = advanceTimestamp(newest, entry.ts);
888
+ return newest;
889
+ }
890
+
891
+ function fileSize(path: string): number {
892
+ try { return statSync(path).size; } catch { return 0; }
893
+ }
894
+
895
+ function hardenFileIfPresent(path: string): void {
896
+ if (existsSync(path)) chmodSync(path, 0o600);
897
+ }
898
+
899
+ function endsWithNewline(path: string, size: number): boolean {
900
+ if (size === 0) return true;
901
+ let fd: number | null = null;
902
+ try {
903
+ fd = openSync(path, constants.O_RDONLY);
904
+ const byte = Buffer.allocUnsafe(1);
905
+ return readSync(fd, byte, 0, 1, size - 1) === 1 && byte[0] === 0x0a;
906
+ } catch {
907
+ return false;
908
+ } finally {
909
+ if (fd !== null) {
910
+ try { closeSync(fd); } catch { /* fail open */ }
911
+ }
912
+ }
913
+ }
914
+
915
+ function readDroppedThrough(metaPath: string): string | null {
916
+ try {
917
+ const value = JSON.parse(readFileSync(metaPath, "utf8"));
918
+ return value?.schemaVersion === 1 && typeof value?.droppedThrough === "string"
919
+ ? value.droppedThrough
920
+ : null;
921
+ } catch {
922
+ return null;
923
+ }
924
+ }
925
+
926
+ function writeDropMetadata(metaPath: string, droppedThrough: string | null): void {
927
+ const updatedAt = new Date().toISOString();
928
+ const metadata: DropMetadata = {
929
+ schemaVersion: 1,
930
+ droppedThrough: advanceTimestamp(readDroppedThrough(metaPath), droppedThrough),
931
+ updatedAt,
932
+ };
933
+ atomicWrite(metaPath, JSON.stringify(metadata) + "\n");
934
+ }
935
+
936
+ function appendSecure(path: string, batch: Buffer): void {
937
+ const existed = existsSync(path);
938
+ const fd = openSync(
939
+ path,
940
+ constants.O_CREAT | constants.O_APPEND | constants.O_WRONLY,
941
+ 0o600,
942
+ );
943
+ try {
944
+ writeFileSync(fd, batch);
945
+ fsyncSync(fd);
946
+ } finally {
947
+ closeSync(fd);
948
+ }
949
+ chmodSync(path, 0o600);
950
+ if (!existed) fsyncDirectory(dirname(path));
951
+ }
952
+
953
+ /**
954
+ * Append one bounded complete-JSONL batch under a portable cross-process lock.
955
+ * The whole size-check/migration/rotation/append sequence is one transaction.
956
+ * Every failure is swallowed so hook telemetry can never break a tool call.
957
+ */
958
+ export function appendHookTimingBatch(
959
+ path: string,
960
+ batchText: string,
961
+ options: {
962
+ lockWaitMs?: number;
963
+ /** @internal Platform-comparable migration memory sampling for tests. */
964
+ onMigrationMemorySample?: (bytes: number) => void;
965
+ } = {},
966
+ ): HookTimingWriteResult {
967
+ try {
968
+ const batch = Buffer.from(batchText, "utf8");
969
+ const batchEntries = parseBatch(batch);
970
+ if (
971
+ batch.length === 0 ||
972
+ batch.length > HOOK_TIMING_BATCH_MAX_BYTES ||
973
+ batch[batch.length - 1] !== 0x0a ||
974
+ batchEntries === null
975
+ ) return "failed";
976
+
977
+ const directory = dirname(path);
978
+ hardenDirectory(directory);
979
+ const lockPath = `${path}.lock`;
980
+ const lock = acquireLock(lockPath, options.lockWaitMs ?? LOCK_WAIT_MS);
981
+ if (!lock.owner) return lock.result;
982
+ const owner = lock.owner;
983
+
984
+ try {
985
+ const retainedPath = `${path}.1`;
986
+ const metaPath = `${path}.meta.json`;
987
+ const activeSize = fileSize(path);
988
+ const retainedSize = fileSize(retainedPath);
989
+ hardenFileIfPresent(path);
990
+ hardenFileIfPresent(retainedPath);
991
+ hardenFileIfPresent(metaPath);
992
+ cleanupMigrationScratch(path);
993
+
994
+ // Normal appends avoid reading or parsing the existing ledger. Files
995
+ // produced here are already valid JSONL; the final-byte check detects
996
+ // interrupted/foreign writes and sends only those through recovery.
997
+ if (
998
+ activeSize <= HOOK_TIMING_FILE_MAX_BYTES &&
999
+ activeSize + batch.length <= HOOK_TIMING_FILE_MAX_BYTES &&
1000
+ endsWithNewline(path, activeSize) &&
1001
+ retainedSize <= HOOK_TIMING_FILE_MAX_BYTES &&
1002
+ endsWithNewline(retainedPath, retainedSize)
1003
+ ) {
1004
+ appendSecure(path, batch);
1005
+ return "written";
1006
+ }
1007
+
1008
+ if (activeSize > HOOK_TIMING_FILE_MAX_BYTES) {
1009
+ const scratchPath = join(
1010
+ dirname(path),
1011
+ `.${basename(path)}.migrate-${process.pid}-${randomUUID()}.tmp`,
1012
+ );
1013
+ let scratchFd: number | null = null;
1014
+ try {
1015
+ scratchFd = openSync(
1016
+ scratchPath,
1017
+ constants.O_CREAT | constants.O_EXCL | constants.O_RDWR,
1018
+ 0o600,
1019
+ );
1020
+ chmodSync(scratchPath, 0o600);
1021
+ const evidence = streamJsonlToScratch(
1022
+ path,
1023
+ scratchFd,
1024
+ options.onMigrationMemorySample,
1025
+ );
1026
+ for (const entry of batchEntries) {
1027
+ writeFileSync(scratchFd, entry.data);
1028
+ evidence.validBytes += entry.data.length;
1029
+ }
1030
+ fsyncSync(scratchFd);
1031
+
1032
+ const activeStart = findTailStart(
1033
+ scratchFd,
1034
+ evidence.validBytes,
1035
+ HOOK_TIMING_FILE_MAX_BYTES,
1036
+ );
1037
+ const retainedStart = findTailStart(
1038
+ scratchFd,
1039
+ activeStart,
1040
+ HOOK_TIMING_FILE_MAX_BYTES,
1041
+ );
1042
+ let droppedThrough = evidence.droppedThrough;
1043
+ if (retainedStart > 0) {
1044
+ droppedThrough = advanceTimestamp(
1045
+ droppedThrough,
1046
+ newestTimestampInRange(scratchFd, 0, retainedStart),
1047
+ );
1048
+ }
1049
+ const retainedHistory = scanJsonlFile(retainedPath, 0);
1050
+ droppedThrough = advanceTimestamp(droppedThrough, retainedHistory.droppedThrough);
1051
+ const metadataNeeded =
1052
+ retainedSize > 0 ||
1053
+ retainedStart > 0 ||
1054
+ evidence.hadDropped ||
1055
+ retainedHistory.hadDropped;
1056
+ if (metadataNeeded) writeDropMetadata(metaPath, droppedThrough);
1057
+ atomicCopyRange(retainedPath, scratchFd, retainedStart, activeStart);
1058
+ atomicCopyRange(path, scratchFd, activeStart, evidence.validBytes);
1059
+ return "written";
1060
+ } finally {
1061
+ if (scratchFd !== null) {
1062
+ try { closeSync(scratchFd); } catch { /* fail open */ }
1063
+ }
1064
+ try { rmSync(scratchPath, { force: true }); } catch { /* fail open */ }
1065
+ fsyncDirectory(dirname(path));
1066
+ }
1067
+ }
1068
+
1069
+ const active = scanJsonlFile(path, HOOK_TIMING_FILE_MAX_BYTES);
1070
+ const retained = scanJsonlFile(retainedPath, HOOK_TIMING_FILE_MAX_BYTES);
1071
+
1072
+ if (active.bytes + batch.length > HOOK_TIMING_FILE_MAX_BYTES) {
1073
+ if (retainedSize > 0 || active.hadDropped) {
1074
+ writeDropMetadata(
1075
+ metaPath,
1076
+ advanceTimestamp(
1077
+ timestampOfEntries(currentEntries(retained), retained.droppedThrough),
1078
+ active.droppedThrough,
1079
+ ),
1080
+ );
1081
+ }
1082
+ atomicWrite(retainedPath, currentEntries(active).map((entry) => entry.data));
1083
+ atomicWrite(path, batch);
1084
+ return "written";
1085
+ }
1086
+
1087
+ if (retained.hadDropped || active.hadDropped) {
1088
+ writeDropMetadata(
1089
+ metaPath,
1090
+ advanceTimestamp(retained.droppedThrough, active.droppedThrough),
1091
+ );
1092
+ }
1093
+ if (retainedSize > HOOK_TIMING_FILE_MAX_BYTES || retained.hadDropped) {
1094
+ atomicWrite(retainedPath, currentEntries(retained).map((entry) => entry.data));
1095
+ }
1096
+
1097
+ if (active.hadDropped || activeSize !== active.bytes) {
1098
+ atomicWrite(path, currentEntries(active).map((entry) => entry.data));
1099
+ } else if (existsSync(path)) {
1100
+ chmodSync(path, 0o600);
1101
+ }
1102
+ appendSecure(path, batch);
1103
+ return "written";
1104
+ } finally {
1105
+ releaseLock(lockPath, owner);
1106
+ }
1107
+ } catch {
1108
+ /* telemetry is fail open */
1109
+ return "failed";
1110
+ }
1111
+ }
1112
+
1113
+ interface TimingWorkerRequest {
1114
+ entries: Array<{ path: string; batches: string[] }>;
1115
+ }
1116
+
1117
+ interface TimingWorkerResponse {
1118
+ contended: Array<{ path: string; batch: string }>;
1119
+ }
1120
+
1121
+ if (!Bun.isMainThread) {
1122
+ globalThis.onmessage = (event: MessageEvent<TimingWorkerRequest>) => {
1123
+ const contended: TimingWorkerResponse["contended"] = [];
1124
+ for (const { path, batches } of event.data.entries) {
1125
+ for (const batch of batches) {
1126
+ if (appendHookTimingBatch(path, batch) === "contended") {
1127
+ contended.push({ path, batch });
1128
+ }
1129
+ }
1130
+ }
1131
+ globalThis.postMessage({ contended } satisfies TimingWorkerResponse);
1132
+ };
1133
+ }