@ilya-lesikov/pi-pi 0.1.0
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/3p/pi-ask-user/index.ts +1835 -0
- package/3p/pi-ask-user/package.json +50 -0
- package/3p/pi-ask-user/single-select-layout.ts +203 -0
- package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
- package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
- package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
- package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
- package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
- package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
- package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
- package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
- package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
- package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
- package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
- package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
- package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
- package/3p/pi-lsp/package.json +54 -0
- package/3p/pi-plannotator/AGENTS.md +583 -0
- package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
- package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
- package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
- package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
- package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
- package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
- package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
- package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
- package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
- package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
- package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
- package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
- package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
- package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
- package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
- package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
- package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
- package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
- package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
- package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
- package/3p/pi-plannotator/package.json +53 -0
- package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
- package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
- package/3p/pi-plannotator/packages/ai/context.ts +250 -0
- package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
- package/3p/pi-plannotator/packages/ai/index.ts +105 -0
- package/3p/pi-plannotator/packages/ai/package.json +21 -0
- package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
- package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
- package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
- package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
- package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
- package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
- package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/ai/types.ts +379 -0
- package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
- package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
- package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
- package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
- package/3p/pi-plannotator/packages/server/browser.ts +245 -0
- package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
- package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
- package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
- package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
- package/3p/pi-plannotator/packages/server/config.ts +8 -0
- package/3p/pi-plannotator/packages/server/draft.ts +1 -0
- package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
- package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
- package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
- package/3p/pi-plannotator/packages/server/git.ts +141 -0
- package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
- package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
- package/3p/pi-plannotator/packages/server/ide.ts +43 -0
- package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
- package/3p/pi-plannotator/packages/server/image.ts +66 -0
- package/3p/pi-plannotator/packages/server/index.ts +648 -0
- package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
- package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
- package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
- package/3p/pi-plannotator/packages/server/jj.ts +86 -0
- package/3p/pi-plannotator/packages/server/p4.ts +417 -0
- package/3p/pi-plannotator/packages/server/package.json +40 -0
- package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
- package/3p/pi-plannotator/packages/server/pr.ts +146 -0
- package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
- package/3p/pi-plannotator/packages/server/project.ts +45 -0
- package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
- package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
- package/3p/pi-plannotator/packages/server/remote.ts +74 -0
- package/3p/pi-plannotator/packages/server/repo.ts +80 -0
- package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
- package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
- package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
- package/3p/pi-plannotator/packages/server/review.ts +1318 -0
- package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
- package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
- package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
- package/3p/pi-plannotator/packages/server/storage.ts +17 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
- package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
- package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
- package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
- package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
- package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
- package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
- package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
- package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
- package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
- package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
- package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
- package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
- package/3p/pi-plannotator/packages/shared/config.ts +262 -0
- package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
- package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
- package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
- package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
- package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
- package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
- package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
- package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
- package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
- package/3p/pi-plannotator/packages/shared/package.json +55 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
- package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
- package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
- package/3p/pi-plannotator/packages/shared/project.ts +71 -0
- package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
- package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
- package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
- package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
- package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
- package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
- package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
- package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
- package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
- package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
- package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
- package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
- package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
- package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/shared/types.ts +29 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
- package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
- package/3p/pi-subagents/package.json +50 -0
- package/3p/pi-subagents/src/agent-manager.ts +413 -0
- package/3p/pi-subagents/src/agent-runner.ts +502 -0
- package/3p/pi-subagents/src/agent-types.ts +248 -0
- package/3p/pi-subagents/src/context.ts +58 -0
- package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
- package/3p/pi-subagents/src/custom-agents.ts +137 -0
- package/3p/pi-subagents/src/default-agents.ts +144 -0
- package/3p/pi-subagents/src/env.ts +33 -0
- package/3p/pi-subagents/src/group-join.ts +141 -0
- package/3p/pi-subagents/src/index.ts +1811 -0
- package/3p/pi-subagents/src/invocation-config.ts +40 -0
- package/3p/pi-subagents/src/memory.ts +165 -0
- package/3p/pi-subagents/src/model-resolver.ts +81 -0
- package/3p/pi-subagents/src/output-file.ts +77 -0
- package/3p/pi-subagents/src/prompts.ts +85 -0
- package/3p/pi-subagents/src/skill-loader.ts +79 -0
- package/3p/pi-subagents/src/types.ts +111 -0
- package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
- package/3p/pi-subagents/src/worktree.ts +162 -0
- package/3p/pi-tasks/package.json +52 -0
- package/3p/pi-tasks/src/auto-clear.ts +91 -0
- package/3p/pi-tasks/src/index.ts +1150 -0
- package/3p/pi-tasks/src/process-tracker.ts +140 -0
- package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
- package/3p/pi-tasks/src/task-store.ts +324 -0
- package/3p/pi-tasks/src/tasks-config.ts +27 -0
- package/3p/pi-tasks/src/types.ts +40 -0
- package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
- package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
- package/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
- package/extensions/orchestrator/agents/explore.ts +33 -0
- package/extensions/orchestrator/agents/librarian.ts +43 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
- package/extensions/orchestrator/agents/planner.ts +61 -0
- package/extensions/orchestrator/agents/registry.ts +156 -0
- package/extensions/orchestrator/agents/task.ts +50 -0
- package/extensions/orchestrator/agents/tool-routing.ts +84 -0
- package/extensions/orchestrator/ast-search.ts +85 -0
- package/extensions/orchestrator/cbm.ts +330 -0
- package/extensions/orchestrator/command-handlers.test.ts +163 -0
- package/extensions/orchestrator/command-handlers.ts +116 -0
- package/extensions/orchestrator/commands.test.ts +81 -0
- package/extensions/orchestrator/commands.ts +75 -0
- package/extensions/orchestrator/config.test.ts +221 -0
- package/extensions/orchestrator/config.ts +230 -0
- package/extensions/orchestrator/context.test.ts +293 -0
- package/extensions/orchestrator/context.ts +206 -0
- package/extensions/orchestrator/event-handlers.test.ts +190 -0
- package/extensions/orchestrator/event-handlers.ts +1416 -0
- package/extensions/orchestrator/exa.ts +104 -0
- package/extensions/orchestrator/flant-infra.ts +486 -0
- package/extensions/orchestrator/index.ts +80 -0
- package/extensions/orchestrator/integration.test.ts +1214 -0
- package/extensions/orchestrator/orchestrator.test.ts +252 -0
- package/extensions/orchestrator/orchestrator.ts +538 -0
- package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
- package/extensions/orchestrator/phases/brainstorm.ts +273 -0
- package/extensions/orchestrator/phases/implementation.ts +40 -0
- package/extensions/orchestrator/phases/machine.test.ts +293 -0
- package/extensions/orchestrator/phases/machine.ts +209 -0
- package/extensions/orchestrator/phases/planning.ts +255 -0
- package/extensions/orchestrator/phases/review.ts +193 -0
- package/extensions/orchestrator/plannotator.ts +56 -0
- package/extensions/orchestrator/pp-menu.ts +866 -0
- package/extensions/orchestrator/state.test.ts +343 -0
- package/extensions/orchestrator/state.ts +237 -0
- package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
- package/extensions/orchestrator/validate-artifacts.ts +272 -0
- package/extensions/orchestrator/vendor.d.ts +26 -0
- package/package.json +73 -0
- package/scripts/postinstall.sh +18 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform browser opening utility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { $ } from "bun";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import { getPlannotatorDataDir } from "@plannotator/shared/data-dir";
|
|
11
|
+
import { loadConfig, resolveUseGlimpse } from "@plannotator/shared/config";
|
|
12
|
+
|
|
13
|
+
const IPC_REGISTRY = path.join(getPlannotatorDataDir(), "vscode-ipc.json");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Common "no-op" values for $BROWSER used by headless/background environments
|
|
17
|
+
* (e.g. Claude Code's agent view sets BROWSER=true) to signal "do not actually
|
|
18
|
+
* launch a browser". Treating these as if the variable were unset prevents
|
|
19
|
+
* silently shelling out to e.g. `true <url>`, which exits 0 without opening
|
|
20
|
+
* anything and leaves the Plannotator server hanging on waitForDecision().
|
|
21
|
+
*/
|
|
22
|
+
const NOOP_BROWSER_VALUES = new Set(["true", "false", "none", ":", "0", "1"]);
|
|
23
|
+
|
|
24
|
+
export function isNoOpBrowserSentinel(value: string | undefined): boolean {
|
|
25
|
+
if (!value) return false;
|
|
26
|
+
return NOOP_BROWSER_VALUES.has(value.trim().toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Try opening URL via VS Code extension IPC registry.
|
|
31
|
+
* Falls back when env vars (PLANNOTATOR_BROWSER) aren't available to the process.
|
|
32
|
+
*/
|
|
33
|
+
async function tryVscodeIpc(url: string): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
const registry: Record<string, number> = JSON.parse(
|
|
36
|
+
fs.readFileSync(IPC_REGISTRY, "utf-8"),
|
|
37
|
+
);
|
|
38
|
+
const cwd = process.cwd();
|
|
39
|
+
// Find the best matching workspace (longest prefix match)
|
|
40
|
+
let bestMatch = "";
|
|
41
|
+
let bestPort = 0;
|
|
42
|
+
for (const [workspace, port] of Object.entries(registry)) {
|
|
43
|
+
if (cwd.startsWith(workspace) && workspace.length > bestMatch.length) {
|
|
44
|
+
bestMatch = workspace;
|
|
45
|
+
bestPort = port;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!bestPort) return false;
|
|
49
|
+
const ipcUrl = new URL("/open", `http://127.0.0.1:${bestPort}`);
|
|
50
|
+
ipcUrl.searchParams.set("url", url);
|
|
51
|
+
const resp = await fetch(ipcUrl.toString());
|
|
52
|
+
return resp.ok;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if running in WSL (Windows Subsystem for Linux)
|
|
60
|
+
*/
|
|
61
|
+
export async function isWSL(): Promise<boolean> {
|
|
62
|
+
if (process.platform !== "linux") {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (os.release().toLowerCase().includes("microsoft")) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fallback: check /proc/version for WSL signature (if available)
|
|
71
|
+
try {
|
|
72
|
+
const file = Bun.file("/proc/version");
|
|
73
|
+
if (await file.exists()) {
|
|
74
|
+
const content = await file.text();
|
|
75
|
+
return (
|
|
76
|
+
content.toLowerCase().includes("wsl") ||
|
|
77
|
+
content.toLowerCase().includes("microsoft")
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
// Ignore errors reading /proc/version
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Open a URL in the browser
|
|
88
|
+
*
|
|
89
|
+
* Uses PLANNOTATOR_BROWSER env var if set, otherwise uses system default.
|
|
90
|
+
* - macOS: Set to app name ("Google Chrome") or path ("/Applications/Firefox.app")
|
|
91
|
+
* - Linux/Windows/WSL: Set to executable path ("/usr/bin/firefox")
|
|
92
|
+
*
|
|
93
|
+
* Fails silently if browser can't be opened
|
|
94
|
+
*/
|
|
95
|
+
export function shouldTryRemoteBrowserFallback(isRemote: boolean): boolean {
|
|
96
|
+
if (!isRemote) return false;
|
|
97
|
+
const plannotatorBrowser = process.env.PLANNOTATOR_BROWSER;
|
|
98
|
+
const browser = process.env.BROWSER;
|
|
99
|
+
// Treat headless sentinels (e.g. BROWSER=true from Claude Code's agent view)
|
|
100
|
+
// as if no real browser handler were configured, so the IPC fallback still runs.
|
|
101
|
+
const hasRealHandler =
|
|
102
|
+
(plannotatorBrowser && !isNoOpBrowserSentinel(plannotatorBrowser)) ||
|
|
103
|
+
(browser && !isNoOpBrowserSentinel(browser));
|
|
104
|
+
return !hasRealHandler;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildGlimpseHtml(url: string): string {
|
|
108
|
+
const encodedUrl = JSON.stringify(url);
|
|
109
|
+
return `<!doctype html>
|
|
110
|
+
<html>
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="utf-8" />
|
|
113
|
+
<title>Plannotator</title>
|
|
114
|
+
<style>
|
|
115
|
+
html, body { width: 100%; height: 100%; margin: 0; }
|
|
116
|
+
body { overflow: hidden; background: #0f1115; }
|
|
117
|
+
</style>
|
|
118
|
+
</head>
|
|
119
|
+
<body>
|
|
120
|
+
<script>
|
|
121
|
+
location.replace(${encodedUrl});
|
|
122
|
+
</script>
|
|
123
|
+
</body>
|
|
124
|
+
</html>`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function openGlimpse(url: string): Promise<boolean> {
|
|
128
|
+
const glimpseCli = Bun.which("glimpseui");
|
|
129
|
+
if (!glimpseCli) return false;
|
|
130
|
+
|
|
131
|
+
const args = [
|
|
132
|
+
"--width",
|
|
133
|
+
String(Number(process.env.PLANNOTATOR_GLIMPSE_WIDTH || 1280)),
|
|
134
|
+
"--height",
|
|
135
|
+
String(Number(process.env.PLANNOTATOR_GLIMPSE_HEIGHT || 900)),
|
|
136
|
+
"--title",
|
|
137
|
+
"Plannotator",
|
|
138
|
+
"--open-links",
|
|
139
|
+
];
|
|
140
|
+
const html = buildGlimpseHtml(url);
|
|
141
|
+
|
|
142
|
+
// On Windows, `glimpseui` resolves to an npm script shim, not an exe, which
|
|
143
|
+
// spawn() can't launch without a shell. `shell: true` would break the stdin
|
|
144
|
+
// HTML pipe below, so run the package entry with node directly instead.
|
|
145
|
+
let command = glimpseCli;
|
|
146
|
+
let spawnArgs = args;
|
|
147
|
+
if (process.platform === "win32" && !/\.exe$/i.test(glimpseCli)) {
|
|
148
|
+
const node = Bun.which("node");
|
|
149
|
+
const entry = path.join(
|
|
150
|
+
path.dirname(glimpseCli),
|
|
151
|
+
"node_modules",
|
|
152
|
+
"glimpseui",
|
|
153
|
+
"bin",
|
|
154
|
+
"glimpse.mjs"
|
|
155
|
+
);
|
|
156
|
+
if (node && fs.existsSync(entry)) {
|
|
157
|
+
command = node;
|
|
158
|
+
spawnArgs = [entry, ...args];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return await new Promise<boolean>((resolve) => {
|
|
163
|
+
let settled = false;
|
|
164
|
+
let successTimer: ReturnType<typeof setTimeout> | undefined;
|
|
165
|
+
const finish = (opened: boolean) => {
|
|
166
|
+
if (settled) return;
|
|
167
|
+
settled = true;
|
|
168
|
+
if (successTimer) clearTimeout(successTimer);
|
|
169
|
+
resolve(opened);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const child = spawn(command, spawnArgs, {
|
|
173
|
+
detached: true,
|
|
174
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
175
|
+
});
|
|
176
|
+
successTimer = setTimeout(() => {
|
|
177
|
+
child.unref();
|
|
178
|
+
finish(true);
|
|
179
|
+
}, 750);
|
|
180
|
+
|
|
181
|
+
child.once("error", () => finish(false));
|
|
182
|
+
child.once("exit", () => finish(false));
|
|
183
|
+
child.stdin.once("error", () => finish(false));
|
|
184
|
+
child.stdin.end(html);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function openBrowser(
|
|
189
|
+
url: string,
|
|
190
|
+
options?: { isRemote?: boolean; useGlimpse?: boolean }
|
|
191
|
+
): Promise<boolean> {
|
|
192
|
+
try {
|
|
193
|
+
const rawPlannotatorBrowser = process.env.PLANNOTATOR_BROWSER;
|
|
194
|
+
const rawBrowser = process.env.BROWSER;
|
|
195
|
+
const plannotatorBrowser = isNoOpBrowserSentinel(rawPlannotatorBrowser)
|
|
196
|
+
? undefined
|
|
197
|
+
: rawPlannotatorBrowser;
|
|
198
|
+
const envBrowser = isNoOpBrowserSentinel(rawBrowser) ? undefined : rawBrowser;
|
|
199
|
+
const browser = plannotatorBrowser || envBrowser;
|
|
200
|
+
const isRemote = options?.isRemote ?? false;
|
|
201
|
+
if (shouldTryRemoteBrowserFallback(isRemote)) {
|
|
202
|
+
const openedViaIpc = await tryVscodeIpc(url);
|
|
203
|
+
if (openedViaIpc) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (options?.useGlimpse && !browser && !isRemote && resolveUseGlimpse(loadConfig())) {
|
|
209
|
+
const openedViaGlimpse = await openGlimpse(url);
|
|
210
|
+
if (openedViaGlimpse) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const platform = process.platform;
|
|
216
|
+
const wsl = await isWSL();
|
|
217
|
+
|
|
218
|
+
if (browser) {
|
|
219
|
+
if (plannotatorBrowser && platform === "darwin") {
|
|
220
|
+
if (plannotatorBrowser.includes("/") && !plannotatorBrowser.endsWith(".app")) {
|
|
221
|
+
await $`${plannotatorBrowser} ${url}`.quiet();
|
|
222
|
+
} else {
|
|
223
|
+
await $`open -a ${plannotatorBrowser} ${url}`.quiet();
|
|
224
|
+
}
|
|
225
|
+
} else if ((platform === "win32" || wsl) && plannotatorBrowser) {
|
|
226
|
+
await $`cmd.exe /c start "" ${plannotatorBrowser} ${url}`.quiet();
|
|
227
|
+
} else {
|
|
228
|
+
await $`${browser} ${url}`.quiet();
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
// Default system browser
|
|
232
|
+
if (platform === "win32" || wsl) {
|
|
233
|
+
await $`cmd.exe /c start ${url}`.quiet();
|
|
234
|
+
} else if (platform === "darwin") {
|
|
235
|
+
await $`open ${url}`.quiet();
|
|
236
|
+
} else {
|
|
237
|
+
await $`xdg-open ${url}`.quiet();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
} catch {
|
|
242
|
+
// Shell-based open failed — try VS Code IPC registry as fallback
|
|
243
|
+
return tryVscodeIpc(url);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { toRelativePath } from "./path-utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code Review Agent — prompt, command builder, and JSONL output parser.
|
|
5
|
+
*
|
|
6
|
+
* Claude has its own review model (severity-based findings with reasoning traces)
|
|
7
|
+
* separate from Codex's priority-based model. The transform layer normalizes
|
|
8
|
+
* both into the shared annotation format.
|
|
9
|
+
*
|
|
10
|
+
* Claude uses --json-schema (inline JSON + Ajv validation with retries) and
|
|
11
|
+
* --output-format stream-json for live JSONL streaming. The final event is
|
|
12
|
+
* type:"result" with structured_output containing validated findings.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Types
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export type ClaudeSeverity = "important" | "nit" | "pre_existing";
|
|
20
|
+
|
|
21
|
+
export interface ClaudeFinding {
|
|
22
|
+
severity: ClaudeSeverity;
|
|
23
|
+
file: string;
|
|
24
|
+
line: number;
|
|
25
|
+
end_line: number;
|
|
26
|
+
description: string;
|
|
27
|
+
reasoning: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ClaudeReviewOutput {
|
|
31
|
+
findings: ClaudeFinding[];
|
|
32
|
+
summary: {
|
|
33
|
+
important: number;
|
|
34
|
+
nit: number;
|
|
35
|
+
pre_existing: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Schema — Claude's own severity-based model
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export const CLAUDE_REVIEW_SCHEMA_JSON = JSON.stringify({
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
findings: {
|
|
47
|
+
type: "array",
|
|
48
|
+
items: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
severity: { type: "string", enum: ["important", "nit", "pre_existing"] },
|
|
52
|
+
file: { type: "string" },
|
|
53
|
+
line: { type: "integer" },
|
|
54
|
+
end_line: { type: "integer" },
|
|
55
|
+
description: { type: "string" },
|
|
56
|
+
reasoning: { type: "string" },
|
|
57
|
+
},
|
|
58
|
+
required: ["severity", "file", "line", "end_line", "description", "reasoning"],
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
summary: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
important: { type: "integer" },
|
|
66
|
+
nit: { type: "integer" },
|
|
67
|
+
pre_existing: { type: "integer" },
|
|
68
|
+
},
|
|
69
|
+
required: ["important", "nit", "pre_existing"],
|
|
70
|
+
additionalProperties: false,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ["findings", "summary"],
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Review prompt — converges open-source Claude Code review + remote service
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
export const CLAUDE_REVIEW_PROMPT = `# Claude Code Review System Prompt
|
|
82
|
+
|
|
83
|
+
## Identity
|
|
84
|
+
You are a code review system. Your job is to find bugs that would break
|
|
85
|
+
production. You are not a linter, formatter, or style checker unless
|
|
86
|
+
project guidance files explicitly expand your scope.
|
|
87
|
+
|
|
88
|
+
## Pipeline
|
|
89
|
+
|
|
90
|
+
Step 1: Gather context
|
|
91
|
+
- Retrieve the PR diff or local diff (gh pr diff, git diff, or jj diff)
|
|
92
|
+
- Read CLAUDE.md and REVIEW.md at the repo root and in every directory
|
|
93
|
+
containing modified files
|
|
94
|
+
- Build a map of which rules apply to which file paths
|
|
95
|
+
- Identify any skip rules (paths, patterns, or file types to ignore)
|
|
96
|
+
|
|
97
|
+
Step 2: Launch 4 parallel review agents
|
|
98
|
+
|
|
99
|
+
Agent 1 — Bug + Regression (Opus-level reasoning)
|
|
100
|
+
Scan for logic errors, regressions, broken edge cases, build failures,
|
|
101
|
+
and code that will produce wrong results. Focus on the diff but read
|
|
102
|
+
surrounding code to understand call sites and data flow. Flag only
|
|
103
|
+
issues where the code is demonstrably wrong — not stylistic concerns,
|
|
104
|
+
not missing tests, not "could be cleaner."
|
|
105
|
+
|
|
106
|
+
Agent 2 — Security + Deep Analysis (Opus-level reasoning)
|
|
107
|
+
Look for security vulnerabilities with concrete exploit paths, race
|
|
108
|
+
conditions, incorrect assumptions about trust boundaries, and subtle
|
|
109
|
+
issues in introduced code. Read surrounding code for context. Do not
|
|
110
|
+
flag theoretical risks without a plausible path to harm.
|
|
111
|
+
|
|
112
|
+
Agent 3 — Code Quality + Reusability (Sonnet-level reasoning)
|
|
113
|
+
Look for code smells, unnecessary duplication, missed opportunities to
|
|
114
|
+
reuse existing utilities or patterns in the codebase, overly complex
|
|
115
|
+
implementations that could be simpler, and elegance issues. Read the
|
|
116
|
+
surrounding codebase to understand existing patterns before flagging.
|
|
117
|
+
Only flag issues a senior engineer would care about.
|
|
118
|
+
|
|
119
|
+
Agent 4 — Guideline Compliance (Haiku-level reasoning)
|
|
120
|
+
Audit changes against rules from CLAUDE.md and REVIEW.md gathered in
|
|
121
|
+
Step 1. Only flag clear, unambiguous violations where you can cite the
|
|
122
|
+
exact rule broken. If a PR makes a CLAUDE.md statement outdated, flag
|
|
123
|
+
that the docs need updating. Respect all skip rules — never flag files
|
|
124
|
+
or patterns that guidance says to ignore.
|
|
125
|
+
|
|
126
|
+
All agents:
|
|
127
|
+
- Do not duplicate each other's findings
|
|
128
|
+
- Do not flag issues in paths excluded by guidance files
|
|
129
|
+
- Provide file, line number, and a concise description for each candidate
|
|
130
|
+
|
|
131
|
+
Step 3: Validate each candidate finding
|
|
132
|
+
For each candidate, launch a validation agent. The validator:
|
|
133
|
+
- Traces the actual code path to confirm the issue is real
|
|
134
|
+
- Checks whether the issue is handled elsewhere (try/catch, upstream
|
|
135
|
+
guard, fallback logic, type system guarantees)
|
|
136
|
+
- Confirms the finding is not a false positive with high confidence
|
|
137
|
+
- If validation fails, drop the finding silently
|
|
138
|
+
- If validation passes, write a clear reasoning chain explaining how
|
|
139
|
+
the issue was confirmed — this becomes the \`reasoning\` field
|
|
140
|
+
|
|
141
|
+
Step 4: Classify each validated finding
|
|
142
|
+
Assign exactly one severity:
|
|
143
|
+
|
|
144
|
+
important — A bug that should be fixed before merging. Build failures,
|
|
145
|
+
clear logic errors, security vulnerabilities with exploit paths, data
|
|
146
|
+
loss risks, race conditions with observable consequences.
|
|
147
|
+
|
|
148
|
+
nit — A minor issue worth fixing but non-blocking. Style deviations
|
|
149
|
+
from project guidelines, code quality concerns, edge cases that are
|
|
150
|
+
unlikely but worth noting, convention violations that don't affect
|
|
151
|
+
correctness.
|
|
152
|
+
|
|
153
|
+
pre_existing — A bug that exists in the surrounding codebase but was
|
|
154
|
+
NOT introduced by this PR. Only flag when directly relevant to the
|
|
155
|
+
changed code path.
|
|
156
|
+
|
|
157
|
+
Step 5: Deduplicate and rank
|
|
158
|
+
- Merge findings that describe the same underlying issue from different
|
|
159
|
+
agents — keep the most specific description and the highest severity
|
|
160
|
+
- Sort by severity: important → nit → pre_existing
|
|
161
|
+
- Within each severity, sort by file path and line number
|
|
162
|
+
|
|
163
|
+
Step 6: Return structured JSON output matching the schema.
|
|
164
|
+
If no issues are found, return an empty findings array with zeroed summary.
|
|
165
|
+
|
|
166
|
+
## Hard constraints
|
|
167
|
+
- Never approve or block the PR
|
|
168
|
+
- Never comment on formatting or code style unless guidance files say to
|
|
169
|
+
- Never flag missing test coverage unless guidance files say to
|
|
170
|
+
- Never invent rules — only enforce what CLAUDE.md or REVIEW.md state
|
|
171
|
+
- Never flag issues in skipped paths or generated files unless guidance
|
|
172
|
+
explicitly includes them
|
|
173
|
+
- Prefer silence over false positives — when in doubt, drop the finding
|
|
174
|
+
- Do NOT post any comments to GitHub or GitLab
|
|
175
|
+
- Do NOT use gh pr comment or any commenting tool
|
|
176
|
+
- Your only output is the structured JSON findings`;
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Command builder
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
export interface ClaudeCommandResult {
|
|
183
|
+
command: string[];
|
|
184
|
+
/** Prompt text to write to stdin (Claude reads prompt from stdin, not argv). */
|
|
185
|
+
stdinPrompt: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Build the `claude -p` command. Prompt is passed via stdin, not as a
|
|
190
|
+
* positional arg — avoids quoting issues, argv limits, and variadic flag conflicts.
|
|
191
|
+
*/
|
|
192
|
+
export function buildClaudeCommand(prompt: string, model: string = "claude-opus-4-7", effort?: string): ClaudeCommandResult {
|
|
193
|
+
const allowedTools = [
|
|
194
|
+
"Agent", "Read", "Glob", "Grep",
|
|
195
|
+
// GitHub CLI
|
|
196
|
+
"Bash(gh pr view:*)", "Bash(gh pr diff:*)", "Bash(gh pr list:*)",
|
|
197
|
+
"Bash(gh issue view:*)", "Bash(gh issue list:*)",
|
|
198
|
+
"Bash(gh api repos/*/*/pulls/*)", "Bash(gh api repos/*/*/pulls/*/files*)",
|
|
199
|
+
"Bash(gh api repos/*/*/pulls/*/comments*)", "Bash(gh api repos/*/*/issues/*/comments*)",
|
|
200
|
+
// GitLab CLI
|
|
201
|
+
"Bash(glab mr view:*)", "Bash(glab mr diff:*)", "Bash(glab mr list:*)",
|
|
202
|
+
"Bash(glab api:*)",
|
|
203
|
+
// Git (read-only)
|
|
204
|
+
"Bash(git status:*)", "Bash(git diff:*)", "Bash(git log:*)",
|
|
205
|
+
"Bash(git show:*)", "Bash(git blame:*)", "Bash(git branch:*)",
|
|
206
|
+
"Bash(git grep:*)", "Bash(git ls-remote:*)", "Bash(git ls-tree:*)",
|
|
207
|
+
"Bash(git merge-base:*)", "Bash(git remote:*)", "Bash(git rev-parse:*)",
|
|
208
|
+
"Bash(git show-ref:*)", "Bash(git -C:*)",
|
|
209
|
+
// JJ (read-only)
|
|
210
|
+
"Bash(jj status:*)", "Bash(jj diff:*)", "Bash(jj log:*)",
|
|
211
|
+
"Bash(jj show:*)", "Bash(jj file show:*)", "Bash(jj cat:*)",
|
|
212
|
+
"Bash(jj bookmark list:*)",
|
|
213
|
+
"Bash(wc:*)",
|
|
214
|
+
].join(",");
|
|
215
|
+
|
|
216
|
+
const disallowedTools = [
|
|
217
|
+
"Edit", "Write", "NotebookEdit", "WebFetch", "WebSearch",
|
|
218
|
+
"Bash(python:*)", "Bash(python3:*)", "Bash(node:*)", "Bash(npx:*)",
|
|
219
|
+
"Bash(bun:*)", "Bash(bunx:*)", "Bash(sh:*)", "Bash(bash:*)", "Bash(zsh:*)",
|
|
220
|
+
"Bash(curl:*)", "Bash(wget:*)",
|
|
221
|
+
].join(",");
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
command: [
|
|
225
|
+
"claude", "-p",
|
|
226
|
+
"--permission-mode", "dontAsk",
|
|
227
|
+
"--output-format", "stream-json",
|
|
228
|
+
"--verbose",
|
|
229
|
+
"--json-schema", CLAUDE_REVIEW_SCHEMA_JSON,
|
|
230
|
+
"--no-session-persistence",
|
|
231
|
+
"--model", model,
|
|
232
|
+
...(effort ? ["--effort", effort] : []),
|
|
233
|
+
"--tools", "Agent,Bash,Read,Glob,Grep",
|
|
234
|
+
"--allowedTools", allowedTools,
|
|
235
|
+
"--disallowedTools", disallowedTools,
|
|
236
|
+
],
|
|
237
|
+
stdinPrompt: prompt,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// JSONL stream output parser
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Parse Claude Code's stream-json output (JSONL).
|
|
247
|
+
* Extracts structured_output from the final type:"result" event.
|
|
248
|
+
*/
|
|
249
|
+
export function parseClaudeStreamOutput(stdout: string): ClaudeReviewOutput | null {
|
|
250
|
+
if (!stdout.trim()) return null;
|
|
251
|
+
|
|
252
|
+
const lines = stdout.trim().split('\n');
|
|
253
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
254
|
+
const line = lines[i].trim();
|
|
255
|
+
if (!line) continue;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const event = JSON.parse(line);
|
|
259
|
+
|
|
260
|
+
if (event.type === 'result') {
|
|
261
|
+
if (event.is_error) return null;
|
|
262
|
+
|
|
263
|
+
const output = event.structured_output;
|
|
264
|
+
if (!output || !Array.isArray(output.findings)) return null;
|
|
265
|
+
|
|
266
|
+
return output as ClaudeReviewOutput;
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
// Not valid JSON — skip
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// Finding transform — Claude findings → external annotations
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
/** Transform Claude findings into the external annotation format. */
|
|
281
|
+
export function transformClaudeFindings(
|
|
282
|
+
findings: ClaudeFinding[],
|
|
283
|
+
source: string,
|
|
284
|
+
cwd?: string,
|
|
285
|
+
pathTransform?: (path: string) => string,
|
|
286
|
+
): Array<{
|
|
287
|
+
source: string;
|
|
288
|
+
filePath: string;
|
|
289
|
+
lineStart: number;
|
|
290
|
+
lineEnd: number;
|
|
291
|
+
type: string;
|
|
292
|
+
side: string;
|
|
293
|
+
scope: string;
|
|
294
|
+
text: string;
|
|
295
|
+
severity: ClaudeSeverity;
|
|
296
|
+
reasoning: string;
|
|
297
|
+
author: string;
|
|
298
|
+
}> {
|
|
299
|
+
return findings
|
|
300
|
+
.filter(f => f.file && typeof f.line === "number")
|
|
301
|
+
.map(f => ({
|
|
302
|
+
source,
|
|
303
|
+
filePath: pathTransform
|
|
304
|
+
? pathTransform(toRelativePath(f.file, cwd))
|
|
305
|
+
: toRelativePath(f.file, cwd),
|
|
306
|
+
lineStart: f.line,
|
|
307
|
+
lineEnd: f.end_line ?? f.line,
|
|
308
|
+
type: "comment",
|
|
309
|
+
side: "new",
|
|
310
|
+
scope: "line",
|
|
311
|
+
text: `[${f.severity}] ${f.description}`,
|
|
312
|
+
severity: f.severity,
|
|
313
|
+
reasoning: f.reasoning,
|
|
314
|
+
author: "Claude Code",
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
// Live log formatter
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Extract log-worthy content from a JSONL line for the LiveLogViewer.
|
|
324
|
+
* Returns a human-readable string, or null if the line should be skipped.
|
|
325
|
+
*/
|
|
326
|
+
export function formatClaudeLogEvent(line: string): string | null {
|
|
327
|
+
try {
|
|
328
|
+
const event = JSON.parse(line);
|
|
329
|
+
|
|
330
|
+
// Skip the final result event — handled separately
|
|
331
|
+
if (event.type === 'result') return null;
|
|
332
|
+
|
|
333
|
+
// Assistant messages (the agent's thinking/responses)
|
|
334
|
+
if (event.type === 'assistant' && event.message?.content) {
|
|
335
|
+
const parts = Array.isArray(event.message.content) ? event.message.content : [event.message.content];
|
|
336
|
+
const texts = parts
|
|
337
|
+
.filter((p: any) => p.type === 'text' && p.text)
|
|
338
|
+
.map((p: any) => p.text);
|
|
339
|
+
if (texts.length > 0) return texts.join('\n');
|
|
340
|
+
|
|
341
|
+
// Tool use events (only reached if no text parts found)
|
|
342
|
+
const tools = parts.filter((p: any) => p.type === 'tool_use');
|
|
343
|
+
if (tools.length > 0) {
|
|
344
|
+
return tools.map((t: any) => `[${t.name}] ${typeof t.input === 'string' ? t.input.slice(0, 100) : JSON.stringify(t.input).slice(0, 100)}`).join('\n');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return null;
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code navigation — Bun runtime adapter and request handler.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type CodeNavRequest,
|
|
7
|
+
type CodeNavRuntime,
|
|
8
|
+
type CodeNavResponse,
|
|
9
|
+
resolveCodeNav,
|
|
10
|
+
validateCodeNavRequest,
|
|
11
|
+
extractChangedFiles,
|
|
12
|
+
} from "@plannotator/shared/code-nav";
|
|
13
|
+
|
|
14
|
+
export type { CodeNavRequest, CodeNavResponse };
|
|
15
|
+
|
|
16
|
+
const bunCodeNavRuntime: CodeNavRuntime = {
|
|
17
|
+
async runCommand(command, args, options) {
|
|
18
|
+
let proc;
|
|
19
|
+
try {
|
|
20
|
+
proc = Bun.spawn([command, ...args], {
|
|
21
|
+
cwd: options?.cwd,
|
|
22
|
+
stdout: "pipe",
|
|
23
|
+
stderr: "pipe",
|
|
24
|
+
});
|
|
25
|
+
} catch {
|
|
26
|
+
return { stdout: "", stderr: "command not found", exitCode: 1 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
30
|
+
if (options?.timeoutMs) {
|
|
31
|
+
timer = setTimeout(() => proc.kill(), options.timeoutMs);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
35
|
+
new Response(proc.stdout).text(),
|
|
36
|
+
new Response(proc.stderr).text(),
|
|
37
|
+
proc.exited,
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
if (timer) clearTimeout(timer);
|
|
41
|
+
return { stdout, stderr, exitCode };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export async function handleCodeNavResolve(
|
|
46
|
+
req: Request,
|
|
47
|
+
cwd: string,
|
|
48
|
+
changedFiles: string[],
|
|
49
|
+
): Promise<Response> {
|
|
50
|
+
try {
|
|
51
|
+
const body = (await req.json()) as CodeNavRequest;
|
|
52
|
+
const error = validateCodeNavRequest(body);
|
|
53
|
+
if (error) {
|
|
54
|
+
return Response.json({ error }, { status: 400 });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = await resolveCodeNav(
|
|
58
|
+
bunCodeNavRuntime,
|
|
59
|
+
body,
|
|
60
|
+
cwd,
|
|
61
|
+
changedFiles,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return Response.json(result);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return Response.json(
|
|
67
|
+
{ error: err instanceof Error ? err.message : "Code navigation failed" },
|
|
68
|
+
{ status: 500 },
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { extractChangedFiles };
|