@intutic/sync-daemon 1.10.0 → 2.0.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 (200) hide show
  1. package/README.md +2 -2
  2. package/dist/agentReporter.d.ts +205 -0
  3. package/dist/agentReporter.d.ts.map +1 -0
  4. package/dist/agentReporter.js +394 -0
  5. package/dist/agentReporter.js.map +1 -0
  6. package/dist/configReader.d.ts +39 -2
  7. package/dist/configReader.d.ts.map +1 -1
  8. package/dist/configReader.js +56 -2
  9. package/dist/configReader.js.map +1 -1
  10. package/dist/configWriter.d.ts +51 -1
  11. package/dist/configWriter.d.ts.map +1 -1
  12. package/dist/configWriter.js +490 -63
  13. package/dist/configWriter.js.map +1 -1
  14. package/dist/harness/antigravityHooks.d.ts.map +1 -1
  15. package/dist/harness/antigravityHooks.js +33 -55
  16. package/dist/harness/antigravityHooks.js.map +1 -1
  17. package/dist/harness/claudeCodeHooks.d.ts +47 -13
  18. package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
  19. package/dist/harness/claudeCodeHooks.js +316 -54
  20. package/dist/harness/claudeCodeHooks.js.map +1 -1
  21. package/dist/harness/claudeDesktopHooks.d.ts.map +1 -1
  22. package/dist/harness/claudeDesktopHooks.js +35 -48
  23. package/dist/harness/claudeDesktopHooks.js.map +1 -1
  24. package/dist/harness/clineHooks.d.ts.map +1 -1
  25. package/dist/harness/clineHooks.js +42 -34
  26. package/dist/harness/clineHooks.js.map +1 -1
  27. package/dist/harness/codexHooks.d.ts +36 -0
  28. package/dist/harness/codexHooks.d.ts.map +1 -0
  29. package/dist/harness/codexHooks.js +227 -0
  30. package/dist/harness/codexHooks.js.map +1 -0
  31. package/dist/harness/continueHooks.d.ts +45 -5
  32. package/dist/harness/continueHooks.d.ts.map +1 -1
  33. package/dist/harness/continueHooks.js +225 -5
  34. package/dist/harness/continueHooks.js.map +1 -1
  35. package/dist/harness/cursorHooks.d.ts +31 -0
  36. package/dist/harness/cursorHooks.d.ts.map +1 -1
  37. package/dist/harness/cursorHooks.js +78 -49
  38. package/dist/harness/cursorHooks.js.map +1 -1
  39. package/dist/harness/dshHooks.d.ts +198 -0
  40. package/dist/harness/dshHooks.d.ts.map +1 -0
  41. package/dist/harness/dshHooks.js +569 -0
  42. package/dist/harness/dshHooks.js.map +1 -0
  43. package/dist/harness/gateBody.d.ts +422 -0
  44. package/dist/harness/gateBody.d.ts.map +1 -0
  45. package/dist/harness/gateBody.js +1816 -0
  46. package/dist/harness/gateBody.js.map +1 -0
  47. package/dist/harness/gateKind.d.ts +164 -0
  48. package/dist/harness/gateKind.d.ts.map +1 -0
  49. package/dist/harness/gateKind.js +143 -0
  50. package/dist/harness/gateKind.js.map +1 -0
  51. package/dist/harness/githubCopilotHooks.d.ts +54 -0
  52. package/dist/harness/githubCopilotHooks.d.ts.map +1 -0
  53. package/dist/harness/githubCopilotHooks.js +218 -0
  54. package/dist/harness/githubCopilotHooks.js.map +1 -0
  55. package/dist/harness/gooseHooks.d.ts.map +1 -1
  56. package/dist/harness/gooseHooks.js +67 -37
  57. package/dist/harness/gooseHooks.js.map +1 -1
  58. package/dist/harness/grokHooks.d.ts +77 -0
  59. package/dist/harness/grokHooks.d.ts.map +1 -0
  60. package/dist/harness/grokHooks.js +350 -0
  61. package/dist/harness/grokHooks.js.map +1 -0
  62. package/dist/harness/hermesHooks.d.ts.map +1 -1
  63. package/dist/harness/hermesHooks.js +26 -55
  64. package/dist/harness/hermesHooks.js.map +1 -1
  65. package/dist/harness/holdRedaction.d.ts +69 -0
  66. package/dist/harness/holdRedaction.d.ts.map +1 -0
  67. package/dist/harness/holdRedaction.js +211 -0
  68. package/dist/harness/holdRedaction.js.map +1 -0
  69. package/dist/harness/jetbrainsXmlConfig.d.ts +51 -0
  70. package/dist/harness/jetbrainsXmlConfig.d.ts.map +1 -0
  71. package/dist/harness/jetbrainsXmlConfig.js +151 -0
  72. package/dist/harness/jetbrainsXmlConfig.js.map +1 -0
  73. package/dist/harness/mcpAutoWrite.d.ts +100 -3
  74. package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
  75. package/dist/harness/mcpAutoWrite.js +712 -90
  76. package/dist/harness/mcpAutoWrite.js.map +1 -1
  77. package/dist/harness/museHooks.d.ts +53 -0
  78. package/dist/harness/museHooks.d.ts.map +1 -0
  79. package/dist/harness/museHooks.js +273 -0
  80. package/dist/harness/museHooks.js.map +1 -0
  81. package/dist/harness/n8nHooks.d.ts +25 -9
  82. package/dist/harness/n8nHooks.d.ts.map +1 -1
  83. package/dist/harness/n8nHooks.js +174 -9
  84. package/dist/harness/n8nHooks.js.map +1 -1
  85. package/dist/harness/openCodeHooks.d.ts +93 -0
  86. package/dist/harness/openCodeHooks.d.ts.map +1 -0
  87. package/dist/harness/openCodeHooks.js +248 -0
  88. package/dist/harness/openCodeHooks.js.map +1 -0
  89. package/dist/harness/openWebuiHooks.d.ts.map +1 -1
  90. package/dist/harness/openWebuiHooks.js +56 -11
  91. package/dist/harness/openWebuiHooks.js.map +1 -1
  92. package/dist/harness/openclawHooks.d.ts.map +1 -1
  93. package/dist/harness/openclawHooks.js +30 -54
  94. package/dist/harness/openclawHooks.js.map +1 -1
  95. package/dist/harness/openhandsHooks.d.ts.map +1 -1
  96. package/dist/harness/openhandsHooks.js +35 -41
  97. package/dist/harness/openhandsHooks.js.map +1 -1
  98. package/dist/harness/piHooks.d.ts.map +1 -1
  99. package/dist/harness/piHooks.js +31 -58
  100. package/dist/harness/piHooks.js.map +1 -1
  101. package/dist/harness/protectedPaths.d.ts +353 -0
  102. package/dist/harness/protectedPaths.d.ts.map +1 -0
  103. package/dist/harness/protectedPaths.js +805 -0
  104. package/dist/harness/protectedPaths.js.map +1 -0
  105. package/dist/harness/rooCodeHooks.d.ts.map +1 -1
  106. package/dist/harness/rooCodeHooks.js +34 -48
  107. package/dist/harness/rooCodeHooks.js.map +1 -1
  108. package/dist/harness/windsurfHooks.d.ts +72 -0
  109. package/dist/harness/windsurfHooks.d.ts.map +1 -1
  110. package/dist/harness/windsurfHooks.js +172 -58
  111. package/dist/harness/windsurfHooks.js.map +1 -1
  112. package/dist/harness/windsurfJetBrainsProxy.d.ts +78 -0
  113. package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -0
  114. package/dist/harness/windsurfJetBrainsProxy.js +159 -0
  115. package/dist/harness/windsurfJetBrainsProxy.js.map +1 -0
  116. package/dist/hashReporter.js +1 -1
  117. package/dist/hashReporter.js.map +1 -1
  118. package/dist/index.d.ts +12 -6
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +14 -6
  121. package/dist/index.js.map +1 -1
  122. package/dist/integrityStore.d.ts +0 -8
  123. package/dist/integrityStore.d.ts.map +1 -1
  124. package/dist/integrityStore.js +6 -29
  125. package/dist/integrityStore.js.map +1 -1
  126. package/dist/lib/approvedBypasses.d.ts +76 -0
  127. package/dist/lib/approvedBypasses.d.ts.map +1 -0
  128. package/dist/lib/approvedBypasses.js +140 -0
  129. package/dist/lib/approvedBypasses.js.map +1 -0
  130. package/dist/lib/complianceProbes.d.ts.map +1 -1
  131. package/dist/lib/complianceProbes.js +5 -0
  132. package/dist/lib/complianceProbes.js.map +1 -1
  133. package/dist/lib/decisionsDigest.d.ts +99 -0
  134. package/dist/lib/decisionsDigest.d.ts.map +1 -0
  135. package/dist/lib/decisionsDigest.js +199 -0
  136. package/dist/lib/decisionsDigest.js.map +1 -0
  137. package/dist/lib/egressPolicy.d.ts +69 -0
  138. package/dist/lib/egressPolicy.d.ts.map +1 -0
  139. package/dist/lib/egressPolicy.js +126 -0
  140. package/dist/lib/egressPolicy.js.map +1 -0
  141. package/dist/lib/gitWorktrees.d.ts +140 -0
  142. package/dist/lib/gitWorktrees.d.ts.map +1 -0
  143. package/dist/lib/gitWorktrees.js +191 -0
  144. package/dist/lib/gitWorktrees.js.map +1 -0
  145. package/dist/lib/policySnapshot.d.ts +224 -0
  146. package/dist/lib/policySnapshot.d.ts.map +1 -0
  147. package/dist/lib/policySnapshot.js +637 -0
  148. package/dist/lib/policySnapshot.js.map +1 -0
  149. package/dist/lib/processPoller.d.ts +44 -0
  150. package/dist/lib/processPoller.d.ts.map +1 -1
  151. package/dist/lib/processPoller.js +176 -1
  152. package/dist/lib/processPoller.js.map +1 -1
  153. package/dist/lib/runtimeEnv.d.ts +8 -0
  154. package/dist/lib/runtimeEnv.d.ts.map +1 -1
  155. package/dist/lib/runtimeEnv.js +3 -0
  156. package/dist/lib/runtimeEnv.js.map +1 -1
  157. package/dist/localTrajectoryHeuristics.d.ts +51 -0
  158. package/dist/localTrajectoryHeuristics.d.ts.map +1 -0
  159. package/dist/localTrajectoryHeuristics.js +90 -0
  160. package/dist/localTrajectoryHeuristics.js.map +1 -0
  161. package/dist/sessionReporter.d.ts +70 -0
  162. package/dist/sessionReporter.d.ts.map +1 -0
  163. package/dist/sessionReporter.js +140 -0
  164. package/dist/sessionReporter.js.map +1 -0
  165. package/dist/skillWriter.d.ts +1 -1
  166. package/dist/skillWriter.d.ts.map +1 -1
  167. package/dist/skillWriter.js +5 -2
  168. package/dist/skillWriter.js.map +1 -1
  169. package/dist/syncLoop.d.ts +105 -3
  170. package/dist/syncLoop.d.ts.map +1 -1
  171. package/dist/syncLoop.js +397 -65
  172. package/dist/syncLoop.js.map +1 -1
  173. package/dist/trajectoryMonitor.d.ts +2 -0
  174. package/dist/trajectoryMonitor.d.ts.map +1 -1
  175. package/dist/trajectoryMonitor.js +57 -0
  176. package/dist/trajectoryMonitor.js.map +1 -1
  177. package/dist/watcher/driftWatcher.d.ts.map +1 -1
  178. package/dist/watcher/driftWatcher.js +12 -3
  179. package/dist/watcher/driftWatcher.js.map +1 -1
  180. package/dist/watcher/settingsGuard.d.ts +23 -0
  181. package/dist/watcher/settingsGuard.d.ts.map +1 -1
  182. package/dist/watcher/settingsGuard.js +202 -17
  183. package/dist/watcher/settingsGuard.js.map +1 -1
  184. package/package.json +37 -6
  185. package/dist/brainIndexer.d.ts +0 -51
  186. package/dist/brainIndexer.d.ts.map +0 -1
  187. package/dist/brainIndexer.js +0 -148
  188. package/dist/brainIndexer.js.map +0 -1
  189. package/dist/harness/cursorHooksJson.d.ts +0 -29
  190. package/dist/harness/cursorHooksJson.d.ts.map +0 -1
  191. package/dist/harness/cursorHooksJson.js +0 -130
  192. package/dist/harness/cursorHooksJson.js.map +0 -1
  193. package/dist/harness/gooseHooksWriter.d.ts +0 -27
  194. package/dist/harness/gooseHooksWriter.d.ts.map +0 -1
  195. package/dist/harness/gooseHooksWriter.js +0 -147
  196. package/dist/harness/gooseHooksWriter.js.map +0 -1
  197. package/dist/harness/openhandsHooksWriter.d.ts +0 -23
  198. package/dist/harness/openhandsHooksWriter.d.ts.map +0 -1
  199. package/dist/harness/openhandsHooksWriter.js +0 -146
  200. package/dist/harness/openhandsHooksWriter.js.map +0 -1
@@ -21,24 +21,11 @@ import * as path from 'node:path';
21
21
  import * as os from 'node:os';
22
22
  import { createLogger } from '@intutic/logger';
23
23
  import { newIso } from '@intutic/id';
24
+ import { emitShellGate, SHELL_EXTRACT, SHELL_FAIL_CLOSED } from './gateBody.js';
24
25
  const log = createLogger('sync-hermes-hooks');
25
26
  /** Path to the Hermes global config file. */
26
27
  const HERMES_CONFIG = path.join(os.homedir(), '.hermes', 'config.yaml');
27
28
  /** 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
29
  // ─── Bash hook script template ────────────────────────────────────────────────
43
30
  function buildHermesCheckScript(proxyUrl, workspaceRoot, workspaceId) {
44
31
  const hookEventsLog = path.join(workspaceRoot, '.intutic', 'events', 'hook-events.jsonl');
@@ -50,7 +37,7 @@ function buildHermesCheckScript(proxyUrl, workspaceRoot, workspaceId) {
50
37
  # Generated: ${newIso()}
51
38
  # Workspace: ${workspaceId}
52
39
  set -euo pipefail
53
-
40
+ ${SHELL_FAIL_CLOSED}
54
41
  # Source runtime credentials (never embedded in this file)
55
42
  if [ -f "${runtimeEnv}" ]; then
56
43
  # shellcheck disable=SC1090
@@ -62,18 +49,35 @@ INTUTIC_API_KEY="\${INTUTIC_API_KEY:-}"
62
49
  INTUTIC_WORKSPACE_ID="\${INTUTIC_WORKSPACE_ID:-${workspaceId}}"
63
50
 
64
51
  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)"
52
+ ${SHELL_EXTRACT}
68
53
 
69
54
  HOOK_EVENTS_LOG=${JSON.stringify(hookEventsLog)}
70
55
 
71
56
  log_event() {
72
57
  local verdict="\$1" tool="\$2" reason="\$3"
58
+ # \`tool\` and \`reason\` carry agent-controlled text — the tool name, and the
59
+ # target path quoted into the block reason — and both are spliced into the
60
+ # JSON string below. Escape first: one unescaped quote yields a line
61
+ # JSON.parse rejects, and drainHookEvents drops malformed lines then wipes the
62
+ # log on a successful drain, so the record of a block is destroyed rather than
63
+ # delayed. A crafted path could otherwise close the string and forge fields.
64
+ tool="\${tool//\\\\/\\\\\\\\}"; tool="\${tool//\\"/\\\\\\"}"
65
+ reason="\${reason//\\\\/\\\\\\\\}"; reason="\${reason//\\"/\\\\\\"}"
66
+ # Every C0 control character, not just newline and carriage return. JSON
67
+ # forbids all of U+0000-U+001F unescaped, so a plain TAB in a filename — no
68
+ # adversary required — was still enough to produce a line JSON.parse rejects.
69
+ # (NUL cannot appear in a bash variable, so the range starts at \\x01.)
70
+ tool="\${tool//[\$'\\x01'-\$'\\x1f']/ }"; reason="\${reason//[\$'\\x01'-\$'\\x1f']/ }"
71
+ # The workspace id is spliced into the same JSON string. It comes from
72
+ # runtime.env on disk rather than from the agent, so it is lower risk — but a
73
+ # value with a quote in it breaks every audit line the same way, and "lower
74
+ # risk" is not "cannot happen" for a file the operator edits by hand.
75
+ local ws="\${INTUTIC_WORKSPACE_ID}"
76
+ ws="\${ws//\\\\/\\\\\\\\}"; ws="\${ws//\\"/\\\\\\"}"; ws="\${ws//[\$'\\x01'-\$'\\x1f']/ }"
73
77
  local ts; ts="\$(date -u +%Y-%m-%dT%H:%M:%SZ)"
74
78
  # incidentId = sha1(timestamp + toolName + workspaceId).slice(0,16)
75
79
  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}\\\"}"
80
+ local entry="{\\\"event\\\":\\\"\${verdict}\\\",\\\"toolName\\\":\\\"\${tool}\\\",\\\"reason\\\":\\\"\${reason}\\\",\\\"workspaceId\\\":\\\"\${INTUTIC_WORKSPACE_ID}\\\",\\\"harnessType\\\":\\\"hermes\\\",\\\"incidentId\\\":\\\"\${incident_id}\\\",\\\"timestamp\\\":\\\"\${ts}\\\"}"
77
81
  # Path B: reliable file append (sync-daemon drains on FSEvents change)
78
82
  printf '%s\\n' "\$entry" >> "\$HOOK_EVENTS_LOG" 2>/dev/null || true
79
83
  # Path A: fire-and-forget HTTP POST (near-real-time dashboard, non-blocking)
@@ -86,40 +90,7 @@ log_event() {
86
90
  fi
87
91
  }
88
92
 
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
-
93
+ ${emitShellGate({ harness: "hermes" })}
123
94
  log_event "tool_allowed" "$TOOL_NAME" ""
124
95
  exit 0
125
96
  `;
@@ -233,8 +204,8 @@ export async function writeHermesHooks(workspaceRoot, proxyUrl, workspaceId = ''
233
204
  // ── 5. Write env snippet ───────────────────────────────────────────────────
234
205
  const envSnippet = [
235
206
  `# Intutic Hermes governance env`,
236
- `INTUITIC_HERMES_HOOK=~/.intutic/hooks/hermes-check.sh`,
237
- `INTUITIC_HERMES_CONFIG=~/.hermes/config.yaml`,
207
+ `INTUTIC_HERMES_HOOK=~/.intutic/hooks/hermes-check.sh`,
208
+ `INTUTIC_HERMES_CONFIG=~/.hermes/config.yaml`,
238
209
  ].join('\n') + '\n';
239
210
  const envFilePath = path.join(envDir, 'hermes.env');
240
211
  const tmpEnv = envFilePath + '.intutic-tmp';
@@ -1 +1 @@
1
- {"version":3,"file":"hermesHooks.js","sourceRoot":"","sources":["../../src/harness/hermesHooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hermesHooks.js","sourceRoot":"","sources":["../../src/harness/hermesHooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Redaction for the request context captured when a review hold fires.
3
+ *
4
+ * A hold snapshot is the one place in the product where raw tool *arguments*
5
+ * leave the developer's machine. `Write` carries file contents; `Bash` carries
6
+ * whole command lines; both routinely contain credentials that the developer
7
+ * never intended to publish. The proxy's DLP scanner does not help here — it
8
+ * sits on the HTTP path, and this runs inside the harness before any request
9
+ * exists.
10
+ *
11
+ * So the snapshot is redacted and capped **in the hook, before it touches
12
+ * disk** — not in the daemon on the way out. If it were done on the way out,
13
+ * the plaintext would already be sitting in `.intutic/events/`, and a file that
14
+ * exists is a file that gets copied into a bug report.
15
+ *
16
+ * ## Why this is emitted rather than imported
17
+ *
18
+ * The generated hook is a standalone script with no module resolution — it runs
19
+ * as whatever `node` the harness invokes, in whatever directory. So the function
20
+ * below is serialised into it with `String()`. That is also why it must be
21
+ * **entirely self-contained**: no imports, no closure captures, no references to
22
+ * anything outside its own body — `emitRedactor` declares every module-level
23
+ * name these two reach, and a test evaluates the emitted text to prove it.
24
+ *
25
+ * Serialising the function instead of pasting a source string is deliberate.
26
+ * Four separate bugs in this file's history came from backticks and `${`
27
+ * sequences inside emitted code terminating the enclosing template literal.
28
+ * An interpolated *value* is never re-parsed, so that whole class is gone — and
29
+ * the function gets ordinary unit tests instead of being tested through a
30
+ * generated artifact.
31
+ *
32
+ * @module
33
+ */
34
+ /** Longest string kept verbatim. Past this the middle is dropped, not the end. */
35
+ export declare const MAX_STRING = 2000;
36
+ /** Deepest object nesting walked. Below this, values become a marker. */
37
+ export declare const MAX_DEPTH = 6;
38
+ /** Most array elements kept. */
39
+ export declare const MAX_ARRAY = 50;
40
+ /** Hard ceiling on the serialised snapshot. */
41
+ export declare const MAX_SNAPSHOT_BYTES = 32768;
42
+ /**
43
+ * Returns a redacted, size-capped copy of `value`.
44
+ *
45
+ * Self-contained by contract — see the module docstring. Do not add imports or
46
+ * outer-scope references to this function; `emitRedactor()` asserts it stays
47
+ * closed over nothing.
48
+ */
49
+ export declare function redactSecrets(value: unknown, depth?: number): unknown;
50
+ /**
51
+ * Emits both functions as standalone JavaScript for the generated hook.
52
+ *
53
+ * The caps are declared ahead of the functions rather than substituted into
54
+ * their bodies, so one definition serves the emitted hook and the unit tests.
55
+ */
56
+ export declare function emitRedactor(): string;
57
+ /**
58
+ * Builds the snapshot the hook writes, redacted and under the byte ceiling.
59
+ *
60
+ * Serialised into the hook by `emitRedactor` and imported directly by the
61
+ * tests, so what the tests exercise is what runs.
62
+ */
63
+ export declare function buildContextSnapshot(input: {
64
+ tool: string;
65
+ toolInput: unknown;
66
+ cwd: string;
67
+ reason: string;
68
+ }): Record<string, unknown>;
69
+ //# sourceMappingURL=holdRedaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"holdRedaction.d.ts","sourceRoot":"","sources":["../../src/harness/holdRedaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,kFAAkF;AAClF,eAAO,MAAM,UAAU,OAAQ,CAAA;AAC/B,yEAAyE;AACzE,eAAO,MAAM,SAAS,IAAI,CAAA;AAC1B,gCAAgC;AAChC,eAAO,MAAM,SAAS,KAAK,CAAA;AAC3B,+CAA+C;AAC/C,eAAO,MAAM,kBAAkB,QAAS,CAAA;AAExC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,OAAO,CAwGhE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAsBrC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,OAAO,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkB1B"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Redaction for the request context captured when a review hold fires.
3
+ *
4
+ * A hold snapshot is the one place in the product where raw tool *arguments*
5
+ * leave the developer's machine. `Write` carries file contents; `Bash` carries
6
+ * whole command lines; both routinely contain credentials that the developer
7
+ * never intended to publish. The proxy's DLP scanner does not help here — it
8
+ * sits on the HTTP path, and this runs inside the harness before any request
9
+ * exists.
10
+ *
11
+ * So the snapshot is redacted and capped **in the hook, before it touches
12
+ * disk** — not in the daemon on the way out. If it were done on the way out,
13
+ * the plaintext would already be sitting in `.intutic/events/`, and a file that
14
+ * exists is a file that gets copied into a bug report.
15
+ *
16
+ * ## Why this is emitted rather than imported
17
+ *
18
+ * The generated hook is a standalone script with no module resolution — it runs
19
+ * as whatever `node` the harness invokes, in whatever directory. So the function
20
+ * below is serialised into it with `String()`. That is also why it must be
21
+ * **entirely self-contained**: no imports, no closure captures, no references to
22
+ * anything outside its own body — `emitRedactor` declares every module-level
23
+ * name these two reach, and a test evaluates the emitted text to prove it.
24
+ *
25
+ * Serialising the function instead of pasting a source string is deliberate.
26
+ * Four separate bugs in this file's history came from backticks and `${`
27
+ * sequences inside emitted code terminating the enclosing template literal.
28
+ * An interpolated *value* is never re-parsed, so that whole class is gone — and
29
+ * the function gets ordinary unit tests instead of being tested through a
30
+ * generated artifact.
31
+ *
32
+ * @module
33
+ */
34
+ /** Longest string kept verbatim. Past this the middle is dropped, not the end. */
35
+ export const MAX_STRING = 2_000;
36
+ /** Deepest object nesting walked. Below this, values become a marker. */
37
+ export const MAX_DEPTH = 6;
38
+ /** Most array elements kept. */
39
+ export const MAX_ARRAY = 50;
40
+ /** Hard ceiling on the serialised snapshot. */
41
+ export const MAX_SNAPSHOT_BYTES = 32_768;
42
+ /**
43
+ * Returns a redacted, size-capped copy of `value`.
44
+ *
45
+ * Self-contained by contract — see the module docstring. Do not add imports or
46
+ * outer-scope references to this function; `emitRedactor()` asserts it stays
47
+ * closed over nothing.
48
+ */
49
+ export function redactSecrets(value, depth = 0) {
50
+ // Keys whose value is a credential regardless of what it looks like. Matched
51
+ // loosely on purpose: `apiKey`, `api_key`, `X-Api-Key` and `apikey` all occur.
52
+ const SECRET_KEY = /(pass(word|phrase)?|secret|token|api[-_ ]?key|auth(orization)?|credential|private[-_ ]?key|session[-_ ]?id|cookie)/i;
53
+ // Credential *shapes*, for values that arrive under an innocent key — the
54
+ // common case, since most of these appear inside a shell command string.
55
+ const SECRET_VALUE = [
56
+ /AKIA[0-9A-Z]{16}/g, // AWS access key id
57
+ /ASIA[0-9A-Z]{16}/g, // AWS temporary access key id
58
+ /sk-ant-[A-Za-z0-9_-]{16,}/g, // Anthropic
59
+ /sk-[A-Za-z0-9]{32,}/g, // OpenAI and lookalikes
60
+ /gh[pousr]_[A-Za-z0-9]{20,}/g, // GitHub
61
+ /github_pat_[A-Za-z0-9_]{20,}/g,
62
+ /xox[abprs]-[A-Za-z0-9-]{10,}/g, // Slack
63
+ /AIza[0-9A-Za-z_-]{30,}/g, // Google
64
+ /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, // JWT
65
+ /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g,
66
+ // Bare basic-auth in a URL.
67
+ /\/\/[^/\s:@]+:[^/\s:@]+@/g,
68
+ ];
69
+ // Assignment forms — `--token=…`, `PASSWORD=…`, `"secret": "…"`,
70
+ // `Authorization: Bearer …`. Matched in two steps, name first and value
71
+ // second, for two independent reasons.
72
+ //
73
+ // **Cost.** The single regex this replaces put two unbounded runs either side
74
+ // of the keyword alternation — `[A-Za-z0-9_-]*(token|…)[A-Za-z0-9_-]*` — so
75
+ // every start offset enumerated every split point of the identifier and the
76
+ // whole thing went quadratic. Measured on `'key'.repeat(n)`: 59 KB took 566 ms
77
+ // and 469 KB took 38 *seconds*, synchronously, inside a `PreToolUse` hook. The
78
+ // size caps do not bound it: `scrub` redacts the whole string and truncates to
79
+ // MAX_STRING only afterwards, and MAX_SNAPSHOT_BYTES applies later still. A
80
+ // `Write` carrying a few hundred KB of file content was enough. Matching the
81
+ // name alone is bounded by the identifier run, and the value is walked once,
82
+ // by hand, only after the name has been found interesting — 1.2 MB in ~25 ms.
83
+ //
84
+ // **Reach.** That same mandatory `[A-Za-z_]` before the alternation meant the
85
+ // keyword could only ever be an *infix* of the identifier, never the whole of
86
+ // it. So `MY_API_KEY=` redacted and `PASSWORD=`, `--token=`, `--credential=`,
87
+ // `passphrase=` and `"secret":` did not — all three of the forms the old
88
+ // comment offered as its examples passed through to disk in plaintext.
89
+ const NAME = /\b([A-Za-z_][A-Za-z0-9_-]*)(["']?\s*[:=]\s*["']?(?:Bearer\s+|Basic\s+)?)/g;
90
+ const NAME_IS_SECRET = /pass(word|phrase)?|secret|token|key|credential|auth(orization)?/i;
91
+ const VALUE_END = /[\s"',;]/;
92
+ const redactAssignments = (s) => {
93
+ let out = '';
94
+ let copied = 0;
95
+ let m;
96
+ NAME.lastIndex = 0;
97
+ while ((m = NAME.exec(s)) !== null) {
98
+ // Declining leaves `lastIndex` just past the separator rather than past a
99
+ // consumed value, so a boring name cannot shadow a secret packed into the
100
+ // same unbroken run — `--set=PGPASSWORD=…` still redacts.
101
+ if (!NAME_IS_SECRET.test(m[1]))
102
+ continue;
103
+ let end = NAME.lastIndex;
104
+ while (end < s.length && !VALUE_END.test(s[end]))
105
+ end += 1;
106
+ // Too short to be a credential, and short enough that redacting it would
107
+ // cost more context than it protects.
108
+ if (end - NAME.lastIndex < 8)
109
+ continue;
110
+ out += s.slice(copied, NAME.lastIndex) + '[redacted]';
111
+ copied = end;
112
+ NAME.lastIndex = end;
113
+ }
114
+ // The name and separator survive. A snapshot exists to be reviewed, and
115
+ // `--token=[redacted]` tells the reviewer what was held; a bare
116
+ // `[redacted]` swallowing the key name does not.
117
+ return copied === 0 ? s : out + s.slice(copied);
118
+ };
119
+ const scrub = (s) => {
120
+ let out = s;
121
+ for (const re of SECRET_VALUE)
122
+ out = out.replace(re, '[redacted]');
123
+ out = redactAssignments(out);
124
+ if (out.length > MAX_STRING) {
125
+ // Keep both ends. A command's verb is at the front and its target is at
126
+ // the back; truncating the tail throws away the half that identifies what
127
+ // the hold was about.
128
+ const half = Math.floor(MAX_STRING / 2);
129
+ out = `${out.slice(0, half)}…[${out.length - MAX_STRING} chars elided]…${out.slice(-half)}`;
130
+ }
131
+ return out;
132
+ };
133
+ if (depth > MAX_DEPTH)
134
+ return '[depth limit]';
135
+ if (value === null || value === undefined)
136
+ return value ?? null;
137
+ if (typeof value === 'string')
138
+ return scrub(value);
139
+ if (typeof value === 'number' || typeof value === 'boolean')
140
+ return value;
141
+ if (Array.isArray(value)) {
142
+ const kept = value.slice(0, MAX_ARRAY).map((v) => redactSecrets(v, depth + 1));
143
+ if (value.length > MAX_ARRAY)
144
+ kept.push(`[${value.length - MAX_ARRAY} more elided]`);
145
+ return kept;
146
+ }
147
+ if (typeof value === 'object') {
148
+ const out = {};
149
+ for (const [k, v] of Object.entries(value)) {
150
+ out[k] = SECRET_KEY.test(k) ? '[redacted]' : redactSecrets(v, depth + 1);
151
+ }
152
+ return out;
153
+ }
154
+ // Functions, symbols, bigints — nothing a tool input should carry.
155
+ return `[${typeof value}]`;
156
+ }
157
+ /**
158
+ * Emits both functions as standalone JavaScript for the generated hook.
159
+ *
160
+ * The caps are declared ahead of the functions rather than substituted into
161
+ * their bodies, so one definition serves the emitted hook and the unit tests.
162
+ */
163
+ export function emitRedactor() {
164
+ // Bound under their SOURCE names first, then aliased.
165
+ //
166
+ // `buildContextSnapshot` calls `redactSecrets` by name, and a named function
167
+ // expression only supplies a binding for its own recursion — not for its
168
+ // siblings. Emitting solely as `__intuticSnapshot` produced a body calling a
169
+ // `redactSecrets` that did not exist in the hook, which threw inside the
170
+ // hold-write catch and silently produced no snapshot at all. The structural
171
+ // assertions did not catch it; evaluating the emitted text did, which is why
172
+ // that test exists.
173
+ return [
174
+ '// ── Intutic hold redaction ───────────────────────────────────────────',
175
+ '// Serialised from harness/holdRedaction.ts. Do not edit here.',
176
+ `const MAX_STRING = ${MAX_STRING};`,
177
+ `const MAX_DEPTH = ${MAX_DEPTH};`,
178
+ `const MAX_ARRAY = ${MAX_ARRAY};`,
179
+ `const MAX_SNAPSHOT_BYTES = ${MAX_SNAPSHOT_BYTES};`,
180
+ `const redactSecrets = ${String(redactSecrets)};`,
181
+ `const buildContextSnapshot = ${String(buildContextSnapshot)};`,
182
+ 'const __intuticRedact = redactSecrets;',
183
+ 'const __intuticSnapshot = buildContextSnapshot;',
184
+ ].join('\n');
185
+ }
186
+ /**
187
+ * Builds the snapshot the hook writes, redacted and under the byte ceiling.
188
+ *
189
+ * Serialised into the hook by `emitRedactor` and imported directly by the
190
+ * tests, so what the tests exercise is what runs.
191
+ */
192
+ export function buildContextSnapshot(input) {
193
+ const snapshot = {
194
+ tool: input.tool,
195
+ reason: input.reason,
196
+ cwd: redactSecrets(input.cwd),
197
+ toolInput: redactSecrets(input.toolInput),
198
+ };
199
+ const serialised = JSON.stringify(snapshot);
200
+ if (serialised.length > MAX_SNAPSHOT_BYTES) {
201
+ // Drop the arguments rather than truncating the JSON into something that
202
+ // will not parse. A snapshot that says why it is incomplete is usable; a
203
+ // snapshot that is silently half a document is not.
204
+ //
205
+ // No re-serialise: the replacement is a short string, so the result is
206
+ // necessarily under the cap, and nothing downstream reads a byte count.
207
+ snapshot['toolInput'] = `[elided: ${serialised.length} bytes over the ${MAX_SNAPSHOT_BYTES} cap]`;
208
+ }
209
+ return snapshot;
210
+ }
211
+ //# sourceMappingURL=holdRedaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"holdRedaction.js","sourceRoot":"","sources":["../../src/harness/holdRedaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * jetbrainsXmlConfig.ts — narrow, dependency-free reader/writer for the
3
+ * JetBrains Platform's `<application><component name="X">...</component>
4
+ * </application>` settings-file shape.
5
+ *
6
+ * # Why this exists, and why it is not a general XML library
7
+ *
8
+ * `windsurfHooks.ts` needs to write into TWO files that live under a
9
+ * JetBrains IDE's `options/` directory — `proxy.settings.xml` (the IDE
10
+ * platform's own HTTP proxy setting) and `CodeiumSettings.xml` (the
11
+ * Windsurf plugin's own settings, including its `detectProxy` toggle) —
12
+ * and BOTH already exist on a real install, carrying settings this
13
+ * codebase has no business touching (a user's proxy exceptions list, their
14
+ * indexing path, dozens of unrelated plugin toggles, some of which — like
15
+ * `customTrackedWorkspaces` — serialize as a NESTED element, not a simple
16
+ * `<option name value/>` pair). A wholesale overwrite — the pattern
17
+ * `atomicWriteJson` uses for files this codebase fully owns, like
18
+ * `hooks.json` — would silently destroy those. This module instead
19
+ * MERGES at two levels:
20
+ *
21
+ * - Every OTHER `<component>` in the file is passed through completely
22
+ * unchanged — this module never parses their contents.
23
+ * - Within the ONE component the caller names, every simple self-closing
24
+ * `<option name="K" value="V" />` this module recognizes is
25
+ * added/overwritten by key; anything else inside that same component
26
+ * (a nested `<option name="X"><list>...</list></option>`, a comment,
27
+ * whatever) is preserved VERBATIM, appended after the merged simple
28
+ * options — never dropped, never guessed at.
29
+ *
30
+ * The only thing this module refuses outright (writes nothing, returns
31
+ * `false`) is content it cannot even locate an `<application>` root and
32
+ * `<component>` children in at all — a file this foreign is not this
33
+ * module's to touch.
34
+ *
35
+ * @module
36
+ */
37
+ /**
38
+ * Merges `desiredOptions` into `filePath`'s `<component name=
39
+ * componentName">` block as simple `<option name value/>` entries,
40
+ * creating the file/component fresh if absent. Every other component,
41
+ * and anything inside the target component this module does not
42
+ * recognize as a simple option, is preserved unchanged (see module doc
43
+ * comment). `desiredOptions` values are stringified as `"true"`/`"false"`
44
+ * for booleans, matching JetBrains' own XML serialization convention.
45
+ *
46
+ * Returns `false` (and writes nothing) when the existing file does not
47
+ * match the `<application><component>...` shape this module understands
48
+ * at all — refusal, not a guess.
49
+ */
50
+ export declare function mergeXmlComponentOptions(filePath: string, componentName: string, desiredOptions: Readonly<Record<string, string | boolean>>, readFile: (path: string) => Promise<string | null>, writeFileAtomic: (path: string, content: string) => Promise<void>): Promise<boolean>;
51
+ //# sourceMappingURL=jetbrainsXmlConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jetbrainsXmlConfig.d.ts","sourceRoot":"","sources":["../../src/harness/jetbrainsXmlConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAwGH;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAC1D,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EAClD,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAChE,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * jetbrainsXmlConfig.ts — narrow, dependency-free reader/writer for the
3
+ * JetBrains Platform's `<application><component name="X">...</component>
4
+ * </application>` settings-file shape.
5
+ *
6
+ * # Why this exists, and why it is not a general XML library
7
+ *
8
+ * `windsurfHooks.ts` needs to write into TWO files that live under a
9
+ * JetBrains IDE's `options/` directory — `proxy.settings.xml` (the IDE
10
+ * platform's own HTTP proxy setting) and `CodeiumSettings.xml` (the
11
+ * Windsurf plugin's own settings, including its `detectProxy` toggle) —
12
+ * and BOTH already exist on a real install, carrying settings this
13
+ * codebase has no business touching (a user's proxy exceptions list, their
14
+ * indexing path, dozens of unrelated plugin toggles, some of which — like
15
+ * `customTrackedWorkspaces` — serialize as a NESTED element, not a simple
16
+ * `<option name value/>` pair). A wholesale overwrite — the pattern
17
+ * `atomicWriteJson` uses for files this codebase fully owns, like
18
+ * `hooks.json` — would silently destroy those. This module instead
19
+ * MERGES at two levels:
20
+ *
21
+ * - Every OTHER `<component>` in the file is passed through completely
22
+ * unchanged — this module never parses their contents.
23
+ * - Within the ONE component the caller names, every simple self-closing
24
+ * `<option name="K" value="V" />` this module recognizes is
25
+ * added/overwritten by key; anything else inside that same component
26
+ * (a nested `<option name="X"><list>...</list></option>`, a comment,
27
+ * whatever) is preserved VERBATIM, appended after the merged simple
28
+ * options — never dropped, never guessed at.
29
+ *
30
+ * The only thing this module refuses outright (writes nothing, returns
31
+ * `false`) is content it cannot even locate an `<application>` root and
32
+ * `<component>` children in at all — a file this foreign is not this
33
+ * module's to touch.
34
+ *
35
+ * @module
36
+ */
37
+ const COMPONENT_RE = /<component\s+name="([^"]*)">([\s\S]*?)<\/component>/g;
38
+ const SIMPLE_OPTION_RE = /<option\s+name="([^"]*)"\s+value="([^"]*)"\s*\/>/g;
39
+ function escapeXmlAttr(value) {
40
+ return value
41
+ .replace(/&/g, '&amp;')
42
+ .replace(/"/g, '&quot;')
43
+ .replace(/</g, '&lt;')
44
+ .replace(/>/g, '&gt;');
45
+ }
46
+ function parseComponentInner(inner) {
47
+ const simpleOptions = new Map();
48
+ let extra = inner;
49
+ SIMPLE_OPTION_RE.lastIndex = 0;
50
+ let match;
51
+ while ((match = SIMPLE_OPTION_RE.exec(inner)) !== null) {
52
+ const [full, key, value] = match;
53
+ if (key === undefined || value === undefined || full === undefined)
54
+ continue;
55
+ simpleOptions.set(key, value);
56
+ extra = extra.replace(full, '');
57
+ }
58
+ // Normalized to one trimmed line per non-empty line, deliberately
59
+ // dropping the ORIGINAL indentation depth: re-applying a fixed indent
60
+ // on top of already-indented text (rather than a normalized base) would
61
+ // make the file's whitespace grow by one indent level on every sync
62
+ // cycle this module runs in. XML semantics never depend on this
63
+ // whitespace, so normalizing costs nothing real and keeps repeated
64
+ // merges idempotent byte-for-byte.
65
+ const normalizedExtra = extra
66
+ .split('\n')
67
+ .map((line) => line.trim())
68
+ .filter((line) => line.length > 0)
69
+ .join('\n');
70
+ return { simpleOptions, extra: normalizedExtra };
71
+ }
72
+ /** Parses `raw` (the file's current content, or `null`/empty if it does
73
+ * not exist yet) looking for the `<application>` shape this module
74
+ * understands. An absent/empty file is `ok: true` with no target —
75
+ * "create fresh," not a parse failure. */
76
+ function parseApplicationXml(raw, targetComponentName) {
77
+ const otherComponents = new Map();
78
+ if (raw === null || raw.trim().length === 0) {
79
+ return { ok: true, otherComponents, target: undefined };
80
+ }
81
+ const appMatch = /^\s*(?:<\?xml[^>]*\?>\s*)?<application>([\s\S]*)<\/application>\s*$/.exec(raw);
82
+ if (!appMatch) {
83
+ return { ok: false, otherComponents, target: undefined };
84
+ }
85
+ const body = appMatch[1] ?? '';
86
+ let target;
87
+ let match;
88
+ COMPONENT_RE.lastIndex = 0;
89
+ while ((match = COMPONENT_RE.exec(body)) !== null) {
90
+ const [full, name, inner] = match;
91
+ if (name === undefined || inner === undefined || full === undefined)
92
+ continue;
93
+ if (name === targetComponentName) {
94
+ target = parseComponentInner(inner);
95
+ }
96
+ else {
97
+ otherComponents.set(name, full);
98
+ }
99
+ }
100
+ // Anything at the <application> level that is not whitespace and not one
101
+ // of the <component> blocks just consumed is content this module does
102
+ // not understand — refuse the whole file rather than silently drop it.
103
+ // (Length-based, not substring-removal: two components could otherwise
104
+ // have byte-identical bodies and make removal ambiguous.)
105
+ const nonWhitespaceLength = body.replace(/\s/g, '').length;
106
+ const consumedNonWhitespaceLength = [...body.matchAll(COMPONENT_RE)]
107
+ .map((m) => (m[0] ?? '').replace(/\s/g, '').length)
108
+ .reduce((a, b) => a + b, 0);
109
+ if (nonWhitespaceLength !== consumedNonWhitespaceLength) {
110
+ return { ok: false, otherComponents, target: undefined };
111
+ }
112
+ return { ok: true, otherComponents, target };
113
+ }
114
+ /**
115
+ * Merges `desiredOptions` into `filePath`'s `<component name=
116
+ * componentName">` block as simple `<option name value/>` entries,
117
+ * creating the file/component fresh if absent. Every other component,
118
+ * and anything inside the target component this module does not
119
+ * recognize as a simple option, is preserved unchanged (see module doc
120
+ * comment). `desiredOptions` values are stringified as `"true"`/`"false"`
121
+ * for booleans, matching JetBrains' own XML serialization convention.
122
+ *
123
+ * Returns `false` (and writes nothing) when the existing file does not
124
+ * match the `<application><component>...` shape this module understands
125
+ * at all — refusal, not a guess.
126
+ */
127
+ export async function mergeXmlComponentOptions(filePath, componentName, desiredOptions, readFile, writeFileAtomic) {
128
+ const existing = await readFile(filePath);
129
+ const parsed = parseApplicationXml(existing, componentName);
130
+ if (!parsed.ok)
131
+ return false;
132
+ const options = parsed.target?.simpleOptions ?? new Map();
133
+ for (const [key, value] of Object.entries(desiredOptions)) {
134
+ options.set(key, typeof value === 'boolean' ? String(value) : value);
135
+ }
136
+ const optionLines = [...options.entries()]
137
+ .sort(([a], [b]) => a.localeCompare(b))
138
+ .map(([key, value]) => ` <option name="${escapeXmlAttr(key)}" value="${escapeXmlAttr(value)}" />`)
139
+ .join('\n');
140
+ const extra = parsed.target?.extra;
141
+ const componentBody = extra ? `${optionLines}\n${extra.split('\n').map((l) => ` ${l}`).join('\n')}` : optionLines;
142
+ const componentBlock = ` <component name="${escapeXmlAttr(componentName)}">\n${componentBody}\n </component>`;
143
+ const otherBlocks = [...parsed.otherComponents.entries()]
144
+ .sort(([a], [b]) => a.localeCompare(b))
145
+ .map(([, block]) => ` ${block}`);
146
+ const body = [componentBlock, ...otherBlocks].join('\n');
147
+ const serialized = `<application>\n${body}\n</application>\n`;
148
+ await writeFileAtomic(filePath, serialized);
149
+ return true;
150
+ }
151
+ //# sourceMappingURL=jetbrainsXmlConfig.js.map