claude-dev-env 2.14.1 → 2.15.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.
Files changed (47) hide show
  1. package/_shared/advisor/scripts/tier_model_ids.py +6 -2
  2. package/bin/AGENTS.md +6 -4
  3. package/bin/install-constants.mjs +51 -0
  4. package/bin/install.codex-rules.test.mjs +173 -0
  5. package/bin/install.cursor-rules.test.mjs +103 -0
  6. package/bin/install.mjs +165 -51
  7. package/bin/install.profile-root.test.mjs +8 -0
  8. package/bin/install.prune.test.mjs +2 -1
  9. package/bin/install.test.mjs +42 -8
  10. package/bin/install.transaction.test.mjs +1 -0
  11. package/bin/install.uninstall-transaction.test.mjs +1 -0
  12. package/bin/resolve-install-root.mjs +43 -10
  13. package/codex-rules/claude-dev-env.rules +12 -0
  14. package/package.json +3 -2
  15. package/scripts/AGENTS.md +0 -7
  16. package/scripts/claude_chain_runner.py +49 -2
  17. package/scripts/invoke_code_review.py +48 -51
  18. package/scripts/resolve_worker_spawn.py +42 -47
  19. package/scripts/sync_to_cursor/AGENTS.md +3 -3
  20. package/scripts/sync_to_cursor/canonical_docs.py +11 -11
  21. package/scripts/sync_to_cursor/config/__init__.py +8 -0
  22. package/scripts/sync_to_cursor/engine.py +26 -1
  23. package/scripts/sync_to_cursor/rules.py +76 -5
  24. package/scripts/test_claude_chain_runner.py +28 -0
  25. package/scripts/test_dispatcher_profile_import.py +184 -0
  26. package/scripts/test_resolve_worker_spawn.py +119 -1
  27. package/scripts/test_validate_instruction_pairs.py +30 -0
  28. package/scripts/tests/AGENTS.md +2 -0
  29. package/scripts/tests/test_engine.py +102 -0
  30. package/scripts/tests/test_rules.py +79 -0
  31. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +0 -25
  32. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +0 -60
  33. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +0 -54
  34. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +0 -64
  35. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +0 -180
  36. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +0 -288
  37. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +0 -275
  38. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +0 -221
  39. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +0 -108
  40. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +0 -147
  41. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +0 -102
  42. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +0 -210
  43. package/scripts/profile-isolation-launchers/version-compatibility.mjs +0 -299
  44. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +0 -127
  45. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +0 -51
  46. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +0 -77
  47. package/scripts/sync_to_cursor/config.py +0 -5
@@ -1,275 +0,0 @@
1
- /**
2
- * Profile MCP bundle validation and materialization.
3
- *
4
- * Bundles map to server inventories written through the supported activation
5
- * interface (profile CLAUDE_CONFIG_DIR mcp.json).
6
- */
7
-
8
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
9
- import { dirname, join } from 'node:path';
10
- import { fileURLToPath } from 'node:url';
11
- import {
12
- loadProfilesManifestDocument,
13
- MCP_BUNDLE_FULL,
14
- MCP_BUNDLE_LEAN,
15
- } from './config/profile-isolation-constants.mjs';
16
- import {
17
- resolveProfileDefinition,
18
- validateProfilesManifest,
19
- } from './lib/profile-manifest.mjs';
20
-
21
- const MODULE_DIRECTORY = dirname(fileURLToPath(import.meta.url));
22
- const MCP_BUNDLES_CONFIG_PATH = join(MODULE_DIRECTORY, 'config', 'mcp-bundles.json');
23
- const SUPPORTED_ACTIVATION_INTERFACE = 'claude-config-dir-mcp-json';
24
- const DEFAULT_MCP_CONFIG_FILE_NAME = 'mcp.json';
25
-
26
- /**
27
- * @typedef {{
28
- * schemaVersion: number,
29
- * supportedActivationInterface: string,
30
- * mcpConfigFileName: string,
31
- * bundles: Record<string, {id: string, allServerNames: string[]}>,
32
- * serverByName: Record<string, {command: string, args: string[]}>,
33
- * }} McpBundlesDocument
34
- */
35
-
36
- /**
37
- * @returns {McpBundlesDocument}
38
- */
39
- export function loadMcpBundlesDocument() {
40
- const raw = JSON.parse(readFileSync(MCP_BUNDLES_CONFIG_PATH, 'utf8'));
41
- return validateMcpBundlesDocument(raw);
42
- }
43
-
44
- /**
45
- * @param {unknown} maybeDocument
46
- * @returns {McpBundlesDocument}
47
- */
48
- export function validateMcpBundlesDocument(maybeDocument) {
49
- if (!maybeDocument || typeof maybeDocument !== 'object' || Array.isArray(maybeDocument)) {
50
- throw new Error('mcp bundles document must be a JSON object');
51
- }
52
- const document = /** @type {Record<string, unknown>} */ (maybeDocument);
53
- if (document.schemaVersion !== 1) {
54
- throw new Error('mcp bundles schemaVersion must be 1');
55
- }
56
- if (document.supportedActivationInterface !== SUPPORTED_ACTIVATION_INTERFACE) {
57
- throw new Error(
58
- `supportedActivationInterface must be ${SUPPORTED_ACTIVATION_INTERFACE}`,
59
- );
60
- }
61
- const mcpConfigFileName = document.mcpConfigFileName;
62
- if (typeof mcpConfigFileName !== 'string' || mcpConfigFileName.length === 0) {
63
- throw new Error('mcpConfigFileName must be a non-empty string');
64
- }
65
- if (
66
- !document.bundles
67
- || typeof document.bundles !== 'object'
68
- || Array.isArray(document.bundles)
69
- ) {
70
- throw new Error('bundles must be an object');
71
- }
72
- if (
73
- !document.serverByName
74
- || typeof document.serverByName !== 'object'
75
- || Array.isArray(document.serverByName)
76
- ) {
77
- throw new Error('serverByName must be an object');
78
- }
79
- /** @type {Record<string, {id: string, allServerNames: string[]}>} */
80
- const bundles = {};
81
- for (const [eachBundleId, eachBundleValue] of Object.entries(
82
- /** @type {Record<string, unknown>} */ (document.bundles),
83
- )) {
84
- if (
85
- !eachBundleValue
86
- || typeof eachBundleValue !== 'object'
87
- || Array.isArray(eachBundleValue)
88
- ) {
89
- throw new Error(`bundle ${eachBundleId} must be an object`);
90
- }
91
- const bundle = /** @type {Record<string, unknown>} */ (eachBundleValue);
92
- if (bundle.id !== eachBundleId) {
93
- throw new Error(`bundle id mismatch for ${eachBundleId}`);
94
- }
95
- if (!Array.isArray(bundle.allServerNames) || bundle.allServerNames.length === 0) {
96
- throw new Error(`bundle ${eachBundleId} requires allServerNames`);
97
- }
98
- bundles[eachBundleId] = {
99
- id: eachBundleId,
100
- allServerNames: bundle.allServerNames.map(String),
101
- };
102
- }
103
- if (!(MCP_BUNDLE_LEAN in bundles) || !(MCP_BUNDLE_FULL in bundles)) {
104
- throw new Error('bundles must define lean and full');
105
- }
106
- const leanServerNames = new Set(bundles[MCP_BUNDLE_LEAN].allServerNames);
107
- for (const eachLeanServerName of leanServerNames) {
108
- if (!bundles[MCP_BUNDLE_FULL].allServerNames.includes(eachLeanServerName)) {
109
- throw new Error(
110
- `lean server ${eachLeanServerName} must also appear in full`,
111
- );
112
- }
113
- }
114
- /** @type {Record<string, {command: string, args: string[]}>} */
115
- const serverByName = {};
116
- for (const [eachServerName, eachServerValue] of Object.entries(
117
- /** @type {Record<string, unknown>} */ (document.serverByName),
118
- )) {
119
- if (
120
- !eachServerValue
121
- || typeof eachServerValue !== 'object'
122
- || Array.isArray(eachServerValue)
123
- ) {
124
- throw new Error(`server ${eachServerName} must be an object`);
125
- }
126
- const server = /** @type {Record<string, unknown>} */ (eachServerValue);
127
- if (typeof server.command !== 'string' || server.command.length === 0) {
128
- throw new Error(`server ${eachServerName} requires command`);
129
- }
130
- if (!Array.isArray(server.args)) {
131
- throw new Error(`server ${eachServerName} requires args array`);
132
- }
133
- serverByName[eachServerName] = {
134
- command: server.command,
135
- args: server.args.map(String),
136
- };
137
- }
138
- for (const eachBundle of Object.values(bundles)) {
139
- for (const eachServerName of eachBundle.allServerNames) {
140
- if (!(eachServerName in serverByName)) {
141
- throw new Error(
142
- `bundle ${eachBundle.id} references unknown server ${eachServerName}`,
143
- );
144
- }
145
- }
146
- }
147
- return {
148
- schemaVersion: 1,
149
- supportedActivationInterface: SUPPORTED_ACTIVATION_INTERFACE,
150
- mcpConfigFileName: String(mcpConfigFileName),
151
- bundles,
152
- serverByName,
153
- };
154
- }
155
-
156
- /**
157
- * Resolve the mcp bundle id for a profile from the profiles manifest.
158
- *
159
- * @param {string} profileId
160
- * @returns {string}
161
- */
162
- export function resolveProfileMcpBundleId(profileId) {
163
- const manifest = validateProfilesManifest(loadProfilesManifestDocument());
164
- try {
165
- return resolveProfileDefinition(manifest, profileId).mcpBundle;
166
- } catch (errorValue) {
167
- if (
168
- errorValue instanceof Error
169
- && errorValue.message.startsWith('Unknown profile id or alias:')
170
- ) {
171
- throw new Error(`unknown profile id for mcp bundle: ${profileId}`);
172
- }
173
- throw errorValue;
174
- }
175
- }
176
-
177
- /**
178
- * Build the effective MCP server inventory for a bundle id.
179
- *
180
- * @param {string} bundleId
181
- * @param {McpBundlesDocument} [bundlesDocument]
182
- * @returns {string[]}
183
- */
184
- export function listServerNamesForBundle(bundleId, bundlesDocument = loadMcpBundlesDocument()) {
185
- const bundle = bundlesDocument.bundles[bundleId];
186
- if (!bundle) {
187
- throw new Error(`unknown mcp bundle id: ${bundleId}`);
188
- }
189
- return [...bundle.allServerNames];
190
- }
191
-
192
- /**
193
- * Materialize mcp.json for a profile under CLAUDE_CONFIG_DIR.
194
- *
195
- * @param {{
196
- * profileId: string,
197
- * claudeConfigDir: string,
198
- * profileRootPlaceholderValue?: string,
199
- * bundlesDocument?: McpBundlesDocument,
200
- * }} parameters
201
- * @returns {{
202
- * activationInterface: string,
203
- * mcpConfigPath: string,
204
- * bundleId: string,
205
- * allServerNames: string[],
206
- * }}
207
- */
208
- export function materializeProfileMcpConfig(parameters) {
209
- const bundlesDocument = parameters.bundlesDocument ?? loadMcpBundlesDocument();
210
- if (!parameters.claudeConfigDir || typeof parameters.claudeConfigDir !== 'string') {
211
- throw new Error('claudeConfigDir is required');
212
- }
213
- if (!existsSync(parameters.claudeConfigDir)) {
214
- mkdirSync(parameters.claudeConfigDir, { recursive: true });
215
- }
216
- const bundleId = resolveProfileMcpBundleId(parameters.profileId);
217
- const allServerNames = listServerNamesForBundle(bundleId, bundlesDocument);
218
- const profileRootValue = parameters.profileRootPlaceholderValue ?? parameters.claudeConfigDir;
219
- /** @type {Record<string, {command: string, args: string[]}>} */
220
- const mcpServers = {};
221
- for (const eachServerName of allServerNames) {
222
- const definition = bundlesDocument.serverByName[eachServerName];
223
- mcpServers[eachServerName] = {
224
- command: definition.command,
225
- args: definition.args.map((eachArg) => eachArg.replaceAll('${PROFILE_ROOT}', profileRootValue)),
226
- };
227
- }
228
- const mcpConfigPath = join(parameters.claudeConfigDir, bundlesDocument.mcpConfigFileName);
229
- writeFileSync(
230
- mcpConfigPath,
231
- `${JSON.stringify({ mcpServers }, null, 2)}\n`,
232
- 'utf8',
233
- );
234
- return {
235
- activationInterface: bundlesDocument.supportedActivationInterface,
236
- mcpConfigPath,
237
- bundleId,
238
- allServerNames,
239
- };
240
- }
241
-
242
- /**
243
- * Read back the effective server inventory from a materialized mcp.json.
244
- *
245
- * @param {string} mcpConfigPath
246
- * @returns {string[]}
247
- */
248
- export function readEffectiveMcpServerInventory(mcpConfigPath) {
249
- if (!existsSync(mcpConfigPath)) {
250
- throw new Error(`mcp config missing: ${mcpConfigPath}`);
251
- }
252
- let document;
253
- try {
254
- document = JSON.parse(readFileSync(mcpConfigPath, 'utf8'));
255
- } catch {
256
- throw new Error(`mcp config malformed: ${mcpConfigPath}`);
257
- }
258
- if (
259
- !document
260
- || typeof document !== 'object'
261
- || Array.isArray(document)
262
- || !document.mcpServers
263
- || typeof document.mcpServers !== 'object'
264
- || Array.isArray(document.mcpServers)
265
- ) {
266
- throw new Error(`mcp config malformed: ${mcpConfigPath}`);
267
- }
268
- return Object.keys(document.mcpServers).sort();
269
- }
270
-
271
- export {
272
- SUPPORTED_ACTIVATION_INTERFACE,
273
- DEFAULT_MCP_CONFIG_FILE_NAME,
274
- MCP_BUNDLES_CONFIG_PATH,
275
- };
@@ -1,221 +0,0 @@
1
- import { test } from 'node:test';
2
- import { strict as assert } from 'node:assert';
3
- import { execFileSync } from 'node:child_process';
4
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
5
- import { dirname, join, relative, sep } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
-
8
- import {
9
- CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE,
10
- CLAUDE_HOME_ENVIRONMENT_VARIABLE,
11
- INSTALL_DESTINATION_ROOT_RELATIVE_PATH,
12
- LAUNCHER_SCHEMA_VERSION,
13
- LIVE_DEPLOYMENT_RESERVED_FOR,
14
- loadProfilesManifestDocument,
15
- loadSharedAllowlistDocument,
16
- PACKAGE_FILES_WHITELIST_SCRIPTS_ENTRY,
17
- PROFILE_ISOLATION_CONTRACT_OWNER,
18
- } from './config/profile-isolation-constants.mjs';
19
- import {
20
- loadAndValidateProfilesManifest,
21
- loadAndValidateSharedAllowlist,
22
- resolveProfileDefinition,
23
- resolveProfileRootDirectoryPath,
24
- validateProfilesManifest,
25
- validateSharedAllowlist,
26
- } from './lib/profile-manifest.mjs';
27
-
28
- const CONTRACT_ROOT_DIRECTORY_PATH = dirname(fileURLToPath(import.meta.url));
29
- const PACKAGE_ROOT_DIRECTORY_PATH = join(CONTRACT_ROOT_DIRECTORY_PATH, '..', '..');
30
- const REPOSITORY_ROOT_DIRECTORY_PATH = join(PACKAGE_ROOT_DIRECTORY_PATH, '..', '..');
31
-
32
- const ALL_REQUIRED_CONTRACT_SOURCE_RELATIVE_PATHS = Object.freeze([
33
- 'config/profile-isolation-constants.mjs',
34
- 'config/profiles.manifest.json',
35
- 'config/shared-allowlist.json',
36
- 'lib/profile-manifest.mjs',
37
- ]);
38
-
39
- /** J1 MCP activation and K1 version-preflight sources allowed beside the A1a contract surface. */
40
- const ALL_ALLOWED_MCP_ACTIVATION_RELATIVE_PATHS = Object.freeze([
41
- 'config/mcp-bundles.json',
42
- 'mcp-bundles.mjs',
43
- 'launcher-runtime.mjs',
44
- 'version-compatibility.mjs',
45
- 'tests/mcp-bundles.test.mjs',
46
- 'tests/launcher-runtime.test.mjs',
47
- 'tests/version-compatibility.test.mjs',
48
- ]);
49
-
50
- const ALL_N1_SHORTCUT_SOURCE_RELATIVE_PATHS = Object.freeze([
51
- 'tests/shortcut-contract.test.ps1',
52
- 'windows/shortcut-inventory.ps1',
53
- 'windows/shortcut-manifest.json',
54
- 'windows/shortcut-reconcile.ps1',
55
- ]);
56
-
57
- test('profiles manifest schemaVersion is 1 and every migrationOrder id resolves', () => {
58
- const validatedManifest = loadAndValidateProfilesManifest();
59
- assert.equal(validatedManifest.schemaVersion, 1);
60
- assert.equal(LAUNCHER_SCHEMA_VERSION, 1);
61
- for (const eachProfileId of validatedManifest.migrationOrder) {
62
- assert.equal(resolveProfileDefinition(validatedManifest, eachProfileId).id, eachProfileId);
63
- }
64
- });
65
-
66
- test('resolveProfileDefinition accepts id, alias, and launcher names', () => {
67
- const validatedManifest = loadAndValidateProfilesManifest();
68
- assert.equal(resolveProfileDefinition(validatedManifest, 'master').id, 'master');
69
- assert.equal(resolveProfileDefinition(validatedManifest, 'default').id, 'master');
70
- assert.equal(resolveProfileDefinition(validatedManifest, 'claude').id, 'master');
71
- assert.equal(resolveProfileDefinition(validatedManifest, 'claude-full').id, 'master');
72
- assert.equal(resolveProfileDefinition(validatedManifest, 'profile-c').id, 'profile-c');
73
- assert.equal(resolveProfileDefinition(validatedManifest, 'claude-profile-c').id, 'profile-c');
74
- assert.equal(resolveProfileDefinition(validatedManifest, 'claude-profile-b-full').id, 'profile-b');
75
- assert.equal(resolveProfileDefinition(validatedManifest, 'Master').id, 'master');
76
- assert.equal(resolveProfileDefinition(validatedManifest, ' CLAUDE-PROFILE-C ').id, 'profile-c');
77
- assert.throws(
78
- () => resolveProfileDefinition(validatedManifest, 'not-a-profile'),
79
- /Unknown profile id or alias/,
80
- );
81
- assert.throws(
82
- () => resolveProfileDefinition(validatedManifest, /** @type {string} */ (/** @type {unknown} */ (42))),
83
- /Unknown profile id or alias/,
84
- );
85
- });
86
-
87
- test('shared allowlist names shared paths, always-local paths, and desktop exclusions', () => {
88
- const validatedAllowlist = loadAndValidateSharedAllowlist();
89
- assert.equal(validatedAllowlist.schemaVersion, 1);
90
- assert.ok(validatedAllowlist.allSharedRelativePaths.includes('scripts'));
91
- assert.ok(validatedAllowlist.allSharedRelativePaths.includes('hooks'));
92
- assert.ok(validatedAllowlist.allAlwaysLocalRelativePaths.includes('settings.json'));
93
- assert.ok(validatedAllowlist.allAlwaysLocalRelativePaths.includes('credentials'));
94
- assert.ok(
95
- validatedAllowlist.allDesktopExcludedPathFragments.some((eachFragment) =>
96
- eachFragment.toLowerCase().includes('desktop'),
97
- ),
98
- );
99
- });
100
-
101
- test('validators reject bad schemaVersion and non-object allowlist', () => {
102
- const rawManifest = loadProfilesManifestDocument();
103
- assert.throws(
104
- () => validateProfilesManifest({ ...rawManifest, schemaVersion: 0 }),
105
- /schemaVersion must be 1/,
106
- );
107
- assert.throws(() => validateSharedAllowlist(null), /shared allowlist must be a JSON object/);
108
- assert.equal(typeof loadSharedAllowlistDocument(), 'object');
109
- });
110
-
111
- test('validators reject duplicate launcher identities across profiles', () => {
112
- const rawManifest = loadProfilesManifestDocument();
113
- const profiles = {
114
- .../** @type {Record<string, object>} */ (rawManifest.profiles),
115
- };
116
- const masterProfile = { ...profiles.master, launcherNames: ['claude', 'claude-profile-c'] };
117
- const profileC = { ...profiles['profile-c'] };
118
- assert.throws(
119
- () =>
120
- validateProfilesManifest({
121
- ...rawManifest,
122
- profiles: { ...profiles, master: masterProfile, 'profile-c': profileC },
123
- }),
124
- /duplicate profile identity/,
125
- );
126
- });
127
-
128
- test('CLAUDE_CONFIG_DIR is the sole authoritative profile-root variable name', () => {
129
- assert.equal(CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE, 'CLAUDE_CONFIG_DIR');
130
- assert.equal(CLAUDE_HOME_ENVIRONMENT_VARIABLE, 'CLAUDE_HOME');
131
- assert.notEqual(CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE, CLAUDE_HOME_ENVIRONMENT_VARIABLE);
132
- const constantsSource = readFileSync(
133
- join(CONTRACT_ROOT_DIRECTORY_PATH, 'config', 'profile-isolation-constants.mjs'),
134
- 'utf8',
135
- );
136
- assert.match(constantsSource, /Sole authoritative profile-root environment variable/);
137
- assert.match(constantsSource, /CLAUDE_HOME is never honored as a profile root/);
138
- });
139
-
140
- test('resolveProfileRootDirectoryPath joins profiles root with directoryName', () => {
141
- const validatedManifest = loadAndValidateProfilesManifest();
142
- const profileC = resolveProfileDefinition(validatedManifest, 'profile-c');
143
- assert.equal(
144
- resolveProfileRootDirectoryPath('/profiles', profileC),
145
- join('/profiles', 'profile-c'),
146
- );
147
- });
148
-
149
- test('package ships contract under scripts/, reserves live deploy for L1, and holds only A1a+N1 files', () => {
150
- const packageJson = JSON.parse(
151
- readFileSync(join(PACKAGE_ROOT_DIRECTORY_PATH, 'package.json'), 'utf8'),
152
- );
153
- assert.ok(packageJson.files.includes(PACKAGE_FILES_WHITELIST_SCRIPTS_ENTRY));
154
- assert.equal(INSTALL_DESTINATION_ROOT_RELATIVE_PATH, 'scripts/profile-isolation-launchers');
155
- assert.equal(LIVE_DEPLOYMENT_RESERVED_FOR, 'L1');
156
- assert.equal(PROFILE_ISOLATION_CONTRACT_OWNER, 'profile-isolation-contract');
157
-
158
- /** @type {string[]} */
159
- const allRelativePaths = [];
160
- /**
161
- * @param {string} currentDirectoryPath
162
- */
163
- function walk(currentDirectoryPath) {
164
- for (const eachEntry of readdirSync(currentDirectoryPath, { withFileTypes: true })) {
165
- const eachAbsolutePath = join(currentDirectoryPath, eachEntry.name);
166
- if (eachEntry.isDirectory()) {
167
- walk(eachAbsolutePath);
168
- continue;
169
- }
170
- if (eachEntry.isFile()) {
171
- allRelativePaths.push(
172
- relative(CONTRACT_ROOT_DIRECTORY_PATH, eachAbsolutePath).split(sep).join('/'),
173
- );
174
- }
175
- }
176
- }
177
- walk(CONTRACT_ROOT_DIRECTORY_PATH);
178
- const allExpectedPaths = new Set([
179
- ...ALL_REQUIRED_CONTRACT_SOURCE_RELATIVE_PATHS,
180
- ...ALL_ALLOWED_MCP_ACTIVATION_RELATIVE_PATHS,
181
- ...ALL_N1_SHORTCUT_SOURCE_RELATIVE_PATHS,
182
- 'profile-isolation-contract.test.mjs',
183
- ]);
184
- for (const eachRelativePath of allRelativePaths) {
185
- assert.ok(allExpectedPaths.has(eachRelativePath), `unexpected package file: ${eachRelativePath}`);
186
- }
187
- for (const eachRequiredPath of [
188
- ...ALL_REQUIRED_CONTRACT_SOURCE_RELATIVE_PATHS,
189
- ...ALL_ALLOWED_MCP_ACTIVATION_RELATIVE_PATHS,
190
- ...ALL_N1_SHORTCUT_SOURCE_RELATIVE_PATHS,
191
- ]) {
192
- const absolutePath = join(CONTRACT_ROOT_DIRECTORY_PATH, ...eachRequiredPath.split('/'));
193
- assert.ok(existsSync(absolutePath) && statSync(absolutePath).isFile());
194
- }
195
- });
196
-
197
- test('committed contract sources are tracked by git (not dirty-worktree-only)', () => {
198
- for (const eachRelativePath of [
199
- ...ALL_REQUIRED_CONTRACT_SOURCE_RELATIVE_PATHS,
200
- ...ALL_N1_SHORTCUT_SOURCE_RELATIVE_PATHS,
201
- ]) {
202
- const repositoryRelativePath =
203
- `packages/claude-dev-env/scripts/profile-isolation-launchers/${eachRelativePath}`;
204
- const lsFilesOutput = execFileSync(
205
- 'git',
206
- ['ls-files', '--', repositoryRelativePath],
207
- { cwd: REPOSITORY_ROOT_DIRECTORY_PATH, encoding: 'utf8' },
208
- ).trim();
209
- assert.ok(lsFilesOutput.length > 0, `expected git-tracked source for ${eachRelativePath}`);
210
- const porcelain = execFileSync(
211
- 'git',
212
- ['status', '--porcelain', '--', repositoryRelativePath],
213
- { cwd: REPOSITORY_ROOT_DIRECTORY_PATH, encoding: 'utf8' },
214
- ).trim();
215
- assert.equal(
216
- porcelain,
217
- '',
218
- `launcher file must be clean committed source: ${eachRelativePath} status=${porcelain}`,
219
- );
220
- }
221
- });
@@ -1,108 +0,0 @@
1
- import { test } from 'node:test';
2
- import { strict as assert } from 'node:assert';
3
- import { mkdtempSync, rmSync, existsSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { tmpdir } from 'node:os';
6
- import { fileURLToPath } from 'node:url';
7
- import { dirname } from 'node:path';
8
- import {
9
- activateLauncherMcpBundle,
10
- activateProfileMcpBundle,
11
- describeLeanServerBoundary,
12
- formatProfileMcpActivationFailure,
13
- listMcpActivationPackageRelativePaths,
14
- resolveProfileIdForLauncherName,
15
- SUPPORTED_ACTIVATION_INTERFACE,
16
- } from '../launcher-runtime.mjs';
17
- import { readEffectiveMcpServerInventory } from '../mcp-bundles.mjs';
18
- import { CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE } from '../config/profile-isolation-constants.mjs';
19
-
20
- const PACKAGE_ROOT = join(
21
- dirname(fileURLToPath(import.meta.url)),
22
- '..',
23
- '..',
24
- '..',
25
- );
26
-
27
- test('claude-profile-a and claude-profile-b resolve to profile-a and profile-b profiles', () => {
28
- assert.equal(resolveProfileIdForLauncherName('claude-profile-a'), 'profile-a');
29
- assert.equal(resolveProfileIdForLauncherName('claude-profile-b'), 'profile-b');
30
- });
31
-
32
- test('activateLauncherMcpBundle materializes lean inventory for profile-a and profile-b', () => {
33
- const profileADir = mkdtempSync(join(tmpdir(), 'launcher-profile-a-'));
34
- const profileBDir = mkdtempSync(join(tmpdir(), 'launcher-profile-b-'));
35
- try {
36
- const profileA = activateLauncherMcpBundle({
37
- launcherName: 'claude-profile-a',
38
- claudeConfigDir: profileADir,
39
- });
40
- const profileB = activateLauncherMcpBundle({
41
- launcherName: 'claude-profile-b',
42
- claudeConfigDir: profileBDir,
43
- });
44
- assert.equal(profileA.activationInterface, SUPPORTED_ACTIVATION_INTERFACE);
45
- assert.equal(profileB.activationInterface, SUPPORTED_ACTIVATION_INTERFACE);
46
- assert.equal(profileA.bundleId, 'lean');
47
- assert.equal(profileB.bundleId, 'lean');
48
- assert.equal(
49
- profileA.environment[CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE],
50
- profileADir,
51
- );
52
- assert.deepEqual(
53
- readEffectiveMcpServerInventory(profileA.mcpConfigPath),
54
- profileA.expectedInventory,
55
- );
56
- assert.deepEqual(
57
- readEffectiveMcpServerInventory(profileB.mcpConfigPath),
58
- profileB.expectedInventory,
59
- );
60
- } finally {
61
- rmSync(profileADir, { recursive: true, force: true });
62
- rmSync(profileBDir, { recursive: true, force: true });
63
- }
64
- });
65
-
66
- test('lean aliases expose only lean servers', () => {
67
- const boundary = describeLeanServerBoundary();
68
- assert.ok(boundary.leanServers.length >= 1);
69
- assert.ok(boundary.fullServers.length >= boundary.leanServers.length);
70
- for (const eachLeanServer of boundary.leanServers) {
71
- assert.ok(boundary.fullServers.includes(eachLeanServer));
72
- }
73
- assert.ok(boundary.fullOnlyServers.every((eachName) => !boundary.leanServers.includes(eachName)));
74
-
75
- const claudeConfigDir = mkdtempSync(join(tmpdir(), 'launcher-lean-'));
76
- try {
77
- const activation = activateProfileMcpBundle({
78
- profileId: 'profile-a',
79
- claudeConfigDir,
80
- });
81
- assert.deepEqual(activation.expectedInventory, boundary.leanServers);
82
- for (const eachFullOnly of boundary.fullOnlyServers) {
83
- assert.ok(!activation.expectedInventory.includes(eachFullOnly));
84
- }
85
- } finally {
86
- rmSync(claudeConfigDir, { recursive: true, force: true });
87
- }
88
- });
89
-
90
- test('unknown launcher name fails with an actionable message', () => {
91
- assert.throws(
92
- () => resolveProfileIdForLauncherName('claude-does-not-exist'),
93
- /no profile owns launcher name/,
94
- );
95
- const message = formatProfileMcpActivationFailure(
96
- 'profile-a',
97
- new Error('bundle missing'),
98
- );
99
- assert.match(message, /profile profile-a mcp activation failed/);
100
- assert.match(message, /bundle missing/);
101
- });
102
-
103
- test('package-relative MCP activation paths exist for pack verification', () => {
104
- const allPaths = listMcpActivationPackageRelativePaths(PACKAGE_ROOT);
105
- for (const eachPath of allPaths) {
106
- assert.ok(existsSync(eachPath), `missing pack path: ${eachPath}`);
107
- }
108
- });