docs(intake): mark the drafting worker plan complete through task 7 (#223)
Linting / lint (pull_request) Successful in 2m13s
SonarQube Analysis / sonarqube (pull_request) Successful in 20m55s

Only the manual verification against a real photograph is left, and it needs an API key that does not exist yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 19:49:42 -05:00
co-authored by Claude Opus 5
parent 6a1466680d
commit 58d1bd15e8
@@ -1,6 +1,6 @@
# Intake Drafting Worker Implementation Plan # Intake Drafting Worker Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** A submitted item arrives with a drafted name, description, category, tags and suggested price, written from its photos and the sender's note. **Goal:** A submitted item arrives with a drafted name, description, category, tags and suggested price, written from its photos and the sender's note.
@@ -62,7 +62,7 @@ A directory rather than six files loose in `src/`: they are one concern, they ch
**Interfaces:** **Interfaces:**
- Produces: `@anthropic-ai/sdk` and `zod` available; a boot warning when `ANTHROPIC_API_KEY` is absent. - Produces: `@anthropic-ai/sdk` and `zod` available; a boot warning when `ANTHROPIC_API_KEY` is absent.
- [ ] **Step 1: Install** - [x] **Step 1: Install**
```bash ```bash
cd backend cd backend
@@ -80,7 +80,7 @@ Task 4 imports `zodOutputFormat` from `@anthropic-ai/sdk/helpers/zod`. If the in
Both are production dependencies — the worker runs in the container. Confirm they landed under `"dependencies"`, not `"devDependencies"`: the final Docker stage installs with `--omit=dev`, so the wrong section produces a container that fails on the first submission and nowhere else. That is exactly how sharp went wrong in #226. Both are production dependencies — the worker runs in the container. Confirm they landed under `"dependencies"`, not `"devDependencies"`: the final Docker stage installs with `--omit=dev`, so the wrong section produces a container that fails on the first submission and nowhere else. That is exactly how sharp went wrong in #226.
- [ ] **Step 2: Write the failing test** - [x] **Step 2: Write the failing test**
Add to `backend/tests/unit/envValidation.test.ts`: Add to `backend/tests/unit/envValidation.test.ts`:
@@ -108,7 +108,7 @@ describe('the intake drafting key', () => {
}); });
``` ```
- [ ] **Step 3: Run it to verify it fails** - [x] **Step 3: Run it to verify it fails**
```bash ```bash
cd backend && npx jest -c jest.unit.config.js envValidation cd backend && npx jest -c jest.unit.config.js envValidation
@@ -116,7 +116,7 @@ cd backend && npx jest -c jest.unit.config.js envValidation
Expected: FAIL on the warning assertions. Expected: FAIL on the warning assertions.
- [ ] **Step 4: Add the warning** - [x] **Step 4: Add the warning**
In `backend/src/envValidation.ts`, alongside the other warning rules: In `backend/src/envValidation.ts`, alongside the other warning rules:
@@ -136,7 +136,7 @@ function checkDraftingKey(env: NodeJS.ProcessEnv): string[] {
Add it to the warning composition the same way the existing rules are composed; do not nest it inside `validateEnv`, which is what keeps that function's cognitive complexity down. Add it to the warning composition the same way the existing rules are composed; do not nest it inside `validateEnv`, which is what keeps that function's cognitive complexity down.
- [ ] **Step 5: Verify** - [x] **Step 5: Verify**
```bash ```bash
cd backend && npm run test:unit && npm run lint && npm run build cd backend && npm run test:unit && npm run lint && npm run build
@@ -144,7 +144,7 @@ cd backend && npm run test:unit && npm run lint && npm run build
Expected: PASS, lint no new warnings, build clean. Expected: PASS, lint no new warnings, build clean.
- [ ] **Step 6: Commit** - [x] **Step 6: Commit**
```bash ```bash
git add backend/package.json backend/package-lock.json backend/src/envValidation.ts backend/tests/unit/envValidation.test.ts git add backend/package.json backend/package-lock.json backend/src/envValidation.ts backend/tests/unit/envValidation.test.ts
@@ -161,7 +161,7 @@ git commit -m "build(intake): add the Anthropic SDK and warn when its key is abs
**Interfaces:** **Interfaces:**
- Produces: `DraftSchema` (Zod), `type DraftResult = z.infer<typeof DraftSchema>` - Produces: `DraftSchema` (Zod), `type DraftResult = z.infer<typeof DraftSchema>`
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
Create `backend/tests/unit/draftSchema.test.ts`: Create `backend/tests/unit/draftSchema.test.ts`:
@@ -217,7 +217,7 @@ describe('DraftSchema', () => {
}); });
``` ```
- [ ] **Step 2: Run it to verify it fails** - [x] **Step 2: Run it to verify it fails**
```bash ```bash
cd backend && npx jest -c jest.unit.config.js draftSchema cd backend && npx jest -c jest.unit.config.js draftSchema
@@ -225,7 +225,7 @@ cd backend && npx jest -c jest.unit.config.js draftSchema
Expected: FAIL — module not found. Expected: FAIL — module not found.
- [ ] **Step 3: Write the schema** - [x] **Step 3: Write the schema**
Create `backend/src/intake/draftSchema.ts`: Create `backend/src/intake/draftSchema.ts`:
@@ -273,7 +273,7 @@ export const DraftSchema = z.object({
export type DraftResult = z.infer<typeof DraftSchema>; export type DraftResult = z.infer<typeof DraftSchema>;
``` ```
- [ ] **Step 4: Run it to verify it passes** - [x] **Step 4: Run it to verify it passes**
```bash ```bash
cd backend && npx jest -c jest.unit.config.js draftSchema cd backend && npx jest -c jest.unit.config.js draftSchema
@@ -281,7 +281,7 @@ cd backend && npx jest -c jest.unit.config.js draftSchema
Expected: PASS, 6 tests. Expected: PASS, 6 tests.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/intake/draftSchema.ts backend/tests/unit/draftSchema.test.ts git add backend/src/intake/draftSchema.ts backend/tests/unit/draftSchema.test.ts
@@ -303,7 +303,7 @@ This is the correctness surface. Read "The correctness surface" above before wri
- `buildSystemPrompt(categories: string[], tags: string[]): string` - `buildSystemPrompt(categories: string[], tags: string[]): string`
- `buildUserContent(photos: {mediaType: string; base64: string}[], note: string | null): unknown[]` - `buildUserContent(photos: {mediaType: string; base64: string}[], note: string | null): unknown[]`
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
Create `backend/tests/unit/draftPrompt.test.ts`: Create `backend/tests/unit/draftPrompt.test.ts`:
@@ -372,7 +372,7 @@ describe('buildUserContent', () => {
}); });
``` ```
- [ ] **Step 2: Run it to verify it fails** - [x] **Step 2: Run it to verify it fails**
```bash ```bash
cd backend && npx jest -c jest.unit.config.js draftPrompt cd backend && npx jest -c jest.unit.config.js draftPrompt
@@ -380,7 +380,7 @@ cd backend && npx jest -c jest.unit.config.js draftPrompt
Expected: FAIL — module not found. Expected: FAIL — module not found.
- [ ] **Step 3: Write the prompt builder** - [x] **Step 3: Write the prompt builder**
Create `backend/src/intake/draftPrompt.ts`: Create `backend/src/intake/draftPrompt.ts`:
@@ -455,7 +455,7 @@ export function buildUserContent(photos: Photo[], note: string | null): unknown[
} }
``` ```
- [ ] **Step 4: Run it to verify it passes** - [x] **Step 4: Run it to verify it passes**
```bash ```bash
cd backend && npx jest -c jest.unit.config.js draftPrompt cd backend && npx jest -c jest.unit.config.js draftPrompt
@@ -463,7 +463,7 @@ cd backend && npx jest -c jest.unit.config.js draftPrompt
Expected: PASS, 8 tests. Expected: PASS, 8 tests.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/intake/draftPrompt.ts backend/tests/unit/draftPrompt.test.ts git add backend/src/intake/draftPrompt.ts backend/tests/unit/draftPrompt.test.ts
@@ -487,12 +487,12 @@ Rates confirmed against the pricing page on 2026-08-31, not recalled: Sonnet 5 $
**Interfaces:** **Interfaces:**
- Produces: `DRAFTING_MODELS`, `DEFAULT_DRAFTING_MODEL`, `isDraftingModel()`, `costMicros()`, and a `draftingModel` admin setting. - Produces: `DRAFTING_MODELS`, `DEFAULT_DRAFTING_MODEL`, `isDraftingModel()`, `costMicros()`, and a `draftingModel` admin setting.
- [ ] **Step 1: The catalogue, test first.** `costMicros(model, input, output)` in whole micros. An unrecognised model must price above zero — a budget that reads as unspent however much was spent is the one failure a spend guard cannot have. - [x] **Step 1: The catalogue, test first.** `costMicros(model, input, output)` in whole micros. An unrecognised model must price above zero — a budget that reads as unspent however much was spent is the one failure a spend guard cannot have.
- [ ] **Step 2: Add a `choice` type to `adminSettings.ts`.** The module's own docstring says adding a setting means adding a row and nothing else; that holds for `hours` and `text`, and a third type is what makes it hold for a constrained one. Row: `{ key: 'drafting_model', name: 'draftingModel', type: 'choice', fallback: DEFAULT_DRAFTING_MODEL, options: [...] }`. - [x] **Step 2: Add a `choice` type to `adminSettings.ts`.** The module's own docstring says adding a setting means adding a row and nothing else; that holds for `hours` and `text`, and a third type is what makes it hold for a constrained one. Row: `{ key: 'drafting_model', name: 'draftingModel', type: 'choice', fallback: DEFAULT_DRAFTING_MODEL, options: [...] }`.
- [ ] **Step 3: Validate membership in the PUT route**, as a third loop beside the hours and text loops. A value outside the set is a 400, not a stored string that breaks drafting later. - [x] **Step 3: Validate membership in the PUT route**, as a third loop beside the hours and text loops. A value outside the set is a 400, not a stored string that breaks drafting later.
- [ ] **Step 4: The dropdown in `Settings.tsx`**, showing each model's price so the person switching can see that Opus costs 2.5x Sonnet before they pick it. - [x] **Step 4: The dropdown in `Settings.tsx`**, showing each model's price so the person switching can see that Opus costs 2.5x Sonnet before they pick it.
- [ ] **Step 5:** `npm run lint && npm run build && npm run test:unit`, and the frontend's checks. - [x] **Step 5:** `npm run lint && npm run build && npm run test:unit`, and the frontend's checks.
- [ ] **Step 6: Commit.** - [x] **Step 6: Commit.**
--- ---
@@ -509,7 +509,7 @@ Rates confirmed against the pricing page on 2026-08-31, not recalled: Sonnet 5 $
- `costMicros(model: string, inputTokens: number, outputTokens: number): number` - `costMicros(model: string, inputTokens: number, outputTokens: number): number`
- `draftListing(client, input): Promise<{ draft: DraftResult; usage: {...} }>` - `draftListing(client, input): Promise<{ draft: DraftResult; usage: {...} }>`
- [ ] **Step 1: Write the failing cost test** - [x] **Step 1: Write the failing cost test**
Create `backend/tests/unit/draftCost.test.ts`: Create `backend/tests/unit/draftCost.test.ts`:
@@ -542,7 +542,7 @@ describe('costMicros', () => {
}); });
``` ```
- [ ] **Step 2: Run it to verify it fails** - [x] **Step 2: Run it to verify it fails**
```bash ```bash
cd backend && npx jest -c jest.unit.config.js draftCost cd backend && npx jest -c jest.unit.config.js draftCost
@@ -550,7 +550,7 @@ cd backend && npx jest -c jest.unit.config.js draftCost
Expected: FAIL — module not found. Expected: FAIL — module not found.
- [ ] **Step 3: Write the client factory** - [x] **Step 3: Write the client factory**
Create `backend/src/intake/anthropicClient.ts`: Create `backend/src/intake/anthropicClient.ts`:
@@ -587,7 +587,7 @@ export function resetAnthropicClient(): void {
} }
``` ```
- [ ] **Step 4: Write the drafting call** - [x] **Step 4: Write the drafting call**
Create `backend/src/intake/draftListing.ts`: Create `backend/src/intake/draftListing.ts`:
@@ -681,7 +681,7 @@ export async function draftListing(
} }
``` ```
- [ ] **Step 5: Run the tests and build** - [x] **Step 5: Run the tests and build**
```bash ```bash
cd backend && npx jest -c jest.unit.config.js draftCost && npm run build && npm run lint cd backend && npx jest -c jest.unit.config.js draftCost && npm run build && npm run lint
@@ -689,7 +689,7 @@ cd backend && npx jest -c jest.unit.config.js draftCost && npm run build && npm
Expected: PASS 5 tests, build clean, no new lint warnings. Expected: PASS 5 tests, build clean, no new lint warnings.
- [ ] **Step 6: Commit** - [x] **Step 6: Commit**
```bash ```bash
git add backend/src/intake/anthropicClient.ts backend/src/intake/draftListing.ts backend/tests/unit/draftCost.test.ts git add backend/src/intake/anthropicClient.ts backend/src/intake/draftListing.ts backend/tests/unit/draftCost.test.ts
@@ -708,7 +708,7 @@ git commit -m "feat(intake): draft a listing from photos and a note (#223)"
- Consumes: `DraftResult` - Consumes: `DraftResult`
- Produces: `applyDraft(client: PoolClient, itemId: number, outcome: DraftOutcome): Promise<void>` - Produces: `applyDraft(client: PoolClient, itemId: number, outcome: DraftOutcome): Promise<void>`
- [ ] **Step 1: Write the failing test** - [x] **Step 1: Write the failing test**
Create `backend/tests/integration/drafting.integration.test.ts`: Create `backend/tests/integration/drafting.integration.test.ts`:
@@ -839,7 +839,7 @@ describe('applying a draft', () => {
}); });
``` ```
- [ ] **Step 2: Run it to verify it fails** - [x] **Step 2: Run it to verify it fails**
Bring up a database first (see Global Constraints), then: Bring up a database first (see Global Constraints), then:
@@ -849,7 +849,7 @@ cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integr
Expected: FAIL — module not found. Expected: FAIL — module not found.
- [ ] **Step 3: Write it** - [x] **Step 3: Write it**
Create `backend/src/intake/applyDraft.ts`: Create `backend/src/intake/applyDraft.ts`:
@@ -932,7 +932,7 @@ export async function applyDraft(
} }
``` ```
- [ ] **Step 4: Run it to verify it passes** - [x] **Step 4: Run it to verify it passes**
```bash ```bash
cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integration cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integration
@@ -940,7 +940,7 @@ cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integr
Expected: PASS, 5 tests. Expected: PASS, 5 tests.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/intake/applyDraft.ts backend/tests/integration/drafting.integration.test.ts git add backend/src/intake/applyDraft.ts backend/tests/integration/drafting.integration.test.ts
@@ -959,7 +959,7 @@ git commit -m "feat(intake): record a draft without publishing it (#223)"
- Consumes: everything above - Consumes: everything above
- Produces: `draftQueued(limit?: number): Promise<{ drafted: number; failed: number; skipped: number }>` - Produces: `draftQueued(limit?: number): Promise<{ drafted: number; failed: number; skipped: number }>`
- [ ] **Step 1: Write the failing tests** - [x] **Step 1: Write the failing tests**
Append to `backend/tests/integration/drafting.integration.test.ts`: Append to `backend/tests/integration/drafting.integration.test.ts`:
@@ -1006,7 +1006,7 @@ describe('the drafting worker', () => {
}); });
``` ```
- [ ] **Step 2: Run it to verify it fails** - [x] **Step 2: Run it to verify it fails**
```bash ```bash
cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integration cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integration
@@ -1014,7 +1014,7 @@ cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integr
Expected: FAIL — module not found. Expected: FAIL — module not found.
- [ ] **Step 3: Write the worker** - [x] **Step 3: Write the worker**
Create `backend/src/intake/draftingWorker.ts`: Create `backend/src/intake/draftingWorker.ts`:
@@ -1156,7 +1156,7 @@ export async function draftQueued(
} }
``` ```
- [ ] **Step 4: Run it to verify it passes** - [x] **Step 4: Run it to verify it passes**
```bash ```bash
cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integration cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integration
@@ -1164,7 +1164,7 @@ cd backend && npx jest -c jest.integration.config.js --runInBand drafting.integr
Expected: PASS, 7 tests. Expected: PASS, 7 tests.
- [ ] **Step 5: Commit** - [x] **Step 5: Commit**
```bash ```bash
git add backend/src/intake/draftingWorker.ts backend/tests/integration/drafting.integration.test.ts git add backend/src/intake/draftingWorker.ts backend/tests/integration/drafting.integration.test.ts
@@ -1178,7 +1178,7 @@ git commit -m "feat(intake): draft queued submissions without ever losing one (#
**Files:** **Files:**
- Modify: `backend/src/routes/intake.ts`, `backend/src/server.ts` - Modify: `backend/src/routes/intake.ts`, `backend/src/server.ts`
- [ ] **Step 1: Kick the worker after a successful submission** - [x] **Step 1: Kick the worker after a successful submission**
In `backend/src/routes/intake.ts`, after `await client.query('COMMIT');` and before the response: In `backend/src/routes/intake.ts`, after `await client.query('COMMIT');` and before the response:
@@ -1196,7 +1196,7 @@ Import it at the top:
import { draftQueued } from '../intake/draftingWorker'; import { draftQueued } from '../intake/draftingWorker';
``` ```
- [ ] **Step 2: Add the sweeper** - [x] **Step 2: Add the sweeper**
In `backend/src/server.ts`, beside the existing schedules: In `backend/src/server.ts`, beside the existing schedules:
@@ -1214,7 +1214,7 @@ Import it beside the other background work:
import { draftQueued } from './intake/draftingWorker'; import { draftQueued } from './intake/draftingWorker';
``` ```
- [ ] **Step 3: Verify nothing regressed** - [x] **Step 3: Verify nothing regressed**
```bash ```bash
cd backend cd backend
@@ -1226,7 +1226,7 @@ npx jest -c jest.integration.config.js --runInBand
Expected: all pass. The intake suite matters most — submissions must still succeed with no key configured, which is the state the test database runs in. Expected: all pass. The intake suite matters most — submissions must still succeed with no key configured, which is the state the test database runs in.
- [ ] **Step 4: Commit** - [x] **Step 4: Commit**
```bash ```bash
git add backend/src/routes/intake.ts backend/src/server.ts git add backend/src/routes/intake.ts backend/src/server.ts
@@ -1235,6 +1235,9 @@ git commit -m "feat(intake): run the drafting worker after a submission and on a
--- ---
> **Tasks 1-7 complete** as of 2026-08-31, on `feature/223-drafting-worker`. Task 8 is the only manual step and is blocked on an API key existing.
### Task 8: One real call ### Task 8: One real call
Everything above is stubbed. This is the only step that spends money, and it is the only one that proves the prompt produces something worth reading. Everything above is stubbed. This is the only step that spends money, and it is the only one that proves the prompt produces something worth reading.