@intutic/sync-daemon 2.0.0 → 2.2.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 (206) hide show
  1. package/dist/agentReporter.d.ts +5 -12
  2. package/dist/agentReporter.d.ts.map +1 -1
  3. package/dist/agentReporter.js +25 -4
  4. package/dist/agentReporter.js.map +1 -1
  5. package/dist/configReader.d.ts +44 -22
  6. package/dist/configReader.d.ts.map +1 -1
  7. package/dist/configReader.js +70 -44
  8. package/dist/configReader.js.map +1 -1
  9. package/dist/configWriter.d.ts +29 -41
  10. package/dist/configWriter.d.ts.map +1 -1
  11. package/dist/configWriter.js +28 -625
  12. package/dist/configWriter.js.map +1 -1
  13. package/dist/disconnect/harnesses.d.ts +31 -0
  14. package/dist/disconnect/harnesses.d.ts.map +1 -0
  15. package/dist/disconnect/harnesses.js +728 -0
  16. package/dist/disconnect/harnesses.js.map +1 -0
  17. package/dist/disconnect/index.d.ts +25 -0
  18. package/dist/disconnect/index.d.ts.map +1 -0
  19. package/dist/disconnect/index.js +82 -0
  20. package/dist/disconnect/index.js.map +1 -0
  21. package/dist/disconnect/mcp.d.ts +60 -0
  22. package/dist/disconnect/mcp.d.ts.map +1 -0
  23. package/dist/disconnect/mcp.js +283 -0
  24. package/dist/disconnect/mcp.js.map +1 -0
  25. package/dist/disconnect/originals.d.ts +88 -0
  26. package/dist/disconnect/originals.d.ts.map +1 -0
  27. package/dist/disconnect/originals.js +257 -0
  28. package/dist/disconnect/originals.js.map +1 -0
  29. package/dist/disconnect/plan.d.ts +175 -0
  30. package/dist/disconnect/plan.d.ts.map +1 -0
  31. package/dist/disconnect/plan.js +580 -0
  32. package/dist/disconnect/plan.js.map +1 -0
  33. package/dist/disconnect/recognise.d.ts +43 -0
  34. package/dist/disconnect/recognise.d.ts.map +1 -0
  35. package/dist/disconnect/recognise.js +98 -0
  36. package/dist/disconnect/recognise.js.map +1 -0
  37. package/dist/disconnect/tomlLines.d.ts +39 -0
  38. package/dist/disconnect/tomlLines.d.ts.map +1 -0
  39. package/dist/disconnect/tomlLines.js +105 -0
  40. package/dist/disconnect/tomlLines.js.map +1 -0
  41. package/dist/harness/aiderConfigMerger.d.ts +42 -5
  42. package/dist/harness/aiderConfigMerger.d.ts.map +1 -1
  43. package/dist/harness/aiderConfigMerger.js +113 -86
  44. package/dist/harness/aiderConfigMerger.js.map +1 -1
  45. package/dist/harness/antigravityHooks.d.ts +13 -8
  46. package/dist/harness/antigravityHooks.d.ts.map +1 -1
  47. package/dist/harness/antigravityHooks.js +53 -39
  48. package/dist/harness/antigravityHooks.js.map +1 -1
  49. package/dist/harness/claudeCodeHooks.d.ts +1 -2
  50. package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
  51. package/dist/harness/claudeCodeHooks.js +61 -34
  52. package/dist/harness/claudeCodeHooks.js.map +1 -1
  53. package/dist/harness/claudeDesktopHooks.d.ts +3 -0
  54. package/dist/harness/claudeDesktopHooks.d.ts.map +1 -1
  55. package/dist/harness/claudeDesktopHooks.js +3 -1
  56. package/dist/harness/claudeDesktopHooks.js.map +1 -1
  57. package/dist/harness/claudeProjectApproval.d.ts +53 -0
  58. package/dist/harness/claudeProjectApproval.d.ts.map +1 -0
  59. package/dist/harness/claudeProjectApproval.js +159 -0
  60. package/dist/harness/claudeProjectApproval.js.map +1 -0
  61. package/dist/harness/clineHooks.d.ts +39 -9
  62. package/dist/harness/clineHooks.d.ts.map +1 -1
  63. package/dist/harness/clineHooks.js +142 -60
  64. package/dist/harness/clineHooks.js.map +1 -1
  65. package/dist/harness/codexConfigMerger.d.ts +38 -0
  66. package/dist/harness/codexConfigMerger.d.ts.map +1 -0
  67. package/dist/harness/codexConfigMerger.js +105 -0
  68. package/dist/harness/codexConfigMerger.js.map +1 -0
  69. package/dist/harness/codexHooks.d.ts +4 -2
  70. package/dist/harness/codexHooks.d.ts.map +1 -1
  71. package/dist/harness/codexHooks.js +14 -11
  72. package/dist/harness/codexHooks.js.map +1 -1
  73. package/dist/harness/continueConfigMerger.d.ts +46 -0
  74. package/dist/harness/continueConfigMerger.d.ts.map +1 -0
  75. package/dist/harness/continueConfigMerger.js +94 -0
  76. package/dist/harness/continueConfigMerger.js.map +1 -0
  77. package/dist/harness/continueHooks.d.ts +15 -25
  78. package/dist/harness/continueHooks.d.ts.map +1 -1
  79. package/dist/harness/continueHooks.js +25 -119
  80. package/dist/harness/continueHooks.js.map +1 -1
  81. package/dist/harness/cursorHooks.d.ts +33 -16
  82. package/dist/harness/cursorHooks.d.ts.map +1 -1
  83. package/dist/harness/cursorHooks.js +69 -25
  84. package/dist/harness/cursorHooks.js.map +1 -1
  85. package/dist/harness/dshHooks.d.ts +59 -52
  86. package/dist/harness/dshHooks.d.ts.map +1 -1
  87. package/dist/harness/dshHooks.js +152 -147
  88. package/dist/harness/dshHooks.js.map +1 -1
  89. package/dist/harness/gateBody.d.ts +11 -2
  90. package/dist/harness/gateBody.d.ts.map +1 -1
  91. package/dist/harness/gateBody.js +33 -6
  92. package/dist/harness/gateBody.js.map +1 -1
  93. package/dist/harness/gateKind.d.ts.map +1 -1
  94. package/dist/harness/gateKind.js +1 -0
  95. package/dist/harness/gateKind.js.map +1 -1
  96. package/dist/harness/githubCopilotHooks.d.ts.map +1 -1
  97. package/dist/harness/githubCopilotHooks.js +5 -10
  98. package/dist/harness/githubCopilotHooks.js.map +1 -1
  99. package/dist/harness/gooseHooks.d.ts +15 -0
  100. package/dist/harness/gooseHooks.d.ts.map +1 -1
  101. package/dist/harness/gooseHooks.js +55 -43
  102. package/dist/harness/gooseHooks.js.map +1 -1
  103. package/dist/harness/grokHooks.d.ts.map +1 -1
  104. package/dist/harness/grokHooks.js +14 -12
  105. package/dist/harness/grokHooks.js.map +1 -1
  106. package/dist/harness/hermesHooks.d.ts +9 -0
  107. package/dist/harness/hermesHooks.d.ts.map +1 -1
  108. package/dist/harness/hermesHooks.js +39 -33
  109. package/dist/harness/hermesHooks.js.map +1 -1
  110. package/dist/harness/holdRedaction.d.ts +4 -1
  111. package/dist/harness/holdRedaction.d.ts.map +1 -1
  112. package/dist/harness/holdRedaction.js +32 -8
  113. package/dist/harness/holdRedaction.js.map +1 -1
  114. package/dist/harness/jetbrainsXmlConfig.d.ts +20 -0
  115. package/dist/harness/jetbrainsXmlConfig.d.ts.map +1 -1
  116. package/dist/harness/jetbrainsXmlConfig.js +39 -18
  117. package/dist/harness/jetbrainsXmlConfig.js.map +1 -1
  118. package/dist/harness/jsonMergeTarget.d.ts +23 -0
  119. package/dist/harness/jsonMergeTarget.d.ts.map +1 -0
  120. package/dist/harness/jsonMergeTarget.js +50 -0
  121. package/dist/harness/jsonMergeTarget.js.map +1 -0
  122. package/dist/harness/mcpAutoWrite.d.ts +64 -20
  123. package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
  124. package/dist/harness/mcpAutoWrite.js +429 -85
  125. package/dist/harness/mcpAutoWrite.js.map +1 -1
  126. package/dist/harness/museHooks.d.ts.map +1 -1
  127. package/dist/harness/museHooks.js +6 -2
  128. package/dist/harness/museHooks.js.map +1 -1
  129. package/dist/harness/n8nHooks.d.ts.map +1 -1
  130. package/dist/harness/n8nHooks.js +6 -7
  131. package/dist/harness/n8nHooks.js.map +1 -1
  132. package/dist/harness/openCodeHooks.d.ts +37 -6
  133. package/dist/harness/openCodeHooks.d.ts.map +1 -1
  134. package/dist/harness/openCodeHooks.js +103 -13
  135. package/dist/harness/openCodeHooks.js.map +1 -1
  136. package/dist/harness/openWebuiHooks.d.ts.map +1 -1
  137. package/dist/harness/openWebuiHooks.js +10 -5
  138. package/dist/harness/openWebuiHooks.js.map +1 -1
  139. package/dist/harness/openclawHooks.d.ts +5 -0
  140. package/dist/harness/openclawHooks.d.ts.map +1 -1
  141. package/dist/harness/openclawHooks.js +3 -1
  142. package/dist/harness/openclawHooks.js.map +1 -1
  143. package/dist/harness/openhandsHooks.d.ts +26 -1
  144. package/dist/harness/openhandsHooks.d.ts.map +1 -1
  145. package/dist/harness/openhandsHooks.js +160 -40
  146. package/dist/harness/openhandsHooks.js.map +1 -1
  147. package/dist/harness/piHooks.d.ts +17 -0
  148. package/dist/harness/piHooks.d.ts.map +1 -1
  149. package/dist/harness/piHooks.js +31 -29
  150. package/dist/harness/piHooks.js.map +1 -1
  151. package/dist/harness/protectedPaths.d.ts +64 -6
  152. package/dist/harness/protectedPaths.d.ts.map +1 -1
  153. package/dist/harness/protectedPaths.js +161 -11
  154. package/dist/harness/protectedPaths.js.map +1 -1
  155. package/dist/harness/rooCodeHooks.d.ts.map +1 -1
  156. package/dist/harness/rooCodeHooks.js +5 -10
  157. package/dist/harness/rooCodeHooks.js.map +1 -1
  158. package/dist/harness/windsurfHooks.d.ts +21 -3
  159. package/dist/harness/windsurfHooks.d.ts.map +1 -1
  160. package/dist/harness/windsurfHooks.js +96 -44
  161. package/dist/harness/windsurfHooks.js.map +1 -1
  162. package/dist/harness/windsurfJetBrainsProxy.d.ts +10 -7
  163. package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -1
  164. package/dist/harness/windsurfJetBrainsProxy.js +22 -7
  165. package/dist/harness/windsurfJetBrainsProxy.js.map +1 -1
  166. package/dist/index.d.ts +12 -12
  167. package/dist/index.d.ts.map +1 -1
  168. package/dist/index.js +11 -10
  169. package/dist/index.js.map +1 -1
  170. package/dist/lib/decisionsDigest.d.ts +1 -1
  171. package/dist/lib/decisionsDigest.js +1 -1
  172. package/dist/lib/policySnapshot.d.ts +37 -0
  173. package/dist/lib/policySnapshot.d.ts.map +1 -1
  174. package/dist/lib/policySnapshot.js +170 -8
  175. package/dist/lib/policySnapshot.js.map +1 -1
  176. package/dist/sessionReporter.d.ts +2 -2
  177. package/dist/sessionReporter.js +2 -2
  178. package/dist/syncCycle.d.ts +142 -0
  179. package/dist/syncCycle.d.ts.map +1 -0
  180. package/dist/syncCycle.js +315 -0
  181. package/dist/syncCycle.js.map +1 -0
  182. package/dist/trajectoryMonitor.d.ts +1 -1
  183. package/dist/trajectoryMonitor.d.ts.map +1 -1
  184. package/dist/trajectoryMonitor.js +1 -1
  185. package/dist/trajectoryMonitor.js.map +1 -1
  186. package/dist/watcher/settingsGuard.d.ts +4 -1
  187. package/dist/watcher/settingsGuard.d.ts.map +1 -1
  188. package/dist/watcher/settingsGuard.js +42 -18
  189. package/dist/watcher/settingsGuard.js.map +1 -1
  190. package/package.json +6 -6
  191. package/dist/hashReporter.d.ts +0 -33
  192. package/dist/hashReporter.d.ts.map +0 -1
  193. package/dist/hashReporter.js +0 -69
  194. package/dist/hashReporter.js.map +0 -1
  195. package/dist/integrityStore.d.ts +0 -35
  196. package/dist/integrityStore.d.ts.map +0 -1
  197. package/dist/integrityStore.js +0 -71
  198. package/dist/integrityStore.js.map +0 -1
  199. package/dist/lib/gitWorktrees.d.ts +0 -140
  200. package/dist/lib/gitWorktrees.d.ts.map +0 -1
  201. package/dist/lib/gitWorktrees.js +0 -191
  202. package/dist/lib/gitWorktrees.js.map +0 -1
  203. package/dist/syncLoop.d.ts +0 -172
  204. package/dist/syncLoop.d.ts.map +0 -1
  205. package/dist/syncLoop.js +0 -878
  206. package/dist/syncLoop.js.map +0 -1
@@ -0,0 +1,159 @@
1
+ /**
2
+ * claudeProjectApproval.ts — which of a repo's `.mcp.json` servers Claude Code
3
+ * would start, decided the way Claude Code decides it.
4
+ *
5
+ * The sync daemon governs project-scope servers through wrapped local-scope
6
+ * copies (mcpAutoWrite.ts's `shadowProjectServers`), and a local-scope server
7
+ * starts without Claude Code's project-server approval. So a copy may exist
8
+ * only for a server Claude Code would itself start; anything looser would let
9
+ * a cloned repository approve its own server and have us wrap and start it.
10
+ *
11
+ * Claude Code's rules (https://code.claude.com/docs/en/mcp, "Project server
12
+ * approvals and workspace trust"):
13
+ *
14
+ * - Approvals (`enableAllProjectMcpServers`, `enabledMcpjsonServers`) from
15
+ * the user's `~/.claude/settings.json`, managed settings and the user's own
16
+ * record in `~/.claude.json` always apply.
17
+ * - Approvals from the project's `.claude/settings.json` and
18
+ * `.claude/settings.local.json` apply only once the folder is trusted.
19
+ * Trust is `projects[<repository root>].hasTrustDialogAccepted` in
20
+ * `~/.claude.json` — keyed on the git root, or the main checkout's root for
21
+ * a worktree, or the folder itself outside a repository. Anything we cannot
22
+ * confirm counts as untrusted.
23
+ * - `disabledMcpjsonServers` in any of those files rejects the server,
24
+ * trusted folder or not.
25
+ *
26
+ * Managed policy can also arrive through MDM (a macOS configuration profile)
27
+ * or the Windows registry, neither of which this module reads. Where one may
28
+ * be present, no project server is approved here: missing an organization's
29
+ * disable is the one mistake this check must not make.
30
+ *
31
+ * @module
32
+ */
33
+ import * as node_fs from 'node:fs/promises';
34
+ import * as node_path from 'node:path';
35
+ import * as node_os from 'node:os';
36
+ import { existsSync, realpathSync } from 'node:fs';
37
+ import { execFileSync } from 'node:child_process';
38
+ function defaultManagedDir(platform) {
39
+ if (platform === 'darwin')
40
+ return '/Library/Application Support/ClaudeCode';
41
+ if (platform === 'win32')
42
+ return 'C:\\Program Files\\ClaudeCode';
43
+ return '/etc/claude-code';
44
+ }
45
+ function defaultMdmProfiles(platform) {
46
+ if (platform !== 'darwin')
47
+ return [];
48
+ const user = (() => {
49
+ try {
50
+ return node_os.userInfo().username;
51
+ }
52
+ catch {
53
+ return '';
54
+ }
55
+ })();
56
+ return [
57
+ '/Library/Managed Preferences/com.anthropic.claudecode.plist',
58
+ ...(user ? [`/Library/Managed Preferences/${user}/com.anthropic.claudecode.plist`] : []),
59
+ ];
60
+ }
61
+ async function readObject(file) {
62
+ try {
63
+ const parsed = JSON.parse(await node_fs.readFile(file, 'utf-8'));
64
+ return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ /** The managed-settings documents: `managed-settings.json` and the `managed-settings.d/` drop-ins. */
71
+ async function readManaged(dir) {
72
+ const out = [];
73
+ const main = await readObject(node_path.join(dir, 'managed-settings.json'));
74
+ if (main)
75
+ out.push(main);
76
+ try {
77
+ const dropIns = (await node_fs.readdir(node_path.join(dir, 'managed-settings.d'))).filter((f) => f.endsWith('.json')).sort();
78
+ for (const f of dropIns) {
79
+ const doc = await readObject(node_path.join(dir, 'managed-settings.d', f));
80
+ if (doc)
81
+ out.push(doc);
82
+ }
83
+ }
84
+ catch {
85
+ // No drop-in directory.
86
+ }
87
+ return out;
88
+ }
89
+ /**
90
+ * The path(s) Claude Code may key this folder's trust on: the repository root
91
+ * (the main checkout's root for a worktree), or the folder itself outside a
92
+ * repository — each as given and as its real path, since `projects` keys are
93
+ * whatever path Claude Code was started with.
94
+ */
95
+ function trustKeys(workspaceRoot) {
96
+ const keys = new Set([workspaceRoot]);
97
+ try {
98
+ const common = execFileSync('git', ['-C', workspaceRoot, 'rev-parse', '--path-format=absolute', '--git-common-dir'], {
99
+ encoding: 'utf-8',
100
+ stdio: ['ignore', 'pipe', 'ignore'],
101
+ timeout: 2000,
102
+ }).trim();
103
+ if (common)
104
+ keys.add(node_path.dirname(common));
105
+ }
106
+ catch {
107
+ // Not a repository, or no git: the folder itself is the key.
108
+ }
109
+ for (const k of [...keys]) {
110
+ try {
111
+ keys.add(realpathSync(k));
112
+ }
113
+ catch {
114
+ // A key that does not resolve stays as given.
115
+ }
116
+ }
117
+ return [...keys];
118
+ }
119
+ /**
120
+ * Decides which project servers Claude Code would start for `workspaceRoot`,
121
+ * given the parsed `~/.claude.json` (`claudeState`).
122
+ */
123
+ export async function projectServerApproval(workspaceRoot, claudeState, env = {}) {
124
+ const platform = env.platform ?? process.platform;
125
+ const mdmProfiles = env.mdmProfiles ?? defaultMdmProfiles(platform);
126
+ if (platform === 'win32' || mdmProfiles.some((p) => existsSync(p))) {
127
+ return {
128
+ approved: () => false,
129
+ blockedReason: 'Claude Code managed policy may be deployed through MDM or the registry, which Intutic cannot read, so no project server is copied',
130
+ };
131
+ }
132
+ const projectEntry = (key) => {
133
+ const entry = claudeState.projects?.[key];
134
+ return entry !== null && typeof entry === 'object' && !Array.isArray(entry) ? entry : {};
135
+ };
136
+ const keys = trustKeys(workspaceRoot);
137
+ const trusted = keys.some((k) => projectEntry(k).hasTrustDialogAccepted === true);
138
+ const userSources = [
139
+ projectEntry(workspaceRoot),
140
+ ...(await readManaged(env.managedDir ?? defaultManagedDir(platform))),
141
+ ];
142
+ const userSettings = await readObject(node_path.join(node_os.homedir(), '.claude', 'settings.json'));
143
+ if (userSettings)
144
+ userSources.push(userSettings);
145
+ const projectSources = [];
146
+ for (const file of ['settings.json', 'settings.local.json']) {
147
+ const doc = await readObject(node_path.join(workspaceRoot, '.claude', file));
148
+ if (doc)
149
+ projectSources.push(doc);
150
+ }
151
+ const names = (v) => (Array.isArray(v) ? v.filter((n) => typeof n === 'string') : []);
152
+ const approving = trusted ? [...userSources, ...projectSources] : userSources;
153
+ const all = approving.some((src) => src.enableAllProjectMcpServers === true);
154
+ const enabled = new Set(approving.flatMap((src) => names(src.enabledMcpjsonServers)));
155
+ // A disable counts from every file, the committed ones included.
156
+ const disabled = new Set([...userSources, ...projectSources].flatMap((src) => names(src.disabledMcpjsonServers)));
157
+ return { approved: (name) => !disabled.has(name) && (all || enabled.has(name)) };
158
+ }
159
+ //# sourceMappingURL=claudeProjectApproval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claudeProjectApproval.js","sourceRoot":"","sources":["../../src/harness/claudeProjectApproval.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,31 @@
1
1
  /**
2
- * clineHooks.ts — Cline PreToolUse governance hook injection.
2
+ * clineHooks.ts — Cline PreToolUse governance hook.
3
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}.
4
+ * Cline runs file hooks: an executable named exactly `PreToolUse` (no
5
+ * extension on macOS/Linux) in a hooks directory, fed the pending tool call as
6
+ * JSON on stdin. It refuses the call when the hook prints `{"cancel": true}`
7
+ * on stdout; the exit code does not decide anything. Confirmed against
8
+ * Cline's source (apps/vscode/src/core/hooks, sdk/packages/core/src/hooks):
7
9
  *
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
+ * - The VS Code extension reads `<workspace>/.clinerules/hooks/PreToolUse` and
11
+ * runs it only when "Enable Hooks" is on in Cline's feature settings and the
12
+ * file is executable. Its payload is
13
+ * `{hookName, preToolUse: {toolName, parameters}}`, each parameter value
14
+ * JSON-encoded as a string.
15
+ * - The Cline CLI/SDK searches the same `.clinerules/hooks` directory (plus
16
+ * `.cline/hooks` and two user-level directories). Its payload is
17
+ * `{hookName, tool_call: {id, name, input}}`.
18
+ *
19
+ * So one file, `.clinerules/hooks/PreToolUse`, covers both, and the gate
20
+ * accepts both payload shapes. There is no `hooks.json` and no matcher: the
21
+ * hook runs for every tool call, MCP calls (`use_mcp_tool`) included, and the
22
+ * gate decides from the arguments.
23
+ *
24
+ * `.clinerules` must therefore be a directory. Earlier versions wrote it as a
25
+ * flat rules file (and registered the gate in a `.cline/hooks/hooks.json`
26
+ * Cline never reads); `ensureClinerulesDirectory` converts the flat file this
27
+ * product wrote, and leaves a flat `.clinerules` the user wrote alone — in
28
+ * that case no gate can be installed and a warning says why.
10
29
  *
11
30
  * LLD #14 — Phase 3 cross-harness defence
12
31
  * HLD §3.14 — Three-Tier Defense Cascade
@@ -14,10 +33,21 @@
14
33
  * @module
15
34
  */
16
35
  /**
17
- * Write the Intutic governance hook into .cline/hooks/.
36
+ * Make `<workspace>/.clinerules` a directory, converting the flat rules file
37
+ * earlier versions of this product wrote there.
38
+ *
39
+ * @returns `true` when `.clinerules` is (now) a directory; `false` when it is a
40
+ * file the user wrote, which is left alone (a warning is logged).
41
+ */
42
+ export declare function ensureClinerulesDirectory(workspaceRoot: string): Promise<boolean>;
43
+ /**
44
+ * Write the Intutic gate to `<workspace>/.clinerules/hooks/PreToolUse`.
18
45
  *
19
46
  * @param workspaceRoot - Absolute workspace root path.
20
- * @param proxyUrl - Intutic proxy URL for inclusion in block messages.
47
+ * @param proxyUrl - Intutic proxy URL for inclusion in the gate header.
48
+ * @param workspaceId - Workspace ID embedded in every hook event payload.
49
+ * @returns the gate path, or `null` when it could not be installed without
50
+ * overwriting a file the user owns (a warning is logged).
21
51
  */
22
- export declare function writeClineHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
52
+ export declare function writeClineHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<string | null>;
23
53
  //# sourceMappingURL=clineHooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"clineHooks.d.ts","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAsBH;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAkJf"}
1
+ {"version":3,"file":"clineHooks.d.ts","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAiBH;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAuBvF;AAkBD;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoKxB"}
@@ -1,12 +1,31 @@
1
1
  /**
2
- * clineHooks.ts — Cline PreToolUse governance hook injection.
2
+ * clineHooks.ts — Cline PreToolUse governance hook.
3
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}.
4
+ * Cline runs file hooks: an executable named exactly `PreToolUse` (no
5
+ * extension on macOS/Linux) in a hooks directory, fed the pending tool call as
6
+ * JSON on stdin. It refuses the call when the hook prints `{"cancel": true}`
7
+ * on stdout; the exit code does not decide anything. Confirmed against
8
+ * Cline's source (apps/vscode/src/core/hooks, sdk/packages/core/src/hooks):
7
9
  *
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
+ * - The VS Code extension reads `<workspace>/.clinerules/hooks/PreToolUse` and
11
+ * runs it only when "Enable Hooks" is on in Cline's feature settings and the
12
+ * file is executable. Its payload is
13
+ * `{hookName, preToolUse: {toolName, parameters}}`, each parameter value
14
+ * JSON-encoded as a string.
15
+ * - The Cline CLI/SDK searches the same `.clinerules/hooks` directory (plus
16
+ * `.cline/hooks` and two user-level directories). Its payload is
17
+ * `{hookName, tool_call: {id, name, input}}`.
18
+ *
19
+ * So one file, `.clinerules/hooks/PreToolUse`, covers both, and the gate
20
+ * accepts both payload shapes. There is no `hooks.json` and no matcher: the
21
+ * hook runs for every tool call, MCP calls (`use_mcp_tool`) included, and the
22
+ * gate decides from the arguments.
23
+ *
24
+ * `.clinerules` must therefore be a directory. Earlier versions wrote it as a
25
+ * flat rules file (and registered the gate in a `.cline/hooks/hooks.json`
26
+ * Cline never reads); `ensureClinerulesDirectory` converts the flat file this
27
+ * product wrote, and leaves a flat `.clinerules` the user wrote alone — in
28
+ * that case no gate can be installed and a warning says why.
10
29
  *
11
30
  * LLD #14 — Phase 3 cross-harness defence
12
31
  * HLD §3.14 — Three-Tier Defense Cascade
@@ -16,52 +35,91 @@
16
35
  import * as fs from 'node:fs/promises';
17
36
  import * as path from 'node:path';
18
37
  import { createLogger } from '@intutic/logger';
38
+ import { keepOriginal, noteWritten } from '../disconnect/originals.js';
19
39
  import { newIso } from '@intutic/id';
20
40
  import { emitJsGate, emitJsFailClosedPrelude, REVIEW_REQUESTS_BASENAME } from './gateBody.js';
21
41
  const log = createLogger('sync-cline-hooks');
22
- /** Tools Cline can invoke that require governance interception. */
23
- const GOVERNED_TOOLS = [
24
- 'execute_command',
25
- 'write_to_file',
26
- 'str_replace_based_edit_tool',
27
- 'replace_in_file',
28
- 'create_file',
29
- 'delete_file',
30
- 'rename_file',
31
- 'read_file', // watch for exfiltration patterns
32
- ];
42
+ /** First line of every rules file this product writes. */
43
+ const RULES_HEADER = '# Intutic Governance Rules (auto-generated)';
44
+ /** Marker line in the generated hook, used to recognise our own file. */
45
+ const GATE_MARKER = 'Intutic Cline PreToolUse governance gate.';
33
46
  /**
34
- * Write the Intutic governance hook into .cline/hooks/.
47
+ * Make `<workspace>/.clinerules` a directory, converting the flat rules file
48
+ * earlier versions of this product wrote there.
49
+ *
50
+ * @returns `true` when `.clinerules` is (now) a directory; `false` when it is a
51
+ * file the user wrote, which is left alone (a warning is logged).
52
+ */
53
+ export async function ensureClinerulesDirectory(workspaceRoot) {
54
+ const rulesPath = path.join(workspaceRoot, '.clinerules');
55
+ let stat;
56
+ try {
57
+ stat = await fs.stat(rulesPath);
58
+ }
59
+ catch {
60
+ await fs.mkdir(rulesPath, { recursive: true });
61
+ return true;
62
+ }
63
+ if (stat.isDirectory())
64
+ return true;
65
+ const content = await fs.readFile(rulesPath, 'utf-8');
66
+ if (!content.startsWith(RULES_HEADER)) {
67
+ log.warn({ action: 'cline_rules_file_kept', path: rulesPath }, '.clinerules is a rules file you wrote, so Intutic cannot create .clinerules/hooks/ — move its content into ' +
68
+ '.clinerules/<name>.md to enable the Intutic gate');
69
+ return false;
70
+ }
71
+ await fs.unlink(rulesPath);
72
+ await fs.mkdir(rulesPath, { recursive: true });
73
+ return true;
74
+ }
75
+ /** Remove the `.cline/hooks` registration earlier versions wrote — Cline never
76
+ * read it. Only files carrying this product's names or marker are removed. */
77
+ async function removeLegacyRegistration(workspaceRoot) {
78
+ const legacyDir = path.join(workspaceRoot, '.cline', 'hooks');
79
+ await fs.rm(path.join(legacyDir, 'intutic-check.js'), { force: true });
80
+ const legacyJson = path.join(legacyDir, 'hooks.json');
81
+ try {
82
+ const parsed = JSON.parse(await fs.readFile(legacyJson, 'utf-8'));
83
+ if (typeof parsed._comment === 'string' && parsed._comment.startsWith('Intutic governance hooks')) {
84
+ await fs.unlink(legacyJson);
85
+ }
86
+ }
87
+ catch {
88
+ // Absent, or not ours to judge — left alone.
89
+ }
90
+ }
91
+ /**
92
+ * Write the Intutic gate to `<workspace>/.clinerules/hooks/PreToolUse`.
35
93
  *
36
94
  * @param workspaceRoot - Absolute workspace root path.
37
- * @param proxyUrl - Intutic proxy URL for inclusion in block messages.
95
+ * @param proxyUrl - Intutic proxy URL for inclusion in the gate header.
96
+ * @param workspaceId - Workspace ID embedded in every hook event payload.
97
+ * @returns the gate path, or `null` when it could not be installed without
98
+ * overwriting a file the user owns (a warning is logged).
38
99
  */
39
100
  export async function writeClineHooks(workspaceRoot, proxyUrl, workspaceId = '') {
40
- const hooksDir = path.join(workspaceRoot, '.cline', 'hooks');
41
- const hooksJsonPath = path.join(hooksDir, 'hooks.json');
42
- const checkScriptPath = path.join(hooksDir, 'intutic-check.js');
101
+ await removeLegacyRegistration(workspaceRoot);
102
+ if (!(await ensureClinerulesDirectory(workspaceRoot)))
103
+ return null;
104
+ const hooksDir = path.join(workspaceRoot, '.clinerules', 'hooks');
105
+ const checkScriptPath = path.join(hooksDir, 'PreToolUse');
106
+ await keepOriginal(checkScriptPath, workspaceRoot);
43
107
  await fs.mkdir(hooksDir, { recursive: true });
44
- // ── hooks.json ──────────────────────────────────────────────────────
45
- // Cline reads this file and runs matched hook scripts before tool execution.
46
- // The matcher regex is matched against the tool name.
47
- const hooksConfig = {
48
- _comment: 'Intutic governance hooks — auto-generated. DO NOT EDIT.',
49
- _lastSync: newIso(),
50
- hooks: GOVERNED_TOOLS.map((tool) => ({
51
- type: 'PreToolUse',
52
- matcher: `^${tool}$`,
53
- command: `node ${checkScriptPath}`,
54
- })),
55
- };
56
- const tmpJson = hooksJsonPath + '.intutic-tmp';
57
- await fs.writeFile(tmpJson, JSON.stringify(hooksConfig, null, 2) + '\n', 'utf-8');
58
- await fs.rename(tmpJson, hooksJsonPath);
108
+ try {
109
+ const existing = await fs.readFile(checkScriptPath, 'utf-8');
110
+ if (!existing.includes(GATE_MARKER)) {
111
+ log.warn({ action: 'cline_hook_kept', path: checkScriptPath }, 'A PreToolUse hook you wrote already exists — left untouched, so the Intutic gate is not installed for Cline');
112
+ return null;
113
+ }
114
+ }
115
+ catch {
116
+ // No hook yet.
117
+ }
59
118
  // ── pre-tool-check script ────────────────────────────────────────────
60
- // Cline passes tool_name + tool_input as JSON on stdin.
61
119
  // Return {"cancel": false} to allow, {"cancel": true, "errorMessage": "..."} to block.
62
120
  const checkScript = `#!/usr/bin/env node
63
121
  /**
64
- * Intutic Cline PreToolUse governance gate.
122
+ * ${GATE_MARKER}
65
123
  * Auto-generated by intutic sync-daemon. DO NOT EDIT.
66
124
  * Proxy: ${proxyUrl}
67
125
  * Generated: ${newIso()}
@@ -100,7 +158,7 @@ function logEvent(verdict, toolName, reason) {
100
158
  // on any machine where nothing else had created ~/.intutic/events the append
101
159
  // threw ENOENT and was discarded — every Cline audit line, including blocks,
102
160
  // silently dropped. The other writers create this directory from the
103
- // TypeScript side; Cline's writer only creates .cline/hooks, so its log
161
+ // TypeScript side; Cline's writer only creates its hooks directory, so its log
104
162
  // existed only by luck.
105
163
  try {
106
164
  const _dir = path.dirname(HOOK_EVENTS_LOG);
@@ -127,30 +185,50 @@ process.stdin.on('data', (c) => { raw += c; });
127
185
  process.stdin.on('end', () => {
128
186
  try {
129
187
  const ctx = JSON.parse(raw);
130
- _intuticSessionId = ctx.session_id || ctx.sessionId || ctx.conversation_id || ctx.conversationId || ctx.task_id || ctx.taskId || '';
188
+ _intuticSessionId = ctx.taskId || ctx.task_id || ctx.session_id || ctx.sessionId || ctx.conversation_id || ctx.conversationId || '';
131
189
  // An envelope carrying none of the tool fields extracts to empty strings,
132
190
  // which match no rule — an allow. Refused instead, and the refusal CANCELS
133
191
  // (this harness ignores exit codes); see intuticGuardEnvelope.
134
- intuticGuardEnvelope(ctx, ['tool_name', 'toolName', 'tool_input', 'toolInput'], logEvent);
135
- const tool = (ctx.tool_name || '').toLowerCase()
136
- // Case preserved for the gate. A BLOCK: SOP compiles to a tool-name
137
- // pattern the operator wrote as they see it (Bash, Write) and this
138
- // harness lowercases the tool for its own matching. Handing the gate the
139
- // lowercased form means every SOP tool rule silently matches nothing here
140
- // while appearing active everywhere else.
141
- const rawToolName = ctx.tool_name || '';
142
- const input = ctx.tool_input || {};
143
- const inputStr = JSON.stringify(input);
192
+ intuticGuardEnvelope(ctx, ['tool_call', 'preToolUse', 'tool_name', 'toolName', 'tool_input', 'toolInput'], logEvent);
193
+
194
+ // Cline sends one of two shapes (see clineHooks.ts): the CLI/SDK's
195
+ // tool_call {name, input}, or the VS Code extension's preToolUse
196
+ // {toolName, parameters} with every parameter value JSON-encoded as a
197
+ // string. tool_name/tool_input is the generic shape the shared gate tests
198
+ // drive. Case is preserved for the gate: a BLOCK: SOP compiles to a
199
+ // tool-name pattern the operator wrote as they see it.
200
+ const dejson = (v) => {
201
+ if (typeof v !== 'string') return v;
202
+ try { const p = JSON.parse(v); return (p !== null && typeof p === 'object') ? p : v; } catch { return v; }
203
+ };
204
+ let rawToolName = '';
205
+ let input = {};
206
+ if (ctx.tool_call && typeof ctx.tool_call === 'object') {
207
+ rawToolName = ctx.tool_call.name || '';
208
+ input = ctx.tool_call.input;
209
+ } else if (ctx.preToolUse && typeof ctx.preToolUse === 'object') {
210
+ rawToolName = ctx.preToolUse.toolName || '';
211
+ const params = ctx.preToolUse.parameters || {};
212
+ input = {};
213
+ for (const k of Object.keys(params)) input[k] = dejson(params[k]);
214
+ } else {
215
+ rawToolName = ctx.tool_name || ctx.toolName || '';
216
+ input = ctx.tool_input || ctx.toolInput || {};
217
+ }
218
+ if (input === null || typeof input !== 'object') input = { input: input };
219
+ const tool = rawToolName.toLowerCase();
144
220
 
145
- // This gate had two defects that hid each other. Its path list was
146
- // hand-rolled as PROTECTED_PATH_FRAGMENTS with four of the twelve universal
147
- // entries missing, and harnessProtectedPaths.test.ts keyed its coverage on
148
- // finding a constant named PROTECTED_PATHS — so not having one read as "not
149
- // a harness" rather than "unguarded". It also had no command guard at all,
150
- // and ran only for six named file tools.
221
+ // The CLI's file and shell tools take lists (read_files {files}, run_commands
222
+ // {commands}); the extension's take single values. Both are read.
223
+ const firstFile = Array.isArray(input.files) && input.files.length > 0
224
+ ? (typeof input.files[0] === 'string' ? input.files[0] : (input.files[0] && input.files[0].path) || '')
225
+ : '';
226
+ const commands = Array.isArray(input.commands)
227
+ ? input.commands.map((c) => (typeof c === 'string' ? c : (c && c.command) || '')).join('\\n')
228
+ : '';
151
229
  const targetPath = input.path || input.file_path || input.filePath ||
152
- input.target || input.source || input.notebook_path || '';
153
- const command = input.command || input.cmd || input.script || '';
230
+ input.target || input.source || input.notebook_path || firstFile || '';
231
+ const command = input.command || input.cmd || input.script || commands || '';
154
232
  intuticGate(rawToolName, targetPath, command, logEvent, _intuticWsId, input);
155
233
 
156
234
  // Allow all other tool calls
@@ -171,7 +249,11 @@ process.stdin.on('end', () => {
171
249
  const tmpScript = checkScriptPath + '.intutic-tmp';
172
250
  await fs.writeFile(tmpScript, checkScript, 'utf-8');
173
251
  await fs.rename(tmpScript, checkScriptPath);
252
+ // Executable is what enables the hook: Cline creates new hook files 0644,
253
+ // i.e. toggled off.
174
254
  await fs.chmod(checkScriptPath, 0o755);
175
- log.info({ action: 'cline_hooks_written', hooksDir, toolCount: GOVERNED_TOOLS.length }, 'Cline governance hooks written');
255
+ await noteWritten(checkScriptPath, workspaceRoot, checkScript);
256
+ log.info({ action: 'cline_hooks_written', path: checkScriptPath }, 'Cline PreToolUse governance hook written');
257
+ return checkScriptPath;
176
258
  }
177
259
  //# sourceMappingURL=clineHooks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"clineHooks.js","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"clineHooks.js","sourceRoot":"","sources":["../../src/harness/clineHooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * codexConfigMerger.ts — point Codex's built-in OpenAI provider at the proxy.
3
+ *
4
+ * Codex reads its user config from `$CODEX_HOME/config.toml` (`~/.codex` by
5
+ * default). The one key this writer owns is the top-level `openai_base_url`,
6
+ * Codex's documented override for the built-in `openai` provider's base URL.
7
+ * It is user-level only, which is why this writes the user file and not a
8
+ * project `.codex/config.toml`. A `[model_providers.openai]` table cannot do
9
+ * this job: Codex reserves the built-in provider ids (`openai`, `ollama`,
10
+ * `lmstudio`) and does not let `model_providers` override them.
11
+ *
12
+ * Everything else in the file belongs to the user — MCP servers, profiles,
13
+ * model choice, comments — and is kept byte for byte: the key is replaced or
14
+ * inserted as a single line rather than by re-serialising the document, which
15
+ * would drop every comment. The result is parsed before it is written, and an
16
+ * existing file that does not parse is left alone.
17
+ *
18
+ * A file whose first line is the old adapter's header was written whole by
19
+ * that adapter (it replaced the user's config with two provider tables), so it
20
+ * holds nothing of the user's and is regenerated.
21
+ *
22
+ * @module
23
+ */
24
+ /**
25
+ * Set the top-level `openai_base_url` in a Codex config.toml's text.
26
+ *
27
+ * @returns the new file content, or `null` when `raw` is not valid TOML (the
28
+ * caller must leave the file alone).
29
+ */
30
+ export declare function setCodexOpenaiBaseUrl(raw: string, baseUrl: string): string | null;
31
+ /**
32
+ * Merge `openai_base_url` into the Codex user config at `configPath`.
33
+ *
34
+ * @returns `true` when the file now carries the URL, `false` when it was left
35
+ * untouched because it is not valid TOML (a warning is logged).
36
+ */
37
+ export declare function mergeCodexConfig(configPath: string, baseUrl: string): Promise<boolean>;
38
+ //# sourceMappingURL=codexConfigMerger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codexConfigMerger.d.ts","sourceRoot":"","sources":["../../src/harness/codexConfigMerger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AA2BH;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuBjF;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0B5F"}