claude-dev-env 8.88.7 → 8.90.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/pr-visual/README.md +29 -0
- package/.agents/skills/pr-visual/SKILL.md +62 -0
- package/.agents/skills/pr-visual/assets/icons.symbols +27 -0
- package/.agents/skills/pr-visual/assets/viewer.html +147 -0
- package/.agents/skills/pr-visual/fixtures/bar.scene.json +193 -0
- package/.agents/skills/pr-visual/fixtures/flow.scene.json +201 -0
- package/.agents/skills/pr-visual/fixtures/head/metrics.json +16 -0
- package/.agents/skills/pr-visual/fixtures/integrated.scene.json +239 -0
- package/.agents/skills/pr-visual/fixtures/line.scene.json +193 -0
- package/.agents/skills/pr-visual/fixtures/scatter.scene.json +193 -0
- package/.agents/skills/pr-visual/fixtures/snapshot.json +21 -0
- package/.agents/skills/pr-visual/references/evidence.md +9 -0
- package/.agents/skills/pr-visual/references/formats.md +30 -0
- package/.agents/skills/pr-visual/references/scene-contract.md +48 -0
- package/.agents/skills/pr-visual/references/visual-contract.md +23 -0
- package/.agents/skills/pr-visual/scripts/collect.py +111 -0
- package/.agents/skills/pr-visual/scripts/metrics.py +99 -0
- package/.agents/skills/pr-visual/scripts/render.py +133 -0
- package/.agents/skills/pr-visual/scripts/test_collect.py +58 -0
- package/.agents/skills/pr-visual/scripts/test_metrics.py +47 -0
- package/.agents/skills/pr-visual/scripts/test_render.py +77 -0
- package/.agents/skills/pr-visual/scripts/verify.py +218 -0
- package/.agents/skills/pr-visual/scripts/visual_config/config/constants.py +104 -0
- package/bin/codex-skill-load-block.mjs +0 -25
- package/bin/codex-skill-load-block.test.mjs +5 -17
- package/bin/codex-trimmed-sol.test.mjs +0 -1
- package/bin/cursor-native-hooks.test.mjs +3 -3
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-constants.mjs +3 -0
- package/bin/install-deepstack-plugin.mjs +42 -25
- package/bin/install-deepstack-plugin.test.mjs +37 -11
- package/bin/install-pstack-plugin.mjs +58 -11
- package/bin/install-pstack-plugin.test.mjs +54 -21
- package/bin/install.agents-home.test.mjs +0 -1
- package/bin/install.codex-rules.test.mjs +0 -2
- package/bin/install.cursor-rules.test.mjs +96 -21
- package/bin/install.mjs +48 -44
- package/bin/install.pr-visual.test.mjs +40 -0
- package/bin/install.prompt-context.test.mjs +3 -4
- package/bin/install.prune.test.mjs +0 -1
- package/bin/install.question-presentation.test.mjs +0 -4
- package/bin/install.settings-defaults.test.mjs +0 -1
- package/bin/install.shared-settings.test.mjs +0 -1
- package/bin/install.test.mjs +14 -44
- package/bin/install.transaction.test.mjs +0 -8
- package/bin/install.uninstall-transaction.test.mjs +0 -2
- package/bin/install.update-keeps-hooks.test.mjs +0 -1
- package/docs/BDD_SCENARIO_QUALITY.md +1 -1
- package/docs/BDD_TEST_LAYOUT.md +1 -1
- package/docs/CODE_RULES.md +1 -1
- package/docs/rule-guides/deepstack-wording.md +3 -5
- package/docs/rule-guides/session-title.md +5 -4
- package/hooks/advisory/owned_pr_review_loop.py +365 -0
- package/hooks/advisory/test_owned_pr_review_loop.py +369 -0
- package/hooks/blocking/session_title_turn_gate.py +287 -0
- package/hooks/blocking/test_session_title_format_gate.py +5 -0
- package/hooks/blocking/test_session_title_turn_gate.py +264 -0
- package/hooks/features/bash-dispatchers.md +3 -0
- package/hooks/features/conduct-gates.md +3 -0
- package/hooks/features/session-context.md +0 -4
- package/hooks/hooks.json +15 -5
- package/hooks/hooks_constants/mod_handoff_constants.py +0 -2
- package/hooks/hooks_constants/owned_pr_review_loop_constants.py +120 -0
- package/hooks/hooks_constants/session_title_constants.py +41 -3
- package/hooks/lifecycle/nested_project_hooks.py +2 -2
- package/hooks/lifecycle/test_nested_project_hooks.py +2 -2
- package/hooks/session/compaction_user_messages.py +2 -2
- package/package.json +2 -2
- package/rules/deepstack-wording.md +3 -3
- package/scripts/ci/install-playtest.mjs +58 -314
- package/scripts/ci/install-playtest.test.mjs +24 -6
- package/scripts/policy_lint/adapter_configuration.py +1 -0
- package/scripts/policy_lint/config/constants.py +1 -0
- package/scripts/test_deepstack_review_criterion.py +2 -3
- package/scripts/test_deepstack_wording_rule.py +6 -4
- package/scripts/tests/test_adapter_configuration.py +8 -0
- package/.agents/skills/deepen/SKILL.md +0 -26
- package/commands/deepen.md +0 -14
- package/docs/decisions/deepstack-gate.md +0 -32
- package/hooks/hooks_constants/deepen_first_prompt_constants.py +0 -187
- package/hooks/session/deepen_default_rewrite.py +0 -128
- package/hooks/session/deepen_first_prompt.py +0 -383
- package/hooks/session/deepen_role_gate.py +0 -171
- package/hooks/session/deepen_turn_marker.py +0 -75
- package/hooks/session/fixtures/deepstack_replay.json +0 -15
- package/hooks/session/grade_deepstack_replay.py +0 -78
- package/hooks/session/test_deepen_default_rewrite.py +0 -90
- package/hooks/session/test_deepen_first_prompt.py +0 -452
- package/hooks/session/test_deepen_role_gate.py +0 -83
- package/hooks/session/test_deepen_turn_marker.py +0 -59
- package/hooks/session/test_grade_deepstack_replay.py +0 -47
|
@@ -62,7 +62,6 @@ 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_DEEPEN_FIRST_PROMPT: '0',
|
|
66
65
|
HOME: homeDirectory,
|
|
67
66
|
USERPROFILE: homeDirectory,
|
|
68
67
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
@@ -96,7 +95,6 @@ function scanTextWithProductionPiiScanner(scannedText) {
|
|
|
96
95
|
CDE_INSTALL_DEEPSTACK: '0',
|
|
97
96
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
98
97
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
99
|
-
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
100
98
|
PYTHONPATH: BLOCKING_HOOKS_DIRECTORY,
|
|
101
99
|
CLAUDE_LOCAL_IDENTITY_PATH: join(
|
|
102
100
|
temporaryDirectory,
|
|
@@ -36,7 +36,6 @@ 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_DEEPEN_FIRST_PROMPT: '0',
|
|
40
39
|
HOME: homeDirectory,
|
|
41
40
|
USERPROFILE: homeDirectory,
|
|
42
41
|
CODEX_HOME: join(homeDirectory, '.codex'),
|
|
@@ -135,14 +134,20 @@ test('seeds one editable policy and one native Codex routing hook', () => {
|
|
|
135
134
|
);
|
|
136
135
|
assert.equal(firstSpawnPromptGroups.length, 1);
|
|
137
136
|
assert.match(firstSpawnPromptGroups[0].hooks[0].command, /skill_loaded_reminder\.py/);
|
|
138
|
-
assert.deepEqual(Object.keys(firstCodexHooks.hooks), ['PreToolUse', 'UserPromptSubmit', 'Stop']);
|
|
137
|
+
assert.deepEqual(Object.keys(firstCodexHooks.hooks), ['PreToolUse', 'UserPromptSubmit', 'PostToolUse', 'Stop']);
|
|
138
|
+
const allCodexPostToolUseCommands = firstCodexHooks.hooks.PostToolUse.flatMap(
|
|
139
|
+
group => group.hooks.map(hook => hook.command),
|
|
140
|
+
);
|
|
141
|
+
assert.equal(allCodexPostToolUseCommands.length, 1, 'Codex gets the owned PR review loop and no other PostToolUse hook');
|
|
142
|
+
assert.match(allCodexPostToolUseCommands[0], /advisory\/owned_pr_review_loop\.py/);
|
|
139
143
|
const allCodexStopCommands = firstCodexHooks.hooks.Stop.flatMap(group => group.hooks.map(hook => hook.command));
|
|
140
144
|
assert.equal(allCodexStopCommands.length, 1, 'Codex gets the open tasks Stop gate and no other Stop hook');
|
|
141
145
|
assert.match(allCodexStopCommands[0], /open_tasks_stop_gate\.py/);
|
|
142
|
-
assert.
|
|
143
|
-
firstCodexHooks.hooks.UserPromptSubmit.some(group =>
|
|
146
|
+
assert.equal(
|
|
147
|
+
(firstCodexHooks.hooks.UserPromptSubmit ?? []).some(group =>
|
|
144
148
|
group.hooks.some(hook => String(hook.command).includes('deepen_first_prompt.py')),
|
|
145
149
|
),
|
|
150
|
+
false,
|
|
146
151
|
);
|
|
147
152
|
assert.equal(
|
|
148
153
|
firstCodexHooks.hooks.PreToolUse.some(group => group.matcher === 'Write|Edit|MultiEdit|apply_patch'),
|
|
@@ -285,6 +290,7 @@ test('Codex reinstall removes omitted package hooks and preserves user configura
|
|
|
285
290
|
const suffixCommand = `python "${join(codexHome, 'hooks', 'blocking', 'pre_tool_use_dispatcher.py.backup')}"`;
|
|
286
291
|
hooksByEvent.PreToolUse.push({ matcher: 'suffix', hooks: [{ type: 'command', command: suffixCommand }] });
|
|
287
292
|
hooksByEvent.CustomEvent = { metadata: 'keep-event' };
|
|
293
|
+
hooksByEvent.PostToolUse = [{ matcher: 'Write', hooks: [{ type: 'command', command: 'python user-post-tool-use.py' }] }];
|
|
288
294
|
const expectedUserHooks = JSON.parse(JSON.stringify(hooksByEvent));
|
|
289
295
|
for (const eachGroup of Object.values(expectedUserHooks).flat()) {
|
|
290
296
|
if (eachGroup.hooks && eachGroup.metadata) eachGroup.hooks.shift();
|
|
@@ -301,15 +307,20 @@ test('Codex reinstall removes omitted package hooks and preserves user configura
|
|
|
301
307
|
['Agent|Task', 'multi_agent_v1__spawn_agent'].includes(eachGroup.matcher));
|
|
302
308
|
assert.equal(allSpawnGroups.length, 2);
|
|
303
309
|
migratedHooks.hooks.PreToolUse = migratedHooks.hooks.PreToolUse.filter(eachGroup => !allSpawnGroups.includes(eachGroup));
|
|
304
|
-
const allPackagePromptCommands = migratedHooks.hooks.UserPromptSubmit
|
|
310
|
+
const allPackagePromptCommands = (migratedHooks.hooks.UserPromptSubmit ?? [])
|
|
305
311
|
.flatMap(eachGroup => eachGroup.hooks)
|
|
306
312
|
.map(eachHook => eachHook.command);
|
|
307
|
-
assert.
|
|
313
|
+
assert.equal(allPackagePromptCommands.some(eachCommand => eachCommand.includes('deepen_first_prompt.py')), false);
|
|
308
314
|
delete migratedHooks.hooks.UserPromptSubmit;
|
|
309
315
|
const allPackageStopGroups = migratedHooks.hooks.Stop.filter(eachGroup =>
|
|
310
316
|
eachGroup.hooks.some(eachHook => eachHook.command.includes('open_tasks_stop_gate.py')));
|
|
311
317
|
assert.equal(allPackageStopGroups.length, 1);
|
|
312
318
|
migratedHooks.hooks.Stop = migratedHooks.hooks.Stop.filter(eachGroup => !allPackageStopGroups.includes(eachGroup));
|
|
319
|
+
const allPackagePostToolUseGroups = migratedHooks.hooks.PostToolUse.filter(eachGroup =>
|
|
320
|
+
eachGroup.hooks.some(eachHook => eachHook.command.includes('owned_pr_review_loop.py')));
|
|
321
|
+
assert.equal(allPackagePostToolUseGroups.length, 1);
|
|
322
|
+
assert.equal(allPackagePostToolUseGroups[0].hooks.length, 1);
|
|
323
|
+
migratedHooks.hooks.PostToolUse = migratedHooks.hooks.PostToolUse.filter(eachGroup => !allPackagePostToolUseGroups.includes(eachGroup));
|
|
313
324
|
assert.deepEqual(migratedHooks, { enabled: false, metadata: 'keep-file', hooks: expectedUserHooks });
|
|
314
325
|
assert.equal(readFileSync(configPath, 'utf8'), disabledConfig);
|
|
315
326
|
const firstInstall = readFileSync(codexHooksPath, 'utf8');
|
|
@@ -356,27 +367,91 @@ test('uninstall removes the routing hook and keeps a Codex user hook', () => {
|
|
|
356
367
|
}
|
|
357
368
|
});
|
|
358
369
|
|
|
359
|
-
|
|
360
|
-
|
|
370
|
+
const RETIRED_DEEPEN_CODEX_BLOCK = '<!-- claude-dev-env deepen: start -->\nDeepen the first interactive user task.\n<!-- claude-dev-env deepen: end -->\n';
|
|
371
|
+
|
|
372
|
+
function allSettingsHookCommands(settingsPath) {
|
|
373
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
374
|
+
return Object.values(settings.hooks ?? {})
|
|
375
|
+
.flatMap(allGroups => allGroups)
|
|
376
|
+
.flatMap(group => group.hooks ?? [])
|
|
377
|
+
.map(hook => String(hook.command));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function assertNoDeepenPieceInstalled(homeDirectory, cursorSkillPath, codexGuidancePath, settingsPath) {
|
|
381
|
+
assert.equal(existsSync(cursorSkillPath), false, 'the Cursor deepen skill is gone');
|
|
382
|
+
assert.equal(existsSync(join(homeDirectory, '.claude', 'skills', 'deepen')), false, 'the Claude deepen skill is gone');
|
|
383
|
+
assert.equal(existsSync(join(homeDirectory, '.claude', 'commands', 'deepen.md')), false, 'the /deepen command is gone');
|
|
384
|
+
const codexGuidance = existsSync(codexGuidancePath) ? readFileSync(codexGuidancePath, 'utf8') : '';
|
|
385
|
+
assert.doesNotMatch(codexGuidance, /claude-dev-env deepen/);
|
|
386
|
+
assert.equal(
|
|
387
|
+
allSettingsHookCommands(settingsPath).some(command => command.includes('deepen_first_prompt.py')),
|
|
388
|
+
false,
|
|
389
|
+
'settings.json runs no deepen hook',
|
|
390
|
+
);
|
|
391
|
+
const cursorHooksPath = join(homeDirectory, DEFAULT_CURSOR_DIRECTORY_NAME, 'hooks.json');
|
|
392
|
+
const cursorHooksText = existsSync(cursorHooksPath) ? readFileSync(cursorHooksPath, 'utf8') : '';
|
|
393
|
+
assert.equal(cursorHooksText.includes('deepen_first_prompt.py'), false, 'Cursor runs no deepen hook');
|
|
394
|
+
const installedHookPath = join(homeDirectory, '.claude', 'hooks', 'session', 'deepen_first_prompt.py');
|
|
395
|
+
if (existsSync(installedHookPath)) {
|
|
396
|
+
assert.match(readFileSync(installedHookPath, 'utf8'), /claude-dev-env retired hook stand-in/);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
test('a full install over an old deepen install removes every deepen piece, and a repeat install keeps them out', () => {
|
|
401
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-deepen-retired-'));
|
|
361
402
|
try {
|
|
362
|
-
const
|
|
403
|
+
const claudeDirectory = join(homeDirectory, '.claude');
|
|
363
404
|
const cursorSkillPath = join(homeDirectory, DEFAULT_CURSOR_DIRECTORY_NAME, 'skills', 'deepen', 'SKILL.md');
|
|
364
405
|
const codexGuidancePath = join(homeDirectory, '.codex', 'AGENTS.md');
|
|
365
|
-
const manifestPath = join(
|
|
406
|
+
const manifestPath = join(claudeDirectory, '.claude-dev-env-manifest.json');
|
|
407
|
+
const settingsPath = join(claudeDirectory, 'settings.json');
|
|
408
|
+
const retiredPluginVersion = join(claudeDirectory, 'plugins', 'cache', 'claude-dev-env', 'deepen-first-prompt', '0.1.0');
|
|
409
|
+
const fakeClaudeLogPath = join(homeDirectory, 'fake-claude.log');
|
|
410
|
+
const orphanedMarkerPath = join(retiredPluginVersion, '.orphaned_at');
|
|
411
|
+
const isWindows = process.platform === 'win32';
|
|
412
|
+
const fakeClaudePath = join(homeDirectory, isWindows ? 'fake-claude.cmd' : 'fake-claude.sh');
|
|
413
|
+
writeFileSync(
|
|
414
|
+
fakeClaudePath,
|
|
415
|
+
isWindows
|
|
416
|
+
? `@echo off\r\n>>"${fakeClaudeLogPath}" echo %*\r\ntype nul > "${orphanedMarkerPath}"\r\n`
|
|
417
|
+
: `#!/bin/sh\necho "$@" >> '${fakeClaudeLogPath}'\ntouch '${orphanedMarkerPath}'\n`,
|
|
418
|
+
{ mode: 0o755 },
|
|
419
|
+
);
|
|
420
|
+
const environment = { CDE_CLAUDE_EXECUTABLE: fakeClaudePath };
|
|
366
421
|
|
|
367
|
-
runInstaller(homeDirectory, []);
|
|
422
|
+
runInstaller(homeDirectory, [], environment);
|
|
368
423
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
424
|
+
mkdirSync(dirname(cursorSkillPath), { recursive: true });
|
|
425
|
+
writeFileSync(cursorSkillPath, '# deepen\n');
|
|
426
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
427
|
+
manifest.files.push(cursorSkillPath);
|
|
428
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
429
|
+
writeFileSync(codexGuidancePath, `${readFileSync(codexGuidancePath, 'utf8')}${RETIRED_DEEPEN_CODEX_BLOCK}`);
|
|
430
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
431
|
+
const reminderCommand = settings.hooks.UserPromptSubmit
|
|
432
|
+
.flatMap(group => group.hooks)
|
|
433
|
+
.map(hook => hook.command)
|
|
434
|
+
.find(command => command.includes('skill_loaded_reminder.py'));
|
|
435
|
+
settings.hooks.UserPromptSubmit.push({
|
|
436
|
+
matcher: '',
|
|
437
|
+
hooks: [{ type: 'command', command: reminderCommand.replace('skill_loaded_reminder.py', 'deepen_first_prompt.py'), timeout: 10 }],
|
|
438
|
+
});
|
|
439
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
440
|
+
const cursorHooksPath = join(homeDirectory, DEFAULT_CURSOR_DIRECTORY_NAME, 'hooks.json');
|
|
441
|
+
const cursorHooks = JSON.parse(readFileSync(cursorHooksPath, 'utf8'));
|
|
442
|
+
cursorHooks.hooks.beforeSubmitPrompt = [{ command: "python3 '/old/hooks/session/deepen_first_prompt.py'", timeout: 10 }];
|
|
443
|
+
writeFileSync(cursorHooksPath, JSON.stringify(cursorHooks, null, 2));
|
|
444
|
+
mkdirSync(retiredPluginVersion, { recursive: true });
|
|
374
445
|
|
|
375
|
-
runInstaller(homeDirectory, [
|
|
446
|
+
runInstaller(homeDirectory, [], environment);
|
|
447
|
+
|
|
448
|
+
assertNoDeepenPieceInstalled(homeDirectory, cursorSkillPath, codexGuidancePath, settingsPath);
|
|
449
|
+
assert.match(readFileSync(fakeClaudeLogPath, 'utf8'), /^plugin uninstall deepen-first-prompt@claude-dev-env\r?$/m);
|
|
450
|
+
assert.equal(existsSync(retiredPluginVersion), false, 'the retired plugin cache is gone');
|
|
451
|
+
|
|
452
|
+
runInstaller(homeDirectory, [], environment);
|
|
376
453
|
|
|
377
|
-
|
|
378
|
-
const remainingGuidance = existsSync(codexGuidancePath) ? readFileSync(codexGuidancePath, 'utf8') : '';
|
|
379
|
-
assert.doesNotMatch(remainingGuidance, /claude-dev-env deepen/);
|
|
454
|
+
assertNoDeepenPieceInstalled(homeDirectory, cursorSkillPath, codexGuidancePath, settingsPath);
|
|
380
455
|
} finally {
|
|
381
456
|
rmSync(homeDirectory, { recursive: true, force: true });
|
|
382
457
|
}
|
|
@@ -568,7 +643,7 @@ test('a full install writes only native Cursor hooks: AGENTS.md on sessionStart
|
|
|
568
643
|
assert.equal(gateCommands.length, 1);
|
|
569
644
|
assert.deepEqual(cursorHooks.hooks.stop.filter(entry => entry.command === userStopEntry.command), [userStopEntry]);
|
|
570
645
|
assert.equal(execSync(gateCommands[0], { input: '{"conversation_id": "c-1", "status": "completed"}', encoding: 'utf8' }), '');
|
|
571
|
-
assert.
|
|
646
|
+
assert.equal(cursorHooks.hooks.beforeSubmitPrompt, undefined);
|
|
572
647
|
|
|
573
648
|
runInstaller(homeDirectory, ['--uninstall']);
|
|
574
649
|
const remainingHooks = JSON.parse(readFileSync(cursorHooksPath, 'utf8'));
|
package/bin/install.mjs
CHANGED
|
@@ -52,19 +52,22 @@ import {
|
|
|
52
52
|
PYTHON_PROBE_TIMEOUT_MILLISECONDS,
|
|
53
53
|
ALL_CODEX_NATIVE_PRE_TOOL_USE_MATCHERS,
|
|
54
54
|
ALL_CODEX_NATIVE_STOP_HOOK_SCRIPT_NAMES,
|
|
55
|
+
ALL_CODEX_NATIVE_POST_TOOL_USE_HOOK_SCRIPT_NAMES,
|
|
55
56
|
} from './install-constants.mjs';
|
|
56
57
|
import {
|
|
58
|
+
ALL_RETIRED_PLUGIN_SPECS,
|
|
57
59
|
PSTACK_PLUGIN_SPEC,
|
|
58
|
-
DEEPEN_FIRST_PROMPT_PLUGIN_SPEC,
|
|
59
60
|
SUBAGENT_MODELS_PLUGIN_SPEC,
|
|
60
61
|
USAGE_WRAPUP_PLUGIN_SPEC,
|
|
61
62
|
installMarketplacePlugins,
|
|
63
|
+
removeRetiredMarketplacePlugins,
|
|
62
64
|
shouldInstallMarketplacePlugin,
|
|
63
65
|
} from './install-pstack-plugin.mjs';
|
|
64
66
|
import {
|
|
65
67
|
DEEPSTACK_PLUGIN_SPEC,
|
|
66
68
|
installDeepStackForHosts,
|
|
67
69
|
removeDeepStackCursorHooks,
|
|
70
|
+
removeRetiredCursorFirstTaskHook,
|
|
68
71
|
} from './install-deepstack-plugin.mjs';
|
|
69
72
|
import { seedCodexPstackModels } from './seed-codex-pstack-models.mjs';
|
|
70
73
|
import {
|
|
@@ -85,7 +88,6 @@ import {
|
|
|
85
88
|
removeCodexPackageGuidance,
|
|
86
89
|
removeCodexQuestionGuidance,
|
|
87
90
|
writeCodexAgentsGuidance,
|
|
88
|
-
writeCodexDeepenGuidance,
|
|
89
91
|
writeCodexPackageGuidance,
|
|
90
92
|
writeCodexQuestionGuidance,
|
|
91
93
|
} from './codex-skill-load-block.mjs';
|
|
@@ -247,8 +249,8 @@ export const CORE_SKILLS = [
|
|
|
247
249
|
'issue-tracker',
|
|
248
250
|
'task-build',
|
|
249
251
|
'eli5',
|
|
250
|
-
'deepen',
|
|
251
252
|
'efficient-review',
|
|
253
|
+
'pr-visual',
|
|
252
254
|
];
|
|
253
255
|
|
|
254
256
|
export function collectPackageSourceConflicts(packageDirectory) {
|
|
@@ -1298,6 +1300,7 @@ export const RETIRED_HOOK_REGISTRATION_RELATIVE_PATHS = new Set([
|
|
|
1298
1300
|
'blocking/artifact_look_gate.py',
|
|
1299
1301
|
'blocking/session_title_stop_gate.py',
|
|
1300
1302
|
'session/issue_tracker_session_starter.py',
|
|
1303
|
+
'session/deepen_first_prompt.py',
|
|
1301
1304
|
]);
|
|
1302
1305
|
|
|
1303
1306
|
export const MOVED_HOOK_RELATIVE_PATHS = new Set([
|
|
@@ -1997,7 +2000,7 @@ export function pruneRetiredHookEntriesFromSettings(
|
|
|
1997
2000
|
return removedCount;
|
|
1998
2001
|
}
|
|
1999
2002
|
|
|
2000
|
-
const RETIRED_HOOK_STAND_IN_MARKER = 'claude-dev-env retired hook stand-in';
|
|
2003
|
+
export const RETIRED_HOOK_STAND_IN_MARKER = 'claude-dev-env retired hook stand-in';
|
|
2001
2004
|
|
|
2002
2005
|
const RETIRED_HOOK_STAND_IN_SOURCE = `"""${RETIRED_HOOK_STAND_IN_MARKER}.
|
|
2003
2006
|
|
|
@@ -2245,15 +2248,17 @@ function mergeHooksAtPath(
|
|
|
2245
2248
|
return groupCount;
|
|
2246
2249
|
}
|
|
2247
2250
|
|
|
2248
|
-
function
|
|
2249
|
-
|
|
2250
|
-
return ALL_CODEX_NATIVE_STOP_HOOK_SCRIPT_NAMES.has(scriptName);
|
|
2251
|
+
function hookScriptName(hook) {
|
|
2252
|
+
return String(hook?.command ?? '').replace(/\\/g, '/').split('/').pop();
|
|
2251
2253
|
}
|
|
2252
2254
|
|
|
2253
|
-
function
|
|
2254
|
-
if (!Array.isArray(
|
|
2255
|
-
return
|
|
2256
|
-
.map(group => ({
|
|
2255
|
+
function selectCodexNativeGroupsByScriptName(groups, allScriptNames) {
|
|
2256
|
+
if (!Array.isArray(groups)) return [];
|
|
2257
|
+
return groups
|
|
2258
|
+
.map(group => ({
|
|
2259
|
+
...group,
|
|
2260
|
+
hooks: (group?.hooks ?? []).filter(hook => allScriptNames.has(hookScriptName(hook))),
|
|
2261
|
+
}))
|
|
2257
2262
|
.filter(group => group.hooks.length > 0);
|
|
2258
2263
|
}
|
|
2259
2264
|
|
|
@@ -2267,7 +2272,14 @@ function selectCodexNativeHooksConfig(hooksConfig) {
|
|
|
2267
2272
|
? preToolUseGroups.filter(group => ALL_CODEX_NATIVE_PRE_TOOL_USE_MATCHERS.has(group?.matcher))
|
|
2268
2273
|
: [],
|
|
2269
2274
|
UserPromptSubmit: Array.isArray(userPromptSubmitGroups) ? userPromptSubmitGroups : [],
|
|
2270
|
-
|
|
2275
|
+
PostToolUse: selectCodexNativeGroupsByScriptName(
|
|
2276
|
+
hooksConfig.hooks?.PostToolUse,
|
|
2277
|
+
ALL_CODEX_NATIVE_POST_TOOL_USE_HOOK_SCRIPT_NAMES,
|
|
2278
|
+
),
|
|
2279
|
+
Stop: selectCodexNativeGroupsByScriptName(
|
|
2280
|
+
hooksConfig.hooks?.Stop,
|
|
2281
|
+
ALL_CODEX_NATIVE_STOP_HOOK_SCRIPT_NAMES,
|
|
2282
|
+
),
|
|
2271
2283
|
},
|
|
2272
2284
|
};
|
|
2273
2285
|
}
|
|
@@ -2561,10 +2573,12 @@ function pruneStaleFilesAcrossManagedRoots(priorInstalledFiles, currentInstalled
|
|
|
2561
2573
|
}
|
|
2562
2574
|
const cursorInstallDirectory = INSTALL_ROOT_RESOLUTION.cursorInstallDirectory;
|
|
2563
2575
|
const retiredCursorSyncManifestPath = join(cursorInstallDirectory, '.sync-manifest.json');
|
|
2564
|
-
const
|
|
2565
|
-
|
|
2576
|
+
const retiredCursorSkillsDirectory = join(cursorInstallDirectory, MANAGED_SKILLS_DIRECTORY_NAME);
|
|
2577
|
+
const isRetiredCursorFile = priorFile => resolve(priorFile) === retiredCursorSyncManifestPath
|
|
2578
|
+
|| isInsideDirectory(resolve(priorFile), INSTALL_ROOT_RESOLUTION.cursorRulesInstallDirectory)
|
|
2579
|
+
|| isInsideDirectory(resolve(priorFile), retiredCursorSkillsDirectory);
|
|
2566
2580
|
const retiredCursorRulesOutcome = pruneStaleInstalledFiles(
|
|
2567
|
-
priorInstalledFiles?.filter(
|
|
2581
|
+
priorInstalledFiles?.filter(isRetiredCursorFile) ?? null,
|
|
2568
2582
|
currentInstalledFiles,
|
|
2569
2583
|
cursorInstallDirectory,
|
|
2570
2584
|
join(backupRoot, 'cursor'),
|
|
@@ -3005,23 +3019,6 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
3005
3019
|
}
|
|
3006
3020
|
summary.skills = { created: skillsCreated, updated: skillsUpdated, pruned: 0, paths: skillPaths };
|
|
3007
3021
|
allInstalledFiles.push(...skillPaths);
|
|
3008
|
-
if (copiedSkillNames.has('deepen')) {
|
|
3009
|
-
for (const sourceRoot of allSourceRoots) {
|
|
3010
|
-
const deepenSourceDirectory = join(
|
|
3011
|
-
resolvePackageManagedDirectory(sourceRoot, MANAGED_SKILLS_DIRECTORY_NAME),
|
|
3012
|
-
'deepen',
|
|
3013
|
-
);
|
|
3014
|
-
if (!existsSync(deepenSourceDirectory)) continue;
|
|
3015
|
-
const cursorDeepenDirectory = join(
|
|
3016
|
-
INSTALL_ROOT_RESOLUTION.cursorInstallDirectory,
|
|
3017
|
-
'skills',
|
|
3018
|
-
'deepen',
|
|
3019
|
-
);
|
|
3020
|
-
const cursorSkillStats = copyTree(deepenSourceDirectory, cursorDeepenDirectory);
|
|
3021
|
-
allInstalledFiles.push(...cursorSkillStats.paths);
|
|
3022
|
-
break;
|
|
3023
|
-
}
|
|
3024
|
-
}
|
|
3025
3022
|
if (!selectedGroups) {
|
|
3026
3023
|
summary.retiredSessionContinuity = {
|
|
3027
3024
|
changedPaths: removeRetiredSessionContinuityHooks(),
|
|
@@ -3078,12 +3075,6 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
3078
3075
|
claudeRoot: CLAUDE_HOME,
|
|
3079
3076
|
codexHome: INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
3080
3077
|
cursorDirectory: INSTALL_ROOT_RESOLUTION.cursorInstallDirectory,
|
|
3081
|
-
firstTaskHookPath: join(
|
|
3082
|
-
INSTALL_ROOT_RESOLUTION.hooksInstallDirectory,
|
|
3083
|
-
'session',
|
|
3084
|
-
'deepen_first_prompt.py',
|
|
3085
|
-
),
|
|
3086
|
-
pythonCommand,
|
|
3087
3078
|
formatPathArgument: commandArgumentFromPath,
|
|
3088
3079
|
});
|
|
3089
3080
|
summary.deepStackPlugin = deepStackPlugin;
|
|
@@ -3096,10 +3087,27 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
3096
3087
|
allActionNeededLines.push(`DeepStack (cursor): failed — ${cursorOutcome.warning}`);
|
|
3097
3088
|
}
|
|
3098
3089
|
}
|
|
3090
|
+
if (!selectedGroups) {
|
|
3091
|
+
const retiredCursorHooksPath = removeRetiredCursorFirstTaskHook(
|
|
3092
|
+
INSTALL_ROOT_RESOLUTION.cursorInstallDirectory,
|
|
3093
|
+
);
|
|
3094
|
+
if (retiredCursorHooksPath) {
|
|
3095
|
+
console.log(` Retired first-task hook removed from ${retiredCursorHooksPath}`);
|
|
3096
|
+
}
|
|
3097
|
+
const retiredPlugins = removeRetiredMarketplacePlugins(ALL_RETIRED_PLUGIN_SPECS, {
|
|
3098
|
+
claudeRoot: CLAUDE_HOME,
|
|
3099
|
+
});
|
|
3100
|
+
summary.retiredPlugins = retiredPlugins;
|
|
3101
|
+
for (const eachIdentifier of retiredPlugins.removedIdentifiers) {
|
|
3102
|
+
console.log(` Retired plugin uninstalled: ${eachIdentifier}`);
|
|
3103
|
+
}
|
|
3104
|
+
for (const eachFailure of retiredPlugins.failedRemovals) {
|
|
3105
|
+
console.warn(` Warning: retired plugin ${eachFailure.pluginIdentifier} was not uninstalled: ${eachFailure.detail}`);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3099
3108
|
const allRepositoryPluginSpecs = [
|
|
3100
3109
|
USAGE_WRAPUP_PLUGIN_SPEC,
|
|
3101
3110
|
SUBAGENT_MODELS_PLUGIN_SPEC,
|
|
3102
|
-
DEEPEN_FIRST_PROMPT_PLUGIN_SPEC,
|
|
3103
3111
|
].filter(eachSpec => shouldInstallMarketplacePlugin(eachSpec));
|
|
3104
3112
|
if (!selectedGroups && allRepositoryPluginSpecs.length > 0) {
|
|
3105
3113
|
const repositoryPlugins = installMarketplacePlugins(allRepositoryPluginSpecs, {
|
|
@@ -3123,10 +3131,7 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
3123
3131
|
readFileSync(join(PACKAGE_ROOT, 'rules', 'question-presentation.md'), 'utf8'),
|
|
3124
3132
|
);
|
|
3125
3133
|
if (questionGuidancePath) allCodexGuidancePaths.push(questionGuidancePath);
|
|
3126
|
-
|
|
3127
|
-
INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
3128
|
-
);
|
|
3129
|
-
if (deepenGuidancePath) allCodexGuidancePaths.push(deepenGuidancePath);
|
|
3134
|
+
removeCodexDeepenGuidance(INSTALL_ROOT_RESOLUTION.codexHomeDirectory);
|
|
3130
3135
|
const solPromptPath = installCodexSolPrompt(
|
|
3131
3136
|
INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
3132
3137
|
join(PACKAGE_ROOT, SOL_PROMPT_DIRECTORY_NAME, SOL_PROMPT_FILE_NAME),
|
|
@@ -3676,7 +3681,6 @@ Usage:
|
|
|
3676
3681
|
npx ${PACKAGE_NAME} --no-deepstack Full install without the DeepStack plugin (also CDE_INSTALL_DEEPSTACK=0)
|
|
3677
3682
|
npx ${PACKAGE_NAME} --no-usage-wrapup Full install without the usage-wrapup plugin (also CDE_INSTALL_USAGE_WRAPUP=0)
|
|
3678
3683
|
npx ${PACKAGE_NAME} --no-subagent-models Full install without the subagent-models plugin (also CDE_INSTALL_SUBAGENT_MODELS=0)
|
|
3679
|
-
npx ${PACKAGE_NAME} --no-deepen-first-prompt Full install without the deepen-first-prompt plugin (also CDE_INSTALL_DEEPEN_FIRST_PROMPT=0)
|
|
3680
3684
|
npx ${PACKAGE_NAME} --uninstall Remove installed files from the selected root
|
|
3681
3685
|
npx ${PACKAGE_NAME} --help Show this help
|
|
3682
3686
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { strict as assert } from 'node:assert';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
|
|
9
|
+
const PACKAGE_DIRECTORY = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
const SKILL_PATH_SEGMENTS = ['.agents', 'skills', 'pr-visual'];
|
|
11
|
+
const REQUIRED_FILES = ['SKILL.md', 'scripts/collect.py', 'scripts/render.py', 'scripts/metrics.py', 'scripts/verify.py', 'scripts/visual_config/config/constants.py', 'assets/viewer.html', 'assets/icons.symbols', 'fixtures/bar.scene.json', 'fixtures/integrated.scene.json', 'references/formats.md'];
|
|
12
|
+
const INSTALL_MODES = [[], ['--only', 'core']];
|
|
13
|
+
const NPM_EXECUTABLE = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
14
|
+
|
|
15
|
+
test('packed full and core installs ship the runnable PR visual skill to the shared agents home', () => {
|
|
16
|
+
const sandbox = mkdtempSync(join(tmpdir(), 'cde-pr-visual-'));
|
|
17
|
+
try {
|
|
18
|
+
const packed = JSON.parse(execFileSync(NPM_EXECUTABLE, ['pack', '--json', '--ignore-scripts', '--offline', '--cache', join(sandbox, 'npm-cache'), '--pack-destination', sandbox], { cwd: PACKAGE_DIRECTORY, encoding: 'utf8', shell: process.platform === 'win32' }));
|
|
19
|
+
execFileSync('tar', ['-xzf', join(sandbox, packed[0].filename), '-C', sandbox]);
|
|
20
|
+
const packaged = join(sandbox, 'package');
|
|
21
|
+
for (const [eachIndex, eachArguments] of INSTALL_MODES.entries()) {
|
|
22
|
+
const homeDirectory = join(sandbox, `home-${eachIndex}`);
|
|
23
|
+
execFileSync(process.execPath, [join(packaged, 'bin/install.mjs'), ...eachArguments], {
|
|
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' },
|
|
26
|
+
});
|
|
27
|
+
const installed = join(homeDirectory, ...SKILL_PATH_SEGMENTS);
|
|
28
|
+
for (const eachFile of REQUIRED_FILES) assert.deepEqual(readFileSync(join(installed, eachFile)), readFileSync(join(packaged, ...SKILL_PATH_SEGMENTS, eachFile)));
|
|
29
|
+
assert.equal(realpathSync(join(homeDirectory, '.claude/skills/pr-visual')), realpathSync(installed));
|
|
30
|
+
const manifest = JSON.parse(readFileSync(join(homeDirectory, '.claude/.claude-dev-env-manifest.json'), 'utf8'));
|
|
31
|
+
assert.ok(manifest.skills.includes('pr-visual'));
|
|
32
|
+
const visual = join(sandbox, `visual-${eachIndex}.html`);
|
|
33
|
+
execFileSync(process.platform === 'win32' ? 'python' : 'python3', [join(installed, 'scripts/render.py'), join(installed, 'fixtures/integrated.scene.json'), '--out', visual]);
|
|
34
|
+
assert.ok(existsSync(visual));
|
|
35
|
+
assert.match(readFileSync(visual, 'utf8'), /Duration \(ms\)/);
|
|
36
|
+
}
|
|
37
|
+
} finally {
|
|
38
|
+
rmSync(sandbox, { recursive: true, force: true });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -35,12 +35,11 @@ test('reinstall drops the retired style reminder and keeps one skill reminder ho
|
|
|
35
35
|
mergeHooksIntoSettings(settings, hooksConfig, 'C:/Users/example/.claude', 'python');
|
|
36
36
|
|
|
37
37
|
const userPromptHooks = settings.hooks.UserPromptSubmit.flatMap(eachGroup => eachGroup.hooks);
|
|
38
|
-
assert.equal(userPromptHooks.length,
|
|
38
|
+
assert.equal(userPromptHooks.length, 4);
|
|
39
39
|
assert.equal(userPromptHooks[0].command, userCommand);
|
|
40
40
|
assert.match(userPromptHooks[1].command, /session\/skill_loaded_reminder\.py$/);
|
|
41
|
-
assert.match(userPromptHooks[2].command, /
|
|
42
|
-
assert.match(userPromptHooks[3].command, /
|
|
43
|
-
assert.match(userPromptHooks[4].command, /blocking\/chat_reply_gate\.py$/);
|
|
41
|
+
assert.match(userPromptHooks[2].command, /lifecycle\/nested_project_hooks\.py$/);
|
|
42
|
+
assert.match(userPromptHooks[3].command, /blocking\/chat_reply_gate\.py$/);
|
|
44
43
|
assert.ok(userPromptHooks.every(eachHook => !eachHook.command.includes('style_reminder_prompt')));
|
|
45
44
|
|
|
46
45
|
const firstMerge = JSON.stringify(settings);
|
|
@@ -266,7 +266,6 @@ 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_DEEPEN_FIRST_PROMPT: '0',
|
|
270
269
|
HOME: homeDirectory,
|
|
271
270
|
USERPROFILE: homeDirectory,
|
|
272
271
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
@@ -28,7 +28,6 @@ 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_DEEPEN_FIRST_PROMPT: '0',
|
|
32
31
|
HOME: homeDirectory,
|
|
33
32
|
USERPROFILE: homeDirectory,
|
|
34
33
|
CODEX_HOME: join(homeDirectory, '.codex'),
|
|
@@ -186,7 +185,6 @@ test('a successful pstack install omits the retired skill block and gains the qu
|
|
|
186
185
|
CDE_INSTALL_DEEPSTACK: '0',
|
|
187
186
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
188
187
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
189
|
-
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
190
188
|
CDE_CODEX_EXECUTABLE: commandPath,
|
|
191
189
|
CDE_CLAUDE_EXECUTABLE: commandPath,
|
|
192
190
|
});
|
|
@@ -216,7 +214,6 @@ test('enabling pstack after a pstack-off install preserves question guidance', (
|
|
|
216
214
|
CDE_INSTALL_DEEPSTACK: '0',
|
|
217
215
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
218
216
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
219
|
-
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
220
217
|
CDE_CODEX_EXECUTABLE: commandPath,
|
|
221
218
|
CDE_CLAUDE_EXECUTABLE: commandPath,
|
|
222
219
|
});
|
|
@@ -247,7 +244,6 @@ test('a failed pstack transition restores question-only guidance and removes its
|
|
|
247
244
|
CDE_INSTALL_DEEPSTACK: '0',
|
|
248
245
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
249
246
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
250
|
-
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
251
247
|
CDE_CODEX_EXECUTABLE: commandPath,
|
|
252
248
|
CDE_CLAUDE_EXECUTABLE: commandPath,
|
|
253
249
|
CLAUDE_DEV_ENV_INSTALL_FAULT: 'after_file_staging',
|
|
@@ -70,7 +70,6 @@ function runInstallerInSandbox(sandboxHome, installerArguments = []) {
|
|
|
70
70
|
CDE_INSTALL_DEEPSTACK: '0',
|
|
71
71
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
72
72
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
73
|
-
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
74
73
|
HOME: sandboxHome,
|
|
75
74
|
USERPROFILE: sandboxHome,
|
|
76
75
|
GIT_CONFIG_GLOBAL: gitConfigGlobal,
|