@polygraph/codex-plugin 0.4.32 → 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.
- package/.codex-plugin/plugin.json +1 -1
- package/.mcp.json +4 -1
- package/README.md +13 -65
- package/hooks/record-session-mapping.mjs +45 -2
- package/hooks/reinject-polygraph-context.mjs +75 -21
- package/package.json +1 -1
package/.mcp.json
CHANGED
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<h1 align="center">Polygraph Skills</h1>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
AI agent skills and subagents for <a href="https://
|
|
11
|
+
AI agent skills and subagents for <a href="https://trypolygraph.com/">Polygraph</a> — the meta-harness for maximum agentic autonomy, giving agents visibility across every repo and memory that survives every session.
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
@@ -23,7 +23,17 @@
|
|
|
23
23
|
|
|
24
24
|
## What is Polygraph?
|
|
25
25
|
|
|
26
|
-
Polygraph is a
|
|
26
|
+
Polygraph is a meta-harness for maximum agentic autonomy. It works with the agents you already use and gives them what they're missing: visibility across every repo boundary, and memory that survives every session. Agents discover how repositories relate, coordinate changes across them, and hand off or resume work later with repos, branches, PRs, and logs all preserved.
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
Run the interactive setup and follow the prompts:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
polygraph config
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It detects your AI agent — Claude Code, Codex, OpenCode, and more — and installs the Polygraph skills and subagents for it. Re-run it any time to add another agent or update an existing install.
|
|
27
37
|
|
|
28
38
|
## Skills
|
|
29
39
|
|
|
@@ -36,66 +46,6 @@ Polygraph is a standalone product for coordinating changes across multiple repos
|
|
|
36
46
|
- **polygraph-init-subagent** — Discovers candidate repositories and initializes a Polygraph session
|
|
37
47
|
- **polygraph-delegate-subagent** — Delegates work to a child agent in another repository, polls for completion
|
|
38
48
|
|
|
39
|
-
## Codex Installer
|
|
40
|
-
|
|
41
|
-
The publishable Codex package now exposes an explicit installer CLI:
|
|
42
|
-
|
|
43
|
-
```sh
|
|
44
|
-
npx @polygraph/codex-plugin
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
That command copies the packaged Codex plugin into:
|
|
48
|
-
|
|
49
|
-
```text
|
|
50
|
-
~/.agents/plugins/polygraph
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
installs the packaged custom Codex subagents into:
|
|
54
|
-
|
|
55
|
-
```text
|
|
56
|
-
$CODEX_HOME/agents
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
updates the personal Codex marketplace at:
|
|
60
|
-
|
|
61
|
-
```text
|
|
62
|
-
~/.agents/plugins/marketplace.json
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
so the `polygraph` plugin points at `./.agents/plugins/polygraph`, and enables the plugin in:
|
|
66
|
-
|
|
67
|
-
```text
|
|
68
|
-
$CODEX_HOME/config.toml
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
`CODEX_HOME` defaults to `~/.codex` when unset.
|
|
72
|
-
|
|
73
|
-
To verify an install, run:
|
|
74
|
-
|
|
75
|
-
```sh
|
|
76
|
-
npx @polygraph/codex-plugin check
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## OpenCode Plugin
|
|
80
|
-
|
|
81
|
-
The publishable OpenCode package exposes the skills and subagents through OpenCode's native plugin system. Add it to `opencode.json`:
|
|
82
|
-
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
85
|
-
"plugin": ["@polygraph/opencode-plugin"]
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
For repeatable installs, pin the npm version:
|
|
90
|
-
|
|
91
|
-
```json
|
|
92
|
-
{
|
|
93
|
-
"plugin": ["@polygraph/opencode-plugin@0.4.18"]
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The plugin adds its packaged `skills/` directory to OpenCode's skill paths and registers the packaged Markdown agents as `subagent` entries in OpenCode config during startup.
|
|
98
|
-
|
|
99
49
|
## Development
|
|
100
50
|
|
|
101
51
|
```sh
|
|
@@ -118,10 +68,8 @@ For the strictest release flow, do not allow direct `npm publish` for the truste
|
|
|
118
68
|
|
|
119
69
|
## Learn More
|
|
120
70
|
|
|
121
|
-
- **[Polygraph](https://
|
|
71
|
+
- **[Polygraph](https://trypolygraph.com/)** — The meta-harness for maximum agentic autonomy
|
|
122
72
|
- **[@polygraph/mcp](https://www.npmjs.com/package/@polygraph/mcp)** — The MCP server that powers Polygraph tools
|
|
123
|
-
- **[Nx AI Agent Skills](https://github.com/nrwl/nx-ai-agents-config)** — The main Nx AI agent skills repo
|
|
124
|
-
|
|
125
73
|
## License
|
|
126
74
|
|
|
127
75
|
License information is defined in the package metadata.
|
|
@@ -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).
|