@jmylchreest/aide-plugin 0.1.0 → 0.1.2

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 (35) hide show
  1. package/package.json +1 -1
  2. package/skills/reflect/SKILL.md +24 -8
  3. package/src/cli/codex-config.ts +15 -0
  4. package/src/core/context-pruning/dedup.ts +7 -2
  5. package/src/core/context-pruning/tracker.ts +0 -23
  6. package/src/core/memory-query.ts +63 -0
  7. package/src/core/partial-memory.ts +23 -105
  8. package/src/core/session-checkpoint-logic.ts +170 -0
  9. package/src/core/session-resume-logic.ts +104 -0
  10. package/src/core/session-summary-logic.ts +16 -41
  11. package/src/core/session-text.ts +60 -0
  12. package/src/core/tool-observe.ts +53 -14
  13. package/src/hooks/agent-cleanup.ts +11 -28
  14. package/src/hooks/agent-signals.ts +11 -18
  15. package/src/hooks/comment-checker.ts +14 -31
  16. package/src/hooks/context-guard.ts +17 -31
  17. package/src/hooks/context-pruning.ts +13 -30
  18. package/src/hooks/hud-updater.ts +11 -29
  19. package/src/hooks/permission-handler.ts +14 -25
  20. package/src/hooks/persistence.ts +15 -31
  21. package/src/hooks/pre-compact.ts +52 -38
  22. package/src/hooks/pre-tool-enforcer.ts +14 -30
  23. package/src/hooks/reflect.ts +15 -8
  24. package/src/hooks/search-enrichment.ts +12 -29
  25. package/src/hooks/session-end.ts +60 -11
  26. package/src/hooks/session-start.ts +68 -30
  27. package/src/hooks/session-summary.ts +11 -27
  28. package/src/hooks/skill-injector.ts +15 -30
  29. package/src/hooks/subagent-tracker.ts +44 -39
  30. package/src/hooks/task-completed.ts +6 -9
  31. package/src/hooks/tool-observe.ts +27 -32
  32. package/src/hooks/tool-tracker.ts +11 -28
  33. package/src/hooks/write-guard.ts +12 -29
  34. package/src/lib/hook-utils.ts +48 -0
  35. package/src/lib/project-root.ts +30 -15
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { execFileSync } from "child_process";
9
9
  import { readFileSync, existsSync } from "fs";
10
+ import { renderBulletSection } from "./session-text.js";
10
11
 
11
12
  /**
12
13
  * Get git commits made during this session.
@@ -84,8 +85,7 @@ function parseTranscript(lines: string[]): TranscriptData {
84
85
  const userMessages: string[] = [];
85
86
 
86
87
  for (const entry of entries) {
87
- const contentObj =
88
- typeof entry.content === "object" ? entry.content : null;
88
+ const contentObj = typeof entry.content === "object" ? entry.content : null;
89
89
  if (
90
90
  entry.type === "tool_use" ||
91
91
  (entry.type === "assistant" && contentObj?.tool_use)
@@ -145,33 +145,15 @@ export function buildSessionSummary(
145
145
  return null;
146
146
  }
147
147
 
148
- const summaryParts: string[] = [];
149
-
150
- if (userMessages.length > 0) {
151
- summaryParts.push(
152
- `## Tasks\n${userMessages
153
- .slice(0, 3)
154
- .map((m) => `- ${m}`)
155
- .join("\n")}`,
156
- );
157
- }
158
-
159
- if (filesModified.size > 0) {
160
- const files = Array.from(filesModified).slice(0, 10);
161
- summaryParts.push(
162
- `## Files Modified\n${files.map((f) => `- ${f}`).join("\n")}`,
163
- );
164
- }
165
-
166
- if (commits.length > 0) {
167
- summaryParts.push(
168
- `## Commits\n${commits.map((c) => `- ${c}`).join("\n")}`,
169
- );
170
- }
171
-
172
- if (toolsUsed.size > 0) {
173
- summaryParts.push(`## Tools Used\n${Array.from(toolsUsed).join(", ")}`);
174
- }
148
+ const summaryParts = [
149
+ renderBulletSection("Tasks", userMessages, 3),
150
+ renderBulletSection("Files Modified", Array.from(filesModified), 10),
151
+ renderBulletSection("Commits", commits),
152
+ // Tools Used is a comma-joined line, not a bullet list.
153
+ toolsUsed.size > 0
154
+ ? `## Tools Used\n${Array.from(toolsUsed).join(", ")}`
155
+ : null,
156
+ ].filter((s): s is string => s !== null);
175
157
 
176
158
  const summary = summaryParts.join("\n\n");
177
159
  return summary.length >= 50 ? summary : null;
@@ -193,9 +175,8 @@ export function buildSessionSummaryFromState(
193
175
 
194
176
  const summaryParts: string[] = [];
195
177
 
196
- if (commits.length > 0) {
197
- summaryParts.push(`## Commits\n${commits.map((c) => `- ${c}`).join("\n")}`);
198
- }
178
+ const commitsSection = renderBulletSection("Commits", commits);
179
+ if (commitsSection) summaryParts.push(commitsSection);
199
180
 
200
181
  // Check for modified files via git
201
182
  try {
@@ -211,15 +192,9 @@ export function buildSessionSummaryFromState(
211
192
  ).trim();
212
193
 
213
194
  if (diff) {
214
- const files = diff
215
- .split("\n")
216
- .filter((f) => f.trim())
217
- .slice(0, 10);
218
- if (files.length > 0) {
219
- summaryParts.push(
220
- `## Files Modified\n${files.map((f) => `- ${f}`).join("\n")}`,
221
- );
222
- }
195
+ const files = diff.split("\n").filter((f) => f.trim());
196
+ const filesSection = renderBulletSection("Files Modified", files, 10);
197
+ if (filesSection) summaryParts.push(filesSection);
223
198
  }
224
199
  } catch {
225
200
  // Ignore
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Session text helpers — platform-agnostic.
3
+ *
4
+ * Shared building blocks for the session summary / checkpoint builders. These
5
+ * were duplicated across partial-memory.ts, session-summary-logic.ts, and
6
+ * session-checkpoint-logic.ts; centralising them keeps the section formatting
7
+ * and the partial-classification rules in one place.
8
+ */
9
+
10
+ /** Partials grouped by the kind of action they record. */
11
+ export interface CategorizedPartials {
12
+ /** Files created or edited, de-duplicated, first-seen order preserved. */
13
+ files: string[];
14
+ /** Shell commands run. */
15
+ commands: string[];
16
+ /** Completed (sub)task descriptions. */
17
+ tasks: string[];
18
+ /** Anything that didn't match a known prefix. */
19
+ other: string[];
20
+ }
21
+
22
+ /**
23
+ * Classify raw partial-memory content strings (as produced by
24
+ * buildPartialContent) into files / commands / tasks / other.
25
+ */
26
+ export function categorizePartials(partials: string[]): CategorizedPartials {
27
+ const files = new Set<string>();
28
+ const commands: string[] = [];
29
+ const tasks: string[] = [];
30
+ const other: string[] = [];
31
+
32
+ for (const p of partials) {
33
+ if (p.startsWith("Created file: ") || p.startsWith("Edited file: ")) {
34
+ files.add(p.replace(/^(Created|Edited) file: /, ""));
35
+ } else if (p.startsWith("Ran command: ")) {
36
+ commands.push(p.replace("Ran command: ", ""));
37
+ } else if (p.startsWith("Completed task: ")) {
38
+ tasks.push(p.replace("Completed task: ", ""));
39
+ } else {
40
+ other.push(p);
41
+ }
42
+ }
43
+
44
+ return { files: Array.from(files), commands, tasks, other };
45
+ }
46
+
47
+ /**
48
+ * Render a markdown bullet section: `## <heading>` followed by `- <item>`
49
+ * lines. Returns null when there are no items (so callers can omit the
50
+ * section). When `cap` is given, only the first `cap` items are rendered.
51
+ */
52
+ export function renderBulletSection(
53
+ heading: string,
54
+ items: string[],
55
+ cap?: number,
56
+ ): string | null {
57
+ if (items.length === 0) return null;
58
+ const list = cap !== undefined ? items.slice(0, cap) : items;
59
+ return `## ${heading}\n${list.map((i) => `- ${i}`).join("\n")}`;
60
+ }
@@ -118,6 +118,41 @@ function extractOutputText(payload: unknown): string {
118
118
  return "";
119
119
  }
120
120
 
121
+ /**
122
+ * Decide whether a tool call failed and, if so, return the error text to
123
+ * attach to the observe event (drives the friction detector). Returns "" when
124
+ * the call succeeded.
125
+ *
126
+ * The meaningful friction signal is a *tool-level* failure — an Edit whose
127
+ * target string wasn't found, a Read of a missing file, a command the shell
128
+ * couldn't run — not every non-zero shell exit (a failing test mid-TDD is
129
+ * expected, not friction). We treat as failed: an explicit success===false,
130
+ * or a tool_response carrying an is_error / error / failed marker (how Claude
131
+ * Code flags tool-level errors). When failed but no text is recoverable, a
132
+ * generic marker still lets the detector count the recurrence.
133
+ */
134
+ export function toolFailureText(
135
+ success: boolean | undefined,
136
+ toolResponse: unknown,
137
+ ): string {
138
+ let failed = success === false;
139
+ let errorField = "";
140
+ if (toolResponse && typeof toolResponse === "object") {
141
+ const r = toolResponse as Record<string, unknown>;
142
+ if (r.is_error === true || r.isError === true) failed = true;
143
+ if (r.status === "error" || r.status === "failed") failed = true;
144
+ if (typeof r.error === "string" && r.error.length > 0) {
145
+ failed = true;
146
+ errorField = r.error;
147
+ }
148
+ }
149
+ if (!failed) return "";
150
+ // Prefer an explicit error field, then any rendered output, then a marker so
151
+ // the detector can still count the recurrence even with no text.
152
+ const text = errorField || extractOutputText(toolResponse);
153
+ return (text || "tool reported failure").slice(0, 500);
154
+ }
155
+
121
156
  export interface ToolObserveInput {
122
157
  toolName: string;
123
158
  toolInput?: {
@@ -138,6 +173,14 @@ export interface ToolObserveInput {
138
173
  */
139
174
  toolResponse?: unknown;
140
175
  success?: boolean;
176
+ /**
177
+ * Explicit error text from a harness failure event (Claude Code's
178
+ * PostToolUseFailure, OpenCode tool errors). When set, it's used verbatim as
179
+ * the event's error rather than inferring failure from toolResponse — the
180
+ * harness already told us it failed. Empty/undefined means "use the
181
+ * heuristic in toolFailureText".
182
+ */
183
+ errorText?: string;
141
184
  sessionId?: string;
142
185
  }
143
186
 
@@ -161,20 +204,6 @@ function lookupTool(name: string): ToolTax | null {
161
204
  return null;
162
205
  }
163
206
 
164
- /**
165
- * Resolve to the canonical tool name (Edit/Read/Write/...) so observe
166
- * events from different harnesses aggregate into the same bucket on the
167
- * dashboard. Falls back to the original name when no alias matches.
168
- */
169
- function canonicalToolName(name: string): string {
170
- if (NATIVE_TOOL_TAXONOMY[name]) return name;
171
- const lower = name.toLowerCase();
172
- for (const k of Object.keys(NATIVE_TOOL_TAXONOMY)) {
173
- if (k.toLowerCase() === lower) return k;
174
- }
175
- return TOOL_ALIASES[lower] ?? name;
176
- }
177
-
178
207
  /**
179
208
  * Estimate tokens for the Read tool. If offset/limit are present, scale by
180
209
  * the portion actually read. Returns 0 on stat failure (caller still records
@@ -281,6 +310,16 @@ export function recordToolEvent(
281
310
  if (typeof pattern === "string" && pattern.length > 0) {
282
311
  args.push(`--attr=pattern=${pattern.slice(0, 200)}`);
283
312
  }
313
+ // Mark tool-level failures so the friction detector can spot a recurring
314
+ // obstacle (the same tool failing on the same target). An explicit
315
+ // errorText from a harness failure event wins; otherwise we infer from the
316
+ // response. Empty when the call succeeded, so successes record as before.
317
+ const errText =
318
+ (input.errorText && input.errorText.slice(0, 500)) ||
319
+ toolFailureText(input.success, input.toolResponse);
320
+ if (errText) {
321
+ args.push(`--error=${errText}`);
322
+ }
284
323
  execFileSync(binary, args, {
285
324
  cwd,
286
325
  timeout: 3000,
@@ -10,8 +10,12 @@
10
10
 
11
11
  import { existsSync, appendFileSync } from "fs";
12
12
  import { join } from "path";
13
- import { readStdin } from "../lib/hook-utils.js";
14
- import { findAideBinary } from "../core/aide-client.js";
13
+ import {
14
+ readStdin,
15
+ emitHookResult,
16
+ installHookSafetyNet,
17
+ findAideBinary,
18
+ } from "../lib/hook-utils.js";
15
19
  import { cleanupAgent } from "../core/cleanup.js";
16
20
  import { debug } from "../lib/logger.js";
17
21
  import { findProjectRoot } from "../lib/project-root.js";
@@ -31,7 +35,7 @@ async function main(): Promise<void> {
31
35
  try {
32
36
  const input = await readStdin();
33
37
  if (!input.trim()) {
34
- console.log(JSON.stringify({ continue: true }));
38
+ emitHookResult({ continue: true });
35
39
  return;
36
40
  }
37
41
 
@@ -41,11 +45,7 @@ async function main(): Promise<void> {
41
45
 
42
46
  // Clean up agent-specific state — delegates to core
43
47
  if (agentId) {
44
- const binary = findAideBinary({
45
- cwd,
46
- pluginRoot:
47
- process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
48
- });
48
+ const binary = findAideBinary(cwd);
49
49
  if (binary) {
50
50
  const cleared = cleanupAgent(binary, cwd, agentId);
51
51
  if (cleared) {
@@ -64,30 +64,13 @@ async function main(): Promise<void> {
64
64
  }
65
65
 
66
66
  // Always continue - cleanup is best-effort
67
- console.log(JSON.stringify({ continue: true }));
67
+ emitHookResult({ continue: true });
68
68
  } catch (error) {
69
69
  debug(SOURCE, `Hook error: ${error}`);
70
- console.log(JSON.stringify({ continue: true }));
70
+ emitHookResult({ continue: true });
71
71
  }
72
72
  }
73
73
 
74
- process.on("uncaughtException", (err) => {
75
- debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
76
- try {
77
- console.log(JSON.stringify({ continue: true }));
78
- } catch {
79
- console.log('{"continue":true}');
80
- }
81
- process.exit(0);
82
- });
83
- process.on("unhandledRejection", (reason) => {
84
- debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
85
- try {
86
- console.log(JSON.stringify({ continue: true }));
87
- } catch {
88
- console.log('{"continue":true}');
89
- }
90
- process.exit(0);
91
- });
74
+ installHookSafetyNet(SOURCE);
92
75
 
93
76
  main();
@@ -13,8 +13,12 @@
13
13
 
14
14
  import { execFileSync } from "child_process";
15
15
  import { Logger } from "../lib/logger.js";
16
- import { readStdin } from "../lib/hook-utils.js";
17
- import { findAideBinary } from "../core/aide-client.js";
16
+ import {
17
+ readStdin,
18
+ emitHookResult,
19
+ installHookSafetyNet,
20
+ findAideBinary,
21
+ } from "../lib/hook-utils.js";
18
22
  import { emitInjectionEvent } from "../core/read-tracking.js";
19
23
 
20
24
  const SOURCE = "agent-signals";
@@ -53,12 +57,12 @@ interface SignalsResponse {
53
57
  let log: Logger | null = null;
54
58
 
55
59
  function passThrough(): void {
56
- console.log(JSON.stringify({ continue: true }));
60
+ emitHookResult({ continue: true });
57
61
  }
58
62
 
59
63
  function block(message: string): void {
60
64
  const out: HookOutput = { continue: false, message };
61
- console.log(JSON.stringify(out));
65
+ emitHookResult(out);
62
66
  }
63
67
 
64
68
  function injectContext(context: string): void {
@@ -69,7 +73,7 @@ function injectContext(context: string): void {
69
73
  additionalContext: context,
70
74
  },
71
75
  };
72
- console.log(JSON.stringify(out));
76
+ emitHookResult(out);
73
77
  }
74
78
 
75
79
  function runAide(binary: string, cwd: string, args: string[]): string | null {
@@ -105,11 +109,7 @@ async function main(): Promise<void> {
105
109
  const cwd = data.cwd || process.cwd();
106
110
  log = new Logger("agent-signals", cwd);
107
111
 
108
- const binary = findAideBinary({
109
- cwd,
110
- pluginRoot:
111
- process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
112
- });
112
+ const binary = findAideBinary(cwd);
113
113
  if (!binary) {
114
114
  passThrough();
115
115
  return;
@@ -237,13 +237,6 @@ async function main(): Promise<void> {
237
237
  }
238
238
  }
239
239
 
240
- process.on("uncaughtException", () => {
241
- passThrough();
242
- process.exit(0);
243
- });
244
- process.on("unhandledRejection", () => {
245
- passThrough();
246
- process.exit(0);
247
- });
240
+ installHookSafetyNet(SOURCE);
248
241
 
249
242
  main();
@@ -9,14 +9,18 @@
9
9
  * Core logic is in src/core/comment-checker.ts for cross-platform reuse.
10
10
  */
11
11
 
12
- import { readStdin } from "../lib/hook-utils.js";
12
+ import {
13
+ readStdin,
14
+ emitHookResult,
15
+ installHookSafetyNet,
16
+ findAideBinary,
17
+ } from "../lib/hook-utils.js";
13
18
  import { debug } from "../lib/logger.js";
14
19
  import {
15
20
  checkComments,
16
21
  getCheckableFilePath,
17
22
  getContentToCheck,
18
23
  } from "../core/comment-checker.js";
19
- import { findAideBinary } from "../core/aide-client.js";
20
24
  import { emitInjectionEvent } from "../core/read-tracking.js";
21
25
 
22
26
  const SOURCE = "comment-checker";
@@ -48,7 +52,7 @@ async function main(): Promise<void> {
48
52
  try {
49
53
  const input = await readStdin();
50
54
  if (!input.trim()) {
51
- console.log(JSON.stringify({ continue: true }));
55
+ emitHookResult({ continue: true });
52
56
  return;
53
57
  }
54
58
 
@@ -61,14 +65,14 @@ async function main(): Promise<void> {
61
65
  // Only check Write/Edit/MultiEdit tool calls
62
66
  const filePath = getCheckableFilePath(toolName, toolInput);
63
67
  if (!filePath) {
64
- console.log(JSON.stringify({ continue: true }));
68
+ emitHookResult({ continue: true });
65
69
  return;
66
70
  }
67
71
 
68
72
  // Get the content to analyze
69
73
  const contentResult = getContentToCheck(toolName, toolInput);
70
74
  if (!contentResult) {
71
- console.log(JSON.stringify({ continue: true }));
75
+ emitHookResult({ continue: true });
72
76
  return;
73
77
  }
74
78
 
@@ -81,11 +85,7 @@ async function main(): Promise<void> {
81
85
  `Detected ${result.suspiciousCount} suspicious comments in ${filePath}`,
82
86
  );
83
87
  try {
84
- const binary = findAideBinary({
85
- cwd,
86
- pluginRoot:
87
- process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
88
- });
88
+ const binary = findAideBinary(cwd);
89
89
  if (binary && result.warning) {
90
90
  emitInjectionEvent(binary, cwd, {
91
91
  source: SOURCE,
@@ -109,33 +109,16 @@ async function main(): Promise<void> {
109
109
  additionalContext: result.warning,
110
110
  },
111
111
  };
112
- console.log(JSON.stringify(output));
112
+ emitHookResult(output);
113
113
  } else {
114
- console.log(JSON.stringify({ continue: true }));
114
+ emitHookResult({ continue: true });
115
115
  }
116
116
  } catch (error) {
117
117
  debug(SOURCE, `Hook error: ${error}`);
118
- console.log(JSON.stringify({ continue: true }));
118
+ emitHookResult({ continue: true });
119
119
  }
120
120
  }
121
121
 
122
- process.on("uncaughtException", (err) => {
123
- debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
124
- try {
125
- console.log(JSON.stringify({ continue: true }));
126
- } catch {
127
- console.log('{"continue":true}');
128
- }
129
- process.exit(0);
130
- });
131
- process.on("unhandledRejection", (reason) => {
132
- debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
133
- try {
134
- console.log(JSON.stringify({ continue: true }));
135
- } catch {
136
- console.log('{"continue":true}');
137
- }
138
- process.exit(0);
139
- });
122
+ installHookSafetyNet(SOURCE);
140
123
 
141
124
  main();
@@ -11,10 +11,17 @@
11
11
  * Core logic is in src/core/context-guard.ts for cross-platform reuse.
12
12
  */
13
13
 
14
- import { readStdin } from "../lib/hook-utils.js";
14
+ import {
15
+ readStdin,
16
+ emitHookResult,
17
+ installHookSafetyNet,
18
+ findAideBinary,
19
+ } from "../lib/hook-utils.js";
15
20
  import { debug } from "../lib/logger.js";
16
- import { checkContextGuard, checkSmartReadHint } from "../core/context-guard.js";
17
- import { findAideBinary } from "../core/aide-client.js";
21
+ import {
22
+ checkContextGuard,
23
+ checkSmartReadHint,
24
+ } from "../core/context-guard.js";
18
25
  import { emitInjectionEvent } from "../core/read-tracking.js";
19
26
 
20
27
  const SOURCE = "context-guard";
@@ -44,7 +51,7 @@ async function main(): Promise<void> {
44
51
  try {
45
52
  const input = await readStdin();
46
53
  if (!input.trim()) {
47
- console.log(JSON.stringify({ continue: true }));
54
+ emitHookResult({ continue: true });
48
55
  return;
49
56
  }
50
57
 
@@ -55,11 +62,7 @@ async function main(): Promise<void> {
55
62
  const sessionId = data.session_id || "unknown";
56
63
 
57
64
  const result = checkContextGuard(toolName, toolInput, cwd, sessionId);
58
- const binary = findAideBinary({
59
- cwd,
60
- pluginRoot:
61
- process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
62
- });
65
+ const binary = findAideBinary(cwd);
63
66
 
64
67
  if (result.shouldAdvise && result.advisory) {
65
68
  debug(SOURCE, `Advising on large file read`);
@@ -84,7 +87,7 @@ async function main(): Promise<void> {
84
87
  additionalContext: result.advisory,
85
88
  },
86
89
  };
87
- console.log(JSON.stringify(output));
90
+ emitHookResult(output);
88
91
  } else {
89
92
  // Smart read hint: suggest code index for re-reads of unchanged files
90
93
  const hintResult = checkSmartReadHint(toolName, toolInput, cwd, binary);
@@ -111,34 +114,17 @@ async function main(): Promise<void> {
111
114
  additionalContext: hintResult.hint,
112
115
  },
113
116
  };
114
- console.log(JSON.stringify(output));
117
+ emitHookResult(output);
115
118
  } else {
116
- console.log(JSON.stringify({ continue: true }));
119
+ emitHookResult({ continue: true });
117
120
  }
118
121
  }
119
122
  } catch (error) {
120
123
  debug(SOURCE, `Hook error: ${error}`);
121
- console.log(JSON.stringify({ continue: true }));
124
+ emitHookResult({ continue: true });
122
125
  }
123
126
  }
124
127
 
125
- process.on("uncaughtException", (err) => {
126
- debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
127
- try {
128
- console.log(JSON.stringify({ continue: true }));
129
- } catch {
130
- console.log('{"continue":true}');
131
- }
132
- process.exit(0);
133
- });
134
- process.on("unhandledRejection", (reason) => {
135
- debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
136
- try {
137
- console.log(JSON.stringify({ continue: true }));
138
- } catch {
139
- console.log('{"continue":true}');
140
- }
141
- process.exit(0);
142
- });
128
+ installHookSafetyNet(SOURCE);
143
129
 
144
130
  main();
@@ -14,14 +14,18 @@
14
14
  * Core logic is in src/core/context-pruning/ for cross-platform reuse.
15
15
  */
16
16
 
17
- import { readStdin } from "../lib/hook-utils.js";
17
+ import {
18
+ readStdin,
19
+ emitHookResult,
20
+ installHookSafetyNet,
21
+ findAideBinary,
22
+ } from "../lib/hook-utils.js";
18
23
  import { debug } from "../lib/logger.js";
19
24
  import { ContextPruningTracker } from "../core/context-pruning/index.js";
20
25
  import type { ToolRecord } from "../core/context-pruning/types.js";
21
26
  import { tmpdir } from "os";
22
27
  import { join } from "path";
23
28
  import { existsSync, readFileSync, writeFileSync } from "fs";
24
- import { findAideBinary } from "../core/aide-client.js";
25
29
  import { emitInjectionEvent } from "../core/read-tracking.js";
26
30
 
27
31
  const SOURCE = "context-pruning";
@@ -103,7 +107,7 @@ async function main(): Promise<void> {
103
107
  try {
104
108
  const input = await readStdin();
105
109
  if (!input.trim()) {
106
- console.log(JSON.stringify({ continue: true }));
110
+ emitHookResult({ continue: true });
107
111
  return;
108
112
  }
109
113
 
@@ -116,7 +120,7 @@ async function main(): Promise<void> {
116
120
 
117
121
  // Skip if no tool output to prune
118
122
  if (!toolOutput || toolOutput.length < 50) {
119
- console.log(JSON.stringify({ continue: true }));
123
+ emitHookResult({ continue: true });
120
124
  return;
121
125
  }
122
126
 
@@ -187,11 +191,7 @@ async function main(): Promise<void> {
187
191
  }
188
192
 
189
193
  try {
190
- const binary = findAideBinary({
191
- cwd,
192
- pluginRoot:
193
- process.env.AIDE_PLUGIN_ROOT || process.env.CLAUDE_PLUGIN_ROOT,
194
- });
194
+ const binary = findAideBinary(cwd);
195
195
  const injected = output.hookSpecificOutput?.additionalContext;
196
196
  if (binary && injected) {
197
197
  emitInjectionEvent(binary, cwd, {
@@ -211,33 +211,16 @@ async function main(): Promise<void> {
211
211
  // Non-fatal
212
212
  }
213
213
 
214
- console.log(JSON.stringify(output));
214
+ emitHookResult(output);
215
215
  } else {
216
- console.log(JSON.stringify({ continue: true }));
216
+ emitHookResult({ continue: true });
217
217
  }
218
218
  } catch (error) {
219
219
  debug(SOURCE, `Hook error: ${error}`);
220
- console.log(JSON.stringify({ continue: true }));
220
+ emitHookResult({ continue: true });
221
221
  }
222
222
  }
223
223
 
224
- process.on("uncaughtException", (err) => {
225
- debug(SOURCE, `UNCAUGHT EXCEPTION: ${err}`);
226
- try {
227
- console.log(JSON.stringify({ continue: true }));
228
- } catch {
229
- console.log('{"continue":true}');
230
- }
231
- process.exit(0);
232
- });
233
- process.on("unhandledRejection", (reason) => {
234
- debug(SOURCE, `UNHANDLED REJECTION: ${reason}`);
235
- try {
236
- console.log(JSON.stringify({ continue: true }));
237
- } catch {
238
- console.log('{"continue":true}');
239
- }
240
- process.exit(0);
241
- });
224
+ installHookSafetyNet(SOURCE);
242
225
 
243
226
  main();