@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 @@
1
+ {"version":3,"file":"gooseHooks.d.ts","sourceRoot":"","sources":["../../src/harness/gooseHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAwBH;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,SAAe,EAAE,WAAW,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAqHrH"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * gooseHooks.ts — Goose PreToolUse governance plugin injection.
3
+ *
4
+ * Writes the Intutic governance plugin to the Goose plugin directory:
5
+ * ~/.agents/plugins/intutic-governance/hooks/hooks.json
6
+ * ~/.agents/plugins/intutic-governance/scripts/intutic-check.sh
7
+ *
8
+ * Also merges the Intutic proxy URL into ~/.config/goose/config.yaml.
9
+ * After writing, calls gooseHardener to apply chmod 444 + immutable flags.
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 * as os from 'node:os';
19
+ import { createLogger } from '@intutic/logger';
20
+ import { newIso } from '@intutic/id';
21
+ import { hardenGoosePlugin, unharden } from './gooseHardener.js';
22
+ const log = createLogger('sync-goose-hooks');
23
+ const PLUGIN_DIR = path.join(os.homedir(), '.agents', 'plugins', 'intutic-governance');
24
+ const GOOSE_CONFIG = path.join(os.homedir(), '.config', 'goose', 'config.yaml');
25
+ const PROTECTED_PATHS = [
26
+ '.agents/plugins/intutic-governance',
27
+ '.intutic/hooks',
28
+ '.intutic/integrity.json',
29
+ '.claude/settings.json',
30
+ '.cursor/hooks.json',
31
+ '.cline/hooks',
32
+ '.openhands/hooks.json',
33
+ ];
34
+ /**
35
+ * Write Goose governance plugin and harden it.
36
+ *
37
+ * @param proxyUrl - Intutic proxy URL written into the plugin script.
38
+ * @param workspaceRoot - Absolute workspace root (for hook events log path).
39
+ * @param workspaceId - Workspace ID embedded in hook event payloads.
40
+ */
41
+ export async function writeGooseHooks(proxyUrl, workspaceRoot = os.homedir(), workspaceId = '') {
42
+ const hooksDir = path.join(PLUGIN_DIR, 'hooks');
43
+ const scriptsDir = path.join(PLUGIN_DIR, 'scripts');
44
+ await fs.mkdir(hooksDir, { recursive: true });
45
+ await fs.mkdir(scriptsDir, { recursive: true });
46
+ // ── hooks.json ──────────────────────────────────────────────────────
47
+ const hooksConfig = {
48
+ _comment: 'Intutic governance plugin — auto-generated. DO NOT EDIT.',
49
+ _lastSync: newIso(),
50
+ name: 'intutic-governance',
51
+ version: '1.0.0',
52
+ hooks: {
53
+ PreToolUse: {
54
+ command: path.join(scriptsDir, 'intutic-check.sh'),
55
+ failClosed: true,
56
+ },
57
+ PostToolUse: {
58
+ command: path.join(scriptsDir, 'intutic-check.sh --event post'),
59
+ failClosed: false,
60
+ },
61
+ },
62
+ };
63
+ const hooksJsonPath = path.join(hooksDir, 'hooks.json');
64
+ // Unharden before writing (in case we're refreshing an existing install)
65
+ await unharden(hooksJsonPath);
66
+ const tmpHooks = hooksJsonPath + '.intutic-tmp';
67
+ await fs.writeFile(tmpHooks, JSON.stringify(hooksConfig, null, 2) + '\n', 'utf-8');
68
+ await fs.rename(tmpHooks, hooksJsonPath);
69
+ // ── intutic-check.sh ─────────────────────────────────────────────────
70
+ const checkScriptPath = path.join(scriptsDir, 'intutic-check.sh');
71
+ await unharden(checkScriptPath);
72
+ const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'hook-events.jsonl');
73
+ // Ensure events dir exists
74
+ await fs.mkdir(path.join(workspaceRoot, '.intutic', 'events'), { recursive: true });
75
+ const checkScript = `#!/usr/bin/env bash
76
+ # Intutic Goose PreToolUse governance gate.
77
+ # Auto-generated by intutic sync-daemon. DO NOT EDIT.
78
+ # Proxy: ${proxyUrl}
79
+ # Generated: ${newIso()}
80
+ set -euo pipefail
81
+
82
+ EVENT="\${1:-pre}"
83
+ INPUT="\$(cat)"
84
+ TOOL="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || true)"
85
+ TARGET="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); i=d.get('tool_input',{}); print(i.get('path',i.get('file_path','')))" 2>/dev/null || true)"
86
+
87
+ HOOK_EVENTS_LOG=${JSON.stringify(hookEventsLog)}
88
+
89
+ # ── Runtime credentials (sourced from ~/.intutic/env/runtime.env) ────────────────────────
90
+ RUNTIME_ENV="$HOME/.intutic/env/runtime.env"
91
+ INTUITIC_HOST="https://api.intutic.ai"
92
+ INTUITIC_API_KEY=""
93
+ INTUITIC_WORKSPACE_ID=${JSON.stringify(workspaceId)}
94
+ [ -f "$RUNTIME_ENV" ] && source "$RUNTIME_ENV" 2>/dev/null || true
95
+
96
+ log_event() {
97
+ local verdict="\$1" tool="\$2" reason="\$3"
98
+ local ts; ts="\$(date -u +%Y-%m-%dT%H:%M:%SZ)"
99
+ local incident_id; incident_id="\$(printf '%s' "\${ts}\${tool}\${INTUTIC_WORKSPACE_ID}" | sha1sum 2>/dev/null | cut -c1-16 || echo \"\$(date +%s)\")"
100
+ local entry="{\"event\":\"\${verdict}\",\"toolName\":\"\${tool}\",\"reason\":\"\${reason}\",\"workspaceId\":\"\${INTUTIC_WORKSPACE_ID}\",\"harnessType\":\"goose\",\"timestamp\":\"\${ts}\",\"incidentId\":\"\${incident_id}\"}"
101
+ # Path B: reliable file append
102
+ printf '%s\\n' "\$entry" >> "\$HOOK_EVENTS_LOG" 2>/dev/null || true
103
+ # Path A: fire-and-forget HTTP (non-blocking, near-real-time)
104
+ if [ -n "\$INTUTIC_API_KEY" ]; then
105
+ curl -s -o /dev/null --max-time 3 -X POST \\
106
+ -H "Content-Type: application/json" \\
107
+ -H "Authorization: Bearer \$INTUTIC_API_KEY" \\
108
+ -d "{\"events\":[\$entry]}" \\
109
+ "\${INTUTIC_HOST}/api/v1/hook-events" & disown 2>/dev/null || true
110
+ fi
111
+ }
112
+
113
+ PROTECTED_PATHS=(
114
+ ".agents/plugins/intutic-governance"
115
+ ".intutic/hooks"
116
+ ".intutic/integrity.json"
117
+ ".claude/settings.json"
118
+ ".cursor/hooks.json"
119
+ ".cline/hooks"
120
+ ".openhands/hooks.json"
121
+ )
122
+
123
+ for p in "\${PROTECTED_PATHS[@]}"; do
124
+ if echo "$TARGET" | grep -qF "$p"; then
125
+ REASON="Attempt to modify governance-protected path \\"$TARGET\\""
126
+ echo "[Intutic Governance] BLOCKED: $REASON" >&2
127
+ log_event "tool_blocked" "\$TOOL" "\$REASON"
128
+ exit 2
129
+ fi
130
+ done
131
+
132
+ log_event "tool_allowed" "\$TOOL" ""
133
+ exit 0
134
+ `;
135
+ const tmpScript = checkScriptPath + '.intutic-tmp';
136
+ await fs.writeFile(tmpScript, checkScript, 'utf-8');
137
+ await fs.rename(tmpScript, checkScriptPath);
138
+ await fs.chmod(checkScriptPath, 0o755);
139
+ // Apply immutable flags after writing
140
+ await hardenGoosePlugin(PLUGIN_DIR);
141
+ log.info({ action: 'goose_plugin_written', pluginDir: PLUGIN_DIR }, 'Goose governance plugin written and hardened');
142
+ // ── Merge proxy URL into goose config.yaml ───────────────────────────
143
+ await mergeGooseConfig(proxyUrl);
144
+ }
145
+ /**
146
+ * Merge provider host URL into ~/.config/goose/config.yaml.
147
+ */
148
+ async function mergeGooseConfig(proxyUrl) {
149
+ let existing = '';
150
+ try {
151
+ existing = await fs.readFile(GOOSE_CONFIG, 'utf-8');
152
+ }
153
+ catch {
154
+ existing = '';
155
+ }
156
+ // Inject or update provider.host
157
+ // Goose config.yaml format: provider.host or OPENAI_HOST env var
158
+ if (existing.includes('provider:')) {
159
+ if (existing.includes(' host:')) {
160
+ existing = existing.replace(/^\s*host:\s*.*/m, ` host: "${proxyUrl}"`);
161
+ }
162
+ else {
163
+ existing = existing.replace(/^provider:/m, `provider:\n host: "${proxyUrl}"`);
164
+ }
165
+ }
166
+ else {
167
+ existing += `\nprovider:\n host: "${proxyUrl}"\n`;
168
+ }
169
+ const tmpConfig = GOOSE_CONFIG + '.intutic-tmp';
170
+ await fs.mkdir(path.dirname(GOOSE_CONFIG), { recursive: true });
171
+ await fs.writeFile(tmpConfig, existing, 'utf-8');
172
+ await fs.rename(tmpConfig, GOOSE_CONFIG);
173
+ log.info({ action: 'goose_config_written', path: GOOSE_CONFIG }, 'Goose config.yaml updated with proxy host');
174
+ }
175
+ //# sourceMappingURL=gooseHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gooseHooks.js","sourceRoot":"","sources":["../../src/harness/gooseHooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * gooseHooksWriter.ts — Goose pre_tool_use native hook writer.
3
+ *
4
+ * Writes two artefacts:
5
+ * 1. `~/.intutic/hooks/goose-pre-tool-use.sh` — bash gate script that
6
+ * sources runtime credentials, POSTs to the Intutic hook-gate endpoint,
7
+ * and exits 2 (block) or 0 (allow).
8
+ * 2. `~/.config/goose/config.yaml` — adds `hooks.pre_tool_use` key if it
9
+ * is not already present (string operations only; no yaml dep needed).
10
+ *
11
+ * Fail-open semantics: if curl errors the script exits 0 so Goose continues.
12
+ * On block, a JSONL event line is also appended to
13
+ * `~/.intutic/events/hook-events.jsonl` for local audit.
14
+ *
15
+ * LLD #14 — Phase 4 WS-4C native hook script writers
16
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
17
+ *
18
+ * @module
19
+ */
20
+ /**
21
+ * Write the Goose pre_tool_use hook script and register it in config.yaml.
22
+ *
23
+ * @param workspaceRoot - Absolute path to the workspace root (unused directly;
24
+ * included for API symmetry with other hook writers).
25
+ */
26
+ export declare function writeGooseHooks(workspaceRoot: string): Promise<void>;
27
+ //# sourceMappingURL=gooseHooksWriter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gooseHooksWriter.d.ts","sourceRoot":"","sources":["../../src/harness/gooseHooksWriter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAwHH;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB1E"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * gooseHooksWriter.ts — Goose pre_tool_use native hook writer.
3
+ *
4
+ * Writes two artefacts:
5
+ * 1. `~/.intutic/hooks/goose-pre-tool-use.sh` — bash gate script that
6
+ * sources runtime credentials, POSTs to the Intutic hook-gate endpoint,
7
+ * and exits 2 (block) or 0 (allow).
8
+ * 2. `~/.config/goose/config.yaml` — adds `hooks.pre_tool_use` key if it
9
+ * is not already present (string operations only; no yaml dep needed).
10
+ *
11
+ * Fail-open semantics: if curl errors the script exits 0 so Goose continues.
12
+ * On block, a JSONL event line is also appended to
13
+ * `~/.intutic/events/hook-events.jsonl` for local audit.
14
+ *
15
+ * LLD #14 — Phase 4 WS-4C native hook script writers
16
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
17
+ *
18
+ * @module
19
+ */
20
+ import * as node_fs from 'node:fs/promises';
21
+ import * as node_path from 'node:path';
22
+ import * as node_os from 'node:os';
23
+ import { createLogger } from '@intutic/logger';
24
+ const log = createLogger('sync-goose-hooks-writer');
25
+ const HOOKS_DIR = node_path.join(node_os.homedir(), '.intutic', 'hooks');
26
+ const EVENTS_DIR = node_path.join(node_os.homedir(), '.intutic', 'events');
27
+ const HOOK_SCRIPT = node_path.join(HOOKS_DIR, 'goose-pre-tool-use.sh');
28
+ const HOOK_EVENTS_LOG = node_path.join(EVENTS_DIR, 'hook-events.jsonl');
29
+ const GOOSE_CONFIG = node_path.join(node_os.homedir(), '.config', 'goose', 'config.yaml');
30
+ /**
31
+ * Build the bash gate script content.
32
+ *
33
+ * Logic:
34
+ * 1. Source ~/.intutic/env/runtime.env for credentials.
35
+ * 2. Read full stdin JSON payload.
36
+ * 3. POST to $INTUTIC_CONTROL_PLANE_URL/api/v1/hook-gate (10 s timeout).
37
+ * 4. If curl fails → fail-open (exit 0).
38
+ * 5. If response body contains `"allowed":false` → append JSONL event, exit 2.
39
+ * 6. Otherwise → exit 0.
40
+ */
41
+ function buildGooseHookScript() {
42
+ return `#!/usr/bin/env bash
43
+ # Intutic Goose pre_tool_use governance gate.
44
+ # Auto-generated by intutic sync-daemon. DO NOT EDIT.
45
+ set -euo pipefail
46
+
47
+ RUNTIME_ENV="$HOME/.intutic/env/runtime.env"
48
+ HOOK_EVENTS_LOG=${JSON.stringify(HOOK_EVENTS_LOG)}
49
+ INTUTIC_CONTROL_PLANE_URL="https://api.intutic.ai"
50
+ INTUTIC_API_KEY=""
51
+ INTUTIC_WORKSPACE_ID=""
52
+
53
+ # Source runtime credentials (key rotation is automatic on next tool call)
54
+ [ -f "$RUNTIME_ENV" ] && source "$RUNTIME_ENV" 2>/dev/null || true
55
+
56
+ INPUT="$(cat)"
57
+ TOOL_NAME="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || true)"
58
+
59
+ # ── POST to hook-gate ─────────────────────────────────────────────────
60
+ GATE_URL="\${INTUTIC_CONTROL_PLANE_URL}/api/v1/hook-gate"
61
+ RESPONSE=""
62
+ if [ -n "$INTUTIC_API_KEY" ]; then
63
+ RESPONSE="$(curl -s --max-time 10 -X POST \\
64
+ -H "Content-Type: application/json" \\
65
+ -H "Authorization: Bearer $INTUTIC_API_KEY" \\
66
+ -d "$INPUT" \\
67
+ "$GATE_URL" 2>/dev/null)" || true
68
+ fi
69
+
70
+ # Fail-open: empty response (curl error / no key) → allow
71
+ if [ -z "$RESPONSE" ]; then
72
+ exit 0
73
+ fi
74
+
75
+ # Check decision
76
+ ALLOWED="$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(str(d.get('allowed', True)).lower())" 2>/dev/null || echo "true")"
77
+
78
+ if [ "$ALLOWED" = "false" ]; then
79
+ REASON="$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('reason',''))" 2>/dev/null || true)"
80
+ TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
81
+ INCIDENT_ID="$(printf '%s' "\${TS}\${TOOL_NAME}\${INTUTIC_WORKSPACE_ID}" | sha1sum 2>/dev/null | cut -c1-16 || echo "$(date +%s)")"
82
+ ENTRY="{\\"event\\":\\"tool_blocked\\",\\"toolName\\":\\"\${TOOL_NAME}\\",\\"reason\\":\\"\${REASON}\\",\\"workspaceId\\":\\"\${INTUTIC_WORKSPACE_ID}\\",\\"harnessType\\":\\"goose\\",\\"timestamp\\":\\"\${TS}\\",\\"incidentId\\":\\"\${INCIDENT_ID}\\"}"
83
+ mkdir -p "$(dirname "$HOOK_EVENTS_LOG")"
84
+ printf '%s\\n' "$ENTRY" >> "$HOOK_EVENTS_LOG" 2>/dev/null || true
85
+ echo "[Intutic Governance] BLOCKED: $REASON" >&2
86
+ exit 2
87
+ fi
88
+
89
+ exit 0
90
+ `;
91
+ }
92
+ /**
93
+ * Merge `hooks.pre_tool_use` into Goose config.yaml using string operations.
94
+ *
95
+ * Reads the existing file (if any), checks if `pre_tool_use:` is already
96
+ * present under a `hooks:` section, and adds it only if absent.
97
+ * Creates the file (with a minimal `hooks:` block) if it does not exist.
98
+ */
99
+ async function mergeGooseConfig() {
100
+ let existing = '';
101
+ try {
102
+ existing = await node_fs.readFile(GOOSE_CONFIG, 'utf-8');
103
+ }
104
+ catch {
105
+ existing = '';
106
+ }
107
+ const hookLine = ` pre_tool_use: ${HOOK_SCRIPT}`;
108
+ // Already registered — nothing to do
109
+ if (existing.includes('pre_tool_use:') && existing.includes(HOOK_SCRIPT)) {
110
+ log.debug({ action: 'goose_config_noop', path: GOOSE_CONFIG }, 'Goose config already contains pre_tool_use hook');
111
+ return;
112
+ }
113
+ if (existing.includes('hooks:')) {
114
+ // hooks: block exists — insert our line after the `hooks:` line
115
+ existing = existing.replace(/^hooks:/m, `hooks:\n${hookLine}`);
116
+ }
117
+ else {
118
+ // No hooks: block — append
119
+ existing = existing.trimEnd() + `\nhooks:\n${hookLine}\n`;
120
+ }
121
+ await node_fs.mkdir(node_path.dirname(GOOSE_CONFIG), { recursive: true });
122
+ const tmp = GOOSE_CONFIG + '.intutic-tmp';
123
+ await node_fs.writeFile(tmp, existing, 'utf-8');
124
+ await node_fs.rename(tmp, GOOSE_CONFIG);
125
+ log.info({ action: 'goose_config_written', path: GOOSE_CONFIG }, 'Goose config.yaml updated with pre_tool_use hook');
126
+ }
127
+ /**
128
+ * Write the Goose pre_tool_use hook script and register it in config.yaml.
129
+ *
130
+ * @param workspaceRoot - Absolute path to the workspace root (unused directly;
131
+ * included for API symmetry with other hook writers).
132
+ */
133
+ export async function writeGooseHooks(workspaceRoot) {
134
+ // Ensure target directories exist
135
+ await node_fs.mkdir(HOOKS_DIR, { recursive: true });
136
+ await node_fs.mkdir(EVENTS_DIR, { recursive: true });
137
+ // Write hook script atomically
138
+ const tmp = HOOK_SCRIPT + '.intutic-tmp';
139
+ await node_fs.writeFile(tmp, buildGooseHookScript(), 'utf-8');
140
+ await node_fs.rename(tmp, HOOK_SCRIPT);
141
+ await node_fs.chmod(HOOK_SCRIPT, 0o755);
142
+ log.info({ action: 'goose_hook_script_written', path: HOOK_SCRIPT }, 'Goose pre_tool_use hook script written');
143
+ // Register in config.yaml
144
+ await mergeGooseConfig();
145
+ log.info({ action: 'goose_hooks_done', workspaceRoot }, 'Goose native hooks written successfully');
146
+ }
147
+ //# sourceMappingURL=gooseHooksWriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gooseHooksWriter.js","sourceRoot":"","sources":["../../src/harness/gooseHooksWriter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * hermesHooks.ts — Hermes AI agent governance hook injection.
3
+ *
4
+ * Hermes is a skills-based AI agent that reads `~/.hermes/config.yaml`
5
+ * for configuration. Hook commands are registered under `hooks.preToolUse`.
6
+ * This module:
7
+ *
8
+ * 1. YAML-safe-merges `~/.hermes/config.yaml` to register the hook command.
9
+ * 2. Writes `~/.intutic/hooks/hermes-check.sh` — dual-path bash governance hook.
10
+ * 3. Writes `~/.hermes/skills/intutic-governance/SKILL.md` — governance skill.
11
+ * 4. Writes `.intutic/env/hermes.env` — env snippet for IDE/shell integration.
12
+ *
13
+ * WS-C1 — Hermes harness (Phase 3 cross-harness defence)
14
+ * LLD #14 — Phase 3 cross-harness defence (Gap 3)
15
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
16
+ *
17
+ * @module
18
+ */
19
+ /**
20
+ * Write the Hermes governance hook script, merge config, and write the
21
+ * governance skill for the Hermes AI agent.
22
+ *
23
+ * Safe to call repeatedly — uses atomic rename so partial writes never
24
+ * leave a corrupt state.
25
+ *
26
+ * @param workspaceRoot - Absolute workspace root path.
27
+ * @param proxyUrl - Intutic proxy URL written into hook headers / skill.
28
+ * @param workspaceId - Workspace ID embedded in every hook event payload.
29
+ */
30
+ export declare function writeHermesHooks(workspaceRoot: string, proxyUrl: string, workspaceId?: string): Promise<void>;
31
+ //# sourceMappingURL=hermesHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hermesHooks.d.ts","sourceRoot":"","sources":["../../src/harness/hermesHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiMH;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,SAAK,GACf,OAAO,CAAC,IAAI,CAAC,CAiEf"}
@@ -0,0 +1,245 @@
1
+ /**
2
+ * hermesHooks.ts — Hermes AI agent governance hook injection.
3
+ *
4
+ * Hermes is a skills-based AI agent that reads `~/.hermes/config.yaml`
5
+ * for configuration. Hook commands are registered under `hooks.preToolUse`.
6
+ * This module:
7
+ *
8
+ * 1. YAML-safe-merges `~/.hermes/config.yaml` to register the hook command.
9
+ * 2. Writes `~/.intutic/hooks/hermes-check.sh` — dual-path bash governance hook.
10
+ * 3. Writes `~/.hermes/skills/intutic-governance/SKILL.md` — governance skill.
11
+ * 4. Writes `.intutic/env/hermes.env` — env snippet for IDE/shell integration.
12
+ *
13
+ * WS-C1 — Hermes harness (Phase 3 cross-harness defence)
14
+ * LLD #14 — Phase 3 cross-harness defence (Gap 3)
15
+ * HLD §3.14 — Three-Tier Defense Cascade (Tier 1 Native Gating)
16
+ *
17
+ * @module
18
+ */
19
+ import * as fs from 'node:fs/promises';
20
+ import * as path from 'node:path';
21
+ import * as os from 'node:os';
22
+ import { createLogger } from '@intutic/logger';
23
+ import { newIso } from '@intutic/id';
24
+ const log = createLogger('sync-hermes-hooks');
25
+ /** Path to the Hermes global config file. */
26
+ const HERMES_CONFIG = path.join(os.homedir(), '.hermes', 'config.yaml');
27
+ /** Governance-sensitive paths that the hook gate protects. */
28
+ const PROTECTED_PATHS = [
29
+ '.gemini/settings.json',
30
+ '.intutic/hooks',
31
+ '.intutic/integrity.json',
32
+ '.intutic/events',
33
+ '.claude/settings.json',
34
+ '.cursor/hooks.json',
35
+ '.codeium/windsurf/hooks.json',
36
+ '.cline/hooks',
37
+ '.agents/plugins/intutic-governance',
38
+ '.openhands/hooks.json',
39
+ '.hermes/config.yaml',
40
+ '.hermes/skills/intutic-governance',
41
+ ];
42
+ // ─── Bash hook script template ────────────────────────────────────────────────
43
+ function buildHermesCheckScript(proxyUrl, workspaceRoot, workspaceId) {
44
+ const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'hook-events.jsonl');
45
+ const runtimeEnv = path.join(os.homedir(), '.intutic', 'env', 'runtime.env');
46
+ return `#!/usr/bin/env bash
47
+ # Intutic Hermes PreToolUse governance gate.
48
+ # Auto-generated by intutic sync-daemon. DO NOT EDIT.
49
+ # Proxy: ${proxyUrl}
50
+ # Generated: ${newIso()}
51
+ # Workspace: ${workspaceId}
52
+ set -euo pipefail
53
+
54
+ # Source runtime credentials (never embedded in this file)
55
+ if [ -f "${runtimeEnv}" ]; then
56
+ # shellcheck disable=SC1090
57
+ source "${runtimeEnv}"
58
+ fi
59
+
60
+ INTUTIC_HOST="\${INTUTIC_HOST:-https://api.intutic.ai}"
61
+ INTUTIC_API_KEY="\${INTUTIC_API_KEY:-}"
62
+ INTUTIC_WORKSPACE_ID="\${INTUTIC_WORKSPACE_ID:-${workspaceId}}"
63
+
64
+ INPUT="$(cat)"
65
+ TOOL_NAME="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" 2>/dev/null || true)"
66
+ TARGET_PATH="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); i=d.get('tool_input',{}); print(i.get('path',i.get('file_path','')))" 2>/dev/null || true)"
67
+ COMMAND="$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); i=d.get('tool_input',{}); print(i.get('command',i.get('cmd','')))" 2>/dev/null || true)"
68
+
69
+ HOOK_EVENTS_LOG=${JSON.stringify(hookEventsLog)}
70
+
71
+ log_event() {
72
+ local verdict="\$1" tool="\$2" reason="\$3"
73
+ local ts; ts="\$(date -u +%Y-%m-%dT%H:%M:%SZ)"
74
+ # incidentId = sha1(timestamp + toolName + workspaceId).slice(0,16)
75
+ local incident_id; incident_id="\$(echo -n \"\${ts}\${tool}\${INTUTIC_WORKSPACE_ID}\" | sha1sum | cut -c1-16)"
76
+ local entry="{\\\"event\\\":\\\"toolGovernance\\\",\\\"verdict\\\":\\\"\${verdict}\\\",\\\"toolName\\\":\\\"\${tool}\\\",\\\"reason\\\":\\\"\${reason}\\\",\\\"workspaceId\\\":\\\"\${INTUTIC_WORKSPACE_ID}\\\",\\\"harnessType\\\":\\\"hermes\\\",\\\"incidentId\\\":\\\"\${incident_id}\\\",\\\"timestamp\\\":\\\"\${ts}\\\"}"
77
+ # Path B: reliable file append (sync-daemon drains on FSEvents change)
78
+ printf '%s\\n' "\$entry" >> "\$HOOK_EVENTS_LOG" 2>/dev/null || true
79
+ # Path A: fire-and-forget HTTP POST (near-real-time dashboard, non-blocking)
80
+ if [ -n "\$INTUTIC_API_KEY" ]; then
81
+ curl -sf -X POST "\${INTUTIC_HOST}/api/v1/hook-events" \\
82
+ -H "Content-Type: application/json" \\
83
+ -H "Authorization: Bearer \${INTUTIC_API_KEY}" \\
84
+ -d "{\\\"events\\\":[\$entry]}" \\
85
+ --max-time 5 >/dev/null 2>&1 &
86
+ fi
87
+ }
88
+
89
+ PROTECTED_PATHS=(
90
+ ${PROTECTED_PATHS.map((p) => ` "${p}"`).join('\n')}
91
+ )
92
+
93
+ # 1. Protected-path guard
94
+ for p in "\${PROTECTED_PATHS[@]}"; do
95
+ if echo "$TARGET_PATH" | grep -qF "$p"; then
96
+ REASON="Attempt to modify governance-protected path \\"$TARGET_PATH\\""
97
+ echo "[Intutic Governance] BLOCKED: $REASON" >&2
98
+ log_event "tool_blocked" "$TOOL_NAME" "$REASON"
99
+ exit 2
100
+ fi
101
+ done
102
+
103
+ # 2. Bash/shell command guard — block governance bypass patterns
104
+ if [ -n "$COMMAND" ]; then
105
+ BYPASS_PATTERNS=(
106
+ "chflags nouchg"
107
+ "chattr -i"
108
+ "rm -rf .intutic"
109
+ "rm -rf .hermes/config"
110
+ "HERMES_PRE_TOOL_HOOK="
111
+ "rm -rf .gemini/settings"
112
+ )
113
+ for pattern in "\${BYPASS_PATTERNS[@]}"; do
114
+ if echo "$COMMAND" | grep -qF "$pattern"; then
115
+ REASON="Shell command matches governance bypass pattern: \\"$pattern\\""
116
+ echo "[Intutic Governance] BLOCKED: $REASON" >&2
117
+ log_event "tool_blocked" "$TOOL_NAME" "$REASON"
118
+ exit 2
119
+ fi
120
+ done
121
+ fi
122
+
123
+ log_event "tool_allowed" "$TOOL_NAME" ""
124
+ exit 0
125
+ `;
126
+ }
127
+ // ─── YAML merge helpers ───────────────────────────────────────────────────────
128
+ /**
129
+ * YAML-safe merge of the Hermes config.
130
+ *
131
+ * Strategy (no YAML library — pure string manipulation):
132
+ * - If file doesn't exist: write fresh with just the hooks block.
133
+ * - If file exists and already contains `preToolUse:`: replace the command line.
134
+ * - If file exists but has no `preToolUse:`: append the hooks block.
135
+ * - If file has `hooks:` but no `preToolUse:`: insert inside existing hooks section.
136
+ */
137
+ async function mergeHermesConfig(hookScriptPath) {
138
+ const hooksBlock = [
139
+ 'hooks:',
140
+ ' preToolUse:',
141
+ ` command: ${hookScriptPath}`,
142
+ ].join('\n');
143
+ let existing = null;
144
+ try {
145
+ existing = await fs.readFile(HERMES_CONFIG, 'utf-8');
146
+ }
147
+ catch {
148
+ // File doesn't exist — write fresh
149
+ }
150
+ let merged;
151
+ if (existing === null) {
152
+ // Fresh write
153
+ merged = hooksBlock + '\n';
154
+ }
155
+ else if (/^\s*preToolUse:/m.test(existing)) {
156
+ // Replace the existing command line under preToolUse:
157
+ merged = existing.replace(/^(\s*command:\s*).*$/m, `$1${hookScriptPath}`);
158
+ }
159
+ else if (/^hooks:/m.test(existing)) {
160
+ // hooks: block exists but no preToolUse: — insert inside hooks section
161
+ merged = existing.replace(/^(hooks:\s*)$/m, `$1\n preToolUse:\n command: ${hookScriptPath}`);
162
+ }
163
+ else {
164
+ // No hooks: block at all — append
165
+ merged = existing.trimEnd() + '\n\n' + hooksBlock + '\n';
166
+ }
167
+ const tmpPath = HERMES_CONFIG + '.intutic-tmp';
168
+ await fs.writeFile(tmpPath, merged, 'utf-8');
169
+ await fs.rename(tmpPath, HERMES_CONFIG);
170
+ }
171
+ // ─── Governance skill template ────────────────────────────────────────────────
172
+ function buildGovernanceSkill(proxyUrl) {
173
+ return `---
174
+ name: intutic-governance
175
+ description: Intutic governance awareness. Always check compliance before tool calls.
176
+ triggers:
177
+ - before any tool call
178
+ - before any file write
179
+ - before any shell command
180
+ ---
181
+
182
+ # Intutic Governance Policy
183
+
184
+ Before every tool call, you MUST verify compliance with workspace SOPs.
185
+ A pre-tool-use hook (\`~/.intutic/hooks/hermes-check.sh\`) enforces these rules automatically.
186
+
187
+ If a tool call is blocked, do NOT attempt to circumvent the governance gate.
188
+ To update policy, use the Intutic control plane at \`${proxyUrl}\`.
189
+ `;
190
+ }
191
+ // ─── Main export ──────────────────────────────────────────────────────────────
192
+ /**
193
+ * Write the Hermes governance hook script, merge config, and write the
194
+ * governance skill for the Hermes AI agent.
195
+ *
196
+ * Safe to call repeatedly — uses atomic rename so partial writes never
197
+ * leave a corrupt state.
198
+ *
199
+ * @param workspaceRoot - Absolute workspace root path.
200
+ * @param proxyUrl - Intutic proxy URL written into hook headers / skill.
201
+ * @param workspaceId - Workspace ID embedded in every hook event payload.
202
+ */
203
+ export async function writeHermesHooks(workspaceRoot, proxyUrl, workspaceId = '') {
204
+ // ── 1. Ensure directories ──────────────────────────────────────────────────
205
+ const globalHookDir = path.join(os.homedir(), '.intutic', 'hooks');
206
+ const hookEventsDir = path.join(workspaceRoot, '.intutic', 'events');
207
+ const envDir = path.join(workspaceRoot, '.intutic', 'env');
208
+ const hermesDir = path.join(os.homedir(), '.hermes');
209
+ const skillsDir = path.join(hermesDir, 'skills', 'intutic-governance');
210
+ await Promise.all([
211
+ fs.mkdir(globalHookDir, { recursive: true }),
212
+ fs.mkdir(hookEventsDir, { recursive: true }),
213
+ fs.mkdir(envDir, { recursive: true }),
214
+ fs.mkdir(hermesDir, { recursive: true }),
215
+ fs.mkdir(skillsDir, { recursive: true }),
216
+ ]);
217
+ // ── 2. Write bash hook script ──────────────────────────────────────────────
218
+ const hookScriptPath = path.join(globalHookDir, 'hermes-check.sh');
219
+ const tmpScript = hookScriptPath + '.intutic-tmp';
220
+ await fs.writeFile(tmpScript, buildHermesCheckScript(proxyUrl, workspaceRoot, workspaceId), 'utf-8');
221
+ await fs.rename(tmpScript, hookScriptPath);
222
+ await fs.chmod(hookScriptPath, 0o755);
223
+ log.info({ action: 'hermes_hook_script_written', path: hookScriptPath }, 'Hermes hook script written');
224
+ // ── 3. YAML-safe merge into ~/.hermes/config.yaml ─────────────────────────
225
+ await mergeHermesConfig(hookScriptPath);
226
+ log.info({ action: 'hermes_config_merged', path: HERMES_CONFIG }, 'Merged Intutic hook into ~/.hermes/config.yaml');
227
+ // ── 4. Write governance skill ──────────────────────────────────────────────
228
+ const skillPath = path.join(skillsDir, 'SKILL.md');
229
+ const tmpSkill = skillPath + '.intutic-tmp';
230
+ await fs.writeFile(tmpSkill, buildGovernanceSkill(proxyUrl), 'utf-8');
231
+ await fs.rename(tmpSkill, skillPath);
232
+ log.info({ action: 'hermes_skill_written', path: skillPath }, 'Hermes governance skill written');
233
+ // ── 5. Write env snippet ───────────────────────────────────────────────────
234
+ const envSnippet = [
235
+ `# Intutic Hermes governance env`,
236
+ `INTUITIC_HERMES_HOOK=~/.intutic/hooks/hermes-check.sh`,
237
+ `INTUITIC_HERMES_CONFIG=~/.hermes/config.yaml`,
238
+ ].join('\n') + '\n';
239
+ const envFilePath = path.join(envDir, 'hermes.env');
240
+ const tmpEnv = envFilePath + '.intutic-tmp';
241
+ await fs.writeFile(tmpEnv, envSnippet, 'utf-8');
242
+ await fs.rename(tmpEnv, envFilePath);
243
+ log.info({ action: 'hermes_env_written', path: envFilePath }, 'Hermes env snippet written');
244
+ }
245
+ //# sourceMappingURL=hermesHooks.js.map