@intutic/sync-daemon 2.0.0 → 2.1.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 (107) hide show
  1. package/dist/agentReporter.d.ts +4 -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/configWriter.d.ts.map +1 -1
  6. package/dist/configWriter.js +20 -6
  7. package/dist/configWriter.js.map +1 -1
  8. package/dist/harness/aiderConfigMerger.d.ts +41 -5
  9. package/dist/harness/aiderConfigMerger.d.ts.map +1 -1
  10. package/dist/harness/aiderConfigMerger.js +110 -86
  11. package/dist/harness/aiderConfigMerger.js.map +1 -1
  12. package/dist/harness/antigravityHooks.d.ts +13 -8
  13. package/dist/harness/antigravityHooks.d.ts.map +1 -1
  14. package/dist/harness/antigravityHooks.js +51 -39
  15. package/dist/harness/antigravityHooks.js.map +1 -1
  16. package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
  17. package/dist/harness/claudeCodeHooks.js +42 -28
  18. package/dist/harness/claudeCodeHooks.js.map +1 -1
  19. package/dist/harness/clineHooks.d.ts +39 -9
  20. package/dist/harness/clineHooks.d.ts.map +1 -1
  21. package/dist/harness/clineHooks.js +139 -60
  22. package/dist/harness/clineHooks.js.map +1 -1
  23. package/dist/harness/codexConfigMerger.d.ts +38 -0
  24. package/dist/harness/codexConfigMerger.d.ts.map +1 -0
  25. package/dist/harness/codexConfigMerger.js +101 -0
  26. package/dist/harness/codexConfigMerger.js.map +1 -0
  27. package/dist/harness/codexHooks.d.ts +4 -2
  28. package/dist/harness/codexHooks.d.ts.map +1 -1
  29. package/dist/harness/codexHooks.js +11 -11
  30. package/dist/harness/codexHooks.js.map +1 -1
  31. package/dist/harness/continueConfigMerger.d.ts +46 -0
  32. package/dist/harness/continueConfigMerger.d.ts.map +1 -0
  33. package/dist/harness/continueConfigMerger.js +90 -0
  34. package/dist/harness/continueConfigMerger.js.map +1 -0
  35. package/dist/harness/continueHooks.d.ts +15 -25
  36. package/dist/harness/continueHooks.d.ts.map +1 -1
  37. package/dist/harness/continueHooks.js +22 -119
  38. package/dist/harness/continueHooks.js.map +1 -1
  39. package/dist/harness/cursorHooks.d.ts +33 -16
  40. package/dist/harness/cursorHooks.d.ts.map +1 -1
  41. package/dist/harness/cursorHooks.js +66 -25
  42. package/dist/harness/cursorHooks.js.map +1 -1
  43. package/dist/harness/dshHooks.d.ts +59 -52
  44. package/dist/harness/dshHooks.d.ts.map +1 -1
  45. package/dist/harness/dshHooks.js +145 -147
  46. package/dist/harness/dshHooks.js.map +1 -1
  47. package/dist/harness/gateKind.d.ts.map +1 -1
  48. package/dist/harness/gateKind.js +1 -0
  49. package/dist/harness/gateKind.js.map +1 -1
  50. package/dist/harness/githubCopilotHooks.js +1 -1
  51. package/dist/harness/grokHooks.d.ts.map +1 -1
  52. package/dist/harness/grokHooks.js +7 -2
  53. package/dist/harness/grokHooks.js.map +1 -1
  54. package/dist/harness/hermesHooks.d.ts +9 -0
  55. package/dist/harness/hermesHooks.d.ts.map +1 -1
  56. package/dist/harness/hermesHooks.js +32 -31
  57. package/dist/harness/hermesHooks.js.map +1 -1
  58. package/dist/harness/jsonMergeTarget.d.ts +23 -0
  59. package/dist/harness/jsonMergeTarget.d.ts.map +1 -0
  60. package/dist/harness/jsonMergeTarget.js +50 -0
  61. package/dist/harness/jsonMergeTarget.js.map +1 -0
  62. package/dist/harness/mcpAutoWrite.d.ts +28 -6
  63. package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
  64. package/dist/harness/mcpAutoWrite.js +258 -32
  65. package/dist/harness/mcpAutoWrite.js.map +1 -1
  66. package/dist/harness/n8nHooks.d.ts.map +1 -1
  67. package/dist/harness/n8nHooks.js +6 -7
  68. package/dist/harness/n8nHooks.js.map +1 -1
  69. package/dist/harness/openCodeHooks.d.ts +37 -6
  70. package/dist/harness/openCodeHooks.d.ts.map +1 -1
  71. package/dist/harness/openCodeHooks.js +101 -8
  72. package/dist/harness/openCodeHooks.js.map +1 -1
  73. package/dist/harness/openWebuiHooks.d.ts.map +1 -1
  74. package/dist/harness/openWebuiHooks.js +5 -4
  75. package/dist/harness/openWebuiHooks.js.map +1 -1
  76. package/dist/harness/openhandsHooks.d.ts +13 -0
  77. package/dist/harness/openhandsHooks.d.ts.map +1 -1
  78. package/dist/harness/openhandsHooks.js +70 -0
  79. package/dist/harness/openhandsHooks.js.map +1 -1
  80. package/dist/harness/piHooks.d.ts +17 -0
  81. package/dist/harness/piHooks.d.ts.map +1 -1
  82. package/dist/harness/piHooks.js +28 -29
  83. package/dist/harness/piHooks.js.map +1 -1
  84. package/dist/harness/protectedPaths.d.ts +64 -6
  85. package/dist/harness/protectedPaths.d.ts.map +1 -1
  86. package/dist/harness/protectedPaths.js +161 -11
  87. package/dist/harness/protectedPaths.js.map +1 -1
  88. package/dist/harness/windsurfHooks.d.ts +3 -1
  89. package/dist/harness/windsurfHooks.d.ts.map +1 -1
  90. package/dist/harness/windsurfHooks.js +64 -42
  91. package/dist/harness/windsurfHooks.js.map +1 -1
  92. package/dist/harness/windsurfJetBrainsProxy.d.ts +9 -6
  93. package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -1
  94. package/dist/harness/windsurfJetBrainsProxy.js +17 -6
  95. package/dist/harness/windsurfJetBrainsProxy.js.map +1 -1
  96. package/dist/index.d.ts +3 -0
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +3 -0
  99. package/dist/index.js.map +1 -1
  100. package/dist/lib/policySnapshot.d.ts +19 -0
  101. package/dist/lib/policySnapshot.d.ts.map +1 -1
  102. package/dist/lib/policySnapshot.js +29 -2
  103. package/dist/lib/policySnapshot.js.map +1 -1
  104. package/dist/watcher/settingsGuard.d.ts.map +1 -1
  105. package/dist/watcher/settingsGuard.js +14 -15
  106. package/dist/watcher/settingsGuard.js.map +1 -1
  107. package/package.json +6 -6
@@ -2,8 +2,26 @@
2
2
  * aiderConfigMerger.ts — Safe YAML merge for .aider.conf.yml.
3
3
  *
4
4
  * Reads any existing .aider.conf.yml, STRIPS dangerous auto-exec keys
5
- * (test-cmd, lint-cmd, auto-test, auto-lint), merges in Intutic governance
6
- * keys (openai-api-base, anthropic-api-base), and writes back atomically.
5
+ * (test-cmd, lint-cmd, auto-test, auto-lint), merges in the Intutic proxy
6
+ * routing, and writes back atomically. Everything else in the file — lists,
7
+ * nested values, comments — is kept: the file is edited through the `yaml`
8
+ * library's document model, never re-serialised from a flattened copy.
9
+ *
10
+ * Aider rejects any config key that is not one of its command-line options
11
+ * ("unrecognized arguments"), so only real options are written:
12
+ *
13
+ * - `openai-api-base` — the proxy's OpenAI-style base URL (host + `/v1`).
14
+ * - `set-env: ANTHROPIC_BASE_URL=<host>` — Aider has no Anthropic base-URL
15
+ * option; its model layer (LiteLLM) reads this variable, and wants the bare
16
+ * host because it appends `/v1/messages` itself. Entries the user set for
17
+ * other variables are kept.
18
+ * - `read: .intutic/aider-sops.md` — the SOP text, as a read-only context
19
+ * file (Aider's own mechanism for conventions), written next to the config.
20
+ *
21
+ * Earlier versions wrote `anthropic-api-base` and `extra-instructions`, which
22
+ * are not Aider options and stopped Aider from starting; both are removed.
23
+ *
24
+ * A file that does not parse as YAML, or is not a mapping, is left untouched.
7
25
  *
8
26
  * Each strip emits a governance_config_sanitized log entry visible in
9
27
  * the control plane audit feed.
@@ -15,125 +33,131 @@
15
33
  */
16
34
  import * as fs from 'node:fs/promises';
17
35
  import * as path from 'node:path';
36
+ import { parseDocument, isMap, isSeq, isScalar, YAMLSeq } from 'yaml';
18
37
  import { createLogger } from '@intutic/logger';
19
- import { newIso } from '@intutic/id';
38
+ import { anthropicBaseUrl, openaiBaseUrl } from '@intutic/shared-types';
20
39
  const log = createLogger('sync-aider-merger');
21
40
  /**
22
41
  * Keys that Aider auto-executes on startup without user confirmation.
23
42
  * Stripping these prevents supply-chain persistence attacks.
24
43
  */
25
44
  const SUPPRESSED_KEYS = ['test-cmd', 'lint-cmd', 'auto-test', 'auto-lint', 'test_cmd', 'lint_cmd'];
26
- /**
27
- * Minimal YAML parser for .aider.conf.yml — handles simple key: value lines
28
- * and multi-line strings. Does not handle complex YAML. Good enough for the
29
- * flat structure of .aider.conf.yml.
30
- */
31
- function parseAiderYaml(raw) {
32
- const result = {};
45
+ /** Keys earlier versions wrote that Aider rejects as unrecognized arguments. */
46
+ const INVALID_LEGACY_KEYS = ['anthropic-api-base', 'extra-instructions'];
47
+ /** SOP file, relative to the workspace root (where Aider runs). */
48
+ export const AIDER_SOPS_FILE = '.intutic/aider-sops.md';
49
+ /** Header written at the top of the file. Kept short: it sits above the
50
+ * user's own content, and is replaced, not stacked, on every sync. */
51
+ const HEADER = [
52
+ '# Intutic: openai-api-base, the ANTHROPIC_BASE_URL set-env entry and the',
53
+ `# ${AIDER_SOPS_FILE} read entry are managed by intutic connect. test-cmd,`,
54
+ '# lint-cmd, auto-test and auto-lint are removed on every sync.',
55
+ ];
56
+ /** Leading lines this product wrote (the header above, or the one earlier
57
+ * versions wrote), removed before the file is parsed. */
58
+ const OWN_HEADER_LINE = /^# (Intutic: |Intutic Governance Rules \(auto-generated|Last sync: |WARNING: test-cmd and lint-cmd keys are suppressed|\.intutic\/aider-sops\.md read entry|lint-cmd, auto-test and auto-lint are removed)/;
59
+ function stripOwnHeader(raw) {
33
60
  const lines = raw.split('\n');
34
61
  let i = 0;
35
- while (i < lines.length) {
36
- const line = lines[i];
37
- // Skip comments and blank lines
38
- if (!line || line.trim().startsWith('#')) {
39
- i++;
40
- continue;
41
- }
42
- const colonIdx = line.indexOf(':');
43
- if (colonIdx === -1) {
44
- i++;
45
- continue;
46
- }
47
- const key = line.slice(0, colonIdx).trim();
48
- const rest = line.slice(colonIdx + 1).trim();
49
- if (rest === '|' || rest === '>') {
50
- // Multi-line block scalar — collect until next key or EOF
51
- const blockLines = [];
52
- i++;
53
- while (i < lines.length && (lines[i].startsWith(' ') || lines[i] === '')) {
54
- blockLines.push(lines[i].startsWith(' ') ? lines[i].slice(2) : '');
55
- i++;
56
- }
57
- result[key] = blockLines.join('\n').trimEnd();
58
- }
59
- else {
60
- // Strip inline quotes
61
- result[key] = rest.replace(/^["']|["']$/g, '');
62
- i++;
63
- }
64
- }
65
- return result;
62
+ while (i < lines.length && OWN_HEADER_LINE.test(lines[i]))
63
+ i++;
64
+ if (i === 0)
65
+ return raw;
66
+ while (i < lines.length && lines[i].trim() === '')
67
+ i++;
68
+ return lines.slice(i).join('\n');
69
+ }
70
+ /** Turn `key`'s value into a sequence (a scalar becomes a one-item list). */
71
+ function ensureSeq(doc, key) {
72
+ const current = doc.get(key, true);
73
+ if (isSeq(current))
74
+ return current;
75
+ const seq = new YAMLSeq();
76
+ if (isScalar(current) && current.value !== null && current.value !== '')
77
+ seq.add(current.value);
78
+ doc.set(key, seq);
79
+ return seq;
66
80
  }
67
81
  /**
68
- * Minimal YAML serializer — writes flat key: value pairs.
69
- * Multi-line values use the | block scalar style.
82
+ * Merge the Intutic keys into the text of an `.aider.conf.yml`.
83
+ *
84
+ * @param raw - Current file content ('' when the file does not exist).
85
+ * @param proxyUrl - The proxy host (a trailing `/v1` is tolerated).
86
+ * @param hasSops - Whether the SOP file is to be listed under `read`.
70
87
  */
71
- function serializeAiderYaml(obj) {
72
- const lines = [];
73
- for (const [key, value] of Object.entries(obj)) {
74
- if (typeof value === 'string' && value.includes('\n')) {
75
- lines.push(`${key}: |`);
76
- for (const ln of value.split('\n')) {
77
- lines.push(` ${ln}`);
78
- }
79
- }
80
- else if (typeof value === 'boolean') {
81
- lines.push(`${key}: ${value}`);
82
- }
83
- else {
84
- lines.push(`${key}: ${String(value)}`);
85
- }
86
- }
87
- return lines.join('\n');
88
+ export function mergeAiderYaml(raw, proxyUrl, hasSops) {
89
+ const doc = parseDocument(stripOwnHeader(raw));
90
+ if (doc.errors.length > 0)
91
+ return { content: null, stripped: [] };
92
+ // A missing, empty or comment-only file starts as an empty mapping (its
93
+ // comments stay on the document); anything else that is not a mapping (a
94
+ // list, a bare scalar) is not an Aider config to merge into.
95
+ if (doc.contents === null)
96
+ doc.contents = doc.createNode({});
97
+ if (!isMap(doc.contents))
98
+ return { content: null, stripped: [] };
99
+ const stripped = SUPPRESSED_KEYS.filter((key) => doc.has(key));
100
+ for (const key of [...stripped, ...INVALID_LEGACY_KEYS])
101
+ doc.delete(key);
102
+ doc.set('openai-api-base', openaiBaseUrl(proxyUrl));
103
+ const setEnv = ensureSeq(doc, 'set-env');
104
+ setEnv.items = setEnv.items.filter((item) => {
105
+ const value = isScalar(item) ? item.value : item;
106
+ return !(typeof value === 'string' && value.startsWith('ANTHROPIC_BASE_URL='));
107
+ });
108
+ setEnv.add(`ANTHROPIC_BASE_URL=${anthropicBaseUrl(proxyUrl)}`);
109
+ const read = ensureSeq(doc, 'read');
110
+ read.items = read.items.filter((item) => (isScalar(item) ? item.value : item) !== AIDER_SOPS_FILE);
111
+ if (hasSops)
112
+ read.add(AIDER_SOPS_FILE);
113
+ if (read.items.length === 0)
114
+ doc.delete('read');
115
+ return { content: `${HEADER.join('\n')}\n\n${doc.toString()}`, stripped };
88
116
  }
89
117
  /**
90
118
  * Merge Intutic governance settings into .aider.conf.yml.
91
119
  *
92
120
  * Preserves all user keys EXCEPT the suppressed auto-exec keys.
93
- * Adds/updates openai-api-base and anthropic-api-base.
94
121
  *
95
122
  * @param configPath - Absolute path to .aider.conf.yml
96
123
  * @param proxyUrl - Intutic proxy URL
97
- * @param sopsText - Optional SOP instructions for extra-instructions field
124
+ * @param sopsText - Optional SOP instructions, written to the `read` file
125
+ * @returns `true` when the config was written, `false` when it was left
126
+ * untouched because it is not a YAML mapping (a warning is logged).
98
127
  */
99
128
  export async function mergeAiderConfig(configPath, proxyUrl, sopsText) {
100
- // Read existing config if present
101
- let existing = {};
129
+ let raw = '';
102
130
  try {
103
- const raw = await fs.readFile(configPath, 'utf-8');
104
- existing = parseAiderYaml(raw);
131
+ raw = await fs.readFile(configPath, 'utf-8');
105
132
  }
106
- catch {
107
- // No existing file — start fresh
133
+ catch (err) {
134
+ if (err.code !== 'ENOENT')
135
+ throw err;
108
136
  }
109
- // Strip dangerous auto-exec keys
110
- const stripped = [];
111
- for (const key of SUPPRESSED_KEYS) {
112
- if (key in existing) {
113
- stripped.push(key);
114
- delete existing[key];
115
- }
137
+ const { content, stripped } = mergeAiderYaml(raw, proxyUrl, Boolean(sopsText));
138
+ if (content === null) {
139
+ log.warn({ action: 'aider_config_merge_skipped', path: configPath }, `${configPath} is not a YAML mapping — left untouched; fix it and the next sync will add the Intutic keys`);
140
+ return false;
116
141
  }
117
142
  if (stripped.length > 0) {
118
143
  log.warn({ action: 'aider_keys_stripped', keys: stripped, path: configPath }, `Stripped dangerous auto-exec keys from .aider.conf.yml: ${stripped.join(', ')}`);
119
144
  }
120
- // Merge governance keys
121
- existing['openai-api-base'] = proxyUrl;
122
- existing['anthropic-api-base'] = proxyUrl;
145
+ // The SOP file lives next to the config, under the workspace's .intutic/.
146
+ const sopsPath = path.join(path.dirname(configPath), AIDER_SOPS_FILE);
123
147
  if (sopsText) {
124
- existing['extra-instructions'] = sopsText;
148
+ await fs.mkdir(path.dirname(sopsPath), { recursive: true });
149
+ const tmpSops = sopsPath + '.intutic-tmp';
150
+ await fs.writeFile(tmpSops, sopsText + '\n', 'utf-8');
151
+ await fs.rename(tmpSops, sopsPath);
152
+ }
153
+ else {
154
+ await fs.rm(sopsPath, { force: true });
125
155
  }
126
- const header = [
127
- '# Intutic Governance Rules (auto-generated — do not edit proxy keys)',
128
- `# Last sync: ${newIso()}`,
129
- `# WARNING: test-cmd and lint-cmd keys are suppressed by Intutic governance`,
130
- '',
131
- ].join('\n');
132
- const content = header + serializeAiderYaml(existing) + '\n';
133
156
  const tmpPath = configPath + '.intutic-tmp';
134
157
  await fs.mkdir(path.dirname(configPath), { recursive: true });
135
158
  await fs.writeFile(tmpPath, content, 'utf-8');
136
159
  await fs.rename(tmpPath, configPath);
137
160
  log.info({ action: 'aider_config_written', path: configPath, stripped }, 'Aider config merged with proxy URL and dangerous keys stripped');
161
+ return true;
138
162
  }
139
163
  //# sourceMappingURL=aiderConfigMerger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"aiderConfigMerger.js","sourceRoot":"","sources":["../../src/harness/aiderConfigMerger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aiderConfigMerger.js","sourceRoot":"","sources":["../../src/harness/aiderConfigMerger.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,21 @@
1
1
  /**
2
2
  * antigravityHooks.ts — Gemini CLI (Antigravity) governance hook injection.
3
3
  *
4
- * Antigravity (Google DeepMind's Gemini CLI) supports a tool-level governance
5
- * hook via the `ANTIGRAVITY_PRE_TOOL_HOOK` environment variable and an optional
6
- * hook configuration file at `~/.gemini/settings.json`. This module:
4
+ * Gemini CLI runs command hooks registered under `hooks.BeforeTool` in
5
+ * `~/.gemini/settings.json`. Each entry carries a `matcher` regex over tool
6
+ * names and a list of `{type: "command", command}` hooks; the command receives
7
+ * `{tool_name, tool_input, ...}` on stdin and blocks the call with exit code 2
8
+ * (stderr is the reason). This module:
7
9
  *
8
- * 1. Writes a pre-tool-check.sh script to `.intutic/hooks/antigravity-check.sh`
10
+ * 1. Writes the gate script to `.intutic/hooks/antigravity-check.sh`
9
11
  * (same pattern as Goose / OpenHands bash hooks).
10
- * 2. Merges the hook path into `~/.gemini/settings.json` under the
11
- * `hooks.preTool` key.
12
- * 3. Optionally writes an `ANTIGRAVITY_PROXY_URL` env snippet to
13
- * `.intutic/env/antigravity.env` for IDE/shell integration.
12
+ * 2. Merges a catch-all `BeforeTool` entry for it into
13
+ * `~/.gemini/settings.json`, keeping every other setting and hook. A
14
+ * settings file that is not a plain JSON object is left alone.
15
+ *
16
+ * Earlier versions registered the script under `hooks.preTool` and wrote an
17
+ * `ANTIGRAVITY_PRE_TOOL_HOOK` env snippet. Neither is a Gemini CLI setting, so
18
+ * that gate never ran; the stale `preTool` key is removed on the next write.
14
19
  *
15
20
  * Why bash and not Node.js?
16
21
  * The Gemini CLI (antigravity) is a standalone binary; when it invokes
@@ -1 +1 @@
1
- {"version":3,"file":"antigravityHooks.d.ts","sourceRoot":"","sources":["../../src/harness/antigravityHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAyFH;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAsEf"}
1
+ {"version":3,"file":"antigravityHooks.d.ts","sourceRoot":"","sources":["../../src/harness/antigravityHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAwFH;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CA0Ef"}
@@ -1,16 +1,21 @@
1
1
  /**
2
2
  * antigravityHooks.ts — Gemini CLI (Antigravity) governance hook injection.
3
3
  *
4
- * Antigravity (Google DeepMind's Gemini CLI) supports a tool-level governance
5
- * hook via the `ANTIGRAVITY_PRE_TOOL_HOOK` environment variable and an optional
6
- * hook configuration file at `~/.gemini/settings.json`. This module:
4
+ * Gemini CLI runs command hooks registered under `hooks.BeforeTool` in
5
+ * `~/.gemini/settings.json`. Each entry carries a `matcher` regex over tool
6
+ * names and a list of `{type: "command", command}` hooks; the command receives
7
+ * `{tool_name, tool_input, ...}` on stdin and blocks the call with exit code 2
8
+ * (stderr is the reason). This module:
7
9
  *
8
- * 1. Writes a pre-tool-check.sh script to `.intutic/hooks/antigravity-check.sh`
10
+ * 1. Writes the gate script to `.intutic/hooks/antigravity-check.sh`
9
11
  * (same pattern as Goose / OpenHands bash hooks).
10
- * 2. Merges the hook path into `~/.gemini/settings.json` under the
11
- * `hooks.preTool` key.
12
- * 3. Optionally writes an `ANTIGRAVITY_PROXY_URL` env snippet to
13
- * `.intutic/env/antigravity.env` for IDE/shell integration.
12
+ * 2. Merges a catch-all `BeforeTool` entry for it into
13
+ * `~/.gemini/settings.json`, keeping every other setting and hook. A
14
+ * settings file that is not a plain JSON object is left alone.
15
+ *
16
+ * Earlier versions registered the script under `hooks.preTool` and wrote an
17
+ * `ANTIGRAVITY_PRE_TOOL_HOOK` env snippet. Neither is a Gemini CLI setting, so
18
+ * that gate never ran; the stale `preTool` key is removed on the next write.
14
19
  *
15
20
  * Why bash and not Node.js?
16
21
  * The Gemini CLI (antigravity) is a standalone binary; when it invokes
@@ -34,9 +39,8 @@ import * as os from 'node:os';
34
39
  import { createLogger } from '@intutic/logger';
35
40
  import { newIso } from '@intutic/id';
36
41
  import { emitShellGate, SHELL_EXTRACT, SHELL_FAIL_CLOSED } from './gateBody.js';
42
+ import { readJsonObjectForMerge } from './jsonMergeTarget.js';
37
43
  const log = createLogger('sync-antigravity-hooks');
38
- /** Default path to the Gemini CLI global settings file. */
39
- const GEMINI_SETTINGS = path.join(os.homedir(), '.gemini', 'settings.json');
40
44
  /** Governance-sensitive paths that the hook gate protects. */
41
45
  // ─── Bash hook script template ────────────────────────────────────────────────
42
46
  function buildAntigravityCheckScript(proxyUrl, workspaceRoot, workspaceId) {
@@ -120,12 +124,12 @@ export async function writeAntigravityHooks(workspaceRoot, proxyUrl, workspaceId
120
124
  // ── 1. Ensure directories ──────────────────────────────────────────────────
121
125
  const hookScriptDir = path.join(workspaceRoot, '.intutic', 'hooks');
122
126
  const hookEventsDir = path.join(workspaceRoot, '.intutic', 'events');
123
- const envDir = path.join(workspaceRoot, '.intutic', 'env');
127
+ // homedir() read at call time so tests that move HOME are honoured.
128
+ const geminiSettings = path.join(os.homedir(), '.gemini', 'settings.json');
124
129
  await Promise.all([
125
130
  fs.mkdir(hookScriptDir, { recursive: true }),
126
131
  fs.mkdir(hookEventsDir, { recursive: true }),
127
- fs.mkdir(envDir, { recursive: true }),
128
- fs.mkdir(path.join(os.homedir(), '.gemini'), { recursive: true }),
132
+ fs.mkdir(path.dirname(geminiSettings), { recursive: true }),
129
133
  ]);
130
134
  // ── 2. Write hook script ───────────────────────────────────────────────────
131
135
  const hookScriptPath = path.join(hookScriptDir, 'antigravity-check.sh');
@@ -135,37 +139,45 @@ export async function writeAntigravityHooks(workspaceRoot, proxyUrl, workspaceId
135
139
  await fs.chmod(hookScriptPath, 0o755);
136
140
  log.info({ action: 'antigravity_hook_script_written', path: hookScriptPath }, 'Antigravity hook script written');
137
141
  // ── 3. Merge into ~/.gemini/settings.json ─────────────────────────────────
138
- let existingSettings = {};
139
- try {
140
- const raw = await fs.readFile(GEMINI_SETTINGS, 'utf-8');
141
- existingSettings = JSON.parse(raw);
142
- }
143
- catch {
144
- // File doesn't exist yet — start fresh
145
- }
142
+ const existingSettings = await readJsonObjectForMerge(geminiSettings);
143
+ if (existingSettings === null)
144
+ return;
145
+ const existingHooks = (typeof existingSettings.hooks === 'object' && existingSettings.hooks !== null
146
+ ? existingSettings.hooks
147
+ : {});
148
+ // `preTool` is where earlier versions registered the gate; it is not a
149
+ // Gemini CLI hook event, so it is dropped rather than carried forward.
150
+ const keptHooks = { ...existingHooks };
151
+ delete keptHooks.preTool;
152
+ const existingBeforeTool = Array.isArray(keptHooks.BeforeTool) ? keptHooks.BeforeTool : [];
153
+ // De-duplicated by command, so repeated syncs replace this entry rather than
154
+ // stacking copies, and every hook the user registered is kept.
155
+ const intuticCmd = `bash ${JSON.stringify(hookScriptPath)}`;
156
+ const filtered = existingBeforeTool.filter((entry) => {
157
+ if (typeof entry !== 'object' || entry === null)
158
+ return true;
159
+ const inner = entry.hooks ?? [];
160
+ return !(inner.length > 0 &&
161
+ inner.every((h) => typeof h === 'object' && h !== null && h.command === intuticCmd));
162
+ });
146
163
  const mergedSettings = {
147
164
  ...existingSettings,
148
165
  hooks: {
149
- ...existingSettings.hooks,
150
- // Register the pre-tool hook command
151
- preTool: hookScriptPath,
166
+ ...keptHooks,
167
+ BeforeTool: [
168
+ ...filtered,
169
+ {
170
+ // Catch-all: the gate decides from the arguments, so a tool name
171
+ // nobody anticipated is still evaluated.
172
+ matcher: '.*',
173
+ hooks: [{ name: 'intutic-governance', type: 'command', command: intuticCmd }],
174
+ },
175
+ ],
152
176
  },
153
177
  };
154
- const tmpSettings = GEMINI_SETTINGS + '.intutic-tmp';
178
+ const tmpSettings = geminiSettings + '.intutic-tmp';
155
179
  await fs.writeFile(tmpSettings, JSON.stringify(mergedSettings, null, 2) + '\n', 'utf-8');
156
- await fs.rename(tmpSettings, GEMINI_SETTINGS);
157
- log.info({ action: 'antigravity_settings_written', path: GEMINI_SETTINGS }, 'Merged Intutic hook into ~/.gemini/settings.json');
158
- // ── 4. Write env snippet (optional — for shell profiles / IDE env) ─────────
159
- const envSnippet = [
160
- `# Intutic Antigravity governance hook — auto-generated ${newIso()}`,
161
- `# Source this file in ~/.bashrc or ~/.zshrc to apply governance to Gemini CLI.`,
162
- `export ANTIGRAVITY_PRE_TOOL_HOOK=${JSON.stringify(hookScriptPath)}`,
163
- proxyUrl ? `export ANTIGRAVITY_PROXY_URL=${JSON.stringify(proxyUrl)}` : '',
164
- ].filter(Boolean).join('\n') + '\n';
165
- const envFilePath = path.join(envDir, 'antigravity.env');
166
- const tmpEnv = envFilePath + '.intutic-tmp';
167
- await fs.writeFile(tmpEnv, envSnippet, 'utf-8');
168
- await fs.rename(tmpEnv, envFilePath);
169
- log.info({ action: 'antigravity_env_written', path: envFilePath }, 'Antigravity env snippet written');
180
+ await fs.rename(tmpSettings, geminiSettings);
181
+ log.info({ action: 'antigravity_settings_written', path: geminiSettings }, 'Merged Intutic BeforeTool hook into ~/.gemini/settings.json');
170
182
  }
171
183
  //# sourceMappingURL=antigravityHooks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"antigravityHooks.js","sourceRoot":"","sources":["../../src/harness/antigravityHooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"antigravityHooks.js","sourceRoot":"","sources":["../../src/harness/antigravityHooks.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"claudeCodeHooks.d.ts","sourceRoot":"","sources":["../../src/harness/claudeCodeHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAwBzD,gFAAgF;AAChF,eAAO,MAAM,eAAe,sCAAsC,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,0BAAgC,CAAA;AACrE,eAAO,MAAM,mBAAmB,0CAA2B,CAAA;AAC3D,eAAO,MAAM,sBAAsB,IAA8B,CAAA;AAmBjE,eAAO,MAAM,wBAAwB;;;IAGnC;;;;;;;;;;OAUG;;;;;;;;;;EAEH,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAsCzE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAAE,EACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,uBAAuB,EAAE,iBAAiB,GACrF,kBAAkB,CAgHpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,YAAY,EAAE,EACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,uBAAuB,EAAE,iBAAiB,EACtF,eAAe,CAAC,EAAE,MAAM,EACxB,WAAW,CAAC,EAAE,MAAM,EACpB,WAAW,SAAgB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAgUf;AA4ID;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAQjB"}
1
+ {"version":3,"file":"claudeCodeHooks.d.ts","sourceRoot":"","sources":["../../src/harness/claudeCodeHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAyBzD,gFAAgF;AAChF,eAAO,MAAM,eAAe,sCAAsC,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,0BAAgC,CAAA;AACrE,eAAO,MAAM,mBAAmB,0CAA2B,CAAA;AAC3D,eAAO,MAAM,sBAAsB,IAA8B,CAAA;AAmBjE,eAAO,MAAM,wBAAwB;;;IAGnC;;;;;;;;;;OAUG;;;;;;;;;;EAEH,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAA;AAsCzE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAAE,EACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,uBAAuB,EAAE,iBAAiB,GACrF,kBAAkB,CAgHpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,YAAY,EAAE,EACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,uBAAuB,EAAE,iBAAiB,EACtF,eAAe,CAAC,EAAE,MAAM,EACxB,WAAW,CAAC,EAAE,MAAM,EACpB,WAAW,SAAgB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAuTf;AAiKD;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAQjB"}
@@ -17,6 +17,7 @@ import { z } from 'zod';
17
17
  import { createLogger } from '@intutic/logger';
18
18
  import { emitJsGate, emitJsFailClosedPrelude, emitPreImageCapture, REVIEW_REQUESTS_BASENAME as GATE_REVIEW_REQUESTS_BASENAME, REVIEW_REQUESTS_LOG as GATE_REVIEW_REQUESTS_LOG, REVIEW_REQUEST_VERSION as GATE_REVIEW_REQUEST_VERSION, } from './gateBody.js';
19
19
  import { emitRedactor } from './holdRedaction.js';
20
+ import { readJsonObjectForMerge } from './jsonMergeTarget.js';
20
21
  const log = createLogger('sync-claude-hooks');
21
22
  /**
22
23
  * Coerces an unvalidated settings value to a string array, dropping anything else.
@@ -454,26 +455,23 @@ process.stdin.on('end', () => {
454
455
  const localClaudeDir = node_path.join(workspaceRoot, '.claude');
455
456
  await node_fs.mkdir(localClaudeDir, { recursive: true });
456
457
  const localSettingsPath = node_path.join(localClaudeDir, 'settings.json');
457
- let existingLocal = {};
458
- try {
459
- const raw = await node_fs.readFile(localSettingsPath, 'utf-8');
460
- existingLocal = JSON.parse(raw);
461
- }
462
- catch {
463
- // Ignore
458
+ // A settings file that is not a plain JSON object is left untouched (and
459
+ // reported) rather than replaced with only the Intutic keys.
460
+ const existingLocal = await readJsonObjectForMerge(localSettingsPath);
461
+ if (existingLocal !== null) {
462
+ const mergedLocal = {
463
+ ...existingLocal,
464
+ permissions: {
465
+ ...existingLocal.permissions,
466
+ deny: denyRules,
467
+ },
468
+ hooks: {
469
+ ...existingLocal.hooks,
470
+ PreToolUse: mergePreToolUse(existingLocal.hooks?.['PreToolUse'], newSettings.hooks.PreToolUse),
471
+ },
472
+ };
473
+ await node_fs.writeFile(localSettingsPath, JSON.stringify(mergedLocal, null, 2) + '\n', 'utf-8');
464
474
  }
465
- const mergedLocal = {
466
- ...existingLocal,
467
- permissions: {
468
- ...existingLocal.permissions,
469
- deny: denyRules,
470
- },
471
- hooks: {
472
- ...existingLocal.hooks,
473
- PreToolUse: newSettings.hooks.PreToolUse,
474
- },
475
- };
476
- await node_fs.writeFile(localSettingsPath, JSON.stringify(mergedLocal, null, 2) + '\n', 'utf-8');
477
475
  // Write global settings config ~/.claude/settings.json
478
476
  //
479
477
  // GUARD (bugfix): never touch the user's real global settings from a test
@@ -490,14 +488,9 @@ process.stdin.on('end', () => {
490
488
  const globalClaudeDir = node_path.join(node_os.homedir(), '.claude');
491
489
  await node_fs.mkdir(globalClaudeDir, { recursive: true });
492
490
  const globalSettingsPath = node_path.join(globalClaudeDir, 'settings.json');
493
- let existingGlobal = {};
494
- try {
495
- const raw = await node_fs.readFile(globalSettingsPath, 'utf-8');
496
- existingGlobal = JSON.parse(raw);
497
- }
498
- catch {
499
- // Ignore
500
- }
491
+ const existingGlobal = await readJsonObjectForMerge(globalSettingsPath);
492
+ if (existingGlobal === null)
493
+ return;
501
494
  // Merge, prioritizing global user settings but updating permissions/hooks
502
495
  const mergedGlobal = {
503
496
  ...existingGlobal,
@@ -511,12 +504,33 @@ process.stdin.on('end', () => {
511
504
  },
512
505
  hooks: {
513
506
  ...existingGlobal.hooks,
514
- PreToolUse: newSettings.hooks.PreToolUse,
507
+ PreToolUse: mergePreToolUse(existingGlobal.hooks?.['PreToolUse'], newSettings.hooks.PreToolUse),
515
508
  },
516
509
  };
517
510
  await node_fs.writeFile(globalSettingsPath, JSON.stringify(mergedGlobal, null, 2) + '\n', 'utf-8');
518
511
  log.info({ action: 'hooks_written' }, 'Successfully updated settings.json hooks globally and locally');
519
512
  }
513
+ /**
514
+ * The PreToolUse list to write: the user's own entries, then the Intutic
515
+ * ones. An entry is Intutic's when every hook in it runs a gate this module
516
+ * wrote (`claude-code-check.js`, or `pre-tool-check.js` from earlier
517
+ * versions), so re-running replaces those instead of stacking copies, and a
518
+ * hook the user registered survives every sync.
519
+ */
520
+ function mergePreToolUse(existing, intutic) {
521
+ const isOurs = (entry) => {
522
+ if (typeof entry !== 'object' || entry === null)
523
+ return false;
524
+ const inner = entry['hooks'];
525
+ return Array.isArray(inner) && inner.length > 0 && inner.every((h) => {
526
+ const command = typeof h === 'object' && h !== null ? h['command'] : undefined;
527
+ return typeof command === 'string' &&
528
+ (command.includes('.intutic/hooks/claude-code-check.js') || command.includes('.intutic/hooks/pre-tool-check.js'));
529
+ });
530
+ };
531
+ const kept = Array.isArray(existing) ? existing.filter((e) => !isOurs(e)) : [];
532
+ return [...kept, ...intutic];
533
+ }
520
534
  // ─── Event Drain Helper ──────────────────────────────────────────────────────
521
535
  /**
522
536
  * Drains the local hook-events log file and POSTs all accumulated governance