Files
Jarvis/CONTRIBUTING.md
james.maesandClaude Opus 4.7 0234eaa07f docs: write CONTRIBUTING.md
Dev environment setup, build/test pointers, GSD workflow, boundary-gate
discipline, conventional-commit conventions matching the existing log,
test-double naming taxonomy, anti-pattern checklist, and step-by-step
guides for adding an MCP tool and adding a HUD button + bus event.
Includes a compaction-recovery section for AI agents.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 17:19:19 -05:00

150 lines
8.5 KiB
Markdown

# Contributing
Personal-use project; the audience for this file is me-six-months-from-now and any AI coding agent operating in this repo. If you're an outside collaborator, this file probably isn't for you — see [`README.md`](README.md).
## Dev environment
| Requirement | Version |
|-------------|---------|
| macOS | 26 Tahoe (Apple Silicon) |
| Xcode | 16+ |
| Swift | 6.0 (strict concurrency) |
| Node | LTS |
| pnpm | 9+ |
| Ollama (optional) | Latest; pull `qwen2.5-coder:32b` + `nomic-embed-text` |
Clone, then:
```bash
# Webview workspace
cd webview && pnpm install
cd ..
# Open in Xcode (project.yml is the source of truth; xcodeproj is generated)
open Jarvis.xcodeproj
```
## Build / test cycle
Don't memorize commands — see the [`Build & test`](README.md#build--test) section in the root README and [`CLAUDE.md > Commands`](CLAUDE.md#commands). The short version:
```bash
swift test --package-path packages/<Pkg> # default loop, fast, offline
bash scripts/check-app-builds.sh # App target via xcodebuild
bash scripts/build-webview.sh # HUD bundle
for s in scripts/check-*.sh; do bash "$s" || break; done # all 18 boundary gates
```
Live-network / real-hardware tests are gated by `JARVIS_REAL_MODELS=1` and `JARVIS_REAL_CAMERA=1` env vars; default runs are deterministic and offline.
## GSD workflow
Project planning runs through the [GSD (Get Shit Done)](https://github.com/anthropics/gsd) toolchain. `.planning/` is authoritative; `docs/` carries cross-session live state.
```
/gsd-discuss-phase N → /gsd-plan-phase N → /gsd-execute-phase N → /gsd-verify-phase N
```
Each step commits atomically. Per-phase artifacts land in `.planning/phases/<N>/`. The roadmap is in `.planning/ROADMAP.md`; current state in `.planning/STATE.md`.
## Boundary gates
18 grep-based architectural-invariant linters live under `scripts/check-*.sh`. They are fast and deterministic. Run them before pushing.
If a gate fails:
1. **Read the gate's output.** It cites file:line.
2. **Read the gate script's header comment.** Tells you what it enforces and why.
3. **Don't disable the gate.** Fix the violation. If the rule has genuinely become wrong, that's a separate conversation that involves updating CLAUDE.md and possibly retiring the gate — never a one-off bypass.
The gates are listed in [`ARCHITECTURE.md > Boundary gates`](ARCHITECTURE.md#boundary-gates).
## Commit message conventions
Conventional Commits with scopes that match the work:
| Prefix | Use for |
|--------|---------|
| `feat(scope):` | New feature |
| `fix(scope):` | Bug fix |
| `chore(scope):` | Tooling, gates, infrastructure |
| `docs(scope):` | Documentation only |
| `test(scope):` | Test-only change |
| `refactor(scope):` | Internal restructuring, no behavior change |
Real examples from `git log`:
```
fix(track-b 7): BufferBroadcaster fan-out for multi-consumer audio ring
chore(gates): add check-applescript-confirmation.sh — defense-in-depth grep gate
docs(session): record P0-P2 audit-fix sweep close
test(voice p1-2): production chunkPump factory extracted + integration test
```
Scope often references the active workstream (`track-a`, `track-b 7`, `voice p1-3`, `memory d-3`) or the gate name. Body explains the **why**, not the what.
Sign commits with the `Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>` trailer when an AI agent did the work.
## Test discipline
- **TDD by default.** Failing test first, then make it pass. The audit-2026-05-04 P1 fixes followed this discipline; load-bearing tests were written before the fixes landed.
- **Mock / Stub / Fake naming taxonomy** ([CLAUDE.md §Test naming conventions](CLAUDE.md)):
- `Mock*` — records calls AND returns scripted responses
- `Stub*` — returns canned data, no recording
- `Fake*` — alternative implementation behaviorally close to production
- **Test seams stay `internal` or `@_spi(Testing) public`, never plain `public`.** Test entry points must not leak into the published ABI. Tests use `@testable import`. (P2-14 — `_forceState`, `_testFireSpeechEnd`, `_testHandle`, `_testProcessIdentifier`.)
- **No tautologies.** `XCTAssertTrue(true)` and rubber-stamp tests get removed. `scripts/check-no-leftover-stubs.sh` flags some patterns; others rely on review. (`08125a4` cleanup.)
- **Fakes at I/O boundaries only.** Don't mock middle-of-system types — fake the LLM provider, the keychain, the SQLite store, and exercise the real code in between.
## Anti-patterns to avoid
These are the rules with teeth. See [`ARCHITECTURE.md > Anti-patterns`](ARCHITECTURE.md#anti-patterns-lessons-from-audits) for the full list. Most-cited:
- **Single `cancelAndSubmit` call site.** VOICE-14, grep-gated.
- **Never log voice transcript text.** T-06-05-03.
- **Multi-consumer audio: `BufferBroadcaster.subscribe()`, never raw `RingBuffer`.** Track B-7.
- **Tool result content capped at 8 KB.** Opus 4.7 footgun.
- **Cache hints gated on prompts ≥1024 tokens.** Track A.
- **`extended-cache-ttl` requires the beta header.**
- **Tool-choice `.none` on cap-recovery turns.**
- **`turnNonce` never on the bus.** SEC-06.
- **Never `--deep` codesign.** Strips per-helper entitlements.
- **No `evaluateJavaScript` outside `WebviewBridge`.**
- **`requiresConfirmation: true` for `run_applescript` at TWO sites.** Single-point edit can't bypass.
- **Single-emission sites for memory replay events.**
## How to add a new MCP tool
1. **Decide if it's a helper or in-process.** Helpers are separately codesigned `.app` bundles under `Contents/Helpers/`; they get their own TCC identity. In-process tools (like `SearchMemoryTool`) live under `packages/MCP/Sources/MCP/InProcess/` and don't spawn a child.
2. **For a helper:**
- Create a new directory under `mcp-servers/<name>/` with a `Package.swift` (executable product) and a `main.swift` that uses `modelcontextprotocol/swift-sdk`'s `Server`, `StdioTransport`, and registers handlers via `withMethodHandler(ListTools.self)` / `withMethodHandler(CallTool.self)`.
- Codesign the helper as a separate identity. Add a build phase or update `scripts/codesign.sh`. **Never `--deep`. Never Xcode "Code Sign On Copy".**
- If the helper needs entitlements (e.g., Apple Events), give the helper its own `.entitlements` file. Don't widen the main app's.
3. **Register in `App/MCP/MCPRuntimeWiring.swift`** with `register(name:binaryURL:requiresConfirmation:)`. Confirmation-required tools (`run_applescript`) MUST set `requiresConfirmation: true` — `scripts/check-applescript-confirmation.sh` enforces this for AppleScript specifically; do similar gates for any new dangerous tool.
4. **Add tests.** Crash/restart fixture via `MockHelper`; happy-path call; sanitize round-trip if the tool returns content.
5. **Update [`ARCHITECTURE.md`](ARCHITECTURE.md) if the tool changes the system's exposed surface.**
## How to add a new HUD button + bus event
1. **Add the case to BOTH sides of the bus.**
- Swift: `packages/Bus/Sources/Bus/BusInbound.swift` (or `BusOutbound.swift`). Update the hand-written `Codable` `init(from:)` AND `encode(to:)`. Adding a case without updating both is a compile error — that's the schema-drift preventer.
- TS: `webview/packages/bus/src/`. Mirror the new case in the union + the zod schema.
2. **Bump the protocol version.** `packages/Bus/Sources/Bus/Protocol.swift` AND `webview/packages/bus/src/version.ts`. Additive changes are MINOR; breaking changes are MAJOR. `scripts/check-bus-protocol-version.sh` enforces the sync.
3. **Update the harness fixtures** under `packages/Bus/Tests/.../Fixtures/`. `scripts/check-bus-harness-parity.sh` requires the TS fixtures to round-trip through the Swift decoder.
4. **Wire the producer.** For a Swift→JS event: emit through `OutboundBatcher`. For a JS→Swift event: handle in `AppDelegate.onInbound` (or the appropriate routing site).
5. **Add the HUD UI** in `webview/packages/hud/src/`. Use the typed bus client (`window.jarvisBus.send` for inbound, the message-handler subscription for outbound).
6. **Add tests on both sides.** Vitest for the React component, XCTest for the Swift handler.
## Compaction recovery (for AI agents)
After context compaction, re-read in this order:
1. `~/.claude/CLAUDE.md` (global rules)
2. This project's [`CLAUDE.md`](CLAUDE.md)
3. [`docs/SESSION-STATE.md`](docs/SESSION-STATE.md) — current state
4. [`docs/TODO.md`](docs/TODO.md) — open items
5. [`ARCHITECTURE.md`](ARCHITECTURE.md) — system shape
6. The git log: `git log --oneline | head -25`
Then orient on the active workstream. The `.planning/audit-*` reports are historical context — use them to understand the why behind recent fix commits, not as instructions.