@the-open-engine/zeroshot 6.38.1 → 6.39.0

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 (377) hide show
  1. package/cli/event-copy.js +8 -7
  2. package/cli/event-copy.ts +12 -0
  3. package/cli/index.js +7 -31
  4. package/cli/json-export.js +61 -64
  5. package/cli/json-export.ts +112 -0
  6. package/cli/message-formatter-utils.js +68 -56
  7. package/cli/message-formatter-utils.ts +121 -0
  8. package/cli/message-formatters-normal.js +124 -259
  9. package/cli/message-formatters-normal.ts +250 -0
  10. package/cli/message-formatters-watch.js +81 -84
  11. package/cli/message-formatters-watch.ts +189 -0
  12. package/lib/cluster/generated/protocol-schema.cjs +1 -1
  13. package/lib/cluster/generated/protocol-schema.mjs +1 -1
  14. package/lib/cluster/generated/protocol.d.cts +8 -0
  15. package/lib/cluster/generated/protocol.d.mts +8 -0
  16. package/lib/cluster/generated/protocol.d.ts +8 -0
  17. package/lib/clusters-registry.d.ts +23 -0
  18. package/lib/clusters-registry.js +17 -29
  19. package/lib/completion.d.ts +12 -0
  20. package/lib/completion.js +84 -94
  21. package/lib/compose-utils.d.ts +32 -0
  22. package/lib/compose-utils.js +57 -72
  23. package/lib/detached-startup.d.ts +83 -0
  24. package/lib/detached-startup.js +231 -302
  25. package/lib/docker-config.d.ts +43 -0
  26. package/lib/docker-config.js +150 -325
  27. package/lib/docker-env-auth.d.ts +17 -0
  28. package/lib/docker-env-auth.js +75 -0
  29. package/lib/git-remote-utils.d.ts +48 -0
  30. package/lib/git-remote-utils.js +147 -213
  31. package/lib/id-detector.d.ts +15 -0
  32. package/lib/id-detector.js +37 -41
  33. package/lib/path-check.d.ts +30 -0
  34. package/lib/path-check.js +42 -45
  35. package/lib/process-liveness.d.ts +16 -0
  36. package/lib/process-liveness.js +19 -12
  37. package/lib/provider-credential-path.d.ts +16 -0
  38. package/lib/provider-credential-path.js +31 -29
  39. package/lib/provider-defaults.d.ts +30 -0
  40. package/lib/provider-defaults.js +47 -42
  41. package/lib/provider-detection.d.ts +11 -0
  42. package/lib/provider-detection.js +91 -54
  43. package/lib/provider-names.d.ts +38 -0
  44. package/lib/provider-names.js +56 -64
  45. package/lib/repo-settings.d.ts +28 -0
  46. package/lib/repo-settings.js +83 -59
  47. package/lib/run-mode.d.ts +21 -0
  48. package/lib/run-mode.js +21 -20
  49. package/lib/run-plan.d.ts +28 -0
  50. package/lib/run-plan.js +17 -16
  51. package/lib/settings/claude-auth.d.ts +35 -0
  52. package/lib/settings/claude-auth.js +30 -47
  53. package/lib/settings-defaults.d.ts +20 -0
  54. package/lib/settings-defaults.js +149 -0
  55. package/lib/settings-error.d.ts +7 -0
  56. package/lib/settings-error.js +8 -0
  57. package/lib/settings-issue-providers.d.ts +10 -0
  58. package/lib/settings-issue-providers.js +12 -0
  59. package/lib/settings-models.d.ts +22 -0
  60. package/lib/settings-models.js +93 -0
  61. package/lib/settings-storage.d.ts +23 -0
  62. package/lib/settings-storage.js +205 -0
  63. package/lib/settings-validation.d.ts +7 -0
  64. package/lib/settings-validation.js +182 -0
  65. package/lib/settings.d.ts +29 -0
  66. package/lib/settings.js +41 -736
  67. package/lib/setup-apply-types.d.ts +66 -0
  68. package/lib/setup-apply-types.js +2 -0
  69. package/lib/setup-apply.d.ts +39 -0
  70. package/lib/setup-apply.js +267 -264
  71. package/lib/setup-journal.d.ts +41 -0
  72. package/lib/setup-journal.js +119 -76
  73. package/lib/setup-plan-types.d.ts +102 -0
  74. package/lib/setup-plan-types.js +2 -0
  75. package/lib/setup-plan.d.ts +30 -0
  76. package/lib/setup-plan.js +300 -324
  77. package/lib/setup-undo.d.ts +44 -0
  78. package/lib/setup-undo.js +61 -74
  79. package/lib/start-cluster-config.d.ts +26 -0
  80. package/lib/start-cluster-config.js +56 -0
  81. package/lib/start-cluster-environment.d.ts +40 -0
  82. package/lib/start-cluster-environment.js +134 -0
  83. package/lib/start-cluster-run-options.d.ts +53 -0
  84. package/lib/start-cluster-run-options.js +147 -0
  85. package/lib/start-cluster.d.ts +64 -0
  86. package/lib/start-cluster.js +98 -454
  87. package/lib/stream-json-parser.d.ts +17 -0
  88. package/lib/stream-json-parser.js +51 -52
  89. package/npm-shrinkwrap.json +2 -2
  90. package/package.json +12 -6
  91. package/scripts/postinstall.js +27 -0
  92. package/scripts/rust-distribution.js +23 -14
  93. package/src/agent/agent-command-proofs-context.js +35 -45
  94. package/src/agent/agent-command-proofs-context.ts +87 -0
  95. package/src/agent/agent-config-settings.js +19 -0
  96. package/src/agent/agent-config-settings.ts +41 -0
  97. package/src/agent/agent-config.js +122 -231
  98. package/src/agent/agent-config.ts +285 -0
  99. package/src/agent/agent-context-builder.js +38 -273
  100. package/src/agent/agent-context-builder.ts +51 -0
  101. package/src/agent/agent-context-environment-sections.js +105 -0
  102. package/src/agent/agent-context-environment-sections.ts +134 -0
  103. package/src/agent/agent-context-packs.js +165 -0
  104. package/src/agent/agent-context-packs.ts +296 -0
  105. package/src/agent/agent-context-prompt-sections.js +100 -0
  106. package/src/agent/agent-context-prompt-sections.ts +148 -0
  107. package/src/agent/agent-context-sections.js +25 -334
  108. package/src/agent/agent-context-sections.ts +36 -0
  109. package/src/agent/agent-context-sources.js +96 -149
  110. package/src/agent/agent-context-sources.ts +253 -0
  111. package/src/agent/agent-context-types.js +2 -0
  112. package/src/agent/agent-context-types.ts +132 -0
  113. package/src/agent/agent-context-validation-sections.js +73 -0
  114. package/src/agent/agent-context-validation-sections.ts +129 -0
  115. package/src/agent/agent-input-injector.js +99 -118
  116. package/src/agent/agent-input-injector.ts +195 -0
  117. package/src/agent/agent-liveness-poll.js +74 -78
  118. package/src/agent/agent-liveness-poll.ts +163 -0
  119. package/src/agent/agent-quality-gate-schema.js +77 -86
  120. package/src/agent/agent-quality-gate-schema.ts +135 -0
  121. package/src/agent/agent-quality-gates-context.js +44 -50
  122. package/src/agent/agent-quality-gates-context.ts +91 -0
  123. package/src/agent/agent-stuck-detector.js +171 -239
  124. package/src/agent/agent-stuck-detector.ts +288 -0
  125. package/src/agent/agent-trigger-evaluator.js +29 -46
  126. package/src/agent/agent-trigger-evaluator.ts +98 -0
  127. package/src/agent/context-metrics.js +110 -141
  128. package/src/agent/context-metrics.ts +261 -0
  129. package/src/agent/context-pack-builder.js +72 -358
  130. package/src/agent/context-pack-builder.ts +105 -0
  131. package/src/agent/context-pack-core.js +70 -0
  132. package/src/agent/context-pack-core.ts +96 -0
  133. package/src/agent/context-pack-selection.js +119 -0
  134. package/src/agent/context-pack-selection.ts +187 -0
  135. package/src/agent/context-pack-truncation.js +139 -0
  136. package/src/agent/context-pack-truncation.ts +208 -0
  137. package/src/agent/context-pack-types.js +2 -0
  138. package/src/agent/context-pack-types.ts +98 -0
  139. package/src/agent/context-replay-policy.js +30 -39
  140. package/src/agent/context-replay-policy.ts +71 -0
  141. package/src/agent/critical-agent-policy.js +6 -10
  142. package/src/agent/critical-agent-policy.ts +20 -0
  143. package/src/agent/guidance-queue.js +74 -87
  144. package/src/agent/guidance-queue.ts +166 -0
  145. package/src/agent/output-extraction-error-detail.js +45 -0
  146. package/src/agent/output-extraction-error-detail.ts +49 -0
  147. package/src/agent/output-extraction-failures.js +125 -0
  148. package/src/agent/output-extraction-failures.ts +149 -0
  149. package/src/agent/output-extraction-json.js +234 -0
  150. package/src/agent/output-extraction-json.ts +235 -0
  151. package/src/agent/output-extraction-pi.js +124 -0
  152. package/src/agent/output-extraction-pi.ts +137 -0
  153. package/src/agent/output-extraction-types.js +2 -0
  154. package/src/agent/output-extraction-types.ts +49 -0
  155. package/src/agent/output-extraction.js +17 -617
  156. package/src/agent/output-extraction.ts +30 -0
  157. package/src/agent/output-reformatter-errors.js +83 -0
  158. package/src/agent/output-reformatter-errors.ts +106 -0
  159. package/src/agent/output-reformatter-types.js +2 -0
  160. package/src/agent/output-reformatter-types.ts +86 -0
  161. package/src/agent/output-reformatter.js +140 -185
  162. package/src/agent/output-reformatter.ts +238 -0
  163. package/src/agent/pi-terminal-lifecycle-types.js +2 -0
  164. package/src/agent/pi-terminal-lifecycle-types.ts +41 -0
  165. package/src/agent/pi-terminal-lifecycle.js +186 -206
  166. package/src/agent/pi-terminal-lifecycle.ts +297 -0
  167. package/src/agent/provider-control-plane.js +22 -21
  168. package/src/agent/provider-control-plane.ts +52 -0
  169. package/src/agent/provider-terminal-failure-types.js +2 -0
  170. package/src/agent/provider-terminal-failure-types.ts +60 -0
  171. package/src/agent/provider-terminal-failure.js +194 -191
  172. package/src/agent/provider-terminal-failure.ts +262 -0
  173. package/src/agent/rate-limit-backoff.js +31 -74
  174. package/src/agent/rate-limit-backoff.ts +71 -0
  175. package/src/agent/schema-utils.js +85 -125
  176. package/src/agent/schema-utils.ts +148 -0
  177. package/src/agent/structured-output-error.js +34 -33
  178. package/src/agent/structured-output-error.ts +105 -0
  179. package/src/agent/validation-platform.js +27 -27
  180. package/src/agent/validation-platform.ts +49 -0
  181. package/src/attach/attach-client.js +232 -405
  182. package/src/attach/attach-client.ts +300 -0
  183. package/src/attach/attach-server-cleanup.js +43 -0
  184. package/src/attach/attach-server-cleanup.ts +39 -0
  185. package/src/attach/attach-server-clients.js +117 -0
  186. package/src/attach/attach-server-clients.ts +122 -0
  187. package/src/attach/attach-server-events.js +38 -0
  188. package/src/attach/attach-server-events.ts +36 -0
  189. package/src/attach/attach-server-pty.js +45 -0
  190. package/src/attach/attach-server-pty.ts +64 -0
  191. package/src/attach/attach-server-runtime.js +73 -0
  192. package/src/attach/attach-server-runtime.ts +62 -0
  193. package/src/attach/attach-server-socket.js +28 -0
  194. package/src/attach/attach-server-socket.ts +23 -0
  195. package/src/attach/attach-server-types.js +2 -0
  196. package/src/attach/attach-server-types.ts +82 -0
  197. package/src/attach/attach-server.js +106 -523
  198. package/src/attach/attach-server.ts +158 -0
  199. package/src/attach/index.js +18 -35
  200. package/src/attach/index.ts +18 -0
  201. package/src/attach/protocol.js +95 -184
  202. package/src/attach/protocol.ts +181 -0
  203. package/src/attach/ring-buffer.js +76 -102
  204. package/src/attach/ring-buffer.ts +109 -0
  205. package/src/attach/send-input.js +64 -75
  206. package/src/attach/send-input.ts +94 -0
  207. package/src/attach/socket-discovery.js +164 -218
  208. package/src/attach/socket-discovery.ts +230 -0
  209. package/src/attach/socket-paths.js +42 -61
  210. package/src/attach/socket-paths.ts +81 -0
  211. package/src/claude-credentials.js +51 -54
  212. package/src/claude-credentials.ts +89 -0
  213. package/src/cluster/generated/protocol-schema.ts +1 -1
  214. package/src/cluster/generated/protocol.ts +2 -1
  215. package/src/command-proofs.js +110 -141
  216. package/src/command-proofs.ts +216 -0
  217. package/src/config-router.js +81 -73
  218. package/src/config-router.ts +131 -0
  219. package/src/copy-worker.js +53 -29
  220. package/src/copy-worker.ts +75 -0
  221. package/src/darwin-keychain-boundary.js +80 -111
  222. package/src/darwin-keychain-boundary.ts +148 -0
  223. package/src/guidance-topics.js +4 -5
  224. package/src/guidance-topics.ts +10 -0
  225. package/src/input-helpers.js +33 -54
  226. package/src/input-helpers.ts +58 -0
  227. package/src/ledger-sequence.js +45 -48
  228. package/src/ledger-sequence.ts +63 -0
  229. package/src/legacy-lib/clusters-registry.ts +40 -0
  230. package/src/legacy-lib/completion.ts +148 -0
  231. package/src/legacy-lib/compose-utils.ts +102 -0
  232. package/src/legacy-lib/detached-startup.ts +449 -0
  233. package/src/legacy-lib/docker-config.ts +282 -0
  234. package/src/legacy-lib/docker-env-auth.ts +116 -0
  235. package/src/legacy-lib/git-remote-utils.ts +244 -0
  236. package/src/legacy-lib/id-detector.ts +57 -0
  237. package/src/legacy-lib/js-yaml.d.ts +3 -0
  238. package/src/legacy-lib/omelette.d.ts +15 -0
  239. package/src/legacy-lib/path-check.ts +89 -0
  240. package/src/legacy-lib/process-liveness.ts +36 -0
  241. package/src/legacy-lib/provider-credential-path.ts +57 -0
  242. package/src/legacy-lib/provider-defaults.ts +114 -0
  243. package/src/legacy-lib/provider-detection.ts +67 -0
  244. package/src/legacy-lib/provider-names.ts +126 -0
  245. package/src/legacy-lib/repo-settings.ts +88 -0
  246. package/src/legacy-lib/run-mode.ts +58 -0
  247. package/src/legacy-lib/run-plan.ts +49 -0
  248. package/src/legacy-lib/settings/claude-auth.ts +80 -0
  249. package/src/legacy-lib/settings-defaults.ts +212 -0
  250. package/src/legacy-lib/settings-error.ts +8 -0
  251. package/src/legacy-lib/settings-issue-providers.ts +18 -0
  252. package/src/legacy-lib/settings-models.ts +124 -0
  253. package/src/legacy-lib/settings-storage.ts +277 -0
  254. package/src/legacy-lib/settings-validation.ts +220 -0
  255. package/src/legacy-lib/settings.ts +87 -0
  256. package/src/legacy-lib/setup-apply-types.ts +70 -0
  257. package/src/legacy-lib/setup-apply.ts +456 -0
  258. package/src/legacy-lib/setup-journal.ts +185 -0
  259. package/src/legacy-lib/setup-plan-types.ts +96 -0
  260. package/src/legacy-lib/setup-plan.ts +500 -0
  261. package/src/legacy-lib/setup-undo.ts +142 -0
  262. package/src/legacy-lib/start-cluster-config.ts +124 -0
  263. package/src/legacy-lib/start-cluster-environment.ts +165 -0
  264. package/src/legacy-lib/start-cluster-run-options.ts +279 -0
  265. package/src/legacy-lib/start-cluster.ts +237 -0
  266. package/src/legacy-lib/stream-json-parser.ts +88 -0
  267. package/src/message-buffer.js +55 -71
  268. package/src/message-buffer.ts +107 -0
  269. package/src/message-bus-bridge.js +92 -133
  270. package/src/message-bus-bridge.ts +149 -0
  271. package/src/name-generator-shared.js +220 -0
  272. package/src/name-generator-shared.ts +231 -0
  273. package/src/name-generator.js +8 -217
  274. package/src/name-generator.ts +27 -0
  275. package/src/omp-blob-root.js +83 -64
  276. package/src/omp-blob-root.ts +116 -0
  277. package/src/omp-config-overlay.js +65 -38
  278. package/src/omp-config-overlay.ts +110 -0
  279. package/src/omp-execution-fingerprint.js +38 -43
  280. package/src/omp-execution-fingerprint.ts +92 -0
  281. package/src/omp-session-limits.js +10 -15
  282. package/src/omp-session-limits.ts +53 -0
  283. package/src/omp-session-partition.js +2 -2
  284. package/src/omp-session-verifier.js +1 -1
  285. package/src/orchestrator.js +19 -4
  286. package/src/providers/anthropic/index.js +3 -1
  287. package/src/providers/anthropic/index.ts +13 -0
  288. package/src/providers/capabilities.js +19 -27
  289. package/src/providers/capabilities.ts +44 -0
  290. package/src/providers/google/index.js +3 -1
  291. package/src/providers/google/index.ts +13 -0
  292. package/src/providers/openai/index.js +3 -1
  293. package/src/providers/openai/index.ts +13 -0
  294. package/src/providers/opencode/index.js +3 -1
  295. package/src/providers/opencode/index.ts +13 -0
  296. package/src/quality-gates.js +67 -127
  297. package/src/quality-gates.ts +149 -0
  298. package/src/repo-settings-access.js +40 -0
  299. package/src/repo-settings-access.ts +64 -0
  300. package/src/schemas/sub-cluster.js +127 -183
  301. package/src/schemas/sub-cluster.ts +238 -0
  302. package/src/state-snapshot-normalization.js +224 -0
  303. package/src/state-snapshot-normalization.ts +222 -0
  304. package/src/state-snapshot.js +192 -359
  305. package/src/state-snapshot.ts +291 -0
  306. package/src/state-snapshotter.js +118 -130
  307. package/src/state-snapshotter.ts +167 -0
  308. package/src/sub-cluster-wrapper.js +4 -13
  309. package/src/task-run-model-args.js +87 -124
  310. package/src/task-run-model-args.ts +204 -0
  311. package/src/task-runner.js +5 -25
  312. package/src/task-runner.ts +28 -0
  313. package/src/task-startup-error.js +73 -58
  314. package/src/task-startup-error.ts +126 -0
  315. package/src/template-validation/consensus-gate-contracts.js +2 -0
  316. package/src/template-validation/consensus-gate-contracts.ts +70 -0
  317. package/src/template-validation/consensus-gate-scenarios.js +76 -0
  318. package/src/template-validation/consensus-gate-scenarios.ts +130 -0
  319. package/src/template-validation/consensus-gate-stage.js +34 -0
  320. package/src/template-validation/consensus-gate-stage.ts +56 -0
  321. package/src/template-validation/random-topology-contracts.js +2 -0
  322. package/src/template-validation/random-topology-contracts.ts +54 -0
  323. package/src/template-validation/random-topology-dispatch.js +25 -0
  324. package/src/template-validation/random-topology-dispatch.ts +33 -0
  325. package/src/template-validation/random-topology-message-loop.js +53 -0
  326. package/src/template-validation/random-topology-message-loop.ts +73 -0
  327. package/src/template-validation/random-topology-operations.js +79 -0
  328. package/src/template-validation/random-topology-operations.ts +101 -0
  329. package/src/template-validation/random-topology-runtime.js +25 -0
  330. package/src/template-validation/random-topology-runtime.ts +43 -0
  331. package/src/template-validation/random-topology-scenario.js +52 -0
  332. package/src/template-validation/random-topology-scenario.ts +57 -0
  333. package/src/template-validation/random-topology-schema.js +159 -0
  334. package/src/template-validation/random-topology-schema.ts +190 -0
  335. package/src/template-validation/random-topology-state.js +28 -0
  336. package/src/template-validation/random-topology-state.ts +30 -0
  337. package/src/template-validation/random-topology-trigger-evaluation.js +45 -0
  338. package/src/template-validation/random-topology-trigger-evaluation.ts +65 -0
  339. package/src/template-validation/random-topology-trigger.js +75 -0
  340. package/src/template-validation/random-topology-trigger.ts +88 -0
  341. package/src/template-validation/report-formatter.js +40 -46
  342. package/src/template-validation/report-formatter.ts +101 -0
  343. package/src/template-validation/simulate-consensus-gates.js +111 -302
  344. package/src/template-validation/simulate-consensus-gates.ts +196 -0
  345. package/src/template-validation/simulate-random-topology.js +26 -519
  346. package/src/template-validation/simulate-random-topology.ts +57 -0
  347. package/src/template-validation/simulate-two-stage-validation.js +41 -265
  348. package/src/template-validation/simulate-two-stage-validation.ts +55 -0
  349. package/src/template-validation/simulation-agent-runtime.js +25 -0
  350. package/src/template-validation/simulation-agent-runtime.ts +40 -0
  351. package/src/template-validation/simulation-agent.js +29 -0
  352. package/src/template-validation/simulation-agent.ts +73 -0
  353. package/src/template-validation/simulation-runtime.js +34 -0
  354. package/src/template-validation/simulation-runtime.ts +72 -0
  355. package/src/template-validation/two-stage-contracts.js +2 -0
  356. package/src/template-validation/two-stage-contracts.ts +41 -0
  357. package/src/template-validation/two-stage-inputs.js +21 -0
  358. package/src/template-validation/two-stage-inputs.ts +29 -0
  359. package/src/template-validation/two-stage-results.js +30 -0
  360. package/src/template-validation/two-stage-results.ts +40 -0
  361. package/src/template-validation/two-stage-scenario.js +86 -0
  362. package/src/template-validation/two-stage-scenario.ts +120 -0
  363. package/src/worktree-claude-config.js +182 -220
  364. package/src/worktree-claude-config.ts +272 -0
  365. package/src/worktree-tooling-env.js +103 -125
  366. package/src/worktree-tooling-env.ts +142 -0
  367. package/task-lib/completion.js +2 -3
  368. package/task-lib/completion.ts +9 -0
  369. package/task-lib/config.js +1 -4
  370. package/task-lib/config.ts +11 -0
  371. package/task-lib/name-generator.js +4 -226
  372. package/task-lib/name-generator.ts +11 -0
  373. package/task-lib/omp-session-cleanup.js +1 -1
  374. package/task-lib/omp-storage-root.js +8 -12
  375. package/task-lib/omp-storage-root.ts +62 -0
  376. package/task-lib/process-termination.js +143 -166
  377. 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
+ };