claude-dev-env 8.82.1 → 8.83.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 (34) hide show
  1. package/.agents/skills/deepen/SKILL.md +42 -0
  2. package/bin/codex-skill-load-block.mjs +32 -0
  3. package/bin/codex-skill-load-block.test.mjs +27 -0
  4. package/bin/codex-trimmed-sol.test.mjs +1 -0
  5. package/bin/ever-shipped-skills.mjs +1 -0
  6. package/bin/install-pstack-plugin.mjs +12 -0
  7. package/bin/install-pstack-plugin.test.mjs +23 -0
  8. package/bin/install.agents-home.test.mjs +1 -0
  9. package/bin/install.codex-rules.test.mjs +2 -0
  10. package/bin/install.cursor-rules.test.mjs +38 -1
  11. package/bin/install.mjs +34 -2
  12. package/bin/install.prompt-context.test.mjs +3 -1
  13. package/bin/install.prune.test.mjs +1 -0
  14. package/bin/install.question-presentation.test.mjs +4 -0
  15. package/bin/install.settings-defaults.test.mjs +1 -0
  16. package/bin/install.shared-settings.test.mjs +1 -0
  17. package/bin/install.test.mjs +34 -1
  18. package/bin/install.transaction.test.mjs +8 -0
  19. package/bin/install.uninstall-transaction.test.mjs +2 -0
  20. package/bin/install.update-keeps-hooks.test.mjs +1 -0
  21. package/commands/deepen.md +19 -0
  22. package/hooks/features/lifecycle-cleanup.md +1 -1
  23. package/hooks/features/session-context.md +3 -0
  24. package/hooks/hooks.json +10 -0
  25. package/hooks/hooks_constants/deepen_first_prompt_constants.py +119 -0
  26. package/hooks/hooks_constants/mod_handoff_constants.py +2 -0
  27. package/hooks/hooks_constants/nested_project_hooks_constants.py +2 -0
  28. package/hooks/lifecycle/nested_project_hooks.py +17 -8
  29. package/hooks/lifecycle/test_nested_project_hooks.py +50 -0
  30. package/hooks/session/deepen_first_prompt.py +390 -0
  31. package/hooks/session/test_deepen_first_prompt.py +274 -0
  32. package/package.json +1 -1
  33. package/scripts/ci/install-playtest.mjs +329 -2
  34. package/scripts/ci/install-playtest.test.mjs +6 -0
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: deepen
3
+ description: >-
4
+ Deepen a first user task before work starts. Triggers: /deepen, deepen this
5
+ task, first-task deepening, Cursor Cloud launch prompt.
6
+ ---
7
+
8
+ # deepen
9
+
10
+ Rewrite a user task so the worker does the omitted expert job. Keep the rewrite short.
11
+
12
+ ## When this applies
13
+
14
+ - An interactive first user task on Claude Code, Cursor, or Codex
15
+ - `/deepen` with a task in the arguments
16
+ - Cursor Cloud, where user `~/.cursor/hooks.json` does not run and `sessionStart` is deferred, so the launch prompt is already sent
17
+
18
+ Skip when the prompt already carries `CDE_DEEPEN:v1`, when the text is a thanks or yes/no or status line, when it is a slash command other than `/deepen`, when it is a `/loop` or helper report, or when it is a spawn rewrite that already starts with the poteto-mode prefix.
19
+
20
+ A prompt that is only `/deepen` with no task is skipped. `/deepen` plus a task deepens the remainder.
21
+
22
+ ## Four answers
23
+
24
+ 1. What a top person in that field would do that this prompt omitted.
25
+ 2. The raw source to start from.
26
+ 3. The methods and tools they would use.
27
+ 4. What besides the agent or Jon would show the result is right.
28
+
29
+ Then rewrite the user prompt to ask for exactly that job. Put `CDE_DEEPEN:v1` at the top.
30
+
31
+ ## How to deepen
32
+
33
+ Read the task. Name the field. Fill the four answers from the checkout, the docs, and the tools already in the tree. Keep the rewrite shorter than a new spec. The worker should start from the raw source and finish at the proof.
34
+
35
+ When you spawn a helper to fill the four answers, that helper's prompt starts with `CDE_DEEPEN_AGENT` so the first-task hook leaves it alone.
36
+
37
+ ## Hosts
38
+
39
+ - Claude desktop and CLI: a `prompt.submit` mod rewrites the visible text. The Python `UserPromptSubmit` hook is silent when that mod is on outside a cloud session.
40
+ - Claude cloud and Codex local: the Python hook injects `additionalContext`. Command hooks cannot replace the prompt string.
41
+ - Cursor Cloud: run this skill on the launch prompt.
42
+ - Codex cloud orchestration skips command hooks. Local Codex only.
@@ -10,6 +10,16 @@ export const PACKAGE_GUIDANCE_BLOCK_START = '<!-- claude-dev-env package guidanc
10
10
  export const PACKAGE_GUIDANCE_BLOCK_END = '<!-- claude-dev-env package guidance: end -->';
11
11
  export const QUESTION_PRESENTATION_BLOCK_START = '<!-- claude-dev-env question presentation: start -->';
12
12
  export const QUESTION_PRESENTATION_BLOCK_END = '<!-- claude-dev-env question presentation: end -->';
13
+ export const DEEPEN_GUIDANCE_BLOCK_START = '<!-- claude-dev-env deepen: start -->';
14
+ export const DEEPEN_GUIDANCE_BLOCK_END = '<!-- claude-dev-env deepen: end -->';
15
+ export const DEEPEN_GUIDANCE_TEXT = [
16
+ 'Deepen the first interactive user task before any work starts.',
17
+ 'Answer four things the prompt left out: what a top person in that field would do, the raw source to start from, the methods and tools they would use, and what besides the agent or Jon would show the result is right.',
18
+ 'Then rewrite the task to ask for exactly that job. The rewrite carries the marker CDE_DEEPEN:v1.',
19
+ 'On demand, follow the deepen skill or /deepen with a task.',
20
+ 'Cursor Cloud has no user hooks.json and defers sessionStart, so the launch prompt is already sent. Use the deepen skill under ~/.cursor/skills there.',
21
+ 'Codex cloud orchestration skips command hooks. This block is the local Codex instruction.',
22
+ ].join('\n');
13
23
 
14
24
  const WINDOWS_LONG_PATH_PREFIX = '\\\\?\\';
15
25
 
@@ -115,6 +125,23 @@ export function writeCodexPackageGuidance(codexHome, packageGuidanceText, allPac
115
125
  );
116
126
  }
117
127
 
128
+ export function writeCodexDeepenGuidance(codexHome, deepenText = DEEPEN_GUIDANCE_TEXT) {
129
+ const agentsPath = join(codexHome, 'AGENTS.md');
130
+ const agentsEntry = lstatSync(agentsPath, { throwIfNoEntry: false });
131
+ if (agentsEntry && !agentsEntry.isFile()) return null;
132
+ const currentText = agentsEntry ? readFileSync(agentsPath, 'utf8') : '';
133
+ const block = managedBlock(DEEPEN_GUIDANCE_BLOCK_START, deepenText, DEEPEN_GUIDANCE_BLOCK_END);
134
+ const replacedText = withBlockReplaced(
135
+ currentText, DEEPEN_GUIDANCE_BLOCK_START, DEEPEN_GUIDANCE_BLOCK_END, block,
136
+ );
137
+ const separator = currentText && !currentText.endsWith('\n') ? '\n' : '';
138
+ const updatedText = replacedText ?? `${currentText}${separator}${block}`;
139
+ if (updatedText === currentText) return null;
140
+ mkdirSync(codexHome, { recursive: true });
141
+ writeFileSync(agentsPath, updatedText, 'utf8');
142
+ return agentsPath;
143
+ }
144
+
118
145
  export function writeCodexQuestionGuidance(codexHome, policyText) {
119
146
  const agentsPath = join(codexHome, 'AGENTS.md');
120
147
  const agentsEntry = lstatSync(agentsPath, { throwIfNoEntry: false });
@@ -136,6 +163,7 @@ const ALL_PACKAGE_MANAGED_BLOCK_MARKERS = [
136
163
  [SKILL_LOAD_BLOCK_START, SKILL_LOAD_BLOCK_END],
137
164
  [PACKAGE_GUIDANCE_BLOCK_START, PACKAGE_GUIDANCE_BLOCK_END],
138
165
  [QUESTION_PRESENTATION_BLOCK_START, QUESTION_PRESENTATION_BLOCK_END],
166
+ [DEEPEN_GUIDANCE_BLOCK_START, DEEPEN_GUIDANCE_BLOCK_END],
139
167
  ];
140
168
 
141
169
  /**
@@ -181,6 +209,10 @@ export function removeCodexQuestionGuidance(codexHome) {
181
209
  );
182
210
  }
183
211
 
212
+ export function removeCodexDeepenGuidance(codexHome) {
213
+ return removeCodexGuidanceBlock(codexHome, DEEPEN_GUIDANCE_BLOCK_START, DEEPEN_GUIDANCE_BLOCK_END);
214
+ }
215
+
184
216
  /**
185
217
  * Remove the managed package-guidance block while keeping surrounding guidance bytes.
186
218
  *
@@ -11,10 +11,15 @@ import {
11
11
  SKILL_LOAD_BLOCK_END,
12
12
  SKILL_LOAD_BLOCK_START,
13
13
  SKILL_LOAD_INSTRUCTION,
14
+ removeCodexDeepenGuidance,
14
15
  removeCodexPackageGuidance,
15
16
  removeCodexQuestionGuidance,
16
17
  withSkillLoadBlock,
18
+ DEEPEN_GUIDANCE_BLOCK_END,
19
+ DEEPEN_GUIDANCE_BLOCK_START,
20
+ DEEPEN_GUIDANCE_TEXT,
17
21
  writeCodexAgentsGuidance,
22
+ writeCodexDeepenGuidance,
18
23
  writeCodexPackageGuidance,
19
24
  writeCodexQuestionGuidance,
20
25
  } from './codex-skill-load-block.mjs';
@@ -359,3 +364,25 @@ test('question removal leaves a missing guidance file absent', (context) => {
359
364
  assert.equal(removeCodexQuestionGuidance(homes.codexHome), null);
360
365
  assert.equal(lstatSync(homes.agentsPath, { throwIfNoEntry: false }), undefined);
361
366
  });
367
+
368
+ test('should write the deepen block into Codex AGENTS.md', (context) => {
369
+ const homes = makeHomes(context);
370
+
371
+ assert.equal(writeCodexDeepenGuidance(homes.codexHome), homes.agentsPath);
372
+ const guidanceText = readFileSync(homes.agentsPath, 'utf8');
373
+ assert.ok(guidanceText.includes(DEEPEN_GUIDANCE_BLOCK_START));
374
+ assert.ok(guidanceText.includes(DEEPEN_GUIDANCE_BLOCK_END));
375
+ assert.ok(guidanceText.includes(DEEPEN_GUIDANCE_TEXT.split('\n')[0]));
376
+ assert.equal(writeCodexDeepenGuidance(homes.codexHome), null);
377
+ });
378
+
379
+ test('should restore the custom guidance bytes when the deepen block is removed', (context) => {
380
+ const homes = makeHomes(context);
381
+ mkdirSync(homes.codexHome);
382
+ const customGuidance = '# My notes\n\nKeep this line.\n';
383
+ writeFileSync(homes.agentsPath, customGuidance);
384
+
385
+ assert.equal(writeCodexDeepenGuidance(homes.codexHome), homes.agentsPath);
386
+ assert.equal(removeCodexDeepenGuidance(homes.codexHome), homes.agentsPath);
387
+ assert.equal(readFileSync(homes.agentsPath, 'utf8'), customGuidance);
388
+ });
@@ -34,6 +34,7 @@ function installInScratchHome(homeDirectory, argumentsList = [], environment = {
34
34
  CDE_INSTALL_PSTACK: '0',
35
35
  CDE_INSTALL_USAGE_WRAPUP: '0',
36
36
  CDE_INSTALL_SUBAGENT_MODELS: '0',
37
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
37
38
  CLAUDE_DEV_ENV_INSTALL_FAULT: undefined,
38
39
  HOME: homeDirectory,
39
40
  USERPROFILE: homeDirectory,
@@ -24,6 +24,7 @@ export const EVER_SHIPPED_SKILL_NAMES = new Set([
24
24
  'copilot-review',
25
25
  'correction',
26
26
  'deep-research',
27
+ 'deepen',
27
28
  'descriptions',
28
29
  'e-code-review',
29
30
  'e-simplify',
@@ -34,6 +34,18 @@ export const USAGE_WRAPUP_PLUGIN_SPEC = Object.freeze({
34
34
  optOutVariable: 'CDE_INSTALL_USAGE_WRAPUP',
35
35
  });
36
36
 
37
+ export const DEEPEN_FIRST_PROMPT_PLUGIN_SPEC = Object.freeze({
38
+ name: 'deepen-first-prompt',
39
+ label: 'Deepen-first-prompt',
40
+ marketplaceRepository: 'jl-cmd-projects/claude-dev-env',
41
+ marketplaceName: 'claude-dev-env',
42
+ sparsePaths: Object.freeze(['.claude-plugin', 'packages/deepen-first-prompt']),
43
+ pluginIdentifier: 'deepen-first-prompt@claude-dev-env',
44
+ hosts: Object.freeze(['claude']),
45
+ optOutFlag: '--no-deepen-first-prompt',
46
+ optOutVariable: 'CDE_INSTALL_DEEPEN_FIRST_PROMPT',
47
+ });
48
+
37
49
  export const SUBAGENT_MODELS_PLUGIN_SPEC = Object.freeze({
38
50
  name: 'subagent-models',
39
51
  label: 'Subagent-models',
@@ -7,6 +7,7 @@ import {
7
7
  PSTACK_MARKETPLACE_REPOSITORY,
8
8
  PSTACK_PLUGIN_HOSTS,
9
9
  PSTACK_PLUGIN_IDENTIFIER,
10
+ DEEPEN_FIRST_PROMPT_PLUGIN_SPEC,
10
11
  PSTACK_PLUGIN_SPEC,
11
12
  SUBAGENT_MODELS_PLUGIN_SPEC,
12
13
  USAGE_WRAPUP_PLUGIN_SPEC,
@@ -272,6 +273,7 @@ test('the usage-wrapup opt-out flag and variable turn off only usage-wrapup', ()
272
273
  for (const [spec, packageFolder] of [
273
274
  [USAGE_WRAPUP_PLUGIN_SPEC, './packages/usage-wrapup'],
274
275
  [SUBAGENT_MODELS_PLUGIN_SPEC, './packages/subagent-models'],
276
+ [DEEPEN_FIRST_PROMPT_PLUGIN_SPEC, './packages/deepen-first-prompt'],
275
277
  ]) {
276
278
  test(`the repository marketplace lists ${spec.name} from its package folder in this repository`, () => {
277
279
  const marketplace = JSON.parse(readFileSync(
@@ -305,6 +307,27 @@ test('should remove and add the shared marketplace once when installing usage-wr
305
307
  ]);
306
308
  });
307
309
 
310
+ test('should include deepen-first-prompt in one marketplace add with the other repository plugins', () => {
311
+ const plan = marketplacePluginsPlan(
312
+ [USAGE_WRAPUP_PLUGIN_SPEC, SUBAGENT_MODELS_PLUGIN_SPEC, DEEPEN_FIRST_PROMPT_PLUGIN_SPEC],
313
+ 'claude',
314
+ );
315
+ assert.deepEqual(plan.commands[0], [
316
+ 'plugin', 'marketplace', 'add', 'jl-cmd-projects/claude-dev-env',
317
+ '--sparse', '.claude-plugin', 'packages/usage-wrapup', 'packages/subagent-models',
318
+ 'packages/deepen-first-prompt',
319
+ ]);
320
+ assert.equal(shouldInstallMarketplacePlugin(DEEPEN_FIRST_PROMPT_PLUGIN_SPEC, ['--no-deepen-first-prompt'], {}), false);
321
+ assert.equal(
322
+ shouldInstallMarketplacePlugin(
323
+ DEEPEN_FIRST_PROMPT_PLUGIN_SPEC,
324
+ [],
325
+ { CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0' },
326
+ ),
327
+ false,
328
+ );
329
+ });
330
+
308
331
  test('should refuse one plan for plugins from two marketplaces', () => {
309
332
  assert.throws(
310
333
  () => marketplacePluginsPlan([PSTACK_PLUGIN_SPEC, SUBAGENT_MODELS_PLUGIN_SPEC], 'claude'),
@@ -55,6 +55,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
55
55
  CDE_INSTALL_PSTACK: '0',
56
56
  CDE_INSTALL_USAGE_WRAPUP: '0',
57
57
  CDE_INSTALL_SUBAGENT_MODELS: '0',
58
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
58
59
  HOME: homeDirectory,
59
60
  USERPROFILE: homeDirectory,
60
61
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -61,6 +61,7 @@ function runInstaller(homeDirectory, extraArguments) {
61
61
  CDE_INSTALL_PSTACK: '0',
62
62
  CDE_INSTALL_USAGE_WRAPUP: '0',
63
63
  CDE_INSTALL_SUBAGENT_MODELS: '0',
64
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
64
65
  HOME: homeDirectory,
65
66
  USERPROFILE: homeDirectory,
66
67
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -93,6 +94,7 @@ function scanTextWithProductionPiiScanner(scannedText) {
93
94
  CDE_INSTALL_PSTACK: '0',
94
95
  CDE_INSTALL_USAGE_WRAPUP: '0',
95
96
  CDE_INSTALL_SUBAGENT_MODELS: '0',
97
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
96
98
  PYTHONPATH: BLOCKING_HOOKS_DIRECTORY,
97
99
  CLAUDE_LOCAL_IDENTITY_PATH: join(
98
100
  temporaryDirectory,
@@ -35,6 +35,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
35
35
  CDE_INSTALL_PSTACK: '0',
36
36
  CDE_INSTALL_USAGE_WRAPUP: '0',
37
37
  CDE_INSTALL_SUBAGENT_MODELS: '0',
38
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
38
39
  HOME: homeDirectory,
39
40
  USERPROFILE: homeDirectory,
40
41
  CODEX_HOME: join(homeDirectory, '.codex'),
@@ -133,7 +134,12 @@ test('seeds one editable policy and one native Codex routing hook', () => {
133
134
  );
134
135
  assert.equal(firstSpawnPromptGroups.length, 1);
135
136
  assert.match(firstSpawnPromptGroups[0].hooks[0].command, /skill_loaded_reminder\.py/);
136
- assert.deepEqual(Object.keys(firstCodexHooks.hooks), ['PreToolUse']);
137
+ assert.deepEqual(Object.keys(firstCodexHooks.hooks), ['PreToolUse', 'UserPromptSubmit']);
138
+ assert.ok(
139
+ firstCodexHooks.hooks.UserPromptSubmit.some(group =>
140
+ group.hooks.some(hook => String(hook.command).includes('deepen_first_prompt.py')),
141
+ ),
142
+ );
137
143
  assert.equal(
138
144
  firstCodexHooks.hooks.PreToolUse.some(group => group.matcher === 'Write|Edit|MultiEdit|apply_patch'),
139
145
  false,
@@ -291,6 +297,11 @@ test('Codex reinstall removes omitted package hooks and preserves user configura
291
297
  ['Agent|Task', 'multi_agent_v1__spawn_agent'].includes(eachGroup.matcher));
292
298
  assert.equal(allSpawnGroups.length, 2);
293
299
  migratedHooks.hooks.PreToolUse = migratedHooks.hooks.PreToolUse.filter(eachGroup => !allSpawnGroups.includes(eachGroup));
300
+ const allPackagePromptCommands = migratedHooks.hooks.UserPromptSubmit
301
+ .flatMap(eachGroup => eachGroup.hooks)
302
+ .map(eachHook => eachHook.command);
303
+ assert.ok(allPackagePromptCommands.some(eachCommand => eachCommand.includes('deepen_first_prompt.py')));
304
+ delete migratedHooks.hooks.UserPromptSubmit;
294
305
  assert.deepEqual(migratedHooks, { enabled: false, metadata: 'keep-file', hooks: expectedUserHooks });
295
306
  assert.equal(readFileSync(configPath, 'utf8'), disabledConfig);
296
307
  const firstInstall = readFileSync(codexHooksPath, 'utf8');
@@ -337,6 +348,32 @@ test('uninstall removes the routing hook and keeps a Codex user hook', () => {
337
348
  }
338
349
  });
339
350
 
351
+ test('should ship the deepen skill to Cursor and the deepen block to Codex, and remove both on uninstall', () => {
352
+ const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-deepen-surfaces-'));
353
+ try {
354
+ const sourceSkillPath = join(PACKAGE_DIRECTORY, '.agents', 'skills', 'deepen', 'SKILL.md');
355
+ const cursorSkillPath = join(homeDirectory, DEFAULT_CURSOR_DIRECTORY_NAME, 'skills', 'deepen', 'SKILL.md');
356
+ const codexGuidancePath = join(homeDirectory, '.codex', 'AGENTS.md');
357
+ const manifestPath = join(homeDirectory, '.claude', '.claude-dev-env-manifest.json');
358
+
359
+ runInstaller(homeDirectory, []);
360
+
361
+ assert.equal(readFileSync(cursorSkillPath, 'utf8'), readFileSync(sourceSkillPath, 'utf8'));
362
+ assert.equal(JSON.parse(readFileSync(manifestPath, 'utf8')).files.includes(cursorSkillPath), true);
363
+ const codexGuidance = readFileSync(codexGuidancePath, 'utf8');
364
+ assert.match(codexGuidance, /<!-- claude-dev-env deepen: start -->/);
365
+ assert.match(codexGuidance, /CDE_DEEPEN:v1/);
366
+
367
+ runInstaller(homeDirectory, ['--uninstall']);
368
+
369
+ assert.equal(existsSync(cursorSkillPath), false);
370
+ const remainingGuidance = existsSync(codexGuidancePath) ? readFileSync(codexGuidancePath, 'utf8') : '';
371
+ assert.doesNotMatch(remainingGuidance, /claude-dev-env deepen/);
372
+ } finally {
373
+ rmSync(homeDirectory, { recursive: true, force: true });
374
+ }
375
+ });
376
+
340
377
  test('malformed Codex hooks restore the staged install', () => {
341
378
  const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-malformed-'));
342
379
  try {
package/bin/install.mjs CHANGED
@@ -54,6 +54,7 @@ import {
54
54
  } from './install-constants.mjs';
55
55
  import {
56
56
  PSTACK_PLUGIN_SPEC,
57
+ DEEPEN_FIRST_PROMPT_PLUGIN_SPEC,
57
58
  SUBAGENT_MODELS_PLUGIN_SPEC,
58
59
  USAGE_WRAPUP_PLUGIN_SPEC,
59
60
  installMarketplacePlugins,
@@ -68,9 +69,11 @@ import {
68
69
  writeCodexSolSetting,
69
70
  } from './codex-trimmed-sol.mjs';
70
71
  import {
72
+ removeCodexDeepenGuidance,
71
73
  removeCodexPackageGuidance,
72
74
  removeCodexQuestionGuidance,
73
75
  writeCodexAgentsGuidance,
76
+ writeCodexDeepenGuidance,
74
77
  writeCodexPackageGuidance,
75
78
  writeCodexQuestionGuidance,
76
79
  } from './codex-skill-load-block.mjs';
@@ -223,6 +226,7 @@ export const CORE_SKILLS = [
223
226
  'issue-tracker',
224
227
  'task-build',
225
228
  'eli5',
229
+ 'deepen',
226
230
  ];
227
231
 
228
232
  export function collectPackageSourceConflicts(packageDirectory) {
@@ -2207,12 +2211,14 @@ function mergeHooksAtPath(
2207
2211
 
2208
2212
  function selectCodexNativeHooksConfig(hooksConfig) {
2209
2213
  const preToolUseGroups = hooksConfig.hooks?.PreToolUse;
2214
+ const userPromptSubmitGroups = hooksConfig.hooks?.UserPromptSubmit;
2210
2215
  return {
2211
2216
  ...hooksConfig,
2212
2217
  hooks: {
2213
2218
  PreToolUse: Array.isArray(preToolUseGroups)
2214
2219
  ? preToolUseGroups.filter(group => ALL_CODEX_NATIVE_PRE_TOOL_USE_MATCHERS.has(group?.matcher))
2215
2220
  : [],
2221
+ UserPromptSubmit: Array.isArray(userPromptSubmitGroups) ? userPromptSubmitGroups : [],
2216
2222
  },
2217
2223
  };
2218
2224
  }
@@ -2876,6 +2882,23 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
2876
2882
  }
2877
2883
  summary.skills = { created: skillsCreated, updated: skillsUpdated, pruned: 0, paths: skillPaths };
2878
2884
  allInstalledFiles.push(...skillPaths);
2885
+ if (copiedSkillNames.has('deepen')) {
2886
+ for (const sourceRoot of allSourceRoots) {
2887
+ const deepenSourceDirectory = join(
2888
+ resolvePackageManagedDirectory(sourceRoot, MANAGED_SKILLS_DIRECTORY_NAME),
2889
+ 'deepen',
2890
+ );
2891
+ if (!existsSync(deepenSourceDirectory)) continue;
2892
+ const cursorDeepenDirectory = join(
2893
+ INSTALL_ROOT_RESOLUTION.cursorInstallDirectory,
2894
+ 'skills',
2895
+ 'deepen',
2896
+ );
2897
+ const cursorSkillStats = copyTree(deepenSourceDirectory, cursorDeepenDirectory);
2898
+ allInstalledFiles.push(...cursorSkillStats.paths);
2899
+ break;
2900
+ }
2901
+ }
2879
2902
  if (!selectedGroups) {
2880
2903
  summary.retiredSessionContinuity = {
2881
2904
  changedPaths: removeRetiredSessionContinuityHooks(),
@@ -2915,8 +2938,11 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
2915
2938
  }
2916
2939
  printPluginHostOutcomes([PSTACK_PLUGIN_SPEC], pstackPlugin);
2917
2940
  }
2918
- const allRepositoryPluginSpecs = [USAGE_WRAPUP_PLUGIN_SPEC, SUBAGENT_MODELS_PLUGIN_SPEC]
2919
- .filter(eachSpec => shouldInstallMarketplacePlugin(eachSpec));
2941
+ const allRepositoryPluginSpecs = [
2942
+ USAGE_WRAPUP_PLUGIN_SPEC,
2943
+ SUBAGENT_MODELS_PLUGIN_SPEC,
2944
+ DEEPEN_FIRST_PROMPT_PLUGIN_SPEC,
2945
+ ].filter(eachSpec => shouldInstallMarketplacePlugin(eachSpec));
2920
2946
  if (!selectedGroups && allRepositoryPluginSpecs.length > 0) {
2921
2947
  const repositoryPlugins = installMarketplacePlugins(allRepositoryPluginSpecs, {
2922
2948
  claudeRoot: CLAUDE_HOME,
@@ -2939,6 +2965,10 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
2939
2965
  readFileSync(join(PACKAGE_ROOT, 'rules', 'question-presentation.md'), 'utf8'),
2940
2966
  );
2941
2967
  if (questionGuidancePath) allCodexGuidancePaths.push(questionGuidancePath);
2968
+ const deepenGuidancePath = writeCodexDeepenGuidance(
2969
+ INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
2970
+ );
2971
+ if (deepenGuidancePath) allCodexGuidancePaths.push(deepenGuidancePath);
2942
2972
  const solSettingPath = writeCodexSolSetting(
2943
2973
  INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
2944
2974
  join(CLAUDE_HOME, SOL_PROMPT_DIRECTORY_NAME, SOL_PROMPT_FILE_NAME),
@@ -3307,6 +3337,7 @@ function executeUninstallPlan(plan, helpers = {}) {
3307
3337
  }
3308
3338
  removeCodexPackageGuidance(INSTALL_ROOT_RESOLUTION.codexHomeDirectory);
3309
3339
  removeCodexQuestionGuidance(INSTALL_ROOT_RESOLUTION.codexHomeDirectory);
3340
+ removeCodexDeepenGuidance(INSTALL_ROOT_RESOLUTION.codexHomeDirectory);
3310
3341
  removeCodexSolSetting(INSTALL_ROOT_RESOLUTION.codexHomeDirectory);
3311
3342
  throwIfFault(FAULT_PHASES.AFTER_FILE_STAGING);
3312
3343
 
@@ -3455,6 +3486,7 @@ Usage:
3455
3486
  npx ${PACKAGE_NAME} --no-pstack Full install without the pstack plugin (also CDE_INSTALL_PSTACK=0)
3456
3487
  npx ${PACKAGE_NAME} --no-usage-wrapup Full install without the usage-wrapup plugin (also CDE_INSTALL_USAGE_WRAPUP=0)
3457
3488
  npx ${PACKAGE_NAME} --no-subagent-models Full install without the subagent-models plugin (also CDE_INSTALL_SUBAGENT_MODELS=0)
3489
+ npx ${PACKAGE_NAME} --no-deepen-first-prompt Full install without the deepen-first-prompt plugin (also CDE_INSTALL_DEEPEN_FIRST_PROMPT=0)
3458
3490
  npx ${PACKAGE_NAME} --uninstall Remove installed files from the selected root
3459
3491
  npx ${PACKAGE_NAME} --help Show this help
3460
3492
 
@@ -35,9 +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, 2);
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, /session\/deepen_first_prompt\.py$/);
42
+ assert.match(userPromptHooks[3].command, /lifecycle\/nested_project_hooks\.py$/);
41
43
  assert.ok(userPromptHooks.every(eachHook => !eachHook.command.includes('style_reminder_prompt')));
42
44
 
43
45
  const firstMerge = JSON.stringify(settings);
@@ -264,6 +264,7 @@ function resolveInstallerInvocation(homeDirectory) {
264
264
  CDE_INSTALL_PSTACK: '0',
265
265
  CDE_INSTALL_USAGE_WRAPUP: '0',
266
266
  CDE_INSTALL_SUBAGENT_MODELS: '0',
267
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
267
268
  HOME: homeDirectory,
268
269
  USERPROFILE: homeDirectory,
269
270
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -27,6 +27,7 @@ function installInScratchHome(homeDirectory, argumentsList = [], extraEnvironmen
27
27
  CDE_INSTALL_PSTACK: '0',
28
28
  CDE_INSTALL_USAGE_WRAPUP: '0',
29
29
  CDE_INSTALL_SUBAGENT_MODELS: '0',
30
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
30
31
  HOME: homeDirectory,
31
32
  USERPROFILE: homeDirectory,
32
33
  CODEX_HOME: join(homeDirectory, '.codex'),
@@ -183,6 +184,7 @@ test('a successful pstack install retains its skill block and gains the question
183
184
  CDE_INSTALL_PSTACK: '1',
184
185
  CDE_INSTALL_USAGE_WRAPUP: '0',
185
186
  CDE_INSTALL_SUBAGENT_MODELS: '0',
187
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
186
188
  CDE_CODEX_EXECUTABLE: commandPath,
187
189
  CDE_CLAUDE_EXECUTABLE: commandPath,
188
190
  });
@@ -211,6 +213,7 @@ test('enabling pstack after a pstack-off install preserves question guidance', (
211
213
  CDE_INSTALL_PSTACK: '1',
212
214
  CDE_INSTALL_USAGE_WRAPUP: '0',
213
215
  CDE_INSTALL_SUBAGENT_MODELS: '0',
216
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
214
217
  CDE_CODEX_EXECUTABLE: commandPath,
215
218
  CDE_CLAUDE_EXECUTABLE: commandPath,
216
219
  });
@@ -240,6 +243,7 @@ test('a failed pstack transition restores question-only guidance and removes its
240
243
  CDE_INSTALL_PSTACK: '1',
241
244
  CDE_INSTALL_USAGE_WRAPUP: '0',
242
245
  CDE_INSTALL_SUBAGENT_MODELS: '0',
246
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
243
247
  CDE_CODEX_EXECUTABLE: commandPath,
244
248
  CDE_CLAUDE_EXECUTABLE: commandPath,
245
249
  CLAUDE_DEV_ENV_INSTALL_FAULT: 'after_file_staging',
@@ -65,6 +65,7 @@ function runInstallerInSandbox(sandboxHome, installerArguments = []) {
65
65
  CDE_INSTALL_PSTACK: '0',
66
66
  CDE_INSTALL_USAGE_WRAPUP: '0',
67
67
  CDE_INSTALL_SUBAGENT_MODELS: '0',
68
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
68
69
  HOME: sandboxHome,
69
70
  USERPROFILE: sandboxHome,
70
71
  GIT_CONFIG_GLOBAL: gitConfigGlobal,
@@ -33,6 +33,7 @@ function installEnvironment(homeDirectory) {
33
33
  CDE_INSTALL_PSTACK: '0',
34
34
  CDE_INSTALL_USAGE_WRAPUP: '0',
35
35
  CDE_INSTALL_SUBAGENT_MODELS: '0',
36
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
36
37
  };
37
38
  }
38
39
 
@@ -257,6 +257,11 @@ test('CORE_SKILLS ships ELI5 as the leaf presentation skill', () => {
257
257
  assert.equal(INSTALL_GROUPS.core.skills.includes('eli5'), true);
258
258
  });
259
259
 
260
+ test('CORE_SKILLS ships deepen so the first-task skill installs with core', () => {
261
+ assert.ok(CORE_SKILLS.includes('deepen'), 'deepen must be in CORE_SKILLS');
262
+ assert.equal(INSTALL_GROUPS.core.skills.includes('deepen'), true);
263
+ });
264
+
260
265
  test('CORE_SKILLS ships test-runner for explicit Django and Playwright readiness checks', () => {
261
266
  assert.ok(CORE_SKILLS.includes('test-runner'));
262
267
  assert.equal(INSTALL_GROUPS.core.skills.includes('test-runner'), true);
@@ -3037,6 +3042,7 @@ test('a failed update preserves an existing skills lookup pointer', () => {
3037
3042
  CDE_INSTALL_PSTACK: '0',
3038
3043
  CDE_INSTALL_USAGE_WRAPUP: '0',
3039
3044
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3045
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
3040
3046
  };
3041
3047
  const argumentsForCoreInstall = [PSTACK_TEST_INSTALLER_PATH, '--only', 'core'];
3042
3048
  const firstRun = spawnSync(process.execPath, argumentsForCoreInstall, {
@@ -3090,6 +3096,7 @@ function runPstackInstaller(homeDirectory, extraArguments, environmentOverrides
3090
3096
  CDE_INSTALL_PSTACK: '1',
3091
3097
  CDE_INSTALL_USAGE_WRAPUP: '0',
3092
3098
  CDE_INSTALL_SUBAGENT_MODELS: '0',
3099
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
3093
3100
  ...environmentOverrides,
3094
3101
  },
3095
3102
  });
@@ -3402,15 +3409,18 @@ test('the help output names the usage-wrapup opt-out beside the pstack one', ()
3402
3409
 
3403
3410
  assert.equal(helpRun.status, 0, helpRun.stderr);
3404
3411
  assert.match(helpRun.stdout, /--no-pstack .*\n.*--no-usage-wrapup .*CDE_INSTALL_USAGE_WRAPUP=0/);
3412
+ assert.match(helpRun.stdout, /--no-deepen-first-prompt .*CDE_INSTALL_DEEPEN_FIRST_PROMPT=0/);
3405
3413
  });
3406
3414
 
3407
3415
  const REPOSITORY_PLUGIN_CLAUDE_COMMANDS = Object.freeze([
3408
3416
  'claude plugin uninstall usage-wrapup@claude-dev-env',
3409
3417
  'claude plugin uninstall subagent-models@claude-dev-env',
3418
+ 'claude plugin uninstall deepen-first-prompt@claude-dev-env',
3410
3419
  'claude plugin marketplace remove claude-dev-env',
3411
- 'claude plugin marketplace add jl-cmd-projects/claude-dev-env --sparse .claude-plugin packages/usage-wrapup packages/subagent-models',
3420
+ 'claude plugin marketplace add jl-cmd-projects/claude-dev-env --sparse .claude-plugin packages/usage-wrapup packages/subagent-models packages/deepen-first-prompt',
3412
3421
  'claude plugin install usage-wrapup@claude-dev-env',
3413
3422
  'claude plugin install subagent-models@claude-dev-env',
3423
+ 'claude plugin install deepen-first-prompt@claude-dev-env',
3414
3424
  ]);
3415
3425
 
3416
3426
  test('should install subagent-models beside usage-wrapup with one marketplace add on a full install', t => {
@@ -3420,11 +3430,13 @@ test('should install subagent-models beside usage-wrapup with one marketplace ad
3420
3430
  ...sandbox.environment,
3421
3431
  CDE_INSTALL_USAGE_WRAPUP: '1',
3422
3432
  CDE_INSTALL_SUBAGENT_MODELS: '1',
3433
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '1',
3423
3434
  });
3424
3435
 
3425
3436
  assert.deepEqual(usageWrapupCommands(sandbox.recordedCommands()), REPOSITORY_PLUGIN_CLAUDE_COMMANDS);
3426
3437
  assert.match(installerOutput, /Usage-wrapup \(claude\): installed/);
3427
3438
  assert.match(installerOutput, /Subagent-models \(claude\): installed/);
3439
+ assert.match(installerOutput, /Deepen-first-prompt \(claude\): installed/);
3428
3440
  });
3429
3441
 
3430
3442
  for (const [caseName, extraArguments, environmentOverrides] of [
@@ -3448,6 +3460,27 @@ for (const [caseName, extraArguments, environmentOverrides] of [
3448
3460
  });
3449
3461
  }
3450
3462
 
3463
+ for (const [caseName, extraArguments, environmentOverrides] of [
3464
+ ['--no-deepen-first-prompt', ['--no-deepen-first-prompt'], { CDE_INSTALL_DEEPEN_FIRST_PROMPT: '1' }],
3465
+ ['CDE_INSTALL_DEEPEN_FIRST_PROMPT=0', [], { CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0' }],
3466
+ ['an --only run', ['--only', 'core'], { CDE_INSTALL_DEEPEN_FIRST_PROMPT: '1' }],
3467
+ ]) {
3468
+ test(`should skip deepen-first-prompt on ${caseName}`, t => {
3469
+ const sandbox = pstackPluginSandbox(t);
3470
+
3471
+ const installerOutput = runPstackInstaller(sandbox.homeDirectory, extraArguments, {
3472
+ ...sandbox.environment,
3473
+ ...environmentOverrides,
3474
+ });
3475
+
3476
+ assert.equal(
3477
+ sandbox.recordedCommands().some(eachCommand => eachCommand.includes('deepen-first-prompt')),
3478
+ false,
3479
+ );
3480
+ assert.doesNotMatch(installerOutput, /Deepen-first-prompt \(/);
3481
+ });
3482
+ }
3483
+
3451
3484
  test('should name the subagent-models opt-out in the help output', () => {
3452
3485
  const helpRun = spawnSync(process.execPath, [PSTACK_TEST_INSTALLER_PATH, '--help'], {
3453
3486
  encoding: 'utf8',
@@ -90,6 +90,7 @@ test('capture and restore recover settings, manifest, files, and hooksPath', ()
90
90
  CDE_INSTALL_PSTACK: '0',
91
91
  CDE_INSTALL_USAGE_WRAPUP: '0',
92
92
  CDE_INSTALL_SUBAGENT_MODELS: '0',
93
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
93
94
  HOME: box.root,
94
95
  USERPROFILE: box.root,
95
96
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -155,6 +156,7 @@ test('capture and restore recover an additional host settings file', () => {
155
156
  CDE_INSTALL_PSTACK: '0',
156
157
  CDE_INSTALL_USAGE_WRAPUP: '0',
157
158
  CDE_INSTALL_SUBAGENT_MODELS: '0',
159
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
158
160
  HOME: box.root,
159
161
  USERPROFILE: box.root,
160
162
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -193,6 +195,7 @@ test('runWithInstallTransaction restores prior state on injected fault', () => {
193
195
  CDE_INSTALL_PSTACK: '0',
194
196
  CDE_INSTALL_USAGE_WRAPUP: '0',
195
197
  CDE_INSTALL_SUBAGENT_MODELS: '0',
198
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
196
199
  HOME: box.root,
197
200
  USERPROFILE: box.root,
198
201
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -250,6 +253,7 @@ test('runWithInstallTransaction commits and discards journal on success', () =>
250
253
  CDE_INSTALL_PSTACK: '0',
251
254
  CDE_INSTALL_USAGE_WRAPUP: '0',
252
255
  CDE_INSTALL_SUBAGENT_MODELS: '0',
256
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
253
257
  HOME: box.root,
254
258
  USERPROFILE: box.root,
255
259
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -296,6 +300,7 @@ function runInstaller(homeDirectory, extraArguments, options = {}) {
296
300
  CDE_INSTALL_PSTACK: '0',
297
301
  CDE_INSTALL_USAGE_WRAPUP: '0',
298
302
  CDE_INSTALL_SUBAGENT_MODELS: '0',
303
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
299
304
  HOME: homeDirectory,
300
305
  USERPROFILE: homeDirectory,
301
306
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
@@ -332,6 +337,7 @@ test('a later install fault removes newly seeded Codex pstack files', () => {
332
337
  CDE_INSTALL_PSTACK: '1',
333
338
  CDE_INSTALL_USAGE_WRAPUP: '0',
334
339
  CDE_INSTALL_SUBAGENT_MODELS: '0',
340
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
335
341
  CDE_CODEX_EXECUTABLE: codexCommandPath,
336
342
  },
337
343
  });
@@ -478,6 +484,7 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
478
484
  CDE_INSTALL_PSTACK: '0',
479
485
  CDE_INSTALL_USAGE_WRAPUP: '0',
480
486
  CDE_INSTALL_SUBAGENT_MODELS: '0',
487
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
481
488
  HOME: homeDirectory,
482
489
  USERPROFILE: homeDirectory,
483
490
  GIT_CONFIG_GLOBAL: gitConfigPath,
@@ -498,6 +505,7 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
498
505
  CDE_INSTALL_PSTACK: '0',
499
506
  CDE_INSTALL_USAGE_WRAPUP: '0',
500
507
  CDE_INSTALL_SUBAGENT_MODELS: '0',
508
+ CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
501
509
  HOME: homeDirectory,
502
510
  USERPROFILE: homeDirectory,
503
511
  GIT_CONFIG_GLOBAL: gitConfigPath,