Skip to main content

Architectural Drift Index and risk scan over a repo's agent-rules surface — local, deterministic, zero network

Project description

flowharness (flowharness-scan console script)

flowharness drift badge

The Architectural Drift Index + Vibe-Risk scan for your repo's agent-rules surface.

Local · static · deterministic · zero network · zero model calls · zero config.


flowharness-scan reads the agent-rules surface of a repository — CLAUDE.md, .cursorrules, .cursor/rules/*.mdc, AGENTS.md, .continue/**, .opencode/**, playbook/**, and an agentharnesses bundle (HARNESS.md + skills/**/SKILL.md + routing/reference files) — and tells you two things in one pass:

  • How messy it is — the Architectural Drift Index (0–100): duplication, contradictions, orphaned rules, and cross-harness divergence.
  • How dangerous it is — a quarantine verdict: leaked secrets, indirect prompt-injection, unattested/always-on guidance.

Quickstart

No install required:

$ uvx flowharness scan .
flowharness-scan: drift index 30/100 - QUARANTINE (policy scoring/1)

uvx flowharness init scaffolds a new repo's agent-rules surface the same way, with no persistent install. flowharness and flowharness-scan are the same entry point (see below); uvx --from flowharness flowharness-scan . also works if you prefer the explicit name.

Note: these packages currently install top-level modules named core, inspection, evaluation, and portability. Prefer uvx (isolated environments) over pip install into a shared venv until the 0.2 module-namespace rename lands.

Exit codes wire straight into CI / pre-commit: PASS = 0 · NEEDS_HUMAN = 1 · FAIL = 2.

Output modes

$ flowharness-scan .                      # terminal banner (default)
$ flowharness-scan . --json               # full inspection/v1 report (content-addressed)
$ flowharness-scan . --badge > drift.svg  # local SVG badge for your README

Gating

$ flowharness-scan . --fail-on risk        # fail on the quarantine verdict (default)
$ flowharness-scan . --fail-on "index>40"  # fail when the drift index exceeds 40
$ flowharness-scan . --policy scoring.toml # custom, versioned scoring weights

Baseline (only fail on new drift)

$ flowharness-scan . --set-baseline > .flowharness-baseline.json   # commit this once
$ flowharness-scan . --baseline .flowharness-baseline.json --fail-on baseline

--set-baseline writes nothing into your repo — it prints the baseline to stdout, so the scanner stays strictly read-only on the project directory.

Platform (opt-in network: pull / upload)

The default scan path is strictly zero-network. The pull/upload subcommands and scan --upload are the only paths that touch the network (install the [remote] extra for the httpx dependency). They authenticate with a scoped bearer token.

$ export FLOWHARNESS_API_URL=https://app.example.com   # required (or pass --api-url)
$ export FLOWHARNESS_TOKEN=fht_...                      # the scoped bearer token

$ flowharness-scan pull <release_id>                    # fetch a release's pins -> offline cache
$ flowharness-scan . --upload --enrollment <id>         # scan, then upload the InspectionRecord
$ flowharness-scan upload sync  --enrollment <id> --release <id> --observed observed.json
$ flowharness-scan upload eval  --run run.json          # signs the report (X-Signature) + uploads

Notes: a leading token that names a real directory is scanned, not treated as a subcommand (flowharness-scan ./pull scans ./pull). A remote http:// URL is refused before any socket opens. Non-2xx responses map to stable exit codes (10–16); a duplicate upload (409) is treated as an idempotent success. FLOWHARNESS_TOKEN is the eval-signing HMAC key and is never written to disk.

status — am I running what the channel publishes?

flowharness status answers two questions in one line — is the applied release the channel's head? and has anyone edited the governed files? — and it is strictly read-only: it writes nothing in any mode, and under --offline it opens no socket and imports no httpx at all.

$ flowharness status
flowharness: 2 releases behind stable (rel_9 → rel_11) — 'flowharness pull' to catch up

$ flowharness status --json          # the flowharness/status/v1 payload, canonical bytes
$ flowharness status --check         # exit 7 on proven divergence; stdout is unchanged
$ flowharness status --offline       # local facts only; freshness is reported as unknown
$ flowharness status --dir path/to/repo --channel beta --api-url https://app.example.com

flowharness and flowharness-scan are two names for the same entry point — the examples above and below use them interchangeably, and every subcommand works under either.

Run it from anywhere inside the repo: status walks up to the nearest ancestor holding .flowharness/ or flowharness.toml, stopping at a .git boundary, the way git status does. --channel overrides [flowharness].channel, which overrides the stable default. --api-url is accepted and ignored under --offline.

Exit map. Bare status always exits 0 — a corrupt lockfile, a dead network, a typo'd flowharness.toml, or an unreadable governed file is a reported state, never an exit code, so the verb is safe in a shell prompt. --check returns 7 on proven divergence (a governed file drifted, or the applied release is provably behind the head); if nothing diverged but the probe itself failed, it returns that failure's code (10–17). A bad flag or a --dir that does not exist is the usual argparse 2.

Divergence requires proof: an unreadable origin, an unrecognised origin, or an unorderable pair of release versions is reported as unknown (drift.known: false in the JSON) and --check returns 0 rather than claiming a divergence it did not observe.

Not yet here. --diff (show the actual edits) lands with PR-D; serverless freshness (a git remote instead of a channel) lands with PR-F.

Known limitations. Drift is byte-comparison against the lockfile's hashes, so: a filename stored NFC on Linux and NFD on macOS can flip between "converged" and "missing", and core.autocrlf=true on Windows makes every governed text file read as modified. Every governed file is re-hashed on every run (a megabyte at a time — a huge file is slow, never an out-of-memory), and a console that cannot encode the banner's ·// gets those characters back-slash-escaped rather than a traceback.

Behind a corporate proxy or a private CA, note that the client sets trust_env=False: status, pull and upload deliberately ignore HTTP_PROXY/HTTPS_PROXY and SSL_CERT_FILE/SSL_CERT_DIR/REQUESTS_CA_BUNDLE, and there is not yet an explicit option to put them back.

Pre-commit

# .pre-commit-config.yaml
-   repo: https://github.com/suleimanmahmoud/flowharness
    rev: <tag>
    hooks:
    -   id: flowharness-scan

What it flags

Dimension Check Catches
duplication DuplicationCheck the same guideline pasted across files/sections
contradiction ContradictionCheck retry-vs-fail-fast, tests-first-vs-prototype, …
orphans OrphanCheck rules no active harness loads; leftovers of removed tools
divergence DivergenceCheck the "same" rule that differs across harnesses
risk LeakageCheck secrets/credentials/template-vars — redacted to fingerprints
risk InjectionHeuristicsCheck indirect prompt-injection (override / exfil / fetch-and-execute)
risk AttestationCheck / BoundsCheck unattested or implicitly always-on guidance

A finding never contains a secret value; the report carries structural metadata only (no rule-body text).

Guarantees

  • Deterministic — identical directory state ⇒ byte-identical report; ids and the report_hash are content hashes; no timestamps.
  • Read-only & offline — the scanner only ever reads the project directory; no network, no model calls on the default path.
  • Fast — a 50-unit repo scans in tens of milliseconds (budget: < 2 s).

Report

--json emits the inspection/v1 schema — snapshot metadata, redacted findings, the risk assessment, and the scored drift index — documented in docs/specs/inspection-report-v1.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flowharness-0.1.0-py3-none-any.whl (138.8 kB view details)

Uploaded Python 3

File details

Details for the file flowharness-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: flowharness-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 138.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for flowharness-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 028db1f0df1320bcfbbe399b5fe2ce44807d8e119efffb34d3d5f264bfc1edb7
MD5 00555259ab5cf4b051d30053f9c0f69a
BLAKE2b-256 d06dca50b6051a5da3e210934d9df9ce0dbd6971bb8e85225985be4309371c7b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page