Scrivo su Dev.to da due posti: dall'editor del portale e da file Markdown in un repo Git, con l'aiuto di Claude Code, come racconto in Un agente AI come editor dei miei articoli Dev.to. Il portale non tiene la storia delle modifiche, e con due punti di scrittura prima o poi uno dei due sovrascrive l'altro senza che nessuno se ne accorga. Per questo ho scritto uno strumento che tiene gli articoli come file, mi dice quando un lato è cambiato rispetto all'altro e non sceglie mai al posto mio quando sono cambiati entrambi. Si chiama devto-workbench, è in Python e usa solo la libreria standard.
Un file per articolo
Ogni articolo è un file articles/<id>-<slug>.md. Il front-matter ha una riga per campo, nella forma chiave: <valore JSON>, così non serve nessuna libreria per leggerlo. Sotto c'è il body_markdown esattamente come lo restituisce Dev.to:
---
id: 1234567
title: "Titolo"
published: false
tags: ["git", "linux"]
url: "https://dev.to/..."
canonical_url: "https://dev.to/..."
cover_image: null
devto_synced_hash: "sha256 del body come era su Dev.to all'ultimo sync"
stato: "revisionata"
reviewed: true
---
Il testo dell'articolo...
L'identificatore è l'id, non il nome del file, che dipende dal titolo e può restare indietro. Il testo non va normalizzato: niente spazi tolti, niente markdown «ripulito». Il motivo è l'hash di cui parlo subito.
Perché un hash e non una data
Per sapere se il portale è cambiato la prima idea è confrontare le date di modifica. Negli elenchi /articles/me e /articles/me/unpublished il campo edited_at è vuoto, quindi non c'è niente da confrontare.
Ho usato l'hash. devto_synced_hash è lo sha256 del body remoto all'ultimo sync riuscito. Per ogni articolo lo strumento mette a confronto tre cose: l'hash del body locale, quello del body remoto e quello salvato. Da lì ricava lo stato:
| Stato | Significato | Cosa si fa |
|---|---|---|
| allineato | nessuno ha cambiato nulla | niente |
| remoto più recente | è cambiato solo il portale | pull |
| locale da pushare | è cambiato solo il file | push |
| conflitto | sono cambiati entrambi, in modo diverso | decido io |
| solo su Dev.to / solo locale | manca un lato | importo o creo |
Non faccio merge automatici del testo. Su prosa un merge a righe mi sembra produrre risultati incoerenti, e in caso di conflitto preferisco guardare le due versioni e scegliere io.
La pagina web
Dalla radice del repo con gli articoli:
python3 /percorso/di/devto-workbench/web/server.py
e poi http://localhost:8787. Il server legge articles/ dalla cartella da cui lo lancio, oppure da ARTICLES_DIR. Fa anche da proxy verso l'API di Dev.to: il browser da solo non può mandare l'header api-key (lo blocca il CORS), quindi la chiave la aggiunge il server leggendola dall'ambiente.
La pagina ha tre viste, elenco, card e kanban, e mostra per ogni articolo lo stato di sync. Un pulsante compare solo quando l'azione non è ambigua: «Scarica in locale» per un articolo solo su Dev.to, «Scarica aggiornamento» quando è cambiato solo il portale, «Manda online» quando è cambiato solo il file. In caso di conflitto non c'è nessun pulsante di scrittura, solo «Differenze», che mostra il confronto riga per riga, unificato o fianco a fianco.
Il kanban e il campo stato
Il kanban ha quattro colonne: Bozza, Revisionata, Pronta, Pubblicato. Trascinare una card tra le prime tre scrive il campo stato nel front-matter del file, senza chiamare Dev.to. La colonna Pubblicato non accetta il drop: pubblicare è un'azione a parte e non si fa trascinando una card.
«Pubblicato» non ha un valore proprio in stato: lo ricava il server da published: true. Se fosse scritto in due posti, prima o poi i due direbbero cose diverse. stato e reviewed sono campi solo locali. Non vanno mai su Dev.to, non entrano nell'hash e si scrivono nel file solo se diversi dal valore di default, così spostare una card cambia una riga sola. Lo stato di sync resta un badge sulla card, indipendente dalla colonna.
Con Claude Code
Gli articoli stanno in un repo privato che include il tool come submodule git. Nel repo del tool c'è una skill per Claude Code, devto-articles, che descrive le operazioni in cui il controllo a hash evita di sovrascrivere una modifica altrui: creare un articolo nuovo, pull, push, conflitti, pubblicazione. Per parlare con Dev.to usa un server MCP, nel mio caso un fork di devto-mcp. Non contiene le norme di scrittura, che sono la mia voce e restano in un repo privato.
La skill è testo, non codice: se l'agente non la segue, il controllo non scatta da solo. Per questo la pagina web fa lo stesso controllo per conto suo prima di ogni pull e push.
Esportare solo gli articoli pubblicati
Volevo alimentare una wiki con gli articoli, senza esporre le bozze. scripts/export_published.py copia in un'altra cartella solo gli articoli con published: true:
python3 scripts/export_published.py --dest /percorso/altro-repo/raw/devto
Il body resta identico byte per byte e il front-matter si riduce a id, title, url e tags. Il motivo è che il server della wiki che uso calcola il checksum sull'intero file: con reviewed o devto_synced_hash dentro, ogni articolo risulterebbe modificato a ogni sync. Una seconda esecuzione senza novità non scrive nulla, --dry-run mostra cosa farebbe, --prune toglie gli articoli esportati che non sono più pubblicati e la destinazione non può coincidere con la cartella degli articoli.
Lo stesso si può fare dalla pagina web, lanciando il server con EXPORT_DEST=/percorso/altro-repo/raw/devto. Il pulsante «Esporta pubblicati» mostra prima l'anteprima, poi esporta e stampa il comando per il commit nell'altro repo, che resta a mano. La destinazione si decide solo all'avvio: una richiesta alla pagina non può sceglierla.
Lo script ha dei test con unittest. Per controllare che i test servissero a qualcosa ho indebolito di proposito la regola sul published, e quello giusto ha fallito.
Cosa non fa
- Non fa merge del testo: in caso di conflitto sei tu a decidere.
- Non pubblica dalla pagina web. Non l'ho ancora implementato, e vorrei una conferma esplicita prima di mandare qualcosa online.
- L'ho provato solo su Linux con Python 3.14. Il codice usa la sintassi
X | None, quindi serve almeno la 3.10. Su Windows non l'ho provato. - Lo script di esportazione esporta quello che dice il front-matter locale. La pagina, in anteprima, segnala invece gli articoli pubblicati sul portale che nel file sono ancora bozze e che l'esportazione salterebbe.
Questo articolo fa parte della serie «Un repo per i miei articoli». Nella serie c'è anche Un server MCP per una wiki in Markdown: stdio per modificare, HTTP per servire, sul server della wiki costruita dagli articoli pubblicati, e Rendere pubblico un repo: cosa ho controllato prima del primo push, su cosa controllare prima di rendere pubblico un repo.
Il codice e il README sono su github.com/minnogit/devto-workbench, con licenza MIT.
Top comments (0)