testsAndMisc/scripts/check_ai_evidence.sh

119 lines
3.5 KiB
Bash
Executable File

#!/bin/bash
# Enforce evidence artifacts for commits that touch source code.
set -euo pipefail
readonly EVIDENCE_GLOB='docs/superpowers/evidence/*.json'
has_code_changes() {
git diff --cached --name-only --diff-filter=ACMR | grep -Eq '\.(py|sh|c|h|cpp|hpp|cc|go|rs|ts|tsx|js|jsx|dart)$'
}
find_staged_evidence_files() {
git diff --cached --name-only --diff-filter=ACMR | grep -E '^docs/superpowers/evidence/.*\.json$' || true
}
validate_json_schema() {
local file_path="$1"
python - "$file_path" <<'PY'
import json
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
try:
data = json.loads(path.read_text(encoding="utf-8"))
except Exception as exc: # pragma: no cover - hook error path
raise SystemExit(f"{path}: invalid JSON ({exc})")
required = [
"intent",
"scope",
"changes",
"verification",
"risks",
"rollback",
]
missing = [key for key in required if key not in data]
if missing:
raise SystemExit(f"{path}: missing required keys: {', '.join(missing)}")
if not isinstance(data["intent"], str) or not data["intent"].strip():
raise SystemExit(f"{path}: intent must be a non-empty string")
for key in ("scope", "changes", "risks", "rollback"):
value = data[key]
if not isinstance(value, list) or not value:
raise SystemExit(f"{path}: {key} must be a non-empty list")
if any(not isinstance(item, str) or not item.strip() for item in value):
raise SystemExit(f"{path}: {key} entries must be non-empty strings")
verification = data["verification"]
if not isinstance(verification, list) or not verification:
raise SystemExit(f"{path}: verification must be a non-empty list")
required_verification_fields = {"command", "result", "evidence"}
for index, item in enumerate(verification):
if not isinstance(item, dict):
raise SystemExit(f"{path}: verification[{index}] must be an object")
missing_fields = required_verification_fields - item.keys()
if missing_fields:
missing_joined = ", ".join(sorted(missing_fields))
raise SystemExit(
f"{path}: verification[{index}] missing fields: {missing_joined}"
)
for field in required_verification_fields:
value = item[field]
if not isinstance(value, str) or not value.strip():
raise SystemExit(
f"{path}: verification[{index}].{field} must be a non-empty string"
)
content_lower = path.read_text(encoding="utf-8").lower()
for phrase in ("should work", "probably fine", "seems right"):
if phrase in content_lower:
raise SystemExit(
f"{path}: contains rationalization phrase '{phrase}', replace with evidence"
)
print(f"{path}: schema OK")
PY
}
main() {
if ! has_code_changes; then
echo "✓ No code changes detected; evidence artifact not required"
exit 0
fi
local evidence_files
evidence_files="$(find_staged_evidence_files)"
if [[ -z "$evidence_files" ]]; then
echo "❌ Code changes detected, but no staged evidence artifact found."
echo " Required: ${EVIDENCE_GLOB}"
echo " Tip: copy docs/superpowers/evidence/template.json and fill it in."
exit 1
fi
local failed=0
while IFS= read -r file_path; do
[[ -z "$file_path" ]] && continue
if ! validate_json_schema "$file_path"; then
failed=1
fi
done <<< "$evidence_files"
if [[ $failed -eq 1 ]]; then
echo "❌ Evidence artifact validation failed"
exit 1
fi
echo "✓ Evidence artifact checks passed"
}
main "$@"