Initial commit
This commit is contained in:
11
.claude-plugin/plugin.json
Normal file
11
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "neills-skills",
|
||||||
|
"description": "Production-grade skills for Claude Code: ExecPlan for structured planning and REPL-Driven Development for Clojure 1.12",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Neill Killgore"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# neills-skills
|
||||||
|
|
||||||
|
Production-grade skills for Claude Code: ExecPlan for structured planning and REPL-Driven Development for Clojure 1.12
|
||||||
49
plugin.lock.json
Normal file
49
plugin.lock.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:neill-k/cc-skills:",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "046a586f5a38d0c5580e618fba2e19a256c4074c",
|
||||||
|
"treeHash": "cca08e0b38f39ea10d2503887c8fcc1db12723a7f73cd9ab98c69c183a5e7be6",
|
||||||
|
"generatedAt": "2025-11-28T10:27:17.829789Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "neills-skills",
|
||||||
|
"description": "Production-grade skills for Claude Code: ExecPlan for structured planning and REPL-Driven Development for Clojure 1.12",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "960d621743412c6593abd4b5391c5c367b68ae13cb7f3606db1b0b4f81825b93"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "9fd8f74279a3c92bd2467453e6aa29344ee6bcdcef4a78830a9e7c0f72cc639f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/development/exec-plan.md",
|
||||||
|
"sha256": "89be573ab5baf01e19f755afcefc0447890498c63d6f0e3bddcc806dc421f6c9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/development/repl-driven-clojure.md",
|
||||||
|
"sha256": "8d30e1e9d5f2a1f9b81f8369e4ef18ccb7c59627f43ead80d01bc150dd39c766"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "cca08e0b38f39ea10d2503887c8fcc1db12723a7f73cd9ab98c69c183a5e7be6"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
673
skills/development/exec-plan.md
Normal file
673
skills/development/exec-plan.md
Normal file
@@ -0,0 +1,673 @@
|
|||||||
|
---
|
||||||
|
name: exec-plan
|
||||||
|
description: Create structured, living work plans for complex coding tasks with progress tracking, decision logs, and verification steps
|
||||||
|
---
|
||||||
|
|
||||||
|
# ExecPlan Skill
|
||||||
|
|
||||||
|
When the user requests help with a complex feature, refactoring, or significant code change, use this skill to create a structured work plan that serves as a living document throughout execution.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
1. **Structured Planning**: Break down complex work into concrete, verifiable steps
|
||||||
|
2. **Living Document**: Update the plan continuously as work progresses
|
||||||
|
3. **Decision Capture**: Log important choices and rationale
|
||||||
|
4. **Progress Visibility**: Track completion with timestamps and status
|
||||||
|
5. **Learning Record**: Document surprises, discoveries, and lessons learned
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Activate this skill when the user:
|
||||||
|
- Starts a new feature implementation
|
||||||
|
- Plans a significant refactoring
|
||||||
|
- Requests help with a complex bug fix
|
||||||
|
- Needs to coordinate multi-file changes
|
||||||
|
- Wants a structured approach to development
|
||||||
|
- Asks "how should I approach this?"
|
||||||
|
- Mentions planning, roadmap, or execution strategy
|
||||||
|
|
||||||
|
## ExecPlan Framework
|
||||||
|
|
||||||
|
An ExecPlan is a **task-scoped work plan** that becomes shared vocabulary between you and the user. It structures how features get built, providing clarity and reducing cognitive load.
|
||||||
|
|
||||||
|
### Document Structure
|
||||||
|
|
||||||
|
Every ExecPlan should contain these sections:
|
||||||
|
|
||||||
|
1. **Metadata**: Ownership, dates, related links
|
||||||
|
2. **Short Description**: One-sentence outcome statement
|
||||||
|
3. **Purpose / Big Picture**: User or system gains with measurable outcomes
|
||||||
|
4. **Context and Orientation**: Current state, file locations, terminology
|
||||||
|
5. **Plan of Work**: Concrete steps with verification
|
||||||
|
6. **Progress**: Checkbox-tracked steps with timestamps
|
||||||
|
7. **Surprises & Discoveries**: Unexpected findings and optimizations
|
||||||
|
8. **Decision Log**: Material decisions with rationale
|
||||||
|
9. **Outcomes & Retrospective**: Results and lessons learned
|
||||||
|
10. **Risks / Open Questions**: (Optional) Assumptions and metrics
|
||||||
|
11. **Next Steps / Handoff Notes**: (Optional) Remaining work
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# ExecPlan: [Feature/Task Name]
|
||||||
|
|
||||||
|
> **Living design and execution record**
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
- **Owner**: [Your name or team]
|
||||||
|
- **Created**: [YYYY-MM-DD]
|
||||||
|
- **Last Updated**: [YYYY-MM-DD HH:MM UTC]
|
||||||
|
- **Agent Path**: [e.g., .agents/feature-name/]
|
||||||
|
- **Related Plans**: [Links to related plans or issues]
|
||||||
|
- **Status**: 🟡 In Progress | 🟢 Complete | 🔴 Blocked
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Short Description
|
||||||
|
|
||||||
|
[One sentence: What new behavior or improvement will exist when complete?]
|
||||||
|
|
||||||
|
Example: "Users can now filter search results by date range with sub-second response times."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose / Big Picture
|
||||||
|
|
||||||
|
**What does the user or system gain?**
|
||||||
|
|
||||||
|
[Explain measurable outcomes and benefits]
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
- [ ] Criterion 1 with measurable target
|
||||||
|
- [ ] Criterion 2 with measurable target
|
||||||
|
- [ ] Criterion 3 with measurable target
|
||||||
|
|
||||||
|
Example: "Users can ask multi-turn clarifications within 2s latency, improving task completion rates by 30%."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context and Orientation
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
[Summarize the existing system, its limitations, and why this work is needed]
|
||||||
|
|
||||||
|
**Key Files:**
|
||||||
|
- `path/to/file1.ext` - Current role and limitations
|
||||||
|
- `path/to/file2.ext` - Current role and limitations
|
||||||
|
- `path/to/file3.ext` - Current role and limitations
|
||||||
|
|
||||||
|
**Domain Terminology:**
|
||||||
|
- **Term 1**: Definition (don't assume prior knowledge)
|
||||||
|
- **Term 2**: Definition
|
||||||
|
- **Term 3**: Definition
|
||||||
|
|
||||||
|
**Assumptions:**
|
||||||
|
- Assumption 1
|
||||||
|
- Assumption 2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plan of Work
|
||||||
|
|
||||||
|
### Phase 1: [Phase Name]
|
||||||
|
|
||||||
|
**1. [Concrete Action]**
|
||||||
|
- **Files affected**: `path/to/file.ext`
|
||||||
|
- **Expected effect**: What will change
|
||||||
|
- **Verification**: How to confirm it works
|
||||||
|
- **Estimated time**: X hours
|
||||||
|
|
||||||
|
**2. [Next Action]**
|
||||||
|
- **Files affected**: `path/to/files.ext`
|
||||||
|
- **Expected effect**: What will change
|
||||||
|
- **Verification**: How to confirm it works
|
||||||
|
- **Estimated time**: Y hours
|
||||||
|
|
||||||
|
### Phase 2: [Phase Name]
|
||||||
|
|
||||||
|
**3. [Action]**
|
||||||
|
...
|
||||||
|
|
||||||
|
### Phase 3: Testing & Validation
|
||||||
|
|
||||||
|
**X. [Test Action]**
|
||||||
|
- **Test coverage**: What scenarios to test
|
||||||
|
- **Expected results**: Pass criteria
|
||||||
|
- **Verification**: Test commands to run
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
**Overall**: ⚪⚪⚪⚪⚪⚪⚪⚪⚪⚪ 0% (0/10 steps)
|
||||||
|
|
||||||
|
- [ ] **Step 1**: [Description]
|
||||||
|
- Status: Pending
|
||||||
|
- Started: —
|
||||||
|
- Completed: —
|
||||||
|
|
||||||
|
- [x] **Step 2**: [Description]
|
||||||
|
- Status: ✅ Complete
|
||||||
|
- Started: 2025-01-10 14:22 UTC
|
||||||
|
- Completed: 2025-01-10 15:45 UTC
|
||||||
|
- Notes: [Any relevant notes]
|
||||||
|
|
||||||
|
- [ ] **Step 3**: [Description]
|
||||||
|
- Status: 🟡 In Progress (60%)
|
||||||
|
- Started: 2025-01-10 15:50 UTC
|
||||||
|
- Completed: —
|
||||||
|
- Notes: Blocked by API rate limit issue
|
||||||
|
|
||||||
|
[Continue for all steps...]
|
||||||
|
|
||||||
|
**Velocity**: [X steps per day/hour, if tracking]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Surprises & Discoveries
|
||||||
|
|
||||||
|
**[YYYY-MM-DD]**: [Discovery Title]
|
||||||
|
- **What**: What we found
|
||||||
|
- **Why it matters**: Impact on the plan
|
||||||
|
- **Action taken**: What we did about it
|
||||||
|
- **Evidence**: Links, metrics, or observations
|
||||||
|
|
||||||
|
Example:
|
||||||
|
**2025-01-10**: Database query N+1 problem in user list
|
||||||
|
- **What**: Found existing user list endpoint making 100+ queries per request
|
||||||
|
- **Why it matters**: 5s+ response time blocking this feature
|
||||||
|
- **Action taken**: Added eager loading, reduced to 2 queries
|
||||||
|
- **Evidence**: Response time dropped from 5.2s to 0.3s (see PR #123)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision Log
|
||||||
|
|
||||||
|
**[YYYY-MM-DD]**: [Decision Title]
|
||||||
|
- **Decision**: What was decided
|
||||||
|
- **Rationale**: Why this choice over alternatives
|
||||||
|
- **Alternatives considered**: What else was evaluated
|
||||||
|
- **Trade-offs**: What we're giving up
|
||||||
|
- **Decided by**: [Name/role]
|
||||||
|
|
||||||
|
Example:
|
||||||
|
**2025-01-10**: Use Redis for caching instead of in-memory cache
|
||||||
|
- **Decision**: Implement Redis-based caching layer
|
||||||
|
- **Rationale**: Need shared cache across multiple servers
|
||||||
|
- **Alternatives considered**: In-memory cache (doesn't scale), Memcached (less feature-rich)
|
||||||
|
- **Trade-offs**: Additional infrastructure dependency, slight latency increase (2ms)
|
||||||
|
- **Decided by**: Engineering team consensus
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Outcomes & Retrospective
|
||||||
|
|
||||||
|
### What Worked ✅
|
||||||
|
- [Successful approach or decision]
|
||||||
|
- [Technique that proved valuable]
|
||||||
|
- [Tool or pattern that helped]
|
||||||
|
|
||||||
|
### What Didn't Work ❌
|
||||||
|
- [Challenge or obstacle]
|
||||||
|
- [Approach that failed]
|
||||||
|
- [Assumption that was wrong]
|
||||||
|
|
||||||
|
### Measured Impact 📊
|
||||||
|
- **Metric 1**: Before → After (% change)
|
||||||
|
- **Metric 2**: Before → After (% change)
|
||||||
|
- **Metric 3**: Before → After (% change)
|
||||||
|
|
||||||
|
### Lessons Learned 💡
|
||||||
|
- [Key insight for future work]
|
||||||
|
- [Pattern to apply elsewhere]
|
||||||
|
- [Pitfall to avoid]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risks / Open Questions
|
||||||
|
|
||||||
|
**Risks:**
|
||||||
|
- ⚠️ **[Risk]**: Description and mitigation strategy
|
||||||
|
- ⚠️ **[Risk]**: Description and mitigation strategy
|
||||||
|
|
||||||
|
**Open Questions:**
|
||||||
|
- ❓ **[Question]**: Context and why it matters
|
||||||
|
- ❓ **[Question]**: Context and why it matters
|
||||||
|
|
||||||
|
**Success Metrics:**
|
||||||
|
- [ ] Metric 1: Target value
|
||||||
|
- [ ] Metric 2: Target value
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps / Handoff Notes
|
||||||
|
|
||||||
|
**Immediate Next Steps:**
|
||||||
|
1. [Next action item]
|
||||||
|
2. [Next action item]
|
||||||
|
3. [Next action item]
|
||||||
|
|
||||||
|
**Future Enhancements:**
|
||||||
|
- [Potential improvement]
|
||||||
|
- [Follow-up feature]
|
||||||
|
- [Technical debt to address]
|
||||||
|
|
||||||
|
**Handoff Information:**
|
||||||
|
- PR: #[number] - [title]
|
||||||
|
- Related Issues: #[number], #[number]
|
||||||
|
- Documentation: [link to docs]
|
||||||
|
- Deployment Notes: [special considerations]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Plan Status**: 🟡 Active | Last updated: [timestamp]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### DO:
|
||||||
|
- ✅ **Update continuously**: Treat the plan as a living document
|
||||||
|
- ✅ **Be specific**: Use concrete file paths and measurable outcomes
|
||||||
|
- ✅ **Track progress**: Update checkboxes and timestamps religiously
|
||||||
|
- ✅ **Log decisions**: Capture rationale while it's fresh
|
||||||
|
- ✅ **Document surprises**: Write down unexpected findings immediately
|
||||||
|
- ✅ **Use timestamps**: Always include UTC timestamps for tracking
|
||||||
|
- ✅ **Break down work**: Keep steps small and verifiable
|
||||||
|
- ✅ **Think in phases**: Group related work logically
|
||||||
|
- ✅ **Define success**: Make success criteria measurable
|
||||||
|
- ✅ **Capture context**: Explain terms and assumptions
|
||||||
|
|
||||||
|
### DON'T:
|
||||||
|
- ❌ **Set and forget**: Don't write the plan then ignore it
|
||||||
|
- ❌ **Be vague**: Avoid "improve performance" without metrics
|
||||||
|
- ❌ **Skip verification**: Every step needs a way to confirm success
|
||||||
|
- ❌ **Ignore discoveries**: Document unexpected findings
|
||||||
|
- ❌ **Assume knowledge**: Define domain-specific terminology
|
||||||
|
- ❌ **Hide decisions**: Always explain why, not just what
|
||||||
|
- ❌ **Make steps too large**: Break down anything taking >4 hours
|
||||||
|
- ❌ **Forget timestamps**: Track when work actually happens
|
||||||
|
- ❌ **Abandon retrospectives**: Always complete the outcomes section
|
||||||
|
|
||||||
|
## Usage Patterns
|
||||||
|
|
||||||
|
### Pattern 1: New Feature Development
|
||||||
|
|
||||||
|
**User**: "I need to add user authentication to my app"
|
||||||
|
|
||||||
|
**Assistant**: "I'll create an ExecPlan to structure this feature implementation."
|
||||||
|
|
||||||
|
*Creates comprehensive plan with:*
|
||||||
|
- Context about current auth state
|
||||||
|
- Phases: Database → API endpoints → Frontend → Testing
|
||||||
|
- Specific file changes with verification steps
|
||||||
|
- Security considerations in decision log
|
||||||
|
- Progress tracking setup
|
||||||
|
|
||||||
|
### Pattern 2: Complex Refactoring
|
||||||
|
|
||||||
|
**User**: "This module has become a mess, help me refactor it"
|
||||||
|
|
||||||
|
**Assistant**: "Let me create an ExecPlan for this refactoring to ensure we don't break anything."
|
||||||
|
|
||||||
|
*Creates plan with:*
|
||||||
|
- Current state analysis and pain points
|
||||||
|
- Incremental refactoring steps (small, testable changes)
|
||||||
|
- Test strategy to maintain behavior
|
||||||
|
- Progress checkboxes for each refactor
|
||||||
|
- Decision log for architectural choices
|
||||||
|
|
||||||
|
### Pattern 3: Bug Fix with Root Cause Analysis
|
||||||
|
|
||||||
|
**User**: "Users are reporting intermittent 500 errors"
|
||||||
|
|
||||||
|
**Assistant**: "I'll create an ExecPlan to systematically debug and fix this issue."
|
||||||
|
|
||||||
|
*Creates plan with:*
|
||||||
|
- Hypothesis section for potential causes
|
||||||
|
- Investigation steps with verification
|
||||||
|
- Fix implementation plan
|
||||||
|
- Monitoring and validation steps
|
||||||
|
- Surprises section to document root cause
|
||||||
|
|
||||||
|
### Pattern 4: Multi-File Coordination
|
||||||
|
|
||||||
|
**User**: "We need to migrate from REST to GraphQL"
|
||||||
|
|
||||||
|
**Assistant**: "This is a complex migration. Let me create an ExecPlan to coordinate all the changes."
|
||||||
|
|
||||||
|
*Creates plan with:*
|
||||||
|
- Complete file inventory and dependencies
|
||||||
|
- Migration phases with rollback points
|
||||||
|
- Parallel work opportunities
|
||||||
|
- Integration testing strategy
|
||||||
|
- Handoff notes for deployment
|
||||||
|
|
||||||
|
## Example: Complete ExecPlan in Action
|
||||||
|
|
||||||
|
**Scenario**: User wants to add API rate limiting
|
||||||
|
|
||||||
|
**ExecPlan Output**:
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
# ExecPlan: API Rate Limiting Implementation
|
||||||
|
|
||||||
|
> **Living design and execution record**
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
- **Owner**: Development Team
|
||||||
|
- **Created**: 2025-01-10
|
||||||
|
- **Last Updated**: 2025-01-10 16:30 UTC
|
||||||
|
- **Agent Path**: `.agents/rate-limiting/`
|
||||||
|
- **Related Plans**: None
|
||||||
|
- **Status**: 🟡 In Progress
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Short Description
|
||||||
|
|
||||||
|
API endpoints now enforce rate limits of 100 requests per minute per user, preventing abuse while maintaining quality of service for legitimate users.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose / Big Picture
|
||||||
|
|
||||||
|
**What does the user or system gain?**
|
||||||
|
|
||||||
|
The system gains protection against API abuse, reduces server load from runaway scripts, and ensures fair resource distribution among users. Legitimate users experience consistent performance.
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
- [ ] 100 req/min limit enforced per user with <5ms overhead
|
||||||
|
- [ ] Rate limit information returned in response headers
|
||||||
|
- [ ] Configurable limits per user tier (free/pro/enterprise)
|
||||||
|
- [ ] Graceful error messages when limits exceeded
|
||||||
|
- [ ] Monitoring dashboard shows rate limit hits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context and Orientation
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
Currently, the API has no rate limiting. Some users run aggressive scripts that cause load spikes (up to 10,000 req/min), degrading service for others. We've had 3 outages in the past month related to API abuse.
|
||||||
|
|
||||||
|
**Key Files:**
|
||||||
|
- `src/middleware/auth.ts` - Current authentication middleware (38 lines)
|
||||||
|
- `src/routes/api.ts` - API route definitions (200+ lines)
|
||||||
|
- `src/config/limits.ts` - (NEW) Rate limit configuration
|
||||||
|
- `src/middleware/rateLimit.ts` - (NEW) Rate limiting logic
|
||||||
|
|
||||||
|
**Domain Terminology:**
|
||||||
|
- **Token bucket algorithm**: Rate limiting method that allows bursts while enforcing average rate
|
||||||
|
- **X-RateLimit headers**: Standard HTTP headers communicating limits to clients
|
||||||
|
- **429 status code**: "Too Many Requests" HTTP status
|
||||||
|
|
||||||
|
**Assumptions:**
|
||||||
|
- Using Redis for distributed rate limit tracking
|
||||||
|
- Rate limits apply per authenticated user (not IP-based)
|
||||||
|
- All existing API routes will be protected
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plan of Work
|
||||||
|
|
||||||
|
### Phase 1: Infrastructure Setup
|
||||||
|
|
||||||
|
**1. Add Redis dependency and configuration**
|
||||||
|
- **Files affected**: `package.json`, `src/config/redis.ts` (NEW)
|
||||||
|
- **Expected effect**: Redis client available for rate limit storage
|
||||||
|
- **Verification**: `npm install` succeeds, Redis connection test passes
|
||||||
|
- **Estimated time**: 1 hour
|
||||||
|
|
||||||
|
**2. Create rate limit configuration**
|
||||||
|
- **Files affected**: `src/config/limits.ts` (NEW)
|
||||||
|
- **Expected effect**: Centralized config for different user tiers
|
||||||
|
- **Verification**: Config values accessible, TypeScript types correct
|
||||||
|
- **Estimated time**: 30 minutes
|
||||||
|
|
||||||
|
### Phase 2: Middleware Implementation
|
||||||
|
|
||||||
|
**3. Implement token bucket rate limiter**
|
||||||
|
- **Files affected**: `src/middleware/rateLimit.ts` (NEW)
|
||||||
|
- **Expected effect**: Middleware checks and updates rate limit counters
|
||||||
|
- **Verification**: Unit tests pass for limit enforcement
|
||||||
|
- **Estimated time**: 3 hours
|
||||||
|
|
||||||
|
**4. Add rate limit headers to responses**
|
||||||
|
- **Files affected**: `src/middleware/rateLimit.ts`
|
||||||
|
- **Expected effect**: Responses include X-RateLimit-* headers
|
||||||
|
- **Verification**: Response headers present in test requests
|
||||||
|
- **Estimated time**: 1 hour
|
||||||
|
|
||||||
|
### Phase 3: Integration
|
||||||
|
|
||||||
|
**5. Apply middleware to API routes**
|
||||||
|
- **Files affected**: `src/routes/api.ts`
|
||||||
|
- **Expected effect**: All routes protected by rate limiting
|
||||||
|
- **Verification**: Test requests get rate limited after threshold
|
||||||
|
- **Estimated time**: 1 hour
|
||||||
|
|
||||||
|
**6. Add error handling for rate limit exceeded**
|
||||||
|
- **Files affected**: `src/middleware/errorHandler.ts`
|
||||||
|
- **Expected effect**: Clear 429 error message with retry info
|
||||||
|
- **Verification**: Exceeded requests get helpful error message
|
||||||
|
- **Estimated time**: 30 minutes
|
||||||
|
|
||||||
|
### Phase 4: Testing & Monitoring
|
||||||
|
|
||||||
|
**7. Write integration tests**
|
||||||
|
- **Files affected**: `tests/rateLimit.integration.test.ts` (NEW)
|
||||||
|
- **Expected effect**: Comprehensive test coverage for rate limiting
|
||||||
|
- **Verification**: All tests pass, >90% coverage
|
||||||
|
- **Estimated time**: 2 hours
|
||||||
|
|
||||||
|
**8. Add monitoring dashboard**
|
||||||
|
- **Files affected**: `src/monitoring/rateLimitMetrics.ts` (NEW)
|
||||||
|
- **Expected effect**: Dashboard shows rate limit hits by user/endpoint
|
||||||
|
- **Verification**: Dashboard accessible, shows real-time data
|
||||||
|
- **Estimated time**: 2 hours
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
|
||||||
|
**Overall**: ⚫⚫⚫⚫⚫⚪⚪⚪⚪⚪ 50% (4/8 steps)
|
||||||
|
|
||||||
|
- [x] **Step 1**: Add Redis dependency and configuration
|
||||||
|
- Status: ✅ Complete
|
||||||
|
- Started: 2025-01-10 14:00 UTC
|
||||||
|
- Completed: 2025-01-10 14:45 UTC
|
||||||
|
- Notes: Used ioredis library, connection pool configured
|
||||||
|
|
||||||
|
- [x] **Step 2**: Create rate limit configuration
|
||||||
|
- Status: ✅ Complete
|
||||||
|
- Started: 2025-01-10 14:50 UTC
|
||||||
|
- Completed: 2025-01-10 15:15 UTC
|
||||||
|
- Notes: Added tiered limits (free: 60/min, pro: 300/min, enterprise: 1000/min)
|
||||||
|
|
||||||
|
- [x] **Step 3**: Implement token bucket rate limiter
|
||||||
|
- Status: ✅ Complete
|
||||||
|
- Started: 2025-01-10 15:20 UTC
|
||||||
|
- Completed: 2025-01-10 16:10 UTC
|
||||||
|
- Notes: Used sliding window for accuracy, 12 unit tests passing
|
||||||
|
|
||||||
|
- [x] **Step 4**: Add rate limit headers to responses
|
||||||
|
- Status: ✅ Complete
|
||||||
|
- Started: 2025-01-10 16:15 UTC
|
||||||
|
- Completed: 2025-01-10 16:30 UTC
|
||||||
|
- Notes: Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
|
||||||
|
|
||||||
|
- [ ] **Step 5**: Apply middleware to API routes
|
||||||
|
- Status: 🟡 In Progress
|
||||||
|
- Started: 2025-01-10 16:35 UTC
|
||||||
|
- Completed: —
|
||||||
|
- Notes: Working on route-specific overrides for admin endpoints
|
||||||
|
|
||||||
|
- [ ] **Step 6**: Add error handling for rate limit exceeded
|
||||||
|
- Status: Pending
|
||||||
|
|
||||||
|
- [ ] **Step 7**: Write integration tests
|
||||||
|
- Status: Pending
|
||||||
|
|
||||||
|
- [ ] **Step 8**: Add monitoring dashboard
|
||||||
|
- Status: Pending
|
||||||
|
|
||||||
|
**Velocity**: ~4 steps per 2.5 hours (1.6 steps/hour)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Surprises & Discoveries
|
||||||
|
|
||||||
|
**2025-01-10 15:45**: Redis performance exceeds expectations
|
||||||
|
- **What**: Rate limit checks averaging 0.8ms instead of expected 5ms
|
||||||
|
- **Why it matters**: Gives us headroom for more sophisticated limiting algorithms
|
||||||
|
- **Action taken**: Documented for future reference, no plan changes needed
|
||||||
|
- **Evidence**: Load testing shows p95 latency of 1.2ms for 10k requests
|
||||||
|
|
||||||
|
**2025-01-10 16:20**: Some routes need different limits
|
||||||
|
- **What**: Admin endpoints and webhooks need separate (higher) limits
|
||||||
|
- **Why it matters**: Current design assumes uniform limits per user
|
||||||
|
- **Action taken**: Adding route-specific limit overrides in middleware
|
||||||
|
- **Evidence**: Admin users reported 429s during bulk operations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decision Log
|
||||||
|
|
||||||
|
**2025-01-10 14:30**: Use token bucket algorithm over fixed window
|
||||||
|
- **Decision**: Implement sliding window token bucket algorithm
|
||||||
|
- **Rationale**: Better handles bursts, more accurate than fixed windows
|
||||||
|
- **Alternatives considered**: Fixed window (simpler but less accurate), leaky bucket (harder to implement)
|
||||||
|
- **Trade-offs**: Slightly more complex implementation, minimal performance difference
|
||||||
|
- **Decided by**: Engineering team after reviewing rate limiting patterns
|
||||||
|
|
||||||
|
**2025-01-10 16:25**: Add route-specific limit overrides
|
||||||
|
- **Decision**: Allow individual routes to override default rate limits
|
||||||
|
- **Rationale**: Admin and webhook endpoints need higher limits than user endpoints
|
||||||
|
- **Alternatives considered**: Separate user tiers (too complex), ignore issue (blocks legitimate use)
|
||||||
|
- **Trade-offs**: Adds configuration complexity, worth it for flexibility
|
||||||
|
- **Decided by**: Lead developer after admin user feedback
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Outcomes & Retrospective
|
||||||
|
|
||||||
|
*(To be completed after implementation)*
|
||||||
|
|
||||||
|
### What Worked ✅
|
||||||
|
- TBD
|
||||||
|
|
||||||
|
### What Didn't Work ❌
|
||||||
|
- TBD
|
||||||
|
|
||||||
|
### Measured Impact 📊
|
||||||
|
- TBD
|
||||||
|
|
||||||
|
### Lessons Learned 💡
|
||||||
|
- TBD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risks / Open Questions
|
||||||
|
|
||||||
|
**Risks:**
|
||||||
|
- ⚠️ **Redis single point of failure**: If Redis goes down, API becomes unusable
|
||||||
|
- Mitigation: Implement fallback to in-memory cache with degraded accuracy
|
||||||
|
- ⚠️ **Clock drift in distributed systems**: Multiple servers might have slightly different time
|
||||||
|
- Mitigation: Use Redis SCRIPT for atomic operations
|
||||||
|
|
||||||
|
**Open Questions:**
|
||||||
|
- ❓ **Should websockets count against rate limits?**: Currently undefined behavior
|
||||||
|
- ❓ **How to handle shared API keys**: Multiple users with same key could hit limits quickly
|
||||||
|
|
||||||
|
**Success Metrics:**
|
||||||
|
- [ ] API abuse incidents: 3/month → 0/month
|
||||||
|
- [ ] P95 latency overhead: <5ms
|
||||||
|
- [ ] False positive rate: <0.1%
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps / Handoff Notes
|
||||||
|
|
||||||
|
**Immediate Next Steps:**
|
||||||
|
1. Complete route integration (Step 5)
|
||||||
|
2. Implement error handling (Step 6)
|
||||||
|
3. Write integration tests (Step 7)
|
||||||
|
4. Deploy to staging for testing
|
||||||
|
|
||||||
|
**Future Enhancements:**
|
||||||
|
- Implement rate limit exemptions for specific users
|
||||||
|
- Add GraphQL-specific rate limiting (query complexity)
|
||||||
|
- Create user-facing rate limit dashboard
|
||||||
|
|
||||||
|
**Handoff Information:**
|
||||||
|
- PR: #458 - Add API rate limiting
|
||||||
|
- Related Issues: #389 (API abuse report), #401 (performance issues)
|
||||||
|
- Documentation: Update API docs with rate limit details
|
||||||
|
- Deployment Notes: Requires Redis cluster, environment variables for limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Plan Status**: 🟡 Active | Last updated: 2025-01-10 16:35 UTC
|
||||||
|
````
|
||||||
|
|
||||||
|
## Integration with Development Workflow
|
||||||
|
|
||||||
|
### Storing Plans
|
||||||
|
|
||||||
|
**Recommended structure:**
|
||||||
|
```
|
||||||
|
.agents/
|
||||||
|
├── template/
|
||||||
|
│ └── PLAN.md # Master template
|
||||||
|
├── feature-auth/
|
||||||
|
│ └── PLAN.md # Feature-specific plan
|
||||||
|
├── refactor-api/
|
||||||
|
│ └── PLAN.md # Refactoring plan
|
||||||
|
└── tmp/ # Quick iterations (gitignored)
|
||||||
|
└── PLAN.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
1. **Start**: User describes complex task
|
||||||
|
2. **Plan**: Create ExecPlan using this skill
|
||||||
|
3. **Execute**: Work through plan, updating progress
|
||||||
|
4. **Discover**: Log surprises and decisions as they happen
|
||||||
|
5. **Review**: Complete retrospective at milestones
|
||||||
|
6. **Complete**: Archive or delete plan after merge
|
||||||
|
|
||||||
|
### Tips for Long-Running Sessions
|
||||||
|
|
||||||
|
- **Update frequently**: Don't let the plan drift from reality
|
||||||
|
- **Checkpoint progress**: Update status after completing each step
|
||||||
|
- **Document blockers**: When stuck, write it in Progress notes
|
||||||
|
- **Capture insights**: Write down discoveries immediately
|
||||||
|
- **Reference the plan**: Regularly review what's next
|
||||||
|
- **Adapt as needed**: Plans can change - document why
|
||||||
|
|
||||||
|
## Adaptation Guidelines
|
||||||
|
|
||||||
|
### For Solo Projects
|
||||||
|
- Simplify metadata (owner, handoff notes less critical)
|
||||||
|
- Focus on Progress and Surprises sections
|
||||||
|
- Use as personal execution log
|
||||||
|
|
||||||
|
### For Team Projects
|
||||||
|
- Emphasize Decision Log for async communication
|
||||||
|
- Include detailed Handoff Notes
|
||||||
|
- Link to PRs and issues extensively
|
||||||
|
- Update more frequently for visibility
|
||||||
|
|
||||||
|
### For Learning Projects
|
||||||
|
- Expand Surprises & Discoveries section
|
||||||
|
- Focus heavily on Retrospective
|
||||||
|
- Document wrong assumptions prominently
|
||||||
|
|
||||||
|
### For Production Systems
|
||||||
|
- Require Risks section completion
|
||||||
|
- Mandate verification steps for every change
|
||||||
|
- Include rollback procedures
|
||||||
|
- Link to monitoring/alerting
|
||||||
|
|
||||||
|
This skill transforms complex development work from chaotic improvisation into structured, trackable, and repeatable execution.
|
||||||
938
skills/development/repl-driven-clojure.md
Normal file
938
skills/development/repl-driven-clojure.md
Normal file
@@ -0,0 +1,938 @@
|
|||||||
|
---
|
||||||
|
name: repl-driven-clojure
|
||||||
|
description: Master REPL-driven development workflow for Clojure 1.12 with live coding, rich comment blocks, state management, and iterative design
|
||||||
|
---
|
||||||
|
|
||||||
|
# REPL-Driven Development with Clojure
|
||||||
|
|
||||||
|
When the user requests guidance on Clojure development, REPL workflow, interactive programming, or asks how to structure Clojure code for rapid iteration, use this skill to provide comprehensive REPL-driven development guidance using Clojure 1.12 conventions.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
1. **Live Interaction**: Code with immediate feedback through continuous evaluation
|
||||||
|
2. **Bottom-Up Development**: Build and test small functions, composing into larger systems
|
||||||
|
3. **Incremental Growth**: Make small changes, evaluate constantly, refine iteratively
|
||||||
|
4. **State Awareness**: Manage application state explicitly for reliable REPL sessions
|
||||||
|
5. **Documentation in Code**: Use rich comment blocks as living documentation
|
||||||
|
6. **Fast Feedback Loop**: Seconds from idea to working code
|
||||||
|
|
||||||
|
## When to Use This Skill
|
||||||
|
|
||||||
|
Activate this skill when the user:
|
||||||
|
- Requests help with Clojure development workflow
|
||||||
|
- Asks about REPL-driven development or interactive programming
|
||||||
|
- Wants to structure Clojure code for iterability
|
||||||
|
- Needs guidance on namespace management and reloading
|
||||||
|
- Asks about state management in long-running REPL sessions
|
||||||
|
- Wants to combine REPL-driven and test-driven development
|
||||||
|
- Mentions "rich comment blocks" or "design journals"
|
||||||
|
- Asks about Clojure 1.12 features or conventions
|
||||||
|
|
||||||
|
## REPL-Driven Development Framework
|
||||||
|
|
||||||
|
### The REPL Cycle
|
||||||
|
|
||||||
|
**Read → Evaluate → Print → Loop**
|
||||||
|
|
||||||
|
1. **Read**: Clojure reader processes code and expands macros
|
||||||
|
2. **Evaluate**: Code compiles to JVM bytecode and executes
|
||||||
|
3. **Print**: Results display in REPL or application
|
||||||
|
4. **Loop**: Continue with next expression
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
**Standard Flow:**
|
||||||
|
1. Start REPL connected to your editor
|
||||||
|
2. Write function in source file
|
||||||
|
3. Evaluate expression in namespace context
|
||||||
|
4. Inspect results, refine implementation
|
||||||
|
5. Iterate until satisfied
|
||||||
|
6. Write tests to codify successful experiments
|
||||||
|
7. Repeat for next function
|
||||||
|
|
||||||
|
## Clojure 1.12 Features and Conventions
|
||||||
|
|
||||||
|
### Qualified Methods (New in 1.12)
|
||||||
|
|
||||||
|
Clojure 1.12 simplifies Java interop with uniform `Classname/member` syntax:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Instance methods - NEW uniform syntax
|
||||||
|
(String/length "hello") ; => 5
|
||||||
|
(String/toUpperCase "hello") ; => "HELLO"
|
||||||
|
|
||||||
|
;; Constructors with Classname/new
|
||||||
|
(java.util.ArrayList/new) ; Create ArrayList
|
||||||
|
(String/new "hello") ; Create String
|
||||||
|
|
||||||
|
;; Works with type hints for performance
|
||||||
|
(defn process-string [^String s]
|
||||||
|
(String/length s))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method Values (New in 1.12)
|
||||||
|
|
||||||
|
Reference methods as values using qualified method syntax:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Method values
|
||||||
|
(map String/length ["foo" "hello" "world"])
|
||||||
|
;; => (3 5 5)
|
||||||
|
|
||||||
|
;; Pass as higher-order functions
|
||||||
|
(filter (String/startsWith "hel") ["hello" "world" "help"])
|
||||||
|
;; => ("hello" "help")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Param Tags (Renamed in 1.12)
|
||||||
|
|
||||||
|
Type hints for method parameters (formerly `:arg-tags`):
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Use :param-tags for explicit method selection
|
||||||
|
(defn add-numbers
|
||||||
|
{:param-tags [Long Long]}
|
||||||
|
[^long a ^long b]
|
||||||
|
(+ a b))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Array Class Syntax (Updated in 1.12)
|
||||||
|
|
||||||
|
New streamlined array class syntax:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; OLD: String-*
|
||||||
|
;; NEW: String*
|
||||||
|
(defn process-array [^String* arr]
|
||||||
|
(alength arr))
|
||||||
|
|
||||||
|
;; Multi-dimensional arrays
|
||||||
|
(defn matrix [^int** grid]
|
||||||
|
(aget grid 0 0))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rich Comment Blocks
|
||||||
|
|
||||||
|
Rich comment blocks are living documentation that capture interactive exploration:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.core
|
||||||
|
(:require [clojure.string :as str]))
|
||||||
|
|
||||||
|
(defn greet [name]
|
||||||
|
(str "Hello, " name "!"))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; REPL experiments and usage examples
|
||||||
|
;; Evaluate these expressions with your editor
|
||||||
|
|
||||||
|
;; Basic usage
|
||||||
|
(greet "Alice")
|
||||||
|
;; => "Hello, Alice!"
|
||||||
|
|
||||||
|
;; Edge cases
|
||||||
|
(greet "")
|
||||||
|
;; => "Hello, !"
|
||||||
|
|
||||||
|
(greet nil)
|
||||||
|
;; => NullPointerException - need to handle!
|
||||||
|
|
||||||
|
;; Fixed version exploration
|
||||||
|
(defn greet-safe [name]
|
||||||
|
(str "Hello, " (or name "stranger") "!"))
|
||||||
|
|
||||||
|
(greet-safe nil)
|
||||||
|
;; => "Hello, stranger!"
|
||||||
|
|
||||||
|
;; Test with various inputs
|
||||||
|
(map greet-safe ["Alice" "Bob" nil ""])
|
||||||
|
;; => ("Hello, Alice!" "Hello, Bob!" "Hello, stranger!" "Hello, stranger!")
|
||||||
|
|
||||||
|
:rcf) ;; Rich Comment Form marker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Best Practices for Comment Blocks
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
```clojure
|
||||||
|
(comment
|
||||||
|
;; Section: System Startup
|
||||||
|
(start-system!)
|
||||||
|
(reset-system!)
|
||||||
|
|
||||||
|
;; Section: Data Exploration
|
||||||
|
(def sample-data {...})
|
||||||
|
(process sample-data)
|
||||||
|
|
||||||
|
;; Section: Performance Testing
|
||||||
|
(time (expensive-operation))
|
||||||
|
|
||||||
|
;; Section: Debugging Helpers
|
||||||
|
(println "Debug:" (capture-state))
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design Journals
|
||||||
|
|
||||||
|
Create separate namespaces to document design decisions:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.design-journal
|
||||||
|
"Living record of design decisions and explorations"
|
||||||
|
(:require [myapp.core :as core]))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Decision: Data structure for user sessions
|
||||||
|
;; Date: 2025-01-10
|
||||||
|
;; Context: Need to track active user sessions with timeout
|
||||||
|
|
||||||
|
;; Option 1: Simple map (REJECTED)
|
||||||
|
;; - Pro: Easy to understand
|
||||||
|
;; - Con: No automatic cleanup, memory leak risk
|
||||||
|
(def sessions-v1
|
||||||
|
{:user-123 {:started-at (System/currentTimeMillis)}})
|
||||||
|
|
||||||
|
;; Option 2: core.cache with TTL (CHOSEN)
|
||||||
|
;; - Pro: Automatic expiration
|
||||||
|
;; - Pro: Battle-tested library
|
||||||
|
;; - Con: Additional dependency
|
||||||
|
(require '[clojure.core.cache :as cache])
|
||||||
|
(def sessions-v2
|
||||||
|
(cache/ttl-cache-factory {} :ttl 3600000))
|
||||||
|
|
||||||
|
;; Rationale: Automatic cleanup is critical for production
|
||||||
|
;; Alternative considered: Redis (overkill for this use case)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## State Management for Long-Running REPLs
|
||||||
|
|
||||||
|
### Using Mount
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.core
|
||||||
|
(:require [mount.core :as mount :refer [defstate]]))
|
||||||
|
|
||||||
|
;; Define stateful components
|
||||||
|
(defstate database
|
||||||
|
:start (connect-db!)
|
||||||
|
:stop (disconnect-db database))
|
||||||
|
|
||||||
|
(defstate http-server
|
||||||
|
:start (start-server! {:port 3000})
|
||||||
|
:stop (stop-server! http-server))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; REPL workflow with Mount
|
||||||
|
|
||||||
|
;; Start all states
|
||||||
|
(mount/start)
|
||||||
|
|
||||||
|
;; Restart specific state after code changes
|
||||||
|
(mount/stop #'database)
|
||||||
|
(mount/start #'database)
|
||||||
|
|
||||||
|
;; Restart entire system
|
||||||
|
(mount/stop)
|
||||||
|
(mount/start)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Integrant
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.system
|
||||||
|
(:require [integrant.core :as ig]))
|
||||||
|
|
||||||
|
;; Define system configuration
|
||||||
|
(def config
|
||||||
|
{:adapter/database {:uri "jdbc:postgresql://localhost/mydb"}
|
||||||
|
:handler/api {:db (ig/ref :adapter/database)}
|
||||||
|
:server/http {:port 3000
|
||||||
|
:handler (ig/ref :handler/api)}})
|
||||||
|
|
||||||
|
;; Define component lifecycle
|
||||||
|
(defmethod ig/init-key :adapter/database [_ {:keys [uri]}]
|
||||||
|
(connect-db uri))
|
||||||
|
|
||||||
|
(defmethod ig/halt-key! :adapter/database [_ db]
|
||||||
|
(disconnect-db db))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; REPL workflow with Integrant
|
||||||
|
|
||||||
|
;; Initialize system
|
||||||
|
(def system (ig/init config))
|
||||||
|
|
||||||
|
;; Access components
|
||||||
|
(:adapter/database system)
|
||||||
|
|
||||||
|
;; Reload with changes
|
||||||
|
(def system (ig/suspend! system))
|
||||||
|
(def system (ig/resume config system))
|
||||||
|
|
||||||
|
;; Complete restart
|
||||||
|
(ig/halt! system)
|
||||||
|
(def system (ig/init config))
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Component
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.system
|
||||||
|
(:require [com.stuartsierra.component :as component]))
|
||||||
|
|
||||||
|
(defrecord Database [uri connection]
|
||||||
|
component/Lifecycle
|
||||||
|
(start [this]
|
||||||
|
(println "Starting database")
|
||||||
|
(assoc this :connection (connect-db uri)))
|
||||||
|
(stop [this]
|
||||||
|
(println "Stopping database")
|
||||||
|
(disconnect-db connection)
|
||||||
|
(assoc this :connection nil)))
|
||||||
|
|
||||||
|
(defn new-database [uri]
|
||||||
|
(map->Database {:uri uri}))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; REPL workflow with Component
|
||||||
|
|
||||||
|
(def db (new-database "jdbc:postgresql://localhost/mydb"))
|
||||||
|
(def db (component/start db))
|
||||||
|
|
||||||
|
;; Use the component
|
||||||
|
(:connection db)
|
||||||
|
|
||||||
|
;; Stop when done
|
||||||
|
(def db (component/stop db))
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Namespace Management
|
||||||
|
|
||||||
|
### Reloading Namespaces
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns user
|
||||||
|
(:require [clojure.tools.namespace.repl :refer [refresh refresh-all]]))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Reload changed namespaces
|
||||||
|
(refresh)
|
||||||
|
|
||||||
|
;; Reload all namespaces (full reset)
|
||||||
|
(refresh-all)
|
||||||
|
|
||||||
|
;; Clear namespace before reload
|
||||||
|
(require '[myapp.core :as core] :reload)
|
||||||
|
|
||||||
|
;; Force reload dependencies
|
||||||
|
(require '[myapp.core :as core] :reload-all)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Namespace Setup
|
||||||
|
|
||||||
|
Create `dev/user.clj` for REPL utilities:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns user
|
||||||
|
"REPL utilities and system management"
|
||||||
|
(:require [clojure.tools.namespace.repl :as repl]
|
||||||
|
[mount.core :as mount]
|
||||||
|
[myapp.core :as core]))
|
||||||
|
|
||||||
|
(defn start
|
||||||
|
"Start the system"
|
||||||
|
[]
|
||||||
|
(mount/start))
|
||||||
|
|
||||||
|
(defn stop
|
||||||
|
"Stop the system"
|
||||||
|
[]
|
||||||
|
(mount/stop))
|
||||||
|
|
||||||
|
(defn reset
|
||||||
|
"Stop, reload code, restart"
|
||||||
|
[]
|
||||||
|
(stop)
|
||||||
|
(repl/refresh :after 'user/start))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Quick system operations in REPL
|
||||||
|
(start)
|
||||||
|
(stop)
|
||||||
|
(reset)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combining REPL-Driven and Test-Driven Development
|
||||||
|
|
||||||
|
### Workflow Integration
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.core-test
|
||||||
|
(:require [clojure.test :refer [deftest is testing]]
|
||||||
|
[myapp.core :as core]))
|
||||||
|
|
||||||
|
;; Start with REPL exploration
|
||||||
|
(comment
|
||||||
|
;; Experiment with function behavior
|
||||||
|
(core/parse-email "user@example.com")
|
||||||
|
;; => {:local "user" :domain "example.com"}
|
||||||
|
|
||||||
|
(core/parse-email "invalid")
|
||||||
|
;; => nil (or should it throw?)
|
||||||
|
|
||||||
|
;; Try different approaches
|
||||||
|
(defn parse-email-v2 [s]
|
||||||
|
(when-let [[_ local domain] (re-matches #"(.+)@(.+)" s)]
|
||||||
|
{:local local :domain domain}))
|
||||||
|
|
||||||
|
(parse-email-v2 "user@example.com")
|
||||||
|
;; => {:local "user" :domain "example.com"}
|
||||||
|
|
||||||
|
(parse-email-v2 "invalid")
|
||||||
|
;; => nil (good!)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
|
||||||
|
;; Codify successful experiments as tests
|
||||||
|
(deftest parse-email-test
|
||||||
|
(testing "valid email"
|
||||||
|
(is (= {:local "user" :domain "example.com"}
|
||||||
|
(core/parse-email "user@example.com"))))
|
||||||
|
|
||||||
|
(testing "invalid email"
|
||||||
|
(is (nil? (core/parse-email "invalid"))))
|
||||||
|
|
||||||
|
(testing "edge cases"
|
||||||
|
(is (nil? (core/parse-email "")))
|
||||||
|
(is (nil? (core/parse-email nil)))))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rich Comment Form (RCF) Testing
|
||||||
|
|
||||||
|
Use RCF-style tests for rapid feedback:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.core
|
||||||
|
(:require [hyperfiddle.rcf :refer [tests]]))
|
||||||
|
|
||||||
|
(defn add [a b]
|
||||||
|
(+ a b))
|
||||||
|
|
||||||
|
(tests
|
||||||
|
"basic addition"
|
||||||
|
(add 2 3) := 5
|
||||||
|
(add 0 0) := 0
|
||||||
|
(add -1 1) := 0
|
||||||
|
|
||||||
|
"works with different number types"
|
||||||
|
(add 1.5 2.5) := 4.0
|
||||||
|
(add 1/2 1/2) := 1)
|
||||||
|
|
||||||
|
;; Tests run automatically when namespace loads in REPL
|
||||||
|
;; Fast feedback without leaving your code
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Inspection and Visualization
|
||||||
|
|
||||||
|
### Portal Integration
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns user
|
||||||
|
(:require [portal.api :as portal]))
|
||||||
|
|
||||||
|
(def p (portal/open))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Send data to Portal for inspection
|
||||||
|
(portal/submit {:users [{:name "Alice" :age 30}
|
||||||
|
{:name "Bob" :age 25}]})
|
||||||
|
|
||||||
|
;; Tap values automatically
|
||||||
|
(add-tap portal/submit)
|
||||||
|
(tap> {:event "user-login" :user-id 123})
|
||||||
|
|
||||||
|
;; Clear portal
|
||||||
|
(portal/clear)
|
||||||
|
|
||||||
|
;; Close portal
|
||||||
|
(portal/close)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### CIDER Inspector
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(comment
|
||||||
|
;; Inspect complex data structures
|
||||||
|
(require '[cider.inspector :as inspect])
|
||||||
|
|
||||||
|
(def complex-data
|
||||||
|
{:users [{:id 1 :name "Alice" :orders [...]}
|
||||||
|
{:id 2 :name "Bob" :orders [...]}]
|
||||||
|
:metadata {...}})
|
||||||
|
|
||||||
|
;; Evaluate with inspector (in CIDER/Emacs)
|
||||||
|
;; C-c M-i to inspect result
|
||||||
|
complex-data
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance and Profiling in REPL
|
||||||
|
|
||||||
|
### Basic Timing
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(comment
|
||||||
|
;; Quick timing
|
||||||
|
(time (expensive-operation))
|
||||||
|
;; "Elapsed time: 1234.56 msecs"
|
||||||
|
|
||||||
|
;; Multiple runs for average
|
||||||
|
(dotimes [_ 5]
|
||||||
|
(time (expensive-operation)))
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Criterium for Accurate Benchmarking
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.perf
|
||||||
|
(:require [criterium.core :as crit]))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Accurate benchmarking with JVM warmup
|
||||||
|
(crit/quick-bench
|
||||||
|
(reduce + (range 1000)))
|
||||||
|
|
||||||
|
;; Detailed benchmark
|
||||||
|
(crit/bench
|
||||||
|
(my-function args))
|
||||||
|
|
||||||
|
;; Compare implementations
|
||||||
|
(crit/quick-bench (map inc (range 1000))) ; lazy
|
||||||
|
(crit/quick-bench (mapv inc (range 1000))) ; eager
|
||||||
|
(crit/quick-bench (into [] (map inc) (range 1000))) ; transducer
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Techniques
|
||||||
|
|
||||||
|
### Tap and Inspect
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn complex-function [data]
|
||||||
|
(let [step1 (process-step-1 data)
|
||||||
|
_ (tap> {:stage :step1 :result step1}) ; Debug point
|
||||||
|
step2 (process-step-2 step1)
|
||||||
|
_ (tap> {:stage :step2 :result step2}) ; Debug point
|
||||||
|
step3 (process-step-3 step2)]
|
||||||
|
step3))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Set up tap handler
|
||||||
|
(add-tap println)
|
||||||
|
;; or
|
||||||
|
(add-tap portal/submit)
|
||||||
|
|
||||||
|
;; Run function and inspect tapped values
|
||||||
|
(complex-function test-data)
|
||||||
|
|
||||||
|
;; Remove tap when done
|
||||||
|
(remove-tap println)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scope Capture
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.debug
|
||||||
|
(:require [sc.api :as sc]))
|
||||||
|
|
||||||
|
(defn buggy-function [x y]
|
||||||
|
(let [a (+ x y)
|
||||||
|
b (* a 2)
|
||||||
|
c (sc/spy (/ b (- y x)))] ; Capture scope here
|
||||||
|
(+ a b c)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; When exception occurs, inspect captured scope
|
||||||
|
(buggy-function 5 5) ; Division by zero!
|
||||||
|
|
||||||
|
;; View last exception with scope
|
||||||
|
(sc/defsc 1) ; Define scope from last capture
|
||||||
|
|
||||||
|
;; Inspect variables
|
||||||
|
ep-1-a ; => 10
|
||||||
|
ep-1-b ; => 20
|
||||||
|
ep-1-x ; => 5
|
||||||
|
ep-1-y ; => 5
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## REPL-Driven Development Patterns
|
||||||
|
|
||||||
|
### Exploratory Development Pattern
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(comment
|
||||||
|
;; 1. Start with data
|
||||||
|
(def sample-users
|
||||||
|
[{:id 1 :name "Alice" :email "alice@example.com"}
|
||||||
|
{:id 2 :name "Bob" :email "bob@example.com"}])
|
||||||
|
|
||||||
|
;; 2. Explore transformations
|
||||||
|
(map :name sample-users)
|
||||||
|
;; => ("Alice" "Bob")
|
||||||
|
|
||||||
|
(group-by :id sample-users)
|
||||||
|
;; => {1 [{:id 1 ...}], 2 [{:id 2 ...}]}
|
||||||
|
|
||||||
|
;; 3. Build helper functions
|
||||||
|
(defn by-id [users]
|
||||||
|
(reduce (fn [acc user]
|
||||||
|
(assoc acc (:id user) user))
|
||||||
|
{}
|
||||||
|
users))
|
||||||
|
|
||||||
|
(by-id sample-users)
|
||||||
|
;; => {1 {:id 1 ...}, 2 {:id 2 ...}}
|
||||||
|
|
||||||
|
;; 4. Refine based on REPL feedback
|
||||||
|
(defn index-by [key-fn coll]
|
||||||
|
(into {} (map (juxt key-fn identity)) coll))
|
||||||
|
|
||||||
|
(index-by :id sample-users)
|
||||||
|
;; => {1 {:id 1 ...}, 2 {:id 2 ...}}
|
||||||
|
|
||||||
|
;; 5. Extract to source file
|
||||||
|
;; 6. Write tests
|
||||||
|
;; 7. Move on to next function
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bottom-Up Composition Pattern
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Start with smallest pieces
|
||||||
|
(defn parse-int [s]
|
||||||
|
(Integer/parseInt s))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(parse-int "42") ; => 42
|
||||||
|
(parse-int "abc") ; => NumberFormatException
|
||||||
|
:rcf)
|
||||||
|
|
||||||
|
;; Add error handling
|
||||||
|
(defn parse-int-safe [s]
|
||||||
|
(try
|
||||||
|
(Integer/parseInt s)
|
||||||
|
(catch NumberFormatException _
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(parse-int-safe "42") ; => 42
|
||||||
|
(parse-int-safe "abc") ; => nil
|
||||||
|
:rcf)
|
||||||
|
|
||||||
|
;; Compose into larger function
|
||||||
|
(defn sum-string-numbers [strings]
|
||||||
|
(->> strings
|
||||||
|
(keep parse-int-safe)
|
||||||
|
(reduce +)))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(sum-string-numbers ["1" "2" "3"]) ; => 6
|
||||||
|
(sum-string-numbers ["1" "abc" "3"]) ; => 4
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Refactoring with REPL Safety Net
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(comment
|
||||||
|
;; Original implementation
|
||||||
|
(defn process-order-v1 [order]
|
||||||
|
(let [total (reduce + (map :price (:items order)))
|
||||||
|
discount (if (:premium? (:user order))
|
||||||
|
(* total 0.1)
|
||||||
|
0)
|
||||||
|
final (- total discount)]
|
||||||
|
{:order-id (:id order)
|
||||||
|
:total final}))
|
||||||
|
|
||||||
|
;; Test current behavior before refactoring
|
||||||
|
(def test-order
|
||||||
|
{:id 123
|
||||||
|
:user {:premium? true}
|
||||||
|
:items [{:price 100} {:price 50}]})
|
||||||
|
|
||||||
|
(process-order-v1 test-order)
|
||||||
|
;; => {:order-id 123, :total 135.0}
|
||||||
|
|
||||||
|
;; Refactor: extract functions
|
||||||
|
(defn calculate-total [items]
|
||||||
|
(reduce + (map :price items)))
|
||||||
|
|
||||||
|
(defn calculate-discount [total premium?]
|
||||||
|
(if premium? (* total 0.1) 0))
|
||||||
|
|
||||||
|
(defn process-order-v2 [order]
|
||||||
|
(let [total (calculate-total (:items order))
|
||||||
|
discount (calculate-discount total (get-in order [:user :premium?]))
|
||||||
|
final (- total discount)]
|
||||||
|
{:order-id (:id order)
|
||||||
|
:total final}))
|
||||||
|
|
||||||
|
;; Verify behavior unchanged
|
||||||
|
(= (process-order-v1 test-order)
|
||||||
|
(process-order-v2 test-order))
|
||||||
|
;; => true ✓
|
||||||
|
|
||||||
|
;; Safe to replace!
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Editor Integration Best Practices
|
||||||
|
|
||||||
|
### Key Bindings (CIDER/Emacs)
|
||||||
|
|
||||||
|
- `C-c C-k` - Load/evaluate current buffer
|
||||||
|
- `C-c C-c` - Evaluate defn at point
|
||||||
|
- `C-M-x` - Evaluate top-level form
|
||||||
|
- `C-c M-n M-n` - Switch REPL namespace to current file
|
||||||
|
- `C-c C-v C-f` - Show function definition
|
||||||
|
- `C-c C-d C-d` - Show documentation
|
||||||
|
|
||||||
|
### Key Bindings (Calva/VS Code)
|
||||||
|
|
||||||
|
- `Ctrl+Alt+C Enter` - Load current file
|
||||||
|
- `Ctrl+Enter` - Evaluate current form
|
||||||
|
- `Alt+Enter` - Evaluate form and replace with result
|
||||||
|
- `Ctrl+Alt+C Space` - Evaluate selected text
|
||||||
|
- `Ctrl+Alt+C C` - Evaluate to comment
|
||||||
|
|
||||||
|
### Key Bindings (Cursive/IntelliJ)
|
||||||
|
|
||||||
|
- `Ctrl+Shift+L` - Load file in REPL
|
||||||
|
- `Ctrl+Shift+P` - Send form to REPL
|
||||||
|
- `Alt+Shift+P` - Send top-level form to REPL
|
||||||
|
- `Ctrl+Shift+M` - Run tests in namespace
|
||||||
|
|
||||||
|
## Common Pitfalls and Solutions
|
||||||
|
|
||||||
|
### Pitfall 1: Stale Namespace State
|
||||||
|
|
||||||
|
**Problem**: Old definitions linger after code changes
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(comment
|
||||||
|
;; Define function
|
||||||
|
(defn old-function [x] (* x 2))
|
||||||
|
|
||||||
|
;; Later, rename to new-function in source
|
||||||
|
;; But old-function still exists in REPL!
|
||||||
|
|
||||||
|
(old-function 5) ; => Still works! Bug risk!
|
||||||
|
|
||||||
|
;; Solution: Reload namespace
|
||||||
|
(require '[myapp.core :as core] :reload)
|
||||||
|
;; or use refresh
|
||||||
|
(require '[clojure.tools.namespace.repl :refer [refresh]])
|
||||||
|
(refresh)
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pitfall 2: Circular Dependencies
|
||||||
|
|
||||||
|
**Problem**: Namespace reload fails due to circular deps
|
||||||
|
|
||||||
|
**Solution**: Restructure namespaces or use protocols
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Bad: Circular dependency
|
||||||
|
;; user.clj requires admin.clj
|
||||||
|
;; admin.clj requires user.clj
|
||||||
|
|
||||||
|
;; Good: Extract shared protocol
|
||||||
|
;; protocol.clj - defines interfaces
|
||||||
|
;; user.clj - requires protocol.clj
|
||||||
|
;; admin.clj - requires protocol.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pitfall 3: Side Effects in Top-Level
|
||||||
|
|
||||||
|
**Problem**: Code executes on namespace load
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Bad: Runs every load
|
||||||
|
(def db-connection (connect-to-db!))
|
||||||
|
|
||||||
|
;; Good: Defer until explicitly called
|
||||||
|
(defn get-db-connection []
|
||||||
|
(or @db-conn-atom (reset! db-conn-atom (connect-to-db!))))
|
||||||
|
|
||||||
|
;; Or use mount/integrant/component
|
||||||
|
(defstate db-connection
|
||||||
|
:start (connect-to-db!)
|
||||||
|
:stop (disconnect-db! db-connection))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pitfall 4: Lost REPL History
|
||||||
|
|
||||||
|
**Solution**: Use `.clojure/repl-history` or editor features
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Add to ~/.clojure/deps.edn
|
||||||
|
{:aliases
|
||||||
|
{:repl
|
||||||
|
{:extra-deps {reply/reply {:mvn/version "0.5.1"}}
|
||||||
|
:main-opts ["-m" "reply.main"]}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example: REPL-Driven Feature Development
|
||||||
|
|
||||||
|
**Goal**: Build a user authentication system
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns myapp.auth
|
||||||
|
(:require [buddy.hashers :as hashers]
|
||||||
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
|
;; 1. Define specs for data validation
|
||||||
|
(s/def ::email (s/and string? #(re-matches #".+@.+\..+" %)))
|
||||||
|
(s/def ::password (s/and string? #(>= (count %) 8)))
|
||||||
|
(s/def ::user (s/keys :req-un [::email ::password]))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Test specs interactively
|
||||||
|
(s/valid? ::email "user@example.com") ; => true
|
||||||
|
(s/valid? ::email "invalid") ; => false
|
||||||
|
(s/explain ::user {:email "test@test.com" :password "short"})
|
||||||
|
:rcf)
|
||||||
|
|
||||||
|
;; 2. Build hash-password function
|
||||||
|
(defn hash-password [password]
|
||||||
|
(hashers/derive password))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(def hashed (hash-password "mysecret123"))
|
||||||
|
hashed
|
||||||
|
;; => "bcrypt+sha512$4i9sd..."
|
||||||
|
|
||||||
|
;; Test verification
|
||||||
|
(hashers/check "mysecret123" hashed) ; => true
|
||||||
|
(hashers/check "wrongpass" hashed) ; => false
|
||||||
|
:rcf)
|
||||||
|
|
||||||
|
;; 3. Create user registration
|
||||||
|
(defn register-user [db user-data]
|
||||||
|
(when (s/valid? ::user user-data)
|
||||||
|
(let [hashed-pwd (hash-password (:password user-data))
|
||||||
|
user (assoc user-data :password hashed-pwd)]
|
||||||
|
(save-user! db user))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Mock database for testing
|
||||||
|
(def mock-db (atom {}))
|
||||||
|
|
||||||
|
(defn save-user! [db user]
|
||||||
|
(swap! db assoc (:email user) user))
|
||||||
|
|
||||||
|
;; Test registration flow
|
||||||
|
(register-user mock-db
|
||||||
|
{:email "alice@example.com"
|
||||||
|
:password "secure123"})
|
||||||
|
|
||||||
|
@mock-db
|
||||||
|
;; => {"alice@example.com" {:email "..." :password "bcrypt+..."}}
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
|
||||||
|
;; 4. Build authentication function
|
||||||
|
(defn authenticate [db email password]
|
||||||
|
(when-let [user (get @db email)]
|
||||||
|
(when (hashers/check password (:password user))
|
||||||
|
(dissoc user :password))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
;; Test authentication
|
||||||
|
(authenticate mock-db "alice@example.com" "secure123")
|
||||||
|
;; => {:email "alice@example.com"}
|
||||||
|
|
||||||
|
(authenticate mock-db "alice@example.com" "wrongpass")
|
||||||
|
;; => nil
|
||||||
|
|
||||||
|
(authenticate mock-db "nobody@example.com" "anypass")
|
||||||
|
;; => nil
|
||||||
|
|
||||||
|
:rcf)
|
||||||
|
|
||||||
|
;; 5. Now write formal tests
|
||||||
|
(ns myapp.auth-test
|
||||||
|
(:require [clojure.test :refer [deftest is testing]]
|
||||||
|
[myapp.auth :as auth]))
|
||||||
|
|
||||||
|
(deftest authentication-test
|
||||||
|
(let [db (atom {})]
|
||||||
|
(testing "user registration"
|
||||||
|
(auth/register-user db {:email "test@example.com"
|
||||||
|
:password "secure123"})
|
||||||
|
(is (contains? @db "test@example.com")))
|
||||||
|
|
||||||
|
(testing "successful authentication"
|
||||||
|
(is (= {:email "test@example.com"}
|
||||||
|
(auth/authenticate db "test@example.com" "secure123"))))
|
||||||
|
|
||||||
|
(testing "failed authentication"
|
||||||
|
(is (nil? (auth/authenticate db "test@example.com" "wrongpass"))))))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices Summary
|
||||||
|
|
||||||
|
### DO:
|
||||||
|
- ✅ **Start REPL first**: Launch before writing code
|
||||||
|
- ✅ **Evaluate continuously**: Test every function immediately
|
||||||
|
- ✅ **Use rich comments**: Document explorations inline
|
||||||
|
- ✅ **Build bottom-up**: Small functions → composition
|
||||||
|
- ✅ **Manage state**: Use mount/integrant/component
|
||||||
|
- ✅ **Inspect data**: Use Portal, tap>, CIDER inspector
|
||||||
|
- ✅ **Write tests after**: Codify successful REPL experiments
|
||||||
|
- ✅ **Reload carefully**: Use refresh, avoid stale state
|
||||||
|
- ✅ **Use 1.12 features**: Method values, qualified methods
|
||||||
|
- ✅ **Keep REPL running**: Long sessions with state management
|
||||||
|
|
||||||
|
### DON'T:
|
||||||
|
- ❌ **Write without REPL**: Don't code in a vacuum
|
||||||
|
- ❌ **Batch evaluation**: Don't wait to test everything at once
|
||||||
|
- ❌ **Side effects at top**: Avoid execution on namespace load
|
||||||
|
- ❌ **Ignore state**: Manage component lifecycle explicitly
|
||||||
|
- ❌ **Lose experiments**: Capture in rich comments or design journals
|
||||||
|
- ❌ **Skip tests**: REPL-driven ≠ test-less
|
||||||
|
- ❌ **Circular deps**: Structure namespaces carefully
|
||||||
|
- ❌ **Complex expressions**: Keep evaluation units small
|
||||||
|
- ❌ **Manual reloads**: Automate with tools
|
||||||
|
- ❌ **Fear the REPL**: It's your friend, not intimidating
|
||||||
|
|
||||||
|
This skill enables the highly productive, interactive development style that makes Clojure unique and powerful.
|
||||||
Reference in New Issue
Block a user