CtrlK
BlogDocsLog inGet started
Tessl Logo

spec-driven-development/spec-as-source

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 and a packaged handoff memory: the skill ships the exporter, importer, graph model, facts pipeline, Neo4j Compose runtime and operating guide to load handoffs into a local, authenticated Neo4j graph and query them.

68

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

handoff-template.mdskills/handoff/references/

HANDOFF-NNN.md — Template Completo

Frontmatter strutturato

Ogni nuovo handoff apre con un frontmatter YAML prima del corpo: il corpo, le sue sezioni e il loro ordine restano quelli di sempre. I campi approvati sono tutti obbligatori come chiavi; un valore sconosciuto o non applicabile si scrive null (o [] per le liste), mai omettendo la chiave.

  • handoff coincide con il nome del file; project_id è l'id canonico, mai vuoto.
  • date è ISO con fuso (2026-09-30T19:45:00+02:00): una data senza fuso viene rifiutata.
  • continues punta a un handoff precedente esistente nella stessa cartella: può saltare numeri o creare rami, non può puntare a sé, al futuro o a un file che non c'è.
  • agent_session e parent_session sono id reali dell'agent-registry, mai inventati.
  • Se il progetto ha openspec/PLAN.md, plan_entry e change si leggono dal piano corrente, non a memoria. Il Goal verbatim, la Posizione nel piano e i riferimenti alle entry nei Next Steps restano obbligatori come prima: il frontmatter non li sostituisce.
  • Sintassi YAML supportata: chiave: valore, stringhe tra virgolette, liste [a, b] o - voce, la mappa operator indentata di due spazi, commenti #. Ancore, tag, blocchi |/> e chiavi duplicate vengono rifiutati.

Il Workflow B valida il frontmatter con python3 skills/handoff/scripts/graph_export.py --check-only --as HANDOFF-NNN.md <bozza> prima di scrivere lo snapshot.

---
handoff: HANDOFF-[NNN]
project_id: [id canonico del progetto, es. spec-as-source]
date: "[YYYY-MM-DDTHH:MM:SS+HH:MM]"   # ISO con fuso, obbligatorio
continues: HANDOFF-[M]                # un handoff precedente esistente in questa cartella (anche saltando numeri), null per il primo
agent_session: [id agent-registry della sessione, o null]
parent_session: null                  # id agent-registry del parent SOLO se scritto da un subagente
operator:
  person: [persona, o null]
  provider: [claude | codex | ...]
  models: [modello-1, modello-2]
client: null                          # null = nessun cliente
work_context: [sviluppo]              # lista aperta, [] se nessuno
track: null                           # il Filone, se esiste
plan_entry: E[NN]                     # dal piano corrente, null senza openspec/PLAN.md
change: [nome del change attivo, o null]
deadline: null
---
# HANDOFF-[NNN] — [Brand/Progetto]
> Data: [YYYY-MM-DD HH:MM] | Sessione: #[N] | Continua da: HANDOFF-[N-1].md (o —)
> Progetto: [nome] | Operatore: [persona + modello AI es. "Giuseppe + Claude Sonnet 4.6"]
> Cliente: [se applicabile] | Deadline: [se applicabile]

---

## 🎯 Goal
[Se il progetto ha `openspec/PLAN.md`: riporta VERBATIM la riga `goal:` del piano e cita il file
come sorgente — es. "(fonte: openspec/PLAN.md)". Non riscriverlo con parole tue: due obiettivi
formulati in modo indipendente divergono, e la divergenza non si vede.
Altrimenti: obiettivo finale, 2-4 righe. Cosa si sta costruendo/creando, per chi, con quale
deliverable atteso. Questa sezione cambia raramente — solo se lo scope del progetto cambia davvero.]

## 📍 Posizione nel piano
[SOLO se il progetto ha `openspec/PLAN.md`. Se non ce l'ha, ometti l'intera sezione.
Si compila LEGGENDO il file, non a memoria: apri `openspec/PLAN.md` e riporta gli stati reali.

- **Voce corrente**: E<NN> — <titolo> (stato: <draft|approved|in-progress|done|dropped>)
- **Cosa stiamo facendo per il goal**: 1-2 righe — in che modo questa voce avvicina il `done-when` del piano
- **Resta da fare**: E<NN> (stato), E<NN> (stato) — solo le voci non ancora `done` né `dropped`
- **Piano**: `openspec/PLAN.md` — gate: `bash scripts/check-plan-gate.sh`

Se una voce legata a un change già sotto `openspec/changes/archive/` non è `done`,
segnalalo qui: è la stessa staleness che riporta il gate.]

## 🔄 Stato volatile
[Sezione OBBLIGATORIA. Qui NON si scrivono valori: si scrivono **i comandi che li
producono**. Conteggi, stati, numeri di commit, task aperti, esiti dei test e del gate
invecchiano fra il momento in cui scrivi e il momento in cui qualcuno legge — spesso
prima: "16 commit locali" era già sbagliato quando è stato scritto, perché il commit
di questo handoff non esisteva ancora. Chi legge deve **rigenerarli, non ereditarli**.

Regola che generalizza il blocco qui sotto: **ogni conteggio citato altrove in questo
documento ha qui il comando che lo rigenera.** Se un numero non ha il suo comando,
o il comando manca o il numero non andava scritto.

~~~bash
git log --oneline origin/main..HEAD | wc -l        # commit non pushati
bash scripts/verify.sh | tail -2                   # test verdi/rossi
bash scripts/check-plan-gate.sh                    # stato del gate
grep -c '^- \[ \]' openspec/changes/*/tasks.md     # task aperti per change
grep -E '^### E[0-9]+|^- \*\*State\*\*:' openspec/PLAN.md | paste - -   # stati delle voci
~~~

Adatta i comandi al progetto: questo blocco è un esempio funzionante, non una lista fissa.
Un comando che in questo progetto non esiste si toglie, non si lascia per inerzia.

Eccezione, unica: se un valore non è rigenerabile da nessun comando, scrivilo datato —
`3 render approvati dal cliente (al 2026-08-04 17:16 — riverificare)`. È un ripiego,
non la forma normale.]

## ✅ Current Progress

### Completato ✓
- [x] [Task specifico con riferimento a file/asset — es. "Homepage hero generata, salvata in /assets/hero-v3.png"]
- [x] [Task specifico]

### In corso / Sospeso
- [ ] [Task — a che punto è, cosa manca]
- [ ] [Task non iniziato]

## 💡 What Worked
[Approcci, prompt, comandi, decisioni con esito positivo.
Riporta i dettagli tecnici ESATTI — il prossimo operatore deve poterli riusare.]

<!-- Graph export: when present, prefix stable reusable entries with `Decision:` or `Fact:` in this existing section or Current Progress. Omit the label when there is no real entry. -->

- **[Tecnica/Approccio]**: [descrizione] → [risultato] ([perché ha funzionato])
- Esempio: **Prompt NanoBanana hero ESAC**: "single red LED strip, deep ocean black bg, cinematic 4K, product hero shot, no text" → qualità eccellente al primo tentativo (lighting semplice = più controllo in post)

## ❌ What Didn't Work
[Errori, vicoli ciechi, approcci falliti con parametri esatti.
Ogni voce qui è tempo risparmiato al prossimo operatore. MAI lasciare vuota per ottimismo.]

- **[Approccio fallito]**: [cosa è andato storto] → [cosa NON rifare]
- Esempio: **Seedance speed 1.0**: movimento troppo agitato, effetto non premium → usare max 0.6 per contenuti luxury

Se nulla è fallito: "Nessun vicolo cieco rilevante in questa sessione."

<!-- Graph export: every meaningful bullet or paragraph in this existing section becomes a `failure` record; the no-failure sentence above, empty entries and fenced examples do not. -->

## 🚀 Next Steps
[Lista numerata, ordinata per priorità. Item #1 eseguibile subito senza domande.
Se il progetto ha `openspec/PLAN.md`: ogni voce cita l'id della entry che fa avanzare
(es. "[E03]"), oppure dichiara esplicitamente "fuori piano". Un next step che non sa a
quale entry appartiene è il sintomo che il piano va aggiornato, non che la citazione va omessa.]

1. [E<NN>] [Azione concreta — tool, file input, output atteso]
2. [E<NN>] [Azione concreta]
3. [fuori piano] [Azione concreta]

## 📎 Context Importante (opzionale)
[Solo se serve: vincoli cliente, decisioni architetturali con motivazione,
convenzioni di naming, link a file di supporto. Non duplicare sezioni sopra.
Qui va anche il contesto operativo che vive fuori dal repo — un progetto di prova,
una cartella temporanea, un file di scratch: se serve a un Next Step e non è
versionato, o lo scrivi qui con il comando per ricrearlo, o sparisce con la sessione.]
- Credenziali: → vedi .env (mai nel testo)
- Riferimenti: `.handoff/PROMPTS.md`, `.handoff/CLIENTS.md`

Le tre categorie di fatto

Un handoff trasporta tre tipi di affermazione. Hanno tempi di vita diversi e vanno scritte in forme diverse — dare loro la stessa forma è il modo in cui un handoff perde informazione senza che nessuno se ne accorga.

CategoriaCosa contieneCome si scrive
Fatti stabilidecisioni, motivazioni, What Worked, What Didn't Workprosa, esattamente come sempre — non invecchiano, e infatti si trasferiscono intatti
Fatti volatiliconteggi, stati, numeri di commit, task aperti, esiti di test e del gateil comando che li produce, mai il valore congelato — sezione 🔄 Stato volatile
Puntatoriqualsiasi affermazione sul contenuto di un filefile § sezione verificabile, mai un riassunto senza fonte

Perché. Misurato su un passaggio di consegne reale — HANDOFF-009 letto da un agente in finestra nuova, 2026-08-04:

  • l'handoff diceva "16 commit locali"; erano 17. Il numero era già sbagliato quando è stato scritto, perché non contava il commit dell'handoff stesso. L'agente ha verificato il disco e ha comunque riportato 16: nulla segnalava quel numero come deperibile, quindi l'ha ereditato invece di rigenerarlo;
  • l'handoff diceva "il task 7.1(e) contraddice ROUTER.md" senza dire dove. L'agente ha ricostruito la tesi e ha citato "§ Reporting", una sezione che non esiste: le sezioni vere sono skills/skill-router/ROUTER.md §§ Stage 2C, Reconciling 2C and 2D, Stage 5 — Trace. Un riassunto senza fonte non è un riassunto: è un invito a inventarne una.

Le motivazioni si trasferiscono, i numeri no. Il formato non deve dargli la stessa autorevolezza.

Regola dei puntatori

Ogni affermazione su cosa contiene un file si cita come file § sezione: il path e il titolo della sezione sotto cui l'affermazione è verificabile. Il numero di riga può accompagnare il titolo, non può sostituirlo — le righe si spostano a ogni edit, i titoli no.

  • ✅ skills/skill-router/ROUTER.md § Stage 5 — Trace (riga 274)
  • ❌ "ROUTER.md dice il contrario" — chi legge non può controllare, quindi ricostruisce
  • ❌ ROUTER.md:274 da solo — fra due sessioni punta a un'altra riga e nessuno se ne accorge

Un puntatore rotto si vede al primo grep; un riassunto sbagliato no. Per questo la forma verificabile vale anche quando costa una riga in più.


Regole di aggiornamento (quando HANDOFF già esiste)

Quando crei HANDOFF-NNN.md a partire dal precedente:

  1. Incrementa numero sessione, aggiorna data.
  2. Migra i Next Steps completati → Current Progress (checked ✓).
  3. Appendi nuove voci a What Worked / What Didn't Work — non cancellare mai le precedenti. Se serve chiarezza, prefissa con (S3) per indicare la sessione.
  4. Riscrivi Next Steps da zero per la prossima sessione. Se esiste openspec/PLAN.md, la continuità di medio periodo vive lì, non qui: prima di riscrivere, controlla che ogni intenzione che sopravvive alla sessione sia una entry del piano. Un item che ricompare come "ereditato" di handoff in handoff è una entry mancante, non un next step.
  5. Goal — tocca solo se lo scope è cambiato davvero. Con un piano presente, il Goal si aggiorna cambiando goal: in openspec/PLAN.md e ricopiandolo qui, mai solo qui.
  6. Posizione nel piano — rigenerala sempre rileggendo openspec/PLAN.md, mai copiata dall'handoff precedente: è una fotografia dello stato corrente, e una fotografia riportata in avanti è esattamente il modo in cui il piano e il racconto smettono di coincidere.
  7. Stato volatile — rigenerala sempre, mai ereditata dall'handoff precedente. Stessa disciplina della posizione nel piano, per la stessa ragione: qui però si rigenera la lista dei comandi, non i loro output. Se durante la sessione hai citato un conteggio nuovo da qualche parte nel documento, il suo comando entra in questa sezione prima di salvare.

README.md

tile.json