@intutic/sync-daemon 1.6.0

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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/brainIndexer.d.ts +51 -0
  4. package/dist/brainIndexer.d.ts.map +1 -0
  5. package/dist/brainIndexer.js +148 -0
  6. package/dist/brainIndexer.js.map +1 -0
  7. package/dist/configReader.d.ts +46 -0
  8. package/dist/configReader.d.ts.map +1 -0
  9. package/dist/configReader.js +133 -0
  10. package/dist/configReader.js.map +1 -0
  11. package/dist/configWriter.d.ts +53 -0
  12. package/dist/configWriter.d.ts.map +1 -0
  13. package/dist/configWriter.js +503 -0
  14. package/dist/configWriter.js.map +1 -0
  15. package/dist/harness/aiderConfigMerger.d.ts +27 -0
  16. package/dist/harness/aiderConfigMerger.d.ts.map +1 -0
  17. package/dist/harness/aiderConfigMerger.js +139 -0
  18. package/dist/harness/aiderConfigMerger.js.map +1 -0
  19. package/dist/harness/antigravityHooks.d.ts +43 -0
  20. package/dist/harness/antigravityHooks.d.ts.map +1 -0
  21. package/dist/harness/antigravityHooks.js +193 -0
  22. package/dist/harness/antigravityHooks.js.map +1 -0
  23. package/dist/harness/claudeCodeHooks.d.ts +59 -0
  24. package/dist/harness/claudeCodeHooks.d.ts.map +1 -0
  25. package/dist/harness/claudeCodeHooks.js +414 -0
  26. package/dist/harness/claudeCodeHooks.js.map +1 -0
  27. package/dist/harness/claudeDesktopHooks.d.ts +30 -0
  28. package/dist/harness/claudeDesktopHooks.d.ts.map +1 -0
  29. package/dist/harness/claudeDesktopHooks.js +267 -0
  30. package/dist/harness/claudeDesktopHooks.js.map +1 -0
  31. package/dist/harness/clineHooks.d.ts +23 -0
  32. package/dist/harness/clineHooks.d.ts.map +1 -0
  33. package/dist/harness/clineHooks.js +169 -0
  34. package/dist/harness/clineHooks.js.map +1 -0
  35. package/dist/harness/continueHooks.d.ts +33 -0
  36. package/dist/harness/continueHooks.d.ts.map +1 -0
  37. package/dist/harness/continueHooks.js +124 -0
  38. package/dist/harness/continueHooks.js.map +1 -0
  39. package/dist/harness/cursorHooks.d.ts +27 -0
  40. package/dist/harness/cursorHooks.d.ts.map +1 -0
  41. package/dist/harness/cursorHooks.js +221 -0
  42. package/dist/harness/cursorHooks.js.map +1 -0
  43. package/dist/harness/cursorHooksJson.d.ts +29 -0
  44. package/dist/harness/cursorHooksJson.d.ts.map +1 -0
  45. package/dist/harness/cursorHooksJson.js +130 -0
  46. package/dist/harness/cursorHooksJson.js.map +1 -0
  47. package/dist/harness/gooseHardener.d.ts +39 -0
  48. package/dist/harness/gooseHardener.d.ts.map +1 -0
  49. package/dist/harness/gooseHardener.js +152 -0
  50. package/dist/harness/gooseHardener.js.map +1 -0
  51. package/dist/harness/gooseHooks.d.ts +24 -0
  52. package/dist/harness/gooseHooks.d.ts.map +1 -0
  53. package/dist/harness/gooseHooks.js +175 -0
  54. package/dist/harness/gooseHooks.js.map +1 -0
  55. package/dist/harness/gooseHooksWriter.d.ts +27 -0
  56. package/dist/harness/gooseHooksWriter.d.ts.map +1 -0
  57. package/dist/harness/gooseHooksWriter.js +147 -0
  58. package/dist/harness/gooseHooksWriter.js.map +1 -0
  59. package/dist/harness/hermesHooks.d.ts +31 -0
  60. package/dist/harness/hermesHooks.d.ts.map +1 -0
  61. package/dist/harness/hermesHooks.js +245 -0
  62. package/dist/harness/hermesHooks.js.map +1 -0
  63. package/dist/harness/mcpAutoWrite.d.ts +35 -0
  64. package/dist/harness/mcpAutoWrite.d.ts.map +1 -0
  65. package/dist/harness/mcpAutoWrite.js +334 -0
  66. package/dist/harness/mcpAutoWrite.js.map +1 -0
  67. package/dist/harness/n8nHooks.d.ts +28 -0
  68. package/dist/harness/n8nHooks.d.ts.map +1 -0
  69. package/dist/harness/n8nHooks.js +205 -0
  70. package/dist/harness/n8nHooks.js.map +1 -0
  71. package/dist/harness/openWebuiHooks.d.ts +31 -0
  72. package/dist/harness/openWebuiHooks.d.ts.map +1 -0
  73. package/dist/harness/openWebuiHooks.js +166 -0
  74. package/dist/harness/openWebuiHooks.js.map +1 -0
  75. package/dist/harness/openclawHooks.d.ts +34 -0
  76. package/dist/harness/openclawHooks.d.ts.map +1 -0
  77. package/dist/harness/openclawHooks.js +296 -0
  78. package/dist/harness/openclawHooks.js.map +1 -0
  79. package/dist/harness/openhandsHooks.d.ts +24 -0
  80. package/dist/harness/openhandsHooks.d.ts.map +1 -0
  81. package/dist/harness/openhandsHooks.js +178 -0
  82. package/dist/harness/openhandsHooks.js.map +1 -0
  83. package/dist/harness/openhandsHooksWriter.d.ts +23 -0
  84. package/dist/harness/openhandsHooksWriter.d.ts.map +1 -0
  85. package/dist/harness/openhandsHooksWriter.js +146 -0
  86. package/dist/harness/openhandsHooksWriter.js.map +1 -0
  87. package/dist/harness/piHooks.d.ts +32 -0
  88. package/dist/harness/piHooks.d.ts.map +1 -0
  89. package/dist/harness/piHooks.js +257 -0
  90. package/dist/harness/piHooks.js.map +1 -0
  91. package/dist/harness/rooCodeHooks.d.ts +31 -0
  92. package/dist/harness/rooCodeHooks.d.ts.map +1 -0
  93. package/dist/harness/rooCodeHooks.js +300 -0
  94. package/dist/harness/rooCodeHooks.js.map +1 -0
  95. package/dist/harness/windsurfHooks.d.ts +27 -0
  96. package/dist/harness/windsurfHooks.d.ts.map +1 -0
  97. package/dist/harness/windsurfHooks.js +202 -0
  98. package/dist/harness/windsurfHooks.js.map +1 -0
  99. package/dist/hashReporter.d.ts +33 -0
  100. package/dist/hashReporter.d.ts.map +1 -0
  101. package/dist/hashReporter.js +69 -0
  102. package/dist/hashReporter.js.map +1 -0
  103. package/dist/index.d.ts +43 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +43 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/integrityStore.d.ts +43 -0
  108. package/dist/integrityStore.d.ts.map +1 -0
  109. package/dist/integrityStore.js +94 -0
  110. package/dist/integrityStore.js.map +1 -0
  111. package/dist/lib/complianceProbes.d.ts +39 -0
  112. package/dist/lib/complianceProbes.d.ts.map +1 -0
  113. package/dist/lib/complianceProbes.js +115 -0
  114. package/dist/lib/complianceProbes.js.map +1 -0
  115. package/dist/lib/processPoller.d.ts +2 -0
  116. package/dist/lib/processPoller.d.ts.map +1 -0
  117. package/dist/lib/processPoller.js +87 -0
  118. package/dist/lib/processPoller.js.map +1 -0
  119. package/dist/lib/runtimeEnv.d.ts +49 -0
  120. package/dist/lib/runtimeEnv.d.ts.map +1 -0
  121. package/dist/lib/runtimeEnv.js +70 -0
  122. package/dist/lib/runtimeEnv.js.map +1 -0
  123. package/dist/skillWriter.d.ts +24 -0
  124. package/dist/skillWriter.d.ts.map +1 -0
  125. package/dist/skillWriter.js +112 -0
  126. package/dist/skillWriter.js.map +1 -0
  127. package/dist/syncLoop.d.ts +70 -0
  128. package/dist/syncLoop.d.ts.map +1 -0
  129. package/dist/syncLoop.js +546 -0
  130. package/dist/syncLoop.js.map +1 -0
  131. package/dist/trajectoryMonitor.d.ts +126 -0
  132. package/dist/trajectoryMonitor.d.ts.map +1 -0
  133. package/dist/trajectoryMonitor.js +291 -0
  134. package/dist/trajectoryMonitor.js.map +1 -0
  135. package/dist/watcher/driftWatcher.d.ts +25 -0
  136. package/dist/watcher/driftWatcher.d.ts.map +1 -0
  137. package/dist/watcher/driftWatcher.js +72 -0
  138. package/dist/watcher/driftWatcher.js.map +1 -0
  139. package/dist/watcher/settingsGuard.d.ts +32 -0
  140. package/dist/watcher/settingsGuard.d.ts.map +1 -0
  141. package/dist/watcher/settingsGuard.js +250 -0
  142. package/dist/watcher/settingsGuard.js.map +1 -0
  143. package/dist/wsClient.d.ts +49 -0
  144. package/dist/wsClient.d.ts.map +1 -0
  145. package/dist/wsClient.js +149 -0
  146. package/dist/wsClient.js.map +1 -0
  147. package/package.json +96 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * claudeDesktopHooks.ts — Claude Desktop PreToolUse hook generator.
3
+ *
4
+ * Claude Desktop shares the same hook format as Claude Code (PreToolUse,
5
+ * exit 2 on stdin JSON) but reads its config from an OS-specific location:
6
+ * macOS : ~/Library/Application Support/Claude/claude_desktop_config.json
7
+ * Linux : ~/.config/Claude/claude_desktop_config.json
8
+ *
9
+ * Hook events are written via dual-path logEvent:
10
+ * Path B (reliable) — synchronous append to .intutic/events/claude-desktop-hook-events.jsonl
11
+ * Path A (telemetry) — fire-and-forget https.request to the control plane
12
+ *
13
+ * LLD #14 — Phase 3 cross-harness defence (Gap 3, WS-B)
14
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
15
+ *
16
+ * @module
17
+ */
18
+ import * as fs from 'node:fs/promises';
19
+ import * as path from 'node:path';
20
+ import * as os from 'node:os';
21
+ import { createLogger } from '@intutic/logger';
22
+ import { newIso } from '@intutic/id';
23
+ const log = createLogger('sync-claude-desktop-hooks');
24
+ /** Governance-sensitive paths that the hook gate protects (mirrors claudeCodeHooks). */
25
+ const PROTECTED_PATHS = [
26
+ path.join(os.homedir(), '.claude', 'settings.json'),
27
+ path.join(os.homedir(), '.claude', 'settings.local.json'),
28
+ '.claude/settings.json',
29
+ '.claude/settings.local.json',
30
+ '.intutic/hooks',
31
+ '.intutic/integrity.json',
32
+ '.intutic/context_integrity.json',
33
+ ];
34
+ /** Resolve the OS-specific claude_desktop_config.json path. */
35
+ function resolveClaudeDesktopConfigPath() {
36
+ switch (process.platform) {
37
+ case 'darwin':
38
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
39
+ case 'linux':
40
+ return path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
41
+ default:
42
+ // Fallback to macOS path — Windows is out of scope for now
43
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
44
+ }
45
+ }
46
+ // ─── Node.js hook script template ─────────────────────────────────────────────
47
+ function buildClaudeDesktopCheckScript(proxyUrl, workspaceRoot, workspaceId) {
48
+ const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'claude-desktop-hook-events.jsonl');
49
+ const runtimeEnvPath = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
50
+ return `/**
51
+ * Intutic Claude Desktop PreToolUse execution gate.
52
+ * Auto-generated by intutic sync-daemon. DO NOT EDIT.
53
+ *
54
+ * Dual-path logEvent:
55
+ * Path B — synchronous file append to hook-events.jsonl
56
+ * Path A — fire-and-forget https.request to the control plane
57
+ *
58
+ * Proxy: ${proxyUrl}
59
+ * Generated: ${newIso()}
60
+ * Workspace: ${workspaceId}
61
+ */
62
+ 'use strict';
63
+ const path = require('path');
64
+ const os = require('os');
65
+ const fs = require('fs');
66
+ const https = require('https');
67
+ const crypto = require('crypto');
68
+ const url = require('url');
69
+
70
+ // ── Runtime env loader (Path A credentials) ──────────────────────────────────
71
+ const RUNTIME_ENV_PATH = ${JSON.stringify(runtimeEnvPath)};
72
+
73
+ function loadRuntimeEnv() {
74
+ const env = {
75
+ INTUTIC_HOST: 'https://api.intutic.ai',
76
+ INTUTIC_API_KEY: '',
77
+ INTUTIC_WORKSPACE_ID: ${JSON.stringify(workspaceId)},
78
+ };
79
+ try {
80
+ const raw = fs.readFileSync(RUNTIME_ENV_PATH, 'utf-8');
81
+ for (const line of raw.split('\\n')) {
82
+ const stripped = line.trim();
83
+ if (!stripped || stripped.startsWith('#') || !stripped.includes('=')) continue;
84
+ const eq = stripped.indexOf('=');
85
+ const k = stripped.slice(0, eq).trim();
86
+ const v = stripped.slice(eq + 1).trim();
87
+ env[k] = v;
88
+ }
89
+ } catch { /* file may not exist yet */ }
90
+ return env;
91
+ }
92
+
93
+ // ── Governance-sensitive paths ────────────────────────────────────────────────
94
+ const PROTECTED_PATHS = ${JSON.stringify(PROTECTED_PATHS)};
95
+
96
+ // ── Dual-path logEvent ────────────────────────────────────────────────────────
97
+ const HOOK_EVENTS_LOG = ${JSON.stringify(hookEventsLog)};
98
+
99
+ function logEvent(env, verdict, toolName, reason) {
100
+ const ts = new Date().toISOString();
101
+ const incidentId = crypto.createHash('sha1')
102
+ .update(ts + toolName + env.INTUTIC_WORKSPACE_ID)
103
+ .digest('hex').slice(0, 16);
104
+
105
+ const entry = {
106
+ event: verdict === 'blocked' ? 'tool_blocked' : 'tool_allowed',
107
+ toolName,
108
+ reason: reason || '',
109
+ workspaceId: env.INTUTIC_WORKSPACE_ID,
110
+ harnessType: 'claude-desktop',
111
+ timestamp: ts,
112
+ incidentId,
113
+ };
114
+
115
+ // Path B — reliable file append
116
+ try {
117
+ const dir = path.dirname(HOOK_EVENTS_LOG);
118
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
119
+ fs.appendFileSync(HOOK_EVENTS_LOG, JSON.stringify(entry) + '\\n', { flag: 'a' });
120
+ } catch { /* never crash the hook */ }
121
+
122
+ // Path A — fire-and-forget https.request
123
+ if (env.INTUTIC_API_KEY) {
124
+ try {
125
+ const body = Buffer.from(JSON.stringify({ events: [entry] }));
126
+ const parsedUrl = url.parse(env.INTUTIC_HOST + '/api/v1/hook-events');
127
+ const req = https.request({
128
+ hostname: parsedUrl.hostname,
129
+ port: parsedUrl.port || 443,
130
+ path: parsedUrl.path,
131
+ method: 'POST',
132
+ headers: {
133
+ 'Content-Type': 'application/json',
134
+ 'Content-Length': body.length,
135
+ 'Authorization': 'Bearer ' + env.INTUTIC_API_KEY,
136
+ },
137
+ });
138
+ req.on('error', () => {}); // fire-and-forget
139
+ req.write(body);
140
+ req.end();
141
+ } catch { /* never crash the hook */ }
142
+ }
143
+ }
144
+
145
+ // ── Main ──────────────────────────────────────────────────────────────────────
146
+ const runtimeEnv = loadRuntimeEnv();
147
+ let inputData = '';
148
+ process.stdin.on('data', (chunk) => { inputData += chunk; });
149
+ process.stdin.on('end', () => {
150
+ try {
151
+ const ctx = JSON.parse(inputData);
152
+ const toolName = (ctx.tool_name || ctx.toolName || '').toLowerCase();
153
+ const toolInput = ctx.tool_input || ctx.toolInput || {};
154
+ const toolInputStr = JSON.stringify(toolInput);
155
+
156
+ // 1. Protected-path guard
157
+ if (['edit', 'write', 'multiedit'].includes(toolName)) {
158
+ const targetPath = toolInput.path || toolInput.file_path ||
159
+ toolInput.new_path || toolInput.target || '';
160
+ for (const p of PROTECTED_PATHS) {
161
+ if (targetPath.includes(p) || String(targetPath).startsWith(p)) {
162
+ const reason = 'Attempt to modify governance-protected path: "' + targetPath + '". ' +
163
+ 'To change governance policy, update your SOP via the Intutic control plane.';
164
+ console.error('[Intutic Guardrail] BLOCKED: ' + reason);
165
+ logEvent(runtimeEnv, 'blocked', toolName, reason);
166
+ process.exit(2);
167
+ }
168
+ }
169
+ }
170
+
171
+ // 2. Bash/shell command bypass guard
172
+ if (toolName === 'bash') {
173
+ const cmd = toolInput.command || toolInput.cmd || '';
174
+ const BYPASS_PATTERNS = [
175
+ 'chflags nouchg',
176
+ 'chattr -i',
177
+ 'rm -rf .intutic',
178
+ 'CLAUDE_DESKTOP_HOOKS=',
179
+ ];
180
+ for (const pattern of BYPASS_PATTERNS) {
181
+ if (cmd.includes(pattern)) {
182
+ const reason = 'Shell command matches governance bypass pattern: "' + pattern + '"';
183
+ console.error('[Intutic Guardrail] BLOCKED: ' + reason);
184
+ logEvent(runtimeEnv, 'blocked', toolName, reason);
185
+ process.exit(2);
186
+ }
187
+ }
188
+ }
189
+
190
+ logEvent(runtimeEnv, 'allowed', toolName, '');
191
+ process.exit(0);
192
+ } catch (err) {
193
+ console.error('[Intutic Guardrail] Hook error (blocking for safety):', err);
194
+ logEvent(runtimeEnv, 'blocked', 'unknown', String(err));
195
+ process.exit(2);
196
+ }
197
+ });
198
+ `.trim();
199
+ }
200
+ // ─── Main export ──────────────────────────────────────────────────────────────
201
+ /**
202
+ * Write the Claude Desktop governance hook script and merge hook configuration
203
+ * into the OS-specific claude_desktop_config.json.
204
+ *
205
+ * Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path).
206
+ * Does NOT touch the `mcpServers` section of the config.
207
+ *
208
+ * @param workspaceRoot - Absolute workspace root path.
209
+ * @param proxyUrl - Intutic proxy URL written into the hook script header.
210
+ * @param workspaceId - Workspace ID embedded in every hook event payload.
211
+ */
212
+ export async function writeClaudeDesktopHooks(workspaceRoot, proxyUrl, workspaceId = '') {
213
+ // ── 1. Ensure directories ──────────────────────────────────────────────────
214
+ const hookScriptDir = path.join(workspaceRoot, '.intutic', 'hooks');
215
+ const hookEventsDir = path.join(workspaceRoot, '.intutic', 'events');
216
+ const desktopConfigPath = resolveClaudeDesktopConfigPath();
217
+ const desktopConfigDir = path.dirname(desktopConfigPath);
218
+ await Promise.all([
219
+ fs.mkdir(hookScriptDir, { recursive: true }),
220
+ fs.mkdir(hookEventsDir, { recursive: true }),
221
+ fs.mkdir(desktopConfigDir, { recursive: true }),
222
+ ]);
223
+ // ── 2. Write hook script (atomic) ─────────────────────────────────────────
224
+ const hookScriptPath = path.join(hookScriptDir, 'claude-desktop-check.js');
225
+ const tmpScript = hookScriptPath + '.intutic-tmp';
226
+ await fs.writeFile(tmpScript, buildClaudeDesktopCheckScript(proxyUrl, workspaceRoot, workspaceId), 'utf-8');
227
+ await fs.rename(tmpScript, hookScriptPath);
228
+ log.info({ action: 'claude_desktop_hook_script_written', path: hookScriptPath }, 'Claude Desktop hook script written');
229
+ // ── 3. Merge into claude_desktop_config.json (safe merge, preserve mcpServers) ──
230
+ let existingConfig = {};
231
+ try {
232
+ const raw = await fs.readFile(desktopConfigPath, 'utf-8');
233
+ existingConfig = JSON.parse(raw);
234
+ }
235
+ catch {
236
+ // File doesn't exist yet — start fresh
237
+ }
238
+ const existingHooks = existingConfig.hooks ?? {};
239
+ const existingPreToolUse = existingHooks.PreToolUse ?? [];
240
+ // De-duplicate: remove any existing Intutic entry for Bash then re-add
241
+ const HOOK_MATCHERS = ['Bash', 'Edit', 'Write', 'MultiEdit'];
242
+ const intuticCmd = `node ${hookScriptPath}`;
243
+ const filteredPTU = existingPreToolUse.filter((entry) => {
244
+ if (typeof entry !== 'object' || entry === null)
245
+ return true;
246
+ const e = entry;
247
+ // Remove entries where all inner hooks use the intutic command
248
+ const innerHooks = e.hooks ?? [];
249
+ return !innerHooks.every((h) => typeof h === 'object' && h !== null && h.command === intuticCmd);
250
+ });
251
+ const newPreToolUseEntries = HOOK_MATCHERS.map((matcher) => ({
252
+ matcher,
253
+ hooks: [{ type: 'command', command: intuticCmd }],
254
+ }));
255
+ const mergedConfig = {
256
+ ...existingConfig,
257
+ hooks: {
258
+ ...existingHooks,
259
+ PreToolUse: [...filteredPTU, ...newPreToolUseEntries],
260
+ },
261
+ };
262
+ const tmpConfig = desktopConfigPath + '.intutic-tmp';
263
+ await fs.writeFile(tmpConfig, JSON.stringify(mergedConfig, null, 2) + '\n', 'utf-8');
264
+ await fs.rename(tmpConfig, desktopConfigPath);
265
+ log.info({ action: 'claude_desktop_config_written', path: desktopConfigPath }, 'Merged Intutic hooks into claude_desktop_config.json');
266
+ }
267
+ //# sourceMappingURL=claudeDesktopHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeDesktopHooks.js","sourceRoot":"","sources":["../../src/harness/claudeDesktopHooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * clineHooks.ts — Cline PreToolUse governance hook injection.
3
+ *
4
+ * Writes .cline/hooks/hooks.json and the Intutic pre-tool-check
5
+ * script into the workspace. Cline's hook system accepts JSON on stdin
6
+ * and blocks execution when the script returns {"cancel": true}.
7
+ *
8
+ * NOTE: Hooks are placed in .cline/hooks/ (NOT .clinerules/hooks/) to avoid
9
+ * ENOTDIR conflict — .clinerules is written as a flat file by the CLI adapter.
10
+ *
11
+ * LLD #14 — Phase 3 cross-harness defence
12
+ * HLD §3.14 — Three-Tier Defense Cascade
13
+ *
14
+ * @module
15
+ */
16
+ /**
17
+ * Write the Intutic governance hook into .cline/hooks/.
18
+ *
19
+ * @param workspaceRoot - Absolute workspace root path.
20
+ * @param proxyUrl - Intutic proxy URL for inclusion in block messages.
21
+ */
22
+ export declare function writeClineHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
23
+ //# sourceMappingURL=clineHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clineHooks.d.ts","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAoCH;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CA6Hf"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * clineHooks.ts — Cline PreToolUse governance hook injection.
3
+ *
4
+ * Writes .cline/hooks/hooks.json and the Intutic pre-tool-check
5
+ * script into the workspace. Cline's hook system accepts JSON on stdin
6
+ * and blocks execution when the script returns {"cancel": true}.
7
+ *
8
+ * NOTE: Hooks are placed in .cline/hooks/ (NOT .clinerules/hooks/) to avoid
9
+ * ENOTDIR conflict — .clinerules is written as a flat file by the CLI adapter.
10
+ *
11
+ * LLD #14 — Phase 3 cross-harness defence
12
+ * HLD §3.14 — Three-Tier Defense Cascade
13
+ *
14
+ * @module
15
+ */
16
+ import * as fs from 'node:fs/promises';
17
+ import * as path from 'node:path';
18
+ import { createLogger } from '@intutic/logger';
19
+ import { newIso } from '@intutic/id';
20
+ const log = createLogger('sync-cline-hooks');
21
+ /** Tools Cline can invoke that require governance interception. */
22
+ const GOVERNED_TOOLS = [
23
+ 'execute_command',
24
+ 'write_to_file',
25
+ 'str_replace_based_edit_tool',
26
+ 'replace_in_file',
27
+ 'create_file',
28
+ 'delete_file',
29
+ 'rename_file',
30
+ 'read_file', // watch for exfiltration patterns
31
+ ];
32
+ /**
33
+ * Governance-sensitive paths that must never be agent-modified.
34
+ * Mirrors the protected path list in claudeCodeHooks.ts.
35
+ */
36
+ const PROTECTED_PATH_FRAGMENTS = [
37
+ '.cline/hooks',
38
+ '.intutic/hooks',
39
+ '.intutic/integrity.json',
40
+ '.claude/settings.json',
41
+ '.cursor/hooks.json',
42
+ '.codeium/windsurf/hooks.json',
43
+ '.agents/plugins/intutic-governance',
44
+ '.openhands/hooks.json',
45
+ ];
46
+ /**
47
+ * Write the Intutic governance hook into .cline/hooks/.
48
+ *
49
+ * @param workspaceRoot - Absolute workspace root path.
50
+ * @param proxyUrl - Intutic proxy URL for inclusion in block messages.
51
+ */
52
+ export async function writeClineHooks(workspaceRoot, proxyUrl, workspaceId = '') {
53
+ const hooksDir = path.join(workspaceRoot, '.cline', 'hooks');
54
+ const hooksJsonPath = path.join(hooksDir, 'hooks.json');
55
+ const checkScriptPath = path.join(hooksDir, 'intutic-check.js');
56
+ await fs.mkdir(hooksDir, { recursive: true });
57
+ // ── hooks.json ──────────────────────────────────────────────────────
58
+ // Cline reads this file and runs matched hook scripts before tool execution.
59
+ // The matcher regex is matched against the tool name.
60
+ const hooksConfig = {
61
+ _comment: 'Intutic governance hooks — auto-generated. DO NOT EDIT.',
62
+ _lastSync: newIso(),
63
+ hooks: GOVERNED_TOOLS.map((tool) => ({
64
+ type: 'PreToolUse',
65
+ matcher: `^${tool}$`,
66
+ command: `node ${checkScriptPath}`,
67
+ })),
68
+ };
69
+ const tmpJson = hooksJsonPath + '.intutic-tmp';
70
+ await fs.writeFile(tmpJson, JSON.stringify(hooksConfig, null, 2) + '\n', 'utf-8');
71
+ await fs.rename(tmpJson, hooksJsonPath);
72
+ // ── pre-tool-check script ────────────────────────────────────────────
73
+ // Cline passes tool_name + tool_input as JSON on stdin.
74
+ // Return {"cancel": false} to allow, {"cancel": true, "errorMessage": "..."} to block.
75
+ const checkScript = `#!/usr/bin/env node
76
+ /**
77
+ * Intutic Cline PreToolUse governance gate.
78
+ * Auto-generated by intutic sync-daemon. DO NOT EDIT.
79
+ * Proxy: ${proxyUrl}
80
+ * Generated: ${newIso()}
81
+ */
82
+ const path = require('path');
83
+ const os = require('os');
84
+ const fs = require('fs');
85
+ const crypto = require('crypto');
86
+ const https = require('https');
87
+
88
+ // Runtime credentials
89
+ const _runtimeEnvPath = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
90
+ let _intuticHost = 'https://api.intutic.ai', _intuticKey = '', _intuticWsId = ${JSON.stringify(workspaceId)};
91
+ try {
92
+ fs.readFileSync(_runtimeEnvPath, 'utf-8').split('\\n').forEach(line => {
93
+ const eq = line.indexOf('='); if (eq < 0) return;
94
+ const k = line.slice(0, eq).trim(), v = line.slice(eq + 1).trim();
95
+ if (k === 'INTUTIC_HOST' && v) _intuticHost = v;
96
+ if (k === 'INTUTIC_API_KEY' && v) _intuticKey = v;
97
+ if (k === 'INTUTIC_WORKSPACE_ID' && v) _intuticWsId = v;
98
+ });
99
+ } catch {}
100
+
101
+ const HOOK_EVENTS_LOG = path.join(os.homedir(), '.intutic', 'events', 'cline-hook-events.jsonl');
102
+
103
+ function logEvent(verdict, toolName, reason) {
104
+ try {
105
+ const ts = new Date().toISOString();
106
+ const incidentId = crypto.createHash('sha1').update(ts + toolName + _intuticWsId).digest('hex').slice(0, 16);
107
+ const entry = JSON.stringify({ event: verdict === 'blocked' ? 'tool_blocked' : 'tool_allowed', toolName, reason: reason || '', workspaceId: _intuticWsId, harnessType: 'cline', timestamp: ts, incidentId }) + '\\n';
108
+ try { fs.appendFileSync(HOOK_EVENTS_LOG, entry, { flag: 'a' }); } catch {}
109
+ if (_intuticKey) {
110
+ try {
111
+ const body = JSON.stringify({ events: [JSON.parse(entry)] });
112
+ const urlObj = new URL('/api/v1/hook-events', _intuticHost);
113
+ const mod = urlObj.protocol === 'https:' ? https : require('http');
114
+ const req = mod.request({ hostname: urlObj.hostname, port: urlObj.port || 443, path: urlObj.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'Authorization': 'Bearer ' + _intuticKey } });
115
+ req.on('error', () => {});
116
+ req.write(body); req.end();
117
+ } catch {}
118
+ }
119
+ } catch {}
120
+ }
121
+
122
+ const PROTECTED_PATHS = ${JSON.stringify(PROTECTED_PATH_FRAGMENTS)};
123
+
124
+ let raw = '';
125
+ process.stdin.on('data', (c) => { raw += c; });
126
+ process.stdin.on('end', () => {
127
+ try {
128
+ const ctx = JSON.parse(raw);
129
+ const tool = (ctx.tool_name || '').toLowerCase();
130
+ const input = ctx.tool_input || {};
131
+ const inputStr = JSON.stringify(input);
132
+
133
+ // Protected-path guard: block any write/edit/delete targeting governance files
134
+ const targetPath = input.path || input.file_path || input.target || input.source || '';
135
+ if (['write_to_file','str_replace_based_edit_tool','replace_in_file','create_file','delete_file','rename_file'].includes(tool)) {
136
+ for (const p of PROTECTED_PATHS) {
137
+ if (String(targetPath).includes(p)) {
138
+ logEvent('blocked', tool, 'Blocked: attempt to modify governance-protected path "' + targetPath + '"');
139
+ process.stdout.write(JSON.stringify({
140
+ cancel: true,
141
+ errorMessage: '[Intutic Governance] Blocked: attempt to modify governance-protected path "' + targetPath + '". Update policy via the Intutic control plane.',
142
+ }));
143
+ process.exit(0);
144
+ }
145
+ }
146
+ }
147
+
148
+ // Allow all other tool calls
149
+ logEvent('allowed', tool || 'unknown', '');
150
+ process.stdout.write(JSON.stringify({ cancel: false }));
151
+ process.exit(0);
152
+ } catch (err) {
153
+ // Fail CLOSED — hook parse error blocks the tool call
154
+ logEvent('blocked', 'unknown', String(err));
155
+ process.stdout.write(JSON.stringify({
156
+ cancel: true,
157
+ errorMessage: '[Intutic Governance] Hook error (fail-closed): ' + String(err),
158
+ }));
159
+ process.exit(0);
160
+ }
161
+ });
162
+ `;
163
+ const tmpScript = checkScriptPath + '.intutic-tmp';
164
+ await fs.writeFile(tmpScript, checkScript, 'utf-8');
165
+ await fs.rename(tmpScript, checkScriptPath);
166
+ await fs.chmod(checkScriptPath, 0o755);
167
+ log.info({ action: 'cline_hooks_written', hooksDir, toolCount: GOVERNED_TOOLS.length }, 'Cline governance hooks written');
168
+ }
169
+ //# sourceMappingURL=clineHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clineHooks.js","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,MAAM,GAAG,GAAG,YAAY,CAAC,kBAAkB,CAAC,CAAA;AAE5C,mEAAmE;AACnE,MAAM,cAAc,GAAG;IACrB,iBAAiB;IACjB,eAAe;IACf,6BAA6B;IAC7B,iBAAiB;IACjB,aAAa;IACb,aAAa;IACb,aAAa;IACb,WAAW,EAAG,kCAAkC;CACjD,CAAA;AAED;;;GAGG;AACH,MAAM,wBAAwB,GAAG;IAC/B,cAAc;IACd,gBAAgB;IAChB,yBAAyB;IACzB,uBAAuB;IACvB,oBAAoB;IACpB,8BAA8B;IAC9B,oCAAoC;IACpC,uBAAuB;CACxB,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,aAAqB,EACrB,QAAgB,EAChB,WAAW,GAAG,EAAE;IAEhB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC5D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;IACvD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAA;IAE/D,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE7C,uEAAuE;IACvE,6EAA6E;IAC7E,sDAAsD;IACtD,MAAM,WAAW,GAAG;QAClB,QAAQ,EAAE,yDAAyD;QACnE,SAAS,EAAE,MAAM,EAAE;QACnB,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,IAAI,IAAI,GAAG;YACpB,OAAO,EAAE,QAAQ,eAAe,EAAE;SACnC,CAAC,CAAC;KACJ,CAAA;IAED,MAAM,OAAO,GAAG,aAAa,GAAG,cAAc,CAAA;IAC9C,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAA;IACjF,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAA;IAEvC,wEAAwE;IACxE,wDAAwD;IACxD,uFAAuF;IACvF,MAAM,WAAW,GAAG;;;;YAIV,QAAQ;gBACJ,MAAM,EAAE;;;;;;;;;;gFAUwD,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAgCjF,IAAI,CAAC,SAAS,CAAC,wBAAwB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCjE,CAAA;IAEC,MAAM,SAAS,GAAG,eAAe,GAAG,cAAc,CAAA;IAClD,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;IACnD,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;IAC3C,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAA;IAEtC,GAAG,CAAC,IAAI,CACN,EAAE,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,CAAC,MAAM,EAAE,EAC7E,gCAAgC,CACjC,CAAA;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * continueHooks.ts — Continue.dev governance via proxy routing.
3
+ *
4
+ * Continue.dev has no native hook system. Governance is applied by routing
5
+ * all model API calls through the Intutic proxy: each model's `apiBase`
6
+ * field is set to `proxyUrl` in `~/.continue/config.json`.
7
+ *
8
+ * JSONC handling: ~/.continue/config.json allows `// comments`. We strip
9
+ * single-line comments with a regex before parsing, then write back as
10
+ * standard JSON with a managed-file header comment.
11
+ *
12
+ * LLD #14 — Phase 3 cross-harness defence (Gap 3, WS-B)
13
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
14
+ *
15
+ * @module
16
+ */
17
+ /**
18
+ * Configure Continue.dev to route all model API calls through the Intutic proxy.
19
+ *
20
+ * 1. Reads `~/.continue/config.json` (JSONC — strips // comments before parsing).
21
+ * 2. Sets `apiBase = proxyUrl` on each model that doesn't already use it.
22
+ * 3. Adds a `customInstructions` governance notice.
23
+ * 4. Writes the merged result back atomically.
24
+ * 5. Writes `.intutic/env/continue.env` with the proxy URL.
25
+ *
26
+ * Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path).
27
+ *
28
+ * @param workspaceRoot - Absolute workspace root path.
29
+ * @param proxyUrl - Intutic proxy URL to set as model apiBase.
30
+ * @param workspaceId - Workspace ID stored in the env snippet.
31
+ */
32
+ export declare function writeContinueHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
33
+ //# sourceMappingURL=continueHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"continueHooks.d.ts","sourceRoot":"","sources":["../../src/harness/continueHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAmCH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAqFf"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * continueHooks.ts — Continue.dev governance via proxy routing.
3
+ *
4
+ * Continue.dev has no native hook system. Governance is applied by routing
5
+ * all model API calls through the Intutic proxy: each model's `apiBase`
6
+ * field is set to `proxyUrl` in `~/.continue/config.json`.
7
+ *
8
+ * JSONC handling: ~/.continue/config.json allows `// comments`. We strip
9
+ * single-line comments with a regex before parsing, then write back as
10
+ * standard JSON with a managed-file header comment.
11
+ *
12
+ * LLD #14 — Phase 3 cross-harness defence (Gap 3, WS-B)
13
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
14
+ *
15
+ * @module
16
+ */
17
+ import * as fs from 'node:fs/promises';
18
+ import * as path from 'node:path';
19
+ import * as os from 'node:os';
20
+ import { createLogger } from '@intutic/logger';
21
+ import { newIso } from '@intutic/id';
22
+ const log = createLogger('sync-continue-hooks');
23
+ /** Default path to the Continue.dev global config file. */
24
+ const CONTINUE_CONFIG_PATH = path.join(os.homedir(), '.continue', 'config.json');
25
+ /** Governance notice injected into customInstructions. */
26
+ const GOVERNANCE_NOTICE = [
27
+ 'This workspace is governed by Intutic SOP policies.',
28
+ 'All LLM API requests are routed through the Intutic governance proxy.',
29
+ 'Do not remove or override the apiBase field in this configuration.',
30
+ ].join(' ');
31
+ // ─── JSONC comment stripper ───────────────────────────────────────────────────
32
+ /**
33
+ * Strip single-line `// ...` comments from a JSONC string so it can be
34
+ * parsed by JSON.parse. Block comments (`/* ... *\/`) are not stripped
35
+ * because they are uncommon in Continue config files.
36
+ */
37
+ function stripJsoncComments(raw) {
38
+ // Remove // comments that are not inside strings.
39
+ // Simple approach: replace // up to end-of-line (acceptable for Continue config).
40
+ return raw.replace(/\/\/[^\n]*/g, '');
41
+ }
42
+ // ─── Main export ──────────────────────────────────────────────────────────────
43
+ /**
44
+ * Configure Continue.dev to route all model API calls through the Intutic proxy.
45
+ *
46
+ * 1. Reads `~/.continue/config.json` (JSONC — strips // comments before parsing).
47
+ * 2. Sets `apiBase = proxyUrl` on each model that doesn't already use it.
48
+ * 3. Adds a `customInstructions` governance notice.
49
+ * 4. Writes the merged result back atomically.
50
+ * 5. Writes `.intutic/env/continue.env` with the proxy URL.
51
+ *
52
+ * Safe to call repeatedly — uses atomic rename (`.intutic-tmp` → final path).
53
+ *
54
+ * @param workspaceRoot - Absolute workspace root path.
55
+ * @param proxyUrl - Intutic proxy URL to set as model apiBase.
56
+ * @param workspaceId - Workspace ID stored in the env snippet.
57
+ */
58
+ export async function writeContinueHooks(workspaceRoot, proxyUrl, workspaceId = '') {
59
+ // ── 1. Ensure directories ──────────────────────────────────────────────────
60
+ const continueDir = path.join(os.homedir(), '.continue');
61
+ const envDir = path.join(workspaceRoot, '.intutic', 'env');
62
+ await Promise.all([
63
+ fs.mkdir(continueDir, { recursive: true }),
64
+ fs.mkdir(envDir, { recursive: true }),
65
+ ]);
66
+ // ── 2. Read and parse ~/.continue/config.json ─────────────────────────────
67
+ let configObj = {};
68
+ try {
69
+ const raw = await fs.readFile(CONTINUE_CONFIG_PATH, 'utf-8');
70
+ const stripped = stripJsoncComments(raw);
71
+ configObj = JSON.parse(stripped);
72
+ }
73
+ catch {
74
+ // File doesn't exist or is malformed — start with empty config
75
+ log.warn({ action: 'continue_config_read_failed', path: CONTINUE_CONFIG_PATH }, 'Could not read ~/.continue/config.json — creating fresh');
76
+ }
77
+ // ── 3. Update model apiBase fields ────────────────────────────────────────
78
+ const models = Array.isArray(configObj.models) ? configObj.models : [];
79
+ let modifiedCount = 0;
80
+ const updatedModels = models.map((model) => {
81
+ if (typeof model !== 'object' || model === null)
82
+ return model;
83
+ const m = model;
84
+ if (m.apiBase === proxyUrl)
85
+ return m; // Already set — no-op
86
+ modifiedCount++;
87
+ return { ...m, apiBase: proxyUrl };
88
+ });
89
+ // ── 4. Build merged config ────────────────────────────────────────────────
90
+ const mergedConfig = {
91
+ ...configObj,
92
+ // Re-attach updated models array (or keep original if empty)
93
+ ...(models.length > 0 ? { models: updatedModels } : {}),
94
+ // Add/overwrite customInstructions with governance notice
95
+ customInstructions: GOVERNANCE_NOTICE,
96
+ // Metadata fields
97
+ _intutic_managed: true,
98
+ _intutic_last_sync: newIso(),
99
+ _intutic_workspace_id: workspaceId,
100
+ };
101
+ // ── 5. Write back atomically ──────────────────────────────────────────────
102
+ // Prepend a managed-file warning as a comment (will be lost on next read —
103
+ // that's acceptable; we re-add it on each sync).
104
+ const managedHeader = `// AUTO-MANAGED by Intutic sync-daemon. Last sync: ${newIso()}\n// Do not edit apiBase fields — they are governance proxy routes.\n`;
105
+ const serialized = managedHeader + JSON.stringify(mergedConfig, null, 2) + '\n';
106
+ const tmpConfig = CONTINUE_CONFIG_PATH + '.intutic-tmp';
107
+ await fs.writeFile(tmpConfig, serialized, 'utf-8');
108
+ await fs.rename(tmpConfig, CONTINUE_CONFIG_PATH);
109
+ log.info({ action: 'continue_config_written', path: CONTINUE_CONFIG_PATH, modifiedModels: modifiedCount }, 'Continue.dev config patched with Intutic proxy apiBase');
110
+ // ── 6. Write .intutic/env/continue.env ────────────────────────────────────
111
+ const envContent = [
112
+ `# Intutic Continue.dev governance env — auto-generated ${newIso()}`,
113
+ `# This file is managed by the Intutic sync-daemon. DO NOT EDIT.`,
114
+ `CONTINUE_PROXY_URL=${proxyUrl}`,
115
+ workspaceId ? `INTUTIC_WORKSPACE_ID=${workspaceId}` : '',
116
+ `# All Continue.dev model requests are routed through CONTINUE_PROXY_URL.`,
117
+ ].filter((l) => l !== '').join('\n') + '\n';
118
+ const envFilePath = path.join(envDir, 'continue.env');
119
+ const tmpEnv = envFilePath + '.intutic-tmp';
120
+ await fs.writeFile(tmpEnv, envContent, 'utf-8');
121
+ await fs.rename(tmpEnv, envFilePath);
122
+ log.info({ action: 'continue_env_written', path: envFilePath }, 'Continue.dev env snippet written');
123
+ }
124
+ //# sourceMappingURL=continueHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"continueHooks.js","sourceRoot":"","sources":["../../src/harness/continueHooks.ts"],"names":[],"mappings":"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"}