Skip to main content

ShockHarness

ShockHarness: a robot agent jolted an instant before pressing Enter on rm -rf /. Constrained agents. Safer code.

A strict, deterministic static-analysis quality gate for Python and JavaScript. Its one idea: unexpected failures, missing information, and unexplained decisions must not silently become apparently valid program behavior.

Three default-on hard policies:

  1. Exception transparency. No catch except at narrow, explicitly approved external boundaries. No catch-and-continue, no success-shaped fallbacks.
  2. No implicit defaults. No fallback values for missing required data, no function-parameter defaults in strict mode.
  3. No magic numbers. Every numeric literal in executable code must be exactly 0 or 1. range(2) is a violation. The tool invents no exemptions.

Plus a separate, advisory complexity family (CPLX001-CPLX005: cyclomatic, cognitive, nesting, NPath, a confidence-classified call graph with cycles and fan-out, git churn and hotspots). Hard violations are never averaged into a score; complexity never fails a plain check.

ShockHarness complements Ruff and ESLint; it is not a formatter or a general linter. It runs offline, needs no LLM, and never uploads source.

Status

Pre-release. Milestones A (skeleton and contracts), B (parsers and MAGIC001), C (exception rules EXC001-EXC003), D (default and fallback rules DEF001-DEF006), F (baselines, revision diff and gate, SARIF output, git churn and co-change metrics, built-in profiles, the check command, tag-triggered PyPI publishing, a reusable GitHub Actions workflow and two consumer examples) and G (four bundled agent skills, skills install / skills update with a manifest, and a skill evaluation corpus) and E (per-unit complexity metrics with versioned conventions, a call graph whose edges are resolved, probable or unresolved, module cycles, fan-in/out, shared-mutable-state notes, hotspots from churn and cognitive complexity, and per-unit regressions in revision diffs) are implemented. MAGIC002 is not yet implemented; every report lists unimplemented rules under rules_not_implemented so a clean run never implies coverage the tool does not have. See STATUS.md for exact evidence.

Implemented rules:

ID What it reports Default
MAGIC001 numeric literal other than 0/1 in executable code error
EXC001 catch outside an approved boundary (bare, broad, unresolved, untyped) error
EXC002 handler that swallows, only logs, or replaces the exception without its cause error
EXC003 approved try wrapping more than the one sanctioned operation error
DEF001 parameter default, dataclass field default / default_factory error
DEF002 m.get(k, v), getattr(o, a, v), o?.x ?? v, const {x = v} = o; .get(k) implicit None as review error
DEF003 x or v, `x
DEF004 x ?? v, x if x is not None else v error
DEF005 handler that returns a default (literal, module constant, one-return helper) error
DEF006 os.getenv("X", v), `process.env.X
SUPP001 / SUPP002 malformed / unused suppression comment error / warning
CPLX001-CPLX004 a unit's cyclomatic, cognitive, nesting or NPath number above its threshold (docs/complexity.md) warning
CPLX005 module import cycle, or fan-out above fan_out_warn warning

Install

Requires Python 3.11+. Runtime dependencies are tree-sitter and tree-sitter-javascript only. Pin an exact version; releases are tagged and published to PyPI from v* tags (see docs/releasing.md).

pip install "shockharness==0.1.0"          # Python project: put the pin in pyproject.toml
uv tool install "shockharness==0.1.0"      # any project (JavaScript included): isolated tool env
pipx install "shockharness==0.1.0"

From a checkout:

uv pip install -e ".[dev]"

Use

shockharness version                    # same as --version
shockharness init                       # writes .shockharness.toml (strict profile)
shockharness init --profile recommended # or minimal: a short file selecting the profile
shockharness config validate            # exit 0 if valid; prints the profile and every deviation
shockharness config effective           # merged config with per-key origins
shockharness check src/                 # human-readable report, exit = gate status
shockharness check src/ --format json --output report.json
shockharness check src/ --format sarif --output report.sarif  # GitHub code scanning
shockharness check src/ --profile minimal                      # try a profile without editing config
shockharness check src/ --parse-errors warn                    # the only other CLI override
shockharness check src/ --exit-zero                            # advisory: full report, exit 0 on violations
shockharness baseline create src/ --output .shockharness-baseline.json
shockharness check src/ --baseline .shockharness-baseline.json # legacy findings do not gate
shockharness diff --base main --head HEAD                      # new / fixed / unchanged, exit 0
shockharness gate --base main --head HEAD                      # exit 1 only on new hard violations
shockharness rules list
shockharness rules explain MAGIC001
shockharness skills list                                       # the four bundled agent skills
shockharness skills install --agent claude                     # .claude/skills/<name>/SKILL.md + manifest
shockharness skills update --agent claude --check              # exit 1 when a skill is stale or edited

check is the canonical analysis command; scan, the name used before Milestone F, still works as a hidden alias with identical arguments.

Baselines are reviewed files of stable finding fingerprints (rule, path, scope, whitespace-free evidence, occurrence index) that survive line moves; nothing ever updates one automatically. diff and gate compare the full trees of two committed revisions through local git only (uncommitted changes are not part of HEAD). Every CLI report also carries metrics.history: per-file churn and co-change pairs from local git log, at file granularity, never gating, and explicitly unavailable outside a repository.

Exit codes: 0 gates pass, 1 policy violations, 2 invalid configuration or invocation, 3 internal error or (with parse_errors = "fail") a file that could not be parsed. A report is still produced when the gate fails.

Example:

src/client.py:12:11: MAGIC001 error: Numeric literal 30 has no permitted justification in executable code  [timeout = 30]
src/client.py:15:24: DEF001 error: parameter 'port' of connect() has a default value: 5432  [def connect(host, port=5432):]
src/client.py:18:15: DEF006 error: environment variable "RETRIES" falls back to "3"; os.getenv(...) substitutes a fallback when environment variable "RETRIES" is unset (getenv)  [retries = os.getenv("RETRIES", "3")]
src/client.py:24:5: DEF005 error: except handler converts the exception into a fallback value: return None (EXC002 classification return_fallback; the handler is reported once, here)  [except OSError:]
src/client.py:24:5: EXC001 error: except handler of 'OSError' is forbidden: catch of 'OSError' is not approved by any boundary for this file  [except OSError:]

shockharness 0.1.0: 1 file(s), 1 analyzed, 0 failed; 5 finding(s), 0 suppressed
  by severity: error=5
  rules applied: MAGIC001, EXC001, EXC002, EXC003, DEF001, DEF002, DEF003, DEF004, DEF005, DEF006, CPLX001, CPLX002, CPLX003, CPLX004, CPLX005, SUPP001, SUPP002
  rules not implemented (not analyzed): MAGIC002
  complexity: 3 unit(s) in 1 file(s); conventions cyclomatic/1, cognitive/1, nesting/1, npath/1; 0 NPath saturated; advisory (CPLX001-CPLX004 are warnings)
  graph: call edges 2 resolved, 1 probable, 1 unresolved; module edges 0 resolved, 2 external, 0 unresolved; 0 cycle(s); 0 shared mutable binding(s)
gate: FAIL (exit 1)
  5 hard violation(s) and gates.new_hard_violations = "fail"

Profiles

Three built-in profiles ship with the package and are selected with profile = "..." (or --profile): strict (default: every rule at its documented severity, unparseable files fail), recommended (strict, but DEF003/DEF004 fallbacks are review), minimal (MAGIC001, EXC* and DEF* all capped at review, syntax failures warn, complexity off: everything is reported, nothing but malformed suppressions fails). Precedence is bundled defaults < profile < repository configuration < --profile / --parse-errors. Any profile below strict is printed as a policy deviation in every report, and the report's effective_profile names it.

Adopting ShockHarness

This section is written for a coding agent (or a person) that was told to set up ShockHarness in a repository and is reading it over HTTP. It is complete on its own: nothing here depends on another page, and the detailed documents are linked with absolute URLs at the end. Run every command from the repository root.

Step 1: install a pinned version

The version is always an exact pin; nothing updates to latest. Pick one:

uv tool install 'shockharness==0.1.0'                    # isolated tool environment; any repository, JavaScript included
uvx --from 'shockharness==0.1.0' shockharness version     # one-off run, nothing installed
pip install 'shockharness==0.1.0'                        # Python project: keep the pin in pyproject.toml (a dev extra) or requirements-dev.txt

The analyzer needs Python 3.11 or newer to run; the analyzed code may be Python or JavaScript. Its only runtime dependencies are tree-sitter and tree-sitter-javascript. The pin that CI installs must live where an updater can read it (pyproject.toml, a requirements file, or the workflow input in step 4), never only inside a shell command.

Step 2: write the policy file

shockharness init                 # writes .shockharness.toml: strict profile, every default spelled out
shockharness config validate      # exit 0; prints the profile and every policy deviation

Commit .shockharness.toml. Adjust include and exclude so only source is analyzed. Tests may stay included; exempt them from MAGIC001 and DEF001 with an [[tool.shockharness.overrides]] entry that carries a reason, as the tool's own configuration does.

Step 3: first run, and the four onboarding paths for an existing codebase

shockharness check .              # exit 0 pass, 1 violations, 2 invalid configuration, 3 tool failure or unparseable file

A codebase that predates the policy will have findings. Choose one of four paths. All four are visible in every report, so none of them hides anything.

Path Command or configuration Use it when
Baseline shockharness baseline create . --output .shockharness-baseline.json, review and commit the file, then run shockharness check . --baseline .shockharness-baseline.json You want a real gate from the first pull request: legacy findings stay listed but do not gate, new ones fail. The usual choice.
profile = "minimal" profile = "minimal" in .shockharness.toml, or shockharness check . --profile minimal for one run First contact: every rule runs and reports at review; only malformed suppressions fail.
gates.new_hard_violations = "warn" [tool.shockharness.gates] with new_hard_violations = "warn" in .shockharness.toml The repository has agreed that the policy is advisory for now, and that decision should be reviewed and diffed like any configuration change. It is reported as a deviation.
--exit-zero shockharness check . --exit-zero One invocation must not block (a dry run, a first SARIF upload, a scheduled strict run) and the repository's policy must not change for it. Exit 1 becomes 0; exit 2 and 3 are never masked; the report says advisory.

On pull requests prefer shockharness gate --base origin/main --head HEAD (use the repository's default branch; both revisions must be committed): it exits 1 only on hard violations that are new relative to the base.

Step 4: CI

Direct invocation works on any CI system. Install the pin from the manifest, then run the analyzer; a report is written even when the gate fails:

- run: pip install -e ".[dev]"      # the pin lives in pyproject.toml; a JavaScript repository uses the reusable workflow below instead
- run: shockharness check . --format sarif --output shockharness.sarif

On GitHub, call the reusable workflow. Pin its ref to a release tag or a full commit SHA. The analyzer version is the shockharness-version input, not the ref; the two are separate pins:

name: ShockHarness
on:
  pull_request:
  push:
    branches: [main]
permissions:
  contents: read
jobs:
  shockharness:
    uses: evolvingstuff/ShockHarness/.github/workflows/check.yml@v0.1.0
    permissions:
      contents: read
      security-events: write
    with:
      shockharness-version: "0.1.0"
      base: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || '' }}

Add .github/dependabot.yml so both pins get upgrade pull requests. Dependabot bumps the uses: ref and any pyproject.toml or requirements pin; it does not rewrite the shockharness-version input, so bump that by hand in the same pull request:

version: 2
updates:
  - package-ecosystem: pip
    directory: /
    schedule:
      interval: weekly
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly

Step 5: install the agent skills

Four skills ship inside the package: write-compliant-code, fix-violations, refactor-complexity and pre-commit-review.

shockharness skills install --agent claude                              # .claude/skills/<name>/SKILL.md in this repository
shockharness skills install --agent codex                               # .agents/skills/<name>/SKILL.md in this repository
shockharness skills install --agent generic --project docs/agent-skills # any agent; prints the line to add to AGENTS.md
shockharness skills update --agent claude --check                       # exit 1 when an installed skill is stale or edited

Add --user to install into the per-user directory (~/.claude/skills or ~/.agents/skills) instead of the repository. install never overwrites an existing file unless --force is given. A manifest, .shockharness-skills.json, is written next to the skills and records the package version and a SHA-256 per file; skills update rewrites unedited files, leaves edited files alone and reports each as a conflict with the remediation. Put skills update --check in CI to detect drift after an upgrade.

Step 6: verify

Each command's output confirms one step.

Step Command Output that confirms it
Install shockharness version shockharness 0.1.0
Policy shockharness config validate a line starting shockharness: configuration is valid naming profile strict, exit 0
Analysis shockharness check . a report whose last lines start with gate: PASS (exit 0) or gate: FAIL (exit 1); exit 2 or 3 means the setup is wrong, not the code
Baseline, if used shockharness check . --baseline .shockharness-baseline.json gate: PASS (exit 0) followed by N legacy finding(s) matched the baseline
Pull-request gate shockharness gate --base origin/main --head HEAD gate: PASS (exit 0) with new, fixed and unchanged counts
Skills shockharness skills update --agent claude --check a line ending up to date, exit 0
CI the workflow run the shockharness job green; findings under the repository's Security tab when SARIF is uploaded

Detailed documents: configuration https://github.com/evolvingstuff/ShockHarness/blob/main/docs/configuration.md, rules https://github.com/evolvingstuff/ShockHarness/blob/main/docs/rules.md, CI and Dependabot https://github.com/evolvingstuff/ShockHarness/blob/main/docs/integration.md, skills https://github.com/evolvingstuff/ShockHarness/blob/main/docs/skills.md, limitations https://github.com/evolvingstuff/ShockHarness/blob/main/docs/limitations.md.

Configuration

.shockharness.toml or shockharness.toml (one of them; both is an error) or a [tool.shockharness] table in pyproject.toml (the dedicated file wins and the shadowed table is reported). Unknown keys, bad types, unknown rule IDs, and invalid globs are errors, never ignored. Every rule can be disabled; every disable is listed in the report.

[tool.shockharness]
profile = "strict"
include = ["src/**/*.py", "src/**/*.js"]
exclude = ["**/node_modules/**", "**/.venv/**"]
parse_errors = "fail"

[tool.shockharness.rules.magic_numbers]
enabled = true
allowed_values = [0, 1]
allow_unary_negative_one = false

[[tool.shockharness.overrides]]
paths = ["tests/**"]
disable = ["MAGIC001"]
reason = "Fixtures encode literal expected outputs"

Inline suppressions need an exact rule ID and a reason, on the same line:

status = 404  # shockharness: ignore[MAGIC001] reason="RFC 9110 section 15.5.5"

A malformed suppression is a SUPP001 error; one that silences nothing is a SUPP002 warning. --no-inline-suppressions disables them for centrally governed policies.

CI

Direct invocation works on any CI system: install the pin, run shockharness check . (or gate --base <base> --head HEAD on pull requests). GitHub users can call the reusable workflow, pinned to a tag or a full commit SHA; the analyzer version is the shockharness-version input, not the ref:

jobs:
  shockharness:
    uses: evolvingstuff/ShockHarness/.github/workflows/check.yml@v0.1.0
    permissions:
      contents: read
      security-events: write
    with:
      shockharness-version: "0.1.0"

It uploads SARIF to code scanning. Keep the version pin in pyproject.toml, a requirements file or that workflow input, never in a shell string, so Dependabot can open upgrade PRs; docs/integration.md has the full recipe and a Dependabot snippet, and examples/python and examples/js are complete consumers exercised by CI.

See docs/ for configuration, rules, complexity conventions, architecture, integration, skills, releasing, and limitations; evals/ holds the skill evaluation corpus. Every JSON report carries metrics.complexity (per-unit numbers, conventions, thresholds, per-file totals), metrics.graph (edges with confidence, module edges, cycles, fan-in/out, shared mutable state) and metrics.history (churn, co-change, hotspots); none of them gates.

Library

from pathlib import Path
from shockharness import analyze_paths
from shockharness.analysis import AnalysisOptions
from shockharness.config import load_config

config = load_config(Path("."), None)
report = analyze_paths([Path("src")], Path("."), config, AnalysisOptions(inline_suppressions=True))
print(report.gate.status, len(report.findings))

# Optional: apply a reviewed baseline and attach git history metrics.
from shockharness.api import apply_baseline, with_history
from shockharness.gating.baseline import load_baseline

report = apply_baseline(report, load_baseline(Path(".shockharness-baseline.json")), config)
report = with_history(report, Path("."), "HEAD", config)

Development

uv pip install -e ".[dev]"
pytest -q
ruff check src tests scripts evals && ruff format --check src tests scripts evals
shockharness check src tests scripts evals   # the tool gates its own source
python -m build && python scripts/consumer_upgrade_check.py --dist dist --examples examples
python evals/run_evals.py --reference   # the skill eval corpus: every reference solution scores clean

ShockHarness runs on itself as a hard CI gate. Its one MAGIC001 exemption is src/shockharness/constants.py; its declared exception boundaries are listed in .shockharness.toml with reasons and enforced by EXC001-EXC003: every try in the tool wraps exactly one external call and converts the failure into a typed diagnostic. DEF001-DEF006 hold too: the tool has no parameter defaults outside tests, no mapping fallbacks, and no or/is None defaults.

License

MIT. See LICENSE.

Metadata

Release files for shockharness 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shockharness 0.1.0
File Size Uploaded
shockharness-0.1.0.tar.gz 1.9 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for shockharness 0.1.0
File Interpreter ABI Platform
shockharness-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 2.1 MB

Release files / shockharness-0.1.0.tar.gz

Download URL shockharness-0.1.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
15e0cada8a01f594a5b91127ac7b8e49a6b73176f261a6145d9410fe258942a4
BLAKE2b-256 checksum
How to use checksums
54f0f9a016fe732558c3ac1df102ead0bceb86060c24656701f6fcdbc0a0fdd8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / shockharness-0.1.0-py3-none-any.whl

Download URL shockharness-0.1.0-py3-none-any.whl
Size 209.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
eceebc6bea28e65d75c810c3cc1b813aa278822445e4ef1fb108b047aad8ac8b
BLAKE2b-256 checksum
How to use checksums
07d02fdb5083fbc115c29ad8538fe0521d4b37db3cc99da649148db798819f74
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page