@the-open-engine/zeroshot 6.38.1 → 6.39.1

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 (383) hide show
  1. package/README.md +6 -0
  2. package/cli/event-copy.js +8 -7
  3. package/cli/event-copy.ts +12 -0
  4. package/cli/index.js +67 -48
  5. package/cli/json-export.js +61 -64
  6. package/cli/json-export.ts +112 -0
  7. package/cli/message-formatter-utils.js +68 -56
  8. package/cli/message-formatter-utils.ts +121 -0
  9. package/cli/message-formatters-normal.js +124 -259
  10. package/cli/message-formatters-normal.ts +250 -0
  11. package/cli/message-formatters-watch.js +81 -84
  12. package/cli/message-formatters-watch.ts +189 -0
  13. package/lib/cluster/generated/protocol-schema.cjs +1 -1
  14. package/lib/cluster/generated/protocol-schema.mjs +1 -1
  15. package/lib/cluster/generated/protocol.d.cts +8 -0
  16. package/lib/cluster/generated/protocol.d.mts +8 -0
  17. package/lib/cluster/generated/protocol.d.ts +8 -0
  18. package/lib/clusters-registry.d.ts +23 -0
  19. package/lib/clusters-registry.js +17 -29
  20. package/lib/completion.d.ts +12 -0
  21. package/lib/completion.js +84 -94
  22. package/lib/compose-utils.d.ts +32 -0
  23. package/lib/compose-utils.js +57 -72
  24. package/lib/detached-startup.d.ts +84 -0
  25. package/lib/detached-startup.js +232 -302
  26. package/lib/docker-config.d.ts +43 -0
  27. package/lib/docker-config.js +150 -325
  28. package/lib/docker-env-auth.d.ts +17 -0
  29. package/lib/docker-env-auth.js +75 -0
  30. package/lib/git-remote-utils.d.ts +48 -0
  31. package/lib/git-remote-utils.js +147 -213
  32. package/lib/id-detector.d.ts +15 -0
  33. package/lib/id-detector.js +37 -41
  34. package/lib/path-check.d.ts +30 -0
  35. package/lib/path-check.js +42 -45
  36. package/lib/process-liveness.d.ts +16 -0
  37. package/lib/process-liveness.js +19 -12
  38. package/lib/provider-credential-path.d.ts +16 -0
  39. package/lib/provider-credential-path.js +31 -29
  40. package/lib/provider-defaults.d.ts +30 -0
  41. package/lib/provider-defaults.js +47 -42
  42. package/lib/provider-detection.d.ts +11 -0
  43. package/lib/provider-detection.js +91 -54
  44. package/lib/provider-names.d.ts +38 -0
  45. package/lib/provider-names.js +56 -64
  46. package/lib/repo-settings.d.ts +28 -0
  47. package/lib/repo-settings.js +83 -59
  48. package/lib/run-mode.d.ts +21 -0
  49. package/lib/run-mode.js +21 -20
  50. package/lib/run-plan.d.ts +28 -0
  51. package/lib/run-plan.js +17 -16
  52. package/lib/settings/claude-auth.d.ts +35 -0
  53. package/lib/settings/claude-auth.js +30 -47
  54. package/lib/settings-defaults.d.ts +20 -0
  55. package/lib/settings-defaults.js +149 -0
  56. package/lib/settings-error.d.ts +7 -0
  57. package/lib/settings-error.js +8 -0
  58. package/lib/settings-issue-providers.d.ts +10 -0
  59. package/lib/settings-issue-providers.js +12 -0
  60. package/lib/settings-models.d.ts +22 -0
  61. package/lib/settings-models.js +93 -0
  62. package/lib/settings-storage.d.ts +23 -0
  63. package/lib/settings-storage.js +205 -0
  64. package/lib/settings-validation.d.ts +7 -0
  65. package/lib/settings-validation.js +182 -0
  66. package/lib/settings.d.ts +29 -0
  67. package/lib/settings.js +41 -736
  68. package/lib/setup-apply-types.d.ts +66 -0
  69. package/lib/setup-apply-types.js +2 -0
  70. package/lib/setup-apply.d.ts +39 -0
  71. package/lib/setup-apply.js +267 -264
  72. package/lib/setup-journal.d.ts +41 -0
  73. package/lib/setup-journal.js +119 -76
  74. package/lib/setup-plan-types.d.ts +102 -0
  75. package/lib/setup-plan-types.js +2 -0
  76. package/lib/setup-plan.d.ts +30 -0
  77. package/lib/setup-plan.js +300 -324
  78. package/lib/setup-undo.d.ts +44 -0
  79. package/lib/setup-undo.js +61 -74
  80. package/lib/start-cluster-config.d.ts +26 -0
  81. package/lib/start-cluster-config.js +56 -0
  82. package/lib/start-cluster-environment.d.ts +41 -0
  83. package/lib/start-cluster-environment.js +134 -0
  84. package/lib/start-cluster-run-options.d.ts +54 -0
  85. package/lib/start-cluster-run-options.js +148 -0
  86. package/lib/start-cluster.d.ts +64 -0
  87. package/lib/start-cluster.js +98 -454
  88. package/lib/stream-json-parser.d.ts +17 -0
  89. package/lib/stream-json-parser.js +51 -52
  90. package/npm-shrinkwrap.json +6 -8
  91. package/package.json +14 -8
  92. package/scripts/postinstall.js +27 -0
  93. package/scripts/rust-distribution.js +36 -45
  94. package/src/agent/agent-command-proofs-context.js +35 -45
  95. package/src/agent/agent-command-proofs-context.ts +87 -0
  96. package/src/agent/agent-config-settings.js +19 -0
  97. package/src/agent/agent-config-settings.ts +41 -0
  98. package/src/agent/agent-config.js +122 -231
  99. package/src/agent/agent-config.ts +285 -0
  100. package/src/agent/agent-context-builder.js +38 -273
  101. package/src/agent/agent-context-builder.ts +51 -0
  102. package/src/agent/agent-context-environment-sections.js +105 -0
  103. package/src/agent/agent-context-environment-sections.ts +134 -0
  104. package/src/agent/agent-context-packs.js +165 -0
  105. package/src/agent/agent-context-packs.ts +296 -0
  106. package/src/agent/agent-context-prompt-sections.js +100 -0
  107. package/src/agent/agent-context-prompt-sections.ts +148 -0
  108. package/src/agent/agent-context-sections.js +25 -334
  109. package/src/agent/agent-context-sections.ts +36 -0
  110. package/src/agent/agent-context-sources.js +96 -149
  111. package/src/agent/agent-context-sources.ts +253 -0
  112. package/src/agent/agent-context-types.js +2 -0
  113. package/src/agent/agent-context-types.ts +132 -0
  114. package/src/agent/agent-context-validation-sections.js +73 -0
  115. package/src/agent/agent-context-validation-sections.ts +129 -0
  116. package/src/agent/agent-input-injector.js +99 -118
  117. package/src/agent/agent-input-injector.ts +195 -0
  118. package/src/agent/agent-liveness-poll.js +74 -78
  119. package/src/agent/agent-liveness-poll.ts +163 -0
  120. package/src/agent/agent-quality-gate-schema.js +77 -86
  121. package/src/agent/agent-quality-gate-schema.ts +135 -0
  122. package/src/agent/agent-quality-gates-context.js +44 -50
  123. package/src/agent/agent-quality-gates-context.ts +91 -0
  124. package/src/agent/agent-stuck-detector.js +171 -239
  125. package/src/agent/agent-stuck-detector.ts +288 -0
  126. package/src/agent/agent-trigger-evaluator.js +29 -46
  127. package/src/agent/agent-trigger-evaluator.ts +98 -0
  128. package/src/agent/context-metrics.js +110 -141
  129. package/src/agent/context-metrics.ts +261 -0
  130. package/src/agent/context-pack-builder.js +72 -358
  131. package/src/agent/context-pack-builder.ts +105 -0
  132. package/src/agent/context-pack-core.js +70 -0
  133. package/src/agent/context-pack-core.ts +96 -0
  134. package/src/agent/context-pack-selection.js +119 -0
  135. package/src/agent/context-pack-selection.ts +187 -0
  136. package/src/agent/context-pack-truncation.js +139 -0
  137. package/src/agent/context-pack-truncation.ts +208 -0
  138. package/src/agent/context-pack-types.js +2 -0
  139. package/src/agent/context-pack-types.ts +98 -0
  140. package/src/agent/context-replay-policy.js +30 -39
  141. package/src/agent/context-replay-policy.ts +71 -0
  142. package/src/agent/critical-agent-policy.js +6 -10
  143. package/src/agent/critical-agent-policy.ts +20 -0
  144. package/src/agent/guidance-queue.js +74 -87
  145. package/src/agent/guidance-queue.ts +166 -0
  146. package/src/agent/output-extraction-error-detail.js +45 -0
  147. package/src/agent/output-extraction-error-detail.ts +49 -0
  148. package/src/agent/output-extraction-failures.js +125 -0
  149. package/src/agent/output-extraction-failures.ts +149 -0
  150. package/src/agent/output-extraction-json.js +234 -0
  151. package/src/agent/output-extraction-json.ts +235 -0
  152. package/src/agent/output-extraction-pi.js +124 -0
  153. package/src/agent/output-extraction-pi.ts +137 -0
  154. package/src/agent/output-extraction-types.js +2 -0
  155. package/src/agent/output-extraction-types.ts +49 -0
  156. package/src/agent/output-extraction.js +17 -617
  157. package/src/agent/output-extraction.ts +30 -0
  158. package/src/agent/output-reformatter-errors.js +83 -0
  159. package/src/agent/output-reformatter-errors.ts +106 -0
  160. package/src/agent/output-reformatter-types.js +2 -0
  161. package/src/agent/output-reformatter-types.ts +86 -0
  162. package/src/agent/output-reformatter.js +140 -185
  163. package/src/agent/output-reformatter.ts +238 -0
  164. package/src/agent/pi-terminal-lifecycle-types.js +2 -0
  165. package/src/agent/pi-terminal-lifecycle-types.ts +41 -0
  166. package/src/agent/pi-terminal-lifecycle.js +186 -206
  167. package/src/agent/pi-terminal-lifecycle.ts +297 -0
  168. package/src/agent/provider-control-plane.js +22 -21
  169. package/src/agent/provider-control-plane.ts +52 -0
  170. package/src/agent/provider-terminal-failure-types.js +2 -0
  171. package/src/agent/provider-terminal-failure-types.ts +60 -0
  172. package/src/agent/provider-terminal-failure.js +194 -191
  173. package/src/agent/provider-terminal-failure.ts +262 -0
  174. package/src/agent/rate-limit-backoff.js +31 -74
  175. package/src/agent/rate-limit-backoff.ts +71 -0
  176. package/src/agent/schema-utils.js +85 -125
  177. package/src/agent/schema-utils.ts +148 -0
  178. package/src/agent/structured-output-error.js +34 -33
  179. package/src/agent/structured-output-error.ts +105 -0
  180. package/src/agent/validation-platform.js +27 -27
  181. package/src/agent/validation-platform.ts +49 -0
  182. package/src/agents/git-pusher-template.js +17 -34
  183. package/src/attach/attach-client.js +232 -405
  184. package/src/attach/attach-client.ts +300 -0
  185. package/src/attach/attach-server-cleanup.js +43 -0
  186. package/src/attach/attach-server-cleanup.ts +39 -0
  187. package/src/attach/attach-server-clients.js +117 -0
  188. package/src/attach/attach-server-clients.ts +122 -0
  189. package/src/attach/attach-server-events.js +38 -0
  190. package/src/attach/attach-server-events.ts +36 -0
  191. package/src/attach/attach-server-pty.js +45 -0
  192. package/src/attach/attach-server-pty.ts +64 -0
  193. package/src/attach/attach-server-runtime.js +73 -0
  194. package/src/attach/attach-server-runtime.ts +62 -0
  195. package/src/attach/attach-server-socket.js +28 -0
  196. package/src/attach/attach-server-socket.ts +23 -0
  197. package/src/attach/attach-server-types.js +2 -0
  198. package/src/attach/attach-server-types.ts +82 -0
  199. package/src/attach/attach-server.js +106 -523
  200. package/src/attach/attach-server.ts +158 -0
  201. package/src/attach/index.js +18 -35
  202. package/src/attach/index.ts +18 -0
  203. package/src/attach/protocol.js +95 -184
  204. package/src/attach/protocol.ts +181 -0
  205. package/src/attach/ring-buffer.js +76 -102
  206. package/src/attach/ring-buffer.ts +109 -0
  207. package/src/attach/send-input.js +64 -75
  208. package/src/attach/send-input.ts +94 -0
  209. package/src/attach/socket-discovery.js +164 -218
  210. package/src/attach/socket-discovery.ts +230 -0
  211. package/src/attach/socket-paths.js +42 -61
  212. package/src/attach/socket-paths.ts +81 -0
  213. package/src/claude-credentials.js +51 -54
  214. package/src/claude-credentials.ts +89 -0
  215. package/src/cluster/generated/protocol-schema.ts +1 -1
  216. package/src/cluster/generated/protocol.ts +2 -1
  217. package/src/command-proofs.js +110 -141
  218. package/src/command-proofs.ts +216 -0
  219. package/src/config-router.js +81 -73
  220. package/src/config-router.ts +131 -0
  221. package/src/copy-containment.js +191 -0
  222. package/src/copy-containment.ts +259 -0
  223. package/src/copy-worker.js +53 -30
  224. package/src/copy-worker.ts +88 -0
  225. package/src/darwin-keychain-boundary.js +80 -111
  226. package/src/darwin-keychain-boundary.ts +148 -0
  227. package/src/guidance-topics.js +4 -5
  228. package/src/guidance-topics.ts +10 -0
  229. package/src/input-helpers.js +33 -54
  230. package/src/input-helpers.ts +58 -0
  231. package/src/isolation-manager.js +59 -20
  232. package/src/ledger-sequence.js +45 -48
  233. package/src/ledger-sequence.ts +63 -0
  234. package/src/legacy-lib/clusters-registry.ts +40 -0
  235. package/src/legacy-lib/completion.ts +148 -0
  236. package/src/legacy-lib/compose-utils.ts +102 -0
  237. package/src/legacy-lib/detached-startup.ts +451 -0
  238. package/src/legacy-lib/docker-config.ts +282 -0
  239. package/src/legacy-lib/docker-env-auth.ts +116 -0
  240. package/src/legacy-lib/git-remote-utils.ts +244 -0
  241. package/src/legacy-lib/id-detector.ts +57 -0
  242. package/src/legacy-lib/js-yaml.d.ts +3 -0
  243. package/src/legacy-lib/omelette.d.ts +15 -0
  244. package/src/legacy-lib/path-check.ts +89 -0
  245. package/src/legacy-lib/process-liveness.ts +36 -0
  246. package/src/legacy-lib/provider-credential-path.ts +57 -0
  247. package/src/legacy-lib/provider-defaults.ts +114 -0
  248. package/src/legacy-lib/provider-detection.ts +67 -0
  249. package/src/legacy-lib/provider-names.ts +126 -0
  250. package/src/legacy-lib/repo-settings.ts +88 -0
  251. package/src/legacy-lib/run-mode.ts +58 -0
  252. package/src/legacy-lib/run-plan.ts +49 -0
  253. package/src/legacy-lib/settings/claude-auth.ts +80 -0
  254. package/src/legacy-lib/settings-defaults.ts +212 -0
  255. package/src/legacy-lib/settings-error.ts +8 -0
  256. package/src/legacy-lib/settings-issue-providers.ts +18 -0
  257. package/src/legacy-lib/settings-models.ts +124 -0
  258. package/src/legacy-lib/settings-storage.ts +277 -0
  259. package/src/legacy-lib/settings-validation.ts +220 -0
  260. package/src/legacy-lib/settings.ts +87 -0
  261. package/src/legacy-lib/setup-apply-types.ts +70 -0
  262. package/src/legacy-lib/setup-apply.ts +456 -0
  263. package/src/legacy-lib/setup-journal.ts +185 -0
  264. package/src/legacy-lib/setup-plan-types.ts +96 -0
  265. package/src/legacy-lib/setup-plan.ts +500 -0
  266. package/src/legacy-lib/setup-undo.ts +142 -0
  267. package/src/legacy-lib/start-cluster-config.ts +124 -0
  268. package/src/legacy-lib/start-cluster-environment.ts +166 -0
  269. package/src/legacy-lib/start-cluster-run-options.ts +281 -0
  270. package/src/legacy-lib/start-cluster.ts +237 -0
  271. package/src/legacy-lib/stream-json-parser.ts +88 -0
  272. package/src/message-buffer.js +55 -71
  273. package/src/message-buffer.ts +107 -0
  274. package/src/message-bus-bridge.js +92 -133
  275. package/src/message-bus-bridge.ts +149 -0
  276. package/src/name-generator-shared.js +220 -0
  277. package/src/name-generator-shared.ts +231 -0
  278. package/src/name-generator.js +8 -217
  279. package/src/name-generator.ts +27 -0
  280. package/src/omp-blob-root.js +83 -64
  281. package/src/omp-blob-root.ts +116 -0
  282. package/src/omp-config-overlay.js +65 -38
  283. package/src/omp-config-overlay.ts +110 -0
  284. package/src/omp-execution-fingerprint.js +38 -43
  285. package/src/omp-execution-fingerprint.ts +92 -0
  286. package/src/omp-session-limits.js +10 -15
  287. package/src/omp-session-limits.ts +53 -0
  288. package/src/omp-session-partition.js +2 -2
  289. package/src/omp-session-verifier.js +1 -1
  290. package/src/orchestrator.js +32 -7
  291. package/src/pr-body-template.js +71 -0
  292. package/src/providers/anthropic/index.js +3 -1
  293. package/src/providers/anthropic/index.ts +13 -0
  294. package/src/providers/capabilities.js +19 -27
  295. package/src/providers/capabilities.ts +44 -0
  296. package/src/providers/google/index.js +3 -1
  297. package/src/providers/google/index.ts +13 -0
  298. package/src/providers/openai/index.js +3 -1
  299. package/src/providers/openai/index.ts +13 -0
  300. package/src/providers/opencode/index.js +3 -1
  301. package/src/providers/opencode/index.ts +13 -0
  302. package/src/quality-gates.js +67 -127
  303. package/src/quality-gates.ts +149 -0
  304. package/src/repo-settings-access.js +40 -0
  305. package/src/repo-settings-access.ts +64 -0
  306. package/src/schemas/sub-cluster.js +127 -183
  307. package/src/schemas/sub-cluster.ts +238 -0
  308. package/src/state-snapshot-normalization.js +224 -0
  309. package/src/state-snapshot-normalization.ts +222 -0
  310. package/src/state-snapshot.js +192 -359
  311. package/src/state-snapshot.ts +291 -0
  312. package/src/state-snapshotter.js +118 -130
  313. package/src/state-snapshotter.ts +167 -0
  314. package/src/sub-cluster-wrapper.js +4 -13
  315. package/src/task-run-model-args.js +87 -124
  316. package/src/task-run-model-args.ts +204 -0
  317. package/src/task-runner.js +5 -25
  318. package/src/task-runner.ts +28 -0
  319. package/src/task-startup-error.js +73 -58
  320. package/src/task-startup-error.ts +126 -0
  321. package/src/template-validation/consensus-gate-contracts.js +2 -0
  322. package/src/template-validation/consensus-gate-contracts.ts +70 -0
  323. package/src/template-validation/consensus-gate-scenarios.js +76 -0
  324. package/src/template-validation/consensus-gate-scenarios.ts +130 -0
  325. package/src/template-validation/consensus-gate-stage.js +34 -0
  326. package/src/template-validation/consensus-gate-stage.ts +56 -0
  327. package/src/template-validation/random-topology-contracts.js +2 -0
  328. package/src/template-validation/random-topology-contracts.ts +54 -0
  329. package/src/template-validation/random-topology-dispatch.js +25 -0
  330. package/src/template-validation/random-topology-dispatch.ts +33 -0
  331. package/src/template-validation/random-topology-message-loop.js +53 -0
  332. package/src/template-validation/random-topology-message-loop.ts +73 -0
  333. package/src/template-validation/random-topology-operations.js +79 -0
  334. package/src/template-validation/random-topology-operations.ts +101 -0
  335. package/src/template-validation/random-topology-runtime.js +25 -0
  336. package/src/template-validation/random-topology-runtime.ts +43 -0
  337. package/src/template-validation/random-topology-scenario.js +52 -0
  338. package/src/template-validation/random-topology-scenario.ts +57 -0
  339. package/src/template-validation/random-topology-schema.js +159 -0
  340. package/src/template-validation/random-topology-schema.ts +190 -0
  341. package/src/template-validation/random-topology-state.js +28 -0
  342. package/src/template-validation/random-topology-state.ts +30 -0
  343. package/src/template-validation/random-topology-trigger-evaluation.js +45 -0
  344. package/src/template-validation/random-topology-trigger-evaluation.ts +65 -0
  345. package/src/template-validation/random-topology-trigger.js +75 -0
  346. package/src/template-validation/random-topology-trigger.ts +88 -0
  347. package/src/template-validation/report-formatter.js +40 -46
  348. package/src/template-validation/report-formatter.ts +101 -0
  349. package/src/template-validation/simulate-consensus-gates.js +111 -302
  350. package/src/template-validation/simulate-consensus-gates.ts +196 -0
  351. package/src/template-validation/simulate-random-topology.js +26 -519
  352. package/src/template-validation/simulate-random-topology.ts +57 -0
  353. package/src/template-validation/simulate-two-stage-validation.js +41 -265
  354. package/src/template-validation/simulate-two-stage-validation.ts +55 -0
  355. package/src/template-validation/simulation-agent-runtime.js +25 -0
  356. package/src/template-validation/simulation-agent-runtime.ts +40 -0
  357. package/src/template-validation/simulation-agent.js +29 -0
  358. package/src/template-validation/simulation-agent.ts +73 -0
  359. package/src/template-validation/simulation-runtime.js +34 -0
  360. package/src/template-validation/simulation-runtime.ts +72 -0
  361. package/src/template-validation/two-stage-contracts.js +2 -0
  362. package/src/template-validation/two-stage-contracts.ts +41 -0
  363. package/src/template-validation/two-stage-inputs.js +21 -0
  364. package/src/template-validation/two-stage-inputs.ts +29 -0
  365. package/src/template-validation/two-stage-results.js +30 -0
  366. package/src/template-validation/two-stage-results.ts +40 -0
  367. package/src/template-validation/two-stage-scenario.js +86 -0
  368. package/src/template-validation/two-stage-scenario.ts +120 -0
  369. package/src/worktree-claude-config.js +182 -220
  370. package/src/worktree-claude-config.ts +272 -0
  371. package/src/worktree-tooling-env.js +103 -125
  372. package/src/worktree-tooling-env.ts +142 -0
  373. package/task-lib/completion.js +2 -3
  374. package/task-lib/completion.ts +9 -0
  375. package/task-lib/config.js +1 -4
  376. package/task-lib/config.ts +11 -0
  377. package/task-lib/name-generator.js +4 -226
  378. package/task-lib/name-generator.ts +11 -0
  379. package/task-lib/omp-session-cleanup.js +1 -1
  380. package/task-lib/omp-storage-root.js +8 -12
  381. package/task-lib/omp-storage-root.ts +62 -0
  382. package/task-lib/process-termination.js +143 -166
  383. package/task-lib/process-termination.ts +268 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * `zeroshot setup apply` — writes decisions from #A's plan/decision contract
3
+ * (lib/setup-plan.js) to global settings, repo-local settings, and the undo
4
+ * journal (lib/setup-journal.js).
5
+ *
6
+ * Fail-closed: every input decisionId + value is resolved and validated
7
+ * against its domain before ANY write happens. Writes are then applied only
8
+ * to settings keys a run-mode resolver actually reads (setup-plan.js's
9
+ * CONSUMED_PATHS/isConsumedPath — the same set that filters proposedWrites)
10
+ * — a settings key nobody reads is dead config, the exact drift the
11
+ * canonical-path rule (issue #605) forbids.
12
+ */
13
+
14
+ import type {
15
+ ApplyDecisionValuesParams,
16
+ ApplyDecisionsParams,
17
+ ApplyDependencies,
18
+ DecisionResult,
19
+ PendingWrite,
20
+ ResolvedDecision,
21
+ SettingsMap,
22
+ SetupJournal,
23
+ } from './setup-apply-types';
24
+
25
+ interface SetupPlanFacade {
26
+ CONSUMED_PATHS: Set<string>;
27
+ domainFor(decisionId: string): string;
28
+ isConsumedPath(scope: string, targetPath: string): boolean;
29
+ resolveDecisionPath(decisionId: string): { path: string; scope: 'global' | 'repo' } | null;
30
+ }
31
+
32
+ interface DockerConfigFacade {
33
+ validateEnvPassthrough(value: unknown): string | null;
34
+ validateMountConfig(value: unknown): string | null;
35
+ }
36
+
37
+ interface ProviderNamesFacade {
38
+ VALID_PROVIDERS: readonly string[];
39
+ }
40
+
41
+ interface FileSystemFacade {
42
+ readFileSync(filePath: string, encoding: 'utf8'): string;
43
+ }
44
+
45
+ interface SetupJournalFacade {
46
+ deepEqual(left: unknown, right: unknown): boolean;
47
+ getNestedValue(source: unknown, pathStr: string): unknown;
48
+ loadJournal(): SetupJournal;
49
+ saveJournal(journal: SetupJournal): void;
50
+ setNestedValue(target: SettingsMap, pathStr: string, value: unknown): void;
51
+ upsertJournalEntry(journal: SetupJournal, entry: SetupJournal['entries'][number]): void;
52
+ }
53
+
54
+ interface ConvertDecisionParams {
55
+ decisionId: string;
56
+ deps: ApplyDependencies;
57
+ globalSettings: SettingsMap;
58
+ value: unknown;
59
+ }
60
+
61
+ interface ResolveDecisionContext {
62
+ allowRiskyDefaults: boolean;
63
+ globalSettings: SettingsMap;
64
+ repoSettings: SettingsMap;
65
+ }
66
+
67
+ interface ApplyWriteContext {
68
+ deps: ApplyDependencies;
69
+ journal: SetupJournal;
70
+ repoRoot: string | null;
71
+ }
72
+
73
+ interface WriteResolvedContext extends ApplyWriteContext {
74
+ allowRiskyDefaults: boolean;
75
+ repoSettings: SettingsMap;
76
+ }
77
+
78
+ // These CommonJS paths intentionally resolve beside the emitted module in lib/.
79
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
80
+ const { resolveDecisionPath, domainFor, isConsumedPath, CONSUMED_PATHS }: SetupPlanFacade =
81
+ require('./setup-plan');
82
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
83
+ const { validateMountConfig, validateEnvPassthrough }: DockerConfigFacade =
84
+ require('./docker-config');
85
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
86
+ const { VALID_PROVIDERS }: ProviderNamesFacade = require('./provider-names');
87
+ const VALID_LEVELS = Object.freeze(['level1', 'level2', 'level3']);
88
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
89
+ const setupJournal: SetupJournalFacade = require('./setup-journal');
90
+ const { loadJournal, saveJournal, upsertJournalEntry } = setupJournal;
91
+ const { getNestedValue, setNestedValue, deepEqual } = setupJournal;
92
+
93
+ const SECRET_PATTERN = /token|secret|password|api[_-]?key|credential/i;
94
+
95
+ function includesValue(values: readonly unknown[], value: unknown): boolean {
96
+ return values.includes(value);
97
+ }
98
+
99
+ function assertSecretSafePath(targetPath: string): void {
100
+ if (SECRET_PATTERN.test(targetPath)) {
101
+ throw new Error(`Refusing to write secret-shaped settings path: ${targetPath}`);
102
+ }
103
+ }
104
+
105
+ function domainError(decisionId: string, value: unknown): Error {
106
+ const expected = domainFor(decisionId);
107
+ const received = JSON.stringify(value);
108
+ return new Error(
109
+ `Invalid value for decision "${decisionId}": expected ${expected}, got ${received}`
110
+ );
111
+ }
112
+
113
+ function convertProviderLevelDecision({
114
+ decisionId,
115
+ value,
116
+ globalSettings,
117
+ deps,
118
+ }: ConvertDecisionParams): SettingsMap {
119
+ if (!decisionId.startsWith('providerLevel.')) {
120
+ throw new Error(`Unknown decision ID: ${decisionId}`);
121
+ }
122
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
123
+ throw domainError(decisionId, value);
124
+ }
125
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
126
+ const decisionValue = value as SettingsMap;
127
+ // Preserve Array.prototype.sort's default UTF-16 ordering.
128
+ // eslint-disable-next-line sonarjs/no-alphabetical-sort
129
+ const keys = Object.keys(decisionValue).sort();
130
+ const expectedKeys = ['defaultLevel', 'maxLevel', 'minLevel'];
131
+ if (
132
+ keys.length !== expectedKeys.length ||
133
+ keys.some((key, index) => key !== expectedKeys[index]) ||
134
+ expectedKeys.some((key) => !includesValue(VALID_LEVELS, decisionValue[key]))
135
+ ) {
136
+ throw domainError(decisionId, value);
137
+ }
138
+ const providerName = decisionId.slice('providerLevel.'.length);
139
+ if (!deps.VALID_PROVIDERS.includes(providerName)) throw domainError(decisionId, value);
140
+ const rank = (level: unknown): number => VALID_LEVELS.findIndex((item) => item === level);
141
+ if (
142
+ rank(decisionValue.minLevel) > rank(decisionValue.defaultLevel) ||
143
+ rank(decisionValue.defaultLevel) > rank(decisionValue.maxLevel)
144
+ ) {
145
+ throw domainError(decisionId, value);
146
+ }
147
+ const existingValue = getNestedValue(globalSettings, `providerSettings.${providerName}`) || {};
148
+ // Both operands have passed the same object-domain checks used by the legacy implementation.
149
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
150
+ const existing = existingValue as SettingsMap;
151
+ return { ...existing, ...decisionValue };
152
+ }
153
+
154
+ // Resolve a submitted decision into the exact value stored at its canonical
155
+ // settings path, validating its decision domain before any write occurs.
156
+ function convertDecisionValue({
157
+ decisionId,
158
+ value,
159
+ globalSettings,
160
+ deps,
161
+ }: ConvertDecisionParams): unknown {
162
+ switch (decisionId) {
163
+ case 'defaultProvider':
164
+ if (typeof value !== 'string' || !deps.VALID_PROVIDERS.includes(value)) {
165
+ throw domainError(decisionId, value);
166
+ }
167
+ return value;
168
+
169
+ case 'defaultIsolation':
170
+ if (!includesValue(['worktree', 'docker', 'none'], value)) {
171
+ throw domainError(decisionId, value);
172
+ }
173
+ return value;
174
+
175
+ case 'allowLocalNoIsolation':
176
+ if (typeof value !== 'boolean') throw domainError(decisionId, value);
177
+ return value;
178
+
179
+ case 'defaultDelivery':
180
+ if (!includesValue(['none', 'pr', 'ship'], value)) throw domainError(decisionId, value);
181
+ return value;
182
+
183
+ case 'defaultIssueSource':
184
+ if (typeof value !== 'string' || !deps.listIssueProviders().includes(value)) {
185
+ throw domainError(decisionId, value);
186
+ }
187
+ return value;
188
+
189
+ case 'prBase':
190
+ if (typeof value !== 'string' || value.trim() === '') throw domainError(decisionId, value);
191
+ return value;
192
+
193
+ case 'dockerMounts': {
194
+ const err = validateMountConfig(value);
195
+ if (err) throw new Error(`Invalid value for decision "${decisionId}": ${err}`);
196
+ return value;
197
+ }
198
+
199
+ case 'dockerEnvPassthrough': {
200
+ const err = validateEnvPassthrough(value);
201
+ if (err) throw new Error(`Invalid value for decision "${decisionId}": ${err}`);
202
+ return value;
203
+ }
204
+
205
+ case 'updatePolicy':
206
+ if (!includesValue(['off', 'notify', 'auto'], value)) throw domainError(decisionId, value);
207
+ return value;
208
+
209
+ default:
210
+ return convertProviderLevelDecision({ decisionId, value, globalSettings, deps });
211
+ }
212
+ }
213
+
214
+ function defaultApplyDeps(): ApplyDependencies {
215
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
216
+ const fs: FileSystemFacade = require('fs');
217
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
218
+ const { loadSettings, mutateSettings }: Pick<
219
+ ApplyDependencies,
220
+ 'loadSettings' | 'mutateSettings'
221
+ > = require('./settings');
222
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
223
+ const { readRepoSettings, writeRepoSettings }: Pick<
224
+ ApplyDependencies,
225
+ 'readRepoSettings' | 'writeRepoSettings'
226
+ > = require('./repo-settings');
227
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
228
+ const { checkGhAuth }: Pick<ApplyDependencies, 'checkGhAuth'> = require('../src/preflight');
229
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
230
+ const { listProviders: listIssueProviders }: {
231
+ listProviders: ApplyDependencies['listIssueProviders'];
232
+ } = require('../src/issue-providers');
233
+
234
+ return {
235
+ readFile: (filePath: string) => fs.readFileSync(filePath, 'utf8'),
236
+ loadSettings,
237
+ mutateSettings,
238
+ readRepoSettings,
239
+ writeRepoSettings,
240
+ checkGhAuth,
241
+ listIssueProviders,
242
+ VALID_PROVIDERS,
243
+ now: () => new Date().toISOString(),
244
+ };
245
+ }
246
+
247
+ // Phase 1: resolve + validate EVERY input decision before any write happens
248
+ // (fail-closed — a single bad decisionId or out-of-domain value rejects the
249
+ // whole request, never a partial apply).
250
+ function resolveAndValidateDecisions(
251
+ input: SettingsMap,
252
+ globalSettings: SettingsMap,
253
+ deps: ApplyDependencies
254
+ ): ResolvedDecision[] {
255
+ const resolved: ResolvedDecision[] = [];
256
+ for (const [decisionId, value] of Object.entries(input)) {
257
+ const target = resolveDecisionPath(decisionId);
258
+ if (!target) {
259
+ throw new Error(`Unknown decision ID: ${decisionId}`);
260
+ }
261
+ assertSecretSafePath(target.path);
262
+ const writeValue = convertDecisionValue({ decisionId, value, globalSettings, deps });
263
+ resolved.push({ decisionId, target, inputValue: value, writeValue });
264
+ }
265
+ return resolved;
266
+ }
267
+
268
+ // Decides the outcome for a single resolved decision: a skip (with reason)
269
+ // or the write to perform. Returns the write descriptor rather than mutating
270
+ // anything, so the caller controls when settings objects/journal are touched.
271
+ function resolveDecisionOutcome(
272
+ decision: ResolvedDecision,
273
+ context: ResolveDecisionContext
274
+ ): { result: DecisionResult; write?: PendingWrite } {
275
+ const { decisionId, target, inputValue, writeValue } = decision;
276
+ const { globalSettings, repoSettings, allowRiskyDefaults } = context;
277
+ const settingsObj = target.scope === 'repo' ? repoSettings : globalSettings;
278
+ const currentValue = getNestedValue(settingsObj, target.path) ?? null;
279
+ const base = { decisionId, from: currentValue, to: writeValue };
280
+
281
+ if (decisionId === 'defaultDelivery' && inputValue === 'ship' && !allowRiskyDefaults) {
282
+ return { result: { ...base, applied: false, skippedReason: 'requires-explicit-opt-in' } };
283
+ }
284
+ if (!isConsumedPath(target.scope, target.path)) {
285
+ return { result: { ...base, applied: false, skippedReason: 'no-consumer' } };
286
+ }
287
+ if (deepEqual(currentValue, writeValue)) {
288
+ return { result: { ...base, applied: false, skippedReason: 'unchanged' } };
289
+ }
290
+
291
+ return {
292
+ result: { ...base, applied: true },
293
+ write: {
294
+ settingsObj,
295
+ scope: target.scope,
296
+ path: target.path,
297
+ writeValue,
298
+ priorValue: currentValue,
299
+ },
300
+ };
301
+ }
302
+
303
+ // Applies one decision's write to its in-memory settings object and journals
304
+ // it (deferred persistence — the caller flushes to disk once at the end).
305
+ function applyWrite(
306
+ write: PendingWrite,
307
+ context: ApplyWriteContext
308
+ ): 'global' | 'repo' {
309
+ const { repoRoot, journal, deps } = context;
310
+ setNestedValue(write.settingsObj, write.path, write.writeValue);
311
+ upsertJournalEntry(journal, {
312
+ scope: write.scope,
313
+ path: write.path,
314
+ repoRoot: write.scope === 'repo' ? repoRoot : null,
315
+ priorValue: write.priorValue,
316
+ appliedValue: write.writeValue,
317
+ appliedAt: deps.now(),
318
+ });
319
+ return write.scope;
320
+ }
321
+
322
+ // Phase 2: resolve each global outcome against the locked, freshly read state.
323
+ // Repo-local settings remain intentionally outside the global settings lock.
324
+ function writeResolvedDecisions(
325
+ resolved: ResolvedDecision[],
326
+ context: WriteResolvedContext
327
+ ): DecisionResult[] {
328
+ const { repoSettings, repoRoot, journal, allowRiskyDefaults, deps } = context;
329
+ const resultsById = new Map<string, DecisionResult>();
330
+ let repoDirty = false;
331
+ let globalDirty = false;
332
+ const globalDecisions = resolved.filter((decision) => decision.target.scope === 'global');
333
+
334
+ if (globalDecisions.length > 0) {
335
+ const globalResults = deps.mutateSettings((freshGlobalSettings) => {
336
+ const results: DecisionResult[] = [];
337
+ for (const decision of globalDecisions) {
338
+ const freshDecision = {
339
+ ...decision,
340
+ writeValue: convertDecisionValue({
341
+ decisionId: decision.decisionId,
342
+ value: decision.inputValue,
343
+ globalSettings: freshGlobalSettings,
344
+ deps,
345
+ }),
346
+ };
347
+ const { result, write } = resolveDecisionOutcome(freshDecision, {
348
+ globalSettings: freshGlobalSettings,
349
+ repoSettings,
350
+ allowRiskyDefaults,
351
+ });
352
+ results.push(result);
353
+ if (write) {
354
+ applyWrite(write, { repoRoot, journal, deps });
355
+ globalDirty = true;
356
+ }
357
+ }
358
+ return results;
359
+ });
360
+ for (const result of globalResults) resultsById.set(result.decisionId, result);
361
+ }
362
+
363
+ for (const decision of resolved.filter((item) => item.target.scope === 'repo')) {
364
+ const { result, write } = resolveDecisionOutcome(decision, {
365
+ globalSettings: {},
366
+ repoSettings,
367
+ allowRiskyDefaults,
368
+ });
369
+ resultsById.set(result.decisionId, result);
370
+ if (!write) continue;
371
+ applyWrite(write, { repoRoot, journal, deps });
372
+ repoDirty = true;
373
+ }
374
+
375
+ if (repoDirty) deps.writeRepoSettings(repoRoot, repoSettings);
376
+ if (globalDirty || repoDirty) saveJournal(journal);
377
+ return resolved.map((decision) => {
378
+ const result = resultsById.get(decision.decisionId);
379
+ if (!result) throw new Error(`Missing apply result for decision: ${decision.decisionId}`);
380
+ return result;
381
+ });
382
+ }
383
+
384
+ function assertDecisionObject(decisions: unknown, label: string): asserts decisions is SettingsMap {
385
+ if (!decisions || typeof decisions !== 'object' || Array.isArray(decisions)) {
386
+ throw new Error(`${label} must be a JSON object of { decisionId: value }`);
387
+ }
388
+ }
389
+
390
+ /** Apply an in-memory decision object without temporary files. */
391
+ function applyDecisionValues({
392
+ decisions,
393
+ cwd,
394
+ allowRiskyDefaults = false,
395
+ deps = {},
396
+ }: ApplyDecisionValuesParams): DecisionResult[] {
397
+ assertDecisionObject(decisions, 'Decisions');
398
+ const resolvedDeps: ApplyDependencies = { ...defaultApplyDeps(), ...deps };
399
+ const globalSettings = resolvedDeps.loadSettings();
400
+ const { repoRoot, settings: repoSettingsRaw } = resolvedDeps.readRepoSettings(cwd);
401
+ const repoSettings = repoSettingsRaw || {};
402
+ const resolved = resolveAndValidateDecisions(decisions, globalSettings, resolvedDeps);
403
+ const results = writeResolvedDecisions(resolved, {
404
+ repoSettings,
405
+ repoRoot,
406
+ journal: loadJournal(),
407
+ allowRiskyDefaults,
408
+ deps: resolvedDeps,
409
+ });
410
+
411
+ const issueSourceApplied = results.find(
412
+ (result) => result.decisionId === 'defaultIssueSource' && result.applied
413
+ );
414
+ if (issueSourceApplied?.to === 'github') {
415
+ const auth = resolvedDeps.checkGhAuth();
416
+ if (!auth?.authenticated) console.log('Run: gh auth login');
417
+ }
418
+ return results;
419
+ }
420
+
421
+ /** Apply a decisions JSON file through the shared object API. */
422
+ function applyDecisions({
423
+ decisionsPath,
424
+ cwd,
425
+ allowRiskyDefaults = false,
426
+ deps = {},
427
+ }: ApplyDecisionsParams): DecisionResult[] {
428
+ const resolvedDeps: ApplyDependencies = { ...defaultApplyDeps(), ...deps };
429
+ let decisions: unknown;
430
+ try {
431
+ decisions = JSON.parse(resolvedDeps.readFile(decisionsPath));
432
+ } catch (error) {
433
+ const message =
434
+ typeof error === 'object' && error !== null && 'message' in error
435
+ ? error.message
436
+ : undefined;
437
+ throw new Error(`Failed to read decisions file "${decisionsPath}": ${String(message)}`);
438
+ }
439
+ assertDecisionObject(decisions, 'Decisions file');
440
+ return applyDecisionValues({
441
+ decisions,
442
+ cwd,
443
+ allowRiskyDefaults,
444
+ deps: resolvedDeps,
445
+ });
446
+ }
447
+
448
+ export = {
449
+ applyDecisions,
450
+ applyDecisionValues,
451
+ resolveAndValidateDecisions,
452
+ writeResolvedDecisions,
453
+ assertSecretSafePath,
454
+ isConsumedPath,
455
+ CONSUMED_PATHS,
456
+ };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Undo journal for `zeroshot setup apply` / `zeroshot setup undo`.
3
+ *
4
+ * One journal entry per setting this setup owns: { scope, path, repoRoot,
5
+ * priorValue, appliedValue, appliedAt }. `priorValue: null` means the key
6
+ * did not exist before apply, so undo deletes it rather than restoring it.
7
+ * Shared by lib/setup-apply.js (writer) and lib/setup-undo.js (reader) so the
8
+ * nested-path mutation and equality semantics can't drift between the two.
9
+ */
10
+
11
+ import fs = require('fs');
12
+ import path = require('path');
13
+
14
+ interface SettingsFacade {
15
+ getSettingsFile(): string;
16
+ }
17
+
18
+ interface SetupPlanFacade {
19
+ getNestedValue(source: Record<string, unknown>, pathStr: string): unknown;
20
+ }
21
+
22
+ interface JournalEntry {
23
+ readonly appliedAt: string;
24
+ readonly appliedValue: unknown;
25
+ readonly path: string;
26
+ readonly priorValue: unknown;
27
+ readonly repoRoot: string | null;
28
+ readonly scope: string;
29
+ readonly [key: string]: unknown;
30
+ }
31
+
32
+ interface SetupJournal {
33
+ entries: JournalEntry[];
34
+ version: number;
35
+ [key: string]: unknown;
36
+ }
37
+
38
+ // These CommonJS paths intentionally resolve beside the emitted module in lib/.
39
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
40
+ const { getSettingsFile }: SettingsFacade = require('./settings');
41
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
42
+ const { getNestedValue }: SetupPlanFacade = require('./setup-plan');
43
+
44
+ function getJournalPath(): string {
45
+ return path.join(path.dirname(getSettingsFile()), 'setup-undo-journal.json');
46
+ }
47
+
48
+ function isRecord(value: unknown): value is Record<string, unknown> {
49
+ return typeof value === 'object' && value !== null;
50
+ }
51
+
52
+ function isSetupJournal(value: unknown): value is SetupJournal {
53
+ return isRecord(value) && Array.isArray(value.entries);
54
+ }
55
+
56
+ function emptyJournal(): SetupJournal {
57
+ return { version: 1, entries: [] };
58
+ }
59
+
60
+ function loadJournal(): SetupJournal {
61
+ const journalPath = getJournalPath();
62
+ if (!fs.existsSync(journalPath)) {
63
+ return emptyJournal();
64
+ }
65
+ try {
66
+ const parsed: unknown = JSON.parse(fs.readFileSync(journalPath, 'utf8'));
67
+ if (!isSetupJournal(parsed)) {
68
+ return emptyJournal();
69
+ }
70
+ return parsed;
71
+ } catch {
72
+ return emptyJournal();
73
+ }
74
+ }
75
+
76
+ function saveJournal(journal: SetupJournal): void {
77
+ const journalPath = getJournalPath();
78
+ fs.mkdirSync(path.dirname(journalPath), { recursive: true });
79
+ fs.writeFileSync(journalPath, JSON.stringify(journal, null, 2), 'utf8');
80
+ }
81
+
82
+ function entryKey(entry: JournalEntry): string {
83
+ return `${entry.scope}:${entry.repoRoot || ''}:${entry.path}`;
84
+ }
85
+
86
+ // Re-applying an already-journaled write updates appliedValue/appliedAt but
87
+ // keeps the original priorValue — that's the true pre-apply state undo must
88
+ // restore to, and it must not drift across repeated `apply` runs.
89
+ function upsertJournalEntry(journal: SetupJournal, entry: JournalEntry): void {
90
+ const key = entryKey(entry);
91
+ const existingIndex = journal.entries.findIndex((candidate) => entryKey(candidate) === key);
92
+ if (existingIndex === -1) {
93
+ journal.entries.push(entry);
94
+ return;
95
+ }
96
+ const existing = journal.entries[existingIndex];
97
+ if (!existing) {
98
+ throw new Error('Journal entry disappeared during synchronous update');
99
+ }
100
+ journal.entries[existingIndex] = {
101
+ ...entry,
102
+ priorValue: existing.priorValue,
103
+ };
104
+ }
105
+
106
+ function safePathSegments(pathStr: string): string[] {
107
+ const keys = pathStr.split('.');
108
+ for (const key of keys) {
109
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
110
+ throw new Error(`Unsafe setup journal path: ${pathStr}`);
111
+ }
112
+ }
113
+ return keys;
114
+ }
115
+
116
+ function defineOwnValue(target: Record<string, unknown>, key: string, value: unknown): void {
117
+ Object.defineProperty(target, key, {
118
+ configurable: true,
119
+ enumerable: true,
120
+ value,
121
+ writable: true,
122
+ });
123
+ }
124
+
125
+ function setNestedValue(target: Record<string, unknown>, pathStr: string, value: unknown): void {
126
+ const keys = safePathSegments(pathStr);
127
+ let node = target;
128
+ for (let index = 0; index < keys.length - 1; index++) {
129
+ const key = keys[index] || '';
130
+ const child = Object.hasOwn(node, key) ? node[key] : undefined;
131
+ if (isRecord(child)) {
132
+ node = child;
133
+ } else {
134
+ const replacement: Record<string, unknown> = {};
135
+ defineOwnValue(node, key, replacement);
136
+ node = replacement;
137
+ }
138
+ }
139
+ defineOwnValue(node, keys[keys.length - 1] || '', value);
140
+ }
141
+
142
+ function deleteNestedKey(target: Record<string, unknown>, pathStr: string): void {
143
+ const keys = safePathSegments(pathStr);
144
+ let node = target;
145
+ for (let index = 0; index < keys.length - 1; index++) {
146
+ const key = keys[index] || '';
147
+ if (!Object.hasOwn(node, key)) return;
148
+ const child = node[key];
149
+ if (!isRecord(child)) return;
150
+ node = child;
151
+ }
152
+ const finalKey = keys[keys.length - 1] || '';
153
+ if (Object.hasOwn(node, finalKey)) {
154
+ delete node[finalKey];
155
+ }
156
+ }
157
+
158
+ function deepEqual(left: unknown, right: unknown): boolean {
159
+ if (left === right) return true;
160
+ if (!isRecord(left) || !isRecord(right)) return false;
161
+ if (Array.isArray(left) !== Array.isArray(right)) return false;
162
+ if (Array.isArray(left) && Array.isArray(right)) {
163
+ if (left.length !== right.length) return false;
164
+ return left.every((value, index) => deepEqual(value, right[index]));
165
+ }
166
+ const leftKeys = Object.keys(left);
167
+ const rightKeys = Object.keys(right);
168
+ if (leftKeys.length !== rightKeys.length) return false;
169
+ return leftKeys.every(
170
+ (key) =>
171
+ Object.prototype.hasOwnProperty.call(right, key) &&
172
+ deepEqual(Reflect.get(left, key), Reflect.get(right, key))
173
+ );
174
+ }
175
+
176
+ export = {
177
+ getJournalPath,
178
+ loadJournal,
179
+ saveJournal,
180
+ upsertJournalEntry,
181
+ getNestedValue,
182
+ setNestedValue,
183
+ deleteNestedKey,
184
+ deepEqual,
185
+ };