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.
Files changed (118) hide show
  1. package/.agents/agents/clean-coder.md +5 -6
  2. package/.agents/agents/issue-tracker.md +1 -1
  3. package/.agents/agents/pr-description-writer.md +1 -1
  4. package/.agents/agents/skill-writer-agent.md +1 -12
  5. package/.agents/agents/test_agent_frontmatter.py +2 -19
  6. package/.agents/skills/issue-tracker/SKILL.md +1 -1
  7. package/.agents/skills/orchestrator/SKILL.md +4 -5
  8. package/.agents/skills/pull-request/SKILL.md +8 -7
  9. package/.agents/skills/pull-request/reference/publication-tasks.md +1 -1
  10. package/.agents/skills/team-advisor/SKILL.md +2 -3
  11. package/_shared/advisor/advisor-protocol.md +0 -1
  12. package/_shared/advisor/reference/lifecycle.md +0 -1
  13. package/_shared/advisor/reference/warm-up.md +1 -2
  14. package/audit-rubrics/audit-categories.json +0 -4
  15. package/audit-rubrics/category_rubrics/category-e-dead-code.md +1 -1
  16. package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +1 -2
  17. package/audit-rubrics/prompts/category-e-dead-code.md +5 -5
  18. package/audit-rubrics/prompts/category-j-code-rules-compliance.md +1 -24
  19. package/bin/install-constants.mjs +0 -5
  20. package/bin/install-pstack-plugin.mjs +192 -0
  21. package/bin/install-pstack-plugin.test.mjs +181 -0
  22. package/bin/install.agents-home.test.mjs +0 -4
  23. package/bin/install.codex-rules.test.mjs +6 -0
  24. package/bin/install.cursor-rules.test.mjs +3 -253
  25. package/bin/install.mjs +55 -160
  26. package/bin/install.test.mjs +110 -149
  27. package/bin/prune-retired-pstack-store.mjs +83 -0
  28. package/bin/prune-retired-pstack-store.test.mjs +105 -0
  29. package/bin/prune-session-continuity.mjs +57 -0
  30. package/bin/prune-session-continuity.test.mjs +70 -0
  31. package/bin/resolve-install-root.mjs +2 -0
  32. package/bin/resolve-install-root.test.mjs +69 -0
  33. package/docs/CODE_RULES.md +1 -1
  34. package/docs/agent-spawn-protocol.md +1 -1
  35. package/docs/codex-compatibility.md +4 -16
  36. package/docs/nas-ssh-invocation.md +1 -1
  37. package/docs/references/prose-style-enforcement.md +2 -2
  38. package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +1 -3
  39. package/hooks/blocking/code_rules_constants_config.py +1 -108
  40. package/hooks/blocking/code_rules_enforcer.py +9 -5
  41. package/hooks/blocking/code_rules_test_layout.py +6 -7
  42. package/hooks/blocking/test_code_rules_constants_config.py +13 -0
  43. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  44. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +15 -0
  45. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +0 -60
  46. package/hooks/blocking/test_context_control_policy_files.py +0 -11
  47. package/hooks/hooks_constants/blocking_check_limits.py +1 -1
  48. package/hooks/hooks_constants/code_rules_enforcer_constants.py +0 -1
  49. package/hooks/hooks_constants/setup_project_paths_constants.py +1 -2
  50. package/hooks/session/test_untracked_repo_detector.py +1 -1
  51. package/hooks/validators/code_quality_checks.py +15 -2
  52. package/hooks/validators/test_code_quality_checks.py +22 -0
  53. package/package.json +1 -2
  54. package/rules/anti-corollary-tests.md +3 -1
  55. package/rules/archiving-agent-config.md +70 -0
  56. package/rules/asd-ste100-language.md +1 -1
  57. package/rules/ci-owns-the-gate.md +73 -0
  58. package/rules/cleanup-temp-files.md +1 -1
  59. package/rules/code-standards.md +3 -4
  60. package/rules/docstring-prose-matches-implementation.md +3 -3
  61. package/rules/explore-thoroughly.md +2 -2
  62. package/rules/failure-blast-radius.md +10 -9
  63. package/rules/git-workflow.md +27 -12
  64. package/rules/long-horizon-autonomy.md +3 -1
  65. package/rules/nas-ssh-invocation.md +1 -1
  66. package/rules/orphan-css-class.md +1 -1
  67. package/rules/paired-test-coverage.md +1 -1
  68. package/rules/research-mode.md +3 -1
  69. package/rules/retired-hook-prose.md +10 -2
  70. package/rules/testing.md +3 -3
  71. package/rules/verify-before-asking.md +4 -1
  72. package/rules/verify-runtime-state.md +2 -2
  73. package/rules/windows-filesystem-safe.md +1 -1
  74. package/rules/workflow-substitution-slots.md +1 -1
  75. package/scripts/Sync-RepoMain.ps1 +71 -6
  76. package/scripts/policy_lint/adapter_detectors.py +116 -10
  77. package/scripts/policy_lint/config/approved_test_pairs.py +12 -0
  78. package/scripts/policy_lint/config/constants.py +3 -0
  79. package/scripts/tests/Sync-RepoMain.Tests.ps1 +108 -0
  80. package/scripts/tests/test_codex_compat_materializer.py +3 -3
  81. package/scripts/tests/test_policy_lint_rules_validator_baseline.py +126 -0
  82. package/.agents/skills/session-continuity/AGENTS.md +0 -1
  83. package/.agents/skills/session-continuity/CLAUDE.md +0 -1
  84. package/.agents/skills/session-continuity/README.md +0 -223
  85. package/.agents/skills/session-continuity/SKILL.md +0 -125
  86. package/.agents/skills/session-continuity/continuity.mjs +0 -445
  87. package/.agents/skills/session-continuity/continuity.test.mjs +0 -381
  88. package/bin/install-session-continuity.mjs +0 -131
  89. package/bin/install-session-continuity.test.mjs +0 -106
  90. package/bin/install.pstack-plugin.test.mjs +0 -115
  91. package/bin/pstack-codex-discovery.test.mjs +0 -79
  92. package/bin/pstack-model-preferences.claude.json +0 -23
  93. package/bin/pstack-model-preferences.codex.json +0 -24
  94. package/bin/pstack.mjs +0 -392
  95. package/bin/pstack.test.mjs +0 -347
  96. package/bin/refresh-pstack-plugin-skills.test.mjs +0 -211
  97. package/bin/select-pstack-models.test.mjs +0 -776
  98. package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +0 -231
  99. package/rules/agent-spawn-protocol.md +0 -9
  100. package/rules/ask-user-question-required.md +0 -31
  101. package/rules/claims-as-quotes.md +0 -55
  102. package/rules/confirm-implementation-forks.md +0 -11
  103. package/rules/file-global-constants.md +0 -81
  104. package/rules/hedging-claims.md +0 -7
  105. package/rules/measurement-denominators.md +0 -39
  106. package/rules/prompt-workflow-context-controls.md +0 -19
  107. package/rules/pstack-host-mapping.md +0 -48
  108. package/rules/pstack-models.md +0 -142
  109. package/rules/vault-context.md +0 -5
  110. package/scripts/pstack-adapters/common.md +0 -17
  111. package/scripts/pstack-adapters/create-skill.md +0 -14
  112. package/scripts/pstack-adapters/host-claude.md +0 -7
  113. package/scripts/pstack-adapters/host-codex.md +0 -11
  114. package/scripts/pstack-adapters/host-cursor.md +0 -7
  115. package/scripts/pstack.lock.json +0 -11
  116. package/scripts/refresh_pstack_plugin_skills.mjs +0 -200
  117. package/scripts/select_pstack_models.mjs +0 -469
  118. /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 preferencePath = join(
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(preferencePath), false);
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(preferencePath), true);
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
- PSTACK_PLUGIN_DIRECTORY_NAME,
47
- PSTACK_PLUGIN_MANIFEST_RELATIVE_PATH,
48
- refreshPstackPluginManifest,
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 './install-session-continuity.mjs';
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
- * Refresh the pstack plugin manifest Claude Code reads, when this home has one.
432
+ * Install the pstack plugin from its marketplace into the hosts this run serves.
485
433
  *
486
- * Claude Code loads a skills-directory folder as a plugin when it holds
487
- * `.claude-plugin/plugin.json`, and namespaces its skills as `pstack:how`.
488
- * Without the manifest, a folder of skill folders loads nothing.
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
- * @param {string} [skillsRoot] The skills root to read. Defaults to the root
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 refreshInstalledPstackPluginManifest(
495
- skillsRoot = join(CLAUDE_HOME, MANAGED_SKILLS_DIRECTORY_NAME),
496
- ) {
497
- const pluginRoot = join(skillsRoot, PSTACK_PLUGIN_DIRECTORY_NAME);
498
- const outcome = refreshPstackPluginManifest(pluginRoot);
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
- * Registers the session-continuity companion in each host configuration this
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
- * A competing profile's registration, or an unwritable host configuration, is
542
- * reported and leaves the rest of the install intact.
543
- *
544
- * @returns {string[]} The host configuration paths this run registered.
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 removeSessionContinuityHooksFromOtherHosts() {
475
+ function removeRetiredSessionContinuityHooks(hosts = ['claude', 'codex', 'cursor']) {
578
476
  const configurationPaths = continuityHostConfigurationPaths(INSTALL_ROOT_RESOLUTION);
579
477
  const changedPaths = [];
580
- for (const host of ['codex', 'cursor']) {
478
+ for (const host of hosts) {
581
479
  const configurationPath = configurationPaths[host];
582
480
  if (!existsSync(configurationPath)) continue;
583
- const configuration = JSON.parse(readFileSync(configurationPath, 'utf8'));
481
+ const configurationText = readFileSync(configurationPath, 'utf8');
482
+ const configuration = JSON.parse(configurationText);
584
483
  if (removeContinuityHooks(configuration) === 0) continue;
585
- writeFileSync(configurationPath, JSON.stringify(configuration, null, 2) + '\n');
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 && shouldInstallPstackRelease()) {
2864
- const pstackRelease = installPstackRelease({ root: CLAUDE_HOME });
2865
- summary.pstackRelease = pstackRelease;
2866
- console.log(pstackRelease.warning
2867
- ? ` Pstack: ${pstackRelease.status} \u2014 ${pstackRelease.warning}`
2868
- : ` Pstack: ${pstackRelease.status} (${pstackRelease.release})`);
2869
- }
2870
- const pstackManifestPath = refreshInstalledPstackPluginManifest();
2871
- if (pstackManifestPath) allInstalledFiles.push(pstackManifestPath);
2872
- summary.pstackPlugin = { manifestPath: pstackManifestPath };
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
- removeSessionContinuityHooksFromOtherHosts();
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 pinned pstack release (also CDE_INSTALL_PSTACK=0)
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