@polygraph/claude-plugin 0.4.33 → 0.4.35
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.
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/
|
|
4
|
-
<img src="https://raw.githubusercontent.com/nrwl/
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/nrwl/polygraph-skills/source/assets/polygraph-light.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/nrwl/polygraph-skills/source/assets/polygraph.svg" alt="Polygraph Logo" width="140">
|
|
5
5
|
</picture>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
@@ -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
|
|
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 {
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
129
|
-
|
|
178
|
+
agentSessionId =
|
|
179
|
+
payload.session_id || process.env.CLAUDE_CODE_SESSION_ID || '';
|
|
130
180
|
|
|
131
|
-
|
|
132
|
-
|
|
181
|
+
const context = buildPolygraphContext(agentSessionId);
|
|
182
|
+
if (!context) return; // not a Polygraph session — stay silent
|
|
133
183
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
6
|
-
stdin.resume();
|
|
7
|
-
stdin.on('end', () => {});
|
|
8
|
+
const HOOK_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
}
|