@sienklogic/plan-build-run 2.34.0 → 2.38.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 +683 -0
- package/dashboard/public/css/command-center.css +152 -65
- package/dashboard/public/css/explorer.css +22 -41
- package/dashboard/public/css/layout.css +119 -1
- package/dashboard/public/css/tokens.css +13 -0
- package/dashboard/src/components/Layout.tsx +32 -6
- package/dashboard/src/components/explorer/tabs/PhasesTab.tsx +11 -1
- package/dashboard/src/components/explorer/tabs/TodosTab.tsx +18 -2
- package/dashboard/src/components/partials/AttentionPanel.tsx +7 -1
- package/dashboard/src/components/partials/CurrentPhaseCard.tsx +26 -24
- package/dashboard/src/components/partials/QuickActions.tsx +21 -11
- package/dashboard/src/components/partials/StatCardGrid.tsx +67 -0
- package/dashboard/src/components/partials/StatusHeader.tsx +1 -0
- package/dashboard/src/routes/command-center.routes.tsx +8 -7
- package/dashboard/src/routes/index.routes.tsx +32 -29
- package/package.json +2 -2
- package/plugins/copilot-pbr/agents/audit.agent.md +129 -16
- package/plugins/copilot-pbr/agents/codebase-mapper.agent.md +49 -1
- package/plugins/copilot-pbr/agents/debugger.agent.md +50 -1
- package/plugins/copilot-pbr/agents/dev-sync.agent.md +23 -0
- package/plugins/copilot-pbr/agents/executor.agent.md +153 -8
- package/plugins/copilot-pbr/agents/general.agent.md +46 -1
- package/plugins/copilot-pbr/agents/integration-checker.agent.md +55 -2
- package/plugins/copilot-pbr/agents/plan-checker.agent.md +50 -2
- package/plugins/copilot-pbr/agents/planner.agent.md +80 -1
- package/plugins/copilot-pbr/agents/researcher.agent.md +50 -2
- package/plugins/copilot-pbr/agents/synthesizer.agent.md +49 -1
- package/plugins/copilot-pbr/agents/verifier.agent.md +114 -13
- package/plugins/copilot-pbr/commands/test.md +5 -0
- package/plugins/copilot-pbr/hooks/hooks.json +11 -0
- package/plugins/copilot-pbr/plugin.json +1 -1
- package/plugins/copilot-pbr/references/agent-contracts.md +27 -0
- package/plugins/copilot-pbr/references/checkpoints.md +32 -1
- package/plugins/copilot-pbr/references/context-quality-tiers.md +45 -0
- package/plugins/copilot-pbr/references/pbr-tools-cli.md +115 -0
- package/plugins/copilot-pbr/references/questioning.md +21 -1
- package/plugins/copilot-pbr/references/verification-patterns.md +96 -18
- package/plugins/copilot-pbr/skills/audit/SKILL.md +19 -3
- package/plugins/copilot-pbr/skills/begin/SKILL.md +57 -4
- package/plugins/copilot-pbr/skills/build/SKILL.md +39 -2
- package/plugins/copilot-pbr/skills/config/SKILL.md +12 -2
- package/plugins/copilot-pbr/skills/debug/SKILL.md +12 -1
- package/plugins/copilot-pbr/skills/explore/SKILL.md +13 -2
- package/plugins/copilot-pbr/skills/health/SKILL.md +13 -5
- package/plugins/copilot-pbr/skills/import/SKILL.md +26 -1
- package/plugins/copilot-pbr/skills/milestone/SKILL.md +15 -3
- package/plugins/copilot-pbr/skills/plan/SKILL.md +50 -0
- package/plugins/copilot-pbr/skills/quick/SKILL.md +21 -0
- package/plugins/copilot-pbr/skills/review/SKILL.md +45 -0
- package/plugins/copilot-pbr/skills/scan/SKILL.md +20 -0
- package/plugins/copilot-pbr/skills/setup/SKILL.md +9 -1
- package/plugins/copilot-pbr/skills/shared/context-budget.md +10 -0
- package/plugins/copilot-pbr/skills/shared/universal-anti-patterns.md +6 -0
- package/plugins/copilot-pbr/skills/test/SKILL.md +210 -0
- package/plugins/copilot-pbr/templates/SUMMARY-complex.md.tmpl +95 -0
- package/plugins/copilot-pbr/templates/SUMMARY-minimal.md.tmpl +48 -0
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-pbr/agents/audit.md +52 -5
- package/plugins/cursor-pbr/agents/codebase-mapper.md +49 -1
- package/plugins/cursor-pbr/agents/debugger.md +50 -1
- package/plugins/cursor-pbr/agents/dev-sync.md +23 -0
- package/plugins/cursor-pbr/agents/executor.md +153 -8
- package/plugins/cursor-pbr/agents/general.md +46 -1
- package/plugins/cursor-pbr/agents/integration-checker.md +54 -1
- package/plugins/cursor-pbr/agents/plan-checker.md +49 -1
- package/plugins/cursor-pbr/agents/planner.md +80 -1
- package/plugins/cursor-pbr/agents/researcher.md +49 -1
- package/plugins/cursor-pbr/agents/synthesizer.md +49 -1
- package/plugins/cursor-pbr/agents/verifier.md +113 -12
- package/plugins/cursor-pbr/commands/test.md +5 -0
- package/plugins/cursor-pbr/hooks/hooks.json +9 -0
- package/plugins/cursor-pbr/references/agent-contracts.md +27 -0
- package/plugins/cursor-pbr/references/checkpoints.md +32 -1
- package/plugins/cursor-pbr/references/context-quality-tiers.md +45 -0
- package/plugins/cursor-pbr/references/pbr-tools-cli.md +115 -0
- package/plugins/cursor-pbr/references/questioning.md +21 -1
- package/plugins/cursor-pbr/references/verification-patterns.md +96 -18
- package/plugins/cursor-pbr/skills/audit/SKILL.md +19 -3
- package/plugins/cursor-pbr/skills/begin/SKILL.md +57 -4
- package/plugins/cursor-pbr/skills/build/SKILL.md +37 -2
- package/plugins/cursor-pbr/skills/config/SKILL.md +12 -2
- package/plugins/cursor-pbr/skills/debug/SKILL.md +12 -1
- package/plugins/cursor-pbr/skills/explore/SKILL.md +13 -2
- package/plugins/cursor-pbr/skills/health/SKILL.md +14 -5
- package/plugins/cursor-pbr/skills/import/SKILL.md +26 -1
- package/plugins/cursor-pbr/skills/milestone/SKILL.md +15 -3
- package/plugins/cursor-pbr/skills/plan/SKILL.md +50 -0
- package/plugins/cursor-pbr/skills/quick/SKILL.md +21 -0
- package/plugins/cursor-pbr/skills/review/SKILL.md +45 -0
- package/plugins/cursor-pbr/skills/scan/SKILL.md +20 -0
- package/plugins/cursor-pbr/skills/setup/SKILL.md +9 -1
- package/plugins/cursor-pbr/skills/shared/context-budget.md +10 -0
- package/plugins/cursor-pbr/skills/shared/universal-anti-patterns.md +6 -0
- package/plugins/cursor-pbr/skills/test/SKILL.md +211 -0
- package/plugins/cursor-pbr/templates/SUMMARY-complex.md.tmpl +95 -0
- package/plugins/cursor-pbr/templates/SUMMARY-minimal.md.tmpl +48 -0
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/agents/audit.md +45 -0
- package/plugins/pbr/agents/codebase-mapper.md +48 -0
- package/plugins/pbr/agents/debugger.md +49 -0
- package/plugins/pbr/agents/dev-sync.md +23 -0
- package/plugins/pbr/agents/executor.md +151 -6
- package/plugins/pbr/agents/general.md +45 -0
- package/plugins/pbr/agents/integration-checker.md +53 -0
- package/plugins/pbr/agents/plan-checker.md +48 -0
- package/plugins/pbr/agents/planner.md +78 -1
- package/plugins/pbr/agents/researcher.md +48 -0
- package/plugins/pbr/agents/synthesizer.md +48 -0
- package/plugins/pbr/agents/verifier.md +112 -11
- package/plugins/pbr/commands/test.md +5 -0
- package/plugins/pbr/hooks/hooks.json +9 -0
- package/plugins/pbr/references/agent-contracts.md +27 -0
- package/plugins/pbr/references/checkpoints.md +32 -0
- package/plugins/pbr/references/context-quality-tiers.md +45 -0
- package/plugins/pbr/references/pbr-tools-cli.md +115 -0
- package/plugins/pbr/references/questioning.md +21 -0
- package/plugins/pbr/references/verification-patterns.md +96 -17
- package/plugins/pbr/scripts/check-plan-format.js +13 -1
- package/plugins/pbr/scripts/check-state-sync.js +26 -7
- package/plugins/pbr/scripts/check-subagent-output.js +30 -2
- package/plugins/pbr/scripts/config-schema.json +11 -1
- package/plugins/pbr/scripts/context-bridge.js +265 -0
- package/plugins/pbr/scripts/lib/config.js +271 -0
- package/plugins/pbr/scripts/lib/core.js +587 -0
- package/plugins/pbr/scripts/lib/history.js +73 -0
- package/plugins/pbr/scripts/lib/init.js +166 -0
- package/plugins/pbr/scripts/lib/migrate.js +169 -0
- package/plugins/pbr/scripts/lib/phase.js +364 -0
- package/plugins/pbr/scripts/lib/roadmap.js +175 -0
- package/plugins/pbr/scripts/lib/state.js +397 -0
- package/plugins/pbr/scripts/lib/todo.js +300 -0
- package/plugins/pbr/scripts/pbr-tools.js +425 -1310
- package/plugins/pbr/scripts/post-write-dispatch.js +5 -4
- package/plugins/pbr/scripts/pre-write-dispatch.js +1 -1
- package/plugins/pbr/scripts/progress-tracker.js +1 -1
- package/plugins/pbr/scripts/suggest-compact.js +1 -1
- package/plugins/pbr/scripts/track-context-budget.js +53 -2
- package/plugins/pbr/scripts/validate-task.js +20 -28
- package/plugins/pbr/skills/audit/SKILL.md +19 -3
- package/plugins/pbr/skills/begin/SKILL.md +48 -2
- package/plugins/pbr/skills/build/SKILL.md +39 -2
- package/plugins/pbr/skills/config/SKILL.md +12 -2
- package/plugins/pbr/skills/debug/SKILL.md +12 -1
- package/plugins/pbr/skills/debug/templates/continuation-prompt.md.tmpl +12 -1
- package/plugins/pbr/skills/debug/templates/initial-investigation-prompt.md.tmpl +12 -5
- package/plugins/pbr/skills/explore/SKILL.md +13 -2
- package/plugins/pbr/skills/health/SKILL.md +14 -3
- package/plugins/pbr/skills/help/SKILL.md +2 -0
- package/plugins/pbr/skills/import/SKILL.md +26 -1
- package/plugins/pbr/skills/milestone/SKILL.md +15 -3
- package/plugins/pbr/skills/plan/SKILL.md +52 -2
- package/plugins/pbr/skills/quick/SKILL.md +21 -0
- package/plugins/pbr/skills/review/SKILL.md +46 -0
- package/plugins/pbr/skills/scan/SKILL.md +20 -0
- package/plugins/pbr/skills/setup/SKILL.md +9 -1
- package/plugins/pbr/skills/shared/context-budget.md +10 -0
- package/plugins/pbr/skills/shared/universal-anti-patterns.md +6 -0
- package/plugins/pbr/skills/test/SKILL.md +212 -0
- package/plugins/pbr/templates/SUMMARY-complex.md.tmpl +95 -0
- package/plugins/pbr/templates/SUMMARY-minimal.md.tmpl +48 -0
|
@@ -4,9 +4,9 @@ Reference patterns for deriving verification criteria from goals. Used by the pl
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## The
|
|
7
|
+
## The Four-Layer Check
|
|
8
8
|
|
|
9
|
-
Every must-have is verified through
|
|
9
|
+
Every must-have is verified through up to four layers, checked in order:
|
|
10
10
|
|
|
11
11
|
### Layer 1: Existence
|
|
12
12
|
|
|
@@ -62,6 +62,28 @@ grep -q "prisma" src/app.ts
|
|
|
62
62
|
grep -q "DISCORD_CLIENT_ID" src/auth/discord.ts
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
### Layer 4: Functional
|
|
66
|
+
|
|
67
|
+
Does the artifact actually work when executed?
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Tests pass
|
|
71
|
+
npm test -- --testPathPattern auth
|
|
72
|
+
pytest tests/test_auth.py -v
|
|
73
|
+
|
|
74
|
+
# Build succeeds
|
|
75
|
+
npm run build
|
|
76
|
+
npx tsc --noEmit
|
|
77
|
+
|
|
78
|
+
# API returns correct data
|
|
79
|
+
curl -s http://localhost:3000/api/auth/login -X POST -d '{"code":"test"}' | jq '.token'
|
|
80
|
+
|
|
81
|
+
# CLI produces expected output
|
|
82
|
+
node src/cli.js --help | grep -q "Usage:"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**When to apply L4:** Only when automated verification commands exist (test suites, build scripts, API endpoints with test data). Skip for items requiring manual/visual testing. L4 is optional — artifacts passing L1-L3 without available automated tests are reported as `PASSED (L3 only)`.
|
|
86
|
+
|
|
65
87
|
---
|
|
66
88
|
|
|
67
89
|
## Verification by Feature Type
|
|
@@ -69,41 +91,46 @@ grep -q "DISCORD_CLIENT_ID" src/auth/discord.ts
|
|
|
69
91
|
### API Endpoint
|
|
70
92
|
|
|
71
93
|
```
|
|
72
|
-
Existence:
|
|
73
|
-
Substance:
|
|
74
|
-
Wiring:
|
|
94
|
+
Existence: curl returns non-404 status
|
|
95
|
+
Substance: curl returns expected response shape (correct fields)
|
|
96
|
+
Wiring: endpoint calls the right service, middleware is applied
|
|
97
|
+
Functional: POST/GET with test data returns correct response, error cases handled
|
|
75
98
|
```
|
|
76
99
|
|
|
77
100
|
### Database Schema
|
|
78
101
|
|
|
79
102
|
```
|
|
80
|
-
Existence:
|
|
81
|
-
Substance:
|
|
82
|
-
Wiring:
|
|
103
|
+
Existence: table/collection exists, can query without error
|
|
104
|
+
Substance: columns/fields match specification, constraints are applied
|
|
105
|
+
Wiring: application code references the schema, migrations run cleanly
|
|
106
|
+
Functional: CRUD operations work end-to-end, constraints reject invalid data
|
|
83
107
|
```
|
|
84
108
|
|
|
85
109
|
### Authentication
|
|
86
110
|
|
|
87
111
|
```
|
|
88
|
-
Existence:
|
|
89
|
-
Substance:
|
|
90
|
-
Wiring:
|
|
112
|
+
Existence: auth routes exist, auth module exports functions
|
|
113
|
+
Substance: login flow returns token, invalid creds return error
|
|
114
|
+
Wiring: protected routes use auth middleware, tokens are validated
|
|
115
|
+
Functional: auth tests pass (valid token, expired token, missing token, malformed token)
|
|
91
116
|
```
|
|
92
117
|
|
|
93
118
|
### UI Component
|
|
94
119
|
|
|
95
120
|
```
|
|
96
|
-
Existence:
|
|
97
|
-
Substance:
|
|
98
|
-
Wiring:
|
|
121
|
+
Existence: component file exists, exports default component
|
|
122
|
+
Substance: component renders expected elements (test or visual check)
|
|
123
|
+
Wiring: component is imported in parent, receives correct props, routes to it
|
|
124
|
+
Functional: component tests pass, build succeeds with component included
|
|
99
125
|
```
|
|
100
126
|
|
|
101
127
|
### Configuration
|
|
102
128
|
|
|
103
129
|
```
|
|
104
|
-
Existence:
|
|
105
|
-
Substance:
|
|
106
|
-
Wiring:
|
|
130
|
+
Existence: config file exists, environment variables documented
|
|
131
|
+
Substance: config values are used (not dead code), defaults are sensible
|
|
132
|
+
Wiring: application reads config at startup, config changes take effect
|
|
133
|
+
Functional: app starts with config, missing config produces clear error message
|
|
107
134
|
```
|
|
108
135
|
|
|
109
136
|
---
|
|
@@ -196,3 +223,55 @@ Bad: "Tests pass"
|
|
|
196
223
|
Good: "All 5 auth middleware tests pass: valid token, expired token,
|
|
197
224
|
missing token, malformed token, and correct user extraction"
|
|
198
225
|
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Wiring Verification Patterns
|
|
230
|
+
|
|
231
|
+
4 concrete patterns for verifying components are actually connected, not just present.
|
|
232
|
+
|
|
233
|
+
### Pattern 1: Component to API
|
|
234
|
+
1. Find the fetch/axios call in the component
|
|
235
|
+
2. Verify the call is NOT commented out
|
|
236
|
+
3. Verify the response is assigned to state (not discarded)
|
|
237
|
+
4. Verify error handling exists (try/catch or .catch)
|
|
238
|
+
|
|
239
|
+
### Pattern 2: API to Database
|
|
240
|
+
1. Find the database query in the route handler
|
|
241
|
+
2. Verify `await` is present (not fire-and-forget)
|
|
242
|
+
3. Verify the result is returned in the response (not discarded)
|
|
243
|
+
4. Verify error cases return appropriate HTTP status codes
|
|
244
|
+
|
|
245
|
+
### Pattern 3: Form to Handler
|
|
246
|
+
1. Find the form's onSubmit handler
|
|
247
|
+
2. Verify it calls an API function (not just preventDefault)
|
|
248
|
+
3. Verify form validation runs before the API call
|
|
249
|
+
4. Verify success/error feedback is shown to the user
|
|
250
|
+
|
|
251
|
+
### Pattern 4: State to Render
|
|
252
|
+
1. Find state variables (useState, store, etc.)
|
|
253
|
+
2. Verify they appear in JSX/template via .map(), interpolation, or conditional rendering
|
|
254
|
+
3. Verify loading/error states are rendered (not just success state)
|
|
255
|
+
4. Verify empty state is handled (not just "no data" crash)
|
|
256
|
+
|
|
257
|
+
### Quick Verification Checklists
|
|
258
|
+
|
|
259
|
+
**Component Checklist (8 items):**
|
|
260
|
+
- [ ] Component file exists and exports correctly
|
|
261
|
+
- [ ] Props/types are defined (not `any`)
|
|
262
|
+
- [ ] API calls use actual endpoints (not hardcoded data)
|
|
263
|
+
- [ ] Loading state renders something meaningful
|
|
264
|
+
- [ ] Error state renders something meaningful
|
|
265
|
+
- [ ] Empty state renders something meaningful
|
|
266
|
+
- [ ] User interactions trigger actual handlers
|
|
267
|
+
- [ ] Component is imported and rendered in parent
|
|
268
|
+
|
|
269
|
+
**API Route Checklist (8 items):**
|
|
270
|
+
- [ ] Route file exists and exports handler
|
|
271
|
+
- [ ] Route is registered in router/app
|
|
272
|
+
- [ ] Request validation exists (body, params, query)
|
|
273
|
+
- [ ] Database query uses parameterized inputs
|
|
274
|
+
- [ ] Success response includes expected data shape
|
|
275
|
+
- [ ] Error response includes status code and message
|
|
276
|
+
- [ ] Authentication/authorization check exists if needed
|
|
277
|
+
- [ ] Response matches what the frontend expects
|
|
@@ -194,7 +194,7 @@ function validatePlan(content, _filePath) {
|
|
|
194
194
|
|
|
195
195
|
// Skip checkpoint tasks - they have different required elements
|
|
196
196
|
const taskTag = taskTags[index] || '';
|
|
197
|
-
if (taskTag.
|
|
197
|
+
if (/\btype\s*=\s*["']?checkpoint/i.test(taskTag) || /\bcheckpoint\s*[:=]/i.test(taskTag)) {
|
|
198
198
|
return; // Checkpoint tasks have different structure
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -205,6 +205,18 @@ function validatePlan(content, _filePath) {
|
|
|
205
205
|
}
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
+
// Path traversal check: ensure <files> elements don't escape project root
|
|
209
|
+
const filesTags = content.match(/<files>([\s\S]*?)<\/files>/g) || [];
|
|
210
|
+
for (const filesTag of filesTags) {
|
|
211
|
+
const filesContent = filesTag.replace(/<\/?files>/g, '');
|
|
212
|
+
const paths = filesContent.split(/[\n,]/).map(p => p.trim()).filter(Boolean);
|
|
213
|
+
for (const p of paths) {
|
|
214
|
+
if (p.includes('..') || path.isAbsolute(p.replace(/^[A-Za-z]:/, ''))) {
|
|
215
|
+
warnings.push(`Path traversal risk in <files>: "${p}" — use relative paths without ".." segments`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
208
220
|
return { errors, warnings };
|
|
209
221
|
}
|
|
210
222
|
|
|
@@ -26,7 +26,25 @@ const fs = require('fs');
|
|
|
26
26
|
const path = require('path');
|
|
27
27
|
const { logHook } = require('./hook-logger');
|
|
28
28
|
const { logEvent } = require('./event-logger');
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Write content to a file atomically using write-then-rename.
|
|
32
|
+
* Writes to a PID-stamped temp file, then renames over the original.
|
|
33
|
+
* If the rename fails, cleans up the temp file and re-throws.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} filePath - Target file path
|
|
36
|
+
* @param {string} content - Content to write
|
|
37
|
+
*/
|
|
38
|
+
function atomicWriteFile(filePath, content) {
|
|
39
|
+
const tmpPath = filePath + '.tmp.' + process.pid;
|
|
40
|
+
try {
|
|
41
|
+
fs.writeFileSync(tmpPath, content, 'utf8');
|
|
42
|
+
fs.renameSync(tmpPath, filePath);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
try { fs.unlinkSync(tmpPath); } catch (_) { /* best effort cleanup */ }
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
30
48
|
|
|
31
49
|
/**
|
|
32
50
|
* Extract phase number from a phase directory name.
|
|
@@ -326,7 +344,7 @@ function checkStateSync(data) {
|
|
|
326
344
|
return null;
|
|
327
345
|
}
|
|
328
346
|
|
|
329
|
-
const cwd = process.cwd();
|
|
347
|
+
const cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
|
|
330
348
|
const planningDir = path.join(cwd, '.planning');
|
|
331
349
|
const roadmapPath = path.join(planningDir, 'ROADMAP.md');
|
|
332
350
|
const statePath = path.join(planningDir, 'STATE.md');
|
|
@@ -370,7 +388,7 @@ function checkStateSync(data) {
|
|
|
370
388
|
} else {
|
|
371
389
|
const updatedRoadmap = updateProgressTable(roadmapContent, phaseNum, plansComplete, newStatus, completedDate);
|
|
372
390
|
if (updatedRoadmap !== roadmapContent) {
|
|
373
|
-
|
|
391
|
+
atomicWriteFile(roadmapPath, updatedRoadmap);
|
|
374
392
|
messages.push(`ROADMAP.md: Phase ${phaseNum} → ${plansComplete} plans, ${newStatus}`);
|
|
375
393
|
}
|
|
376
394
|
}
|
|
@@ -410,7 +428,7 @@ function checkStateSync(data) {
|
|
|
410
428
|
|
|
411
429
|
const updatedState = updateStatePosition(stateContent, stateUpdates);
|
|
412
430
|
if (updatedState !== stateContent) {
|
|
413
|
-
|
|
431
|
+
atomicWriteFile(statePath, updatedState);
|
|
414
432
|
messages.push(`STATE.md: ${artifacts.completeSummaries}/${artifacts.plans} plans, ${overallPct}%`);
|
|
415
433
|
}
|
|
416
434
|
} catch (e) {
|
|
@@ -455,7 +473,7 @@ function checkStateSync(data) {
|
|
|
455
473
|
} else {
|
|
456
474
|
const updatedRoadmap = updateProgressTable(roadmapContent, phaseNum, plansComplete, roadmapStatus, completedDate);
|
|
457
475
|
if (updatedRoadmap !== roadmapContent) {
|
|
458
|
-
|
|
476
|
+
atomicWriteFile(roadmapPath, updatedRoadmap);
|
|
459
477
|
messages.push(`ROADMAP.md: Phase ${phaseNum} → ${roadmapStatus}`);
|
|
460
478
|
}
|
|
461
479
|
}
|
|
@@ -493,7 +511,7 @@ function checkStateSync(data) {
|
|
|
493
511
|
|
|
494
512
|
const updatedState = updateStatePosition(stateContent, stateUpdates);
|
|
495
513
|
if (updatedState !== stateContent) {
|
|
496
|
-
|
|
514
|
+
atomicWriteFile(statePath, updatedState);
|
|
497
515
|
messages.push(`STATE.md: ${stateStatus}, ${overallPct}%`);
|
|
498
516
|
}
|
|
499
517
|
} catch (e) {
|
|
@@ -543,7 +561,7 @@ function checkStateSync(data) {
|
|
|
543
561
|
const plansComplete = `${artifacts.completeSummaries}/${artifacts.plans}`;
|
|
544
562
|
const updatedRoadmap = updateProgressTable(roadmapContent, phaseNum, plansComplete, 'Planning', null);
|
|
545
563
|
if (updatedRoadmap !== roadmapContent) {
|
|
546
|
-
|
|
564
|
+
atomicWriteFile(roadmapPath, updatedRoadmap);
|
|
547
565
|
messages.push(`ROADMAP.md: Phase ${phaseNum} → Planning`);
|
|
548
566
|
}
|
|
549
567
|
}
|
|
@@ -588,6 +606,7 @@ function main() {
|
|
|
588
606
|
|
|
589
607
|
if (require.main === module || process.argv[1] === __filename) { main(); }
|
|
590
608
|
module.exports = {
|
|
609
|
+
atomicWriteFile,
|
|
591
610
|
extractPhaseNum,
|
|
592
611
|
countPhaseArtifacts,
|
|
593
612
|
updateProgressTable,
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const path = require('path');
|
|
22
22
|
const { logHook } = require('./hook-logger');
|
|
23
|
+
const { KNOWN_AGENTS } = require('./pbr-tools');
|
|
23
24
|
const { resolveConfig } = require('./local-llm/health');
|
|
24
25
|
const { classifyError } = require('./local-llm/operations/classify-error');
|
|
25
26
|
|
|
@@ -27,7 +28,7 @@ const { classifyError } = require('./local-llm/operations/classify-error');
|
|
|
27
28
|
* Check if a file was modified recently (within thresholdMs).
|
|
28
29
|
* Returns false if file doesn't exist or on error.
|
|
29
30
|
*/
|
|
30
|
-
function isRecent(filePath, thresholdMs =
|
|
31
|
+
function isRecent(filePath, thresholdMs = 1800000) {
|
|
31
32
|
try {
|
|
32
33
|
const stat = fs.statSync(filePath);
|
|
33
34
|
return (Date.now() - stat.mtimeMs) < thresholdMs;
|
|
@@ -151,6 +152,25 @@ const AGENT_OUTPUTS = {
|
|
|
151
152
|
description: 'advisory output (no file expected)',
|
|
152
153
|
noFileExpected: true,
|
|
153
154
|
check: () => []
|
|
155
|
+
},
|
|
156
|
+
'pbr:audit': {
|
|
157
|
+
description: 'audit report in .planning/audits/',
|
|
158
|
+
check: (planningDir) => {
|
|
159
|
+
const auditsDir = path.join(planningDir, 'audits');
|
|
160
|
+
if (!fs.existsSync(auditsDir)) return [];
|
|
161
|
+
try {
|
|
162
|
+
return fs.readdirSync(auditsDir)
|
|
163
|
+
.filter(f => f.endsWith('.md'))
|
|
164
|
+
.map(f => path.join('audits', f));
|
|
165
|
+
} catch (_e) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
'pbr:dev-sync': {
|
|
171
|
+
description: 'advisory output (no file expected)',
|
|
172
|
+
noFileExpected: true,
|
|
173
|
+
check: () => []
|
|
154
174
|
}
|
|
155
175
|
};
|
|
156
176
|
|
|
@@ -324,7 +344,7 @@ function loadLocalLlmConfig(cwd) {
|
|
|
324
344
|
|
|
325
345
|
async function main() {
|
|
326
346
|
const data = readStdin();
|
|
327
|
-
const cwd = process.cwd();
|
|
347
|
+
const cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
|
|
328
348
|
const planningDir = path.join(cwd, '.planning');
|
|
329
349
|
|
|
330
350
|
// Only relevant for Plan-Build-Run projects
|
|
@@ -338,6 +358,14 @@ async function main() {
|
|
|
338
358
|
// Only check known Plan-Build-Run agent types
|
|
339
359
|
const outputSpec = AGENT_OUTPUTS[agentType];
|
|
340
360
|
if (!outputSpec) {
|
|
361
|
+
// Log when agent is in KNOWN_AGENTS but missing from AGENT_OUTPUTS
|
|
362
|
+
const shortName = agentType.startsWith('pbr:') ? agentType.slice(4) : agentType;
|
|
363
|
+
if (KNOWN_AGENTS && KNOWN_AGENTS.includes && KNOWN_AGENTS.includes(shortName)) {
|
|
364
|
+
logHook('check-subagent-output', 'PostToolUse', 'missing-output-spec', {
|
|
365
|
+
agent_type: agentType,
|
|
366
|
+
message: `Agent ${agentType} is in KNOWN_AGENTS but has no AGENT_OUTPUTS entry. Add one to check-subagent-output.js.`
|
|
367
|
+
});
|
|
368
|
+
}
|
|
341
369
|
process.exit(0);
|
|
342
370
|
}
|
|
343
371
|
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"description": "Configuration schema for .planning/config.json",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
|
-
"version": { "type": "integer", "enum": [1, 2] },
|
|
7
|
+
"version": { "type": ["integer", "string"], "enum": [1, 2, "1", "2"], "description": "Planning format version. v2 is current." },
|
|
8
|
+
"schema_version": { "type": "integer", "enum": [1], "description": "Config schema version for migration detection. Increment when config structure changes." },
|
|
8
9
|
"context_strategy": { "type": "string", "enum": ["aggressive", "conservative", "balanced"] },
|
|
9
10
|
"mode": { "type": "string", "enum": ["interactive", "autonomous"] },
|
|
10
11
|
"depth": { "type": "string", "enum": ["quick", "standard", "comprehensive"], "description": "Workflow depth: quick = budget mode (fewer spawns, skip optional stages), standard = balanced mode (conditional spawns), comprehensive = thorough mode (all spawns, current default)" },
|
|
@@ -131,6 +132,15 @@
|
|
|
131
132
|
},
|
|
132
133
|
"additionalProperties": false
|
|
133
134
|
},
|
|
135
|
+
"timeouts": {
|
|
136
|
+
"type": "object",
|
|
137
|
+
"properties": {
|
|
138
|
+
"task_default_ms": { "type": "integer", "minimum": 30000, "description": "Default timeout per task in milliseconds (default: 300000 = 5 min)" },
|
|
139
|
+
"build_max_ms": { "type": "integer", "minimum": 60000, "description": "Maximum time for entire build command in milliseconds" },
|
|
140
|
+
"verify_max_ms": { "type": "integer", "minimum": 30000, "description": "Maximum time for verification in milliseconds" }
|
|
141
|
+
},
|
|
142
|
+
"additionalProperties": false
|
|
143
|
+
},
|
|
134
144
|
"hooks": {
|
|
135
145
|
"type": "object",
|
|
136
146
|
"properties": {
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostToolUse hook: Context monitor bridge.
|
|
5
|
+
*
|
|
6
|
+
* Replaces heuristic context budget estimation with real data when available.
|
|
7
|
+
* Writes context state to .planning/.context-budget.json for consumption by
|
|
8
|
+
* track-context-budget.js and suggest-compact.js.
|
|
9
|
+
*
|
|
10
|
+
* Context tiers:
|
|
11
|
+
* PEAK (0-30%) — no warnings
|
|
12
|
+
* GOOD (30-50%) — no warnings
|
|
13
|
+
* DEGRADING (50-70%) — suggest subagent delegation
|
|
14
|
+
* POOR (70-85%) — recommend /pbr:pause
|
|
15
|
+
* CRITICAL (85%+) — urgent stop, context rot imminent
|
|
16
|
+
*
|
|
17
|
+
* Debounce: same-tier warnings suppressed for 5 tool calls (2 for CRITICAL).
|
|
18
|
+
* Tier escalation always warns immediately.
|
|
19
|
+
*
|
|
20
|
+
* Exit codes:
|
|
21
|
+
* 0 = always (PostToolUse hook, advisory only)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const { logHook } = require('./hook-logger');
|
|
27
|
+
|
|
28
|
+
const TIERS = [
|
|
29
|
+
{ name: 'PEAK', min: 0, max: 30 },
|
|
30
|
+
{ name: 'GOOD', min: 30, max: 50 },
|
|
31
|
+
{ name: 'DEGRADING', min: 50, max: 70 },
|
|
32
|
+
{ name: 'POOR', min: 70, max: 85 },
|
|
33
|
+
{ name: 'CRITICAL', min: 85, max: 100 }
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const TIER_MESSAGES = {
|
|
37
|
+
DEGRADING: 'Context at ~50-70%. Delegate heavy reads and analysis to Task() subagents to preserve orchestrator quality.',
|
|
38
|
+
POOR: 'Context at ~70-85%. Run /pbr:pause soon to save state before quality degrades.',
|
|
39
|
+
CRITICAL: 'STOP — Context at 85%+. Run /pbr:pause NOW. Context rot is imminent — further work risks hallucinations and skipped steps.'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DEBOUNCE_INTERVAL = 5; // tool calls between same-tier warnings
|
|
43
|
+
const CRITICAL_DEBOUNCE_INTERVAL = 2; // shorter debounce for CRITICAL tier
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Determine the context tier for a given percentage.
|
|
47
|
+
* @param {number} percent - Context usage percentage (0-100)
|
|
48
|
+
* @returns {{ name: string, min: number, max: number }}
|
|
49
|
+
*/
|
|
50
|
+
function getTier(percent) {
|
|
51
|
+
for (const tier of TIERS) {
|
|
52
|
+
if (percent < tier.max) return tier;
|
|
53
|
+
}
|
|
54
|
+
return TIERS[TIERS.length - 1];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load the bridge state file.
|
|
59
|
+
* @param {string} bridgePath - Absolute path to .context-budget.json
|
|
60
|
+
* @returns {Object} Bridge state
|
|
61
|
+
*/
|
|
62
|
+
function loadBridge(bridgePath) {
|
|
63
|
+
try {
|
|
64
|
+
const content = fs.readFileSync(bridgePath, 'utf8');
|
|
65
|
+
return JSON.parse(content);
|
|
66
|
+
} catch (_e) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Save the bridge state file atomically.
|
|
73
|
+
* @param {string} bridgePath - Absolute path to .context-budget.json
|
|
74
|
+
* @param {Object} data - Bridge state to persist
|
|
75
|
+
*/
|
|
76
|
+
function saveBridge(bridgePath, data) {
|
|
77
|
+
try {
|
|
78
|
+
const tmpPath = bridgePath + '.' + process.pid;
|
|
79
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf8');
|
|
80
|
+
fs.renameSync(tmpPath, bridgePath);
|
|
81
|
+
} catch (_e) {
|
|
82
|
+
// Best-effort — clean up temp file if rename failed
|
|
83
|
+
try { fs.unlinkSync(bridgePath + '.' + process.pid); } catch (_e2) { /* best-effort */ }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Estimate context percentage from heuristic tracker data.
|
|
89
|
+
* Uses the .context-tracker file written by track-context-budget.js.
|
|
90
|
+
* Assumes 200k token context window (~800k chars).
|
|
91
|
+
*
|
|
92
|
+
* @param {string} planningDir - Path to .planning/
|
|
93
|
+
* @returns {number} Estimated context usage percentage (0-100)
|
|
94
|
+
*/
|
|
95
|
+
function estimateFromHeuristic(planningDir) {
|
|
96
|
+
const trackerPath = path.join(planningDir, '.context-tracker');
|
|
97
|
+
try {
|
|
98
|
+
const content = fs.readFileSync(trackerPath, 'utf8');
|
|
99
|
+
const tracker = JSON.parse(content);
|
|
100
|
+
const totalChars = tracker.total_chars || 0;
|
|
101
|
+
// 200k tokens ~ 800k chars; use 800000 as denominator
|
|
102
|
+
const percent = Math.min(100, Math.round((totalChars / 800000) * 100));
|
|
103
|
+
return percent;
|
|
104
|
+
} catch (_e) {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if a tier warning should fire, applying debounce logic.
|
|
111
|
+
*
|
|
112
|
+
* @param {Object} bridge - Current bridge state
|
|
113
|
+
* @param {string} tierName - Current tier name
|
|
114
|
+
* @returns {boolean} True if warning should fire
|
|
115
|
+
*/
|
|
116
|
+
function shouldWarn(bridge, tierName) {
|
|
117
|
+
// No warning for PEAK or GOOD
|
|
118
|
+
if (tierName === 'PEAK' || tierName === 'GOOD') return false;
|
|
119
|
+
|
|
120
|
+
const prevTier = bridge.last_warned_tier || 'PEAK';
|
|
121
|
+
const callsSinceWarn = bridge.calls_since_warn || 0;
|
|
122
|
+
|
|
123
|
+
// Tier escalation — always warn
|
|
124
|
+
const tierOrder = { PEAK: 0, GOOD: 1, DEGRADING: 2, POOR: 3, CRITICAL: 4 };
|
|
125
|
+
if ((tierOrder[tierName] || 0) > (tierOrder[prevTier] || 0)) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Same tier — debounce (CRITICAL uses shorter interval)
|
|
130
|
+
const interval = tierName === 'CRITICAL' ? CRITICAL_DEBOUNCE_INTERVAL : DEBOUNCE_INTERVAL;
|
|
131
|
+
if (callsSinceWarn >= interval) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Core bridge logic: update bridge state, return warning if applicable.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} planningDir - Path to .planning/
|
|
142
|
+
* @param {Object} stdinData - Parsed stdin JSON from Claude Code
|
|
143
|
+
* @returns {{ bridge: Object, output: Object|null }} Updated bridge and optional warning output
|
|
144
|
+
*/
|
|
145
|
+
function updateBridge(planningDir, stdinData) {
|
|
146
|
+
const bridgePath = path.join(planningDir, '.context-budget.json');
|
|
147
|
+
|
|
148
|
+
// Check if Claude Code provides real context percentage in stdin
|
|
149
|
+
// Look for context_percent, usage_percent, context_usage, or similar fields
|
|
150
|
+
const contextPercent = stdinData.context_percent
|
|
151
|
+
|| stdinData.usage_percent
|
|
152
|
+
|| (stdinData.context && stdinData.context.percent)
|
|
153
|
+
|| null;
|
|
154
|
+
|
|
155
|
+
const source = contextPercent !== null ? 'bridge' : 'heuristic';
|
|
156
|
+
const estimatedPercent = contextPercent !== null
|
|
157
|
+
? Math.round(contextPercent)
|
|
158
|
+
: estimateFromHeuristic(planningDir);
|
|
159
|
+
|
|
160
|
+
// Load existing bridge state
|
|
161
|
+
let bridge = loadBridge(bridgePath) || {
|
|
162
|
+
timestamp: new Date().toISOString(),
|
|
163
|
+
estimated_percent: 0,
|
|
164
|
+
source: 'heuristic',
|
|
165
|
+
chars_read: 0,
|
|
166
|
+
warnings_issued: [],
|
|
167
|
+
last_warned_tier: 'PEAK',
|
|
168
|
+
calls_since_warn: 0,
|
|
169
|
+
tool_calls: 0
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Update bridge
|
|
173
|
+
bridge.timestamp = new Date().toISOString();
|
|
174
|
+
bridge.estimated_percent = estimatedPercent;
|
|
175
|
+
bridge.source = source;
|
|
176
|
+
bridge.tool_calls = (bridge.tool_calls || 0) + 1;
|
|
177
|
+
bridge.calls_since_warn = (bridge.calls_since_warn || 0) + 1;
|
|
178
|
+
|
|
179
|
+
// Read chars from tracker if available
|
|
180
|
+
const trackerPath = path.join(planningDir, '.context-tracker');
|
|
181
|
+
try {
|
|
182
|
+
const tracker = JSON.parse(fs.readFileSync(trackerPath, 'utf8'));
|
|
183
|
+
bridge.chars_read = tracker.total_chars || 0;
|
|
184
|
+
} catch (_e) {
|
|
185
|
+
// Keep existing value
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const tier = getTier(estimatedPercent);
|
|
189
|
+
let output = null;
|
|
190
|
+
|
|
191
|
+
if (shouldWarn(bridge, tier.name)) {
|
|
192
|
+
const msg = TIER_MESSAGES[tier.name];
|
|
193
|
+
if (msg) {
|
|
194
|
+
bridge.last_warned_tier = tier.name;
|
|
195
|
+
bridge.calls_since_warn = 0;
|
|
196
|
+
bridge.warnings_issued = bridge.warnings_issued || [];
|
|
197
|
+
bridge.warnings_issued.push({
|
|
198
|
+
tier: tier.name,
|
|
199
|
+
percent: estimatedPercent,
|
|
200
|
+
timestamp: new Date().toISOString()
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Keep only last 20 warnings
|
|
204
|
+
if (bridge.warnings_issued.length > 20) {
|
|
205
|
+
bridge.warnings_issued = bridge.warnings_issued.slice(-20);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
output = {
|
|
209
|
+
additionalContext: `[Context Monitor — ${tier.name}] ${estimatedPercent}% used (${source}). ${msg}`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Save bridge state
|
|
215
|
+
saveBridge(bridgePath, bridge);
|
|
216
|
+
|
|
217
|
+
return { bridge, output };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function main() {
|
|
221
|
+
let input = '';
|
|
222
|
+
|
|
223
|
+
process.stdin.setEncoding('utf8');
|
|
224
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
225
|
+
process.stdin.on('end', () => {
|
|
226
|
+
try {
|
|
227
|
+
const cwd = process.cwd();
|
|
228
|
+
const planningDir = path.join(cwd, '.planning');
|
|
229
|
+
if (!fs.existsSync(planningDir)) {
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const data = input ? JSON.parse(input) : {};
|
|
234
|
+
const { output } = updateBridge(planningDir, data);
|
|
235
|
+
|
|
236
|
+
if (output) {
|
|
237
|
+
logHook('context-bridge', 'PostToolUse', 'warn', {
|
|
238
|
+
percent: output.additionalContext.match(/(\d+)%/)?.[1],
|
|
239
|
+
source: 'bridge'
|
|
240
|
+
});
|
|
241
|
+
process.stdout.write(JSON.stringify(output));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
process.exit(0);
|
|
245
|
+
} catch (_e) {
|
|
246
|
+
// Never block on tracking errors
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
getTier,
|
|
254
|
+
loadBridge,
|
|
255
|
+
saveBridge,
|
|
256
|
+
estimateFromHeuristic,
|
|
257
|
+
shouldWarn,
|
|
258
|
+
updateBridge,
|
|
259
|
+
TIERS,
|
|
260
|
+
TIER_MESSAGES,
|
|
261
|
+
DEBOUNCE_INTERVAL,
|
|
262
|
+
CRITICAL_DEBOUNCE_INTERVAL
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
if (require.main === module || process.argv[1] === __filename) { main(); }
|