← Back to task

Commit fa438b05

commit fa438b0519be0babcf069bece0f8952dcd5b3d1d
Author: Ben Sima <ben@bensima.com>
Date:   Thu Jan 1 16:25:19 2026

    Omni/Ide: Add development workflow skills
    
    New skills for agent-assisted development:
    - Coder.md - implementing code changes
    - Reviewer.md - reviewing diffs/PRs
    - Git.md - git-branchless workflow
    - Debug.md - debugging issues
    - Test.md - writing and running tests
    - Feature.md - end-to-end feature development
    
    Also adds:
    - Omni/Bild/Skill.md - build tool usage
    - Omni/Deploy/Skill.md - deployment system
    
    These skills live next to the code they relate to rather than
    in a separate skills directory. Agents read them on demand via
    read_file after seeing the skill index in AGENTS.md.
    
    Task-Id: t-321, t-322, t-323, t-324, t-325, t-326, t-327

diff --git a/Omni/Bild/Skill.md b/Omni/Bild/Skill.md
new file mode 100644
index 00000000..96be49c2
--- /dev/null
+++ b/Omni/Bild/Skill.md
@@ -0,0 +1,131 @@
+# Build
+
+Build tool usage and dependency management. Use when building, adding dependencies, or fixing build errors.
+
+## Common Commands
+
+```bash
+bild Omni/Task.hs           # Build a namespace
+bild --test Omni/Task.hs    # Build and run tests
+bild --plan Omni/Task.hs    # Analyze without building
+bild --time 0 Omni/Task.hs  # Build with no timeout
+```
+
+Output goes to `_/bin/`:
+```bash
+bild Omni/Task.hs
+_/bin/task --help
+```
+
+## Adding Dependencies
+
+### Python (in nixpkgs)
+
+Just add the dep comment:
+```python
+# : dep requests
+import requests as Requests
+```
+
+Note: Use `import X as Y` pattern, not `from X import Y`.
+
+### Haskell (in nixpkgs)
+
+Add to `Omni/Bild/Deps/Haskell.nix` whitelist:
+```nix
+[
+  "aeson"
+  "your-package"  # alphabetically
+]
+```
+
+### Not in nixpkgs
+
+1. `deps add owner/repo -n package-name`
+2. Set `"auto-override": false` in Sources.json
+3. Create derivation in `Omni/Bild/Deps/`
+
+## Lint and Type Check
+
+```bash
+lint Omni/Task.hs              # Lint
+lint --fix Omni/Task.py        # Lint and auto-fix
+typecheck.sh Omni/Example.py   # Type check Python
+```
+
+## Common Errors
+
+### "attribute 'foo' missing"
+
+The Haskell package isn't whitelisted. Add it to `Omni/Bild/Deps/Haskell.nix`:
+
+```nix
+[
+  ...
+  "foo"  # Add alphabetically
+  ...
+]
+```
+
+### Python "ModuleNotFoundError"
+
+Check package name in nixpkgs:
+```bash
+nix-env -qaP -A nixpkgs.python312Packages | grep <name>
+```
+
+The nixpkgs name may differ from pip name.
+
+### Timeout during build
+
+Use no timeout:
+```bash
+bild --time 0 <namespace>
+```
+
+### "module X not found" (Haskell)
+
+Check:
+1. File exists at the import path
+2. Module name matches file path
+3. Dep comment is present: `-- : dep Omni/Other/Module.hs`
+
+### Import not detected (Python)
+
+bild only detects `import X as Y` pattern. Change:
+```python
+# Wrong - bild won't detect
+from requests import get
+
+# Right - bild will detect
+import requests as Requests
+```
+
+## Package Name Lookup
+
+```bash
+# Find Python packages
+nix-env -qaP -A nixpkgs.python312Packages | grep <name>
+
+# Find Haskell packages
+nix-env -qaP -A nixpkgs.haskellPackages | grep <name>
+
+# Find system packages
+nix-env -qaP -A nixpkgs | grep <name>
+```
+
+## Dep Comments
+
+Haskell:
+```haskell
+-- : out myapp           -- Output binary name
+-- : dep aeson           -- Hackage dependency
+-- : dep Omni/Other.hs   -- Local module dependency
+```
+
+Python:
+```python
+# : out myapp            # Output binary name
+# : dep requests         # PyPI dependency
+# : dep Omni/Other.py    # Local module dependency
+```
diff --git a/Omni/Deploy/Skill.md b/Omni/Deploy/Skill.md
new file mode 100644
index 00000000..cf57acfd
--- /dev/null
+++ b/Omni/Deploy/Skill.md
@@ -0,0 +1,158 @@
+# Deploy
+
+Deploy services using the mini-PaaS system. Use when deploying, checking deployment status, or troubleshooting deployments.
+
+## Deploying a Service
+
+```bash
+# Build, cache, and update manifest
+Omni/Ide/push.sh Biz/PodcastItLater/Web.py
+
+# Or for Ava specifically  
+Omni/Ide/ship.sh Omni/Ava.hs
+```
+
+The deployer on target hosts polls every 5 minutes and applies changes.
+
+## Force Immediate Deploy
+
+```bash
+ssh biz sudo systemctl start deployer
+```
+
+## Checking Status
+
+```bash
+# View current manifest
+deploy-manifest show
+
+# Check specific service
+deploy-manifest show | jq '.services[] | select(.name == "ava")'
+
+# Check deployer on target
+ssh biz sudo systemctl status deployer
+ssh biz cat /var/lib/deployer/state.json
+```
+
+## Viewing Logs
+
+```bash
+# Service logs
+ssh biz sudo journalctl -u <service-name> -f
+
+# Deployer logs
+ssh biz sudo journalctl -u deployer -f
+
+# Caddy (reverse proxy) logs
+ssh biz sudo journalctl -u caddy -f
+```
+
+## Rollback
+
+```bash
+# List available versions
+deploy-manifest list
+
+# Rollback to previous
+deploy-manifest rollback manifest-YYYYMMDD.json
+
+# Force deploy after rollback
+ssh biz sudo systemctl start deployer
+```
+
+## Secrets
+
+Secrets are stored on target hosts, never in S3:
+
+- Location: `/var/lib/deployer-secrets/<service>.env`
+- Format: Standard env file
+- Permissions: 600, owned by root
+
+```bash
+# View secrets (on target)
+ssh biz sudo cat /var/lib/deployer-secrets/ava.env
+
+# Edit secrets (on target)
+ssh biz sudo vim /var/lib/deployer-secrets/ava.env
+
+# Restart service to pick up secret changes
+ssh biz sudo systemctl restart <service-name>
+```
+
+## Adding New Service
+
+1. Add service to manifest:
+```bash
+deploy-manifest add-service '{
+  "name": "my-service",
+  "artifact": {"storePath": "/nix/store/placeholder"},
+  "hosts": ["biz"],
+  "exec": {"command": null, "user": "root", "group": "root"},
+  "env": {"PORT": "8080"},
+  "envFile": "/var/lib/deployer-secrets/my-service.env",
+  "http": {"domain": "my.example.com", "path": "/", "internalPort": 8080}
+}'
+```
+
+2. Create secrets file on target:
+```bash
+ssh biz sudo tee /var/lib/deployer-secrets/my-service.env << 'EOF'
+SECRET_KEY=...
+DATABASE_URL=...
+EOF
+ssh biz sudo chmod 600 /var/lib/deployer-secrets/my-service.env
+```
+
+3. Deploy:
+```bash
+Omni/Ide/push.sh Biz/MyService.py
+```
+
+## Troubleshooting
+
+### Service won't start
+
+1. Check logs: `ssh biz sudo journalctl -u <service> -n 50`
+2. Check if binary exists: `ssh biz ls -la /nix/store/<path>`
+3. Check permissions on secrets file
+4. Try running manually: `ssh biz sudo /nix/store/<path>/bin/<name>`
+
+### Deployment not picked up
+
+1. Check deployer is running: `ssh biz sudo systemctl status deployer`
+2. Check manifest was updated: `deploy-manifest show | jq '.services[] | .artifact.storePath'`
+3. Force deploy: `ssh biz sudo systemctl start deployer`
+
+### 502 Bad Gateway
+
+1. Is the service running? `ssh biz sudo systemctl status <service>`
+2. Is it on the right port? Check manifest's `internalPort`
+3. Check Caddy config: `ssh biz curl -s localhost:2019/config/ | jq .`
+
+### Store path not found
+
+The binary wasn't cached to S3. Re-run:
+```bash
+Omni/Ide/push.sh <namespace>
+```
+
+This builds, caches to S3, and updates the manifest.
+
+## Architecture
+
+```
+Developer Machine                    S3 Cache                    Target Host
+       │                                │                              │
+       │ push.sh Biz/App.py             │                              │
+       ├───────────────────────────────►│                              │
+       │  1. bild builds                │                              │
+       │  2. nix copy to S3             │                              │
+       │  3. deploy-manifest update     │                              │
+       │                                │   poll every 5 min           │
+       │                                │◄─────────────────────────────┤
+       │                                │   deployer checks manifest   │
+       │                                │   - pulls closure from S3    │
+       │                                │   - generates systemd unit   │
+       │                                │   - updates Caddy route      │
+       │                                │   - restarts service         │
+```
diff --git a/Omni/Ide/Coder.md b/Omni/Ide/Coder.md
new file mode 100644
index 00000000..425fa8ae
--- /dev/null
+++ b/Omni/Ide/Coder.md
@@ -0,0 +1,58 @@
+# Coder
+
+Implement code changes for a task. Use when given a task to implement, bug to fix, or feature to build.
+
+## Process
+
+1. **Understand** - Read the task requirements carefully
+2. **Explore** - Use `read_file` and `bash` to understand existing code
+3. **Plan** - Identify what files need to change
+4. **Implement** - Make minimal, focused changes
+5. **Verify** - Run `bild <namespace>` to confirm compilation
+6. **Fix** - If build fails, fix errors and re-verify
+
+## Guidelines
+
+- Make the smallest change that solves the problem
+- Follow existing code patterns and conventions
+- Don't refactor unrelated code
+- Don't add features not requested
+- Always verify compilation before finishing
+
+## Verification
+
+You MUST run:
+```bash
+bild <namespace>
+```
+
+If it fails, fix the errors. Do not declare success until it compiles.
+
+If there are tests, also run:
+```bash
+bild --test <namespace>
+```
+
+## Common Patterns
+
+### Haskell
+
+- Check existing imports for patterns
+- Use `Text` not `String` (unless interfacing with legacy code)
+- Add type signatures to all top-level definitions
+- Use qualified imports: `import qualified Data.Text as Text`
+
+### Python
+
+- Use `import X as Y` pattern (not `from X import Y`) - required by bild
+- Add type hints to function signatures
+- Use `# : dep <package>` comments for dependencies
+
+## Output
+
+When done, summarize:
+- What files were changed
+- What the changes do
+- Any concerns or follow-up work needed
+
+Do NOT commit - a reviewer will handle that.
diff --git a/Omni/Ide/Debug.md b/Omni/Ide/Debug.md
new file mode 100644
index 00000000..89319e4d
--- /dev/null
+++ b/Omni/Ide/Debug.md
@@ -0,0 +1,153 @@
+# Debugging
+
+Debug issues in Haskell and Python code. Use when something isn't working, you see an error, or need to trace a problem.
+
+## Process
+
+1. **Reproduce** - Can you trigger the error consistently?
+2. **Isolate** - What's the minimal case that fails?
+3. **Trace** - Follow the error from symptom to cause
+4. **Fix** - Make the smallest change that fixes it
+5. **Verify** - Confirm the fix works and didn't break anything
+
+## Reading Error Messages
+
+### Haskell
+
+```
+Foo.hs:42:15: error:
+    • Couldn't match type 'Text' with 'String'
+    • Expected: String
+      Actual: Text
+```
+
+- File:line:column tells you exactly where
+- The bullet points explain what's wrong
+- Look for "Expected" vs "Actual" type
+
+Common Haskell errors:
+- `Couldn't match type` - Type mismatch, check function signatures
+- `Not in scope` - Missing import or typo
+- `Ambiguous occurrence` - Multiple imports define same name, use qualified
+- `No instance for` - Missing typeclass instance, add constraint or derive
+
+### Python
+
+```
+Traceback (most recent call last):
+  File "foo.py", line 42, in bar
+    result = process(data)
+  File "foo.py", line 10, in process
+    return data["key"]
+KeyError: 'key'
+```
+
+- Read bottom-up: last line is the error
+- Traceback shows the call stack
+- Look at the line that raised the exception
+
+Common Python errors:
+- `KeyError` - Dictionary key doesn't exist
+- `AttributeError` - Object doesn't have that attribute
+- `TypeError` - Wrong type passed to function
+- `ImportError` - Module not found or circular import
+
+## Checking Logs
+
+### Systemd services
+
+```bash
+journalctl -u <service-name> -f              # Follow logs
+journalctl -u <service-name> --since "1 hour ago"
+journalctl -u <service-name> -p err          # Errors only
+```
+
+### Agent/subagent logs
+
+```bash
+ls ~/logs/subagents/                         # Subagent traces
+cat ~/logs/orchestrator/*.log                # Orchestrator logs
+```
+
+### Build logs
+
+```bash
+bild Omni/Thing.hs 2>&1 | less              # Capture and page
+```
+
+## Common Issues
+
+### "Connection refused"
+
+1. Is the service running? `systemctl status <service>`
+2. Is it the right port? Check config
+3. Is it bound to localhost vs 0.0.0.0?
+4. Firewall? `sudo iptables -L`
+
+### "Permission denied"
+
+1. Check file permissions: `ls -la <file>`
+2. Check ownership: `stat <file>`
+3. Check if path is in protected directory
+4. Check systemd hardening (ProtectSystem, ReadOnlyPaths)
+
+### "Module not found"
+
+1. Check import statement matches file path
+2. Check the dep comment is present
+3. Run `bild` to regenerate
+
+### Infinite loop / hang
+
+1. Add print/trace statements to narrow down
+2. Check for recursive calls without base case
+3. Check for blocking I/O without timeout
+4. Use `timeout` command: `timeout 10s command`
+
+## Debugging Techniques
+
+### Print debugging
+
+Haskell:
+```haskell
+import Debug.Trace (trace, traceShow)
+
+foo x = trace ("x = " <> show x) $ doThing x
+```
+
+Python:
+```python
+print(f"DEBUG: x = {x}")
+```
+
+### Simplify
+
+Remove code until it works, then add back piece by piece.
+
+### Check assumptions
+
+```bash
+# Verify file exists
+ls -la /path/to/file
+
+# Verify command output
+echo "input" | command
+
+# Verify environment
+env | grep RELEVANT_VAR
+```
+
+### Search codebase
+
+```bash
+grep -r "error message" .
+grep -r "function_name" --include="*.hs"
+```
+
+## When Stuck
+
+1. Re-read the error message carefully
+2. Check if you've seen this before (search git history)
+3. Simplify to minimal reproduction
+4. Check documentation
+5. Ask for help - describe what you tried
diff --git a/Omni/Ide/Feature.md b/Omni/Ide/Feature.md
new file mode 100644
index 00000000..1e3cefbd
--- /dev/null
+++ b/Omni/Ide/Feature.md
@@ -0,0 +1,102 @@
+# Feature Development
+
+End-to-end process for developing a product feature. Use when working on a task that involves building something user-facing, adding a new capability, or implementing a feature request.
+
+This process ensures features are well-specified, user-friendly, and correctly implemented by applying multiple perspectives sequentially.
+
+## Step 1: Clarify Requirements (Product)
+
+Before any implementation, analyze the task:
+
+1. **Problem** - What problem does this solve? Who benefits?
+2. **Scope** - What's the minimum viable solution?
+3. **Criteria** - What are the acceptance criteria? How do we know it's done?
+4. **Boundaries** - What should we explicitly NOT do?
+
+Record your analysis:
+```bash
+task comment <task-id> --role=product "<your spec>"
+```
+
+If requirements are unclear, ask the user before proceeding.
+
+## Step 2: Design Review (Designer)
+
+Consider the user experience:
+
+1. **Discovery** - How will users discover and use this feature?
+2. **Errors** - What happens when things go wrong?
+3. **Edge cases** - What unusual situations might users encounter?
+4. **Consistency** - Does this match existing patterns?
+
+Record your notes:
+```bash
+task comment <task-id> --role=designer "<your notes>"
+```
+
+For CLI changes, describe the expected interaction:
+```
+$ command --new-flag value
+Expected output...
+
+$ command --new-flag (missing value)
+Error: --new-flag requires a value
+```
+
+## Step 3: Implement (Engineer)
+
+Now implement the solution:
+
+1. Read the Coder skill (`Omni/Ide/Coder.md`) for implementation guidelines
+2. Write tests that verify the acceptance criteria
+3. Follow existing code conventions
+4. Keep changes focused - don't refactor unrelated code
+
+The implementation should satisfy both the product spec and design notes.
+
+```bash
+bild --test <namespace>
+lint <namespace>
+```
+
+## Step 4: Review
+
+Re-read all role comments on this task:
+
+```bash
+task show <task-id> --json | jq '.taskComments'
+```
+
+Check:
+- Does the implementation satisfy the Product spec?
+- Does it address the Designer's concerns?
+- Do tests pass?
+- Does lint pass?
+
+If anything is missing, iterate on the relevant step.
+
+## Step 5: Complete
+
+When everything checks out:
+
+1. Summarize what was done
+2. Note any follow-up work discovered
+3. Mark the task done:
+
+```bash
+task update <task-id> done
+```
+
+## When to Use This vs Just Coding
+
+**Use Feature Development for:**
+- New user-facing features
+- Changes to existing UX
+- Anything with acceptance criteria
+- Tasks that need stakeholder alignment
+
+**Just use Coder skill for:**
+- Bug fixes with clear reproduction
+- Internal refactoring
+- Simple additions with obvious implementation
+- Tasks where requirements are already crystal clear
diff --git a/Omni/Ide/Git.md b/Omni/Ide/Git.md
new file mode 100644
index 00000000..89be30be
--- /dev/null
+++ b/Omni/Ide/Git.md
@@ -0,0 +1,108 @@
+# Git Workflow
+
+Git operations for the omnirepo using git-branchless.
+
+## Key Commands
+
+### View commit graph
+```bash
+git smartlog
+```
+
+### Create a commit
+```bash
+git add .
+git commit -m "Namespace: Short description"
+```
+
+### Amend current commit
+```bash
+git add .
+git amend
+```
+
+### Move/restack commits
+```bash
+git move -s <source> -d <destination>
+git restack
+```
+
+## Commit Message Format
+
+```
+Namespace: Short description (max 72 chars)
+
+Longer explanation if needed. Explain why, not just what.
+
+Task-Id: t-123
+```
+
+Example:
+```
+Omni/Task: Add --namespace flag to create command
+
+Allows associating tasks with specific code namespaces for
+better organization and filtering.
+
+Task-Id: t-456
+```
+
+### Guidelines
+
+- First line: `Namespace: Short description`
+- Keep first line under 72 characters
+- Blank line before body (if body needed)
+- Body explains *why*, not just *what*
+- Add `Task-Id:` trailer if working on a task
+
+## Before Committing
+
+Always run:
+```bash
+bild --test <namespace>
+lint <namespace>
+```
+
+Fix all errors before committing.
+
+## What NOT to Do
+
+- ❌ `git push` without explicit user request
+- ❌ `git pull` without explicit user request
+- ❌ Force pushes or destructive operations
+- ❌ Large commits mixing unrelated changes
+- ❌ Commits that don't compile
+- ❌ Commits without running tests
+
+## When to Commit
+
+**DO commit:**
+- Completed features or bug fixes
+- Working code that passes tests
+- Significant milestones
+
+**DON'T commit:**
+- Work in progress (unless asked)
+- Broken or untested code
+- Temporary debugging changes
+
+## Stacking Workflow
+
+For larger features, use stacking:
+
+```bash
+# Create first commit
+git add . && git commit -m "Omni/Thing: Part 1 - types"
+
+# Create second commit on top
+git add . && git commit -m "Omni/Thing: Part 2 - implementation"
+
+# View the stack
+git smartlog
+
+# Amend an earlier commit in the stack
+git checkout <commit>
+git add . && git amend
+git checkout -   # back to tip
+git restack      # rebase everything
+```
diff --git a/Omni/Ide/Reviewer.md b/Omni/Ide/Reviewer.md
new file mode 100644
index 00000000..ac4751c1
--- /dev/null
+++ b/Omni/Ide/Reviewer.md
@@ -0,0 +1,79 @@
+# Reviewer
+
+Review code changes for correctness and quality. Use when evaluating a diff, PR, or uncommitted changes.
+
+## Process
+
+1. **Understand** - Read the task requirements
+2. **Review diff** - Examine what changed (`git diff`)
+3. **Check results** - Review lint/build/test output
+4. **Evaluate** - Does this satisfy the requirements?
+5. **Decide** - APPROVE, REJECT, or REQUEST_CHANGES
+
+## Criteria
+
+1. **Correctness** - Does the code accomplish what the task requested?
+2. **Bugs** - Are there any obvious bugs or issues?
+3. **Build** - Did lint/build/test pass?
+4. **Conventions** - Does it follow project patterns?
+5. **Completeness** - Is anything missing?
+
+## Running Checks
+
+Before reviewing, run:
+```bash
+lint <namespace>
+bild <namespace>
+bild --test <namespace>
+```
+
+Include the output in your review.
+
+## Verdicts
+
+- **APPROVE** - Changes are good, ready to commit
+- **REJECT** - Changes are fundamentally wrong, need to start over
+- **REQUEST_CHANGES** - Changes are on the right track but need fixes
+
+## Output Format
+
+Explain your reasoning, then end with exactly one of:
+
+```
+VERDICT: APPROVE
+VERDICT: REJECT
+VERDICT: REQUEST_CHANGES
+```
+
+If REJECT or REQUEST_CHANGES, explain what's wrong BEFORE the verdict line.
+
+## Example
+
+```
+## Review
+
+The implementation correctly adds the --namespace flag to task create.
+
+### What's Good
+- Flag parsing is correct
+- Database schema updated properly
+- Tests added for new functionality
+
+### Issues
+- Missing test for empty namespace string
+- Help text could be clearer
+
+### Build Results
+- lint: passed
+- bild: passed  
+- tests: 14/14 passed
+
+VERDICT: APPROVE
+```
+
+## Guidelines
+
+- Be specific - reference file:line when possible
+- Be constructive - suggest fixes, don't just criticize
+- Focus on correctness first, style second
+- Don't block on nitpicks - note them but still approve if code is correct
diff --git a/Omni/Ide/Test.md b/Omni/Ide/Test.md
new file mode 100644
index 00000000..80b5100c
--- /dev/null
+++ b/Omni/Ide/Test.md
@@ -0,0 +1,184 @@
+# Testing
+
+Write and run tests for Haskell and Python code. Use when adding tests, verifying behavior, or debugging test failures.
+
+## Running Tests
+
+```bash
+bild --test Omni/Task.hs    # Build and run tests
+_/bin/task test             # Run tests on existing binary
+```
+
+## Test Convention
+
+All programs follow the pattern: if first arg is `test`, run tests.
+
+```haskell
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    ["test"] -> runTests
+    _ -> normalMain
+```
+
+## Writing Tests (Haskell)
+
+Use HUnit:
+
+```haskell
+-- : dep HUnit
+
+import Test.HUnit
+
+runTests :: IO ()
+runTests = do
+  results <- runTestTT $ TestList
+    [ "parse valid input" ~: parseInput "foo" ~?= Right Foo
+    , "parse empty fails" ~: parseInput "" ~?= Left "empty input"
+    , "handles unicode" ~: parseInput "日本語" ~?= Right (Text "日本語")
+    ]
+  when (failures results > 0) exitFailure
+```
+
+### Test helpers
+
+```haskell
+-- Test that something throws
+assertThrows :: IO a -> IO ()
+assertThrows action = do
+  result <- try action
+  case result of
+    Left (_ :: SomeException) -> pure ()
+    Right _ -> assertFailure "Expected exception"
+
+-- Test with setup/teardown
+withTempFile :: (FilePath -> IO a) -> IO a
+withTempFile action = do
+  path <- emptySystemTempFile "test"
+  action path `finally` removeFile path
+```
+
+## Writing Tests (Python)
+
+Use pytest:
+
+```python
+# : dep pytest
+
+import pytest
+
+def test_parse_valid():
+    assert parse_input("foo") == Foo()
+
+def test_parse_empty_fails():
+    with pytest.raises(ValueError):
+        parse_input("")
+
+def test_handles_unicode():
+    assert parse_input("日本語") == Text("日本語")
+```
+
+### Fixtures
+
+```python
+@pytest.fixture
+def temp_db():
+    db = create_temp_database()
+    yield db
+    db.cleanup()
+
+def test_with_db(temp_db):
+    temp_db.insert({"key": "value"})
+    assert temp_db.get("key") == "value"
+```
+
+## What to Test
+
+### Do test
+
+- **Happy path** - Does it work with normal input?
+- **Edge cases** - Empty, None, max values, boundaries
+- **Error cases** - Invalid input, missing data, failures
+- **Regressions** - Add test for each bug fix
+
+### Don't over-test
+
+- Simple getters/setters
+- Obvious wrappers
+- Implementation details that may change
+- External services (mock them)
+
+## Test-Driven Debugging
+
+When something breaks:
+
+1. Write a test that reproduces the bug
+2. Verify test fails
+3. Fix the code
+4. Verify test passes
+5. Keep the test (prevents regression)
+
+## Test Organization
+
+```haskell
+runTests :: IO ()
+runTests = do
+  results <- runTestTT $ TestList
+    [ -- Group related tests
+      TestLabel "Parsing" $ TestList
+        [ "valid input" ~: ...
+        , "invalid input" ~: ...
+        ]
+    , TestLabel "Serialization" $ TestList
+        [ "round trip" ~: ...
+        ]
+    ]
+```
+
+## Common Test Patterns
+
+### Property-based (Haskell)
+
+```haskell
+-- : dep QuickCheck
+
+import Test.QuickCheck
+
+prop_roundTrip :: Text -> Bool
+prop_roundTrip t = decode (encode t) == Just t
+
+runTests = quickCheck prop_roundTrip
+```
+
+### Parameterized (Python)
+
+```python
+@pytest.mark.parametrize("input,expected", [
+    ("foo", Foo()),
+    ("bar", Bar()),
+    ("", None),
+])
+def test_parse(input, expected):
+    assert parse(input) == expected
+```
+
+### Mocking
+
+```python
+from unittest.mock import patch
+
+def test_api_call():
+    with patch('module.requests.get') as mock_get:
+        mock_get.return_value.json.return_value = {"key": "value"}
+        result = fetch_data()
+        assert result == {"key": "value"}
+```
+
+## Debugging Test Failures
+
+1. Run single test: `pytest test_file.py::test_name -v`
+2. Add print statements
+3. Check test isolation (does it pass alone but fail with others?)
+4. Check for shared mutable state
+5. Check for timing/race conditions