diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 015f889..e3769b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,10 @@ jobs: run: | shellcheck --severity=error setup-linux.sh + - name: Verify opencode commands in sync with Claude skills (AI-012) + run: | + ./scripts/skills-to-opencode.sh --check + lint-powershell: runs-on: ubuntu-latest steps: diff --git a/ai/opencode/commands/audit.md b/ai/opencode/commands/audit.md new file mode 100644 index 0000000..e317445 --- /dev/null +++ b/ai/opencode/commands/audit.md @@ -0,0 +1,41 @@ +--- +description: Use when reviewing code for security vulnerabilities (SQL injection, XSS, hardcoded secrets, CSRF), performance issues (N+1 queries, memory leaks, blocking async), or code quality concerns (complexity, error handling, type safety). +--- + +# Security Audit + +Analyze code for vulnerabilities, performance issues, and bad practices. + +## Checklist + +| Category | Issues to Find | +|----------|----------------| +| Injection | SQL concatenation, XSS, command injection, path traversal | +| Secrets | Hardcoded credentials, API keys in code, .env committed | +| Auth | Missing validation, broken access control, CSRF | +| Performance | N+1 queries, unbounded loops, blocking in async | +| Resilience | Unhandled errors, missing timeouts, race conditions | +| Quality | Magic numbers, deep nesting, missing types, dead code | + +## Output Format + +```markdown +## Issues + +### HIGH +- [file:line] Issue description +- [file:line] Issue description + +### MEDIUM +- [file:line] Issue description + +### LOW +- [file:line] Issue description + +## Fixes + +### [Issue name] +[Fixed code - no explanation] +``` + +Provide fixes for top 3 HIGH/MEDIUM issues. Code only, no explanations. diff --git a/ai/opencode/commands/debug-hardware.md b/ai/opencode/commands/debug-hardware.md new file mode 100644 index 0000000..deae6b5 --- /dev/null +++ b/ai/opencode/commands/debug-hardware.md @@ -0,0 +1,39 @@ +--- +description: Use when troubleshooting hardware or firmware issues -- device communication, register configuration, signal processing, camera/sensor behavior, or embedded systems. +--- + +# Hardware Debugging + +Systematic approach for hardware/firmware issues. Evidence before hypotheses. + +## The Iron Rule + +``` +NO GUESSING. NO CYCLING THROUGH HYPOTHESES WITHOUT EVIDENCE. +``` + +## Process + +1. **Read reference code** — Find ALL related source files and working implementations (GUI code, vendor examples, known-good configurations) +2. **Read documentation** — Check firmware/hardware datasheets, register maps, timing diagrams, protocol specs +3. **Gather observations** — Ask user to describe observed vs expected behavior. Get concrete data: register values, signal traces, error codes +4. **Form single hypothesis** — Only AFTER steps 1-3. State clearly: "I think X because evidence Y shows Z" +5. **Propose minimal fix** — Smallest possible change to test the hypothesis. One variable at a time +6. **Verify** — Run tests after every change + +## When Fix Fails + +- Do NOT guess another cause +- Ask user for more observations +- Re-read documentation with new context +- Repeat from step 3 + +## Common Pitfalls + +| Pitfall | Correct Approach | +|---------|-----------------| +| Assuming register defaults | Read datasheet for actual reset values | +| Changing multiple registers at once | One register change per test | +| Ignoring timing requirements | Check setup/hold times, clock domains | +| Guessing endianness | Verify byte order from documentation | +| Skipping reference implementation | Always compare against known-working code | diff --git a/ai/opencode/commands/docker.md b/ai/opencode/commands/docker.md new file mode 100644 index 0000000..eaf7823 --- /dev/null +++ b/ai/opencode/commands/docker.md @@ -0,0 +1,76 @@ +--- +description: Use when containerizing applications, setting up local development environments, or creating multi-service Docker deployments. +--- + +# Docker Configuration + +Generate `Dockerfile` and `docker-compose.yml`. + +## Requirements + +| Aspect | Rule | +|--------|------| +| Base image | Specific version, never `latest`. Prefer slim/alpine/distroless | +| User | Non-root (`USER appuser`) | +| Build | Multi-stage to minimize final image | +| Layers | Dependencies before code for caching | +| Secrets | Via environment variables, never in image | +| Health | `HEALTHCHECK` instruction required | + +## Dockerfile Template + +```dockerfile +# Build +FROM python:3.12-slim-bookworm AS builder +WORKDIR /build +COPY requirements.txt . +RUN pip wheel --no-cache-dir -r requirements.txt -w /wheels + +# Production +FROM python:3.12-slim-bookworm +WORKDIR /app +RUN useradd -r -s /bin/false appuser +COPY --from=builder /wheels /wheels +RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels +COPY --chown=appuser:appuser . . +USER appuser +EXPOSE 8000 +HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8000/health || exit 1 +CMD ["python", "-m", "app"] +``` + +## docker-compose.yml Template + +```yaml +services: + app: + build: . + ports: + - "8000:8000" + environment: + DATABASE_URL: postgresql://user:pass@db:5432/app + depends_on: + db: + condition: service_healthy + + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: user + POSTGRES_PASSWORD: pass + POSTGRES_DB: app + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U user -d app"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + pgdata: +``` + +## Output + +Both files with comments explaining non-obvious choices. diff --git a/ai/opencode/commands/prd-to-issues.md b/ai/opencode/commands/prd-to-issues.md new file mode 100644 index 0000000..c439457 --- /dev/null +++ b/ai/opencode/commands/prd-to-issues.md @@ -0,0 +1,226 @@ +--- +description: Use when creating GitHub issues from a PRD, syncing an existing PRD to GitHub, or converting requirements documents into tracked issues. +--- + +# PRD to GitHub Issues + +Convert a PRD into GitHub issues (epics + stories/tasks) using the `gh` CLI. Supports initial creation and re-sync when the PRD changes. + +## Prerequisites + +Before starting, verify: +- `gh` CLI is installed and authenticated (`gh auth status`) +- A PRD exists in `docs/prd/` (created via the `prd` skill) +- The project has a GitHub remote (`git remote -v`) + +If any prerequisite fails, stop and tell the user what to fix. + +## Label Setup + +Create these labels before creating issues (idempotent — safe to re-run): + +```bash +gh label create "epic" --color "3B0A8C" --description "Epic: high-level feature group" --force +gh label create "story" --color "0E8A16" --description "Story: user-facing deliverable" --force +gh label create "task" --color "1D76DB" --description "Task: technical work item" --force +gh label create "must-have" --color "B60205" --description "MoSCoW: Must have" --force +gh label create "should-have" --color "D93F0B" --description "MoSCoW: Should have" --force +gh label create "could-have" --color "FBCA04" --description "MoSCoW: Could have" --force +gh label create "wont-have" --color "CCCCCC" --description "MoSCoW: Won't have (this time)" --force +``` + +## Initial Create Mode + +When no ` + +| FR-001 | User login | Must | EPIC-001 | | +``` + +**Marker format:** `` + +Placement rules: +- Epic markers: end of the Epic heading line +- FR markers: end of the FR table row or as a new column + +## Re-Sync Mode + +Triggered when `` markers from the PRD using: + +``` +Pattern: +``` + +### 2. Fetch Issue State + +For each marker, fetch the current issue state: + +```bash +gh issue view --json title,state,body,labels +``` + +### 3. Diff PRD vs Issues + +Compare PRD content against issue content. Classify each item: + +| Status | Meaning | +|--------|---------| +| **UNCHANGED** | PRD and issue match | +| **NEW** | In PRD but no marker (new requirement) | +| **MODIFIED** | PRD content differs from issue body | +| **REMOVED** | Marker exists but FR removed from PRD | +| **CLOSED** | Issue was closed on GitHub | + +### 4. Present Diff Summary + +Show the user a summary table before making any changes: + +``` +| # | Issue | Status | Action | +|---|-------|--------|--------| +| 1 | #42 EPIC-001 | UNCHANGED | — | +| 2 | #43 FR-001 | MODIFIED | Update issue body | +| 3 | — FR-004 | NEW | Create issue | +| 4 | #45 FR-003 | REMOVED | Close issue? | +``` + +### 5. User Confirms + +Ask: "Which actions should I apply? (all / select by number / none)" + +**Never auto-apply re-sync changes.** + +### 6. Execute and Update Markers + +Apply confirmed actions: +- **NEW:** `gh issue create` + add marker to PRD +- **MODIFIED:** `gh issue edit --body "..."` +- **REMOVED:** `gh issue close ` (only if user confirms) + +Update markers in the PRD file after execution. + +## Issue Body Template + +All issues follow this structure: + +```markdown +## [User Story | Epic Description] + +[Content from PRD] + +### Acceptance Criteria + +- [ ] [Criterion] + +### Details + +- **Priority:** [Must/Should/Could/Won't] +- **Estimate:** [S/M/L/XL] +- **Epic:** [EPIC-ID] (#N) +- **FR:** [FR-ID] + +--- +> Source: docs/prd/-prd.md +``` + +## Rules + +1. **gh CLI only** — never use the GitHub API directly or curl; always use `gh` +2. **Confirm before creating** — show the summary table, wait for user approval +3. **Never auto-apply re-sync** — always present the diff and ask for confirmation +4. **Labels are idempotent** — use `--force` flag so re-runs do not fail +5. **Markers are sacred** — never delete or modify markers manually; only this skill manages them +6. **One PR per epic** — suggest (do not enforce) branching strategy: one branch per epic +7. **Traceability** — update the PRD traceability matrix after creating issues +8. **Error handling** — if `gh issue create` fails, report the error and continue with remaining issues diff --git a/ai/opencode/commands/project-maturation.md b/ai/opencode/commands/project-maturation.md new file mode 100644 index 0000000..01dab08 --- /dev/null +++ b/ai/opencode/commands/project-maturation.md @@ -0,0 +1,135 @@ +--- +description: Use when a project needs a structured quality audit and improvement plan. Triggers include new projects needing hardening, repos with missing tests or CI, codebases before first release, or when technical debt has accumulated across multiple dimensions. +--- + +# Project Maturation + +Structured audit and improvement cycle for codebases. Detects stack, scores maturity across dimensions, generates a prioritized plan, and executes phase by phase. + +## Protocol + +### Step 1 — Detect Stack + +Scan the project root to identify: + +| Signal | Stack indicator | +|--------|----------------| +| `go.mod` | Go | +| `pyproject.toml`, `setup.py`, `requirements.txt` | Python | +| `package.json` | Node.js / TypeScript | +| `Cargo.toml` | Rust | +| `pom.xml`, `build.gradle` | Java | +| `*.sh` + `*.bats` | Shell/POSIX | +| `.astro` files | Astro | +| `Makefile`, `Dockerfile` | Infrastructure | + +Detect CI system: `.github/workflows/`, `.gitlab-ci.yml`, `Jenkinsfile`. +Detect build tool: Make, npm scripts, Poetry, Gradle, Cargo. + +### Step 2 — Audit Dimensions + +Score each dimension 0-3: + +| Score | Meaning | +|-------|---------| +| 0 — Absent | Not present at all | +| 1 — Basic | Exists but incomplete or inconsistent | +| 2 — Solid | Covers happy paths, integrated into workflow | +| 3 — Exemplar | Comprehensive, automated, battle-tested | + +#### Dimensions (adapt to detected stack) + +| Dimension | What to check | Stack-specific examples | +|-----------|---------------|------------------------| +| **Tests** | Coverage, edge cases, integration | pytest/Go table-driven/bats/JUnit | +| **CI/CD** | Pipeline exists, jobs, triggers, caching | GitHub Actions/GitLab CI | +| **Types & Linting** | Type safety, static analysis | mypy/golangci-lint/eslint+tsc/shellcheck | +| **Error Handling** | Explicit errors, no silent failures | Go `if err != nil`/Python exceptions/Rust Result | +| **Docs** | README, API docs, inline | README quality, architecture docs | +| **Security** | Deps audit, secrets, auth | `npm audit`/`pip-audit`/Snyk/age encryption | +| **Architecture** | Separation of concerns, patterns | Layered, hexagonal, clean architecture | +| **Observability** | Logging, metrics, health checks | Structured logging, healthcheck endpoints | + +### Step 3 — Score Report + +Present a maturity matrix: + +``` +=== Project Maturity Audit === +Stack: Python 3.12 + pytest + GitHub Actions +Project: youtube-toolkit + +| Dimension | Score | Notes | +|----------------|-------|--------------------------------| +| Tests | 1 | 12 tests, no edge cases | +| CI/CD | 2 | Lint + test, no deploy | +| Types/Linting | 1 | Ruff configured, no mypy | +| Error Handling | 2 | Try/except, some silent catches| +| Docs | 1 | README only, no API docs | +| Security | 0 | No dependency audit | +| Architecture | 2 | Clean layering, some coupling | +| Observability | 0 | Print statements only | + +Overall: 9/24 (37%) — Basic maturity + +Priority order: Security → Tests → Types → Observability → Docs +``` + +### Step 4 — Generate Plan + +Create phased plan ordered by impact (what hurts most first): + +**Priority heuristic:** +1. Security (score 0-1) — always first +2. Tests (score 0-1) — safety net before any refactoring +3. CI/CD (score 0-1) — automation before manual improvements +4. Types/Linting — catch bugs statically +5. Error Handling — resilience +6. Architecture — structural improvements +7. Docs — document what's stable +8. Observability — production readiness + +Each phase follows the project's established workflow (TDD, atomic PRs, etc.). + +**Plan format:** + +```markdown +## Phase 1: Security (score 0 → 2) +- [ ] Add dependency audit to CI (pip-audit/npm audit) +- [ ] Scan for hardcoded secrets +- [ ] Add SECURITY.md with disclosure policy + +## Phase 2: Tests (score 1 → 2) +- [ ] Add edge case tests for core functions +- [ ] Add integration tests for API layer +- [ ] Add coverage reporting to CI (target: 80%) +``` + +### Step 5 — Execute + +Use `executing-plans` skill to implement phase by phase with checkpoints. + +**Between phases:** +- Run full test suite — no regressions +- Update the maturity score +- Commit completed phase + +### Step 6 — Crystallize + +After completing all phases: +- Update vault `11-tasks.md` with completion status +- Log lessons learned to `90-lessons.md` +- If new patterns emerged, propose for `00_meta/patterns/` + +## Rules + +- **One phase at a time.** Don't mix security fixes with test improvements. +- **Verify before advancing.** Each phase must pass CI before starting the next. +- **Stack-aware.** Don't recommend mypy for a Go project or golangci-lint for Python. +- **Respect existing patterns.** If the project already uses a specific testing framework, don't switch it. +- **Atomic PRs.** Each phase may decompose into multiple PRs (~300 lines each). + +## Pipeline + +- Previous: Read project CLAUDE.md and vault context for existing standards +- Next: `/executing-plans` to implement each phase diff --git a/ai/opencode/commands/systematic-debugging.md b/ai/opencode/commands/systematic-debugging.md new file mode 100644 index 0000000..896f525 --- /dev/null +++ b/ai/opencode/commands/systematic-debugging.md @@ -0,0 +1,295 @@ +--- +description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes +--- + +# Systematic Debugging + +## Overview + +Random fixes waste time and create new bugs. Quick patches mask underlying issues. + +**Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure. + +**Violating the letter of this process is violating the spirit of debugging.** + +## The Iron Law + +``` +NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST +``` + +If you haven't completed Phase 1, you cannot propose fixes. + +## When to Use + +Use for ANY technical issue: +- Test failures +- Bugs in production +- Unexpected behavior +- Performance problems +- Build failures +- Integration issues + +**Use this ESPECIALLY when:** +- Under time pressure (emergencies make guessing tempting) +- "Just one quick fix" seems obvious +- You've already tried multiple fixes +- Previous fix didn't work +- You don't fully understand the issue + +**Don't skip when:** +- Issue seems simple (simple bugs have root causes too) +- You're in a hurry (rushing guarantees rework) +- Manager wants it fixed NOW (systematic is faster than thrashing) + +## The Four Phases + +You MUST complete each phase before proceeding to the next. + +### Phase 1: Root Cause Investigation + +**BEFORE attempting ANY fix:** + +1. **Read Error Messages Carefully** + - Don't skip past errors or warnings + - They often contain the exact solution + - Read stack traces completely + - Note line numbers, file paths, error codes + +2. **Reproduce Consistently** + - Can you trigger it reliably? + - What are the exact steps? + - Does it happen every time? + - If not reproducible → gather more data, don't guess + +3. **Check Recent Changes** + - What changed that could cause this? + - Git diff, recent commits + - New dependencies, config changes + - Environmental differences + +4. **Gather Evidence in Multi-Component Systems** + + **WHEN system has multiple components (CI → build → signing, API → service → database):** + + **BEFORE proposing fixes, add diagnostic instrumentation:** + ``` + For EACH component boundary: + - Log what data enters component + - Log what data exits component + - Verify environment/config propagation + - Check state at each layer + + Run once to gather evidence showing WHERE it breaks + THEN analyze evidence to identify failing component + THEN investigate that specific component + ``` + + **Example (multi-layer system):** + ```bash + # Layer 1: Workflow + echo "=== Secrets available in workflow: ===" + echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}" + + # Layer 2: Build script + echo "=== Env vars in build script: ===" + env | grep IDENTITY || echo "IDENTITY not in environment" + + # Layer 3: Signing script + echo "=== Keychain state: ===" + security list-keychains + security find-identity -v + + # Layer 4: Actual signing + codesign --sign "$IDENTITY" --verbose=4 "$APP" + ``` + + **This reveals:** Which layer fails (secrets → workflow ✓, workflow → build ✗) + +5. **Trace Data Flow** + + **WHEN error is deep in call stack:** + + See `root-cause-tracing.md` in this directory for the complete backward tracing technique. + + **Quick version:** + - Where does bad value originate? + - What called this with bad value? + - Keep tracing up until you find the source + - Fix at source, not at symptom + +### Phase 2: Pattern Analysis + +**Find the pattern before fixing:** + +1. **Find Working Examples** + - Locate similar working code in same codebase + - What works that's similar to what's broken? + +2. **Compare Against References** + - If implementing pattern, read reference implementation COMPLETELY + - Don't skim - read every line + - Understand the pattern fully before applying + +3. **Identify Differences** + - What's different between working and broken? + - List every difference, however small + - Don't assume "that can't matter" + +4. **Understand Dependencies** + - What other components does this need? + - What settings, config, environment? + - What assumptions does it make? + +### Phase 3: Hypothesis and Testing + +**Scientific method:** + +1. **Form Single Hypothesis** + - State clearly: "I think X is the root cause because Y" + - Write it down + - Be specific, not vague + +2. **Test Minimally** + - Make the SMALLEST possible change to test hypothesis + - One variable at a time + - Don't fix multiple things at once + +3. **Verify Before Continuing** + - Did it work? Yes → Phase 4 + - Didn't work? Form NEW hypothesis + - DON'T add more fixes on top + +4. **When You Don't Know** + - Say "I don't understand X" + - Don't pretend to know + - Ask for help + - Research more + +### Phase 4: Implementation + +**Fix the root cause, not the symptom:** + +1. **Create Failing Test Case** + - Simplest possible reproduction + - Automated test if possible + - One-off test script if no framework + - MUST have before fixing + - Use the `superpowers:test-driven-development` skill for writing proper failing tests + +2. **Implement Single Fix** + - Address the root cause identified + - ONE change at a time + - No "while I'm here" improvements + - No bundled refactoring + +3. **Verify Fix** + - Test passes now? + - No other tests broken? + - Issue actually resolved? + +4. **If Fix Doesn't Work** + - STOP + - Count: How many fixes have you tried? + - If < 3: Return to Phase 1, re-analyze with new information + - **If ≥ 3: STOP and question the architecture (step 5 below)** + - DON'T attempt Fix #4 without architectural discussion + +5. **If 3+ Fixes Failed: Question Architecture** + + **Pattern indicating architectural problem:** + - Each fix reveals new shared state/coupling/problem in different place + - Fixes require "massive refactoring" to implement + - Each fix creates new symptoms elsewhere + + **STOP and question fundamentals:** + - Is this pattern fundamentally sound? + - Are we "sticking with it through sheer inertia"? + - Should we refactor architecture vs. continue fixing symptoms? + + **Discuss with your human partner before attempting more fixes** + + This is NOT a failed hypothesis - this is a wrong architecture. + +## Red Flags - STOP and Follow Process + +If you catch yourself thinking: +- "Quick fix for now, investigate later" +- "Just try changing X and see if it works" +- "Add multiple changes, run tests" +- "Skip the test, I'll manually verify" +- "It's probably X, let me fix that" +- "I don't fully understand but this might work" +- "Pattern says X but I'll adapt it differently" +- "Here are the main problems: [lists fixes without investigation]" +- Proposing solutions before tracing data flow +- **"One more fix attempt" (when already tried 2+)** +- **Each fix reveals new problem in different place** + +**ALL of these mean: STOP. Return to Phase 1.** + +**If 3+ fixes failed:** Question the architecture (see Phase 4.5) + +## your human partner's Signals You're Doing It Wrong + +**Watch for these redirections:** +- "Is that not happening?" - You assumed without verifying +- "Will it show us...?" - You should have added evidence gathering +- "Stop guessing" - You're proposing fixes without understanding +- "Ultrathink this" - Question fundamentals, not just symptoms +- "We're stuck?" (frustrated) - Your approach isn't working + +**When you see these:** STOP. Return to Phase 1. + +## Common Rationalizations + +| Excuse | Reality | +|--------|---------| +| "Issue is simple, don't need process" | Simple issues have root causes too. Process is fast for simple bugs. | +| "Emergency, no time for process" | Systematic debugging is FASTER than guess-and-check thrashing. | +| "Just try this first, then investigate" | First fix sets the pattern. Do it right from the start. | +| "I'll write test after confirming fix works" | Untested fixes don't stick. Test first proves it. | +| "Multiple fixes at once saves time" | Can't isolate what worked. Causes new bugs. | +| "Reference too long, I'll adapt the pattern" | Partial understanding guarantees bugs. Read it completely. | +| "I see the problem, let me fix it" | Seeing symptoms ≠ understanding root cause. | +| "One more fix attempt" (after 2+ failures) | 3+ failures = architectural problem. Question pattern, don't fix again. | + +## Quick Reference + +| Phase | Key Activities | Success Criteria | +|-------|---------------|------------------| +| **1. Root Cause** | Read errors, reproduce, check changes, gather evidence | Understand WHAT and WHY | +| **2. Pattern** | Find working examples, compare | Identify differences | +| **3. Hypothesis** | Form theory, test minimally | Confirmed or new hypothesis | +| **4. Implementation** | Create test, fix, verify | Bug resolved, tests pass | + +## When Process Reveals "No Root Cause" + +If systematic investigation reveals issue is truly environmental, timing-dependent, or external: + +1. You've completed the process +2. Document what you investigated +3. Implement appropriate handling (retry, timeout, error message) +4. Add monitoring/logging for future investigation + +**But:** 95% of "no root cause" cases are incomplete investigation. + +## Supporting Techniques + +These techniques are part of systematic debugging and available in this directory: + +- **`root-cause-tracing.md`** - Trace bugs backward through call stack to find original trigger +- **`defense-in-depth.md`** - Add validation at multiple layers after finding root cause +- **`condition-based-waiting.md`** - Replace arbitrary timeouts with condition polling + +**Related skills:** +- **superpowers:test-driven-development** - For creating failing test case (Phase 4, Step 1) +- **superpowers:verification-before-completion** - Verify fix worked before claiming success + +## Real-World Impact + +From debugging sessions: +- Systematic approach: 15-30 minutes to fix +- Random fixes approach: 2-3 hours of thrashing +- First-time fix rate: 95% vs 40% +- New bugs introduced: Near zero vs common diff --git a/ai/opencode/commands/test-driven-development.md b/ai/opencode/commands/test-driven-development.md new file mode 100644 index 0000000..1905c7b --- /dev/null +++ b/ai/opencode/commands/test-driven-development.md @@ -0,0 +1,370 @@ +--- +description: Use when implementing any feature or bugfix, before writing implementation code +--- + +# Test-Driven Development (TDD) + +## Overview + +Write the test first. Watch it fail. Write minimal code to pass. + +**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing. + +**Violating the letter of the rules is violating the spirit of the rules.** + +## When to Use + +**Always:** +- New features +- Bug fixes +- Refactoring +- Behavior changes + +**Exceptions (ask your human partner):** +- Throwaway prototypes +- Generated code +- Configuration files + +Thinking "skip TDD just this once"? Stop. That's rationalization. + +## The Iron Law + +``` +NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST +``` + +Write code before the test? Delete it. Start over. + +**No exceptions:** +- Don't keep it as "reference" +- Don't "adapt" it while writing tests +- Don't look at it +- Delete means delete + +Implement fresh from tests. Period. + +## Red-Green-Refactor + +```dot +digraph tdd_cycle { + rankdir=LR; + red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"]; + verify_red [label="Verify fails\ncorrectly", shape=diamond]; + green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"]; + verify_green [label="Verify passes\nAll green", shape=diamond]; + refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"]; + next [label="Next", shape=ellipse]; + + red -> verify_red; + verify_red -> green [label="yes"]; + verify_red -> red [label="wrong\nfailure"]; + green -> verify_green; + verify_green -> refactor [label="yes"]; + verify_green -> green [label="no"]; + refactor -> verify_green [label="stay\ngreen"]; + verify_green -> next; + next -> red; +} +``` + +### RED - Write Failing Test + +Write one minimal test showing what should happen. + + +```typescript +test('retries failed operations 3 times', async () => { + let attempts = 0; + const operation = () => { + attempts++; + if (attempts < 3) throw new Error('fail'); + return 'success'; + }; + + const result = await retryOperation(operation); + + expect(result).toBe('success'); + expect(attempts).toBe(3); +}); +``` +Clear name, tests real behavior, one thing + + + +```typescript +test('retry works', async () => { + const mock = jest.fn() + .mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce('success'); + await retryOperation(mock); + expect(mock).toHaveBeenCalledTimes(3); +}); +``` +Vague name, tests mock not code + + +**Requirements:** +- One behavior +- Clear name +- Real code (no mocks unless unavoidable) + +### Verify RED - Watch It Fail + +**MANDATORY. Never skip.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: +- Test fails (not errors) +- Failure message is expected +- Fails because feature missing (not typos) + +**Test passes?** You're testing existing behavior. Fix test. + +**Test errors?** Fix error, re-run until it fails correctly. + +### GREEN - Minimal Code + +Write simplest code to pass the test. + + +```typescript +async function retryOperation(fn: () => Promise): Promise { + for (let i = 0; i < 3; i++) { + try { + return await fn(); + } catch (e) { + if (i === 2) throw e; + } + } + throw new Error('unreachable'); +} +``` +Just enough to pass + + + +```typescript +async function retryOperation( + fn: () => Promise, + options?: { + maxRetries?: number; + backoff?: 'linear' | 'exponential'; + onRetry?: (attempt: number) => void; + } +): Promise { + // YAGNI +} +``` +Over-engineered + + +Don't add features, refactor other code, or "improve" beyond the test. + +### Verify GREEN - Watch It Pass + +**MANDATORY.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: +- Test passes +- Other tests still pass +- Output pristine (no errors, warnings) + +**Test fails?** Fix code, not test. + +**Other tests fail?** Fix now. + +### REFACTOR - Clean Up + +After green only: +- Remove duplication +- Improve names +- Extract helpers + +Keep tests green. Don't add behavior. + +### Repeat + +Next failing test for next feature. + +## Good Tests + +| Quality | Good | Bad | +|---------|------|-----| +| **Minimal** | One thing. "and" in name? Split it. | `test('validates email and domain and whitespace')` | +| **Clear** | Name describes behavior | `test('test1')` | +| **Shows intent** | Demonstrates desired API | Obscures what code should do | + +## Why Order Matters + +**"I'll write tests after to verify it works"** + +Tests written after code pass immediately. Passing immediately proves nothing: +- Might test wrong thing +- Might test implementation, not behavior +- Might miss edge cases you forgot +- You never saw it catch the bug + +Test-first forces you to see the test fail, proving it actually tests something. + +**"I already manually tested all the edge cases"** + +Manual testing is ad-hoc. You think you tested everything but: +- No record of what you tested +- Can't re-run when code changes +- Easy to forget cases under pressure +- "It worked when I tried it" ≠ comprehensive + +Automated tests are systematic. They run the same way every time. + +**"Deleting X hours of work is wasteful"** + +Sunk cost fallacy. The time is already gone. Your choice now: +- Delete and rewrite with TDD (X more hours, high confidence) +- Keep it and add tests after (30 min, low confidence, likely bugs) + +The "waste" is keeping code you can't trust. Working code without real tests is technical debt. + +**"TDD is dogmatic, being pragmatic means adapting"** + +TDD IS pragmatic: +- Finds bugs before commit (faster than debugging after) +- Prevents regressions (tests catch breaks immediately) +- Documents behavior (tests show how to use code) +- Enables refactoring (change freely, tests catch breaks) + +"Pragmatic" shortcuts = debugging in production = slower. + +**"Tests after achieve the same goals - it's spirit not ritual"** + +No. Tests-after answer "What does this do?" Tests-first answer "What should this do?" + +Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones. + +Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't). + +30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work. + +## Common Rationalizations + +| Excuse | Reality | +|--------|---------| +| "Too simple to test" | Simple code breaks. Test takes 30 seconds. | +| "I'll test after" | Tests passing immediately prove nothing. | +| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" | +| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. | +| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. | +| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. | +| "Need to explore first" | Fine. Throw away exploration, start with TDD. | +| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. | +| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. | +| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. | +| "Existing code has no tests" | You're improving it. Add tests for existing code. | + +## Red Flags - STOP and Start Over + +- Code before test +- Test after implementation +- Test passes immediately +- Can't explain why test failed +- Tests added "later" +- Rationalizing "just this once" +- "I already manually tested it" +- "Tests after achieve the same purpose" +- "It's about spirit not ritual" +- "Keep as reference" or "adapt existing code" +- "Already spent X hours, deleting is wasteful" +- "TDD is dogmatic, I'm being pragmatic" +- "This is different because..." + +**All of these mean: Delete code. Start over with TDD.** + +## Example: Bug Fix + +**Bug:** Empty email accepted + +**RED** +```typescript +test('rejects empty email', async () => { + const result = await submitForm({ email: '' }); + expect(result.error).toBe('Email required'); +}); +``` + +**Verify RED** +```bash +$ npm test +FAIL: expected 'Email required', got undefined +``` + +**GREEN** +```typescript +function submitForm(data: FormData) { + if (!data.email?.trim()) { + return { error: 'Email required' }; + } + // ... +} +``` + +**Verify GREEN** +```bash +$ npm test +PASS +``` + +**REFACTOR** +Extract validation for multiple fields if needed. + +## Verification Checklist + +Before marking work complete: + +- [ ] Every new function/method has a test +- [ ] Watched each test fail before implementing +- [ ] Each test failed for expected reason (feature missing, not typo) +- [ ] Wrote minimal code to pass each test +- [ ] All tests pass +- [ ] Output pristine (no errors, warnings) +- [ ] Tests use real code (mocks only if unavoidable) +- [ ] Edge cases and errors covered + +Can't check all boxes? You skipped TDD. Start over. + +## When Stuck + +| Problem | Solution | +|---------|----------| +| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. | +| Test too complicated | Design too complicated. Simplify interface. | +| Must mock everything | Code too coupled. Use dependency injection. | +| Test setup huge | Extract helpers. Still complex? Simplify design. | + +## Debugging Integration + +Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression. + +Never fix bugs without a test. + +## Testing Anti-Patterns + +When adding mocks or test utilities, read @testing-anti-patterns.md to avoid common pitfalls: +- Testing mock behavior instead of real behavior +- Adding test-only methods to production classes +- Mocking without understanding dependencies + +## Final Rule + +``` +Production code → test exists and failed first +Otherwise → not TDD +``` + +No exceptions without your human partner's permission. diff --git a/ai/opencode/commands/test.md b/ai/opencode/commands/test.md new file mode 100644 index 0000000..c37c02d --- /dev/null +++ b/ai/opencode/commands/test.md @@ -0,0 +1,53 @@ +--- +description: Use when creating tests for functions, classes, or modules. Covers edge cases, failure modes, boundary conditions, and proper mocking. Supports pytest (Python), testing package (Go), vitest/jest (JS/TS), and built-in test (Rust). +--- + +# Test Generation + +Generate complete test file ready to run. + +## Coverage Requirements + +- **Edge cases:** Empty inputs, nil/null, boundary values, zero, negative +- **Failure modes:** Invalid inputs, network errors, timeouts, exceptions +- **Boundary conditions:** Off-by-one, max/min values, empty collections + +Do NOT only test happy path. + +## Patterns + +| Language | Framework | Style | +|----------|-----------|-------| +| Python | pytest | `@pytest.mark.parametrize`, fixtures, `unittest.mock` | +| Go | testing | Table-driven with `t.Run`, `testify` for mocks | +| JS/TS | vitest | `describe`/`it`, `vi.mock` | +| Rust | built-in | `#[test]`, `#[should_panic]` | + +## Structure (Python) + +```python +import pytest +from unittest.mock import Mock, patch + +@pytest.fixture +def mock_db(): + return Mock() + +class TestUserService: + @pytest.mark.parametrize("email,valid", [ + ("user@example.com", True), + ("invalid", False), + ("", False), + ]) + def test_validate_email(self, email, valid): + assert validate_email(email) == valid + + def test_create_user_duplicate_raises(self, mock_db): + mock_db.exists.return_value = True + with pytest.raises(DuplicateError): + create_user(mock_db, "existing@example.com") +``` + +## Output + +Complete test file with imports, fixtures, mocks, and all test cases. diff --git a/ai/opencode/commands/using-git-worktrees.md b/ai/opencode/commands/using-git-worktrees.md new file mode 100644 index 0000000..c59e289 --- /dev/null +++ b/ai/opencode/commands/using-git-worktrees.md @@ -0,0 +1,217 @@ +--- +description: Use when starting feature work that needs isolation from current workspace, or before executing implementation plans that should not affect the main working directory. +--- + +# Using Git Worktrees + +## Overview + +Git worktrees create isolated workspaces sharing the same repository, allowing work on multiple branches simultaneously without switching. + +**Core principle:** Systematic directory selection + safety verification = reliable isolation. + +**Announce at start:** "I'm using the using-git-worktrees skill to set up an isolated workspace." + +## Directory Selection Process + +Follow this priority order: + +### 1. Check Existing Directories + +```bash +# Check in priority order +ls -d .worktrees 2>/dev/null # Preferred (hidden) +ls -d worktrees 2>/dev/null # Alternative +``` + +**If found:** Use that directory. If both exist, `.worktrees` wins. + +### 2. Check CLAUDE.md + +```bash +grep -i "worktree.*director" CLAUDE.md 2>/dev/null +``` + +**If preference specified:** Use it without asking. + +### 3. Ask User + +If no directory exists and no CLAUDE.md preference: + +``` +No worktree directory found. Where should I create worktrees? + +1. .worktrees/ (project-local, hidden) +2. ~/.config/superpowers/worktrees// (global location) + +Which would you prefer? +``` + +## Safety Verification + +### For Project-Local Directories (.worktrees or worktrees) + +**MUST verify directory is ignored before creating worktree:** + +```bash +# Check if directory is ignored (respects local, global, and system gitignore) +git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null +``` + +**If NOT ignored:** + +Per Jesse's rule "Fix broken things immediately": +1. Add appropriate line to .gitignore +2. Commit the change +3. Proceed with worktree creation + +**Why critical:** Prevents accidentally committing worktree contents to repository. + +### For Global Directory (~/.config/superpowers/worktrees) + +No .gitignore verification needed - outside project entirely. + +## Creation Steps + +### 1. Detect Project Name + +```bash +project=$(basename "$(git rev-parse --show-toplevel)") +``` + +### 2. Create Worktree + +```bash +# Determine full path +case $LOCATION in + .worktrees|worktrees) + path="$LOCATION/$BRANCH_NAME" + ;; + ~/.config/superpowers/worktrees/*) + path="~/.config/superpowers/worktrees/$project/$BRANCH_NAME" + ;; +esac + +# Create worktree with new branch +git worktree add "$path" -b "$BRANCH_NAME" +cd "$path" +``` + +### 3. Run Project Setup + +Auto-detect and run appropriate setup: + +```bash +# Node.js +if [ -f package.json ]; then npm install; fi + +# Rust +if [ -f Cargo.toml ]; then cargo build; fi + +# Python +if [ -f requirements.txt ]; then pip install -r requirements.txt; fi +if [ -f pyproject.toml ]; then poetry install; fi + +# Go +if [ -f go.mod ]; then go mod download; fi +``` + +### 4. Verify Clean Baseline + +Run tests to ensure worktree starts clean: + +```bash +# Examples - use project-appropriate command +npm test +cargo test +pytest +go test ./... +``` + +**If tests fail:** Report failures, ask whether to proceed or investigate. + +**If tests pass:** Report ready. + +### 5. Report Location + +``` +Worktree ready at +Tests passing ( tests, 0 failures) +Ready to implement +``` + +## Quick Reference + +| Situation | Action | +|-----------|--------| +| `.worktrees/` exists | Use it (verify ignored) | +| `worktrees/` exists | Use it (verify ignored) | +| Both exist | Use `.worktrees/` | +| Neither exists | Check CLAUDE.md → Ask user | +| Directory not ignored | Add to .gitignore + commit | +| Tests fail during baseline | Report failures + ask | +| No package.json/Cargo.toml | Skip dependency install | + +## Common Mistakes + +### Skipping ignore verification + +- **Problem:** Worktree contents get tracked, pollute git status +- **Fix:** Always use `git check-ignore` before creating project-local worktree + +### Assuming directory location + +- **Problem:** Creates inconsistency, violates project conventions +- **Fix:** Follow priority: existing > CLAUDE.md > ask + +### Proceeding with failing tests + +- **Problem:** Can't distinguish new bugs from pre-existing issues +- **Fix:** Report failures, get explicit permission to proceed + +### Hardcoding setup commands + +- **Problem:** Breaks on projects using different tools +- **Fix:** Auto-detect from project files (package.json, etc.) + +## Example Workflow + +``` +You: I'm using the using-git-worktrees skill to set up an isolated workspace. + +[Check .worktrees/ - exists] +[Verify ignored - git check-ignore confirms .worktrees/ is ignored] +[Create worktree: git worktree add .worktrees/auth -b feature/auth] +[Run npm install] +[Run npm test - 47 passing] + +Worktree ready at /Users/jesse/myproject/.worktrees/auth +Tests passing (47 tests, 0 failures) +Ready to implement auth feature +``` + +## Red Flags + +**Never:** +- Create worktree without verifying it's ignored (project-local) +- Skip baseline test verification +- Proceed with failing tests without asking +- Assume directory location when ambiguous +- Skip CLAUDE.md check + +**Always:** +- Follow directory priority: existing > CLAUDE.md > ask +- Verify directory is ignored for project-local +- Auto-detect and run project setup +- Verify clean test baseline + +## Integration + +**Called by:** +- **brainstorming** (Phase 4) - REQUIRED when design is approved and implementation follows +- **subagent-driven-development** - REQUIRED before executing any tasks +- **executing-plans** - REQUIRED before executing any tasks +- Any skill needing isolated workspace + +**Pairs with:** +- **finishing-a-development-branch** - REQUIRED for cleanup after work complete diff --git a/ai/opencode/commands/vault-doctor.md b/ai/opencode/commands/vault-doctor.md new file mode 100644 index 0000000..d8ca7d5 --- /dev/null +++ b/ai/opencode/commands/vault-doctor.md @@ -0,0 +1,138 @@ +--- +description: Use when the Obsidian vault needs structural maintenance — unresolved links, missing frontmatter, orphan notes, or stale content. Triggers include vault_health reporting failures, /insights showing structural warnings, or periodic vault cleanup sessions. +--- + +# Vault Doctor + +Structural maintenance for the Obsidian knowledge vault. Diagnoses issues, prioritizes by severity, and fixes them systematically. + +## Prerequisites + +- **Hive MCP** — `vault_health`, `vault_query`, `vault_search`, `vault_patch`, `vault_write` +- **obs-cli.sh** (optional) — `orphans`, `dead-ends`, `unresolved`, `backlinks` (requires Obsidian GUI) + +If Obsidian GUI is not running, use Hive MCP only (headless, always available). obs-cli commands that require the GUI will exit with code 2 — degrade gracefully. + +## Protocol + +### Step 1 — Diagnose + +Run diagnostics in parallel: + +```bash +# Via Hive MCP (always available) +vault_health(include_usage=True) + +# Via obs-cli (if Obsidian GUI running) +obs-cli.sh unresolved # Broken wikilinks +obs-cli.sh orphans # No incoming links +obs-cli.sh dead-ends # No outgoing links +``` + +Collect: +- Unresolved links (broken wikilinks pointing to non-existent notes) +- Frontmatter violations (files missing `id`, `type`, or `status` fields) +- Orphan notes (no incoming links — isolated content) +- Dead-end notes (no outgoing links — terminal nodes) +- Vault health test failures from Hive MCP + +### Step 2 — Prioritize + +| Severity | Issue type | Why | +|----------|-----------|-----| +| **HIGH** | Unresolved links | Broken navigation, knowledge gaps | +| **HIGH** | Frontmatter missing `type` or `status` | Breaks vault queries and templates | +| **MEDIUM** | Orphan notes | Lost knowledge, may need connecting or archiving | +| **MEDIUM** | Frontmatter missing `id` | Inconsistency but doesn't break functionality | +| **LOW** | Dead-end notes | May be intentional (leaf content) | + +Present a severity report: + +``` +=== Vault Doctor Diagnosis === + +HIGH: + - 22 unresolved links (list top 10) + - 3 files missing type/status frontmatter + +MEDIUM: + - 5 orphan notes + - 8 files missing id field + +LOW: + - 12 dead-end notes + +Recommended action: Fix HIGH first, then MEDIUM. Review LOW manually. +``` + +### Step 3 — Fix Unresolved Links + +For each unresolved link: + +1. **Search for candidates** — Use `vault_search` to find notes with similar names +2. **If match found** — Suggest the correct target, apply via `vault_patch` +3. **If ambiguous** — Present options to user, wait for selection +4. **If no match** — The link target was never created: + - If it's a common concept → suggest creating a stub note + - If it's stale → suggest removing the link +5. **Never auto-remove** links without user confirmation + +### Step 4 — Fix Frontmatter + +For files missing required frontmatter fields (`id`, `type`, `status`): + +1. **Infer values** from file location and content: + - `id`: derive from filename slug (e.g., `my-note.md` → `my-note`) + - `type`: derive from directory (e.g., `30-architecture/` → `adr`, `90-lessons.md` → `lesson`) + - `status`: default to `active` unless content suggests otherwise +2. **Apply in bulk** using `vault_patch` — inject frontmatter at file top +3. **Preserve existing fields** — only add missing ones, never overwrite + +**Frontmatter template:** +```yaml +--- +id: "" +type: +status: active +tags: [] +--- +``` + +### Step 5 — Handle Orphans + +For each orphan note (no incoming links): + +1. **Check content value** — Is this useful knowledge or stale? +2. **If valuable** — Find related notes via `vault_search` and suggest adding a link from the most relevant note +3. **If stale** — Suggest moving to `90_archive/` +4. **Present list** to user for batch decision: connect, archive, or skip + +### Step 6 — Report + +``` +=== Vault Doctor Report === + +Fixed: + - 18/22 unresolved links resolved + - 8 frontmatter fields added across 5 files + - 3 orphans connected, 2 archived + +Remaining (needs manual review): + - 4 unresolved links (ambiguous targets) + - 12 dead-end notes (may be intentional) + +Vault health: PASS (was: 1 failure) +``` + +## Rules + +- **Never auto-delete content.** Always confirm with user before removing links, archiving notes, or deleting anything. +- **Preserve existing data.** When fixing frontmatter, only add missing fields. Never overwrite existing values. +- **Batch similar fixes.** Group frontmatter fixes by directory for efficiency. +- **Degrade gracefully.** If obs-cli is unavailable, work with Hive MCP only. Report what couldn't be checked. +- **One severity tier at a time.** Fix all HIGH issues before moving to MEDIUM. + +## Pipeline + +- Previous: `/insights` detects structural issues in its Vault Structural Health section +- Next: Run `vault_health` to verify fixes, then `/insights` to confirm clean state diff --git a/ai/opencode/commands/verification-before-completion.md b/ai/opencode/commands/verification-before-completion.md new file mode 100644 index 0000000..8187080 --- /dev/null +++ b/ai/opencode/commands/verification-before-completion.md @@ -0,0 +1,138 @@ +--- +description: Use when about to claim work is complete, fixed, or passing -- before committing, creating PRs, or closing tasks. Evidence before assertions. +--- + +# Verification Before Completion + +## Overview + +Claiming work is complete without verification is dishonesty, not efficiency. + +**Core principle:** Evidence before claims, always. + +**Violating the letter of this rule is violating the spirit of this rule.** + +## The Iron Law + +``` +NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE +``` + +If you haven't run the verification command in this message, you cannot claim it passes. + +## The Gate Function + +``` +BEFORE claiming any status or expressing satisfaction: + +1. IDENTIFY: What command proves this claim? +2. RUN: Execute the FULL command (fresh, complete) +3. READ: Full output, check exit code, count failures +4. VERIFY: Does output confirm the claim? + - If NO: State actual status with evidence + - If YES: State claim WITH evidence +5. ONLY THEN: Make the claim + +Skip any step = lying, not verifying +``` + +## Common Failures + +| Claim | Requires | Not Sufficient | +|-------|----------|----------------| +| Tests pass | Test command output: 0 failures | Previous run, "should pass" | +| Linter clean | Linter output: 0 errors | Partial check, extrapolation | +| Build succeeds | Build command: exit 0 | Linter passing, logs look good | +| Bug fixed | Test original symptom: passes | Code changed, assumed fixed | +| Regression test works | Red-green cycle verified | Test passes once | +| Agent completed | VCS diff shows changes | Agent reports "success" | +| Requirements met | Line-by-line checklist | Tests passing | + +## Red Flags - STOP + +- Using "should", "probably", "seems to" +- Expressing satisfaction before verification ("Great!", "Perfect!", "Done!", etc.) +- About to commit/push/PR without verification +- Trusting agent success reports +- Relying on partial verification +- Thinking "just this once" +- Tired and wanting work over +- **ANY wording implying success without having run verification** + +## Rationalization Prevention + +| Excuse | Reality | +|--------|---------| +| "Should work now" | RUN the verification | +| "I'm confident" | Confidence ≠ evidence | +| "Just this once" | No exceptions | +| "Linter passed" | Linter ≠ compiler | +| "Agent said success" | Verify independently | +| "I'm tired" | Exhaustion ≠ excuse | +| "Partial check is enough" | Partial proves nothing | +| "Different words so rule doesn't apply" | Spirit over letter | + +## Key Patterns + +**Tests:** +``` +✅ [Run test command] [See: 34/34 pass] "All tests pass" +❌ "Should pass now" / "Looks correct" +``` + +**Regression tests (TDD Red-Green):** +``` +✅ Write → Run (pass) → Revert fix → Run (MUST FAIL) → Restore → Run (pass) +❌ "I've written a regression test" (without red-green verification) +``` + +**Build:** +``` +✅ [Run build] [See: exit 0] "Build passes" +❌ "Linter passed" (linter doesn't check compilation) +``` + +**Requirements:** +``` +✅ Re-read plan → Create checklist → Verify each → Report gaps or completion +❌ "Tests pass, phase complete" +``` + +**Agent delegation:** +``` +✅ Agent reports success → Check VCS diff → Verify changes → Report actual state +❌ Trust agent report +``` + +## Why This Matters + +From 24 failure memories: +- your human partner said "I don't believe you" - trust broken +- Undefined functions shipped - would crash +- Missing requirements shipped - incomplete features +- Time wasted on false completion → redirect → rework +- Violates: "Honesty is a core value. If you lie, you'll be replaced." + +## When To Apply + +**ALWAYS before:** +- ANY variation of success/completion claims +- ANY expression of satisfaction +- ANY positive statement about work state +- Committing, PR creation, task completion +- Moving to next task +- Delegating to agents + +**Rule applies to:** +- Exact phrases +- Paraphrases and synonyms +- Implications of success +- ANY communication suggesting completion/correctness + +## The Bottom Line + +**No shortcuts for verification.** + +Run the command. Read the output. THEN claim the result. + +This is non-negotiable. diff --git a/ai/opencode/commands/writing-plans.md b/ai/opencode/commands/writing-plans.md new file mode 100644 index 0000000..0f13a0a --- /dev/null +++ b/ai/opencode/commands/writing-plans.md @@ -0,0 +1,89 @@ +--- +description: Use when you have a spec or requirements for a multi-step task, before touching code. Use after brainstorming or requirements gathering to create an actionable implementation plan. +--- + +# Writing Plans + +Create implementation plans assuming the engineer has zero codebase context. Document everything needed: files to touch, code, testing, verification steps. + +## Task Granularity + +Each step is one action (2-5 minutes): + +- "Write the failing test" -- step +- "Run it to verify it fails" -- step +- "Implement minimal code to pass" -- step +- "Run tests to verify pass" -- step +- "Commit" -- step + +Each task follows the **RED-GREEN-REFACTOR** cycle: write a failing test first, implement minimal code to pass, then refactor. See `test-driven-development` for the full discipline, anti-patterns, and rationalization counters. + +## Plan Header Template + +```markdown +# [Feature Name] Implementation Plan + +**Goal:** [One sentence] + +**Architecture:** [2-3 sentences about approach] + +**Tech Stack:** [Key technologies] + +--- +``` + +## Task Template + +```markdown +### Task N: [Component Name] + +**Files:** +- Create: `exact/path/to/file.py` +- Modify: `exact/path/to/existing.py` +- Test: `tests/exact/path/to/test.py` + +**Step 1: Write failing test** + +` ` `python +def test_specific_behavior(): + result = function(input) + assert result == expected +` ` ` + +**Step 2: Run test to verify failure** + +Run: `pytest tests/path/test.py::test_name -v` +Expected: FAIL with "function not defined" + +**Step 3: Write minimal implementation** + +` ` `python +def function(input): + return expected +` ` ` + +**Step 4: Run test to verify pass** + +Run: `pytest tests/path/test.py::test_name -v` +Expected: PASS + +**Step 5: Commit** + +` ` `bash +git add tests/path/test.py src/path/file.py +git commit -m "feat: add specific feature" +` ` ` +``` + +## Rules + +- Exact file paths always +- Complete code in plan (not "add validation") +- Exact commands with expected output +- DRY, YAGNI, TDD, frequent commits + +## Pipeline + +- Previous: Requirements, spec, or design document +- Next: `/executing-plans` to implement task-by-task +- Quality gate: `/verification-before-completion` before claiming done diff --git a/scripts/skills-to-opencode.sh b/scripts/skills-to-opencode.sh new file mode 100755 index 0000000..e8ec44c --- /dev/null +++ b/scripts/skills-to-opencode.sh @@ -0,0 +1,167 @@ +#!/bin/bash +# scripts/skills-to-opencode.sh +# +# Port Claude Code skills (ai/skills//SKILL.md) to OpenCode commands +# (ai/opencode/commands/.md). Source of truth stays the SKILL.md files; +# OpenCode commands are a deterministic derivative. +# +# Differences between the two formats: +# - SKILL.md frontmatter has `name:` + `description:` -- the filename plus +# `name:` both encode the skill identifier (redundant on purpose). +# - OpenCode commands take only `description:` -- the *filename* alone is +# the command identifier, so `audit.md` becomes `/audit` in the TUI. +# - Body markdown is copied verbatim. +# +# Skip-list: skills that depend on Claude Code-only primitives (TaskCreate, +# AskUserQuestion, subagent_type) or on Anthropic-only MCPs (claude-mem) +# would not work in OpenCode and are excluded. +# +# Usage: +# scripts/skills-to-opencode.sh # generate / sync +# scripts/skills-to-opencode.sh --check # CI gate: exit 1 if drift +# +# Idempotent: re-running with identical inputs is a no-op. + +set -euo pipefail + +SKIP_SKILLS=( + "creating-skills" # meta-skill for Claude skill anatomy + "dispatching-parallel-agents" # uses Task subagent_type (Claude-only) + "crystallize" # depends on claude-mem MCP (Anthropic-only) + "insights" # depends on claude-mem MCP + "executing-plans" # depends on TaskCreate primitive +) + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +SKILLS_DIR="$REPO_DIR/ai/skills" +COMMANDS_DIR="$REPO_DIR/ai/opencode/commands" + +CHECK_ONLY=0 +case "${1:-}" in + --check) CHECK_ONLY=1 ;; + -h|--help) + sed -n '/^# Usage:/,/^# Idempotent:/p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + "") ;; + *) + echo "Unknown argument: $1 (use -h)" >&2 + exit 2 + ;; +esac + +is_skipped() { + local name=$1 + for skip in "${SKIP_SKILLS[@]}"; do + [ "$skip" = "$name" ] && return 0 + done + return 1 +} + +# transform_skill SRC DST +# Reads SKILL.md frontmatter, drops `name:` lines, keeps `description:` and +# any other yaml fields, then appends the body unchanged. +transform_skill() { + local src=$1 + local dst=$2 + awk ' + BEGIN { state = "before"; } + # First --- opens frontmatter. + state == "before" && /^---$/ { + state = "front" + print + next + } + # Second --- closes frontmatter. + state == "front" && /^---$/ { + state = "body" + print + next + } + # Inside frontmatter: skip `name:` lines (OpenCode does not consume it). + state == "front" { + if ($0 ~ /^name:[[:space:]]/) next + print + next + } + # Body: copy verbatim. + state == "body" { print } + ' "$src" > "$dst" +} + +mkdir -p "$COMMANDS_DIR" + +EXIT_CODE=0 +GENERATED=0 +SKIPPED=0 +DRIFT=0 +ORPHAN=0 + +# Phase 1: process each SKILL.md. +for skill_dir in "$SKILLS_DIR"/*/; do + name=$(basename "$skill_dir") + src="$skill_dir/SKILL.md" + [ -f "$src" ] || continue + + if is_skipped "$name"; then + SKIPPED=$((SKIPPED + 1)) + # If a stale command file exists for a now-skipped skill, flag it. + if [ -f "$COMMANDS_DIR/$name.md" ]; then + if [ "$CHECK_ONLY" = "1" ]; then + echo "DRIFT: $COMMANDS_DIR/$name.md exists but $name is on the skip-list" + DRIFT=$((DRIFT + 1)) + EXIT_CODE=1 + else + rm -f "$COMMANDS_DIR/$name.md" + echo "Removed stale $name.md (skill is on skip-list)" + fi + fi + continue + fi + + dst="$COMMANDS_DIR/$name.md" + + if [ "$CHECK_ONLY" = "1" ]; then + tmp=$(mktemp) + transform_skill "$src" "$tmp" + if [ ! -f "$dst" ] || ! cmp -s "$tmp" "$dst"; then + echo "DRIFT: $dst out of sync with $src" + DRIFT=$((DRIFT + 1)) + EXIT_CODE=1 + fi + rm -f "$tmp" + else + transform_skill "$src" "$dst" + GENERATED=$((GENERATED + 1)) + fi +done + +# Phase 2: detect orphan command files (no matching SKILL.md, not on skip-list). +if [ -d "$COMMANDS_DIR" ]; then + for cmd_file in "$COMMANDS_DIR"/*.md; do + [ -f "$cmd_file" ] || continue + cmd_name=$(basename "$cmd_file" .md) + if [ ! -d "$SKILLS_DIR/$cmd_name" ]; then + if [ "$CHECK_ONLY" = "1" ]; then + echo "DRIFT: $cmd_file has no matching skill at $SKILLS_DIR/$cmd_name/" + ORPHAN=$((ORPHAN + 1)) + EXIT_CODE=1 + else + rm -f "$cmd_file" + echo "Removed orphan $cmd_file" + fi + fi + done +fi + +if [ "$CHECK_ONLY" = "1" ]; then + if [ "$DRIFT" -gt 0 ] || [ "$ORPHAN" -gt 0 ]; then + echo "FAIL: $DRIFT drift + $ORPHAN orphan(s). Run: scripts/skills-to-opencode.sh" + else + echo "OK: ai/opencode/commands/ in sync with ai/skills/ (skipped $SKIPPED Claude-only)" + fi +else + echo "Generated $GENERATED command(s) in $COMMANDS_DIR (skipped $SKIPPED Claude-only)" +fi + +exit $EXIT_CODE diff --git a/setup-linux.sh b/setup-linux.sh index 565340a..99b1b4a 100755 --- a/setup-linux.sh +++ b/setup-linux.sh @@ -420,6 +420,43 @@ else log_warning "opencode.jsonc source missing: $OPENCODE_CONFIG_SRC" fi +# Deploy opencode commands (AI-012: skills→commands port). +# Source: ai/opencode/commands/*.md (kept in sync with ai/skills/ via +# scripts/skills-to-opencode.sh; CI gate enforces parity). Target: the +# OpenCode global commands directory, so /audit, /test, /writing-plans, +# etc. are available from any repo where the user launches `oc`. +OPENCODE_CMDS_SRC="$CURRENT_DIR/ai/opencode/commands" +OPENCODE_CMDS_DST="$HOME/.config/opencode/commands" +if [ -d "$OPENCODE_CMDS_SRC" ]; then + ensure_directory "$OPENCODE_CMDS_DST" + cmds_added=0 + cmds_skipped=0 + cmds_removed=0 + for src in "$OPENCODE_CMDS_SRC"/*.md; do + [ -f "$src" ] || continue + dst="$OPENCODE_CMDS_DST/$(basename "$src")" + if [ -f "$dst" ] && cmp -s "$src" "$dst"; then + cmds_skipped=$((cmds_skipped + 1)) + else + cp "$src" "$dst" + cmds_added=$((cmds_added + 1)) + fi + done + # Remove orphan commands on disk that no longer exist in the repo + # (e.g. a skill was added to the skip-list and its command should + # disappear from the user's deployed set). + for dst in "$OPENCODE_CMDS_DST"/*.md; do + [ -f "$dst" ] || continue + if [ ! -f "$OPENCODE_CMDS_SRC/$(basename "$dst")" ]; then + rm -f "$dst" + cmds_removed=$((cmds_removed + 1)) + fi + done + log_success "opencode commands: $cmds_added added, $cmds_skipped already in sync, $cmds_removed orphans removed" +else + log_info "opencode commands source missing: $OPENCODE_CMDS_SRC (skipping; run scripts/skills-to-opencode.sh to generate)" +fi + # Post-deploy assertion — binary reachable + version reports if [ -x "$OPENCODE_BINARY" ] || command -v opencode >/dev/null 2>&1; then OPENCODE_VERSION=$("$OPENCODE_BINARY" --version 2>&1 | head -1 || echo "unknown") diff --git a/tests/skills-to-opencode.bats b/tests/skills-to-opencode.bats new file mode 100644 index 0000000..8cad175 --- /dev/null +++ b/tests/skills-to-opencode.bats @@ -0,0 +1,129 @@ +#!/usr/bin/env bats +# Tests for scripts/skills-to-opencode.sh (AI-012) +# Verifies the deterministic port of ai/skills/*/SKILL.md to +# ai/opencode/commands/*.md. + +setup() { + export REPO_DIR="$BATS_TEST_DIRNAME/.." + export SCRIPT="$REPO_DIR/scripts/skills-to-opencode.sh" + export SKILLS_DIR="$REPO_DIR/ai/skills" + export COMMANDS_DIR="$REPO_DIR/ai/opencode/commands" +} + +@test "script exists and is executable" { + [ -x "$SCRIPT" ] +} + +@test "script passes bash syntax check" { + bash -n "$SCRIPT" +} + +@test "script passes zsh syntax check" { + zsh -n "$SCRIPT" +} + +@test "script --check exits 0 when in sync" { + "$SCRIPT" --check +} + +@test "commands/ directory exists" { + [ -d "$COMMANDS_DIR" ] +} + +@test "12 portable commands generated (5 Claude-only skipped)" { + local count + count=$(find "$COMMANDS_DIR" -maxdepth 1 -name '*.md' | wc -l) + [ "$count" -eq 12 ] +} + +@test "skip-list applied: creating-skills NOT in commands/" { + [ ! -f "$COMMANDS_DIR/creating-skills.md" ] +} + +@test "skip-list applied: dispatching-parallel-agents NOT in commands/" { + [ ! -f "$COMMANDS_DIR/dispatching-parallel-agents.md" ] +} + +@test "skip-list applied: crystallize NOT in commands/" { + [ ! -f "$COMMANDS_DIR/crystallize.md" ] +} + +@test "skip-list applied: insights NOT in commands/" { + [ ! -f "$COMMANDS_DIR/insights.md" ] +} + +@test "skip-list applied: executing-plans NOT in commands/" { + [ ! -f "$COMMANDS_DIR/executing-plans.md" ] +} + +@test "portable command audit.md exists" { + [ -f "$COMMANDS_DIR/audit.md" ] +} + +@test "portable command writing-plans.md exists" { + [ -f "$COMMANDS_DIR/writing-plans.md" ] +} + +@test "portable command vault-doctor.md exists (Hive MCP works in OpenCode)" { + [ -f "$COMMANDS_DIR/vault-doctor.md" ] +} + +@test "generated frontmatter drops 'name:' line (filename is the identifier in OpenCode)" { + ! grep -q '^name:' "$COMMANDS_DIR/audit.md" +} + +@test "generated frontmatter keeps 'description:' line" { + grep -q '^description:' "$COMMANDS_DIR/audit.md" +} + +@test "generated command preserves frontmatter delimiters" { + local first second + first=$(grep -n '^---$' "$COMMANDS_DIR/audit.md" | head -1 | cut -d: -f1) + second=$(grep -n '^---$' "$COMMANDS_DIR/audit.md" | sed -n '2p' | cut -d: -f1) + [ "$first" = "1" ] + [ -n "$second" ] + [ "$second" -gt "$first" ] +} + +@test "generated command preserves body content" { + # audit.md body starts with '# Security Audit' + grep -q '^# Security Audit' "$COMMANDS_DIR/audit.md" +} + +@test "script --check detects drift when a command file is mutated" { + local victim="$COMMANDS_DIR/audit.md" + local backup + backup=$(mktemp) + cp "$victim" "$backup" + echo "# rogue line" >> "$victim" + run "$SCRIPT" --check + cp "$backup" "$victim" + rm -f "$backup" + [ "$status" -ne 0 ] +} + +@test "script --check detects orphan command (no matching skill)" { + local orphan="$COMMANDS_DIR/nonexistent-skill.md" + echo "---" > "$orphan" + echo "description: stray" >> "$orphan" + echo "---" >> "$orphan" + run "$SCRIPT" --check + rm -f "$orphan" + [ "$status" -ne 0 ] +} + +@test "script regeneration is idempotent (no diff on second run)" { + "$SCRIPT" >/dev/null + "$SCRIPT" --check +} + +@test "script -h / --help prints usage" { + run "$SCRIPT" --help + [ "$status" -eq 0 ] + [[ "$output" == *"Usage:"* ]] || [[ "$output" == *"usage:"* ]] +} + +@test "script rejects unknown arguments" { + run "$SCRIPT" --bogus-flag + [ "$status" -ne 0 ] +} diff --git a/tests/verify-setup.bats b/tests/verify-setup.bats index a55e72e..54422c9 100644 --- a/tests/verify-setup.bats +++ b/tests/verify-setup.bats @@ -294,6 +294,19 @@ setup() { [ ! -d "$HOME/.copilot" ] } +@test "opencode commands deployed to ~/.config/opencode/commands/ (AI-012)" { + # Post-AI-012: setup-linux.sh copies ai/opencode/commands/*.md to the + # global opencode commands directory. 12 portable commands expected + # (5 Claude-only skills are skipped by scripts/skills-to-opencode.sh). + [ -d "$HOME/.config/opencode/commands" ] + local count + count=$(find "$HOME/.config/opencode/commands" -maxdepth 1 -name '*.md' | wc -l) + [ "$count" -eq 12 ] + # Spot check: audit.md present (portable), creating-skills.md absent (skip-list). + [ -f "$HOME/.config/opencode/commands/audit.md" ] + [ ! -f "$HOME/.config/opencode/commands/creating-skills.md" ] +} + @test "no MCP servers registered (claude CLI absent)" { # setup-linux.sh skips MCP registration when claude is not found # Just verify it didn't crash — the container built successfully