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

74 KiB
Raw Blame History

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 (periscopexpinscopex: 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 / pinscopexperiscopex 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 (48246f330aaf55): OpenAI-compatible provider, local skills, datasheet fetch, catalog passives, reprocess, quote verify.
  2. CAD / KiCad (e4fd6c3c5b260b): hierarchy zip, .kicad_sch / .kicad_pcb nets, cad-bridge plugin later.
  3. Deterministic schematic checks (b1ee4457aed64c): decoupling, I2C, DNP, sequencing, thermal, errata, layout_rules.
  4. Impedance / antenna (6fc2ac5556306b): vendored ImpedenceFinder, RF tab.
  5. Auth (5a69b38): local multi-user JWT (PinScope-named originally).
  6. Layout F1 / Placement F2 (295044686121e6): domains, satellites, gated xy pack.
  7. Rebrand (8d2b856, dc364f5): Pinscope → Periscope, pinscopexperiscopex, PS-*PE-*.
  8. Deploy (6b0e44a344ecb3): /root/periscope, Docker networks pinscope_pinscope / pinscope_periscope.
  9. PCB exam + finding engine (721ede4facbaa2): 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.


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.


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