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