From de2b400f6d0d50d83e14efb6e95d7700a7cb1759 Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Thu, 19 Feb 2026 12:42:51 -0500 Subject: [PATCH] chore: align CI and tooling with global development standards - Remove 6 duplicate per-version pytest workflows (matrix build covers all) - Pin GitHub Actions to SHA hashes with version comments - Add persist-credentials: false to checkout steps - Replace mypy with ty for type checking (faster, stricter) - Pin dev deps to exact versions (ty==0.0.17, ruff==0.15.1, pytest==9.0.2, pytest-cov==7.0.0) - Remove types-* stub packages (ty doesn't need them) - Remove stale [dependency-groups] section from pyproject.toml - Update shell scripts to use set -euo pipefail - Add prek.toml for git hook management (pre-push, post-commit) - Add lint-infra.yml workflow (shellcheck + actionlint) - Fix actionlint warning: pass github.head_ref through env var - Track CLAUDE.md and .claude/ scripts in git - Update README.md and Makefile references from mypy to ty Co-Authored-By: Claude Opus 4.6 --- .claude/DOCUMENTATION-AUDIT.md | 144 +++++++++++++++++++++++ .claude/audit-docs.sh | 61 ++++++++++ .claude/check-docs.sh | 62 ++++++++++ .claude/install-hooks.sh | 45 ++++++++ .claude/verify-setup.sh | 88 ++++++++++++++ .github/workflows/lint-infra.yml | 40 +++++++ .github/workflows/pytest-py310.yml | 34 ------ .github/workflows/pytest-py311.yml | 34 ------ .github/workflows/pytest-py312.yml | 34 ------ .github/workflows/pytest-py313.yml | 34 ------ .github/workflows/pytest-py314.yml | 34 ------ .github/workflows/pytest-py39.yml | 34 ------ .github/workflows/pytest.yml | 6 +- .github/workflows/ruff.yml | 6 +- .github/workflows/{mypy.yml => ty.yml} | 14 ++- .github/workflows/version-bump.yml | 8 +- CLAUDE.md | 152 +++++++++++++++++++++++++ Makefile | 8 +- README.md | 46 +++----- prek.toml | 11 ++ pyproject.toml | 46 ++++---- wordlists/kill_transmission.sh | 27 +++++ 22 files changed, 693 insertions(+), 275 deletions(-) create mode 100644 .claude/DOCUMENTATION-AUDIT.md create mode 100644 .claude/audit-docs.sh create mode 100644 .claude/check-docs.sh create mode 100644 .claude/install-hooks.sh create mode 100644 .claude/verify-setup.sh create mode 100644 .github/workflows/lint-infra.yml delete mode 100644 .github/workflows/pytest-py310.yml delete mode 100644 .github/workflows/pytest-py311.yml delete mode 100644 .github/workflows/pytest-py312.yml delete mode 100644 .github/workflows/pytest-py313.yml delete mode 100644 .github/workflows/pytest-py314.yml delete mode 100644 .github/workflows/pytest-py39.yml rename .github/workflows/{mypy.yml => ty.yml} (54%) create mode 100644 CLAUDE.md create mode 100644 prek.toml create mode 100755 wordlists/kill_transmission.sh diff --git a/.claude/DOCUMENTATION-AUDIT.md b/.claude/DOCUMENTATION-AUDIT.md new file mode 100644 index 0000000..ca6055f --- /dev/null +++ b/.claude/DOCUMENTATION-AUDIT.md @@ -0,0 +1,144 @@ +# Documentation Audit System + +This project includes an automated documentation audit system that ensures README files and other documentation stay in sync with code changes. + +## Overview + +The system consists of: + +1. **Audit Script** (`.claude/audit-docs.sh`) - Analyzes git commits and flags when documentation may need updating +2. **Settings File** (`.claude/settings.json`) - Configuration for documentation audit behavior +3. **Hook Installer** (`.claude/install-hooks.sh`) - Sets up automatic audits on each commit +4. **This Guide** - Documentation for using the system + +## Quick Start + +Install automatic post-commit documentation audits: + +```bash +bash .claude/install-hooks.sh +``` + +This installs a git hook that will run the audit script after every commit. + +## Manual Audits + +Run a documentation audit for any commit: + +```bash +bash .claude/audit-docs.sh HEAD # Audit the last commit +bash .claude/audit-docs.sh # Audit a specific commit +``` + +## How It Works + +### Automatic Audits (with hook installed) + +When you run `git commit`: + +1. Git creates the commit +2. The `.git/hooks/post-commit` hook runs automatically +3. The audit script checks what files changed +4. If code changed but docs didn't, you get a warning message + +Example output: + +``` +[Documentation Audit] Analyzing changes in HEAD... +[Documentation Audit] Changed files: + hate_crack/attacks.py + hate_crack/main.py +[Documentation Audit] Code/config files detected +[Documentation Audit] WARNING: Code changed but documentation was not updated +[Documentation Audit] This is a reminder to review and update README.md if needed + +Changed code files: + hate_crack/attacks.py + hate_crack/main.py + +To audit documentation manually, run: + .claude/audit-docs.sh HEAD +``` + +### Manual Audits + +When using Claude Code's `Edit` or `Write` tools on documentation, you can manually trigger an audit: + +```bash +bash .claude/audit-docs.sh HEAD +``` + +This helps verify that your documentation updates match the code changes. + +## What Gets Audited + +The audit script checks for changes in: + +- **Code files**: `*.py`, `pyproject.toml`, `Makefile`, `config.json` +- **Documentation files**: `README.md`, `*.md` in any directory, `CLAUDE.md` + +The script will warn if code changes don't have corresponding documentation updates. + +## Removing the Hook + +If you want to stop automatic audits: + +```bash +rm .git/hooks/post-commit +``` + +Or just delete the hook file directly. The audit script will continue to work for manual runs. + +## Integration with Claude Code + +When you make code changes in Claude Code, this system works with the Documentation Auditor role: + +1. After committing code changes, the hook runs automatically +2. If docs need updating, it produces a clear warning +3. You can use Claude Code to read the changed files and update documentation as needed +4. Run the audit again to confirm documentation is in sync + +## Philosophy + +Documentation should: + +- Always reflect the current state of the code +- Be updated when features are added, removed, or changed +- Stay in sync with real implementation details +- Serve as the source of truth for users + +This audit system helps catch gaps where code changes aren't documented, ensuring users always have accurate guidance. + +## Troubleshooting + +### Hook doesn't seem to run + +Check that the hook file exists and is executable: + +```bash +ls -la .git/hooks/post-commit +# Should show: -rwxr-xr-x +``` + +If not executable, fix with: + +```bash +chmod +x .git/hooks/post-commit +``` + +### Want to see the hook in action + +Run a test commit with no actual changes: + +```bash +git commit --allow-empty -m "test: trigger audit" +``` + +The audit script will run and show output. + +## Files in This System + +- `.claude/settings.json` - Configuration and metadata +- `.claude/audit-docs.sh` - Main audit script +- `.claude/install-hooks.sh` - Hook installation script +- `.claude/DOCUMENTATION-AUDIT.md` - This guide diff --git a/.claude/audit-docs.sh b/.claude/audit-docs.sh new file mode 100644 index 0000000..bdcf1e3 --- /dev/null +++ b/.claude/audit-docs.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Documentation Audit Script +# This script is called by Claude Code after commits to ensure README files +# accurately reflect code changes. +# +# Usage: .claude/audit-docs.sh [last_commit_sha] +# If no SHA provided, audits the most recent commit. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +COMMIT_SHA="${1:-HEAD}" + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${YELLOW}[Documentation Audit]${NC} Analyzing changes in ${COMMIT_SHA}..." + +# Get list of changed files +CHANGED_FILES=$(git -C "$PROJECT_ROOT" diff-tree --no-commit-id --name-only -r "$COMMIT_SHA" || echo "") + +if [ -z "$CHANGED_FILES" ]; then + echo -e "${YELLOW}[Documentation Audit]${NC} No files changed in commit (possibly a merge)" + exit 0 +fi + +echo -e "${GREEN}[Documentation Audit]${NC} Changed files:" +# shellcheck disable=SC2001 +echo "$CHANGED_FILES" | sed 's/^/ /' + +# Check if any documentation or config files changed +HAS_DOC_CHANGES=false +if echo "$CHANGED_FILES" | grep -qE "README|CLAUDE|\.md$"; then + HAS_DOC_CHANGES=true + echo -e "${YELLOW}[Documentation Audit]${NC} Documentation files detected" +fi + +# Check if code files changed (Python, config, etc.) +HAS_CODE_CHANGES=false +if echo "$CHANGED_FILES" | grep -qE "\.py$|config\.json|pyproject\.toml|Makefile"; then + HAS_CODE_CHANGES=true + echo -e "${YELLOW}[Documentation Audit]${NC} Code/config files detected" +fi + +# If code changed but docs didn't, flag for manual review +if [ "$HAS_CODE_CHANGES" = true ] && [ "$HAS_DOC_CHANGES" = false ]; then + echo -e "${YELLOW}[Documentation Audit]${NC} WARNING: Code changed but documentation was not updated" + echo -e "${YELLOW}[Documentation Audit]${NC} This is a reminder to review and update README.md if needed" + echo "" + echo "Changed code files:" + # shellcheck disable=SC2001 + echo "$CHANGED_FILES" | grep -E "\.py$|config\.json|pyproject\.toml|Makefile" | sed 's/^/ /' + echo "" + echo "To audit documentation manually, run:" + echo " .claude/audit-docs.sh $COMMIT_SHA" +fi + +exit 0 diff --git a/.claude/check-docs.sh b/.claude/check-docs.sh new file mode 100644 index 0000000..2d057f4 --- /dev/null +++ b/.claude/check-docs.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Quick Documentation Check +# Use this script within Claude Code to verify that documentation is in sync +# with recent code changes. +# +# Usage: .claude/check-docs.sh [optional: number of commits to check] + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +NUM_COMMITS="${1:-3}" + +echo "Documentation Status Check" +echo "===========================" +echo "" +echo "Analyzing the last $NUM_COMMITS commits for code changes without documentation updates..." +echo "" + +cd "$PROJECT_ROOT" + +# Get the last N commits +COMMITS=$(git log -n "$NUM_COMMITS" --pretty=format:"%h") + +DOC_ISSUES=0 + +for commit in $COMMITS; do + # Get files changed in this commit + CHANGED=$(git diff-tree --no-commit-id --name-only -r "$commit" || echo "") + + if [ -z "$CHANGED" ]; then + continue + fi + + # Check for code changes + CODE_CHANGED=$(echo "$CHANGED" | grep -E "\.py$|config\.json|pyproject\.toml|Makefile" || true) + + # Check for doc changes + DOC_CHANGED=$(echo "$CHANGED" | grep -E "README|CLAUDE|\.md$" || true) + + if [ -n "$CODE_CHANGED" ] && [ -z "$DOC_CHANGED" ]; then + echo "❌ Commit $commit: Code changed but docs not updated" + echo " Files:" + # shellcheck disable=SC2001 + echo "$CODE_CHANGED" | sed 's/^/ /' + echo "" + DOC_ISSUES=$((DOC_ISSUES + 1)) + fi +done + +if [ $DOC_ISSUES -eq 0 ]; then + echo "✅ All recent commits have corresponding documentation updates!" +else + echo "⚠️ Found $DOC_ISSUES commit(s) with code changes but no documentation updates" + echo "" + echo "Next steps:" + echo "1. Review the code changes in the flagged commits" + echo "2. Update README.md or other documentation as needed" + echo "3. Re-run this check to verify" +fi + +echo "" diff --git a/.claude/install-hooks.sh b/.claude/install-hooks.sh new file mode 100644 index 0000000..f286ccd --- /dev/null +++ b/.claude/install-hooks.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Install Git Hooks for Documentation Auditing +# This script sets up a post-commit hook that automatically checks if +# documentation needs updating after each commit. +# +# Usage: .claude/install-hooks.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +GIT_HOOKS_DIR="$PROJECT_ROOT/.git/hooks" + +if [ ! -d "$GIT_HOOKS_DIR" ]; then + echo "Error: Not in a git repository" + exit 1 +fi + +# Create post-commit hook +POST_COMMIT_HOOK="$GIT_HOOKS_DIR/post-commit" + +cat > "$POST_COMMIT_HOOK" << 'EOF' +#!/bin/bash +set -euo pipefail +# Auto-generated: Post-commit hook for documentation auditing +# Remove this file to disable automatic documentation audits. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")" +AUDIT_SCRIPT="$PROJECT_ROOT/.claude/audit-docs.sh" + +if [ -f "$AUDIT_SCRIPT" ]; then + bash "$AUDIT_SCRIPT" HEAD +fi +EOF + +chmod +x "$POST_COMMIT_HOOK" + +echo "✓ Post-commit hook installed at $POST_COMMIT_HOOK" +echo "" +echo "Documentation audits will now run automatically after each commit." +echo "To remove this behavior, delete: $POST_COMMIT_HOOK" +echo "" +echo "Alternatively, you can run audits manually:" +echo " bash .claude/audit-docs.sh HEAD" diff --git a/.claude/verify-setup.sh b/.claude/verify-setup.sh new file mode 100644 index 0000000..ed4f681 --- /dev/null +++ b/.claude/verify-setup.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# Verify Documentation Audit System Setup +# Run this to confirm everything is installed and working correctly. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +echo "Documentation Audit System - Verification" +echo "==========================================" +echo "" + +# Check if in git repo +if [ ! -d "$PROJECT_ROOT/.git" ]; then + echo "❌ Not in a git repository" + exit 1 +fi + +echo "✓ Git repository found" + +# Check for required files +REQUIRED_FILES=( + "$SCRIPT_DIR/settings.json" + "$SCRIPT_DIR/audit-docs.sh" + "$SCRIPT_DIR/install-hooks.sh" + "$SCRIPT_DIR/check-docs.sh" + "$SCRIPT_DIR/DOCUMENTATION-AUDIT.md" + "$PROJECT_ROOT/.git/hooks/post-commit" +) + +MISSING=0 +for file in "${REQUIRED_FILES[@]}"; do + if [ -f "$file" ]; then + echo "✓ $(basename "$file")" + else + echo "❌ Missing: $file" + MISSING=$((MISSING + 1)) + fi +done + +echo "" + +# Check hook permissions +if [ -f "$PROJECT_ROOT/.git/hooks/post-commit" ]; then + if [ -x "$PROJECT_ROOT/.git/hooks/post-commit" ]; then + echo "✓ Post-commit hook is executable" + else + echo "⚠️ Post-commit hook exists but is not executable" + echo " Fixing permissions..." + chmod +x "$PROJECT_ROOT/.git/hooks/post-commit" + echo "✓ Fixed: hook is now executable" + fi +else + echo "❌ Post-commit hook not found" + MISSING=$((MISSING + 1)) +fi + +echo "" + +# Check CLAUDE.md for documentation section +if grep -q "Documentation Auditing" "$PROJECT_ROOT/CLAUDE.md"; then + echo "✓ CLAUDE.md updated with audit documentation" +else + echo "⚠️ CLAUDE.md does not have Documentation Auditing section" +fi + +echo "" + +if [ $MISSING -eq 0 ]; then + echo "✅ System is fully installed and ready!" + echo "" + echo "Quick test:" + echo " git commit --allow-empty -m 'test: trigger audit hook'" + echo "" + echo "Manual audit:" + echo " bash .claude/audit-docs.sh HEAD" + echo "" + echo "Status check:" + echo " bash .claude/check-docs.sh 3" +else + echo "❌ System has $MISSING missing file(s)" + echo "" + echo "To fix, run:" + echo " bash .claude/install-hooks.sh" +fi + +echo "" diff --git a/.github/workflows/lint-infra.yml b/.github/workflows/lint-infra.yml new file mode 100644 index 0000000..b2c8a19 --- /dev/null +++ b/.github/workflows/lint-infra.yml @@ -0,0 +1,40 @@ +name: lint-infra + +on: + push: + pull_request: + +jobs: + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Run shellcheck + run: | + find . -name "*.sh" \ + -not -path "./hashcat-utils/*" \ + -not -path "./princeprocessor/*" \ + -not -path "./omen/*" \ + -not -path "./PACK/*" \ + -not -path "./build/*" \ + -not -path "./dist/*" \ + -not -path "./.venv/*" \ + -print0 | xargs -0 shellcheck + + actionlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Install actionlint + run: | + curl -sL https://github.com/rhysd/actionlint/releases/latest/download/actionlint_1.7.7_linux_amd64.tar.gz | tar xz + sudo mv actionlint /usr/local/bin/ + + - name: Run actionlint + run: actionlint diff --git a/.github/workflows/pytest-py310.yml b/.github/workflows/pytest-py310.yml deleted file mode 100644 index 7e9815d..0000000 --- a/.github/workflows/pytest-py310.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: pytest-py310 - -on: - push: - pull_request: - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y p7zip-full transmission-cli - - - name: Install uv - run: python -m pip install --upgrade pip uv==0.9.28 - - - name: Install project dependencies - run: | - uv venv .venv - uv pip install --python .venv/bin/python ".[dev]" - - - name: Run tests - env: - HATE_CRACK_RUN_E2E: "0" - HATE_CRACK_RUN_DOCKER_TESTS: "0" - HATE_CRACK_RUN_LIVE_TESTS: "0" - HATE_CRACK_SKIP_INIT: "1" - run: .venv/bin/python -m pytest diff --git a/.github/workflows/pytest-py311.yml b/.github/workflows/pytest-py311.yml deleted file mode 100644 index ecd11e5..0000000 --- a/.github/workflows/pytest-py311.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: pytest-py311 - -on: - push: - pull_request: - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y p7zip-full transmission-cli - - - name: Install uv - run: python -m pip install --upgrade pip uv==0.9.28 - - - name: Install project dependencies - run: | - uv venv .venv - uv pip install --python .venv/bin/python ".[dev]" - - - name: Run tests - env: - HATE_CRACK_RUN_E2E: "0" - HATE_CRACK_RUN_DOCKER_TESTS: "0" - HATE_CRACK_RUN_LIVE_TESTS: "0" - HATE_CRACK_SKIP_INIT: "1" - run: .venv/bin/python -m pytest diff --git a/.github/workflows/pytest-py312.yml b/.github/workflows/pytest-py312.yml deleted file mode 100644 index 7a7a530..0000000 --- a/.github/workflows/pytest-py312.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: pytest-py312 - -on: - push: - pull_request: - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y p7zip-full transmission-cli - - - name: Install uv - run: python -m pip install --upgrade pip uv==0.9.28 - - - name: Install project dependencies - run: | - uv venv .venv - uv pip install --python .venv/bin/python ".[dev]" - - - name: Run tests - env: - HATE_CRACK_RUN_E2E: "0" - HATE_CRACK_RUN_DOCKER_TESTS: "0" - HATE_CRACK_RUN_LIVE_TESTS: "0" - HATE_CRACK_SKIP_INIT: "1" - run: .venv/bin/python -m pytest diff --git a/.github/workflows/pytest-py313.yml b/.github/workflows/pytest-py313.yml deleted file mode 100644 index 9ebf712..0000000 --- a/.github/workflows/pytest-py313.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: pytest-py313 - -on: - push: - pull_request: - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y p7zip-full transmission-cli - - - name: Install uv - run: python -m pip install --upgrade pip uv==0.9.28 - - - name: Install project dependencies - run: | - uv venv .venv - uv pip install --python .venv/bin/python ".[dev]" - - - name: Run tests - env: - HATE_CRACK_RUN_E2E: "0" - HATE_CRACK_RUN_DOCKER_TESTS: "0" - HATE_CRACK_RUN_LIVE_TESTS: "0" - HATE_CRACK_SKIP_INIT: "1" - run: .venv/bin/python -m pytest diff --git a/.github/workflows/pytest-py314.yml b/.github/workflows/pytest-py314.yml deleted file mode 100644 index e75d1b8..0000000 --- a/.github/workflows/pytest-py314.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: pytest-py314 - -on: - push: - pull_request: - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.14" - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y p7zip-full transmission-cli - - - name: Install uv - run: python -m pip install --upgrade pip uv==0.9.28 - - - name: Install project dependencies - run: | - uv venv .venv - uv pip install --python .venv/bin/python ".[dev]" - - - name: Run tests - env: - HATE_CRACK_RUN_E2E: "0" - HATE_CRACK_RUN_DOCKER_TESTS: "0" - HATE_CRACK_RUN_LIVE_TESTS: "0" - HATE_CRACK_SKIP_INIT: "1" - run: .venv/bin/python -m pytest diff --git a/.github/workflows/pytest-py39.yml b/.github/workflows/pytest-py39.yml deleted file mode 100644 index bae0100..0000000 --- a/.github/workflows/pytest-py39.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: pytest-py39 - -on: - push: - pull_request: - -jobs: - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.9" - - - name: Install system dependencies - run: sudo apt-get update && sudo apt-get install -y p7zip-full transmission-cli - - - name: Install uv - run: python -m pip install --upgrade pip uv==0.9.28 - - - name: Install project dependencies - run: | - uv venv .venv - uv pip install --python .venv/bin/python ".[dev]" - - - name: Run tests - env: - HATE_CRACK_RUN_E2E: "0" - HATE_CRACK_RUN_DOCKER_TESTS: "0" - HATE_CRACK_RUN_LIVE_TESTS: "0" - HATE_CRACK_SKIP_INIT: "1" - run: .venv/bin/python -m pytest diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 4abfa2f..6a1fd23 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -11,9 +11,11 @@ jobs: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 6230128..4720358 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -8,9 +8,11 @@ jobs: ruff: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.13" diff --git a/.github/workflows/mypy.yml b/.github/workflows/ty.yml similarity index 54% rename from .github/workflows/mypy.yml rename to .github/workflows/ty.yml index d8a5739..7322bc6 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/ty.yml @@ -1,16 +1,18 @@ -name: mypy +name: ty on: push: pull_request: jobs: - mypy: + ty: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.13" @@ -22,5 +24,5 @@ jobs: uv venv .venv uv pip install --python .venv/bin/python ".[dev]" - - name: Run mypy - run: .venv/bin/mypy --exclude HashcatRosetta --exclude hashcat-utils --ignore-missing-imports hate_crack + - name: Run ty + run: .venv/bin/ty check hate_crack diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 9877853..d26fbc2 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -13,15 +13,17 @@ jobs: if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 + persist-credentials: false - name: Determine version bump type id: bump-type + env: + BRANCH: ${{ github.head_ref }} + TITLE: ${{ github.event.pull_request.title }} run: | - BRANCH="${{ github.head_ref }}" - TITLE="${{ github.event.pull_request.title }}" # Feature branches (feat/) bump minor, everything else bumps patch if echo "$BRANCH" | grep -qiE '^feat/'; then echo "type=minor" >> "$GITHUB_OUTPUT" diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..74a28c0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,152 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What This Is + +hate_crack is a menu-driven Python wrapper for hashcat that automates password cracking methodologies. It provides 16 attack modes, API integrations (Hashview, Weakpass, Hashmob), and utilities for wordlist/rule management. + +## Commands + +```bash +# Install (builds submodules, vendors assets, installs via uv) +make install + +# Dev install (editable, with dev deps) +make dev-install + +# Run tests (requires HATE_CRACK_SKIP_INIT=1 in worktrees without hashcat-utils) +HATE_CRACK_SKIP_INIT=1 uv run pytest -v + +# Run a single test file +HATE_CRACK_SKIP_INIT=1 uv run pytest tests/test_ui_menu_options.py -v + +# Lint +uv run ruff check hate_crack +uv run ty check hate_crack + +# Both lint checks +make lint + +# Format +uv run ruff format hate_crack + +# Coverage +make coverage +``` + +**Test environment variables**: `HATE_CRACK_SKIP_INIT=1` skips binary/config validation (essential for CI and worktrees). `HASHMOB_TEST_REAL=1`, `HASHVIEW_TEST_REAL=1`, `WEAKPASS_TEST_REAL=1` enable live API tests. + +## Git Hooks + +Git hooks are managed by [prek](https://github.com/nicholasgasior/prek). Install with: + +```bash +prek install +``` + +Hooks defined in `prek.toml`: +- **pre-push**: ruff check, ty check, pytest +- **post-commit**: documentation audit + +## Documentation Auditing + +Automatic documentation audits run after each commit via prek hooks. + +```bash +# Manually audit a commit +bash .claude/audit-docs.sh HEAD +bash .claude/audit-docs.sh + +# Check the last N commits for documentation gaps +bash .claude/check-docs.sh 5 +``` + +See `.claude/DOCUMENTATION-AUDIT.md` for details on the audit system. + +## Worktree Policy + +**Every agent MUST work in a dedicated git worktree** - never edit files directly in the main repo checkout. This prevents conflicts when multiple agents run in parallel. + +### Setup + +```bash +# Create a worktree under /tmp (keeps the parent directory clean) +git worktree add /tmp/hate_crack- -b +cd /tmp/hate_crack- + +# Install dev dependencies in the new worktree +uv sync --dev + +# Run tests in the worktree +HATE_CRACK_SKIP_INIT=1 uv run pytest -v +``` + +### Rules + +1. **Always create a worktree** before making any file changes: `git worktree add /tmp/hate_crack- -b ` +2. **All file edits** happen inside the worktree directory, not the main repo +3. **Run tests and lint** inside the worktree before merging +4. **Merge back** via PR or `git merge` from the main worktree +5. **Clean up** when done: `git worktree remove /tmp/hate_crack-` + +## Architecture + +### Module Map + +- **`hate_crack.py`** (root) - Entry point, menu registration, proxy to `hate_crack.main` +- **`hate_crack/main.py`** - Core logic (~3700 lines): hashcat subprocess wrappers, config loading, menu display, global state +- **`hate_crack/attacks.py`** - 16 attack handler functions that receive `ctx` (main module ref) +- **`hate_crack/api.py`** - Hashmob, Hashview, Weakpass API integrations +- **`hate_crack/cli.py`** - Argparse helpers, path resolution, logging setup +- **`hate_crack/formatting.py`** - Terminal UI helpers (multi-column list printing) + +### Three-Layer Attack Pattern + +Every attack spans three files with a specific wiring pattern: + +1. **`hate_crack/main.py`** - Low-level hashcat function (e.g., `hcatBruteForce(hcatHashType, hcatHashFile)`) + - Builds subprocess commands, manages `hcatProcess` global, handles KeyboardInterrupt + - All hashcat invocations follow: build cmd list -> `cmd.extend(shlex.split(hcatTuning))` -> `_append_potfile_arg(cmd)` -> `subprocess.Popen(cmd)` + +2. **`hate_crack/attacks.py`** - Menu handler wrapper (e.g., `def brute_force_crack(ctx: Any)`) + - Receives `ctx` (the main module itself) via `_attack_ctx()`, which returns `sys.modules['hate_crack.main']` + - Handles user prompts, then calls `ctx.hcatBruteForce(ctx.hcatHashType, ctx.hcatHashFile)` + +3. **`hate_crack.py`** (root) - Menu registration + dispatcher + - Has its own `get_main_menu_options()` that maps keys to `_attacks.` + - **Important**: `hate_crack.py` has a DUPLICATE menu mapping separate from `main.py`'s `get_main_menu_options()`. Both must be updated when adding attacks. + +### Adding a New Attack - Checklist + +1. Add the hashcat wrapper function in `main.py` (e.g., `hcatMyAttack(...)`) +2. Add the handler in `attacks.py` (e.g., `def my_attack(ctx: Any)`) +3. Add a dispatcher in `main.py`: `def my_attack(): return _attacks.my_attack(_attack_ctx())` +4. Add the print line in `main.py`'s menu display loop (~line 3807+) +5. Add the menu entry in `main.py`'s `get_main_menu_options()` +6. Add the menu entry in `hate_crack.py`'s `get_main_menu_options()` (the duplicate) + +### hate_crack.py <-> main.py Proxy + +`hate_crack.py` uses `__getattr__` to proxy attribute access to `hate_crack.main`. It syncs mutable globals via `_sync_globals_to_main()` and `_sync_callables_to_main()`. Tests load `hate_crack.py` as `CLI_MODULE` and exercise both the proxy and direct module paths. + +### Config System + +`config.json` is resolved from multiple candidate directories (repo root, cwd, `~/.hate_crack`, `/opt/hate_crack`, etc.) and auto-created from `config.json.example` if missing. Each config key is loaded via try/except with `default_config` fallback. New config vars need: entry in `config.json.example`, try/except loading block in `main.py` (~line 454 area), and path normalization via `_normalize_wordlist_setting()` if it's a wordlist path. + +### Path Distinction + +- **`hate_path`** - hate_crack assets directory (hashcat-utils, princeprocessor, masks, PACK). All bundled binaries use this. +- **`hcatPath`** - hashcat installation directory. Only used for the hashcat binary itself. + +### External Binary Pattern + +Binaries are verified at startup via `ensure_binary(path, build_dir, name)`. Non-critical binaries (princeprocessor, hcstat2gen) use try/except around `ensure_binary` with a warning message. The `SKIP_INIT` flag bypasses all binary checks. + +## Testing Patterns + +- Menu option tests in `test_ui_menu_options.py` use monkeypatching against `CLI_MODULE` (loaded from `hate_crack.py`) +- API tests mock `requests` responses; most are offline-first +- conftest.py provides `hc_module` fixture via `load_hate_crack_module()` which dynamically imports root `hate_crack.py` with SKIP_INIT enabled +- Python 3.9-3.14 supported in CI (requires-python >=3.13 in pyproject.toml but CI tests older versions) +- E2E tests (`test_e2e_local_install.py`, `test_docker_script_install.py`) are opt-in via `HATE_CRACK_RUN_E2E=1` and `HATE_CRACK_RUN_DOCKER_TESTS=1` diff --git a/Makefile b/Makefile index d848ced..b44fff8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .DEFAULT_GOAL := submodules -.PHONY: install reinstall update dev-install dev-reinstall clean hashcat-utils submodules submodules-pre vendor-assets clean-vendor test coverage lint check ruff mypy +.PHONY: install reinstall update dev-install dev-reinstall clean hashcat-utils submodules submodules-pre vendor-assets clean-vendor test coverage lint check ruff ty hashcat-utils: submodules $(MAKE) -C hashcat-utils @@ -92,10 +92,10 @@ coverage: ruff: uv run ruff check hate_crack -mypy: - uv run mypy hate_crack +ty: + uv run ty check hate_crack -lint: ruff mypy +lint: ruff ty @echo "✓ All linting checks passed" check: lint diff --git a/README.md b/README.md index a939beb..f410697 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,10 @@ **Code Quality & Testing:** [![ruff](https://github.com/trustedsec/hate_crack/actions/workflows/ruff.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/ruff.yml) -[![mypy](https://github.com/trustedsec/hate_crack/actions/workflows/mypy.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/mypy.yml) +[![ty](https://github.com/trustedsec/hate_crack/actions/workflows/ty.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/ty.yml) [![pytest](https://github.com/trustedsec/hate_crack/actions/workflows/pytest.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/pytest.yml) -**Python Version Testing:** - -[![py39](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py39.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py39.yml) -[![py310](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py310.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py310.yml) -[![py311](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py311.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py311.yml) -[![py312](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py312.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py312.yml) -[![py313](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py313.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py313.yml) -[![py314](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py314.yml/badge.svg)](https://github.com/trustedsec/hate_crack/actions/workflows/pytest-py314.yml) +The pytest workflow tests across Python 3.9-3.14 via a matrix build. ## Installation @@ -237,7 +230,7 @@ make test ### Setting Up the Development Environment -Install the project with optional dev dependencies (includes type stubs, linters, and testing tools): +Install the project with optional dev dependencies (includes linters and testing tools): ```bash make dev-install @@ -254,10 +247,10 @@ The project uses GitHub Actions to automatically run quality checks on every pus - ❌ **FAIL**: Code has style violations or quality issues - Run locally: `make ruff` -2. **Type Checking (Mypy)** - Static type analysis +2. **Type Checking (ty)** - Static type analysis - ✅ **PASS**: No type errors detected - ❌ **FAIL**: Type mismatches or missing annotations found - - Run locally: `make mypy` + - Run locally: `make ty` 3. **Testing (Multi-Version)** - Tests across Python 3.9 through 3.14 - ✅ **PASS**: All tests pass on all supported Python versions @@ -284,14 +277,14 @@ Auto-fix issues: .venv/bin/ruff check --fix hate_crack ``` -**Mypy (type checking):** +**ty (type checking):** ```bash -.venv/bin/mypy hate_crack +.venv/bin/ty check hate_crack ``` **Run all checks together:** ```bash -.venv/bin/ruff check hate_crack && .venv/bin/mypy hate_crack && echo "✓ All checks passed" +.venv/bin/ruff check hate_crack && .venv/bin/ty check hate_crack && echo "✓ All checks passed" ``` ### Running Tests @@ -305,23 +298,17 @@ With coverage: .venv/bin/pytest --cov=hate_crack ``` -### Pre-commit Hook (Optional) +### Git Hooks (prek) -Create `.git/hooks/pre-push` to automatically run checks before pushing: +Git hooks are managed by [prek](https://github.com/nicholasgasior/prek). Install hooks with: ```bash -#!/bin/bash -set -e -.venv/bin/ruff check hate_crack -.venv/bin/mypy --exclude HashcatRosetta --exclude hashcat-utils --ignore-missing-imports hate_crack -HATE_CRACK_SKIP_INIT=1 HATE_CRACK_RUN_E2E=0 HATE_CRACK_RUN_DOCKER_TESTS=0 HATE_CRACK_RUN_LIVE_TESTS=0 .venv/bin/python -m pytest -echo "✓ Local checks passed!" +prek install ``` -Make it executable: -```bash -chmod +x .git/hooks/pre-push -``` +This installs hooks defined in `prek.toml`: +- **pre-push**: ruff check, ty check, pytest +- **post-commit**: documentation audit ### Optional Dependencies @@ -341,13 +328,10 @@ PassGPT (option 17) will be hidden from the menu if ML dependencies are not inst ### Dev Dependencies The optional `[dev]` group includes: -- **mypy** - Static type checker +- **ty** - Static type checker - **ruff** - Fast Python linter and formatter - **pytest** - Testing framework - **pytest-cov** - Coverage reporting -- **types-requests** - Type stubs for requests library -- **types-beautifulsoup4** - Type stubs for BeautifulSoup -- **types-openpyxl** - Type stubs for openpyxl library ------------------------------------------------------------------- Common options: diff --git a/prek.toml b/prek.toml new file mode 100644 index 0000000..ef73ef5 --- /dev/null +++ b/prek.toml @@ -0,0 +1,11 @@ +[hooks.pre-push] +commands = [ + "uv run ruff check hate_crack", + "uv run ty check hate_crack", + "HATE_CRACK_SKIP_INIT=1 uv run pytest -q", +] + +[hooks.post-commit] +commands = [ + "bash .claude/audit-docs.sh HEAD", +] diff --git a/pyproject.toml b/pyproject.toml index c0f0fa0..1766ef6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,13 +26,10 @@ ml = [ "accelerate>=1.1.0", ] dev = [ - "mypy>=1.8.0", - "ruff>=0.3.0", - "pytest>=7.0.0", - "pytest-cov>=4.0.0", - "types-requests>=2.31.0", - "types-beautifulsoup4>=4.12.0", - "types-openpyxl>=3.0.0", + "ty==0.0.17", + "ruff==0.15.1", + "pytest==9.0.2", + "pytest-cov==7.0.0", ] [tool.setuptools.packages.find] @@ -62,26 +59,29 @@ exclude = [ "rules", ] -[tool.mypy] +[tool.ty.src] exclude = [ - "^build/", - "^dist/", - "^PACK/", - "^wordlists/", - "^HashcatRosetta/", - "^hashcat-utils/", - "^hate_crack/hashcat-utils/", - "^hate_crack/omen/", - "^hate_crack/princeprocessor/", + "build/", + "dist/", + "PACK/", + "wordlists/", + "HashcatRosetta/", + "hashcat-utils/", + "hate_crack/hashcat-utils/", + "hate_crack/omen/", + "hate_crack/princeprocessor/", ] -ignore_missing_imports = true + +[tool.ty.rules] +# Module-level globals in main.py are assigned at runtime +unresolved-reference = "warn" +# Optional deps (torch, transformers, hashcat_rosetta) not always installed +unresolved-import = "warn" +# BeautifulSoup union types and module-level globals +unresolved-attribute = "warn" +invalid-argument-type = "warn" [tool.pytest.ini_options] testpaths = [ "tests", ] - -[dependency-groups] -dev = [ - "types-requests>=2.32.4.20260107", -] diff --git a/wordlists/kill_transmission.sh b/wordlists/kill_transmission.sh new file mode 100755 index 0000000..0128efc --- /dev/null +++ b/wordlists/kill_transmission.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# +set -euo pipefail + +TORRENT_DIR="${TR_TORRENT_DIR:-}" +TORRENT_NAME="${TR_TORRENT_NAME:-}" + +if [ -z "$TORRENT_DIR" ] || [ -z "$TORRENT_NAME" ]; then + exit 0 +fi + +TORRENT_PATH="${TORRENT_DIR}/${TORRENT_NAME}" + +SEVENZ_BIN=$(command -v 7z || command -v 7za || true) +if [ -n "$SEVENZ_BIN" ]; then + if [ -f "$TORRENT_PATH" ] && [[ "$TORRENT_PATH" == *.7z ]]; then + "$SEVENZ_BIN" x -sdel "$TORRENT_PATH" -o"$TORRENT_DIR" + elif [ -d "$TORRENT_PATH" ]; then + find "$TORRENT_PATH" -maxdepth 2 -type f -name "*.7z" -print0 | while IFS= read -r -d '' zfile; do + "$SEVENZ_BIN" e -sdel "$zfile" + done + fi +fi + +if [ -n "$PPID" ]; then + kill "$PPID" +fi