@sienklogic/plan-build-run 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/public/css/layout.css +66 -0
- package/dashboard/src/repositories/planning.repository.js +2 -0
- package/dashboard/src/routes/pages.routes.js +56 -0
- package/dashboard/src/services/milestone.service.js +103 -0
- package/dashboard/src/views/milestone-detail.ejs +5 -0
- package/dashboard/src/views/milestones.ejs +5 -0
- package/dashboard/src/views/partials/head.ejs +10 -4
- package/dashboard/src/views/partials/milestone-detail-content.ejs +19 -0
- package/dashboard/src/views/partials/milestones-content.ejs +44 -0
- package/dashboard/src/views/partials/sidebar.ejs +8 -0
- package/package.json +1 -1
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/hooks/hooks.json +10 -0
- package/plugins/cursor-pbr/skills/dashboard/SKILL.md +22 -0
- package/plugins/cursor-pbr/skills/do/SKILL.md +67 -0
- package/plugins/cursor-pbr/skills/plan/SKILL.md +4 -2
- package/plugins/cursor-pbr/skills/todo/SKILL.md +65 -3
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/commands/dashboard.md +5 -0
- package/plugins/pbr/hooks/hooks.json +10 -0
- package/plugins/pbr/scripts/config-schema.json +8 -0
- package/plugins/pbr/scripts/progress-tracker.js +46 -1
- package/plugins/pbr/scripts/validate-skill-args.js +150 -0
- package/plugins/pbr/skills/dashboard/SKILL.md +34 -0
- package/plugins/pbr/skills/do/SKILL.md +70 -0
- package/plugins/pbr/skills/plan/SKILL.md +4 -2
- package/plugins/pbr/skills/todo/SKILL.md +66 -4
|
@@ -267,6 +267,72 @@ details li {
|
|
|
267
267
|
background: rgba(255, 255, 255, 0.06);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
/* --- Markdown Body (rendered markdown content) --- */
|
|
271
|
+
.markdown-body h1 { font-size: 1.5rem; margin-top: var(--space-xl); margin-bottom: var(--space-sm); }
|
|
272
|
+
.markdown-body h2 { font-size: 1.25rem; margin-top: var(--space-xl); margin-bottom: var(--space-sm); }
|
|
273
|
+
.markdown-body h3 { font-size: 1.05rem; margin-top: var(--space-lg); margin-bottom: var(--space-xs); }
|
|
274
|
+
|
|
275
|
+
.markdown-body table {
|
|
276
|
+
border-collapse: collapse;
|
|
277
|
+
width: 100%;
|
|
278
|
+
margin: var(--space-md) 0;
|
|
279
|
+
font-size: 0.875rem;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.markdown-body th,
|
|
283
|
+
.markdown-body td {
|
|
284
|
+
border: 1px solid var(--border-subtle);
|
|
285
|
+
padding: var(--space-xs) var(--space-sm);
|
|
286
|
+
text-align: left;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.markdown-body th {
|
|
290
|
+
background: rgba(255, 255, 255, 0.04);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.markdown-body pre {
|
|
294
|
+
background: rgba(0, 0, 0, 0.3);
|
|
295
|
+
border-radius: var(--radius-sm);
|
|
296
|
+
padding: var(--space-md);
|
|
297
|
+
overflow-x: auto;
|
|
298
|
+
margin: var(--space-md) 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.markdown-body pre code {
|
|
302
|
+
background: none;
|
|
303
|
+
padding: 0;
|
|
304
|
+
font-size: 0.85em;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.markdown-body blockquote {
|
|
308
|
+
border-left: 3px solid var(--pico-primary);
|
|
309
|
+
background: rgba(255, 255, 255, 0.02);
|
|
310
|
+
margin: var(--space-md) 0;
|
|
311
|
+
padding: var(--space-sm) var(--space-md);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.markdown-body ul.contains-task-list {
|
|
315
|
+
list-style: none;
|
|
316
|
+
padding-left: var(--space-sm);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.markdown-body ul.contains-task-list li {
|
|
320
|
+
position: relative;
|
|
321
|
+
padding-left: var(--space-xs);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.markdown-body img {
|
|
325
|
+
max-width: 100%;
|
|
326
|
+
height: auto;
|
|
327
|
+
border-radius: var(--radius-sm);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.markdown-body hr {
|
|
331
|
+
border: none;
|
|
332
|
+
border-top: 1px solid var(--border-subtle);
|
|
333
|
+
margin: var(--space-lg) 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
270
336
|
/* --- Back Link --- */
|
|
271
337
|
main > p:first-of-type > a[href="/"] {
|
|
272
338
|
font-size: 0.875rem;
|
|
@@ -3,6 +3,8 @@ import { join, resolve, relative, normalize } from 'node:path';
|
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
import { marked } from 'marked';
|
|
5
5
|
|
|
6
|
+
marked.setOptions({ gfm: true, breaks: false });
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Strip UTF-8 BOM (Byte Order Mark) if present.
|
|
8
10
|
* Windows editors (Notepad, older VS Code) may prepend BOM to UTF-8 files.
|
|
@@ -3,6 +3,7 @@ import { getPhaseDetail, getPhaseDocument } from '../services/phase.service.js';
|
|
|
3
3
|
import { getRoadmapData } from '../services/roadmap.service.js';
|
|
4
4
|
import { parseStateFile, derivePhaseStatuses } from '../services/dashboard.service.js';
|
|
5
5
|
import { listPendingTodos, getTodoDetail, createTodo, completeTodo } from '../services/todo.service.js';
|
|
6
|
+
import { getAllMilestones, getMilestoneDetail } from '../services/milestone.service.js';
|
|
6
7
|
|
|
7
8
|
const router = Router();
|
|
8
9
|
|
|
@@ -225,6 +226,61 @@ router.post('/todos/:id/done', async (req, res) => {
|
|
|
225
226
|
}
|
|
226
227
|
});
|
|
227
228
|
|
|
229
|
+
router.get('/milestones', async (req, res) => {
|
|
230
|
+
const projectDir = req.app.locals.projectDir;
|
|
231
|
+
const milestoneData = await getAllMilestones(projectDir);
|
|
232
|
+
|
|
233
|
+
const templateData = {
|
|
234
|
+
title: 'Milestones',
|
|
235
|
+
activePage: 'milestones',
|
|
236
|
+
currentPath: '/milestones',
|
|
237
|
+
...milestoneData
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
res.setHeader('Vary', 'HX-Request');
|
|
241
|
+
|
|
242
|
+
if (req.get('HX-Request') === 'true') {
|
|
243
|
+
res.render('partials/milestones-content', templateData);
|
|
244
|
+
} else {
|
|
245
|
+
res.render('milestones', templateData);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
router.get('/milestones/:version', async (req, res) => {
|
|
250
|
+
const { version } = req.params;
|
|
251
|
+
|
|
252
|
+
// Validate version: alphanumeric with dots and dashes
|
|
253
|
+
if (!/^[\w.-]+$/.test(version)) {
|
|
254
|
+
const err = new Error('Invalid milestone version format');
|
|
255
|
+
err.status = 404;
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const projectDir = req.app.locals.projectDir;
|
|
260
|
+
const detail = await getMilestoneDetail(projectDir, version);
|
|
261
|
+
|
|
262
|
+
if (detail.sections.length === 0) {
|
|
263
|
+
const err = new Error(`No archived files found for milestone v${version}`);
|
|
264
|
+
err.status = 404;
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const templateData = {
|
|
269
|
+
title: `Milestone v${version}`,
|
|
270
|
+
activePage: 'milestones',
|
|
271
|
+
currentPath: '/milestones/' + version,
|
|
272
|
+
...detail
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
res.setHeader('Vary', 'HX-Request');
|
|
276
|
+
|
|
277
|
+
if (req.get('HX-Request') === 'true') {
|
|
278
|
+
res.render('partials/milestone-detail-content', templateData);
|
|
279
|
+
} else {
|
|
280
|
+
res.render('milestone-detail', templateData);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
228
284
|
router.get('/roadmap', async (req, res) => {
|
|
229
285
|
const projectDir = req.app.locals.projectDir;
|
|
230
286
|
const [roadmapData, stateData] = await Promise.all([
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readMarkdownFile } from '../repositories/planning.repository.js';
|
|
4
|
+
import { getRoadmapData } from './roadmap.service.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scan .planning/milestones/ for archived milestone files.
|
|
8
|
+
* Groups files by version prefix (e.g., v1.0-ROADMAP.md, v1.0-STATS.md).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} projectDir - Absolute path to the project root
|
|
11
|
+
* @returns {Promise<Array<{version: string, name: string, date: string, duration: string, files: string[]}>>}
|
|
12
|
+
*/
|
|
13
|
+
export async function listArchivedMilestones(projectDir) {
|
|
14
|
+
const milestonesDir = join(projectDir, '.planning', 'milestones');
|
|
15
|
+
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = await readdir(milestonesDir);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err.code === 'ENOENT') return [];
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Group files by version prefix: v1.0-ROADMAP.md -> version "1.0"
|
|
25
|
+
const versionMap = new Map();
|
|
26
|
+
const versionPattern = /^v([\w.-]+)-(\w+)\.md$/;
|
|
27
|
+
|
|
28
|
+
for (const file of entries) {
|
|
29
|
+
const match = file.match(versionPattern);
|
|
30
|
+
if (!match) continue;
|
|
31
|
+
|
|
32
|
+
const version = match[1];
|
|
33
|
+
if (!versionMap.has(version)) {
|
|
34
|
+
versionMap.set(version, { version, name: '', date: '', duration: '', files: [] });
|
|
35
|
+
}
|
|
36
|
+
versionMap.get(version).files.push(file);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Try to parse STATS.md for each version to get name/date/duration
|
|
40
|
+
for (const [version, milestone] of versionMap) {
|
|
41
|
+
const statsFile = `v${version}-STATS.md`;
|
|
42
|
+
if (milestone.files.includes(statsFile)) {
|
|
43
|
+
try {
|
|
44
|
+
const { frontmatter } = await readMarkdownFile(join(milestonesDir, statsFile));
|
|
45
|
+
milestone.name = frontmatter.milestone || frontmatter.name || `v${version}`;
|
|
46
|
+
milestone.date = frontmatter.completed || frontmatter.date || '';
|
|
47
|
+
milestone.duration = frontmatter.duration || '';
|
|
48
|
+
} catch (_e) {
|
|
49
|
+
milestone.name = `v${version}`;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
milestone.name = `v${version}`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Sort by version descending (newest first)
|
|
57
|
+
return [...versionMap.values()].sort((a, b) => b.version.localeCompare(a.version, undefined, { numeric: true }));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Combine active milestones from ROADMAP.md with archived milestones.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} projectDir - Absolute path to the project root
|
|
64
|
+
* @returns {Promise<{active: Array, archived: Array}>}
|
|
65
|
+
*/
|
|
66
|
+
export async function getAllMilestones(projectDir) {
|
|
67
|
+
const [roadmapData, archived] = await Promise.all([
|
|
68
|
+
getRoadmapData(projectDir),
|
|
69
|
+
listArchivedMilestones(projectDir)
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
active: roadmapData.milestones || [],
|
|
74
|
+
archived
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read all archived files for a specific milestone version.
|
|
80
|
+
* Returns rendered HTML for each file type (ROADMAP, STATS, REQUIREMENTS).
|
|
81
|
+
*
|
|
82
|
+
* @param {string} projectDir - Absolute path to the project root
|
|
83
|
+
* @param {string} version - Milestone version (e.g., "1.0")
|
|
84
|
+
* @returns {Promise<{version: string, sections: Array<{type: string, frontmatter: object, html: string}>}>}
|
|
85
|
+
*/
|
|
86
|
+
export async function getMilestoneDetail(projectDir, version) {
|
|
87
|
+
const milestonesDir = join(projectDir, '.planning', 'milestones');
|
|
88
|
+
const fileTypes = ['ROADMAP', 'STATS', 'REQUIREMENTS'];
|
|
89
|
+
|
|
90
|
+
const sections = [];
|
|
91
|
+
for (const type of fileTypes) {
|
|
92
|
+
const filePath = join(milestonesDir, `v${version}-${type}.md`);
|
|
93
|
+
try {
|
|
94
|
+
const result = await readMarkdownFile(filePath);
|
|
95
|
+
sections.push({ type, frontmatter: result.frontmatter, html: result.html });
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err.code !== 'ENOENT') throw err;
|
|
98
|
+
// File doesn't exist for this type — skip
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { version, sections };
|
|
103
|
+
}
|
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
<title><%= typeof title !== 'undefined' ? title : 'Plan-Build-Run' %> - Plan-Build-Run</title>
|
|
5
5
|
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
|
|
6
6
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.min.css">
|
|
8
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.min.css">
|
|
9
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-700-normal.min.css">
|
|
10
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono@latest/latin-400-normal.min.css">
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.min.css" media="print" onload="this.media='all'">
|
|
8
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.min.css" media="print" onload="this.media='all'">
|
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-700-normal.min.css" media="print" onload="this.media='all'">
|
|
10
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono@latest/latin-400-normal.min.css" media="print" onload="this.media='all'">
|
|
11
|
+
<noscript>
|
|
12
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-400-normal.min.css">
|
|
13
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.min.css">
|
|
14
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-700-normal.min.css">
|
|
15
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/fontsource/fonts/jetbrains-mono@latest/latin-400-normal.min.css">
|
|
16
|
+
</noscript>
|
|
11
17
|
<link rel="stylesheet" href="/css/layout.css">
|
|
12
18
|
<link rel="stylesheet" href="/css/status-colors.css">
|
|
13
19
|
<script
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<h1>Milestone v<%= version %></h1>
|
|
2
|
+
|
|
3
|
+
<p>
|
|
4
|
+
<a href="/milestones"
|
|
5
|
+
hx-get="/milestones"
|
|
6
|
+
hx-target="#main-content"
|
|
7
|
+
hx-push-url="true">← Back to Milestones</a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<% sections.forEach(function(section) { %>
|
|
11
|
+
<article>
|
|
12
|
+
<header>
|
|
13
|
+
<strong><%= section.type %></strong>
|
|
14
|
+
</header>
|
|
15
|
+
<div class="markdown-body">
|
|
16
|
+
<%- section.html %>
|
|
17
|
+
</div>
|
|
18
|
+
</article>
|
|
19
|
+
<% }); %>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<h1>Milestones</h1>
|
|
2
|
+
|
|
3
|
+
<% if (active.length > 0) { %>
|
|
4
|
+
<h2>Active</h2>
|
|
5
|
+
<% active.forEach(function(m) { %>
|
|
6
|
+
<article>
|
|
7
|
+
<header>
|
|
8
|
+
<strong><%= m.name %></strong>
|
|
9
|
+
<% if (m.goal) { %><small> — <%= m.goal %></small><% } %>
|
|
10
|
+
</header>
|
|
11
|
+
<p>
|
|
12
|
+
Phases <%= m.startPhase %> – <%= m.endPhase %>
|
|
13
|
+
</p>
|
|
14
|
+
</article>
|
|
15
|
+
<% }); %>
|
|
16
|
+
<% } %>
|
|
17
|
+
|
|
18
|
+
<% if (archived.length > 0) { %>
|
|
19
|
+
<h2>Archived</h2>
|
|
20
|
+
<% archived.forEach(function(m) { %>
|
|
21
|
+
<article>
|
|
22
|
+
<header>
|
|
23
|
+
<strong>
|
|
24
|
+
<a href="/milestones/<%= m.version %>"
|
|
25
|
+
hx-get="/milestones/<%= m.version %>"
|
|
26
|
+
hx-target="#main-content"
|
|
27
|
+
hx-push-url="true">
|
|
28
|
+
v<%= m.version %> — <%= m.name %>
|
|
29
|
+
</a>
|
|
30
|
+
</strong>
|
|
31
|
+
</header>
|
|
32
|
+
<p>
|
|
33
|
+
<% if (m.date) { %><small>Completed: <%= m.date %></small><% } %>
|
|
34
|
+
<% if (m.duration) { %><small> • Duration: <%= m.duration %></small><% } %>
|
|
35
|
+
<br>
|
|
36
|
+
<small><%= m.files.length %> archived file<%= m.files.length !== 1 ? 's' : '' %></small>
|
|
37
|
+
</p>
|
|
38
|
+
</article>
|
|
39
|
+
<% }); %>
|
|
40
|
+
<% } %>
|
|
41
|
+
|
|
42
|
+
<% if (active.length === 0 && archived.length === 0) { %>
|
|
43
|
+
<p>No milestones found. Use <code>/pbr:milestone new</code> to create one.</p>
|
|
44
|
+
<% } %>
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
Roadmap
|
|
34
34
|
</a>
|
|
35
35
|
</li>
|
|
36
|
+
<li>
|
|
37
|
+
<a href="/milestones"
|
|
38
|
+
hx-get="/milestones"
|
|
39
|
+
hx-target="#main-content"
|
|
40
|
+
hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'milestones' ? ' aria-current="page"' : '' %>>
|
|
41
|
+
Milestones
|
|
42
|
+
</a>
|
|
43
|
+
</li>
|
|
36
44
|
</ul>
|
|
37
45
|
</nav>
|
|
38
46
|
</aside>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
3
|
"displayName": "Plan-Build-Run",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.3.1",
|
|
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",
|
|
@@ -106,6 +106,16 @@
|
|
|
106
106
|
"statusMessage": "Validating Task() call..."
|
|
107
107
|
}
|
|
108
108
|
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"matcher": "Skill",
|
|
112
|
+
"hooks": [
|
|
113
|
+
{
|
|
114
|
+
"type": "command",
|
|
115
|
+
"command": "node -e \"var r=process.env.CLAUDE_PLUGIN_ROOT||'',m=r.match(/^\\/([a-zA-Z])\\/(.*)/);if(m)r=m[1]+String.fromCharCode(58)+String.fromCharCode(92)+m[2];require(require('path').resolve(r,'..','pbr','scripts','run-hook.js'))\" validate-skill-args.js",
|
|
116
|
+
"statusMessage": "Validating skill arguments..."
|
|
117
|
+
}
|
|
118
|
+
]
|
|
109
119
|
}
|
|
110
120
|
],
|
|
111
121
|
"PreCompact": [
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dashboard
|
|
3
|
+
description: "Launch the PBR web dashboard for the current project."
|
|
4
|
+
argument-hint: "[--port N]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Behavior
|
|
8
|
+
|
|
9
|
+
1. **Parse arguments**: Extract `--port N` from the user's input. Default to `3000`.
|
|
10
|
+
|
|
11
|
+
2. **Check dependencies**: Check if the dashboard `node_modules/` exists in the plugin's dashboard directory. If not, run `npm install` in that directory.
|
|
12
|
+
|
|
13
|
+
3. **Launch dashboard**: Run in background:
|
|
14
|
+
```
|
|
15
|
+
node <plugin-root>/dashboard/bin/cli.js --dir <cwd> --port <port> &
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
4. **Output to user**:
|
|
19
|
+
```
|
|
20
|
+
Dashboard running at http://localhost:<port>
|
|
21
|
+
Open this URL in your browser to view your project's planning state.
|
|
22
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: do
|
|
3
|
+
description: "Route freeform text to the right PBR skill automatically."
|
|
4
|
+
argument-hint: "<freeform description of what you want to do>"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /pbr:do — Freeform Task Router
|
|
8
|
+
|
|
9
|
+
You are running the **do** skill. Your job is to analyze freeform text from the user and route it to the most appropriate PBR skill. You are a dispatcher, not an executor — you never do the work yourself.
|
|
10
|
+
|
|
11
|
+
## Step 0 — Immediate Output
|
|
12
|
+
|
|
13
|
+
**Before ANY tool calls**, display this banner:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
PLAN-BUILD-RUN ► ROUTING
|
|
18
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then proceed to Step 1.
|
|
22
|
+
|
|
23
|
+
## Step 1 — Validate Input
|
|
24
|
+
|
|
25
|
+
If `$ARGUMENTS` is empty, ask the user what they want to do via AskUserQuestion:
|
|
26
|
+
```
|
|
27
|
+
What would you like to do? Describe the task, bug, or idea and I'll route it to the right skill.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Step 2 — Analyze and Route
|
|
31
|
+
|
|
32
|
+
Evaluate `$ARGUMENTS` against these routing criteria. Apply the **first matching** rule:
|
|
33
|
+
|
|
34
|
+
| If the text describes... | Route to | Why |
|
|
35
|
+
|--------------------------|----------|-----|
|
|
36
|
+
| A bug, error, crash, failure, or something broken | `/pbr:debug` | Needs systematic investigation |
|
|
37
|
+
| Exploration, research, comparison, or "how does X work" | `/pbr:explore` | Open-ended investigation |
|
|
38
|
+
| A complex task: refactoring, migration, multi-file architecture, system redesign | `/pbr:plan add` | Needs a full phase with research/plan/build cycle |
|
|
39
|
+
| A review or quality concern about existing work | `/pbr:review` | Needs verification against plan |
|
|
40
|
+
| A note, idea, or "remember to..." | `/pbr:note` | Capture for later |
|
|
41
|
+
| A specific, actionable task (add feature, fix typo, update config, write test) | `/pbr:quick` | Self-contained, single executor |
|
|
42
|
+
|
|
43
|
+
**Ambiguity handling**: If the text could reasonably match multiple routes, ask the user via AskUserQuestion with the top 2-3 options. For example:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
"Refactor the authentication system" could be:
|
|
47
|
+
- /pbr:plan add — Full planning cycle (recommended for multi-file refactors)
|
|
48
|
+
- /pbr:quick — Quick execution (if scope is small and clear)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Step 3 — Confirm and Dispatch
|
|
52
|
+
|
|
53
|
+
Display the routing decision:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
**Input:** {first 80 chars of arguments}
|
|
57
|
+
**Routing to:** {chosen skill}
|
|
58
|
+
**Reason:** {one-line explanation}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then invoke the chosen skill via the Skill tool, passing `$ARGUMENTS` as the args.
|
|
62
|
+
|
|
63
|
+
**Special case for `/pbr:plan add`**: When routing to plan, check if `.planning/ROADMAP.md` exists first (via Read). If it doesn't, suggest `/pbr:begin` instead — the user needs to set up the project before they can add phases.
|
|
64
|
+
|
|
65
|
+
## Step 4 — No Follow-Up
|
|
66
|
+
|
|
67
|
+
After invoking the skill, your job is done. The dispatched skill handles everything from here (execution, commits, state updates). Do not add any additional output after the Skill tool call.
|
|
@@ -63,9 +63,9 @@ Parse the phase number and optional flags:
|
|
|
63
63
|
| `insert <N>` | Insert a new phase at position N (uses decimal numbering) |
|
|
64
64
|
| `remove <N>` | Remove phase N from the roadmap |
|
|
65
65
|
|
|
66
|
-
### Freeform Text Guard
|
|
66
|
+
### Freeform Text Guard — CRITICAL
|
|
67
67
|
|
|
68
|
-
**Before
|
|
68
|
+
**STOP. Before ANY context loading or Step 1 work**, you MUST check whether `$ARGUMENTS` looks like freeform text rather than a valid invocation. This check is non-negotiable. Valid patterns are:
|
|
69
69
|
|
|
70
70
|
- Empty (no arguments)
|
|
71
71
|
- A phase number: integer (`3`, `03`) or decimal (`3.1`)
|
|
@@ -97,6 +97,8 @@ Then suggest the appropriate skill based on the text content:
|
|
|
97
97
|
|
|
98
98
|
Do NOT proceed with planning. The user needs to use the correct skill.
|
|
99
99
|
|
|
100
|
+
**Self-check**: If you reach Step 1 without having matched a valid argument pattern above, you have a bug. Stop immediately and show the usage block.
|
|
101
|
+
|
|
100
102
|
---
|
|
101
103
|
|
|
102
104
|
## Orchestration Flow: Standard Planning
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: todo
|
|
3
3
|
description: "File-based persistent todos. Add, list, complete — survives sessions."
|
|
4
|
-
argument-hint: "add <description> | list [theme] | done <NNN>"
|
|
4
|
+
argument-hint: "add <description> | list [theme] | done <NNN> | work <NNN>"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Step 0 — Immediate Output
|
|
@@ -121,12 +121,12 @@ Pending Todos:
|
|
|
121
121
|
|
|
122
122
|
**Pick a todo** — mark one done or start working
|
|
123
123
|
|
|
124
|
-
`/pbr:todo
|
|
124
|
+
`/pbr:todo work <NNN>` — start working on a todo
|
|
125
|
+
`/pbr:todo done <NNN>` — mark a todo as complete
|
|
125
126
|
|
|
126
127
|
───────────────────────────────────────────────────────────────
|
|
127
128
|
|
|
128
129
|
**Also available:**
|
|
129
|
-
- `/pbr:quick` — work on one now
|
|
130
130
|
- `/pbr:status` — see project status
|
|
131
131
|
|
|
132
132
|
───────────────────────────────────────────────────────────────
|
|
@@ -178,6 +178,68 @@ Todo {NNN} not found in pending todos.
|
|
|
178
178
|
───────────────────────────────────────────────────────────────
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
### `work <NNN>`
|
|
182
|
+
|
|
183
|
+
1. Find `.planning/todos/pending/{NNN}-*.md` (match by number prefix)
|
|
184
|
+
2. If not found, display the same error block as `done` — suggest `/pbr:todo list`
|
|
185
|
+
3. Read the todo file content (frontmatter + body)
|
|
186
|
+
4. Extract the `title` from frontmatter and the full body (Goal, Scope, Acceptance Criteria sections)
|
|
187
|
+
|
|
188
|
+
5. **Assess complexity** to choose the right skill. Evaluate the todo content against these criteria:
|
|
189
|
+
|
|
190
|
+
| Signal | Route to |
|
|
191
|
+
|--------|----------|
|
|
192
|
+
| Single file change, small fix, simple addition | `/pbr:quick` |
|
|
193
|
+
| Multiple acceptance criteria, multi-file scope, architectural decisions, needs research | `/pbr:plan` (requires an active phase) |
|
|
194
|
+
| Investigation needed, unclear root cause | `/pbr:debug` |
|
|
195
|
+
| Open-ended exploration, no clear deliverable | `/pbr:explore` |
|
|
196
|
+
|
|
197
|
+
If unsure, ask the user via AskUserQuestion:
|
|
198
|
+
```
|
|
199
|
+
Todo {NNN} could be handled as a quick task or may need full planning.
|
|
200
|
+
|
|
201
|
+
Which approach?
|
|
202
|
+
- Quick task (/pbr:quick) — single executor, atomic commit
|
|
203
|
+
- Full planning (/pbr:plan) — research, plan, build cycle
|
|
204
|
+
- Debug (/pbr:debug) — systematic investigation
|
|
205
|
+
- Explore (/pbr:explore) — open-ended investigation
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
6. Display branded output:
|
|
209
|
+
```
|
|
210
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
211
|
+
PLAN-BUILD-RUN ► WORKING ON TODO {NNN}
|
|
212
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
213
|
+
|
|
214
|
+
**Todo {NNN}:** {title}
|
|
215
|
+
**Routing to:** /pbr:{chosen-skill}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
7. Invoke the chosen skill via the Skill tool, passing the todo title and body as args:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
{title}
|
|
222
|
+
|
|
223
|
+
Context from todo {NNN}:
|
|
224
|
+
{body content — Goal, Scope, Acceptance Criteria sections}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
For `/pbr:plan`, if no phase exists for this work yet, suggest the user run `/pbr:plan add` first to create one, then re-run `/pbr:todo work {NNN}`.
|
|
228
|
+
|
|
229
|
+
8. When the skill completes, remind the user:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
───────────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
## ▶ Next Up
|
|
235
|
+
|
|
236
|
+
**Mark this todo as done if the work is complete**
|
|
237
|
+
|
|
238
|
+
`/pbr:todo done {NNN}`
|
|
239
|
+
|
|
240
|
+
───────────────────────────────────────────────────────────────
|
|
241
|
+
```
|
|
242
|
+
|
|
181
243
|
### No arguments
|
|
182
244
|
|
|
183
245
|
Show a brief summary: count of pending todos, grouped by theme, plus usage hint.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
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",
|
|
@@ -106,6 +106,16 @@
|
|
|
106
106
|
"statusMessage": "Validating Task() call..."
|
|
107
107
|
}
|
|
108
108
|
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"matcher": "Skill",
|
|
112
|
+
"hooks": [
|
|
113
|
+
{
|
|
114
|
+
"type": "command",
|
|
115
|
+
"command": "node -e \"var r=process.env.CLAUDE_PLUGIN_ROOT||'',m=r.match(/^\\/([a-zA-Z])\\/(.*)/);if(m)r=m[1]+String.fromCharCode(58)+String.fromCharCode(92)+m[2];require(require('path').resolve(r,'scripts','run-hook.js'))\" validate-skill-args.js",
|
|
116
|
+
"statusMessage": "Validating skill arguments..."
|
|
117
|
+
}
|
|
118
|
+
]
|
|
109
119
|
}
|
|
110
120
|
],
|
|
111
121
|
"PreCompact": [
|
|
@@ -211,6 +211,14 @@
|
|
|
211
211
|
},
|
|
212
212
|
"additionalProperties": false
|
|
213
213
|
},
|
|
214
|
+
"dashboard": {
|
|
215
|
+
"type": "object",
|
|
216
|
+
"properties": {
|
|
217
|
+
"auto_launch": { "type": "boolean" },
|
|
218
|
+
"port": { "type": "integer", "minimum": 1024, "maximum": 65535 }
|
|
219
|
+
},
|
|
220
|
+
"additionalProperties": false
|
|
221
|
+
},
|
|
214
222
|
"status_line": {
|
|
215
223
|
"type": "object",
|
|
216
224
|
"properties": {
|
|
@@ -32,6 +32,12 @@ function main() {
|
|
|
32
32
|
|
|
33
33
|
const context = buildContext(planningDir, stateFile);
|
|
34
34
|
|
|
35
|
+
// Auto-launch dashboard if configured
|
|
36
|
+
const config = configLoad(planningDir);
|
|
37
|
+
if (config && config.dashboard && config.dashboard.auto_launch) {
|
|
38
|
+
tryLaunchDashboard(config.dashboard.port || 3000, planningDir, cwd);
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
if (context) {
|
|
36
42
|
const output = {
|
|
37
43
|
additionalContext: context
|
|
@@ -275,7 +281,46 @@ function getHookHealthSummary(planningDir) {
|
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Attempt to launch the dashboard in a detached background process.
|
|
286
|
+
* Checks if the port is already in use before spawning.
|
|
287
|
+
*/
|
|
288
|
+
function tryLaunchDashboard(port, _planningDir, projectDir) {
|
|
289
|
+
const net = require('net');
|
|
290
|
+
const { spawn } = require('child_process');
|
|
291
|
+
|
|
292
|
+
// Quick port probe — if something is already listening, skip launch
|
|
293
|
+
const probe = net.createConnection({ port, host: '127.0.0.1' });
|
|
294
|
+
probe.on('connect', () => {
|
|
295
|
+
probe.destroy();
|
|
296
|
+
logHook('progress-tracker', 'SessionStart', 'dashboard-already-running', { port });
|
|
297
|
+
});
|
|
298
|
+
probe.on('error', () => {
|
|
299
|
+
// Port is free — launch dashboard
|
|
300
|
+
const cliPath = path.join(__dirname, '..', 'dashboard', 'bin', 'cli.js');
|
|
301
|
+
if (!fs.existsSync(cliPath)) {
|
|
302
|
+
logHook('progress-tracker', 'SessionStart', 'dashboard-cli-missing', { cliPath });
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const child = spawn(process.execPath, [cliPath, '--dir', projectDir, '--port', String(port)], {
|
|
308
|
+
detached: true,
|
|
309
|
+
stdio: 'ignore',
|
|
310
|
+
cwd: projectDir
|
|
311
|
+
});
|
|
312
|
+
child.unref();
|
|
313
|
+
logHook('progress-tracker', 'SessionStart', 'dashboard-launched', { port, pid: child.pid });
|
|
314
|
+
} catch (e) {
|
|
315
|
+
logHook('progress-tracker', 'SessionStart', 'dashboard-launch-error', { error: e.message });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Don't let the probe keep the process alive
|
|
320
|
+
probe.unref();
|
|
321
|
+
}
|
|
322
|
+
|
|
278
323
|
// Exported for testing
|
|
279
|
-
module.exports = { getHookHealthSummary, FAILURE_DECISIONS, HOOK_HEALTH_MAX_ENTRIES };
|
|
324
|
+
module.exports = { getHookHealthSummary, FAILURE_DECISIONS, HOOK_HEALTH_MAX_ENTRIES, tryLaunchDashboard };
|
|
280
325
|
|
|
281
326
|
main();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PreToolUse hook: Validates Skill tool arguments before execution.
|
|
5
|
+
*
|
|
6
|
+
* Currently validates:
|
|
7
|
+
* - /pbr:plan — blocks freeform text arguments that don't match
|
|
8
|
+
* valid patterns (phase number, subcommand, flags).
|
|
9
|
+
*
|
|
10
|
+
* When freeform text is detected, analyzes it to suggest the most
|
|
11
|
+
* appropriate skill (quick, debug, explore, todo, plan).
|
|
12
|
+
*
|
|
13
|
+
* Exit codes:
|
|
14
|
+
* 0 = allowed (valid args or non-plan skill)
|
|
15
|
+
* 2 = blocked (freeform text detected for /pbr:plan)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { logHook } = require('./hook-logger');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Valid argument patterns for /pbr:plan.
|
|
22
|
+
*
|
|
23
|
+
* Matches:
|
|
24
|
+
* - Empty / whitespace only
|
|
25
|
+
* - Phase number: "3", "03", "3.1"
|
|
26
|
+
* - Phase number + flags: "3 --skip-research", "3 --assumptions", "3 --gaps", "3 --teams"
|
|
27
|
+
* - Subcommands: "add", "insert 3", "remove 3"
|
|
28
|
+
* - Legacy: "check"
|
|
29
|
+
*/
|
|
30
|
+
const PLAN_VALID_PATTERN = /^\s*$|^\s*\d+(\.\d+)?\s*(--(?:skip-research|assumptions|gaps|teams)\s*)*$|^\s*(?:add|check)\s*$|^\s*(?:insert|remove)\s+\d+(\.\d+)?\s*$/i;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Keyword patterns for routing freeform text to the right skill.
|
|
34
|
+
* Order matters — first match wins.
|
|
35
|
+
*/
|
|
36
|
+
const ROUTE_PATTERNS = [
|
|
37
|
+
{
|
|
38
|
+
pattern: /\b(bugs?|fix(es|ing)?|errors?|crash(es|ing)?|fails?|failing|broken|issues?|debug(ging)?|diagnos(e|ing)|stack\s*trace|exceptions?|regress(ion|ing)?)\b/i,
|
|
39
|
+
skill: '/pbr:debug',
|
|
40
|
+
reason: 'Looks like a bug or debugging task'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
pattern: /\b(explore|research|understand|how does|what is|analy[zs]e|evaluate|compare|pros and cons|trade-?offs?|approach(es)?)\b/i,
|
|
44
|
+
skill: '/pbr:explore',
|
|
45
|
+
reason: 'Looks like exploration or research'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
pattern: /\b(refactor|redesign|architect|migrate|restructure|overhaul|rewrite|multi-?phase|complex|system|infrastructure)\b/i,
|
|
49
|
+
skill: '/pbr:plan add',
|
|
50
|
+
reason: 'Looks like a complex task that needs full planning'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
// Default: anything actionable goes to quick
|
|
54
|
+
pattern: /./,
|
|
55
|
+
skill: '/pbr:quick',
|
|
56
|
+
reason: 'Looks like a straightforward task'
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Suggest the best skill for freeform text.
|
|
62
|
+
* Returns { skill, reason }.
|
|
63
|
+
*/
|
|
64
|
+
function suggestSkill(text) {
|
|
65
|
+
for (const route of ROUTE_PATTERNS) {
|
|
66
|
+
if (route.pattern.test(text)) {
|
|
67
|
+
return { skill: route.skill, reason: route.reason };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { skill: '/pbr:quick', reason: 'Default routing' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check whether a Skill tool call has valid arguments.
|
|
75
|
+
* Returns null if valid, or { output, exitCode } if blocked.
|
|
76
|
+
*/
|
|
77
|
+
function checkSkillArgs(data) {
|
|
78
|
+
const toolInput = data.tool_input || {};
|
|
79
|
+
const skill = toolInput.skill || '';
|
|
80
|
+
const args = toolInput.args || '';
|
|
81
|
+
|
|
82
|
+
// Only validate /pbr:plan for now
|
|
83
|
+
if (skill !== 'pbr:plan') {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Test against valid patterns
|
|
88
|
+
if (PLAN_VALID_PATTERN.test(args)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Freeform text detected — suggest the right skill
|
|
93
|
+
const suggestion = suggestSkill(args);
|
|
94
|
+
|
|
95
|
+
logHook('validate-skill-args', 'PreToolUse', 'blocked', {
|
|
96
|
+
skill,
|
|
97
|
+
args: args.substring(0, 100),
|
|
98
|
+
reason: 'freeform-text',
|
|
99
|
+
suggested: suggestion.skill
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
output: {
|
|
104
|
+
additionalContext: [
|
|
105
|
+
'BLOCKED: /pbr:plan received freeform text instead of a phase number.',
|
|
106
|
+
'',
|
|
107
|
+
'The arguments "' + args.substring(0, 80) + (args.length > 80 ? '...' : '') + '" do not match any valid pattern.',
|
|
108
|
+
'',
|
|
109
|
+
'Valid /pbr:plan usage:',
|
|
110
|
+
' /pbr:plan <N> Plan phase N',
|
|
111
|
+
' /pbr:plan <N> --gaps Create gap-closure plans',
|
|
112
|
+
' /pbr:plan add Add a new phase',
|
|
113
|
+
' /pbr:plan insert <N> Insert a phase at position N',
|
|
114
|
+
' /pbr:plan remove <N> Remove phase N',
|
|
115
|
+
'',
|
|
116
|
+
'Suggested skill for this text:',
|
|
117
|
+
' ' + suggestion.skill + ' — ' + suggestion.reason,
|
|
118
|
+
'',
|
|
119
|
+
'Or use /pbr:do to auto-route freeform text to the right skill.'
|
|
120
|
+
].join('\n')
|
|
121
|
+
},
|
|
122
|
+
exitCode: 2
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function main() {
|
|
127
|
+
let input = '';
|
|
128
|
+
|
|
129
|
+
process.stdin.setEncoding('utf8');
|
|
130
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
131
|
+
process.stdin.on('end', () => {
|
|
132
|
+
try {
|
|
133
|
+
const data = JSON.parse(input);
|
|
134
|
+
const result = checkSkillArgs(data);
|
|
135
|
+
|
|
136
|
+
if (result) {
|
|
137
|
+
process.stdout.write(JSON.stringify(result.output));
|
|
138
|
+
process.exit(result.exitCode);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
process.exit(0);
|
|
142
|
+
} catch (_e) {
|
|
143
|
+
// Don't block on errors
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { checkSkillArgs, suggestSkill, PLAN_VALID_PATTERN, ROUTE_PATTERNS };
|
|
150
|
+
if (require.main === module || process.argv[1] === __filename) { main(); }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dashboard
|
|
3
|
+
description: "Launch the PBR web dashboard for the current project."
|
|
4
|
+
allowed-tools: Bash, Read
|
|
5
|
+
argument-hint: "[--port N]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**STOP — DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by Claude Code's plugin system. Begin executing immediately.**
|
|
9
|
+
|
|
10
|
+
## Behavior
|
|
11
|
+
|
|
12
|
+
1. **Parse arguments**: Extract `--port N` from the user's input. Default to `3000`.
|
|
13
|
+
|
|
14
|
+
2. **Check dependencies**: Check if `${CLAUDE_PLUGIN_ROOT}/dashboard/node_modules/` exists. If not, run:
|
|
15
|
+
```
|
|
16
|
+
npm install --prefix ${CLAUDE_PLUGIN_ROOT}/dashboard
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
3. **Launch dashboard**: Run in background via Bash:
|
|
20
|
+
```
|
|
21
|
+
node ${CLAUDE_PLUGIN_ROOT}/dashboard/bin/cli.js --dir <cwd> --port <port> &
|
|
22
|
+
```
|
|
23
|
+
Use `&` to background the process so it doesn't block the session.
|
|
24
|
+
|
|
25
|
+
4. **Output to user**:
|
|
26
|
+
```
|
|
27
|
+
Dashboard running at http://localhost:<port>
|
|
28
|
+
Open this URL in your browser to view your project's planning state.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Notes
|
|
32
|
+
|
|
33
|
+
- If the port is already in use, the dashboard will fail to start — suggest the user try a different port with `--port`.
|
|
34
|
+
- The dashboard watches `.planning/` for live updates via SSE.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: do
|
|
3
|
+
description: "Route freeform text to the right PBR skill automatically."
|
|
4
|
+
allowed-tools: Read, Skill, AskUserQuestion
|
|
5
|
+
argument-hint: "<freeform description of what you want to do>"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**STOP — DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by Claude Code's plugin system. Using the Read tool on this SKILL.md file wastes ~3,000 tokens. Begin executing Step 1 immediately.**
|
|
9
|
+
|
|
10
|
+
# /pbr:do — Freeform Task Router
|
|
11
|
+
|
|
12
|
+
You are running the **do** skill. Your job is to analyze freeform text from the user and route it to the most appropriate PBR skill. You are a dispatcher, not an executor — you never do the work yourself.
|
|
13
|
+
|
|
14
|
+
## Step 0 — Immediate Output
|
|
15
|
+
|
|
16
|
+
**Before ANY tool calls**, display this banner:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
20
|
+
PLAN-BUILD-RUN ► ROUTING
|
|
21
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then proceed to Step 1.
|
|
25
|
+
|
|
26
|
+
## Step 1 — Validate Input
|
|
27
|
+
|
|
28
|
+
If `$ARGUMENTS` is empty, ask the user what they want to do via AskUserQuestion:
|
|
29
|
+
```
|
|
30
|
+
What would you like to do? Describe the task, bug, or idea and I'll route it to the right skill.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Step 2 — Analyze and Route
|
|
34
|
+
|
|
35
|
+
Evaluate `$ARGUMENTS` against these routing criteria. Apply the **first matching** rule:
|
|
36
|
+
|
|
37
|
+
| If the text describes... | Route to | Why |
|
|
38
|
+
|--------------------------|----------|-----|
|
|
39
|
+
| A bug, error, crash, failure, or something broken | `/pbr:debug` | Needs systematic investigation |
|
|
40
|
+
| Exploration, research, comparison, or "how does X work" | `/pbr:explore` | Open-ended investigation |
|
|
41
|
+
| A complex task: refactoring, migration, multi-file architecture, system redesign | `/pbr:plan add` | Needs a full phase with research/plan/build cycle |
|
|
42
|
+
| A review or quality concern about existing work | `/pbr:review` | Needs verification against plan |
|
|
43
|
+
| A note, idea, or "remember to..." | `/pbr:note` | Capture for later |
|
|
44
|
+
| A specific, actionable task (add feature, fix typo, update config, write test) | `/pbr:quick` | Self-contained, single executor |
|
|
45
|
+
|
|
46
|
+
**Ambiguity handling**: If the text could reasonably match multiple routes, ask the user via AskUserQuestion with the top 2-3 options. For example:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
"Refactor the authentication system" could be:
|
|
50
|
+
- /pbr:plan add — Full planning cycle (recommended for multi-file refactors)
|
|
51
|
+
- /pbr:quick — Quick execution (if scope is small and clear)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Step 3 — Confirm and Dispatch
|
|
55
|
+
|
|
56
|
+
Display the routing decision:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
**Input:** {first 80 chars of arguments}
|
|
60
|
+
**Routing to:** {chosen skill}
|
|
61
|
+
**Reason:** {one-line explanation}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then invoke the chosen skill via the Skill tool, passing `$ARGUMENTS` as the args.
|
|
65
|
+
|
|
66
|
+
**Special case for `/pbr:plan add`**: When routing to plan, check if `.planning/ROADMAP.md` exists first (via Read). If it doesn't, suggest `/pbr:begin` instead — the user needs to set up the project before they can add phases.
|
|
67
|
+
|
|
68
|
+
## Step 4 — No Follow-Up
|
|
69
|
+
|
|
70
|
+
After invoking the skill, your job is done. The dispatched skill handles everything from here (execution, commits, state updates). Do not add any additional output after the Skill tool call.
|
|
@@ -66,9 +66,9 @@ Parse the phase number and optional flags:
|
|
|
66
66
|
| `insert <N>` | Insert a new phase at position N (uses decimal numbering) |
|
|
67
67
|
| `remove <N>` | Remove phase N from the roadmap |
|
|
68
68
|
|
|
69
|
-
### Freeform Text Guard
|
|
69
|
+
### Freeform Text Guard — CRITICAL
|
|
70
70
|
|
|
71
|
-
**Before
|
|
71
|
+
**STOP. Before ANY context loading or Step 1 work**, you MUST check whether `$ARGUMENTS` looks like freeform text rather than a valid invocation. This check is non-negotiable. Valid patterns are:
|
|
72
72
|
|
|
73
73
|
- Empty (no arguments)
|
|
74
74
|
- A phase number: integer (`3`, `03`) or decimal (`3.1`)
|
|
@@ -100,6 +100,8 @@ Then suggest the appropriate skill based on the text content:
|
|
|
100
100
|
|
|
101
101
|
Do NOT proceed with planning. The user needs to use the correct skill.
|
|
102
102
|
|
|
103
|
+
**Self-check**: If you reach Step 1 without having matched a valid argument pattern above, you have a bug. Stop immediately and show the usage block.
|
|
104
|
+
|
|
103
105
|
---
|
|
104
106
|
|
|
105
107
|
## Orchestration Flow: Standard Planning
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: todo
|
|
3
3
|
description: "File-based persistent todos. Add, list, complete — survives sessions."
|
|
4
|
-
allowed-tools: Read, Write, Bash, Glob, Grep
|
|
5
|
-
argument-hint: "add <description> | list [theme] | done <NNN>"
|
|
4
|
+
allowed-tools: Read, Write, Bash, Glob, Grep, Skill, AskUserQuestion
|
|
5
|
+
argument-hint: "add <description> | list [theme] | done <NNN> | work <NNN>"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
**STOP — DO NOT READ THIS FILE. You are already reading it. This prompt was injected into your context by Claude Code's plugin system. Using the Read tool on this SKILL.md file wastes ~7,600 tokens. Begin executing Step 1 immediately.**
|
|
@@ -124,12 +124,12 @@ Pending Todos:
|
|
|
124
124
|
|
|
125
125
|
**Pick a todo** — mark one done or start working
|
|
126
126
|
|
|
127
|
-
`/pbr:todo
|
|
127
|
+
`/pbr:todo work <NNN>` — start working on a todo
|
|
128
|
+
`/pbr:todo done <NNN>` — mark a todo as complete
|
|
128
129
|
|
|
129
130
|
───────────────────────────────────────────────────────────────
|
|
130
131
|
|
|
131
132
|
**Also available:**
|
|
132
|
-
- `/pbr:quick` — work on one now
|
|
133
133
|
- `/pbr:status` — see project status
|
|
134
134
|
|
|
135
135
|
───────────────────────────────────────────────────────────────
|
|
@@ -181,6 +181,68 @@ Todo {NNN} not found in pending todos.
|
|
|
181
181
|
───────────────────────────────────────────────────────────────
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
+
### `work <NNN>`
|
|
185
|
+
|
|
186
|
+
1. Find `.planning/todos/pending/{NNN}-*.md` (match by number prefix)
|
|
187
|
+
2. If not found, display the same error block as `done` — suggest `/pbr:todo list`
|
|
188
|
+
3. Read the todo file content (frontmatter + body)
|
|
189
|
+
4. Extract the `title` from frontmatter and the full body (Goal, Scope, Acceptance Criteria sections)
|
|
190
|
+
|
|
191
|
+
5. **Assess complexity** to choose the right skill. Evaluate the todo content against these criteria:
|
|
192
|
+
|
|
193
|
+
| Signal | Route to |
|
|
194
|
+
|--------|----------|
|
|
195
|
+
| Single file change, small fix, simple addition | `/pbr:quick` |
|
|
196
|
+
| Multiple acceptance criteria, multi-file scope, architectural decisions, needs research | `/pbr:plan` (requires an active phase) |
|
|
197
|
+
| Investigation needed, unclear root cause | `/pbr:debug` |
|
|
198
|
+
| Open-ended exploration, no clear deliverable | `/pbr:explore` |
|
|
199
|
+
|
|
200
|
+
If unsure, ask the user via AskUserQuestion:
|
|
201
|
+
```
|
|
202
|
+
Todo {NNN} could be handled as a quick task or may need full planning.
|
|
203
|
+
|
|
204
|
+
Which approach?
|
|
205
|
+
- Quick task (/pbr:quick) — single executor, atomic commit
|
|
206
|
+
- Full planning (/pbr:plan) — research, plan, build cycle
|
|
207
|
+
- Debug (/pbr:debug) — systematic investigation
|
|
208
|
+
- Explore (/pbr:explore) — open-ended investigation
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
6. Display branded output:
|
|
212
|
+
```
|
|
213
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
214
|
+
PLAN-BUILD-RUN ► WORKING ON TODO {NNN}
|
|
215
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
216
|
+
|
|
217
|
+
**Todo {NNN}:** {title}
|
|
218
|
+
**Routing to:** /pbr:{chosen-skill}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
7. Invoke the chosen skill via the Skill tool, passing the todo title and body as args:
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
{title}
|
|
225
|
+
|
|
226
|
+
Context from todo {NNN}:
|
|
227
|
+
{body content — Goal, Scope, Acceptance Criteria sections}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
For `/pbr:plan`, if no phase exists for this work yet, suggest the user run `/pbr:plan add` first to create one, then re-run `/pbr:todo work {NNN}`.
|
|
231
|
+
|
|
232
|
+
8. When the skill completes, remind the user:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
───────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
## ▶ Next Up
|
|
238
|
+
|
|
239
|
+
**Mark this todo as done if the work is complete**
|
|
240
|
+
|
|
241
|
+
`/pbr:todo done {NNN}`
|
|
242
|
+
|
|
243
|
+
───────────────────────────────────────────────────────────────
|
|
244
|
+
```
|
|
245
|
+
|
|
184
246
|
### No arguments
|
|
185
247
|
|
|
186
248
|
Show a brief summary: count of pending todos, grouped by theme, plus usage hint.
|