Skip to content
Back to skills

Audit

ASecurity

**Domanda:** una skill viene scelta quando serve, e una sola? Sessione indipendente, nessun contesto precedente sul repo. Ogni riscontro porta percorso + riga o il comando eseguito. Classificazione esplicita per ognuno. ---

  • 5 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
developmentpythongobashnextjsnodecode-reviewgitapibackendsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned November 11, 2026

npx -y skills add lukedj78/dev-flow --skill audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lukedj78-audit/badge)](https://www.skillsdirectory.com/skills/lukedj78-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Giro 2 — Skill e instradamento

**Domanda:** una skill viene scelta quando serve, e una sola?

Sessione indipendente, nessun contesto precedente sul repo. Ogni riscontro porta percorso +
riga o il comando eseguito. Classificazione esplicita per ognuno.

---

## 0. Il conteggio, prima di tutto

Il prompt dell'audit dice «59 skill». Misurato:

```
$ ls -d */SKILL.md | wc -l
56
$ grep '"skill_count"' skills.json
  "skill_count": 56,
```

**Le skill sono 56.** `skills.json` è corretto; il prompt dell'audit no. Non è un dettaglio
isolato — è la stessa classe di difetto di §4 (fatti duplicati in prosa fuori dalle liste di
controllo), e il prompt dell'audit ne è esso stesso un esempio. Vedi §4.1.

Nota di metodo: i 56 `SKILL.md` di primo livello sono le skill. `dist/` ne contiene 45 copie
di `references/contracts.md` e 56 pacchetti `.skill`; sono artefatti di build, esclusi da ogni
conteggio qui.

---

## 1. Instradabilità delle 56 `description`

### 1.1 Il dato grezzo

Tutte e 56 hanno `name` + `description`, e tutte e 56 contengono un marcatore di invocazione
(`Trigger` / `Use when` / `Use to` …) **e** una clausola `Not for:`. Non è un caso: è verificato
da un controllo automatico, `scripts/lint_skills.py:146-152`, che emette un `warn` quando manca
il marcatore e un altro quando manca `Not for:`. Eseguito:

```
$ python3 scripts/lint_skills.py
→ Linting 56 skills…
· 19 note(s) — informational, do not fail the build:
  …
✓ Clean (notes above).
```

**Nessuna skill è priva di una clausola «quando».** La risposta semplice alla domanda 1 è:
l'elenco delle skill «che un agente non saprebbe quando invocare» è vuoto, se il criterio è la
presenza di segnali. Ma il controllo verifica **la forma, non la sostanza**: `has_invocation_marker`
(`scripts/lint_skills.py:146`) è un test di sottostringa su sei marcatori. Una descrizione che
dicesse «Use to do the thing» lo supererebbe. Quindi la forma è applicata, l'instradabilità è prosa.

Misura meccanica della sostanza — quante enunciazioni dell'utente, fra virgolette, porta ogni
descrizione (è il formato di segnale che il repo stesso usa):

| enunciazioni citate | 0 | 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 15 | 17 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| n. skill | 1 | 2 | 2 | 8 | 17 | 8 | 5 | 8 | 1 | 1 | 1 | 2 |

Solo tre skill stanno sotto le tre enunciazioni: `registry-intake` (0), `data-fetching` (1),
`state-discipline` (1). Lette a mano, `registry-intake/SKILL.md:3` è comunque instradabile —
nomina situazioni concrete («asks if one is safe», «a hook refused an install») e registri
concreti (pdfcn, agentcn). Le altre due sono il problema di §2.3, non di questa sezione.

### 1.2 L'unica descrizione che instrada sé stessa sopra le altre

`rn-fundamentals/SKILL.md:3`:

> `Triggers on: "starting a new RN/Expo project", … Also triggers as a precondition when any other rn-* skill is selected and project setup is unclear.`

Questa seconda frase la rende co-candidata a **ognuno** dei 15 altri `rn-*`. È l'unica
descrizione del repo che si dichiara precondizione di un'altra famiglia intera. Chi vince non è
scritto in nessun punto applicato: `README.md:1340` la descrive («Start of any RN/Expo task»),
`dev-flow/references/stack-expo-rn.md:49` la elenca fra le skill di conoscenza, ma nessuno dei
due dice cosa fare quando entrambe si attivano, e la condizione «project setup is unclear» non è
osservabile dall'agente prima di aver letto il progetto.

Nove `rn-*` la citano nel corpo, non nella descrizione, e con una formula che non è un
instradamento (`rn-data-fetching/SKILL.md:8`, `rn-backend/SKILL.md:8`, `rn-expo-router/SKILL.md:8`,
`rn-animations-gestures/SKILL.md:8`, `rn-push-notifications/SKILL.md:8`,
`rn-publishing-payments/SKILL.md:8`: «see rn-fundamentals → …»). Il corpo si legge solo **dopo**
aver scelto la skill, quindi non partecipa alla scelta.

**Classificazione: rischio plausibile.** Non ho una sessione che si sia rotta su questo.
La misura che lo mostrerebbe: `evals/compliance/comply.py` è l'unico strumento del repo che
misura cosa l'agente *fa* invece di cosa la prosa dice; una batteria di prompt `claude -p` del
tipo «aggiungi una schermata di login» su un progetto Expo, contando quante volte viene invocata
`rn-fundamentals` oltre alla skill proprietaria, darebbe il numero.

**Intervento minimo:** togliere la seconda frase dalla descrizione e lasciare la precondizione
al corpo, oppure renderla condizionale a uno stato leggibile (`meta.json#stack` assente o
`stack.framework` non ancora scritto) invece di «unclear».

---

## 2. Sovrapposizioni

### 2.0 Cosa il repo applica già (e il limite esatto)

Esiste un controllo automatico, il **check 20** (`scripts/lint_skills.py:642-679`): due skill che
condividono una frase di trigger devono escludersi a vicenda nel `Not for:`. È vero enforcement,
non prosa, e oggi passa. Misurato replicando la sua logica:

```
== quoted phrases shared by >1 skill: 5 ==
   "add a cms"        ['module-add', 'rn-module-add']
   "add auth"         ['module-add', 'rn-module-add']
   "aggiungi sentry"  ['module-add', 'rn-module-add']
   "artboard"         ['iframe-canvas', 'wireframe-canvas']
   "set up sanity"    ['module-add', 'rn-module-add']
```

Tutte e cinque hanno esclusione reciproca. **Ma il check 20 vede solo le frasi fra doppi apici**
(`TRIGGER_PHRASE_RE = re.compile(r'["“]([^"”]{4,80})["”]')`, riga 648) e le confronta per
**uguaglianza esatta** dopo il lowercase (riga 670-672). Tre classi di sovrapposizione gli sono
strutturalmente invisibili:

1. un trigger in **backtick** anziché fra apici (§2.3);
2. un trigger che è **prefisso o variante** di un altro, non identico (§2.1);
3. un trigger espresso in **prosa non citata** (§2.2, §2.4).

Inoltre il test di esclusione è una **sottostringa** (`if b not in not_for`, riga 677): un
`Not for:` che nomina `rn-module-add` soddisfa automaticamente anche il requisito per
`module-add`, e `rn-write-tests` soddisfa `write-tests`. Ho verificato che **oggi nessuna delle 5
coppie passa solo per questa via** (replicato con confronto a confine di parola: zero buchi
attivi). Resta un falso negativo latente, non un difetto in atto.

**Classificazione del limite: rischio plausibile** (meccanismo che può rompersi, senza
dimostrazione in atto). *Intervento minimo:* in `check_shared_triggers` sostituire
`b not in not_for` con una ricerca a confine di parola — tre righe, nessun cambiamento di
architettura.

### 2.1 `transitions` ↔ `animated-icons` — trigger per prefisso, esclusione a senso unico

| | trigger | `Not for:` nomina l'altra? |
|---|---|---|
| `transitions/SKILL.md:3` | `"animate this"`, `"add a transition"`, `"aggiungi una transizione / animazione"` … | **no** |
| `animated-icons/SKILL.md:3` | `"animate this icon"`, `"animated icon"`, `"aggiungi un'icona animata"` … | **sì** — «a whole motion system (`transitions`)» |

`"animate this"` è prefisso stretto di `"animate this icon"`: su «animate this icon» entrambe
corrispondono. Il check 20 non lo vede perché le due stringhe non sono identiche.
`transitions`' `Not for:` nomina `rn-animations-gestures` e `module-add motion`, **non**
`animated-icons`.

**Chi dovrebbe vincere:** `animated-icons`, per una singola icona. Lo dice **solo**
`animated-icons`; `transitions` non lo dice da nessuna parte della descrizione, che è l'unica cosa
che l'agente legge prima di scegliere.

**Classificazione: problema dimostrato** (il file contraddice la simmetria che il check 20 esiste
per imporre; la dimostrazione è l'asimmetria stessa, verificata sui due file).
**Intervento minimo:** aggiungere «a single animated icon (`animated-icons`)» al `Not for:` di
`transitions/SKILL.md:3`. Una frase, in un file.

### 2.2 `vercel-deploy` attribuisce a `module-add deploy` ciò che la decisione registrata assegna a `project-infra-setup`

Tre skill toccano il progetto Vercel. La divisione è stata decisa e scritta in
`CHANGELOG.md:354`:

> «**`project-infra-setup`** creates and links the project, sets the project-level region and puts
> the values in. **`module-add deploy`** writes `vercel.json` and the build settings and decides
> which variables each environment needs.»

Ma `vercel-deploy/SKILL.md:3` dice:

> «Not for: the Vercel project config — `vercel.json`, **region, env-var matrix**, monorepo root
> directory are `module-add deploy` (**run it first**)»

e `project-infra-setup/SKILL.md:3` rivendica «the Vercel project (**region, environment
variables**, Git link)».

Quindi: `vercel-deploy` manda l'agente a `module-add deploy` per la *region* e le *variabili*,
che la decisione registrata assegna a `project-infra-setup` — ed è `project-infra-setup` la skill
che **crea e collega** il progetto. Un agente che segue «run it first» salta il passo che deve
venire prima. `vercel-deploy`'s `Not for:` non nomina `project-infra-setup` affatto (verificato:
`a→b=False`), mentre `project-infra-setup` nomina entrambe («Not for: wiring the code
(`module-add`), deploying (`vercel-deploy`)»).

**Classificazione: problema dimostrato** — il file contraddice la decisione registrata nel
CHANGELOG e nella descrizione della skill proprietaria.
**Intervento minimo:** in `vercel-deploy/SKILL.md:3` separare le due voci: «la creazione del
progetto, la region e i valori delle variabili sono `project-infra-setup`; `vercel.json` e le
build settings sono `module-add deploy`». Nessun'altra modifica.

### 2.3 `data-fetching` ↔ `state-discipline` — trigger identico, in backtick

`data-fetching/SKILL.md:3`:
> «…or **pastes `useState + useEffect + fetch`** in a Client Component»

`state-discipline/SKILL.md:3`:
> «Use when the user **pastes `useState + useEffect + fetch`**, reaches for `useState` to mirror a prop…»

Lo stesso trigger letterale, rivendicato da entrambe. Entrambe **si escludono** a vicenda
(`data-fetching`: «local UI state (`state-discipline`)»; `state-discipline`: «server-data reads
(use `data-fetching`)»), quindi la regola di simmetria è rispettata — ma l'esclusione è
formulata su **cosa l'utente vuole** (una lettura di dati vs stato locale), e l'incollata da sola
non lo dice: quello snippet *è* entrambe le cose. Il check 20 non lo vede perché la frase è in
backtick, non fra apici (riga 648).

Sono anche le due sole skill con una sola enunciazione citata in descrizione (§1.1), ed è la
stessa.

**Classificazione: rischio plausibile.** Non ho eseguito la misura.
**La misura che lo mostrerebbe:** `evals/compliance/comply.py` con un prompt che è *solo* quello
snippet, ripetuto ai tre livelli di supporto che quello script già implementa, contando quale
skill viene invocata. È l'unico dato del repo che misura l'obbedienza alla prosa.
**Intervento minimo:** in una delle due, qualificare il trigger con ciò che lo disambigua già nel
testo — p.es. `data-fetching` → «pastes `useState + useEffect + fetch` **to load server data**».

### 2.4 I quattro produttori di DESIGN.md — `figma-to-design-md` non esclude nessun fratello

Misurato (matrice di esclusione reciproca sulle descrizioni):

```
figma-to-design-md <-> image-to-design-md   a→b=False b→a=True
figma-to-design-md <-> url-to-design-md     a→b=False b→a=True
figma-to-design-md <-> screenshot-to-page   a→b=False b→a=True
figma-to-design-md <-> wireframe-canvas     a→b=False b→a=True
figma-to-design-md <-> design-md-to-app     a→b=False b→a=False
```

`figma-to-design-md/SKILL.md:3` è l'unica dei quattro produttori a non nominare **nessuno** dei
fratelli: il suo `Not for:` dice «converting Figma to code, reviewing production websites, or
design brainstorming before Figma work» — tre attività, zero skill. È anche la descrizione più
corta del repo (436 caratteri contro una media di ~830), quindi ha il margine per aggiungerle.

Nella pratica l'arbitrato esiste e funziona **per tipo di sorgente**: Figma URL / immagini raster
/ URL live sono segnali concreti e disgiunti, e i trigger citati non collidono
(`"crea il design.md"` e `"estrai i token"` sono solo di `figma-to-design-md`). Il rischio si
materializza solo quando la richiesta non nomina la sorgente — «estrai i token», «crea il
design.md» — e lì tre skill corrispondono mentre solo due si escludono.

**Classificazione: rischio plausibile** (non *dimostrato*: non ho una sessione in cui la scelta
sia andata male, e l'arbitrato per sorgente è reale).
**Intervento minimo:** aggiungere al `Not for:` di `figma-to-design-md` i due fratelli, come i
due fratelli già fanno con lei.

### 2.5 Sovrapposizioni esaminate e giudicate sane

Le dichiaro per non lasciarle sospese:

- **i cinque cancelli pre-deploy** (`shadscan`, `compliance-audit`, `vercel-doctor`,
  `launch-audit`, `app-security`): ognuno nomina gli altri quattro nel `Not for:`, con l'asse di
  competenza esplicito (UI / legale / costo / lancio / sicurezza). Arbitrato chiaro, in ogni
  descrizione. (Il *conteggio* è un problema separato: §4.2.)
- **`module-add` ↔ `rn-module-add`**: quattro trigger condivisi, esclusione reciproca su
  `next` vs `expo-rn`. È il caso che ha fatto nascere il check 20 ed è chiuso.
- **`iframe-canvas` ↔ `wireframe-canvas`** su `"artboard"`: esclusione reciproca presente
  («wireframes before a scaffold (`wireframe-canvas`)» / «a canvas inside the product
  (`iframe-canvas`)»). Chiuso.
- **`composition-patterns-guide` ↔ `promote-component`**: entrambe si attivano su «refactor
  questo componente», entrambe nominano l'altra, con l'asse giusto (componibilità vs movimento
  verticale nella gerarchia). Chiuso.
- **`registry-intake` ↔ `product-to-agent-skill`**: nessuna nomina l'altra, ed entrambe parlano
  di «agent skill». Ma `registry-intake` qualifica sempre «from a community registry» e
  `product-to-agent-skill` «for the product you just built». **Miglioramento facoltativo**, non
  un rischio: i qualificatori bastano.

---

## 3. Rinvii rotti

### 3.1 Validazione dei comandi, prima dei risultati

Il prompt avverte che un `grep` che non trova nulla può essere un `grep` sbagliato. **È accaduto,
e l'ho corretto.** Primo tentativo:

```
$ grep -rn --include=*.md '§' . | …
(eval):1: no matches found: --include=*.md
```

zsh espande `--include=*.md` come glob prima di passarlo a `grep`: il comando non ha cercato
nulla e non ha segnalato zero risultati, ha **fallito**. Con `--include='*.md'` quotato, e
validato su una stringa che so esistere:

```
$ grep -rn --include='*.md' 'dev-flow contract' . | grep -v '^./dist/' | head -3
CONTEXT.md:128:| **a DESIGN.md** | the Google design.md spec: …
dev-flow/references/scripts.md:48:- Is part of the dev-flow contract (`.workflow/DESIGN.md`, …
state-discipline/SKILL.md:24:Follows the dev-flow contract — see `references/contracts.md`. …
```

Il comando funziona. Lo stesso per i link markdown: il risolutore ha trovato **90 link relativi
che risolvono** oltre a quelli rotti, quindi non stava semplicemente non-trovando nulla.
E per i percorsi in backtick: **4067 percorsi interni al repo risolti** contro 391 non risolti,
poi filtrati a mano (vedi 3.4).

### 3.2 Link markdown: sani

448 file markdown scansionati (esclusi `dist/`, `.git/`, `node_modules/`, `.workflow/`).
90 link relativi risolvono, 19 no — e tutti e 19 sono falsi positivi verificati a mano:

- 16 in `.skill-doctor-report/transcripts/*.md`, che sono **trascrizioni di sessioni d'agente**,
  non documentazione del repo;
- `module-add/references/module-observability.md:61` e
  `rn-module-add/references/module-observability.md:56`: `[?&](token|code|key)` è un **letterale
  di espressione regolare**, non un link;
- `scrum/references/issue-standard.md:9`: `[file#Ln](link)` è un **segnaposto** in un esempio.

**Nessun link markdown rotto.** Classificazione: nessun riscontro.

### 3.3 Il rinvio `§` rotto — una sola istanza, dimostrata

`CHANGELOG.md:165` registra lo spostamento:

> «the Step 2 policies, "Shipping the product's own agent-skill" and **"Resources the user hands
> over" → new `references/policies.md`**»

La sezione vive ora in `dev-flow/references/policies.md:74` («## Resources the user hands over —
analyse, record, then actually use»), con sette passi numerati. Ma **due file continuano a
puntare alla vecchia casa**:

- `dev-flow/references/resources.md:11` → «`SKILL.md` §*Resources the user hands over* — review
  run, traps resolved, one yes missing.»
- `dev-flow/references/before-you-build.md:46-47` → «the work starts at **step 5 of `SKILL.md`
  §*Resources the user hands over***, not at zero.»

Verificato che la sezione non esiste più in `dev-flow/SKILL.md` — l'elenco completo delle sue
intestazioni non la contiene, e la stringa non compare:

```
$ grep -rn 'Resources the user hands over' --include='*.md' . | grep -v dist | grep -v skill-doctor
CHANGELOG.md:165: … → new `references/policies.md`;
dev-flow/references/resources.md:11:`SKILL.md` §*Resources the user hands over* …
dev-flow/references/policies.md:74:## Resources the user hands over — analyse, record, then actually use
dev-flow/SKILL.md:211:- **Resources the user hands over** — analyse, record in `references/resources.md`, then actually use them.
```

`dev-flow/SKILL.md:211` è una **riga di elenco**, non una sezione, e non ha passi: lo «step 5»
citato da `before-you-build.md` esiste solo in `policies.md:74` (è «5. **Do not stop at "it is an
option"**», `dev-flow/references/policies.md`, quinto passo numerato della sezione).

**Classificazione: problema dimostrato.** Un agente che segue `before-you-build.md:46` apre
`dev-flow/SKILL.md`, non trova né la sezione né uno step 5, e perde la regola che gli dice di
portare il lavoro fino a «manca un solo sì» — che è esattamente la regola che la memoria
dell'utente registra come *use the resources given*.

**Intervento minimo:** in quei due file sostituire `SKILL.md` con `references/policies.md`.
Due stringhe.

### 3.4 Rinvii `§` controllati e validi (falsi positivi del mio risolutore)

Li dichiaro perché un elenco di «sezioni mancanti» senza questa verifica sarebbe rumore. 179
coppie `` `file` §Sezione `` unificate; il risolutore automatico ne segnalava ~60, e la
verifica a mano ha mostrato che tutte tranne quella di §3.3 sono valide. Le forme che il mio
matcher sbagliava:

- **sotto-voci con lettera**: `eve-patterns.md §8f` da `rn-eas-deploy/references/asc-cli.md:41`
  → esiste, è la voce «**f. Gate on reversibility…**» dentro `## 8.` di
  `eve-agent/references/eve-patterns.md:380`. Idem `§9e`, `§7d`, `§11b`, `§8b`.
- **grassetto con testo di coda**: `design-md-to-app/SKILL.md §Visual verification` → esiste a
  `design-md-to-app/SKILL.md:1146` («**Visual verification — best-effort obbligatorio.**»).
  Idem `iframe-canvas/SKILL.md §Overlays render in the parent` → `iframe-canvas/SKILL.md:156`.
- **voci di elenco anziché intestazioni**: `references/contracts.md § nextjs_version` da
  `compliance-audit/SKILL.md:54` → esiste a `compliance-audit/references/contracts.md:467`.
- **documentazione esterna**: `authentication.md §Optimistic` da
  `module-add/references/module-auth.md:299` è la **documentazione di Next.js**, non un file del
  repo. `docs/engineering/code-review.md` da `spec-review/SKILL.md:18` è un URL di
  `mattpocock/skills`. `docs/eve-registries.md` da `eve-registry-porting/SKILL.md:180` è
  dichiarato «in that repo» (AgentOS).
- **artefatti del progetto generato, non del repo**: `.workflow/meta.json`, `lib/auth.ts`,
  `app/layout.tsx`, `docs/compliance/dpia-template.md`, `docs/vercel/doctor-report.md`,
  `assets/icon.png`, `scripts/guard/primitives-guard.mjs`. Sono percorsi *nel progetto bersaglio*;
  l'originale di quest'ultimo è vendorizzato a
  `design-md-to-app/assets/guard/primitives-guard.mjs` e viene copiato all'installazione.
- **nomi di controllo, non di sezione**: `launch-audit §third-party-marks` da
  `design-md-to-app/SKILL.md:434` è l'id di un check dello scanner, verificato in
  `launch-audit/scripts/test_scan_launch.py:327`.
- **`linear-scrum`**: la skill non esiste più (rinominata `scrum`). Le sole due citazioni in file
  vivi — `README.md:955` e `CHANGELOG.md:22` — sono **deliberate**: descrivono la migrazione che
  `scripts/meta_tracker.py migrate` esegue dal vecchio formato. Non sono rinvii rotti. Le altre
  stanno in `docs/superpowers/` (piani archiviati).

Verifica che `AUDIT.md:283-296` non segnali script mancanti: i nomi vi compaiono senza estensione
(`dev-flow/scripts/test_inventory`), ma i file esistono tutti
(`dev-flow/scripts/test_inventory.py`, `…/test_wireframes_gate.py`,
`…/test_primitives_guard_gate.py`, `…/test_research.py`,
`design-md-to-app/scripts/test_setup_primitives_guard.py`,
`registry-intake/scripts/test_registry_intake.py`). Abbreviazione in prosa, non un rinvio rotto.

### 3.5 Il rinvio che punta a una skill che il repo non contiene

`write-tests/SKILL.md:3`, nella clausola `Not for:`:

> «running QA on a deployed site (**`qa`**)»

Misurato:

```
$ ls -d qa qa-only
ls: qa: No such file or directory
ls: qa-only: No such file or directory
$ grep -c '"qa"' skills.json
0
$ grep -n 'qa' install.sh
(nessun risultato)
$ grep -n '^### `qa\|^| `qa' README.md
(nessun risultato)
$ grep -n '\bqa\b' dev-flow/references/external-skills.md
(nessun risultato)
$ ls -d ~/.claude/skills/qa
/Users/lucadigerlando/.claude/skills/qa
```

`qa` **non è una delle 56 skill**, non è in `skills.json`, non è installata da `install.sh`, non
è nel catalogo del README, e non è nel registro delle skill esterne
(`dev-flow/references/external-skills.md`, che è il posto dove il repo dichiara ciò che non
possiede: vi stanno `sleekdotdesign/agent-skills`, `rorkai/app-store-connect-cli-skills`,
`mattpocock/skills`, `Rizzo-AI-Academy/rizzo-flow`, Skillry, `Leonxlnx/taste-skill`,
`echris6/motion-video-kit`). Esiste **solo** in `~/.claude/skills/qa`, la cartella personale di
questa macchina.

Chi installa dev-flow da questo repo ottiene una descrizione che instrada verso una skill che non
ha, e nulla nel repo gli dice dove trovarla.

Il lint non può vederlo: `check_routing_references` (`scripts/lint_skills.py:299-327`) richiede un
**verbo di instradamento** prima del nome in backtick (`invoke`, `route to`, `hand off to`,
`delegate to`, `defer to`, `that's`, `that is`), e il commento alla riga 292-293 dichiara la
scelta esplicitamente: «*a bare "→ `x`" is NOT a marker … Deliberately narrow*». Una clausola
`Not for: … (`qa`)` non ha verbo, quindi è fuori portata — ed è la stessa classe del difetto che
quel commento racconta: «*which is how `setup-deploy` stayed routed-to for months without
existing*».

**Classificazione: problema dimostrato.**
**Dove il fatto dovrebbe vivere:** `dev-flow/references/external-skills.md` ha già la forma
giusta (riga per skill esterna, con costo, licenza e «cosa fa dev-flow invece»). Se `qa` è una
skill esterna che vogliamo citare, le serve una riga lì; se non lo è, va togliuta dal `Not for:`
di `write-tests`.
**Intervento minimo aggiuntivo (facoltativo):** estendere `check_routing_references` a riconoscere
anche il contesto `Not for:` — le descrizioni sono 56 stringhe, il costo di scansionarle è nullo.

---

## 4. Ridondanza — lo stesso fatto in più documenti

### 4.0 Cosa è già applicato, e dove si ferma

Il repo **applica** la duplicazione a livello di *file*, e lo fa bene. `check_vendored_source`
(`scripts/lint_skills.py:770-793`) confronta byte a byte ogni copia vendorizzata, e per
`contracts.md` usa una **glob**, non un elenco:

```python
VENDORED_EVERYWHERE = [
    ("dev-flow/references/contracts.md", "*/references/contracts.md"),
]   # scripts/lint_skills.py:765-767
```

Il commento alla riga 763 spiega perché: «*Discovered rather than listed, so a skill that starts
carrying it is checked from its first commit.*» È la forma giusta — una lista non può invecchiare
se non è una lista. Verificato:

```
$ ls */references/contracts.md | wc -l
45
$ # confronto sha256 di ognuna contro dev-flow/references/contracts.md
identical-to-source: 44   diverging: 0
$ find dist -name contracts.md | wc -l
45
$ # idem per le copie in dist
dist identical: 45  diverging: 0
```

**45 copie nel repo e 45 in `dist/`, tutte e 90 byte-identiche. Zero divergenze.**

Dove si ferma: i fatti duplicati **dentro la prosa**. Lì il controllo è
`check_stated_counts` (`scripts/lint_skills.py:439-468`), e poggia su **due liste compilate a
mano**:

- `COUNT_FILES` (riga 401-407): **11 file**. `README.md`, `CONTEXT.md`,
  `docs/dev-flow-skill-map.html`, `install.sh`, `uninstall.sh`, `dev-flow/SKILL.md`,
  `dev-flow/references/external-skills.md`, `docs/skill-doctor-referto.html`, i due
  `.claude-plugin/*.json`, `dist/agent-plugin/plugin.json`. **Nessun altro `SKILL.md`, non
  `AUDIT.md`, non `CHANGELOG.md`, non `docs/`.**
- `TOTAL_PATTERNS` (riga 408-431): **25 espressioni regolari**, ognuna aggiunta dopo una
  mancanza specifica. I commenti lo ammettono: «*Found by eye, not by the check*» (riga 423),
  «*It sat at 44 for a full release*» (riga 429).

Un fatto in un file fuori dall'elenco, o in una formulazione fuori dalle 25, invecchia in
silenzio. Segue la dimostrazione.

### 4.1 Lo stesso fatto, tre numeri diversi, in tre file vivi — dimostrato

Il fatto: *in quante skill è vendorizzato `contracts.md`*. Misurato sopra: **45** (44 copie più
la sorgente).

| file | riga | dice | vero |
|---|---|---|---|
| `CONTEXT.md` | 172 | «byte-identical in **44 skills**» | 44 copie oltre la sorgente — **corretto** se letto così |
| `spec-review/references/risk.md` | 34 | «`contracts.md` in **41 skills**» | **falso, 45** |
| `docs/audit-prompt.md` | 51 | «vendorizzato in **41 skill** (più **41 copie** in `dist/`)» | **falso, 45 e 45** |

`CONTEXT.md` è dentro `COUNT_FILES` — ma la sua formulazione, «byte-identical in 44 skills», non
corrisponde a nessuno dei 25 `TOTAL_PATTERNS`, quindi nemmeno lì il numero è verificato: è
corretto per cura dell'autore, non per controllo. Gli altri due file non sono nemmeno
nell'elenco.

E lo stesso `docs/audit-prompt.md:88` dice «**59 skill**» dove sono **56** (§0).

**Classificazione: problema dimostrato.** Non è un'inferenza: ho contato i file e confrontato gli
hash.

**Conseguenza misurata in questa stessa sessione:** il prompt che mi è stato dato ordinava al
Giro 1 di «verificare che le **41** copie vendorizzate siano byte-identiche (e le **41** in
`dist/`)». Sono 45 e 45. Un numero stantio in un documento ha prodotto un'istruzione sbagliata a
un altro agente dell'audit — che è esattamente la classe di difetto che la memoria dell'utente
registra come misurata («dieci memorie `reference` erano duplicati, e tre fasi di progetto erano
già false»).

**Dove il fatto dovrebbe vivere:** in **nessun documento**. È già derivabile con un comando
(`ls */references/contracts.md | wc -l`), e `check_vendored_source` lo scopre già con una glob.
*Intervento minimo:* sostituire il numero con la glob o con il comando in
`spec-review/references/risk.md:34` e `docs/audit-prompt.md:51`; oppure, se un numero deve
restare leggibile, aggiungere a `TOTAL_PATTERNS` il pattern
`r"byte-identical in (\d+) skills"` + `r"contracts\.md` in (\d+) skills"` e mettere i due file in
`COUNT_FILES` — ma la prima strada è più corta e non può invecchiare.

### 4.2 «I cinque cancelli pre-deploy», in 16 posti vivi, senza alcun controllo

Il fatto — *quanti sono i cancelli pre-deploy e quali* — è ripetuto in almeno 16 posizioni vive,
cinque delle quali sono **frontmatter `description`** (ciò che instrada):

| file | riga |
|---|---|
| `launch-audit/SKILL.md` | 3 (descrizione), 17 |
| `shadscan/SKILL.md` | 3 (descrizione) |
| `app-security/SKILL.md` | 3 (descrizione) |
| `compliance-audit/SKILL.md` | 3 (descrizione) |
| `vercel-deploy/SKILL.md` | 3 (descrizione), 90, 106 |
| `vercel-doctor/SKILL.md` | 122 |
| `dev-flow/SKILL.md` | 189, 203 |
| `dev-flow/references/policies.md` | 23, 31 |
| `dev-flow/references/finishing-a-feature.md` | 68 |
| `spec-review/SKILL.md` | 83, 251 |
| `spec-review/references/risk.md` | 101 |
| `vercel-deploy/references/deploy-checklist.md` | 19 |
| `docs/knowledge-index.md` | 73 |
| `README.md` | 1269, 1288 |

Oggi **tutte dicono cinque e sono coerenti** — l'ho verificato con
`grep -rniE '(…|five|four|six) pre-deploy gates?|(five|four|six) gates'`. Il riscontro non è che
siano sbagliate: è che **nessun controllo le tiene insieme**, e sono già andate fuori sincrono
**due volte**, per ammissione del repo stesso:

- `CHANGELOG.md:360`: «*The number of pre-deploy gates moved from file to file: **four** in the
  routing table, **three** in the `deployed` row, the review policy, `spec-review`,
  `vercel-deploy` and the knowledge…*»
- `docs/superpowers/specs/2026-10-18-debt-audit-design.md:56`: «*Conteggi vecchi dei gate:
  `compliance-audit/SKILL.md:109` dice «**three** pre-deploy gates»…*»

Entrambe le volte la correzione è arrivata **a occhio**, elencata a mano in un piano
(`docs/superpowers/plans/2026-10-18-app-security.md:164-176` enumera i file da modificare uno per
uno, e la riga 176 dice «*and any other "four pre-deploy gates" that `grep -rn` finds*»). Il
piano riconosce che il meccanismo è un grep manuale.

C'è già pressione sul numero: `dev-flow/references/finishing-a-feature.md:68` elenca
«`app-verification`, **the five gates**» — un sesto passo pre-spedizione che non è contato — e
`spec-review/SKILL.md:251` deve dichiarare di «*non* essere un sesto cancello».

**Classificazione: rischio plausibile** (il meccanismo è dimostratamente fragile — due rotture
registrate — ma oggi i 16 posti sono coerenti, quindi non è un problema in atto).

**Dove il fatto dovrebbe vivere:** in `skills.json`, che è **generato**
(`scripts/build_skills_registry.py`) e già porta le famiglie. Un campo
`"gates": ["compliance-audit","vercel-doctor","shadscan","launch-audit","app-security"]` darebbe
al lint la stessa presa che ha sul conteggio delle skill.
**Intervento minimo:** aggiungere a `check_stated_counts` un pattern
`r"(?:the )?(five|four|six|three|\d+) (?:pre-deploy )?gates"` risolto contro
`len(skills.json["gates"])`, e aggiungere i cinque `SKILL.md` dei cancelli a `COUNT_FILES`.
Un controllo, una lista di cinque nomi: chiude 16 copie.

### 4.3 Ridondanza sana, dichiarata

- `rn-fundamentals/references/stack-defaults.md` ↔ `rn-bootstrap/references/stack-defaults.md`:
  duplicati **con controllo** (`scripts/lint_skills.py:759`), e il file stesso lo dichiara
  (`rn-bootstrap/references/stack-defaults.md:2`: «*This file is carried by `rn-fundamentals` and
  `rn-bootstrap`, byte-identical (`lint_skills.py` fails otherwise)*»). È il modello da imitare
  per §4.2.
- `contracts.md` in 45 skill: duplicazione **voluta**, dichiarata in `CONTEXT.md:170-174`, e
  verificata da una glob. Il difetto non è la duplicazione del file; è il *numero* ripetuto in
  prosa (§4.1).
- `dev-flow/scripts/meta_io.py`, `surfaces.py` e altri vendorizzati in più skill: tutti coperti
  da `VENDORED_SOURCE` (`scripts/lint_skills.py:752-759`). **Nessun riscontro.**

---

## 5. Cosa si rompe per primo, per area

Una frase per area, come richiesto, senza voti.

- **Instradabilità (§1).** Si rompe per prima `rn-fundamentals`, che si dichiara precondizione di
  15 altre skill senza che nessun documento dica chi vince; lo mostrerebbe una batteria di
  `claude -p` su un compito Expo ordinario, contando le invocazioni in eccesso con lo stesso
  metodo di `evals/compliance/comply.py`.
- **Sovrapposizioni (§2).** Si rompe per prima la coppia `transitions`/`animated-icons`, perché un
  trigger è prefisso stretto dell'altro e l'esclusione è a senso unico; lo mostrerebbe il check 20
  se confrontasse per prefisso invece che per uguaglianza esatta — oggi la misura è il diff
  manuale fra le due descrizioni, già fatto qui.
- **Rinvii (§3).** Si rompe per primo `write-tests` → `qa`, perché il rinvio esce dal repo e il
  lint è deliberatamente cieco alle clausole `Not for:`; lo mostrerebbe estendere
  `check_routing_references` alle 56 descrizioni, che costa una scansione di 56 stringhe.
- **Ridondanza (§4).** Si rompe per primo il conteggio dei cancelli pre-deploy, perché vive in 16
  posti di cui cinque instradano e nessun controllo li lega; lo mostrerebbe un pattern in
  `check_stated_counts` risolto contro un elenco in `skills.json` — e il fatto che sia già
  accaduto due volte è la misura che abbiamo.

---

## 6. Riepilogo dei riscontri

**Problemi dimostrati (4)**

| | riscontro | prova |
|---|---|---|
| D1 | `write-tests` instrada verso la skill `qa`, che il repo non contiene, non installa e non dichiara fra le esterne | `write-tests/SKILL.md:3`; `ls -d qa` → assente; `grep -c '"qa"' skills.json` → 0; assente da `install.sh`, dal catalogo README e da `dev-flow/references/external-skills.md`; presente solo in `~/.claude/skills/qa` |
| D2 | Lo stesso fatto (copie di `contracts.md`) porta tre numeri diversi in tre file vivi, e uno ha già sviato un altro agente di questo audit | vero = 45 (`ls */references/contracts.md \| wc -l`, 44 hash identici + sorgente, 45 in `dist/`); `CONTEXT.md:172` = 44, `spec-review/references/risk.md:34` = 41, `docs/audit-prompt.md:51` = 41 «+41 in dist». Inoltre `docs/audit-prompt.md:88` = «59 skill» vs 56 |
| D3 | `vercel-deploy` attribuisce region e variabili a `module-add deploy`, mentre la decisione registrata le assegna a `project-infra-setup`, che non è nominata | `vercel-deploy/SKILL.md:3` vs `CHANGELOG.md:354` e `project-infra-setup/SKILL.md:3`; matrice di esclusione: `vercel-deploy`→`project-infra-setup` = False |
| D4 | Due file puntano a una sezione di `dev-flow/SKILL.md` che è stata spostata in `references/policies.md` | `dev-flow/references/resources.md:11` e `dev-flow/references/before-you-build.md:46-47` vs `dev-flow/references/policies.md:74`; `dev-flow/SKILL.md:211` è una riga di elenco senza passi |

*(D3 e l'asimmetria `transitions`/`animated-icons` di §2.1 sono due istanze della stessa classe —
una descrizione che non nomina la skill con cui condivide il terreno. Conto §2.1 come quinto
riscontro dimostrato se si preferisce separarle; qui sta sotto D3 per non gonfiare il totale.)*

**Rischi plausibili (5)** — §1.2 `rn-fundamentals` precondizione di 15 skill · §2.0 il test di
esclusione del check 20 è una sottostringa (falso negativo latente, zero buchi attivi oggi) ·
§2.1 `transitions` non esclude `animated-icons` su un trigger per prefisso · §2.3
`data-fetching`/`state-discipline` rivendicano lo stesso trigger letterale in backtick · §4.2 il
conteggio dei cancelli in 16 posti senza controllo, già rotto due volte.

**Miglioramenti facoltativi (4)** — §2.4 `figma-to-design-md` non esclude i fratelli produttori di
DESIGN.md · §2.5 `registry-intake`/`product-to-agent-skill` non si nominano · §3.5 estendere
`check_routing_references` alle clausole `Not for:` · §4.2 spostare l'elenco dei cancelli in
`skills.json`.

**Preferenze personali (1)** — le 19 note su descrizioni fra 900 e 1024 caratteri
(`scripts/lint_skills.py:140-144`): il margine è del 7% su un limite dove, sopra, «*a conforming
client skips the skill*» (riga 59-62). Il limite **è** applicato come errore (riga 123-134), e
`GRANDFATHERED_LONG_DESC` è vuoto per scelta dichiarata, quindi il meccanismo è sano. Che 19
descrizioni su 56 stiano a meno di 125 caratteri dal taglio è una scelta di stile dell'autore, non
un difetto: lo dico e passo oltre.

---

## 7. Verifiche che non ho potuto eseguire

- **Quale skill un agente scelga davvero** sui trigger ambigui di §2.1, §2.3 e §1.2. Non l'ho
  misurato. Servirebbe: `python3 evals/compliance/comply.py` adattato a un corpus di prompt di
  instradamento (lo snippet `useState + useEffect + fetch` nudo, «animate this icon», «aggiungi una
  schermata di login» su progetto Expo), ai tre livelli di supporto che quello script già
  implementa, contando la skill invocata. È l'unico strumento del repo che misura cosa l'agente
  *fa*; tutto il resto in questo referto misura cosa i file *dicono*.
- **Se il client reale tronchi o scarti le descrizioni vicine al limite di 1024.** Non ho un modo
  di osservare la lista di skill che il client costruisce. Servirebbe un confronto fra le 56
  `description` sul disco e quelle effettivamente presentate all'agente.
- **Il check 20 con il confronto a confine di parola.** Ho replicato la sua logica in uno script
  di scratch e non ho trovato buchi attivi, ma non ho modificato `scripts/lint_skills.py` (vincolo:
  nessuna modifica ai sorgenti). Il comando che lo chiuderebbe: sostituire `b not in not_for` con
  `re.search(rf'(?<![\w-]){re.escape(b)}(?![\w-])', not_for)` alla riga 677 e rieseguire
  `python3 scripts/lint_skills.py`.

## 8. Comandi per riprodurre

```bash
cd /Users/lucadigerlando/my-skills

# §0 — il conteggio
ls -d */SKILL.md | wc -l                       # 56
grep '"skill_count"' skills.json               # 56

# §1 — il lint, e i suoi 19 note
python3 scripts/lint_skills.py

# §2 — frasi di trigger condivise (la logica del check 20)
#   vedi scripts/lint_skills.py:642-679 per l'originale

# §3.1 — la trappola del grep in zsh: quotare sempre --include
grep -rn --include='*.md' 'dev-flow contract' . | grep -v '^./dist/' | head -3   # valida il comando
grep -rn --include='*.md' '§' . | grep -v '^./dist/' | wc -l

# §3.3 — la sezione spostata
grep -rn 'Resources the user hands over' --include='*.md' . | grep -v dist | grep -v skill-doctor
grep -n '^#' dev-flow/SKILL.md                 # la sezione non c'è

# §3.5 — la skill qa
ls -d qa 2>&1; grep -c '"qa"' skills.json; grep -n 'qa' install.sh
grep -n '\bqa\b' dev-flow/references/external-skills.md

# §4.0/§4.1 — le copie vendorizzate, contate e confrontate
ls */references/contracts.md | wc -l           # 45
find dist -name contracts.md | wc -l           # 45
h=$(shasum -a256 dev-flow/references/contracts.md | cut -d' ' -f1)
for f in */references/contracts.md dist/agent-plugin/skills/*/references/contracts.md; do
  [ "$(shasum -a256 "$f" | cut -d' ' -f1)" = "$h" ] || echo "DIVERGE: $f"
done                                           # nessun output

# §4.2 — il conteggio dei cancelli, in ogni file vivo
grep -rniE '(five|four|six|three) (pre-deploy )?gates?' --include='*.md' . \
  | grep -v '^./dist/' | grep -v '^./docs/superpowers/' | grep -v skill-doctor
```

---

*Giro 2 di 3. Nessun sorgente di skill modificato, nessuna `.workflow/` toccata, nessun commit.*

Files in this skill

  • 01-contratto.md44.8 KB
  • 02-skill.md37.9 KB
  • 03-test-sicurezza.md54.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.