Give later waves a measurable import/table/path baseline without changing business behavior or occupying the integration slot. Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
4.1 KiB
Bash
Executable File
152 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Architecture boundary check. Wave 0: warn by default, never rewrite code.
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
BOUNDARIES="${ROOT}/.doc/system-design/module-boundaries.yaml"
|
|
MODE="warn"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--mode) MODE="${2:-warn}"; shift 2 ;;
|
|
--mode=*) MODE="${1#--mode=}"; shift ;;
|
|
*) echo "usage: $0 [--mode warn|block]" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ "${MODE}" != "warn" && "${MODE}" != "block" ]]; then
|
|
echo "mode must be warn or block" >&2
|
|
exit 2
|
|
fi
|
|
|
|
violations=0
|
|
warn() {
|
|
printf 'WARN %s\n' "$1"
|
|
violations=$((violations + 1))
|
|
}
|
|
|
|
lookup_imports() {
|
|
local pkg="$1"
|
|
awk -v pkg="$pkg" '
|
|
$1=="imports:" {sec=1; next}
|
|
sec && $1 ~ /^(table_owners|path_owners):/ { exit }
|
|
sec && $1 ~ /^[A-Za-z0-9_]+:$/ || sec && $1 ~ /^[A-Za-z0-9_]+:/ {
|
|
key=$1; sub(":","",key)
|
|
$1=""
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0)
|
|
gsub(/"/,"",$0)
|
|
if (key==pkg) { print $0; exit }
|
|
next
|
|
}
|
|
' "${BOUNDARIES}"
|
|
}
|
|
|
|
lookup_owners() {
|
|
local section="$1" key="$2"
|
|
awk -v secname="$section" -v key="$key" '
|
|
$0 ~ "^"secname":" {sec=1; next}
|
|
sec && $1 ~ /^(imports|table_owners|path_owners):/ && $1 !~ "^"secname":" { exit }
|
|
sec && $1 ~ /^[A-Za-z0-9_./]+:/ {
|
|
k=$1; sub(":","",k)
|
|
$1=""
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0)
|
|
if (k==key) { print $0; exit }
|
|
next
|
|
}
|
|
' "${BOUNDARIES}"
|
|
}
|
|
|
|
allowed_has() {
|
|
local needle="$1"
|
|
local list="$2"
|
|
[[ -z "${list}" ]] && return 1
|
|
IFS=',' read -r -a parts <<< "${list}"
|
|
for p in "${parts[@]}"; do
|
|
p="${p// /}"
|
|
[[ "${p}" == "${needle}" ]] && return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
root_pkg() {
|
|
local rel="${1#chat-agent-system/internal/}"
|
|
echo "${rel%%/*}"
|
|
}
|
|
|
|
echo "arch-check mode=${MODE}"
|
|
echo "boundaries=${BOUNDARIES}"
|
|
|
|
while IFS= read -r line; do
|
|
[[ -z "${line}" ]] && continue
|
|
pkg="$(echo "${line}" | awk '{print $1}')"
|
|
root="$(root_pkg "${pkg}")"
|
|
[[ "${root}" == "workflow" ]] && continue
|
|
allow="$(lookup_imports "${root}")"
|
|
deps="$(echo "${line}" | awk '{for(i=2;i<=NF;i++) print $i}')"
|
|
for dep in ${deps}; do
|
|
[[ "${dep}" != chat-agent-system/internal/* ]] && continue
|
|
droot="$(root_pkg "${dep}")"
|
|
[[ "${droot}" == "${root}" ]] && continue
|
|
if ! allowed_has "${droot}" "${allow}"; then
|
|
warn "R-import ${root} -> ${droot} not in baseline allowlist"
|
|
fi
|
|
done
|
|
if [[ "${root}" == "contracts" ]]; then
|
|
for dep in ${deps}; do
|
|
if [[ "${dep}" == chat-agent-system/internal/* && "${dep}" != chat-agent-system/internal/contracts ]]; then
|
|
warn "R1 contracts imports ${dep}"
|
|
fi
|
|
done
|
|
fi
|
|
done < <(go list -f '{{.ImportPath}} {{join .Imports " "}}' ./internal/...)
|
|
|
|
declare -A seen_table
|
|
while IFS= read -r table; do
|
|
owners="$(lookup_owners table_owners "${table}")"
|
|
while IFS= read -r file; do
|
|
[[ -z "${file}" ]] && continue
|
|
rel="${file#${ROOT}/internal/}"
|
|
pkg="${rel%%/*}"
|
|
key="${table}|${pkg}"
|
|
[[ -n "${seen_table[$key]+x}" ]] && continue
|
|
seen_table[$key]=1
|
|
if ! allowed_has "${pkg}" "${owners}"; then
|
|
warn "R5 table ${table} mentioned in ${pkg} (target owner ${owners})"
|
|
fi
|
|
done < <(rg -l --glob '*.go' --glob '!*_test.go' --fixed-strings "${table}" "${ROOT}/internal" || true)
|
|
done <<'TABLES'
|
|
document_records
|
|
chat_messages
|
|
chat_turns
|
|
freeform_documents
|
|
task_runs
|
|
TABLES
|
|
|
|
declare -A seen_path
|
|
while IFS= read -r lit; do
|
|
owners="$(lookup_owners path_owners "${lit}")"
|
|
while IFS= read -r file; do
|
|
[[ -z "${file}" ]] && continue
|
|
rel="${file#${ROOT}/internal/}"
|
|
pkg="${rel%%/*}"
|
|
key="${lit}|${pkg}"
|
|
[[ -n "${seen_path[$key]+x}" ]] && continue
|
|
seen_path[$key]=1
|
|
if ! allowed_has "${pkg}" "${owners}"; then
|
|
warn "R6 path ${lit} mentioned in ${pkg} (target owner ${owners})"
|
|
fi
|
|
done < <(rg -l --glob '*.go' --glob '!*_test.go' --fixed-strings "${lit}" "${ROOT}/internal" || true)
|
|
done <<'PATHS'
|
|
output.md
|
|
data/dev
|
|
workspaces/
|
|
PATHS
|
|
|
|
echo "violations=${violations}"
|
|
if [[ "${MODE}" == "block" && "${violations}" -gt 0 ]]; then
|
|
echo "arch-check block: failing" >&2
|
|
exit 1
|
|
fi
|
|
echo "arch-check ${MODE}: recorded ${violations} baseline findings"
|
|
exit 0
|