From c362ef8a56e8c49f9e441bed344d2286e9d2076f Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Sun, 20 Sep 2026 14:31:42 +0200 Subject: [PATCH] Split native Periscope (periscope/src) from inherited PinScope (periscope/dependency). Keep validate.py and the finding engine as-is. AGPL LICENSE stays at the repo root. Docker overlays dependency then src. Do not delete the inherited tree. --- .github/workflows/ci.yml | 10 +- .gitignore | 8 +- CLAUDE.md | 144 +---------------- README.md | 25 ++- backend/Dockerfile | 33 ---- backend/__init__.py | 18 +++ backend/periscopex/__init__.py | 0 backend/routers/__init__.py | 0 backend/services/__init__.py | 0 docker-compose.yml | 8 +- docs/development/PINSCOPE_FENCE.md | 46 ------ periscope/README.md | 14 ++ periscope/dependency/CLAUDE.md | 149 ++++++++++++++++++ .../dependency/backend}/.env.example | 0 .../dependency/backend}/CLAUDE.md | 0 periscope/dependency/backend/__init__.py | 4 + .../dependency/backend}/config.py | 15 +- .../dependency/backend}/main.py | 0 .../backend}/middleware/__init__.py | 0 .../dependency/backend}/middleware/auth.py | 0 .../dependency/backend/periscopex/__init__.py | 3 + .../backend}/periscopex/bom_summary.py | 0 .../backend}/periscopex/derating.py | 0 .../dependency/backend}/periscopex/graph.py | 0 .../backend}/periscopex/led_current_check.py | 0 .../dependency/backend}/periscopex/models.py | 0 .../dependency/backend}/periscopex/parsers.py | 0 .../backend}/periscopex/parsers_edif.py | 0 .../periscopex/pin_function_tokens.py | 0 .../backend}/periscopex/pin_mux_check.py | 0 .../backend}/periscopex/resolve_passives.py | 0 .../backend}/periscopex/taxonomy.py | 0 .../dependency/backend}/periscopex/utils.py | 0 .../backend}/periscopex/validate.py | 0 .../backend}/periscopex/validation_tools.py | 0 .../dependency/backend}/pipeline_worker.py | 0 .../dependency/backend}/requirements.txt | 0 .../dependency/backend/routers/__init__.py | 3 + .../dependency/backend}/routers/admin.py | 0 .../dependency/backend}/routers/contact.py | 0 .../dependency/backend}/routers/deps.py | 0 .../dependency/backend}/routers/feedback.py | 0 .../dependency/backend}/routers/pipeline.py | 0 .../dependency/backend}/routers/projects.py | 0 .../dependency/backend}/routers/reports.py | 0 .../dependency/backend}/routers/survey.py | 0 .../dependency/backend/services/__init__.py | 3 + .../backend}/services/admin_settings.py | 0 .../dependency/backend}/services/api_logs.py | 0 .../backend}/services/billing_hook.py | 0 .../backend}/services/cost_estimator.py | 0 .../backend}/services/datasheet_store.py | 0 .../backend}/services/dedupe_findings.py | 0 .../dependency/backend}/services/digikey.py | 0 .../dependency/backend}/services/email.py | 0 .../backend}/services/event_bridge.py | 0 .../backend}/services/extraction.py | 0 .../backend}/services/job_runner.py | 0 .../backend}/services/llm/__init__.py | 3 + .../services/llm/anthropic_provider.py | 0 .../dependency/backend}/services/llm/base.py | 0 .../backend}/services/llm/factory.py | 0 .../backend}/services/llm/gemini_provider.py | 0 .../backend}/services/llm/pricing.py | 0 .../dependency/backend}/services/llm/types.py | 0 .../backend}/services/normalize_findings.py | 0 .../dependency/backend}/services/pipeline.py | 0 .../dependency/backend}/services/projects.py | 0 .../backend}/services/purple_parts.py | 0 .../dependency/backend}/services/storage.py | 0 .../backend}/services/storage_gcs.py | 0 .../dependency/backend}/services/survey.py | 0 .../backend}/services/validation.py | 0 .../dependency/backend}/skills_manifest.json | 0 .../dependency/docs}/how-it-works.svg | 0 .../dependency/frontend}/.gitignore | 0 .../dependency/frontend}/AGENTS.md | 0 .../dependency/frontend}/CLAUDE.md | 0 .../dependency/frontend}/README.md | 0 .../dependency/frontend}/components.json | 0 .../dependency/frontend}/content/changelog.md | 8 + .../frontend}/content/file-guide.md | 0 .../dependency/frontend}/content/privacy.md | 0 .../dependency/frontend}/content/terms.md | 0 .../dependency/frontend}/eslint.config.mjs | 0 .../dependency/frontend}/next.config.ts | 0 .../dependency/frontend}/package-lock.json | 0 .../dependency/frontend}/package.json | 2 +- .../dependency/frontend}/postcss.config.mjs | 0 .../dependency/frontend}/public/datasheet.gif | Bin .../dependency/frontend}/public/derating.png | Bin .../frontend}/public/eda-logos/altium.svg | 0 .../frontend}/public/eda-logos/autodesk.svg | 0 .../frontend}/public/eda-logos/cadence.svg | 0 .../frontend}/public/eda-logos/easyeda.svg | 0 .../frontend}/public/eda-logos/kicad.svg | 0 .../frontend}/public/eda-logos/orcad.svg | 0 .../frontend}/public/eda-logos/siemens.svg | 0 .../TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf | Bin .../examples/TI-MSP-KICAD9-TUTORIAL.asc | 0 .../examples/TI-MSP-KICAD9-TUTORIAL.csv | 0 .../public/faradworks-logo-white.png | Bin .../favicon_io/android-chrome-192x192.png | Bin .../favicon_io/android-chrome-512x512.png | Bin .../public/favicon_io/apple-touch-icon.png | Bin .../public/favicon_io/favicon-16x16.png | Bin .../public/favicon_io/favicon-32x32.png | Bin .../frontend}/public/favicon_io/favicon.ico | Bin .../public/favicon_io/site.webmanifest | 0 .../dependency/frontend}/public/file.svg | 0 .../dependency/frontend}/public/globe.svg | 0 .../dependency/frontend}/public/next.svg | 0 .../frontend}/public/power-tree.gif | Bin .../dependency/frontend}/public/report.png | Bin .../dependency/frontend}/public/vercel.svg | 0 .../dependency/frontend}/public/window.svg | 0 .../frontend}/scripts/sync-version.mjs | 0 .../frontend}/src/app/(app)/admin/page.tsx | 0 .../src/app/(app)/api/graph/[id]/route.ts | 0 .../src/app/(app)/api/projects/route.ts | 0 .../src/app/(app)/api/report/[id]/route.ts | 0 .../src/app/(app)/dashboard/page.tsx | 0 .../frontend}/src/app/(app)/feedback/page.tsx | 0 .../frontend}/src/app/(app)/layout.tsx | 0 .../frontend/src/app/(app)/library/page.tsx | 1 + .../src/app/(app)/project/[id]/page.tsx | 0 .../src/app/(app)/project/[id]/pcb/page.tsx | 1 + .../app/(app)/project/[id]/placement/page.tsx | 1 + .../app/(app)/project/[id]/progress/page.tsx | 0 .../app/(app)/project/[id]/report/page.tsx | 0 .../src/app/(marketing)/changelog/page.tsx | 0 .../src/app/(marketing)/contact/actions.ts | 0 .../app/(marketing)/contact/contact-form.tsx | 0 .../src/app/(marketing)/contact/nav.tsx | 0 .../src/app/(marketing)/contact/page.tsx | 0 .../src/app/(marketing)/file-guide/page.tsx | 0 .../src/app/(marketing)/landing-cta.tsx | 0 .../frontend}/src/app/(marketing)/layout.tsx | 0 .../frontend}/src/app/(marketing)/page.tsx | 0 .../src/app/(marketing)/privacy/page.tsx | 0 .../src/app/(marketing)/sign-in/page.tsx | 1 + .../src/app/(marketing)/sign-up/page.tsx | 1 + .../src/app/(marketing)/terms/page.tsx | 0 .../dependency/frontend}/src/app/favicon.ico | Bin .../dependency/frontend}/src/app/globals.css | 0 .../dependency/frontend}/src/app/layout.tsx | 0 .../frontend}/src/app/opengraph-image.tsx | 0 .../dependency/frontend}/src/app/robots.ts | 0 .../dependency/frontend}/src/app/sitemap.ts | 0 .../frontend}/src/app/twitter-image.tsx | 0 .../analytics/reddit-pixel-match-keys.tsx | 0 .../src/components/analytics/reddit-pixel.tsx | 0 .../components/billing/credits-context.tsx | 0 .../components/billing/paused-run-banner.tsx | 0 .../dashboard/create-project-dialog.tsx | 0 .../dashboard/onboarding-survey.tsx | 0 .../src/components/dashboard/project-card.tsx | 0 .../components/dashboard/projects-table.tsx | 0 .../components/feedback/feedback-dialog.tsx | 0 .../src/components/layout/auth-gate.tsx | 1 + .../src/components/layout/sidebar-auth.tsx | 1 + .../src/components/layout/sidebar.tsx | 0 .../components/legal/changelog-timeline.tsx | 0 .../src/components/legal/file-guide-page.tsx | 0 .../src/components/legal/legal-page.tsx | 0 .../components/marketing/pricing-section.tsx | 0 .../src/components/pdf/pdf-viewer-panel.tsx | 0 .../src/components/pdf/pdf-viewer-sheet.tsx | 0 .../components/progress/pipeline-stepper.tsx | 0 .../components/project/impedance-panel.tsx | 1 + .../src/components/project/pcb-upload.tsx | 1 + .../src/components/project/topology-panel.tsx | 1 + .../src/components/report/component-group.tsx | 0 .../src/components/report/finding-card.tsx | 0 .../components/report/finding-comments.tsx | 0 .../components/report/finding-focus-view.tsx | 0 .../report/finding-review-controls.tsx | 1 + .../src/components/report/findings-list.tsx | 0 .../src/components/report/findings-tree.tsx | 1 + .../src/components/report/mention-input.tsx | 0 .../components/report/pcb-exam-section.tsx | 1 + .../src/components/report/report-filters.tsx | 0 .../src/components/report/report-summary.tsx | 0 .../components/report/reviewed-section.tsx | 0 .../src/components/report/status-badge.tsx | 0 .../components/theme/clerk-theme-provider.tsx | 0 .../src/components/theme/theme-provider.tsx | 0 .../src/components/theme/theme-toggle.tsx | 0 .../src/components/ui/alert-dialog.tsx | 0 .../frontend}/src/components/ui/badge.tsx | 0 .../frontend}/src/components/ui/button.tsx | 0 .../frontend}/src/components/ui/card.tsx | 0 .../src/components/ui/collapsible.tsx | 0 .../frontend}/src/components/ui/dialog.tsx | 0 .../src/components/ui/dropdown-menu.tsx | 0 .../frontend}/src/components/ui/input.tsx | 0 .../frontend}/src/components/ui/label.tsx | 0 .../frontend}/src/components/ui/progress.tsx | 0 .../src/components/ui/scroll-area.tsx | 0 .../frontend}/src/components/ui/select.tsx | 0 .../frontend}/src/components/ui/separator.tsx | 0 .../frontend}/src/components/ui/sheet.tsx | 0 .../frontend}/src/components/ui/skeleton.tsx | 0 .../frontend}/src/components/ui/tabs.tsx | 0 .../frontend}/src/components/ui/textarea.tsx | 0 .../frontend}/src/components/ui/toast.tsx | 0 .../frontend}/src/components/ui/tooltip.tsx | 0 .../components/upload/file-upload-zone.tsx | 0 .../frontend}/src/hooks/use-auth-api.ts | 0 .../frontend}/src/hooks/use-optional-auth.ts | 0 .../frontend/src/hooks/use-pcb-progress.ts | 1 + .../src/hooks/use-pipeline-progress.ts | 0 .../src/hooks/use-placement-progress.ts | 1 + .../frontend}/src/hooks/use-report.ts | 0 .../frontend}/src/hooks/use-reviewed-count.ts | 0 .../src/hooks/use-reviewed-findings.ts | 0 .../dependency/frontend}/src/lib/api.ts | 0 periscope/dependency/frontend/src/lib/auth.ts | 1 + .../dependency/frontend}/src/lib/csp-hosts.ts | 0 .../frontend/src/lib/layout-finding.ts | 1 + .../dependency/frontend}/src/lib/mock-data.ts | 0 .../frontend/src/lib/pinscope-compat.ts | 1 + .../frontend}/src/lib/report-export.ts | 0 .../dependency/frontend}/src/lib/site.ts | 0 .../frontend/src/lib/storage-keys.ts | 1 + .../dependency/frontend}/src/lib/types.ts | 0 .../dependency/frontend}/src/lib/utils.ts | 0 .../dependency/frontend}/src/lib/version.ts | 4 +- .../dependency/frontend}/src/proxy.ts | 0 .../dependency/frontend}/tsconfig.json | 0 .../scripts}/backfill_passive_subtype.py | 0 .../scripts}/clear_rules_from_extractions.py | 0 .../scripts}/dedup_library_datasheets.py | 0 .../dependency/scripts}/gc_orphan_blobs.py | 0 .../scripts}/migrate_datasheets_to_blobs.py | 0 .../scripts}/migrate_datasheets_to_library.py | 0 .../dependency/scripts}/upload_skills.py | 0 .../TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf | Bin .../TI-MSP-KICAD9-TUTORIAL.asc | 0 .../TI-MSP-KICAD9-TUTORIAL.csv | 0 .../simple_project}/design_graph.json | 0 .../simple_project}/eval_golden.json | 0 .../simple_project}/smoke_baseline.json | 0 .../skills}/extract-pattern/SKILL.md | 0 .../skills}/extract-pattern/schema.json | 0 .../skills}/extract-pattern/validate.py | 0 .../skills}/extract-pintable/SKILL.md | 0 .../skills}/extract-pintable/schema.json | 0 .../skills}/extract-pintable/validate.py | 0 .../dependency/skills}/extract-specs/SKILL.md | 0 .../skills}/extract-specs/schema.json | 0 .../skills}/extract-specs/validate.py | 0 .../dependency/taxonomy}/connector.json | 0 .../dependency/taxonomy}/crystal.json | 0 .../dependency/taxonomy}/discrete.json | 0 .../dependency/taxonomy}/fuse.json | 0 .../dependency/taxonomy}/ic.json | 0 .../dependency/taxonomy}/passive.json | 0 .../dependency/taxonomy}/switch.json | 0 .../dependency/taxonomy}/test_point.json | 0 .../dependency/taxonomy}/transformer.json | 0 periscope/src/backend/Dockerfile | 23 +++ periscope/src/backend/__init__.py | 4 + .../src/backend}/_version.py | 8 +- periscope/src/backend/periscopex/__init__.py | 4 + .../backend}/periscopex/antenna_geometry.py | 0 .../src/backend}/periscopex/antenna_rf.py | 0 .../backend}/periscopex/bom_match_check.py | 0 .../src/backend}/periscopex/bom_pcb_check.py | 0 .../src/backend}/periscopex/cad_bridge.py | 0 .../backend}/periscopex/crystal_cl_check.py | 0 .../src/backend}/periscopex/dnp_check.py | 0 .../src/backend}/periscopex/emi_check.py | 0 .../src/backend}/periscopex/errata_check.py | 0 .../backend}/periscopex/esd_return_check.py | 0 .../src/backend}/periscopex/eval_report.py | 0 .../src/backend}/periscopex/filter_check.py | 0 .../src/backend}/periscopex/finding_engine.py | 0 .../backend}/periscopex/functional_groups.py | 0 .../backend}/periscopex/hf_coverage_check.py | 0 .../src/backend}/periscopex/hierarchy.py | 0 .../src/backend}/periscopex/impedance.py | 0 .../backend}/periscopex/impedance_traces.py | 0 .../periscopex/internal_features_check.py | 0 .../src/backend}/periscopex/layout_rules.py | 0 .../src/backend}/periscopex/library_gate.py | 0 .../src/backend}/periscopex/lifecycle.py | 0 .../src/backend}/periscopex/nc_pin_check.py | 0 .../src/backend}/periscopex/netlist_bundle.py | 0 .../src/backend}/periscopex/parsers_kicad.py | 0 .../backend}/periscopex/parsers_kicad_pcb.py | 0 .../backend}/periscopex/passive_rail_check.py | 0 .../src/backend}/periscopex/pcb_checks.py | 0 .../src/backend}/periscopex/pcb_inventory.py | 0 .../src/backend}/periscopex/pcb_net_match.py | 0 .../backend}/periscopex/pcb_power_thermal.py | 0 .../src/backend}/periscopex/pcb_review.py | 0 .../src/backend}/periscopex/pdf_text.py | 0 .../src/backend}/periscopex/pi_check.py | 0 .../backend}/periscopex/placement_check.py | 0 .../src/backend}/periscopex/placement_pack.py | 0 .../backend}/periscopex/power_margin_check.py | 0 .../src/backend}/periscopex/quote_verify.py | 0 .../backend}/periscopex/review_fingerprint.py | 0 .../backend}/periscopex/review_workflow.py | 0 .../backend}/periscopex/sequencing_check.py | 0 .../src/backend}/periscopex/si_check.py | 0 .../src/backend}/periscopex/spof_check.py | 0 .../src/backend}/periscopex/thermal_check.py | 0 .../src/backend}/periscopex/timing_check.py | 0 .../src/backend}/pinscope_compat.py | 0 periscope/src/backend/repo_paths.py | 89 +++++++++++ periscope/src/backend/routers/__init__.py | 3 + .../src/backend}/routers/auth.py | 0 .../src/backend}/routers/impedance.py | 0 periscope/src/backend/services/__init__.py | 3 + .../src/backend}/services/datasheet_finder.py | 0 .../src/backend/services/llm/__init__.py | 33 ++++ .../services/llm/deepseek_provider.py | 0 .../src/backend}/services/llm/local_skill.py | 0 .../src/backend}/services/llm/pdf_ingest.py | 0 .../src/backend}/services/local_jwt.py | 0 .../src/backend}/services/local_users.py | 0 .../src/backend}/services/mouser.py | 0 .../services/passive_from_distributor.py | 0 .../src/backend}/services/passive_from_mpn.py | 0 .../backend}/services/passive_from_value.py | 0 .../src/backend}/services/pcb_pipeline.py | 0 .../src/backend}/services/pcb_validation.py | 0 .../backend}/services/placement_pipeline.py | 0 .../src/backend}/services/user_directory.py | 0 .../src/backend}/vendor_path.py | 5 +- .../src/docs}/development/PINSCOPE_AUDIT.md | 0 .../src/docs/development/PINSCOPE_FENCE.md | 38 +++++ .../development/PINSCOPE_INDEPENDENCE_PLAN.md | 54 ++++--- .../src/docs}/motore-finding.md | 0 .../src/docs}/piano-implementazione.md | 0 .../src/docs}/piano-pcb-review.md | 0 .../src/frontend}/dockerfile | 5 +- .../frontend}/src/app/(app)/library/page.tsx | 0 .../src/app/(app)/project/[id]/pcb/page.tsx | 0 .../app/(app)/project/[id]/placement/page.tsx | 0 .../src/app/(marketing)/sign-in/page.tsx | 0 .../src/app/(marketing)/sign-up/page.tsx | 0 .../src/components/layout/auth-gate.tsx | 0 .../src/components/layout/sidebar-auth.tsx | 0 .../components/project/impedance-panel.tsx | 0 .../src/components/project/pcb-upload.tsx | 0 .../src/components/project/topology-panel.tsx | 0 .../report/finding-review-controls.tsx | 0 .../src/components/report/findings-tree.tsx | 0 .../components/report/pcb-exam-section.tsx | 0 .../frontend}/src/hooks/use-pcb-progress.ts | 0 .../src/hooks/use-placement-progress.ts | 0 .../src/frontend}/src/lib/auth.ts | 0 .../src/frontend}/src/lib/layout-finding.ts | 0 .../src/frontend}/src/lib/pinscope-compat.ts | 0 .../src/frontend}/src/lib/storage-keys.ts | 0 .../src/plugins}/__init__.py | 0 .../src/plugins}/kicad/__init__.py | 0 .../src/plugins}/kicad/focus.py | 0 .../src/plugins}/kicad/periscope_plugin.py | 0 periscope/src/scripts/smoke_simple_project.py | 138 ++++++++++++++++ pytest.ini | 3 + scripts/materialize-frontend.sh | 20 +++ scripts/smoke_simple_project.py | 137 +--------------- scripts/update-periscope.sh | 3 + tests/conftest.py | 10 +- tests/paths.py | 9 ++ tests/test_cost_estimator.py | 5 +- tests/test_crystal_cl_check.py | 4 +- tests/test_datasheet_excerpt_tool.py | 4 +- tests/test_eval_report.py | 6 +- tests/test_functional_groups.py | 4 +- tests/test_passive_from_distributor.py | 5 +- tests/test_passive_from_value.py | 5 +- tests/test_pcb_review.py | 4 +- tests/test_pin_mux_check.py | 6 +- tests/test_placement_check.py | 4 +- tests/test_placement_pack.py | 4 +- tests/test_placement_pipeline.py | 4 +- tests/test_si_check.py | 4 +- tests/test_validation_no_tool_recovery.py | 4 +- tests/test_validation_trace_log.py | 4 +- 384 files changed, 742 insertions(+), 433 deletions(-) delete mode 100644 backend/Dockerfile delete mode 100644 backend/periscopex/__init__.py delete mode 100644 backend/routers/__init__.py delete mode 100644 backend/services/__init__.py delete mode 100644 docs/development/PINSCOPE_FENCE.md create mode 100644 periscope/README.md create mode 100644 periscope/dependency/CLAUDE.md rename {backend => periscope/dependency/backend}/.env.example (100%) rename {backend => periscope/dependency/backend}/CLAUDE.md (100%) create mode 100644 periscope/dependency/backend/__init__.py rename {backend => periscope/dependency/backend}/config.py (96%) rename {backend => periscope/dependency/backend}/main.py (100%) rename {backend => periscope/dependency/backend}/middleware/__init__.py (100%) rename {backend => periscope/dependency/backend}/middleware/auth.py (100%) create mode 100644 periscope/dependency/backend/periscopex/__init__.py rename {backend => periscope/dependency/backend}/periscopex/bom_summary.py (100%) rename {backend => periscope/dependency/backend}/periscopex/derating.py (100%) rename {backend => periscope/dependency/backend}/periscopex/graph.py (100%) rename {backend => periscope/dependency/backend}/periscopex/led_current_check.py (100%) rename {backend => periscope/dependency/backend}/periscopex/models.py (100%) rename {backend => periscope/dependency/backend}/periscopex/parsers.py (100%) rename {backend => periscope/dependency/backend}/periscopex/parsers_edif.py (100%) rename {backend => periscope/dependency/backend}/periscopex/pin_function_tokens.py (100%) rename {backend => periscope/dependency/backend}/periscopex/pin_mux_check.py (100%) rename {backend => periscope/dependency/backend}/periscopex/resolve_passives.py (100%) rename {backend => periscope/dependency/backend}/periscopex/taxonomy.py (100%) rename {backend => periscope/dependency/backend}/periscopex/utils.py (100%) rename {backend => periscope/dependency/backend}/periscopex/validate.py (100%) rename {backend => periscope/dependency/backend}/periscopex/validation_tools.py (100%) rename {backend => periscope/dependency/backend}/pipeline_worker.py (100%) rename {backend => periscope/dependency/backend}/requirements.txt (100%) create mode 100644 periscope/dependency/backend/routers/__init__.py rename {backend => periscope/dependency/backend}/routers/admin.py (100%) rename {backend => periscope/dependency/backend}/routers/contact.py (100%) rename {backend => periscope/dependency/backend}/routers/deps.py (100%) rename {backend => periscope/dependency/backend}/routers/feedback.py (100%) rename {backend => periscope/dependency/backend}/routers/pipeline.py (100%) rename {backend => periscope/dependency/backend}/routers/projects.py (100%) rename {backend => periscope/dependency/backend}/routers/reports.py (100%) rename {backend => periscope/dependency/backend}/routers/survey.py (100%) create mode 100644 periscope/dependency/backend/services/__init__.py rename {backend => periscope/dependency/backend}/services/admin_settings.py (100%) rename {backend => periscope/dependency/backend}/services/api_logs.py (100%) rename {backend => periscope/dependency/backend}/services/billing_hook.py (100%) rename {backend => periscope/dependency/backend}/services/cost_estimator.py (100%) rename {backend => periscope/dependency/backend}/services/datasheet_store.py (100%) rename {backend => periscope/dependency/backend}/services/dedupe_findings.py (100%) rename {backend => periscope/dependency/backend}/services/digikey.py (100%) rename {backend => periscope/dependency/backend}/services/email.py (100%) rename {backend => periscope/dependency/backend}/services/event_bridge.py (100%) rename {backend => periscope/dependency/backend}/services/extraction.py (100%) rename {backend => periscope/dependency/backend}/services/job_runner.py (100%) rename {backend => periscope/dependency/backend}/services/llm/__init__.py (91%) rename {backend => periscope/dependency/backend}/services/llm/anthropic_provider.py (100%) rename {backend => periscope/dependency/backend}/services/llm/base.py (100%) rename {backend => periscope/dependency/backend}/services/llm/factory.py (100%) rename {backend => periscope/dependency/backend}/services/llm/gemini_provider.py (100%) rename {backend => periscope/dependency/backend}/services/llm/pricing.py (100%) rename {backend => periscope/dependency/backend}/services/llm/types.py (100%) rename {backend => periscope/dependency/backend}/services/normalize_findings.py (100%) rename {backend => periscope/dependency/backend}/services/pipeline.py (100%) rename {backend => periscope/dependency/backend}/services/projects.py (100%) rename {backend => periscope/dependency/backend}/services/purple_parts.py (100%) rename {backend => periscope/dependency/backend}/services/storage.py (100%) rename {backend => periscope/dependency/backend}/services/storage_gcs.py (100%) rename {backend => periscope/dependency/backend}/services/survey.py (100%) rename {backend => periscope/dependency/backend}/services/validation.py (100%) rename {backend => periscope/dependency/backend}/skills_manifest.json (100%) rename {docs => periscope/dependency/docs}/how-it-works.svg (100%) rename {frontend => periscope/dependency/frontend}/.gitignore (100%) rename {frontend => periscope/dependency/frontend}/AGENTS.md (100%) rename {frontend => periscope/dependency/frontend}/CLAUDE.md (100%) rename {frontend => periscope/dependency/frontend}/README.md (100%) rename {frontend => periscope/dependency/frontend}/components.json (100%) rename {frontend => periscope/dependency/frontend}/content/changelog.md (98%) rename {frontend => periscope/dependency/frontend}/content/file-guide.md (100%) rename {frontend => periscope/dependency/frontend}/content/privacy.md (100%) rename {frontend => periscope/dependency/frontend}/content/terms.md (100%) rename {frontend => periscope/dependency/frontend}/eslint.config.mjs (100%) rename {frontend => periscope/dependency/frontend}/next.config.ts (100%) rename {frontend => periscope/dependency/frontend}/package-lock.json (100%) rename {frontend => periscope/dependency/frontend}/package.json (97%) rename {frontend => periscope/dependency/frontend}/postcss.config.mjs (100%) rename {frontend => periscope/dependency/frontend}/public/datasheet.gif (100%) rename {frontend => periscope/dependency/frontend}/public/derating.png (100%) rename {frontend => periscope/dependency/frontend}/public/eda-logos/altium.svg (100%) rename {frontend => periscope/dependency/frontend}/public/eda-logos/autodesk.svg (100%) rename {frontend => periscope/dependency/frontend}/public/eda-logos/cadence.svg (100%) rename {frontend => periscope/dependency/frontend}/public/eda-logos/easyeda.svg (100%) rename {frontend => periscope/dependency/frontend}/public/eda-logos/kicad.svg (100%) rename {frontend => periscope/dependency/frontend}/public/eda-logos/orcad.svg (100%) rename {frontend => periscope/dependency/frontend}/public/eda-logos/siemens.svg (100%) rename {frontend => periscope/dependency/frontend}/public/examples/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf (100%) rename {frontend => periscope/dependency/frontend}/public/examples/TI-MSP-KICAD9-TUTORIAL.asc (100%) rename {frontend => periscope/dependency/frontend}/public/examples/TI-MSP-KICAD9-TUTORIAL.csv (100%) rename {frontend => periscope/dependency/frontend}/public/faradworks-logo-white.png (100%) rename {frontend => periscope/dependency/frontend}/public/favicon_io/android-chrome-192x192.png (100%) rename {frontend => periscope/dependency/frontend}/public/favicon_io/android-chrome-512x512.png (100%) rename {frontend => periscope/dependency/frontend}/public/favicon_io/apple-touch-icon.png (100%) rename {frontend => periscope/dependency/frontend}/public/favicon_io/favicon-16x16.png (100%) rename {frontend => periscope/dependency/frontend}/public/favicon_io/favicon-32x32.png (100%) rename {frontend => periscope/dependency/frontend}/public/favicon_io/favicon.ico (100%) rename {frontend => periscope/dependency/frontend}/public/favicon_io/site.webmanifest (100%) rename {frontend => periscope/dependency/frontend}/public/file.svg (100%) rename {frontend => periscope/dependency/frontend}/public/globe.svg (100%) rename {frontend => periscope/dependency/frontend}/public/next.svg (100%) rename {frontend => periscope/dependency/frontend}/public/power-tree.gif (100%) rename {frontend => periscope/dependency/frontend}/public/report.png (100%) rename {frontend => periscope/dependency/frontend}/public/vercel.svg (100%) rename {frontend => periscope/dependency/frontend}/public/window.svg (100%) rename {frontend => periscope/dependency/frontend}/scripts/sync-version.mjs (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/admin/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/api/graph/[id]/route.ts (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/api/projects/route.ts (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/api/report/[id]/route.ts (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/dashboard/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/feedback/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/layout.tsx (100%) create mode 120000 periscope/dependency/frontend/src/app/(app)/library/page.tsx rename {frontend => periscope/dependency/frontend}/src/app/(app)/project/[id]/page.tsx (100%) create mode 120000 periscope/dependency/frontend/src/app/(app)/project/[id]/pcb/page.tsx create mode 120000 periscope/dependency/frontend/src/app/(app)/project/[id]/placement/page.tsx rename {frontend => periscope/dependency/frontend}/src/app/(app)/project/[id]/progress/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(app)/project/[id]/report/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/changelog/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/contact/actions.ts (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/contact/contact-form.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/contact/nav.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/contact/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/file-guide/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/landing-cta.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/layout.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/privacy/page.tsx (100%) create mode 120000 periscope/dependency/frontend/src/app/(marketing)/sign-in/page.tsx create mode 120000 periscope/dependency/frontend/src/app/(marketing)/sign-up/page.tsx rename {frontend => periscope/dependency/frontend}/src/app/(marketing)/terms/page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/favicon.ico (100%) rename {frontend => periscope/dependency/frontend}/src/app/globals.css (100%) rename {frontend => periscope/dependency/frontend}/src/app/layout.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/opengraph-image.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/app/robots.ts (100%) rename {frontend => periscope/dependency/frontend}/src/app/sitemap.ts (100%) rename {frontend => periscope/dependency/frontend}/src/app/twitter-image.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/analytics/reddit-pixel-match-keys.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/analytics/reddit-pixel.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/billing/credits-context.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/billing/paused-run-banner.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/dashboard/create-project-dialog.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/dashboard/onboarding-survey.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/dashboard/project-card.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/dashboard/projects-table.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/feedback/feedback-dialog.tsx (100%) create mode 120000 periscope/dependency/frontend/src/components/layout/auth-gate.tsx create mode 120000 periscope/dependency/frontend/src/components/layout/sidebar-auth.tsx rename {frontend => periscope/dependency/frontend}/src/components/layout/sidebar.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/legal/changelog-timeline.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/legal/file-guide-page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/legal/legal-page.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/marketing/pricing-section.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/pdf/pdf-viewer-panel.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/pdf/pdf-viewer-sheet.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/progress/pipeline-stepper.tsx (100%) create mode 120000 periscope/dependency/frontend/src/components/project/impedance-panel.tsx create mode 120000 periscope/dependency/frontend/src/components/project/pcb-upload.tsx create mode 120000 periscope/dependency/frontend/src/components/project/topology-panel.tsx rename {frontend => periscope/dependency/frontend}/src/components/report/component-group.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/report/finding-card.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/report/finding-comments.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/report/finding-focus-view.tsx (100%) create mode 120000 periscope/dependency/frontend/src/components/report/finding-review-controls.tsx rename {frontend => periscope/dependency/frontend}/src/components/report/findings-list.tsx (100%) create mode 120000 periscope/dependency/frontend/src/components/report/findings-tree.tsx rename {frontend => periscope/dependency/frontend}/src/components/report/mention-input.tsx (100%) create mode 120000 periscope/dependency/frontend/src/components/report/pcb-exam-section.tsx rename {frontend => periscope/dependency/frontend}/src/components/report/report-filters.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/report/report-summary.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/report/reviewed-section.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/report/status-badge.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/theme/clerk-theme-provider.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/theme/theme-provider.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/theme/theme-toggle.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/alert-dialog.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/badge.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/button.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/card.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/collapsible.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/dialog.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/dropdown-menu.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/input.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/label.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/progress.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/scroll-area.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/select.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/separator.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/sheet.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/skeleton.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/tabs.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/textarea.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/toast.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/ui/tooltip.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/components/upload/file-upload-zone.tsx (100%) rename {frontend => periscope/dependency/frontend}/src/hooks/use-auth-api.ts (100%) rename {frontend => periscope/dependency/frontend}/src/hooks/use-optional-auth.ts (100%) create mode 120000 periscope/dependency/frontend/src/hooks/use-pcb-progress.ts rename {frontend => periscope/dependency/frontend}/src/hooks/use-pipeline-progress.ts (100%) create mode 120000 periscope/dependency/frontend/src/hooks/use-placement-progress.ts rename {frontend => periscope/dependency/frontend}/src/hooks/use-report.ts (100%) rename {frontend => periscope/dependency/frontend}/src/hooks/use-reviewed-count.ts (100%) rename {frontend => periscope/dependency/frontend}/src/hooks/use-reviewed-findings.ts (100%) rename {frontend => periscope/dependency/frontend}/src/lib/api.ts (100%) create mode 120000 periscope/dependency/frontend/src/lib/auth.ts rename {frontend => periscope/dependency/frontend}/src/lib/csp-hosts.ts (100%) create mode 120000 periscope/dependency/frontend/src/lib/layout-finding.ts rename {frontend => periscope/dependency/frontend}/src/lib/mock-data.ts (100%) create mode 120000 periscope/dependency/frontend/src/lib/pinscope-compat.ts rename {frontend => periscope/dependency/frontend}/src/lib/report-export.ts (100%) rename {frontend => periscope/dependency/frontend}/src/lib/site.ts (100%) create mode 120000 periscope/dependency/frontend/src/lib/storage-keys.ts rename {frontend => periscope/dependency/frontend}/src/lib/types.ts (100%) rename {frontend => periscope/dependency/frontend}/src/lib/utils.ts (100%) rename {frontend => periscope/dependency/frontend}/src/lib/version.ts (64%) rename {frontend => periscope/dependency/frontend}/src/proxy.ts (100%) rename {frontend => periscope/dependency/frontend}/tsconfig.json (100%) rename {scripts => periscope/dependency/scripts}/backfill_passive_subtype.py (100%) rename {scripts => periscope/dependency/scripts}/clear_rules_from_extractions.py (100%) rename {scripts => periscope/dependency/scripts}/dedup_library_datasheets.py (100%) rename {scripts => periscope/dependency/scripts}/gc_orphan_blobs.py (100%) rename {scripts => periscope/dependency/scripts}/migrate_datasheets_to_blobs.py (100%) rename {scripts => periscope/dependency/scripts}/migrate_datasheets_to_library.py (100%) rename {scripts => periscope/dependency/scripts}/upload_skills.py (100%) rename {simple_project => periscope/dependency/simple_project}/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf (100%) rename {simple_project => periscope/dependency/simple_project}/TI-MSP-KICAD9-TUTORIAL.asc (100%) rename {simple_project => periscope/dependency/simple_project}/TI-MSP-KICAD9-TUTORIAL.csv (100%) rename {simple_project => periscope/dependency/simple_project}/design_graph.json (100%) rename {simple_project => periscope/dependency/simple_project}/eval_golden.json (100%) rename {simple_project => periscope/dependency/simple_project}/smoke_baseline.json (100%) rename {skills => periscope/dependency/skills}/extract-pattern/SKILL.md (100%) rename {skills => periscope/dependency/skills}/extract-pattern/schema.json (100%) rename {skills => periscope/dependency/skills}/extract-pattern/validate.py (100%) rename {skills => periscope/dependency/skills}/extract-pintable/SKILL.md (100%) rename {skills => periscope/dependency/skills}/extract-pintable/schema.json (100%) rename {skills => periscope/dependency/skills}/extract-pintable/validate.py (100%) rename {skills => periscope/dependency/skills}/extract-specs/SKILL.md (100%) rename {skills => periscope/dependency/skills}/extract-specs/schema.json (100%) rename {skills => periscope/dependency/skills}/extract-specs/validate.py (100%) rename {taxonomy => periscope/dependency/taxonomy}/connector.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/crystal.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/discrete.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/fuse.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/ic.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/passive.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/switch.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/test_point.json (100%) rename {taxonomy => periscope/dependency/taxonomy}/transformer.json (100%) create mode 100644 periscope/src/backend/Dockerfile create mode 100644 periscope/src/backend/__init__.py rename {backend => periscope/src/backend}/_version.py (83%) create mode 100644 periscope/src/backend/periscopex/__init__.py rename {backend => periscope/src/backend}/periscopex/antenna_geometry.py (100%) rename {backend => periscope/src/backend}/periscopex/antenna_rf.py (100%) rename {backend => periscope/src/backend}/periscopex/bom_match_check.py (100%) rename {backend => periscope/src/backend}/periscopex/bom_pcb_check.py (100%) rename {backend => periscope/src/backend}/periscopex/cad_bridge.py (100%) rename {backend => periscope/src/backend}/periscopex/crystal_cl_check.py (100%) rename {backend => periscope/src/backend}/periscopex/dnp_check.py (100%) rename {backend => periscope/src/backend}/periscopex/emi_check.py (100%) rename {backend => periscope/src/backend}/periscopex/errata_check.py (100%) rename {backend => periscope/src/backend}/periscopex/esd_return_check.py (100%) rename {backend => periscope/src/backend}/periscopex/eval_report.py (100%) rename {backend => periscope/src/backend}/periscopex/filter_check.py (100%) rename {backend => periscope/src/backend}/periscopex/finding_engine.py (100%) rename {backend => periscope/src/backend}/periscopex/functional_groups.py (100%) rename {backend => periscope/src/backend}/periscopex/hf_coverage_check.py (100%) rename {backend => periscope/src/backend}/periscopex/hierarchy.py (100%) rename {backend => periscope/src/backend}/periscopex/impedance.py (100%) rename {backend => periscope/src/backend}/periscopex/impedance_traces.py (100%) rename {backend => periscope/src/backend}/periscopex/internal_features_check.py (100%) rename {backend => periscope/src/backend}/periscopex/layout_rules.py (100%) rename {backend => periscope/src/backend}/periscopex/library_gate.py (100%) rename {backend => periscope/src/backend}/periscopex/lifecycle.py (100%) rename {backend => periscope/src/backend}/periscopex/nc_pin_check.py (100%) rename {backend => periscope/src/backend}/periscopex/netlist_bundle.py (100%) rename {backend => periscope/src/backend}/periscopex/parsers_kicad.py (100%) rename {backend => periscope/src/backend}/periscopex/parsers_kicad_pcb.py (100%) rename {backend => periscope/src/backend}/periscopex/passive_rail_check.py (100%) rename {backend => periscope/src/backend}/periscopex/pcb_checks.py (100%) rename {backend => periscope/src/backend}/periscopex/pcb_inventory.py (100%) rename {backend => periscope/src/backend}/periscopex/pcb_net_match.py (100%) rename {backend => periscope/src/backend}/periscopex/pcb_power_thermal.py (100%) rename {backend => periscope/src/backend}/periscopex/pcb_review.py (100%) rename {backend => periscope/src/backend}/periscopex/pdf_text.py (100%) rename {backend => periscope/src/backend}/periscopex/pi_check.py (100%) rename {backend => periscope/src/backend}/periscopex/placement_check.py (100%) rename {backend => periscope/src/backend}/periscopex/placement_pack.py (100%) rename {backend => periscope/src/backend}/periscopex/power_margin_check.py (100%) rename {backend => periscope/src/backend}/periscopex/quote_verify.py (100%) rename {backend => periscope/src/backend}/periscopex/review_fingerprint.py (100%) rename {backend => periscope/src/backend}/periscopex/review_workflow.py (100%) rename {backend => periscope/src/backend}/periscopex/sequencing_check.py (100%) rename {backend => periscope/src/backend}/periscopex/si_check.py (100%) rename {backend => periscope/src/backend}/periscopex/spof_check.py (100%) rename {backend => periscope/src/backend}/periscopex/thermal_check.py (100%) rename {backend => periscope/src/backend}/periscopex/timing_check.py (100%) rename {backend => periscope/src/backend}/pinscope_compat.py (100%) create mode 100644 periscope/src/backend/repo_paths.py create mode 100644 periscope/src/backend/routers/__init__.py rename {backend => periscope/src/backend}/routers/auth.py (100%) rename {backend => periscope/src/backend}/routers/impedance.py (100%) create mode 100644 periscope/src/backend/services/__init__.py rename {backend => periscope/src/backend}/services/datasheet_finder.py (100%) create mode 100644 periscope/src/backend/services/llm/__init__.py rename {backend => periscope/src/backend}/services/llm/deepseek_provider.py (100%) rename {backend => periscope/src/backend}/services/llm/local_skill.py (100%) rename {backend => periscope/src/backend}/services/llm/pdf_ingest.py (100%) rename {backend => periscope/src/backend}/services/local_jwt.py (100%) rename {backend => periscope/src/backend}/services/local_users.py (100%) rename {backend => periscope/src/backend}/services/mouser.py (100%) rename {backend => periscope/src/backend}/services/passive_from_distributor.py (100%) rename {backend => periscope/src/backend}/services/passive_from_mpn.py (100%) rename {backend => periscope/src/backend}/services/passive_from_value.py (100%) rename {backend => periscope/src/backend}/services/pcb_pipeline.py (100%) rename {backend => periscope/src/backend}/services/pcb_validation.py (100%) rename {backend => periscope/src/backend}/services/placement_pipeline.py (100%) rename {backend => periscope/src/backend}/services/user_directory.py (100%) rename {backend => periscope/src/backend}/vendor_path.py (74%) rename {docs => periscope/src/docs}/development/PINSCOPE_AUDIT.md (100%) create mode 100644 periscope/src/docs/development/PINSCOPE_FENCE.md rename {docs => periscope/src/docs}/development/PINSCOPE_INDEPENDENCE_PLAN.md (81%) rename {docs => periscope/src/docs}/motore-finding.md (100%) rename {docs => periscope/src/docs}/piano-implementazione.md (100%) rename {docs => periscope/src/docs}/piano-pcb-review.md (100%) rename {frontend => periscope/src/frontend}/dockerfile (87%) rename {frontend => periscope/src/frontend}/src/app/(app)/library/page.tsx (100%) rename {frontend => periscope/src/frontend}/src/app/(app)/project/[id]/pcb/page.tsx (100%) rename {frontend => periscope/src/frontend}/src/app/(app)/project/[id]/placement/page.tsx (100%) rename {frontend => periscope/src/frontend}/src/app/(marketing)/sign-in/page.tsx (100%) rename {frontend => periscope/src/frontend}/src/app/(marketing)/sign-up/page.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/layout/auth-gate.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/layout/sidebar-auth.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/project/impedance-panel.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/project/pcb-upload.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/project/topology-panel.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/report/finding-review-controls.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/report/findings-tree.tsx (100%) rename {frontend => periscope/src/frontend}/src/components/report/pcb-exam-section.tsx (100%) rename {frontend => periscope/src/frontend}/src/hooks/use-pcb-progress.ts (100%) rename {frontend => periscope/src/frontend}/src/hooks/use-placement-progress.ts (100%) rename {frontend => periscope/src/frontend}/src/lib/auth.ts (100%) rename {frontend => periscope/src/frontend}/src/lib/layout-finding.ts (100%) rename {frontend => periscope/src/frontend}/src/lib/pinscope-compat.ts (100%) rename {frontend => periscope/src/frontend}/src/lib/storage-keys.ts (100%) rename {plugins => periscope/src/plugins}/__init__.py (100%) rename {plugins => periscope/src/plugins}/kicad/__init__.py (100%) rename {plugins => periscope/src/plugins}/kicad/focus.py (100%) rename {plugins => periscope/src/plugins}/kicad/periscope_plugin.py (100%) create mode 100644 periscope/src/scripts/smoke_simple_project.py create mode 100644 pytest.ini create mode 100755 scripts/materialize-frontend.sh create mode 100644 tests/paths.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b6857c..bc0f728 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,20 +14,20 @@ jobs: with: python-version: "3.12" cache: pip - - run: pip install -r backend/requirements.txt pytest pytest-asyncio + - run: pip install -r periscope/dependency/backend/requirements.txt pytest pytest-asyncio - run: pytest tests/ -q frontend: runs-on: ubuntu-latest - defaults: - run: - working-directory: frontend steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - cache-dependency-path: frontend/package-lock.json + cache-dependency-path: periscope/dependency/frontend/package-lock.json + - run: chmod +x scripts/materialize-frontend.sh && scripts/materialize-frontend.sh - run: npm ci + working-directory: .merge/frontend - run: npm run build + working-directory: .merge/frontend diff --git a/.gitignore b/.gitignore index d1163a2..17f8913 100644 --- a/.gitignore +++ b/.gitignore @@ -49,10 +49,10 @@ skills-lock.json data/ backend/data/ -# Frontend -frontend/node_modules/ -frontend/.next/ -frontend/out/ +.merge/ +periscope/dependency/frontend/node_modules/ +periscope/dependency/frontend/.next/ +periscope/src/frontend/.next/ .next/ # retrospective diff --git a/CLAUDE.md b/CLAUDE.md index 7015a21..eac43a5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,149 +1,15 @@ # Periscope — Agentic Schematic Validation -Periscope validates hardware schematics against component datasheets. It extracts constraints from PDFs, parses netlists and BOMs into a queryable graph, and runs an agentic validation loop to flag design violations. - -> **Open-core note.** This is the open-source core. A small set of files are -> "gateway-owned seams" — pass-through stubs here (`frontend/src/proxy.ts`, -> `use-optional-auth.ts`, `clerk-theme-provider.tsx`, -> `components/billing/*`, `sidebar-auth.tsx`, `pricing-section.tsx`, -> `analytics/*`, `lib/csp-hosts.ts`) that the hosted-cloud repo replaces -> with auth/billing implementations. Keep their export signatures stable, -> and never import auth/billing SDKs anywhere else in the frontend. On the -> backend, everything reaches billing only through -> `backend/services/billing_hook.py:get_billing()` (a no-op here). - -## System Overview - -Three layers: - -| Layer | Location | Purpose | -|-------|----------|---------| -| **Core library** | `backend/periscopex/` | Models, parsers, graph builder, agentic validator, passive resolver, taxonomy, BOM summary, derating | -| **Backend** | `backend/` | FastAPI app — async pipeline orchestration, SSE progress, project/file storage | -| **Frontend** | `frontend/` | Next.js 16 app — project dashboard, pipeline progress, report viewer, derating, admin dashboard | - -Plus `skills/` — extraction prompts (pintable, patterns, specs) inlined locally for DeepSeek. Do not upload to Anthropic Console. - -The pipeline stages: Parse BOM → Extract IC Pintables → Extract Simple Components → Extract Passives → DigiKey Auto-Resolve + Value Fallback → Build Graph → Direct Datasheet Review. Pipeline runs can be cancelled mid-execution via `POST /api/pipeline/{id}/cancel`. - -## Example Project - -`simple_project/` is the reference design for development and testing: - -- **MCU**: TI MSPM0G3507SPTR (U3) — 48-pin LQFP -- **USB-UART Bridge**: CH340E (U2) -- **LDO Regulator**: SPX3819M5-L-3-3 (U1) — 5V to 3.3V -- **ESD Protection**: USBLC6-2SC6 (D1) -- **Crystal**: 8 MHz (X1) with 18pF load caps (C9, C10) - -Files: `.asc` (PADS-PCB netlist; `.edn` EDIF 2.0.0 also accepted), `.csv`/`.xlsx` (BOM), `design_graph.json` (committed reference fixture used by tests). - -## Architecture Principles - -- **Modular extractors** — Domain-specific extraction per component type, unified constraint schema -- **Netlist as graph** — Queryable bipartite graph (components + nets) with traversal helpers -- **LLM API for PDF extraction** — Forced tool calls for structured output (pintable, passive patterns, specs). Default provider is DeepSeek. -- **Prompt caching** — Anthropic stamps `cache_control`; Gemini uses CachedContent; DeepSeek uses automatic prefix cache (cache-hit tokens in usage). -- **Local extraction skills** — `skills/*/SKILL.md` is inlined and `validate.py` runs in-process. Never call `scripts/upload_skills.py` (Anthropic Console). -- **Direct datasheet review** — The model reads the IC datasheet plus circuit neighborhood, compares to the reference application circuit, and flags issues via graph query tools (`find_connected_components`, `get_net_for_pin`, `get_pintable`). DeepSeek converts PDFs to text (and page images on the vision model). -- **Datasheet page trimming** — Large PDFs are keyword-trimmed to relevant pages before sending to Claude, reducing token cost (`pypdf`) -- **DigiKey fallback (exact MPN only)** — When pattern-based and direct extraction fail, DigiKey API fetches product parameters for auto-resolve. DigiKey matches only on exact MPN; fuzzy hits are rejected to avoid polluting the shared library with wrong-dielectric / wrong-voltage parts. -- **Value-string fallback** — When DigiKey misses an R/C/L/FB passive, a value-string resolver maps the BOM `Value` string to typed passive specs. Value-derived specs are persisted per-project only — never to the shared library. -- **Per-IC review error isolation** — Direct datasheet review runs each IC independently; one malformed payload or bad response cannot kill the whole run. Failed ICs surface as skipped components with the error. -- **Cross-IC excerpt budget (per-neighbor)** — To verify an interface finding the reviewer can pull a *connected* IC's datasheet pages (`get_datasheet_excerpt`). The budget is a global per-review page ceiling **plus a per-neighbor sub-budget**, so verifying one interface is never starved by pages already spent on other neighbors. -- **Finding normalization is downgrade-only** — A post-review per-IC normalize pass (`services/normalize_findings.py`) drops self-cancelling findings, merges same-root-cause findings, and re-grades severity — but only ever *downward*. A deterministic clamp caps each finding at the reviewer's calibrated severity (and any `Unverified:` finding at WARNING, preserving the prefix). -- **Cross-IC finding dedup** — After all per-IC reviews complete, a single pass (`services/dedupe_findings.py`) collapses one physical interface defect reported from both endpoints into a single finding. Gated by `cross_ic_dedup_enabled`; fail-soft. -- **Capacitor voltage derating** — Deterministic derating table computed from graph (ceramic/tantalum/electrolytic percentages, pass/fail per capacitor) -- **Deterministic checks over heuristics** — Exact checks where possible -- **Zero coupling between layers** — Backend calls periscopex functions with paths; frontend talks to backend via REST + SSE -- **Library deduplication** — Shared library (`library/extracted/`, `library/patterns/`, `library/models/`, `library/passives/`, `library/datasheets/`) caches extractions across projects -- **Content-addressed datasheets** — `library/datasheets/blobs/{md5}.pdf` stores unique PDFs once; `library/datasheets/refs/{safe_mpn}.json` maps MPNs to blobs (dedupe + multi-MPN sharing) -- **Taxonomy-driven extraction** — Living component taxonomy (`taxonomy/`) with per-subtype classification and specs schemas -- **Per-stage model config** — Each pipeline stage can use a different Claude model (e.g., Sonnet for review, Haiku for auto-resolve) -- **API call logging** — Every Claude API call is logged with token counts, cost, and timing per pipeline run -- **Report versioning** — Each project run is stamped with the current app version on the first `/start` transition (`ProjectMeta.periscope_version`). The version comes from `frontend/content/changelog.md`'s latest `##` heading — single source of truth — read at backend startup via `backend/_version.py`. - -## Datasheet Extraction - -Extracted data lives in `library/extracted/` (shared) or per-project under the storage backend. One JSON per MPN, schema in `backend/periscopex/models.py`. - -Per-MPN IC extraction captures: -1. **Pintable** — Pin number + name (required), description + alt functions (optional) -2. **Package info** — Base family, package, pin count, description -3. **Component subtype** — Dotted taxonomy path (e.g., `ic.mcu`, `ic.power.ldo`) - -For discrete/simple components: -4. **Specs** — Component specs (value, tolerance, package, voltage rating, etc.); parameters are filtered against taxonomy specs schemas - -Extraction inlines **local skills** (`skills/*/SKILL.md` + `validate.py`) against DeepSeek. Do not use Anthropic Console Skills. - -## Claude Console Skills - -``` -skills/ -├── extract-pintable/ # Pin table + package info + taxonomy -│ ├── SKILL.md # System prompt (YAML frontmatter + markdown) -│ ├── schema.json # Tool output schema -│ └── validate.py # Validation script -├── extract-pattern/ # Passive MPN pattern -└── extract-specs/ # Component specs (discrete, connectors, crystals, etc.) -``` - -## Taxonomy - -Living component taxonomy in `taxonomy/` — one JSON file per top-level type (ic, passive, connector, crystal, discrete, fuse, switch, test_point, transformer). Each subtype entry includes `description` and `example_mpn`. - -Key taxonomy features: -- **Ref prefix mapping** — `U→ic`, `R/C/L→passive`, `D/Q→discrete`, `X→crystal`, etc. -- **Dotted subtype paths** — e.g., `ic.mcu`, `passive.capacitor.ceramic`, `ic.protection.esd` -- **Dynamic growth** — `add_subtype()` adds new entries; concurrent-safe JSON writes -- **Specs schema auto-generation** — Type-level and subtype-level parameter specs schemas are auto-generated via Claude when a taxonomy entry has none; extraction discards parameters not in the schema (`extra_specs` field) - -## Scripts - -- `scripts/upload_skills.py` — leftover Claude Console uploader. **Do not run.** Skills are local + DeepSeek only. -- `scripts/migrate_datasheets_to_library.py` — One-time migration: copy per-project datasheets to `library/datasheets/` (dry-run by default, `--apply` to execute) -- `scripts/migrate_datasheets_to_blobs.py` — Migrate named-PDF datasheets into the content-addressed blobs/refs layout (dry-run by default, `--apply` to execute) -- `scripts/dedup_library_datasheets.py` — Remove redundant per-MPN datasheet PDFs when a passive pattern already has a `datasheet_key` (dry-run by default, `--apply` to execute) -- `scripts/gc_orphan_blobs.py` — Garbage-collect `library/datasheets/blobs/*.pdf` not referenced by any ref file -- `scripts/clear_rules_from_extractions.py` — Strip deprecated `rules`/`absolute_maximum_ratings` from existing library extractions - -## Tech Stack - -- **Core**: Python 3.12+, Pydantic 2.x, OpenAI SDK (DeepSeek), Anthropic SDK (optional), google-genai (optional), openpyxl, pypdf, PyMuPDF -- **Backend**: FastAPI, uvicorn, sse-starlette, pydantic-settings -- **Frontend**: Next.js 16 (App Router, Turbopack), React 19, Tailwind CSS v4, shadcn/ui (Base UI), react-pdf -- **AI**: DeepSeek Chat Completions (OpenAI-compatible) with forced tool calls for extraction and agentic review. Do not route stages to Anthropic. -- **Model**: `deepseek-flash` for extraction, review, auto-resolve, and normalize (per-stage overrides via `.env`) -- **Skills**: Local SKILL.md + validate.py on DeepSeek -- **External APIs**: DigiKey API v4 (OAuth2) — optional datasheet auto-fetch and parameter-based auto-resolve (`DIGIKEY_CLIENT_ID`, `DIGIKEY_CLIENT_SECRET`) - -## Extracted Model Versioning - -All `ComponentConstraints` extracted JSON files carry a `model_version` semver field: - -- **Initial value** — set from `default_model_version` in `backend/skills_manifest.json` (starts at `1.0.0`) -- **Minor bump** — increment `default_model_version` in `skills_manifest.json` when extraction prompts change (do **not** run `upload_skills.py`). - -**Rule**: When committing changes under `skills/`, bump `default_model_version` locally. Never call Anthropic. - -## Development Guidelines - -- Write tests against `simple_project/` — it's the ground truth -- Netlist parser and BOM parser are pure functions with no side effects -- All data structures use Pydantic models in `backend/periscopex/models.py` -- Frontend types in `frontend/src/lib/types.ts` must stay in sync with `backend/periscopex/models.py` -- Extraction prompts live in `skills/` (SKILL.md + schema.json + validate.py) and run locally against DeepSeek -- **Never swallow exceptions silently** — prefer logging or re-raising over bare `except: continue`. Silent failures hide real bugs. +Open-core checkout. **Native code** lives in `periscope/src`. **Inherited PinScope** lives in `periscope/dependency` (in-tree AGPL dependency — do not delete). Root `LICENSE` is AGPL-3.0. ## Running ```bash -# Backend (copy backend/.env.example to .env at repo root first) -python3 -m uvicorn backend.main:app --reload # localhost:8000 +# Backend (copy periscope/dependency/backend/.env.example to .env at repo root first) +python3 -m uvicorn backend.main:app --reload # localhost:8000 # Frontend -cd frontend && npm run dev # localhost:3000 +cd periscope/dependency/frontend && npm run dev # localhost:3000 ``` -Local mode needs no cloud services and no auth — projects are stored in `data/` and you are `user_id="local"` with admin access. +See `periscope/README.md` and root `README.md`. diff --git a/README.md b/README.md index 2a3f1f6..1e7a73b 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,21 @@ Default routing: | Per-IC datasheet review | `deepseek-flash` | | Auto-resolve / normalize | `deepseek-flash` | -Override with `PROVIDER_*` and `MODEL_*_DEEPSEEK` in `backend/.env`. See `backend/.env.example`. +Override with `PROVIDER_*` and `MODEL_*_DEEPSEEK` in `.env`. See `periscope/dependency/backend/.env.example`. + +## Layout + +- `periscope/src/` — native Periscope +- `periscope/dependency/` — inherited PinScope (AGPL in-tree dependency; do not delete) +- `LICENSE` — GNU AGPL v3 (visible at repo root) +- `vendor/impedancefinder/` — third party (license UNKNOWN) + +See `periscope/README.md` and `periscope/src/docs/development/PINSCOPE_INDEPENDENCE_PLAN.md`. ## How it works

- Pipeline: the netlist and BOM are parsed into a design graph; datasheet PDFs are extracted into pin tables and specs; a per-IC review reads both and files findings cited to datasheet pages; the derating table and BOM roll-up are computed straight from the graph, no model involved. + Pipeline: the netlist and BOM are parsed into a design graph; datasheet PDFs are extracted into pin tables and specs; a per-IC review reads both and files findings cited to datasheet pages; the derating table and BOM roll-up are computed straight from the graph, no model involved.

1. **Parse** the BOM (CSV/XLSX) and netlist (PADS-PCB `.asc` or EDIF 2.0.0 `.edn`) into a queryable bipartite graph of components and nets. @@ -38,29 +47,29 @@ Override with `PROVIDER_*` and `MODEL_*_DEEPSEEK` in `backend/.env`. See `backen ## Try it on the bundled design -`simple_project/` is a small MSPM0G3507 board with a CH340E USB-UART bridge and an SPX3819 LDO. +`periscope/dependency/simple_project/` is a small MSPM0G3507 board with a CH340E USB-UART bridge and an SPX3819 LDO. You need Python 3.12+, Node 20+, and a [DeepSeek API key](https://platform.deepseek.com/): ```bash python3 -m venv .venv source .venv/bin/activate -pip install -r backend/requirements.txt -cp backend/.env.example backend/.env # set DEEPSEEK_API_KEY +pip install -r periscope/dependency/backend/requirements.txt +cp periscope/dependency/backend/.env.example .env # set DEEPSEEK_API_KEY python3 -m uvicorn backend.main:app --reload --host 127.0.0.1 --port 18741 # in another terminal -cd frontend && npm install +cd periscope/dependency/frontend && npm install NEXT_PUBLIC_API_URL=http://127.0.0.1:18741 npm run dev -- --port 18742 --hostname 127.0.0.1 ``` -Open the frontend URL, create a project, and feed it the netlist and BOM from `simple_project/`. Datasheets are fetched automatically (LCSC, TI, optional DigiKey); you can still drop in PDFs by hand. Fetched PDFs and extracted pin tables land in the **Library** (sidebar) and are reused on later projects. Everything runs locally against your own DeepSeek key; projects and the extraction library live in `data/`. Skills are local `skills/*/SKILL.md` — do not run `scripts/upload_skills.py`. +Open the frontend URL, create a project, and feed it the netlist and BOM from `periscope/dependency/simple_project/`. Datasheets are fetched automatically (LCSC, TI, optional DigiKey); you can still drop in PDFs by hand. Fetched PDFs and extracted pin tables land in the **Library** (sidebar) and are reused on later projects. Everything runs locally against your own DeepSeek key; projects and the extraction library live in `data/`. Skills are local `periscope/dependency/skills/*/SKILL.md` — do not run `periscope/dependency/scripts/upload_skills.py`. ## Docker ```bash -cp backend/.env.example .env # set DEEPSEEK_API_KEY +cp periscope/dependency/backend/.env.example .env # set DEEPSEEK_API_KEY docker compose up --build ``` diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 3b379b1..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM python:3.12-slim -RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -# Install dependencies first (layer caching). -# Open-core: the private gateway repo adds backend/requirements-gateway.txt -# (Stripe, etc.); a plain core checkout has no such file and skips that step. -COPY backend/requirements*.txt /app/backend/ -RUN pip install --no-cache-dir -r /app/backend/requirements.txt \ - && if [ -f /app/backend/requirements-gateway.txt ]; then \ - pip install --no-cache-dir -r /app/backend/requirements-gateway.txt; \ - fi - -# Copy application code -COPY backend/ /app/backend/ - -# Copy runtime assets needed by the backend -# Taxonomy: fallback for local mode; GCS mode downloads from bucket -COPY taxonomy/ /app/taxonomy/ - -# Extraction skills (SKILL.md + validate.py) — required for DeepSeek/Gemini -COPY skills/ /app/skills/ - -# Changelog: single source of truth for the user-facing Periscope version. -COPY frontend/content/changelog.md /app/changelog.md - -# ImpedenceFinder closed-form engine (no OpenEMS / pcbnew). -COPY vendor/ /app/vendor/ - -EXPOSE 8080 - -CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/backend/__init__.py b/backend/__init__.py index e69de29..18c8f1d 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -0,0 +1,18 @@ +"""Backend package facade: native Periscope + inherited PinScope. + +``periscope/src/backend`` and ``periscope/dependency/backend`` are merged via +pkgutil path extension. Do not put application modules in this directory. +""" +from __future__ import annotations + +import sys +from pathlib import Path +from pkgutil import extend_path + +_REPO = Path(__file__).resolve().parents[1] +for _p in (_REPO / "periscope" / "src", _REPO / "periscope" / "dependency"): + _s = str(_p) + if _p.is_dir() and _s not in sys.path: + sys.path.insert(0, _s) + +__path__ = list(extend_path(__path__, __name__)) diff --git a/backend/periscopex/__init__.py b/backend/periscopex/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/routers/__init__.py b/backend/routers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/backend/services/__init__.py b/backend/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/docker-compose.yml b/docker-compose.yml index 81cc02e..20b76b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: backend: build: context: . - dockerfile: backend/Dockerfile + dockerfile: periscope/src/backend/Dockerfile container_name: periscope-backend restart: unless-stopped @@ -25,7 +25,7 @@ services: volumes: - ./data:/app/data - - ./taxonomy:/app/taxonomy + - ./periscope/dependency/taxonomy:/app/taxonomy ports: - "8080:8080" @@ -38,8 +38,8 @@ services: frontend: build: - context: ./frontend - dockerfile: dockerfile + context: . + dockerfile: periscope/src/frontend/dockerfile args: NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8080} NEXT_PUBLIC_AUTH_MODE: ${NEXT_PUBLIC_AUTH_MODE:-} diff --git a/docs/development/PINSCOPE_FENCE.md b/docs/development/PINSCOPE_FENCE.md deleted file mode 100644 index 853288d..0000000 --- a/docs/development/PINSCOPE_FENCE.md +++ /dev/null @@ -1,46 +0,0 @@ -# Recinto PinScope (Fase A) - -Documented fence. **No file moves.** `LICENSE` AGPL-3.0 is unchanged. The GitHub fork `manvalan/periscope` → `Faradworks/Pinscope` is **not** detached. - -## Branch topology - -| Ref | Role | -| --- | --- | -| `origin/main` | Archival: upstream Pinscope + Docker/OSS (+3). Not the live product. | -| `cursor/pcb-review-plan-44dd` | Live product (schema, PCB exam, finding engine). **PCB workers stay here.** | -| `cursor/pinscope-fase-a-identity` | Fase A identity + compat fence only. Do not land engine/PCB rewrites on this branch. | - -Merging product onto `main` is a later process step, still without detaching the fork. - -## Inherited tree (KEEP behind the fence) - -Treat as in-tree PinScope, not as “native Periscope”: - -- `backend/periscopex/{graph,models,parsers*,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check,pin_function_tokens}` -- `backend/services/pipeline.py`, `pipeline_worker.py`, `validation.py`, `extraction.py` (DIRECT) -- `skills/`, `scripts/upload_skills.py` -- Open-core Clerk/billing stubs -- `simple_project/` fixtures, `docs/how-it-works.svg` - -Native Periscope (do not fold into the fence): `finding_engine.py`, PCB/placement/antenna jobs, DeepSeek providers, local JWT, this identity layer. - -## Write vs read (identifiers) - -| Write (native) | Read-only / migrate | -| --- | --- | -| `periscope_token` | `pinscope_token` | -| `periscopex:*` localStorage | `pinscopex:*` | -| JWT `iss=periscope-local` | also accept `pinscope-local` | -| `periscope_version` in `project.json` | also accept `pinscope_version` | - -Code: `frontend/src/lib/pinscope-compat.ts`, `backend/pinscope_compat.py`. - -**Do not rename** live Docker networks `pinscope_pinscope` / `pinscope_periscope` in Fase A (VPS Caddy). - -## Identity - -Operator of https://periscope.michelebigi.it is **Michele Bigi**. UI, TOS, privacy, metadata, and contact must not say the operator is Faradworks, Inc. Attribution to Faradworks/Pinscope remains for AGPL lineage only. - -## Out of scope here (later phases) - -Clerk/Anthropic runtime isolation (B). Rewrite of `validate.py` / finding engine (C). UI FindingCard (D). Parsers/graph (E). Fork detach (legal, not this plan). diff --git a/periscope/README.md b/periscope/README.md new file mode 100644 index 0000000..af81ef9 --- /dev/null +++ b/periscope/README.md @@ -0,0 +1,14 @@ +# Periscope tree layout + +Physical split (not a rewrite). AGPL `LICENSE` stays at the git root. The GitHub fork is not detached. + +| Tree | Path | Role | +| --- | --- | --- | +| Native Periscope | `periscope/src/` | Finding engine clamp, PCB/placement/antenna, DeepSeek, local auth, KiCad plugin, deploy Dockerfiles | +| Inherited PinScope | `periscope/dependency/` | Graph/parsers/`validate.py`, pipeline/extraction, OSS Next.js shell, skills, taxonomy, `simple_project` | +| Third party | `vendor/impedancefinder/` | ImpedenceFinder (license UNKNOWN) | +| Glue | `backend/__init__.py` | Merges the two `backend` packages for local imports | + +**Do not empty-delete `periscope/dependency/`.** Later phases replace PinScope modules incrementally in `periscope/src` (Fase C). `validate.py` stays until a native reviewer exists. + +Docker overlays `dependency` then `src` into `/app`. Local frontend: `cd periscope/dependency/frontend && npm run dev` (native files are symlinked from `periscope/src/frontend`). diff --git a/periscope/dependency/CLAUDE.md b/periscope/dependency/CLAUDE.md new file mode 100644 index 0000000..7015a21 --- /dev/null +++ b/periscope/dependency/CLAUDE.md @@ -0,0 +1,149 @@ +# Periscope — Agentic Schematic Validation + +Periscope validates hardware schematics against component datasheets. It extracts constraints from PDFs, parses netlists and BOMs into a queryable graph, and runs an agentic validation loop to flag design violations. + +> **Open-core note.** This is the open-source core. A small set of files are +> "gateway-owned seams" — pass-through stubs here (`frontend/src/proxy.ts`, +> `use-optional-auth.ts`, `clerk-theme-provider.tsx`, +> `components/billing/*`, `sidebar-auth.tsx`, `pricing-section.tsx`, +> `analytics/*`, `lib/csp-hosts.ts`) that the hosted-cloud repo replaces +> with auth/billing implementations. Keep their export signatures stable, +> and never import auth/billing SDKs anywhere else in the frontend. On the +> backend, everything reaches billing only through +> `backend/services/billing_hook.py:get_billing()` (a no-op here). + +## System Overview + +Three layers: + +| Layer | Location | Purpose | +|-------|----------|---------| +| **Core library** | `backend/periscopex/` | Models, parsers, graph builder, agentic validator, passive resolver, taxonomy, BOM summary, derating | +| **Backend** | `backend/` | FastAPI app — async pipeline orchestration, SSE progress, project/file storage | +| **Frontend** | `frontend/` | Next.js 16 app — project dashboard, pipeline progress, report viewer, derating, admin dashboard | + +Plus `skills/` — extraction prompts (pintable, patterns, specs) inlined locally for DeepSeek. Do not upload to Anthropic Console. + +The pipeline stages: Parse BOM → Extract IC Pintables → Extract Simple Components → Extract Passives → DigiKey Auto-Resolve + Value Fallback → Build Graph → Direct Datasheet Review. Pipeline runs can be cancelled mid-execution via `POST /api/pipeline/{id}/cancel`. + +## Example Project + +`simple_project/` is the reference design for development and testing: + +- **MCU**: TI MSPM0G3507SPTR (U3) — 48-pin LQFP +- **USB-UART Bridge**: CH340E (U2) +- **LDO Regulator**: SPX3819M5-L-3-3 (U1) — 5V to 3.3V +- **ESD Protection**: USBLC6-2SC6 (D1) +- **Crystal**: 8 MHz (X1) with 18pF load caps (C9, C10) + +Files: `.asc` (PADS-PCB netlist; `.edn` EDIF 2.0.0 also accepted), `.csv`/`.xlsx` (BOM), `design_graph.json` (committed reference fixture used by tests). + +## Architecture Principles + +- **Modular extractors** — Domain-specific extraction per component type, unified constraint schema +- **Netlist as graph** — Queryable bipartite graph (components + nets) with traversal helpers +- **LLM API for PDF extraction** — Forced tool calls for structured output (pintable, passive patterns, specs). Default provider is DeepSeek. +- **Prompt caching** — Anthropic stamps `cache_control`; Gemini uses CachedContent; DeepSeek uses automatic prefix cache (cache-hit tokens in usage). +- **Local extraction skills** — `skills/*/SKILL.md` is inlined and `validate.py` runs in-process. Never call `scripts/upload_skills.py` (Anthropic Console). +- **Direct datasheet review** — The model reads the IC datasheet plus circuit neighborhood, compares to the reference application circuit, and flags issues via graph query tools (`find_connected_components`, `get_net_for_pin`, `get_pintable`). DeepSeek converts PDFs to text (and page images on the vision model). +- **Datasheet page trimming** — Large PDFs are keyword-trimmed to relevant pages before sending to Claude, reducing token cost (`pypdf`) +- **DigiKey fallback (exact MPN only)** — When pattern-based and direct extraction fail, DigiKey API fetches product parameters for auto-resolve. DigiKey matches only on exact MPN; fuzzy hits are rejected to avoid polluting the shared library with wrong-dielectric / wrong-voltage parts. +- **Value-string fallback** — When DigiKey misses an R/C/L/FB passive, a value-string resolver maps the BOM `Value` string to typed passive specs. Value-derived specs are persisted per-project only — never to the shared library. +- **Per-IC review error isolation** — Direct datasheet review runs each IC independently; one malformed payload or bad response cannot kill the whole run. Failed ICs surface as skipped components with the error. +- **Cross-IC excerpt budget (per-neighbor)** — To verify an interface finding the reviewer can pull a *connected* IC's datasheet pages (`get_datasheet_excerpt`). The budget is a global per-review page ceiling **plus a per-neighbor sub-budget**, so verifying one interface is never starved by pages already spent on other neighbors. +- **Finding normalization is downgrade-only** — A post-review per-IC normalize pass (`services/normalize_findings.py`) drops self-cancelling findings, merges same-root-cause findings, and re-grades severity — but only ever *downward*. A deterministic clamp caps each finding at the reviewer's calibrated severity (and any `Unverified:` finding at WARNING, preserving the prefix). +- **Cross-IC finding dedup** — After all per-IC reviews complete, a single pass (`services/dedupe_findings.py`) collapses one physical interface defect reported from both endpoints into a single finding. Gated by `cross_ic_dedup_enabled`; fail-soft. +- **Capacitor voltage derating** — Deterministic derating table computed from graph (ceramic/tantalum/electrolytic percentages, pass/fail per capacitor) +- **Deterministic checks over heuristics** — Exact checks where possible +- **Zero coupling between layers** — Backend calls periscopex functions with paths; frontend talks to backend via REST + SSE +- **Library deduplication** — Shared library (`library/extracted/`, `library/patterns/`, `library/models/`, `library/passives/`, `library/datasheets/`) caches extractions across projects +- **Content-addressed datasheets** — `library/datasheets/blobs/{md5}.pdf` stores unique PDFs once; `library/datasheets/refs/{safe_mpn}.json` maps MPNs to blobs (dedupe + multi-MPN sharing) +- **Taxonomy-driven extraction** — Living component taxonomy (`taxonomy/`) with per-subtype classification and specs schemas +- **Per-stage model config** — Each pipeline stage can use a different Claude model (e.g., Sonnet for review, Haiku for auto-resolve) +- **API call logging** — Every Claude API call is logged with token counts, cost, and timing per pipeline run +- **Report versioning** — Each project run is stamped with the current app version on the first `/start` transition (`ProjectMeta.periscope_version`). The version comes from `frontend/content/changelog.md`'s latest `##` heading — single source of truth — read at backend startup via `backend/_version.py`. + +## Datasheet Extraction + +Extracted data lives in `library/extracted/` (shared) or per-project under the storage backend. One JSON per MPN, schema in `backend/periscopex/models.py`. + +Per-MPN IC extraction captures: +1. **Pintable** — Pin number + name (required), description + alt functions (optional) +2. **Package info** — Base family, package, pin count, description +3. **Component subtype** — Dotted taxonomy path (e.g., `ic.mcu`, `ic.power.ldo`) + +For discrete/simple components: +4. **Specs** — Component specs (value, tolerance, package, voltage rating, etc.); parameters are filtered against taxonomy specs schemas + +Extraction inlines **local skills** (`skills/*/SKILL.md` + `validate.py`) against DeepSeek. Do not use Anthropic Console Skills. + +## Claude Console Skills + +``` +skills/ +├── extract-pintable/ # Pin table + package info + taxonomy +│ ├── SKILL.md # System prompt (YAML frontmatter + markdown) +│ ├── schema.json # Tool output schema +│ └── validate.py # Validation script +├── extract-pattern/ # Passive MPN pattern +└── extract-specs/ # Component specs (discrete, connectors, crystals, etc.) +``` + +## Taxonomy + +Living component taxonomy in `taxonomy/` — one JSON file per top-level type (ic, passive, connector, crystal, discrete, fuse, switch, test_point, transformer). Each subtype entry includes `description` and `example_mpn`. + +Key taxonomy features: +- **Ref prefix mapping** — `U→ic`, `R/C/L→passive`, `D/Q→discrete`, `X→crystal`, etc. +- **Dotted subtype paths** — e.g., `ic.mcu`, `passive.capacitor.ceramic`, `ic.protection.esd` +- **Dynamic growth** — `add_subtype()` adds new entries; concurrent-safe JSON writes +- **Specs schema auto-generation** — Type-level and subtype-level parameter specs schemas are auto-generated via Claude when a taxonomy entry has none; extraction discards parameters not in the schema (`extra_specs` field) + +## Scripts + +- `scripts/upload_skills.py` — leftover Claude Console uploader. **Do not run.** Skills are local + DeepSeek only. +- `scripts/migrate_datasheets_to_library.py` — One-time migration: copy per-project datasheets to `library/datasheets/` (dry-run by default, `--apply` to execute) +- `scripts/migrate_datasheets_to_blobs.py` — Migrate named-PDF datasheets into the content-addressed blobs/refs layout (dry-run by default, `--apply` to execute) +- `scripts/dedup_library_datasheets.py` — Remove redundant per-MPN datasheet PDFs when a passive pattern already has a `datasheet_key` (dry-run by default, `--apply` to execute) +- `scripts/gc_orphan_blobs.py` — Garbage-collect `library/datasheets/blobs/*.pdf` not referenced by any ref file +- `scripts/clear_rules_from_extractions.py` — Strip deprecated `rules`/`absolute_maximum_ratings` from existing library extractions + +## Tech Stack + +- **Core**: Python 3.12+, Pydantic 2.x, OpenAI SDK (DeepSeek), Anthropic SDK (optional), google-genai (optional), openpyxl, pypdf, PyMuPDF +- **Backend**: FastAPI, uvicorn, sse-starlette, pydantic-settings +- **Frontend**: Next.js 16 (App Router, Turbopack), React 19, Tailwind CSS v4, shadcn/ui (Base UI), react-pdf +- **AI**: DeepSeek Chat Completions (OpenAI-compatible) with forced tool calls for extraction and agentic review. Do not route stages to Anthropic. +- **Model**: `deepseek-flash` for extraction, review, auto-resolve, and normalize (per-stage overrides via `.env`) +- **Skills**: Local SKILL.md + validate.py on DeepSeek +- **External APIs**: DigiKey API v4 (OAuth2) — optional datasheet auto-fetch and parameter-based auto-resolve (`DIGIKEY_CLIENT_ID`, `DIGIKEY_CLIENT_SECRET`) + +## Extracted Model Versioning + +All `ComponentConstraints` extracted JSON files carry a `model_version` semver field: + +- **Initial value** — set from `default_model_version` in `backend/skills_manifest.json` (starts at `1.0.0`) +- **Minor bump** — increment `default_model_version` in `skills_manifest.json` when extraction prompts change (do **not** run `upload_skills.py`). + +**Rule**: When committing changes under `skills/`, bump `default_model_version` locally. Never call Anthropic. + +## Development Guidelines + +- Write tests against `simple_project/` — it's the ground truth +- Netlist parser and BOM parser are pure functions with no side effects +- All data structures use Pydantic models in `backend/periscopex/models.py` +- Frontend types in `frontend/src/lib/types.ts` must stay in sync with `backend/periscopex/models.py` +- Extraction prompts live in `skills/` (SKILL.md + schema.json + validate.py) and run locally against DeepSeek +- **Never swallow exceptions silently** — prefer logging or re-raising over bare `except: continue`. Silent failures hide real bugs. + +## Running + +```bash +# Backend (copy backend/.env.example to .env at repo root first) +python3 -m uvicorn backend.main:app --reload # localhost:8000 + +# Frontend +cd frontend && npm run dev # localhost:3000 +``` + +Local mode needs no cloud services and no auth — projects are stored in `data/` and you are `user_id="local"` with admin access. diff --git a/backend/.env.example b/periscope/dependency/backend/.env.example similarity index 100% rename from backend/.env.example rename to periscope/dependency/backend/.env.example diff --git a/backend/CLAUDE.md b/periscope/dependency/backend/CLAUDE.md similarity index 100% rename from backend/CLAUDE.md rename to periscope/dependency/backend/CLAUDE.md diff --git a/periscope/dependency/backend/__init__.py b/periscope/dependency/backend/__init__.py new file mode 100644 index 0000000..0b54cc6 --- /dev/null +++ b/periscope/dependency/backend/__init__.py @@ -0,0 +1,4 @@ +"""PinScope-inherited backend package (in-tree dependency).""" +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/config.py b/periscope/dependency/backend/config.py similarity index 96% rename from backend/config.py rename to periscope/dependency/backend/config.py index cfd918d..36ff9e9 100644 --- a/backend/config.py +++ b/periscope/dependency/backend/config.py @@ -7,6 +7,11 @@ from pathlib import Path from pydantic import Field from pydantic_settings import BaseSettings +from backend.repo_paths import data_dir as _data_dir +from backend.repo_paths import env_file as _env_file +from backend.repo_paths import skills_dir as _skills_dir +from backend.repo_paths import taxonomy_dir as _taxonomy_dir + # Resolve paths relative to the project root (one level up from backend/) _BACKEND_DIR = Path(__file__).resolve().parent _PROJECT_ROOT = _BACKEND_DIR.parent @@ -111,10 +116,10 @@ class Settings(BaseSettings): # single finding. Runs once after all per-IC reviews complete. cross_ic_dedup_enabled: bool = True - # Paths (relative to project root, used by LocalStorageBackend) - data_dir: Path = _PROJECT_ROOT / "data" - taxonomy_dir: Path = _PROJECT_ROOT / "taxonomy" - skills_dir: Path = _PROJECT_ROOT / "skills" + # Paths (git split: data at repo root; taxonomy/skills under periscope/dependency) + data_dir: Path = _data_dir() + taxonomy_dir: Path = _taxonomy_dir() + skills_dir: Path = _skills_dir() # GCS (if set, use GCSStorageBackend; otherwise LocalStorageBackend) gcs_bucket: str = "" @@ -193,7 +198,7 @@ class Settings(BaseSettings): pipeline_sweeper_stale_seconds: int = 60 model_config = { - "env_file": str(_BACKEND_DIR / ".env"), + "env_file": str(_env_file()), "env_file_encoding": "utf-8", "extra": "ignore", } diff --git a/backend/main.py b/periscope/dependency/backend/main.py similarity index 100% rename from backend/main.py rename to periscope/dependency/backend/main.py diff --git a/backend/middleware/__init__.py b/periscope/dependency/backend/middleware/__init__.py similarity index 100% rename from backend/middleware/__init__.py rename to periscope/dependency/backend/middleware/__init__.py diff --git a/backend/middleware/auth.py b/periscope/dependency/backend/middleware/auth.py similarity index 100% rename from backend/middleware/auth.py rename to periscope/dependency/backend/middleware/auth.py diff --git a/periscope/dependency/backend/periscopex/__init__.py b/periscope/dependency/backend/periscopex/__init__.py new file mode 100644 index 0000000..b36383a --- /dev/null +++ b/periscope/dependency/backend/periscopex/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/periscopex/bom_summary.py b/periscope/dependency/backend/periscopex/bom_summary.py similarity index 100% rename from backend/periscopex/bom_summary.py rename to periscope/dependency/backend/periscopex/bom_summary.py diff --git a/backend/periscopex/derating.py b/periscope/dependency/backend/periscopex/derating.py similarity index 100% rename from backend/periscopex/derating.py rename to periscope/dependency/backend/periscopex/derating.py diff --git a/backend/periscopex/graph.py b/periscope/dependency/backend/periscopex/graph.py similarity index 100% rename from backend/periscopex/graph.py rename to periscope/dependency/backend/periscopex/graph.py diff --git a/backend/periscopex/led_current_check.py b/periscope/dependency/backend/periscopex/led_current_check.py similarity index 100% rename from backend/periscopex/led_current_check.py rename to periscope/dependency/backend/periscopex/led_current_check.py diff --git a/backend/periscopex/models.py b/periscope/dependency/backend/periscopex/models.py similarity index 100% rename from backend/periscopex/models.py rename to periscope/dependency/backend/periscopex/models.py diff --git a/backend/periscopex/parsers.py b/periscope/dependency/backend/periscopex/parsers.py similarity index 100% rename from backend/periscopex/parsers.py rename to periscope/dependency/backend/periscopex/parsers.py diff --git a/backend/periscopex/parsers_edif.py b/periscope/dependency/backend/periscopex/parsers_edif.py similarity index 100% rename from backend/periscopex/parsers_edif.py rename to periscope/dependency/backend/periscopex/parsers_edif.py diff --git a/backend/periscopex/pin_function_tokens.py b/periscope/dependency/backend/periscopex/pin_function_tokens.py similarity index 100% rename from backend/periscopex/pin_function_tokens.py rename to periscope/dependency/backend/periscopex/pin_function_tokens.py diff --git a/backend/periscopex/pin_mux_check.py b/periscope/dependency/backend/periscopex/pin_mux_check.py similarity index 100% rename from backend/periscopex/pin_mux_check.py rename to periscope/dependency/backend/periscopex/pin_mux_check.py diff --git a/backend/periscopex/resolve_passives.py b/periscope/dependency/backend/periscopex/resolve_passives.py similarity index 100% rename from backend/periscopex/resolve_passives.py rename to periscope/dependency/backend/periscopex/resolve_passives.py diff --git a/backend/periscopex/taxonomy.py b/periscope/dependency/backend/periscopex/taxonomy.py similarity index 100% rename from backend/periscopex/taxonomy.py rename to periscope/dependency/backend/periscopex/taxonomy.py diff --git a/backend/periscopex/utils.py b/periscope/dependency/backend/periscopex/utils.py similarity index 100% rename from backend/periscopex/utils.py rename to periscope/dependency/backend/periscopex/utils.py diff --git a/backend/periscopex/validate.py b/periscope/dependency/backend/periscopex/validate.py similarity index 100% rename from backend/periscopex/validate.py rename to periscope/dependency/backend/periscopex/validate.py diff --git a/backend/periscopex/validation_tools.py b/periscope/dependency/backend/periscopex/validation_tools.py similarity index 100% rename from backend/periscopex/validation_tools.py rename to periscope/dependency/backend/periscopex/validation_tools.py diff --git a/backend/pipeline_worker.py b/periscope/dependency/backend/pipeline_worker.py similarity index 100% rename from backend/pipeline_worker.py rename to periscope/dependency/backend/pipeline_worker.py diff --git a/backend/requirements.txt b/periscope/dependency/backend/requirements.txt similarity index 100% rename from backend/requirements.txt rename to periscope/dependency/backend/requirements.txt diff --git a/periscope/dependency/backend/routers/__init__.py b/periscope/dependency/backend/routers/__init__.py new file mode 100644 index 0000000..b36383a --- /dev/null +++ b/periscope/dependency/backend/routers/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/routers/admin.py b/periscope/dependency/backend/routers/admin.py similarity index 100% rename from backend/routers/admin.py rename to periscope/dependency/backend/routers/admin.py diff --git a/backend/routers/contact.py b/periscope/dependency/backend/routers/contact.py similarity index 100% rename from backend/routers/contact.py rename to periscope/dependency/backend/routers/contact.py diff --git a/backend/routers/deps.py b/periscope/dependency/backend/routers/deps.py similarity index 100% rename from backend/routers/deps.py rename to periscope/dependency/backend/routers/deps.py diff --git a/backend/routers/feedback.py b/periscope/dependency/backend/routers/feedback.py similarity index 100% rename from backend/routers/feedback.py rename to periscope/dependency/backend/routers/feedback.py diff --git a/backend/routers/pipeline.py b/periscope/dependency/backend/routers/pipeline.py similarity index 100% rename from backend/routers/pipeline.py rename to periscope/dependency/backend/routers/pipeline.py diff --git a/backend/routers/projects.py b/periscope/dependency/backend/routers/projects.py similarity index 100% rename from backend/routers/projects.py rename to periscope/dependency/backend/routers/projects.py diff --git a/backend/routers/reports.py b/periscope/dependency/backend/routers/reports.py similarity index 100% rename from backend/routers/reports.py rename to periscope/dependency/backend/routers/reports.py diff --git a/backend/routers/survey.py b/periscope/dependency/backend/routers/survey.py similarity index 100% rename from backend/routers/survey.py rename to periscope/dependency/backend/routers/survey.py diff --git a/periscope/dependency/backend/services/__init__.py b/periscope/dependency/backend/services/__init__.py new file mode 100644 index 0000000..b36383a --- /dev/null +++ b/periscope/dependency/backend/services/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/services/admin_settings.py b/periscope/dependency/backend/services/admin_settings.py similarity index 100% rename from backend/services/admin_settings.py rename to periscope/dependency/backend/services/admin_settings.py diff --git a/backend/services/api_logs.py b/periscope/dependency/backend/services/api_logs.py similarity index 100% rename from backend/services/api_logs.py rename to periscope/dependency/backend/services/api_logs.py diff --git a/backend/services/billing_hook.py b/periscope/dependency/backend/services/billing_hook.py similarity index 100% rename from backend/services/billing_hook.py rename to periscope/dependency/backend/services/billing_hook.py diff --git a/backend/services/cost_estimator.py b/periscope/dependency/backend/services/cost_estimator.py similarity index 100% rename from backend/services/cost_estimator.py rename to periscope/dependency/backend/services/cost_estimator.py diff --git a/backend/services/datasheet_store.py b/periscope/dependency/backend/services/datasheet_store.py similarity index 100% rename from backend/services/datasheet_store.py rename to periscope/dependency/backend/services/datasheet_store.py diff --git a/backend/services/dedupe_findings.py b/periscope/dependency/backend/services/dedupe_findings.py similarity index 100% rename from backend/services/dedupe_findings.py rename to periscope/dependency/backend/services/dedupe_findings.py diff --git a/backend/services/digikey.py b/periscope/dependency/backend/services/digikey.py similarity index 100% rename from backend/services/digikey.py rename to periscope/dependency/backend/services/digikey.py diff --git a/backend/services/email.py b/periscope/dependency/backend/services/email.py similarity index 100% rename from backend/services/email.py rename to periscope/dependency/backend/services/email.py diff --git a/backend/services/event_bridge.py b/periscope/dependency/backend/services/event_bridge.py similarity index 100% rename from backend/services/event_bridge.py rename to periscope/dependency/backend/services/event_bridge.py diff --git a/backend/services/extraction.py b/periscope/dependency/backend/services/extraction.py similarity index 100% rename from backend/services/extraction.py rename to periscope/dependency/backend/services/extraction.py diff --git a/backend/services/job_runner.py b/periscope/dependency/backend/services/job_runner.py similarity index 100% rename from backend/services/job_runner.py rename to periscope/dependency/backend/services/job_runner.py diff --git a/backend/services/llm/__init__.py b/periscope/dependency/backend/services/llm/__init__.py similarity index 91% rename from backend/services/llm/__init__.py rename to periscope/dependency/backend/services/llm/__init__.py index 8cb1b88..b2f37ee 100644 --- a/backend/services/llm/__init__.py +++ b/periscope/dependency/backend/services/llm/__init__.py @@ -5,6 +5,9 @@ All model calls in the backend route through this package via the overrides via ``Settings.provider_*`` env vars route specific stages to Anthropic or Gemini if those keys are configured. """ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) from backend.services.llm.factory import call_with_fallback, get_provider from backend.services.llm.types import ( diff --git a/backend/services/llm/anthropic_provider.py b/periscope/dependency/backend/services/llm/anthropic_provider.py similarity index 100% rename from backend/services/llm/anthropic_provider.py rename to periscope/dependency/backend/services/llm/anthropic_provider.py diff --git a/backend/services/llm/base.py b/periscope/dependency/backend/services/llm/base.py similarity index 100% rename from backend/services/llm/base.py rename to periscope/dependency/backend/services/llm/base.py diff --git a/backend/services/llm/factory.py b/periscope/dependency/backend/services/llm/factory.py similarity index 100% rename from backend/services/llm/factory.py rename to periscope/dependency/backend/services/llm/factory.py diff --git a/backend/services/llm/gemini_provider.py b/periscope/dependency/backend/services/llm/gemini_provider.py similarity index 100% rename from backend/services/llm/gemini_provider.py rename to periscope/dependency/backend/services/llm/gemini_provider.py diff --git a/backend/services/llm/pricing.py b/periscope/dependency/backend/services/llm/pricing.py similarity index 100% rename from backend/services/llm/pricing.py rename to periscope/dependency/backend/services/llm/pricing.py diff --git a/backend/services/llm/types.py b/periscope/dependency/backend/services/llm/types.py similarity index 100% rename from backend/services/llm/types.py rename to periscope/dependency/backend/services/llm/types.py diff --git a/backend/services/normalize_findings.py b/periscope/dependency/backend/services/normalize_findings.py similarity index 100% rename from backend/services/normalize_findings.py rename to periscope/dependency/backend/services/normalize_findings.py diff --git a/backend/services/pipeline.py b/periscope/dependency/backend/services/pipeline.py similarity index 100% rename from backend/services/pipeline.py rename to periscope/dependency/backend/services/pipeline.py diff --git a/backend/services/projects.py b/periscope/dependency/backend/services/projects.py similarity index 100% rename from backend/services/projects.py rename to periscope/dependency/backend/services/projects.py diff --git a/backend/services/purple_parts.py b/periscope/dependency/backend/services/purple_parts.py similarity index 100% rename from backend/services/purple_parts.py rename to periscope/dependency/backend/services/purple_parts.py diff --git a/backend/services/storage.py b/periscope/dependency/backend/services/storage.py similarity index 100% rename from backend/services/storage.py rename to periscope/dependency/backend/services/storage.py diff --git a/backend/services/storage_gcs.py b/periscope/dependency/backend/services/storage_gcs.py similarity index 100% rename from backend/services/storage_gcs.py rename to periscope/dependency/backend/services/storage_gcs.py diff --git a/backend/services/survey.py b/periscope/dependency/backend/services/survey.py similarity index 100% rename from backend/services/survey.py rename to periscope/dependency/backend/services/survey.py diff --git a/backend/services/validation.py b/periscope/dependency/backend/services/validation.py similarity index 100% rename from backend/services/validation.py rename to periscope/dependency/backend/services/validation.py diff --git a/backend/skills_manifest.json b/periscope/dependency/backend/skills_manifest.json similarity index 100% rename from backend/skills_manifest.json rename to periscope/dependency/backend/skills_manifest.json diff --git a/docs/how-it-works.svg b/periscope/dependency/docs/how-it-works.svg similarity index 100% rename from docs/how-it-works.svg rename to periscope/dependency/docs/how-it-works.svg diff --git a/frontend/.gitignore b/periscope/dependency/frontend/.gitignore similarity index 100% rename from frontend/.gitignore rename to periscope/dependency/frontend/.gitignore diff --git a/frontend/AGENTS.md b/periscope/dependency/frontend/AGENTS.md similarity index 100% rename from frontend/AGENTS.md rename to periscope/dependency/frontend/AGENTS.md diff --git a/frontend/CLAUDE.md b/periscope/dependency/frontend/CLAUDE.md similarity index 100% rename from frontend/CLAUDE.md rename to periscope/dependency/frontend/CLAUDE.md diff --git a/frontend/README.md b/periscope/dependency/frontend/README.md similarity index 100% rename from frontend/README.md rename to periscope/dependency/frontend/README.md diff --git a/frontend/components.json b/periscope/dependency/frontend/components.json similarity index 100% rename from frontend/components.json rename to periscope/dependency/frontend/components.json diff --git a/frontend/content/changelog.md b/periscope/dependency/frontend/content/changelog.md similarity index 98% rename from frontend/content/changelog.md rename to periscope/dependency/frontend/content/changelog.md index 5d85c31..1f444b0 100644 --- a/frontend/content/changelog.md +++ b/periscope/dependency/frontend/content/changelog.md @@ -2,6 +2,14 @@ What's new in Periscope. +## 2.38.0 — 2026-09-20 — Physical split: periscope/src vs periscope/dependency + +Split the checkout so native Periscope and inherited PinScope are separate trees. No finding-engine rewrite. AGPL `LICENSE` stays at the repo root. The GitHub fork is not detached. + +- [Changed] Native modules live under `periscope/src/` (PCB, placement, finding clamp, DeepSeek, local auth, plugin, Dockerfiles). +- [Changed] PinScope-derived modules live under `periscope/dependency/` (graph, parsers, `validate.py`, pipeline, OSS Next shell, skills, taxonomy, `simple_project`). +- [Changed] Docker overlays dependency then src. Local `backend` package merges both trees. Do not delete the inherited tree; Fase C replaces modules incrementally. + ## 2.37.1 — 2026-09-20 — PCB not_reviewed is board ICs, not the shared library Emmaforo showed 20 “components not reviewed” that were other projects’ 1.5–1.7 extracts (LAN8720A, W25Q, …). BOM ICs were already 1.13.0 and *were* reviewed. diff --git a/frontend/content/file-guide.md b/periscope/dependency/frontend/content/file-guide.md similarity index 100% rename from frontend/content/file-guide.md rename to periscope/dependency/frontend/content/file-guide.md diff --git a/frontend/content/privacy.md b/periscope/dependency/frontend/content/privacy.md similarity index 100% rename from frontend/content/privacy.md rename to periscope/dependency/frontend/content/privacy.md diff --git a/frontend/content/terms.md b/periscope/dependency/frontend/content/terms.md similarity index 100% rename from frontend/content/terms.md rename to periscope/dependency/frontend/content/terms.md diff --git a/frontend/eslint.config.mjs b/periscope/dependency/frontend/eslint.config.mjs similarity index 100% rename from frontend/eslint.config.mjs rename to periscope/dependency/frontend/eslint.config.mjs diff --git a/frontend/next.config.ts b/periscope/dependency/frontend/next.config.ts similarity index 100% rename from frontend/next.config.ts rename to periscope/dependency/frontend/next.config.ts diff --git a/frontend/package-lock.json b/periscope/dependency/frontend/package-lock.json similarity index 100% rename from frontend/package-lock.json rename to periscope/dependency/frontend/package-lock.json diff --git a/frontend/package.json b/periscope/dependency/frontend/package.json similarity index 97% rename from frontend/package.json rename to periscope/dependency/frontend/package.json index ba0d3f4..d29c19a 100644 --- a/frontend/package.json +++ b/periscope/dependency/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "2.26.3", + "version": "2.38.0", "private": true, "scripts": { "sync-version": "node scripts/sync-version.mjs", diff --git a/frontend/postcss.config.mjs b/periscope/dependency/frontend/postcss.config.mjs similarity index 100% rename from frontend/postcss.config.mjs rename to periscope/dependency/frontend/postcss.config.mjs diff --git a/frontend/public/datasheet.gif b/periscope/dependency/frontend/public/datasheet.gif similarity index 100% rename from frontend/public/datasheet.gif rename to periscope/dependency/frontend/public/datasheet.gif diff --git a/frontend/public/derating.png b/periscope/dependency/frontend/public/derating.png similarity index 100% rename from frontend/public/derating.png rename to periscope/dependency/frontend/public/derating.png diff --git a/frontend/public/eda-logos/altium.svg b/periscope/dependency/frontend/public/eda-logos/altium.svg similarity index 100% rename from frontend/public/eda-logos/altium.svg rename to periscope/dependency/frontend/public/eda-logos/altium.svg diff --git a/frontend/public/eda-logos/autodesk.svg b/periscope/dependency/frontend/public/eda-logos/autodesk.svg similarity index 100% rename from frontend/public/eda-logos/autodesk.svg rename to periscope/dependency/frontend/public/eda-logos/autodesk.svg diff --git a/frontend/public/eda-logos/cadence.svg b/periscope/dependency/frontend/public/eda-logos/cadence.svg similarity index 100% rename from frontend/public/eda-logos/cadence.svg rename to periscope/dependency/frontend/public/eda-logos/cadence.svg diff --git a/frontend/public/eda-logos/easyeda.svg b/periscope/dependency/frontend/public/eda-logos/easyeda.svg similarity index 100% rename from frontend/public/eda-logos/easyeda.svg rename to periscope/dependency/frontend/public/eda-logos/easyeda.svg diff --git a/frontend/public/eda-logos/kicad.svg b/periscope/dependency/frontend/public/eda-logos/kicad.svg similarity index 100% rename from frontend/public/eda-logos/kicad.svg rename to periscope/dependency/frontend/public/eda-logos/kicad.svg diff --git a/frontend/public/eda-logos/orcad.svg b/periscope/dependency/frontend/public/eda-logos/orcad.svg similarity index 100% rename from frontend/public/eda-logos/orcad.svg rename to periscope/dependency/frontend/public/eda-logos/orcad.svg diff --git a/frontend/public/eda-logos/siemens.svg b/periscope/dependency/frontend/public/eda-logos/siemens.svg similarity index 100% rename from frontend/public/eda-logos/siemens.svg rename to periscope/dependency/frontend/public/eda-logos/siemens.svg diff --git a/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf b/periscope/dependency/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf similarity index 100% rename from frontend/public/examples/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf rename to periscope/dependency/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf diff --git a/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.asc b/periscope/dependency/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.asc similarity index 100% rename from frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.asc rename to periscope/dependency/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.asc diff --git a/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.csv b/periscope/dependency/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.csv similarity index 100% rename from frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.csv rename to periscope/dependency/frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.csv diff --git a/frontend/public/faradworks-logo-white.png b/periscope/dependency/frontend/public/faradworks-logo-white.png similarity index 100% rename from frontend/public/faradworks-logo-white.png rename to periscope/dependency/frontend/public/faradworks-logo-white.png diff --git a/frontend/public/favicon_io/android-chrome-192x192.png b/periscope/dependency/frontend/public/favicon_io/android-chrome-192x192.png similarity index 100% rename from frontend/public/favicon_io/android-chrome-192x192.png rename to periscope/dependency/frontend/public/favicon_io/android-chrome-192x192.png diff --git a/frontend/public/favicon_io/android-chrome-512x512.png b/periscope/dependency/frontend/public/favicon_io/android-chrome-512x512.png similarity index 100% rename from frontend/public/favicon_io/android-chrome-512x512.png rename to periscope/dependency/frontend/public/favicon_io/android-chrome-512x512.png diff --git a/frontend/public/favicon_io/apple-touch-icon.png b/periscope/dependency/frontend/public/favicon_io/apple-touch-icon.png similarity index 100% rename from frontend/public/favicon_io/apple-touch-icon.png rename to periscope/dependency/frontend/public/favicon_io/apple-touch-icon.png diff --git a/frontend/public/favicon_io/favicon-16x16.png b/periscope/dependency/frontend/public/favicon_io/favicon-16x16.png similarity index 100% rename from frontend/public/favicon_io/favicon-16x16.png rename to periscope/dependency/frontend/public/favicon_io/favicon-16x16.png diff --git a/frontend/public/favicon_io/favicon-32x32.png b/periscope/dependency/frontend/public/favicon_io/favicon-32x32.png similarity index 100% rename from frontend/public/favicon_io/favicon-32x32.png rename to periscope/dependency/frontend/public/favicon_io/favicon-32x32.png diff --git a/frontend/public/favicon_io/favicon.ico b/periscope/dependency/frontend/public/favicon_io/favicon.ico similarity index 100% rename from frontend/public/favicon_io/favicon.ico rename to periscope/dependency/frontend/public/favicon_io/favicon.ico diff --git a/frontend/public/favicon_io/site.webmanifest b/periscope/dependency/frontend/public/favicon_io/site.webmanifest similarity index 100% rename from frontend/public/favicon_io/site.webmanifest rename to periscope/dependency/frontend/public/favicon_io/site.webmanifest diff --git a/frontend/public/file.svg b/periscope/dependency/frontend/public/file.svg similarity index 100% rename from frontend/public/file.svg rename to periscope/dependency/frontend/public/file.svg diff --git a/frontend/public/globe.svg b/periscope/dependency/frontend/public/globe.svg similarity index 100% rename from frontend/public/globe.svg rename to periscope/dependency/frontend/public/globe.svg diff --git a/frontend/public/next.svg b/periscope/dependency/frontend/public/next.svg similarity index 100% rename from frontend/public/next.svg rename to periscope/dependency/frontend/public/next.svg diff --git a/frontend/public/power-tree.gif b/periscope/dependency/frontend/public/power-tree.gif similarity index 100% rename from frontend/public/power-tree.gif rename to periscope/dependency/frontend/public/power-tree.gif diff --git a/frontend/public/report.png b/periscope/dependency/frontend/public/report.png similarity index 100% rename from frontend/public/report.png rename to periscope/dependency/frontend/public/report.png diff --git a/frontend/public/vercel.svg b/periscope/dependency/frontend/public/vercel.svg similarity index 100% rename from frontend/public/vercel.svg rename to periscope/dependency/frontend/public/vercel.svg diff --git a/frontend/public/window.svg b/periscope/dependency/frontend/public/window.svg similarity index 100% rename from frontend/public/window.svg rename to periscope/dependency/frontend/public/window.svg diff --git a/frontend/scripts/sync-version.mjs b/periscope/dependency/frontend/scripts/sync-version.mjs similarity index 100% rename from frontend/scripts/sync-version.mjs rename to periscope/dependency/frontend/scripts/sync-version.mjs diff --git a/frontend/src/app/(app)/admin/page.tsx b/periscope/dependency/frontend/src/app/(app)/admin/page.tsx similarity index 100% rename from frontend/src/app/(app)/admin/page.tsx rename to periscope/dependency/frontend/src/app/(app)/admin/page.tsx diff --git a/frontend/src/app/(app)/api/graph/[id]/route.ts b/periscope/dependency/frontend/src/app/(app)/api/graph/[id]/route.ts similarity index 100% rename from frontend/src/app/(app)/api/graph/[id]/route.ts rename to periscope/dependency/frontend/src/app/(app)/api/graph/[id]/route.ts diff --git a/frontend/src/app/(app)/api/projects/route.ts b/periscope/dependency/frontend/src/app/(app)/api/projects/route.ts similarity index 100% rename from frontend/src/app/(app)/api/projects/route.ts rename to periscope/dependency/frontend/src/app/(app)/api/projects/route.ts diff --git a/frontend/src/app/(app)/api/report/[id]/route.ts b/periscope/dependency/frontend/src/app/(app)/api/report/[id]/route.ts similarity index 100% rename from frontend/src/app/(app)/api/report/[id]/route.ts rename to periscope/dependency/frontend/src/app/(app)/api/report/[id]/route.ts diff --git a/frontend/src/app/(app)/dashboard/page.tsx b/periscope/dependency/frontend/src/app/(app)/dashboard/page.tsx similarity index 100% rename from frontend/src/app/(app)/dashboard/page.tsx rename to periscope/dependency/frontend/src/app/(app)/dashboard/page.tsx diff --git a/frontend/src/app/(app)/feedback/page.tsx b/periscope/dependency/frontend/src/app/(app)/feedback/page.tsx similarity index 100% rename from frontend/src/app/(app)/feedback/page.tsx rename to periscope/dependency/frontend/src/app/(app)/feedback/page.tsx diff --git a/frontend/src/app/(app)/layout.tsx b/periscope/dependency/frontend/src/app/(app)/layout.tsx similarity index 100% rename from frontend/src/app/(app)/layout.tsx rename to periscope/dependency/frontend/src/app/(app)/layout.tsx diff --git a/periscope/dependency/frontend/src/app/(app)/library/page.tsx b/periscope/dependency/frontend/src/app/(app)/library/page.tsx new file mode 120000 index 0000000..561e799 --- /dev/null +++ b/periscope/dependency/frontend/src/app/(app)/library/page.tsx @@ -0,0 +1 @@ +../../../../../../src/frontend/src/app/(app)/library/page.tsx \ No newline at end of file diff --git a/frontend/src/app/(app)/project/[id]/page.tsx b/periscope/dependency/frontend/src/app/(app)/project/[id]/page.tsx similarity index 100% rename from frontend/src/app/(app)/project/[id]/page.tsx rename to periscope/dependency/frontend/src/app/(app)/project/[id]/page.tsx diff --git a/periscope/dependency/frontend/src/app/(app)/project/[id]/pcb/page.tsx b/periscope/dependency/frontend/src/app/(app)/project/[id]/pcb/page.tsx new file mode 120000 index 0000000..3728a98 --- /dev/null +++ b/periscope/dependency/frontend/src/app/(app)/project/[id]/pcb/page.tsx @@ -0,0 +1 @@ +../../../../../../../../src/frontend/src/app/(app)/project/[id]/pcb/page.tsx \ No newline at end of file diff --git a/periscope/dependency/frontend/src/app/(app)/project/[id]/placement/page.tsx b/periscope/dependency/frontend/src/app/(app)/project/[id]/placement/page.tsx new file mode 120000 index 0000000..c7f9b95 --- /dev/null +++ b/periscope/dependency/frontend/src/app/(app)/project/[id]/placement/page.tsx @@ -0,0 +1 @@ +../../../../../../../../src/frontend/src/app/(app)/project/[id]/placement/page.tsx \ No newline at end of file diff --git a/frontend/src/app/(app)/project/[id]/progress/page.tsx b/periscope/dependency/frontend/src/app/(app)/project/[id]/progress/page.tsx similarity index 100% rename from frontend/src/app/(app)/project/[id]/progress/page.tsx rename to periscope/dependency/frontend/src/app/(app)/project/[id]/progress/page.tsx diff --git a/frontend/src/app/(app)/project/[id]/report/page.tsx b/periscope/dependency/frontend/src/app/(app)/project/[id]/report/page.tsx similarity index 100% rename from frontend/src/app/(app)/project/[id]/report/page.tsx rename to periscope/dependency/frontend/src/app/(app)/project/[id]/report/page.tsx diff --git a/frontend/src/app/(marketing)/changelog/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/changelog/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/changelog/page.tsx rename to periscope/dependency/frontend/src/app/(marketing)/changelog/page.tsx diff --git a/frontend/src/app/(marketing)/contact/actions.ts b/periscope/dependency/frontend/src/app/(marketing)/contact/actions.ts similarity index 100% rename from frontend/src/app/(marketing)/contact/actions.ts rename to periscope/dependency/frontend/src/app/(marketing)/contact/actions.ts diff --git a/frontend/src/app/(marketing)/contact/contact-form.tsx b/periscope/dependency/frontend/src/app/(marketing)/contact/contact-form.tsx similarity index 100% rename from frontend/src/app/(marketing)/contact/contact-form.tsx rename to periscope/dependency/frontend/src/app/(marketing)/contact/contact-form.tsx diff --git a/frontend/src/app/(marketing)/contact/nav.tsx b/periscope/dependency/frontend/src/app/(marketing)/contact/nav.tsx similarity index 100% rename from frontend/src/app/(marketing)/contact/nav.tsx rename to periscope/dependency/frontend/src/app/(marketing)/contact/nav.tsx diff --git a/frontend/src/app/(marketing)/contact/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/contact/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/contact/page.tsx rename to periscope/dependency/frontend/src/app/(marketing)/contact/page.tsx diff --git a/frontend/src/app/(marketing)/file-guide/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/file-guide/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/file-guide/page.tsx rename to periscope/dependency/frontend/src/app/(marketing)/file-guide/page.tsx diff --git a/frontend/src/app/(marketing)/landing-cta.tsx b/periscope/dependency/frontend/src/app/(marketing)/landing-cta.tsx similarity index 100% rename from frontend/src/app/(marketing)/landing-cta.tsx rename to periscope/dependency/frontend/src/app/(marketing)/landing-cta.tsx diff --git a/frontend/src/app/(marketing)/layout.tsx b/periscope/dependency/frontend/src/app/(marketing)/layout.tsx similarity index 100% rename from frontend/src/app/(marketing)/layout.tsx rename to periscope/dependency/frontend/src/app/(marketing)/layout.tsx diff --git a/frontend/src/app/(marketing)/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/page.tsx rename to periscope/dependency/frontend/src/app/(marketing)/page.tsx diff --git a/frontend/src/app/(marketing)/privacy/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/privacy/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/privacy/page.tsx rename to periscope/dependency/frontend/src/app/(marketing)/privacy/page.tsx diff --git a/periscope/dependency/frontend/src/app/(marketing)/sign-in/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/sign-in/page.tsx new file mode 120000 index 0000000..2333196 --- /dev/null +++ b/periscope/dependency/frontend/src/app/(marketing)/sign-in/page.tsx @@ -0,0 +1 @@ +../../../../../../src/frontend/src/app/(marketing)/sign-in/page.tsx \ No newline at end of file diff --git a/periscope/dependency/frontend/src/app/(marketing)/sign-up/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/sign-up/page.tsx new file mode 120000 index 0000000..5c89b49 --- /dev/null +++ b/periscope/dependency/frontend/src/app/(marketing)/sign-up/page.tsx @@ -0,0 +1 @@ +../../../../../../src/frontend/src/app/(marketing)/sign-up/page.tsx \ No newline at end of file diff --git a/frontend/src/app/(marketing)/terms/page.tsx b/periscope/dependency/frontend/src/app/(marketing)/terms/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/terms/page.tsx rename to periscope/dependency/frontend/src/app/(marketing)/terms/page.tsx diff --git a/frontend/src/app/favicon.ico b/periscope/dependency/frontend/src/app/favicon.ico similarity index 100% rename from frontend/src/app/favicon.ico rename to periscope/dependency/frontend/src/app/favicon.ico diff --git a/frontend/src/app/globals.css b/periscope/dependency/frontend/src/app/globals.css similarity index 100% rename from frontend/src/app/globals.css rename to periscope/dependency/frontend/src/app/globals.css diff --git a/frontend/src/app/layout.tsx b/periscope/dependency/frontend/src/app/layout.tsx similarity index 100% rename from frontend/src/app/layout.tsx rename to periscope/dependency/frontend/src/app/layout.tsx diff --git a/frontend/src/app/opengraph-image.tsx b/periscope/dependency/frontend/src/app/opengraph-image.tsx similarity index 100% rename from frontend/src/app/opengraph-image.tsx rename to periscope/dependency/frontend/src/app/opengraph-image.tsx diff --git a/frontend/src/app/robots.ts b/periscope/dependency/frontend/src/app/robots.ts similarity index 100% rename from frontend/src/app/robots.ts rename to periscope/dependency/frontend/src/app/robots.ts diff --git a/frontend/src/app/sitemap.ts b/periscope/dependency/frontend/src/app/sitemap.ts similarity index 100% rename from frontend/src/app/sitemap.ts rename to periscope/dependency/frontend/src/app/sitemap.ts diff --git a/frontend/src/app/twitter-image.tsx b/periscope/dependency/frontend/src/app/twitter-image.tsx similarity index 100% rename from frontend/src/app/twitter-image.tsx rename to periscope/dependency/frontend/src/app/twitter-image.tsx diff --git a/frontend/src/components/analytics/reddit-pixel-match-keys.tsx b/periscope/dependency/frontend/src/components/analytics/reddit-pixel-match-keys.tsx similarity index 100% rename from frontend/src/components/analytics/reddit-pixel-match-keys.tsx rename to periscope/dependency/frontend/src/components/analytics/reddit-pixel-match-keys.tsx diff --git a/frontend/src/components/analytics/reddit-pixel.tsx b/periscope/dependency/frontend/src/components/analytics/reddit-pixel.tsx similarity index 100% rename from frontend/src/components/analytics/reddit-pixel.tsx rename to periscope/dependency/frontend/src/components/analytics/reddit-pixel.tsx diff --git a/frontend/src/components/billing/credits-context.tsx b/periscope/dependency/frontend/src/components/billing/credits-context.tsx similarity index 100% rename from frontend/src/components/billing/credits-context.tsx rename to periscope/dependency/frontend/src/components/billing/credits-context.tsx diff --git a/frontend/src/components/billing/paused-run-banner.tsx b/periscope/dependency/frontend/src/components/billing/paused-run-banner.tsx similarity index 100% rename from frontend/src/components/billing/paused-run-banner.tsx rename to periscope/dependency/frontend/src/components/billing/paused-run-banner.tsx diff --git a/frontend/src/components/dashboard/create-project-dialog.tsx b/periscope/dependency/frontend/src/components/dashboard/create-project-dialog.tsx similarity index 100% rename from frontend/src/components/dashboard/create-project-dialog.tsx rename to periscope/dependency/frontend/src/components/dashboard/create-project-dialog.tsx diff --git a/frontend/src/components/dashboard/onboarding-survey.tsx b/periscope/dependency/frontend/src/components/dashboard/onboarding-survey.tsx similarity index 100% rename from frontend/src/components/dashboard/onboarding-survey.tsx rename to periscope/dependency/frontend/src/components/dashboard/onboarding-survey.tsx diff --git a/frontend/src/components/dashboard/project-card.tsx b/periscope/dependency/frontend/src/components/dashboard/project-card.tsx similarity index 100% rename from frontend/src/components/dashboard/project-card.tsx rename to periscope/dependency/frontend/src/components/dashboard/project-card.tsx diff --git a/frontend/src/components/dashboard/projects-table.tsx b/periscope/dependency/frontend/src/components/dashboard/projects-table.tsx similarity index 100% rename from frontend/src/components/dashboard/projects-table.tsx rename to periscope/dependency/frontend/src/components/dashboard/projects-table.tsx diff --git a/frontend/src/components/feedback/feedback-dialog.tsx b/periscope/dependency/frontend/src/components/feedback/feedback-dialog.tsx similarity index 100% rename from frontend/src/components/feedback/feedback-dialog.tsx rename to periscope/dependency/frontend/src/components/feedback/feedback-dialog.tsx diff --git a/periscope/dependency/frontend/src/components/layout/auth-gate.tsx b/periscope/dependency/frontend/src/components/layout/auth-gate.tsx new file mode 120000 index 0000000..686abb6 --- /dev/null +++ b/periscope/dependency/frontend/src/components/layout/auth-gate.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/layout/auth-gate.tsx \ No newline at end of file diff --git a/periscope/dependency/frontend/src/components/layout/sidebar-auth.tsx b/periscope/dependency/frontend/src/components/layout/sidebar-auth.tsx new file mode 120000 index 0000000..0d706d5 --- /dev/null +++ b/periscope/dependency/frontend/src/components/layout/sidebar-auth.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/layout/sidebar-auth.tsx \ No newline at end of file diff --git a/frontend/src/components/layout/sidebar.tsx b/periscope/dependency/frontend/src/components/layout/sidebar.tsx similarity index 100% rename from frontend/src/components/layout/sidebar.tsx rename to periscope/dependency/frontend/src/components/layout/sidebar.tsx diff --git a/frontend/src/components/legal/changelog-timeline.tsx b/periscope/dependency/frontend/src/components/legal/changelog-timeline.tsx similarity index 100% rename from frontend/src/components/legal/changelog-timeline.tsx rename to periscope/dependency/frontend/src/components/legal/changelog-timeline.tsx diff --git a/frontend/src/components/legal/file-guide-page.tsx b/periscope/dependency/frontend/src/components/legal/file-guide-page.tsx similarity index 100% rename from frontend/src/components/legal/file-guide-page.tsx rename to periscope/dependency/frontend/src/components/legal/file-guide-page.tsx diff --git a/frontend/src/components/legal/legal-page.tsx b/periscope/dependency/frontend/src/components/legal/legal-page.tsx similarity index 100% rename from frontend/src/components/legal/legal-page.tsx rename to periscope/dependency/frontend/src/components/legal/legal-page.tsx diff --git a/frontend/src/components/marketing/pricing-section.tsx b/periscope/dependency/frontend/src/components/marketing/pricing-section.tsx similarity index 100% rename from frontend/src/components/marketing/pricing-section.tsx rename to periscope/dependency/frontend/src/components/marketing/pricing-section.tsx diff --git a/frontend/src/components/pdf/pdf-viewer-panel.tsx b/periscope/dependency/frontend/src/components/pdf/pdf-viewer-panel.tsx similarity index 100% rename from frontend/src/components/pdf/pdf-viewer-panel.tsx rename to periscope/dependency/frontend/src/components/pdf/pdf-viewer-panel.tsx diff --git a/frontend/src/components/pdf/pdf-viewer-sheet.tsx b/periscope/dependency/frontend/src/components/pdf/pdf-viewer-sheet.tsx similarity index 100% rename from frontend/src/components/pdf/pdf-viewer-sheet.tsx rename to periscope/dependency/frontend/src/components/pdf/pdf-viewer-sheet.tsx diff --git a/frontend/src/components/progress/pipeline-stepper.tsx b/periscope/dependency/frontend/src/components/progress/pipeline-stepper.tsx similarity index 100% rename from frontend/src/components/progress/pipeline-stepper.tsx rename to periscope/dependency/frontend/src/components/progress/pipeline-stepper.tsx diff --git a/periscope/dependency/frontend/src/components/project/impedance-panel.tsx b/periscope/dependency/frontend/src/components/project/impedance-panel.tsx new file mode 120000 index 0000000..93f1817 --- /dev/null +++ b/periscope/dependency/frontend/src/components/project/impedance-panel.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/project/impedance-panel.tsx \ No newline at end of file diff --git a/periscope/dependency/frontend/src/components/project/pcb-upload.tsx b/periscope/dependency/frontend/src/components/project/pcb-upload.tsx new file mode 120000 index 0000000..be4ea02 --- /dev/null +++ b/periscope/dependency/frontend/src/components/project/pcb-upload.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/project/pcb-upload.tsx \ No newline at end of file diff --git a/periscope/dependency/frontend/src/components/project/topology-panel.tsx b/periscope/dependency/frontend/src/components/project/topology-panel.tsx new file mode 120000 index 0000000..57c2a9a --- /dev/null +++ b/periscope/dependency/frontend/src/components/project/topology-panel.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/project/topology-panel.tsx \ No newline at end of file diff --git a/frontend/src/components/report/component-group.tsx b/periscope/dependency/frontend/src/components/report/component-group.tsx similarity index 100% rename from frontend/src/components/report/component-group.tsx rename to periscope/dependency/frontend/src/components/report/component-group.tsx diff --git a/frontend/src/components/report/finding-card.tsx b/periscope/dependency/frontend/src/components/report/finding-card.tsx similarity index 100% rename from frontend/src/components/report/finding-card.tsx rename to periscope/dependency/frontend/src/components/report/finding-card.tsx diff --git a/frontend/src/components/report/finding-comments.tsx b/periscope/dependency/frontend/src/components/report/finding-comments.tsx similarity index 100% rename from frontend/src/components/report/finding-comments.tsx rename to periscope/dependency/frontend/src/components/report/finding-comments.tsx diff --git a/frontend/src/components/report/finding-focus-view.tsx b/periscope/dependency/frontend/src/components/report/finding-focus-view.tsx similarity index 100% rename from frontend/src/components/report/finding-focus-view.tsx rename to periscope/dependency/frontend/src/components/report/finding-focus-view.tsx diff --git a/periscope/dependency/frontend/src/components/report/finding-review-controls.tsx b/periscope/dependency/frontend/src/components/report/finding-review-controls.tsx new file mode 120000 index 0000000..1b91591 --- /dev/null +++ b/periscope/dependency/frontend/src/components/report/finding-review-controls.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/report/finding-review-controls.tsx \ No newline at end of file diff --git a/frontend/src/components/report/findings-list.tsx b/periscope/dependency/frontend/src/components/report/findings-list.tsx similarity index 100% rename from frontend/src/components/report/findings-list.tsx rename to periscope/dependency/frontend/src/components/report/findings-list.tsx diff --git a/periscope/dependency/frontend/src/components/report/findings-tree.tsx b/periscope/dependency/frontend/src/components/report/findings-tree.tsx new file mode 120000 index 0000000..c9e07dd --- /dev/null +++ b/periscope/dependency/frontend/src/components/report/findings-tree.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/report/findings-tree.tsx \ No newline at end of file diff --git a/frontend/src/components/report/mention-input.tsx b/periscope/dependency/frontend/src/components/report/mention-input.tsx similarity index 100% rename from frontend/src/components/report/mention-input.tsx rename to periscope/dependency/frontend/src/components/report/mention-input.tsx diff --git a/periscope/dependency/frontend/src/components/report/pcb-exam-section.tsx b/periscope/dependency/frontend/src/components/report/pcb-exam-section.tsx new file mode 120000 index 0000000..f53da65 --- /dev/null +++ b/periscope/dependency/frontend/src/components/report/pcb-exam-section.tsx @@ -0,0 +1 @@ +../../../../../src/frontend/src/components/report/pcb-exam-section.tsx \ No newline at end of file diff --git a/frontend/src/components/report/report-filters.tsx b/periscope/dependency/frontend/src/components/report/report-filters.tsx similarity index 100% rename from frontend/src/components/report/report-filters.tsx rename to periscope/dependency/frontend/src/components/report/report-filters.tsx diff --git a/frontend/src/components/report/report-summary.tsx b/periscope/dependency/frontend/src/components/report/report-summary.tsx similarity index 100% rename from frontend/src/components/report/report-summary.tsx rename to periscope/dependency/frontend/src/components/report/report-summary.tsx diff --git a/frontend/src/components/report/reviewed-section.tsx b/periscope/dependency/frontend/src/components/report/reviewed-section.tsx similarity index 100% rename from frontend/src/components/report/reviewed-section.tsx rename to periscope/dependency/frontend/src/components/report/reviewed-section.tsx diff --git a/frontend/src/components/report/status-badge.tsx b/periscope/dependency/frontend/src/components/report/status-badge.tsx similarity index 100% rename from frontend/src/components/report/status-badge.tsx rename to periscope/dependency/frontend/src/components/report/status-badge.tsx diff --git a/frontend/src/components/theme/clerk-theme-provider.tsx b/periscope/dependency/frontend/src/components/theme/clerk-theme-provider.tsx similarity index 100% rename from frontend/src/components/theme/clerk-theme-provider.tsx rename to periscope/dependency/frontend/src/components/theme/clerk-theme-provider.tsx diff --git a/frontend/src/components/theme/theme-provider.tsx b/periscope/dependency/frontend/src/components/theme/theme-provider.tsx similarity index 100% rename from frontend/src/components/theme/theme-provider.tsx rename to periscope/dependency/frontend/src/components/theme/theme-provider.tsx diff --git a/frontend/src/components/theme/theme-toggle.tsx b/periscope/dependency/frontend/src/components/theme/theme-toggle.tsx similarity index 100% rename from frontend/src/components/theme/theme-toggle.tsx rename to periscope/dependency/frontend/src/components/theme/theme-toggle.tsx diff --git a/frontend/src/components/ui/alert-dialog.tsx b/periscope/dependency/frontend/src/components/ui/alert-dialog.tsx similarity index 100% rename from frontend/src/components/ui/alert-dialog.tsx rename to periscope/dependency/frontend/src/components/ui/alert-dialog.tsx diff --git a/frontend/src/components/ui/badge.tsx b/periscope/dependency/frontend/src/components/ui/badge.tsx similarity index 100% rename from frontend/src/components/ui/badge.tsx rename to periscope/dependency/frontend/src/components/ui/badge.tsx diff --git a/frontend/src/components/ui/button.tsx b/periscope/dependency/frontend/src/components/ui/button.tsx similarity index 100% rename from frontend/src/components/ui/button.tsx rename to periscope/dependency/frontend/src/components/ui/button.tsx diff --git a/frontend/src/components/ui/card.tsx b/periscope/dependency/frontend/src/components/ui/card.tsx similarity index 100% rename from frontend/src/components/ui/card.tsx rename to periscope/dependency/frontend/src/components/ui/card.tsx diff --git a/frontend/src/components/ui/collapsible.tsx b/periscope/dependency/frontend/src/components/ui/collapsible.tsx similarity index 100% rename from frontend/src/components/ui/collapsible.tsx rename to periscope/dependency/frontend/src/components/ui/collapsible.tsx diff --git a/frontend/src/components/ui/dialog.tsx b/periscope/dependency/frontend/src/components/ui/dialog.tsx similarity index 100% rename from frontend/src/components/ui/dialog.tsx rename to periscope/dependency/frontend/src/components/ui/dialog.tsx diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/periscope/dependency/frontend/src/components/ui/dropdown-menu.tsx similarity index 100% rename from frontend/src/components/ui/dropdown-menu.tsx rename to periscope/dependency/frontend/src/components/ui/dropdown-menu.tsx diff --git a/frontend/src/components/ui/input.tsx b/periscope/dependency/frontend/src/components/ui/input.tsx similarity index 100% rename from frontend/src/components/ui/input.tsx rename to periscope/dependency/frontend/src/components/ui/input.tsx diff --git a/frontend/src/components/ui/label.tsx b/periscope/dependency/frontend/src/components/ui/label.tsx similarity index 100% rename from frontend/src/components/ui/label.tsx rename to periscope/dependency/frontend/src/components/ui/label.tsx diff --git a/frontend/src/components/ui/progress.tsx b/periscope/dependency/frontend/src/components/ui/progress.tsx similarity index 100% rename from frontend/src/components/ui/progress.tsx rename to periscope/dependency/frontend/src/components/ui/progress.tsx diff --git a/frontend/src/components/ui/scroll-area.tsx b/periscope/dependency/frontend/src/components/ui/scroll-area.tsx similarity index 100% rename from frontend/src/components/ui/scroll-area.tsx rename to periscope/dependency/frontend/src/components/ui/scroll-area.tsx diff --git a/frontend/src/components/ui/select.tsx b/periscope/dependency/frontend/src/components/ui/select.tsx similarity index 100% rename from frontend/src/components/ui/select.tsx rename to periscope/dependency/frontend/src/components/ui/select.tsx diff --git a/frontend/src/components/ui/separator.tsx b/periscope/dependency/frontend/src/components/ui/separator.tsx similarity index 100% rename from frontend/src/components/ui/separator.tsx rename to periscope/dependency/frontend/src/components/ui/separator.tsx diff --git a/frontend/src/components/ui/sheet.tsx b/periscope/dependency/frontend/src/components/ui/sheet.tsx similarity index 100% rename from frontend/src/components/ui/sheet.tsx rename to periscope/dependency/frontend/src/components/ui/sheet.tsx diff --git a/frontend/src/components/ui/skeleton.tsx b/periscope/dependency/frontend/src/components/ui/skeleton.tsx similarity index 100% rename from frontend/src/components/ui/skeleton.tsx rename to periscope/dependency/frontend/src/components/ui/skeleton.tsx diff --git a/frontend/src/components/ui/tabs.tsx b/periscope/dependency/frontend/src/components/ui/tabs.tsx similarity index 100% rename from frontend/src/components/ui/tabs.tsx rename to periscope/dependency/frontend/src/components/ui/tabs.tsx diff --git a/frontend/src/components/ui/textarea.tsx b/periscope/dependency/frontend/src/components/ui/textarea.tsx similarity index 100% rename from frontend/src/components/ui/textarea.tsx rename to periscope/dependency/frontend/src/components/ui/textarea.tsx diff --git a/frontend/src/components/ui/toast.tsx b/periscope/dependency/frontend/src/components/ui/toast.tsx similarity index 100% rename from frontend/src/components/ui/toast.tsx rename to periscope/dependency/frontend/src/components/ui/toast.tsx diff --git a/frontend/src/components/ui/tooltip.tsx b/periscope/dependency/frontend/src/components/ui/tooltip.tsx similarity index 100% rename from frontend/src/components/ui/tooltip.tsx rename to periscope/dependency/frontend/src/components/ui/tooltip.tsx diff --git a/frontend/src/components/upload/file-upload-zone.tsx b/periscope/dependency/frontend/src/components/upload/file-upload-zone.tsx similarity index 100% rename from frontend/src/components/upload/file-upload-zone.tsx rename to periscope/dependency/frontend/src/components/upload/file-upload-zone.tsx diff --git a/frontend/src/hooks/use-auth-api.ts b/periscope/dependency/frontend/src/hooks/use-auth-api.ts similarity index 100% rename from frontend/src/hooks/use-auth-api.ts rename to periscope/dependency/frontend/src/hooks/use-auth-api.ts diff --git a/frontend/src/hooks/use-optional-auth.ts b/periscope/dependency/frontend/src/hooks/use-optional-auth.ts similarity index 100% rename from frontend/src/hooks/use-optional-auth.ts rename to periscope/dependency/frontend/src/hooks/use-optional-auth.ts diff --git a/periscope/dependency/frontend/src/hooks/use-pcb-progress.ts b/periscope/dependency/frontend/src/hooks/use-pcb-progress.ts new file mode 120000 index 0000000..2dc9413 --- /dev/null +++ b/periscope/dependency/frontend/src/hooks/use-pcb-progress.ts @@ -0,0 +1 @@ +../../../../src/frontend/src/hooks/use-pcb-progress.ts \ No newline at end of file diff --git a/frontend/src/hooks/use-pipeline-progress.ts b/periscope/dependency/frontend/src/hooks/use-pipeline-progress.ts similarity index 100% rename from frontend/src/hooks/use-pipeline-progress.ts rename to periscope/dependency/frontend/src/hooks/use-pipeline-progress.ts diff --git a/periscope/dependency/frontend/src/hooks/use-placement-progress.ts b/periscope/dependency/frontend/src/hooks/use-placement-progress.ts new file mode 120000 index 0000000..074552e --- /dev/null +++ b/periscope/dependency/frontend/src/hooks/use-placement-progress.ts @@ -0,0 +1 @@ +../../../../src/frontend/src/hooks/use-placement-progress.ts \ No newline at end of file diff --git a/frontend/src/hooks/use-report.ts b/periscope/dependency/frontend/src/hooks/use-report.ts similarity index 100% rename from frontend/src/hooks/use-report.ts rename to periscope/dependency/frontend/src/hooks/use-report.ts diff --git a/frontend/src/hooks/use-reviewed-count.ts b/periscope/dependency/frontend/src/hooks/use-reviewed-count.ts similarity index 100% rename from frontend/src/hooks/use-reviewed-count.ts rename to periscope/dependency/frontend/src/hooks/use-reviewed-count.ts diff --git a/frontend/src/hooks/use-reviewed-findings.ts b/periscope/dependency/frontend/src/hooks/use-reviewed-findings.ts similarity index 100% rename from frontend/src/hooks/use-reviewed-findings.ts rename to periscope/dependency/frontend/src/hooks/use-reviewed-findings.ts diff --git a/frontend/src/lib/api.ts b/periscope/dependency/frontend/src/lib/api.ts similarity index 100% rename from frontend/src/lib/api.ts rename to periscope/dependency/frontend/src/lib/api.ts diff --git a/periscope/dependency/frontend/src/lib/auth.ts b/periscope/dependency/frontend/src/lib/auth.ts new file mode 120000 index 0000000..a4acb28 --- /dev/null +++ b/periscope/dependency/frontend/src/lib/auth.ts @@ -0,0 +1 @@ +../../../../src/frontend/src/lib/auth.ts \ No newline at end of file diff --git a/frontend/src/lib/csp-hosts.ts b/periscope/dependency/frontend/src/lib/csp-hosts.ts similarity index 100% rename from frontend/src/lib/csp-hosts.ts rename to periscope/dependency/frontend/src/lib/csp-hosts.ts diff --git a/periscope/dependency/frontend/src/lib/layout-finding.ts b/periscope/dependency/frontend/src/lib/layout-finding.ts new file mode 120000 index 0000000..bca9f2f --- /dev/null +++ b/periscope/dependency/frontend/src/lib/layout-finding.ts @@ -0,0 +1 @@ +../../../../src/frontend/src/lib/layout-finding.ts \ No newline at end of file diff --git a/frontend/src/lib/mock-data.ts b/periscope/dependency/frontend/src/lib/mock-data.ts similarity index 100% rename from frontend/src/lib/mock-data.ts rename to periscope/dependency/frontend/src/lib/mock-data.ts diff --git a/periscope/dependency/frontend/src/lib/pinscope-compat.ts b/periscope/dependency/frontend/src/lib/pinscope-compat.ts new file mode 120000 index 0000000..a45829d --- /dev/null +++ b/periscope/dependency/frontend/src/lib/pinscope-compat.ts @@ -0,0 +1 @@ +../../../../src/frontend/src/lib/pinscope-compat.ts \ No newline at end of file diff --git a/frontend/src/lib/report-export.ts b/periscope/dependency/frontend/src/lib/report-export.ts similarity index 100% rename from frontend/src/lib/report-export.ts rename to periscope/dependency/frontend/src/lib/report-export.ts diff --git a/frontend/src/lib/site.ts b/periscope/dependency/frontend/src/lib/site.ts similarity index 100% rename from frontend/src/lib/site.ts rename to periscope/dependency/frontend/src/lib/site.ts diff --git a/periscope/dependency/frontend/src/lib/storage-keys.ts b/periscope/dependency/frontend/src/lib/storage-keys.ts new file mode 120000 index 0000000..ba6cceb --- /dev/null +++ b/periscope/dependency/frontend/src/lib/storage-keys.ts @@ -0,0 +1 @@ +../../../../src/frontend/src/lib/storage-keys.ts \ No newline at end of file diff --git a/frontend/src/lib/types.ts b/periscope/dependency/frontend/src/lib/types.ts similarity index 100% rename from frontend/src/lib/types.ts rename to periscope/dependency/frontend/src/lib/types.ts diff --git a/frontend/src/lib/utils.ts b/periscope/dependency/frontend/src/lib/utils.ts similarity index 100% rename from frontend/src/lib/utils.ts rename to periscope/dependency/frontend/src/lib/utils.ts diff --git a/frontend/src/lib/version.ts b/periscope/dependency/frontend/src/lib/version.ts similarity index 64% rename from frontend/src/lib/version.ts rename to periscope/dependency/frontend/src/lib/version.ts index 57f543c..0d85ddc 100644 --- a/frontend/src/lib/version.ts +++ b/periscope/dependency/frontend/src/lib/version.ts @@ -1,4 +1,4 @@ // AUTO-GENERATED by scripts/sync-version.mjs from content/changelog.md. // Do not edit by hand — change the top "## X.Y.Z" heading in the changelog. -export const APP_VERSION = "2.26.3"; -export const APP_VERSION_DATE = "2026-09-11"; +export const APP_VERSION = "2.38.0"; +export const APP_VERSION_DATE = "2026-09-20"; diff --git a/frontend/src/proxy.ts b/periscope/dependency/frontend/src/proxy.ts similarity index 100% rename from frontend/src/proxy.ts rename to periscope/dependency/frontend/src/proxy.ts diff --git a/frontend/tsconfig.json b/periscope/dependency/frontend/tsconfig.json similarity index 100% rename from frontend/tsconfig.json rename to periscope/dependency/frontend/tsconfig.json diff --git a/scripts/backfill_passive_subtype.py b/periscope/dependency/scripts/backfill_passive_subtype.py similarity index 100% rename from scripts/backfill_passive_subtype.py rename to periscope/dependency/scripts/backfill_passive_subtype.py diff --git a/scripts/clear_rules_from_extractions.py b/periscope/dependency/scripts/clear_rules_from_extractions.py similarity index 100% rename from scripts/clear_rules_from_extractions.py rename to periscope/dependency/scripts/clear_rules_from_extractions.py diff --git a/scripts/dedup_library_datasheets.py b/periscope/dependency/scripts/dedup_library_datasheets.py similarity index 100% rename from scripts/dedup_library_datasheets.py rename to periscope/dependency/scripts/dedup_library_datasheets.py diff --git a/scripts/gc_orphan_blobs.py b/periscope/dependency/scripts/gc_orphan_blobs.py similarity index 100% rename from scripts/gc_orphan_blobs.py rename to periscope/dependency/scripts/gc_orphan_blobs.py diff --git a/scripts/migrate_datasheets_to_blobs.py b/periscope/dependency/scripts/migrate_datasheets_to_blobs.py similarity index 100% rename from scripts/migrate_datasheets_to_blobs.py rename to periscope/dependency/scripts/migrate_datasheets_to_blobs.py diff --git a/scripts/migrate_datasheets_to_library.py b/periscope/dependency/scripts/migrate_datasheets_to_library.py similarity index 100% rename from scripts/migrate_datasheets_to_library.py rename to periscope/dependency/scripts/migrate_datasheets_to_library.py diff --git a/scripts/upload_skills.py b/periscope/dependency/scripts/upload_skills.py similarity index 100% rename from scripts/upload_skills.py rename to periscope/dependency/scripts/upload_skills.py diff --git a/simple_project/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf b/periscope/dependency/simple_project/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf similarity index 100% rename from simple_project/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf rename to periscope/dependency/simple_project/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf diff --git a/simple_project/TI-MSP-KICAD9-TUTORIAL.asc b/periscope/dependency/simple_project/TI-MSP-KICAD9-TUTORIAL.asc similarity index 100% rename from simple_project/TI-MSP-KICAD9-TUTORIAL.asc rename to periscope/dependency/simple_project/TI-MSP-KICAD9-TUTORIAL.asc diff --git a/simple_project/TI-MSP-KICAD9-TUTORIAL.csv b/periscope/dependency/simple_project/TI-MSP-KICAD9-TUTORIAL.csv similarity index 100% rename from simple_project/TI-MSP-KICAD9-TUTORIAL.csv rename to periscope/dependency/simple_project/TI-MSP-KICAD9-TUTORIAL.csv diff --git a/simple_project/design_graph.json b/periscope/dependency/simple_project/design_graph.json similarity index 100% rename from simple_project/design_graph.json rename to periscope/dependency/simple_project/design_graph.json diff --git a/simple_project/eval_golden.json b/periscope/dependency/simple_project/eval_golden.json similarity index 100% rename from simple_project/eval_golden.json rename to periscope/dependency/simple_project/eval_golden.json diff --git a/simple_project/smoke_baseline.json b/periscope/dependency/simple_project/smoke_baseline.json similarity index 100% rename from simple_project/smoke_baseline.json rename to periscope/dependency/simple_project/smoke_baseline.json diff --git a/skills/extract-pattern/SKILL.md b/periscope/dependency/skills/extract-pattern/SKILL.md similarity index 100% rename from skills/extract-pattern/SKILL.md rename to periscope/dependency/skills/extract-pattern/SKILL.md diff --git a/skills/extract-pattern/schema.json b/periscope/dependency/skills/extract-pattern/schema.json similarity index 100% rename from skills/extract-pattern/schema.json rename to periscope/dependency/skills/extract-pattern/schema.json diff --git a/skills/extract-pattern/validate.py b/periscope/dependency/skills/extract-pattern/validate.py similarity index 100% rename from skills/extract-pattern/validate.py rename to periscope/dependency/skills/extract-pattern/validate.py diff --git a/skills/extract-pintable/SKILL.md b/periscope/dependency/skills/extract-pintable/SKILL.md similarity index 100% rename from skills/extract-pintable/SKILL.md rename to periscope/dependency/skills/extract-pintable/SKILL.md diff --git a/skills/extract-pintable/schema.json b/periscope/dependency/skills/extract-pintable/schema.json similarity index 100% rename from skills/extract-pintable/schema.json rename to periscope/dependency/skills/extract-pintable/schema.json diff --git a/skills/extract-pintable/validate.py b/periscope/dependency/skills/extract-pintable/validate.py similarity index 100% rename from skills/extract-pintable/validate.py rename to periscope/dependency/skills/extract-pintable/validate.py diff --git a/skills/extract-specs/SKILL.md b/periscope/dependency/skills/extract-specs/SKILL.md similarity index 100% rename from skills/extract-specs/SKILL.md rename to periscope/dependency/skills/extract-specs/SKILL.md diff --git a/skills/extract-specs/schema.json b/periscope/dependency/skills/extract-specs/schema.json similarity index 100% rename from skills/extract-specs/schema.json rename to periscope/dependency/skills/extract-specs/schema.json diff --git a/skills/extract-specs/validate.py b/periscope/dependency/skills/extract-specs/validate.py similarity index 100% rename from skills/extract-specs/validate.py rename to periscope/dependency/skills/extract-specs/validate.py diff --git a/taxonomy/connector.json b/periscope/dependency/taxonomy/connector.json similarity index 100% rename from taxonomy/connector.json rename to periscope/dependency/taxonomy/connector.json diff --git a/taxonomy/crystal.json b/periscope/dependency/taxonomy/crystal.json similarity index 100% rename from taxonomy/crystal.json rename to periscope/dependency/taxonomy/crystal.json diff --git a/taxonomy/discrete.json b/periscope/dependency/taxonomy/discrete.json similarity index 100% rename from taxonomy/discrete.json rename to periscope/dependency/taxonomy/discrete.json diff --git a/taxonomy/fuse.json b/periscope/dependency/taxonomy/fuse.json similarity index 100% rename from taxonomy/fuse.json rename to periscope/dependency/taxonomy/fuse.json diff --git a/taxonomy/ic.json b/periscope/dependency/taxonomy/ic.json similarity index 100% rename from taxonomy/ic.json rename to periscope/dependency/taxonomy/ic.json diff --git a/taxonomy/passive.json b/periscope/dependency/taxonomy/passive.json similarity index 100% rename from taxonomy/passive.json rename to periscope/dependency/taxonomy/passive.json diff --git a/taxonomy/switch.json b/periscope/dependency/taxonomy/switch.json similarity index 100% rename from taxonomy/switch.json rename to periscope/dependency/taxonomy/switch.json diff --git a/taxonomy/test_point.json b/periscope/dependency/taxonomy/test_point.json similarity index 100% rename from taxonomy/test_point.json rename to periscope/dependency/taxonomy/test_point.json diff --git a/taxonomy/transformer.json b/periscope/dependency/taxonomy/transformer.json similarity index 100% rename from taxonomy/transformer.json rename to periscope/dependency/taxonomy/transformer.json diff --git a/periscope/src/backend/Dockerfile b/periscope/src/backend/Dockerfile new file mode 100644 index 0000000..c3e9ec3 --- /dev/null +++ b/periscope/src/backend/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.12-slim +RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Overlay: PinScope inherited tree, then native Periscope modules. +COPY periscope/dependency/backend/requirements*.txt /app/backend/ +RUN pip install --no-cache-dir -r /app/backend/requirements.txt \ + && if [ -f /app/backend/requirements-gateway.txt ]; then \ + pip install --no-cache-dir -r /app/backend/requirements-gateway.txt; \ + fi + +COPY periscope/dependency/backend/ /app/backend/ +COPY periscope/src/backend/ /app/backend/ + +COPY periscope/dependency/taxonomy/ /app/taxonomy/ +COPY periscope/dependency/skills/ /app/skills/ +COPY periscope/dependency/frontend/content/changelog.md /app/changelog.md +COPY vendor/ /app/vendor/ + +EXPOSE 8080 + +CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8080"] diff --git a/periscope/src/backend/__init__.py b/periscope/src/backend/__init__.py new file mode 100644 index 0000000..ee69a73 --- /dev/null +++ b/periscope/src/backend/__init__.py @@ -0,0 +1,4 @@ +"""Native Periscope backend modules.""" +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/_version.py b/periscope/src/backend/_version.py similarity index 83% rename from backend/_version.py rename to periscope/src/backend/_version.py index 9a24590..6676720 100644 --- a/backend/_version.py +++ b/periscope/src/backend/_version.py @@ -12,11 +12,9 @@ from pathlib import Path def _candidate_paths() -> list[Path]: - here = Path(__file__).resolve() - return [ - Path("/app/changelog.md"), - here.parent.parent / "frontend" / "content" / "changelog.md", - ] + from backend.repo_paths import changelog_paths + + return changelog_paths() _VERSION_RE = re.compile(r"^##\s+(\d+\.\d+\.\d+)\b", re.MULTILINE) diff --git a/periscope/src/backend/periscopex/__init__.py b/periscope/src/backend/periscopex/__init__.py new file mode 100644 index 0000000..c503d2f --- /dev/null +++ b/periscope/src/backend/periscopex/__init__.py @@ -0,0 +1,4 @@ +"""Native Periscope core modules (finding engine, PCB, checks).""" +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/periscopex/antenna_geometry.py b/periscope/src/backend/periscopex/antenna_geometry.py similarity index 100% rename from backend/periscopex/antenna_geometry.py rename to periscope/src/backend/periscopex/antenna_geometry.py diff --git a/backend/periscopex/antenna_rf.py b/periscope/src/backend/periscopex/antenna_rf.py similarity index 100% rename from backend/periscopex/antenna_rf.py rename to periscope/src/backend/periscopex/antenna_rf.py diff --git a/backend/periscopex/bom_match_check.py b/periscope/src/backend/periscopex/bom_match_check.py similarity index 100% rename from backend/periscopex/bom_match_check.py rename to periscope/src/backend/periscopex/bom_match_check.py diff --git a/backend/periscopex/bom_pcb_check.py b/periscope/src/backend/periscopex/bom_pcb_check.py similarity index 100% rename from backend/periscopex/bom_pcb_check.py rename to periscope/src/backend/periscopex/bom_pcb_check.py diff --git a/backend/periscopex/cad_bridge.py b/periscope/src/backend/periscopex/cad_bridge.py similarity index 100% rename from backend/periscopex/cad_bridge.py rename to periscope/src/backend/periscopex/cad_bridge.py diff --git a/backend/periscopex/crystal_cl_check.py b/periscope/src/backend/periscopex/crystal_cl_check.py similarity index 100% rename from backend/periscopex/crystal_cl_check.py rename to periscope/src/backend/periscopex/crystal_cl_check.py diff --git a/backend/periscopex/dnp_check.py b/periscope/src/backend/periscopex/dnp_check.py similarity index 100% rename from backend/periscopex/dnp_check.py rename to periscope/src/backend/periscopex/dnp_check.py diff --git a/backend/periscopex/emi_check.py b/periscope/src/backend/periscopex/emi_check.py similarity index 100% rename from backend/periscopex/emi_check.py rename to periscope/src/backend/periscopex/emi_check.py diff --git a/backend/periscopex/errata_check.py b/periscope/src/backend/periscopex/errata_check.py similarity index 100% rename from backend/periscopex/errata_check.py rename to periscope/src/backend/periscopex/errata_check.py diff --git a/backend/periscopex/esd_return_check.py b/periscope/src/backend/periscopex/esd_return_check.py similarity index 100% rename from backend/periscopex/esd_return_check.py rename to periscope/src/backend/periscopex/esd_return_check.py diff --git a/backend/periscopex/eval_report.py b/periscope/src/backend/periscopex/eval_report.py similarity index 100% rename from backend/periscopex/eval_report.py rename to periscope/src/backend/periscopex/eval_report.py diff --git a/backend/periscopex/filter_check.py b/periscope/src/backend/periscopex/filter_check.py similarity index 100% rename from backend/periscopex/filter_check.py rename to periscope/src/backend/periscopex/filter_check.py diff --git a/backend/periscopex/finding_engine.py b/periscope/src/backend/periscopex/finding_engine.py similarity index 100% rename from backend/periscopex/finding_engine.py rename to periscope/src/backend/periscopex/finding_engine.py diff --git a/backend/periscopex/functional_groups.py b/periscope/src/backend/periscopex/functional_groups.py similarity index 100% rename from backend/periscopex/functional_groups.py rename to periscope/src/backend/periscopex/functional_groups.py diff --git a/backend/periscopex/hf_coverage_check.py b/periscope/src/backend/periscopex/hf_coverage_check.py similarity index 100% rename from backend/periscopex/hf_coverage_check.py rename to periscope/src/backend/periscopex/hf_coverage_check.py diff --git a/backend/periscopex/hierarchy.py b/periscope/src/backend/periscopex/hierarchy.py similarity index 100% rename from backend/periscopex/hierarchy.py rename to periscope/src/backend/periscopex/hierarchy.py diff --git a/backend/periscopex/impedance.py b/periscope/src/backend/periscopex/impedance.py similarity index 100% rename from backend/periscopex/impedance.py rename to periscope/src/backend/periscopex/impedance.py diff --git a/backend/periscopex/impedance_traces.py b/periscope/src/backend/periscopex/impedance_traces.py similarity index 100% rename from backend/periscopex/impedance_traces.py rename to periscope/src/backend/periscopex/impedance_traces.py diff --git a/backend/periscopex/internal_features_check.py b/periscope/src/backend/periscopex/internal_features_check.py similarity index 100% rename from backend/periscopex/internal_features_check.py rename to periscope/src/backend/periscopex/internal_features_check.py diff --git a/backend/periscopex/layout_rules.py b/periscope/src/backend/periscopex/layout_rules.py similarity index 100% rename from backend/periscopex/layout_rules.py rename to periscope/src/backend/periscopex/layout_rules.py diff --git a/backend/periscopex/library_gate.py b/periscope/src/backend/periscopex/library_gate.py similarity index 100% rename from backend/periscopex/library_gate.py rename to periscope/src/backend/periscopex/library_gate.py diff --git a/backend/periscopex/lifecycle.py b/periscope/src/backend/periscopex/lifecycle.py similarity index 100% rename from backend/periscopex/lifecycle.py rename to periscope/src/backend/periscopex/lifecycle.py diff --git a/backend/periscopex/nc_pin_check.py b/periscope/src/backend/periscopex/nc_pin_check.py similarity index 100% rename from backend/periscopex/nc_pin_check.py rename to periscope/src/backend/periscopex/nc_pin_check.py diff --git a/backend/periscopex/netlist_bundle.py b/periscope/src/backend/periscopex/netlist_bundle.py similarity index 100% rename from backend/periscopex/netlist_bundle.py rename to periscope/src/backend/periscopex/netlist_bundle.py diff --git a/backend/periscopex/parsers_kicad.py b/periscope/src/backend/periscopex/parsers_kicad.py similarity index 100% rename from backend/periscopex/parsers_kicad.py rename to periscope/src/backend/periscopex/parsers_kicad.py diff --git a/backend/periscopex/parsers_kicad_pcb.py b/periscope/src/backend/periscopex/parsers_kicad_pcb.py similarity index 100% rename from backend/periscopex/parsers_kicad_pcb.py rename to periscope/src/backend/periscopex/parsers_kicad_pcb.py diff --git a/backend/periscopex/passive_rail_check.py b/periscope/src/backend/periscopex/passive_rail_check.py similarity index 100% rename from backend/periscopex/passive_rail_check.py rename to periscope/src/backend/periscopex/passive_rail_check.py diff --git a/backend/periscopex/pcb_checks.py b/periscope/src/backend/periscopex/pcb_checks.py similarity index 100% rename from backend/periscopex/pcb_checks.py rename to periscope/src/backend/periscopex/pcb_checks.py diff --git a/backend/periscopex/pcb_inventory.py b/periscope/src/backend/periscopex/pcb_inventory.py similarity index 100% rename from backend/periscopex/pcb_inventory.py rename to periscope/src/backend/periscopex/pcb_inventory.py diff --git a/backend/periscopex/pcb_net_match.py b/periscope/src/backend/periscopex/pcb_net_match.py similarity index 100% rename from backend/periscopex/pcb_net_match.py rename to periscope/src/backend/periscopex/pcb_net_match.py diff --git a/backend/periscopex/pcb_power_thermal.py b/periscope/src/backend/periscopex/pcb_power_thermal.py similarity index 100% rename from backend/periscopex/pcb_power_thermal.py rename to periscope/src/backend/periscopex/pcb_power_thermal.py diff --git a/backend/periscopex/pcb_review.py b/periscope/src/backend/periscopex/pcb_review.py similarity index 100% rename from backend/periscopex/pcb_review.py rename to periscope/src/backend/periscopex/pcb_review.py diff --git a/backend/periscopex/pdf_text.py b/periscope/src/backend/periscopex/pdf_text.py similarity index 100% rename from backend/periscopex/pdf_text.py rename to periscope/src/backend/periscopex/pdf_text.py diff --git a/backend/periscopex/pi_check.py b/periscope/src/backend/periscopex/pi_check.py similarity index 100% rename from backend/periscopex/pi_check.py rename to periscope/src/backend/periscopex/pi_check.py diff --git a/backend/periscopex/placement_check.py b/periscope/src/backend/periscopex/placement_check.py similarity index 100% rename from backend/periscopex/placement_check.py rename to periscope/src/backend/periscopex/placement_check.py diff --git a/backend/periscopex/placement_pack.py b/periscope/src/backend/periscopex/placement_pack.py similarity index 100% rename from backend/periscopex/placement_pack.py rename to periscope/src/backend/periscopex/placement_pack.py diff --git a/backend/periscopex/power_margin_check.py b/periscope/src/backend/periscopex/power_margin_check.py similarity index 100% rename from backend/periscopex/power_margin_check.py rename to periscope/src/backend/periscopex/power_margin_check.py diff --git a/backend/periscopex/quote_verify.py b/periscope/src/backend/periscopex/quote_verify.py similarity index 100% rename from backend/periscopex/quote_verify.py rename to periscope/src/backend/periscopex/quote_verify.py diff --git a/backend/periscopex/review_fingerprint.py b/periscope/src/backend/periscopex/review_fingerprint.py similarity index 100% rename from backend/periscopex/review_fingerprint.py rename to periscope/src/backend/periscopex/review_fingerprint.py diff --git a/backend/periscopex/review_workflow.py b/periscope/src/backend/periscopex/review_workflow.py similarity index 100% rename from backend/periscopex/review_workflow.py rename to periscope/src/backend/periscopex/review_workflow.py diff --git a/backend/periscopex/sequencing_check.py b/periscope/src/backend/periscopex/sequencing_check.py similarity index 100% rename from backend/periscopex/sequencing_check.py rename to periscope/src/backend/periscopex/sequencing_check.py diff --git a/backend/periscopex/si_check.py b/periscope/src/backend/periscopex/si_check.py similarity index 100% rename from backend/periscopex/si_check.py rename to periscope/src/backend/periscopex/si_check.py diff --git a/backend/periscopex/spof_check.py b/periscope/src/backend/periscopex/spof_check.py similarity index 100% rename from backend/periscopex/spof_check.py rename to periscope/src/backend/periscopex/spof_check.py diff --git a/backend/periscopex/thermal_check.py b/periscope/src/backend/periscopex/thermal_check.py similarity index 100% rename from backend/periscopex/thermal_check.py rename to periscope/src/backend/periscopex/thermal_check.py diff --git a/backend/periscopex/timing_check.py b/periscope/src/backend/periscopex/timing_check.py similarity index 100% rename from backend/periscopex/timing_check.py rename to periscope/src/backend/periscopex/timing_check.py diff --git a/backend/pinscope_compat.py b/periscope/src/backend/pinscope_compat.py similarity index 100% rename from backend/pinscope_compat.py rename to periscope/src/backend/pinscope_compat.py diff --git a/periscope/src/backend/repo_paths.py b/periscope/src/backend/repo_paths.py new file mode 100644 index 0000000..34da1d0 --- /dev/null +++ b/periscope/src/backend/repo_paths.py @@ -0,0 +1,89 @@ +"""Locate the git root vs the Docker merged image layout. + +Physical split: + periscope/src native Periscope + periscope/dependency inherited PinScope (in-tree dependency) + +Docker overlays both trees into /app/backend, /app/taxonomy, /app/skills. +""" +from __future__ import annotations + +from functools import lru_cache +from pathlib import Path + + +@lru_cache(maxsize=1) +def repo_root() -> Path: + here = Path(__file__).resolve() + for p in [here, *here.parents]: + if (p / "LICENSE").is_file() and (p / "periscope" / "dependency").is_dir(): + return p + if ( + (p / "backend").is_dir() + and (p / "taxonomy").is_dir() + and (p / "skills").is_dir() + and not (p / "periscope" / "src").is_dir() + ): + return p + return here.parents[3] if len(here.parents) >= 4 else here.parent.parent + + +def _split() -> bool: + return (repo_root() / "periscope" / "dependency").is_dir() + + +def dependency_root() -> Path: + root = repo_root() + if _split(): + return root / "periscope" / "dependency" + return root + + +def src_root() -> Path: + root = repo_root() + if _split(): + return root / "periscope" / "src" + return root + + +def taxonomy_dir() -> Path: + return dependency_root() / "taxonomy" + + +def skills_dir() -> Path: + return dependency_root() / "skills" + + +def simple_project_dir() -> Path: + return dependency_root() / "simple_project" + + +def data_dir() -> Path: + return repo_root() / "data" + + +def vendor_dir() -> Path: + return repo_root() / "vendor" + + +def changelog_paths() -> list[Path]: + dep = dependency_root() + src = src_root() + return [ + Path("/app/changelog.md"), + dep / "frontend" / "content" / "changelog.md", + src / "frontend" / "content" / "changelog.md", + repo_root() / "frontend" / "content" / "changelog.md", + ] + + +def env_file() -> Path: + root = repo_root() + for candidate in ( + root / ".env", + dependency_root() / "backend" / ".env", + src_root() / "backend" / ".env", + ): + if candidate.is_file(): + return candidate + return root / ".env" diff --git a/periscope/src/backend/routers/__init__.py b/periscope/src/backend/routers/__init__.py new file mode 100644 index 0000000..b36383a --- /dev/null +++ b/periscope/src/backend/routers/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/routers/auth.py b/periscope/src/backend/routers/auth.py similarity index 100% rename from backend/routers/auth.py rename to periscope/src/backend/routers/auth.py diff --git a/backend/routers/impedance.py b/periscope/src/backend/routers/impedance.py similarity index 100% rename from backend/routers/impedance.py rename to periscope/src/backend/routers/impedance.py diff --git a/periscope/src/backend/services/__init__.py b/periscope/src/backend/services/__init__.py new file mode 100644 index 0000000..b36383a --- /dev/null +++ b/periscope/src/backend/services/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/backend/services/datasheet_finder.py b/periscope/src/backend/services/datasheet_finder.py similarity index 100% rename from backend/services/datasheet_finder.py rename to periscope/src/backend/services/datasheet_finder.py diff --git a/periscope/src/backend/services/llm/__init__.py b/periscope/src/backend/services/llm/__init__.py new file mode 100644 index 0000000..fb5cea5 --- /dev/null +++ b/periscope/src/backend/services/llm/__init__.py @@ -0,0 +1,33 @@ +"""Native LLM pieces (DeepSeek, local skills, PDF ingest) merged with PinScope providers.""" +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) + +from backend.services.llm.factory import call_with_fallback, get_provider +from backend.services.llm.types import ( + Completion, + ContentBlock, + Message, + PdfBlock, + TextBlock, + ToolCall, + ToolChoice, + ToolResultBlock, + ToolSchema, + Usage, +) + +__all__ = [ + "Completion", + "ContentBlock", + "Message", + "PdfBlock", + "TextBlock", + "ToolCall", + "ToolChoice", + "ToolResultBlock", + "ToolSchema", + "Usage", + "call_with_fallback", + "get_provider", +] diff --git a/backend/services/llm/deepseek_provider.py b/periscope/src/backend/services/llm/deepseek_provider.py similarity index 100% rename from backend/services/llm/deepseek_provider.py rename to periscope/src/backend/services/llm/deepseek_provider.py diff --git a/backend/services/llm/local_skill.py b/periscope/src/backend/services/llm/local_skill.py similarity index 100% rename from backend/services/llm/local_skill.py rename to periscope/src/backend/services/llm/local_skill.py diff --git a/backend/services/llm/pdf_ingest.py b/periscope/src/backend/services/llm/pdf_ingest.py similarity index 100% rename from backend/services/llm/pdf_ingest.py rename to periscope/src/backend/services/llm/pdf_ingest.py diff --git a/backend/services/local_jwt.py b/periscope/src/backend/services/local_jwt.py similarity index 100% rename from backend/services/local_jwt.py rename to periscope/src/backend/services/local_jwt.py diff --git a/backend/services/local_users.py b/periscope/src/backend/services/local_users.py similarity index 100% rename from backend/services/local_users.py rename to periscope/src/backend/services/local_users.py diff --git a/backend/services/mouser.py b/periscope/src/backend/services/mouser.py similarity index 100% rename from backend/services/mouser.py rename to periscope/src/backend/services/mouser.py diff --git a/backend/services/passive_from_distributor.py b/periscope/src/backend/services/passive_from_distributor.py similarity index 100% rename from backend/services/passive_from_distributor.py rename to periscope/src/backend/services/passive_from_distributor.py diff --git a/backend/services/passive_from_mpn.py b/periscope/src/backend/services/passive_from_mpn.py similarity index 100% rename from backend/services/passive_from_mpn.py rename to periscope/src/backend/services/passive_from_mpn.py diff --git a/backend/services/passive_from_value.py b/periscope/src/backend/services/passive_from_value.py similarity index 100% rename from backend/services/passive_from_value.py rename to periscope/src/backend/services/passive_from_value.py diff --git a/backend/services/pcb_pipeline.py b/periscope/src/backend/services/pcb_pipeline.py similarity index 100% rename from backend/services/pcb_pipeline.py rename to periscope/src/backend/services/pcb_pipeline.py diff --git a/backend/services/pcb_validation.py b/periscope/src/backend/services/pcb_validation.py similarity index 100% rename from backend/services/pcb_validation.py rename to periscope/src/backend/services/pcb_validation.py diff --git a/backend/services/placement_pipeline.py b/periscope/src/backend/services/placement_pipeline.py similarity index 100% rename from backend/services/placement_pipeline.py rename to periscope/src/backend/services/placement_pipeline.py diff --git a/backend/services/user_directory.py b/periscope/src/backend/services/user_directory.py similarity index 100% rename from backend/services/user_directory.py rename to periscope/src/backend/services/user_directory.py diff --git a/backend/vendor_path.py b/periscope/src/backend/vendor_path.py similarity index 74% rename from backend/vendor_path.py rename to periscope/src/backend/vendor_path.py index dc31989..b3ebfef 100644 --- a/backend/vendor_path.py +++ b/periscope/src/backend/vendor_path.py @@ -3,9 +3,10 @@ from __future__ import annotations import sys -from pathlib import Path -VENDOR_DIR = Path(__file__).resolve().parents[1] / "vendor" +from backend.repo_paths import vendor_dir + +VENDOR_DIR = vendor_dir() def ensure_impedancefinder() -> None: diff --git a/docs/development/PINSCOPE_AUDIT.md b/periscope/src/docs/development/PINSCOPE_AUDIT.md similarity index 100% rename from docs/development/PINSCOPE_AUDIT.md rename to periscope/src/docs/development/PINSCOPE_AUDIT.md diff --git a/periscope/src/docs/development/PINSCOPE_FENCE.md b/periscope/src/docs/development/PINSCOPE_FENCE.md new file mode 100644 index 0000000..b94742f --- /dev/null +++ b/periscope/src/docs/development/PINSCOPE_FENCE.md @@ -0,0 +1,38 @@ +# Recinto PinScope + +`LICENSE` AGPL-3.0 is unchanged at the git root. The GitHub fork `manvalan/periscope` → `Faradworks/Pinscope` is **not** detached. + +## Physical trees (2.38.0) + +| Tree | Path | +| --- | --- | +| Native Periscope | `periscope/src/` | +| Inherited PinScope (in-tree dependency) | `periscope/dependency/` | +| Third party RF | `vendor/impedancefinder/` | + +**Never empty-delete `periscope/dependency/`.** Replace later by adding modules under `periscope/src` (Fase C) and stopping calls into the inherited file. + +## Inherited (KEEP) + +- `periscope/dependency/backend/periscopex/{graph,models,parsers*,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check,pin_function_tokens}` +- `periscope/dependency/backend/services/pipeline.py`, `pipeline_worker.py`, `validation.py`, `extraction.py` +- `periscope/dependency/skills/`, `periscope/dependency/scripts/upload_skills.py` +- Open-core Clerk/billing stubs under dependency frontend/backend +- `periscope/dependency/simple_project/`, `periscope/dependency/docs/how-it-works.svg` + +Native (do not fold into the fence): `finding_engine.py`, PCB/placement/antenna, DeepSeek, local JWT, KiCad plugin — all under `periscope/src`. + +## Write vs read (identifiers) + +| Write (native) | Read-only / migrate | +| --- | --- | +| `periscope_token` | `pinscope_token` | +| `periscopex:*` localStorage | `pinscopex:*` | +| JWT `iss=periscope-local` | also accept `pinscope-local` | +| `periscope_version` in `project.json` | also accept `pinscope_version` | + +Code: `periscope/src/frontend/src/lib/pinscope-compat.ts`, `periscope/src/backend/pinscope_compat.py`. + +## Out of scope here + +Clerk/Anthropic runtime isolation (B). Rewrite of `validate.py` / finding engine (C). UI FindingCard (D). Parsers/graph (E). Fork detach (legal). Auto-place. diff --git a/docs/development/PINSCOPE_INDEPENDENCE_PLAN.md b/periscope/src/docs/development/PINSCOPE_INDEPENDENCE_PLAN.md similarity index 81% rename from docs/development/PINSCOPE_INDEPENDENCE_PLAN.md rename to periscope/src/docs/development/PINSCOPE_INDEPENDENCE_PLAN.md index aa5c8a2..d24235d 100644 --- a/docs/development/PINSCOPE_INDEPENDENCE_PLAN.md +++ b/periscope/src/docs/development/PINSCOPE_INDEPENDENCE_PLAN.md @@ -1,8 +1,7 @@ # Piano — indipendenza architettonica e di licenza da PinScope -**Stato:** solo piano. Nessuna modifica a source di prodotto, remotes, `LICENSE`, né git history. -**Base:** [PINSCOPE_AUDIT.md](./PINSCOPE_AUDIT.md) (snapshot prodotto `facbaa2` / `cursor/pcb-review-plan-44dd`). -**Intento di Michele:** *lasciare* il codice PinScope e le sue licenze; **isolare** l’albero ereditato; disegnare dipendenze; marcare cosa **deve** essere riscritto. Non cancellare ogni riga ereditata. +**Stato:** split fisico shipped in **2.38.0**. Fork GitHub non staccato; `validate.py` non riscritto. +**Sequenza confermata:** (1) split `periscope/src` vs `periscope/dependency/` — **questa fase**; (2) **poi** sostituzione incrementale dei moduli PinScope (Fase C). **Mai** svuotare o cancellare l’albero ereditato. Questo piano **non** stacca il fork GitHub (`manvalan/periscope` ← `Faradworks/Pinscope`). Lo stacco è un passo legale successivo, fuori da queste fasi di lavoro, salvo decisione esplicita. @@ -29,38 +28,39 @@ L’audit già dice: l’indipendenza **non** è bloccata dalle stringhe `pinsco Trattare come **una dipendenza in-tree**, non come prodotto Periscope: -| Pacchetto logico | Path odierni | Licenza da audit | +| Pacchetto logico | Path **dopo lo split** | Licenza da audit | | --- | --- | --- | -| Core schematico PinScope | `backend/periscopex/{graph,models,parsers,parsers_edif,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check,pin_function_tokens}.py` | AGPL-3.0 del fork (blob `LICENSE` identico a upstream) | -| Orchestrazione review | `backend/services/pipeline.py`, `pipeline_worker.py`, `services/validation.py`, `services/extraction.py` (parte DIRECT) | stessa | -| Skills Anthropic/Console | `skills/extract-pattern`, `skills/extract-specs`, `skills/extract-pintable/{schema.json,validate.py}`, `scripts/upload_skills.py`, `backend/skills_manifest.json` | stessa + contratto Claude | -| UI OSS / marketing shell | gran parte di `frontend/src` UPSTREAM/DERIVED, `frontend/public`, `frontend/content/terms.md` + `privacy.md` (testo Faradworks) | AGPL + TOS Faradworks **nel contenuto** | -| Gateway seams | `billing_hook.py`, `proxy.ts`, `clerk-theme-provider.tsx`, `use-optional-auth.ts`, analytics, CSP hosts | stub open-core PinScope | -| Fixture upstream | `simple_project/` (asc/csv/pdf/`design_graph.json`), `docs/how-it-works.svg` | AGPL / contenuto upstream | +| Core schematico PinScope | `periscope/dependency/backend/periscopex/{graph,models,parsers,parsers_edif,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check,pin_function_tokens}.py` | AGPL-3.0 del fork (blob `LICENSE` identico a upstream) | +| Orchestrazione review | `periscope/dependency/backend/services/pipeline.py`, `pipeline_worker.py`, `validation.py`, `extraction.py` (DIRECT) | stessa | +| Skills Anthropic/Console | `periscope/dependency/skills/…`, `periscope/dependency/scripts/upload_skills.py`, `periscope/dependency/backend/skills_manifest.json` | stessa + contratto Claude | +| UI OSS / marketing shell | `periscope/dependency/frontend/` (UPSTREAM/DERIVED); file nativi in `periscope/src/frontend/` con symlink nel recinto | AGPL | +| Gateway seams | `billing_hook.py`, `proxy.ts`, `clerk-theme-provider.tsx`, … sotto `periscope/dependency/` | stub open-core PinScope | +| Fixture upstream | `periscope/dependency/simple_project/`, `periscope/dependency/docs/how-it-works.svg` | AGPL / contenuto upstream | -**Regola del recinto (quando si implementerà, non ora):** +**Regola del recinto:** - Import **solo** da una facciata stretta (es. `build_graph`, `parse_bom`, `parse_netlist_any`, `Finding` Pydantic, `validate_design_async`). - Nessun nuovo check PCB/schema scrive dentro `validate.py`. - Nessun file nativo assume Clerk, Anthropic Console, `pinscope.ai`, Faradworks Inc. - Il recinto **conserva** `LICENSE` AGPL e l’attribuzione “derived from Faradworks/Pinscope”. +- **Non cancellare** `periscope/dependency/` quando un modulo viene sostituito: si aggiunge il sostituto in `periscope/src` e si smette di *chiamare* il vecchio file. Empty-delete è fuori piano. -Implementazione fisica futura (non in fase identità): directory tipo `vendor/pinscope/` **oppure** package `backend/pinscope_legacy/` con shim `periscopex` — solo dopo che i consumer nativi non importano più path sparsi. Non è un rename cosmetico (`pinscopex` → `periscopex` è già successo e **non** è un recinto). +**Split fisico (2.38.0):** `periscope/src` (nativo) + `periscope/dependency/` (PinScope in-tree). Docker overlay: dependency poi src. Glue locale: `backend/__init__.py`. Non è un rename cosmetico (`pinscopex` → `periscopex` **non** era un recinto). ### 2.2 Nativo Periscope (fuori recinto) | Area | Path | Perché è nativo | | --- | --- | --- | -| Contratto finding | `finding_engine.py`, `docs/motore-finding.md` | NEW; clamp FACT/REQUIREMENT/INFERENCE | -| PCB exam | `pcb_pipeline.py`, `pcb_*.py`, `parsers_kicad_pcb.py`, UI `pcb/` | NEW; INDIRECT sul grafo | -| Placement F2 | `placement_pipeline.py`, `placement_pack.py`, `functional_groups.py` | NEW | -| RF / antenna | `antenna_*.py`, parte Impedance UI | NEW | -| Facade Z0 | `periscopex/impedance.py` | NEW; il solver è terzi | -| Auth self-host | `local_jwt.py`, `local_users.py`, `routers/auth.py` | REPLACEMENT | -| LLM DeepSeek | `deepseek_provider.py`, `local_skill.py`, `pdf_ingest.py` | NEW | -| Deploy | `scripts/update-periscope.sh`, `docker-compose.yml` | NEW (nomi `pinscope_*` ancora WEAK) | -| Plugin KiCad | `plugins/kicad/` | NEW | -| Check deterministici fork | `dnp_check`, `sequencing_check`, `layout_rules`, … | NEW ma **INDIRECT**: usano `models` / graph | +| Contratto finding | `periscope/src/backend/periscopex/finding_engine.py`, `periscope/src/docs/motore-finding.md` | NEW; clamp FACT/REQUIREMENT/INFERENCE | +| PCB exam | `periscope/src/backend/…/pcb_*.py`, UI `periscope/src/frontend/…/pcb/` | NEW; INDIRECT sul grafo | +| Placement F2 | `periscope/src/backend/…/placement_*.py`, `functional_groups.py` | NEW | +| RF / antenna | `periscope/src/backend/periscopex/antenna_*.py` | NEW | +| Facade Z0 | `periscope/src/backend/periscopex/impedance.py` | NEW; il solver è terzi | +| Auth self-host | `periscope/src/backend/services/local_jwt.py`, `routers/auth.py` | REPLACEMENT | +| LLM DeepSeek | `periscope/src/backend/services/llm/deepseek_provider.py`, `local_skill.py`, `pdf_ingest.py` | NEW | +| Deploy | `scripts/update-periscope.sh`, `docker-compose.yml`, `periscope/src/backend/Dockerfile` | NEW (nomi `pinscope_*` ancora WEAK) | +| Plugin KiCad | `periscope/src/plugins/kicad/` | NEW | +| Check deterministici fork | `dnp_check`, `sequencing_check`, `layout_rules`, … under `periscope/src` | NEW ma **INDIRECT**: usano `models` / graph | ### 2.3 Terzi (né PinScope né “nostro motore”) @@ -191,8 +191,18 @@ Allineato all’audit §11 punti 1–4: `finding_engine` / `validate.py` **non** 4. Compatibility fence: un modulo/doc per chiavi `pinscope_*`; smettere di **scrivere** `pinscopex:` in `localStorage`. 5. Dichiarare a codice (commento/ADR) il recinto §2 — ancora senza muovere file. +**Shipped 2.36.0:** operatore Michele Bigi; `pinscope_compat`; fork invariato. + **Done quando:** UI e legal non dicono Faradworks Inc.; AGPL e fork parent invariati; PCB/schema invariati. +### Split fisico — `periscope/src` vs `periscope/dependency/` (**prima** di riscrivere PinScope) + +Confermato da Michele: **prima** lo split degli alberi, **poi** sostituzione incrementale (Fase C). Non si parte da empty-delete. + +**Shipped 2.38.0:** git mv + path/Docker/test/deploy. `validate.py` e finding engine **non** riscritti. `LICENSE` AGPL visibile alla root. Fork GitHub non staccato. Auto-place invariato (non in scope). + +**Done quando:** l’app bootstrappa con i due alberi; Docker overlay `dependency` poi `src`; pytest verde; nessun modulo PinScope cancellato. + ### Fase B — Seam runtime (Clerk, Anthropic, vendor license) Audit §11 punti 5–6, 8–9. diff --git a/docs/motore-finding.md b/periscope/src/docs/motore-finding.md similarity index 100% rename from docs/motore-finding.md rename to periscope/src/docs/motore-finding.md diff --git a/docs/piano-implementazione.md b/periscope/src/docs/piano-implementazione.md similarity index 100% rename from docs/piano-implementazione.md rename to periscope/src/docs/piano-implementazione.md diff --git a/docs/piano-pcb-review.md b/periscope/src/docs/piano-pcb-review.md similarity index 100% rename from docs/piano-pcb-review.md rename to periscope/src/docs/piano-pcb-review.md diff --git a/frontend/dockerfile b/periscope/src/frontend/dockerfile similarity index 87% rename from frontend/dockerfile rename to periscope/src/frontend/dockerfile index f619651..8f95493 100644 --- a/frontend/dockerfile +++ b/periscope/src/frontend/dockerfile @@ -7,10 +7,11 @@ ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL ARG NEXT_PUBLIC_AUTH_MODE ENV NEXT_PUBLIC_AUTH_MODE=$NEXT_PUBLIC_AUTH_MODE -COPY package*.json ./ +COPY periscope/dependency/frontend/package*.json ./ RUN npm ci -COPY . . +COPY periscope/dependency/frontend/ ./ +COPY periscope/src/frontend/ ./ ENV NEXT_TELEMETRY_DISABLED=1 diff --git a/frontend/src/app/(app)/library/page.tsx b/periscope/src/frontend/src/app/(app)/library/page.tsx similarity index 100% rename from frontend/src/app/(app)/library/page.tsx rename to periscope/src/frontend/src/app/(app)/library/page.tsx diff --git a/frontend/src/app/(app)/project/[id]/pcb/page.tsx b/periscope/src/frontend/src/app/(app)/project/[id]/pcb/page.tsx similarity index 100% rename from frontend/src/app/(app)/project/[id]/pcb/page.tsx rename to periscope/src/frontend/src/app/(app)/project/[id]/pcb/page.tsx diff --git a/frontend/src/app/(app)/project/[id]/placement/page.tsx b/periscope/src/frontend/src/app/(app)/project/[id]/placement/page.tsx similarity index 100% rename from frontend/src/app/(app)/project/[id]/placement/page.tsx rename to periscope/src/frontend/src/app/(app)/project/[id]/placement/page.tsx diff --git a/frontend/src/app/(marketing)/sign-in/page.tsx b/periscope/src/frontend/src/app/(marketing)/sign-in/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/sign-in/page.tsx rename to periscope/src/frontend/src/app/(marketing)/sign-in/page.tsx diff --git a/frontend/src/app/(marketing)/sign-up/page.tsx b/periscope/src/frontend/src/app/(marketing)/sign-up/page.tsx similarity index 100% rename from frontend/src/app/(marketing)/sign-up/page.tsx rename to periscope/src/frontend/src/app/(marketing)/sign-up/page.tsx diff --git a/frontend/src/components/layout/auth-gate.tsx b/periscope/src/frontend/src/components/layout/auth-gate.tsx similarity index 100% rename from frontend/src/components/layout/auth-gate.tsx rename to periscope/src/frontend/src/components/layout/auth-gate.tsx diff --git a/frontend/src/components/layout/sidebar-auth.tsx b/periscope/src/frontend/src/components/layout/sidebar-auth.tsx similarity index 100% rename from frontend/src/components/layout/sidebar-auth.tsx rename to periscope/src/frontend/src/components/layout/sidebar-auth.tsx diff --git a/frontend/src/components/project/impedance-panel.tsx b/periscope/src/frontend/src/components/project/impedance-panel.tsx similarity index 100% rename from frontend/src/components/project/impedance-panel.tsx rename to periscope/src/frontend/src/components/project/impedance-panel.tsx diff --git a/frontend/src/components/project/pcb-upload.tsx b/periscope/src/frontend/src/components/project/pcb-upload.tsx similarity index 100% rename from frontend/src/components/project/pcb-upload.tsx rename to periscope/src/frontend/src/components/project/pcb-upload.tsx diff --git a/frontend/src/components/project/topology-panel.tsx b/periscope/src/frontend/src/components/project/topology-panel.tsx similarity index 100% rename from frontend/src/components/project/topology-panel.tsx rename to periscope/src/frontend/src/components/project/topology-panel.tsx diff --git a/frontend/src/components/report/finding-review-controls.tsx b/periscope/src/frontend/src/components/report/finding-review-controls.tsx similarity index 100% rename from frontend/src/components/report/finding-review-controls.tsx rename to periscope/src/frontend/src/components/report/finding-review-controls.tsx diff --git a/frontend/src/components/report/findings-tree.tsx b/periscope/src/frontend/src/components/report/findings-tree.tsx similarity index 100% rename from frontend/src/components/report/findings-tree.tsx rename to periscope/src/frontend/src/components/report/findings-tree.tsx diff --git a/frontend/src/components/report/pcb-exam-section.tsx b/periscope/src/frontend/src/components/report/pcb-exam-section.tsx similarity index 100% rename from frontend/src/components/report/pcb-exam-section.tsx rename to periscope/src/frontend/src/components/report/pcb-exam-section.tsx diff --git a/frontend/src/hooks/use-pcb-progress.ts b/periscope/src/frontend/src/hooks/use-pcb-progress.ts similarity index 100% rename from frontend/src/hooks/use-pcb-progress.ts rename to periscope/src/frontend/src/hooks/use-pcb-progress.ts diff --git a/frontend/src/hooks/use-placement-progress.ts b/periscope/src/frontend/src/hooks/use-placement-progress.ts similarity index 100% rename from frontend/src/hooks/use-placement-progress.ts rename to periscope/src/frontend/src/hooks/use-placement-progress.ts diff --git a/frontend/src/lib/auth.ts b/periscope/src/frontend/src/lib/auth.ts similarity index 100% rename from frontend/src/lib/auth.ts rename to periscope/src/frontend/src/lib/auth.ts diff --git a/frontend/src/lib/layout-finding.ts b/periscope/src/frontend/src/lib/layout-finding.ts similarity index 100% rename from frontend/src/lib/layout-finding.ts rename to periscope/src/frontend/src/lib/layout-finding.ts diff --git a/frontend/src/lib/pinscope-compat.ts b/periscope/src/frontend/src/lib/pinscope-compat.ts similarity index 100% rename from frontend/src/lib/pinscope-compat.ts rename to periscope/src/frontend/src/lib/pinscope-compat.ts diff --git a/frontend/src/lib/storage-keys.ts b/periscope/src/frontend/src/lib/storage-keys.ts similarity index 100% rename from frontend/src/lib/storage-keys.ts rename to periscope/src/frontend/src/lib/storage-keys.ts diff --git a/plugins/__init__.py b/periscope/src/plugins/__init__.py similarity index 100% rename from plugins/__init__.py rename to periscope/src/plugins/__init__.py diff --git a/plugins/kicad/__init__.py b/periscope/src/plugins/kicad/__init__.py similarity index 100% rename from plugins/kicad/__init__.py rename to periscope/src/plugins/kicad/__init__.py diff --git a/plugins/kicad/focus.py b/periscope/src/plugins/kicad/focus.py similarity index 100% rename from plugins/kicad/focus.py rename to periscope/src/plugins/kicad/focus.py diff --git a/plugins/kicad/periscope_plugin.py b/periscope/src/plugins/kicad/periscope_plugin.py similarity index 100% rename from plugins/kicad/periscope_plugin.py rename to periscope/src/plugins/kicad/periscope_plugin.py diff --git a/periscope/src/scripts/smoke_simple_project.py b/periscope/src/scripts/smoke_simple_project.py new file mode 100644 index 0000000..6829795 --- /dev/null +++ b/periscope/src/scripts/smoke_simple_project.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""P0-5 smoke: deepseek-flash defaults + simple_project offline checks. + +Usage: + python3 scripts/smoke_simple_project.py # offline (no API) + python3 scripts/smoke_simple_project.py --live # needs DEEPSEEK_API_KEY + +Offline asserts: model defaults, vision gate, graph+eval golden, shortest_path. +Live (optional): PDF ingest attaches page images under vision. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve() +for _p in ROOT.parents: + if (_p / "LICENSE").is_file() and (_p / "periscope" / "dependency").is_dir(): + ROOT = _p + break +sys.path.insert(0, str(ROOT)) + +SIMPLE = ROOT / "periscope" / "dependency" / "simple_project" +BASELINE = SIMPLE / "smoke_baseline.json" + + +def _check_model_defaults() -> list[str]: + from backend.config import settings + + errs: list[str] = [] + if settings.deepseek_model != "deepseek-flash": + errs.append(f"deepseek_model={settings.deepseek_model!r}, want deepseek-flash") + # Mirror deepseek_provider._is_vision_model without importing openai. + name = settings.deepseek_model.strip().lower() + vision_ok = ( + name in {"deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"} + or "vision" in name + or name.startswith("deepseek-flash") + ) + if not vision_ok: + errs.append("deepseek-flash not treated as vision model") + effort = (settings.deepseek_reasoning_effort or "").lower() + if effort not in {"low", "high", "max"}: + errs.append(f"deepseek_reasoning_effort={effort!r}, want low|high|max") + return errs + + +def _check_simple_project_offline() -> list[str]: + from backend.periscopex.models import DesignGraph + from backend.periscopex.validation_tools import shortest_path + + errs: list[str] = [] + graph_path = SIMPLE / "design_graph.json" + if not graph_path.is_file(): + return ["simple_project/design_graph.json missing"] + g = DesignGraph.model_validate_json(graph_path.read_text(encoding="utf-8")) + + golden_path = SIMPLE / "eval_golden.json" + if not golden_path.is_file(): + return ["simple_project/eval_golden.json missing"] + golden_doc = json.loads(golden_path.read_text(encoding="utf-8")) + for ref in golden_doc.get("required_refs") or []: + if ref not in g.components: + errs.append(f"missing ref {ref}") + min_c = golden_doc.get("min_components") + if min_c and len(g.components) < int(min_c): + errs.append(f"components {len(g.components)} < {min_c}") + min_n = golden_doc.get("min_nets") + if min_n and len(g.nets) < int(min_n): + errs.append(f"nets {len(g.nets)} < {min_n}") + + if "U3" in g.components and "X1" in g.components: + pin = next(iter(g.components["U3"].pins), None) + xpin = next(iter(g.components["X1"].pins), None) + if pin and xpin: + msg = shortest_path(g, {}, "U3", pin, "X1", xpin) + print(f"note: shortest_path U3–X1 → {msg}") + + baseline = { + "component_count": len(g.components), + "net_count": len(g.nets), + "graph_ok": not errs, + } + if BASELINE.is_file(): + prev = json.loads(BASELINE.read_text(encoding="utf-8")) + for k in ("component_count", "net_count"): + if prev.get(k) != baseline.get(k): + errs.append(f"{k} {baseline.get(k)} != baseline {prev.get(k)}") + else: + BASELINE.write_text(json.dumps(baseline, indent=2) + "\n", encoding="utf-8") + print(f"Wrote baseline {BASELINE}") + + print(json.dumps({"offline": baseline, "errors": errs}, indent=2)) + return errs + + +def _check_live() -> list[str]: + from backend.config import settings + from backend.services.llm.deepseek_provider import _is_vision_model + from backend.services.llm.pdf_ingest import pdf_to_openai_content + + if not settings.deepseek_api_key: + return ["DEEPSEEK_API_KEY not set"] + pdfs = list((ROOT / "library" / "datasheets").rglob("*.pdf"))[:1] + if not pdfs: + pdfs = list(SIMPLE.rglob("*.pdf")) + if not pdfs: + return ["no PDF found for live vision check"] + pdf = pdfs[0] + parts = pdf_to_openai_content( + pdf, vision=_is_vision_model(settings.deepseek_model), max_images=4, + ) + n_img = sum(1 for p in parts if p.get("type") == "image_url") + print(json.dumps({"live_pdf": str(pdf), "content_parts": len(parts), "images": n_img})) + if n_img == 0 and _is_vision_model(settings.deepseek_model): + return ["vision model produced 0 page images"] + return [] + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--live", action="store_true", help="Also exercise PDF page images") + args = ap.parse_args() + errs = _check_model_defaults() + _check_simple_project_offline() + if args.live: + errs += _check_live() + if errs: + print("SMOKE FAIL:", *errs, sep="\n - ") + return 1 + print("SMOKE OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..c7b23ec --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +pythonpath = . +testpaths = tests diff --git a/scripts/materialize-frontend.sh b/scripts/materialize-frontend.sh new file mode 100755 index 0000000..e6667db --- /dev/null +++ b/scripts/materialize-frontend.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Overlay PinScope frontend (dependency) with native Periscope files (src). +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +DEST="${1:-"$ROOT/.merge/frontend"}" +python3 - "$ROOT" "$DEST" <<'PY' +import shutil +import sys +from pathlib import Path + +root, dest = Path(sys.argv[1]), Path(sys.argv[2]) +dep = root / "periscope" / "dependency" / "frontend" +src = root / "periscope" / "src" / "frontend" +if dest.exists(): + shutil.rmtree(dest) +shutil.copytree(dep, dest, dirs_exist_ok=True) +if src.is_dir(): + shutil.copytree(src, dest, dirs_exist_ok=True) +print(dest) +PY diff --git a/scripts/smoke_simple_project.py b/scripts/smoke_simple_project.py index 77ed15f..4459ad9 100644 --- a/scripts/smoke_simple_project.py +++ b/scripts/smoke_simple_project.py @@ -1,134 +1,9 @@ #!/usr/bin/env python3 -"""P0-5 smoke: deepseek-flash defaults + simple_project offline checks. - -Usage: - python3 scripts/smoke_simple_project.py # offline (no API) - python3 scripts/smoke_simple_project.py --live # needs DEEPSEEK_API_KEY - -Offline asserts: model defaults, vision gate, graph+eval golden, shortest_path. -Live (optional): PDF ingest attaches page images under vision. -""" - -from __future__ import annotations - -import argparse -import json -import sys +"""Wrapper kept at the historic path after the tree split.""" from pathlib import Path +import runpy -ROOT = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(ROOT)) - -SIMPLE = ROOT / "simple_project" -BASELINE = SIMPLE / "smoke_baseline.json" - - -def _check_model_defaults() -> list[str]: - from backend.config import settings - - errs: list[str] = [] - if settings.deepseek_model != "deepseek-flash": - errs.append(f"deepseek_model={settings.deepseek_model!r}, want deepseek-flash") - # Mirror deepseek_provider._is_vision_model without importing openai. - name = settings.deepseek_model.strip().lower() - vision_ok = ( - name in {"deepseek-flash", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp"} - or "vision" in name - or name.startswith("deepseek-flash") - ) - if not vision_ok: - errs.append("deepseek-flash not treated as vision model") - effort = (settings.deepseek_reasoning_effort or "").lower() - if effort not in {"low", "high", "max"}: - errs.append(f"deepseek_reasoning_effort={effort!r}, want low|high|max") - return errs - - -def _check_simple_project_offline() -> list[str]: - from backend.periscopex.models import DesignGraph - from backend.periscopex.validation_tools import shortest_path - - errs: list[str] = [] - graph_path = SIMPLE / "design_graph.json" - if not graph_path.is_file(): - return ["simple_project/design_graph.json missing"] - g = DesignGraph.model_validate_json(graph_path.read_text(encoding="utf-8")) - - golden_path = SIMPLE / "eval_golden.json" - if not golden_path.is_file(): - return ["simple_project/eval_golden.json missing"] - golden_doc = json.loads(golden_path.read_text(encoding="utf-8")) - for ref in golden_doc.get("required_refs") or []: - if ref not in g.components: - errs.append(f"missing ref {ref}") - min_c = golden_doc.get("min_components") - if min_c and len(g.components) < int(min_c): - errs.append(f"components {len(g.components)} < {min_c}") - min_n = golden_doc.get("min_nets") - if min_n and len(g.nets) < int(min_n): - errs.append(f"nets {len(g.nets)} < {min_n}") - - if "U3" in g.components and "X1" in g.components: - pin = next(iter(g.components["U3"].pins), None) - xpin = next(iter(g.components["X1"].pins), None) - if pin and xpin: - msg = shortest_path(g, {}, "U3", pin, "X1", xpin) - print(f"note: shortest_path U3–X1 → {msg}") - - baseline = { - "component_count": len(g.components), - "net_count": len(g.nets), - "graph_ok": not errs, - } - if BASELINE.is_file(): - prev = json.loads(BASELINE.read_text(encoding="utf-8")) - for k in ("component_count", "net_count"): - if prev.get(k) != baseline.get(k): - errs.append(f"{k} {baseline.get(k)} != baseline {prev.get(k)}") - else: - BASELINE.write_text(json.dumps(baseline, indent=2) + "\n", encoding="utf-8") - print(f"Wrote baseline {BASELINE}") - - print(json.dumps({"offline": baseline, "errors": errs}, indent=2)) - return errs - - -def _check_live() -> list[str]: - from backend.config import settings - from backend.services.llm.deepseek_provider import _is_vision_model - from backend.services.llm.pdf_ingest import pdf_to_openai_content - - if not settings.deepseek_api_key: - return ["DEEPSEEK_API_KEY not set"] - pdfs = list((ROOT / "library" / "datasheets").rglob("*.pdf"))[:1] - if not pdfs: - pdfs = list(SIMPLE.rglob("*.pdf")) - if not pdfs: - return ["no PDF found for live vision check"] - pdf = pdfs[0] - parts = pdf_to_openai_content( - pdf, vision=_is_vision_model(settings.deepseek_model), max_images=4, - ) - n_img = sum(1 for p in parts if p.get("type") == "image_url") - print(json.dumps({"live_pdf": str(pdf), "content_parts": len(parts), "images": n_img})) - if n_img == 0 and _is_vision_model(settings.deepseek_model): - return ["vision model produced 0 page images"] - return [] - - -def main() -> int: - ap = argparse.ArgumentParser(description=__doc__) - ap.add_argument("--live", action="store_true", help="Also exercise PDF page images") - args = ap.parse_args() - errs = _check_model_defaults() + _check_simple_project_offline() - if args.live: - errs += _check_live() - if errs: - print("SMOKE FAIL:", *errs, sep="\n - ") - return 1 - print("SMOKE OK") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) +runpy.run_path( + str(Path(__file__).resolve().parents[1] / "periscope" / "src" / "scripts" / "smoke_simple_project.py"), + run_name="__main__", +) diff --git a/scripts/update-periscope.sh b/scripts/update-periscope.sh index 2b7332e..ab5dcd9 100755 --- a/scripts/update-periscope.sh +++ b/scripts/update-periscope.sh @@ -126,6 +126,9 @@ if [[ ! -f .env ]]; then if [[ -f backend/.env ]]; then log "No ./.env — copying backend/.env" cp backend/.env .env + elif [[ -f periscope/dependency/backend/.env.example ]]; then + log "No ./.env — copying periscope/dependency/backend/.env.example (you must set DEEPSEEK_API_KEY)" + cp periscope/dependency/backend/.env.example .env elif [[ -f backend/.env.example ]]; then log "No ./.env — copying backend/.env.example (you must set DEEPSEEK_API_KEY)" cp backend/.env.example .env diff --git a/tests/conftest.py b/tests/conftest.py index 101e7d7..0816048 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,8 +10,14 @@ import pytest # Ensure the repo root is on sys.path so `import backend.*` works whether # pytest is invoked from the repo root or a subdirectory. REPO_ROOT = Path(__file__).resolve().parents[1] -if str(REPO_ROOT) not in sys.path: - sys.path.insert(0, str(REPO_ROOT)) +for p in ( + REPO_ROOT / "periscope" / "dependency", + REPO_ROOT / "periscope" / "src", + REPO_ROOT, +): + s = str(p) + if p.is_dir() and s not in sys.path: + sys.path.insert(0, s) from backend.vendor_path import ensure_impedancefinder diff --git a/tests/paths.py b/tests/paths.py new file mode 100644 index 0000000..4baac96 --- /dev/null +++ b/tests/paths.py @@ -0,0 +1,9 @@ +"""Shared fixture paths after the periscope/src vs periscope/dependency split.""" + +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +SIMPLE_PROJECT = REPO_ROOT / "periscope" / "dependency" / "simple_project" +TAXONOMY = REPO_ROOT / "periscope" / "dependency" / "taxonomy" diff --git a/tests/test_cost_estimator.py b/tests/test_cost_estimator.py index 2edc3e7..7c86da5 100644 --- a/tests/test_cost_estimator.py +++ b/tests/test_cost_estimator.py @@ -2,7 +2,7 @@ from __future__ import annotations -from pathlib import Path +from tests.paths import SIMPLE_PROJECT import pytest @@ -14,9 +14,6 @@ from backend.services.cost_estimator import ( ) -SIMPLE_PROJECT = Path(__file__).resolve().parents[1] / "simple_project" - - def _seed_project(storage, user_id: str, project_id: str) -> None: """Create a minimal project with the simple_project BOM uploaded.""" proj_svc.create_project(storage, user_id, "Fixture") diff --git a/tests/test_crystal_cl_check.py b/tests/test_crystal_cl_check.py index 05d35d7..54eafc8 100644 --- a/tests/test_crystal_cl_check.py +++ b/tests/test_crystal_cl_check.py @@ -2,6 +2,8 @@ from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from backend.periscopex.crystal_cl_check import check_crystal_cl from backend.periscopex.models import ( Component, @@ -92,6 +94,6 @@ def test_with_stray_mismatch_warns(): def test_simple_project_without_cl_silent(): from pathlib import Path from backend.periscopex.models import DesignGraph - path = Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json" + path = SIMPLE_PROJECT / "design_graph.json" g = DesignGraph.model_validate_json(path.read_text()) assert check_crystal_cl(g) == [] diff --git a/tests/test_datasheet_excerpt_tool.py b/tests/test_datasheet_excerpt_tool.py index 832a829..171e837 100644 --- a/tests/test_datasheet_excerpt_tool.py +++ b/tests/test_datasheet_excerpt_tool.py @@ -2,6 +2,8 @@ from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + import json from pathlib import Path @@ -19,7 +21,7 @@ from backend.periscopex.validation_tools import ( from backend.services.llm.types import PdfBlock from backend.services.validation import _signal_neighbors -GRAPH = Path(__file__).resolve().parent.parent / "simple_project" / "design_graph.json" +GRAPH = SIMPLE_PROJECT / "design_graph.json" IC_MPNS = { "U1": "SPX3819M5-L-3-3/TR", "U2": "CH340E", diff --git a/tests/test_eval_report.py b/tests/test_eval_report.py index 8389015..4980734 100644 --- a/tests/test_eval_report.py +++ b/tests/test_eval_report.py @@ -8,6 +8,8 @@ Unverified quotes are citation misses, not hits. from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from pathlib import Path from backend.periscopex.eval_report import ( @@ -80,7 +82,7 @@ def test_citation_rate_ignores_deterministic_and_counts_unverified(): def test_simple_project_eval_matches_committed_golden(): - root = Path(__file__).resolve().parents[1] / "simple_project" + root = SIMPLE_PROJECT scores = eval_simple_project(root) assert scores.graph_ok, scores.graph_errors assert scores.precision == 1.0 @@ -94,7 +96,7 @@ def test_simple_project_eval_rejects_truncated_graph(tmp_path: Path): import json import shutil - src = Path(__file__).resolve().parents[1] / "simple_project" + src = SIMPLE_PROJECT dest = tmp_path / "simple_project" shutil.copytree(src, dest) g = json.loads((dest / "design_graph.json").read_text()) diff --git a/tests/test_functional_groups.py b/tests/test_functional_groups.py index 04370ee..b69397a 100644 --- a/tests/test_functional_groups.py +++ b/tests/test_functional_groups.py @@ -2,13 +2,15 @@ from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + import json from pathlib import Path from backend.periscopex.functional_groups import build_functional_groups from backend.periscopex.models import DesignGraph -SIMPLE = Path(__file__).resolve().parents[1] / "simple_project" +SIMPLE = SIMPLE_PROJECT def _graph() -> DesignGraph: diff --git a/tests/test_passive_from_distributor.py b/tests/test_passive_from_distributor.py index 1b630a3..19ef58b 100644 --- a/tests/test_passive_from_distributor.py +++ b/tests/test_passive_from_distributor.py @@ -1,3 +1,4 @@ +from tests.paths import SIMPLE_PROJECT, TAXONOMY from backend.services.passive_from_distributor import specs_from_distributor @@ -91,7 +92,7 @@ def test_auto_resolve_skips_llm_when_catalog_parses(monkeypatch): raise AssertionError("LLM must not run") monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom) - tax = Path(__file__).resolve().parents[1] / "taxonomy" + tax = TAXONOMY model = asyncio.run( auto_resolve_specs( mpn="CL21B225KPFNNNE", @@ -115,7 +116,7 @@ def test_auto_resolve_use_llm_false_on_ferrite(monkeypatch): raise AssertionError("LLM must not run") monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom) - tax = Path(__file__).resolve().parents[1] / "taxonomy" + tax = TAXONOMY model = asyncio.run( auto_resolve_specs( mpn="BLM18PG121SN1D", diff --git a/tests/test_passive_from_value.py b/tests/test_passive_from_value.py index f7d080e..d0da194 100644 --- a/tests/test_passive_from_value.py +++ b/tests/test_passive_from_value.py @@ -1,3 +1,4 @@ +from tests.paths import SIMPLE_PROJECT, TAXONOMY import asyncio from pathlib import Path @@ -57,7 +58,7 @@ def test_resolve_from_value_skips_llm_when_parseable(monkeypatch): raise AssertionError("LLM must not run") monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom) - tax = Path(__file__).resolve().parents[1] / "taxonomy" + tax = TAXONOMY model = asyncio.run( resolve_from_value( mpn="18pF", @@ -76,7 +77,7 @@ def test_resolve_from_value_placeholder_no_llm(monkeypatch): raise AssertionError("LLM must not run") monkeypatch.setattr("backend.services.extraction.call_with_fallback", boom) - tax = Path(__file__).resolve().parents[1] / "taxonomy" + tax = TAXONOMY with pytest.raises(ValueError, match="Placeholder"): asyncio.run( resolve_from_value( diff --git a/tests/test_pcb_review.py b/tests/test_pcb_review.py index 4a1a068..c73d939 100644 --- a/tests/test_pcb_review.py +++ b/tests/test_pcb_review.py @@ -2,6 +2,8 @@ from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from pathlib import Path from backend.periscopex.functional_groups import FunctionalGroupsReport, PlacementDomain, PlacementIcGroup @@ -26,7 +28,7 @@ from backend.periscopex.pcb_net_match import check_pcb_net_match from backend.periscopex.pcb_review import build_pcb_layout_context from backend.services.projects import ProjectMeta, STATUS_QUEUED, STATUS_RUNNING -SIMPLE = Path(__file__).resolve().parents[1] / "simple_project" +SIMPLE = SIMPLE_PROJECT def _graph() -> DesignGraph: diff --git a/tests/test_pin_mux_check.py b/tests/test_pin_mux_check.py index b01d780..6d5e3fb 100644 --- a/tests/test_pin_mux_check.py +++ b/tests/test_pin_mux_check.py @@ -13,6 +13,8 @@ Locks in: from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from backend.periscopex.models import ( Component, ComponentConstraints, @@ -184,7 +186,7 @@ def test_simple_project_uart0_nets_are_feasible_on_mspm0_pins(): from backend.periscopex.models import DesignGraph graph = DesignGraph.model_validate_json( - (Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json").read_text() + (SIMPLE_PROJECT / "design_graph.json").read_text() ) cmap = { "MSPM0G3507SPTR": _constraints( @@ -206,7 +208,7 @@ def test_simple_project_uart0_swapped_on_mspm0_is_error(): from backend.periscopex.models import DesignGraph graph = DesignGraph.model_validate_json( - (Path(__file__).resolve().parents[1] / "simple_project" / "design_graph.json").read_text() + (SIMPLE_PROJECT / "design_graph.json").read_text() ) cmap = { "MSPM0G3507SPTR": _constraints( diff --git a/tests/test_placement_check.py b/tests/test_placement_check.py index 9bd2f06..03e0095 100644 --- a/tests/test_placement_check.py +++ b/tests/test_placement_check.py @@ -6,6 +6,8 @@ Against: no PCB; same copper; same_layer unset; via in courtyard. from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from pathlib import Path from backend.periscopex.eval_report import eval_simple_project @@ -21,7 +23,7 @@ from backend.periscopex.models import ( ) from backend.periscopex.placement_check import _in_poly, check_placement -SIMPLE = Path(__file__).resolve().parents[1] / "simple_project" +SIMPLE = SIMPLE_PROJECT def _graph() -> DesignGraph: diff --git a/tests/test_placement_pack.py b/tests/test_placement_pack.py index 227c1e7..d44525c 100644 --- a/tests/test_placement_pack.py +++ b/tests/test_placement_pack.py @@ -2,6 +2,8 @@ from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from pathlib import Path from backend.periscopex.functional_groups import ( @@ -18,7 +20,7 @@ from backend.periscopex.models import ( ) from backend.periscopex.placement_pack import build_placement_pack -SIMPLE = Path(__file__).resolve().parents[1] / "simple_project" +SIMPLE = SIMPLE_PROJECT def _graph() -> DesignGraph: diff --git a/tests/test_placement_pipeline.py b/tests/test_placement_pipeline.py index 0614c47..b3ad4d1 100644 --- a/tests/test_placement_pipeline.py +++ b/tests/test_placement_pipeline.py @@ -2,6 +2,8 @@ from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from pathlib import Path import pytest @@ -14,7 +16,7 @@ from backend.periscopex.models import DesignGraph ROOT = Path(__file__).resolve().parents[1] -SIMPLE = ROOT / "simple_project" +SIMPLE = SIMPLE_PROJECT @pytest.fixture diff --git a/tests/test_si_check.py b/tests/test_si_check.py index ff0e24c..53ba3ee 100644 --- a/tests/test_si_check.py +++ b/tests/test_si_check.py @@ -7,6 +7,8 @@ I2C/GPIO/CC are not 50/90 Ω. ImpedenceFinder numbers vs layout_rules only. from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + from pathlib import Path from backend.periscopex.eval_report import eval_simple_project @@ -26,7 +28,7 @@ from backend.periscopex.models import ( ) from backend.periscopex.si_check import bus_class, check_si, partner_net, skip_si_net -SIMPLE = Path(__file__).resolve().parents[1] / "simple_project" +SIMPLE = SIMPLE_PROJECT def _graph() -> DesignGraph: diff --git a/tests/test_validation_no_tool_recovery.py b/tests/test_validation_no_tool_recovery.py index 9a2737d..efc59fc 100644 --- a/tests/test_validation_no_tool_recovery.py +++ b/tests/test_validation_no_tool_recovery.py @@ -9,6 +9,8 @@ so it survives future prompt regressions. from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + import json from pathlib import Path @@ -20,7 +22,7 @@ from backend.services import validation as val from backend.services.llm.types import Completion, ToolCall, Usage from backend.services.storage import LocalStorageBackend -GRAPH = Path(__file__).resolve().parent.parent / "simple_project" / "design_graph.json" +GRAPH = SIMPLE_PROJECT / "design_graph.json" IC_MPNS = { "U1": "SPX3819M5-L-3-3/TR", "U2": "CH340E", diff --git a/tests/test_validation_trace_log.py b/tests/test_validation_trace_log.py index aceba75..55f13e6 100644 --- a/tests/test_validation_trace_log.py +++ b/tests/test_validation_trace_log.py @@ -8,6 +8,8 @@ and that trace failures never break the review/report. from __future__ import annotations +from tests.paths import SIMPLE_PROJECT, TAXONOMY + import json import re from pathlib import Path @@ -20,7 +22,7 @@ from backend.services import validation as val from backend.services.llm.types import Completion, ToolCall, Usage from backend.services.storage import LocalStorageBackend -GRAPH = Path(__file__).resolve().parent.parent / "simple_project" / "design_graph.json" +GRAPH = SIMPLE_PROJECT / "design_graph.json" IC_MPNS = { "U1": "SPX3819M5-L-3-3/TR", "U2": "CH340E",