docs(roadmap): implementation-ready M1 (feature briefs) checklist with signatures
This commit is contained in:
1 parent
23db4b6dac
commit
106ffed786
1 file changed
+60
-10
+60
-10
@@ -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/<id>.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/<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
|
||||
|
||||
Reference in new issue
Block a user