intent-audit-harness 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- intent_audit_harness-1.2.0/.gitignore +40 -0
- intent_audit_harness-1.2.0/PKG-INFO +76 -0
- intent_audit_harness-1.2.0/README.md +53 -0
- intent_audit_harness-1.2.0/pyproject.toml +60 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/__init__.py +10 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/__main__.py +5 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/cli.py +125 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/scripts/arch-check.sh +185 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/scripts/bias-count.sh +151 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/scripts/crap-score.py +496 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/scripts/escape-scan.sh +250 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/scripts/gherkin-lint.sh +160 -0
- intent_audit_harness-1.2.0/src/intent_audit_harness/scripts/harness-hash.sh +194 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
*.tgz
|
|
4
|
+
.DS_Store
|
|
5
|
+
coverage/
|
|
6
|
+
build/
|
|
7
|
+
reports/
|
|
8
|
+
|
|
9
|
+
# NOTE: .harness-hash is INTENTIONALLY tracked, not ignored.
|
|
10
|
+
# Per iep-P3 iah-self-pin (bd_000-projects-itpl) + audit-harness CLAUDE.md
|
|
11
|
+
# design rule 5 ("the harness tests itself"): the manifest IS the policy
|
|
12
|
+
# commitment. CI's `harness-hash --verify` step (hard-fail since v1.1.0)
|
|
13
|
+
# compares working-tree hashes against the committed manifest. If
|
|
14
|
+
# .harness-hash were gitignored, --verify would either compare against a
|
|
15
|
+
# locally-regenerated manifest (no integrity guarantee) or fail with
|
|
16
|
+
# exit-3 (no manifest) — both useless.
|
|
17
|
+
|
|
18
|
+
# Python wrapper build artifacts
|
|
19
|
+
python/.venv/
|
|
20
|
+
python/dist/
|
|
21
|
+
python/build/
|
|
22
|
+
python/*.egg-info/
|
|
23
|
+
**/__pycache__/
|
|
24
|
+
*.pyc
|
|
25
|
+
|
|
26
|
+
# Rust wrapper build artifacts
|
|
27
|
+
rust/target/
|
|
28
|
+
rust/Cargo.lock
|
|
29
|
+
|
|
30
|
+
# Beads / Dolt files (added by bd init)
|
|
31
|
+
.dolt/
|
|
32
|
+
*.db
|
|
33
|
+
.beads-credential-key
|
|
34
|
+
|
|
35
|
+
# Auto-generated by ~/.claude/skills/sync-testing-harness/ (weekly cron). Operational state, not policy commitment.
|
|
36
|
+
/HARNESS-SYNC-REPORT.md
|
|
37
|
+
|
|
38
|
+
# crap-score writes reports/ into whatever dir `audit --deep` runs in;
|
|
39
|
+
# the audit suite exercises it on fixtures — never commit those generated artifacts.
|
|
40
|
+
tests/fixtures/audit/**/reports/
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: intent-audit-harness
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Deterministic test-enforcement harness — escape-scan, hash-pinning, CRAP, architecture checks, bias detection, Gherkin lint. Python-native wrapper around the @intentsolutions/audit-harness toolkit.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jeremylongshore/intent-audit-harness
|
|
6
|
+
Project-URL: Repository, https://github.com/jeremylongshore/intent-audit-harness
|
|
7
|
+
Project-URL: Issues, https://github.com/jeremylongshore/intent-audit-harness/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/jeremylongshore/intent-audit-harness/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: 7-layer-testing,ai-containment,architecture,claude-code,coverage-gate,crap,escape-scan,hash-pin,mutation-testing,test-audit,testing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# intent-audit-harness (Python)
|
|
25
|
+
|
|
26
|
+
Python-native install of the Intent Solutions deterministic test-enforcement toolkit.
|
|
27
|
+
|
|
28
|
+
Mirrors the CLI surface of the Node package
|
|
29
|
+
[`@intentsolutions/audit-harness`](https://www.npmjs.com/package/@intentsolutions/audit-harness)
|
|
30
|
+
so the command line is identical across ecosystems.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install intent-audit-harness
|
|
36
|
+
# or, inside a project venv:
|
|
37
|
+
python -m pip install intent-audit-harness
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This ships a console script `audit-harness` and a module entry point
|
|
41
|
+
`python -m intent_audit_harness`.
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- Python 3.8+
|
|
46
|
+
- `bash` available on `PATH` (Linux, macOS, or WSL)
|
|
47
|
+
- `python3` on `PATH` for the `crap` subcommand
|
|
48
|
+
- Optional per-subcommand tools: `radon` / `gocyclo` / `dependency-cruiser` / `import-linter`
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
audit-harness --help
|
|
54
|
+
audit-harness verify
|
|
55
|
+
audit-harness escape-scan --staged
|
|
56
|
+
audit-harness crap src/
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
See the root project for the full docs:
|
|
60
|
+
<https://github.com/jeremylongshore/intent-audit-harness>
|
|
61
|
+
|
|
62
|
+
## What this package does
|
|
63
|
+
|
|
64
|
+
Dispatches to the same shell/python scripts shipped with the canonical npm package.
|
|
65
|
+
The Python wheel bundles:
|
|
66
|
+
|
|
67
|
+
- `harness-hash.sh` — pinning / verification
|
|
68
|
+
- `escape-scan.sh` — diff scanner for AI escape grammar
|
|
69
|
+
- `arch-check.sh` — language-appropriate architecture checker
|
|
70
|
+
- `bias-count.sh` — test-bias heuristics
|
|
71
|
+
- `gherkin-lint.sh` — advisory Gherkin quality check
|
|
72
|
+
- `crap-score.py` — CRAP (complexity x coverage) scorer
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# intent-audit-harness (Python)
|
|
2
|
+
|
|
3
|
+
Python-native install of the Intent Solutions deterministic test-enforcement toolkit.
|
|
4
|
+
|
|
5
|
+
Mirrors the CLI surface of the Node package
|
|
6
|
+
[`@intentsolutions/audit-harness`](https://www.npmjs.com/package/@intentsolutions/audit-harness)
|
|
7
|
+
so the command line is identical across ecosystems.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install intent-audit-harness
|
|
13
|
+
# or, inside a project venv:
|
|
14
|
+
python -m pip install intent-audit-harness
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This ships a console script `audit-harness` and a module entry point
|
|
18
|
+
`python -m intent_audit_harness`.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Python 3.8+
|
|
23
|
+
- `bash` available on `PATH` (Linux, macOS, or WSL)
|
|
24
|
+
- `python3` on `PATH` for the `crap` subcommand
|
|
25
|
+
- Optional per-subcommand tools: `radon` / `gocyclo` / `dependency-cruiser` / `import-linter`
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
audit-harness --help
|
|
31
|
+
audit-harness verify
|
|
32
|
+
audit-harness escape-scan --staged
|
|
33
|
+
audit-harness crap src/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
See the root project for the full docs:
|
|
37
|
+
<https://github.com/jeremylongshore/intent-audit-harness>
|
|
38
|
+
|
|
39
|
+
## What this package does
|
|
40
|
+
|
|
41
|
+
Dispatches to the same shell/python scripts shipped with the canonical npm package.
|
|
42
|
+
The Python wheel bundles:
|
|
43
|
+
|
|
44
|
+
- `harness-hash.sh` — pinning / verification
|
|
45
|
+
- `escape-scan.sh` — diff scanner for AI escape grammar
|
|
46
|
+
- `arch-check.sh` — language-appropriate architecture checker
|
|
47
|
+
- `bias-count.sh` — test-bias heuristics
|
|
48
|
+
- `gherkin-lint.sh` — advisory Gherkin quality check
|
|
49
|
+
- `crap-score.py` — CRAP (complexity x coverage) scorer
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "intent-audit-harness"
|
|
7
|
+
version = "1.2.0"
|
|
8
|
+
description = "Deterministic test-enforcement harness — escape-scan, hash-pinning, CRAP, architecture checks, bias detection, Gherkin lint. Python-native wrapper around the @intentsolutions/audit-harness toolkit."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jeremy Longshore", email = "jeremy@intentsolutions.io" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"testing",
|
|
17
|
+
"test-audit",
|
|
18
|
+
"hash-pin",
|
|
19
|
+
"escape-scan",
|
|
20
|
+
"crap",
|
|
21
|
+
"architecture",
|
|
22
|
+
"mutation-testing",
|
|
23
|
+
"coverage-gate",
|
|
24
|
+
"7-layer-testing",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"ai-containment",
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"License :: OSI Approved :: Apache Software License",
|
|
32
|
+
"Operating System :: POSIX",
|
|
33
|
+
"Operating System :: MacOS",
|
|
34
|
+
"Programming Language :: Python :: 3",
|
|
35
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
36
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
37
|
+
"Topic :: Software Development :: Testing",
|
|
38
|
+
]
|
|
39
|
+
dependencies = []
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/jeremylongshore/intent-audit-harness"
|
|
43
|
+
Repository = "https://github.com/jeremylongshore/intent-audit-harness"
|
|
44
|
+
Issues = "https://github.com/jeremylongshore/intent-audit-harness/issues"
|
|
45
|
+
Changelog = "https://github.com/jeremylongshore/intent-audit-harness/blob/main/CHANGELOG.md"
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
audit-harness = "intent_audit_harness.cli:main"
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.wheel]
|
|
51
|
+
packages = ["src/intent_audit_harness"]
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.sdist]
|
|
54
|
+
include = [
|
|
55
|
+
"/src",
|
|
56
|
+
"/README.md",
|
|
57
|
+
"/pyproject.toml",
|
|
58
|
+
"/LICENSE",
|
|
59
|
+
"/NOTICE",
|
|
60
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""intent-audit-harness — deterministic test-enforcement toolkit.
|
|
2
|
+
|
|
3
|
+
Python-native wrapper around the canonical shell/python scripts maintained at
|
|
4
|
+
https://github.com/jeremylongshore/intent-audit-harness.
|
|
5
|
+
|
|
6
|
+
The Python package mirrors the CLI surface of the Node package
|
|
7
|
+
``@intentsolutions/audit-harness`` so commands are identical across languages.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "1.2.0"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""CLI dispatcher — thin wrapper that shells out to the bundled scripts.
|
|
2
|
+
|
|
3
|
+
Mirrors the command surface of ``@intentsolutions/audit-harness``:
|
|
4
|
+
|
|
5
|
+
verify | init | list | escape-scan | arch | bias | gherkin-lint | crap
|
|
6
|
+
|
|
7
|
+
The scripts are shipped inside the wheel as package data; we resolve their
|
|
8
|
+
filesystem path via ``importlib.resources`` and invoke ``bash``/``python3``.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import stat
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from importlib import resources
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Iterable
|
|
19
|
+
|
|
20
|
+
from . import __version__
|
|
21
|
+
|
|
22
|
+
COMMANDS: dict[str, tuple[str, list[str]]] = {
|
|
23
|
+
"verify": ("harness-hash.sh", ["--verify"]),
|
|
24
|
+
"init": ("harness-hash.sh", ["--init"]),
|
|
25
|
+
"list": ("harness-hash.sh", ["--list"]),
|
|
26
|
+
"escape-scan": ("escape-scan.sh", []),
|
|
27
|
+
"arch": ("arch-check.sh", []),
|
|
28
|
+
"bias": ("bias-count.sh", []),
|
|
29
|
+
"gherkin-lint": ("gherkin-lint.sh", []),
|
|
30
|
+
"crap": ("crap-score.py", []),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
USAGE = """audit-harness — deterministic test-enforcement toolkit (Python)
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
audit-harness <command> [args...]
|
|
37
|
+
|
|
38
|
+
Commands:
|
|
39
|
+
verify Verify hash-pinned artifacts (exit 2 = HARNESS_TAMPERED)
|
|
40
|
+
init Initialize or re-init the .harness-hash manifest
|
|
41
|
+
list List currently pinned files
|
|
42
|
+
escape-scan <source> Scan a diff for escape attempts
|
|
43
|
+
source: --staged | --range A..B | - (stdin) | path.patch
|
|
44
|
+
arch Run architecture-rule checks (Wall 7)
|
|
45
|
+
bias Count test-bias patterns (tautology, smoke-only, etc.)
|
|
46
|
+
gherkin-lint Advisory Gherkin quality check
|
|
47
|
+
crap [args...] CRAP complexity x coverage scorer (multi-language)
|
|
48
|
+
|
|
49
|
+
Options:
|
|
50
|
+
--version, -v Print version
|
|
51
|
+
--help, -h Print this help
|
|
52
|
+
|
|
53
|
+
Exit codes (escape-scan):
|
|
54
|
+
0 = clean
|
|
55
|
+
1 = CHALLENGE (engineer-approved comment required)
|
|
56
|
+
2 = REFUSE (pipeline halted)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _script_path(name: str) -> Path:
|
|
61
|
+
"""Return the on-disk path of a bundled script, ensuring it's executable."""
|
|
62
|
+
# importlib.resources.files -> Traversable; .joinpath returns a Path-like
|
|
63
|
+
# When installed from a wheel, this resolves to the extracted scripts/ dir.
|
|
64
|
+
pkg_scripts = resources.files("intent_audit_harness").joinpath("scripts")
|
|
65
|
+
path = Path(str(pkg_scripts.joinpath(name)))
|
|
66
|
+
if not path.exists():
|
|
67
|
+
print(f"audit-harness: script not found at {path}", file=sys.stderr)
|
|
68
|
+
sys.exit(2)
|
|
69
|
+
# Ensure executable bit for shell scripts (wheels don't always preserve it)
|
|
70
|
+
try:
|
|
71
|
+
mode = path.stat().st_mode
|
|
72
|
+
path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
73
|
+
except OSError:
|
|
74
|
+
pass # best-effort; we always invoke via explicit interpreter anyway
|
|
75
|
+
return path
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _interpreter_for(script: str) -> str:
|
|
79
|
+
return "python3" if script.endswith(".py") else "bash"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _dispatch(cmd: str, extra_args: Iterable[str]) -> int:
|
|
83
|
+
script, default_args = COMMANDS[cmd]
|
|
84
|
+
script_path = _script_path(script)
|
|
85
|
+
interpreter = _interpreter_for(script)
|
|
86
|
+
|
|
87
|
+
if shutil.which(interpreter) is None:
|
|
88
|
+
print(
|
|
89
|
+
f"audit-harness: required interpreter '{interpreter}' not found on PATH",
|
|
90
|
+
file=sys.stderr,
|
|
91
|
+
)
|
|
92
|
+
return 2
|
|
93
|
+
|
|
94
|
+
argv = [interpreter, str(script_path), *default_args, *extra_args]
|
|
95
|
+
try:
|
|
96
|
+
completed = subprocess.run(argv, check=False)
|
|
97
|
+
except OSError as exc:
|
|
98
|
+
print(f"audit-harness: failed to spawn {interpreter}: {exc}", file=sys.stderr)
|
|
99
|
+
return 2
|
|
100
|
+
return completed.returncode
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main(argv: list[str] | None = None) -> int:
|
|
104
|
+
args = list(argv if argv is not None else sys.argv[1:])
|
|
105
|
+
|
|
106
|
+
if not args or args[0] in ("--help", "-h"):
|
|
107
|
+
print(USAGE)
|
|
108
|
+
return 0
|
|
109
|
+
|
|
110
|
+
if args[0] in ("--version", "-v"):
|
|
111
|
+
print(__version__)
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
cmd, rest = args[0], args[1:]
|
|
115
|
+
if cmd not in COMMANDS:
|
|
116
|
+
print(f"audit-harness: unknown command '{cmd}'", file=sys.stderr)
|
|
117
|
+
print(USAGE, file=sys.stderr)
|
|
118
|
+
return 2
|
|
119
|
+
|
|
120
|
+
rc = _dispatch(cmd, rest)
|
|
121
|
+
return rc
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__": # pragma: no cover
|
|
125
|
+
sys.exit(main())
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# arch-check.sh — Wall 7 architecture-constraint dispatcher.
|
|
3
|
+
#
|
|
4
|
+
# Detects the primary language of the repo, invokes the appropriate
|
|
5
|
+
# dependency / architecture checker with the project's rule pack, and
|
|
6
|
+
# normalizes the exit code.
|
|
7
|
+
#
|
|
8
|
+
# Exit codes:
|
|
9
|
+
# 0 — all rules pass
|
|
10
|
+
# 1 — rule violations detected
|
|
11
|
+
# 2 — no tool installed / no config / unsupported language
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# bash arch-check.sh # run from repo root
|
|
15
|
+
# bash arch-check.sh --json # emit JSON summary to stdout
|
|
16
|
+
# bash arch-check.sh --help
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
# Bash version floor: these gates rely on bash 4+ features. Refuse early with a
|
|
21
|
+
# clear message on bash 3.x (e.g. macOS system bash) instead of failing later
|
|
22
|
+
# with a cryptic syntax error (jcgw).
|
|
23
|
+
[ "${BASH_VERSINFO:-0}" -ge 4 ] || { echo 'audit-harness requires bash >= 4' >&2; exit 3; }
|
|
24
|
+
|
|
25
|
+
# Cross-platform SHA-256: `sha256sum` ships with GNU coreutils (Linux);
|
|
26
|
+
# macOS only has `shasum -a 256`. Both produce identical `<hash> <file>`
|
|
27
|
+
# output, so downstream awk parsing is unchanged. Same pattern as
|
|
28
|
+
# harness-hash.sh / escape-scan.sh / bias-count.sh.
|
|
29
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
30
|
+
SHA256_CMD=(sha256sum)
|
|
31
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
32
|
+
SHA256_CMD=(shasum -a 256)
|
|
33
|
+
else
|
|
34
|
+
echo "arch-check: neither sha256sum nor shasum found in PATH" >&2
|
|
35
|
+
exit 2
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
ROOT="${ROOT:-$(pwd)}"
|
|
39
|
+
JSON_OUT=0
|
|
40
|
+
REPORT_DIR="${ROOT}/reports/arch"
|
|
41
|
+
|
|
42
|
+
usage() {
|
|
43
|
+
sed -n '2,20p' "$0"
|
|
44
|
+
exit 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for arg in "$@"; do
|
|
48
|
+
case "$arg" in
|
|
49
|
+
--json) JSON_OUT=1 ;;
|
|
50
|
+
--help|-h) usage ;;
|
|
51
|
+
*) echo "arch-check: unknown flag $arg" >&2; exit 2 ;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
mkdir -p "$REPORT_DIR"
|
|
56
|
+
|
|
57
|
+
emit_result() {
|
|
58
|
+
local tool="$1" status="$2" violations="$3" log="$4"
|
|
59
|
+
if [[ "$JSON_OUT" -eq 1 ]]; then
|
|
60
|
+
# status: pass / fail / missing-tool / not-configured
|
|
61
|
+
local result
|
|
62
|
+
case "$status" in
|
|
63
|
+
pass) result="PASS" ;;
|
|
64
|
+
fail) result="FAIL" ;;
|
|
65
|
+
missing-tool|not-configured) result="NOT_APPLICABLE" ;;
|
|
66
|
+
*) result="ADVISORY" ;;
|
|
67
|
+
esac
|
|
68
|
+
local input_hash="sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
69
|
+
local policy_hash="sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
70
|
+
# Best-effort: input_hash is the source tree fingerprint when running against ROOT/src
|
|
71
|
+
if [[ -d "${ROOT}/src" ]]; then
|
|
72
|
+
input_hash=$(find "${ROOT}/src" -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.java" -o -name "*.kt" -o -name "*.cs" -o -name "*.php" \) -exec "${SHA256_CMD[@]}" {} \; 2>/dev/null | sort | "${SHA256_CMD[@]}" | awk '{print "sha256:"$1}')
|
|
73
|
+
fi
|
|
74
|
+
# Hash the architecture rule config (whichever tool's config was used)
|
|
75
|
+
for cfg in .dependency-cruiser.js .dependency-cruiser.cjs .importlinter deptrac.yaml arch-go.yml; do
|
|
76
|
+
if [[ -f "${ROOT}/${cfg}" ]]; then
|
|
77
|
+
policy_hash=$("${SHA256_CMD[@]}" "${ROOT}/${cfg}" | awk '{print "sha256:"$1}')
|
|
78
|
+
break
|
|
79
|
+
fi
|
|
80
|
+
done
|
|
81
|
+
local fail_block=""
|
|
82
|
+
[[ "$result" == "FAIL" ]] && fail_block=',"failure_mode":"arch-violation"'
|
|
83
|
+
printf '{"gate_id":"audit-harness:%s:arch-check","result":"%s"%s,"input_hash":"%s","policy_hash":"%s","metadata":{"tool":"%s","status":"%s","violations":%s,"log":"%s"}}\n' \
|
|
84
|
+
"${AUDIT_HARNESS_SIDE:-ci}" "$result" "$fail_block" "$input_hash" "$policy_hash" \
|
|
85
|
+
"$tool" "$status" "$violations" "$log"
|
|
86
|
+
else
|
|
87
|
+
echo "arch-check: tool=$tool status=$status violations=$violations"
|
|
88
|
+
echo " log=$log"
|
|
89
|
+
fi
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# 1. dependency-cruiser (JS/TS)
|
|
93
|
+
if [[ -f "${ROOT}/.dependency-cruiser.js" || -f "${ROOT}/.dependency-cruiser.cjs" ]]; then
|
|
94
|
+
LOG="${REPORT_DIR}/dep-cruiser.log"
|
|
95
|
+
if command -v npx >/dev/null 2>&1; then
|
|
96
|
+
if npx --no-install dependency-cruiser --validate --output-type err "${ROOT}/src" > "$LOG" 2>&1; then
|
|
97
|
+
emit_result dependency-cruiser pass 0 "$LOG"
|
|
98
|
+
exit 0
|
|
99
|
+
else
|
|
100
|
+
VIOL=$(grep -c "error" "$LOG" || echo 0)
|
|
101
|
+
emit_result dependency-cruiser fail "$VIOL" "$LOG"
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
else
|
|
105
|
+
emit_result dependency-cruiser missing-tool 0 "$LOG"
|
|
106
|
+
exit 2
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# 2. import-linter (Python)
|
|
111
|
+
if [[ -f "${ROOT}/.importlinter" ]] || grep -q "^\[importlinter\]" "${ROOT}/pyproject.toml" 2>/dev/null; then
|
|
112
|
+
LOG="${REPORT_DIR}/import-linter.log"
|
|
113
|
+
if command -v lint-imports >/dev/null 2>&1; then
|
|
114
|
+
if (cd "$ROOT" && lint-imports) > "$LOG" 2>&1; then
|
|
115
|
+
emit_result import-linter pass 0 "$LOG"
|
|
116
|
+
exit 0
|
|
117
|
+
else
|
|
118
|
+
VIOL=$(grep -c "BROKEN" "$LOG" || echo 0)
|
|
119
|
+
emit_result import-linter fail "$VIOL" "$LOG"
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
else
|
|
123
|
+
emit_result import-linter missing-tool 0 "$LOG"
|
|
124
|
+
exit 2
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# 3. deptrac (PHP)
|
|
129
|
+
if [[ -f "${ROOT}/deptrac.yaml" ]]; then
|
|
130
|
+
LOG="${REPORT_DIR}/deptrac.log"
|
|
131
|
+
if [[ -x "${ROOT}/vendor/bin/deptrac" ]]; then
|
|
132
|
+
if (cd "$ROOT" && vendor/bin/deptrac analyse --no-progress) > "$LOG" 2>&1; then
|
|
133
|
+
emit_result deptrac pass 0 "$LOG"
|
|
134
|
+
exit 0
|
|
135
|
+
else
|
|
136
|
+
VIOL=$(grep -Ec "violation" "$LOG" || echo 0)
|
|
137
|
+
emit_result deptrac fail "$VIOL" "$LOG"
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
else
|
|
141
|
+
emit_result deptrac missing-tool 0 "$LOG"
|
|
142
|
+
exit 2
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# 4. arch-go
|
|
147
|
+
if [[ -f "${ROOT}/arch-go.yml" ]]; then
|
|
148
|
+
LOG="${REPORT_DIR}/arch-go.log"
|
|
149
|
+
if command -v arch-go >/dev/null 2>&1; then
|
|
150
|
+
if (cd "$ROOT" && arch-go) > "$LOG" 2>&1; then
|
|
151
|
+
emit_result arch-go pass 0 "$LOG"
|
|
152
|
+
exit 0
|
|
153
|
+
else
|
|
154
|
+
VIOL=$(grep -c "Violation" "$LOG" || echo 0)
|
|
155
|
+
emit_result arch-go fail "$VIOL" "$LOG"
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
else
|
|
159
|
+
emit_result arch-go missing-tool 0 "$LOG"
|
|
160
|
+
exit 2
|
|
161
|
+
fi
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# 5. ArchUnit (Java/Kotlin) — run via build tool
|
|
165
|
+
if [[ -f "${ROOT}/build.gradle" || -f "${ROOT}/build.gradle.kts" ]] && \
|
|
166
|
+
grep -rq "com.tngtech.archunit" "${ROOT}" --include="*.gradle*" 2>/dev/null; then
|
|
167
|
+
LOG="${REPORT_DIR}/archunit.log"
|
|
168
|
+
if [[ -x "${ROOT}/gradlew" ]]; then
|
|
169
|
+
if (cd "$ROOT" && ./gradlew test --tests '*ArchitectureTest*' --tests '*ArchTest*') > "$LOG" 2>&1; then
|
|
170
|
+
emit_result archunit pass 0 "$LOG"
|
|
171
|
+
exit 0
|
|
172
|
+
else
|
|
173
|
+
VIOL=$(grep -Ec "violated|FAILED" "$LOG" || echo 0)
|
|
174
|
+
emit_result archunit fail "$VIOL" "$LOG"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
else
|
|
178
|
+
emit_result archunit missing-tool 0 "$LOG"
|
|
179
|
+
exit 2
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# No tool / config found
|
|
184
|
+
emit_result none not-configured 0 "$REPORT_DIR/none.log"
|
|
185
|
+
exit 2
|