Architectural Drift Index and risk scan over a repo's agent-rules surface — local, deterministic, zero network
Project description
flowharness (flowharness-scan console script)
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, andportability. Preferuvx(isolated environments) overpip installinto 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_hashare 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
028db1f0df1320bcfbbe399b5fe2ce44807d8e119efffb34d3d5f264bfc1edb7
|
|
| MD5 |
00555259ab5cf4b051d30053f9c0f69a
|
|
| BLAKE2b-256 |
d06dca50b6051a5da3e210934d9df9ce0dbd6971bb8e85225985be4309371c7b
|