Answers the 3 adoption questions (Plan-21 follow-up):
1) LEVEL SELECTION (4 packaging levels, packaging/levels.json):
- install.sh --level core|devkit; platform refused (preview service),
enterprise refused (future). Level recorded in .casan-level.
- casan init --level 1..4: L1=gate+Plan-20 hooks only; L2=+CI+domain-pack;
L3=L2 base+preview note; L4=refused. New `casan level show|set`.
- levels.json core now includes adapters/ + schemas/ + install scripts.
2) EXISTING SHELLS (agents/skills): init MERGES Plan-20 hooks into an existing
.claude/settings.json and .codex/{hooks.json,config.toml} idempotently
instead of clobbering — preserves the project's own hooks/agents/skills and
unrelated keys. Re-running never duplicates the CASAN hook.
3) NO RE-INDEX / NO SHELL REWRITE: init only adds config; it does not parse or
index code and does not rewrite the project shell.
Safety fixes after a test accidentally ran init in the real repo:
- launcher shim now SELF-LOCATES its install from its own path (no ambient
CASAN_HOME cross-talk).
- casan init REFUSES to adopt a CASAN source hub into itself (--force to
override), so the Plan-20 hooks can't block the developing agent.
- test always runs init inside throwaway dirs; +source-hub guard test.
hybrid-install-tests.sh: 41/41 PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
129 lines
6.0 KiB
Bash
Executable File
129 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# CASAN — lightweight CLI wrapper (Level 1 Core Harness).
|
|
#
|
|
# Locates the harness package relative to this script (works both in the full
|
|
# source hub and in an extracted casan-core / casan-devkit bundle) and dispatches
|
|
# to the harness bash entrypoints. No dependencies beyond bash + python3.
|
|
set -uo pipefail
|
|
|
|
# --- locate the harness root (dir containing scripts/bash/casan-harness.sh) ----
|
|
_self="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
_find_harness() {
|
|
# 1) sibling packages/casan-harness (source hub or bundle root layout)
|
|
local c
|
|
for c in "$_self/../packages/casan-harness" "$_self/../casan-harness" "$_self/packages/casan-harness"; do
|
|
[[ -f "$c/scripts/bash/casan-harness.sh" ]] && { (cd "$c" && pwd); return 0; }
|
|
done
|
|
# 2) walk up looking for it
|
|
local d="$_self"
|
|
while [[ "$d" != "/" ]]; do
|
|
[[ -f "$d/packages/casan-harness/scripts/bash/casan-harness.sh" ]] && { echo "$d/packages/casan-harness"; return 0; }
|
|
d="$(dirname "$d")"
|
|
done
|
|
return 1
|
|
}
|
|
HARNESS="${CASAN_HARNESS_ROOT:-$(_find_harness)}"
|
|
if [[ -z "${HARNESS:-}" || ! -d "$HARNESS" ]]; then
|
|
echo "casan: cannot locate packages/casan-harness (set CASAN_HARNESS_ROOT)" >&2
|
|
exit 1
|
|
fi
|
|
BASH_DIR="$HARNESS/scripts/bash"
|
|
TESTS_DIR="$HARNESS/tests"
|
|
CASAN_APP_ROOT="${CASAN_APP_ROOT:-$(cd "$HARNESS/../.." && pwd)}"
|
|
VERSION_FILE="$_self/../VERSION"
|
|
[[ -f "$VERSION_FILE" ]] || VERSION_FILE="$HARNESS/../../VERSION"
|
|
|
|
version() { printf 'casan %s\n' "$( [[ -f "$VERSION_FILE" ]] && cat "$VERSION_FILE" || echo 'unknown')"; }
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
casan — CASAN governance harness CLI ($(version))
|
|
|
|
Usage: casan <command> [args]
|
|
|
|
Commands:
|
|
init [--level 1..4] [--project id] Adopt CASAN into THIS project (config only, hybrid model)
|
|
level <show|set 1..4> Show / change the project's packaging level
|
|
verify-harness Verify the resolved harness matches the project pin
|
|
run <in> <out> [action] [-- cmd...] Run a step through the harness (H4→H5→H6→exec→H4-out)
|
|
gate Run the full CI harness gate (all suites)
|
|
test Run the core harness test suite (run-casan4)
|
|
verify Verify audit chain + tool audit + policy bundle
|
|
reuse Verify multi-project harness reuse (registry)
|
|
project validate [--manifest path] Validate a project manifest and quality profile
|
|
project init <scaffold args...> Create an idempotent NestJS/React project shell
|
|
prompt verify Verify the adopted prompt-enforcement contract
|
|
prompt trace <trace-id> Verify that a prompt trace is H1-H7 certified
|
|
pipeline [--manifest path] Run the manifest-driven SRS→test pipeline
|
|
dashboard [port] Serve the AgentOps dashboard (default 8787)
|
|
version Print version
|
|
help This help
|
|
|
|
Env: CASAN_HARNESS_ROOT overrides harness location.
|
|
Harness: $HARNESS
|
|
EOF
|
|
}
|
|
|
|
cmd="${1:-help}"; shift || true
|
|
DEVKIT_ROOT="${CASAN_DEVKIT_ROOT:-$CASAN_APP_ROOT/packages/casan-devkit}"
|
|
[[ -f "$DEVKIT_ROOT/casan-init.py" ]] || DEVKIT_ROOT="$HARNESS/../casan-devkit"
|
|
case "$cmd" in
|
|
init)
|
|
[[ -f "$DEVKIT_ROOT/casan-init.py" ]] || { echo "casan: init requires the casan-devkit package" >&2; exit 1; }
|
|
exec python3 "$DEVKIT_ROOT/casan-init.py" init "$@" ;;
|
|
verify-harness)
|
|
[[ -f "$DEVKIT_ROOT/casan-init.py" ]] || { echo "casan: verify-harness requires the casan-devkit package" >&2; exit 1; }
|
|
exec python3 "$DEVKIT_ROOT/casan-init.py" verify "$@" ;;
|
|
level)
|
|
[[ -f "$DEVKIT_ROOT/casan-init.py" ]] || { echo "casan: level requires the casan-devkit package" >&2; exit 1; }
|
|
sub="${1:-show}"; shift || true
|
|
case "$sub" in
|
|
show) exec python3 "$DEVKIT_ROOT/casan-init.py" level --show "$@" ;;
|
|
set) n="${1:-devkit}"; shift || true
|
|
exec python3 "$DEVKIT_ROOT/casan-init.py" init --level "$n" "$@" ;;
|
|
*) echo "casan: usage: casan level <show|set <1..4>>" >&2; exit 64 ;;
|
|
esac ;;
|
|
run) exec bash "$BASH_DIR/casan-harness.sh" "$@" ;;
|
|
gate)
|
|
if [[ -n "${CASAN_PROJECT_MANIFEST:-}${CASAN_PROJECT_ID:-}" ]]; then
|
|
exec bash "$BASH_DIR/project-gate.sh" "$@"
|
|
fi
|
|
exec bash "$BASH_DIR/ci-harness-gate.sh" "$@" ;;
|
|
test) exec bash "$TESTS_DIR/run-casan4-harness-tests.sh" "$@" ;;
|
|
verify)
|
|
rc=0
|
|
bash "$BASH_DIR/verify-audit-chain.sh" "$@" || rc=$?
|
|
bash "$BASH_DIR/verify-tool-audit.sh" || rc=$?
|
|
bash "$BASH_DIR/sign-policy-bundle.sh" verify || rc=$?
|
|
exit $rc ;;
|
|
reuse) exec bash "$BASH_DIR/verify-harness-reuse.sh" "$@" ;;
|
|
project)
|
|
sub="${1:-help}"; shift || true
|
|
case "$sub" in
|
|
validate) exec python3 "$BASH_DIR/project_manifest.py" validate --root "$CASAN_APP_ROOT" "$@" ;;
|
|
init)
|
|
DEVKIT="${CASAN_DEVKIT_ROOT:-$CASAN_APP_ROOT/packages/casan-devkit}"
|
|
[[ -f "$DEVKIT/project-scaffold.py" ]] || { echo "casan: project init requires the casan-devkit bundle" >&2; exit 1; }
|
|
exec python3 "$DEVKIT/project-scaffold.py" "$@" ;;
|
|
*) echo "casan: usage: casan project <validate|init>" >&2; exit 64 ;;
|
|
esac ;;
|
|
prompt)
|
|
sub="${1:-help}"; shift || true
|
|
case "$sub" in
|
|
verify) exec bash "$BASH_DIR/prompt-enforcement-verify.sh" --root "$CASAN_APP_ROOT" "$@" ;;
|
|
trace)
|
|
trace_id="${1:-}"; [[ -n "$trace_id" ]] || { echo "casan: usage: casan prompt trace <trace-id>" >&2; exit 64; }
|
|
shift
|
|
exec bash "$BASH_DIR/prompt-enforcement-verify.sh" --root "$CASAN_APP_ROOT" --trace-id "$trace_id" "$@" ;;
|
|
*) echo "casan: usage: casan prompt <verify|trace>" >&2; exit 64 ;;
|
|
esac ;;
|
|
pipeline)
|
|
RUNNER="$CASAN_APP_ROOT/scripts/run-casan-pipeline.mjs"
|
|
[[ -f "$RUNNER" ]] || { echo "casan: pipeline runner is not installed" >&2; exit 1; }
|
|
exec node "$RUNNER" "$@" ;;
|
|
dashboard) exec bash "$BASH_DIR/dashboard-serve.sh" "$@" ;;
|
|
version|-v|--version) version ;;
|
|
help|-h|--help) usage ;;
|
|
*) echo "casan: unknown command '$cmd'" >&2; usage >&2; exit 64 ;;
|
|
esac
|