docs(ai): mark plan tasks done
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1f111b80db
commit
0ff8e6a5de
@@ -44,8 +44,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- `createSecretBox(secret: string): { encrypt(plain: string): Promise<string>; decrypt(token: string): Promise<string> }`. HKDF-SHA-256 (salt `"stcourses/byok"`, info `"aes-gcm-256/v1"`) → AES-GCM-256; token `v1.<b64 iv>.<b64 ciphertext>`.
|
||||
- `migrationsDirs: string[]` (empty: the package adds no tables).
|
||||
|
||||
- [ ] Tests: round trip; two encryptions differ (random IV); a box with another secret rejects; a tampered token rejects.
|
||||
- [ ] Implement; commit `feat(ai): package scaffold and AES-GCM secret box`.
|
||||
- [x] Tests: round trip; two encryptions differ (random IV); a box with another secret rejects; a tampered token rejects.
|
||||
- [x] Implement; commit `feat(ai): package scaffold and AES-GCM secret box`.
|
||||
|
||||
### Task 2: Settings repository
|
||||
|
||||
@@ -59,8 +59,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- `getUser(userId): Promise<{ byok: (ProviderConfig & { hasApiKey }) | null; mentorOff: Record<courseId, boolean> }>`, `saveUserKey(userId, cfg & { apiKey })`, `removeUserKey(userId)`, `userApiKey(userId, kind)`, `setMentorOff(userId, courseId, off)`, `isMentorOff(userId, courseId)`.
|
||||
- Storage: `ai_settings` row `('server','')` holds `{ provider, apiKeyCipher, mcpServers }`; row `('user', userId)` holds `{ byok, mentorOff }`. The per-course flag lives in `config_json` because the frozen primary key is `(scope, owner_id)`; the `mentor_off` column mirrors "off for every course" (always 0 for now). Contract request filed. Keys go in `user_api_keys` encrypted. Upserts are select-then-insert-or-update in a transaction.
|
||||
|
||||
- [ ] Tests (both DBs): empty server config is `null` provider; saving a key stores ciphertext only (raw row does not contain the key) and `serverApiKey()` returns it; omitting `apiKey` on save keeps the old key; BYOK save/remove; mentor toggle per course is independent.
|
||||
- [ ] Implement; commit `feat(ai): settings repository for server config, BYOK keys and mentor toggles`.
|
||||
- [x] Tests (both DBs): empty server config is `null` provider; saving a key stores ciphertext only (raw row does not contain the key) and `serverApiKey()` returns it; omitting `apiKey` on save keeps the old key; BYOK save/remove; mentor toggle per course is independent.
|
||||
- [x] Implement; commit `feat(ai): settings repository for server config, BYOK keys and mentor toggles`.
|
||||
|
||||
### Task 3: SSE parsing and the Anthropic provider
|
||||
|
||||
@@ -74,8 +74,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- The provider remembers each response's full content blocks (thinking with signature, text, tool_use) by tool_use id and replays them unchanged for the matching assistant tool-call messages.
|
||||
- Streams `{ text }` per `text_delta`, `{ toolCall }` per finished `tool_use` block; HTTP errors and `error` events throw with the API message; `stop_reason: "refusal"` yields a plain sentence.
|
||||
|
||||
- [ ] Tests with a fake `fetch` that returns canned SSE: text streams in order; a tool_use with split `input_json_delta` yields one parsed toolCall; request body has model, system, tools as `input_schema`, no `thinking`/`temperature`; a second call after a tool round replays the thinking block with its signature unchanged before `tool_result`; non-2xx throws with the error message.
|
||||
- [ ] Implement; commit `feat(ai): Anthropic provider over fetch with streaming tool use`.
|
||||
- [x] Tests with a fake `fetch` that returns canned SSE: text streams in order; a tool_use with split `input_json_delta` yields one parsed toolCall; request body has model, system, tools as `input_schema`, no `thinking`/`temperature`; a second call after a tool round replays the thinking block with its signature unchanged before `tool_result`; non-2xx throws with the error message.
|
||||
- [x] Implement; commit `feat(ai): Anthropic provider over fetch with streaming tool use`.
|
||||
|
||||
### Task 4: OpenAI-compatible provider
|
||||
|
||||
@@ -85,8 +85,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- `createOpenAiProvider({ baseUrl, apiKey?, model, fetch? }): AiProvider`. POST `<baseUrl>/chat/completions`, `stream: true`, tools as `{ type: "function", function: { name, description, parameters } }`. Assistant tool-call messages map to `tool_calls`; `tool` messages to `{ role: "tool", tool_call_id }`. Accumulates `delta.tool_calls` by index and emits on finish or `[DONE]`.
|
||||
- `createProvider(cfg: ProviderConfig, apiKey: string | null, fetchImpl?): AiProvider`.
|
||||
|
||||
- [ ] Tests: text deltas; fragmented tool-call arguments; no `Authorization` header without a key (Ollama); request mapping of a tool round.
|
||||
- [ ] Implement; commit `feat(ai): OpenAI-compatible provider (covers Ollama)`.
|
||||
- [x] Tests: text deltas; fragmented tool-call arguments; no `Authorization` header without a key (Ollama); request mapping of a tool round.
|
||||
- [x] Implement; commit `feat(ai): OpenAI-compatible provider (covers Ollama)`.
|
||||
|
||||
### Task 5: Retrieval
|
||||
|
||||
@@ -100,8 +100,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- Sources: every `.md` under the course root (depth-limited, skipping `.git`, `node_modules`), manifest code dirs and common code extensions (size cap 200 KB per file, 2 000 files per course), `.md/.txt/.vtt/.srt/.json` text under `<dataDir>/cache/<courseId>/`. Markdown splits at headings; long chunks split at ~1 500 chars. Paths are mapped to lessons through `file`, `transcript` and `hints.file` (anchor heading when it matches).
|
||||
- Never indexed: `browser.solution` files and any path segment `solutions`, `solution`, `reference` under `expansion/`, or `reference/` in Systemic-Design style modules (see `isSolutionPath`).
|
||||
|
||||
- [ ] Tests on the `sample` fixture: "sum of its arguments" hits `lessons/04-exercise.md` with lessonId `m2-text` or `m2-ex-add`; `exercises/add/solution.js` never appears; a deck text file in a temp `cache/sample/decks/d1/page-1.txt` is found with kind `deck`; note chunks passed as `extra` rank.
|
||||
- [ ] Implement; commit `feat(ai): BM25 course retrieval over lessons, transcripts, code, deck text and notes`.
|
||||
- [x] Tests on the `sample` fixture: "sum of its arguments" hits `lessons/04-exercise.md` with lessonId `m2-text` or `m2-ex-add`; `exercises/add/solution.js` never appears; a deck text file in a temp `cache/sample/decks/d1/page-1.txt` is found with kind `deck`; note chunks passed as `extra` rank.
|
||||
- [x] Implement; commit `feat(ai): BM25 course retrieval over lessons, transcripts, code, deck text and notes`.
|
||||
|
||||
### Task 6: MCP client
|
||||
|
||||
@@ -112,8 +112,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- JSON-RPC 2.0: `initialize` (protocolVersion `2025-06-18`), `notifications/initialized`, `tools/list`, `tools/call`. stdio = newline-delimited JSON over `Bun.spawn`; streamable HTTP = POST with `Accept: application/json, text/event-stream`, JSON or SSE replies, `Mcp-Session-Id` echoed.
|
||||
- `mcpServersFor(courseId, servers)` selects global servers plus ones listing the course.
|
||||
|
||||
- [ ] Tests: the fake server over stdio lists `echo` and `tools/call` returns its text; the same over HTTP (fake via `Bun.serve` on port 0, replying with SSE for `tools/call`) with the session id echoed; a server that exits gives a rejected promise, not a hang.
|
||||
- [ ] Implement; commit `feat(ai): MCP client over stdio and streamable HTTP`.
|
||||
- [x] Tests: the fake server over stdio lists `echo` and `tools/call` returns its text; the same over HTTP (fake via `Bun.serve` on port 0, replying with SSE for `tools/call`) with the session id echoed; a server that exits gives a rejected promise, not a hang.
|
||||
- [x] Implement; commit `feat(ai): MCP client over stdio and streamable HTTP`.
|
||||
|
||||
### Task 7: Assistant core
|
||||
|
||||
@@ -125,8 +125,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- `runAssistant({ provider, messages, tools, signal, maxSteps = 6 }): AsyncIterable<AssistantEvent>` where `AssistantEvent = { type: "text", text } | { type: "tool", name } | { type: "error", message } | { type: "done", text }`. Tool calls become assistant messages with `toolCallId`/`name` and `tool` result messages; unknown tools and tool errors are fed back as error text.
|
||||
- Citations: `citationsFor(chunks): { path, lessonId?, lessonTitle?, kind }[]` deduplicated.
|
||||
|
||||
- [ ] Tests: mentor on puts the sample `MENTOR.md` text in the system prompt; mentor off omits it and states full answers; a fake provider that calls `search_course` then answers gets the tool result and the loop ends with text; `read_lesson` on the exercise returns markdown; solution paths are refused.
|
||||
- [ ] Implement; commit `feat(ai): assistant prompt, course tools and tool loop`.
|
||||
- [x] Tests: mentor on puts the sample `MENTOR.md` text in the system prompt; mentor off omits it and states full answers; a fake provider that calls `search_course` then answers gets the tool result and the loop ends with text; `read_lesson` on the exercise returns markdown; solution paths are refused.
|
||||
- [x] Implement; commit `feat(ai): assistant prompt, course tools and tool loop`.
|
||||
|
||||
### Task 8: Generator and draft sink
|
||||
|
||||
@@ -140,8 +140,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- `checkHouseStyle(req, files): string[]` warnings for missing beats/tiers.
|
||||
- `generateDraft({ provider, req, course, ctx, sink, authorId }): Promise<{ location, paths, warnings }>`; every Markdown file starts with a draft marker comment.
|
||||
|
||||
- [ ] Tests: prompt for each style contains its beats; parse handles two files and rejects `../x`; fake provider output written by the fs sink lands under `<dataDir>/drafts/sample/…` and nothing changes in the course folder; missing tiers give warnings.
|
||||
- [ ] Implement; commit `feat(ai): house-style generator writing drafts through a DraftSink`.
|
||||
- [x] Tests: prompt for each style contains its beats; parse handles two files and rejects `../x`; fake provider output written by the fs sink lands under `<dataDir>/drafts/sample/…` and nothing changes in the course folder; missing tiers give warnings.
|
||||
- [x] Implement; commit `feat(ai): house-style generator writing drafts through a DraftSink`.
|
||||
|
||||
### Task 9: Feature routes and jobs
|
||||
|
||||
@@ -157,8 +157,8 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- `GET /api/ai/studio/sources?courseId=`, `POST /api/ai/studio/generate` (enqueues job `ai.generate`), `GET /api/ai/studio/jobs/:id`.
|
||||
- Job `ai.generate` runs `generateDraft` with the requesting user's provider.
|
||||
|
||||
- [ ] Tests through `createApp` with a fake `AuthService`: status disabled by default; admin config hides the key; non-owner gets 403; BYOK enables the assistant for that user only; chat with a fake provider streams text and `meta.citations` include `lessons/04-exercise.md`; system prompt holds mentor rules, and not after `PUT mentor {off:true}`; anonymous on a restricted course gets 404; generate as a learner gets 403; the job handler writes drafts under `<dataDir>/drafts/sample`.
|
||||
- [ ] Implement; commit `feat(ai): HTTP routes, chat streaming and generation job`.
|
||||
- [x] Tests through `createApp` with a fake `AuthService`: status disabled by default; admin config hides the key; non-owner gets 403; BYOK enables the assistant for that user only; chat with a fake provider streams text and `meta.citations` include `lessons/04-exercise.md`; system prompt holds mentor rules, and not after `PUT mentor {off:true}`; anonymous on a restricted course gets 404; generate as a learner gets 403; the job handler writes drafts under `<dataDir>/drafts/sample`.
|
||||
- [x] Implement; commit `feat(ai): HTTP routes, chat streaming and generation job`.
|
||||
|
||||
### Task 10: Web UI
|
||||
|
||||
@@ -169,10 +169,10 @@ Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain
|
||||
- Admin: provider kind, model, base URL, write-only key, MCP server rows. Account: BYOK form and remove. Generate: course, kind, style, sources checklist, topic, instructions, job progress, list of draft files with location.
|
||||
- `readNdjson(stream)` client helper.
|
||||
|
||||
- [ ] Tests: `readNdjson` splits across chunk boundaries; `renderToString` of the banner component shows the exact sentence when off and nothing when on.
|
||||
- [ ] Implement, check in headless Chrome via `packages/feature-ai/scripts/dev.ts` on port 3464; commit `feat(ai): assistant panel, AI settings and studio generator UI`.
|
||||
- [x] Tests: `readNdjson` splits across chunk boundaries; `renderToString` of the banner component shows the exact sentence when off and nothing when on.
|
||||
- [x] Implement, check in headless Chrome via `packages/feature-ai/scripts/dev.ts` on port 3464; commit `feat(ai): assistant panel, AI settings and studio generator UI`.
|
||||
|
||||
### Task 11: Contract requests and report
|
||||
|
||||
- [ ] `docs/contract-requests/ai.md`: feature factories in `startServer`; tool-call fields on `ChatMessage`; per-course `ai_settings` key; studio `createDraftWriter` wiring; cache layout for deck text and transcripts; learner lesson URL.
|
||||
- [ ] Run `bun test`, `bun run typecheck`, the Postgres run; write `wp-ai-report.md`.
|
||||
- [x] `docs/contract-requests/ai.md`: feature factories in `startServer`; tool-call fields on `ChatMessage`; per-course `ai_settings` key; studio `createDraftWriter` wiring; cache layout for deck text and transcripts; learner lesson URL.
|
||||
- [x] Run `bun test`, `bun run typecheck`, the Postgres run; write `wp-ai-report.md`.
|
||||
|
||||
Reference in New Issue
Block a user