@sienklogic/plan-build-run 2.24.0 → 2.26.1

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 (58) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +62 -13
  3. package/dashboard/package.json +1 -2
  4. package/dashboard/public/css/layout.css +128 -21
  5. package/dashboard/public/css/status-colors.css +14 -2
  6. package/dashboard/public/css/tokens.css +36 -0
  7. package/dashboard/src/middleware/current-phase.js +2 -1
  8. package/dashboard/src/routes/events.routes.js +49 -0
  9. package/dashboard/src/routes/pages.routes.js +250 -1
  10. package/dashboard/src/services/config.service.js +140 -0
  11. package/dashboard/src/services/dashboard.service.js +156 -11
  12. package/dashboard/src/services/log.service.js +105 -0
  13. package/dashboard/src/services/notes.service.js +16 -0
  14. package/dashboard/src/services/phase.service.js +58 -9
  15. package/dashboard/src/services/requirements.service.js +130 -0
  16. package/dashboard/src/services/research.service.js +137 -0
  17. package/dashboard/src/services/todo.service.js +30 -0
  18. package/dashboard/src/views/config.ejs +5 -0
  19. package/dashboard/src/views/logs.ejs +3 -0
  20. package/dashboard/src/views/note-detail.ejs +3 -0
  21. package/dashboard/src/views/partials/activity-feed.ejs +12 -0
  22. package/dashboard/src/views/partials/config-content.ejs +196 -0
  23. package/dashboard/src/views/partials/dashboard-content.ejs +71 -46
  24. package/dashboard/src/views/partials/log-entries-content.ejs +17 -0
  25. package/dashboard/src/views/partials/logs-content.ejs +131 -0
  26. package/dashboard/src/views/partials/note-detail-content.ejs +22 -0
  27. package/dashboard/src/views/partials/notes-content.ejs +7 -1
  28. package/dashboard/src/views/partials/phase-content.ejs +181 -146
  29. package/dashboard/src/views/partials/phase-timeline.ejs +16 -0
  30. package/dashboard/src/views/partials/requirements-content.ejs +44 -0
  31. package/dashboard/src/views/partials/research-content.ejs +49 -0
  32. package/dashboard/src/views/partials/research-detail-content.ejs +23 -0
  33. package/dashboard/src/views/partials/sidebar.ejs +63 -26
  34. package/dashboard/src/views/partials/todos-done-content.ejs +44 -0
  35. package/dashboard/src/views/requirements.ejs +3 -0
  36. package/dashboard/src/views/research-detail.ejs +3 -0
  37. package/dashboard/src/views/research.ejs +3 -0
  38. package/dashboard/src/views/todos-done.ejs +3 -0
  39. package/package.json +1 -1
  40. package/plugins/copilot-pbr/agents/dev-sync.agent.md +114 -0
  41. package/plugins/copilot-pbr/hooks/hooks.json +12 -0
  42. package/plugins/copilot-pbr/plugin.json +1 -1
  43. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
  44. package/plugins/cursor-pbr/agents/dev-sync.md +113 -0
  45. package/plugins/cursor-pbr/hooks/hooks.json +10 -0
  46. package/plugins/pbr/.claude-plugin/plugin.json +1 -1
  47. package/plugins/pbr/agents/dev-sync.md +120 -0
  48. package/plugins/pbr/hooks/hooks.json +10 -0
  49. package/plugins/pbr/scripts/config-schema.json +4 -1
  50. package/plugins/pbr/scripts/local-llm/health.js +4 -1
  51. package/plugins/pbr/scripts/local-llm/operations/classify-commit.js +68 -0
  52. package/plugins/pbr/scripts/local-llm/operations/classify-file-intent.js +73 -0
  53. package/plugins/pbr/scripts/local-llm/operations/triage-test-output.js +72 -0
  54. package/plugins/pbr/scripts/post-bash-triage.js +132 -0
  55. package/plugins/pbr/scripts/post-write-dispatch.js +44 -0
  56. package/plugins/pbr/scripts/pre-bash-dispatch.js +17 -11
  57. package/plugins/pbr/scripts/status-line.js +50 -5
  58. package/plugins/pbr/scripts/validate-commit.js +66 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,64 @@ All notable changes to Plan-Build-Run will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.26.1](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.26.0...plan-build-run-v2.26.1) (2026-02-24)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **tools:** remove platform-specific rollup dep from dashboard devDependencies ([91bb4a8](https://github.com/SienkLogic/plan-build-run/commit/91bb4a8a384bb48b6eb93806257213e9eb40abeb))
14
+
15
+ ## [2.26.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.25.0...plan-build-run-v2.26.0) (2026-02-24)
16
+
17
+
18
+ ### Features
19
+
20
+ * **36-01:** add .status-badge--sm and .status-badge--lg variants; tokenize base badge sizing ([2ed3a85](https://github.com/SienkLogic/plan-build-run/commit/2ed3a85de91fa359705dc0eb1ab231531aa4bbfd))
21
+ * **36-02:** create phase-timeline.ejs and activity-feed.ejs partials ([cfcad33](https://github.com/SienkLogic/plan-build-run/commit/cfcad33824ea291be101ad0cb3b288e53b6064ec))
22
+ * **36-02:** GREEN - add getRecentActivity and deriveQuickActions to dashboard.service.js ([9d8f55f](https://github.com/SienkLogic/plan-build-run/commit/9d8f55f68c7825245248ac70bd07e09e91d7109b))
23
+ * **36-02:** rework dashboard-content.ejs with status cards, timeline, activity feed, and quick actions ([7d8d735](https://github.com/SienkLogic/plan-build-run/commit/7d8d735f4d8e05c67a9f70190f402e3fc450ab0d))
24
+ * **36-03:** add prev/next phase navigation to phase detail view ([91b8bd5](https://github.com/SienkLogic/plan-build-run/commit/91b8bd5323ffaf9128dc66adeb74a0a8ccfa0da5))
25
+ * **36-04:** enrich getPhaseDetail with planTitle, taskCount, and mustHaves ([2ce86bb](https://github.com/SienkLogic/plan-build-run/commit/2ce86bb63cbbda0c6a6ede905936302a51e59e8b))
26
+ * **36-04:** overhaul plan cards to use .card component with wave, task count, and status ([ea963b8](https://github.com/SienkLogic/plan-build-run/commit/ea963b89b40a7f3c6b61d3ef0f6e19fd6deeee39))
27
+ * **36-04:** replace commit history table with visual .commit-timeline in phase-content.ejs ([a12d57e](https://github.com/SienkLogic/plan-build-run/commit/a12d57ef85852867bfcd33f171a3514a3151bd12))
28
+ * **36-05:** add config page CSS to layout.css ([687ee05](https://github.com/SienkLogic/plan-build-run/commit/687ee05be2acd543144267f0192adb71539a370a))
29
+ * **36-05:** add config shell page, hybrid form partial, and config CSS ([6827673](https://github.com/SienkLogic/plan-build-run/commit/68276734806efdbd3dd869f44e2283ae5b00ea18))
30
+ * **36-05:** add config.service with readConfig, writeConfig, validateConfig (TDD) ([26cf43a](https://github.com/SienkLogic/plan-build-run/commit/26cf43a08962d312deea31a2af51d0714be35a48))
31
+ * **36-05:** add GET /config and POST /api/config routes ([363348a](https://github.com/SienkLogic/plan-build-run/commit/363348adb75e83644d9accf0c401ef97d68be5da))
32
+ * **36-06:** add GET /research and GET /research/:slug routes with HTMX support ([a3ef246](https://github.com/SienkLogic/plan-build-run/commit/a3ef24633f865356a1ec591c3cd5c339eba14b0d))
33
+ * **36-06:** add research list and detail EJS templates with card layout and HTMX navigation ([4295545](https://github.com/SienkLogic/plan-build-run/commit/4295545d2feaa610034b728bdfd868f274e61d6f))
34
+ * **36-06:** GREEN - implement research.service with listResearchDocs, listCodebaseDocs, getResearchDocBySlug ([c1d2c1f](https://github.com/SienkLogic/plan-build-run/commit/c1d2c1fdd1b663b71388631a1e76554f06c01494))
35
+ * **36-07:** add GET /requirements route and EJS templates ([48d6d82](https://github.com/SienkLogic/plan-build-run/commit/48d6d8228250c17184abb5b7810d512b6a1b2f82))
36
+ * **36-07:** GREEN - implement getRequirementsData service ([016e0bc](https://github.com/SienkLogic/plan-build-run/commit/016e0bc1b22cef9f153bff0e8d2a4b692b57648d))
37
+ * **36-08:** add GET /logs route and GET /logs/stream SSE endpoint ([e5cdca5](https://github.com/SienkLogic/plan-build-run/commit/e5cdca5f9603de9973a41e1ef37b37630a527460))
38
+ * **36-08:** create logs EJS templates with SSE live-tail and filter controls ([c164794](https://github.com/SienkLogic/plan-build-run/commit/c1647944c9f8ef37be4b30df30ce18c702dee827))
39
+ * **36-08:** GREEN - implement log.service with listLogFiles, readLogPage, tailLogFile ([5827bd4](https://github.com/SienkLogic/plan-build-run/commit/5827bd4c9e0edce022784e14986b764b2970d229))
40
+ * **quick-004:** add local LLM token counter to statusline ([f5f5d4c](https://github.com/SienkLogic/plan-build-run/commit/f5f5d4c907a7a96e9d835c6e5b342fab7be86aad))
41
+ * **quick-004:** show session + lifetime LLM stats using stdin duration ([ef2512f](https://github.com/SienkLogic/plan-build-run/commit/ef2512fe1214eb9c0483253c67c8eb144643dcef))
42
+
43
+
44
+ ### Bug Fixes
45
+
46
+ * **36-01:** replace hardcoded CSS values with design tokens and expand config.service.js ([68e9261](https://github.com/SienkLogic/plan-build-run/commit/68e9261456f5e3d9f23b58dbfc7b55ca85036b11))
47
+ * **36-02:** add typeof guard for quickActions in dashboard template ([2d19f72](https://github.com/SienkLogic/plan-build-run/commit/2d19f72015945291ecb73cb8dbcfe01f6654fa5c))
48
+ * **quick-004:** render LLM stats on second line with explicit Local LLM label ([16509b3](https://github.com/SienkLogic/plan-build-run/commit/16509b3f85805592580f80355e6258a0bee3e4b7))
49
+ * **quick-006:** bump vitest to ^4.0.18 to match @vitest/coverage-v8 peer dep ([4f5b172](https://github.com/SienkLogic/plan-build-run/commit/4f5b172d4f83fed7b04ff972e7848ce5762edb71))
50
+ * **quick-007:** correct todo test to match title fallback behavior (H1 heading recovery) ([50397bb](https://github.com/SienkLogic/plan-build-run/commit/50397bb16f17cfd253ab919bb83d52afa5aee9ad))
51
+
52
+
53
+ ### Documentation
54
+
55
+ * **quick-005:** add Local LLM nav link and mention in feature highlights ([56784d8](https://github.com/SienkLogic/plan-build-run/commit/56784d800131df024aedc8d5ee6c8ddc81795e03))
56
+ * **quick-005:** add Local LLM Offload section and update stats across README and Getting Started ([264c891](https://github.com/SienkLogic/plan-build-run/commit/264c891e9aebc696f9194efd45428788606fecb3))
57
+
58
+ ## [2.25.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.24.0...plan-build-run-v2.25.0) (2026-02-24)
59
+
60
+
61
+ ### Features
62
+
63
+ * **36-01:** expand tokens.css with card, shadow, transition, table, and badge tokens ([75df300](https://github.com/SienkLogic/plan-build-run/commit/75df3000ecf3d44ed8c57f0528e719a9cc4b7c06))
64
+ * **tools:** add 3 local LLM operations — classify-commit, triage-test-output, classify-file-intent ([f456048](https://github.com/SienkLogic/plan-build-run/commit/f4560480888b916826f7c7e5ca6a091001779ab6))
65
+
8
66
  ## [2.24.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.23.0...plan-build-run-v2.24.0) (2026-02-24)
9
67
 
10
68
 
package/README.md CHANGED
@@ -14,6 +14,7 @@
14
14
  <a href="#getting-started">Getting Started</a> &bull;
15
15
  <a href="#commands">Commands</a> &bull;
16
16
  <a href="#how-it-works">How It Works</a> &bull;
17
+ <a href="#local-llm-offload">Local LLM</a> &bull;
17
18
  <a href="https://github.com/SienkLogic/plan-build-run/wiki">Wiki</a> &bull;
18
19
  <a href=".github/CONTRIBUTING.md">Contributing</a>
19
20
  </p>
@@ -26,7 +27,7 @@
26
27
  <img src="https://img.shields.io/badge/Node.js-18%2B-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js 18+" />
27
28
  <a href="LICENSE"><img src="https://img.shields.io/github/license/SienkLogic/plan-build-run?style=for-the-badge" alt="License" /></a>
28
29
  <a href="https://www.npmjs.com/package/@sienklogic/plan-build-run"><img src="https://img.shields.io/npm/v/@sienklogic/plan-build-run?style=for-the-badge&logo=npm&logoColor=white" alt="npm" /></a>
29
- <img src="https://img.shields.io/badge/Tests-1213_passing-brightgreen?style=for-the-badge" alt="1213 Tests" />
30
+ <img src="https://img.shields.io/badge/Tests-2153_passing-brightgreen?style=for-the-badge" alt="2153 Tests" />
30
31
  </p>
31
32
 
32
33
  ---
@@ -47,7 +48,7 @@ Plan-Build-Run takes a different approach: **structured context isolation**. Ins
47
48
  <img src="./assets/pbr-demo.gif" alt="Plan-Build-Run workflow: begin → plan → build → review" width="800" />
48
49
  </p>
49
50
 
50
- Goal-backward verification, lifecycle hooks, wave-based parallelism, kill-safe state, and more. See **[What Sets It Apart](https://github.com/SienkLogic/plan-build-run/wiki/What-Sets-It-Apart)** for the full comparison and differentiators.
51
+ Goal-backward verification, lifecycle hooks, wave-based parallelism, kill-safe state, and **[local LLM offloading](#local-llm-offload)** to cut frontier token costs on routine tasks. See **[What Sets It Apart](https://github.com/SienkLogic/plan-build-run/wiki/What-Sets-It-Apart)** for the full comparison and differentiators.
51
52
 
52
53
  > **When to use Plan-Build-Run:** Multi-phase projects where quality matters. New features spanning 5+ files, large refactors, greenfield builds, anything that would take more than one Claude Code session to complete. Use `depth: quick` or `depth: standard` to control agent spawn count per phase.
53
54
  >
@@ -218,7 +219,7 @@ Set `depth: quick` in `/pbr:config` to reduce agent spawns across all workflows.
218
219
  | `/pbr:build <N>` | Build a phase: parallel execution in waves, atomic commits | 2-4 (quick: 1-2) |
219
220
  | `/pbr:review <N>` | Verify a phase: automated 3-layer checks + conversational UAT | 1 |
220
221
 
221
- See **[Commands](https://github.com/SienkLogic/plan-build-run/wiki/Commands)** for all 22 commands with flags, cost-by-depth tables, and detailed descriptions.
222
+ See **[Commands](https://github.com/SienkLogic/plan-build-run/wiki/Commands)** for all 26 commands with flags, cost-by-depth tables, and detailed descriptions.
222
223
 
223
224
  ---
224
225
 
@@ -240,13 +241,60 @@ See **[Architecture](https://github.com/SienkLogic/plan-build-run/wiki/Architect
240
241
 
241
242
  ---
242
243
 
244
+ ## Local LLM Offload
245
+
246
+ Plan-Build-Run can offload lightweight inference tasks to a local model running on your machine, saving frontier tokens and reducing API costs — without sacrificing quality on complex operations.
247
+
248
+ **This is a novel capability.** Other AI coding frameworks use local LLMs as the primary model or not at all. PBR operates at the **sub-session level**: hook scripts that fire on every tool call use a local model for fast classification tasks (artifact type, commit scope, error category, file intent) while the frontier model handles planning, execution, and verification. You get the cost savings of local inference where it matters, with automatic fallback to Claude when confidence is low.
249
+
250
+ ### How it works
251
+
252
+ ```
253
+ Hook fires (e.g., PostToolUse:Write)
254
+
255
+ ├─ Complexity scorer evaluates the prompt
256
+ │ └─ High complexity? → Route to frontier (Claude)
257
+ │ └─ Low complexity? ↓
258
+
259
+ ├─ Local LLM classifies (Ollama, ~50-200ms)
260
+ │ └─ Confidence below threshold? → Fallback to frontier
261
+ │ └─ Confidence OK? → Use local result ✓
262
+
263
+ └─ Metrics logged to .planning/logs/local-llm-metrics.jsonl
264
+ ```
265
+
266
+ ### Key features
267
+
268
+ - **3 routing strategies**: `local_first` (default), `balanced`, `quality_first` — control the cost/quality tradeoff
269
+ - **8 offloadable operations**: artifact classification, task validation, commit classification, error classification, file intent, test triage, source scoring, context summarization
270
+ - **Shadow mode**: Run local and frontier in parallel, compare results, but always use frontier output — safe way to validate local accuracy before trusting it
271
+ - **Adaptive threshold tuning**: Analyzes shadow logs to suggest confidence threshold adjustments per operation
272
+ - **Circuit breaker**: Automatically disables local routing after consecutive failures — no cascading slowdowns
273
+ - **Session metrics**: See tokens saved, cost reduction, and per-operation breakdowns via `/pbr:status` or the dashboard
274
+
275
+ ### Quick setup
276
+
277
+ ```bash
278
+ # 1. Install Ollama (ollama.com/download)
279
+ # 2. Pull the recommended model
280
+ ollama pull qwen2.5-coder:7b
281
+
282
+ # 3. Enable in your project config
283
+ /pbr:config local_llm.enabled true
284
+ ```
285
+
286
+ Requires a GPU with 6+ GB VRAM for best performance. CPU-only works but adds latency. See **[Configuration](https://github.com/SienkLogic/plan-build-run/wiki/Configuration#local_llm)** for full setup details, hardware requirements, and Windows-specific notes.
287
+
288
+ ---
289
+
243
290
  ## Deep Dive
244
291
 
245
292
  | Topic | Description |
246
293
  |-------|-------------|
247
- | **[Agents](https://github.com/SienkLogic/plan-build-run/wiki/Agents)** | 10 specialized agents with configurable model profiles and file-based communication |
248
- | **[Configuration](https://github.com/SienkLogic/plan-build-run/wiki/Configuration)** | 12 config keys, depth/model profiles, 16+ feature toggles |
249
- | **[Hooks](https://github.com/SienkLogic/plan-build-run/wiki/Hooks)** | 15 lifecycle hooks that enforce discipline at zero token cost |
294
+ | **[Agents](https://github.com/SienkLogic/plan-build-run/wiki/Agents)** | 12 specialized agents with configurable model profiles and file-based communication |
295
+ | **[Configuration](https://github.com/SienkLogic/plan-build-run/wiki/Configuration)** | 13 config keys, depth/model profiles, 16+ feature toggles, local LLM settings |
296
+ | **[Hooks](https://github.com/SienkLogic/plan-build-run/wiki/Hooks)** | 38 hook scripts that enforce discipline at zero token cost |
297
+ | **[Local LLM](https://github.com/SienkLogic/plan-build-run/wiki/Configuration#local_llm)** | Offload hook-level inference to Ollama for token savings with automatic fallback |
250
298
  | **[Project Structure](https://github.com/SienkLogic/plan-build-run/wiki/Project-Structure)** | The `.planning/` directory layout, key files, and file ownership |
251
299
  | **[Dashboard](https://github.com/SienkLogic/plan-build-run/wiki/Dashboard)** | Web UI with live updates for browsing project state |
252
300
  | **[Cursor IDE](https://github.com/SienkLogic/plan-build-run/wiki/Cursor-IDE)** | Cursor plugin installation and cross-IDE workflow |
@@ -264,7 +312,7 @@ git clone https://github.com/SienkLogic/plan-build-run.git
264
312
  cd plan-build-run
265
313
  npm install
266
314
 
267
- # Run tests (1213 tests, 44 suites)
315
+ # Run tests (2153 tests, 73 suites)
268
316
  npm test
269
317
 
270
318
  # Lint
@@ -285,13 +333,14 @@ CI runs on Node 18/20/22 across Windows, macOS, and Linux. See [CONTRIBUTING.md]
285
333
 
286
334
  | Metric | Count |
287
335
  |--------|-------|
288
- | Skills (slash commands) | 22 |
289
- | Specialized agents | 10 |
290
- | Hook scripts | 28 |
336
+ | Skills (slash commands) | 26 |
337
+ | Specialized agents | 12 |
338
+ | Hook scripts | 38 |
339
+ | Local LLM operations | 8 |
291
340
  | Supported platforms | 3 (Claude Code, Cursor, Copilot CLI) |
292
- | Tests | 1213 |
293
- | Test suites | 44 |
294
- | Config toggles | 12 top-level keys |
341
+ | Tests | 2153 |
342
+ | Test suites | 73 |
343
+ | Config keys | 13 top-level (62+ properties) |
295
344
 
296
345
  ---
297
346
 
@@ -29,10 +29,9 @@
29
29
  "sanitize-html": "^2.13.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@rollup/rollup-win32-x64-msvc": "^4.57.1",
33
32
  "@vitest/coverage-v8": "^4.0.18",
34
33
  "memfs": "^4.56.10",
35
34
  "supertest": "^7.2.2",
36
- "vitest": "^3.1.0"
35
+ "vitest": "^4.0.18"
37
36
  }
38
37
  }
@@ -19,9 +19,9 @@ code, pre, kbd, samp {
19
19
  font-size: 0.875em;
20
20
  }
21
21
 
22
- h1 { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.025em; margin-bottom: var(--space-sm); }
23
- h2 { font-size: 1.35rem; font-weight: 600; letter-spacing: -0.02em; margin-top: var(--space-2xl); margin-bottom: var(--space-md); }
24
- h3 { font-size: 1.1rem; font-weight: 600; }
22
+ h1 { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.025em; line-height: 1.2; margin-bottom: var(--space-sm); }
23
+ h2 { font-size: 1.35rem; font-weight: 600; letter-spacing: -0.02em; line-height: 1.3; margin-top: var(--space-2xl); margin-bottom: var(--space-md); }
24
+ h3 { font-size: 1.1rem; font-weight: 600; line-height: 1.4; }
25
25
 
26
26
  p { line-height: 1.65; }
27
27
 
@@ -108,7 +108,7 @@ aside.sidebar nav a {
108
108
  font-size: 0.9rem;
109
109
  font-weight: 500;
110
110
  color: var(--color-text-dim);
111
- transition: all 0.15s ease;
111
+ transition: all var(--transition-fast);
112
112
  }
113
113
 
114
114
  aside.sidebar nav a:hover {
@@ -210,26 +210,34 @@ aside.sidebar details ul {
210
210
  color: var(--color-text-dim);
211
211
  }
212
212
 
213
- /* --- Article Cards --- */
213
+ /* --- Card Component --- */
214
+ .card,
214
215
  article {
215
- border: 1px solid var(--color-border);
216
- border-radius: var(--radius-md);
217
- background: var(--color-surface-raised);
216
+ border: 1px solid var(--card-border);
217
+ border-radius: var(--card-radius);
218
+ background: var(--card-bg);
219
+ box-shadow: var(--card-shadow);
218
220
  margin-bottom: var(--space-lg);
219
221
  }
220
222
 
223
+ .card__header,
221
224
  article > header {
222
- border-bottom: 1px solid var(--color-border);
223
- padding: var(--space-md) var(--space-lg);
224
- background: var(--color-surface-raised);
225
- border-radius: var(--radius-md) var(--radius-md) 0 0;
225
+ border-bottom: 1px solid var(--card-border);
226
+ padding: var(--card-header-padding);
227
+ background: var(--card-bg);
228
+ border-radius: var(--card-radius) var(--card-radius) 0 0;
226
229
  }
227
230
 
231
+ .card__header strong,
228
232
  article > header strong {
229
233
  font-size: 0.95rem;
230
234
  font-weight: 600;
231
235
  }
232
236
 
237
+ .card__body {
238
+ padding: var(--card-padding) var(--space-lg);
239
+ }
240
+
233
241
  /* --- Tables --- */
234
242
  .table-wrap {
235
243
  overflow-x: auto;
@@ -251,11 +259,13 @@ th {
251
259
  letter-spacing: 0.04em;
252
260
  color: var(--color-text-dim);
253
261
  white-space: nowrap;
262
+ padding: var(--table-cell-padding);
254
263
  }
255
264
 
256
265
  td {
257
266
  vertical-align: top;
258
267
  line-height: 1.5;
268
+ padding: var(--table-cell-padding);
259
269
  }
260
270
 
261
271
  /* --- Progress Bar --- */
@@ -272,7 +282,7 @@ progress::-webkit-progress-bar {
272
282
  progress::-webkit-progress-value {
273
283
  background: var(--color-accent);
274
284
  border-radius: 999px;
275
- transition: width 0.4s ease;
285
+ transition: width var(--transition-base);
276
286
  }
277
287
 
278
288
  progress::-moz-progress-bar {
@@ -331,7 +341,7 @@ details li {
331
341
  .markdown-body th,
332
342
  .markdown-body td {
333
343
  border: 1px solid var(--color-border);
334
- padding: var(--space-xs) var(--space-sm);
344
+ padding: var(--table-cell-padding);
335
345
  text-align: left;
336
346
  }
337
347
 
@@ -340,7 +350,7 @@ details li {
340
350
  }
341
351
 
342
352
  .markdown-body pre {
343
- background: rgba(0, 0, 0, 0.3);
353
+ background: var(--overlay-bg);
344
354
  border-radius: var(--radius-sm);
345
355
  padding: var(--space-md);
346
356
  overflow-x: auto;
@@ -412,6 +422,26 @@ details li {
412
422
  font-weight: 500;
413
423
  }
414
424
 
425
+ /* Phase Navigation */
426
+ .phase-nav {
427
+ display: grid;
428
+ grid-template-columns: 1fr auto 1fr;
429
+ align-items: center;
430
+ gap: var(--space-md);
431
+ margin-bottom: var(--space-lg);
432
+ font-size: 0.875rem;
433
+ }
434
+
435
+ .phase-nav > a:first-child,
436
+ .phase-nav > span:first-child {
437
+ text-align: left;
438
+ }
439
+
440
+ .phase-nav > a:last-child,
441
+ .phase-nav > span:last-child {
442
+ text-align: right;
443
+ }
444
+
415
445
  /* --- Back Link --- */
416
446
  main > p:first-of-type > a[href="/"] {
417
447
  font-size: 0.875rem;
@@ -431,7 +461,7 @@ main > p:first-of-type > a[href="/"]:hover {
431
461
  border-radius: 50%;
432
462
  margin-left: var(--space-sm);
433
463
  vertical-align: middle;
434
- transition: background-color 0.3s ease;
464
+ transition: background-color var(--transition-base);
435
465
  }
436
466
 
437
467
  #sse-status[data-connected="true"] {
@@ -522,7 +552,7 @@ main > p:first-of-type > a[href="/"]:hover {
522
552
  /* --- Bar Chart --- */
523
553
  .bar-chart-row { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-xs); }
524
554
  .bar-chart-label { min-width: 160px; font-size: 0.875rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
525
- .bar-chart-bar { height: 1.5rem; border-radius: var(--radius-sm); background: var(--pico-primary); color: var(--pico-primary-inverse, #fff); font-size: 0.75rem; display: flex; align-items: center; padding-left: var(--space-sm); min-width: 2rem; transition: width 0.3s ease; }
555
+ .bar-chart-bar { height: 1.5rem; border-radius: var(--radius-sm); background: var(--pico-primary); color: var(--pico-primary-inverse, #fff); font-size: 0.75rem; display: flex; align-items: center; padding-left: var(--space-sm); min-width: 2rem; transition: width var(--transition-base); }
526
556
 
527
557
  /* --- Loading Bar --- */
528
558
  .loading-bar {
@@ -602,18 +632,18 @@ main > p:first-of-type > a[href="/"]:hover {
602
632
  height: calc(100vh - var(--size-header));
603
633
  z-index: 20;
604
634
  transform: translateX(-100%);
605
- transition: transform 0.25s ease;
635
+ transition: var(--transition-transform);
606
636
  display: block;
607
637
  border-right: 1px solid var(--color-border);
608
638
  border-bottom: none;
609
639
  padding: var(--space-lg) 0;
610
640
  overflow-y: auto;
611
- background: var(--pico-background-color, #1a1a2e);
641
+ background: var(--pico-background-color, var(--color-dark-surface));
612
642
  }
613
643
 
614
644
  .page-wrapper > aside.sidebar.open {
615
645
  transform: translateX(0);
616
- box-shadow: 4px 0 24px rgba(0, 0, 0, 0.3);
646
+ box-shadow: 4px 0 24px var(--overlay-bg);
617
647
  }
618
648
 
619
649
  .sidebar-backdrop {
@@ -621,7 +651,7 @@ main > p:first-of-type > a[href="/"]:hover {
621
651
  position: fixed;
622
652
  inset: 0;
623
653
  top: var(--size-header);
624
- background: rgba(0, 0, 0, 0.7);
654
+ background: var(--overlay-bg-heavy);
625
655
  z-index: 19;
626
656
  }
627
657
 
@@ -702,3 +732,80 @@ main > p:first-of-type > a[href="/"]:hover {
702
732
  text-align: center;
703
733
  }
704
734
  }
735
+
736
+ /* --- Commit Timeline --- */
737
+ .commit-timeline {
738
+ list-style: none;
739
+ padding: 0;
740
+ margin: 0 0 var(--space-lg);
741
+ border-left: 2px solid var(--color-border);
742
+ padding-left: var(--space-md);
743
+ }
744
+
745
+ .commit-timeline__item {
746
+ display: flex;
747
+ flex-wrap: wrap;
748
+ align-items: center;
749
+ gap: var(--space-xs);
750
+ padding: var(--space-xs) 0;
751
+ border-bottom: 1px solid var(--color-border);
752
+ position: relative;
753
+ }
754
+
755
+ .commit-timeline__item::before {
756
+ content: '';
757
+ position: absolute;
758
+ left: calc(-1 * var(--space-md) - 1px);
759
+ top: 50%;
760
+ transform: translateY(-50%);
761
+ width: 8px;
762
+ height: 8px;
763
+ border-radius: 50%;
764
+ background: var(--color-accent);
765
+ border: 2px solid var(--color-surface);
766
+ }
767
+
768
+ .commit-timeline__hash {
769
+ font-size: 0.75rem;
770
+ color: var(--color-text-muted);
771
+ }
772
+
773
+ .commit-timeline__task {
774
+ flex: 1;
775
+ font-size: 0.875rem;
776
+ min-width: 0;
777
+ overflow: hidden;
778
+ text-overflow: ellipsis;
779
+ white-space: nowrap;
780
+ }
781
+
782
+ .commit-timeline__meta {
783
+ font-size: 0.8125rem;
784
+ color: var(--color-text-muted);
785
+ display: flex;
786
+ align-items: center;
787
+ gap: var(--space-xs);
788
+ }
789
+
790
+ /* Config page */
791
+ .config-section { margin-bottom: var(--spacing-md); }
792
+ .config-section summary { cursor: pointer; list-style: none; }
793
+ .config-section summary::-webkit-details-marker { display: none; }
794
+ .config-row {
795
+ display: flex;
796
+ align-items: center;
797
+ gap: var(--spacing-md);
798
+ margin-bottom: var(--spacing-sm);
799
+ }
800
+ .config-row label { min-width: 160px; font-weight: 500; }
801
+ .config-row input[type="text"],
802
+ .config-row input[type="number"],
803
+ .config-row select { flex: 1; }
804
+ .config-fieldset { border: 1px solid var(--card-border); border-radius: var(--radius); padding: var(--spacing-md); margin-bottom: var(--spacing-md); }
805
+ .config-toggle-row { display: flex; align-items: center; margin-bottom: var(--spacing-xs); }
806
+ .config-toggle-row label { display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer; }
807
+ .config-raw-json { width: 100%; font-family: var(--font-mono, monospace); font-size: 0.85rem; }
808
+ .config-actions { display: flex; align-items: center; gap: var(--spacing-md); margin-top: var(--spacing-lg); }
809
+ .config-hint { color: var(--muted); font-size: 0.85rem; margin-bottom: var(--spacing-sm); }
810
+ .config-feedback--success { color: var(--color-success, green); font-weight: 500; }
811
+ .config-feedback--error { color: var(--color-error, red); font-weight: 500; }
@@ -23,9 +23,9 @@
23
23
  .status-badge {
24
24
  display: inline-flex;
25
25
  align-items: center;
26
- padding: 0.15rem 0.55rem;
26
+ padding: var(--badge-padding-base);
27
27
  border-radius: 999px;
28
- font-size: 0.75rem;
28
+ font-size: var(--badge-font-size-base);
29
29
  font-weight: 600;
30
30
  letter-spacing: 0.02em;
31
31
  line-height: 1.5;
@@ -96,3 +96,15 @@
96
96
  color: #a5b4fc;
97
97
  border-color: rgba(129, 140, 248, 0.2);
98
98
  }
99
+
100
+ /* Size variants */
101
+ .status-badge--sm {
102
+ padding: var(--badge-padding-sm);
103
+ font-size: var(--badge-font-size-sm);
104
+ }
105
+
106
+ .status-badge--lg {
107
+ padding: var(--badge-padding-lg);
108
+ font-size: var(--badge-font-size-lg);
109
+ font-weight: 700;
110
+ }
@@ -26,11 +26,47 @@
26
26
  /* Radii */
27
27
  --radius-sm: 0.375rem;
28
28
  --radius-md: 0.5rem;
29
+ --radius-lg: 0.75rem;
30
+
31
+ /* Shadows */
32
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);
33
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);
34
+
35
+ /* Transitions */
36
+ --transition-fast: 0.12s ease;
37
+ --transition-base: 0.2s ease;
38
+
39
+ /* Cards */
40
+ --card-bg: var(--color-surface-raised);
41
+ --card-border: var(--color-border);
42
+ --card-radius: var(--radius-md);
43
+ --card-shadow: var(--shadow-sm);
44
+ --card-padding: var(--space-md);
45
+ --card-header-padding: var(--space-sm) var(--space-lg);
46
+
47
+ /* Tables */
48
+ --table-cell-padding: var(--space-xs) var(--space-sm);
49
+ --table-cell-padding-y: var(--space-xs);
50
+ --table-cell-padding-x: var(--space-sm);
51
+
52
+ /* Badges */
53
+ --badge-padding-sm: 0.1rem 0.4rem;
54
+ --badge-padding-base: 0.15rem 0.55rem;
55
+ --badge-padding-lg: 0.25rem 0.75rem;
56
+ --badge-font-size-sm: 0.6875rem;
57
+ --badge-font-size-base: 0.75rem;
58
+ --badge-font-size-lg: 0.8125rem;
29
59
 
30
60
  /* Typography */
31
61
  --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
32
62
  --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Fira Code', monospace;
33
63
 
64
+ /* Overlays & Modals */
65
+ --overlay-bg: rgba(0, 0, 0, 0.3);
66
+ --overlay-bg-heavy: rgba(0, 0, 0, 0.7);
67
+ --color-dark-surface: #1a1a2e;
68
+ --transition-transform: transform 0.25s ease;
69
+
34
70
  /* Sizing */
35
71
  --size-sidebar: 220px;
36
72
  --size-header: 3.5rem;
@@ -12,7 +12,8 @@ export default async function currentPhaseMiddleware(req, res, next) {
12
12
  res.locals.currentPhase = {
13
13
  number: cp.id,
14
14
  name: cp.name,
15
- status: cp.status
15
+ status: cp.status,
16
+ nextAction: state.nextAction || null,
16
17
  };
17
18
  } else {
18
19
  res.locals.currentPhase = null;
@@ -1,5 +1,7 @@
1
1
  import { Router } from 'express';
2
2
  import { addClient, removeClient } from '../services/sse.service.js';
3
+ import { tailLogFile } from '../services/log.service.js';
4
+ import { join } from 'node:path';
3
5
 
4
6
  const router = Router();
5
7
 
@@ -42,4 +44,51 @@ router.get('/stream', (req, res) => {
42
44
  });
43
45
  });
44
46
 
47
+ /**
48
+ * GET /logs/stream?file=<filename>
49
+ * SSE endpoint that tails a .planning/logs/<filename> for new JSONL entries.
50
+ * Sends log-entry events to the connected client.
51
+ */
52
+ router.get('/logs/stream', async (req, res) => {
53
+ const { file } = req.query;
54
+
55
+ // Validate filename to prevent path traversal
56
+ if (!file || !/^[\w.-]+\.jsonl$/.test(file)) {
57
+ res.status(400).end('Invalid log file parameter');
58
+ return;
59
+ }
60
+
61
+ const projectDir = req.app.locals.projectDir;
62
+ const filePath = join(projectDir, '.planning', 'logs', file);
63
+
64
+ res.writeHead(200, {
65
+ 'Content-Type': 'text/event-stream',
66
+ 'Cache-Control': 'no-cache',
67
+ 'Connection': 'keep-alive',
68
+ 'X-Accel-Buffering': 'no'
69
+ });
70
+ res.flushHeaders();
71
+ res.write(': connected\n\n');
72
+
73
+ const sendEntry = (entry) => {
74
+ try {
75
+ const id = Date.now();
76
+ res.write(`event: log-entry\ndata: ${JSON.stringify(entry)}\nid: ${id}\n\n`);
77
+ } catch {
78
+ // client disconnected
79
+ }
80
+ };
81
+
82
+ const cleanup = await tailLogFile(filePath, sendEntry);
83
+
84
+ const heartbeat = setInterval(() => {
85
+ res.write(': heartbeat\n\n');
86
+ }, 30000);
87
+
88
+ req.on('close', () => {
89
+ clearInterval(heartbeat);
90
+ cleanup();
91
+ });
92
+ });
93
+
45
94
  export default router;