claude-dev-env 7.7.1 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/clean-coder.md +5 -6
- package/.agents/agents/issue-tracker.md +1 -1
- package/.agents/agents/pr-description-writer.md +1 -1
- package/.agents/agents/skill-writer-agent.md +1 -12
- package/.agents/agents/test_agent_frontmatter.py +2 -19
- package/.agents/skills/issue-tracker/SKILL.md +1 -1
- package/.agents/skills/orchestrator/SKILL.md +4 -5
- package/.agents/skills/pull-request/SKILL.md +8 -7
- package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +2 -3
- package/_shared/advisor/advisor-protocol.md +0 -1
- package/_shared/advisor/reference/lifecycle.md +0 -1
- package/_shared/advisor/reference/warm-up.md +1 -2
- package/audit-rubrics/audit-categories.json +0 -4
- package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -1
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -2
- package/audit-rubrics/prompts/category-e-dead-code.md +5 -5
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +1 -24
- package/bin/install-constants.mjs +0 -5
- package/bin/install-pstack-plugin.mjs +192 -0
- package/bin/install-pstack-plugin.test.mjs +181 -0
- package/bin/install.agents-home.test.mjs +0 -4
- package/bin/install.codex-rules.test.mjs +6 -0
- package/bin/install.cursor-rules.test.mjs +3 -253
- package/bin/install.mjs +55 -160
- package/bin/install.test.mjs +110 -149
- package/bin/prune-retired-pstack-store.mjs +83 -0
- package/bin/prune-retired-pstack-store.test.mjs +105 -0
- package/bin/prune-session-continuity.mjs +57 -0
- package/bin/prune-session-continuity.test.mjs +70 -0
- package/bin/resolve-install-root.mjs +2 -0
- package/bin/resolve-install-root.test.mjs +69 -0
- package/docs/CODE_RULES.md +1 -1
- package/docs/agent-spawn-protocol.md +1 -1
- package/docs/codex-compatibility.md +4 -16
- package/docs/nas-ssh-invocation.md +1 -1
- package/docs/references/prose-style-enforcement.md +2 -2
- package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +1 -3
- package/hooks/blocking/code_rules_constants_config.py +1 -108
- package/hooks/blocking/code_rules_enforcer.py +9 -5
- package/hooks/blocking/code_rules_test_layout.py +6 -7
- package/hooks/blocking/test_code_rules_constants_config.py +13 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +15 -0
- package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +0 -60
- package/hooks/blocking/test_context_control_policy_files.py +0 -11
- package/hooks/hooks_constants/blocking_check_limits.py +1 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +0 -1
- package/hooks/hooks_constants/setup_project_paths_constants.py +1 -2
- package/hooks/session/test_untracked_repo_detector.py +1 -1
- package/hooks/validators/code_quality_checks.py +15 -2
- package/hooks/validators/test_code_quality_checks.py +22 -0
- package/package.json +1 -2
- package/rules/anti-corollary-tests.md +3 -1
- package/rules/archiving-agent-config.md +70 -0
- package/rules/asd-ste100-language.md +1 -1
- package/rules/ci-owns-the-gate.md +73 -0
- package/rules/cleanup-temp-files.md +1 -1
- package/rules/code-standards.md +3 -4
- package/rules/docstring-prose-matches-implementation.md +3 -3
- package/rules/explore-thoroughly.md +2 -2
- package/rules/failure-blast-radius.md +10 -9
- package/rules/git-workflow.md +27 -12
- package/rules/long-horizon-autonomy.md +3 -1
- package/rules/nas-ssh-invocation.md +1 -1
- package/rules/orphan-css-class.md +1 -1
- package/rules/paired-test-coverage.md +1 -1
- package/rules/research-mode.md +3 -1
- package/rules/retired-hook-prose.md +10 -2
- package/rules/testing.md +3 -3
- package/rules/verify-before-asking.md +4 -1
- package/rules/verify-runtime-state.md +2 -2
- package/rules/windows-filesystem-safe.md +1 -1
- package/rules/workflow-substitution-slots.md +1 -1
- package/scripts/Sync-RepoMain.ps1 +71 -6
- package/scripts/policy_lint/adapter_detectors.py +116 -10
- package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
- package/scripts/policy_lint/config/constants.py +3 -0
- package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
- package/scripts/tests/test_codex_compat_materializer.py +3 -3
- package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
- package/.agents/skills/session-continuity/AGENTS.md +0 -1
- package/.agents/skills/session-continuity/CLAUDE.md +0 -1
- package/.agents/skills/session-continuity/README.md +0 -223
- package/.agents/skills/session-continuity/SKILL.md +0 -125
- package/.agents/skills/session-continuity/continuity.mjs +0 -445
- package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
- package/bin/install-session-continuity.mjs +0 -131
- package/bin/install-session-continuity.test.mjs +0 -106
- package/bin/install.pstack-plugin.test.mjs +0 -115
- package/bin/pstack-codex-discovery.test.mjs +0 -79
- package/bin/pstack-model-preferences.claude.json +0 -23
- package/bin/pstack-model-preferences.codex.json +0 -24
- package/bin/pstack.mjs +0 -392
- package/bin/pstack.test.mjs +0 -347
- package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
- package/bin/select-pstack-models.test.mjs +0 -776
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
- package/rules/agent-spawn-protocol.md +0 -9
- package/rules/ask-user-question-required.md +0 -31
- package/rules/claims-as-quotes.md +0 -55
- package/rules/confirm-implementation-forks.md +0 -11
- package/rules/file-global-constants.md +0 -81
- package/rules/hedging-claims.md +0 -7
- package/rules/measurement-denominators.md +0 -39
- package/rules/prompt-workflow-context-controls.md +0 -19
- package/rules/pstack-host-mapping.md +0 -48
- package/rules/pstack-models.md +0 -142
- package/rules/vault-context.md +0 -5
- package/scripts/pstack-adapters/common.md +0 -17
- package/scripts/pstack-adapters/create-skill.md +0 -14
- package/scripts/pstack-adapters/host-claude.md +0 -7
- package/scripts/pstack-adapters/host-codex.md +0 -11
- package/scripts/pstack-adapters/host-cursor.md +0 -7
- package/scripts/pstack.lock.json +0 -11
- package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
- package/scripts/select_pstack_models.mjs +0 -469
- /package/.agents/{skills → skills-archived}/source-command-logifix/SKILL.md +0 -0
|
@@ -43,13 +43,6 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function runSelector(selectorPath, selectionInput) {
|
|
47
|
-
return JSON.parse(execFileSync('node', [selectorPath], {
|
|
48
|
-
encoding: 'utf8',
|
|
49
|
-
input: JSON.stringify(selectionInput),
|
|
50
|
-
}));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
46
|
test('resolveInstallRoot names ~/.cursor/rules and allows generated mdc files under it', () => {
|
|
54
47
|
const homeDirectory = join(tmpdir(), 'cdev-cursor-rules-home');
|
|
55
48
|
const resolution = resolveInstallRoot({
|
|
@@ -111,246 +104,22 @@ test('--only journal is rejected before Cursor rules change; --only core writes
|
|
|
111
104
|
CURSOR_RULES_DIRECTORY_NAME,
|
|
112
105
|
'asd-ste100-language.mdc',
|
|
113
106
|
);
|
|
114
|
-
const
|
|
115
|
-
homeDirectory,
|
|
116
|
-
'.agents',
|
|
117
|
-
'rules',
|
|
118
|
-
'pstack-model-preferences.codex.json',
|
|
119
|
-
);
|
|
107
|
+
const policyPath = join(homeDirectory, '.agents', 'rules', 'subagent-model-policy.json');
|
|
120
108
|
assert.throws(
|
|
121
109
|
() => runInstaller(homeDirectory, ['--only', 'journal']),
|
|
122
110
|
error => error.status === 1 && /Unknown group\(s\): journal/.test(error.stderr),
|
|
123
111
|
);
|
|
124
112
|
assert.equal(existsSync(generatedPath), false);
|
|
125
|
-
assert.equal(existsSync(
|
|
113
|
+
assert.equal(existsSync(policyPath), false);
|
|
126
114
|
|
|
127
115
|
runInstaller(homeDirectory, ['--only', 'core']);
|
|
128
116
|
assert.equal(existsSync(generatedPath), true);
|
|
129
|
-
assert.equal(existsSync(
|
|
117
|
+
assert.equal(existsSync(policyPath), true);
|
|
130
118
|
} finally {
|
|
131
119
|
rmSync(homeDirectory, { recursive: true, force: true });
|
|
132
120
|
}
|
|
133
121
|
});
|
|
134
122
|
|
|
135
|
-
for (const targetName of [null, 'profile']) {
|
|
136
|
-
test('pstack rules survive reinstall and sync for ' + (targetName ?? 'main'), () => {
|
|
137
|
-
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-'));
|
|
138
|
-
try {
|
|
139
|
-
const targetArguments = targetName
|
|
140
|
-
? ['--target', join(homeDirectory, targetName)]
|
|
141
|
-
: [];
|
|
142
|
-
const resolution = resolveInstallRoot({
|
|
143
|
-
homeDirectory,
|
|
144
|
-
environment: {},
|
|
145
|
-
explicitTarget: targetName ? join(homeDirectory, targetName) : null,
|
|
146
|
-
});
|
|
147
|
-
const sharedPath = join(resolution.agentsHome, 'rules', 'pstack-models.mdc');
|
|
148
|
-
const cursorPath = join(resolution.cursorRulesInstallDirectory, 'pstack-models.mdc');
|
|
149
|
-
const preferencesPath = join(
|
|
150
|
-
resolution.agentsHome,
|
|
151
|
-
'rules',
|
|
152
|
-
'pstack-model-preferences.codex.json',
|
|
153
|
-
);
|
|
154
|
-
const policyPath = join(
|
|
155
|
-
resolution.agentsHome,
|
|
156
|
-
'rules',
|
|
157
|
-
'subagent-model-policy.json',
|
|
158
|
-
);
|
|
159
|
-
const selectorPath = join(
|
|
160
|
-
resolution.agentsHome,
|
|
161
|
-
'scripts',
|
|
162
|
-
'select_pstack_models.mjs',
|
|
163
|
-
);
|
|
164
|
-
const managedSelectorPath = join(
|
|
165
|
-
resolution.managedRoot,
|
|
166
|
-
'scripts',
|
|
167
|
-
'select_pstack_models.mjs',
|
|
168
|
-
);
|
|
169
|
-
runInstaller(homeDirectory, [...targetArguments, '--only', 'core']);
|
|
170
|
-
|
|
171
|
-
const installedText = readFileSync(sharedPath, 'utf8');
|
|
172
|
-
assert.doesNotMatch(installedText, /gpt-|claude-|grok-/i);
|
|
173
|
-
assert.match(installedText, /feature, refactoring: reliable code execution/);
|
|
174
|
-
assert.match(installedText, /interrogate reviewers: independent adversarial review/);
|
|
175
|
-
assert.match(installedText, /alwaysApply: true/);
|
|
176
|
-
assert.match(installedText, /replaces the fixed model defaults in pstack skills/);
|
|
177
|
-
assert.match(installedText, /Run the installed selector once for each agent/);
|
|
178
|
-
assert.equal(readFileSync(cursorPath, 'utf8'), installedText);
|
|
179
|
-
assert.equal(isAllowedInstallDestination(sharedPath, resolution), true);
|
|
180
|
-
assert.equal(existsSync(selectorPath), true);
|
|
181
|
-
assert.equal(existsSync(policyPath), true);
|
|
182
|
-
|
|
183
|
-
const savedPreferences = JSON.parse(readFileSync(preferencesPath, 'utf8'));
|
|
184
|
-
assert.deepEqual(savedPreferences, {
|
|
185
|
-
host: 'codex',
|
|
186
|
-
defaultEffort: 'medium',
|
|
187
|
-
modelsByRole: {
|
|
188
|
-
'feature, refactoring': ['gpt-5.6-sol'],
|
|
189
|
-
'bug-fix': ['gpt-5.6-sol'],
|
|
190
|
-
'perf-issue': ['gpt-6-astra'],
|
|
191
|
-
hillclimb: ['gpt-6-astra'],
|
|
192
|
-
'judgment and prose': ['gpt-6-astra'],
|
|
193
|
-
'hardest tasks': ['gpt-6-astra'],
|
|
194
|
-
'how explorer': ['gpt-5.6-terra'],
|
|
195
|
-
'how explainer': ['gpt-5.6-sol'],
|
|
196
|
-
'how critics': ['gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-6-astra'],
|
|
197
|
-
'why investigators': ['gpt-5.6-terra'],
|
|
198
|
-
'why synthesizer': ['gpt-6-astra'],
|
|
199
|
-
'reflect tooling': ['gpt-5.6-sol'],
|
|
200
|
-
'reflect judgment, divergent, synthesizer': ['gpt-6-astra'],
|
|
201
|
-
'arena runners': ['gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-6-astra'],
|
|
202
|
-
'arena cross-judge pool': ['gpt-5.6-sol', 'gpt-6-astra'],
|
|
203
|
-
'swarm workers': ['gpt-5.6-luna'],
|
|
204
|
-
'architect runners': ['gpt-5.6-sol', 'gpt-6-astra'],
|
|
205
|
-
'interrogate reviewers': [
|
|
206
|
-
'gpt-5.6-terra',
|
|
207
|
-
'gpt-5.6-sol',
|
|
208
|
-
'gpt-6-astra',
|
|
209
|
-
],
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
for (const role of Object.keys(savedPreferences.modelsByRole)) {
|
|
213
|
-
assert.equal(installedText.includes(role + ':'), true);
|
|
214
|
-
}
|
|
215
|
-
const codexSelection = runSelector(selectorPath, {
|
|
216
|
-
host: 'codex',
|
|
217
|
-
inventoryHost: 'codex',
|
|
218
|
-
role: 'feature, refactoring',
|
|
219
|
-
delegationIndex: 0,
|
|
220
|
-
availableModelIds: ['gpt-5.6-sol'],
|
|
221
|
-
confirmedSuitableModelIds: [],
|
|
222
|
-
parentFallback: {
|
|
223
|
-
isAllowed: false,
|
|
224
|
-
hasMaterialCapabilityLoss: false,
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
assert.equal(codexSelection.canDelegate, false);
|
|
228
|
-
assert.equal(codexSelection.failure, 'no-supported-model');
|
|
229
|
-
assert.deepEqual(codexSelection.nativeSpawnArguments, {});
|
|
230
|
-
|
|
231
|
-
const claudePreferencesPath = join(
|
|
232
|
-
resolution.agentsHome,
|
|
233
|
-
'rules',
|
|
234
|
-
'pstack-model-preferences.claude.json',
|
|
235
|
-
);
|
|
236
|
-
const seededClaudePreferences = JSON.parse(
|
|
237
|
-
readFileSync(claudePreferencesPath, 'utf8'),
|
|
238
|
-
);
|
|
239
|
-
assert.deepEqual(seededClaudePreferences, {
|
|
240
|
-
host: 'claude',
|
|
241
|
-
modelsByRole: {
|
|
242
|
-
'feature, refactoring': ['opus'],
|
|
243
|
-
'bug-fix': ['opus'],
|
|
244
|
-
'perf-issue': ['fable'],
|
|
245
|
-
hillclimb: ['fable'],
|
|
246
|
-
'judgment and prose': ['fable'],
|
|
247
|
-
'hardest tasks': ['fable'],
|
|
248
|
-
'how explorer': ['sonnet'],
|
|
249
|
-
'how explainer': ['opus'],
|
|
250
|
-
'how critics': ['sonnet', 'opus', 'fable'],
|
|
251
|
-
'why investigators': ['sonnet'],
|
|
252
|
-
'why synthesizer': ['fable'],
|
|
253
|
-
'reflect tooling': ['opus'],
|
|
254
|
-
'reflect judgment, divergent, synthesizer': ['fable'],
|
|
255
|
-
'arena runners': ['sonnet', 'opus', 'fable'],
|
|
256
|
-
'arena cross-judge pool': ['opus', 'fable'],
|
|
257
|
-
'swarm workers': ['sonnet'],
|
|
258
|
-
'architect runners': ['opus', 'fable'],
|
|
259
|
-
'interrogate reviewers': ['sonnet', 'opus', 'fable'],
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
const claudeSelection = runSelector(selectorPath, {
|
|
263
|
-
host: 'claude',
|
|
264
|
-
inventoryHost: 'claude',
|
|
265
|
-
role: 'swarm workers',
|
|
266
|
-
delegationIndex: 0,
|
|
267
|
-
availableModelIds: ['sonnet', 'opus', 'fable'],
|
|
268
|
-
confirmedSuitableModelIds: [],
|
|
269
|
-
parentFallback: {
|
|
270
|
-
isAllowed: false,
|
|
271
|
-
hasMaterialCapabilityLoss: false,
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
assert.deepEqual(claudeSelection.nativeSpawnArguments, {
|
|
275
|
-
model: 'sonnet',
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
writeFileSync(claudePreferencesPath, JSON.stringify({
|
|
279
|
-
host: 'claude',
|
|
280
|
-
modelsByRole: {
|
|
281
|
-
'feature, refactoring': ['confirmed-claude-model'],
|
|
282
|
-
},
|
|
283
|
-
}));
|
|
284
|
-
const editedClaudeSelection = runSelector(selectorPath, {
|
|
285
|
-
host: 'claude',
|
|
286
|
-
inventoryHost: 'claude',
|
|
287
|
-
role: 'feature, refactoring',
|
|
288
|
-
delegationIndex: 0,
|
|
289
|
-
availableModelIds: ['confirmed-claude-model'],
|
|
290
|
-
confirmedSuitableModelIds: [],
|
|
291
|
-
parentFallback: {
|
|
292
|
-
isAllowed: false,
|
|
293
|
-
hasMaterialCapabilityLoss: false,
|
|
294
|
-
},
|
|
295
|
-
});
|
|
296
|
-
assert.deepEqual(editedClaudeSelection.nativeSpawnArguments, {
|
|
297
|
-
model: 'confirmed-claude-model',
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const manifest = JSON.parse(readFileSync(resolution.manifestFilePath, 'utf8'));
|
|
301
|
-
assert.equal(manifest.files.includes(sharedPath), true);
|
|
302
|
-
assert.equal(manifest.files.includes(managedSelectorPath), true);
|
|
303
|
-
assert.equal(manifest.files.includes(preferencesPath), false);
|
|
304
|
-
assert.equal(manifest.files.includes(claudePreferencesPath), false);
|
|
305
|
-
|
|
306
|
-
writeFileSync(sharedPath, 'stale');
|
|
307
|
-
writeFileSync(preferencesPath, JSON.stringify({
|
|
308
|
-
host: 'codex',
|
|
309
|
-
modelsByRole: {
|
|
310
|
-
'feature, refactoring': ['user-edited-codex-model'],
|
|
311
|
-
},
|
|
312
|
-
}));
|
|
313
|
-
runInstaller(homeDirectory, [...targetArguments, '--only', 'core']);
|
|
314
|
-
assert.equal(readFileSync(sharedPath, 'utf8'), installedText);
|
|
315
|
-
assert.deepEqual(
|
|
316
|
-
JSON.parse(readFileSync(preferencesPath, 'utf8'))
|
|
317
|
-
.modelsByRole['feature, refactoring'],
|
|
318
|
-
['user-edited-codex-model'],
|
|
319
|
-
);
|
|
320
|
-
assert.deepEqual(
|
|
321
|
-
JSON.parse(readFileSync(claudePreferencesPath, 'utf8'))
|
|
322
|
-
.modelsByRole['feature, refactoring'],
|
|
323
|
-
['confirmed-claude-model'],
|
|
324
|
-
);
|
|
325
|
-
|
|
326
|
-
const syncArguments = [
|
|
327
|
-
join(resolution.managedRoot, 'scripts', 'sync_to_cursor.py'),
|
|
328
|
-
'--claude-root', resolution.managedRoot,
|
|
329
|
-
'--cursor-root', resolution.cursorInstallDirectory,
|
|
330
|
-
'--quiet',
|
|
331
|
-
];
|
|
332
|
-
execFileSync('python', syncArguments);
|
|
333
|
-
execFileSync('python', [...syncArguments, '--check']);
|
|
334
|
-
assert.equal(readFileSync(cursorPath, 'utf8'), installedText);
|
|
335
|
-
assert.equal(readFileSync(sharedPath, 'utf8'), installedText);
|
|
336
|
-
if (targetName) {
|
|
337
|
-
assert.equal(
|
|
338
|
-
existsSync(join(homeDirectory, '.agents', 'rules', 'pstack-models.mdc')),
|
|
339
|
-
false,
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
runInstaller(homeDirectory, [...targetArguments, '--uninstall']);
|
|
344
|
-
assert.equal(existsSync(sharedPath), false);
|
|
345
|
-
assert.equal(existsSync(cursorPath), false);
|
|
346
|
-
assert.equal(existsSync(selectorPath), false);
|
|
347
|
-
assert.equal(existsSync(preferencesPath), true);
|
|
348
|
-
assert.equal(existsSync(claudePreferencesPath), true);
|
|
349
|
-
} finally {
|
|
350
|
-
rmSync(homeDirectory, { recursive: true, force: true });
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
123
|
|
|
355
124
|
test('seeds one editable policy and one native Codex routing hook', () => {
|
|
356
125
|
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-routing-install-'));
|
|
@@ -667,22 +436,3 @@ test('a blocked shared rule destination rolls back generated Cursor files', () =
|
|
|
667
436
|
}
|
|
668
437
|
});
|
|
669
438
|
|
|
670
|
-
test('a failed install rolls back newly seeded host preference files', () => {
|
|
671
|
-
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-pstack-seed-rollback-'));
|
|
672
|
-
try {
|
|
673
|
-
const preferencesDirectory = join(homeDirectory, '.agents', 'rules');
|
|
674
|
-
assert.throws(() => runInstaller(
|
|
675
|
-
homeDirectory,
|
|
676
|
-
['--only', 'core'],
|
|
677
|
-
{ CLAUDE_DEV_ENV_INSTALL_FAULT: 'after_file_staging' },
|
|
678
|
-
));
|
|
679
|
-
for (const hostName of ['claude', 'codex']) {
|
|
680
|
-
assert.equal(
|
|
681
|
-
existsSync(join(preferencesDirectory, 'pstack-model-preferences.' + hostName + '.json')),
|
|
682
|
-
false,
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
} finally {
|
|
686
|
-
rmSync(homeDirectory, { recursive: true, force: true });
|
|
687
|
-
}
|
|
688
|
-
});
|
package/bin/install.mjs
CHANGED
|
@@ -35,24 +35,20 @@ import {
|
|
|
35
35
|
CODEX_RULES_PACKAGE_DIRECTORY_NAME,
|
|
36
36
|
CURSOR_SYNC_SCRIPT_FILE_NAME,
|
|
37
37
|
CURSOR_RULES_DIRECTORY_NAME,
|
|
38
|
-
PSTACK_MODEL_RULE_FILE_NAME,
|
|
39
38
|
SUBAGENT_MODEL_POLICY_FILE_NAME,
|
|
40
|
-
PSTACK_SEEDED_MODEL_PREFERENCES_FILE_NAMES,
|
|
41
39
|
WINDOWS_PYTHON_LAUNCHER_COMMAND,
|
|
42
40
|
PYTHON_PROBE_TIMEOUT_MILLISECONDS,
|
|
43
41
|
CODEX_NATIVE_ROUTING_MATCHER,
|
|
44
42
|
} from './install-constants.mjs';
|
|
45
43
|
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} from '../scripts/refresh_pstack_plugin_skills.mjs';
|
|
50
|
-
import { installPstack } from './pstack.mjs';
|
|
44
|
+
installPstackPlugin,
|
|
45
|
+
shouldInstallPstackPlugin,
|
|
46
|
+
} from './install-pstack-plugin.mjs';
|
|
51
47
|
import {
|
|
52
|
-
configureContinuityHosts,
|
|
53
48
|
continuityHostConfigurationPaths,
|
|
54
49
|
removeContinuityHooks,
|
|
55
|
-
} from './
|
|
50
|
+
} from './prune-session-continuity.mjs';
|
|
51
|
+
import { removeRetiredPstackInstallation } from './prune-retired-pstack-store.mjs';
|
|
56
52
|
import {
|
|
57
53
|
resolveInstallRoot,
|
|
58
54
|
parseExplicitTargetFromArgv,
|
|
@@ -432,90 +428,22 @@ export function runCursorRuleSync(pythonCommand, scriptPath, claudeRoot, cursorR
|
|
|
432
428
|
);
|
|
433
429
|
}
|
|
434
430
|
|
|
435
|
-
const PSTACK_RELEASE_OPT_OUT_FLAG = '--no-pstack';
|
|
436
|
-
const PSTACK_RELEASE_OPT_OUT_VARIABLE = 'CDE_INSTALL_PSTACK';
|
|
437
|
-
const PSTACK_RELEASE_OPT_OUT_VALUE = '0';
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Decide whether a full install also installs the pstack release.
|
|
441
|
-
*
|
|
442
|
-
* The step reaches the network for the pinned upstream commit. `--no-pstack`
|
|
443
|
-
* on the command line, or `CDE_INSTALL_PSTACK=0` in the environment, turns it
|
|
444
|
-
* off for an air-gapped or offline run.
|
|
445
|
-
*
|
|
446
|
-
* @param {string[]} [argumentList] The command-line arguments after the script.
|
|
447
|
-
* @param {Record<string, string|undefined>} [environment] The process environment.
|
|
448
|
-
* @returns {boolean} True when this run installs the pstack release.
|
|
449
|
-
*/
|
|
450
|
-
export function shouldInstallPstackRelease(
|
|
451
|
-
argumentList = process.argv.slice(2),
|
|
452
|
-
environment = process.env,
|
|
453
|
-
) {
|
|
454
|
-
if (argumentList.includes(PSTACK_RELEASE_OPT_OUT_FLAG)) return false;
|
|
455
|
-
return environment[PSTACK_RELEASE_OPT_OUT_VARIABLE] !== PSTACK_RELEASE_OPT_OUT_VALUE;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Install the pstack release into a managed root without failing the run.
|
|
460
|
-
*
|
|
461
|
-
* The pstack store keeps its own state and its own skill pointers, so this
|
|
462
|
-
* step reports its outcome rather than adding paths to the install manifest.
|
|
463
|
-
* A network failure returns a warning so the rules, hooks, and skills this
|
|
464
|
-
* run already wrote still reach their durable places.
|
|
465
|
-
*
|
|
466
|
-
* @param {object} [options] Options for `installPstack`, including `root`.
|
|
467
|
-
* @param {object} [dependencies] Seams `installPstack` accepts, such as `fetchSource`.
|
|
468
|
-
* @returns {{status: string, release: string|null, warning: string|null}} The outcome.
|
|
469
|
-
*/
|
|
470
|
-
export function installPstackRelease(options = {}, dependencies = {}) {
|
|
471
|
-
try {
|
|
472
|
-
const installation = installPstack(options, dependencies);
|
|
473
|
-
return {
|
|
474
|
-
status: installation.status,
|
|
475
|
-
release: installation.release,
|
|
476
|
-
warning: installation.warning ?? null,
|
|
477
|
-
};
|
|
478
|
-
} catch (installError) {
|
|
479
|
-
return { status: 'failed', release: null, warning: installError.message };
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
431
|
/**
|
|
484
|
-
*
|
|
432
|
+
* Install the pstack plugin from its marketplace into the hosts this run serves.
|
|
485
433
|
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
434
|
+
* The plugin carries its own skills, agents, and SessionStart hook, so the step
|
|
435
|
+
* reports its outcome rather than adding paths to the install manifest. A host
|
|
436
|
+
* without its command-line tool is skipped and a failing command is reported,
|
|
437
|
+
* so the rules, hooks, and skills this run already wrote still reach their
|
|
438
|
+
* durable places.
|
|
489
439
|
*
|
|
490
|
-
* @
|
|
491
|
-
* this run installs into.
|
|
492
|
-
* @returns {string|null} The manifest path when this run wrote it, else null.
|
|
440
|
+
* @returns {{status: string, hosts: object[], warning: string|null}} The outcome.
|
|
493
441
|
*/
|
|
494
|
-
export function
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (!outcome.didWrite) return null;
|
|
500
|
-
return join(pluginRoot, PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function seedPstackPreferences(preferenceFileName) {
|
|
504
|
-
const preferenceDirectory = join(AGENTS_HOME, CURSOR_RULES_DIRECTORY_NAME);
|
|
505
|
-
const preferencePath = join(preferenceDirectory, preferenceFileName);
|
|
506
|
-
const preferenceSource = join(PACKAGE_ROOT, 'bin', preferenceFileName);
|
|
507
|
-
mkdirSync(preferenceDirectory, { recursive: true });
|
|
508
|
-
try {
|
|
509
|
-
copyFileSync(
|
|
510
|
-
preferenceSource,
|
|
511
|
-
preferencePath,
|
|
512
|
-
filesystemConstants.COPYFILE_EXCL,
|
|
513
|
-
);
|
|
514
|
-
return preferencePath;
|
|
515
|
-
} catch (copyError) {
|
|
516
|
-
if (copyError.code === 'EEXIST') return null;
|
|
517
|
-
throw copyError;
|
|
518
|
-
}
|
|
442
|
+
export function installPstackPluginForHosts() {
|
|
443
|
+
return installPstackPlugin({
|
|
444
|
+
claudeRoot: CLAUDE_HOME,
|
|
445
|
+
codexHome: INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
446
|
+
});
|
|
519
447
|
}
|
|
520
448
|
|
|
521
449
|
function seedSubagentModelPolicy() {
|
|
@@ -533,58 +461,30 @@ function seedSubagentModelPolicy() {
|
|
|
533
461
|
}
|
|
534
462
|
|
|
535
463
|
/**
|
|
536
|
-
*
|
|
537
|
-
* install root resolves. Without this step the companion ships to the agents
|
|
538
|
-
* home but no host ever calls it, so Poteto Mode only activates when someone
|
|
539
|
-
* remembers a second setup command.
|
|
464
|
+
* Removes the retired session-continuity registrations from the named hosts.
|
|
540
465
|
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
|
|
546
|
-
function registerSessionContinuityHooks() {
|
|
547
|
-
const configurationPaths = continuityHostConfigurationPaths(INSTALL_ROOT_RESOLUTION);
|
|
548
|
-
const hosts = Object.keys(configurationPaths)
|
|
549
|
-
.filter(host => existsSync(dirname(configurationPaths[host])));
|
|
550
|
-
if (hosts.length === 0) return [];
|
|
551
|
-
try {
|
|
552
|
-
const results = configureContinuityHosts(INSTALL_ROOT_RESOLUTION, hosts);
|
|
553
|
-
for (const result of results) {
|
|
554
|
-
const state = result.changed ? 'registered' : 'already registered';
|
|
555
|
-
console.log(` Session continuity: ${state} in ${result.path}`);
|
|
556
|
-
}
|
|
557
|
-
return results.map(result => result.path);
|
|
558
|
-
} catch (registrationError) {
|
|
559
|
-
console.warn(
|
|
560
|
-
` Warning: session-continuity hooks were not registered (${registrationError.message}) — `
|
|
561
|
-
+ 'run bin/install-session-continuity.mjs once the cause is resolved.',
|
|
562
|
-
);
|
|
563
|
-
return [];
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Removes the companion's registrations from the Codex and Cursor hook files
|
|
569
|
-
* during an uninstall. The Claude settings.json copy is pruned inside the
|
|
570
|
-
* settings block, which owns that file's single write.
|
|
571
|
-
*
|
|
572
|
-
* Each host this installer registered is a host it has to clean, or the host
|
|
573
|
-
* keeps calling a script the uninstall deleted.
|
|
466
|
+
* An earlier claude-dev-env registered the companion in Claude, Codex, and
|
|
467
|
+
* Cursor. The pstack plugin now carries the SessionStart context the companion
|
|
468
|
+
* supplied, so a registration left behind points every session at a deleted
|
|
469
|
+
* script. Every install clears all three hosts; an uninstall passes the two the
|
|
470
|
+
* settings block does not already own.
|
|
574
471
|
*
|
|
472
|
+
* @param {string[]} [hosts] The hosts to clear. Defaults to all three.
|
|
575
473
|
* @returns {string[]} The host configuration paths this run changed.
|
|
576
474
|
*/
|
|
577
|
-
function
|
|
475
|
+
function removeRetiredSessionContinuityHooks(hosts = ['claude', 'codex', 'cursor']) {
|
|
578
476
|
const configurationPaths = continuityHostConfigurationPaths(INSTALL_ROOT_RESOLUTION);
|
|
579
477
|
const changedPaths = [];
|
|
580
|
-
for (const host of
|
|
478
|
+
for (const host of hosts) {
|
|
581
479
|
const configurationPath = configurationPaths[host];
|
|
582
480
|
if (!existsSync(configurationPath)) continue;
|
|
583
|
-
const
|
|
481
|
+
const configurationText = readFileSync(configurationPath, 'utf8');
|
|
482
|
+
const configuration = JSON.parse(configurationText);
|
|
584
483
|
if (removeContinuityHooks(configuration) === 0) continue;
|
|
585
|
-
|
|
484
|
+
const indent = hostConfigurationIndent(configurationText);
|
|
485
|
+
writeFileSync(configurationPath, JSON.stringify(configuration, null, indent) + '\n');
|
|
586
486
|
changedPaths.push(configurationPath);
|
|
587
|
-
console.log(` Session continuity: hook registrations removed from ${configurationPath}`);
|
|
487
|
+
console.log(` Session continuity: retired hook registrations removed from ${configurationPath}`);
|
|
588
488
|
}
|
|
589
489
|
return changedPaths;
|
|
590
490
|
}
|
|
@@ -2811,20 +2711,6 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2811
2711
|
allInstalledFiles.push(...generatedCursorPaths);
|
|
2812
2712
|
summary.cursorRules = { created: generatedCursorPaths.length, updated: 0, paths: generatedCursorPaths };
|
|
2813
2713
|
syncWrittenPaths(allInstalledFiles);
|
|
2814
|
-
const pstackRuleSource = join(INSTALL_ROOT_RESOLUTION.cursorRulesInstallDirectory, PSTACK_MODEL_RULE_FILE_NAME);
|
|
2815
|
-
const sharedRuleDirectory = join(AGENTS_HOME, CURSOR_RULES_DIRECTORY_NAME);
|
|
2816
|
-
const sharedRulePath = join(sharedRuleDirectory, PSTACK_MODEL_RULE_FILE_NAME);
|
|
2817
|
-
mkdirSync(sharedRuleDirectory, { recursive: true });
|
|
2818
|
-
copyFileSync(pstackRuleSource, sharedRulePath);
|
|
2819
|
-
allInstalledFiles.push(sharedRulePath);
|
|
2820
|
-
syncWrittenPaths(allInstalledFiles);
|
|
2821
|
-
for (const eachPreferenceFileName of PSTACK_SEEDED_MODEL_PREFERENCES_FILE_NAMES) {
|
|
2822
|
-
const seededPreferencePath = seedPstackPreferences(eachPreferenceFileName);
|
|
2823
|
-
if (seededPreferencePath) {
|
|
2824
|
-
allInstalledFiles.push(seededPreferencePath);
|
|
2825
|
-
allUserOwnedPreferencePaths.add(seededPreferencePath);
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
2714
|
const seededPolicyPath = seedSubagentModelPolicy();
|
|
2829
2715
|
if (seededPolicyPath) {
|
|
2830
2716
|
allInstalledFiles.push(seededPolicyPath);
|
|
@@ -2860,16 +2746,28 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2860
2746
|
}
|
|
2861
2747
|
summary.skills = { created: skillsCreated, updated: skillsUpdated, pruned: 0, paths: skillPaths };
|
|
2862
2748
|
allInstalledFiles.push(...skillPaths);
|
|
2863
|
-
if (!selectedGroups
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2749
|
+
if (!selectedGroups) {
|
|
2750
|
+
summary.retiredSessionContinuity = {
|
|
2751
|
+
changedPaths: removeRetiredSessionContinuityHooks(),
|
|
2752
|
+
};
|
|
2753
|
+
const retiredPstack = removeRetiredPstackInstallation(INSTALL_ROOT_RESOLUTION);
|
|
2754
|
+
summary.retiredPstackStore = retiredPstack;
|
|
2755
|
+
for (const pointerPath of retiredPstack.removedPointerPaths) {
|
|
2756
|
+
console.log(` Pstack: retired pointer removed from ${pointerPath}`);
|
|
2757
|
+
}
|
|
2758
|
+
if (retiredPstack.removedStorePath) {
|
|
2759
|
+
console.log(` Pstack: retired release store removed from ${retiredPstack.removedStorePath}`);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
if (!selectedGroups && shouldInstallPstackPlugin()) {
|
|
2763
|
+
const pstackPlugin = installPstackPluginForHosts();
|
|
2764
|
+
summary.pstackPlugin = pstackPlugin;
|
|
2765
|
+
for (const eachHost of pstackPlugin.hosts) {
|
|
2766
|
+
console.log(eachHost.warning
|
|
2767
|
+
? ` Pstack (${eachHost.host}): ${eachHost.status} \u2014 ${eachHost.warning}`
|
|
2768
|
+
: ` Pstack (${eachHost.host}): ${eachHost.status}`);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2873
2771
|
syncWrittenPaths([...allInstalledFiles, ...publishedPointerPaths]);
|
|
2874
2772
|
throwIfFault(FAULT_PHASES.AFTER_FILE_STAGING);
|
|
2875
2773
|
throwIfFault(FAULT_PHASES.BEFORE_DURABLE_PROMOTION);
|
|
@@ -2999,9 +2897,6 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2999
2897
|
console.log(` \u2713 ${relative(CLAUDE_HOME, agentsHubDest)} (canonical guidance)`);
|
|
3000
2898
|
}
|
|
3001
2899
|
const isFullInstall = !selectedGroups;
|
|
3002
|
-
if (isFullInstall && shouldInstallAnyHooks) {
|
|
3003
|
-
summary.sessionContinuity = { configuredPaths: registerSessionContinuityHooks() };
|
|
3004
|
-
}
|
|
3005
2900
|
const didPruneRun = isFullInstall && UNRESOLVED_DEPENDENCY_NAMES.length === 0;
|
|
3006
2901
|
let failedPrunePaths = [];
|
|
3007
2902
|
let stalePrunedTotal = 0;
|
|
@@ -3268,7 +3163,7 @@ function executeUninstallPlan(plan, helpers = {}) {
|
|
|
3268
3163
|
if (!codexRoutingHookExists()) {
|
|
3269
3164
|
pruneManagedHooksFromHostConfiguration(CODEX_HOOKS_CONFIGURATION_PATH, 2);
|
|
3270
3165
|
}
|
|
3271
|
-
|
|
3166
|
+
removeRetiredSessionContinuityHooks(['codex', 'cursor']);
|
|
3272
3167
|
throwIfFault(FAULT_PHASES.AFTER_SETTINGS_WRITE);
|
|
3273
3168
|
|
|
3274
3169
|
unsetGlobalGitHooksPathIfOurs();
|
|
@@ -3394,7 +3289,7 @@ Usage:
|
|
|
3394
3289
|
npx ${PACKAGE_NAME} --target DIR Install into DIR instead of ~/.claude (overrides CLAUDE_CONFIG_DIR)
|
|
3395
3290
|
npx ${PACKAGE_NAME} --profile ID Install into one named profile root (under the profiles root)
|
|
3396
3291
|
npx ${PACKAGE_NAME} --profiles A,B Install into each selected profile (one ownership manifest per target)
|
|
3397
|
-
npx ${PACKAGE_NAME} --no-pstack Full install without the
|
|
3292
|
+
npx ${PACKAGE_NAME} --no-pstack Full install without the pstack plugin (also CDE_INSTALL_PSTACK=0)
|
|
3398
3293
|
npx ${PACKAGE_NAME} --uninstall Remove installed files from the selected root
|
|
3399
3294
|
npx ${PACKAGE_NAME} --help Show this help
|
|
3400
3295
|
|