Skip to content

Commit f96758d

Browse files
backnotpropclaude
andauthored
feat(pi): complete Pi server rewrite — modular architecture, full Bun parity, shared code extraction (#382)
* feat(pi): add missing endpoints to plan, review, and annotate servers Phase 1-3 of Pi endpoint parity: Plan server: image, upload, draft, editor-annotations, agents, favicon, linked documents, Obsidian vaults/files/doc, file browser, VS Code diff Annotate server: image, upload, draft, favicon, linked documents, file browser Review server: extract shared handlers, add favicon Shared utilities extracted from review server inline code into reusable functions (handleImageRequest, handleUploadRequest, handleDraftRequest, handleFavicon). Reference handlers (doc, Obsidian, file browser) implemented using Node.js fs APIs replacing Bun.Glob/Bun.file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(pi): add PR review endpoints and Node.js PR runtime adapter Phase 4 of Pi endpoint parity: - Node.js PRRuntime using child_process.spawn (matches Bun adapter pattern) - GET /api/pr-context — fetch PR summary, comments, checks - POST /api/pr-action — submit review to GitHub/GitLab - PR mode guards on /api/diff/switch and /api/git-add - /api/diff response includes prMetadata and platformUser in PR mode - /api/file-content fetches from platform API in PR mode - Build script copies pr-provider, pr-github, pr-gitlab from shared Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(pi): wire AI backbone with Node.js Pi SDK provider Phase 5 of Pi endpoint parity: - Create packages/ai/providers/pi-sdk-node.ts — PiProcessNode class using child_process.spawn instead of Bun.spawn, same RPC protocol - Register 4 AI providers in Pi review server (claude-agent-sdk, codex-sdk, pi-sdk-node, opencode-sdk) with graceful degradation - Route /api/ai/* endpoints through createAIEndpoints handlers - Pipe Web Response → node:http response with ReadableStream support for SSE streaming - Dispose AI sessions and registry on server stop Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(pi): address parity audit findings across all three servers Plan server: - /api/plan: add repoInfo and projectRoot to response - /api/approve: pass agentSwitch and permissionMode in decision - Update decision promise type to include agentSwitch, permissionMode Review server: - /api/diff/switch: pass gitContext.cwd to runGitDiff - /api/file-content: pass gitContext.cwd to getFileContentsForDiffCore - /api/git-add: add fallback to gitContext.cwd when worktree parse fails Annotate server: - /api/plan: add repoInfo and projectRoot to response - /api/feedback: capture annotations array (was silently dropped) - Update decision promise type to include annotations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(pi): complete parity — integrations, planSave, save-notes Ports all remaining missing functionality: - Node.js versions of saveToObsidian, saveToBear, saveToOctarine (Bun.write → writeFileSync, Bun.$ → spawn) - Node.js detectProjectNameSync (Bun.$ → execSync) - extractTags, generateFrontmatter, generateFilename, extractTitle - POST /api/save-notes — decoupled note saving - POST /api/approve — full implementation: note integrations, planSave snapshots, saveAnnotations, saveFinalSnapshot - POST /api/deny — planSave snapshots on denial - Import saveAnnotations, saveFinalSnapshot from storage.js Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(pi): wire domain module imports and fix type errors - Add all missing imports from ./server/* domain modules to server.ts - Export interfaces from integrations.ts (ObsidianConfig, BearConfig, etc.) - Move toWebRequest to helpers.ts, remove duplicate from handlers.ts - Add git() helper to project.ts (was in server.ts, needed by getRepoInfo) - Fix os default import → named imports in handlers.ts and network.ts - Fix readdirSync Dirent type in reference.ts - Fix Headers.entries() → forEach for Node compat in AI endpoint piping - Fix ReadableStream type cast in AI SSE streaming - Fix matchAll iterator compat in integrations.ts (use while + exec) - Cast pi-sdk provider config to any (PiSDKConfig not in base union) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(pi): move generated shared files to generated/ directory Moves all build-time copied shared files (feedback-templates, review-core, storage, draft, project, pr-provider, pr-github, pr-gitlab) from the pi-extension root into generated/ subdirectory. Updates build script to output there. Updates all imports in server.ts, index.ts, and server/ domain modules to use ./generated/ paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(pi): replace hand-maintained utils.ts with generated checklist utils.ts was a manual copy of parseChecklist, extractDoneSteps, and markCompletedSteps from packages/shared/checklist.ts. Add checklist to the build-time copy list and import from generated/checklist.js. Delete the redundant utils.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(pi): gitignore generated/ and built HTML files These are build artifacts created by `bun run build:pi`. Untrack them and add .gitignore to prevent re-adding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(pi): split server.ts into domain-organized modules - server.ts is now a barrel re-exporting from server/ modules - server/serverPlan.ts — plan review server - server/serverReview.ts — code review server - server/serverAnnotate.ts — annotate server - server/helpers.ts — add requestUrl() to eliminate non-null assertions - server/project.ts — linter fix (sanitizeTag import path) - packages/ai/package.json — add pi-sdk-node export entry - index.ts — fix waitForDone non-null assertion with guard check, update imports for generated/checklist.js Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(pi): parity audit fixes + shared code extraction Systematic side-by-side audit of Pi vs Bun servers (A1-A22, B1-B2 complete). Fixes found during audit: - PlanServerResult.waitForDecision missing savedPath/agentSwitch/permissionMode - Missing permissionMode option and /api/plan response field - editorAnnotations created unnecessarily in archive mode - repoInfo called per-request instead of cached at init - Approve handler missing effectivePermissionMode fallback - Deny handler missing savedPath in decision resolution - Archive /api/plan response had extra pasteApiUrl - Missing GET method guards on archive/plans, archive/plan, doc, obsidian/files, obsidian/doc, reference/files - Review server had stray pasteApiUrl option/response field - AI getCwd missing worktree support Shared code extraction: - packages/shared/favicon.ts — single source for favicon SVG - packages/shared/integrations-common.ts — note app pure functions - packages/shared/reference-common.ts — file tree building - packages/shared/repo.ts — git remote parsing - Updated all consumers to import from shared sources For provenance purposes, this commit was AI assisted. * fix: parity audit B3-C10 — review + annotate server fixes Review server (B3-B17): - diff/switch missing try/catch error handling - git-add parseBody outside try/catch - feedback missing try/catch error handling - Unknown /api/ai/* paths now return 404 (both Bun and Pi) Annotate server (C1-C10): - Bun annotate server missing pasteApiUrl (short URL sharing broken) - Added pasteApiUrl to Bun options, response, and both hook callers - Pi repoInfo called per-request instead of cached at init - Pi feedback missing try/catch error handling - Missing GET method guards on doc and reference/files For provenance purposes, this commit was AI assisted. * fix: parity audit D3-D5 — draft error handling, editor annotations, resolve-file extraction D3: Pi draft save handler missing error handling — added .catch() with 500 + console.error D4: Pi editor annotation POST missing try/catch — added with "Invalid JSON" 400 D5: Extracted resolveMarkdownFile to packages/shared/resolve-file.ts - Replaced Bun.Glob with runtime-agnostic walkMarkdownFiles (readdirSync) - Made function sync (no longer async) - Pi handleDocRequest now uses shared resolveMarkdownFile instead of inline resolution - Gains Windows path normalization, isWithinProjectRoot security check - Deleted packages/server/resolve-file.ts re-export, consumers import from shared - Cleaned up stale await calls in hook entry, reference handler, and tests - All 19 resolve-file tests pass For provenance purposes, this commit was AI assisted. * fix: parity audit D6-D10 — integrations, PR naming, shared modules D6: Fixed broken detectProjectNameSync — was using require() for non-existent exports. Now uses basename + sanitizeTag directly. D7: Renamed checkAuth → checkPRAuth, getUser → getPRUser across Bun server, hook, and OpenCode plugin to match Pi naming. Also fixed stale resolve-file import in OpenCode plugin. D8-D10: Verified clean — ide, project detection, network. For provenance purposes, this commit was AI assisted. * update openpackage.yml * fix: bump Pi git-add test timeout to 15s for parallel suite stability For provenance purposes, this commit was AI assisted. * test: add route parity test — Bun ↔ Pi server route drift detection For provenance purposes, this commit was AI assisted. * fix(ci): update Pi generate step to use generated/ directory with full file list The Pi extension was refactored to use generated/ subdirectory but the CI generate step still used the old flat layout with a subset of files. For provenance purposes, this commit was AI assisted. * fix(ci): update release workflow Pi generate step to match new layout Same stale generate step as test.yml — old flat layout, missing files. For provenance purposes, this commit was AI assisted. * fix(pi): update files array for modular server layout The files array still referenced the old flat layout (server.ts monolith, root-level generated files, deleted utils.ts). npm publish would have produced a broken package missing server/ and generated/ directories. For provenance purposes, this commit was AI assisted. * feat: add TypeScript type-checking to CI pipeline - Fix broken barrel export: buildFileTree/VaultNode re-exported from @plannotator/shared instead of reference-handlers (P1 bug) - Fix server.port type narrowing in all 3 servers - Fix AI provider type errors (claude-agent-sdk, codex-sdk, opencode-sdk, pi-sdk) - Extract mapPiEvent to pi-events.ts to break Bun→Node type chain - Add tsconfig.json to packages/shared, packages/ai, packages/server, apps/pi-extension - Add `typecheck` script to root package.json - Add type-check step to test.yml and release.yml CI workflows For provenance purposes, this commit was AI assisted. * fix(ci): use bun-types instead of @types/node for typecheck CI environment has bun-types (includes Node types) but not @types/node as a standalone package. For provenance purposes, this commit was AI assisted. * fix(ci): add @types/node for Node-runtime type checks Pi extension and packages/shared run on Node, not Bun — they should type-check against @types/node, not bun-types. Added @types/node as a dev dependency so CI resolves it. For provenance purposes, this commit was AI assisted. * fix: cast Uint8Array.buffer to ArrayBuffer for TS 5.9 compat crypto.subtle.importKey expects BufferSource, but TS 5.9 is stricter about Uint8Array.buffer being ArrayBufferLike (includes SharedArrayBuffer) vs ArrayBuffer. Explicit cast resolves the overload mismatch. Astro pulls in TS 5.9 transitively, so CI resolves a different TypeScript version than local dev. This fix works on both 5.8 and 5.9. For provenance purposes, this commit was AI assisted. * fix(ci): add bun-types as explicit devDependency CI's bun install doesn't hoist bun-types to root node_modules when it's only a transitive dep of @types/bun. Adding it as a direct devDependency guarantees tsc can resolve it. For provenance purposes, this commit was AI assisted. * fix(ci): remove Pi extension from typecheck Pi extension depends on @mariozechner/pi-* peer dependencies that aren't installed in CI. Type-checking it requires Pi's runtime environment. The three packages we check (shared, ai, server) are sufficient to catch barrel export bugs and type errors. Pi extension coverage comes from route parity tests and bun test. For provenance purposes, this commit was AI assisted. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 54a00e8 commit f96758d

57 files changed

Lines changed: 4822 additions & 2962 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ jobs:
3838
- name: Generate Pi extension shared copies
3939
run: |
4040
cd apps/pi-extension
41-
for f in feedback-templates review-core storage draft project; do
41+
mkdir -p generated
42+
for f in feedback-templates review-core storage draft project pr-provider pr-github pr-gitlab checklist integrations-common repo reference-common favicon resolve-file; do
4243
src="../../packages/shared/$f.ts"
43-
printf '// @generated — DO NOT EDIT. Source: packages/shared/%s.ts\n' "$f" | cat - "$src" > "$f.ts"
44+
printf '// @generated — DO NOT EDIT. Source: packages/shared/%s.ts\n' "$f" | cat - "$src" > "generated/$f.ts"
4445
done
4546
47+
- name: Type check
48+
run: bun run typecheck
49+
4650
- name: Run tests
4751
run: bun test
4852

.github/workflows/test.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ jobs:
2525
- name: Generate Pi extension shared copies
2626
run: |
2727
cd apps/pi-extension
28-
for f in feedback-templates review-core storage draft project; do
28+
mkdir -p generated
29+
for f in feedback-templates review-core storage draft project pr-provider pr-github pr-gitlab checklist integrations-common repo reference-common favicon resolve-file; do
2930
src="../../packages/shared/$f.ts"
30-
printf '// @generated — DO NOT EDIT. Source: packages/shared/%s.ts\n' "$f" | cat - "$src" > "$f.ts"
31+
printf '// @generated — DO NOT EDIT. Source: packages/shared/%s.ts\n' "$f" | cat - "$src" > "generated/$f.ts"
3132
done
3233
34+
- name: Type check
35+
run: bun run typecheck
36+
3337
- name: Run tests
3438
run: bun test

apps/hook/server/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ import {
4949
handleAnnotateServerReady,
5050
} from "@plannotator/server/annotate";
5151
import { getGitContext, runGitDiff } from "@plannotator/server/git";
52-
import { parsePRUrl, checkAuth, fetchPR, getCliName, getCliInstallUrl, getMRLabel, getMRNumberLabel, getDisplayRepo } from "@plannotator/server/pr";
52+
import { parsePRUrl, checkPRAuth, fetchPR, getCliName, getCliInstallUrl, getMRLabel, getMRNumberLabel, getDisplayRepo } from "@plannotator/server/pr";
5353
import { writeRemoteShareLink } from "@plannotator/server/share-url";
54-
import { resolveMarkdownFile } from "@plannotator/server/resolve-file";
54+
import { resolveMarkdownFile } from "@plannotator/shared/resolve-file";
5555
import { registerSession, unregisterSession, listSessions } from "@plannotator/server/sessions";
5656
import { openBrowser } from "@plannotator/server/browser";
5757
import { detectProjectName } from "@plannotator/server/project";
@@ -165,7 +165,7 @@ if (args[0] === "sessions") {
165165
const cliUrl = getCliInstallUrl(prRef);
166166

167167
try {
168-
await checkAuth(prRef);
168+
await checkPRAuth(prRef);
169169
} catch (err) {
170170
const msg = err instanceof Error ? err.message : String(err);
171171
if (msg.includes("not found") || msg.includes("ENOENT")) {
@@ -274,7 +274,7 @@ if (args[0] === "sessions") {
274274
}
275275

276276
// Smart file resolution: exact path, case-insensitive relative, or bare filename search
277-
const resolved = await resolveMarkdownFile(filePath, projectRoot);
277+
const resolved = resolveMarkdownFile(filePath, projectRoot);
278278

279279
if (resolved.kind === "ambiguous") {
280280
console.error(`Ambiguous filename "${resolved.input}" — found ${resolved.matches.length} matches:`);
@@ -301,6 +301,7 @@ if (args[0] === "sessions") {
301301
origin: "claude-code",
302302
sharingEnabled,
303303
shareBaseUrl,
304+
pasteApiUrl,
304305
htmlContent: planHtmlContent,
305306
onReady: async (url, isRemote, port) => {
306307
handleAnnotateServerReady(url, isRemote, port);
@@ -418,6 +419,7 @@ if (args[0] === "sessions") {
418419
mode: "annotate-last",
419420
sharingEnabled,
420421
shareBaseUrl,
422+
pasteApiUrl,
421423
htmlContent: planHtmlContent,
422424
onReady: async (url, isRemote, port) => {
423425
handleAnnotateServerReady(url, isRemote, port);

apps/opencode-plugin/commands.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import {
1414
handleAnnotateServerReady,
1515
} from "@plannotator/server/annotate";
1616
import { getGitContext, runGitDiffWithContext } from "@plannotator/server/git";
17-
import { parsePRUrl, checkAuth, fetchPR, getCliName, getMRLabel, getMRNumberLabel, getDisplayRepo } from "@plannotator/server/pr";
18-
import { resolveMarkdownFile } from "@plannotator/server/resolve-file";
17+
import { parsePRUrl, checkPRAuth, fetchPR, getCliName, getMRLabel, getMRNumberLabel, getDisplayRepo } from "@plannotator/server/pr";
18+
import { resolveMarkdownFile } from "@plannotator/shared/resolve-file";
1919

2020
/** Shared dependencies injected by the plugin */
2121
export interface CommandDeps {
@@ -53,7 +53,7 @@ export async function handleReviewCommand(
5353
client.app.log({ level: "info", message: `Fetching ${getMRLabel(prRef)} ${getMRNumberLabel(prRef)} from ${getDisplayRepo(prRef)}...` });
5454

5555
try {
56-
await checkAuth(prRef);
56+
await checkPRAuth(prRef);
5757
} catch (err) {
5858
const cliName = getCliName(prRef);
5959
client.app.log({ level: "error", message: err instanceof Error ? err.message : `${cliName} auth check failed` });

apps/pi-extension/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
generated/
2+
plannotator.html
3+
review-editor.html

0 commit comments

Comments
 (0)