Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 97 additions & 30 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@ plannotator/
├── apps/
│ ├── hook/ # Claude Code plugin
│ │ ├── .claude-plugin/plugin.json
│ │ ├── commands/ # Slash commands (plannotator-review.md)
│ │ ├── commands/ # Slash commands (plannotator-review.md, plannotator-annotate.md)
│ │ ├── hooks/hooks.json # PermissionRequest hook config
│ │ ├── server/index.ts # Entry point (plan + review subcommand)
│ │ ├── server/index.ts # Entry point (plan + review + annotate subcommands)
│ │ └── dist/ # Built single-file apps (index.html, review.html)
│ ├── opencode-plugin/ # OpenCode plugin
│ │ ├── commands/ # Slash commands (plannotator-review.md)
│ │ ├── index.ts # Plugin entry with submit_plan tool + review event handler
│ │ ├── plannotator.html # Built plan review app
│ │ └── review-editor.html # Built code review app
│ └── review/ # Standalone review server (for development)
│ ├── index.html
│ ├── index.tsx
│ └── vite.config.ts
├── packages/
│ ├── core/ # Shared types and utilities (CUSTOM)
│ │ ├── types.ts # AnnotationType, ReviewTag, Annotation, Block
│ │ ├── parser.ts # parseMarkdownToBlocks(), exportDiff()
│ │ ├── markers.ts # injectValidationMarkers(), extractValidationMarkers()
│ │ └── index.ts # Re-exports
│ ├── server/ # Shared server implementation
│ │ ├── index.ts # startPlannotatorServer(), handleServerReady()
│ │ ├── annotate.ts # startAnnotateServer() (CUSTOM)
│ │ ├── review.ts # startReviewServer(), handleReviewServerReady()
│ │ ├── storage.ts # Plan saving to disk (getPlanDir, savePlan, etc.)
│ │ ├── remote.ts # isRemoteSession(), getServerPort()
Expand All @@ -33,9 +34,9 @@ plannotator/
│ │ └── project.ts # Project name detection for tags
│ ├── ui/ # Shared React components
│ │ ├── components/ # Viewer, Toolbar, Settings, etc.
│ │ ├── utils/ # parser.ts, sharing.ts, storage.ts, planSave.ts, agentSwitch.ts
│ │ ├── utils/ # sharing.ts, storage.ts, planSave.ts, agentSwitch.ts
│ │ ├── hooks/ # useSharing.ts
│ │ └── types.ts
│ │ └── types.ts # Re-exports from @plannotator/core
│ ├── editor/ # Plan review App.tsx
│ └── review-editor/ # Code review UI
│ ├── App.tsx # Main review app
Expand Down Expand Up @@ -98,8 +99,7 @@ Deny → stdout: {"hookSpecificOutput":{"decision":{"behavior":"deny","messag
```
User runs /plannotator-review command
Claude Code: plannotator review subcommand runs
OpenCode: event handler intercepts command
plannotator review subcommand runs
git diff captures unstaged changes
Expand Down Expand Up @@ -137,7 +137,7 @@ Both servers use random ports locally or fixed port (`19432`) in remote mode.

## Data Types

**Location:** `packages/ui/types.ts`
**Location:** `packages/core/types.ts` (re-exported from `packages/ui/types.ts`)

```typescript
enum AnnotationType {
Expand All @@ -148,31 +148,107 @@ enum AnnotationType {
GLOBAL_COMMENT = "GLOBAL_COMMENT",
}

// Review methodology tags for structured feedback
enum ReviewTag {
// Modification (action required)
TODO = "@TODO",
FIX = "@FIX",
CLARIFY = "@CLARIFY",
MISSING = "@MISSING",
ADD_EXAMPLE = "@ADD-EXAMPLE",
// Verification (fact-checking)
VERIFY = "@VERIFY",
VERIFY_SOURCES = "@VERIFY-SOURCES",
CHECK_FORMULA = "@CHECK-FORMULA",
CHECK_LINK = "@CHECK-LINK",
// Validation
OK = "@OK",
APPROVED = "@APPROVED",
LOCKED = "@LOCKED",
}

interface Annotation {
id: string;
blockId: string;
startOffset: number;
endOffset: number;
type: AnnotationType;
text?: string; // For comment/replacement/insertion
tag?: ReviewTag; // Optional methodology tag
isMacro?: boolean; // [MACRO] flag - cross-document impact
text?: string; // For comment/replacement/insertion
originalText: string; // The selected text
createdA: number; // Timestamp
author?: string; // Tater identity
createdAt: number; // Timestamp
author?: string; // Tater identity
imagePaths?: string[];
startMeta?: { parentTagName; parentIndex; textOffset };
endMeta?: { parentTagName; parentIndex; textOffset };
}

interface Block {
id: string;
type: "paragraph" | "heading" | "blockquote" | "list-item" | "code" | "hr";
type: "paragraph" | "heading" | "blockquote" | "list-item" | "code" | "hr" | "table";
content: string;
level?: number; // For headings (1-6)
level?: number; // For headings (1-6)
language?: string; // For code blocks
checked?: boolean; // For checkbox list items
order: number;
startLine: number;
}
```

## Custom Features (Fork Extensions)

### Review Tags

Structured methodology tags for annotations. Available in toolbar dropdown:

| Category | Tags | Purpose |
|----------|------|---------|
| Modification | `@TODO`, `@FIX`, `@CLARIFY`, `@MISSING`, `@ADD-EXAMPLE` | Action required |
| Verification | `@VERIFY`, `@VERIFY-SOURCES`, `@CHECK-FORMULA`, `@CHECK-LINK` | Fact-checking |
| Validation | `@OK`, `@APPROVED`, `@LOCKED` | Section validation |

### [MACRO] Flag

Toggle button in toolbar to mark annotations with cross-document impact. When enabled:
- Badge appears in annotation panel
- Export includes `[MACRO]` prefix: `## 1. @FIX [MACRO] Feedback on:`
- Claude understands to check related documents

### Annotate Mode

Annotate any markdown file (not just plans from ExitPlanMode):

```bash
plannotator annotate <file.md>
# Or via slash command:
/plannotator-annotate README.md
```

Features:
- File path shown in header
- Save Markers button (validation markers only)
- Feedback exported to stdout

### Persistent Validation Markers

Validation tags (@OK, @APPROVED, @LOCKED) can be saved to source file as HTML comments:

```markdown
<!-- @APPROVED -->
## Step 1: Initialize
```

API endpoint: `POST /api/save-markers` (annotate mode only)

### Annotate Server (`packages/server/annotate.ts`)

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/plan` | GET | Returns `{ plan, mode: "annotate", filePath, existingMarkers }` |
| `/api/save-markers` | POST | Write validation markers to source file |
| `/api/feedback` | POST | Submit feedback (resolves with feedback text) |

## Markdown Parser

**Location:** `packages/ui/utils/parser.ts`
Expand Down Expand Up @@ -235,7 +311,7 @@ type ShareableAnnotation =

**Location:** `packages/ui/utils/storage.ts`, `planSave.ts`, `agentSwitch.ts`

Uses cookies (not localStorage) because each hook invocation runs on a random port. Settings include identity, plan saving (enabled/custom path), and agent switching (OpenCode only).
Uses cookies (not localStorage) because each hook invocation runs on a random port. Settings include identity and plan saving (enabled/custom path).

## Syntax Highlighting

Expand All @@ -244,7 +320,7 @@ Code blocks use bundled `highlight.js`. Language is extracted from fence (```rus
## Requirements

- Bun runtime
- Claude Code with plugin/hooks support, or OpenCode
- Claude Code with plugin/hooks support
- Cross-platform: macOS (`open`), Linux (`xdg-open`), Windows (`start`)

## Development
Expand All @@ -262,22 +338,13 @@ bun run dev:marketing # Marketing site
## Build

```bash
bun run build:hook # Single-file HTML for hook server
bun run build:hook # Single-file HTML for hook server (main target)
bun run build:review # Code review editor
bun run build:opencode # OpenCode plugin (copies HTML from hook + review)
bun run build:portal # Static build for share.plannotator.ai
bun run build:marketing # Static build for plannotator.ai
bun run build # Build hook + opencode (main targets)
```

**Important:** The OpenCode plugin copies pre-built HTML from `apps/hook/dist/` and `apps/review/dist/`. When making UI changes (in `packages/ui/`, `packages/editor/`, or `packages/review-editor/`), you must rebuild the hook/review first:

```bash
bun run build:hook && bun run build:opencode # For UI changes
bun run build # Alias for build:hook
```

Running only `build:opencode` will copy stale HTML files.

## Test plugin locally

```
Expand Down
19 changes: 19 additions & 0 deletions apps/hook/commands/plannotator-annotate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
description: Annotate any markdown file with visual feedback
allowed-tools: Bash(plannotator:*)
---

## Markdown Annotation Feedback

!`plannotator annotate $ARGUMENTS`

## Your task

Address the feedback above. The user has reviewed the markdown file and provided specific annotations:

- **DELETION**: Remove the specified text
- **REPLACEMENT**: Change text from X to Y exactly as indicated
- **COMMENT**: Consider this feedback (may or may not require changes)
- **INSERTION**: Add new content at the specified location

Apply the requested changes to the file.
7 changes: 4 additions & 3 deletions apps/hook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
"@tailwindcss/vite": "^4.1.18"
},
"devDependencies": {
"@vitejs/plugin-react": "^5.0.0",
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "4.3.4",
"micromatch": "^4.0.8",
"typescript": "~5.8.2",
"vite": "^6.2.0",
"vite-plugin-singlefile": "^2.0.3",
"@types/node": "^22.14.0"
"vite-plugin-singlefile": "^2.0.3"
}
}
31 changes: 31 additions & 0 deletions apps/hook/scripts/julien-doc-plannotator.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash
# Julien Doc Plannotator - Bash Wrapper
# Runs plannotator from local fork instead of marketplace version
# Location: ~/.claude/scripts/julien-doc-plannotator.sh

PLANNOTATOR_PATH="$HOME/OneDrive/Coding/_Projets de code/2026.01 Planotator (Claude Code annotation)/plannotator"

# Capture original working directory BEFORE cd'ing
# Convert to Windows-style path for Node/Bun compatibility
if command -v cygpath &> /dev/null; then
# Git Bash / MSYS2 / Cygwin - convert Unix path to Windows
export PLANNOTATOR_CWD="$(cygpath -w "$(pwd)")"
elif [[ "$(pwd)" == /[a-zA-Z]/* ]]; then
# Manual conversion for /c/Users/... style paths
DRIVE_LETTER="${PWD:1:1}"
REST_OF_PATH="${PWD:2}"
export PLANNOTATOR_CWD="${DRIVE_LETTER}:${REST_OF_PATH//\//\\}"
else
# Already Windows-style or native path
export PLANNOTATOR_CWD="$(pwd)"
fi

# Debug output (set PLANNOTATOR_DEBUG=1 to enable)
if [[ -n "$PLANNOTATOR_DEBUG" ]]; then
echo "[DEBUG] Original PWD: $(pwd)" >&2
echo "[DEBUG] PLANNOTATOR_CWD: $PLANNOTATOR_CWD" >&2
echo "[DEBUG] Arguments: $@" >&2
fi

# Run with bun from the workspace root (required for package resolution)
cd "$PLANNOTATOR_PATH" && bun run "apps/hook/server/index.ts" "$@"
74 changes: 72 additions & 2 deletions apps/hook/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
/**
* Plannotator CLI for Claude Code
*
* Supports two modes:
* Supports three modes:
*
* 1. Plan Review (default, no args):
* - Spawned by ExitPlanMode hook
* - Reads hook event from stdin, extracts plan content
* - Serves UI, returns approve/deny decision to stdout
*
* 2. Code Review (`plannotator review`):
* - Triggered by /review slash command
* - Triggered by /plannotator-review slash command
* - Runs git diff, opens review UI
* - Outputs feedback to stdout (captured by slash command)
*
* 3. Markdown Annotation (`plannotator annotate <file.md>`):
* - Triggered by /plannotator-annotate slash command
* - Opens any markdown file for annotation
* - Outputs feedback to stdout for Claude to apply corrections
*
* Environment variables:
* PLANNOTATOR_REMOTE - Set to "1" or "true" for remote mode (preferred)
* PLANNOTATOR_PORT - Fixed port to use (default: random locally, 19432 for remote)
*/

import path from "path";
import {
startPlannotatorServer,
handleServerReady,
Expand All @@ -26,6 +32,7 @@ import {
startReviewServer,
handleReviewServerReady,
} from "@plannotator/server/review";
import { startAnnotateServer } from "@plannotator/server/annotate";
import { getGitContext, runGitDiff } from "@plannotator/server/git";

// Embed the built HTML at compile time
Expand Down Expand Up @@ -82,6 +89,69 @@ if (args[0] === "review") {
console.log(result.feedback || "No feedback provided.");
process.exit(0);

} else if (args[0] === "annotate") {
// ============================================
// MARKDOWN ANNOTATION MODE
// ============================================

let filePath = args[1];
if (!filePath) {
console.error("Usage: plannotator annotate <file.md>");
process.exit(1);
}

// Strip @ prefix if present (Claude Code file reference syntax)
if (filePath.startsWith("@")) {
filePath = filePath.slice(1);
}

// Resolve path - use PLANNOTATOR_CWD if set (original working directory before script cd'd)
const originalCwd = process.env.PLANNOTATOR_CWD || process.cwd();

// Debug: log path resolution (visible in stderr, won't interfere with stdout JSON)
if (process.env.PLANNOTATOR_DEBUG) {
console.error(`[DEBUG] Original CWD: ${originalCwd}`);
console.error(`[DEBUG] File path arg: ${filePath}`);
}

const absolutePath = path.isAbsolute(filePath)
? filePath
: path.join(originalCwd, filePath);

if (process.env.PLANNOTATOR_DEBUG) {
console.error(`[DEBUG] Resolved path: ${absolutePath}`);
}

// Read file
const file = Bun.file(absolutePath);
if (!(await file.exists())) {
console.error(`File not found: ${absolutePath}`);
process.exit(1);
}
const markdown = await file.text();

// Start annotation server (reuses plan UI)
const server = await startAnnotateServer({
markdown,
filePath: absolutePath,
origin: "claude-code",
sharingEnabled,
htmlContent: planHtmlContent,
onReady: handleServerReady,
});

const result = await server.waitForDecision();

// Give browser time to receive response and update UI
await Bun.sleep(1500);

// Cleanup
server.stop();

// Output feedback markdown (captured by slash command)
console.log(result.feedback || "No annotations provided.");
process.exit(0);

} else {
// ============================================
// PLAN REVIEW MODE (default)
Expand Down
Loading