@polygraph/claude-plugin 0.4.33 → 0.4.34

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygraph",
3
- "version": "0.4.33",
3
+ "version": "0.4.34",
4
4
  "description": "AI agent skills and subagents for Polygraph multi-repo coordination",
5
5
  "author": {
6
6
  "name": "Narwhal Technologies Inc",
@@ -18,17 +18,58 @@
18
18
  // injects hook stdout into the model context); never exits non-zero.
19
19
 
20
20
  import {
21
+ appendFileSync,
21
22
  existsSync,
22
23
  mkdirSync,
23
24
  readFileSync,
24
25
  realpathSync,
25
26
  renameSync,
27
+ statSync,
26
28
  writeFileSync,
27
29
  } from 'node:fs';
28
30
  import { homedir } from 'node:os';
29
31
  import { join } from 'node:path';
30
32
  import { fileURLToPath } from 'node:url';
31
33
 
34
+ const HOOK_LOG_MAX_BYTES = 5 * 1024 * 1024;
35
+
36
+ // Append a one-line JSON record of a hook failure to ~/.polygraph/logs/hooks.log.
37
+ // This hook swallows its errors silently and must never write to stdout (Claude
38
+ // Code injects hook stdout into the model context), so this on-disk log is the
39
+ // only record that something went wrong. The logger is itself failure-proof.
40
+ function logHookFailure(
41
+ hook,
42
+ error,
43
+ meta = {},
44
+ home = process.env.HOME?.trim() || homedir()
45
+ ) {
46
+ try {
47
+ const logsDir = join(home, '.polygraph', 'logs');
48
+ mkdirSync(logsDir, { recursive: true });
49
+ const logFile = join(logsDir, 'hooks.log');
50
+
51
+ try {
52
+ if (statSync(logFile).size > HOOK_LOG_MAX_BYTES) {
53
+ renameSync(logFile, `${logFile}.1`);
54
+ }
55
+ } catch {
56
+ // no prior log, or rotation failed — ignore
57
+ }
58
+
59
+ const entry = {
60
+ time: new Date().toISOString(),
61
+ hook,
62
+ pid: process.pid,
63
+ ...meta,
64
+ error: error instanceof Error ? error.message : String(error),
65
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
66
+ };
67
+ appendFileSync(logFile, JSON.stringify(entry) + '\n');
68
+ } catch {
69
+ // Logging must never throw — a failing logger must not break the hook.
70
+ }
71
+ }
72
+
32
73
  function readStdin() {
33
74
  try {
34
75
  return readFileSync(0, 'utf8');
@@ -147,8 +188,10 @@ export function main() {
147
188
  // process.ppid is the harness pid when the hook is spawned as a child.
148
189
  pid: process.ppid,
149
190
  });
150
- } catch {
151
- // Silent — a broken hook must never break the agent session.
191
+ } catch (error) {
192
+ // Silent toward the agent — a broken hook must never break the session
193
+ // but record it so failures are not invisible.
194
+ logHookFailure(`${process.argv[2] || 'unknown'}:record-session-mapping`, error);
152
195
  }
153
196
  }
154
197
 
@@ -18,7 +18,16 @@
18
18
  //
19
19
  // Outside a Polygraph session (no matching sidecar) the hook is a silent no-op.
20
20
 
21
- import { readFileSync, readdirSync, existsSync, realpathSync } from 'node:fs';
21
+ import {
22
+ appendFileSync,
23
+ existsSync,
24
+ mkdirSync,
25
+ readFileSync,
26
+ readdirSync,
27
+ realpathSync,
28
+ renameSync,
29
+ statSync,
30
+ } from 'node:fs';
22
31
  import { homedir } from 'node:os';
23
32
  import path from 'node:path';
24
33
  import { fileURLToPath } from 'node:url';
@@ -27,6 +36,45 @@ export function polygraphRoot(home = homedir()) {
27
36
  return path.join(home, '.polygraph');
28
37
  }
29
38
 
39
+ const HOOK_LOG_MAX_BYTES = 5 * 1024 * 1024;
40
+
41
+ // Append a one-line JSON record of a hook failure to ~/.polygraph/logs/hooks.log.
42
+ // This hook must never write to stdout except its hookSpecificOutput payload
43
+ // (Claude Code injects hook stdout into the model context), so this on-disk log
44
+ // is the only record that something went wrong. The logger is failure-proof.
45
+ function logHookFailure(
46
+ hook,
47
+ error,
48
+ meta = {},
49
+ home = process.env.HOME?.trim() || homedir()
50
+ ) {
51
+ try {
52
+ const logsDir = path.join(home, '.polygraph', 'logs');
53
+ mkdirSync(logsDir, { recursive: true });
54
+ const logFile = path.join(logsDir, 'hooks.log');
55
+
56
+ try {
57
+ if (statSync(logFile).size > HOOK_LOG_MAX_BYTES) {
58
+ renameSync(logFile, `${logFile}.1`);
59
+ }
60
+ } catch {
61
+ // no prior log, or rotation failed — ignore
62
+ }
63
+
64
+ const entry = {
65
+ time: new Date().toISOString(),
66
+ hook,
67
+ pid: process.pid,
68
+ ...meta,
69
+ error: error instanceof Error ? error.message : String(error),
70
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
71
+ };
72
+ appendFileSync(logFile, JSON.stringify(entry) + '\n');
73
+ } catch {
74
+ // Logging must never throw — a failing logger must not break the hook.
75
+ }
76
+ }
77
+
30
78
  function readJson(file) {
31
79
  try {
32
80
  return JSON.parse(readFileSync(file, 'utf8'));
@@ -115,30 +163,36 @@ function readStdin() {
115
163
  }
116
164
 
117
165
  export function main() {
118
- let payload = {};
119
- const raw = readStdin();
120
- if (raw) {
121
- try {
122
- payload = JSON.parse(raw);
123
- } catch {
124
- payload = {};
166
+ let agentSessionId = '';
167
+ try {
168
+ let payload = {};
169
+ const raw = readStdin();
170
+ if (raw) {
171
+ try {
172
+ payload = JSON.parse(raw);
173
+ } catch {
174
+ payload = {};
175
+ }
125
176
  }
126
- }
127
177
 
128
- const agentSessionId =
129
- payload.session_id || process.env.CLAUDE_CODE_SESSION_ID || '';
178
+ agentSessionId =
179
+ payload.session_id || process.env.CLAUDE_CODE_SESSION_ID || '';
130
180
 
131
- const context = buildPolygraphContext(agentSessionId);
132
- if (!context) return; // not a Polygraph session — stay silent
181
+ const context = buildPolygraphContext(agentSessionId);
182
+ if (!context) return; // not a Polygraph session — stay silent
133
183
 
134
- process.stdout.write(
135
- JSON.stringify({
136
- hookSpecificOutput: {
137
- hookEventName: 'SessionStart',
138
- additionalContext: context,
139
- },
140
- })
141
- );
184
+ process.stdout.write(
185
+ JSON.stringify({
186
+ hookSpecificOutput: {
187
+ hookEventName: 'SessionStart',
188
+ additionalContext: context,
189
+ },
190
+ })
191
+ );
192
+ } catch (error) {
193
+ // Never let a hook failure surface to the agent; just record it.
194
+ logHookFailure('reinject-polygraph-context', error, { agentSessionId });
195
+ }
142
196
  }
143
197
 
144
198
  // Run only when executed directly as a hook, not when imported (e.g. by tests).
@@ -1,17 +1,63 @@
1
1
  // Remind agents to use subagents for delegation and polling.
2
2
  // Outputs a non-blocking systemMessage — does not prevent the tool call.
3
+ import { appendFileSync, mkdirSync, renameSync, statSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { join } from 'node:path';
3
6
  import { stdin } from 'node:process';
4
7
 
5
- // Consume stdin (hook protocol requires it)
6
- stdin.resume();
7
- stdin.on('end', () => {});
8
+ const HOOK_LOG_MAX_BYTES = 5 * 1024 * 1024;
8
9
 
9
- console.log(
10
- JSON.stringify({
11
- hookSpecificOutput: {
12
- hookEventName: 'PreToolUse',
13
- systemMessage:
14
- 'REMINDER: spawn_agent and show_agent should be called via background subagents (polygraph-delegate-subagent), not directly. Direct calls flood the context window with polling noise. If you are already inside a subagent, ignore this reminder.',
15
- },
16
- })
17
- );
10
+ // Append a one-line JSON record of a hook failure to ~/.polygraph/logs/hooks.log.
11
+ // This hook must never write anything to stdout except its hookSpecificOutput
12
+ // payload, so this on-disk log is the only record that something went wrong.
13
+ // The logger is itself failure-proof.
14
+ function logHookFailure(
15
+ hook,
16
+ error,
17
+ meta = {},
18
+ home = process.env.HOME?.trim() || homedir()
19
+ ) {
20
+ try {
21
+ const logsDir = join(home, '.polygraph', 'logs');
22
+ mkdirSync(logsDir, { recursive: true });
23
+ const logFile = join(logsDir, 'hooks.log');
24
+
25
+ try {
26
+ if (statSync(logFile).size > HOOK_LOG_MAX_BYTES) {
27
+ renameSync(logFile, `${logFile}.1`);
28
+ }
29
+ } catch {
30
+ // no prior log, or rotation failed — ignore
31
+ }
32
+
33
+ const entry = {
34
+ time: new Date().toISOString(),
35
+ hook,
36
+ pid: process.pid,
37
+ ...meta,
38
+ error: error instanceof Error ? error.message : String(error),
39
+ ...(error instanceof Error && error.stack ? { stack: error.stack } : {}),
40
+ };
41
+ appendFileSync(logFile, JSON.stringify(entry) + '\n');
42
+ } catch {
43
+ // Logging must never throw — a failing logger must not break the hook.
44
+ }
45
+ }
46
+
47
+ try {
48
+ // Consume stdin (hook protocol requires it)
49
+ stdin.resume();
50
+ stdin.on('end', () => {});
51
+
52
+ console.log(
53
+ JSON.stringify({
54
+ hookSpecificOutput: {
55
+ hookEventName: 'PreToolUse',
56
+ systemMessage:
57
+ 'REMINDER: spawn_agent and show_agent should be called via background subagents (polygraph-delegate-subagent), not directly. Direct calls flood the context window with polling noise. If you are already inside a subagent, ignore this reminder.',
58
+ },
59
+ })
60
+ );
61
+ } catch (error) {
62
+ logHookFailure('remind-subagents', error);
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/claude-plugin",
3
- "version": "0.4.33",
3
+ "version": "0.4.34",
4
4
  "description": "AI agent skills and subagents for Polygraph multi-repo coordination",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,