baldart 3.18.1 → 3.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,198 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.21.1] - 2026-05-26
9
+
10
+ Fix del Step 0 e Step 1 della skill `/baldart-update` introdotta in v3.21.0. La prima release usava `git -C .framework fetch origin main` per il pre-flight check del numero di commit ahead — pattern **sbagliato** perché `.framework/` è un git subtree all'interno del repo consumer, non un git repo separato: i comandi `git` eseguiti con `-C .framework` ricadono sul `.git` del consumer e fetcherebbero il remote del CONSUMER (es. `github.com/antbald/mayo`), non quello di BALDART. Bug individuato durante test live su `mayo` (l'output mostrava `Da https://github.com/antbald/mayo * branch main -> FETCH_HEAD` invece di BALDART).
11
+
12
+ ### Fixed — Pattern git corretto nella skill `/baldart-update`
13
+
14
+ - **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: Step 0 ora delega completamente a `npx baldart version` per ottenere installed version, commit ahead count, e BALDART repo URL — nessuna git operation a mano. Step 1 usa il pattern corretto del CLI (`src/utils/git.js`): `git fetch <BALDART-repo-url> main && git log HEAD..FETCH_HEAD --oneline -- .framework/`, dove l'URL viene estratto dal campo `Repository:` di Step 0. Aggiunta callout box dopo la tabella dei 5 decision point con la regola esplicita: *"do NOT use `git -C .framework fetch`"*, motivazione (`.framework/` è subtree, non repo separato), e pattern corretto.
15
+
16
+ ### Schema change propagation
17
+
18
+ Nessuna nuova chiave in `baldart.config.yml`. Nessun cambiamento al CLI.
19
+
20
+ ### Versioning rationale
21
+
22
+ PATCH (3.21.0 → 3.21.1) per la decision tree di [MAINTAINING.md](MAINTAINING.md): bug fix interno alla skill, nessun cambio di behaviour del CLI, nessuna nuova capability, nessun cambio di directory layout o install command.
23
+
24
+ ### Verification (manuale)
25
+
26
+ Eseguita in `~/mayo`:
27
+
28
+ 1. Test del comando originale ERRATO: `git -C .framework fetch origin main` → l'output mostrava `Da https://github.com/antbald/mayo` (remote di mayo, NOT BALDART) — bug confermato.
29
+ 2. Test del pattern CORRETTO: `npx baldart version` espone `Repository: https://github.com/antbald/BALDART`. `git fetch https://github.com/antbald/BALDART.git main && git log HEAD..FETCH_HEAD --oneline -- .framework/` rileva correttamente i commit BALDART ahead del subtree merge nel consumer.
30
+
31
+ ## [3.21.0] - 2026-05-26
32
+
33
+ Fino a v3.20.x l'aggiornamento di un consumer all'ultima versione del framework passava da due strade: **CLI diretta** (`npx baldart update`, interattivo, con 5 prompt nativi gestiti dall'utente in TTY) oppure **chat ad hoc** (l'utente chiede a Claude di aggiornare, e Claude — senza protocollo standardizzato — improvvisa ogni volta, gestendo gli edge case in modo non riproducibile). La prima opzione è solida ma richiede di lasciare la chat e tornare al terminale; la seconda è comoda ma fragile: silenzia decisioni che il CLI farebbe esplicitamente.
34
+
35
+ Questa release introduce la skill `/baldart-update` come **wrapper conversazionale agent-driven** di `npx baldart update`, complementare a `/baldart-push`. La skill **replica in chat ognuno dei 5 decision point nativi del CLI** prima di lanciare il comando con `--yes` — non per silenziarli, ma perché ogni prompt è già stato risposto in chat. Il CLI resta source of truth: la skill non re-implementa la logica di `update.js`, la guida e la riporta.
36
+
37
+ ### Added — Skill `/baldart-update`
38
+
39
+ - **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: nuova skill (~220 righe) modellata strutturalmente su [/baldart-push](framework/.claude/skills/baldart-push/SKILL.md) ma con divergenza intenzionale sul modello esecutivo: `/baldart-push` è pure-guidance (l'utente lancia il CLI in TTY), `/baldart-update` è agent-driven con conferme replicate in chat. La skill:
40
+ - **Pre-flight read-only** (Step 0): legge `.framework/VERSION` e conta i commit ahead con `git -C .framework rev-list HEAD..origin/main --count`. Se 0 → "Already up-to-date", stop senza lanciare il CLI.
41
+ - **Replica 5 decision point** (Step 1–4) — surface in chat di: preview diff (delta versione + commit message), working tree dirty + stash automatico in ref `baldart-pre-update-<timestamp>`, hooks drift confrontato con `HOOK_REGISTRY` di `.framework/src/utils/hooks.js`, schema config drift confrontato con `.framework/framework/templates/baldart.config.template.yml`, auto-commit classification (BALDART-managed vs user-owned per path-prefix matching su `.framework/`, `.claude/agents/`, `.claude/skills/`, `.claude/commands/`, `AGENTS.md`, `agents/`).
42
+ - **Run CLI** (Step 5): `Bash` con `timeout: 600000` (10 min — il default 120s è insufficiente per subtree pull + symlink reconcile + post-update wizard + hooks register + auto-commit su repo reali), comando `npx baldart update --yes`. NON passa `--auto-stash` (ridondante con `--yes`, segnalerebbe imprecisione).
43
+ - **Report** (Step 6): estrae backup tag con `git tag -l 'baldart-pre-update-*' --sort=-creatordate | head -1` (più affidabile del parsing stdout), nuova versione da `cat .framework/VERSION`, e detection stash-pop conflict via grep su stdout/stderr per pattern `CONFLICT` / `stash pop failed` / `unmerged paths`. Fornisce sempre il comando di rollback `git reset --hard <backup-tag>`.
44
+
45
+ ### Bootstrap paradox — esplicitato in cima al SKILL.md
46
+
47
+ I consumer pre-v3.21.0 incontreranno il paradosso al primo aggiornamento dopo questa release: la skill non è ancora installata, quindi `/baldart-update` non esiste. La skill mette in **box CAPS in prima posizione** l'istruzione di lanciare `npx baldart update` direttamente nel terminale una volta — dopo, `/baldart-update` diventa disponibile per ogni aggiornamento successivo. Non è un edge case nascosto: è il caso primario del rollout, e va trattato come tale.
48
+
49
+ ### Hard rules nella skill
50
+
51
+ Sette hard rule chiudono i vettori di errore:
52
+
53
+ 1. NEVER re-implementare la logica di `update.js`.
54
+ 2. MUST replicare in chat tutti e 5 i decision point prima di passare `--yes`.
55
+ 3. MUST passare `timeout: 600000` al Bash.
56
+ 4. NEVER passare `--auto-stash`.
57
+ 5. MUST riportare il backup tag.
58
+ 6. NEVER lanciare update se `.framework/` manca → defer a `npx baldart add`.
59
+ 7. NEVER assumere default quando un pre-check fallisce — chiedi all'utente o aborta.
60
+
61
+ ### Schema change propagation
62
+
63
+ **Nessuna nuova chiave in `baldart.config.yml`** — la skill legge solo `.framework/VERSION`, `.baldart/state.json`, `.claude/settings.json` (file BALDART-owned, non gated da config). La regola di propagazione schema (memoria utente + CLAUDE.md) non si applica a questa release.
64
+
65
+ ### Versioning rationale
66
+
67
+ MINOR (3.20.0 → 3.21.0) secondo la decision tree di [MAINTAINING.md](MAINTAINING.md):
68
+ - Nuova skill (capability additiva) → MINOR.
69
+ - Nessuna rimozione / cambio directory layout / cambio install command → non MAJOR.
70
+ - Non è un fix doc-only / perf / security senza behaviour change → non PATCH.
71
+
72
+ ### Verification (manuale, no test suite)
73
+
74
+ Da verificare in un consumer downstream (dopo merge + push + `npx baldart update`):
75
+
76
+ 1. **Discoverability**: dopo l'update, `.claude/skills/baldart-update` esiste come symlink a `.framework/framework/.claude/skills/baldart-update/SKILL.md`. La skill appare nella lista delle skill caricate alla prossima sessione Claude Code.
77
+ 2. **Collision test (bootstrap edge case)**: creando `.claude/skills/baldart-update/` user-owned PRIMA dell'update, `SymlinkUtils.mergeSkills` logga in `.baldart/skill-conflicts.json` e NON sovrascrive. La skill ufficiale resta non disponibile — comportamento corretto e documentato.
78
+ 3. **Happy path**: in repo allineata, `/baldart-update` risponde "Already up-to-date" via Step 0 (count = 0), senza lanciare il CLI.
79
+ 4. **Behind path**: forzato `cd .framework && git reset --hard HEAD~1`, `/baldart-update` rileva 1 commit ahead, replica Step 1–4 in chat, lancia CLI con timeout 600000, riporta backup tag + nuova versione.
80
+ 5. **Dirty path**: working tree modificato → Step 2 lista i file e chiede conferma esplicita per lo stash. Se rifiuto → STOP pulito.
81
+ 6. **Conflict path**: file modificato sia upstream che locale → CLI esce non-zero, skill riporta verbatim + comando rollback con backup tag.
82
+
83
+ ## [3.20.0] - 2026-05-26
84
+
85
+ Su `mayo` è stato osservato il seguente comportamento patologico: il modello ha chiamato `Agent({ subagent_type: "code-reviewer" })` e `Agent({ subagent_type: "doc-reviewer" })` (entrambi agent BALDART legittimi e fisicamente presenti su disco), il tool è ritornato `0 tool uses · Done` senza errore visibile, e il modello ha **silenziosamente fatto fallback** su `feature-dev:code-reviewer` + `plan-auditor` — proseguendo come se nulla fosse. Né l'utente né l'agent orchestratore hanno avuto modo di accorgersi che la review codice + doc review obbligatorie non erano state eseguite dagli agent attesi.
86
+
87
+ Ricerca su issue tracker Anthropic ha confermato due bug Claude Code che si combinano in questo scenario: [#56869](https://github.com/anthropics/claude-code/issues/56869) (sub-agent error non propagato al parent — `0 tool uses · Done` è il sintomo) e [#20931](https://github.com/anthropics/claude-code/issues/20931) (file-based discovery di `.claude/agents/*.md` documentato come BROKEN in certi scenari, anche con file validi). **Nessuno dei due è patchabile lato BALDART** — il framework può solo mitigare con difese stratificate.
88
+
89
+ Questa release introduce **quattro layer di difesa stratificati** contro la silent-substitution di agent BALDART, senza nuove chiavi in `baldart.config.yml`:
90
+
91
+ ### Added — Hook `agent-discovery-gate` (PreToolUse, bloccante)
92
+
93
+ - **[framework/.claude/hooks/agent-discovery-gate.js](framework/.claude/hooks/agent-discovery-gate.js)**: nuovo PreToolUse hook con `matcher: 'Agent'` che intercetta ogni invocazione del tool `Agent` (alias `Task`). Estrae `subagent_type` (o `agent_type`) dall'input, lo confronta con la lista degli agent BALDART derivata via `fs.readdirSync` su `.framework/framework/.claude/agents/*.md` (esclusa `REGISTRY.md`). Se il nome è BALDART e il symlink `.claude/agents/<name>.md` del consumer è mancante o rotto (`fs.statSync` segue il link → ENOENT/ELOOP), emette `permissionDecision: "deny"` con reason esplicito che istruisce: *"riavvia la sessione, esegui `npx baldart doctor`, NON sostituire con un altro agent"*. Nomi non-BALDART (built-in `Explore`/`Plan`/`general-purpose`, marketplace `feature-dev:*`/`vercel:*`/`sentry:*`, plugin) passano sempre. Fail-safe contract identico a `framework-edit-gate.js`: ogni try-catch interno esce 0 (allow), mai bloccare per errore proprio.
94
+
95
+ ### Added — Hook `agent-discovery-info` (SessionStart, informativo)
96
+
97
+ - **[framework/.claude/hooks/agent-discovery-info.sh](framework/.claude/hooks/agent-discovery-info.sh)**: nuovo SessionStart hook bash che enumera gli agent BALDART attesi (stesso `find` su `.framework/framework/.claude/agents/`) e li confronta con `.claude/agents/<name>.md` nel consumer. Quando uno o più sono mancanti, inietta `additionalContext` come **fatto dichiarativo** (best practice anti-prompt-injection da [Claude Code hooks docs](https://code.claude.com/docs/en/hooks)) elencando i nomi non disponibili. Silente quando tutto è OK. **Limite onesto**: il bug Claude Code [#10373](https://github.com/anthropics/claude-code/issues/10373) può saltare l'iniezione su new conversations — in quel caso la difesa scende sul gate bloccante + regola AGENTS.md.
98
+
99
+ ### Added — Regola MUST `framework/AGENTS.md` § 4.a
100
+
101
+ - **[framework/AGENTS.md](framework/AGENTS.md)**: nuova regola in sezione *Universal MUST*, inserita immediatamente dopo la MUST su `codebase-architect`. Istruisce ogni agent che lavora in un consumer BALDART a **NON fare fallback silenzioso** quando una chiamata `Agent` ritorna `0 tool uses · Done`, va in timeout, o riceve `permissionDecision: deny`. Comportamento richiesto: STOP, replicare verbatim al user il messaggio *"Agent `<name>` non discoverato o fallito in questa sessione — riavvia Claude Code e verifica con `npx baldart doctor`"*, attendere direzione. Nessun auto-routing a `feature-dev:*`/`general-purpose`/altri marketplace. La regola chiude esplicitamente: *"This rule is the ONLY defense against bug #56869, which BALDART cannot patch from outside Claude Code; deviating from it reintroduces the exact failure mode the rule exists to prevent."* È inseparabile dall'hook bloccante — senza la MUST, il `deny` del gate produrrebbe esattamente la silent-substitution che si vuole prevenire.
102
+
103
+ ### Added — `baldart doctor` check integrità symlink agent
104
+
105
+ - **[src/commands/doctor.js](src/commands/doctor.js)**: aggiunta detection `state.agentSymlinksBroken` nel blocco `detectState` (stesso pattern del check `hooksStatus`, non-blocking probe in `try`/`catch` silenzioso). Enumera `.framework/framework/.claude/agents/*.md`, per ogni nome verifica `fs.statSync('.claude/agents/<name>.md')`, raccoglie i broken. Nuova action `repair-agent-symlinks` con `autoOk: true` che chiama direttamente `new SymlinkUtils().mergeAgents({ tools })` — NON dispatcha `require('./update')` (l'update completo avrebbe side-effect indesiderati: pull, overlay merge, hook register, config drift verify). Tools letti via `loadConfig(cwd).tools.enabled` con fallback `['claude']`.
106
+
107
+ ### Added — Registry idempotente per i due nuovi hook
108
+
109
+ - **[src/utils/hooks.js](src/utils/hooks.js)**: appese due entry a `HOOK_REGISTRY`: `baldart-agent-discovery-gate` (PreToolUse, matcher `Agent`, order 200 — corre dopo `framework-edit-gate`) e `baldart-agent-discovery-info` (SessionStart, matcher `*`, order 100). Nessuna modifica strutturale al modulo — l'infrastruttura multi-hook esistente (`registerAll`/`getStatus`/`createDriftPrompt`, introdotta in v3.18.0) gestisce automaticamente i due nuovi hook: `baldart add`/`update`/`doctor` li registrano idempotentemente in `.claude/settings.json` del consumer, e `unregisterAll` li rimuove pulitamente. Backfill garantito su consumer pre-3.20.0 via `baldart update`.
110
+
111
+ ### Changed — Nota machine-readability in REGISTRY.md
112
+
113
+ - **[framework/.claude/agents/REGISTRY.md](framework/.claude/agents/REGISTRY.md)**: nuova nota in cima che dichiara la directory filesystem `framework/.claude/agents/` come fonte di verità machine-readable per i tre nuovi consumatori (gate hook, info hook, doctor). Aggiungere un agent richiede solo `<name>.md` nella directory — nessun manifest JSON da mantenere, nessuna drift da sincronizzare. La tabella Agent Map resta human-readable.
114
+
115
+ ### Limiti dichiarati
116
+
117
+ Onestà tecnica > marketing: questa release **non risolve** i bug Claude Code che hanno causato il problema su `mayo`, li mitiga.
118
+
119
+ - **#56869 (silent sub-agent fallback)** non è patchabile dall'esterno di Claude Code. L'unica difesa è la regola MUST in `AGENTS.md` — soft, non meccanicamente bloccante. Il successo dipende dall'aderenza del modello alla regola.
120
+ - **#20931 (file-based discovery broken con file validi)** resta **scoperto dal gate** quando il file esiste e Claude Code lo ignora comunque: il gate vede il file → ritorna allow → la call torna `0 tool uses` (bug originale). In quello scenario worst-case, l'unica difesa attiva è la regola MUST.
121
+ - **#10373 (SessionStart non iniettato)** può saltare il warning informativo su new conversations — accettato come best-effort.
122
+
123
+ In pratica, il caso comune (symlink rotto / consumer mid-migration) è coperto al 100% dal gate. Il caso patologico (Claude Code ignora silenziosamente un file `.md` valido) è coperto solo dalla regola comportamentale.
124
+
125
+ ### Schema change propagation
126
+
127
+ - **Nessuna nuova chiave in `baldart.config.yml`** — la lista degli agent vive nel filesystem del payload, gli hook sono auto-registrati via `HOOK_REGISTRY`, il check doctor è always-on quando `state.frameworkPresent`. Schema-change propagation rule non si applica a questa release.
128
+
129
+ ### Versioning rationale
130
+
131
+ MINOR (3.19.0 → 3.20.0) secondo la decision tree di [MAINTAINING.md](MAINTAINING.md):
132
+ - Due nuovi hook (capability) → MINOR.
133
+ - Nuovo doctor check + nuova action (capability) → MINOR.
134
+ - Nuova MUST in AGENTS.md (regola comportamentale aggiuntiva ma additiva, non rompe install esistenti) → MINOR.
135
+ - Nessuna rimozione / cambio directory layout / cambio install command → non MAJOR.
136
+
137
+ ### Verification (manuale, no test suite)
138
+
139
+ Eseguita in-place su questa working copy via smoke test del hook standalone (`node framework/.claude/hooks/agent-discovery-gate.js < input`):
140
+
141
+ 1. **Happy path** — input `{ tool_name: 'Agent', tool_input: { subagent_type: 'definitely-not-an-agent' } }`: exit 0, output vuoto. ✓ (nome non in `baldartAgents` → allow).
142
+ 2. **Deny path** — input `{ tool_name: 'Agent', tool_input: { subagent_type: 'code-reviewer' }, cwd: '/tmp' }`: exit 0 con stdout JSON `permissionDecision: "deny"` e reason verbose istruttivo. ✓.
143
+ 3. **Allow path** — tmpdir con `.claude/agents/code-reviewer.md` simulato + invocation: exit 0, output vuoto. ✓ (symlink healthy → allow).
144
+ 4. **Module load** — `node -e "require('./src/utils/hooks.js')"` riporta 3 entries in HOOK_REGISTRY. ✓.
145
+ 5. **Doctor module load** — `node -e "require('./src/commands/doctor.js')"` carica senza syntax error. ✓.
146
+
147
+ Da verificare manualmente in un consumer downstream (dopo merge):
148
+ - `baldart doctor` con un symlink agent volontariamente rotto → propone action `repair-agent-symlinks` con `autoOk: true`, l'esecuzione ripristina il symlink.
149
+ - `baldart update` da consumer pre-3.20.0 → backfilla i due nuovi hook in `.claude/settings.json` senza toccare le entry esistenti.
150
+ - Sessione Claude Code in consumer con symlink rotto → `agent-discovery-info.sh` inietta `additionalContext` al boot; chiamata `Agent` verso quel nome → gate emette deny + il modello (se aderisce alla MUST) replica il messaggio all'utente invece di sostituire.
151
+
152
+ ## [3.19.0] - 2026-05-26
153
+
154
+ `.baldart/overlays/` smette di essere un percorso "andare al buio". Fino a v3.18.x, un consumer che voleva personalizzare uno skill / agent / command doveva ricordare a memoria tre canali distinti, due modelli di merging (concat per le skill, marker-based per agent/command), il path overlay corretto per ogni canale, lo schema frontmatter (`base_<kind>`, `base_<kind>_version`, `mode`) e la versione framework da targetare — il tutto senza nessun feedback su drift quando il base file cambiava upstream. L'unico touchpoint guida era il messaggio del hook `framework-edit-gate` quando bloccava un edit dentro `.framework/`, che diceva "metti la roba in `.baldart/overlays/<skill>.md`" senza spiegare *quale path esatto*, *quale schema*, *quale versione*. Risultato: overlay scritti raramente o male.
155
+
156
+ Questa release introduce uno skill `/overlay` + tre nuovi sotto-comandi CLI (`baldart overlay scaffold|validate|drift`) che colmano il gap end-to-end, seguendo il pattern stabilito da `/baldart-push` (skill = guidance, CLI = source of truth). Nessuna nuova chiave in `baldart.config.yml`: gli overlay vivono su path universali che non dipendono dalla configurazione del consumer.
157
+
158
+ ### Added — Skill `/overlay`
159
+
160
+ - **Nuova skill [framework/.claude/skills/overlay/SKILL.md](framework/.claude/skills/overlay/SKILL.md)**. Thin conversational wrapper attorno al nuovo sotto-comando CLI. Sette fasi: pre-check (`.framework/` esiste?), discovery (skill/agent/command + nome), inspect base + overlay esistente, scaffold via CLI, author body con Edit, validate via CLI, drift maintenance. La skill non re-implementa mai la logica del merger — invoca sempre `npx baldart overlay <sub>`. Quattro hard rules: mai editare `.framework/` direttamente, mai editare file con marker `<!-- baldart-generated: -->`, mai inventare `base_file_sha` o `base_<kind>_version` (computati dalla CLI), mai re-implementare i marker sezionali (vivono in `overlay-merger.js`). Frontmatter porta `contamination_scan: skip` perché contiene esempi illustrativi con token di overlay reali.
161
+
162
+ ### Added — Sotto-comando CLI `baldart overlay {scaffold,validate,drift}`
163
+
164
+ - **Nuovo file [src/commands/overlay.js](src/commands/overlay.js)** + dispatcher aggiunto in [bin/baldart.js](bin/baldart.js) come gruppo `overlay` (stesso pattern di `routines`). Tre sotto-comandi:
165
+ - `baldart overlay scaffold <type>/<name> [--mode extend|override]` — idempotente. Valida che il base esista in `.framework/`, computa `base_file_sha` (sha256 12-char) dal contenuto base, legge framework `VERSION`, scrive `.baldart/overlays/<path>` con frontmatter completo e body placeholder appropriato al kind (concat per skill, marker-based per agent/command). Refuse pulito se l'overlay esiste già — punta l'utente a `drift` o all'Edit diretto.
166
+ - `baldart overlay validate <path>` — dry-run del merger. Per agent/command invoca `mergeOverlay()` e riporta numero linee generate + warning (es. heading non matchato nel base, che diventa nuova sezione in coda). Per skill valida frontmatter YAML + esistenza base. Confronta `base_file_sha` overlay vs SHA attuale e segnala se il base è cambiato.
167
+ - `baldart overlay drift [<path>] [--all]` — drift report cross-overlay. SHA-based quando `base_file_sha` è presente nel frontmatter, version-based fallback altrimenti. Stampa summary `{clean, drifted, unknown}` su tutti gli overlay sotto `.baldart/overlays/` (root + `agents/` + `commands/`). Skip `*.example.md` e `README.md`.
168
+ - **Pre-check fail-safe**: ogni sotto-comando rifiuta con messaggio chiaro quando `.framework/` non esiste (consumer non installato). Non assume mai stato.
169
+
170
+ ### Changed — `overlay-merger.js` arricchito (additivo, retro-compat)
171
+
172
+ - **[src/utils/overlay-merger.js](src/utils/overlay-merger.js)**: nuova funzione esportata `computeBaseFileSha(content): string` (12-char sha256 prefix) usata dal CLI scaffolder per scrivere `base_file_sha` nel frontmatter overlay. Il marker `<!-- baldart-generated: -->` ora include opzionalmente `base_sha=<sha>` come campo aggiuntivo; `readMarker()` lo legge se presente, altrimenti ritorna `null` per quel campo (retro-compatibile con file generati pre-v3.19.0). Il merger continua a produrre output bit-identico nei casi senza `base_file_sha` nel overlay frontmatter — l'arricchimento è opt-in via lo scaffolder.
173
+ - **YAML SHA quote**: il scaffolder quota il sha (`base_file_sha: "fd940fcc1786"`) perché un 12-char hex può essere tutto-digit, che YAML interpreterebbe come numero (`0` per `000000000000`). `parseFrontmatter()` lato CLI coerce sempre a string per difensività.
174
+
175
+ ### Changed — Hook `framework-edit-gate` con handoff `/overlay`
176
+
177
+ - **[framework/.claude/hooks/framework-edit-gate.js](framework/.claude/hooks/framework-edit-gate.js)**: due piccoli edit testuali (logica del hook intatta). Nel messaggio "(B) Project-specific opinion" e nel messaggio "Cannot edit BALDART-generated file" la guida ora suggerisce di invocare `/overlay` per scaffolding guidato + drift check. La consistenza è garantita perché skill, CLI e hook vengono distribuiti insieme dal `baldart update`.
178
+
179
+ ### Schema change propagation
180
+
181
+ - **Nessuna nuova chiave in `baldart.config.yml`** — gli overlay vivono su path universali (`.framework/`, `.baldart/overlays/`) non parametrizzati. La regola di propagazione schema (template + configure prompt + update detector + doctor) NON si applica a questa release.
182
+ - **Schema change additivo nel frontmatter overlay**: nuovo campo opzionale `base_file_sha`. Retro-compatibile — overlay scritti pre-v3.19.0 senza questo campo restano validi; `drift` li classifica come "unknown" (drift detection by version only) invece di errori. Documentato in `framework/templates/overlays/README.md` come campo opzionale via il body placeholder dello scaffolder.
183
+
184
+ ### Versioning rationale
185
+
186
+ MINOR (3.18.1 → 3.19.0): added capability (nuova skill + nuovi sotto-comandi CLI). No breaking change: nessuna API esistente modificata semanticamente; `readMarker()` resta backward-compatible; il merger produce lo stesso output per ogni input pre-esistente. Decision tree CLAUDE.md: "Added an agent / command / skill / routine / template → MINOR" qualifica.
187
+
188
+ ### Verification (manuale, no test suite)
189
+
190
+ Tested in sandbox `/tmp/overlay-test` con symlink `.framework/ → /Users/antoniobaldassarre/BALDART` per simulare un consumer:
191
+
192
+ 1. `baldart overlay scaffold agents/coder` → overlay creato a `.baldart/overlays/agents/coder.md` con frontmatter completo (`base_agent: coder`, `base_agent_version: 3.19.0`, `base_file_sha: "<sha>"`, `mode: extend`).
193
+ 2. `baldart overlay validate .baldart/overlays/agents/coder.md` → dry-run del merger, "Overlay valid (agent, marker model), 350 lines generated".
194
+ 3. `baldart overlay drift --all` → cross-overlay drift report, summary corretto.
195
+ 4. Drift positivo: alterazione manuale di `base_file_sha` nel frontmatter → `drift` segnala "drifted (sha mismatch)".
196
+ 5. Scaffold idempotente: re-run con overlay esistente → "Overlay already exists, run drift to check it".
197
+ 6. Skill scaffolding: `baldart overlay scaffold bug` → overlay skill creato a `.baldart/overlays/bug.md` con `base_skill: bug`, body concat-friendly.
198
+ 7. Pre-check: `baldart overlay scaffold ...` in dir senza `.framework/` → exit 1 con messaggio chiaro.
199
+
8
200
  ## [3.18.1] - 2026-05-25
9
201
 
10
202
  Chiusura del debito tecnico introdotto da v3.18.0. Il release v3.18.0 ha annunciato che `/design-review` supporta un programmatic JSON output mode invocabile da `/e2e-review` per gating uniforme — ma l'implementazione era una sola nota nel frontmatter ("se invocato con `mode: programmatic` ritorna JSON") senza body command che la concretizzasse, senza schema vincolato, senza mode-detection deterministica, senza taxonomy mapping tra le severity Markdown (Blockers/High/Medium/Nitpicks) e le severity programmatic (critical/major/minor) del `visual-fidelity-verifier`. Il risultato: una feature half-implemented, parsable only in teoria. Questo PATCH la rende reale.
package/README.md CHANGED
@@ -208,7 +208,7 @@ Skills always-ask when required keys are missing — never silently default.
208
208
  never overwrites your file. Full guide:
209
209
  [`framework/docs/PROJECT-CONFIGURATION.md`](framework/docs/PROJECT-CONFIGURATION.md).
210
210
 
211
- ### Skills (25 portable skills)
211
+ ### Skills (29 portable skills)
212
212
 
213
213
  Skills live under `.claude/skills/` and are auto-discovered by Claude Code.
214
214
  Bundled skills:
@@ -219,6 +219,7 @@ Bundled skills:
219
219
  - **Product**: `seo-audit`, `copywriting`, `api-design-principles`
220
220
  - **Knowledge**: `doc-writing-for-rag`, `capture` (LLM wiki overlay)
221
221
  - **Integration**: `kie-ai`, `remotion-best-practices`
222
+ - **Framework**: `baldart-push` (upstream contribution), `baldart-update` (v3.21.0 — agent-driven framework update), `overlay` (v3.19.0 — guided overlay author)
222
223
 
223
224
  ### Registry-First UI Protocol (new in v3.11.0)
224
225
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.18.1
1
+ 3.21.1
package/bin/baldart.js CHANGED
@@ -115,6 +115,36 @@ program
115
115
  await doctorCommand({ auto: !!options.auto, offline: !!options.offline });
116
116
  });
117
117
 
118
+ const overlayGroup = program
119
+ .command('overlay')
120
+ .description('Author and check .baldart/overlays/ — scaffolds, validates, and detects drift on skill/agent/command overlays');
121
+
122
+ overlayGroup
123
+ .command('scaffold <target>')
124
+ .description('Create a new overlay file (e.g. `scaffold agents/coder`, `scaffold bug`)')
125
+ .option('--mode <mode>', 'Overlay mode: extend (default) or override (skill-only)', 'extend')
126
+ .action(async (target, options) => {
127
+ const overlay = require('../src/commands/overlay');
128
+ await overlay.scaffold(target, options);
129
+ });
130
+
131
+ overlayGroup
132
+ .command('validate <path>')
133
+ .description('Dry-run the merger on an overlay and report any errors / warnings')
134
+ .action(async (overlayPath) => {
135
+ const overlay = require('../src/commands/overlay');
136
+ await overlay.validate(overlayPath);
137
+ });
138
+
139
+ overlayGroup
140
+ .command('drift [path]')
141
+ .description('Check whether the base file changed since an overlay was authored. Without `path`, checks all overlays.')
142
+ .option('--all', 'Check every overlay under .baldart/overlays/ (default when no path is given)')
143
+ .action(async (overlayPath, options) => {
144
+ const overlay = require('../src/commands/overlay');
145
+ await overlay.drift(overlayPath, options);
146
+ });
147
+
118
148
  const routinesGroup = program
119
149
  .command('routines')
120
150
  .description('Manage scheduled routines (wiki-review, doc-review, code-review, skill-improve, ds-drift, full-sweep)');
@@ -4,6 +4,8 @@ Quick-reference for all custom agents. Use this to route tasks to the right spec
4
4
 
5
5
  **Location**: `.claude/agents/<name>.md` | **Invoke via**: `Task` tool with `subagent_type="<name>"`
6
6
 
7
+ > **Machine-readable source of truth (since v3.20.0)**: the filesystem directory `framework/.claude/agents/` is authoritative for the list of BALDART agents. The `agent-discovery-gate.js` PreToolUse hook, the `agent-discovery-info.sh` SessionStart hook, and `baldart doctor` enumerate the directory directly (every `<name>.md` except this file is an agent — no manifest JSON is shipped). Adding a new agent means dropping `<name>.md` here; no other touchpoint.
8
+
7
9
  ## Agent Map
8
10
 
9
11
  | Agent | Category | When to Use | Specialization | Can Edit Code | Key Tools |
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * agent-discovery-gate — Claude Code PreToolUse hook (tool: Agent).
4
+ *
5
+ * Intercepts `Agent` tool calls and denies the call when the requested
6
+ * `subagent_type` / `agent_type` matches the name of a BALDART agent but
7
+ * the corresponding `.claude/agents/<name>.md` file is missing or its
8
+ * symlink is broken on the consumer's filesystem.
9
+ *
10
+ * Background: Claude Code has two known bugs that combine into a silent
11
+ * failure mode for sub-agent invocations —
12
+ * #56869 — sub-agent returning internal error silently hangs parent
13
+ * (no error propagation, parent sees `0 tool uses · Done`)
14
+ * #20931 — file-based discovery of `.claude/agents/*.md` is documented
15
+ * as BROKEN in some scenarios (files ignored at session start)
16
+ *
17
+ * This hook covers the filesystem-detectable subset of the problem
18
+ * (missing/broken symlinks). The model-side defense (the MUST rule in
19
+ * `framework/AGENTS.md`) covers the rest. The two are inseparable:
20
+ * without the MUST rule, a `permissionDecision: deny` from this hook
21
+ * would likely be worked around by the model trying a different
22
+ * `subagent_type` — the very failure mode this hook exists to prevent.
23
+ *
24
+ * Source of truth for the list of BALDART agents: the filesystem
25
+ * directory next to this file (`.framework/framework/.claude/agents/`).
26
+ * Every `<name>.md` (except `REGISTRY.md`) is a BALDART agent. No
27
+ * separate manifest is shipped — the directory IS the manifest.
28
+ *
29
+ * Fail-safe contract: this hook MUST NOT block legitimate work. If
30
+ * anything unexpected happens (missing directory, parse error, IO
31
+ * error), it exits 0 and lets the tool call through. We optimise for
32
+ * false negatives over false blocks — the gate is a safety net, not a
33
+ * tribunal.
34
+ *
35
+ * Registration: see src/utils/hooks.js — `baldart add` and `baldart
36
+ * update` auto-register this hook in the consumer's
37
+ * `.claude/settings.json`.
38
+ */
39
+
40
+ 'use strict';
41
+
42
+ const fs = require('fs');
43
+ const path = require('path');
44
+
45
+ function safeExit() {
46
+ process.exit(0);
47
+ }
48
+
49
+ function readStdin() {
50
+ try {
51
+ return fs.readFileSync(0, 'utf8');
52
+ } catch (_) {
53
+ return '';
54
+ }
55
+ }
56
+
57
+ function parseInput(raw) {
58
+ if (!raw) return null;
59
+ try { return JSON.parse(raw); } catch (_) { return null; }
60
+ }
61
+
62
+ function loadBaldartAgents() {
63
+ // `__dirname` resolves to .framework/framework/.claude/hooks/.
64
+ // The agent files live alongside, in ../agents/.
65
+ const agentsDir = path.join(__dirname, '..', 'agents');
66
+ try {
67
+ if (!fs.existsSync(agentsDir)) return null;
68
+ const names = fs.readdirSync(agentsDir)
69
+ .filter((f) => f.endsWith('.md') && f !== 'REGISTRY.md')
70
+ .map((f) => f.replace(/\.md$/, ''));
71
+ return new Set(names);
72
+ } catch (_) {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function isConsumerSymlinkHealthy(consumerCwd, agentName) {
78
+ // fs.statSync follows symlinks; ENOENT/ELOOP/EACCES → broken / missing.
79
+ const target = path.join(consumerCwd, '.claude', 'agents', `${agentName}.md`);
80
+ try {
81
+ fs.statSync(target);
82
+ return true;
83
+ } catch (_) {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ function denyReason(agentName, consumerCwd) {
89
+ const targetPath = path.join('.claude', 'agents', `${agentName}.md`);
90
+ return [
91
+ `agent-discovery-gate: BALDART agent \`${agentName}\` is not available on this`,
92
+ `consumer's filesystem (\`${targetPath}\` is missing or its symlink is broken).`,
93
+ ``,
94
+ `This is a recoverable state, not a transient one — calling this agent`,
95
+ `again will keep failing until the symlink is restored.`,
96
+ ``,
97
+ `Fix:`,
98
+ ` 1. Run \`npx baldart doctor\` — it will detect and repair the broken`,
99
+ ` agent symlink(s) using SymlinkUtils.mergeAgents.`,
100
+ ` 2. Restart this Claude Code session so the discovery index is rebuilt.`,
101
+ ``,
102
+ `IMPORTANT — for the agent that received this denial:`,
103
+ `Do NOT silently fall back to a different \`subagent_type\` (e.g.`,
104
+ `\`feature-dev:*\`, \`general-purpose\`, or another BALDART agent). The`,
105
+ `AGENTS.md MUST rule explicitly forbids this — substitution masks the`,
106
+ `failure that this gate exists to surface. Instead, stop and report this`,
107
+ `message verbatim to the user, then wait for direction.`,
108
+ ``,
109
+ `(Consumer cwd: ${consumerCwd})`,
110
+ ].join('\n');
111
+ }
112
+
113
+ function emitDeny(reason) {
114
+ try {
115
+ process.stdout.write(JSON.stringify({
116
+ hookSpecificOutput: {
117
+ hookEventName: 'PreToolUse',
118
+ permissionDecision: 'deny',
119
+ permissionDecisionReason: reason,
120
+ },
121
+ }));
122
+ } catch (_) {
123
+ // Fallback to legacy mode: exit code 2 + stderr.
124
+ process.stderr.write(reason + '\n');
125
+ process.exit(2);
126
+ }
127
+ process.exit(0);
128
+ }
129
+
130
+ function main() {
131
+ const raw = readStdin();
132
+ const input = parseInput(raw);
133
+ if (!input) safeExit();
134
+
135
+ // Defensive: hook is registered with matcher 'Agent', but in case Claude
136
+ // Code routes other tools through us, only proceed for the Agent tool.
137
+ if (input.tool_name !== 'Agent') safeExit();
138
+
139
+ const toolInput = input.tool_input || {};
140
+ // Claude Code's hook docs name the field `agent_type`; the public SDK
141
+ // exposes it as `subagent_type`. Accept either for forward/backward
142
+ // robustness — whichever is present wins.
143
+ const requested = String(
144
+ toolInput.agent_type || toolInput.subagent_type || ''
145
+ ).trim();
146
+ if (!requested) safeExit();
147
+
148
+ const baldartAgents = loadBaldartAgents();
149
+ if (!baldartAgents || baldartAgents.size === 0) safeExit();
150
+
151
+ // Only gate names that BALDART claims as its own. Built-in agents
152
+ // (Explore, Plan, general-purpose), marketplace agents (feature-dev:*,
153
+ // vercel:*, sentry:*), and plugin-provided agents are unaffected.
154
+ if (!baldartAgents.has(requested)) safeExit();
155
+
156
+ // Consumer cwd comes from Claude Code's input envelope; fall back to
157
+ // process.cwd() for robustness.
158
+ const consumerCwd = (typeof input.cwd === 'string' && input.cwd)
159
+ ? input.cwd
160
+ : process.cwd();
161
+
162
+ if (isConsumerSymlinkHealthy(consumerCwd, requested)) safeExit();
163
+
164
+ emitDeny(denyReason(requested, consumerCwd));
165
+ }
166
+
167
+ try { main(); } catch (_) { safeExit(); }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # agent-discovery-info.sh — BALDART SessionStart hook (informational).
4
+ #
5
+ # Runs at session start (and on /clear, /compact, URL resume). Compares the
6
+ # set of BALDART agents shipped under .framework/framework/.claude/agents/
7
+ # against the per-name symlinks the consumer should have under
8
+ # .claude/agents/, and — when one or more are missing — injects an
9
+ # `additionalContext` warning so the model can see, at the very top of the
10
+ # session, which agent names will be unavailable.
11
+ #
12
+ # Why injection instead of blocking: SessionStart hooks have no decision
13
+ # control (per Claude Code docs), they can only inject context. The
14
+ # blocking counterpart is `agent-discovery-gate.js` (PreToolUse on the
15
+ # Agent tool). These two hooks complement each other:
16
+ # - This hook (C′) raises early awareness so the model never tries to
17
+ # call a missing agent in the first place.
18
+ # - The gate (B′) is the hard wall when a call slips through anyway.
19
+ #
20
+ # Known limitation: there's a Claude Code bug (#10373) where SessionStart
21
+ # hook output is not injected on new conversations under certain
22
+ # conditions. When that happens, the defense falls back to B′ + the
23
+ # AGENTS.md MUST rule. We accept this — the hook is a best-effort
24
+ # augmentation, not the primary line of defense.
25
+ #
26
+ # Anti-prompt-injection note: per Claude Code docs, SessionStart context
27
+ # is most reliable when phrased as factual statements ("The following
28
+ # agents are missing: …") rather than imperative system instructions.
29
+ # We follow that convention below.
30
+ #
31
+ # Exit: always 0 (Stop/SessionStart hooks should never bubble errors out
32
+ # to the user; failure isolation is a matter of hygiene).
33
+
34
+ set -u
35
+ exec 2>/dev/null # silence stderr; we are not user-visible
36
+
37
+ # --- read envelope --------------------------------------------------------
38
+
39
+ INPUT=""
40
+ if [ ! -t 0 ]; then
41
+ INPUT="$(cat)"
42
+ fi
43
+
44
+ get_field() {
45
+ local field="$1"
46
+ if command -v jq >/dev/null 2>&1; then
47
+ printf '%s' "$INPUT" | jq -r ".$field // empty" 2>/dev/null
48
+ else
49
+ printf '%s' "$INPUT" | sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -1
50
+ fi
51
+ }
52
+
53
+ CWD_FROM_ENV="$(get_field cwd)"
54
+ CWD="${CWD_FROM_ENV:-$PWD}"
55
+
56
+ cd "$CWD" 2>/dev/null || exit 0
57
+
58
+ # --- locate the framework agent directory --------------------------------
59
+
60
+ FRAMEWORK_AGENTS_DIR=".framework/framework/.claude/agents"
61
+ CONSUMER_AGENTS_DIR=".claude/agents"
62
+
63
+ [ -d "$FRAMEWORK_AGENTS_DIR" ] || exit 0 # consumer not installed — silent.
64
+
65
+ # --- compute missing set --------------------------------------------------
66
+
67
+ MISSING=""
68
+ while IFS= read -r f; do
69
+ [ -z "$f" ] && continue
70
+ name="$(basename "$f" .md)"
71
+ [ "$name" = "REGISTRY" ] && continue
72
+ target="$CONSUMER_AGENTS_DIR/${name}.md"
73
+ # `test -e` returns false for broken symlinks too — exactly what we want.
74
+ if [ ! -e "$target" ]; then
75
+ if [ -z "$MISSING" ]; then
76
+ MISSING="$name"
77
+ else
78
+ MISSING="$MISSING, $name"
79
+ fi
80
+ fi
81
+ done < <(find "$FRAMEWORK_AGENTS_DIR" -maxdepth 1 -type f -name '*.md' 2>/dev/null | sort)
82
+
83
+ # Nothing missing — exit silently. No noise when everything is healthy.
84
+ [ -z "$MISSING" ] && exit 0
85
+
86
+ # --- emit additionalContext ----------------------------------------------
87
+
88
+ # Build the JSON string with awk-based escaping (no jq required).
89
+ escape_json() {
90
+ printf '%s' "$1" | awk 'BEGIN{ORS=""} {gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); gsub(/\r/,""); gsub(/\n/,"\\n"); print}'
91
+ }
92
+
93
+ MSG="BALDART expects the following sub-agents to be reachable via the Agent tool in this session, but their backing files are missing from .claude/agents/: ${MISSING}. Calling Agent with one of these subagent_type values will be denied by the agent-discovery-gate PreToolUse hook. Run \`npx baldart doctor\` to repair, then restart this session."
94
+
95
+ cat <<JSON
96
+ {
97
+ "hookSpecificOutput": {
98
+ "hookEventName": "SessionStart",
99
+ "additionalContext": "$(escape_json "$MSG")"
100
+ }
101
+ }
102
+ JSON
103
+
104
+ exit 0
@@ -133,6 +133,9 @@ function blockReason(realPath, decisions, blockings) {
133
133
  ` → Do not write to \`.framework/\`. Instead write to:`,
134
134
  ` .baldart/overlays/<skill-name>.md`,
135
135
  ` This file is consumer-owned, never pushed upstream.`,
136
+ ` → Tip: invoke \`/overlay\` for a guided scaffolding flow (correct`,
137
+ ` frontmatter, base_file_sha, drift check). See also`,
138
+ ` \`npx baldart overlay scaffold <type>/<name>\`.`,
136
139
  ``,
137
140
  `(C) Legitimate illustrative example (e.g. docs that quote the canonical`,
138
141
  ` paths as examples — \`PROJECT-CONFIGURATION.md\`, \`project-context.md\`)`,
@@ -177,7 +180,8 @@ function main() {
177
180
  `This file is auto-generated by \`npx baldart update\` from a base ` +
178
181
  `framework file plus an overlay. Manual edits will be overwritten.\n\n` +
179
182
  `To customise: edit the corresponding overlay under .baldart/overlays/` +
180
- `(or create it if absent), then re-run \`npx baldart update\`.`;
183
+ `(or create it if absent), then re-run \`npx baldart update\`.\n` +
184
+ `→ Or invoke \`/overlay\` for a guided flow.`;
181
185
  try {
182
186
  process.stdout.write(JSON.stringify({
183
187
  hookSpecificOutput: {