@sienklogic/plan-build-run 2.24.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +62 -13
  3. package/dashboard/package.json +1 -1
  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
@@ -1,49 +1,84 @@
1
1
  <%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
2
2
  <h1>Phase <%= phaseId %>: <%= phaseName %></h1>
3
3
 
4
- <p><a href="/phases" hx-get="/phases" hx-target="#main-content" hx-push-url="true">&larr; Back to Phases</a></p>
4
+ <nav class="phase-nav" aria-label="Phase navigation">
5
+ <% if (typeof prevPhase !== 'undefined' && prevPhase) { %>
6
+ <a href="/phases/<%= String(prevPhase.id).padStart(2, '0') %>"
7
+ hx-get="/phases/<%= String(prevPhase.id).padStart(2, '0') %>"
8
+ hx-target="#main-content"
9
+ hx-push-url="true">
10
+ &larr; Phase <%= String(prevPhase.id).padStart(2, '0') %>: <%= prevPhase.name %>
11
+ </a>
12
+ <% } else { %>
13
+ <span></span>
14
+ <% } %>
15
+
16
+ <a href="/phases"
17
+ hx-get="/phases"
18
+ hx-target="#main-content"
19
+ hx-push-url="true">All Phases</a>
20
+
21
+ <% if (typeof nextPhase !== 'undefined' && nextPhase) { %>
22
+ <a href="/phases/<%= String(nextPhase.id).padStart(2, '0') %>"
23
+ hx-get="/phases/<%= String(nextPhase.id).padStart(2, '0') %>"
24
+ hx-target="#main-content"
25
+ hx-push-url="true">
26
+ Phase <%= String(nextPhase.id).padStart(2, '0') %>: <%= nextPhase.name %> &rarr;
27
+ </a>
28
+ <% } else { %>
29
+ <span></span>
30
+ <% } %>
31
+ </nav>
5
32
 
6
33
  <% if (verification) { %>
7
- <!-- Verification Summary Card -->
8
- <article>
9
- <header>
34
+ <%
35
+ var vStatus = verification.result || verification.status || verification.verdict || 'unknown';
36
+ var verificationCssStatus = 'not-started';
37
+ if (vStatus === 'pass' || vStatus === 'passed') verificationCssStatus = 'complete';
38
+ else if (vStatus === 'fail' || vStatus === 'failed') verificationCssStatus = 'blocked';
39
+ else if (vStatus === 'partial') verificationCssStatus = 'in-progress';
40
+ %>
41
+ <div class="card">
42
+ <div class="card__header">
10
43
  <strong>Verification Status</strong>
11
- </header>
12
- <%
13
- // Map verification status to CSS status-badge data-status values
14
- var vStatus = verification.result || verification.status || verification.verdict || 'unknown';
15
- var verificationCssStatus = 'not-started';
16
- if (vStatus === 'pass' || vStatus === 'passed') verificationCssStatus = 'complete';
17
- else if (vStatus === 'fail' || vStatus === 'failed') verificationCssStatus = 'blocked';
18
- else if (vStatus === 'partial') verificationCssStatus = 'in-progress';
19
- %>
20
- <p>
21
- <span class="status-badge" data-status="<%= verificationCssStatus %>">
22
- <%= vStatus.toUpperCase() %>
44
+ <span style="float:right;">
45
+ <span class="status-badge" data-status="<%= verificationCssStatus %>"><%= vStatus.toUpperCase() %></span>
23
46
  </span>
47
+ </div>
48
+ <div class="card__body">
24
49
  <% if (verification.verified) { %>
25
- &nbsp; Verified: <%= new Date(verification.verified).toLocaleString() %>
50
+ <p style="margin:0 0 var(--space-sm);">Verified: <%= new Date(verification.verified).toLocaleString() %></p>
26
51
  <% } %>
27
- </p>
28
- <% if (verification.score) { %>
29
- <p>
30
- Score: <%= verification.score.verified %>/<%= verification.score.total_must_haves %> must-haves verified
31
- </p>
32
- <% if (verification.score.failed > 0) { %>
33
- <p><strong><%= verification.score.failed %> must-haves FAILED</strong></p>
52
+ <% if (verification.score) { %>
53
+ <p style="margin:0 0 var(--space-sm);">
54
+ Score: <%= verification.score.verified %>/<%= verification.score.total_must_haves %> must-haves verified
55
+ <% if (verification.score.failed > 0) { %>
56
+ &nbsp; <strong><%= verification.score.failed %> FAILED</strong>
57
+ <% } %>
58
+ </p>
34
59
  <% } %>
35
- <% } %>
36
- <% if (verification.gaps && verification.gaps.length > 0) { %>
37
- <details>
38
- <summary>Verification Gaps (<%= verification.gaps.length %>)</summary>
39
- <ul>
40
- <% verification.gaps.forEach(function(gap) { %>
41
- <li><%= gap %></li>
42
- <% }); %>
43
- </ul>
44
- </details>
45
- <% } %>
46
- </article>
60
+ <% if (verification.mustHaves && verification.mustHaves.length > 0) { %>
61
+ <details>
62
+ <summary>Must-Haves (<%= verification.mustHaves.length %>)</summary>
63
+ <ul style="list-style:none;padding:0;margin:var(--space-sm) 0 0;">
64
+ <% verification.mustHaves.forEach(function(mh) { %>
65
+ <li style="display:flex;gap:var(--space-xs);align-items:flex-start;padding:var(--space-xs) 0;border-bottom:1px solid var(--color-border);">
66
+ <span class="status-badge status-badge--sm" data-status="<%= mh.passed ? 'complete' : 'blocked' %>">
67
+ <%= mh.passed ? 'PASS' : 'FAIL' %>
68
+ </span>
69
+ <span style="flex:1;font-size:0.875rem;"><%= mh.text %></span>
70
+ </li>
71
+ <% }); %>
72
+ </ul>
73
+ </details>
74
+ <% } else if (verification.gaps && verification.gaps.length > 0) { %>
75
+ <details>
76
+ <summary>Verification Gaps (<%= verification.gaps.length %>)</summary>
77
+ <ul><% verification.gaps.forEach(function(gap) { %><li><%= gap %></li><% }); %></ul>
78
+ </details>
79
+ <% } %>
80
+ </div>
81
+ </div>
47
82
  <% } %>
48
83
 
49
84
  <% if (plans.length === 0) { %>
@@ -57,94 +92,92 @@
57
92
 
58
93
  <!-- Plan Cards -->
59
94
  <% plans.forEach(function(plan) { %>
60
- <article>
61
- <header>
62
- <strong>Plan <%= plan.planId %></strong>
63
- <% if (plan.summary && plan.summary.status) { %>
64
- &nbsp;
65
- <span class="status-badge" data-status="<%= plan.summary.status %>">
66
- <%= plan.summary.status.replace('-', ' ') %>
67
- </span>
68
- <% } %>
69
- </header>
95
+ <%
96
+ var planStatus = (plan.summary && plan.summary.status) ? plan.summary.status : 'planned';
97
+ var planCssStatus = planStatus === 'built' ? 'complete'
98
+ : planStatus === 'building' ? 'in-progress'
99
+ : planStatus === 'blocked' ? 'blocked'
100
+ : 'not-started';
101
+ var wave = (plan.summary && plan.summary.wave) ? plan.summary.wave : null;
102
+ var taskCount = plan.taskCount || 0;
103
+ var planTitle = plan.planTitle || ('Plan ' + plan.planId);
104
+ %>
105
+ <div class="card">
106
+ <div class="card__header">
107
+ <span><strong><%= planTitle %></strong></span>
108
+ <span style="display:inline-flex;gap:var(--space-xs);align-items:center;float:right;">
109
+ <% if (wave) { %>
110
+ <span class="status-badge status-badge--sm" data-status="not-started" title="Wave <%= wave %>">W<%= wave %></span>
111
+ <% } %>
112
+ <% if (taskCount > 0) { %>
113
+ <span class="status-badge status-badge--sm" data-status="not-started"><%= taskCount %> task<%= taskCount !== 1 ? 's' : '' %></span>
114
+ <% } %>
115
+ <span class="status-badge" data-status="<%= planCssStatus %>"><%= planStatus.replace(/-/g, ' ').toUpperCase() %></span>
116
+ </span>
117
+ </div>
118
+ <div class="card__body">
119
+ <p style="margin:0 0 var(--space-sm);">
120
+ <a href="/phases/<%= phaseId %>/<%= plan.planId %>/plan"
121
+ hx-get="/phases/<%= phaseId %>/<%= plan.planId %>/plan"
122
+ hx-target="#main-content" hx-push-url="true">View Plan</a>
123
+ <% if (plan.summary) { %>
124
+ &nbsp;|&nbsp;
125
+ <a href="/phases/<%= phaseId %>/<%= plan.planId %>/summary"
126
+ hx-get="/phases/<%= phaseId %>/<%= plan.planId %>/summary"
127
+ hx-target="#main-content" hx-push-url="true">View Summary</a>
128
+ <% } %>
129
+ <% if (verification) { %>
130
+ &nbsp;|&nbsp;
131
+ <a href="/phases/<%= phaseId %>/<%= plan.planId %>/verification"
132
+ hx-get="/phases/<%= phaseId %>/<%= plan.planId %>/verification"
133
+ hx-target="#main-content" hx-push-url="true">View Verification</a>
134
+ <% } %>
135
+ </p>
70
136
 
71
- <p>
72
- <a href="/phases/<%= phaseId %>/<%= plan.planId %>/plan">View Plan</a>
73
137
  <% if (plan.summary) { %>
74
- &nbsp;|&nbsp; <a href="/phases/<%= phaseId %>/<%= plan.planId %>/summary">View Summary</a>
75
- <% } %>
76
- <% if (verification) { %>
77
- &nbsp;|&nbsp; <a href="/phases/<%= phaseId %>/<%= plan.planId %>/verification">View Verification</a>
78
- <% } %>
79
- </p>
80
-
81
- <% if (plan.summary) { %>
82
-
83
- <% if (plan.summary.subsystem) { %>
84
- <p><strong>Subsystem:</strong> <%= plan.summary.subsystem %></p>
138
+ <% if (plan.summary.subsystem) { %>
139
+ <p><strong>Subsystem:</strong> <%= plan.summary.subsystem %></p>
140
+ <% } %>
141
+ <% if (plan.summary.key_decisions && plan.summary.key_decisions.length > 0) { %>
142
+ <details>
143
+ <summary>Key Decisions (<%= plan.summary.key_decisions.length %>)</summary>
144
+ <ul><% plan.summary.key_decisions.forEach(function(d) { %><li><%= d %></li><% }); %></ul>
145
+ </details>
146
+ <% } %>
147
+ <% if (plan.summary.key_files && plan.summary.key_files.length > 0) { %>
148
+ <details>
149
+ <summary>Key Files (<%= plan.summary.key_files.length %>)</summary>
150
+ <ul><% plan.summary.key_files.forEach(function(f) { %><li><code><%= f %></code></li><% }); %></ul>
151
+ </details>
152
+ <% } %>
153
+ <% if (plan.summary.deferred && plan.summary.deferred.length > 0) { %>
154
+ <details>
155
+ <summary>Deferred Items (<%= plan.summary.deferred.length %>)</summary>
156
+ <ul><% plan.summary.deferred.forEach(function(item) { %><li><%= item %></li><% }); %></ul>
157
+ </details>
158
+ <% } %>
159
+ <% if (plan.summary.metrics) { %>
160
+ <p style="margin-top:var(--space-sm);">
161
+ <small>
162
+ <% if (plan.summary.metrics.duration_minutes != null) { %>Duration: <%= plan.summary.metrics.duration_minutes %> min<% } %>
163
+ <% if (plan.summary.metrics.commits != null) { %> | Commits: <%= plan.summary.metrics.commits %><% } %>
164
+ <% if (plan.summary.metrics.files_created != null || plan.summary.metrics.files_modified != null) { %>
165
+ | Files: <%= plan.summary.metrics.files_created || 0 %> created, <%= plan.summary.metrics.files_modified || 0 %> modified
166
+ <% } %>
167
+ </small>
168
+ </p>
169
+ <% } %>
170
+ <% } else { %>
171
+ <p><em>Plan file exists but has not been executed yet. No summary available.</em></p>
85
172
  <% } %>
86
-
87
- <% if (plan.summary.key_decisions && plan.summary.key_decisions.length > 0) { %>
88
- <details>
89
- <summary>Key Decisions (<%= plan.summary.key_decisions.length %>)</summary>
90
- <ul>
91
- <% plan.summary.key_decisions.forEach(function(decision) { %>
92
- <li><%= decision %></li>
93
- <% }); %>
94
- </ul>
95
- </details>
96
- <% } %>
97
-
98
- <% if (plan.summary.key_files && plan.summary.key_files.length > 0) { %>
99
- <details>
100
- <summary>Key Files (<%= plan.summary.key_files.length %>)</summary>
101
- <ul>
102
- <% plan.summary.key_files.forEach(function(file) { %>
103
- <li><code><%= file %></code></li>
104
- <% }); %>
105
- </ul>
106
- </details>
107
- <% } %>
108
-
109
- <% if (plan.summary.deferred && plan.summary.deferred.length > 0) { %>
110
- <details>
111
- <summary>Deferred Items (<%= plan.summary.deferred.length %>)</summary>
112
- <ul>
113
- <% plan.summary.deferred.forEach(function(item) { %>
114
- <li><%= item %></li>
115
- <% }); %>
116
- </ul>
117
- </details>
118
- <% } %>
119
-
120
- <% if (plan.summary.metrics) { %>
121
- <p>
122
- <small>
123
- <% if (plan.summary.metrics.duration_minutes != null) { %>
124
- Duration: <%= plan.summary.metrics.duration_minutes %> min
125
- <% } %>
126
- <% if (plan.summary.metrics.commits != null) { %>
127
- | Commits: <%= plan.summary.metrics.commits %>
128
- <% } %>
129
- <% if (plan.summary.metrics.files_created != null || plan.summary.metrics.files_modified != null) { %>
130
- | Files: <%= plan.summary.metrics.files_created || 0 %> created, <%= plan.summary.metrics.files_modified || 0 %> modified
131
- <% } %>
132
- </small>
133
- </p>
134
- <% } %>
135
-
136
- <% } else { %>
137
- <!-- Plan exists but not executed yet -->
138
- <p><em>Plan file exists but has not been executed yet. No summary available.</em></p>
139
- <% } %>
140
- </article>
173
+ </div>
174
+ </div>
141
175
  <% }); %>
142
176
 
143
177
  <% } %>
144
178
 
145
179
  <!-- Commit History Section -->
146
180
  <%
147
- // Collect all commits across all plans, tagged with their planId
148
181
  var allCommits = [];
149
182
  plans.forEach(function(plan) {
150
183
  if (plan.commits && plan.commits.length > 0) {
@@ -153,41 +186,43 @@
153
186
  });
154
187
  }
155
188
  });
189
+
190
+ // Extract commit type from task string: "NN-NN-T1: feat(scope): msg" -> "feat"
191
+ function commitType(entry) {
192
+ var m = entry.commit.task && entry.commit.task.match(/\b(feat|fix|refactor|test|docs|chore|wip|perf)\b/i);
193
+ return m ? m[1].toLowerCase() : 'chore';
194
+ }
195
+
196
+ var typeStatusMap = {
197
+ feat: 'complete', fix: 'in-progress', refactor: 'not-started',
198
+ test: 'not-started', docs: 'not-started', chore: 'not-started',
199
+ wip: 'in-progress', perf: 'complete'
200
+ };
156
201
  %>
157
202
 
158
203
  <h2>Commit History (<%= allCommits.length %>)</h2>
159
204
 
160
205
  <% if (allCommits.length === 0) { %>
161
- <article>
162
- <p><em>No commits yet for this phase.</em></p>
163
- </article>
206
+ <div class="card"><div class="card__body"><p><em>No commits yet for this phase.</em></p></div></div>
164
207
  <% } else { %>
165
- <div class="table-wrap">
166
- <table>
167
- <thead>
168
- <tr>
169
- <th>Commit</th>
170
- <th>Task</th>
171
- <th>Plan</th>
172
- <th>Files</th>
173
- <th>Status</th>
174
- </tr>
175
- </thead>
176
- <tbody>
177
- <% allCommits.forEach(function(entry) { %>
178
- <tr>
179
- <td><code><%= entry.commit.hash %></code></td>
180
- <td><%= entry.commit.task %></td>
181
- <td><%= entry.planId %></td>
182
- <td><%= entry.commit.files %></td>
183
- <td>
184
- <span class="status-badge" data-status="<%= entry.commit.verify === 'passed' ? 'complete' : (entry.commit.verify === 'failed' ? 'blocked' : 'in-progress') %>">
185
- <%= entry.commit.verify %>
186
- </span>
187
- </td>
188
- </tr>
189
- <% }); %>
190
- </tbody>
191
- </table>
192
- </div>
208
+ <ol class="commit-timeline">
209
+ <% allCommits.forEach(function(entry) { %>
210
+ <%
211
+ var ctype = commitType(entry);
212
+ var cStatus = typeStatusMap[ctype] || 'not-started';
213
+ var verifyStatus = entry.commit.verify === 'passed' ? 'complete'
214
+ : entry.commit.verify === 'failed' ? 'blocked' : 'in-progress';
215
+ %>
216
+ <li class="commit-timeline__item">
217
+ <span class="status-badge status-badge--sm" data-status="<%= cStatus %>" title="<%= ctype %>"><%= ctype %></span>
218
+ <code class="commit-timeline__hash"><%= entry.commit.hash %></code>
219
+ <span class="commit-timeline__task"><%= entry.commit.task %></span>
220
+ <span class="commit-timeline__meta">
221
+ plan <strong><%= entry.planId %></strong>
222
+ &middot; <%= entry.commit.files %> file<%= entry.commit.files !== 1 ? 's' : '' %>
223
+ &middot; <span class="status-badge status-badge--sm" data-status="<%= verifyStatus %>"><%= entry.commit.verify %></span>
224
+ </span>
225
+ </li>
226
+ <% }); %>
227
+ </ol>
193
228
  <% } %>
@@ -0,0 +1,16 @@
1
+ <% if (!phases || phases.length === 0) { %>
2
+ <p class="muted">No phases found.</p>
3
+ <% } else { %>
4
+ <ol class="phase-timeline">
5
+ <% phases.forEach(function(phase) { %>
6
+ <li class="phase-step" data-status="<%= phase.status %>"<% if (currentPhase && phase.id === currentPhase.id) { %> aria-current="step"<% } %>>
7
+ <span class="step-indicator"></span>
8
+ <a class="step-label"
9
+ href="/phases/<%= String(phase.id).padStart(2, '0') %>"
10
+ hx-get="/phases/<%= String(phase.id).padStart(2, '0') %>"
11
+ hx-target="#main-content"
12
+ hx-push-url="true">Phase <%= phase.id %></a>
13
+ </li>
14
+ <% }); %>
15
+ </ol>
16
+ <% } %>
@@ -0,0 +1,44 @@
1
+ <%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
2
+ <h1>Requirements</h1>
3
+
4
+ <% if (totalCount === 0) { %>
5
+ <%- include('empty-state', { icon: 'R', title: 'No requirements found', action: 'Add a REQUIREMENTS.md file to your .planning/ directory.' }) %>
6
+ <% } else { %>
7
+ <p class="requirements-summary">
8
+ <strong><%= coveredCount %> / <%= totalCount %></strong> requirements covered by plans.
9
+ <% if (uncoveredCount > 0) { %>
10
+ <span class="status-badge" data-status="warning"><%= uncoveredCount %> uncovered</span>
11
+ <% } else { %>
12
+ <span class="status-badge" data-status="verified">All covered</span>
13
+ <% } %>
14
+ </p>
15
+
16
+ <% sections.forEach(function(section) { %>
17
+ <section class="requirements-section">
18
+ <h2><%= section.sectionTitle %></h2>
19
+ <% section.requirements.forEach(function(req) { %>
20
+ <article class="card requirements-card" data-covered="<%= req.covered %>">
21
+ <header class="card__header">
22
+ <code class="req-id"><%= req.id %></code>
23
+ <% if (req.covered) { %>
24
+ <span class="status-badge" data-status="verified">covered</span>
25
+ <% } else { %>
26
+ <span class="status-badge" data-status="warning">uncovered</span>
27
+ <% } %>
28
+ </header>
29
+ <div class="card__body">
30
+ <p><%= req.text %></p>
31
+ <% if (req.planRefs.length > 0) { %>
32
+ <p class="req-plan-refs">
33
+ <small>Plans: </small>
34
+ <% req.planRefs.forEach(function(planId) { %>
35
+ <code class="req-plan-ref"><%= planId %></code>
36
+ <% }); %>
37
+ </p>
38
+ <% } %>
39
+ </div>
40
+ </article>
41
+ <% }); %>
42
+ </section>
43
+ <% }); %>
44
+ <% } %>
@@ -0,0 +1,49 @@
1
+ <%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
2
+ <h1>Research</h1>
3
+
4
+ <h2>Research Documents</h2>
5
+ <% if (researchDocs.length === 0) { %>
6
+ <%- include('empty-state', { icon: 'R', title: 'No research documents', action: 'Run /pbr:explore or /pbr:scan to generate research.' }) %>
7
+ <% } else { %>
8
+ <% researchDocs.forEach(function(doc) { %>
9
+ <article class="card">
10
+ <header class="card__header">
11
+ <strong>
12
+ <a href="/research/<%= doc.slug %>"
13
+ hx-get="/research/<%= doc.slug %>"
14
+ hx-target="#main-content"
15
+ hx-push-url="true"><%= doc.title %></a>
16
+ </strong>
17
+ <% if (doc.date) { %><small style="float:right"><%= doc.date %></small><% } %>
18
+ </header>
19
+ <div class="card__body">
20
+ <% if (doc.topic) { %><p><strong>Topic:</strong> <%= doc.topic %></p><% } %>
21
+ <% if (doc.confidence || doc.coverage) { %>
22
+ <p>
23
+ <% if (doc.confidence) { %><span class="status-badge" data-status="<%= doc.confidence %>"><%= doc.confidence %></span>&nbsp;<% } %>
24
+ <% if (doc.coverage) { %><small>Coverage: <%= doc.coverage %></small><% } %>
25
+ </p>
26
+ <% } %>
27
+ </div>
28
+ </article>
29
+ <% }); %>
30
+ <% } %>
31
+
32
+ <h2>Codebase Documents</h2>
33
+ <% if (codebaseDocs.length === 0) { %>
34
+ <%- include('empty-state', { icon: 'C', title: 'No codebase documents', action: 'Run /pbr:scan to generate codebase analysis.' }) %>
35
+ <% } else { %>
36
+ <% codebaseDocs.forEach(function(doc) { %>
37
+ <article class="card">
38
+ <header class="card__header">
39
+ <strong>
40
+ <a href="/research/<%= doc.slug %>"
41
+ hx-get="/research/<%= doc.slug %>"
42
+ hx-target="#main-content"
43
+ hx-push-url="true"><%= doc.title %></a>
44
+ </strong>
45
+ <% if (doc.date) { %><small style="float:right"><%= doc.date %></small><% } %>
46
+ </header>
47
+ </article>
48
+ <% }); %>
49
+ <% } %>
@@ -0,0 +1,23 @@
1
+ <%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
2
+ <h1><%= title %></h1>
3
+
4
+ <% if (topic || date || confidence || coverage || sources_checked) { %>
5
+ <aside class="research-meta">
6
+ <% if (topic) { %><p><strong>Topic:</strong> <%= topic %></p><% } %>
7
+ <% if (date) { %><p><strong>Date:</strong> <%= date %></p><% } %>
8
+ <% if (confidence) { %><p><strong>Confidence:</strong> <span class="status-badge" data-status="<%= confidence %>"><%= confidence %></span></p><% } %>
9
+ <% if (coverage) { %><p><strong>Coverage:</strong> <%= coverage %></p><% } %>
10
+ <% if (sources_checked) { %><p><strong>Sources checked:</strong> <%= sources_checked %></p><% } %>
11
+ </aside>
12
+ <% } %>
13
+
14
+ <article class="card">
15
+ <div class="card__body markdown-body">
16
+ <%- html %>
17
+ </div>
18
+ </article>
19
+
20
+ <p><a href="/research"
21
+ hx-get="/research"
22
+ hx-target="#main-content"
23
+ hx-push-url="true">&larr; Back to Research</a></p>
@@ -13,9 +13,16 @@
13
13
  </div>
14
14
  <% } %>
15
15
 
16
+ <% if (typeof currentPhase !== 'undefined' && currentPhase && currentPhase.nextAction) { %>
17
+ <div class="sidebar-next-action">
18
+ <small>Suggested next:</small>
19
+ <code><%= currentPhase.nextAction %></code>
20
+ </div>
21
+ <% } %>
22
+
16
23
  <nav aria-label="Main navigation">
17
24
  <details open>
18
- <summary>Overview</summary>
25
+ <summary>Project</summary>
19
26
  <ul>
20
27
  <li>
21
28
  <a href="/"
@@ -33,6 +40,14 @@
33
40
  Roadmap
34
41
  </a>
35
42
  </li>
43
+ <li>
44
+ <a href="/phases"
45
+ hx-get="/phases"
46
+ hx-target="#main-content"
47
+ hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'phases' ? ' aria-current="page"' : '' %>>
48
+ Phases
49
+ </a>
50
+ </li>
36
51
  <li>
37
52
  <a href="/dependencies"
38
53
  hx-get="/dependencies"
@@ -41,12 +56,34 @@
41
56
  Dependencies
42
57
  </a>
43
58
  </li>
59
+ </ul>
60
+ </details>
61
+
62
+ <details open>
63
+ <summary>Planning</summary>
64
+ <ul>
44
65
  <li>
45
- <a href="/analytics"
46
- hx-get="/analytics"
66
+ <a href="/config"
67
+ hx-get="/config"
47
68
  hx-target="#main-content"
48
- hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'analytics' ? ' aria-current="page"' : '' %>>
49
- Analytics
69
+ hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'config' ? ' aria-current="page"' : '' %>>
70
+ Config
71
+ </a>
72
+ </li>
73
+ <li>
74
+ <a href="/research"
75
+ hx-get="/research"
76
+ hx-target="#main-content"
77
+ hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'research' ? ' aria-current="page"' : '' %>>
78
+ Research
79
+ </a>
80
+ </li>
81
+ <li>
82
+ <a href="/requirements"
83
+ hx-get="/requirements"
84
+ hx-target="#main-content"
85
+ hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'requirements' ? ' aria-current="page"' : '' %>>
86
+ Requirements
50
87
  </a>
51
88
  </li>
52
89
  </ul>
@@ -55,14 +92,6 @@
55
92
  <details open>
56
93
  <summary>Work</summary>
57
94
  <ul>
58
- <li>
59
- <a href="/phases"
60
- hx-get="/phases"
61
- hx-target="#main-content"
62
- hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'phases' ? ' aria-current="page"' : '' %>>
63
- Phases
64
- </a>
65
- </li>
66
95
  <li>
67
96
  <a href="/todos"
68
97
  hx-get="/todos"
@@ -71,14 +100,6 @@
71
100
  Todos
72
101
  </a>
73
102
  </li>
74
- <li>
75
- <a href="/notes"
76
- hx-get="/notes"
77
- hx-target="#main-content"
78
- hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'notes' ? ' aria-current="page"' : '' %>>
79
- Notes
80
- </a>
81
- </li>
82
103
  <li>
83
104
  <a href="/quick"
84
105
  hx-get="/quick"
@@ -88,17 +109,17 @@
88
109
  </a>
89
110
  </li>
90
111
  <li>
91
- <a href="/audits"
92
- hx-get="/audits"
112
+ <a href="/notes"
113
+ hx-get="/notes"
93
114
  hx-target="#main-content"
94
- hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'audits' ? ' aria-current="page"' : '' %>>
95
- Audit Reports
115
+ hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'notes' ? ' aria-current="page"' : '' %>>
116
+ Notes
96
117
  </a>
97
118
  </li>
98
119
  </ul>
99
120
  </details>
100
121
 
101
- <details open>
122
+ <details>
102
123
  <summary>History</summary>
103
124
  <ul>
104
125
  <li>
@@ -109,6 +130,22 @@
109
130
  Milestones
110
131
  </a>
111
132
  </li>
133
+ <li>
134
+ <a href="/analytics"
135
+ hx-get="/analytics"
136
+ hx-target="#main-content"
137
+ hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'analytics' ? ' aria-current="page"' : '' %>>
138
+ Analytics
139
+ </a>
140
+ </li>
141
+ <li>
142
+ <a href="/audits"
143
+ hx-get="/audits"
144
+ hx-target="#main-content"
145
+ hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'audits' ? ' aria-current="page"' : '' %>>
146
+ Audit Reports
147
+ </a>
148
+ </li>
112
149
  </ul>
113
150
  </details>
114
151
  </nav>