claude-dev-env 8.101.0 → 8.102.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.
@@ -35,6 +35,7 @@ function installInScratchHome(homeDirectory, argumentsList = [], environment = {
35
35
  CDE_INSTALL_DEEPSTACK: '0',
36
36
  CDE_INSTALL_USAGE_WRAPUP: '0',
37
37
  CDE_INSTALL_SUBAGENT_MODELS: '0',
38
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
38
39
  CLAUDE_DEV_ENV_INSTALL_FAULT: undefined,
39
40
  HOME: homeDirectory,
40
41
  USERPROFILE: homeDirectory,
@@ -59,6 +59,27 @@ export const SUBAGENT_MODELS_PLUGIN_SPEC = Object.freeze({
59
59
  optOutVariable: 'CDE_INSTALL_SUBAGENT_MODELS',
60
60
  });
61
61
 
62
+ /**
63
+ * Anthropic's security-guidance plugin, which reviews each edit, turn, and
64
+ * commit an agent makes for security problems.
65
+ *
66
+ * Its marketplace holds every official Anthropic plugin and may supply other
67
+ * plugins the user installed, so the install never removes that marketplace
68
+ * to rebuild it.
69
+ */
70
+ export const SECURITY_GUIDANCE_PLUGIN_SPEC = Object.freeze({
71
+ name: 'security-guidance',
72
+ label: 'Security-guidance',
73
+ marketplaceRepository: 'anthropics/claude-plugins-official',
74
+ marketplaceName: 'claude-plugins-official',
75
+ sparsePaths: Object.freeze([]),
76
+ pluginIdentifier: 'security-guidance@claude-plugins-official',
77
+ hosts: Object.freeze(['claude']),
78
+ optOutFlag: '--no-security-guidance',
79
+ optOutVariable: 'CDE_INSTALL_SECURITY_GUIDANCE',
80
+ sharesMarketplace: true,
81
+ });
82
+
62
83
  const HOST_DEFINITIONS = Object.freeze({
63
84
  claude: Object.freeze({
64
85
  executable: 'claude',
@@ -303,7 +324,8 @@ function lockedFolderDetail(allSpecs, host, homeDirectory, errorText, findLocker
303
324
  }).join('');
304
325
  }
305
326
 
306
- const REBUILD_REQUIRED_PATTERN = /doesn't match its extraKnownMarketplaces entry|Source path does not exist/;
327
+ const MARKETPLACE_SOURCE_MISMATCH_PATTERN = /doesn't match its extraKnownMarketplaces entry/;
328
+ const REBUILD_REQUIRED_PATTERN = new RegExp(`${MARKETPLACE_SOURCE_MISMATCH_PATTERN.source}|Source path does not exist`);
307
329
 
308
330
  function commandFailureDetail(outcome) {
309
331
  return singleLineDetail(outcome.stderr) || singleLineDetail(outcome.stdout)
@@ -356,7 +378,25 @@ function installForHost(allSpecs, host, homeDirectory, environment, runCommand,
356
378
  return { host, executable, status: 'installed', warning: null };
357
379
  }
358
380
  const refreshOutput = `${refreshOutcome.outcome.stderr ?? ''}\n${refreshOutcome.outcome.stdout ?? ''}`;
359
- if (!REBUILD_REQUIRED_PATTERN.test(refreshOutput)) {
381
+ const marketplaceIsShared = allSpecs.some(eachSpec => eachSpec.sharesMarketplace);
382
+ const sourceMismatchOnAdd = refreshOutcome.commandArguments === plan.refreshCommands[0]
383
+ && MARKETPLACE_SOURCE_MISMATCH_PATTERN.test(refreshOutput);
384
+ if (marketplaceIsShared && sourceMismatchOnAdd) {
385
+ const knownMarketplacePlan = { ...plan, refreshCommands: plan.refreshCommands.slice(1) };
386
+ const knownMarketplaceOutcome = refreshInPlace(
387
+ knownMarketplacePlan, executable, commandEnvironment, runCommand,
388
+ );
389
+ if (knownMarketplaceOutcome.state === 'refreshed') {
390
+ removeOrphanedVersionsForHost(allSpecs, host, homeDirectory);
391
+ return { host, executable, status: 'installed', warning: null };
392
+ }
393
+ if (knownMarketplaceOutcome.state === 'absent') return absentHostOutcome(allSpecs, host, executable);
394
+ return failedCommandOutcome(
395
+ allSpecs, host, homeDirectory, executable,
396
+ knownMarketplaceOutcome.commandArguments, knownMarketplaceOutcome.outcome, findLockers,
397
+ );
398
+ }
399
+ if (marketplaceIsShared || !REBUILD_REQUIRED_PATTERN.test(refreshOutput)) {
360
400
  return failedCommandOutcome(
361
401
  allSpecs, host, homeDirectory, executable,
362
402
  refreshOutcome.commandArguments, refreshOutcome.outcome, findLockers,
@@ -418,7 +458,12 @@ export function restartNotice(allSpecs, allInstalledHosts) {
418
458
  * back to the clean install Codex always runs: it removes every plugin in the
419
459
  * set and then the marketplace, so an older version never survives beside the
420
460
  * new one, and adds them again. A removal that finds nothing to remove exits
421
- * non-zero on a fresh home, and the install goes on.
461
+ * non-zero on a fresh home, and the install goes on. A spec that marks its
462
+ * marketplace as shared skips the rebuild, because removing that marketplace
463
+ * would uninstall plugins this run does not own. When the shared marketplace
464
+ * is already known under another source, the add fails on the mismatch and
465
+ * the host runs the rest of the refresh against the known entry; any other
466
+ * refresh failure reports the host failed.
422
467
  * Before each Claude marketplace add, the install deletes the half-made clone
423
468
  * folder an interrupted earlier add left, so the add starts in an empty slot.
424
469
  *
@@ -9,6 +9,7 @@ import {
9
9
  PSTACK_PLUGIN_IDENTIFIER,
10
10
  ALL_RETIRED_PLUGIN_SPECS,
11
11
  PSTACK_PLUGIN_SPEC,
12
+ SECURITY_GUIDANCE_PLUGIN_SPEC,
12
13
  SUBAGENT_MODELS_PLUGIN_SPEC,
13
14
  USAGE_WRAPUP_PLUGIN_SPEC,
14
15
  hostCommandInvocation,
@@ -584,4 +585,60 @@ test('a run that installs nothing prints no restart notice', () => {
584
585
  assert.equal(outcome.status, 'skipped');
585
586
  assert.deepEqual(allLoggedLines, []);
586
587
  assert.equal(outcome.restartNotice, null);
587
- });
588
+ });
589
+
590
+ test('should install security-guidance from the official marketplace on Claude only', () => {
591
+ const runner = recordingRunner();
592
+ const outcome = installMarketplacePlugins([SECURITY_GUIDANCE_PLUGIN_SPEC], ROOTS, runner);
593
+ assert.equal(outcome.status, 'installed');
594
+ assert.deepEqual(outcome.hosts.map(host => host.host), ['claude']);
595
+ assert.deepEqual(runner.calls.map(call => [call.executable, ...call.commandArguments]), [
596
+ ['claude', 'plugin', 'marketplace', 'add', 'anthropics/claude-plugins-official'],
597
+ ['claude', 'plugin', 'marketplace', 'update', 'claude-plugins-official'],
598
+ ['claude', 'plugin', 'install', 'security-guidance@claude-plugins-official'],
599
+ ['claude', 'plugin', 'update', 'security-guidance@claude-plugins-official'],
600
+ ]);
601
+ assert.equal(runner.calls[0].environment.CLAUDE_CONFIG_DIR, ROOTS.claudeRoot);
602
+ });
603
+
604
+ test('should install against an official marketplace already known under another source', () => {
605
+ const runner = recordingRunner((executable, commandArguments) => (
606
+ commandArguments[2] === 'add'
607
+ ? { status: 1, stderr: "its source doesn't match its extraKnownMarketplaces entry" }
608
+ : { status: 0, stderr: '' }));
609
+ const outcome = installMarketplacePlugins([SECURITY_GUIDANCE_PLUGIN_SPEC], ROOTS, runner);
610
+ assert.equal(outcome.status, 'installed');
611
+ assert.deepEqual(runner.calls.map(call => call.commandArguments), [
612
+ ['plugin', 'marketplace', 'add', 'anthropics/claude-plugins-official'],
613
+ ['plugin', 'marketplace', 'update', 'claude-plugins-official'],
614
+ ['plugin', 'install', 'security-guidance@claude-plugins-official'],
615
+ ['plugin', 'update', 'security-guidance@claude-plugins-official'],
616
+ ]);
617
+ });
618
+
619
+ test('should leave the shared official marketplace in place when its refresh needs a rebuild', () => {
620
+ const runner = recordingRunner((executable, commandArguments) => (
621
+ commandArguments[1] === 'install'
622
+ ? { status: 1, stderr: 'Source path does not exist: plugins/security-guidance' }
623
+ : { status: 0, stderr: '' }));
624
+ const outcome = installMarketplacePlugins([SECURITY_GUIDANCE_PLUGIN_SPEC], ROOTS, runner);
625
+ assert.equal(outcome.status, 'failed');
626
+ assert.match(outcome.warning, /Source path does not exist/);
627
+ assert.equal(
628
+ runner.calls.some(call => call.commandArguments.includes('remove') || call.commandArguments.includes('uninstall')),
629
+ false,
630
+ );
631
+ });
632
+
633
+ test('should turn off only security-guidance with its opt-out flag or variable', () => {
634
+ assert.equal(shouldInstallMarketplacePlugin(SECURITY_GUIDANCE_PLUGIN_SPEC, [], {}), true);
635
+ assert.equal(shouldInstallMarketplacePlugin(SECURITY_GUIDANCE_PLUGIN_SPEC, ['--no-security-guidance'], {}), false);
636
+ assert.equal(
637
+ shouldInstallMarketplacePlugin(SECURITY_GUIDANCE_PLUGIN_SPEC, [], { CDE_INSTALL_SECURITY_GUIDANCE: '0' }),
638
+ false,
639
+ );
640
+ assert.equal(
641
+ shouldInstallMarketplacePlugin(PSTACK_PLUGIN_SPEC, ['--no-security-guidance'], { CDE_INSTALL_SECURITY_GUIDANCE: '0' }),
642
+ true,
643
+ );
644
+ });
@@ -56,6 +56,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
56
56
  CDE_INSTALL_DEEPSTACK: '0',
57
57
  CDE_INSTALL_USAGE_WRAPUP: '0',
58
58
  CDE_INSTALL_SUBAGENT_MODELS: '0',
59
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
59
60
  HOME: homeDirectory,
60
61
  USERPROFILE: homeDirectory,
61
62
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -62,6 +62,7 @@ function runInstaller(homeDirectory, extraArguments) {
62
62
  CDE_INSTALL_DEEPSTACK: '0',
63
63
  CDE_INSTALL_USAGE_WRAPUP: '0',
64
64
  CDE_INSTALL_SUBAGENT_MODELS: '0',
65
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
65
66
  HOME: homeDirectory,
66
67
  USERPROFILE: homeDirectory,
67
68
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -95,6 +96,7 @@ function scanTextWithProductionPiiScanner(scannedText) {
95
96
  CDE_INSTALL_DEEPSTACK: '0',
96
97
  CDE_INSTALL_USAGE_WRAPUP: '0',
97
98
  CDE_INSTALL_SUBAGENT_MODELS: '0',
99
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
98
100
  PYTHONPATH: BLOCKING_HOOKS_DIRECTORY,
99
101
  CLAUDE_LOCAL_IDENTITY_PATH: join(
100
102
  temporaryDirectory,
@@ -36,6 +36,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
36
36
  CDE_INSTALL_DEEPSTACK: '0',
37
37
  CDE_INSTALL_USAGE_WRAPUP: '0',
38
38
  CDE_INSTALL_SUBAGENT_MODELS: '0',
39
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
39
40
  HOME: homeDirectory,
40
41
  USERPROFILE: homeDirectory,
41
42
  CODEX_HOME: join(homeDirectory, '.codex'),
package/bin/install.mjs CHANGED
@@ -58,6 +58,7 @@ import {
58
58
  import {
59
59
  ALL_RETIRED_PLUGIN_SPECS,
60
60
  PSTACK_PLUGIN_SPEC,
61
+ SECURITY_GUIDANCE_PLUGIN_SPEC,
61
62
  SUBAGENT_MODELS_PLUGIN_SPEC,
62
63
  USAGE_WRAPUP_PLUGIN_SPEC,
63
64
  installMarketplacePlugins,
@@ -3121,6 +3122,13 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
3121
3122
  summary.repositoryPlugins = repositoryPlugins;
3122
3123
  printPluginHostOutcomes(allRepositoryPluginSpecs, repositoryPlugins);
3123
3124
  }
3125
+ if (!selectedGroups && shouldInstallMarketplacePlugin(SECURITY_GUIDANCE_PLUGIN_SPEC)) {
3126
+ const securityGuidancePlugin = installMarketplacePlugins([SECURITY_GUIDANCE_PLUGIN_SPEC], {
3127
+ claudeRoot: CLAUDE_HOME,
3128
+ });
3129
+ summary.securityGuidancePlugin = securityGuidancePlugin;
3130
+ printPluginHostOutcomes([SECURITY_GUIDANCE_PLUGIN_SPEC], securityGuidancePlugin);
3131
+ }
3124
3132
  if (!selectedGroups) {
3125
3133
  const packageGuidancePath = writeCodexPackageGuidance(
3126
3134
  INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
@@ -3686,6 +3694,7 @@ Usage:
3686
3694
  npx ${PACKAGE_NAME} --no-deepstack Full install without the DeepStack plugin (also CDE_INSTALL_DEEPSTACK=0)
3687
3695
  npx ${PACKAGE_NAME} --no-usage-wrapup Full install without the usage-wrapup plugin (also CDE_INSTALL_USAGE_WRAPUP=0)
3688
3696
  npx ${PACKAGE_NAME} --no-subagent-models Full install without the subagent-models plugin (also CDE_INSTALL_SUBAGENT_MODELS=0)
3697
+ npx ${PACKAGE_NAME} --no-security-guidance Full install without Anthropic's security-guidance plugin (also CDE_INSTALL_SECURITY_GUIDANCE=0)
3689
3698
  npx ${PACKAGE_NAME} --uninstall Remove installed files from the selected root
3690
3699
  npx ${PACKAGE_NAME} --help Show this help
3691
3700
 
@@ -22,7 +22,7 @@ test('packed full and core installs ship the runnable PR visual skill to the sha
22
22
  const homeDirectory = join(sandbox, `home-${eachIndex}`);
23
23
  execFileSync(process.execPath, [join(packaged, 'bin/install.mjs'), ...eachArguments], {
24
24
  encoding: 'utf8',
25
- env: { ...process.env, CLAUDE_CONFIG_DIR: undefined, LLM_SETTINGS_PROFILES_ROOT: undefined, HOME: homeDirectory, USERPROFILE: homeDirectory, CODEX_HOME: join(homeDirectory, '.codex'), GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'), CDE_INSTALL_PSTACK: '0', CDE_INSTALL_DEEPSTACK: '0', CDE_INSTALL_USAGE_WRAPUP: '0', CDE_INSTALL_SUBAGENT_MODELS: '0', CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0' },
25
+ env: { ...process.env, CLAUDE_CONFIG_DIR: undefined, LLM_SETTINGS_PROFILES_ROOT: undefined, HOME: homeDirectory, USERPROFILE: homeDirectory, CODEX_HOME: join(homeDirectory, '.codex'), GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'), CDE_INSTALL_PSTACK: '0', CDE_INSTALL_DEEPSTACK: '0', CDE_INSTALL_USAGE_WRAPUP: '0', CDE_INSTALL_SUBAGENT_MODELS: '0', CDE_INSTALL_SECURITY_GUIDANCE: '0', CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0' },
26
26
  });
27
27
  const installed = join(homeDirectory, ...SKILL_PATH_SEGMENTS);
28
28
  for (const eachFile of REQUIRED_FILES) assert.deepEqual(readFileSync(join(installed, eachFile)), readFileSync(join(packaged, ...SKILL_PATH_SEGMENTS, eachFile)));
@@ -266,6 +266,7 @@ function resolveInstallerInvocation(homeDirectory) {
266
266
  CDE_INSTALL_DEEPSTACK: '0',
267
267
  CDE_INSTALL_USAGE_WRAPUP: '0',
268
268
  CDE_INSTALL_SUBAGENT_MODELS: '0',
269
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
269
270
  HOME: homeDirectory,
270
271
  USERPROFILE: homeDirectory,
271
272
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -28,6 +28,7 @@ function installInScratchHome(homeDirectory, argumentsList = [], extraEnvironmen
28
28
  CDE_INSTALL_DEEPSTACK: '0',
29
29
  CDE_INSTALL_USAGE_WRAPUP: '0',
30
30
  CDE_INSTALL_SUBAGENT_MODELS: '0',
31
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
31
32
  HOME: homeDirectory,
32
33
  USERPROFILE: homeDirectory,
33
34
  CODEX_HOME: join(homeDirectory, '.codex'),
@@ -185,6 +186,7 @@ test('a successful pstack install omits the retired skill block and gains the qu
185
186
  CDE_INSTALL_DEEPSTACK: '0',
186
187
  CDE_INSTALL_USAGE_WRAPUP: '0',
187
188
  CDE_INSTALL_SUBAGENT_MODELS: '0',
189
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
188
190
  CDE_CODEX_EXECUTABLE: commandPath,
189
191
  CDE_CLAUDE_EXECUTABLE: commandPath,
190
192
  });
@@ -214,6 +216,7 @@ test('enabling pstack after a pstack-off install preserves question guidance', (
214
216
  CDE_INSTALL_DEEPSTACK: '0',
215
217
  CDE_INSTALL_USAGE_WRAPUP: '0',
216
218
  CDE_INSTALL_SUBAGENT_MODELS: '0',
219
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
217
220
  CDE_CODEX_EXECUTABLE: commandPath,
218
221
  CDE_CLAUDE_EXECUTABLE: commandPath,
219
222
  });
@@ -244,6 +247,7 @@ test('a failed pstack transition restores question-only guidance and removes its
244
247
  CDE_INSTALL_DEEPSTACK: '0',
245
248
  CDE_INSTALL_USAGE_WRAPUP: '0',
246
249
  CDE_INSTALL_SUBAGENT_MODELS: '0',
250
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
247
251
  CDE_CODEX_EXECUTABLE: commandPath,
248
252
  CDE_CLAUDE_EXECUTABLE: commandPath,
249
253
  CLAUDE_DEV_ENV_INSTALL_FAULT: 'after_file_staging',
@@ -74,6 +74,7 @@ function runInstallerInSandbox(sandboxHome, installerArguments = []) {
74
74
  CDE_INSTALL_DEEPSTACK: '0',
75
75
  CDE_INSTALL_USAGE_WRAPUP: '0',
76
76
  CDE_INSTALL_SUBAGENT_MODELS: '0',
77
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
77
78
  HOME: sandboxHome,
78
79
  USERPROFILE: sandboxHome,
79
80
  GIT_CONFIG_GLOBAL: gitConfigGlobal,
@@ -34,6 +34,7 @@ function installEnvironment(homeDirectory) {
34
34
  CDE_INSTALL_DEEPSTACK: '0',
35
35
  CDE_INSTALL_USAGE_WRAPUP: '0',
36
36
  CDE_INSTALL_SUBAGENT_MODELS: '0',
37
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
37
38
  };
38
39
  }
39
40
 
@@ -3050,6 +3050,7 @@ test('a failed update preserves an existing skills lookup pointer', () => {
3050
3050
  CDE_INSTALL_DEEPSTACK: '0',
3051
3051
  CDE_INSTALL_USAGE_WRAPUP: '0',
3052
3052
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3053
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
3053
3054
  };
3054
3055
  const argumentsForCoreInstall = [PSTACK_TEST_INSTALLER_PATH, '--only', 'core'];
3055
3056
  const firstRun = spawnSync(process.execPath, argumentsForCoreInstall, {
@@ -3104,6 +3105,7 @@ function runPstackInstaller(homeDirectory, extraArguments, environmentOverrides
3104
3105
  CDE_INSTALL_DEEPSTACK: '0',
3105
3106
  CDE_INSTALL_USAGE_WRAPUP: '0',
3106
3107
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3108
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
3107
3109
  ...environmentOverrides,
3108
3110
  },
3109
3111
  });
@@ -3529,6 +3531,54 @@ for (const [caseName, extraArguments, environmentOverrides] of [
3529
3531
  });
3530
3532
  }
3531
3533
 
3534
+ test('should install security-guidance from the official marketplace on a full install', t => {
3535
+ const sandbox = pstackPluginSandbox(t);
3536
+
3537
+ const installerOutput = runPstackInstaller(sandbox.homeDirectory, ['--no-pstack'], {
3538
+ ...sandbox.environment,
3539
+ CDE_INSTALL_SECURITY_GUIDANCE: '1',
3540
+ });
3541
+
3542
+ assert.deepEqual(sandbox.recordedCommands(), [
3543
+ 'claude plugin marketplace add anthropics/claude-plugins-official',
3544
+ 'claude plugin marketplace update claude-plugins-official',
3545
+ 'claude plugin install security-guidance@claude-plugins-official',
3546
+ 'claude plugin update security-guidance@claude-plugins-official',
3547
+ ]);
3548
+ assert.match(installerOutput, /Security-guidance \(claude\): installed/);
3549
+ });
3550
+
3551
+ for (const [caseName, extraArguments, environmentOverrides] of [
3552
+ ['--no-security-guidance', ['--no-security-guidance'], { CDE_INSTALL_SECURITY_GUIDANCE: '1' }],
3553
+ ['CDE_INSTALL_SECURITY_GUIDANCE=0', [], { CDE_INSTALL_SECURITY_GUIDANCE: '0' }],
3554
+ ['an --only run', ['--only', 'core'], { CDE_INSTALL_SECURITY_GUIDANCE: '1' }],
3555
+ ]) {
3556
+ test(`should skip security-guidance on ${caseName}`, t => {
3557
+ const sandbox = pstackPluginSandbox(t);
3558
+
3559
+ const installerOutput = runPstackInstaller(sandbox.homeDirectory, extraArguments, {
3560
+ ...sandbox.environment,
3561
+ ...environmentOverrides,
3562
+ });
3563
+
3564
+ assert.equal(
3565
+ sandbox.recordedCommands().some(eachCommand => eachCommand.includes('security-guidance')),
3566
+ false,
3567
+ );
3568
+ assert.doesNotMatch(installerOutput, /Security-guidance \(/);
3569
+ });
3570
+ }
3571
+
3572
+ test('should name the security-guidance opt-out in the help output', () => {
3573
+ const helpRun = spawnSync(process.execPath, [PSTACK_TEST_INSTALLER_PATH, '--help'], {
3574
+ encoding: 'utf8',
3575
+ env: process.env,
3576
+ });
3577
+
3578
+ assert.equal(helpRun.status, 0, helpRun.stderr);
3579
+ assert.match(helpRun.stdout, /--no-security-guidance .*CDE_INSTALL_SECURITY_GUIDANCE=0/);
3580
+ });
3581
+
3532
3582
  test('should name the subagent-models opt-out in the help output', () => {
3533
3583
  const helpRun = spawnSync(process.execPath, [PSTACK_TEST_INSTALLER_PATH, '--help'], {
3534
3584
  encoding: 'utf8',
@@ -3623,6 +3673,7 @@ function installWithGlobalPrefix(context, globalPrefix) {
3623
3673
  CDE_INSTALL_DEEPSTACK: '0',
3624
3674
  CDE_INSTALL_USAGE_WRAPUP: '0',
3625
3675
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3676
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
3626
3677
  HOME: homeDirectory,
3627
3678
  USERPROFILE: homeDirectory,
3628
3679
  APPDATA: join(homeDirectory, 'AppData', 'Roaming'),
@@ -3679,6 +3730,7 @@ test('install adds the open-tasks Stop gate to every profile with its own settin
3679
3730
  CDE_INSTALL_DEEPSTACK: '0',
3680
3731
  CDE_INSTALL_USAGE_WRAPUP: '0',
3681
3732
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3733
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
3682
3734
  HOME: homeDirectory,
3683
3735
  USERPROFILE: homeDirectory,
3684
3736
  CODEX_HOME: join(homeDirectory, '.codex'),
@@ -3721,6 +3773,7 @@ test('a profile settings.json that is not valid JSON is left alone and named und
3721
3773
  CDE_INSTALL_DEEPSTACK: '0',
3722
3774
  CDE_INSTALL_USAGE_WRAPUP: '0',
3723
3775
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3776
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
3724
3777
  HOME: homeDirectory,
3725
3778
  USERPROFILE: homeDirectory,
3726
3779
  CODEX_HOME: join(homeDirectory, '.codex'),
@@ -3766,6 +3819,7 @@ test('uninstall stops before changing anything while a profile settings.json tha
3766
3819
  CDE_INSTALL_DEEPSTACK: '0',
3767
3820
  CDE_INSTALL_USAGE_WRAPUP: '0',
3768
3821
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3822
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
3769
3823
  HOME: homeDirectory,
3770
3824
  USERPROFILE: homeDirectory,
3771
3825
  CODEX_HOME: join(homeDirectory, '.codex'),
@@ -91,6 +91,7 @@ test('capture and restore recover settings, manifest, files, and hooksPath', ()
91
91
  CDE_INSTALL_DEEPSTACK: '0',
92
92
  CDE_INSTALL_USAGE_WRAPUP: '0',
93
93
  CDE_INSTALL_SUBAGENT_MODELS: '0',
94
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
94
95
  HOME: box.root,
95
96
  USERPROFILE: box.root,
96
97
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -157,6 +158,7 @@ test('capture and restore recover an additional host settings file', () => {
157
158
  CDE_INSTALL_DEEPSTACK: '0',
158
159
  CDE_INSTALL_USAGE_WRAPUP: '0',
159
160
  CDE_INSTALL_SUBAGENT_MODELS: '0',
161
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
160
162
  HOME: box.root,
161
163
  USERPROFILE: box.root,
162
164
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -196,6 +198,7 @@ test('runWithInstallTransaction restores prior state on injected fault', () => {
196
198
  CDE_INSTALL_DEEPSTACK: '0',
197
199
  CDE_INSTALL_USAGE_WRAPUP: '0',
198
200
  CDE_INSTALL_SUBAGENT_MODELS: '0',
201
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
199
202
  HOME: box.root,
200
203
  USERPROFILE: box.root,
201
204
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -254,6 +257,7 @@ test('runWithInstallTransaction commits and discards journal on success', () =>
254
257
  CDE_INSTALL_DEEPSTACK: '0',
255
258
  CDE_INSTALL_USAGE_WRAPUP: '0',
256
259
  CDE_INSTALL_SUBAGENT_MODELS: '0',
260
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
257
261
  HOME: box.root,
258
262
  USERPROFILE: box.root,
259
263
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -301,6 +305,7 @@ function runInstaller(homeDirectory, extraArguments, options = {}) {
301
305
  CDE_INSTALL_DEEPSTACK: '0',
302
306
  CDE_INSTALL_USAGE_WRAPUP: '0',
303
307
  CDE_INSTALL_SUBAGENT_MODELS: '0',
308
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
304
309
  HOME: homeDirectory,
305
310
  USERPROFILE: homeDirectory,
306
311
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -338,6 +343,7 @@ test('a later install fault removes newly seeded Codex pstack files', () => {
338
343
  CDE_INSTALL_DEEPSTACK: '0',
339
344
  CDE_INSTALL_USAGE_WRAPUP: '0',
340
345
  CDE_INSTALL_SUBAGENT_MODELS: '0',
346
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
341
347
  CDE_CODEX_EXECUTABLE: codexCommandPath,
342
348
  },
343
349
  });
@@ -485,6 +491,7 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
485
491
  CDE_INSTALL_DEEPSTACK: '0',
486
492
  CDE_INSTALL_USAGE_WRAPUP: '0',
487
493
  CDE_INSTALL_SUBAGENT_MODELS: '0',
494
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
488
495
  HOME: homeDirectory,
489
496
  USERPROFILE: homeDirectory,
490
497
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -506,6 +513,7 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
506
513
  CDE_INSTALL_DEEPSTACK: '0',
507
514
  CDE_INSTALL_USAGE_WRAPUP: '0',
508
515
  CDE_INSTALL_SUBAGENT_MODELS: '0',
516
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
509
517
  HOME: homeDirectory,
510
518
  USERPROFILE: homeDirectory,
511
519
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -77,6 +77,7 @@ function runInstaller(homeDirectory, extraArguments, options = {}) {
77
77
  CDE_INSTALL_DEEPSTACK: '0',
78
78
  CDE_INSTALL_USAGE_WRAPUP: '0',
79
79
  CDE_INSTALL_SUBAGENT_MODELS: '0',
80
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
80
81
  HOME: homeDirectory,
81
82
  USERPROFILE: homeDirectory,
82
83
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -315,6 +316,7 @@ test('uninstall fault after_git_config restores prior core.hooksPath', () => {
315
316
  CDE_INSTALL_DEEPSTACK: '0',
316
317
  CDE_INSTALL_USAGE_WRAPUP: '0',
317
318
  CDE_INSTALL_SUBAGENT_MODELS: '0',
319
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
318
320
  HOME: homeDirectory,
319
321
  USERPROFILE: homeDirectory,
320
322
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -28,6 +28,7 @@ function installerEnvironment(homeDirectory) {
28
28
  CDE_INSTALL_DEEPSTACK: '0',
29
29
  CDE_INSTALL_USAGE_WRAPUP: '0',
30
30
  CDE_INSTALL_SUBAGENT_MODELS: '0',
31
+ CDE_INSTALL_SECURITY_GUIDANCE: '0',
31
32
  HOME: homeDirectory,
32
33
  USERPROFILE: homeDirectory,
33
34
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.101.0",
3
+ "version": "8.102.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {