plan-review 1.0.5 → 1.1.2

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.
Files changed (43) hide show
  1. package/dist/browser/app.js +64 -40
  2. package/dist/browser/index.html +451 -42
  3. package/dist/index.js +30 -272
  4. package/dist/index.js.map +7 -1
  5. package/package.json +12 -9
  6. package/README.md +0 -145
  7. package/dist/formatter.d.ts +0 -2
  8. package/dist/formatter.js +0 -60
  9. package/dist/formatter.js.map +0 -1
  10. package/dist/index.d.ts +0 -2
  11. package/dist/navigator.d.ts +0 -5
  12. package/dist/navigator.js +0 -94
  13. package/dist/navigator.js.map +0 -1
  14. package/dist/output.d.ts +0 -7
  15. package/dist/output.js +0 -93
  16. package/dist/output.js.map +0 -1
  17. package/dist/parser.d.ts +0 -3
  18. package/dist/parser.js +0 -265
  19. package/dist/parser.js.map +0 -1
  20. package/dist/renderer.d.ts +0 -3
  21. package/dist/renderer.js +0 -78
  22. package/dist/renderer.js.map +0 -1
  23. package/dist/server/assets.d.ts +0 -1
  24. package/dist/server/assets.js +0 -25
  25. package/dist/server/assets.js.map +0 -1
  26. package/dist/server/routes.d.ts +0 -9
  27. package/dist/server/routes.js +0 -112
  28. package/dist/server/routes.js.map +0 -1
  29. package/dist/server/server.d.ts +0 -7
  30. package/dist/server/server.js +0 -23
  31. package/dist/server/server.js.map +0 -1
  32. package/dist/session.d.ts +0 -25
  33. package/dist/session.js +0 -133
  34. package/dist/session.js.map +0 -1
  35. package/dist/transport.d.ts +0 -22
  36. package/dist/transport.js +0 -35
  37. package/dist/transport.js.map +0 -1
  38. package/dist/types.d.ts +0 -34
  39. package/dist/types.js +0 -2
  40. package/dist/types.js.map +0 -1
  41. package/examples/demo-browser.gif +0 -0
  42. package/examples/demo-plan.md +0 -129
  43. package/skills/plan-review/SKILL.md +0 -70
package/dist/session.js DELETED
@@ -1,133 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { mkdirSync, readFileSync, writeFileSync, unlinkSync, readdirSync, existsSync, } from 'node:fs';
3
- import { join, resolve } from 'node:path';
4
- import { homedir } from 'node:os';
5
- // ── Internal helpers (not exported) ────────────────────────────────
6
- function pathHash(planPath) {
7
- const abs = resolve(planPath);
8
- const hash = createHash('sha256').update(abs).digest('hex');
9
- return hash.slice(0, 16);
10
- }
11
- function sessionFilePath(planPath) {
12
- return join(getSessionDir(), pathHash(planPath) + '.json');
13
- }
14
- // ── Exported functions ─────────────────────────────────────────────
15
- export function getSessionDir() {
16
- const dir = join(homedir(), '.plan-review', 'sessions');
17
- mkdirSync(dir, { recursive: true });
18
- return dir;
19
- }
20
- export function computeContentHash(content) {
21
- const hex = createHash('sha256').update(content).digest('hex');
22
- return `sha256:${hex}`;
23
- }
24
- export function saveSession(planPath, contentHash, comments, activeSection) {
25
- try {
26
- const data = {
27
- version: 1,
28
- planPath,
29
- contentHash,
30
- comments,
31
- activeSection,
32
- lastModified: new Date().toISOString(),
33
- };
34
- const filePath = sessionFilePath(planPath);
35
- writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
36
- }
37
- catch (err) {
38
- console.warn(`[plan-review] Failed to save session: ${err.message}`);
39
- }
40
- }
41
- export function loadSession(planPath, currentContentHash) {
42
- const filePath = sessionFilePath(planPath);
43
- if (!existsSync(filePath)) {
44
- return null;
45
- }
46
- let raw;
47
- try {
48
- raw = readFileSync(filePath, 'utf-8');
49
- }
50
- catch {
51
- return null;
52
- }
53
- let data;
54
- try {
55
- data = JSON.parse(raw);
56
- }
57
- catch {
58
- console.warn(`[plan-review] Corrupt session file, removing: ${filePath}`);
59
- try {
60
- unlinkSync(filePath);
61
- }
62
- catch {
63
- // ignore
64
- }
65
- return null;
66
- }
67
- // Restore Date objects in comment timestamps
68
- const comments = data.comments.map((c) => ({
69
- ...c,
70
- timestamp: new Date(c.timestamp),
71
- }));
72
- return {
73
- comments,
74
- activeSection: data.activeSection,
75
- stale: data.contentHash !== currentContentHash,
76
- };
77
- }
78
- export function clearSession(planPath) {
79
- const filePath = sessionFilePath(planPath);
80
- try {
81
- unlinkSync(filePath);
82
- }
83
- catch {
84
- // No error if missing
85
- }
86
- }
87
- export function listSessions() {
88
- const dir = getSessionDir();
89
- let files;
90
- try {
91
- files = readdirSync(dir);
92
- }
93
- catch {
94
- return [];
95
- }
96
- const results = [];
97
- for (const file of files) {
98
- if (!file.endsWith('.json'))
99
- continue;
100
- const filePath = join(dir, file);
101
- let data;
102
- try {
103
- const raw = readFileSync(filePath, 'utf-8');
104
- data = JSON.parse(raw);
105
- }
106
- catch {
107
- console.warn(`[plan-review] Skipping corrupt session file: ${filePath}`);
108
- continue;
109
- }
110
- let stale;
111
- if (!existsSync(data.planPath)) {
112
- stale = null;
113
- }
114
- else {
115
- try {
116
- const currentContent = readFileSync(data.planPath, 'utf-8');
117
- const currentHash = computeContentHash(currentContent);
118
- stale = currentHash !== data.contentHash;
119
- }
120
- catch {
121
- stale = null;
122
- }
123
- }
124
- results.push({
125
- planPath: data.planPath,
126
- commentCount: data.comments.length,
127
- lastModified: data.lastModified,
128
- stale,
129
- });
130
- }
131
- return results;
132
- }
133
- //# sourceMappingURL=session.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
@@ -1,22 +0,0 @@
1
- import type { PlanDocument, ReviewComment } from './types.js';
2
- export interface Transport {
3
- sendDocument(doc: PlanDocument): void;
4
- onReviewSubmit(handler: (comments: ReviewComment[]) => void): void;
5
- start(port: number): Promise<{
6
- url: string;
7
- }>;
8
- stop(): Promise<void>;
9
- }
10
- export declare class HttpTransport implements Transport {
11
- private doc;
12
- private submitHandler;
13
- private sessionSaveHandler;
14
- private server;
15
- sendDocument(doc: PlanDocument): void;
16
- onReviewSubmit(handler: (comments: ReviewComment[]) => void): void;
17
- onSessionSave(handler: (comments: ReviewComment[], activeSection: string | null) => void): void;
18
- start(port: number): Promise<{
19
- url: string;
20
- }>;
21
- stop(): Promise<void>;
22
- }
package/dist/transport.js DELETED
@@ -1,35 +0,0 @@
1
- import { createReviewServer, startServer, stopServer } from './server/server.js';
2
- import { getAssetHtml } from './server/assets.js';
3
- export class HttpTransport {
4
- doc = null;
5
- submitHandler = null;
6
- sessionSaveHandler = null;
7
- server = null;
8
- sendDocument(doc) {
9
- this.doc = doc;
10
- }
11
- onReviewSubmit(handler) {
12
- this.submitHandler = handler;
13
- }
14
- onSessionSave(handler) {
15
- this.sessionSaveHandler = handler;
16
- }
17
- async start(port) {
18
- if (!this.doc)
19
- throw new Error('No document set');
20
- this.server = createReviewServer({
21
- getDocument: () => this.doc,
22
- onSubmit: (comments) => this.submitHandler?.(comments),
23
- getAssetHtml: () => getAssetHtml(),
24
- onSessionSave: (comments, activeSection) => this.sessionSaveHandler?.(comments, activeSection),
25
- });
26
- return startServer(this.server, port);
27
- }
28
- async stop() {
29
- if (this.server && this.server.listening) {
30
- await stopServer(this.server);
31
- this.server = null;
32
- }
33
- }
34
- }
35
- //# sourceMappingURL=transport.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AASlD,MAAM,OAAO,aAAa;IAChB,GAAG,GAAwB,IAAI,CAAC;IAChC,aAAa,GAAiD,IAAI,CAAC;IACnE,kBAAkB,GAA+E,IAAI,CAAC;IACtG,MAAM,GAAkB,IAAI,CAAC;IAErC,YAAY,CAAC,GAAiB;QAC5B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,cAAc,CAAC,OAA4C;QACzD,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;IAC/B,CAAC;IAED,aAAa,CAAC,OAA0E;QACtF,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAElD,IAAI,CAAC,MAAM,GAAG,kBAAkB,CAAC;YAC/B,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAI;YAC5B,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,QAAQ,CAAC;YACtD,YAAY,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE;YAClC,aAAa,EAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC;SAC/F,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACzC,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
package/dist/types.d.ts DELETED
@@ -1,34 +0,0 @@
1
- export interface PlanDocument {
2
- title: string;
3
- metadata: Record<string, string>;
4
- mode: 'plan' | 'generic';
5
- sections: Section[];
6
- comments: ReviewComment[];
7
- }
8
- export interface Section {
9
- id: string;
10
- heading: string;
11
- level: number;
12
- body: string;
13
- parent?: string;
14
- dependencies?: {
15
- dependsOn: string[];
16
- blocks: string[];
17
- };
18
- relatedFiles?: string[];
19
- verification?: string;
20
- }
21
- export interface LineAnchor {
22
- type: 'lines';
23
- startLine: number;
24
- endLine: number;
25
- lineTexts: string[];
26
- }
27
- export interface ReviewComment {
28
- sectionId: string;
29
- text: string;
30
- timestamp: Date;
31
- anchor?: LineAnchor;
32
- }
33
- export type OutputTarget = 'stdout' | 'clipboard' | 'file' | 'claude';
34
- export type SplitStrategy = 'heading' | 'separator' | 'auto';
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
package/dist/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
Binary file
@@ -1,129 +0,0 @@
1
- # User Authentication System — Implementation Plan
2
-
3
- **Goal:** Add email/password authentication with session management to the web app.
4
-
5
- **Architecture:** Express middleware + bcrypt + JWT tokens + PostgreSQL sessions table. Three milestones: database schema, auth endpoints, session management.
6
-
7
- **Tech Stack:** Node.js, Express, PostgreSQL, bcrypt, jsonwebtoken
8
-
9
- ---
10
-
11
- ## Milestone 1: Database Foundation
12
-
13
- ### Task 1.1: Create users table
14
-
15
- **Depends On:** (none)
16
- **Blocks:** 1.2, 2.1, 2.2
17
- **Related Files:** `src/db/migrations/001_users.sql`, `src/db/schema.ts`
18
- **Verification:** `npm run migrate && npm test`
19
-
20
- Create the users table with the following columns:
21
-
22
- | Column | Type | Constraints |
23
- |--------|------|-------------|
24
- | id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() |
25
- | email | VARCHAR(255) | UNIQUE, NOT NULL |
26
- | password_hash | VARCHAR(255) | NOT NULL |
27
- | created_at | TIMESTAMP | DEFAULT NOW() |
28
- | updated_at | TIMESTAMP | DEFAULT NOW() |
29
-
30
- Add an index on `email` for login lookups. The migration should be idempotent (use `IF NOT EXISTS`).
31
-
32
- ### Task 1.2: Create sessions table
33
-
34
- **Depends On:** 1.1
35
- **Blocks:** 3.1, 3.2
36
- **Related Files:** `src/db/migrations/002_sessions.sql`, `src/db/schema.ts`
37
- **Verification:** `npm run migrate && npm test`
38
-
39
- Sessions table for server-side session tracking:
40
-
41
- - `id` (UUID, primary key)
42
- - `user_id` (UUID, foreign key → users.id, ON DELETE CASCADE)
43
- - `token` (VARCHAR, unique, indexed)
44
- - `expires_at` (TIMESTAMP, NOT NULL)
45
- - `created_at` (TIMESTAMP, DEFAULT NOW())
46
-
47
- Add a cleanup index on `expires_at` for the session pruning job.
48
-
49
- ---
50
-
51
- ## Milestone 2: Authentication Endpoints
52
-
53
- ### Task 2.1: POST /auth/register
54
-
55
- **Depends On:** 1.1
56
- **Blocks:** 2.3
57
- **Related Files:** `src/routes/auth.ts`, `src/services/user.ts`, `tests/auth.test.ts`
58
- **Verification:** `npm test -- --grep "register"`
59
-
60
- Accepts `{ email, password }`. Validates:
61
- - Email format (basic regex, no need for RFC 5322 compliance)
62
- - Password minimum 8 characters
63
- - Email not already registered (unique constraint handles race conditions)
64
-
65
- Hash password with bcrypt (cost factor 12). Return `201 { user: { id, email } }` on success, `400` with validation errors, `409` if email exists.
66
-
67
- ### Task 2.2: POST /auth/login
68
-
69
- **Depends On:** 1.1
70
- **Blocks:** 2.3
71
- **Related Files:** `src/routes/auth.ts`, `src/services/auth.ts`, `tests/auth.test.ts`
72
- **Verification:** `npm test -- --grep "login"`
73
-
74
- Accepts `{ email, password }`. Compares against stored bcrypt hash.
75
-
76
- On success: create a session (Task 3.1), return `200 { token, expiresAt }`.
77
- On failure: return `401 { error: "Invalid credentials" }`. Do **not** reveal whether the email exists.
78
-
79
- Rate limiting: 5 attempts per email per 15 minutes. Use a simple in-memory counter (Redis in v2).
80
-
81
- ### Task 2.3: Auth middleware
82
-
83
- **Depends On:** 2.1, 2.2
84
- **Blocks:** 3.2
85
- **Related Files:** `src/middleware/auth.ts`, `tests/middleware.test.ts`
86
- **Verification:** `npm test -- --grep "middleware"`
87
-
88
- Express middleware that:
89
- 1. Extracts `Authorization: Bearer <token>` header
90
- 2. Looks up session by token
91
- 3. Checks `expires_at > NOW()`
92
- 4. Attaches `req.user = { id, email }` if valid
93
- 5. Returns `401` if missing/invalid/expired
94
-
95
- Should be composable: `router.get('/profile', requireAuth, handler)`.
96
-
97
- ---
98
-
99
- ## Milestone 3: Session Management
100
-
101
- ### Task 3.1: Session creation and token generation
102
-
103
- **Depends On:** 1.2
104
- **Blocks:** 3.2
105
- **Related Files:** `src/services/session.ts`, `tests/session.test.ts`
106
- **Verification:** `npm test -- --grep "session"`
107
-
108
- Generate tokens using `crypto.randomBytes(32).toString('hex')` — not JWT for session tokens (JWTs can't be revoked without a blacklist, defeating the purpose of server-side sessions).
109
-
110
- Default expiry: 7 days. Configurable via `SESSION_TTL_HOURS` env var.
111
-
112
- ### Task 3.2: Session cleanup and logout
113
-
114
- **Depends On:** 1.2, 2.3
115
- **Blocks:** (none)
116
- **Related Files:** `src/services/session.ts`, `src/routes/auth.ts`, `tests/session.test.ts`
117
- **Verification:** `npm test -- --grep "logout|cleanup"`
118
-
119
- Two features:
120
-
121
- **Logout endpoint** — `POST /auth/logout` (requires auth middleware). Deletes the current session row. Returns `204`.
122
-
123
- **Cleanup job** — Runs every hour via `setInterval`. Deletes sessions where `expires_at < NOW()`. Log the count of pruned sessions.
124
-
125
- ```sql
126
- DELETE FROM sessions WHERE expires_at < NOW();
127
- ```
128
-
129
- Consider: should logout invalidate all sessions for the user, or just the current one? Start with current-only. Add "logout everywhere" as a v2 feature.
@@ -1,70 +0,0 @@
1
- ---
2
- name: plan-review
3
- description: Use when the user asks to review a plan, start plan review, or says "I want to review this plan". Triggers on plan review requests for markdown implementation plans, specs, or design docs — including plans produced on-the-fly in this conversation. Builds and runs the plan-review browser UI, feeds review output back into the conversation.
4
- ---
5
-
6
- # Plan Review
7
-
8
- Launch the plan-review browser UI for interactive review of markdown plans, then feed the structured review output back into this session.
9
-
10
- Works with two input sources:
11
- - **A plan file on disk** (path given by the user, or the most recent plan file in the project).
12
- - **An on-the-fly plan** produced in this conversation (e.g. from plan mode, or markdown the user just pasted). The plan content is piped in via stdin — no temp file needed.
13
-
14
- ## Prerequisites
15
-
16
- Either the `plan-review` CLI is on `$PATH` (installed via `npm install -g plan-review`) or a local dev checkout exists at `~/desenv/personal/plan-review/`.
17
-
18
- ## Process
19
-
20
- 1. **Identify the plan source.** Decide which branch you're in:
21
- - **File branch** — the user named a file, or pointed at a path, or asked to review "the plan at X". Also the default when you find a single recent match in `docs/superpowers/plans/*.md`. If multiple candidates exist, ask which one.
22
- - **Inline branch** — the user asks to review "this plan" / "the plan above" / "the plan you just wrote" / pastes markdown, or plan mode just produced a plan in the conversation. No file path exists.
23
-
24
- 2. **Pick the binary.** Prefer the installed CLI; fall back to the local dev build.
25
- ```bash
26
- if command -v plan-review >/dev/null 2>&1; then
27
- PLAN_REVIEW_CMD="plan-review"
28
- else
29
- # Dev fallback: build if dist missing
30
- if [ ! -f ~/desenv/personal/plan-review/dist/index.js ]; then
31
- (cd ~/desenv/personal/plan-review && npm run build)
32
- fi
33
- PLAN_REVIEW_CMD="node $HOME/desenv/personal/plan-review/dist/index.js"
34
- fi
35
- ```
36
-
37
- 3. **Run the review.**
38
-
39
- **File branch:**
40
- ```bash
41
- $PLAN_REVIEW_CMD <plan-file> --browser -o stdout
42
- ```
43
-
44
- **Inline branch** — pipe the plan content via a quoted heredoc so markdown is passed through verbatim (no shell expansion, no escaping needed):
45
- ```bash
46
- $PLAN_REVIEW_CMD --browser -o stdout <<'PLAN_EOF'
47
- # My Plan
48
-
49
- ## Section 1
50
- ...plan content from this conversation...
51
- PLAN_EOF
52
- ```
53
-
54
- Both variants open the browser review UI and block until the user clicks "Submit Review", then print structured review output to stdout.
55
-
56
- 4. **Read the output.** The review output is structured markdown with the user's comments anchored to specific sections. Read it and present a summary to the user.
57
-
58
- 5. **Act on feedback.** Ask the user what they want to do with the review:
59
- - Address the comments (modify the plan or code)
60
- - Save the review to a file
61
- - Continue discussion about specific comments
62
-
63
- ## Important
64
-
65
- - The `--browser` flag opens a three-panel review UI (TOC + content + comments).
66
- - The `-o stdout` flag ensures the review output comes back to this session.
67
- - The command will block until the user clicks "Submit Review" in the browser.
68
- - **File branch only:** if a session exists for this plan, the user is prompted to resume. Use `--fresh` to skip.
69
- - **Inline branch:** there is no file anchor, so no session resume — the review is ephemeral.
70
- - Always use a **quoted** heredoc delimiter (`<<'PLAN_EOF'`) so backticks, `$`, and other shell metacharacters in the plan are left alone.