- AGENTS.md: design principles, enforcement hierarchy, deferred loading - agents/: brainstorm, build, orchestrator, research (auto-discovered by MCP server) - skills/: research methodology (auto-discovered by MCP server) - hooks/: pre-tool-use, post-tool-use (BFF block removed), session-start, stop, pre-compact, user-prompt-submit - frameworks/: opencode/plugin.ts (resolves hooks via import.meta.url — works as project-local or global plugin), github/hooks.json - mcp/index.ts: auto-discovers agents/*.md and skills/*.md from frontmatter (replaces hand-maintained registry); server renamed all-agents - docs/: agent-infrastructure.md (generalized), research docs (7 files), ai_architectures.md, llama-server-cuda-wsl2.md - install.sh: idempotent setup — Copilot global hooks, OpenCode global plugin + AGENTS.md + MCP entry, VS Code global MCP config
177 lines
8.1 KiB
Bash
Executable File
177 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Stop hook:
|
|
# 1. Validates TODO.md and COMPLETED.md — blocking if violations found.
|
|
# 2. Prompts agent to record lessons learned before session ends (non-blocking).
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")"
|
|
SESSION_DIR="$REPO_ROOT/.session"
|
|
TODO_FILE="$REPO_ROOT/docs/TODO.md"
|
|
COMPLETED_FILE="$REPO_ROOT/docs/projects/COMPLETED.md"
|
|
|
|
# ── Validation (blocking) ────────────────────────────────────────────────────
|
|
|
|
problems=""
|
|
|
|
# Check TODO.md for completed [x] or ✅ numbered items — they should be moved to COMPLETED.md
|
|
if [[ -f "$TODO_FILE" ]]; then
|
|
todo_completed=$(grep -n '^\s*[0-9]\+\. \(\[x\]\|✅\)' "$TODO_FILE" 2>/dev/null || true)
|
|
if [[ -n "$todo_completed" ]]; then
|
|
count=$(echo "$todo_completed" | wc -l | tr -d ' ')
|
|
problems="docs/TODO.md contains ${count} completed [x]/✅ task(s). Move them to docs/projects/COMPLETED.md and remove from TODO.md.\nLines:\n${todo_completed}"
|
|
fi
|
|
fi
|
|
|
|
# Check COMPLETED.md for inline [x] items — it should contain stubs/summaries only
|
|
if [[ -f "$COMPLETED_FILE" ]]; then
|
|
completed_inline=$(grep -n '^\s*\(-\|[0-9]\+\.\) \[x\]' "$COMPLETED_FILE" 2>/dev/null || true)
|
|
if [[ -n "$completed_inline" ]]; then
|
|
count=$(echo "$completed_inline" | wc -l | tr -d ' ')
|
|
if [[ -n "$problems" ]]; then
|
|
problems="${problems}\n\n"
|
|
fi
|
|
problems="${problems}docs/projects/COMPLETED.md contains ${count} inline [x] item(s). Archive to a completed/*.md file and replace with a stub entry."
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$problems" ]]; then
|
|
msg=$(printf '%b' "$problems" | node -e 'process.stdout.write(JSON.stringify(require("fs").readFileSync("/dev/stdin","utf8")))')
|
|
cat <<EOF
|
|
{
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "Stop",
|
|
"decision": "block",
|
|
"reason": ${msg}
|
|
}
|
|
}
|
|
EOF
|
|
exit 0
|
|
fi
|
|
|
|
# ── Lessons learned prompt (non-blocking) ────────────────────────────────────
|
|
|
|
# Check whether all questions in the user's last prompt were answered
|
|
unanswered_reminder=""
|
|
LAST_PROMPT_FILE="/tmp/.last-user-prompt.txt"
|
|
if [[ -f "$LAST_PROMPT_FILE" ]]; then
|
|
last_prompt=$(cat "$LAST_PROMPT_FILE" 2>/dev/null || true)
|
|
if [[ -n "$last_prompt" ]]; then
|
|
unanswered_reminder="$last_prompt"
|
|
fi
|
|
fi
|
|
|
|
# Check whether the dev server is running (ports 3000/3001)
|
|
dev_server_running=""
|
|
if ss -tlnp 2>/dev/null | grep -qE ':300[01]\s'; then
|
|
dev_server_running="yes"
|
|
fi
|
|
dead_ends_active=""
|
|
DEAD_ENDS_FILE="$SESSION_DIR/dead-ends.md"
|
|
if [[ -f "$DEAD_ENDS_FILE" ]]; then
|
|
entry_count=$(grep -c '^\s*- \*\*' "$DEAD_ENDS_FILE" 2>/dev/null || echo "0")
|
|
if [[ "$entry_count" -gt 0 ]]; then
|
|
dead_ends_active="yes"
|
|
fi
|
|
fi
|
|
|
|
# Check for investigation files that may need status updates
|
|
open_investigations=""
|
|
EXPLORATIONS_DIR="$REPO_ROOT/docs/explorations"
|
|
if [[ -d "$EXPLORATIONS_DIR" ]]; then
|
|
inv_files=$(find "$EXPLORATIONS_DIR" -name "*.md" -not -empty 2>/dev/null || true)
|
|
if [[ -n "$inv_files" ]]; then
|
|
# Check for explorations NOT yet marked complete (status line doesn't contain 'complete')
|
|
active=$(echo "$inv_files" | while read -r f; do
|
|
if ! grep -qi '^\*\*Status\*\*.*complete\|^Status:.*complete' "$f" 2>/dev/null; then
|
|
echo "$f"
|
|
fi
|
|
done || true)
|
|
if [[ -n "$active" ]]; then
|
|
active_names=$(echo "$active" | xargs -I{} basename {} .md | sed 's/^/ - /' || true)
|
|
open_investigations="yes"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Check if pre-compact-state.md still lists active investigations
|
|
stale_compact=""
|
|
COMPACT_FILE="$SESSION_DIR/pre-compact-state.md"
|
|
if [[ -f "$COMPACT_FILE" ]]; then
|
|
active_in_compact=$(grep -A20 '## Active Investigations' "$COMPACT_FILE" 2>/dev/null | grep -v '##' | grep -v '^\s*$' | head -5 || true)
|
|
if [[ -n "$active_in_compact" ]]; then
|
|
stale_compact="yes"
|
|
fi
|
|
fi
|
|
|
|
# Build the lessons-learned prompt
|
|
prompt="SESSION END — QUALITY GATE"
|
|
|
|
# Remind agent to verify it answered every question in the user's last message
|
|
if [[ -n "$unanswered_reminder" ]]; then
|
|
prompt="${prompt}\n\nANSWER CHECK: Before finishing, re-read the user's last message below and confirm"
|
|
prompt="${prompt} you addressed EVERY question and request in it — not just the primary task:"
|
|
prompt="${prompt}\n---"
|
|
prompt="${prompt}\n${unanswered_reminder}"
|
|
prompt="${prompt}\n---"
|
|
fi
|
|
if [[ -n "$dev_server_running" ]]; then
|
|
prompt="${prompt}\nThe dev server IS running (port 3000/3001 detected). Run: npm test && npm run lint"
|
|
prompt="${prompt}\nThen ask the user to confirm the build is clean in their terminal."
|
|
else
|
|
prompt="${prompt}\nThe dev server is NOT running. Run: npm run build:strict"
|
|
prompt="${prompt}\nThis runs build + lint + format:check + tests. Do NOT skip this if you changed any source files."
|
|
fi
|
|
prompt="${prompt}\n"
|
|
prompt="${prompt}\n---"
|
|
prompt="${prompt}\nLESSONS LEARNED CAPTURE — Before finishing, consider recording reusable insights:"
|
|
prompt="${prompt}\n"
|
|
prompt="${prompt}\n1. **Process insights** (what worked, what didn't, workflow improvements) → write to /memories/repo/ or /memories/"
|
|
|
|
if [[ -n "$dead_ends_active" ]]; then
|
|
prompt="${prompt}\n2. **Dead-ends file** has entries — verify all have results recorded (ELIMINATED/CONFIRMED with reasons)"
|
|
fi
|
|
|
|
if [[ -n "$open_investigations" ]]; then
|
|
prompt="${prompt}\n3. **Open explorations** not yet marked complete — update their status or mark complete:"
|
|
prompt="${prompt}\n${active_names}"
|
|
fi
|
|
|
|
if [[ -n "$stale_compact" ]]; then
|
|
prompt="${prompt}\n3. **pre-compact-state.md** still lists active investigations — clear or update it now that the session is ending"
|
|
fi
|
|
|
|
prompt="${prompt}\n4. **TODO.md review** — Check docs/TODO.md: did any work this session complete or partially complete an item?"
|
|
prompt="${prompt}\n - If an item is done: mark it [x], then move the whole entry to docs/projects/COMPLETED.md and remove it from TODO.md"
|
|
prompt="${prompt}\n - If partially done: update the description to reflect current state"
|
|
prompt="${prompt}\n - Don't wait for the blocking check — proactively keep TODO.md accurate"
|
|
prompt="${prompt}\n5. **New tasks discovered** → note them for the user or add to docs/TODO.md"
|
|
prompt="${prompt}\n"
|
|
prompt="${prompt}\n---"
|
|
prompt="${prompt}\nEFFORT REFLECTION: If this session required significant effort (many tool calls, multiple dead-ends, complex investigation):"
|
|
prompt="${prompt}\n Ask yourself: What information, if it had existed at the start, would have prevented most of that work?"
|
|
prompt="${prompt}\n First, determine scope — is this globally applicable, or specific to certain files/patterns?"
|
|
prompt="${prompt}\n Then lean toward hooks as the solution:"
|
|
prompt="${prompt}\n • Hard stops via PreToolUse blocks (best when the bad action is a terminal command)"
|
|
prompt="${prompt}\n • PostToolUse reminders (fire right after editing a relevant file — effective because they appear mid-task)"
|
|
prompt="${prompt}\n • applyTo: instructions files scoped to a file glob (fire when the agent opens matching files)"
|
|
prompt="${prompt}\n • PreCompact saves investigation state before context compression (PostCompact does not exist)"
|
|
prompt="${prompt}\n • Stop / SessionStart: on-demand summary injection — less precise than PostToolUse but good for broad reminders"
|
|
prompt="${prompt}\n These are all more reliable than AGENTS.md sections (lost-in-the-middle problem)."
|
|
prompt="${prompt}\n Record the insight in the right hook/instructions file, NOT just in AGENTS.md."
|
|
prompt="${prompt}\n"
|
|
prompt="${prompt}\nSkip categories that don't apply. Only record genuinely new insights."
|
|
|
|
# Prefix with a self-identifying marker so the model cannot confuse the
|
|
# injection with the user's own message or prior tool output.
|
|
prompt="[HOOK INJECTION: stop] System reminder — injected at session end, not part of any user message or tool output:\n\n${prompt}"
|
|
|
|
json_prompt=$(printf '%b' "$prompt" | node -e 'process.stdout.write(JSON.stringify(require("fs").readFileSync("/dev/stdin","utf8")))')
|
|
cat <<EOF
|
|
{
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "Stop",
|
|
"additionalContext": ${json_prompt}
|
|
}
|
|
}
|
|
EOF
|