Reorganize: Move all skills to skills/ folder
- 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>
This commit is contained in:
450
skills/plugins/claude-code-safety-net/tests/rules-git.test.ts
Normal file
450
skills/plugins/claude-code-safety-net/tests/rules-git.test.ts
Normal file
@@ -0,0 +1,450 @@
|
||||
import { describe, test } from 'bun:test';
|
||||
import { assertAllowed, assertBlocked } from './helpers.ts';
|
||||
|
||||
describe('git checkout', () => {
|
||||
test('git checkout -- blocked', () => {
|
||||
assertBlocked('git checkout -- file.txt', 'git checkout --');
|
||||
});
|
||||
|
||||
test('git checkout -- multiple files blocked', () => {
|
||||
assertBlocked('git checkout -- file1.txt file2.txt', 'git checkout --');
|
||||
});
|
||||
|
||||
test('git checkout -- . blocked', () => {
|
||||
assertBlocked('git checkout -- .', 'git checkout --');
|
||||
});
|
||||
|
||||
test('git checkout ref -- blocked', () => {
|
||||
assertBlocked('git checkout HEAD -- file.txt', 'git checkout <ref> -- <path>');
|
||||
});
|
||||
|
||||
test('git checkout -b allowed', () => {
|
||||
assertAllowed('git checkout -b new-branch');
|
||||
});
|
||||
|
||||
test('git checkout --orphan allowed', () => {
|
||||
assertAllowed('git checkout --orphan orphan-branch');
|
||||
});
|
||||
|
||||
test('git checkout -bnew-branch allowed', () => {
|
||||
assertAllowed('git checkout -bnew-branch');
|
||||
});
|
||||
|
||||
test('git checkout -Bnew-branch allowed', () => {
|
||||
assertAllowed('git checkout -Bnew-branch');
|
||||
});
|
||||
|
||||
test('git checkout ref pathspec blocked', () => {
|
||||
assertBlocked('git checkout HEAD file.txt', 'multiple positional args');
|
||||
});
|
||||
|
||||
test('git checkout ref multiple pathspecs blocked', () => {
|
||||
assertBlocked('git checkout main a.txt b.txt', 'multiple positional args');
|
||||
});
|
||||
|
||||
test('git checkout branch only allowed', () => {
|
||||
assertAllowed('git checkout main');
|
||||
});
|
||||
|
||||
test('git checkout -U3 main allowed', () => {
|
||||
assertAllowed('git checkout -U3 main');
|
||||
});
|
||||
|
||||
test('git checkout - allowed', () => {
|
||||
assertAllowed('git checkout -');
|
||||
});
|
||||
|
||||
test('git checkout --detach allowed', () => {
|
||||
assertAllowed('git checkout --detach main');
|
||||
});
|
||||
|
||||
test('git checkout --recurse-submodules allowed', () => {
|
||||
assertAllowed('git checkout --recurse-submodules main');
|
||||
});
|
||||
|
||||
test('git checkout --pathspec-from-file blocked', () => {
|
||||
assertBlocked(
|
||||
'git checkout HEAD --pathspec-from-file=paths.txt',
|
||||
'git checkout --pathspec-from-file',
|
||||
);
|
||||
});
|
||||
|
||||
test('git checkout ref pathspec from file arg blocked', () => {
|
||||
assertBlocked(
|
||||
'git checkout HEAD --pathspec-from-file paths.txt',
|
||||
'git checkout --pathspec-from-file',
|
||||
);
|
||||
});
|
||||
|
||||
test('git checkout --conflict=merge allowed', () => {
|
||||
assertAllowed('git checkout --conflict=merge main');
|
||||
});
|
||||
|
||||
test('git checkout --conflict merge allowed', () => {
|
||||
assertAllowed('git checkout --conflict merge main');
|
||||
});
|
||||
|
||||
test('git checkout -q ref pathspec blocked', () => {
|
||||
assertBlocked('git checkout -q main file.txt', 'multiple positional args');
|
||||
});
|
||||
|
||||
test('git checkout --recurse-submodules=checkout allowed', () => {
|
||||
assertAllowed('git checkout --recurse-submodules=checkout main');
|
||||
});
|
||||
|
||||
test('git checkout --recurse-submodules=on-demand allowed', () => {
|
||||
assertAllowed('git checkout --recurse-submodules=on-demand main');
|
||||
});
|
||||
|
||||
test('git checkout --recurse-submodules ref pathspec blocked', () => {
|
||||
assertBlocked('git checkout --recurse-submodules main file.txt', 'multiple positional args');
|
||||
});
|
||||
|
||||
test('git checkout --recurse-submodules without mode allowed', () => {
|
||||
assertAllowed('git checkout --recurse-submodules main');
|
||||
});
|
||||
|
||||
test('git checkout --recurse-submodules without mode ref pathspec blocked', () => {
|
||||
assertBlocked('git checkout --recurse-submodules main file.txt', 'multiple positional args');
|
||||
});
|
||||
|
||||
test('git checkout --track=direct allowed', () => {
|
||||
assertAllowed('git checkout --track=direct main');
|
||||
});
|
||||
|
||||
test('git checkout --track=inherit allowed', () => {
|
||||
assertAllowed('git checkout --track=inherit main');
|
||||
});
|
||||
|
||||
test('git checkout --track without mode ref pathspec blocked', () => {
|
||||
assertBlocked('git checkout --track main file.txt', 'multiple positional args');
|
||||
});
|
||||
|
||||
test('git checkout --unified 3 allowed', () => {
|
||||
assertAllowed('git checkout --unified 3 main');
|
||||
});
|
||||
|
||||
test('git checkout -U attached value allowed', () => {
|
||||
assertAllowed('git checkout -U3 main');
|
||||
});
|
||||
|
||||
test('git checkout unknown long option consumes value allowed', () => {
|
||||
assertAllowed('git checkout --unknown main file.txt');
|
||||
});
|
||||
|
||||
test('git checkout unknown long option does not consume option value allowed', () => {
|
||||
assertAllowed('git checkout --unknown -q main');
|
||||
});
|
||||
|
||||
test('git checkout unknown long option equals allowed', () => {
|
||||
assertAllowed('git checkout --unknown=value main');
|
||||
});
|
||||
});
|
||||
|
||||
describe('git restore', () => {
|
||||
test('git restore file blocked', () => {
|
||||
assertBlocked('git restore file.txt', 'git restore');
|
||||
});
|
||||
|
||||
test('git restore multiple files blocked', () => {
|
||||
assertBlocked('git restore a.txt b.txt', 'git restore');
|
||||
});
|
||||
|
||||
test('git restore --worktree blocked', () => {
|
||||
assertBlocked('git restore --worktree file.txt', 'git restore --worktree');
|
||||
});
|
||||
|
||||
test('git restore --staged allowed', () => {
|
||||
assertAllowed('git restore --staged file.txt');
|
||||
});
|
||||
|
||||
test('git restore --staged . allowed', () => {
|
||||
assertAllowed('git restore --staged .');
|
||||
});
|
||||
|
||||
test('git restore --help allowed', () => {
|
||||
assertAllowed('git restore --help');
|
||||
});
|
||||
});
|
||||
|
||||
describe('git reset', () => {
|
||||
test('git reset --hard blocked', () => {
|
||||
assertBlocked('git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git reset --hard HEAD~1 blocked', () => {
|
||||
assertBlocked('git reset --hard HEAD~1', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git reset -q --hard blocked', () => {
|
||||
assertBlocked('git reset -q --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('echo ok | git reset --hard blocked', () => {
|
||||
assertBlocked('echo ok | git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git -C repo reset --hard blocked', () => {
|
||||
assertBlocked('git -C repo reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git -Crepo reset --hard blocked', () => {
|
||||
assertBlocked('git -Crepo reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git reset --hard global option -C attached blocked', () => {
|
||||
assertBlocked('git -Crepo reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git --git-dir=repo/.git reset --hard blocked', () => {
|
||||
assertBlocked('git --git-dir=repo/.git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git --git-dir repo/.git reset --hard blocked', () => {
|
||||
assertBlocked('git --git-dir repo/.git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git --work-tree=repo reset --hard blocked', () => {
|
||||
assertBlocked('git --work-tree=repo reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git --no-pager reset --hard blocked', () => {
|
||||
assertBlocked('git --no-pager reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git -c foo=bar reset --hard blocked', () => {
|
||||
assertBlocked('git -c foo=bar reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git -- reset --hard blocked', () => {
|
||||
assertBlocked('git -- reset --hard', 'reset --hard');
|
||||
});
|
||||
|
||||
test('git -cfoo=bar reset --hard blocked', () => {
|
||||
assertBlocked('git -cfoo=bar reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('sudo env VAR=1 git reset --hard blocked', () => {
|
||||
assertBlocked('sudo env VAR=1 git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env -- git reset --hard blocked', () => {
|
||||
assertBlocked('env -- git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('command -- git reset --hard blocked', () => {
|
||||
assertBlocked('command -- git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('env -u PATH git reset --hard blocked', () => {
|
||||
assertBlocked('env -u PATH git reset --hard', 'git reset --hard');
|
||||
});
|
||||
|
||||
test('git reset --merge blocked', () => {
|
||||
assertBlocked('git reset --merge', 'git reset --merge');
|
||||
});
|
||||
|
||||
test("sh -c 'git reset --hard' blocked", () => {
|
||||
assertBlocked("sh -c 'git reset --hard'", 'git reset --hard');
|
||||
});
|
||||
});
|
||||
|
||||
describe('git clean', () => {
|
||||
test('git clean -f blocked', () => {
|
||||
assertBlocked('git clean -f', 'git clean');
|
||||
});
|
||||
|
||||
test('git clean --force blocked', () => {
|
||||
assertBlocked('git clean --force', 'git clean -f');
|
||||
});
|
||||
|
||||
test('git clean -nf blocked', () => {
|
||||
assertBlocked('git clean -nf', 'git clean -f');
|
||||
});
|
||||
|
||||
test('git clean -n && git clean -f blocked', () => {
|
||||
assertBlocked('git clean -n && git clean -f', 'git clean -f');
|
||||
});
|
||||
|
||||
test('git clean -fd blocked', () => {
|
||||
assertBlocked('git clean -fd', 'git clean');
|
||||
});
|
||||
|
||||
test('git clean -xf blocked', () => {
|
||||
assertBlocked('git clean -xf', 'git clean');
|
||||
});
|
||||
|
||||
test('git clean -n allowed', () => {
|
||||
assertAllowed('git clean -n');
|
||||
});
|
||||
|
||||
test('git clean --dry-run allowed', () => {
|
||||
assertAllowed('git clean --dry-run');
|
||||
});
|
||||
|
||||
test('git clean -nd allowed', () => {
|
||||
assertAllowed('git clean -nd');
|
||||
});
|
||||
});
|
||||
|
||||
describe('git push', () => {
|
||||
test('git push --force blocked', () => {
|
||||
assertBlocked('git push --force', 'push --force');
|
||||
});
|
||||
|
||||
test('git push --force origin main blocked', () => {
|
||||
assertBlocked('git push --force origin main', 'push --force');
|
||||
});
|
||||
|
||||
test('git push -f blocked', () => {
|
||||
assertBlocked('git push -f', 'push --force');
|
||||
});
|
||||
|
||||
test('git push -f origin main blocked', () => {
|
||||
assertBlocked('git push -f origin main', 'push --force');
|
||||
});
|
||||
|
||||
test('git push --force-with-lease allowed', () => {
|
||||
assertAllowed('git push --force-with-lease');
|
||||
});
|
||||
|
||||
test('git push --force-with-lease origin main allowed', () => {
|
||||
assertAllowed('git push --force-with-lease origin main');
|
||||
});
|
||||
|
||||
test('git push --force-with-lease=refs/heads/main allowed', () => {
|
||||
assertAllowed('git push --force-with-lease=refs/heads/main');
|
||||
});
|
||||
|
||||
test('git push --force --force-with-lease allowed', () => {
|
||||
assertAllowed('git push --force --force-with-lease');
|
||||
});
|
||||
|
||||
test('git push -f --force-with-lease allowed', () => {
|
||||
assertAllowed('git push -f --force-with-lease');
|
||||
});
|
||||
|
||||
test('git push origin main allowed', () => {
|
||||
assertAllowed('git push origin main');
|
||||
});
|
||||
});
|
||||
|
||||
describe('git worktree', () => {
|
||||
test('git worktree remove --force blocked', () => {
|
||||
assertBlocked('git worktree remove --force /tmp/wt', 'git worktree remove --force');
|
||||
});
|
||||
|
||||
test('git worktree remove -f blocked', () => {
|
||||
assertBlocked('git worktree remove -f /tmp/wt', 'git worktree remove --force');
|
||||
});
|
||||
|
||||
test('git worktree remove without force allowed', () => {
|
||||
assertAllowed('git worktree remove /tmp/wt');
|
||||
});
|
||||
|
||||
test('git worktree remove -- -f allowed', () => {
|
||||
assertAllowed('git worktree remove -- -f');
|
||||
});
|
||||
});
|
||||
|
||||
describe('git branch', () => {
|
||||
test('git branch -D blocked', () => {
|
||||
assertBlocked('git branch -D feature', 'git branch -D');
|
||||
});
|
||||
|
||||
test('git branch -Dv blocked', () => {
|
||||
assertBlocked('git branch -Dv feature', 'git branch -D');
|
||||
});
|
||||
|
||||
test('git branch -d allowed', () => {
|
||||
assertAllowed('git branch -d feature');
|
||||
});
|
||||
});
|
||||
|
||||
describe('git stash', () => {
|
||||
test('git stash drop blocked', () => {
|
||||
assertBlocked('git stash drop', 'git stash drop');
|
||||
});
|
||||
|
||||
test('git stash drop stash@{0} blocked', () => {
|
||||
assertBlocked('git stash drop stash@{0}', 'git stash drop');
|
||||
});
|
||||
|
||||
test('git stash clear blocked', () => {
|
||||
assertBlocked('git stash clear', 'git stash clear');
|
||||
});
|
||||
|
||||
test('git stash allowed', () => {
|
||||
assertAllowed('git stash');
|
||||
});
|
||||
|
||||
test('git stash list allowed', () => {
|
||||
assertAllowed('git stash list');
|
||||
});
|
||||
|
||||
test('git stash pop allowed', () => {
|
||||
assertAllowed('git stash pop');
|
||||
});
|
||||
});
|
||||
|
||||
describe('safe commands', () => {
|
||||
test('git allowed', () => {
|
||||
assertAllowed('git');
|
||||
});
|
||||
|
||||
test('git --help allowed', () => {
|
||||
assertAllowed('git --help');
|
||||
});
|
||||
|
||||
test('git status allowed', () => {
|
||||
assertAllowed('git status');
|
||||
});
|
||||
|
||||
test('git -C repo status allowed', () => {
|
||||
assertAllowed('git -C repo status');
|
||||
});
|
||||
|
||||
test('git status global option -C allowed', () => {
|
||||
assertAllowed('git -Crepo status');
|
||||
});
|
||||
|
||||
test('sudo env VAR=1 git status allowed', () => {
|
||||
assertAllowed('sudo env VAR=1 git status');
|
||||
});
|
||||
|
||||
test('git diff allowed', () => {
|
||||
assertAllowed('git diff');
|
||||
});
|
||||
|
||||
test('git log --oneline -10 allowed', () => {
|
||||
assertAllowed('git log --oneline -10');
|
||||
});
|
||||
|
||||
test('git add . allowed', () => {
|
||||
assertAllowed('git add .');
|
||||
});
|
||||
|
||||
test("git commit -m 'test' allowed", () => {
|
||||
assertAllowed("git commit -m 'test'");
|
||||
});
|
||||
|
||||
test('git pull allowed', () => {
|
||||
assertAllowed('git pull');
|
||||
});
|
||||
|
||||
test("bash -c 'echo ok' allowed", () => {
|
||||
assertAllowed("bash -c 'echo ok'");
|
||||
});
|
||||
|
||||
test('python -c "print(\'ok\')" allowed', () => {
|
||||
assertAllowed('python -c "print(\'ok\')"');
|
||||
});
|
||||
|
||||
test('ls -la allowed', () => {
|
||||
assertAllowed('ls -la');
|
||||
});
|
||||
|
||||
test('cat file.txt allowed', () => {
|
||||
assertAllowed('cat file.txt');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user