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.
@@ -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,5 @@
1
+ """Entry point for ``python -m intent_audit_harness``."""
2
+ from .cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -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