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 <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-02-19 12:42:51 -05:00
parent e7f4ed815b
commit de2b400f6d
22 changed files with 693 additions and 275 deletions

View File

@@ -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 <commit_sha> # 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

61
.claude/audit-docs.sh Normal file
View File

@@ -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

62
.claude/check-docs.sh Normal file
View File

@@ -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 ""

45
.claude/install-hooks.sh Normal file
View File

@@ -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"

88
.claude/verify-setup.sh Normal file
View File

@@ -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 ""

40
.github/workflows/lint-infra.yml vendored Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

152
CLAUDE.md Normal file
View File

@@ -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 <commit_sha>
# 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-<task-name> -b <branch-name>
cd /tmp/hate_crack-<task-name>
# 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-<task> -b <branch>`
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-<task>`
## 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.<handler>`
- **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`

View File

@@ -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

View File

@@ -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:

11
prek.toml Normal file
View File

@@ -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",
]

View File

@@ -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",
]

27
wordlists/kill_transmission.sh Executable file
View File

@@ -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