baldart 3.19.0 → 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 +144 -0
- package/README.md +2 -2
- package/VERSION +1 -1
- package/framework/.claude/agents/REGISTRY.md +2 -0
- package/framework/.claude/hooks/agent-discovery-gate.js +167 -0
- package/framework/.claude/hooks/agent-discovery-info.sh +104 -0
- package/framework/.claude/skills/baldart-update/SKILL.md +300 -0
- package/framework/AGENTS.md +1 -0
- package/package.json +1 -1
- package/src/commands/add.js +16 -11
- package/src/commands/doctor.js +138 -13
- package/src/commands/update.js +18 -6
- package/src/utils/hooks.js +357 -86
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,150 @@ 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
|
+
|
|
8
152
|
## [3.19.0] - 2026-05-26
|
|
9
153
|
|
|
10
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.
|
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 (
|
|
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,7 +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), `overlay` (v3.19.0 — guided overlay author)
|
|
222
|
+
- **Framework**: `baldart-push` (upstream contribution), `baldart-update` (v3.21.0 — agent-driven framework update), `overlay` (v3.19.0 — guided overlay author)
|
|
223
223
|
|
|
224
224
|
### Registry-First UI Protocol (new in v3.11.0)
|
|
225
225
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.21.1
|
|
@@ -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
|