Commit Graph

40 Commits

Author SHA1 Message Date
foxytanuki 26364543b2 fix(remote): support explicit local override (#481) 2026-04-04 13:17:16 -07:00
Michael Ramos 1800c24d80 feat(gemini): add Gemini CLI plan review integration
* feat(gemini): add Gemini CLI plan review integration

Adds a new `apps/gemini-hook/` adapter that enables Plannotator plan
review for Gemini CLI users via the BeforeTool hook system.

The adapter reads the plan file from disk (Gemini provides a path, not
inline content), delegates to the shared @plannotator/server for the
browser-based review UI, and translates the decision back into Gemini's
hook output format.

Requires an upstream fix (google-gemini/gemini-cli#21802) that makes
`decision = "allow"` user policies work for exit_plan_mode, allowing
hooks to replace the built-in TUI approval dialog.

Includes:
- apps/gemini-hook/server/index.ts — stdin/stdout adapter
- apps/gemini-hook/hooks/ — policy TOML + settings snippet
- scripts/install.sh — Gemini binary download, policy install, settings config

For provenance purposes, this commit was AI assisted.

* refactor(gemini): use single binary with auto-detection instead of separate app

Removes apps/gemini-hook/ — the plannotator binary now auto-detects
Gemini CLI from stdin (plan_path = file on disk) vs Claude Code
(plan = inline content) and branches input parsing + output formatting.

Config fixtures live in apps/gemini/ (policy TOML + settings snippet).
Install script gates on ~/.gemini existing so Claude-only users are
unaffected.

For provenance purposes, this commit was AI assisted.

* test(gemini): add manual sandbox script for Gemini CLI integration

Three modes:
- --simulate: pipes BeforeTool JSON to hook, tests approve/deny output
- (default): runs local patched Gemini build
- --nightly: installs Gemini nightly and runs it

Backs up and restores ~/.gemini config on exit.

For provenance purposes, this commit was AI assisted.

* feat(gemini): add slash commands, marketing tab, and docs for Gemini CLI

- Add /plannotator-review and /plannotator-annotate slash commands (.toml)
- Install Gemini slash commands in all three install scripts (sh, ps1, cmd)
- Add Gemini tab to marketing landing page with icon
- Add Gemini CLI to top-level README install section
- Create apps/gemini/README.md with full setup and usage docs
- Remove stale dev:gemini script and regenerate bun.lock

For provenance purposes, this commit was AI assisted.

* fix(gemini): merge hook into existing settings.json instead of printing instructions

When ~/.gemini/settings.json already exists, use node to JSON-merge
the BeforeTool hook config rather than asking the user to do it manually.
Falls back to instructions only if node is unavailable.

For provenance purposes, this commit was AI assisted.

* fix(gemini): handle plan_filename rename and fix scoping bug

Gemini CLI nightly renamed plan_path to plan_filename in exit_plan_mode.
Accept both field names for forward/backward compatibility. Reconstruct
full plan path from transcript_path + session_id + plans/ + filename.

Also hoist planFilename variable out of try block so it's accessible
in the deny output path (was causing ReferenceError).

For provenance purposes, this commit was AI assisted.

* fix(gemini): dim approve button when annotations exist for Gemini CLI

Gemini's hook runner ignores systemMessage on the allow path, so
approve-with-feedback is silently dropped — same limitation as Claude
Code. Extend the existing UI gate to also apply for gemini-cli origin.

For provenance purposes, this commit was AI assisted.

* fix(gemini): add AGENT_CONFIG entry and fix sandbox simulate mode

Register "gemini-cli" in AGENT_CONFIG so the UI shows "Gemini CLI"
with proper badge styling instead of generic "Coding Agent" fallback.

Update sandbox simulate mode to match production input format:
use plan_filename instead of plan_path, include transcript_path,
and simulate the Gemini directory structure for path reconstruction.

For provenance purposes, this commit was AI assisted.
2026-04-02 15:24:10 -07:00
rtsummit e642e25124 feat: add Perforce (P4) pending changelist diff support (#472)
* feat: add Perforce pending changelist diff support for code review

Introduce P4 support alongside existing Git backend via a VCS dispatch
layer (vcs.ts). Detects Git vs Perforce workspace automatically and
routes diff/file-content operations to the appropriate backend.

- Add p4-default and p4-changelist:<N> DiffType variants
- Add P4 runtime (p4.ts): workspace detection with TTL cache, changelist
  context, unified diff conversion, binary file exclusion, batch p4 where
- Add VCS dispatch layer (vcs.ts): auto-detect Git/P4, unified API
- Update review server and CLI entry point to use VCS dispatch
- Filter empty changelists from dropdown, disable staging for P4

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: introduce VcsProvider interface for extensible VCS support

Replace hardcoded Git/P4 dispatch with a provider-based architecture.
Each VCS implements VcsProvider interface and registers in a providers
array — adding a new VCS only requires implementing the interface.

- Define VcsProvider with detect, ownsDiffType, getContext, runDiff,
  getFileContents, and optional stageFile/unstageFile/resolveCwd
- Create gitProvider and p4Provider as concrete implementations
- Replace isP4DiffType/gitAddFile/gitResetFile in review.ts with
  provider-based canStageFiles/stageFile/unstageFile/resolveVcsCwd
- Remove VCS-specific branching from review server endpoints

Note: GitContext interface is not renamed to VcsContext in this commit.
The rename touches too many files across the codebase (Pi extension,
OpenCode plugin, shared types, tests) and should be handled as a
separate pull request to keep the diff reviewable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: rtsummit <rtsummit@abutton.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 10:49:24 -07:00
Michael Ramos f8d4969e73 feat(hook): improvement hook context injection for planning (#459)
* feat(shared): add improvement hook reader utility

Adds a runtime-agnostic module for reading improvement hook files from
~/.plannotator/hooks/. Uses a hardcoded base path with an allowlist of
known hook files and a 50KB size cap to prevent runaway context injection.

Designed for cross-harness reuse (Claude Code, OpenCode, Pi, etc.).

For provenance purposes, this commit was AI assisted.

* feat(hook): wire up improve-context PreToolUse hook for EnterPlanMode

Adds a PreToolUse hook on EnterPlanMode that injects corrective planning
instructions into Claude's context at plan mode entry. The new
`plannotator improve-context` subcommand reads the improvement hook file
and returns additionalContext, or silently passes through if no file exists.

For provenance purposes, this commit was AI assisted.

* fix(skill): update compound skill improvement hook path

Moves the improvement hook file path from ~/.plannotator/compound/ to
~/.plannotator/hooks/compound/ to namespace hook-injectable files
separately from other plannotator data.

For provenance purposes, this commit was AI assisted.

* fix(shared): add legacy path fallback for improvement hook reader

Users who ran the compound skill before the path migration have their
improvement hook file at ~/.plannotator/compound/ instead of the new
~/.plannotator/hooks/compound/ path. Add a fallback that checks the
legacy path only when the new path is absent — if the new path exists
but is invalid (empty, oversized), no fallback occurs to prevent
resurrecting stale instructions.

Includes tests for new-path-wins, legacy-fallback, and
invalid-new-blocks-legacy scenarios.

For provenance purposes, this commit was AI assisted.
2026-04-01 13:16:55 -07:00
foxytanuki ab9cae3488 cli: clarify no-arg usage and add top-level help (#448) 2026-03-31 09:36:10 -07:00
Stacey Haffner 2d90d381c9 fix: Detect calling agent via env vars and centralize agent config (#418)
* fix: detect calling agent via env vars for correct badge display

Add prioritized agent detection chain: CODEX_THREAD_ID (codex) >
COPILOT_CLI (copilot-cli) > default (claude-code). All subcommands
use the shared detectedOrigin constant. Display name updated from
'Copilot CLI' to 'GitHub Copilot'. TypeScript origin union type
updated to include 'copilot-cli'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor: centralize agent config into @plannotator/shared/agents

- Create packages/shared/agents.ts as single source of truth for Origin type,
  agent display names (AGENT_CONFIG), getAgentName(), and getAgentBadge()
- Replace all inline origin type unions across 8 files with Origin import
- Type detectedOrigin and ServerOptions.origin as Origin for compile-time safety
- Add @plannotator/shared dependency to @plannotator/editor package.json
- Adding a new agent is now a one-file change to AGENT_CONFIG

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore gitContext in startReviewServer call

Accidentally removed alongside the origin refactor. Without it, local
code reviews lose branch detection, diff-type switching, and CWD resolution.

For provenance purposes, this commit was AI assisted.

---------

Co-authored-by: Yecats <Yecats@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-03-28 16:37:56 -07:00
Stacey Haffner 15af1c40eb Add GitHub Copilot CLI integration (#409)
* feat: add GitHub Copilot CLI integration

Add full Copilot CLI support via a new plugin (apps/copilot/) with:
- preToolUse hook intercepting exit_plan_mode for plan review
- copilot-plan subcommand with correct permissionDecision output format
- copilot-last subcommand parsing events.jsonl for last message annotation
- Copilot CLI session parser (copilot-session.ts)
- Plugin manifest, hooks.json, and slash commands
- UI origin branding (blue badge for Copilot CLI)
- Marketplace entry for plugin discovery
- README install section

Key design decisions:
- Intercepts exit_plan_mode (not create) to avoid double-review
- Uses sessionId (with fallback) to locate plan.md from session state
- Follows Codex integration pattern: same CLI binary, new plugin dir

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: handle actual Copilot CLI preToolUse input format (toolCalls array)

The Copilot CLI hook input uses { toolCalls: [{ name, args }] } format
instead of the documented { toolName, toolArgs } format. The filter was
checking event.toolName (always undefined), causing exit_plan_mode to
pass through unintercepted. Now extracts tool name from either format.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Revert "fix: handle actual Copilot CLI preToolUse input format (toolCalls array)"

This reverts commit b214bc02c58fc5d8f305b128457099afb2ea6ecc.

* fix: use absolute path in hooks.json for reliable binary discovery

The hook subprocess inherits the PATH from when Copilot CLI was started,
which may not include the plannotator binary directory. Using the full
absolute path via env vars ensures the hook always finds the binary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Finishing the feature

* fix: remove Copilot CLI plugin from Claude Code marketplace.json

.claude-plugin/marketplace.json is for Claude Code's plugin marketplace.
The Copilot CLI plugin has its own install path and doesn't belong here.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: align Copilot CLI plugin with official spec

- Add .github/plugin/marketplace.json for Copilot CLI plugin discovery
- Change allowed-tools from Bash() to shell() syntax in commands
- Respect COPILOT_HOME env var in session path discovery
- Update plugin.json version to 0.14.5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Yecats <Yecats@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: stahaff <222254525+stahaff@users.noreply.github.com>
2026-03-27 10:20:49 -07:00
Michael Ramos 919f582e0c feat: support folder annotation via plannotator annotate <folder/> (#389)
* feat: add hasMarkdownFiles() and folderPath support for annotate servers

Add hasMarkdownFiles() to resolve-file.ts — a shared validation function
that checks if a directory contains at least one markdown file, skipping
IGNORED_DIRS. Lives alongside the existing walkMarkdownFiles pattern.

Both annotate servers (Bun + Pi) accept an optional folderPath that
overrides projectRoot in the /api/plan response, and a new
"annotate-folder" mode value.

For provenance purposes, this commit was AI assisted.

* feat: support folder annotation via plannotator annotate <folder/>

Wire folder detection through both CLI entry points (Hook + Pi) and
the plan editor UI. When a directory is passed instead of a file:

- CLI validates markdown files exist via hasMarkdownFiles()
- Server starts with empty markdown and mode "annotate-folder"
- UI clears demo content, auto-opens sidebar to Files tab
- Empty state prompts user to select a file from the tree
- Linked doc system handles per-file annotation caching
- Feedback exports with per-file headers via existing infrastructure

Single-file annotation is unchanged.

For provenance purposes, this commit was AI assisted.

* fix: address review findings for folder annotation

- Include .mdx files in file browser globs (reference-handlers.ts)
- Fix imageBaseDir in folder mode — use folder path directly instead of
  stripping last segment, which produced the parent directory
- Guard statSync in Pi extension with try/catch to match hook server pattern

For provenance purposes, this commit was AI assisted.

* fix: align folder validation exclusions with file browser

hasMarkdownFiles() now accepts an excludedDirs parameter. Both CLI
entry points pass FILE_BROWSER_EXCLUDED so the preflight check
matches the file browser — no more empty sidebar after validation.

For provenance purposes, this commit was AI assisted.
2026-03-24 23:37:19 -07:00
Michael Ramos f96758da0a 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>
2026-03-24 00:44:54 -07:00
Michael Ramos a278fdaf77 feat: plan archive browser (#369)
* 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.
2026-03-23 11:31:38 -07:00
Michael Ramos 40002e69dc feat: GitLab merge request review support (#364)
* feat: GitLab merge request review support

Add full GitLab MR review parity with existing GitHub PR review:

- Auto-detect platform from URL (github.com vs any GitLab host)
- Extract GitHub logic into pr-github.ts, new pr-gitlab.ts implementation
- Widen PRRef/PRMetadata to discriminated unions for type safety
- Dispatch functions route to correct platform implementation
- Platform-aware UI labels (PR/MR, #/!, GitHub/GitLab icons)
- Self-hosted GitLab support via --hostname flag
- Normalize glab diff output to standard git format
- Handle glab CLI differences (no --jq, Content-Type header for --input)
- Defensive JSON parsing for GitLab context API responses

Tested against gitlab.com with inline comments, multi-line ranges,
approval, and PR context tabs (summary, comments, checks).

For provenance purposes, this commit was AI assisted.

* fix: correct GitLab enum mappings and add shared file path encoding

- Map GitLab job statuses to UI-expected enums (failed→FAILURE, canceled→NEUTRAL)
- Map GitLab detailed_merge_status to CLEAN/BLOCKED/BEHIND/DIRTY/UNKNOWN
- Fix false approval state on repos without required approvers
- Add shared encodeApiFilePath helper used by both GitHub and GitLab

For provenance purposes, this commit was AI assisted.

* fix: align panel headers and refine file tree selection style

- Use shared --panel-header-h CSS variable for consistent header heights
  across file tree search, file header, and annotations panel
- Update GitLab icon to use official tanuki SVG paths with currentColor
- Replace solid primary fill on active file tree items with 30% tinted
  background for better readability and semantic color preservation

For provenance purposes, this commit was AI assisted.
2026-03-22 10:22:36 -07:00
Yassir Janah d3cb2b945d fix: annotate-last resolves wrong session after cd (#366)
* fix: annotate-last resolves wrong session after cd

When the user cd's during a Claude Code session, /plannotator-last
picks up a stale session log from a different project directory because
findSessionLogsForCwd() uses the current shell CWD, not the session's
original project directory.

Add three-tier session resolution:
1. PPID metadata (~/.claude/sessions/<ppid>.json) for deterministic O(1) lookup
2. CWD slug match (existing behavior, unchanged)
3. Ancestor directory walk as fallback

Also: getLastRenderedMessage() now catches read errors instead of
throwing, and extracted tryLogCandidates() to deduplicate the
candidate iteration loop.

* fix: use case-insensitive slug lookup in PPID resolver

resolveSessionLogByPpid() was constructing the log path directly,
bypassing the case-insensitive directory scan that findSessionLogsForCwd()
already provides for Windows. Reuse that function instead.

For provenance purposes, this commit was AI assisted.

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-03-22 10:04:38 -07:00
Rock Neurotiko c352465254 feat: Approve and review PR to github (#352)
* Approve and review PR to github

* fix: GitHub review improvements — multi-line ranges, own-PR detection, error handling

- Add start_line/start_side to review payload for multi-line annotations
- Detect current GitHub user and disable Approve on own PRs with tooltip
- Fix loading state: approve button no longer shows "Approving..." during feedback
- Fix empty submit: block Post Comments when no annotations and no comment
- Fix Cmd+Enter in GitHub comment dialog
- Fix completion screen text for GitHub mode ("submitted to GitHub")
- Fix agent label fallback to "Your agent" instead of "OpenCode"
- Improve gh-not-installed error message with install URL
- Fix stale useCallback dependency array on handleGitHubAction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(review): destination dropdown, icon components, and UI polish

Replace Agent/GitHub toggle with a compact destination dropdown.
Buttons stay consistent ("Send Feedback"/"Post Comments" + "Approve")
with behavior routed by destination. Double-tap Option to toggle.

- Add GitHubIcon, RepoIcon, PullRequestIcon components (currentColor)
- Add repo/PR icons to editor header
- Add keyboard hint in destination dropdown
- Add double-Option shortcut to settings
- Hide duplicate filename from Pierre diff header
- Refine FileHeader typography (text-xs font-semibold, no mono)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: include VS Code editor annotations in GitHub PR reviews

Editor annotations were silently dropped when submitting to GitHub.
Now converts them to inline PR comments with selected text as context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename visibility, own-PR keyboard guard, and editor annotation filtering

- Keep data-prev-name visible in Pierre header for rename diffs
- Block Cmd+Enter approve path for own PRs (was only blocked via mouse)
- Filter editor annotations to diff files only to prevent GitHub API rejection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:48:26 -07:00
Michael Ramos 216815c12e feat: PR review support via GitHub URL (#324)
* feat(review): add runtime-agnostic PR provider

Introduces `packages/shared/pr-provider.ts` with a `PRRuntime` interface
(same pattern as ReviewGitRuntime in review-core.ts) and GitHub PR
operations: URL parsing, auth check, diff/metadata fetching, and file
content retrieval via `gh` CLI.

`packages/server/pr.ts` is the Bun wrapper that pre-binds the runtime,
matching the git.ts pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(review): add PR mode to review server

When `prMetadata` is provided to `startReviewServer`, the server enters
PR mode: `/api/diff` includes PR metadata and omits gitContext,
`/api/diff/switch` and `/api/git-add` return 400 (not applicable),
and `/api/file-content` fetches from GitHub API using base/head SHAs
instead of local git.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(review): PR review flow for Claude Code and OpenCode

Detects URL argument in `/plannotator-review` command. When a GitHub PR
URL is provided, fetches diff and metadata via `gh` CLI and starts the
review server in PR mode. Local review mode is unchanged when no URL
is passed.

Updates slash command definitions to pass $ARGUMENTS through.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(review): PR metadata display in review UI

Shows "PR Review" badge, PR title with link, and owner/repo in the
header when reviewing a pull request. Diff switcher and staging
controls auto-hide since gitContext is omitted in PR mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(review): contextualized PR feedback for agent

In PR mode, the feedback markdown now includes PR metadata (repo,
number, title, branches, URL) so the agent has full context about
the remote PR being reviewed. Removes the aggressive "address all
of them" instruction in PR mode since the content is self-explanatory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(review): extract exportReviewFeedback + add tests

Moves the pure feedback construction function from App.tsx to
utils/exportFeedback.ts so it can be unit tested. Drops the unused
`files` parameter. 11 tests covering local/PR headers, annotation
grouping, sorting, file-scope ordering, and suggested code rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(review): strengthen PR/local boundary tests

Replaces shallow header checks with comprehensive boundary assertions:
local mode must never contain PR-specific content (repo, URL, branches),
PR mode must include all context fields and exclude the generic header.
Covers null/undefined prMetadata edge cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(review): remove redundant empty-annotations test

Covered by the "no annotations: returns generic empty regardless of
prMetadata" test which checks all three cases (no arg, null, PR mode).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(review): guard fetchPR, fix UTF-8 decoding, correct useCallback deps

- Wrap fetchPR() in try/catch in both hook and OpenCode entry points
  so network/auth errors show a clean message instead of a stack trace
- Replace atob() with Buffer.from() for UTF-8 correct base64 decoding
  of PR file content from GitHub API
- Fix stale useCallback deps in handleCopyFeedback (files → prMetadata)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(install): add $ARGUMENTS to review command for PR URL support

Install scripts were writing the review slash command without
$ARGUMENTS, so PR URLs passed to /plannotator-review were silently
dropped. Also switches PS1 heredoc to single-quoted to prevent
$ARGUMENTS from being expanded as a PowerShell variable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add PR review support to docs and READMEs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 06:01:40 -07:00
Michael Ramos 6b775ea1ed feat: /plannotator-last — annotate the last agent message (#325)
* feat: add /plannotator-last command to annotate last assistant message

Adds a new slash command that extracts the last rendered assistant message
from Claude Code's session log and opens it in the annotation UI.

Session log parser (apps/hook/server/session-log.ts):
- Parses Claude Code JSONL logs at ~/.claude/projects/{slug}/*.jsonl
- Finds the last assistant message.id with text content blocks
- Skips noise entries (progress, system, file-history-snapshot, queue-operation)
- Filters system-generated user messages by prefix to avoid false turn boundaries
- Walks backward through empty turns when back-to-back user messages exist
- No anchoring — reads from end of log since <command-message> isn't written
  until after the binary completes

New files:
- apps/hook/commands/plannotator-last.md — slash command definition
- apps/hook/server/session-log.ts — Claude-Code-specific log parser
- apps/hook/server/session-log.test.ts — 30 tests covering streaming chunks,
  tool call turns, sub-agent noise, stop hooks, thinking blocks, and edge cases

Modified:
- apps/hook/server/index.ts — annotate-last subcommand

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: remove 3 redundant real-world scenario tests

These duplicated coverage already provided by focused unit tests:
- "full conversation" → covered by "grabs last message.id in multi-tool turn"
- "stop hook interrupted" → covered by "skips progress and system noise"
- "long tool-only sequence" → covered by "skips tool-only assistant entries"

Kept the thinking block test (unique coverage). 27 tests remain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add /plannotator-last command to Pi extension

Uses Pi's session manager API to find the last assistant message —
walks backward through ctx.sessionManager.getEntries(), finds the
last entry with role "assistant" and text content, opens it in the
annotation UI. Reuses existing isAssistantMessage(), getTextContent(),
startAnnotateServer(), and runBrowserReview() from the extension.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add /plannotator-last to OpenCode plugin + extract command handlers

Adds annotate-last command that fetches session messages via
client.session.messages(), finds the last assistant message with text
parts, and opens it in the annotation UI.

Refactors command handling: extracts review, annotate, and annotate-last
handlers from the inline event hook into commands.ts module. Reduces
index.ts by ~120 lines and makes adding future commands cleaner.

New files:
- apps/opencode-plugin/commands.ts — extracted command handlers
- apps/opencode-plugin/commands/plannotator-last.md — command metadata

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: context-aware UI labels for annotate-last mode

Adds "annotate-last" mode to the annotate server, passed through to the
UI via /api/plan response. The editor uses this to show "Copy message"
instead of "Copy plan", and "annotations on the message" in the
completion overlay.

- packages/server/annotate.ts: new `mode` option on AnnotateServerOptions
- packages/editor/App.tsx: annotateSource state derived from mode
- packages/ui/components/Viewer.tsx: copyLabel prop for button text
- All three harnesses pass mode: "annotate-last" in their callers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add Codex support to annotate-last command

Detects Codex via CODEX_THREAD_ID env var (injected by Codex into every
spawned process). Uses the thread ID to find the rollout file in
~/.codex/sessions/, parses the Codex rollout JSONL format to extract
the last assistant message.

Also adds `plannotator last` alias for shorter usage in Codex bang
commands (!plannotator last).

New files:
- apps/hook/server/codex-session.ts — Codex rollout parser
- apps/hook/server/codex-session.test.ts — 9 tests

Modified:
- apps/hook/server/index.ts — Codex detection + `last` alias

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: context-aware feedback title + top spacing for paragraph-first content

- exportAnnotations now accepts a title param: "Message Feedback" for
  annotate-last, "File Feedback" for file annotation, "Plan Feedback"
  for plan review (default)
- Adds top spacer when content starts with a paragraph (not a heading)
  and has no frontmatter, fixing tight spacing in annotate-last mode

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add sandbox scripts for Pi and Codex testing

- sandbox-pi.sh: builds extension, creates temp project, installs via
  `pi install`, launches Pi with sample files
- sandbox-codex.sh: compiles binary, creates temp project, launches
  Codex. Test with `!plannotator last`

Both follow the same pattern as sandbox-opencode.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add hook build step to opencode sandbox script

The opencode build copies HTML from hook/dist/ — without building hook
first, the sandbox could use stale HTML. Pi and Codex sandboxes already
had this step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove command body from plannotator-last to prevent agent response

The .md body was being sent to the agent as a prompt, causing it to
respond with "Opening annotation UI..." before the event handler could
fetch messages. That response became the "last message" instead of the
actual one. Empty body = agent stays silent, event handler intercepts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use command.execute.before hook for OpenCode annotate-last

Moves plannotator-last from the passive event hook to the
command.execute.before hook. This intercepts the command before the
agent sees it, clears output.parts so the agent stays silent, fetches
session messages, opens the annotation UI, then sends feedback via
client.session.prompt() — same pattern as review/annotate.

Previously the agent would respond to the command body before the
event handler could fetch messages, polluting the session history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add Codex to origin type and agent name mapping

Origin "codex" was falling through to the default "Coding Agent" label.
Added "codex" to the origin union type across annotate server, editor,
and removed the `as any` cast in the hook.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remote share link, plan-specific prose, and codex type unions

- Add writeRemoteShareLink to annotate-last onReady callback so remote
  sessions get a reachable URL
- Add subject parameter to exportAnnotations so feedback says "message"
  or "file" instead of "plan" when appropriate
- Add 'codex' to origin type unions in useAgents, Settings, UpdateBanner,
  and App.tsx fetch handler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: correct JSDoc for projectSlugFromCwd (leading dash is kept, not stripped)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: use RenderedMessage type instead of inline structural type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:49:34 -07:00
Michael Ramos 908ea9cf32 refactor: shared feedback templates + preserve plan title on deny (#296) (#298)
* refactor: shared feedback templates across all integrations

The deny/feedback prompts sent to LLM agents were duplicated as inline
string templates in hook, opencode-plugin, and pi-extension — each with
different tone and framing. The hook's directive style (from #224) was
the most effective at getting agents to address feedback. This extracts
all feedback text into @plannotator/shared/feedback-templates and has
every integration import from the single source of truth.

Closes #215 follow-up (propagates fix to OpenCode and Pi).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: rewrite feedback template tests around contracts not implementation

Tests now verify: cross-integration consistency, verbatim feedback
preservation, empty input handling, and that approved messages don't
contain directive language. Wording can change freely without breaking
tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: instruct agent to preserve plan title on resubmission (#296)

Version history slugs are derived from the plan's first # heading.
When the agent renames the heading after a deny, the version chain
breaks and the user loses diffs. The deny template now tells the
agent not to change the title unless explicitly asked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: improve plan deny preamble readability

Break the dense single-paragraph preamble into structured sections:
verdict, directive, and rules list. Easier for agents to parse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add missing @plannotator/shared workspace dependency

Hook and OpenCode plugin imported from @plannotator/shared/feedback-templates
without declaring it as a dependency. Worked locally but failed in CI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: remove code review and annotate from shared templates

Scope-crept into code review/annotate feedback which introduced a double
heading regression and dropped integration-specific strings. Reverts those
paths to their original inline strings; shared module now only covers
plan deny feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore Pi plan file hint and vendor template for source installs

Add optional planFilePath to planDenyFeedback so Pi can tell the agent
to read the plan file before editing. Check in a vendored copy of the
template so Pi source installs work without running build:pi first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 19:38:22 -07:00
Michael Ramos 54e951e2f7 fix: don't ask agent to address feedback on LGTM approval (#293)
* fix: don't ask agent to address feedback on LGTM approval (#284)

When the reviewer approves with no annotations, send a neutral
"Code review completed — no changes requested." message instead of
the contradictory "LGTM" + "Please address this feedback."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use explicit approved flag instead of annotations.length heuristic

The previous fix inferred LGTM from an empty annotations array, but
VS Code editor annotations are carried in feedbackMarkdown without
populating the annotations array — causing real review comments to be
misclassified as approvals. Thread an explicit `approved` boolean from
the UI through the review server to all three integrations.

Closes #284

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: append assertive instruction to review feedback output

When the reviewer submits actual feedback, append "The reviewer has
identified issues above. You must address all of them." so the agent
treats annotations with urgency rather than soft-acknowledging them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: annotate unused LGTM feedback string

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 17:58:57 -07:00
Michael Ramos c2037b78bf feat: --browser CLI flag & session discovery (#242)
* feat: add --browser CLI flag and session discovery (#135)

- Add `--browser <name>` global flag to override which browser opens
  (sets PLANNOTATOR_BROWSER, works with existing openBrowser() logic)
- Add `plannotator sessions` subcommand to list active server sessions
  with `--open [N]` to reopen in browser and `--clean` to prune stale entries
- Track active sessions in ~/.plannotator/sessions/<pid>.json with
  automatic stale cleanup via PID liveness checks
- Register/unregister sessions in all three server modes (plan, review, annotate)
- Export ./sessions and ./project from @plannotator/server package

Closes #135

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add integration test for session discovery

Non-interactive test script that validates:
- Session file creation/cleanup lifecycle
- Session file content (all fields)
- `plannotator sessions` listing, --open, --clean
- Stale PID auto-cleanup

Also syncs bun.lock versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add --browser flag and session discovery to configuration page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add troubleshooting page

Covers lost tabs (sessions --open), data storage locations,
browser issues, and hook not firing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:45:33 -08:00
dgrissen2 026194ad06 feat: smart file resolution for plannotator annotate (#237)
Add case-insensitive file resolution so users can open markdown files
without typing exact paths. Applies to both the CLI (`plannotator
annotate`) and the `/api/doc` endpoint for linked documents.

Resolution strategies (tried in order):
1. Exact path — absolute or relative to cwd (existing behavior)
2. Case-insensitive relative path — `docs/setup.md` matches `docs/SETUP.md`
3. Bare filename search — `setup.md` searches the entire project tree

Ambiguity handling:
- 1 match → opens the file, prints "Resolved: /full/path"
- 0 matches → "File not found: <input>"
- 2+ matches → "Ambiguous filename: found N matches" with full paths

Skips node_modules, .git, dist, build, .next, __pycache__, .obsidian,
.trash during search. Restricts results to .md/.mdx/.markdown files
and enforces project root boundary (no path traversal).

Extracted shared resolveMarkdownFile() into packages/server/resolve-file.ts
and refactored /api/doc handler to use it, removing ~40 lines of inline
resolution logic.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-03-07 08:09:57 -08:00
theflysurfer c762bbe31e fix(annotate): resolve file paths correctly on Windows (#233)
* fix(annotate): resolve file paths correctly on Windows

- Use PLANNOTATOR_CWD env var to get original working directory
- Strip @ prefix from file paths (Claude Code reference syntax)
- Add debug logging (PLANNOTATOR_DEBUG=1)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add missing path import for annotate subcommand

The dynamic `import("path")` was removed but `path.isAbsolute()` and
`path.join()` are still used, causing a ReferenceError at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Julien Fernandez <theflysurfer@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-03-07 07:58:17 -08:00
Michael Ramos 3dca97760a fix: add directive framing to deny message so Claude addresses feedback (#224)
Closes #215

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:20:38 -08:00
김영준E 09384361e4 fix: Fix review showing empty files + propagate diff errors to UI (#200)
* 🐛 fix: force standard a/b diff prefix to handle mnemonic prefix config

When diff.mnemonicPrefix is enabled in git config, `git diff` uses
context-dependent prefixes (c/ for commit, w/ for worktree, i/ for
index) instead of the standard a/b. Both the internal parseDiffToFiles
and @pierre/diffs library expect a/b prefixes, causing silent parse
failures that result in an empty file list.

Add --src-prefix=a/ --dst-prefix=b/ to all git diff invocations to
ensure consistent output regardless of user git configuration.

* ✨ feat: propagate git diff errors to review UI

Previously, git diff errors were silently caught and returned as empty
patches. The UI showed "No changes" with no indication of failure.

- Add error field to DiffResult and propagate through ReviewServerOptions
- Include error in /api/diff and /api/diff/switch responses
- Show distinct error state in review UI (red icon + error message)
- Clear/set error state on diff type switch

* Fix mnemonic prefix handling in pi-extension runGitDiff

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:26:07 -08:00
Gunit Bindal c470dcebf1 Add short URL sharing via paste service to fix URL truncation (#188)
* Add short URL sharing via paste service to fix URL truncation on Slack/messaging apps

Share URLs for large plans can be 10-40KB+ because the entire plan + annotations
are compressed into the URL hash. Services like Slack, WhatsApp, and Twitter
truncate these URLs, making shared plans unviewable (related: #142).

This adds an optional paste-service backend that stores compressed payloads and
returns short ~60-char URLs (e.g. share.plannotator.ai/p/aBcDeFgH).

Changes:
- Add Cloudflare Worker paste service (apps/paste-worker/) with KV storage
  and 90-day TTL for stored plans
- Add createShortShareUrl() and loadFromPasteId() to sharing utils
- Update useSharing hook to auto-generate short URLs with 1s debounce
- Update ExportModal to show short URL as primary copy target with
  full hash URL as backup
- Portal automatically supports /p/<id> paths via useSharing hook
- Fully backward compatible: hash-based URLs continue to work unchanged
- Graceful degradation: falls back to hash URLs if paste service is unavailable

* Fix critical bugs found during code review of short URL feature

- Worker: return only { id } so client constructs URL with its own
  shareBaseUrl (fixes self-hosted deployments)
- importFromShareUrl: handle /p/<id> short URLs in addition to hash
  URLs (fixes teammate import via short links)
- Anchor /p/<id> regex with ^ to prevent false matches on nested paths
- Pass shareBaseUrl to loadFromPasteId (was always defaulting)
- replaceState preserves base path instead of hardcoding /
- Clear stale shortShareUrl immediately on debounce to prevent showing
  outdated link during the 1s delay
- Add shareBaseUrl to dependency arrays for loadFromHash and
  importFromShareUrl callbacks
- Remove unused url field fallback from paste API response parsing

* Fix pasteApiUrl/shareBaseUrl confusion and add remote session share URLs

Bug fix (addresses @backnotprop's code review):
- useSharing hook now accepts separate `pasteApiUrl` parameter instead of
  incorrectly passing `shareBaseUrl` to `loadFromPasteId`. These are
  different domains (share portal vs paste backend). When omitted, the
  default `https://paste.plannotator.ai` is used correctly.
- Fixed in all three call sites: loadFromHash, importFromShareUrl, and
  generateShortUrl (via createShortShareUrl options).

Remote session share URLs (implements #192):
- When running on a remote instance (SSH, devcontainer), Plannotator now
  generates a share.plannotator.ai URL and prints it to stderr so the
  user can open the plan review in their local browser.
- Works for all three modes: plan review, code review, and annotate.
- Uses the same deflate-raw + base64url encoding as the client.
- Fails silently if URL generation fails — port forwarding still works.
- New server utility: packages/server/share-url.ts

* Address code review findings: fix spread limit, CORS, deps array

- share-url.ts: Replace btoa(String.fromCharCode(...compressed)) with
  a loop to avoid RangeError on plans >65K compressed bytes
- share-url.ts: Add cross-reference comment noting the server-side
  SharePayload is an intentional subset of the canonical UI type
- paste-worker: Only return CORS headers for allowed origins; disallowed
  origins get no CORS headers instead of misleading partial headers
- useSharing.ts: Remove unused shareBaseUrl from loadFromHash dependency
  array (loadFromHash only uses pasteApiUrl, not shareBaseUrl)

* Restructure paste service with pluggable storage, fix consent flow and security issues

Replaces monolithic paste-worker with multi-target paste-service architecture:
- Core logic (handler, storage interface, CORS) separated from deployment targets
- Filesystem store (self-hosted), KV store (Cloudflare), S3 stub (future)
- Bun binary target + Cloudflare Worker target
- Fix auto-upload removed: short URLs only created on explicit user click
- Clear stale short URL state when content changes
- Fix require('fs') in ESM module, add path-traversal guard in FsPasteStore
- Return 400 (not 500) for malformed JSON in both targets
- Fix compress() spread limit for large plans
- Updated docs: self-hosting guide, sharing guide, env vars, API endpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Simplify paste service: extract shared router, remove TOCTOU patterns

- Extract duplicated HTTP routing from bun.ts and cloudflare.ts into
  handleRequest() in core/handler.ts — targets are now thin wrappers
- Move ID_PATTERN regex to core (was duplicated in both targets)
- Remove existsSync guard before mkdirSync({recursive:true}) in fs.ts
- Remove file.exists() check before file.json() in fs.ts get() — the
  try/catch already handles missing files
- Extract DEFAULT_SHARE_BASE constant in sharing.ts (was hardcoded twice)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Deduplicate compression, image parsing, and remote share URL logic

- Create packages/shared/ with compress/decompress (single source of truth)
  — both @plannotator/server and @plannotator/ui import from here
- Export parseShareableImages from sharing.ts, remove duplicate
  parseGlobalAttachments from useSharing.ts
- Extract writeRemoteShareLink() helper in share-url.ts, replace 3
  near-identical blocks in apps/hook/server/index.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix modulo bias in paste ID generation and remove nonexistent doc artifacts

- Use rejection sampling in generateId() to eliminate modulo bias
  (bytes >= 248 discarded, uniform distribution over 62 chars)
- Remove references to install-paste.sh and Docker image that don't
  exist yet; point self-hosting docs to GitHub Releases binaries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:55:55 -08:00
Michael Ramos e46815d8ea feat: named image references and annotate command (#147)
* feat: named image references and annotate command (#67, #109)

Add human-readable names to image attachments throughout the annotation
pipeline, and add a new `plannotator annotate <file.md>` command for
annotating arbitrary markdown files.

Image names: ImageAttachment type replaces plain string paths, upload
endpoints return originalName, editable name inputs under thumbnails,
[name] path format in exported feedback, backward-compatible sharing.

Annotate command: new server module reusing plan editor HTML with
mode:"annotate", CLI subcommand, slash commands for Claude Code and
OpenCode, annotate mode UI (hides Approve, shows Send Annotations).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move image name input to ImageAnnotator screen

The name input now appears on the full-screen annotator modal that opens
immediately when uploading/pasting an image, pre-populated from the
filename. Removes the disruptive inline name editing from thumbnails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: stale closure in paste handler, update CLAUDE.md for new features

Fix race condition where globalAttachments was captured as empty array
in the paste event listener (missing dependency). Also update CLAUDE.md
to document ImageAttachment type, annotate server/flow, updated sharing
format with image support, and new slash commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 20:23:49 -08:00
Michael Ramos 0b47dd54a7 feat: add configurable share URL for self-hosted portals (#146)
Add PLANNOTATOR_SHARE_URL env var so users can point share links at
their own self-hosted portal instance instead of share.plannotator.ai.
Threads the base URL through the same path as sharingEnabled: env var →
server options → API response → editor state → useSharing hook →
generateShareUrl(). Includes self-hosting guide and documentation.

Closes #12

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 15:07:43 -08:00
Michael Ramos 131d1b9d0e feat: add config to disable URL sharing (#69)
Add deployment-level configuration to disable URL sharing for
security/privacy concerns.

Configuration methods:
- Claude Code: PLANNOTATOR_SHARE=disabled env var
- OpenCode: "share": "disabled" in opencode.json (primary), env var fallback

When disabled:
- Quick Share button hidden in annotation panel
- Share tab hidden in export modal (shows only Raw Diff)

Closes #69

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 13:12:37 -08:00
Michael Ramos 0e07425116 feat: permission mode preservation for Claude Code hooks (#71)
* feat: add permission mode utility and setup dialog

Add cookie-based storage for permission mode preference with first-time
setup dialog for Claude Code users. Supports three modes:
- Auto-accept Edits (default)
- Bypass Permissions (equivalent to --dangerously-skip-permissions)
- Manual Approval

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: integrate permission mode in editor and settings

- Show first-time setup dialog for Claude Code users
- Add permission mode dropdown to Settings (Claude Code only)
- Pass selected mode in approve request

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: output updatedPermissions in hook response

Use Claude Code 2.1.7+ hook protocol to preserve permission mode after
plan approval. Extracts permission_mode from hook input, allows client
to override via request, and outputs setMode in updatedPermissions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 20:31:23 -08:00
Michael Ramos 1ee40ef144 fix: always open review UI, allow switching diff types
Remove early exit when no uncommitted changes - user can switch to
"Last commit" or other diff types via dropdown.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 20:16:07 -08:00
Michael Ramos 8019f73a44 Feat/code review system (#57)
## Summary
Complete code review system for reviewing git diffs with annotations.

### Features
- Interactive diff viewer with split/unified views
- Line-level annotation system
- Diff type selector (uncommitted, last commit, vs main branch)
- Dynamic default branch detection
- Empty state handling
- Simplified UX with streamlined feedback flow

Closes #51
Closes #56
2026-01-12 19:36:09 -08:00
Michael Ramos 14e9aea0ec Fix/devcontainer & plan auto switching (opencode) (#31)
* Refactor: shared server package with PLANNOTATOR_REMOTE env var

- Create packages/server/ with shared server implementation
- Add PLANNOTATOR_REMOTE=1 env var for devcontainer/SSH mode
- Deprecate SSH_CONNECTION detection (still works with warning)
- Both Claude Code and OpenCode now use identical server logic
- OpenCode gains Obsidian/Bear integrations and remote detection
- Update documentation with environment variables section

Fixes #27

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Remove PLANNOTATOR_ORIGIN env var, fix onReady callback timing

- Remove PLANNOTATOR_ORIGIN hack from Claude Code hook (hardcode "claude-code")
- Fix onReady callback to pass port directly (was referencing undefined server)
- Create tests/manual/test-server.ts for testing either origin
- Update test-hook-2.sh to use new test server with opencode origin

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add devcontainer test setup, reduce logging verbosity

- Add tests/devcontainer/ with devcontainer.json for testing remote mode
- Add tests/opencode-local/ for local OpenCode testing
- Update package.json build script for proper bundling
- Remove verbose multi-line error messages, keep useful errors
- Clean up unused deprecationWarned variable

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add devcontainer support and documentation

- Add PLANNOTATOR_REMOTE env var for container/remote detection
- Remove console logging (silent operation)
- Add devcontainer.md with full setup instructions
- Add devcontainer section to OpenCode plugin README
- Add port-only test setup to reproduce common misconfiguration
- Update test devcontainers to forward port 4096 for opencode web
- Bump version to 0.4.1

Fixes #27

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Update CLAUDE.md: legacy SSH detection, not deprecated

Removed incorrect mention of deprecation warning - SSH_TTY/SSH_CONNECTION
detection is silent by design.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Fix OpenCode agent switching after plan approval

After approving a plan, the conversation context stayed in "plan" mode
even though the TUI showed "build". This caused edits to fail because
the plan agent has edit permissions denied.

The fix uses session.prompt() to inject a message with agent: "build",
which triggers a new agentic loop with proper build agent permissions.

Fixes #29

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 14:25:19 -08:00
Michael Ramos 386a9e6d4a Feat: Add image attachments with annotation drawing
- Add global (plan-level) and per-annotation image attachments
- Image upload via drag-drop, file picker, or clipboard paste (Cmd+V)
- ImageAnnotator component for drawing on images before saving
  - Freehand pen, arrow, and circle tools
  - Adjustable stroke size and color presets
  - Edge-to-edge circle drawing
- Server endpoints for image upload (/api/upload) and serving (/api/image)
- Images stored as paths in /tmp/plannotator/, included in export for Claude
- Preserve image paths in URL sharing for round-trip scenarios
- Uses perfect-freehand (~2KB) for smooth pen strokes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 17:06:43 -08:00
Michael Ramos 1820142d28 Feat: Add origin tracking badge (Claude Code vs OpenCode)
- Server returns origin field in /api/plan response
- UI displays badge next to version in header
- Claude Code: orange badge
- OpenCode: neutral zinc/gray badge
- Supports PLANNOTATOR_ORIGIN env var for testing

Closes #22

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 14:16:35 -08:00
Michael Ramos 685c67f780 Feat: Add project name to tags (Obsidian/Bear)
- Add project.ts utility for detecting git repo name
- Fallback to directory name if not in a git repo
- Include project name as second tag after "plannotator"
- Add 21 unit tests for project detection
- Increase tag limit from 6 to 7

Closes #24

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 09:14:31 -08:00
Michael Ramos 8828ca01ba Feat: Add Bear Notes integration (#21)
- Add bear.ts utility for settings storage
- Add Bear toggle to Settings UI (simple on/off)
- Add saveToBear() using x-callback-url protocol
- Reuse extractTags() for inline #hashtags
- Change base tag from "plan" to "plannotator"
- Add test-hook-2.sh with alternate plan for testing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 10:14:36 -08:00
Michael Ramos 6aae06500c Feat/save to obsidian (#11)
* Add Obsidian integration for auto-saving approved plans

- Auto-detect Obsidian vaults from system config
- Settings UI with vault dropdown and folder config
- Save plans with YAML frontmatter (created, source, tags)
- Extract tags heuristically from H1 title and code fence languages
- Human-readable filenames: "Title - Jan 2, 2026 2-30pm.md"
- Cross-platform support (macOS, Windows, Linux)
- Add local test script for hook simulation

* Add backlinks and improve filename generation

- Add [[Plannotator Plans]] backlink to saved notes for graph connectivity
- Extract title from H1 for human-readable filenames
- Format: "Title - Jan 2, 2026 2-30pm.md"
- Add bulk test script for ~/.claude/plans
- Add fix script to add backlinks to existing vault files

* Add Obsidian integration docs to README, landing page, and plugins

- Root README: Quick note about Obsidian support after video table
- apps/hook README: Detailed setup and example file format
- apps/opencode-plugin README: Feature list and setup instructions
- Landing page: New "Save to Obsidian" feature card

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add Obsidian Integration section to README

* Screenshot Obsidian settings

Added an implementation plan section for user authentication.

* Update README with Obsidian auto-save feature

Added information about auto-saving approved plans to Obsidian.

* Update README with Obsidian integration link

Added link to details about Obsidian integration.

* Update README to include Obsidian auto-saving feature

Clarified the new feature for saving to Obsidian with a link.

---------
2026-01-02 11:08:19 -08:00
Dave Smith 3391d071a4 Add SSH remote support with fixed port (#5)
* Add SSH remote session support with fixed port

- Auto-detect SSH sessions via SSH_TTY/SSH_CONNECTION env vars
- Use fixed port 19432 over SSH for predictable port forwarding
- Add PLANNOTATOR_PORT env var for custom port configuration
- Add retry loop (5 attempts) for port conflicts
- Print SSH config hint when running remotely
- Skip browser auto-open in SSH sessions

* integration test for ssh

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2025-12-31 10:00:03 -08:00
Michael Ramos 3b88fdc25a Clean up duplicate log and fix indentation in server
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 22:05:21 -08:00
ChocoMilk#4759 37a274ccab Add cross-platform browser support (Windows/Linux)
- Windows: use `cmd /c start`
- Linux: use `xdg-open`
- macOS: keep existing `open`
2025-12-30 14:41:22 +09:00
Michael Ramos 44abf97ade Fix: embed HTML at compile time for binary distribution
The server was reading dist/index.html from filesystem at runtime,
which fails in compiled standalone binaries. Changed to use Bun's
import attributes to embed the HTML at compile time.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 15:28:58 -08:00
Michael Ramos 4e654f369d Add Claude Code plugin structure for marketplace distribution
- Rename apps/hooks → apps/hook (singular)
- Add .claude-plugin/plugin.json manifest
- Add hooks/hooks.json for PermissionRequest hook
- Add root .claude-plugin/marketplace.json for install via:
  /plugin marketplace add backnotprop/plannotator
- Refactor server to read stdin directly (removes bash/jq dependency)
- Delete shell wrapper script

Plugin tested successfully with: claude --plugin-dir ./apps/hook

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 09:54:48 -08:00