Consolidates three codebases into a single agent orchestration repo: - agentci (from go-scm): Clotho dual-run verification, agent config, SSH security (sanitisation, secure commands, token masking) - jobrunner (from go-scm): Poll-dispatch-report pipeline with 7 handlers (dispatch, completion, auto-merge, publish draft, dismiss reviews, send fix command, tick parent epic) - plugins marketplace (from agentic/plugins): 27 Claude/Codex/Gemini plugins with shared MCP server All 150+ tests passing across 6 packages. Co-Authored-By: Virgil <virgil@lethean.io>
264 lines
10 KiB
Bash
Executable file
264 lines
10 KiB
Bash
Executable file
#!/bin/bash
|
|
# Session Start Hook - Project Detection & Context Loading
|
|
# Detects project type, sets environment variables, provides actionable next steps
|
|
|
|
set -euo pipefail
|
|
|
|
# Read input (not used much for SessionStart, but good practice)
|
|
input=$(cat)
|
|
|
|
# Get project directory
|
|
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
ENV_FILE="${CLAUDE_ENV_FILE:-}"
|
|
|
|
# Initialize detection results
|
|
PROJECT_TYPE="unknown"
|
|
HAS_CORE_CLI="false"
|
|
DETECTED_FEATURES=""
|
|
GIT_STATUS=""
|
|
|
|
# Check for core CLI
|
|
if command -v core &>/dev/null; then
|
|
HAS_CORE_CLI="true"
|
|
fi
|
|
|
|
# Detect Go project
|
|
if [[ -f "$PROJECT_DIR/go.mod" ]]; then
|
|
PROJECT_TYPE="go"
|
|
MODULE_NAME=$(head -1 "$PROJECT_DIR/go.mod" | sed 's/module //')
|
|
DETECTED_FEATURES="$DETECTED_FEATURES go-module"
|
|
|
|
# Check for specific Go patterns
|
|
[[ -d "$PROJECT_DIR/cmd" ]] && DETECTED_FEATURES="$DETECTED_FEATURES cmd-pattern"
|
|
[[ -d "$PROJECT_DIR/internal" ]] && DETECTED_FEATURES="$DETECTED_FEATURES internal-pkg"
|
|
[[ -f "$PROJECT_DIR/Makefile" ]] && DETECTED_FEATURES="$DETECTED_FEATURES makefile"
|
|
[[ -f "$PROJECT_DIR/go.work" ]] && DETECTED_FEATURES="$DETECTED_FEATURES workspace"
|
|
fi
|
|
|
|
# Detect PHP/Laravel project
|
|
if [[ -f "$PROJECT_DIR/composer.json" ]]; then
|
|
PROJECT_TYPE="php"
|
|
[[ -f "$PROJECT_DIR/artisan" ]] && PROJECT_TYPE="laravel"
|
|
[[ -d "$PROJECT_DIR/app/Http" ]] && DETECTED_FEATURES="$DETECTED_FEATURES laravel-http"
|
|
fi
|
|
|
|
# Detect Node.js project
|
|
if [[ -f "$PROJECT_DIR/package.json" ]]; then
|
|
[[ "$PROJECT_TYPE" == "unknown" ]] && PROJECT_TYPE="nodejs"
|
|
[[ -f "$PROJECT_DIR/next.config.js" || -f "$PROJECT_DIR/next.config.mjs" ]] && PROJECT_TYPE="nextjs"
|
|
[[ -f "$PROJECT_DIR/nuxt.config.ts" ]] && PROJECT_TYPE="nuxt"
|
|
[[ -f "$PROJECT_DIR/tailwind.config.js" || -f "$PROJECT_DIR/tailwind.config.ts" ]] && DETECTED_FEATURES="$DETECTED_FEATURES tailwind"
|
|
fi
|
|
|
|
# Detect Rust project
|
|
if [[ -f "$PROJECT_DIR/Cargo.toml" ]]; then
|
|
PROJECT_TYPE="rust"
|
|
fi
|
|
|
|
# Detect Python project
|
|
if [[ -f "$PROJECT_DIR/pyproject.toml" || -f "$PROJECT_DIR/setup.py" || -f "$PROJECT_DIR/requirements.txt" ]]; then
|
|
[[ "$PROJECT_TYPE" == "unknown" ]] && PROJECT_TYPE="python"
|
|
fi
|
|
|
|
# Detect crypto/blockchain projects
|
|
if [[ -d "$PROJECT_DIR/src/cryptonote_core" || -f "$PROJECT_DIR/cryptonote_config.h" ]]; then
|
|
PROJECT_TYPE="cryptonote"
|
|
DETECTED_FEATURES="$DETECTED_FEATURES blockchain crypto"
|
|
fi
|
|
|
|
# Check for Lethean-specific
|
|
if [[ "$PROJECT_DIR" == *"lethean"* || -f "$PROJECT_DIR/.lethean" ]]; then
|
|
DETECTED_FEATURES="$DETECTED_FEATURES lethean-project"
|
|
fi
|
|
|
|
# Check for Host UK repos
|
|
if [[ "$PROJECT_DIR" == *"host-uk"* || "$PROJECT_DIR" == *"hostuk"* ]]; then
|
|
DETECTED_FEATURES="$DETECTED_FEATURES host-uk-project"
|
|
fi
|
|
|
|
# Detect git info
|
|
GIT_BRANCH=""
|
|
GIT_REMOTE=""
|
|
GIT_DIRTY="false"
|
|
GIT_UNPUSHED="false"
|
|
if [[ -d "$PROJECT_DIR/.git" ]]; then
|
|
GIT_BRANCH=$(git -C "$PROJECT_DIR" branch --show-current 2>/dev/null || echo "")
|
|
GIT_REMOTE=$(git -C "$PROJECT_DIR" remote get-url origin 2>/dev/null || echo "")
|
|
DETECTED_FEATURES="$DETECTED_FEATURES git"
|
|
|
|
# Check for uncommitted changes
|
|
if [[ -n $(git -C "$PROJECT_DIR" status --porcelain 2>/dev/null) ]]; then
|
|
GIT_DIRTY="true"
|
|
DETECTED_FEATURES="$DETECTED_FEATURES uncommitted-changes"
|
|
fi
|
|
|
|
# Check for unpushed commits
|
|
if [[ -n "$GIT_BRANCH" ]]; then
|
|
UNPUSHED=$(git -C "$PROJECT_DIR" log origin/"$GIT_BRANCH"..HEAD --oneline 2>/dev/null | wc -l || echo "0")
|
|
if [[ "$UNPUSHED" -gt 0 ]]; then
|
|
GIT_UNPUSHED="true"
|
|
DETECTED_FEATURES="$DETECTED_FEATURES unpushed-commits:$UNPUSHED"
|
|
fi
|
|
fi
|
|
|
|
# Detect if it's a Gitea repo
|
|
[[ "$GIT_REMOTE" == *"forge.lthn.ai"* ]] && DETECTED_FEATURES="$DETECTED_FEATURES forge-hosted"
|
|
fi
|
|
|
|
# Persist environment variables for the session
|
|
if [[ -n "$ENV_FILE" ]]; then
|
|
{
|
|
echo "export PROJECT_TYPE=\"$PROJECT_TYPE\""
|
|
echo "export HAS_CORE_CLI=\"$HAS_CORE_CLI\""
|
|
echo "export DETECTED_FEATURES=\"$DETECTED_FEATURES\""
|
|
echo "export GIT_DIRTY=\"$GIT_DIRTY\""
|
|
[[ -n "$GIT_BRANCH" ]] && echo "export GIT_BRANCH=\"$GIT_BRANCH\""
|
|
} >> "$ENV_FILE"
|
|
fi
|
|
|
|
# Build context message for Claude
|
|
CONTEXT_MSG="**Project Context**\\n"
|
|
CONTEXT_MSG+="Type: \`$PROJECT_TYPE\` | Core CLI: $HAS_CORE_CLI"
|
|
[[ -n "$GIT_BRANCH" ]] && CONTEXT_MSG+=" | Branch: \`$GIT_BRANCH\`"
|
|
[[ "$GIT_DIRTY" == "true" ]] && CONTEXT_MSG+=" | ⚠️ Uncommitted changes"
|
|
[[ "$GIT_UNPUSHED" == "true" ]] && CONTEXT_MSG+=" | 📤 Unpushed commits"
|
|
CONTEXT_MSG+="\\n"
|
|
|
|
# Add actionable next steps based on project type and core CLI
|
|
if [[ "$HAS_CORE_CLI" == "true" ]]; then
|
|
CONTEXT_MSG+="\\n**Core CLI Commands:**\\n"
|
|
|
|
case "$PROJECT_TYPE" in
|
|
go)
|
|
CONTEXT_MSG+="| Task | Command |\\n"
|
|
CONTEXT_MSG+="|------|---------|\\n"
|
|
CONTEXT_MSG+="| Fix everything | \`core go qa --fix\` |\\n"
|
|
CONTEXT_MSG+="| Quick check (no tests) | \`core go qa quick\` |\\n"
|
|
CONTEXT_MSG+="| Pre-commit | \`core go qa pre-commit\` |\\n"
|
|
CONTEXT_MSG+="| Full QA + coverage | \`core go qa --coverage --threshold=80\` |\\n"
|
|
CONTEXT_MSG+="| PR ready | \`core go qa pr\` |\\n"
|
|
CONTEXT_MSG+="| Only tests | \`core go qa --only=test\` |\\n"
|
|
CONTEXT_MSG+="| Tests with race | \`core go qa --race\` |\\n"
|
|
CONTEXT_MSG+="| Check changed files | \`core go qa --changed\` |\\n"
|
|
if [[ "$DETECTED_FEATURES" == *"workspace"* ]]; then
|
|
CONTEXT_MSG+="| Workspace sync | \`core go work sync\` |\\n"
|
|
fi
|
|
CONTEXT_MSG+="| Build release | \`core build\` |\\n"
|
|
CONTEXT_MSG+="| Security scan | \`core security alerts\` |\\n"
|
|
;;
|
|
php|laravel)
|
|
CONTEXT_MSG+="| Task | Command |\\n"
|
|
CONTEXT_MSG+="|------|---------|\\n"
|
|
CONTEXT_MSG+="| Fix everything | \`core php qa --fix\` |\\n"
|
|
CONTEXT_MSG+="| Quick check | \`core php qa --quick\` |\\n"
|
|
CONTEXT_MSG+="| Full QA | \`core php qa --full\` |\\n"
|
|
CONTEXT_MSG+="| Run tests | \`core php test\` |\\n"
|
|
CONTEXT_MSG+="| Format code | \`core php fmt\` |\\n"
|
|
CONTEXT_MSG+="| Static analysis | \`core php stan\` |\\n"
|
|
CONTEXT_MSG+="| Security audit | \`core php audit\` |\\n"
|
|
if [[ "$PROJECT_TYPE" == "laravel" ]]; then
|
|
CONTEXT_MSG+="| Start dev | \`core php dev\` |\\n"
|
|
CONTEXT_MSG+="| Deploy | \`core php deploy\` |\\n"
|
|
fi
|
|
;;
|
|
nodejs|nextjs|nuxt)
|
|
CONTEXT_MSG+="| Task | Command |\\n"
|
|
CONTEXT_MSG+="|------|---------|\\n"
|
|
CONTEXT_MSG+="| Build project | \`core build\` |\\n"
|
|
CONTEXT_MSG+="| Security scan | \`core security alerts\` |\\n"
|
|
;;
|
|
*)
|
|
CONTEXT_MSG+="| Task | Command |\\n"
|
|
CONTEXT_MSG+="|------|---------|\\n"
|
|
CONTEXT_MSG+="| Environment check | \`core doctor\` |\\n"
|
|
CONTEXT_MSG+="| Repo health | \`core dev health\` |\\n"
|
|
CONTEXT_MSG+="| CI status | \`core dev ci\` |\\n"
|
|
CONTEXT_MSG+="| Security alerts | \`core security alerts\` |\\n"
|
|
;;
|
|
esac
|
|
|
|
# Git workflow commands (always available)
|
|
if [[ "$DETECTED_FEATURES" == *"git"* ]]; then
|
|
CONTEXT_MSG+="\\n**Git Workflow:**\\n"
|
|
CONTEXT_MSG+="| Task | Command |\\n"
|
|
CONTEXT_MSG+="|------|---------|\\n"
|
|
CONTEXT_MSG+="| Multi-repo health | \`core git health\` |\\n"
|
|
CONTEXT_MSG+="| Smart commit | \`core git commit\` |\\n"
|
|
CONTEXT_MSG+="| Pull all repos | \`core git pull\` |\\n"
|
|
CONTEXT_MSG+="| Push all repos | \`core git push\` |\\n"
|
|
if [[ "$GIT_DIRTY" == "true" ]]; then
|
|
CONTEXT_MSG+="| ⚠️ You have uncommitted changes |\\n"
|
|
fi
|
|
if [[ "$GIT_UNPUSHED" == "true" ]]; then
|
|
CONTEXT_MSG+="| 📤 Push pending: \`core git push\` |\\n"
|
|
fi
|
|
fi
|
|
|
|
# Suggested first action based on state
|
|
CONTEXT_MSG+="\\n**Suggested First Action:**\\n"
|
|
if [[ "$GIT_DIRTY" == "true" && "$PROJECT_TYPE" == "go" ]]; then
|
|
CONTEXT_MSG+="\`core go qa --fix && core git commit\` - Fix issues and commit\\n"
|
|
elif [[ "$PROJECT_TYPE" == "go" ]]; then
|
|
CONTEXT_MSG+="\`core go qa --fix\` - Ensure code is clean\\n"
|
|
elif [[ "$PROJECT_TYPE" == "php" || "$PROJECT_TYPE" == "laravel" ]]; then
|
|
CONTEXT_MSG+="\`core php qa --fix\` - Ensure code is clean\\n"
|
|
else
|
|
CONTEXT_MSG+="\`core doctor\` - Check environment is ready\\n"
|
|
fi
|
|
else
|
|
# No core CLI - provide manual commands
|
|
CONTEXT_MSG+="\\n**Note:** \`core\` CLI not found. Install for enhanced workflow.\\n"
|
|
|
|
case "$PROJECT_TYPE" in
|
|
go)
|
|
CONTEXT_MSG+="Manual: \`go fmt ./... && go vet ./... && go test ./...\`\\n"
|
|
;;
|
|
php|laravel)
|
|
CONTEXT_MSG+="Manual: \`composer test\`\\n"
|
|
;;
|
|
nodejs|nextjs|nuxt)
|
|
CONTEXT_MSG+="Manual: Check \`package.json\` scripts\\n"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# CryptoNote-specific warnings
|
|
if [[ "$PROJECT_TYPE" == "cryptonote" || "$DETECTED_FEATURES" == *"crypto"* ]]; then
|
|
CONTEXT_MSG+="\\n**⚠️ CryptoNote Project:**\\n"
|
|
CONTEXT_MSG+="- Consensus-critical code - changes may fork the network\\n"
|
|
CONTEXT_MSG+="- Review cryptonote-archive plugin for protocol specs\\n"
|
|
CONTEXT_MSG+="- Test thoroughly on testnet before mainnet\\n"
|
|
fi
|
|
|
|
# KB suggestions based on context — "Know kung fu?"
|
|
KB_HINT=""
|
|
case "$PROJECT_TYPE" in
|
|
go)
|
|
[[ "$DETECTED_FEATURES" == *"host-uk"* ]] && KB_HINT="go lethean-specs"
|
|
[[ -z "$KB_HINT" ]] && KB_HINT="go"
|
|
;;
|
|
php|laravel)
|
|
KB_HINT="php"
|
|
;;
|
|
cryptonote)
|
|
KB_HINT="cryptonote lethean-specs"
|
|
;;
|
|
*)
|
|
[[ "$DETECTED_FEATURES" == *"lethean"* ]] && KB_HINT="lethean-specs lethean-tech"
|
|
[[ "$DETECTED_FEATURES" == *"host-uk"* && -z "$KB_HINT" ]] && KB_HINT="go infra"
|
|
;;
|
|
esac
|
|
if [[ -n "$KB_HINT" ]]; then
|
|
CONTEXT_MSG+="\\n**Know kung fu?** \`/learn $KB_HINT\`\\n"
|
|
fi
|
|
|
|
# Output JSON response (escape for JSON)
|
|
ESCAPED_MSG=$(echo -e "$CONTEXT_MSG" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | tr '\n' ' ' | sed 's/ */ /g')
|
|
|
|
cat << EOF
|
|
{
|
|
"continue": true,
|
|
"suppressOutput": false,
|
|
"systemMessage": "$ESCAPED_MSG"
|
|
}
|
|
EOF
|