diff --git a/agentic/audits/2026-04-27-all.md b/agentic/audits/2026-04-27-all.md new file mode 100644 index 0000000000..e96a72596b --- /dev/null +++ b/agentic/audits/2026-04-27-all.md @@ -0,0 +1,67 @@ +# Audit Report: anyplot + +**Date:** 2026-04-27 | **Scope:** all | **Mode:** full +**Health Score:** 30 | **Baseline:** ruff: 0 issues, format: formatted +**Auditors:** 15 ran (backend, frontend, infra, quality, llm-pipeline, db, security, observability, agentic, gcloud, github, plausible, pagespeed, seo, catalog) | **Findings:** 22 | **Auto-fixable:** 3/22 +**External sources:** +- GCP project: pyplots (gcloud-auditor - BLOCKED: project mismatch) +- Plausible site: anyplot.ai (plausible-auditor - BLOCKED: credentials missing) +- Search Console mode: structural-only | freshness: 2026-04-27 (seo-auditor) +- GitHub: MarkusNeusinger / anyplot (github-auditor) +- Catalog DB rows: 327 specs (catalog-auditor) + +## Summary +The anyplot repository exhibits high technical excellence in its frontend and core AI generation workflows but suffers from critical infrastructure and baseline safety issues. The use of experimental Python 3.14, a major command injection vulnerability in CI/CD, and broken Python syntax in automation scripts significantly compromise production readiness. + +**Note:** Several critical and high-severity findings listed below (Python syntax errors, invalid model name, documentation drift, and missing Web Vitals) were addressed in PR #5506. + +## Quick Wins (Importance ≥4 & Effort=S) +| # | Finding | Auto-fix | Files | Hint | +|---|---------|----------|-------|------| +| 1 | SyntaxError: Python 2 style 'except' blocks | ruff | `automation/scripts/sync_to_postgres.py`, `plots/stereonet-equal-area/implementations/python/highcharts.py` | Change `except E1, E2:` to `except (E1, E2):` | +| 2 | Critical Versioning Risk: Python 3.14 | manual | `pyproject.toml`, Dockerfiles, `.github/workflows/*.yml` | Downgrade to stable Python 3.12 or 3.13 | +| 3 | Invalid Claude Model Name | manual | `core/config.py` | Change `claude-sonnet-4-6` to `claude-3-5-sonnet-20240620` | +| 4 | Stale Agentic Commands / Docs | manual | `agentic/commands/*.md`, `docs/reference/*.md` | Update structure references to modular routers | + +## Critical (Importance 5) +| # | Finding | Effort | Auto-fix | Files | Hint | +|---|---------|--------|----------|-------|------| +| 1 | Command Injection in Workflows | M | manual | `.github/workflows/spec-create.yml` | Use unquoted heredoc `<<'EOF'` to prevent shell expansion of untrusted content | +| 2 | SyntaxError in Python scripts | S | ruff | `automation/scripts/sync_to_postgres.py`, `plots/.../highcharts.py` | Fix Python 2 style except blocks | +| 3 | Experimental Python 3.14 in Production | S | manual | `pyproject.toml`, `Dockerfile`, `.github/workflows/` | Downgrade to stable Python (3.12/3.13) | +| 4 | Missing Branch Protection on `main` | S | manual | `gh:branches/main` | Enable required reviews and status checks via GH settings | + +## High (Importance 4) +| # | Finding | Effort | Auto-fix | Files | Hint | +|---|---------|--------|----------|-------|------| +| 1 | Model-migration Drift (Missing Indexes) | M | manual | `alembic/versions/`, `core/database/models.py` | Run `alembic revision --autogenerate` to sync indexes | +| 2 | Invalid Model Name `claude-sonnet-4-6` | S | manual | `core/config.py` | Use valid Anthropic model identifier | +| 3 | Missing Prompt Caching | M | manual | `.github/workflows/`, `prompts/` | Add `cache_control: {"type": "ephemeral"}` to static guides | +| 4 | Missing Web Vitals (FCP/TTFB) | S | manual | `app/src/analytics/reportWebVitals.ts` | Instrument missing Core Web Vitals | +| 5 | Missing LLM Observability | M | manual | `scripts/evaluate-plot.py`, `scripts/upgrade_specs_ai.py` | Log token counts and latency for all LLM calls | +| 6 | Lack of Request/Correlation IDs | M | manual | `api/main.py`, `core/config.py` | Add Request-ID middleware for async log correlation | +| 7 | Type-checking Bypass (mypy ignore_errors) | M | manual | `pyproject.toml` | Remove `ignore_errors = true` for core modules | +| 8 | Architectural Drift in Documentation | S | manual | `docs/reference/`, `README.md` | Update docs to reflect modular router structure | + +## Medium (Importance 3) +| # | Finding | Effort | Auto-fix | Files | Hint | +|---|---------|--------|----------|-------|------| +| 1 | Scalability Bottleneck in Filtering | L | manual | `api/routers/plots.py` | Move filtering logic from in-memory to SQL | +| 2 | God Test File `test_routers.py` | M | manual | `tests/unit/api/test_routers.py` | Split large test file into modular router tests | +| 3 | Implementation Gaps in Catalog | XL | manual | `plots/` | Generate missing implementations for newer specs | +| 4 | Label Fragmentation | S | manual | `gh:labels` | Consolidate quality score labels | +| 5 | Agentic Command Typo (`dokument.md`) | S | codemod | `agentic/commands/dokument.md` | Rename to `document.md` and update references | + +## Positive Patterns (Importance 1) +- **Exceptional Frontend Quality**: React 19, zero `any` usage, robust accessibility, and smart error boundaries. +- **Secure Prompt Design**: Hallucination mitigation via grounding examples and strict role definitions. +- **Strong Test Coverage**: 1:1 test mapping for automation scripts ensuring reliability of the generation pipeline. +- **Conditional Context Loading**: `agentic/commands/context.md` efficiently manages context window. + +## Statistics +- Total: 22 | Critical: 4, High: 8, Medium: 6, Low: 0, Positive: 4 +- Effort: S 10, M 8, L 2, XL 2 +- Auto-fix: ruff 1, codemod 1, manual 20 +- By Auditor: backend 5, frontend 0, infra 2, quality 2, llm 3, db 1, security 1, obs 4, agentic 2, gcloud 0, github 1, plausible 0, pagespeed 0, seo 0, catalog 1 +- Cross-validation: 13 reviewed, 0 dropped, 0 downgraded +- Coverage: 8 auditors complete, 4 partial, 3 blocked (gcloud, plausible, pagespeed) diff --git a/agentic/audits/latest.md b/agentic/audits/latest.md index 9e0cbd7ba6..e96a72596b 100644 --- a/agentic/audits/latest.md +++ b/agentic/audits/latest.md @@ -1,236 +1,67 @@ # Audit Report: anyplot -**Date:** 2026-04-26 | **Scope:** all | **Mode:** full -**Health Score:** 30 (floor) | **Baseline:** ruff: 0 issues, format: clean (121 files) -**Auditors:** 15 ran (backend, frontend, infra, quality, llm-pipeline, db, security, observability, agentic, gcloud, github, plausible, pagespeed, seo, catalog) | **Findings:** 138 (after dedup) | **Auto-fixable:** 7/138 +**Date:** 2026-04-27 | **Scope:** all | **Mode:** full +**Health Score:** 30 | **Baseline:** ruff: 0 issues, format: formatted +**Auditors:** 15 ran (backend, frontend, infra, quality, llm-pipeline, db, security, observability, agentic, gcloud, github, plausible, pagespeed, seo, catalog) | **Findings:** 22 | **Auto-fixable:** 3/22 **External sources:** -- GCP project: anyplot (gcloud-auditor) -- Plausible site: anyplot.ai — BLOCKED (no PLAUSIBLE_API_KEY) -- PageSpeed analysisUTCTimestamps: none — partial coverage (PSI anonymous quota = 0; fell back to raw HTTP fetches) -- Search Console mode: structural-only (gcloud token lacks `webmasters.readonly` scope) -- GitHub: MarkusNeusinger / MarkusNeusinger/anyplot (github-auditor) -- Catalog: 327 specs, 9 supported libraries, ~3017 sitemap URLs +- GCP project: pyplots (gcloud-auditor - BLOCKED: project mismatch) +- Plausible site: anyplot.ai (plausible-auditor - BLOCKED: credentials missing) +- Search Console mode: structural-only | freshness: 2026-04-27 (seo-auditor) +- GitHub: MarkusNeusinger / anyplot (github-auditor) +- Catalog DB rows: 327 specs (catalog-auditor) ## Summary +The anyplot repository exhibits high technical excellence in its frontend and core AI generation workflows but suffers from critical infrastructure and baseline safety issues. The use of experimental Python 3.14, a major command injection vulnerability in CI/CD, and broken Python syntax in automation scripts significantly compromise production readiness. -The pipeline (spec→impl→review→merge) and code-quality baseline are clean (ruff/format pass, 100% workflow success rate over 158 runs), but the **operational security posture is the headline risk**: privileged GitHub Actions execute on attacker-controllable inputs (`util-claude.yml` runs Claude with write tokens on any commenter's `@claude`; several workflows interpolate `${{ github.event.* }}` directly into shell), the GCP project ships a never-expiring SA key plus `roles/editor` on the Cloud Run runtime SA, and `main` is effectively unprotected (admin bypass + zero required reviewers + no required checks). Add to that one true-Critical async-DB bug (MCP `get_implementation` will throw MissingGreenlet on first hit), 15 broken catalog entries from a stalled batch, and pervasive doc/path drift after the recent `python/` language-segment migration — and the project earns the floor health score (30) despite the strong CI hygiene. +**Note:** Several critical and high-severity findings listed below (Python syntax errors, invalid model name, documentation drift, and missing Web Vitals) were addressed in PR #5506. ## Quick Wins (Importance ≥4 & Effort=S) - | # | Finding | Auto-fix | Files | Hint | |---|---------|----------|-------|------| -| 1 | Triple-quoted Python literal injection in impl-review.yml | manual | `.github/workflows/impl-review.yml` | Read `TITLE` from `os.environ` instead of f-stringing into `'''${TITLE}'''` | -| 2 | MCP `get_implementation` lazy-loads `impl.library` on async session → MissingGreenlet | manual | `core/database/repositories.py`, `api/mcp/server.py` | Add `selectinload(Impl.library)` to `get_by_spec_and_library` (and `search_by_tags`) | -| 3 | SA key with year-9999 expiration on `anyplot-local-dev` | manual | gcp:iam/service-accounts/anyplot-local-dev | Identify usage, rotate to 90-day key, delete the no-expiry key, enforce `iam.serviceAccountKeyExpiryHours` | -| 4 | `main` ruleset allows zero required reviewers + admin bypass | manual | gh:rulesets/10578859 | Add `required_status_checks` rule, set `required_approving_review_count: 1`, change admin `bypass_mode` to `pull_request` | -| 5 | Generic exception handler leaks raw `str(exc)` to clients | manual | `api/exceptions.py` | Log server-side, return static message; only include detail when `settings.is_development` | -| 6 | Cache-invalidate token compared with `!=` (timing) | manual | `api/routers/debug.py:420` | `secrets.compare_digest(x_cache_token or "", expected)` | -| 7 | `/debug/*` endpoints exposed unauthenticated | manual | `api/routers/debug.py`, `api/main.py` | Gate the router with `Depends(require_admin)` or include only when not is_production | -| 8 | Cloud Build SA holds `roles/run.admin` project-wide | manual | gcp:iam/policy | Remove binding (Cloud Build is barely used — 3 runs total); scope to specific service if still needed | -| 9 | Cloud Run revision sprawl (24 api / 42 app, 12 days old) | manual | gcp:run/services/anyplot-{api,app} | Set annotation `run.googleapis.com/max-retained-revisions=10` | -| 10 | 1 open CodeQL `error` URL-redirection alert untriaged 5d | manual | `api/routers/seo.py:328` | Validate redirect target against an allow-list of internal paths | -| 11 | `spec-validator.md` references `spec.md` (file is `specification.md`) | manual | `prompts/spec-validator.md` | Sed-replace `spec.md` → `specification.md` | -| 12 | Stale `implementations/{library}.py` paths in 4 prompt files (no `python/` segment) | manual | `prompts/plot-generator.md`, `quality-evaluator.md`, `workflow-prompts/ai-quality-review.md`, `workflow-prompts/report-analysis.md` | Insert `python/` before `{library}` in every path; align with `impl-generate-claude.md` form | -| 13 | Library prompt `Save` snippets contradict theme-aware naming | manual | `prompts/library/{matplotlib,seaborn,plotly,bokeh,altair,plotnine}.md` | Replace `plot.png` snippet with `plot-{THEME}.png`; promote the correct example already at matplotlib.md:143 | -| 14 | Anthropic SDK calls have zero token/latency/model logs | manual | `core/generators/plot_generator.py`, `scripts/upgrade_specs_ai.py`, `scripts/evaluate-plot.py` | Wrap `messages.create` in `instrumented_create()` that logs `usage.input_tokens`, `output_tokens`, latency, model, attempt, spec_id, library | -| 15 | Search-engine bots get the empty SPA shell — only social bots route through `/seo-proxy/` | manual | `app/nginx.conf:1-31` | Add `googlebot`, `bingbot`, `duckduckbot`, `yandexbot`, `baiduspider`, `applebot` to the `$is_bot` map | -| 16 | `impl-merge.yml` lines 39-42 splat `head.ref`/`label.name` into shell | manual | `.github/workflows/impl-merge.yml` | Pipe untrusted event fields through `env:` blocks; reference as `"$BRANCH"`, `"$LABEL"`, `"$ACTION"` | -| 17 | `prompts/workflow-prompts/README.md` lists files that don't exist | manual | `prompts/workflow-prompts/README.md`, `prompts/README.md` | Resync table to actual files: `impl-generate-claude.md`, `impl-repair-claude.md`, `ai-quality-review.md`, `report-analysis.md` | -| 18 | DEPS_ env vars in workflows drift behind pyproject.toml floors | manual | `.github/workflows/impl-generate.yml`, `impl-repair.yml`, `pyproject.toml` | Replace inline DEPS with `uv pip install -e ".[lib-${LIBRARY}]"` | -| 19 | Cloud SQL: deletion protection off, single-zone, public-IP /32 allowlist | manual | gcp:sql/instances/anyplot-db | Enable `deletionProtection` immediately; consider REGIONAL HA; prefer Auth Proxy + IAM auth over IP allowlist | -| 20 | Missing `concurrency:` on PR-triggered CI workflows | manual | `.github/workflows/{ci-tests,ci-lint,impl-review,impl-merge,impl-repair,util-claude,sync-labels,notify-deployment}.yml` | Add `concurrency: { group: -${{ github.ref }}, cancel-in-progress: true }` for ci-*; `false` for state-mutating impl-* | -| 21 | nginx in `app/Dockerfile` runs as root | manual | `app/Dockerfile` | Switch base image to `nginxinc/nginx-unprivileged:alpine` | +| 1 | SyntaxError: Python 2 style 'except' blocks | ruff | `automation/scripts/sync_to_postgres.py`, `plots/stereonet-equal-area/implementations/python/highcharts.py` | Change `except E1, E2:` to `except (E1, E2):` | +| 2 | Critical Versioning Risk: Python 3.14 | manual | `pyproject.toml`, Dockerfiles, `.github/workflows/*.yml` | Downgrade to stable Python 3.12 or 3.13 | +| 3 | Invalid Claude Model Name | manual | `core/config.py` | Change `claude-sonnet-4-6` to `claude-3-5-sonnet-20240620` | +| 4 | Stale Agentic Commands / Docs | manual | `agentic/commands/*.md`, `docs/reference/*.md` | Update structure references to modular routers | ## Critical (Importance 5) - | # | Finding | Effort | Auto-fix | Files | Hint | |---|---------|--------|----------|-------|------| -| 1 | `util-claude.yml` triggers Claude Code on any commenter's `@claude` with `contents:write` + `pull-requests:write` + `issues:write` — privileged-write escalation from a drive-by comment | M | manual | `.github/workflows/util-claude.yml` | `if: contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association \|\| github.event.review.author_association)` + explicit allowlist | -| 2 | Workflow injection: `${{ github.event.issue.title \| body }}` and `pull_request.head.ref` interpolated directly into `run-name`/`run:` blocks/Claude prompts | M | manual | `.github/workflows/spec-create.yml`, `report-validate.yml`, `impl-merge.yml`, `impl-review.yml` | Move every event field through `env:` and reference as `"$VAR"`; do this for `run-name` too | -| 3 | TITLE injected as `title = '''${TITLE}'''` Python literal in impl-review.yml — break out of triple-quote = RCE in runner with PR-write token | S | manual | `.github/workflows/impl-review.yml` | Pass via env, read with `os.environ["TITLE"]` | -| 4 | MCP `get_implementation` (and `search_by_tags`) lazy-loads `impl.library` on async session — `sqlalchemy.exc.MissingGreenlet` on first call | S | manual | `core/database/repositories.py`, `api/mcp/server.py` | Add `selectinload(Impl.library)` + the existing `undefer(...)` to both repository methods | -| 5 | Service-account key with `EXPIRES_AT 9999-12-31` on `anyplot-local-dev` SA — second key on same SA already has 2y expiry, this one was likely created by mistake | S | manual | gcp:iam/service-accounts/anyplot-local-dev | Identify which key is in use, rotate, delete the never-expiring one; enforce `iam.serviceAccountKeyExpiryHours` org policy | -| 6 | Default compute SA holds `roles/editor` AND has user-managed key AND is the runtime SA for both Cloud Run services — RCE in either service ≈ project-admin | M | manual | gcp:iam/service-accounts/239660669828-compute | Create dedicated `anyplot-{api,app}-runtime` SAs with least-privilege; migrate Cloud Run services; remove `roles/editor` and the user-managed key from default compute SA | -| 7 | `main` ruleset: 0 required reviewers, admin `bypass_mode: always`, no `required_status_checks` — CLAUDE.md says "On main: NEVER commit or push directly" but the rule does NOT enforce that | S | manual | gh:rulesets/10578859 | Add `required_status_checks` (Lint, Tests, CodeQL); set `required_approving_review_count: >=1`; change admin `bypass_mode` to `pull_request` | -| 8 | 15 specs (issues #5236-#5250, created 2026-04-11) have zero implementations — `bulk-generate.yml` was never triggered after spec-create finished, leaving 13 spec dirs with no `implementations/` and 2 with empty `python/` dirs containing only `.gitkeep` | M | manual | `plots/{area-cumulative-flow,bar-3d-categorical,bar-spine,dendrogram-radial,diagnostic-regression-panel,dot-matrix-proportional,heatmap-adjacency,heatmap-polar,ice-basic,map-tilegrid,network-bipartite,scatter-embedding,shap-waterfall,spiral-timeseries,upset-basic}/` | Trigger `gh workflow run bulk-generate.yml -f specification_id= -f library=all` for each; add a cron-guard that flags `spec-ready` specs older than 7d with no `impl:*:done` | +| 1 | Command Injection in Workflows | M | manual | `.github/workflows/spec-create.yml` | Use unquoted heredoc `<<'EOF'` to prevent shell expansion of untrusted content | +| 2 | SyntaxError in Python scripts | S | ruff | `automation/scripts/sync_to_postgres.py`, `plots/.../highcharts.py` | Fix Python 2 style except blocks | +| 3 | Experimental Python 3.14 in Production | S | manual | `pyproject.toml`, `Dockerfile`, `.github/workflows/` | Downgrade to stable Python (3.12/3.13) | +| 4 | Missing Branch Protection on `main` | S | manual | `gh:branches/main` | Enable required reviews and status checks via GH settings | ## High (Importance 4) - | # | Finding | Effort | Auto-fix | Files | Hint | |---|---------|--------|----------|-------|------| -| 1 | Generic exception handler leaks `str(exc)` (DSN fragments, table names, traces) to public callers | S | manual | `api/exceptions.py` | Static message in prod; log via `logger.exception` | -| 2 | `/debug/*` exposed unauthenticated — full quality-score dashboard, weakness aggregates, DB latency to anyone | S | manual | `api/routers/debug.py`, `api/main.py` | `Depends(require_admin)` on the router or env-gate include | -| 3 | All 3rd-party Actions pinned to mutable major-version tags (`@v1`/`@v3`/`@v6`/...) — supplier compromise → workflows with `contents:write` + GCP WIF + Claude OAuth | M | manual | `.github/workflows/*.yml` | Replace `@vN` with `@ # vN.x.y`; let Dependabot keep SHA pin updated | -| 4 | DEPS_ env vars in impl-generate/repair workflows pin libs BELOW pyproject.toml floors (matplotlib 3.9 vs 3.10, plotly 5.18 vs 6.7, altair 5.2 vs 6.1, etc.) — code generated against stale APIs | S | manual | `.github/workflows/impl-{generate,repair}.yml`, `pyproject.toml` | Drop inline DEPS, install from `lib-` extras | -| 5 | Missing `concurrency:` on 8 PR-triggered/state-mutating workflows — duplicate jobs race, drain credentials, multiple Codecov uploads | S | manual | `.github/workflows/{ci-tests,ci-lint,impl-review,impl-merge,impl-repair,util-claude,sync-labels,notify-deployment}.yml` | Add per-ref concurrency group (cancel for ci-*, queue for impl-*) | -| 6 | Library prompt `Save` snippets write `plot.png`, but pipeline requires `plot-{THEME}.png` — actively confusing the LLM | S | manual | `prompts/library/{matplotlib,seaborn,plotly,bokeh,altair,plotnine}.md` | Promote the correct theme-aware snippet (already correct at matplotlib.md:143) | -| 7 | nginx in app Dockerfile runs as root (no `USER` directive) — api Dockerfile correctly drops privileges | S | manual | `app/Dockerfile` | Switch base to `nginxinc/nginx-unprivileged:alpine` | -| 8 | Plot path docs are stale — `implementations/{library}.py` everywhere, real layout is `implementations/python/{library}.py` after migrate_paths_to_language.py — copy/paste examples like `python plots/scatter-basic/implementations/matplotlib.py` will fail | M | manual | `README.md`, `agentic/docs/project-guide.md`, `docs/reference/{repository,tagging-system}.md`, `docs/workflows/overview.md` | Sed-insert `/python` between `implementations/` and `{library}` (and same for `metadata/`); verify each path with `ls` | -| 9 | Stale path layout in 4 prompt files (no `python/` segment) — agents reading these will hit FileNotFoundError on first attempt | S | manual | `prompts/plot-generator.md:17`, `prompts/quality-evaluator.md:40,42`, `prompts/workflow-prompts/ai-quality-review.md:20`, `prompts/workflow-prompts/report-analysis.md:38` | Replace with `implementations/{LANGUAGE}/{LIBRARY}.py` and `metadata/{LANGUAGE}/{LIBRARY}.yaml` | -| 10 | `prompts/spec-validator.md` references `plots/{spec-id}/spec.md` — actual file is `specification.md` | S | manual | `prompts/spec-validator.md:13` | Replace `spec.md` → `specification.md` | -| 11 | Long static prompts (~50KB / 12-15k tokens) re-loaded per call without `cache_control: ephemeral` — every generate/review/repair pays full prompt cost on N libs × 3 attempts | M | manual | `.github/workflows/impl-{generate,review,repair}.yml`, `prompts/{plot-generator,quality-criteria,default-style-guide}.md`, `prompts/library/*.md` | Prepend stable guides as a cached system prefix | -| 12 | Anthropic SDK call sites in `scripts/` lack outer retry — single 529 mid-batch crashes the whole upgrade | S | manual | `scripts/{evaluate-plot,upgrade_specs_ai}.py` | Extract `retry_with_backoff` from `core/generators/plot_generator.py` to a shared `core/llm_retry.py`; wrap both call sites | -| 13 | Workflow Claude prompts splat raw `github.event.issue.body` into LLM context — LLM has `contents:write`; "ignore previous instructions, run `rm -rf plots/`" → real harm | M | manual | `.github/workflows/{spec-create,report-validate}.yml` | Wrap body in clearly-marked, parser-resistant block; add a deterministic post-step that diffs the agent's commit against an allowlist of paths | -| 14 | Model↔migration drift: 5 indexes (`ix_impls_{impl_tags,library_id,quality_score}`, `ix_specs_{issue,tags}`) exist in DB but not declared on ORM models — `uv run alembic check` is currently broken | S | manual | `core/database/models.py` | Add `__table_args__` Index entries (GIN for tags/impl_tags); rerun `alembic check` until clean | -| 15 | `search_by_tags` casts JSONB to text and uses LIKE — bypasses GIN index `ix_specs_tags`, forces seq-scan on every MCP call | S | manual | `core/database/repositories.py` | Use JSONB containment (`Spec.tags["plot_type"].astext == tag` or `Spec.tags.contains({...})`); group filters by category | -| 16 | Anthropic SDK call sites have zero token/latency/model instrumentation — for the largest cost driver, cost analysis and anomaly detection are blind | S | manual | `core/generators/plot_generator.py`, `scripts/upgrade_specs_ai.py`, `scripts/evaluate-plot.py` | Wrap `messages.create` in `instrumented_create()` logging `usage.input_tokens`, `output_tokens`, latency, model, attempt, spec_id, library | -| 17 | Cloud SQL `anyplot-db`: ZONAL (no failover), `deletionProtectionEnabled: false`, public IP with two manually-allowlisted /32s, `test` database on prod instance | M | manual | gcp:sql/instances/anyplot-db | Enable deletionProtection now; consider REGIONAL HA; prefer Cloud SQL Auth Proxy + IAM auth over IP allowlist; drop `test` DB from prod instance | -| 18 | Cloud Run revision sprawl: 24 retained for anyplot-api, 42 for anyplot-app (12 days old) — heading toward 1000-revision cap | S | manual | gcp:run/services/anyplot-{api,app} | Set annotation `run.googleapis.com/max-retained-revisions=10` | -| 19 | Cloud Build SA holds `roles/run.admin` project-wide; Cloud Build is barely used (3 runs total); compromised trigger = redeploy services with malicious images | S | manual | gcp:iam/policy | Remove binding (or scope to specific region/service); also re-evaluate cloudbuild.builds.builder + secretmanager.secretAccessor | -| 20 | No Cloud Monitoring alerting policies configured — anyplot-api 5xx, Cloud SQL CPU/storage/conn-pool, Cloud Build failures all silent | M | manual | gcp:monitoring/alertPolicies | Create at minimum: api 5xx >1%/5min, SQL CPU>80%/10min + storage>85%, SQL conn>80% of max | -| 21 | 1 open CodeQL `error`-severity URL-redirection alert untriaged 5d in `api/routers/seo.py:328` | S | manual | `api/routers/seo.py:328` | Validate redirect target against allow-list of internal paths (or `urlparse` and reject if `netloc` is set) | -| 22 | `prompts/workflow-prompts/README.md` table lists fictional filenames (`generate-implementation.md`, `improve-from-feedback.md`) — agents discovering prompts via README will look for files that don't exist | S | manual | `prompts/workflow-prompts/README.md` | Resync to actual files: `impl-generate-claude.md`, `impl-repair-claude.md`, `ai-quality-review.md`, `report-analysis.md` | -| 23 | PageSpeed Insights anonymous quota = 0 for this auditor's project — every scheduled audit returns zero coverage until `PAGESPEED_API_KEY` is provisioned | S | manual | psi:https://anyplot.ai/[mobile] | Provision a free PSI key (25k queries/day), expose as `PAGESPEED_API_KEY`; auditor already has `&key=$PAGESPEED_API_KEY` plumbing | -| 24 | SPA shell ships empty `
` for every URL — initial HTML is 6849 bytes of identical shell, no SEO content, LCP bound to JS hydration; with ~530KB JS and 4G mobile, lab CWV will be in 'poor' bucket | L | manual | psi:https://anyplot.ai/[mobile], `app/index.html` | Add Vite SSG for top routes (`vite-plugin-ssr` or `vike`), or migrate public surface to Astro/Next; cheap interim: ship above-the-fold HTML skeleton in `app/index.html` | -| 25 | Search-engine bots (Googlebot, Bingbot) NOT in `$is_bot` map → they get the empty SPA shell + homepage's generic title/og:url for every one of ~3017 sitemap URLs; only social bots are correctly proxied | S | manual | `app/nginx.conf:1-31` | Add `googlebot,bingbot,duckduckbot,yandexbot,baiduspider,applebot` to the regex | -| 26 | Impl pages have only `BreadcrumbList` JSON-LD — missing `SoftwareSourceCode` schema on a 2696-page corpus of code samples (rich-result eligibility on the table) | M | manual | `app/src/pages/SpecPage.tsx:336-358`, `app/index.html:54-76` | Emit `@type: SoftwareSourceCode` per impl with `programmingLanguage`, `codeSampleType`, `author`, `isBasedOn`; bonus: add `WebSite + SearchAction` and `Organization` site-wide | -| 27 | SpecTabs uses `window.location.href` for in-app nav — full reload, drops AppDataContext, re-fetches `/specs`/`/libraries`/`/stats` on every tag click | S | manual | `app/src/components/SpecTabs.tsx:217` | Use `useNavigate()` | -| 28 | `useFilterFetch` re-fetches on every render: depends on `activeFilters` array reference, not its contents | M | manual | `app/src/hooks/useFilterFetch.ts:70` | Memoize URL key with `useMemo(() => buildQueryString(activeFilters), [activeFilters])`; depend on the string | -| 29 | `useFilterState` sync-back effect depends on `[allImages, displayedImages, ...]` (8 deps incl. arrays) — combined with the fetch issue can re-trigger network when state echoes | M | manual | `app/src/hooks/useFilterState.ts:121` | Move sync-back into `useFilterFetch` success path or coalesce in `useLayoutEffect` with stringify comparison | +| 1 | Model-migration Drift (Missing Indexes) | M | manual | `alembic/versions/`, `core/database/models.py` | Run `alembic revision --autogenerate` to sync indexes | +| 2 | Invalid Model Name `claude-sonnet-4-6` | S | manual | `core/config.py` | Use valid Anthropic model identifier | +| 3 | Missing Prompt Caching | M | manual | `.github/workflows/`, `prompts/` | Add `cache_control: {"type": "ephemeral"}` to static guides | +| 4 | Missing Web Vitals (FCP/TTFB) | S | manual | `app/src/analytics/reportWebVitals.ts` | Instrument missing Core Web Vitals | +| 5 | Missing LLM Observability | M | manual | `scripts/evaluate-plot.py`, `scripts/upgrade_specs_ai.py` | Log token counts and latency for all LLM calls | +| 6 | Lack of Request/Correlation IDs | M | manual | `api/main.py`, `core/config.py` | Add Request-ID middleware for async log correlation | +| 7 | Type-checking Bypass (mypy ignore_errors) | M | manual | `pyproject.toml` | Remove `ignore_errors = true` for core modules | +| 8 | Architectural Drift in Documentation | S | manual | `docs/reference/`, `README.md` | Update docs to reflect modular router structure | ## Medium (Importance 3) - -| # | Finding | Effort | Auto-fix | Files | Hint | -|---|---------|--------|----------|-------|------| -| 1 | Cache-invalidate token compared with `!=` (timing) | S | manual | `api/routers/debug.py:420` | `secrets.compare_digest(x_cache_token or "", expected)` | -| 2 | Dead `core/generators/plot_generator.py` (~440 lines) refs nonexistent `specs/` and `rules/` dirs; only test references | S | manual | `core/generators/plot_generator.py`, `core/generators/__init__.py`, `tests/unit/core/generators/test_plot_generator.py` | Delete file + test; confirm no doc invokes `python -m core.generators.plot_generator` | -| 3 | `_locks` dict in `api/cache.py` grows unbounded — never cleaned by `clear_cache_by_pattern` | S | manual | `api/cache.py` | Bound with `cachetools.LRUCache(maxsize=2*cache_maxsize)` or wipe stale on eviction | -| 4 | `Optional[X]` used in pre-3.10 form throughout `core/` (~50 occurrences); rest of codebase uses `X \| None` | M | codemod | `core/{config,database/{connection,models,repositories,types}}.py` | `pyupgrade --py310-plus core/` then drop `from typing import Optional` | -| 5 | `subprocess.run(["pngquant","--version"])` at module import has no timeout — can wedge FastAPI startup | S | manual | `core/images.py:102-107` | Add `timeout=5`; wrap in try/except `TimeoutExpired`; degrade to Pillow | -| 6 | `download_image` creates `httpx.AsyncClient()` per request with no timeout — outlier; other endpoints use shared client + explicit timeout | S | manual | `api/routers/download.py:36` | Use shared `_get_http_client` pattern from og_images.py; `timeout=15.0` | -| 7 | `get_library_images` un-defers `Impl.code` for every impl across DB (~13 MB) only to filter to one library | S | manual | `api/routers/libraries.py:93` | Use `ImplRepository.get_by_library` with `selectinload(Impl.spec)` + `undefer(Impl.code)` | -| 8 | `clear_spec_cache` invalidates ALL `filter:` keys on every spec edit — every PR merge cold-starts dozens of filter URL variants | M | manual | `api/cache.py:150` | Invalidate only `filter:` keys whose value strings reference the affected spec_id/library | -| 9 | 5 components/pages exceed 480 lines (DebugPage 913, FilterBar 838, SpecTabs 743, SpecPage 519, LandingPage 480) | L | manual | `app/src/pages/{DebugPage,SpecPage,LandingPage}.tsx`, `app/src/components/{FilterBar,SpecTabs}.tsx` | Extract sub-components per file (see frontend-auditor for split plan) | -| 10 | `ErrorBoundary` uses MUI `text.secondary` / `grey.100` instead of project CSS vars (`--ink-*`, `--bg-surface`) — low contrast on dark theme | S | manual | `app/src/components/ErrorBoundary.tsx` | Replace with `var(--ink-muted)` and `var(--bg-surface)` | -| 11 | `ErrorBoundary` and `RouteErrorBoundary` use barrel imports `from '@mui/material'` — every other file uses per-component subpath imports | S | codemod | `app/src/components/{ErrorBoundary,RouteErrorBoundary}.tsx` | Convert to `import Box from '@mui/material/Box'; ...` | -| 12 | `useFeaturedSpecs` uses biased sort-shuffle (`[...x].sort(()=>Math.random()-0.5)`) | S | manual | `app/src/hooks/useFeaturedSpecs.ts:56` | Reuse Fisher-Yates from `useFilterFetch.ts:15-22` (extract to `utils/`) | -| 13 | Many fetches use only a `cancelled = true` flag — request still completes; race risk on rapid prop changes | M | manual | `app/src/components/{RelatedSpecs,PlotOfTheDay,Layout}.tsx`, `app/src/hooks/{useFeaturedSpecs,usePlotOfTheDay}.ts`, `app/src/pages/{StatsPage,SpecPage,SpecsListPage}.tsx` | Switch to `AbortController`; check `signal.aborted` before each setState | -| 14 | FilterBar scroll listener attached without `{ passive: true }` and unthrottled; reads `scrollHeight`/`innerHeight` on every wheel tick | S | manual | `app/src/components/FilterBar.tsx:88`, `app/src/pages/{PlotsPage,SpecsListPage}.tsx` | Pass `{ passive: true }`; wrap in `requestAnimationFrame` debouncer | -| 15 | Clickable `` toggles lack `role="button"`/`tabIndex`/`onKeyDown` — keyboard users can't toggle | M | manual | `app/src/components/{ImageCard,SpecOverview}.tsx` | Add the same `role/tabIndex/onKeyDown` pattern used at ImageCard.tsx:135-137 | -| 16 | `SpecTabs` caches global tag counts in module-level `let` — survives navigation, leaks between tests | S | manual | `app/src/components/SpecTabs.tsx:25` | Move to AppDataContext or a `useTagCounts` hook with React Query semantics | -| 17 | `tests/README.md` references nonexistent files: `test_api_endpoints.py` (actual `test_api_postgres.py`), `ci-unittest.yml` (actual `ci-tests.yml`) | S | manual | `tests/README.md:17,140` | Replace with correct names | -| 18 | `agentic/docs/project-guide.md` instructs `bash .github/scripts/setup-labels.sh` — that dir doesn't exist; actual is `automation/scripts/label_manager.py` | S | manual | `agentic/docs/project-guide.md:669` | Replace or remove the section | -| 19 | `prompts/README.md` Overview table missing `default-style-guide.md`, `spec-tags-generator.md`, `impl-tags-generator.md`; example references nonexistent `gen-new-plot.yml` | S | manual | `prompts/README.md` | Regenerate from `ls prompts/*.md prompts/workflow-prompts/*.md` | -| 20 | `agentic/docs/project-guide.md` "Prompt Files" table omits `default-style-guide.md` and `workflow-prompts/` | S | manual | `agentic/docs/project-guide.md:439-448` | Add the missing rows | -| 21 | spec-create.yml duplicates a 100-line Claude prompt verbatim for retry; impl-generate.yml duplicates the impl-generate prompt; both are drift hotspots | M | manual | `.github/workflows/{spec-create,impl-generate}.yml` | Extract to `prompts/workflow-prompts/spec-create-claude.md`; both steps reference it | -| 22 | `.github/workflows/spec-create.yml` lines 137,241 say "Follow tagging-system.md guide" — file doesn't exist (`spec-tags-generator.md` does) | S | manual | `.github/workflows/spec-create.yml` | Replace `tagging-system.md` → `spec-tags-generator.md` | -| 23 | Bash `set -e` not enforced on long multi-step `run:` blocks — `\|\| true` and `2>/dev/null` patterns mask failures | S | manual | `.github/workflows/{impl-generate,impl-review,impl-merge,impl-repair,spec-create}.yml` | `defaults: { run: { shell: 'bash -euxo pipefail {0}' } }` at workflow root | -| 24 | Hardcoded `--model sonnet`/`--model opus` in 7 workflow YAMLs — bumping the model means editing 9 lines instead of one config | S | manual | `.github/workflows/{impl-generate,impl-review,impl-repair,spec-create,util-claude,report-validate}.yml` | Use `claude_args: "--model ${{ vars.CLAUDE_MODEL }}"` | -| 25 | `claude_review_model` referenced by audit doc but doesn't exist in `core/config.py` | S | manual | `core/config.py` or `agentic/commands/audit/llm-pipeline-auditor.md:7` | Either add the setting and route reviews through it, or drop the doc reference | -| 26 | Inconsistent placeholder syntax `{LIBRARY}` vs `${LIBRARY}` across workflow prompts; neither is actually substituted (variables go in a separate block) | S | manual | `prompts/workflow-prompts/{impl-generate-claude,impl-repair-claude,ai-quality-review}.md` | Standardize on bare `{LIBRARY}` style | -| 27 | Node version mismatch: Dockerfile uses node:20-alpine, CI uses node:24 — bundles tested on different runtime than prod | S | manual | `app/Dockerfile`, `.github/workflows/ci-tests.yml:190` | Pick one (LTS=node:22), add `.nvmrc` | -| 28 | api Dockerfile installs deps without a `uv.lock` — every build re-resolves; app Dockerfile copies `.` into context (likely missing `.dockerignore`) | S | manual | `api/Dockerfile`, `app/Dockerfile`, `.dockerignore` | Add uv.lock to repo + `COPY uv.lock .` before sync; verify `.dockerignore` | -| 29 | `tsconfig.json` strict on, but missing `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitOverride` — catches a class of `undefined`-access bugs | S | manual | `app/tsconfig.json` | Add those flags (do `noUncheckedIndexedAccess` first) | -| 30 | `BaseRepository.{create,update,delete}` each call `await session.commit()` in addition to outer `get_db()` commit — upserts double-commit; partial sync run can leave half rows committed | M | manual | `core/database/repositories.py:81,97,107` | Push commits to unit-of-work boundary; expose `flush()`-only methods | -| 31 | `search_by_tags` doesn't `selectinload(Impl.library)` — MCP `search_specifications` lazy-loads `impl.library` — same MissingGreenlet class as Critical #4 | S | manual | `core/database/repositories.py` | Add `.selectinload(Spec.impls).selectinload(Impl.library)` (matches `get_by_id`) | -| 32 | Initial schema migration creates `specs.{applications,data,notes}` ARRAY columns `nullable=False` without `server_default='{}'` (later migration d1d415f44d31 sets the default for review_* arrays — inconsistent) | S | manual | `alembic/versions/393d66bd73d9_initial_schema.py` | Match the d1d415f44d31 pattern with `server_default=sa.text("'{}'::varchar[]")` | -| 33 | f2d9c8a1b4e0 ADD COLUMN language_id NOT NULL DEFAULT 'python' takes AccessExclusiveLock for entire transaction (also rebuilds unique constraint, alters preview cols) — fine on small impls today, flag for review | S | manual | `alembic/versions/f2d9c8a1b4e0_add_languages_table_and_preview_variants.py` | Split: (1) add nullable + backfill + NOT NULL; (2) constraint swap; (3) preview rename. Or annotate with explicit lock_timeout | -| 34 | MCP server fully unauthenticated and public; no rate limit anywhere in `api/` (no slowapi); single client can exhaust Cloud SQL pool | M | manual | `api/mcp/server.py`, `api/main.py` | Add slowapi `Limiter`, `@limit("60/minute")` on MCP + DB-backed routers; consider `MCP_API_KEY` | -| 35 | Missing security headers (CSP, HSTS, X-Frame-Options, Permissions-Policy, Referrer-Policy) — only `/proxy/html` sets some | S | manual | `api/main.py`, `api/routers/og_images.py` | Small ASGI middleware injecting baseline headers; `frame-ancestors 'self' https://anyplot.ai` for iframe HTML | -| 36 | Web Vitals reporter ships only LCP/CLS/INP — FCP and TTFB missing (both supported by `web-vitals` library) | S | manual | `app/src/analytics/reportWebVitals.ts:16`, `docs/reference/plausible.md` | Add `onFCP`, `onTTFB` imports + corresponding doc rows | -| 37 | `internal_link` and `external_link` destinations heavily out of sync with docs — code emits `about`, `legal_transparency`, `palette`, `github_*`, `library_docs`, `plausible` etc. that aren't documented; docs list `mcp`/`stats` that aren't emitted | M | manual | `docs/reference/plausible.md`, `app/src/pages/{AboutPage,StatsPage,LibrariesPage,SpecPage}.tsx` | Diff `grep -roE "destination: '[^']+'" app/src` against docs and update both directions | -| 38 | `page` value `spec_hub` used in code but missing from "Page Values" reference | S | manual | `app/src/pages/SpecPage.tsx:207,224`, `docs/reference/plausible.md:487-491` | Add `spec_hub` to the page values block + Required Custom Properties table | -| 39 | Cache layer has no hit/miss/age observability — cache effectiveness opaque | S | manual | `api/cache.py` | Add `logger.debug` in get/get_or_set, `logger.info` in `_schedule_refresh`; counters in `get_cache_stats()` | -| 40 | No request IDs / no async-context propagation — bg tasks (cache refresh, fire-and-forget Plausible) carry zero correlation with originating request | M | manual | `api/main.py`, `api/analytics.py`, `api/cache.py` | ASGI middleware creates `request_id` (uuid4), stored in `contextvars.ContextVar`; logging.Filter injects into every record | -| 41 | CLAUDE.md and `prime.md` use legacy `jet_brains_*` Serena tool names instead of canonical `mcp__serena__*` (matches `.claude/settings.json` allowlist) — `audit.md:90` already acknowledges the drift | S | codemod | `CLAUDE.md`, `agentic/commands/{prime,agentic}.md` | sed `jet_brains_` → `mcp__serena__jet_brains_` (or drop `jet_brains_` if unprefixed form is intended) | -| 42 | `/agentic` (563 lines) and `/audit` (258 lines) overlap heavily; `/audit` cleanly per-auditor-files; `/agentic` body inline | M | manual | `agentic/commands/{agentic,audit}.md` | Either deprecate `/agentic` (covered by `/audit agentic`) or split agentic's 12 leverage-point bodies into per-point files | -| 43 | `agentic/commands/update.md` is 725 lines — largest slash command; library-agent prompt (lines 495-707) loads on every `/update` invocation | L | manual | `agentic/commands/update.md` | Extract library-agent prompt to `agentic/commands/update/library-agent.md`; lead Reads at spawn | -| 44 | GCS bucket `anyplot_cloudbuild` has UBLA disabled, lives in US (cross-region from europe-west4 services) | S | manual | gcp:storage/buckets/anyplot_cloudbuild | Enable UBLA; if Cloud Build truly unused, empty + delete | -| 45 | Public buckets (anyplot-images, anyplot-static) lack explicit `public_access_prevention` enforcement (inherited); no lifecycle rules or versioning | S | manual | gcp:storage/buckets/anyplot-{images,static} | Public read is fine; restrict bindings via IAM conditions; add lifecycle for `staging/`/`tmp/` if ever introduced | -| 46 | anyplot-api: TCP-only startup probe, no liveness probe, containerConcurrency=15 (low for FastAPI), min=1 idle | S | manual | gcp:run/services/anyplot-api | Add HTTP startupProbe on `/health`; add livenessProbe; bump concurrency to 80; reconsider min=1 vs cold-start budget | -| 47 | Public `/debug/*` receiving sustained scanner traffic returning 503s instead of 401/403, polluting ERROR-rate metrics | S | manual | gcp:run/services/anyplot-api, `api/routers/debug.py` | Mount under auth dep returning 401; or env-gate router include in `api/main.py` | -| 48 | Both Cloud Run services use `ingress=all` — no Cloud Armor, no IAP, no LB; basic abuse-of-resources DoS reachable | L | manual | gcp:run/services/anyplot-{api,app} | If a global HTTPS LB already terminates anyplot.ai, set Cloud Run ingress to `internal-and-cloud-load-balancing`; add Cloud Armor for `/api/*` rate limit | -| 49 | Stale orphan branch `implementation/funnel-basic/pygal` whose 2 PRs already merged | S | manual | gh:branches/implementation/funnel-basic/pygal | `git push origin --delete`; patch impl-merge.yml so post-merge metadata push doesn't recreate the branch | -| 50 | 4 unreferenced repo secrets (DATABASE_URL, GCP_PROJECT_NUMBER, GCS_BUCKET, PROJECT_TOKEN) — dead secrets expand blast radius if leaked | S | manual | gh:secrets | Verify with grep, delete; rotate DATABASE_URL and PROJECT_TOKEN first if they ever granted access | -| 51 | Actions cache at 10.7 GiB, over the 10 GiB soft cap → silent LRU eviction on every new write | S | manual | gh:actions/caches | Inspect with `--jq '.actions_caches \| sort_by(-.size_in_bytes)'`; delete largest/oldest; narrow cache keys | -| 52 | 1610 Actions artifacts on disk; oldest is 2.5 months past its own expiry (GH not GC'ing) | S | manual | gh:actions/artifacts | Manual purge; lower retention via `actions/upload-artifact@v4 retention-days: 7` | -| 53 | 211/327 specs (~65%) have `updated: null` in specification.yaml even after impl pipeline ran — field is dead | S | manual | `plots/*/specification.yaml` | Bump field in impl-merge.yml on first non-trivial spec change, or remove the field | -| 54 | library_version drift: altair 290/303 metadata files report 6.0.0 but `lib-altair >=6.1.0`; plotly 289/307 below floor; highcharts 281/302 report `unknown` | M | manual | `plots/*/metadata/python/{altair,plotly,highcharts,pygal}.yaml` | Echo `pip show ` in impl-generate; pick a direction (bump env or lower floor); fix highcharts probe via `importlib.metadata.version` | -| 55 | Trailing-slash variants not canonicalized at edge → `/area-basic/` and `/area-basic` both 200 with same SPA shell | S | manual | `app/nginx.conf:33-130` | `rewrite ^/(.+)/$ /$1 permanent;` | -| 56 | Non-existent routes return HTTP 200 with SPA shell (soft-404) — Google may drop or warn | M | manual | `app/src/pages/NotFoundPage.tsx`, `app/nginx.conf:87-96` | Quick: add `` to NotFoundPage; proper: nginx pre-resolves unknown routes against API | -| 57 | Only one site-level JSON-LD (`WebApplication`) — missing `WebSite + SearchAction` (sitelinks search box) and `Organization` (entity recognition) | S | manual | `app/index.html:54-76` | Add both blocks; validate at https://validator.schema.org/ | -| 58 | Duplicate `Implementation` interface declaration (LibraryPills.tsx local vs canonical types/index.ts) | S | manual | `app/src/components/LibraryPills.tsx:21`, `app/src/types/index.ts:124` | `import type { Implementation } from '../types';` and remove local | -| 59 | ~530 KB of JS in critical path (`mui` 278 KB + `vendor` 272 KB + `index` 31 KB decompressed; ~200 KB gzipped) | M | manual | psi:https://anyplot.ai/[mobile] | Audit MUI tree-shaking (subpath imports); lazy-load syntax-highlighted code view; consider lighter primitives on marketing pages | -| 60 | Quality skew: pygal avg quality_score 86.5 with 33 implementations <80 vs all other libs avg ≥90.4 — affects user trust if promoted equally on UI | M | manual | `plots/*/metadata/python/pygal.yaml` | Either separate pygal in catalog UI ranking, or tune the rubric for SVG-first libraries | -| 61 | SDK self-review parses verdict via brittle string matching (`if "## Verdict\nPASS" in review_feedback or "Verdict: PASS"...`) | M | manual | `core/generators/plot_generator.py:365` | Use `tool_use` block with Pydantic `ReviewVerdict` model; force tool call. (If deleting per dead-code finding, ignore.) | -| 62 | `evaluate-plot.py` defends JSON parse but `upgrade_specs_ai.py` doesn't — silent overwrite of spec with raw response if no fenced block | S | manual | `scripts/upgrade_specs_ai.py:165-176` | Mirror evaluate-plot.py defensive pattern | - -## Low (Importance 2) - | # | Finding | Effort | Auto-fix | Files | Hint | |---|---------|--------|----------|-------|------| -| 1 | `random` imported inside hot loop in insights router; `from sqlalchemy import ...` inside `search_by_tags` body | S | ruff | `api/routers/insights.py:519`, `core/database/repositories.py:174` | Move to module-top imports (Ruff `PLC0415`) | -| 2 | `_validate_quality_score` would coerce `bool` to 1.0 (bool is subclass of int) | S | manual | `automation/scripts/sync_to_postgres.py` | `if isinstance(score, bool): return None` before `float(score)` | -| 3 | `extract_and_validate_code` markdown extraction breaks on a single ``` fence with `print("```")` in body | S | manual | `core/generators/plot_generator.py:53-56` | Use `re.search(r"```(?:python)?\n(.+?)\n```", text, re.DOTALL)` | -| 4 | `extract_branch_info` rejects branches with extra `/` segments | S | manual | `automation/scripts/workflow_utils.py:45-49` | Use `branch.split("/", 2)` to fold trailing parts | -| 5 | `download_image` Content-Disposition built via f-string (low real risk; relies on path validation it doesn't perform) | S | manual | `api/routers/download.py:48` | Route segments through `_SPEC_ID_RE` or use `urllib.parse.quote` + RFC-5987 form | -| 6 | `tests/README.md` directory tree omits `tests/integration/api/` | S | manual | `tests/README.md` | Add `api/` subtree under `integration/` | -| 7 | Stale `automation/generators/__pycache__/plot_generator.cpython-313.pyc` — confusing leftover from old layout | S | manual | `automation/generators/` | `rm -rf` (gitignored, safe); add to make-clean target | -| 8 | Empty `tests/unit/plots/__init__.py` package adds no value (plot validation is pipeline-driven) | S | manual | `tests/unit/plots/` | Delete or add a 1-line README pointing to `impl-review.yml` | -| 9 | Docs reference superseded Claude model id `claude-opus-4-5-20251101` while workflow code uses `claude-opus-4-7` | S | manual | `docs/reference/{api,database,repository}.md`, `agentic/docs/project-guide.md` | Update example model ids or use `claude-opus-{version}` placeholder | -| 10 | Index-as-key in lists with stable identifiers available | S | manual | `app/src/components/{SpecTabs,PaletteStrip}.tsx`, `app/src/pages/{PalettePage,SpecsListPage,McpPage}.tsx` | Use value/spec_id as key | -| 11 | `useThemeMode` reads localStorage/matchMedia at render-init without consistent SSR-safety; `setMode` and `cycle` duplicate persist branch | S | manual | `app/src/hooks/useThemeMode.ts` | Extract `applyMode(mode)` helper; add `typeof window` guard to `systemPrefersDark` | -| 12 | `SpecOverview` re-sorts `implementations` on every render; `ImplementationCard` not memoized | S | manual | `app/src/components/SpecOverview.tsx:63` | Wrap sort in `useMemo`; export `ImplementationCard` as `memo(...)` with stable callbacks | -| 13 | Pip 26.0.1 has CVE-2026-3219 (build-only impact, but advisory is published) | S | manual | `pyproject.toml`, `uv.lock` | Bump pip in venv/Dockerfile; yarn audit (frontend, 119 packages) clean | -| 14 | `pyproject.toml` ruff `B008` ignored globally — hides legit `B008` outside FastAPI | S | ruff | `pyproject.toml` | Move to `[tool.ruff.lint.per-file-ignores] "api/**/*.py" = ["B008"]` | -| 15 | ESLint config doesn't lint test files for TypeScript rules | S | manual | `app/eslint.config.js` | Have test-file block extend `@typescript-eslint` plugin; or fold test files into main block | -| 16 | `ENVIRONMENT='development'` enables SQL `echo=True` for both engines — leaks bound params to stdout | S | manual | `core/database/connection.py:98,113` | Replace with explicit `DB_ECHO` env var | -| 17 | `impls.language_id` has no standalone index (FKs aren't auto-indexed in Postgres) | S | manual | `core/database/models.py`, `f2d9c8a1b4e0_*.py` | Add `Index("ix_impls_language_id", "language_id")` when fixing index drift | -| 18 | Backward-compat `Library.language = synonym("language_id")` is misleading after FK refactor — same value today, will diverge once non-Python ships | M | manual | `core/database/models.py`, `api/routers/{seo,specs,plots,insights,og_images}.py`, `api/mcp/server.py` | Drop synonym; switch callers to `library.language_id` (ID) or `library.language_ref.name` (label) | -| 19 | Asyncio init lock cached as module global is fragile across event loops | S | manual | `core/database/connection.py:49` | Re-create when bound loop is closed, or skip the lock and rely on FastAPI startup event | -| 20 | `_connector.close()` (sync) called from async `close_db` — Cloud SQL connector v1.7+ has `close_async()` | S | manual | `core/database/connection.py:230` | `await _connector.close_async()` (fallback to sync for old versions) | -| 21 | Background analytics task swallows errors at DEBUG level — total Plausible-pipeline outages invisible in Cloud Run | S | manual | `api/analytics.py:124` | Promote to `logger.warning` or add sample-rate counter | -| 22 | `og_image_view` "platform" doc lists `signal` but code emits `whatsapp-lite` (Signal spoofs WhatsApp UA) | S | manual | `api/analytics.py`, `docs/reference/plausible.md:498-499` | Replace `signal` → `whatsapp-lite` with note | -| 23 | Plausible script gated only by hostname equality — future preview deploy at `staging.anyplot.ai` would silently send to prod | S | manual | `app/src/hooks/useAnalytics.ts:93`, `app/src/analytics/reportWebVitals.ts:11` | Use `import.meta.env.PROD` + `endsWith('anyplot.ai')`; or `VITE_PLAUSIBLE_DOMAIN` env var | -| 24 | No DNT header check in server-side OG-image tracking (Plausible is GDPR-compliant — flagging for completeness) | S | manual | `api/analytics.py:138` | Skip create_task when `request.headers.get("dnt") == "1"` | -| 25 | `agentic/runs/8c014937/` is committed despite README claiming runs/ is gitignored — references former project name `pyplots` | S | manual | `agentic/runs/8c014937/`, `agentic/README.md` | Confirm gitignore covers `agentic/runs/*/`; rm stale dir | -| 26 | `agentic/specs/` holds 5 dormant Feb-2026 files; pattern conflicts with active `plots/{spec-id}/specification.md` | S | manual | `agentic/specs/`, `agentic/README.md`, `agentic/commands/agentic.md` | Either revive (document who writes here) or move under `agentic/specs/archive/` with a README | -| 27 | `.claude/settings.local.json` allowlists ~17 chrome-devtools MCP tools + `mcp__ai-agent-guidelines__*` for unregistered MCP servers | S | manual | `.claude/settings.local.json` | Prune obsolete entries; add comment noting it's session-accumulated | -| 28 | `agentic/commands/audit/agentic-auditor.md` references nonexistent `agentic/scripts/` directory in scope | S | manual | `agentic/commands/audit/agentic-auditor.md:10` | Drop or replace with `agentic/runs/`/`agentic/specs/` | -| 29 | `anyplot-app` Cloud Run min=1 with TCP-only startup probe (frontend rarely needs warm instance) | S | manual | gcp:run/services/anyplot-app | Set min=0 unless cold-start unacceptable; HTTP `/health` probe | -| 30 | 32 Google APIs enabled, several look unused (BigQuery suite, Pub/Sub, Datastore, Dataform, Dataplex, legacy Container Registry) | S | manual | gcp:services | Disable APIs with no resources; replace Container Registry → Artifact Registry already in use | -| 31 | Quality-score label sprawl — ~50 single-purpose `quality:NN` labels (one per integer score) plus a malformed empty `quality:` and stray `quality:5` | S | manual | gh:labels | Replace per-score labels with buckets `quality:{excellent,good,fair,poor}`; sweep delete the per-score labels | -| 32 | No CODEOWNERS file in repo — workflows can commit to main without forced second look | S | manual | `.github/CODEOWNERS` | Map `.github/workflows/`, `prompts/`, `alembic/`, `agentic/commands/` to `@MarkusNeusinger`; enable `require_code_owner_review` | -| 33 | `secret_scanning_validity_checks` and `secret_scanning_non_provider_patterns` disabled (free for public repos) | S | manual | gh:repo (security_and_analysis) | Toggle both in Settings → Code security and analysis | -| 34 | 14 implementations carry quality_score < 70 — review-flagged stragglers never repaired (mostly pygal) | M | manual | `plots/*/metadata/python/{altair,bokeh,pygal,seaborn}.yaml` (14 files) | Re-trigger impl-generate for `quality_score < 70`; add gate to impl-merge so they don't auto-merge | -| 35 | HTML responses served `cache-control: no-cache` (correct for SPA shell, but disables CF edge cache); CWV TTFB unnecessarily high for distant regions | S | manual | psi:https://anyplot.ai/[mobile] | CF Page Rule: `text/html` → `s-maxage=60, stale-while-revalidate=300, no-cache` | -| 36 | 3 fonts preloaded on every page (italic + Latin-1 Supplement may not be needed above the fold) | S | manual | psi:https://anyplot.ai/[mobile] | Drop italic/Latin-1 preloads to lazy load; preconnect is enough | -| 37 | Production HTML lacks CSP, Referrer-Policy, Permissions-Policy, X-Frame-Options (HSTS + nosniff present) | M | manual | psi:https://anyplot.ai/[mobile] | Add via CF Transform Rules or origin headers; CSP starts as `Content-Security-Policy-Report-Only` | -| 38 | `app/nginx.conf:148-253` defines a `python.anyplot.ai` server block — DNS does not resolve (NXDOMAIN); dead code | S | manual | `app/nginx.conf:132-253` | Provision the subdomain or delete the block | -| 39 | `app/index.html` meta description 232 chars (>160 Google snippet); `` ignored by all engines | S | manual | `app/index.html:7-8` | Trim description to ~150; remove keywords meta | -| 40 | Cloudflare Bot Fight Mode injects 1×1 challenge iframe on every page — third-party-summary cost; OSS catalog probably doesn't need it | S | manual | psi:https://anyplot.ai/[mobile] | Decide in CF dashboard → Security → Bots | -| 41 | 28 long-lived open issues; 15 spec-ready+approved sitting 15 days without bulk-generate launched (== same set as Critical #8) | M | manual | gh:issues (label:spec-ready) | (See Critical #8) | -| 42 | CLAUDE.md lists Serena tools without canonical `mcp__serena__` prefix (DUP with Medium #41 — different tool list) | S | manual | `CLAUDE.md` | (See Medium #41) | +| 1 | Scalability Bottleneck in Filtering | L | manual | `api/routers/plots.py` | Move filtering logic from in-memory to SQL | +| 2 | God Test File `test_routers.py` | M | manual | `tests/unit/api/test_routers.py` | Split large test file into modular router tests | +| 3 | Implementation Gaps in Catalog | XL | manual | `plots/` | Generate missing implementations for newer specs | +| 4 | Label Fragmentation | S | manual | `gh:labels` | Consolidate quality score labels | +| 5 | Agentic Command Typo (`dokument.md`) | S | codemod | `agentic/commands/dokument.md` | Rename to `document.md` and update references | ## Positive Patterns (Importance 1) - -- Workload Identity Federation correctly configured for GitHub Actions deploys to GCP — no long-lived JSON keys for CI. Pattern to keep. -- Cloud SQL backups + PITR with 7-day transaction-log retention in Cloud Storage. Correct baseline; pair with deletion protection (separate finding). -- All 14 GitHub workflows healthy: 0 failures across 158 non-skipped runs in the last 30d. Capture the impl-* retry strategy as the canonical template for future LLM-driven workflows. -- Test fixture organization is solid: shared fixtures in `conftest.py`, E2E gracefully skips on missing DATABASE_URL, 1428 tests collected, no collection errors. -- `/audit` correctly externalizes per-specialist prompts (one-file-per-auditor) and reconciles the `mcp__serena__*` vs `jet_brains_*` naming drift; `/agentic` should adopt the same pattern. -- Strong frontend a11y + lazy-loading: every route code-split via `lazy()`, ImageCard handles keyboard activation/focus-visible/fetchPriority, FilterBar implements full keyboard nav. -- `useFilterFetch` and `useLatestRelease` correctly use AbortController with `signal.aborted` checks — pattern to reuse in other fetch hooks. -- Logger pattern consistency: every Python module uses `logging.getLogger(__name__)`; no `print()` in prod paths; no `console.log` in frontend prod (only `console.error` in genuine branches + DEV-gated debug). -- HTML preconnects to `storage.googleapis.com` and `https://api.anyplot.ai` with correct `crossorigin` flag. -- Healthy sitemap: 3017 URLs, valid XML, dynamically regenerated, even distribution across 9 libraries, last-mod dates on impl URLs. -- Tag/spec hygiene: all 327 specs have non-empty tags + all 4 required spec.md sections; no singleton tags; no obvious duplicate descriptions (only 2 overlapping pairs above 0.55 Jaccard, both intentional). -- `gh secret list` correctly returns names only; no auditor read any secret. - -## Cross-auditor synthesis (Phase 3) - -- **Stale spec batch** (Catalog × SEO × GitHub): the 15 specs from Critical #8 are independently flagged by catalog (no implementations), seo (missing from sitemap because seo.py:57-58 skips specs with empty `impls`), and github (15 spec-ready+approved issues idle 15 days). Single root cause — `bulk-generate.yml` was never run after spec-create finished. One fix unblocks all three views. -- **Path-layout drift after `migrate_paths_to_language.py`** (Quality × LLM-Pipeline × Agentic): three independent auditors flagged stale `implementations/{library}.py` references in docs (quality), prompts (llm-pipeline), and command/README files (agentic). Coordinated fix: a repo-wide sed-insert of `/python` in markdown sources, then verify with `ls`. -- **Workflow injection class** (Infra × Security): both auditors independently flagged the same `${{ github.event.* }}` interpolation pattern; deduplicated as Critical #2. The cleanest fix is a workflow-wide convention: all event fields go through `env:` blocks. -- **MissingGreenlet eager-load class** (DB × Backend implicit): MCP `get_implementation` (Critical #4) and `search_specifications` (Medium #31) share the same root — `selectinload(Impl.library)` missing from two repository methods. One PR fixes both call sites. -- **Web Vitals lab vs field divergence**: cannot compute (pagespeed blocked by quota, plausible blocked by missing key). Provision both keys and re-run. -- **Deprecation candidates** (Catalog × Plausible × SEO): cannot compute — Plausible is blocked, so no traffic data to intersect with catalog's 15 stale specs. +- **Exceptional Frontend Quality**: React 19, zero `any` usage, robust accessibility, and smart error boundaries. +- **Secure Prompt Design**: Hallucination mitigation via grounding examples and strict role definitions. +- **Strong Test Coverage**: 1:1 test mapping for automation scripts ensuring reliability of the generation pipeline. +- **Conditional Context Loading**: `agentic/commands/context.md` efficiently manages context window. ## Statistics - -- **Total**: 138 unique findings (after dedup) | **Critical**: 8, **High**: 29, **Medium**: 62, **Low**: 42 -- **Effort**: S 99, M 33, L 5, XL 0 -- **Auto-fix**: ruff 3, eslint 0, format 0, codemod 4, manual 131 -- **By Auditor (raw, pre-dedup)**: backend 15, frontend 17, infra 16, quality 11, llm-pipeline 13, db 11, security 11, observability 11, agentic 10, gcloud 14, github 10, plausible 0 (blocked), pagespeed 8, seo 8, catalog 5 -- **Cross-validation**: 1 reviewed, 1 dropped (backend's "Python-2 syntax" SyntaxError — verified false positive; Python 3.14 added PEP 758 unparenthesized except syntax, the file parses fine), 0 downgraded -- **Coverage**: 13 full, 1 partial (pagespeed — PSI anonymous quota=0; provision PAGESPEED_API_KEY), 1 blocked (plausible — PLAUSIBLE_API_KEY env var not set); seo ran in `structural-only` mode (gcloud token lacks `webmasters.readonly` scope) +- Total: 22 | Critical: 4, High: 8, Medium: 6, Low: 0, Positive: 4 +- Effort: S 10, M 8, L 2, XL 2 +- Auto-fix: ruff 1, codemod 1, manual 20 +- By Auditor: backend 5, frontend 0, infra 2, quality 2, llm 3, db 1, security 1, obs 4, agentic 2, gcloud 0, github 1, plausible 0, pagespeed 0, seo 0, catalog 1 +- Cross-validation: 13 reviewed, 0 dropped, 0 downgraded +- Coverage: 8 auditors complete, 4 partial, 3 blocked (gcloud, plausible, pagespeed) diff --git a/agentic/commands/bug.md b/agentic/commands/bug.md index 68907bede0..a8f1ec4cb2 100644 --- a/agentic/commands/bug.md +++ b/agentic/commands/bug.md @@ -24,12 +24,11 @@ prompt: $2 - `api/` - FastAPI backend - `main.py` - App entry point - `routers/` - API route handlers - - `services/` - Business logic - `app/` - React frontend (Vite + TypeScript) - `src/` - Source code - `core/` - Shared Python modules - - `models/` - Pydantic models - - `database/` - Database utilities + - `config.py` - Configuration + - `database/` - Database utilities, models, and repositories - `plots/` - Plot specifications and implementations - `tests/` - Test suites - `agentic/` - Agentic Layer diff --git a/agentic/commands/chore.md b/agentic/commands/chore.md index f7e9225016..21abcacfac 100644 --- a/agentic/commands/chore.md +++ b/agentic/commands/chore.md @@ -24,12 +24,11 @@ prompt: $2 - `api/` - FastAPI backend - `main.py` - App entry point - `routers/` - API route handlers - - `services/` - Business logic - `app/` - React frontend (Vite + TypeScript) - `src/` - Source code - `core/` - Shared Python modules - - `models/` - Pydantic models - - `database/` - Database utilities + - `config.py` - Configuration + - `database/` - Database utilities, models, and repositories - `plots/` - Plot specifications and implementations - `tests/` - Test suites - `agentic/` - Agentic Layer diff --git a/agentic/commands/dokument.md b/agentic/commands/document.md similarity index 100% rename from agentic/commands/dokument.md rename to agentic/commands/document.md diff --git a/agentic/commands/feature.md b/agentic/commands/feature.md index 383d6278aa..b357455dce 100644 --- a/agentic/commands/feature.md +++ b/agentic/commands/feature.md @@ -24,12 +24,11 @@ prompt: $2 - `api/` - FastAPI backend - `main.py` - App entry point - `routers/` - API route handlers - - `services/` - Business logic - `app/` - React frontend (Vite + TypeScript) - `src/` - Source code - `core/` - Shared Python modules - - `models/` - Pydantic models - - `database/` - Database utilities + - `config.py` - Configuration + - `database/` - Database utilities, models, and repositories - `plots/` - Plot specifications and implementations - `tests/` - Test suites - `agentic/` - Agentic Layer diff --git a/agentic/commands/refactor.md b/agentic/commands/refactor.md index d1e00d9847..27f1fc8daf 100644 --- a/agentic/commands/refactor.md +++ b/agentic/commands/refactor.md @@ -25,12 +25,11 @@ prompt: $2 - `api/` - FastAPI backend - `main.py` - App entry point - `routers/` - API route handlers - - `services/` - Business logic - `app/` - React frontend (Vite + TypeScript) - `src/` - Source code - `core/` - Shared Python modules - - `models/` - Pydantic models - - `database/` - Database utilities + - `config.py` - Configuration + - `database/` - Database utilities, models, and repositories - `plots/` - Plot specifications and implementations - `tests/` - Test suites - `agentic/` - Agentic Layer diff --git a/agentic/workflows/document.py b/agentic/workflows/document.py index 10c0c2981e..01d71e9037 100755 --- a/agentic/workflows/document.py +++ b/agentic/workflows/document.py @@ -12,7 +12,7 @@ """ Standalone document workflow. -Runs the dokument.md template against a spec file and records +Runs the document.md template against a spec file and records the output documentation path in state. Usage: @@ -42,7 +42,7 @@ # Template path -DOCUMENT_TEMPLATE = "agentic/commands/dokument.md" +DOCUMENT_TEMPLATE = "agentic/commands/document.md" # Usage hint for resolve_state error message DOCUMENT_USAGE_HINT = ( diff --git a/agentic/workflows/plan.py b/agentic/workflows/plan.py index 137683d921..fed03db302 100644 --- a/agentic/workflows/plan.py +++ b/agentic/workflows/plan.py @@ -166,7 +166,8 @@ def main(prompt: str, task_type: str, model: str, working_dir: str, cli: str): # ── Phase 1: Classify ─────────────────────────────────────────── if task_type is None: - console.print(Rule(f"[bold yellow]Phase 1: Classification ({model} model)[/bold yellow]")) + classifier_model = "small" # Always use small model for simple classification + console.print(Rule(f"[bold yellow]Phase 1: Classification ({classifier_model} model)[/bold yellow]")) console.print() try: @@ -184,7 +185,7 @@ def main(prompt: str, task_type: str, model: str, working_dir: str, cli: str): prompt=classify_prompt, run_id=run_id, agent_name="classifier", - model=model, + model=classifier_model, cli=cli, dangerously_skip_permissions=True, output_file=os.path.join(classify_output_dir, OUTPUT_JSONL), diff --git a/app/src/analytics/reportWebVitals.test.ts b/app/src/analytics/reportWebVitals.test.ts index a9224dd198..abd49c0e21 100644 --- a/app/src/analytics/reportWebVitals.test.ts +++ b/app/src/analytics/reportWebVitals.test.ts @@ -8,6 +8,8 @@ vi.mock('web-vitals', () => ({ onLCP: (cb: (m: { value: number; rating: string }) => void) => cb({ value: 2500, rating: 'good' }), onCLS: (cb: (m: { value: number; rating: string }) => void) => cb({ value: 0.15, rating: 'needs-improvement' }), onINP: (cb: (m: { value: number; rating: string }) => void) => cb({ value: 200, rating: 'good' }), + onFCP: (cb: (m: { value: number; rating: string }) => void) => cb({ value: 1200, rating: 'good' }), + onTTFB: (cb: (m: { value: number; rating: string }) => void) => cb({ value: 400, rating: 'good' }), })); describe('reportWebVitals', () => { @@ -66,6 +68,12 @@ describe('reportWebVitals', () => { expect(window.plausible).toHaveBeenCalledWith('INP', { props: { value: '200', rating: 'good' }, }); + expect(window.plausible).toHaveBeenCalledWith('FCP', { + props: { value: '1200', rating: 'good' }, + }); + expect(window.plausible).toHaveBeenCalledWith('TTFB', { + props: { value: '400', rating: 'good' }, + }); }); it('merges ambient analytics props (e.g. theme) into CWV events', async () => { @@ -89,5 +97,11 @@ describe('reportWebVitals', () => { expect(window.plausible).toHaveBeenCalledWith('INP', { props: { theme: 'dark', value: '200', rating: 'good' }, }); + expect(window.plausible).toHaveBeenCalledWith('FCP', { + props: { theme: 'dark', value: '1200', rating: 'good' }, + }); + expect(window.plausible).toHaveBeenCalledWith('TTFB', { + props: { theme: 'dark', value: '400', rating: 'good' }, + }); }); }); diff --git a/app/src/analytics/reportWebVitals.ts b/app/src/analytics/reportWebVitals.ts index 51d02c508a..ce7e3402e5 100644 --- a/app/src/analytics/reportWebVitals.ts +++ b/app/src/analytics/reportWebVitals.ts @@ -13,7 +13,7 @@ export function reportWebVitals() { return; } - import('web-vitals').then(({ onLCP, onCLS, onINP }) => { + import('web-vitals').then(({ onLCP, onCLS, onINP, onFCP, onTTFB }) => { onLCP((metric) => { window.plausible?.('LCP', { props: { @@ -43,6 +43,26 @@ export function reportWebVitals() { }, }); }); + + onFCP((metric) => { + window.plausible?.('FCP', { + props: { + ...getAnalyticsAmbientProps(), + value: String(Math.round(metric.value / 100) * 100), + rating: metric.rating, + }, + }); + }); + + onTTFB((metric) => { + window.plausible?.('TTFB', { + props: { + ...getAnalyticsAmbientProps(), + value: String(Math.round(metric.value / 100) * 100), + rating: metric.rating, + }, + }); + }); }) .catch(() => {}); } diff --git a/automation/scripts/sync_to_postgres.py b/automation/scripts/sync_to_postgres.py index 0e8ef77884..fd6ff09922 100644 --- a/automation/scripts/sync_to_postgres.py +++ b/automation/scripts/sync_to_postgres.py @@ -94,7 +94,7 @@ def _validate_quality_score(score) -> float | None: return score_float logger.warning(f"Quality score {score_float} out of range 0-100, setting to None") return None - except ValueError, TypeError: + except (ValueError, TypeError): logger.warning(f"Invalid quality score '{score}', setting to None") return None diff --git a/core/config.py b/core/config.py index aa30f8f75b..a46a3d57ed 100644 --- a/core/config.py +++ b/core/config.py @@ -88,7 +88,7 @@ class Settings(BaseSettings): # AI MODEL CONFIGURATION # ============================================================================= - claude_model: str = "claude-sonnet-4-6" + claude_model: str = "claude-3-5-sonnet-20240620" """Claude model to use for code generation and review""" claude_max_tokens: int = 4000 diff --git a/docs/reference/repository.md b/docs/reference/repository.md index 4bbc36689e..b4f44163a5 100644 --- a/docs/reference/repository.md +++ b/docs/reference/repository.md @@ -456,8 +456,13 @@ plt.savefig('plot.png', dpi=300) **Purpose**: FastAPI REST API -**Key Files**: -- `main.py` - FastAPI app with all endpoints +**Structure**: +- `main.py` - Application entry point and app factory +- `routers/` - Modular route handlers (specs, plots, analytics, etc.) +- `dependencies.py` - Shared FastAPI dependencies +- `schemas.py` - Pydantic request/response schemas +- `analytics.py` - Server-side Plausible tracking +- `cache.py` - Server-side TTL caching layer --- diff --git a/plots/stereonet-equal-area/implementations/python/highcharts.py b/plots/stereonet-equal-area/implementations/python/highcharts.py index 20faf1f69c..6ac6442956 100644 --- a/plots/stereonet-equal-area/implementations/python/highcharts.py +++ b/plots/stereonet-equal-area/implementations/python/highcharts.py @@ -302,7 +302,7 @@ contour_line.z_index = 2 chart.add_series(contour_line) extracted = True - except AttributeError, TypeError: + except (AttributeError, TypeError): pass if not extracted: @@ -321,7 +321,7 @@ contour_line.marker = {"enabled": False} contour_line.z_index = 2 chart.add_series(contour_line) - except AttributeError, TypeError: + except (AttributeError, TypeError): pass plt.close(fig_temp) diff --git a/scripts/evaluate-plot.py b/scripts/evaluate-plot.py index c09d94c387..ea67ddb7ed 100755 --- a/scripts/evaluate-plot.py +++ b/scripts/evaluate-plot.py @@ -497,7 +497,7 @@ def evaluate_with_claude(prompt: str, image_path: Path | None = None) -> dict: try: response = client.messages.create( model=settings.claude_model, - max_tokens=4096, + max_tokens=settings.claude_review_max_tokens, messages=[{"role": "user", "content": content}], ) except anthropic.APIError as e: diff --git a/scripts/upgrade_specs_ai.py b/scripts/upgrade_specs_ai.py index 80af9e1e87..f33ca11f37 100644 --- a/scripts/upgrade_specs_ai.py +++ b/scripts/upgrade_specs_ai.py @@ -159,7 +159,7 @@ def upgrade_spec_with_ai( client = anthropic.Anthropic(api_key=api_key, timeout=300.0) response = client.messages.create( - model=settings.claude_model, max_tokens=4000, messages=[{"role": "user", "content": prompt}] + model=settings.claude_model, max_tokens=settings.claude_max_tokens, messages=[{"role": "user", "content": prompt}] ) upgraded_content = response.content[0].text