claude-dev-env 8.85.0 → 8.85.2

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 (32) hide show
  1. package/.agents/skills/efficient-review/SKILL.md +79 -0
  2. package/.agents/skills/efficient-review/defaults.json +12 -0
  3. package/.agents/skills/efficient-review/reference/cloud-session.md +13 -0
  4. package/bin/codex-skill-load-block.mjs +27 -28
  5. package/bin/codex-skill-load-block.test.mjs +62 -30
  6. package/bin/codex-trimmed-sol.test.mjs +1 -0
  7. package/bin/ever-shipped-skills.mjs +1 -0
  8. package/bin/install-deepstack-plugin.mjs +300 -0
  9. package/bin/install-deepstack-plugin.test.mjs +98 -0
  10. package/bin/install-pstack-plugin.mjs +3 -5
  11. package/bin/install-pstack-plugin.test.mjs +28 -0
  12. package/bin/install.agents-home.test.mjs +1 -0
  13. package/bin/install.codex-rules.test.mjs +2 -0
  14. package/bin/install.cursor-rules.test.mjs +1 -0
  15. package/bin/install.mjs +27 -3
  16. package/bin/install.profile-settings.test.mjs +1 -0
  17. package/bin/install.prune.test.mjs +20 -0
  18. package/bin/install.question-presentation.test.mjs +7 -3
  19. package/bin/install.settings-defaults.test.mjs +1 -0
  20. package/bin/install.shared-settings.test.mjs +1 -0
  21. package/bin/install.test.mjs +79 -10
  22. package/bin/install.transaction.test.mjs +8 -0
  23. package/bin/install.uninstall-transaction.test.mjs +2 -0
  24. package/bin/install.update-keeps-hooks.test.mjs +1 -0
  25. package/docs/decisions/deepstack-install.md +9 -0
  26. package/docs/rule-guides/deepstack-wording.md +23 -0
  27. package/package.json +1 -1
  28. package/rules/deepstack-wording.md +15 -0
  29. package/scripts/deepstack_inventory.py +348 -0
  30. package/scripts/dev_env_scripts_constants/deepstack_inventory_constants.py +42 -0
  31. package/scripts/test_deepstack_inventory.py +137 -0
  32. package/scripts/test_deepstack_wording_rule.py +20 -0
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: efficient-review
3
+ description: >-
4
+ Run one low-cost code review of the current branch, or of a branch in another
5
+ repository, as a headless Claude run on haiku with the slim review profile.
6
+ An auto run lets the account broker pick the account. A headless run names
7
+ the account. Triggers: /efficient-review, "efficient review", "cheap review",
8
+ "haiku review of this branch".
9
+ ---
10
+
11
+ # Efficient review
12
+
13
+ This skill runs one headless code review of a branch on haiku and prints its findings. Pick the mode, run the planner, then run the review on the machine that holds the Claude accounts, or through the agent-pc runner from a cloud session.
14
+
15
+ Argument shape: `/efficient-review [auto|headless] [account] [model=] [effort=] [level=] [base=]`. Pass `model=` only as the Defaults section allows.
16
+ A blank argument comes from [`defaults.json`](defaults.json).
17
+
18
+ ## Defaults
19
+
20
+ **Always run the review on haiku.** `defaults.json` names `haiku`, and a blank `model` input runs it. Pass `model=` or the workflow's `model` input only when the owner's own message names the family as a directive, such as "run this review on sonnet". Then run it on that family. Do not ask which model to use. Do not change the model on your own, for depth, for cost, or because passes keep finding issues. A stream of findings is a reason to fix the findings and run haiku again.
21
+
22
+ **The review must run headless.** Never spawn the review as an Agent subagent in this session. A subagent runs inside the parent session, so it cannot load the slim profile's setting sources, plugin folder, MCP config, or auto-compact window. `defaults.json` holds a headless mode, and `load_defaults` refuses any other mode.
23
+
24
+ ## Subcommands
25
+
26
+ Run each one as `python -m tools.efficient_review <subcommand> <arguments>` from the checkout that contains `tools/efficient_review.py`.
27
+
28
+ | Subcommand | Action |
29
+ |---|---|
30
+ | `plan` | Prints the mode, account, model family, harness, effort, level, base, and the prompt. |
31
+ | `accounts` | Lists the Claude accounts a headless run can name. |
32
+ | `run` | Runs a headless or auto review and prints the review, the served models, and the saved file. |
33
+
34
+ ## Modes
35
+
36
+ | Mode | Picked when | Account |
37
+ |---|---|---|
38
+ | auto | No mode word and no account. The word `auto` picks it too. | The account broker picks it. Ask nothing. |
39
+ | headless | The word `headless`, or an account launcher name. | Required. With no account named, ask for one. |
40
+
41
+ A model is a family name: haiku, sonnet, opus, sol, or luna. The planner refuses a versioned model ID. `codex review` takes no level, so the planner refuses `level=` with sol or luna.
42
+
43
+ ## 1. Headless mode with no account
44
+
45
+ Run `python -m tools.efficient_review accounts`. Ask with the session's question tool, one option per listed account, and wait for the answer. The answer is the account. On the machine that holds the launchers, the list comes from the `claude-*.cmd` launchers, without `claude-base`, `claude-raw`, and `claude-kimi`. In the cloud the list is the known roster.
46
+
47
+ ## 2. Headless or auto on that machine
48
+
49
+ Run `python -m tools.efficient_review run <arguments> --repository <checkout>`. When the checkout has no launcher folder, go to step 3.
50
+
51
+ - It builds the slim profile from `tools.review_profile` into a temp folder.
52
+ - Auto asks `account_broker.py choose --product claude` for the account.
53
+ - It runs the account launcher with `-p`, the model alias, the effort, the profile arguments, and the environment. The system prompt file holds the review rules from the checkout that contains the runner, and the code standards. The prompt and the diff against the base go on standard input.
54
+ - A sol or luna family runs `codex review` on the newest slug of that family. The log prints that slug.
55
+ - It prints the review, the served models from the stream's `modelUsage` keys, and the saved file. The review and its record go to a temp folder outside the repository.
56
+
57
+ ## 3. Headless or auto from a cloud session
58
+
59
+ The cloud container has no other Claude accounts, so the review runs on the agent-pc runner. Read [reference/cloud-session.md](reference/cloud-session.md).
60
+
61
+ ## Reading guide
62
+
63
+ | File | What it does |
64
+ |---|---|
65
+ | [`reference/cloud-session.md`](reference/cloud-session.md) | Dispatching the review from a cloud session and reading its results branch. |
66
+ | [`defaults.json`](defaults.json) | The default model family, effort, level, prompt, base, profile, advisor, validator, mode, and timeout. |
67
+
68
+ ## Common pitfalls
69
+
70
+ - **Picking a stronger model after several passes.** Fix the findings and run haiku again.
71
+ - **Setting a valid finding aside.** Check each finding against the code. Fix every one that holds in the same branch, even when it reaches files the diff did not touch. Answer only the findings that fail the check, with the file and line that rules each out.
72
+ - **Asking which model to use.** The answer is haiku. When the owner names another family, run it without a confirmation question.
73
+ - **Spawning the review as a subagent.** The review loses the slim profile. Run it headless.
74
+ - **Dispatching from the branch under review.** The workflow runs only from `main`.
75
+
76
+ ## Out of scope
77
+
78
+ - The `validator` default stays `off`.
79
+ - A fixer step after the review. The skill has no fixer step.
@@ -0,0 +1,12 @@
1
+ {
2
+ "model": "haiku",
3
+ "effort": "high",
4
+ "level": "high",
5
+ "prompt": "/code-review {level} --max-findings all {base}",
6
+ "base": "main",
7
+ "profile": "slim",
8
+ "advisor": "none",
9
+ "validator": "off",
10
+ "mode": "auto",
11
+ "timeout_minutes": 60
12
+ }
@@ -0,0 +1,13 @@
1
+ # Headless or auto from a cloud session
2
+
3
+ The cloud container has no other Claude accounts, so the review runs on the agent-pc runner.
4
+
5
+ 1. From the checkout that contains `tools/efficient_review.py`, dispatch `.github/workflows/efficient-review.yml` on ref `main`. The job runs only from `main`, so the runner executes the reviewed tool code and never the branch under review. Inputs: `account` (blank means auto), `effort`, `level`, `base`, `ref` (the branch to review), and `repository` (the `owner/name` to review when it is not that checkout). Leave `model` blank.
6
+ 2. Wait for the run to finish.
7
+ 3. Read `review.md` from the results branch `efficient-review-results/<run id>-<attempt>`. The branch holds that file and `review.json`. The commit message names the account, the launcher, and the served models. Print the review and the served models.
8
+
9
+ A review of another repository checks it out with the read token configured for that workflow.
10
+
11
+ To review a pull request in the checkout that contains the workflow, pass its number as `pull_request` and leave `ref` blank. The run reviews the pull request head, then posts each finding on it as a review thread.
12
+
13
+ For a pull request in this package's public repository, pass `repository` as `jl-cmd/claude-dev-env` and `pull_request` together. The workflow accepts that repository name. The run posts nothing on the public pull request. When the review lists findings, it dispatches the landing repair with the pull request number and the results branch.
@@ -3,9 +3,6 @@ import { dirname, join, resolve } from 'node:path';
3
3
 
4
4
  export const SKILL_LOAD_BLOCK_START = '<!-- claude-dev-env skill load: start -->';
5
5
  export const SKILL_LOAD_BLOCK_END = '<!-- claude-dev-env skill load: end -->';
6
- export const SKILL_LOAD_INSTRUCTION = 'Load the pstack:poteto-mode skill before your first tool call or reply: '
7
- + 'open its SKILL.md from your skills list and follow it. This binds every Codex agent, '
8
- + 'including a helper started with spawn_agent, whatever its task message says.';
9
6
  export const PACKAGE_GUIDANCE_BLOCK_START = '<!-- claude-dev-env package guidance: start -->';
10
7
  export const PACKAGE_GUIDANCE_BLOCK_END = '<!-- claude-dev-env package guidance: end -->';
11
8
  export const QUESTION_PRESENTATION_BLOCK_START = '<!-- claude-dev-env question presentation: start -->';
@@ -39,17 +36,24 @@ function withBlockReplaced(guidanceText, blockStart, blockEnd, block) {
39
36
  }
40
37
 
41
38
  /**
42
- * Return the Codex guidance text with the package's skill-load block in it.
39
+ * Return guidance text with every complete skill-load block removed.
43
40
  *
44
- * Codex starts a spawn_agent helper without running any hook, so the global
45
- * AGENTS.md is the one channel that reaches every helper. A block already in
46
- * the text is replaced where it stands; otherwise the block goes first.
41
+ * When a block is the start of the remaining text, one following blank line is
42
+ * removed with it. A start marker with no end marker stays.
47
43
  */
48
- export function withSkillLoadBlock(guidanceText) {
49
- const block = managedBlock(SKILL_LOAD_BLOCK_START, SKILL_LOAD_INSTRUCTION, SKILL_LOAD_BLOCK_END);
50
- const replacedText = withBlockReplaced(guidanceText, SKILL_LOAD_BLOCK_START, SKILL_LOAD_BLOCK_END, block);
51
- if (replacedText !== null) return replacedText;
52
- return guidanceText ? `${block}\n${guidanceText}` : block;
44
+ export function withoutSkillLoadBlock(guidanceText) {
45
+ let remainingText = guidanceText;
46
+ let removedBlockCount = 0;
47
+ while (remainingText.includes(SKILL_LOAD_BLOCK_START)) {
48
+ const blockStartsAtTop = remainingText.startsWith(SKILL_LOAD_BLOCK_START);
49
+ const replacedText = withBlockReplaced(remainingText, SKILL_LOAD_BLOCK_START, SKILL_LOAD_BLOCK_END, '');
50
+ if (replacedText === null || replacedText === remainingText) break;
51
+ removedBlockCount += 1;
52
+ remainingText = blockStartsAtTop && replacedText.startsWith('\n')
53
+ ? replacedText.slice(1)
54
+ : replacedText;
55
+ }
56
+ return removedBlockCount === 0 ? guidanceText : remainingText;
53
57
  }
54
58
 
55
59
  function withPackageGuidanceBlock(guidanceText, packageGuidanceText) {
@@ -84,7 +88,7 @@ function writeCodexGuidanceFile(codexHome, allPackageGuidancePaths, transformGui
84
88
  const isPackageGuidanceLink = agentsEntry?.isSymbolicLink() ?? false;
85
89
  if (isPackageGuidanceLink && !linksToPackageGuidance(agentsPath, allPackageGuidancePaths)) return null;
86
90
  const currentText = agentsEntry && !isPackageGuidanceLink ? readFileSync(agentsPath, 'utf8') : '';
87
- const updatedText = transformGuidance(currentText);
91
+ const updatedText = transformGuidance(withoutSkillLoadBlock(currentText));
88
92
  if (!isPackageGuidanceLink && updatedText === currentText) return null;
89
93
  mkdirSync(codexHome, { recursive: true });
90
94
  if (isPackageGuidanceLink) unlinkSync(agentsPath);
@@ -93,29 +97,24 @@ function writeCodexGuidanceFile(codexHome, allPackageGuidancePaths, transformGui
93
97
  }
94
98
 
95
99
  /**
96
- * Keep CODEX_HOME/AGENTS.md carrying the skill-load block and the package guidance.
97
- *
98
- * The skill-load block goes first, and the package-guidance block holds the
99
- * current guidance text. A symbolic link that points at the package guidance
100
- * (the shared agents-home copy, or the retired copy under the Claude home) is
101
- * replaced by a regular file holding both blocks, so Codex keeps both after
102
- * the package moves its guidance. A link to any other file is left alone.
100
+ * Remove a skill-load block and write package guidance into CODEX_HOME/AGENTS.md.
103
101
  *
104
- * Returns the file path when the file changed, and null otherwise.
102
+ * Link handling matches writeCodexPackageGuidance. Returns the file path when
103
+ * the file changed, and null otherwise.
105
104
  */
106
105
  export function writeCodexAgentsGuidance(codexHome, packageGuidanceText, allPackageGuidancePaths) {
107
- return writeCodexGuidanceFile(
108
- codexHome,
109
- allPackageGuidancePaths,
110
- currentText => withPackageGuidanceBlock(withSkillLoadBlock(currentText), packageGuidanceText),
111
- );
106
+ return writeCodexPackageGuidance(codexHome, packageGuidanceText, allPackageGuidancePaths);
112
107
  }
113
108
 
114
109
  /**
115
110
  * Keep CODEX_HOME/AGENTS.md carrying the package guidance, with no skill-load block.
116
111
  *
117
- * Links follow the same rule as writeCodexAgentsGuidance. Returns the file
118
- * path when the file changed, and null otherwise.
112
+ * An existing skill-load block is removed. A symbolic link that points at the
113
+ * package guidance, the shared agents-home copy or the retired copy under the
114
+ * Claude home, is replaced by a regular file. A link to any other file is left
115
+ * alone.
116
+ *
117
+ * Returns the file path when the file changed, and null otherwise.
119
118
  */
120
119
  export function writeCodexPackageGuidance(codexHome, packageGuidanceText, allPackageGuidancePaths) {
121
120
  return writeCodexGuidanceFile(
@@ -10,11 +10,10 @@ import {
10
10
  QUESTION_PRESENTATION_BLOCK_START,
11
11
  SKILL_LOAD_BLOCK_END,
12
12
  SKILL_LOAD_BLOCK_START,
13
- SKILL_LOAD_INSTRUCTION,
14
13
  removeCodexDeepenGuidance,
15
14
  removeCodexPackageGuidance,
16
15
  removeCodexQuestionGuidance,
17
- withSkillLoadBlock,
16
+ withoutSkillLoadBlock,
18
17
  DEEPEN_GUIDANCE_BLOCK_END,
19
18
  DEEPEN_GUIDANCE_BLOCK_START,
20
19
  DEEPEN_GUIDANCE_TEXT,
@@ -24,7 +23,10 @@ import {
24
23
  writeCodexQuestionGuidance,
25
24
  } from './codex-skill-load-block.mjs';
26
25
 
27
- const EXPECTED_BLOCK = `${SKILL_LOAD_BLOCK_START}\n${SKILL_LOAD_INSTRUCTION}\n${SKILL_LOAD_BLOCK_END}\n`;
26
+ const STALE_SKILL_LOAD_SENTENCE = 'Load the pstack:poteto-mode skill before your first tool call or reply: '
27
+ + 'open its SKILL.md from your skills list and follow it. This binds every Codex agent, '
28
+ + 'including a helper started with spawn_agent, whatever its task message says.';
29
+ const STALE_SKILL_LOAD_BLOCK = `${SKILL_LOAD_BLOCK_START}\n${STALE_SKILL_LOAD_SENTENCE}\n${SKILL_LOAD_BLOCK_END}\n`;
28
30
  const PACKAGE_GUIDANCE = 'Status\n\nChanged / proof / blocked.\n';
29
31
  const QUESTION_POLICY = '# Present questions clearly\n\nAsk one question.\n';
30
32
 
@@ -52,34 +54,44 @@ function writeGuidance(homes) {
52
54
  return writeCodexAgentsGuidance(homes.codexHome, PACKAGE_GUIDANCE, homes.allPackageGuidancePaths);
53
55
  }
54
56
 
55
- test('the instruction names the skill and binds spawned helpers', () => {
56
- assert.match(SKILL_LOAD_INSTRUCTION, /pstack:poteto-mode/);
57
- assert.match(SKILL_LOAD_INSTRUCTION, /spawn_agent/);
57
+ test('a complete skill-load block is removed and the lines around it stay', () => {
58
+ assert.equal(withoutSkillLoadBlock(`Intro\n${STALE_SKILL_LOAD_BLOCK}Outro\n`), 'Intro\nOutro\n');
59
+ assert.equal(withoutSkillLoadBlock(`${STALE_SKILL_LOAD_BLOCK}\nNotes\n`), 'Notes\n');
60
+ assert.equal(
61
+ withoutSkillLoadBlock(`${STALE_SKILL_LOAD_BLOCK}\n${STALE_SKILL_LOAD_BLOCK}\nNotes\n`),
62
+ 'Notes\n',
63
+ );
64
+ assert.equal(
65
+ withoutSkillLoadBlock(`${SKILL_LOAD_BLOCK_START}\nHalf a block\n`),
66
+ `${SKILL_LOAD_BLOCK_START}\nHalf a block\n`,
67
+ );
58
68
  });
59
69
 
60
- test('a missing Codex guidance file is created holding the skill-load block and the package guidance', (context) => {
70
+ test('a missing Codex guidance file is created holding the package guidance', (context) => {
61
71
  const homes = makeHomes(context);
62
72
 
63
73
  const writtenPath = writeGuidance(homes);
64
74
 
65
75
  assert.equal(writtenPath, homes.agentsPath);
66
- assert.equal(
67
- readFileSync(homes.agentsPath, 'utf8'),
68
- `${EXPECTED_BLOCK}\n${guidanceBlock(PACKAGE_GUIDANCE)}`,
69
- );
76
+ const rendered = readFileSync(homes.agentsPath, 'utf8');
77
+ assert.equal(rendered, guidanceBlock(PACKAGE_GUIDANCE));
78
+ assert.equal(rendered.includes('pstack:poteto-mode'), false);
79
+ assert.equal(rendered.includes(SKILL_LOAD_BLOCK_START), false);
70
80
  });
71
81
 
72
- test('existing guidance keeps every line between the skill-load block and the package guidance', (context) => {
82
+ test('existing guidance keeps its lines and gains the package guidance', (context) => {
73
83
  const homes = makeHomes(context);
74
84
  mkdirSync(homes.codexHome);
75
85
  writeFileSync(homes.agentsPath, '# pstack model configuration\n\nbug-fix: gpt-6-astra\n');
76
86
 
77
87
  writeGuidance(homes);
78
88
 
89
+ const rendered = readFileSync(homes.agentsPath, 'utf8');
79
90
  assert.equal(
80
- readFileSync(homes.agentsPath, 'utf8'),
81
- `${EXPECTED_BLOCK}\n# pstack model configuration\n\nbug-fix: gpt-6-astra\n\n${guidanceBlock(PACKAGE_GUIDANCE)}`,
91
+ rendered,
92
+ `# pstack model configuration\n\nbug-fix: gpt-6-astra\n\n${guidanceBlock(PACKAGE_GUIDANCE)}`,
82
93
  );
94
+ assert.equal(rendered.includes('pstack:poteto-mode'), false);
83
95
  });
84
96
 
85
97
  test('a repeat install leaves the file unchanged and reports no write', (context) => {
@@ -93,20 +105,43 @@ test('a repeat install leaves the file unchanged and reports no write', (context
93
105
  assert.equal(readFileSync(homes.agentsPath, 'utf8'), afterFirstInstall);
94
106
  });
95
107
 
96
- test('an outdated block is replaced in place and the text around it is kept', () => {
97
- const outdated = `Intro\n${SKILL_LOAD_BLOCK_START}\nOld wording\n${SKILL_LOAD_BLOCK_END}\nOutro\n`;
108
+ test('a stale skill-load sentence is removed and question and deepen blocks stay', (context) => {
109
+ const homes = makeHomes(context);
110
+ mkdirSync(homes.codexHome);
111
+ const questionBlock = `${QUESTION_PRESENTATION_BLOCK_START}\n${QUESTION_POLICY}${QUESTION_PRESENTATION_BLOCK_END}\n`;
112
+ const deepenBlock = `${DEEPEN_GUIDANCE_BLOCK_START}\n${DEEPEN_GUIDANCE_TEXT}\n${DEEPEN_GUIDANCE_BLOCK_END}\n`;
113
+ writeFileSync(
114
+ homes.agentsPath,
115
+ `${STALE_SKILL_LOAD_BLOCK}\n${guidanceBlock('Old guidance\n')}${questionBlock}${deepenBlock}`,
116
+ );
98
117
 
99
- assert.equal(withSkillLoadBlock(outdated), `Intro\n${EXPECTED_BLOCK}Outro\n`);
118
+ writeGuidance(homes);
119
+
120
+ const rendered = readFileSync(homes.agentsPath, 'utf8');
121
+ assert.equal(rendered.includes(STALE_SKILL_LOAD_SENTENCE), false);
122
+ assert.equal(rendered.includes('pstack:poteto-mode'), false);
123
+ assert.equal(rendered.includes(SKILL_LOAD_BLOCK_START), false);
124
+ assert.ok(rendered.includes(guidanceBlock(PACKAGE_GUIDANCE)));
125
+ assert.ok(rendered.includes(questionBlock));
126
+ assert.ok(rendered.includes(deepenBlock));
100
127
  });
101
128
 
102
- test('a start marker with no end marker gets a fresh block and keeps the stray text', () => {
129
+ test('a start marker with no end marker stays and no skill name is added', (context) => {
130
+ const homes = makeHomes(context);
103
131
  const broken = `${SKILL_LOAD_BLOCK_START}\nHalf a block\n`;
132
+ mkdirSync(homes.codexHome);
133
+ writeFileSync(homes.agentsPath, broken);
134
+
135
+ writeGuidance(homes);
104
136
 
105
- assert.equal(withSkillLoadBlock(broken), `${EXPECTED_BLOCK}\n${broken}`);
137
+ const rendered = readFileSync(homes.agentsPath, 'utf8');
138
+ assert.ok(rendered.startsWith(broken));
139
+ assert.equal(rendered.includes('pstack:poteto-mode'), false);
140
+ assert.ok(rendered.includes(PACKAGE_GUIDANCE_BLOCK_START));
106
141
  });
107
142
 
108
143
  for (const linkName of ['retiredGuidancePath', 'sharedGuidancePath']) {
109
- test(`a link to package guidance at ${linkName} becomes a file with both blocks`, (context) => {
144
+ test(`a link to package guidance at ${linkName} becomes a file with the package block`, (context) => {
110
145
  const homes = makeHomes(context);
111
146
  mkdirSync(homes.codexHome);
112
147
  symlinkSync(homes[linkName], homes.agentsPath);
@@ -115,24 +150,21 @@ for (const linkName of ['retiredGuidancePath', 'sharedGuidancePath']) {
115
150
 
116
151
  assert.equal(writtenPath, homes.agentsPath);
117
152
  assert.equal(lstatSync(homes.agentsPath).isSymbolicLink(), false);
118
- assert.equal(
119
- readFileSync(homes.agentsPath, 'utf8'),
120
- `${EXPECTED_BLOCK}\n${guidanceBlock(PACKAGE_GUIDANCE)}`,
121
- );
153
+ assert.equal(readFileSync(homes.agentsPath, 'utf8'), guidanceBlock(PACKAGE_GUIDANCE));
154
+ assert.equal(readFileSync(homes.agentsPath, 'utf8').includes('pstack:poteto-mode'), false);
122
155
  });
123
156
  }
124
157
 
125
- test('a converted file takes the new package guidance on the next install', (context) => {
158
+ test('a converted file takes the new package guidance and drops a stale skill-load block', (context) => {
126
159
  const homes = makeHomes(context);
127
160
  mkdirSync(homes.codexHome);
128
- writeFileSync(homes.agentsPath, `${EXPECTED_BLOCK}\n${guidanceBlock('Old guidance\n')}`);
161
+ writeFileSync(homes.agentsPath, `${STALE_SKILL_LOAD_BLOCK}\n${guidanceBlock('Old guidance\n')}`);
129
162
 
130
163
  writeGuidance(homes);
131
164
 
132
- assert.equal(
133
- readFileSync(homes.agentsPath, 'utf8'),
134
- `${EXPECTED_BLOCK}\n${guidanceBlock(PACKAGE_GUIDANCE)}`,
135
- );
165
+ const rendered = readFileSync(homes.agentsPath, 'utf8');
166
+ assert.equal(rendered, guidanceBlock(PACKAGE_GUIDANCE));
167
+ assert.equal(rendered.includes('pstack:poteto-mode'), false);
136
168
  });
137
169
 
138
170
  test('a link to a file the package does not own is left alone', (context) => {
@@ -32,6 +32,7 @@ function installInScratchHome(homeDirectory, argumentsList = [], environment = {
32
32
  CLAUDE_CONFIG_DIR: undefined,
33
33
  LLM_SETTINGS_PROFILES_ROOT: undefined,
34
34
  CDE_INSTALL_PSTACK: '0',
35
+ CDE_INSTALL_DEEPSTACK: '0',
35
36
  CDE_INSTALL_USAGE_WRAPUP: '0',
36
37
  CDE_INSTALL_SUBAGENT_MODELS: '0',
37
38
  CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
@@ -28,6 +28,7 @@ export const EVER_SHIPPED_SKILL_NAMES = new Set([
28
28
  'descriptions',
29
29
  'e-code-review',
30
30
  'e-simplify',
31
+ 'efficient-review',
31
32
  'eli5',
32
33
  'emergencies',
33
34
  'everything-search',