@polderlabs/bizar 10.7.0 → 10.7.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.
@@ -46,7 +46,8 @@ Highlights:
46
46
  - bullet 2
47
47
  - bullet 3
48
48
 
49
- Co-authored-by: Odin <noreply@polderlabs.dev>"
49
+ Tests: <N> pass / 0 fail across <M> runners.
50
+ tsc: clean. build: <main> KB + <mobile> KB."
50
51
 
51
52
  # Tag
52
53
  git tag -a vX.Y.Z -m "vX.Y.Z — short title"
package/cli/provision.mjs CHANGED
@@ -490,6 +490,34 @@ export async function syncHookFiles({ dryRun = false } = {}) {
490
490
  return { ok: true, message: 'hook scripts installed', copied: readdirSync(dest).length, skipped: 0 };
491
491
  }
492
492
 
493
+ // ─── git hooks ──────────────────────────────────────────────────────────────
494
+ //
495
+ // Install the repo-local git hooks (.git/hooks/commit-msg, pre-commit, pre-push)
496
+ // from scripts/git-hooks/. Runs only when invoked from inside a git repo, so
497
+ // the npm-install path (which doesn't have a .git/) is a no-op.
498
+ export function installGitHooks({ dryRun = false } = {}) {
499
+ if (!existsSync(REPO_ROOT) || !existsSync(join(REPO_ROOT, '.git'))) {
500
+ return { ok: true, message: 'no git repo — skipping git hook install', installed: [] };
501
+ }
502
+ if (dryRun) return { ok: true, message: '[dry-run] would install git hooks from scripts/git-hooks/', installed: [] };
503
+
504
+ const src = join(REPO_ROOT, 'scripts', 'git-hooks');
505
+ const dest = join(REPO_ROOT, '.git', 'hooks');
506
+ if (!existsSync(src)) return { ok: true, message: `no git hooks source at ${src}`, installed: [] };
507
+ ensureDir(dest);
508
+ const installed = [];
509
+ for (const name of readdirSync(src)) {
510
+ const fp = join(src, name);
511
+ const st = statSync(fp);
512
+ if (!st.isFile()) continue;
513
+ const dst = join(dest, name);
514
+ copyFileSync(fp, dst);
515
+ try { chmodSync(dst, 0o755); } catch { /* ignore */ }
516
+ installed.push(name);
517
+ }
518
+ return { ok: true, message: `${installed.length} git hook(s) installed`, installed };
519
+ }
520
+
493
521
  // ─── settings.json ──────────────────────────────────────────────────────────
494
522
 
495
523
  export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
@@ -697,6 +725,7 @@ export async function runProvision(opts = {}) {
697
725
  await runStep('Syncing commands', () => syncCommandFiles({ dryRun, force }));
698
726
  await runStep('Syncing rules', () => syncRulesFiles({ dryRun, force }));
699
727
  await runStep('Syncing hooks', () => syncHookFiles({ dryRun, force }));
728
+ await runStep('Installing git hooks', () => installGitHooks({ dryRun }));
700
729
  await runStep('Building SDK', () => buildSdk({ dryRun }));
701
730
  await runStep('Building plugin', () => buildPlugin({ dryRun }));
702
731
  await runStep('Building dashboard', () => buildDash({ dryRun }));
package/install.sh CHANGED
@@ -32,6 +32,13 @@ EOF
32
32
  Darwin) macos;;
33
33
  *) err "Unsupported OS — use install.ps1 on Windows"; exit 1;;
34
34
  esac
35
+ # Auto-install the repo-local git hooks (pre-commit, pre-push, commit-msg).
36
+ # The commit-msg hook strips Claude/agent co-author trailers from commits
37
+ # so the human author identity is the only one on every Bizar commit.
38
+ if [ "${DRY:-0}" -eq 0 ] && [ -f "$(dirname "$0")/scripts/install-hooks.sh" ]; then
39
+ note "Installing git hooks (commit-msg + pre-commit + pre-push)..."
40
+ bash "$(dirname "$0")/scripts/install-hooks.sh" || warn "git hook install failed — run ./scripts/install-hooks.sh manually"
41
+ fi
35
42
  local a=(--mode=install)
36
43
  [ "$ni" -eq 1 ] && a+=(--yes)
37
44
  [ "${DRY:-0}" -eq 1 ] && a+=(--dry-run)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.7.0",
3
+ "version": "10.7.1",
4
4
  "description": "90s-office multi-agent system for Claude Code — 14 agents across 4 cost tiers with cost-aware routing, plans, and a configurable agent harness. Ships as a single npm package with the dashboard server, Claude Code MCP server, and typed SDK.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,61 @@
1
+ /**
2
+ * scripts/git-hooks/__tests__/commit-msg.test.mjs
3
+ *
4
+ * Tests the commit-msg hook that strips Claude/agent co-author trailers.
5
+ * Runs the bash hook against synthetic commit messages and asserts the
6
+ * post-rewrite contents.
7
+ */
8
+ import { test } from 'node:test';
9
+ import assert from 'node:assert/strict';
10
+ import { execFileSync } from 'node:child_process';
11
+ import { mkdtempSync, writeFileSync, readFileSync } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { join, dirname, resolve } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ const HOOK = resolve(__dirname, '..', 'commit-msg');
18
+
19
+ function runHook(input) {
20
+ const dir = mkdtempSync(join(tmpdir(), 'bizar-hook-'));
21
+ const msgFile = join(dir, 'msg');
22
+ writeFileSync(msgFile, input);
23
+ execFileSync('bash', [HOOK, msgFile], { stdio: 'pipe' });
24
+ return readFileSync(msgFile, 'utf8');
25
+ }
26
+
27
+ test('strips Claude Co-Authored-By trailer', () => {
28
+ const out = runHook('feat: test\n\nCo-Authored-By: Claude <noreply@anthropic.com>\n');
29
+ assert.doesNotMatch(out, /Claude <noreply@anthropic\.com>/);
30
+ assert.match(out, /feat: test/);
31
+ });
32
+
33
+ test('strips lowercase co-authored-by trailer (case-insensitive header)', () => {
34
+ const out = runHook('feat: test\n\nco-authored-by: claude <noreply@anthropic.com>\n');
35
+ assert.doesNotMatch(out, /claude <noreply@anthropic\.com>/);
36
+ });
37
+
38
+ test('strips agent-themed polderlabs.dev trailers', () => {
39
+ const out = runHook('feat: test\n\nCo-authored-by: Odin <noreply@polderlabs.dev>\nCo-authored-by: Thor <noreply@polderlabs.dev>\n');
40
+ assert.doesNotMatch(out, /Odin <noreply@polderlabs\.dev>/);
41
+ assert.doesNotMatch(out, /Thor <noreply@polderlabs\.dev>/);
42
+ });
43
+
44
+ test('preserves human co-author trailers', () => {
45
+ const out = runHook('feat: test\n\nCo-Authored-By: Real Human <real@example.com>\n');
46
+ assert.match(out, /Real Human <real@example\.com>/);
47
+ });
48
+
49
+ test('preserves embedded Claude mentions in commit body', () => {
50
+ const out = runHook('feat: test\n\nDiscussed Claude <noreply@anthropic.com> inline.\n\nSigned-off-by: Berk <berk@berkderooij.nl>\n');
51
+ assert.match(out, /Discussed Claude <noreply@anthropic\.com> inline\./);
52
+ assert.match(out, /Signed-off-by: Berk <berk@berkderooij\.nl>/);
53
+ });
54
+
55
+ test('collapses blank lines left by trailer removal', () => {
56
+ const input = 'feat: x\n\nbody line\n\n\nCo-Authored-By: Claude <noreply@anthropic.com>\n';
57
+ const out = runHook(input);
58
+ // No triple-newlines, no trailing Claude line
59
+ assert.doesNotMatch(out, /\n\n\n/);
60
+ assert.doesNotMatch(out, /Claude/);
61
+ });
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # Strip Claude/agent co-author trailers that don't belong on Bizar commits.
3
+ #
4
+ # Why: Claude Code's CLI appends "Co-Authored-By: Claude <noreply@anthropic.com>"
5
+ # by default; older Bizar release docs also templated Norse/office-agent
6
+ # personas as co-authors. This repo's commits should only carry the human
7
+ # author identity. Any matching trailers get removed before the commit lands.
8
+ #
9
+ # To bypass in an emergency: git commit --no-verify
10
+ set -euo pipefail
11
+
12
+ msg_file="$1"
13
+ tmp="$(mktemp)"
14
+ trap 'rm -f "$tmp"' EXIT
15
+
16
+ # Remove the two known offender trailers (case-insensitive on the header).
17
+ # Also strip any Co-authored-by: line whose email ends in @anthropic.com
18
+ # or @polderlabs.dev — defense-in-depth against future variations.
19
+ sed -E \
20
+ -e '/^[Cc]o-[Aa]uthored-[Bb]y:[[:space:]]*Claude <noreply@anthropic\.com>[[:space:]]*$/d' \
21
+ -e '/^[Cc]o-[Aa]uthored-[Bb]y:[[:space:]]*[^<]*<(noreply@anthropic\.com|noreply@polderlabs\.dev)>$/d' \
22
+ "$msg_file" > "$tmp"
23
+
24
+ # Collapse runs of blank lines that may result from trailer removal.
25
+ awk 'BEGIN { blank=0; seen=0 } {
26
+ if ($0 == "") { blank++ }
27
+ else {
28
+ if (blank > 0 && seen) print "";
29
+ blank=0; print; seen=1
30
+ }
31
+ }' "$tmp" > "${tmp}.2"
32
+ mv "${tmp}.2" "$tmp"
33
+
34
+ if ! cmp -s "$msg_file" "$tmp"; then
35
+ echo "commit-msg: stripped Claude/agent co-author trailers" >&2
36
+ fi
37
+
38
+ mv "$tmp" "$msg_file"
@@ -18,6 +18,9 @@ chmod +x "$GIT_HOOKS_DIR/pre-commit"
18
18
  cp "$HOOKS_DIR/pre-push" "$GIT_HOOKS_DIR/pre-push"
19
19
  chmod +x "$GIT_HOOKS_DIR/pre-push"
20
20
 
21
+ cp "$HOOKS_DIR/commit-msg" "$GIT_HOOKS_DIR/commit-msg"
22
+ chmod +x "$GIT_HOOKS_DIR/commit-msg"
23
+
21
24
  echo "✓ Installed pre-commit hook"
22
25
  echo " The hook scans staged changes for likely secrets (Bearer tokens, API keys, etc.)"
23
26
  echo " It also blocks commits touching .bizar/ or config/cline.json (per-machine state)"
@@ -27,3 +30,9 @@ echo "✓ Installed pre-push hook"
27
30
  echo " Blocks pushes whose commit range includes .bizar/ or config/cline.json changes"
28
31
  echo " Defense-in-depth: catches bypasses of pre-commit via --no-verify"
29
32
  echo " To bypass in an emergency: git push --no-verify"
33
+ echo ""
34
+ echo "✓ Installed commit-msg hook"
35
+ echo " Strips Co-Authored-By: Claude <noreply@anthropic.com> (and agent-themed"
36
+ echo " noreply@polderlabs.dev trailers) from commit messages — this repo's"
37
+ echo " commits should only carry the human author identity."
38
+ echo " To bypass in an emergency: git commit --no-verify"