claude-dev-env 7.6.0 → 7.6.1
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.
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Session continuity companion
|
|
2
2
|
|
|
3
3
|
Status: draft implementation. Claude, Codex, and Cursor adapters have
|
|
4
|
-
repository-level contract tests.
|
|
4
|
+
repository-level contract tests. Claude installed-host automatic activation is
|
|
5
|
+
verified: a headless `claude -p` session on Claude Code 2.1.268 created a new
|
|
6
|
+
session-scope record carrying the `automatic` flag, with no user invocation.
|
|
7
|
+
Codex and Cursor installed-host activation, post-compaction reload, and agent
|
|
5
8
|
compliance remain unverified.
|
|
6
9
|
|
|
7
10
|
Every supported host activates Poteto Mode automatically when a session starts
|
|
@@ -27,21 +30,28 @@ pstack manifest refresher exposes namespaced Claude skills and leaves pstack
|
|
|
27
30
|
content with its existing owner. The companion edits none of that source,
|
|
28
31
|
manifest, invocation code, or model policy.
|
|
29
32
|
|
|
30
|
-
From this checkout, run the full existing installer
|
|
33
|
+
From this checkout, run the full existing installer:
|
|
31
34
|
|
|
32
35
|
```sh
|
|
33
36
|
node packages/claude-dev-env/bin/install.mjs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The full installer publishes this skill under the canonical agents home and
|
|
40
|
+
registers its hooks in every host configuration the install root resolves. No
|
|
41
|
+
second command is needed. A selective `--only <group>` install skips both steps,
|
|
42
|
+
and a plugin-only installation needs the canonical installed skill first. For
|
|
43
|
+
either of those, or to configure one host alone, run the focused setup:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
34
46
|
node packages/claude-dev-env/bin/install-session-continuity.mjs
|
|
35
47
|
```
|
|
36
48
|
|
|
37
|
-
The
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
draft. Use the full install above. Plugin-only installations need the same
|
|
41
|
-
canonical installed skill before this setup can run.
|
|
49
|
+
The focused setup defaults to `claude codex cursor`. Pass one or more host names
|
|
50
|
+
to configure that subset alone. Registration is idempotent, so running it after a
|
|
51
|
+
full install reports each host as already configured.
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
An uninstall removes the companion's registrations along with its files, so no
|
|
54
|
+
host keeps a hook pointing at a script that is gone.
|
|
45
55
|
It uses `bin/resolve-install-root.mjs`, including `CLAUDE_CONFIG_DIR` and
|
|
46
56
|
`CODEX_HOME`. It merges only its own hook groups into Claude `settings.json` and
|
|
47
57
|
Codex `hooks.json`, preserves other settings, writes a one-time backup before
|
|
@@ -44,41 +44,84 @@ export function mergeContinuityHooks(existing, host, script) {
|
|
|
44
44
|
return mergedConfiguration;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
export const CONTINUITY_SCRIPT_MARKER = '/session-continuity/continuity.mjs';
|
|
48
|
+
|
|
49
|
+
export function isContinuityHookCommand(command) {
|
|
50
|
+
return typeof command === 'string' && command.includes(CONTINUITY_SCRIPT_MARKER);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function continuityScriptPath(roots) {
|
|
54
|
+
return join(roots.skillsInstallDirectory, 'session-continuity', 'continuity.mjs');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function continuityHostConfigurationPaths(roots) {
|
|
58
|
+
return {
|
|
59
|
+
claude: join(roots.managedRoot, 'settings.json'),
|
|
60
|
+
codex: join(dirname(roots.codexRulesInstallDirectory), 'hooks.json'),
|
|
61
|
+
cursor: join(roots.cursorInstallDirectory, 'hooks.json'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function removeContinuityHooks(configuration) {
|
|
66
|
+
let removedCount = 0;
|
|
67
|
+
for (const [event, entries] of Object.entries(configuration.hooks || {})) {
|
|
68
|
+
const kept = entries.flatMap(entry => {
|
|
69
|
+
if (!entry.hooks) {
|
|
70
|
+
if (!isContinuityHookCommand(entry.command)) return [entry];
|
|
71
|
+
removedCount += 1;
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const hooks = entry.hooks.filter(hook => !isContinuityHookCommand(hook.command));
|
|
75
|
+
removedCount += entry.hooks.length - hooks.length;
|
|
76
|
+
return hooks.length ? [{ ...entry, hooks }] : [];
|
|
77
|
+
});
|
|
78
|
+
if (kept.length) configuration.hooks[event] = kept;
|
|
79
|
+
else delete configuration.hooks[event];
|
|
80
|
+
}
|
|
81
|
+
return removedCount;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function continuityConfigurationPlan(host, path, script) {
|
|
85
|
+
if (!existsSync(dirname(path))) throw new Error(`Host config directory is absent: ${dirname(path)}`);
|
|
86
|
+
const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
87
|
+
const merged = mergeContinuityHooks(existing ? JSON.parse(existing) : {}, host, script);
|
|
88
|
+
return { host, path, existing, content: JSON.stringify(merged, null, 2) + '\n' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function applyContinuityConfigurationPlan(plan) {
|
|
92
|
+
if (plan.existing === plan.content) return { host: plan.host, path: plan.path, changed: false };
|
|
93
|
+
if (plan.existing !== null) {
|
|
94
|
+
const backup = `${plan.path}.before-session-continuity`;
|
|
95
|
+
if (!existsSync(backup)) writeFileSync(backup, plan.existing, { flag: 'wx', mode: 0o600 });
|
|
96
|
+
}
|
|
97
|
+
const current = existsSync(plan.path) ? readFileSync(plan.path, 'utf8') : null;
|
|
98
|
+
if (current !== plan.existing) throw new Error(`Config changed during setup: ${plan.path}. Rerun setup.`);
|
|
99
|
+
const temporary = `${plan.path}.continuity-${process.pid}.tmp`;
|
|
100
|
+
writeFileSync(temporary, plan.content, { flag: 'wx', mode: 0o600 });
|
|
101
|
+
renameSync(temporary, plan.path);
|
|
102
|
+
if (readFileSync(plan.path, 'utf8') !== plan.content) throw new Error(`Config read-back mismatch: ${plan.path}`);
|
|
103
|
+
return { host: plan.host, path: plan.path, changed: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function configureContinuityHosts(roots, selected) {
|
|
50
107
|
const unsupported = selected.find(host => !supportedHosts.includes(host));
|
|
51
108
|
if (unsupported) throw new Error(`Unsupported host ${unsupported}`);
|
|
52
|
-
const
|
|
53
|
-
const roots = resolveInstallRoot();
|
|
54
|
-
const script = join(roots.skillsInstallDirectory, 'session-continuity', 'continuity.mjs');
|
|
109
|
+
const script = continuityScriptPath(roots);
|
|
55
110
|
if (!existsSync(script) || !existsSync(join(dirname(script), 'SKILL.md'))) {
|
|
56
111
|
throw new Error('Run the full claude-dev-env installer from this checkout first so the companion is in the canonical agents home.');
|
|
57
112
|
}
|
|
58
|
-
const paths =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (plan.existing === plan.content) { console.log(`Already configured: ${plan.path}`); continue; }
|
|
71
|
-
if (plan.existing !== null) {
|
|
72
|
-
const backup = `${plan.path}.before-session-continuity`;
|
|
73
|
-
if (!existsSync(backup)) writeFileSync(backup, plan.existing, { flag: 'wx', mode: 0o600 });
|
|
74
|
-
}
|
|
75
|
-
const current = existsSync(plan.path) ? readFileSync(plan.path, 'utf8') : null;
|
|
76
|
-
if (current !== plan.existing) throw new Error(`Config changed during setup: ${plan.path}. Rerun setup.`);
|
|
77
|
-
const temporary = `${plan.path}.continuity-${process.pid}.tmp`;
|
|
78
|
-
writeFileSync(temporary, plan.content, { flag: 'wx', mode: 0o600 });
|
|
79
|
-
renameSync(temporary, plan.path);
|
|
80
|
-
if (readFileSync(plan.path, 'utf8') !== plan.content) throw new Error(`Config read-back mismatch: ${plan.path}`);
|
|
81
|
-
console.log(`Configured and read back: ${plan.path}`);
|
|
113
|
+
const paths = continuityHostConfigurationPaths(roots);
|
|
114
|
+
return selected
|
|
115
|
+
.map(host => continuityConfigurationPlan(host, paths[host], script))
|
|
116
|
+
.map(applyContinuityConfigurationPlan);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function main() {
|
|
120
|
+
const requested = process.argv.slice(2);
|
|
121
|
+
const selected = requested.length ? requested : supportedHosts;
|
|
122
|
+
const { resolveInstallRoot } = await import('./resolve-install-root.mjs');
|
|
123
|
+
for (const result of configureContinuityHosts(resolveInstallRoot(), selected)) {
|
|
124
|
+
console.log(`${result.changed ? 'Configured and read back' : 'Already configured'}: ${result.path}`);
|
|
82
125
|
}
|
|
83
126
|
console.log('Review and trust the new hooks in each host.');
|
|
84
127
|
}
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
|
-
import {
|
|
3
|
+
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
configureContinuityHosts,
|
|
8
|
+
continuityHookConfiguration,
|
|
9
|
+
mergeContinuityHooks,
|
|
10
|
+
removeContinuityHooks,
|
|
11
|
+
} from './install-session-continuity.mjs';
|
|
12
|
+
|
|
13
|
+
function installRootFixture() {
|
|
14
|
+
const root = mkdtempSync(join(tmpdir(), 'continuity-roots-'));
|
|
15
|
+
const skillsInstallDirectory = join(root, 'agents', 'skills');
|
|
16
|
+
const companionDirectory = join(skillsInstallDirectory, 'session-continuity');
|
|
17
|
+
mkdirSync(companionDirectory, { recursive: true });
|
|
18
|
+
writeFileSync(join(companionDirectory, 'continuity.mjs'), 'export default null;\n');
|
|
19
|
+
writeFileSync(join(companionDirectory, 'SKILL.md'), '# companion\n');
|
|
20
|
+
const managedRoot = join(root, 'claude');
|
|
21
|
+
const codexRulesInstallDirectory = join(root, 'codex', 'rules');
|
|
22
|
+
const cursorInstallDirectory = join(root, 'cursor');
|
|
23
|
+
for (const directory of [managedRoot, codexRulesInstallDirectory, cursorInstallDirectory]) {
|
|
24
|
+
mkdirSync(directory, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
return { managedRoot, skillsInstallDirectory, codexRulesInstallDirectory, cursorInstallDirectory };
|
|
27
|
+
}
|
|
4
28
|
|
|
5
29
|
test('setup preserves other hooks and settings, is idempotent, and rejects competing profiles', () => {
|
|
6
30
|
const existing = { permissions: { deny: ['secret'] }, hooks: { SessionStart: [{ hooks: [{ type: 'command', command: 'existing-hook' }] }] } };
|
|
@@ -53,3 +77,30 @@ for (const host of ['claude', 'codex']) {
|
|
|
53
77
|
}
|
|
54
78
|
});
|
|
55
79
|
}
|
|
80
|
+
|
|
81
|
+
test('configureContinuityHosts writes each host config, reads it back, and repeats without a change', () => {
|
|
82
|
+
const roots = installRootFixture();
|
|
83
|
+
const settingsPath = join(roots.managedRoot, 'settings.json');
|
|
84
|
+
writeFileSync(settingsPath, JSON.stringify({ permissions: { deny: ['secret'] } }, null, 2) + '\n');
|
|
85
|
+
const first = configureContinuityHosts(roots, ['claude', 'codex', 'cursor']);
|
|
86
|
+
assert.deepEqual(first.map(result => result.changed), [true, true, true]);
|
|
87
|
+
const written = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
88
|
+
assert.deepEqual(written.permissions.deny, ['secret']);
|
|
89
|
+
const command = written.hooks.SessionStart[0].hooks[0].command;
|
|
90
|
+
assert.equal(command.endsWith('session-continuity/continuity.mjs" hook claude'), true);
|
|
91
|
+
const second = configureContinuityHosts(roots, ['claude', 'codex', 'cursor']);
|
|
92
|
+
assert.deepEqual(second.map(result => result.changed), [false, false, false]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('removeContinuityHooks takes out only the companion registrations', () => {
|
|
96
|
+
const roots = installRootFixture();
|
|
97
|
+
configureContinuityHosts(roots, ['claude']);
|
|
98
|
+
const settingsPath = join(roots.managedRoot, 'settings.json');
|
|
99
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
100
|
+
settings.hooks.SessionStart.unshift({ hooks: [{ type: 'command', command: 'python other_hook.py' }] });
|
|
101
|
+
const removedCount = removeContinuityHooks(settings);
|
|
102
|
+
assert.equal(removedCount, 3);
|
|
103
|
+
assert.deepEqual(Object.keys(settings.hooks), ['SessionStart']);
|
|
104
|
+
assert.equal(settings.hooks.SessionStart.length, 1);
|
|
105
|
+
assert.equal(settings.hooks.SessionStart[0].hooks[0].command, 'python other_hook.py');
|
|
106
|
+
});
|
package/bin/install.mjs
CHANGED
|
@@ -44,6 +44,11 @@ import {
|
|
|
44
44
|
refreshPstackPluginManifest,
|
|
45
45
|
} from '../scripts/refresh_pstack_plugin_skills.mjs';
|
|
46
46
|
import { installPstack } from './pstack.mjs';
|
|
47
|
+
import {
|
|
48
|
+
configureContinuityHosts,
|
|
49
|
+
continuityHostConfigurationPaths,
|
|
50
|
+
removeContinuityHooks,
|
|
51
|
+
} from './install-session-continuity.mjs';
|
|
47
52
|
import {
|
|
48
53
|
resolveInstallRoot,
|
|
49
54
|
parseExplicitTargetFromArgv,
|
|
@@ -509,6 +514,63 @@ function seedPstackPreferences(preferenceFileName) {
|
|
|
509
514
|
}
|
|
510
515
|
}
|
|
511
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Registers the session-continuity companion in each host configuration this
|
|
519
|
+
* install root resolves. Without this step the companion ships to the agents
|
|
520
|
+
* home but no host ever calls it, so Poteto Mode only activates when someone
|
|
521
|
+
* remembers a second setup command.
|
|
522
|
+
*
|
|
523
|
+
* A competing profile's registration, or an unwritable host configuration, is
|
|
524
|
+
* reported and leaves the rest of the install intact.
|
|
525
|
+
*
|
|
526
|
+
* @returns {string[]} The host configuration paths this run registered.
|
|
527
|
+
*/
|
|
528
|
+
function registerSessionContinuityHooks() {
|
|
529
|
+
const configurationPaths = continuityHostConfigurationPaths(INSTALL_ROOT_RESOLUTION);
|
|
530
|
+
const hosts = Object.keys(configurationPaths)
|
|
531
|
+
.filter(host => existsSync(dirname(configurationPaths[host])));
|
|
532
|
+
if (hosts.length === 0) return [];
|
|
533
|
+
try {
|
|
534
|
+
const results = configureContinuityHosts(INSTALL_ROOT_RESOLUTION, hosts);
|
|
535
|
+
for (const result of results) {
|
|
536
|
+
const state = result.changed ? 'registered' : 'already registered';
|
|
537
|
+
console.log(` Session continuity: ${state} in ${result.path}`);
|
|
538
|
+
}
|
|
539
|
+
return results.map(result => result.path);
|
|
540
|
+
} catch (registrationError) {
|
|
541
|
+
console.warn(
|
|
542
|
+
` Warning: session-continuity hooks were not registered (${registrationError.message}) — `
|
|
543
|
+
+ 'run bin/install-session-continuity.mjs once the cause is resolved.',
|
|
544
|
+
);
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Removes the companion's registrations from the Codex and Cursor hook files
|
|
551
|
+
* during an uninstall. The Claude settings.json copy is pruned inside the
|
|
552
|
+
* settings block, which owns that file's single write.
|
|
553
|
+
*
|
|
554
|
+
* Each host this installer registered is a host it has to clean, or the host
|
|
555
|
+
* keeps calling a script the uninstall deleted.
|
|
556
|
+
*
|
|
557
|
+
* @returns {string[]} The host configuration paths this run changed.
|
|
558
|
+
*/
|
|
559
|
+
function removeSessionContinuityHooksFromOtherHosts() {
|
|
560
|
+
const configurationPaths = continuityHostConfigurationPaths(INSTALL_ROOT_RESOLUTION);
|
|
561
|
+
const changedPaths = [];
|
|
562
|
+
for (const host of ['codex', 'cursor']) {
|
|
563
|
+
const configurationPath = configurationPaths[host];
|
|
564
|
+
if (!existsSync(configurationPath)) continue;
|
|
565
|
+
const configuration = JSON.parse(readFileSync(configurationPath, 'utf8'));
|
|
566
|
+
if (removeContinuityHooks(configuration) === 0) continue;
|
|
567
|
+
writeFileSync(configurationPath, JSON.stringify(configuration, null, 2) + '\n');
|
|
568
|
+
changedPaths.push(configurationPath);
|
|
569
|
+
console.log(` Session continuity: hook registrations removed from ${configurationPath}`);
|
|
570
|
+
}
|
|
571
|
+
return changedPaths;
|
|
572
|
+
}
|
|
573
|
+
|
|
512
574
|
/**
|
|
513
575
|
* Formats an absolute interpreter path as a settings.json hook command prefix:
|
|
514
576
|
* forward-slash separators, double-quoted when the path contains a space so the
|
|
@@ -2922,6 +2984,9 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2922
2984
|
console.log(` \u2713 ${relative(CLAUDE_HOME, agentsHubDest)} (canonical guidance)`);
|
|
2923
2985
|
}
|
|
2924
2986
|
const isFullInstall = !selectedGroups;
|
|
2987
|
+
if (isFullInstall && shouldInstallAnyHooks) {
|
|
2988
|
+
summary.sessionContinuity = { configuredPaths: registerSessionContinuityHooks() };
|
|
2989
|
+
}
|
|
2925
2990
|
const didPruneRun = isFullInstall && UNRESOLVED_DEPENDENCY_NAMES.length === 0;
|
|
2926
2991
|
let failedPrunePaths = [];
|
|
2927
2992
|
let stalePrunedTotal = 0;
|
|
@@ -3139,6 +3204,10 @@ function executeUninstallPlan(plan, helpers = {}) {
|
|
|
3139
3204
|
pruneManagedHooksFromSettings(settings, managedHookRelativePaths);
|
|
3140
3205
|
didSettingsChange = true;
|
|
3141
3206
|
console.log(' Hook entries removed from settings.json');
|
|
3207
|
+
const removedContinuityCount = removeContinuityHooks(settings);
|
|
3208
|
+
if (removedContinuityCount > 0) {
|
|
3209
|
+
console.log(` Session continuity: ${removedContinuityCount} hook registration(s) removed from settings.json`);
|
|
3210
|
+
}
|
|
3142
3211
|
}
|
|
3143
3212
|
const managedDenyFromPlan = plan.managedPermissionDenyEntries.length > 0
|
|
3144
3213
|
? plan.managedPermissionDenyEntries
|
|
@@ -3156,6 +3225,7 @@ function executeUninstallPlan(plan, helpers = {}) {
|
|
|
3156
3225
|
writeFileSync(plan.settingsPath, JSON.stringify(settings, null, 4) + '\n');
|
|
3157
3226
|
}
|
|
3158
3227
|
}
|
|
3228
|
+
removeSessionContinuityHooksFromOtherHosts();
|
|
3159
3229
|
throwIfFault(FAULT_PHASES.AFTER_SETTINGS_WRITE);
|
|
3160
3230
|
|
|
3161
3231
|
unsetGlobalGitHooksPathIfOurs();
|
package/bin/install.test.mjs
CHANGED
|
@@ -3088,3 +3088,60 @@ test('--no-pstack leaves the pstack store absent', t => {
|
|
|
3088
3088
|
assert.equal(existsSync(join(homeDirectory, '.claude', 'pstack')), false);
|
|
3089
3089
|
});
|
|
3090
3090
|
});
|
|
3091
|
+
|
|
3092
|
+
function runContinuityInstaller(homeDirectory, extraArguments) {
|
|
3093
|
+
return execFileSync('node', [PSTACK_TEST_INSTALLER_PATH, ...extraArguments], {
|
|
3094
|
+
cwd: dirname(PSTACK_TEST_PACKAGE_ROOT),
|
|
3095
|
+
encoding: 'utf8',
|
|
3096
|
+
env: {
|
|
3097
|
+
...process.env,
|
|
3098
|
+
CDE_INSTALL_PSTACK: '0',
|
|
3099
|
+
HOME: homeDirectory,
|
|
3100
|
+
USERPROFILE: homeDirectory,
|
|
3101
|
+
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
3102
|
+
CODEX_HOME: join(homeDirectory, '.codex'),
|
|
3103
|
+
},
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
function continuityCommandCount(configurationPath) {
|
|
3108
|
+
if (!existsSync(configurationPath)) return 0;
|
|
3109
|
+
const configuration = JSON.parse(readFileSync(configurationPath, 'utf8'));
|
|
3110
|
+
const allCommands = Object.values(configuration.hooks || {})
|
|
3111
|
+
.flat()
|
|
3112
|
+
.flatMap(entry => (entry.hooks ? entry.hooks.map(eachHook => eachHook.command) : [entry.command]));
|
|
3113
|
+
return allCommands.filter(
|
|
3114
|
+
eachCommand => typeof eachCommand === 'string'
|
|
3115
|
+
&& eachCommand.includes('session-continuity/continuity.mjs'),
|
|
3116
|
+
).length;
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
test('a full install registers the session-continuity companion in every host, and uninstall takes it back out', t => {
|
|
3120
|
+
const homeDirectory = mkdtempSync(join(tmpdir(), 'cdev-continuity-install-'));
|
|
3121
|
+
t.after(() => rmSync(homeDirectory, { recursive: true, force: true }));
|
|
3122
|
+
const allConfigurationPaths = [
|
|
3123
|
+
join(homeDirectory, '.claude', 'settings.json'),
|
|
3124
|
+
join(homeDirectory, '.codex', 'hooks.json'),
|
|
3125
|
+
join(homeDirectory, '.cursor', 'hooks.json'),
|
|
3126
|
+
];
|
|
3127
|
+
|
|
3128
|
+
runContinuityInstaller(homeDirectory, []);
|
|
3129
|
+
|
|
3130
|
+
for (const configurationPath of allConfigurationPaths) {
|
|
3131
|
+
assert.equal(
|
|
3132
|
+
continuityCommandCount(configurationPath) > 0,
|
|
3133
|
+
true,
|
|
3134
|
+
`the install registers the companion in ${configurationPath} without a second setup command`,
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
runContinuityInstaller(homeDirectory, ['--uninstall']);
|
|
3139
|
+
|
|
3140
|
+
for (const configurationPath of allConfigurationPaths) {
|
|
3141
|
+
assert.equal(
|
|
3142
|
+
continuityCommandCount(configurationPath),
|
|
3143
|
+
0,
|
|
3144
|
+
`uninstall leaves no registration in ${configurationPath} pointing at a removed script`,
|
|
3145
|
+
);
|
|
3146
|
+
}
|
|
3147
|
+
});
|