@sienklogic/plan-build-run 2.22.1 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dashboard/package.json +2 -1
- package/dashboard/src/middleware/errorHandler.js +12 -2
- package/dashboard/src/repositories/planning.repository.js +23 -1
- package/dashboard/src/routes/pages.routes.js +65 -2
- package/dashboard/src/services/local-llm-metrics.service.js +81 -0
- package/dashboard/src/services/quick.service.js +62 -0
- package/dashboard/src/views/partials/analytics-content.ejs +61 -0
- package/dashboard/src/views/partials/quick-content.ejs +40 -0
- package/dashboard/src/views/partials/quick-detail-content.ejs +29 -0
- package/dashboard/src/views/partials/sidebar.ejs +8 -0
- package/dashboard/src/views/quick-detail.ejs +5 -0
- package/dashboard/src/views/quick.ejs +5 -0
- package/package.json +1 -1
- package/plugins/copilot-pbr/agents/debugger.agent.md +15 -0
- package/plugins/copilot-pbr/agents/researcher.agent.md +20 -0
- package/plugins/copilot-pbr/agents/synthesizer.agent.md +12 -0
- package/plugins/copilot-pbr/plugin.json +1 -1
- package/plugins/copilot-pbr/references/config-reference.md +89 -0
- package/plugins/copilot-pbr/skills/continue/SKILL.md +1 -1
- package/plugins/copilot-pbr/skills/health/SKILL.md +8 -1
- package/plugins/copilot-pbr/skills/help/SKILL.md +17 -4
- package/plugins/copilot-pbr/skills/milestone/SKILL.md +13 -13
- package/plugins/copilot-pbr/skills/quick/SKILL.md +4 -1
- package/plugins/copilot-pbr/skills/setup/SKILL.md +17 -6
- package/plugins/copilot-pbr/skills/status/SKILL.md +37 -1
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/agents/debugger.md +15 -0
- package/plugins/cursor-pbr/agents/researcher.md +20 -0
- package/plugins/cursor-pbr/agents/synthesizer.md +12 -0
- package/plugins/cursor-pbr/references/config-reference.md +89 -0
- package/plugins/cursor-pbr/skills/continue/SKILL.md +1 -1
- package/plugins/cursor-pbr/skills/health/SKILL.md +8 -1
- package/plugins/cursor-pbr/skills/help/SKILL.md +17 -4
- package/plugins/cursor-pbr/skills/milestone/SKILL.md +13 -13
- package/plugins/cursor-pbr/skills/quick/SKILL.md +4 -1
- package/plugins/cursor-pbr/skills/setup/SKILL.md +17 -6
- package/plugins/cursor-pbr/skills/status/SKILL.md +37 -1
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/agents/debugger.md +15 -0
- package/plugins/pbr/agents/researcher.md +20 -0
- package/plugins/pbr/agents/synthesizer.md +12 -0
- package/plugins/pbr/references/config-reference.md +89 -0
- package/plugins/pbr/scripts/check-config-change.js +33 -0
- package/plugins/pbr/scripts/check-plan-format.js +52 -4
- package/plugins/pbr/scripts/check-subagent-output.js +43 -3
- package/plugins/pbr/scripts/config-schema.json +48 -0
- package/plugins/pbr/scripts/local-llm/client.js +214 -0
- package/plugins/pbr/scripts/local-llm/health.js +217 -0
- package/plugins/pbr/scripts/local-llm/metrics.js +252 -0
- package/plugins/pbr/scripts/local-llm/operations/classify-artifact.js +76 -0
- package/plugins/pbr/scripts/local-llm/operations/classify-error.js +75 -0
- package/plugins/pbr/scripts/local-llm/operations/score-source.js +72 -0
- package/plugins/pbr/scripts/local-llm/operations/summarize-context.js +62 -0
- package/plugins/pbr/scripts/local-llm/operations/validate-task.js +59 -0
- package/plugins/pbr/scripts/local-llm/router.js +101 -0
- package/plugins/pbr/scripts/local-llm/shadow.js +60 -0
- package/plugins/pbr/scripts/local-llm/threshold-tuner.js +118 -0
- package/plugins/pbr/scripts/pbr-tools.js +120 -3
- package/plugins/pbr/scripts/post-write-dispatch.js +2 -2
- package/plugins/pbr/scripts/progress-tracker.js +29 -3
- package/plugins/pbr/scripts/session-cleanup.js +36 -1
- package/plugins/pbr/scripts/validate-task.js +30 -1
- package/plugins/pbr/skills/continue/SKILL.md +1 -1
- package/plugins/pbr/skills/health/SKILL.md +8 -1
- package/plugins/pbr/skills/help/SKILL.md +17 -4
- package/plugins/pbr/skills/milestone/SKILL.md +13 -13
- package/plugins/pbr/skills/quick/SKILL.md +4 -1
- package/plugins/pbr/skills/setup/SKILL.md +17 -6
- package/plugins/pbr/skills/status/SKILL.md +38 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ 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.23.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.22.2...plan-build-run-v2.23.0) (2026-02-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **28-01:** add local LLM foundation — client, health, metrics, config schema, hook integrations, tests ([44b5a77](https://github.com/SienkLogic/plan-build-run/commit/44b5a773fbb22d77ac0dbf3325201980dd1e9635))
|
|
14
|
+
* **29-01:** integrate local LLM into hooks — artifact classification, task validation, error classification, CLI ([785a708](https://github.com/SienkLogic/plan-build-run/commit/785a708039b0c363589e7151c40df989bb2a7959))
|
|
15
|
+
* **30-01:** add metrics display — session summaries, status skill, CLI, dashboard analytics ([d4ae4a4](https://github.com/SienkLogic/plan-build-run/commit/d4ae4a400932a324f61872169c1dae73cc2923ce))
|
|
16
|
+
* **30-03:** add local-llm-metrics.service.js with getLlmMetrics and Vitest tests ([fc1dd8f](https://github.com/SienkLogic/plan-build-run/commit/fc1dd8f6fbada707543dea848e7355ff52896667))
|
|
17
|
+
* **30-03:** wire getLlmMetrics into /analytics route and add Local LLM Offload section to EJS template ([5fcb61e](https://github.com/SienkLogic/plan-build-run/commit/5fcb61e25848395a4d59f4c555bbb8cf913efb72))
|
|
18
|
+
* **31-01:** add adaptive router — complexity heuristic, confidence gate, 3 routing strategies ([7905462](https://github.com/SienkLogic/plan-build-run/commit/7905462a5dc11063813c1c31400a17f4d2314a23))
|
|
19
|
+
* **32-01:** add agent support — source scoring, error classification, context summarization, prompt injection ([3693660](https://github.com/SienkLogic/plan-build-run/commit/36936609200596fe04cbf27f6eff41566733a74e))
|
|
20
|
+
* **33-01:** add shadow mode, threshold tuner, comprehensive tests, docs, cross-plugin sync ([dbacfed](https://github.com/SienkLogic/plan-build-run/commit/dbacfed98bd6819babad2f07df588cf545d5b99c))
|
|
21
|
+
* **34-01:** add config.features.source_scoring feature flag guard to score-source.js ([a0945a2](https://github.com/SienkLogic/plan-build-run/commit/a0945a2ffd1067ab3644063a8deee4e06a42ea9e))
|
|
22
|
+
* **34-01:** wire runShadow() into router.js post-call path for all 3 routing strategies ([f233948](https://github.com/SienkLogic/plan-build-run/commit/f23394810ab0f647f8eb1ba1d8f78a5eb7d048d8))
|
|
23
|
+
* **35-01:** GREEN - add escapeHtml helper and use it in HTMX error handler path ([a1830c9](https://github.com/SienkLogic/plan-build-run/commit/a1830c9230ba7ab1ea693a7ff2d36e786d5878d3))
|
|
24
|
+
* **35-01:** GREEN - add sanitize-html post-processing to planning.repository ([6d8122b](https://github.com/SienkLogic/plan-build-run/commit/6d8122b83cf1ed62101a3e9fc2f436caf6b0858e))
|
|
25
|
+
* **35-03:** add Quick Tasks view with /quick and /quick/:id routes ([22abe29](https://github.com/SienkLogic/plan-build-run/commit/22abe29e3951ea8e4fbd045116f6216326ee3907))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
* **34-01:** mark all LLM-01 through LLM-34 traceability entries as Verified ([4311497](https://github.com/SienkLogic/plan-build-run/commit/4311497e1e91f55a575da4468924d21903457cdf))
|
|
31
|
+
* **quick-002:** add .active-skill stale detection to health Check 10 ([3f95b16](https://github.com/SienkLogic/plan-build-run/commit/3f95b16461478bf4271ef4f7ce94582188869bae))
|
|
32
|
+
* **quick-002:** fix NEXT UP banner indentation in milestone SKILL.md ([2983000](https://github.com/SienkLogic/plan-build-run/commit/2983000bd4df5e95c63cdc8f79601c7894883dbb))
|
|
33
|
+
* **quick-002:** replace arrow-list with bullet style in help SKILL.md ([ad78663](https://github.com/SienkLogic/plan-build-run/commit/ad7866375c6f7fd7dda3e195413df95f3ee191bc))
|
|
34
|
+
|
|
35
|
+
## [2.22.2](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.22.1...plan-build-run-v2.22.2) (2026-02-24)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Documentation
|
|
39
|
+
|
|
40
|
+
* **tools:** fix banner consistency, add status/continue/do comparison, fix continue description ([028c0fd](https://github.com/SienkLogic/plan-build-run/commit/028c0fd6b09f77928f8d321f7442b87f7f07c538))
|
|
41
|
+
|
|
8
42
|
## [2.22.1](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.22.0...plan-build-run-v2.22.1) (2026-02-24)
|
|
9
43
|
|
|
10
44
|
|
package/dashboard/package.json
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* MUST have exactly 4 parameters for Express to recognize it as an error handler.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
function escapeHtml(str) {
|
|
7
|
+
if (!str) return '';
|
|
8
|
+
return str
|
|
9
|
+
.replace(/&/g, '&')
|
|
10
|
+
.replace(/</g, '<')
|
|
11
|
+
.replace(/>/g, '>')
|
|
12
|
+
.replace(/"/g, '"')
|
|
13
|
+
.replace(/'/g, ''');
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
// eslint-disable-next-line no-unused-vars
|
|
7
17
|
export default function errorHandler(err, req, res, next) {
|
|
8
18
|
// If headers already sent, delegate to Express default handler
|
|
@@ -40,9 +50,9 @@ export default function errorHandler(err, req, res, next) {
|
|
|
40
50
|
|
|
41
51
|
// Render response
|
|
42
52
|
if (isHtmx) {
|
|
43
|
-
let html = `<h1>Error ${status}</h1><p>${templateData.message}</p>`;
|
|
53
|
+
let html = `<h1>Error ${status}</h1><p>${escapeHtml(templateData.message)}</p>`;
|
|
44
54
|
if (templateData.stack) {
|
|
45
|
-
html += `<pre><code>${templateData.stack}</code></pre>`;
|
|
55
|
+
html += `<pre><code>${escapeHtml(templateData.stack)}</code></pre>`;
|
|
46
56
|
}
|
|
47
57
|
html += '<p><a href="/">Return to Dashboard</a></p>';
|
|
48
58
|
return res.status(status).send(html);
|
|
@@ -2,6 +2,7 @@ import { readFile, readdir } from 'node:fs/promises';
|
|
|
2
2
|
import { join, resolve, relative, normalize } from 'node:path';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
import { marked } from 'marked';
|
|
5
|
+
import sanitizeHtml from 'sanitize-html';
|
|
5
6
|
|
|
6
7
|
marked.setOptions({ gfm: true, breaks: false });
|
|
7
8
|
|
|
@@ -76,9 +77,30 @@ export async function readMarkdownFile(filePath) {
|
|
|
76
77
|
|
|
77
78
|
const html = marked.parse(content);
|
|
78
79
|
|
|
80
|
+
const sanitizedHtml = sanitizeHtml(html, {
|
|
81
|
+
allowedTags: sanitizeHtml.defaults.allowedTags.concat([
|
|
82
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
83
|
+
'img', 'details', 'summary', 'del', 'ins',
|
|
84
|
+
'sup', 'sub', 'abbr', 'kbd', 'mark'
|
|
85
|
+
]),
|
|
86
|
+
allowedAttributes: {
|
|
87
|
+
...sanitizeHtml.defaults.allowedAttributes,
|
|
88
|
+
'a': ['href', 'name', 'target', 'rel'],
|
|
89
|
+
'img': ['src', 'alt', 'title', 'width', 'height'],
|
|
90
|
+
'code': ['class'],
|
|
91
|
+
'pre': ['class'],
|
|
92
|
+
'span': ['class'],
|
|
93
|
+
'div': ['class'],
|
|
94
|
+
'td': ['align'],
|
|
95
|
+
'th': ['align', 'scope'],
|
|
96
|
+
'h1': ['id'], 'h2': ['id'], 'h3': ['id'],
|
|
97
|
+
'h4': ['id'], 'h5': ['id'], 'h6': ['id']
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
79
101
|
return {
|
|
80
102
|
frontmatter: data,
|
|
81
|
-
html,
|
|
103
|
+
html: sanitizedHtml,
|
|
82
104
|
rawContent: content
|
|
83
105
|
};
|
|
84
106
|
}
|
|
@@ -5,7 +5,9 @@ import { parseStateFile, derivePhaseStatuses } from '../services/dashboard.servi
|
|
|
5
5
|
import { listPendingTodos, getTodoDetail, createTodo, completeTodo } from '../services/todo.service.js';
|
|
6
6
|
import { getAllMilestones, getMilestoneDetail } from '../services/milestone.service.js';
|
|
7
7
|
import { getProjectAnalytics } from '../services/analytics.service.js';
|
|
8
|
+
import { getLlmMetrics } from '../services/local-llm-metrics.service.js';
|
|
8
9
|
import { listNotes } from '../services/notes.service.js';
|
|
10
|
+
import { listQuickTasks, getQuickTask } from '../services/quick.service.js';
|
|
9
11
|
|
|
10
12
|
const router = Router();
|
|
11
13
|
|
|
@@ -358,14 +360,18 @@ router.get('/dependencies', async (req, res) => {
|
|
|
358
360
|
|
|
359
361
|
router.get('/analytics', async (req, res) => {
|
|
360
362
|
const projectDir = req.app.locals.projectDir;
|
|
361
|
-
const analytics = await
|
|
363
|
+
const [analytics, llmMetrics] = await Promise.all([
|
|
364
|
+
getProjectAnalytics(projectDir),
|
|
365
|
+
getLlmMetrics(projectDir)
|
|
366
|
+
]);
|
|
362
367
|
|
|
363
368
|
const templateData = {
|
|
364
369
|
title: 'Analytics',
|
|
365
370
|
activePage: 'analytics',
|
|
366
371
|
currentPath: '/analytics',
|
|
367
372
|
breadcrumbs: [{ label: 'Analytics' }],
|
|
368
|
-
analytics
|
|
373
|
+
analytics,
|
|
374
|
+
llmMetrics
|
|
369
375
|
};
|
|
370
376
|
|
|
371
377
|
res.setHeader('Vary', 'HX-Request');
|
|
@@ -423,4 +429,61 @@ router.get('/roadmap', async (req, res) => {
|
|
|
423
429
|
}
|
|
424
430
|
});
|
|
425
431
|
|
|
432
|
+
router.get('/quick', async (req, res) => {
|
|
433
|
+
const projectDir = req.app.locals.projectDir;
|
|
434
|
+
const tasks = await listQuickTasks(projectDir);
|
|
435
|
+
|
|
436
|
+
const templateData = {
|
|
437
|
+
title: 'Quick Tasks',
|
|
438
|
+
activePage: 'quick',
|
|
439
|
+
currentPath: '/quick',
|
|
440
|
+
breadcrumbs: [{ label: 'Quick Tasks' }],
|
|
441
|
+
tasks
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
res.setHeader('Vary', 'HX-Request');
|
|
445
|
+
|
|
446
|
+
if (req.get('HX-Request') === 'true') {
|
|
447
|
+
res.render('partials/quick-content', templateData);
|
|
448
|
+
} else {
|
|
449
|
+
res.render('quick', templateData);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
router.get('/quick/:id', async (req, res) => {
|
|
454
|
+
const { id } = req.params;
|
|
455
|
+
|
|
456
|
+
// Validate ID format: must be exactly three digits
|
|
457
|
+
if (!/^\d{3}$/.test(id)) {
|
|
458
|
+
const err = new Error('Quick Task ID must be a three-digit number (e.g., 001, 005, 042)');
|
|
459
|
+
err.status = 404;
|
|
460
|
+
throw err;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const projectDir = req.app.locals.projectDir;
|
|
464
|
+
const task = await getQuickTask(projectDir, id);
|
|
465
|
+
|
|
466
|
+
if (!task) {
|
|
467
|
+
const err = new Error(`Quick task ${id} not found`);
|
|
468
|
+
err.status = 404;
|
|
469
|
+
throw err;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const templateData = {
|
|
473
|
+
title: `Quick Task ${task.id}: ${task.title}`,
|
|
474
|
+
activePage: 'quick',
|
|
475
|
+
currentPath: '/quick/' + id,
|
|
476
|
+
breadcrumbs: [{ label: 'Quick Tasks', url: '/quick' }, { label: 'Task ' + id }],
|
|
477
|
+
...task
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
res.setHeader('Vary', 'HX-Request');
|
|
481
|
+
|
|
482
|
+
if (req.get('HX-Request') === 'true') {
|
|
483
|
+
res.render('partials/quick-detail-content', templateData);
|
|
484
|
+
} else {
|
|
485
|
+
res.render('quick-detail', templateData);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
426
489
|
export default router;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Read and aggregate local LLM metrics from .planning/logs/local-llm-metrics.jsonl.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} projectDir - Absolute path to the project root
|
|
8
|
+
* @returns {Promise<{summary: object, byOperation: Array, baseline: object}|null>}
|
|
9
|
+
* Returns null if the file does not exist, is empty, or has no valid entries.
|
|
10
|
+
*/
|
|
11
|
+
export async function getLlmMetrics(projectDir) {
|
|
12
|
+
try {
|
|
13
|
+
const filePath = join(projectDir, '.planning', 'logs', 'local-llm-metrics.jsonl');
|
|
14
|
+
|
|
15
|
+
let raw;
|
|
16
|
+
try {
|
|
17
|
+
raw = await readFile(filePath, 'utf8');
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (err.code === 'ENOENT') return null;
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Parse valid JSON lines, skip malformed
|
|
24
|
+
const entries = [];
|
|
25
|
+
for (const line of raw.split('\n')) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (!trimmed) continue;
|
|
28
|
+
try {
|
|
29
|
+
entries.push(JSON.parse(trimmed));
|
|
30
|
+
} catch {
|
|
31
|
+
// skip malformed lines
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (entries.length === 0) return null;
|
|
36
|
+
|
|
37
|
+
// Compute aggregate summary
|
|
38
|
+
const total_calls = entries.length;
|
|
39
|
+
const fallback_count = entries.filter(e => e.fallback_used === true).length;
|
|
40
|
+
const fallback_rate_pct = Math.round(fallback_count / total_calls * 100);
|
|
41
|
+
const avg_latency_ms = Math.round(
|
|
42
|
+
entries.reduce((sum, e) => sum + (e.latency_ms || 0), 0) / total_calls
|
|
43
|
+
);
|
|
44
|
+
const tokens_saved = entries.reduce((sum, e) => sum + (e.tokens_saved_frontier || 0), 0);
|
|
45
|
+
const cost_saved_usd = parseFloat((tokens_saved * (3.0 / 1_000_000)).toFixed(4));
|
|
46
|
+
|
|
47
|
+
const summary = {
|
|
48
|
+
total_calls,
|
|
49
|
+
fallback_count,
|
|
50
|
+
fallback_rate_pct,
|
|
51
|
+
avg_latency_ms,
|
|
52
|
+
tokens_saved,
|
|
53
|
+
cost_saved_usd
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Compute byOperation: group entries by operation field
|
|
57
|
+
const opMap = new Map();
|
|
58
|
+
for (const e of entries) {
|
|
59
|
+
const op = e.operation || 'unknown';
|
|
60
|
+
if (!opMap.has(op)) {
|
|
61
|
+
opMap.set(op, { operation: op, calls: 0, fallbacks: 0, tokens_saved: 0 });
|
|
62
|
+
}
|
|
63
|
+
const rec = opMap.get(op);
|
|
64
|
+
rec.calls += 1;
|
|
65
|
+
if (e.fallback_used === true) rec.fallbacks += 1;
|
|
66
|
+
rec.tokens_saved += e.tokens_saved_frontier || 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const byOperation = Array.from(opMap.values()).sort((a, b) => b.calls - a.calls);
|
|
70
|
+
|
|
71
|
+
// Compute baseline (LLM-15)
|
|
72
|
+
const baseline = {
|
|
73
|
+
hook_invocations: total_calls,
|
|
74
|
+
estimated_frontier_tokens_without_local: tokens_saved
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return { summary, byOperation, baseline };
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readMarkdownFile } from '../repositories/planning.repository.js';
|
|
4
|
+
|
|
5
|
+
export async function listQuickTasks(projectDir) {
|
|
6
|
+
const quickDir = join(projectDir, '.planning', 'quick');
|
|
7
|
+
let entries;
|
|
8
|
+
try {
|
|
9
|
+
entries = await readdir(quickDir, { withFileTypes: true });
|
|
10
|
+
} catch (err) {
|
|
11
|
+
if (err.code === 'ENOENT') return [];
|
|
12
|
+
throw err;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
|
|
16
|
+
const tasks = [];
|
|
17
|
+
for (const dirName of dirs) {
|
|
18
|
+
const match = dirName.match(/^(\d{3})-(.+)$/);
|
|
19
|
+
if (!match) continue;
|
|
20
|
+
const [, id, slug] = match;
|
|
21
|
+
const title = slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
22
|
+
let status = 'in-progress';
|
|
23
|
+
try {
|
|
24
|
+
const summaryPath = join(quickDir, dirName, 'SUMMARY.md');
|
|
25
|
+
const { frontmatter } = await readMarkdownFile(summaryPath);
|
|
26
|
+
if (frontmatter.status) status = frontmatter.status;
|
|
27
|
+
} catch { /* No SUMMARY.md yet */ }
|
|
28
|
+
tasks.push({ id, slug, dirName, title, status });
|
|
29
|
+
}
|
|
30
|
+
return tasks;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function getQuickTask(projectDir, id) {
|
|
34
|
+
const quickDir = join(projectDir, '.planning', 'quick');
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
entries = await readdir(quickDir, { withFileTypes: true });
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (err.code === 'ENOENT') return null;
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
const dirEntry = entries.filter(e => e.isDirectory()).find(e => e.name.startsWith(id + '-'));
|
|
43
|
+
if (!dirEntry) return null;
|
|
44
|
+
|
|
45
|
+
const taskDir = join(quickDir, dirEntry.name);
|
|
46
|
+
const match = dirEntry.name.match(/^(\d{3})-(.+)$/);
|
|
47
|
+
const slug = match ? match[2] : dirEntry.name;
|
|
48
|
+
const title = slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
49
|
+
|
|
50
|
+
let planHtml = null, summaryHtml = null, summaryFrontmatter = {};
|
|
51
|
+
try {
|
|
52
|
+
const plan = await readMarkdownFile(join(taskDir, 'PLAN.md'));
|
|
53
|
+
planHtml = plan.html;
|
|
54
|
+
} catch { /* missing */ }
|
|
55
|
+
try {
|
|
56
|
+
const summary = await readMarkdownFile(join(taskDir, 'SUMMARY.md'));
|
|
57
|
+
summaryHtml = summary.html;
|
|
58
|
+
summaryFrontmatter = summary.frontmatter;
|
|
59
|
+
} catch { /* missing */ }
|
|
60
|
+
|
|
61
|
+
return { id, slug, dirName: dirEntry.name, title, status: summaryFrontmatter.status || 'in-progress', planHtml, summaryHtml };
|
|
62
|
+
}
|
|
@@ -88,3 +88,64 @@
|
|
|
88
88
|
<% } else { %>
|
|
89
89
|
<p>No phase data available.</p>
|
|
90
90
|
<% } %>
|
|
91
|
+
|
|
92
|
+
<% if (typeof llmMetrics !== 'undefined' && llmMetrics) { %>
|
|
93
|
+
<article>
|
|
94
|
+
<header>Local LLM Offload</header>
|
|
95
|
+
<div class="grid">
|
|
96
|
+
<article>
|
|
97
|
+
<header>Total Calls</header>
|
|
98
|
+
<strong class="stat-value"><%= llmMetrics.summary.total_calls %></strong>
|
|
99
|
+
<span class="stat-unit">calls</span>
|
|
100
|
+
</article>
|
|
101
|
+
<article>
|
|
102
|
+
<header>Tokens Saved</header>
|
|
103
|
+
<strong class="stat-value"><%= llmMetrics.summary.tokens_saved.toLocaleString() %></strong>
|
|
104
|
+
<span class="stat-unit">frontier tokens</span>
|
|
105
|
+
</article>
|
|
106
|
+
<article>
|
|
107
|
+
<header>Est. Cost Saved</header>
|
|
108
|
+
<strong class="stat-value">$<%= llmMetrics.summary.cost_saved_usd.toFixed(4) %></strong>
|
|
109
|
+
<span class="stat-unit">at $3/M tokens</span>
|
|
110
|
+
</article>
|
|
111
|
+
<article>
|
|
112
|
+
<header>Fallback Rate</header>
|
|
113
|
+
<strong class="stat-value"><%= llmMetrics.summary.fallback_rate_pct %>%</strong>
|
|
114
|
+
<span class="stat-unit"><%= llmMetrics.summary.fallback_count %> fallbacks</span>
|
|
115
|
+
</article>
|
|
116
|
+
<article>
|
|
117
|
+
<header>Avg Latency</header>
|
|
118
|
+
<strong class="stat-value"><%= llmMetrics.summary.avg_latency_ms %></strong>
|
|
119
|
+
<span class="stat-unit">ms/call</span>
|
|
120
|
+
</article>
|
|
121
|
+
</div>
|
|
122
|
+
<% if (llmMetrics.byOperation && llmMetrics.byOperation.length > 0) { %>
|
|
123
|
+
<div class="overflow-auto" style="margin-top: var(--space-md);">
|
|
124
|
+
<table>
|
|
125
|
+
<thead>
|
|
126
|
+
<tr>
|
|
127
|
+
<th>Operation</th>
|
|
128
|
+
<th>Calls</th>
|
|
129
|
+
<th>Fallbacks</th>
|
|
130
|
+
<th>Tokens Saved</th>
|
|
131
|
+
</tr>
|
|
132
|
+
</thead>
|
|
133
|
+
<tbody>
|
|
134
|
+
<% llmMetrics.byOperation.forEach(op => { %>
|
|
135
|
+
<tr>
|
|
136
|
+
<td><%= op.operation %></td>
|
|
137
|
+
<td><%= op.calls %></td>
|
|
138
|
+
<td><%= op.fallbacks %></td>
|
|
139
|
+
<td><%= op.tokens_saved.toLocaleString() %></td>
|
|
140
|
+
</tr>
|
|
141
|
+
<% }) %>
|
|
142
|
+
</tbody>
|
|
143
|
+
</table>
|
|
144
|
+
</div>
|
|
145
|
+
<% } %>
|
|
146
|
+
<footer style="color: var(--pico-muted-color); font-size: 0.85em;">
|
|
147
|
+
Baseline estimate: each local call replaced ~<%= llmMetrics.baseline.estimated_frontier_tokens_without_local.toLocaleString() %> frontier tokens total.
|
|
148
|
+
Advisory only — no data collected when local LLM is disabled.
|
|
149
|
+
</footer>
|
|
150
|
+
</article>
|
|
151
|
+
<% } %>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
|
|
2
|
+
<h1>Quick Tasks</h1>
|
|
3
|
+
|
|
4
|
+
<% if (typeof tasks !== 'undefined' && tasks.length > 0) { %>
|
|
5
|
+
<article>
|
|
6
|
+
<div class="table-wrap">
|
|
7
|
+
<table>
|
|
8
|
+
<thead>
|
|
9
|
+
<tr>
|
|
10
|
+
<th scope="col">ID</th>
|
|
11
|
+
<th scope="col">Title</th>
|
|
12
|
+
<th scope="col">Status</th>
|
|
13
|
+
</tr>
|
|
14
|
+
</thead>
|
|
15
|
+
<tbody>
|
|
16
|
+
<% tasks.forEach(function(task) { %>
|
|
17
|
+
<tr>
|
|
18
|
+
<td><%= task.id %></td>
|
|
19
|
+
<td>
|
|
20
|
+
<a href="/quick/<%= task.id %>"
|
|
21
|
+
hx-get="/quick/<%= task.id %>"
|
|
22
|
+
hx-target="#main-content"
|
|
23
|
+
hx-push-url="true">
|
|
24
|
+
<%= task.title %>
|
|
25
|
+
</a>
|
|
26
|
+
</td>
|
|
27
|
+
<td>
|
|
28
|
+
<span class="status-badge" data-status="<%= task.status %>">
|
|
29
|
+
<%= task.status %>
|
|
30
|
+
</span>
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
<% }); %>
|
|
34
|
+
</tbody>
|
|
35
|
+
</table>
|
|
36
|
+
</div>
|
|
37
|
+
</article>
|
|
38
|
+
<% } else { %>
|
|
39
|
+
<%- include('empty-state', { icon: '⚡', title: 'No quick tasks found', action: '' }) %>
|
|
40
|
+
<% } %>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<%- include('breadcrumbs', { breadcrumbs: typeof breadcrumbs !== 'undefined' ? breadcrumbs : [] }) %>
|
|
2
|
+
<h1><%= title %></h1>
|
|
3
|
+
|
|
4
|
+
<p><a href="/quick">← Back to Quick Tasks</a></p>
|
|
5
|
+
|
|
6
|
+
<article>
|
|
7
|
+
<header>
|
|
8
|
+
<strong>Quick Task <%= id %></strong>
|
|
9
|
+
|
|
10
|
+
<span class="status-badge" data-status="<%= status %>">
|
|
11
|
+
<%= status %>
|
|
12
|
+
</span>
|
|
13
|
+
</header>
|
|
14
|
+
|
|
15
|
+
<% if (planHtml) { %>
|
|
16
|
+
<section>
|
|
17
|
+
<h2>Plan</h2>
|
|
18
|
+
<%- planHtml %>
|
|
19
|
+
</section>
|
|
20
|
+
<% } %>
|
|
21
|
+
|
|
22
|
+
<% if (summaryHtml) { %>
|
|
23
|
+
<hr>
|
|
24
|
+
<section>
|
|
25
|
+
<h2>Summary</h2>
|
|
26
|
+
<%- summaryHtml %>
|
|
27
|
+
</section>
|
|
28
|
+
<% } %>
|
|
29
|
+
</article>
|
|
@@ -79,6 +79,14 @@
|
|
|
79
79
|
Notes
|
|
80
80
|
</a>
|
|
81
81
|
</li>
|
|
82
|
+
<li>
|
|
83
|
+
<a href="/quick"
|
|
84
|
+
hx-get="/quick"
|
|
85
|
+
hx-target="#main-content"
|
|
86
|
+
hx-push-url="true"<%= typeof activePage !== 'undefined' && activePage === 'quick' ? ' aria-current="page"' : '' %>>
|
|
87
|
+
Quick Tasks
|
|
88
|
+
</a>
|
|
89
|
+
</li>
|
|
82
90
|
</ul>
|
|
83
91
|
</details>
|
|
84
92
|
|
package/package.json
CHANGED
|
@@ -138,6 +138,21 @@ Then emit a `DECISION` checkpoint asking the user to approve, modify, or reject
|
|
|
138
138
|
|
|
139
139
|
**Commit format**: `fix({scope}): {description}` with body: `Root cause: ...` and `Debug session: .planning/debug/{slug}.md`
|
|
140
140
|
|
|
141
|
+
## Local LLM Error Classification (Optional)
|
|
142
|
+
|
|
143
|
+
When you receive an error message or stack trace, you MAY use the local LLM to classify it before starting hypothesis generation. This is advisory — skip it if unavailable.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Write the error to a temp file, then classify:
|
|
147
|
+
echo "Error text here" > /tmp/debug-error.txt
|
|
148
|
+
node "${PLUGIN_ROOT}/scripts/pbr-tools.js" llm classify-error /tmp/debug-error.txt debugger 2>/dev/null
|
|
149
|
+
# Returns: {"category":"missing_output","confidence":0.91,"latency_ms":1840,"fallback_used":false}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Categories: `connection_refused`, `timeout`, `missing_output`, `wrong_output_format`, `permission_error`, `unknown`.
|
|
153
|
+
|
|
154
|
+
If classification succeeds, use the returned category to bias your initial hypothesis ranking. If it returns null or fails, proceed with manual hypothesis generation as normal.
|
|
155
|
+
|
|
141
156
|
## Common Bug Patterns
|
|
142
157
|
|
|
143
158
|
Reference: `references/common-bug-patterns.md` — covers off-by-one, null/undefined, async/timing, state management, import/module, environment, and data shape patterns.
|
|
@@ -54,6 +54,26 @@ All claims must be attributed to a source level. Higher levels override lower le
|
|
|
54
54
|
|
|
55
55
|
**Offline Fallback**: If web tools are unavailable (air-gapped environment, MCP not configured), rely on local sources: codebase analysis via Glob/Grep, existing documentation, and README files. Assign these S3-S4 confidence levels. Do not attempt WebFetch or WebSearch — note in the output header that external sources were unavailable.
|
|
56
56
|
|
|
57
|
+
## Local LLM Source Scoring (Optional)
|
|
58
|
+
|
|
59
|
+
If local LLM offload is configured, you MAY use it to score source credibility instead of manually assigning S-levels. This is advisory — never wait on it or fail if it returns null.
|
|
60
|
+
|
|
61
|
+
Check availability first:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
node "${PLUGIN_ROOT}/scripts/pbr-tools.js" llm status 2>/dev/null
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If `enabled: true`, score a source excerpt:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
echo "Source URL and content excerpt" > /tmp/source-excerpt.txt
|
|
71
|
+
node "${PLUGIN_ROOT}/scripts/pbr-tools.js" llm score-source "https://example.com/docs" /tmp/source-excerpt.txt 2>/dev/null
|
|
72
|
+
# Returns: {"level":"S2","confidence":0.87,"reason":"Official library documentation page"}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use the returned `level` to set your source tag. If the call fails or returns `null`, assign the level manually per the hierarchy table above.
|
|
76
|
+
|
|
57
77
|
---
|
|
58
78
|
|
|
59
79
|
## Confidence Levels
|
|
@@ -98,6 +98,18 @@ conflicts: N
|
|
|
98
98
|
- **Research gaps**: Add `[RESEARCH GAP]` flag, add to Open Questions with high impact, never fabricate
|
|
99
99
|
- **Duplicates**: Consolidate into one entry, note multi-source agreement, reference all documents
|
|
100
100
|
|
|
101
|
+
## Local LLM Context Summarization (Optional)
|
|
102
|
+
|
|
103
|
+
When input research documents are large (>2000 words combined), you MAY use the local LLM to pre-summarize each document before synthesis. This reduces your own context consumption. Advisory only — if unavailable, read documents normally.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Pre-summarize a large research document to ~150 words:
|
|
107
|
+
node "${PLUGIN_ROOT}/scripts/pbr-tools.js" llm summarize /path/to/RESEARCH.md 150 2>/dev/null
|
|
108
|
+
# Returns: {"summary":"...plain text summary under 150 words...","latency_ms":2100,"fallback_used":false}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use the returned `summary` string as your working copy of that document's findings. Still read the original for any specific version numbers, code examples, or direct quotes needed in the output.
|
|
112
|
+
|
|
101
113
|
## Anti-Patterns
|
|
102
114
|
|
|
103
115
|
### Universal Anti-Patterns
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pbr",
|
|
3
3
|
"displayName": "Plan-Build-Run",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.23.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",
|