Spec-driven development on OpenSpec, with mechanical spec-as-source enforcement: a custom 'spec-as-source' OpenSpec schema adds file-ownership (targets) and test-verification ([@test]) metadata to every capability spec, three scripts (link check, ownership check, manifest build) keep code and specs from drifting apart, plus requirement-gathering, spec-writer, work-review, and a session-handoff skill with a proactive context-warning hook.
68
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
📦 Pubblicato su Tessl: tessl.io/registry/spec-driven-development/spec-as-source
⚠️ Il workspace è cambiato. Fino alla 2.2.0 il plugin viveva su
spec-driven-devlopment— con un refuso nel nome. Quella versione è archiviata dal 2026-07-16 e non riceve più aggiornamenti: non contiene il router,plan-mode, né il gate del piano. Il nome corretto èspec-driven-development, e da lì riparte la 3.0.0. Se avevi installato il vecchio:tessl uninstall spec-driven-devlopment/spec-as-source, poi il comando di installazione qui sotto.
Workflow di spec-driven development costruito su OpenSpec, con enforcement meccanico per lo spec-as-source sopra di esso.
OpenSpec porta un progetto a spec-anchored (propose → apply → archive, con openspec/specs/<capability>/spec.md come specifica canonica), ma non verifica nulla da solo. Questo plugin aggiunge una schema OpenSpec custom (spec-as-source) e gli strumenti che rendono la spec un vincolo strutturale: ownership dei file, verifica dei test, workflow CI, e le skill che li orchestrano.
# prerequisito: CLI OpenSpec
npm install -g @fission-ai/openspec
# nel tuo progetto
tessl install spec-driven-development/spec-as-sourceRichiede: openspec CLI, git, un test runner (pytest / vitest / jest / cargo / go), e PyYAML (pip install -r requirements.txt) per il router.
Le skill sono 18 e formano una pipeline ordinata: non serve conoscerle tutte. skill-router è pensata per essere l'unica registrata globalmente; le altre restano nel repo e vengono lette da lì quando servono.
bash skills/skill-router/install.sh # symlink in ~/.claude/skills/, mai una copia
bash skills/skill-router/install.sh --check # sola lettura: stato + omonimi obsoletiDato un prompt, il router decide in tre passaggi in quest'ordine:
P0-P8) — e se la richiesta somiglia a una skill a valle della fase rilevata, dirotta a monte dicendo quale sonda ha fallito;description, e solo fra i candidati ammissibili in quella fase.Il motivo dell'ordine: queste skill sono le fasi di un processo, non un menù di attrezzi paritetici. Un router che instrada per sola somiglianza salta le fasi, e saltare le fasi è esattamente ciò che questo metodo esiste per impedire.
Ogni decisione è tracciata: gate con la fonte, fase con la sonda e il suo output, candidati, scartati con motivo, scelta, modalità di esecuzione, e il frammento di description su cui il match si fonda.
skills/skill-router/routing-table.md è generata dalle skill (python3 skills/skill-router/build_router.py), non scritta a mano: è deterministica byte-a-byte, e riporta anche le divergenze fra disco e plugin.json, le copie ombra che sono andate alla deriva, e i riferimenti a skill inesistenti.
Limite dichiarato: il router governa cosa succede quando viene invocato lui, non cosa carica l'agente all'avvio. Se in ~/.claude/skills/ è già installata una versione obsoleta di una skill, quella continua ad attivarsi da sola — install.sh la segnala e non la rimuove, perché è una decisione dell'operatore.
| Rule | Comportamento imposto |
|---|---|
spec-as-source | L'implementazione non è completa finché tutti i [@test] esistono; un targets non cambia senza la sua spec |
generated-file-header | Ogni file targets inizia con # GENERATED FROM SPEC |
handoff-suggestion | Suggerisce /handoff save quando il contesto della conversazione si fa lungo (fallback portabile dell'hook PreCompact) |
prompt-loop | Prima di openspec-propose, per lavoro di sviluppo non banale: attiva in automatico il loop di rifinimento del prompt (punteggio→intervista→lock) finché non è meccanicamente pronto |
history-log | Dopo ogni transizione di stato (proposta creata, task completato, verify passato/fallito, iterazione di spec-loop, change archiviato, snapshot handoff salvato, voce di piano avanzata) appende una riga a .handoff/HISTORY.md, se presente — il diario continuo che completa gli snapshot periodici di handoff |
plan-mode | Nessun change parte fuori dal piano: openspec/PLAN.md deve avere una voce approvata da un umano che lo dichiari, e scripts/check-plan-gate.sh lo verifica meccanicamente. Il gate è imposto a due porte — il router non instrada verso openspec-propose, e openspec-propose lo esegue da sé prima di creare qualunque artifact — così invocare la skill direttamente non lo aggira. Un cancello solo: stesso script, stesse reason, nessuna logica di approvazione duplicata |
| Skill | Quando usarla | Comando |
|---|---|---|
plan-mode | per costruire il piano che porta al goal, prima ancora di sapere quali change servono: esplora in sola lettura, scrive le voci, poi si ferma e chiede l'approvazione | Use plan-mode. |
prompt-loop | per rifinire il prompt in ingresso con un loop punteggio→intervista→lock finché non è pronto (si attiva anche in automatico prima di openspec-propose per lavoro non banale, assorbendo l'interview di requirement-gathering) | Use prompt-loop. |
requirement-gathering | quando la richiesta è vaga: interview strutturata, una domanda alla volta | Use requirement-gathering. |
spec-writer | per scrivere o correggere a mano una openspec/specs/<capability>/spec.md | Use spec-writer. |
| Skill | Quando usarla | Comando |
|---|---|---|
openspec-explore | per investigare un'idea prima di proporla | Use openspec-explore. |
openspec-propose | per proporre un change e generare tutti gli artifact in un colpo — esegue il gate del piano come propria precondizione: senza una voce approvata si ferma prima di creare qualsiasi cosa | Use openspec-propose. |
openspec-apply-change | per implementare i task di un change | Use openspec-apply-change. |
spec-loop | per implementare i task in loop autonomo Ralph-style: un task per iterazione a contesto fresco, gate meccanico su verify.sh | Use spec-loop. |
openspec-sync-specs | per unire le delta spec di un change in openspec/specs/ | Use openspec-sync-specs. |
openspec-archive-change | per chiudere e archiviare un change completato | Use openspec-archive-change. |
spec-loop (scripts/ralph/loop.sh) espone i suoi guardrail come variabili d'ambiente — MAX_ITERATIONS (default 25), MAX_CONSECUTIVE_FAILURES (default 1), MAX_FIX_ATTEMPTS (default 10). I default sono bassi apposta per non sprecare crediti su un fallimento sistemico (es. session/rate limit esaurito): il loop si ferma dopo un solo tentativo a vuoto invece di bruciarne diversi prima di arrendersi. Se serve più impegno/precisione — tollerare un fallimento transitorio che si autocorregge al tentativo successivo, o lasciare più margine alla modalità di repair — questi valori si possono alzare passandoli come variabili d'ambiente al lancio (es. MAX_CONSECUTIVE_FAILURES=3 bash scripts/ralph/loop.sh <change>), a costo di consumare più crediti per run.
| Skill | Quando usarla | Comando |
|---|---|---|
spec-as-source-setup | una volta, per installare lo schema OpenSpec + script + CI + pre-commit | Use spec-as-source-setup. |
spec-verify | a ogni giro, per verificare spec/test/codice + drift semantico | Use spec-verify. |
work-review | prima di dire "ho finito": review requisito-per-requisito con evidenza file:riga | Use work-review. |
spec-ci-sync | dopo aver aggiunto una spec o cambiato stack | Use spec-ci-sync. |
spec-rebuild | per dimostrare che le spec sono la sorgente | Use spec-rebuild. |
| Skill | Quando usarla | Comando |
|---|---|---|
handoff | per congelare lo stato della sessione (.handoff/HANDOFF-NNN.md) e riprenderlo dopo, o per installare l'hook PreCompact | Use handoff. |
handoff-skill | per tracciare bug/malfunzionamenti emersi testando una skill (.handoffskill/<nome-skill>/BUG-NNN.md + STATUS.md), in vista del suo upgrade | Use handoff-skill. |
docs/policy.md — la policy in chiaro: perché esiste, come funziona, il ciclo di vita completo, il gap di migrazione da v1.x. Vive nel repo, non nel pacchetto: la CLI Tessl (0.94.0) non impacchetta docs/ anche quando è dichiarato, quindi il link punta a GitHub invece di promettere un file che non arriverebbe.
Il registry segnala questo plugin con un advisory: un W012 e due W011. Non sono un difetto da correggere — nominano una dipendenza su cui il plugin è costruito, e il conteggio ne descrive esattamente la forma.
| Warning | Cosa lo innesca |
|---|---|
W012 — dipendenza esterna non verificabile | spec-as-source-setup installa la CLI OpenSpec (npm install -g @fission-ai/openspec) e il workflow è guidato dal suo output. Togliere la dipendenza toglierebbe ciò che questo plugin serve a rendere vincolante |
W011 ×2 — contenuto di terze parti nel contesto | openspec-propose e openspec-apply-change eseguono openspec instructions --json e seguono i campi instruction, template e rules restituiti. È il modo in cui gli schemi OpenSpec restano autorevoli sulla forma degli artifact, ma significa che contenuto esterno alla conversazione influenza ciò che l'agente scrive |
Cosa resta imposto comunque: i controlli meccanici non si fidano di quel canale. check-target-ownership.sh, check-spec-links.sh e check-plan-gate.sh leggono il repository, non l'output della CLI. Uno schema che dicesse all'agente di scrivere codice fuori da una spec fallirebbe comunque l'ownership; un change senza voce approvata fallirebbe comunque il gate, a entrambe le porte. Il raggio d'azione della fiducia concessa è la forma degli artifact, non l'enforcement.
# 1. una tantum: installa schema OpenSpec + infrastruttura di enforcement
Use spec-as-source-setup.
Use handoff. # opzionale: installa anche l'hook PreCompact (Workflow E)
# 2. una volta per progetto (e poi a ogni nuovo blocco di lavoro)
Use plan-mode. # costruisce openspec/PLAN.md e ne fa approvare le voci — senza una voce approvata, openspec-propose non parte
# 3. per ogni nuova feature
Use prompt-loop. # rifinisce il prompt in ingresso (punteggio→intervista→lock) prima di proporre — assorbe l'interview di requirement-gathering
Use openspec-propose. # o openspec-explore se l'idea non è ancora chiara
Use spec-writer. # se serve scrivere/correggere una spec a mano
Use openspec-apply-change. # implementa tasks.md
Use spec-loop. # oppure: implementa tasks.md in loop autonomo (Ralph)
Use spec-verify. # verifica targets/test/drift contro la spec
Use work-review. # review requisito-per-requisito prima di dire "fatto"
Use openspec-archive-change. # chiude il change, unisce le spec
# 4. manutenzione occasionale
Use spec-ci-sync. # dopo aver cambiato stack o aggiunto test
Use spec-rebuild. # per dimostrare che le spec bastano a ricostruire il codice
# 5. quando la sessione si allunga
Use handoff. # salva lo stato prima che il contesto rischi allucinazioniCosa crea spec-as-source-setup nel progetto:
openspec/schemas/spec-as-source/ # schema OpenSpec custom con targets + [@test]
scripts/check-spec-links.sh # ogni [@test] punta a un file esistente
scripts/check-target-ownership.sh # un target non cambia senza la sua spec
scripts/build-spec-manifest.py # mappa spec → requisiti → target → test
scripts/verify.sh # esegue i tre script + test suite
scripts/check-plan-gate.sh # il gate del piano, eseguibile e pronto all'uso
.pre-commit-config.yaml # gli stessi check in locale
.github/workflows/spec-verification.yml # gli stessi check in CIIl gate del piano viene installato dal setup, non lasciato a un passaggio manuale: un progetto appena inizializzato può eseguirlo subito, prima ancora che il piano esista. plan-mode crea poi il piano che il gate giudica:
openspec/PLAN.md # il piano: goal + voci approvate che sequenziano i changeopenspec/PLAN.md non è un target di nessuna spec, di proposito: cambia a ogni transizione di stato, e dichiararlo posseduto renderebbe check-target-ownership.sh rosso a ogni voce che avanza. La spec di plan-mode governa il formato e la rule, non l'istanza — stessa scelta già fatta per .handoff/HISTORY.md.
spec-verifyI primi 4 girano anche in CI (meccanici); il quinto è una review qualitativa che solo un agente può fare:
| # | Controllo | Se fallisce | Recupero |
|---|---|---|---|
| 1 | check-spec-links | un [@test] punta a un file inesistente | crea il file di test mancante |
| 2 | check-target-ownership | un targets è cambiato senza la sua spec | annulla la modifica → aggiorna la spec → rigenera dalla spec |
| 3 | build-spec-manifest | una spec non è parsabile | correggi frontmatter / header dei requisiti |
| 4 | test suite | il comportamento contraddice la spec | allinea il codice, o cambia prima la spec |
| 5 | drift semantico | il codice fa qualcosa che la spec non descrive (o viceversa), pur con link e test a posto | aggiorna la spec (spec-writer) se il codice è corretto, altrimenti correggi il codice |
Il controllo #2 è il cuore dello spec-as-source: è ciò che OpenSpec da solo non ha. Il controllo #5 è il cuore di work-review: prova che spec e codice dicano davvero la stessa cosa, non solo che i link tra loro siano formalmente a posto.
tessl update spec-driven-development/spec-as-source
# poi RIAVVIA Claude Code: le skill si caricano all'avvio della sessioneSe tessl update non vede il plugin, non è registrato in tessl.json: installalo prima con tessl install spec-driven-development/spec-as-source.
Perché gli script funzionino, ogni openspec/specs/<capability>/spec.md deve avere:
---
targets:
- src/percorso/file.py # i file che questa capability possiede
---
# <Capability> Specification
## Purpose
...
## Requirements
### Requirement: <nome del requisito>
Descrizione normativa (SHALL/MUST).
**Verified by**: [@test] tests/percorso/test_file.py
#### Scenario: <nome scenario>
- **WHEN** ...
- **THEN** ...targets (dichiarazione di proprietà), prima del titolo;### Requirement: <nome> (formato OpenSpec, non più ID REQ-*);**Verified by**: [@test] <path> dopo la frase normativa, mai come prima riga del blocco.Una spec appena creata da openspec archive per una capability nuova parte senza frontmatter (lo scheletro di OpenSpec non ne genera uno): non è una violazione finché non viene modificata in modo non banale, momento in cui deve guadagnarsi un targets:.
Le versioni precedenti alla 2.0.0 imponevano un formato di spec diverso e fatto in casa: file flat in specs/<nome>.spec.md con header REQ-<AREA>-<NNN>. Non esiste una skill di migrazione automatica. Spostare un progetto v1.x a questa versione richiede di ricreare manualmente ogni spec sotto openspec/specs/<capability>/spec.md seguendo la nuova convenzione frontmatter + **Verified by**, e aggiornare i percorsi [@test] di conseguenza.
Costruito da Giuseppe Di Canosa. Workspace spec-driven-development.