diff --git a/.claude/commands/.gitkeep b/.claude/commands/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 20eb959..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "$schema": "https://claude.ai/claude-code/settings.schema.json" -} diff --git a/CLAUDE.md b/CLAUDE.md index 89278cd..85f3e3c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,13 +2,28 @@ This repository contains configurations, prompts, and tools to improve the Claude Code AI workflow. +## Setup + +```bash +# Clone and install symlinks +git clone ssh://git@code.flowmade.one/flowmade-one/ai.git +cd ai +make install +``` + ## Project Structure -- `.claude/` - Claude Code configuration - - `commands/` - Custom slash commands (skills) -- `prompts/` - Reusable prompt templates -- `scripts/` - Automation scripts -- `hooks/` - Claude Code hooks (pre/post tool execution) +``` +ai/ +├── commands/ # Slash commands (/work-issue, /dashboard) +├── skills/ # Auto-triggered capabilities +├── agents/ # Subagents with isolated context +├── scripts/ # Hook scripts (pre-commit, token loading) +├── settings.json # Claude Code settings +└── Makefile # Install/uninstall symlinks +``` + +All files symlink to `~/.claude/` via `make install`. ## Forgejo Integration diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..73b3379 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +.PHONY: install uninstall status + +CLAUDE_DIR := $(HOME)/.claude +REPO_DIR := $(shell pwd) + +# Items to symlink +ITEMS := commands scripts skills agents settings.json + +install: + @echo "Installing Claude Code config symlinks..." + @mkdir -p $(CLAUDE_DIR) + @for item in $(ITEMS); do \ + if [ -e "$(REPO_DIR)/$$item" ]; then \ + if [ -L "$(CLAUDE_DIR)/$$item" ]; then \ + echo " $$item: already symlinked"; \ + elif [ -e "$(CLAUDE_DIR)/$$item" ]; then \ + echo " $$item: backing up existing to $$item.bak"; \ + mv "$(CLAUDE_DIR)/$$item" "$(CLAUDE_DIR)/$$item.bak"; \ + ln -s "$(REPO_DIR)/$$item" "$(CLAUDE_DIR)/$$item"; \ + echo " $$item: symlinked"; \ + else \ + ln -s "$(REPO_DIR)/$$item" "$(CLAUDE_DIR)/$$item"; \ + echo " $$item: symlinked"; \ + fi \ + fi \ + done + @echo "Done! Restart Claude Code to apply changes." + +uninstall: + @echo "Removing Claude Code config symlinks..." + @for item in $(ITEMS); do \ + if [ -L "$(CLAUDE_DIR)/$$item" ]; then \ + rm "$(CLAUDE_DIR)/$$item"; \ + echo " $$item: removed symlink"; \ + if [ -e "$(CLAUDE_DIR)/$$item.bak" ]; then \ + mv "$(CLAUDE_DIR)/$$item.bak" "$(CLAUDE_DIR)/$$item"; \ + echo " $$item: restored backup"; \ + fi \ + fi \ + done + @echo "Done!" + +status: + @echo "Claude Code config status:" + @for item in $(ITEMS); do \ + if [ -L "$(CLAUDE_DIR)/$$item" ]; then \ + target=$$(readlink "$(CLAUDE_DIR)/$$item"); \ + echo " $$item: symlink -> $$target"; \ + elif [ -e "$(CLAUDE_DIR)/$$item" ]; then \ + echo " $$item: exists (not symlinked)"; \ + else \ + echo " $$item: not found"; \ + fi \ + done diff --git a/commands/create-issue.md b/commands/create-issue.md new file mode 100644 index 0000000..0b0dd36 --- /dev/null +++ b/commands/create-issue.md @@ -0,0 +1,17 @@ +--- +description: Create a new Forgejo issue. Can create single issues or batch create from a plan. +argument-hint: [title] or "batch" +--- + +# Create Issue(s) + +## Single Issue (default) +If title provided: `fj issue create "$1" --body ""` + +## Batch Mode +If $1 is "batch": +1. Ask user for the plan/direction +2. Generate list of issues with titles and descriptions +3. Show for approval +4. Create each: `fj issue create "" --body "<body>"` +5. Display all created issue numbers diff --git a/commands/dashboard.md b/commands/dashboard.md new file mode 100644 index 0000000..0c2e511 --- /dev/null +++ b/commands/dashboard.md @@ -0,0 +1,12 @@ +--- +description: Show dashboard of open issues, PRs awaiting review, and CI status. +--- + +# Repository Dashboard + +Run these commands and present a summary: + +1. **Open Issues**: `fj issue search -s open` +2. **Open PRs**: `fj pr search -s open` + +Format as tables showing issue/PR number, title, and author. diff --git a/commands/review-pr.md b/commands/review-pr.md new file mode 100644 index 0000000..703ad99 --- /dev/null +++ b/commands/review-pr.md @@ -0,0 +1,18 @@ +--- +description: Review a Forgejo pull request. Fetches PR details, diff, and comments. +argument-hint: <pr-number> +--- + +# Review PR #$1 + +1. **View PR details**: `fj pr view $1` +2. **Check status**: `fj pr status $1` +3. **Get the diff**: `fj pr view $1 diff` + +Review the changes and provide feedback on: +- Code quality +- Potential bugs +- Test coverage +- Documentation + +Ask the user if they want to approve, request changes, or comment. diff --git a/commands/work-issue.md b/commands/work-issue.md new file mode 100644 index 0000000..c855528 --- /dev/null +++ b/commands/work-issue.md @@ -0,0 +1,14 @@ +--- +description: Work on a Forgejo issue. Fetches issue details and sets up branch for implementation. +argument-hint: <issue-number> +--- + +# Work on Issue #$1 + +1. **View the issue**: `fj issue view $1` +2. **Create a branch**: `git checkout -b issue-$1-<short-kebab-title>` +3. **Plan**: Use TodoWrite to break down the work based on acceptance criteria +4. **Implement** the changes +5. **Commit** with message referencing the issue +6. **Push**: `git push -u origin <branch>` +7. **Create PR**: `fj pr create "[Issue #$1] <title>" --body "Closes #$1"` diff --git a/hooks/.gitkeep b/hooks/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/prompts/.gitkeep b/prompts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/.gitkeep b/scripts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/load-forgejo-token.sh b/scripts/load-forgejo-token.sh new file mode 100755 index 0000000..e0f98a7 --- /dev/null +++ b/scripts/load-forgejo-token.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Load Forgejo token from macOS Keychain into Claude Code session +if [ -n "$CLAUDE_ENV_FILE" ]; then + TOKEN=$(security find-generic-password -a "$USER" -s "forgejo-token" -w 2>/dev/null) + if [ -n "$TOKEN" ]; then + echo "export FORGEJO_TOKEN=\"$TOKEN\"" >> "$CLAUDE_ENV_FILE" + fi +fi +exit 0 diff --git a/scripts/pre-commit-checks.sh b/scripts/pre-commit-checks.sh new file mode 100755 index 0000000..7deb7e6 --- /dev/null +++ b/scripts/pre-commit-checks.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Pre-commit validation script for Claude Code +# Validates YAML, checks for secrets, validates K8s manifests + +set -e + +# Get staged files +STAGED_FILES=$(git diff --cached --name-only 2>/dev/null || echo "") + +if [ -z "$STAGED_FILES" ]; then + exit 0 +fi + +# Check for potential secrets in staged files +echo "Checking for potential secrets..." +SECRET_PATTERN='(password|secret|token|api_key|apikey|private_key).*[=:].{20,}' +if echo "$STAGED_FILES" | xargs grep -l -iE "$SECRET_PATTERN" 2>/dev/null | grep -v '.sops.yaml' | grep -v 'secret.*\.enc\.yaml'; then + echo "WARNING: Potential secrets detected in staged files (excluding SOPS-encrypted files)" + echo "Please verify these are encrypted or not actual secrets." +fi + +# Validate YAML syntax +echo "Validating YAML syntax..." +for file in $(echo "$STAGED_FILES" | grep -E '\.ya?ml$'); do + if [ -f "$file" ]; then + if ! python3 -c "import yaml; yaml.safe_load(open('$file'))" 2>/dev/null; then + echo "ERROR: Invalid YAML syntax: $file" + exit 1 + fi + fi +done + +# Validate Kubernetes manifests (if kubectl available) +if command -v kubectl &>/dev/null; then + echo "Validating Kubernetes manifests..." + for file in $(echo "$STAGED_FILES" | grep -E '\.ya?ml$'); do + if [ -f "$file" ] && grep -q "^kind:" "$file" 2>/dev/null; then + # Skip SOPS-encrypted files and kustomization files + if echo "$file" | grep -qE '(\.sops\.yaml|\.enc\.yaml|kustomization\.yaml)$'; then + continue + fi + if ! kubectl apply --dry-run=client -f "$file" 2>/dev/null; then + echo "WARNING: Kubernetes validation failed: $file (may be expected for partial manifests)" + fi + fi + done +fi + +echo "Pre-commit checks passed." +exit 0 diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..4590b02 --- /dev/null +++ b/settings.json @@ -0,0 +1,34 @@ +{ + "model": "opus", + "statusLine": { + "type": "command", + "command": "input=$(cat); current_dir=$(echo \"$input\" | jq -r '.workspace.current_dir'); model=$(echo \"$input\" | jq -r '.model.display_name'); style=$(echo \"$input\" | jq -r '.output_style.name'); git_info=\"\"; if [ -d \"$current_dir/.git\" ]; then cd \"$current_dir\" && branch=$(git branch --show-current 2>/dev/null) && status=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ') && git_info=\" [$branch$([ \"$status\" != \"0\" ] && echo \"*\")]\"; fi; printf \"\\033[2m$(whoami)@$(hostname -s) $(basename \"$current_dir\")$git_info | $model ($style)\\033[0m\"" + }, + "enabledPlugins": { + "gopls-lsp@claude-plugins-official": true + }, + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "~/.claude/scripts/load-forgejo-token.sh" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": "Bash(git commit:*)", + "hooks": [ + { + "type": "command", + "command": "~/.claude/scripts/pre-commit-checks.sh", + "statusMessage": "Running pre-commit checks..." + } + ] + } + ] + } +}