Files
periscope/docs/development/PINSCOPE_AUDIT.md
T
michele a63e5bd7ab Fase A identity on PCB HEAD: operator fence, keep BOM/SPOF/EMI.
Replace Faradworks Inc. TOS/privacy/contact/metadata with Michele Bigi.
Dual-read pinscope_* keys; write periscope_* only. AGPL LICENSE and
GitHub fork parent unchanged. validate.py not edited.
2026-09-20 12:41:17 +02:00

893 lines
74 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PHASE 0 — PinScope / Periscope forensic audit
**Status:** observational only. No source, license, git history, remotes, or fork relationship was modified as part of this audit.
**Goal:** architectural independence of Periscope from PinScope — not deletion of every inherited line.
**Does not implement Phase 1.** Disposition labels are inventory, not a rewrite plan.
| Field | Value |
| --- | --- |
| Audit date | 2026-09-20 |
| GitHub repo | https://github.com/manvalan/periscope (`fork: true`) |
| Declared parent / source | https://github.com/Faradworks/Pinscope |
| Product snapshot (working tree described here) | `origin/cursor/pcb-review-plan-44dd` = `facbaa23058be51de3eb2551e074ada270b394d5` |
| GitHub default branch | `origin/main` = `ab1c5b081cefb6445b053c170203d6ffe0a88d1b` |
| Upstream `main` | `upstream/main` = `b26ad3509f4a19878fb7050aad3da9eacba8d914` (fetched locally for comparison only; **GitHub remotes of the fork were not changed**) |
| Merge-base (all three tips) | `b26ad35` — upstream HEAD *is* the fork point |
| This document | `docs/development/PINSCOPE_AUDIT.md` (only file this phase is allowed to add) |
---
## 1. Executive summary
Periscope is a **live GitHub fork** of Faradworks/Pinscope (AGPL-3.0). Upstream has **five** commits and has not moved since 2026-07-17. GitHub `main` on the fork is only **three** commits ahead (Docker / OSS / README). The **product** (DeepSeek, rebrand, PCB exam, finding engine, v2.32.1) lives on `cursor/pcb-review-plan-44dd`: **92** commits ahead of upstream, **89** ahead of `origin/main`, **not merged** to the default branch. GitHub lists **no pull requests**.
On the product snapshot (`facbaa2`, 404 files, 123544 text lines):
| Class | Files | File % | Lines | Line % |
| --- | --- | --- | --- | --- |
| UPSTREAM (identical blob) | 125 | 30.9% | 45211 | 36.6% |
| DERIVED (measurable overlap with an upstream path or rename) | 127 | 31.4% | 52455 | 42.5% |
| REPLACEMENT (same path, Jaccard < 0.20) | 4 | 1.0% | 359 | 0.3% |
| NEW (no upstream match at the thresholds below) | 139 | 34.4% | 24437 | 19.8% |
| THIRD_PARTY (`vendor/` except empty `__init__.py` mis-tagged — see §3) | 9 | 2.2% | 1082 | 0.9% |
| UNKNOWN | 0 | — | — | — |
**PinScope-lineage share (UPSTREAM + DERIVED + REPLACEMENT):** 63.4% of files, 79.4% of lines.
**New / vendored share (NEW + THIRD_PARTY):** 36.6% of files, 20.7% of lines.
Architectural independence is **not** blocked by leftover strings. It **is** blocked by: (1) the still-PinScope pipeline (`periscopex``pinscopex`: graph, parsers, `validate.py`, extraction, Next.js report shell); (2) Faradworks legal/metadata identity still shipping in the UI; (3) GitHub `main` not carrying the product; (4) Clerk / Anthropic seams still in the runtime graph; (5) vendored ImpedenceFinder with **no license file and GitHub `license: null`**.
Most important to replace **first** (identity and branch topology), not the finding engine or PCB job: Faradworks terms/privacy/contact/metadata; then isolate Clerk and Anthropic; keep parsers/graph as KEEP until a later phase.
---
## 2. Method and non-goals
**Compared trees**
- Upstream: `git fetch` of `https://github.com/Faradworks/Pinscope.git` into a **local** remote named `upstream` on a disposable clone. This audit did not `git remote set-url`, force-push, rewrite history, or detach the GitHub fork.
- Product: `origin/cursor/pcb-review-plan-44dd` (also compared `origin/main` and `origin/cursor/deepseek-71c5`).
- File identity: `git ls-tree` blob SHAs.
- Text overlap: line-**set** Jaccard on `git show` text. Thresholds (fixed, not guessed): identical SHA → UPSTREAM; same path and Jaccard ≥ 0.20 **or** ≥ 20 shared lines → DERIVED; same path and below that → REPLACEMENT; path only in HEAD with identical SHA to an unmatched upstream blob, or Jaccard ≥ 0.15 against a basename / `pinscopex``periscopex` map → DERIVED/REPLACEMENT with `renamed_from`; `vendor/` → THIRD_PARTY; else NEW.
- Licenses: root `LICENSE` blob vs upstream; npm `license` field on **every** `frontend/package-lock.json` `packages` entry (827 entries, 0 missing); PyPI JSON `info.license` / `license_expression` / License classifiers for each `backend/requirements.txt` name; ImpedenceFinder GitHub API + tree + `pyproject.toml` + `LICENSE` URL. **No license was inferred from a package name.**
- Strings: ripgrep for `Pinscope`, `PinScope`, `pinscope`, `Faradworks`, `faradworks`, `SPDX`, `copyright` (lockfile excluded from hit lists). Matches were classified, not deleted.
**Classifier caveat (empty blobs):** git empty-file SHA `e69de29` is shared by `backend/__init__.py` (UPSTREAM) and three HEAD empty files. The rename heuristic tagged `backend/periscopex/__init__.py`, `tests/impedancefinder/__init__.py`, and `vendor/impedancefinder/__init__.py` as DERIVED-from-`backend/__init__.py`. That is **not** a meaningful derivation. Corrected intent: package markers NEW / THIRD_PARTY. Counts in §1 are the **raw** classifier (reproducible). Adjusted: DERIVED 124, NEW 141, THIRD_PARTY 10.
**Non-goals:** no refactors, no PCB/finding-engine edits, no license changes, no history rewrite, no “delete every PinScope line.”
---
## 3. Git history map
### 3.1 Exact upstream and remotes (GitHub, not this clone)
From `GET https://api.github.com/repos/manvalan/periscope` (2026-09-20):
| Field | Value |
| --- | --- |
| `full_name` | `manvalan/periscope` |
| `fork` | `true` |
| `parent.full_name` / `source.full_name` | `Faradworks/Pinscope` |
| `default_branch` | `main` |
| `license.spdx_id` | `AGPL-3.0` (same as parent) |
| `created_at` | 2026-07-17T16:23:01Z |
| Parent `pushed_at` | 2026-07-17T07:34:32Z |
| Fork `pushed_at` | 2026-09-20T07:13:57Z |
| Open / listed PRs | `[]` (empty) |
GitHub still records a **fork of Faradworks/Pinscope**. This audit did not detach it.
Visible `origin` branches: `main`, `cursor/deepseek-71c5`, `cursor/pcb-review-plan-44dd`. Upstream GitHub has only `main`.
### 3.2 Upstream commit list (complete — 5 commits)
| SHA | Date | Author | Subject |
| --- | --- | --- | --- |
| `6672d2be5744239bd6edfee8abc2ac43d3705f93` | 2026-07-17 | Siddharth Kothari `<Sid@faradworks.com>` | Pinscope open-source core (root commit; extracted from cloud codebase; auth/billing in private gateway) |
| `6e83ef9da021ae3a21b76bf38058f034e8dc40d9` | 2026-07-17 | Siddharth Kothari | Remove internal review case log; public support address |
| `f89ca0803f571a57703766a154d628def1fe3696` | 2026-07-17 | Siddharth Kothari | README: rewrite for the public launch |
| `a8c242933a6917aaeeade383c09f0ea998d467d0` | 2026-07-17 | Siddharth Kothari | README: add pipeline diagram |
| `b26ad3509f4a19878fb7050aad3da9eacba8d914` | 2026-07-17 | Sid / GitHub | Update README.md (demo screenshot) |
`upstream/main` **is** `b26ad35`. Fork `origin/main` contains those five plus three fork-only commits. **0** upstream commits are missing from the fork (ahead/behind `upstream/main...origin/main` = `0 3`).
### 3.3 Divergence
```
6672d2b Pinscope OSS root (Faradworks)
b26ad35 upstream/main = merge-base
├─ origin/main (+3)
│ 4888092 Docker, remove Clerk, OSS mode
│ 2c5d9d3 Document fork changes in README
│ ab1c5b0 Fix Anthropic skill references and backend git support
└─ origin/cursor/pcb-review-plan-44dd (+92 from upstream, +89 from origin/main)
48246f3 Adapt Pinscope to DeepSeek, auto datasheets, shared library
… DeepSeek, KiCad, layout F1, placement, impedance, rebrand 8d2b856,
PCB job 16c606a, finding engine dc65e96, … facbaa2 (2.32.1)
origin/cursor/deepseek-71c5 is 14 commits behind pcb-review (linear ancestor)
```
`origin/main` does **not** contain DeepSeek, `periscopex`, PCB exam, or the rebrand. A clone of default `main` is still branded Pinscope plus Docker.
### 3.4 Periscope vs PinScope authorship on product snapshot
`git log --format='%an <%ae>'` on `origin/cursor/pcb-review-plan-44dd` (97 commits including upstream):
| Count | Author |
| --- | --- |
| 88 | Michele Bigi `<mikbigi@gmail.com>` |
| 2 | manvalan `<mikbigi@gmail.com>` |
| 1 | Michele `<mikbigi@gmail.com>` |
| 1 | Cursor Agent `<cursoragent@cursor.com>` |
| 3 | Siddharth Kothari `<siddkot@gmail.com>` |
| 1 | Siddharth Kothari `<Sid@faradworks.com>` |
| 1 | Sid `<44057927+Monacrylic@users.noreply.github.com>` |
PinScope authors = the five upstream commits. Periscope authors = the 92 fork commits (Michele / manvalan / Cursor Agent).
### 3.5 Product commit spine (fork-only, oldest → newest groups)
Not every SHA is repeated here; `git log origin/main..origin/cursor/pcb-review-plan-44dd` is the complete 89-commit list. Grouped:
1. **DeepSeek / library** (`48246f3``30aaf55`): OpenAI-compatible provider, local skills, datasheet fetch, catalog passives, reprocess, quote verify.
2. **CAD / KiCad** (`e4fd6c3``c5b260b`): hierarchy zip, `.kicad_sch` / `.kicad_pcb` nets, cad-bridge plugin later.
3. **Deterministic schematic checks** (`b1ee445``7aed64c`): decoupling, I2C, DNP, sequencing, thermal, errata, layout_rules.
4. **Impedance / antenna** (`6fc2ac5``556306b`): vendored ImpedenceFinder, RF tab.
5. **Auth** (`5a69b38`): local multi-user JWT (PinScope-named originally).
6. **Layout F1 / Placement F2** (`2950446``86121e6`): domains, satellites, gated xy pack.
7. **Rebrand** (`8d2b856`, `dc364f5`): Pinscope → Periscope, `pinscopex``periscopex`, `PS-*``PE-*`.
8. **Deploy** (`6b0e44a``344ecb3`): `/root/periscope`, Docker networks `pinscope_pinscope` / `pinscope_periscope`.
9. **PCB exam + finding engine** (`721ede4``facbaa2`): MODE=pcb, shared `library/extracted`, FACT/REQUIREMENT/INFERENCE.
---
## 4. File classification vs upstream
Rules: see §2. Full per-file table: **Appendix A**. Snapshot: 404 HEAD files vs 254 upstream files.
**Unmatched upstream path still absent from HEAD:** `backend/pinscopex/__init__.py` (package renamed; other `pinscopex/*` files matched as `periscopex/*`).
**REPLACEMENT (4)** — same path, low overlap:
| Path | Jaccard | Note |
| --- | --- | --- |
| `README.md` | 0.133 | DeepSeek/Periscope README vs Pinscope launch README |
| `frontend/src/lib/auth.ts` | 0.093 | Local JWT + legacy `pinscope_token` vs open-core stub |
| `frontend/src/components/layout/sidebar-auth.tsx` | 0.105 | Local account UI vs Clerk seam stub |
| `skills/extract-pintable/SKILL.md` | 0.066 | Prompt rewritten; sibling `schema.json` / `validate.py` still DERIVED |
**THIRD_PARTY:** `vendor/impedancefinder/*` except `__init__.py` (empty-blob caveat). `SOURCE.md` cites https://github.com/manvalan/ImpedenceFinder commit `a0c8d0ec37c9a1b099082926e50a245778ec8d6e`. Wrapper `backend/periscopex/impedance.py` is NEW. Tests under `tests/impedancefinder/` are NEW (except empty `__init__` mis-tag).
**NEW (high-signal, not exhaustive):** `finding_engine.py`, PCB/placement/antenna/impedance modules, `pcb_pipeline.py` / `placement_pipeline.py`, DeepSeek/local skill/PDF ingest, local auth routers, KiCad plugin, `docs/motore-finding.md`, `docs/piano-pcb-review.md`, `scripts/update-periscope.sh`, `docker-compose.yml`.
**DERIVED core:** `backend/periscopex/{graph,models,parsers,parsers_edif,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check}.py` with Jaccard typically 0.630.98 vs `backend/pinscopex/*`. `parsers_edif.py`, `pin_function_tokens.py`, `taxonomy.py` are **identical blobs** after rename.
**UPSTREAM clusters:** `LICENSE` (identical AGPL-3.0 text), most `frontend/src` primitives / marketing assets, `frontend/public`, `skills/extract-pattern` + `extract-specs` trees, most `taxonomy/*.json`, `simple_project` netlist/BOM/PDF/design_graph, several `backend/services` and routers unchanged.
---
## 5. Architectural dependency map (PinScope → Periscope)
Strengths: **DIRECT** = still the same module/control flow; **INDIRECT** = new code that imports DIRECT modules; **WEAK** = naming, docs, compatibility aliases, unused seams; **NONE** = no PinScope type/control coupling observed.
```
PinScope OSS (pinscopex + FastAPI + Next.js + skills)
│ DIRECT
├─ Design graph / BOM / netlist parsers / taxonomy / derating
├─ LLM review loop (validate.py, validation_tools, extraction, pipeline.py)
├─ Finding Pydantic model + normalize/dedupe
├─ Next.js report / project / admin shell
├─ Clerk/billing gateway seams (stubs + remaining calls)
└─ Claude Console skills contract (manifest + upload_skills.py)
Periscope (rebrand + DeepSeek + extra jobs)
│ INDIRECT (new jobs on the same graph/library)
├─ MODE=pcb (pcb_pipeline → parsers_kicad_pcb, graph, validate._parse_review, finding_engine)
├─ MODE=placement
├─ cad-bridge / KiCad plugin
├─ finding_engine.complete_finding() wrapping models.Finding
└─ datasheet finder / Mouser / local skills (replaces Anthropic upload in practice)
WEAK
├─ pinscope_version alias, pinscopex: localStorage, pinscope_token, JWT issuer pinscope-local
├─ Docker network names pinscope_*
├─ Faradworks terms/privacy/metadata/Twitter
└─ CLAUDE.md still describing Console Skills while README forbids Anthropic
NONE (new engines, still may import geometry types)
├─ antenna_geometry / antenna_rf
└─ vendor ImpedenceFinder (third party) via vendor_path
```
| Subsystem | Strength | Evidence |
| --- | --- | --- |
| `periscopex.graph` / `parsers*` / `models` / `taxonomy` | DIRECT | Rename + high Jaccard; `pipeline.py` still `build_graph` / `parse_bom` / `parse_netlist_any` |
| `periscopex.validate` / `validation_tools` / `services/validation.py` | DIRECT | Same per-IC datasheet review; `complete_findings` grafted at end of validate |
| `services/pipeline.py` + `pipeline_worker.py` | DIRECT | Same stage machine; provider swapped |
| `finding_engine.py` | INDIRECT | NEW; imports `models.Finding`; seeds `PE-*`; used by `validate.py` and `pcb_checks.py` |
| PCB exam (`pcb_pipeline`, `pcb_*`) | INDIRECT | NEW; `ensure_graph` uses PinScope graph; AI path reuses `_parse_review` |
| Placement F2 | INDIRECT | NEW on `functional_groups` + graph |
| Impedance tab | INDIRECT + THIRD_PARTY | NEW facade; solver from vendor |
| Frontend report / wizard | DIRECT / DERIVED | Same App Router tree; FindingCard extended |
| Local auth | REPLACEMENT + WEAK | Replaces Clerk for self-host; Clerk still in `middleware/auth.py`, admin, email |
| Legal content | DIRECT | `terms.md` / `privacy.md` still Faradworks, Inc. |
| `skills/extract-pattern`, `extract-specs` | DIRECT | UPSTREAM blobs |
| `skills/extract-pintable/SKILL.md` | REPLACEMENT | Contract (schema/validate) still DERIVED |
| `scripts/upload_skills.py` | DIRECT | UPSTREAM; README on product branch says do not run it |
| Docker / `update-periscope.sh` | WEAK / NONE | New deploy; leftover `pinscope` host/network names |
| `LICENSE` | DIRECT | Identical AGPL-3.0 |
PCB/finding-engine **code was not modified** for this audit. Observation only: PCB is already a second job, but it is not architecturally independent of PinScopes graph, pintable cache, or review parser.
---
## 6. Package and lockfile licenses
Root project license: **GNU AGPL v3** text, blob-identical to upstream (`LICENSE` = UPSTREAM). README on the product branch: “AGPL-3.0, same as upstream Pinscope (Faradworks). For commercial licensing of the original, write to dev@faradworks.com.” GitHub API also reports `AGPL-3.0` on both repos. No `SPDX-License-Identifier` headers in the tree (0 files).
There is **no** Python lockfile (`poetry.lock` / `uv.lock` / `requirements.lock`). Versions below are **declared ranges** plus **PyPIs current JSON license field** (fetched 2026-09-20), not the exact installed wheel in a given deploy.
### 6.1 `backend/requirements.txt` (direct)
| Requirement | PyPI `info.license` (as returned) | Classifiers / notes |
| --- | --- | --- |
| fastapi | MIT | |
| uvicorn | BSD-3-Clause | |
| openai | Apache-2.0 | OSI Apache; **added on fork** |
| anthropic | MIT | still declared; product README says do not use Anthropic |
| google-genai | Apache-2.0 | |
| pydantic | MIT | |
| pydantic-settings | MIT | OSI MIT |
| python-multipart | Apache-2.0 | |
| sse-starlette | BSD-3-Clause | |
| python-dotenv | BSD-3-Clause | |
| openpyxl | MIT | |
| google-cloud-storage | Apache-2.0 | |
| google-cloud-run | Apache-2.0 | |
| google-api-python-client | `Apache 2.0` (PyPI spelling, not SPDX token) | OSI Apache |
| pypdf | BSD-3-Clause | |
| pymupdf | `Dual Licensed - GNU AFFERO GPL 3.0 or Artifex Commercial License` | **added on fork**; dual license from PyPI, not inferred |
| PyJWT | MIT | extras `[crypto]` |
| cryptography | Apache-2.0 OR BSD-3-Clause | |
| httpx | BSD-3-Clause | **added on fork** |
| packaging | Apache-2.0 OR BSD-2-Clause | |
| shapely | `BSD 3-Clause` (PyPI spelling) | OSI BSD; **added on fork** (ImpedenceFinder) |
| PyYAML | MIT | **added on fork** |
Transitive Python licenses were **not** enumerated (no lockfile). Marked INVESTIGATE.
### 6.2 Frontend direct dependencies (`package-lock.json` `packages["node_modules/<name>"]`)
Lockfile version field is `2.10.0` while `package.json` is `2.26.3` (quality note §8). Every lock `packages` entry has a `license` field (827/827).
| Package | Locked version | `license` field |
| --- | --- | --- |
| @base-ui/react | 1.3.0 | MIT |
| @xyflow/react | 12.10.2 | MIT |
| @types/dagre | 0.7.54 | MIT |
| class-variance-authority | 0.7.1 | Apache-2.0 |
| clsx | 2.1.1 | MIT |
| dagre | 0.8.5 | MIT |
| lucide-react | 1.8.0 | ISC |
| next | 16.2.3 | MIT |
| next-themes | 0.4.6 | MIT |
| react / react-dom | 19.2.4 | MIT |
| react-markdown | 10.1.0 | MIT |
| react-pdf | 10.4.1 | MIT |
| shadcn | 4.2.0 | MIT |
| tailwind-merge | 3.5.0 | MIT |
| tw-animate-css | 1.4.0 | MIT |
| xlsx | 0.18.5 | Apache-2.0 |
| @tailwindcss/postcss / tailwindcss | 4.2.2 | MIT |
| typescript | 5.9.3 | Apache-2.0 |
| eslint / eslint-config-next | 9.39.4 / 16.2.3 | MIT |
| @types/node | 20.19.39 | MIT |
| @types/react / react-dom | 19.2.14 / 19.2.3 | MIT |
### 6.3 Transitive npm license histogram (lockfile fields only)
MIT 684; ISC 44; Apache-2.0 43; MPL-2.0 13 (e.g. `axe-core`, `lightningcss*`); BSD-2-Clause 11; LGPL-3.0-or-later 10 (`@img/sharp-libvips-*`); BSD-3-Clause 9; BlueOak-1.0.0 3; Apache-2.0 AND LGPL-3.0-or-later 3; 0BSD 2; plus one each of Apache-2.0 AND LGPL-3.0-or-later AND MIT, Python-2.0 (`argparse`), CC-BY-4.0 (`caniuse-lite`), CC0-1.0, (MIT OR CC0-1.0).
**Copyleft transitive (frontend):** LGPL-3.0-or-later via sharp/libvips optional platform packages; MPL-2.0 via lightningcss/axe-core. Not removed; recorded.
### 6.4 Vendored ImpedenceFinder
| Probe | Result |
| --- | --- |
| GitHub `license` on `manvalan/ImpedenceFinder` | JSON `license: null` |
| `LICENSE` at vendored commit `a0c8d0ec…` | HTTP 404 |
| `pyproject.toml` on default `main` | **no** `[project].license` / license-files |
| README | **no** license section (Italian functional README) |
| Vendored Python headers | describe KiCad formula provenance; **no** SPDX, **no** copyright line |
License of the vendored snapshot is **UNKNOWN from evidence**. Do not treat as MIT/AGPL by name. INVESTIGATE before any additional distribution story.
---
## 7. String search: Pinscope / PinScope / pinscope / Faradworks / SPDX / copyright
**Do not remove these in Phase 0.** Classification only.
| Needle | Files | Match lines (approx) | Class of hits |
| --- | --- | --- | --- |
| `PinScope` | 0 | 0 | none |
| `SPDX` | 0 | 0 | none in source |
| `copyright` / `Copyright` | 1 (`LICENSE`) | AGPL template + FSF | UPSTREAM license text |
| `Pinscope` | 3 | 4 | README license line; changelog rebrand notes; deploy CORS comment |
| `pinscope` (any case via listing) | 9 | 30 | legacy IDs, paths, Docker networks, clone URL |
| `Faradworks` | 8 | 67 | terms, privacy, marketing, layout metadata, footers |
| `faradworks` | 7 | 21 | `dev@faradworks.com`, faradworks.com, `@getFaradWorks` |
### 7.1 Compatibility / ops (KEEP or ISOLATE — still serving a migration)
| Location | Hit | Role |
| --- | --- | --- |
| `backend/services/projects.py` | `pinscope_version` alias | Reads old `project.json` |
| `frontend/src/lib/api.ts` | `p.pinscope_version` | Same |
| `frontend/src/lib/auth.ts` | `LEGACY_TOKEN_STORAGE_KEY = "pinscope_token"` | Pre-rebrand JWT |
| `frontend/src/lib/storage-keys.ts` | still **writes** `pinscopex:*` keys | Comment says migrate; implementation still PinScope prefix |
| `scripts/update-periscope.sh` / `docker-compose.yml` | `pinscope_pinscope`, `pinscope.michelebigi.it`, clone `manvalan/pinscope` | Live VPS topology |
| `frontend/content/changelog.md` | historical Pinscope / `PS-*` | Documentation of migration |
### 7.2 Faradworks identity still on the Periscope product UI (REPLACE for independence; not “delete PinScope code”)
| Location | Hit |
| --- | --- |
| `frontend/src/app/layout.tsx` | `authors` / `creator` / `publisher`: Faradworks; `url: https://faradworks.com` |
| `frontend/src/lib/site.ts` | `TWITTER_HANDLE = "@getFaradWorks"`; `SITE_NAME` already `"Periscope"` |
| `frontend/src/components/legal/*` | `© {year} Faradworks` |
| `frontend/content/terms.md` | Entire TOS: Faradworks, Inc. Delaware; ownership of “the Service”; `dev@faradworks.com` |
| `frontend/content/privacy.md` | Faradworks contact |
| `frontend/src/app/(marketing)/page.tsx`, `contact/*` | `dev@faradworks.com` |
| `README.md` | commercial license → `dev@faradworks.com` |
`terms.md` still defines the operator as Faradworks while `SITE_URL` defaults to `https://periscope.michelebigi.it`. That is a **product-identity coupling**, not a parser coupling.
### 7.3 © in LICENSE
FSF copyright on the AGPL **license document** itself (2007). That is expected and KEEP. No per-file `Copyright (C) Faradworks` headers were found in `.py`/`.ts` (search limited as in §2). Absence of headers ≠ absence of copyright; the fork inherits AGPL obligations from the parent work.
---
## 8. Duplicated concepts and partial migrations
| Concept | PinScope side (still present) | Periscope side | Migration state |
| --- | --- | --- | --- |
| Package name | `backend/pinscopex` gone; empty `__init__` only unmatched | `backend/periscopex` | **Done** (rename); imports updated |
| Finding IDs | changelog `PS-*` | `PE-*` in finding_engine | **Done** for new IDs; old reports not in repo |
| App version field | `pinscope_version` alias | `periscope_version` | **Partial** — dual read |
| Browser storage | `pinscopex:` keys still **produced** | comment claims migration | **Partial / inverted** |
| Auth | Clerk JWKS, Clerk admin/email, `ClerkThemeProvider` | local JWT `AUTH_JWT_SECRET` | **Partial** — two stacks |
| LLM | `anthropic` dep, `upload_skills.py`, CLAUDE.md Console Skills | DeepSeek + `local_skill.py` + README “do not use Anthropic” | **Partial** |
| Review findings | `validate._parse_review`, normalize/dedupe | `finding_engine.complete_finding` | **Layered**, not replaced |
| Jobs | `MODE=run` analysis | `MODE=pcb`, `MODE=placement` | **Additive** three mutexed jobs |
| Default branch vs product | GitHub `main` = Docker Pinscope OSS | pcb-review = 2.32.1 | **Not merged** |
| Docker networks | `pinscope_pinscope` (Caddy) | `pinscope_periscope` / aliases | **Partial** rename |
| Legal | Faradworks TOS/privacy | SITE_NAME Periscope | **Partial** |
| Changelog vs lockfile | `package-lock.json` `"version": "2.10.0"` | `package.json` `2.26.3`, changelog 2.32.1 | **Drift** |
| Impedance | (none upstream) | vendor + `periscopex.impedance` | NEW + THIRD_PARTY |
| Open-core seams | `billing_hook`, proxy.ts, clerk-theme | OSS no-op + local auth | Stubs KEEP for API shape; identity REPLACE |
---
## 9. Technical quality observations (no rewrite proposals)
These are facts about the snapshot, not a license to rip out PinScope.
1. **Default branch is not the product.** Anyone cloning `manvalan/periscope` without a branch flag gets pre-DeepSeek Pinscope + Docker (`ab1c5b0`). Live notes refer to 2.32.1 on the PCB branch.
2. **Docs disagree:** `CLAUDE.md` (DERIVED, Jaccard 0.67) still titles Periscope but describes Claude Console Skills as required. Product `README.md` (REPLACEMENT) says skills are local and Anthropic must not be used.
3. **Finding engine is a clamp, not a new IR.** `validate.py` still produces `Finding` objects; `complete_findings` fills FACT/REQUIREMENT/INFERENCE. Replacing PinScope review in one shot would discard the working schematic loop.
4. **PCB job correctly refuses auto-place** (comments in `pcb_pipeline.py`) but **depends** on schematic graph + pintable cache (“run schematic review first”). That is coupling, not a bug by itself.
5. **Empty `__init__.py` files** share one git blob; mechanical rename detection over-claims derivation (§2).
6. **`frontend/package-lock.json` is almost identical to upstream** (Jaccard 0.999) while app version moved to 2.26.3/2.32.1 — lock **name-version** stale; dependency licenses still readable from the lock.
7. **PyMuPDF dual AGPL/commercial** on PyPI is a distribution constraint for binaries/SaaS (AGPL network clause already applies via root LICENSE). Recorded, not changed.
8. **ImpedenceFinder** has no license metadata. KiCad formula comments are provenance of **math**, not a license grant.
9. **xlsx@0.18.5** lock field is Apache-2.0 (not guessed from “SheetJS”).
10. **Auth production guard** still mentions Clerk (`backend/main.py`, `update-periscope.sh` dies if `ENVIRONMENT=production` without Clerk). Local JWT exists but deploy script language is mixed.
11. **No per-source-file copyright/SPDX** — harder provenance later; AGPL still applies to the combined work.
12. Tests for NEW modules are substantial (`tests/test_pcb_review.py`, `test_finding_engine.py`, etc.) — quality of *additions* is high relative to unmerged `main`.
---
## 10. Disposition (KEEP / ISOLATE / REPLACE / REMOVE / INVESTIGATE)
Independence means Periscope can evolve its product, legal identity, and review contract without pretending to be Faradworks/Pinscope. It does **not** mean deleting `graph.py`.
| Area | Action | Why |
| --- | --- | --- |
| AGPL `LICENSE` blob | **KEEP** | Identical upstream license; changing it is out of scope and would not create independence |
| Parsers, graph, taxonomy, BOM, derating, EDIF | **KEEP** | DIRECT lineage; this *is* the schematic core; replacing is a later engineering program |
| `models.Finding` + normalize/dedupe | **KEEP** + **ISOLATE** | Keep types; route new semantics through `finding_engine` |
| `validate.py` LLM loop | **KEEP** for now; **ISOLATE** provider/skills | Working product; independence = provider + finding contract, not a greenfield reviewer |
| PCB / placement / antenna / finding_engine / KiCad plugin | **KEEP** | NEW Periscope surface |
| `vendor/impedancefinder` | **ISOLATE** + **INVESTIGATE** | Boundary already in `impedance.py`; license unknown |
| Faradworks TOS, privacy, footers, layout authors, Twitter, contact email | **REPLACE** | Operator identity; largest *non-engine* coupling |
| `pinscope_version` / JWT issuer / CORS host aliases | **ISOLATE** then expire | Needed for live data; not the architecture |
| `storage-keys.ts` still writing `pinscopex:` | **REPLACE** (write path) after dual-read | Partial migration bug-shaped |
| Clerk seams (`clerk-theme`, JWKS, admin Clerk API, email) | **ISOLATE**; **REMOVE** only when local auth is the only supported mode | Dual stack |
| `anthropic` dependency, `upload_skills.py`, Console-oriented CLAUDE.md | **ISOLATE**; **REMOVE** later if DeepSeek-only is policy | README already forbids Anthropic |
| GitHub `main` vs pcb-review | **INVESTIGATE** (process) | Merge/publish strategy; do not detach fork |
| PyMuPDF dual license + Python transitives | **INVESTIGATE** | No pip lock |
| ImpedenceFinder license from author | **INVESTIGATE** | Blocker for *clean* third-party story, not for reading code |
| `simple_project` / how-it-works.svg | **KEEP** | UPSTREAM fixtures; rebrand docs separately |
| Billing hook / gateway stubs | **KEEP** signatures; **ISOLATE** comments that imply Faradworks cloud | Open-core seam |
| PinScope *strings* in changelog | **KEEP** | Historical accuracy |
**REMOVE** is empty for Phase 0. Nothing should be deleted on the basis of this audit alone.
---
## 11. Phase 1 sequence (recommended order; not this change)
Phase 1 should increase **independence**, not maximize diff against PinScope.
1. **Land this audit** on a branch that does not rewrite engines (`docs/development/PINSCOPE_AUDIT.md` only).
2. **Branch topology:** decide how GitHub `main` relates to `cursor/pcb-review-plan-44dd` without detaching the fork (merge/PR of product onto `main`, or document that `main` is archival). Until then, every clone is the wrong tree.
3. **Operator identity:** replace Faradworks TOS/privacy/contact/metadata/Twitter/footers with Periscope operator documents; keep AGPL and a factual “derived from Faradworks/Pinscope” attribution (README already has a form of this).
4. **Compatibility fence:** one module/doc for legacy `pinscope_*` keys, JWT issuer, CORS, Docker network names; stop writing new `pinscopex:` storage keys.
5. **Auth ISOLATE:** make the self-host path (local JWT) the documented default; keep Clerk behind a named seam so admin/email do not assume Clerk.
6. **LLM ISOLATE:** document DeepSeek as the supported provider; treat Anthropic SDK + `upload_skills.py` as optional/legacy so CLAUDE.md matches README.
7. **Finding contract:** keep using `finding_engine` as the independence layer on top of `Finding`; do not rewrite `validate.py` in Phase 1.
8. **Vendor license:** obtain an explicit license for ImpedenceFinder (or stop distributing the vendor tree until known).
9. **Python lock:** add a lockfile so transitive licenses can be audited like npm.
10. **Only then** consider isolating parsers/graph behind a narrower Periscope API — a large KEEP surface, not a Phase 1 delete.
---
## 12. Unresolved questions and evidence gaps
1. **Intent of GitHub `main`:** accidental staleness vs deliberate “OSS mirror of upstream + Docker”? No PRs exist to explain it.
2. **Legal operator of periscope.michelebigi.it** vs documents that still say Faradworks, Inc. — this audit cannot assign a legal entity.
3. **ImpedenceFinder license** (author, KiCad cpp port implications). GitHub API `license` is null.
4. **PyMuPDF** AGPL vs Artifex commercial: which grant is used in the VPS deploy?
5. **Python transitive licenses** without a lockfile.
6. **Whether Anthropic is still reachable** if env keys exist despite README (code path exists; runtime not executed here).
7. **Private Faradworks gateway** files mentioned in upstream CLAUDE.md — not in this repo; cannot audit what the parent still replaces in cloud builds.
8. **Trademark** “Pinscope” / “Periscope” / “Faradworks”: search found **use**, not a registration database. No legal conclusion.
9. **Empty-file DERIVED tags** — see §2; raw vs adjusted counts.
10. **`origin` on the analyst clone** included a local `upstream` remote for fetch-only comparison. GitHub fork remotes were not edited; confirm live GitHub still has `parent: Faradworks/Pinscope` after this docs-only PR.
11. **mbparks/pinscope** (Arduino) is a **different project**; zero code overlap observed; ignored.
12. **Exact installed npm/pip versions on production** (`/root/periscope`) were not inspected; licenses are from lock + PyPI JSON as of 2026-09-20.
---
## Appendix A — Per-file classification
Evidence strings are mechanical (SHA / Jaccard / path). Empty-blob rename caveat applies to three `__init__.py` files listed as DERIVED.
### UPSTREAM (125)
| Path | Evidence |
| --- | --- |
| `.github/workflows/ci.yml` | identical blob SHA |
| `LICENSE` | identical blob SHA |
| `backend/__init__.py` | identical blob SHA |
| `backend/middleware/__init__.py` | identical blob SHA |
| `backend/routers/__init__.py` | identical blob SHA |
| `backend/routers/deps.py` | identical blob SHA |
| `backend/routers/feedback.py` | identical blob SHA |
| `backend/routers/survey.py` | identical blob SHA |
| `backend/services/__init__.py` | identical blob SHA |
| `backend/services/admin_settings.py` | identical blob SHA |
| `backend/services/billing_hook.py` | identical blob SHA |
| `backend/services/purple_parts.py` | identical blob SHA |
| `backend/services/storage.py` | identical blob SHA |
| `backend/services/storage_gcs.py` | identical blob SHA |
| `backend/services/survey.py` | identical blob SHA |
| `docs/how-it-works.svg` | identical blob SHA |
| `frontend/.gitignore` | identical blob SHA |
| `frontend/AGENTS.md` | identical blob SHA |
| `frontend/README.md` | identical blob SHA |
| `frontend/components.json` | identical blob SHA |
| `frontend/eslint.config.mjs` | identical blob SHA |
| `frontend/postcss.config.mjs` | identical blob SHA |
| `frontend/public/datasheet.gif` | identical blob SHA |
| `frontend/public/derating.png` | identical blob SHA |
| `frontend/public/eda-logos/altium.svg` | identical blob SHA |
| `frontend/public/eda-logos/autodesk.svg` | identical blob SHA |
| `frontend/public/eda-logos/cadence.svg` | identical blob SHA |
| `frontend/public/eda-logos/easyeda.svg` | identical blob SHA |
| `frontend/public/eda-logos/kicad.svg` | identical blob SHA |
| `frontend/public/eda-logos/orcad.svg` | identical blob SHA |
| `frontend/public/eda-logos/siemens.svg` | identical blob SHA |
| `frontend/public/examples/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf` | identical blob SHA |
| `frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.asc` | identical blob SHA |
| `frontend/public/examples/TI-MSP-KICAD9-TUTORIAL.csv` | identical blob SHA |
| `frontend/public/faradworks-logo-white.png` | identical blob SHA |
| `frontend/public/favicon_io/android-chrome-192x192.png` | identical blob SHA |
| `frontend/public/favicon_io/android-chrome-512x512.png` | identical blob SHA |
| `frontend/public/favicon_io/apple-touch-icon.png` | identical blob SHA |
| `frontend/public/favicon_io/favicon-16x16.png` | identical blob SHA |
| `frontend/public/favicon_io/favicon-32x32.png` | identical blob SHA |
| `frontend/public/favicon_io/favicon.ico` | identical blob SHA |
| `frontend/public/file.svg` | identical blob SHA |
| `frontend/public/globe.svg` | identical blob SHA |
| `frontend/public/next.svg` | identical blob SHA |
| `frontend/public/power-tree.gif` | identical blob SHA |
| `frontend/public/report.png` | identical blob SHA |
| `frontend/public/vercel.svg` | identical blob SHA |
| `frontend/public/window.svg` | identical blob SHA |
| `frontend/scripts/sync-version.mjs` | identical blob SHA |
| `frontend/src/app/(app)/api/graph/[id]/route.ts` | identical blob SHA |
| `frontend/src/app/(app)/api/projects/route.ts` | identical blob SHA |
| `frontend/src/app/(app)/api/report/[id]/route.ts` | identical blob SHA |
| `frontend/src/app/(marketing)/contact/contact-form.tsx` | identical blob SHA |
| `frontend/src/app/(marketing)/layout.tsx` | identical blob SHA |
| `frontend/src/app/favicon.ico` | identical blob SHA |
| `frontend/src/app/globals.css` | identical blob SHA |
| `frontend/src/app/layout.tsx` | identical blob SHA |
| `frontend/src/app/robots.ts` | identical blob SHA |
| `frontend/src/app/sitemap.ts` | identical blob SHA |
| `frontend/src/app/twitter-image.tsx` | identical blob SHA |
| `frontend/src/components/analytics/reddit-pixel-match-keys.tsx` | identical blob SHA |
| `frontend/src/components/analytics/reddit-pixel.tsx` | identical blob SHA |
| `frontend/src/components/billing/credits-context.tsx` | identical blob SHA |
| `frontend/src/components/billing/paused-run-banner.tsx` | identical blob SHA |
| `frontend/src/components/feedback/feedback-dialog.tsx` | identical blob SHA |
| `frontend/src/components/legal/file-guide-page.tsx` | identical blob SHA |
| `frontend/src/components/marketing/pricing-section.tsx` | identical blob SHA |
| `frontend/src/components/pdf/pdf-viewer-sheet.tsx` | identical blob SHA |
| `frontend/src/components/progress/pipeline-stepper.tsx` | identical blob SHA |
| `frontend/src/components/report/finding-comments.tsx` | identical blob SHA |
| `frontend/src/components/report/mention-input.tsx` | identical blob SHA |
| `frontend/src/components/report/reviewed-section.tsx` | identical blob SHA |
| `frontend/src/components/report/status-badge.tsx` | identical blob SHA |
| `frontend/src/components/theme/clerk-theme-provider.tsx` | identical blob SHA |
| `frontend/src/components/theme/theme-provider.tsx` | identical blob SHA |
| `frontend/src/components/theme/theme-toggle.tsx` | identical blob SHA |
| `frontend/src/components/ui/alert-dialog.tsx` | identical blob SHA |
| `frontend/src/components/ui/badge.tsx` | identical blob SHA |
| `frontend/src/components/ui/button.tsx` | identical blob SHA |
| `frontend/src/components/ui/card.tsx` | identical blob SHA |
| `frontend/src/components/ui/collapsible.tsx` | identical blob SHA |
| `frontend/src/components/ui/dialog.tsx` | identical blob SHA |
| `frontend/src/components/ui/dropdown-menu.tsx` | identical blob SHA |
| `frontend/src/components/ui/input.tsx` | identical blob SHA |
| `frontend/src/components/ui/label.tsx` | identical blob SHA |
| `frontend/src/components/ui/progress.tsx` | identical blob SHA |
| `frontend/src/components/ui/scroll-area.tsx` | identical blob SHA |
| `frontend/src/components/ui/select.tsx` | identical blob SHA |
| `frontend/src/components/ui/separator.tsx` | identical blob SHA |
| `frontend/src/components/ui/sheet.tsx` | identical blob SHA |
| `frontend/src/components/ui/skeleton.tsx` | identical blob SHA |
| `frontend/src/components/ui/tabs.tsx` | identical blob SHA |
| `frontend/src/components/ui/textarea.tsx` | identical blob SHA |
| `frontend/src/components/ui/toast.tsx` | identical blob SHA |
| `frontend/src/components/ui/tooltip.tsx` | identical blob SHA |
| `frontend/src/hooks/use-auth-api.ts` | identical blob SHA |
| `frontend/src/lib/mock-data.ts` | identical blob SHA |
| `frontend/src/lib/utils.ts` | identical blob SHA |
| `frontend/src/proxy.ts` | identical blob SHA |
| `frontend/tsconfig.json` | identical blob SHA |
| `scripts/backfill_passive_subtype.py` | identical blob SHA |
| `scripts/clear_rules_from_extractions.py` | identical blob SHA |
| `scripts/gc_orphan_blobs.py` | identical blob SHA |
| `scripts/migrate_datasheets_to_blobs.py` | identical blob SHA |
| `scripts/migrate_datasheets_to_library.py` | identical blob SHA |
| `scripts/upload_skills.py` | identical blob SHA |
| `simple_project/TI-MSP-KICAD9-TUTORIAL-SCHEMATIC.pdf` | identical blob SHA |
| `simple_project/TI-MSP-KICAD9-TUTORIAL.asc` | identical blob SHA |
| `simple_project/TI-MSP-KICAD9-TUTORIAL.csv` | identical blob SHA |
| `simple_project/design_graph.json` | identical blob SHA |
| `skills/extract-pattern/SKILL.md` | identical blob SHA |
| `skills/extract-pattern/schema.json` | identical blob SHA |
| `skills/extract-pattern/validate.py` | identical blob SHA |
| `skills/extract-specs/SKILL.md` | identical blob SHA |
| `skills/extract-specs/schema.json` | identical blob SHA |
| `skills/extract-specs/validate.py` | identical blob SHA |
| `taxonomy/connector.json` | identical blob SHA |
| `taxonomy/discrete.json` | identical blob SHA |
| `taxonomy/fuse.json` | identical blob SHA |
| `taxonomy/ic.json` | identical blob SHA |
| `taxonomy/switch.json` | identical blob SHA |
| `taxonomy/test_point.json` | identical blob SHA |
| `taxonomy/transformer.json` | identical blob SHA |
| `tests/__init__.py` | identical blob SHA |
| `tests/test_cache_breakpoint_limit.py` | identical blob SHA |
### DERIVED (127)
| Path | Evidence |
| --- | --- |
| `.dockerignore` | same path; line-set Jaccard=0.857; shared_lines=12; head_lines=13; upstream_lines=13 |
| `.gitignore` | same path; line-set Jaccard=0.963; shared_lines=52; head_lines=54; upstream_lines=52 |
| `CLAUDE.md` | same path; line-set Jaccard=0.669; shared_lines=89; head_lines=111; upstream_lines=111 |
| `backend/.env.example` | same path; line-set Jaccard=0.236; shared_lines=26; head_lines=77; upstream_lines=59 |
| `backend/CLAUDE.md` | same path; line-set Jaccard=0.847; shared_lines=111; head_lines=121; upstream_lines=121 |
| `backend/Dockerfile` | same path; line-set Jaccard=0.600; shared_lines=18; head_lines=25; upstream_lines=23 |
| `backend/_version.py` | same path; line-set Jaccard=0.812; shared_lines=26; head_lines=29; upstream_lines=29 |
| `backend/config.py` | same path; line-set Jaccard=0.628; shared_lines=172; head_lines=261; upstream_lines=185 |
| `backend/main.py` | same path; line-set Jaccard=0.715; shared_lines=108; head_lines=138; upstream_lines=121 |
| `backend/middleware/auth.py` | same path; line-set Jaccard=0.372; shared_lines=42; head_lines=84; upstream_lines=71 |
| `backend/periscopex/__init__.py` | identical blob to upstream backend/__init__.py (rename/move) renamed_from=`backend/__init__.py` |
| `backend/periscopex/bom_summary.py` | path absent; matched upstream backend/pinscopex/bom_summary.py Jaccard=0.9231; shared_lines=72 renamed_from=`backend/pinscopex/bom_summary.py` |
| `backend/periscopex/derating.py` | path absent; matched upstream backend/pinscopex/derating.py Jaccard=0.6358; shared_lines=96 renamed_from=`backend/pinscopex/derating.py` |
| `backend/periscopex/graph.py` | path absent; matched upstream backend/pinscopex/graph.py Jaccard=0.7812; shared_lines=275 renamed_from=`backend/pinscopex/graph.py` |
| `backend/periscopex/led_current_check.py` | path absent; matched upstream backend/pinscopex/led_current_check.py Jaccard=0.983; shared_lines=231 renamed_from=`backend/pinscopex/led_current_check.py` |
| `backend/periscopex/models.py` | path absent; matched upstream backend/pinscopex/models.py Jaccard=0.7241; shared_lines=252 renamed_from=`backend/pinscopex/models.py` |
| `backend/periscopex/parsers.py` | path absent; matched upstream backend/pinscopex/parsers.py Jaccard=0.6929; shared_lines=185 renamed_from=`backend/pinscopex/parsers.py` |
| `backend/periscopex/parsers_edif.py` | identical blob to upstream backend/pinscopex/parsers_edif.py (rename/move) renamed_from=`backend/pinscopex/parsers_edif.py` |
| `backend/periscopex/pin_function_tokens.py` | identical blob to upstream backend/pinscopex/pin_function_tokens.py (rename/move) renamed_from=`backend/pinscopex/pin_function_tokens.py` |
| `backend/periscopex/pin_mux_check.py` | path absent; matched upstream backend/pinscopex/pin_mux_check.py Jaccard=0.9379; shared_lines=136 renamed_from=`backend/pinscopex/pin_mux_check.py` |
| `backend/periscopex/resolve_passives.py` | path absent; matched upstream backend/pinscopex/resolve_passives.py Jaccard=0.9652; shared_lines=388 renamed_from=`backend/pinscopex/resolve_passives.py` |
| `backend/periscopex/taxonomy.py` | identical blob to upstream backend/pinscopex/taxonomy.py (rename/move) renamed_from=`backend/pinscopex/taxonomy.py` |
| `backend/periscopex/utils.py` | path absent; matched upstream backend/pinscopex/utils.py Jaccard=0.8824; shared_lines=15 renamed_from=`backend/pinscopex/utils.py` |
| `backend/periscopex/validate.py` | path absent; matched upstream backend/pinscopex/validate.py Jaccard=0.8792; shared_lines=793 renamed_from=`backend/pinscopex/validate.py` |
| `backend/periscopex/validation_tools.py` | path absent; matched upstream backend/pinscopex/validation_tools.py Jaccard=0.7846; shared_lines=499 renamed_from=`backend/pinscopex/validation_tools.py` |
| `backend/pipeline_worker.py` | same path; line-set Jaccard=0.817; shared_lines=94; head_lines=109; upstream_lines=100 |
| `backend/requirements.txt` | same path; line-set Jaccard=0.696; shared_lines=16; head_lines=22; upstream_lines=17 |
| `backend/routers/admin.py` | same path; line-set Jaccard=0.831; shared_lines=404; head_lines=429; upstream_lines=461 |
| `backend/routers/contact.py` | same path; line-set Jaccard=0.925; shared_lines=98; head_lines=102; upstream_lines=102 |
| `backend/routers/pipeline.py` | same path; line-set Jaccard=0.467; shared_lines=236; head_lines=490; upstream_lines=251 |
| `backend/routers/projects.py` | same path; line-set Jaccard=0.696; shared_lines=626; head_lines=810; upstream_lines=716 |
| `backend/routers/reports.py` | same path; line-set Jaccard=0.551; shared_lines=150; head_lines=268; upstream_lines=154 |
| `backend/services/api_logs.py` | same path; line-set Jaccard=0.775; shared_lines=79; head_lines=100; upstream_lines=81 |
| `backend/services/cost_estimator.py` | same path; line-set Jaccard=0.961; shared_lines=292; head_lines=298; upstream_lines=298 |
| `backend/services/datasheet_store.py` | same path; line-set Jaccard=0.586; shared_lines=102; head_lines=168; upstream_lines=108 |
| `backend/services/dedupe_findings.py` | same path; line-set Jaccard=0.923; shared_lines=300; head_lines=309; upstream_lines=316 |
| `backend/services/digikey.py` | same path; line-set Jaccard=0.679; shared_lines=188; head_lines=256; upstream_lines=209 |
| `backend/services/email.py` | same path; line-set Jaccard=0.933; shared_lines=610; head_lines=632; upstream_lines=632 |
| `backend/services/event_bridge.py` | same path; line-set Jaccard=0.881; shared_lines=133; head_lines=145; upstream_lines=139 |
| `backend/services/extraction.py` | same path; line-set Jaccard=0.772; shared_lines=563; head_lines=711; upstream_lines=581 |
| `backend/services/job_runner.py` | same path; line-set Jaccard=0.744; shared_lines=203; head_lines=260; upstream_lines=216 |
| `backend/services/llm/__init__.py` | same path; line-set Jaccard=0.838; shared_lines=31; head_lines=34; upstream_lines=34 |
| `backend/services/llm/anthropic_provider.py` | same path; line-set Jaccard=0.911; shared_lines=276; head_lines=297; upstream_lines=282 |
| `backend/services/llm/base.py` | same path; line-set Jaccard=0.897; shared_lines=70; head_lines=73; upstream_lines=75 |
| `backend/services/llm/factory.py` | same path; line-set Jaccard=0.806; shared_lines=54; head_lines=62; upstream_lines=59 |
| `backend/services/llm/gemini_provider.py` | same path; line-set Jaccard=0.958; shared_lines=276; head_lines=283; upstream_lines=281 |
| `backend/services/llm/pricing.py` | same path; line-set Jaccard=0.742; shared_lines=66; head_lines=85; upstream_lines=70 |
| `backend/services/llm/types.py` | same path; line-set Jaccard=0.883; shared_lines=68; head_lines=75; upstream_lines=70 |
| `backend/services/normalize_findings.py` | same path; line-set Jaccard=0.945; shared_lines=430; head_lines=440; upstream_lines=445 |
| `backend/services/pipeline.py` | same path; line-set Jaccard=0.657; shared_lines=1157; head_lines=1569; upstream_lines=1350 |
| `backend/services/projects.py` | same path; line-set Jaccard=0.630; shared_lines=511; head_lines=789; upstream_lines=533 |
| `backend/services/validation.py` | same path; line-set Jaccard=0.816; shared_lines=738; head_lines=863; upstream_lines=779 |
| `backend/skills_manifest.json` | same path; line-set Jaccard=0.417; shared_lines=10; head_lines=17; upstream_lines=17 |
| `frontend/CLAUDE.md` | same path; line-set Jaccard=0.882; shared_lines=75; head_lines=80; upstream_lines=80 |
| `frontend/content/changelog.md` | same path; line-set Jaccard=0.198; shared_lines=58; head_lines=285; upstream_lines=66 |
| `frontend/content/file-guide.md` | same path; line-set Jaccard=0.702; shared_lines=87; head_lines=107; upstream_lines=104 |
| `frontend/content/privacy.md` | same path; line-set Jaccard=0.943; shared_lines=100; head_lines=103; upstream_lines=103 |
| `frontend/content/terms.md` | same path; line-set Jaccard=0.936; shared_lines=176; head_lines=182; upstream_lines=182 |
| `frontend/next.config.ts` | same path; line-set Jaccard=0.710; shared_lines=44; head_lines=59; upstream_lines=47 |
| `frontend/package-lock.json` | same path; line-set Jaccard=0.999; shared_lines=4170; head_lines=4172; upstream_lines=4172 |
| `frontend/package.json` | same path; line-set Jaccard=0.953; shared_lines=41; head_lines=42; upstream_lines=42 |
| `frontend/public/favicon_io/site.webmanifest` | same path; line-set Jaccard=0.789; shared_lines=15; head_lines=17; upstream_lines=17 |
| `frontend/src/app/(app)/admin/page.tsx` | same path; line-set Jaccard=0.983; shared_lines=1188; head_lines=1205; upstream_lines=1191 |
| `frontend/src/app/(app)/dashboard/page.tsx` | same path; line-set Jaccard=0.980; shared_lines=300; head_lines=303; upstream_lines=303 |
| `frontend/src/app/(app)/feedback/page.tsx` | same path; line-set Jaccard=0.981; shared_lines=102; head_lines=103; upstream_lines=103 |
| `frontend/src/app/(app)/layout.tsx` | same path; line-set Jaccard=0.545; shared_lines=18; head_lines=27; upstream_lines=24 |
| `frontend/src/app/(app)/project/[id]/page.tsx` | same path; line-set Jaccard=0.757; shared_lines=758; head_lines=967; upstream_lines=792 |
| `frontend/src/app/(app)/project/[id]/progress/page.tsx` | same path; line-set Jaccard=0.729; shared_lines=266; head_lines=346; upstream_lines=285 |
| `frontend/src/app/(app)/project/[id]/report/page.tsx` | same path; line-set Jaccard=0.721; shared_lines=271; head_lines=362; upstream_lines=285 |
| `frontend/src/app/(marketing)/changelog/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
| `frontend/src/app/(marketing)/contact/actions.ts` | same path; line-set Jaccard=0.949; shared_lines=37; head_lines=38; upstream_lines=38 |
| `frontend/src/app/(marketing)/contact/nav.tsx` | same path; line-set Jaccard=0.918; shared_lines=56; head_lines=59; upstream_lines=58 |
| `frontend/src/app/(marketing)/contact/page.tsx` | same path; line-set Jaccard=0.909; shared_lines=60; head_lines=63; upstream_lines=63 |
| `frontend/src/app/(marketing)/file-guide/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
| `frontend/src/app/(marketing)/landing-cta.tsx` | same path; line-set Jaccard=0.865; shared_lines=45; head_lines=49; upstream_lines=48 |
| `frontend/src/app/(marketing)/page.tsx` | same path; line-set Jaccard=0.977; shared_lines=340; head_lines=344; upstream_lines=344 |
| `frontend/src/app/(marketing)/privacy/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
| `frontend/src/app/(marketing)/terms/page.tsx` | same path; line-set Jaccard=0.895; shared_lines=17; head_lines=18; upstream_lines=18 |
| `frontend/src/app/opengraph-image.tsx` | same path; line-set Jaccard=0.933; shared_lines=84; head_lines=87; upstream_lines=87 |
| `frontend/src/components/dashboard/create-project-dialog.tsx` | same path; line-set Jaccard=0.849; shared_lines=1960; head_lines=2218; upstream_lines=2050 |
| `frontend/src/components/dashboard/onboarding-survey.tsx` | same path; line-set Jaccard=0.980; shared_lines=100; head_lines=101; upstream_lines=101 |
| `frontend/src/components/dashboard/project-card.tsx` | same path; line-set Jaccard=0.882; shared_lines=134; head_lines=147; upstream_lines=139 |
| `frontend/src/components/dashboard/projects-table.tsx` | same path; line-set Jaccard=0.917; shared_lines=143; head_lines=153; upstream_lines=146 |
| `frontend/src/components/layout/sidebar.tsx` | same path; line-set Jaccard=0.685; shared_lines=185; head_lines=250; upstream_lines=205 |
| `frontend/src/components/legal/changelog-timeline.tsx` | same path; line-set Jaccard=0.974; shared_lines=151; head_lines=153; upstream_lines=153 |
| `frontend/src/components/legal/legal-page.tsx` | same path; line-set Jaccard=0.983; shared_lines=116; head_lines=117; upstream_lines=117 |
| `frontend/src/components/pdf/pdf-viewer-panel.tsx` | same path; line-set Jaccard=0.992; shared_lines=257; head_lines=258; upstream_lines=258 |
| `frontend/src/components/report/component-group.tsx` | same path; line-set Jaccard=0.911; shared_lines=82; head_lines=88; upstream_lines=84 |
| `frontend/src/components/report/finding-card.tsx` | same path; line-set Jaccard=0.696; shared_lines=167; head_lines=231; upstream_lines=176 |
| `frontend/src/components/report/finding-focus-view.tsx` | same path; line-set Jaccard=0.935; shared_lines=115; head_lines=122; upstream_lines=116 |
| `frontend/src/components/report/findings-list.tsx` | same path; line-set Jaccard=0.824; shared_lines=136; head_lines=161; upstream_lines=140 |
| `frontend/src/components/report/report-filters.tsx` | same path; line-set Jaccard=0.678; shared_lines=78; head_lines=115; upstream_lines=78 |
| `frontend/src/components/report/report-summary.tsx` | same path; line-set Jaccard=0.872; shared_lines=68; head_lines=77; upstream_lines=69 |
| `frontend/src/components/upload/file-upload-zone.tsx` | same path; line-set Jaccard=0.364; shared_lines=55; head_lines=143; upstream_lines=63 |
| `frontend/src/hooks/use-optional-auth.ts` | same path; line-set Jaccard=0.198; shared_lines=24; head_lines=113; upstream_lines=32 |
| `frontend/src/hooks/use-pipeline-progress.ts` | same path; line-set Jaccard=0.953; shared_lines=202; head_lines=212; upstream_lines=202 |
| `frontend/src/hooks/use-report.ts` | same path; line-set Jaccard=0.263; shared_lines=15; head_lines=50; upstream_lines=22 |
| `frontend/src/hooks/use-reviewed-count.ts` | same path; line-set Jaccard=0.688; shared_lines=22; head_lines=31; upstream_lines=23 |
| `frontend/src/hooks/use-reviewed-findings.ts` | same path; line-set Jaccard=0.841; shared_lines=58; head_lines=67; upstream_lines=60 |
| `frontend/src/lib/api.ts` | same path; line-set Jaccard=0.726; shared_lines=605; head_lines=816; upstream_lines=622 |
| `frontend/src/lib/csp-hosts.ts` | same path; line-set Jaccard=0.500; shared_lines=7; head_lines=13; upstream_lines=8 |
| `frontend/src/lib/report-export.ts` | same path; line-set Jaccard=0.860; shared_lines=49; head_lines=56; upstream_lines=50 |
| `frontend/src/lib/site.ts` | same path; line-set Jaccard=0.895; shared_lines=51; head_lines=54; upstream_lines=54 |
| `frontend/src/lib/types.ts` | same path; line-set Jaccard=0.570; shared_lines=241; head_lines=419; upstream_lines=245 |
| `frontend/src/lib/version.ts` | same path; line-set Jaccard=0.333; shared_lines=2; head_lines=4; upstream_lines=4 |
| `scripts/dedup_library_datasheets.py` | same path; line-set Jaccard=0.975; shared_lines=78; head_lines=79; upstream_lines=79 |
| `skills/extract-pintable/schema.json` | same path; line-set Jaccard=0.522; shared_lines=35; head_lines=67; upstream_lines=35 |
| `skills/extract-pintable/validate.py` | same path; line-set Jaccard=0.416; shared_lines=57; head_lines=137; upstream_lines=57 |
| `taxonomy/crystal.json` | same path; line-set Jaccard=0.857; shared_lines=18; head_lines=21; upstream_lines=18 |
| `taxonomy/passive.json` | same path; line-set Jaccard=0.955; shared_lines=42; head_lines=43; upstream_lines=43 |
| `tests/conftest.py` | same path; line-set Jaccard=0.939; shared_lines=31; head_lines=33; upstream_lines=31 |
| `tests/impedancefinder/__init__.py` | identical blob to upstream backend/__init__.py (rename/move) renamed_from=`backend/__init__.py` |
| `tests/test_cost_estimator.py` | same path; line-set Jaccard=0.970; shared_lines=64; head_lines=65; upstream_lines=65 |
| `tests/test_cost_estimator_model_aware.py` | same path; line-set Jaccard=0.530; shared_lines=70; head_lines=94; upstream_lines=108 |
| `tests/test_datasheet_excerpt_tool.py` | same path; line-set Jaccard=0.962; shared_lines=150; head_lines=153; upstream_lines=153 |
| `tests/test_dedupe_findings.py` | same path; line-set Jaccard=0.849; shared_lines=107; head_lines=125; upstream_lines=108 |
| `tests/test_edif_parser.py` | same path; line-set Jaccard=0.970; shared_lines=129; head_lines=131; upstream_lines=131 |
| `tests/test_led_current_check.py` | same path; line-set Jaccard=0.957; shared_lines=90; head_lines=92; upstream_lines=92 |
| `tests/test_netlist_parser.py` | same path; line-set Jaccard=0.974; shared_lines=76; head_lines=77; upstream_lines=77 |
| `tests/test_normalize_findings.py` | same path; line-set Jaccard=0.869; shared_lines=153; head_lines=175; upstream_lines=154 |
| `tests/test_pin_mux_check.py` | same path; line-set Jaccard=0.828; shared_lines=154; head_lines=183; upstream_lines=157 |
| `tests/test_purple_parts_resolver.py` | same path; line-set Jaccard=0.961; shared_lines=368; head_lines=375; upstream_lines=376 |
| `tests/test_validation_concurrency.py` | same path; line-set Jaccard=0.961; shared_lines=146; head_lines=149; upstream_lines=149 |
| `tests/test_validation_deterministic_seed.py` | same path; line-set Jaccard=0.970; shared_lines=64; head_lines=65; upstream_lines=65 |
| `tests/test_validation_no_tool_recovery.py` | same path; line-set Jaccard=0.951; shared_lines=136; head_lines=140; upstream_lines=139 |
| `tests/test_validation_trace_log.py` | same path; line-set Jaccard=0.961; shared_lines=172; head_lines=176; upstream_lines=175 |
| `vendor/impedancefinder/__init__.py` | identical blob to upstream backend/__init__.py (rename/move) renamed_from=`backend/__init__.py` |
### REPLACEMENT (4)
| Path | Evidence |
| --- | --- |
| `README.md` | same path; low overlap Jaccard=0.133; shared_lines=10; head_lines=54; upstream_lines=31 |
| `frontend/src/components/layout/sidebar-auth.tsx` | same path; low overlap Jaccard=0.105; shared_lines=6; head_lines=55; upstream_lines=8 |
| `frontend/src/lib/auth.ts` | same path; low overlap Jaccard=0.093; shared_lines=4; head_lines=35; upstream_lines=12 |
| `skills/extract-pintable/SKILL.md` | same path; low overlap Jaccard=0.066; shared_lines=10; head_lines=114; upstream_lines=48 |
### NEW (139)
| Path | Evidence |
| --- | --- |
| `backend/periscopex/antenna_geometry.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/antenna_rf.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/bom_match_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/cad_bridge.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/crystal_cl_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/dnp_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/errata_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/eval_report.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/filter_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/finding_engine.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/functional_groups.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/hf_coverage_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/impedance.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/impedance_traces.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/internal_features_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/layout_rules.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/library_gate.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/lifecycle.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/nc_pin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/netlist_bundle.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/parsers_kicad.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/parsers_kicad_pcb.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/passive_rail_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/pcb_checks.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/pcb_inventory.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/pcb_net_match.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/pcb_power_thermal.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/pcb_review.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/pdf_text.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/placement_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/placement_pack.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/power_margin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/quote_verify.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/review_fingerprint.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/review_workflow.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/sequencing_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/si_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/periscopex/thermal_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/routers/auth.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/routers/impedance.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/datasheet_finder.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/llm/deepseek_provider.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/llm/local_skill.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/llm/pdf_ingest.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/local_jwt.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/local_users.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/mouser.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/passive_from_distributor.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/passive_from_mpn.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/passive_from_value.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/pcb_pipeline.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/pcb_validation.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/placement_pipeline.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/services/user_directory.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `backend/vendor_path.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `docker-compose.yml` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `docs/motore-finding.md` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `docs/piano-implementazione.md` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `docs/piano-pcb-review.md` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/dockerfile` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/app/(app)/library/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/app/(app)/project/[id]/pcb/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/app/(app)/project/[id]/placement/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/app/(marketing)/sign-in/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/app/(marketing)/sign-up/page.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/components/layout/auth-gate.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/components/project/impedance-panel.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/components/project/pcb-upload.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/components/project/topology-panel.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/components/report/finding-review-controls.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/components/report/pcb-exam-section.tsx` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/hooks/use-pcb-progress.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/hooks/use-placement-progress.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/lib/layout-finding.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `frontend/src/lib/storage-keys.ts` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `plugins/__init__.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `plugins/kicad/__init__.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `plugins/kicad/focus.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `plugins/kicad/periscope_plugin.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `scripts/smoke_simple_project.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `scripts/update-periscope.sh` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `simple_project/eval_golden.json` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `simple_project/smoke_baseline.json` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/impedancefinder/conftest.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/impedancefinder/test_geometry.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/impedancefinder/test_net_walk.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/impedancefinder/test_planes.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/impedancefinder/test_zsolver.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_antenna_rf.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_bom_match_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_bom_parser.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_cad_bridge.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_crystal_cl_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_datasheet_finder.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_deepseek_only.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_deepseek_provider.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_derating.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_digikey_match.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_dnp_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_errata_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_eval_report.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_event_bridge.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_filter_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_finding_engine.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_finding_schema.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_functional_groups.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_hf_coverage_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_impedance.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_impedance_traces.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_internal_features_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_job_runner.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_kicad_parser.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_kicad_pcb.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_layout_rules.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_library.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_lifecycle_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_local_auth.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_nc_pin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_netlist_bundle.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_passive_from_distributor.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_passive_from_mpn.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_passive_from_value.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_passive_rail_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_pcb_review.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_pdf_ingest.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_placement_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_placement_pack.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_placement_pipeline.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_power_margin_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_quote_verify.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_reopen_replace.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_reprocess.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_review_fingerprint.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_review_parse.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_review_workflow.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_roadmap_integrations.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_sequencing_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_si_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
| `tests/test_thermal_check.py` | path absent from upstream/main; no identical-SHA or >=0.15 Jaccard rename match |
### THIRD_PARTY (9)
| Path | Evidence |
| --- | --- |
| `vendor/impedancefinder/SOURCE.md` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/geometry.py` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/model.py` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/net_analysis.py` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/net_walk.py` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/planes.py` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/report.py` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/stackup.schema.yml` | lives under vendor/; see SOURCE.md if present |
| `vendor/impedancefinder/zsolver.py` | lives under vendor/; see SOURCE.md if present |
### UNKNOWN (0)
| Path | Evidence |
| --- | --- |