@sienklogic/plan-build-run 2.23.0 → 2.26.0
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/CHANGELOG.md +66 -0
- package/README.md +62 -13
- package/dashboard/package.json +2 -2
- package/dashboard/public/css/layout.css +128 -21
- package/dashboard/public/css/status-colors.css +14 -2
- package/dashboard/public/css/tokens.css +36 -0
- package/dashboard/src/middleware/current-phase.js +2 -1
- package/dashboard/src/repositories/planning.repository.js +1 -11
- package/dashboard/src/routes/events.routes.js +49 -0
- package/dashboard/src/routes/pages.routes.js +367 -3
- package/dashboard/src/server.js +4 -0
- package/dashboard/src/services/audit.service.js +42 -0
- package/dashboard/src/services/config.service.js +140 -0
- package/dashboard/src/services/dashboard.service.js +153 -19
- package/dashboard/src/services/log.service.js +105 -0
- package/dashboard/src/services/notes.service.js +16 -0
- package/dashboard/src/services/phase.service.js +58 -9
- package/dashboard/src/services/requirements.service.js +130 -0
- package/dashboard/src/services/research.service.js +137 -0
- package/dashboard/src/services/roadmap.service.js +1 -11
- package/dashboard/src/services/todo.service.js +30 -0
- package/dashboard/src/utils/strip-bom.js +8 -0
- package/dashboard/src/views/audit-detail.ejs +5 -0
- package/dashboard/src/views/audits.ejs +5 -0
- package/dashboard/src/views/config.ejs +5 -0
- package/dashboard/src/views/logs.ejs +3 -0
- package/dashboard/src/views/note-detail.ejs +3 -0
- package/dashboard/src/views/partials/activity-feed.ejs +12 -0
- package/dashboard/src/views/partials/audit-detail-content.ejs +12 -0
- package/dashboard/src/views/partials/audits-content.ejs +34 -0
- package/dashboard/src/views/partials/config-content.ejs +196 -0
- package/dashboard/src/views/partials/dashboard-content.ejs +71 -46
- package/dashboard/src/views/partials/log-entries-content.ejs +17 -0
- package/dashboard/src/views/partials/logs-content.ejs +131 -0
- package/dashboard/src/views/partials/note-detail-content.ejs +22 -0
- package/dashboard/src/views/partials/notes-content.ejs +7 -1
- package/dashboard/src/views/partials/phase-content.ejs +181 -146
- package/dashboard/src/views/partials/phase-timeline.ejs +16 -0
- package/dashboard/src/views/partials/requirements-content.ejs +44 -0
- package/dashboard/src/views/partials/research-content.ejs +49 -0
- package/dashboard/src/views/partials/research-detail-content.ejs +23 -0
- package/dashboard/src/views/partials/sidebar.ejs +67 -22
- package/dashboard/src/views/partials/todos-content.ejs +13 -3
- package/dashboard/src/views/partials/todos-done-content.ejs +44 -0
- package/dashboard/src/views/requirements.ejs +3 -0
- package/dashboard/src/views/research-detail.ejs +3 -0
- package/dashboard/src/views/research.ejs +3 -0
- package/dashboard/src/views/todos-done.ejs +3 -0
- package/package.json +1 -1
- package/plugins/copilot-pbr/agents/dev-sync.agent.md +114 -0
- package/plugins/copilot-pbr/agents/integration-checker.agent.md +9 -2
- package/plugins/copilot-pbr/agents/planner.agent.md +19 -0
- package/plugins/copilot-pbr/agents/verifier.agent.md +22 -2
- package/plugins/copilot-pbr/hooks/hooks.json +12 -0
- package/plugins/copilot-pbr/plugin.json +1 -1
- package/plugins/copilot-pbr/references/plan-format.md +22 -0
- package/plugins/copilot-pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
- package/plugins/copilot-pbr/templates/VERIFICATION-DETAIL.md.tmpl +2 -1
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/agents/dev-sync.md +113 -0
- package/plugins/cursor-pbr/agents/integration-checker.md +9 -2
- package/plugins/cursor-pbr/agents/planner.md +19 -0
- package/plugins/cursor-pbr/agents/verifier.md +22 -2
- package/plugins/cursor-pbr/hooks/hooks.json +10 -0
- package/plugins/cursor-pbr/references/plan-format.md +22 -0
- package/plugins/cursor-pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
- package/plugins/cursor-pbr/templates/VERIFICATION-DETAIL.md.tmpl +2 -1
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/agents/dev-sync.md +120 -0
- package/plugins/pbr/agents/integration-checker.md +9 -2
- package/plugins/pbr/agents/planner.md +19 -0
- package/plugins/pbr/agents/verifier.md +22 -2
- package/plugins/pbr/hooks/hooks.json +10 -0
- package/plugins/pbr/references/plan-format.md +22 -0
- package/plugins/pbr/scripts/check-plan-format.js +2 -2
- package/plugins/pbr/scripts/check-subagent-output.js +2 -2
- package/plugins/pbr/scripts/config-schema.json +4 -1
- package/plugins/pbr/scripts/local-llm/health.js +4 -1
- package/plugins/pbr/scripts/local-llm/operations/classify-commit.js +68 -0
- package/plugins/pbr/scripts/local-llm/operations/classify-file-intent.js +73 -0
- package/plugins/pbr/scripts/local-llm/operations/triage-test-output.js +72 -0
- package/plugins/pbr/scripts/post-bash-triage.js +132 -0
- package/plugins/pbr/scripts/post-write-dispatch.js +44 -0
- package/plugins/pbr/scripts/pre-bash-dispatch.js +17 -11
- package/plugins/pbr/scripts/status-line.js +50 -5
- package/plugins/pbr/scripts/validate-commit.js +66 -2
- package/plugins/pbr/scripts/validate-task.js +1 -1
- package/plugins/pbr/templates/INTEGRATION-REPORT.md.tmpl +18 -2
- package/plugins/pbr/templates/VERIFICATION-DETAIL.md.tmpl +2 -1
- package/dashboard/src/views/coming-soon.ejs +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,72 @@ 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.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.25.0...plan-build-run-v2.26.0) (2026-02-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **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))
|
|
14
|
+
* **36-02:** create phase-timeline.ejs and activity-feed.ejs partials ([cfcad33](https://github.com/SienkLogic/plan-build-run/commit/cfcad33824ea291be101ad0cb3b288e53b6064ec))
|
|
15
|
+
* **36-02:** GREEN - add getRecentActivity and deriveQuickActions to dashboard.service.js ([9d8f55f](https://github.com/SienkLogic/plan-build-run/commit/9d8f55f68c7825245248ac70bd07e09e91d7109b))
|
|
16
|
+
* **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))
|
|
17
|
+
* **36-03:** add prev/next phase navigation to phase detail view ([91b8bd5](https://github.com/SienkLogic/plan-build-run/commit/91b8bd5323ffaf9128dc66adeb74a0a8ccfa0da5))
|
|
18
|
+
* **36-04:** enrich getPhaseDetail with planTitle, taskCount, and mustHaves ([2ce86bb](https://github.com/SienkLogic/plan-build-run/commit/2ce86bb63cbbda0c6a6ede905936302a51e59e8b))
|
|
19
|
+
* **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))
|
|
20
|
+
* **36-04:** replace commit history table with visual .commit-timeline in phase-content.ejs ([a12d57e](https://github.com/SienkLogic/plan-build-run/commit/a12d57ef85852867bfcd33f171a3514a3151bd12))
|
|
21
|
+
* **36-05:** add config page CSS to layout.css ([687ee05](https://github.com/SienkLogic/plan-build-run/commit/687ee05be2acd543144267f0192adb71539a370a))
|
|
22
|
+
* **36-05:** add config shell page, hybrid form partial, and config CSS ([6827673](https://github.com/SienkLogic/plan-build-run/commit/68276734806efdbd3dd869f44e2283ae5b00ea18))
|
|
23
|
+
* **36-05:** add config.service with readConfig, writeConfig, validateConfig (TDD) ([26cf43a](https://github.com/SienkLogic/plan-build-run/commit/26cf43a08962d312deea31a2af51d0714be35a48))
|
|
24
|
+
* **36-05:** add GET /config and POST /api/config routes ([363348a](https://github.com/SienkLogic/plan-build-run/commit/363348adb75e83644d9accf0c401ef97d68be5da))
|
|
25
|
+
* **36-06:** add GET /research and GET /research/:slug routes with HTMX support ([a3ef246](https://github.com/SienkLogic/plan-build-run/commit/a3ef24633f865356a1ec591c3cd5c339eba14b0d))
|
|
26
|
+
* **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))
|
|
27
|
+
* **36-06:** GREEN - implement research.service with listResearchDocs, listCodebaseDocs, getResearchDocBySlug ([c1d2c1f](https://github.com/SienkLogic/plan-build-run/commit/c1d2c1fdd1b663b71388631a1e76554f06c01494))
|
|
28
|
+
* **36-07:** add GET /requirements route and EJS templates ([48d6d82](https://github.com/SienkLogic/plan-build-run/commit/48d6d8228250c17184abb5b7810d512b6a1b2f82))
|
|
29
|
+
* **36-07:** GREEN - implement getRequirementsData service ([016e0bc](https://github.com/SienkLogic/plan-build-run/commit/016e0bc1b22cef9f153bff0e8d2a4b692b57648d))
|
|
30
|
+
* **36-08:** add GET /logs route and GET /logs/stream SSE endpoint ([e5cdca5](https://github.com/SienkLogic/plan-build-run/commit/e5cdca5f9603de9973a41e1ef37b37630a527460))
|
|
31
|
+
* **36-08:** create logs EJS templates with SSE live-tail and filter controls ([c164794](https://github.com/SienkLogic/plan-build-run/commit/c1647944c9f8ef37be4b30df30ce18c702dee827))
|
|
32
|
+
* **36-08:** GREEN - implement log.service with listLogFiles, readLogPage, tailLogFile ([5827bd4](https://github.com/SienkLogic/plan-build-run/commit/5827bd4c9e0edce022784e14986b764b2970d229))
|
|
33
|
+
* **quick-004:** add local LLM token counter to statusline ([f5f5d4c](https://github.com/SienkLogic/plan-build-run/commit/f5f5d4c907a7a96e9d835c6e5b342fab7be86aad))
|
|
34
|
+
* **quick-004:** show session + lifetime LLM stats using stdin duration ([ef2512f](https://github.com/SienkLogic/plan-build-run/commit/ef2512fe1214eb9c0483253c67c8eb144643dcef))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
* **36-01:** replace hardcoded CSS values with design tokens and expand config.service.js ([68e9261](https://github.com/SienkLogic/plan-build-run/commit/68e9261456f5e3d9f23b58dbfc7b55ca85036b11))
|
|
40
|
+
* **36-02:** add typeof guard for quickActions in dashboard template ([2d19f72](https://github.com/SienkLogic/plan-build-run/commit/2d19f72015945291ecb73cb8dbcfe01f6654fa5c))
|
|
41
|
+
* **quick-004:** render LLM stats on second line with explicit Local LLM label ([16509b3](https://github.com/SienkLogic/plan-build-run/commit/16509b3f85805592580f80355e6258a0bee3e4b7))
|
|
42
|
+
* **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))
|
|
43
|
+
* **quick-007:** correct todo test to match title fallback behavior (H1 heading recovery) ([50397bb](https://github.com/SienkLogic/plan-build-run/commit/50397bb16f17cfd253ab919bb83d52afa5aee9ad))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Documentation
|
|
47
|
+
|
|
48
|
+
* **quick-005:** add Local LLM nav link and mention in feature highlights ([56784d8](https://github.com/SienkLogic/plan-build-run/commit/56784d800131df024aedc8d5ee6c8ddc81795e03))
|
|
49
|
+
* **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))
|
|
50
|
+
|
|
51
|
+
## [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)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Features
|
|
55
|
+
|
|
56
|
+
* **36-01:** expand tokens.css with card, shadow, transition, table, and badge tokens ([75df300](https://github.com/SienkLogic/plan-build-run/commit/75df3000ecf3d44ed8c57f0528e719a9cc4b7c06))
|
|
57
|
+
* **tools:** add 3 local LLM operations — classify-commit, triage-test-output, classify-file-intent ([f456048](https://github.com/SienkLogic/plan-build-run/commit/f4560480888b916826f7c7e5ca6a091001779ab6))
|
|
58
|
+
|
|
59
|
+
## [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)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### Features
|
|
63
|
+
|
|
64
|
+
* **35-05:** add Audit Reports view with /audits and /audits/:filename routes ([33dfae7](https://github.com/SienkLogic/plan-build-run/commit/33dfae7fff02cbba93e250211e54b26d565f4f76))
|
|
65
|
+
* **35-05:** GREEN - implement audit.service.js with listAuditReports and getAuditReport ([c79179a](https://github.com/SienkLogic/plan-build-run/commit/c79179a75a386096e74589f13fea4a56174b1870))
|
|
66
|
+
* **quick-003:** add data-flow to plan-format reference, verification template, and integration report template ([e73a31e](https://github.com/SienkLogic/plan-build-run/commit/e73a31e0c1eb9535014de4b60924bd98ced2f8cb))
|
|
67
|
+
* **quick-003:** add data-flow verification to planner, verifier, and integration-checker agents ([e37e192](https://github.com/SienkLogic/plan-build-run/commit/e37e19297e038c38aecd7d04e93c007928242f0f))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### Bug Fixes
|
|
71
|
+
|
|
72
|
+
* **quick-003:** pass data.session_id to LLM operations instead of undefined ([df4d168](https://github.com/SienkLogic/plan-build-run/commit/df4d1682b50935b53ddcc665b8dcdc394b8b277e))
|
|
73
|
+
|
|
8
74
|
## [2.23.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.22.2...plan-build-run-v2.23.0) (2026-02-24)
|
|
9
75
|
|
|
10
76
|
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
<a href="#getting-started">Getting Started</a> •
|
|
15
15
|
<a href="#commands">Commands</a> •
|
|
16
16
|
<a href="#how-it-works">How It Works</a> •
|
|
17
|
+
<a href="#local-llm-offload">Local LLM</a> •
|
|
17
18
|
<a href="https://github.com/SienkLogic/plan-build-run/wiki">Wiki</a> •
|
|
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-
|
|
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
|
|
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
|
|
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)** |
|
|
248
|
-
| **[Configuration](https://github.com/SienkLogic/plan-build-run/wiki/Configuration)** |
|
|
249
|
-
| **[Hooks](https://github.com/SienkLogic/plan-build-run/wiki/Hooks)** |
|
|
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 (
|
|
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) |
|
|
289
|
-
| Specialized agents |
|
|
290
|
-
| Hook scripts |
|
|
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 |
|
|
293
|
-
| Test suites |
|
|
294
|
-
| Config
|
|
341
|
+
| Tests | 2153 |
|
|
342
|
+
| Test suites | 73 |
|
|
343
|
+
| Config keys | 13 top-level (62+ properties) |
|
|
295
344
|
|
|
296
345
|
---
|
|
297
346
|
|
package/dashboard/package.json
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
],
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@rollup/rollup-win32-x64-msvc": "^4.57.1",
|
|
23
22
|
"chokidar": "^5.0.0",
|
|
24
23
|
"commander": "^12.1.0",
|
|
25
24
|
"ejs": "^3.1.10",
|
|
@@ -30,9 +29,10 @@
|
|
|
30
29
|
"sanitize-html": "^2.13.0"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
32
|
+
"@rollup/rollup-win32-x64-msvc": "^4.57.1",
|
|
33
33
|
"@vitest/coverage-v8": "^4.0.18",
|
|
34
34
|
"memfs": "^4.56.10",
|
|
35
35
|
"supertest": "^7.2.2",
|
|
36
|
-
"vitest": "^
|
|
36
|
+
"vitest": "^4.0.18"
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -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
|
|
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
|
-
/* ---
|
|
213
|
+
/* --- Card Component --- */
|
|
214
|
+
.card,
|
|
214
215
|
article {
|
|
215
|
-
border: 1px solid var(--
|
|
216
|
-
border-radius: var(--radius
|
|
217
|
-
background: var(--
|
|
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(--
|
|
223
|
-
padding: var(--
|
|
224
|
-
background: var(--
|
|
225
|
-
border-radius: var(--radius
|
|
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
|
|
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(--
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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:
|
|
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:
|
|
26
|
+
padding: var(--badge-padding-base);
|
|
27
27
|
border-radius: 999px;
|
|
28
|
-
font-size:
|
|
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;
|
|
@@ -3,20 +3,10 @@ import { join, resolve, relative, normalize } from 'node:path';
|
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
import { marked } from 'marked';
|
|
5
5
|
import sanitizeHtml from 'sanitize-html';
|
|
6
|
+
import { stripBOM } from '../utils/strip-bom.js';
|
|
6
7
|
|
|
7
8
|
marked.setOptions({ gfm: true, breaks: false });
|
|
8
9
|
|
|
9
|
-
/**
|
|
10
|
-
* Strip UTF-8 BOM (Byte Order Mark) if present.
|
|
11
|
-
* Windows editors (Notepad, older VS Code) may prepend BOM to UTF-8 files.
|
|
12
|
-
* gray-matter will fail to detect frontmatter delimiters if BOM is present.
|
|
13
|
-
* @param {string} content - Raw file content
|
|
14
|
-
* @returns {string} Content without BOM
|
|
15
|
-
*/
|
|
16
|
-
function stripBOM(content) {
|
|
17
|
-
return content.replace(/^\uFEFF/, '');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
10
|
/**
|
|
21
11
|
* Validate that a resolved path stays within the base directory.
|
|
22
12
|
* Prevents path traversal attacks (e.g., ../../etc/passwd).
|
|
@@ -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;
|