From f9a80945033d8d627dac33335161609222451b2f Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Wed, 7 Jan 2026 18:49:11 -0800 Subject: [PATCH 01/49] Add feature-spec skill for pre-implementation documentation Creates a PM-style interview workflow that generates three documents: - PRD (Product Requirements Document) via interview - Technical Architecture doc from PRD - Testing doc with unit and integration test plans Documents are saved to projects/{branch-name}/ in the repo root. Co-Authored-By: Claude Opus 4.5 --- claude/skills/feature-spec/SKILL.md | 156 ++++++++++++++++ .../references/interview-guide.md | 133 ++++++++++++++ .../feature-spec/templates/architecture.md | 170 ++++++++++++++++++ claude/skills/feature-spec/templates/prd.md | 112 ++++++++++++ .../skills/feature-spec/templates/testing.md | 151 ++++++++++++++++ 5 files changed, 722 insertions(+) create mode 100644 claude/skills/feature-spec/SKILL.md create mode 100644 claude/skills/feature-spec/references/interview-guide.md create mode 100644 claude/skills/feature-spec/templates/architecture.md create mode 100644 claude/skills/feature-spec/templates/prd.md create mode 100644 claude/skills/feature-spec/templates/testing.md diff --git a/claude/skills/feature-spec/SKILL.md b/claude/skills/feature-spec/SKILL.md new file mode 100644 index 0000000..d70088a --- /dev/null +++ b/claude/skills/feature-spec/SKILL.md @@ -0,0 +1,156 @@ +--- +name: Feature Spec Generator +description: Use this skill when the user asks to "create a feature spec", "generate PRD", "spec out a feature", "plan a feature", "write product requirements", "create technical architecture", or needs comprehensive feature documentation before implementation. +--- + +# Feature Spec Generator + +Generate comprehensive feature documentation through a PM-style interview workflow. Creates three documents: +1. **PRD (Product Requirements Document)** - filled via interview +2. **Technical Architecture Document** - generated from PRD +3. **Testing Document** - generated from PRD + Architecture + +## Prerequisites + +- Feature description provided by user (can be brief) +- Git repository context (uses current branch name for folder naming) + +## Output Location + +Documents are saved to `projects/{feature-branch-name}/` in the repository root: +``` +projects/{branch-name}/ +├── PRD.md +├── ARCHITECTURE.md +└── TESTING.md +``` + +## Workflow Execution + +### Initialization + +1. Capture feature description from $ARGUMENTS or prompt user if not provided +2. Get branch name: `git branch --show-current` +3. Create output directory: `mkdir -p projects/{branch-name}` +4. Announce the three-phase process to user + +**Opening message:** +"I'll help you create comprehensive feature documentation in three phases: +1. PRD (through a PM-style interview) +2. Technical Architecture +3. Testing Document + +All documents will be saved to `projects/{branch-name}/`. Let's start with the PRD." + +### Phase 1: PRD Interview + +Conduct a PM-style interview to understand and document the feature. Ask questions in batches of 2-3, waiting for responses before proceeding. + +**Interview sections** (in order): + +1. **Problem Context** (maps to Feature Overview) + - What problem does this feature solve? + - Who experiences this problem and how often? + - Why is now the right time to address this? + +2. **Users and Personas** (maps to User Stories) + - Who is the primary user? + - What is their context when using this feature? + - Are there secondary user types? + +3. **Functionality** (maps to Functional Requirements) + - Walk through the happy path from start to finish + - What are the must-have capabilities (P0)? + - What inputs/outputs are involved? + +4. **Constraints** (maps to Non-Functional Requirements) + - Any performance requirements? + - Security/privacy considerations? + - Technical or compatibility constraints? + +5. **Success Criteria** (maps to Success Metrics) + - What metrics indicate success? + - How will we measure them? + +6. **Boundaries** (maps to Out of Scope) + - What are we explicitly NOT building? + - What might users expect that we won't deliver? + +7. **Open Items** (maps to Open Questions) + - What decisions still need to be made? + - What assumptions need validation? + +**Interview rules:** +- Present 2-3 questions at a time +- Allow "skip" or "not sure yet" for optional items +- Summarize understanding after each section +- At the end, present a summary and ask for confirmation + +**After interview completion:** +1. Load PRD template from `templates/prd.md` +2. Fill template with interview responses +3. Write to `projects/{branch}/PRD.md` +4. Present summary and ask: "Does this PRD capture the feature correctly? Reply 'yes' to proceed to Architecture, or tell me what to adjust." + +### Phase 2: Technical Architecture + +Generate architecture document based on the completed PRD. + +1. Read the PRD from `projects/{branch}/PRD.md` +2. Analyze requirements to determine: + - System components needed + - Data flows between components + - APIs and interfaces + - Dependencies (internal and external) + - Implementation approach + - Technical risks +3. Load architecture template from `templates/architecture.md` +4. Generate architecture document +5. Write to `projects/{branch}/ARCHITECTURE.md` +6. Present key highlights: "Architecture document complete. Key components: {list}. Proceeding to Testing document..." + +### Phase 3: Testing Document + +Generate testing document based on PRD and Architecture. + +1. Read PRD from `projects/{branch}/PRD.md` +2. Read Architecture from `projects/{branch}/ARCHITECTURE.md` +3. Generate test plan covering: + - Unit tests for each component + - Integration tests for component interactions + - Test cases mapped to requirements + - Edge cases and error conditions + - Test data requirements +4. Load testing template from `templates/testing.md` +5. Generate testing document +6. Write to `projects/{branch}/TESTING.md` + +### Completion + +Present final summary: +"Feature specification complete! Documents created: +- `projects/{branch}/PRD.md` +- `projects/{branch}/ARCHITECTURE.md` +- `projects/{branch}/TESTING.md` + +Next steps: +1. Review documents with stakeholders +2. Address open questions in the PRD +3. Begin implementation" + +## Phase Transitions + +| Transition | User Action Required | +|------------|---------------------| +| Init → Phase 1 | None | +| Phase 1 → Phase 2 | Explicit approval of PRD | +| Phase 2 → Phase 3 | Acknowledgment | +| Phase 3 → Complete | None | + +## Important Notes + +- This workflow is single-session and non-resumable +- If user abandons mid-interview, warn them the session cannot be resumed +- All three documents must be generated in sequence +- Templates are in the `templates/` subdirectory +- Interview guide reference is in `references/interview-guide.md` diff --git a/claude/skills/feature-spec/references/interview-guide.md b/claude/skills/feature-spec/references/interview-guide.md new file mode 100644 index 0000000..02a9bdb --- /dev/null +++ b/claude/skills/feature-spec/references/interview-guide.md @@ -0,0 +1,133 @@ +# PRD Interview Guide + +## Interview Principles + +1. Ask 2-3 questions at a time +2. Wait for response before proceeding +3. Allow "skip" or "not sure yet" responses +4. Summarize understanding after each section +5. Get explicit approval before moving to architecture phase + +--- + +## Section 1: Problem Context + +**Transition:** "Let me understand the problem this feature solves." + +### Questions (present 2-3 at a time): + +1. "What specific problem does this feature solve?" +2. "Who is experiencing this problem? How often?" +3. "What happens if we don't solve this problem?" +4. "Why is now the right time to address this?" +5. "Are there existing workarounds users employ?" + +**Maps to:** Feature Overview / Problem Statement + +--- + +## Section 2: Users and Personas + +**Transition:** "Now let's talk about who will use this feature." + +### Questions: + +1. "Who is the primary user of this feature?" +2. "What is their role or context?" +3. "What is their technical sophistication level?" +4. "In what context will they use this feature? (device, environment, frequency)" +5. "Are there other user types who will interact with this?" + +**Maps to:** User Stories / Use Cases + +--- + +## Section 3: Functionality + +**Transition:** "Let's get into what this feature actually does." + +### Questions: + +1. "Walk me through the happy path - what does a user do from start to finish?" +2. "What are the absolute must-have capabilities (P0s)?" +3. "What would be nice to have but not essential (P1s)?" +4. "What inputs does the user provide?" +5. "What outputs or feedback do they receive?" +6. "How does this feature connect to existing features?" + +**Maps to:** Functional Requirements + +--- + +## Section 4: Constraints + +**Transition:** "Let's discuss any constraints or requirements beyond functionality." + +### Questions: + +1. "Are there performance requirements? (response time, throughput)" +2. "What are the security/privacy considerations?" +3. "Are there technology constraints? (must use X, can't use Y)" +4. "Are there compatibility requirements? (browsers, devices, APIs)" +5. "Are there compliance or regulatory requirements?" + +**Maps to:** Non-Functional Requirements + +--- + +## Section 5: Success Metrics + +**Transition:** "How will we know if this feature is successful?" + +### Questions: + +1. "What metrics would indicate success?" +2. "What's the target for each metric?" +3. "How will we measure these?" + +**Maps to:** Success Metrics + +--- + +## Section 6: Boundaries + +**Transition:** "It's equally important to define what's NOT in scope." + +### Questions: + +1. "What are we explicitly NOT building in this version?" +2. "What might users expect that we won't deliver?" +3. "Are there adjacent features we're deferring to later?" + +**Maps to:** Out of Scope + +--- + +## Section 7: Open Questions + +**Transition:** "Finally, let's capture what we still need to figure out." + +### Questions: + +1. "What decisions still need to be made?" +2. "What information are we missing?" +3. "What assumptions are we making that should be validated?" + +**Maps to:** Open Questions + +--- + +## Interview Wrap-Up + +**Summary Template:** +"Based on our discussion, here's what I understand: + +**Problem:** {summary} +**Users:** {summary} +**Core Functionality:** {summary} +**Key Constraints:** {summary} +**Success Looks Like:** {summary} +**Not Included:** {summary} +**Open Items:** {summary} + +Does this capture the feature correctly? Reply 'yes' to proceed to the Technical Architecture phase, or tell me what to adjust." diff --git a/claude/skills/feature-spec/templates/architecture.md b/claude/skills/feature-spec/templates/architecture.md new file mode 100644 index 0000000..7b26ec6 --- /dev/null +++ b/claude/skills/feature-spec/templates/architecture.md @@ -0,0 +1,170 @@ +# Technical Architecture: {Feature Name} + +**PRD Reference:** projects/{branch}/PRD.md +**Created:** {date} +**Status:** Draft + +--- + +## 1. System Overview + +### Purpose +{Brief description of what this architecture enables} + +### Scope +{Boundaries of this architecture document} + +### High-Level Diagram +``` +{ASCII diagram of system components} +``` + +--- + +## 2. Component Design + +### Component Overview + +| Component | Responsibility | Technology | +|-----------|---------------|------------| +| {name} | {what it does} | {tech stack} | + +### Component Details + +#### Component: {Name} + +**Purpose:** {what this component does} + +**Responsibilities:** +- {responsibility 1} +- {responsibility 2} + +**Interfaces:** +- Input: {interface description} +- Output: {interface description} + +**Error Handling:** +{error handling strategy} + +--- + +## 3. Data Flow + +### Primary Data Flow + +``` +[Input] -> [Component A] -> [Component B] -> [Output] +``` + +### Flow Description + +1. **{Step Name}:** {description} +2. **{Step Name}:** {description} + +### Data Transformations + +| Stage | Input Format | Output Format | Transformation | +|-------|--------------|---------------|----------------| +| {stage} | {format} | {format} | {what changes} | + +--- + +## 4. API/Interface Definitions + +### External APIs + +#### API: {Name} + +**Endpoint:** `{HTTP method} /path` + +**Request:** +```json +{ + "field": "type" +} +``` + +**Response:** +```json +{ + "field": "type" +} +``` + +**Error Codes:** +| Code | Description | +|------|-------------| +| {code} | {description} | + +### Internal Interfaces + +#### Interface: {Name} + +**Methods:** +- `{method signature}` - {description} + +--- + +## 5. Dependencies + +### External Dependencies + +| Dependency | Version | Purpose | Risk Level | +|------------|---------|---------|------------| +| {name} | {version} | {why needed} | Low/Medium/High | + +### Internal Dependencies + +| Module | Dependency Type | Impact if Unavailable | +|--------|-----------------|----------------------| +| {name} | {hard/soft} | {what breaks} | + +--- + +## 6. Implementation Approach + +### Development Phases + +| Phase | Scope | Deliverables | +|-------|-------|--------------| +| 1 | {scope} | {what gets delivered} | + +### Implementation Order + +1. **{Component/Feature}** - {rationale for order} +2. **{Component/Feature}** - {rationale} + +### Key Implementation Decisions + +| Decision | Options Considered | Choice | Rationale | +|----------|-------------------|--------|-----------| +| {decision} | {options} | {chosen} | {why} | + +--- + +## 7. Risk Assessment + +### Technical Risks + +| Risk | Probability | Impact | Mitigation | +|------|-------------|--------|------------| +| {risk} | Low/Med/High | Low/Med/High | {mitigation strategy} | + +### Security Considerations +{security risks and mitigations} + +### Performance Risks +{performance concerns and mitigations} + +--- + +## Appendix + +### Technology Stack Summary +- **Language:** {language} +- **Framework:** {framework} +- **Database:** {if applicable} +- **Infrastructure:** {if applicable} + +### References +- {reference 1} diff --git a/claude/skills/feature-spec/templates/prd.md b/claude/skills/feature-spec/templates/prd.md new file mode 100644 index 0000000..4637f45 --- /dev/null +++ b/claude/skills/feature-spec/templates/prd.md @@ -0,0 +1,112 @@ +# PRD: {Feature Name} + +**Branch:** {branch-name} +**Created:** {date} +**Status:** Draft + +--- + +## 1. Feature Overview / Problem Statement + +### Problem +{What problem does this feature solve?} + +### Context +{Background information and why this matters now} + +### Goal +{High-level objective of this feature} + +--- + +## 2. User Stories / Use Cases + +### Primary User Persona +{Description of the main user type} + +### User Stories + +| ID | As a... | I want to... | So that... | Priority | +|----|---------|--------------|------------|----------| +| US-1 | {role} | {action} | {benefit} | {P0/P1/P2} | + +### Use Case Scenarios + +#### Scenario 1: {Name} +**Preconditions:** {state before} +**Steps:** +1. {step} +2. {step} + +**Expected Outcome:** {result} + +--- + +## 3. Functional Requirements + +### Core Requirements + +| ID | Requirement | Description | Acceptance Criteria | +|----|-------------|-------------|---------------------| +| FR-1 | {name} | {description} | {criteria} | + +### Detailed Specifications + +#### FR-1: {Requirement Name} +- **Description:** {detailed description} +- **Behavior:** {expected behavior} +- **Inputs:** {input specifications} +- **Outputs:** {output specifications} + +--- + +## 4. Non-Functional Requirements + +### Performance +- {performance requirement} + +### Security +- {security requirement} + +### Scalability +- {scalability requirement} + +### Compatibility +- {compatibility requirement} + +--- + +## 5. Success Metrics + +| Metric | Target | Measurement Method | +|--------|--------|-------------------| +| {metric name} | {target value} | {how to measure} | + +--- + +## 6. Out of Scope + +The following are explicitly NOT part of this feature: +- {item 1} +- {item 2} + +### Future Considerations +- {potential future enhancement} + +--- + +## 7. Open Questions + +| ID | Question | Status | +|----|----------|--------| +| OQ-1 | {question} | Open/Resolved | + +--- + +## Appendix + +### Glossary +- **{term}:** {definition} + +### References +- {reference 1} diff --git a/claude/skills/feature-spec/templates/testing.md b/claude/skills/feature-spec/templates/testing.md new file mode 100644 index 0000000..1a06435 --- /dev/null +++ b/claude/skills/feature-spec/templates/testing.md @@ -0,0 +1,151 @@ +# Testing Document: {Feature Name} + +**PRD Reference:** projects/{branch}/PRD.md +**Architecture Reference:** projects/{branch}/ARCHITECTURE.md +**Created:** {date} +**Status:** Draft + +--- + +## 1. Unit Test Plan + +### Testing Strategy +{Overall unit testing approach} + +### Coverage Targets +- Line coverage: {target}% +- Branch coverage: {target}% + +### Object-Level Tests + +#### {Component/Class Name} + +**Test File:** `{path/to/test/file}` + +| Test ID | Method/Function | Test Description | Expected Result | +|---------|-----------------|------------------|-----------------| +| UT-1 | `{method}` | {what is being tested} | {expected outcome} | + +**Setup Requirements:** +- {mock/stub requirements} + +--- + +## 2. Integration Test Plan + +### Integration Scope +{What integrations are being tested} + +### Integration Points + +| Integration | Components Involved | Test Approach | +|-------------|--------------------| --------------| +| {name} | {component A} <-> {component B} | {strategy} | + +### Integration Test Cases + +#### Integration: {Name} + +**Test ID:** IT-1 + +**Description:** {what this integration test validates} + +**Preconditions:** +- {precondition 1} + +**Test Steps:** +1. {step} +2. {step} + +**Expected Results:** +- {expected outcome} + +--- + +## 3. Test Cases by Component + +### Component: {Name} + +**Test Coverage Summary:** +- Unit Tests: {count} +- Integration Tests: {count} + +#### Functional Tests + +| Test ID | Requirement | Test Description | Expected Result | Priority | +|---------|-------------|------------------|-----------------|----------| +| TC-1 | FR-1 | {description} | {expected} | P0/P1/P2 | + +--- + +## 4. Edge Cases + +### Identified Edge Cases + +| ID | Scenario | Input Condition | Expected Behavior | +|----|----------|-----------------|-------------------| +| EC-1 | {scenario name} | {condition} | {behavior} | + +### Boundary Conditions + +| Boundary | Min Value | Max Value | At Boundary | Beyond Boundary | +|----------|-----------|-----------|-------------|-----------------| +| {field} | {min} | {max} | {behavior} | {behavior} | + +### Error Conditions + +| Error Type | Trigger Condition | Expected Response | +|------------|-------------------|-------------------| +| {error} | {how to trigger} | {expected message/behavior} | + +--- + +## 5. Test Data Requirements + +### Test Data Sets + +| Data Set | Purpose | Size | Generation Method | +|----------|---------|------|-------------------| +| {name} | {what it tests} | {volume} | Manual/Generated | + +### Sample Test Data + +```json +{ + "example": "data structure" +} +``` + +### Data Cleanup Strategy +{how test data is managed/cleaned} + +--- + +## 6. Acceptance Criteria Mapping + +### PRD Requirement Traceability + +| Requirement ID | Requirement | Test Cases | Coverage Status | +|----------------|-------------|------------|-----------------| +| FR-1 | {requirement text} | TC-1, TC-2 | Covered/Partial | + +### User Story Validation + +| User Story | Acceptance Criteria | Test Case | Automated | +|------------|--------------------| ----------|-----------| +| US-1 | {criteria} | TC-1 | Yes/No | + +--- + +## Appendix + +### Test Environment Requirements +- {environment specifications} + +### Test Tools and Frameworks +- Unit Testing: {framework} +- Integration Testing: {framework} +- Mocking: {library} + +### Known Testing Limitations +- {limitation 1} From 394ef6062e23b41c632ddc3cf98004802c130560 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 8 Jan 2026 15:24:46 -0800 Subject: [PATCH 02/49] Reframe interview as engineering requirements, not product discovery - Rename PRD to Requirements document - Remove product-focused questions (personas, success metrics) - Add edge cases, error handling, concurrency questions - Add data persistence, scale, validation questions - Add integration points, backwards compatibility questions - Focus on concrete behavior and technical constraints Co-Authored-By: Claude Opus 4.5 --- claude/skills/feature-spec/SKILL.md | 118 ++++++------- .../references/interview-guide.md | 127 +++++++------- claude/skills/feature-spec/templates/prd.md | 112 ------------- .../feature-spec/templates/requirements.md | 158 ++++++++++++++++++ 4 files changed, 277 insertions(+), 238 deletions(-) delete mode 100644 claude/skills/feature-spec/templates/prd.md create mode 100644 claude/skills/feature-spec/templates/requirements.md diff --git a/claude/skills/feature-spec/SKILL.md b/claude/skills/feature-spec/SKILL.md index d70088a..6efee84 100644 --- a/claude/skills/feature-spec/SKILL.md +++ b/claude/skills/feature-spec/SKILL.md @@ -1,14 +1,16 @@ --- name: Feature Spec Generator -description: Use this skill when the user asks to "create a feature spec", "generate PRD", "spec out a feature", "plan a feature", "write product requirements", "create technical architecture", or needs comprehensive feature documentation before implementation. +description: Use this skill when the user asks to "create a feature spec", "spec out a feature", "write requirements", "document requirements", "create technical spec", or needs to clarify requirements before implementation. --- # Feature Spec Generator -Generate comprehensive feature documentation through a PM-style interview workflow. Creates three documents: -1. **PRD (Product Requirements Document)** - filled via interview -2. **Technical Architecture Document** - generated from PRD -3. **Testing Document** - generated from PRD + Architecture +Generate comprehensive feature documentation through a requirements interview. Creates three documents: +1. **Requirements Document** - filled via interview +2. **Technical Architecture Document** - generated from requirements +3. **Testing Document** - generated from requirements + architecture + +**Perspective:** Engineering manager peer helping clarify requirements before implementation. Focus on concrete behavior, edge cases, and technical constraints. ## Prerequisites @@ -20,7 +22,7 @@ Generate comprehensive feature documentation through a PM-style interview workfl Documents are saved to `projects/{feature-branch-name}/` in the repository root: ``` projects/{branch-name}/ -├── PRD.md +├── REQUIREMENTS.md ├── ARCHITECTURE.md └── TESTING.md ``` @@ -35,68 +37,72 @@ projects/{branch-name}/ 4. Announce the three-phase process to user **Opening message:** -"I'll help you create comprehensive feature documentation in three phases: -1. PRD (through a PM-style interview) +"I'll help you document this feature in three phases: +1. Requirements (through an interview) 2. Technical Architecture -3. Testing Document +3. Testing Plan -All documents will be saved to `projects/{branch-name}/`. Let's start with the PRD." +All documents will be saved to `projects/{branch-name}/`. Let's start by nailing down the requirements." -### Phase 1: PRD Interview +### Phase 1: Requirements Interview -Conduct a PM-style interview to understand and document the feature. Ask questions in batches of 2-3, waiting for responses before proceeding. +Conduct an interview to clarify and document requirements. Ask questions in batches of 2-3, waiting for responses before proceeding. **Interview sections** (in order): -1. **Problem Context** (maps to Feature Overview) - - What problem does this feature solve? - - Who experiences this problem and how often? - - Why is now the right time to address this? - -2. **Users and Personas** (maps to User Stories) - - Who is the primary user? - - What is their context when using this feature? - - Are there secondary user types? - -3. **Functionality** (maps to Functional Requirements) - - Walk through the happy path from start to finish - - What are the must-have capabilities (P0)? - - What inputs/outputs are involved? - -4. **Constraints** (maps to Non-Functional Requirements) - - Any performance requirements? - - Security/privacy considerations? - - Technical or compatibility constraints? - -5. **Success Criteria** (maps to Success Metrics) - - What metrics indicate success? - - How will we measure them? - -6. **Boundaries** (maps to Out of Scope) - - What are we explicitly NOT building? - - What might users expect that we won't deliver? - -7. **Open Items** (maps to Open Questions) - - What decisions still need to be made? - - What assumptions need validation? +1. **Core Behavior** (maps to Functional Requirements) + - Walk through the main flow step by step + - What are the inputs and outputs? + - What triggers this? + - What existing systems does this interact with? + +2. **Edge Cases & Error Handling** (maps to Edge Cases) + - What if input is malformed or missing? + - What if a dependency is unavailable? + - Rate limits, quotas, resource constraints? + - Retry/fallback behavior? + - Concurrency concerns? + +3. **Data & State** (maps to Data Requirements) + - What data is persisted vs ephemeral? + - Expected volume/scale? + - Validation rules? + - Sensitive data considerations? + +4. **Integration Points** (maps to Integration) + - What existing code/systems does this touch? + - APIs consumed or exposed? + - Database changes needed? + - Backwards compatibility concerns? + +5. **Scope Boundaries** (maps to Scope) + - What's the minimum viable version? + - What's explicitly out of scope? + - What can be hardcoded for now? + +6. **Open Questions** (maps to Open Questions) + - Technical unknowns or spikes needed? + - Decisions depending on external factors? + - Assumptions to validate? **Interview rules:** - Present 2-3 questions at a time -- Allow "skip" or "not sure yet" for optional items +- Allow "skip" or "I'll figure it out later" for items +- Dig into edge cases - that's where bugs hide - Summarize understanding after each section - At the end, present a summary and ask for confirmation **After interview completion:** -1. Load PRD template from `templates/prd.md` +1. Load requirements template from `templates/requirements.md` 2. Fill template with interview responses -3. Write to `projects/{branch}/PRD.md` -4. Present summary and ask: "Does this PRD capture the feature correctly? Reply 'yes' to proceed to Architecture, or tell me what to adjust." +3. Write to `projects/{branch}/REQUIREMENTS.md` +4. Present summary and ask: "Does this capture the requirements? Reply 'yes' to proceed to Architecture, or tell me what to adjust." ### Phase 2: Technical Architecture -Generate architecture document based on the completed PRD. +Generate architecture document based on the completed requirements. -1. Read the PRD from `projects/{branch}/PRD.md` +1. Read the requirements from `projects/{branch}/REQUIREMENTS.md` 2. Analyze requirements to determine: - System components needed - Data flows between components @@ -111,10 +117,10 @@ Generate architecture document based on the completed PRD. ### Phase 3: Testing Document -Generate testing document based on PRD and Architecture. +Generate testing document based on requirements and architecture. -1. Read PRD from `projects/{branch}/PRD.md` -2. Read Architecture from `projects/{branch}/ARCHITECTURE.md` +1. Read requirements from `projects/{branch}/REQUIREMENTS.md` +2. Read architecture from `projects/{branch}/ARCHITECTURE.md` 3. Generate test plan covering: - Unit tests for each component - Integration tests for component interactions @@ -129,13 +135,13 @@ Generate testing document based on PRD and Architecture. Present final summary: "Feature specification complete! Documents created: -- `projects/{branch}/PRD.md` +- `projects/{branch}/REQUIREMENTS.md` - `projects/{branch}/ARCHITECTURE.md` - `projects/{branch}/TESTING.md` Next steps: -1. Review documents with stakeholders -2. Address open questions in the PRD +1. Review documents +2. Resolve open questions 3. Begin implementation" ## Phase Transitions @@ -143,7 +149,7 @@ Next steps: | Transition | User Action Required | |------------|---------------------| | Init → Phase 1 | None | -| Phase 1 → Phase 2 | Explicit approval of PRD | +| Phase 1 → Phase 2 | Explicit approval of requirements | | Phase 2 → Phase 3 | Acknowledgment | | Phase 3 → Complete | None | diff --git a/claude/skills/feature-spec/references/interview-guide.md b/claude/skills/feature-spec/references/interview-guide.md index 02a9bdb..fe65aac 100644 --- a/claude/skills/feature-spec/references/interview-guide.md +++ b/claude/skills/feature-spec/references/interview-guide.md @@ -1,133 +1,120 @@ -# PRD Interview Guide +# Requirements Interview Guide ## Interview Principles 1. Ask 2-3 questions at a time 2. Wait for response before proceeding -3. Allow "skip" or "not sure yet" responses -4. Summarize understanding after each section +3. Allow "skip" or "I'll figure it out later" responses +4. Dig into edge cases and error conditions 5. Get explicit approval before moving to architecture phase ---- - -## Section 1: Problem Context - -**Transition:** "Let me understand the problem this feature solves." - -### Questions (present 2-3 at a time): - -1. "What specific problem does this feature solve?" -2. "Who is experiencing this problem? How often?" -3. "What happens if we don't solve this problem?" -4. "Why is now the right time to address this?" -5. "Are there existing workarounds users employ?" - -**Maps to:** Feature Overview / Problem Statement +**Perspective:** Engineering manager peer helping to clarify requirements before implementation. Focus on concrete behavior, edge cases, and technical constraints - not product strategy. --- -## Section 2: Users and Personas +## Section 1: Core Behavior -**Transition:** "Now let's talk about who will use this feature." +**Transition:** "Let's nail down exactly what this needs to do." ### Questions: -1. "Who is the primary user of this feature?" -2. "What is their role or context?" -3. "What is their technical sophistication level?" -4. "In what context will they use this feature? (device, environment, frequency)" -5. "Are there other user types who will interact with this?" +1. "Walk me through the main flow - what happens step by step?" +2. "What are the inputs? What are the outputs?" +3. "What triggers this? (user action, scheduled, event-driven?)" +4. "What existing systems or data does this interact with?" -**Maps to:** User Stories / Use Cases +**Maps to:** Functional Requirements --- -## Section 3: Functionality +## Section 2: Edge Cases & Error Handling -**Transition:** "Let's get into what this feature actually does." +**Transition:** "Now let's think about what could go wrong." ### Questions: -1. "Walk me through the happy path - what does a user do from start to finish?" -2. "What are the absolute must-have capabilities (P0s)?" -3. "What would be nice to have but not essential (P1s)?" -4. "What inputs does the user provide?" -5. "What outputs or feedback do they receive?" -6. "How does this feature connect to existing features?" +1. "What happens if the input is malformed or missing?" +2. "What if a dependency (API, database, service) is unavailable?" +3. "Are there rate limits, quotas, or resource constraints to handle?" +4. "What's the retry/fallback behavior when something fails?" +5. "Any race conditions or concurrency concerns?" -**Maps to:** Functional Requirements +**Maps to:** Edge Cases, Error Handling --- -## Section 4: Constraints +## Section 3: Data & State -**Transition:** "Let's discuss any constraints or requirements beyond functionality." +**Transition:** "Let's talk about data." ### Questions: -1. "Are there performance requirements? (response time, throughput)" -2. "What are the security/privacy considerations?" -3. "Are there technology constraints? (must use X, can't use Y)" -4. "Are there compatibility requirements? (browsers, devices, APIs)" -5. "Are there compliance or regulatory requirements?" +1. "What data needs to be persisted vs. ephemeral?" +2. "What's the expected data volume/scale?" +3. "Are there data validation rules or constraints?" +4. "Any data that needs to be kept in sync across systems?" +5. "Sensitive data considerations? (PII, credentials, etc.)" -**Maps to:** Non-Functional Requirements +**Maps to:** Data Requirements, Non-Functional Requirements --- -## Section 5: Success Metrics +## Section 4: Integration Points -**Transition:** "How will we know if this feature is successful?" +**Transition:** "How does this fit with existing systems?" ### Questions: -1. "What metrics would indicate success?" -2. "What's the target for each metric?" -3. "How will we measure these?" +1. "What existing code/systems does this touch or modify?" +2. "Any APIs being consumed or exposed?" +3. "Database changes needed? (new tables, schema changes)" +4. "Does this need to work with existing auth/permissions?" +5. "Any backwards compatibility concerns?" -**Maps to:** Success Metrics +**Maps to:** Dependencies, Integration Requirements --- -## Section 6: Boundaries +## Section 5: Scope Boundaries -**Transition:** "It's equally important to define what's NOT in scope." +**Transition:** "Let's be explicit about what's in and out of scope." ### Questions: -1. "What are we explicitly NOT building in this version?" -2. "What might users expect that we won't deliver?" -3. "Are there adjacent features we're deferring to later?" +1. "What's the minimum viable version of this?" +2. "What are we explicitly NOT doing in this iteration?" +3. "Any tempting additions we should resist for now?" +4. "What can we hardcode now and make configurable later?" -**Maps to:** Out of Scope +**Maps to:** Out of Scope, MVP Definition --- -## Section 7: Open Questions +## Section 6: Open Questions -**Transition:** "Finally, let's capture what we still need to figure out." +**Transition:** "What do we still need to figure out?" ### Questions: -1. "What decisions still need to be made?" -2. "What information are we missing?" -3. "What assumptions are we making that should be validated?" +1. "Any technical unknowns or spikes needed?" +2. "Decisions that depend on other teams or external factors?" +3. "Assumptions we're making that should be validated?" +4. "Anything blocked on more information?" -**Maps to:** Open Questions +**Maps to:** Open Questions, Technical Risks --- ## Interview Wrap-Up **Summary Template:** -"Based on our discussion, here's what I understand: - -**Problem:** {summary} -**Users:** {summary} -**Core Functionality:** {summary} -**Key Constraints:** {summary} -**Success Looks Like:** {summary} -**Not Included:** {summary} +"Here's my understanding: + +**Core Flow:** {summary} +**Key Edge Cases:** {summary} +**Data Considerations:** {summary} +**Integration Points:** {summary} +**Scope:** {what's in} / NOT: {what's out} **Open Items:** {summary} -Does this capture the feature correctly? Reply 'yes' to proceed to the Technical Architecture phase, or tell me what to adjust." +Does this capture the requirements? Reply 'yes' to proceed to Technical Architecture, or let me know what to adjust." diff --git a/claude/skills/feature-spec/templates/prd.md b/claude/skills/feature-spec/templates/prd.md deleted file mode 100644 index 4637f45..0000000 --- a/claude/skills/feature-spec/templates/prd.md +++ /dev/null @@ -1,112 +0,0 @@ -# PRD: {Feature Name} - -**Branch:** {branch-name} -**Created:** {date} -**Status:** Draft - ---- - -## 1. Feature Overview / Problem Statement - -### Problem -{What problem does this feature solve?} - -### Context -{Background information and why this matters now} - -### Goal -{High-level objective of this feature} - ---- - -## 2. User Stories / Use Cases - -### Primary User Persona -{Description of the main user type} - -### User Stories - -| ID | As a... | I want to... | So that... | Priority | -|----|---------|--------------|------------|----------| -| US-1 | {role} | {action} | {benefit} | {P0/P1/P2} | - -### Use Case Scenarios - -#### Scenario 1: {Name} -**Preconditions:** {state before} -**Steps:** -1. {step} -2. {step} - -**Expected Outcome:** {result} - ---- - -## 3. Functional Requirements - -### Core Requirements - -| ID | Requirement | Description | Acceptance Criteria | -|----|-------------|-------------|---------------------| -| FR-1 | {name} | {description} | {criteria} | - -### Detailed Specifications - -#### FR-1: {Requirement Name} -- **Description:** {detailed description} -- **Behavior:** {expected behavior} -- **Inputs:** {input specifications} -- **Outputs:** {output specifications} - ---- - -## 4. Non-Functional Requirements - -### Performance -- {performance requirement} - -### Security -- {security requirement} - -### Scalability -- {scalability requirement} - -### Compatibility -- {compatibility requirement} - ---- - -## 5. Success Metrics - -| Metric | Target | Measurement Method | -|--------|--------|-------------------| -| {metric name} | {target value} | {how to measure} | - ---- - -## 6. Out of Scope - -The following are explicitly NOT part of this feature: -- {item 1} -- {item 2} - -### Future Considerations -- {potential future enhancement} - ---- - -## 7. Open Questions - -| ID | Question | Status | -|----|----------|--------| -| OQ-1 | {question} | Open/Resolved | - ---- - -## Appendix - -### Glossary -- **{term}:** {definition} - -### References -- {reference 1} diff --git a/claude/skills/feature-spec/templates/requirements.md b/claude/skills/feature-spec/templates/requirements.md new file mode 100644 index 0000000..03a8e28 --- /dev/null +++ b/claude/skills/feature-spec/templates/requirements.md @@ -0,0 +1,158 @@ +# Requirements: {Feature Name} + +**Branch:** {branch-name} +**Created:** {date} +**Status:** Draft + +--- + +## 1. Overview + +### Summary +{One paragraph describing what this feature does} + +### Trigger +{What initiates this feature - user action, event, schedule, etc.} + +--- + +## 2. Functional Requirements + +### Core Flow + +1. {Step 1} +2. {Step 2} +3. {Step 3} + +### Inputs + +| Input | Type | Required | Description | +|-------|------|----------|-------------| +| {name} | {type} | Yes/No | {description} | + +### Outputs + +| Output | Type | Description | +|--------|------|-------------| +| {name} | {type} | {description} | + +### Detailed Behavior + +#### {Scenario Name} +**Given:** {precondition} +**When:** {action} +**Then:** {expected result} + +--- + +## 3. Edge Cases & Error Handling + +### Input Validation + +| Condition | Expected Behavior | +|-----------|-------------------| +| Missing required field | {behavior} | +| Invalid format | {behavior} | +| {other condition} | {behavior} | + +### Failure Scenarios + +| Failure | Handling | Retry? | +|---------|----------|--------| +| Dependency unavailable | {fallback behavior} | Yes/No | +| Timeout | {behavior} | Yes/No | +| {other failure} | {behavior} | Yes/No | + +### Concurrency Considerations +{Race conditions, locking, idempotency requirements} + +--- + +## 4. Data Requirements + +### Persistence + +| Data | Storage | Retention | +|------|---------|-----------| +| {data item} | {where stored} | {how long} | + +### Data Validation Rules +- {rule 1} +- {rule 2} + +### Sensitive Data +{PII, credentials, or other sensitive data considerations} + +### Scale Expectations +- Expected volume: {volume} +- Growth rate: {rate} + +--- + +## 5. Integration Points + +### Systems Touched + +| System | Interaction | Impact | +|--------|-------------|--------| +| {system} | Read/Write/Both | {what changes} | + +### APIs + +#### Consumed +| API | Purpose | Auth | +|-----|---------|------| +| {api} | {why needed} | {auth method} | + +#### Exposed +| Endpoint | Method | Purpose | +|----------|--------|---------| +| {path} | GET/POST/etc | {description} | + +### Database Changes +{Schema changes, new tables, migrations needed} + +### Backwards Compatibility +{Breaking changes, deprecations, migration path} + +--- + +## 6. Scope + +### In Scope (MVP) +- {item 1} +- {item 2} + +### Explicitly Out of Scope +- {item 1} - {reason/deferral} +- {item 2} - {reason/deferral} + +### Hardcoded for Now +| Item | Current Value | Make Configurable When | +|------|---------------|------------------------| +| {item} | {value} | {trigger} | + +--- + +## 7. Open Questions + +| Question | Blocker? | Owner | Notes | +|----------|----------|-------|-------| +| {question} | Yes/No | {who} | {context} | + +### Assumptions to Validate +- {assumption 1} +- {assumption 2} + +### Technical Unknowns +- {unknown 1} - {spike needed?} + +--- + +## Appendix + +### Glossary +- **{term}:** {definition} + +### References +- {reference 1} From d8ef9950bf15119daf2b52d8c9e3b6b571e27cea Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Wed, 7 Jan 2026 18:01:18 -0800 Subject: [PATCH 03/49] let it skip tests if no tests exist --- claude/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index fe02566..15a2a65 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -45,7 +45,7 @@ "hooks": [ { "type": "prompt", - "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken." + "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. If no tests exists, then ignore this." } ] } From 8aecf48558d17cbea83a8953d53b3abee5cc1791 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Wed, 7 Jan 2026 19:55:04 -0800 Subject: [PATCH 04/49] more logging for sending usage data --- claude/hooks/send-usage-data.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/claude/hooks/send-usage-data.py b/claude/hooks/send-usage-data.py index ab869c3..502e2a1 100755 --- a/claude/hooks/send-usage-data.py +++ b/claude/hooks/send-usage-data.py @@ -68,29 +68,37 @@ def post_to_api(data): return response.status, response.read().decode("utf-8") def main(): + print("Sending usage data...") + input_data = json.loads(sys.stdin.read()) transcript_path = input_data.get("transcript_path") session_id = input_data.get("session_id") if not transcript_path or not os.path.exists(transcript_path): + print("no transcript path") return if not session_id: + print("no session id") return if not API_TOKEN: + print("No API token for destination server") return entry = read_last_jsonl_entry(transcript_path) if not entry: + print("No jsonl entry") return usage_data = extract_usage_data(entry, session_id) if not usage_data: + print("No usage data in jsonl entry") return post_to_api(usage_data) + print(f"usage data sent to {API_URL}") if __name__ == "__main__": main() From 826564e8fcbbe192c87f0339e01a9590cf25665a Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Wed, 7 Jan 2026 21:57:56 -0800 Subject: [PATCH 05/49] remove unused ralph wiggum plugin --- claude/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index 15a2a65..76796e4 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -85,7 +85,6 @@ "feature-dev@claude-plugins-official": true, "code-review@claude-plugins-official": true, "commit-commands@claude-plugins-official": true, - "ralph-wiggum@claude-plugins-official": true, "plugin-dev@claude-plugins-official": true, "hookify@claude-plugins-official": true, "agent-sdk-dev@claude-plugins-official": true, From bedc45db68948780d378b99fa349f0dff9206653 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 8 Jan 2026 13:58:58 -0800 Subject: [PATCH 06/49] remove hookify plugin --- claude/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index 76796e4..8924b1c 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -86,7 +86,6 @@ "code-review@claude-plugins-official": true, "commit-commands@claude-plugins-official": true, "plugin-dev@claude-plugins-official": true, - "hookify@claude-plugins-official": true, "agent-sdk-dev@claude-plugins-official": true, "pr-review-toolkit@claude-plugins-official": true } From a4d1c91a3aa25972ec15121e406c3e46da8bd0d7 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sun, 11 Jan 2026 14:33:09 -0800 Subject: [PATCH 07/49] command for addressing pr comments --- claude/commands/address-pr-comments.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 claude/commands/address-pr-comments.md diff --git a/claude/commands/address-pr-comments.md b/claude/commands/address-pr-comments.md new file mode 100644 index 0000000..7105e04 --- /dev/null +++ b/claude/commands/address-pr-comments.md @@ -0,0 +1,11 @@ +User input: $ARGUMENTS + +Pull the comments from the user input or, if that is blank, the corresponding PR to this branch. + +The user probably put in two kinds of comments: +1. Questions for discussion or clarification +2. Direct commands to change how code words + +Respond to type #1 directly on the relevant comment. + +Address type #2 comments by making changes in code then push the changes to the branch. From 6afce88f5590b0ecb0e6ff80880ba3c8055458dc Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sun, 11 Jan 2026 14:41:17 -0800 Subject: [PATCH 08/49] tighten up failing PostToolUse hook --- claude/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index 8924b1c..0a2c9d2 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -45,7 +45,7 @@ "hooks": [ { "type": "prompt", - "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. If no tests exists, then ignore this." + "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. If no tests exists, then continue. If no changes were made or no testable changes were made (e.g. documentation), then continue." } ] } From 4869e808c056c8c264aebef9499e3cbcb3bd07ad Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sun, 11 Jan 2026 15:06:01 -0800 Subject: [PATCH 09/49] show simplecov data after running specs --- claude/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index 0a2c9d2..8132343 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -45,7 +45,7 @@ "hooks": [ { "type": "prompt", - "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. If no tests exists, then continue. If no changes were made or no testable changes were made (e.g. documentation), then continue." + "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. Then show the data from any coverage libraries. If no tests exists, then continue. If no changes were made or no testable changes were made (e.g. documentation), then continue." } ] } From 3789accebe34db4adc0afa07c43350ac58f095f5 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sun, 11 Jan 2026 18:18:09 -0800 Subject: [PATCH 10/49] add instruction to avoid duplication --- claude/CLAUDE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/claude/CLAUDE.md b/claude/CLAUDE.md index 3808de2..f9e0650 100644 --- a/claude/CLAUDE.md +++ b/claude/CLAUDE.md @@ -22,3 +22,6 @@ - When specifying a path, use paths relative to the current working directory, rather than absolute paths. - There is no need to cd into the local directory. Instead, first check your current working directory. If it is the repo's directory, then run the command. - When running commands, unless there is a specific reason to combine stderr and stdout using `2>&1`, don't do it. + +# Maintaining CLAUDE.md +- There should be one place where information is maintained. Rely on the underlying framework to do this. Don't create a separate list. (For example, in Rails, don't list the routes in CLAUDE.md. The routes are already documented in config/rails.rb.) Instead, in the Claude.md file, point to the file where the information is stored. From 7eeccd043ad125af1631eca6d146b8f340903e1a Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sun, 11 Jan 2026 19:31:09 -0800 Subject: [PATCH 11/49] tell it to stop guarding! --- claude/CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/claude/CLAUDE.md b/claude/CLAUDE.md index f9e0650..63e3828 100644 --- a/claude/CLAUDE.md +++ b/claude/CLAUDE.md @@ -5,6 +5,7 @@ - No argument validation at function start - No try-catch unless explicitly asked - Make minimal changes when fixing issues - don't refactor unrelated code +- Don't guard against missing attributes unless explicitly asked. Let the system fail. # Git Workflow From c0b1ffc5d94de77b6d2311db2c33f32c8b51f7b8 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sun, 11 Jan 2026 19:54:29 -0800 Subject: [PATCH 12/49] allow claude to run rspec --- claude/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index 8132343..bc62524 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -19,7 +19,9 @@ "Bash(cd:*)", "WebSearch", "Bash(bundle install:*)", - "Bash(rails generate:*)" + "Bash(rails generate:*)", + "Bash(rspec:*)", + "Bash(bundle exec rspec:*)" ], "deny": [], "ask": [ From f463354eadf03f8b4f34292042d53eb12140eaeb Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sun, 11 Jan 2026 22:03:09 -0800 Subject: [PATCH 13/49] try to be more specific about not holding up the program --- claude/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index bc62524..238d550 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -47,7 +47,7 @@ "hooks": [ { "type": "prompt", - "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. Then show the data from any coverage libraries. If no tests exists, then continue. If no changes were made or no testable changes were made (e.g. documentation), then continue." + "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. Then show the data from any coverage libraries. If no tests exists, then let the program continue. If no changes were made or no testable changes were made (e.g. documentation), then let the program continue." } ] } From 1edde38fa98197d4ffa8c345b402f9aad1430ed5 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Mon, 12 Jan 2026 11:43:20 -0800 Subject: [PATCH 14/49] run tests before stopping --- claude/hooks/run-tests.sh | 9 +++++++++ claude/settings.json | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100755 claude/hooks/run-tests.sh diff --git a/claude/hooks/run-tests.sh b/claude/hooks/run-tests.sh new file mode 100755 index 0000000..3ffe913 --- /dev/null +++ b/claude/hooks/run-tests.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [ -x "bin/test.sh" ]; then + bin/test.sh + exit $? +else + echo "bin/test.sh not found or not executable. Each project should have a bin/test.sh script." >&2 + exit 1 +fi diff --git a/claude/settings.json b/claude/settings.json index 238d550..994ef2b 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -46,8 +46,8 @@ "matcher": "Write|Edit", "hooks": [ { - "type": "prompt", - "prompt": "if any changes were made, run the tests to ensure that everything still works. Fix any thing that is broken. Then show the data from any coverage libraries. If no tests exists, then let the program continue. If no changes were made or no testable changes were made (e.g. documentation), then let the program continue." + "type": "command", + "command": "~/.claude/hooks/run-tests.sh" } ] } From 75d114f9ddca5545396dc00ec44f2ad4c109f1b3 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Mon, 12 Jan 2026 11:47:24 -0800 Subject: [PATCH 15/49] update symlink script to not create nested symlinks inside claude/commands and claude/hooks --- symlink_script.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/symlink_script.sh b/symlink_script.sh index fb14231..1b18081 100755 --- a/symlink_script.sh +++ b/symlink_script.sh @@ -12,8 +12,9 @@ mkdir -p "$HOME/.claude" for item in claude/*; do name=$(basename "$item") target="$HOME/.claude/$name" - if [ -d "$target" ] && [ ! -L "$target" ]; then - echo "Removing existing directory $target" + if [ -L "$target" ]; then + rm -f "$target" + elif [ -d "$target" ]; then rm -rf "$target" fi echo "Linking ~/.claude/$name to $PWD/$item" From 27af10ecf8e8b961083fba79df5a22f9c7df4f7d Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Wed, 14 Jan 2026 16:09:41 -0800 Subject: [PATCH 16/49] getting all comments on a PR using gh command --- claude/CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/claude/CLAUDE.md b/claude/CLAUDE.md index 63e3828..542f9e3 100644 --- a/claude/CLAUDE.md +++ b/claude/CLAUDE.md @@ -12,6 +12,7 @@ - `gh` is aliased to `git hist`, so use `command gh` for GitHub CLI - Prefer rebase over merge when updating feature branches from master/main - Name worktrees as `{repo-name}--{branch-name}` +- When fetching comments with the `gh` utility, use --paginate to make sure you get all of the comments. # Process From ebbcaab8878d811b22749349e8ebb04e5442a930 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 12:13:30 -0800 Subject: [PATCH 17/49] simplify git and github cli perissions for claude --- claude/settings.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/claude/settings.json b/claude/settings.json index 994ef2b..1ab11f4 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -1,14 +1,7 @@ { "permissions": { "allow": [ - "Bash(git push:*)", - "Bash(git fetch:*)", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(git checkout:*)", - "Bash(git worktree:*)", - "Bash(git branch:*)", - "Bash(git rebase:*)", + "Bash(git:*)", "Bash(command gh:*)", "Bash(grep:*)", "Bash(cat:*)", @@ -25,7 +18,14 @@ ], "deny": [], "ask": [ - "Bash(git merge:*)" + "Bash(git merge:*)", + "Bash(rm:*)", + "Bash(command gh repo archive:*)", + "Bash(command gh repo delete:*)", + "Bash(command gh repo edit:*)", + "Bash(command gh repo rename:*)", + "Bash(command gh pr merge:*)", + "Bash(command gh pr close:*)", ], "defaultMode": "default" }, From 3c1cab188f0a27b8a721672b5fe2b8c1481e123f Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 12:51:23 -0800 Subject: [PATCH 18/49] allow webfetch --- .claude/settings.json | 1 - claude/settings.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/settings.json b/.claude/settings.json index 44279c4..34eacd4 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,7 +1,6 @@ { "permissions": { "allow": [ - "WebFetch(domain:gist.github.com)" ] } } diff --git a/claude/settings.json b/claude/settings.json index 1ab11f4..735e1fb 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -11,6 +11,7 @@ "Bash(cp:*)", "Bash(cd:*)", "WebSearch", + "WebFetch(domain:*)", "Bash(bundle install:*)", "Bash(rails generate:*)", "Bash(rspec:*)", From 1a3ae83c0e6ca6133818c3e3eab9b5866e66daca Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 12:51:54 -0800 Subject: [PATCH 19/49] deny claude to look at the rails credentias --- claude/settings.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/claude/settings.json b/claude/settings.json index 735e1fb..786b507 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -17,7 +17,9 @@ "Bash(rspec:*)", "Bash(bundle exec rspec:*)" ], - "deny": [], + "deny": [ + "Bash(bin/rails credentials/show)" + ], "ask": [ "Bash(git merge:*)", "Bash(rm:*)", @@ -26,7 +28,7 @@ "Bash(command gh repo edit:*)", "Bash(command gh repo rename:*)", "Bash(command gh pr merge:*)", - "Bash(command gh pr close:*)", + "Bash(command gh pr close:*)" ], "defaultMode": "default" }, From 1cee353a15e1d6f8a28fae8a76c9c8a44664dbd1 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 12:52:09 -0800 Subject: [PATCH 20/49] remove hook that seems to be blocking usage data being sent up --- claude/settings.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/claude/settings.json b/claude/settings.json index 786b507..7a315ca 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -58,10 +58,6 @@ "Stop": [ { "hooks": [ - { - "type": "prompt", - "prompt": "If you changed any front end UI, go use Chrome and look at what you change and decide if it looks good. If it does not, update it until you think it looks good to present to the user." - }, { "type": "command", "command": "~/.claude/hooks/send-usage-data.py" From 32ea7f096986155b05e8d57e2537940dea3619f5 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 12:56:30 -0800 Subject: [PATCH 21/49] set up sending to dev environment --- claude/hooks/.env.example | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/claude/hooks/.env.example b/claude/hooks/.env.example index 1111d3e..0b8056a 100644 --- a/claude/hooks/.env.example +++ b/claude/hooks/.env.example @@ -1,2 +1,5 @@ -TOKEN_USAGE_API_HOST="localhost:1234" -TOKEN_USAGE_API_TOKEN="abc123" +TOKEN_USAGE_API_HOST_DEV=localhost:3000 +TOKEN_USAGE_API_TOKEN_DEV=abc123 + +TOKEN_USAGE_API_HOST_PROD=example.com +TOKEN_USAGE_API_TOKEN_PROD=abc123 From e4f0fe5617019dd592091110d28825ac0382b767 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 14:01:22 -0800 Subject: [PATCH 22/49] adapt to new jsonl structure for token usage uploading --- claude/hooks/.env.example | 2 +- claude/hooks/send-usage-data.py | 60 +++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/claude/hooks/.env.example b/claude/hooks/.env.example index 0b8056a..28c4cd7 100644 --- a/claude/hooks/.env.example +++ b/claude/hooks/.env.example @@ -1,4 +1,4 @@ -TOKEN_USAGE_API_HOST_DEV=localhost:3000 +TOKEN_USAGE_API_HOST_DEV=https://localhost:3000 TOKEN_USAGE_API_TOKEN_DEV=abc123 TOKEN_USAGE_API_HOST_PROD=example.com diff --git a/claude/hooks/send-usage-data.py b/claude/hooks/send-usage-data.py index 502e2a1..7374142 100755 --- a/claude/hooks/send-usage-data.py +++ b/claude/hooks/send-usage-data.py @@ -21,20 +21,27 @@ def load_env(): load_env() -API_HOST = os.environ.get("TOKEN_USAGE_API_HOST", "http://localhost:3000") -API_URL = f"{API_HOST.rstrip('/')}/token_usage" -API_TOKEN = os.environ.get("TOKEN_USAGE_API_TOKEN", "") - -def read_last_jsonl_entry(transcript_path): - last_line = None +ENVIRONMENTS = { + "dev": { + "host": os.environ.get("TOKEN_USAGE_API_HOST_DEV", ""), + "token": os.environ.get("TOKEN_USAGE_API_TOKEN_DEV", ""), + }, + "prod": { + "host": os.environ.get("TOKEN_USAGE_API_HOST_PROD", ""), + "token": os.environ.get("TOKEN_USAGE_API_TOKEN_PROD", ""), + }, +} + +def read_last_assistant_entry(transcript_path): + last_assistant = None with open(transcript_path, "r") as f: for line in f: line = line.strip() if line: - last_line = line - if last_line: - return json.loads(last_line) - return None + entry = json.loads(line) + if entry.get("type") == "assistant" and entry.get("message", {}).get("usage"): + last_assistant = entry + return last_assistant def extract_usage_data(entry, session_id): message = entry.get("message", {}) @@ -55,14 +62,15 @@ def extract_usage_data(entry, session_id): "error_message": error.get("message") if error else None } -def post_to_api(data): - payload = json.dumps({"datum": data}).encode("utf-8") +def post_to_api(data, host, token): + url = f"{host.rstrip('/')}/token_usage" + payload = json.dumps({"token_usage": data}).encode("utf-8") headers = { "Content-Type": "application/json", - "Authorization": f"Bearer {API_TOKEN}" + "Authorization": f"Bearer {token}" } - req = urllib.request.Request(API_URL, data=payload, headers=headers, method="POST") + req = urllib.request.Request(url, data=payload, headers=headers, method="POST") with urllib.request.urlopen(req, timeout=30) as response: return response.status, response.read().decode("utf-8") @@ -83,13 +91,9 @@ def main(): print("no session id") return - if not API_TOKEN: - print("No API token for destination server") - return - - entry = read_last_jsonl_entry(transcript_path) + entry = read_last_assistant_entry(transcript_path) if not entry: - print("No jsonl entry") + print("No assistant entry with usage data") return usage_data = extract_usage_data(entry, session_id) @@ -97,8 +101,20 @@ def main(): print("No usage data in jsonl entry") return - post_to_api(usage_data) - print(f"usage data sent to {API_URL}") + print(f"Reporting: input_tokens={usage_data['input_tokens']}, output_tokens={usage_data['output_tokens']}, session_id={usage_data['session_id']}, message_uuid={usage_data['message_uuid']}") + + for env_name, env_config in ENVIRONMENTS.items(): + host = env_config["host"] + token = env_config["token"] + if not host or not token: + print(f"Skipping {env_name}: missing host or token") + continue + print(f"Attempting: {host}") + try: + post_to_api(usage_data, host, token) + print(f"Success") + except Exception as e: + print(f"Failed: {e}") if __name__ == "__main__": main() From 696e3d6f40f657193b0e801f23989d788ede2f2c Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 14:34:30 -0800 Subject: [PATCH 23/49] prevent git push master or main by claude --- claude/settings.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/claude/settings.json b/claude/settings.json index 7a315ca..9f52522 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -22,6 +22,8 @@ ], "ask": [ "Bash(git merge:*)", + "Bash(git push master)", + "Bash(git push main)", "Bash(rm:*)", "Bash(command gh repo archive:*)", "Bash(command gh repo delete:*)", From 5f090e61aad2567ea3bf21bcd627da175e3ad312 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 15:03:00 -0800 Subject: [PATCH 24/49] Fix hook exit codes to show output to user (#9) Use exit code 1 with stderr output so users can see usage reporting status. Exit code 0 hides all output, while exit code 2 would inject messages into the model context unnecessarily. Co-authored-by: Claude Opus 4.5 --- claude/hooks/send-usage-data.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/claude/hooks/send-usage-data.py b/claude/hooks/send-usage-data.py index 7374142..8dc3771 100755 --- a/claude/hooks/send-usage-data.py +++ b/claude/hooks/send-usage-data.py @@ -76,7 +76,7 @@ def post_to_api(data, host, token): return response.status, response.read().decode("utf-8") def main(): - print("Sending usage data...") + print("Sending usage data...", file=sys.stderr) input_data = json.loads(sys.stdin.read()) @@ -84,37 +84,39 @@ def main(): session_id = input_data.get("session_id") if not transcript_path or not os.path.exists(transcript_path): - print("no transcript path") - return + print("no transcript path", file=sys.stderr) + sys.exit(1) if not session_id: - print("no session id") - return + print("no session id", file=sys.stderr) + sys.exit(1) entry = read_last_assistant_entry(transcript_path) if not entry: - print("No assistant entry with usage data") - return + print("No assistant entry with usage data", file=sys.stderr) + sys.exit(1) usage_data = extract_usage_data(entry, session_id) if not usage_data: - print("No usage data in jsonl entry") - return + print("No usage data in jsonl entry", file=sys.stderr) + sys.exit(1) - print(f"Reporting: input_tokens={usage_data['input_tokens']}, output_tokens={usage_data['output_tokens']}, session_id={usage_data['session_id']}, message_uuid={usage_data['message_uuid']}") + print(f"Reporting: input_tokens={usage_data['input_tokens']}, output_tokens={usage_data['output_tokens']}", file=sys.stderr) for env_name, env_config in ENVIRONMENTS.items(): host = env_config["host"] token = env_config["token"] if not host or not token: - print(f"Skipping {env_name}: missing host or token") + print(f"Skipping {env_name}: missing host or token", file=sys.stderr) continue - print(f"Attempting: {host}") + print(f"Attempting: {host}", file=sys.stderr) try: post_to_api(usage_data, host, token) - print(f"Success") + print(f"Success", file=sys.stderr) except Exception as e: - print(f"Failed: {e}") + print(f"Failed: {e}", file=sys.stderr) + + sys.exit(1) if __name__ == "__main__": main() From e243e5d2db9df7a37612ebc932aecdf5e08ffee1 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 15:26:53 -0800 Subject: [PATCH 25/49] Update new-worktree command to give explicit handoff instructions Remove /add-dir and cd instructions that don't work mid-session. Instead, output a copy-pasteable command for the user to run in a new terminal. Co-Authored-By: Claude Opus 4.5 --- claude/commands/new-worktree.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/claude/commands/new-worktree.md b/claude/commands/new-worktree.md index b5515a4..cf93ff5 100644 --- a/claude/commands/new-worktree.md +++ b/claude/commands/new-worktree.md @@ -1,9 +1,14 @@ $ARGUMENTS -Create a new worktree for this feature. Worktree name should be {repo-name}--{worktree-name}. Put the worktree in a folder that a sibling of the current repo. - -After creating the new worktree: -1. use the /add-dir command to add the new worktree into current Claude's permission instance. -2. `cd` into the new worktree as your new working directory +Create a new worktree for this feature. Worktree name should be {repo-name}--{worktree-name}. Put the worktree in a folder that is a sibling of the current repo. Be sure to specify in the git command to create a worktree off of master or main branch, not the current branch. + +After creating the worktree, output: + +``` +Worktree created. Open a new terminal tab and run: +cd {worktree-path} && claude +``` + +Where {worktree-path} is the relative path to the worktree (e.g., `../myrepo--feature-branch`). From ba1c0dbfa5bdca5f505941a91a22645e3f75de06 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 18:34:16 -0800 Subject: [PATCH 26/49] Add catch-up sync for usage data (#10) * get id of latest message in server then send up all messages after that * simplify the code * only spit out simplified error * catch the other error * send to stderr * format prettier error * fix logic --- claude/hooks/send-usage-data.py | 121 +++++++++++++++++--------------- 1 file changed, 63 insertions(+), 58 deletions(-) diff --git a/claude/hooks/send-usage-data.py b/claude/hooks/send-usage-data.py index 8dc3771..c714208 100755 --- a/claude/hooks/send-usage-data.py +++ b/claude/hooks/send-usage-data.py @@ -21,50 +21,56 @@ def load_env(): load_env() -ENVIRONMENTS = { - "dev": { - "host": os.environ.get("TOKEN_USAGE_API_HOST_DEV", ""), - "token": os.environ.get("TOKEN_USAGE_API_TOKEN_DEV", ""), - }, - "prod": { - "host": os.environ.get("TOKEN_USAGE_API_HOST_PROD", ""), - "token": os.environ.get("TOKEN_USAGE_API_TOKEN_PROD", ""), - }, -} - -def read_last_assistant_entry(transcript_path): - last_assistant = None +def get_last_message_id(session_id, host, api_token): + url = f"{host.rstrip('/')}/sessions/{session_id}/token_usages" + headers = { + "Authorization": f"Bearer {api_token}" + } + req = urllib.request.Request(url, headers=headers, method="GET") + with urllib.request.urlopen(req, timeout=30) as response: + data = json.loads(response.read().decode("utf-8")) + if data and len(data) > 0: + return data[0].get("message_id") + return None + +def get_usage_data(transcript_path, after_message_id, session_id): + usage_data = {} + after_message_has_been_passed = False with open(transcript_path, "r") as f: for line in f: line = line.strip() - if line: - entry = json.loads(line) - if entry.get("type") == "assistant" and entry.get("message", {}).get("usage"): - last_assistant = entry - return last_assistant + entry = json.loads(line) + msg_id = entry.get("message", {}).get("id") + + if not after_message_has_been_passed: + if msg_id == after_message_id: + after_message_has_been_passed = True + continue + elif entry.get("type") == "assistant": + usage_data[msg_id] = extract_usage_data(entry, session_id) + + # Doing this de-duplicates the usage_data. Clever! + return list(usage_data.values()) def extract_usage_data(entry, session_id): message = entry.get("message", {}) - message_uuid = entry.get("uuid") - - if not message_uuid: - return None + message_id = message.get("id") usage = message.get("usage", {}) error = message.get("error", {}) return { "session_id": session_id, - "message_uuid": message_uuid, + "message_id": message_id, "input_tokens": usage.get("input_tokens", 0), "output_tokens": usage.get("output_tokens", 0), "error_type": error.get("type") if error else None, "error_message": error.get("message") if error else None } -def post_to_api(data, host, token): - url = f"{host.rstrip('/')}/token_usage" - payload = json.dumps({"token_usage": data}).encode("utf-8") +def post_usage_datum(datum, host, token): + url = f"{host.rstrip('/')}/token_usages" + payload = json.dumps({"token_usage": datum}).encode("utf-8") headers = { "Content-Type": "application/json", "Authorization": f"Bearer {token}" @@ -75,6 +81,27 @@ def post_to_api(data, host, token): with urllib.request.urlopen(req, timeout=30) as response: return response.status, response.read().decode("utf-8") +def send_usage_data(usage_data, host, token): + for usage_datum in usage_data: + try: + post_usage_datum(usage_datum, host, token) + except urllib.error.HTTPError as e: + print(f"Failed: {e.url} {e.code} - {e.reason}", file=sys.stderr) + +def extract_and_send_usage_data_for_env(transcript_path, session_id, host, api_token): + try: + last_message_id = get_last_message_id(session_id, host, api_token) + except urllib.error.HTTPError as e: + print(f"Failed: {e.url} {e.code} - {e.reason}", file=sys.stderr) + return + + usage_data = get_usage_data(transcript_path, last_message_id, session_id) + + total_input = sum(e.get("input_tokens", 0) for e in usage_data) + total_output = sum(e.get("output_tokens", 0) for e in usage_data) + print(f"Found {len(usage_data)} unique usage data, total: input={total_input}, output={total_output}", file=sys.stderr) + send_usage_data(usage_data, host, api_token) + def main(): print("Sending usage data...", file=sys.stderr) @@ -83,38 +110,16 @@ def main(): transcript_path = input_data.get("transcript_path") session_id = input_data.get("session_id") - if not transcript_path or not os.path.exists(transcript_path): - print("no transcript path", file=sys.stderr) - sys.exit(1) - - if not session_id: - print("no session id", file=sys.stderr) - sys.exit(1) - - entry = read_last_assistant_entry(transcript_path) - if not entry: - print("No assistant entry with usage data", file=sys.stderr) - sys.exit(1) - - usage_data = extract_usage_data(entry, session_id) - if not usage_data: - print("No usage data in jsonl entry", file=sys.stderr) - sys.exit(1) - - print(f"Reporting: input_tokens={usage_data['input_tokens']}, output_tokens={usage_data['output_tokens']}", file=sys.stderr) - - for env_name, env_config in ENVIRONMENTS.items(): - host = env_config["host"] - token = env_config["token"] - if not host or not token: - print(f"Skipping {env_name}: missing host or token", file=sys.stderr) - continue - print(f"Attempting: {host}", file=sys.stderr) - try: - post_to_api(usage_data, host, token) - print(f"Success", file=sys.stderr) - except Exception as e: - print(f"Failed: {e}", file=sys.stderr) + dev_host = os.environ.get("TOKEN_USAGE_API_HOST_DEV", "") + dev_api_token = os.environ.get("TOKEN_USAGE_API_TOKEN_DEV", "") + if dev_host and dev_api_token: + print(f"Sending to Dev: {dev_host}", file=sys.stderr) + extract_and_send_usage_data_for_env(transcript_path, session_id, dev_host, dev_api_token) + + prod_host = os.environ.get("TOKEN_USAGE_API_HOST_PROD", "") + prod_api_token = os.environ.get("TOKEN_USAGE_API_TOKEN_PROD", "") + print(f"Sending to Prod: {prod_host}", file=sys.stderr) + extract_and_send_usage_data_for_env(transcript_path, session_id, prod_host, prod_api_token) sys.exit(1) From ba3de14d638804dfd6269e9fdf2c5065c6e519c1 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 19:04:23 -0800 Subject: [PATCH 27/49] Fix usage data collection for subagents (#11) SubagentStop provides agent_transcript_path for the subagent's transcript, not transcript_path which is the main session. Use agent_id as session_id for subagent usage tracking. Co-authored-by: Claude Opus 4.5 --- claude/hooks/send-usage-data.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/claude/hooks/send-usage-data.py b/claude/hooks/send-usage-data.py index c714208..4ecd989 100755 --- a/claude/hooks/send-usage-data.py +++ b/claude/hooks/send-usage-data.py @@ -107,8 +107,13 @@ def main(): input_data = json.loads(sys.stdin.read()) - transcript_path = input_data.get("transcript_path") - session_id = input_data.get("session_id") + hook_event = input_data.get("hook_event_name") + if hook_event == "SubagentStop": + transcript_path = input_data.get("agent_transcript_path") + session_id = input_data.get("agent_id") + else: + transcript_path = input_data.get("transcript_path") + session_id = input_data.get("session_id") dev_host = os.environ.get("TOKEN_USAGE_API_HOST_DEV", "") dev_api_token = os.environ.get("TOKEN_USAGE_API_TOKEN_DEV", "") From 0fbe181be0143669fc1083f68cc954a62f7045e8 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Fri, 23 Jan 2026 19:10:11 -0800 Subject: [PATCH 28/49] fix the off by one error --- claude/hooks/send-usage-data.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/claude/hooks/send-usage-data.py b/claude/hooks/send-usage-data.py index 4ecd989..5a86ffe 100755 --- a/claude/hooks/send-usage-data.py +++ b/claude/hooks/send-usage-data.py @@ -35,21 +35,20 @@ def get_last_message_id(session_id, host, api_token): def get_usage_data(transcript_path, after_message_id, session_id): usage_data = {} - after_message_has_been_passed = False + after_message_has_been_passed = after_message_id is None with open(transcript_path, "r") as f: for line in f: line = line.strip() entry = json.loads(line) msg_id = entry.get("message", {}).get("id") - if not after_message_has_been_passed: - if msg_id == after_message_id: - after_message_has_been_passed = True + if msg_id == after_message_id: + after_message_has_been_passed = True continue - elif entry.get("type") == "assistant": + + if after_message_has_been_passed and entry.get("type") == "assistant": usage_data[msg_id] = extract_usage_data(entry, session_id) - # Doing this de-duplicates the usage_data. Clever! return list(usage_data.values()) def extract_usage_data(entry, session_id): @@ -83,10 +82,11 @@ def post_usage_datum(datum, host, token): def send_usage_data(usage_data, host, token): for usage_datum in usage_data: + message_id = usage_datum.get("message_id", "unknown") try: post_usage_datum(usage_datum, host, token) except urllib.error.HTTPError as e: - print(f"Failed: {e.url} {e.code} - {e.reason}", file=sys.stderr) + print(f"Failed: {message_id} - {e.code} {e.reason}", file=sys.stderr) def extract_and_send_usage_data_for_env(transcript_path, session_id, host, api_token): try: From b83e7811da48d3d931c71dd898f96db1fee164d7 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Mon, 26 Jan 2026 18:48:50 -0800 Subject: [PATCH 29/49] allow running rubocop --- claude/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index 9f52522..e57636c 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -15,7 +15,8 @@ "Bash(bundle install:*)", "Bash(rails generate:*)", "Bash(rspec:*)", - "Bash(bundle exec rspec:*)" + "Bash(bundle exec rspec:*)", + "Bash(rubocop:*)" ], "deny": [ "Bash(bin/rails credentials/show)" From 682e31535caf8e770475bfb73ccf09c6a2066c1a Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Mon, 26 Jan 2026 19:02:06 -0800 Subject: [PATCH 30/49] dont let claude write to the gemfile willy nilly --- claude/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index e57636c..59e9d9e 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -16,6 +16,7 @@ "Bash(rails generate:*)", "Bash(rspec:*)", "Bash(bundle exec rspec:*)", + "Bash(bundle install)", "Bash(rubocop:*)" ], "deny": [ @@ -31,7 +32,9 @@ "Bash(command gh repo edit:*)", "Bash(command gh repo rename:*)", "Bash(command gh pr merge:*)", - "Bash(command gh pr close:*)" + "Bash(command gh pr close:*)", + "Edit(Gemfile)", + "Write(Gemfile)" ], "defaultMode": "default" }, From e5a3b4811826c796625578294d092fe74920d7cb Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Mon, 26 Jan 2026 21:39:44 -0800 Subject: [PATCH 31/49] prevnt destructive rails db changes --- claude/settings.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/claude/settings.json b/claude/settings.json index 59e9d9e..d9eecb0 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -17,6 +17,7 @@ "Bash(rspec:*)", "Bash(bundle exec rspec:*)", "Bash(bundle install)", + "Bash(rails db:*)", "Bash(rubocop:*)" ], "deny": [ @@ -27,6 +28,11 @@ "Bash(git push master)", "Bash(git push main)", "Bash(rm:*)", + "Bash(rails db:drop)", + "Bash(rails db:rollback)", + "Bash(rails db:reset)", + "Bash(rails db:migrate:redo)", + "Bash(rails db:migrate:down)", "Bash(command gh repo archive:*)", "Bash(command gh repo delete:*)", "Bash(command gh repo edit:*)", From 0e87e20c870a282ef6c201801fc4684c46fadd27 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 29 Jan 2026 11:46:52 -0800 Subject: [PATCH 32/49] shortcut for claude --- shell/zshrc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/zshrc b/shell/zshrc index a9b30c1..b666eaf 100644 --- a/shell/zshrc +++ b/shell/zshrc @@ -26,6 +26,7 @@ export PS1="%F{yellow}%n@%1~\${vcs_info_msg_0_} =>%f " export EDITOR=nvim alias vim="nvim" alias v="vim" +alias c="claude" export PYTHONPATH="/usr/local/lib/python:/usr/local/lib/python/site-packages:/usr/local/lib/python/site-packages/caption_positioning:/usr/local/lib/python/site-packages/dsptools:$PYTHONPATH" @@ -57,10 +58,9 @@ export PATH=$PATH:$GOPATH/bin eval "$(rbenv init - zsh)" -# Added by Windsurf -export PATH="/Users/mozhu/.codeium/windsurf/bin:$PATH" - . "$HOME/.local/bin/env" # Added by Antigravity export PATH="/Users/mozhu/.antigravity/antigravity/bin:$PATH" + +export PATH="$HOME/.local/bin:$PATH" From ce71412e17dc6330f9ceb57bf87ca7769315131f Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 29 Jan 2026 11:51:45 -0800 Subject: [PATCH 33/49] git pull uses rebase instead of merge --- shell/gitconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shell/gitconfig b/shell/gitconfig index aec3675..0051208 100644 --- a/shell/gitconfig +++ b/shell/gitconfig @@ -17,3 +17,5 @@ helper = osxkeychain [init] defaultBranch = master +[pull] + rebase = true From 84fa90ff25cb02fc5c14b6ac9c395b3a8cf4ba0e Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 29 Jan 2026 11:58:51 -0800 Subject: [PATCH 34/49] alias for git fetch -a and git push, and some reformatting of the file --- shell/gitconfig | 3 +++ shell/zshrc | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/shell/gitconfig b/shell/gitconfig index 0051208..bd31d41 100644 --- a/shell/gitconfig +++ b/shell/gitconfig @@ -13,9 +13,12 @@ [stash] showPatch = true + [credential] helper = osxkeychain + [init] defaultBranch = master + [pull] rebase = true diff --git a/shell/zshrc b/shell/zshrc index b666eaf..3be2cec 100644 --- a/shell/zshrc +++ b/shell/zshrc @@ -30,19 +30,25 @@ alias c="claude" export PYTHONPATH="/usr/local/lib/python:/usr/local/lib/python/site-packages:/usr/local/lib/python/site-packages/caption_positioning:/usr/local/lib/python/site-packages/dsptools:$PYTHONPATH" +# Bash aliases alias rm="rm -i" +alias ls="ls -alF" + +# Git aliases alias gh="git hist" alias gs="git status" +alias gaa="git add -A" alias gcm="git commit -m" -alias gbr="git branch" alias gd="git diff" alias gds="git diff --staged" -alias gaa="git add -A" +alias gbr="git branch" alias gco="git checkout" -alias ls="ls -alF" alias gss="git stash" alias gsp="git stash pop" +alias gfa="git fetch -a" +alias gpp="git fetch -a && git pull" +# Rails aliases alias mrmt="rake db:migrate && rake db:rollback && rake db:migrate && rake db:test:prepare" alias mrm="rake db:migrate && rake db:rollback && rake db:migrate" alias test!="rake db:test:prepare" From deb751be97608659d883feb1ef21555369f37754 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 29 Jan 2026 12:02:58 -0800 Subject: [PATCH 35/49] remove the env thing because that was for claude code proxy --- shell/zshrc | 2 -- 1 file changed, 2 deletions(-) diff --git a/shell/zshrc b/shell/zshrc index 3be2cec..707eb86 100644 --- a/shell/zshrc +++ b/shell/zshrc @@ -64,8 +64,6 @@ export PATH=$PATH:$GOPATH/bin eval "$(rbenv init - zsh)" -. "$HOME/.local/bin/env" - # Added by Antigravity export PATH="/Users/mozhu/.antigravity/antigravity/bin:$PATH" From d31f9273cedc2866b4240210bc0bf8bc9526cfa3 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 29 Jan 2026 12:58:08 -0800 Subject: [PATCH 36/49] prepend [CLAUDE] to address pr comments skill --- claude/commands/address-pr-comments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/commands/address-pr-comments.md b/claude/commands/address-pr-comments.md index 7105e04..af5f94d 100644 --- a/claude/commands/address-pr-comments.md +++ b/claude/commands/address-pr-comments.md @@ -6,6 +6,6 @@ The user probably put in two kinds of comments: 1. Questions for discussion or clarification 2. Direct commands to change how code words -Respond to type #1 directly on the relevant comment. +Respond to type #1 directly on the relevant comment. Prepend [CLAUDE] to make it clear it's coming from you. Address type #2 comments by making changes in code then push the changes to the branch. From 7137af480fd6528068fc09f4e7296803baff96c6 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 12:57:13 -0800 Subject: [PATCH 37/49] improve worktree creation --- claude/commands/new-worktree.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/claude/commands/new-worktree.md b/claude/commands/new-worktree.md index cf93ff5..9d9ee09 100644 --- a/claude/commands/new-worktree.md +++ b/claude/commands/new-worktree.md @@ -4,7 +4,11 @@ Create a new worktree for this feature. Worktree name should be {repo-name}--{wo Be sure to specify in the git command to create a worktree off of master or main branch, not the current branch. -After creating the worktree, output: +After creating the worktree: +1. Run any installation script necessary such as npm install or bundle install +2. Copy any .env or secret credentials files from the master repo to the worktree repo + +Once all of the above is done, output: ``` Worktree created. Open a new terminal tab and run: From 77d23eb51785fd72818de3358687a443006df5ee Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 14:35:33 -0800 Subject: [PATCH 38/49] shortcut for git pull --- shell/zshrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/zshrc b/shell/zshrc index 707eb86..e56799f 100644 --- a/shell/zshrc +++ b/shell/zshrc @@ -46,7 +46,7 @@ alias gco="git checkout" alias gss="git stash" alias gsp="git stash pop" alias gfa="git fetch -a" -alias gpp="git fetch -a && git pull" +alias gfp="git fetch -a && git pull" # Rails aliases alias mrmt="rake db:migrate && rake db:rollback && rake db:migrate && rake db:test:prepare" From f2e99cf5180184df0d1d4c24a499cf82fc666876 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 14:36:31 -0800 Subject: [PATCH 39/49] shortcut for fetch and rebase onto master --- shell/zshrc | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/zshrc b/shell/zshrc index e56799f..fffd243 100644 --- a/shell/zshrc +++ b/shell/zshrc @@ -47,6 +47,7 @@ alias gss="git stash" alias gsp="git stash pop" alias gfa="git fetch -a" alias gfp="git fetch -a && git pull" +alias gfrm="git fetch -a && git rebase origin/master" # Rails aliases alias mrmt="rake db:migrate && rake db:rollback && rake db:migrate && rake db:test:prepare" From c3d6e978205ad0ee5e540fc6a1587c1e7780c8b4 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 15:07:49 -0800 Subject: [PATCH 40/49] ask it to use the chrome browser plugin after each round of changes --- claude/CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/claude/CLAUDE.md b/claude/CLAUDE.md index 542f9e3..b9e5744 100644 --- a/claude/CLAUDE.md +++ b/claude/CLAUDE.md @@ -19,6 +19,7 @@ - Update CLAUDE.md after significant code changes - Write out purpose before running bash scripts - Reflect on tool results before proceeding +- After finishing each round of changes, go click through the application using the Chrome browser plugin. Verify the functionality and any functionality that was impacted by the last round of changes. # Running Bash Commands - When specifying a path, use paths relative to the current working directory, rather than absolute paths. From 593fc303d309bcc0d44d538b88233a8f5c5290f3 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 15:01:13 -0800 Subject: [PATCH 41/49] update vimrc to include testing hook --- shell/vimrc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shell/vimrc b/shell/vimrc index e88179d..7752efc 100644 --- a/shell/vimrc +++ b/shell/vimrc @@ -17,9 +17,16 @@ Plugin 'tpope/vim-fugitive' Plugin 'sheerun/vim-polyglot' Plugin 'Lokaltog/vim-distinguished' Plugin 'morhetz/gruvbox' +Plugin 'vim-test/vim-test' call vundle#end() filetype plugin indent on + +let test#strategy = 'basic' +let test#javascript#vitest#options = '--run' +nmap a :TestSuite +nmap t :TestFile +nmap s :TestNearest au BufNewFile,BufRead *.prawn set filetype=ruby au BufNewFile,BufRead *.json.jbuilder set filetype=ruby au BufNewFile,BufRead *.axlsx set filetype=ruby From 6d1c45686af371373bb023cf4b2b2178b7d434bd Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 17:13:43 -0800 Subject: [PATCH 42/49] remove vimrc settings that are already defaults in neovim Co-Authored-By: Claude Opus 4.5 --- shell/vimrc | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/shell/vimrc b/shell/vimrc index 7752efc..b10f87e 100644 --- a/shell/vimrc +++ b/shell/vimrc @@ -1,5 +1,3 @@ -"separates vim from vi, allowing the many customizations found in vim -set nocompatible filetype off set rtp+=~/.vim/bundle/Vundle.vim @@ -27,6 +25,7 @@ let test#javascript#vitest#options = '--run' nmap a :TestSuite nmap t :TestFile nmap s :TestNearest + au BufNewFile,BufRead *.prawn set filetype=ruby au BufNewFile,BufRead *.json.jbuilder set filetype=ruby au BufNewFile,BufRead *.axlsx set filetype=ruby @@ -35,8 +34,6 @@ au BufNewFile,BufRead *.axlsx set filetype=ruby au BufNewFile,BufRead *.md set wrap au BufNewFile,BufRead *.txt set wrap -"In the bottom of the screen, it will show me the XY coordinates of my cursor -set ruler set rulerformat='%60(%f:%l\ of\ %L%)' "Highlight cursor line @@ -52,15 +49,6 @@ set nowritebackup "prevents vim from creating a separate swap file, which tends to get in the way of git. set noswapfile -set history=50 - -"shows relevant information at bottom of screen when you are using commands. -set showcmd - -"live searching as you type -set incsearch -"search terms stay highlighted after you hit enter -set hlsearch map "get rid of highlighting after you are done with searching @@ -79,15 +67,6 @@ set tabstop=2 set shiftwidth=2 set expandtab -"hit tab for vim to autocomplete you file name. Hit tab again to cycle to next option. -set wildmode=full - -" Switch syntax highlighting on, when the terminal has colors -" Also switch on highlighting the last used search pattern. -if (&t_Co > 2 || has("gui_running")) && !exists("syntax_on") - syntax on -endif - " Display extra whitespace set list listchars=tab:··,trail:· @@ -116,8 +95,6 @@ endfunction "set toggle line numbers map l :call ToggleLineNumbers() -"set auto indentation -set autoindent "set smartindent <- This has been set off because it interferes with the endwise plugin "movement keys always move cursor to start of a line. @@ -135,7 +112,6 @@ map k :Ex set t_Co=256 color gruvbox -set noerrorbells "open up todo file map o :e .todo @@ -166,7 +142,7 @@ map o "map << "being typing shell commands -mapi :! +mapi :! "Common mistaken keys for saving and quitting map :W :w From 15eab732f6ae8a299a0a2b565cf50def1b0fda87 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 17:25:04 -0800 Subject: [PATCH 43/49] remove t_Co=256, neovim handles terminal colors automatically Co-Authored-By: Claude Opus 4.5 --- shell/vimrc | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/vimrc b/shell/vimrc index b10f87e..08649db 100644 --- a/shell/vimrc +++ b/shell/vimrc @@ -110,7 +110,6 @@ let g:netrw_list_hide='.*\.DS_Store$' "let NERDTreeShowHidden=1 map k :Ex -set t_Co=256 color gruvbox "open up todo file From 8bd62f7846ce8948675a48ae5b89c6829d1b5ee7 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Sat, 31 Jan 2026 17:26:34 -0800 Subject: [PATCH 44/49] set e2e/ as folder for playwright --- shell/vimrc | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/vimrc b/shell/vimrc index 08649db..3b5c674 100644 --- a/shell/vimrc +++ b/shell/vimrc @@ -22,6 +22,7 @@ filetype plugin indent on let test#strategy = 'basic' let test#javascript#vitest#options = '--run' +autocmd BufEnter */e2e/* let b:test_runner = 'playwright' nmap a :TestSuite nmap t :TestFile nmap s :TestNearest From 53a8bf2bea2b33e701da01e71536fdabdd267615 Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Wed, 4 Feb 2026 16:07:59 -0800 Subject: [PATCH 45/49] more complexity on address-pr-comments --- claude/commands/address-pr-comments.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/claude/commands/address-pr-comments.md b/claude/commands/address-pr-comments.md index af5f94d..66035b4 100644 --- a/claude/commands/address-pr-comments.md +++ b/claude/commands/address-pr-comments.md @@ -1,11 +1,13 @@ User input: $ARGUMENTS -Pull the comments from the user input or, if that is blank, the corresponding PR to this branch. +Take the comments from the user input (it may be blank) and user comments on the corresponding PR to this branch (if it exists). The user probably put in two kinds of comments: 1. Questions for discussion or clarification 2. Direct commands to change how code words -Respond to type #1 directly on the relevant comment. Prepend [CLAUDE] to make it clear it's coming from you. +For type #1 comments, respond #1 directly on the relevant comment. Prepend [CLAUDE] to make it clear it's coming from you. + +For type #2 comments, make new branch called updates/pr-123-202601011345 (updates/pr-[pr number]-[timestamp yyyymmddhhmm]) changes in code then create a new PR wherein the new branch is being merged into the underlying feature branch. On the relevant parts of the PR, put a comment that has the full comment history which drove the change. [Mo Zhu] for my comments [Claude] for your comments. + -Address type #2 comments by making changes in code then push the changes to the branch. From 502a01b102cd6726d0ab3d33afee61fe858e85ac Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 5 Feb 2026 16:03:09 -0800 Subject: [PATCH 46/49] enable webfetch everywhere --- claude/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index d9eecb0..e8aeb82 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -11,7 +11,7 @@ "Bash(cp:*)", "Bash(cd:*)", "WebSearch", - "WebFetch(domain:*)", + "WebFetch", "Bash(bundle install:*)", "Bash(rails generate:*)", "Bash(rspec:*)", From fa9e1f0a4092cf2e5ce1044cd6689e3d37b5990d Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Thu, 5 Feb 2026 16:04:54 -0800 Subject: [PATCH 47/49] command to refresh branch --- claude/commands/refresh-branch.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 claude/commands/refresh-branch.md diff --git a/claude/commands/refresh-branch.md b/claude/commands/refresh-branch.md new file mode 100644 index 0000000..5358efc --- /dev/null +++ b/claude/commands/refresh-branch.md @@ -0,0 +1,3 @@ +If this current branch has no associated remote branch, do nothing. + +If it does have an associated remote branch, ensure that this branch is up to date with the remote branch, including force pulling and resetting the head of the current branch to match the remote. From b88263685ce53456b25c44db229efbd4dc48a70f Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Mon, 9 Feb 2026 13:45:52 -0800 Subject: [PATCH 48/49] ask permission before git forcing --- claude/settings.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/claude/settings.json b/claude/settings.json index e8aeb82..8f47369 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -27,6 +27,8 @@ "Bash(git merge:*)", "Bash(git push master)", "Bash(git push main)", + "Bash(git push --force)", + "Bash(git push -f)", "Bash(rm:*)", "Bash(rails db:drop)", "Bash(rails db:rollback)", From 16aec46d84db998d736725d4a82909c5871694eb Mon Sep 17 00:00:00 2001 From: Mo Zhu Date: Wed, 11 Feb 2026 13:50:40 -0800 Subject: [PATCH 49/49] allow running npm commands --- claude/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/claude/settings.json b/claude/settings.json index 8f47369..b522cbe 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -18,7 +18,9 @@ "Bash(bundle exec rspec:*)", "Bash(bundle install)", "Bash(rails db:*)", - "Bash(rubocop:*)" + "Bash(rubocop:*)", + "Bash(npm run dev)", + "Bash(npm dev)" ], "deny": [ "Bash(bin/rails credentials/show)"