Merge branch 'affaan-m:main' into main

This commit is contained in:
Panger Lkr
2026-01-27 10:15:34 +05:30
committed by GitHub
71 changed files with 6517 additions and 227 deletions

View File

@@ -23,5 +23,7 @@
"best-practices"
],
"commands": "./commands",
"skills": "./skills"
"skills": "./skills",
"agents": "./agents",
"hooks": "./hooks/hooks.json"
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Affaan Mustafa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -4,6 +4,7 @@
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![Shell](https://img.shields.io/badge/-Shell-4EAA25?logo=gnu-bash&logoColor=white)
![TypeScript](https://img.shields.io/badge/-TypeScript-3178C6?logo=typescript&logoColor=white)
![Go](https://img.shields.io/badge/-Go-00ADD8?logo=go&logoColor=white)
![Markdown](https://img.shields.io/badge/-Markdown-000000?logo=markdown&logoColor=white)
**The complete collection of Claude Code configs from an Anthropic hackathon winner.**
@@ -101,17 +102,23 @@ everything-claude-code/
| |-- e2e-runner.md # Playwright E2E testing
| |-- refactor-cleaner.md # Dead code cleanup
| |-- doc-updater.md # Documentation sync
| |-- go-reviewer.md # Go code review (NEW)
| |-- go-build-resolver.md # Go build error resolution (NEW)
|
|-- skills/ # Workflow definitions and domain knowledge
| |-- coding-standards/ # Language best practices
| |-- backend-patterns/ # API, database, caching patterns
| |-- frontend-patterns/ # React, Next.js patterns
| |-- continuous-learning/ # Auto-extract patterns from sessions (Longform Guide)
| |-- continuous-learning-v2/ # Instinct-based learning with confidence scoring
| |-- iterative-retrieval/ # Progressive context refinement for subagents
| |-- strategic-compact/ # Manual compaction suggestions (Longform Guide)
| |-- tdd-workflow/ # TDD methodology
| |-- security-review/ # Security checklist
| |-- eval-harness/ # Verification loop evaluation (Longform Guide)
| |-- verification-loop/ # Continuous verification (Longform Guide)
| |-- golang-patterns/ # Go idioms and best practices (NEW)
| |-- golang-testing/ # Go testing patterns, TDD, benchmarks (NEW)
|
|-- commands/ # Slash commands for quick execution
| |-- tdd.md # /tdd - Test-driven development
@@ -123,7 +130,10 @@ everything-claude-code/
| |-- learn.md # /learn - Extract patterns mid-session (Longform Guide)
| |-- checkpoint.md # /checkpoint - Save verification state (Longform Guide)
| |-- verify.md # /verify - Run verification loop (Longform Guide)
| |-- setup-pm.md # /setup-pm - Configure package manager (NEW)
| |-- setup-pm.md # /setup-pm - Configure package manager
| |-- go-review.md # /go-review - Go code review (NEW)
| |-- go-test.md # /go-test - Go TDD workflow (NEW)
| |-- go-build.md # /go-build - Fix Go build errors (NEW)
|
|-- rules/ # Always-follow guidelines (copy to ~/.claude/rules/)
| |-- security.md # Mandatory security checks
@@ -172,6 +182,28 @@ everything-claude-code/
---
## Ecosystem Tools
### ecc.tools - Skill Creator
Automatically generate Claude Code skills from your repository.
[Install GitHub App](https://github.com/apps/skill-creator) | [ecc.tools](https://ecc.tools)
Analyzes your repository and creates:
- **SKILL.md files** - Ready-to-use skills for Claude Code
- **Instinct collections** - For continuous-learning-v2
- **Pattern extraction** - Learns from your commit history
```bash
# After installing the GitHub App, skills appear in:
~/.claude/skills/generated/
```
Works seamlessly with the `continuous-learning-v2` skill for inherited instincts.
---
## Installation
### Option 1: Install as Plugin (Recommended)
@@ -251,7 +283,7 @@ Subagents handle delegated tasks with limited scope. Example:
---
name: code-reviewer
description: Reviews code for quality, security, and maintainability
tools: Read, Grep, Glob, Bash
tools: ["Read", "Grep", "Glob", "Bash"]
model: opus
---
@@ -329,7 +361,7 @@ Please contribute! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
### Ideas for Contributions
- Language-specific skills (Python, Go, Rust patterns)
- Language-specific skills (Python, Rust patterns) - Go now included!
- Framework-specific configs (Django, Rails, Laravel)
- DevOps agents (Kubernetes, Terraform, AWS)
- Testing strategies (different frameworks)

View File

@@ -1,7 +1,7 @@
---
name: architect
description: Software architecture specialist for system design, scalability, and technical decision-making. Use PROACTIVELY when planning new features, refactoring large systems, or making architectural decisions.
tools: Read, Grep, Glob
tools: ["Read", "Grep", "Glob"]
model: opus
---

View File

@@ -1,7 +1,7 @@
---
name: build-error-resolver
description: Build and TypeScript error resolution specialist. Use PROACTIVELY when build fails or type errors occur. Fixes build/type errors only with minimal diffs, no architectural edits. Focuses on getting the build green quickly.
tools: Read, Write, Edit, Bash, Grep, Glob
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
---

View File

@@ -1,7 +1,7 @@
---
name: code-reviewer
description: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code. MUST BE USED for all code changes.
tools: Read, Grep, Glob, Bash
tools: ["Read", "Grep", "Glob", "Bash"]
model: opus
---

654
agents/database-reviewer.md Normal file
View File

@@ -0,0 +1,654 @@
---
name: database-reviewer
description: PostgreSQL database specialist for query optimization, schema design, security, and performance. Use PROACTIVELY when writing SQL, creating migrations, designing schemas, or troubleshooting database performance. Incorporates Supabase best practices.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
---
# Database Reviewer
You are an expert PostgreSQL database specialist focused on query optimization, schema design, security, and performance. Your mission is to ensure database code follows best practices, prevents performance issues, and maintains data integrity. This agent incorporates patterns from [Supabase's postgres-best-practices](https://github.com/supabase/agent-skills).
## Core Responsibilities
1. **Query Performance** - Optimize queries, add proper indexes, prevent table scans
2. **Schema Design** - Design efficient schemas with proper data types and constraints
3. **Security & RLS** - Implement Row Level Security, least privilege access
4. **Connection Management** - Configure pooling, timeouts, limits
5. **Concurrency** - Prevent deadlocks, optimize locking strategies
6. **Monitoring** - Set up query analysis and performance tracking
## Tools at Your Disposal
### Database Analysis Commands
```bash
# Connect to database
psql $DATABASE_URL
# Check for slow queries (requires pg_stat_statements)
psql -c "SELECT query, mean_exec_time, calls FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;"
# Check table sizes
psql -c "SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC;"
# Check index usage
psql -c "SELECT indexrelname, idx_scan, idx_tup_read FROM pg_stat_user_indexes ORDER BY idx_scan DESC;"
# Find missing indexes on foreign keys
psql -c "SELECT conrelid::regclass, a.attname FROM pg_constraint c JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey) WHERE c.contype = 'f' AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.conrelid AND a.attnum = ANY(i.indkey));"
# Check for table bloat
psql -c "SELECT relname, n_dead_tup, last_vacuum, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup > 1000 ORDER BY n_dead_tup DESC;"
```
## Database Review Workflow
### 1. Query Performance Review (CRITICAL)
For every SQL query, verify:
```
a) Index Usage
- Are WHERE columns indexed?
- Are JOIN columns indexed?
- Is the index type appropriate (B-tree, GIN, BRIN)?
b) Query Plan Analysis
- Run EXPLAIN ANALYZE on complex queries
- Check for Seq Scans on large tables
- Verify row estimates match actuals
c) Common Issues
- N+1 query patterns
- Missing composite indexes
- Wrong column order in indexes
```
### 2. Schema Design Review (HIGH)
```
a) Data Types
- bigint for IDs (not int)
- text for strings (not varchar(n) unless constraint needed)
- timestamptz for timestamps (not timestamp)
- numeric for money (not float)
- boolean for flags (not varchar)
b) Constraints
- Primary keys defined
- Foreign keys with proper ON DELETE
- NOT NULL where appropriate
- CHECK constraints for validation
c) Naming
- lowercase_snake_case (avoid quoted identifiers)
- Consistent naming patterns
```
### 3. Security Review (CRITICAL)
```
a) Row Level Security
- RLS enabled on multi-tenant tables?
- Policies use (select auth.uid()) pattern?
- RLS columns indexed?
b) Permissions
- Least privilege principle followed?
- No GRANT ALL to application users?
- Public schema permissions revoked?
c) Data Protection
- Sensitive data encrypted?
- PII access logged?
```
---
## Index Patterns
### 1. Add Indexes on WHERE and JOIN Columns
**Impact:** 100-1000x faster queries on large tables
```sql
-- ❌ BAD: No index on foreign key
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
-- Missing index!
);
-- ✅ GOOD: Index on foreign key
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
);
CREATE INDEX orders_customer_id_idx ON orders (customer_id);
```
### 2. Choose the Right Index Type
| Index Type | Use Case | Operators |
|------------|----------|-----------|
| **B-tree** (default) | Equality, range | `=`, `<`, `>`, `BETWEEN`, `IN` |
| **GIN** | Arrays, JSONB, full-text | `@>`, `?`, `?&`, `?|`, `@@` |
| **BRIN** | Large time-series tables | Range queries on sorted data |
| **Hash** | Equality only | `=` (marginally faster than B-tree) |
```sql
-- ❌ BAD: B-tree for JSONB containment
CREATE INDEX products_attrs_idx ON products (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
-- ✅ GOOD: GIN for JSONB
CREATE INDEX products_attrs_idx ON products USING gin (attributes);
```
### 3. Composite Indexes for Multi-Column Queries
**Impact:** 5-10x faster multi-column queries
```sql
-- ❌ BAD: Separate indexes
CREATE INDEX orders_status_idx ON orders (status);
CREATE INDEX orders_created_idx ON orders (created_at);
-- ✅ GOOD: Composite index (equality columns first, then range)
CREATE INDEX orders_status_created_idx ON orders (status, created_at);
```
**Leftmost Prefix Rule:**
- Index `(status, created_at)` works for:
- `WHERE status = 'pending'`
- `WHERE status = 'pending' AND created_at > '2024-01-01'`
- Does NOT work for:
- `WHERE created_at > '2024-01-01'` alone
### 4. Covering Indexes (Index-Only Scans)
**Impact:** 2-5x faster queries by avoiding table lookups
```sql
-- ❌ BAD: Must fetch name from table
CREATE INDEX users_email_idx ON users (email);
SELECT email, name FROM users WHERE email = 'user@example.com';
-- ✅ GOOD: All columns in index
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);
```
### 5. Partial Indexes for Filtered Queries
**Impact:** 5-20x smaller indexes, faster writes and queries
```sql
-- ❌ BAD: Full index includes deleted rows
CREATE INDEX users_email_idx ON users (email);
-- ✅ GOOD: Partial index excludes deleted rows
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;
```
**Common Patterns:**
- Soft deletes: `WHERE deleted_at IS NULL`
- Status filters: `WHERE status = 'pending'`
- Non-null values: `WHERE sku IS NOT NULL`
---
## Schema Design Patterns
### 1. Data Type Selection
```sql
-- ❌ BAD: Poor type choices
CREATE TABLE users (
id int, -- Overflows at 2.1B
email varchar(255), -- Artificial limit
created_at timestamp, -- No timezone
is_active varchar(5), -- Should be boolean
balance float -- Precision loss
);
-- ✅ GOOD: Proper types
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL,
created_at timestamptz DEFAULT now(),
is_active boolean DEFAULT true,
balance numeric(10,2)
);
```
### 2. Primary Key Strategy
```sql
-- ✅ Single database: IDENTITY (default, recommended)
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
);
-- ✅ Distributed systems: UUIDv7 (time-ordered)
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
CREATE TABLE orders (
id uuid DEFAULT uuid_generate_v7() PRIMARY KEY
);
-- ❌ AVOID: Random UUIDs cause index fragmentation
CREATE TABLE events (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY -- Fragmented inserts!
);
```
### 3. Table Partitioning
**Use When:** Tables > 100M rows, time-series data, need to drop old data
```sql
-- ✅ GOOD: Partitioned by month
CREATE TABLE events (
id bigint GENERATED ALWAYS AS IDENTITY,
created_at timestamptz NOT NULL,
data jsonb
) PARTITION BY RANGE (created_at);
CREATE TABLE events_2024_01 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE events_2024_02 PARTITION OF events
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- Drop old data instantly
DROP TABLE events_2023_01; -- Instant vs DELETE taking hours
```
### 4. Use Lowercase Identifiers
```sql
-- ❌ BAD: Quoted mixed-case requires quotes everywhere
CREATE TABLE "Users" ("userId" bigint, "firstName" text);
SELECT "firstName" FROM "Users"; -- Must quote!
-- ✅ GOOD: Lowercase works without quotes
CREATE TABLE users (user_id bigint, first_name text);
SELECT first_name FROM users;
```
---
## Security & Row Level Security (RLS)
### 1. Enable RLS for Multi-Tenant Data
**Impact:** CRITICAL - Database-enforced tenant isolation
```sql
-- ❌ BAD: Application-only filtering
SELECT * FROM orders WHERE user_id = $current_user_id;
-- Bug means all orders exposed!
-- ✅ GOOD: Database-enforced RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
CREATE POLICY orders_user_policy ON orders
FOR ALL
USING (user_id = current_setting('app.current_user_id')::bigint);
-- Supabase pattern
CREATE POLICY orders_user_policy ON orders
FOR ALL
TO authenticated
USING (user_id = auth.uid());
```
### 2. Optimize RLS Policies
**Impact:** 5-10x faster RLS queries
```sql
-- ❌ BAD: Function called per row
CREATE POLICY orders_policy ON orders
USING (auth.uid() = user_id); -- Called 1M times for 1M rows!
-- ✅ GOOD: Wrap in SELECT (cached, called once)
CREATE POLICY orders_policy ON orders
USING ((SELECT auth.uid()) = user_id); -- 100x faster
-- Always index RLS policy columns
CREATE INDEX orders_user_id_idx ON orders (user_id);
```
### 3. Least Privilege Access
```sql
-- ❌ BAD: Overly permissive
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
-- ✅ GOOD: Minimal permissions
CREATE ROLE app_readonly NOLOGIN;
GRANT USAGE ON SCHEMA public TO app_readonly;
GRANT SELECT ON public.products, public.categories TO app_readonly;
CREATE ROLE app_writer NOLOGIN;
GRANT USAGE ON SCHEMA public TO app_writer;
GRANT SELECT, INSERT, UPDATE ON public.orders TO app_writer;
-- No DELETE permission
REVOKE ALL ON SCHEMA public FROM public;
```
---
## Connection Management
### 1. Connection Limits
**Formula:** `(RAM_in_MB / 5MB_per_connection) - reserved`
```sql
-- 4GB RAM example
ALTER SYSTEM SET max_connections = 100;
ALTER SYSTEM SET work_mem = '8MB'; -- 8MB * 100 = 800MB max
SELECT pg_reload_conf();
-- Monitor connections
SELECT count(*), state FROM pg_stat_activity GROUP BY state;
```
### 2. Idle Timeouts
```sql
ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
ALTER SYSTEM SET idle_session_timeout = '10min';
SELECT pg_reload_conf();
```
### 3. Use Connection Pooling
- **Transaction mode**: Best for most apps (connection returned after each transaction)
- **Session mode**: For prepared statements, temp tables
- **Pool size**: `(CPU_cores * 2) + spindle_count`
---
## Concurrency & Locking
### 1. Keep Transactions Short
```sql
-- ❌ BAD: Lock held during external API call
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- HTTP call takes 5 seconds...
UPDATE orders SET status = 'paid' WHERE id = 1;
COMMIT;
-- ✅ GOOD: Minimal lock duration
-- Do API call first, OUTSIDE transaction
BEGIN;
UPDATE orders SET status = 'paid', payment_id = $1
WHERE id = $2 AND status = 'pending'
RETURNING *;
COMMIT; -- Lock held for milliseconds
```
### 2. Prevent Deadlocks
```sql
-- ❌ BAD: Inconsistent lock order causes deadlock
-- Transaction A: locks row 1, then row 2
-- Transaction B: locks row 2, then row 1
-- DEADLOCK!
-- ✅ GOOD: Consistent lock order
BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- Now both rows locked, update in any order
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
```
### 3. Use SKIP LOCKED for Queues
**Impact:** 10x throughput for worker queues
```sql
-- ❌ BAD: Workers wait for each other
SELECT * FROM jobs WHERE status = 'pending' LIMIT 1 FOR UPDATE;
-- ✅ GOOD: Workers skip locked rows
UPDATE jobs
SET status = 'processing', worker_id = $1, started_at = now()
WHERE id = (
SELECT id FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED
)
RETURNING *;
```
---
## Data Access Patterns
### 1. Batch Inserts
**Impact:** 10-50x faster bulk inserts
```sql
-- ❌ BAD: Individual inserts
INSERT INTO events (user_id, action) VALUES (1, 'click');
INSERT INTO events (user_id, action) VALUES (2, 'view');
-- 1000 round trips
-- ✅ GOOD: Batch insert
INSERT INTO events (user_id, action) VALUES
(1, 'click'),
(2, 'view'),
(3, 'click');
-- 1 round trip
-- ✅ BEST: COPY for large datasets
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);
```
### 2. Eliminate N+1 Queries
```sql
-- ❌ BAD: N+1 pattern
SELECT id FROM users WHERE active = true; -- Returns 100 IDs
-- Then 100 queries:
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM orders WHERE user_id = 2;
-- ... 98 more
-- ✅ GOOD: Single query with ANY
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
-- ✅ GOOD: JOIN
SELECT u.id, u.name, o.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true;
```
### 3. Cursor-Based Pagination
**Impact:** Consistent O(1) performance regardless of page depth
```sql
-- ❌ BAD: OFFSET gets slower with depth
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
-- Scans 200,000 rows!
-- ✅ GOOD: Cursor-based (always fast)
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
-- Uses index, O(1)
```
### 4. UPSERT for Insert-or-Update
```sql
-- ❌ BAD: Race condition
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
-- Both threads find nothing, both insert, one fails
-- ✅ GOOD: Atomic UPSERT
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value, updated_at = now()
RETURNING *;
```
---
## Monitoring & Diagnostics
### 1. Enable pg_stat_statements
```sql
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Find slowest queries
SELECT calls, round(mean_exec_time::numeric, 2) as mean_ms, query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- Find most frequent queries
SELECT calls, query
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 10;
```
### 2. EXPLAIN ANALYZE
```sql
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE customer_id = 123;
```
| Indicator | Problem | Solution |
|-----------|---------|----------|
| `Seq Scan` on large table | Missing index | Add index on filter columns |
| `Rows Removed by Filter` high | Poor selectivity | Check WHERE clause |
| `Buffers: read >> hit` | Data not cached | Increase `shared_buffers` |
| `Sort Method: external merge` | `work_mem` too low | Increase `work_mem` |
### 3. Maintain Statistics
```sql
-- Analyze specific table
ANALYZE orders;
-- Check when last analyzed
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY last_analyze NULLS FIRST;
-- Tune autovacuum for high-churn tables
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_scale_factor = 0.02
);
```
---
## JSONB Patterns
### 1. Index JSONB Columns
```sql
-- GIN index for containment operators
CREATE INDEX products_attrs_gin ON products USING gin (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
-- Expression index for specific keys
CREATE INDEX products_brand_idx ON products ((attributes->>'brand'));
SELECT * FROM products WHERE attributes->>'brand' = 'Nike';
-- jsonb_path_ops: 2-3x smaller, only supports @>
CREATE INDEX idx ON products USING gin (attributes jsonb_path_ops);
```
### 2. Full-Text Search with tsvector
```sql
-- Add generated tsvector column
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
to_tsvector('english', coalesce(title,'') || ' ' || coalesce(content,''))
) STORED;
CREATE INDEX articles_search_idx ON articles USING gin (search_vector);
-- Fast full-text search
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('english', 'postgresql & performance');
-- With ranking
SELECT *, ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('english', 'postgresql') query
WHERE search_vector @@ query
ORDER BY rank DESC;
```
---
## Anti-Patterns to Flag
### ❌ Query Anti-Patterns
- `SELECT *` in production code
- Missing indexes on WHERE/JOIN columns
- OFFSET pagination on large tables
- N+1 query patterns
- Unparameterized queries (SQL injection risk)
### ❌ Schema Anti-Patterns
- `int` for IDs (use `bigint`)
- `varchar(255)` without reason (use `text`)
- `timestamp` without timezone (use `timestamptz`)
- Random UUIDs as primary keys (use UUIDv7 or IDENTITY)
- Mixed-case identifiers requiring quotes
### ❌ Security Anti-Patterns
- `GRANT ALL` to application users
- Missing RLS on multi-tenant tables
- RLS policies calling functions per-row (not wrapped in SELECT)
- Unindexed RLS policy columns
### ❌ Connection Anti-Patterns
- No connection pooling
- No idle timeouts
- Prepared statements with transaction-mode pooling
- Holding locks during external API calls
---
## Review Checklist
### Before Approving Database Changes:
- [ ] All WHERE/JOIN columns indexed
- [ ] Composite indexes in correct column order
- [ ] Proper data types (bigint, text, timestamptz, numeric)
- [ ] RLS enabled on multi-tenant tables
- [ ] RLS policies use `(SELECT auth.uid())` pattern
- [ ] Foreign keys have indexes
- [ ] No N+1 query patterns
- [ ] EXPLAIN ANALYZE run on complex queries
- [ ] Lowercase identifiers used
- [ ] Transactions kept short
---
**Remember**: Database issues are often the root cause of application performance problems. Optimize queries and schema design early. Use EXPLAIN ANALYZE to verify assumptions. Always index foreign keys and RLS policy columns.
*Patterns adapted from [Supabase Agent Skills](https://github.com/supabase/agent-skills) under MIT license.*

View File

@@ -1,7 +1,7 @@
---
name: doc-updater
description: Documentation and codemap specialist. Use PROACTIVELY for updating codemaps and documentation. Runs /update-codemaps and /update-docs, generates docs/CODEMAPS/*, updates READMEs and guides.
tools: Read, Write, Edit, Bash, Grep, Glob
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
---
@@ -27,8 +27,8 @@ You are a documentation specialist focused on keeping codemaps and documentation
### Analysis Commands
```bash
# Analyze TypeScript project structure
npx ts-morph
# Analyze TypeScript project structure (run custom script using ts-morph library)
npx tsx scripts/codemaps/generate.ts
# Generate dependency graph
npx madge --image graph.svg src/

View File

@@ -1,26 +1,115 @@
---
name: e2e-runner
description: End-to-end testing specialist using Playwright. Use PROACTIVELY for generating, maintaining, and running E2E tests. Manages test journeys, quarantines flaky tests, uploads artifacts (screenshots, videos, traces), and ensures critical user flows work.
tools: Read, Write, Edit, Bash, Grep, Glob
description: End-to-end testing specialist using Vercel Agent Browser (preferred) with Playwright fallback. Use PROACTIVELY for generating, maintaining, and running E2E tests. Manages test journeys, quarantines flaky tests, uploads artifacts (screenshots, videos, traces), and ensures critical user flows work.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
---
# E2E Test Runner
You are an expert end-to-end testing specialist focused on Playwright test automation. Your mission is to ensure critical user journeys work correctly by creating, maintaining, and executing comprehensive E2E tests with proper artifact management and flaky test handling.
You are an expert end-to-end testing specialist. Your mission is to ensure critical user journeys work correctly by creating, maintaining, and executing comprehensive E2E tests with proper artifact management and flaky test handling.
## Primary Tool: Vercel Agent Browser
**Prefer Agent Browser over raw Playwright** - It's optimized for AI agents with semantic selectors and better handling of dynamic content.
### Why Agent Browser?
- **Semantic selectors** - Find elements by meaning, not brittle CSS/XPath
- **AI-optimized** - Designed for LLM-driven browser automation
- **Auto-waiting** - Intelligent waits for dynamic content
- **Built on Playwright** - Full Playwright compatibility as fallback
### Agent Browser Setup
```bash
# Install agent-browser globally
npm install -g agent-browser
# Install Chromium (required)
agent-browser install
```
### Agent Browser CLI Usage (Primary)
Agent Browser uses a snapshot + refs system optimized for AI agents:
```bash
# Open a page and get a snapshot with interactive elements
agent-browser open https://example.com
agent-browser snapshot -i # Returns elements with refs like [ref=e1]
# Interact using element references from snapshot
agent-browser click @e1 # Click element by ref
agent-browser fill @e2 "user@example.com" # Fill input by ref
agent-browser fill @e3 "password123" # Fill password field
agent-browser click @e4 # Click submit button
# Wait for conditions
agent-browser wait visible @e5 # Wait for element
agent-browser wait navigation # Wait for page load
# Take screenshots
agent-browser screenshot after-login.png
# Get text content
agent-browser get text @e1
```
### Agent Browser in Scripts
For programmatic control, use the CLI via shell commands:
```typescript
import { execSync } from 'child_process'
// Execute agent-browser commands
const snapshot = execSync('agent-browser snapshot -i --json').toString()
const elements = JSON.parse(snapshot)
// Find element ref and interact
execSync('agent-browser click @e1')
execSync('agent-browser fill @e2 "test@example.com"')
```
### Programmatic API (Advanced)
For direct browser control (screencasts, low-level events):
```typescript
import { BrowserManager } from 'agent-browser'
const browser = new BrowserManager()
await browser.launch({ headless: true })
await browser.navigate('https://example.com')
// Low-level event injection
await browser.injectMouseEvent({ type: 'mousePressed', x: 100, y: 200, button: 'left' })
await browser.injectKeyboardEvent({ type: 'keyDown', key: 'Enter', code: 'Enter' })
// Screencast for AI vision
await browser.startScreencast() // Stream viewport frames
```
### Agent Browser with Claude Code
If you have the `agent-browser` skill installed, use `/agent-browser` for interactive browser automation tasks.
---
## Fallback Tool: Playwright
When Agent Browser isn't available or for complex test suites, fall back to Playwright.
## Core Responsibilities
1. **Test Journey Creation** - Write Playwright tests for user flows
1. **Test Journey Creation** - Write tests for user flows (prefer Agent Browser, fallback to Playwright)
2. **Test Maintenance** - Keep tests up to date with UI changes
3. **Flaky Test Management** - Identify and quarantine unstable tests
4. **Artifact Management** - Capture screenshots, videos, traces
5. **CI/CD Integration** - Ensure tests run reliably in pipelines
6. **Test Reporting** - Generate HTML reports and JUnit XML
## Tools at Your Disposal
## Playwright Testing Framework (Fallback)
### Playwright Testing Framework
### Tools
- **@playwright/test** - Core testing framework
- **Playwright Inspector** - Debug tests interactively
- **Playwright Trace Viewer** - Analyze test execution

368
agents/go-build-resolver.md Normal file
View File

@@ -0,0 +1,368 @@
---
name: go-build-resolver
description: Go build, vet, and compilation error resolution specialist. Fixes build errors, go vet issues, and linter warnings with minimal changes. Use when Go builds fail.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
---
# Go Build Error Resolver
You are an expert Go build error resolution specialist. Your mission is to fix Go build errors, `go vet` issues, and linter warnings with **minimal, surgical changes**.
## Core Responsibilities
1. Diagnose Go compilation errors
2. Fix `go vet` warnings
3. Resolve `staticcheck` / `golangci-lint` issues
4. Handle module dependency problems
5. Fix type errors and interface mismatches
## Diagnostic Commands
Run these in order to understand the problem:
```bash
# 1. Basic build check
go build ./...
# 2. Vet for common mistakes
go vet ./...
# 3. Static analysis (if available)
staticcheck ./... 2>/dev/null || echo "staticcheck not installed"
golangci-lint run 2>/dev/null || echo "golangci-lint not installed"
# 4. Module verification
go mod verify
go mod tidy -v
# 5. List dependencies
go list -m all
```
## Common Error Patterns & Fixes
### 1. Undefined Identifier
**Error:** `undefined: SomeFunc`
**Causes:**
- Missing import
- Typo in function/variable name
- Unexported identifier (lowercase first letter)
- Function defined in different file with build constraints
**Fix:**
```go
// Add missing import
import "package/that/defines/SomeFunc"
// Or fix typo
// somefunc -> SomeFunc
// Or export the identifier
// func someFunc() -> func SomeFunc()
```
### 2. Type Mismatch
**Error:** `cannot use x (type A) as type B`
**Causes:**
- Wrong type conversion
- Interface not satisfied
- Pointer vs value mismatch
**Fix:**
```go
// Type conversion
var x int = 42
var y int64 = int64(x)
// Pointer to value
var ptr *int = &x
var val int = *ptr
// Value to pointer
var val int = 42
var ptr *int = &val
```
### 3. Interface Not Satisfied
**Error:** `X does not implement Y (missing method Z)`
**Diagnosis:**
```bash
# Find what methods are missing
go doc package.Interface
```
**Fix:**
```go
// Implement missing method with correct signature
func (x *X) Z() error {
// implementation
return nil
}
// Check receiver type matches (pointer vs value)
// If interface expects: func (x X) Method()
// You wrote: func (x *X) Method() // Won't satisfy
```
### 4. Import Cycle
**Error:** `import cycle not allowed`
**Diagnosis:**
```bash
go list -f '{{.ImportPath}} -> {{.Imports}}' ./...
```
**Fix:**
- Move shared types to a separate package
- Use interfaces to break the cycle
- Restructure package dependencies
```text
# Before (cycle)
package/a -> package/b -> package/a
# After (fixed)
package/types <- shared types
package/a -> package/types
package/b -> package/types
```
### 5. Cannot Find Package
**Error:** `cannot find package "x"`
**Fix:**
```bash
# Add dependency
go get package/path@version
# Or update go.mod
go mod tidy
# Or for local packages, check go.mod module path
# Module: github.com/user/project
# Import: github.com/user/project/internal/pkg
```
### 6. Missing Return
**Error:** `missing return at end of function`
**Fix:**
```go
func Process() (int, error) {
if condition {
return 0, errors.New("error")
}
return 42, nil // Add missing return
}
```
### 7. Unused Variable/Import
**Error:** `x declared but not used` or `imported and not used`
**Fix:**
```go
// Remove unused variable
x := getValue() // Remove if x not used
// Use blank identifier if intentionally ignoring
_ = getValue()
// Remove unused import or use blank import for side effects
import _ "package/for/init/only"
```
### 8. Multiple-Value in Single-Value Context
**Error:** `multiple-value X() in single-value context`
**Fix:**
```go
// Wrong
result := funcReturningTwo()
// Correct
result, err := funcReturningTwo()
if err != nil {
return err
}
// Or ignore second value
result, _ := funcReturningTwo()
```
### 9. Cannot Assign to Field
**Error:** `cannot assign to struct field x.y in map`
**Fix:**
```go
// Cannot modify struct in map directly
m := map[string]MyStruct{}
m["key"].Field = "value" // Error!
// Fix: Use pointer map or copy-modify-reassign
m := map[string]*MyStruct{}
m["key"] = &MyStruct{}
m["key"].Field = "value" // Works
// Or
m := map[string]MyStruct{}
tmp := m["key"]
tmp.Field = "value"
m["key"] = tmp
```
### 10. Invalid Operation (Type Assertion)
**Error:** `invalid type assertion: x.(T) (non-interface type)`
**Fix:**
```go
// Can only assert from interface
var i interface{} = "hello"
s := i.(string) // Valid
var s string = "hello"
// s.(int) // Invalid - s is not interface
```
## Module Issues
### Replace Directive Problems
```bash
# Check for local replaces that might be invalid
grep "replace" go.mod
# Remove stale replaces
go mod edit -dropreplace=package/path
```
### Version Conflicts
```bash
# See why a version is selected
go mod why -m package
# Get specific version
go get package@v1.2.3
# Update all dependencies
go get -u ./...
```
### Checksum Mismatch
```bash
# Clear module cache
go clean -modcache
# Re-download
go mod download
```
## Go Vet Issues
### Suspicious Constructs
```go
// Vet: unreachable code
func example() int {
return 1
fmt.Println("never runs") // Remove this
}
// Vet: printf format mismatch
fmt.Printf("%d", "string") // Fix: %s
// Vet: copying lock value
var mu sync.Mutex
mu2 := mu // Fix: use pointer *sync.Mutex
// Vet: self-assignment
x = x // Remove pointless assignment
```
## Fix Strategy
1. **Read the full error message** - Go errors are descriptive
2. **Identify the file and line number** - Go directly to the source
3. **Understand the context** - Read surrounding code
4. **Make minimal fix** - Don't refactor, just fix the error
5. **Verify fix** - Run `go build ./...` again
6. **Check for cascading errors** - One fix might reveal others
## Resolution Workflow
```text
1. go build ./...
↓ Error?
2. Parse error message
3. Read affected file
4. Apply minimal fix
5. go build ./...
↓ Still errors?
→ Back to step 2
↓ Success?
6. go vet ./...
↓ Warnings?
→ Fix and repeat
7. go test ./...
8. Done!
```
## Stop Conditions
Stop and report if:
- Same error persists after 3 fix attempts
- Fix introduces more errors than it resolves
- Error requires architectural changes beyond scope
- Circular dependency that needs package restructuring
- Missing external dependency that needs manual installation
## Output Format
After each fix attempt:
```text
[FIXED] internal/handler/user.go:42
Error: undefined: UserService
Fix: Added import "project/internal/service"
Remaining errors: 3
```
Final summary:
```text
Build Status: SUCCESS/FAILED
Errors Fixed: N
Vet Warnings Fixed: N
Files Modified: list
Remaining Issues: list (if any)
```
## Important Notes
- **Never** add `//nolint` comments without explicit approval
- **Never** change function signatures unless necessary for the fix
- **Always** run `go mod tidy` after adding/removing imports
- **Prefer** fixing root cause over suppressing symptoms
- **Document** any non-obvious fixes with inline comments
Build errors should be fixed surgically. The goal is a working build, not a refactored codebase.

267
agents/go-reviewer.md Normal file
View File

@@ -0,0 +1,267 @@
---
name: go-reviewer
description: Expert Go code reviewer specializing in idiomatic Go, concurrency patterns, error handling, and performance. Use for all Go code changes. MUST BE USED for Go projects.
tools: ["Read", "Grep", "Glob", "Bash"]
model: opus
---
You are a senior Go code reviewer ensuring high standards of idiomatic Go and best practices.
When invoked:
1. Run `git diff -- '*.go'` to see recent Go file changes
2. Run `go vet ./...` and `staticcheck ./...` if available
3. Focus on modified `.go` files
4. Begin review immediately
## Security Checks (CRITICAL)
- **SQL Injection**: String concatenation in `database/sql` queries
```go
// Bad
db.Query("SELECT * FROM users WHERE id = " + userID)
// Good
db.Query("SELECT * FROM users WHERE id = $1", userID)
```
- **Command Injection**: Unvalidated input in `os/exec`
```go
// Bad
exec.Command("sh", "-c", "echo " + userInput)
// Good
exec.Command("echo", userInput)
```
- **Path Traversal**: User-controlled file paths
```go
// Bad
os.ReadFile(filepath.Join(baseDir, userPath))
// Good
cleanPath := filepath.Clean(userPath)
if strings.HasPrefix(cleanPath, "..") {
return ErrInvalidPath
}
```
- **Race Conditions**: Shared state without synchronization
- **Unsafe Package**: Use of `unsafe` without justification
- **Hardcoded Secrets**: API keys, passwords in source
- **Insecure TLS**: `InsecureSkipVerify: true`
- **Weak Crypto**: Use of MD5/SHA1 for security purposes
## Error Handling (CRITICAL)
- **Ignored Errors**: Using `_` to ignore errors
```go
// Bad
result, _ := doSomething()
// Good
result, err := doSomething()
if err != nil {
return fmt.Errorf("do something: %w", err)
}
```
- **Missing Error Wrapping**: Errors without context
```go
// Bad
return err
// Good
return fmt.Errorf("load config %s: %w", path, err)
```
- **Panic Instead of Error**: Using panic for recoverable errors
- **errors.Is/As**: Not using for error checking
```go
// Bad
if err == sql.ErrNoRows
// Good
if errors.Is(err, sql.ErrNoRows)
```
## Concurrency (HIGH)
- **Goroutine Leaks**: Goroutines that never terminate
```go
// Bad: No way to stop goroutine
go func() {
for { doWork() }
}()
// Good: Context for cancellation
go func() {
for {
select {
case <-ctx.Done():
return
default:
doWork()
}
}
}()
```
- **Race Conditions**: Run `go build -race ./...`
- **Unbuffered Channel Deadlock**: Sending without receiver
- **Missing sync.WaitGroup**: Goroutines without coordination
- **Context Not Propagated**: Ignoring context in nested calls
- **Mutex Misuse**: Not using `defer mu.Unlock()`
```go
// Bad: Unlock might not be called on panic
mu.Lock()
doSomething()
mu.Unlock()
// Good
mu.Lock()
defer mu.Unlock()
doSomething()
```
## Code Quality (HIGH)
- **Large Functions**: Functions over 50 lines
- **Deep Nesting**: More than 4 levels of indentation
- **Interface Pollution**: Defining interfaces not used for abstraction
- **Package-Level Variables**: Mutable global state
- **Naked Returns**: In functions longer than a few lines
```go
// Bad in long functions
func process() (result int, err error) {
// ... 30 lines ...
return // What's being returned?
}
```
- **Non-Idiomatic Code**:
```go
// Bad
if err != nil {
return err
} else {
doSomething()
}
// Good: Early return
if err != nil {
return err
}
doSomething()
```
## Performance (MEDIUM)
- **Inefficient String Building**:
```go
// Bad
for _, s := range parts { result += s }
// Good
var sb strings.Builder
for _, s := range parts { sb.WriteString(s) }
```
- **Slice Pre-allocation**: Not using `make([]T, 0, cap)`
- **Pointer vs Value Receivers**: Inconsistent usage
- **Unnecessary Allocations**: Creating objects in hot paths
- **N+1 Queries**: Database queries in loops
- **Missing Connection Pooling**: Creating new DB connections per request
## Best Practices (MEDIUM)
- **Accept Interfaces, Return Structs**: Functions should accept interface parameters
- **Context First**: Context should be first parameter
```go
// Bad
func Process(id string, ctx context.Context)
// Good
func Process(ctx context.Context, id string)
```
- **Table-Driven Tests**: Tests should use table-driven pattern
- **Godoc Comments**: Exported functions need documentation
```go
// ProcessData transforms raw input into structured output.
// It returns an error if the input is malformed.
func ProcessData(input []byte) (*Data, error)
```
- **Error Messages**: Should be lowercase, no punctuation
```go
// Bad
return errors.New("Failed to process data.")
// Good
return errors.New("failed to process data")
```
- **Package Naming**: Short, lowercase, no underscores
## Go-Specific Anti-Patterns
- **init() Abuse**: Complex logic in init functions
- **Empty Interface Overuse**: Using `interface{}` instead of generics
- **Type Assertions Without ok**: Can panic
```go
// Bad
v := x.(string)
// Good
v, ok := x.(string)
if !ok { return ErrInvalidType }
```
- **Deferred Call in Loop**: Resource accumulation
```go
// Bad: Files opened until function returns
for _, path := range paths {
f, _ := os.Open(path)
defer f.Close()
}
// Good: Close in loop iteration
for _, path := range paths {
func() {
f, _ := os.Open(path)
defer f.Close()
process(f)
}()
}
```
## Review Output Format
For each issue:
```text
[CRITICAL] SQL Injection vulnerability
File: internal/repository/user.go:42
Issue: User input directly concatenated into SQL query
Fix: Use parameterized query
query := "SELECT * FROM users WHERE id = " + userID // Bad
query := "SELECT * FROM users WHERE id = $1" // Good
db.Query(query, userID)
```
## Diagnostic Commands
Run these checks:
```bash
# Static analysis
go vet ./...
staticcheck ./...
golangci-lint run
# Race detection
go build -race ./...
go test -race ./...
# Security scanning
govulncheck ./...
```
## Approval Criteria
- **Approve**: No CRITICAL or HIGH issues
- **Warning**: MEDIUM issues only (can merge with caution)
- **Block**: CRITICAL or HIGH issues found
## Go Version Considerations
- Check `go.mod` for minimum Go version
- Note if code uses features from newer Go versions (generics 1.18+, fuzzing 1.18+)
- Flag deprecated functions from standard library
Review with the mindset: "Would this code pass review at Google or a top Go shop?"

View File

@@ -1,7 +1,7 @@
---
name: planner
description: Expert planning specialist for complex features and refactoring. Use PROACTIVELY when users request feature implementation, architectural changes, or complex refactoring. Automatically activated for planning tasks.
tools: Read, Grep, Glob
tools: ["Read", "Grep", "Glob"]
model: opus
---

View File

@@ -1,7 +1,7 @@
---
name: refactor-cleaner
description: Dead code cleanup and consolidation specialist. Use PROACTIVELY for removing unused code, duplicates, and refactoring. Runs analysis tools (knip, depcheck, ts-prune) to identify dead code and safely removes it.
tools: Read, Write, Edit, Bash, Grep, Glob
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
---

View File

@@ -1,7 +1,7 @@
---
name: security-reviewer
description: Security vulnerability detection and remediation specialist. Use PROACTIVELY after writing code that handles user input, authentication, API endpoints, or sensitive data. Flags secrets, SSRF, injection, unsafe crypto, and OWASP Top 10 vulnerabilities.
tools: Read, Write, Edit, Bash, Grep, Glob
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: opus
---

View File

@@ -1,7 +1,7 @@
---
name: tdd-guide
description: Test-Driven Development specialist enforcing write-tests-first methodology. Use PROACTIVELY when writing new features, fixing bugs, or refactoring code. Ensures 80%+ test coverage.
tools: Read, Write, Edit, Bash, Grep
tools: ["Read", "Write", "Edit", "Bash", "Grep"]
model: opus
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

183
commands/go-build.md Normal file
View File

@@ -0,0 +1,183 @@
---
description: Fix Go build errors, go vet warnings, and linter issues incrementally. Invokes the go-build-resolver agent for minimal, surgical fixes.
---
# Go Build and Fix
This command invokes the **go-build-resolver** agent to incrementally fix Go build errors with minimal changes.
## What This Command Does
1. **Run Diagnostics**: Execute `go build`, `go vet`, `staticcheck`
2. **Parse Errors**: Group by file and sort by severity
3. **Fix Incrementally**: One error at a time
4. **Verify Each Fix**: Re-run build after each change
5. **Report Summary**: Show what was fixed and what remains
## When to Use
Use `/go-build` when:
- `go build ./...` fails with errors
- `go vet ./...` reports issues
- `golangci-lint run` shows warnings
- Module dependencies are broken
- After pulling changes that break the build
## Diagnostic Commands Run
```bash
# Primary build check
go build ./...
# Static analysis
go vet ./...
# Extended linting (if available)
staticcheck ./...
golangci-lint run
# Module issues
go mod verify
go mod tidy -v
```
## Example Session
```text
User: /go-build
Agent:
# Go Build Resolution
## Initial Diagnostics
```bash
$ go build ./...
internal/service/user.go:25:15: undefined: UserRepository
internal/handler/api.go:42:9: cannot use x (type string) as type int
internal/handler/api.go:58:2: missing return at end of function
```
Errors found: 3
## Fix 1: Undefined Identifier
File: internal/service/user.go:25
Error: undefined: UserRepository
Cause: Missing import
```go
// Added import
import "project/internal/repository"
// Changed
var repo UserRepository
// To
var repo repository.UserRepository
```
```bash
$ go build ./...
# 2 errors remaining
```
## Fix 2: Type Mismatch
File: internal/handler/api.go:42
Error: cannot use x (type string) as type int
```go
// Changed
count := params.Get("count")
// To
countStr := params.Get("count")
count, _ := strconv.Atoi(countStr)
```
```bash
$ go build ./...
# 1 error remaining
```
## Fix 3: Missing Return
File: internal/handler/api.go:58
Error: missing return at end of function
```go
func GetUser(id string) (*User, error) {
if id == "" {
return nil, ErrInvalidID
}
user := findUser(id)
// Added missing return
return user, nil
}
```
```bash
$ go build ./...
# Build successful!
```
## Final Verification
```bash
$ go vet ./...
# No issues
$ go test ./...
ok project/internal/service 0.015s
ok project/internal/handler 0.023s
```
## Summary
| Metric | Count |
|--------|-------|
| Build errors fixed | 3 |
| Vet warnings fixed | 0 |
| Files modified | 2 |
| Remaining issues | 0 |
Build Status: ✅ SUCCESS
```
## Common Errors Fixed
| Error | Typical Fix |
|-------|-------------|
| `undefined: X` | Add import or fix typo |
| `cannot use X as Y` | Type conversion or fix assignment |
| `missing return` | Add return statement |
| `X does not implement Y` | Add missing method |
| `import cycle` | Restructure packages |
| `declared but not used` | Remove or use variable |
| `cannot find package` | `go get` or `go mod tidy` |
## Fix Strategy
1. **Build errors first** - Code must compile
2. **Vet warnings second** - Fix suspicious constructs
3. **Lint warnings third** - Style and best practices
4. **One fix at a time** - Verify each change
5. **Minimal changes** - Don't refactor, just fix
## Stop Conditions
The agent will stop and report if:
- Same error persists after 3 attempts
- Fix introduces more errors
- Requires architectural changes
- Missing external dependencies
## Related Commands
- `/go-test` - Run tests after build succeeds
- `/go-review` - Review code quality
- `/verify` - Full verification loop
## Related
- Agent: `agents/go-build-resolver.md`
- Skill: `skills/golang-patterns/`

148
commands/go-review.md Normal file
View File

@@ -0,0 +1,148 @@
---
description: Comprehensive Go code review for idiomatic patterns, concurrency safety, error handling, and security. Invokes the go-reviewer agent.
---
# Go Code Review
This command invokes the **go-reviewer** agent for comprehensive Go-specific code review.
## What This Command Does
1. **Identify Go Changes**: Find modified `.go` files via `git diff`
2. **Run Static Analysis**: Execute `go vet`, `staticcheck`, and `golangci-lint`
3. **Security Scan**: Check for SQL injection, command injection, race conditions
4. **Concurrency Review**: Analyze goroutine safety, channel usage, mutex patterns
5. **Idiomatic Go Check**: Verify code follows Go conventions and best practices
6. **Generate Report**: Categorize issues by severity
## When to Use
Use `/go-review` when:
- After writing or modifying Go code
- Before committing Go changes
- Reviewing pull requests with Go code
- Onboarding to a new Go codebase
- Learning idiomatic Go patterns
## Review Categories
### CRITICAL (Must Fix)
- SQL/Command injection vulnerabilities
- Race conditions without synchronization
- Goroutine leaks
- Hardcoded credentials
- Unsafe pointer usage
- Ignored errors in critical paths
### HIGH (Should Fix)
- Missing error wrapping with context
- Panic instead of error returns
- Context not propagated
- Unbuffered channels causing deadlocks
- Interface not satisfied errors
- Missing mutex protection
### MEDIUM (Consider)
- Non-idiomatic code patterns
- Missing godoc comments on exports
- Inefficient string concatenation
- Slice not preallocated
- Table-driven tests not used
## Automated Checks Run
```bash
# Static analysis
go vet ./...
# Advanced checks (if installed)
staticcheck ./...
golangci-lint run
# Race detection
go build -race ./...
# Security vulnerabilities
govulncheck ./...
```
## Example Usage
```text
User: /go-review
Agent:
# Go Code Review Report
## Files Reviewed
- internal/handler/user.go (modified)
- internal/service/auth.go (modified)
## Static Analysis Results
✓ go vet: No issues
✓ staticcheck: No issues
## Issues Found
[CRITICAL] Race Condition
File: internal/service/auth.go:45
Issue: Shared map accessed without synchronization
```go
var cache = map[string]*Session{} // Concurrent access!
func GetSession(id string) *Session {
return cache[id] // Race condition
}
```
Fix: Use sync.RWMutex or sync.Map
```go
var (
cache = map[string]*Session{}
cacheMu sync.RWMutex
)
func GetSession(id string) *Session {
cacheMu.RLock()
defer cacheMu.RUnlock()
return cache[id]
}
```
[HIGH] Missing Error Context
File: internal/handler/user.go:28
Issue: Error returned without context
```go
return err // No context
```
Fix: Wrap with context
```go
return fmt.Errorf("get user %s: %w", userID, err)
```
## Summary
- CRITICAL: 1
- HIGH: 1
- MEDIUM: 0
Recommendation: ❌ Block merge until CRITICAL issue is fixed
```
## Approval Criteria
| Status | Condition |
|--------|-----------|
| ✅ Approve | No CRITICAL or HIGH issues |
| ⚠️ Warning | Only MEDIUM issues (merge with caution) |
| ❌ Block | CRITICAL or HIGH issues found |
## Integration with Other Commands
- Use `/go-test` first to ensure tests pass
- Use `/go-build` if build errors occur
- Use `/go-review` before committing
- Use `/code-review` for non-Go specific concerns
## Related
- Agent: `agents/go-reviewer.md`
- Skills: `skills/golang-patterns/`, `skills/golang-testing/`

268
commands/go-test.md Normal file
View File

@@ -0,0 +1,268 @@
---
description: Enforce TDD workflow for Go. Write table-driven tests first, then implement. Verify 80%+ coverage with go test -cover.
---
# Go TDD Command
This command enforces test-driven development methodology for Go code using idiomatic Go testing patterns.
## What This Command Does
1. **Define Types/Interfaces**: Scaffold function signatures first
2. **Write Table-Driven Tests**: Create comprehensive test cases (RED)
3. **Run Tests**: Verify tests fail for the right reason
4. **Implement Code**: Write minimal code to pass (GREEN)
5. **Refactor**: Improve while keeping tests green
6. **Check Coverage**: Ensure 80%+ coverage
## When to Use
Use `/go-test` when:
- Implementing new Go functions
- Adding test coverage to existing code
- Fixing bugs (write failing test first)
- Building critical business logic
- Learning TDD workflow in Go
## TDD Cycle
```
RED → Write failing table-driven test
GREEN → Implement minimal code to pass
REFACTOR → Improve code, tests stay green
REPEAT → Next test case
```
## Example Session
```text
User: /go-test I need a function to validate email addresses
Agent:
# TDD Session: Email Validator
## Step 1: Define Interface
```go
// validator/email.go
package validator
// ValidateEmail checks if the given string is a valid email address.
// Returns nil if valid, error describing the issue otherwise.
func ValidateEmail(email string) error {
panic("not implemented")
}
```
## Step 2: Write Table-Driven Tests (RED)
```go
// validator/email_test.go
package validator
import (
"testing"
)
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
// Valid emails
{"simple email", "user@example.com", false},
{"with subdomain", "user@mail.example.com", false},
{"with plus", "user+tag@example.com", false},
{"with dots", "first.last@example.com", false},
// Invalid emails
{"empty string", "", true},
{"no at sign", "userexample.com", true},
{"no domain", "user@", true},
{"no local part", "@example.com", true},
{"double at", "user@@example.com", true},
{"spaces", "user @example.com", true},
{"no tld", "user@example", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.email)
if tt.wantErr && err == nil {
t.Errorf("ValidateEmail(%q) = nil; want error", tt.email)
}
if !tt.wantErr && err != nil {
t.Errorf("ValidateEmail(%q) = %v; want nil", tt.email, err)
}
})
}
}
```
## Step 3: Run Tests - Verify FAIL
```bash
$ go test ./validator/...
--- FAIL: TestValidateEmail (0.00s)
--- FAIL: TestValidateEmail/simple_email (0.00s)
panic: not implemented
FAIL
```
✓ Tests fail as expected (panic).
## Step 4: Implement Minimal Code (GREEN)
```go
// validator/email.go
package validator
import (
"errors"
"regexp"
)
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
var (
ErrEmailEmpty = errors.New("email cannot be empty")
ErrEmailInvalid = errors.New("email format is invalid")
)
func ValidateEmail(email string) error {
if email == "" {
return ErrEmailEmpty
}
if !emailRegex.MatchString(email) {
return ErrEmailInvalid
}
return nil
}
```
## Step 5: Run Tests - Verify PASS
```bash
$ go test ./validator/...
PASS
ok project/validator 0.003s
```
✓ All tests passing!
## Step 6: Check Coverage
```bash
$ go test -cover ./validator/...
PASS
coverage: 100.0% of statements
ok project/validator 0.003s
```
✓ Coverage: 100%
## TDD Complete!
```
## Test Patterns
### Table-Driven Tests
```go
tests := []struct {
name string
input InputType
want OutputType
wantErr bool
}{
{"case 1", input1, want1, false},
{"case 2", input2, want2, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Function(tt.input)
// assertions
})
}
```
### Parallel Tests
```go
for _, tt := range tests {
tt := tt // Capture
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// test body
})
}
```
### Test Helpers
```go
func setupTestDB(t *testing.T) *sql.DB {
t.Helper()
db := createDB()
t.Cleanup(func() { db.Close() })
return db
}
```
## Coverage Commands
```bash
# Basic coverage
go test -cover ./...
# Coverage profile
go test -coverprofile=coverage.out ./...
# View in browser
go tool cover -html=coverage.out
# Coverage by function
go tool cover -func=coverage.out
# With race detection
go test -race -cover ./...
```
## Coverage Targets
| Code Type | Target |
|-----------|--------|
| Critical business logic | 100% |
| Public APIs | 90%+ |
| General code | 80%+ |
| Generated code | Exclude |
## TDD Best Practices
**DO:**
- Write test FIRST, before any implementation
- Run tests after each change
- Use table-driven tests for comprehensive coverage
- Test behavior, not implementation details
- Include edge cases (empty, nil, max values)
**DON'T:**
- Write implementation before tests
- Skip the RED phase
- Test private functions directly
- Use `time.Sleep` in tests
- Ignore flaky tests
## Related Commands
- `/go-build` - Fix build errors
- `/go-review` - Review code after implementation
- `/verify` - Run full verification loop
## Related
- Skill: `skills/golang-testing/`
- Skill: `skills/tdd-workflow/`

View File

@@ -35,6 +35,7 @@ Detailed guidelines are in `~/.claude/rules/`:
| agents.md | Agent orchestration, when to use which agent |
| patterns.md | API response, repository patterns |
| performance.md | Model selection, context management |
| hooks.md | Hooks System |
---
@@ -58,6 +59,10 @@ Located in `~/.claude/agents/`:
## Personal Preferences
### Privacy
- Always redact logs; never paste secrets (API keys/tokens/passwords/JWTs)
- Review output before sharing - remove any sensitive data
### Code Style
- No emojis in code, comments, or documentation
- Prefer immutability - never mutate objects or arrays

View File

@@ -88,6 +88,18 @@
],
"description": "Log PR URL and provide review command after PR creation"
},
{
"matcher": "tool == \"Bash\" && tool_input.command matches \"(npm run build|pnpm build|yarn build)\"",
"hooks": [
{
"type": "command",
"command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{console.error('[Hook] Build completed - async analysis running in background');console.log(d)})\"",
"async": true,
"timeout": 30
}
],
"description": "Example: async hook for build analysis (runs in background without blocking)"
},
{
"matcher": "tool == \"Edit\" && tool_input.file_path matches \"\\\\.(ts|tsx|js|jsx)$\"",
"hooks": [
@@ -125,7 +137,7 @@
"hooks": [
{
"type": "command",
"command": "node -e \"const{execSync}=require('child_process');const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{execSync('git rev-parse --git-dir',{stdio:'pipe'})}catch{console.log(d);process.exit(0)}try{const files=execSync('git diff --name-only HEAD',{encoding:'utf8',stdio:['pipe','pipe','pipe']}).split('\\n').filter(f=>/\\.(ts|tsx|js|jsx)$/.test(f)&&fs.existsSync(f));let hasConsole=false;for(const f of files){if(fs.readFileSync(f,'utf8').includes('console.log')){console.error('[Hook] WARNING: console.log found in '+f);hasConsole=true}}if(hasConsole)console.error('[Hook] Remove console.log statements before committing')}catch(e){}console.log(d)})\""
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/check-console-log.js\""
}
],
"description": "Check for console.log in modified files after each response"

View File

@@ -1,36 +0,0 @@
#!/bin/bash
# PreCompact Hook - Save state before context compaction
#
# Runs before Claude compacts context, giving you a chance to
# preserve important state that might get lost in summarization.
#
# Hook config (in ~/.claude/settings.json):
# {
# "hooks": {
# "PreCompact": [{
# "matcher": "*",
# "hooks": [{
# "type": "command",
# "command": "~/.claude/hooks/memory-persistence/pre-compact.sh"
# }]
# }]
# }
# }
SESSIONS_DIR="${HOME}/.claude/sessions"
COMPACTION_LOG="${SESSIONS_DIR}/compaction-log.txt"
mkdir -p "$SESSIONS_DIR"
# Log compaction event with timestamp
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Context compaction triggered" >> "$COMPACTION_LOG"
# If there's an active session file, note the compaction
ACTIVE_SESSION=$(ls -t "$SESSIONS_DIR"/*.tmp 2>/dev/null | head -1)
if [ -n "$ACTIVE_SESSION" ]; then
echo "" >> "$ACTIVE_SESSION"
echo "---" >> "$ACTIVE_SESSION"
echo "**[Compaction occurred at $(date '+%H:%M')]** - Context was summarized" >> "$ACTIVE_SESSION"
fi
echo "[PreCompact] State saved before compaction" >&2

View File

@@ -1,61 +0,0 @@
#!/bin/bash
# Stop Hook (Session End) - Persist learnings when session ends
#
# Runs when Claude session ends. Creates/updates session log file
# with timestamp for continuity tracking.
#
# Hook config (in ~/.claude/settings.json):
# {
# "hooks": {
# "Stop": [{
# "matcher": "*",
# "hooks": [{
# "type": "command",
# "command": "~/.claude/hooks/memory-persistence/session-end.sh"
# }]
# }]
# }
# }
SESSIONS_DIR="${HOME}/.claude/sessions"
TODAY=$(date '+%Y-%m-%d')
SESSION_FILE="${SESSIONS_DIR}/${TODAY}-session.tmp"
mkdir -p "$SESSIONS_DIR"
# If session file exists for today, update the end time
if [ -f "$SESSION_FILE" ]; then
# Update Last Updated timestamp
sed -i '' "s/\*\*Last Updated:\*\*.*/\*\*Last Updated:\*\* $(date '+%H:%M')/" "$SESSION_FILE" 2>/dev/null || \
sed -i "s/\*\*Last Updated:\*\*.*/\*\*Last Updated:\*\* $(date '+%H:%M')/" "$SESSION_FILE" 2>/dev/null
echo "[SessionEnd] Updated session file: $SESSION_FILE" >&2
else
# Create new session file with template
cat > "$SESSION_FILE" << EOF
# Session: $(date '+%Y-%m-%d')
**Date:** $TODAY
**Started:** $(date '+%H:%M')
**Last Updated:** $(date '+%H:%M')
---
## Current State
[Session context goes here]
### Completed
- [ ]
### In Progress
- [ ]
### Notes for Next Session
-
### Context to Load
\`\`\`
[relevant files]
\`\`\`
EOF
echo "[SessionEnd] Created session file: $SESSION_FILE" >&2
fi

View File

@@ -1,37 +0,0 @@
#!/bin/bash
# SessionStart Hook - Load previous context on new session
#
# Runs when a new Claude session starts. Checks for recent session
# files and notifies Claude of available context to load.
#
# Hook config (in ~/.claude/settings.json):
# {
# "hooks": {
# "SessionStart": [{
# "matcher": "*",
# "hooks": [{
# "type": "command",
# "command": "~/.claude/hooks/memory-persistence/session-start.sh"
# }]
# }]
# }
# }
SESSIONS_DIR="${HOME}/.claude/sessions"
LEARNED_DIR="${HOME}/.claude/skills/learned"
# Check for recent session files (last 7 days)
recent_sessions=$(find "$SESSIONS_DIR" -name "*.tmp" -mtime -7 2>/dev/null | wc -l | tr -d ' ')
if [ "$recent_sessions" -gt 0 ]; then
latest=$(ls -t "$SESSIONS_DIR"/*.tmp 2>/dev/null | head -1)
echo "[SessionStart] Found $recent_sessions recent session(s)" >&2
echo "[SessionStart] Latest: $latest" >&2
fi
# Check for learned skills
learned_count=$(find "$LEARNED_DIR" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
if [ "$learned_count" -gt 0 ]; then
echo "[SessionStart] $learned_count learned skill(s) available in $LEARNED_DIR" >&2
fi

View File

@@ -1,52 +0,0 @@
#!/bin/bash
# Strategic Compact Suggester
# Runs on PreToolUse or periodically to suggest manual compaction at logical intervals
#
# Why manual over auto-compact:
# - Auto-compact happens at arbitrary points, often mid-task
# - Strategic compacting preserves context through logical phases
# - Compact after exploration, before execution
# - Compact after completing a milestone, before starting next
#
# Hook config (in ~/.claude/settings.json):
# {
# "hooks": {
# "PreToolUse": [{
# "matcher": "Edit|Write",
# "hooks": [{
# "type": "command",
# "command": "~/.claude/skills/strategic-compact/suggest-compact.sh"
# }]
# }]
# }
# }
#
# Criteria for suggesting compact:
# - Session has been running for extended period
# - Large number of tool calls made
# - Transitioning from research/exploration to implementation
# - Plan has been finalized
# Track tool call count (increment in a temp file)
COUNTER_FILE="/tmp/claude-tool-count-$$"
THRESHOLD=${COMPACT_THRESHOLD:-50}
# Initialize or increment counter
if [ -f "$COUNTER_FILE" ]; then
count=$(cat "$COUNTER_FILE")
count=$((count + 1))
echo "$count" > "$COUNTER_FILE"
else
echo "1" > "$COUNTER_FILE"
count=1
fi
# Suggest compact after threshold tool calls
if [ "$count" -eq "$THRESHOLD" ]; then
echo "[StrategicCompact] $THRESHOLD tool calls reached - consider /compact if transitioning phases" >&2
fi
# Suggest at regular intervals after threshold
if [ "$count" -gt "$THRESHOLD" ] && [ $((count % 25)) -eq 0 ]; then
echo "[StrategicCompact] $count tool calls - good checkpoint for /compact if context is stale" >&2
fi

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env node
/**
* Stop Hook: Check for console.log statements in modified files
*
* This hook runs after each response and checks if any modified
* JavaScript/TypeScript files contain console.log statements.
* It provides warnings to help developers remember to remove
* debug statements before committing.
*/
const { execSync } = require('child_process');
const fs = require('fs');
let data = '';
// Read stdin
process.stdin.on('data', chunk => {
data += chunk;
});
process.stdin.on('end', () => {
try {
// Check if we're in a git repository
try {
execSync('git rev-parse --git-dir', { stdio: 'pipe' });
} catch {
// Not in a git repo, just pass through the data
console.log(data);
process.exit(0);
}
// Get list of modified files
const files = execSync('git diff --name-only HEAD', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe']
})
.split('\n')
.filter(f => /\.(ts|tsx|js|jsx)$/.test(f) && fs.existsSync(f));
let hasConsole = false;
// Check each file for console.log
for (const file of files) {
const content = fs.readFileSync(file, 'utf8');
if (content.includes('console.log')) {
console.error(`[Hook] WARNING: console.log found in ${file}`);
hasConsole = true;
}
}
if (hasConsole) {
console.error('[Hook] Remove console.log statements before committing');
}
} catch (error) {
// Silently ignore errors (git might not be available, etc.)
}
// Always output the original data
console.log(data);
});

View File

@@ -14,6 +14,7 @@ const {
getSessionsDir,
getDateString,
getTimeString,
getSessionIdShort,
ensureDir,
readFile,
writeFile,
@@ -24,7 +25,9 @@ const {
async function main() {
const sessionsDir = getSessionsDir();
const today = getDateString();
const sessionFile = path.join(sessionsDir, `${today}-session.tmp`);
const shortId = getSessionIdShort();
// Include session ID in filename for unique per-session tracking
const sessionFile = path.join(sessionsDir, `${today}-${shortId}-session.tmp`);
ensureDir(sessionsDir);

View File

@@ -27,7 +27,8 @@ async function main() {
ensureDir(learnedDir);
// Check for recent session files (last 7 days)
const recentSessions = findFiles(sessionsDir, '*.tmp', { maxAge: 7 });
// Match both old format (YYYY-MM-DD-session.tmp) and new format (YYYY-MM-DD-shortid-session.tmp)
const recentSessions = findFiles(sessionsDir, '*-session.tmp', { maxAge: 7 });
if (recentSessions.length > 0) {
const latest = recentSessions[0];

View File

@@ -79,6 +79,19 @@ function getTimeString() {
return `${hours}:${minutes}`;
}
/**
* Get short session ID from CLAUDE_SESSION_ID environment variable
* Returns the last 8 characters for uniqueness with brevity
* @param {string} fallback - Fallback value if no session ID (default: 'default')
*/
function getSessionIdShort(fallback = 'default') {
const sessionId = process.env.CLAUDE_SESSION_ID;
if (!sessionId || sessionId.length === 0) {
return fallback;
}
return sessionId.slice(-8);
}
/**
* Get current datetime in YYYY-MM-DD HH:MM:SS format
*/
@@ -223,15 +236,23 @@ function appendFile(filePath, content) {
/**
* Check if a command exists in PATH
* Uses execFileSync to prevent command injection
*/
function commandExists(cmd) {
// Validate command name - only allow alphanumeric, dash, underscore, dot
if (!/^[a-zA-Z0-9_.-]+$/.test(cmd)) {
return false;
}
try {
if (isWindows) {
execSync(`where ${cmd}`, { stdio: 'pipe' });
// Use spawnSync to avoid shell interpolation
const result = spawnSync('where', [cmd], { stdio: 'pipe' });
return result.status === 0;
} else {
execSync(`which ${cmd}`, { stdio: 'pipe' });
const result = spawnSync('which', [cmd], { stdio: 'pipe' });
return result.status === 0;
}
return true;
} catch {
return false;
}
@@ -239,6 +260,13 @@ function commandExists(cmd) {
/**
* Run a command and return output
*
* SECURITY NOTE: This function executes shell commands. Only use with
* trusted, hardcoded commands. Never pass user-controlled input directly.
* For user input, use spawnSync with argument arrays instead.
*
* @param {string} cmd - Command to execute (should be trusted/hardcoded)
* @param {object} options - execSync options
*/
function runCommand(cmd, options = {}) {
try {
@@ -345,6 +373,7 @@ module.exports = {
getDateString,
getTimeString,
getDateTimeString,
getSessionIdShort,
// File operations
findFiles,

View File

@@ -89,8 +89,8 @@ function detectAndShow() {
console.log('');
console.log('Commands:');
console.log(` Install: ${pm.config.installCmd}`);
console.log(` Run script: ${pm.config.runCmd} <script>`);
console.log(` Execute binary: ${pm.config.execCmd} <binary>`);
console.log(` Run script: ${pm.config.runCmd} [script-name]`);
console.log(` Execute binary: ${pm.config.execCmd} [binary-name]`);
console.log('');
}

View File

@@ -395,21 +395,26 @@ export function hasPermission(user: User, permission: Permission): boolean {
}
export function requirePermission(permission: Permission) {
return async (request: Request) => {
const user = await requireAuth(request)
return (handler: (request: Request, user: User) => Promise<Response>) => {
return async (request: Request) => {
const user = await requireAuth(request)
if (!hasPermission(user, permission)) {
throw new ApiError(403, 'Insufficient permissions')
if (!hasPermission(user, permission)) {
throw new ApiError(403, 'Insufficient permissions')
}
return handler(request, user)
}
return user
}
}
// Usage
export const DELETE = requirePermission('delete')(async (request: Request) => {
// Handler with permission check
})
// Usage - HOF wraps the handler
export const DELETE = requirePermission('delete')(
async (request: Request, user: User) => {
// Handler receives authenticated user with verified permission
return new Response('Deleted', { status: 200 })
}
)
```
## Rate Limiting

View File

@@ -0,0 +1,257 @@
---
name: continuous-learning-v2
description: Instinct-based learning system that observes sessions via hooks, creates atomic instincts with confidence scoring, and evolves them into skills/commands/agents.
version: 2.0.0
---
# Continuous Learning v2 - Instinct-Based Architecture
An advanced learning system that turns your Claude Code sessions into reusable knowledge through atomic "instincts" - small learned behaviors with confidence scoring.
## What's New in v2
| Feature | v1 | v2 |
|---------|----|----|
| Observation | Stop hook (session end) | PreToolUse/PostToolUse (100% reliable) |
| Analysis | Main context | Background agent (Haiku) |
| Granularity | Full skills | Atomic "instincts" |
| Confidence | None | 0.3-0.9 weighted |
| Evolution | Direct to skill | Instincts → cluster → skill/command/agent |
| Sharing | None | Export/import instincts |
## The Instinct Model
An instinct is a small learned behavior:
```yaml
---
id: prefer-functional-style
trigger: "when writing new functions"
confidence: 0.7
domain: "code-style"
source: "session-observation"
---
# Prefer Functional Style
## Action
Use functional patterns over classes when appropriate.
## Evidence
- Observed 5 instances of functional pattern preference
- User corrected class-based approach to functional on 2025-01-15
```
**Properties:**
- **Atomic** — one trigger, one action
- **Confidence-weighted** — 0.3 = tentative, 0.9 = near certain
- **Domain-tagged** — code-style, testing, git, debugging, workflow, etc.
- **Evidence-backed** — tracks what observations created it
## How It Works
```
Session Activity
│ Hooks capture prompts + tool use (100% reliable)
┌─────────────────────────────────────────┐
│ observations.jsonl │
│ (prompts, tool calls, outcomes) │
└─────────────────────────────────────────┘
│ Observer agent reads (background, Haiku)
┌─────────────────────────────────────────┐
│ PATTERN DETECTION │
│ • User corrections → instinct │
│ • Error resolutions → instinct │
│ • Repeated workflows → instinct │
└─────────────────────────────────────────┘
│ Creates/updates
┌─────────────────────────────────────────┐
│ instincts/personal/ │
│ • prefer-functional.md (0.7) │
│ • always-test-first.md (0.9) │
│ • use-zod-validation.md (0.6) │
└─────────────────────────────────────────┘
│ /evolve clusters
┌─────────────────────────────────────────┐
│ evolved/ │
│ • commands/new-feature.md │
│ • skills/testing-workflow.md │
│ • agents/refactor-specialist.md │
└─────────────────────────────────────────┘
```
## Quick Start
### 1. Enable Observation Hooks
Add to your `~/.claude/settings.json`:
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh pre"
}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh post"
}]
}]
}
}
```
### 2. Initialize Directory Structure
```bash
mkdir -p ~/.claude/homunculus/{instincts/{personal,inherited},evolved/{agents,skills,commands}}
touch ~/.claude/homunculus/observations.jsonl
```
### 3. Run the Observer Agent (Optional)
The observer can run in the background analyzing observations:
```bash
# Start background observer
~/.claude/skills/continuous-learning-v2/agents/start-observer.sh
```
## Commands
| Command | Description |
|---------|-------------|
| `/instinct-status` | Show all learned instincts with confidence |
| `/evolve` | Cluster related instincts into skills/commands |
| `/instinct-export` | Export instincts for sharing |
| `/instinct-import <file>` | Import instincts from others |
## Configuration
Edit `config.json`:
```json
{
"version": "2.0",
"observation": {
"enabled": true,
"store_path": "~/.claude/homunculus/observations.jsonl",
"max_file_size_mb": 10,
"archive_after_days": 7
},
"instincts": {
"personal_path": "~/.claude/homunculus/instincts/personal/",
"inherited_path": "~/.claude/homunculus/instincts/inherited/",
"min_confidence": 0.3,
"auto_approve_threshold": 0.7,
"confidence_decay_rate": 0.05
},
"observer": {
"enabled": true,
"model": "haiku",
"run_interval_minutes": 5,
"patterns_to_detect": [
"user_corrections",
"error_resolutions",
"repeated_workflows",
"tool_preferences"
]
},
"evolution": {
"cluster_threshold": 3,
"evolved_path": "~/.claude/homunculus/evolved/"
}
}
```
## File Structure
```
~/.claude/homunculus/
├── identity.json # Your profile, technical level
├── observations.jsonl # Current session observations
├── observations.archive/ # Processed observations
├── instincts/
│ ├── personal/ # Auto-learned instincts
│ └── inherited/ # Imported from others
└── evolved/
├── agents/ # Generated specialist agents
├── skills/ # Generated skills
└── commands/ # Generated commands
```
## Integration with Skill Creator
When you use the [Skill Creator GitHub App](https://skill-creator.app), it now generates **both**:
- Traditional SKILL.md files (for backward compatibility)
- Instinct collections (for v2 learning system)
Instincts from repo analysis have `source: "repo-analysis"` and include the source repository URL.
## Confidence Scoring
Confidence evolves over time:
| Score | Meaning | Behavior |
|-------|---------|----------|
| 0.3 | Tentative | Suggested but not enforced |
| 0.5 | Moderate | Applied when relevant |
| 0.7 | Strong | Auto-approved for application |
| 0.9 | Near-certain | Core behavior |
**Confidence increases** when:
- Pattern is repeatedly observed
- User doesn't correct the suggested behavior
- Similar instincts from other sources agree
**Confidence decreases** when:
- User explicitly corrects the behavior
- Pattern isn't observed for extended periods
- Contradicting evidence appears
## Why Hooks vs Skills for Observation?
> "v1 relied on skills to observe. Skills are probabilistic—they fire ~50-80% of the time based on Claude's judgment."
Hooks fire **100% of the time**, deterministically. This means:
- Every tool call is observed
- No patterns are missed
- Learning is comprehensive
## Backward Compatibility
v2 is fully compatible with v1:
- Existing `~/.claude/skills/learned/` skills still work
- Stop hook still runs (but now also feeds into v2)
- Gradual migration path: run both in parallel
## Privacy
- Observations stay **local** on your machine
- Only **instincts** (patterns) can be exported
- No actual code or conversation content is shared
- You control what gets exported
## Related
- [Skill Creator](https://skill-creator.app) - Generate instincts from repo history
- [Homunculus](https://github.com/humanplane/homunculus) - Inspiration for v2 architecture
- [The Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - Continuous learning section
---
*Instinct-based learning: teaching Claude your patterns, one observation at a time.*

View File

@@ -0,0 +1,137 @@
---
name: observer
description: Background agent that analyzes session observations to detect patterns and create instincts. Uses Haiku for cost-efficiency.
model: haiku
run_mode: background
---
# Observer Agent
A background agent that analyzes observations from Claude Code sessions to detect patterns and create instincts.
## When to Run
- After significant session activity (20+ tool calls)
- When user runs `/analyze-patterns`
- On a scheduled interval (configurable, default 5 minutes)
- When triggered by observation hook (SIGUSR1)
## Input
Reads observations from `~/.claude/homunculus/observations.jsonl`:
```jsonl
{"timestamp":"2025-01-22T10:30:00Z","event":"tool_start","session":"abc123","tool":"Edit","input":"..."}
{"timestamp":"2025-01-22T10:30:01Z","event":"tool_complete","session":"abc123","tool":"Edit","output":"..."}
{"timestamp":"2025-01-22T10:30:05Z","event":"tool_start","session":"abc123","tool":"Bash","input":"npm test"}
{"timestamp":"2025-01-22T10:30:10Z","event":"tool_complete","session":"abc123","tool":"Bash","output":"All tests pass"}
```
## Pattern Detection
Look for these patterns in observations:
### 1. User Corrections
When a user's follow-up message corrects Claude's previous action:
- "No, use X instead of Y"
- "Actually, I meant..."
- Immediate undo/redo patterns
→ Create instinct: "When doing X, prefer Y"
### 2. Error Resolutions
When an error is followed by a fix:
- Tool output contains error
- Next few tool calls fix it
- Same error type resolved similarly multiple times
→ Create instinct: "When encountering error X, try Y"
### 3. Repeated Workflows
When the same sequence of tools is used multiple times:
- Same tool sequence with similar inputs
- File patterns that change together
- Time-clustered operations
→ Create workflow instinct: "When doing X, follow steps Y, Z, W"
### 4. Tool Preferences
When certain tools are consistently preferred:
- Always uses Grep before Edit
- Prefers Read over Bash cat
- Uses specific Bash commands for certain tasks
→ Create instinct: "When needing X, use tool Y"
## Output
Creates/updates instincts in `~/.claude/homunculus/instincts/personal/`:
```yaml
---
id: prefer-grep-before-edit
trigger: "when searching for code to modify"
confidence: 0.65
domain: "workflow"
source: "session-observation"
---
# Prefer Grep Before Edit
## Action
Always use Grep to find the exact location before using Edit.
## Evidence
- Observed 8 times in session abc123
- Pattern: Grep → Read → Edit sequence
- Last observed: 2025-01-22
```
## Confidence Calculation
Initial confidence based on observation frequency:
- 1-2 observations: 0.3 (tentative)
- 3-5 observations: 0.5 (moderate)
- 6-10 observations: 0.7 (strong)
- 11+ observations: 0.85 (very strong)
Confidence adjusts over time:
- +0.05 for each confirming observation
- -0.1 for each contradicting observation
- -0.02 per week without observation (decay)
## Important Guidelines
1. **Be Conservative**: Only create instincts for clear patterns (3+ observations)
2. **Be Specific**: Narrow triggers are better than broad ones
3. **Track Evidence**: Always include what observations led to the instinct
4. **Respect Privacy**: Never include actual code snippets, only patterns
5. **Merge Similar**: If a new instinct is similar to existing, update rather than duplicate
## Example Analysis Session
Given observations:
```jsonl
{"event":"tool_start","tool":"Grep","input":"pattern: useState"}
{"event":"tool_complete","tool":"Grep","output":"Found in 3 files"}
{"event":"tool_start","tool":"Read","input":"src/hooks/useAuth.ts"}
{"event":"tool_complete","tool":"Read","output":"[file content]"}
{"event":"tool_start","tool":"Edit","input":"src/hooks/useAuth.ts..."}
```
Analysis:
- Detected workflow: Grep → Read → Edit
- Frequency: Seen 5 times this session
- Create instinct:
- trigger: "when modifying code"
- action: "Search with Grep, confirm with Read, then Edit"
- confidence: 0.6
- domain: "workflow"
## Integration with Skill Creator
When instincts are imported from Skill Creator (repo analysis), they have:
- `source: "repo-analysis"`
- `source_repo: "https://github.com/..."`
These should be treated as team/project conventions with higher initial confidence (0.7+).

View File

@@ -0,0 +1,134 @@
#!/bin/bash
# Continuous Learning v2 - Observer Agent Launcher
#
# Starts the background observer agent that analyzes observations
# and creates instincts. Uses Haiku model for cost efficiency.
#
# Usage:
# start-observer.sh # Start observer in background
# start-observer.sh stop # Stop running observer
# start-observer.sh status # Check if observer is running
set -e
CONFIG_DIR="${HOME}/.claude/homunculus"
PID_FILE="${CONFIG_DIR}/.observer.pid"
LOG_FILE="${CONFIG_DIR}/observer.log"
OBSERVATIONS_FILE="${CONFIG_DIR}/observations.jsonl"
mkdir -p "$CONFIG_DIR"
case "${1:-start}" in
stop)
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "Stopping observer (PID: $pid)..."
kill "$pid"
rm -f "$PID_FILE"
echo "Observer stopped."
else
echo "Observer not running (stale PID file)."
rm -f "$PID_FILE"
fi
else
echo "Observer not running."
fi
exit 0
;;
status)
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "Observer is running (PID: $pid)"
echo "Log: $LOG_FILE"
echo "Observations: $(wc -l < "$OBSERVATIONS_FILE" 2>/dev/null || echo 0) lines"
exit 0
else
echo "Observer not running (stale PID file)"
rm -f "$PID_FILE"
exit 1
fi
else
echo "Observer not running"
exit 1
fi
;;
start)
# Check if already running
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
echo "Observer already running (PID: $pid)"
exit 0
fi
rm -f "$PID_FILE"
fi
echo "Starting observer agent..."
# The observer loop
(
trap 'rm -f "$PID_FILE"; exit 0' TERM INT
analyze_observations() {
# Only analyze if we have enough observations
obs_count=$(wc -l < "$OBSERVATIONS_FILE" 2>/dev/null || echo 0)
if [ "$obs_count" -lt 10 ]; then
return
fi
echo "[$(date)] Analyzing $obs_count observations..." >> "$LOG_FILE"
# Use Claude Code with Haiku to analyze observations
# This spawns a quick analysis session
if command -v claude &> /dev/null; then
claude --model haiku --max-turns 3 --print \
"Read $OBSERVATIONS_FILE and identify patterns. If you find 3+ occurrences of the same pattern, create an instinct file in $CONFIG_DIR/instincts/personal/ following the format in the observer agent spec. Be conservative - only create instincts for clear patterns." \
>> "$LOG_FILE" 2>&1 || true
fi
# Archive processed observations
if [ -f "$OBSERVATIONS_FILE" ]; then
archive_dir="${CONFIG_DIR}/observations.archive"
mkdir -p "$archive_dir"
mv "$OBSERVATIONS_FILE" "$archive_dir/processed-$(date +%Y%m%d-%H%M%S).jsonl"
touch "$OBSERVATIONS_FILE"
fi
}
# Handle SIGUSR1 for on-demand analysis
trap 'analyze_observations' USR1
echo "$$" > "$PID_FILE"
echo "[$(date)] Observer started (PID: $$)" >> "$LOG_FILE"
while true; do
# Check every 5 minutes
sleep 300
analyze_observations
done
) &
disown
# Wait a moment for PID file
sleep 1
if [ -f "$PID_FILE" ]; then
echo "Observer started (PID: $(cat "$PID_FILE"))"
echo "Log: $LOG_FILE"
else
echo "Failed to start observer"
exit 1
fi
;;
*)
echo "Usage: $0 {start|stop|status}"
exit 1
;;
esac

View File

@@ -0,0 +1,186 @@
---
name: evolve
description: Cluster related instincts into skills, commands, or agents
command: /evolve
implementation: python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py evolve
---
# Evolve Command
## Implementation
```bash
python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py evolve [--generate]
```
Analyzes instincts and clusters related ones into higher-level structures:
- **Commands**: When instincts describe user-invoked actions
- **Skills**: When instincts describe auto-triggered behaviors
- **Agents**: When instincts describe complex, multi-step processes
## Usage
```
/evolve # Analyze all instincts and suggest evolutions
/evolve --domain testing # Only evolve instincts in testing domain
/evolve --dry-run # Show what would be created without creating
/evolve --threshold 5 # Require 5+ related instincts to cluster
```
## Evolution Rules
### → Command (User-Invoked)
When instincts describe actions a user would explicitly request:
- Multiple instincts about "when user asks to..."
- Instincts with triggers like "when creating a new X"
- Instincts that follow a repeatable sequence
Example:
- `new-table-step1`: "when adding a database table, create migration"
- `new-table-step2`: "when adding a database table, update schema"
- `new-table-step3`: "when adding a database table, regenerate types"
→ Creates: `/new-table` command
### → Skill (Auto-Triggered)
When instincts describe behaviors that should happen automatically:
- Pattern-matching triggers
- Error handling responses
- Code style enforcement
Example:
- `prefer-functional`: "when writing functions, prefer functional style"
- `use-immutable`: "when modifying state, use immutable patterns"
- `avoid-classes`: "when designing modules, avoid class-based design"
→ Creates: `functional-patterns` skill
### → Agent (Needs Depth/Isolation)
When instincts describe complex, multi-step processes that benefit from isolation:
- Debugging workflows
- Refactoring sequences
- Research tasks
Example:
- `debug-step1`: "when debugging, first check logs"
- `debug-step2`: "when debugging, isolate the failing component"
- `debug-step3`: "when debugging, create minimal reproduction"
- `debug-step4`: "when debugging, verify fix with test"
→ Creates: `debugger` agent
## What to Do
1. Read all instincts from `~/.claude/homunculus/instincts/`
2. Group instincts by:
- Domain similarity
- Trigger pattern overlap
- Action sequence relationship
3. For each cluster of 3+ related instincts:
- Determine evolution type (command/skill/agent)
- Generate the appropriate file
- Save to `~/.claude/homunculus/evolved/{commands,skills,agents}/`
4. Link evolved structure back to source instincts
## Output Format
```
🧬 Evolve Analysis
==================
Found 3 clusters ready for evolution:
## Cluster 1: Database Migration Workflow
Instincts: new-table-migration, update-schema, regenerate-types
Type: Command
Confidence: 85% (based on 12 observations)
Would create: /new-table command
Files:
- ~/.claude/homunculus/evolved/commands/new-table.md
## Cluster 2: Functional Code Style
Instincts: prefer-functional, use-immutable, avoid-classes, pure-functions
Type: Skill
Confidence: 78% (based on 8 observations)
Would create: functional-patterns skill
Files:
- ~/.claude/homunculus/evolved/skills/functional-patterns.md
## Cluster 3: Debugging Process
Instincts: debug-check-logs, debug-isolate, debug-reproduce, debug-verify
Type: Agent
Confidence: 72% (based on 6 observations)
Would create: debugger agent
Files:
- ~/.claude/homunculus/evolved/agents/debugger.md
---
Run `/evolve --execute` to create these files.
```
## Flags
- `--execute`: Actually create the evolved structures (default is preview)
- `--dry-run`: Preview without creating
- `--domain <name>`: Only evolve instincts in specified domain
- `--threshold <n>`: Minimum instincts required to form cluster (default: 3)
- `--type <command|skill|agent>`: Only create specified type
## Generated File Format
### Command
```markdown
---
name: new-table
description: Create a new database table with migration, schema update, and type generation
command: /new-table
evolved_from:
- new-table-migration
- update-schema
- regenerate-types
---
# New Table Command
[Generated content based on clustered instincts]
## Steps
1. ...
2. ...
```
### Skill
```markdown
---
name: functional-patterns
description: Enforce functional programming patterns
evolved_from:
- prefer-functional
- use-immutable
- avoid-classes
---
# Functional Patterns Skill
[Generated content based on clustered instincts]
```
### Agent
```markdown
---
name: debugger
description: Systematic debugging agent
model: sonnet
evolved_from:
- debug-check-logs
- debug-isolate
- debug-reproduce
---
# Debugger Agent
[Generated content based on clustered instincts]
```

View File

@@ -0,0 +1,91 @@
---
name: instinct-export
description: Export instincts for sharing with teammates or other projects
command: /instinct-export
---
# Instinct Export Command
Exports instincts to a shareable format. Perfect for:
- Sharing with teammates
- Transferring to a new machine
- Contributing to project conventions
## Usage
```
/instinct-export # Export all personal instincts
/instinct-export --domain testing # Export only testing instincts
/instinct-export --min-confidence 0.7 # Only export high-confidence instincts
/instinct-export --output team-instincts.yaml
```
## What to Do
1. Read instincts from `~/.claude/homunculus/instincts/personal/`
2. Filter based on flags
3. Strip sensitive information:
- Remove session IDs
- Remove file paths (keep only patterns)
- Remove timestamps older than "last week"
4. Generate export file
## Output Format
Creates a YAML file:
```yaml
# Instincts Export
# Generated: 2025-01-22
# Source: personal
# Count: 12 instincts
version: "2.0"
exported_by: "continuous-learning-v2"
export_date: "2025-01-22T10:30:00Z"
instincts:
- id: prefer-functional-style
trigger: "when writing new functions"
action: "Use functional patterns over classes"
confidence: 0.8
domain: code-style
observations: 8
- id: test-first-workflow
trigger: "when adding new functionality"
action: "Write test first, then implementation"
confidence: 0.9
domain: testing
observations: 12
- id: grep-before-edit
trigger: "when modifying code"
action: "Search with Grep, confirm with Read, then Edit"
confidence: 0.7
domain: workflow
observations: 6
```
## Privacy Considerations
Exports include:
- ✅ Trigger patterns
- ✅ Actions
- ✅ Confidence scores
- ✅ Domains
- ✅ Observation counts
Exports do NOT include:
- ❌ Actual code snippets
- ❌ File paths
- ❌ Session transcripts
- ❌ Personal identifiers
## Flags
- `--domain <name>`: Export only specified domain
- `--min-confidence <n>`: Minimum confidence threshold (default: 0.3)
- `--output <file>`: Output file path (default: instincts-export-YYYYMMDD.yaml)
- `--format <yaml|json|md>`: Output format (default: yaml)
- `--include-evidence`: Include evidence text (default: excluded)

View File

@@ -0,0 +1,135 @@
---
name: instinct-import
description: Import instincts from teammates, Skill Creator, or other sources
command: /instinct-import
implementation: python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import <file>
---
# Instinct Import Command
## Implementation
```bash
python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py import <file-or-url> [--dry-run] [--force] [--min-confidence 0.7]
```
Import instincts from:
- Teammates' exports
- Skill Creator (repo analysis)
- Community collections
- Previous machine backups
## Usage
```
/instinct-import team-instincts.yaml
/instinct-import https://github.com/org/repo/instincts.yaml
/instinct-import --from-skill-creator acme/webapp
```
## What to Do
1. Fetch the instinct file (local path or URL)
2. Parse and validate the format
3. Check for duplicates with existing instincts
4. Merge or add new instincts
5. Save to `~/.claude/homunculus/instincts/inherited/`
## Import Process
```
📥 Importing instincts from: team-instincts.yaml
================================================
Found 12 instincts to import.
Analyzing conflicts...
## New Instincts (8)
These will be added:
✓ use-zod-validation (confidence: 0.7)
✓ prefer-named-exports (confidence: 0.65)
✓ test-async-functions (confidence: 0.8)
...
## Duplicate Instincts (3)
Already have similar instincts:
⚠️ prefer-functional-style
Local: 0.8 confidence, 12 observations
Import: 0.7 confidence
→ Keep local (higher confidence)
⚠️ test-first-workflow
Local: 0.75 confidence
Import: 0.9 confidence
→ Update to import (higher confidence)
## Conflicting Instincts (1)
These contradict local instincts:
❌ use-classes-for-services
Conflicts with: avoid-classes
→ Skip (requires manual resolution)
---
Import 8 new, update 1, skip 3?
```
## Merge Strategies
### For Duplicates
When importing an instinct that matches an existing one:
- **Higher confidence wins**: Keep the one with higher confidence
- **Merge evidence**: Combine observation counts
- **Update timestamp**: Mark as recently validated
### For Conflicts
When importing an instinct that contradicts an existing one:
- **Skip by default**: Don't import conflicting instincts
- **Flag for review**: Mark both as needing attention
- **Manual resolution**: User decides which to keep
## Source Tracking
Imported instincts are marked with:
```yaml
source: "inherited"
imported_from: "team-instincts.yaml"
imported_at: "2025-01-22T10:30:00Z"
original_source: "session-observation" # or "repo-analysis"
```
## Skill Creator Integration
When importing from Skill Creator:
```
/instinct-import --from-skill-creator acme/webapp
```
This fetches instincts generated from repo analysis:
- Source: `repo-analysis`
- Higher initial confidence (0.7+)
- Linked to source repository
## Flags
- `--dry-run`: Preview without importing
- `--force`: Import even if conflicts exist
- `--merge-strategy <higher|local|import>`: How to handle duplicates
- `--from-skill-creator <owner/repo>`: Import from Skill Creator analysis
- `--min-confidence <n>`: Only import instincts above threshold
## Output
After import:
```
✅ Import complete!
Added: 8 instincts
Updated: 1 instinct
Skipped: 3 instincts (2 duplicates, 1 conflict)
New instincts saved to: ~/.claude/homunculus/instincts/inherited/
Run /instinct-status to see all instincts.
```

View File

@@ -0,0 +1,79 @@
---
name: instinct-status
description: Show all learned instincts with their confidence levels
command: /instinct-status
implementation: python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status
---
# Instinct Status Command
Shows all learned instincts with their confidence scores, grouped by domain.
## Implementation
```bash
python3 ~/.claude/skills/continuous-learning-v2/scripts/instinct-cli.py status
```
## Usage
```
/instinct-status
/instinct-status --domain code-style
/instinct-status --low-confidence
```
## What to Do
1. Read all instinct files from `~/.claude/homunculus/instincts/personal/`
2. Read inherited instincts from `~/.claude/homunculus/instincts/inherited/`
3. Display them grouped by domain with confidence bars
## Output Format
```
📊 Instinct Status
==================
## Code Style (4 instincts)
### prefer-functional-style
Trigger: when writing new functions
Action: Use functional patterns over classes
Confidence: ████████░░ 80%
Source: session-observation | Last updated: 2025-01-22
### use-path-aliases
Trigger: when importing modules
Action: Use @/ path aliases instead of relative imports
Confidence: ██████░░░░ 60%
Source: repo-analysis (github.com/acme/webapp)
## Testing (2 instincts)
### test-first-workflow
Trigger: when adding new functionality
Action: Write test first, then implementation
Confidence: █████████░ 90%
Source: session-observation
## Workflow (3 instincts)
### grep-before-edit
Trigger: when modifying code
Action: Search with Grep, confirm with Read, then Edit
Confidence: ███████░░░ 70%
Source: session-observation
---
Total: 9 instincts (4 personal, 5 inherited)
Observer: Running (last analysis: 5 min ago)
```
## Flags
- `--domain <name>`: Filter by domain (code-style, testing, git, etc.)
- `--low-confidence`: Show only instincts with confidence < 0.5
- `--high-confidence`: Show only instincts with confidence >= 0.7
- `--source <type>`: Filter by source (session-observation, repo-analysis, inherited)
- `--json`: Output as JSON for programmatic use

View File

@@ -0,0 +1,41 @@
{
"version": "2.0",
"observation": {
"enabled": true,
"store_path": "~/.claude/homunculus/observations.jsonl",
"max_file_size_mb": 10,
"archive_after_days": 7,
"capture_tools": ["Edit", "Write", "Bash", "Read", "Grep", "Glob"],
"ignore_tools": ["TodoWrite"]
},
"instincts": {
"personal_path": "~/.claude/homunculus/instincts/personal/",
"inherited_path": "~/.claude/homunculus/instincts/inherited/",
"min_confidence": 0.3,
"auto_approve_threshold": 0.7,
"confidence_decay_rate": 0.02,
"max_instincts": 100
},
"observer": {
"enabled": false,
"model": "haiku",
"run_interval_minutes": 5,
"min_observations_to_analyze": 20,
"patterns_to_detect": [
"user_corrections",
"error_resolutions",
"repeated_workflows",
"tool_preferences",
"file_patterns"
]
},
"evolution": {
"cluster_threshold": 3,
"evolved_path": "~/.claude/homunculus/evolved/",
"auto_evolve": false
},
"integration": {
"skill_creator_api": "https://skill-creator.app/api",
"backward_compatible_v1": true
}
}

View File

@@ -0,0 +1,137 @@
#!/bin/bash
# Continuous Learning v2 - Observation Hook
#
# Captures tool use events for pattern analysis.
# Claude Code passes hook data via stdin as JSON.
#
# Hook config (in ~/.claude/settings.json):
# {
# "hooks": {
# "PreToolUse": [{
# "matcher": "*",
# "hooks": [{ "type": "command", "command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh" }]
# }],
# "PostToolUse": [{
# "matcher": "*",
# "hooks": [{ "type": "command", "command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh" }]
# }]
# }
# }
set -e
CONFIG_DIR="${HOME}/.claude/homunculus"
OBSERVATIONS_FILE="${CONFIG_DIR}/observations.jsonl"
MAX_FILE_SIZE_MB=10
# Ensure directory exists
mkdir -p "$CONFIG_DIR"
# Skip if disabled
if [ -f "$CONFIG_DIR/disabled" ]; then
exit 0
fi
# Read JSON from stdin (Claude Code hook format)
INPUT_JSON=$(cat)
# Exit if no input
if [ -z "$INPUT_JSON" ]; then
exit 0
fi
# Parse using python (more reliable than jq for complex JSON)
PARSED=$(python3 << EOF
import json
import sys
try:
data = json.loads('''$INPUT_JSON''')
# Extract fields - Claude Code hook format
hook_type = data.get('hook_type', 'unknown') # PreToolUse or PostToolUse
tool_name = data.get('tool_name', data.get('tool', 'unknown'))
tool_input = data.get('tool_input', data.get('input', {}))
tool_output = data.get('tool_output', data.get('output', ''))
session_id = data.get('session_id', 'unknown')
# Truncate large inputs/outputs
if isinstance(tool_input, dict):
tool_input_str = json.dumps(tool_input)[:5000]
else:
tool_input_str = str(tool_input)[:5000]
if isinstance(tool_output, dict):
tool_output_str = json.dumps(tool_output)[:5000]
else:
tool_output_str = str(tool_output)[:5000]
# Determine event type
event = 'tool_start' if 'Pre' in hook_type else 'tool_complete'
print(json.dumps({
'parsed': True,
'event': event,
'tool': tool_name,
'input': tool_input_str if event == 'tool_start' else None,
'output': tool_output_str if event == 'tool_complete' else None,
'session': session_id
}))
except Exception as e:
print(json.dumps({'parsed': False, 'error': str(e)}))
EOF
)
# Check if parsing succeeded
PARSED_OK=$(echo "$PARSED" | python3 -c "import json,sys; print(json.load(sys.stdin).get('parsed', False))")
if [ "$PARSED_OK" != "True" ]; then
# Fallback: log raw input for debugging
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "{\"timestamp\":\"$timestamp\",\"event\":\"parse_error\",\"raw\":$(echo "$INPUT_JSON" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()[:1000]))')}" >> "$OBSERVATIONS_FILE"
exit 0
fi
# Archive if file too large
if [ -f "$OBSERVATIONS_FILE" ]; then
file_size_mb=$(du -m "$OBSERVATIONS_FILE" 2>/dev/null | cut -f1)
if [ "${file_size_mb:-0}" -ge "$MAX_FILE_SIZE_MB" ]; then
archive_dir="${CONFIG_DIR}/observations.archive"
mkdir -p "$archive_dir"
mv "$OBSERVATIONS_FILE" "$archive_dir/observations-$(date +%Y%m%d-%H%M%S).jsonl"
fi
fi
# Build and write observation
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
python3 << EOF
import json
parsed = json.loads('''$PARSED''')
observation = {
'timestamp': '$timestamp',
'event': parsed['event'],
'tool': parsed['tool'],
'session': parsed['session']
}
if parsed['input']:
observation['input'] = parsed['input']
if parsed['output']:
observation['output'] = parsed['output']
with open('$OBSERVATIONS_FILE', 'a') as f:
f.write(json.dumps(observation) + '\n')
EOF
# Signal observer if running
OBSERVER_PID_FILE="${CONFIG_DIR}/.observer.pid"
if [ -f "$OBSERVER_PID_FILE" ]; then
observer_pid=$(cat "$OBSERVER_PID_FILE")
if kill -0 "$observer_pid" 2>/dev/null; then
kill -USR1 "$observer_pid" 2>/dev/null || true
fi
fi
exit 0

View File

@@ -0,0 +1,494 @@
#!/usr/bin/env python3
"""
Instinct CLI - Manage instincts for Continuous Learning v2
Commands:
status - Show all instincts and their status
import - Import instincts from file or URL
export - Export instincts to file
evolve - Cluster instincts into skills/commands/agents
"""
import argparse
import json
import os
import sys
import re
import urllib.request
from pathlib import Path
from datetime import datetime
from collections import defaultdict
from typing import Optional
# ─────────────────────────────────────────────
# Configuration
# ─────────────────────────────────────────────
HOMUNCULUS_DIR = Path.home() / ".claude" / "homunculus"
INSTINCTS_DIR = HOMUNCULUS_DIR / "instincts"
PERSONAL_DIR = INSTINCTS_DIR / "personal"
INHERITED_DIR = INSTINCTS_DIR / "inherited"
EVOLVED_DIR = HOMUNCULUS_DIR / "evolved"
OBSERVATIONS_FILE = HOMUNCULUS_DIR / "observations.jsonl"
# Ensure directories exist
for d in [PERSONAL_DIR, INHERITED_DIR, EVOLVED_DIR / "skills", EVOLVED_DIR / "commands", EVOLVED_DIR / "agents"]:
d.mkdir(parents=True, exist_ok=True)
# ─────────────────────────────────────────────
# Instinct Parser
# ─────────────────────────────────────────────
def parse_instinct_file(content: str) -> list[dict]:
"""Parse YAML-like instinct file format."""
instincts = []
current = {}
in_frontmatter = False
content_lines = []
for line in content.split('\n'):
if line.strip() == '---':
if in_frontmatter:
# End of frontmatter
in_frontmatter = False
if current:
current['content'] = '\n'.join(content_lines).strip()
instincts.append(current)
current = {}
content_lines = []
else:
# Start of frontmatter
in_frontmatter = True
if current:
current['content'] = '\n'.join(content_lines).strip()
instincts.append(current)
current = {}
content_lines = []
elif in_frontmatter:
# Parse YAML-like frontmatter
if ':' in line:
key, value = line.split(':', 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
if key == 'confidence':
current[key] = float(value)
else:
current[key] = value
else:
content_lines.append(line)
# Don't forget the last instinct
if current:
current['content'] = '\n'.join(content_lines).strip()
instincts.append(current)
return [i for i in instincts if i.get('id')]
def load_all_instincts() -> list[dict]:
"""Load all instincts from personal and inherited directories."""
instincts = []
for directory in [PERSONAL_DIR, INHERITED_DIR]:
if not directory.exists():
continue
for file in directory.glob("*.yaml"):
try:
content = file.read_text()
parsed = parse_instinct_file(content)
for inst in parsed:
inst['_source_file'] = str(file)
inst['_source_type'] = directory.name
instincts.extend(parsed)
except Exception as e:
print(f"Warning: Failed to parse {file}: {e}", file=sys.stderr)
return instincts
# ─────────────────────────────────────────────
# Status Command
# ─────────────────────────────────────────────
def cmd_status(args):
"""Show status of all instincts."""
instincts = load_all_instincts()
if not instincts:
print("No instincts found.")
print(f"\nInstinct directories:")
print(f" Personal: {PERSONAL_DIR}")
print(f" Inherited: {INHERITED_DIR}")
return
# Group by domain
by_domain = defaultdict(list)
for inst in instincts:
domain = inst.get('domain', 'general')
by_domain[domain].append(inst)
# Print header
print(f"\n{'='*60}")
print(f" INSTINCT STATUS - {len(instincts)} total")
print(f"{'='*60}\n")
# Summary by source
personal = [i for i in instincts if i.get('_source_type') == 'personal']
inherited = [i for i in instincts if i.get('_source_type') == 'inherited']
print(f" Personal: {len(personal)}")
print(f" Inherited: {len(inherited)}")
print()
# Print by domain
for domain in sorted(by_domain.keys()):
domain_instincts = by_domain[domain]
print(f"## {domain.upper()} ({len(domain_instincts)})")
print()
for inst in sorted(domain_instincts, key=lambda x: -x.get('confidence', 0.5)):
conf = inst.get('confidence', 0.5)
conf_bar = '' * int(conf * 10) + '' * (10 - int(conf * 10))
trigger = inst.get('trigger', 'unknown trigger')
source = inst.get('source', 'unknown')
print(f" {conf_bar} {int(conf*100):3d}% {inst.get('id', 'unnamed')}")
print(f" trigger: {trigger}")
# Extract action from content
content = inst.get('content', '')
action_match = re.search(r'## Action\s*\n\s*(.+?)(?:\n\n|\n##|$)', content, re.DOTALL)
if action_match:
action = action_match.group(1).strip().split('\n')[0]
print(f" action: {action[:60]}{'...' if len(action) > 60 else ''}")
print()
# Observations stats
if OBSERVATIONS_FILE.exists():
obs_count = sum(1 for _ in open(OBSERVATIONS_FILE))
print(f"─────────────────────────────────────────────────────────")
print(f" Observations: {obs_count} events logged")
print(f" File: {OBSERVATIONS_FILE}")
print(f"\n{'='*60}\n")
# ─────────────────────────────────────────────
# Import Command
# ─────────────────────────────────────────────
def cmd_import(args):
"""Import instincts from file or URL."""
source = args.source
# Fetch content
if source.startswith('http://') or source.startswith('https://'):
print(f"Fetching from URL: {source}")
try:
with urllib.request.urlopen(source) as response:
content = response.read().decode('utf-8')
except Exception as e:
print(f"Error fetching URL: {e}", file=sys.stderr)
return 1
else:
path = Path(source).expanduser()
if not path.exists():
print(f"File not found: {path}", file=sys.stderr)
return 1
content = path.read_text()
# Parse instincts
new_instincts = parse_instinct_file(content)
if not new_instincts:
print("No valid instincts found in source.")
return 1
print(f"\nFound {len(new_instincts)} instincts to import.\n")
# Load existing
existing = load_all_instincts()
existing_ids = {i.get('id') for i in existing}
# Categorize
to_add = []
duplicates = []
to_update = []
for inst in new_instincts:
inst_id = inst.get('id')
if inst_id in existing_ids:
# Check if we should update
existing_inst = next((e for e in existing if e.get('id') == inst_id), None)
if existing_inst:
if inst.get('confidence', 0) > existing_inst.get('confidence', 0):
to_update.append(inst)
else:
duplicates.append(inst)
else:
to_add.append(inst)
# Filter by minimum confidence
min_conf = args.min_confidence or 0.0
to_add = [i for i in to_add if i.get('confidence', 0.5) >= min_conf]
to_update = [i for i in to_update if i.get('confidence', 0.5) >= min_conf]
# Display summary
if to_add:
print(f"NEW ({len(to_add)}):")
for inst in to_add:
print(f" + {inst.get('id')} (confidence: {inst.get('confidence', 0.5):.2f})")
if to_update:
print(f"\nUPDATE ({len(to_update)}):")
for inst in to_update:
print(f" ~ {inst.get('id')} (confidence: {inst.get('confidence', 0.5):.2f})")
if duplicates:
print(f"\nSKIP ({len(duplicates)} - already exists with equal/higher confidence):")
for inst in duplicates[:5]:
print(f" - {inst.get('id')}")
if len(duplicates) > 5:
print(f" ... and {len(duplicates) - 5} more")
if args.dry_run:
print("\n[DRY RUN] No changes made.")
return 0
if not to_add and not to_update:
print("\nNothing to import.")
return 0
# Confirm
if not args.force:
response = input(f"\nImport {len(to_add)} new, update {len(to_update)}? [y/N] ")
if response.lower() != 'y':
print("Cancelled.")
return 0
# Write to inherited directory
timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')
source_name = Path(source).stem if not source.startswith('http') else 'web-import'
output_file = INHERITED_DIR / f"{source_name}-{timestamp}.yaml"
all_to_write = to_add + to_update
output_content = f"# Imported from {source}\n# Date: {datetime.now().isoformat()}\n\n"
for inst in all_to_write:
output_content += "---\n"
output_content += f"id: {inst.get('id')}\n"
output_content += f"trigger: \"{inst.get('trigger', 'unknown')}\"\n"
output_content += f"confidence: {inst.get('confidence', 0.5)}\n"
output_content += f"domain: {inst.get('domain', 'general')}\n"
output_content += f"source: inherited\n"
output_content += f"imported_from: \"{source}\"\n"
if inst.get('source_repo'):
output_content += f"source_repo: {inst.get('source_repo')}\n"
output_content += "---\n\n"
output_content += inst.get('content', '') + "\n\n"
output_file.write_text(output_content)
print(f"\n✅ Import complete!")
print(f" Added: {len(to_add)}")
print(f" Updated: {len(to_update)}")
print(f" Saved to: {output_file}")
return 0
# ─────────────────────────────────────────────
# Export Command
# ─────────────────────────────────────────────
def cmd_export(args):
"""Export instincts to file."""
instincts = load_all_instincts()
if not instincts:
print("No instincts to export.")
return 1
# Filter by domain if specified
if args.domain:
instincts = [i for i in instincts if i.get('domain') == args.domain]
# Filter by minimum confidence
if args.min_confidence:
instincts = [i for i in instincts if i.get('confidence', 0.5) >= args.min_confidence]
if not instincts:
print("No instincts match the criteria.")
return 1
# Generate output
output = f"# Instincts export\n# Date: {datetime.now().isoformat()}\n# Total: {len(instincts)}\n\n"
for inst in instincts:
output += "---\n"
for key in ['id', 'trigger', 'confidence', 'domain', 'source', 'source_repo']:
if inst.get(key):
value = inst[key]
if key == 'trigger':
output += f'{key}: "{value}"\n'
else:
output += f"{key}: {value}\n"
output += "---\n\n"
output += inst.get('content', '') + "\n\n"
# Write to file or stdout
if args.output:
Path(args.output).write_text(output)
print(f"Exported {len(instincts)} instincts to {args.output}")
else:
print(output)
return 0
# ─────────────────────────────────────────────
# Evolve Command
# ─────────────────────────────────────────────
def cmd_evolve(args):
"""Analyze instincts and suggest evolutions to skills/commands/agents."""
instincts = load_all_instincts()
if len(instincts) < 3:
print("Need at least 3 instincts to analyze patterns.")
print(f"Currently have: {len(instincts)}")
return 1
print(f"\n{'='*60}")
print(f" EVOLVE ANALYSIS - {len(instincts)} instincts")
print(f"{'='*60}\n")
# Group by domain
by_domain = defaultdict(list)
for inst in instincts:
domain = inst.get('domain', 'general')
by_domain[domain].append(inst)
# High-confidence instincts by domain (candidates for skills)
high_conf = [i for i in instincts if i.get('confidence', 0) >= 0.8]
print(f"High confidence instincts (>=80%): {len(high_conf)}")
# Find clusters (instincts with similar triggers)
trigger_clusters = defaultdict(list)
for inst in instincts:
trigger = inst.get('trigger', '')
# Normalize trigger
trigger_key = trigger.lower()
for keyword in ['when', 'creating', 'writing', 'adding', 'implementing', 'testing']:
trigger_key = trigger_key.replace(keyword, '').strip()
trigger_clusters[trigger_key].append(inst)
# Find clusters with 3+ instincts (good skill candidates)
skill_candidates = []
for trigger, cluster in trigger_clusters.items():
if len(cluster) >= 2:
avg_conf = sum(i.get('confidence', 0.5) for i in cluster) / len(cluster)
skill_candidates.append({
'trigger': trigger,
'instincts': cluster,
'avg_confidence': avg_conf,
'domains': list(set(i.get('domain', 'general') for i in cluster))
})
# Sort by cluster size and confidence
skill_candidates.sort(key=lambda x: (-len(x['instincts']), -x['avg_confidence']))
print(f"\nPotential skill clusters found: {len(skill_candidates)}")
if skill_candidates:
print(f"\n## SKILL CANDIDATES\n")
for i, cand in enumerate(skill_candidates[:5], 1):
print(f"{i}. Cluster: \"{cand['trigger']}\"")
print(f" Instincts: {len(cand['instincts'])}")
print(f" Avg confidence: {cand['avg_confidence']:.0%}")
print(f" Domains: {', '.join(cand['domains'])}")
print(f" Instincts:")
for inst in cand['instincts'][:3]:
print(f" - {inst.get('id')}")
print()
# Command candidates (workflow instincts with high confidence)
workflow_instincts = [i for i in instincts if i.get('domain') == 'workflow' and i.get('confidence', 0) >= 0.7]
if workflow_instincts:
print(f"\n## COMMAND CANDIDATES ({len(workflow_instincts)})\n")
for inst in workflow_instincts[:5]:
trigger = inst.get('trigger', 'unknown')
# Suggest command name
cmd_name = trigger.replace('when ', '').replace('implementing ', '').replace('a ', '')
cmd_name = cmd_name.replace(' ', '-')[:20]
print(f" /{cmd_name}")
print(f" From: {inst.get('id')}")
print(f" Confidence: {inst.get('confidence', 0.5):.0%}")
print()
# Agent candidates (complex multi-step patterns)
agent_candidates = [c for c in skill_candidates if len(c['instincts']) >= 3 and c['avg_confidence'] >= 0.75]
if agent_candidates:
print(f"\n## AGENT CANDIDATES ({len(agent_candidates)})\n")
for cand in agent_candidates[:3]:
agent_name = cand['trigger'].replace(' ', '-')[:20] + '-agent'
print(f" {agent_name}")
print(f" Covers {len(cand['instincts'])} instincts")
print(f" Avg confidence: {cand['avg_confidence']:.0%}")
print()
if args.generate:
print("\n[Would generate evolved structures here]")
print(" Skills would be saved to:", EVOLVED_DIR / "skills")
print(" Commands would be saved to:", EVOLVED_DIR / "commands")
print(" Agents would be saved to:", EVOLVED_DIR / "agents")
print(f"\n{'='*60}\n")
return 0
# ─────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description='Instinct CLI for Continuous Learning v2')
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Status
status_parser = subparsers.add_parser('status', help='Show instinct status')
# Import
import_parser = subparsers.add_parser('import', help='Import instincts')
import_parser.add_argument('source', help='File path or URL')
import_parser.add_argument('--dry-run', action='store_true', help='Preview without importing')
import_parser.add_argument('--force', action='store_true', help='Skip confirmation')
import_parser.add_argument('--min-confidence', type=float, help='Minimum confidence threshold')
# Export
export_parser = subparsers.add_parser('export', help='Export instincts')
export_parser.add_argument('--output', '-o', help='Output file')
export_parser.add_argument('--domain', help='Filter by domain')
export_parser.add_argument('--min-confidence', type=float, help='Minimum confidence')
# Evolve
evolve_parser = subparsers.add_parser('evolve', help='Analyze and evolve instincts')
evolve_parser.add_argument('--generate', action='store_true', help='Generate evolved structures')
args = parser.parse_args()
if args.command == 'status':
return cmd_status(args)
elif args.command == 'import':
return cmd_import(args)
elif args.command == 'export':
return cmd_export(args)
elif args.command == 'evolve':
return cmd_evolve(args)
else:
parser.print_help()
return 1
if __name__ == '__main__':
sys.exit(main() or 0)

View File

@@ -78,3 +78,33 @@ Add to your `~/.claude/settings.json`:
- [The Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - Section on continuous learning
- `/learn` command - Manual pattern extraction mid-session
---
## Comparison Notes (Research: Jan 2025)
### vs Homunculus (github.com/humanplane/homunculus)
Homunculus v2 takes a more sophisticated approach:
| Feature | Our Approach | Homunculus v2 |
|---------|--------------|---------------|
| Observation | Stop hook (end of session) | PreToolUse/PostToolUse hooks (100% reliable) |
| Analysis | Main context | Background agent (Haiku) |
| Granularity | Full skills | Atomic "instincts" |
| Confidence | None | 0.3-0.9 weighted |
| Evolution | Direct to skill | Instincts → cluster → skill/command/agent |
| Sharing | None | Export/import instincts |
**Key insight from homunculus:**
> "v1 relied on skills to observe. Skills are probabilistic—they fire ~50-80% of the time. v2 uses hooks for observation (100% reliable) and instincts as the atomic unit of learned behavior."
### Potential v2 Enhancements
1. **Instinct-based learning** - Smaller, atomic behaviors with confidence scoring
2. **Background observer** - Haiku agent analyzing in parallel
3. **Confidence decay** - Instincts lose confidence if contradicted
4. **Domain tagging** - code-style, testing, git, debugging, etc.
5. **Evolution path** - Cluster related instincts into skills/commands
See: `/Users/affoon/Documents/tasks/12-continuous-learning-v2.md` for full spec.

View File

@@ -1,3 +1,9 @@
---
name: eval-harness
description: Formal evaluation framework for Claude Code sessions implementing eval-driven development (EDD) principles
tools: Read, Write, Edit, Bash, Grep, Glob
---
# Eval Harness Skill
A formal evaluation framework for Claude Code sessions, implementing eval-driven development (EDD) principles.

View File

@@ -0,0 +1,673 @@
---
name: golang-patterns
description: Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications.
---
# Go Development Patterns
Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.
## When to Activate
- Writing new Go code
- Reviewing Go code
- Refactoring existing Go code
- Designing Go packages/modules
## Core Principles
### 1. Simplicity and Clarity
Go favors simplicity over cleverness. Code should be obvious and easy to read.
```go
// Good: Clear and direct
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// Bad: Overly clever
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}
```
### 2. Make the Zero Value Useful
Design types so their zero value is immediately usable without initialization.
```go
// Good: Zero value is useful
type Counter struct {
mu sync.Mutex
count int // zero value is 0, ready to use
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// Good: bytes.Buffer works with zero value
var buf bytes.Buffer
buf.WriteString("hello")
// Bad: Requires initialization
type BadCounter struct {
counts map[string]int // nil map will panic
}
```
### 3. Accept Interfaces, Return Structs
Functions should accept interface parameters and return concrete types.
```go
// Good: Accepts interface, returns concrete type
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
// Bad: Returns interface (hides implementation details unnecessarily)
func ProcessData(r io.Reader) (io.Reader, error) {
// ...
}
```
## Error Handling Patterns
### Error Wrapping with Context
```go
// Good: Wrap errors with context
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}
```
### Custom Error Types
```go
// Define domain-specific errors
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Sentinel errors for common cases
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
```
### Error Checking with errors.Is and errors.As
```go
func HandleError(err error) {
// Check for specific error
if errors.Is(err, sql.ErrNoRows) {
log.Println("No records found")
return
}
// Check for error type
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("Validation error on field %s: %s",
validationErr.Field, validationErr.Message)
return
}
// Unknown error
log.Printf("Unexpected error: %v", err)
}
```
### Never Ignore Errors
```go
// Bad: Ignoring error with blank identifier
result, _ := doSomething()
// Good: Handle or explicitly document why it's safe to ignore
result, err := doSomething()
if err != nil {
return err
}
// Acceptable: When error truly doesn't matter (rare)
_ = writer.Close() // Best-effort cleanup, error logged elsewhere
```
## Concurrency Patterns
### Worker Pool
```go
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
```
### Context for Cancellation and Timeouts
```go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
```
### Graceful Shutdown
```go
func GracefulShutdown(server *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}
```
### errgroup for Coordinated Goroutines
```go
import "golang.org/x/sync/errgroup"
func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([][]byte, len(urls))
for i, url := range urls {
i, url := i, url // Capture loop variables
g.Go(func() error {
data, err := FetchWithTimeout(ctx, url)
if err != nil {
return err
}
results[i] = data
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
```
### Avoiding Goroutine Leaks
```go
// Bad: Goroutine leak if context is cancelled
func leakyFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte)
go func() {
data, _ := fetch(url)
ch <- data // Blocks forever if no receiver
}()
return ch
}
// Good: Properly handles cancellation
func safeFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte, 1) // Buffered channel
go func() {
data, err := fetch(url)
if err != nil {
return
}
select {
case ch <- data:
case <-ctx.Done():
}
}()
return ch
}
```
## Interface Design
### Small, Focused Interfaces
```go
// Good: Single-method interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Compose interfaces as needed
type ReadWriteCloser interface {
Reader
Writer
Closer
}
```
### Define Interfaces Where They're Used
```go
// In the consumer package, not the provider
package service
// UserStore defines what this service needs
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type Service struct {
store UserStore
}
// Concrete implementation can be in another package
// It doesn't need to know about this interface
```
### Optional Behavior with Type Assertions
```go
type Flusher interface {
Flush() error
}
func WriteAndFlush(w io.Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return err
}
// Flush if supported
if f, ok := w.(Flusher); ok {
return f.Flush()
}
return nil
}
```
## Package Organization
### Standard Project Layout
```text
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # Entry point
├── internal/
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── config/ # Configuration
├── pkg/
│ └── client/ # Public API client
├── api/
│ └── v1/ # API definitions (proto, OpenAPI)
├── testdata/ # Test fixtures
├── go.mod
├── go.sum
└── Makefile
```
### Package Naming
```go
// Good: Short, lowercase, no underscores
package http
package json
package user
// Bad: Verbose, mixed case, or redundant
package httpHandler
package json_parser
package userService // Redundant 'Service' suffix
```
### Avoid Package-Level State
```go
// Bad: Global mutable state
var db *sql.DB
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}
// Good: Dependency injection
type Server struct {
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
return &Server{db: db}
}
```
## Struct Design
### Functional Options Pattern
```go
type Server struct {
addr string
timeout time.Duration
logger *log.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l *log.Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // default
logger: log.Default(), // default
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
server := NewServer(":8080",
WithTimeout(60*time.Second),
WithLogger(customLogger),
)
```
### Embedding for Composition
```go
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
type Server struct {
*Logger // Embedding - Server gets Log method
addr string
}
func NewServer(addr string) *Server {
return &Server{
Logger: &Logger{prefix: "SERVER"},
addr: addr,
}
}
// Usage
s := NewServer(":8080")
s.Log("Starting...") // Calls embedded Logger.Log
```
## Memory and Performance
### Preallocate Slices When Size is Known
```go
// Bad: Grows slice multiple times
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, process(item))
}
return results
}
// Good: Single allocation
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, process(item))
}
return results
}
```
### Use sync.Pool for Frequent Allocations
```go
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) []byte {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
// Process...
return buf.Bytes()
}
```
### Avoid String Concatenation in Loops
```go
// Bad: Creates many string allocations
func join(parts []string) string {
var result string
for _, p := range parts {
result += p + ","
}
return result
}
// Good: Single allocation with strings.Builder
func join(parts []string) string {
var sb strings.Builder
for i, p := range parts {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(p)
}
return sb.String()
}
// Best: Use standard library
func join(parts []string) string {
return strings.Join(parts, ",")
}
```
## Go Tooling Integration
### Essential Commands
```bash
# Build and run
go build ./...
go run ./cmd/myapp
# Testing
go test ./...
go test -race ./...
go test -cover ./...
# Static analysis
go vet ./...
staticcheck ./...
golangci-lint run
# Module management
go mod tidy
go mod verify
# Formatting
gofmt -w .
goimports -w .
```
### Recommended Linter Configuration (.golangci.yml)
```yaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
linters-settings:
errcheck:
check-type-assertions: true
govet:
check-shadowing: true
issues:
exclude-use-default: false
```
## Quick Reference: Go Idioms
| Idiom | Description |
|-------|-------------|
| Accept interfaces, return structs | Functions accept interface params, return concrete types |
| Errors are values | Treat errors as first-class values, not exceptions |
| Don't communicate by sharing memory | Use channels for coordination between goroutines |
| Make the zero value useful | Types should work without explicit initialization |
| A little copying is better than a little dependency | Avoid unnecessary external dependencies |
| Clear is better than clever | Prioritize readability over cleverness |
| gofmt is no one's favorite but everyone's friend | Always format with gofmt/goimports |
| Return early | Handle errors first, keep happy path unindented |
## Anti-Patterns to Avoid
```go
// Bad: Naked returns in long functions
func process() (result int, err error) {
// ... 50 lines ...
return // What is being returned?
}
// Bad: Using panic for control flow
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil {
panic(err) // Don't do this
}
return user
}
// Bad: Passing context in struct
type Request struct {
ctx context.Context // Context should be first param
ID string
}
// Good: Context as first parameter
func ProcessRequest(ctx context.Context, id string) error {
// ...
}
// Bad: Mixing value and pointer receivers
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // Value receiver
func (c *Counter) Increment() { c.n++ } // Pointer receiver
// Pick one style and be consistent
```
**Remember**: Go code should be boring in the best way - predictable, consistent, and easy to understand. When in doubt, keep it simple.

View File

@@ -0,0 +1,719 @@
---
name: golang-testing
description: Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices.
---
# Go Testing Patterns
Comprehensive Go testing patterns for writing reliable, maintainable tests following TDD methodology.
## When to Activate
- Writing new Go functions or methods
- Adding test coverage to existing code
- Creating benchmarks for performance-critical code
- Implementing fuzz tests for input validation
- Following TDD workflow in Go projects
## TDD Workflow for Go
### The RED-GREEN-REFACTOR Cycle
```
RED → Write a failing test first
GREEN → Write minimal code to pass the test
REFACTOR → Improve code while keeping tests green
REPEAT → Continue with next requirement
```
### Step-by-Step TDD in Go
```go
// Step 1: Define the interface/signature
// calculator.go
package calculator
func Add(a, b int) int {
panic("not implemented") // Placeholder
}
// Step 2: Write failing test (RED)
// calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; want %d", got, want)
}
}
// Step 3: Run test - verify FAIL
// $ go test
// --- FAIL: TestAdd (0.00s)
// panic: not implemented
// Step 4: Implement minimal code (GREEN)
func Add(a, b int) int {
return a + b
}
// Step 5: Run test - verify PASS
// $ go test
// PASS
// Step 6: Refactor if needed, verify tests still pass
```
## Table-Driven Tests
The standard pattern for Go tests. Enables comprehensive coverage with minimal code.
```go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zero values", 0, 0, 0},
{"mixed signs", -1, 1, 0},
{"large numbers", 1000000, 2000000, 3000000},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, got, tt.expected)
}
})
}
}
```
### Table-Driven Tests with Error Cases
```go
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want *Config
wantErr bool
}{
{
name: "valid config",
input: `{"host": "localhost", "port": 8080}`,
want: &Config{Host: "localhost", Port: 8080},
},
{
name: "invalid JSON",
input: `{invalid}`,
wantErr: true,
},
{
name: "empty input",
input: "",
wantErr: true,
},
{
name: "minimal config",
input: `{}`,
want: &Config{}, // Zero value config
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig(tt.input)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %+v; want %+v", got, tt.want)
}
})
}
}
```
## Subtests and Sub-benchmarks
### Organizing Related Tests
```go
func TestUser(t *testing.T) {
// Setup shared by all subtests
db := setupTestDB(t)
t.Run("Create", func(t *testing.T) {
user := &User{Name: "Alice"}
err := db.CreateUser(user)
if err != nil {
t.Fatalf("CreateUser failed: %v", err)
}
if user.ID == "" {
t.Error("expected user ID to be set")
}
})
t.Run("Get", func(t *testing.T) {
user, err := db.GetUser("alice-id")
if err != nil {
t.Fatalf("GetUser failed: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
})
t.Run("Update", func(t *testing.T) {
// ...
})
t.Run("Delete", func(t *testing.T) {
// ...
})
}
```
### Parallel Subtests
```go
func TestParallel(t *testing.T) {
tests := []struct {
name string
input string
}{
{"case1", "input1"},
{"case2", "input2"},
{"case3", "input3"},
}
for _, tt := range tests {
tt := tt // Capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Run subtests in parallel
result := Process(tt.input)
// assertions...
_ = result
})
}
}
```
## Test Helpers
### Helper Functions
```go
func setupTestDB(t *testing.T) *sql.DB {
t.Helper() // Marks this as a helper function
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open database: %v", err)
}
// Cleanup when test finishes
t.Cleanup(func() {
db.Close()
})
// Run migrations
if _, err := db.Exec(schema); err != nil {
t.Fatalf("failed to create schema: %v", err)
}
return db
}
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertEqual[T comparable](t *testing.T, got, want T) {
t.Helper()
if got != want {
t.Errorf("got %v; want %v", got, want)
}
}
```
### Temporary Files and Directories
```go
func TestFileProcessing(t *testing.T) {
// Create temp directory - automatically cleaned up
tmpDir := t.TempDir()
// Create test file
testFile := filepath.Join(tmpDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0644)
if err != nil {
t.Fatalf("failed to create test file: %v", err)
}
// Run test
result, err := ProcessFile(testFile)
if err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}
// Assert...
_ = result
}
```
## Golden Files
Testing against expected output files stored in `testdata/`.
```go
var update = flag.Bool("update", false, "update golden files")
func TestRender(t *testing.T) {
tests := []struct {
name string
input Template
}{
{"simple", Template{Name: "test"}},
{"complex", Template{Name: "test", Items: []string{"a", "b"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Render(tt.input)
golden := filepath.Join("testdata", tt.name+".golden")
if *update {
// Update golden file: go test -update
err := os.WriteFile(golden, got, 0644)
if err != nil {
t.Fatalf("failed to update golden file: %v", err)
}
}
want, err := os.ReadFile(golden)
if err != nil {
t.Fatalf("failed to read golden file: %v", err)
}
if !bytes.Equal(got, want) {
t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
}
})
}
}
```
## Mocking with Interfaces
### Interface-Based Mocking
```go
// Define interface for dependencies
type UserRepository interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
// Production implementation
type PostgresUserRepository struct {
db *sql.DB
}
func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
// Real database query
}
// Mock implementation for tests
type MockUserRepository struct {
GetUserFunc func(id string) (*User, error)
SaveUserFunc func(user *User) error
}
func (m *MockUserRepository) GetUser(id string) (*User, error) {
return m.GetUserFunc(id)
}
func (m *MockUserRepository) SaveUser(user *User) error {
return m.SaveUserFunc(user)
}
// Test using mock
func TestUserService(t *testing.T) {
mock := &MockUserRepository{
GetUserFunc: func(id string) (*User, error) {
if id == "123" {
return &User{ID: "123", Name: "Alice"}, nil
}
return nil, ErrNotFound
},
}
service := NewUserService(mock)
user, err := service.GetUserProfile("123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
}
```
## Benchmarks
### Basic Benchmarks
```go
func BenchmarkProcess(b *testing.B) {
data := generateTestData(1000)
b.ResetTimer() // Don't count setup time
for i := 0; i < b.N; i++ {
Process(data)
}
}
// Run: go test -bench=BenchmarkProcess -benchmem
// Output: BenchmarkProcess-8 10000 105234 ns/op 4096 B/op 10 allocs/op
```
### Benchmark with Different Sizes
```go
func BenchmarkSort(b *testing.B) {
sizes := []int{100, 1000, 10000, 100000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
data := generateRandomSlice(size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Make a copy to avoid sorting already sorted data
tmp := make([]int, len(data))
copy(tmp, data)
sort.Ints(tmp)
}
})
}
}
```
### Memory Allocation Benchmarks
```go
func BenchmarkStringConcat(b *testing.B) {
parts := []string{"hello", "world", "foo", "bar", "baz"}
b.Run("plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var s string
for _, p := range parts {
s += p
}
_ = s
}
})
b.Run("builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for _, p := range parts {
sb.WriteString(p)
}
_ = sb.String()
}
})
b.Run("join", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.Join(parts, "")
}
})
}
```
## Fuzzing (Go 1.18+)
### Basic Fuzz Test
```go
func FuzzParseJSON(f *testing.F) {
// Add seed corpus
f.Add(`{"name": "test"}`)
f.Add(`{"count": 123}`)
f.Add(`[]`)
f.Add(`""`)
f.Fuzz(func(t *testing.T, input string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(input), &result)
if err != nil {
// Invalid JSON is expected for random input
return
}
// If parsing succeeded, re-encoding should work
_, err = json.Marshal(result)
if err != nil {
t.Errorf("Marshal failed after successful Unmarshal: %v", err)
}
})
}
// Run: go test -fuzz=FuzzParseJSON -fuzztime=30s
```
### Fuzz Test with Multiple Inputs
```go
func FuzzCompare(f *testing.F) {
f.Add("hello", "world")
f.Add("", "")
f.Add("abc", "abc")
f.Fuzz(func(t *testing.T, a, b string) {
result := Compare(a, b)
// Property: Compare(a, a) should always equal 0
if a == b && result != 0 {
t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
}
// Property: Compare(a, b) and Compare(b, a) should have opposite signs
reverse := Compare(b, a)
if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
if result != 0 || reverse != 0 {
t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
a, b, result, b, a, reverse)
}
}
})
}
```
## Test Coverage
### Running Coverage
```bash
# Basic coverage
go test -cover ./...
# Generate coverage profile
go test -coverprofile=coverage.out ./...
# View coverage in browser
go tool cover -html=coverage.out
# View coverage by function
go tool cover -func=coverage.out
# Coverage with race detection
go test -race -coverprofile=coverage.out ./...
```
### Coverage Targets
| Code Type | Target |
|-----------|--------|
| Critical business logic | 100% |
| Public APIs | 90%+ |
| General code | 80%+ |
| Generated code | Exclude |
### Excluding Generated Code from Coverage
```go
//go:generate mockgen -source=interface.go -destination=mock_interface.go
// In coverage profile, exclude with build tags:
// go test -cover -tags=!generate ./...
```
## HTTP Handler Testing
```go
func TestHealthHandler(t *testing.T) {
// Create request
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
// Call handler
HealthHandler(w, req)
// Check response
resp := w.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "OK" {
t.Errorf("got body %q; want %q", body, "OK")
}
}
func TestAPIHandler(t *testing.T) {
tests := []struct {
name string
method string
path string
body string
wantStatus int
wantBody string
}{
{
name: "get user",
method: http.MethodGet,
path: "/users/123",
wantStatus: http.StatusOK,
wantBody: `{"id":"123","name":"Alice"}`,
},
{
name: "not found",
method: http.MethodGet,
path: "/users/999",
wantStatus: http.StatusNotFound,
},
{
name: "create user",
method: http.MethodPost,
path: "/users",
body: `{"name":"Bob"}`,
wantStatus: http.StatusCreated,
},
}
handler := NewAPIHandler()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var body io.Reader
if tt.body != "" {
body = strings.NewReader(tt.body)
}
req := httptest.NewRequest(tt.method, tt.path, body)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != tt.wantStatus {
t.Errorf("got status %d; want %d", w.Code, tt.wantStatus)
}
if tt.wantBody != "" && w.Body.String() != tt.wantBody {
t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody)
}
})
}
}
```
## Testing Commands
```bash
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run specific test
go test -run TestAdd ./...
# Run tests matching pattern
go test -run "TestUser/Create" ./...
# Run tests with race detector
go test -race ./...
# Run tests with coverage
go test -cover -coverprofile=coverage.out ./...
# Run short tests only
go test -short ./...
# Run tests with timeout
go test -timeout 30s ./...
# Run benchmarks
go test -bench=. -benchmem ./...
# Run fuzzing
go test -fuzz=FuzzParse -fuzztime=30s ./...
# Count test runs (for flaky test detection)
go test -count=10 ./...
```
## Best Practices
**DO:**
- Write tests FIRST (TDD)
- Use table-driven tests for comprehensive coverage
- Test behavior, not implementation
- Use `t.Helper()` in helper functions
- Use `t.Parallel()` for independent tests
- Clean up resources with `t.Cleanup()`
- Use meaningful test names that describe the scenario
**DON'T:**
- Test private functions directly (test through public API)
- Use `time.Sleep()` in tests (use channels or conditions)
- Ignore flaky tests (fix or remove them)
- Mock everything (prefer integration tests when possible)
- Skip error path testing
## Integration with CI/CD
```yaml
# GitHub Actions example
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run tests
run: go test -race -coverprofile=coverage.out ./...
- name: Check coverage
run: |
go tool cover -func=coverage.out | grep total | awk '{print $3}' | \
awk -F'%' '{if ($1 < 80) exit 1}'
```
**Remember**: Tests are documentation. They show how your code is meant to be used. Write them clearly and keep them up to date.

View File

@@ -0,0 +1,202 @@
---
name: iterative-retrieval
description: Pattern for progressively refining context retrieval to solve the subagent context problem
---
# Iterative Retrieval Pattern
Solves the "context problem" in multi-agent workflows where subagents don't know what context they need until they start working.
## The Problem
Subagents are spawned with limited context. They don't know:
- Which files contain relevant code
- What patterns exist in the codebase
- What terminology the project uses
Standard approaches fail:
- **Send everything**: Exceeds context limits
- **Send nothing**: Agent lacks critical information
- **Guess what's needed**: Often wrong
## The Solution: Iterative Retrieval
A 4-phase loop that progressively refines context:
```
┌─────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ DISPATCH │─────▶│ EVALUATE │ │
│ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ LOOP │◀─────│ REFINE │ │
│ └──────────┘ └──────────┘ │
│ │
│ Max 3 cycles, then proceed │
└─────────────────────────────────────────────┘
```
### Phase 1: DISPATCH
Initial broad query to gather candidate files:
```javascript
// Start with high-level intent
const initialQuery = {
patterns: ['src/**/*.ts', 'lib/**/*.ts'],
keywords: ['authentication', 'user', 'session'],
excludes: ['*.test.ts', '*.spec.ts']
};
// Dispatch to retrieval agent
const candidates = await retrieveFiles(initialQuery);
```
### Phase 2: EVALUATE
Assess retrieved content for relevance:
```javascript
function evaluateRelevance(files, task) {
return files.map(file => ({
path: file.path,
relevance: scoreRelevance(file.content, task),
reason: explainRelevance(file.content, task),
missingContext: identifyGaps(file.content, task)
}));
}
```
Scoring criteria:
- **High (0.8-1.0)**: Directly implements target functionality
- **Medium (0.5-0.7)**: Contains related patterns or types
- **Low (0.2-0.4)**: Tangentially related
- **None (0-0.2)**: Not relevant, exclude
### Phase 3: REFINE
Update search criteria based on evaluation:
```javascript
function refineQuery(evaluation, previousQuery) {
return {
// Add new patterns discovered in high-relevance files
patterns: [...previousQuery.patterns, ...extractPatterns(evaluation)],
// Add terminology found in codebase
keywords: [...previousQuery.keywords, ...extractKeywords(evaluation)],
// Exclude confirmed irrelevant paths
excludes: [...previousQuery.excludes, ...evaluation
.filter(e => e.relevance < 0.2)
.map(e => e.path)
],
// Target specific gaps
focusAreas: evaluation
.flatMap(e => e.missingContext)
.filter(unique)
};
}
```
### Phase 4: LOOP
Repeat with refined criteria (max 3 cycles):
```javascript
async function iterativeRetrieve(task, maxCycles = 3) {
let query = createInitialQuery(task);
let bestContext = [];
for (let cycle = 0; cycle < maxCycles; cycle++) {
const candidates = await retrieveFiles(query);
const evaluation = evaluateRelevance(candidates, task);
// Check if we have sufficient context
const highRelevance = evaluation.filter(e => e.relevance >= 0.7);
if (highRelevance.length >= 3 && !hasCriticalGaps(evaluation)) {
return highRelevance;
}
// Refine and continue
query = refineQuery(evaluation, query);
bestContext = mergeContext(bestContext, highRelevance);
}
return bestContext;
}
```
## Practical Examples
### Example 1: Bug Fix Context
```
Task: "Fix the authentication token expiry bug"
Cycle 1:
DISPATCH: Search for "token", "auth", "expiry" in src/**
EVALUATE: Found auth.ts (0.9), tokens.ts (0.8), user.ts (0.3)
REFINE: Add "refresh", "jwt" keywords; exclude user.ts
Cycle 2:
DISPATCH: Search refined terms
EVALUATE: Found session-manager.ts (0.95), jwt-utils.ts (0.85)
REFINE: Sufficient context (2 high-relevance files)
Result: auth.ts, tokens.ts, session-manager.ts, jwt-utils.ts
```
### Example 2: Feature Implementation
```
Task: "Add rate limiting to API endpoints"
Cycle 1:
DISPATCH: Search "rate", "limit", "api" in routes/**
EVALUATE: No matches - codebase uses "throttle" terminology
REFINE: Add "throttle", "middleware" keywords
Cycle 2:
DISPATCH: Search refined terms
EVALUATE: Found throttle.ts (0.9), middleware/index.ts (0.7)
REFINE: Need router patterns
Cycle 3:
DISPATCH: Search "router", "express" patterns
EVALUATE: Found router-setup.ts (0.8)
REFINE: Sufficient context
Result: throttle.ts, middleware/index.ts, router-setup.ts
```
## Integration with Agents
Use in agent prompts:
```markdown
When retrieving context for this task:
1. Start with broad keyword search
2. Evaluate each file's relevance (0-1 scale)
3. Identify what context is still missing
4. Refine search criteria and repeat (max 3 cycles)
5. Return files with relevance >= 0.7
```
## Best Practices
1. **Start broad, narrow progressively** - Don't over-specify initial queries
2. **Learn codebase terminology** - First cycle often reveals naming conventions
3. **Track what's missing** - Explicit gap identification drives refinement
4. **Stop at "good enough"** - 3 high-relevance files beats 10 mediocre ones
5. **Exclude confidently** - Low-relevance files won't become relevant
## Related
- [The Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - Subagent orchestration section
- `continuous-learning` skill - For patterns that improve over time
- Agent definitions in `~/.claude/agents/`

View File

@@ -0,0 +1,146 @@
---
name: postgres-patterns
description: PostgreSQL database patterns for query optimization, schema design, indexing, and security. Based on Supabase best practices.
---
# PostgreSQL Patterns
Quick reference for PostgreSQL best practices. For detailed guidance, use the `database-reviewer` agent.
## When to Activate
- Writing SQL queries or migrations
- Designing database schemas
- Troubleshooting slow queries
- Implementing Row Level Security
- Setting up connection pooling
## Quick Reference
### Index Cheat Sheet
| Query Pattern | Index Type | Example |
|--------------|------------|---------|
| `WHERE col = value` | B-tree (default) | `CREATE INDEX idx ON t (col)` |
| `WHERE col > value` | B-tree | `CREATE INDEX idx ON t (col)` |
| `WHERE a = x AND b > y` | Composite | `CREATE INDEX idx ON t (a, b)` |
| `WHERE jsonb @> '{}'` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| `WHERE tsv @@ query` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| Time-series ranges | BRIN | `CREATE INDEX idx ON t USING brin (col)` |
### Data Type Quick Reference
| Use Case | Correct Type | Avoid |
|----------|-------------|-------|
| IDs | `bigint` | `int`, random UUID |
| Strings | `text` | `varchar(255)` |
| Timestamps | `timestamptz` | `timestamp` |
| Money | `numeric(10,2)` | `float` |
| Flags | `boolean` | `varchar`, `int` |
### Common Patterns
**Composite Index Order:**
```sql
-- Equality columns first, then range columns
CREATE INDEX idx ON orders (status, created_at);
-- Works for: WHERE status = 'pending' AND created_at > '2024-01-01'
```
**Covering Index:**
```sql
CREATE INDEX idx ON users (email) INCLUDE (name, created_at);
-- Avoids table lookup for SELECT email, name, created_at
```
**Partial Index:**
```sql
CREATE INDEX idx ON users (email) WHERE deleted_at IS NULL;
-- Smaller index, only includes active users
```
**RLS Policy (Optimized):**
```sql
CREATE POLICY policy ON orders
USING ((SELECT auth.uid()) = user_id); -- Wrap in SELECT!
```
**UPSERT:**
```sql
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value;
```
**Cursor Pagination:**
```sql
SELECT * FROM products WHERE id > $last_id ORDER BY id LIMIT 20;
-- O(1) vs OFFSET which is O(n)
```
**Queue Processing:**
```sql
UPDATE jobs SET status = 'processing'
WHERE id = (
SELECT id FROM jobs WHERE status = 'pending'
ORDER BY created_at LIMIT 1
FOR UPDATE SKIP LOCKED
) RETURNING *;
```
### Anti-Pattern Detection
```sql
-- Find unindexed foreign keys
SELECT conrelid::regclass, a.attname
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
WHERE c.contype = 'f'
AND NOT EXISTS (
SELECT 1 FROM pg_index i
WHERE i.indrelid = c.conrelid AND a.attnum = ANY(i.indkey)
);
-- Find slow queries
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
WHERE mean_exec_time > 100
ORDER BY mean_exec_time DESC;
-- Check table bloat
SELECT relname, n_dead_tup, last_vacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
```
### Configuration Template
```sql
-- Connection limits (adjust for RAM)
ALTER SYSTEM SET max_connections = 100;
ALTER SYSTEM SET work_mem = '8MB';
-- Timeouts
ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
ALTER SYSTEM SET statement_timeout = '30s';
-- Monitoring
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Security defaults
REVOKE ALL ON SCHEMA public FROM public;
SELECT pg_reload_conf();
```
## Related
- Agent: `database-reviewer` - Full database review workflow
- Skill: `clickhouse-io` - ClickHouse analytics patterns
- Skill: `backend-patterns` - API and backend patterns
---
*Based on [Supabase Agent Skills](https://github.com/supabase/agent-skills) (MIT License)*

View File

@@ -113,14 +113,35 @@ async function runTests() {
// Run the script
await runScript(path.join(scriptsDir, 'session-end.js'));
// Check if session file was created
// Check if session file was created (default session ID)
// Use local time to match the script's getDateString() function
const sessionsDir = path.join(os.homedir(), '.claude', 'sessions');
const today = new Date().toISOString().split('T')[0];
const sessionFile = path.join(sessionsDir, `${today}-session.tmp`);
const now = new Date();
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
const sessionFile = path.join(sessionsDir, `${today}-default-session.tmp`);
assert.ok(fs.existsSync(sessionFile), 'Session file should exist');
})) passed++; else failed++;
if (await asyncTest('includes session ID in filename', async () => {
const testSessionId = 'test-session-abc12345';
const expectedShortId = 'abc12345'; // Last 8 chars
// Run with custom session ID
await runScript(path.join(scriptsDir, 'session-end.js'), '', {
CLAUDE_SESSION_ID: testSessionId
});
// Check if session file was created with session ID
// Use local time to match the script's getDateString() function
const sessionsDir = path.join(os.homedir(), '.claude', 'sessions');
const now = new Date();
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
const sessionFile = path.join(sessionsDir, `${today}-${expectedShortId}-session.tmp`);
assert.ok(fs.existsSync(sessionFile), `Session file should exist: ${sessionFile}`);
})) passed++; else failed++;
// pre-compact.js tests
console.log('\npre-compact.js:');

View File

@@ -106,6 +106,61 @@ function runTests() {
assert.ok(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dt), `Expected YYYY-MM-DD HH:MM:SS, got ${dt}`);
})) passed++; else failed++;
// Session ID tests
console.log('\nSession ID Functions:');
if (test('getSessionIdShort returns default when no env var', () => {
const originalEnv = process.env.CLAUDE_SESSION_ID;
delete process.env.CLAUDE_SESSION_ID;
try {
const shortId = utils.getSessionIdShort();
assert.strictEqual(shortId, 'default');
} finally {
if (originalEnv) process.env.CLAUDE_SESSION_ID = originalEnv;
}
})) passed++; else failed++;
if (test('getSessionIdShort returns last 8 characters', () => {
const originalEnv = process.env.CLAUDE_SESSION_ID;
process.env.CLAUDE_SESSION_ID = 'test-session-abc12345';
try {
const shortId = utils.getSessionIdShort();
assert.strictEqual(shortId, 'abc12345');
} finally {
if (originalEnv) {
process.env.CLAUDE_SESSION_ID = originalEnv;
} else {
delete process.env.CLAUDE_SESSION_ID;
}
}
})) passed++; else failed++;
if (test('getSessionIdShort uses custom fallback', () => {
const originalEnv = process.env.CLAUDE_SESSION_ID;
delete process.env.CLAUDE_SESSION_ID;
try {
const shortId = utils.getSessionIdShort('custom');
assert.strictEqual(shortId, 'custom');
} finally {
if (originalEnv) process.env.CLAUDE_SESSION_ID = originalEnv;
}
})) passed++; else failed++;
if (test('getSessionIdShort handles short session IDs', () => {
const originalEnv = process.env.CLAUDE_SESSION_ID;
process.env.CLAUDE_SESSION_ID = 'short';
try {
const shortId = utils.getSessionIdShort();
assert.strictEqual(shortId, 'short');
} finally {
if (originalEnv) {
process.env.CLAUDE_SESSION_ID = originalEnv;
} else {
delete process.env.CLAUDE_SESSION_ID;
}
}
})) passed++; else failed++;
// File operations tests
console.log('\nFile Operations:');

354
the-longform-guide.md Normal file
View File

@@ -0,0 +1,354 @@
# The Longform Guide to Everything Claude Code
![Header: The Longform Guide to Everything Claude Code](./assets/images/longform/01-header.png)
---
> **Prerequisite**: This guide builds on [The Shorthand Guide to Everything Claude Code](./the-shortform-guide.md). Read that first if you haven't set up skills, hooks, subagents, MCPs, and plugins.
![Reference to Shorthand Guide](./assets/images/longform/02-shortform-reference.png)
*The Shorthand Guide - read it first*
In the shorthand guide, I covered the foundational setup: skills and commands, hooks, subagents, MCPs, plugins, and the configuration patterns that form the backbone of an effective Claude Code workflow. That was the setup guide and the base infrastructure.
This longform guide goes into the techniques that separate productive sessions from wasteful ones. If you haven't read the shorthand guide, go back and set up your configs first. What follows assumes you have skills, agents, hooks, and MCPs already configured and working.
The themes here: token economics, memory persistence, verification patterns, parallelization strategies, and the compound effects of building reusable workflows. These are the patterns I've refined over 10+ months of daily use that make the difference between being plagued by context rot within the first hour, versus maintaining productive sessions for hours.
Everything covered in the shorthand and longform guides is available on GitHub: `github.com/affaan-m/everything-claude-code`
---
## Tips and Tricks
### Some MCPs are Replaceable and Will Free Up Your Context Window
For MCPs such as version control (GitHub), databases (Supabase), deployment (Vercel, Railway) etc. - most of these platforms already have robust CLIs that the MCP is essentially just wrapping. The MCP is a nice wrapper but it comes at a cost.
To have the CLI function more like an MCP without actually using the MCP (and the decreased context window that comes with it), consider bundling the functionality into skills and commands. Strip out the tools the MCP exposes that make things easy and turn those into commands.
Example: instead of having the GitHub MCP loaded at all times, create a `/gh-pr` command that wraps `gh pr create` with your preferred options. Instead of the Supabase MCP eating context, create skills that use the Supabase CLI directly.
With lazy loading, the context window issue is mostly solved. But token usage and cost is not solved in the same way. The CLI + skills approach is still a token optimization method.
---
## IMPORTANT STUFF
### Context and Memory Management
For sharing memory across sessions, a skill or command that summarizes and checks in on progress then saves to a `.tmp` file in your `.claude` folder and appends to it until the end of your session is the best bet. The next day it can use that as context and pick up where you left off, create a new file for each session so you don't pollute old context into new work.
![Session Storage File Tree](./assets/images/longform/03-session-storage.png)
*Example of session storage -> https://github.com/affaan-m/everything-claude-code/tree/main/examples/sessions*
Claude creates a file summarizing current state. Review it, ask for edits if needed, then start fresh. For the new conversation, just provide the file path. Particularly useful when you're hitting context limits and need to continue complex work. These files should contain:
- What approaches worked (verifiably with evidence)
- Which approaches were attempted but did not work
- Which approaches have not been attempted and what's left to do
**Clearing Context Strategically:**
Once you have your plan set and context cleared (default option in plan mode in Claude Code now), you can work from the plan. This is useful when you've accumulated a lot of exploration context that's no longer relevant to execution. For strategic compacting, disable auto compact. Manually compact at logical intervals or create a skill that does so for you.
**Advanced: Dynamic System Prompt Injection**
One pattern I picked up: instead of solely putting everything in CLAUDE.md (user scope) or `.claude/rules/` (project scope) which loads every session, use CLI flags to inject context dynamically.
```bash
claude --system-prompt "$(cat memory.md)"
```
This lets you be more surgical about what context loads when. System prompt content has higher authority than user messages, which have higher authority than tool results.
**Practical setup:**
```bash
# Daily development
alias claude-dev='claude --system-prompt "$(cat ~/.claude/contexts/dev.md)"'
# PR review mode
alias claude-review='claude --system-prompt "$(cat ~/.claude/contexts/review.md)"'
# Research/exploration mode
alias claude-research='claude --system-prompt "$(cat ~/.claude/contexts/research.md)"'
```
**Advanced: Memory Persistence Hooks**
There are hooks most people don't know about that help with memory:
- **PreCompact Hook**: Before context compaction happens, save important state to a file
- **Stop Hook (Session End)**: On session end, persist learnings to a file
- **SessionStart Hook**: On new session, load previous context automatically
I've built these hooks and they're in the repo at `github.com/affaan-m/everything-claude-code/tree/main/hooks/memory-persistence`
---
### Continuous Learning / Memory
If you've had to repeat a prompt multiple times and Claude ran into the same problem or gave you a response you've heard before - those patterns must be appended to skills.
**The Problem:** Wasted tokens, wasted context, wasted time.
**The Solution:** When Claude Code discovers something that isn't trivial - a debugging technique, a workaround, some project-specific pattern - it saves that knowledge as a new skill. Next time a similar problem comes up, the skill gets loaded automatically.
I've built a continuous learning skill that does this: `github.com/affaan-m/everything-claude-code/tree/main/skills/continuous-learning`
**Why Stop Hook (Not UserPromptSubmit):**
The key design decision is using a **Stop hook** instead of UserPromptSubmit. UserPromptSubmit runs on every single message - adds latency to every prompt. Stop runs once at session end - lightweight, doesn't slow you down during the session.
---
### Token Optimization
**Primary Strategy: Subagent Architecture**
Optimize the tools you use and subagent architecture designed to delegate the cheapest possible model that is sufficient for the task.
**Model Selection Quick Reference:**
![Model Selection Table](./assets/images/longform/04-model-selection.png)
*Hypothetical setup of subagents on various common tasks and reasoning behind the choices*
| Task Type | Model | Why |
| ------------------------- | ------ | ------------------------------------------ |
| Exploration/search | Haiku | Fast, cheap, good enough for finding files |
| Simple edits | Haiku | Single-file changes, clear instructions |
| Multi-file implementation | Sonnet | Best balance for coding |
| Complex architecture | Opus | Deep reasoning needed |
| PR reviews | Sonnet | Understands context, catches nuance |
| Security analysis | Opus | Can't afford to miss vulnerabilities |
| Writing docs | Haiku | Structure is simple |
| Debugging complex bugs | Opus | Needs to hold entire system in mind |
Default to Sonnet for 90% of coding tasks. Upgrade to Opus when first attempt failed, task spans 5+ files, architectural decisions, or security-critical code.
**Pricing Reference:**
![Claude Model Pricing](./assets/images/longform/05-pricing-table.png)
*Source: https://platform.claude.com/docs/en/about-claude/pricing*
**Tool-Specific Optimizations:**
Replace grep with mgrep - ~50% token reduction on average compared to traditional grep or ripgrep:
![mgrep Benchmark](./assets/images/longform/06-mgrep-benchmark.png)
*In our 50-task benchmark, mgrep + Claude Code used ~2x fewer tokens than grep-based workflows at similar or better judged quality. Source: https://github.com/mixedbread-ai/mgrep*
**Modular Codebase Benefits:**
Having a more modular codebase with main files being in the hundreds of lines instead of thousands of lines helps both in token optimization costs and getting a task done right on the first try.
---
### Verification Loops and Evals
**Benchmarking Workflow:**
Compare asking for the same thing with and without a skill and checking the output difference:
Fork the conversation, initiate a new worktree in one of them without the skill, pull up a diff at the end, see what was logged.
**Eval Pattern Types:**
- **Checkpoint-Based Evals**: Set explicit checkpoints, verify against defined criteria, fix before proceeding
- **Continuous Evals**: Run every N minutes or after major changes, full test suite + lint
**Key Metrics:**
```
pass@k: At least ONE of k attempts succeeds
k=1: 70% k=3: 91% k=5: 97%
pass^k: ALL k attempts must succeed
k=1: 70% k=3: 34% k=5: 17%
```
Use **pass@k** when you just need it to work. Use **pass^k** when consistency is essential.
---
## PARALLELIZATION
When forking conversations in a multi-Claude terminal setup, make sure the scope is well-defined for the actions in the fork and the original conversation. Aim for minimal overlap when it comes to code changes.
**My Preferred Pattern:**
Main chat for code changes, forks for questions about the codebase and its current state, or research on external services.
**On Arbitrary Terminal Counts:**
![Boris on Parallel Terminals](./assets/images/longform/07-boris-parallel.png)
*Boris (Anthropic) on running multiple Claude instances*
Boris has tips on parallelization. He's suggested things like running 5 Claude instances locally and 5 upstream. I advise against setting arbitrary terminal amounts. The addition of a terminal should be out of true necessity.
Your goal should be: **how much can you get done with the minimum viable amount of parallelization.**
**Git Worktrees for Parallel Instances:**
```bash
# Create worktrees for parallel work
git worktree add ../project-feature-a feature-a
git worktree add ../project-feature-b feature-b
git worktree add ../project-refactor refactor-branch
# Each worktree gets its own Claude instance
cd ../project-feature-a && claude
```
IF you are to begin scaling your instances AND you have multiple instances of Claude working on code that overlaps with one another, it's imperative you use git worktrees and have a very well-defined plan for each. Use `/rename <name here>` to name all your chats.
![Two Terminal Setup](./assets/images/longform/08-two-terminals.png)
*Starting Setup: Left Terminal for Coding, Right Terminal for Questions - use /rename and /fork*
**The Cascade Method:**
When running multiple Claude Code instances, organize with a "cascade" pattern:
- Open new tasks in new tabs to the right
- Sweep left to right, oldest to newest
- Focus on at most 3-4 tasks at a time
---
## GROUNDWORK
**The Two-Instance Kickoff Pattern:**
For my own workflow management, I like to start an empty repo with 2 open Claude instances.
**Instance 1: Scaffolding Agent**
- Lays down the scaffold and groundwork
- Creates project structure
- Sets up configs (CLAUDE.md, rules, agents)
**Instance 2: Deep Research Agent**
- Connects to all your services, web search
- Creates the detailed PRD
- Creates architecture mermaid diagrams
- Compiles the references with actual documentation clips
**llms.txt Pattern:**
If available, you can find an `llms.txt` on many documentation references by doing `/llms.txt` on them once you reach their docs page. This gives you a clean, LLM-optimized version of the documentation.
**Philosophy: Build Reusable Patterns**
From @omarsar0: "Early on, I spent time building reusable workflows/patterns. Tedious to build, but this had a wild compounding effect as models and agent harnesses improved."
**What to invest in:**
- Subagents
- Skills
- Commands
- Planning patterns
- MCP tools
- Context engineering patterns
---
## Best Practices for Agents & Sub-Agents
**The Sub-Agent Context Problem:**
Sub-agents exist to save context by returning summaries instead of dumping everything. But the orchestrator has semantic context the sub-agent lacks. The sub-agent only knows the literal query, not the PURPOSE behind the request.
**Iterative Retrieval Pattern:**
1. Orchestrator evaluates every sub-agent return
2. Ask follow-up questions before accepting it
3. Sub-agent goes back to source, gets answers, returns
4. Loop until sufficient (max 3 cycles)
**Key:** Pass objective context, not just the query.
**Orchestrator with Sequential Phases:**
```markdown
Phase 1: RESEARCH (use Explore agent) → research-summary.md
Phase 2: PLAN (use planner agent) → plan.md
Phase 3: IMPLEMENT (use tdd-guide agent) → code changes
Phase 4: REVIEW (use code-reviewer agent) → review-comments.md
Phase 5: VERIFY (use build-error-resolver if needed) → done or loop back
```
**Key rules:**
1. Each agent gets ONE clear input and produces ONE clear output
2. Outputs become inputs for next phase
3. Never skip phases
4. Use `/clear` between agents
5. Store intermediate outputs in files
---
## FUN STUFF / NOT CRITICAL JUST FUN TIPS
### Custom Status Line
You can set it using `/statusline` - then Claude will say you don't have one but can set it up for you and ask what you want in it.
See also: https://github.com/sirmalloc/ccstatusline
### Voice Transcription
Talk to Claude Code with your voice. Faster than typing for many people.
- superwhisper, MacWhisper on Mac
- Even with transcription mistakes, Claude understands intent
### Terminal Aliases
```bash
alias c='claude'
alias gb='github'
alias co='code'
alias q='cd ~/Desktop/projects'
```
---
## Milestone
![25k+ GitHub Stars](./assets/images/longform/09-25k-stars.png)
*25,000+ GitHub stars in under a week*
---
## Resources
**Agent Orchestration:**
- https://github.com/ruvnet/claude-flow - Enterprise orchestration platform with 54+ specialized agents
**Self-Improving Memory:**
- https://github.com/affaan-m/everything-claude-code/tree/main/skills/continuous-learning
- rlancemartin.github.io/2025/12/01/claude_diary/ - Session reflection pattern
**System Prompts Reference:**
- https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools - Collection of system prompts (110k stars)
**Official:**
- Anthropic Academy: anthropic.skilljar.com
---
## References
- [Anthropic: Demystifying evals for AI agents](https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents)
- [YK: 32 Claude Code Tips](https://agenticcoding.substack.com/p/32-claude-code-tips-from-basics-to)
- [RLanceMartin: Session Reflection Pattern](https://rlancemartin.github.io/2025/12/01/claude_diary/)
- @PerceptualPeak: Sub-Agent Context Negotiation
- @menhguin: Agent Abstractions Tierlist
- @omarsar0: Compound Effects Philosophy
---
*Everything covered in both guides is available on GitHub at [everything-claude-code](https://github.com/affaan-m/everything-claude-code)*

431
the-shortform-guide.md Normal file
View File

@@ -0,0 +1,431 @@
# The Shorthand Guide to Everything Claude Code
![Header: Anthropic Hackathon Winner - Tips & Tricks for Claude Code](./assets/images/shortform/01-hackathon-tweet.png)
*Won the Anthropic x Forum Ventures hackathon with [zenith.chat](https://zenith.chat) alongside [@DRodriguezFX](https://x.com/DRodriguezFX)*
---
**Been an avid Claude Code user since the experimental rollout in Feb, and won the Anthropic x Forum Ventures hackathon with [zenith.chat](https://zenith.chat) alongside [@DRodriguezFX](https://x.com/DRodriguezFX) - completely using Claude Code.**
Here's my complete setup after 10 months of daily use: skills, hooks, subagents, MCPs, plugins, and what actually works.
---
## Skills and Commands
Skills operate like rules, constricted to certain scopes and workflows. They're shorthand to prompts when you need to execute a particular workflow.
After a long session of coding with Opus 4.5, you want to clean out dead code and loose .md files? Run `/refactor-clean`. Need testing? `/tdd`, `/e2e`, `/test-coverage`. Skills can also include codemaps - a way for Claude to quickly navigate your codebase without burning context on exploration.
![Terminal showing chained commands](./assets/images/shortform/02-chaining-commands.jpeg)
*Chaining commands together*
Commands are skills executed via slash commands. They overlap but are stored differently:
- **Skills**: `~/.claude/skills/` - broader workflow definitions
- **Commands**: `~/.claude/commands/` - quick executable prompts
```bash
# Example skill structure
~/.claude/skills/
pmx-guidelines.md # Project-specific patterns
coding-standards.md # Language best practices
tdd-workflow/ # Multi-file skill with README.md
security-review/ # Checklist-based skill
```
---
## Hooks
Hooks are trigger-based automations that fire on specific events. Unlike skills, they're constricted to tool calls and lifecycle events.
**Hook Types:**
1. **PreToolUse** - Before a tool executes (validation, reminders)
2. **PostToolUse** - After a tool finishes (formatting, feedback loops)
3. **UserPromptSubmit** - When you send a message
4. **Stop** - When Claude finishes responding
5. **PreCompact** - Before context compaction
6. **Notification** - Permission requests
**Example: tmux reminder before long-running commands**
```json
{
"PreToolUse": [
{
"matcher": "tool == \"Bash\" && tool_input.command matches \"(npm|pnpm|yarn|cargo|pytest)\"",
"hooks": [
{
"type": "command",
"command": "if [ -z \"$TMUX\" ]; then echo '[Hook] Consider tmux for session persistence' >&2; fi"
}
]
}
]
}
```
![PostToolUse hook feedback](./assets/images/shortform/03-posttooluse-hook.png)
*Example of what feedback you get in Claude Code, while running a PostToolUse hook*
**Pro tip:** Use the `hookify` plugin to create hooks conversationally instead of writing JSON manually. Run `/hookify` and describe what you want.
---
## Subagents
Subagents are processes your orchestrator (main Claude) can delegate tasks to with limited scopes. They can run in background or foreground, freeing up context for the main agent.
Subagents work nicely with skills - a subagent capable of executing a subset of your skills can be delegated tasks and use those skills autonomously. They can also be sandboxed with specific tool permissions.
```bash
# Example subagent structure
~/.claude/agents/
planner.md # Feature implementation planning
architect.md # System design decisions
tdd-guide.md # Test-driven development
code-reviewer.md # Quality/security review
security-reviewer.md # Vulnerability analysis
build-error-resolver.md
e2e-runner.md
refactor-cleaner.md
```
Configure allowed tools, MCPs, and permissions per subagent for proper scoping.
---
## Rules and Memory
Your `.rules` folder holds `.md` files with best practices Claude should ALWAYS follow. Two approaches:
1. **Single CLAUDE.md** - Everything in one file (user or project level)
2. **Rules folder** - Modular `.md` files grouped by concern
```bash
~/.claude/rules/
security.md # No hardcoded secrets, validate inputs
coding-style.md # Immutability, file organization
testing.md # TDD workflow, 80% coverage
git-workflow.md # Commit format, PR process
agents.md # When to delegate to subagents
performance.md # Model selection, context management
```
**Example rules:**
- No emojis in codebase
- Refrain from purple hues in frontend
- Always test code before deployment
- Prioritize modular code over mega-files
- Never commit console.logs
---
## MCPs (Model Context Protocol)
MCPs connect Claude to external services directly. Not a replacement for APIs - it's a prompt-driven wrapper around them, allowing more flexibility in navigating information.
**Example:** Supabase MCP lets Claude pull specific data, run SQL directly upstream without copy-paste. Same for databases, deployment platforms, etc.
![Supabase MCP listing tables](./assets/images/shortform/04-supabase-mcp.jpeg)
*Example of the Supabase MCP listing the tables within the public schema*
**Chrome in Claude:** is a built-in plugin MCP that lets Claude autonomously control your browser - clicking around to see how things work.
**CRITICAL: Context Window Management**
Be picky with MCPs. I keep all MCPs in user config but **disable everything unused**. Navigate to `/plugins` and scroll down or run `/mcp`.
![/plugins interface](./assets/images/shortform/05-plugins-interface.jpeg)
*Using /plugins to navigate to MCPs to see which ones are currently installed and their status*
Your 200k context window before compacting might only be 70k with too many tools enabled. Performance degrades significantly.
**Rule of thumb:** Have 20-30 MCPs in config, but keep under 10 enabled / under 80 tools active.
```bash
# Check enabled MCPs
/mcp
# Disable unused ones in ~/.claude.json under projects.disabledMcpServers
```
---
## Plugins
Plugins package tools for easy installation instead of tedious manual setup. A plugin can be a skill + MCP combined, or hooks/tools bundled together.
**Installing plugins:**
```bash
# Add a marketplace
claude plugin marketplace add https://github.com/mixedbread-ai/mgrep
# Open Claude, run /plugins, find new marketplace, install from there
```
![Marketplaces tab showing mgrep](./assets/images/shortform/06-marketplaces-mgrep.jpeg)
*Displaying the newly installed Mixedbread-Grep marketplace*
**LSP Plugins** are particularly useful if you run Claude Code outside editors frequently. Language Server Protocol gives Claude real-time type checking, go-to-definition, and intelligent completions without needing an IDE open.
```bash
# Enabled plugins example
typescript-lsp@claude-plugins-official # TypeScript intelligence
pyright-lsp@claude-plugins-official # Python type checking
hookify@claude-plugins-official # Create hooks conversationally
mgrep@Mixedbread-Grep # Better search than ripgrep
```
Same warning as MCPs - watch your context window.
---
## Tips and Tricks
### Keyboard Shortcuts
- `Ctrl+U` - Delete entire line (faster than backspace spam)
- `!` - Quick bash command prefix
- `@` - Search for files
- `/` - Initiate slash commands
- `Shift+Enter` - Multi-line input
- `Tab` - Toggle thinking display
- `Esc Esc` - Interrupt Claude / restore code
### Parallel Workflows
- **Fork** (`/fork`) - Fork conversations to do non-overlapping tasks in parallel instead of spamming queued messages
- **Git Worktrees** - For overlapping parallel Claudes without conflicts. Each worktree is an independent checkout
```bash
git worktree add ../feature-branch feature-branch
# Now run separate Claude instances in each worktree
```
### tmux for Long-Running Commands
Stream and watch logs/bash processes Claude runs:
https://github.com/user-attachments/assets/shortform/07-tmux-video.mp4
```bash
tmux new -s dev
# Claude runs commands here, you can detach and reattach
tmux attach -t dev
```
### mgrep > grep
`mgrep` is a significant improvement from ripgrep/grep. Install via plugin marketplace, then use the `/mgrep` skill. Works with both local search and web search.
```bash
mgrep "function handleSubmit" # Local search
mgrep --web "Next.js 15 app router changes" # Web search
```
### Other Useful Commands
- `/rewind` - Go back to a previous state
- `/statusline` - Customize with branch, context %, todos
- `/checkpoints` - File-level undo points
- `/compact` - Manually trigger context compaction
### GitHub Actions CI/CD
Set up code review on your PRs with GitHub Actions. Claude can review PRs automatically when configured.
![Claude bot approving a PR](./assets/images/shortform/08-github-pr-review.jpeg)
*Claude approving a bug fix PR*
### Sandboxing
Use sandbox mode for risky operations - Claude runs in restricted environment without affecting your actual system.
---
## On Editors
Your editor choice significantly impacts Claude Code workflow. While Claude Code works from any terminal, pairing it with a capable editor unlocks real-time file tracking, quick navigation, and integrated command execution.
### Zed (My Preference)
I use [Zed](https://zed.dev) - written in Rust, so it's genuinely fast. Opens instantly, handles massive codebases without breaking a sweat, and barely touches system resources.
**Why Zed + Claude Code is a great combo:**
- **Speed** - Rust-based performance means no lag when Claude is rapidly editing files. Your editor keeps up
- **Agent Panel Integration** - Zed's Claude integration lets you track file changes in real-time as Claude edits. Jump between files Claude references without leaving the editor
- **CMD+Shift+R Command Palette** - Quick access to all your custom slash commands, debuggers, build scripts in a searchable UI
- **Minimal Resource Usage** - Won't compete with Claude for RAM/CPU during heavy operations. Important when running Opus
- **Vim Mode** - Full vim keybindings if that's your thing
![Zed Editor with custom commands](./assets/images/shortform/09-zed-editor.jpeg)
*Zed Editor with custom commands dropdown using CMD+Shift+R. Following mode shown as the bullseye in the bottom right.*
**Editor-Agnostic Tips:**
1. **Split your screen** - Terminal with Claude Code on one side, editor on the other
2. **Ctrl + G** - quickly open the file Claude is currently working on in Zed
3. **Auto-save** - Enable autosave so Claude's file reads are always current
4. **Git integration** - Use editor's git features to review Claude's changes before committing
5. **File watchers** - Most editors auto-reload changed files, verify this is enabled
### VSCode / Cursor
This is also a viable choice and works well with Claude Code. You can use it in either terminal format, with automatic sync with your editor using `\ide` enabling LSP functionality (somewhat redundant with plugins now). Or you can opt for the extension which is more integrated with the Editor and has a matching UI.
![VS Code Claude Code Extension](./assets/images/shortform/10-vscode-extension.jpeg)
*The VS Code extension provides a native graphical interface for Claude Code, integrated directly into your IDE.*
---
## My Setup
### Plugins
**Installed:** (I usually only have 4-5 of these enabled at a time)
```markdown
ralph-wiggum@claude-code-plugins # Loop automation
frontend-design@claude-code-plugins # UI/UX patterns
commit-commands@claude-code-plugins # Git workflow
security-guidance@claude-code-plugins # Security checks
pr-review-toolkit@claude-code-plugins # PR automation
typescript-lsp@claude-plugins-official # TS intelligence
hookify@claude-plugins-official # Hook creation
code-simplifier@claude-plugins-official
feature-dev@claude-code-plugins
explanatory-output-style@claude-code-plugins
code-review@claude-code-plugins
context7@claude-plugins-official # Live documentation
pyright-lsp@claude-plugins-official # Python types
mgrep@Mixedbread-Grep # Better search
```
### MCP Servers
**Configured (User Level):**
```json
{
"github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] },
"firecrawl": { "command": "npx", "args": ["-y", "firecrawl-mcp"] },
"supabase": {
"command": "npx",
"args": ["-y", "@supabase/mcp-server-supabase@latest", "--project-ref=YOUR_REF"]
},
"memory": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-memory"] },
"sequential-thinking": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
},
"vercel": { "type": "http", "url": "https://mcp.vercel.com" },
"railway": { "command": "npx", "args": ["-y", "@railway/mcp-server"] },
"cloudflare-docs": { "type": "http", "url": "https://docs.mcp.cloudflare.com/mcp" },
"cloudflare-workers-bindings": {
"type": "http",
"url": "https://bindings.mcp.cloudflare.com/mcp"
},
"clickhouse": { "type": "http", "url": "https://mcp.clickhouse.cloud/mcp" },
"AbletonMCP": { "command": "uvx", "args": ["ableton-mcp"] },
"magic": { "command": "npx", "args": ["-y", "@magicuidesign/mcp@latest"] }
}
```
This is the key - I have 14 MCPs configured but only ~5-6 enabled per project. Keeps context window healthy.
### Key Hooks
```json
{
"PreToolUse": [
{ "matcher": "npm|pnpm|yarn|cargo|pytest", "hooks": ["tmux reminder"] },
{ "matcher": "Write && .md file", "hooks": ["block unless README/CLAUDE"] },
{ "matcher": "git push", "hooks": ["open editor for review"] }
],
"PostToolUse": [
{ "matcher": "Edit && .ts/.tsx/.js/.jsx", "hooks": ["prettier --write"] },
{ "matcher": "Edit && .ts/.tsx", "hooks": ["tsc --noEmit"] },
{ "matcher": "Edit", "hooks": ["grep console.log warning"] }
],
"Stop": [
{ "matcher": "*", "hooks": ["check modified files for console.log"] }
]
}
```
### Custom Status Line
Shows user, directory, git branch with dirty indicator, context remaining %, model, time, and todo count:
![Custom status line](./assets/images/shortform/11-statusline.jpeg)
*Example statusline in my Mac root directory*
```
affoon:~ ctx:65% Opus 4.5 19:52
▌▌ plan mode on (shift+tab to cycle)
```
### Rules Structure
```
~/.claude/rules/
security.md # Mandatory security checks
coding-style.md # Immutability, file size limits
testing.md # TDD, 80% coverage
git-workflow.md # Conventional commits
agents.md # Subagent delegation rules
patterns.md # API response formats
performance.md # Model selection (Haiku vs Sonnet vs Opus)
hooks.md # Hook documentation
```
### Subagents
```
~/.claude/agents/
planner.md # Break down features
architect.md # System design
tdd-guide.md # Write tests first
code-reviewer.md # Quality review
security-reviewer.md # Vulnerability scan
build-error-resolver.md
e2e-runner.md # Playwright tests
refactor-cleaner.md # Dead code removal
doc-updater.md # Keep docs synced
```
---
## Key Takeaways
1. **Don't overcomplicate** - treat configuration like fine-tuning, not architecture
2. **Context window is precious** - disable unused MCPs and plugins
3. **Parallel execution** - fork conversations, use git worktrees
4. **Automate the repetitive** - hooks for formatting, linting, reminders
5. **Scope your subagents** - limited tools = focused execution
---
## References
- [Plugins Reference](https://code.claude.com/docs/en/plugins-reference)
- [Hooks Documentation](https://code.claude.com/docs/en/hooks)
- [Checkpointing](https://code.claude.com/docs/en/checkpointing)
- [Interactive Mode](https://code.claude.com/docs/en/interactive-mode)
- [Memory System](https://code.claude.com/docs/en/memory)
- [Subagents](https://code.claude.com/docs/en/sub-agents)
- [MCP Overview](https://code.claude.com/docs/en/mcp-overview)
---
**Note:** This is a subset of detail. See the [Longform Guide](./the-longform-guide.md) for advanced patterns.
---
*Won the Anthropic x Forum Ventures hackathon in NYC building [zenith.chat](https://zenith.chat) with [@DRodriguezFX](https://x.com/DRodriguezFX)*