@sienklogic/plan-build-run 2.19.1 → 2.19.2
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 +39 -0
- package/CLAUDE.md +29 -16
- package/README.md +3 -3
- package/dashboard/server/index.js +10 -1
- package/dashboard/server/routes/agents.js +23 -2
- package/dashboard/server/routes/health.js +7 -4
- package/dashboard/server/routes/telemetry.js +20 -1
- package/dashboard/server/services/planning-reader.js +3 -17
- package/package.json +1 -1
- package/plan-build-run/bin/config-schema.json +23 -145
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/agents/advisor-researcher.md +1 -0
- package/plugins/pbr/agents/debugger.md +0 -4
- package/plugins/pbr/agents/researcher.md +0 -4
- package/plugins/pbr/agents/synthesizer.md +0 -4
- package/plugins/pbr/dist/check-config-change.js +0 -7
- package/plugins/pbr/dist/check-cross-plugin-sync.js +1 -1
- package/plugins/pbr/dist/check-plan-format.js +0 -32
- package/plugins/pbr/dist/check-roadmap-sync.js +15 -11
- package/plugins/pbr/dist/check-subagent-output.js +4 -60
- package/plugins/pbr/dist/check-summary-gate.js +3 -14
- package/plugins/pbr/dist/feedback-loop.js +12 -29
- package/plugins/pbr/dist/hook-server.js +58 -6
- package/plugins/pbr/dist/milestone-learnings.js +6 -56
- package/plugins/pbr/dist/pbr-tools.js +8 -91
- package/plugins/pbr/dist/post-bash-triage.js +5 -63
- package/plugins/pbr/dist/post-hoc.js +3 -52
- package/plugins/pbr/dist/post-write-dispatch.js +0 -36
- package/plugins/pbr/dist/pre-bash-dispatch.js +1 -7
- package/plugins/pbr/dist/pre-task-dispatch.js +0 -28
- package/plugins/pbr/dist/progress-tracker.js +2 -27
- package/plugins/pbr/dist/session-cleanup.js +1 -31
- package/plugins/pbr/dist/status-line.js +13 -11
- package/plugins/pbr/dist/suggest-compact.js +2 -10
- package/plugins/pbr/dist/validate-commit.js +8 -64
- package/plugins/pbr/dist/validate-task.js +0 -30
- package/plugins/pbr/references/config-reference.md +0 -96
- package/plugins/pbr/scripts/audit-checks/si-agent-hook-config-checks.js +2 -72
- package/plugins/pbr/scripts/audit-checks/workflow-compliance.js +5 -41
- package/plugins/pbr/scripts/check-config-change.js +0 -7
- package/plugins/pbr/scripts/check-cross-plugin-sync.js +1 -1
- package/plugins/pbr/scripts/check-plan-format.js +0 -32
- package/plugins/pbr/scripts/check-roadmap-sync.js +15 -11
- package/plugins/pbr/scripts/check-subagent-output.js +4 -60
- package/plugins/pbr/scripts/check-summary-gate.js +3 -14
- package/plugins/pbr/scripts/config-schema.json +16 -129
- package/plugins/pbr/scripts/feedback-loop.js +12 -29
- package/plugins/pbr/scripts/hook-server.js +58 -6
- package/plugins/pbr/scripts/lib/config.js +4 -11
- package/plugins/pbr/scripts/lib/contextual-help.js +5 -29
- package/plugins/pbr/scripts/lib/format-validators.js +1 -26
- package/plugins/pbr/scripts/lib/frontmatter.js +4 -4
- package/plugins/pbr/scripts/lib/gates/rich-agent-context.js +13 -19
- package/plugins/pbr/scripts/lib/health.js +4 -5
- package/plugins/pbr/scripts/lib/help.js +3 -54
- package/plugins/pbr/scripts/lib/phase.js +2 -4
- package/plugins/pbr/scripts/lib/pre-commit-checks.js +1 -1
- package/plugins/pbr/scripts/lib/pre-research.js +10 -17
- package/plugins/pbr/scripts/lib/roadmap.js +11 -35
- package/plugins/pbr/scripts/lib/smart-next-task.js +11 -20
- package/plugins/pbr/scripts/lib/spot-check.js +3 -106
- package/plugins/pbr/scripts/lib/state.js +25 -130
- package/plugins/pbr/scripts/lib/verify.js +56 -46
- package/plugins/pbr/scripts/milestone-learnings.js +6 -56
- package/plugins/pbr/scripts/pbr-tools.js +8 -91
- package/plugins/pbr/scripts/post-bash-triage.js +5 -63
- package/plugins/pbr/scripts/post-hoc.js +3 -52
- package/plugins/pbr/scripts/post-write-dispatch.js +0 -36
- package/plugins/pbr/scripts/pre-bash-dispatch.js +1 -7
- package/plugins/pbr/scripts/pre-task-dispatch.js +0 -28
- package/plugins/pbr/scripts/progress-tracker.js +2 -27
- package/plugins/pbr/scripts/session-cleanup.js +1 -31
- package/plugins/pbr/scripts/status-line.js +13 -11
- package/plugins/pbr/scripts/suggest-compact.js +2 -10
- package/plugins/pbr/scripts/test/state.test.js +5 -13
- package/plugins/pbr/scripts/validate-commit.js +8 -64
- package/plugins/pbr/scripts/validate-task.js +0 -30
- package/plugins/pbr/skills/begin/SKILL.md +1 -0
- package/plugins/pbr/skills/begin/templates/config.json.tmpl +0 -4
- package/plugins/pbr/skills/build/SKILL.md +6 -6
- package/plugins/pbr/skills/config/SKILL.md +1 -0
- package/plugins/pbr/skills/help/SKILL.md +1 -0
- package/plugins/pbr/skills/pause/SKILL.md +1 -0
- package/plugins/pbr/skills/profile-user/SKILL.md +1 -0
- package/plugins/pbr/skills/quick/SKILL.md +2 -1
- package/plugins/pbr/skills/resume/SKILL.md +1 -0
- package/plugins/pbr/skills/scan/SKILL.md +1 -0
- package/plugins/pbr/skills/setup/SKILL.md +1 -0
- package/plugins/pbr/skills/shared/state-update.md +2 -2
- package/plugins/pbr/skills/status/SKILL.md +1 -0
- package/plugins/pbr/references/behavioral-contexts.md +0 -53
- package/plugins/pbr/scripts/lib/autonomy.js +0 -91
- package/plugins/pbr/scripts/lib/circuit-state.js +0 -133
- package/plugins/pbr/scripts/lib/completion.js +0 -377
- package/plugins/pbr/scripts/lib/hypothesis-runner.js +0 -127
- package/plugins/pbr/scripts/lib/local-llm/client.js +0 -237
- package/plugins/pbr/scripts/lib/local-llm/health.js +0 -12
- package/plugins/pbr/scripts/lib/local-llm/index.js +0 -89
- package/plugins/pbr/scripts/lib/local-llm/metrics.js +0 -20
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-artifact.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-commit.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-error.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/classify-file-intent.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/operations/score-source.js +0 -72
- package/plugins/pbr/scripts/lib/local-llm/operations/summarize-context.js +0 -62
- package/plugins/pbr/scripts/lib/local-llm/operations/triage-test-output.js +0 -12
- package/plugins/pbr/scripts/lib/local-llm/operations/validate-task.js +0 -4
- package/plugins/pbr/scripts/lib/local-llm/router.js +0 -101
- package/plugins/pbr/scripts/lib/local-llm/shadow.js +0 -60
- package/plugins/pbr/scripts/lib/local-llm/threshold-tuner.js +0 -118
- package/plugins/pbr/scripts/lib/team-composer.js +0 -87
- package/plugins/pbr/scripts/lib/team-coordinator.js +0 -153
- package/plugins/pbr/scripts/lib/template.js +0 -222
- package/plugins/pbr/scripts/lib/test-cache.js +0 -54
- package/plugins/pbr/scripts/lib/trust-gate.js +0 -84
- package/plugins/pbr/scripts/lib/wiring-check.js +0 -196
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* fresh (<60s old). CRITICAL tier always emits; others use REMINDER_INTERVAL debounce.
|
|
10
10
|
*
|
|
11
11
|
* Fallback: when bridge is absent or stale, uses call-count threshold.
|
|
12
|
-
* Counter stored in .planning/.compact-counter (JSON).
|
|
12
|
+
* Counter stored in .planning/.compact-counter (JSON) and .session.json.
|
|
13
13
|
* Threshold configurable via config.json hooks.compactThreshold (default: 50).
|
|
14
14
|
* Counter resets on SessionStart (via progress-tracker.js).
|
|
15
15
|
*
|
|
@@ -281,15 +281,7 @@ function resetCounter(planningDir, sessionId) {
|
|
|
281
281
|
sessionSave(planningDir, { compactCounter: { count: 0, lastSuggested: 0 } }, sessionId);
|
|
282
282
|
} catch (_e) { /* best-effort */ }
|
|
283
283
|
|
|
284
|
-
//
|
|
285
|
-
const counterPath = path.join(planningDir, '.compact-counter');
|
|
286
|
-
try {
|
|
287
|
-
if (fs.existsSync(counterPath)) {
|
|
288
|
-
fs.unlinkSync(counterPath);
|
|
289
|
-
}
|
|
290
|
-
} catch (_e) {
|
|
291
|
-
// Best-effort
|
|
292
|
-
}
|
|
284
|
+
// .compact-counter cleanup removed -- legacy file no longer used
|
|
293
285
|
}
|
|
294
286
|
|
|
295
287
|
/**
|
|
@@ -26,8 +26,6 @@ const path = require('path');
|
|
|
26
26
|
const { execSync } = require('child_process');
|
|
27
27
|
const { logHook } = require('./hook-logger');
|
|
28
28
|
const { logEvent } = require('./event-logger');
|
|
29
|
-
const { resolveConfig } = require('./lib/local-llm/health');
|
|
30
|
-
const { classifyCommit } = require('./lib/local-llm/operations/classify-commit');
|
|
31
29
|
|
|
32
30
|
const VALID_TYPES = ['feat', 'fix', 'refactor', 'test', 'docs', 'chore', 'wip', 'revert', 'perf', 'ci', 'build'];
|
|
33
31
|
|
|
@@ -75,7 +73,13 @@ function checkAiCoAuthorResult(command, sessionId) {
|
|
|
75
73
|
|
|
76
74
|
function checkSensitiveFilesResult(sessionId) {
|
|
77
75
|
try {
|
|
78
|
-
|
|
76
|
+
let output;
|
|
77
|
+
try {
|
|
78
|
+
output = execSync('git diff --cached --name-only', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
79
|
+
} catch (_e) {
|
|
80
|
+
// git diff --cached fails during git tag or in --no-index contexts
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
79
83
|
const files = output.trim().split('\n').filter(Boolean);
|
|
80
84
|
|
|
81
85
|
const matched = files.filter((file) => {
|
|
@@ -157,58 +161,6 @@ function checkCommit(data) {
|
|
|
157
161
|
return null;
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
/**
|
|
161
|
-
* Load and resolve the local_llm config block from .planning/config.json.
|
|
162
|
-
* Returns a resolved config (always safe to use — disabled by default on error).
|
|
163
|
-
*/
|
|
164
|
-
function loadLocalLlmConfig(cwd) {
|
|
165
|
-
try {
|
|
166
|
-
const configPath = path.join(cwd || process.cwd(), '.planning', 'config.json');
|
|
167
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
168
|
-
return resolveConfig(parsed.local_llm);
|
|
169
|
-
} catch (_e) {
|
|
170
|
-
return resolveConfig(undefined);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Async LLM enrichment for commit messages. Returns an advisory string or null.
|
|
176
|
-
* Called after checkCommit passes (valid format) to provide semantic classification.
|
|
177
|
-
*
|
|
178
|
-
* @param {object} data - parsed hook data
|
|
179
|
-
* @returns {Promise<string|null>}
|
|
180
|
-
*/
|
|
181
|
-
async function enrichCommitLlm(data) {
|
|
182
|
-
try {
|
|
183
|
-
const command = data.tool_input?.command || '';
|
|
184
|
-
const message = extractCommitMessage(command);
|
|
185
|
-
if (!message) return null;
|
|
186
|
-
|
|
187
|
-
const cwd = process.cwd();
|
|
188
|
-
const llmConfig = loadLocalLlmConfig(cwd);
|
|
189
|
-
const planningDir = path.join(cwd, '.planning');
|
|
190
|
-
|
|
191
|
-
// Get staged files for scope validation
|
|
192
|
-
let stagedFiles = [];
|
|
193
|
-
try {
|
|
194
|
-
const output = execSync('git diff --cached --name-only', { encoding: 'utf8' });
|
|
195
|
-
stagedFiles = output.trim().split('\n').filter(Boolean);
|
|
196
|
-
} catch (_e) {
|
|
197
|
-
// Not in a git repo — skip staged files context
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const llmResult = await classifyCommit(llmConfig, planningDir, message, stagedFiles, data.session_id);
|
|
201
|
-
if (llmResult && llmResult.classification !== 'correct') {
|
|
202
|
-
return 'LLM commit advisory: ' + llmResult.classification +
|
|
203
|
-
' (confidence: ' + (llmResult.confidence * 100).toFixed(0) + '%)';
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
206
|
-
} catch (_llmErr) {
|
|
207
|
-
// Never propagate LLM errors
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
164
|
function main() {
|
|
213
165
|
let input = '';
|
|
214
166
|
|
|
@@ -223,14 +175,6 @@ function main() {
|
|
|
223
175
|
process.exit(result.exitCode);
|
|
224
176
|
}
|
|
225
177
|
|
|
226
|
-
// LLM semantic classification — advisory only (after format validation passes)
|
|
227
|
-
const llmAdvisory = await enrichCommitLlm(data);
|
|
228
|
-
if (llmAdvisory) {
|
|
229
|
-
process.stdout.write(JSON.stringify({
|
|
230
|
-
additionalContext: '[pbr] ' + llmAdvisory
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
178
|
process.exit(0);
|
|
235
179
|
} catch (_e) {
|
|
236
180
|
// Parse error - don't block
|
|
@@ -267,5 +211,5 @@ function extractCommitMessage(command) {
|
|
|
267
211
|
return null;
|
|
268
212
|
}
|
|
269
213
|
|
|
270
|
-
module.exports = { checkCommit
|
|
214
|
+
module.exports = { checkCommit };
|
|
271
215
|
if (require.main === module || process.argv[1] === __filename) { main(); }
|
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const path = require('path');
|
|
22
22
|
const { logHook } = require('./hook-logger');
|
|
23
|
-
const { resolveConfig } = require('./lib/local-llm/health');
|
|
24
|
-
const { validateTask: llmValidateTask } = require('./lib/local-llm/operations/validate-task');
|
|
25
23
|
const { checkNonPbrAgent } = require('./enforce-pbr-workflow');
|
|
26
24
|
const { KNOWN_AGENTS } = require('./lib/core');
|
|
27
25
|
|
|
@@ -39,21 +37,6 @@ const { checkDebuggerAdvisory, checkCheckpointManifest, checkActiveSkillIntegrit
|
|
|
39
37
|
const { checkDocExistence } = require('./lib/gates/doc-existence');
|
|
40
38
|
const { checkUserConfirmationGate } = require('./lib/gates/user-confirmation');
|
|
41
39
|
|
|
42
|
-
/**
|
|
43
|
-
* Load and resolve the local_llm config block from .planning/config.json.
|
|
44
|
-
* Returns a resolved config (always safe to use — disabled by default on error).
|
|
45
|
-
* @param {string} cwd - working directory to resolve .planning/config.json from
|
|
46
|
-
*/
|
|
47
|
-
function loadLocalLlmConfig(cwd) {
|
|
48
|
-
try {
|
|
49
|
-
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
50
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
51
|
-
return resolveConfig(parsed.local_llm);
|
|
52
|
-
} catch (_e) {
|
|
53
|
-
return resolveConfig(undefined);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
40
|
const MAX_DESCRIPTION_LENGTH = 100;
|
|
58
41
|
|
|
59
42
|
/**
|
|
@@ -264,19 +247,6 @@ function main() {
|
|
|
264
247
|
if (nonPbrAgentResult) warnings.push(nonPbrAgentResult.output.additionalContext);
|
|
265
248
|
if (planValGate && planValGate.warning) warnings.push(planValGate.warning);
|
|
266
249
|
|
|
267
|
-
// LLM task coherence check — advisory only
|
|
268
|
-
try {
|
|
269
|
-
const llmCwd = process.env.PBR_PROJECT_ROOT || process.cwd();
|
|
270
|
-
const llmConfig = loadLocalLlmConfig(llmCwd);
|
|
271
|
-
const planningDir = path.join(llmCwd, '.planning');
|
|
272
|
-
const llmResult = await llmValidateTask(llmConfig, planningDir, data.tool_input || {}, data.session_id);
|
|
273
|
-
if (llmResult && !llmResult.coherent) {
|
|
274
|
-
warnings.push('LLM task coherence advisory: ' + (llmResult.issue || 'Task description may not match intended operation.') + ' (confidence: ' + (llmResult.confidence * 100).toFixed(0) + '%)');
|
|
275
|
-
}
|
|
276
|
-
} catch (_llmErr) {
|
|
277
|
-
// Never propagate LLM errors
|
|
278
|
-
}
|
|
279
|
-
|
|
280
250
|
if (warnings.length > 0) {
|
|
281
251
|
for (const warning of warnings) {
|
|
282
252
|
logHook('validate-task', 'PreToolUse', 'warn', { warning });
|
|
@@ -118,7 +118,6 @@ Boolean toggles that enable or disable specific workflow capabilities. All defau
|
|
|
118
118
|
| `auto_advance` | `false` | Chain build, review, and plan automatically (requires `mode: autonomous`) |
|
|
119
119
|
| `team_discussions` | `false` | Enable team-based discussion workflows (never used for execution) |
|
|
120
120
|
| `inline_verify` | `false` | Per-task verification after each executor commit; adds ~10-20s latency per plan |
|
|
121
|
-
| `multi_layer_validation` | `false` | **(DEPRECATED)** Parallel BugBot-style review passes. Never integrated; removed in plan 100-01. Key retained for backward compatibility. |
|
|
122
121
|
| `extended_context` | `false` | Enable aggressive 1M context optimizations: higher concurrency (5 agents vs 3), default team review, always-parallel scan, pre-load build steps. Auto-set by quality profile. Safe optimizations (parallel research, full SUMMARY reads) use `context_window_tokens >= 500000` instead. |
|
|
123
122
|
|
|
124
123
|
**Notable interactions:**
|
|
@@ -468,14 +467,6 @@ When `confidence_gate` is `true` and the executor SUMMARY reports all must-haves
|
|
|
468
467
|
|
|
469
468
|
---
|
|
470
469
|
|
|
471
|
-
## validation_passes (DEPRECATED)
|
|
472
|
-
|
|
473
|
-
> **DEPRECATED**: The `multi_layer_validation` feature was never integrated and was removed in plan 100-01. This config key is retained for backward compatibility. The array has no effect.
|
|
474
|
-
|
|
475
|
-
Defined which review passes would run with the multi-layer validation feature. Options included: `correctness`, `security`, `performance`, `style`, `tests`, `accessibility`, `docs`, `deps`.
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
470
|
## context_budget
|
|
480
471
|
|
|
481
472
|
Controls context budget warning thresholds.
|
|
@@ -814,90 +805,3 @@ Run validation with: `node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js config val
|
|
|
814
805
|
| `tdd_mode: true` + `depth: quick` | quick depth skips verification, which conflicts with TDD's verify-first approach |
|
|
815
806
|
| `git.mode: disabled` + `atomic_commits: true` | atomic_commits has no effect when git is disabled |
|
|
816
807
|
| `git.branching: phase` + `git.mode: disabled` | Branching settings are ignored when git is disabled |
|
|
817
|
-
|
|
818
|
-
---
|
|
819
|
-
|
|
820
|
-
## local_llm (DEPRECATED)
|
|
821
|
-
|
|
822
|
-
> **DEPRECATED**: This feature has been removed as of v14.0. The config key is retained for backward compatibility. Setting `enabled: true` has no effect.
|
|
823
|
-
|
|
824
|
-
Offloads selected PBR inference tasks to a locally running Ollama instance, reducing frontier model usage and latency for fast classification calls. The key `enabled` defaults to `false`, so users without Ollama see no change — all LLM calls continue routing to Claude as normal. When enabled, PBR uses a `local_first` routing strategy: fast tasks (artifact classification, task validation) go to the local model; complex tasks (planning, execution) stay on the frontier model.
|
|
825
|
-
|
|
826
|
-
### Quick setup
|
|
827
|
-
|
|
828
|
-
1. Install Ollama:
|
|
829
|
-
- **Linux/macOS**: `curl -fsSL https://ollama.com/install.sh | sh`
|
|
830
|
-
- **Windows**: Download from [ollama.com/download](https://ollama.com/download) and run the installer
|
|
831
|
-
2. Pull the recommended model: `ollama pull qwen2.5-coder:7b`
|
|
832
|
-
3. Add to `.planning/config.json`:
|
|
833
|
-
|
|
834
|
-
```json
|
|
835
|
-
"local_llm": {
|
|
836
|
-
"enabled": true,
|
|
837
|
-
"model": "qwen2.5-coder:7b"
|
|
838
|
-
}
|
|
839
|
-
```
|
|
840
|
-
|
|
841
|
-
4. Verify connectivity: `node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm health` (DEPRECATED -- returns `{ deprecated: true }` as a no-op)
|
|
842
|
-
|
|
843
|
-
### Field reference
|
|
844
|
-
|
|
845
|
-
| Property | Type | Default | Description |
|
|
846
|
-
|----------|------|---------|-------------|
|
|
847
|
-
| `local_llm.enabled` | boolean | `false` | Enable local LLM offloading; `false` = all calls use frontier |
|
|
848
|
-
| `local_llm.provider` | string | `"ollama"` | Backend provider; only `"ollama"` is supported |
|
|
849
|
-
| `local_llm.endpoint` | string | `"http://localhost:11434"` | Ollama API base URL |
|
|
850
|
-
| `local_llm.model` | string | `"qwen2.5-coder:7b"` | Model tag to use for local inference |
|
|
851
|
-
| `local_llm.timeout_ms` | integer | `3000` | Per-request timeout in milliseconds; >= 500 |
|
|
852
|
-
| `local_llm.max_retries` | integer | `1` | Number of retry attempts on failure before falling back |
|
|
853
|
-
| `local_llm.fallback` | string | `"frontier"` | What to use when local LLM fails: `"frontier"` or `"skip"` |
|
|
854
|
-
| `local_llm.routing_strategy` | string | `"local_first"` | `"local_first"` sends fast tasks local; `"always_local"` routes everything |
|
|
855
|
-
|
|
856
|
-
### features sub-table
|
|
857
|
-
|
|
858
|
-
Controls which PBR tasks are eligible for local LLM offloading.
|
|
859
|
-
|
|
860
|
-
| Property | Default | Description |
|
|
861
|
-
|----------|---------|-------------|
|
|
862
|
-
| `artifact_classification` | `true` | Classify artifact types (PLAN, SUMMARY, VERIFICATION) locally |
|
|
863
|
-
| `task_validation` | `true` | Validate task scope and completeness locally |
|
|
864
|
-
| `context_summarization` | `false` | Summarize context windows locally (higher token demand) |
|
|
865
|
-
| `source_scoring` | `false` | Score source files by relevance locally |
|
|
866
|
-
|
|
867
|
-
### advanced sub-table
|
|
868
|
-
|
|
869
|
-
| Property | Default | Description |
|
|
870
|
-
|----------|---------|-------------|
|
|
871
|
-
| `confidence_threshold` | `0.9` | Minimum confidence (0–1) for local output to be accepted; below this, falls back to frontier |
|
|
872
|
-
| `shadow_mode` | `false` | Run local LLM in parallel with frontier but discard local results — useful for tuning confidence thresholds without affecting output |
|
|
873
|
-
| `max_input_tokens` | `2000` | Truncate inputs longer than this before sending to local model |
|
|
874
|
-
| `keep_alive` | `"30m"` | How long Ollama keeps the model loaded between requests (Ollama format: `"5m"`, `"1h"`) |
|
|
875
|
-
| `num_ctx` | `4096` | Context window size passed to Ollama; **must be 4096 on Windows** (see Windows gotchas) |
|
|
876
|
-
| `disable_after_failures` | `3` | Automatically disable local LLM for the session after this many consecutive failures |
|
|
877
|
-
|
|
878
|
-
### Hardware requirements
|
|
879
|
-
|
|
880
|
-
| Tier | Hardware | Notes |
|
|
881
|
-
|------|----------|-------|
|
|
882
|
-
| Recommended | RTX 3060+ with 8 GB VRAM | Full GPU acceleration; qwen2.5-coder:7b loads entirely in VRAM |
|
|
883
|
-
| Functional | GTX 1660+ with 6 GB VRAM | GPU acceleration with slight layer offload to RAM |
|
|
884
|
-
| Marginal | CPU only, 32 GB RAM | Works but adds 5-20s latency per call; disable context-heavy features |
|
|
885
|
-
|
|
886
|
-
For GPU acceleration, ensure NVIDIA drivers are 520+ and CUDA 11.8+ is installed. AMD GPU support is available via ROCm on Linux only.
|
|
887
|
-
|
|
888
|
-
### Windows gotchas
|
|
889
|
-
|
|
890
|
-
- **Smart App Control**: May block `ollama_llama_server.exe` on first run. Allow it via Security settings or disable Smart App Control.
|
|
891
|
-
- **Windows Defender**: Add an exclusion for `%LOCALAPPDATA%\Programs\Ollama\ollama_llama_server.exe` to prevent Defender from scanning inference calls in real time.
|
|
892
|
-
- **`num_ctx` must be 4096**: Higher values cause GPU memory fragmentation on Windows and result in OOM errors mid-session. Always set `advanced.num_ctx: 4096` in your config.
|
|
893
|
-
- **Firewall**: Ollama listens on `localhost:11434` by default. If you see connection refused errors, check that Windows Firewall is not blocking loopback connections.
|
|
894
|
-
|
|
895
|
-
### Viewing metrics (DEPRECATED)
|
|
896
|
-
|
|
897
|
-
The local LLM feature was removed in v14.0. The `llm metrics` and `llm adjust-thresholds` subcommands are no-ops that return `{ deprecated: true }`. No metrics are collected.
|
|
898
|
-
|
|
899
|
-
```bash
|
|
900
|
-
# These commands return { deprecated: true } and perform no action:
|
|
901
|
-
node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm metrics
|
|
902
|
-
node ${CLAUDE_PLUGIN_ROOT}/scripts/pbr-tools.js llm adjust-thresholds
|
|
903
|
-
```
|
|
@@ -9,82 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const { extractFrontmatter } = require('../lib/frontmatter');
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// Helpers
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
|
|
17
|
-
/**
|
|
18
|
-
* Parse YAML frontmatter from a markdown file.
|
|
19
|
-
* Returns an object with extracted key-value pairs (simple flat parsing).
|
|
20
|
-
*/
|
|
21
|
-
function parseFrontmatter(content) {
|
|
22
|
-
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
23
|
-
if (lines[0] !== '---') return {};
|
|
24
|
-
|
|
25
|
-
const endIdx = lines.indexOf('---', 1);
|
|
26
|
-
if (endIdx === -1) return {};
|
|
27
|
-
|
|
28
|
-
const fmLines = lines.slice(1, endIdx);
|
|
29
|
-
const result = {};
|
|
30
|
-
let currentKey = null;
|
|
31
|
-
let inArray = false;
|
|
32
|
-
|
|
33
|
-
for (const line of fmLines) {
|
|
34
|
-
// Array item
|
|
35
|
-
if (inArray && /^\s+-\s+/.test(line)) {
|
|
36
|
-
const val = line.replace(/^\s+-\s+/, '').trim();
|
|
37
|
-
result[currentKey].push(val);
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Key-value pair
|
|
42
|
-
const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)/);
|
|
43
|
-
if (kvMatch) {
|
|
44
|
-
const key = kvMatch[1];
|
|
45
|
-
const rawVal = kvMatch[2].trim();
|
|
46
|
-
|
|
47
|
-
if (rawVal === '' || rawVal === '[]') {
|
|
48
|
-
// Could be start of array or empty value
|
|
49
|
-
result[key] = [];
|
|
50
|
-
currentKey = key;
|
|
51
|
-
inArray = true;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Inline array like [Read, Write, Edit]
|
|
56
|
-
if (rawVal.startsWith('[') && rawVal.endsWith(']')) {
|
|
57
|
-
result[key] = rawVal.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
|
|
58
|
-
currentKey = key;
|
|
59
|
-
inArray = false;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Scalar value
|
|
64
|
-
result[key] = rawVal.replace(/^["']|["']$/g, '');
|
|
65
|
-
currentKey = key;
|
|
66
|
-
inArray = false;
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Array item under current key (indented with -)
|
|
71
|
-
if (currentKey && /^\s+-\s/.test(line)) {
|
|
72
|
-
if (!Array.isArray(result[currentKey])) {
|
|
73
|
-
result[currentKey] = [];
|
|
74
|
-
}
|
|
75
|
-
const val = line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, '');
|
|
76
|
-
result[currentKey].push(val);
|
|
77
|
-
inArray = true;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Not a recognized line — end array mode
|
|
82
|
-
inArray = false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
18
|
/**
|
|
89
19
|
* Read all agent .md files and return array of { file, frontmatter, content }.
|
|
90
20
|
*/
|
|
@@ -97,7 +27,7 @@ function readAgentFiles(pluginRoot) {
|
|
|
97
27
|
.map(f => {
|
|
98
28
|
const filePath = path.join(agentsDir, f);
|
|
99
29
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
100
|
-
return { file: f, frontmatter:
|
|
30
|
+
return { file: f, frontmatter: extractFrontmatter(content), content };
|
|
101
31
|
});
|
|
102
32
|
}
|
|
103
33
|
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
const fs = require('fs');
|
|
26
26
|
const path = require('path');
|
|
27
27
|
const { execSync } = require('child_process');
|
|
28
|
+
const { extractFrontmatter } = require('../lib/frontmatter');
|
|
28
29
|
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
// Result helper
|
|
@@ -47,43 +48,6 @@ function result(dimCode, status, message, evidence) {
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// YAML frontmatter parser (lightweight, no external deps)
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Extract YAML frontmatter from markdown content as key-value pairs.
|
|
56
|
-
* Handles both \n and \r\n line endings.
|
|
57
|
-
* @param {string} content
|
|
58
|
-
* @returns {object} Parsed frontmatter fields
|
|
59
|
-
*/
|
|
60
|
-
function parseFrontmatter(content) {
|
|
61
|
-
const lines = content.replace(/\r\n/g, '\n').split('\n');
|
|
62
|
-
if (lines[0] !== '---') return {};
|
|
63
|
-
|
|
64
|
-
const fields = {};
|
|
65
|
-
for (let i = 1; i < lines.length; i++) {
|
|
66
|
-
if (lines[i] === '---') break;
|
|
67
|
-
const match = lines[i].match(/^(\w[\w_-]*):\s*(.*)$/);
|
|
68
|
-
if (match) {
|
|
69
|
-
let value = match[2].trim();
|
|
70
|
-
// Strip surrounding quotes
|
|
71
|
-
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
72
|
-
(value.startsWith("'") && value.endsWith("'"))) {
|
|
73
|
-
value = value.slice(1, -1);
|
|
74
|
-
}
|
|
75
|
-
// Parse arrays (simple single-line format)
|
|
76
|
-
if (value === '[]') {
|
|
77
|
-
value = [];
|
|
78
|
-
} else if (value === 'null' || value === '') {
|
|
79
|
-
value = value === 'null' ? null : value;
|
|
80
|
-
}
|
|
81
|
-
fields[match[1]] = value;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return fields;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
51
|
// ---------------------------------------------------------------------------
|
|
88
52
|
// Session log helper (shared by WC-01, WC-07, WC-09, WC-10)
|
|
89
53
|
// ---------------------------------------------------------------------------
|
|
@@ -285,7 +249,7 @@ function checkCompactionQuality(planningDir, _config) {
|
|
|
285
249
|
const statePath = path.join(planningDir, 'STATE.md');
|
|
286
250
|
try {
|
|
287
251
|
const stateContent = fs.readFileSync(statePath, 'utf8');
|
|
288
|
-
const fm =
|
|
252
|
+
const fm = extractFrontmatter(stateContent);
|
|
289
253
|
const criticalFields = ['current_phase', 'status', 'last_activity'];
|
|
290
254
|
const missing = criticalFields.filter(f => fm[f] === undefined || fm[f] === '');
|
|
291
255
|
if (missing.length > 0) {
|
|
@@ -330,7 +294,7 @@ function checkStateFileIntegrity(planningDir, _config) {
|
|
|
330
294
|
]);
|
|
331
295
|
}
|
|
332
296
|
|
|
333
|
-
const fm =
|
|
297
|
+
const fm = extractFrontmatter(content);
|
|
334
298
|
const evidence = [];
|
|
335
299
|
|
|
336
300
|
// Get actual phase directories
|
|
@@ -429,7 +393,7 @@ function checkStateFrontmatterIntegrity(planningDir, _config) {
|
|
|
429
393
|
]);
|
|
430
394
|
}
|
|
431
395
|
|
|
432
|
-
const fm =
|
|
396
|
+
const fm = extractFrontmatter(content);
|
|
433
397
|
const evidence = [];
|
|
434
398
|
|
|
435
399
|
// Required fields
|
|
@@ -799,7 +763,7 @@ function checkArtifactFormatValidation(planningDir, _config) {
|
|
|
799
763
|
for (const sf of summaryFiles) {
|
|
800
764
|
try {
|
|
801
765
|
const content = fs.readFileSync(path.join(dirPath, sf), 'utf8');
|
|
802
|
-
const fm =
|
|
766
|
+
const fm = extractFrontmatter(content);
|
|
803
767
|
const missingFields = [];
|
|
804
768
|
|
|
805
769
|
if (fm.requires === undefined) missingFields.push('requires');
|
|
@@ -64,8 +64,6 @@ function validateConfig(configPath) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// local_llm is deprecated — no advisory for missing key
|
|
68
|
-
|
|
69
67
|
// Check version
|
|
70
68
|
if (config.version && config.version < 2) {
|
|
71
69
|
warnings.push(`Config version ${config.version} is outdated — expected version 2+`);
|
|
@@ -92,11 +90,6 @@ function validateConfig(configPath) {
|
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
// DEPRECATED: local_llm infrastructure removed in v14.0
|
|
96
|
-
if (config.local_llm && config.local_llm.enabled === true) {
|
|
97
|
-
warnings.push('local_llm feature is deprecated and has no effect. Set enabled: false to suppress this warning.');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
93
|
return warnings;
|
|
101
94
|
}
|
|
102
95
|
|
|
@@ -29,7 +29,7 @@ function checkCrossPluginSync(data) {
|
|
|
29
29
|
|
|
30
30
|
let stagedFiles;
|
|
31
31
|
try {
|
|
32
|
-
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim().split('\n').filter(Boolean);
|
|
32
|
+
stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim().split('\n').filter(Boolean);
|
|
33
33
|
} catch (_e) {
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
@@ -25,8 +25,6 @@ const fs = require('fs');
|
|
|
25
25
|
const path = require('path');
|
|
26
26
|
const { logHook } = require('./hook-logger');
|
|
27
27
|
const { logEvent } = require('./event-logger');
|
|
28
|
-
const { resolveConfig } = require('./lib/local-llm/health');
|
|
29
|
-
const { classifyArtifact } = require('./lib/local-llm/operations/classify-artifact');
|
|
30
28
|
|
|
31
29
|
// Import all validators from extracted module
|
|
32
30
|
const {
|
|
@@ -49,20 +47,6 @@ const {
|
|
|
49
47
|
validateContext
|
|
50
48
|
} = require('./lib/format-validators');
|
|
51
49
|
|
|
52
|
-
/**
|
|
53
|
-
* Load and resolve the local_llm config block from .planning/config.json.
|
|
54
|
-
* Returns a resolved config (always safe to use -- disabled by default on error).
|
|
55
|
-
*/
|
|
56
|
-
function loadLocalLlmConfig() {
|
|
57
|
-
try {
|
|
58
|
-
const configPath = path.join(process.cwd(), '.planning', 'config.json');
|
|
59
|
-
const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
60
|
-
return resolveConfig(parsed.local_llm);
|
|
61
|
-
} catch (_e) {
|
|
62
|
-
return resolveConfig(undefined);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
50
|
async function main() {
|
|
67
51
|
let input = '';
|
|
68
52
|
|
|
@@ -119,22 +103,6 @@ async function main() {
|
|
|
119
103
|
result.warnings.push(`Plan file "${basename}" uses non-standard naming. Expected format: PLAN-{NN}.md or PLAN.md. Phase-prefixed names (e.g., "10-02-PLAN.md") bypass some validation checks.`);
|
|
120
104
|
}
|
|
121
105
|
|
|
122
|
-
// LLM advisory enrichment -- advisory only, never blocks
|
|
123
|
-
if ((isPlan || isSummary) && result.errors.length === 0) {
|
|
124
|
-
try {
|
|
125
|
-
const llmConfig = loadLocalLlmConfig();
|
|
126
|
-
const planningDir = path.join(process.cwd(), '.planning');
|
|
127
|
-
const fileType = isPlan ? 'PLAN' : 'SUMMARY';
|
|
128
|
-
const llmResult = await classifyArtifact(llmConfig, planningDir, content, fileType, data.session_id);
|
|
129
|
-
if (llmResult && llmResult.classification) {
|
|
130
|
-
const llmNote = `Local LLM: ${fileType} classified as "${llmResult.classification}" (confidence: ${(llmResult.confidence * 100).toFixed(0)}%)${llmResult.reason ? ' — ' + llmResult.reason : ''}`;
|
|
131
|
-
result.warnings.push(llmNote);
|
|
132
|
-
}
|
|
133
|
-
} catch (_llmErr) {
|
|
134
|
-
// Never propagate LLM errors
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
106
|
const eventType = isPlan ? 'plan-validated' : isVerification ? 'verification-validated' : isRoadmap ? 'roadmap-validated' : isLearnings ? 'learnings-validated' : isConfig ? 'config-validated' : isResearch ? 'research-validated' : isContext ? 'context-validated' : 'summary-validated';
|
|
139
107
|
|
|
140
108
|
// Detect Write vs Edit: Write = full creation/overwrite (likely first attempt)
|
|
@@ -17,6 +17,7 @@ const fs = require('fs');
|
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const { logHook } = require('./hook-logger');
|
|
19
19
|
const { logEvent } = require('./event-logger');
|
|
20
|
+
const { extractFrontmatter } = require('./lib/frontmatter');
|
|
20
21
|
|
|
21
22
|
const LIFECYCLE_STATUSES = ['planned', 'built', 'partial', 'verified'];
|
|
22
23
|
|
|
@@ -197,29 +198,32 @@ function main() {
|
|
|
197
198
|
|
|
198
199
|
/**
|
|
199
200
|
* Extract current phase number and status from STATE.md.
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* "Current phase: 03-slug-name"
|
|
204
|
-
* "**Status**: planned"
|
|
205
|
-
* "Phase status: built"
|
|
201
|
+
* Tries frontmatter first (v2), falls back to body regex (legacy).
|
|
202
|
+
* Delegates frontmatter parsing to canonical extractFrontmatter.
|
|
203
|
+
* Returns { phase, status } or null if unparseable.
|
|
206
204
|
*/
|
|
207
|
-
|
|
205
|
+
const parseState = (content) => {
|
|
206
|
+
// Try frontmatter first (v2 format)
|
|
207
|
+
const fm = extractFrontmatter(content);
|
|
208
|
+
if (fm.current_phase && fm.status) {
|
|
209
|
+
return {
|
|
210
|
+
phase: normalizePhaseNum(String(fm.current_phase)),
|
|
211
|
+
status: fm.status.toLowerCase()
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Fallback: body regex for legacy formats
|
|
208
215
|
const phaseMatch = content.match(
|
|
209
216
|
/\*{0,2}(?:Current\s+)?Phase\*{0,2}:\s*(\d+(?:\.\d+)?)/i
|
|
210
217
|
);
|
|
211
|
-
|
|
212
218
|
const statusMatch = content.match(
|
|
213
219
|
/\*{0,2}(?:Phase\s+)?Status\*{0,2}:\s*["']?(\w+)["']?/i
|
|
214
220
|
);
|
|
215
|
-
|
|
216
221
|
if (!phaseMatch || !statusMatch) return null;
|
|
217
|
-
|
|
218
222
|
return {
|
|
219
223
|
phase: normalizePhaseNum(phaseMatch[1]),
|
|
220
224
|
status: statusMatch[1].toLowerCase()
|
|
221
225
|
};
|
|
222
|
-
}
|
|
226
|
+
};
|
|
223
227
|
|
|
224
228
|
/**
|
|
225
229
|
* Find the status for a given phase in ROADMAP.md's Phase Overview table.
|