Files
WhispAssist/docs/05-roadmap.md
T

28 KiB
Raw Blame History

05 — Roadmap

Eight phases, each a shippable increment. Every task carries an ID and the requirement(s) it satisfies; every phase has acceptance criteria that gate "done" (verified by the tests in 06-test-strategy.md). Phases are ordered so each builds on a working previous one. Suggested effort labels: S ≤1 day, M a few days, L ~1–2 weeks (team-dependent, indicative only).

Legend: [T1.2] = task; → FR/NFR satisfied.


Phase 1 — Foundation: capture + CPU transcription

Goal: a window where you click Record, system audio is captured to disk, and plain transcript text streams in on CPU.

  • [T1.1] Scaffold Tauri 2 + Svelte project; window, build, dev loop. M → ADR-0001/0002
  • [T1.2] audio: WASAPI loopback capture thread (polling), PCM ring buffer, WAV writer. L → FR-CAP-1/2
  • [T1.3] Recording lifecycle commands + recording://state events; start/stop/pause/resume. M → FR-CAP-3
  • [T1.4] Recording indicator: in-app banner + tray icon. S → FR-CAP-4
  • [T1.5] transcription: integrate whisper-rs CPU; load small model; window+overlap feeder. L → FR-TRX-1, NFR-MNT-4
  • [T1.6] Stream interim/final segments → transcript://segment; minimal transcript view. M → FR-TRX-2
  • [T1.7] Idle discipline: no threads/timers when not recording; model unloaded when idle. S → NFR-RES-1
  • [T1.8] "Record this meeting" toggle (default OFF) + mid-meeting set_recording_retention; "saving" indicator. S → FR-REC-1/3
  • [T1.9] Consent notice + one-time acknowledgment before first retained recording. S → FR-REC-2

Acceptance: Record a 5-min meeting; transcript appears live within a few seconds of speech on a CPU-only laptop; stopping leaves the app idle with near-zero CPU. With "Record" ON, a playable audio.wav remains; with it OFF (default), no audio file remains after finalize. The consent notice appears before the first retained recording and must be acknowledged once. (Tests: P1 suite.)


Phase 2 — Storage, notes, persistence, recovery

Goal: meetings are saved, listable, editable as Markdown, exportable, and survive a crash.

  • [T2.1] storage: SQLite schema + migrations; meeting folder layout; transactional writes. L → FR-STORE-1
  • [T2.2] Persist transcript.json + metadata on stop; status lifecycle. M → FR-STORE-1
  • [T2.3] Meetings list (create/open/delete) UI + commands. M → FR-STORE-5
  • [T2.4] notes: assemble Markdown (speaker-tagged) from segments; render + raw side-by-side. M → FR-NOTE-1/6
  • [T2.5] Markdown editor with toolbar (headings/bold/italic/lists/checkboxes); update_notes. M → FR-NOTE-2
  • [T2.6] Export single meeting to .md and bundle (audio+transcript+notes). M → FR-NOTE-3, FR-STORE-3
  • [T2.10] Finalize path honors retention: delete working WAV only after transcript finalized; set recorded/audio_path (FR-REC-4). S → FR-REC-1/4
  • [T2.7] Auto-save notes/transcript at intervals. S → FR-REL-2
  • [T2.8] Startup recover-scan: audio-without-transcript → recovering; re-run flow. M → FR-REL-1, NFR-REL-3
  • [T2.9] Storage settings: base directory + retention policy (size/age) + background enforcement. M → FR-STORE-2

Acceptance: Kill the app mid-meeting; on relaunch the meeting is recoverable from audio and transcription can be regenerated. Notes edit + .md export round-trip. Retention deletes the oldest meeting when the cap is exceeded and never touches a recording in progress. (Tests: P2 suite.)


Phase 3 — Hardware acceleration + model management

Goal: detect and use NPU/GPU; switch models; show the active backend; batch re-transcribe.

  • [T3.1] hardware: enumerate CPU/GPU (DXGI) + NPU (ONNX/Windows ML); rank backends. L → FR-HW-1
  • [T3.2] Backend selection (auto order + manual override); construct matching Transcriber. M → FR-HW-2
  • [T3.3] whisper.cpp accelerated builds behind Cargo features (CUDA/Vulkan); CPU always builds. L → FR-TRX-5, NFR-MNT-4
  • [T3.4] NPU path: ort + DirectML Whisper-ONNX Transcriber (same trait/output). L → FR-TRX-5, ADR-0004
  • [T3.5] Graceful fallback NPU→GPU→CPU on failure; hardware://changed notice. M → FR-HW-4, NFR-REL-2
  • [T3.6] Show active backend, model size, est. RTF in Settings + recording bar. S → FR-HW-3
  • [T3.7] Model management UI: choose Whisper size; download/remove with progress. M → FR-MODEL-1
  • [T3.8] Batch re-transcription with a larger model post-meeting. M → FR-TRX-3
  • [T3.9] "Low overhead" preset (CPU + small model + no realtime summary + reduced indexing). S → NFR-RES-3, NFR-PERF-3

Acceptance: On a machine with a supported GPU/NPU, the active backend is detected and shown, and transcription is measurably faster than the CPU baseline; forcing a backend failure falls back cleanly to CPU without aborting. Output segments are equivalent across backends. (Tests: P3 suite.)


Phase 4 — Speaker diarization & naming

Goal: segments get speaker labels; users name/merge speakers during and after meetings.

  • [T4.1] diarization: integrate sherpa-onnx (segmentation+embedding+clustering) via FFI. L → FR-SPK-1, ADR-0005
  • [T4.2] Align speaker spans to transcript segments by timestamp overlap. M → FR-SPK-1
  • [T4.3] Live provisional speaker turns from cheap segmentation during recording. M → FR-SPK-2
  • [T4.4] In-session naming: name a speaker → applies to past & future segments. M → FR-SPK-2
  • [T4.5] Post-meeting review screen: rename + merge over-split speakers. M → FR-SPK-3
  • [T4.6] Persist speaker→name mapping; apply at render/export (non-destructive). S → FR-SPK-5
  • [T4.7] Diarization models in model-management; download/select. S → FR-MODEL-1

Acceptance: A two-person recording yields ≥2 speakers; naming a speaker relabels all their segments live; merging two IDs into one updates notes and export; renaming never rewrites segment speaker IDs in storage. (Tests: P4 suite.)


Phase 5 — Local LLM integration

Goal: configure a local LLM and get summaries, decisions, and editable action items.

  • [T5.1] llm: Ollama client (/api/tags, /api/chat streaming) behind LlmProvider. M → FR-LLM-1, ADR-0007
  • [T5.2] Provider settings: Ollama | custom endpoint | off; llm_status + reachability. M → FR-LLM-1
  • [T5.3] Local-endpoint guard + "data leaves WA" banner for remote/proxy endpoints. S → FR-LLM-6, FR-SEC-1
  • [T5.4] Prompt assembly: transcript + metadata + optional template → summary/decisions/actions. M → FR-LLM-2
  • [T5.5] Stream tokens to summary panel (llm://token/done); persist summary.json. M → FR-LLM-4
  • [T5.6] Parse action items into editable list; confirm → action_items rows. M → FR-LLM-3
  • [T5.7] Guided provider install + hardware-aware model suggestions when none detected. M → FR-LLM-5

Acceptance: With Ollama running, a finished meeting produces a streamed summary plus a list of action items the user can edit and confirm; with the LLM off or unreachable, capture/transcription/ notes still work and the UI degrades gracefully. (Tests: P5 suite.)


Phase 6 — Calendar & Outlook .pst integration

Goal: import .pst events/attendees, attach recordings to events, suggest speaker names.

  • [T6.1] calendar: .pst reader (outlook-pst; libpff fallback) → events + attendees. L → FR-CAL-1, ADR-0008
  • [T6.2] import_pst command with optional password; progress events; persist events/participants. M → FR-CAL-1
  • [T6.3] Calendar/meetings view: upcoming/recent; attach a recording to an event. M → FR-CAL-2
  • [T6.4] Pre-meeting context panel: title, organizer, participants. S → FR-CAL-3
  • [T6.5] Participant-aware naming: attendee dropdown in speaker UI + "add new name". M → FR-SPK-4
  • [T6.6] Link recording ↔ historical .pst event; continuity of named speakers. M → FR-CAL-4

Acceptance: Importing a sample .pst yields events with attendees; selecting an event prefills the speaker-naming dropdown; PST parse failure is non-fatal and surfaced clearly. (Tests: P6 suite.)


Phase 7 — UX polish, themes, accessibility, privacy panel

Goal: the modern, fast, themeable, accessible experience.

  • [T7.1] Final three-pane layout (list · transcript/notes · summary/actions/participants). M → FR-UX-1
  • [T7.2] Light/dark themes following system + override; tokens/contrast pass. M → FR-UX-2
  • [T7.3] Live waveform/level meter; device-change resilience. M → FR-CAP-5/6
  • [T7.4] Keyboard shortcuts (record start/stop, view toggles, template apply). S → FR-UX-3
  • [T7.5] Screen-reader labels, focus order, ARIA; contrast audit. M → FR-UX-4
  • [T7.6] Privacy panel: active backend + LLM endpoint + local-only confirmation; privacy_self_check. M → FR-SEC-2
  • [T7.7] Startup/perf pass: cold start < ~2 s, lazy-load models, idle audit. M → NFR-PERF-4, NFR-RES-1

Acceptance: Themes switch with the OS and override; keyboard-only operation works for core flows; a contrast/screen-reader audit passes; the privacy panel accurately reflects egress; cold start meets target. (Tests: P7 suite.)


Phase 8 — Advanced features

Goal: templates, search/tagging, reminders, richer export, encryption.

  • [T8.1] Note templates by meeting type (sections per type); apply on creation. M → FR-NOTE-5
  • [T8.2] Full-text search (FTS5) across transcripts + notes. M → FR-SEARCH-1
  • [T8.3] Tagging + list filters (date/tag/participant). M → FR-SEARCH-2
  • [T8.4] PDF/Word export via local conversion. M → FR-NOTE-4
  • [T8.5] Bulk export by date range/tag. S → FR-STORE-4
  • [T8.6] Local reminders / OS notifications for action items. M → FR-CAL-5
  • [T8.7] Multi-language transcription + UI localization scaffold. M → FR-TRX-4
  • [T8.8] Optional at-rest encryption + password-protected vault. L → FR-SEC-3
  • [T8.9] (Later) Microsoft Graph calendar source behind CalendarSource (opt-in, consented). L → FR-CAL-6

Acceptance: Search returns relevant meetings; templates structure new notes; PDF/Word exports render correctly offline; reminders fire locally; encryption (when enabled) protects the storage root and unlocks with the vault password. (Tests: P8 suite.)


Phase 9 — Remote sync / upload

Goal: optionally upload meeting artifacts to user-configured destinations — off by default, self-hostable first. (ADR-0010.) Can begin in parallel after Phase 2 since it only needs the storage/file layer; OAuth sub-phase is independent of the WebDAV one.

9a — Core + WebDAV primary set (Nextcloud, ownCloud, Cloudreve, Seafile, Synology)

  • [T9.1] sync module: SyncTarget/SyncManager traits; sync_targets/sync_jobs schema (migration 0002). M → FR-SYNC-1
  • [T9.2] WebDavTarget: PUT/MKCOL/PROPFIND; per-provider base paths; chunked upload for large files. L → FR-SYNC-2
  • [T9.3] Target config UI + add/update/remove/list_sync_targets; per-target artifact + trigger selection. M → FR-SYNC-2/3
  • [T9.4] test_sync_target (reachability + auth) with provider-specific setup hints (e.g. enable SeafDAV). M → FR-SYNC-4
  • [T9.5] Durable queue: enqueue on finalize / "Upload now"; backoff retry; SHA-256 skip-if-unchanged; sync://job events. L → FR-SYNC-5
  • [T9.6] Credentials in OS credential store (keyring); never in settings/DB. M → FR-SYNC-6, NFR-SEC-4
  • [T9.7] TLS enforcement; refuse plaintext http except explicit per-target LAN opt-in. S → FR-SYNC-7
  • [T9.8] Sync state + target labeling in UI (self-hosted vs third-party); wire into privacy panel + privacy_self_check. M → FR-SYNC-8, FR-SEC-2

9b — Secondary OAuth targets (OneDrive, Dropbox, Box)

  • [T9.9] OAuth 2.0 PKCE with loopback redirect; token storage in credential store; begin_oauth_link. L → FR-SYNC-9
  • [T9.10] OneDriveTarget (MS Graph), DropboxTarget, BoxTarget: upload + resumable sessions behind SyncTarget. L → FR-SYNC-9
  • [T9.11] Third-party "data leaves your device" banner + explicit enable acknowledgment. S → FR-SYNC-8

9c — Optional

  • [T9.12] Client-side encryption before upload (uses the Phase 8 vault): destination holds only ciphertext. M → FR-SYNC-10

Acceptance: With sync disabled (default), the egress allowlist contains no sync hosts and nothing uploads. After adding and enabling a WebDAV target (tested against a real Nextcloud/Seafile or an rclone serve webdav instance), finishing a meeting uploads the selected artifacts to the correct remote path; interrupting the network leaves a failed job that retries and completes; an unchanged re-upload is skipped. Credentials never appear in settings.json/wa.db. Third-party targets show the leaves-your-device banner. (Tests: P9 suite.)


Phase 10 — External AI & coding-agent integration

Goal: optionally use hosted AI for summaries, and hand a meeting off to the user's coding agent to "get started right away." (ADR-0011.) All off by default. Needs Phase 5 (LLM) for distillation; otherwise independent.

10a — Cloud summary providers (Layer 1)

  • [T10.1] Extend LlmProvider with OpenAiCompatProvider (/v1/chat/completions) and AnthropicProvider (/v1/messages). M → FR-AI-1
  • [T10.2] set_llm_provider; API keys in OS credential store; host added to egress allowlist; third-party banner. M → FR-AI-2, FR-SEC-1
  • [T10.3] Per-use provider selection + active-provider display. S → FR-AI-3

10b — WhispAssist as an MCP server + feature briefs (Layer 2, the primary handoff)

  • [T10.4] mcp module on rmcp: loopback MCP server, off by default, token-gated; Streamable HTTP (/mcp) + stdio adapter. L → FR-MCP-1/6, NFR-SEC-5
  • [T10.5] Tools-first surface: list_recent_meetings, get_transcript, get_action_items, get_feature_brief. M → FR-MCP-2
  • [T10.6] FeatureBriefBuilder: distill transcript → structured brief; create/list/get_feature_brief; persist to briefs/. L → FR-MCP-4
  • [T10.7] Scope control (none|selected|all, recordings excluded by default) + set_brief_exposed. M → FR-MCP-3
  • [T10.8] Disclosure UI ("connected agents may forward data") + mcp_access_log audit. M → FR-MCP-5
  • [T10.9] Privacy panel + privacy_self_check show MCP state and confirm it adds no egress. S → FR-MCP-7, FR-SEC-2

10c — Push & task-tracker handoff (Layer 3, later)

  • [T10.10] AgentRunner: spawn claude -p / codex exec / opencode run / copilot headless against a repo; stream output. L → FR-AGENT-1
  • [T10.11] IssueTracker: create a GitHub issue from a brief; optional assign-to-Copilot-cloud. M → FR-AGENT-2

Acceptance: With everything off (default), no AI host is on the allowlist and no MCP port is open. Enabling a hosted provider routes a summary to it and shows the third-party banner. Enabling the MCP server lets a local agent (e.g. Claude Code) call get_feature_brief and receive a usable spec; the access is logged; WA opens no outbound socket for it (egress test unchanged). Recordings are never served unless explicitly allowed. (Tests: P10 suite.)


Cross-cutting (every phase)

  • Privacy invariant FR-SEC-1 verified continuously: the CI network test asserts egress stays within the settings-derived allowlist (LLM + enabled sync hosts + model downloads). See 06-test-strategy.md.
  • cargo fmt/clippy -D warnings + Prettier/ESLint clean.
  • Update docs/ whenever a contract or behavior changes (CLAUDE.md rule).

Dependency graph (high level)

P1 ─► P2 ─► P3 ─► P4 ─► P5 ─► P10 (AI/MCP; 10a/10b need P5's LLM)
        │        └► P6 ─► P7 ─► P8
        └► P9 (sync; needs only P2's storage layer — can run in parallel)
P5 and P6 can proceed in parallel after P4; P7 needs P3–P6 features to polish; P8 last.
P9 needs only P2; 9b (OAuth) is independent of 9a (WebDAV); 9c needs P8's vault.
P10 needs P5; 10b (MCP) is the priority; 10c (push/issue) is later and optional.

Suggested first milestone (thin vertical slice)

T1.1 → T1.2 → T1.5 → T1.6 → T2.1 → T2.2 → T2.4 gives a usable "record → live transcript → saved Markdown notes" loop — the smallest thing worth dogfooding.


Remaining work — post-v0.2.0 execution plan

As of v0.2.0, Phases 1–9 ship (capture incl. microphone, CPU/NPU/Vulkan transcription, diarization, storage/recovery, notes, local-LLM summaries, PST calendar, UX/a11y, templates/search/tags/export/reminders/encryption, WebDAV + OneDrive sync) in a single universal installer. What remains is Phase 10 (external AI & agent handoff, ADR-0011) plus a few breadth/reliability items. This section sequences that work by leverage, dependency, and the privacy invariant. Task IDs reference the Phase 10 list above; finer sub-tasks add a letter suffix. Traits/contracts for all of this already exist in 04-api-contracts.md (FeatureBriefBuilder, McpServer, hosted LlmProvider impls, AgentRunner, IssueTracker); tests live in 06-test-strategy.md (P10 + the cross-cutting egress gate).

Status snapshot (what's actually in the tree):

  • Built: Phases 1–9; OpenAiCompatProvider (used as the Phase 5 "custom" endpoint); chunked/ resumable upload (M4.1); multi-language transcription (M4.2); DropboxTarget/BoxTarget (M4.3); MS Graph CalendarSource (GraphSource, M4.4).
  • Stubbed (commands return Err(not_implemented(...))): create/list/get_feature_brief, set_brief_exposed, mcp_status, set_mcp_enabled, set_mcp_scope, mcp_access_log, run_agent, create_issue_from_brief.
  • Skeleton/absent: mcp/mod.rs (trait + todo!() only); AnthropicProvider (returns "isn't built yet").

Sequence: M1 → M2 (briefs are the MCP payload); M3 can run in parallel with M1/M2; M4 is opportunistic; M5 is last and optional.

M1 — Feature briefs (do first; standalone value) → FR-MCP-4 (T10.6)

Needs only the already-built LLM, delivers value before any MCP transport (view/copy a brief), and is the exact payload M2 serves. No new egress (uses the configured LlmProvider).

Already scaffolded — do NOT re-create: IPC types FeatureBrief/FeatureBriefInfo/ ContextExcerpt (models.rs); api.ts bindings createFeatureBrief/listFeatureBriefs/ getFeatureBrief/setBriefExposed; the four commands registered in lib.rs; DB tables feature_briefs + mcp_access_log (migrations/0003_ai_mcp.sql); the briefs/<id>.json schema (03-data-model.md) and the FeatureBriefBuilder trait (04-api-contracts.md). The four command bodies today return Err(not_implemented(...)) — M1 fills them in.

  • [M1.1] Storage methods (Store trait + SqliteStore, storage/mod.rs) over feature_briefs; row struct FeatureBriefRow { id, meeting_id, title, target_repo: Option<String>, path, exposed: bool, created_at }. Deletion cascades via the meeting FK (existing delete_meeting already drops the row + folder). S

    • async fn insert_feature_brief(&self, row: FeatureBriefRow) -> Result<(), StoreError>;
    • async fn list_feature_briefs(&self, meeting_id: Option<&MeetingId>) -> Result<Vec<FeatureBriefInfo>, StoreError>; (newest first)
    • async fn get_feature_brief_row(&self, id: &str) -> Result<FeatureBriefRow, StoreError>; (resolves path)
    • async fn set_brief_exposed(&self, id: &str, exposed: bool) -> Result<(), StoreError>;
  • [M1.2] LLM completion primitive (llm/mod.rs) — one non-streaming method on LlmProvider (mirrors suggest_tags), implemented for OllamaProvider + OpenAiCompatProvider now (Anthropic lands in M3): async fn complete(&self, system: &str, user: &str) -> Result<String, LlmError>;. S

  • [M1.3] FeatureBriefBuilder (new briefs module) — the distiller. M

    • Prompt contract (system, reuse the RESPONSE_FORMAT_INSTRUCTIONS pattern): "Distill this transcript into an implementation brief for a coding agent. Respond in Markdown with exactly, in order: ## Title (one line), ## Problem, ## Desired Outcome, ## Acceptance Criteria (a - bullet list, one testable criterion per line). Be concrete and terse; invent nothing not in the transcript; no other sections." User: build_prompt-style metadata + optional target_repo hint + "Transcript:\n" + transcript.
    • Parser parse_brief(md) -> BriefFields { title, problem, desired_outcome, acceptance_criteria } — mirror parse_summary/bullet_text; missing section → ""/[]; title fallback = meeting title.
    • context_excerpts (grounding, verbatim): tokenize problem + acceptance_criteria, score each transcript segment by keyword overlap, take the top ≤5 of length ≥ ~40 chars (fallback: first 3 substantive segments); speaker = resolved display name. // ponytail: keyword-overlap select; upgrade to embedding similarity if excerpts feel off.
  • [M1.4] Command bodies (commands.rs, replace the four stubs; keep names/args so api.ts is unchanged — add state: State<'_, AppState>, which Tauri injects). M

    • create_feature_brief(state, meeting_id: MeetingId, target_repo: Option<String>) -> WaResult<FeatureBrief> — reject the currently-recording meeting; load_settings() + llm_provider_from_settings() (error if none); load transcript via state.store; run the builder; assemble BriefFile { schema: 1, id, meeting_id, generated_at, provider, model, …fields, source }; vault::seal → write briefs/<id>.json under meeting_dir; store.insert_feature_brief(row) (exposed=false); return the IPC FeatureBrief. Write the file + row only after a successful distill (no partial artifacts on LLM failure).
    • list_feature_briefs(state, meeting_id: Option<MeetingId>) -> WaResult<Vec<FeatureBriefInfo>>
    • get_feature_brief(state, id: String) -> WaResult<FeatureBrief> (row → vault::open(path) → subset)
    • set_brief_exposed(state, id: String, exposed: bool) -> WaResult<()>
  • [M1.5] UI (SummaryPanel.svelte or a new "Feature briefs" block; existing bindings, no new client code): "Create feature brief" button + optional target-repo input; list via listFeatureBriefs(meetingId); viewer with copy-as-Markdown and copy-as-JSON; exposed toggle wired to setBriefExposed but shown as "available when the MCP server is on" until M2. M

  • [M1.6] Tests — golden transcript (see 06-test-strategy.md P10): parse_brief unit (golden reply → fields; empty/malformed → empty, no panic); builder over a golden transcript with a MockLlmProvider (no network) asserting the fields, non-empty acceptance_criteria, and the grounding invariant (every excerpt text is a verbatim substring of a transcript segment); command-level: LLM off/unreachable → Err, nothing written. S

  • Acceptance (M1 done): on a finished meeting with an LLM configured, "Create feature brief" produces a schema-valid, vault-sealed briefs/<id>.json + a feature_briefs row that lists and re-opens; excerpts are verbatim from the transcript; with the LLM off/unreachable the command errors cleanly and writes nothing partial; the golden-transcript unit tests pass. No new egress; exposed defaults off.

M2 — MCP server (the differentiator) → FR-MCP-1/2/3/5/6/7 (T10.4/10.5/10.7/10.8/10.9)

The unique capability (meeting → coding-agent handoff) and a privacy reinforcement: inbound on loopback, zero added egress.

  • [M2.1] mcp module on rmcp: loopback bind + token gate; Streamable HTTP (/mcp) + stdio adapter; implement McpServer::start/stop (replace todo!()). L → FR-MCP-1/6, NFR-SEC-5
  • [M2.2] Tools-first surface: list_recent_meetings, get_transcript, get_action_items, get_feature_brief. M → FR-MCP-2
  • [M2.3] Scope control set_mcp_scope(none|selected|all); recordings never served unless explicitly allowed; enforce in every tool handler. M → FR-MCP-3
  • [M2.4] Lifecycle: mcp_status/set_mcp_enabled (replace stubs); persist enabled/scope in settings, token in the OS credential store (never settings.json). M → FR-MCP-1
  • [M2.5] Disclosure UI ("connected agents may forward data") + mcp_access_log rows / mcp://access events on every tool read. M → FR-MCP-5
  • [M2.6] Privacy panel shows MCP state and confirms it adds no egress; extend privacy_self_check. S → FR-MCP-7, FR-SEC-2
  • Acceptance: an in-process MCP client gets a schema-valid brief; server binds loopback only (assert a non-loopback bind is refused) and requires a token; the egress test is unchanged with MCP on (merge blocker, FR-MCP-7); recordings not served unless allowed; every read logged.
  • Depends on: M1 (a served tool). Guardrail: the cross-cutting egress gate must stay green.

M3 — Hosted AI providers (finish 10a) → FR-AI-1/2/3 (T10.1/10.2/10.3)

Commodity but low-effort (OpenAI-compat already exists); gives a cloud-summary choice and pairs with M1 (a brief can be distilled by a hosted model). Adds allowlisted egress by design.

  • [M3.1] Implement AnthropicProvider (/v1/messages, x-api-key, streaming) behind LlmProvider; is_local() == false. M → FR-AI-1
  • [M3.2] set_llm_provider: store the API key in the OS credential store via credential_ref (never settings/DB); add the host to the settings-derived egress allowlist. M → FR-AI-2, NFR-SEC-4
  • [M3.3] Third-party "data leaves your device" banner + one-time acknowledgment; per-use provider selection + active-provider display wherever a summary is generated. S → FR-AI-2/3
  • Acceptance: summaries route to mocked OpenAI-compat and Anthropic endpoints with correct shapes; key read only from the credential store; host on the allowlist only when configured; banner shown before first hosted use.

M4 — Reliability & breadth (opportunistic, by value)

  • [M4.1] Chunked/resumable upload (T9.2 refinement) — top reliability item: recordings are now native-quality (~50–100 MB) and put() buffers the whole file in memory; OneDrive/Graph caps a single PUT at 250 MB. Stream from disk in chunks (Nextcloud chunked upload + Graph upload session). M → FR-SYNC-2/5
  • [M4.2] Multi-language transcription (T8.7): multilingual model option; whisper language param (select/auto); persist per-meeting language; UI localization scaffold. M → FR-TRX-4
  • [M4.3] Dropbox/Box upload targets (T9.10): implement DropboxTarget/BoxTarget SyncTarget impls (OAuth is already wired). M → FR-SYNC-9
  • [M4.4] MS Graph calendar source (T8.9) behind CalendarSource (GraphSource), consented (OAuth PKCE), metadata-only. L → FR-CAL-6 (Could) — shipped.

M5 — Push handoff (Layer 3, last & optional) → FR-AGENT-1/2 (Could) (T10.10/10.11)

Only on demand — the MCP handoff (M2) already covers the agent use case (agents pull context).

  • [M5.1] AgentRunner: spawn claude -p / codex exec / opencode run / copilot headless against a chosen repo from a brief; stream output (run_agent). L → FR-AGENT-1
  • [M5.2] IssueTracker: create a GitHub issue from a confirmed action item / brief; optional Copilot-cloud assign (create_issue_from_brief). Third-party egress: off/labeled/allowlisted. M → FR-AGENT-2

Privacy checkpoints (gate every milestone): the egress self-test stays green (FR-SEC-1); enabling MCP adds no allowlist host (FR-MCP-7); hosted AI/agent handoff adds only the explicitly configured host; all secrets (AI keys, MCP token, OAuth tokens) live only in the OS credential store (NFR-SEC-4). Plus the standing gates: cargo fmt/clippy -D warnings, Prettier/ESLint/tsc, and a docs update whenever a command/event contract changes (CLAUDE.md).