claude-dev-env 7.6.0 → 7.7.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 (35) hide show
  1. package/.agents/skills/session-continuity/README.md +19 -9
  2. package/AGENTS.md +48 -71
  3. package/_shared/advisor/advisor-protocol.md +2 -2
  4. package/_shared/advisor/reference/advisor-block.md +1 -1
  5. package/_shared/advisor/reference/astra-rung.md +2 -2
  6. package/_shared/advisor/scripts/codex_astra_advisor.py +122 -23
  7. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +76 -9
  8. package/_shared/advisor/scripts/tests/test_advisor_route_constants.py +51 -0
  9. package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +52 -2
  10. package/bin/install-constants.mjs +3 -0
  11. package/bin/install-session-continuity.mjs +73 -30
  12. package/bin/install-session-continuity.test.mjs +52 -1
  13. package/bin/install-transaction.mjs +41 -44
  14. package/bin/install.cursor-rules.test.mjs +311 -3
  15. package/bin/install.mjs +226 -95
  16. package/bin/install.test.mjs +57 -0
  17. package/bin/install.transaction.test.mjs +61 -1
  18. package/bin/pstack-model-preferences.codex.json +1 -0
  19. package/bin/pstack.mjs +28 -6
  20. package/bin/pstack.test.mjs +48 -0
  21. package/bin/select-pstack-models.test.mjs +375 -26
  22. package/bin/subagent-model-policy.test.mjs +399 -0
  23. package/docs/codex-compatibility.md +19 -5
  24. package/docs/subagent-model-routing.md +50 -0
  25. package/hooks/hooks.json +10 -0
  26. package/hooks/routing/subagent_model_routing.mjs +81 -0
  27. package/hooks/routing/subagent_model_routing.test.mjs +218 -0
  28. package/package.json +1 -1
  29. package/rules/pstack-models.md +22 -0
  30. package/rules/subagent-model-policy.json +85 -0
  31. package/scripts/resolve_advisor_model_route.mjs +52 -0
  32. package/scripts/select_pstack_models.mjs +203 -38
  33. package/scripts/subagent_model_policy.mjs +620 -0
  34. package/scripts/tests/resolve_advisor_model_route.test.mjs +51 -0
  35. package/scripts/tests/test_codex_compat_materializer.py +15 -0
@@ -1,12 +1,13 @@
1
1
  """Behavioral tests for the executable Codex Astra advisor path."""
2
- import io
3
2
  import importlib.util
3
+ import io
4
4
  import json
5
5
  import subprocess
6
6
  import sys
7
7
  from collections.abc import Callable
8
8
  from pathlib import Path
9
9
  from types import ModuleType
10
+
10
11
  import pytest
11
12
 
12
13
  _ProcessRunner = Callable[..., subprocess.CompletedProcess[str]]
@@ -28,6 +29,7 @@ def _load_astra_module() -> ModuleType:
28
29
 
29
30
 
30
31
  astra_advisor = _load_astra_module()
32
+
31
33
  SCRIPTS_ROOT = Path(__file__).parent.parent
32
34
  USAGE_PROBE_PATH = (
33
35
  SCRIPTS_ROOT.parents[1] / "pr-loop" / "scripts" / "codex_usage_probe.py"
@@ -87,7 +89,37 @@ def test_astra_flag_rejects_legacy_sol_setting() -> None:
87
89
 
88
90
  def test_resolve_advisor_effort_uses_shared_setting_and_default() -> None:
89
91
  assert astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "HIGH"}) == "high"
90
- assert astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "unknown"}) == "low"
92
+ with pytest.raises(RuntimeError, match="effort is unknown"):
93
+ astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "unknown"})
94
+ assert astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "Light"}) == "low"
95
+ assert astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "XHIGH"}) == "medium"
96
+ assert astra_advisor.resolve_advisor_effort({"ADVISOR_EFFORT": "MAX"}) == "medium"
97
+
98
+
99
+ def test_advisor_route_uses_edited_policy_model_and_effort(tmp_path: Path) -> None:
100
+ policy = json.loads(
101
+ (SCRIPTS_ROOT.parents[2] / "rules" / "subagent-model-policy.json").read_text(
102
+ encoding="utf-8"
103
+ )
104
+ )
105
+ for each_entry in policy["roleReplacements"]:
106
+ if (
107
+ each_entry["role"] == "advisor"
108
+ and each_entry["requested"] == {"model": "astra", "effort": "xhigh"}
109
+ ):
110
+ each_entry["selected"] = {"model": "luna", "effort": "high"}
111
+ policy_path = tmp_path / "subagent-model-policy.json"
112
+ policy_path.write_text(json.dumps(policy), encoding="utf-8")
113
+ assert astra_advisor.resolve_advisor_pair(
114
+ {"ADVISOR_EFFORT": "xhigh"}, policy_path
115
+ ) == ("gpt-5.6-luna", "high")
116
+
117
+
118
+ def test_advisor_route_blocks_malformed_policy(tmp_path: Path) -> None:
119
+ policy_path = tmp_path / "subagent-model-policy.json"
120
+ policy_path.write_text('{"schemaVersion": 1}', encoding="utf-8")
121
+ with pytest.raises(RuntimeError, match="models must be an object"):
122
+ astra_advisor.resolve_advisor_pair({}, policy_path)
91
123
 
92
124
 
93
125
  def test_resolve_codex_executable_prefers_override_and_falls_back_to_path(
@@ -118,6 +150,7 @@ def test_argument_parser_accepts_astra_and_rejects_sol() -> None:
118
150
  parser = astra_advisor.build_argument_parser()
119
151
  parsed = parser.parse_args(["--bind", "--cwd", ".", "--enable-astra"])
120
152
  assert parsed.is_astra_requested
153
+ assert parser.parse_args(["--bind", "--cwd", ".", "--effort", "light"]).astra_effort == "light"
121
154
  with pytest.raises(SystemExit):
122
155
  parser.parse_args(["--bind", "--cwd", ".", "--enable-sol"])
123
156
 
@@ -190,6 +223,23 @@ def test_bind_runs_probe_then_codex() -> None:
190
223
  assert calls[1] == astra_advisor.build_codex_arguments("codex")
191
224
 
192
225
 
226
+ def test_bind_routes_astra_xhigh_to_the_policy_effort() -> None:
227
+ calls: list[list[str]] = []
228
+ settings = {**ENABLED_SETTINGS, astra_advisor.ADVISOR_EFFORT_ENV_VAR: "xhigh"}
229
+ reply = astra_advisor.run_codex_astra_advisor(
230
+ "consult",
231
+ Path("."),
232
+ None,
233
+ USAGE_PROBE_PATH,
234
+ settings,
235
+ None,
236
+ _two_step_runner(calls),
237
+ )
238
+ assert reply.successful
239
+ config_index = calls[1].index("--config")
240
+ assert calls[1][config_index + 1] == 'model_reasoning_effort="medium"'
241
+
242
+
193
243
  def test_disabled_flag_returns_declined_fallback() -> None:
194
244
  reply = astra_advisor.run_codex_astra_advisor(
195
245
  "consult",
@@ -56,6 +56,7 @@ export const RUN_BACKUP_DIRECTORY_NAME_PATTERN =
56
56
  export const PACKAGE_AGENTS_HOME_DIRECTORY_NAME = '.agents';
57
57
 
58
58
  export const PSTACK_MODEL_RULE_FILE_NAME = 'pstack-models.mdc';
59
+ export const SUBAGENT_MODEL_POLICY_FILE_NAME = 'subagent-model-policy.json';
59
60
  export const PSTACK_SEEDED_MODEL_PREFERENCES_FILE_NAMES = Object.freeze([
60
61
  'pstack-model-preferences.claude.json',
61
62
  'pstack-model-preferences.codex.json',
@@ -181,3 +182,5 @@ export const WINDOWS_PYTHON_LAUNCHER_COMMAND = 'py -3';
181
182
  * Maximum time each Python interpreter probe may run during detection.
182
183
  */
183
184
  export const PYTHON_PROBE_TIMEOUT_MILLISECONDS = 10_000;
185
+ export const ADDITIONAL_SETTINGS_BLOB_PREFIX = 'settings-';
186
+ export const CODEX_NATIVE_ROUTING_MATCHER = 'multi_agent_v1__spawn_agent';
@@ -44,41 +44,84 @@ export function mergeContinuityHooks(existing, host, script) {
44
44
  return mergedConfiguration;
45
45
  }
46
46
 
47
- async function main() {
48
- const requested = process.argv.slice(2);
49
- const selected = requested.length ? requested : supportedHosts;
47
+ export const CONTINUITY_SCRIPT_MARKER = '/session-continuity/continuity.mjs';
48
+
49
+ export function isContinuityHookCommand(command) {
50
+ return typeof command === 'string' && command.includes(CONTINUITY_SCRIPT_MARKER);
51
+ }
52
+
53
+ export function continuityScriptPath(roots) {
54
+ return join(roots.skillsInstallDirectory, 'session-continuity', 'continuity.mjs');
55
+ }
56
+
57
+ export function continuityHostConfigurationPaths(roots) {
58
+ return {
59
+ claude: join(roots.managedRoot, 'settings.json'),
60
+ codex: join(dirname(roots.codexRulesInstallDirectory), 'hooks.json'),
61
+ cursor: join(roots.cursorInstallDirectory, 'hooks.json'),
62
+ };
63
+ }
64
+
65
+ export function removeContinuityHooks(configuration) {
66
+ let removedCount = 0;
67
+ for (const [event, entries] of Object.entries(configuration.hooks || {})) {
68
+ const kept = entries.flatMap(entry => {
69
+ if (!entry.hooks) {
70
+ if (!isContinuityHookCommand(entry.command)) return [entry];
71
+ removedCount += 1;
72
+ return [];
73
+ }
74
+ const hooks = entry.hooks.filter(hook => !isContinuityHookCommand(hook.command));
75
+ removedCount += entry.hooks.length - hooks.length;
76
+ return hooks.length ? [{ ...entry, hooks }] : [];
77
+ });
78
+ if (kept.length) configuration.hooks[event] = kept;
79
+ else delete configuration.hooks[event];
80
+ }
81
+ return removedCount;
82
+ }
83
+
84
+ function continuityConfigurationPlan(host, path, script) {
85
+ if (!existsSync(dirname(path))) throw new Error(`Host config directory is absent: ${dirname(path)}`);
86
+ const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
87
+ const merged = mergeContinuityHooks(existing ? JSON.parse(existing) : {}, host, script);
88
+ return { host, path, existing, content: JSON.stringify(merged, null, 2) + '\n' };
89
+ }
90
+
91
+ function applyContinuityConfigurationPlan(plan) {
92
+ if (plan.existing === plan.content) return { host: plan.host, path: plan.path, changed: false };
93
+ if (plan.existing !== null) {
94
+ const backup = `${plan.path}.before-session-continuity`;
95
+ if (!existsSync(backup)) writeFileSync(backup, plan.existing, { flag: 'wx', mode: 0o600 });
96
+ }
97
+ const current = existsSync(plan.path) ? readFileSync(plan.path, 'utf8') : null;
98
+ if (current !== plan.existing) throw new Error(`Config changed during setup: ${plan.path}. Rerun setup.`);
99
+ const temporary = `${plan.path}.continuity-${process.pid}.tmp`;
100
+ writeFileSync(temporary, plan.content, { flag: 'wx', mode: 0o600 });
101
+ renameSync(temporary, plan.path);
102
+ if (readFileSync(plan.path, 'utf8') !== plan.content) throw new Error(`Config read-back mismatch: ${plan.path}`);
103
+ return { host: plan.host, path: plan.path, changed: true };
104
+ }
105
+
106
+ export function configureContinuityHosts(roots, selected) {
50
107
  const unsupported = selected.find(host => !supportedHosts.includes(host));
51
108
  if (unsupported) throw new Error(`Unsupported host ${unsupported}`);
52
- const { resolveInstallRoot } = await import('./resolve-install-root.mjs');
53
- const roots = resolveInstallRoot();
54
- const script = join(roots.skillsInstallDirectory, 'session-continuity', 'continuity.mjs');
109
+ const script = continuityScriptPath(roots);
55
110
  if (!existsSync(script) || !existsSync(join(dirname(script), 'SKILL.md'))) {
56
111
  throw new Error('Run the full claude-dev-env installer from this checkout first so the companion is in the canonical agents home.');
57
112
  }
58
- const paths = {
59
- claude: join(roots.managedRoot, 'settings.json'),
60
- codex: join(dirname(roots.codexRulesInstallDirectory), 'hooks.json'),
61
- cursor: join(roots.cursorInstallDirectory, 'hooks.json'),
62
- };
63
- const plans = selected.map(host => {
64
- const path = paths[host];
65
- if (!existsSync(dirname(path))) throw new Error(`Host config directory is absent: ${dirname(path)}`);
66
- const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
67
- return { path, existing, content: JSON.stringify(mergeContinuityHooks(existing ? JSON.parse(existing) : {}, host, script), null, 2) + '\n' };
68
- });
69
- for (const plan of plans) {
70
- if (plan.existing === plan.content) { console.log(`Already configured: ${plan.path}`); continue; }
71
- if (plan.existing !== null) {
72
- const backup = `${plan.path}.before-session-continuity`;
73
- if (!existsSync(backup)) writeFileSync(backup, plan.existing, { flag: 'wx', mode: 0o600 });
74
- }
75
- const current = existsSync(plan.path) ? readFileSync(plan.path, 'utf8') : null;
76
- if (current !== plan.existing) throw new Error(`Config changed during setup: ${plan.path}. Rerun setup.`);
77
- const temporary = `${plan.path}.continuity-${process.pid}.tmp`;
78
- writeFileSync(temporary, plan.content, { flag: 'wx', mode: 0o600 });
79
- renameSync(temporary, plan.path);
80
- if (readFileSync(plan.path, 'utf8') !== plan.content) throw new Error(`Config read-back mismatch: ${plan.path}`);
81
- console.log(`Configured and read back: ${plan.path}`);
113
+ const paths = continuityHostConfigurationPaths(roots);
114
+ return selected
115
+ .map(host => continuityConfigurationPlan(host, paths[host], script))
116
+ .map(applyContinuityConfigurationPlan);
117
+ }
118
+
119
+ async function main() {
120
+ const requested = process.argv.slice(2);
121
+ const selected = requested.length ? requested : supportedHosts;
122
+ const { resolveInstallRoot } = await import('./resolve-install-root.mjs');
123
+ for (const result of configureContinuityHosts(resolveInstallRoot(), selected)) {
124
+ console.log(`${result.changed ? 'Configured and read back' : 'Already configured'}: ${result.path}`);
82
125
  }
83
126
  console.log('Review and trust the new hooks in each host.');
84
127
  }
@@ -1,6 +1,30 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
- import { continuityHookConfiguration, mergeContinuityHooks } from './install-session-continuity.mjs';
3
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import {
7
+ configureContinuityHosts,
8
+ continuityHookConfiguration,
9
+ mergeContinuityHooks,
10
+ removeContinuityHooks,
11
+ } from './install-session-continuity.mjs';
12
+
13
+ function installRootFixture() {
14
+ const root = mkdtempSync(join(tmpdir(), 'continuity-roots-'));
15
+ const skillsInstallDirectory = join(root, 'agents', 'skills');
16
+ const companionDirectory = join(skillsInstallDirectory, 'session-continuity');
17
+ mkdirSync(companionDirectory, { recursive: true });
18
+ writeFileSync(join(companionDirectory, 'continuity.mjs'), 'export default null;\n');
19
+ writeFileSync(join(companionDirectory, 'SKILL.md'), '# companion\n');
20
+ const managedRoot = join(root, 'claude');
21
+ const codexRulesInstallDirectory = join(root, 'codex', 'rules');
22
+ const cursorInstallDirectory = join(root, 'cursor');
23
+ for (const directory of [managedRoot, codexRulesInstallDirectory, cursorInstallDirectory]) {
24
+ mkdirSync(directory, { recursive: true });
25
+ }
26
+ return { managedRoot, skillsInstallDirectory, codexRulesInstallDirectory, cursorInstallDirectory };
27
+ }
4
28
 
5
29
  test('setup preserves other hooks and settings, is idempotent, and rejects competing profiles', () => {
6
30
  const existing = { permissions: { deny: ['secret'] }, hooks: { SessionStart: [{ hooks: [{ type: 'command', command: 'existing-hook' }] }] } };
@@ -53,3 +77,30 @@ for (const host of ['claude', 'codex']) {
53
77
  }
54
78
  });
55
79
  }
80
+
81
+ test('configureContinuityHosts writes each host config, reads it back, and repeats without a change', () => {
82
+ const roots = installRootFixture();
83
+ const settingsPath = join(roots.managedRoot, 'settings.json');
84
+ writeFileSync(settingsPath, JSON.stringify({ permissions: { deny: ['secret'] } }, null, 2) + '\n');
85
+ const first = configureContinuityHosts(roots, ['claude', 'codex', 'cursor']);
86
+ assert.deepEqual(first.map(result => result.changed), [true, true, true]);
87
+ const written = JSON.parse(readFileSync(settingsPath, 'utf8'));
88
+ assert.deepEqual(written.permissions.deny, ['secret']);
89
+ const command = written.hooks.SessionStart[0].hooks[0].command;
90
+ assert.equal(command.endsWith('session-continuity/continuity.mjs" hook claude'), true);
91
+ const second = configureContinuityHosts(roots, ['claude', 'codex', 'cursor']);
92
+ assert.deepEqual(second.map(result => result.changed), [false, false, false]);
93
+ });
94
+
95
+ test('removeContinuityHooks takes out only the companion registrations', () => {
96
+ const roots = installRootFixture();
97
+ configureContinuityHosts(roots, ['claude']);
98
+ const settingsPath = join(roots.managedRoot, 'settings.json');
99
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
100
+ settings.hooks.SessionStart.unshift({ hooks: [{ type: 'command', command: 'python other_hook.py' }] });
101
+ const removedCount = removeContinuityHooks(settings);
102
+ assert.equal(removedCount, 3);
103
+ assert.deepEqual(Object.keys(settings.hooks), ['SessionStart']);
104
+ assert.equal(settings.hooks.SessionStart.length, 1);
105
+ assert.equal(settings.hooks.SessionStart[0].hooks[0].command, 'python other_hook.py');
106
+ });
@@ -30,6 +30,7 @@ import {
30
30
  } from 'node:fs';
31
31
  import { dirname, join } from 'node:path';
32
32
  import { execFileSync } from 'node:child_process';
33
+ import { ADDITIONAL_SETTINGS_BLOB_PREFIX } from './install-constants.mjs';
33
34
 
34
35
  /** Env var that forces a throw after a named mutation phase (tests only). */
35
36
  export const INSTALL_FAULT_ENV = 'CLAUDE_DEV_ENV_INSTALL_FAULT';
@@ -180,19 +181,6 @@ function classifyPath(absolutePath, io) {
180
181
  return { kind: ENTRY_KIND_MISSING };
181
182
  }
182
183
 
183
- /**
184
- * Capture prior settings, manifest, hooksPath, and package-owned files.
185
- *
186
- * @param {{
187
- * managedRoot: string,
188
- * manifestFilePath: string,
189
- * settingsPath: string,
190
- * priorManifestFiles?: string[]|null,
191
- * journalParentDirectory?: string,
192
- * io?: object,
193
- * }} input
194
- * @returns {object}
195
- */
196
184
  export function capturePriorInstallSnapshot(input) {
197
185
  const io = input.io || {};
198
186
  const exists = io.existsSync || existsSync;
@@ -208,10 +196,23 @@ export function capturePriorInstallSnapshot(input) {
208
196
  const filesDirectory = join(journalRoot, FILES_DIRECTORY_NAME);
209
197
  mkdir(filesDirectory, { recursive: true });
210
198
 
211
- const settingsExisted = exists(input.settingsPath);
212
- if (settingsExisted) {
213
- copyFile(input.settingsPath, join(journalRoot, SETTINGS_BLOB_NAME));
214
- }
199
+ const allSettingsPaths = [
200
+ input.settingsPath,
201
+ ...(Array.isArray(input.additionalSettingsPaths) ? input.additionalSettingsPaths : []),
202
+ ].filter((eachPath, index, allPaths) => (
203
+ typeof eachPath === 'string'
204
+ && eachPath !== ''
205
+ && allPaths.indexOf(eachPath) === index
206
+ ));
207
+ const settingsSnapshots = allSettingsPaths.map((settingsPath, index) => {
208
+ const settingsExisted = exists(settingsPath);
209
+ const blobName = index === 0
210
+ ? SETTINGS_BLOB_NAME
211
+ : `${ADDITIONAL_SETTINGS_BLOB_PREFIX}${index}.json`;
212
+ if (settingsExisted) copyFile(settingsPath, join(journalRoot, blobName));
213
+ return { path: settingsPath, existed: settingsExisted, blobName };
214
+ });
215
+ const primarySettingsSnapshot = settingsSnapshots[0];
215
216
 
216
217
  const manifestExisted = exists(input.manifestFilePath);
217
218
  if (manifestExisted) {
@@ -255,7 +256,8 @@ export function capturePriorInstallSnapshot(input) {
255
256
  managedRoot: input.managedRoot,
256
257
  settingsPath: input.settingsPath,
257
258
  manifestFilePath: input.manifestFilePath,
258
- settingsExisted,
259
+ settingsSnapshots,
260
+ settingsExisted: primarySettingsSnapshot?.existed ?? false,
259
261
  manifestExisted,
260
262
  priorHooksPath,
261
263
  allFileEntries,
@@ -267,23 +269,14 @@ export function capturePriorInstallSnapshot(input) {
267
269
  managedRoot: input.managedRoot,
268
270
  settingsPath: input.settingsPath,
269
271
  manifestFilePath: input.manifestFilePath,
270
- settingsExisted,
272
+ settingsSnapshots: Object.freeze(settingsSnapshots.map(eachSnapshot => Object.freeze(eachSnapshot))),
273
+ settingsExisted: primarySettingsSnapshot?.existed ?? false,
271
274
  manifestExisted,
272
275
  priorHooksPath,
273
276
  allFileEntries: Object.freeze(allFileEntries.map((eachEntry) => Object.freeze({ ...eachEntry }))),
274
277
  });
275
278
  }
276
279
 
277
- /**
278
- * Restore the captured prior installation and drop extra write-set paths.
279
- *
280
- * @param {ReturnType<typeof capturePriorInstallSnapshot>} snapshot
281
- * @param {{
282
- * allWrittenPaths?: string[],
283
- * io?: object,
284
- * }} [options]
285
- * @returns {void}
286
- */
287
280
  export function restorePriorInstallSnapshot(snapshot, options = {}) {
288
281
  const io = options.io || {};
289
282
  const exists = io.existsSync || existsSync;
@@ -294,6 +287,13 @@ export function restorePriorInstallSnapshot(snapshot, options = {}) {
294
287
  const lstat = io.lstatSync || lstatSync;
295
288
 
296
289
  const filesDirectory = join(snapshot.journalRoot, FILES_DIRECTORY_NAME);
290
+ const settingsSnapshots = snapshot.settingsSnapshots?.length > 0
291
+ ? snapshot.settingsSnapshots
292
+ : [{
293
+ path: snapshot.settingsPath,
294
+ existed: snapshot.settingsExisted,
295
+ blobName: SETTINGS_BLOB_NAME,
296
+ }];
297
297
  for (const eachEntry of snapshot.allFileEntries) {
298
298
  try {
299
299
  if (exists(eachEntry.absolutePath)) {
@@ -302,9 +302,7 @@ export function restorePriorInstallSnapshot(snapshot, options = {}) {
302
302
  unlink(eachEntry.absolutePath);
303
303
  }
304
304
  }
305
- } catch {
306
- // keep restoring remaining entries
307
- }
305
+ } catch {}
308
306
  if (eachEntry.kind === ENTRY_KIND_FILE && eachEntry.blobName) {
309
307
  mkdir(dirname(eachEntry.absolutePath), { recursive: true });
310
308
  copyFile(join(filesDirectory, eachEntry.blobName), eachEntry.absolutePath);
@@ -324,7 +322,8 @@ export function restorePriorInstallSnapshot(snapshot, options = {}) {
324
322
  if (priorPathSet.has(eachWrittenPath)) {
325
323
  continue;
326
324
  }
327
- if (eachWrittenPath === snapshot.settingsPath || eachWrittenPath === snapshot.manifestFilePath) {
325
+ if (settingsSnapshots.some(eachSnapshot => eachSnapshot.path === eachWrittenPath)
326
+ || eachWrittenPath === snapshot.manifestFilePath) {
328
327
  continue;
329
328
  }
330
329
  try {
@@ -339,14 +338,14 @@ export function restorePriorInstallSnapshot(snapshot, options = {}) {
339
338
  }
340
339
  }
341
340
 
342
- if (snapshot.settingsExisted) {
343
- mkdir(dirname(snapshot.settingsPath), { recursive: true });
344
- copyFile(join(snapshot.journalRoot, SETTINGS_BLOB_NAME), snapshot.settingsPath);
345
- } else if (exists(snapshot.settingsPath)) {
346
- try {
347
- unlink(snapshot.settingsPath);
348
- } catch {
349
- // leave in place when unlink fails
341
+ for (const eachSnapshot of settingsSnapshots) {
342
+ if (eachSnapshot.existed) {
343
+ mkdir(dirname(eachSnapshot.path), { recursive: true });
344
+ copyFile(join(snapshot.journalRoot, eachSnapshot.blobName), eachSnapshot.path);
345
+ } else if (exists(eachSnapshot.path)) {
346
+ try {
347
+ unlink(eachSnapshot.path);
348
+ } catch {}
350
349
  }
351
350
  }
352
351
 
@@ -356,9 +355,7 @@ export function restorePriorInstallSnapshot(snapshot, options = {}) {
356
355
  } else if (exists(snapshot.manifestFilePath)) {
357
356
  try {
358
357
  unlink(snapshot.manifestFilePath);
359
- } catch {
360
- // leave in place when unlink fails
361
- }
358
+ } catch {}
362
359
  }
363
360
 
364
361
  writeGlobalCoreHooksPath(snapshot.priorHooksPath, io);