check-tools.sh 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. #!/usr/bin/env bash
  2. #
  3. # check-tools.sh — enforce a single source of truth for the supported tool set.
  4. #
  5. # tools.json (repo root) is canonical. This script fails if any of the following
  6. # disagree with it:
  7. # 1. ALL_TOOLS in scripts/install.sh (exact set — every installable tool)
  8. # 2. valid_tools in scripts/convert.sh (every converter tool must exist in tools.json)
  9. # 3. Every tools.json entry has id, label, kebab, format, installKind, dest
  10. # (installKind is one of: per-agent | roster | plugin)
  11. #
  12. # Add a tool: add an entry to tools.json, a convert_<tool> (or reuse a `format`)
  13. # in convert.sh, and an install_<tool> in install.sh, then run this script — it
  14. # tells you every place that must agree. No deps beyond bash 3.2 + coreutils
  15. # (no jq) so it runs the same on macOS and CI. Mirrors scripts/check-divisions.sh.
  16. #
  17. # Usage: ./scripts/check-tools.sh
  18. set -euo pipefail
  19. cd "$(dirname "$0")/.."
  20. JSON="tools.json"
  21. errors=0
  22. fail() { echo "ERROR $*"; errors=$((errors + 1)); }
  23. # --- helpers ---------------------------------------------------------------
  24. # Canonical tool keys (kebab) from tools.json: the keys at 4-space indent inside
  25. # the "tools" object. One tool per line keeps the nested "scope"/"detect"/…
  26. # objects off the line start, so only tool keys match.
  27. canonical() {
  28. awk '/"tools"[[:space:]]*:[[:space:]]*\{/{f=1; next} f' "$JSON" \
  29. | grep -oE '^ "[a-z0-9-]+"' \
  30. | sed -E 's/.*"([a-z0-9-]+)".*/\1/' | sort -u
  31. }
  32. # Entries of a single-line bash array NAME=( ... ) (quoted or bare), one per line.
  33. bash_array() {
  34. grep -oE "$2=\([^)]*\)" "$1" | head -1 | sed -E "s/^$2=\(//; s/\)\$//" \
  35. | tr -d '"' | tr ' \t' '\n\n' | grep -E '^[a-z0-9-]+$' | sort -u
  36. }
  37. # --- checks ----------------------------------------------------------------
  38. [[ -f "$JSON" ]] || { echo "ERROR $JSON not found at repo root"; exit 1; }
  39. canon="$(canonical)"
  40. # 1. tools.json keys == ALL_TOOLS in install.sh (exact, both directions).
  41. all_tools="$(bash_array scripts/install.sh ALL_TOOLS)"
  42. missing="$(comm -23 <(echo "$canon") <(echo "$all_tools"))"
  43. extra="$(comm -13 <(echo "$canon") <(echo "$all_tools"))"
  44. [[ -n "$missing" ]] && fail "scripts/install.sh ALL_TOOLS is missing tool(s) in $JSON: $(echo $missing)"
  45. [[ -n "$extra" ]] && fail "scripts/install.sh ALL_TOOLS has tool(s) not in $JSON: $(echo $extra)"
  46. # 2. Every converter in convert.sh must exist in tools.json (subset; identity
  47. # tools like claude-code/copilot are install-only, so it's a subset not equal).
  48. conv="$(bash_array scripts/convert.sh valid_tools | grep -v '^all$' || true)"
  49. notin="$(comm -13 <(echo "$canon") <(echo "$conv"))"
  50. [[ -n "$notin" ]] && fail "scripts/convert.sh converts tool(s) absent from $JSON: $(echo $notin)"
  51. # 3. Required fields per entry (each tool is one line). aa converts+installs
  52. # every listed tool, so every entry must carry format + dest — there is no
  53. # "half-described" tool. (Renderer coverage is a consumer's concern, derived
  54. # from `format`; the catalog itself carries no such flag.)
  55. while IFS= read -r t; do
  56. [[ -n "$t" ]] || continue
  57. line="$(grep -E "^ \"$t\"[[:space:]]*:" "$JSON")"
  58. for field in id label kebab format installKind dest; do
  59. echo "$line" | grep -qE "\"$field\":" || fail "tool '$t' in $JSON is missing \"$field\""
  60. done
  61. # installKind is the install MECHANISM (upstream truth), not app state: it must
  62. # be one of the known kinds so every consumer can branch on it deterministically.
  63. if echo "$line" | grep -qE '"installKind":'; then
  64. echo "$line" | grep -qE '"installKind":[[:space:]]*"(per-agent|roster|plugin)"' \
  65. || fail "tool '$t' in $JSON has an invalid installKind (must be per-agent|roster|plugin)"
  66. fi
  67. done < <(echo "$canon")
  68. # --- result ----------------------------------------------------------------
  69. count="$(echo "$canon" | grep -c .)"
  70. if [[ $errors -gt 0 ]]; then
  71. echo ""
  72. echo "FAILED: $errors tool consistency error(s). $JSON is the source of truth."
  73. exit 1
  74. fi
  75. echo "PASSED: $count tools consistent across $JSON, install.sh, and convert.sh."