- Created skills/ directory - Moved 272 skills to skills/ subfolder - Kept agents/ at root level - Kept installation scripts and docs at root level Repository structure: - skills/ - All 272 skills from skills.sh - agents/ - Agent definitions - *.sh, *.ps1 - Installation scripts - README.md, etc. - Documentation Co-Authored-By: Claude <noreply@anthropic.com>
230 lines
6.3 KiB
TypeScript
230 lines
6.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { analyzeCommand } from '../src/core/analyze.ts';
|
|
import { loadConfig } from '../src/core/config.ts';
|
|
|
|
function writeConfig(dir: string, data: object): void {
|
|
const path = join(dir, '.safety-net.json');
|
|
writeFileSync(path, JSON.stringify(data), 'utf-8');
|
|
}
|
|
|
|
function runGuard(command: string, cwd?: string): string | null {
|
|
const config = loadConfig(cwd);
|
|
return analyzeCommand(command, { cwd, config })?.reason ?? null;
|
|
}
|
|
|
|
function assertBlocked(command: string, reasonContains: string, cwd?: string): void {
|
|
const result = runGuard(command, cwd);
|
|
expect(result).not.toBeNull();
|
|
expect(result).toContain(reasonContains);
|
|
}
|
|
|
|
function assertAllowed(command: string, cwd?: string): void {
|
|
const result = runGuard(command, cwd);
|
|
expect(result).toBeNull();
|
|
}
|
|
|
|
describe('custom rules integration', () => {
|
|
let tempDir: string;
|
|
|
|
beforeEach(() => {
|
|
tempDir = mkdtempSync(join(tmpdir(), 'safety-net-custom-rules-'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test('custom rule blocks command', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-git-add-all',
|
|
command: 'git',
|
|
subcommand: 'add',
|
|
block_args: ['-A', '--all', '.'],
|
|
reason: 'Use specific files.',
|
|
},
|
|
],
|
|
});
|
|
assertBlocked('git add -A', '[block-git-add-all] Use specific files.', tempDir);
|
|
});
|
|
|
|
test('custom rule blocks with dot', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-git-add-all',
|
|
command: 'git',
|
|
subcommand: 'add',
|
|
block_args: ['-A', '--all', '.'],
|
|
reason: 'Use specific files.',
|
|
},
|
|
],
|
|
});
|
|
assertBlocked('git add .', '[block-git-add-all]', tempDir);
|
|
});
|
|
|
|
test('custom rule allows non-matching command', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-git-add-all',
|
|
command: 'git',
|
|
subcommand: 'add',
|
|
block_args: ['-A'],
|
|
reason: 'Use specific files.',
|
|
},
|
|
],
|
|
});
|
|
assertAllowed('git add file.txt', tempDir);
|
|
});
|
|
|
|
test('builtin rule takes precedence', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'custom-reset-rule',
|
|
command: 'git',
|
|
subcommand: 'reset',
|
|
block_args: ['--soft'],
|
|
reason: 'Custom reason.',
|
|
},
|
|
],
|
|
});
|
|
// Built-in rule blocks git reset --hard, not custom rule
|
|
assertBlocked('git reset --hard', 'git reset --hard destroys', tempDir);
|
|
});
|
|
|
|
test('multiple custom rules - any match triggers block', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-git-add-all',
|
|
command: 'git',
|
|
subcommand: 'add',
|
|
block_args: ['-A'],
|
|
reason: 'No blanket add.',
|
|
},
|
|
{
|
|
name: 'block-npm-global',
|
|
command: 'npm',
|
|
subcommand: 'install',
|
|
block_args: ['-g'],
|
|
reason: 'No global installs.',
|
|
},
|
|
],
|
|
});
|
|
assertBlocked('git add -A', '[block-git-add-all]', tempDir);
|
|
assertBlocked('npm install -g pkg', '[block-npm-global]', tempDir);
|
|
});
|
|
|
|
test('rule without subcommand matches any invocation', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-npm-global',
|
|
command: 'npm',
|
|
block_args: ['-g', '--global'],
|
|
reason: 'No global.',
|
|
},
|
|
],
|
|
});
|
|
assertBlocked('npm install -g pkg', '[block-npm-global]', tempDir);
|
|
assertBlocked('npm uninstall -g pkg', '[block-npm-global]', tempDir);
|
|
});
|
|
|
|
test('no config uses builtin only', () => {
|
|
// tempDir has no config file
|
|
assertBlocked('git reset --hard', 'git reset --hard destroys', tempDir);
|
|
assertAllowed('git add -A', tempDir);
|
|
});
|
|
|
|
test('empty rules list uses builtin only', () => {
|
|
writeConfig(tempDir, { version: 1, rules: [] });
|
|
assertBlocked('git reset --hard', 'git reset --hard destroys', tempDir);
|
|
assertAllowed('git add -A', tempDir);
|
|
});
|
|
|
|
test('invalid config uses builtin only', () => {
|
|
const path = join(tempDir, '.safety-net.json');
|
|
writeFileSync(path, '{"version": 2}', 'utf-8');
|
|
|
|
assertBlocked('git reset --hard', 'git reset --hard destroys', tempDir);
|
|
assertAllowed('echo hello', tempDir);
|
|
});
|
|
|
|
test('custom rules not applied to embedded commands', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-git-add-all',
|
|
command: 'git',
|
|
subcommand: 'add',
|
|
block_args: ['-A'],
|
|
reason: 'No blanket add.',
|
|
},
|
|
],
|
|
});
|
|
// Direct command is blocked
|
|
assertBlocked('git add -A', '[block-git-add-all]', tempDir);
|
|
// Embedded in bash -c is NOT blocked by custom rule (per spec)
|
|
assertAllowed("bash -c 'git add -A'", tempDir);
|
|
});
|
|
|
|
test('custom rules apply to xargs', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-xargs-grep',
|
|
command: 'xargs',
|
|
block_args: ['grep'],
|
|
reason: 'Use ripgrep instead.',
|
|
},
|
|
],
|
|
});
|
|
assertBlocked('find . | xargs grep pattern', '[block-xargs-grep]', tempDir);
|
|
});
|
|
|
|
test('custom rules apply to parallel', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-parallel-curl',
|
|
command: 'parallel',
|
|
block_args: ['curl'],
|
|
reason: 'No parallel curl.',
|
|
},
|
|
],
|
|
});
|
|
assertBlocked('parallel curl ::: url1 url2', '[block-parallel-curl]', tempDir);
|
|
});
|
|
|
|
test('attached option value not false positive', () => {
|
|
writeConfig(tempDir, {
|
|
version: 1,
|
|
rules: [
|
|
{
|
|
name: 'block-p-flag',
|
|
command: 'git',
|
|
block_args: ['-p'],
|
|
reason: 'No -p allowed.',
|
|
},
|
|
],
|
|
});
|
|
// -C/path/to/project contains 'p' in the path, but should NOT match -p
|
|
assertAllowed('git -C/path/to/project status', tempDir);
|
|
});
|
|
});
|