Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@AGENTS.md

## Claude Code only
- **Desktop app:** `.claude/launch.json` runs the app for the Browser pane: `app` with the fakes, `app-real` with the chat on the real model, which spends neurons (`AGENTS.md`, Boundaries), both on port 3000, and `app-build` with the fakes on port 3200. After a UI change, check the page there once before you finish: with `app-build` in a build-lane worktree, with `app` otherwise (`docs/plan.md`, Now, the lanes). Never `app-real` outside the measurement lane. A session that started in the main checkout and made its worktree afterwards gets `app-build` started in the main checkout, on its code (its log lists `.env.development.local`, which a worktree hasn't got): run `npm run dev -- --port 3200` in the background from the worktree instead, open `http://localhost:3200` with `preview_start`'s `url`, and stop the server afterwards (found 2026-10-20). Stopping that background command can leave Next's own server process listening on 3200: check the port, and end that process too. That `next dev` also writes its block into `AGENTS.md` (KI-8): undo it before you commit.
- **Desktop app:** `.claude/launch.json` runs the app for the Browser pane: `app` with the fakes, `app-real` with the chat on the real model, which spends neurons (`AGENTS.md`, Boundaries), both on port 3000, and `app-build` with the fakes on port 3200. After a UI change, check the page there once before you finish: with `app-build` in a build-lane worktree, with `app` otherwise (`docs/plan.md`, Now, the lanes). Never `app-real` outside the measurement lane. `preview_start` runs `app-build` in the folder the session started in: in a worktree entered later, it served the main checkout’s code (found 2026-10-20). Check the server’s folder, or start `npm run dev -- --port 3200` from the worktree and open its address with `preview_start`’s `url`. A session that started in the main checkout and made its worktree afterwards gets `app-build` started in the main checkout, on its code (its log lists `.env.development.local`, which a worktree hasn't got): run `npm run dev -- --port 3200` in the background from the worktree instead, open `http://localhost:3200` with `preview_start`'s `url`, and stop the server afterwards (found 2026-10-20). Stopping that background command can leave Next's own server process listening on 3200: check the port, and end that process too. That `next dev` also writes its block into `AGENTS.md` (KI-8): undo it before you commit.
- **Worktree sessions** (desktop worktree option or `claude --worktree`): run `npm ci` first. `.worktreeinclude` copies `.env.local` in for you, not `.env.development.local`, so `npm run dev:real` works in the main checkout only, unless the owner copies that file in by hand. A worktree made with `git worktree add` and entered afterwards gets no copy: run `npm run env:local` with the local stack up, or the chat refuses every request for want of `AI_PROVIDER` (found 2026-10-20, as 24 failed end-to-end tests).
1 change: 1 addition & 0 deletions KNOWN-ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ What happens and how to reproduce it. What's known about the cause, with guesses
**Found:** 2026-10-20 · **Severity:** low
In one run during slice 11, and not on the run before or after: `tests/e2e/prd.spec.ts`, "reads Drafting while it works, and comes back with a line beside it when the answer fails" (since slice 13, "says Drafting beside the next section while it works, …"), waited past its 5 seconds with the fake model's answer stuck after the saved section. It passed five times in a row on its own and in the full suites afterwards. A guess: it waits on the fake model's timing, which six workers on a busy machine stretch. Fixing it means finding what it waits on and waiting for that condition instead; if it fails again, note the run here. (The unit test that failed with it was a test reading storage too early, fixed with slice 12; `tests/AGENTS.md`, *Writing tests*.)
Again during slice 14, in two full runs of five: 2 and then 3 tests (`prd.spec.ts`'s pop-over tests, "Start over asks once…", "follows the newest message…", `quota.spec.ts`'s "the chat and the PRD so far stay readable…"), each in `draftFirstSection` or its like, waiting past 5 seconds for the first draft's answer to end while the fake model was still streaming its reply. The same tests passed twice each on their own, and the three full runs after them passed, one of them without slice 14's change to the chat. So the wait is the fake model's answer under six workers, not what the page does.
Again during slice 15 (2026-10-20), in full runs back to back on the finished code: one failed 2 tests (not recorded), one passed all 59, one failed 4: `layout.spec.ts`'s three Jump to latest tests and `prd.spec.ts`'s "follows the newest message…", each waiting past 5 seconds for a first draft's answer while the fake model was still at a search or the check. One of them, "offers Jump to latest once opened searches push…", now drafts after three searches (`FAKE_SEARCH_AGAIN`), a longer answer, so it has less room in that wait.

## KI-8 · `next dev` adds its own block to `AGENTS.md`
**Found:** 2026-10-20 · **Severity:** low
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PRD Assistant is a web app being built to turn research notes into a product req
## Status

- **Planning:** done. The [product description](docs/product/prd.md) and the [scope](docs/product/mvp.md) are approved.
- **Build:** in progress. Six fictional interviews are split into passages, embedded and stored in Postgres, and listed on a transcripts page. On the home page, a streaming chat drafts the PRD with you, section by section, from the interviews: the model decides when to search them, and each search shows as a line that opens to its passages. The model saves each section it drafts, and the app's own code checks every quote before you see it. Beside the chat, the PRD builds up as a document: each statement names its sources, a click on a name opens the quoted words in the full answer, and whatever the check objects to is written out under the statement. The home page opens on a short intro that makes you the product manager, and its one button drafts the first section; once that's saved, a suggested message invites you to try to break it.
- **Build:** in progress. Six fictional interviews are split into passages, embedded and stored in Postgres, and listed on a transcripts page. On the home page, a streaming chat drafts the PRD with you, section by section, from the interviews: the model decides when to search them, and each search shows as one line naming whose passages came back, with searches in a row folded into one. The model saves each section it drafts, and the app's own code checks every quote before you see it. Beside the chat, the PRD builds up as a document: each statement names its sources, a click on a name opens the quoted words in the full answer, and whatever the check objects to is written out under the statement. The home page opens on a short intro that makes you the product manager, and its one button drafts the first section; once that's saved, a suggested message invites you to try to break it.
- **Demo:** none yet.

## How it works
Expand All @@ -26,7 +26,7 @@ Locally and in CI, a fake embedder stands in for Cloudflare. It counts shared wo

The chat runs on the [Vercel AI SDK](https://ai-sdk.dev). The browser's `useChat` posts the conversation to [`/api/chat`](src/app/api/chat/route.ts), and the server streams the answer back ([`chat.ts`](src/server/chat.ts)). Each model reply arrives whole, though: streamed, Cloudflare sends gpt-oss's search requests as reasoning text without the tool's name, so the app asks for whole replies and passes each one on as a single piece ([`cloudflare.ts`](src/server/ai/cloudflare.ts)). The search line still appears before the answer.

1. **Tool calling.** `streamText` runs OpenAI's open-weight `gpt-oss-120b` on Cloudflare Workers AI, through its OpenAI-compatible endpoint, with two tools: `searchNotes`, and `saveSection` for the citation check below. The model decides when to search and what for; the app's code runs the search above and hands back the five closest passages. The model sees each passage's interviewee, question and answer. The browser gets the same passages to show, and each search appears as one line, such as "closest passages from Maya, Henk and Daan", that opens to them. After three searches the app stops offering the search, so the model has to save or write: asked for a section, gpt-oss kept searching until it ran out of model calls. Once the section is saved, it offers no tools at all, so the model replies, and the last call of an answer never carries tools. Each call may write up to 4,096 tokens, reasoning included; Cloudflare's default of 256 cut drafts off.
1. **Tool calling.** `streamText` runs OpenAI's open-weight `gpt-oss-120b` on Cloudflare Workers AI, through its OpenAI-compatible endpoint, with two tools: `searchNotes`, and `saveSection` for the citation check below. The model decides when to search and what for; the app's code runs the search above and hands back the five closest passages. The model sees each passage's interviewee, question and answer. The browser gets the same passages, and each search appears as one line naming whose they are, such as "Search “offline use” · Maya, Henk, Daan". After three searches the app stops offering the search, so the model has to save or write: asked for a section, gpt-oss kept searching until it ran out of model calls. Once the section is saved, it offers no tools at all, so the model replies, and the last call of an answer never carries tools. Each call may write up to 4,096 tokens, reasoning included; Cloudflare's default of 256 cut drafts off.
2. **Failures stay visible.** Whatever breaks, the provider before its first word, the provider partway through, or the connection, the stream ends with a plain error, and the cause is logged on the server. So does an answer the model ends without writing anything, or one cut off at its output limit. An answer that broke off stays on screen marked as unfinished. "Try again" asks again, and the unfinished answer never goes back to the model as if it were whole. Two refusals from Cloudflare get their own message. Once the day's free allocation is used up, the app doesn't retry: it hides the message box and says when the allocation resets (00:00 UTC, also in the visitor's own time), and the chat and the PRD stay readable. When Cloudflare is busy or at its per-minute limit, the app retries twice within a few seconds, then says the demo is busy and to try again in a minute.
3. **Token awareness.** The server logs the tokens every answer used, summed over its model calls, including answers that broke off. The model reasons before it answers; that reasoning stays on the server and never goes back to the model with later messages. Searches from earlier answers go back to the model as passage ids and interviewees only, so a long conversation doesn't send every passage it ever found with each new message; to quote one again, the model searches again.
4. **The instructions.** The system prompt is the PRD prompt from my [AI Product Development Toolkit](https://github.com/TechNomadCode/AI-Product-Development-Toolkit), adapted and versioned in [`system-prompt.ts`](src/server/prompts/system-prompt.ts). It keeps the template's role, its check-in before each new section and its nine sections. Where the template asks questions before writing anything, the assistant drafts first from the interviews and asks only what they don't say. On top come the traceability rules of the toolkit's research prompt (every statement traces to a passage or to your own message, interpretations are marked as such, unsupported assumptions become open questions), saving each section for the check, the citation format, how to show a contradiction, and a rule never to invent. Every model call sends the whole prompt, about 1,775 tokens. Any change to the text needs a new version: a test compares the text's fingerprint with the one recorded for its version.
Expand Down
Loading
Loading