diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 52adea7..18549cd 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -69,7 +69,7 @@ **Milestone Goal:** Transform GearBox from a login-first tool into a public-first discovery platform with always-on catalog search and a browsable feed of community content. - [x] **Phase 24: Public Access & Infrastructure** - Remove the login wall from read-only routes and add rate limiting to public endpoints (completed 2026-04-10) -- [ ] **Phase 25: Catalog Enrichment & Agent Tools** - Add attribution fields to global items, bulk import API, and MCP tools for agent-powered seeding +- [x] **Phase 25: Catalog Enrichment & Agent Tools** - Add attribution fields to global items, bulk import API, and MCP tools for agent-powered seeding (completed 2026-04-10) - [ ] **Phase 26: Discovery Landing Page** - Replace the dashboard with a public-first landing page featuring catalog search and community feed ## Phase Details @@ -149,7 +149,7 @@ Plans: | 22. Add-from-Catalog & Thread Integration | v2.0 | 2/2 | Complete | 2026-04-06 | | 23. Manual Entry Fallback | v2.0 | 1/1 | Complete | 2026-04-06 | | 24. Public Access & Infrastructure | v2.1 | 2/2 | Complete | 2026-04-10 | -| 25. Catalog Enrichment & Agent Tools | v2.1 | 1/2 | In Progress| | +| 25. Catalog Enrichment & Agent Tools | v2.1 | 1/2 | Complete | 2026-04-10 | | 26. Discovery Landing Page | v2.1 | 0/TBD | Not started | - | ## Backlog diff --git a/.planning/STATE.md b/.planning/STATE.md index c8c4796..eae3b0a 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,13 +4,13 @@ milestone: v2.1 milestone_name: Public Discovery status: verifying stopped_at: Completed 25-02-PLAN.md -last_updated: "2026-04-10T09:07:33.638Z" +last_updated: "2026-04-10T09:13:14.646Z" last_activity: 2026-04-10 progress: total_phases: 6 - completed_phases: 1 + completed_phases: 2 total_plans: 4 - completed_plans: 3 + completed_plans: 4 percent: 0 --- @@ -25,8 +25,8 @@ See: .planning/PROJECT.md (updated 2026-04-09) ## Current Position -Phase: 25 (catalog-enrichment-agent-tools) — EXECUTING -Plan: 2 of 2 +Phase: 999.1 +Plan: Not started Status: Phase complete — ready for verification Last activity: 2026-04-10 diff --git a/.planning/phases/25-catalog-enrichment-agent-tools/25-VERIFICATION.md b/.planning/phases/25-catalog-enrichment-agent-tools/25-VERIFICATION.md new file mode 100644 index 0000000..02b3e74 --- /dev/null +++ b/.planning/phases/25-catalog-enrichment-agent-tools/25-VERIFICATION.md @@ -0,0 +1,158 @@ +--- +phase: 25-catalog-enrichment-agent-tools +verified: 2026-04-10T09:30:00Z +status: passed +score: 11/11 must-haves verified +re_verification: false +human_verification: + - test: "Open a catalog item with imageCredit and imageSourceUrl set in the database" + expected: "'Photo: ' text appears below the product image, with a 'Source' link that opens the original image URL" + why_human: "Visual rendering and link behavior cannot be verified without a running browser" + - test: "Open a catalog item with sourceUrl set" + expected: "'View product page →' link appears below the description and opens the product page" + why_human: "Visual layout and link behavior cannot be verified without a running browser" +--- + +# Phase 25: Catalog Enrichment Agent Tools — Verification Report + +**Phase Goal:** Global items carry attribution metadata and can be bulk-populated by an MCP agent swarm +**Verified:** 2026-04-10T09:30:00Z +**Status:** PASSED +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths (Plan 01) + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | upsertGlobalItem called with sourceUrl, imageCredit, imageSourceUrl returns them in the result | VERIFIED | `tests/services/global-item.service.test.ts` line 306: passes all 3 attribution fields and asserts each is returned; test passes | +| 2 | Two upserts with the same (brand, model) return the same item id and created: false on the second call | VERIFIED | `tests/services/global-item.service.test.ts` line 285: verifies `created: false` on second upsert; test passes | +| 3 | Inserting a duplicate (brand, model) updates the existing row instead of failing | VERIFIED | `onConflictDoUpdate` with `target: [globalItems.brand, globalItems.model]` in service; migration adds unique constraint | +| 4 | bulkUpsertGlobalItems returns accurate created vs updated counts matching input mix | VERIFIED | `tests/services/global-item.service.test.ts` tests bulk with mix of new and existing; 21 tests pass | +| 5 | Tags are synced (create-if-not-exists) when provided, left untouched when omitted | VERIFIED | Three-way tag logic (undefined/[]/[names]) confirmed in service and tested at lines 324, 344, 368 | + +### Observable Truths (Plan 02) + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 6 | POST /api/global-items upserts a single catalog item and returns the item with id | VERIFIED | Route implemented with `zValidator`; `tests/routes/global-items.test.ts` 16 tests pass | +| 7 | POST /api/global-items/bulk upserts up to 100 items in a single transaction and returns created/updated counts | VERIFIED | Route implemented; test covers count accuracy and max-100 enforcement | +| 8 | POST /api/global-items/bulk rejects the entire batch if any item fails validation | VERIFIED | `zValidator` middleware rejects before DB; test confirms 400 with invalid item in array | +| 9 | MCP tool upsert_catalog_item writes a global item with attribution fields | VERIFIED | `catalog.ts` implements handler calling `upsertGlobalItem`; SEED-03 test at line 296 passes all 3 attribution fields and asserts result; 24 MCP tests pass | +| 10 | MCP tool bulk_upsert_catalog batch-writes global items via the bulk service | VERIFIED | `catalog.ts` implements handler calling `bulkUpsertGlobalItems`; tests at lines 318 and 336 pass | +| 11 | Catalog detail page shows image credit and source link below the image when present | VERIFIED (code) | `$globalItemId.tsx` lines 87–103: conditional attribution block with `Photo:` credit and `Source` link; client type extended with all 3 fields. Human visual check needed | + +**Score:** 11/11 truths verified (1 with additional human visual check recommended) + +--- + +## Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `src/db/schema.ts` | globalItems table with attribution columns and unique constraint | VERIFIED | Lines 147–152: sourceUrl, imageCredit, imageSourceUrl columns + `unique().on(table.brand, table.model)` | +| `drizzle-pg/0003_loving_serpent_society.sql` | Migration adding 3 columns + unique constraint | VERIFIED | 4-line migration: 3 ALTER TABLE ADD COLUMN + unique constraint | +| `src/shared/schemas.ts` | Zod schemas for upsert and bulk upsert | VERIFIED | `upsertGlobalItemSchema` at line 106, `bulkUpsertGlobalItemsSchema` at line 120 with `.max(100)` | +| `src/shared/types.ts` | UpsertGlobalItemInput and BulkUpsertGlobalItemsInput types | VERIFIED | Lines 55–56 export both types inferred from Zod schemas | +| `src/server/services/global-item.service.ts` | upsertGlobalItem and bulkUpsertGlobalItems functions | VERIFIED | Both exported at lines 105 and 176; full implementation, no stubs | +| `tests/services/global-item.service.test.ts` | Tests for upsert, duplicate handling, bulk, tags | VERIFIED | 8 new tests in `describe("upsert operations")`; 21 total pass | +| `src/server/routes/global-items.ts` | POST / and POST /bulk route handlers | VERIFIED | Lines 43–60: both routes with zValidator | +| `src/server/mcp/tools/catalog.ts` | catalogToolDefinitions and registerCatalogTools | VERIFIED | File exists; both exports present; attribution fields in inputSchema | +| `src/server/mcp/index.ts` | Catalog tool registration in createMcpServer | VERIFIED | Lines 10–12: imports; lines 62–67: registration loop | +| `src/client/hooks/useGlobalItems.ts` | GlobalItem interface with attribution fields | VERIFIED | Lines 13–15: sourceUrl, imageCredit, imageSourceUrl as `string \| null` | +| `src/client/routes/global-items/$globalItemId.tsx` | Attribution display below image | VERIFIED | Lines 87–104: attribution block + fallback spacer div | +| `tests/routes/global-items.test.ts` | Tests for POST single and bulk endpoints | VERIFIED | 9 new tests for POST; 16 total pass | +| `tests/mcp/tools.test.ts` | Tests for catalog MCP tools | VERIFIED | 6 new catalog tool tests; 24 total pass | + +--- + +## Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `src/server/routes/global-items.ts` | `src/server/services/global-item.service.ts` | import + call upsertGlobalItem / bulkUpsertGlobalItems | WIRED | Lines 8–13: both service functions imported; lines 46, 57: called in handlers | +| `src/server/mcp/tools/catalog.ts` | `src/server/services/global-item.service.ts` | import + call upsertGlobalItem / bulkUpsertGlobalItems | WIRED | Lines 3–6: both imported; lines 96, 122: called in tool handlers | +| `src/server/mcp/index.ts` | `src/server/mcp/tools/catalog.ts` | import catalogToolDefinitions + registerCatalogTools | WIRED | Lines 10–12: both imported; lines 63–67: registerCatalogTools(db) called, loop registers all tools | +| `src/client/routes/global-items/$globalItemId.tsx` | `src/client/hooks/useGlobalItems.ts` | useGlobalItem hook, GlobalItem interface | WIRED | Line 4: useGlobalItem imported; lines 88, 90, 91, 193: item.imageCredit, item.imageSourceUrl, item.sourceUrl all referenced in JSX | + +--- + +## Data-Flow Trace (Level 4) + +| Artifact | Data Variable | Source | Produces Real Data | Status | +|----------|---------------|--------|--------------------|--------| +| `$globalItemId.tsx` | `item.imageCredit`, `item.imageSourceUrl`, `item.sourceUrl` | `useGlobalItem` → `GET /api/global-items/:id` → `getGlobalItemWithOwnerCount` → `db.select().from(globalItems)` | Yes — `select()` without column restriction returns all columns including attribution fields | FLOWING | +| `tests/services/global-item.service.test.ts` | `result.item.sourceUrl`, `result.item.imageCredit`, `result.item.imageSourceUrl` | `upsertGlobalItem` → `tx.insert(...).returning()` | Yes — `returning()` returns full inserted/updated row | FLOWING | + +--- + +## Behavioral Spot-Checks + +| Behavior | Command | Result | Status | +|----------|---------|--------|--------| +| Service tests pass | `bun test tests/services/global-item.service.test.ts` | 21 pass, 0 fail | PASS | +| Route tests pass | `bun test tests/routes/global-items.test.ts` | 16 pass, 0 fail | PASS | +| MCP tool tests pass | `bun test tests/mcp/tools.test.ts` | 24 pass, 0 fail | PASS | +| Source lint clean | `bun run lint` (src/ and tests/ only) | No errors in src/ or tests/ | PASS | +| Build succeeds | Not run (no build output to check) | N/A | SKIP — build output not verified | + +Note: Full `bun test` suite shows 15 failures and 7 errors, but all failures are in `tests/services/storage.service.test.ts` — a pre-existing mock/dynamic-import issue from phase 17 (commit `be1197f`, April 7) that predates phase 25. No phase 25 file is responsible. The `.obsidian/` lint errors are from Obsidian vault JSON files outside the source tree. + +--- + +## Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|------------|-------------|--------|----------| +| CATL-01 | 25-01 | Global items have attribution fields (sourceUrl, manufacturer, imageCredit, imageSourceUrl) | SATISFIED | sourceUrl, imageCredit, imageSourceUrl columns added to schema; `brand` column serves as manufacturer per plan D-02 | +| CATL-02 | 25-01 | Global items have a unique constraint on (brand, model) preventing duplicates | SATISFIED | `unique().on(table.brand, table.model)` in schema; migration 0003 applied | +| CATL-03 | 25-02 | Catalog detail pages display image attribution with credit and source link | SATISFIED (visual check needed) | Attribution block in `$globalItemId.tsx` lines 87–103; human visual test required | +| CATL-04 | 25-02 | Bulk import API endpoint accepts multiple catalog items in one request | SATISFIED | `POST /api/global-items/bulk` implemented and tested | +| CATL-05 | 25-01 | Bulk import uses upsert semantics (ON CONFLICT update, not fail) | SATISFIED | `onConflictDoUpdate` in both `upsertGlobalItem` and `bulkUpsertGlobalItems` | +| SEED-01 | 25-02 | MCP server has a dedicated `upsert_catalog_item` tool that writes to globalItems (not user-scoped) | SATISFIED | `upsert_catalog_item` in `catalogToolDefinitions`; registered without userId | +| SEED-02 | 25-02 | MCP server has a `bulk_upsert_catalog` tool for batch catalog population | SATISFIED | `bulk_upsert_catalog` in `catalogToolDefinitions`; registered and tested | +| SEED-03 | 25-02 | Catalog MCP tools include attribution fields (sourceUrl, manufacturer, imageCredit) as parameters | SATISFIED | `sourceUrl`, `imageCredit`, `imageSourceUrl` in `catalogItemInputSchema`; test at line 296 explicitly passes and asserts all 3 | + +All 8 required requirement IDs are satisfied. No orphaned requirements found for phase 25. + +--- + +## Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| None | — | — | — | — | + +No TODO, FIXME, placeholder, empty return, or hardcoded-empty-data patterns found in phase 25 files. + +--- + +## Human Verification Required + +### 1. Image Attribution Display + +**Test:** Create a global item via `POST /api/global-items` with `imageCredit: "Test Photographer"` and `imageSourceUrl: "https://example.com/image"`. Open the catalog detail page for that item. +**Expected:** Below the product image, "Photo: Test Photographer · Source" appears in small gray text, with "Source" as a clickable link opening `https://example.com/image`. +**Why human:** Visual layout, conditional rendering, and link behavior require a running browser. + +### 2. Product Page Link Display + +**Test:** Create a global item with `sourceUrl: "https://example.com/product"`. Open its detail page. +**Expected:** "View product page →" link appears below the description section and opens the correct URL. +**Why human:** Visual layout and link behavior require a running browser. + +--- + +## Summary + +Phase 25 achieves its goal. Global items now carry attribution metadata (`sourceUrl`, `imageCredit`, `imageSourceUrl`) stored in the database with a unique constraint on `(brand, model)`. An MCP agent swarm can populate the catalog in bulk via `upsert_catalog_item` and `bulk_upsert_catalog` tools, both wired to the service layer through direct imports. The HTTP surface is also available via `POST /api/global-items` and `POST /api/global-items/bulk` with Zod validation. The client detail page renders attribution inline below the product image. + +All 8 requirement IDs (CATL-01 through CATL-05, SEED-01 through SEED-03) are satisfied with direct code evidence. All phase-specific tests (61 across 3 test files) pass. Pre-existing storage.service test failures and .obsidian lint issues are not introduced by this phase. + +Two items are flagged for human visual verification: attribution rendering and product page link on the catalog detail page. + +--- + +_Verified: 2026-04-10T09:30:00Z_ +_Verifier: Claude (gsd-verifier)_