Commit Graph
561 Commits
Author SHA1 Message Date
james.maes f6637ec39a ci(actions): fix typecheck/build order + share webview artifact across jobs (#161)
Three surgical fixes to .github/workflows/ci.yml from the first develop-push validation cycle:

1. Webview job: typecheck ran before build, so packages/hud TS imports of @jarvis/bus failed (TS2307). Reordered: install → build → typecheck → test. Build invokes scripts/build-webview.sh, producing App/Resources/webview/ directly.

2. swift-packages job: 6 packages (AgentCore/Bus/Harness/MCP/Voice/VoiceLog) failed with 'bus not mounted' because BusTests mount real WKWebView. Added needs: [webview] + download-artifact for the new webview-resources artifact.

3. app-build job: replaced inline pnpm+node+build-webview.sh with the same artifact download. One webview build serves all consumers.

Artifact renamed webview-dist → webview-resources (production-stamped App/Resources/webview/ tree). Old name was 1 day old, no external consumers.
2026-05-13 06:55:49 -05:00
james.maes a8c7f1a09b chore(deps): batch Dependabot bumps — fiber + plugin-react + jsdom + vite (#144, #145, #147, #148, #160)
Lands four SAFE-MERGE Dependabot bumps in one commit. Each was confirmed SAFE-MERGE by the H' triage on PR #155, then re-verified locally on the new lockfile baseline (vitest@3 + vite@8 + esbuild dropped) from PR #159.

- @react-three/fiber 9.6.0 → 9.6.1 (closes #148)
- @vitejs/plugin-react 5.x → 6.0.1 (closes #147)
- jsdom 25.x → 29.1.1 (closes #144)
- vite pinned to >=8.0.12 via pnpm.overrides (closes #145)

37/37 bus + 101/101 hud tests; both workspaces build; production webview bundle stamps cleanly. PR #146 (TypeScript 6) NOT included; deferred for focused evaluation.
2026-05-13 06:47:16 -05:00
james.maes 08095281fc fix(deps): close vite + esbuild CVEs via pnpm.overrides + vitest@3 bump (#159)
Closes Dependabot alerts #1 (esbuild dev-server response read) and #2 (Vite path traversal). Adds pnpm.overrides pinning vite >= 8.0.5 (drops esbuild transitive) + bumps vitest 2.1 → 3 in root + bus workspaces (vitest 2 incompat with vite 8 SSR transform). 37/37 bus + 101/101 hud tests pass; both workspaces build cleanly. Deferred to v0.2: vitest 4 major + oxc plugin migration.
2026-05-13 06:41:52 -05:00
james.maes 795c3bf5c9 feat(app): v0.1 UX surfaces — startup panels + InputMonitoring banner + PTT (#87, #88, #89, #156)
#89: new AppDelegate.startupSurfacesMode enum (.minimal/.diagnostic). Debug defaults .diagnostic; Release defaults .minimal. openStartupSurfacesIfDiagnostic() runs at tail of bootHealthTask (skipped under XCTest or when wizard is open). Status panel top-right + Dev Overlay bottom-right of NSScreen.main.visibleFrame. DevOverlay opens to Logs tab.

#88: BannerContent.inputMonitoringDenied is now nonDismissible: true. New HUDBannerCoordinator.dismiss(id:) clears a banner programmatically without writing dismissedThisLaunch. AppDelegate.reprobeInputMonitoring fires on NSApplication.didBecomeActiveNotification; rebinds the global hotkey on re-grant. Uses IOHIDCheckAccess (query-only, no prompt) per CLAUDE.md guidance. 3 new coordinator tests.

#87: WizardState.pttHotkey persisted under Jarvis.pttHotkey (independent of summon hotkey). Ships unbound per CLAUDE.md hotkey hygiene. AppDelegate.rebindPTTFromState() called from installVoice, foreground TCC re-grant, Settings Save/Clear. SettingsView extended with PTT hotkey section. Audio-graph isolation: NSEvent monitors only, no audio-graph surgery — safe alongside fix/voice-crit-v0.1. 3 new WizardState tests.

DevOverlay 14 + Voice 26 tests pass; all 17 grep boundary gates green (incl. single-writer-hudstate + no-modal-presentation).
2026-05-13 06:32:16 -05:00
james.maes bc74494ada fix(mcp): v0.1 CRIT cluster — turnID resolver wired + tool-result double-cap (#20, #21, #157)
#21 (CRIT): ToolResultPacker.pack(_:) is now a pass-through decoder; redundant inner 8 KB cap removed. MCP dispatcher's SanitizeForModel.prepareForBoundary(_, capBytes: 8192) is the sole cap site. Doc-comment rewritten honestly: replay row reflects boundary-prepared bytes (8 KB), not original helper output. Replay fidelity beyond 8 KB lifted to v0.5/M-3 once SanitizeForModel can move to a shared module without dep cycle.

#20 (CRIT): ReplayingToolResultObserver.turnIDResolver lock-protected via OSAllocatedUnfairLock<ResolverBox> (same Swift-6-async-safe pattern as InProcessConfirmationCache). New attach(turnIDResolver:) method. AgentOrchestrator.currentTurnID() actor-isolated accessor. installAgent attaches the resolver immediately after orchestrator assignment, with defensive systemLogger.warning if observer type mismatches.

Tests: ToolResultPackerTests TP2/TP5 updated to assert no [TRUNCATED] marker; OrchestratorSubmitTests OS6 reframed + new OS6b reads SQLite events table directly to assert 8 KB exact; MCPRuntimeWiringTests adds attach pre/post-attach + replay-log dual-write correlation.

AgentCore 243 + Memory 98 + Replay 34 + MCP all green; all 18 boundary gates green.
2026-05-13 06:31:46 -05:00
james.maes 4b1dd6404b fix(voice): v0.1 CRIT cluster — wake / STT / TTS / boot probes (#28, #29, #30, #31, #32, #33, #34, #36, #158)
#28 (CRIT): setCancelInFlight calls stopFeed() (preserves stream); rebuild consumer re-arms DAG against new ring. Wake-word no longer dies on audio-graph rebuild.

#29 (CRIT): sttFactory reads PerTurnSnapshot.stt.whisperKitFallback; get_self_state returns canonical snake_case backend constants. Factory signature: () -> any STTProvider → () async -> any STTProvider (existing test sites compile unchanged under Swift 6 implicit-async-closure rules).

#30 (CRIT): TTSEngineActor.synthesize emits .ttsStopped after .finished on natural completion (two-emit-site model: barge-in + natural-completion).

#31 (CRIT): VoiceTTSAdapter.tierResolver wired to read snap.tts.tier from configStore.

#32 (HIGH): Rolling-window trim in OpenWakeWordSession.runORT — mel ≤ 84, embeddings ≤ 16, nextEmbStart slides on drop.

#33 (HIGH): three new boot probes — WakeWordBootHealthProbe, STTBootHealthProbe, TTSBootHealthProbe. New WakeWordDAG.isFeedArmed + VoiceTTSAdapter.hasEngine accessors.

#34 (HIGH): vad.reset() at top of startSTTSession clears Silero VAD LSTM hidden state + wasSpeech between sessions.

#36 (HIGH): new Broadcaster.Priority.agentHud (protects stateChange/turnEnd/error); installAgent adds subscriber pumping AgentHudIntent into dormantAgentContinuation.

Voice 90 + AgentCore 242 tests pass; all 18 boundary gates green.
2026-05-13 06:31:17 -05:00
james.maes f59f5d12e3 fix(memory): v0.1 CRIT cluster — facts_vec / replay correlation / FTS5 sanitize / search_memory preamble (#12, #13, #14, #15, #16, #153)
#14 (CRIT): ContextBuilder.selfAwarePreamble names search_memory + forget_fact with explicit cue 'call search_memory FIRST. Never claim to remember without searching.'

#16 (HIGH): MemoryQueries.sanitizeFTS5Query strips non-alphanumeric, quotes tokens; HybridSearch.searchFacts calls it before binding to MATCH ?. Embedding still runs on raw query.

#12 (CRIT): MemoryStore.init accepts optional embedder; insertNewFact writes facts_vec after the facts commit. Vec failures log + leave FTS row (no rollback). installMemory shares one embedder between writer + reader.

#15 (HIGH): drop stableHash; pass sentinel 0 as sourceTurnId (option 1 per audit — smaller change surface than threading real rowid through 5 files). Schema comment updated.

#13 (CRIT): two-layer fix. Layer 1: AppDelegate owns a per-process sentinel TurnID; installAgent writes the matching turns row after beginSession; AppDelegateMemoryReplaySink attributes events to it. Layer 2: ReplayLog.flushPending retries failed batches event-by-event so one poisoned row only drops itself.

Memory 98 + Replay 34 + AgentCore 243 tests pass; all 18 boundary gates green.
2026-05-13 06:30:35 -05:00
james.maes 05411f82ff fix(vision): v0.1 cluster — T2 honesty + pendingFrame leak + TCC revoke observation (#1, #2, #3, #152)
#1 (HIGH): new hasRealT2Provider init param on AgentOrchestrator; t2AvailableForThisTurn now requires both decision.tier != .t3Cloud AND hasRealT2Provider. Production passes false (MissingT2Provider only). Pre-existing escalation test still passes true with mock T2.

#2 (MED): new FrameAttachController.releaseAfterRejectedSubmit() routes through the sole discardFrame() emission site (D-15 single-emission grep gate still green). Both handleChatSubmit and handleChatCancelAndSubmit call it on .rejected.

#3 (MED): AVCaptureSessionWasInterruptedNotification observer yields .midSessionRevoked. macOS limitation surfaced: AVCaptureSessionInterruptionReasonKey is API_UNAVAILABLE(macos), so we cannot discriminate by reason — any interruption surfaces the banner. NSApplication.didBecomeActive observer calls becameAuthorized() on non-authorized → .authorized flip.

Vision 75 + AgentCore 243 tests pass; all 18 boundary gates green.
2026-05-13 06:30:09 -05:00
james.maes a6f91e6b73 fix(bus,hud): v0.1 S1 cluster — webview didFail + menu-bar icon transition (#40, #41, #151)
#40: BridgeNavigationDelegate gains onDidFail closure (covers didFailProvisionalNavigation + didFail). WebviewBridge.failHandshake(reason:) transitions to new terminal .loadFailed state, cancels pending 2s timeout, fires existing alertPresenter, idempotent. AppDelegate.handleWebviewLoadFailure dual-surfaces: failHandshake + critical non-dismissible banner (defense-in-depth across test seams). WebviewBootHealthProbe gets .loadFailed case.

#41: HudStateCoordinator emit closure now ALSO calls menuBarController.transition(to:) in addition to bridge.send(.hudState(...)). Doc-comment correctly notes this is not a HUD-08 single-writer violation — transition writes currentState, not BusOutbound.hudState. scripts/check-single-writer-hudstate.sh still green.

Tests: 4 new Handshake cases (load-failure-from-sentHello with alert-not-duplicated, from-idle, idempotency, post-armed no-op), BridgeNavigationFailureTests end-to-end + per-arm, MenuBarIconStateTransitionTests drives dormantAgentContinuation and asserts .thinking → .speaking → .idle. 62/62 Bus pass; all 18 boundary gates green.
2026-05-13 06:29:34 -05:00
james.maes b643159735 chore: PR + dependency triage report — 2026-05-12 (#155)
Triage report for the 5 Dependabot PRs (4 SAFE-MERGE / 1 NEEDS-INVESTIGATION), the 2 moderate CVEs (vite + esbuild path traversal — root cause is vitest@2 → vite@5.4.21 → esbuild@0.21.5 transitive chain; existing PR #145 alone does NOT close them; surgical fix is pnpm.overrides), and the 6 stashes (5 DROP + 1 PRESERVE-AS-BRANCH for Voice Log v1.1 + 1 POP-TO-LANE-G). Methodology: each Dependabot PR was tested in an isolated tmp worktree; no main repo HEAD mutations.
2026-05-13 06:29:04 -05:00
james.maes 223310aa27 ci(actions): full pipeline — build / test / scan / publish (#154)
Adds five GitHub Actions workflows + CODEOWNERS + BUILDING.md CI section.

- ci.yml: boundary gates (Ubuntu), webview pnpm test+build (Ubuntu), 14-package SPM matrix on macos-15, app target via xcodebuild, optional self-hosted real-hardware lane, semver tag-publish on push to develop/rc/release/main.
- codeql.yml: Swift + JS/TS with security-extended queries, weekly cron.
- semgrep.yml: p/default + p/swift + p/typescript + p/secrets, SARIF upload, optional SEMGREP_APP_TOKEN.
- dependency-review.yml: blocks severity:high on PRs touching manifests/lockfiles.
- release.yml: tag-triggered GitHub Release with auto-generated changelog.

Branch + tag conventions: feature/fix branches run checks only; develop publishes v0.X.Y-dev.<run_number>; rc/release publishes v0.X.Y-rc.N; main publishes v0.X.Y. Conventional-Commits-aware bumping (feat → minor, fix → patch, feat!/BREAKING → major). Seed version v0.1.0.
2026-05-13 06:28:10 -05:00
james.maes 5bb1e778b5 fix(hud): chat panel auto-scroll on streaming append — B-06 tactical (#51, #150)
Adds a useLayoutEffect on ChatPanel keyed to total content length (captures streaming token append where chatEvents.length is constant). 24px bottom-pin hysteresis; user scroll-up breaks the pin; auto-re-arms on scroll-back-to-bottom. scrollTo feature-detected with scrollTop fallback for JSDOM. 4 vitest cases assert decision logic.
2026-05-13 06:27:42 -05:00
james.maes ba42b6918a docs: BUILDING.md + finalize README MIT license section (#149)
Extracts the long-form build reference (per-package SPM tests, app-target xcodebuild wrapper, webview workflow, 18 boundary-gate linters, real-hardware env-flag tests) into BUILDING.md so the README can stay first-screen weight. Finalizes the README License section against today's MIT decision (e1fcdc5), removing the stale TODO block.
2026-05-12 15:57:48 -05:00
james.maesandClaude Opus 4.7 71b93898ac docs(handoff): end-of-day 2026-05-12 — round 2/3/4 + audits + tracking migration + roadmap + branding
Closes the session that took Jarvis from "wired-but-dead-everywhere"
to fully-mapped + branded + license-clear + audit-tracked.

Highlights:
- Round 2/3/4 strict-mode boot health shipped (probes → status panel →
  severity model + non-dismissible banners + agent self-awareness preamble +
  red menu-bar icon). Toby fix verified end-to-end mid-dogfood.
- facts.source_turn_id FK dropped (a5999e2) — facts persist now.
- Xcode 26 ProcessInfoPlistFile build race fixed (1557302).
- Dev Overlay shipped with 4 tabs + live log streaming via LogBroadcaster.
- 5-agent audit pass at .planning/audit-2026-05-12/ — ~37 findings.
- 141 GH issues filed across audit findings + carry-forwards + M-0..M-7
  migration epic (54 sub-issues) + comment-cleanup + v1.1 backlog.
- TODO.md deprecated. GitHub Issues canonical.
- Code-commenting style guide locked + applied to AppDelegate as the P0
  exemplar (281b909).
- Roadmap at .planning/ROADMAP-2026-05-12.md with 6 milestones bucketing
  all 141 issues.
- MIT license confirmed; LICENSE/SECURITY/CHANGELOG/issue templates/PR
  template/dependabot all in place.
- README rewritten with mermaid diagrams + acknowledgments + culture
  section. Repo metadata + 12 topics + 3 pinned issues set.

Outstanding: PR #149 (BUILDING.md + README MIT cleanup) draft pending
review. Repo still PRIVATE — public-flip decision is the user's call.

Next session: read this handoff + .planning/ROADMAP-2026-05-12.md, merge
PR #149, decide visibility, then start v0.1 fan-out sprint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 15:45:26 -05:00
james.maesandClaude Opus 4.7 0cc94c637a docs(branding): assets/branding/ + restore MIT license section
- Add assets/branding/README.md with shot list for the five high-impact
  brand assets (social preview, logo, hero shot, app icon, banner) and
  GitHub upload instructions for each.
- Add assets/branding/STYLE.md documenting the colour palette (cyan
  family + HUD navy/black + warning/critical), typography (Eurostile /
  Saira / Orbitron family for display, SF Pro for UI), and tone of
  voice ("terse, technical, dryly opinionated, slightly sardonic").
- Drop five .todo placeholder files so the directory tree shows intent
  without faking assets.
- Restore the README License section to a clean MIT pointer (a
  concurrent agent had reverted it to "decision still open"; that
  decision is now made and lives at .planning/decisions/license.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 15:09:14 -05:00
james.maesandClaude Opus 4.7 281b9093e7 docs(appdelegate): apply commenting style guide (P0 of #64)
Rewrites App/AppDelegate.swift docstrings + inline comments to the
.planning/code-commenting-guide.md standard. Comments + docstrings
only — zero code behavior changes.

Closes the P0 entry in the migration table (#65 under epic #64).

Highlights:
- File header: 5-line opener + "See also" + audit-trail suffix.
- Class docstring: install-order ASCII DAG, threading model,
  "Before you edit" block listing the five sharp-edged invariants
  (single-writer HudState, locked install order, shared in-process
  tool registry, JIT entitlement, no-modal presentation).
- Property docstrings reorganized under role-named MARKs
  (Test-injectable seams / AppKit surfaces / HUD state coordinator
  + bus producer continuations / Orch→replay seam + MCP runtime /
  Boot health / Voice / Memory / Vision / Agent). Every property
  now leads with WHY; audit references (Plan IDs, B-/D-/CR- numbers,
  dated audits) are parenthetical suffixes.
- Public + key private method docstrings rewritten:
  applicationWillFinishLaunching gets the 16-step bootstrap + the
  hard-block branches called out; applicationWillTerminate explains
  why each cancellation is explicit; runBootHealth + register-
  BootHealthProbes list every probe + its role; installVoice /
  installMemory / installAgent / installVision /
  installSelfKnowledgeTools open with the subsystem's
  responsibility + fail-soft-vs-hard policy + step sequence.
- The motivating bad example from the guide
  (`inProcessToolRegistry` Plan-ID-lede comment) is rewritten
  exactly as the guide prescribes.
- Inline comments still carrying Plan-ID ledes ("Phase E 2026-05-03
  audit fix:", "Track B-4 (...):") rewritten to lead with WHY/WHAT
  with audit trail demoted to parentheticals at end.

Boundary gates:
- 18/18 green (full sweep: app-builds, bus-harness-parity,
  bus-protocol-version, corpus-secrets, embedding-dim-literal,
  install-order, no-evaluate-javascript, no-leftover-stubs,
  no-modal-presentation, no-null-voice-adapters,
  orchestrator-events-single-consumer,
  presence-bus-no-tts-orchestrator, presence-vision-isolation,
  single-memory-mutated-emit, single-memory-used-emit,
  single-writer-hudstate, vision-isolation,
  applescript-confirmation).
- check-install-order verified literal Task spawn lines + the
  `await self?.agentInstallTask?.value` invariant are intact.
- check-single-writer-hudstate verified AppDelegate still only
  references `.hudState(_:)` inside the allowlisted wiring closure.

Surfaced during review:
- None — only comments touched. The audit-2026-05-04 P3-16 split
  (AppDelegate+Voice/+Vision/+Memory/+Agent) remains outstanding
  but is out of scope for this P0.

Line delta: +965 / -740, net +225. Net growth is from the install-
order DAG ASCII art + the "Before you edit" block + expanded
six-step method docstrings.

Refs: #64, closes #65.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 15:08:33 -05:00
james.maesandClaude Opus 4.7 b2710eee48 docs(readme): branding pass — badges, MIT license, acknowledgments
- Add status badges (license, last commit, issues, platform, Swift) under
  the tagline so the GitHub card carries signal at a glance.
- Replace the "personal use only / no license granted" footer (which
  contradicted the actual MIT LICENSE file committed at 9b62db5) with a
  proper MIT pointer + link to the decision rationale.
- Reframe the scope paragraph: still single-user single-machine, but
  acknowledge the repo is open source and useful artifacts (agent loop,
  Bus, MCP, voice, HUD, boundary gates) are here for the lifting.
- Add Acknowledgments section calling out the major OSS dependencies
  (Anthropic, Ollama, MCP SDK, openWakeWord, Silero, ONNX Runtime,
  argmax-oss-swift, mlx-audio-swift, sqlite-vec, R3F, swift-log,
  swift-nio, Yams) so credit and discoverability are explicit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 15:05:33 -05:00
james.maesandClaude Opus 4.7 e1fcdc520f docs(license): MIT decision + rationale
Confirms MIT (already at 9b62db5). Documents what was considered (Apache
2.0, MPL-2.0, AGPL-3.0) and why MIT fits a solo personal project with
permissive intent. Lists revisit triggers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 15:02:49 -05:00
james.maesandClaude Opus 4.7 c4c32f7aae docs(readme): refresh — diagrams + architecture + culture + roadmap
Audit-and-rewrite of root README against current truth (2026-05-12).
The previous README was accurate but list-shaped and unillustrated;
the rewrite reorients around what a new reader (human or AI agent)
needs in 60 seconds.

New / changed sections:
- Tagline + two-paragraph intro (what / who / why)
- Status banner pointing at GitHub Issues + Milestones (canonical)
- Screenshots placeholder + shot list under assets/screenshots/
- "Architecture at a glance" with a top-level mermaid flowchart
  (Swift host + LLM providers + WKWebView, with 14 nodes)
- Subsystems table — eight first-class + supporting packages
- "Install order" section with a mermaid DAG of the AppDelegate
  install sequence (vision → agent → voice, plus parallel
  memory/MCP/replay), matching check-install-order.sh
- "Agent turn lifecycle" mermaid showing the orchestrator → single
  broadcaster fanout, with the strict-mode degradation preamble
  inject point called out
- "Boot-health probes" mermaid for the eight NOT FAKED probes →
  rollup → menu-bar icon / banner / agent preamble
- "Project culture" section codifying the in-code-docs philosophy,
  NOT FAKED probes, strict-mode escalation, 18 boundary gates, the
  three-name test-double taxonomy, audit-and-stabilize cadence,
  and the GitHub-Issues-is-canonical policy
- "Roadmap" reorganized around the v0.1 → v1.1 milestones from
  GitHub Milestones (user-outcome-focused, not subsystem-focused)
- Build & run cleaned up; boundary-gates one-liner; opt-in
  real-hardware test commands kept

Also adds assets/screenshots/README.md with a prioritized shot
list — eight screenshots in priority order — and a note on why
diagrams stay as inline mermaid (GitHub-native, no re-export).

Removed: the older "Status" subsystem-state table, which had
become a mix of v0.12.0 milestone audit terms (Track A/B/C/D,
B-8) that don't map cleanly to the new GitHub Issues taxonomy.
Status now points at GitHub Issues + Milestones as the source
of truth.

Gates: 17/18 pass. check-app-builds.sh fails on the in-flight
AppDelegate.swift rewrite (unstaged, owned by a parallel agent)
— unrelated to this markdown-only change. Confirmed only
README.md + assets/screenshots/README.md are staged here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 15:00:21 -05:00
james.maes 4e6911472d docs(changelog): add CHANGELOG.md with milestone history
Keep a Changelog format. [Unreleased] tracks the May 2026 audit-and-
stabilize work (boot-health probes, status panel, Dev Overlay Logs tab,
memory FK fix). Milestone history groups v0.10.0 → v0.12.0 by phase
rather than commit; per-commit detail stays in git log.
2026-05-12 14:56:56 -05:00
james.maes e0c5bcba69 docs(github): issue + PR templates + dependabot config
Four issue templates (bug / enhancement / audit-finding / epic) pre-fill
existing labels (severity:*, area:*, audit-finding, epic). PR template
references the 18 boundary gates + comment style guide. Dependabot covers
swift, npm (/webview), and github-actions on a weekly cadence.
2026-05-12 14:56:49 -05:00
james.maes 9b62db5c33 docs(github): add LICENSE + SECURITY.md
MIT license (permissive, attribution required); SECURITY.md documents
the personal-use threat model and private reporting channels (email +
GitHub Security Advisories).
2026-05-12 14:56:42 -05:00
james.maes 61e06fb2dc docs(roadmap): user-outcome-focused milestone roadmap
All 142 open issues triaged into 6 GitHub milestones organized by
user-facing capability rather than technical area:

- v0.1 — It actually works (28 issues): voice, listening, seeing,
  facts, debugging dialogs, basic UI. Tactical patches for CRITICAL
  audit findings so user has working Jarvis NOW.
- v0.2 — Observable + stable (23): MED/LOW audit punch-list + PTT/TCC.
- v0.5 — API M-0..M-3 (31): Self/Settings/Diagnostics surfaces; B-08.
- v0.8 — API M-4..M-7 (36): Memory/Vision/Voice/Turn; B-03/B-04/B-05/B-06.
- v1.0 — Shipping (13): commenting, doc reconciliation.
- v1.1 — Backlog (11): Orpheus, WhisperKit, Voice Log, presence.

ROADMAP-2026-05-12.md supersedes the build-phase ROADMAP.md from
2026-04-22 (kept for history; describes shipped phases). CLAUDE.md
gains a Roadmap section pointing to the new doc + milestones URL.

Also lands the 5 audit-2026-05-12 reports referenced by the roadmap
(bus-hud, mcp-agent, memory, vision, voice).
2026-05-12 14:52:25 -05:00
james.maes 6ea2852af1 feat(devoverlay,logging): live log stream — every swift-log line into the overlay
Slice 3 of dev-overlay-end-to-end. Adds a fourth DevOverlay tab that
tails every log record from every Jarvis subsystem in real time —
agent, voice, vision, memory, MCP, bus, replay, system — without
polling files or relying on Console.app.

How it works:

1. `LogBroadcaster` (new) — process-wide actor with a 500-line ring
   buffer + subscriber set. Borrowed shape from
   OrchestratorEventBroadcaster so the project's three broadcasters
   share a mental model. New subscribers replay the buffer in order,
   then go live.

2. `BroadcastLogHandler` (new) — swift-log LogHandler conformance.
   Formats each log call into a `LogLine` and schedules an unstructured
   `Task { await LogBroadcaster.shared.publish(...) }` so the logging
   caller never blocks on actor isolation. Synchronous handler contract
   preserved; ordering is best-effort under heavy load.

3. `LoggingBootstrap.make(label:)` now returns
   `MultiplexLogHandler([file, os, broadcast])` — the new handler is
   purely additive. File rotation + os.Logger streaming unchanged.

4. `DevOverlayLogsView` (new) — fourth tab in the overlay. Subscribes
   to `LogBroadcaster.shared` on appear, caps at 1000 lines, renders
   monospaced. Per-channel filter (Menu w/ checkboxes), per-level
   filter (debug → critical), substring search, Pause/Resume button
   (Cmd+P), auto-scroll-to-bottom while not paused. Color-coded by
   level (warn=orange, error/critical=red).

Tests:
- 5 new LogBroadcasterTests covering publish/cap/replay/live/handler.
- swift test --package-path packages/Logging → 22/22 pass.
- swift test --package-path packages/DevOverlay → 14/14 pass.
- bash scripts/check-app-builds.sh → PASS.
- 18/18 boundary gates green.

Redaction note: file handler still routes through Redact.apply for
disk writes; the broadcaster sees raw messages because the DevOverlay
is a developer-only in-process surface. If this ever becomes
user-facing, route through Redact before publishing.
2026-05-12 14:27:57 -05:00
james.maesandClaude Opus 4.7 2a872fc597 docs(session-state): point to GitHub Issues; preserve handoff narrative
Adds a note at the top declaring GitHub Issues as the live tracker.
Replaces the Pending Work bullet list with a crosswalk to issue numbers.
Narrative paragraphs (session-handoff history) preserved unchanged.

Refs #4..#63

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:17:38 -05:00
james.maesandClaude Opus 4.7 60f179cf39 docs(todo): deprecate TODO.md — issues moved to GitHub
All open work has migrated to GitHub Issues. This file is replaced by a
crosswalk from former sections to issue numbers. Historical content is
preserved in git history (see docs/TODO.md at commit 8d94c1c or earlier).

Refs #4..#63

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:13:07 -05:00
james.maesandClaude Opus 4.7 4f4a5581c1 docs(claude.md): declare GitHub Issues as canonical tracker
Adds a new "Issue tracking" section near the top declaring
github.com/KofTwentyTwo/Jarvis/issues as the canonical tracker for all
bugs, audit findings, and feature work going forward. docs/TODO.md is
retained for historical reference only.

Refs #4..#63 (migration epics + audit findings + carry-forward bugs
filed 2026-05-12).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 14:12:06 -05:00
james.maes 50c2783a8c feat(devoverlay): wire DevSnapshotEmitter end-to-end + add tabbed view
The DevOverlay panel has been silently rendering DevSnapshot.initial since
Phase 4 because three independent breaks left the data path open-circuit
(see .planning/audit-2026-05-12/devoverlay-diagnosis.md for the full trace).

Slice 2 closes all three:

1. AppDelegate constructs `DevSnapshotEmitter` in `installAgent` and
   stores it on the delegate. Provider/model is set from configStore
   so the header row shows the active provider rather than a blank.

2. The `.devOverlay` broadcaster subscriber that previously drained to
   `/dev/null` now calls `emitter.apply(event)` for every event. This
   produces real DevSnapshots that flow through the bounded output
   channel (capacity 32, .dropOldest — AGENT-10 observational seam).

3. `DevOverlayWindow` now accepts the live emitter + BootHealthOrchestrator
   + a HudState reader closure. It instantiates its own DevOverlayBridge
   and attaches the bridge to `emitter.output` so viewModel.apply is
   actually called as snapshots arrive.

Also reshapes the SwiftUI surface as a TabView with three observational
panes (Agent, Tools, Health). The Health pane is read-only — the Status…
menu remains the place to trigger re-probes. The Logs pane lands in
Slice 3 (live log stream).

ViewModel extends with `bootHealth` + `hudStateLabel` slices, each with
its own single-writer mutator and `private(set)` enforcement.

Verification:
- swift test --package-path packages/DevOverlay → 14/14 pass
- swift test --package-path packages/AgentCore --filter DevSnapshotEmitterTests → 9/9 pass
- bash scripts/check-app-builds.sh → PASS
- 18/18 boundary gates green
2026-05-12 14:11:19 -05:00
james.maes 8d94c1c398 docs(devoverlay): diagnose why the panel renders nothing
Three independent breaks identified:
1. DevSnapshotEmitter exists but is never instantiated in App/AppDelegate.
2. The .devOverlay broadcaster subscriber drains every event to /dev/null
   (AppDelegate.swift:1808-1813 — wired-but-dead per Phase 9 audit pattern).
3. DevOverlayWindow constructs a fresh viewModel(.initial) with no
   DevOverlayBridge attached, so viewModel.apply(_:) is never called.

Slice 1 of the dev-overlay-end-to-end work. Slices 2 (wire it up) and 3
(live log stream) follow.
2026-05-12 13:54:07 -05:00
james.maesandClaude Opus 4.7 a5999e2dde fix(memory): drop broken FK on facts.source_turn_id — closes Toby-fact persistence
Root cause of "facts never persist" surfaced during 2026-05-12 dogfood:

  ERROR [memory.orchestrator] memory applyOp failed: applyOpFailed(
    underlying: Replay.SQLiteError.stepFailed(
      code: 19, message: "FOREIGN KEY constraint failed"))

The schema declared:
  source_turn_id INTEGER REFERENCES turns(id)

But the writer at MemoryExtractionOrchestrator.swift:125 inserts:
  let turnIdInt: Int64 = stableHash(job.turnId.rawValue)

`TurnID` is a UUID-string wrapper; the writer FNV-1a-hashes it to an
Int64 for the integer column. The hash space and `turns.id`
(AUTOINCREMENT) are disjoint number systems by design, so every
INSERT into `facts` violated the FK and SQLite rolled back the row.

The BootHealth probe reports the symptom honestly ("26 turns, 0/0
facts") but couldn't flag this as a failure — 0 facts is the
legitimate empty-history case. The bug stayed invisible until the
user asked "is Toby a fact in the database?" and search returned
nothing despite asynchronous extraction running.

This bug was latent for the entire project history — the original
RESEARCH §1 schema specified the FK, but the writer was never wired
to use real `turns.id` rowids. Reintroducing the FK without first
fixing the writer (forward through MemoryStore.appendTurn returning
the rowid, then plumbed back to MemoryExtractionCoordinator) would
just re-break persistence.

Fix: drop the REFERENCES on `source_turn_id`. The column is now
documented as a "correlation tag", not a hard foreign key. The
`superseded_by REFERENCES facts(id)` constraint stays — that one
IS valid because supersede chains use `priorFact.id` (a real
AUTOINCREMENT rowid).

Existing DBs: `CREATE TABLE IF NOT EXISTS` won't ALTER an existing
table; the user must delete `~/Library/Application Support/Jarvis/
jarvis.db*` and relaunch. Their facts table has 0 rows so nothing
of value is lost. A proper migration framework (PRAGMA user_version)
is deferred to M-1+.

Follow-up (M-1+): plumb the real `turns.id` rowid through
`MemoryStore.appendTurn` → orchestrator state → coordinator →
applyOp so `source_turn_id` becomes a real FK again. Requires
changes across 4 files; not in scope for this hotfix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 12:07:44 -05:00
james.maesandClaude Opus 4.7 b76327364d docs(style): code-commenting style guide — felt orientation in 30s
Research pass produced a 583-line normative style guide capturing
the project's commitment to in-code documentation over external
markdown. The adversarial benchmark: a new developer reading any
one file must understand WHY every non-obvious line exists,
without opening a Plan ledger or a PR description.

Synthesized from:
  * Apple's Swift API design guidelines + DocC
  * Code Complete / Clean Code / Knuth literate programming
  * Reference Swift OSS (swift-nio, swift-log, swift-async-algorithms)
  * Survey of existing Jarvis files to characterize current state

Coverage:
  1. Five principles (lead with what+why, audit IDs as footnotes,
     felt orientation, no comment rot, no fluff).
  2. Five comment shapes (file header, type docstring, method
     docstring, inline //, MARK: -) with good/bad/rewritten examples.
  3. ASCII diagrams for orchestration / state machines / multi-actor
     flows — when they pay for themselves and when they don't.
  4. DocC cross-references for jump-to-definition from comments.
  5. Concurrency / actor / single-writer invariants worth always
     calling out.
  6. "Before you edit" invariants block — explicit anti-patterns
     per load-bearing file.
  7. What NOT to comment.
  8. Comment-rot prevention.
  9. Migration plan — leads with AppDelegate (the install-chain
     cliff), then load-bearing actors, then leaf packages
     opportunistically. One file = one atomic commit.

Anti-goals (explicit): not building ARCHITECTURE.md, ONBOARDING.md,
or a wiki. The code teaches itself. The guide itself is meta
(documentation about how to comment) and is allowed at
.planning/code-commenting-guide.md as a planning artifact, not
runtime documentation.

Follow-ups (next sessions):
  * Add TL;DR section to CLAUDE.md so future agents inherit the
    convention automatically.
  * Apply the guide to AppDelegate.swift as the first atomic
    rewrite commit — proves the convention on the steepest cliff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 11:57:02 -05:00
james.maesandClaude Opus 4.7 1557302e63 fix(build): force ProcessInfoPlistFile before pre-codesign
Symptom: app hard-blocks at boot with "JarvisEntitlementsVerified
missing/false" after every incremental build, even though the build
log shows pre-codesign writing YES into the bundle Info.plist.

Root cause: Xcode 26's new build system schedules the built-in
ProcessInfoPlistFile task (which copies App/Info.plist into
Contents/Info.plist) independently from postBuildScripts. On
incremental builds with no Swift compile work, ProcessInfoPlistFile
ran AFTER pre-codesign, codesign, post-codesign, AND
verify-codesign-settings — silently clobbering the bundle Info.plist
with the source one. Source App/Info.plist carries
<key>JarvisEntitlementsVerified</key><false/> by design as the
static-state default; pre-codesign is supposed to flip it to <true/>
inside the bundle. With the new ordering, the bundle ended every
build at <false/> (and the signature was also stale relative to
that plist, though codesign --verify happened to still pass).

Fix: declare $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH) as an inputFile
of the pre-codesign phase, plus a per-build stamp file as its
outputFile. This forces Xcode to make ProcessInfoPlistFile a
prerequisite of pre-codesign, and chains codesign (also gaining
the stamp as inputFile) deterministically behind it. Xcode 26's
"mutable output but no other virtual output node" check rejects
naming the plist as both input and output of the same phase, so
the separate stamp file is required.

Verified: clean and incremental builds both produce a bundle
with JarvisEntitlementsVerified=true and a valid signature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 11:50:56 -05:00
james.maesandClaude Opus 4.7 beb9d51aa5 test(agent-core): update qwen2.5-coder literal to q8_0 variant
Two test assertions still expected `"qwen2.5-coder:32b"` after commit
8d0a959 flipped ModelID.qwen25coder32b to
`"qwen2.5-coder:32b-instruct-q8_0"`. Same shape of fix as commit
5718bdd (qwen36 rename). The other "qwen2.5-coder:32b" occurrences
in NDJSONDecoderTests and OllamaImageBlockEncodingTests are
fixture JSON strings used as decoder input — not asserted against,
so they don't break.

After:
  swift test --package-path packages/AgentCore --filter LLMEventTests
  → 10/10 pass
  swift test --package-path packages/AgentCore --filter OllamaProviderTests
  → 38/38 pass

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 11:14:07 -05:00
james.maesandClaude Opus 4.7 740bed4cbf fix(round-4): tag-agnostic ollama probe + mcp probe counts in-process tools
Two false-positive degraded reports surfaced during the second
Round 4 dogfood run:

1. Ollama probe was exact-string matching against required model
   names. `requiredModels` had `"nomic-embed-text"` (no tag), but
   `/api/tags` returns `"nomic-embed-text:latest"` (with default
   tag) — so the probe reported "missing models: nomic-embed-text"
   even after `ollama pull` succeeded. Fix: when a required name
   has no `:tag`, accept any tag (`name == required OR
   name.hasPrefix(required + ":")`). Required names that DO
   include a tag (e.g., `qwen3.6:latest`) keep their strict match.

2. MCP probe was reading ONLY `runtime.client.registeredToolNames()`
   — the stdio side (3 helpers: get_clipboard, get_time,
   run_applescript). The other 4+ in-process tools (self-knowledge
   + memory) live in `InProcessToolRegistry` and are routed
   through `InProcessAwareToolDispatcher` (commit 1b7fb81's B-01b
   fix). Probe now reads both registries, unions the names, and
   reports the combined count. Evidence line breaks out stdio vs
   in-process counts so the operator can see the split.

Verified: app build clean, app target compiles. Re-launch should
show ollama=ok (all 3 models present) and mcp=ok (8 tools after
self-knowledge tools register at install step 15).

The Toby fix verification mid-dogfood: the model received the
degradation summary in its system prompt and truthfully reported
"my memory subsystem is currently degraded (nomic-embed-text is
missing)" instead of yesterday's fake "Got it" promise. Round 4
slice 3 confirmed working end-to-end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 10:29:58 -05:00
james.maesandClaude Opus 4.7 a9531e65e3 feat(round-4): menu-bar icon turns red when subsystems are degraded
Slice 4 of Round 4 — the visible polish. With Slices 1–3 the agent
preamble + non-dismissible banner cover the "agent must know +
user must see" loop, but a closed HUD with no banner visible can
still hide the warning. This slice paints the menu-bar icon red so
the warning is glanceable from anywhere on the screen.

Option A from the slice plan (single-writer-safe):

- `MenuBarIconController.applyHealth(_ health: OverallHealth)` paints
  `button.contentTintColor = NSColor.systemRed` for `.loud` and
  `.critical`, clears the tint for `.ok` and `.soft`. Template images
  recolor automatically with `contentTintColor` so no second icon
  asset is needed.
- `currentHealth: OverallHealth?` exposed read-only for tests.
- `AppDelegate.runBootHealth` calls `menuBarController?.applyHealth(
  snapshot.overallHealth)` after computing the degradation summary,
  so the icon updates on every probe sweep (cold boot + Status panel
  re-probe).

Single-writer invariant for HudState (HUD-08) is intact:
`contentTintColor` lives on `NSStatusItemButton`, not in HudState.
`scripts/check-single-writer-hudstate.sh` is green.

1 new test in MenuBarIconControllerTests:
  * test_applyHealthTintsRedForLoudAndCritical — exercises all four
    OverallHealth cases and asserts contentTintColor toggles correctly.

Boundary gates 18/18 green; App target compiles cleanly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:59:48 -05:00
james.maesandClaude Opus 4.7 23b4ca7712 feat(round-4): agent system prompt knows when subsystems are degraded
Slice 3 of Round 4 — the Toby-the-dog fix. With Slice 1 the probes
report severity and with Slice 2 banners route by severity, but the
critical behavioral change is here: the agent's system prompt itself
must carry the degradation truth so the model can't confidently lie
about features that don't work.

The Toby case in detail: user said "remember my dog Toby is a golden
retriever". The Ollama probe correctly reported missing nomic-embed-text
(degraded). The memory extractor tried to embed Toby, the embed call
failed, no row was inserted in `facts`. But the agent's system prompt
said "you are Jarvis with memory" and the model answered "Got it —
Toby." A lie, dressed up as competence.

Implementation:

- `AgentOrchestrator` gains a private `var degradationSummary: String?`
  + a public `setDegradationSummary(_:)` actor-isolated setter. Mutable
  because installAgent runs BEFORE runBootHealth in the boot order;
  the orchestrator is constructed with `nil` and AppDelegate pushes
  the summary in post-construction. Re-probes from the Status panel
  update the value mid-session.
- `runTurn` prepends the summary BEFORE `composedSystem` (which already
  includes the UntrustedWrapper nonce + locked self-aware preamble),
  so the warning lands at the very top of the system message — outranks
  the preamble, outranks presence/memory enrichment.
- `AppDelegate.runBootHealth` now calls
  `orchestrator.setDegradationSummary(buildDegradationSummary(snapshot:))`
  after every probe sweep. The builder produces text like:

    CRITICAL — SUBSYSTEMS DEGRADED:
    - memory: failed (vec0 missing) — DO NOT promise to use memory
    - ollama: degraded — missing models: nomic-embed-text
    Be honest. Do not claim to use tools that are unavailable. Do not
    pretend memory works if it doesn't.

  When every subsystem is `.ok`, the function returns `nil` and the
  orchestrator's behavior is identical to pre-Round-4.

- Cache-hints invariant preserved: the locked preamble is ~700 chars,
  the worst-case degradation summary is <500 chars; sum stays well
  under the 4096-char Anthropic cache-eligibility boundary so adding
  the summary doesn't unintentionally flip `cache_control` emission.
  CacheHintsEligibilityTests/preambleDoesNotEnableCacheUnintentionally
  remains green.

4 new tests in AgentOrchestratorDegradationPreambleTests (all pass):
  * DP1: initial summary threaded into first turn's system message
  * DP2: nil summary leaves prompt unchanged (no "CRITICAL" / "DO NOT")
  * DP3: setDegradationSummary updates the next turn's prompt
  * DP4: setting summary back to nil clears it from the next turn

Boundary gates 18/18 green. `swift test --package-path packages/AgentCore`
runs 242/240 (4 new minus 2 pre-existing unrelated failures from
qwen2.5-coder model-ID literal drift, present before Round 4).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:47:58 -05:00
james.maesandClaude Opus 4.7 79955b67b7 feat(round-4): non-dismissible banners for critical subsystem failures
Slice 2 of Round 4. With Slice 1 the probe layer now reports severity;
this slice routes severity to the banner system so the user CANNOT
silently dismiss a "your memory is dead, Jarvis will lie to you" warning.

- `BannerContent.nonDismissible: Bool` (default false).
- `HUDBanner` hides the dismiss X when `content.nonDismissible == true`.
- `HUDBannerCoordinator.dismissCurrent()` no-ops on non-dismissible
  banners; the banner stays on screen until programmatically cleared
  (`clear()` on app quit, or a re-probe finding the subsystem healthy
  enqueues a different banner that pre-empts it).
- Two new presets:
  * `bootHealthCritical(subsystem:reason:)` — priority 1, non-dismissible.
    Title format: "Memory: critical failure". Body promises that the
    agent will be warned not to rely on this subsystem (Slice 3).
  * `bootHealthLoud(subsystem:reason:)` — priority 3, dismissible.
- Existing `bootHealthFailed(...)` preset kept as backwards-compat
  fallback (no current call sites after this slice).
- `AppDelegate.enqueueFailedBootHealthBanners` rewritten to iterate
  every non-ok subsystem and route by severity:
    .critical → bootHealthCritical + `[system] CRITICAL` log line
    .loud     → bootHealthLoud
    .soft     → no banner (Status panel only)
  Previously only `.failed` cases enqueued banners, which silently
  dropped `.degraded` (the Toby case) entirely.

Two new tests in HUDBannerCoordinatorTests cover the new behavior:
  * test_nonDismissibleBannerIgnoresDismiss
  * test_criticalBannerPreemptsLowerPriorityCurrent
(Note: `xcodebuild test -only-testing JarvisAppTests` is pre-existing-
broken on this checkout due to onnxruntime static-link duplication —
confirmed by `git stash` before this slice. The test code compiles
cleanly via the regular App build; SPM-target tests for the
AgentCore-side severity model are green.)

Boundary gates 18/18 green. `swift test --package-path packages/AgentCore
--filter BootHealthOrchestratorTests` → 13/13.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:36:43 -05:00
james.maesandClaude Opus 4.7 cfd469f72b feat(round-4): probe severity model — ok/soft/loud/critical
Round 4 of boot-health work. The Toby-the-dog incident — Jarvis said "Got
it" while the embedder model was missing and the fact never persisted —
proved that Round 2's probe banner was too quiet. Probes were already
reporting `.degraded` correctly; what was missing was a way for the App
layer to route a critical failure differently from a soft one (e.g.,
non-dismissible banner for memory off vs. silent Status row for camera
TCC not yet requested).

This slice introduces a severity axis without changing behavior:

- `FailureSeverity { critical, loud, soft }` on `ProbeStatus`'s non-ok
  cases (degraded/failed/unknown). `.ok` carries no severity.
- `ProbeStatus.severity` accessor (nil for `.ok`).
- `BootHealthSnapshot.overallHealth: OverallHealth { ok, soft, loud,
  critical }` — worst severity wins; `.ok` only if every subsystem ok.
- Each concrete probe annotates its failure modes:
  * Memory: failed → .critical (agent grounding; the Toby case)
  * Anthropic: missing/empty key → .critical (agent can't run)
  * Ollama: missing models → .loud (fact extraction broken, chat fine)
  * Voice: mic denied → .loud; dormant install → .soft
  * Vision: TCC denied → .soft (vision is optional)
  * MCP: 0 tools → .critical; <minimum → .loud
  * Replay: init failed → .critical; missing file → .loud
  * Webview: mismatched/timedOut → .critical; sentHello → .soft
- DormantSubsystemProbe gains `severity` parameter (default .soft);
  AppDelegate wires memory-dormant as .critical.

No behavioral change yet — banners still treat all failures equally;
the agent system prompt is unchanged. Slices 2 + 3 land those.

Tests: 13/13 in BootHealthOrchestratorTests pass, including 4 new tests
for overallHealth + severity accessor + JSON roundtrip with severity.
2 pre-existing failures in LLMEventTests/OllamaRequestBodyTests are
unrelated (qwen model-ID literal drift from commit 8d0a959, present
before this slice — confirmed via git stash).

Boundary gates 18/18 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:24:43 -05:00
james.maesandClaude Opus 4.7 b2f0abb670 fix(round-2): await probe registration inline to close runAll race
registerBootHealthProbes was spawning 8 fire-and-forget Task blocks,
each doing `await bootHealthOrchestrator.register(...)`. Then
runBootHealth called `await bootHealthOrchestrator.runAll()` —
which could fire BEFORE any register Task landed, yielding an empty
snapshot. Worst case: silent failure (no log lines, no banners,
empty Status panel).

The dogfood pass on commit 67748f5 happened to land all 8 register
Tasks before runAll fired (race won), but that's accidental — the
JS task queue scheduling is not load-bearing.

Fix: convert registerBootHealthProbes to `async` and await each
register call inline. runBootHealth then awaits the helper before
calling runAll. The orchestrator's runAll fan-out continues to use
TaskGroup for parallel probe execution, so this change has no
runtime perf impact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:35:00 -05:00
james.maesandClaude Opus 4.7 67748f5336 feat(round-3): Status… menu item + NSPanel — live re-probe on open
New AppKit panel (NSPanel + SwiftUI hosting view) renders the live
BootHealthSnapshot, re-probes on every appearance, and offers
"Re-probe all" + "Copy report" buttons. Operator/dev tool, not a
user-facing surface — AppKit deliberately so it doesn't ride the
R3F render pipeline.

Anti-fake-status carries forward from Round 2: the panel's `.task`
modifier fires a fresh `runBootHealth()` sweep on every open, never
showing a cached boot snapshot. Cmd+R re-probes; Cmd+Shift+C copies.

Non-modal: NSPanel with [.titled, .closable, .resizable,
.nonactivatingPanel] + makeKeyAndOrderFront(_:) — no runModal calls.
scripts/check-no-modal-presentation.sh stays green.
isReleasedWhenClosed=false so the user can close and re-open without
re-creating the model (preserves "Copied X B" caption + last
snapshot across opens, in addition to surviving the AppKit
"window deallocs on close" default that would otherwise crash on
second open).

Files:
  - App/MenuBar/StatusPanel.swift (new):
    * StatusPanelModel — @MainActor ObservableObject, owns the
      snapshot + isProbing + copyConfirmation @Published state.
      `probeRunner` closure injected so "Re-probe all" hits the
      production AppDelegate.runBootHealth() path (single source
      of truth — panel + boot use the same code).
    * StatusPanelView — SwiftUI body. Scrollable rows, header
      with spinner during probe, footer with re-probe + copy
      buttons + caption.
    * SubsystemRow (private) — glyph (✓/⚠/✗/?), name, status
      label, latency, evidence, reason. Colors per status
      (green/orange/red/gray). Multi-line evidence + reason.
    * StatusPanel: NSPanel — hosts the SwiftUI view, `present()`
      method centers + brings front + activates app.
  - App/MenuBar/MenuBarContextMenu.swift:
    * `build` now takes a `statusAction:` closure (5th param).
    * "Status…" NSMenuItem between "Settings…" and "Show Dev Overlay".
  - App/AppDelegate.swift:
    * statusPanel: StatusPanel? lazy property.
    * openStatusPanel() method — lazy-creates panel on first click,
      passes self.runBootHealth as the probe runner closure.
    * installMenuBar's MenuBarContextMenu.build call updated for
      the new param.
  - Jarvis.xcodeproj/project.pbxproj: PBXBuildFile + PBXFileReference
    for StatusPanel.swift, added to MenuBar PBXGroup + Sources phase.

Verification:
  - bash scripts/check-app-builds.sh: PASS
  - bash scripts/check-no-modal-presentation.sh: OK
  - bash scripts/check-install-order.sh: PASS
  - bash scripts/check-no-leftover-stubs.sh: PASS

Dogfooding next: cold-launch → right-click menu bar icon → Status…,
verify rows render with concrete evidence (mic device name, model
ID list, etc.); click Re-probe and confirm rows refresh with new
latency values; click Copy report and paste into a text editor to
verify JSON is well-formed and complete.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:12:37 -05:00
james.maesandClaude Opus 4.7 687b80b3f0 feat(round-2): boot-phase health probes — NOT FAKED, structured logs
After all install Tasks join, runs one live probe per subsystem and
emits structured `BootHealth: subsystem=X state=Y latencyMs=Z evidence="..."`
log lines. Any subsystem reporting .failed enqueues a non-modal banner
via HUDBannerCoordinator (priority 3 — sits between hotkey-bind-failed
and ollama-url-rejected). The snapshot is stored on bootHealthOrchestrator
so Round 3's Status menu can read + re-probe.

Anti-fake-status invariant is type-level: ProbeStatus is a sum type with
.ok / .degraded(reason:) / .failed(reason:) / .unknown(reason:) — every
non-ok case carries a reason string, so a snapshot can never report
state=unknown without explaining why. A probe that can't execute MUST
return .unknown — never .ok with a fake evidence line.

New types in packages/AgentCore/Sources/AgentCore/BootHealth.swift:
  - ProbeStatus, ProbeOutcome, BootHealthProbe protocol
  - SubsystemHealth (Codable for Round 3 "Copy report"), BootHealthSnapshot
  - BootHealthOrchestrator actor — parallel TaskGroup fan-out,
    registration-order-preserved snapshot, injectable clock for tests

Eight concrete probes in App/Boot/BootHealthProbes.swift:
  - MemoryBootHealthProbe — MemoryStatsStoreAdapter.getMemoryStats round-trip
  - AnthropicBootHealthProbe — Keychain structural check (no billable
    network call at boot; Round 3 panel offers explicit network re-probe)
  - OllamaBootHealthProbe — GET /api/tags, 3s timeout, asserts qwen3.6 +
    qwen2.5-coder + nomic-embed-text all present
  - VoiceBootHealthProbe — AudioGraphOwner state + mic TCC
  - VisionBootHealthProbe — camera TCC + DiscoverySession (read-only,
    does NOT prompt TCC per RESEARCH §Example 2)
  - MCPBootHealthProbe — runtime.client.registeredToolNames count
  - ReplayBootHealthProbe — replay.sqlite presence + parent-dir writability
  - WebviewBootHealthProbe — WebviewBridge.handshakeState (.armed = live)
  - DormantSubsystemProbe — placeholder for subsystems whose actor was nil
    at probe-registration; reports .unknown honestly rather than skipping

AppDelegate wiring (step 16, runs after selfKnowledgeInstallTask which
transitively awaits every other install Task):
  - bootHealthOrchestrator: BootHealthOrchestrator stored property
  - bootHealthTask: Task<Void, Never>? cancelled in applicationWillTerminate
  - runBootHealth() async: registers probes (idempotent — re-probes from
    Round 3 reuse the existing registry), runs, logs, enqueues banners
  - emitBootHealthLog / enqueueFailedBootHealthBanners helpers

Tests (packages/AgentCore/Tests/AgentCoreTests/BootHealthOrchestratorTests.swift):
  - 8/8 passing (210ms wall — 3×200ms parallel probes prove TaskGroup
    fan-out works; serial would be 600ms+)
  - Coverage: runAll invokes every probe, snapshot preserves registration
    order, parallel execution timing, lastSnapshot persistence, .failed
    propagation, .unknown not coerced to .ok, deterministic clock
    injection for latencyMs, JSON round-trip for all 4 ProbeStatus variants

Verification before commit:
  - swift test --package-path packages/AgentCore --filter
    BootHealthOrchestratorTests: 8/8 PASS (0.210s)
  - swift test --package-path packages/Memory --filter
    MemoryExtractorTests: 11/11 PASS
  - bash scripts/check-app-builds.sh: PASS
  - 18/18 boundary gates: PASS

Dogfooding next: cold-launch + grep system.log for `BootHealth:
subsystem=` lines; force a failure (e.g. kill Ollama) and verify
banner appears. Status menu (Round 3) is next session task.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:03:24 -05:00
james.maesandClaude Opus 4.7 5718bdd820 test(memory): rename qwen25Coder32B test to qwen36
Matches commit 8d0a959 model flip — extractor default is now
ModelID.qwen36 ("qwen3.6:latest"). The previous test name + assertion
were stale and failed on `swift test --package-path packages/Memory`
with the "Optional(qwen3.6:latest)) is not equal to (qwen2.5-coder…)"
diff. No production code change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 14:02:57 -05:00
james.maesandClaude Opus 4.7 70dd0d41c6 docs(handoff): scope Rounds 2 (boot health) + 3 (status menu) for fresh session
Context-budget hand-off. Round 1 (model split) shipped in 8d0a959. The
'NOT FAKED — 100% real only' ethos is preserved as the load-bearing
design constraint for both deferred rounds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:37:40 -05:00
james.maesandClaude Opus 4.7 8d0a959a19 feat(model): qwen3.6 for memory extractor; q8_0 for agent-loop fallback
Round 1 of the post-D-5/D-6 model strategy split. The user has both
`qwen3.6:latest` (~22GB MoE) and `qwen2.5-coder:32b-instruct-q8_0` (~32GB)
pulled. CLAUDE.md previously pinned the entire local-model strategy to
the base `qwen2.5-coder:32b` because of an April 2026 Ollama tool-call
bug affecting Qwen 3 / 3.5 / Gemma 4. That ban was correct then; today
(2026-05-11) it isn't.

**Empirical retest 2026-05-11:** POST /api/chat (non-streaming) against
qwen3.6:latest with a representative `apply_memory_ops` tool definition
returned a clean tool_calls array, well-formed JSON args, no leaked
<think> tags. Upstream Ollama appears to have closed the relevant
issues (#14493 / #14601 / #14745 / #15315) in the interim. Documented
in ModelID.qwen36 doc comment.

Two-model strategy:

- **MemoryExtractor default → qwen3.6:latest** (`ModelID.qwen36`)
  Single-turn, single-tool, no cap recovery — the lowest-risk place to
  validate the newer model. If extraction stays stable for a few days
  of dogfooding, we revisit the agent-loop fallback.
- **Agent-loop Ollama fallback → qwen2.5-coder:32b-instruct-q8_0**
  (`ModelID.qwen25coder32b`'s rawValue is now the Q8_0 variant the user
  already has — higher fidelity than the default Q4_K_M, same model
  family, same tool-call wire format). The agent loop has multi-turn
  tool chains, cap recovery, tool_choice=none paths, and streaming all
  in scope — kept conservative until Qwen3.6 is verified there too.

Changes:
- packages/AgentCore/Sources/AgentCore/ModelID.swift
    + `qwen36` constant for qwen3.6:latest with empirical retest doc.
    ! `qwen25coder32b` rawValue is now `qwen2.5-coder:32b-instruct-q8_0`
      (same constant name, the Q8_0 raw value). All ModelID.qwen25coder32b
      consumers pick up the change automatically.
- packages/Memory/Sources/Memory/MemoryExtractor.swift
    - `model: ModelID = .qwen25coder32b` → `.qwen36`.
    Docstring updated to explain the empirical retest + why the
    extractor is the safest place for the newer model.
- packages/Harness/Sources/Harness/Runners/LiveOllamaRunner.swift
    `requiredModelID` constant now sources from
    `ModelID.qwen25coder32b.rawValue` so a future single-edit on the
    canonical model name propagates here automatically. Doc updated to
    point at the q8_0 variant + ~32GB pull size.

User-facing note: no model pulls required for this commit; both
models are already on the user's machine.

Round 2 (boot-phase health check) and Round 3 (Status menu dialog)
deferred to a fresh session for context budget — handoff doc to follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:36:30 -05:00
james.maesandClaude Opus 4.7 cb584a2ac8 feat(d5d6): real memory database — vec0 statically linked, hard-fail boot, stats tool
Closes D-5 / D-6 (sqlite-vec dylib bundling) carry-forwards from the
2026-05-04 audit by adopting jkrukowski/SQLiteVec as a SPM dependency.
The package's CSQLiteVec target compiles sqlite3.c + sqlite-vec.c into
one statically-linked C target with `core_vec_init()` registering vec0
as an auto-extension. All sqlite3_* calls in Replay's SQLiteConnection
and Memory's MemoryStore resolve to this bundled SQLite (vec0 always
available) instead of Apple's stripped /usr/lib/libsqlite3.dylib.

The retired hand-rolled approach (dlsym pathway + JARVIS_VEC0_STUB_PATH
env-flag + Bundle.module placeholder text file) never worked on macOS
because Apple stripped sqlite3_load_extension from the system libsqlite3.
Memory init has been silently dormant since Phase 7 (2026-04 onward),
which is what made B-02 (history threading) appear permanently broken
to the user during dogfooding even after the 48ad8d6 tactical patch
landed.

Live verification 2026-05-11: fresh launch with empty jarvis.db creates
schema, persists turn rows across submits, B-02 multi-turn context
resolves correctly ("what breed is Toby?" → "Golden Retriever" after
prior turn established Toby is a Golden Retriever), and the new
get_memory_stats tool reports `turnsCount=6, vecVersion=v0.1.6,
sqliteVersion=3.51.0`.

Changes:

- packages/Replay/Package.swift
    + SQLiteVec SPM dependency (from: "0.0.14")
    + CSQLiteVec product wired into Replay target
- packages/Replay/Sources/Replay/SQLiteConnection.swift
    - import SQLite3
    + @_exported import CSQLiteVec
    All sqlite3_* call sites unchanged — linker now resolves to our
    bundled sqlite3 with vec0 statically linked in.
- packages/Memory/Package.swift
    + SQLiteVec SPM dependency (named product so MemoryStore can import
      CSQLiteVec directly for the core_vec_init() one-shot call)
    - resources: PLACEHOLDER.txt copy (defunct vec0.dylib stub slot)
- packages/Memory/Sources/Memory/MemoryStore.swift
    - import SQLite3 → import CSQLiteVec
    + _vecAutoExtensionRegistered static-let one-shot: calls
      CSQLiteVec.core_vec_init() which registers vec0 via
      sqlite3_auto_extension on the first MemoryStore construction.
      Idempotent (static-let initializer fires exactly once).
    - loadVecExtension(on:) dlsym/JARVIS_VEC0_STUB_PATH/Bundle.module
      pathway (retired; ~70 lines removed)
    Updated docstring to reflect the new hard-fail boot policy.
- packages/Memory/Sources/Memory/Resources/PLACEHOLDER.txt — deleted
- App/AppDelegate.swift installMemory()
    Reverses Track-D D-1 (75a10be, 2026-05-04): MemoryStore.init failures
    are now FATAL via Logger.critical + TCCAlertService.presentHardBlock
    + NSApp.terminate. The graceful-degradation path that kept the app
    booting with memoryStore=nil hid the entire B-02/B-04/B-05 silent-
    failure bug class. Aligns with the 2026-05-07 API-first pivot
    thesis: "no silent failures".
    `store` is now non-optional throughout installMemory; downstream
    callers no longer need `if let store = store`.
- packages/MCP/Sources/MCP/InProcess/GetMemoryStatsTool.swift (new)
    + MemoryStatsDispatching protocol
    + MemoryStats Codable struct (turnsCount, factsTotal, factsActive,
      sessionsCount, dbFileBytes, vecVersion, sqliteVersion,
      lastTurnAtUnixMs, lastFactAtUnixMs)
    + GetMemoryStatsTool conforming to InProcessTool
- App/MCP/InProcessMemoryAdapters.swift
    + MemoryStatsStoreAdapter — bridges the protocol to MemoryStore's
      public queryRowCount / querySingleString seams and FileManager
      for the on-disk file size. Read-only, no confirmation.
- App/AppDelegate.swift installSelfKnowledgeTools()
    + Registers GetMemoryStatsTool with MemoryStatsStoreAdapter.
- packages/Memory/Tests/MemoryTests/*
    Retired the JARVIS_VEC0_STUB_PATH XCTSkipUnless guards from 9 tests
    (vec0 is always available now — no need to env-gate). Rewrote
    MemoryStoreTests to drop the now-impossible "init throws when vec
    missing" case and assert facts_vec virtual table creation +
    vec_version() readback. Added seedTurnRow helper to
    MemoryStoreApplyOpTests so the now-enforced
    `facts.source_turn_id REFERENCES turns(id)` foreign key is honored
    in tests that previously passed when FK enforcement was dormant.

Test summary: Memory full suite 91 tests, 10 skipped (env-gated real-
Ollama tests), 0 failures. App target builds clean. Substrate verified
end-to-end via live launch (vec_version: v0.1.6 logged at boot;
get_memory_stats returns actual schema row counts).

Pending D-7 carry-forward: ollama pull nomic-embed-text (~270MB) for
embedder; user already has qwen2.5-coder:32b-instruct-q8_0 which is
a higher-quality variant of the project's hardcoded
qwen2.5-coder:32b — small ModelID config delta to follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:25:56 -05:00
james.maesandClaude Opus 4.7 48ad8d631c fix(b-02): thread recent turns into AgentOrchestrator messages array
Carry-forward bug B-02 (live evidence 2026-05-07): conversation
continuity broken — "yes" / "why 4.5?" lose prior turn context because
AgentOrchestrator.process(_:) built [system, user(input.userText)]
with no history fetch.

Tactical patch outside the M-0..M-7 migration sequence, per
JARVIS-API-MIGRATION-PLAN.md §3 and UQ-2 (option B). Lands on develop
directly. M-4 (Memory surface) will reframe this path against the new
API contract but the user-visible bug closes today.

Substrate gap discovered + addressed in-scope. The `turns` table was
unwritten (schema present at MemorySchema.swift, FTS5 trigger ready,
read API at MemoryStore.recentTurnsForSession, but no INSERT site
anywhere in the codebase). The migration plan assumed turn writes
existed; they did not. Patch adds the missing writer plus the read
side wiring.

Changes:

- packages/Memory/Sources/Memory/MemoryQueries.swift
    + turnInsertSQL — single-row INSERT into turns
- packages/Memory/Sources/Memory/MemoryStore.swift
    + appendTurn(sessionId:role:content:source:createdAt:)
    Single-row INSERT; FTS5 trigger mirrors content into turns_fts
    automatically. T-06-05-03: function MUST NOT log content.
- packages/AgentCore/Sources/AgentOrchestrator/AgentOrchestrator.swift
    + PriorTurn struct + SessionHistoryLookup typealias
    + emptySessionHistoryLookup default (preserves pre-B-02 test setups)
    + sessionHistoryLookup ctor parameter
    Replaces the [system, user] messages-array build with prior-history
    hydration: system + chronological prior user/assistant rows + current
    user. Lookup throws -> warning log + empty history (matches D-3
    PriorFactsLookup resilience contract).
- App/AppDelegate.swift
    + sessionHistoryLookup closure: recentTurnsForSession(limit: 10),
    reverse DESC -> chronological, filter to user/assistant only, return
    [PriorTurn]
    + extends existing turnContent flush closure with appendTurn calls
    (rides on memory-extraction drain path; assistant row offset +1 ms
    so DESC + id-tiebreaker preserves user->assistant order)

Tests:

- packages/AgentCore/Tests/AgentOrchestratorTests/HistoryThreadingRegressionTests.swift
    HT-1: third turn includes prior user/assistant pairs in chronological
    order. 3/3 PASS.
    HT-2: empty-history default produces [system, user] (pre-B-02
    baseline preserved).
    HT-3: three-turn happy path emits zero LLMProviderError
    .streamTruncatedFinal events. Pins R-006 / Phase E gate behavior:
    patch grows messages array, not finalSystem, so
    CacheHints.eligibleForSystemPrompt(finalSystem) is unchanged.
- packages/Memory/Tests/MemoryTests/MemoryStoreAppendTurnTests.swift
    AT-1: appendTurn round-trips via recentTurnsForSession.
    AT-2: user/assistant pair reverses to chronological.
    AT-3: session_id scoping isolates rows.
    AT-4: turns_fts trigger fires on append.
    All env-gated on JARVIS_VEC0_STUB_PATH per existing MemoryStore-test
    convention; skip cleanly without vec0.dylib.

Verification:

- check-app-builds.sh PASS.
- Boundary gates sampled (check-no-leftover-stubs, applescript-confirm,
  corpus-secrets, bus-protocol-version, single-memory-mutated-emit,
  single-writer-hudstate) PASS.
- AgentCore affected-subset (Submit/Retry/TextEnd2End/CapRecovery/
  HistoryThreading) 26/26 PASS, 0 failures.
- Memory full suite 92 run, 23 skipped (env-gated), 0 failures.

Degradation contract: when MemoryStore is nil (dormant memory — vec0
or Ollama unavailable), turn persistence is also dormant; the
appendTurn call is guarded by memoryStore != nil. When D-5/D-6 close
and memory comes up live, persistence activates without further code
changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 08:50:40 -05:00
james.maesandClaude Opus 4.7 46590cd9aa docs(architecture): API design swarm output — v1.0 contract locked
Six-agent design swarm executed end-to-end on 2026-05-07 evening per
.planning/architecture/HANDOFF-2026-05-07.md. The architectural pivot
to API-first reaches lock state: v1.0 = JARVIS-API-DESIGN-v0.1.md +
JARVIS-API-DESIGN-v0.2.md (UQ-1..UQ-5 answered).

Swarm output (.planning/architecture/):

  - INVENTORY.md (~190 capabilities, file:line cited; ALIVE/DEAD/
    DEFERRED/RETIRED-CANDIDATE classification)
  - JARVIS-API-DESIGN-v0.1.md (1036 lines; 7 surfaces — Turn / Voice /
    Memory / Vision / Settings / Diagnostics / Self; CQRS framing;
    TurnID universal correlation; typed Results)
  - REVIEW-API-CORRECTNESS.md (21 findings: 2 BLOCKING, 5 HIGH)
  - REVIEW-MIGRATION-RISK.md (19 findings: 1 BLOCKING, 7 HIGH;
    M-0..M-7 cutover sequence)
  - REVIEW-TEST-CONTRACT.md (16 findings: 2 BLOCKING, 5 HIGH; harness
    shape recommendation)
  - JARVIS-API-DESIGN-v0.2.md (synthesis delta; every critic finding
    dispositioned ACCEPT/DEFER/USER-DECISION; UQ-1..UQ-5 locked in §5)
  - JARVIS-API-TEST-CONTRACT.md (~70 scenarios across 7 surfaces +
    cross-surface; B-02..B-08 reproduction matrix)
  - JARVIS-API-MIGRATION-PLAN.md (M-0..M-7 + B-02 tactical patch;
    ~52 distinct gates; 19-26 engineer-day estimate)
  - README.md (artifact index + reading order)

Locked decisions:

  - UQ-1: runtime gate JARVIS_HARNESS=1 (single binary)
  - UQ-2: B-02 tactical patch outside migration + streamTruncated
    regression test; B-04 stays in M-6
  - UQ-3: explicit Voice.synthesizeTurn(turnId:text:tier:) command
  - UQ-4: Settings owns ALL configuration mutation; Voice retains only
    runtime-control verbs (pttDown/pttUp/cancelTTS/synthesizeTurn);
    bargeIn migrates to Turn surface
  - UQ-5: parametric harness mandated — every scenario runs
    .inProcessActor AND .jsonRoundTripWebView

Tools/jarvis-diag/ — Swift Package skeleton authored by Phase 5:
type signatures + scenario stubs only, no implementation logic.
Compiles green (swift build under Tools/jarvis-diag/). M-0 step 3
wires it against packages/JarvisAPI/ once that package exists.

Constraints honored: zero code changes outside .planning/architecture/
and Tools/jarvis-diag/. No fixes to B-02..B-08 inline. Parallel agents
wrote disjoint files (.continue-here.md anti-pattern preserved).

Next session: cross-AI peer review of v1.0 (GPT-5 Pro / Gemini 2.5 Pro
/ fresh Opus), then B-02 tactical patch on develop, then M-0 (pre-
migration gates), then M-1 (Self surface) on its own feature branch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 16:21:45 -05:00
james.maes b36e8ebbe5 docs(handoff): architectural pivot to API-first — close session 2026-05-07
After two days of fix → test green → relaunch → still broken loop, the
verification gate (executor self-reports + boundary gates) cannot detect
substrate failures. Today's substrate fixes (B-01a / B-01b — tool catalog
enumeration + dispatch routing) shipped and live-verified, but live testing
surfaced B-02..B-08: history threading, camera click, voice input, TTS
output, chat scroll, voice menu, model-name paraphrase. Stop-loss decision:
pivot to API-first architecture.

This commit packages the session-end state for clean handoff:

- .planning/architecture/HANDOFF-2026-05-07.md — primary handoff doc.
  Specifies the 6-agent design swarm (inventory → architect → 3 critics in
  parallel → synthesis → test contract + migration in parallel) that the
  next session dispatches. Goal: JARVIS-API-DESIGN.md v1.0 +
  test contract + migration plan, implementation-ready by midnight.

- docs/SESSION-STATE.md — updated with 2026-05-07 session-end state and a
  ▶ Next Session pointer to the handoff. Earlier 2026-05-04 entry preserved
  below the line.

- docs/TODO.md — top-of-file pivot directive + B-01 closures + B-02..B-08
  carry-forward + Phase 10 status + housekeeping loose ends.

Branch: develop @ 1b7fb81 (B-01b dispatch routing fix). Tree clean.
Substrate alive for tool calls; remaining bugs deferred to fix against
the new API contract once locked.
2026-05-07 14:01:11 -05:00
james.maes 1b7fb81408 fix(10-02c): wire InProcessAwareToolDispatcher into runtime — closes B-01b
Prior B-01a fix (e0c0310) wired tool catalog ENUMERATION so the model gets
schemas for in-process tools. But ROUTING was still broken: MCPRuntimeWiring's
ToolRegistry only knew about the 3 stdio tools (get_time / get_clipboard /
run_applescript), so when the model emitted tool_use for get_active_audio_route
the inner MCPToolDispatcher returned "tool unknown" and the model surfaced the
failure as visible chat text ("the audio route tools are erroring out").

This commit lands the dispatch wire-up. With the types from 0961fef, 6503b23,
3cf0d39 already in place:

- App/AppDelegate.swift: eager-default-init inProcessToolRegistry at property
  declaration so the SAME actor reference is shared between installMCP (passes
  it to MCPRuntimeWiring.build for the composite dispatcher) and installMemory
  / installSelfKnowledgeTools (register tools into it). Refactor installMemory
  to populate INTO the existing registry via a new populateMemoryTools helper
  rather than constructing a fresh one. The pre-existing
  buildInProcessToolRegistry static delegates through the same helper for
  back-compat.

- App/MCP/MCPRuntimeWiring.swift: build() takes a new inProcessRegistry
  parameter (defaulted nil for tests). When supplied, the inner stdio
  MCPToolDispatcher gets wrapped by InProcessAwareToolDispatcher so name-routing
  picks in-process tools first, falls through to stdio. Confirmation gating,
  bus emission, and observer hooks fire identically on both paths because the
  composite sits INSIDE ConfirmingToolDispatcher's outer chain.

- .planning/phases/10-self-awareness-diagnostics/10-02c-PLAN.md: skeleton plan
  documenting the surgical fix.

Build: swift test --package-path packages/MCP PASS (91/91 XCTest + 6 swift-test).
bash scripts/check-app-builds.sh PASS.

Live verification deferred to user — relaunch + ask "what mic are you using?"
expects real device name in reply (no JSON blob), tool_use + tool_result for
get_active_audio_route in the log tail.
2026-05-07 13:12:23 -05:00
james.maes 0961fef40b feat(10-02c): add InProcessAwareToolDispatcher composite for B-01b dispatch routing
- Composite ToolDispatcher: name-routes between InProcessToolRegistry (memory
  + four self-knowledge tools) and inner stdio dispatcher (MCPToolDispatcher).
- Sits INSIDE ConfirmingToolDispatcher's chain so confirmation gating, bus
  emission, and observer hooks fire identically on both paths.
- In-process result bytes flow through the same SanitizeForModel.prepareForBoundary
  + ToolResultObserver pipeline as stdio results (audit-trail parity).
- Tools registered AFTER composite construction are still routable because
  the composite holds the registry actor reference and queries by name on
  every dispatch (resolves the installSelfKnowledgeTools-runs-after-installAgent
  install-order wrinkle).
- Closes 5/5 cases in InProcessAwareDispatchRoutingTests.
2026-05-07 11:19:13 -05:00