plan-review 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/dist/index.js +0 -1
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# plan-review
|
|
2
|
+
|
|
3
|
+
Interactive CLI for reviewing AI-generated markdown plans. Parses a plan into sections, opens a three-panel browser review UI, collects line-anchored comments, and pipes structured feedback back — to the AI that wrote the plan, your clipboard, or a file.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g plan-review
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Claude Code skill (optional)
|
|
14
|
+
|
|
15
|
+
If you use Claude Code, install the companion skill so you can say *"review this plan"*:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
plan-review install-skill
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Review a plan — opens the browser UI (default)
|
|
25
|
+
plan-review path/to/plan.md
|
|
26
|
+
|
|
27
|
+
# Try the included fixture
|
|
28
|
+
plan-review examples/renderer-fixture.md
|
|
29
|
+
|
|
30
|
+
# Pipe feedback directly back to Claude
|
|
31
|
+
plan-review path/to/plan.md -o claude
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That's it. The browser mode is the default and the recommended way to review plans — line-anchored comments, auto-save, full markdown rendering including mermaid, math, footnotes, and admonitions.
|
|
35
|
+
|
|
36
|
+
## Browser mode (default)
|
|
37
|
+
|
|
38
|
+
Three panels:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
+------------------+----------------------------+------------------+
|
|
42
|
+
| | | |
|
|
43
|
+
| Table of | Rendered markdown | Comment |
|
|
44
|
+
| Contents | with plan metadata | Sidebar |
|
|
45
|
+
| | | |
|
|
46
|
+
| - Milestone 1 | ## Task 1.1 | [Add comment] |
|
|
47
|
+
| * Task 1.1 ✓ | | |
|
|
48
|
+
| * Task 1.2 | **Depends on:** 1.0 | > "Line 3-5" |
|
|
49
|
+
| - Milestone 2 | **Blocks:** 1.2 | Fix the error |
|
|
50
|
+
| * Task 2.1 | | handling here |
|
|
51
|
+
| | Content with line | |
|
|
52
|
+
| | gutters for anchoring | [Submit Review]|
|
|
53
|
+
| | comments to ranges | |
|
|
54
|
+
+------------------+----------------------------+------------------+
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Line-anchored comments.** Click a gutter marker to start a selection, shift-click another line to extend the range. Comments anchor to the exact lines and travel back in the output.
|
|
58
|
+
|
|
59
|
+
**Section-level comments.** "Add comment to entire section" under any section header when line-level granularity isn't needed.
|
|
60
|
+
|
|
61
|
+
**Auto-save.** Your progress writes to `~/.plan-review/sessions/` as you work. Close the tab, come back later, pick up where you left off. Closing the tab mid-review exits the CLI cleanly with the session preserved.
|
|
62
|
+
|
|
63
|
+
**Full markdown rendering.** Paragraphs, nested lists, task lists, tables, code fences, blockquotes, GFM admonitions (`> [!NOTE]`), footnotes, inline HTML (`<kbd>`, `<sub>`, `<sup>`, `<details>`), emoji shortcodes, horizontal rules, images, reference-style links — plus mermaid diagrams and KaTeX math, both lazy-loaded from CDN only when the plan contains them.
|
|
64
|
+
|
|
65
|
+
## Terminal mode (`--cli`)
|
|
66
|
+
|
|
67
|
+
For SSH sessions, CI, or headless environments where launching a browser isn't an option:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
plan-review path/to/plan.md --cli
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Interactive terminal UI with a table of contents, section navigation, and inline commenting.
|
|
74
|
+
|
|
75
|
+
| Command | Action |
|
|
76
|
+
|---------|--------|
|
|
77
|
+
| `all` | Linear review through all sections |
|
|
78
|
+
| `1.1` | Jump to a specific section |
|
|
79
|
+
| `done` / `q` | Finish review |
|
|
80
|
+
| `toc` | Return to table of contents |
|
|
81
|
+
| `back` | Go to previous section |
|
|
82
|
+
| *(enter)* | Skip section |
|
|
83
|
+
| *(any text)* | Add comment on current section |
|
|
84
|
+
|
|
85
|
+
Terminal mode is a fallback — you get text rendering and section-level comments, but no line anchors, no mermaid, no math, no live markdown preview.
|
|
86
|
+
|
|
87
|
+
## Options
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
-o, --output <target> Output target: stdout, clipboard, file, claude
|
|
91
|
+
--output-file <path> Custom output file path (with --output file)
|
|
92
|
+
--split-by <strategy> Force split strategy: heading, separator
|
|
93
|
+
--fresh Skip session resume, start clean review
|
|
94
|
+
--cli Use the terminal review UI instead (SSH/CI/headless)
|
|
95
|
+
-V, --version Show version
|
|
96
|
+
-h, --help Show help
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## The AI feedback loop
|
|
100
|
+
|
|
101
|
+
The point is closing the loop between AI-generated plans and human review:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
AI writes plan → You review with plan-review → Feedback pipes to Claude → AI revises
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Review in browser, send structured feedback straight to Claude
|
|
109
|
+
plan-review plan.md -o claude
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Line-anchored, section-scoped comments become input the AI can act on — not a wall of prose in a chat message.
|
|
113
|
+
|
|
114
|
+
## How it works
|
|
115
|
+
|
|
116
|
+
1. **Parses** your markdown — auto-detects plan-style documents (milestones, tasks, dependencies) or falls back to generic heading-based splitting.
|
|
117
|
+
2. **Renders** in the browser by default, or in the terminal via `--cli`.
|
|
118
|
+
3. **Collects** your comments as you review each section.
|
|
119
|
+
4. **Outputs** structured markdown with your comments alongside the original content.
|
|
120
|
+
|
|
121
|
+
### Plan mode
|
|
122
|
+
|
|
123
|
+
Documents with `## Milestone` / `### Task` hierarchy and fields like `**Depends On:**`, `**Blocks:**`, `**Verification:**` are detected as plans. Sections show dependency metadata and task IDs in the sidebar.
|
|
124
|
+
|
|
125
|
+
### Generic mode
|
|
126
|
+
|
|
127
|
+
Any markdown with headings gets split into reviewable sections. Non-plan docs still work — you just don't get the plan-specific chrome.
|
|
128
|
+
|
|
129
|
+
## Output targets
|
|
130
|
+
|
|
131
|
+
- **stdout** — print to terminal (default when not prompted otherwise)
|
|
132
|
+
- **clipboard** — copy to clipboard (pbcopy/xclip)
|
|
133
|
+
- **file** — write to `<input>.review.md` or a custom path via `--output-file`
|
|
134
|
+
- **claude** — pipe directly to the Claude Code CLI
|
|
135
|
+
|
|
136
|
+
## Saved sessions
|
|
137
|
+
|
|
138
|
+
Review progress auto-saves as you work. Re-running `plan-review` on the same file prompts to resume. Stored in `~/.plan-review/sessions/`.
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
plan-review plan.md --fresh Skip session resume, start clean
|
|
142
|
+
plan-review sessions List all saved sessions
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Manual cleanup: delete files in `~/.plan-review/sessions/`.
|
|
146
|
+
|
|
147
|
+
## VS Code extension
|
|
148
|
+
|
|
149
|
+
Review plans inside VS Code without leaving the editor. See [packages/vscode-extension/README.md](packages/vscode-extension/README.md) for install and usage.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
2
|
import{Command as rt}from"commander";import{readFileSync as ge,mkdirSync as st,copyFileSync as at}from"node:fs";import{existsSync as ve}from"node:fs";import{homedir as ct}from"node:os";import{dirname as lt,join as N,resolve as ut}from"node:path";import{fileURLToPath as mt}from"node:url";import p from"chalk";function L(e,t="auto"){let n=e.split(`
|
|
4
3
|
`),i=we(n),o=Se(n);return t==="auto"?ye(e)?Ce(e,i,o):M(e,i,o):t==="separator"?E(e,i,o):M(e,i,o)}function we(e){let t=e.find(n=>/^# /.test(n));return t?t.replace(/^# /,"").trim():"Untitled"}function Se(e){let t={};for(let n of e.slice(0,20)){let i=n.match(/^\*\*(\w[\w\s]*?):\*\*\s*(.+)/);i&&(t[i[1].trim()]=i[2].trim())}return t}function ye(e){let t=e.replace(/```[\s\S]*?```/g,""),n=/^## /m.test(t)&&/^### /m.test(t),i=/\*\*Depends On:\*\*/m.test(t)||/\*\*Blocks:\*\*/m.test(t)||/\*\*Verification:\*\*/m.test(t)||/\*\*Related Files:\*\*/m.test(t);return n&&i}function M(e,t,n){let i=be(e);return i.length===0?E(e,t,n):{title:t,metadata:n,mode:"generic",sections:i.map((o,r)=>({id:`section-${r+1}`,heading:o.heading,level:o.level,body:o.body})),comments:[]}}function be(e){let t=e.split(`
|
|
5
4
|
`),n=[],i="",o=0,r=[],s=(e.match(/^## /gm)||[]).length,a=(e.match(/^### /gm)||[]).length,c=s>0?2:a>0?3:0;if(c===0)return[];let u=new RegExp(`^${"#".repeat(c)} (.+)`),l=!1;for(let m of t){if(m.startsWith("```")){l=!l,i&&r.push(m);continue}if(l){i&&r.push(m);continue}let f=m.match(u);f?(i&&n.push({heading:i,level:o,body:r.join(`
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../../core/src/parser.ts", "../../core/src/session.ts", "../../core/src/formatter.ts", "../src/navigator.ts", "../src/renderer.ts", "../src/output.ts", "../src/prompts.ts", "../src/browser-review.ts", "../src/server/server.ts", "../src/server/routes.ts", "../src/server/assets.ts", "../src/transport.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport { readFileSync, mkdirSync, copyFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport chalk from 'chalk';\nimport { parse, formatReview, loadSession, saveSession, clearSession, computeContentHash, listSessions, getSessionDir } from '@plan-review/core';\nimport type { OutputTarget, PlanDocument, ReviewSubmission } from '@plan-review/core';\nimport { navigate } from './navigator.js';\nimport { writeOutput, isClaudeAvailable } from './output.js';\nimport { createRequire } from 'node:module';\nimport { promptOutputTarget, promptYesNo } from './prompts.js';\nimport { runBrowserReview } from './browser-review.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json');\n\nconst program = new Command();\n\nprogram\n .name('plan-review')\n .description('Interactive CLI for reviewing AI-generated markdown plans')\n .version(version)\n .argument('[file]', 'Path to markdown file (omit to read stdin)')\n .option('-o, --output <target>', 'Output target: stdout, clipboard, file, claude')\n .option('--output-file <path>', 'Custom output file path (with --output file)')\n .option('--split-by <strategy>', 'Force split strategy: heading, separator')\n .option('--fresh', 'Skip session resume, start clean review')\n .option('--cli', 'Use the terminal review UI instead of the browser (SSH/CI/headless)')\n .action(async (file: string | undefined, opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean }) => {\n try {\n await run(file, opts);\n } catch (err) {\n if (err instanceof Error) {\n const cancelled = err.message.startsWith('Review cancelled');\n console.error(cancelled ? chalk.yellow(err.message) : chalk.red(`Error: ${err.message}`));\n }\n process.exit(1);\n }\n });\n\nprogram\n .command('install-skill')\n .description('Install Claude Code skill to ~/.claude/skills/plan-review/')\n .action(() => {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const src = join(__dirname, '..', 'skills', 'plan-review', 'SKILL.md');\n if (!existsSync(src)) {\n console.error(chalk.red('Skill file not found in package. Expected at: ' + src));\n process.exit(1);\n }\n const dest = join(homedir(), '.claude', 'skills', 'plan-review');\n mkdirSync(dest, { recursive: true });\n copyFileSync(src, join(dest, 'SKILL.md'));\n console.error(chalk.green(`Skill installed to ${dest}/SKILL.md`));\n console.error(chalk.dim('Claude Code will auto-discover it. Try: \"I want to review this plan\"'));\n });\n\nprogram\n .command('sessions')\n .description('List all saved review sessions')\n .action(() => {\n const sessions = listSessions();\n const dir = getSessionDir();\n if (sessions.length === 0) {\n console.error(chalk.dim(`No saved sessions. (${dir})`));\n process.exit(0);\n }\n console.error(chalk.bold(`Saved review sessions (${dir}):\\n`));\n for (const s of sessions) {\n const age = formatRelativeTime(s.lastModified);\n let status = '';\n if (s.stale === true) status = chalk.yellow(' | plan file changed since last review');\n else if (s.stale === null) status = chalk.red(' | plan file not found');\n console.error(` ${s.planPath}`);\n console.error(chalk.dim(` ${s.commentCount} comment${s.commentCount !== 1 ? 's' : ''} | last modified ${age}${status}\\n`));\n }\n });\n\nprogram.parse();\n\nasync function run(\n file: string | undefined,\n opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean },\n): Promise<void> {\n // Validate explicit output target early, before the review starts\n const validTargets: OutputTarget[] = ['stdout', 'clipboard', 'file', 'claude'];\n if (opts.output !== undefined) {\n const explicitTarget = opts.output as OutputTarget;\n if (!validTargets.includes(explicitTarget)) {\n throw new Error(`Invalid output target: \"${opts.output}\". Use: ${validTargets.join(', ')}`);\n }\n // Fail fast: check claude availability before starting review\n if (explicitTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Will fall back to stdout after review.'));\n }\n }\n\n // Read input \u2014 track whether it came from stdin\n const inputFromStdin = !file && !process.stdin.isTTY;\n const input = readInput(file);\n if (!input.trim()) {\n console.error(chalk.yellow('Empty file, nothing to review.'));\n process.exit(0);\n }\n\n // Parse\n const splitStrategy = opts.splitBy === 'heading' ? 'heading' as const\n : opts.splitBy === 'separator' ? 'separator' as const\n : 'auto' as const;\n const doc = parse(input, splitStrategy);\n\n console.error(chalk.dim(`Detected mode: ${doc.mode} | ${doc.sections.length} sections`));\n\n // Session resume logic\n const absPath = file ? resolvePath(file) : null;\n const contentHash = computeContentHash(input);\n\n let restoredActiveSection: string | null = null;\n if (absPath) {\n if (opts.fresh) {\n clearSession(absPath);\n } else {\n const session = loadSession(absPath, contentHash);\n if (session && session.comments.length > 0) {\n if (!session.stale) {\n console.error(chalk.green(`Resuming review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}).`));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n // Prompt user for stale session\n const answer = await promptYesNo(\n `Plan file changed since last review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}). Resume anyway?`,\n inputFromStdin,\n );\n if (answer) {\n console.error(chalk.yellow('Resuming with stale session.'));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n clearSession(absPath);\n }\n }\n }\n }\n }\n\n // Navigate (interactive review or browser)\n let reviewed: PlanDocument;\n let reviewMeta: Pick<ReviewSubmission, 'verdict' | 'summary'> = { verdict: null, summary: '' };\n if (!opts.cli) {\n const submission = await runBrowserReview({ doc, absPath, contentHash, restoredActiveSection });\n doc.comments = submission.comments;\n reviewMeta = { verdict: submission.verdict, summary: submission.summary };\n reviewed = doc;\n } else {\n const onCommentChange = absPath\n ? () => saveSession(absPath, contentHash, doc.comments, null)\n : undefined;\n reviewed = await navigate(doc, inputFromStdin, onCommentChange);\n }\n\n // Clear session after successful review completion\n if (absPath) clearSession(absPath);\n\n // Determine output target after review is complete\n let outputTarget: OutputTarget;\n if (opts.output !== undefined) {\n outputTarget = opts.output as OutputTarget;\n } else {\n outputTarget = await promptOutputTarget(inputFromStdin);\n // Check claude availability after prompting\n if (outputTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n outputTarget = 'stdout';\n }\n }\n\n // Format and output\n const output = formatReview(reviewed, reviewMeta);\n writeOutput(output, outputTarget, { outputFile: opts.outputFile, inputFile: file });\n}\n\nfunction readInput(file: string | undefined): string {\n if (file) {\n if (!existsSync(file)) {\n throw new Error(`File not found: ${file}`);\n }\n return readFileSync(file, 'utf-8');\n }\n\n // Read from stdin (piped)\n if (!process.stdin.isTTY) {\n return readFileSync('/dev/stdin', 'utf-8');\n }\n\n // No file, no stdin pipe \u2014 show help\n program.help();\n return ''; // unreachable\n}\n\nfunction formatRelativeTime(iso: string): string {\n const ms = Date.now() - new Date(iso).getTime();\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return 'just now';\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n if (days < 7) return `${days}d ago`;\n const weeks = Math.floor(days / 7);\n return `${weeks}w ago`;\n}\n", "import type { PlanDocument, Section, SplitStrategy } from './types.js';\n\nconst MIN_SECTION_CHARS = 5;\n\nexport function parse(input: string, strategy: SplitStrategy = 'auto'): PlanDocument {\n const lines = input.split('\\n');\n const title = extractTitle(lines);\n const metadata = extractMetadata(lines);\n\n if (strategy === 'auto') {\n if (isPlanDocument(input)) {\n return parsePlan(input, title, metadata);\n }\n return parseGeneric(input, title, metadata);\n }\n\n if (strategy === 'separator') {\n return parseBySeparator(input, title, metadata);\n }\n\n return parseGeneric(input, title, metadata);\n}\n\nfunction extractTitle(lines: string[]): string {\n const h1 = lines.find((l) => /^# /.test(l));\n return h1 ? h1.replace(/^# /, '').trim() : 'Untitled';\n}\n\nfunction extractMetadata(lines: string[]): Record<string, string> {\n const meta: Record<string, string> = {};\n for (const line of lines.slice(0, 20)) {\n const match = line.match(/^\\*\\*(\\w[\\w\\s]*?):\\*\\*\\s*(.+)/);\n if (match) {\n meta[match[1].trim()] = match[2].trim();\n }\n }\n return meta;\n}\n\nexport function isPlanDocument(input: string): boolean {\n const stripped = input.replace(/```[\\s\\S]*?```/g, '');\n const hasH2H3Hierarchy =\n /^## /m.test(stripped) && /^### /m.test(stripped);\n const hasPlanFields =\n /\\*\\*Depends On:\\*\\*/m.test(stripped) ||\n /\\*\\*Blocks:\\*\\*/m.test(stripped) ||\n /\\*\\*Verification:\\*\\*/m.test(stripped) ||\n /\\*\\*Related Files:\\*\\*/m.test(stripped);\n\n return hasH2H3Hierarchy && hasPlanFields;\n}\n\nfunction parseGeneric(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const sections = splitByHeadings(input);\n\n if (sections.length === 0) {\n return parseBySeparator(input, title, metadata);\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: sections.map((s, i) => ({\n id: `section-${i + 1}`,\n heading: s.heading,\n level: s.level,\n body: s.body,\n })),\n comments: [],\n };\n}\n\ninterface RawSection {\n heading: string;\n level: number;\n body: string;\n}\n\nfunction splitByHeadings(input: string): RawSection[] {\n const lines = input.split('\\n');\n const sections: RawSection[] = [];\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n\n // Find the most common heading level (## or ###)\n const h2Count = (input.match(/^## /gm) || []).length;\n const h3Count = (input.match(/^### /gm) || []).length;\n const splitLevel = h2Count > 0 ? 2 : h3Count > 0 ? 3 : 0;\n\n if (splitLevel === 0) return [];\n\n const headingRegex = new RegExp(`^${'#'.repeat(splitLevel)} (.+)`);\n let inCodeBlock = false;\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n if (inCodeBlock) {\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n const match = line.match(headingRegex);\n if (match) {\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n currentHeading = match[1].trim();\n currentLevel = splitLevel;\n currentBody = [];\n } else if (currentHeading) {\n currentBody.push(line);\n }\n }\n\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n\n return sections;\n}\n\nfunction parseBySeparator(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const parts = input.split(/\\n---\\n/).filter((p) => {\n return p.trim().length >= MIN_SECTION_CHARS;\n });\n\n if (parts.length <= 1) {\n return {\n title,\n metadata,\n mode: 'generic',\n sections: [\n {\n id: 'section-1',\n heading: title,\n level: 1,\n body: input.trim(),\n },\n ],\n comments: [],\n };\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: parts.map((p, i) => {\n const lines = p.trim().split('\\n');\n const firstLine = lines[0].replace(/^#+\\s*/, '').trim();\n return {\n id: `section-${i + 1}`,\n heading: firstLine || `Section ${i + 1}`,\n level: 2,\n body: p.trim(),\n };\n }),\n comments: [],\n };\n}\n\nfunction parsePlan(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const lines = input.split('\\n');\n const sections: Section[] = [];\n\n let milestoneIndex = 0;\n let taskIndex = 0;\n let currentMilestoneId = '';\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n let inCodeBlock = false;\n\n function flushSection() {\n if (!currentHeading) return;\n\n const body = currentBody.join('\\n').trim();\n\n if (currentLevel === 2) {\n milestoneIndex++;\n taskIndex = 0;\n currentMilestoneId = `milestone-${milestoneIndex}`;\n sections.push({\n id: currentMilestoneId,\n heading: currentHeading,\n level: 2,\n body,\n });\n return;\n }\n\n taskIndex++;\n const id = `${milestoneIndex}.${taskIndex}`;\n sections.push({\n id,\n heading: currentHeading,\n level: 3,\n body,\n parent: currentMilestoneId,\n dependencies: extractDependencies(body),\n relatedFiles: extractRelatedFiles(body),\n verification: extractVerification(body),\n });\n }\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n currentBody.push(line);\n continue;\n }\n\n if (inCodeBlock) {\n currentBody.push(line);\n continue;\n }\n\n const h2Match = line.match(/^## (.+)/);\n const h3Match = line.match(/^### (.+)/);\n\n if (h2Match) {\n flushSection();\n currentHeading = h2Match[1].trim();\n currentLevel = 2;\n currentBody = [];\n } else if (h3Match) {\n flushSection();\n currentHeading = h3Match[1].trim();\n currentLevel = 3;\n currentBody = [];\n } else {\n currentBody.push(line);\n }\n }\n flushSection();\n\n return {\n title,\n metadata,\n mode: 'plan',\n sections,\n comments: [],\n };\n}\n\nfunction extractDependencies(body: string): { dependsOn: string[]; blocks: string[] } {\n const dependsMatch = body.match(/\\*\\*Depends On:\\*\\*\\s*(.+)/);\n const blocksMatch = body.match(/\\*\\*Blocks:\\*\\*\\s*(.+)/);\n\n const parseList = (raw: string): string[] => {\n const trimmed = raw.trim();\n if (trimmed === '(none)' || trimmed === '') return [];\n return trimmed.split(/,\\s*/).map((s) => s.trim());\n };\n\n return {\n dependsOn: dependsMatch ? parseList(dependsMatch[1]) : [],\n blocks: blocksMatch ? parseList(blocksMatch[1]) : [],\n };\n}\n\nfunction extractRelatedFiles(body: string): string[] {\n const files: string[] = [];\n const lines = body.split('\\n');\n let inRelatedFiles = false;\n\n for (const line of lines) {\n if (/\\*\\*Related Files:\\*\\*/.test(line)) {\n inRelatedFiles = true;\n continue;\n }\n if (inRelatedFiles) {\n const fileMatch = line.match(/^- `(.+)`(.*)$/);\n if (fileMatch) {\n const suffix = fileMatch[2].trim();\n files.push(suffix ? `${fileMatch[1]} ${suffix}` : fileMatch[1]);\n } else if (line.trim() === '' || /^\\*\\*/.test(line.trim())) {\n inRelatedFiles = false;\n }\n }\n }\n\n return files;\n}\n\nfunction extractVerification(body: string): string | undefined {\n const match = body.match(/\\*\\*Verification:\\*\\*\\s*`(.+?)`/);\n return match ? match[1] : undefined;\n}\n", "import { createHash } from 'node:crypto';\nimport {\n mkdirSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n readdirSync,\n existsSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { ReviewComment } from './types.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface SessionData {\n version: number;\n planPath: string;\n contentHash: string;\n comments: ReviewComment[];\n activeSection: string | null;\n lastModified: string;\n}\n\nexport interface SessionLoadResult {\n comments: ReviewComment[];\n activeSection: string | null;\n stale: boolean;\n}\n\n// \u2500\u2500 Internal helpers (not exported) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction pathHash(planPath: string): string {\n const abs = resolve(planPath);\n const hash = createHash('sha256').update(abs).digest('hex');\n return hash.slice(0, 16);\n}\n\nfunction sessionFilePath(planPath: string): string {\n return join(getSessionDir(), pathHash(planPath) + '.json');\n}\n\n// \u2500\u2500 Exported functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function getSessionDir(): string {\n const dir = join(homedir(), '.plan-review', 'sessions');\n mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nexport function computeContentHash(content: string): string {\n const hex = createHash('sha256').update(content).digest('hex');\n return `sha256:${hex}`;\n}\n\nexport function saveSession(\n planPath: string,\n contentHash: string,\n comments: ReviewComment[],\n activeSection: string | null,\n): void {\n try {\n const data: SessionData = {\n version: 1,\n planPath,\n contentHash,\n comments,\n activeSection,\n lastModified: new Date().toISOString(),\n };\n const filePath = sessionFilePath(planPath);\n writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n } catch (err) {\n console.warn(`[plan-review] Failed to save session: ${(err as Error).message}`);\n }\n}\n\nexport function loadSession(\n planPath: string,\n currentContentHash: string,\n): SessionLoadResult | null {\n const filePath = sessionFilePath(planPath);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n let data: SessionData;\n try {\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Corrupt session file, removing: ${filePath}`);\n try {\n unlinkSync(filePath);\n } catch {\n // ignore\n }\n return null;\n }\n\n // Restore Date objects in comment timestamps\n const comments: ReviewComment[] = data.comments.map((c) => ({\n ...c,\n timestamp: new Date(c.timestamp),\n }));\n\n return {\n comments,\n activeSection: data.activeSection,\n stale: data.contentHash !== currentContentHash,\n };\n}\n\nexport function clearSession(planPath: string): void {\n const filePath = sessionFilePath(planPath);\n try {\n unlinkSync(filePath);\n } catch {\n // No error if missing\n }\n}\n\nexport function listSessions(): Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n}> {\n const dir = getSessionDir();\n let files: string[];\n try {\n files = readdirSync(dir);\n } catch {\n return [];\n }\n\n const results: Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n }> = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n const filePath = join(dir, file);\n let data: SessionData;\n try {\n const raw = readFileSync(filePath, 'utf-8');\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Skipping corrupt session file: ${filePath}`);\n continue;\n }\n\n let stale: boolean | null;\n if (!existsSync(data.planPath)) {\n stale = null;\n } else {\n try {\n const currentContent = readFileSync(data.planPath, 'utf-8');\n const currentHash = computeContentHash(currentContent);\n stale = currentHash !== data.contentHash;\n } catch {\n stale = null;\n }\n }\n\n results.push({\n planPath: data.planPath,\n commentCount: data.comments.length,\n lastModified: data.lastModified,\n stale,\n });\n }\n\n return results;\n}\n", "import type { PlanDocument, ReviewComment, ReviewVerdict } from './types.js';\n\nfunction escapeMarkdown(text: string): string {\n return text.replace(/([\\\\*_`~\\[\\]#>|])/g, '\\\\$1');\n}\n\nfunction sortComments(comments: ReviewComment[]): ReviewComment[] {\n return [...comments].sort((a, b) => {\n const aLine = a.anchor?.startLine ?? Infinity;\n const bLine = b.anchor?.startLine ?? Infinity;\n return aLine - bLine;\n });\n}\n\nfunction verdictLabel(verdict: ReviewVerdict): string {\n return verdict === 'approved' ? 'Approved' : 'Comment';\n}\n\nexport interface FormatReviewOptions {\n verdict: ReviewVerdict;\n summary: string;\n}\n\nexport function formatReview(doc: PlanDocument, opts: FormatReviewOptions): string {\n const commentedSectionIds = new Set(doc.comments.map((c) => c.sectionId));\n const reviewableSections = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const commentedSections = reviewableSections.filter((s) => commentedSectionIds.has(s.id));\n\n const parts: string[] = [];\n\n parts.push(`# Plan Review: ${doc.title}`);\n parts.push('');\n parts.push('## Review Summary');\n parts.push(`- **Verdict:** ${verdictLabel(opts.verdict)}`);\n parts.push(`- **Sections reviewed:** ${commentedSections.length}/${reviewableSections.length}`);\n parts.push(`- **Comments:** ${doc.comments.length}`);\n const skippedCount = reviewableSections.length - commentedSections.length;\n parts.push(\n `- **Skipped:** ${skippedCount} section${skippedCount === 1 ? '' : 's'} without comments`,\n );\n\n if (opts.summary.trim() !== '') {\n parts.push('');\n parts.push('## Overall Comments');\n parts.push('');\n parts.push(escapeMarkdown(opts.summary));\n }\n\n if (commentedSections.length > 0) {\n parts.push('');\n parts.push('---');\n }\n\n for (const section of commentedSections) {\n const sectionComments = sortComments(\n doc.comments.filter((c) => c.sectionId === section.id),\n );\n\n parts.push('');\n parts.push(`## Section ${section.id}: ${section.heading}`);\n parts.push('');\n\n if (doc.mode === 'plan' && section.dependencies) {\n const deps = section.dependencies;\n if (deps.dependsOn.length > 0) {\n parts.push(`Depends on: ${deps.dependsOn.join(', ')}`);\n }\n if (deps.blocks.length > 0) {\n parts.push(`Blocks: ${deps.blocks.join(', ')}`);\n }\n parts.push('');\n }\n\n for (const comment of sectionComments) {\n if (comment.anchor) {\n parts.push('### Reviewer Comment');\n parts.push('');\n for (const line of comment.anchor.lineTexts) {\n parts.push(`> ${line}`);\n }\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n } else {\n parts.push('### Reviewer Comment (entire section)');\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n }\n parts.push('');\n parts.push('---');\n }\n }\n\n return parts.join('\\n');\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { PlanDocument, ReviewComment, Section } from '@plan-review/core';\nimport { renderSection, renderToc } from './renderer.js';\n\nexport async function navigate(doc: PlanDocument, inputFromStdin: boolean = false, onCommentChange?: () => void): Promise<PlanDocument> {\n // When input was read from stdin, stdin is exhausted.\n // Open /dev/tty directly for interactive prompts.\n const ttyInput = inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n\n const rl = readline.createInterface({\n input: ttyInput,\n output: process.stderr,\n });\n\n const ask = (prompt: string): Promise<string> =>\n new Promise((resolve) => {\n rl.question(prompt, (answer) => resolve(answer.trim()));\n });\n\n const reviewableSections = getReviewableSections(doc);\n\n let running = true;\n\n while (running) {\n console.error(renderToc(doc));\n const input = await ask(\n chalk.cyan('> Enter section (e.g. 1.1), \\'all\\' for linear review, or \\'done\\' to finish: '),\n );\n\n if (input === 'done' || input === 'q') {\n running = false;\n } else if (input === 'all') {\n await linearReview(doc, reviewableSections, ask, onCommentChange);\n } else {\n const section = findSection(doc, input);\n if (section) {\n const startIdx = reviewableSections.indexOf(section);\n await linearReview(doc, reviewableSections.slice(startIdx), ask, onCommentChange);\n } else {\n console.error(chalk.red(`Section \"${input}\" not found. Try again.`));\n }\n }\n }\n\n rl.close();\n printSummary(doc);\n return doc;\n}\n\nasync function linearReview(\n doc: PlanDocument,\n sections: Section[],\n ask: (prompt: string) => Promise<string>,\n onCommentChange?: () => void,\n): Promise<void> {\n for (let i = 0; i < sections.length; i++) {\n const section = sections[i];\n console.error(renderSection(section));\n\n const input = await ask(\n chalk.cyan('> Comment (enter to skip, \\'toc\\' for menu, \\'back\\' for previous): '),\n );\n\n if (input === 'toc') {\n return;\n } else if (input === 'back') {\n i -= (i > 0) ? 2 : 1; // -2 to go back (loop increments), -1 to re-show current\n continue;\n } else if (input !== '') {\n doc.comments.push({\n sectionId: section.id,\n text: input,\n timestamp: new Date(),\n });\n onCommentChange?.();\n }\n }\n}\n\nexport function findSection(doc: PlanDocument, input: string): Section | undefined {\n // Try exact ID match first\n const byId = doc.sections.find((s) => s.id === input);\n if (byId) return byId;\n\n // Try numeric index for generic mode\n const num = parseInt(input, 10);\n if (!isNaN(num)) {\n const reviewable = getReviewableSections(doc);\n if (num >= 1 && num <= reviewable.length) {\n return reviewable[num - 1];\n }\n }\n\n return undefined;\n}\n\nexport function getReviewableSections(doc: PlanDocument): Section[] {\n return doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n}\n\nexport function printSummary(doc: PlanDocument): void {\n const reviewable = getReviewableSections(doc);\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n console.error('');\n console.error(chalk.bold('Review Summary'));\n console.error(` Sections: ${reviewable.length}`);\n console.error(` Commented: ${chalk.green(String(commentedIds.size))}`);\n console.error(` Skipped: ${chalk.dim(String(reviewable.length - commentedIds.size))}`);\n console.error(` Total comments: ${doc.comments.length}`);\n console.error('');\n}\n", "import { marked } from 'marked';\nimport { markedTerminal } from 'marked-terminal';\nimport chalk from 'chalk';\nimport type { Section, PlanDocument } from '@plan-review/core';\n\nmarked.use(markedTerminal());\n\nexport function renderSection(section: Section): string {\n const parts: string[] = [];\n\n if (section.level === 3 && section.dependencies) {\n parts.push(renderMetadataHeader(section));\n parts.push('');\n }\n\n const heading = `${'#'.repeat(section.level)} ${section.heading}`;\n const body = section.body || '';\n const markdown = `${heading}\\n\\n${body}`;\n parts.push(marked.parse(markdown) as string);\n\n return parts.join('\\n');\n}\n\nfunction renderMetadataHeader(section: Section): string {\n const deps = section.dependencies!;\n const dependsOn = deps.dependsOn.length > 0 ? deps.dependsOn.join(', ') : '(none)';\n const blocks = deps.blocks.length > 0 ? deps.blocks.join(', ') : '(none)';\n\n const lines: string[] = [\n `Task ${section.id}: ${section.heading}`,\n `\u2190 Depends on: ${dependsOn}`,\n `\u2192 Blocks: ${blocks}`,\n ];\n\n if (section.relatedFiles && section.relatedFiles.length > 0) {\n const fileList =\n section.relatedFiles.length <= 2\n ? section.relatedFiles.join(', ')\n : `${section.relatedFiles[0]} (+${section.relatedFiles.length - 1} more)`;\n lines.push(`Files: ${fileList}`);\n }\n\n if (section.verification) {\n lines.push(`Verify: ${section.verification}`);\n }\n\n const maxLen = Math.max(...lines.map((l) => l.length));\n const width = Math.min(maxLen + 4, process.stdout.columns || 80);\n const innerWidth = width - 2;\n\n const top = chalk.dim(`\u250C${'\u2500'.repeat(innerWidth)}\u2510`);\n const bottom = chalk.dim(`\u2514${'\u2500'.repeat(innerWidth)}\u2518`);\n const content = lines.map(\n (l) => chalk.dim('\u2502') + ' ' + chalk.cyan(l.slice(0, innerWidth - 2).padEnd(innerWidth - 2)) + ' ' + chalk.dim('\u2502'),\n );\n\n return [top, ...content, bottom].join('\\n');\n}\n\nexport function renderToc(doc: PlanDocument): string {\n const parts: string[] = [];\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n parts.push('');\n parts.push(chalk.bold.underline(doc.title));\n parts.push('');\n\n if (doc.mode === 'plan') {\n for (const section of doc.sections) {\n if (section.level === 2) {\n parts.push(chalk.bold.yellow(` ${section.heading}`));\n } else if (section.level === 3) {\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(section.id)} ${section.heading}`);\n }\n }\n } else {\n const reviewable = doc.sections.filter((s) => s.level >= 2);\n for (let i = 0; i < reviewable.length; i++) {\n const section = reviewable[i];\n const num = String(i + 1).padStart(2);\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(num)} ${section.heading}`);\n }\n }\n\n const commentedCount = commentedIds.size;\n const reviewable = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const remaining = reviewable.length - commentedCount;\n\n parts.push('');\n parts.push(\n ` ${chalk.green(`${commentedCount} section${commentedCount !== 1 ? 's' : ''} commented`)}` +\n ` ${chalk.dim(`${remaining} remaining`)}`,\n );\n parts.push('');\n\n return parts.join('\\n');\n}\n", "import { execSync, spawn } from 'node:child_process';\nimport { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\nexport function writeOutput(\n content: string,\n target: OutputTarget,\n options: { outputFile?: string; inputFile?: string } = {},\n): void {\n switch (target) {\n case 'stdout':\n process.stdout.write(content + '\\n');\n break;\n case 'clipboard':\n writeToClipboard(content);\n break;\n case 'file':\n writeToFile(content, options.outputFile, options.inputFile);\n break;\n case 'claude':\n sendToClaude(content);\n break;\n }\n}\n\nfunction writeToClipboard(content: string): void {\n const cmd = getClipboardCommand(process.platform);\n if (!cmd) {\n console.error(chalk.yellow('Clipboard not supported on this platform. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n try {\n execSync(cmd, { input: content, stdio: ['pipe', 'ignore', 'ignore'] });\n console.error(chalk.green('Review copied to clipboard.'));\n } catch {\n console.error(chalk.yellow('Failed to copy to clipboard. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction writeToFile(content: string, outputFile?: string, inputFile?: string): void {\n const filePath = outputFile\n ? resolve(outputFile)\n : inputFile\n ? resolve(inputFile.replace(/\\.md$/, '.review.md'))\n : resolve('review.md');\n\n try {\n writeFileSync(filePath, content, 'utf-8');\n console.error(chalk.green(`Review written to ${filePath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(`Failed to write file: ${msg}`));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction sendToClaude(content: string): void {\n if (!isClaudeAvailable()) {\n console.error(\n chalk.red('Claude CLI not found in PATH.'),\n );\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n const child = spawn('claude', [], {\n stdio: ['pipe', 'inherit', 'inherit'],\n });\n child.stdin.write(content);\n child.stdin.end();\n child.on('error', (err) => {\n console.error(chalk.yellow(`Failed to pipe to claude: ${err.message}. Falling back to stdout.`));\n process.stdout.write(content + '\\n');\n });\n}\n\nexport function getClipboardCommand(platform: string): string | null {\n switch (platform) {\n case 'darwin':\n return 'pbcopy';\n case 'linux':\n return 'xclip -selection clipboard';\n case 'win32':\n return 'clip';\n default:\n return null;\n }\n}\n\nexport function isClaudeAvailable(): boolean {\n try {\n execSync('which claude', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\n// When stdin is the piped plan, readline can't reuse it for prompts \u2014 open\n// /dev/tty directly so the terminal still answers keystrokes.\nasync function ttyInputStream(inputFromStdin: boolean): Promise<NodeJS.ReadableStream> {\n return inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n}\n\nexport async function promptOutputTarget(inputFromStdin: boolean): Promise<OutputTarget> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(\n chalk.cyan('> Output: (s)tdout, (c)lipboard, (f)ile, cl(a)ude? '),\n (a) => resolve(a.trim().toLowerCase()),\n );\n });\n rl.close();\n\n switch (answer) {\n case 's': case 'stdout': return 'stdout';\n case 'c': case 'clipboard': return 'clipboard';\n case 'f': case 'file': return 'file';\n case 'a': case 'claude': return 'claude';\n default: return 'stdout';\n }\n}\n\nexport async function promptYesNo(message: string, inputFromStdin: boolean): Promise<boolean> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(chalk.yellow(`${message} (y/n) `), (a) => resolve(a.trim().toLowerCase()));\n });\n rl.close();\n\n return answer === 'y' || answer === 'yes';\n}\n", "import { spawnSync } from 'node:child_process';\nimport { dirname as dirnamePath } from 'node:path';\nimport type { PlanDocument } from '@plan-review/core';\nimport { saveSession } from '@plan-review/core';\nimport { HttpTransport } from './transport.js';\nimport type { ReviewSubmission } from './transport.js';\n\nexport interface BrowserReviewOptions {\n doc: PlanDocument;\n absPath: string | null;\n contentHash: string;\n restoredActiveSection: string | null;\n}\n\nconst IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes \u2014 overall ceiling\nconst HEARTBEAT_TIMEOUT_MS = 30 * 1000; // 30s without a heartbeat while visible = browser gone\n\n// Boot an HttpTransport, open the URL in the user's default browser, and\n// resolve with the reviewed comments when the browser posts them. Rejects if\n// the user cancels, closes the tab, or the overall idle ceiling fires.\nexport async function runBrowserReview(\n { doc, absPath, contentHash, restoredActiveSection }: BrowserReviewOptions,\n): Promise<ReviewSubmission> {\n const transport = new HttpTransport();\n transport.sendDocument(doc);\n transport.setInitialActiveSection(restoredActiveSection);\n // Plan-file directory anchors relative image paths via /_assets/<rel>.\n transport.setAssetBaseDir(absPath ? dirnamePath(absPath) : null);\n\n if (absPath) {\n transport.onSessionSave((comments, activeSection) => {\n saveSession(absPath, contentHash, comments, activeSection);\n });\n }\n\n const reviewPromise = new Promise<ReviewSubmission>((resolve, reject) => {\n const idleTimer = setTimeout(\n () => reject(new Error('Browser review timed out after 30 minutes of inactivity')),\n IDLE_TIMEOUT_MS,\n );\n let heartbeatTimer: NodeJS.Timeout | null = null;\n const armHeartbeat = (): void => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = setTimeout(() => {\n clearTimeout(idleTimer);\n reject(new Error('Review cancelled: browser closed (heartbeat lost)'));\n }, HEARTBEAT_TIMEOUT_MS);\n };\n const clearAll = (): void => {\n clearTimeout(idleTimer);\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n };\n transport.onHeartbeat(armHeartbeat);\n transport.onPause(() => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n });\n transport.onCancel(() => {\n clearAll();\n reject(new Error('Review cancelled: browser closed'));\n });\n transport.onReviewSubmit((submission) => {\n clearAll();\n resolve(submission);\n });\n });\n\n const { url } = await transport.start(0);\n process.stderr.write(`Review server running at ${url}\\n`);\n\n try {\n const openCmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'start'\n : 'xdg-open';\n spawnSync(openCmd, [url], { stdio: 'ignore' });\n } catch {\n process.stderr.write(`Open ${url} in your browser\\n`);\n }\n\n try {\n return await reviewPromise;\n } finally {\n await transport.stop();\n }\n}\n", "import { createServer, type Server } from 'node:http';\nimport { createRouteHandler, type RouteContext } from './routes.js';\n\nexport function createReviewServer(ctx: RouteContext): Server {\n return createServer(createRouteHandler(ctx));\n}\n\nexport function startServer(server: Server, port: number): Promise<{ url: string }> {\n return new Promise((resolve, reject) => {\n server.on('error', reject);\n server.listen(port, () => {\n const addr = server.address();\n const actualPort = typeof addr === 'object' && addr ? addr.port : port;\n resolve({ url: `http://localhost:${actualPort}` });\n });\n });\n}\n\nexport function stopServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n // Force-close keep-alive sockets so close() doesn't hang on an idle browser tab.\n server.closeAllConnections();\n });\n}\n", "import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { readFile } from 'node:fs';\nimport { join, normalize, resolve as resolvePath, sep, extname } from 'node:path';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\n\nconst MAX_BODY_SIZE = 1024 * 1024; // 1MB\n\nconst MIME_BY_EXT: Record<string, string> = {\n '.gif': 'image/gif',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.ico': 'image/x-icon',\n};\n\nexport interface RouteContext {\n getDocument: () => PlanDocument;\n getInitialActiveSection?: () => string | null;\n getAssetBaseDir?: () => string | null;\n onSubmit: (submission: ReviewSubmission) => void;\n getAssetHtml: () => string;\n onSessionSave?: (comments: ReviewComment[], activeSection: string | null) => void;\n onHeartbeat?: () => void;\n onPause?: () => void;\n onCancel?: () => void;\n}\n\nfunction validateComment(obj: unknown): obj is ReviewComment {\n if (typeof obj !== 'object' || obj === null) return false;\n const c = obj as Record<string, unknown>;\n return typeof c.sectionId === 'string' && typeof c.text === 'string';\n}\n\nfunction validateVerdict(value: unknown): value is ReviewSubmission['verdict'] {\n return value === 'approved' || value === null;\n}\n\nexport function createRouteHandler(ctx: RouteContext): (req: IncomingMessage, res: ServerResponse) => void {\n return (req, res) => {\n const { method, url } = req;\n\n if (method === 'GET' && url === '/') {\n const html = ctx.getAssetHtml();\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n\n if (method === 'GET' && url === '/api/doc') {\n const doc = ctx.getDocument();\n const initialState = { activeSection: ctx.getInitialActiveSection?.() ?? null };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ document: doc, initialState }));\n return;\n }\n\n if (method === 'POST' && url === '/api/review') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n const verdict = parsed.verdict;\n const summary = parsed.summary;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n if (!validateVerdict(verdict)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'verdict must be \"approved\" or null' }));\n return;\n }\n if (typeof summary !== 'string') {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'summary must be a string' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n ctx.onSubmit({ comments: comments as ReviewComment[], verdict, summary });\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'PUT' && url === '/api/session') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n const activeSection = typeof parsed.activeSection === 'string' ? parsed.activeSection : null;\n ctx.onSessionSave?.(comments as ReviewComment[], activeSection);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'POST' && url === '/api/heartbeat') {\n ctx.onHeartbeat?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/pause') {\n ctx.onPause?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/cancel') {\n ctx.onCancel?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Static asset proxy: /_assets/<rel-path> serves files from the plan\n // file's directory. Only image extensions are allowed; path traversal\n // (e.g. ../etc/passwd) is rejected. Inline plans set baseDir to null and\n // get a 404 \u2014 there is no on-disk anchor to resolve against.\n if (method === 'GET' && url && url.startsWith('/_assets/')) {\n const baseDir = ctx.getAssetBaseDir?.();\n if (!baseDir) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('No asset base directory');\n return;\n }\n let rel: string;\n try {\n rel = decodeURIComponent(url.slice('/_assets/'.length).split('?')[0].split('#')[0]);\n } catch {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Bad request');\n return;\n }\n const ext = extname(rel).toLowerCase();\n if (!MIME_BY_EXT[ext]) {\n res.writeHead(415, { 'Content-Type': 'text/plain' });\n res.end('Unsupported media type');\n return;\n }\n const normalized = normalize(rel);\n const resolvedBase = resolvePath(baseDir);\n const resolvedFile = resolvePath(join(resolvedBase, normalized));\n if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {\n res.writeHead(403, { 'Content-Type': 'text/plain' });\n res.end('Forbidden');\n return;\n }\n readFile(resolvedFile, (err, buf) => {\n if (err) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n res.writeHead(200, {\n 'Content-Type': MIME_BY_EXT[ext],\n 'Content-Length': buf.length,\n 'Cache-Control': 'no-store',\n });\n res.end(buf);\n });\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n };\n}\n", "import { readFileSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// When running from source (vitest), look for pre-built dist/browser/index.html\n// When running from dist, the sibling ../browser/ path is used\nfunction resolveHtmlPath(): string {\n const siblingPath = join(__dirname, '..', 'browser', 'index.html');\n if (existsSync(siblingPath)) return siblingPath;\n\n // Fallback: walk up to project root and look in dist/browser/\n const projectRoot = join(__dirname, '..', '..');\n const distPath = join(projectRoot, 'dist', 'browser', 'index.html');\n if (existsSync(distPath)) return distPath;\n\n throw new Error(`Browser HTML not found. Run 'npm run build' first.\\nLooked in:\\n ${siblingPath}\\n ${distPath}`);\n}\n\nlet cached: string | null = null;\n\nexport function getAssetHtml(): string {\n if (!cached) {\n cached = readFileSync(resolveHtmlPath(), 'utf-8');\n }\n return cached;\n}\n", "import type { Server } from 'node:http';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\nimport { createReviewServer, startServer, stopServer } from './server/server.js';\nimport { getAssetHtml } from './server/assets.js';\n\nexport type { ReviewSubmission, ReviewVerdict } from '@plan-review/core';\n\nexport interface Transport {\n sendDocument(doc: PlanDocument): void;\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void;\n start(port: number): Promise<{ url: string }>;\n stop(): Promise<void>;\n}\n\nexport class HttpTransport implements Transport {\n private doc: PlanDocument | null = null;\n private initialActiveSection: string | null = null;\n private assetBaseDir: string | null = null;\n private submitHandler: ((submission: ReviewSubmission) => void) | null = null;\n private sessionSaveHandler: ((comments: ReviewComment[], activeSection: string | null) => void) | null = null;\n private heartbeatHandler: (() => void) | null = null;\n private pauseHandler: (() => void) | null = null;\n private cancelHandler: (() => void) | null = null;\n private server: Server | null = null;\n\n sendDocument(doc: PlanDocument): void {\n this.doc = doc;\n }\n\n setInitialActiveSection(section: string | null): void {\n this.initialActiveSection = section;\n }\n\n // Plan-file directory used to serve relative images via /_assets/<rel>.\n // Null for inline plans where there's no on-disk anchor.\n setAssetBaseDir(dir: string | null): void {\n this.assetBaseDir = dir;\n }\n\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void {\n this.submitHandler = handler;\n }\n\n onSessionSave(handler: (comments: ReviewComment[], activeSection: string | null) => void): void {\n this.sessionSaveHandler = handler;\n }\n\n onHeartbeat(handler: () => void): void {\n this.heartbeatHandler = handler;\n }\n\n onPause(handler: () => void): void {\n this.pauseHandler = handler;\n }\n\n onCancel(handler: () => void): void {\n this.cancelHandler = handler;\n }\n\n async start(port: number): Promise<{ url: string }> {\n if (!this.doc) throw new Error('No document set');\n\n this.server = createReviewServer({\n getDocument: () => this.doc!,\n getInitialActiveSection: () => this.initialActiveSection,\n getAssetBaseDir: () => this.assetBaseDir,\n onSubmit: (submission) => this.submitHandler?.(submission),\n getAssetHtml: () => getAssetHtml(),\n onSessionSave: (comments, activeSection) => this.sessionSaveHandler?.(comments, activeSection),\n onHeartbeat: () => this.heartbeatHandler?.(),\n onPause: () => this.pauseHandler?.(),\n onCancel: () => this.cancelHandler?.(),\n });\n\n return startServer(this.server, port);\n }\n\n async stop(): Promise<void> {\n if (this.server && this.server.listening) {\n await stopServer(this.server);\n this.server = null;\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { Command } from 'commander';\nimport { readFileSync, mkdirSync, copyFileSync } from 'node:fs';\nimport { existsSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport chalk from 'chalk';\nimport { parse, formatReview, loadSession, saveSession, clearSession, computeContentHash, listSessions, getSessionDir } from '@plan-review/core';\nimport type { OutputTarget, PlanDocument, ReviewSubmission } from '@plan-review/core';\nimport { navigate } from './navigator.js';\nimport { writeOutput, isClaudeAvailable } from './output.js';\nimport { createRequire } from 'node:module';\nimport { promptOutputTarget, promptYesNo } from './prompts.js';\nimport { runBrowserReview } from './browser-review.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json');\n\nconst program = new Command();\n\nprogram\n .name('plan-review')\n .description('Interactive CLI for reviewing AI-generated markdown plans')\n .version(version)\n .argument('[file]', 'Path to markdown file (omit to read stdin)')\n .option('-o, --output <target>', 'Output target: stdout, clipboard, file, claude')\n .option('--output-file <path>', 'Custom output file path (with --output file)')\n .option('--split-by <strategy>', 'Force split strategy: heading, separator')\n .option('--fresh', 'Skip session resume, start clean review')\n .option('--cli', 'Use the terminal review UI instead of the browser (SSH/CI/headless)')\n .action(async (file: string | undefined, opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean }) => {\n try {\n await run(file, opts);\n } catch (err) {\n if (err instanceof Error) {\n const cancelled = err.message.startsWith('Review cancelled');\n console.error(cancelled ? chalk.yellow(err.message) : chalk.red(`Error: ${err.message}`));\n }\n process.exit(1);\n }\n });\n\nprogram\n .command('install-skill')\n .description('Install Claude Code skill to ~/.claude/skills/plan-review/')\n .action(() => {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const src = join(__dirname, '..', 'skills', 'plan-review', 'SKILL.md');\n if (!existsSync(src)) {\n console.error(chalk.red('Skill file not found in package. Expected at: ' + src));\n process.exit(1);\n }\n const dest = join(homedir(), '.claude', 'skills', 'plan-review');\n mkdirSync(dest, { recursive: true });\n copyFileSync(src, join(dest, 'SKILL.md'));\n console.error(chalk.green(`Skill installed to ${dest}/SKILL.md`));\n console.error(chalk.dim('Claude Code will auto-discover it. Try: \"I want to review this plan\"'));\n });\n\nprogram\n .command('sessions')\n .description('List all saved review sessions')\n .action(() => {\n const sessions = listSessions();\n const dir = getSessionDir();\n if (sessions.length === 0) {\n console.error(chalk.dim(`No saved sessions. (${dir})`));\n process.exit(0);\n }\n console.error(chalk.bold(`Saved review sessions (${dir}):\\n`));\n for (const s of sessions) {\n const age = formatRelativeTime(s.lastModified);\n let status = '';\n if (s.stale === true) status = chalk.yellow(' | plan file changed since last review');\n else if (s.stale === null) status = chalk.red(' | plan file not found');\n console.error(` ${s.planPath}`);\n console.error(chalk.dim(` ${s.commentCount} comment${s.commentCount !== 1 ? 's' : ''} | last modified ${age}${status}\\n`));\n }\n });\n\nprogram.parse();\n\nasync function run(\n file: string | undefined,\n opts: { output?: string; outputFile?: string; splitBy?: string; cli?: boolean; fresh?: boolean },\n): Promise<void> {\n // Validate explicit output target early, before the review starts\n const validTargets: OutputTarget[] = ['stdout', 'clipboard', 'file', 'claude'];\n if (opts.output !== undefined) {\n const explicitTarget = opts.output as OutputTarget;\n if (!validTargets.includes(explicitTarget)) {\n throw new Error(`Invalid output target: \"${opts.output}\". Use: ${validTargets.join(', ')}`);\n }\n // Fail fast: check claude availability before starting review\n if (explicitTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Will fall back to stdout after review.'));\n }\n }\n\n // Read input \u2014 track whether it came from stdin\n const inputFromStdin = !file && !process.stdin.isTTY;\n const input = readInput(file);\n if (!input.trim()) {\n console.error(chalk.yellow('Empty file, nothing to review.'));\n process.exit(0);\n }\n\n // Parse\n const splitStrategy = opts.splitBy === 'heading' ? 'heading' as const\n : opts.splitBy === 'separator' ? 'separator' as const\n : 'auto' as const;\n const doc = parse(input, splitStrategy);\n\n console.error(chalk.dim(`Detected mode: ${doc.mode} | ${doc.sections.length} sections`));\n\n // Session resume logic\n const absPath = file ? resolvePath(file) : null;\n const contentHash = computeContentHash(input);\n\n let restoredActiveSection: string | null = null;\n if (absPath) {\n if (opts.fresh) {\n clearSession(absPath);\n } else {\n const session = loadSession(absPath, contentHash);\n if (session && session.comments.length > 0) {\n if (!session.stale) {\n console.error(chalk.green(`Resuming review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}).`));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n // Prompt user for stale session\n const answer = await promptYesNo(\n `Plan file changed since last review (${session.comments.length} comment${session.comments.length !== 1 ? 's' : ''}). Resume anyway?`,\n inputFromStdin,\n );\n if (answer) {\n console.error(chalk.yellow('Resuming with stale session.'));\n doc.comments = session.comments;\n restoredActiveSection = session.activeSection;\n } else {\n clearSession(absPath);\n }\n }\n }\n }\n }\n\n // Navigate (interactive review or browser)\n let reviewed: PlanDocument;\n let reviewMeta: Pick<ReviewSubmission, 'verdict' | 'summary'> = { verdict: null, summary: '' };\n if (!opts.cli) {\n const submission = await runBrowserReview({ doc, absPath, contentHash, restoredActiveSection });\n doc.comments = submission.comments;\n reviewMeta = { verdict: submission.verdict, summary: submission.summary };\n reviewed = doc;\n } else {\n const onCommentChange = absPath\n ? () => saveSession(absPath, contentHash, doc.comments, null)\n : undefined;\n reviewed = await navigate(doc, inputFromStdin, onCommentChange);\n }\n\n // Clear session after successful review completion\n if (absPath) clearSession(absPath);\n\n // Determine output target after review is complete\n let outputTarget: OutputTarget;\n if (opts.output !== undefined) {\n outputTarget = opts.output as OutputTarget;\n } else {\n outputTarget = await promptOutputTarget(inputFromStdin);\n // Check claude availability after prompting\n if (outputTarget === 'claude' && !isClaudeAvailable()) {\n console.error(chalk.red('Claude CLI not found in PATH.'));\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n outputTarget = 'stdout';\n }\n }\n\n // Format and output\n const output = formatReview(reviewed, reviewMeta);\n writeOutput(output, outputTarget, { outputFile: opts.outputFile, inputFile: file });\n}\n\nfunction readInput(file: string | undefined): string {\n if (file) {\n if (!existsSync(file)) {\n throw new Error(`File not found: ${file}`);\n }\n return readFileSync(file, 'utf-8');\n }\n\n // Read from stdin (piped)\n if (!process.stdin.isTTY) {\n return readFileSync('/dev/stdin', 'utf-8');\n }\n\n // No file, no stdin pipe \u2014 show help\n program.help();\n return ''; // unreachable\n}\n\nfunction formatRelativeTime(iso: string): string {\n const ms = Date.now() - new Date(iso).getTime();\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return 'just now';\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n if (days < 7) return `${days}d ago`;\n const weeks = Math.floor(days / 7);\n return `${weeks}w ago`;\n}\n", "import type { PlanDocument, Section, SplitStrategy } from './types.js';\n\nconst MIN_SECTION_CHARS = 5;\n\nexport function parse(input: string, strategy: SplitStrategy = 'auto'): PlanDocument {\n const lines = input.split('\\n');\n const title = extractTitle(lines);\n const metadata = extractMetadata(lines);\n\n if (strategy === 'auto') {\n if (isPlanDocument(input)) {\n return parsePlan(input, title, metadata);\n }\n return parseGeneric(input, title, metadata);\n }\n\n if (strategy === 'separator') {\n return parseBySeparator(input, title, metadata);\n }\n\n return parseGeneric(input, title, metadata);\n}\n\nfunction extractTitle(lines: string[]): string {\n const h1 = lines.find((l) => /^# /.test(l));\n return h1 ? h1.replace(/^# /, '').trim() : 'Untitled';\n}\n\nfunction extractMetadata(lines: string[]): Record<string, string> {\n const meta: Record<string, string> = {};\n for (const line of lines.slice(0, 20)) {\n const match = line.match(/^\\*\\*(\\w[\\w\\s]*?):\\*\\*\\s*(.+)/);\n if (match) {\n meta[match[1].trim()] = match[2].trim();\n }\n }\n return meta;\n}\n\nexport function isPlanDocument(input: string): boolean {\n const stripped = input.replace(/```[\\s\\S]*?```/g, '');\n const hasH2H3Hierarchy =\n /^## /m.test(stripped) && /^### /m.test(stripped);\n const hasPlanFields =\n /\\*\\*Depends On:\\*\\*/m.test(stripped) ||\n /\\*\\*Blocks:\\*\\*/m.test(stripped) ||\n /\\*\\*Verification:\\*\\*/m.test(stripped) ||\n /\\*\\*Related Files:\\*\\*/m.test(stripped);\n\n return hasH2H3Hierarchy && hasPlanFields;\n}\n\nfunction parseGeneric(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const sections = splitByHeadings(input);\n\n if (sections.length === 0) {\n return parseBySeparator(input, title, metadata);\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: sections.map((s, i) => ({\n id: `section-${i + 1}`,\n heading: s.heading,\n level: s.level,\n body: s.body,\n })),\n comments: [],\n };\n}\n\ninterface RawSection {\n heading: string;\n level: number;\n body: string;\n}\n\nfunction splitByHeadings(input: string): RawSection[] {\n const lines = input.split('\\n');\n const sections: RawSection[] = [];\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n\n // Find the most common heading level (## or ###)\n const h2Count = (input.match(/^## /gm) || []).length;\n const h3Count = (input.match(/^### /gm) || []).length;\n const splitLevel = h2Count > 0 ? 2 : h3Count > 0 ? 3 : 0;\n\n if (splitLevel === 0) return [];\n\n const headingRegex = new RegExp(`^${'#'.repeat(splitLevel)} (.+)`);\n let inCodeBlock = false;\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n if (inCodeBlock) {\n if (currentHeading) {\n currentBody.push(line);\n }\n continue;\n }\n\n const match = line.match(headingRegex);\n if (match) {\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n currentHeading = match[1].trim();\n currentLevel = splitLevel;\n currentBody = [];\n } else if (currentHeading) {\n currentBody.push(line);\n }\n }\n\n if (currentHeading) {\n sections.push({\n heading: currentHeading,\n level: currentLevel,\n body: currentBody.join('\\n').trim(),\n });\n }\n\n return sections;\n}\n\nfunction parseBySeparator(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const parts = input.split(/\\n---\\n/).filter((p) => {\n return p.trim().length >= MIN_SECTION_CHARS;\n });\n\n if (parts.length <= 1) {\n return {\n title,\n metadata,\n mode: 'generic',\n sections: [\n {\n id: 'section-1',\n heading: title,\n level: 1,\n body: input.trim(),\n },\n ],\n comments: [],\n };\n }\n\n return {\n title,\n metadata,\n mode: 'generic',\n sections: parts.map((p, i) => {\n const lines = p.trim().split('\\n');\n const firstLine = lines[0].replace(/^#+\\s*/, '').trim();\n return {\n id: `section-${i + 1}`,\n heading: firstLine || `Section ${i + 1}`,\n level: 2,\n body: p.trim(),\n };\n }),\n comments: [],\n };\n}\n\nfunction parsePlan(\n input: string,\n title: string,\n metadata: Record<string, string>,\n): PlanDocument {\n const lines = input.split('\\n');\n const sections: Section[] = [];\n\n let milestoneIndex = 0;\n let taskIndex = 0;\n let currentMilestoneId = '';\n let currentHeading = '';\n let currentLevel = 0;\n let currentBody: string[] = [];\n let inCodeBlock = false;\n\n function flushSection() {\n if (!currentHeading) return;\n\n const body = currentBody.join('\\n').trim();\n\n if (currentLevel === 2) {\n milestoneIndex++;\n taskIndex = 0;\n currentMilestoneId = `milestone-${milestoneIndex}`;\n sections.push({\n id: currentMilestoneId,\n heading: currentHeading,\n level: 2,\n body,\n });\n return;\n }\n\n taskIndex++;\n const id = `${milestoneIndex}.${taskIndex}`;\n sections.push({\n id,\n heading: currentHeading,\n level: 3,\n body,\n parent: currentMilestoneId,\n dependencies: extractDependencies(body),\n relatedFiles: extractRelatedFiles(body),\n verification: extractVerification(body),\n });\n }\n\n for (const line of lines) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n currentBody.push(line);\n continue;\n }\n\n if (inCodeBlock) {\n currentBody.push(line);\n continue;\n }\n\n const h2Match = line.match(/^## (.+)/);\n const h3Match = line.match(/^### (.+)/);\n\n if (h2Match) {\n flushSection();\n currentHeading = h2Match[1].trim();\n currentLevel = 2;\n currentBody = [];\n } else if (h3Match) {\n flushSection();\n currentHeading = h3Match[1].trim();\n currentLevel = 3;\n currentBody = [];\n } else {\n currentBody.push(line);\n }\n }\n flushSection();\n\n return {\n title,\n metadata,\n mode: 'plan',\n sections,\n comments: [],\n };\n}\n\nfunction extractDependencies(body: string): { dependsOn: string[]; blocks: string[] } {\n const dependsMatch = body.match(/\\*\\*Depends On:\\*\\*\\s*(.+)/);\n const blocksMatch = body.match(/\\*\\*Blocks:\\*\\*\\s*(.+)/);\n\n const parseList = (raw: string): string[] => {\n const trimmed = raw.trim();\n if (trimmed === '(none)' || trimmed === '') return [];\n return trimmed.split(/,\\s*/).map((s) => s.trim());\n };\n\n return {\n dependsOn: dependsMatch ? parseList(dependsMatch[1]) : [],\n blocks: blocksMatch ? parseList(blocksMatch[1]) : [],\n };\n}\n\nfunction extractRelatedFiles(body: string): string[] {\n const files: string[] = [];\n const lines = body.split('\\n');\n let inRelatedFiles = false;\n\n for (const line of lines) {\n if (/\\*\\*Related Files:\\*\\*/.test(line)) {\n inRelatedFiles = true;\n continue;\n }\n if (inRelatedFiles) {\n const fileMatch = line.match(/^- `(.+)`(.*)$/);\n if (fileMatch) {\n const suffix = fileMatch[2].trim();\n files.push(suffix ? `${fileMatch[1]} ${suffix}` : fileMatch[1]);\n } else if (line.trim() === '' || /^\\*\\*/.test(line.trim())) {\n inRelatedFiles = false;\n }\n }\n }\n\n return files;\n}\n\nfunction extractVerification(body: string): string | undefined {\n const match = body.match(/\\*\\*Verification:\\*\\*\\s*`(.+?)`/);\n return match ? match[1] : undefined;\n}\n", "import { createHash } from 'node:crypto';\nimport {\n mkdirSync,\n readFileSync,\n writeFileSync,\n unlinkSync,\n readdirSync,\n existsSync,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport type { ReviewComment } from './types.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface SessionData {\n version: number;\n planPath: string;\n contentHash: string;\n comments: ReviewComment[];\n activeSection: string | null;\n lastModified: string;\n}\n\nexport interface SessionLoadResult {\n comments: ReviewComment[];\n activeSection: string | null;\n stale: boolean;\n}\n\n// \u2500\u2500 Internal helpers (not exported) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction pathHash(planPath: string): string {\n const abs = resolve(planPath);\n const hash = createHash('sha256').update(abs).digest('hex');\n return hash.slice(0, 16);\n}\n\nfunction sessionFilePath(planPath: string): string {\n return join(getSessionDir(), pathHash(planPath) + '.json');\n}\n\n// \u2500\u2500 Exported functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function getSessionDir(): string {\n const dir = join(homedir(), '.plan-review', 'sessions');\n mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nexport function computeContentHash(content: string): string {\n const hex = createHash('sha256').update(content).digest('hex');\n return `sha256:${hex}`;\n}\n\nexport function saveSession(\n planPath: string,\n contentHash: string,\n comments: ReviewComment[],\n activeSection: string | null,\n): void {\n try {\n const data: SessionData = {\n version: 1,\n planPath,\n contentHash,\n comments,\n activeSection,\n lastModified: new Date().toISOString(),\n };\n const filePath = sessionFilePath(planPath);\n writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n } catch (err) {\n console.warn(`[plan-review] Failed to save session: ${(err as Error).message}`);\n }\n}\n\nexport function loadSession(\n planPath: string,\n currentContentHash: string,\n): SessionLoadResult | null {\n const filePath = sessionFilePath(planPath);\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n let data: SessionData;\n try {\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Corrupt session file, removing: ${filePath}`);\n try {\n unlinkSync(filePath);\n } catch {\n // ignore\n }\n return null;\n }\n\n // Restore Date objects in comment timestamps\n const comments: ReviewComment[] = data.comments.map((c) => ({\n ...c,\n timestamp: new Date(c.timestamp),\n }));\n\n return {\n comments,\n activeSection: data.activeSection,\n stale: data.contentHash !== currentContentHash,\n };\n}\n\nexport function clearSession(planPath: string): void {\n const filePath = sessionFilePath(planPath);\n try {\n unlinkSync(filePath);\n } catch {\n // No error if missing\n }\n}\n\nexport function listSessions(): Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n}> {\n const dir = getSessionDir();\n let files: string[];\n try {\n files = readdirSync(dir);\n } catch {\n return [];\n }\n\n const results: Array<{\n planPath: string;\n commentCount: number;\n lastModified: string;\n stale: boolean | null;\n }> = [];\n\n for (const file of files) {\n if (!file.endsWith('.json')) continue;\n\n const filePath = join(dir, file);\n let data: SessionData;\n try {\n const raw = readFileSync(filePath, 'utf-8');\n data = JSON.parse(raw) as SessionData;\n } catch {\n console.warn(`[plan-review] Skipping corrupt session file: ${filePath}`);\n continue;\n }\n\n let stale: boolean | null;\n if (!existsSync(data.planPath)) {\n stale = null;\n } else {\n try {\n const currentContent = readFileSync(data.planPath, 'utf-8');\n const currentHash = computeContentHash(currentContent);\n stale = currentHash !== data.contentHash;\n } catch {\n stale = null;\n }\n }\n\n results.push({\n planPath: data.planPath,\n commentCount: data.comments.length,\n lastModified: data.lastModified,\n stale,\n });\n }\n\n return results;\n}\n", "import type { PlanDocument, ReviewComment, ReviewVerdict } from './types.js';\n\nfunction escapeMarkdown(text: string): string {\n return text.replace(/([\\\\*_`~\\[\\]#>|])/g, '\\\\$1');\n}\n\nfunction sortComments(comments: ReviewComment[]): ReviewComment[] {\n return [...comments].sort((a, b) => {\n const aLine = a.anchor?.startLine ?? Infinity;\n const bLine = b.anchor?.startLine ?? Infinity;\n return aLine - bLine;\n });\n}\n\nfunction verdictLabel(verdict: ReviewVerdict): string {\n return verdict === 'approved' ? 'Approved' : 'Comment';\n}\n\nexport interface FormatReviewOptions {\n verdict: ReviewVerdict;\n summary: string;\n}\n\nexport function formatReview(doc: PlanDocument, opts: FormatReviewOptions): string {\n const commentedSectionIds = new Set(doc.comments.map((c) => c.sectionId));\n const reviewableSections = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const commentedSections = reviewableSections.filter((s) => commentedSectionIds.has(s.id));\n\n const parts: string[] = [];\n\n parts.push(`# Plan Review: ${doc.title}`);\n parts.push('');\n parts.push('## Review Summary');\n parts.push(`- **Verdict:** ${verdictLabel(opts.verdict)}`);\n parts.push(`- **Sections reviewed:** ${commentedSections.length}/${reviewableSections.length}`);\n parts.push(`- **Comments:** ${doc.comments.length}`);\n const skippedCount = reviewableSections.length - commentedSections.length;\n parts.push(\n `- **Skipped:** ${skippedCount} section${skippedCount === 1 ? '' : 's'} without comments`,\n );\n\n if (opts.summary.trim() !== '') {\n parts.push('');\n parts.push('## Overall Comments');\n parts.push('');\n parts.push(escapeMarkdown(opts.summary));\n }\n\n if (commentedSections.length > 0) {\n parts.push('');\n parts.push('---');\n }\n\n for (const section of commentedSections) {\n const sectionComments = sortComments(\n doc.comments.filter((c) => c.sectionId === section.id),\n );\n\n parts.push('');\n parts.push(`## Section ${section.id}: ${section.heading}`);\n parts.push('');\n\n if (doc.mode === 'plan' && section.dependencies) {\n const deps = section.dependencies;\n if (deps.dependsOn.length > 0) {\n parts.push(`Depends on: ${deps.dependsOn.join(', ')}`);\n }\n if (deps.blocks.length > 0) {\n parts.push(`Blocks: ${deps.blocks.join(', ')}`);\n }\n parts.push('');\n }\n\n for (const comment of sectionComments) {\n if (comment.anchor) {\n parts.push('### Reviewer Comment');\n parts.push('');\n for (const line of comment.anchor.lineTexts) {\n parts.push(`> ${line}`);\n }\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n } else {\n parts.push('### Reviewer Comment (entire section)');\n parts.push('');\n parts.push(escapeMarkdown(comment.text));\n }\n parts.push('');\n parts.push('---');\n }\n }\n\n return parts.join('\\n');\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { PlanDocument, ReviewComment, Section } from '@plan-review/core';\nimport { renderSection, renderToc } from './renderer.js';\n\nexport async function navigate(doc: PlanDocument, inputFromStdin: boolean = false, onCommentChange?: () => void): Promise<PlanDocument> {\n // When input was read from stdin, stdin is exhausted.\n // Open /dev/tty directly for interactive prompts.\n const ttyInput = inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n\n const rl = readline.createInterface({\n input: ttyInput,\n output: process.stderr,\n });\n\n const ask = (prompt: string): Promise<string> =>\n new Promise((resolve) => {\n rl.question(prompt, (answer) => resolve(answer.trim()));\n });\n\n const reviewableSections = getReviewableSections(doc);\n\n let running = true;\n\n while (running) {\n console.error(renderToc(doc));\n const input = await ask(\n chalk.cyan('> Enter section (e.g. 1.1), \\'all\\' for linear review, or \\'done\\' to finish: '),\n );\n\n if (input === 'done' || input === 'q') {\n running = false;\n } else if (input === 'all') {\n await linearReview(doc, reviewableSections, ask, onCommentChange);\n } else {\n const section = findSection(doc, input);\n if (section) {\n const startIdx = reviewableSections.indexOf(section);\n await linearReview(doc, reviewableSections.slice(startIdx), ask, onCommentChange);\n } else {\n console.error(chalk.red(`Section \"${input}\" not found. Try again.`));\n }\n }\n }\n\n rl.close();\n printSummary(doc);\n return doc;\n}\n\nasync function linearReview(\n doc: PlanDocument,\n sections: Section[],\n ask: (prompt: string) => Promise<string>,\n onCommentChange?: () => void,\n): Promise<void> {\n for (let i = 0; i < sections.length; i++) {\n const section = sections[i];\n console.error(renderSection(section));\n\n const input = await ask(\n chalk.cyan('> Comment (enter to skip, \\'toc\\' for menu, \\'back\\' for previous): '),\n );\n\n if (input === 'toc') {\n return;\n } else if (input === 'back') {\n i -= (i > 0) ? 2 : 1; // -2 to go back (loop increments), -1 to re-show current\n continue;\n } else if (input !== '') {\n doc.comments.push({\n sectionId: section.id,\n text: input,\n timestamp: new Date(),\n });\n onCommentChange?.();\n }\n }\n}\n\nexport function findSection(doc: PlanDocument, input: string): Section | undefined {\n // Try exact ID match first\n const byId = doc.sections.find((s) => s.id === input);\n if (byId) return byId;\n\n // Try numeric index for generic mode\n const num = parseInt(input, 10);\n if (!isNaN(num)) {\n const reviewable = getReviewableSections(doc);\n if (num >= 1 && num <= reviewable.length) {\n return reviewable[num - 1];\n }\n }\n\n return undefined;\n}\n\nexport function getReviewableSections(doc: PlanDocument): Section[] {\n return doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n}\n\nexport function printSummary(doc: PlanDocument): void {\n const reviewable = getReviewableSections(doc);\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n console.error('');\n console.error(chalk.bold('Review Summary'));\n console.error(` Sections: ${reviewable.length}`);\n console.error(` Commented: ${chalk.green(String(commentedIds.size))}`);\n console.error(` Skipped: ${chalk.dim(String(reviewable.length - commentedIds.size))}`);\n console.error(` Total comments: ${doc.comments.length}`);\n console.error('');\n}\n", "import { marked } from 'marked';\nimport { markedTerminal } from 'marked-terminal';\nimport chalk from 'chalk';\nimport type { Section, PlanDocument } from '@plan-review/core';\n\nmarked.use(markedTerminal());\n\nexport function renderSection(section: Section): string {\n const parts: string[] = [];\n\n if (section.level === 3 && section.dependencies) {\n parts.push(renderMetadataHeader(section));\n parts.push('');\n }\n\n const heading = `${'#'.repeat(section.level)} ${section.heading}`;\n const body = section.body || '';\n const markdown = `${heading}\\n\\n${body}`;\n parts.push(marked.parse(markdown) as string);\n\n return parts.join('\\n');\n}\n\nfunction renderMetadataHeader(section: Section): string {\n const deps = section.dependencies!;\n const dependsOn = deps.dependsOn.length > 0 ? deps.dependsOn.join(', ') : '(none)';\n const blocks = deps.blocks.length > 0 ? deps.blocks.join(', ') : '(none)';\n\n const lines: string[] = [\n `Task ${section.id}: ${section.heading}`,\n `\u2190 Depends on: ${dependsOn}`,\n `\u2192 Blocks: ${blocks}`,\n ];\n\n if (section.relatedFiles && section.relatedFiles.length > 0) {\n const fileList =\n section.relatedFiles.length <= 2\n ? section.relatedFiles.join(', ')\n : `${section.relatedFiles[0]} (+${section.relatedFiles.length - 1} more)`;\n lines.push(`Files: ${fileList}`);\n }\n\n if (section.verification) {\n lines.push(`Verify: ${section.verification}`);\n }\n\n const maxLen = Math.max(...lines.map((l) => l.length));\n const width = Math.min(maxLen + 4, process.stdout.columns || 80);\n const innerWidth = width - 2;\n\n const top = chalk.dim(`\u250C${'\u2500'.repeat(innerWidth)}\u2510`);\n const bottom = chalk.dim(`\u2514${'\u2500'.repeat(innerWidth)}\u2518`);\n const content = lines.map(\n (l) => chalk.dim('\u2502') + ' ' + chalk.cyan(l.slice(0, innerWidth - 2).padEnd(innerWidth - 2)) + ' ' + chalk.dim('\u2502'),\n );\n\n return [top, ...content, bottom].join('\\n');\n}\n\nexport function renderToc(doc: PlanDocument): string {\n const parts: string[] = [];\n const commentedIds = new Set(doc.comments.map((c) => c.sectionId));\n\n parts.push('');\n parts.push(chalk.bold.underline(doc.title));\n parts.push('');\n\n if (doc.mode === 'plan') {\n for (const section of doc.sections) {\n if (section.level === 2) {\n parts.push(chalk.bold.yellow(` ${section.heading}`));\n } else if (section.level === 3) {\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(section.id)} ${section.heading}`);\n }\n }\n } else {\n const reviewable = doc.sections.filter((s) => s.level >= 2);\n for (let i = 0; i < reviewable.length; i++) {\n const section = reviewable[i];\n const num = String(i + 1).padStart(2);\n const marker = commentedIds.has(section.id) ? chalk.green('\u2713') : ' ';\n parts.push(` ${marker} ${chalk.dim(num)} ${section.heading}`);\n }\n }\n\n const commentedCount = commentedIds.size;\n const reviewable = doc.sections.filter((s) =>\n doc.mode === 'plan' ? s.level === 3 : s.level >= 2,\n );\n const remaining = reviewable.length - commentedCount;\n\n parts.push('');\n parts.push(\n ` ${chalk.green(`${commentedCount} section${commentedCount !== 1 ? 's' : ''} commented`)}` +\n ` ${chalk.dim(`${remaining} remaining`)}`,\n );\n parts.push('');\n\n return parts.join('\\n');\n}\n", "import { execSync, spawn } from 'node:child_process';\nimport { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\nexport function writeOutput(\n content: string,\n target: OutputTarget,\n options: { outputFile?: string; inputFile?: string } = {},\n): void {\n switch (target) {\n case 'stdout':\n process.stdout.write(content + '\\n');\n break;\n case 'clipboard':\n writeToClipboard(content);\n break;\n case 'file':\n writeToFile(content, options.outputFile, options.inputFile);\n break;\n case 'claude':\n sendToClaude(content);\n break;\n }\n}\n\nfunction writeToClipboard(content: string): void {\n const cmd = getClipboardCommand(process.platform);\n if (!cmd) {\n console.error(chalk.yellow('Clipboard not supported on this platform. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n try {\n execSync(cmd, { input: content, stdio: ['pipe', 'ignore', 'ignore'] });\n console.error(chalk.green('Review copied to clipboard.'));\n } catch {\n console.error(chalk.yellow('Failed to copy to clipboard. Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction writeToFile(content: string, outputFile?: string, inputFile?: string): void {\n const filePath = outputFile\n ? resolve(outputFile)\n : inputFile\n ? resolve(inputFile.replace(/\\.md$/, '.review.md'))\n : resolve('review.md');\n\n try {\n writeFileSync(filePath, content, 'utf-8');\n console.error(chalk.green(`Review written to ${filePath}`));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(chalk.red(`Failed to write file: ${msg}`));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n }\n}\n\nfunction sendToClaude(content: string): void {\n if (!isClaudeAvailable()) {\n console.error(\n chalk.red('Claude CLI not found in PATH.'),\n );\n console.error(chalk.dim('Install: https://docs.anthropic.com/en/docs/claude-code'));\n console.error(chalk.yellow('Falling back to stdout.'));\n process.stdout.write(content + '\\n');\n return;\n }\n\n const child = spawn('claude', [], {\n stdio: ['pipe', 'inherit', 'inherit'],\n });\n child.stdin.write(content);\n child.stdin.end();\n child.on('error', (err) => {\n console.error(chalk.yellow(`Failed to pipe to claude: ${err.message}. Falling back to stdout.`));\n process.stdout.write(content + '\\n');\n });\n}\n\nexport function getClipboardCommand(platform: string): string | null {\n switch (platform) {\n case 'darwin':\n return 'pbcopy';\n case 'linux':\n return 'xclip -selection clipboard';\n case 'win32':\n return 'clip';\n default:\n return null;\n }\n}\n\nexport function isClaudeAvailable(): boolean {\n try {\n execSync('which claude', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n", "import * as readline from 'node:readline';\nimport chalk from 'chalk';\nimport type { OutputTarget } from '@plan-review/core';\n\n// When stdin is the piped plan, readline can't reuse it for prompts \u2014 open\n// /dev/tty directly so the terminal still answers keystrokes.\nasync function ttyInputStream(inputFromStdin: boolean): Promise<NodeJS.ReadableStream> {\n return inputFromStdin\n ? (await import('node:fs')).createReadStream('/dev/tty')\n : process.stdin;\n}\n\nexport async function promptOutputTarget(inputFromStdin: boolean): Promise<OutputTarget> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(\n chalk.cyan('> Output: (s)tdout, (c)lipboard, (f)ile, cl(a)ude? '),\n (a) => resolve(a.trim().toLowerCase()),\n );\n });\n rl.close();\n\n switch (answer) {\n case 's': case 'stdout': return 'stdout';\n case 'c': case 'clipboard': return 'clipboard';\n case 'f': case 'file': return 'file';\n case 'a': case 'claude': return 'claude';\n default: return 'stdout';\n }\n}\n\nexport async function promptYesNo(message: string, inputFromStdin: boolean): Promise<boolean> {\n const rl = readline.createInterface({\n input: await ttyInputStream(inputFromStdin),\n output: process.stderr,\n });\n\n const answer = await new Promise<string>((resolve) => {\n rl.question(chalk.yellow(`${message} (y/n) `), (a) => resolve(a.trim().toLowerCase()));\n });\n rl.close();\n\n return answer === 'y' || answer === 'yes';\n}\n", "import { spawnSync } from 'node:child_process';\nimport { dirname as dirnamePath } from 'node:path';\nimport type { PlanDocument } from '@plan-review/core';\nimport { saveSession } from '@plan-review/core';\nimport { HttpTransport } from './transport.js';\nimport type { ReviewSubmission } from './transport.js';\n\nexport interface BrowserReviewOptions {\n doc: PlanDocument;\n absPath: string | null;\n contentHash: string;\n restoredActiveSection: string | null;\n}\n\nconst IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes \u2014 overall ceiling\nconst HEARTBEAT_TIMEOUT_MS = 30 * 1000; // 30s without a heartbeat while visible = browser gone\n\n// Boot an HttpTransport, open the URL in the user's default browser, and\n// resolve with the reviewed comments when the browser posts them. Rejects if\n// the user cancels, closes the tab, or the overall idle ceiling fires.\nexport async function runBrowserReview(\n { doc, absPath, contentHash, restoredActiveSection }: BrowserReviewOptions,\n): Promise<ReviewSubmission> {\n const transport = new HttpTransport();\n transport.sendDocument(doc);\n transport.setInitialActiveSection(restoredActiveSection);\n // Plan-file directory anchors relative image paths via /_assets/<rel>.\n transport.setAssetBaseDir(absPath ? dirnamePath(absPath) : null);\n\n if (absPath) {\n transport.onSessionSave((comments, activeSection) => {\n saveSession(absPath, contentHash, comments, activeSection);\n });\n }\n\n const reviewPromise = new Promise<ReviewSubmission>((resolve, reject) => {\n const idleTimer = setTimeout(\n () => reject(new Error('Browser review timed out after 30 minutes of inactivity')),\n IDLE_TIMEOUT_MS,\n );\n let heartbeatTimer: NodeJS.Timeout | null = null;\n const armHeartbeat = (): void => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = setTimeout(() => {\n clearTimeout(idleTimer);\n reject(new Error('Review cancelled: browser closed (heartbeat lost)'));\n }, HEARTBEAT_TIMEOUT_MS);\n };\n const clearAll = (): void => {\n clearTimeout(idleTimer);\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n };\n transport.onHeartbeat(armHeartbeat);\n transport.onPause(() => {\n if (heartbeatTimer) clearTimeout(heartbeatTimer);\n heartbeatTimer = null;\n });\n transport.onCancel(() => {\n clearAll();\n reject(new Error('Review cancelled: browser closed'));\n });\n transport.onReviewSubmit((submission) => {\n clearAll();\n resolve(submission);\n });\n });\n\n const { url } = await transport.start(0);\n process.stderr.write(`Review server running at ${url}\\n`);\n\n try {\n const openCmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'start'\n : 'xdg-open';\n spawnSync(openCmd, [url], { stdio: 'ignore' });\n } catch {\n process.stderr.write(`Open ${url} in your browser\\n`);\n }\n\n try {\n return await reviewPromise;\n } finally {\n await transport.stop();\n }\n}\n", "import { createServer, type Server } from 'node:http';\nimport { createRouteHandler, type RouteContext } from './routes.js';\n\nexport function createReviewServer(ctx: RouteContext): Server {\n return createServer(createRouteHandler(ctx));\n}\n\nexport function startServer(server: Server, port: number): Promise<{ url: string }> {\n return new Promise((resolve, reject) => {\n server.on('error', reject);\n server.listen(port, () => {\n const addr = server.address();\n const actualPort = typeof addr === 'object' && addr ? addr.port : port;\n resolve({ url: `http://localhost:${actualPort}` });\n });\n });\n}\n\nexport function stopServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => (err ? reject(err) : resolve()));\n // Force-close keep-alive sockets so close() doesn't hang on an idle browser tab.\n server.closeAllConnections();\n });\n}\n", "import type { IncomingMessage, ServerResponse } from 'node:http';\nimport { readFile } from 'node:fs';\nimport { join, normalize, resolve as resolvePath, sep, extname } from 'node:path';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\n\nconst MAX_BODY_SIZE = 1024 * 1024; // 1MB\n\nconst MIME_BY_EXT: Record<string, string> = {\n '.gif': 'image/gif',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.ico': 'image/x-icon',\n};\n\nexport interface RouteContext {\n getDocument: () => PlanDocument;\n getInitialActiveSection?: () => string | null;\n getAssetBaseDir?: () => string | null;\n onSubmit: (submission: ReviewSubmission) => void;\n getAssetHtml: () => string;\n onSessionSave?: (comments: ReviewComment[], activeSection: string | null) => void;\n onHeartbeat?: () => void;\n onPause?: () => void;\n onCancel?: () => void;\n}\n\nfunction validateComment(obj: unknown): obj is ReviewComment {\n if (typeof obj !== 'object' || obj === null) return false;\n const c = obj as Record<string, unknown>;\n return typeof c.sectionId === 'string' && typeof c.text === 'string';\n}\n\nfunction validateVerdict(value: unknown): value is ReviewSubmission['verdict'] {\n return value === 'approved' || value === null;\n}\n\nexport function createRouteHandler(ctx: RouteContext): (req: IncomingMessage, res: ServerResponse) => void {\n return (req, res) => {\n const { method, url } = req;\n\n if (method === 'GET' && url === '/') {\n const html = ctx.getAssetHtml();\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(html);\n return;\n }\n\n if (method === 'GET' && url === '/api/doc') {\n const doc = ctx.getDocument();\n const initialState = { activeSection: ctx.getInitialActiveSection?.() ?? null };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ document: doc, initialState }));\n return;\n }\n\n if (method === 'POST' && url === '/api/review') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n const verdict = parsed.verdict;\n const summary = parsed.summary;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n if (!validateVerdict(verdict)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'verdict must be \"approved\" or null' }));\n return;\n }\n if (typeof summary !== 'string') {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'summary must be a string' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n ctx.onSubmit({ comments: comments as ReviewComment[], verdict, summary });\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'PUT' && url === '/api/session') {\n let body = '';\n let size = 0;\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_SIZE) {\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Request body too large' }));\n req.destroy();\n return;\n }\n body += chunk.toString();\n });\n req.on('end', () => {\n if (size > MAX_BODY_SIZE) return;\n try {\n const parsed = JSON.parse(body);\n const comments = parsed.comments;\n if (!Array.isArray(comments)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'comments must be an array' }));\n return;\n }\n for (const c of comments) {\n if (!validateComment(c)) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Each comment must have sectionId (string) and text (string)' }));\n return;\n }\n }\n const activeSection = typeof parsed.activeSection === 'string' ? parsed.activeSection : null;\n ctx.onSessionSave?.(comments as ReviewComment[], activeSection);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n if (method === 'POST' && url === '/api/heartbeat') {\n ctx.onHeartbeat?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/pause') {\n ctx.onPause?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'POST' && url === '/api/cancel') {\n ctx.onCancel?.();\n res.writeHead(204);\n res.end();\n return;\n }\n\n // Static asset proxy: /_assets/<rel-path> serves files from the plan\n // file's directory. Only image extensions are allowed; path traversal\n // (e.g. ../etc/passwd) is rejected. Inline plans set baseDir to null and\n // get a 404 \u2014 there is no on-disk anchor to resolve against.\n if (method === 'GET' && url && url.startsWith('/_assets/')) {\n const baseDir = ctx.getAssetBaseDir?.();\n if (!baseDir) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('No asset base directory');\n return;\n }\n let rel: string;\n try {\n rel = decodeURIComponent(url.slice('/_assets/'.length).split('?')[0].split('#')[0]);\n } catch {\n res.writeHead(400, { 'Content-Type': 'text/plain' });\n res.end('Bad request');\n return;\n }\n const ext = extname(rel).toLowerCase();\n if (!MIME_BY_EXT[ext]) {\n res.writeHead(415, { 'Content-Type': 'text/plain' });\n res.end('Unsupported media type');\n return;\n }\n const normalized = normalize(rel);\n const resolvedBase = resolvePath(baseDir);\n const resolvedFile = resolvePath(join(resolvedBase, normalized));\n if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {\n res.writeHead(403, { 'Content-Type': 'text/plain' });\n res.end('Forbidden');\n return;\n }\n readFile(resolvedFile, (err, buf) => {\n if (err) {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n return;\n }\n res.writeHead(200, {\n 'Content-Type': MIME_BY_EXT[ext],\n 'Content-Length': buf.length,\n 'Cache-Control': 'no-store',\n });\n res.end(buf);\n });\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not Found');\n };\n}\n", "import { readFileSync, existsSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// When running from source (vitest), look for pre-built dist/browser/index.html\n// When running from dist, the sibling ../browser/ path is used\nfunction resolveHtmlPath(): string {\n const siblingPath = join(__dirname, '..', 'browser', 'index.html');\n if (existsSync(siblingPath)) return siblingPath;\n\n // Fallback: walk up to project root and look in dist/browser/\n const projectRoot = join(__dirname, '..', '..');\n const distPath = join(projectRoot, 'dist', 'browser', 'index.html');\n if (existsSync(distPath)) return distPath;\n\n throw new Error(`Browser HTML not found. Run 'npm run build' first.\\nLooked in:\\n ${siblingPath}\\n ${distPath}`);\n}\n\nlet cached: string | null = null;\n\nexport function getAssetHtml(): string {\n if (!cached) {\n cached = readFileSync(resolveHtmlPath(), 'utf-8');\n }\n return cached;\n}\n", "import type { Server } from 'node:http';\nimport type { PlanDocument, ReviewComment, ReviewSubmission } from '@plan-review/core';\nimport { createReviewServer, startServer, stopServer } from './server/server.js';\nimport { getAssetHtml } from './server/assets.js';\n\nexport type { ReviewSubmission, ReviewVerdict } from '@plan-review/core';\n\nexport interface Transport {\n sendDocument(doc: PlanDocument): void;\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void;\n start(port: number): Promise<{ url: string }>;\n stop(): Promise<void>;\n}\n\nexport class HttpTransport implements Transport {\n private doc: PlanDocument | null = null;\n private initialActiveSection: string | null = null;\n private assetBaseDir: string | null = null;\n private submitHandler: ((submission: ReviewSubmission) => void) | null = null;\n private sessionSaveHandler: ((comments: ReviewComment[], activeSection: string | null) => void) | null = null;\n private heartbeatHandler: (() => void) | null = null;\n private pauseHandler: (() => void) | null = null;\n private cancelHandler: (() => void) | null = null;\n private server: Server | null = null;\n\n sendDocument(doc: PlanDocument): void {\n this.doc = doc;\n }\n\n setInitialActiveSection(section: string | null): void {\n this.initialActiveSection = section;\n }\n\n // Plan-file directory used to serve relative images via /_assets/<rel>.\n // Null for inline plans where there's no on-disk anchor.\n setAssetBaseDir(dir: string | null): void {\n this.assetBaseDir = dir;\n }\n\n onReviewSubmit(handler: (submission: ReviewSubmission) => void): void {\n this.submitHandler = handler;\n }\n\n onSessionSave(handler: (comments: ReviewComment[], activeSection: string | null) => void): void {\n this.sessionSaveHandler = handler;\n }\n\n onHeartbeat(handler: () => void): void {\n this.heartbeatHandler = handler;\n }\n\n onPause(handler: () => void): void {\n this.pauseHandler = handler;\n }\n\n onCancel(handler: () => void): void {\n this.cancelHandler = handler;\n }\n\n async start(port: number): Promise<{ url: string }> {\n if (!this.doc) throw new Error('No document set');\n\n this.server = createReviewServer({\n getDocument: () => this.doc!,\n getInitialActiveSection: () => this.initialActiveSection,\n getAssetBaseDir: () => this.assetBaseDir,\n onSubmit: (submission) => this.submitHandler?.(submission),\n getAssetHtml: () => getAssetHtml(),\n onSessionSave: (comments, activeSection) => this.sessionSaveHandler?.(comments, activeSection),\n onHeartbeat: () => this.heartbeatHandler?.(),\n onPause: () => this.pauseHandler?.(),\n onCancel: () => this.cancelHandler?.(),\n });\n\n return startServer(this.server, port);\n }\n\n async stop(): Promise<void> {\n if (this.server && this.server.listening) {\n await stopServer(this.server);\n this.server = null;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["Command", "readFileSync", "mkdirSync", "copyFileSync", "existsSync", "homedir", "dirname", "join", "resolvePath", "fileURLToPath", "chalk", "parse", "input", "strategy", "lines", "title", "extractTitle", "metadata", "extractMetadata", "isPlanDocument", "parsePlan", "parseGeneric", "parseBySeparator", "h1", "l", "meta", "line", "match", "stripped", "hasH2H3Hierarchy", "hasPlanFields", "sections", "splitByHeadings", "s", "i", "currentHeading", "currentLevel", "currentBody", "h2Count", "h3Count", "splitLevel", "headingRegex", "inCodeBlock", "parts", "p", "firstLine", "milestoneIndex", "taskIndex", "currentMilestoneId", "flushSection", "body", "id", "extractDependencies", "extractRelatedFiles", "extractVerification", "h2Match", "h3Match", "dependsMatch", "blocksMatch", "parseList", "raw", "trimmed", "files", "inRelatedFiles", "fileMatch", "suffix", "createHash", "mkdirSync", "readFileSync", "writeFileSync", "unlinkSync", "readdirSync", "existsSync", "join", "resolve", "homedir", "pathHash", "planPath", "abs", "sessionFilePath", "getSessionDir", "dir", "computeContentHash", "content", "saveSession", "contentHash", "comments", "activeSection", "data", "filePath", "err", "loadSession", "currentContentHash", "raw", "c", "clearSession", "listSessions", "files", "results", "file", "stale", "currentContent", "escapeMarkdown", "text", "sortComments", "comments", "a", "b", "aLine", "bLine", "verdictLabel", "verdict", "formatReview", "doc", "opts", "commentedSectionIds", "c", "reviewableSections", "s", "commentedSections", "parts", "skippedCount", "section", "sectionComments", "deps", "comment", "line", "readline", "chalk", "marked", "markedTerminal", "chalk", "renderSection", "section", "parts", "renderMetadataHeader", "heading", "body", "markdown", "deps", "dependsOn", "blocks", "lines", "fileList", "maxLen", "l", "innerWidth", "top", "bottom", "content", "renderToc", "doc", "commentedIds", "c", "marker", "reviewable", "s", "i", "num", "commentedCount", "remaining", "navigate", "doc", "inputFromStdin", "onCommentChange", "ttyInput", "rl", "ask", "prompt", "resolve", "answer", "reviewableSections", "getReviewableSections", "running", "renderToc", "input", "chalk", "linearReview", "section", "findSection", "startIdx", "printSummary", "sections", "i", "renderSection", "byId", "s", "num", "reviewable", "commentedIds", "c", "execSync", "spawn", "writeFileSync", "resolve", "chalk", "writeOutput", "content", "target", "options", "writeToClipboard", "writeToFile", "sendToClaude", "cmd", "getClipboardCommand", "outputFile", "inputFile", "filePath", "err", "msg", "isClaudeAvailable", "child", "platform", "createRequire", "readline", "chalk", "ttyInputStream", "inputFromStdin", "promptOutputTarget", "rl", "answer", "resolve", "a", "promptYesNo", "message", "spawnSync", "dirnamePath", "createServer", "readFile", "join", "normalize", "resolvePath", "sep", "extname", "MAX_BODY_SIZE", "MIME_BY_EXT", "validateComment", "obj", "c", "validateVerdict", "value", "createRouteHandler", "ctx", "req", "res", "method", "url", "html", "doc", "initialState", "body", "size", "chunk", "parsed", "comments", "verdict", "summary", "activeSection", "baseDir", "rel", "ext", "normalized", "resolvedBase", "resolvedFile", "err", "buf", "createReviewServer", "ctx", "createServer", "createRouteHandler", "startServer", "server", "port", "resolve", "reject", "addr", "actualPort", "stopServer", "err", "readFileSync", "existsSync", "join", "dirname", "fileURLToPath", "__dirname", "resolveHtmlPath", "siblingPath", "projectRoot", "distPath", "cached", "getAssetHtml", "HttpTransport", "doc", "section", "dir", "handler", "port", "createReviewServer", "submission", "getAssetHtml", "comments", "activeSection", "startServer", "stopServer", "IDLE_TIMEOUT_MS", "HEARTBEAT_TIMEOUT_MS", "runBrowserReview", "doc", "absPath", "contentHash", "restoredActiveSection", "transport", "HttpTransport", "dirnamePath", "comments", "activeSection", "saveSession", "reviewPromise", "resolve", "reject", "idleTimer", "heartbeatTimer", "armHeartbeat", "clearAll", "submission", "url", "openCmd", "spawnSync", "require", "createRequire", "version", "program", "Command", "file", "opts", "run", "err", "cancelled", "chalk", "__dirname", "dirname", "fileURLToPath", "src", "join", "existsSync", "dest", "homedir", "mkdirSync", "copyFileSync", "sessions", "listSessions", "dir", "getSessionDir", "s", "age", "formatRelativeTime", "status", "validTargets", "explicitTarget", "isClaudeAvailable", "inputFromStdin", "input", "readInput", "splitStrategy", "doc", "parse", "absPath", "resolvePath", "contentHash", "computeContentHash", "restoredActiveSection", "clearSession", "session", "loadSession", "promptYesNo", "reviewed", "reviewMeta", "navigate", "saveSession", "submission", "runBrowserReview", "outputTarget", "promptOutputTarget", "output", "formatReview", "writeOutput", "readFileSync", "iso", "ms", "seconds", "minutes", "hours", "days"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plan-review",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Interactive CLI for reviewing AI-generated markdown plans",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"url": "https://github.com/alvaroaac/plan-review.git",
|
|
32
32
|
"directory": "packages/cli"
|
|
33
33
|
},
|
|
34
|
-
"homepage": "https://github.com/alvaroaac/plan-review#readme",
|
|
34
|
+
"homepage": "https://github.com/alvaroaac/plan-review/tree/main/packages/cli#readme",
|
|
35
35
|
"bugs": {
|
|
36
36
|
"url": "https://github.com/alvaroaac/plan-review/issues"
|
|
37
37
|
},
|