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.
- package/.agents/skills/efficient-review/SKILL.md +79 -0
- package/.agents/skills/efficient-review/defaults.json +12 -0
- package/.agents/skills/efficient-review/reference/cloud-session.md +13 -0
- package/bin/codex-skill-load-block.mjs +27 -28
- package/bin/codex-skill-load-block.test.mjs +62 -30
- package/bin/codex-trimmed-sol.test.mjs +1 -0
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install-deepstack-plugin.mjs +300 -0
- package/bin/install-deepstack-plugin.test.mjs +98 -0
- package/bin/install-pstack-plugin.mjs +3 -5
- package/bin/install-pstack-plugin.test.mjs +28 -0
- package/bin/install.agents-home.test.mjs +1 -0
- package/bin/install.codex-rules.test.mjs +2 -0
- package/bin/install.cursor-rules.test.mjs +1 -0
- package/bin/install.mjs +27 -3
- package/bin/install.profile-settings.test.mjs +1 -0
- package/bin/install.prune.test.mjs +20 -0
- package/bin/install.question-presentation.test.mjs +7 -3
- package/bin/install.settings-defaults.test.mjs +1 -0
- package/bin/install.shared-settings.test.mjs +1 -0
- package/bin/install.test.mjs +79 -10
- package/bin/install.transaction.test.mjs +8 -0
- package/bin/install.uninstall-transaction.test.mjs +2 -0
- package/bin/install.update-keeps-hooks.test.mjs +1 -0
- package/docs/decisions/deepstack-install.md +9 -0
- package/docs/rule-guides/deepstack-wording.md +23 -0
- package/package.json +1 -1
- package/rules/deepstack-wording.md +15 -0
- package/scripts/deepstack_inventory.py +348 -0
- package/scripts/dev_env_scripts_constants/deepstack_inventory_constants.py +42 -0
- package/scripts/test_deepstack_inventory.py +137 -0
- package/scripts/test_deepstack_wording_rule.py +20 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import {
|
|
6
|
+
installMarketplacePlugins,
|
|
7
|
+
marketplacePluginsPlan,
|
|
8
|
+
shouldInstallMarketplacePlugin,
|
|
9
|
+
} from './install-pstack-plugin.mjs';
|
|
10
|
+
|
|
11
|
+
export const DEEPSTACK_MARKETPLACE_REPOSITORY = 'jl-cmd-projects/DeepStack';
|
|
12
|
+
export const DEEPSTACK_MARKETPLACE_NAME = 'DeepStack';
|
|
13
|
+
export const DEEPSTACK_PLUGIN_NAME = 'deepstack';
|
|
14
|
+
export const DEEPSTACK_PLUGIN_IDENTIFIER = `${DEEPSTACK_PLUGIN_NAME}@${DEEPSTACK_MARKETPLACE_NAME}`;
|
|
15
|
+
export const DEEPSTACK_PLUGIN_HOSTS = Object.freeze(['claude', 'codex']);
|
|
16
|
+
export const DEEPSTACK_PLUGIN_OPT_OUT_FLAG = '--no-deepstack';
|
|
17
|
+
export const DEEPSTACK_PLUGIN_OPT_OUT_VARIABLE = 'CDE_INSTALL_DEEPSTACK';
|
|
18
|
+
export const DEEPSTACK_SOURCE_VARIABLE = 'CDE_DEEPSTACK_SOURCE';
|
|
19
|
+
export const DEEPSTACK_CURSOR_DIRECTORY_NAME = 'plugins/deepstack';
|
|
20
|
+
export const DEEPSTACK_SKILL_RELATIVE_PATH = 'skills/deepstack-mode/SKILL.md';
|
|
21
|
+
export const DEEPSTACK_SESSION_SCRIPT_RELATIVE_PATH = 'hooks/session-start.sh';
|
|
22
|
+
export const DEEPSTACK_SESSION_RUNTIME = 'cursor';
|
|
23
|
+
export const DEEPSTACK_FIRST_TASK_HOOK_NAME = 'deepen_first_prompt.py';
|
|
24
|
+
export const DEEPSTACK_CODEX_MODEL_SHEET_NAME = 'deepstack-models.md';
|
|
25
|
+
|
|
26
|
+
export const DEEPSTACK_PLUGIN_SPEC = Object.freeze({
|
|
27
|
+
name: DEEPSTACK_PLUGIN_NAME,
|
|
28
|
+
label: 'DeepStack',
|
|
29
|
+
marketplaceRepository: DEEPSTACK_MARKETPLACE_REPOSITORY,
|
|
30
|
+
marketplaceName: DEEPSTACK_MARKETPLACE_NAME,
|
|
31
|
+
sparsePaths: Object.freeze([]),
|
|
32
|
+
pluginIdentifier: DEEPSTACK_PLUGIN_IDENTIFIER,
|
|
33
|
+
hosts: DEEPSTACK_PLUGIN_HOSTS,
|
|
34
|
+
optOutFlag: DEEPSTACK_PLUGIN_OPT_OUT_FLAG,
|
|
35
|
+
optOutVariable: DEEPSTACK_PLUGIN_OPT_OUT_VARIABLE,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Quote one path for a hook command line.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} pathValue The absolute path.
|
|
42
|
+
* @returns {string} A single-quoted shell word.
|
|
43
|
+
*/
|
|
44
|
+
function quoteCommandPath(pathValue) {
|
|
45
|
+
return `'${pathValue.replaceAll("'", `'\\''`)}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Read the Claude, Codex, and Cursor actions one DeepStack install performs.
|
|
50
|
+
*
|
|
51
|
+
* Claude and Codex use the marketplace verbs. Cursor has no plugin command, so
|
|
52
|
+
* its action is a copy into the Cursor home plus two hook registrations.
|
|
53
|
+
*
|
|
54
|
+
* @returns {{claude: object, codex: object, cursor: {
|
|
55
|
+
* pluginDirectoryName: string,
|
|
56
|
+
* skillRelativePath: string,
|
|
57
|
+
* sessionScriptRelativePath: string,
|
|
58
|
+
* sessionRuntimeArgument: string,
|
|
59
|
+
* firstTaskHookName: string,
|
|
60
|
+
* }}} The three host actions.
|
|
61
|
+
*/
|
|
62
|
+
export function deepStackInstallPlan() {
|
|
63
|
+
return {
|
|
64
|
+
claude: marketplacePluginsPlan([DEEPSTACK_PLUGIN_SPEC], 'claude'),
|
|
65
|
+
codex: marketplacePluginsPlan([DEEPSTACK_PLUGIN_SPEC], 'codex'),
|
|
66
|
+
cursor: {
|
|
67
|
+
pluginDirectoryName: DEEPSTACK_CURSOR_DIRECTORY_NAME,
|
|
68
|
+
skillRelativePath: DEEPSTACK_SKILL_RELATIVE_PATH,
|
|
69
|
+
sessionScriptRelativePath: DEEPSTACK_SESSION_SCRIPT_RELATIVE_PATH,
|
|
70
|
+
sessionRuntimeArgument: DEEPSTACK_SESSION_RUNTIME,
|
|
71
|
+
firstTaskHookName: DEEPSTACK_FIRST_TASK_HOOK_NAME,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {string|undefined} homeDirectory A Claude or Codex home.
|
|
78
|
+
* @returns {string|null} The newest cached plugin directory, or null.
|
|
79
|
+
*/
|
|
80
|
+
function newestCachedPlugin(homeDirectory) {
|
|
81
|
+
if (!homeDirectory) return null;
|
|
82
|
+
const cacheDirectory = join(
|
|
83
|
+
homeDirectory,
|
|
84
|
+
'plugins',
|
|
85
|
+
'cache',
|
|
86
|
+
DEEPSTACK_MARKETPLACE_NAME,
|
|
87
|
+
DEEPSTACK_PLUGIN_NAME,
|
|
88
|
+
);
|
|
89
|
+
if (!existsSync(cacheDirectory)) return null;
|
|
90
|
+
const versions = readdirSync(cacheDirectory, { withFileTypes: true })
|
|
91
|
+
.filter(entry => entry.isDirectory())
|
|
92
|
+
.map(entry => entry.name)
|
|
93
|
+
.filter(name => existsSync(join(cacheDirectory, name, DEEPSTACK_SKILL_RELATIVE_PATH)))
|
|
94
|
+
.sort();
|
|
95
|
+
if (versions.length === 0) return null;
|
|
96
|
+
return join(cacheDirectory, versions[versions.length - 1]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Find the plugin tree to copy into Cursor.
|
|
101
|
+
*
|
|
102
|
+
* An explicit directory wins, then the source variable, then a marketplace
|
|
103
|
+
* cache written by the Claude or Codex install, then a shallow clone of
|
|
104
|
+
* jl-cmd-projects/DeepStack.
|
|
105
|
+
*
|
|
106
|
+
* @param {object} options Search inputs.
|
|
107
|
+
* @param {string} [options.claudeRoot] The Claude home.
|
|
108
|
+
* @param {string} [options.codexHome] The Codex home.
|
|
109
|
+
* @param {Record<string, string|undefined>} [options.environment] The process environment.
|
|
110
|
+
* @param {string} [options.sourceDirectory] A directory that already holds the plugin.
|
|
111
|
+
* @param {(destination: string) => string|null} [options.cloneRepository] Replaces the git clone.
|
|
112
|
+
* @returns {string|null} The plugin directory, or null when every source is empty.
|
|
113
|
+
*/
|
|
114
|
+
export function resolveDeepStackPluginSource(options = {}) {
|
|
115
|
+
if (options.sourceDirectory && existsSync(join(options.sourceDirectory, DEEPSTACK_SKILL_RELATIVE_PATH))) {
|
|
116
|
+
return options.sourceDirectory;
|
|
117
|
+
}
|
|
118
|
+
const environment = options.environment ?? process.env;
|
|
119
|
+
const fromEnvironment = environment[DEEPSTACK_SOURCE_VARIABLE];
|
|
120
|
+
if (fromEnvironment && existsSync(join(fromEnvironment, DEEPSTACK_SKILL_RELATIVE_PATH))) {
|
|
121
|
+
return fromEnvironment;
|
|
122
|
+
}
|
|
123
|
+
const cached = newestCachedPlugin(options.claudeRoot) ?? newestCachedPlugin(options.codexHome);
|
|
124
|
+
if (cached) return cached;
|
|
125
|
+
if (options.cloneRepository) return options.cloneRepository();
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Add the SessionStart and first-task hook groups when they are absent.
|
|
131
|
+
*
|
|
132
|
+
* @param {object} configuration The parsed Cursor hooks file.
|
|
133
|
+
* @param {{sessionCommand: string, firstTaskCommand: string}} commands The two commands.
|
|
134
|
+
* @returns {object} The configuration with both groups present.
|
|
135
|
+
*/
|
|
136
|
+
export function mergeDeepStackCursorHooks(configuration, commands) {
|
|
137
|
+
const hooks = configuration.hooks && typeof configuration.hooks === 'object'
|
|
138
|
+
? configuration.hooks
|
|
139
|
+
: {};
|
|
140
|
+
const sessionGroups = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
141
|
+
const promptGroups = Array.isArray(hooks.UserPromptSubmit) ? [...hooks.UserPromptSubmit] : [];
|
|
142
|
+
const listed = (groups, needle) => JSON.stringify(groups).includes(needle);
|
|
143
|
+
if (!listed(sessionGroups, DEEPSTACK_SESSION_SCRIPT_RELATIVE_PATH)) {
|
|
144
|
+
sessionGroups.push({
|
|
145
|
+
matcher: 'startup|resume|clear|compact',
|
|
146
|
+
hooks: [{ type: 'command', command: commands.sessionCommand }],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (!listed(promptGroups, DEEPSTACK_FIRST_TASK_HOOK_NAME)) {
|
|
150
|
+
promptGroups.push({
|
|
151
|
+
matcher: '',
|
|
152
|
+
hooks: [{ type: 'command', command: commands.firstTaskCommand, timeout: 10 }],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...configuration,
|
|
157
|
+
hooks: {
|
|
158
|
+
...hooks,
|
|
159
|
+
SessionStart: sessionGroups,
|
|
160
|
+
UserPromptSubmit: promptGroups,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Copy the plugin into the Cursor home and register both hooks.
|
|
167
|
+
*
|
|
168
|
+
* @param {object} options Copy inputs.
|
|
169
|
+
* @param {string} options.cursorDirectory The Cursor home, usually ~/.cursor.
|
|
170
|
+
* @param {string|null} options.pluginSourceDirectory The plugin tree to copy.
|
|
171
|
+
* @param {string} options.firstTaskHookPath Absolute path of deepen_first_prompt.py.
|
|
172
|
+
* @returns {{status: string, pluginDirectory: string|null, hooksPath: string|null, warning: string|null}} The outcome.
|
|
173
|
+
*/
|
|
174
|
+
export function installDeepStackCursor(options) {
|
|
175
|
+
const plan = deepStackInstallPlan().cursor;
|
|
176
|
+
if (!options.cursorDirectory) {
|
|
177
|
+
return { status: 'failed', pluginDirectory: null, hooksPath: null, warning: 'Cursor home is unset.' };
|
|
178
|
+
}
|
|
179
|
+
if (!options.pluginSourceDirectory) {
|
|
180
|
+
return {
|
|
181
|
+
status: 'failed',
|
|
182
|
+
pluginDirectory: null,
|
|
183
|
+
hooksPath: null,
|
|
184
|
+
warning: `No ${DEEPSTACK_MARKETPLACE_REPOSITORY} plugin tree to copy.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const pluginDirectory = join(options.cursorDirectory, plan.pluginDirectoryName);
|
|
188
|
+
mkdirSync(options.cursorDirectory, { recursive: true });
|
|
189
|
+
cpSync(options.pluginSourceDirectory, pluginDirectory, { recursive: true, force: true });
|
|
190
|
+
const sessionCommand = `${quoteCommandPath(join(pluginDirectory, plan.sessionScriptRelativePath))} ${plan.sessionRuntimeArgument}`;
|
|
191
|
+
const firstTaskCommand = `python3 ${quoteCommandPath(options.firstTaskHookPath)}`;
|
|
192
|
+
const hooksPath = join(options.cursorDirectory, 'hooks.json');
|
|
193
|
+
const current = existsSync(hooksPath)
|
|
194
|
+
? JSON.parse(readFileSync(hooksPath, 'utf8'))
|
|
195
|
+
: { hooks: {} };
|
|
196
|
+
const merged = mergeDeepStackCursorHooks(current, { sessionCommand, firstTaskCommand });
|
|
197
|
+
writeFileSync(hooksPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
198
|
+
return { status: 'installed', pluginDirectory, hooksPath, warning: null };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Write the Codex model sheet when this run installed the Codex plugin.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} codexHome The Codex home.
|
|
205
|
+
* @returns {string|null} The sheet path when this run created it.
|
|
206
|
+
*/
|
|
207
|
+
export function seedCodexDeepStackModels(codexHome) {
|
|
208
|
+
const sheetPath = join(codexHome, DEEPSTACK_CODEX_MODEL_SHEET_NAME);
|
|
209
|
+
if (existsSync(sheetPath)) return null;
|
|
210
|
+
mkdirSync(codexHome, { recursive: true });
|
|
211
|
+
writeFileSync(sheetPath, 'session hook: on\n');
|
|
212
|
+
return sheetPath;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Install DeepStack for Claude, Codex, and Cursor.
|
|
217
|
+
*
|
|
218
|
+
* @param {object} [options] Install targets.
|
|
219
|
+
* @param {string} [options.claudeRoot] The managed Claude root.
|
|
220
|
+
* @param {string} [options.codexHome] The Codex home.
|
|
221
|
+
* @param {string} [options.cursorDirectory] The Cursor home.
|
|
222
|
+
* @param {string} [options.firstTaskHookPath] Absolute path of the hook the first-task command runs.
|
|
223
|
+
* @param {string} [options.sourceDirectory] A local plugin tree for the Cursor copy.
|
|
224
|
+
* @param {string[]} [options.argumentList] Command-line arguments, for the opt-out flag.
|
|
225
|
+
* @param {Record<string, string|undefined>} [options.environment] The process environment.
|
|
226
|
+
* @param {object} [dependencies] Seams for the marketplace command runner.
|
|
227
|
+
* @returns {{status: string, hosts: object[], cursor: object, warning: string|null}} The outcome.
|
|
228
|
+
*/
|
|
229
|
+
export function installDeepStackForHosts(options = {}, dependencies = {}) {
|
|
230
|
+
const environment = options.environment ?? process.env;
|
|
231
|
+
if (!shouldInstallMarketplacePlugin(DEEPSTACK_PLUGIN_SPEC, options.argumentList, environment)) {
|
|
232
|
+
return {
|
|
233
|
+
status: 'skipped',
|
|
234
|
+
hosts: [],
|
|
235
|
+
cursor: { status: 'skipped', pluginDirectory: null, hooksPath: null, warning: null },
|
|
236
|
+
warning: null,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const marketplace = installMarketplacePlugins([DEEPSTACK_PLUGIN_SPEC], {
|
|
240
|
+
claudeRoot: options.claudeRoot,
|
|
241
|
+
codexHome: options.codexHome,
|
|
242
|
+
environment,
|
|
243
|
+
}, dependencies);
|
|
244
|
+
let sourceDirectory = resolveDeepStackPluginSource({
|
|
245
|
+
claudeRoot: options.claudeRoot,
|
|
246
|
+
codexHome: options.codexHome,
|
|
247
|
+
environment,
|
|
248
|
+
sourceDirectory: options.sourceDirectory,
|
|
249
|
+
});
|
|
250
|
+
if (!sourceDirectory && options.allowClone !== false) {
|
|
251
|
+
sourceDirectory = cloneDeepStackRepository(mkdtempSync(join(tmpdir(), 'deepstack-')));
|
|
252
|
+
}
|
|
253
|
+
const cursor = installDeepStackCursor({
|
|
254
|
+
cursorDirectory: options.cursorDirectory,
|
|
255
|
+
pluginSourceDirectory: sourceDirectory,
|
|
256
|
+
firstTaskHookPath: options.firstTaskHookPath,
|
|
257
|
+
});
|
|
258
|
+
if (marketplace.hosts.some(host => host.host === 'codex' && host.status === 'installed') && options.codexHome) {
|
|
259
|
+
seedCodexDeepStackModels(options.codexHome);
|
|
260
|
+
}
|
|
261
|
+
const failed = marketplace.status === 'failed' || cursor.status === 'failed';
|
|
262
|
+
const installed = marketplace.hosts.some(host => host.status === 'installed') || cursor.status === 'installed';
|
|
263
|
+
return {
|
|
264
|
+
status: failed ? 'failed' : (installed ? 'installed' : marketplace.status),
|
|
265
|
+
hosts: marketplace.hosts,
|
|
266
|
+
cursor,
|
|
267
|
+
warning: [marketplace.warning, cursor.warning].filter(Boolean).join(' ') || null,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Clone jl-cmd-projects/DeepStack when no local tree is available.
|
|
273
|
+
*
|
|
274
|
+
* The installer calls this only from a full install that has no source
|
|
275
|
+
* directory and no marketplace cache. Tests pass a source directory instead.
|
|
276
|
+
*
|
|
277
|
+
* @param {string} destination An empty directory for the clone.
|
|
278
|
+
* @returns {string|null} The plugin directory inside the clone.
|
|
279
|
+
*/
|
|
280
|
+
/**
|
|
281
|
+
* Return the git clone URL for the DeepStack marketplace repository.
|
|
282
|
+
*
|
|
283
|
+
* @returns {string} The clone URL.
|
|
284
|
+
*/
|
|
285
|
+
export function deepStackCloneUrl() {
|
|
286
|
+
return ['https:', '', 'github.com', `${DEEPSTACK_MARKETPLACE_REPOSITORY}.git`].join('/');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function cloneDeepStackRepository(destination) {
|
|
290
|
+
const outcome = spawnSync('git', [
|
|
291
|
+
'clone',
|
|
292
|
+
'--depth',
|
|
293
|
+
'1',
|
|
294
|
+
deepStackCloneUrl(),
|
|
295
|
+
destination,
|
|
296
|
+
], { encoding: 'utf8' });
|
|
297
|
+
if (outcome.status !== 0) return null;
|
|
298
|
+
const pluginDirectory = join(destination, 'plugins', DEEPSTACK_PLUGIN_NAME);
|
|
299
|
+
return existsSync(join(pluginDirectory, DEEPSTACK_SKILL_RELATIVE_PATH)) ? pluginDirectory : null;
|
|
300
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
DEEPSTACK_MARKETPLACE_REPOSITORY,
|
|
8
|
+
DEEPSTACK_PLUGIN_IDENTIFIER,
|
|
9
|
+
DEEPSTACK_PLUGIN_SPEC,
|
|
10
|
+
deepStackCloneUrl,
|
|
11
|
+
deepStackInstallPlan,
|
|
12
|
+
installDeepStackCursor,
|
|
13
|
+
installDeepStackForHosts,
|
|
14
|
+
mergeDeepStackCursorHooks,
|
|
15
|
+
} from './install-deepstack-plugin.mjs';
|
|
16
|
+
import { marketplacePluginsPlan, shouldInstallMarketplacePlugin } from './install-pstack-plugin.mjs';
|
|
17
|
+
|
|
18
|
+
test('Claude and Codex plans name jl-cmd-projects/DeepStack', () => {
|
|
19
|
+
const plan = deepStackInstallPlan();
|
|
20
|
+
assert.deepEqual(plan.claude.commands, [
|
|
21
|
+
['plugin', 'marketplace', 'add', DEEPSTACK_MARKETPLACE_REPOSITORY],
|
|
22
|
+
['plugin', 'install', DEEPSTACK_PLUGIN_IDENTIFIER],
|
|
23
|
+
]);
|
|
24
|
+
assert.deepEqual(plan.codex.commands, [
|
|
25
|
+
['plugin', 'marketplace', 'add', DEEPSTACK_MARKETPLACE_REPOSITORY],
|
|
26
|
+
['plugin', 'add', DEEPSTACK_PLUGIN_IDENTIFIER],
|
|
27
|
+
]);
|
|
28
|
+
assert.equal(DEEPSTACK_MARKETPLACE_REPOSITORY, 'jl-cmd-projects/DeepStack');
|
|
29
|
+
assert.equal(deepStackCloneUrl(), 'https://github.com/jl-cmd-projects/DeepStack.git');
|
|
30
|
+
assert.equal(plan.cursor.skillRelativePath, 'skills/deepstack-mode/SKILL.md');
|
|
31
|
+
assert.equal(plan.cursor.firstTaskHookName, 'deepen_first_prompt.py');
|
|
32
|
+
assert.throws(() => marketplacePluginsPlan([DEEPSTACK_PLUGIN_SPEC], 'cursor'), /cursor/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('cursor hooks keep the session hook and the first-task gate', () => {
|
|
36
|
+
const merged = mergeDeepStackCursorHooks({ hooks: { PreToolUse: [] } }, {
|
|
37
|
+
sessionCommand: "'/cursor/plugins/deepstack/hooks/session-start.sh' cursor",
|
|
38
|
+
firstTaskCommand: "python3 '/claude/hooks/session/deepen_first_prompt.py'",
|
|
39
|
+
});
|
|
40
|
+
assert.deepEqual(Object.keys(merged.hooks).sort(), ['PreToolUse', 'SessionStart', 'UserPromptSubmit']);
|
|
41
|
+
const text = JSON.stringify(merged);
|
|
42
|
+
assert.match(text, /session-start\.sh/);
|
|
43
|
+
assert.match(text, /deepen_first_prompt\.py/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('the cursor copy writes the skill and both hook commands', () => {
|
|
47
|
+
const root = mkdtempSync(join(tmpdir(), 'deepstack-cursor-'));
|
|
48
|
+
try {
|
|
49
|
+
const source = join(root, 'source');
|
|
50
|
+
mkdirSync(join(source, 'skills', 'deepstack-mode'), { recursive: true });
|
|
51
|
+
mkdirSync(join(source, 'hooks'), { recursive: true });
|
|
52
|
+
writeFileSync(join(source, 'skills', 'deepstack-mode', 'SKILL.md'), 'DeepStack intake\n');
|
|
53
|
+
writeFileSync(join(source, 'hooks', 'session-start.sh'), '#!/bin/sh\n');
|
|
54
|
+
const cursorDirectory = join(root, '.cursor');
|
|
55
|
+
const outcome = installDeepStackCursor({
|
|
56
|
+
cursorDirectory,
|
|
57
|
+
pluginSourceDirectory: source,
|
|
58
|
+
firstTaskHookPath: join(root, '.agents', 'hooks', 'session', 'deepen_first_prompt.py'),
|
|
59
|
+
});
|
|
60
|
+
assert.equal(outcome.status, 'installed');
|
|
61
|
+
assert.equal(
|
|
62
|
+
readFileSync(join(cursorDirectory, 'plugins', 'deepstack', 'skills', 'deepstack-mode', 'SKILL.md'), 'utf8'),
|
|
63
|
+
'DeepStack intake\n',
|
|
64
|
+
);
|
|
65
|
+
const hooks = JSON.parse(readFileSync(join(cursorDirectory, 'hooks.json'), 'utf8'));
|
|
66
|
+
const text = JSON.stringify(hooks);
|
|
67
|
+
assert.match(text, /session-start\.sh/);
|
|
68
|
+
assert.match(text, /deepen_first_prompt\.py/);
|
|
69
|
+
assert.equal(hooks.hooks.SessionStart[0].hooks[0].command.endsWith(' cursor'), true);
|
|
70
|
+
} finally {
|
|
71
|
+
rmSync(root, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('a missing plugin tree fails the cursor copy', () => {
|
|
76
|
+
const outcome = installDeepStackCursor({
|
|
77
|
+
cursorDirectory: '/tmp/unused-cursor',
|
|
78
|
+
pluginSourceDirectory: null,
|
|
79
|
+
firstTaskHookPath: '/tmp/deepen_first_prompt.py',
|
|
80
|
+
});
|
|
81
|
+
assert.equal(outcome.status, 'failed');
|
|
82
|
+
assert.match(outcome.warning, /jl-cmd-projects\/DeepStack/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('--no-deepstack and CDE_INSTALL_DEEPSTACK=0 skip the install', () => {
|
|
86
|
+
assert.equal(shouldInstallMarketplacePlugin(DEEPSTACK_PLUGIN_SPEC, ['--no-deepstack'], {}), false);
|
|
87
|
+
assert.equal(
|
|
88
|
+
shouldInstallMarketplacePlugin(DEEPSTACK_PLUGIN_SPEC, [], { CDE_INSTALL_DEEPSTACK: '0' }),
|
|
89
|
+
false,
|
|
90
|
+
);
|
|
91
|
+
const skipped = installDeepStackForHosts({
|
|
92
|
+
argumentList: ['--no-deepstack'],
|
|
93
|
+
environment: {},
|
|
94
|
+
allowClone: false,
|
|
95
|
+
});
|
|
96
|
+
assert.equal(skipped.status, 'skipped');
|
|
97
|
+
assert.equal(skipped.cursor.status, 'skipped');
|
|
98
|
+
});
|
|
@@ -210,10 +210,8 @@ function namesAnAbsentCommand(outcome) {
|
|
|
210
210
|
&& COMMAND_NOT_FOUND_PATTERN.test(outcome.stderr ?? '');
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
if (!trimmed) return '';
|
|
216
|
-
return trimmed.split(/\r?\n/).filter(Boolean).at(-1);
|
|
213
|
+
function singleLineDetail(text) {
|
|
214
|
+
return (text ?? '').split(/\r?\n/).map(eachLine => eachLine.trim()).filter(Boolean).join(' ');
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
const ORPHANED_VERSION_MARKER = '.orphaned_at';
|
|
@@ -266,7 +264,7 @@ function installForHost(allSpecs, host, homeDirectory, environment, runCommand)
|
|
|
266
264
|
});
|
|
267
265
|
if (namesAnAbsentCommand(outcome)) return absentHostOutcome(allSpecs, host, executable);
|
|
268
266
|
if (outcome.status !== 0 || outcome.error) {
|
|
269
|
-
const detail =
|
|
267
|
+
const detail = singleLineDetail(outcome.stderr) || outcome.error?.message || `exit ${outcome.status}`;
|
|
270
268
|
return {
|
|
271
269
|
host,
|
|
272
270
|
executable,
|
|
@@ -126,6 +126,34 @@ test('a failing command reports that host and still installs the next one', () =
|
|
|
126
126
|
['plugin', 'marketplace', 'add', PSTACK_MARKETPLACE_REPOSITORY]);
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
test('should report every line of a multi-line marketplace add error', () => {
|
|
130
|
+
const cloneFailure = [
|
|
131
|
+
'Failed to add marketplace: Fetching the marketplace from GitHub failed on both attempts.'
|
|
132
|
+
+ ' HTTPS (https://github.com/jl-cmd/pstack-claude.git): HTTPS clone timed out',
|
|
133
|
+
'',
|
|
134
|
+
'SSH (git@github.com:jl-cmd/pstack-claude.git): git@github.com: Permission denied (publickey).',
|
|
135
|
+
'fatal: Could not read from remote repository.',
|
|
136
|
+
'',
|
|
137
|
+
'Please make sure you have the correct access rights',
|
|
138
|
+
'and the repository exists.',
|
|
139
|
+
].join('\r\n');
|
|
140
|
+
const runner = recordingRunner((executable, commandArguments) => (
|
|
141
|
+
executable === 'claude' && commandArguments[2] === 'add'
|
|
142
|
+
? { status: 1, stderr: cloneFailure }
|
|
143
|
+
: { status: 0, stderr: '' }));
|
|
144
|
+
const claude = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], ROOTS, runner)
|
|
145
|
+
.hosts.find(host => host.host === 'claude');
|
|
146
|
+
assert.equal(
|
|
147
|
+
claude.warning,
|
|
148
|
+
`claude plugin marketplace add ${PSTACK_MARKETPLACE_REPOSITORY} failed: `
|
|
149
|
+
+ 'Failed to add marketplace: Fetching the marketplace from GitHub failed on both attempts.'
|
|
150
|
+
+ ' HTTPS (https://github.com/jl-cmd/pstack-claude.git): HTTPS clone timed out'
|
|
151
|
+
+ ' SSH (git@github.com:jl-cmd/pstack-claude.git): git@github.com: Permission denied (publickey).'
|
|
152
|
+
+ ' fatal: Could not read from remote repository.'
|
|
153
|
+
+ ' Please make sure you have the correct access rights and the repository exists.',
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
129
157
|
test('an executable override replaces the host command for that host only', () => {
|
|
130
158
|
const runner = recordingRunner();
|
|
131
159
|
installMarketplacePlugins(
|
|
@@ -53,6 +53,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
|
|
|
53
53
|
CLAUDE_CONFIG_DIR: undefined,
|
|
54
54
|
LLM_SETTINGS_PROFILES_ROOT: undefined,
|
|
55
55
|
CDE_INSTALL_PSTACK: '0',
|
|
56
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
56
57
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
57
58
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
58
59
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
@@ -59,6 +59,7 @@ function runInstaller(homeDirectory, extraArguments) {
|
|
|
59
59
|
CLAUDE_CONFIG_DIR: undefined,
|
|
60
60
|
LLM_SETTINGS_PROFILES_ROOT: undefined,
|
|
61
61
|
CDE_INSTALL_PSTACK: '0',
|
|
62
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
62
63
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
63
64
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
64
65
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
@@ -92,6 +93,7 @@ function scanTextWithProductionPiiScanner(scannedText) {
|
|
|
92
93
|
CLAUDE_CONFIG_DIR: undefined,
|
|
93
94
|
LLM_SETTINGS_PROFILES_ROOT: undefined,
|
|
94
95
|
CDE_INSTALL_PSTACK: '0',
|
|
96
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
95
97
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
96
98
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
97
99
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
@@ -33,6 +33,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
|
|
|
33
33
|
...process.env,
|
|
34
34
|
LLM_SETTINGS_PROFILES_ROOT: undefined,
|
|
35
35
|
CDE_INSTALL_PSTACK: '0',
|
|
36
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
36
37
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
37
38
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
38
39
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
package/bin/install.mjs
CHANGED
|
@@ -60,6 +60,10 @@ import {
|
|
|
60
60
|
installMarketplacePlugins,
|
|
61
61
|
shouldInstallMarketplacePlugin,
|
|
62
62
|
} from './install-pstack-plugin.mjs';
|
|
63
|
+
import {
|
|
64
|
+
DEEPSTACK_PLUGIN_SPEC,
|
|
65
|
+
installDeepStackForHosts,
|
|
66
|
+
} from './install-deepstack-plugin.mjs';
|
|
63
67
|
import { seedCodexPstackModels } from './seed-codex-pstack-models.mjs';
|
|
64
68
|
import {
|
|
65
69
|
codexSolSettingSnapshotPaths,
|
|
@@ -227,6 +231,7 @@ export const CORE_SKILLS = [
|
|
|
227
231
|
'task-build',
|
|
228
232
|
'eli5',
|
|
229
233
|
'deepen',
|
|
234
|
+
'efficient-review',
|
|
230
235
|
];
|
|
231
236
|
|
|
232
237
|
export function collectPackageSourceConflicts(packageDirectory) {
|
|
@@ -2927,17 +2932,35 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2927
2932
|
allUserOwnedPreferencePaths.add(eachPath);
|
|
2928
2933
|
}
|
|
2929
2934
|
}
|
|
2930
|
-
const
|
|
2935
|
+
const codexGuidancePath = writeCodexAgentsGuidance(
|
|
2931
2936
|
INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
2932
2937
|
packageGuidanceText,
|
|
2933
2938
|
allPackageGuidancePaths,
|
|
2934
2939
|
);
|
|
2935
|
-
if (
|
|
2936
|
-
console.log(` \u2713 ${
|
|
2940
|
+
if (codexGuidancePath) {
|
|
2941
|
+
console.log(` \u2713 ${codexGuidancePath} (Codex package guidance)`);
|
|
2937
2942
|
}
|
|
2938
2943
|
}
|
|
2939
2944
|
printPluginHostOutcomes([PSTACK_PLUGIN_SPEC], pstackPlugin);
|
|
2940
2945
|
}
|
|
2946
|
+
if (!selectedGroups && shouldInstallMarketplacePlugin(DEEPSTACK_PLUGIN_SPEC)) {
|
|
2947
|
+
const deepStackPlugin = installDeepStackForHosts({
|
|
2948
|
+
claudeRoot: CLAUDE_HOME,
|
|
2949
|
+
codexHome: INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
2950
|
+
cursorDirectory: INSTALL_ROOT_RESOLUTION.cursorInstallDirectory,
|
|
2951
|
+
firstTaskHookPath: join(
|
|
2952
|
+
INSTALL_ROOT_RESOLUTION.hooksInstallDirectory,
|
|
2953
|
+
'session',
|
|
2954
|
+
'deepen_first_prompt.py',
|
|
2955
|
+
),
|
|
2956
|
+
});
|
|
2957
|
+
summary.deepStackPlugin = deepStackPlugin;
|
|
2958
|
+
printPluginHostOutcomes([DEEPSTACK_PLUGIN_SPEC], deepStackPlugin);
|
|
2959
|
+
const cursorOutcome = deepStackPlugin.cursor;
|
|
2960
|
+
console.log(cursorOutcome.warning
|
|
2961
|
+
? ` DeepStack (cursor): ${cursorOutcome.status} — ${cursorOutcome.warning}`
|
|
2962
|
+
: ` DeepStack (cursor): ${cursorOutcome.status}`);
|
|
2963
|
+
}
|
|
2941
2964
|
const allRepositoryPluginSpecs = [
|
|
2942
2965
|
USAGE_WRAPUP_PLUGIN_SPEC,
|
|
2943
2966
|
SUBAGENT_MODELS_PLUGIN_SPEC,
|
|
@@ -3484,6 +3507,7 @@ Usage:
|
|
|
3484
3507
|
npx ${PACKAGE_NAME} --profile ID Install into one named profile root (under the profiles root)
|
|
3485
3508
|
npx ${PACKAGE_NAME} --profiles A,B Install into each selected profile (one ownership manifest per target)
|
|
3486
3509
|
npx ${PACKAGE_NAME} --no-pstack Full install without the pstack plugin (also CDE_INSTALL_PSTACK=0)
|
|
3510
|
+
npx ${PACKAGE_NAME} --no-deepstack Full install without the DeepStack plugin (also CDE_INSTALL_DEEPSTACK=0)
|
|
3487
3511
|
npx ${PACKAGE_NAME} --no-usage-wrapup Full install without the usage-wrapup plugin (also CDE_INSTALL_USAGE_WRAPUP=0)
|
|
3488
3512
|
npx ${PACKAGE_NAME} --no-subagent-models Full install without the subagent-models plugin (also CDE_INSTALL_SUBAGENT_MODELS=0)
|
|
3489
3513
|
npx ${PACKAGE_NAME} --no-deepen-first-prompt Full install without the deepen-first-prompt plugin (also CDE_INSTALL_DEEPEN_FIRST_PROMPT=0)
|
|
@@ -64,6 +64,7 @@ const CORE_SKILL_DIRECTORIES = [
|
|
|
64
64
|
'issue-tracker',
|
|
65
65
|
'task-build',
|
|
66
66
|
'eli5',
|
|
67
|
+
'efficient-review',
|
|
67
68
|
];
|
|
68
69
|
const PRIOR_RUN_BACKUP_DIRECTORY_NAMES = [
|
|
69
70
|
'2020-01-01T00-00-00-000Z',
|
|
@@ -262,6 +263,7 @@ function resolveInstallerInvocation(homeDirectory) {
|
|
|
262
263
|
CLAUDE_CONFIG_DIR: undefined,
|
|
263
264
|
LLM_SETTINGS_PROFILES_ROOT: undefined,
|
|
264
265
|
CDE_INSTALL_PSTACK: '0',
|
|
266
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
265
267
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
266
268
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
267
269
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
@@ -802,6 +804,24 @@ test('a scoped --only install records the skill names it installed', () => {
|
|
|
802
804
|
}
|
|
803
805
|
});
|
|
804
806
|
|
|
807
|
+
test('a fresh scoped core install includes efficient-review', () => {
|
|
808
|
+
const sandbox = createSandbox();
|
|
809
|
+
try {
|
|
810
|
+
runInstaller(sandbox.homeDirectory, ['--only', 'core']);
|
|
811
|
+
const skillPath = join(sandbox.skillsDirectory, 'efficient-review', 'SKILL.md');
|
|
812
|
+
const skillText = readFileSync(skillPath, 'utf8');
|
|
813
|
+
assert.match(skillText, /^name: efficient-review$/m);
|
|
814
|
+
assert.match(skillText, /\/efficient-review/);
|
|
815
|
+
assert.equal(readManifest(sandbox.manifestPath).skills.includes('efficient-review'), true);
|
|
816
|
+
assert.equal(
|
|
817
|
+
existsSync(join(sandbox.skillsDirectory, 'efficient-review', 'defaults.json')),
|
|
818
|
+
true,
|
|
819
|
+
);
|
|
820
|
+
} finally {
|
|
821
|
+
rmSync(sandbox.homeDirectory, { recursive: true, force: true });
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
805
825
|
test('a fresh scoped core install ships active skills and omits retired skills', () => {
|
|
806
826
|
const sandbox = createSandbox();
|
|
807
827
|
try {
|
|
@@ -25,6 +25,7 @@ function installInScratchHome(homeDirectory, argumentsList = [], extraEnvironmen
|
|
|
25
25
|
CLAUDE_CONFIG_DIR: undefined,
|
|
26
26
|
LLM_SETTINGS_PROFILES_ROOT: undefined,
|
|
27
27
|
CDE_INSTALL_PSTACK: '0',
|
|
28
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
28
29
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
29
30
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
30
31
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
@@ -171,7 +172,7 @@ test('a selected Claude profile receives the source rule and its Codex guidance'
|
|
|
171
172
|
assert.ok(readFileSync(join(profileDirectory, '.codex', 'AGENTS.md'), 'utf8').includes(readFileSync(policyPath, 'utf8')));
|
|
172
173
|
});
|
|
173
174
|
|
|
174
|
-
test('a successful pstack install
|
|
175
|
+
test('a successful pstack install omits the retired skill block and gains the question block', (context) => {
|
|
175
176
|
const homeDirectory = makeScratchHome(context);
|
|
176
177
|
const commandPath = join(homeDirectory, process.platform === 'win32' ? 'codex.cmd' : 'codex');
|
|
177
178
|
writeFileSync(
|
|
@@ -182,6 +183,7 @@ test('a successful pstack install retains its skill block and gains the question
|
|
|
182
183
|
|
|
183
184
|
const installation = installInScratchHome(homeDirectory, [], {
|
|
184
185
|
CDE_INSTALL_PSTACK: '1',
|
|
186
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
185
187
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
186
188
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
187
189
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
@@ -191,7 +193,7 @@ test('a successful pstack install retains its skill block and gains the question
|
|
|
191
193
|
|
|
192
194
|
assert.equal(installation.status, 0, installation.stdout + installation.stderr);
|
|
193
195
|
const guidanceText = readFileSync(join(homeDirectory, '.codex', 'AGENTS.md'), 'utf8');
|
|
194
|
-
assert.
|
|
196
|
+
assert.doesNotMatch(guidanceText, /<!-- claude-dev-env skill load: start -->/);
|
|
195
197
|
assert.match(guidanceText, /<!-- claude-dev-env question presentation: start -->/);
|
|
196
198
|
assert.ok(guidanceText.includes(readFileSync(policyPath, 'utf8')));
|
|
197
199
|
});
|
|
@@ -211,6 +213,7 @@ test('enabling pstack after a pstack-off install preserves question guidance', (
|
|
|
211
213
|
|
|
212
214
|
const secondInstallation = installInScratchHome(homeDirectory, [], {
|
|
213
215
|
CDE_INSTALL_PSTACK: '1',
|
|
216
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
214
217
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
215
218
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
216
219
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|
|
@@ -221,7 +224,7 @@ test('enabling pstack after a pstack-off install preserves question guidance', (
|
|
|
221
224
|
assert.equal(secondInstallation.status, 0, secondInstallation.stdout + secondInstallation.stderr);
|
|
222
225
|
const updatedGuidance = readFileSync(codexGuidancePath, 'utf8');
|
|
223
226
|
assert.ok(updatedGuidance.includes(firstGuidance));
|
|
224
|
-
assert.
|
|
227
|
+
assert.doesNotMatch(updatedGuidance, /<!-- claude-dev-env skill load: start -->/);
|
|
225
228
|
assert.match(updatedGuidance, /^feature, refactoring: gpt-6-sol$/m);
|
|
226
229
|
assert.match(readFileSync(join(homeDirectory, '.codex', 'pstack-models.md'), 'utf8'), /^session hook: on$/m);
|
|
227
230
|
});
|
|
@@ -241,6 +244,7 @@ test('a failed pstack transition restores question-only guidance and removes its
|
|
|
241
244
|
|
|
242
245
|
const failedInstallation = installInScratchHome(homeDirectory, [], {
|
|
243
246
|
CDE_INSTALL_PSTACK: '1',
|
|
247
|
+
CDE_INSTALL_DEEPSTACK: '0',
|
|
244
248
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
245
249
|
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
246
250
|
CDE_INSTALL_DEEPEN_FIRST_PROMPT: '0',
|