OpenSpec: Proposte Rifiutate – Una Convenzione per la Memoria Decisionale

Nessun stato di rifiuto. Ecco il workaround.

Indice

Un agente che ha proposto e implementato “spostare la persistenza in una libreria condivisa” sei mesi fa proporrà volentieri di nuovo la stessa idea il prossimo trimestre, a meno che non esista un registro durevole che gli indichi come l’idea sia già stata indagata e rifiutata – e OpenSpec al momento non ha uno stato interno per questo scopo.

/opsx:archive è progettato per un solo esito: un cambiamento già rilasciato. Sincronizza le specifiche di delta in openspec/specs/ e sposta la cartella in openspec/changes/archive/YYYY-MM-DD-<name>/ come registro di ciò che è cambiato e perché. Non esiste un corrispettivo /opsx:reject o /opsx:abandon, e nulla nel formato dell’archivio indica a una futura proposta che “questa esatta idea è stata indagata e rifiutata”. Questo limite è più rilevante proprio nei codebase in cui OpenSpec è altrimenti un buon fit: i sistemi brownfield con un numero ridotto di contributor e agenti che esplorano periodicamente le stesse domande architetturali – unire questi due servizi, condividere questo livello di persistenza, sostituire questo confine HTTP con un import diretto.

Un archivio stratificato di decisioni che fornisce contesto a una nuova proposta

Non si tratta di un limite ipotetico. È stato sollevato direttamente ai maintainer di OpenSpec come richiesta di funzionalità, e il modo in cui quella conversazione si è svolta è importante da conoscere prima di improvvisare una soluzione propria: cosa ha effettivamente concluso il progetto determina quale convenienza vale la pena adottare. Questa guida illustra cosa succede se si fa affidamento solo su /opsx:archive, la reale discussione già avvenuta nell’issue tracker di OpenSpec, e un pattern leggero decision.md che puoi adottare oggi senza attendere – o necessitare di – supporto core.

Perché l’archiviazione da sola non registra una decisione rifiutata

Archiviare un cambiamento che hai deciso di non costruire funziona tecnicamente – la cartella viene spostata fuori dalla tua lista attiva in ogni caso. Il problema è ciò che quella cartella archiviata fallisce a comunicare quando si trova accanto a decine di cambiamenti rilasciati:

  • Nessun campo di stato. Un cambiamento archiviato appare identico che sia stato rilasciato o abbandonato dopo tre messaggi in /opsx:propose. Un compagno di team o un agente che scansa openspec/changes/archive/ non può distinguere la differenza senza aprire ogni cartella di proposta e leggere gli artifact interni.
  • Nessun segnale per verificare prima. Nulla nel workflow predefinito istruisce un agente a cercare nell’archivio prima di redigere una nuova proposta. /opsx:propose redige sulla base della tua richiesta attuale e dello stato del codebase, punto e basta – non incrocia i cambiamenti rifiutati in precedenza a meno che non glielo tu dica.
  • Specifiche di delta che non vuoi sincronizzare. Se un cambiamento rifiutato ha già specifiche di delta in bozza e lo archivi nel modo ordinario, /opsx:archive ti offrirà prima di sincronizzare quelle delta in openspec/specs/. Accettare quell’offerta insegna alle tue specifiche canoniche a descrivere un comportamento che hai deciso di non costruire, corrompendo silenziosamente il registro di “cosa fa attualmente il sistema” che ogni altra proposta legge prima di pianificare qualsiasi cosa.

Nessuna di queste cose è un bug. /opsx:archive sta facendo esattamente ciò che la sua documentazione dice che fa: completare un cambiamento che è stato rilasciato. Il caso di rifiuto si trova fuori da quel scope documentato volutamente, e la guida al team-workflow di OpenSpec è esplicita nel dire che la maggior parte di ciò che consiglia – convenzioni sui branch, ordine di revisione dei PR, quando archiviare – è convenienza stratificata sopra lo strumento, non qualcosa che OpenSpec applica per te. Gestire un rifiuto è un’altra convenzione che puoi definire tu stesso, e la CLI ti dà già la flag di cui hai bisogno per farlo in modo pulito: passa --skip-specs quando archivi un cambiamento che non stai rilasciando, così openspec archive investigate-shared-persistence --skip-specs archivia la cartella senza toccare openspec/specs/ in nessun modo.

Cosa hanno effettivamente deciso i maintainer di OpenSpec riguardo al supporto ADR

Prima di inventare una convenzione interna, vale la pena leggere come questa esatta domanda si è risolta pubblicamente, perché la risoluzione è più specifica – e più interessante – di un semplice “no”. La GitHub issue #557 è stata aperta nel gennaio 2026 con una richiesta di supporto di primo livello per le Architecture Decision Record (ADR): registri durevoli che persistono indipendentemente dal ciclo di vita di un singolo cambiamento, in modo che una decisione rifiutata o superata rimanga visibile a ogni futura proposta. Un contributor ha persino aperto una pull request che la implementava.

Quello che è seguito è stato sette mesi di sostanziale discussione tra il maintainer principale Tabish Bidiwale (@TabishB) e diversi membri della comunità profondamente coinvolti, coprendo registri immutabili vs mutabili, se un ADR appartenga alla fase di ricerca o alla fase di design, la proprietà tra cambiamenti quando una decisione si propaga in una dozzina di cambiamenti successivi, e come le ADR si relazionino alle specifiche come descrizione “autentica” del sistema. La cornice iniziale di Tabish Bidiwale ha impostato la direzione su cui il thread è poi approdato: OpenSpec dovrebbe rimanere leggero di default e rendere i workflow specializzati come le ADR configurabili tramite il suo sistema di schema, invece di integrarli nel core. Un membro della comunità ha successivamente riassunto dove la discussione è approdata:

I workflow ADR sono preziosi, ma OpenSpec attualmente non ha supporto ADR di primo livello/nativo… la direzione discussa qui è mantenere il workflow predefinito leggero e rendere i workflow specializzati configurabili.

Il maintainer Clay Good (@clay-good) ha chiuso l’issue su questa base nell’agosto 2026 e l’ha spostata nella GitHub Discussion #1553 in modo che la conversazione potesse continuare a evolversi senza restare aperta come bug irrisolto. È una decisione ragionevole per uno strumento il cui intero pitch è evitare la cerimonia stile Spec Kit di default. Significa anche che la soluzione vive un livello più in alto, in uno di questi due posti:

  1. Uno schema della comunità. Lo schema spec-driven-with-adr, costruito dal tecnico advisor di OpenSpec Hari Krishnan (@harikrishnan83) e documentato su intent-driven.dev, aggiunge un quinto artifact alla pipeline predefinita di OpenSpec a quattro artifact. Esiste perché lo schema predefinito perde il ragionamento di design.md nel momento in cui un cambiamento viene archiviato – solo le delta delle specifiche vengono sincronizzate in avanti, quindi il “perché” dietro una decisione scompare con il cambiamento a meno che altro non lo conservi.
  2. Una convenzione a livello di repository. Un piccolo file decision.md fatto a mano più una regola di denominazione, che costa nulla da adottare e non richiede l’installazione di uno schema personalizzato.

Il resto di questa guida copre l’opzione due in profondità, poiché è il punto di partenza con minor attrito per la maggior parte dei team – e, come mostra la sezione sullo schema della comunità qui sotto, è compatibile con il passaggio a quello strumento più pesante in seguito, se il tuo registro di rifiuti cresce abbastanza da meritarselo.

La convenzione decision.md per registrare un cambiamento rifiutato

Struttura un’indagine rifiutata allo stesso modo in cui faresti per uno rilasciato, ma fermati prima di sincronizzare qualsiasi delta, e aggiungi un file che dichiari l’esito chiaramente:

openspec/
  changes/
    archive/
      2026-09-16-rejected-shared-persistence-layer/
        proposal.md
        decision.md

decision.md risponde alle stesse quattro domande a cui risponde un appropriato Architecture Decision Record – cosa è stato deciso, perché, quali alternative esistevano e cosa cambierebbe la risposta:

# Decision

Status: Rifiutata

## Decision

Non sostituire il confine HTTP tra servizi con un import di pacchetto
diretto tra i due servizi Go.

## Reasons

- Aumenta l'accoppiamento a tempo di compilazione tra servizi distribuiti indipendentemente.
- Rende il livello di persistenza un contratto implicito e non documentato.
- Il beneficio misurato (latenza, duplicazione del codice) era più piccolo
  del costo di accoppiamento in questo codebase.

## Alternatives considered

- Modulo Go interno condiviso -- rifiutato per la stessa ragione di accoppiamento.
- gRPC invece di HTTP -- differito, non rifiutato; riesaminare se l'overhead
  di HTTP diventa un collo di bottiglia misurato.

## Reconsider only if

- I due servizi vengono intenzionalmente uniti in un unico deployable, oppure
- Le misurazioni di latenza mostrano che il hop HTTP è un collo di bottiglia provato.

## Related

- Regola architetturale: i servizi comunicano tramite HTTP, non pacchetti condivisi.

L’unica regola rigida che fa funzionare tutta questa convenzione: non eseguire il passo di sincronizzazione per un cambiamento rifiutato. Se /opsx:propose ha già redatto specifiche di delta prima che decidessi contro il cambiamento, usa la flag che la CLI ti dà già per esattamente questa situazione:

openspec archive investigate-shared-persistence --skip-specs

--skip-specs dice a openspec archive di archiviare il cambiamento senza toccare openspec/specs/ in nessun modo, che è il default più sicuro per qualsiasi cosa tu stia archiviando senza rilasciare. Accettare il prompt di sincronizzazione ordinario invece fonderebbe le specifiche di delta dell’idea rifiutata nelle tue specifiche canoniche, e le specifiche canoniche openspec/specs/ dovrebbero descrivere cosa fa attualmente il sistema, non ogni idea che è stata redatta e rifiutata. Se un cambiamento produce permanentemente nessun cambiamento alle specifiche per una ragione strutturale – una cartella di pura indagine, per esempio – OpenSpec supporta anche la dichiarazione di skip_specs: true in .openspec.yaml di quel cambiamento in modo che venga archiviato pulito senza la flag ogni volta.

Denominare i cambiamenti rifiutati affinché umani e agenti possano scansionare l’archivio

Un file decision.md aiuta solo se qualcuno apre la cartella. Prefissa il nome della cartella con l’esito in modo che sia un umano che scansiona ls openspec/changes/archive/ sia un agente che elenca i cambiamenti possano capire lo stato senza aprire un singolo file:

2026-09-16-rejected-shared-persistence-layer/
2026-09-20-abandoned-react-router-migration/
2026-10-01-superseded-old-auth-design/
2026-10-10-add-project-filtering/          # rilasciato, nessun prefisso necessario

Questo riflette il vocabolario di stato già raccomandato per i registri di decisione autonomi – proposta, accettata, superata, deprecata – applicato all’archivio stesso di OpenSpec invece che a una cartella separata docs/decisions/. Mantieni il vocabolario piccolo. Tre o quattro prefissi consistenti battono una riga di stato in testo libero che ogni proposta ortografa leggermente in modo diverso.

Come fare in modo che il tuo agente controlli l’archivio prima di proporre di nuovo

La denominazione e un file decision.md risolvono la ricercabilità per un umano che scansiona la cartella. Non fanno nulla da soli per far sì che un agente cerchi nell’archivio prima di redigere una nuova proposta – questo deve essere un’istruzione esplicita, perché /opsx:propose non lo fa di default, e nessuna quantità di denominazione ordinata dei file lo cambia da sola.

Due posti dove mettere quell’istruzione, in linea con come OpenSpec si aspetta già che la guida specifica del progetto venga iniettata:

In openspec/config.yaml, sotto il campo context: che viene iniettato in ogni richiesta di pianificazione (fai attenzione al limite di 50KB coperto nella Guida rapida OpenSpec):

context: |
  Prima di proporre un cambiamento, cerca in openspec/changes/archive cartelle
  prefissate con "rejected-" o "abandoned-" che descrivano un'idea materialmente simile.
  Se ne esiste una, riassumi la sua decision.md e dichiara cosa è cambiato prima
  di proporre di nuovo l'idea. Non riesaminare una decisione rifiutata senza nuove evidenze.

In AGENTS.md o nelle istruzioni specifiche del tuo progetto, come una regola permanente piuttosto che un blocco di contesto per richiesta:

## Cambiamenti OpenSpec rifiutati

Quando una proposta viene indagata e rifiutata:

1. Non sincronizzare o applicare le sue specifiche di delta.
2. Aggiungi `decision.md` con Stato, Decisione, Ragioni, Alternative
   considerate e "Riesaminare solo se".
3. Prefissa il nome della cartella archiviata: `rejected-<name>` o `abandoned-<name>`.
4. Prima di proporre un cambiamento materialmente simile, cerca in
   `openspec/changes/archive/` e fai riferimento alla decisione precedente.
5. Non riaprire una decisione rifiutata a meno che le sue condizioni
   documentate per il riesame non siano effettivamente cambiate.
flowchart TD A[Nuova idea degna di un cambiamento] --> B{Cerca in openspec/changes/archive} B -->|Trovata decisione rifiutata simile| C[Riassumi la precedente decision.md] C --> D{Le condizioni per il riesame sono cambiate?} D -->|No| E[Non riproporre. Fai riferimento alla decisione.] D -->|Sì| F["/opsx:propose con il contesto cambiato dichiarato"] B -->|Niente di simile trovato| F

Nessuna di queste istruzioni garantisce la conformità – un agente può comunque saltare il passo di ricerca, nello stesso modo in cui può saltare la lettura di qualsiasi altro contesto che inietti. Ma è la differenza tra “l’informazione esiste da qualche parte nel repository” e “l’agente viene detto, ogni volta, di andare a cercarla”, e solo la seconda riduce effettivamente in pratica le indagini ripetute.

Un Esempio Pratico: Rifiutare una Proposta, poi Riesaminarla Correttamente

Metti insieme i pezzi su un caso concreto. Supponiamo che un compagno di team chieda a un agente di guardare alla sostituzione di una chiamata HTTP tra servizi con un import diretto di pacchetto Go, per ridurre la latenza di rete.

  1. Esplora, poi proponi. /opsx:explore legge entrambi i servizi, e /opsx:propose replace-http-with-direct-import redige una proposta, un documento di design che bilancia il guadagno in latenza contro il costo di accoppiamento, e una specifica di delta in bozza.
  2. Indaga e rifiuta. Dopo aver revisionato il documento di design, il team decide che il costo di accoppiamento – due servizi distribuiti indipendentemente che condividono ora una dipendenza a tempo di compilazione – supera un guadagno in latenza che nessuno ha effettivamente misurato come problema. Non viene costruito nulla.
  3. Archivia senza sincronizzare. Invece di eliminare la cartella, esegui openspec archive replace-http-with-direct-import --skip-specs, poi aggiungi decision.md alla cartella archiviata con Status: Rifiutata, le ragioni sopra, e una clausola Riesaminare solo se che nomina la condizione che cambierebbe la risposta – per esempio, “le misurazioni di latenza mostrano che il hop HTTP è un collo di bottiglia provato”. Rinomina la cartella con un prefisso rejected- in modo che appaia come openspec/changes/archive/2026-09-16-rejected-replace-http-with-direct-import/.
  4. Mesi dopo, qualcuno la riapre. Un contributor diverso, o lo stesso agente in una sessione fresca, viene chiesto di “accelerare la chiamata da checkout a inventario” e inizia a redigere una proposta che assomiglia molto alla stessa idea. Perché openspec/config.yaml istruisce l’agente a cercare nell’archivio prima, trova la cartella rifiutata, legge decision.md, e riporta indietro: “Un cambiamento materialmente simile è stato proposto e rifiutato il 2026-09-16 per ragioni di accoppiamento. La condizione per il riesame era ’le misurazioni di latenza mostrano che il hop HTTP è un collo di bottiglia provato.’ Hai nuove misurazioni, o è un problema diverso?”
  5. Il team fornisce nuove evidenze. Se il profiling ora mostra che il hop HTTP domina genuinamente la latenza di checkout, quella è esattamente la circostanza cambiata che la decision.md originale chiedeva. L’agente procede con /opsx:propose, e la decision.md della nuova proposta – una volta che anche questa viene archiviata, accettata o rifiutata – fa riferimento alla precedente sotto Related, in modo che l’archivio appaia come una storia di decisioni continua piuttosto che due cartelle non collegate che succedono di descrivere la stessa idea.

Quel quinto passo è l’intero punto della convenzione. Senza di esso, il passo 4 o non avviene affatto – l’agente semplicemente re-indaga da zero – o avviene per caso, perché un umano si ricordava della conversazione precedente. Il file decision.md e l’istruzione di ricerca nell’archivio trasformano “qualcuno potrebbe ricordarselo” in qualcosa che il workflow verifica effettivamente.

L’Archivio di OpenSpec vs. un Log ADR Dedicato: Chi Possiede Cosa

Una volta che stai mantenendo file decision.md dentro l’archivio, vale la pena essere espliciti su quale artifact risponde a quale domanda, in modo che la convenzione non diventi silenziosamente una documentazione duplicata:

Artifact Risponde a
openspec/specs/ Cosa fa attualmente il sistema?
openspec/changes/<name>/ (attivo) Cosa stiamo proponendo di cambiare, proprio ora?
openspec/changes/archive/<name>/ Cosa è cambiato (o è stato rifiutato) in passato, e perché?
docs/adr/ (autonomo, neutrale rispetto agli strumenti) Quale regola architetturale durevole abbiamo imparato, indipendente da un singolo cambiamento?

Per una decisione abbastanza ristretta da appartenere a un’indagine singola – “abbiamo guardato a condividere questo livello di persistenza e abbiamo detto di no” – la convenzione della decision.md-dentro-l’archivio sopra è sufficiente. Per una decisione che dovrebbe sopravvivere e vincolare molti cambiamenti futuri – “i servizi comunicano tramite HTTP, mai pacchetti condivisi” – promuovila a un Architecture Decision Record autonomo in docs/adr/, e fai sì che la decision.md del cambiamento rifiutato lo faccia riferimento sotto Related. Quella separazione mantiene l’archivio di OpenSpec focalizzato sulle indagini individuali mentre il log ADR detiene il piccolo numero di regole che dovrebbero sopravvivere al ciclo di vita di qualsiasi singolo strumento – incluso una futura migrazione lontano da OpenSpec interamente.

Quando Adottare lo Schema spec-driven-with-adr Invece

La convenzione fatta a mano sopra costa nulla e si adatta in quindici minuti di setup, il che la rende il default giusto. Ma vale la pena capire cosa fa esattamente l’alternativa più strutturata prima di decidere di aver superato un prefisso di denominazione.

spec-driven-with-adr inserisce un quinto artifact, adr, tra design e tasks nella pipeline di OpenSpec. Invece di scrivere contenuto ADR direttamente nella cartella del cambiamento, il passo adr produce un breve adr.md di revisione locale al cambiamento e, quando il cambiamento introduce un impegno architetturale genuinamente durevole, un record numerato alla radice del repository – /adr/0042-use-postgres-for-catalog.md, fratello di openspec/, non annidato dentro di esso. Ogni ADR che lo schema crea è immutabile una volta accettata: le istruzioni dello schema stesso chiamano questo una “regola di ferro” – non si mai editare lo stato, il corpo o la data di un record accettato. Per cambiare una decisione precedente, si scrive una nuova ADR il cui campo Supersedes: nomina la vecchia, e i design futuri percorrono quella catena di sostituzione per sapere quali decisioni sono ancora in vigore. Questa è una versione più rigorosa dell’esatta idea di “riesaminare solo se” nella convenzione decision.md sopra, applicata dallo schema piuttosto che lasciata alla memoria umana di scriverlo.

Vale la pena essere precisi su cosa questo schema faccia e cosa non risolva. È costruito per decisioni che vengono accettate e devono sopravvivere all’archiviazione – Postgres su DynamoDB, JWT su sessioni cookie – non per proposte che sono state indagate e rifiutate senza rilasciare nulla. Un’indagine rifiutata non ha comunque un posto ovvio dove vivere sotto questo schema; si stratificherebbe la stessa convenzione decision.md-e-denominazione da questa guida sopra di essa, facendo semplicemente riferimento a record in /adr/ invece che a una cartella autonoma docs/adr/.

Rivolgiti ad essa una volta che noti uno di questi:

  • Il tuo numero di decisioni rifiutate è grande abbastanza che greppare openspec/changes/archive/ per prefissi smette di essere veloce.
  • Vuoi decisioni architetturali durevoli validate e incrociate automaticamente contro ogni nuovo design, piuttosto che per convenzione e grep.
  • Multipli contributor continuano a inventare forme leggermente diverse di decision.md, e vuoi uno schema che imponga un formato immutabile, numerato e unico.

Installare uno schema personalizzato è un impegno maggiore di una convenzione di denominazione – cambia ciò che /opsx:propose genera per ogni cambiamento futuro, non solo quelli rifiutati – quindi trattalo come un passo verso l’alto una volta che la versione leggera è visibilmente sotto sforzo, non come una mossa predefinita iniziale.

Conclusione

L’archivio di OpenSpec è stato progettato attorno a un solo esito – un cambiamento che è stato rilasciato – e i suoi stessi maintainer sono stati espliciti, dopo una discussione pubblica di sette mesi, che il supporto di primo livello per rifiuti o ADR non arriverà al workflow core presto. Questo lascia la soluzione dove OpenSpec mette già la maggior parte delle sue convenzioni di team: nel tuo repository, non nello strumento. Un file decision.md, la flag --skip-specs su archive, un prefisso di denominazione rejected-/abandoned-, e un’istruzione esplicita che dice all’agente di cercare nell’archivio prima di proporre sono sufficienti per fermare la maggior parte delle indagini ripetute. Rivolgiti allo schema spec-driven-with-adr solo una volta che quella convenzione leggera è genuinamente sotto sforzo per il numero di decisioni che stai tracciando – e anche in quel caso, mantieni la distinzione chiara: gestisce decisioni che hai accettato e vuoi far sopravvivere all’archiviazione, non quelle che hai rifiutato.

Iscriviti

Ricevi nuovi articoli su sistemi, infrastruttura e ingegneria AI.