syntaxai/tdd.md · main · tools / sama-cli / src / d21_main.sh
#!/usr/bin/env bash
# d21 — entry: the SAMA v2 shell-CLI dispatcher. Wires the seven §4
# checks, the doctor diagnostic, and the graph adapter together into
# the user-facing `sama` command. This is the only layer permitted
# to read argv, exit, or write to stdout for user consumption.
#
# Invoked by the `tools/sama-cli/sama` wrapper. The wrapper sources
# this file with SAMA_SRC_DIR pointing at tools/sama-cli/src.
# sama-import: a31_constants.sh
# sama-import: b32_utils.sh
# sama-import: b32_checks.sh
# sama-import: c14_graph.sh
set -u
# SAMA_SRC_DIR is exported by the entry wrapper. Re-source siblings
# defensively in case d21 is invoked directly.
SAMA_SRC_DIR="${SAMA_SRC_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)}"
# shellcheck disable=SC1091
. "$SAMA_SRC_DIR/a31_constants.sh"
# shellcheck disable=SC1091
. "$SAMA_SRC_DIR/b32_utils.sh"
# shellcheck disable=SC1091
. "$SAMA_SRC_DIR/b32_checks.sh"
# shellcheck disable=SC1091
. "$SAMA_SRC_DIR/c14_graph.sh"
# Disable color when stdout isn't a TTY (so cross-verify diffs stay clean).
if [ ! -t 1 ]; then
sama_color_disable
fi
# Respect the NO_COLOR convention (https://no-color.org/).
if [ -n "${NO_COLOR:-}" ]; then
sama_color_disable
fi
print_usage() {
cat <<'EOF'
sama — SAMA v2 verifier (shell implementation)
USAGE
sama check [check-name] Run all seven §4 checks (or just one)
sama graph [out.dot] Emit graphviz .dot of the import graph
sama doctor List tools the verifier depends on
sama --version Print the verifier version
sama --help This message
CHECK NAMES (for `sama check <name>`)
sorted, architecture, modeled-tests, modeled-boundary,
atomic, law, consistency
FLAGS
--profile=PATH Path to sama.profile.toml (default: ./sama.profile.toml)
--src=PATH Path to src directory (default: ./src)
--summary Emit only the "N / 7" verdict line (for scripting)
EXAMPLES
sama check # run all checks in the current repo
sama check --src=tools/sama-cli/src --profile=tools/sama-cli/sama.profile.toml
sama graph /tmp/graph.dot
sama doctor
The shell verifier is the independent oracle for SAMA v2: a second
implementation in a different language that should produce the same
7/7 ✓ verdict as the TypeScript verifier at src/b32_sama_v2_verify.ts.
EOF
}
print_version() {
echo "sama (sama-cli shell verifier) — SAMA v2.0"
}
# Parse --key=value style flags from argv. Sets the globals
# OPT_PROFILE, OPT_SRC, OPT_SUMMARY, and leaves POSITIONAL args
# in the POSITIONAL array.
OPT_PROFILE=""
OPT_SRC=""
OPT_SUMMARY=0
POSITIONAL=()
parse_flags() {
POSITIONAL=()
local arg
for arg in "$@"; do
case "$arg" in
--profile=*) OPT_PROFILE="${arg#--profile=}" ;;
--src=*) OPT_SRC="${arg#--src=}" ;;
--summary) OPT_SUMMARY=1 ;;
--no-color) sama_color_disable ;;
--help|-h) print_usage; exit 0 ;;
--version) print_version; exit 0 ;;
--*) echo "unknown flag: $arg" >&2; exit 2 ;;
*) POSITIONAL+=("$arg") ;;
esac
done
}
# Resolve repo root + src dir + profile path based on flags or defaults.
resolve_paths() {
local profile_path="$OPT_PROFILE"
local src_path="$OPT_SRC"
if [ -z "$profile_path" ]; then
profile_path="./sama.profile.toml"
fi
if [ -z "$src_path" ]; then
src_path="./src"
fi
# Absolute-ify
profile_path="$(cd "$(dirname "$profile_path")" 2>/dev/null && pwd)/$(basename "$profile_path")"
src_path="$(cd "$src_path" 2>/dev/null && pwd)"
if [ -z "$profile_path" ] || [ ! -f "$profile_path" ]; then
echo "error: profile not found: $OPT_PROFILE (looked at $profile_path)" >&2
exit 2
fi
if [ -z "$src_path" ] || [ ! -d "$src_path" ]; then
echo "error: src dir not found: $OPT_SRC (looked at $src_path)" >&2
exit 2
fi
RESOLVED_PROFILE="$profile_path"
# Repo root = parent of src dir.
RESOLVED_REPO_ROOT="$(cd "$src_path/.." && pwd)"
RESOLVED_SRC_REL="${src_path#${RESOLVED_REPO_ROOT}/}"
RESOLVED_SRC_DIR="$src_path"
}
cmd_check() {
resolve_paths
parse_profile "$RESOLVED_PROFILE"
collect_input "$RESOLVED_REPO_ROOT" "$RESOLVED_SRC_DIR"
local target=""
if [ "${#POSITIONAL[@]}" -gt 1 ]; then
target="${POSITIONAL[1]}"
fi
local passed=0
local total=0
local results=()
# Order matches src/b32_sama_v2_verify.ts.
local checks_meta=(
"1::Sorted::run_check_sorted::sorted"
"2::Architecture::run_check_architecture::architecture"
"3::Modeled (tests)::run_check_modeled_tests::modeled-tests"
"4::Modeled (boundary)::run_check_modeled_boundary::modeled-boundary"
"5::Atomic::run_check_atomic::atomic"
"6::Law (§1.2)::run_check_law::law"
"7::Consistency (§3)::run_check_consistency::consistency"
)
if [ "$OPT_SUMMARY" = "0" ]; then
print_section_header "SAMA v2 verifier — profile: $PROFILE_NAME"
printf " Source tree: %s\n" "$RESOLVED_SRC_REL"
printf " Files examined: %d (sources + tests)\n" "${#ALL_FILES[@]}"
fi
local meta
for meta in "${checks_meta[@]}"; do
local id name fn short
id="$(echo "$meta" | awk -F '::' '{print $1}')"
name="$(echo "$meta" | awk -F '::' '{print $2}')"
fn="$(echo "$meta" | awk -F '::' '{print $3}')"
short="$(echo "$meta" | awk -F '::' '{print $4}')"
if [ -n "$target" ] && [ "$target" != "$short" ]; then
continue
fi
total=$((total + 1))
"$fn"
local violations="${#CHECK_VIOLATIONS[@]}"
if [ "$violations" -eq 0 ]; then
passed=$((passed + 1))
fi
if [ "$OPT_SUMMARY" = "0" ]; then
print_check_verdict "$id" "$name" "$CHECK_EXAMINED" "$violations"
local v
for v in "${CHECK_VIOLATIONS[@]}"; do
local vf vd
vf="${v%%::*}"
vd="${v#*::}"
print_violation "$vf" "$vd"
done
fi
done
echo
if [ "$passed" = "$total" ]; then
_c "$COLOR_GREEN"
printf " %d / %d %s — all checks passed\n" "$passed" "$total" "$GLYPH_PASS"
_c "$COLOR_RESET"
else
_c "$COLOR_RED"
printf " %d / %d %s — %d check(s) failed\n" "$passed" "$total" "$GLYPH_FAIL" "$((total - passed))"
_c "$COLOR_RESET"
fi
echo
if [ "$passed" = "$total" ]; then
return 0
fi
return 1
}
cmd_graph() {
resolve_paths
parse_profile "$RESOLVED_PROFILE"
collect_input "$RESOLVED_REPO_ROOT" "$RESOLVED_SRC_DIR"
local out_dot="${POSITIONAL[1]:-/tmp/sama-graph.dot}"
local out_png="${out_dot%.dot}.png"
render_graph "$RESOLVED_REPO_ROOT" "$RESOLVED_SRC_DIR" "$out_dot" "$out_png"
}
cmd_doctor() {
print_section_header "sama doctor — tool availability"
local entry tool req status version
for entry in $SAMA_CLI_TOOLS; do
tool="${entry%:*}"
req="${entry#*:}"
if command -v "$tool" > /dev/null 2>&1; then
version="$("$tool" --version 2>/dev/null | head -1)"
[ -z "$version" ] && version="(version unknown)"
_c "$COLOR_GREEN"
printf " %s %s [%s]\n" "$GLYPH_PASS" "$tool" "$req"
_c "$COLOR_DIM"
printf " %s\n" "$version"
_c "$COLOR_RESET"
else
if [ "$req" = "required" ]; then
_c "$COLOR_RED"
printf " %s %s [required] — NOT FOUND\n" "$GLYPH_FAIL" "$tool"
_c "$COLOR_RESET"
else
_c "$COLOR_YELLOW"
printf " - %s [optional] — not installed\n" "$tool"
_c "$COLOR_RESET"
fi
fi
done
echo
}
# Entry: dispatch on the first POSITIONAL.
sama_main() {
parse_flags "$@"
if [ "${#POSITIONAL[@]}" -eq 0 ]; then
print_usage
exit 0
fi
local subcmd="${POSITIONAL[0]}"
case "$subcmd" in
check) cmd_check ;;
graph) cmd_graph ;;
doctor) cmd_doctor ;;
help) print_usage ;;
*) echo "unknown command: $subcmd" >&2; print_usage >&2; exit 2 ;;
esac
}