* Make Pi browser review and annotation sessions async
* fix(pi-extension): harden async browser feedback
* fix(pi-extension): stop async browser sessions on failure
* fix(pi-extension): tighten async browser session helpers
* chore(pi-extension): remove stale await from message lookup
* fix(pi-extension): route stale browser feedback to active session
* fix(pi-extension): route feedback by active session identity
* fix(pi-extension): include helper modules in package
* feat(ui): extract reusable PopoutDialog, fix backdrop blur
The table popout lost its backdrop blur when we switched to modal={false}
to keep annotation toolbars interactive. Radix ignores Dialog.Overlay in
non-modal mode, so replace it with a plain div backdrop that works
regardless. Extract the dialog shell (backdrop, close button, portal,
annotation-aware dismiss) into a reusable PopoutDialog component for
upcoming use cases. Add a demo table to the dev plan content.
* Add read-only code file popout
* Add code file annotation support
* Fix code selection popover position
* fix(editor): restore global-attachment-only drafts
The save condition was broadened to persist drafts with only global
attachments, but the restore handler still skipped applying when both
annotation arrays were empty — silently dropping the attachments.
* fix(ui): import SelectedLineRange from @pierre/diffs base package
SelectedLineRange is not re-exported from @pierre/diffs/react —
import it from the base @pierre/diffs entry point instead.
* chore: add TODO for bot callback + code annotation limitation
Bump version across all 7 package/plugin manifests. Also refresh
AGENTS.md (new Block types from #597, Code Tour endpoints from #569),
prune removed flags from the Pi README (--plan-file, /plannotator-set-file
from #595), and pin Renovate off bun-version bumps to keep the macOS
codesign hotfix from v0.17.9 in place.
For provenance purposes, this commit was AI assisted.
Also fix stale annotate description in README and add missing PLANNOTATOR_ORIGIN entry to the env vars table.
For provenance purposes, this commit was AI assisted.
* refactor(path): centralize user path resolution
* fix(annotate): resolve user paths in file entrypoints
* fix(pi-extension): restore resolve import and add typecheck to CI
The refactor removed `resolve` from `node:path` imports, but `resolvePlanPath()`
and the planning-mode write/edit guards still call `resolve(...)`. That breaks
plan submission and plan-file restriction at runtime for Pi users.
Also wires pi-extension's tsconfig into the root `typecheck` script so CI
catches this class of missing-symbol regression in the future. Required
adding @mariozechner/pi-* packages as explicit devDependencies so tsc can
resolve them (they were previously only reachable transitively via the
peer dep, which Bun keeps in its `.bun/` store unhoisted).
For provenance purposes, this commit was AI assisted.
* fix(path): reject whitespace-only user paths and run vendor before typecheck
resolveUserPath() trims input, so whitespace-only customPath/vaultPath
resolved to process.cwd(). Plans silently wrote into the repo root and
Obsidian notes landed in <cwd>/plannotator/ instead of erroring.
Guard at both call sites (getPlanDir, saveToObsidian — Bun + Pi copies).
Also prepend vendor.sh to the root typecheck script so fresh-clone
`bun run typecheck` works without a separate vendoring step.
For provenance purposes, this commit was AI assisted.
* fix(path): short-circuit resolveUserPath on empty input
Trimming in normalizeUserPathInput meant whitespace-only input resolved
to cwd/baseDir. Callers like the annotate CLI and reference API endpoints
would then list the project root instead of erroring. Return "" early so
downstream existsSync/resolveMarkdownFile checks fail naturally.
For provenance purposes, this commit was AI assisted.
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
* fix(annotate): sanitize dangerous link protocols in markdown renderer
Block javascript:, data:, and vbscript: URLs in InlineMarkdown link
rendering. Links with dangerous protocols render as plain text instead
of clickable anchors. Uses a blocklist approach so existing links with
custom protocols (obsidian://, vscode://, Windows C:\ paths) continue
to work.
For provenance purposes, this commit was AI assisted.
* feat(annotate): add HTML-to-markdown and URL-to-markdown utilities
- html-to-markdown.ts: Turndown wrapper with GFM table rule, strips
script/style/noscript tags
- url-to-markdown.ts: Jina Reader (free, returns markdown) with
fetch+Turndown fallback. Warns on Jina failure, auto-skips Jina for
local/private URLs (localhost, 192.168.*, 10.*, etc.)
- config.ts: add jina setting and resolveUseJina() with priority chain
--no-jina flag > PLANNOTATOR_JINA env > config.json > default true
For provenance purposes, this commit was AI assisted.
* feat(annotate): support HTML files and URLs in annotate command
Extend the annotate subcommand to accept .html/.htm local files
(converted via Turndown) and https:// URLs (fetched via Jina Reader
with fetch+Turndown fallback). URL content is fetched terminal-side
before opening the browser.
Add --no-jina global flag to disable Jina Reader per-invocation.
Add 10MB file size guard for local HTML files.
For provenance purposes, this commit was AI assisted.
* feat(annotate): HTML files in folder browser and on-demand conversion
- Widen file browser glob to include .html/.htm alongside markdown
- handleDoc converts HTML files via Turndown on demand when selected
- hasMarkdownFiles accepts optional extensions param for folder validation
- Add sourceInfo field to annotate server API response
- Add _site/, public/, out/, .docusaurus/, .jekyll-cache/,
storybook-static/ to FILE_BROWSER_EXCLUDED
For provenance purposes, this commit was AI assisted.
* feat(annotate): source attribution badge for HTML/URL annotations
Show a subtle badge in DocBadges displaying the URL hostname or HTML
filename for converted content. Thread sourceInfo from API response
through App → Viewer → DocBadges.
Also update Pi extension to accept HTML-only folders in annotate mode.
For provenance purposes, this commit was AI assisted.
* test: update CLI help text assertion for HTML/URL annotate support
For provenance purposes, this commit was AI assisted.
* fix(annotate): address PR review findings
Security:
- Add project-root containment check for HTML files in /api/doc handler
using exported isWithinProjectRoot() from resolve-file.ts
- Blocks path traversal via absolute paths or ../ escapes
isLocalUrl fixes:
- Add bracketed IPv6 loopback [::1] detection
- Replace hostname.startsWith('10.') with proper IPv4 regex to avoid
matching public hostnames like 10.example.com
Revert Pi extension change:
- Pi server doesn't implement HTML file browsing or conversion yet
- Keep Pi folder validation markdown-only until both implementations
are updated per CLAUDE.md guidelines
Cleanup:
- Remove dead el.children || el.childNodes fallback in table rule
- Extract hostnameOrFallback() helper to @plannotator/shared/project
replacing duplicated try/catch IIFEs in DocBadges and index.ts
For provenance purposes, this commit was AI assisted.
* feat(annotate): Pi extension HTML annotation parity
Bring the Pi extension to full parity with the Bun server for HTML
annotation support:
- Vendor html-to-markdown and url-to-markdown via vendor.sh
- walkMarkdownFiles now scans .html/.htm alongside markdown
- handleDocRequest converts HTML files on-demand via Turndown with
isWithinProjectRoot containment check
- serverAnnotate includes sourceInfo in /api/plan response
- index.ts supports URL detection (Jina Reader + fallback), HTML file
detection with Turndown conversion, folder HTML validation, and 10MB
file size guard
- openMarkdownAnnotation accepts and threads sourceInfo
- Add turndown as a Pi extension dependency
For provenance purposes, this commit was AI assisted.
* fix(pi): Obsidian vault walks stay markdown-only, add try/catch for HTML
- Add extensions param to walkMarkdownFiles (default: HTML-inclusive)
- Obsidian callers pass /\.mdx?$/i to match Bun server behavior
- Add try/catch around HTML file reads in handleDocRequest
For provenance purposes, this commit was AI assisted.
* fix(annotate): address second review — base-block traversal, metadata IP, dead code
Security:
- Add isWithinProjectRoot check to the base-relative block for HTML
files in both Bun and Pi /api/doc handlers. Previously HTML files
served via the base query param bypassed the containment guard.
- Add 169.254.0.0/16 (link-local / cloud metadata) to isLocalUrl
private IP ranges
Cleanup:
- Remove dead hostname === "[::1]" check (WHATWG URL parser strips
brackets; hostname === "::1" already handles it)
- Remove dead parent?.childNodes fallback in table cell() function
For provenance purposes, this commit was AI assisted.
* refactor(annotate): replace custom table rules with turndown-plugin-gfm
Drop ~60 lines of hand-rolled GFM table conversion that had a bug
(tables without explicit <thead> produced invalid GFM). Use the
official turndown-plugin-gfm plugin (24KB) which correctly handles
all table patterns plus adds strikethrough and task list support.
For provenance purposes, this commit was AI assisted.
* fix(annotate): handle all CommonMark backslash escapes in InlineMarkdown
Expand the backslash escape regex to cover all CommonMark-defined
escapable characters (. ) - # > + | { } &), not just the subset
the parser uses for formatting. Fixes literal backslashes appearing
in rendered output for Turndown-escaped content like "1\." → "1.".
For provenance purposes, this commit was AI assisted.
* fix(annotate): prevent SSRF via redirect to private/local URLs
Replace redirect: "follow" with redirect: "manual" in fetchViaTurndown
and validate each redirect hop against isLocalUrl. Blocks attacks where
an external URL redirects to cloud metadata endpoints (169.254.169.254)
or other private IPs. Limits redirect chain to 10 hops.
For provenance purposes, this commit was AI assisted.
* chore: update lockfile for turndown-plugin-gfm in Pi extension
bun install needed to resolve turndown-plugin-gfm in the Pi extension
workspace after adding it to apps/pi-extension/package.json.
For provenance purposes, this commit was AI assisted.
* fix(annotate): switch to @joplin/turndown-plugin-gfm, fix TS errors
Replace unmaintained turndown-plugin-gfm (2017, v1.0.2) with the
actively maintained Joplin fork (2025, v1.0.64, 16KB).
Fix TypeScript errors that broke CI:
- Add @ts-expect-error for untyped @joplin/turndown-plugin-gfm import
- Restructure fetchViaTurndown redirect loop to avoid uninitialized
variable — first fetch before loop, loop only for redirects
For provenance purposes, this commit was AI assisted.
* fix(annotate): use proper declarations.d.ts instead of ts-expect-error
Add declarations.d.ts for @joplin/turndown-plugin-gfm with typed
function signatures, remove the ts-expect-error suppression.
For provenance purposes, this commit was AI assisted.
* fix: explicitly include declarations.d.ts in shared tsconfig
CI's tsc wasn't finding the ambient module declaration with implicit
include. Add explicit include to ensure declarations.d.ts is always
picked up regardless of environment.
For provenance purposes, this commit was AI assisted.
* fix: use ts-expect-error for @joplin/turndown-plugin-gfm types
CI's tsc does not pick up ambient declarations.d.ts files despite
local tsc finding them — likely a module resolution discrepancy
between environments. Revert to @ts-expect-error which passes in
both CI and local typecheck.
For provenance purposes, this commit was AI assisted.
* fix(annotate): body size limit for URL fetches, redirect error, file: protocol
- Add 10MB body size limit to both Jina and fetch+Turndown URL paths,
matching the local HTML file guard. Streams response body and aborts
if limit exceeded.
- Distinguish "Too many redirects" from a genuine 3xx response after
redirect loop exhaustion.
- Add file: to the dangerous protocol blocklist in sanitizeLinkUrl.
For provenance purposes, this commit was AI assisted.
* fix(annotate): HTML folder outside cwd, HTML linked doc navigation
- Remove containment check from base-relative block for HTML files in
both Bun and Pi /api/doc handlers. Matches markdown behavior so HTML
files in annotated folders outside cwd are served correctly.
Standalone block (no base) retains its cwd check as fallback.
- Widen isLocalMd → isLocalDoc to treat .html/.htm links as linked
documents. Clicking [Next](next.html) in a converted page now opens
it via /api/doc with Turndown conversion instead of a new browser tab.
For provenance purposes, this commit was AI assisted.
* fix(annotate): full loopback range, drain redirect bodies, document env vars
- Expand loopback check from just 127.0.0.1 to the full 127.0.0.0/8
range so all loopback addresses skip Jina Reader
- Cancel redirect response body before re-fetching to avoid leaking
TCP connections back to the pool
- Document PLANNOTATOR_JINA and JINA_API_KEY in CLAUDE.md env var table
For provenance purposes, this commit was AI assisted.
* fix(annotate): IPv6 loopback, readBodyWithLimit fallback, env var docs, comments
- Add [::1] back to isLocalUrl — WHATWG URL hostname getter preserves
brackets for IPv6 (verified: Bun and Node both return "[::1]").
Add comment explaining the empirical verification so future reviewers
don't re-flag.
- Fix readBodyWithLimit null-body fallback to still enforce the 10MB
limit via text length check instead of silently falling through.
- Document PLANNOTATOR_JINA and JINA_API_KEY in AGENTS.md env var table
(CLAUDE.md is a symlink to AGENTS.md).
- Add comments to base-relative blocks in both Bun and Pi handleDoc
explaining the intentional lack of containment check (matches
pre-existing markdown behavior, base is set server-side).
For provenance purposes, this commit was AI assisted.
* fix(annotate): block IPv4-mapped IPv6 and private IPv6 ranges in isLocalUrl
Add PRIVATE_IPV6 regex matching bracketed IPv6 private/reserved ranges:
- ::ffff: (IPv4-mapped — embeds private IPv4 as hex, e.g. [::ffff:c0a8:1])
- fe80: (link-local)
- fc00::/7 (unique-local, covers fc00:: through fdff::)
Closes the redirect-SSRF bypass where a public URL redirects to a
private address expressed as IPv4-mapped IPv6, e.g.
http://[::ffff:169.254.169.254]/latest/meta-data/
For provenance purposes, this commit was AI assisted.
* fix(annotate): document IPv6 hostname verification, sourceInfo type, annotate flow
- Expand isLocalUrl comment with full empirical verification table
showing actual hostname getter output for every IPv6 format in both
Bun and Node — prevents false-positive review findings about brackets
- Add sourceInfo to /api/plan response type in App.tsx for type safety
- Update CLAUDE.md annotate flow diagram to reflect HTML/URL/folder
input types
For provenance purposes, this commit was AI assisted.
* fix(annotate): escape \(, cancel response bodies on error, doc sourceInfo
- Add ( to backslash escape regex alongside existing ) — Turndown
emits \( in link-adjacent contexts
- Cancel response body before throwing on !res.ok in both fetchViaJina
and fetchViaTurndown error paths (redirect loop already did this)
- Document sourceInfo field in AGENTS.md annotate server API table
For provenance purposes, this commit was AI assisted.
* fix(annotate): skip base injection for URL annotations, body cleanup
- Skip dirname(filePath) base injection when filePath is a URL in both
Bun and Pi annotate servers. dirname on a URL string produces a
nonsensical filesystem path, causing linked doc clicks to 404.
URL annotations now let links open normally instead.
- Cancel response body before throwing on content-type mismatch and
content-length overflow in fetchViaTurndown/readBodyWithLimit.
- Fix double parseInt in readBodyWithLimit content-length check.
- Correct AGENTS.md flow diagram: OpenCode not yet implemented for
HTML/URL annotation.
For provenance purposes, this commit was AI assisted.
* feat(annotate): OpenCode HTML file and URL annotation support
Add URL detection (Jina Reader + fallback), HTML file detection with
Turndown conversion, 10MB file size guard, and sourceInfo threading
to OpenCode's handleAnnotateCommand. Uses the same shared utilities
as the Bun CLI and Pi extension.
OpenCode uses the Bun server directly (startAnnotateServer from
@plannotator/server/annotate), so no server-side changes needed —
only the command handler routing was missing.
Note: folder annotation mode is not added (OpenCode didn't have it
before this PR for markdown either — separate scope).
For provenance purposes, this commit was AI assisted.
* chore(annotate): update slash command description, align fetch log messages
- OpenCode plannotator-annotate.md description now mentions HTML/URL
- Align fetch progress messages across all three clients: all now show
"(via Jina Reader)" or "(via fetch+Turndown)" consistently
For provenance purposes, this commit was AI assisted.
* fix(annotate): skip conversion for .md URLs, wikilink HTML targets, cleanup
- URLs ending in .md/.mdx are fetched raw — no Jina, no Turndown.
Content is already markdown. Removes text/plain from fetchViaTurndown
content-type whitelist since .md URLs are now short-circuited.
- Wikilink regex widened to preserve .html/.htm targets instead of
appending .md (e.g. [[page.html]] no longer becomes page.html.md)
- Remove redundant existsSync before statSync in OpenCode handler
For provenance purposes, this commit was AI assisted.
* test(annotate): add htmlToMarkdown conversion tests
Tests cover the core conversion utility that all three clients depend on:
- Basic HTML → markdown (headings, paragraphs, links, code blocks)
- Tables with and without <thead> (the GFM plugin bug that was caught)
- Script/style/noscript stripping
- Strikethrough (GFM)
- Empty HTML handling
- Dangerous links preserved (sanitization is in the renderer, not here)
For provenance purposes, this commit was AI assisted.
* fix(annotate): check content-type before treating .md URLs as raw markdown
URLs ending in .md/.mdx (e.g. GitHub's viewer page for README.md)
may return HTML instead of raw markdown. fetchRawText now checks the
response content-type — if the server returns HTML, returns null so
the caller falls through to Jina/Turndown for proper conversion.
For provenance purposes, this commit was AI assisted.
* fix(annotate): add SSRF redirect protection to fetchRawText
fetchRawText (for .md/.mdx URLs) was using default redirect: "follow"
with no isLocalUrl validation on redirect hops — a .md URL redirecting
to 169.254.169.254 would be followed and credentials returned as
"markdown". Now uses redirect: "manual" with per-hop isLocalUrl checks,
matching fetchViaTurndown's SSRF protection.
For provenance purposes, this commit was AI assisted.
Bun 1.3.12 introduced a regression where cross-compiled macOS binaries
lose their ad-hoc linker signature, causing macOS Sequoia to SIGKILL
them immediately. Pin bun-version to 1.3.11 across all three workflow
jobs (test, build, npm-publish) until the Bun issue is resolved.
Fixes#541
Bumps version to 0.17.9.
* add pi signal support
* remove PLAN
* fix: pass cwd through to getGitContext in openCodeReview
getGitContext() was called without the custom cwd override, so the
initial diff was correct but live diff switching, file content, git-add,
and branch labels in the review UI would resolve against process.cwd()
instead of the caller-provided cwd.
For provenance purposes, this commit was AI assisted.
---------
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Adds user-editable display names and persistent config via ~/.plannotator/config.json.
- ConfigStore singleton with precedence: server config file > cookie > default
- Editable identity input in Settings with "Use git name" and regenerate buttons
- POST /api/config endpoint for write-back across all 6 servers
- getServerConfig() reads config fresh per request (no stale cache)
- Eager constructor hydration so the store is safe to read before init()
- vendor.sh as single source of truth for Pi extension vendoring
- Vendor parity test to prevent missing generated modules
Closes#396
Adds skills/ to the pi manifest and files array so Pi auto-discovers
the compound-planning SKILL.md. Build script copies from apps/skills/.
For provenance purposes, this commit was AI assisted.
@plannotator/ai is a private workspace package unavailable on npm.
Pi's dynamic import silently failed, so AI features never loaded for
published package users. Now copies all AI files into generated/ai/
at build time, same pattern as the shared utils.
For provenance purposes, this commit was AI assisted.
* 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>
* feat: plan archive browser with in-session sidebar tab (#362)
Add `plannotator archive` subcommand and archive sidebar tab for browsing
saved plan decisions from ~/.plannotator/plans/. Plans show approved/denied
badges and timestamps. In-session mode uses the linked doc overlay pattern
so users can reference old plans while reviewing a current one.
- New archive server (packages/server/archive.ts) following annotate pattern
- New ArchiveBrowser sidebar component, reusable in both contexts
- Archive listing/parsing functions in storage.ts (reads decision snapshots)
- Archive endpoints on plan server for in-session use (/api/archive/plans)
- Remove dead "Other Plans" UI, projectPlans state, /api/plan/history endpoint
- Fix resize handle touch area covering scrollbars in sidebar/main content
- Fix sidebar tab bar overflow when narrow
For provenance purposes, this commit was AI assisted.
* fix: code quality sweep for plan archive
- Remove `as any` cast: add "archive" to SessionInfo.mode union
- Replace inline import() type with proper import for ArchivedPlan
- Replace any[] with ArchivedPlan[] in fetch response types
- Fix infinite re-fetch when archive is empty (use hasFetched ref)
- Cache archive plan list in plan server (avoid re-scanning filesystem)
- Document ResizeHandle side prop behavior
- Remove redundant comment on Viewer archiveInfo prop
For provenance purposes, this commit was AI assisted.
* chore: remove dead marketing components
Step.astro and Landing.astro are unused — landing page inlines
step markup and pages use Base.astro directly.
For provenance purposes, this commit was AI assisted.
* fix: address code review findings for plan archive
- Path traversal: use resolve() + trailing separator guard (matches reference-handlers.ts)
- Thread customPath into in-session archive endpoints via query param
- Sort same-day archive entries by mtime instead of title
- Clear selectedArchiveFile on linked doc back to prevent badge leak
- Hide archive tab in annotate mode (server doesn't serve those endpoints)
- Add targetTab param to useLinkedDoc.open() to preserve calling sidebar tab
- Replace mutable render variable with index-based date grouping
For provenance purposes, this commit was AI assisted.
* refactor: collapse standalone archive server into plan server
Delete packages/server/archive.ts (187 lines) — nearly all duplicated
from the plan server. Add mode:"archive" option to startPlannotatorServer
instead. Fixes two bugs from code review:
- handleArchiveCopy now splits on "# Plan Feedback" marker instead of
bare "---", preventing truncation at horizontal rules in plan content
- customPath support works in standalone archive mode (was only working
in-session because the standalone server never received it)
For provenance purposes, this commit was AI assisted.
* refactor: extract useArchive hook from App.tsx
Move archive state (archiveMode, plans, selectedFile, isLoading) and
handlers (select, fetchPlans, done, copy) into a dedicated useArchive
hook. Reduces App.tsx by ~75 lines and makes the archive feature
self-contained.
For provenance purposes, this commit was AI assisted.
* feat: Pi archive parity + eliminate server duplication
Move runtime-agnostic storage, draft, and project functions from
packages/server/ to packages/shared/ — eliminating ~250 lines of
duplicated code in Pi's server.ts. Server package becomes thin
re-exports, preserving all existing import paths.
Add archive mode to Pi's plan review server (mode, routes, waitForDone)
and register /plannotator-archive command in the Pi extension. Consolidate
ArchivedPlan type to single definition in shared/storage.ts.
Simplify archive copy to include full content with feedback.
For provenance purposes, this commit was AI assisted.
* fix: drop -core suffix from Pi shared copies
The -core suffix broke cross-file imports — storage.ts imports from
./project which didn't resolve to project-core.ts. Using the original
filenames (no collision) lets relative imports work naturally.
For provenance purposes, this commit was AI assisted.
* fix: archive custom path bugs, disable sharing, update docs
- Normalize planDir via resolve() in getPlanDir() to handle relative
paths and trailing slashes in the path traversal guard
- Re-fetch archive plans client-side with cookie-backed customPath
so standalone archive respects the user's configured save location
- Disable sharing in archive mode (read-only viewer, no need)
- Remove dead /api/plan/history endpoint and listProjectPlans import
from Pi extension
- Remove dead /api/plan/history mock from dev-mock-api
- Update CLAUDE.md and AGENTS.md: add archive flow, archive API
endpoints, shared package structure, correct storage location,
sidebar tab count, remove stale /api/plan/history references
- Update hook server docstring from four to five modes
For provenance purposes, this commit was AI assisted.
* fix: empty archive shows demo content, stale viewer after customPath fetch
- Clear demo markdown when archive opens with no plans (plan: "" was falsy,
so setMarkdown was never called)
- Remove redundant fetchPlans() from archive init — server already sends
archivePlans in initial response
- After fetchPlans() resolves with customPath results, auto-select and load
the first plan into the viewer
- Remove dead listProjectPlans re-export from server barrel
For provenance purposes, this commit was AI assisted.
* refactor: gitignore Pi shared copies, add @generated headers
Pi's copied .ts files (storage, draft, project, feedback-templates,
review-core) are build artifacts generated from packages/shared/. They
looked like editable source files, leading to confusion about which file
to edit. Now gitignored like the HTML copies, with @generated headers
prepended by the build script.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in CI before tests
The Pi .ts copies are now gitignored build artifacts. CI needs to
generate them before running tests since server.test.ts transitively
imports them via server.ts.
For provenance purposes, this commit was AI assisted.
* fix: generate Pi shared copies in release pipeline test job
Same fix as test.yml — the Pi .ts copies are gitignored, so the test
job in the release pipeline also needs to generate them before bun test.
For provenance purposes, this commit was AI assisted.
* fix: use block scalar in CI workflow to avoid YAML parse error
The inline `run:` had a colon in the printf string that YAML
interpreted as a mapping key. Switch to `run: |` block scalar.
For provenance purposes, this commit was AI assisted.