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

8.5 KiB

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.

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:

# 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 section in the root README and CLAUDE.md > Commands. The short version:

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) 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.

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):
    • 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 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 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
  3. docs/SESSION-STATE.md — current state
  4. docs/TODO.md — open items
  5. 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.