@polygraph/claude-plugin 0.4.24 → 0.4.26

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.24",
3
+ "version": "0.4.26",
4
4
  "description": "AI agent skills and subagents for Polygraph multi-repo coordination",
5
5
  "author": {
6
6
  "name": "Narwhal Technologies Inc",
package/hooks/hooks.json CHANGED
@@ -10,6 +10,17 @@
10
10
  }
11
11
  ]
12
12
  }
13
+ ],
14
+ "SessionStart": [
15
+ {
16
+ "matcher": "startup|resume|compact",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/reinject-polygraph-context.mjs"
21
+ }
22
+ ]
23
+ }
13
24
  ]
14
25
  }
15
26
  }
@@ -0,0 +1,161 @@
1
+ // SessionStart hook: when the calling agent (Claude Code or Codex) is running
2
+ // inside a Polygraph session, re-inject the Polygraph session id and basic
3
+ // session info as context. This restores facts that context compaction may
4
+ // have dropped, and re-establishes them on resume.
5
+ //
6
+ // Shared by the Claude and Codex plugins — both fire a SessionStart hook whose
7
+ // stdin carries `session_id` and whose stdout `additionalContext` is injected
8
+ // into the model.
9
+ //
10
+ // Everything is read from local Polygraph state — no network calls:
11
+ // ~/.polygraph/sidecars/<polygraphSessionId>/parent-<agentSessionId>.json
12
+ // maps this agent session id -> Polygraph session id (the "parent log
13
+ // sidecar" the CLI uses to stream parent-agent activity to the UI).
14
+ // ~/.polygraph/sessions/<polygraphSessionId>/session/session.json
15
+ // holds the session's repos, agentType, and orgId.
16
+ // ~/.polygraph/config.json
17
+ // holds selectedUrl, used to build the session URL.
18
+ //
19
+ // Outside a Polygraph session (no matching sidecar) the hook is a silent no-op.
20
+
21
+ import { readFileSync, readdirSync, existsSync, realpathSync } from 'node:fs';
22
+ import { homedir } from 'node:os';
23
+ import path from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ export function polygraphRoot(home = homedir()) {
27
+ return path.join(home, '.polygraph');
28
+ }
29
+
30
+ function readJson(file) {
31
+ try {
32
+ return JSON.parse(readFileSync(file, 'utf8'));
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ // Find the sidecar that maps an agent session id to a Polygraph session.
39
+ // Returns the parsed sidecar object, or null when none matches.
40
+ export function findSidecar(agentSessionId, root = polygraphRoot()) {
41
+ if (!agentSessionId) return null;
42
+
43
+ const sidecarsDir = path.join(root, 'sidecars');
44
+ if (!existsSync(sidecarsDir)) return null;
45
+
46
+ const fileName = `parent-${agentSessionId}.json`;
47
+ let entries;
48
+ try {
49
+ entries = readdirSync(sidecarsDir, { withFileTypes: true });
50
+ } catch {
51
+ return null;
52
+ }
53
+
54
+ for (const entry of entries) {
55
+ if (!entry.isDirectory()) continue;
56
+ const candidate = path.join(sidecarsDir, entry.name, fileName);
57
+ if (existsSync(candidate)) {
58
+ return readJson(candidate);
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+
64
+ // Build the context block for a Polygraph session, or null when the agent is
65
+ // not running inside a Polygraph session.
66
+ export function buildPolygraphContext(agentSessionId, root = polygraphRoot()) {
67
+ const sidecar = findSidecar(agentSessionId, root);
68
+ if (!sidecar || !sidecar.sessionId) return null;
69
+
70
+ const polygraphSessionId = sidecar.sessionId;
71
+ const agentType = sidecar.parentAgentType || 'agent';
72
+ const session =
73
+ readJson(
74
+ path.join(root, 'sessions', polygraphSessionId, 'session', 'session.json')
75
+ ) ?? {};
76
+ const config = readJson(path.join(root, 'config.json')) ?? {};
77
+
78
+ const baseUrl = config.selectedUrl;
79
+ const orgId = session.orgId;
80
+ const sessionUrl =
81
+ baseUrl && orgId
82
+ ? `${baseUrl}/orgs/${orgId}/sessions/${polygraphSessionId}`
83
+ : null;
84
+
85
+ const repos = Array.isArray(session.repos) ? session.repos : [];
86
+ const repoLines = repos.map((repo) => {
87
+ const role = repo.isInitiator ? ' (initiator)' : '';
88
+ const strategy = repo.materialization?.strategy
89
+ ? ` [${repo.materialization.strategy}]`
90
+ : '';
91
+ return ` - ${repo.repoFullName}${role}${strategy}`;
92
+ });
93
+
94
+ const lines = [
95
+ 'You are running inside a Polygraph session. Keep this in mind across compaction:',
96
+ `- Polygraph session id: ${polygraphSessionId}`,
97
+ sessionUrl ? `- Session URL: ${sessionUrl}` : null,
98
+ `- Parent agent (${agentType}) session id: ${agentSessionId}`,
99
+ repoLines.length
100
+ ? ['- Repositories in this session:', ...repoLines].join('\n')
101
+ : '- Repositories in this session: (none recorded)',
102
+ '- To act in this session (delegating work, monitoring CI, opening PRs, etc.), load the polygraph skill for guidance.',
103
+ ].filter((line) => line != null);
104
+
105
+ return lines.join('\n');
106
+ }
107
+
108
+ function readStdin() {
109
+ try {
110
+ // fd 0 — Claude Code / Codex pipe the hook payload as JSON on stdin.
111
+ return readFileSync(0, 'utf8');
112
+ } catch {
113
+ return '';
114
+ }
115
+ }
116
+
117
+ export function main() {
118
+ let payload = {};
119
+ const raw = readStdin();
120
+ if (raw) {
121
+ try {
122
+ payload = JSON.parse(raw);
123
+ } catch {
124
+ payload = {};
125
+ }
126
+ }
127
+
128
+ const agentSessionId =
129
+ payload.session_id || process.env.CLAUDE_CODE_SESSION_ID || '';
130
+
131
+ const context = buildPolygraphContext(agentSessionId);
132
+ if (!context) return; // not a Polygraph session — stay silent
133
+
134
+ process.stdout.write(
135
+ JSON.stringify({
136
+ hookSpecificOutput: {
137
+ hookEventName: 'SessionStart',
138
+ additionalContext: context,
139
+ },
140
+ })
141
+ );
142
+ }
143
+
144
+ // Run only when executed directly as a hook, not when imported (e.g. by tests).
145
+ // realpath both sides so the check holds when the plugin lives under a symlinked
146
+ // path (e.g. macOS /tmp -> /private/tmp, or a symlinked plugin install dir),
147
+ // where import.meta.url is realpath'd by Node but process.argv[1] is not.
148
+ function isMainModule() {
149
+ if (!process.argv[1]) return false;
150
+ try {
151
+ return (
152
+ realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
153
+ );
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ if (isMainModule()) {
160
+ main();
161
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/claude-plugin",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "description": "AI agent skills and subagents for Polygraph multi-repo coordination",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -122,7 +122,7 @@ The subagent will:
122
122
  4. Call `show_session` to retrieve session details
123
123
  5. Return a summary with session URL and repo info
124
124
 
125
- **Case C only — new session just created:** After the init subagent completes and creates the new session, render the session welcome card (skip the session-details block below for this case). Prefer the `session_intro` MCP tool — call it with the session ID; it returns the card as markdown. If that tool is unavailable, run `polygraph session intro -s <sessionId>` via the CLI instead. Either way, print the result to the user verbatim as markdown — do NOT wrap it in a code block or reformat it (the logo is pre-fenced; the rest is live markdown). It needs no other input, and you do not need to call `show_session` first. Then continue (ask the user what they want, or start the requested task).
125
+ **Case C only — new session just created:** After the init subagent completes and creates the new session, render the session welcome card (skip the session-details block below for this case). Prefer the `session_intro` MCP tool — call it with the session ID; it returns the card as markdown. If that tool is unavailable, run `polygraph session intro -s <sessionId>` via the CLI instead. The CLI command is intentionally hidden/internal and may not appear in public command listings, but it remains the correct skill fallback for rendering the welcome card. Either way, print the result to the user verbatim as markdown — do NOT wrap it in a code block or reformat it (the logo is pre-fenced; the rest is live markdown). It needs no other input, and you do not need to call `show_session` first. Then continue (ask the user what they want, or start the requested task).
126
126
 
127
127
  **After receiving the subagent's summary (Case B) or after calling `show_session` for an existing session (Case A), print the session details:**
128
128