lazycodex-ai 5.1.48 → 5.1.51

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 (437) hide show
  1. package/dist/cli/index.js +1194 -752
  2. package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
  3. package/dist/cli/service-auth/commands.d.ts +2 -4
  4. package/dist/cli/service-auth/index.d.ts +1 -1
  5. package/dist/cli/service-auth/keystore.d.ts +26 -2
  6. package/dist/cli/service-auth/protocol.d.ts +7 -1
  7. package/dist/cli/service-auth/session.d.ts +6 -0
  8. package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
  9. package/dist/cli-node/index.js +1194 -752
  10. package/package.json +3 -1
  11. package/packages/lsp-core/package.json +45 -0
  12. package/packages/lsp-core/src/index.ts +30 -0
  13. package/packages/lsp-core/src/lsp/bun-command-output.ts +82 -0
  14. package/packages/lsp-core/src/lsp/bun-project-checks.test.ts +170 -0
  15. package/packages/lsp-core/src/lsp/bun-project-checks.ts +223 -0
  16. package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
  17. package/packages/lsp-core/src/lsp/cleanup-errors.ts +14 -0
  18. package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +52 -0
  19. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +471 -0
  20. package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
  21. package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
  22. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +213 -0
  23. package/packages/lsp-core/src/lsp/client-wrapper.ts +290 -0
  24. package/packages/lsp-core/src/lsp/client.ts +398 -0
  25. package/packages/lsp-core/src/lsp/config-loader.ts +254 -0
  26. package/packages/lsp-core/src/lsp/connection.ts +95 -0
  27. package/packages/lsp-core/src/lsp/constants.ts +17 -0
  28. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +90 -0
  29. package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +221 -0
  30. package/packages/lsp-core/src/lsp/directory-diagnostics.ts +187 -0
  31. package/packages/lsp-core/src/lsp/effective-extension.ts +10 -0
  32. package/packages/lsp-core/src/lsp/errors.ts +89 -0
  33. package/packages/lsp-core/src/lsp/fake-bun-test-support.ts +102 -0
  34. package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
  35. package/packages/lsp-core/src/lsp/fixtures/disposed-stdin-epipe-probe.ts +28 -0
  36. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
  37. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +234 -0
  38. package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
  39. package/packages/lsp-core/src/lsp/format-document.ts +90 -0
  40. package/packages/lsp-core/src/lsp/formatters.ts +144 -0
  41. package/packages/lsp-core/src/lsp/infer-extension.ts +66 -0
  42. package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
  43. package/packages/lsp-core/src/lsp/json-rpc-connection.ts +369 -0
  44. package/packages/lsp-core/src/lsp/language-mappings.ts +173 -0
  45. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  46. package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
  47. package/packages/lsp-core/src/lsp/manager.ts +495 -0
  48. package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
  49. package/packages/lsp-core/src/lsp/process-pipe-errors.test.ts +21 -0
  50. package/packages/lsp-core/src/lsp/process-signal-cleanup.ts +21 -0
  51. package/packages/lsp-core/src/lsp/process.ts +221 -0
  52. package/packages/lsp-core/src/lsp/server-definitions.ts +205 -0
  53. package/packages/lsp-core/src/lsp/server-install-state.ts +67 -0
  54. package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
  55. package/packages/lsp-core/src/lsp/server-installation.ts +218 -0
  56. package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
  57. package/packages/lsp-core/src/lsp/server-resolution.ts +117 -0
  58. package/packages/lsp-core/src/lsp/startup-failure.ts +1 -0
  59. package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
  60. package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
  61. package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
  62. package/packages/lsp-core/src/lsp/transport.ts +339 -0
  63. package/packages/lsp-core/src/lsp/types.ts +134 -0
  64. package/packages/lsp-core/src/lsp/utils.test.ts +72 -0
  65. package/packages/lsp-core/src/lsp/utils.ts +40 -0
  66. package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
  67. package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +218 -0
  68. package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
  69. package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +194 -0
  70. package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
  71. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +150 -0
  72. package/packages/lsp-core/src/lsp/workspace-document-state.ts +392 -0
  73. package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +132 -0
  74. package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
  75. package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
  76. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
  77. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
  78. package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
  79. package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
  80. package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
  81. package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
  82. package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
  83. package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
  84. package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
  85. package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
  86. package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
  87. package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
  88. package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
  89. package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
  90. package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
  91. package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
  92. package/packages/lsp-core/src/lsp/workspace-edit.ts +55 -0
  93. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -0
  94. package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
  95. package/packages/lsp-core/src/mcp-protocol-pin.test.ts +97 -0
  96. package/packages/lsp-core/src/mcp.ts +122 -0
  97. package/packages/lsp-core/src/missing-dependency-result.test.ts +135 -0
  98. package/packages/lsp-core/src/missing-dependency-result.ts +79 -0
  99. package/packages/lsp-core/src/post-edit/file-location.ts +55 -0
  100. package/packages/lsp-core/src/post-edit/index.ts +3 -0
  101. package/packages/lsp-core/src/post-edit/not-installed-guidance.ts +36 -0
  102. package/packages/lsp-core/src/post-edit/not-installed.test.ts +224 -0
  103. package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
  104. package/packages/lsp-core/src/post-edit/orchestration.ts +205 -0
  105. package/packages/lsp-core/src/request-context.test.ts +200 -0
  106. package/packages/lsp-core/src/request-context.ts +235 -0
  107. package/packages/lsp-core/src/tool-surface.test.ts +173 -0
  108. package/packages/lsp-core/src/tools/bun-directory-diagnostics.test.ts +113 -0
  109. package/packages/lsp-core/src/tools/bun-directory-diagnostics.ts +75 -0
  110. package/packages/lsp-core/src/tools/definitions.ts +156 -0
  111. package/packages/lsp-core/src/tools/diagnostics.ts +133 -0
  112. package/packages/lsp-core/src/tools/format.test.ts +56 -0
  113. package/packages/lsp-core/src/tools/format.ts +56 -0
  114. package/packages/lsp-core/src/tools/index.ts +11 -0
  115. package/packages/lsp-core/src/tools/install-decision.ts +39 -0
  116. package/packages/lsp-core/src/tools/navigation.ts +88 -0
  117. package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
  118. package/packages/lsp-core/src/tools/parameters.ts +49 -0
  119. package/packages/lsp-core/src/tools/rename.ts +69 -0
  120. package/packages/lsp-core/src/tools/result.ts +5 -0
  121. package/packages/lsp-core/src/tools/runtime.ts +21 -0
  122. package/packages/lsp-core/src/tools/schema.ts +9 -0
  123. package/packages/lsp-core/src/tools/status.ts +28 -0
  124. package/packages/lsp-core/src/tools/symbols.ts +99 -0
  125. package/packages/lsp-core/src/tools/types.ts +116 -0
  126. package/packages/lsp-core/src/tools.ts +1 -0
  127. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  128. package/packages/omo-codex/plugin/README.md +1 -1
  129. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  130. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  131. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  132. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  133. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  134. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  135. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  136. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  137. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  138. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  139. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
  140. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
  141. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  142. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  143. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  144. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  145. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  146. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  147. package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
  148. package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
  149. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
  150. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
  151. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  152. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  153. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
  154. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
  155. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
  156. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
  157. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
  158. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
  159. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  160. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  161. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  162. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  163. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  164. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
  165. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  166. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  167. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  168. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  169. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  170. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  171. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  188. package/packages/omo-codex/plugin/package-lock.json +16 -16
  189. package/packages/omo-codex/plugin/package.json +1 -1
  190. package/packages/omo-codex/plugin/shared/package.json +1 -1
  191. package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
  192. package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
  193. package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
  194. package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
  195. package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  196. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  197. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
  198. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
  199. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  200. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
  201. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  202. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  203. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
  204. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
  205. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
  206. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
  207. package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
  208. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
  209. package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
  210. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  211. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  212. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  213. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  214. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
  215. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  216. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
  217. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  218. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
  219. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
  220. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  221. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  222. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
  223. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
  224. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -316
  225. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +23 -21
  226. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  227. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
  228. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  229. package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
  230. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +12 -5
  231. package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
  232. package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
  233. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  234. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +13 -4
  235. package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  236. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  237. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
  238. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
  239. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  240. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  241. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  242. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  243. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  244. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  245. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  246. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  247. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  248. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  249. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  250. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  251. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  252. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  253. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  254. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  255. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  256. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  257. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  258. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  259. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  260. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
  261. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
  262. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
  263. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  264. package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  265. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
  266. package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  267. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
  268. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  269. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
  270. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
  271. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  272. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  273. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  274. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  275. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
  276. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +79 -26
  277. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  278. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  279. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
  280. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  281. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
  282. package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
  283. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
  284. package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
  285. package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
  286. package/packages/shared-skills/skill-source-filter.mjs +6 -2
  287. package/packages/shared-skills/skills/ast-grep/README.md +1 -1
  288. package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
  289. package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
  290. package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
  291. package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  292. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  293. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
  294. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
  295. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  296. package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
  297. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  298. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  299. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
  300. package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
  301. package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
  302. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
  303. package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
  304. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
  305. package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
  306. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  307. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  308. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
  309. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  310. package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
  311. package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
  312. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  313. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  314. package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
  315. package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -316
  316. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +23 -21
  317. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  318. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
  319. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  320. package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
  321. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +12 -5
  322. package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
  323. package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  325. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  326. package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  328. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +13 -4
  329. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -110
  330. package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
  336. package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  337. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
  338. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  339. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  340. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
  341. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  342. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  343. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  344. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  345. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  346. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  347. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  348. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  349. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  350. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  351. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  352. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  353. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  354. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  355. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  356. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  357. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  358. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  359. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  360. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  361. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  362. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  363. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  364. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  366. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  367. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  368. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  369. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  370. package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  371. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
  372. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
  373. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
  374. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  375. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  376. package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  377. package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  378. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
  379. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  380. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  381. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
  382. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
  383. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  384. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  385. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
  386. package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  387. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
  388. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  389. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  390. package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  394. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  396. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  398. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  399. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
  400. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +5 -800
  401. package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  402. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +79 -26
  403. package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  404. package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  405. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  406. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  407. package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
  408. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  409. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  410. package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  411. package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  412. package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  413. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  414. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  415. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  416. package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  417. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  418. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  419. package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  420. package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  421. package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  422. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
  423. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
  424. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  425. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  426. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  427. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  428. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  429. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
  430. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
  431. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
  432. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
  433. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
  434. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
  435. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
  436. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
  437. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
@@ -0,0 +1,196 @@
1
+ import { readFileSync, realpathSync, writeFileSync } from "node:fs";
2
+ import { pathToFileURL } from "node:url";
3
+
4
+ import {
5
+ createWorkspaceEditTestHarness,
6
+ diagnostic,
7
+ readEvents,
8
+ renameTextEdit,
9
+ waitForEventCount,
10
+ } from "../workspace-apply-edit-test-support.js";
11
+ import { workspaceEditContractFailureEvidence } from "../workspace-edit-contract-evidence.js";
12
+
13
+ class WorkspaceEditContractProbeError extends Error {
14
+ override readonly name = "WorkspaceEditContractProbeError";
15
+ }
16
+
17
+ function requireProbe(condition: boolean, message: string): asserts condition {
18
+ if (!condition) throw new WorkspaceEditContractProbeError(message);
19
+ }
20
+
21
+ function isRecord(value: unknown): value is Record<string, unknown> {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+
25
+ function applyResponses(eventsPath: string): readonly unknown[] {
26
+ return readEvents(eventsPath)
27
+ .filter((event) => event.type === "clientResponse" && event.method === "workspace/applyEdit")
28
+ .map((event) => event.result);
29
+ }
30
+
31
+ function isRejectedApply(value: unknown): boolean {
32
+ return isRecord(value) && value["applied"] === false && typeof value["failureReason"] === "string";
33
+ }
34
+
35
+ function didChangeVersions(eventsPath: string): readonly number[] {
36
+ const versions: number[] = [];
37
+ for (const event of readEvents(eventsPath)) {
38
+ if (event.type !== "clientNotification" || event.method !== "textDocument/didChange") continue;
39
+ if (!isRecord(event.params)) continue;
40
+ const textDocument = event.params["textDocument"];
41
+ if (!isRecord(textDocument) || typeof textDocument["version"] !== "number") continue;
42
+ versions.push(textDocument["version"]);
43
+ }
44
+ return versions;
45
+ }
46
+
47
+ const outputPath = process.argv[2];
48
+ if (!outputPath) throw new WorkspaceEditContractProbeError("output path is required");
49
+
50
+ const harness = createWorkspaceEditTestHarness();
51
+ try {
52
+ const success = await harness.makeClient({
53
+ renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 1 }), renameResult: "same" }],
54
+ diagnostics: [diagnostic("todo3-fresh")],
55
+ });
56
+ let successWrites = 0;
57
+ success.client.setWorkspaceEditIo({
58
+ writeFile(path, content) {
59
+ successWrites += 1;
60
+ writeFileSync(path, content, "utf-8");
61
+ },
62
+ });
63
+ await success.client.openFile(success.source);
64
+ const successResult = await success.client.rename(success.source, 1, 6, "after");
65
+ const successResponses = applyResponses(success.events);
66
+ const successVersions = didChangeVersions(success.events);
67
+ const successUri = pathToFileURL(realpathSync(success.source)).href;
68
+ const successEvidence = {
69
+ serverAppliedRename:
70
+ successResponses.length === 1 && isRecord(successResponses[0]) && successResponses[0]["applied"] === true,
71
+ recordedResultReused: successResult.edit !== null && successResult.apply.success && successWrites === 1,
72
+ writeCount: successWrites,
73
+ secondWritePrevented: successWrites === 1 && successVersions.length === 1,
74
+ synchronizedDocumentVersion: success.client.getOpenDocumentVersion(success.source) ?? null,
75
+ didChangeVersions: successVersions,
76
+ immediateDiagnostics: success.client
77
+ .getStoredDiagnostics(successUri)
78
+ .some((entry) => entry.message === "todo3-fresh"),
79
+ finalContent: readFileSync(success.source, "utf-8"),
80
+ };
81
+ requireProbe(successEvidence.serverAppliedRename, "successful server apply was not reported truthfully");
82
+ requireProbe(successEvidence.recordedResultReused, "rename did not reuse the recorded server result");
83
+ requireProbe(successEvidence.secondWritePrevented, "server edit was written or synchronized more than once");
84
+ requireProbe(successEvidence.synchronizedDocumentVersion === 2, "open document version did not advance to 2");
85
+ requireProbe(successEvidence.immediateDiagnostics, "fresh diagnostics were not visible immediately after applyEdit");
86
+ requireProbe(successEvidence.finalContent === "const after = 1;\n", "successful rename produced unexpected bytes");
87
+
88
+ const unscoped = await harness.makeClient({
89
+ unscopedApplyEdit: renameTextEdit("before", "after", { version: 1 }),
90
+ });
91
+ const unscopedResponses = await waitForEventCount(
92
+ unscoped.events,
93
+ (event) => event.type === "clientResponse" && event.method === "workspace/applyEdit",
94
+ 1,
95
+ );
96
+ const unscopedFailure = unscopedResponses[0]?.result;
97
+ requireProbe(isRejectedApply(unscopedFailure), "unscoped applyEdit was not rejected");
98
+ requireProbe(readFileSync(unscoped.source, "utf-8") === "const before = 1;\n", "unscoped failure mutated bytes");
99
+
100
+ const concurrent = await harness.makeClient({
101
+ renameSteps: [
102
+ {
103
+ applyEdit: renameTextEdit("before", "after", { version: 1 }),
104
+ applyConcurrently: true,
105
+ renameResult: "same",
106
+ },
107
+ ],
108
+ });
109
+ let concurrentWrites = 0;
110
+ concurrent.client.setWorkspaceEditIo({
111
+ writeFile(path, content) {
112
+ concurrentWrites += 1;
113
+ writeFileSync(path, content, "utf-8");
114
+ },
115
+ });
116
+ await concurrent.client.openFile(concurrent.source);
117
+ const concurrentResult = await concurrent.client.rename(concurrent.source, 1, 6, "after");
118
+ const concurrentFailure = applyResponses(concurrent.events).find(isRejectedApply);
119
+ requireProbe(concurrentResult.apply.success, "the winning concurrent applyEdit did not complete");
120
+ requireProbe(concurrentWrites === 1, "concurrent applyEdit wrote more than once");
121
+ requireProbe(isRejectedApply(concurrentFailure), "concurrent applyEdit loser was not rejected");
122
+
123
+ const mismatched = await harness.makeClient({
124
+ renameSteps: [{ applyEdit: renameTextEdit("before", "after", { version: 9 }), renameResult: "same" }],
125
+ });
126
+ await mismatched.client.openFile(mismatched.source);
127
+ const mismatchedResult = await mismatched.client.rename(mismatched.source, 1, 6, "after");
128
+ const mismatchedFailure = applyResponses(mismatched.events).find(isRejectedApply);
129
+ requireProbe(!mismatchedResult.apply.success, "mismatched document version unexpectedly succeeded");
130
+ requireProbe(isRejectedApply(mismatchedFailure), "mismatched version was not rejected over applyEdit");
131
+ requireProbe(readFileSync(mismatched.source, "utf-8") === "const before = 1;\n", "version failure mutated bytes");
132
+
133
+ const preGate = await harness.makeClient({
134
+ renameSteps: [
135
+ {
136
+ applyEdit: renameTextEdit("before", "after", { version: 1 }),
137
+ applyDelayMs: 100,
138
+ renameResult: "same",
139
+ },
140
+ ],
141
+ });
142
+ const controller = new AbortController();
143
+ await preGate.client.openFile(preGate.source);
144
+ const pendingRename = preGate.client.rename(preGate.source, 1, 6, "after", controller.signal);
145
+ await waitForEventCount(
146
+ preGate.events,
147
+ (event) => event.type === "clientRequest" && event.method === "textDocument/rename",
148
+ 1,
149
+ );
150
+ controller.abort();
151
+ const preGateError = await pendingRename.then(
152
+ () => null,
153
+ (error: unknown) => error,
154
+ );
155
+ const preGateMessage = preGateError instanceof Error ? preGateError.message : String(preGateError);
156
+ const preGateFailure = {
157
+ success: false,
158
+ filesModified: [],
159
+ totalEdits: 0,
160
+ errors: [preGateMessage],
161
+ };
162
+ requireProbe(preGateError !== null, "pre-gate cancellation unexpectedly succeeded");
163
+ requireProbe(
164
+ preGateFailure.errors.some((message) => /cancel|abort/i.test(message)),
165
+ "pre-gate cancellation returned the wrong failure",
166
+ );
167
+ requireProbe(readFileSync(preGate.source, "utf-8") === "const before = 1;\n", "pre-gate cancellation mutated bytes");
168
+
169
+ const failureCases = {
170
+ unscoped: workspaceEditContractFailureEvidence(unscopedFailure, unscoped.workspace),
171
+ concurrent: workspaceEditContractFailureEvidence(concurrentFailure, concurrent.workspace),
172
+ mismatched: workspaceEditContractFailureEvidence(mismatchedFailure, mismatched.workspace),
173
+ preGate: workspaceEditContractFailureEvidence(preGateFailure, preGate.workspace),
174
+ };
175
+ writeFileSync(
176
+ outputPath,
177
+ `${JSON.stringify(
178
+ {
179
+ schemaVersion: 1,
180
+ success: successEvidence,
181
+ failureCases,
182
+ failureHashes: {
183
+ unscoped: failureCases.unscoped.sha256,
184
+ concurrent: failureCases.concurrent.sha256,
185
+ mismatched: failureCases.mismatched.sha256,
186
+ preGate: failureCases.preGate.sha256,
187
+ },
188
+ },
189
+ null,
190
+ 2,
191
+ )}\n`,
192
+ "utf-8",
193
+ );
194
+ } finally {
195
+ await harness.cleanup();
196
+ }
@@ -0,0 +1,234 @@
1
+ import { appendFileSync, readFileSync } from "node:fs";
2
+
3
+ const [, , scenarioPath, eventsPath] = process.argv;
4
+ const scenario = JSON.parse(readFileSync(scenarioPath, "utf-8"));
5
+
6
+ let buffer = Buffer.alloc(0);
7
+ let nextServerRequestId = 10_000;
8
+ let renameIndex = 0;
9
+ let diagnosticIndex = 0;
10
+ const pendingServerRequests = new Map();
11
+ const releasedDiagnosticResponses = [];
12
+
13
+ function record(event) {
14
+ appendFileSync(eventsPath, `${JSON.stringify(event)}\n`, "utf-8");
15
+ }
16
+
17
+ function send(message) {
18
+ const body = Buffer.from(JSON.stringify(message), "utf-8");
19
+ process.stdout.write(`Content-Length: ${body.length}\r\n\r\n`);
20
+ process.stdout.write(body);
21
+ }
22
+
23
+ function sendResponse(id, result) {
24
+ send({ jsonrpc: "2.0", id, result });
25
+ }
26
+
27
+ function sendError(id, code, message) {
28
+ send({ jsonrpc: "2.0", id, error: { code, message } });
29
+ }
30
+
31
+ function sendApplyEdit(edit, context) {
32
+ const id = nextServerRequestId;
33
+ nextServerRequestId += 1;
34
+ pendingServerRequests.set(String(id), context);
35
+ record({ type: "serverRequest", method: "workspace/applyEdit", id, edit });
36
+ send({ jsonrpc: "2.0", id, method: "workspace/applyEdit", params: { edit } });
37
+ }
38
+
39
+ function renameResult(step) {
40
+ if (step.renameResult === "same") return step.applyEdit ?? null;
41
+ if (step.renameResult === "different") return step.differentEdit ?? null;
42
+ if (step.renameResult === "edit") return step.renameEdit ?? null;
43
+ return null;
44
+ }
45
+
46
+ function finishRename(context) {
47
+ const result = renameResult(context.step);
48
+ const delay = Number(context.step.responseDelayMs ?? 0);
49
+ setTimeout(() => {
50
+ record({ type: "serverResponse", method: "textDocument/rename", result });
51
+ sendResponse(context.renameRequestId, result);
52
+ }, delay);
53
+ }
54
+
55
+ function sendPublishDeliveryBarrier() {
56
+ const id = nextServerRequestId;
57
+ nextServerRequestId += 1;
58
+ pendingServerRequests.set(String(id), { kind: "publishDeliveryBarrier", method: "workspace/configuration" });
59
+ record({ type: "serverRequest", method: "workspace/configuration", id });
60
+ send({ jsonrpc: "2.0", id, method: "workspace/configuration", params: { items: [] } });
61
+ }
62
+
63
+ function schedulePublish(step, fallbackUri, fallbackVersion) {
64
+ const delay = Number(step.delayMs ?? 0);
65
+ setTimeout(() => {
66
+ const params = {
67
+ uri: step.uri ?? fallbackUri,
68
+ ...(step.version === undefined ? {} : { version: step.version }),
69
+ diagnostics: Array.isArray(step.diagnostics) ? step.diagnostics : [],
70
+ };
71
+ record({ type: "serverNotification", method: "textDocument/publishDiagnostics", params });
72
+ send({ jsonrpc: "2.0", method: "textDocument/publishDiagnostics", params });
73
+ if (step.awaitClientDelivery === true) sendPublishDeliveryBarrier();
74
+ }, delay);
75
+ }
76
+
77
+ function publishSteps(trigger) {
78
+ if (!Array.isArray(scenario.publishDiagnostics)) return [];
79
+ return scenario.publishDiagnostics.filter((step) => step?.trigger === trigger);
80
+ }
81
+
82
+ function applyPublishSteps(trigger, fallbackUri, fallbackVersion) {
83
+ for (const step of publishSteps(trigger)) schedulePublish(step, fallbackUri, fallbackVersion);
84
+ }
85
+
86
+ function handleClientResponse(message) {
87
+ const context = pendingServerRequests.get(String(message.id));
88
+ if (!context) return;
89
+ pendingServerRequests.delete(String(message.id));
90
+ record({
91
+ type: "clientResponse",
92
+ method: context.method ?? "workspace/applyEdit",
93
+ result: message.result ?? null,
94
+ error: message.error ?? null,
95
+ });
96
+ if (context.kind === "publishDeliveryBarrier" || context.kind === "unscoped") return;
97
+ if (typeof context.pendingApplyResponses === "number") {
98
+ context.pendingApplyResponses -= 1;
99
+ if (context.pendingApplyResponses > 0) return;
100
+ finishRename(context);
101
+ return;
102
+ }
103
+ if (context.step.applyTwice === true && context.applyCount === 1) {
104
+ sendApplyEdit(context.step.applyEdit, { ...context, applyCount: 2 });
105
+ return;
106
+ }
107
+ finishRename(context);
108
+ }
109
+
110
+ function handleRequest(message) {
111
+ if (message.method === "initialize") {
112
+ record({ type: "clientRequest", method: message.method, id: message.id, params: message.params });
113
+ sendResponse(message.id, { capabilities: scenario.capabilities ?? {} });
114
+ return;
115
+ }
116
+ if (message.method === "textDocument/rename") {
117
+ const step = scenario.renameSteps?.[renameIndex] ?? {};
118
+ renameIndex += 1;
119
+ record({ type: "clientRequest", method: message.method, id: message.id, params: message.params, renameIndex });
120
+ if (step.applyEdit) {
121
+ const context = { kind: "rename", renameRequestId: message.id, step, applyCount: 1 };
122
+ if (step.applyConcurrently === true) {
123
+ context.pendingApplyResponses = 2;
124
+ sendApplyEdit(step.applyEdit, context);
125
+ sendApplyEdit(step.applyEdit, context);
126
+ return;
127
+ }
128
+ const delay = Number(step.applyDelayMs ?? 0);
129
+ if (delay > 0) {
130
+ setTimeout(() => sendApplyEdit(step.applyEdit, context), delay);
131
+ return;
132
+ }
133
+ sendApplyEdit(step.applyEdit, context);
134
+ return;
135
+ }
136
+ finishRename({ renameRequestId: message.id, step });
137
+ return;
138
+ }
139
+ if (message.method === "textDocument/diagnostic") {
140
+ record({ type: "clientRequest", method: message.method, id: message.id, params: message.params, diagnosticIndex: diagnosticIndex + 1 });
141
+ applyPublishSteps("diagnosticRequest", message.params?.textDocument?.uri, undefined);
142
+ const step = Array.isArray(scenario.diagnosticResponses) ? scenario.diagnosticResponses[diagnosticIndex] : undefined;
143
+ diagnosticIndex += 1;
144
+ if (step?.action === "hang") return;
145
+ if (step?.action === "close") {
146
+ const delay = Number(step.delayMs ?? 0);
147
+ setTimeout(() => process.exit(0), delay);
148
+ return;
149
+ }
150
+ const sendDiagnosticResponse = () => {
151
+ if (step?.error) {
152
+ record({ type: "serverError", method: "textDocument/diagnostic", error: step.error });
153
+ sendError(message.id, step.error.code, step.error.message);
154
+ return;
155
+ }
156
+ const result = step?.report ?? { items: scenario.diagnostics ?? [] };
157
+ record({ type: "serverResponse", method: "textDocument/diagnostic", result });
158
+ sendResponse(message.id, result);
159
+ };
160
+ if (step?.releaseOnDidChange === true) {
161
+ releasedDiagnosticResponses.push(sendDiagnosticResponse);
162
+ } else {
163
+ const delay = Number(step?.delayMs ?? 0);
164
+ setTimeout(sendDiagnosticResponse, delay);
165
+ }
166
+ return;
167
+ }
168
+ if (message.method === "shutdown") {
169
+ sendResponse(message.id, null);
170
+ return;
171
+ }
172
+ sendResponse(message.id, null);
173
+ }
174
+
175
+ function handleNotification(message) {
176
+ record({ type: "clientNotification", method: message.method, params: message.params ?? null });
177
+ if (message.method === "initialized" && scenario.unscopedApplyEdit) {
178
+ sendApplyEdit(scenario.unscopedApplyEdit, { kind: "unscoped" });
179
+ }
180
+ if (message.method === "initialized") {
181
+ applyPublishSteps("initialized", undefined, undefined);
182
+ }
183
+ if (message.method === "textDocument/didOpen") {
184
+ applyPublishSteps("didOpen", message.params.textDocument.uri, message.params.textDocument.version);
185
+ if (scenario.unscopedApplyEditOnDidOpen) {
186
+ sendApplyEdit(scenario.unscopedApplyEditOnDidOpen, { kind: "unscoped" });
187
+ }
188
+ }
189
+ if (message.method === "textDocument/didChange" && Array.isArray(scenario.diagnostics)) {
190
+ send({
191
+ jsonrpc: "2.0",
192
+ method: "textDocument/publishDiagnostics",
193
+ params: {
194
+ uri: message.params.textDocument.uri,
195
+ version: message.params.textDocument.version,
196
+ diagnostics: scenario.diagnostics,
197
+ },
198
+ });
199
+ }
200
+ if (message.method === "textDocument/didChange") {
201
+ applyPublishSteps("didChange", message.params.textDocument.uri, message.params.textDocument.version);
202
+ for (const release of releasedDiagnosticResponses.splice(0)) release();
203
+ }
204
+ if (message.method === "exit") process.exit(0);
205
+ }
206
+
207
+ function handleMessage(message) {
208
+ if (Object.hasOwn(message, "id") && (Object.hasOwn(message, "result") || Object.hasOwn(message, "error"))) {
209
+ handleClientResponse(message);
210
+ return;
211
+ }
212
+ if (Object.hasOwn(message, "id")) {
213
+ handleRequest(message);
214
+ return;
215
+ }
216
+ handleNotification(message);
217
+ }
218
+
219
+ process.stdin.on("data", (chunk) => {
220
+ buffer = Buffer.concat([buffer, chunk]);
221
+ for (;;) {
222
+ const headerEnd = buffer.indexOf("\r\n\r\n");
223
+ if (headerEnd === -1) return;
224
+ const header = buffer.subarray(0, headerEnd).toString("ascii");
225
+ const match = /content-length:\s*(\d+)/i.exec(header);
226
+ if (!match) process.exit(2);
227
+ const length = Number(match[1]);
228
+ const bodyStart = headerEnd + 4;
229
+ if (buffer.length < bodyStart + length) return;
230
+ const body = buffer.subarray(bodyStart, bodyStart + length).toString("utf-8");
231
+ buffer = buffer.subarray(bodyStart + length);
232
+ handleMessage(JSON.parse(body));
233
+ }
234
+ });
@@ -0,0 +1,143 @@
1
+ import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ import { afterEach, describe, expect, it } from "bun:test";
6
+
7
+ import type { LspClient } from "./client.js";
8
+ import { formatDocumentWithClient } from "./format-document.js";
9
+ import type { TextEdit } from "./types.js";
10
+
11
+ const workspaces: string[] = [];
12
+
13
+ afterEach(() => {
14
+ for (const workspace of workspaces.splice(0)) rmSync(workspace, { recursive: true, force: true });
15
+ });
16
+
17
+ function workspaceWith(fileName: string, content: string): { readonly root: string; readonly filePath: string } {
18
+ const root = mkdtempSync(join(tmpdir(), "lsp-format-"));
19
+ workspaces.push(root);
20
+ const filePath = join(root, fileName);
21
+ writeFileSync(filePath, content, "utf-8");
22
+ return { root, filePath };
23
+ }
24
+
25
+ function stubClient(edits: TextEdit[] | null): LspClient {
26
+ return {
27
+ async formatDocument() {
28
+ return edits;
29
+ },
30
+ async openFile() {},
31
+ } as Pick<LspClient, "formatDocument" | "openFile"> as LspClient;
32
+ }
33
+
34
+ describe("formatDocumentWithClient", () => {
35
+ it("#given a server returning text edits #when the document is formatted #then the file is rewritten and line deltas are reported", async () => {
36
+ // given
37
+ const { filePath } = workspaceWith("sample.ts", "const a=1\nconst b =2\nconst c=3\n");
38
+ const edits: TextEdit[] = [
39
+ { range: { start: { line: 0, character: 0 }, end: { line: 1, character: 12 } }, newText: "const a = 1;\nconst b = 2;" },
40
+ ];
41
+
42
+ // when
43
+ const result = await formatDocumentWithClient(stubClient(edits), filePath);
44
+
45
+ // then
46
+ expect(result.status).toBe("formatted");
47
+ expect(readFileSync(filePath, "utf-8")).toBe("const a = 1;\nconst b = 2;\nconst c=3\n");
48
+ expect(result).toMatchObject({ status: "formatted", linesAdded: 2, linesRemoved: 2 });
49
+ });
50
+
51
+ it("#given a server returning an edit that adds lines #when the document is formatted #then the added and removed line counts differ", async () => {
52
+ // given
53
+ const { filePath } = workspaceWith("sample.ts", "const a=1;const b=2\n");
54
+ const edits: TextEdit[] = [
55
+ { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 19 } }, newText: "const a = 1;\nconst b = 2;" },
56
+ ];
57
+
58
+ // when
59
+ const result = await formatDocumentWithClient(stubClient(edits), filePath);
60
+
61
+ // then
62
+ expect(result).toMatchObject({ status: "formatted", linesAdded: 2, linesRemoved: 1 });
63
+ expect(readFileSync(filePath, "utf-8")).toBe("const a = 1;\nconst b = 2;\n");
64
+ });
65
+
66
+ it("#given a server that does not advertise document formatting #when the document is formatted #then the result is unavailable and the file bytes are untouched", async () => {
67
+ // given
68
+ const original = "const a=1\n";
69
+ const { filePath } = workspaceWith("sample.ts", original);
70
+ const before = statSync(filePath);
71
+
72
+ // when
73
+ const result = await formatDocumentWithClient(stubClient(null), filePath);
74
+
75
+ // then
76
+ expect(result).toEqual({ status: "unavailable", reason: "capability_not_advertised" });
77
+ expect(readFileSync(filePath)).toEqual(Buffer.from(original));
78
+ expect(statSync(filePath).mtimeMs).toBe(before.mtimeMs);
79
+ });
80
+
81
+ it("#given a server returning no edits #when the document is formatted #then the result is unchanged and the file bytes are untouched", async () => {
82
+ // given
83
+ const original = "const a = 1;\n";
84
+ const { filePath } = workspaceWith("sample.ts", original);
85
+
86
+ // when
87
+ const result = await formatDocumentWithClient(stubClient([]), filePath);
88
+
89
+ // then
90
+ expect(result).toEqual({ status: "unchanged", linesAdded: 0, linesRemoved: 0 });
91
+ expect(readFileSync(filePath)).toEqual(Buffer.from(original));
92
+ });
93
+
94
+ it("#given edits that reproduce the current bytes #when the document is formatted #then the result is unchanged", async () => {
95
+ // given
96
+ const original = "const a = 1;\n";
97
+ const { filePath } = workspaceWith("sample.ts", original);
98
+ const edits: TextEdit[] = [
99
+ { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 12 } }, newText: "const a = 1;" },
100
+ ];
101
+
102
+ // when
103
+ const result = await formatDocumentWithClient(stubClient(edits), filePath);
104
+
105
+ // then
106
+ expect(result.status).toBe("unchanged");
107
+ expect(readFileSync(filePath, "utf-8")).toBe(original);
108
+ });
109
+
110
+ it("#given the file shrinks after the server computed its edits #when the edits are applied #then the stale edit is rejected instead of corrupting the file", async () => {
111
+ // given
112
+ const { filePath } = workspaceWith("sample.ts", "const a=1\nconst b=2\n");
113
+ const staleEdits: TextEdit[] = [
114
+ { range: { start: { line: 1, character: 0 }, end: { line: 1, character: 9 } }, newText: "const b = 2;" },
115
+ ];
116
+ const racingClient = {
117
+ async formatDocument() {
118
+ writeFileSync(filePath, "short\n", "utf-8");
119
+ return staleEdits;
120
+ },
121
+ async openFile() {},
122
+ } as Pick<LspClient, "formatDocument" | "openFile"> as LspClient;
123
+
124
+ // when / then
125
+ await expect(formatDocumentWithClient(racingClient, filePath)).rejects.toThrow(/outside line/);
126
+ expect(readFileSync(filePath, "utf-8")).toBe("short\n");
127
+ });
128
+
129
+ it("#given a formatted document #when the write completes #then no temporary file is left behind", async () => {
130
+ // given
131
+ const { root, filePath } = workspaceWith("sample.ts", "const a=1\n");
132
+ const edits: TextEdit[] = [
133
+ { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 9 } }, newText: "const a = 1;" },
134
+ ];
135
+
136
+ // when
137
+ await formatDocumentWithClient(stubClient(edits), filePath);
138
+
139
+ // then
140
+ expect(readFileSync(filePath, "utf-8")).toBe("const a = 1;\n");
141
+ expect(() => statSync(join(root, "sample.ts.tmp"))).toThrow();
142
+ });
143
+ });
@@ -0,0 +1,90 @@
1
+ import { readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+
3
+ import type { LspClient } from "./client.js";
4
+ import type { FormattingOptions, TextEdit } from "./types.js";
5
+ import { normalizeTextEdits } from "./workspace-edit-text.js";
6
+
7
+ export const DEFAULT_FORMATTING_OPTIONS: FormattingOptions = {
8
+ tabSize: 4,
9
+ insertSpaces: false,
10
+ trimTrailingWhitespace: true,
11
+ insertFinalNewline: true,
12
+ trimFinalNewlines: true,
13
+ };
14
+
15
+ export type FormatDocumentResult =
16
+ | { readonly status: "formatted"; readonly linesAdded: number; readonly linesRemoved: number }
17
+ | { readonly status: "unchanged"; readonly linesAdded: 0; readonly linesRemoved: 0 }
18
+ | { readonly status: "unavailable"; readonly reason: "capability_not_advertised" };
19
+
20
+ export interface FormatDocumentOptions {
21
+ readonly formattingOptions?: FormattingOptions;
22
+ readonly signal?: AbortSignal;
23
+ }
24
+
25
+ const UNCHANGED: FormatDocumentResult = { status: "unchanged", linesAdded: 0, linesRemoved: 0 };
26
+
27
+ /**
28
+ * Formats one document through the resident language server and commits the result to disk.
29
+ *
30
+ * The server is asked for whole-document edits rather than formatted text, so the edits are
31
+ * validated and applied with the same normalization the workspace-edit path uses. Nothing is
32
+ * written when the server declines the capability or when the edits reproduce the current bytes,
33
+ * which keeps a no-op format from touching file mtimes that downstream tooling watches.
34
+ */
35
+ export async function formatDocumentWithClient(
36
+ client: LspClient,
37
+ filePath: string,
38
+ options: FormatDocumentOptions = {},
39
+ ): Promise<FormatDocumentResult> {
40
+ const edits = await client.formatDocument(
41
+ filePath,
42
+ options.formattingOptions ?? DEFAULT_FORMATTING_OPTIONS,
43
+ options.signal,
44
+ );
45
+ if (edits === null) return { status: "unavailable", reason: "capability_not_advertised" };
46
+ if (edits.length === 0) return UNCHANGED;
47
+
48
+ const before = readFileSync(filePath, "utf-8");
49
+ const normalized = normalizeTextEdits(before, edits, 0);
50
+ if (normalized.text === before) return UNCHANGED;
51
+
52
+ writeAtomically(filePath, normalized.text);
53
+ await client.openFile(filePath);
54
+
55
+ return {
56
+ status: "formatted",
57
+ ...lineDelta(normalized.edits),
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Counts the lines each edit removes from the document and adds back in its place.
63
+ *
64
+ * Counting from the edits instead of diffing the whole file keeps the report proportional to what
65
+ * the server actually rewrote, so an unrelated line never shows up as churn.
66
+ */
67
+ function lineDelta(edits: readonly TextEdit[]): { readonly linesAdded: number; readonly linesRemoved: number } {
68
+ let linesAdded = 0;
69
+ let linesRemoved = 0;
70
+ for (const edit of edits) {
71
+ linesRemoved += edit.range.end.line - edit.range.start.line + 1;
72
+ linesAdded += edit.newText.split("\n").length;
73
+ }
74
+ return { linesAdded, linesRemoved };
75
+ }
76
+
77
+ function writeAtomically(filePath: string, content: string): void {
78
+ const tempPath = `${filePath}.omo-format.tmp`;
79
+ writeFileSync(tempPath, content, "utf-8");
80
+ try {
81
+ renameSync(tempPath, filePath);
82
+ } catch (error) {
83
+ try {
84
+ unlinkSync(tempPath);
85
+ } catch {
86
+ // The rename failure is the actionable error; a leftover temp file must not mask it.
87
+ }
88
+ throw error;
89
+ }
90
+ }