264 lines
12 KiB
Bash
Executable File
264 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
# test-repo-bootstrap.sh — Automated tests for the Repository Intelligence Bootstrap
|
|
#
|
|
# Exit codes: 0 = all pass, 1 = any failure (PASS/FAIL per test, summary at end)
|
|
# Conventions follow the existing verify-permission-patterns.sh style:
|
|
# temp fixture dirs, PASS/FAIL echo, deterministic assertions, no external deps
|
|
# beyond bash, git (optional), and the repo-bootstrap.sh script under test.
|
|
#
|
|
# Acceptance matrix (10 items):
|
|
# 1. .opencode created for new repository
|
|
# 2. bootstrap is idempotent (second run = no drift / no change)
|
|
# 3. existing .opencode content (manual) is preserved
|
|
# 4. relevant repository skills are generated (node → build-and-test; Dockerfile → deployment)
|
|
# 5. orchestrator.md contains repository-context-before-dispatch stage (structural)
|
|
# 6. every agents/*.md references .opencode (consumes context)
|
|
# 7. stale knowledge is detectable (status exits 1 when fingerprint changes)
|
|
# 8. unrelated repositories do not inherit hardcoded assumptions (generic scaffold in plain dir)
|
|
# 9. existing agent workflows still work (install.sh to temp target)
|
|
# 10. malformed .opencode intelligence is handled safely (corrupted meta → re-bootstrap works)
|
|
|
|
TEAM_ROOT="${TEAM_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
BOOTSTRAP="$TEAM_ROOT/scripts/repo-bootstrap.sh"
|
|
|
|
PASS=0; FAIL=0
|
|
T="$(mktemp -d)"
|
|
trap 'rm -rf "$T"' EXIT
|
|
|
|
ok() { PASS=$((PASS+1)); echo "PASS $1"; }
|
|
fail(){ FAIL=$((FAIL+1)); echo "FAIL $1 — $2" >&2; }
|
|
skip(){ PASS=$((PASS+1)); echo "SKIP $1 — $2"; }
|
|
assert_file() { [ -f "$1" ] && return 0; return 1; }
|
|
assert_dir() { [ -d "$1" ] && return 0; return 1; }
|
|
assert_contains() { grep -qF "$2" "$1" 2>/dev/null && return 0; return 1; }
|
|
|
|
# Helper: run bootstrap/status against a specific root (not cwd)
|
|
# Signature: RB <root> <command> [args...] → bash BOOTSTRAP <command> --root <root> [args...]
|
|
RB() { local r="$1"; shift; bash "$BOOTSTRAP" "$@" --root "$r"; }
|
|
|
|
# Fixture: generic project repo
|
|
make_repo() { # make_repo <name> [extra files...]
|
|
local name="$1"; shift
|
|
local d="$T/$name"
|
|
mkdir -p "$d/src"
|
|
git -C "$d" init -q . 2>/dev/null || true
|
|
printf '# README\n' > "$d/README.md"
|
|
for f in "$@"; do
|
|
case "$f" in
|
|
*/*) mkdir -p "$d/$(dirname "$f")" ;;
|
|
esac
|
|
printf '/* placeholder */\n' > "$d/$f"
|
|
done
|
|
printf '%s' "$d"
|
|
}
|
|
|
|
# ======================================================================== #
|
|
# TEST 1: .opencode is created for a new repository
|
|
# ======================================================================== #
|
|
TEST1="$(make_repo test1)"
|
|
if RB "$TEST1" bootstrap >/dev/null 2>&1; then
|
|
if assert_dir "$TEST1/.opencode" && \
|
|
assert_file "$TEST1/.opencode/AGENTS.md" && \
|
|
assert_file "$TEST1/.opencode/.bootstrap-meta" && \
|
|
assert_dir "$TEST1/.opencode/skills/repo-context"; then
|
|
ok "T01 .opencode created for new repo"
|
|
else
|
|
fail "T01 .opencode created for new repo" "missing expected files"
|
|
fi
|
|
else
|
|
fail "T01 .opencode created for new repo" "bootstrap exited non-zero"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 2: bootstrap is idempotent (second run produces no changes)
|
|
# ======================================================================== #
|
|
TEST2="$(make_repo test2 package.json)"
|
|
RB "$TEST2" bootstrap >/dev/null 2>&1
|
|
META1=$(cat "$TEST2/.opencode/.bootstrap-meta" 2>/dev/null || echo "MISSING")
|
|
AGENTS1=$(cat "$TEST2/.opencode/AGENTS.md" 2>/dev/null || echo "MISSING")
|
|
if RB "$TEST2" bootstrap >/dev/null 2>&1; then
|
|
META2=$(cat "$TEST2/.opencode/.bootstrap-meta" 2>/dev/null || echo "MISSING")
|
|
AGENTS2=$(cat "$TEST2/.opencode/AGENTS.md" 2>/dev/null || echo "MISSING")
|
|
if [ "$META1" = "$META2" ] && [ "$AGENTS1" = "$AGENTS2" ]; then
|
|
ok "T02 bootstrap is idempotent"
|
|
else
|
|
fail "T02 bootstrap is idempotent" "file changed on second run"
|
|
fi
|
|
else
|
|
fail "T02 bootstrap is idempotent" "second run exited non-zero"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 3: existing .opencode content (manual) is preserved
|
|
# ======================================================================== #
|
|
TEST3="$(make_repo test3)"
|
|
mkdir -p "$TEST3/.opencode/skills/repo-context"
|
|
printf '# Manual repo-context\nCustom content\n' > "$TEST3/.opencode/skills/repo-context/SKILL.md"
|
|
RB "$TEST3" bootstrap >/dev/null 2>&1
|
|
if assert_contains "$TEST3/.opencode/skills/repo-context/SKILL.md" "Custom content"; then
|
|
ok "T03 existing manual content preserved"
|
|
else
|
|
fail "T03 existing manual content preserved" "manual SKILL.md was overwritten"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 4: relevant skills are generated (build indicator → build-and-test;
|
|
# deploy indicator → deployment; plain → only repo-context + conventions)
|
|
# ======================================================================== #
|
|
TEST4A="$(make_repo test4a package.json Dockerfile src/lib/main.rs)"
|
|
RB "$TEST4A" bootstrap >/dev/null 2>&1
|
|
if assert_dir "$TEST4A/.opencode/skills/build-and-test" && \
|
|
assert_dir "$TEST4A/.opencode/skills/deployment" && \
|
|
assert_dir "$TEST4A/.opencode/skills/architecture" && \
|
|
assert_dir "$TEST4A/.opencode/skills/repo-context" && \
|
|
assert_dir "$TEST4A/.opencode/skills/conventions"; then
|
|
ok "T04a build+deploy+code indicators produce expected skills"
|
|
else
|
|
fail "T04a build+deploy+code indicators produce expected skills" \
|
|
"missing skills: $(ls "$TEST4A/.opencode/skills" 2>/dev/null)"
|
|
fi
|
|
|
|
TEST4B="$(make_repo test4b README.md)"
|
|
RB "$TEST4B" bootstrap >/dev/null 2>&1
|
|
SKILLS_B=$(ls "$TEST4B/.opencode/skills" 2>/dev/null | sort | tr '\n' ' ')
|
|
if ! assert_dir "$TEST4B/.opencode/skills/build-and-test" && \
|
|
! assert_dir "$TEST4B/.opencode/skills/deployment" && \
|
|
assert_dir "$TEST4B/.opencode/skills/repo-context" && \
|
|
assert_dir "$TEST4B/.opencode/skills/conventions"; then
|
|
ok "T04b plain repo only gets repo-context + conventions"
|
|
else
|
|
fail "T04b plain repo only gets repo-context + conventions" "got: $SKILLS_B"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 5: orchestrator.md contains repo-context-before-dispatch workflow stage
|
|
# (structural: grep for bootstrap consumption and repo-context loading)
|
|
# ======================================================================== #
|
|
ORCH="$TEAM_ROOT/agents/orchestrator.md"
|
|
if assert_contains "$ORCH" "Repository Intelligence" && \
|
|
assert_contains "$ORCH" ".opencode" && \
|
|
assert_contains "$ORCH" "stale"; then
|
|
ok "T05 orchestrator.md has bootstrap stage (structural)"
|
|
else
|
|
fail "T05 orchestrator.md has bootstrap stage (structural)" \
|
|
"missing expected Repository Intelligence / .opencode / stale content"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 6: every agents/*.md references .opencode (consumes context)
|
|
# ======================================================================== #
|
|
MISSING_AGENTS=""
|
|
AGENT_COUNT=0
|
|
for af in "$TEAM_ROOT"/agents/*.md; do
|
|
AGENT_COUNT=$((AGENT_COUNT+1))
|
|
ANAME="$(basename "$af")"
|
|
if ! assert_contains "$af" ".opencode"; then
|
|
MISSING_AGENTS="$MISSING_AGENTS $ANAME"
|
|
fi
|
|
done
|
|
if [ "$AGENT_COUNT" -eq 14 ] && [ -z "$MISSING_AGENTS" ]; then
|
|
ok "T06 all $AGENT_COUNT agents reference .opencode"
|
|
else
|
|
fail "T06 all agents reference .opencode" \
|
|
"count=$AGENT_COUNT, missing:$MISSING_AGENTS"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 7: stale knowledge is detectable (status exits 1 when fingerprint changes)
|
|
# ======================================================================== #
|
|
TEST7="$(make_repo test7 package.json)"
|
|
RB "$TEST7" bootstrap >/dev/null 2>&1
|
|
# fingerprint should be fresh
|
|
if RB "$TEST7" status >/dev/null 2>&1; then
|
|
# mutate manifest → fingerprint should go stale
|
|
printf '/* changed */\n' > "$TEST7/package.json"
|
|
STALE_RC=0
|
|
RB "$TEST7" status >/dev/null 2>&1 || STALE_RC=$?
|
|
if [ "$STALE_RC" -eq 1 ]; then
|
|
ok "T07 stale knowledge detectable after manifest change"
|
|
else
|
|
fail "T07 stale knowledge detectable after manifest change" \
|
|
"status returned $STALE_RC (expected 1)"
|
|
fi
|
|
else
|
|
fail "T07 stale knowledge detectable" "initial status already non-zero"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 8: unrelated repos do not inherit hardcoded assumptions
|
|
# (script runs generically; no repo-specific name literals in scaffold)
|
|
# ======================================================================== #
|
|
TEST8="$(make_repo test8)"
|
|
RB "$TEST8" bootstrap >/dev/null 2>&1
|
|
AGENTS_CONTENT="$(cat "$TEST8/.opencode/AGENTS.md")"
|
|
RC_CONTENT="$(cat "$TEST8/.opencode/skills/repo-context/SKILL.md")"
|
|
# Verify no hardcoded repo/team names appear (should only contain generic placeholders)
|
|
if ! assert_contains "$AGENTS_CONTENT" "dev_agent_team" && \
|
|
! assert_contains "$RC_CONTENT" "orchestrator"; then
|
|
ok "T08 no hardcoded repo-specific assumptions in scaffold"
|
|
else
|
|
fail "T08 no hardcoded repo-specific assumptions in scaffold" \
|
|
"found hardcoded terms in generated content"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 9: existing agent workflows still work
|
|
# (install.sh to temp target — 14 agents copied; treat "no runtime"
|
|
# verifier exit 2 as SKIP since copies succeed regardless)
|
|
# ======================================================================== #
|
|
TEST9_TARGET="$T/install_target9"
|
|
if [ -f "$TEAM_ROOT/scripts/install.sh" ]; then
|
|
INST_RC=0
|
|
mkdir -p "$TEST9_TARGET"
|
|
OPENCODE_AGENTS_DIR="$TEST9_TARGET" bash "$TEAM_ROOT/scripts/install.sh" >/dev/null 2>&1 || INST_RC=$?
|
|
AGENTS_COPIED=$(find "$TEST9_TARGET" -name '*.md' -maxdepth 1 | wc -l)
|
|
if [ "$INST_RC" -eq 0 ] || [ "$INST_RC" -eq 2 ]; then
|
|
if [ "$AGENTS_COPIED" -eq 14 ]; then
|
|
ok "T09 install.sh copies 14 agents to temp target (rc=$INST_RC, runtime=$([ $INST_RC -eq 0 ] && echo 'yes' || echo 'skip/no-op'))"
|
|
else
|
|
fail "T09 install.sh copies 14 agents to temp target" \
|
|
"only $AGENTS_COPIED .md files copied"
|
|
fi
|
|
else
|
|
fail "T09 install.sh copies 14 agents to temp target" \
|
|
"install.sh exited $INST_RC"
|
|
fi
|
|
else
|
|
skip "T09 install.sh copies 14 agents" "scripts/install.sh not found"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# TEST 10: malformed/incomplete .opencode intelligence is handled safely
|
|
# ======================================================================== #
|
|
TEST10="$(make_repo test10 package.json)"
|
|
mkdir -p "$TEST10/.opencode/skills/repo-context"
|
|
printf 'GARBAGE CONTENT\nno frontmatter\n' > "$TEST10/.opencode/skills/repo-context/SKILL.md"
|
|
echo "not-a-meta" > "$TEST10/.opencode/.bootstrap-meta"
|
|
RC=0
|
|
RB "$TEST10" bootstrap >/dev/null 2>&1 || RC=$?
|
|
# Assert: script did not crash, manual garbage in repo-context preserved (no marker),
|
|
# and meta was regenerated.
|
|
if [ "$RC" -eq 0 ] && \
|
|
assert_contains "$TEST10/.opencode/skills/repo-context/SKILL.md" "GARBAGE CONTENT" && \
|
|
assert_file "$TEST10/.opencode/.bootstrap-meta" && \
|
|
assert_contains "$TEST10/.opencode/.bootstrap-meta" "schema=1"; then
|
|
ok "T10 malformed intelligence handled safely"
|
|
else
|
|
fail "T10 malformed intelligence handled safely" \
|
|
"rc=$RC; corrupted repo-context still present after bootstrap"
|
|
fi
|
|
|
|
# ======================================================================== #
|
|
# SUMMARY
|
|
# ======================================================================== #
|
|
echo ""
|
|
TOTAL=$((PASS+FAIL))
|
|
if [ "$FAIL" -eq 0 ]; then
|
|
echo "RESULT: ALL $TOTAL TESTS PASSED"
|
|
exit 0
|
|
else
|
|
echo "RESULT: $FAIL/$TOTAL TESTS FAILED"
|
|
exit 1
|
|
fi |