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.
@@ -14,20 +14,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
cache: pip
|
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
|
- run: pytest tests/ -q
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: frontend
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: npm
|
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
|
- run: npm ci
|
||||||
|
working-directory: .merge/frontend
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
working-directory: .merge/frontend
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ skills-lock.json
|
|||||||
data/
|
data/
|
||||||
backend/data/
|
backend/data/
|
||||||
|
|
||||||
# Frontend
|
.merge/
|
||||||
frontend/node_modules/
|
periscope/dependency/frontend/node_modules/
|
||||||
frontend/.next/
|
periscope/dependency/frontend/.next/
|
||||||
frontend/out/
|
periscope/src/frontend/.next/
|
||||||
.next/
|
.next/
|
||||||
|
|
||||||
# retrospective
|
# retrospective
|
||||||
|
|||||||
@@ -1,149 +1,15 @@
|
|||||||
# Periscope — Agentic Schematic Validation
|
# 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 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.
|
||||||
|
|
||||||
> **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
|
## Running
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Backend (copy backend/.env.example to .env at repo root first)
|
# Backend (copy periscope/dependency/backend/.env.example to .env at repo root first)
|
||||||
python3 -m uvicorn backend.main:app --reload # localhost:8000
|
python3 -m uvicorn backend.main:app --reload # localhost:8000
|
||||||
|
|
||||||
# Frontend
|
# 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`.
|
||||||
|
|||||||
@@ -23,12 +23,21 @@ Default routing:
|
|||||||
| Per-IC datasheet review | `deepseek-flash` |
|
| Per-IC datasheet review | `deepseek-flash` |
|
||||||
| Auto-resolve / normalize | `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
|
## How it works
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/how-it-works.svg" width="920" alt="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.">
|
<img src="periscope/dependency/docs/how-it-works.svg" width="920" alt="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.">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
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.
|
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
|
## 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/):
|
You need Python 3.12+, Node 20+, and a [DeepSeek API key](https://platform.deepseek.com/):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
pip install -r backend/requirements.txt
|
pip install -r periscope/dependency/backend/requirements.txt
|
||||||
cp backend/.env.example backend/.env # set DEEPSEEK_API_KEY
|
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
|
python3 -m uvicorn backend.main:app --reload --host 127.0.0.1 --port 18741
|
||||||
|
|
||||||
# in another terminal
|
# 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
|
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
|
## Docker
|
||||||
|
|
||||||
```bash
|
```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
|
docker compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
@@ -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__))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ services:
|
|||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: backend/Dockerfile
|
dockerfile: periscope/src/backend/Dockerfile
|
||||||
|
|
||||||
container_name: periscope-backend
|
container_name: periscope-backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -25,7 +25,7 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- ./taxonomy:/app/taxonomy
|
- ./periscope/dependency/taxonomy:/app/taxonomy
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
@@ -38,8 +38,8 @@ services:
|
|||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: .
|
||||||
dockerfile: dockerfile
|
dockerfile: periscope/src/frontend/dockerfile
|
||||||
args:
|
args:
|
||||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8080}
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8080}
|
||||||
NEXT_PUBLIC_AUTH_MODE: ${NEXT_PUBLIC_AUTH_MODE:-}
|
NEXT_PUBLIC_AUTH_MODE: ${NEXT_PUBLIC_AUTH_MODE:-}
|
||||||
|
|||||||
@@ -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).
|
|
||||||
@@ -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`).
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
"""PinScope-inherited backend package (in-tree dependency)."""
|
||||||
|
from pkgutil import extend_path
|
||||||
|
|
||||||
|
__path__ = extend_path(__path__, __name__)
|
||||||
@@ -7,6 +7,11 @@ from pathlib import Path
|
|||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
from pydantic_settings import BaseSettings
|
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/)
|
# Resolve paths relative to the project root (one level up from backend/)
|
||||||
_BACKEND_DIR = Path(__file__).resolve().parent
|
_BACKEND_DIR = Path(__file__).resolve().parent
|
||||||
_PROJECT_ROOT = _BACKEND_DIR.parent
|
_PROJECT_ROOT = _BACKEND_DIR.parent
|
||||||
@@ -111,10 +116,10 @@ class Settings(BaseSettings):
|
|||||||
# single finding. Runs once after all per-IC reviews complete.
|
# single finding. Runs once after all per-IC reviews complete.
|
||||||
cross_ic_dedup_enabled: bool = True
|
cross_ic_dedup_enabled: bool = True
|
||||||
|
|
||||||
# Paths (relative to project root, used by LocalStorageBackend)
|
# Paths (git split: data at repo root; taxonomy/skills under periscope/dependency)
|
||||||
data_dir: Path = _PROJECT_ROOT / "data"
|
data_dir: Path = _data_dir()
|
||||||
taxonomy_dir: Path = _PROJECT_ROOT / "taxonomy"
|
taxonomy_dir: Path = _taxonomy_dir()
|
||||||
skills_dir: Path = _PROJECT_ROOT / "skills"
|
skills_dir: Path = _skills_dir()
|
||||||
|
|
||||||
# GCS (if set, use GCSStorageBackend; otherwise LocalStorageBackend)
|
# GCS (if set, use GCSStorageBackend; otherwise LocalStorageBackend)
|
||||||
gcs_bucket: str = ""
|
gcs_bucket: str = ""
|
||||||
@@ -193,7 +198,7 @@ class Settings(BaseSettings):
|
|||||||
pipeline_sweeper_stale_seconds: int = 60
|
pipeline_sweeper_stale_seconds: int = 60
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
"env_file": str(_BACKEND_DIR / ".env"),
|
"env_file": str(_env_file()),
|
||||||
"env_file_encoding": "utf-8",
|
"env_file_encoding": "utf-8",
|
||||||
"extra": "ignore",
|
"extra": "ignore",
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from pkgutil import extend_path
|
||||||
|
|
||||||
|
__path__ = extend_path(__path__, __name__)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from pkgutil import extend_path
|
||||||
|
|
||||||
|
__path__ = extend_path(__path__, __name__)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from pkgutil import extend_path
|
||||||
|
|
||||||
|
__path__ = extend_path(__path__, __name__)
|
||||||
@@ -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
|
overrides via ``Settings.provider_*`` env vars route specific stages to
|
||||||
Anthropic or Gemini if those keys are configured.
|
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.factory import call_with_fallback, get_provider
|
||||||
from backend.services.llm.types import (
|
from backend.services.llm.types import (
|
||||||
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
What's new in Periscope.
|
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
|
## 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.
|
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.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "2.26.3",
|
"version": "2.38.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"sync-version": "node scripts/sync-version.mjs",
|
"sync-version": "node scripts/sync-version.mjs",
|
||||||
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |