claude-dev-env 2.15.0 → 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 (28) hide show
  1. package/_shared/advisor/scripts/tier_model_ids.py +6 -2
  2. package/bin/install.mjs +35 -32
  3. package/bin/install.test.mjs +42 -8
  4. package/package.json +2 -2
  5. package/scripts/AGENTS.md +0 -7
  6. package/scripts/claude_chain_runner.py +49 -2
  7. package/scripts/invoke_code_review.py +48 -51
  8. package/scripts/resolve_worker_spawn.py +42 -47
  9. package/scripts/test_claude_chain_runner.py +28 -0
  10. package/scripts/test_dispatcher_profile_import.py +184 -0
  11. package/scripts/test_resolve_worker_spawn.py +119 -1
  12. package/scripts/test_validate_instruction_pairs.py +30 -0
  13. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +0 -25
  14. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +0 -60
  15. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +0 -54
  16. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +0 -64
  17. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +0 -180
  18. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +0 -288
  19. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +0 -275
  20. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +0 -221
  21. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +0 -108
  22. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +0 -147
  23. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +0 -102
  24. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +0 -210
  25. package/scripts/profile-isolation-launchers/version-compatibility.mjs +0 -299
  26. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +0 -127
  27. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +0 -51
  28. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +0 -77
@@ -1,180 +0,0 @@
1
- /**
2
- * Launcher runtime helpers for MCP activation through a supported interface.
3
- */
4
-
5
- import { join } from 'node:path';
6
- import {
7
- CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE,
8
- loadProfilesManifestDocument,
9
- MCP_BUNDLE_FULL,
10
- MCP_BUNDLE_LEAN,
11
- } from './config/profile-isolation-constants.mjs';
12
- import {
13
- resolveProfileDefinition,
14
- validateProfilesManifest,
15
- } from './lib/profile-manifest.mjs';
16
- import {
17
- listServerNamesForBundle,
18
- loadMcpBundlesDocument,
19
- materializeProfileMcpConfig,
20
- resolveProfileMcpBundleId,
21
- SUPPORTED_ACTIVATION_INTERFACE,
22
- } from './mcp-bundles.mjs';
23
- import {
24
- classifyVersionCompatibility,
25
- shouldBlockLaunch,
26
- } from './version-compatibility.mjs';
27
-
28
- /**
29
- * Resolve a launcher name to a profile id from the profiles manifest.
30
- *
31
- * @param {string} launcherName
32
- * @returns {string}
33
- */
34
- export function resolveProfileIdForLauncherName(launcherName) {
35
- const manifest = validateProfilesManifest(loadProfilesManifestDocument());
36
- try {
37
- return resolveProfileDefinition(manifest, launcherName).id;
38
- } catch (errorValue) {
39
- if (
40
- errorValue instanceof Error
41
- && errorValue.message.startsWith('Unknown profile id or alias:')
42
- ) {
43
- throw new Error(`no profile owns launcher name: ${launcherName}`);
44
- }
45
- throw errorValue;
46
- }
47
- }
48
-
49
- /**
50
- * Assemble activation for one profile into a disposable CLAUDE_CONFIG_DIR.
51
- *
52
- * @param {{
53
- * profileId: string,
54
- * claudeConfigDir: string,
55
- * }} parameters
56
- * @returns {{
57
- * profileId: string,
58
- * environment: Record<string, string>,
59
- * activationInterface: string,
60
- * mcpConfigPath: string,
61
- * bundleId: string,
62
- * allServerNames: string[],
63
- * expectedInventory: string[],
64
- * }}
65
- */
66
- export function activateProfileMcpBundle(parameters) {
67
- const bundlesDocument = loadMcpBundlesDocument();
68
- const materialization = materializeProfileMcpConfig({
69
- profileId: parameters.profileId,
70
- claudeConfigDir: parameters.claudeConfigDir,
71
- bundlesDocument,
72
- });
73
- const expectedInventory = [...materialization.allServerNames].sort();
74
- return {
75
- profileId: parameters.profileId,
76
- environment: {
77
- [CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE]: parameters.claudeConfigDir,
78
- },
79
- activationInterface: materialization.activationInterface,
80
- mcpConfigPath: materialization.mcpConfigPath,
81
- bundleId: materialization.bundleId,
82
- allServerNames: materialization.allServerNames,
83
- expectedInventory,
84
- };
85
- }
86
-
87
- /**
88
- * Activate MCP for a launcher name (claude-profile-a / claude-profile-b).
89
- *
90
- * @param {{
91
- * launcherName: string,
92
- * claudeConfigDir: string,
93
- * }} parameters
94
- * @returns {ReturnType<typeof activateProfileMcpBundle>}
95
- */
96
- export function activateLauncherMcpBundle(parameters) {
97
- const profileId = resolveProfileIdForLauncherName(parameters.launcherName);
98
- return activateProfileMcpBundle({
99
- profileId,
100
- claudeConfigDir: parameters.claudeConfigDir,
101
- });
102
- }
103
-
104
- /**
105
- * Lean-boundary check: lean inventory is a subset of full and excludes full-only servers.
106
- *
107
- * @returns {{leanServers: string[], fullServers: string[], fullOnlyServers: string[]}}
108
- */
109
- export function describeLeanServerBoundary() {
110
- const bundlesDocument = loadMcpBundlesDocument();
111
- const leanServers = listServerNamesForBundle(MCP_BUNDLE_LEAN, bundlesDocument).sort();
112
- const fullServers = listServerNamesForBundle(MCP_BUNDLE_FULL, bundlesDocument).sort();
113
- const leanSet = new Set(leanServers);
114
- const fullOnlyServers = fullServers.filter((eachName) => !leanSet.has(eachName));
115
- return { leanServers, fullServers, fullOnlyServers };
116
- }
117
-
118
- /**
119
- * Build a diagnostic when a required bundle or profile is invalid.
120
- *
121
- * @param {string} profileId
122
- * @param {unknown} errorValue
123
- * @returns {string}
124
- */
125
- export function formatProfileMcpActivationFailure(profileId, errorValue) {
126
- const message = errorValue instanceof Error ? errorValue.message : String(errorValue);
127
- return `profile ${profileId} mcp activation failed: ${message}`;
128
- }
129
-
130
- /**
131
- * Evaluate CLI vs Desktop version compatibility from already-collected probes.
132
- *
133
- * Call this before any profile-state mutation. Spawn and binary discovery stay
134
- * in the caller; this adapter only runs the shared classifier.
135
- *
136
- * @param {{
137
- * cli: import('./version-compatibility.mjs').VersionProbeResult,
138
- * desktop: import('./version-compatibility.mjs').VersionProbeResult,
139
- * }} parameters
140
- * @returns {import('./version-compatibility.mjs').CompatibilityResult}
141
- */
142
- export function evaluateLauncherVersionCompatibility(parameters) {
143
- return classifyVersionCompatibility(parameters);
144
- }
145
-
146
- /**
147
- * Build a preflight failure message when compatibility blocks launch.
148
- *
149
- * @param {import('./version-compatibility.mjs').CompatibilityResult} result
150
- * @returns {string}
151
- */
152
- export function formatCompatibilityPreflightFailure(result) {
153
- return `version compatibility preflight blocked launch (${result.class}): ${result.message}`;
154
- }
155
-
156
- /**
157
- * Path helpers for tests and pack verification.
158
- *
159
- * @param {string} packageRoot
160
- * @returns {string[]}
161
- */
162
- export function listMcpActivationPackageRelativePaths(packageRoot) {
163
- return [
164
- join(packageRoot, 'scripts/profile-isolation-launchers/mcp-bundles.mjs'),
165
- join(packageRoot, 'scripts/profile-isolation-launchers/launcher-runtime.mjs'),
166
- join(packageRoot, 'scripts/profile-isolation-launchers/version-compatibility.mjs'),
167
- join(packageRoot, 'scripts/profile-isolation-launchers/config/mcp-bundles.json'),
168
- join(packageRoot, 'scripts/profile-isolation-launchers/config/profiles.manifest.json'),
169
- join(packageRoot, 'scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs'),
170
- join(packageRoot, 'scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs'),
171
- join(packageRoot, 'scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs'),
172
- ];
173
- }
174
-
175
- export {
176
- SUPPORTED_ACTIVATION_INTERFACE,
177
- resolveProfileMcpBundleId,
178
- classifyVersionCompatibility,
179
- shouldBlockLaunch,
180
- };
@@ -1,288 +0,0 @@
1
- import { join } from 'node:path';
2
-
3
- import {
4
- LAUNCHER_SCHEMA_VERSION,
5
- loadProfilesManifestDocument,
6
- loadSharedAllowlistDocument,
7
- MCP_BUNDLE_FULL,
8
- MCP_BUNDLE_LEAN,
9
- MIGRATION_MODE_CLEAN_LOCAL_RUNTIME,
10
- MIGRATION_MODE_MATERIALIZE_FROM_LEGACY,
11
- } from '../config/profile-isolation-constants.mjs';
12
-
13
- /**
14
- * @typedef {{
15
- * id: string,
16
- * aliases: string[],
17
- * directoryName: string,
18
- * launcherNames: string[],
19
- * fullLauncherNames: string[],
20
- * migrationMode: string,
21
- * mcpBundle: string,
22
- * }} ProfileDefinition
23
- */
24
-
25
- /**
26
- * @typedef {{
27
- * schemaVersion: number,
28
- * profilesRootPlaceholder: string,
29
- * sharedSourcePlaceholder: string,
30
- * pluginSeedPlaceholder: string,
31
- * migrationOrder: string[],
32
- * profileById: Record<string, ProfileDefinition>,
33
- * }} ValidatedProfilesManifest
34
- */
35
-
36
- /**
37
- * @typedef {{
38
- * schemaVersion: number,
39
- * allSharedRelativePaths: string[],
40
- * allAlwaysLocalRelativePaths: string[],
41
- * allDesktopExcludedPathFragments: string[],
42
- * }} ValidatedSharedAllowlist
43
- */
44
-
45
- const ALL_ALLOWED_MIGRATION_MODES = new Set([
46
- MIGRATION_MODE_CLEAN_LOCAL_RUNTIME,
47
- MIGRATION_MODE_MATERIALIZE_FROM_LEGACY,
48
- ]);
49
-
50
- const ALL_ALLOWED_MCP_BUNDLES = new Set([MCP_BUNDLE_LEAN, MCP_BUNDLE_FULL]);
51
-
52
- /**
53
- * @param {unknown} maybeManifest
54
- * @returns {ValidatedProfilesManifest}
55
- */
56
- export function validateProfilesManifest(maybeManifest) {
57
- if (!isPlainObject(maybeManifest)) {
58
- throw new Error('profiles manifest must be a JSON object');
59
- }
60
- const schemaVersion = maybeManifest.schemaVersion;
61
- if (schemaVersion !== LAUNCHER_SCHEMA_VERSION) {
62
- throw new Error(`profiles manifest schemaVersion must be ${LAUNCHER_SCHEMA_VERSION}`);
63
- }
64
- if (!Array.isArray(maybeManifest.migrationOrder) || maybeManifest.migrationOrder.length === 0) {
65
- throw new Error('profiles manifest migrationOrder must be a non-empty array');
66
- }
67
- if (!isPlainObject(maybeManifest.profiles)) {
68
- throw new Error('profiles manifest profiles must be an object');
69
- }
70
- /** @type {Record<string, ProfileDefinition>} */
71
- const profileById = {};
72
- for (const [eachProfileKey, eachProfileValue] of Object.entries(maybeManifest.profiles)) {
73
- profileById[eachProfileKey] = validateProfileDefinition(eachProfileKey, eachProfileValue);
74
- }
75
- assertUniqueProfileIdentities(profileById);
76
- /** @type {string[]} */
77
- const allMigrationOrderIds = [];
78
- for (const eachOrderedProfileId of maybeManifest.migrationOrder) {
79
- if (typeof eachOrderedProfileId !== 'string' || !(eachOrderedProfileId in profileById)) {
80
- throw new Error(`migrationOrder references unknown profile id: ${String(eachOrderedProfileId)}`);
81
- }
82
- allMigrationOrderIds.push(eachOrderedProfileId);
83
- }
84
- return {
85
- schemaVersion,
86
- profilesRootPlaceholder: requireNonEmptyString(
87
- maybeManifest.profilesRootPlaceholder,
88
- 'profilesRootPlaceholder',
89
- ),
90
- sharedSourcePlaceholder: requireNonEmptyString(
91
- maybeManifest.sharedSourcePlaceholder,
92
- 'sharedSourcePlaceholder',
93
- ),
94
- pluginSeedPlaceholder: requireNonEmptyString(
95
- maybeManifest.pluginSeedPlaceholder,
96
- 'pluginSeedPlaceholder',
97
- ),
98
- migrationOrder: allMigrationOrderIds,
99
- profileById,
100
- };
101
- }
102
-
103
- /**
104
- * @param {unknown} maybeAllowlist
105
- * @returns {ValidatedSharedAllowlist}
106
- */
107
- export function validateSharedAllowlist(maybeAllowlist) {
108
- if (!isPlainObject(maybeAllowlist)) {
109
- throw new Error('shared allowlist must be a JSON object');
110
- }
111
- if (maybeAllowlist.schemaVersion !== LAUNCHER_SCHEMA_VERSION) {
112
- throw new Error(`shared allowlist schemaVersion must be ${LAUNCHER_SCHEMA_VERSION}`);
113
- }
114
- return {
115
- schemaVersion: LAUNCHER_SCHEMA_VERSION,
116
- allSharedRelativePaths: requireStringArray(
117
- maybeAllowlist.allSharedRelativePaths,
118
- 'allSharedRelativePaths',
119
- ),
120
- allAlwaysLocalRelativePaths: requireStringArray(
121
- maybeAllowlist.allAlwaysLocalRelativePaths,
122
- 'allAlwaysLocalRelativePaths',
123
- ),
124
- allDesktopExcludedPathFragments: requireStringArray(
125
- maybeAllowlist.allDesktopExcludedPathFragments,
126
- 'allDesktopExcludedPathFragments',
127
- ),
128
- };
129
- }
130
-
131
- /**
132
- * @returns {ValidatedProfilesManifest}
133
- */
134
- export function loadAndValidateProfilesManifest() {
135
- return validateProfilesManifest(loadProfilesManifestDocument());
136
- }
137
-
138
- /**
139
- * @returns {ValidatedSharedAllowlist}
140
- */
141
- export function loadAndValidateSharedAllowlist() {
142
- return validateSharedAllowlist(loadSharedAllowlistDocument());
143
- }
144
-
145
- /**
146
- * @param {ValidatedProfilesManifest} validatedManifest
147
- * @param {string} profileIdOrAlias
148
- * @returns {ProfileDefinition}
149
- */
150
- export function resolveProfileDefinition(validatedManifest, profileIdOrAlias) {
151
- if (typeof profileIdOrAlias !== 'string') {
152
- throw new Error(`Unknown profile id or alias: ${String(profileIdOrAlias)}`);
153
- }
154
- const normalizedIdentity = profileIdOrAlias.trim().toLowerCase();
155
- for (const eachProfile of Object.values(validatedManifest.profileById)) {
156
- const allProfileIdentities = [
157
- eachProfile.id,
158
- ...eachProfile.aliases,
159
- ...eachProfile.launcherNames,
160
- ...eachProfile.fullLauncherNames,
161
- ];
162
- if (
163
- allProfileIdentities.some(
164
- (eachIdentity) => eachIdentity.trim().toLowerCase() === normalizedIdentity,
165
- )
166
- ) {
167
- return eachProfile;
168
- }
169
- }
170
- throw new Error(`Unknown profile id or alias: ${profileIdOrAlias}`);
171
- }
172
-
173
- /**
174
- * @param {string} profilesRootDirectoryPath
175
- * @param {ProfileDefinition} profileDefinition
176
- * @returns {string}
177
- */
178
- export function resolveProfileRootDirectoryPath(profilesRootDirectoryPath, profileDefinition) {
179
- return join(profilesRootDirectoryPath, profileDefinition.directoryName);
180
- }
181
-
182
- /**
183
- * Reject a launcher name, alias, or id claimed by more than one profile.
184
- *
185
- * ::
186
- *
187
- * profiles['profile-c'].launcherNames = ["claude"]
188
- * profiles.master.launcherNames = ["claude"]
189
- * flag: same identity claimed twice → resolve order would be nondeterministic
190
- * ok: each identity maps to exactly one profile id
191
- *
192
- * @param {Record<string, ProfileDefinition>} profileById
193
- * @returns {void}
194
- */
195
- function assertUniqueProfileIdentities(profileById) {
196
- /** @type {Map<string, string>} */
197
- const profileIdByNormalizedIdentity = new Map();
198
- for (const eachProfile of Object.values(profileById)) {
199
- const allProfileIdentities = [
200
- eachProfile.id,
201
- ...eachProfile.aliases,
202
- ...eachProfile.launcherNames,
203
- ...eachProfile.fullLauncherNames,
204
- ];
205
- for (const eachIdentity of allProfileIdentities) {
206
- const normalizedIdentity = eachIdentity.trim().toLowerCase();
207
- const previousProfileId = profileIdByNormalizedIdentity.get(normalizedIdentity);
208
- if (previousProfileId !== undefined) {
209
- throw new Error(
210
- `duplicate profile identity '${eachIdentity}' claimed by ${previousProfileId} and ${eachProfile.id}`,
211
- );
212
- }
213
- profileIdByNormalizedIdentity.set(normalizedIdentity, eachProfile.id);
214
- }
215
- }
216
- }
217
-
218
- /**
219
- * @param {string} profileKey
220
- * @param {unknown} maybeProfile
221
- * @returns {ProfileDefinition}
222
- */
223
- function validateProfileDefinition(profileKey, maybeProfile) {
224
- if (!isPlainObject(maybeProfile)) {
225
- throw new Error(`profile ${profileKey} must be an object`);
226
- }
227
- const profileId = requireNonEmptyString(maybeProfile.id, `${profileKey}.id`);
228
- if (profileId !== profileKey) {
229
- throw new Error(`profile key ${profileKey} must match id ${profileId}`);
230
- }
231
- const migrationMode = requireNonEmptyString(maybeProfile.migrationMode, `${profileKey}.migrationMode`);
232
- if (!ALL_ALLOWED_MIGRATION_MODES.has(migrationMode)) {
233
- throw new Error(`profile ${profileKey} has unsupported migrationMode: ${migrationMode}`);
234
- }
235
- const mcpBundle = requireNonEmptyString(maybeProfile.mcpBundle, `${profileKey}.mcpBundle`);
236
- if (!ALL_ALLOWED_MCP_BUNDLES.has(mcpBundle)) {
237
- throw new Error(`profile ${profileKey} has unsupported mcpBundle: ${mcpBundle}`);
238
- }
239
- return {
240
- id: profileId,
241
- aliases: requireStringArray(maybeProfile.aliases, `${profileKey}.aliases`),
242
- directoryName: requireNonEmptyString(maybeProfile.directoryName, `${profileKey}.directoryName`),
243
- launcherNames: requireStringArray(maybeProfile.launcherNames, `${profileKey}.launcherNames`),
244
- fullLauncherNames: requireStringArray(
245
- maybeProfile.fullLauncherNames,
246
- `${profileKey}.fullLauncherNames`,
247
- ),
248
- migrationMode,
249
- mcpBundle,
250
- };
251
- }
252
-
253
- /**
254
- * @param {unknown} candidate
255
- * @returns {candidate is Record<string, unknown>}
256
- */
257
- function isPlainObject(candidate) {
258
- return typeof candidate === 'object' && candidate !== null && !Array.isArray(candidate);
259
- }
260
-
261
- /**
262
- * @param {unknown} candidate
263
- * @param {string} fieldName
264
- * @returns {string}
265
- */
266
- function requireNonEmptyString(candidate, fieldName) {
267
- if (typeof candidate !== 'string' || candidate.trim() === '') {
268
- throw new Error(`${fieldName} must be a non-empty string`);
269
- }
270
- return candidate;
271
- }
272
-
273
- /**
274
- * @param {unknown} candidate
275
- * @param {string} fieldName
276
- * @returns {string[]}
277
- */
278
- function requireStringArray(candidate, fieldName) {
279
- if (
280
- !Array.isArray(candidate) ||
281
- candidate.some(
282
- (eachEntry) => typeof eachEntry !== 'string' || eachEntry.trim() === '',
283
- )
284
- ) {
285
- throw new Error(`${fieldName} must be an array of non-empty strings`);
286
- }
287
- return /** @type {string[]} */ (candidate);
288
- }