shellfirm 0.3.9

`shellfirm` will intercept any risky patterns (default or defined by you) and prompt you a small challenge for double verification, kinda like a captcha for your terminal.
Documentation
# Decision Matrix: (command, context, policy) -> expected outcome
# This is the single source of truth for shellfirm's behavior.

# --- Basic pattern matching (no context, no policy) ---
# Note: severity escalation is ON by default, so:
#   High severity → Enter, Critical severity → Yes
- name: "force push detected with default settings"
  command: "git push -f origin main"
  context: {}
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: Enter   # High severity → Enter
    risk_level: Normal

- name: "safe command passes through"
  command: "git status"
  context: {}
  expected:
    matched_ids: []

- name: "kubectl delete namespace detected"
  command: "kubectl delete namespace payments"
  context: {}
  expected:
    matched_ids: ["kubernetes:delete_namespace"]
    effective_challenge: "Yes"   # Critical severity → Yes
    risk_level: Normal

- name: "docker system prune detected"
  command: "docker system prune -a"
  context: {}
  expected:
    matched_ids: ["docker:system_prune_all"]
    effective_challenge: Enter   # High severity → Enter
    risk_level: Normal

- name: "terraform auto-approve detected"
  command: "terraform apply -auto-approve"
  context: {}
  expected:
    matched_ids: ["terraform:apply_with_auto_approve"]
    effective_challenge: "Yes"   # Critical severity → Yes
    risk_level: Normal

- name: "AWS S3 recursive delete detected"
  command: "aws s3 rm s3://bucket/path --recursive"
  context: {}
  expected:
    matched_ids: ["aws:s3_recursive_delete"]
    effective_challenge: Enter   # High severity → Enter
    risk_level: Normal

- name: "database drop detected"
  command: "DROP DATABASE production"
  context: {}
  expected:
    matched_ids: ["database:drop_database"]
    effective_challenge: "Yes"   # Critical severity → Yes
    risk_level: Normal

# --- Context escalation ---
- name: "force push on protected branch escalates to Yes"
  command: "git push -f origin main"
  context:
    git_branch: main
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: "Yes"
    risk_level: Critical

- name: "kubectl delete in prod context escalates"
  command: "kubectl delete namespace payments"
  context:
    k8s_context: prod-us-east-1
  expected:
    matched_ids: ["kubernetes:delete_namespace"]
    effective_challenge: "Yes"
    risk_level: Critical

- name: "same kubectl delete in minikube is normal"
  command: "kubectl delete namespace payments"
  context:
    k8s_context: minikube
  expected:
    matched_ids: ["kubernetes:delete_namespace"]
    effective_challenge: "Yes"   # Critical severity → Yes (even in normal context)
    risk_level: Normal

- name: "SSH session elevates to Enter"
  command: "git push -f origin main"
  context:
    ssh: true
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: Enter
    risk_level: Elevated

- name: "root user escalates to Critical"
  command: "git push -f origin main"
  context:
    root: true
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: "Yes"
    risk_level: Critical

- name: "production env var escalates to Critical"
  command: "git push -f origin main"
  context:
    env:
      NODE_ENV: production
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: "Yes"
    risk_level: Critical

- name: "feature branch + minikube stays Normal"
  command: "git push -f origin feature/foo"
  context:
    git_branch: "feature/foo"
    k8s_context: minikube
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: Enter   # High severity → Enter (no context escalation)
    risk_level: Normal

- name: "release wildcard branch is Critical"
  command: "git push -f origin release/v2.0"
  context:
    git_branch: "release/v2.0"
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: "Yes"
    risk_level: Critical

# --- Project policies ---
- name: "project policy denies force push"
  command: "git push -f origin feature/foo"
  context:
    git_branch: "feature/foo"
  policy:
    deny: ["git:force_push"]
  expected:
    matched_ids: ["git:force_push"]
    is_denied: true

- name: "project policy CANNOT weaken context escalation"
  command: "git push -f origin main"
  context:
    git_branch: main
  policy:
    overrides:
      - id: "git:force_push"
        challenge: Enter
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: "Yes"
    risk_level: Critical

# --- Alternatives ---
- name: "force push shows alternative"
  command: "git push -f"
  context: {}
  expected:
    matched_ids: ["git:force_push"]
    alternative_shown: "--force-with-lease"

# --- Compound commands ---
- name: "risky command after &&"
  command: "cd /tmp && git push -f origin main"
  context: {}
  expected:
    matched_ids: ["git:force_push"]

- name: "risky command after pipe"
  command: "echo test | git push -f"
  context: {}
  expected:
    matched_ids: ["git:force_push"]

# --- Dry-run / NotContains filters suppress false positives ---
- name: "kubectl delete namespace with --dry-run is suppressed"
  command: "kubectl delete namespace payments --dry-run=client"
  context: {}
  expected:
    matched_ids: []

- name: "kubectl delete namespace without --dry-run fires"
  command: "kubectl delete namespace payments"
  context: {}
  expected:
    matched_ids: ["kubernetes:delete_namespace"]

- name: "git reset --soft is suppressed"
  command: "git reset --soft HEAD~1"
  context: {}
  expected:
    matched_ids: []

- name: "git reset --hard still fires"
  command: "git reset --hard HEAD~1"
  context: {}
  expected:
    matched_ids: ["git:reset"]

- name: "git clean -fd --dry-run is suppressed"
  command: "git clean -fd --dry-run"
  context: {}
  expected:
    matched_ids: []

- name: "git clean -fdn is suppressed"
  command: "git clean -fdn"
  context: {}
  expected:
    matched_ids: []

- name: "git clean -fd without dry-run fires"
  command: "git clean -fd"
  context: {}
  expected:
    matched_ids: ["git:clean_force"]

- name: "aws s3 rm --recursive --dryrun is suppressed"
  command: "aws s3 rm s3://bucket/path --recursive --dryrun"
  context: {}
  expected:
    matched_ids: []

- name: "terraform state mv -dry-run is suppressed"
  command: "terraform state mv -dry-run aws_s3.old aws_s3.new"
  context: {}
  expected:
    matched_ids: []

- name: "terraform state mv without -dry-run fires"
  command: "terraform state mv aws_s3.old aws_s3.new"
  context: {}
  expected:
    matched_ids: ["terraform:state"]

- name: "UPDATE without WHERE fires"
  command: "UPDATE users SET active=false;"
  context: {}
  expected:
    matched_ids: ["database:update_all_rows"]

- name: "UPDATE with WHERE is suppressed"
  command: "UPDATE users SET active=false WHERE id=1;"
  context: {}
  expected:
    matched_ids: []

# --- Severity escalation ---
- name: "Critical severity gets Yes challenge on normal context"
  command: "DROP DATABASE production"
  context: {}
  expected:
    matched_ids: ["database:drop_database"]
    effective_challenge: "Yes"
    risk_level: Normal

- name: "High severity gets Enter challenge on normal context"
  command: "git push -f origin feature/foo"
  context:
    git_branch: "feature/foo"
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: Enter
    risk_level: Normal

- name: "High severity + Critical context stacks correctly"
  command: "git push -f origin main"
  context:
    git_branch: main
  expected:
    matched_ids: ["git:force_push"]
    effective_challenge: "Yes"  # max(Enter from severity, Yes from context)
    risk_level: Critical

- name: "Critical severity + Elevated context takes max"
  command: "kubectl delete namespace payments"
  context:
    ssh: true
  expected:
    matched_ids: ["kubernetes:delete_namespace"]
    effective_challenge: "Yes"  # max(Yes from severity, Enter from SSH)
    risk_level: Elevated