Clean up docs and update for Phase 2#2
Merged
arberx merged 6 commits intoax/separate-monitor-repofrom Mar 9, 2026
Merged
Conversation
Remove stale Docker/Postgres-era files (compose.yaml, .env.example, self-hosting.md) and superseded plan drafts. Add canonical Phase 2 design doc. Update all agent docs (CLAUDE.md, AGENTS.md), README, CONTRIBUTING, architecture, product plan, testing guide, workspace packaging, and all package-level READMEs to reflect Phase 2 reality: single-process local server, SQLite, CLI, config-as-code, publishable npm package. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Database (packages/db): - Drizzle ORM schema with 8 tables: projects, keywords, competitors, runs, query_snapshots, audit_log, api_keys, usage_counters - SQLite client factory with WAL mode and foreign keys - Auto-migration runner (idempotent CREATE IF NOT EXISTS) - 9 tests covering CRUD, cascade deletes, unique constraints Contracts (packages/contracts): - canonry.yaml config-schema with Zod validation (apiVersion/kind/ metadata/spec structure, slug validation for project names) - Structured error codes with AppError class and factory helpers - Extended ProjectDto with labels, configSource, configRevision - Extended RunDto with trigger, startedAt, finishedAt, error - New schemas: citationState, computedTransition, querySnapshot, auditLogEntry - 12 tests covering all new schemas and error handling Also: remove stale Docker scripts from root package.json, approve better-sqlite3 native builds, fix web mock data for extended DTOs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5 tasks
Phase 2b — packages/api-routes/: - Fastify route plugins: auth, projects, keywords, competitors, runs, apply (config-as-code), history/timeline/snapshots - API key auth via SHA-256 hash lookup in api_keys table - Audit log entries on all config mutations - Usage counter increments on runs - Computed citation transitions (lost/emerging) from consecutive snapshots - 8 tests covering CRUD, run trigger, duplicate guard, audit log Phase 2c — provider execution + job runner: - In-process JobRunner in packages/canonry/src/job-runner.ts - Executes visibility queries via provider-gemini for each keyword - Determines citation state (cited/not-cited) by checking if canonical domain appears in cited domains - Persists query_snapshots, updates run status, increments usage Phase 2d — packages/canonry/ (publishable npm package): - CLI entry point with parseArgs routing for all commands - Unified Fastify server mounting api-routes + @fastify/static for SPA - Config reader/writer for ~/.canonry/config.yaml - HTTP API client for CLI → server communication - Commands: init, serve, project CRUD, keyword/competitor management, run trigger, status, history, apply, export - canonry init: creates ~/.canonry/, generates cnry_* API key, SQLite DB - 4 tests covering config, server creation, API flow, health endpoint Also: cascade delete on query_snapshots.keyword_id (user fix). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… parity - runs.ts: add orderBy(asc) to GET /projects/:name/runs so status/export commands reliably get the latest run via runs[runs.length - 1] - runs.ts: wrap activeRun check + insert in a db.transaction to eliminate the race condition that could create duplicate concurrent runs - history.ts: replace full-table query + JS filter with inArray WHERE clause in both /snapshots and /timeline endpoints - history.ts + runs.ts: wrap all JSON.parse calls in tryParseJson helper so a single malformed DB value no longer poisons the entire response - config.ts: write ~/.canonry/config.yaml with mode 0o600 to prevent apiKey and geminiApiKey from being world-readable - commands/evidence.ts: implement canonry evidence using the timeline API to display keyword-level citation states (was silently calling showStatus) - cli.ts: wire evidence case to new showEvidence command - api.ts: add fetchExport for GET /projects/:name/export - App.tsx: add Export (Download) button to ProjectPage header for surface parity with CLI and API export surfaces Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api.ts: new typed SPA client for all API operations - build-dashboard.ts: transforms live API responses into DashboardVm tree - App.tsx: load real data on mount, reactive setup wizard with live API calls, delete confirmation, optional readinessSummary/technicalFindings sections - view-models.ts: make readinessSummary and technicalFindings optional - mock-data.ts: update mocks for new required DTO fields - styles.css: gauge layout fixes, setup-wizard styles - vite.config.ts: simplify dev proxy to single canonry serve instance - server.ts: serve bundled SPA assets, inject API key, /health endpoint - build-web.ts: script to build SPA and copy assets into package - button.tsx: add destructive variant - CLAUDE.md / AGENTS.md: phase 2 documentation updates - docs/phase-2-design.md: architecture refinements Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 2a: Database schema and contracts foundation
arberx
added a commit
that referenced
this pull request
Mar 13, 2026
- Replace dense EvidenceTable with EvidencePhraseCards grid - One card per keyword phrase (2-col responsive grid) - Color-accented left border by citation state (emerald/rose/zinc) - Citation status badge + trend arrow + streak label at a glance - Timeline dot history per phrase across all providers - Per-provider clickable pills (Gemini ✓ / OpenAI ✗) that open the drawer - Redesign EvidenceDrawer for human readability - Position hero: 'Cited #2 of 4 domains' or 'Not in this answer' - AI answer with your domain/name highlighted in emerald - Citation leaderboard: ordered ranked list (You / Competitor / Other) - Action items: technical signals as styled list (✓ why cited / ⚠ what to fix) - Grounding sources and evidence URLs as clickable links - Add highlightTermsInText() utility for domain highlighting - Add ~170 lines of new CSS in styles.css following existing @apply pattern
arberx
added a commit
that referenced
this pull request
Mar 13, 2026
- Replace dense EvidenceTable with EvidencePhraseCards grid - One card per keyword phrase (2-col responsive grid) - Color-accented left border by citation state (emerald/rose/zinc) - Citation status badge + trend arrow + streak label at a glance - Timeline dot history per phrase across all providers - Per-provider clickable pills (Gemini ✓ / OpenAI ✗) that open the drawer - Redesign EvidenceDrawer for human readability - Position hero: 'Cited #2 of 4 domains' or 'Not in this answer' - AI answer with your domain/name highlighted in emerald - Citation leaderboard: ordered ranked list (You / Competitor / Other) - Action items: technical signals as styled list (✓ why cited / ⚠ what to fix) - Grounding sources and evidence URLs as clickable links - Add highlightTermsInText() utility for domain highlighting - Add ~170 lines of new CSS in styles.css following existing @apply pattern
arberx
added a commit
that referenced
this pull request
Mar 13, 2026
* Redesign evidence tracking: phrase cards + improved drawer - Replace dense EvidenceTable with EvidencePhraseCards grid - One card per keyword phrase (2-col responsive grid) - Color-accented left border by citation state (emerald/rose/zinc) - Citation status badge + trend arrow + streak label at a glance - Timeline dot history per phrase across all providers - Per-provider clickable pills (Gemini ✓ / OpenAI ✗) that open the drawer - Redesign EvidenceDrawer for human readability - Position hero: 'Cited #2 of 4 domains' or 'Not in this answer' - AI answer with your domain/name highlighted in emerald - Citation leaderboard: ordered ranked list (You / Competitor / Other) - Action items: technical signals as styled list (✓ why cited / ⚠ what to fix) - Grounding sources and evidence URLs as clickable links - Add highlightTermsInText() utility for domain highlighting - Add ~170 lines of new CSS in styles.css following existing @apply pattern * Fix evidence redesign: pending cards, highlight markdown, uncited copy - Filter pending keywords from provider buttons; show dedicated "Awaiting first run" text instead of blank provider CTAs - Add pending case to drawer heroCopy so pending evidence doesn't render as "Not in this answer" with empty provider meta - Parse **bold** markdown before splitting on highlight terms so bolded domain/project names render correctly instead of leaking literal ** markers - Change uncited hero text from "competitor(s) cited" to "domain(s) cited" since citedDomains includes non-competitor sources Bump version to 1.4.4. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Replace evidence sidebar drawer with centered detail modal - Switch from side-panel Sheet to a centered Radix Dialog modal (~1100px wide, 90vh tall) for much better content readability - Two-column layout: left side has status hero + AI answer + action items, right side has citation leaderboard + grounding sources - Add run history timeline at the top with clickable dots per run, showing citation state over time with back-to-latest navigation - Improve markdown rendering: headings render as styled text with proper hierarchy, horizontal rules as <hr>, emoji prefixes stripped, paragraphs break on single empty lines - Fade-mask on collapsed answers instead of hard line-clamp cutoff Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Wire run history timeline to fetch full snapshot data per run The run history dots in the evidence modal were visual-only — clicking them didn't update the displayed content because RunHistoryPoint only stored citationState + createdAt. - Add runId to RunHistoryPoint (already available from timeline API) - On historical dot click, fetch GET /runs/:id to get full snapshot - Find matching keyword+provider snapshot and display its answer text, cited domains, competitor overlap, and grounding sources - Cache fetched run details so re-clicking dots is instant - Show loading state while fetching, graceful fallback on error - All display data (hero, answer, leaderboard, sources) switches between latest evidence and fetched historical snapshot via a shared EvidenceDisplayData interface The DB already persists answerText, citedDomains, competitorOverlap, groundingSources, and rawResponse per querySnapshot — this just surfaces that data in the UI when browsing historical runs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(web): guard stale async race, restore evidence URLs, improve a11y P1: Guard selectHistoricalRun against out-of-order async completions using a ref-based request counter. Stale fetch results are discarded so rapid dot clicks never show mismatched content. P2: Restore evidenceUrls to EvidenceDisplayData and render them in the modal sidebar. These were dropped when moving from the drawer to the modal, removing the direct path from citation items to source pages. P2: Add aria-label, aria-pressed, focus-visible ring, and aria-hidden on decorative dot spans for run history buttons so screen-reader and keyboard users get the same context as pointer users. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claw (AINYC Agent) <agent@ainyc.ai> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
arberx
added a commit
that referenced
this pull request
Mar 16, 2026
Wrap JSON.parse(toolCall.function.arguments) in try-catch to prevent crashes when LLMs return malformed JSON. On parse error, persist the error as a tool result and continue the agent loop instead of crashing. Addresses review comment #2 (Bug)
arberx
added a commit
that referenced
this pull request
Mar 16, 2026
Wrap JSON.parse(toolCall.function.arguments) in try-catch to prevent crashes when LLMs return malformed JSON. On parse error, persist the error as a tool result and continue the agent loop instead of crashing. Addresses review comment #2 (Bug)
arberx
added a commit
that referenced
this pull request
Mar 16, 2026
Wrap JSON.parse(toolCall.function.arguments) in try-catch to prevent crashes when LLMs return malformed JSON. On parse error, persist the error as a tool result and continue the agent loop instead of crashing. Addresses review comment #2 (Bug)
arberx
added a commit
that referenced
this pull request
Mar 17, 2026
Wrap JSON.parse(toolCall.function.arguments) in try-catch to prevent crashes when LLMs return malformed JSON. On parse error, persist the error as a tool result and continue the agent loop instead of crashing. Addresses review comment #2 (Bug)
arberx
added a commit
that referenced
this pull request
Mar 17, 2026
Wrap JSON.parse(toolCall.function.arguments) in try-catch to prevent crashes when LLMs return malformed JSON. On parse error, persist the error as a tool result and continue the agent loop instead of crashing. Addresses review comment #2 (Bug)
arberx
added a commit
that referenced
this pull request
Mar 17, 2026
Wrap JSON.parse(toolCall.function.arguments) in try-catch to prevent crashes when LLMs return malformed JSON. On parse error, persist the error as a tool result and continue the agent loop instead of crashing. Addresses review comment #2 (Bug)
arberx
added a commit
that referenced
this pull request
Mar 18, 2026
Issue #1 — trailing-slash mismatch on root route: Register both '/canonry/' and '/canonry' (bare) variants so either URL shape returns the SPA without a 404. Issue #2 — SPA fallback over-broad scope: When basePath is set, only serve index.html for paths that start with basePath; return JSON 404 for everything else so co-hosted apps on the same origin are not hijacked. Issue #3 — bare '/' base path causing duplicate-route error: Normalised base path equal to '/' is now treated as undefined (no base path), keeping the fastify-static prefix at '/' and avoiding a duplicate route registration error. Issue #4 — routePrefix without leading slash silently mis-routes: Validate opts.routePrefix at startup and throw a descriptive error if it does not start with '/'. Issue #5 — copy-pasted health handler body: Extract to a shared healthHandler const; both /health and ${basePath}health now reference the same function.
arberx
added a commit
that referenced
this pull request
Mar 18, 2026
* fix: base-path-aware API route prefix and SPA fallback
When `--base-path /canonry/` is set, three things were broken:
1. API routes were registered at /api/v1 regardless of base path,
so requests to /canonry/api/v1/* had no matching route.
2. setNotFoundHandler checked request.url.startsWith('/api/') —
correct without base path, but missed /canonry/api/v1/* URLs,
causing the SPA catch-all to return 200 HTML for all API routes.
3. @fastify/static and root route handler were prefix-unaware,
so static assets and the root index.html were not served under
the configured base path.
Fix:
- Add `routePrefix` option to ApiRoutesOptions (named to avoid
collision with Fastify's reserved `prefix` register option).
- Compute basePath once before apiRoutes registration and use it
to set routePrefix = `${basePath}api/v1` or '/api/v1' default.
- Register @fastify/static and root handler at `basePath ?? '/'`.
- Update setNotFoundHandler guard to check both '/api/' and
'${basePath}api/' so it works with and without --base-path.
- Register /health at both '/health' and '${basePath}health'.
Closes #113
* fix: address review feedback on base-path routing
Issue #1 — trailing-slash mismatch on root route:
Register both '/canonry/' and '/canonry' (bare) variants so
either URL shape returns the SPA without a 404.
Issue #2 — SPA fallback over-broad scope:
When basePath is set, only serve index.html for paths that
start with basePath; return JSON 404 for everything else so
co-hosted apps on the same origin are not hijacked.
Issue #3 — bare '/' base path causing duplicate-route error:
Normalised base path equal to '/' is now treated as undefined
(no base path), keeping the fastify-static prefix at '/' and
avoiding a duplicate route registration error.
Issue #4 — routePrefix without leading slash silently mis-routes:
Validate opts.routePrefix at startup and throw a descriptive
error if it does not start with '/'.
Issue #5 — copy-pasted health handler body:
Extract to a shared healthHandler const; both /health and
${basePath}health now reference the same function.
---------
Co-authored-by: Claw (AINYC Agent) <agent@ainyc.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
compose.yaml,.env.example,docs/self-hosting.md)docs/phase-2-design.md) — moved from.context/plans/CLAUDE.md,AGENTS.md),README.md,CONTRIBUTING.md, and alldocs/files to reflect Phase 2 architecture: single-process local server, SQLite, CLI, config-as-code, publishable npm packagepackages/db,packages/contracts,packages/provider-gemini,apps/api,apps/worker,apps/web)docs/site-audit.mdas deferred to Phase 3Stacked on
PR #1 (
ax/separate-monitor-repo→main)Test plan
pnpm run typecheckpassespnpm run testpasses (all 30 tests)pnpm run lintpasses🤖 Generated with Claude Code