@getmarrow/install 0.1.33 → 0.1.34

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 CHANGED
@@ -53,7 +53,7 @@ Verify current claims through the [public evidence manifest](https://getmarrow.a
53
53
  ## Install
54
54
 
55
55
  ```bash
56
- npx @getmarrow/install --yes
56
+ npx @getmarrow/install activate
57
57
  ```
58
58
 
59
59
  Required secret:
@@ -62,9 +62,19 @@ Required secret:
62
62
  export MARROW_API_KEY=mrw_live_...
63
63
  ```
64
64
 
65
- ## What's New in v0.1.33
65
+ ## What's New in v0.1.34
66
+
67
+ v0.1.34 verifies whether passive governance is actually active after install. Activation now registers a bounded capability profile, a one-way configuration fingerprint, expected and observed hook surfaces, and a server-accepted lifecycle receipt. The Fleet Operator shows activation state, capture coverage, outcome closure, intervention follow-through, drift, and the exact repair:
66
68
 
67
- v0.1.33 adds agent-disagreement visibility to the Fleet Operator TUI. Operators can see open and review-required arbitration receipts and inspect the exact next action produced through the existing Marrow runtime. It preserves the machine-readable governance-fit contract introduced in v0.1.32 and the server-verified first-run activation introduced in v0.1.29:
69
+ - Claude Code installation includes exact `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, and `Stop` hooks;
70
+ - matching pre-action/result receipts use one tool correlation, and activation fingerprints the exact hook contract without uploading configuration contents;
71
+ - the capability registry distinguishes native hooks, MCP, SDK passive runtime, governed wrappers, and custom event contracts;
72
+ - `activate` fails when the local integration is incomplete or the server does not accept the exact activation profile;
73
+ - `doctor` and `--repair` use the same configuration evidence without exposing configuration contents;
74
+ - the harness certification suite prevents support claims from overstating what is automatic;
75
+ - unavailable coverage remains “insufficient data” instead of a synthetic percentage.
76
+
77
+ It preserves agent-disagreement visibility from v0.1.33 and the server-verified first-run activation introduced in v0.1.29:
68
78
 
69
79
  - GitHub and npm now advertise separate signed discovery placements;
70
80
  - package metadata identifies the installer as agent governance rather than a general memory utility;
@@ -104,10 +114,13 @@ With a valid key, `activate`:
104
114
  4. closes its outcome;
105
115
  5. sends the exact self-test decision ID to Marrow for server-side verification;
106
116
  6. reads agent status and the one-call runtime;
107
- 7. returns an activation receipt with capture, intervention, closure, first-value state, and the exact next action.
117
+ 7. registers the detected capability, expected hooks, and one-way configuration fingerprint;
118
+ 8. returns an activation receipt with capture, intervention, closure, first-value state, and the exact next action.
108
119
 
109
120
  Healthy output confirms the exact decision outcome exists under the authenticated account and agent. A local file write or client-supplied `verified: true` value cannot produce an active receipt.
110
121
 
122
+ The installer does not claim identical automation for every harness. Native hooks provide the broadest automatic coverage; MCP covers MCP-routed actions; the SDK covers owned Node processes; the governed runner covers commands launched through it; custom harnesses must map their own lifecycle events.
123
+
111
124
  ## Govern TUI
112
125
 
113
126
  Open the interactive setup panel:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
4
4
  "description": "Universal installer and governed runner for Marrow agent fleets.",
5
5
  "bin": {
6
6
  "marrow-install": "bin/marrow-install.js"
@@ -806,6 +806,9 @@ function normalizeFixCommands(status, capacity) {
806
806
  add(status.route_contract?.exact_fix);
807
807
  add(status.auto_outcome_closure?.exact_fix);
808
808
  add(status.capture_coverage?.exact_fix);
809
+ add(status.activation_coverage?.exact_fix);
810
+ add(status.activation_coverage?.drift?.repair_command, true);
811
+ add(status.passive_activation?.exact_fix);
809
812
  add(capacity.exact_next_action, true);
810
813
  add(capacity.next_action, true);
811
814
  if (listValue(status.missed_hooks, status.degraded_hooks).length) add('npx @getmarrow/install --repair');
@@ -813,6 +816,52 @@ function normalizeFixCommands(status, capacity) {
813
816
  return commands.slice(0, 6);
814
817
  }
815
818
 
819
+ function normalizeActivationCoverage(status, report, fleet) {
820
+ const source = firstDefined(
821
+ status.activation_coverage,
822
+ status.passive_activation,
823
+ report.activation_coverage,
824
+ report.passive_activation,
825
+ fleet.activation_coverage,
826
+ {},
827
+ ) || {};
828
+ const activation = source.activation || {};
829
+ const capture = source.capture_coverage || source.capture || source.coverage || {};
830
+ const closure = source.outcome_closure || source.closure || {};
831
+ const effectiveness = source.intervention_effectiveness || source.effectiveness || {};
832
+ const drift = source.drift && typeof source.drift === 'object' ? source.drift : {};
833
+ const available = source.available === true || capture.available === true;
834
+ const driftAvailable = available && drift.available === true && typeof drift.detected === 'boolean';
835
+ const percent = (value) => {
836
+ const number = Number(value);
837
+ if (!Number.isFinite(number)) return null;
838
+ return Math.max(0, Math.min(100, number));
839
+ };
840
+ const ratio = (value) => {
841
+ const number = Number(value);
842
+ if (!Number.isFinite(number)) return null;
843
+ return Math.max(0, Math.min(100, number * 100));
844
+ };
845
+ const metricPercent = (explicitPercent, rate, fallbackPercent) => {
846
+ const explicit = firstDefined(explicitPercent, fallbackPercent);
847
+ return explicit == null ? ratio(rate) : percent(explicit);
848
+ };
849
+ return {
850
+ available,
851
+ state: statusLabel(firstDefined(source.state, source.status), available ? 'active' : 'warming_up'),
852
+ capability_level: displayText(firstDefined(activation.capability_level, source.capability_level, source.capability, 'unknown'), 40),
853
+ capture_percent: metricPercent(capture.percent, capture.rate, source.capture_percent),
854
+ closure_percent: metricPercent(closure.percent, closure.rate, source.closure_percent),
855
+ effectiveness_percent: metricPercent(
856
+ effectiveness.followed_percent,
857
+ firstDefined(effectiveness.follow_through_rate, effectiveness.rate),
858
+ source.effectiveness_percent,
859
+ ),
860
+ drift: driftAvailable ? drift.detected : null,
861
+ exact_fix: displayText(firstDefined(drift.repair_command, source.exact_fix, source.repair_command, ''), 180),
862
+ };
863
+ }
864
+
816
865
  function normalizeFleetSnapshot(raw, options) {
817
866
  const status = raw.status?.data || {};
818
867
  const capacity = raw.capacity?.data || {};
@@ -873,6 +922,7 @@ function normalizeFleetSnapshot(raw, options) {
873
922
  const recentDecisions = normalizeRecentDecisions(status, report, fleet);
874
923
  const gates = normalizeGates(status, report);
875
924
  const arbitrations = normalizeArbitrations(status, report, fleet);
925
+ const activationCoverage = normalizeActivationCoverage(status, report, fleet);
876
926
  const fixCommands = normalizeFixCommands(status, capacity);
877
927
  const errors = Object.entries(raw)
878
928
  .filter(([, value]) => value && value.ok === false)
@@ -893,6 +943,7 @@ function normalizeFleetSnapshot(raw, options) {
893
943
  degraded_hooks: missedHooks,
894
944
  gates,
895
945
  arbitrations,
946
+ activation_coverage: activationCoverage,
896
947
  agents,
897
948
  fix_commands: fixCommands.length ? fixCommands : ['npx @getmarrow/install doctor'],
898
949
  source_errors: errors,
@@ -944,6 +995,8 @@ function fleetPanel(snapshot) {
944
995
  '',
945
996
  `Live agents: ${snapshot.live_agents ?? 'unknown'}`,
946
997
  `Active workflows: ${snapshot.active_workflows ?? 'unknown'}`,
998
+ `Passive activation: ${snapshot.activation_coverage.state} capability=${snapshot.activation_coverage.capability_level}`,
999
+ `Passive coverage: ${snapshot.activation_coverage.capture_percent ?? 'insufficient data'}${snapshot.activation_coverage.capture_percent == null ? '' : '%'}; outcome closure=${snapshot.activation_coverage.closure_percent ?? 'insufficient data'}${snapshot.activation_coverage.closure_percent == null ? '' : '%'}; intervention follow-through=${snapshot.activation_coverage.effectiveness_percent ?? 'insufficient data'}${snapshot.activation_coverage.effectiveness_percent == null ? '' : '%'}`,
947
1000
  `Agent disagreements: open=${snapshot.arbitrations.open_count} review_required=${snapshot.arbitrations.review_required_count}`,
948
1001
  `Latest arbitration: ${arbitrationSummary}`,
949
1002
  `Risky actions waiting for proof: ${snapshot.proof_waiting}`,
@@ -1361,6 +1414,10 @@ function renderFleetTui(state) {
1361
1414
  const snapshot = state.snapshot;
1362
1415
  const selectedAgent = snapshot.agents[state.agentIndex] || { id: 'no agent returned', status: 'unknown', role: '' };
1363
1416
  const degraded = snapshot.degraded_hooks.length ? snapshot.degraded_hooks.join(', ') : 'none';
1417
+ const activation = snapshot.activation_coverage;
1418
+ const coverageValue = activation.available
1419
+ ? `${activation.state}; capture=${activation.capture_percent ?? 'n/a'}%; closure=${activation.closure_percent ?? 'n/a'}%`
1420
+ : `${activation.state}; insufficient data`;
1364
1421
  const gateSummary = `deploy=${snapshot.gates.deploy} publish=${snapshot.gates.publish} merge=${snapshot.gates.merge}`;
1365
1422
  const fixCommand = snapshot.fix_commands[0] || 'npx @getmarrow/install doctor';
1366
1423
  const recent = snapshot.recent_decisions[0] || 'none reported yet';
@@ -1404,9 +1461,13 @@ function renderFleetTui(state) {
1404
1461
  hint: 'Latest fleet decision signal returned by Marrow.',
1405
1462
  },
1406
1463
  {
1407
- label: 'Degraded hooks',
1408
- value: degraded,
1409
- hint: degraded === 'none' ? 'No missing hooks reported.' : 'Repair hooks before trusting passive coverage.',
1464
+ label: 'Passive activation / coverage',
1465
+ value: coverageValue,
1466
+ hint: activation.drift
1467
+ ? `Configuration drift detected. ${activation.exact_fix || fixCommand}`
1468
+ : degraded === 'none'
1469
+ ? `Capability=${activation.capability_level}; no missing hooks reported.`
1470
+ : `Degraded hooks: ${degraded}. Repair before trusting passive coverage.`,
1410
1471
  },
1411
1472
  {
1412
1473
  label: 'Deploy/publish/merge gates',
@@ -1725,9 +1786,8 @@ async function runFleetInteractive(options, input = process.stdin, output = proc
1725
1786
  ? state.snapshot.recent_decisions.map((decision, index) => `${index + 1}. ${decision}`).join(' ')
1726
1787
  : 'No recent decisions returned yet.';
1727
1788
  } else if (state.cursor === 7) {
1728
- state.lastResult = state.snapshot.degraded_hooks.length
1729
- ? `Degraded hooks: ${state.snapshot.degraded_hooks.join(', ')}. Fix: ${state.snapshot.fix_commands[0]}`
1730
- : 'No degraded hooks reported.';
1789
+ const coverage = state.snapshot.activation_coverage;
1790
+ state.lastResult = `Passive activation=${coverage.state}; capability=${coverage.capability_level}; capture=${coverage.capture_percent ?? 'insufficient data'}; closure=${coverage.closure_percent ?? 'insufficient data'}; intervention follow-through=${coverage.effectiveness_percent ?? 'insufficient data'}${coverage.drift ? `. Drift detected. Fix: ${coverage.exact_fix || state.snapshot.fix_commands[0]}` : ''}`;
1731
1791
  } else if (state.cursor === 8) {
1732
1792
  state.lastResult = `Release gates: deploy=${state.snapshot.gates.deploy}, publish=${state.snapshot.gates.publish}, merge=${state.snapshot.gates.merge}.`;
1733
1793
  } else {
package/src/installer.js CHANGED
@@ -6,7 +6,38 @@ const crypto = require('node:crypto');
6
6
  const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
7
7
  const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
8
8
  const MARROW_BLOCK_END = '<!-- marrow:passive-end -->';
9
- const SOURCE_CLIENTS = new Set(['claude-code', 'cursor', 'windsurf', 'openclaw', 'codex', 'gemini', 'grok', 'deepseek', 'qwen', 'kimi', 'minimax', 'cline', 'opencode', 'hermes', 'glm', 'custom', 'unknown']);
9
+ const INSTALLER_ADAPTER_VERSION = '0.1.34';
10
+ const MCP_ADAPTER_VERSION = '3.9.50';
11
+ const SDK_ADAPTER_VERSION = '3.7.49';
12
+ const SDK_ADAPTER_INTEGRITY = 'sha512-9a6pWWACubWTmulG7TwwgMNP/iSzoEh4wCH2oWUyO8XHeD2Eokj1GXrkCyX86md3bV7vGX6kyllfswZDlGj6WA==';
13
+ const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
14
+ const MCP_PACKAGE_SPEC = `@getmarrow/mcp@${MCP_ADAPTER_VERSION}`;
15
+ const MCP_CONTEXT_HOOK_COMMAND = `npx -y ${MCP_PACKAGE_SPEC} context-hook`;
16
+ const MCP_PRE_ACTION_HOOK_COMMAND = `npx -y ${MCP_PACKAGE_SPEC} pre-action-hook`;
17
+ const MCP_ACTION_RESULT_HOOK_COMMAND = `npx -y ${MCP_PACKAGE_SPEC} hook`;
18
+ const MCP_SESSION_END_HOOK_COMMAND = `npx -y ${MCP_PACKAGE_SPEC} session-hook`;
19
+ const NATIVE_HOOK_MATCHER = 'Bash|Edit|Write|MultiEdit|mcp__(?!marrow_).*';
20
+ const NATIVE_EXPECTED_HOOKS = ['prompt', 'pre_action', 'action_result', 'session_end'];
21
+ const SOURCE_CLIENTS = new Set(['claude-code', 'cursor', 'composer', 'windsurf', 'openclaw', 'codex', 'gemini', 'grok', 'deepseek', 'qwen', 'kimi', 'minimax', 'cline', 'opencode', 'hermes', 'glm', 'custom', 'unknown']);
22
+ const HARNESS_CAPABILITY_REGISTRY = Object.freeze([
23
+ { client: 'claude-code', capability_level: 'native_hooks', automatic: ['prompt', 'pre_action', 'action_result', 'session_end'], install_surface: 'mcp' },
24
+ { client: 'cursor', capability_level: 'mcp', automatic: ['mcp_tool_calls'], install_surface: 'mcp' },
25
+ { client: 'composer', capability_level: 'mcp', automatic: ['mcp_tool_calls'], install_surface: 'mcp' },
26
+ { client: 'cline', capability_level: 'mcp', automatic: ['mcp_tool_calls'], install_surface: 'mcp' },
27
+ { client: 'windsurf', capability_level: 'mcp', automatic: ['mcp_tool_calls'], install_surface: 'mcp' },
28
+ { client: 'codex', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
29
+ { client: 'opencode', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
30
+ { client: 'hermes', capability_level: 'event_contract', automatic: [], install_surface: 'addon' },
31
+ { client: 'openclaw', capability_level: 'event_contract', automatic: [], install_surface: 'addon' },
32
+ { client: 'gemini', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
33
+ { client: 'grok', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
34
+ { client: 'deepseek', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
35
+ { client: 'qwen', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
36
+ { client: 'kimi', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
37
+ { client: 'minimax', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
38
+ { client: 'glm', capability_level: 'governed_wrapper', automatic: ['pre_action', 'action_result', 'outcome_closure'], install_surface: 'runner' },
39
+ { client: 'custom', capability_level: 'event_contract', automatic: [], install_surface: 'event_contract' },
40
+ ]);
10
41
 
11
42
  function sourceClient() {
12
43
  const raw = String(process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || process.env.MARROW_AGENT_CLIENT || '').trim().toLowerCase().replace(/\s+/g, '-').replace(/^@/, '');
@@ -15,6 +46,7 @@ function sourceClient() {
15
46
  claude_code: 'claude-code',
16
47
  'claude-code': 'claude-code',
17
48
  cursor: 'cursor',
49
+ composer: 'composer',
18
50
  windsurf: 'windsurf',
19
51
  openclaw: 'openclaw',
20
52
  codex: 'codex',
@@ -422,38 +454,245 @@ function upsertBlock(content, block) {
422
454
  return `${content}${separator}${block}\n`;
423
455
  }
424
456
 
425
- function upsertClaudeHooks(settingsPath) {
426
- const settings = parseJsonObject(settingsPath);
427
- const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
428
- ? settings.hooks
429
- : {};
430
- const postToolUse = Array.isArray(hooks.PostToolUse) ? [...hooks.PostToolUse] : [];
431
- const userPromptSubmit = Array.isArray(hooks.UserPromptSubmit) ? [...hooks.UserPromptSubmit] : [];
432
-
433
- const hasPost = postToolUse.some((entry) => JSON.stringify(entry).includes('npx -y @getmarrow/mcp hook'));
434
- const hasPrompt = userPromptSubmit.some((entry) => JSON.stringify(entry).includes('npx -y @getmarrow/mcp context-hook'));
457
+ function exactHookConfigured(settings, eventName, command, matcher) {
458
+ const entries = settings?.hooks?.[eventName];
459
+ if (!Array.isArray(entries)) return false;
460
+ return entries.some((entry) => {
461
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return false;
462
+ if (matcher != null && entry.matcher !== matcher) return false;
463
+ if (!Array.isArray(entry.hooks)) return false;
464
+ return entry.hooks.some((hook) => (
465
+ hook
466
+ && typeof hook === 'object'
467
+ && !Array.isArray(hook)
468
+ && hook.type === 'command'
469
+ && typeof hook.command === 'string'
470
+ && hook.command.trim() === command
471
+ ));
472
+ });
473
+ }
435
474
 
436
- if (!hasPost) {
437
- postToolUse.push({
438
- matcher: 'Bash|Edit|Write|MultiEdit|mcp__(?!marrow_).*',
439
- hooks: [{ type: 'command', command: 'npx -y @getmarrow/mcp hook' }],
475
+ function exactHookDescriptors(settings, eventName, command, matcher) {
476
+ const entries = settings?.hooks?.[eventName];
477
+ if (!Array.isArray(entries)) return [];
478
+ return entries.flatMap((entry) => {
479
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return [];
480
+ if (matcher != null && entry.matcher !== matcher) return [];
481
+ if (!Array.isArray(entry.hooks)) return [];
482
+ return entry.hooks.flatMap((hook) => {
483
+ if (!hook || typeof hook !== 'object' || Array.isArray(hook)
484
+ || hook.type !== 'command' || typeof hook.command !== 'string'
485
+ || hook.command.trim() !== command) return [];
486
+ return [{
487
+ matcher: typeof entry.matcher === 'string' ? entry.matcher : null,
488
+ command,
489
+ timeout: typeof hook.timeout === 'number' && Number.isFinite(hook.timeout)
490
+ ? hook.timeout
491
+ : null,
492
+ }];
440
493
  });
494
+ });
495
+ }
496
+
497
+ function marrowHookSubcommand(command) {
498
+ if (typeof command !== 'string') return null;
499
+ const match = command.trim().match(
500
+ /^npx\s+(?:-y\s+)?@getmarrow\/mcp(?:@[^\s]+)?\s+(context-hook|pre-action-hook|hook|session-hook)$/,
501
+ );
502
+ return match?.[1] || null;
503
+ }
504
+
505
+ function reconcileMarrowCommandHook(settings, eventName, subcommand, command, matcher) {
506
+ const original = Array.isArray(settings?.hooks?.[eventName]) ? settings.hooks[eventName] : [];
507
+ let preferredHandler = null;
508
+ const retained = [];
509
+ for (const entry of original) {
510
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !Array.isArray(entry.hooks)) {
511
+ retained.push(entry);
512
+ continue;
513
+ }
514
+ const remaining = [];
515
+ for (const hook of entry.hooks) {
516
+ const detected = hook && typeof hook === 'object' && !Array.isArray(hook)
517
+ && hook.type === 'command' ? marrowHookSubcommand(hook.command) : null;
518
+ if (detected) {
519
+ const exactMatcher = matcher == null ? entry.matcher === undefined : entry.matcher === matcher;
520
+ if (detected === subcommand && (!preferredHandler || (hook.command === command && exactMatcher))) {
521
+ preferredHandler = hook;
522
+ }
523
+ continue;
524
+ }
525
+ remaining.push(hook);
526
+ }
527
+ if (remaining.length > 0) retained.push({ ...entry, hooks: remaining });
441
528
  }
442
- if (!hasPrompt) {
443
- userPromptSubmit.push({
444
- hooks: [{ type: 'command', command: 'npx -y @getmarrow/mcp context-hook' }],
529
+ const canonical = { hooks: [{ ...(preferredHandler || {}), type: 'command', command }] };
530
+ if (matcher != null) canonical.matcher = matcher;
531
+ retained.push(canonical);
532
+ return retained;
533
+ }
534
+
535
+ function marrowHookDescriptors(settings, eventName, subcommand) {
536
+ const entries = settings?.hooks?.[eventName];
537
+ if (!Array.isArray(entries)) return [];
538
+ return entries.flatMap((entry) => {
539
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !Array.isArray(entry.hooks)) return [];
540
+ return entry.hooks.flatMap((hook) => {
541
+ if (!hook || typeof hook !== 'object' || Array.isArray(hook)
542
+ || hook.type !== 'command') return [];
543
+ const detected = marrowHookSubcommand(hook.command);
544
+ if (!detected || (subcommand && detected !== subcommand)) return [];
545
+ return [{
546
+ matcher: typeof entry.matcher === 'string' ? entry.matcher : null,
547
+ command: hook.command.trim(),
548
+ timeout: typeof hook.timeout === 'number' && Number.isFinite(hook.timeout) ? hook.timeout : null,
549
+ }];
445
550
  });
551
+ });
552
+ }
553
+
554
+ function safeJsonObject(filePath) {
555
+ try {
556
+ return parseJsonObject(filePath);
557
+ } catch {
558
+ return {};
446
559
  }
560
+ }
561
+
562
+ function claudeNativeHookFingerprint(settings) {
563
+ const contract = {
564
+ schema: 'marrow-claude-native-hooks.v3',
565
+ adapter_version: MCP_ADAPTER_VERSION,
566
+ expected_hooks: NATIVE_EXPECTED_HOOKS,
567
+ configured: {
568
+ prompt: exactHookConfigured(settings, 'UserPromptSubmit', MCP_CONTEXT_HOOK_COMMAND),
569
+ pre_action: exactHookConfigured(settings, 'PreToolUse', MCP_PRE_ACTION_HOOK_COMMAND, NATIVE_HOOK_MATCHER),
570
+ action_result_success: exactHookConfigured(settings, 'PostToolUse', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER),
571
+ action_result_failure: exactHookConfigured(settings, 'PostToolUseFailure', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER),
572
+ session_end: exactHookConfigured(settings, 'Stop', MCP_SESSION_END_HOOK_COMMAND),
573
+ },
574
+ descriptors: {
575
+ prompt: exactHookDescriptors(settings, 'UserPromptSubmit', MCP_CONTEXT_HOOK_COMMAND),
576
+ pre_action: exactHookDescriptors(settings, 'PreToolUse', MCP_PRE_ACTION_HOOK_COMMAND, NATIVE_HOOK_MATCHER),
577
+ action_result_success: exactHookDescriptors(settings, 'PostToolUse', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER),
578
+ action_result_failure: exactHookDescriptors(settings, 'PostToolUseFailure', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER),
579
+ session_end: exactHookDescriptors(settings, 'Stop', MCP_SESSION_END_HOOK_COMMAND),
580
+ },
581
+ active_marrow_handlers: {
582
+ prompt: marrowHookDescriptors(settings, 'UserPromptSubmit'),
583
+ pre_action: marrowHookDescriptors(settings, 'PreToolUse'),
584
+ action_result_success: marrowHookDescriptors(settings, 'PostToolUse'),
585
+ action_result_failure: marrowHookDescriptors(settings, 'PostToolUseFailure'),
586
+ session_end: marrowHookDescriptors(settings, 'Stop'),
587
+ },
588
+ };
589
+ return crypto.createHash('sha256').update(JSON.stringify(contract)).digest('hex');
590
+ }
591
+
592
+ function upsertClaudeHooks(settingsPath) {
593
+ const settings = parseJsonObject(settingsPath);
594
+ const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
595
+ ? settings.hooks
596
+ : {};
597
+ const postToolUse = reconcileMarrowCommandHook(
598
+ settings, 'PostToolUse', 'hook', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER,
599
+ );
600
+ const postToolUseFailure = reconcileMarrowCommandHook(
601
+ settings, 'PostToolUseFailure', 'hook', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER,
602
+ );
603
+ const preToolUse = reconcileMarrowCommandHook(
604
+ settings, 'PreToolUse', 'pre-action-hook', MCP_PRE_ACTION_HOOK_COMMAND, NATIVE_HOOK_MATCHER,
605
+ );
606
+ const userPromptSubmit = reconcileMarrowCommandHook(
607
+ settings, 'UserPromptSubmit', 'context-hook', MCP_CONTEXT_HOOK_COMMAND,
608
+ );
609
+ const stop = reconcileMarrowCommandHook(
610
+ settings, 'Stop', 'session-hook', MCP_SESSION_END_HOOK_COMMAND,
611
+ );
447
612
 
448
613
  settings.hooks = {
449
614
  ...hooks,
615
+ PreToolUse: preToolUse,
450
616
  PostToolUse: postToolUse,
617
+ PostToolUseFailure: postToolUseFailure,
451
618
  UserPromptSubmit: userPromptSubmit,
619
+ Stop: stop,
452
620
  };
453
621
 
454
622
  return JSON.stringify(settings, null, 2) + '\n';
455
623
  }
456
624
 
625
+ function activationProfile(detection, plan, changes, client) {
626
+ const registry = HARNESS_CAPABILITY_REGISTRY.find((entry) => entry.client === client)
627
+ || HARNESS_CAPABILITY_REGISTRY.find((entry) => entry.client === 'custom');
628
+ const sdkDependency = inspectSdkDependency(detection);
629
+ const capabilityLevel = client === 'custom' && (plan.mode === 'sdk' || plan.mode === 'both')
630
+ ? 'sdk_passive_runtime'
631
+ : registry.capability_level;
632
+ const expectedHooks = capabilityLevel === 'sdk_passive_runtime'
633
+ ? ['pre_action', 'action_result', 'outcome_closure']
634
+ : [...registry.automatic];
635
+ const adapterVersion = capabilityLevel === 'native_hooks' || capabilityLevel === 'mcp'
636
+ ? MCP_ADAPTER_VERSION
637
+ : capabilityLevel === 'sdk_passive_runtime'
638
+ ? SDK_ADAPTER_VERSION
639
+ : INSTALLER_ADAPTER_VERSION;
640
+ const observedHooks = [];
641
+ const claudeSettings = safeJsonObject(detection.paths.claudeSettings);
642
+ if (capabilityLevel === 'native_hooks'
643
+ && exactHookConfigured(claudeSettings, 'UserPromptSubmit', MCP_CONTEXT_HOOK_COMMAND)) observedHooks.push('prompt');
644
+ if (capabilityLevel === 'native_hooks'
645
+ && exactHookConfigured(claudeSettings, 'PreToolUse', MCP_PRE_ACTION_HOOK_COMMAND, NATIVE_HOOK_MATCHER)) observedHooks.push('pre_action');
646
+ if (capabilityLevel === 'native_hooks'
647
+ && exactHookConfigured(claudeSettings, 'PostToolUse', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER)
648
+ && exactHookConfigured(claudeSettings, 'PostToolUseFailure', MCP_ACTION_RESULT_HOOK_COMMAND, NATIVE_HOOK_MATCHER)) observedHooks.push('action_result');
649
+ if (capabilityLevel === 'native_hooks'
650
+ && exactHookConfigured(claudeSettings, 'Stop', MCP_SESSION_END_HOOK_COMMAND)) observedHooks.push('session_end');
651
+ const passiveRuntime = safeRead(detection.paths.passiveRuntime);
652
+ if (capabilityLevel === 'sdk_passive_runtime'
653
+ && sdkDependency.present
654
+ && /await import\('@getmarrow\/sdk'\)/.test(passiveRuntime)
655
+ && /runtime\.install\(\)/.test(passiveRuntime)) {
656
+ for (const hook of ['pre_action', 'action_result', 'outcome_closure']) {
657
+ if (!observedHooks.includes(hook)) observedHooks.push(hook);
658
+ }
659
+ }
660
+ const mcpConfigs = [detection.paths.mcpJson, detection.paths.cursorMcp]
661
+ .map((filePath) => safeJsonObject(filePath));
662
+ if (capabilityLevel === 'mcp' && mcpConfigs.some((config) => (
663
+ config?.mcpServers?.marrow?.command === 'npx'
664
+ && Array.isArray(config.mcpServers.marrow.args)
665
+ && config.mcpServers.marrow.args.join(' ') === `-y ${MCP_PACKAGE_SPEC}`
666
+ ))) observedHooks.push('mcp_tool_calls');
667
+ const fingerprintMaterial = changes
668
+ .filter((change) => change.applied || change.already_present)
669
+ .map((change) => `${change.label}:${crypto.createHash('sha256').update(safeRead(change.path)).digest('hex')}`)
670
+ .sort()
671
+ .join('|');
672
+ const configFingerprint = capabilityLevel === 'native_hooks'
673
+ ? claudeNativeHookFingerprint(claudeSettings)
674
+ : crypto.createHash('sha256')
675
+ .update(`${client}:${capabilityLevel}:${expectedHooks.join(',')}:${fingerprintMaterial}`)
676
+ .digest('hex');
677
+ const complete = expectedHooks.length > 0 && expectedHooks.every((hook) => observedHooks.includes(hook));
678
+ const exactFix = complete
679
+ ? null
680
+ : capabilityLevel === 'sdk_passive_runtime' && !sdkDependency.present
681
+ ? `${sdkDependency.install_command} && npx @getmarrow/install --repair`
682
+ : capabilityLevel === 'governed_wrapper'
683
+ ? `npx @getmarrow/install run --agent <agent-id> -- ${client}`
684
+ : 'npx @getmarrow/install --repair';
685
+ return {
686
+ adapter_version: adapterVersion,
687
+ capability_level: capabilityLevel,
688
+ config_fingerprint: configFingerprint,
689
+ expected_hooks: expectedHooks,
690
+ observed_hooks: observedHooks,
691
+ complete,
692
+ exact_fix: exactFix,
693
+ };
694
+ }
695
+
457
696
  function upsertMcpServerConfig(filePath) {
458
697
  const config = parseJsonObject(filePath);
459
698
  const servers = config.mcpServers && typeof config.mcpServers === 'object' && !Array.isArray(config.mcpServers)
@@ -461,7 +700,7 @@ function upsertMcpServerConfig(filePath) {
461
700
  : {};
462
701
  servers.marrow = {
463
702
  command: 'npx',
464
- args: ['-y', '@getmarrow/mcp'],
703
+ args: ['-y', MCP_PACKAGE_SPEC],
465
704
  env: {
466
705
  MARROW_API_KEY: '${MARROW_API_KEY}',
467
706
  MARROW_BASE_URL: '${MARROW_BASE_URL}',
@@ -485,7 +724,7 @@ function inspectSdkDependency(detection) {
485
724
  return {
486
725
  required: true,
487
726
  present: false,
488
- install_command: 'npm install @getmarrow/sdk',
727
+ install_command: `npm install @getmarrow/sdk@${SDK_ADAPTER_VERSION}`,
489
728
  warning: 'package.json could not be parsed; verify @getmarrow/sdk manually.',
490
729
  };
491
730
  }
@@ -496,11 +735,72 @@ function inspectSdkDependency(detection) {
496
735
  packageJson.optionalDependencies,
497
736
  packageJson.peerDependencies,
498
737
  ];
499
- const present = dependencyBlocks.some((deps) => deps && Object.prototype.hasOwnProperty.call(deps, '@getmarrow/sdk'));
738
+ const declaredSpec = dependencyBlocks
739
+ .map((deps) => deps && typeof deps['@getmarrow/sdk'] === 'string' ? deps['@getmarrow/sdk'] : null)
740
+ .find(Boolean) || null;
741
+ const objectTargetsSdk = (value) => {
742
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
743
+ return Object.entries(value).some(([key, nested]) => (
744
+ key.includes('@getmarrow/sdk') || objectTargetsSdk(nested)
745
+ ));
746
+ };
747
+ const overrideDetected = objectTargetsSdk(packageJson.overrides)
748
+ || objectTargetsSdk(packageJson.resolutions)
749
+ || objectTargetsSdk(packageJson.pnpm?.overrides);
750
+ let lockVerified = false;
751
+ try {
752
+ const lock = JSON.parse(safeRead(path.join(detection.root, 'package-lock.json')) || '{}');
753
+ const rootLock = lock?.packages?.[''];
754
+ const lockedSdk = lock?.packages?.['node_modules/@getmarrow/sdk'];
755
+ const lockedDeclaration = [
756
+ rootLock?.dependencies,
757
+ rootLock?.devDependencies,
758
+ rootLock?.optionalDependencies,
759
+ rootLock?.peerDependencies,
760
+ ].map((deps) => deps && typeof deps['@getmarrow/sdk'] === 'string' ? deps['@getmarrow/sdk'] : null)
761
+ .find(Boolean) || null;
762
+ lockVerified = [2, 3].includes(lock?.lockfileVersion)
763
+ && lockedDeclaration === declaredSpec
764
+ && lockedSdk?.version === SDK_ADAPTER_VERSION
765
+ && lockedSdk?.resolved === SDK_ADAPTER_TARBALL
766
+ && lockedSdk?.integrity === SDK_ADAPTER_INTEGRITY;
767
+ } catch {
768
+ lockVerified = false;
769
+ }
770
+ const installedPackagePath = findUp(
771
+ detection.root,
772
+ [path.join('node_modules', '@getmarrow', 'sdk', 'package.json')],
773
+ );
774
+ let installedVersion = null;
775
+ let installedName = null;
776
+ try {
777
+ const installedPackage = installedPackagePath ? JSON.parse(safeRead(installedPackagePath)) : null;
778
+ installedVersion = typeof installedPackage?.version === 'string' ? installedPackage.version : null;
779
+ installedName = typeof installedPackage?.name === 'string' ? installedPackage.name : null;
780
+ } catch {
781
+ installedVersion = null;
782
+ installedName = null;
783
+ }
784
+ const declarationTrusted = typeof declaredSpec === 'string'
785
+ && declaredSpec.trim().length > 0
786
+ && /^[v0-9xX*<>=~^|.\s-]+$/.test(declaredSpec.trim());
787
+ const present = declarationTrusted
788
+ && !overrideDetected
789
+ && lockVerified
790
+ && installedName === '@getmarrow/sdk'
791
+ && installedVersion === SDK_ADAPTER_VERSION;
500
792
  return {
501
793
  required: true,
502
794
  present,
503
- install_command: present ? null : 'npm install @getmarrow/sdk',
795
+ declared: declaredSpec != null,
796
+ declared_spec: declaredSpec,
797
+ declaration_trusted: declarationTrusted,
798
+ override_detected: overrideDetected,
799
+ lock_verified: lockVerified,
800
+ installed_name: installedName,
801
+ installed_version: installedVersion,
802
+ expected_version: SDK_ADAPTER_VERSION,
803
+ install_command: present ? null : `npm install @getmarrow/sdk@${SDK_ADAPTER_VERSION}`,
504
804
  };
505
805
  }
506
806
 
@@ -569,8 +869,7 @@ function buildPlan(detection, options) {
569
869
  }
570
870
 
571
871
  function applyPlan(plan, options) {
572
- const changes = [];
573
- for (const write of plan.writes) {
872
+ const prepared = plan.writes.map((write) => {
574
873
  const before = safeRead(write.path);
575
874
  let after;
576
875
  if (write.type === 'file') {
@@ -587,6 +886,11 @@ function applyPlan(plan, options) {
587
886
  throw new Error(`Unknown write type: ${write.type}`);
588
887
  }
589
888
 
889
+ return { write, before, after };
890
+ });
891
+
892
+ const changes = [];
893
+ for (const { write, before, after } of prepared) {
590
894
  const changed = before !== after;
591
895
  const writeApplied = Boolean(options.yes && !options.dryRun && !options.doctor);
592
896
  changes.push({
@@ -628,7 +932,6 @@ function isCanonicalTimestamp(value) {
628
932
 
629
933
  function runtimeGateVerified(runtime) {
630
934
  if (!runtime || typeof runtime !== 'object') return false;
631
- if (runtime.ok === true) return true;
632
935
  const gate = runtime.risk_gate;
633
936
  if (!gate || typeof gate !== 'object') return false;
634
937
  if (typeof gate.allow === 'boolean' || typeof gate.allowed === 'boolean') return true;
@@ -733,7 +1036,19 @@ async function runSelfTest(options) {
733
1036
  const installValueMoment = buildInstallValueMoment(firstValueSignal, status, runtime, performance, firstValue, tokenValueProof);
734
1037
  let activationReceipt = null;
735
1038
  let activationVerified = false;
1039
+ let activationProfileReceipt = null;
736
1040
  if (options.activation) {
1041
+ const activationAdapterVersion = options.activation.adapter_version || INSTALLER_ADAPTER_VERSION;
1042
+ const activationCapabilityLevel = options.activation.capability_level || 'event_contract';
1043
+ const activationExpectedHooks = Array.isArray(options.activation.expected_hooks) ? options.activation.expected_hooks : [];
1044
+ const activationConfigFingerprint = options.activation.config_fingerprint || crypto.createHash('sha256')
1045
+ .update(JSON.stringify({
1046
+ harness: options.activation.harness || options.client || 'custom',
1047
+ install_surface: options.activation.install_surface || 'unknown',
1048
+ adapter_version: activationAdapterVersion,
1049
+ expected_hooks: activationExpectedHooks,
1050
+ }))
1051
+ .digest('hex');
737
1052
  activationReceipt = firstValue && firstValue.activation_receipt;
738
1053
  const receiptValid = activationReceipt
739
1054
  && typeof activationReceipt === 'object'
@@ -750,7 +1065,58 @@ async function runSelfTest(options) {
750
1065
  if (!receiptValid) {
751
1066
  throw new Error('activation receipt did not verify the exact self-test decision, agent, runtime gate, and closed successful outcome');
752
1067
  }
753
- activationVerified = Boolean(firstValue.active && runtimeGateVerified(runtime) && (status.enabled ?? status.ok));
1068
+ activationProfileReceipt = await requestJson(`${baseUrl}/v1/agent/integrations/events`, {
1069
+ method: 'POST',
1070
+ headers,
1071
+ body: JSON.stringify({
1072
+ event_id: `activation-${activationConfigFingerprint.slice(0, 32)}`,
1073
+ event_type: 'activation_profile_registered',
1074
+ harness: options.activation.harness,
1075
+ agent_id: options.agentId,
1076
+ session_id: headers['x-marrow-session-id'],
1077
+ adapter_version: activationAdapterVersion,
1078
+ capability_level: activationCapabilityLevel,
1079
+ config_fingerprint: activationConfigFingerprint,
1080
+ expected_hooks: activationExpectedHooks,
1081
+ action: 'passive integration activation profile registered',
1082
+ occurred_at: new Date().toISOString(),
1083
+ }),
1084
+ });
1085
+ const profileCoverage = activationProfileReceipt?.activation_coverage;
1086
+ const profileReceipt = profileCoverage?.profile_receipt;
1087
+ const accountBindingId = profileCoverage?.account_binding_id;
1088
+ const expectedConfigurationBindingId = typeof accountBindingId === 'string'
1089
+ ? crypto.createHash('sha256')
1090
+ .update(`agent-config-receipt:v2:${accountBindingId}:${activationConfigFingerprint}`)
1091
+ .digest('hex')
1092
+ : null;
1093
+ const expectedHooksMatch = Array.isArray(profileCoverage?.capture_coverage?.expected_hooks)
1094
+ && [...profileCoverage.capture_coverage.expected_hooks].sort().join(',') === [...activationExpectedHooks].sort().join(',');
1095
+ const profileBound = Boolean(
1096
+ activationProfileReceipt?.accepted === true
1097
+ && profileCoverage?.agent_id === options.agentId
1098
+ && profileCoverage?.harness === options.activation.harness
1099
+ && profileCoverage?.activation?.adapter_version === activationAdapterVersion
1100
+ && profileCoverage?.activation?.capability_level === activationCapabilityLevel
1101
+ && profileReceipt?.agent_id === options.agentId
1102
+ && profileReceipt?.harness === options.activation.harness
1103
+ && profileReceipt?.adapter_version === activationAdapterVersion
1104
+ && profileReceipt?.capability_level === activationCapabilityLevel
1105
+ && typeof accountBindingId === 'string'
1106
+ && accountBindingId.length === 64
1107
+ && profileReceipt?.account_binding_id === accountBindingId
1108
+ && profileReceipt?.config_fingerprint_verified === true
1109
+ && profileReceipt?.configuration_binding_id === expectedConfigurationBindingId
1110
+ && profileReceipt?.expected_hooks_verified === true
1111
+ && expectedHooksMatch
1112
+ );
1113
+ activationVerified = Boolean(
1114
+ firstValue.active
1115
+ && runtimeGateVerified(runtime)
1116
+ && (status.enabled ?? status.ok)
1117
+ && options.activation.complete === true
1118
+ && profileBound,
1119
+ );
754
1120
  if (!activationVerified) {
755
1121
  throw new Error('activation prerequisites were not all verified by the server');
756
1122
  }
@@ -769,6 +1135,8 @@ async function runSelfTest(options) {
769
1135
  runtime_before_you_act: runtime.before_you_act || null,
770
1136
  activation_verified: activationVerified,
771
1137
  activation_receipt: activationReceipt,
1138
+ activation_profile_receipt: activationProfileReceipt,
1139
+ activation_coverage: status.activation_coverage || firstValue.activation_coverage || null,
772
1140
  first_value: firstValue && firstValue.ok !== false ? firstValue : null,
773
1141
  first_value_signal: firstValueSignal,
774
1142
  install_value_moment: installValueMoment,
@@ -916,7 +1284,7 @@ function printReport(report) {
916
1284
 
917
1285
  process.stdout.write('\nPlanned changes:\n');
918
1286
  for (const change of report.changes) {
919
- const marker = change.changed ? (report.writeMode === 'write' ? 'wrote' : 'would write') : 'unchanged';
1287
+ const marker = change.applied ? 'wrote' : change.changed ? 'would write' : 'unchanged';
920
1288
  process.stdout.write(`- ${marker}: ${change.label} (${change.path})\n`);
921
1289
  }
922
1290
 
@@ -1025,6 +1393,9 @@ async function install(options) {
1025
1393
  if (options.activate && options.selfTest === false) {
1026
1394
  throw new Error('activate requires the server self-test');
1027
1395
  }
1396
+ if (options.repair && options.yes !== true && !options.dryRun && !options.doctor) {
1397
+ throw new Error('repair requires explicit write authorization (--yes)');
1398
+ }
1028
1399
  const detection = detectEnvironment(options.cwd);
1029
1400
  const plan = buildPlan(detection, options);
1030
1401
  const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
@@ -1032,6 +1403,7 @@ async function install(options) {
1032
1403
  const client = detectedClient(detection);
1033
1404
  options.client = client;
1034
1405
  if (!options.agentId) options.agentId = stableAgentId(detection.root, client);
1406
+ const profile = activationProfile(detection, plan, changes, client);
1035
1407
  options.activation = options.activate ? {
1036
1408
  harness: client,
1037
1409
  agent_id: options.agentId,
@@ -1041,14 +1413,20 @@ async function install(options) {
1041
1413
  .filter((change) => change.changed && change.applied && /hook|runtime|rule|instruction|config/i.test(change.label))
1042
1414
  .map((change) => change.label)
1043
1415
  .slice(0, 20),
1044
- capture_verified: changes.every((change) => change.applied),
1416
+ capture_verified: changes.every((change) => change.applied || change.already_present),
1417
+ adapter_version: profile.adapter_version,
1418
+ capability_level: profile.capability_level,
1419
+ config_fingerprint: profile.config_fingerprint,
1420
+ expected_hooks: profile.expected_hooks,
1421
+ observed_hooks: profile.observed_hooks,
1422
+ complete: profile.complete,
1045
1423
  intervention_verified: false,
1046
1424
  closure_verified: false,
1047
1425
  } : null;
1048
1426
  const configInspection = inspectNpmTokenConfig();
1049
1427
  const sdkDependency = inspectSdkDependency(detection);
1050
1428
  const configDiagnostics = configInspection.safe;
1051
- const configRepairs = options.repair && !options.dryRun && !options.doctor
1429
+ const configRepairs = options.repair && options.yes && !options.dryRun && !options.doctor
1052
1430
  ? repairConfigDiagnostics(configDiagnostics)
1053
1431
  : [];
1054
1432
  const envHints = options.apiKey ? [] : findLikelyEnvFiles(detection);
@@ -1063,7 +1441,7 @@ async function install(options) {
1063
1441
  if (options.activate && !selfTest.activation_verified) {
1064
1442
  throw new Error('Marrow activation failed: server confirmation was not returned');
1065
1443
  }
1066
- const changedConfig = changes.some((change) => change.changed) || configRepairs.some((repair) => repair.changed);
1444
+ const changedConfig = changes.some((change) => change.applied) || configRepairs.some((repair) => repair.changed);
1067
1445
  const selfTestPassed = Boolean(!selfTest.skipped && selfTest.active && !selfTest.error);
1068
1446
  const remediation = options.repair
1069
1447
  ? {
@@ -1098,6 +1476,7 @@ async function install(options) {
1098
1476
  agent_id: options.agentId,
1099
1477
  server_confirmed: Boolean(selfTest.activation_verified),
1100
1478
  receipt: selfTest.activation_receipt || null,
1479
+ profile,
1101
1480
  },
1102
1481
  changes,
1103
1482
  doctor: {
@@ -1150,4 +1529,8 @@ module.exports = {
1150
1529
  buildInstallValueMoment,
1151
1530
  buildTokenValueProof,
1152
1531
  stableAgentId,
1532
+ activationProfile,
1533
+ claudeNativeHookFingerprint,
1534
+ printReport,
1535
+ HARNESS_CAPABILITY_REGISTRY,
1153
1536
  };