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.
- package/.agents/skills/session-continuity/README.md +19 -9
- package/AGENTS.md +48 -71
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/advisor/reference/advisor-block.md +1 -1
- package/_shared/advisor/reference/astra-rung.md +2 -2
- package/_shared/advisor/scripts/codex_astra_advisor.py +122 -23
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +76 -9
- package/_shared/advisor/scripts/tests/test_advisor_route_constants.py +51 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +52 -2
- package/bin/install-constants.mjs +3 -0
- package/bin/install-session-continuity.mjs +73 -30
- package/bin/install-session-continuity.test.mjs +52 -1
- package/bin/install-transaction.mjs +41 -44
- package/bin/install.cursor-rules.test.mjs +311 -3
- package/bin/install.mjs +226 -95
- package/bin/install.test.mjs +57 -0
- package/bin/install.transaction.test.mjs +61 -1
- package/bin/pstack-model-preferences.codex.json +1 -0
- package/bin/pstack.mjs +28 -6
- package/bin/pstack.test.mjs +48 -0
- package/bin/select-pstack-models.test.mjs +375 -26
- package/bin/subagent-model-policy.test.mjs +399 -0
- package/docs/codex-compatibility.md +19 -5
- package/docs/subagent-model-routing.md +50 -0
- package/hooks/hooks.json +10 -0
- package/hooks/routing/subagent_model_routing.mjs +81 -0
- package/hooks/routing/subagent_model_routing.test.mjs +218 -0
- package/package.json +1 -1
- package/rules/pstack-models.md +22 -0
- package/rules/subagent-model-policy.json +85 -0
- package/scripts/resolve_advisor_model_route.mjs +52 -0
- package/scripts/select_pstack_models.mjs +203 -38
- package/scripts/subagent_model_policy.mjs +620 -0
- package/scripts/tests/resolve_advisor_model_route.test.mjs +51 -0
- package/scripts/tests/test_codex_compat_materializer.py +15 -0
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
writeFileSync,
|
|
7
7
|
readFileSync,
|
|
8
8
|
existsSync,
|
|
9
|
+
copyFileSync,
|
|
9
10
|
rmSync,
|
|
10
11
|
} from 'node:fs';
|
|
11
12
|
import { tmpdir } from 'node:os';
|
|
@@ -150,6 +151,11 @@ for (const targetName of [null, 'profile']) {
|
|
|
150
151
|
'rules',
|
|
151
152
|
'pstack-model-preferences.codex.json',
|
|
152
153
|
);
|
|
154
|
+
const policyPath = join(
|
|
155
|
+
resolution.agentsHome,
|
|
156
|
+
'rules',
|
|
157
|
+
'subagent-model-policy.json',
|
|
158
|
+
);
|
|
153
159
|
const selectorPath = join(
|
|
154
160
|
resolution.agentsHome,
|
|
155
161
|
'scripts',
|
|
@@ -172,10 +178,12 @@ for (const targetName of [null, 'profile']) {
|
|
|
172
178
|
assert.equal(readFileSync(cursorPath, 'utf8'), installedText);
|
|
173
179
|
assert.equal(isAllowedInstallDestination(sharedPath, resolution), true);
|
|
174
180
|
assert.equal(existsSync(selectorPath), true);
|
|
181
|
+
assert.equal(existsSync(policyPath), true);
|
|
175
182
|
|
|
176
183
|
const savedPreferences = JSON.parse(readFileSync(preferencesPath, 'utf8'));
|
|
177
184
|
assert.deepEqual(savedPreferences, {
|
|
178
185
|
host: 'codex',
|
|
186
|
+
defaultEffort: 'medium',
|
|
179
187
|
modelsByRole: {
|
|
180
188
|
'feature, refactoring': ['gpt-5.6-sol'],
|
|
181
189
|
'bug-fix': ['gpt-5.6-sol'],
|
|
@@ -216,9 +224,9 @@ for (const targetName of [null, 'profile']) {
|
|
|
216
224
|
hasMaterialCapabilityLoss: false,
|
|
217
225
|
},
|
|
218
226
|
});
|
|
219
|
-
assert.
|
|
220
|
-
|
|
221
|
-
});
|
|
227
|
+
assert.equal(codexSelection.canDelegate, false);
|
|
228
|
+
assert.equal(codexSelection.failure, 'no-supported-model');
|
|
229
|
+
assert.deepEqual(codexSelection.nativeSpawnArguments, {});
|
|
222
230
|
|
|
223
231
|
const claudePreferencesPath = join(
|
|
224
232
|
resolution.agentsHome,
|
|
@@ -344,6 +352,306 @@ for (const targetName of [null, 'profile']) {
|
|
|
344
352
|
});
|
|
345
353
|
}
|
|
346
354
|
|
|
355
|
+
test('seeds one editable policy and one native Codex routing hook', () => {
|
|
356
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-install-'));
|
|
357
|
+
try {
|
|
358
|
+
const resolution = resolveInstallRoot({
|
|
359
|
+
homeDirectory,
|
|
360
|
+
environment: {},
|
|
361
|
+
explicitTarget: null,
|
|
362
|
+
});
|
|
363
|
+
const policyPath = join(resolution.agentsHome, 'rules', 'subagent-model-policy.json');
|
|
364
|
+
const installedRulesPolicyPath = join(
|
|
365
|
+
resolution.managedRoot,
|
|
366
|
+
'rules',
|
|
367
|
+
'subagent-model-policy.json',
|
|
368
|
+
);
|
|
369
|
+
const codexHooksPath = join(homeDirectory, '.codex', 'hooks.json');
|
|
370
|
+
|
|
371
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
372
|
+
|
|
373
|
+
assert.equal(existsSync(policyPath), true);
|
|
374
|
+
assert.equal(existsSync(installedRulesPolicyPath), false);
|
|
375
|
+
const firstCodexHooks = JSON.parse(readFileSync(codexHooksPath, 'utf8'));
|
|
376
|
+
const firstRoutingGroups = firstCodexHooks.hooks.PreToolUse.filter(
|
|
377
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
378
|
+
);
|
|
379
|
+
assert.equal(firstRoutingGroups.length, 1);
|
|
380
|
+
assert.equal(firstRoutingGroups[0].hooks.length, 1);
|
|
381
|
+
assert.match(firstRoutingGroups[0].hooks[0].command, /subagent_model_routing\.mjs/);
|
|
382
|
+
assert.equal(
|
|
383
|
+
firstCodexHooks.hooks.PreToolUse.some(group => group.matcher === 'Write|Edit|MultiEdit|apply_patch'),
|
|
384
|
+
false,
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const editedPolicy = '{"schemaVersion":1,"edited":true}\n';
|
|
388
|
+
writeFileSync(policyPath, editedPolicy);
|
|
389
|
+
firstCodexHooks.hooks.PreToolUse.push({
|
|
390
|
+
matcher: 'custom',
|
|
391
|
+
hooks: [{ type: 'command', command: 'python user-hook.py' }],
|
|
392
|
+
});
|
|
393
|
+
writeFileSync(codexHooksPath, JSON.stringify(firstCodexHooks, null, 2) + '\n');
|
|
394
|
+
|
|
395
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
396
|
+
|
|
397
|
+
assert.equal(readFileSync(policyPath, 'utf8'), editedPolicy);
|
|
398
|
+
const secondCodexHooks = JSON.parse(readFileSync(codexHooksPath, 'utf8'));
|
|
399
|
+
assert.equal(
|
|
400
|
+
secondCodexHooks.hooks.PreToolUse.filter(
|
|
401
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
402
|
+
).length,
|
|
403
|
+
1,
|
|
404
|
+
);
|
|
405
|
+
assert.equal(
|
|
406
|
+
secondCodexHooks.hooks.PreToolUse.some(
|
|
407
|
+
group => group.matcher === 'custom'
|
|
408
|
+
&& group.hooks.some(hook => hook.command === 'python user-hook.py'),
|
|
409
|
+
),
|
|
410
|
+
true,
|
|
411
|
+
);
|
|
412
|
+
} finally {
|
|
413
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('reinstall moves the old routing hook and keeps user hooks', () => {
|
|
418
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-hook-upgrade-'));
|
|
419
|
+
try {
|
|
420
|
+
const resolution = resolveInstallRoot({
|
|
421
|
+
homeDirectory,
|
|
422
|
+
environment: {},
|
|
423
|
+
explicitTarget: null,
|
|
424
|
+
});
|
|
425
|
+
const codexHooksPath = join(homeDirectory, '.codex', 'hooks.json');
|
|
426
|
+
const manifestPath = resolution.manifestFilePath;
|
|
427
|
+
const routingHookPath = join(
|
|
428
|
+
homeDirectory,
|
|
429
|
+
'.codex',
|
|
430
|
+
'hooks',
|
|
431
|
+
'routing',
|
|
432
|
+
'subagent_model_routing.mjs',
|
|
433
|
+
);
|
|
434
|
+
const oldRoutingHookPath = join(
|
|
435
|
+
homeDirectory,
|
|
436
|
+
'.codex',
|
|
437
|
+
'hooks',
|
|
438
|
+
'blocking',
|
|
439
|
+
'subagent_model_routing.mjs',
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
runInstaller(homeDirectory, []);
|
|
443
|
+
const firstCodexHooks = JSON.parse(readFileSync(codexHooksPath, 'utf8'));
|
|
444
|
+
const firstRoutingGroup = firstCodexHooks.hooks.PreToolUse.find(
|
|
445
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
446
|
+
);
|
|
447
|
+
firstRoutingGroup.hooks[0].command = firstRoutingGroup.hooks[0].command.replace(
|
|
448
|
+
/routing([\\/])subagent_model_routing\.mjs$/,
|
|
449
|
+
'blocking$1subagent_model_routing.mjs',
|
|
450
|
+
);
|
|
451
|
+
firstCodexHooks.hooks.PreToolUse.push({
|
|
452
|
+
matcher: 'custom',
|
|
453
|
+
hooks: [{ type: 'command', command: 'node user-hook.mjs' }],
|
|
454
|
+
});
|
|
455
|
+
writeFileSync(codexHooksPath, JSON.stringify(firstCodexHooks, null, 2) + '\n');
|
|
456
|
+
copyFileSync(routingHookPath, oldRoutingHookPath);
|
|
457
|
+
rmSync(routingHookPath);
|
|
458
|
+
|
|
459
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
460
|
+
manifest.files = manifest.files.map(file => file.replace(
|
|
461
|
+
/([\\/])routing([\\/]subagent_model_routing\.mjs)$/,
|
|
462
|
+
'$1blocking$2',
|
|
463
|
+
));
|
|
464
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
465
|
+
|
|
466
|
+
runInstaller(homeDirectory, []);
|
|
467
|
+
|
|
468
|
+
const secondCodexHooks = JSON.parse(readFileSync(codexHooksPath, 'utf8'));
|
|
469
|
+
const routingGroups = secondCodexHooks.hooks.PreToolUse.filter(
|
|
470
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
471
|
+
);
|
|
472
|
+
assert.equal(routingGroups.length, 1);
|
|
473
|
+
assert.match(routingGroups[0].hooks[0].command, /[\\/]hooks[\\/]routing[\\/]subagent_model_routing\.mjs/);
|
|
474
|
+
assert.doesNotMatch(
|
|
475
|
+
JSON.stringify(secondCodexHooks),
|
|
476
|
+
/[\\/]hooks[\\/]blocking[\\/]subagent_model_routing\.mjs/,
|
|
477
|
+
);
|
|
478
|
+
assert.equal(existsSync(routingHookPath), true);
|
|
479
|
+
assert.equal(existsSync(oldRoutingHookPath), false);
|
|
480
|
+
assert.equal(
|
|
481
|
+
secondCodexHooks.hooks.PreToolUse.some(
|
|
482
|
+
group => group.matcher === 'custom'
|
|
483
|
+
&& group.hooks.some(hook => hook.command === 'node user-hook.mjs'),
|
|
484
|
+
),
|
|
485
|
+
true,
|
|
486
|
+
);
|
|
487
|
+
} finally {
|
|
488
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test('uninstall removes the routing hook and keeps a Codex user hook', () => {
|
|
493
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-uninstall-'));
|
|
494
|
+
try {
|
|
495
|
+
const profileRoot = join(homeDirectory, 'named-profile');
|
|
496
|
+
const codexHooksPath = join(profileRoot, '.codex', 'hooks.json');
|
|
497
|
+
const targetArguments = ['--target', profileRoot];
|
|
498
|
+
runInstaller(homeDirectory, [...targetArguments, '--only', 'core']);
|
|
499
|
+
const codexHooks = JSON.parse(readFileSync(codexHooksPath, 'utf8'));
|
|
500
|
+
codexHooks.hooks.PreToolUse.push({
|
|
501
|
+
matcher: 'custom',
|
|
502
|
+
hooks: [{ type: 'command', command: 'python user-hook.py' }],
|
|
503
|
+
});
|
|
504
|
+
writeFileSync(codexHooksPath, JSON.stringify(codexHooks, null, 2) + '\n');
|
|
505
|
+
|
|
506
|
+
runInstaller(homeDirectory, [...targetArguments, '--uninstall']);
|
|
507
|
+
|
|
508
|
+
const remainingHooks = JSON.parse(readFileSync(codexHooksPath, 'utf8'));
|
|
509
|
+
assert.equal(
|
|
510
|
+
remainingHooks.hooks.PreToolUse.some(group => group.matcher === 'multi_agent_v1__spawn_agent'),
|
|
511
|
+
false,
|
|
512
|
+
);
|
|
513
|
+
assert.equal(
|
|
514
|
+
remainingHooks.hooks.PreToolUse.some(
|
|
515
|
+
group => group.matcher === 'custom'
|
|
516
|
+
&& group.hooks.some(hook => hook.command === 'python user-hook.py'),
|
|
517
|
+
),
|
|
518
|
+
true,
|
|
519
|
+
);
|
|
520
|
+
} finally {
|
|
521
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test('malformed Codex hooks restore the staged install', () => {
|
|
526
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-malformed-'));
|
|
527
|
+
try {
|
|
528
|
+
const codexHooksPath = join(homeDirectory, '.codex', 'hooks.json');
|
|
529
|
+
const claudeSettingsPath = join(homeDirectory, '.claude', 'settings.json');
|
|
530
|
+
const originalCodexHooks = '{ malformed\n';
|
|
531
|
+
const originalClaudeSettings = '{"hooks":{"PreToolUse":[{"matcher":"user","hooks":[{"type":"command","command":"user-hook"}]}]}}\n';
|
|
532
|
+
mkdirSync(dirname(codexHooksPath), { recursive: true });
|
|
533
|
+
mkdirSync(dirname(claudeSettingsPath), { recursive: true });
|
|
534
|
+
writeFileSync(codexHooksPath, originalCodexHooks);
|
|
535
|
+
writeFileSync(claudeSettingsPath, originalClaudeSettings);
|
|
536
|
+
|
|
537
|
+
assert.throws(
|
|
538
|
+
() => runInstaller(homeDirectory, ['--only', 'core']),
|
|
539
|
+
error => error.status === 1,
|
|
540
|
+
);
|
|
541
|
+
assert.equal(readFileSync(codexHooksPath, 'utf8'), originalCodexHooks);
|
|
542
|
+
assert.equal(readFileSync(claudeSettingsPath, 'utf8'), originalClaudeSettings);
|
|
543
|
+
assert.equal(existsSync(join(homeDirectory, '.claude', 'hooks')), false);
|
|
544
|
+
} finally {
|
|
545
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('malformed Codex hooks restore an uninstall', () => {
|
|
550
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-uninstall-malformed-'));
|
|
551
|
+
try {
|
|
552
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
553
|
+
const codexHooksPath = join(homeDirectory, '.codex', 'hooks.json');
|
|
554
|
+
const manifestPath = join(homeDirectory, '.claude', '.claude-dev-env-manifest.json');
|
|
555
|
+
const managedHookPath = join(
|
|
556
|
+
homeDirectory,
|
|
557
|
+
'.claude',
|
|
558
|
+
'hooks',
|
|
559
|
+
'routing',
|
|
560
|
+
'subagent_model_routing.mjs',
|
|
561
|
+
);
|
|
562
|
+
writeFileSync(codexHooksPath, '{ malformed\n');
|
|
563
|
+
|
|
564
|
+
assert.throws(
|
|
565
|
+
() => runInstaller(homeDirectory, ['--uninstall']),
|
|
566
|
+
error => error.status === 1,
|
|
567
|
+
);
|
|
568
|
+
assert.equal(existsSync(manifestPath), true);
|
|
569
|
+
assert.equal(existsSync(managedHookPath), true);
|
|
570
|
+
assert.equal(readFileSync(codexHooksPath, 'utf8'), '{ malformed\n');
|
|
571
|
+
} finally {
|
|
572
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test('two profiles keep separate Codex routing hooks', () => {
|
|
577
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-profiles-'));
|
|
578
|
+
try {
|
|
579
|
+
const profileRoot = join(homeDirectory, 'named-profile');
|
|
580
|
+
const mainCodexHooksPath = join(homeDirectory, '.codex', 'hooks.json');
|
|
581
|
+
const profileCodexHooksPath = join(profileRoot, '.codex', 'hooks.json');
|
|
582
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
583
|
+
runInstaller(homeDirectory, ['--target', profileRoot, '--only', 'core']);
|
|
584
|
+
|
|
585
|
+
const mainCodexHooks = JSON.parse(readFileSync(mainCodexHooksPath, 'utf8'));
|
|
586
|
+
const profileCodexHooks = JSON.parse(readFileSync(profileCodexHooksPath, 'utf8'));
|
|
587
|
+
const mainRoutingGroups = (mainCodexHooks.hooks?.PreToolUse ?? []).filter(
|
|
588
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
589
|
+
);
|
|
590
|
+
const profileRoutingGroups = profileCodexHooks.hooks.PreToolUse.filter(
|
|
591
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
592
|
+
);
|
|
593
|
+
assert.equal(mainRoutingGroups.length, 1);
|
|
594
|
+
assert.equal(profileRoutingGroups.length, 1);
|
|
595
|
+
assert.match(mainRoutingGroups[0].hooks[0].command, /[\\/]\.codex[\\/]hooks[\\/]routing[\\/]subagent_model_routing\.mjs/);
|
|
596
|
+
assert.match(profileRoutingGroups[0].hooks[0].command, /named-profile[\\/]\.codex[\\/]hooks[\\/]routing[\\/]subagent_model_routing\.mjs/);
|
|
597
|
+
assert.notEqual(mainRoutingGroups[0].hooks[0].command, profileRoutingGroups[0].hooks[0].command);
|
|
598
|
+
|
|
599
|
+
const profileHookPath = join(
|
|
600
|
+
profileRoot,
|
|
601
|
+
'.codex',
|
|
602
|
+
'hooks',
|
|
603
|
+
'routing',
|
|
604
|
+
'subagent_model_routing.mjs',
|
|
605
|
+
);
|
|
606
|
+
const profileHookResponse = JSON.parse(execFileSync(
|
|
607
|
+
'node',
|
|
608
|
+
[profileHookPath],
|
|
609
|
+
{
|
|
610
|
+
encoding: 'utf8',
|
|
611
|
+
input: JSON.stringify({
|
|
612
|
+
tool_name: 'multi_agent_v1__spawn_agent',
|
|
613
|
+
tool_input: { model: 'Terra', reasoning_effort: 'medium' },
|
|
614
|
+
}),
|
|
615
|
+
},
|
|
616
|
+
));
|
|
617
|
+
assert.equal(
|
|
618
|
+
profileHookResponse.hookSpecificOutput.updatedInput.model,
|
|
619
|
+
'gpt-5.6-luna',
|
|
620
|
+
);
|
|
621
|
+
assert.equal(
|
|
622
|
+
profileHookResponse.hookSpecificOutput.updatedInput.reasoning_effort,
|
|
623
|
+
'xhigh',
|
|
624
|
+
);
|
|
625
|
+
} finally {
|
|
626
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test('uninstalling one profile keeps routing for another installed profile', () => {
|
|
631
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-profile-uninstall-'));
|
|
632
|
+
try {
|
|
633
|
+
const profileRoot = join(homeDirectory, 'named-profile');
|
|
634
|
+
const mainCodexHooksPath = join(homeDirectory, '.codex', 'hooks.json');
|
|
635
|
+
const profileCodexHooksPath = join(profileRoot, '.codex', 'hooks.json');
|
|
636
|
+
runInstaller(homeDirectory, ['--only', 'core']);
|
|
637
|
+
runInstaller(homeDirectory, ['--target', profileRoot, '--only', 'core']);
|
|
638
|
+
runInstaller(homeDirectory, ['--uninstall']);
|
|
639
|
+
|
|
640
|
+
const mainCodexHooks = JSON.parse(readFileSync(mainCodexHooksPath, 'utf8'));
|
|
641
|
+
const profileCodexHooks = JSON.parse(readFileSync(profileCodexHooksPath, 'utf8'));
|
|
642
|
+
const mainRoutingGroups = (mainCodexHooks.hooks?.PreToolUse ?? []).filter(
|
|
643
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
644
|
+
);
|
|
645
|
+
const profileRoutingGroups = profileCodexHooks.hooks.PreToolUse.filter(
|
|
646
|
+
group => group.matcher === 'multi_agent_v1__spawn_agent',
|
|
647
|
+
);
|
|
648
|
+
assert.equal(mainRoutingGroups.length, 0);
|
|
649
|
+
assert.equal(profileRoutingGroups.length, 1);
|
|
650
|
+
} finally {
|
|
651
|
+
rmSync(homeDirectory, { recursive: true, force: true });
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
347
655
|
test('a blocked shared rule destination rolls back generated Cursor files', () => {
|
|
348
656
|
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-rollback-'));
|
|
349
657
|
try {
|