@polderlabs/bizar 10.12.1 → 10.12.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.
@@ -10,7 +10,6 @@ import { spawnSync } from 'node:child_process';
10
10
  import { findGitCommand } from './git-command-parser.mjs';
11
11
 
12
12
  const ALLOWED_COMMIT_TYPES = ['feat', 'fix', 'refactor', 'docs', 'style', 'test', 'build', 'chore'];
13
- const AI_ATTRIBUTION = /Claude-Session:|Co-Authored-By:\s*(?:Claude|Codex|ChatGPT|OpenAI|Gemini|Cursor|Copilot)|Generated with[^\n]*Claude Code|claude\.ai\/code\/session/i;
14
13
  const GH_GLOBAL_OPTION = String.raw`(?:(?:-R|--repo|--hostname)\s+(?:"[^"]*"|'[^']*'|\S+)|--(?:help|version))`;
15
14
 
16
15
  function commandPattern(program, globalOption, subcommand) {
@@ -82,19 +81,24 @@ process.stdin.on('end', () => {
82
81
  output('deny', 'Rebasing rewrites history. Use a merge or follow-up commit unless the user explicitly changes project policy.');
83
82
  return;
84
83
  }
85
- if (AI_ATTRIBUTION.test(command) && (commit || hasGhCommand(command, 'pr', '\\w+'))) {
86
- output('deny', 'Remove AI-attribution trailers or generated-by links from the commit or pull-request text.');
87
- return;
88
- }
89
-
90
84
  if (commit) {
91
85
  const message = commitMessage(command);
92
86
  const subject = message.split('\n').find((line) => line.trim())?.trim() ?? '';
93
- if (subject && !new RegExp(`^(?:${ALLOWED_COMMIT_TYPES.join('|')})(\\([^)]+\\))?:\\s+\\S`, 'i').test(subject)) {
94
- output('deny', `Use a conventional commit subject: ${ALLOWED_COMMIT_TYPES.join(', ')} followed by ": description".`);
95
- return;
87
+ // F-145 loosening: subject-shape check is a soft warning attached to
88
+ // the same hook response as the `ask` decision (the dispatcher
89
+ // expects one JSON line per leaf).
90
+ const conventional = subject && !new RegExp(`^(?:${ALLOWED_COMMIT_TYPES.join('|')})(\\([^)]+\\))?:\\s+\\S`, 'i').test(subject);
91
+ const payload = {
92
+ hookSpecificOutput: {
93
+ hookEventName: 'PreToolUse',
94
+ permissionDecision: 'ask',
95
+ permissionDecisionReason: `Create this local commit${subject ? `: ${subject}` : ''}?`,
96
+ },
97
+ };
98
+ if (conventional) {
99
+ payload.hookSpecificOutput.additionalContext = `Conventional commit hint: prefer "type: subject" (types: ${ALLOWED_COMMIT_TYPES.join(', ')}).`;
96
100
  }
97
- output('ask', `Create this local commit${subject ? `: ${subject}` : ''}?`);
101
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
98
102
  return;
99
103
  }
100
104
 
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  // PreToolUse — deny edits outside the current task scope or inside a path
3
3
  // leased by a sibling worktree.
4
+ //
5
+ // F-145 loosening: removed the per-edit `git worktree list --porcelain`
6
+ // fork and the `requireTask` gate. The hook is now a single in-memory
7
+ // ledger lookup. Edits inside the project are allowed by default; only
8
+ // an active lease held by another agent on the same path denies.
4
9
 
5
10
  import { existsSync } from 'node:fs';
6
11
  import { resolve } from 'node:path';
@@ -36,20 +41,11 @@ process.stdin.on('end', () => {
36
41
  const top = spawnSync('git', ['rev-parse', '--show-toplevel'], {
37
42
  cwd,
38
43
  encoding: 'utf8',
39
- });
40
- const worktrees = spawnSync('git', ['worktree', 'list', '--porcelain'], {
41
- cwd,
42
- encoding: 'utf8',
44
+ timeout: 3_000,
43
45
  });
44
46
  const repoRoot = top.status === 0 && top.stdout.trim()
45
47
  ? resolve(top.stdout.trim())
46
48
  : cwd;
47
- const mainWorktree = worktrees.status === 0
48
- ? /^worktree (.+)$/m.exec(worktrees.stdout)?.[1]
49
- : null;
50
- const requireTask = Boolean(
51
- mainWorktree && resolve(mainWorktree) !== repoRoot,
52
- );
53
49
 
54
50
  let ledger;
55
51
  try {
@@ -58,7 +54,7 @@ process.stdin.on('end', () => {
58
54
  cwd,
59
55
  filePath,
60
56
  repoRoot,
61
- requireTask,
57
+ requireTask: false,
62
58
  });
63
59
  if (!authorization.allowed) {
64
60
  process.stdout.write(JSON.stringify({
@@ -27,12 +27,13 @@
27
27
  // }}
28
28
  //
29
29
  // Behaviour:
30
- // 1. Block writes to .env, .envrc, secrets/, credentials/, node_modules/.
31
- // .env.example/.sample/.template are explicitly allowed (docs, not
32
- // secrets). Lockfiles are allowed — they're package-manager output.
33
- // 2. Always return a small context line for the next AI decision.
34
- // (console.log / debugger / .only() are enforced by `make clean-check`
35
- // at commit time duplicate-warning here adds noise without safety.)
30
+ // Block writes to .env, .envrc, secrets/, credentials/, node_modules/.
31
+ // .env.example/.sample/.template are explicitly allowed (docs, not
32
+ // secrets). Lockfiles are allowed — they're package-manager output.
33
+ //
34
+ // F-145: removed the always-on `additionalContext` line. Every Write/
35
+ // Edit was emitting a tool/path note into the model's context — pure
36
+ // noise. The model already knows what tool and path it called.
36
37
 
37
38
  'use strict';
38
39
 
@@ -47,25 +48,21 @@ process.stdin.on('end', () => {
47
48
  const toolInput =
48
49
  (input.tool_input && typeof input.tool_input === 'object') ? input.tool_input : {};
49
50
 
50
- // Extract the "file_path" mapped onto Claude Code's Write/Edit/MultiEdit
51
- // shapes. Content scanning was removed (debug artifacts are enforced at
52
- // commit time via `make clean-check`, not write time).
53
51
  let filePath = '';
54
- if (toolName === 'Write') {
55
- filePath = String(toolInput.file_path || '');
56
- } else if (toolName === 'Edit') {
57
- filePath = String(toolInput.file_path || '');
58
- } else if (toolName === 'MultiEdit') {
52
+ if (/^(Write|Edit|MultiEdit)$/.test(toolName)) {
59
53
  filePath = String(toolInput.file_path || '');
60
54
  } else {
61
55
  process.stdout.write('{}\n');
62
56
  return;
63
57
  }
64
58
 
59
+ if (!filePath) {
60
+ process.stdout.write('{}\n');
61
+ return;
62
+ }
63
+
65
64
  const lowerPath = filePath.toLowerCase();
66
65
 
67
- // 1. Hard block — secrets + protected paths. .env.example/.sample/.template
68
- // and all lockfiles are explicitly allowed (docs / package-manager output).
69
66
  const allowed = [
70
67
  /\/\.env\.(example|sample|template|dist)$/i,
71
68
  /\/(package-lock|yarn|pnpm-lock|bun)\.lock\w*$/i,
@@ -80,38 +77,19 @@ process.stdin.on('end', () => {
80
77
  /\/node_modules\//,
81
78
  ];
82
79
 
83
- const isAllowed = filePath && allowed.some((re) => re.test(lowerPath));
84
- let blockReason = '';
85
- if (filePath && !isAllowed && blocked.some((re) => re.test(lowerPath))) {
86
- blockReason = filePath;
87
- }
88
-
89
- if (blockReason) {
80
+ const isAllowed = allowed.some((re) => re.test(lowerPath));
81
+ if (!isAllowed && blocked.some((re) => re.test(lowerPath))) {
90
82
  const out = {
91
83
  hookSpecificOutput: {
92
84
  hookEventName: 'PreToolUse',
93
85
  permissionDecision: 'deny',
94
86
  permissionDecisionReason:
95
- `Bizar PreToolUse: refusing to write to protected path '${blockReason}' (Bizar harness policy).`,
87
+ `Bizar PreToolUse: refusing to write to protected path '${filePath}' (Bizar harness policy).`,
96
88
  },
97
89
  };
98
90
  process.stdout.write(JSON.stringify(out) + '\n');
99
91
  return;
100
92
  }
101
93
 
102
- // 2. Always add a small context line for the AI's next decision.
103
- // Debug-artifact warnings (console.log / debugger / .only()) live in
104
- // `make clean-check` — duplicating them here would add noise without
105
- // adding safety (they're caught at commit time, not write time).
106
- const notes = [
107
- `Bizar PreToolUse: tool=${toolName || 'unknown'} path=${filePath || '(no path)'}`,
108
- ];
109
-
110
- const out = {
111
- hookSpecificOutput: {
112
- hookEventName: 'PreToolUse',
113
- additionalContext: notes.join(' '),
114
- },
115
- };
116
- process.stdout.write(JSON.stringify(out) + '\n');
94
+ process.stdout.write('{}\n');
117
95
  });
@@ -17,7 +17,8 @@ import { join } from 'node:path';
17
17
  import { spawnSync } from 'node:child_process';
18
18
  import { findGitCommand } from './git-command-parser.mjs';
19
19
 
20
- const FRESHNESS_WINDOW_MS = 30 * 60 * 1000;
20
+ const FRESHNESS_WINDOW_MS = 4 * 60 * 60 * 1000;
21
+ const TRIVIAL_PATH = /^(?:CHANGELOG\.md|package(-lock)?\.json|.*\/package(-lock)?\.json|\.claude-plugin\/plugin\.json|packages\/[^/]+\/src\/version\.ts)$/;
21
22
 
22
23
  function marker(cwd) {
23
24
  const result = spawnSync('git', ['rev-parse', '--path-format=absolute', '--git-dir'], {
@@ -50,6 +51,18 @@ function readMarker(path) {
50
51
  }
51
52
  }
52
53
 
54
+ function isTrivialDiff(cwd) {
55
+ const result = spawnSync('git', ['diff', '--cached', '--name-only'], {
56
+ cwd,
57
+ encoding: 'utf8',
58
+ timeout: 5_000,
59
+ });
60
+ if (result.status !== 0) return false;
61
+ const paths = result.stdout.split('\n').map((p) => p.trim()).filter(Boolean);
62
+ if (paths.length === 0) return false;
63
+ return paths.every((p) => TRIVIAL_PATH.test(p));
64
+ }
65
+
53
66
  let raw = '';
54
67
  process.stdin.setEncoding('utf8');
55
68
  process.stdin.on('data', (chunk) => { raw += chunk; });
@@ -70,6 +83,9 @@ process.stdin.on('end', () => {
70
83
  const commandValue = input.tool_input?.command;
71
84
  const command = Array.isArray(commandValue) ? commandValue.join(' ') : String(commandValue || '');
72
85
  if (!findGitCommand(command, 'commit')) return;
86
+
87
+ if (isTrivialDiff(cwd)) return;
88
+
73
89
  const approval = readMarker(mark);
74
90
  const age = approval ? Date.now() - approval.timestamp : Infinity;
75
91
  const fingerprint = stagedFingerprint(cwd);
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "bizar-harness",
4
4
  "displayName": "Bizar Harness",
5
- "version": "10.12.1",
5
+ "version": "10.12.2",
6
6
  "description": "Guarded multi-agent workflows for Claude Code with a single orchestrator, durable workflow state, and explicit human approval boundaries.",
7
7
  "author": {
8
8
  "name": "Polderlabs"
@@ -16,8 +16,9 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
16
16
  const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
17
17
  const HOOK_ROOT = resolve(PACKAGE_ROOT, '.claude', 'hooks');
18
18
  const PRETOOL_SAFETY_LEAVES = new Set([
19
- 'pretooluse-editwrite', 'path-ownership-guard', 'content-style-guard',
20
- 'pretooluse-bash', 'git-workflow-guard', 'simplify-guard', 'agent-model-guard',
19
+ 'pretooluse-editwrite', 'path-ownership-guard',
20
+ 'pretooluse-bash', 'git-workflow-guard',
21
+ 'agent-model-guard',
21
22
  ]);
22
23
 
23
24
  export const HOOK_PROGRAMS = Object.freeze({
@@ -65,16 +66,13 @@ export const EVENT_CHAINS = Object.freeze({
65
66
  'pre-tool-use': Object.freeze([
66
67
  'pretooluse-editwrite',
67
68
  'path-ownership-guard',
68
- 'content-style-guard',
69
69
  'pretooluse-bash',
70
70
  'git-workflow-guard',
71
- 'simplify-guard',
72
71
  ]),
73
72
  'permission-request': Object.freeze(['permission-request-policy']),
74
73
  'post-tool-use': Object.freeze([
75
74
  'posttooluse-editwrite',
76
75
  'auto-instinct',
77
- 'simplify-guard',
78
76
  ]),
79
77
  'post-tool-use-failure': Object.freeze(['post-tool-use-failure-policy']),
80
78
  'subagent-start': Object.freeze([
@@ -224,10 +222,10 @@ export function selectEventChain(eventKey, input = '') {
224
222
 
225
223
  if (eventKey === 'pre-tool-use') {
226
224
  if (/^(Write|Edit|MultiEdit)$/.test(toolName)) {
227
- return ['pretooluse-editwrite', 'path-ownership-guard', 'content-style-guard'];
225
+ return ['pretooluse-editwrite', 'path-ownership-guard'];
228
226
  }
229
227
  if (toolName === 'Bash') {
230
- return ['pretooluse-bash', 'git-workflow-guard', 'content-style-guard', 'simplify-guard'];
228
+ return ['pretooluse-bash', 'git-workflow-guard'];
231
229
  }
232
230
  if (toolName === 'Agent') return ['agent-model-guard'];
233
231
  return [];
@@ -236,7 +234,6 @@ export function selectEventChain(eventKey, input = '') {
236
234
  if (eventKey === 'post-tool-use') {
237
235
  if (/^(Write|Edit|MultiEdit)$/.test(toolName)) return ['posttooluse-editwrite'];
238
236
  if (toolName === 'Bash') return ['auto-instinct'];
239
- if (toolName === 'Skill') return ['simplify-guard'];
240
237
  return [];
241
238
  }
242
239
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.12.1",
3
+ "version": "10.12.2",
4
4
  "description": "Autonomous, human-in-the-loop multi-agent harness for Claude Code with guarded workflows, typed SDK primitives, and MCP tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export declare const SDK_VERSION: "10.12.1";
4
+ export declare const SDK_VERSION: "10.12.2";
5
5
  //# sourceMappingURL=version.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export const SDK_VERSION = "10.12.1";
4
+ export const SDK_VERSION = "10.12.2";
5
5
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "10.12.1",
3
+ "version": "10.12.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",