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.
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
# Piano — indipendenza architettonica e di licenza da PinScope
|
||||
|
||||
**Stato:** solo piano. Nessuna modifica a source di prodotto, remotes, `LICENSE`, né git history.
|
||||
**Base:** [PINSCOPE_AUDIT.md](./PINSCOPE_AUDIT.md) (snapshot prodotto `facbaa2` / `cursor/pcb-review-plan-44dd`).
|
||||
**Intento di Michele:** *lasciare* il codice PinScope e le sue licenze; **isolare** l’albero ereditato; disegnare dipendenze; marcare cosa **deve** essere riscritto. Non cancellare ogni riga ereditata.
|
||||
|
||||
Questo piano **non** stacca il fork GitHub (`manvalan/periscope` ← `Faradworks/Pinscope`). Lo stacco è un passo legale successivo, fuori da queste fasi di lavoro, salvo decisione esplicita.
|
||||
|
||||
---
|
||||
|
||||
## 1. Obiettivo
|
||||
|
||||
Due indipendenze distinte, nello stesso prodotto:
|
||||
|
||||
| Asse | Cosa significa | Cosa *non* significa |
|
||||
| --- | --- | --- |
|
||||
| **Architetturale** | Periscope evolve identity, auth, LLM, contratto finding, job PCB/placement **senza** fingere di essere Faradworks/Pinscope e senza che il nuovo codice importi il loop PinScope come se fosse “nostro” | Cancellare `graph.py`, i parser, o il 79% di linee lineage |
|
||||
| **Licenza / identità** | Operatore, TOS, privacy, metadata, vendor terzi con licenza **nota**; AGPL del fork **resta visibile** e attribuita | Relicenziare l’albero PinScope, togliere AGPL, o chiedere a Faradworks una licenza commerciale come se fossimo noi l’upstream |
|
||||
|
||||
**Criterio di fatto:** un modulo è “nativo Periscope” se può cambiare contratto (finding, PCB, auth locale, DeepSeek) senza patchare il recinto PinScope. Un modulo è “ereditato” se un `git blame`/Jaccard da audit lo lega a `pinscopex` / UI OSS Faradworks.
|
||||
|
||||
L’audit già dice: l’indipendenza **non** è bloccata dalle stringhe `pinscope`; è bloccata da pipeline, identità legale, `main` stantio, seam Clerk/Anthropic, ImpedenceFinder senza licenza.
|
||||
|
||||
---
|
||||
|
||||
## 2. Confine di isolamento (fence)
|
||||
|
||||
### 2.1 Recinto — albero ereditato (resta, non si “ripulisce”)
|
||||
|
||||
Trattare come **una dipendenza in-tree**, non come prodotto Periscope:
|
||||
|
||||
| Pacchetto logico | Path odierni | Licenza da audit |
|
||||
| --- | --- | --- |
|
||||
| Core schematico PinScope | `backend/periscopex/{graph,models,parsers,parsers_edif,validate,validation_tools,resolve_passives,derating,bom_summary,taxonomy,pin_mux_check,led_current_check,pin_function_tokens}.py` | AGPL-3.0 del fork (blob `LICENSE` identico a upstream) |
|
||||
| Orchestrazione review | `backend/services/pipeline.py`, `pipeline_worker.py`, `services/validation.py`, `services/extraction.py` (parte DIRECT) | stessa |
|
||||
| Skills Anthropic/Console | `skills/extract-pattern`, `skills/extract-specs`, `skills/extract-pintable/{schema.json,validate.py}`, `scripts/upload_skills.py`, `backend/skills_manifest.json` | stessa + contratto Claude |
|
||||
| UI OSS / marketing shell | gran parte di `frontend/src` UPSTREAM/DERIVED, `frontend/public`, `frontend/content/terms.md` + `privacy.md` (testo Faradworks) | AGPL + TOS Faradworks **nel contenuto** |
|
||||
| Gateway seams | `billing_hook.py`, `proxy.ts`, `clerk-theme-provider.tsx`, `use-optional-auth.ts`, analytics, CSP hosts | stub open-core PinScope |
|
||||
| Fixture upstream | `simple_project/` (asc/csv/pdf/`design_graph.json`), `docs/how-it-works.svg` | AGPL / contenuto upstream |
|
||||
|
||||
**Regola del recinto (quando si implementerà, non ora):**
|
||||
|
||||
- Import **solo** da una facciata stretta (es. `build_graph`, `parse_bom`, `parse_netlist_any`, `Finding` Pydantic, `validate_design_async`).
|
||||
- Nessun nuovo check PCB/schema scrive dentro `validate.py`.
|
||||
- Nessun file nativo assume Clerk, Anthropic Console, `pinscope.ai`, Faradworks Inc.
|
||||
- Il recinto **conserva** `LICENSE` AGPL e l’attribuzione “derived from Faradworks/Pinscope”.
|
||||
|
||||
Implementazione fisica futura (non in fase identità): directory tipo `vendor/pinscope/` **oppure** package `backend/pinscope_legacy/` con shim `periscopex` — solo dopo che i consumer nativi non importano più path sparsi. Non è un rename cosmetico (`pinscopex` → `periscopex` è già successo e **non** è un recinto).
|
||||
|
||||
### 2.2 Nativo Periscope (fuori recinto)
|
||||
|
||||
| Area | Path | Perché è nativo |
|
||||
| --- | --- | --- |
|
||||
| Contratto finding | `finding_engine.py`, `docs/motore-finding.md` | NEW; clamp FACT/REQUIREMENT/INFERENCE |
|
||||
| PCB exam | `pcb_pipeline.py`, `pcb_*.py`, `parsers_kicad_pcb.py`, UI `pcb/` | NEW; INDIRECT sul grafo |
|
||||
| Placement F2 | `placement_pipeline.py`, `placement_pack.py`, `functional_groups.py` | NEW |
|
||||
| RF / antenna | `antenna_*.py`, parte Impedance UI | NEW |
|
||||
| Facade Z0 | `periscopex/impedance.py` | NEW; il solver è terzi |
|
||||
| Auth self-host | `local_jwt.py`, `local_users.py`, `routers/auth.py` | REPLACEMENT |
|
||||
| LLM DeepSeek | `deepseek_provider.py`, `local_skill.py`, `pdf_ingest.py` | NEW |
|
||||
| Deploy | `scripts/update-periscope.sh`, `docker-compose.yml` | NEW (nomi `pinscope_*` ancora WEAK) |
|
||||
| Plugin KiCad | `plugins/kicad/` | NEW |
|
||||
| Check deterministici fork | `dnp_check`, `sequencing_check`, `layout_rules`, … | NEW ma **INDIRECT**: usano `models` / graph |
|
||||
|
||||
### 2.3 Terzi (né PinScope né “nostro motore”)
|
||||
|
||||
`vendor/impedancefinder/` — commit `a0c8d0ec`, licenza **UNKNOWN** (audit §6.4). Resta dietro `impedance.py` + `vendor_path.py`. Non mescolare con AGPL PinScope né con il finding engine.
|
||||
|
||||
---
|
||||
|
||||
## 3. Albero delle dipendenze
|
||||
|
||||
Forza: **DIRECT** = stesso control flow / blob lineage PinScope; **INDIRECT** = codice nativo che importa DIRECT; **WEAK** = alias, brand, reti Docker.
|
||||
|
||||
```
|
||||
Faradworks/Pinscope (AGPL-3.0)
|
||||
│
|
||||
├─ DIRECT periscopex.graph / parsers* / taxonomy / models
|
||||
│ ├─ INDIRECT pipeline MODE=run (Parse → extract → build_graph → review)
|
||||
│ ├─ INDIRECT pcb_pipeline.ensure_graph + pintable cache
|
||||
│ ├─ INDIRECT placement / functional_groups
|
||||
│ └─ INDIRECT cad_bridge, parsers_kicad*, pcb_inventory
|
||||
│
|
||||
├─ DIRECT validate.py + validation_tools + extraction skills
|
||||
│ ├─ INDIRECT finding_engine.complete_findings (graft, non sostituisce il loop)
|
||||
│ ├─ INDIRECT pcb AI (_parse_review)
|
||||
│ └─ WEAK anthropic SDK, upload_skills.py, CLAUDE.md Console
|
||||
│
|
||||
├─ DIRECT models.Finding + normalize_findings + dedupe_findings
|
||||
│ └─ INDIRECT finding_engine (PE-*, provenance clamp) + FindingCard
|
||||
│
|
||||
├─ DIRECT Next.js app (wizard, report, admin, legal pages)
|
||||
│ ├─ INDIRECT pcb-exam-section, impedance-panel, topology-panel, local sign-in
|
||||
│ └─ WEAK Faradworks metadata, pinscopex localStorage, ClerkThemeProvider
|
||||
│
|
||||
├─ DIRECT Clerk / billing_hook seams
|
||||
│ └─ INDIRECT local JWT (parallel stack, non ha spento Clerk)
|
||||
│
|
||||
└─ (non PinScope) vendor/impedancefinder
|
||||
└─ INDIRECT periscopex.impedance → tab RF
|
||||
```
|
||||
|
||||
### 3.1 Consumer Periscope × sottosistema PinScope
|
||||
|
||||
| Sottosistema PinScope | Consumer Periscope | Forza | Se si riscrive il PinScope… |
|
||||
| --- | --- | --- | --- |
|
||||
| Graph + parsers BOM/netlist | `pipeline.py`, PCB, placement, tutti i check | DIRECT / INDIRECT | Cade tutto il prodotto |
|
||||
| `validate.py` review loop | report schema, PCB AI | DIRECT | Serve un reviewer nativo **prima** di spegnere PinScope |
|
||||
| `Finding` Pydantic | `finding_engine`, UI card, cad-bridge | DIRECT | Mappare al contratto `motore-finding.md` |
|
||||
| Skills pintable/pattern/specs | extraction, library | DIRECT | Già in parte sostituiti da `local_skill.py` |
|
||||
| UI shell | ogni pagina | DIRECT | Identità e finding card prima; wizard dopo |
|
||||
| Clerk/billing | `main.py`, admin, email | DIRECT + WEAK | Auth locale già c’è |
|
||||
| Legal Faradworks | `/terms` `/privacy` layout | DIRECT contenuto | Non è codice motore |
|
||||
|
||||
**Cosa deve essere riscritto** (per indipendenza vera, fasi tarde — non per “pulizia”):
|
||||
|
||||
1. **Identità legale/UI** — obbligatorio e **primo**; non è il motore.
|
||||
2. **Seam Clerk + Anthropic** — obbligatorio per non dipendere dal cloud Faradworks.
|
||||
3. **Loop `validate.py` / `_parse_review` / tool graph** — obbligatorio per un motore *Periscope*, **dopo** identità; oggi `finding_engine` è solo un clamp.
|
||||
4. **IR `Finding`** — allineare il tipo Pydantic al contratto FACT/REQUIREMENT/INFERENCE (oggi campi doppi `finding`/`why`).
|
||||
5. **UI report/wizard** — quando il contratto finding è nativo; non nella fase identità.
|
||||
6. **Parsers/graph** — *non* obbligatori per identità; rewrite solo se si vuole un CAD layer Periscope senza AGPL PinScope (programma XL, ultimo).
|
||||
|
||||
---
|
||||
|
||||
## 4. Mappa licenze — cosa blocca una licenza Periscope “pulita”
|
||||
|
||||
“Pulita” = *Periscope come opera dell’operatore Michele, con dipendenze dichiarate*, **non** “Periscope senza AGPL”. L’albero PinScope **resta AGPL**; toglierlo richiederebbe rewrite totale del recinto (fase ultima) **e** comunque rispetto dei diritti sul codice già pubblicato.
|
||||
|
||||
| Pezzo | Evidenza audit | Blocca identità Periscope? | Blocca relicenza non-AGPL del binario combinato? |
|
||||
| --- | --- | --- | --- |
|
||||
| `LICENSE` AGPL-3.0 identico a upstream | KEEP | No, se attribuito | **Sì**, finché il recinto è nel tree/servizio |
|
||||
| TOS/privacy Faradworks, Inc. + `dev@faradworks.com` | REPLACE | **Sì** (operatore sbagliato) | No (non è la GPL) |
|
||||
| Metadata `layout.tsx` authors/publisher Faradworks, `@getFaradWorks` | REPLACE | **Sì** | No |
|
||||
| README “commercial licensing of the original → Faradworks” | tenere come *attribuzione upstream*, non come offerta nostra | Parziale | No |
|
||||
| Fork GitHub ancora `parent: Faradworks/Pinscope` | non staccare in questo piano | No (fatto) | No |
|
||||
| `anthropic` in `requirements.txt` + `upload_skills.py` | ISOLATE | Policy prodotto / ToS Anthropic vs DeepSeek | No |
|
||||
| Clerk JWKS / API | ISOLATE | ToS Clerk se usato in prod | No |
|
||||
| ImpedenceFinder `license: null`, no LICENSE file | INVESTIGATE | Distribuzione RF tab | **Sì** per *quel* vendor (sconosciuta) |
|
||||
| PyMuPDF dual AGPL *or* Artifex commercial | INVESTIGATE | Quale grant in VPS | AGPL già dal root; commerciale Artifex è altro contratto |
|
||||
| Transitive Python | no lockfile | Audit incompleto | Possibile gap |
|
||||
| npm MPL/LGPL (lightningcss, sharp-libvips) | lock fields | Attribuzione frontend | Copyleft diverso, non Faradworks |
|
||||
| `xlsx@0.18.5` Apache-2.0 nel lock | OK dichiarato | No | No |
|
||||
|
||||
**Bloccanti identità (fase A):** testi Faradworks nel prodotto.
|
||||
**Bloccanti “SaaS Periscope senza AGPL”:** l’intero recinto PinScope + PyMuPDF se si usa il grant AGPL. **Questo piano non promette relicenza.**
|
||||
**Bloccante vendor:** ImpedenceFinder senza licenza — non distribuire come “Periscope MIT/Apache”.
|
||||
|
||||
---
|
||||
|
||||
## 5. KEEP / ISOLATE / REWRITE / REMOVE
|
||||
|
||||
Sforzo: **S** (file/config), **M** (un sottosistema, test esistenti), **L** (contratto + UI + pipeline), **XL** (nuovo CAD/IR). Non è calendario.
|
||||
|
||||
| Area | Azione | Sforzo | Fase |
|
||||
| --- | --- | --- | --- |
|
||||
| Blob `LICENSE` AGPL + attribuzione PinScope | **KEEP** | — | tutte |
|
||||
| Graph, parsers, taxonomy, BOM, derating, EDIF | **KEEP** nel recinto | — | A–C; **REWRITE** solo E (XL) |
|
||||
| `simple_project`, how-it-works.svg | **KEEP** | — | |
|
||||
| Changelog storico Pinscope/`PS-*` | **KEEP** | — | |
|
||||
| PCB, placement, antenna, plugin, `finding_engine` come clamp | **KEEP** nativo | — | non toccare in A |
|
||||
| TOS, privacy, contact, footer ©, `layout.tsx` authors, Twitter | **REWRITE** (identità) | S–M | **A** |
|
||||
| `storage-keys.ts` write `pinscopex:` | **REWRITE** write path; dual-read | S | A |
|
||||
| Alias `pinscope_version`, JWT `pinscope-local`, CORS host, Docker `pinscope_*` | **ISOLATE** poi scadere | S | A |
|
||||
| Clerk stack | **ISOLATE**; **REMOVE** quando JWT locale è l’unico modo | M | B |
|
||||
| `anthropic`, `upload_skills.py`, CLAUDE.md Console | **ISOLATE**; **REMOVE** se DeepSeek-only | M | B |
|
||||
| `billing_hook` / proxy stubs | **ISOLATE** (firme); non Faradworks in copy | S | B |
|
||||
| `validate.py` + `validation_tools` + `_parse_review` | **KEEP** in A–B; **REWRITE** reviewer nativo | L | **C** |
|
||||
| `models.Finding` vs `motore-finding.md` | **REWRITE** IR (compat shim) | M–L | C |
|
||||
| `normalize_findings` / `dedupe_findings` | **KEEP** o reimplementare sul nuovo IR | M | C |
|
||||
| Skills extraction (schema) | **ISOLATE**; prompt già in parte REPLACEMENT | M | C |
|
||||
| FindingCard / report UI | **REWRITE** quando IR nativo | M–L | **D** |
|
||||
| Wizard upload / admin Clerkisms | **REWRITE** / pulizia | M | D |
|
||||
| `vendor/impedancefinder` | **ISOLATE** + licenza esplicita | S legale + M se drop | B (licenza), non engine |
|
||||
| Parsers/graph nativi Periscope | **REWRITE** solo se si esce da AGPL PinScope | XL | **E** |
|
||||
| Stacco fork GitHub | **non in questo piano** | legale | dopo E o mai |
|
||||
|
||||
**REMOVE** in A: niente codice motore. Solo copy Faradworks *dopo* TOS Periscope pronti.
|
||||
|
||||
---
|
||||
|
||||
## 6. Sequenza per fasi
|
||||
|
||||
### Fase A — Identità e recinto documentale (**non** rewriting del finding engine)
|
||||
|
||||
Allineato all’audit §11 punti 1–4: `finding_engine` / `validate.py` **non** si riscrivono qui.
|
||||
|
||||
1. Pubblicare audit + questo piano (solo docs).
|
||||
2. Decidere `origin/main` vs `cursor/pcb-review-plan-44dd` **senza** staccare il fork (merge prodotto su `main` *oppure* `main` dichiarato archivio).
|
||||
3. TOS/privacy/contact/metadata/Twitter/footer operatore Periscope; una riga di attribuzione AGPL PinScope/Faradworks (non “siamo Faradworks”).
|
||||
4. Compatibility fence: un modulo/doc per chiavi `pinscope_*`; smettere di **scrivere** `pinscopex:` in `localStorage`.
|
||||
5. Dichiarare a codice (commento/ADR) il recinto §2 — ancora senza muovere file.
|
||||
|
||||
**Done quando:** UI e legal non dicono Faradworks Inc.; AGPL e fork parent invariati; PCB/schema invariati.
|
||||
|
||||
### Fase B — Seam runtime (Clerk, Anthropic, vendor license)
|
||||
|
||||
Audit §11 punti 5–6, 8–9.
|
||||
|
||||
- JWT locale default documentato; Clerk dietro seam nominato (`use_clerk`).
|
||||
- DeepSeek unico provider supportato; Anthropic + Console skills = optional/legacy; CLAUDE.md = README.
|
||||
- Licenza scritta per ImpedenceFinder **o** non distribuire `vendor/`.
|
||||
- Lockfile Python per transitive.
|
||||
|
||||
**Done quando:** boot produzione non richiede Clerk; nessun path “devi uploadare skill Anthropic”; vendor RF ha SPDX o è fuori tree.
|
||||
|
||||
### Fase C — Motore review PinScope-derived (**rewrite map**, dopo A–B)
|
||||
|
||||
Qui sì si riscrive l’engine ereditato. Ordine interno:
|
||||
|
||||
| Step | Oggetto | Perché |
|
||||
| --- | --- | --- |
|
||||
| C0 | Spec freeze `motore-finding.md` = unico IR | Già nativo; non diluire in `validate.py` |
|
||||
| C1 | Adapter: `validate.py` emette solo `Finding` grezzi → `complete_finding` | Già parziale; chiudere i campi doppi |
|
||||
| C2 | **REWRITE** loop per-IC: tool `find_connected_components` / `get_pintable` / excerpt su provider DeepSeek **senza** copiare `validation_tools.py` | Cuore DIRECT da sostituire |
|
||||
| C3 | Extraction: `local_skill.py` + schemi JSON (KEEP schema se identici; REWRITE orchestrazione Anthropic) | |
|
||||
| C4 | Spegnere import da `validate.py` nel PCB (`_parse_review` → parser finding nativo) | Toglie INDIRECT PCB→PinScope reviewer |
|
||||
| C5 | Test golden `simple_project` + Emmaforo: parity FACT/REQUIREMENT, non parity prose | |
|
||||
|
||||
**Non in C:** riscrivere `graph.py` / parser KiCad.
|
||||
|
||||
**Done quando:** `validate.py` PinScope può restare nel recinto **non chiamato** dal job schema/PCB, oppure ridotto a wrapper deprecato.
|
||||
|
||||
### Fase D — UI
|
||||
|
||||
FindingCard, filtri Layout, wizard, admin senza Clerk copy, `package-lock` version drift. Dopo C1 almeno (stesso oggetto finding).
|
||||
|
||||
### Fase E — Parsers / graph (opzionale, XL)
|
||||
|
||||
Solo se l’obiettivo diventa *opera senza codice AGPL PinScope*. Sostituire BOM/netlist/graph/taxonomy è un secondo prodotto. PCB nativo resta INDIRECT finché `ensure_graph` chiama PinScope.
|
||||
|
||||
**Stacco fork GitHub:** solo dopo consiglio legale, **non** in A–D.
|
||||
|
||||
---
|
||||
|
||||
## 7. Rischi e sconosciuti
|
||||
|
||||
| Rischio | Impatto |
|
||||
| --- | --- |
|
||||
| Clone di default `main` ≠ prodotto 2.32.1 | Ogni PR “solo docs” su `main` descrive un altro tree |
|
||||
| Rewrite `validate.py` in A | Rompe schema+PCB; l’audit lo vieta come primo passo |
|
||||
| Recinto solo rename di cartella | Falso recinto (già successo con `periscopex`) |
|
||||
| TOS Periscope scritti male | Resta il testo Faradworks *o* si perde AGPL attribution |
|
||||
| ImpedenceFinder | Distribuzione RF senza grant |
|
||||
| PyMuPDF grant in VPS | Dual license; non verificato a runtime |
|
||||
| Clerk rimasto in `ENVIRONMENT=production` | Deploy script vs JWT locale |
|
||||
| Anthropic ancora importabile se c’è la key | README ≠ runtime |
|
||||
| Trademark Pinscope/Periscope/Faradworks | Audit: solo *use*, nessuna registrazione |
|
||||
| Relicenza “Periscope MIT” | Impossibile finché il recinto è servito (AGPL §13 network) |
|
||||
| Gateway privato Faradworks | Non è nel repo; non auditabile |
|
||||
|
||||
---
|
||||
|
||||
## 8. Non-obiettivi (espliciti)
|
||||
|
||||
- Non modificare source prodotto, remotes, `LICENSE`, git history **in questa consegna** (piano only).
|
||||
- Non staccare il fork GitHub in queste fasi (**passo legale successivo**, se mai).
|
||||
- Non cancellare il codice PinScope “perché è PinScope”.
|
||||
- Non riscrivere `finding_engine` / `validate.py` / PCB nella **fase A**.
|
||||
- Non auto-placer, non write-back pcbnew (già fuori da `piano-pcb-review.md`).
|
||||
- Non promettere una licenza Periscope non-AGPL.
|
||||
- Non mescolare mbparks/pinscope (Arduino) — progetto altro.
|
||||
- Non usare questo piano come licenza a refactor su `cursor/pcb-review-plan-44dd` oltre un eventuale **solo** file docs su **nuovo** branch.
|
||||
|
||||
---
|
||||
|
||||
## 9. Priorità di rewrite (lista operativa)
|
||||
|
||||
1. **Identità** — TOS, privacy, contact, `layout.tsx`, `site.ts` Twitter, footer ©
|
||||
2. **Fence compat** — stop write `pinscopex:`; isolare alias versione/JWT/CORS/Docker
|
||||
3. **`main` vs product branch** — topologia git, fork attaccato
|
||||
4. **Clerk seam** — default JWT locale
|
||||
5. **Anthropic/Console seam** — DeepSeek-only di fatto
|
||||
6. **Licenza ImpedenceFinder** — grant o drop vendor
|
||||
7. **Lockfile Python** — transitive
|
||||
8. **IR finding** — `Finding` ≡ `motore-finding.md` (fase C, non A)
|
||||
9. **Loop `validate.py` / `validation_tools`** — reviewer nativo (C)
|
||||
10. **PCB/schema smettono di chiamare `_parse_review` PinScope** (C4)
|
||||
11. **UI FindingCard/wizard** (D)
|
||||
12. **Parsers/graph** solo se si vuole uscire da AGPL PinScope (E)
|
||||
13. **Stacco fork** — legale, fuori piano
|
||||
|
||||
---
|
||||
|
||||
## 10. Riferimenti
|
||||
|
||||
- Audit: [PINSCOPE_AUDIT.md](./PINSCOPE_AUDIT.md)
|
||||
- Contratto finding nativo: [`docs/motore-finding.md`](../motore-finding.md) (store) / `docs/motore-finding.md` nel repo prodotto
|
||||
- Job PCB: [`docs/piano-pcb-review.md`](../piano-pcb-review.md)
|
||||
Reference in New Issue
Block a user