From 4cf5eb89b47a67df868fdac8b503deed756b5a7d Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 6 Jul 2026 23:19:51 -0500 Subject: [PATCH 1/4] docs(roadmap): post-v0.2.0 execution plan (Phase 10 + reliability milestones) --- docs/05-roadmap.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/docs/05-roadmap.md b/docs/05-roadmap.md index b8b2a2a..d875414 100644 --- a/docs/05-roadmap.md +++ b/docs/05-roadmap.md @@ -235,3 +235,101 @@ 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). +- **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"); MS Graph `CalendarSource`; multi-language selection; `DropboxTarget`/ + `BoxTarget` (OAuth wired in `sync/oauth.rs`, upload impl missing); chunked/resumable upload. + +**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`). +- `[M1.1]` `FeatureBriefBuilder::build(meeting_id, target_repo)`: prompt-assemble transcript + + metadata → `{problem, desired_outcome, acceptance_criteria[], target_repo, context_excerpts[]}` + via the configured provider; tolerant parse with graceful fallback on a malformed model reply. **M** +- `[M1.2]` Persist to `briefs/.json` (schema-versioned per `03-data-model.md`); implement + `create_feature_brief`/`list_feature_briefs`/`get_feature_brief` (replace the stubs). **M** +- `[M1.3]` UI: "Create feature brief" on a finished meeting; view/edit/copy; target-repo field. **M** +- `[M1.4]` `set_brief_exposed` + persisted `exposed` flag (the scope foundation M2 reads). **S** +- **Acceptance:** a golden transcript yields a schema-valid brief; it persists and re-opens; with the + LLM off/unreachable the command errors cleanly (no panic, no partial write). + +### 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]` *(Defer)* MS Graph calendar source (T8.9) behind `CalendarSource`, consented, + metadata-only. **L** → FR-CAL-6 (Could) + +### 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). From 23db4b6dac1db4d2a26c9d49d23da7eff24d132e Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 6 Jul 2026 23:27:02 -0500 Subject: [PATCH 2/4] docs(data-model): align briefs/.json schema (provenance + grounding + file-vs-IPC) --- docs/03-data-model.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/03-data-model.md b/docs/03-data-model.md index fdf9d4d..5875b19 100644 --- a/docs/03-data-model.md +++ b/docs/03-data-model.md @@ -267,13 +267,22 @@ label so re-diarization or renaming never requires rewriting every segment (FR-S ## `briefs/.json` (feature brief — ADR-0011) Agent-ready spec the MCP `get_feature_brief` tool returns. Designed to drop straight into a coding -agent's context. +agent's context. Written by `create_feature_brief` (M1); one file per brief under the meeting's +`briefs/` folder, **sealed at rest with the vault** when unlocked (T8.8), exactly like +`summary.json`. The `feature_briefs` table indexes it (id, meeting_id, title, target_repo, path, +exposed) for `list_feature_briefs` and the MCP scope check — the file is the source of truth; the +row is the index. The IPC `FeatureBrief` type (`04-api-contracts.md`) is the subset returned to the +UI / MCP: everything below **except** the `schema`/provenance envelope (`generated_at`, `provider`, +`model`, `source`). ```jsonc { "schema": 1, "id": "b7a1…", "meeting_id": "f1c2…", + "generated_at": 1751299200, // envelope: which model distilled this, and when + "provider": "ollama", + "model": "llama3", "title": "Bulk CSV export for the reporting view", "problem": "Customer can't get their data out for offline analysis.", "desired_outcome": "One-click CSV export of the current filtered report.", @@ -282,15 +291,21 @@ agent's context. "Respects active filters and column order", "Streams large exports without blocking the UI", ], - "target_repo": "acme/reporting-web", // optional hint for the agent + "target_repo": "acme/reporting-web", // optional hint the user supplies at create time "context_excerpts": [ - // minimal transcript quotes that ground the request + // verbatim transcript quotes that ground the request — NOT model paraphrase; + // the builder selects them from the real transcript (speaker = resolved display name) { "speaker": "Customer", "text": "We really need to pull this into our own spreadsheets." }, ], "source": { "meeting_title": "Acme quarterly sync", "at": 1751299200 }, } ``` +Field presence: `title`, `problem`, `desired_outcome` are always strings (the builder falls back to +the meeting title / `""` on a sparse model reply); `acceptance_criteria` and `context_excerpts` may +be empty arrays. Every `context_excerpts[].text` is a verbatim substring of a real transcript +segment (the M1 grounding invariant, asserted by the golden-transcript test). + ## `settings.json` ```jsonc From 106ffed7864f7ab63f6824f63be2cb4269710cd1 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 6 Jul 2026 23:27:03 -0500 Subject: [PATCH 3/4] docs(roadmap): implementation-ready M1 (feature briefs) checklist with signatures --- docs/05-roadmap.md | 70 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/05-roadmap.md b/docs/05-roadmap.md index d875414..208807f 100644 --- a/docs/05-roadmap.md +++ b/docs/05-roadmap.md @@ -264,16 +264,66 @@ 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`). -- `[M1.1]` `FeatureBriefBuilder::build(meeting_id, target_repo)`: prompt-assemble transcript + - metadata → `{problem, desired_outcome, acceptance_criteria[], target_repo, context_excerpts[]}` - via the configured provider; tolerant parse with graceful fallback on a malformed model reply. **M** -- `[M1.2]` Persist to `briefs/.json` (schema-versioned per `03-data-model.md`); implement - `create_feature_brief`/`list_feature_briefs`/`get_feature_brief` (replace the stubs). **M** -- `[M1.3]` UI: "Create feature brief" on a finished meeting; view/edit/copy; target-repo field. **M** -- `[M1.4]` `set_brief_exposed` + persisted `exposed` flag (the scope foundation M2 reads). **S** -- **Acceptance:** a golden transcript yields a schema-valid brief; it persists and re-opens; with the - LLM off/unreachable the command errors cleanly (no panic, no partial write). +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/.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, + 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, StoreError>;` (newest first) + - `async fn get_feature_brief_row(&self, id: &str) -> Result;` (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;`. **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) -> WaResult` + — 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/.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) -> WaResult>` + - `get_feature_brief(state, id: String) -> WaResult` (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/.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 From b40a7e2fbc12c8e50650e633ee6cd50b5bdc4510 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 6 Jul 2026 23:27:04 -0500 Subject: [PATCH 4/4] docs(test): expand M1 golden-transcript brief tests (mock provider, grounding invariant) --- docs/06-test-strategy.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/06-test-strategy.md b/docs/06-test-strategy.md index 328a7ba..ef72661 100644 --- a/docs/06-test-strategy.md +++ b/docs/06-test-strategy.md @@ -114,7 +114,14 @@ is green and the cross-cutting gates pass. requires a token, and **opens no outbound socket** — the egress test is unchanged with MCP on (FR-MCP-7, NFR-SEC-5). Recordings are not served unless `expose_recordings` is true (FR-MCP-3). - Audit: every tool call appends an `mcp_access_log` row / `mcp://access` event (FR-MCP-5). -- Unit: `FeatureBriefBuilder` produces problem/outcome/acceptance-criteria from a golden transcript. +- Unit (M1 — feature briefs, no network): (1) `parse_brief` splits a golden + `## Title/## Problem/## Desired Outcome/## Acceptance Criteria` reply into the right fields, and an + empty/malformed reply yields empty fields without panicking. (2) `FeatureBriefBuilder` over a golden + transcript, driven by a `MockLlmProvider` that returns a fixed sectioned reply, produces a + schema-valid `FeatureBrief` with non-empty `acceptance_criteria` and satisfies the **grounding + invariant**: every `context_excerpts[].text` is a verbatim substring of some transcript segment + (never model paraphrase). (3) Command-level: `create_feature_brief` with the LLM off/unreachable + returns `Err` and writes no `briefs/*.json` and no `feature_briefs` row (no partial artifacts). - (10c, when built) push: `AgentRunner` invokes a stub CLI with the brief; `IssueTracker` creates a mocked GitHub issue and (optional) Copilot assignment (FR-AGENT-1/2).