@getmarrow/install 0.1.55 → 0.1.57
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.
- package/README.md +17 -7
- package/package.json +1 -1
- package/src/installer.js +220 -42
package/README.md
CHANGED
|
@@ -75,17 +75,19 @@ Tool visibility is not authorization. Every visible call still reaches Marrow's
|
|
|
75
75
|
Marrow's hosted API, website, and dashboard update automatically; local SDK dependencies, generated runtime files, MCP hooks/configuration, and pinned package versions do not silently rewrite themselves. Keeping them current delivers new client-side features, compatibility improvements, and any published security fixes. Supported clients report their package version during authenticated status/runtime activity, and Marrow returns a `client_update` notice with the exact action when the version is behind or unknown.
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
79
|
-
npx -y @getmarrow/install@latest
|
|
78
|
+
# When doctor reports stale or mixed MCP configuration
|
|
79
|
+
npx -y @getmarrow/install@latest update
|
|
80
|
+
|
|
81
|
+
# Restart the detected owning harnesses once, then verify
|
|
82
|
+
npx -y @getmarrow/install@latest doctor --self-test
|
|
80
83
|
|
|
81
84
|
# Measured API read health and local backlog
|
|
82
85
|
npx -y --package=@getmarrow/mcp@latest marrow-mcp ping
|
|
83
|
-
|
|
84
|
-
# Use only when doctor reports drift
|
|
85
|
-
npx -y @getmarrow/install@latest --repair
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
`
|
|
88
|
+
`update` resolves official npm metadata once, selects one exact verified MCP target, and synchronizes every Marrow-managed surface in the detected owning workspace while retaining unrelated user hooks and configuration. Restart the detected owning harnesses once after it completes; running processes do not change before that restart. Run the doctor verification once after restart. Do not run separate `marrow-mcp setup` and restart cycles for the same detected workspace.
|
|
89
|
+
|
|
90
|
+
`activate` remains available for initial activation. After explicit activation, the local controller can restore drifted Marrow-managed hooks and configuration. Package upgrades, owner policy, credentials, explicitly disabled hooks, and unrelated local files remain explicit and subject to the operator's normal change policy.
|
|
89
91
|
|
|
90
92
|
## Automatic Controller
|
|
91
93
|
|
|
@@ -99,7 +101,15 @@ npx @getmarrow/install controller stop
|
|
|
99
101
|
|
|
100
102
|
Persistent controller lifecycle is currently Linux-only. On macOS or Windows, activation still writes supported configuration and verifies one server-side install self-test without certifying that hooks continuously ran; run `npx @getmarrow/install sidecar` under an owner-managed service and pass `--no-controller`. The controller does not silently upgrade packages, change governance policy, rotate credentials, or modify unrelated project configuration.
|
|
101
103
|
|
|
102
|
-
## What's New in v0.1.
|
|
104
|
+
## What's New in v0.1.57
|
|
105
|
+
|
|
106
|
+
v0.1.57 pins the npm-verified MCP `3.9.88` and SDK `3.7.63` packages. Doctor now recognizes an installed and lockfile-verified SDK `3.7.63` instead of suggesting `3.7.62`. If a workspace has a newer stable SDK version, doctor preserves it and asks for official registry verification before replacement. The generated MCP setup, hooks, and update instructions target `3.9.88`; restart the owning harness and run `npx @getmarrow/install@latest doctor --self-test` after activation.
|
|
107
|
+
|
|
108
|
+
## Previous: v0.1.56
|
|
109
|
+
|
|
110
|
+
v0.1.56 pins the officially verified MCP `3.9.80` release from source `ee1eda3f201965a6530256accbdf175660dd8ad6` with registry integrity `sha512-uou3X18pESV39EMmddDrYN7yd6MrZzosrnQ1eRtvK5j3yuBLAlupj5kQVrrKEWAL6xwuLebXq2isivK2O/2WrA==`; SDK remains `3.7.62`. Doctor now directs stale, mixed, or version-unknown MCP installations through one `npx -y @getmarrow/install@latest update`, followed by one owning-harness restart and one `doctor --self-test` verification. The update resolves one official target and applies it consistently across detected Marrow-managed instructions, MCP launch configuration, and supported native hooks without claiming that already-running processes changed.
|
|
111
|
+
|
|
112
|
+
## Previous: v0.1.55
|
|
103
113
|
|
|
104
114
|
v0.1.55 pins sealed MCP candidate `3.9.79` from source `11f00049043d0aba90704ecbf69f32d2278a4573` for ordinary setup, generated launch configuration, and native hooks. Doctor, update, and repair retain exact-version resolution and never propagate a newer local version unless official registry metadata verifies it; offline operation preserves unverified-ahead owner surfaces and uses the sealed candidate for new managed targets.
|
|
105
115
|
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -10,11 +10,11 @@ const { evidence: localControlEvidence } = require('./control-state');
|
|
|
10
10
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
11
11
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
12
12
|
const MARROW_BLOCK_END = '<!-- marrow:passive-end -->';
|
|
13
|
-
const MCP_ADAPTER_VERSION = '3.9.
|
|
14
|
-
const MCP_ADAPTER_SOURCE_SHA = '
|
|
15
|
-
const MCP_ADAPTER_INTEGRITY = 'sha512-
|
|
16
|
-
const SDK_ADAPTER_VERSION = '3.7.
|
|
17
|
-
const SDK_ADAPTER_INTEGRITY = 'sha512-
|
|
13
|
+
const MCP_ADAPTER_VERSION = '3.9.88';
|
|
14
|
+
const MCP_ADAPTER_SOURCE_SHA = '2385f0554dd9455e6a7d2c0396058710ab56611a';
|
|
15
|
+
const MCP_ADAPTER_INTEGRITY = 'sha512-1WQtVU8oT1XLGFa9aINm9Ypn/xhNh/mPLpK2bziPIxRURsAGrs8TAfb23JL7HXDcmpXFgMLd8R6gmltVbBbHIg==';
|
|
16
|
+
const SDK_ADAPTER_VERSION = '3.7.63';
|
|
17
|
+
const SDK_ADAPTER_INTEGRITY = 'sha512-5BiV1P0J1NMVdgjqxqULfM+zGrTjHBuWm1amKZWba4yW6C3t10VlhIlj/A+4YKbKqvyZe8gXgUwW+VrYDbVQrQ==';
|
|
18
18
|
const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
|
|
19
19
|
const MCP_PACKAGE_SPEC = `@getmarrow/mcp@${MCP_ADAPTER_VERSION}`;
|
|
20
20
|
const ADAPTER_PROVENANCE = Object.freeze({
|
|
@@ -23,7 +23,7 @@ const ADAPTER_PROVENANCE = Object.freeze({
|
|
|
23
23
|
version: MCP_ADAPTER_VERSION,
|
|
24
24
|
source_sha: MCP_ADAPTER_SOURCE_SHA,
|
|
25
25
|
integrity: MCP_ADAPTER_INTEGRITY,
|
|
26
|
-
integrity_state: '
|
|
26
|
+
integrity_state: 'verified_npm_registry_metadata',
|
|
27
27
|
}),
|
|
28
28
|
sdk: Object.freeze({
|
|
29
29
|
package: '@getmarrow/sdk',
|
|
@@ -33,6 +33,9 @@ const ADAPTER_PROVENANCE = Object.freeze({
|
|
|
33
33
|
});
|
|
34
34
|
const MCP_REGISTRY_LATEST_URL = 'https://registry.npmjs.org/%40getmarrow%2Fmcp/latest';
|
|
35
35
|
const MCP_REGISTRY_VERIFICATION_COMMAND = 'npm view @getmarrow/mcp@latest name version dist.integrity dist.tarball --json --registry=https://registry.npmjs.org';
|
|
36
|
+
const INSTALLER_UPDATE_COMMAND = 'npx -y @getmarrow/install@latest update';
|
|
37
|
+
const INSTALLER_DOCTOR_COMMAND = 'npx -y @getmarrow/install@latest doctor --self-test';
|
|
38
|
+
const INSTALLER_RESTART_INSTRUCTION = 'After the update completes, restart the detected owning harnesses once. Running Marrow MCP processes remain unchanged until that restart.';
|
|
36
39
|
const MCP_STABLE_VERSION_RE = /^(\d{1,6})\.(\d{1,6})\.(\d{1,9})$/;
|
|
37
40
|
const SHA512_INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]+={0,2}$/;
|
|
38
41
|
const VERIFIED_MCP_EXECUTABLE_TARGET = Symbol('verified_mcp_executable_target');
|
|
@@ -65,6 +68,13 @@ function compareMcpVersions(left, right) {
|
|
|
65
68
|
return 0;
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
function declaredStableSdkLowerBound(spec) {
|
|
72
|
+
if (typeof spec !== 'string') return null;
|
|
73
|
+
const match = spec.trim().match(/^(v|\^|~|>=|>)?(\d{1,6}\.\d{1,6}\.\d{1,9})$/);
|
|
74
|
+
if (!match || !parsedStableMcpVersion(match[2])) return null;
|
|
75
|
+
return { version: match[2], exclusive: match[1] === '>' };
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
function compatibleMcpTargetVersion(value) {
|
|
69
79
|
const candidate = parsedStableMcpVersion(value);
|
|
70
80
|
const sealed = parsedStableMcpVersion(MCP_ADAPTER_VERSION);
|
|
@@ -79,10 +89,19 @@ function mcpVersionsInText(value) {
|
|
|
79
89
|
.map((match) => match[1]);
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
function marrowManagedBlockInText(value) {
|
|
93
|
+
const content = String(value || '');
|
|
94
|
+
const start = content.indexOf(MARROW_BLOCK_START);
|
|
95
|
+
if (start < 0) return '';
|
|
96
|
+
const end = content.indexOf(MARROW_BLOCK_END, start + MARROW_BLOCK_START.length);
|
|
97
|
+
if (end < 0) return '';
|
|
98
|
+
return content.slice(start, end + MARROW_BLOCK_END.length);
|
|
99
|
+
}
|
|
100
|
+
|
|
82
101
|
function unverifiedAheadMcpVersions(versions, targetVersion = MCP_ADAPTER_VERSION) {
|
|
83
|
-
if (!
|
|
102
|
+
if (!parsedStableMcpVersion(targetVersion)) return [];
|
|
84
103
|
return [...new Set((Array.isArray(versions) ? versions : [])
|
|
85
|
-
.filter((version) =>
|
|
104
|
+
.filter((version) => parsedStableMcpVersion(version)
|
|
86
105
|
&& compareMcpVersions(version, targetVersion) > 0))].sort((left, right) => compareMcpVersions(left, right));
|
|
87
106
|
}
|
|
88
107
|
|
|
@@ -554,7 +573,6 @@ function inspectMcpProcesses(options = {}) {
|
|
|
554
573
|
const aheadUnverified = aheadUnverifiedVersions.length > 0;
|
|
555
574
|
const needsRepair = stale || mixedVersions || unknownVersionProcesses > 0 || aheadUnverified;
|
|
556
575
|
const automaticRepairSuppressed = aheadUnverified;
|
|
557
|
-
const repairCommand = `npx -y --package=@getmarrow/mcp@${expectedVersion} marrow-mcp setup`;
|
|
558
576
|
return {
|
|
559
577
|
available: process.platform === 'linux' || Array.isArray(options.commands),
|
|
560
578
|
expected_version: expectedVersion,
|
|
@@ -570,21 +588,30 @@ function inspectMcpProcesses(options = {}) {
|
|
|
570
588
|
registry_verification_required: aheadUnverified,
|
|
571
589
|
exact_fix: automaticRepairSuppressed
|
|
572
590
|
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
573
|
-
: needsRepair ?
|
|
591
|
+
: needsRepair ? INSTALLER_UPDATE_COMMAND : null,
|
|
574
592
|
restart_required: needsRepair && !automaticRepairSuppressed,
|
|
575
593
|
restart_instruction: needsRepair && !automaticRepairSuppressed
|
|
576
|
-
?
|
|
594
|
+
? INSTALLER_RESTART_INSTRUCTION
|
|
577
595
|
: null,
|
|
578
|
-
verification_command: needsRepair
|
|
596
|
+
verification_command: needsRepair && !automaticRepairSuppressed ? INSTALLER_DOCTOR_COMMAND : null,
|
|
579
597
|
};
|
|
580
598
|
}
|
|
581
599
|
|
|
582
600
|
function inspectMcpConfigurations(detection, options = {}) {
|
|
583
601
|
const home = options.home || process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
584
602
|
const configuredPaths = Array.isArray(options.paths) ? options.paths : [
|
|
603
|
+
detection?.paths?.agentsMd,
|
|
604
|
+
detection?.paths?.mcpJson,
|
|
585
605
|
detection?.paths?.claudeSettings,
|
|
606
|
+
detection?.paths?.codexHooks,
|
|
607
|
+
detection?.paths?.cursorHooks,
|
|
586
608
|
detection?.paths?.cursorMcp,
|
|
587
|
-
detection?.paths?.
|
|
609
|
+
detection?.paths?.clinePreToolUseHook,
|
|
610
|
+
detection?.paths?.clinePostToolUseHook,
|
|
611
|
+
detection?.paths?.clineTaskCancelHook,
|
|
612
|
+
detection?.paths?.windsurfHooks,
|
|
613
|
+
detection?.paths?.geminiSettings,
|
|
614
|
+
detection?.paths?.grokHooks,
|
|
588
615
|
path.join(home, '.claude', 'settings.json'),
|
|
589
616
|
path.join(home, '.claude.json'),
|
|
590
617
|
path.join(home, '.cursor', 'mcp.json'),
|
|
@@ -601,9 +628,11 @@ function inspectMcpConfigurations(detection, options = {}) {
|
|
|
601
628
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 2 * 1024 * 1024) continue;
|
|
602
629
|
filesChecked += 1;
|
|
603
630
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
631
|
+
const inspected = path.basename(filePath) === 'AGENTS.md'
|
|
632
|
+
? marrowManagedBlockInText(raw)
|
|
633
|
+
: raw;
|
|
634
|
+
const specs = mcpVersionsInText(inspected);
|
|
635
|
+
if (/@getmarrow\/mcp(?:@|["'\s])/.test(inspected)) {
|
|
607
636
|
configurationsFound += 1;
|
|
608
637
|
if (specs.length === 0) unknownVersionConfigurations += 1;
|
|
609
638
|
}
|
|
@@ -639,8 +668,10 @@ function inspectMcpConfigurations(detection, options = {}) {
|
|
|
639
668
|
? null
|
|
640
669
|
: aheadUnverified
|
|
641
670
|
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
642
|
-
:
|
|
643
|
-
|
|
671
|
+
: INSTALLER_UPDATE_COMMAND,
|
|
672
|
+
restart_required: !healthy && !aheadUnverified,
|
|
673
|
+
restart_instruction: !healthy && !aheadUnverified ? INSTALLER_RESTART_INSTRUCTION : null,
|
|
674
|
+
verification_command: !healthy && !aheadUnverified ? INSTALLER_DOCTOR_COMMAND : null,
|
|
644
675
|
};
|
|
645
676
|
}
|
|
646
677
|
|
|
@@ -1691,6 +1722,75 @@ function grokNativeHookFingerprint(settings) {
|
|
|
1691
1722
|
})).digest('hex');
|
|
1692
1723
|
}
|
|
1693
1724
|
|
|
1725
|
+
function grokMarrowHookSubcommand(command) {
|
|
1726
|
+
const direct = marrowHookSubcommand(command);
|
|
1727
|
+
if (direct) return direct;
|
|
1728
|
+
if (typeof command !== 'string') return null;
|
|
1729
|
+
const guarded = command.match(
|
|
1730
|
+
/@getmarrow\/mcp@[^\s"',]+["'],["']marrow-mcp["'],["']grok-(pre-action-hook)["']/,
|
|
1731
|
+
);
|
|
1732
|
+
return guarded?.[1] || null;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function reconcileGrokHook(settings, eventName, subcommand, command, matcher, timeout) {
|
|
1736
|
+
const original = Array.isArray(settings?.hooks?.[eventName]) ? settings.hooks[eventName] : [];
|
|
1737
|
+
const retained = [];
|
|
1738
|
+
let preferredHandler = null;
|
|
1739
|
+
for (const entry of original) {
|
|
1740
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !Array.isArray(entry.hooks)) {
|
|
1741
|
+
retained.push(entry);
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
const remaining = [];
|
|
1745
|
+
for (const handler of entry.hooks) {
|
|
1746
|
+
const detected = handler && typeof handler === 'object' && !Array.isArray(handler)
|
|
1747
|
+
&& handler.type === 'command' ? grokMarrowHookSubcommand(handler.command) : null;
|
|
1748
|
+
if (detected) {
|
|
1749
|
+
if (detected === subcommand && !preferredHandler) preferredHandler = handler;
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
remaining.push(handler);
|
|
1753
|
+
}
|
|
1754
|
+
if (remaining.length > 0) retained.push({ ...entry, hooks: remaining });
|
|
1755
|
+
}
|
|
1756
|
+
if (!command) return retained;
|
|
1757
|
+
const canonical = {
|
|
1758
|
+
...(matcher === undefined ? {} : { matcher }),
|
|
1759
|
+
hooks: [{ ...preferredHandler, type: 'command', command, timeout }],
|
|
1760
|
+
};
|
|
1761
|
+
return [...retained, canonical];
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function upsertGrokHooks(settingsPath) {
|
|
1765
|
+
const settings = parseJsonObject(settingsPath);
|
|
1766
|
+
const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
|
|
1767
|
+
? settings.hooks
|
|
1768
|
+
: {};
|
|
1769
|
+
settings.hooks = {
|
|
1770
|
+
...hooks,
|
|
1771
|
+
UserPromptSubmit: reconcileGrokHook(settings, 'UserPromptSubmit', 'context-hook', GROK_CONTEXT_HOOK_COMMAND, undefined, 5),
|
|
1772
|
+
PreToolUse: reconcileGrokHook(settings, 'PreToolUse', 'pre-action-hook', GROK_PRE_ACTION_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 7),
|
|
1773
|
+
PostToolUse: reconcileGrokHook(settings, 'PostToolUse', 'hook', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5),
|
|
1774
|
+
PostToolUseFailure: reconcileGrokHook(settings, 'PostToolUseFailure', 'hook', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5),
|
|
1775
|
+
Stop: reconcileGrokHook(settings, 'Stop', 'session-hook', GROK_SESSION_END_HOOK_COMMAND, undefined, 3),
|
|
1776
|
+
SessionEnd: reconcileGrokHook(settings, 'SessionEnd', 'session-hook', null, undefined, 3),
|
|
1777
|
+
};
|
|
1778
|
+
return JSON.stringify(settings, null, 2) + '\n';
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function managedGrokHooksFile(filePath) {
|
|
1782
|
+
if (!exists(filePath)) return false;
|
|
1783
|
+
try {
|
|
1784
|
+
const stat = fs.lstatSync(filePath);
|
|
1785
|
+
if (stat.isSymbolicLink() || !stat.isFile()) return true;
|
|
1786
|
+
if (stat.size > 2 * 1024 * 1024) return true;
|
|
1787
|
+
return /@getmarrow\/mcp@[^\s"']+[\s\S]{0,4096}marrow-mcp[\s\S]{0,512}grok-(?:context-hook|pre-action-hook|hook|session-hook)/
|
|
1788
|
+
.test(fs.readFileSync(filePath, 'utf8'));
|
|
1789
|
+
} catch {
|
|
1790
|
+
return true;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1694
1794
|
function upsertGeminiHooks(settingsPath) {
|
|
1695
1795
|
const settings = parseJsonObject(settingsPath);
|
|
1696
1796
|
const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
|
|
@@ -1938,7 +2038,9 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
1938
2038
|
? 'Restart Grok, inspect the installed global hooks with /hooks, and confirm they are enabled. Configuration remains client-self-reported and does not verify observed coverage.'
|
|
1939
2039
|
: null
|
|
1940
2040
|
: capabilityLevel === 'sdk_passive_runtime' && !sdkDependency.present
|
|
1941
|
-
?
|
|
2041
|
+
? sdkDependency.install_command
|
|
2042
|
+
? `${sdkDependency.install_command} && npx @getmarrow/install --repair`
|
|
2043
|
+
: sdkDependency.warning
|
|
1942
2044
|
: capabilityLevel === 'governed_wrapper'
|
|
1943
2045
|
? `npx @getmarrow/install run --agent <agent-id> -- ${client}`
|
|
1944
2046
|
: client === 'cline' && clineConflicts.length > 0
|
|
@@ -2057,10 +2159,12 @@ function inspectSdkDependency(detection) {
|
|
|
2057
2159
|
|| objectTargetsSdk(packageJson.resolutions)
|
|
2058
2160
|
|| objectTargetsSdk(packageJson.pnpm?.overrides);
|
|
2059
2161
|
let lockVerified = false;
|
|
2162
|
+
let lockedVersion = null;
|
|
2060
2163
|
try {
|
|
2061
2164
|
const lock = JSON.parse(safeRead(path.join(detection.root, 'package-lock.json')) || '{}');
|
|
2062
2165
|
const rootLock = lock?.packages?.[''];
|
|
2063
2166
|
const lockedSdk = lock?.packages?.['node_modules/@getmarrow/sdk'];
|
|
2167
|
+
lockedVersion = typeof lockedSdk?.version === 'string' ? lockedSdk.version : null;
|
|
2064
2168
|
const lockedDeclaration = [
|
|
2065
2169
|
rootLock?.dependencies,
|
|
2066
2170
|
rootLock?.devDependencies,
|
|
@@ -2090,9 +2194,16 @@ function inspectSdkDependency(detection) {
|
|
|
2090
2194
|
installedVersion = null;
|
|
2091
2195
|
installedName = null;
|
|
2092
2196
|
}
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2095
|
-
|
|
2197
|
+
const declaredLowerBound = declaredStableSdkLowerBound(declaredSpec);
|
|
2198
|
+
const declarationTrusted = declaredLowerBound !== null;
|
|
2199
|
+
const unsupportedDeclaration = declaredSpec !== null && !declarationTrusted;
|
|
2200
|
+
const declaredComparison = declaredLowerBound
|
|
2201
|
+
? compareMcpVersions(declaredLowerBound.version, SDK_ADAPTER_VERSION) : null;
|
|
2202
|
+
const declaredAhead = declaredComparison !== null
|
|
2203
|
+
&& (declaredComparison > 0 || (declaredLowerBound.exclusive && declaredComparison === 0));
|
|
2204
|
+
const aheadVersions = [installedVersion, lockedVersion]
|
|
2205
|
+
.filter((version) => compareMcpVersions(version, SDK_ADAPTER_VERSION) > 0);
|
|
2206
|
+
const aheadUnverified = aheadVersions.length > 0 || declaredAhead;
|
|
2096
2207
|
const present = declarationTrusted
|
|
2097
2208
|
&& !overrideDetected
|
|
2098
2209
|
&& lockVerified
|
|
@@ -2108,8 +2219,15 @@ function inspectSdkDependency(detection) {
|
|
|
2108
2219
|
lock_verified: lockVerified,
|
|
2109
2220
|
installed_name: installedName,
|
|
2110
2221
|
installed_version: installedVersion,
|
|
2222
|
+
ahead_unverified: aheadUnverified,
|
|
2111
2223
|
expected_version: SDK_ADAPTER_VERSION,
|
|
2112
|
-
install_command: present
|
|
2224
|
+
install_command: present || aheadUnverified || unsupportedDeclaration
|
|
2225
|
+
? null : `npm install @getmarrow/sdk@${SDK_ADAPTER_VERSION}`,
|
|
2226
|
+
...(aheadUnverified ? {
|
|
2227
|
+
warning: 'A newer SDK version is configured or installed. Preserve it and verify its exact version and integrity against the official npm registry before changing it.',
|
|
2228
|
+
} : unsupportedDeclaration ? {
|
|
2229
|
+
warning: 'The configured SDK version range is unsupported or ambiguous. Preserve it and verify the intended version against the official npm registry before changing it.',
|
|
2230
|
+
} : {}),
|
|
2113
2231
|
};
|
|
2114
2232
|
}
|
|
2115
2233
|
|
|
@@ -2303,6 +2421,15 @@ function buildPlan(detection, options) {
|
|
|
2303
2421
|
transform: (filePath) => retarget(upsertGeminiHooks(filePath)),
|
|
2304
2422
|
});
|
|
2305
2423
|
}
|
|
2424
|
+
if (options.update && detection.grok && managedGrokHooksFile(detection.paths.grokHooks)) {
|
|
2425
|
+
writes.push({
|
|
2426
|
+
type: 'json-transform',
|
|
2427
|
+
path: detection.paths.grokHooks,
|
|
2428
|
+
root: path.resolve(detection.paths.grokHooks, '..', '..', '..'),
|
|
2429
|
+
label: 'Grok native hooks',
|
|
2430
|
+
transform: (filePath) => retarget(upsertGrokHooks(filePath)),
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2306
2433
|
writes.push({
|
|
2307
2434
|
type: 'json-transform',
|
|
2308
2435
|
path: detection.paths.mcpJson,
|
|
@@ -2405,12 +2532,17 @@ function atomicWriteManagedFile(root, targetPath, contents) {
|
|
|
2405
2532
|
function applyPlan(plan, options) {
|
|
2406
2533
|
if (!Array.isArray(plan?.writes) || plan.writes.length === 0) return [];
|
|
2407
2534
|
const root = path.resolve(plan.root || path.dirname(plan.writes[0].path));
|
|
2408
|
-
for (const write of plan.writes)
|
|
2535
|
+
for (const write of plan.writes) {
|
|
2536
|
+
assertContainedManagedTarget(path.resolve(write.root || root), write.path);
|
|
2537
|
+
}
|
|
2409
2538
|
const prepared = plan.writes.map((write) => {
|
|
2410
2539
|
const fileExists = exists(write.path);
|
|
2411
2540
|
const before = safeRead(write.path);
|
|
2541
|
+
const inspectedBefore = write.type === 'md-block'
|
|
2542
|
+
? marrowManagedBlockInText(before)
|
|
2543
|
+
: before;
|
|
2412
2544
|
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(
|
|
2413
|
-
mcpVersionsInText(
|
|
2545
|
+
mcpVersionsInText(inspectedBefore),
|
|
2414
2546
|
plan.mcp_target_version,
|
|
2415
2547
|
);
|
|
2416
2548
|
const automaticRepairSuppressed = aheadUnverifiedVersions.length > 0;
|
|
@@ -2481,10 +2613,10 @@ function applyPlan(plan, options) {
|
|
|
2481
2613
|
...(hookConflict ? { exact_fix: write.conflict_fix } : {}),
|
|
2482
2614
|
});
|
|
2483
2615
|
if (contentChanged && writeApplied) {
|
|
2484
|
-
atomicWriteManagedFile(root, write.path, after);
|
|
2616
|
+
atomicWriteManagedFile(path.resolve(write.root || root), write.path, after);
|
|
2485
2617
|
}
|
|
2486
2618
|
if (modeChanged && writeApplied) {
|
|
2487
|
-
assertContainedManagedTarget(root, write.path);
|
|
2619
|
+
assertContainedManagedTarget(path.resolve(write.root || root), write.path);
|
|
2488
2620
|
fs.chmodSync(write.path, write.mode);
|
|
2489
2621
|
}
|
|
2490
2622
|
}
|
|
@@ -2997,12 +3129,12 @@ function printReport(report) {
|
|
|
2997
3129
|
}
|
|
2998
3130
|
}
|
|
2999
3131
|
|
|
3000
|
-
if (report.harnessReload) {
|
|
3132
|
+
if (report.harnessReload && report.writeMode !== 'doctor') {
|
|
3001
3133
|
process.stdout.write('\nHarness reload:\n');
|
|
3002
3134
|
process.stdout.write(`- required: ${report.harnessReload.required ? 'yes' : 'no'}\n`);
|
|
3003
3135
|
process.stdout.write(`- live in this process: ${report.harnessReload.live_in_this_process ? 'yes' : 'no'}\n`);
|
|
3004
|
-
if (report.harnessReload.instruction) process.stdout.write(`- restart: ${report.harnessReload.instruction}\n`);
|
|
3005
|
-
if (report.harnessReload.prove_command) process.stdout.write(`- prove after restart: ${report.harnessReload.prove_command}\n`);
|
|
3136
|
+
if (report.harnessReload.required && report.harnessReload.instruction) process.stdout.write(`- restart: ${report.harnessReload.instruction}\n`);
|
|
3137
|
+
if (report.harnessReload.required && report.harnessReload.prove_command) process.stdout.write(`- prove after restart: ${report.harnessReload.prove_command}\n`);
|
|
3006
3138
|
}
|
|
3007
3139
|
if (report.firstCapture) {
|
|
3008
3140
|
process.stdout.write('\nFirst capture:\n');
|
|
@@ -3038,7 +3170,7 @@ function printReport(report) {
|
|
|
3038
3170
|
|
|
3039
3171
|
if (report.sdkDependency?.required) {
|
|
3040
3172
|
process.stdout.write('\nSDK dependency:\n');
|
|
3041
|
-
process.stdout.write(`- @getmarrow/sdk: ${report.sdkDependency.present ? 'present' : 'missing'}\n`);
|
|
3173
|
+
process.stdout.write(`- @getmarrow/sdk: ${report.sdkDependency.present ? 'present' : report.sdkDependency.ahead_unverified ? 'newer version requires verification' : 'missing'}\n`);
|
|
3042
3174
|
if (report.sdkDependency.install_command) process.stdout.write(`- exact fix: ${report.sdkDependency.install_command}\n`);
|
|
3043
3175
|
if (report.sdkDependency.warning) process.stdout.write(`- warning: ${report.sdkDependency.warning}\n`);
|
|
3044
3176
|
}
|
|
@@ -3049,7 +3181,9 @@ function printReport(report) {
|
|
|
3049
3181
|
if (report.controller?.exact_fix) process.stdout.write(`- exact fix: ${report.controller.exact_fix}\n`);
|
|
3050
3182
|
|
|
3051
3183
|
if (report.writeMode === 'doctor') {
|
|
3052
|
-
const liveHere = !report.harnessReload?.required
|
|
3184
|
+
const liveHere = !report.harnessReload?.required
|
|
3185
|
+
&& report.doctor.mcpProcesses?.healthy !== false
|
|
3186
|
+
&& report.doctor.mcpConfigurations?.healthy !== false;
|
|
3053
3187
|
const activeLabel = report.doctor.active && liveHere
|
|
3054
3188
|
? 'yes'
|
|
3055
3189
|
: report.doctor.active
|
|
@@ -3066,8 +3200,6 @@ function printReport(report) {
|
|
|
3066
3200
|
process.stdout.write(`- stale/mixed/version-unknown MCP clients: ${processes.healthy ? 'no' : 'yes'}\n`);
|
|
3067
3201
|
if (processes.ahead_unverified_versions.length) process.stdout.write(`- unverified-ahead MCP clients: ${processes.ahead_unverified_versions.join(', ')}\n`);
|
|
3068
3202
|
if (processes.automatic_repair_suppressed) process.stdout.write('- automatic MCP process repair: suppressed pending official registry verification\n');
|
|
3069
|
-
if (processes.restart_instruction) process.stdout.write(`- restart required: ${processes.restart_instruction}\n`);
|
|
3070
|
-
if (processes.verification_command) process.stdout.write(`- verify repair: ${processes.verification_command}\n`);
|
|
3071
3203
|
}
|
|
3072
3204
|
if (report.doctor.mcpConfigurations) {
|
|
3073
3205
|
const configurations = report.doctor.mcpConfigurations;
|
|
@@ -3077,6 +3209,10 @@ function printReport(report) {
|
|
|
3077
3209
|
if (configurations.automatic_repair_suppressed) process.stdout.write('- automatic MCP configuration repair: suppressed pending official registry verification\n');
|
|
3078
3210
|
}
|
|
3079
3211
|
if (report.doctor.recommendedFix) process.stdout.write(`- recommended fix: ${report.doctor.recommendedFix}\n`);
|
|
3212
|
+
if (report.doctor.mcp_update_required) {
|
|
3213
|
+
process.stdout.write(`- restart once after update: ${report.doctor.restart_instruction}\n`);
|
|
3214
|
+
process.stdout.write(`- verify after restart: ${report.doctor.verification_command}\n`);
|
|
3215
|
+
}
|
|
3080
3216
|
process.stdout.write(`- live health: ${report.doctor.healthCommand}\n`);
|
|
3081
3217
|
}
|
|
3082
3218
|
|
|
@@ -3176,6 +3312,8 @@ async function install(options) {
|
|
|
3176
3312
|
const registryVerificationAction = automaticMcpRepairSuppressed
|
|
3177
3313
|
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
3178
3314
|
: null;
|
|
3315
|
+
const mcpUpdateRequired = !automaticMcpRepairSuppressed
|
|
3316
|
+
&& (!mcpConfigurations.healthy || (!options.update && !mcpProcesses.healthy));
|
|
3179
3317
|
if (automaticMcpRepairSuppressed) {
|
|
3180
3318
|
profile = {
|
|
3181
3319
|
...profile,
|
|
@@ -3208,7 +3346,40 @@ async function install(options) {
|
|
|
3208
3346
|
if (options.activate && !selfTest.activation_verified) {
|
|
3209
3347
|
throw new Error('Marrow activation failed: server confirmation was not returned');
|
|
3210
3348
|
}
|
|
3211
|
-
const
|
|
3349
|
+
const plannedHarnessReload = harnessReloadPlan(detection, options.doctor ? [] : changes);
|
|
3350
|
+
const updateNeedsRestart = Boolean(options.update
|
|
3351
|
+
&& !automaticMcpRepairSuppressed
|
|
3352
|
+
&& (plannedHarnessReload.required || (mcpConfigurations.healthy && !mcpProcesses.healthy)));
|
|
3353
|
+
const harnessReload = options.doctor && mcpUpdateRequired
|
|
3354
|
+
? {
|
|
3355
|
+
...plannedHarnessReload,
|
|
3356
|
+
required: true,
|
|
3357
|
+
live_in_this_process: false,
|
|
3358
|
+
instruction: INSTALLER_RESTART_INSTRUCTION,
|
|
3359
|
+
prove_command: INSTALLER_DOCTOR_COMMAND,
|
|
3360
|
+
}
|
|
3361
|
+
: updateNeedsRestart
|
|
3362
|
+
? {
|
|
3363
|
+
...plannedHarnessReload,
|
|
3364
|
+
required: true,
|
|
3365
|
+
live_in_this_process: false,
|
|
3366
|
+
instruction: INSTALLER_RESTART_INSTRUCTION,
|
|
3367
|
+
prove_command: INSTALLER_DOCTOR_COMMAND,
|
|
3368
|
+
}
|
|
3369
|
+
: plannedHarnessReload;
|
|
3370
|
+
const updateAwaitingRestart = Boolean(options.update
|
|
3371
|
+
&& updateNeedsRestart
|
|
3372
|
+
&& mcpConfigurations.healthy
|
|
3373
|
+
&& !mcpProcesses.healthy
|
|
3374
|
+
&& !automaticMcpRepairSuppressed);
|
|
3375
|
+
const reportedMcpProcesses = updateAwaitingRestart
|
|
3376
|
+
? {
|
|
3377
|
+
...mcpProcesses,
|
|
3378
|
+
update_completed: true,
|
|
3379
|
+
awaiting_restart: true,
|
|
3380
|
+
exact_fix: null,
|
|
3381
|
+
}
|
|
3382
|
+
: mcpProcesses;
|
|
3212
3383
|
if (harnessReload.required && selfTest.mcp_tool_profile) {
|
|
3213
3384
|
selfTest.mcp_tool_profile = {
|
|
3214
3385
|
...selfTest.mcp_tool_profile,
|
|
@@ -3268,6 +3439,8 @@ async function install(options) {
|
|
|
3268
3439
|
automaticMcpRepairSuppressed,
|
|
3269
3440
|
message: automaticMcpRepairSuppressed
|
|
3270
3441
|
? registryVerificationAction
|
|
3442
|
+
: options.update && harnessReload.required
|
|
3443
|
+
? 'Managed Marrow configuration is synchronized. Running MCP processes are unchanged; complete the Harness reload steps below before treating the update as live.'
|
|
3271
3444
|
: selfTestPassed
|
|
3272
3445
|
? selfTest.health === 'healthy'
|
|
3273
3446
|
? 'I fixed Marrow passive config, one-call runtime is active, and self-test passed.'
|
|
@@ -3277,7 +3450,6 @@ async function install(options) {
|
|
|
3277
3450
|
: `Config repair ran, but self-test failed: ${selfTest.error || 'unknown error'}.`,
|
|
3278
3451
|
}
|
|
3279
3452
|
: null;
|
|
3280
|
-
|
|
3281
3453
|
return {
|
|
3282
3454
|
root: detection.root,
|
|
3283
3455
|
adapterProvenance: adapterProvenanceForMcpTarget(mcpTarget),
|
|
@@ -3314,12 +3486,18 @@ async function install(options) {
|
|
|
3314
3486
|
missingEnv: options.apiKey ? [] : ['MARROW_API_KEY'],
|
|
3315
3487
|
envHints,
|
|
3316
3488
|
missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
|
|
3317
|
-
mcpProcesses,
|
|
3489
|
+
mcpProcesses: reportedMcpProcesses,
|
|
3318
3490
|
mcpConfigurations,
|
|
3319
3491
|
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
3320
3492
|
automatic_mcp_repair_suppressed: automaticMcpRepairSuppressed,
|
|
3321
3493
|
registry_verification_action: registryVerificationAction,
|
|
3322
|
-
|
|
3494
|
+
mcp_update_required: mcpUpdateRequired,
|
|
3495
|
+
restart_required: mcpUpdateRequired || updateAwaitingRestart,
|
|
3496
|
+
restart_instruction: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null,
|
|
3497
|
+
verification_command: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_DOCTOR_COMMAND : null,
|
|
3498
|
+
recommendedFix: registryVerificationAction
|
|
3499
|
+
|| (updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null)
|
|
3500
|
+
|| mcpProcesses.exact_fix || mcpConfigurations.exact_fix || configDiagnostics.npm_token.recommended_fix || (!options.apiKey
|
|
3323
3501
|
? envHints.length
|
|
3324
3502
|
? `MARROW_API_KEY was found in a likely env file at ${envHints[0]}. Load that key from trusted secret storage, export only MARROW_API_KEY, then run npx @getmarrow/install --repair.`
|
|
3325
3503
|
: 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
|
|
@@ -3339,13 +3517,13 @@ async function install(options) {
|
|
|
3339
3517
|
: []),
|
|
3340
3518
|
...(mcpProcesses.ahead_unverified
|
|
3341
3519
|
? ['Registry-unverified ahead Marrow MCP clients are active. Automatic repair is suppressed; run the exact registry verification action before owner-directed replacement.']
|
|
3342
|
-
: !mcpProcesses.healthy
|
|
3343
|
-
? [
|
|
3520
|
+
: !mcpProcesses.healthy && !options.update && !options.doctor
|
|
3521
|
+
? [`Stale, mixed, or version-unknown Marrow MCP clients are active. Run ${INSTALLER_UPDATE_COMMAND}; the installer will report the one required restart and later doctor verification.`]
|
|
3344
3522
|
: []),
|
|
3345
3523
|
...(mcpConfigurations.ahead_unverified || changes.some((change) => change.automatic_repair_suppressed)
|
|
3346
3524
|
? ['Registry-unverified ahead Marrow MCP configuration was preserved. It was not copied into other managed targets, and automatic repair is suppressed for that surface.']
|
|
3347
|
-
: !mcpConfigurations.healthy
|
|
3348
|
-
? [
|
|
3525
|
+
: !mcpConfigurations.healthy && !options.update && !options.doctor
|
|
3526
|
+
? [`Stale, mixed, or version-unknown Marrow MCP versions remain in the detected workspace. Run ${INSTALLER_UPDATE_COMMAND} once to synchronize its managed surfaces.`]
|
|
3349
3527
|
: []),
|
|
3350
3528
|
],
|
|
3351
3529
|
};
|