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>
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:
- Read the gate's output. It cites file:line.
- Read the gate script's header comment. Tells you what it enforces and why.
- 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 responsesStub*— returns canned data, no recordingFake*— alternative implementation behaviorally close to production
- Test seams stay
internalor@_spi(Testing) public, never plainpublic. 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.shflags some patterns; others rely on review. (08125a4cleanup.) - 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
cancelAndSubmitcall site. VOICE-14, grep-gated. - Never log voice transcript text. T-06-05-03.
- Multi-consumer audio:
BufferBroadcaster.subscribe(), never rawRingBuffer. 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-ttlrequires the beta header.- Tool-choice
.noneon cap-recovery turns. turnNoncenever on the bus. SEC-06.- Never
--deepcodesign. Strips per-helper entitlements. - No
evaluateJavaScriptoutsideWebviewBridge. requiresConfirmation: trueforrun_applescriptat TWO sites. Single-point edit can't bypass.- Single-emission sites for memory replay events.
How to add a new MCP tool
- Decide if it's a helper or in-process. Helpers are separately codesigned
.appbundles underContents/Helpers/; they get their own TCC identity. In-process tools (likeSearchMemoryTool) live underpackages/MCP/Sources/MCP/InProcess/and don't spawn a child. - For a helper:
- Create a new directory under
mcp-servers/<name>/with aPackage.swift(executable product) and amain.swiftthat usesmodelcontextprotocol/swift-sdk'sServer,StdioTransport, and registers handlers viawithMethodHandler(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
.entitlementsfile. Don't widen the main app's.
- Create a new directory under
- Register in
App/MCP/MCPRuntimeWiring.swiftwithregister(name:binaryURL:requiresConfirmation:). Confirmation-required tools (run_applescript) MUST setrequiresConfirmation: true—scripts/check-applescript-confirmation.shenforces this for AppleScript specifically; do similar gates for any new dangerous tool. - Add tests. Crash/restart fixture via
MockHelper; happy-path call; sanitize round-trip if the tool returns content. - Update
ARCHITECTURE.mdif the tool changes the system's exposed surface.
How to add a new HUD button + bus event
- Add the case to BOTH sides of the bus.
- Swift:
packages/Bus/Sources/Bus/BusInbound.swift(orBusOutbound.swift). Update the hand-writtenCodableinit(from:)ANDencode(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.
- Swift:
- Bump the protocol version.
packages/Bus/Sources/Bus/Protocol.swiftANDwebview/packages/bus/src/version.ts. Additive changes are MINOR; breaking changes are MAJOR.scripts/check-bus-protocol-version.shenforces the sync. - Update the harness fixtures under
packages/Bus/Tests/.../Fixtures/.scripts/check-bus-harness-parity.shrequires the TS fixtures to round-trip through the Swift decoder. - Wire the producer. For a Swift→JS event: emit through
OutboundBatcher. For a JS→Swift event: handle inAppDelegate.onInbound(or the appropriate routing site). - Add the HUD UI in
webview/packages/hud/src/. Use the typed bus client (window.jarvisBus.sendfor inbound, the message-handler subscription for outbound). - 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:
~/.claude/CLAUDE.md(global rules)- This project's
CLAUDE.md docs/SESSION-STATE.md— current statedocs/TODO.md— open itemsARCHITECTURE.md— system shape- 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.