@sienklogic/plan-build-run 2.26.1 → 2.27.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +29 -0
  3. package/dashboard/public/css/layout.css +110 -3
  4. package/dashboard/src/services/roadmap.service.js +28 -0
  5. package/dashboard/src/views/partials/activity-feed.ejs +9 -2
  6. package/dashboard/src/views/partials/analytics-content.ejs +4 -4
  7. package/dashboard/src/views/partials/config-content.ejs +28 -8
  8. package/dashboard/src/views/partials/dashboard-content.ejs +7 -1
  9. package/dashboard/src/views/partials/logs-content.ejs +4 -1
  10. package/dashboard/src/views/partials/milestones-content.ejs +34 -4
  11. package/dashboard/src/views/partials/notes-content.ejs +10 -11
  12. package/dashboard/src/views/partials/phase-timeline.ejs +5 -1
  13. package/dashboard/src/views/partials/phases-content.ejs +1 -2
  14. package/dashboard/src/views/partials/research-content.ejs +7 -0
  15. package/dashboard/src/views/partials/sidebar.ejs +2 -2
  16. package/dashboard/src/views/partials/todos-content.ejs +2 -2
  17. package/package.json +1 -1
  18. package/plugins/copilot-pbr/plugin.json +1 -1
  19. package/plugins/copilot-pbr/skills/build/SKILL.md +12 -0
  20. package/plugins/copilot-pbr/skills/quick/SKILL.md +12 -0
  21. package/plugins/copilot-pbr/skills/review/SKILL.md +14 -0
  22. package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
  23. package/plugins/cursor-pbr/README.md +20 -0
  24. package/plugins/cursor-pbr/skills/build/SKILL.md +12 -0
  25. package/plugins/cursor-pbr/skills/quick/SKILL.md +12 -0
  26. package/plugins/cursor-pbr/skills/review/SKILL.md +14 -0
  27. package/plugins/pbr/.claude-plugin/plugin.json +1 -1
  28. package/plugins/pbr/skills/build/SKILL.md +12 -0
  29. package/plugins/pbr/skills/quick/SKILL.md +12 -0
  30. package/plugins/pbr/skills/review/SKILL.md +14 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ 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.27.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.26.2...plan-build-run-v2.27.0) (2026-02-24)
9
+
10
+
11
+ ### Features
12
+
13
+ * **tools:** add local LLM skill-level fallbacks and platform compatibility docs ([d6d1242](https://github.com/SienkLogic/plan-build-run/commit/d6d1242d79eb924525761e0bc6ac65e1a2d51375))
14
+
15
+ ## [2.26.2](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.26.1...plan-build-run-v2.26.2) (2026-02-24)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **36-08:** improve dashboard UX across 12 pages with 16 visual fixes ([3e0d715](https://github.com/SienkLogic/plan-build-run/commit/3e0d715bb93c7fa18d93960e7ca322c14a1c9deb))
21
+
8
22
  ## [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
23
 
10
24
 
package/README.md CHANGED
@@ -304,6 +304,35 @@ Requires a GPU with 6+ GB VRAM for best performance. CPU-only works but adds lat
304
304
 
305
305
  ---
306
306
 
307
+ ## Platform Compatibility
308
+
309
+ Plan-Build-Run works across three platforms with varying levels of hook support. Hooks are the mechanism that fires validation scripts on every tool call — they power local LLM offloading, commit format enforcement, context budget tracking, and workflow gates.
310
+
311
+ | Feature | Claude Code | Copilot CLI | Cursor IDE |
312
+ |---------|:-----------:|:-----------:|:----------:|
313
+ | Skills (slash commands) | All 26 | All 26 | All 26 |
314
+ | Agents (subagent delegation) | All 12 | All 12 | All 12 |
315
+ | `.planning/` state management | Full | Full | Full |
316
+ | **Hook support** | **Full (14 events)** | **Partial (4 events)** | **Unverified** |
317
+ | Commit format enforcement | Hook-enforced | Hook-enforced | Manual |
318
+ | PLAN/SUMMARY quality classification | Hook + skill fallback | Hook + skill fallback | Skill fallback only |
319
+ | Test failure triage | Automatic (hook) | Automatic (hook) | Not available |
320
+ | Context budget tracking | Automatic (hook) | Not available | Not available |
321
+ | Auto-continue between skills | Automatic (hook) | Not available | Not available |
322
+ | Subagent lifecycle logging | Automatic (hook) | Not available | Not available |
323
+ | **Local LLM offloading** | **Full (8 operations)** | **Mostly (6-7 operations)** | **CLI only** |
324
+ | `pbr-tools.js llm` CLI commands | Full | Full | Full |
325
+
326
+ **Key differences:**
327
+
328
+ - **Claude Code** has full hook support — all local LLM operations fire automatically on every tool call
329
+ - **Copilot CLI** supports `sessionStart`, `preToolUse`, `postToolUse`, and `sessionEnd` — covers most validation hooks but misses lifecycle events (`SubagentStop`, `PreCompact`, `Stop`)
330
+ - **Cursor IDE** hook support is unverified — hooks.json is configured but whether Cursor actually fires them is unknown. Skills include `pbr-tools.js llm` fallback calls for key operations (plan quality, verification quality) so local LLM classification is available even without hooks
331
+
332
+ All platforms share the same scripts via relative paths — no code duplication. See the [Copilot CLI](plugins/copilot-pbr/README.md) and [Cursor IDE](plugins/cursor-pbr/README.md) READMEs for platform-specific details.
333
+
334
+ ---
335
+
307
336
  ## Local Development
308
337
 
309
338
  ```bash
@@ -164,6 +164,30 @@ aside.sidebar nav a[aria-current="page"] {
164
164
  margin-top: 2px;
165
165
  }
166
166
 
167
+ /* --- Sidebar: Next Action --- */
168
+ .sidebar-next-action {
169
+ padding: var(--space-sm) var(--space-lg);
170
+ border-bottom: 1px solid var(--color-border);
171
+ overflow: hidden;
172
+ }
173
+
174
+ .sidebar-next-action small {
175
+ text-transform: uppercase;
176
+ letter-spacing: 0.04em;
177
+ font-size: 0.6875rem;
178
+ color: var(--color-text-dim);
179
+ display: block;
180
+ }
181
+
182
+ .sidebar-next-action__cmd {
183
+ display: block;
184
+ font-size: 0.75rem;
185
+ margin-top: 2px;
186
+ overflow: hidden;
187
+ text-overflow: ellipsis;
188
+ white-space: nowrap;
189
+ }
190
+
167
191
  /* --- Sidebar: Section Details --- */
168
192
  aside.sidebar details {
169
193
  border: none;
@@ -171,22 +195,29 @@ aside.sidebar details {
171
195
  padding: 0;
172
196
  }
173
197
 
198
+ aside.sidebar details + details {
199
+ margin-top: var(--space-xs);
200
+ }
201
+
174
202
  aside.sidebar details summary {
175
203
  text-transform: uppercase;
176
- font-size: 0.75rem;
204
+ font-size: 0.6875rem;
177
205
  font-weight: 600;
178
- letter-spacing: 0.04em;
206
+ letter-spacing: 0.06em;
179
207
  color: var(--color-text-dim);
180
- padding: var(--space-sm) var(--space-lg);
208
+ padding: var(--space-xs) var(--space-lg);
181
209
  cursor: pointer;
210
+ border-left: 3px solid transparent;
182
211
  }
183
212
 
184
213
  aside.sidebar details[open] summary {
185
214
  color: var(--color-accent);
215
+ border-left-color: var(--color-accent);
186
216
  }
187
217
 
188
218
  aside.sidebar details ul {
189
219
  margin: 0;
220
+ padding-bottom: var(--space-xs);
190
221
  }
191
222
 
192
223
  /* --- Main Content --- */
@@ -422,6 +453,82 @@ details li {
422
453
  font-weight: 500;
423
454
  }
424
455
 
456
+ /* --- Phase Timeline (homepage) --- */
457
+ .phase-timeline {
458
+ list-style: none;
459
+ padding: 0;
460
+ margin: 0;
461
+ }
462
+
463
+ .phase-step {
464
+ display: flex;
465
+ align-items: center;
466
+ gap: var(--space-sm);
467
+ padding: var(--space-xs) 0;
468
+ border-bottom: 1px solid var(--color-border);
469
+ }
470
+
471
+ .phase-step:last-child {
472
+ border-bottom: none;
473
+ }
474
+
475
+ .step-label {
476
+ text-decoration: none;
477
+ display: flex;
478
+ gap: var(--space-sm);
479
+ align-items: baseline;
480
+ flex: 1;
481
+ min-width: 0;
482
+ }
483
+
484
+ .step-label strong {
485
+ font-size: 0.85rem;
486
+ white-space: nowrap;
487
+ }
488
+
489
+ .step-name {
490
+ font-size: 0.85rem;
491
+ color: var(--color-text-dim);
492
+ overflow: hidden;
493
+ text-overflow: ellipsis;
494
+ white-space: nowrap;
495
+ }
496
+
497
+ /* --- Activity Feed (homepage) --- */
498
+ .activity-feed {
499
+ list-style: none;
500
+ padding: 0;
501
+ margin: 0;
502
+ }
503
+
504
+ .activity-item {
505
+ display: flex;
506
+ justify-content: space-between;
507
+ align-items: baseline;
508
+ gap: var(--space-md);
509
+ padding: var(--space-xs) 0;
510
+ border-bottom: 1px solid var(--color-border);
511
+ font-size: 0.85rem;
512
+ }
513
+
514
+ .activity-item:last-child {
515
+ border-bottom: none;
516
+ }
517
+
518
+ .activity-path {
519
+ overflow: hidden;
520
+ text-overflow: ellipsis;
521
+ white-space: nowrap;
522
+ min-width: 0;
523
+ flex: 1;
524
+ }
525
+
526
+ .activity-time {
527
+ white-space: nowrap;
528
+ color: var(--color-text-dim);
529
+ font-size: 0.8rem;
530
+ }
531
+
425
532
  /* Phase Navigation */
426
533
  .phase-nav {
427
534
  display: grid;
@@ -111,6 +111,34 @@ function extractMilestones(roadmapContent) {
111
111
  });
112
112
  }
113
113
 
114
+ // Parse milestones that use ### Phase NN: format instead of **Phases:** N - M
115
+ // Format: "## Milestone: Name\n\nDescription\n\n### Phase NN: ..."
116
+ const sectionRegex = /## Milestone:\s*([^\n]+?)(?:\s*\{[^}]*\})?\s*\n([\s\S]*?)(?=\n## |\n---|\n$)/g;
117
+ for (const match of roadmapContent.matchAll(sectionRegex)) {
118
+ const rawName = match[1].trim();
119
+ // Skip already-seen (explicit or completed formats)
120
+ if (seenNames.has(rawName)) continue;
121
+ // Skip collapsed "-- COMPLETED" entries (already handled above)
122
+ if (/--\s*COMPLETED/i.test(rawName)) continue;
123
+ const body = match[2];
124
+ // Find ### Phase NN: lines within this milestone section
125
+ const phaseLines = [...body.matchAll(/### Phase\s+(\d+):/g)];
126
+ if (phaseLines.length > 0) {
127
+ const phaseIds = phaseLines.map(m => parseInt(m[1], 10));
128
+ const startPhase = Math.min(...phaseIds);
129
+ const endPhase = Math.max(...phaseIds);
130
+ // Extract goal from first paragraph (before first ### heading)
131
+ const goalText = body.split(/### /)[0].trim();
132
+ seenNames.add(rawName);
133
+ explicit.push({
134
+ name: rawName,
135
+ goal: goalText || '',
136
+ startPhase,
137
+ endPhase
138
+ });
139
+ }
140
+ }
141
+
114
142
  // Sort explicit milestones by start phase
115
143
  explicit.sort((a, b) => a.startPhase - b.startPhase);
116
144
 
@@ -3,9 +3,16 @@
3
3
  <% } else { %>
4
4
  <ul class="activity-feed">
5
5
  <% recentActivity.forEach(function(item) { %>
6
+ <%
7
+ // Format path: strip .planning/ prefix for brevity
8
+ var displayPath = item.path.replace(/^\.planning\//, '');
9
+ // Format timestamp: show just date+time, no timezone
10
+ var ts = item.timestamp || '';
11
+ var dateOnly = ts.replace(/\s+[-+]\d{4}$/, '').replace(/:\d{2}$/, '');
12
+ %>
6
13
  <li class="activity-item">
7
- <span class="activity-path"><%= item.path %></span>
8
- <time class="activity-time" datetime="<%= item.timestamp %>"><%= item.timestamp %></time>
14
+ <span class="activity-path"><%= displayPath %></span>
15
+ <time class="activity-time" datetime="<%= ts %>"><%= dateOnly %></time>
9
16
  </li>
10
17
  <% }); %>
11
18
  </ul>
@@ -92,7 +92,7 @@
92
92
  <% if (typeof llmMetrics !== 'undefined' && llmMetrics) { %>
93
93
  <article>
94
94
  <header>Local LLM Offload</header>
95
- <div class="grid">
95
+ <div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(140px,1fr))">
96
96
  <article>
97
97
  <header>Total Calls</header>
98
98
  <strong class="stat-value"><%= llmMetrics.summary.total_calls %></strong>
@@ -104,17 +104,17 @@
104
104
  <span class="stat-unit">frontier tokens</span>
105
105
  </article>
106
106
  <article>
107
- <header>Est. Cost Saved</header>
107
+ <header>Cost Saved</header>
108
108
  <strong class="stat-value">$<%= llmMetrics.summary.cost_saved_usd.toFixed(4) %></strong>
109
109
  <span class="stat-unit">at $3/M tokens</span>
110
110
  </article>
111
111
  <article>
112
- <header>Fallback Rate</header>
112
+ <header>Fallbacks</header>
113
113
  <strong class="stat-value"><%= llmMetrics.summary.fallback_rate_pct %>%</strong>
114
114
  <span class="stat-unit"><%= llmMetrics.summary.fallback_count %> fallbacks</span>
115
115
  </article>
116
116
  <article>
117
- <header>Avg Latency</header>
117
+ <header>Latency</header>
118
118
  <strong class="stat-value"><%= llmMetrics.summary.avg_latency_ms %></strong>
119
119
  <span class="stat-unit">ms/call</span>
120
120
  </article>
@@ -132,37 +132,57 @@
132
132
  </fieldset>
133
133
  <% } %>
134
134
 
135
- <%# Parallelization number inputs %>
135
+ <%# Parallelization inputs (toggles for booleans, number for numbers) %>
136
136
  <% if (config.parallelization && typeof config.parallelization === 'object') { %>
137
137
  <fieldset class="config-fieldset">
138
138
  <legend>Parallelization</legend>
139
139
  <% for (const [key, val] of Object.entries(config.parallelization)) { %>
140
+ <% if (typeof val === 'boolean') { %>
141
+ <div class="config-toggle-row">
142
+ <label>
143
+ <input type="checkbox" role="switch" name="parallelization.<%= key %>" value="on" <%= val ? 'checked' : '' %> />
144
+ <%= key %>
145
+ </label>
146
+ </div>
147
+ <% } else if (typeof val === 'number') { %>
140
148
  <div class="config-row">
141
149
  <label for="cfg-par-<%= key %>"><%= key %></label>
142
- <% if (typeof val === 'number') { %>
143
150
  <input id="cfg-par-<%= key %>" type="number" name="parallelization.<%= key %>" value="<%= val %>" min="1" />
144
- <% } else { %>
151
+ </div>
152
+ <% } else { %>
153
+ <div class="config-row">
154
+ <label for="cfg-par-<%= key %>"><%= key %></label>
145
155
  <input id="cfg-par-<%= key %>" type="text" name="parallelization.<%= key %>" value="<%= val %>" />
146
- <% } %>
147
156
  </div>
148
157
  <% } %>
158
+ <% } %>
149
159
  </fieldset>
150
160
  <% } %>
151
161
 
152
- <%# Planning number/text inputs %>
162
+ <%# Planning inputs (toggles for booleans, number for numbers) %>
153
163
  <% if (config.planning && typeof config.planning === 'object') { %>
154
164
  <fieldset class="config-fieldset">
155
165
  <legend>Planning</legend>
156
166
  <% for (const [key, val] of Object.entries(config.planning)) { %>
167
+ <% if (typeof val === 'boolean') { %>
168
+ <div class="config-toggle-row">
169
+ <label>
170
+ <input type="checkbox" role="switch" name="planning.<%= key %>" value="on" <%= val ? 'checked' : '' %> />
171
+ <%= key %>
172
+ </label>
173
+ </div>
174
+ <% } else if (typeof val === 'number') { %>
157
175
  <div class="config-row">
158
176
  <label for="cfg-plan-<%= key %>"><%= key %></label>
159
- <% if (typeof val === 'number') { %>
160
177
  <input id="cfg-plan-<%= key %>" type="number" name="planning.<%= key %>" value="<%= val %>" />
161
- <% } else { %>
178
+ </div>
179
+ <% } else { %>
180
+ <div class="config-row">
181
+ <label for="cfg-plan-<%= key %>"><%= key %></label>
162
182
  <input id="cfg-plan-<%= key %>" type="text" name="planning.<%= key %>" value="<%= val %>" />
163
- <% } %>
164
183
  </div>
165
184
  <% } %>
185
+ <% } %>
166
186
  </fieldset>
167
187
  <% } %>
168
188
 
@@ -1,5 +1,11 @@
1
1
  <%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
2
- <h1><%= projectName %></h1>
2
+ <%
3
+ // Show just the phase name as h1, not the full focus description
4
+ var dashboardTitle = (typeof currentPhase !== 'undefined' && currentPhase && currentPhase.name)
5
+ ? currentPhase.name
6
+ : (typeof projectName !== 'undefined' ? projectName.split(/\s*[—–-]\s*/)[0] : 'Dashboard');
7
+ %>
8
+ <h1><%= dashboardTitle %></h1>
3
9
 
4
10
  <!-- Status Cards -->
5
11
  <div class="status-cards">
@@ -98,7 +98,10 @@
98
98
  <% } %>
99
99
 
100
100
  <% } else { %>
101
- <p>Select a log file to view entries.</p>
101
+ <div style="text-align:center;padding:var(--space-xl) var(--space-md);color:var(--color-text-dim)">
102
+ <p style="font-size:1.5rem;margin-bottom:var(--space-sm)">&#8592;</p>
103
+ <p>Select a log file from the list to view entries.</p>
104
+ </div>
102
105
  <% } %>
103
106
  </div>
104
107
  </div>
@@ -1,9 +1,26 @@
1
1
  <%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
2
2
  <h1>Milestones</h1>
3
3
 
4
- <% if (active.length > 0) { %>
4
+ <%
5
+ // Filter out milestones that the parser already flagged as completed (collapsed in ROADMAP.md)
6
+ var nonCollapsed = active.filter(function(m) { return !m.completed; });
7
+
8
+ // From non-collapsed, separate truly active from ones whose phases are all complete
9
+ var trulyActive = nonCollapsed.filter(function(m) {
10
+ var msPhases = (typeof phases !== 'undefined' && phases) ? phases.filter(function(p) { return p.id >= m.startPhase && p.id <= m.endPhase; }) : [];
11
+ var completedPhases = msPhases.filter(function(p) { return p.status === 'complete'; }).length;
12
+ return msPhases.length === 0 || completedPhases < msPhases.length;
13
+ });
14
+ var completedNotArchived = nonCollapsed.filter(function(m) {
15
+ var msPhases = (typeof phases !== 'undefined' && phases) ? phases.filter(function(p) { return p.id >= m.startPhase && p.id <= m.endPhase; }) : [];
16
+ var completedPhases = msPhases.filter(function(p) { return p.status === 'complete'; }).length;
17
+ return msPhases.length > 0 && completedPhases >= msPhases.length;
18
+ });
19
+ %>
20
+
21
+ <% if (trulyActive.length > 0) { %>
5
22
  <h2>Active</h2>
6
- <% active.forEach(function(m) {
23
+ <% trulyActive.forEach(function(m) {
7
24
  var msPhases = (typeof phases !== 'undefined' && phases) ? phases.filter(function(p) { return p.id >= m.startPhase && p.id <= m.endPhase; }) : [];
8
25
  var totalPhases = msPhases.length;
9
26
  var completedPhases = msPhases.filter(function(p) { return p.status === 'complete'; }).length;
@@ -24,6 +41,19 @@
24
41
  <% }); %>
25
42
  <% } %>
26
43
 
44
+ <% if (completedNotArchived.length > 0) { %>
45
+ <h2>Completed (Pending Archive)</h2>
46
+ <% completedNotArchived.forEach(function(m) { %>
47
+ <article>
48
+ <header>
49
+ <strong><%= m.name %></strong>
50
+ <span class="status-badge status-badge--sm" data-status="complete" style="float:right">completed</span>
51
+ </header>
52
+ <p>Phases <%= m.startPhase %> &ndash; <%= m.endPhase %></p>
53
+ </article>
54
+ <% }); %>
55
+ <% } %>
56
+
27
57
  <% if (archived.length > 0) { %>
28
58
  <h2>Archived</h2>
29
59
  <article>
@@ -45,12 +75,12 @@
45
75
  <td colspan="6" style="padding:0">
46
76
  <details>
47
77
  <summary style="padding:0.5rem 1rem;cursor:pointer">
48
- <span class="grid" style="display:inline-grid;grid-template-columns:repeat(6,1fr);width:100%;text-align:left">
78
+ <span class="grid" style="display:inline-grid;grid-template-columns:60px 2fr 1fr 1fr 60px 60px;width:100%;text-align:left;gap:var(--space-sm)">
49
79
  <span><a href="/milestones/<%= m.version %>"
50
80
  hx-get="/milestones/<%= m.version %>"
51
81
  hx-target="#main-content"
52
82
  hx-push-url="true">v<%= m.version %></a></span>
53
- <span><%= m.name %></span>
83
+ <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis"><%= m.name %></span>
54
84
  <span><%= m.date || '—' %></span>
55
85
  <span><%= m.duration || '—' %></span>
56
86
  <span><%= m.stats && m.stats.phaseCount ? m.stats.phaseCount : '—' %></span>
@@ -6,24 +6,23 @@
6
6
  <% } else { %>
7
7
  <% notes.forEach(function(note) { %>
8
8
  <% const noteSlug = note.filename.replace(/^\d{4}-\d{2}-\d{2}-/, '').replace(/\.md$/, ''); %>
9
- <article>
10
- <header>
9
+ <article class="card">
10
+ <header class="card__header">
11
11
  <strong>
12
12
  <a href="/notes/<%= noteSlug %>"
13
13
  hx-get="/notes/<%= noteSlug %>"
14
14
  hx-target="#main-content"
15
15
  hx-push-url="true"><%= note.title %></a>
16
16
  </strong>
17
- <% if (note.promoted) { %>
18
- &nbsp; <span class="status-badge" data-status="complete">promoted</span>
19
- <% } %>
20
- <% if (note.date) { %>
21
- <small style="float:right"><%= note.date %></small>
22
- <% } %>
17
+ <span style="display:flex;gap:var(--space-sm);align-items:center;margin-left:auto">
18
+ <% if (note.promoted) { %>
19
+ <span class="status-badge status-badge--sm" data-status="complete">promoted</span>
20
+ <% } %>
21
+ <% if (note.date) { %>
22
+ <small style="color:var(--color-text-dim)"><%= typeof note.date === 'object' ? note.date.toISOString().slice(0, 10) : String(note.date).slice(0, 10) %></small>
23
+ <% } %>
24
+ </span>
23
25
  </header>
24
- <div class="markdown-body">
25
- <%- note.html %>
26
- </div>
27
26
  </article>
28
27
  <% }); %>
29
28
  <% } %>
@@ -9,7 +9,11 @@
9
9
  href="/phases/<%= String(phase.id).padStart(2, '0') %>"
10
10
  hx-get="/phases/<%= String(phase.id).padStart(2, '0') %>"
11
11
  hx-target="#main-content"
12
- hx-push-url="true">Phase <%= phase.id %></a>
12
+ hx-push-url="true">
13
+ <strong>Phase <%= phase.id %></strong>
14
+ <span class="step-name"><%= phase.name %></span>
15
+ </a>
16
+ <span class="status-badge status-badge--sm" data-status="<%= phase.status %>"><%= phase.status %></span>
13
17
  </li>
14
18
  <% }); %>
15
19
  </ol>
@@ -23,8 +23,7 @@
23
23
  <article>
24
24
  <header><strong>Summary</strong></header>
25
25
  <p>
26
- <%= phases.filter(p => p.status === 'complete').length %> of <%= phases.length %> phases complete
27
- across <%= milestoneGroups.length %> milestone<%= milestoneGroups.length !== 1 ? 's' : '' %>.
26
+ <%= phases.filter(p => p.status === 'complete').length %> of <%= phases.length %> phases complete<%= milestoneGroups.length > 0 ? ' across ' + milestoneGroups.length + ' milestone' + (milestoneGroups.length !== 1 ? 's' : '') : '' %>.
28
27
  </p>
29
28
  <progress value="<%= phases.filter(p => p.status === 'complete').length %>" max="<%= phases.length %>"></progress>
30
29
  </article>
@@ -24,6 +24,9 @@
24
24
  <% if (doc.coverage) { %><small>Coverage: <%= doc.coverage %></small><% } %>
25
25
  </p>
26
26
  <% } %>
27
+ <% if (!doc.topic && !doc.confidence && !doc.coverage) { %>
28
+ <p class="muted"><small>Click to view full document</small></p>
29
+ <% } %>
27
30
  </div>
28
31
  </article>
29
32
  <% }); %>
@@ -44,6 +47,10 @@
44
47
  </strong>
45
48
  <% if (doc.date) { %><small style="float:right"><%= doc.date %></small><% } %>
46
49
  </header>
50
+ <div class="card__body">
51
+ <% if (doc.focus) { %><p><strong>Focus:</strong> <%= doc.focus %></p><% } %>
52
+ <% if (!doc.focus) { %><p class="muted"><small>Click to view full document</small></p><% } %>
53
+ </div>
47
54
  </article>
48
55
  <% }); %>
49
56
  <% } %>
@@ -15,8 +15,8 @@
15
15
 
16
16
  <% if (typeof currentPhase !== 'undefined' && currentPhase && currentPhase.nextAction) { %>
17
17
  <div class="sidebar-next-action">
18
- <small>Suggested next:</small>
19
- <code><%= currentPhase.nextAction %></code>
18
+ <small>Suggested next</small>
19
+ <code class="sidebar-next-action__cmd"><%= currentPhase.nextAction %></code>
20
20
  </div>
21
21
  <% } %>
22
22
 
@@ -89,13 +89,13 @@
89
89
  <%= todo.priority %>
90
90
  </span>
91
91
  </td>
92
- <td><%= todo.phase %></td>
92
+ <td><%= todo.phase || '—' %></td>
93
93
  <td>
94
94
  <span class="status-badge" data-status="<%= todo.status %>">
95
95
  <%= todo.status %>
96
96
  </span>
97
97
  </td>
98
- <td><%= todo.created %></td>
98
+ <td><%= todo.created ? new Date(todo.created).toISOString().slice(0, 10) : '—' %></td>
99
99
  </tr>
100
100
  <% }); %>
101
101
  </tbody>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sienklogic/plan-build-run",
3
- "version": "2.26.1",
3
+ "version": "2.27.0",
4
4
  "description": "Plan it, Build it, Run it — structured development workflow for Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pbr",
3
3
  "displayName": "Plan-Build-Run",
4
- "version": "2.26.1",
4
+ "version": "2.27.0",
5
5
  "description": "Plan-Build-Run — Structured development workflow for GitHub Copilot CLI. Solves context rot through disciplined agent delegation, structured planning, atomic execution, and goal-backward verification.",
6
6
  "author": {
7
7
  "name": "SienkLogic",
@@ -242,6 +242,18 @@ For each wave, in order (Wave 1, then Wave 2, etc.):
242
242
 
243
243
  For each plan in the current wave (excluding skipped plans):
244
244
 
245
+ **Local LLM plan quality check (optional, advisory):**
246
+
247
+ Before spawning executors for this wave, if `config.local_llm.enabled` is `true`, run a quick classification on each plan to catch stubs before wasting an executor spawn:
248
+
249
+ ```bash
250
+ node ${PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/phases/{NN}-{slug}/{plan_id}-PLAN.md"
251
+ ```
252
+
253
+ - If classification is `"stub"` or `"partial"` with confidence >= 0.7: warn the user before spawning: `"⚠ Plan {plan_id} classified as {classification} (confidence {conf}) — consider refining before building."`
254
+ - If the command fails or returns null: skip silently (local LLM unavailable — not an error)
255
+ - This is advisory only — never block on the result
256
+
245
257
  **Present plan narrative before spawning:**
246
258
 
247
259
  Display to the user before spawning:
@@ -147,6 +147,18 @@ Before proceeding to Step 7, confirm these exist on disk:
147
147
 
148
148
  If either check fails, you have skipped steps. Go back and complete Steps 4-6. Do NOT proceed to spawning an executor.
149
149
 
150
+ ### Step 6b: Local LLM Task Validation (optional, advisory)
151
+
152
+ If `config.local_llm.enabled` is `true`, run a quick scope validation before spawning:
153
+
154
+ ```bash
155
+ node ${PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/quick/{NNN}-{slug}/PLAN.md"
156
+ ```
157
+
158
+ - If classification is `"stub"` with confidence >= 0.7: warn `"⚠ Plan looks like a stub — executor may struggle. Consider adding more detail to task descriptions."`
159
+ - If the command fails or returns null: skip silently (local LLM unavailable)
160
+ - This is advisory only — never block on the result
161
+
150
162
  ### Step 7: Spawn Executor
151
163
 
152
164
  **Pre-spawn check** — Verify `.planning/quick/{NNN}-{slug}/PLAN.md` exists and contains at least one `<task>` block. If missing, STOP and complete Steps 4-6 first.
@@ -182,6 +182,20 @@ Then display the overall verdict (`PASSED`, `GAPS FOUND`, or `HUMAN NEEDED`) bef
182
182
 
183
183
  ---
184
184
 
185
+ ### Step 3b: Local LLM Verification Quality Check (optional, advisory)
186
+
187
+ After the verifier completes and writes VERIFICATION.md, if `config.local_llm.enabled` is `true`, run a quality classification:
188
+
189
+ ```bash
190
+ node ${PLUGIN_ROOT}/scripts/pbr-tools.js llm classify SUMMARY ".planning/phases/{NN}-{slug}/VERIFICATION.md"
191
+ ```
192
+
193
+ - If classification is `"thin"` with confidence >= 0.7: warn `"⚠ Verification report appears thin on details — UAT may not catch all gaps. Consider re-running with /pbr:review {N}."`
194
+ - If the command fails or returns null: skip silently (local LLM unavailable)
195
+ - This is advisory only — never block on the result
196
+
197
+ ---
198
+
185
199
  ### Step 4: Present Verification Results (inline)
186
200
 
187
201
  Read the VERIFICATION.md frontmatter. Check the `attempt` counter.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pbr",
3
3
  "displayName": "Plan-Build-Run",
4
- "version": "2.26.1",
4
+ "version": "2.27.0",
5
5
  "description": "Plan-Build-Run — Structured development workflow for Cursor. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
6
6
  "author": {
7
7
  "name": "SienkLogic",
@@ -113,6 +113,26 @@ Plan-Build-Run stores all state in a `.planning/` directory at your project root
113
113
 
114
114
  Run `/pbr:config` to interactively adjust settings like depth, model profiles, and gate behavior.
115
115
 
116
+ ## Hook Compatibility
117
+
118
+ Cursor's plugin hooks.json is configured with all 14 hook events matching Claude Code. However, **whether Cursor IDE actually fires these hooks is unverified** — no integration testing has confirmed hook execution in real Cursor sessions.
119
+
120
+ **If hooks DO fire**, Cursor gets the full hook experience identical to Claude Code:
121
+ - Commit format enforcement (PreToolUse)
122
+ - PLAN/SUMMARY quality classification via local LLM (PostToolUse)
123
+ - Test failure triage (PostToolUse)
124
+ - Context budget tracking (PostToolUse)
125
+ - Auto-continue between skills (Stop)
126
+
127
+ **If hooks do NOT fire**, the following are unavailable:
128
+ - Commit format enforcement — commits won't be validated automatically
129
+ - Automatic local LLM classification on writes — but skills include explicit `pbr-tools.js llm` fallback calls for plan quality (build Step 6a), task validation (quick Step 6b), and verification quality (review Step 3b)
130
+ - Context budget tracking — no automatic warnings when context is filling up
131
+ - Auto-continue — you must manually run the next command
132
+ - Subagent lifecycle logging — agent spawn/completion events aren't tracked
133
+
134
+ **Local LLM via CLI (always works):** Regardless of hook support, skills and agents can call `pbr-tools.js llm` commands directly via Bash. The `/pbr:status` skill displays local LLM metrics, and agents (debugger, researcher, synthesizer) use CLI commands for error classification, source scoring, and summarization.
135
+
116
136
  ## Cross-Plugin Compatibility
117
137
 
118
138
  This plugin works alongside the Claude Code version of Plan-Build-Run. Both plugins share the same `.planning/` directory and file formats, so you can switch between Cursor and Claude Code without losing state. Hook scripts under `plugins/pbr/scripts/` are shared between both plugins via relative paths.
@@ -243,6 +243,18 @@ For each wave, in order (Wave 1, then Wave 2, etc.):
243
243
 
244
244
  For each plan in the current wave (excluding skipped plans):
245
245
 
246
+ **Local LLM plan quality check (optional, advisory):**
247
+
248
+ Before spawning executors for this wave, if `config.local_llm.enabled` is `true`, run a quick classification on each plan to catch stubs before wasting an executor spawn:
249
+
250
+ ```bash
251
+ node ${PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/phases/{NN}-{slug}/{plan_id}-PLAN.md"
252
+ ```
253
+
254
+ - If classification is `"stub"` or `"partial"` with confidence >= 0.7: warn the user before spawning: `"⚠ Plan {plan_id} classified as {classification} (confidence {conf}) — consider refining before building."`
255
+ - If the command fails or returns null: skip silently (local LLM unavailable — not an error)
256
+ - This is advisory only — never block on the result
257
+
246
258
  **Present plan narrative before spawning:**
247
259
 
248
260
  Display to the user before spawning:
@@ -147,6 +147,18 @@ Before proceeding to Step 7, confirm these exist on disk:
147
147
 
148
148
  If either check fails, you have skipped steps. Go back and complete Steps 4-6. Do NOT proceed to spawning an executor.
149
149
 
150
+ ### Step 6b: Local LLM Task Validation (optional, advisory)
151
+
152
+ If `config.local_llm.enabled` is `true`, run a quick scope validation before spawning:
153
+
154
+ ```bash
155
+ node ${PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/quick/{NNN}-{slug}/PLAN.md"
156
+ ```
157
+
158
+ - If classification is `"stub"` with confidence >= 0.7: warn `"⚠ Plan looks like a stub — executor may struggle. Consider adding more detail to task descriptions."`
159
+ - If the command fails or returns null: skip silently (local LLM unavailable)
160
+ - This is advisory only — never block on the result
161
+
150
162
  ### Step 7: Spawn Executor
151
163
 
152
164
  **Pre-spawn check** — Verify `.planning/quick/{NNN}-{slug}/PLAN.md` exists and contains at least one `<task>` block. If missing, STOP and complete Steps 4-6 first.
@@ -183,6 +183,20 @@ Then display the overall verdict (`PASSED`, `GAPS FOUND`, or `HUMAN NEEDED`) bef
183
183
 
184
184
  ---
185
185
 
186
+ ### Step 3b: Local LLM Verification Quality Check (optional, advisory)
187
+
188
+ After the verifier completes and writes VERIFICATION.md, if `config.local_llm.enabled` is `true`, run a quality classification:
189
+
190
+ ```bash
191
+ node ${PLUGIN_ROOT}/scripts/pbr-tools.js llm classify SUMMARY ".planning/phases/{NN}-{slug}/VERIFICATION.md"
192
+ ```
193
+
194
+ - If classification is `"thin"` with confidence >= 0.7: warn `"⚠ Verification report appears thin on details — UAT may not catch all gaps. Consider re-running with /pbr:review {N}."`
195
+ - If the command fails or returns null: skip silently (local LLM unavailable)
196
+ - This is advisory only — never block on the result
197
+
198
+ ---
199
+
186
200
  ### Step 4: Present Verification Results (inline)
187
201
 
188
202
  Read the VERIFICATION.md frontmatter. Check the `attempt` counter.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pbr",
3
- "version": "2.26.1",
3
+ "version": "2.27.0",
4
4
  "description": "Plan-Build-Run — Structured development workflow for Claude Code. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
5
5
  "author": {
6
6
  "name": "SienkLogic",
@@ -244,6 +244,18 @@ For each wave, in order (Wave 1, then Wave 2, etc.):
244
244
 
245
245
  For each plan in the current wave (excluding skipped plans):
246
246
 
247
+ **Local LLM plan quality check (optional, advisory):**
248
+
249
+ Before spawning executors for this wave, if `config.local_llm.enabled` is `true`, run a quick classification on each plan to catch stubs before wasting an executor spawn:
250
+
251
+ ```bash
252
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/phases/{NN}-{slug}/{plan_id}-PLAN.md"
253
+ ```
254
+
255
+ - If classification is `"stub"` or `"partial"` with confidence >= 0.7: warn the user before spawning: `"⚠ Plan {plan_id} classified as {classification} (confidence {conf}) — consider refining before building."`
256
+ - If the command fails or returns null: skip silently (local LLM unavailable — not an error)
257
+ - This is advisory only — never block on the result
258
+
247
259
  **Present plan narrative before spawning:**
248
260
 
249
261
  Display to the user before spawning:
@@ -148,6 +148,18 @@ Before proceeding to Step 7, confirm these exist on disk:
148
148
 
149
149
  If either check fails, you have skipped steps. Go back and complete Steps 4-6. Do NOT proceed to spawning an executor.
150
150
 
151
+ ### Step 6b: Local LLM Task Validation (optional, advisory)
152
+
153
+ If `config.local_llm.enabled` is `true`, run a quick scope validation before spawning:
154
+
155
+ ```bash
156
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm classify PLAN ".planning/quick/{NNN}-{slug}/PLAN.md"
157
+ ```
158
+
159
+ - If classification is `"stub"` with confidence >= 0.7: warn `"⚠ Plan looks like a stub — executor may struggle. Consider adding more detail to task descriptions."`
160
+ - If the command fails or returns null: skip silently (local LLM unavailable)
161
+ - This is advisory only — never block on the result
162
+
151
163
  ### Step 7: Spawn Executor
152
164
 
153
165
  **Pre-spawn check** — Verify `.planning/quick/{NNN}-{slug}/PLAN.md` exists and contains at least one `<task>` block. If missing, STOP and complete Steps 4-6 first.
@@ -202,6 +202,20 @@ Then display the overall verdict (`PASSED`, `GAPS FOUND`, or `HUMAN NEEDED`) bef
202
202
 
203
203
  ---
204
204
 
205
+ ### Step 3b: Local LLM Verification Quality Check (optional, advisory)
206
+
207
+ After the verifier completes and writes VERIFICATION.md, if `config.local_llm.enabled` is `true`, run a quality classification:
208
+
209
+ ```bash
210
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm classify SUMMARY ".planning/phases/{NN}-{slug}/VERIFICATION.md"
211
+ ```
212
+
213
+ - If classification is `"thin"` with confidence >= 0.7: warn `"⚠ Verification report appears thin on details — UAT may not catch all gaps. Consider re-running with /pbr:review {N}."`
214
+ - If the command fails or returns null: skip silently (local LLM unavailable)
215
+ - This is advisory only — never block on the result
216
+
217
+ ---
218
+
205
219
  ### Step 4: Present Verification Results (inline)
206
220
 
207
221
  Read the VERIFICATION.md frontmatter. Check the `attempt` counter.