@sienklogic/plan-build-run 2.61.0 → 2.61.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ 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.61.1](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.61.0...plan-build-run-v2.61.1) (2026-03-06)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **hooks:** resolve script path before .planning/ early-exit check ([06487f7](https://github.com/SienkLogic/plan-build-run/commit/06487f78799a521f6d9041dc1323d36bfdf4a1d8))
14
+
8
15
  ## [2.61.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.60.0...plan-build-run-v2.61.0) (2026-03-06)
9
16
 
10
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sienklogic/plan-build-run",
3
- "version": "2.61.0",
3
+ "version": "2.61.1",
4
4
  "description": "Plan it, Build it, Run it — structured development workflow for Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -79,18 +79,6 @@
79
79
  }
80
80
  ]
81
81
  },
82
- {
83
- "matcher": "Write|Edit",
84
- "hooks": [
85
- {
86
- "type": "command",
87
- "bash": "node \"$(cd \"$(dirname \"$0\")\" && pwd)/../../pbr/scripts/run-hook.js\" suggest-compact.js",
88
- "powershell": "node (Join-Path (Split-Path -Parent $PSScriptRoot) 'pbr\\scripts\\run-hook.js') suggest-compact.js",
89
- "cwd": ".",
90
- "timeoutSec": 15
91
- }
92
- ]
93
- },
94
82
  {
95
83
  "matcher": "Bash",
96
84
  "hooks": [
@@ -166,18 +154,6 @@
166
154
  }
167
155
  ]
168
156
  },
169
- {
170
- "matcher": "Bash",
171
- "hooks": [
172
- {
173
- "type": "command",
174
- "bash": "node \"$(cd \"$(dirname \"$0\")\" && pwd)/../../pbr/scripts/run-hook.js\" check-cross-plugin-sync.js",
175
- "powershell": "node (Join-Path (Split-Path -Parent $PSScriptRoot) 'pbr\\scripts\\run-hook.js') check-cross-plugin-sync.js",
176
- "cwd": ".",
177
- "timeoutSec": 15
178
- }
179
- ]
180
- },
181
157
  {
182
158
  "matcher": "Write|Edit",
183
159
  "hooks": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pbr",
3
3
  "displayName": "Plan-Build-Run",
4
- "version": "2.61.0",
4
+ "version": "2.61.1",
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",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pbr",
3
3
  "displayName": "Plan-Build-Run",
4
- "version": "2.61.0",
4
+ "version": "2.61.1",
5
5
  "description": "Plan-Build-Run — Structured development workflow for Cursor. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
6
6
  "author": {
7
7
  "name": "SienkLogic",
@@ -67,16 +67,6 @@
67
67
  }
68
68
  ]
69
69
  },
70
- {
71
- "matcher": "Write|Edit",
72
- "hooks": [
73
- {
74
- "type": "command",
75
- "command": "node -e \"var r=process.env.CLAUDE_PLUGIN_ROOT||'',m=r.match(/^\\/([a-zA-Z])\\/(.*)/);if(m)r=m[1]+String.fromCharCode(58)+String.fromCharCode(92)+m[2];require(require('path').resolve(r,'..','pbr','scripts','run-hook.js'))\" suggest-compact.js",
76
- "statusMessage": "Checking context budget..."
77
- }
78
- ]
79
- },
80
70
  {
81
71
  "matcher": "Bash",
82
72
  "hooks": [
@@ -140,16 +130,6 @@
140
130
  }
141
131
  ]
142
132
  },
143
- {
144
- "matcher": "Bash",
145
- "hooks": [
146
- {
147
- "type": "command",
148
- "command": "node -e \"var r=process.env.CLAUDE_PLUGIN_ROOT||'',m=r.match(/^\\/([a-zA-Z])\\/(.*)/);if(m)r=m[1]+String.fromCharCode(58)+String.fromCharCode(92)+m[2];require(require('path').resolve(r,'..','pbr','scripts','run-hook.js'))\" check-cross-plugin-sync.js",
149
- "statusMessage": "Checking cross-plugin sync..."
150
- }
151
- ]
152
- },
153
133
  {
154
134
  "matcher": "Write|Edit",
155
135
  "hooks": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pbr",
3
- "version": "2.61.0",
3
+ "version": "2.61.1",
4
4
  "description": "Plan-Build-Run — Structured development workflow for Claude Code. Solves context rot through disciplined subagent delegation, structured planning, atomic execution, and goal-backward verification.",
5
5
  "author": {
6
6
  "name": "SienkLogic",
@@ -72,16 +72,6 @@
72
72
  }
73
73
  ]
74
74
  },
75
- {
76
- "matcher": "Write|Edit",
77
- "hooks": [
78
- {
79
- "type": "command",
80
- "command": "node -e \"var r=process.env.CLAUDE_PLUGIN_ROOT||'',m=r.match(/^\\/([a-zA-Z])\\/(.*)/);if(m)r=m[1]+String.fromCharCode(58)+String.fromCharCode(92)+m[2];require(require('path').resolve(r,'scripts','run-hook.js'))\" suggest-compact.js",
81
- "statusMessage": "Checking context budget..."
82
- }
83
- ]
84
- },
85
75
  {
86
76
  "matcher": "Bash",
87
77
  "hooks": [
@@ -145,16 +135,6 @@
145
135
  }
146
136
  ]
147
137
  },
148
- {
149
- "matcher": "Bash",
150
- "hooks": [
151
- {
152
- "type": "command",
153
- "command": "node -e \"var r=process.env.CLAUDE_PLUGIN_ROOT||'',m=r.match(/^\\/([a-zA-Z])\\/(.*)/);if(m)r=m[1]+String.fromCharCode(58)+String.fromCharCode(92)+m[2];require(require('path').resolve(r,'scripts','run-hook.js'))\" check-cross-plugin-sync.js",
154
- "statusMessage": "Checking cross-plugin sync..."
155
- }
156
- ]
157
- },
158
138
  {
159
139
  "matcher": "Write|Edit",
160
140
  "hooks": [
@@ -239,6 +239,21 @@ function main() {
239
239
  source: 'bridge'
240
240
  });
241
241
  process.stdout.write(JSON.stringify(output));
242
+ process.exit(0);
243
+ }
244
+
245
+ // For Write|Edit tools, also run suggest-compact check in-process
246
+ // (eliminates a separate Node process spawn per Write/Edit).
247
+ // Detect Write|Edit by presence of file_path in tool_input.
248
+ const toolInput = data.tool_input || {};
249
+ if (toolInput.file_path || toolInput.path) {
250
+ try {
251
+ const { checkCompaction } = require('./suggest-compact');
252
+ const compactResult = checkCompaction(planningDir, cwd);
253
+ if (compactResult) {
254
+ process.stdout.write(JSON.stringify(compactResult));
255
+ }
256
+ } catch (_e) { /* best-effort — never block on compact check */ }
242
257
  }
243
258
 
244
259
  process.exit(0);
@@ -21,7 +21,6 @@
21
21
  const fs = require('fs');
22
22
  const path = require('path');
23
23
  const { logHook } = require('./hook-logger');
24
- const { sessionLoad } = require('./pbr-tools');
25
24
 
26
25
  /**
27
26
  * Load the enforcement configuration from .planning/config.json.
@@ -75,6 +74,7 @@ function checkUnmanagedSourceWrite(data) {
75
74
  if (!fs.existsSync(planningDir)) return null;
76
75
 
77
76
  // Skip if a PBR skill is active — try .session.json first, fall back to legacy .active-skill
77
+ const { sessionLoad } = require('./pbr-tools');
78
78
  let activeSkill = sessionLoad(planningDir).activeSkill || '';
79
79
  if (!activeSkill) {
80
80
  try { activeSkill = fs.readFileSync(path.join(planningDir, '.active-skill'), 'utf8').trim(); } catch (_) { /* legacy file missing */ }
@@ -240,6 +240,7 @@ function checkUnmanagedCommit(data) {
240
240
  if (!fs.existsSync(planningDir)) return null;
241
241
 
242
242
  // Skip if a PBR skill is active — try .session.json first, fall back to legacy .active-skill
243
+ const { sessionLoad } = require('./pbr-tools');
243
244
  let activeSkillCommit = sessionLoad(planningDir).activeSkill || '';
244
245
  if (!activeSkillCommit) {
245
246
  try { activeSkillCommit = fs.readFileSync(path.join(planningDir, '.active-skill'), 'utf8').trim(); } catch (_) { /* legacy file missing */ }
@@ -50,6 +50,7 @@ const { logHook } = require('./hook-logger');
50
50
  const { checkDangerous } = require('./check-dangerous-commands');
51
51
  const { checkCommit, enrichCommitLlm } = require('./validate-commit');
52
52
  const { checkUnmanagedCommit } = require('./enforce-pbr-workflow');
53
+ const { checkCrossPluginSync } = require('./check-cross-plugin-sync');
53
54
 
54
55
  function main() {
55
56
  let input = '';
@@ -104,6 +105,12 @@ function main() {
104
105
  warnings.push(unmanagedCommitResult.output.additionalContext);
105
106
  }
106
107
 
108
+ // Cross-plugin sync advisory — warn when pbr changes lack cursor/copilot counterparts
109
+ const syncResult = checkCrossPluginSync(data);
110
+ if (syncResult) {
111
+ warnings.push(syncResult.additionalContext);
112
+ }
113
+
107
114
  // LLM commit semantic classification — advisory only
108
115
  const llmAdvisory = await enrichCommitLlm(data);
109
116
  if (llmAdvisory) {
@@ -21,8 +21,22 @@
21
21
 
22
22
  'use strict';
23
23
 
24
+ const fs = require('fs');
24
25
  const path = require('path');
25
26
 
27
+ // Scripts that must run even without .planning/ (lifecycle hooks).
28
+ // All per-tool-call hooks (PreToolUse, PostToolUse, etc.) are PBR-specific
29
+ // and can early-exit when .planning/ doesn't exist, skipping their entire
30
+ // require() chain (~10-20ms saved per hook invocation on non-PBR projects).
31
+ const ALWAYS_RUN = new Set([
32
+ 'progress-tracker.js', // SessionStart — reports non-PBR status
33
+ 'session-cleanup.js', // SessionEnd — cleanup
34
+ 'worktree-create.js', // WorktreeCreate — creates .planning/ in worktrees
35
+ 'worktree-remove.js', // WorktreeRemove — cleans up worktrees
36
+ 'instructions-loaded.js', // InstructionsLoaded — detects instruction changes
37
+ 'check-config-change.js', // ConfigChange — monitors config
38
+ ]);
39
+
26
40
  /**
27
41
  * Fix MSYS-style paths on Windows.
28
42
  * Converts /d/Repos/... to D:\Repos\...
@@ -73,23 +87,46 @@ if (scriptName) {
73
87
 
74
88
  function runScript(name, args) {
75
89
  args = args || [];
90
+
76
91
  // Try __dirname first, then pluginRoot
77
92
  const candidates = [
78
93
  path.resolve(__dirname, name),
79
94
  pluginRoot ? path.resolve(pluginRoot, 'scripts', name) : null
80
95
  ].filter(Boolean);
81
96
 
97
+ // Phase 1: Resolve the script path (cheap — just fs.statSync, no require).
98
+ // This must happen before the .planning/ check so missing scripts still
99
+ // get proper error messages regardless of project type.
100
+ let resolvedPath = null;
82
101
  for (const candidate of candidates) {
83
102
  try {
84
- process.argv = [process.argv[0], candidate, ...args];
85
- require(candidate);
86
- return;
87
- } catch (err) {
88
- if (err.code !== 'MODULE_NOT_FOUND') throw err;
103
+ fs.statSync(candidate);
104
+ resolvedPath = candidate;
105
+ break;
106
+ } catch (_e) {
107
+ // Not found at this path
108
+ }
109
+ }
110
+
111
+ if (!resolvedPath) {
112
+ process.stderr.write(`run-hook: cannot find script: ${name}\n`);
113
+ process.stderr.write(` searched: ${candidates.join(', ')}\n`);
114
+ process.exit(1);
115
+ }
116
+
117
+ // Phase 2: Early-exit for non-PBR projects. Script exists but .planning/
118
+ // doesn't — skip loading the module entirely. This avoids the full
119
+ // require() chain (~10-20ms) for every hook invocation in non-PBR projects.
120
+ if (!ALWAYS_RUN.has(name)) {
121
+ const cwd = process.env.PBR_PROJECT_ROOT || process.cwd();
122
+ try {
123
+ fs.statSync(path.join(cwd, '.planning'));
124
+ } catch (_e) {
125
+ process.exit(0);
89
126
  }
90
127
  }
91
128
 
92
- process.stderr.write(`run-hook: cannot find script: ${name}\n`);
93
- process.stderr.write(` searched: ${candidates.join(', ')}\n`);
94
- process.exit(1);
129
+ // Phase 3: Load and execute the script.
130
+ process.argv = [process.argv[0], resolvedPath, ...args];
131
+ require(resolvedPath);
95
132
  }
@@ -20,7 +20,6 @@
20
20
  const fs = require('fs');
21
21
  const path = require('path');
22
22
  const { logHook } = require('./hook-logger');
23
- const { configLoad, sessionSave } = require('./pbr-tools');
24
23
  const { loadBridge, TIER_MESSAGES } = require('./context-bridge');
25
24
 
26
25
  const DEFAULT_THRESHOLD = 50;
@@ -159,12 +158,14 @@ function saveCounter(counterPath, counter) {
159
158
  // Derive planningDir from counterPath (counterPath is planningDir/.compact-counter)
160
159
  try {
161
160
  const planningDir = path.dirname(counterPath);
161
+ const { sessionSave } = require('./pbr-tools');
162
162
  sessionSave(planningDir, { compactCounter: counter });
163
163
  } catch (_e) { /* non-fatal mirror */ }
164
164
  }
165
165
 
166
166
  function getThreshold(cwd) {
167
167
  const planningDir = path.join(cwd, '.planning');
168
+ const { configLoad } = require('./pbr-tools');
168
169
  const config = configLoad(planningDir);
169
170
  if (!config) return DEFAULT_THRESHOLD;
170
171
  return config.hooks?.compactThreshold || DEFAULT_THRESHOLD;
@@ -173,6 +174,7 @@ function getThreshold(cwd) {
173
174
  function resetCounter(planningDir) {
174
175
  // Primary: reset compactCounter in .session.json to 0
175
176
  try {
177
+ const { sessionSave } = require('./pbr-tools');
176
178
  sessionSave(planningDir, { compactCounter: { count: 0, lastSuggested: 0 } });
177
179
  } catch (_e) { /* best-effort */ }
178
180
 
@@ -194,8 +194,8 @@ function loadTracker(trackerPath) {
194
194
  function checkBridge(planningDir) {
195
195
  const bridgePath = path.join(planningDir, '.context-budget.json');
196
196
  try {
197
- if (!fs.existsSync(bridgePath)) return null;
198
-
197
+ // Single statSync replaces existsSync + statSync (2 syscalls → 1).
198
+ // If the file doesn't exist, statSync throws and we catch below.
199
199
  const stats = fs.statSync(bridgePath);
200
200
  const ageMs = Date.now() - stats.mtimeMs;
201
201
  if (ageMs > BRIDGE_STALENESS_MS) return null;