@polygraph/claude-plugin 0.4.50 → 0.4.52

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.50",
3
+ "version": "0.4.52",
4
4
  "description": "AI agent skills and subagents for Polygraph sessions, repository context, and coordination",
5
5
  "author": {
6
6
  "name": "Narwhal Technologies Inc",
package/README.md CHANGED
@@ -61,7 +61,7 @@ Neither is required — they are a throughput optimization, not a correctness fi
61
61
  ## Skills
62
62
 
63
63
  - **polygraph** — Comprehensive guidance for Polygraph sessions: shared context, repository graph visibility, PR/CI state, delegation, and session management.
64
- - **adversarial-review** — Second-opinion review of a session's work by independent reviewer agents, one per repo, running under a read-only `reviewer` role.
64
+ - **adversarial-review** — Second-opinion review by independent per-repo agents that presents and attaches one consolidated review artifact.
65
65
  - **await-polygraph-ci** — Wait for CI pipelines to settle across all repos in a session, investigate failures, and present fix options.
66
66
  - **get-latest-ci** — One-shot fetch of the latest CI pipeline execution for the current branch.
67
67
  - **session-debrief** — Analyze the raw logs of past Polygraph sessions and produce structured, rank-ordered debriefs for use in a different session.
@@ -82,6 +82,18 @@ npm install
82
82
  npm run sync-artifacts
83
83
  ```
84
84
 
85
+ Estimate the compiled prompt cost of every skill and subagent for Claude Code,
86
+ Codex, and OpenCode:
87
+
88
+ ```sh
89
+ npm run report:token-costs
90
+ ```
91
+
92
+ The report uses four characters per estimated token by default. Override the
93
+ ratio when needed with `npm run report:token-costs -- --characters-per-token 3.5`.
94
+ Pull requests run the same report when opened or updated and keep the latest
95
+ results in a single collapsible comment.
96
+
85
97
  ## Releasing
86
98
 
87
99
  Run the `Release PR` GitHub Actions workflow with a version bump (`patch`, `minor`, or `major`).
@@ -2,6 +2,7 @@
2
2
 
3
3
  name: session-debrief
4
4
  description: Analyze the raw logs of one or more past Polygraph sessions and return a structured, rank-ordered debrief for the current task. Launch as a background agent with a ranked list of relevant Polygraph session IDs/lines and a one-paragraph statement of the current task; it invokes the session-debrief skill, pulls parent and child transcripts via the polygraph CLI, and returns one consolidated debrief. Read-only with respect to the inspected sessions.
5
+ model: haiku
5
6
 
6
7
  ---
7
8
 
@@ -1,11 +1,11 @@
1
1
  import { appendFileSync, mkdirSync, renameSync, statSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
- import { join } from 'node:path';
3
+ import { basename, join } from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
5
 
6
6
  const HOOK_LOG_MAX_BYTES = 5 * 1024 * 1024;
7
7
 
8
- const AGENT_TYPES = new Set(['claude', 'codex', 'opencode']);
8
+ const AGENT_TYPES = new Set(['claude', 'codex', 'opencode', 'cursor']);
9
9
  const COMMAND_HOOK_TOOL = /^mcp__(?:plugin_polygraph_)?polygraph[-_]mcp__/;
10
10
  const OPENCODE_TOOL = /^polygraph(?:(?:-|_)mcp)?_/;
11
11
 
@@ -56,6 +56,17 @@ export function buildLinkAgentSessionArgs({
56
56
  return args;
57
57
  }
58
58
 
59
+ /**
60
+ * Node runtime for the JS-entry fallback. This shim also runs inside
61
+ * non-Node hosts (the opencode plugin executes it in-process, and opencode
62
+ * is a compiled Bun binary), where process.execPath is not a Node
63
+ * executable — fall back to PATH resolution there.
64
+ */
65
+ function nodeRuntime() {
66
+ const base = basename(process.execPath).toLowerCase();
67
+ return base === 'node' || base === 'node.exe' ? process.execPath : 'node';
68
+ }
69
+
59
70
  export function linkAgentSession(claim, spawn = spawnSync, env = process.env) {
60
71
  if (isManagedChildEnvironment(env)) return false;
61
72
 
@@ -67,11 +78,22 @@ export function linkAgentSession(claim, spawn = spawnSync, env = process.env) {
67
78
  delete commandEnv.POLYGRAPH_CAPTURE_TOKEN;
68
79
  }
69
80
 
70
- const result = spawn(command, args, {
81
+ const spawnOptions = {
71
82
  encoding: 'utf8',
72
83
  env: commandEnv,
73
84
  stdio: ['ignore', 'ignore', 'pipe'],
74
- });
85
+ };
86
+
87
+ let result = spawn(command, args, spawnOptions);
88
+
89
+ // POLYGRAPH_CLI may point at a plain JS entry that cannot be spawned
90
+ // directly: a dev build without the executable bit, or a platform that
91
+ // cannot exec scripts. A spawn that failed to LAUNCH ran nothing, so the
92
+ // retry under a Node runtime is side-effect free — and anything that
93
+ // spawns directly today keeps its exact behavior.
94
+ if (result?.error && /\.[cm]?js$/i.test(command)) {
95
+ result = spawn(nodeRuntime(), [command, ...args], spawnOptions);
96
+ }
75
97
 
76
98
  if (result?.error) throw result.error;
77
99
  if (result?.status !== 0) {
@@ -89,18 +111,30 @@ export function buildCommandHookLink(payload, agentType, env = process.env) {
89
111
  if (!payload || typeof payload !== 'object') return undefined;
90
112
  if (isManagedChildEnvironment(env)) return undefined;
91
113
 
92
- const agentSessionId = nonEmptyString(payload.session_id);
114
+ // Cursor payloads carry the id in both session_id and conversation_id;
115
+ // the fallback keeps the link working if one of them disappears.
116
+ const agentSessionId =
117
+ nonEmptyString(payload.session_id) ?? nonEmptyString(payload.conversation_id);
93
118
  if (!agentSessionId) return undefined;
94
119
 
120
+ // Cursor has no top-level cwd; workspace_roots[0] is the launch directory.
121
+ const workspaceRoot = Array.isArray(payload.workspace_roots)
122
+ ? nonEmptyString(payload.workspace_roots[0])
123
+ : undefined;
124
+
95
125
  const common = {
96
126
  agentType,
97
127
  agentSessionId,
98
- cwd: nonEmptyString(payload.cwd),
128
+ cwd: nonEmptyString(payload.cwd) ?? workspaceRoot,
99
129
  transcriptPath: nonEmptyString(payload.transcript_path),
100
130
  source: 'hook',
101
131
  };
102
132
 
103
- if (payload.hook_event_name === 'SessionStart') {
133
+ // Claude and Codex send PascalCase event names; cursor sends camelCase.
134
+ if (
135
+ payload.hook_event_name === 'SessionStart' ||
136
+ payload.hook_event_name === 'sessionStart'
137
+ ) {
104
138
  const polygraphSessionId = nonEmptyString(env.POLYGRAPH_SESSION_ID);
105
139
  if (polygraphSessionId) return { ...common, polygraphSessionId };
106
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/claude-plugin",
3
- "version": "0.4.50",
3
+ "version": "0.4.52",
4
4
  "description": "AI agent skills and subagents for Polygraph sessions, repository context, and coordination",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: adversarial-review
3
- description: Independent second-opinion review of a Polygraph session's work. USE WHEN user says "adversarial review", "review this session", or when the Polygraph CLI launches an agent with an instruction to load this skill.
3
+ description: Review a Polygraph session with independent per-repo reviewers and attach one consolidated review artifact.
4
4
 
5
5
  user-invocable: true
6
6
  allowed-tools:
@@ -14,10 +14,10 @@ allowed-tools:
14
14
 
15
15
  # Adversarial Review
16
16
 
17
- 1. **Pick the agent.** Ask whether Claude, Codex, or OpenCode should review `claude`, `codex`, or `opencode` for `spawn_agent`'s `agent` parameter. Skip if the user already named one.
17
+ 1. Skip this step entirely if a reviewer agent was already named — by the user, or in the instruction that launched you (e.g. from `polygraph session review --adversarial`); in that case use that agent and do not ask. Otherwise, **pick the agent**: Ask which should review—`claude`, `codex`, or `opencode` for `spawn_agent`'s `agent` parameter. If the user names a model, pass it via `spawn_agent`'s optional `model` parameter; don't ask about models.
18
18
  2. **Get the session description.**
19
- 3. **Get each repo's plan.** Ask each repo's agent to provide the plan.
20
- 4. **Delegate one reviewer per repo** in parallel, with `role: "reviewer"`. Pass the overall plan and that repo's plan, and ask it to review the code, identify issues, and return a summary. Do the delegation even for the "initiator" repo.
21
- 5. **Summarize.** Once every review is back, analyze them and present one summary to the user.
22
- 6. **Ask what next.** Address the feedback, upload the summary via `upload_artifact`, or continue with the session. Skip if the user already said.
19
+ 3. **Get each repo's plan.**
20
+ 4. **Delegate one reviewer per repo** in parallel, `role: "reviewer"`. Ask to review, identify issues. Do the delegation even for the "initiator" repo.
21
+ 5. **Summarize and attach.** Consolidate reviews into one consolidated Markdown review with per-repo sections and present it to the user. Then call `upload_artifact` once with `sessionId`, that review as `content`, `kind: "review"`, `format: "markdown"`, name `adversarial-review-YYYY-MM-DDTHH-mm-ssZ.md`. If the upload fails, report that separately without suppressing the review.
22
+ 6. **Ask what next.** Address feedback or continue. Skip if the user already said.
23
23
  7. If the user selects "address the feedback", pass each repo's feedback to the repo default agent (not the reviewer). The initiator should fix things itself without delegating.
@@ -20,10 +20,14 @@ spawn_agent(
20
20
  repo: "<org/repo-name>",
21
21
  instruction: "<the task instruction>",
22
22
  role: "<optional role>",
23
- context: "<optional context>"
23
+ context: "<optional context>",
24
+ agent: "<optional: claude | codex | opencode>",
25
+ model: "<optional model override>"
24
26
  )
25
27
  ```
26
28
 
29
+ `agent` picks the child's harness and `model` overrides its default model; include either only when the user named one.
30
+
27
31
  Write the instruction as if to a competent engineer who cannot see your conversation: state the goal, the constraints, and what "done" looks like. The child has its own repo and its own context; it inherits nothing from yours.
28
32
 
29
33
  Delegate to several repos in parallel by calling `spawn_agent` once per repo before waiting on any of them.
@@ -37,6 +41,11 @@ For each id, launch one background poller subagent whose entire job is to block
37
41
  - **Claude Code** — a background `Task` with `subagent_type: "polygraph:polygraph-delegate-subagent"`, `run_in_background: true`, and description `Delegate to <repo>`. Fall back to the bare agent name only if the namespaced form is not found.
38
42
  - **OpenCode** — invoke `@polygraph-delegate-subagent`.
39
43
  - **Codex** — launch `agent_type: "polygraph-delegate-subagent"` via Codex's own `spawn_agent`, and collect it with `wait_agent`.
44
+ - **Cursor** — a background `Task` with `subagent_type: "polygraph-delegate-subagent"`, `run_in_background: true`, and description `Delegate to <repo>`. Collect it with `Await`.
45
+
46
+ A collect step that returns while the poller subagent is still running has not failed. It has only reached the end of its collection window. Collect the same background-task id again, as many times as it takes for the poller subagent to stop. A poller that runs for several minutes is ordinary, and it is never a reason to take the wait back into the main conversation.
47
+
48
+ The background-task id is the handle your own harness returned when you launched the poller. It is not the Polygraph delegation id (`frontend-1`), which addresses the child agent. Once the poller subagent has finished, do not collect it again: read the child instead, as described below. A child that stops for attention also ends the poller, so treat that as a finished poller and not as a collection window running out.
40
49
 
41
50
  The poller has exactly one tool and cannot read logs. It exits with a few lines naming the repo, the id, and the final status. That message is a doorbell, not a report — it tells you the child is worth reading, and nothing about what the child did.
42
51