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,299 +0,0 @@
1
- /**
2
- * Pure CLI vs Desktop embedded-version compatibility classifier.
3
- *
4
- * The classifier takes probe results only. Callers own process spawn, timeouts,
5
- * and binary discovery. Fail closed on unreadable or missing inputs.
6
- */
7
-
8
- export const COMPATIBILITY_POLICY_VERSION = 1;
9
-
10
- /** @typedef {'pass' | 'warn' | 'block'} CompatibilityAction */
11
- /** @typedef {
12
- * | 'equal'
13
- * | 'patch-drift'
14
- * | 'minor-drift'
15
- * | 'major-drift'
16
- * | 'missing-binary'
17
- * | 'unreadable'
18
- * | 'process-error'
19
- * | 'non-semver'
20
- * } CompatibilityClass */
21
-
22
- /**
23
- * Action table for every compatibility class. Missing / unreadable / process
24
- * errors and major/minor drift block; patch drift warns; equality passes.
25
- */
26
- export const COMPATIBILITY_ACTION_BY_CLASS = Object.freeze({
27
- equal: 'pass',
28
- 'patch-drift': 'warn',
29
- 'minor-drift': 'block',
30
- 'major-drift': 'block',
31
- 'missing-binary': 'block',
32
- unreadable: 'block',
33
- 'process-error': 'block',
34
- 'non-semver': 'block',
35
- });
36
-
37
- /**
38
- * @typedef {{
39
- * path: string | null,
40
- * versionText: string | null,
41
- * errorCode?: 'missing' | 'unreadable' | 'process-error' | null,
42
- * errorMessage?: string | null,
43
- * }} VersionProbeResult
44
- */
45
-
46
- /**
47
- * @typedef {{
48
- * cliPath: string | null,
49
- * desktopPath: string | null,
50
- * cliVersion: string | null,
51
- * desktopVersion: string | null,
52
- * policyVersion: number,
53
- * class: CompatibilityClass,
54
- * action: CompatibilityAction,
55
- * message: string,
56
- * }} CompatibilityResult
57
- */
58
-
59
- /**
60
- * Classify CLI and Desktop embedded version compatibility from probe results.
61
- *
62
- * ::
63
- *
64
- * classifyVersionCompatibility({
65
- * cli: { path: 'cli', versionText: '2.1.220' },
66
- * desktop: { path: 'desktop', versionText: '2.1.220' },
67
- * })
68
- * // class: equal, action: pass
69
- *
70
- * classifyVersionCompatibility({
71
- * cli: { path: 'cli', versionText: '2.1.220' },
72
- * desktop: { path: 'desktop', versionText: '2.1.219' },
73
- * })
74
- * // class: patch-drift, action: warn
75
- *
76
- * @param {{
77
- * cli: VersionProbeResult,
78
- * desktop: VersionProbeResult,
79
- * }} parameters
80
- * @returns {CompatibilityResult}
81
- */
82
- export function classifyVersionCompatibility(parameters) {
83
- const cliProbe = normalizeProbe(parameters.cli);
84
- const desktopProbe = normalizeProbe(parameters.desktop);
85
-
86
- const base = {
87
- cliPath: cliProbe.path,
88
- desktopPath: desktopProbe.path,
89
- cliVersion: extractVersionLabel(cliProbe.versionText),
90
- desktopVersion: extractVersionLabel(desktopProbe.versionText),
91
- policyVersion: COMPATIBILITY_POLICY_VERSION,
92
- };
93
-
94
- const probeFailure = firstProbeFailure(cliProbe, desktopProbe);
95
- if (probeFailure) {
96
- return finalizeResult({
97
- ...base,
98
- class: probeFailure.className,
99
- message: probeFailure.message,
100
- });
101
- }
102
-
103
- const cliVersion = parseSemver(cliProbe.versionText);
104
- const desktopVersion = parseSemver(desktopProbe.versionText);
105
- if (!cliVersion || !desktopVersion) {
106
- return finalizeResult({
107
- ...base,
108
- class: 'non-semver',
109
- message: formatNonSemverMessage(cliProbe.versionText, desktopProbe.versionText),
110
- });
111
- }
112
-
113
- if (
114
- cliVersion.major === desktopVersion.major
115
- && cliVersion.minor === desktopVersion.minor
116
- && cliVersion.patch === desktopVersion.patch
117
- ) {
118
- return finalizeResult({
119
- ...base,
120
- class: 'equal',
121
- message: `CLI and Desktop versions match (${formatSemver(cliVersion)})`,
122
- });
123
- }
124
-
125
- if (cliVersion.major !== desktopVersion.major) {
126
- return finalizeResult({
127
- ...base,
128
- class: 'major-drift',
129
- message: formatDriftMessage('major', cliVersion, desktopVersion),
130
- });
131
- }
132
-
133
- if (cliVersion.minor !== desktopVersion.minor) {
134
- return finalizeResult({
135
- ...base,
136
- class: 'minor-drift',
137
- message: formatDriftMessage('minor', cliVersion, desktopVersion),
138
- });
139
- }
140
-
141
- return finalizeResult({
142
- ...base,
143
- class: 'patch-drift',
144
- message: formatDriftMessage('patch', cliVersion, desktopVersion),
145
- });
146
- }
147
-
148
- /**
149
- * True when the classified action blocks launch before profile-state mutation.
150
- *
151
- * @param {CompatibilityResult} result
152
- * @returns {boolean}
153
- */
154
- export function shouldBlockLaunch(result) {
155
- return result.action === 'block';
156
- }
157
-
158
- /**
159
- * Parse a version string into major.minor.patch when the leading triple is semver-shaped.
160
- *
161
- * @param {string | null | undefined} versionText
162
- * @returns {{major: number, minor: number, patch: number} | null}
163
- */
164
- export function parseSemver(versionText) {
165
- if (typeof versionText !== 'string') {
166
- return null;
167
- }
168
- const trimmed = versionText.trim();
169
- if (!trimmed) {
170
- return null;
171
- }
172
- const match = trimmed.match(/(\d+)\.(\d+)\.(\d+)/);
173
- if (!match) {
174
- return null;
175
- }
176
- return {
177
- major: Number(match[1]),
178
- minor: Number(match[2]),
179
- patch: Number(match[3]),
180
- };
181
- }
182
-
183
- /**
184
- * @param {VersionProbeResult | null | undefined} probe
185
- * @returns {VersionProbeResult}
186
- */
187
- function normalizeProbe(probe) {
188
- if (!probe || typeof probe !== 'object') {
189
- return {
190
- path: null,
191
- versionText: null,
192
- errorCode: 'missing',
193
- errorMessage: 'probe missing',
194
- };
195
- }
196
- return {
197
- path: typeof probe.path === 'string' ? probe.path : null,
198
- versionText: typeof probe.versionText === 'string' ? probe.versionText : null,
199
- errorCode: probe.errorCode ?? null,
200
- errorMessage: probe.errorMessage ?? null,
201
- };
202
- }
203
-
204
- /**
205
- * @param {VersionProbeResult} cliProbe
206
- * @param {VersionProbeResult} desktopProbe
207
- * @returns {{className: CompatibilityClass, message: string} | null}
208
- */
209
- function firstProbeFailure(cliProbe, desktopProbe) {
210
- for (const [eachLabel, eachProbe] of [
211
- ['CLI', cliProbe],
212
- ['Desktop', desktopProbe],
213
- ]) {
214
- if (eachProbe.errorCode === 'missing' || !eachProbe.path) {
215
- return {
216
- className: 'missing-binary',
217
- message: `${eachLabel} binary path is missing`,
218
- };
219
- }
220
- if (eachProbe.errorCode === 'process-error') {
221
- return {
222
- className: 'process-error',
223
- message: `${eachLabel} version probe failed: ${eachProbe.errorMessage || 'process error'}`,
224
- };
225
- }
226
- if (eachProbe.errorCode === 'unreadable' || eachProbe.versionText === null) {
227
- return {
228
- className: 'unreadable',
229
- message: `${eachLabel} version output is unreadable`,
230
- };
231
- }
232
- }
233
- return null;
234
- }
235
-
236
- /**
237
- * @param {string | null} versionText
238
- * @returns {string | null}
239
- */
240
- function extractVersionLabel(versionText) {
241
- if (typeof versionText !== 'string') {
242
- return null;
243
- }
244
- const trimmed = versionText.trim();
245
- return trimmed || null;
246
- }
247
-
248
- /**
249
- * @param {{
250
- * cliPath: string | null,
251
- * desktopPath: string | null,
252
- * cliVersion: string | null,
253
- * desktopVersion: string | null,
254
- * policyVersion: number,
255
- * class: CompatibilityClass,
256
- * message: string,
257
- * }} partial
258
- * @returns {CompatibilityResult}
259
- */
260
- function finalizeResult(partial) {
261
- const action = COMPATIBILITY_ACTION_BY_CLASS[partial.class];
262
- return {
263
- cliPath: partial.cliPath,
264
- desktopPath: partial.desktopPath,
265
- cliVersion: partial.cliVersion,
266
- desktopVersion: partial.desktopVersion,
267
- policyVersion: partial.policyVersion,
268
- class: partial.class,
269
- action,
270
- message: partial.message,
271
- };
272
- }
273
-
274
- /**
275
- * @param {'major' | 'minor' | 'patch'} axis
276
- * @param {{major: number, minor: number, patch: number}} cliVersion
277
- * @param {{major: number, minor: number, patch: number}} desktopVersion
278
- * @returns {string}
279
- */
280
- function formatDriftMessage(axis, cliVersion, desktopVersion) {
281
- return `${axis} drift: CLI ${formatSemver(cliVersion)} vs Desktop ${formatSemver(desktopVersion)}`;
282
- }
283
-
284
- /**
285
- * @param {{major: number, minor: number, patch: number}} version
286
- * @returns {string}
287
- */
288
- function formatSemver(version) {
289
- return `${version.major}.${version.minor}.${version.patch}`;
290
- }
291
-
292
- /**
293
- * @param {string | null} cliText
294
- * @param {string | null} desktopText
295
- * @returns {string}
296
- */
297
- function formatNonSemverMessage(cliText, desktopText) {
298
- return `non-semver version text: CLI=${JSON.stringify(cliText)} Desktop=${JSON.stringify(desktopText)}`;
299
- }
@@ -1,127 +0,0 @@
1
- # Read-only inventory of managed Claude profile shortcuts.
2
- # Does not mutate Desktop, Start Menu, or taskbar state.
3
-
4
- [CmdletBinding()]
5
- param(
6
- [string]$ManifestPath = (Join-Path $PSScriptRoot 'shortcut-manifest.json'),
7
- [string]$DesktopPath = [Environment]::GetFolderPath('Desktop'),
8
- [string]$StartMenuPath = [Environment]::GetFolderPath('StartMenu'),
9
- [string]$OutputPath
10
- )
11
-
12
- Set-StrictMode -Version Latest
13
- $ErrorActionPreference = 'Stop'
14
-
15
- if (-not (Test-Path -LiteralPath $ManifestPath)) {
16
- throw "shortcut manifest missing: $ManifestPath"
17
- }
18
-
19
- $manifest = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
20
- if ($manifest.schemaVersion -ne 1) {
21
- throw "unsupported shortcut manifest schemaVersion: $($manifest.schemaVersion)"
22
- }
23
- if ($null -eq $manifest.allManagedShortcuts) {
24
- throw 'shortcut manifest allManagedShortcuts is required'
25
- }
26
-
27
- $allSeenIds = @{}
28
- $allSeenVisibleNames = @{}
29
- $allSeenGrouping = @{}
30
- foreach ($eachShortcut in $manifest.allManagedShortcuts) {
31
- foreach ($eachFieldName in @('id', 'visibleName', 'source', 'profileId', 'locationKind', 'groupingIdentity')) {
32
- $fieldValue = $eachShortcut.$eachFieldName
33
- if ([string]::IsNullOrWhiteSpace([string]$fieldValue)) {
34
- throw "shortcut missing required field $eachFieldName"
35
- }
36
- }
37
- if ($allSeenIds.ContainsKey($eachShortcut.id)) {
38
- throw "duplicate shortcut id: $($eachShortcut.id)"
39
- }
40
- if ($allSeenVisibleNames.ContainsKey($eachShortcut.visibleName)) {
41
- throw "duplicate shortcut visibleName: $($eachShortcut.visibleName)"
42
- }
43
- if ($allSeenGrouping.ContainsKey($eachShortcut.groupingIdentity)) {
44
- throw "duplicate shortcut groupingIdentity: $($eachShortcut.groupingIdentity)"
45
- }
46
- $allSeenIds[$eachShortcut.id] = $true
47
- $allSeenVisibleNames[$eachShortcut.visibleName] = $true
48
- $allSeenGrouping[$eachShortcut.groupingIdentity] = $true
49
- }
50
-
51
- function Get-ShortcutMetadata {
52
- param(
53
- [Parameter(Mandatory = $true)]
54
- [string]$ShortcutPath,
55
- [Parameter(Mandatory = $true)]
56
- $Shell
57
- )
58
- if (-not (Test-Path -LiteralPath $ShortcutPath)) {
59
- return $null
60
- }
61
- $link = $Shell.CreateShortcut($ShortcutPath)
62
- return [pscustomobject]@{
63
- path = $ShortcutPath
64
- targetPath = $link.TargetPath
65
- arguments = $link.Arguments
66
- workingDirectory = $link.WorkingDirectory
67
- iconLocation = $link.IconLocation
68
- exists = $true
69
- }
70
- }
71
-
72
- $shell = New-Object -ComObject WScript.Shell
73
- $allRows = [System.Collections.Generic.List[object]]::new()
74
- try {
75
- foreach ($eachShortcut in $manifest.allManagedShortcuts) {
76
- if ($eachShortcut.locationKind -eq 'desktop') {
77
- $baseDirectory = $DesktopPath
78
- }
79
- elseif ($eachShortcut.locationKind -eq 'start-menu') {
80
- $baseDirectory = $StartMenuPath
81
- }
82
- else {
83
- throw "unsupported locationKind for $($eachShortcut.id): $($eachShortcut.locationKind)"
84
- }
85
- $shortcutPath = Join-Path $baseDirectory ($eachShortcut.visibleName + '.lnk')
86
- $metadata = Get-ShortcutMetadata -ShortcutPath $shortcutPath -Shell $shell
87
- $allRows.Add([pscustomobject]@{
88
- id = $eachShortcut.id
89
- visibleName = $eachShortcut.visibleName
90
- source = $eachShortcut.source
91
- profileId = $eachShortcut.profileId
92
- locationKind = $eachShortcut.locationKind
93
- targetKind = $eachShortcut.targetKind
94
- launcherName = $eachShortcut.launcherName
95
- groupingIdentity = $eachShortcut.groupingIdentity
96
- expectedPath = $shortcutPath
97
- exists = [bool]$metadata
98
- targetPath = if ($metadata) { $metadata.targetPath } else { $null }
99
- arguments = if ($metadata) { $metadata.arguments } else { $null }
100
- workingDirectory = if ($metadata) { $metadata.workingDirectory } else { $null }
101
- iconLocation = if ($metadata) { $metadata.iconLocation } else { $null }
102
- liveMutationAuthorized = [bool]$manifest.policy.liveMutationAuthorized
103
- })
104
- }
105
- }
106
- finally {
107
- [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($shell)
108
- }
109
-
110
- $result = [pscustomobject]@{
111
- mode = 'read-only-inventory'
112
- policy = $manifest.policy
113
- scannedAt = (Get-Date).ToString('o')
114
- desktopPath = $DesktopPath
115
- startMenuPath = $StartMenuPath
116
- shortcuts = $allRows
117
- }
118
-
119
- $json = $result | ConvertTo-Json -Depth 6
120
- if ($OutputPath) {
121
- $directory = Split-Path -Parent $OutputPath
122
- if ($directory -and -not (Test-Path -LiteralPath $directory)) {
123
- New-Item -ItemType Directory -Path $directory | Out-Null
124
- }
125
- [IO.File]::WriteAllText($OutputPath, $json, [Text.UTF8Encoding]::new($false))
126
- }
127
- Write-Output $json
@@ -1,51 +0,0 @@
1
- {
2
- "schemaVersion": 1,
3
- "policy": {
4
- "unsuffixedNames": "Native Desktop wins: Claude Profile A and Claude Profile B",
5
- "browserNames": "Chrome variants use explicit Web or Chrome source labels",
6
- "authority": "The owner confirms or revises this policy before live mutation.",
7
- "liveMutationAuthorized": false
8
- },
9
- "allManagedShortcuts": [
10
- {
11
- "id": "desktop-profile-a-native",
12
- "visibleName": "Claude Profile A",
13
- "source": "native-desktop",
14
- "profileId": "profile-a",
15
- "locationKind": "desktop",
16
- "targetKind": "native-launcher",
17
- "launcherName": "claude-profile-a",
18
- "groupingIdentity": "native-profile-a"
19
- },
20
- {
21
- "id": "desktop-profile-b-native",
22
- "visibleName": "Claude Profile B",
23
- "source": "native-desktop",
24
- "profileId": "profile-b",
25
- "locationKind": "desktop",
26
- "targetKind": "native-launcher",
27
- "launcherName": "claude-profile-b",
28
- "groupingIdentity": "native-profile-b"
29
- },
30
- {
31
- "id": "start-profile-a-chrome",
32
- "visibleName": "Claude Profile A (Chrome)",
33
- "source": "chrome-start-menu",
34
- "profileId": "profile-a",
35
- "locationKind": "start-menu",
36
- "targetKind": "chrome-profile",
37
- "launcherName": "claude-profile-a",
38
- "groupingIdentity": "chrome-profile-a"
39
- },
40
- {
41
- "id": "start-profile-b-chrome",
42
- "visibleName": "Claude Profile B (Chrome)",
43
- "source": "chrome-start-menu",
44
- "profileId": "profile-b",
45
- "locationKind": "start-menu",
46
- "targetKind": "chrome-profile",
47
- "launcherName": "claude-profile-b",
48
- "groupingIdentity": "chrome-profile-b"
49
- }
50
- ]
51
- }
@@ -1,77 +0,0 @@
1
- # Read-only reconciliation preview for managed Claude profile shortcuts.
2
- # Live mutation is refused while policy.liveMutationAuthorized is false.
3
-
4
- [CmdletBinding()]
5
- param(
6
- [string]$ManifestPath = (Join-Path $PSScriptRoot 'shortcut-manifest.json'),
7
- [string]$DesktopPath = [Environment]::GetFolderPath('Desktop'),
8
- [string]$StartMenuPath = [Environment]::GetFolderPath('StartMenu'),
9
- [string]$OutputPath,
10
- [switch]$Apply
11
- )
12
-
13
- Set-StrictMode -Version Latest
14
- $ErrorActionPreference = 'Stop'
15
-
16
- $inventoryScript = Join-Path $PSScriptRoot 'shortcut-inventory.ps1'
17
- if (-not (Test-Path -LiteralPath $inventoryScript)) {
18
- throw "inventory script missing: $inventoryScript"
19
- }
20
-
21
- $manifest = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
22
- $inventoryJson = & $inventoryScript -ManifestPath $ManifestPath -DesktopPath $DesktopPath -StartMenuPath $StartMenuPath
23
- $inventory = $inventoryJson | ConvertFrom-Json
24
-
25
- $allActions = [System.Collections.Generic.List[object]]::new()
26
- foreach ($eachRow in $inventory.shortcuts) {
27
- $actualPresent = [bool]$eachRow.exists
28
- $hasSourceIdentity = -not [string]::IsNullOrWhiteSpace([string]$eachRow.source) -and
29
- -not [string]::IsNullOrWhiteSpace([string]$eachRow.profileId) -and
30
- -not [string]::IsNullOrWhiteSpace([string]$eachRow.groupingIdentity)
31
- $status = if (-not $actualPresent) {
32
- 'missing'
33
- }
34
- elseif ($hasSourceIdentity) {
35
- 'present-unverified-target'
36
- }
37
- else {
38
- 'source-ambiguous'
39
- }
40
- $allActions.Add([pscustomobject]@{
41
- id = $eachRow.id
42
- visibleName = $eachRow.visibleName
43
- source = $eachRow.source
44
- profileId = $eachRow.profileId
45
- groupingIdentity = $eachRow.groupingIdentity
46
- desiredPresent = $true
47
- actualPresent = $actualPresent
48
- status = $status
49
- expectedPath = $eachRow.expectedPath
50
- mutation = 'none'
51
- })
52
- }
53
-
54
- if ($Apply) {
55
- if (-not [bool]$manifest.policy.liveMutationAuthorized) {
56
- throw 'live shortcut mutation refused: policy.liveMutationAuthorized is false; owner confirmation required'
57
- }
58
- throw 'live shortcut mutation adapter is residual after owner confirmation (N1 apply path)'
59
- }
60
-
61
- $preview = [pscustomobject]@{
62
- mode = 'preview-only'
63
- policy = $manifest.policy
64
- actionCount = $allActions.Count
65
- actions = $allActions
66
- liveMutationAuthorized = [bool]$manifest.policy.liveMutationAuthorized
67
- }
68
-
69
- $json = $preview | ConvertTo-Json -Depth 6
70
- if ($OutputPath) {
71
- $directory = Split-Path -Parent $OutputPath
72
- if ($directory -and -not (Test-Path -LiteralPath $directory)) {
73
- New-Item -ItemType Directory -Path $directory | Out-Null
74
- }
75
- [IO.File]::WriteAllText($OutputPath, $json, [Text.UTF8Encoding]::new($false))
76
- }
77
- Write-Output $json