import assert from 'node:assert/strict' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import test from 'node:test' import { checkTool, forDshReason } from '../bundle/lib/guards.js' import { cleanup, git, makeRepo, write } from './helpers.mjs' const hooksDir = fileURLToPath(new URL('../bundle/hooks/', import.meta.url)) function call(name, args) { return { name, arguments: args } } test('guard-main blocks a write on main', () => { const repo = makeRepo() try { const reason = checkTool({ exec: call('write', { path: join(repo, 'a.txt'), content: 'x' }), cwd: repo, hooksDir, }) assert.match(reason ?? '', /BLOCKED \[guard-main\]/) assert.doesNotMatch(reason ?? '', /superpowers:/) assert.match(reason ?? '', /using-git-worktrees/) } finally { cleanup(repo) } }) test('forDshReason removes the Claude skill namespace from hook messages', () => { assert.equal( forDshReason('Run superpowers:brainstorming; use superpowers:using-git-worktrees.'), 'Run brainstorming; use using-git-worktrees.', ) }) test('guard-main allows a write on a feature branch', () => { const repo = makeRepo() try { git(repo, ['checkout', '-b', 'feat/x']) const reason = checkTool({ exec: call('write', { path: join(repo, 'a.txt'), content: 'x' }), cwd: repo, hooksDir, }) assert.equal(reason, undefined) } finally { cleanup(repo) } }) test('guard-main inspects bash commands on main', () => { const repo = makeRepo() try { const blocked = checkTool({ exec: call('bash', { command: 'echo hi > a.txt' }), cwd: repo, hooksDir, }) assert.match(blocked ?? '', /BLOCKED \[guard-main\]/) const allowed = checkTool({ exec: call('bash', { command: 'git status --short' }), cwd: repo, hooksDir, }) assert.equal(allowed, undefined) } finally { cleanup(repo) } }) test('require-plan blocks src edits without a plan or .bounded', () => { const repo = makeRepo() try { git(repo, ['checkout', '-b', 'feat/x']) write(join(repo, 'src', '.keep'), '') const blocked = checkTool({ exec: call('write', { path: join(repo, 'src', 'a.ts'), content: 'export {}\n' }), cwd: repo, hooksDir, }) assert.match(blocked ?? '', /BLOCKED \[require-plan\]/) assert.doesNotMatch(blocked ?? '', /superpowers:/) assert.match(blocked ?? '', /brainstorming/) write(join(repo, '.bounded'), '') const bounded = checkTool({ exec: call('write', { path: join(repo, 'src', 'a.ts'), content: 'export {}\n' }), cwd: repo, hooksDir, }) assert.equal(bounded, undefined) } finally { cleanup(repo) } }) test('require-plan accepts a plan that names the branch', () => { const repo = makeRepo() try { git(repo, ['checkout', '-b', 'feat/x']) write(join(repo, 'src', '.keep'), '') write(join(repo, 'docs', 'superpowers', 'plans', 'test.md'), '# plan for feat/x\n') const reason = checkTool({ exec: call('edit', { path: join(repo, 'src', 'a.ts'), old_string: 'a', new_string: 'b' }), cwd: repo, hooksDir, }) assert.equal(reason, undefined) } finally { cleanup(repo) } }) test('guard-main blocks an edit on main before require-plan runs', () => { const repo = makeRepo() try { const reason = checkTool({ exec: call('edit', { path: join(repo, 'README.md'), old_string: 'test', new_string: 'changed' }), cwd: repo, hooksDir, }) assert.match(reason ?? '', /BLOCKED \[guard-main\]/) } finally { cleanup(repo) } })