#!/usr/bin/env node
import*as e from'fs';import*as r from'path';import*as E from'os';import {execSync}from'child_process';import o from'chalk';var N=`#!/bin/sh
# paradigm-common.sh \u2014 Shared compliance checks for Paradigm stop hooks
# Sourced by claude-code-stop.sh and cursor-stop.sh
#
# Caller must set:
#   CWD      \u2014 Project root directory (already cd'd into)
#   MODIFIED \u2014 Output of \`git diff --name-only HEAD\`
#
# Caller may set:
#   PARADIGM_AUTO_FIX \u2014 Set to "1" to auto-fix trivial violations (missing .purpose stubs, missing lore)
#
# Sets:
#   VIOLATIONS      \u2014 Newline-separated violation messages
#   VIOLATION_COUNT \u2014 Number of violations
#   ADVISORY        \u2014 Non-blocking advisory text
#   SOURCE_COUNT    \u2014 Number of modified source files
#   AUTO_FIXED      \u2014 Newline-separated auto-fix actions taken
#   AUTO_FIX_COUNT  \u2014 Number of auto-fixes applied
#
# Checks:
#   1. Source files modified without .purpose updates (threshold: 2+)
#   2. Modified source directories missing .purpose files entirely
#   3. Route-like patterns added without portal.yaml updates
#   4. Aspect anchor files that no longer exist
#   5. Per-directory .purpose freshness (tracked via .pending-review)
#   6. Aspect coverage advisory
#   7. Lore entry expected for significant sessions (3+ source files)
#   8. Blocking habits not satisfied (from unified compliance-check)
#   9. Purpose-required patterns from config.yaml
#  10. Aspect drift detection with auto-heal (from unified compliance-check)
#  11. Portal gate implementation compliance (from unified compliance-check)
#  12. Graduation failure tracking (auto-demotion)
#  13. Orchestration required for complex tasks

VIOLATIONS=""
VIOLATION_COUNT=0
AUTO_FIXED=""
AUTO_FIX_COUNT=0
PARADIGM_AUTO_FIX="\${PARADIGM_AUTO_FIX:-0}"

# --- Load enforcement config ---
ENFORCEMENT_MAP=""
if command -v paradigm >/dev/null 2>&1; then
  ENFORCEMENT_MAP=$(paradigm enforcement resolve --json 2>/dev/null) || true
fi

# Helper: get severity for a check ID
_check_severity() {
  _id="$1"
  _default="$2"
  if [ -z "$ENFORCEMENT_MAP" ]; then
    echo "$_default"
    return
  fi
  _val=$(echo "$ENFORCEMENT_MAP" | grep -o "\\"$_id\\":\\"[^\\"]*\\"" | sed 's/.*":\\"//' | sed 's/"//')
  if [ -n "$_val" ]; then
    echo "$_val"
  else
    echo "$_default"
  fi
}

# Read orchestration threshold
ORCH_THRESHOLD=3
if [ -n "$ENFORCEMENT_MAP" ]; then
  _ot=$(echo "$ENFORCEMENT_MAP" | grep -o '"orchestrationThreshold":[0-9]*' | sed 's/.*://')
  [ -n "$_ot" ] && ORCH_THRESHOLD="$_ot"
fi

# --- Cache .purpose file paths (avoid repeated find scans) ---
PURPOSE_CACHE=".paradigm/.purpose-paths"
if [ -f "$PURPOSE_CACHE" ]; then
  PURPOSE_PATHS=$(cat "$PURPOSE_CACHE")
else
  PURPOSE_PATHS=$(find . -name ".purpose" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null)
  if [ -n "$PURPOSE_PATHS" ]; then
    echo "$PURPOSE_PATHS" > "$PURPOSE_CACHE"
  fi
fi

# --- Check 1: Source files modified without .purpose updates ---
# Instead of counting total source files globally, check per-directory:
# only flag directories where source files were modified but no .purpose
# exists in that directory's ancestor chain OR the covering .purpose was not updated.
SOURCE_COUNT=0
PARADIGM_COUNT=0
UNCOVERED_SOURCE_DIRS=""

for file in $MODIFIED; do
  case "$file" in
    .paradigm/*|*.purpose|portal.yaml)
      PARADIGM_COUNT=$((PARADIGM_COUNT + 1))
      ;;
    *.md|*.lock|*.log|.gitignore|.env*|*.json) ;;
    *)
      SOURCE_COUNT=$((SOURCE_COUNT + 1))
      # Check if this file's directory has a covering .purpose that was modified
      src_dir=$(dirname "$file")
      covering=""
      walk_dir="$src_dir"
      while [ "$walk_dir" != "." ] && [ "$walk_dir" != "" ]; do
        if [ -f "$walk_dir/.purpose" ]; then
          covering="$walk_dir/.purpose"
          break
        fi
        walk_dir=$(dirname "$walk_dir")
      done
      if [ -z "$covering" ] && [ -f ".purpose" ]; then
        covering=".purpose"
      fi
      # If there IS a covering .purpose, check if it was modified too
      if [ -n "$covering" ]; then
        purpose_was_modified=false
        for mod_file in $MODIFIED; do
          if [ "$mod_file" = "$covering" ]; then
            purpose_was_modified=true
            break
          fi
        done
        if [ "$purpose_was_modified" = false ]; then
          # Deduplicate by directory
          case "$UNCOVERED_SOURCE_DIRS" in
            *"$src_dir"*) ;;
            *) UNCOVERED_SOURCE_DIRS="$UNCOVERED_SOURCE_DIRS $src_dir" ;;
          esac
        fi
      else
        # No .purpose at all \u2014 already caught by Check 2
        :
      fi
      ;;
  esac
done

# Only flag if there are uncovered directories AND zero paradigm files were touched
_SEV=$(_check_severity "purpose-coverage" "block")
if [ "$_SEV" != "off" ] && [ -n "$UNCOVERED_SOURCE_DIRS" ] && [ "$PARADIGM_COUNT" -eq 0 ]; then
  # Count uncovered dirs
  UNCOVERED_DIR_COUNT=0
  for _d in $UNCOVERED_SOURCE_DIRS; do
    UNCOVERED_DIR_COUNT=$((UNCOVERED_DIR_COUNT + 1))
  done
  if [ "$_SEV" = "block" ]; then
    VIOLATIONS="$VIOLATIONS
  - You modified source files in $UNCOVERED_DIR_COUNT director(ies) without updating their .purpose files:
   $UNCOVERED_SOURCE_DIRS
    Update the nearest .purpose file for each modified code area."
    VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
  else
    ADVISORY="$ADVISORY
  - (warn) Source files in $UNCOVERED_DIR_COUNT director(ies) modified without .purpose updates:
   $UNCOVERED_SOURCE_DIRS"
  fi
fi

# --- Check 2: Modified source directories missing .purpose files ---
DIRS_WITHOUT_PURPOSE=""

for file in $MODIFIED; do
  case "$file" in
    .paradigm/*|*.md|*.lock|*.log|.gitignore|.env*|*.json|*.purpose|portal.yaml) continue ;;
  esac

  dir=$(dirname "$file")
  # Walk up to find a .purpose file
  found_purpose=false
  check_dir="$dir"
  while [ "$check_dir" != "." ] && [ "$check_dir" != "" ]; do
    if [ -f "$check_dir/.purpose" ]; then
      found_purpose=true
      break
    fi
    check_dir=$(dirname "$check_dir")
  done
  # Also check root
  if [ "$found_purpose" = false ] && [ -f ".purpose" ]; then
    found_purpose=true
  fi

  if [ "$found_purpose" = false ]; then
    # Deduplicate directory names
    case "$DIRS_WITHOUT_PURPOSE" in
      *"$dir"*) ;;
      *) DIRS_WITHOUT_PURPOSE="$DIRS_WITHOUT_PURPOSE $dir" ;;
    esac
  fi
done

_SEV=$(_check_severity "purpose-exists" "block")
if [ "$_SEV" != "off" ] && [ -n "$DIRS_WITHOUT_PURPOSE" ]; then
  if [ "$PARADIGM_AUTO_FIX" = "1" ]; then
    # Auto-fix: create stub .purpose files for directories missing them
    for dir in $DIRS_WITHOUT_PURPOSE; do
      dir_basename=$(basename "$dir")
      cat > "$dir/.purpose" <<PURPOSEEOF
# Auto-generated .purpose stub \u2014 update with real descriptions
components:
  $dir_basename:
    description: "TODO: describe this component"
    tags: []
PURPOSEEOF
      AUTO_FIXED="$AUTO_FIXED
  - Created stub .purpose in $dir (update descriptions)"
      AUTO_FIX_COUNT=$((AUTO_FIX_COUNT + 1))
    done
  elif [ "$_SEV" = "block" ]; then
    VIOLATIONS="$VIOLATIONS
  - These directories have modified source files but no .purpose file anywhere in their path:
   $DIRS_WITHOUT_PURPOSE
    Create a .purpose file using paradigm_purpose_init + paradigm_purpose_add_component."
    VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
  else
    ADVISORY="$ADVISORY
  - (warn) Directories with modified source files but no .purpose file:
   $DIRS_WITHOUT_PURPOSE"
  fi
fi

# --- Check 3: Route patterns added without portal.yaml ---
_SEV=$(_check_severity "portal-gates" "block")
if [ "$_SEV" != "off" ]; then
  if [ -f "portal.yaml" ] || echo "$MODIFIED" | grep -q "portal.yaml"; then
    : # portal.yaml exists or was modified \u2014 OK
  else
    # Check if any modified files contain route-like patterns
    # Skip: test/spec/fixture files, markdown, and comment-only matches
    ROUTE_FILES=""
    for file in $MODIFIED; do
      # Skip test, spec, and fixture files entirely
      case "$file" in
        *test*|*spec*|*fixture*|*__tests__*|*__mocks__*|*.test.*|*.spec.*) continue ;;
      esac
      case "$file" in
        *.ts|*.js|*.tsx|*.jsx|*.py|*.rs|*.go)
          if [ -f "$file" ]; then
            # Grep for route patterns, then filter out comment lines and description strings
            ROUTE_MATCH=$(grep -nE '\\.(get|post|put|patch|delete)\\s*\\(|router\\.|app\\.(get|post|put|delete)|@(Get|Post|Put|Delete)|#\\[actix_web::(get|post)' "$file" 2>/dev/null \\
              | grep -v '^\\s*[0-9]*:\\s*//' \\
              | grep -v '^\\s*[0-9]*:\\s*\\*' \\
              | grep -v '^\\s*[0-9]*:\\s*#' \\
              | grep -v '^\\s*[0-9]*:\\s*<!--' \\
              | grep -v 'description:' \\
              | grep -v 'summary:' \\
              | grep -v 'comment:' \\
              | grep -v '@example' \\
              | grep -v '@see' \\
              || true)
            if [ -n "$ROUTE_MATCH" ]; then
              ROUTE_FILES="$ROUTE_FILES $file"
            fi
          fi
          ;;
      esac
    done

    if [ -n "$ROUTE_FILES" ]; then
      if [ "$_SEV" = "block" ]; then
        VIOLATIONS="$VIOLATIONS
  - Route/endpoint patterns found in modified files but no portal.yaml exists:
   $ROUTE_FILES
    Create portal.yaml with gate definitions. Use paradigm_gates_for_route for suggestions."
        VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
      else
        ADVISORY="$ADVISORY
  - (warn) Route/endpoint patterns found without portal.yaml:
   $ROUTE_FILES"
      fi
    fi
  fi
fi

# --- Check 4: Aspect anchor files that no longer exist ---
_SEV=$(_check_severity "aspect-anchors" "block")
if [ "$_SEV" != "off" ]; then
  for purpose_file in $PURPOSE_PATHS; do
    if grep -q "anchors:" "$purpose_file" 2>/dev/null; then
      purpose_dir=$(dirname "$purpose_file")
      in_anchors=false
      while IFS= read -r line; do
        case "$line" in
          *"anchors:"*) in_anchors=true; continue ;;
          *"- "*)
            if [ "$in_anchors" = true ]; then
              anchor_path=$(echo "$line" | sed 's/.*- //' | sed 's/:.*//' | tr -d ' ')
              if [ -n "$anchor_path" ]; then
                # Try relative to .purpose dir first, then project root
                if [ ! -f "$purpose_dir/$anchor_path" ] && [ ! -f "./$anchor_path" ]; then
                  if [ "$_SEV" = "block" ]; then
                    VIOLATIONS="$VIOLATIONS
  - Aspect anchor '$anchor_path' in $purpose_file does not exist.
    Update the anchor or remove the stale aspect."
                    VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
                  else
                    ADVISORY="$ADVISORY
  - (warn) Aspect anchor '$anchor_path' in $purpose_file does not exist."
                  fi
                fi
              fi
            fi
            ;;
          *) in_anchors=false ;;
        esac
      done < "$purpose_file"
    fi
  done
fi

# --- Check 5: Per-directory .purpose freshness ---
# Uses .pending-review if available, plus a git-based fallback that cross-references
# modified source files against .purpose modification times.
PENDING_FILE=".paradigm/.pending-review"
STALE_PURPOSES=""

# Helper: record a stale .purpose (deduplicating)
_record_stale_purpose() {
  _cov="$1"
  case "$STALE_PURPOSES" in
    *"$_cov"*) ;;
    *) STALE_PURPOSES="$STALE_PURPOSES $_cov" ;;
  esac
}

# Strategy A: Use .pending-review tracking file if present
if [ -f "$PENDING_FILE" ]; then
  while IFS= read -r tracked_file; do
    [ -z "$tracked_file" ] && continue
    # Find covering .purpose for this tracked file
    check_dir=$(dirname "$tracked_file")
    covering_purpose=""
    while [ "$check_dir" != "." ] && [ "$check_dir" != "" ]; do
      if [ -f "$check_dir/.purpose" ]; then
        covering_purpose="$check_dir/.purpose"
        break
      fi
      check_dir=$(dirname "$check_dir")
    done
    if [ -z "$covering_purpose" ] && [ -f ".purpose" ]; then
      covering_purpose=".purpose"
    fi
    # Check if covering .purpose was also modified
    if [ -n "$covering_purpose" ]; then
      if ! echo "$MODIFIED" | grep -qxF "$covering_purpose"; then
        _record_stale_purpose "$covering_purpose"
      fi
    fi
  done < "$PENDING_FILE"
fi

# Strategy B: Git-based fallback \u2014 for each modified source file, check whether
# its covering .purpose was modified more recently (by filesystem mtime).
# This catches cases where .pending-review is missing or out of sync.
for file in $MODIFIED; do
  case "$file" in
    .paradigm/*|*.md|*.lock|*.log|.gitignore|.env*|*.json|*.purpose|portal.yaml) continue ;;
  esac
  [ -f "$file" ] || continue

  # Find covering .purpose
  check_dir=$(dirname "$file")
  covering_purpose=""
  while [ "$check_dir" != "." ] && [ "$check_dir" != "" ]; do
    if [ -f "$check_dir/.purpose" ]; then
      covering_purpose="$check_dir/.purpose"
      break
    fi
    check_dir=$(dirname "$check_dir")
  done
  if [ -z "$covering_purpose" ] && [ -f ".purpose" ]; then
    covering_purpose=".purpose"
  fi
  [ -n "$covering_purpose" ] || continue

  # Skip if .purpose was in the modified list (already updated this session)
  echo "$MODIFIED" | grep -qxF "$covering_purpose" && continue

  # Compare modification times: if source file is newer than .purpose, it's stale
  if [ -f "$covering_purpose" ]; then
    # Use portable stat-based mtime comparison (works on macOS + Linux)
    if command -v stat >/dev/null 2>&1; then
      # macOS stat: -f %m gives epoch seconds; Linux stat: -c %Y
      src_mtime=$(stat -f %m "$file" 2>/dev/null || stat -c %Y "$file" 2>/dev/null || echo "0")
      purpose_mtime=$(stat -f %m "$covering_purpose" 2>/dev/null || stat -c %Y "$covering_purpose" 2>/dev/null || echo "0")
      if [ "$src_mtime" -gt "$purpose_mtime" ] 2>/dev/null; then
        _record_stale_purpose "$covering_purpose"
      fi
    fi
  fi
done

_SEV=$(_check_severity "purpose-freshness" "warn")
if [ "$_SEV" != "off" ] && [ -n "$STALE_PURPOSES" ]; then
  if [ "$_SEV" = "block" ]; then
    VIOLATIONS="$VIOLATIONS
  - These .purpose files cover modified source code but were NOT updated:
   $STALE_PURPOSES
    Update each with: #components, ~aspects (with anchors), !signals, \\$flows, ^gates."
    VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
  else
    ADVISORY="$ADVISORY
  - (warn) These .purpose files cover modified source code but were NOT updated:
   $STALE_PURPOSES"
  fi
fi

# --- Check 6: Aspect coverage advisory ---
ADVISORY=""
_SEV=$(_check_severity "aspect-advisory" "warn")
if [ "$_SEV" != "off" ]; then
  HAS_ASPECTS=false
  for purpose_file in $PURPOSE_PATHS; do
    if grep -qE '^\\s*~' "$purpose_file" 2>/dev/null; then
      HAS_ASPECTS=true
      break
    fi
  done

  if [ "$HAS_ASPECTS" = true ] && [ "$SOURCE_COUNT" -gt 0 ]; then
    ASPECT_UPDATED=false
    for file in $MODIFIED; do
      case "$file" in
        *.purpose)
          if grep -qE '^\\s*~|anchors:|applies-to:' "$file" 2>/dev/null; then
            ASPECT_UPDATED=true
            break
          fi
          ;;
      esac
    done

    if [ "$ASPECT_UPDATED" = false ]; then
      ADVISORY="  This project defines ~aspects with code anchors. Check if existing
  ~aspects need updated anchors or applies-to patterns."
    fi
  fi
fi

# --- Check 7: Lore entry expected for significant sessions ---
_SEV=$(_check_severity "lore-required" "block")
if [ "$_SEV" != "off" ] && [ "$SOURCE_COUNT" -ge 3 ] && [ -d ".paradigm/lore" ]; then
  LORE_RECORDED=false

  # Check git diff first (covers staged/committed lore)
  for file in $MODIFIED; do
    case "$file" in
      .paradigm/lore/entries/*.yaml|.paradigm/lore/entries/*/*.yaml|.paradigm/lore/entries/*.lore|.paradigm/lore/entries/*/*.lore)
        LORE_RECORDED=true
        break
        ;;
    esac
  done

  # Also check for recent lore on disk (covers MCP-written entries not yet staged)
  if [ "$LORE_RECORDED" = false ]; then
    TODAY=$(date -u +"%Y-%m-%d")
    if [ -d ".paradigm/lore/entries/$TODAY" ]; then
      ENTRY_COUNT=$(find ".paradigm/lore/entries/$TODAY" \\( -name "*.yaml" -o -name "*.lore" \\) 2>/dev/null | head -1)
      if [ -n "$ENTRY_COUNT" ]; then
        LORE_RECORDED=true
      fi
    fi
  fi

  if [ "$LORE_RECORDED" = false ]; then
    if [ "$PARADIGM_AUTO_FIX" = "1" ]; then
      # Auto-fix: create a stub lore entry with modified file info
      TODAY=$(date -u +"%Y-%m-%d" 2>/dev/null || date +"%Y-%m-%d")
      LORE_TIMESTAMP=$(date -u +"%H%M%S" 2>/dev/null || date +"%H%M%S")
      LORE_DIR=".paradigm/lore/entries/$TODAY"
      mkdir -p "$LORE_DIR" 2>/dev/null
      LORE_ID="L-\${TODAY}-auto-\${LORE_TIMESTAMP}"
      LORE_FILE="$LORE_DIR/\${LORE_ID}.yaml"

      # Extract symbols from modified file paths (directory basenames as component names)
      LORE_SYMBOLS=""
      for file in $MODIFIED; do
        case "$file" in
          .paradigm/*|*.md|*.lock|*.log|.gitignore|.env*|*.json|*.purpose|portal.yaml) continue ;;
        esac
        sym_dir=$(basename "$(dirname "$file")")
        case "$LORE_SYMBOLS" in
          *"#$sym_dir"*) ;;
          *) LORE_SYMBOLS="$LORE_SYMBOLS \\"#$sym_dir\\"" ;;
        esac
      done

      cat > "$LORE_FILE" <<LOREEOF
id: "$LORE_ID"
type: agent-session
title: "Auto-recorded session \u2014 $SOURCE_COUNT files modified"
summary: "Session modified $SOURCE_COUNT source files. Auto-recorded by stop hook."
symbols_touched: [$(echo $LORE_SYMBOLS | sed 's/^ //')]
timestamp: "$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ")"
LOREEOF
      AUTO_FIXED="$AUTO_FIXED
  - Created stub lore entry $LORE_ID (update with real summary)"
      AUTO_FIX_COUNT=$((AUTO_FIX_COUNT + 1))
    elif [ "$_SEV" = "block" ]; then
      VIOLATIONS="$VIOLATIONS
  - You modified $SOURCE_COUNT source files but recorded no lore entry.
    Record your session: paradigm_lore_record (MCP) or paradigm lore record (CLI).
    Include: type, title, summary, and symbols_touched."
      VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
    else
      ADVISORY="$ADVISORY
  - (warn) $SOURCE_COUNT source files modified without a lore entry."
    fi
  fi
fi

# --- Checks 8, 10, 11: Unified compliance check (single Node.js process) ---
# Replaces 3 separate subprocess calls (habits check, drift check, portal check)
# with a single \`paradigm compliance-check\` invocation.
COMPLIANCE_RESULT=""
if command -v paradigm >/dev/null 2>&1; then
  COMPLIANCE_RESULT=$(paradigm compliance-check --json --auto-heal --learn --trigger on-stop 2>/dev/null) || true
elif command -v npx >/dev/null 2>&1; then
  COMPLIANCE_RESULT=$(npx paradigm compliance-check --json --auto-heal --learn --trigger on-stop 2>/dev/null) || true
fi

if [ -n "$COMPLIANCE_RESULT" ]; then
  # --- Check 8: Blocking habits (from unified result) ---
  _SEV=$(_check_severity "habits-blocking" "block")
  if [ "$_SEV" != "off" ] && [ -f ".paradigm/.habits-blocking" ]; then
    HABITS_BLOCKING=$(cat ".paradigm/.habits-blocking")
    if [ "$_SEV" = "block" ]; then
      VIOLATIONS="$VIOLATIONS
  - Blocking habit(s) not satisfied:
    $HABITS_BLOCKING
    Fix: satisfy the habit(s), or change severity to 'warn' in .paradigm/habits.yaml to make advisory."
      VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
    else
      ADVISORY="$ADVISORY
  - (warn) Blocking habit(s) not satisfied: $HABITS_BLOCKING"
    fi
  fi

  # --- Check 10: Aspect drift (from unified result) ---
  _SEV=$(_check_severity "drift-detection" "block")
  DRIFTED_COUNT=$(echo "$COMPLIANCE_RESULT" | grep -o '"driftedCount":[0-9]*' | sed 's/.*://')
  HEALED_COUNT=$(echo "$COMPLIANCE_RESULT" | grep -o '"healedCount":[0-9]*' | sed 's/.*://')

  if [ -n "$HEALED_COUNT" ] && [ "$HEALED_COUNT" -gt 0 ] 2>/dev/null; then
    echo "[paradigm] Auto-healed $HEALED_COUNT shifted anchor(s)." >&2
  fi

  if [ "$_SEV" != "off" ] && [ -n "$DRIFTED_COUNT" ] && [ "$DRIFTED_COUNT" -gt 0 ] 2>/dev/null; then
    if [ "$_SEV" = "block" ]; then
      VIOLATIONS="$VIOLATIONS
  - $DRIFTED_COUNT aspect anchor(s) have drifted (content genuinely changed).
    Run paradigm_aspect_check to review. Update anchors in .purpose files."
      VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
    else
      ADVISORY="$ADVISORY
  - (warn) $DRIFTED_COUNT aspect anchor(s) have drifted."
    fi
  fi

  # --- Check 11: Portal gate compliance (from unified result) ---
  _SEV=$(_check_severity "portal-compliance" "block")
  UNDECLARED=$(echo "$COMPLIANCE_RESULT" | grep -o '"usedButUndeclaredCount":[0-9]*' | sed 's/.*://')

  if [ "$_SEV" != "off" ] && [ -n "$UNDECLARED" ] && [ "$UNDECLARED" -gt 0 ] 2>/dev/null; then
    UNDECLARED_LIST=$(echo "$COMPLIANCE_RESULT" | grep -o '"usedButUndeclared":\\[[^]]*\\]' | sed 's/.*\\[//;s/\\].*//;s/"//g')
    if [ "$_SEV" = "block" ]; then
      VIOLATIONS="$VIOLATIONS
  - $UNDECLARED gate(s) used in code but not declared in portal.yaml:
    $UNDECLARED_LIST
    Add them to portal.yaml or use paradigm_portal_add_gate."
      VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
    else
      ADVISORY="$ADVISORY
  - (warn) $UNDECLARED gate(s) used in code but not declared in portal.yaml: $UNDECLARED_LIST"
    fi
  fi
else
  # Fallback: check habits blocking marker even if compliance-check unavailable
  _SEV=$(_check_severity "habits-blocking" "block")
  if [ "$_SEV" != "off" ] && [ -f ".paradigm/.habits-blocking" ]; then
    HABITS_BLOCKING=$(cat ".paradigm/.habits-blocking")
    if [ "$_SEV" = "block" ]; then
      VIOLATIONS="$VIOLATIONS
  - Blocking habit(s) not satisfied:
    $HABITS_BLOCKING
    Fix: satisfy the habit(s), or change severity to 'warn' in .paradigm/habits.yaml to make advisory."
      VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
    else
      ADVISORY="$ADVISORY
  - (warn) Blocking habit(s) not satisfied: $HABITS_BLOCKING"
    fi
  fi
fi

# --- Check 9: Purpose-required patterns from config.yaml ---
_SEV=$(_check_severity "purpose-required-patterns" "block")
CONFIG_FILE=".paradigm/config.yaml"
if [ "$_SEV" != "off" ] && [ -f "$CONFIG_FILE" ]; then
  MISSING_REQUIRED=""
  in_section=false
  current_pattern=""

  while IFS= read -r line; do
    # Detect start of purpose-required section
    case "$line" in
      "purpose-required:"*|"  purpose-required:"*)
        in_section=true
        continue
        ;;
    esac

    if [ "$in_section" = true ]; then
      # Check if we've left the section (new top-level or same-level key)
      case "$line" in
        "  - pattern:"*)
          current_pattern=$(echo "$line" | sed 's/.*pattern:[[:space:]]*//' | tr -d '"' | tr -d "'")
          ;;
        "    depth:"*)
          # We have pattern + depth \u2014 validate
          if [ -n "$current_pattern" ]; then
            for dir in $current_pattern; do
              [ -d "$dir" ] || continue
              if [ ! -f "$dir/.purpose" ]; then
                # Deduplicate
                case "$MISSING_REQUIRED" in
                  *"$dir"*) ;;
                  *) MISSING_REQUIRED="$MISSING_REQUIRED $dir" ;;
                esac
              fi
            done
            current_pattern=""
          fi
          ;;
        [a-z]*|[A-Z]*)
          # New top-level key \u2014 end of section
          # Handle last pattern if depth wasn't specified
          if [ -n "$current_pattern" ]; then
            for dir in $current_pattern; do
              [ -d "$dir" ] || continue
              if [ ! -f "$dir/.purpose" ]; then
                case "$MISSING_REQUIRED" in
                  *"$dir"*) ;;
                  *) MISSING_REQUIRED="$MISSING_REQUIRED $dir" ;;
                esac
              fi
            done
            current_pattern=""
          fi
          in_section=false
          ;;
      esac
    fi
  done < "$CONFIG_FILE"

  # Handle last pattern if file ended while in section
  if [ "$in_section" = true ] && [ -n "$current_pattern" ]; then
    for dir in $current_pattern; do
      [ -d "$dir" ] || continue
      if [ ! -f "$dir/.purpose" ]; then
        case "$MISSING_REQUIRED" in
          *"$dir"*) ;;
          *) MISSING_REQUIRED="$MISSING_REQUIRED $dir" ;;
        esac
      fi
    done
  fi

  if [ -n "$MISSING_REQUIRED" ]; then
    if [ "$PARADIGM_AUTO_FIX" = "1" ]; then
      # Auto-fix: create stub .purpose files for required patterns
      for dir in $MISSING_REQUIRED; do
        dir_basename=$(basename "$dir")
        cat > "$dir/.purpose" <<PURPOSEEOF
# Auto-generated .purpose stub (purpose-required) \u2014 update with real descriptions
components:
  $dir_basename:
    description: "TODO: describe this component"
    tags: []
PURPOSEEOF
        AUTO_FIXED="$AUTO_FIXED
  - Created stub .purpose in $dir (purpose-required pattern)"
        AUTO_FIX_COUNT=$((AUTO_FIX_COUNT + 1))
      done
    elif [ "$_SEV" = "block" ]; then
      VIOLATIONS="$VIOLATIONS
  - These directories match purpose-required patterns but have no .purpose file:
   $MISSING_REQUIRED
    Create .purpose files: paradigm_purpose_init + paradigm_purpose_add_component."
      VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
    else
      ADVISORY="$ADVISORY
  - (warn) Directories matching purpose-required patterns without .purpose file:
   $MISSING_REQUIRED"
    fi
  fi
fi

# --- Check 12: Graduation failure tracking ---
# When violations occur for graduated habits, record failures for auto-demotion.
_SEV=$(_check_severity "graduation-tracking" "warn")
if [ "$_SEV" != "off" ] && [ "$VIOLATION_COUNT" -gt 0 ] && [ -f ".paradigm/graduation.yaml" ]; then
  GRAD_FAILURES_DIR=".paradigm/.graduation-failures"
  mkdir -p "$GRAD_FAILURES_DIR" 2>/dev/null

  # Map violations to graduated habit IDs
  # Check 1/2/5 \u2192 purpose-coverage, Check 3/11 \u2192 gates-for-routes, Check 7 \u2192 record-lore-for-significant
  NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date +%Y-%m-%dT%H:%M:%SZ)

  # Purpose coverage violations \u2192 purpose-coverage habit
  if echo "$VIOLATIONS" | grep -q "source file.*without .purpose\\|missing .purpose\\|purpose.*stale" 2>/dev/null; then
    if grep -q "purpose-coverage" ".paradigm/graduation.yaml" 2>/dev/null && grep -A1 "purpose-coverage" ".paradigm/graduation.yaml" | grep -q "tier: hook" 2>/dev/null; then
      echo "$NOW" >> "$GRAD_FAILURES_DIR/purpose-coverage"
    fi
  fi

  # Portal gate violations \u2192 gates-for-routes habit
  if echo "$VIOLATIONS" | grep -q "portal.yaml\\|gate.*undeclared\\|gate.*not declared" 2>/dev/null; then
    if grep -q "gates-for-routes" ".paradigm/graduation.yaml" 2>/dev/null && grep -A1 "gates-for-routes" ".paradigm/graduation.yaml" | grep -q "tier: hook" 2>/dev/null; then
      echo "$NOW" >> "$GRAD_FAILURES_DIR/gates-for-routes"
    fi
  fi

  # Lore entry violations \u2192 record-lore-for-significant habit
  if echo "$VIOLATIONS" | grep -q "lore entry expected\\|no lore" 2>/dev/null; then
    if grep -q "record-lore-for-significant" ".paradigm/graduation.yaml" 2>/dev/null && grep -A1 "record-lore-for-significant" ".paradigm/graduation.yaml" | grep -q "tier: hook" 2>/dev/null; then
      echo "$NOW" >> "$GRAD_FAILURES_DIR/record-lore-for-significant"
    fi
  fi

  # Count recent failures and emit advisory if approaching demotion threshold
  for fail_file in "$GRAD_FAILURES_DIR"/*; do
    [ -f "$fail_file" ] || continue
    habit_id=$(basename "$fail_file")
    fail_count=$(wc -l < "$fail_file" | tr -d ' ')
    if [ "$fail_count" -ge 3 ]; then
      ADVISORY="$ADVISORY
  - Graduated habit '$habit_id' has $fail_count failures \u2014 auto-demotion triggered.
    Run paradigm_graduate_status to review tier changes."
    elif [ "$fail_count" -ge 2 ]; then
      ADVISORY="$ADVISORY
  - Graduated habit '$habit_id' has $fail_count failures (demotion at 3).
    Fix the underlying issue or it will be demoted to habit tier."
    fi
  done
fi

# --- Check 13: Orchestration required for complex tasks (magnitude-based) ---
_SEV=$(_check_severity "orchestration-required" "warn")
if [ "$_SEV" != "off" ]; then
  # Compute magnitude score from multiple signals
  MAGNITUDE=0
  MAGNITUDE_REASONS=""

  # Signal 1: Source files modified (1 point each)
  MAGNITUDE=$((MAGNITUDE + SOURCE_COUNT))
  [ "$SOURCE_COUNT" -gt 0 ] && MAGNITUDE_REASONS="$SOURCE_COUNT source files"

  # Signal 2: Cross-package changes (2 points \u2014 different packages/ dirs)
  CROSS_PKG_COUNT=0
  SEEN_PKGS=""
  for file in $MODIFIED; do
    case "$file" in
      packages/*)
        _pkg=$(echo "$file" | cut -d'/' -f2)
        case "$SEEN_PKGS" in
          *"|$_pkg|"*) ;;
          *)
            SEEN_PKGS="$SEEN_PKGS|$_pkg|"
            CROSS_PKG_COUNT=$((CROSS_PKG_COUNT + 1))
            ;;
        esac
        ;;
    esac
  done
  if [ "$CROSS_PKG_COUNT" -ge 2 ]; then
    MAGNITUDE=$((MAGNITUDE + 2))
    MAGNITUDE_REASONS="$MAGNITUDE_REASONS, $CROSS_PKG_COUNT packages"
  fi

  # Signal 3: Security-adjacent files (2 points each)
  SEC_ADJACENT=0
  for file in $MODIFIED; do
    case "$file" in
      portal.yaml|*/portal.yaml) SEC_ADJACENT=$((SEC_ADJACENT + 1)) ;;
      *auth*|*permission*|*gate*|*security*|*token*|*session*) SEC_ADJACENT=$((SEC_ADJACENT + 1)) ;;
    esac
  done
  if [ "$SEC_ADJACENT" -gt 0 ]; then
    MAGNITUDE=$((MAGNITUDE + SEC_ADJACENT * 2))
    MAGNITUDE_REASONS="$MAGNITUDE_REASONS, $SEC_ADJACENT security-adjacent"
  fi

  # Signal 4: Symbol-bearing files (1 point if .purpose files were changed alongside source)
  SYMBOL_FILES=0
  for file in $MODIFIED; do
    case "$file" in
      *.purpose) SYMBOL_FILES=$((SYMBOL_FILES + 1)) ;;
    esac
  done
  if [ "$SYMBOL_FILES" -ge 2 ]; then
    MAGNITUDE=$((MAGNITUDE + 1))
    MAGNITUDE_REASONS="$MAGNITUDE_REASONS, $SYMBOL_FILES .purpose files"
  fi

  # Compare magnitude against threshold (default 3)
  if [ "$MAGNITUDE" -ge "$ORCH_THRESHOLD" ]; then
    if [ ! -f ".paradigm/.orchestrated" ]; then
      if [ "$_SEV" = "block" ]; then
        VIOLATIONS="$VIOLATIONS
  - Task magnitude $MAGNITUDE >= $ORCH_THRESHOLD without orchestration ($MAGNITUDE_REASONS).
    Run paradigm_orchestrate_inline mode=\\"quick\\" for fast pre-check.
    Override: paradigm enforcement override orchestration-required warn"
        VIOLATION_COUNT=$((VIOLATION_COUNT + 1))
      else
        ADVISORY="$ADVISORY
  - (orchestration) Magnitude $MAGNITUDE ($MAGNITUDE_REASONS) without team orchestration."
      fi
    fi
  fi
fi

# --- Compliance snapshot (non-fatal, fire-and-forget) ---
# Record this stop-hook run to .paradigm/events/compliance-history.jsonl for trend analysis.
# VIOLATION_COUNT and SOURCE_COUNT are now final at this point.
_compliance_snapshot() {
  _ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%dT%H:%M:%SZ")
  _checks=$((SOURCE_COUNT + PARADIGM_COUNT))
  [ "$_checks" -eq 0 ] && _checks=1  # Avoid divide-by-zero downstream
  _dir=".paradigm/events"
  mkdir -p "$_dir" 2>/dev/null || return
  printf '{"timestamp":"%s","violations":%d,"warnings":0,"checks":%d}\\n' \\
    "$_ts" "$VIOLATION_COUNT" "$_checks" >> "$_dir/compliance-history.jsonl" 2>/dev/null || true
}
_compliance_snapshot
`,R=`#!/bin/sh
# Paradigm Claude Code Stop Hook (v2)
# Validates paradigm compliance before allowing the agent to finish.
# Installed by: paradigm hooks install --claude-code
#
# Hook type: Stop
# Exit 0 = allow, Exit 2 = block with message
#
# Checks 1\u201313 are defined in paradigm-common.sh (shared with Cursor hook).

# Read JSON from stdin (hook input)
INPUT=$(cat)

# Extract cwd from input (try jq first, fallback to grep)
if command -v jq >/dev/null 2>&1; then
  CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
else
  CWD=$(echo "$INPUT" | grep -o '"cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"cwd"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
fi

if [ -z "$CWD" ]; then
  CWD="$(pwd)"
fi

# Not a paradigm project \u2014 pass
if [ ! -d "$CWD/.paradigm" ]; then
  exit 0
fi

cd "$CWD" || exit 0

# Get modified files (uncommitted changes)
MODIFIED=$(git diff --name-only HEAD 2>/dev/null)
if [ -z "$MODIFIED" ]; then
  # Clean up session markers on pass (no modifications)
  rm -f ".paradigm/.pending-review"
  rm -f ".paradigm/.session-started"
  exit 0
fi

# Source shared compliance checks
SCRIPT_DIR=$(dirname "$0")
. "$SCRIPT_DIR/paradigm-common.sh"

# --- Report auto-fixes if any ---
if [ "$AUTO_FIX_COUNT" -gt 0 ]; then
  echo "" >&2
  echo "[paradigm] Auto-fixed $AUTO_FIX_COUNT issue(s):" >&2
  echo "$AUTO_FIXED" >&2
fi

# --- Final verdict ---
if [ "$VIOLATION_COUNT" -gt 0 ]; then
  # Emit compliance-violation event (fire-and-forget, backgrounded)
  # NOTE: Could be absorbed into compliance-check command in a future iteration.
  if command -v paradigm >/dev/null 2>&1; then
    paradigm event emit --type compliance-violation --source stop-hook --severity error --context "Stop hook: $VIOLATION_COUNT violation(s)" &
  fi
  echo "" >&2
  echo "Paradigm compliance check failed ($VIOLATION_COUNT violation(s)):" >&2
  echo "$VIOLATIONS" >&2
  if [ -n "$ADVISORY" ]; then
    echo "" >&2
    echo "Advisory:" >&2
    echo "$ADVISORY" >&2
  fi
  echo "" >&2
  echo "Run these MCP tools to fix:" >&2
  echo "  1. paradigm_purpose_add_component \u2014 register new code units" >&2
  echo "  2. paradigm_purpose_add_aspect \u2014 register cross-cutting concerns (with anchors)" >&2
  echo "  3. paradigm_portal_add_route \u2014 register new endpoints with gates" >&2
  echo "  4. paradigm_reindex \u2014 rebuild indexes after updates" >&2
  echo "  5. paradigm_lore_record \u2014 record session lore entry" >&2
  echo "  6. paradigm_habits_check \u2014 evaluate habit compliance" >&2
  echo "" >&2
  echo "Tip: Set PARADIGM_AUTO_FIX=1 to auto-fix trivial violations (missing .purpose stubs, missing lore)." >&2
  exit 2
fi

# Print advisory even on pass (informational)
if [ -n "$ADVISORY" ]; then
  echo "" >&2
  echo "[paradigm] Advisory:" >&2
  echo "$ADVISORY" >&2
fi

# Auto-demote graduated habits with 3+ failures
# NOTE: Could be absorbed into compliance-check command in a future iteration.
if [ -d ".paradigm/.graduation-failures" ]; then
  for fail_file in .paradigm/.graduation-failures/*; do
    [ -f "$fail_file" ] || continue
    habit_id=$(basename "$fail_file")
    fail_count=$(wc -l < "$fail_file" | tr -d ' ')
    if [ "$fail_count" -ge 3 ]; then
      if command -v paradigm >/dev/null 2>&1; then
        paradigm graduate demote "$habit_id" --cooldown 14 2>/dev/null || true
      fi
      rm -f "$fail_file"
      echo "[paradigm] Auto-demoted '$habit_id' after $fail_count failures." >&2
    fi
  done
fi

# Clean up session markers on pass
rm -f ".paradigm/.pending-review"
rm -f ".paradigm/.habits-blocking"
rm -f ".paradigm/.session-started"
rm -f ".paradigm/.purpose-paths"
rm -f ".paradigm/.orchestrated"

exit 0
`,A=`#!/bin/sh
# Legacy afterFileEdit hook \u2014 replaced by paradigm-posttooluse.sh (postToolUse)
# Kept as a no-op because Claude Code expects the file to exist.
exit 0
`,U=`#!/bin/sh
# Paradigm Claude Code Pre-Commit Hook
# Intercepts git commit Bash calls and auto-rebuilds the index.
# Installed by: paradigm hooks install --claude-code
#
# Hook type: PreToolUse (matcher: Bash)
# Exit 0 = allow (never blocks), just ensures index is fresh

# Read JSON from stdin (hook input)
INPUT=$(cat)

# Extract the command from tool_input
if command -v jq >/dev/null 2>&1; then
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
else
  COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"command"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
fi

# If command doesn't contain "git commit", pass through
case "$COMMAND" in
  *"git commit"*) ;;
  *) exit 0 ;;
esac

# If no .paradigm directory, not a paradigm project
if [ ! -d ".paradigm" ]; then
  exit 0
fi

# Run paradigm index --quiet (the existing CLI command)
if command -v paradigm >/dev/null 2>&1; then
  paradigm index --quiet 2>/dev/null || true
elif command -v npx >/dev/null 2>&1; then
  npx paradigm index --quiet 2>/dev/null || true
fi

# Stage the rebuilt files if they exist
for f in .paradigm/scan-index.json .paradigm/navigator.yaml .paradigm/flow-index.json; do
  if [ -f "$f" ]; then
    git add "$f" 2>/dev/null || true
  fi
done

# Never block \u2014 exit 0
exit 0
`,D=`#!/bin/sh
# Paradigm Cursor Session Start Hook
# Fires before the agent does anything \u2014 injects additional_context
# that acts as a deterministic system prompt (not subject to context compaction).
# Installed by: paradigm hooks install --cursor
#
# Hook type: sessionStart
# Output: JSON with additional_context + continue: true
# Exit 0 always (never blocks)

# Read JSON from stdin (hook input)
INPUT=$(cat)

# Extract workspace root from Cursor's input
if command -v jq >/dev/null 2>&1; then
  CWD=$(echo "$INPUT" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
else
  CWD=$(echo "$INPUT" | grep -o '"workspace_roots"[[:space:]]*:[[:space:]]*\\["[^"]*"' | head -1 | sed 's/.*\\["//' | sed 's/"$//')
fi

if [ -z "$CWD" ]; then
  CWD="$(pwd)"
fi

# Not a paradigm project \u2014 pass without injection
if [ ! -d "$CWD/.paradigm" ]; then
  echo '{"continue":true}'
  exit 0
fi

# Build the additional_context payload
# This is injected as a system-level context that survives context compaction.

# Detect project characteristics for task-size guidance
HAS_PORTAL="false"
if [ -f "$CWD/portal.yaml" ]; then
  HAS_PORTAL="true"
fi

HAS_LORE="false"
if [ -d "$CWD/.paradigm/lore" ]; then
  HAS_LORE="true"
fi

HAS_FLOWS="false"
if [ -f "$CWD/.paradigm/flow-index.json" ]; then
  HAS_FLOWS="true"
fi

# Build context string (escaped for JSON)
CONTEXT="MANDATORY PARADIGM PROTOCOL \u2014 These rules are NON-NEGOTIABLE:\\\\n\\\\n"
CONTEXT="\${CONTEXT}Rule 1: SESSION BOOKENDS\\\\n"
CONTEXT="\${CONTEXT}  - FIRST action: Call paradigm_session_recover() to load previous session context\\\\n"
CONTEXT="\${CONTEXT}  - LAST action before finishing: Call paradigm_pm_postflight() with filesModified and symbolsTouched\\\\n\\\\n"

CONTEXT="\${CONTEXT}Rule 2: .PURPOSE UPDATES\\\\n"
CONTEXT="\${CONTEXT}  - Every source file you modify MUST have a covering .purpose file\\\\n"
CONTEXT="\${CONTEXT}  - Update the nearest .purpose file with: #components, ~aspects (with anchors), !signals\\\\n"
CONTEXT="\${CONTEXT}  - The STOP HOOK WILL BLOCK you if .purpose files are not updated\\\\n\\\\n"

CONTEXT="\${CONTEXT}Rule 3: RIPPLE BEFORE MODIFY\\\\n"
CONTEXT="\${CONTEXT}  - Before modifying any existing symbol, call paradigm_ripple({ symbol: \\\\\\"#symbol-name\\\\\\" })\\\\n"
CONTEXT="\${CONTEXT}  - This shows the blast radius \u2014 what else will break if you change it\\\\n\\\\n"

CONTEXT="\${CONTEXT}ESSENTIAL MCP TOOLS:\\\\n"
CONTEXT="\${CONTEXT}  paradigm_session_recover()          \u2014 Load previous session (call FIRST)\\\\n"
CONTEXT="\${CONTEXT}  paradigm_ripple({ symbol })          \u2014 Check impact before modifying\\\\n"
CONTEXT="\${CONTEXT}  paradigm_pm_postflight({ filesModified, symbolsTouched }) \u2014 Compliance check (call LAST)\\\\n"
CONTEXT="\${CONTEXT}  paradigm_purpose_add_component({ path, name, description }) \u2014 Register code units\\\\n"
CONTEXT="\${CONTEXT}  paradigm_reindex()                   \u2014 Rebuild indexes after .purpose changes\\\\n"
CONTEXT="\${CONTEXT}  paradigm_lore_record({ type, title, summary, symbols_touched }) \u2014 Record session\\\\n"

if [ "$HAS_PORTAL" = "true" ]; then
  CONTEXT="\${CONTEXT}  paradigm_gates_for_route({ method, path }) \u2014 Get auth gate suggestions for endpoints\\\\n"
  CONTEXT="\${CONTEXT}  paradigm_portal_add_route({ method, path, gates }) \u2014 Register route with gates\\\\n"
fi

CONTEXT="\${CONTEXT}\\\\nTASK-SIZE TIERS:\\\\n"
CONTEXT="\${CONTEXT}  1 file:   Session bookends only (recover + postflight)\\\\n"
CONTEXT="\${CONTEXT}  2-3 files: + ripple before modify + update .purpose files\\\\n"
CONTEXT="\${CONTEXT}  3+ files:  + full workflow (ripple, .purpose, lore entry"

if [ "$HAS_PORTAL" = "true" ]; then
  CONTEXT="\${CONTEXT}, portal.yaml for routes"
fi

if [ "$HAS_FLOWS" = "true" ]; then
  CONTEXT="\${CONTEXT}, flow validation"
fi

CONTEXT="\${CONTEXT})\\\\n"

if [ "$HAS_LORE" = "true" ]; then
  CONTEXT="\${CONTEXT}\\\\nLORE: This project tracks session history. Record a lore entry when modifying 3+ source files.\\\\n"
fi

# Output JSON to stdout
printf '{"additional_context":"%s","continue":true}\\n' "$CONTEXT"

exit 0
`,v=`#!/bin/sh
# Paradigm Cursor Stop Hook (v2)
# Validates paradigm compliance before allowing the agent to finish.
# Installed by: paradigm hooks install --cursor
#
# Hook type: stop
# Exit 0 = allow, Exit 2 = block with message
#
# Checks 1\u201313 are defined in paradigm-common.sh (shared with Claude Code hook).

# Read JSON from stdin (hook input)
INPUT=$(cat)

# Extract workspace root from Cursor's input (try jq first, fallback to grep)
if command -v jq >/dev/null 2>&1; then
  CWD=$(echo "$INPUT" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
else
  CWD=$(echo "$INPUT" | grep -o '"workspace_roots"[[:space:]]*:[[:space:]]*\\["[^"]*"' | head -1 | sed 's/.*\\["//' | sed 's/"$//')
fi

if [ -z "$CWD" ]; then
  CWD="$(pwd)"
fi

# Not a paradigm project \u2014 pass
if [ ! -d "$CWD/.paradigm" ]; then
  exit 0
fi

cd "$CWD" || exit 0

# --- Loop guard: prevent infinite retry loops ---
# Cursor's stop hook with loop_limit fires repeatedly. Cap retries at 3.
LOOP_GUARD_FILE=".paradigm/.stop-hook-active"
if [ -f "$LOOP_GUARD_FILE" ]; then
  RETRY_COUNT=$(cat "$LOOP_GUARD_FILE" 2>/dev/null | tr -d '[:space:]')
  RETRY_COUNT=\${RETRY_COUNT:-0}
  if [ "$RETRY_COUNT" -ge 3 ]; then
    # Max retries reached \u2014 allow session to end to avoid infinite loop
    echo "[paradigm] Stop hook: max retries (3) reached. Allowing session to end." >&2
    rm -f "$LOOP_GUARD_FILE"
    rm -f ".paradigm/.pending-review"
    rm -f ".paradigm/.habits-blocking"
    exit 0
  fi
  RETRY_COUNT=$((RETRY_COUNT + 1))
  echo "$RETRY_COUNT" > "$LOOP_GUARD_FILE"
else
  echo "1" > "$LOOP_GUARD_FILE"
fi

# Get modified files (uncommitted changes)
MODIFIED=$(git diff --name-only HEAD 2>/dev/null)
if [ -z "$MODIFIED" ]; then
  # Clean up session markers on pass (no modifications)
  rm -f ".paradigm/.pending-review"
  rm -f ".paradigm/.session-started"
  exit 0
fi

# Source shared compliance checks
SCRIPT_DIR=$(dirname "$0")
. "$SCRIPT_DIR/paradigm-common.sh"

# --- Report auto-fixes if any ---
if [ "$AUTO_FIX_COUNT" -gt 0 ]; then
  echo "" >&2
  echo "[paradigm] Auto-fixed $AUTO_FIX_COUNT issue(s):" >&2
  echo "$AUTO_FIXED" >&2
fi

# --- Final verdict ---
if [ "$VIOLATION_COUNT" -gt 0 ]; then
  echo "" >&2
  echo "Paradigm compliance check failed ($VIOLATION_COUNT violation(s)):" >&2
  echo "$VIOLATIONS" >&2
  if [ -n "$ADVISORY" ]; then
    echo "" >&2
    echo "Advisory:" >&2
    echo "$ADVISORY" >&2
  fi
  echo "" >&2
  echo "Run these MCP tools to fix:" >&2
  echo "  1. paradigm_purpose_add_component \u2014 register new code units" >&2
  echo "  2. paradigm_purpose_add_aspect \u2014 register cross-cutting concerns (with anchors)" >&2
  echo "  3. paradigm_portal_add_route \u2014 register new endpoints with gates" >&2
  echo "  4. paradigm_reindex \u2014 rebuild indexes after updates" >&2
  echo "  5. paradigm_lore_record \u2014 record session lore entry" >&2
  echo "  6. paradigm_habits_check \u2014 evaluate habit compliance" >&2
  echo "" >&2
  echo "Tip: Set PARADIGM_AUTO_FIX=1 to auto-fix trivial violations (missing .purpose stubs, missing lore)." >&2

  # Output followup_message JSON to stdout for Cursor's compliance loop.
  # Cursor auto-submits this as the next user message, creating a retry loop.
  # Escape violations for JSON embedding (newlines \u2192 \\n, quotes \u2192 \\", backslash \u2192 \\\\)
  ESCAPED_VIOLATIONS=$(printf '%s' "$VIOLATIONS" | sed 's/\\\\/\\\\\\\\/g' | sed 's/"/\\\\"/g' | sed ':a;N;$!ba;s/\\n/\\\\n/g')
  printf '{"followup_message":"Paradigm compliance check found %d violation(s). Fix these:\\\\n%s\\\\nThen try finishing again."}\\n' "$VIOLATION_COUNT" "$ESCAPED_VIOLATIONS"

  exit 2
fi

# Print advisory even on pass (informational)
if [ -n "$ADVISORY" ]; then
  echo "" >&2
  echo "[paradigm] Advisory:" >&2
  echo "$ADVISORY" >&2
fi

# Auto-demote graduated habits with 3+ failures
if [ -d ".paradigm/.graduation-failures" ]; then
  for fail_file in .paradigm/.graduation-failures/*; do
    [ -f "$fail_file" ] || continue
    habit_id=$(basename "$fail_file")
    fail_count=$(wc -l < "$fail_file" | tr -d ' ')
    if [ "$fail_count" -ge 3 ]; then
      if command -v paradigm >/dev/null 2>&1; then
        paradigm graduate demote "$habit_id" --cooldown 14 2>/dev/null || true
      fi
      rm -f "$fail_file"
      echo "[paradigm] Auto-demoted '$habit_id' after $fail_count failures." >&2
    fi
  done
fi

# Clean up session markers and loop guard on pass
rm -f ".paradigm/.pending-review"
rm -f ".paradigm/.habits-blocking"
rm -f ".paradigm/.stop-hook-active"
rm -f ".paradigm/.session-started"
rm -f ".paradigm/.purpose-paths"
rm -f ".paradigm/.orchestrated"

exit 0
`,P=`#!/bin/sh
# Legacy afterFileEdit hook \u2014 replaced by paradigm-posttooluse.sh (postToolUse)
# Kept as a no-op because Cursor expects the file to exist.
exit 0
`,b=`#!/bin/sh
# Paradigm Cursor Pre-Commit Hook
# Intercepts git commit shell executions and auto-rebuilds the index.
# Installed by: paradigm hooks install --cursor
#
# Hook type: beforeShellExecution (matcher: "git commit")
# Exit 0 = allow (never blocks), just ensures index is fresh

# Read JSON from stdin (hook input)
INPUT=$(cat)

# Extract the command from Cursor's beforeShellExecution input
if command -v jq >/dev/null 2>&1; then
  COMMAND=$(echo "$INPUT" | jq -r '.command // .shellCommand // empty' 2>/dev/null)
else
  COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"command"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
fi

# If command doesn't contain "git commit", pass through
case "$COMMAND" in
  *"git commit"*) ;;
  *) exit 0 ;;
esac

# If no .paradigm directory, not a paradigm project
if [ ! -d ".paradigm" ]; then
  exit 0
fi

# Run paradigm index --quiet (the existing CLI command)
if command -v paradigm >/dev/null 2>&1; then
  paradigm index --quiet 2>/dev/null || true
elif command -v npx >/dev/null 2>&1; then
  npx paradigm index --quiet 2>/dev/null || true
fi

# Stage the rebuilt files if they exist
for f in .paradigm/scan-index.json .paradigm/navigator.yaml .paradigm/flow-index.json; do
  if [ -f "$f" ]; then
    git add "$f" 2>/dev/null || true
  fi
done

# Never block \u2014 exit 0
exit 0
`,L=`#!/bin/sh
# Paradigm Cursor PreToolUse Hook \u2014 Graduated Blocking
# Fires BEFORE the agent calls Edit or Write.
# Uses graduated enforcement based on uncovered source edits.
# Installed by: paradigm hooks install --cursor
#
# Hook type: preToolUse
# Matcher: Edit|Write
# Exit 0 = allow, Exit 2 = block with stderr message
#
# Graduated enforcement:
#   1-2 uncovered edits \u2192 silent pass (exit 0)
#   3-4 uncovered edits \u2192 warn via stderr (exit 0)
#   5+  uncovered edits \u2192 BLOCK (exit 2 + stderr)

# Read JSON from stdin (hook input)
INPUT=$(cat)

# Extract tool_name and file_path from preToolUse input
if command -v jq >/dev/null 2>&1; then
  TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
  FILE_PATH=$(echo "$INPUT" | jq -r '.file_path // .input.file_path // empty' 2>/dev/null)
else
  TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"tool_name"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
  FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
fi

# Must have a file path to check
if [ -z "$FILE_PATH" ]; then
  exit 0
fi

# Extract workspace root
if command -v jq >/dev/null 2>&1; then
  CWD=$(echo "$INPUT" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
else
  CWD=$(echo "$INPUT" | grep -o '"workspace_roots"[[:space:]]*:[[:space:]]*\\["[^"]*"' | head -1 | sed 's/.*\\["//' | sed 's/"$//')
fi

if [ -z "$CWD" ]; then
  CWD="$(pwd)"
fi

# Not a paradigm project \u2014 pass
if [ ! -d "$CWD/.paradigm" ]; then
  exit 0
fi

cd "$CWD" || exit 0

# Convert to relative path
REL_PATH="$FILE_PATH"
case "$FILE_PATH" in
  "$CWD"/*) REL_PATH=$(echo "$FILE_PATH" | sed "s|^$CWD/||") ;;
esac

# If still absolute, file is outside project \u2014 skip
case "$REL_PATH" in
  /*) exit 0 ;;
esac

# Skip non-source files (paradigm metadata, docs, config)
case "$REL_PATH" in
  *.purpose|portal.yaml|*.md|*.lock|*.log|*.json|*.yaml|*.yml|.gitignore|.env*) exit 0 ;;
esac

# Skip .paradigm, .claude, and .cursor directories
case "$REL_PATH" in
  .paradigm/*|.claude/*|.cursor/*) exit 0 ;;
esac

# Check if target file has a covering .purpose file
dir=$(dirname "$REL_PATH")
has_purpose=false

while [ "$dir" != "." ] && [ "$dir" != "/" ] && [ "$dir" != "" ]; do
  if [ -f "$dir/.purpose" ]; then
    has_purpose=true
    break
  fi
  dir=$(dirname "$dir")
done

# Check root .purpose
if [ "$has_purpose" = false ] && [ -f ".purpose" ]; then
  has_purpose=true
fi

# If this file already has .purpose coverage, always allow
if [ "$has_purpose" = true ]; then
  exit 0
fi

# Count uncovered source edits from .pending-review
PENDING_FILE=".paradigm/.pending-review"
UNCOVERED_COUNT=0

if [ -f "$PENDING_FILE" ]; then
  while IFS= read -r tracked_file; do
    [ -z "$tracked_file" ] && continue
    # Check if this tracked file has .purpose coverage
    check_dir=$(dirname "$tracked_file")
    found=false
    while [ "$check_dir" != "." ] && [ "$check_dir" != "/" ] && [ "$check_dir" != "" ]; do
      if [ -f "$check_dir/.purpose" ]; then
        found=true
        break
      fi
      check_dir=$(dirname "$check_dir")
    done
    if [ "$found" = false ] && [ -f ".purpose" ]; then
      found=true
    fi
    if [ "$found" = false ]; then
      UNCOVERED_COUNT=$((UNCOVERED_COUNT + 1))
    fi
  done < "$PENDING_FILE"
fi

# Include the current file (not yet tracked)
UNCOVERED_COUNT=$((UNCOVERED_COUNT + 1))

# Graduated enforcement
if [ "$UNCOVERED_COUNT" -le 2 ]; then
  # Silent pass \u2014 don't slow down small fixes
  exit 0
elif [ "$UNCOVERED_COUNT" -le 4 ]; then
  # Warn but allow
  echo "" >&2
  echo "[paradigm] Warning: $UNCOVERED_COUNT source files modified without .purpose coverage." >&2
  echo "  Update the nearest .purpose file before the stop hook blocks you." >&2
  echo "  Use: paradigm_purpose_init + paradigm_purpose_add_component" >&2
  exit 0
else
  # Block \u2014 too many uncovered edits
  echo "" >&2
  echo "[paradigm] BLOCKED: $UNCOVERED_COUNT source files modified without .purpose coverage." >&2
  echo "  You must update .purpose files before continuing." >&2
  echo "  Steps:" >&2
  echo "    1. paradigm_purpose_init \u2014 create .purpose in uncovered directories" >&2
  echo "    2. paradigm_purpose_add_component \u2014 register code units" >&2
  echo "    3. paradigm_reindex \u2014 rebuild the index" >&2
  echo "  Then retry your edit." >&2
  exit 2
fi
`,w=`#!/bin/sh
# Paradigm Cursor PostToolUse Hook \u2014 Advisory Feedback
# Fires AFTER the agent calls Edit or Write.
# Tracks modified source files and outputs advisory the agent can see.
# Installed by: paradigm hooks install --cursor
#
# Hook type: postToolUse
# Matcher: Edit|Write
# Exit 0 always (never blocks \u2014 advisory only)
#
# Unlike afterFileEdit, postToolUse output is visible to the Cursor agent.
# This is the primary advisory mechanism for Cursor enforcement.

# Read JSON from stdin (hook input)
INPUT=$(cat)

# Extract file_path from postToolUse input
if command -v jq >/dev/null 2>&1; then
  FILE_PATH=$(echo "$INPUT" | jq -r '.file_path // .input.file_path // empty' 2>/dev/null)
else
  FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
fi

if [ -z "$FILE_PATH" ]; then
  exit 0
fi

# Extract workspace root
if command -v jq >/dev/null 2>&1; then
  CWD=$(echo "$INPUT" | jq -r '.workspace_roots[0] // empty' 2>/dev/null)
else
  CWD=$(echo "$INPUT" | grep -o '"workspace_roots"[[:space:]]*:[[:space:]]*\\["[^"]*"' | head -1 | sed 's/.*\\["//' | sed 's/"$//')
fi

if [ -z "$CWD" ]; then
  CWD="$(pwd)"
fi

# Not a paradigm project \u2014 pass
if [ ! -d "$CWD/.paradigm" ]; then
  exit 0
fi

cd "$CWD" || exit 0

# Pseudo-session-start: first edit of session emits one-time guidance
if [ ! -f ".paradigm/.session-started" ]; then
  PREV_PENDING=$(cat .paradigm/.pending-review 2>/dev/null | wc -l | tr -d ' ')
  if [ "$PREV_PENDING" -gt 0 ] 2>/dev/null; then
    echo "[paradigm] Session started. $PREV_PENDING uncovered edit(s) from last session." >&2
  fi
  touch ".paradigm/.session-started"
fi

# Convert to relative path
REL_PATH="$FILE_PATH"
case "$FILE_PATH" in
  "$CWD"/*) REL_PATH=$(echo "$FILE_PATH" | sed "s|^$CWD/||") ;;
esac

# If still absolute, file is outside project \u2014 skip
case "$REL_PATH" in
  /*) exit 0 ;;
esac

# Skip non-source files
case "$REL_PATH" in
  *.purpose|portal.yaml|*.md|*.lock|*.log|*.json|*.yaml|*.yml|.gitignore|.env*) exit 0 ;;
esac

# Skip .paradigm, .claude, and .cursor directories
case "$REL_PATH" in
  .paradigm/*|.claude/*|.cursor/*) exit 0 ;;
esac

# Track: append to .paradigm/.pending-review (deduplicated)
PENDING_FILE=".paradigm/.pending-review"
if [ -f "$PENDING_FILE" ]; then
  if ! grep -qxF "$REL_PATH" "$PENDING_FILE" 2>/dev/null; then
    echo "$REL_PATH" >> "$PENDING_FILE"
  fi
else
  echo "$REL_PATH" > "$PENDING_FILE"
fi

# Count pending files
PENDING_COUNT=$(wc -l < "$PENDING_FILE" | tr -d ' ')

# Walk up from the file's directory to find a .purpose file
dir=$(dirname "$REL_PATH")
found_purpose=""

while [ "$dir" != "." ] && [ "$dir" != "/" ] && [ "$dir" != "" ]; do
  if [ -f "$dir/.purpose" ]; then
    found_purpose="$dir/.purpose"
    break
  fi
  dir=$(dirname "$dir")
done

# Check root .purpose
if [ -z "$found_purpose" ] && [ -f ".purpose" ]; then
  found_purpose=".purpose"
fi

if [ -z "$found_purpose" ]; then
  file_dir=$(dirname "$REL_PATH")
  echo "" >&2
  echo "[paradigm] No .purpose file covers $file_dir/" >&2
  echo "  Create one: paradigm_purpose_init + paradigm_purpose_add_component" >&2
  echo "  $PENDING_COUNT file(s) pending review. The stop hook WILL BLOCK." >&2
elif [ "$PENDING_COUNT" -gt 0 ] && [ "$((PENDING_COUNT % 3))" -eq 0 ]; then
  echo "" >&2
  echo "[paradigm] $PENDING_COUNT source file(s) modified. Update $found_purpose:" >&2
  echo "  -> #components, ~aspects (with anchors), !signals, \\$flows, ^gates" >&2
  echo "  The stop hook WILL BLOCK if .purpose files aren't updated." >&2
fi

# Context budget heuristic: suggest handoff check at high edit counts
if [ "$PENDING_COUNT" -ge 30 ]; then
  echo "[paradigm] ~$PENDING_COUNT edits this session. Consider preparing handoff." >&2
fi

exit 0
`;function x(){try{let i=r.join(E.homedir(),".claude","settings.json");if(!e.existsSync(i))return {active:!1};if(!JSON.parse(e.readFileSync(i,"utf8")).enabledPlugins?.["paradigm@a-paradigm"])return {active:!1};let d=r.join(E.homedir(),".claude","plugins","cache","a-paradigm","paradigm");if(!e.existsSync(d))return {active:!1};let a=e.readdirSync(d).filter(s=>e.statSync(r.join(d,s)).isDirectory()).sort().reverse();if(a.length===0)return {active:!1};let p=r.join(d,a[0]),l=r.join(p,"hooks","hooks.json");return e.existsSync(l)?{active:!0,cacheVersion:a[0]}:{active:!1}}catch{return {active:false}}}function Y(){try{let i=x();if(!i.active||!i.cacheVersion)return {compatible:!0};let h=r.join(E.homedir(),".claude","plugins","cache","a-paradigm","paradigm",i.cacheVersion,"hooks.json");if(!e.existsSync(h))return {compatible:!0};let d=JSON.parse(e.readFileSync(h,"utf8")).compatibleVersions;if(!d)return {compatible:!0};let a=X();if(!a)return {compatible:!0};let p=d.split(/\s+/);for(let l of p){let s=l.match(/^(>=?|<=?)\s*(\d+\.\d+\.\d+)/);if(!s)continue;let[,t,n]=s,f=W(a,n);if(t===">="&&f<0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t===">"&&f<=0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t==="<="&&f>0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`};if(t==="<"&&f>=0)return {compatible:!1,message:`Plugin requires paradigm ${d}, current: ${a}`}}return {compatible:!0}}catch{return {compatible:true}}}function X(){try{let i=r.join(r.dirname(new URL(import.meta.url).pathname),"..","..","package.json");return JSON.parse(e.readFileSync(i,"utf8")).version||null}catch{return null}}function W(i,h){let c=i.split(".").map(Number),d=h.split(".").map(Number);for(let a=0;a<3;a++){if((c[a]||0)<(d[a]||0))return  -1;if((c[a]||0)>(d[a]||0))return 1}return 0}function J(i){let h=[],c=r.join(i,".claude","hooks");if(e.existsSync(c)){for(let a of ["paradigm-common.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"]){let p=r.join(c,a);e.existsSync(p)&&(e.unlinkSync(p),h.push(a));}try{e.readdirSync(c).length===0&&e.rmdirSync(c);}catch{}}let d=r.join(i,".claude","settings.json");if(e.existsSync(d))try{let a=JSON.parse(e.readFileSync(d,"utf8")),p=a.hooks;if(p){let l=!1;for(let[s,t]of Object.entries(p)){if(!Array.isArray(t))continue;let n=t.filter(f=>!JSON.stringify(f).includes("paradigm-"));n.length!==t.length&&(l=!0,n.length===0?delete p[s]:p[s]=n);}l&&(Object.keys(p).length===0?delete a.hooks:a.hooks=p,e.writeFileSync(d,JSON.stringify(a,null,2)+`
`,"utf8"),h.push("settings.json hooks"));}}catch{}return {cleaned:h.length>0,removed:h}}function q(i,h){try{let c=r.join(E.tmpdir(),`paradigm-hook-validate-${Date.now()}.sh`);e.writeFileSync(c,i,"utf8");try{return execSync(`bash -n "${c}" 2>&1`,{encoding:"utf-8"}),null}catch(d){return `${h}: bash syntax error \u2014 ${d.message?.split(`
`)[0]||"unknown error"}`}finally{try{e.unlinkSync(c);}catch{}}}catch{return null}}var F=`#!/bin/sh
# Paradigm post-commit hook - captures history from commits
# Installed by: paradigm hooks install

# Get the commit message and hash
COMMIT_HASH=$(git rev-parse HEAD)
COMMIT_MSG=$(git log -1 --pretty=%B)

# Get changed files
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD)

# Extract symbols from changed files (look for .purpose files)
extract_symbols() {
  local symbols=""
  for file in $CHANGED_FILES; do
    # Check if there's a .purpose file in the directory
    dir=$(dirname "$file")
    while [ "$dir" != "." ]; do
      if [ -f "$dir/.purpose" ]; then
        # Extract feature/component names from .purpose
        purpose_symbols=$(grep -E '^(features|components|gates|flows):' "$dir/.purpose" -A 10 2>/dev/null | grep -E '^  - (name|id):' | sed 's/.*: //' | tr '\\n' ',' | sed 's/,$//')
        if [ -n "$purpose_symbols" ]; then
          symbols="$symbols,$purpose_symbols"
        fi
        break
      fi
      dir=$(dirname "$dir")
    done
  done
  echo "$symbols" | sed 's/^,//' | tr ',' '\\n' | sort -u | tr '\\n' ',' | sed 's/,$//'
}

SYMBOLS=$(extract_symbols)

# Extract symbols from commit message Symbols: trailer
MSG_SYMBOLS=$(echo "$COMMIT_MSG" | grep -E '^Symbols:' | sed 's/^Symbols: //' | tr -d ' ')
if [ -n "$MSG_SYMBOLS" ]; then
  if [ -n "$SYMBOLS" ]; then
    SYMBOLS="$SYMBOLS,$MSG_SYMBOLS"
  else
    SYMBOLS="$MSG_SYMBOLS"
  fi
  # Deduplicate
  SYMBOLS=$(echo "$SYMBOLS" | tr ',' '\\n' | sort -u | tr '\\n' ',' | sed 's/,$//')
fi

# Determine intent from commit message
determine_intent() {
  case "$COMMIT_MSG" in
    feat*|feature*|add*) echo "feature" ;;
    fix*|bug*) echo "fix" ;;
    refactor*) echo "refactor" ;;
    *) echo "feature" ;;
  esac
}

INTENT=$(determine_intent)

# Record if we found symbols (from .purpose or commit message) and .paradigm/history exists
if [ -n "$SYMBOLS" ] && [ -d ".paradigm/history" ]; then
  # Generate entry ID
  if [ -f ".paradigm/history/log.jsonl" ]; then
    COUNT=$(wc -l < ".paradigm/history/log.jsonl" | tr -d ' ')
    COUNT=$((COUNT + 1))
  else
    COUNT=1
  fi
  ID=$(printf "h%04d" $COUNT)

  # Create entry
  TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  AUTHOR=$(git config user.name || echo "unknown")

  # Format symbols as JSON array
  SYMBOLS_JSON=$(echo "$SYMBOLS" | sed 's/,/","/g' | sed 's/^/"/' | sed 's/$/"/')

  # Format files as JSON array
  FILES_JSON=$(echo "$CHANGED_FILES" | tr '\\n' ',' | sed 's/,$//' | sed 's/,/","/g' | sed 's/^/"/' | sed 's/$/"/')

  # Write entry
  echo "{\\"id\\":\\"$ID\\",\\"ts\\":\\"$TIMESTAMP\\",\\"type\\":\\"implement\\",\\"symbols\\":[$SYMBOLS_JSON],\\"author\\":{\\"type\\":\\"human\\",\\"id\\":\\"$AUTHOR\\"},\\"commit\\":\\"$COMMIT_HASH\\",\\"intent\\":\\"$INTENT\\",\\"files\\":[$FILES_JSON],\\"description\\":\\"$(echo "$COMMIT_MSG" | head -1 | sed 's/"/\\\\"/g')\\"}" >> .paradigm/history/log.jsonl

  echo "[paradigm] History entry $ID recorded"
fi
`,V=`#!/bin/sh
# Paradigm pre-push hook - reindex history before pushing
# Installed by: paradigm hooks install

if [ -d ".paradigm/history" ] && [ -f ".paradigm/history/log.jsonl" ]; then
  echo "[paradigm] Reindexing history..."
  npx paradigm history reindex 2>/dev/null || true
fi
`;async function te(i={}){let h=process.cwd(),c=i.dryRun||false;c&&console.log(o.cyan(`
  [dry-run] Showing what would be installed:
`));let d=i.claudeCode&&!i.postCommit&&!i.prePush&&!i.cursor,a=i.cursor&&!i.postCommit&&!i.prePush&&!i.claudeCode;if(!c){let s=[{name:"post-commit",content:F},{name:"pre-push",content:V},{name:"paradigm-common",content:N},{name:"claude-code-stop",content:R},{name:"claude-code-precommit",content:U},{name:"claude-code-postwrite",content:A},{name:"cursor-session-start",content:D},{name:"cursor-stop",content:v},{name:"cursor-precommit",content:b},{name:"cursor-postwrite",content:P},{name:"cursor-pretooluse",content:L},{name:"cursor-posttooluse",content:w}];for(let t of s){let n=q(t.content,t.name);if(n){console.log(o.red(`Hook syntax error: ${n}`)),console.log(o.gray("Aborting installation. Fix the hook script and try again."));return}}}let p=Y();if(p.compatible||(console.log(o.yellow(`
\u26A0  ${p.message}`)),console.log(o.gray(`  Hook installation will continue, but behavior may differ from plugin expectations.
`))),!d&&!a){let s=r.join(h,".git");if(!e.existsSync(s)){console.log(o.red("Not a git repository."));return}let t=r.join(s,"hooks"),n=!i.postCommit&&!i.prePush&&!i.claudeCode,f=[];if(n||i.postCommit){let u=r.join(t,"post-commit");if(c){let g=e.existsSync(u)&&!i.force?"skip (exists)":"install";console.log(o.gray(`  post-commit: ${g} \u2192 ${u}`));}else e.existsSync(u)&&!i.force?e.readFileSync(u,"utf8").includes("paradigm")?console.log(o.gray("post-commit hook already installed by paradigm")):console.log(o.yellow("post-commit hook exists. Use --force to overwrite.")):(e.mkdirSync(t,{recursive:true}),e.writeFileSync(u,F),e.chmodSync(u,"755"),f.push("post-commit"));}if(n||i.prePush){let u=r.join(t,"pre-push");if(c){let g=e.existsSync(u)&&!i.force?"skip (exists)":"install";console.log(o.gray(`  pre-push: ${g} \u2192 ${u}`));}else e.existsSync(u)&&!i.force?e.readFileSync(u,"utf8").includes("paradigm")?console.log(o.gray("pre-push hook already installed by paradigm")):console.log(o.yellow("pre-push hook exists. Use --force to overwrite.")):(e.mkdirSync(t,{recursive:true}),e.writeFileSync(u,V),e.chmodSync(u,"755"),f.push("pre-push"));}!c&&f.length>0&&console.log(o.green(`Git hooks installed: ${f.join(", ")}`));let O=r.join(h,".paradigm/history");!e.existsSync(O)&&!c&&console.log(o.gray("Tip: Run `paradigm history init` to initialize history tracking"));}let l=!i.postCommit&&!i.prePush&&!i.claudeCode&&!i.cursor;(l||i.claudeCode)&&(c?(console.log(o.gray("  Claude Code hooks: would install paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh")),console.log(o.gray(`  \u2192 ${r.join(h,".claude","hooks")}/`)),console.log(o.gray("  \u2192 Update .claude/settings.json with hook configuration"))):await B(h,i.force)),(l||i.cursor)&&(c?(console.log(o.gray("  Cursor hooks: would install paradigm-session-start.sh, paradigm-stop.sh, paradigm-precommit.sh, paradigm-postwrite.sh, paradigm-pretooluse.sh, paradigm-posttooluse.sh")),console.log(o.gray(`  \u2192 ${r.join(h,".cursor","hooks")}/`)),console.log(o.gray("  \u2192 Update .cursor/hooks.json"))):await K(h,i.force)),c&&console.log(o.cyan(`
  [dry-run] No changes made.
`));}async function B(i,h){let c=x();if(c.active){console.log(o.cyan(`  Paradigm plugin v${c.cacheVersion} is active \u2014 hooks are managed by the plugin.`));let{cleaned:_,removed:$}=J(i);console.log(_?o.green(`  Cleaned up stale project hooks: ${$.join(", ")}`):o.gray("  No stale project hooks to clean up.")),console.log(o.gray("  Plugin hooks auto-update with each session \u2014 no manual install needed."));return}let d=r.join(i,".claude","hooks");e.mkdirSync(d,{recursive:true});let a=[],p=r.join(d,"paradigm-common.sh");e.writeFileSync(p,N,"utf8"),e.chmodSync(p,"755");let l=[{name:"paradigm-stop.sh",content:R},{name:"paradigm-precommit.sh",content:U},{name:"paradigm-postwrite.sh",content:A}];for(let _ of l){let $=r.join(d,_.name);if(e.existsSync($)&&!h){console.log(o.gray(`  ${_.name}: already installed`));continue}e.writeFileSync($,_.content,"utf8"),e.chmodSync($,"755"),a.push(_.name);}let s=r.join(i,".claude","settings.json"),t={};if(e.existsSync(s))try{t=JSON.parse(e.readFileSync(s,"utf8"));}catch{}let n=t.hooks||{},f={hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-stop.sh"',timeout:10}]},O={matcher:"Bash",hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-precommit.sh"',timeout:30}]},u=n.Stop||[];u.some(_=>JSON.stringify(_).includes("paradigm-stop.sh"))||u.push(f),n.Stop=u;let T=n.PreToolUse||[];T.some(_=>JSON.stringify(_).includes("paradigm-precommit.sh"))||T.push(O),n.PreToolUse=T;let M={matcher:"Edit,Write",hooks:[{type:"command",command:'bash "$CLAUDE_PROJECT_DIR/.claude/hooks/paradigm-postwrite.sh"',timeout:5}]},S=n.PostToolUse||[];S.some(_=>JSON.stringify(_).includes("paradigm-postwrite.sh"))||S.push(M),n.PostToolUse=S,t.hooks=n,e.writeFileSync(s,JSON.stringify(t,null,2)+`
`,"utf8"),a.length>0&&console.log(o.green(`Claude Code hooks installed: ${a.join(", ")}`)),console.log(o.green("Claude Code settings.json updated with hook configuration"));}async function K(i,h){let c=r.join(i,".cursor","hooks");e.mkdirSync(c,{recursive:true});let d=[],a=r.join(c,"paradigm-common.sh");e.writeFileSync(a,N,"utf8"),e.chmodSync(a,"755");let p=[{name:"paradigm-session-start.sh",content:D},{name:"paradigm-stop.sh",content:v},{name:"paradigm-precommit.sh",content:b},{name:"paradigm-postwrite.sh",content:P},{name:"paradigm-pretooluse.sh",content:L},{name:"paradigm-posttooluse.sh",content:w}];for(let m of p){let y=r.join(c,m.name);if(e.existsSync(y)&&!h){console.log(o.gray(`  ${m.name}: already installed (Cursor)`));continue}e.writeFileSync(y,m.content,"utf8"),e.chmodSync(y,"755"),d.push(m.name);}let l=r.join(i,".cursor","hooks.json"),s={};if(e.existsSync(l))try{s=JSON.parse(e.readFileSync(l,"utf8"));}catch{}s.version=1;let t=s.hooks||{},n={command:".cursor/hooks/paradigm-session-start.sh",timeout:5},f={command:".cursor/hooks/paradigm-stop.sh",timeout:10,loop_limit:3},O={command:".cursor/hooks/paradigm-postwrite.sh",timeout:5},u={command:".cursor/hooks/paradigm-precommit.sh",matcher:"git commit",timeout:30},g=t.sessionStart||[];g.some(m=>JSON.stringify(m).includes("paradigm-session-start.sh"))||g.push(n),t.sessionStart=g;let I=t.stop||[];I.some(m=>JSON.stringify(m).includes("paradigm-stop.sh"))||I.push(f),t.stop=I;let S=t.afterFileEdit||[];S.some(m=>JSON.stringify(m).includes("paradigm-postwrite.sh"))||S.push(O),t.afterFileEdit=S;let _={command:".cursor/hooks/paradigm-pretooluse.sh",matcher:"Edit|Write",timeout:5},$=t.preToolUse||[];$.some(m=>JSON.stringify(m).includes("paradigm-pretooluse.sh"))||$.push(_),t.preToolUse=$;let j={command:".cursor/hooks/paradigm-posttooluse.sh",matcher:"Edit|Write",timeout:5},C=t.postToolUse||[];C.some(m=>JSON.stringify(m).includes("paradigm-posttooluse.sh"))||C.push(j),t.postToolUse=C;let k=t.beforeShellExecution||[];k.some(m=>JSON.stringify(m).includes("paradigm-precommit.sh"))||k.push(u),t.beforeShellExecution=k,s.hooks=t,e.writeFileSync(l,JSON.stringify(s,null,2)+`
`,"utf8"),d.length>0&&console.log(o.green(`Cursor hooks installed: ${d.join(", ")}`)),console.log(o.green("Cursor hooks.json updated with hook configuration"));}async function re(i={}){let h=process.cwd(),c=i.dryRun||false;if(c&&console.log(o.cyan(`
  [dry-run] Showing what would be removed:
`)),!i.cursor){let d=r.join(h,".git");if(!e.existsSync(d)){console.log(o.red("Not a git repository."));return}let a=r.join(d,"hooks"),p=[];for(let l of ["post-commit","pre-push"]){let s=r.join(a,l);e.existsSync(s)&&e.readFileSync(s,"utf8").includes("paradigm")&&(c?console.log(o.gray(`  Would remove: ${s}`)):e.unlinkSync(s),p.push(l));}c?p.length===0&&console.log(o.gray("  No paradigm git hooks to remove")):p.length>0?console.log(o.green(`Git hooks removed: ${p.join(", ")}`)):console.log(o.gray("No paradigm git hooks found to remove"));}if(i.cursor){let d=r.join(h,".cursor","hooks"),a=[];for(let l of ["paradigm-session-start.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh","paradigm-pretooluse.sh","paradigm-posttooluse.sh"]){let s=r.join(d,l);e.existsSync(s)&&(c?console.log(o.gray(`  Would remove: ${s}`)):e.unlinkSync(s),a.push(l));}let p=r.join(h,".cursor","hooks.json");if(e.existsSync(p))if(c)console.log(o.gray(`  Would clean paradigm entries from: ${p}`));else try{let l=JSON.parse(e.readFileSync(p,"utf8")),s=l.hooks||{};for(let t of ["sessionStart","stop","afterFileEdit","beforeShellExecution","preToolUse","postToolUse"])Array.isArray(s[t])&&(s[t]=s[t].filter(n=>!JSON.stringify(n).includes("paradigm-")),s[t].length===0&&delete s[t]);l.hooks=s,e.writeFileSync(p,JSON.stringify(l,null,2)+`
`,"utf8");}catch{}c?a.length===0&&console.log(o.gray("  No paradigm Cursor hooks to remove")):a.length>0?console.log(o.green(`Cursor hooks removed: ${a.join(", ")}`)):console.log(o.gray("No paradigm Cursor hooks found to remove"));}c&&console.log(o.cyan(`
  [dry-run] No changes made.
`));}async function ae(){let i=process.cwd(),h=r.join(i,".git");if(e.existsSync(h)){console.log(o.magenta(`
  Git Hooks Status
`));let l=r.join(h,"hooks"),s=["post-commit","pre-push"];for(let n of s){let f=r.join(l,n);e.existsSync(f)?e.readFileSync(f,"utf8").includes("paradigm")?console.log(o.green(`  ${n}: installed (paradigm)`)):console.log(o.yellow(`  ${n}: exists (other)`)):console.log(o.gray(`  ${n}: not installed`));}console.log();let t=r.join(i,".paradigm/history");if(e.existsSync(t)){let n=r.join(t,"log.jsonl");if(e.existsSync(n)){let O=e.readFileSync(n,"utf8").split(`
`).filter(u=>u.trim()).length;console.log(o.white(`  History entries: ${O}`));}}else console.log(o.gray("  History: not initialized")),console.log(o.gray("  Run `paradigm history init` to enable"));}else console.log(o.gray(`
  Not a git repository (git hooks N/A)
`));console.log(o.magenta(`  Claude Code Hooks Status
`));let c=x();if(c.active){console.log(o.cyan(`  Plugin: paradigm v${c.cacheVersion} (active)`)),console.log(o.green("  Hooks are managed by the plugin \u2014 auto-updates with each session."));let l=r.join(i,".claude","hooks"),s=[];for(let f of ["paradigm-common.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"])e.existsSync(r.join(l,f))&&s.push(f);let t=r.join(i,".claude","settings.json"),n=false;if(e.existsSync(t))try{let f=JSON.parse(e.readFileSync(t,"utf8"));n=JSON.stringify(f.hooks||{}).includes("paradigm-");}catch{}(s.length>0||n)&&(console.log(o.yellow(`  WARNING: Stale project hooks detected (${s.join(", ")}${n?", settings.json entries":""})`)),console.log(o.yellow("  These shadow the plugin hooks and may run outdated logic.")),console.log(o.gray("  Run `paradigm hooks install --claude-code` to clean them up.")));}else {console.log(o.gray("  Plugin: not active (using project-level hooks)"));let l=r.join(i,".claude","hooks"),s=["paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh"];for(let n of s){let f=r.join(l,n);e.existsSync(f)?console.log(o.green(`  ${n}: installed`)):console.log(o.gray(`  ${n}: not installed`));}let t=r.join(i,".claude","settings.json");if(e.existsSync(t))try{let f=JSON.parse(e.readFileSync(t,"utf8")).hooks||{},O=JSON.stringify(f.Stop||[]).includes("paradigm-stop.sh"),u=JSON.stringify(f.PreToolUse||[]).includes("paradigm-precommit.sh"),g=JSON.stringify(f.PostToolUse||[]).includes("paradigm-postwrite.sh");console.log(o.gray(`  settings.json Stop hook: ${O?"configured":"missing"}`)),console.log(o.gray(`  settings.json PreToolUse hook: ${u?"configured":"missing"}`)),console.log(o.gray(`  settings.json PostToolUse hook: ${g?"configured":"missing"}`));}catch{console.log(o.yellow("  settings.json: parse error"));}else console.log(o.gray("  settings.json: not found"));}console.log(o.magenta(`
  Cursor Hooks Status
`));let d=r.join(i,".cursor","hooks"),a=["paradigm-session-start.sh","paradigm-stop.sh","paradigm-precommit.sh","paradigm-postwrite.sh","paradigm-pretooluse.sh","paradigm-posttooluse.sh"];for(let l of a){let s=r.join(d,l);e.existsSync(s)?console.log(o.green(`  ${l}: installed`)):console.log(o.gray(`  ${l}: not installed`));}let p=r.join(i,".cursor","hooks.json");if(e.existsSync(p))try{let s=JSON.parse(e.readFileSync(p,"utf8")).hooks||{},t=JSON.stringify(s.sessionStart||[]).includes("paradigm-session-start.sh"),n=JSON.stringify(s.stop||[]).includes("paradigm-stop.sh"),f=JSON.stringify(s.afterFileEdit||[]).includes("paradigm-postwrite.sh"),O=JSON.stringify(s.beforeShellExecution||[]).includes("paradigm-precommit.sh"),u=JSON.stringify(s.preToolUse||[]).includes("paradigm-pretooluse.sh"),g=JSON.stringify(s.postToolUse||[]).includes("paradigm-posttooluse.sh");console.log(o.gray(`  hooks.json sessionStart: ${t?"configured":"missing"}`)),console.log(o.gray(`  hooks.json stop: ${n?"configured":"missing"}`)),console.log(o.gray(`  hooks.json afterFileEdit: ${f?"configured":"missing"}`)),console.log(o.gray(`  hooks.json preToolUse: ${u?"configured":"missing"}`)),console.log(o.gray(`  hooks.json postToolUse: ${g?"configured":"missing"}`)),console.log(o.gray(`  hooks.json beforeShellExecution: ${O?"configured":"missing"}`));}catch{console.log(o.yellow("  hooks.json: parse error"));}else console.log(o.gray("  hooks.json: not found"));console.log();}export{te as a,re as b,ae as c};