Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
928aa0a
Add Memoryloop v2 specification for goal-based learning platform
nicholaspsmith Dec 27, 2025
bc29381
Add design artifacts for goal-based learning platform
nicholaspsmith Dec 27, 2025
03b3e5f
Update dependencies list
nicholaspsmith Dec 27, 2025
cf387cc
Complete Phase 9 Polish with E2E tests and docs
nicholaspsmith Dec 28, 2025
8c33997
Integrate unified Claude client into AI generators
nicholaspsmith Dec 28, 2025
fa3aa2a
Add goal-based learning platform implementation
nicholaspsmith Dec 28, 2025
a0855c6
Strengthen agent workflow requirements in CLAUDE.md
nicholaspsmith Dec 28, 2025
0713132
Fix ClassifiedError handling in AI generators
nicholaspsmith Dec 28, 2025
f77acc8
Remove user API key functionality from codebase
nicholaspsmith Dec 28, 2025
3a8e3ff
Add comprehensive test suite for goal-based learning feature
nicholaspsmith Dec 28, 2025
5585889
Add test file guardian hook for automated test-agent enforcement
nicholaspsmith Dec 28, 2025
fc2c767
Implement comprehensive hook enforcement system
nicholaspsmith Dec 28, 2025
00432b9
Add navigation-guardian hook to guide Serena and lance-context usage
nicholaspsmith Dec 28, 2025
325d9c0
Update local settings to allow shellcheck usage
nicholaspsmith Dec 28, 2025
2e594f6
Format ESLint configuration
nicholaspsmith Dec 28, 2025
790af53
Increase CLAUDE.md line limit to 300 in test
nicholaspsmith Dec 28, 2025
8d4dfb0
Skip AI tests in CI when Ollama is unavailable
nicholaspsmith Dec 28, 2025
6703961
Use webpack instead of Turbopack for E2E tests
nicholaspsmith Dec 28, 2025
0e443c5
Remove invalid --no-turbopack flag from dev:webpack script
nicholaspsmith Dec 28, 2025
62afa28
Allow git-agent commits through bash-guardian hook
nicholaspsmith Dec 28, 2025
da6d91e
Allow git push through bash-guardian hook
nicholaspsmith Dec 28, 2025
d9a20ef
Use webpack dev server in CI, Turbopack locally
nicholaspsmith Dec 28, 2025
f3924e4
Skip build step and add AUTH_TRUST_HOST for E2E tests
nicholaspsmith Dec 28, 2025
87d6aae
Temporarily disable error.tsx due to Next.js bug
nicholaspsmith Dec 28, 2025
a2c31f0
Disable test file blocking in agent guardian hook
nicholaspsmith Dec 28, 2025
92bec22
Restore protected error handler component
nicholaspsmith Dec 28, 2025
5ff3ed4
Skip E2E tests in CI pending selector updates (issue #231)
nicholaspsmith Dec 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .claude/hooks/bash-guardian.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/bin/bash
# Bash Guardian Hook
# Intercepts Bash commands and suggests git-agent for git operations
# Uses jq for safe JSON parsing

set -euo pipefail

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

# Validate input is JSON
if ! echo "$input" | jq empty 2>/dev/null; then
exit 0
fi

# Extract tool name and command safely
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
command=$(echo "$input" | jq -r '.tool_input.command // empty')

# Only process Bash operations
if [[ "$tool_name" != "Bash" ]]; then
exit 0
fi

# Exit if no command
if [[ -z "$command" ]]; then
exit 0
fi

# Check for git commands that should use git-agent
agent=""
reason=""

# git push - remind about review-agent workflow (but allow through for git-agent)
# Note: Review enforcement is process-based via CLAUDE.md, not technically enforced here
# since we can't distinguish main agent from git-agent subagent
if [[ "$command" =~ ^git[[:space:]]+push ]] || \
[[ "$command" =~ \&\&[[:space:]]*git[[:space:]]+push ]] || \
[[ "$command" =~ \;[[:space:]]*git[[:space:]]+push ]]; then
# Allow push - review-agent approval is enforced by workflow discipline
: # pass through

# git commit should use git-agent (unless it has proper Co-Authored-By format, indicating git-agent is running)
elif [[ "$command" =~ ^git[[:space:]]+commit ]] || \
[[ "$command" =~ \&\&[[:space:]]*git[[:space:]]+commit ]] || \
[[ "$command" =~ \;[[:space:]]*git[[:space:]]+commit ]]; then
# Allow commits that follow the required format (have Co-Authored-By: Claude)
# This indicates git-agent is properly formatting the commit
if [[ "$command" =~ Co-Authored-By:[[:space:]]*Claude ]]; then
: # Allow - properly formatted git-agent commit
else
agent="git-agent"
reason="Git commits should be created by git-agent for proper commit message formatting."
fi

# git rebase/merge/cherry-pick should use git-agent
elif [[ "$command" =~ ^git[[:space:]]+(rebase|merge|cherry-pick) ]]; then
agent="git-agent"
reason="Complex git operations should be handled by git-agent."

# git reset --hard is dangerous
elif [[ "$command" =~ git[[:space:]]+reset[[:space:]]+--hard ]]; then
agent="git-agent"
reason="Destructive git operations should be handled by git-agent with proper safeguards."

fi

# If an agent should handle this, deny with guidance
if [[ -n "$agent" ]]; then
jq -n \
--arg agent "$agent" \
--arg cmd "$command" \
--arg reason "$reason" \
'{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: (
"Command: " + $cmd + "\n\n" +
$reason + "\n\n" +
"Use: Task tool with subagent_type=\"git-agent\"\n\n" +
"Git workflow: git-agent (commit) → review-agent → git-agent (push)"
)
}
}'
exit 0
fi

# Allow commands not matching git patterns
exit 0
94 changes: 94 additions & 0 deletions .claude/hooks/file-agent-guardian.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/bin/bash
# File Agent Guardian Hook
# Intercepts Write/Edit calls and suggests appropriate specialized agents
# Uses jq for safe JSON parsing to prevent injection attacks

set -euo pipefail

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

# Validate input is JSON
if ! echo "$input" | jq empty 2>/dev/null; then
exit 0 # Allow if input is invalid (fail open)
fi

# Extract tool name and file path safely using jq
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

# Only process Write and Edit operations
if [[ "$tool_name" != "Write" && "$tool_name" != "Edit" ]]; then
exit 0
fi

# Exit if no file path
if [[ -z "$file_path" ]]; then
exit 0
fi

# Determine which agent should handle this file
agent=""
reason=""

# Test files → test-agent
# NOTE: Hook cannot distinguish subagent from main agent, so enforcement is advisory.
# test-agent itself gets blocked, making this a process-based rule via CLAUDE.md.
# Temporarily allowing test file edits for maintenance tasks (skip conditions, etc.)
# TODO: Re-enable when subagent detection is possible

# UI components → ui-agent
if [[ "$file_path" =~ ^components/ ]] || \
[[ "$file_path" =~ ^app/.*\.tsx$ ]] || \
[[ "$file_path" =~ ^app/\(protected\)/.* && "$file_path" =~ \.tsx$ ]]; then
agent="ui-agent"
reason="React components should be written by ui-agent for proper patterns and styling."

# Database files → db-agent
elif [[ "$file_path" =~ ^drizzle/ ]] || \
[[ "$file_path" =~ ^lib/db/ ]] || \
[[ "$file_path" =~ \.sql$ ]] || \
[[ "$file_path" =~ migration ]]; then
agent="db-agent"
reason="Database schema and migrations should be handled by db-agent."

# Deployment files → deploy-agent
elif [[ "$file_path" =~ ^Dockerfile ]] || \
[[ "$file_path" =~ ^docker-compose ]] || \
[[ "$file_path" =~ ^\.github/workflows/ ]] || \
[[ "$file_path" =~ ^nginx/ ]] || \
[[ "$file_path" =~ \.yml$ && "$file_path" =~ (deploy|ci|cd) ]]; then
agent="deploy-agent"
reason="Docker, CI/CD, and deployment configs should be handled by deploy-agent."

# Spec files → spec-agent
elif [[ "$file_path" =~ ^specs/.*\.(md|yaml|json)$ ]] && \
[[ ! "$file_path" =~ tasks\.md$ ]]; then
agent="spec-agent"
reason="Feature specifications should be written by spec-agent for proper structure."
fi

# If an agent should handle this, deny with guidance
if [[ -n "$agent" ]]; then
jq -n \
--arg agent "$agent" \
--arg path "$file_path" \
--arg reason "$reason" \
'{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: (
"File: " + $path + "\n\n" +
$reason + "\n\n" +
"Spawn the agent:\n" +
" Task tool with subagent_type=\"" + $agent + "\"\n\n" +
"If you have a specific reason to write this file directly, explain to the user."
)
}
}'
exit 0
fi

# Allow files not matching any pattern
exit 0
81 changes: 81 additions & 0 deletions .claude/hooks/navigation-guardian.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/bin/bash
# Navigation Guardian Hook
# Suggests Serena for symbol lookups and lance-context for semantic search
# Uses jq for safe JSON parsing

set -euo pipefail

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

# Validate input is JSON
if ! echo "$input" | jq empty 2>/dev/null; then
exit 0
fi

# Extract tool name and inputs safely
tool_name=$(echo "$input" | jq -r '.tool_name // empty')

suggestion=""
reason=""

case "$tool_name" in
"Grep")
pattern=$(echo "$input" | jq -r '.tool_input.pattern // empty')

# Check for symbol-like patterns that Serena handles better
# Patterns like "class Foo", "function bar", "def method", "interface IName"
if [[ "$pattern" =~ ^(class|function|def|const|let|var|interface|type|enum|struct|impl)[[:space:]]+ ]] || \
[[ "$pattern" =~ ^(export[[:space:]]+(default[[:space:]]+)?(class|function|const|interface|type)) ]]; then
suggestion="Serena find_symbol"
reason="For finding symbol definitions, use Serena's find_symbol tool instead of Grep. It's more precise and token-efficient.\n\nExample: find_symbol with name_path=\"ClassName\" or name_path=\"ClassName/methodName\""
fi
;;

"Read")
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

# Check if reading a large source file - suggest Serena for targeted reading
if [[ "$file_path" =~ \.(ts|tsx|js|jsx|py|go|rs|java|rb|cpp|c|h)$ ]] && \
[[ ! "$file_path" =~ (config|\.d\.ts|types\.ts|index\.) ]] && \
[[ ! "$file_path" =~ ^tests/ ]] && \
[[ ! "$file_path" =~ \.test\. ]] && \
[[ ! "$file_path" =~ \.spec\. ]]; then
# Only suggest for implementation files, not configs or tests
suggestion="Serena get_symbols_overview"
reason="Before reading an entire source file, consider using Serena's get_symbols_overview to see the file structure first, then find_symbol with include_body=True for specific functions.\n\nThis is more token-efficient for large files."
fi
;;

"Glob"|"Grep")
# For multiple search operations, suggest Explore agent or lance-context
# But we can't detect "multiple" in a single hook call, so skip this
;;
esac

# If we have a suggestion, provide it (but allow the operation for now)
# Navigation suggestions are softer - we want to educate, not block
if [[ -n "$suggestion" ]]; then
jq -n \
--arg suggestion "$suggestion" \
--arg reason "$reason" \
'{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: (
"Consider using: " + $suggestion + "\n\n" +
$reason + "\n\n" +
"For open-ended codebase exploration, use:\n" +
" Task tool with subagent_type=\"Explore\"\n\n" +
"For semantic code search, use:\n" +
" mcp__lance-context__search_code\n\n" +
"If you need to proceed with this tool, explain why the specialized tools are not suitable."
)
}
}'
exit 0
fi

# Allow operations not matching any pattern
exit 0
130 changes: 130 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"permissions": {
"allow": [
"Bash(gh issue close:*)",
"Bash(npm run build:*)",
"WebFetch(domain:lancedb.github.io)",
"Bash(git add:*)",
"Bash(gh issue list:*)",
"Bash(git commit:*)",
"Bash(gh issue view:*)",
"Bash(gh issue create:*)",
"Bash(npm test:*)",
"Bash(ls:*)",
"Bash(gh issue edit:*)",
"Bash(echo:*)",
"Bash(npm run test:integration:*)",
"Bash(npm run test:unit:*)",
"Bash(npm run)",
"Bash(test:*)",
"Bash(for task in 'Provision VPS (Hetzner CX22 or DigitalOcean Droplet)' 'Configure firewall (UFW)' 'Install Docker and Docker Compose on VPS' 'Create deploy user with SSH key authentication' 'Configure SSH hardening' 'Create directory structure on VPS' 'Create Nginx configuration for reverse proxy' 'Install Certbot on VPS for SSL certificates' 'Configure DNS A record for memoryloop.nicholaspsmith.com' 'Obtain SSL certificate using Certbot' 'Configure Nginx SSL settings' 'Set up automatic SSL certificate renewal')",
"Bash(cat:*)",
"Bash(.specify/scripts/bash/check-prerequisites.sh:*)",
"Bash(.specify/scripts/bash/setup-plan.sh:*)",
"WebSearch",
"Bash(npx eslint:*)",
"Bash(npx next lint:*)",
"Bash(npm install:*)",
"Bash(npm run lint:*)",
"Bash(npm run typecheck:*)",
"Bash(npm run format:*)",
"Bash(npm run format:check:*)",
"Bash(gh run list:*)",
"Bash(gh run watch:*)",
"Bash(gh run view:*)",
"Bash(git fetch:*)",
"Bash(.specify/scripts/bash/create-new-feature.sh:*)",
"Bash(gh pr view:*)",
"Bash(.specify/scripts/bash/update-agent-context.sh:*)",
"Bash(git stash:*)",
"Bash(git checkout:*)",
"Bash(git pull:*)",
"Bash(if [ -d \"/Users/nick/Code/memoryloop/specs/001-lancedb-schema-fixes/checklists\" ])",
"Bash(then find /Users/nick/Code/memoryloop/specs/001-lancedb-schema-fixes/checklists -name \"*.md\" -type f)",
"Bash(else echo \"No checklists directory found\")",
"Bash(fi)",
"Bash(git rev-parse:*)",
"Bash(gh pr diff:*)",
"Bash(/speckit.specify \"Spec-Kit Workflow Improvements: Agent Context Refactoring and Enhanced Developer Experience\")",
"Skill(speckit.specify)",
"Bash(./.specify/scripts/bash/update-agent-context.sh:*)",
"Bash(./.specify/scripts/bash/check-prerequisites.sh:*)",
"Bash(.specify/scripts/bash/get-current-branch.sh:*)",
"Bash(bash:*)",
"Bash(git -C /Users/nick/Code/memoryloop log --oneline --all --grep=\"main\" --source --decorate -30)",
"Bash(git -C /Users/nick/Code/memoryloop show main:.specify/scripts/bash/update-agent-context.sh)",
"Bash(git -C /Users/nick/Code/memoryloop log --oneline --all --grep=\"agent\" -15)",
"Bash(source .specify/scripts/bash/update-agent-context.sh)",
"Bash(extract_plan_field:*)",
"Bash(set -a)",
"Bash(source:*)",
"Bash(set +a)",
"Bash(npm run db:migrate:*)",
"Bash(npm run test:contract:*)",
"Bash(npx vitest:*)",
"Bash(wc:*)",
"Bash(grep:*)",
"Bash(npx playwright test:*)",
"WebFetch(domain:github.com)",
"WebFetch(domain:www.vibesparking.com)",
"Bash(claude mcp list:*)",
"Bash(curl:*)",
"Bash(lsof:*)",
"Bash(npm view:*)",
"Bash(npx drizzle-kit:*)",
"mcp__lance-context__get_index_status",
"Bash(gh pr checks:*)",
"WebFetch(domain:www.anthropic.com)",
"Bash(git reset:*)",
"Bash(.claude/hooks/file-agent-guardian.sh:*)",
"Bash(.claude/hooks/bash-guardian.sh:*)",
"Bash(jq:*)",
"Bash(/Users/nick/Code/memoryloop/.claude/hooks/file-agent-guardian.sh)",
"Bash(/Users/nick/Code/memoryloop/.claude/hooks/bash-guardian.sh)",
"Bash(gh pr list:*)",
"Bash(chmod:*)",
"Bash(.claude/hooks/navigation-guardian.sh:*)",
"Bash(/Users/nick/Code/memoryloop/.claude/hooks/navigation-guardian.sh)",
"Bash(shellcheck:*)"
]
},
"enabledMcpjsonServers": [
"lance-context"
],
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/file-agent-guardian.sh",
"timeout": 5
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/bash-guardian.sh",
"timeout": 5
}
]
},
{
"matcher": "Grep|Read",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/navigation-guardian.sh",
"timeout": 5
}
]
}
]
},
"outputStyle": "default",
"spinnerTipsEnabled": true
}
Loading
Loading