#!/usr/bin/env bash
# smoke-cross-cli-behavior.sh  -  enforces the cross-CLI parity invariant
# defined in ~/.claude/multi-agent-refs/cross-cli-contract.md.
#
# This is a behavioral complement to smoke-sync-parity.sh (which tests the
# parity checker itself). This script asserts the actual Claude Code and
# Copilot CLI command trees respect:
#   1. Command-set identity (17 shared commands present on both sides)
#   2. Placeholder vocabulary (no deprecated names in Claude source)
#   3. No personal-data leaks (hardcoded personal keychain keys, etc.)
#   4. Canonical placeholders present where required (sanity)
#   5. Canonical project-name vocabulary in user-facing docs
#
# Must pass before any `sync` run that touches shared commands.

set -uo pipefail

CLAUDE_CMDS="$HOME/.claude/commands/multi-agent"
COPILOT_SKILLS="$HOME/.copilot/skills"

PASS=0
FAIL=0
pass() { PASS=$((PASS + 1)); echo "  ✓ $1"; }
fail() { FAIL=$((FAIL + 1)); echo "  ✗ $1"; }

# ──────────────────────────────────────────────────────────────────────────
echo "→ 1. Command-set identity (Claude vs Copilot)"
if [ ! -d "$CLAUDE_CMDS" ]; then
  fail "Claude commands dir missing: $CLAUDE_CMDS"
  echo ""
  echo "══ cross-cli-behavior: aborted (setup invalid) ══"
  exit 2
fi
if [ ! -d "$COPILOT_SKILLS" ]; then
  fail "Copilot skills dir missing: $COPILOT_SKILLS (install or sync first)"
  echo ""
  echo "══ cross-cli-behavior: aborted (setup invalid) ══"
  exit 2
fi

# Underscore-prefixed Claude files (e.g. _account-picker.md) are internal
# fragments lazy-loaded by other commands; they're not invocable on either CLI
# and intentionally have no Copilot skill counterpart.
# Also exclude local-only alias wrappers (frontmatter `local-only: true`)  -  personal
# aliases to private marketplace-plugin skills, intentionally Claude-only, no Copilot peer.
CLAUDE_LIST=$(find "$CLAUDE_CMDS" -mindepth 2 -maxdepth 2 -name "SKILL.md" 2>/dev/null \
  | while IFS= read -r f; do grep -q "^local-only: true" "$f" || basename "$(dirname "$f")"; done | sort)
COPILOT_LIST=$(find "$COPILOT_SKILLS" -maxdepth 1 -type d -name "multi-agent-*" -exec basename {} \; 2>/dev/null | sed 's/^multi-agent-//' | sort)

if [ "$CLAUDE_LIST" = "$COPILOT_LIST" ]; then
  COUNT=$(echo "$CLAUDE_LIST" | grep -c '^..*')
  pass "command sets identical ($COUNT commands each)"
else
  fail "command sets diverged"
  echo "    Claude only:"
  comm -23 <(echo "$CLAUDE_LIST") <(echo "$COPILOT_LIST") | sed 's/^/      /'
  echo "    Copilot only:"
  comm -13 <(echo "$CLAUDE_LIST") <(echo "$COPILOT_LIST") | sed 's/^/      /'
fi

# ──────────────────────────────────────────────────────────────────────────
echo ""
echo "→ 2. No deprecated placeholders in Claude source"
# Corpus guard: the scans below can only be trusted if there is something to
# scan. Without this, an empty or missing command tree reports every pattern
# clean instead of reporting that it found nothing to check.
SKILL_COUNT=$(find "$CLAUDE_CMDS" -name SKILL.md 2>/dev/null | wc -l | tr -d ' ')
if [ "$SKILL_COUNT" -gt 0 ]; then
  pass "command corpus non-empty ($SKILL_COUNT SKILL.md)"
else
  fail "no SKILL.md under $CLAUDE_CMDS  -  placeholder and hardcode scans below are vacuous"
fi

# Per refs/cross-cli-contract.md Section 2.5
DEPRECATED=(
  "{github-username}"
  "{website-repo}"
  "{your-website}"
  "my-project"
  "my-figma-project"
  "my-mobile-app"
)
for p in "${DEPRECATED[@]}"; do
  # grep -l returns filenames; empty = no matches
  FOUND=$(find "$CLAUDE_CMDS" -name SKILL.md -exec grep -l -F -- "$p" {} + 2>/dev/null || true)
  if [ -z "$FOUND" ]; then
    pass "no '$p' in Claude commands"
  else
    fail "deprecated '$p' found in: $(echo "$FOUND" | xargs -n1 basename | paste -sd, -)"
  fi
done

# ──────────────────────────────────────────────────────────────────────────
echo ""
echo "→ 3. No personal-data hardcodes in generic docs"
# These are patterns that must be placeholders, never literals
PERSONAL_HARDCODES=(
  "${USER}_tkgithub"
)
for p in "${PERSONAL_HARDCODES[@]}"; do
  FOUND=$(find "$CLAUDE_CMDS" -name SKILL.md -exec grep -l -F -- "$p" {} + 2>/dev/null || true)
  if [ -z "$FOUND" ]; then
    pass "no '$p' leaked into Claude commands"
  else
    fail "personal hardcode '$p' found in: $(echo "$FOUND" | xargs -n1 basename | paste -sd, -)"
  fi
done

# ──────────────────────────────────────────────────────────────────────────
echo ""
echo "→ 4. Canonical placeholders present (sanity)"
# These MUST exist (regressions would mean someone undid a drift fix)
if grep -q -F "{owner}" "$CLAUDE_CMDS/sync/SKILL.md" 2>/dev/null; then
  pass "sync.md uses canonical '{owner}'"
else
  fail "sync.md missing '{owner}'  -  drift fix may have regressed"
fi

if grep -q -F "{website-host}" "$CLAUDE_CMDS/sync/SKILL.md" 2>/dev/null; then
  pass "sync.md uses canonical '{website-host}'"
else
  fail "sync.md missing '{website-host}'  -  drift fix may have regressed"
fi

# ──────────────────────────────────────────────────────────────────────────
echo ""
echo "→ 5. Canonical project-name vocabulary in log.md"
CANONICAL_PROJECTS=("my-ui-components" "my-figma-app" "my-ios-app")
for c in "${CANONICAL_PROJECTS[@]}"; do
  if grep -q -F -- "$c" "$CLAUDE_CMDS/log/SKILL.md" 2>/dev/null; then
    pass "log.md references '$c'"
  else
    fail "log.md missing canonical '$c'"
  fi
done

# ──────────────────────────────────────────────────────────────────────────
echo ""
echo "→ 6. Contract doc exists (required source of truth)"
CONTRACT="$HOME/.claude/multi-agent-refs/cross-cli-contract.md"
if [ -f "$CONTRACT" ]; then
  SIZE=$(wc -l < "$CONTRACT" | tr -d ' ')
  if [ "$SIZE" -ge 100 ]; then
    pass "cross-cli-contract.md present ($SIZE lines)"
  else
    fail "cross-cli-contract.md suspiciously short ($SIZE lines)  -  stub?"
  fi
else
  fail "cross-cli-contract.md missing at $CONTRACT"
fi

# ──────────────────────────────────────────────────────────────────────────
echo ""
# Figma/component skill trees no longer ship in the pipeline (v11.2.0+): they
# live in the marketplace component plugins (ai-ios-toolkit /
# multi-agent-plugins), which each CLI loads through its own plugin channel.
# The pipeline therefore has no figma tree to deploy or parity-check here.
echo "→ 7. Agent personas deployed to both CLIs"

CLAUDE_AGENTS="$HOME/.claude/agents"
COPILOT_AGENTS="$HOME/.copilot/agents"
AGENT_FILES=(code-reviewer.md explorer.md ios-architect.md android-architect.md backend-architect.md security-auditor.md)

if [ -d "$CLAUDE_AGENTS" ] && [ -d "$COPILOT_AGENTS" ]; then
  missing_on_copilot=0
  for a in "${AGENT_FILES[@]}"; do
    [ -f "$COPILOT_AGENTS/$a" ] || missing_on_copilot=$((missing_on_copilot+1))
  done
  if [ "$missing_on_copilot" -eq 0 ]; then
    pass "all 6 reviewer/explorer personas present on both CLIs"
  else
    fail "$missing_on_copilot persona file(s) missing from Copilot side"
  fi
  claude_count=$(find "$CLAUDE_AGENTS" -maxdepth 1 -type f -name '*.md' | wc -l | tr -d ' ')
  copilot_count=$(find "$COPILOT_AGENTS" -maxdepth 1 -type f -name '*.md' | wc -l | tr -d ' ')
  if [ "$claude_count" -eq "$copilot_count" ]; then
    pass "agents/ file count matches ($claude_count each)"
  else
    fail "agents/ count mismatch (claude=$claude_count copilot=$copilot_count)"
  fi
elif [ -d "$CLAUDE_AGENTS" ] || [ -d "$COPILOT_AGENTS" ]; then
  fail "agents/ deployed to only ONE CLI  -  cross-CLI parity broken"
else
  echo "    (agents/ not installed on either CLI  -  run \`node install.js --all\` to deploy)"
fi

# ──────────────────────────────────────────────────────────────────────────
# CLI-aware reviewer-count contract: Claude Code = 2 (Fable + Sonnet), Copilot CLI
# = 3 (Opus + GPT-5.4 + Sonnet), Codex CLI = 3 (gpt-5.6 xhigh + gpt-5.4 + gpt-5.6
# medium). Nothing in code enforces the count (the orchestrator dispatches per the
# doc), so lock the CONTRACT here against drift across the phase doc, the schema,
# and the consensus block.
echo "→ reviewer-count contract (Claude=2, Copilot=3, Codex=3)"
# `/multi-agent:update` runs this smoke on the user's machine, where the tree is
# ~/.claude/{schemas,multi-agent-refs} and NOT <root>/pipeline/*. Resolving the
# root by hand here reported three phantom failures in an install, which reads as
# a broken installation. _smoke-root.sh handles both layouts.
# shellcheck source=pipeline/scripts/_smoke-root.sh
. "$(dirname "${BASH_SOURCE[0]}")/_smoke-root.sh"
P4="${MA_REFS:+$MA_REFS/phases/phase-4-review.md}"
REVSCHEMA="${MA_SCHEMAS:+$MA_SCHEMAS/reviewer-output.schema.json}"
TRSCHEMA="${MA_SCHEMAS:+$MA_SCHEMAS/triage-output.schema.json}"

if [ -z "$P4" ] || [ ! -f "$P4" ]; then
  echo "  ↷ SKIP: phase-4-review.md not present in this $MA_LAYOUT layout"
elif grep -qiE "Claude Code (dispatches|=|:) ?2|2-model" "$P4" \
  && grep -qiE "Copilot CLI (dispatches|=|:) ?3|3-model" "$P4" \
  && grep -qiE "Codex CLI (dispatches|=|:) ?3|Claude Code 2, Copilot CLI 3, Codex CLI 3" "$P4"; then
  pass "phase-4-review declares Claude=2 / Copilot=3 / Codex=3 reviewers"
else
  fail "phase-4-review does not declare the CLI-aware reviewer count for all three hosts"
fi

# The two Codex constraints are silent-failure shaped, so the contract has to name
# them: a spawn without fork_turns collapses the panel onto one model, and the
# 4-slot ceiling (orchestrator included) is why the count is 3 and not more.
if [ -n "$P4" ] && [ -f "$P4" ]; then
  if grep -q 'fork_turns' "$P4" && grep -qiE "concurrency|slots" "$P4"; then
    pass "phase-4-review documents the fork_turns override rule + the concurrency ceiling"
  else
    fail "phase-4-review must document fork_turns and the Codex concurrency ceiling"
  fi
fi

if [ -z "$REVSCHEMA" ] || [ ! -f "$REVSCHEMA" ]; then
  echo "  ↷ SKIP: reviewer-output.schema.json not present in this $MA_LAYOUT layout"
elif grep -qi "Copilot CLI" "$REVSCHEMA" && grep -qi "Claude" "$REVSCHEMA" \
  && grep -qi "Codex" "$REVSCHEMA"; then
  pass "reviewer-output schema notes the CLI-aware reviewer set (all three hosts)"
else
  fail "reviewer-output schema missing the CLI-aware note"
fi

# consensus.reviewerCount must accommodate both 2 and 3 (min 1, no max < 3)
if [ -z "$TRSCHEMA" ] || [ ! -f "$TRSCHEMA" ]; then
  echo "  ↷ SKIP: triage-output.schema.json not present in this $MA_LAYOUT layout"
elif grep -q '"reviewerCount"' "$TRSCHEMA" && grep -qi "Claude Code = 2, Copilot CLI = 3" "$TRSCHEMA"; then
  pass "triage consensus.reviewerCount documents 2 (Claude) and 3 (Copilot)"
else
  fail "triage consensus.reviewerCount does not document the 2/3 split"
fi

echo ""
echo "══ cross-cli-behavior smoke: $PASS passed, $FAIL failed ══"
[ "$FAIL" -eq 0 ]
