baldart 3.19.0 → 3.21.2
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 +180 -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 +315 -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
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: baldart-update
|
|
3
|
+
description: "Guidance for updating BALDART framework to the latest version in a consumer repo. Use when the user says /baldart-update, 'aggiorna baldart', 'aggiorna framework', 'update del framework', 'pull baldart latest', or wants to bring the framework up-to-date. Replicates all 5 native CLI decision points (preview diff, working tree stash, hooks drift, schema config drift, BALDART-managed auto-commit) as chat-side confirmations before launching `npx baldart update --yes` via Bash. For non-update drift, defers to `npx baldart` (smart doctor)."
|
|
4
|
+
contamination_scan: skip
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!--
|
|
8
|
+
DRIFT-CHECK
|
|
9
|
+
This block is read by scripts/check-update-skill-drift.js (triggered by
|
|
10
|
+
.github/workflows/check-update-skill-drift.yml on push tag v*.*.* and
|
|
11
|
+
pull_request). When any of the numbers below changes against the actual
|
|
12
|
+
sources, a warning is surfaced in the workflow run. The decision whether
|
|
13
|
+
this skill needs an update is left to a human reviewer.
|
|
14
|
+
|
|
15
|
+
update_js_prompts: 6
|
|
16
|
+
hook_registry_entries: 3
|
|
17
|
+
config_template_top_level_keys: features,git,identity,lsp,paths,stack,tools,version
|
|
18
|
+
last_verified: v3.21.2
|
|
19
|
+
-->
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# /baldart-update — Update framework in this consumer
|
|
23
|
+
|
|
24
|
+
Guide the user through an update of the BALDART framework inside their
|
|
25
|
+
consumer repo, replicating in chat **every interactive decision point** the
|
|
26
|
+
CLI would normally ask in the terminal — so the CLI can be driven with
|
|
27
|
+
`--yes` while the user keeps full control of each choice.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
╔══════════════════════════════════════════════════════════════════════╗
|
|
31
|
+
║ BOOTSTRAP NOTICE ║
|
|
32
|
+
║ ║
|
|
33
|
+
║ IF THIS IS THE FIRST UPDATE AFTER A CONSUMER WAS ON A VERSION ║
|
|
34
|
+
║ THAT PREDATES /baldart-update, THIS SKILL DOES NOT YET EXIST IN ║
|
|
35
|
+
║ THE CONSUMER'S .claude/skills/. The user must run: ║
|
|
36
|
+
║ ║
|
|
37
|
+
║ npx baldart update ║
|
|
38
|
+
║ ║
|
|
39
|
+
║ directly in their terminal **once**, and the skill becomes ║
|
|
40
|
+
║ available for every subsequent update. This skill is the pattern ║
|
|
41
|
+
║ for follow-up updates, not the very first one. ║
|
|
42
|
+
╚══════════════════════════════════════════════════════════════════════╝
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Execution model — IMPORTANT
|
|
46
|
+
|
|
47
|
+
This is an **agent-driven skill with full chat-side replication** of every
|
|
48
|
+
decision point the CLI would pose interactively. The flow is:
|
|
49
|
+
|
|
50
|
+
1. Run *read-only* probes to detect the same conditions `update.js` would
|
|
51
|
+
detect (version drift, dirty working tree, hooks drift, schema config
|
|
52
|
+
drift, BALDART-managed files to auto-commit).
|
|
53
|
+
2. For each detected condition, surface it in chat and ask the user the
|
|
54
|
+
exact same question the CLI would ask in a TTY.
|
|
55
|
+
3. Once every prompt has been answered in chat, launch `npx baldart update
|
|
56
|
+
--yes` via `Bash` (with a long timeout — see Hard rules). The `--yes`
|
|
57
|
+
flag is safe here precisely because there is no native prompt left to
|
|
58
|
+
silence: every one of them has already been resolved above.
|
|
59
|
+
4. Parse the CLI output, report the backup tag + new version, surface any
|
|
60
|
+
stash-pop conflicts that may have happened after the subtree pull.
|
|
61
|
+
|
|
62
|
+
This is the **intentional divergence** from `/baldart-push`, which is
|
|
63
|
+
pure-guidance (the user runs the CLI themselves). Here the user has asked
|
|
64
|
+
for an agent that drives the update, so the skill drives it — but it
|
|
65
|
+
replicates every confirmation so nothing is silently auto-accepted.
|
|
66
|
+
|
|
67
|
+
## When to use
|
|
68
|
+
|
|
69
|
+
- The user invokes `/baldart-update`.
|
|
70
|
+
- The user says "aggiorna baldart", "aggiorna framework", "update del
|
|
71
|
+
framework", "pull baldart latest", "porta baldart all'ultima versione".
|
|
72
|
+
- The user wants to bring the framework payload in the consumer repo
|
|
73
|
+
in sync with upstream `main`.
|
|
74
|
+
|
|
75
|
+
## When NOT to use
|
|
76
|
+
|
|
77
|
+
- The user wants to fix drift that is NOT an update (hooks missing, config
|
|
78
|
+
schema drift in isolation, broken symlinks). → `npx baldart` (smart
|
|
79
|
+
doctor) is the right tool — it picks the right action among ~12.
|
|
80
|
+
- The consumer has no `.framework/` directory yet. → `npx baldart add`.
|
|
81
|
+
- The user wants to contribute local framework improvements upstream.
|
|
82
|
+
→ `/baldart-push`.
|
|
83
|
+
- The user is in the BALDART repo itself (i.e. `/Users/.../BALDART`),
|
|
84
|
+
not in a consumer. This skill is for consumers; this repo updates
|
|
85
|
+
via `git pull`.
|
|
86
|
+
|
|
87
|
+
## Project Context
|
|
88
|
+
|
|
89
|
+
**Reads from `baldart.config.yml`:** none directly (the skill reads
|
|
90
|
+
`.framework/VERSION`, `.baldart/state.json`, and `.claude/settings.json`,
|
|
91
|
+
which are BALDART-owned and not gated by config keys).
|
|
92
|
+
**Gated by features:** none.
|
|
93
|
+
**Overlay:** loads `.baldart/overlays/baldart-update.md` if present.
|
|
94
|
+
Typical overlay content: extra pre-flight checks specific to the consumer
|
|
95
|
+
(e.g. "block update if a release branch is open"), or post-update
|
|
96
|
+
verification steps (e.g. "run `npm test` after symlink reconcile").
|
|
97
|
+
**On missing/empty keys:** not applicable — the skill operates on BALDART's
|
|
98
|
+
own state files. See [framework/agents/project-context.md](../../../agents/project-context.md)
|
|
99
|
+
for the protocol.
|
|
100
|
+
|
|
101
|
+
## Hard rules
|
|
102
|
+
|
|
103
|
+
1. **MUST NOT re-implement the logic of `update.js`.** Same rule as
|
|
104
|
+
`/baldart-push`: the CLI is the source of truth. The skill only
|
|
105
|
+
replicates prompts and parses output.
|
|
106
|
+
2. **MUST replicate in chat all 5 decision points** before passing `--yes`:
|
|
107
|
+
preview, working-tree stash, hooks drift, schema config drift,
|
|
108
|
+
auto-commit of BALDART-managed files. Silencing one is dishonest — the
|
|
109
|
+
user thinks they confirmed everything, but they didn't see that prompt.
|
|
110
|
+
3. **MUST pass `timeout: 600000` (10 minutes)** to the `Bash` call running
|
|
111
|
+
`npx baldart update --yes`. The default 120s is not enough for subtree
|
|
112
|
+
pull + symlink reconcile + post-update wizard + hooks register +
|
|
113
|
+
auto-commit on real-world repos.
|
|
114
|
+
4. **MUST NOT pass `--auto-stash`** to the CLI: `--yes` already implies it.
|
|
115
|
+
Passing both is imprecise and signals a misread of the CLI.
|
|
116
|
+
5. **MUST report the backup tag** generated by the CLI, so the user has a
|
|
117
|
+
one-line rollback path.
|
|
118
|
+
6. **MUST NOT launch update if `.framework/` is missing** — defer to
|
|
119
|
+
`npx baldart add` and stop.
|
|
120
|
+
7. **MUST NOT assume defaults when a pre-check fails.** If the skill cannot
|
|
121
|
+
read `.claude/settings.json` or `baldart.config.yml`, it asks the user
|
|
122
|
+
how to proceed or aborts. Never silence a decision point because a
|
|
123
|
+
probe failed.
|
|
124
|
+
|
|
125
|
+
## The 5 decision points the skill replicates
|
|
126
|
+
|
|
127
|
+
| # | Native CLI prompt | Chat-side replication | Detect via |
|
|
128
|
+
|---|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
129
|
+
| 1 | "Proceed with update?" | Show "Remote: N commit(s) ahead" + last N commit messages, ask "Procedo?" | `npx baldart version` (parse "Remote: …" + "Repository: <url>") → if N > 0, `git fetch <url> main && git log HEAD..FETCH_HEAD --oneline -- .framework/` |
|
|
130
|
+
| 2 | "Stash dirty working tree?" | List modified files, explain they will go into `baldart-pre-update-<timestamp>`, ask explicit confirmation | `git status --porcelain` in consumer root |
|
|
131
|
+
| 3 | Hooks drift prompt | Compare `.claude/settings.json` entries vs `HOOK_REGISTRY`; if drift, describe what backfill will change | `cat .claude/settings.json` + read `.framework/src/utils/hooks.js` for `HOOK_REGISTRY` |
|
|
132
|
+
| 4 | Schema config migration | Compare `baldart.config.yml` keys vs `.framework/framework/templates/baldart.config.template.yml`; if missing keys, list which + consequence | semantic diff key-by-key (NOT textual diff) |
|
|
133
|
+
| 5 | Auto-commit BALDART-managed | Classify `git status --porcelain` paths as BALDART-managed vs user-owned by path-prefix, show classification, confirm commit of the managed subset | path-prefix matching on `.framework/`, `.claude/agents/`, `.claude/skills/`, `.claude/commands/`, `AGENTS.md`, `agents/` |
|
|
134
|
+
|
|
135
|
+
> **IMPORTANT — do NOT use `git -C .framework fetch`.** `.framework/` is a git
|
|
136
|
+
> subtree, not a separate repo: git commands inside it fall back to the consumer's
|
|
137
|
+
> own repo and would fetch the wrong remote. The correct pattern (mirroring
|
|
138
|
+
> `update.js` + `src/utils/git.js`) is `git fetch <BALDART-repo-url> main` from
|
|
139
|
+
> the consumer root, then `git log HEAD..FETCH_HEAD -- .framework/` for the
|
|
140
|
+
> preview. The repo URL is exposed by `npx baldart version` in the
|
|
141
|
+
> `Repository:` line.
|
|
142
|
+
|
|
143
|
+
## Workflow
|
|
144
|
+
|
|
145
|
+
### Step 0 — Pre-flight (read-only)
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# One call gives both: installed version, behind count, and the
|
|
149
|
+
# BALDART repo URL. NO git operation inside .framework/ — that
|
|
150
|
+
# directory is a git subtree, not a separate repo.
|
|
151
|
+
npx baldart version
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Parse the output for:
|
|
155
|
+
|
|
156
|
+
- `Installed: vX.Y.Z` — current framework version in the consumer.
|
|
157
|
+
- `Remote: N commit(s) ahead — pull with …` — number of commits behind upstream. If this line is absent or shows 0, the consumer is up-to-date.
|
|
158
|
+
- `Repository: <url>` — the BALDART git URL (needed in Step 1).
|
|
159
|
+
|
|
160
|
+
If `Remote:` is absent / shows 0 → tell the user "Already up-to-date — version vX.Y.Z" and STOP. Do not launch the CLI.
|
|
161
|
+
|
|
162
|
+
If `.framework/` does not exist, `npx baldart version` will say "framework not installed" — STOP and instruct `npx baldart add`. Do not attempt to recover.
|
|
163
|
+
|
|
164
|
+
### Step 1 — Replicate decision point #1 (preview)
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Use the URL extracted from Step 0, not `git -C .framework`.
|
|
168
|
+
# This mirrors src/utils/git.js (fetch + diffWithRemote use FETCH_HEAD,
|
|
169
|
+
# never `git -C .framework`).
|
|
170
|
+
git fetch <BALDART-repo-url> main
|
|
171
|
+
git log HEAD..FETCH_HEAD --oneline -- .framework/
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Surface to the user:
|
|
175
|
+
|
|
176
|
+
- Current installed version (from Step 0).
|
|
177
|
+
- Number of commits behind (from Step 0).
|
|
178
|
+
- One-line summary of each incoming commit (from the `git log` above).
|
|
179
|
+
|
|
180
|
+
Ask: *"Procedo con l'aggiornamento?"*. If no → STOP.
|
|
181
|
+
|
|
182
|
+
### Step 2 — Replicate decision point #2 (working tree)
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git status --porcelain
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
If output is empty, proceed silently. If not, list the modified files in
|
|
189
|
+
chat, explain:
|
|
190
|
+
|
|
191
|
+
> *"Il working tree ha N file modificati. Il CLI li stasherà in un ref
|
|
192
|
+
> `baldart-pre-update-<timestamp>` prima del pull e li ripristinerà dopo.
|
|
193
|
+
> Posso procedere con lo stash automatico?"*
|
|
194
|
+
|
|
195
|
+
If the user refuses → STOP, instruct them to commit or stash manually
|
|
196
|
+
and re-run `/baldart-update`.
|
|
197
|
+
|
|
198
|
+
### Step 3 — Replicate decision points #3 + #4 (hooks/schema drift)
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Hooks drift detection
|
|
202
|
+
cat .claude/settings.json
|
|
203
|
+
# Compare entries against HOOK_REGISTRY in .framework/src/utils/hooks.js
|
|
204
|
+
# (the skill reads both and reports any expected entry missing
|
|
205
|
+
# or any installed entry with a stale config)
|
|
206
|
+
|
|
207
|
+
# Schema drift detection
|
|
208
|
+
# Compare top-level keys in baldart.config.yml against
|
|
209
|
+
# .framework/framework/templates/baldart.config.template.yml
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
For each drift detected, describe what `update.js` would do:
|
|
213
|
+
|
|
214
|
+
- Hooks drift → "il CLI registrerà le hook mancanti in `settings.json`
|
|
215
|
+
(entry: `baldart-<hook-name>`). Procedo?"
|
|
216
|
+
- Schema drift → "il CLI segnalerà che mancano le chiavi `X.Y` da
|
|
217
|
+
`baldart.config.yml` e suggerirà di rilanciare `npx baldart configure`
|
|
218
|
+
dopo l'update. Procedo (l'update non bloccherà, ma è bene saperlo)?"
|
|
219
|
+
|
|
220
|
+
If a probe fails (e.g. `settings.json` malformed), ask the user how to
|
|
221
|
+
proceed — never silently assume "no drift".
|
|
222
|
+
|
|
223
|
+
### Step 4 — Replicate decision point #5 (auto-commit classification)
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
git status --porcelain
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Classify each path:
|
|
230
|
+
|
|
231
|
+
- **BALDART-managed**: starts with `.framework/`, `.claude/agents/`,
|
|
232
|
+
`.claude/skills/`, `.claude/commands/`, `AGENTS.md`, `agents/`.
|
|
233
|
+
- **User-owned**: everything else.
|
|
234
|
+
|
|
235
|
+
Show the user the classification:
|
|
236
|
+
|
|
237
|
+
> *"Dopo il pull, il CLI auto-committa SOLO i file BALDART-managed che
|
|
238
|
+
> risultano modificati dal reconcile (es. symlink ricreati, agent/command
|
|
239
|
+
> generati). I tuoi file user-owned non vengono toccati. Procedo?"*
|
|
240
|
+
|
|
241
|
+
If no managed files would be modified, skip the prompt silently.
|
|
242
|
+
|
|
243
|
+
### Step 5 — Run the CLI
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npx baldart update --yes
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Use the Bash tool with `timeout: 600000`** (10 minutes). Capture stdout
|
|
250
|
+
and stderr — both are needed for Step 6.
|
|
251
|
+
|
|
252
|
+
Do NOT pass `--auto-stash` (redundant with `--yes`).
|
|
253
|
+
|
|
254
|
+
### Step 6 — Report
|
|
255
|
+
|
|
256
|
+
Extract the post-update facts and surface them:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Backup tag (most recent baldart-pre-update-* tag)
|
|
260
|
+
git tag -l 'baldart-pre-update-*' --sort=-creatordate | head -1
|
|
261
|
+
|
|
262
|
+
# New installed version
|
|
263
|
+
cat .framework/VERSION
|
|
264
|
+
|
|
265
|
+
# Stash-pop conflicts (if any)
|
|
266
|
+
# Grep stdout/stderr for: CONFLICT, "stash pop failed", "unmerged paths"
|
|
267
|
+
# If matches found, list the conflicted paths:
|
|
268
|
+
git status --porcelain | grep '^UU'
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Report to the user, in order:
|
|
272
|
+
|
|
273
|
+
1. **Version**: `vX.Y.Z → vA.B.C` (delta).
|
|
274
|
+
2. **Backup tag**: the `baldart-pre-update-<timestamp>` ref, with the
|
|
275
|
+
rollback command: `git reset --hard <tag>` (only if needed).
|
|
276
|
+
3. **Hooks reconciled**: any hook backfilled.
|
|
277
|
+
4. **Schema notes**: any missing key still flagged → suggest
|
|
278
|
+
`npx baldart configure`.
|
|
279
|
+
5. **Stash-pop status**: clean / conflicts present (with file list).
|
|
280
|
+
6. **Suggested next step**: if schema drift residual or other non-update
|
|
281
|
+
doctor checks fired, suggest `npx baldart` (smart doctor).
|
|
282
|
+
|
|
283
|
+
## Failure modes
|
|
284
|
+
|
|
285
|
+
| Symptom | Cause | What you do |
|
|
286
|
+
|--------------------------------------------------------|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
287
|
+
| `git -C .framework fetch` errors out | Offline / auth issue / repo URL changed | STOP. Surface the error verbatim. Ask the user to retry when network is back. Do not launch update. |
|
|
288
|
+
| Working tree dirty + user refuses stash | Authoring decision | STOP cleanly. Instruct: commit or stash manually, then re-run `/baldart-update`. |
|
|
289
|
+
| `update.js` exits non-zero (subtree pull conflict) | Upstream and local both modified the same file | Surface the CLI error verbatim. Provide the backup tag + `git reset --hard <tag>` rollback command. Do NOT attempt automatic conflict resolution. |
|
|
290
|
+
| Stash-pop conflicts after the subtree pull | User-modified file collides with framework change | Detected by grep on stdout/stderr (`CONFLICT`, `stash pop failed`, `unmerged paths`). List `git status --porcelain | grep '^UU'` paths. Instruct manual resolve. NEVER auto-resolve with `--hard` or `--no-verify`. |
|
|
291
|
+
| `.framework/` missing | Consumer never installed BALDART | STOP. Instruct `npx baldart add`. Do not improvise. |
|
|
292
|
+
| Bash 10-minute timeout exceeded | Very slow network / disk / huge repo | Surface the timeout. Instruct the user to run `npx baldart update` manually in their terminal to see live progress. |
|
|
293
|
+
| Pre-check probe fails (e.g. `settings.json` malformed) | Consumer state inconsistent | STOP. Surface the probe failure. Ask the user whether to proceed without that pre-check, or to fix manually first. Never silently assume "no drift". |
|
|
294
|
+
|
|
295
|
+
## Why this skill exists
|
|
296
|
+
|
|
297
|
+
The smart doctor (`npx baldart`, no args) already proposes an update
|
|
298
|
+
when it detects remote ahead — but it does so as **one of about twelve**
|
|
299
|
+
possible actions (install, configure, migrate, repair symlinks, register
|
|
300
|
+
hooks, reconcile state ledger, …). When the user knows they want *only*
|
|
301
|
+
to update, going through the doctor is more friction than signal.
|
|
302
|
+
|
|
303
|
+
This skill is the direct path for that case, **and** it does what the
|
|
304
|
+
terminal CLI does — surface every decision point as a confirmation —
|
|
305
|
+
inside the chat instead of in the user's shell. The user keeps the
|
|
306
|
+
control they would have in a TTY, but they get it conversationally.
|
|
307
|
+
|
|
308
|
+
`/baldart-push` and `/baldart-update` together form the conversational
|
|
309
|
+
spine of BALDART maintenance:
|
|
310
|
+
|
|
311
|
+
- `/baldart-update` — pull from upstream.
|
|
312
|
+
- `/baldart-push` — push to upstream.
|
|
313
|
+
|
|
314
|
+
Both wrap the CLI without re-implementing it; both keep the user in the
|
|
315
|
+
loop. The CLI remains the source of truth.
|
package/framework/AGENTS.md
CHANGED
|
@@ -94,6 +94,7 @@ Conflict steps (must follow in order):
|
|
|
94
94
|
|
|
95
95
|
- MUST treat `AGENTS.md` as authoritative for agent rules.
|
|
96
96
|
- MUST invoke `codebase-architect` agent (via Task tool) whenever you need to understand codebase structure, existing patterns, or code architecture before planning or implementing changes; do not proceed with planning or implementation without first understanding the existing system through codebase-architect.
|
|
97
|
+
- MUST NOT silently fall back when an `Agent` (a.k.a. `Task`) tool call returns empty (`0 tool uses · Done`), times out, or fails with `permissionDecision: deny`. This pattern indicates a Claude Code sub-agent failure (see anthropics/claude-code#56869) or a discovery miss / blocked agent (see #20931 and BALDART's own `agent-discovery-gate` PreToolUse hook), NOT a signal to substitute another agent. STOP, report to the user verbatim: "Agent `<name>` non discoverato o fallito in questa sessione — riavvia Claude Code e verifica con `npx baldart doctor`", and wait for direction. Never auto-route to `feature-dev:*`, `general-purpose`, or any other marketplace/built-in agent as a replacement. **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.**
|
|
97
98
|
- MUST NOT work on files/components already claimed by another agent; multiple agents allowed if working on independent areas.
|
|
98
99
|
- MUST perform a mandatory clarity analysis before fixing any issue labeled `bug`; confirm that the issue description, proposed correct behavior, and edge cases are unambiguous, document any resolved doubts, and only start fix work once every zone of uncertainty is covered.
|
|
99
100
|
- MUST mark missing info as UNKNOWN and ask the user; if blocked, set `BLOCKED` with a blocker.
|
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -166,19 +166,24 @@ async function add(repo, options) {
|
|
|
166
166
|
UI.success('Git aliases configured');
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
//
|
|
170
|
-
// Auto-register
|
|
171
|
-
//
|
|
169
|
+
// BALDART hooks (multi-hook registry since v3.18.0)
|
|
170
|
+
// Auto-register every hook in src/utils/hooks.js HOOK_REGISTRY into
|
|
171
|
+
// .claude/settings.json. Drift on an existing entry triggers an
|
|
172
|
+
// interactive prompt (Keep / Replace / Show diff).
|
|
172
173
|
try {
|
|
173
|
-
const res = Hooks.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
UI.success('Added framework-edit-gate hook to existing .claude/settings.json');
|
|
178
|
-
} else if (res.status === 'already') {
|
|
179
|
-
UI.info('framework-edit-gate hook already registered.');
|
|
180
|
-
} else if (res.status === 'malformed') {
|
|
174
|
+
const res = await Hooks.registerAll(process.cwd(), {
|
|
175
|
+
onDrift: Hooks.createDriftPrompt(UI),
|
|
176
|
+
});
|
|
177
|
+
if (res.malformed) {
|
|
181
178
|
UI.warning('.claude/settings.json is malformed; skipped hook registration. Run `npx baldart doctor` after fixing it.');
|
|
179
|
+
} else {
|
|
180
|
+
for (const r of res.results) {
|
|
181
|
+
if (r.status === 'created') UI.success(`Registered hook \`${r.id}\` in .claude/settings.json`);
|
|
182
|
+
else if (r.status === 'legacy-migrated') UI.success(`Migrated legacy hook to \`${r.id}\` (previous: ${r.previousCommand})`);
|
|
183
|
+
else if (r.status === 'drift-replaced') UI.success(`Replaced drifted hook \`${r.id}\` (was: ${r.previousCommand})`);
|
|
184
|
+
else if (r.status === 'drift-kept') UI.info(`Kept user-customized hook \`${r.id}\` as-is.`);
|
|
185
|
+
else if (r.status === 'already') UI.info(`Hook \`${r.id}\` already registered.`);
|
|
186
|
+
}
|
|
182
187
|
}
|
|
183
188
|
} catch (err) {
|
|
184
189
|
UI.warning(`Hook registration skipped: ${err.message}`);
|
package/src/commands/doctor.js
CHANGED
|
@@ -212,8 +212,47 @@ async function detectState(cwd, opts = {}) {
|
|
|
212
212
|
state.overlays = countOverlays(cwd);
|
|
213
213
|
state.overlaysDrifted = overlayDrift(cwd, state.frameworkVersion);
|
|
214
214
|
|
|
215
|
-
state.
|
|
216
|
-
|
|
215
|
+
state.hooksStatus = { malformed: false, hooks: [] };
|
|
216
|
+
state.hooksMissing = [];
|
|
217
|
+
state.hooksDrifted = [];
|
|
218
|
+
state.hooksLegacy = [];
|
|
219
|
+
try {
|
|
220
|
+
const status = Hooks.getStatus(cwd);
|
|
221
|
+
state.hooksStatus = status;
|
|
222
|
+
if (!status.malformed) {
|
|
223
|
+
state.hooksMissing = status.hooks.filter((h) => !h.registered).map((h) => h.id);
|
|
224
|
+
state.hooksDrifted = status.hooks.filter((h) => h.registered && h.drift && !h.legacy).map((h) => h.id);
|
|
225
|
+
state.hooksLegacy = status.hooks.filter((h) => h.legacy).map((h) => h.id);
|
|
226
|
+
}
|
|
227
|
+
} catch (_) {}
|
|
228
|
+
|
|
229
|
+
// ---- Agent symlink integrity (since v3.20.0) -----------------------
|
|
230
|
+
// Source of truth for the BALDART agent list is the framework filesystem
|
|
231
|
+
// directory itself (`.framework/framework/.claude/agents/*.md`). For each
|
|
232
|
+
// name, the consumer must have a working `.claude/agents/<name>.md`
|
|
233
|
+
// (symlink to the framework copy, possibly merged with an overlay).
|
|
234
|
+
// A missing or broken symlink would otherwise be detected at runtime by
|
|
235
|
+
// `agent-discovery-gate.js`, blocking Agent calls. This probe lets the
|
|
236
|
+
// doctor heal it offline before the user hits the gate.
|
|
237
|
+
state.agentSymlinksBroken = [];
|
|
238
|
+
try {
|
|
239
|
+
if (state.frameworkPresent) {
|
|
240
|
+
const frameworkAgentsDir = path.join(cwd, '.framework', 'framework', '.claude', 'agents');
|
|
241
|
+
const consumerAgentsDir = path.join(cwd, '.claude', 'agents');
|
|
242
|
+
if (fs.existsSync(frameworkAgentsDir)) {
|
|
243
|
+
const baldartAgents = fs.readdirSync(frameworkAgentsDir)
|
|
244
|
+
.filter((f) => f.endsWith('.md') && f !== 'REGISTRY.md')
|
|
245
|
+
.map((f) => f.replace(/\.md$/, ''));
|
|
246
|
+
for (const name of baldartAgents) {
|
|
247
|
+
try {
|
|
248
|
+
fs.statSync(path.join(consumerAgentsDir, `${name}.md`));
|
|
249
|
+
} catch (_) {
|
|
250
|
+
state.agentSymlinksBroken.push(name);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} catch (_) { /* never block doctor on probe */ }
|
|
217
256
|
|
|
218
257
|
// `.framework/` is a git subtree — gitignore matching it breaks every
|
|
219
258
|
// future `git add` under that path (the failure mode that hit v3.13.0
|
|
@@ -410,15 +449,75 @@ function planActions(state) {
|
|
|
410
449
|
// Non-blocking — keep looking for other actions to run in the same pass.
|
|
411
450
|
}
|
|
412
451
|
|
|
413
|
-
|
|
452
|
+
// BALDART hooks (multi-hook registry since v3.18.0).
|
|
453
|
+
// Three buckets, three actions: missing → propose; legacy → propose
|
|
454
|
+
// (silent migration to current id); drifted → require interactive prompt.
|
|
455
|
+
if ((state.hooksMissing && state.hooksMissing.length > 0)
|
|
456
|
+
|| (state.hooksLegacy && state.hooksLegacy.length > 0)) {
|
|
457
|
+
const ids = [...(state.hooksMissing || []), ...(state.hooksLegacy || [])];
|
|
414
458
|
actions.push({
|
|
415
|
-
key: 'register-
|
|
416
|
-
label:
|
|
417
|
-
why: '
|
|
459
|
+
key: 'register-hooks',
|
|
460
|
+
label: `Register/migrate BALDART hook(s) in .claude/settings.json (${ids.join(', ')})`,
|
|
461
|
+
why: 'BALDART hooks are missing or registered under a pre-v3.18 layout. They run on PreToolUse/Stop/SessionStart to gate edits and feed the auto-learning loop. Re-registration is idempotent and never overwrites user-customized commands without asking.',
|
|
418
462
|
autoOk: true,
|
|
419
463
|
run: async () => {
|
|
420
|
-
const
|
|
421
|
-
|
|
464
|
+
const Hooks = require('../utils/hooks');
|
|
465
|
+
const res = await Hooks.registerAll(process.cwd(), {
|
|
466
|
+
onDrift: Hooks.createDriftPrompt(UI, { autoYes: state.autoMode }),
|
|
467
|
+
});
|
|
468
|
+
if (res.malformed) {
|
|
469
|
+
UI.warning('.claude/settings.json is malformed; fix it manually then re-run `npx baldart doctor`.');
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
for (const r of res.results) {
|
|
473
|
+
if (r.status === 'created') UI.success(`Registered hook \`${r.id}\``);
|
|
474
|
+
else if (r.status === 'legacy-migrated') UI.success(`Migrated legacy hook to \`${r.id}\` (previous: ${r.previousCommand})`);
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (state.hooksDrifted && state.hooksDrifted.length > 0) {
|
|
480
|
+
actions.push({
|
|
481
|
+
key: 'reconcile-hooks-drift',
|
|
482
|
+
label: `Reconcile drifted BALDART hook(s) (${state.hooksDrifted.join(', ')})`,
|
|
483
|
+
why: 'These hooks are registered but their command differs from the BALDART default. This is typically a sign of a manual customization or a stale framework version on disk. Reconciliation prompts you per hook (Keep / Replace / Show diff).',
|
|
484
|
+
autoOk: false, // drift reconciliation is the kind of decision --auto must NOT make
|
|
485
|
+
run: async () => {
|
|
486
|
+
const Hooks = require('../utils/hooks');
|
|
487
|
+
const res = await Hooks.registerAll(process.cwd(), {
|
|
488
|
+
onDrift: Hooks.createDriftPrompt(UI), // always interactive here
|
|
489
|
+
});
|
|
490
|
+
for (const r of res.results) {
|
|
491
|
+
if (r.status === 'drift-replaced') UI.success(`Replaced \`${r.id}\` (was: ${r.previousCommand})`);
|
|
492
|
+
else if (r.status === 'drift-kept') UI.info(`Kept \`${r.id}\` as-is.`);
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Agent symlink integrity (since v3.20.0). Re-runs the agent merge loop to
|
|
499
|
+
// recreate any missing per-agent symlink. Calls `SymlinkUtils.mergeAgents`
|
|
500
|
+
// directly rather than dispatching to `update` — we want only the symlink
|
|
501
|
+
// pass, not a full update (which would also pull, merge overlays, register
|
|
502
|
+
// hooks, and verify config drift).
|
|
503
|
+
if (state.agentSymlinksBroken && state.agentSymlinksBroken.length > 0) {
|
|
504
|
+
actions.push({
|
|
505
|
+
key: 'repair-agent-symlinks',
|
|
506
|
+
label: `Repair ${state.agentSymlinksBroken.length} broken agent symlink(s)`,
|
|
507
|
+
why: `BALDART expects these agents to be reachable via .claude/agents/<name>.md but the symlinks are missing or broken: ${state.agentSymlinksBroken.join(', ')}. Without these, the agent-discovery-gate PreToolUse hook will deny Agent calls targeting these names.`,
|
|
508
|
+
autoOk: true,
|
|
509
|
+
run: async () => {
|
|
510
|
+
const SymlinkUtils = require('../utils/symlinks');
|
|
511
|
+
// Re-read the config here (not stashed on state) to pick up
|
|
512
|
+
// `tools.enabled`. Falls back to ['claude'] if absent/malformed.
|
|
513
|
+
const cfg = loadConfig(process.cwd());
|
|
514
|
+
const enabledTools = (cfg && !cfg.__malformed
|
|
515
|
+
&& cfg.tools && Array.isArray(cfg.tools.enabled))
|
|
516
|
+
? cfg.tools.enabled
|
|
517
|
+
: ['claude'];
|
|
518
|
+
const symlinks = new SymlinkUtils();
|
|
519
|
+
symlinks.mergeAgents({ tools: enabledTools });
|
|
520
|
+
UI.success(`Re-merged agent symlinks for tools: ${enabledTools.join(', ')}`);
|
|
422
521
|
},
|
|
423
522
|
});
|
|
424
523
|
}
|
|
@@ -616,11 +715,36 @@ function renderDiagnostic(state) {
|
|
|
616
715
|
console.log(` • ${o.name} targets v${o.target}, installed v${o.installed}`));
|
|
617
716
|
}
|
|
618
717
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
state.
|
|
622
|
-
state.
|
|
623
|
-
|
|
718
|
+
{
|
|
719
|
+
const total = state.hooksStatus && state.hooksStatus.hooks ? state.hooksStatus.hooks.length : 0;
|
|
720
|
+
const missing = state.hooksMissing ? state.hooksMissing.length : 0;
|
|
721
|
+
const drifted = state.hooksDrifted ? state.hooksDrifted.length : 0;
|
|
722
|
+
const legacy = state.hooksLegacy ? state.hooksLegacy.length : 0;
|
|
723
|
+
const registered = total - missing;
|
|
724
|
+
let summary;
|
|
725
|
+
let status;
|
|
726
|
+
if (state.hooksStatus && state.hooksStatus.malformed) {
|
|
727
|
+
summary = '.claude/settings.json is malformed';
|
|
728
|
+
status = 'warn';
|
|
729
|
+
} else if (missing === 0 && drifted === 0 && legacy === 0) {
|
|
730
|
+
summary = `${registered}/${total} registered`;
|
|
731
|
+
status = 'ok';
|
|
732
|
+
} else {
|
|
733
|
+
const parts = [`${registered}/${total} registered`];
|
|
734
|
+
if (missing) parts.push(`${missing} missing`);
|
|
735
|
+
if (drifted) parts.push(`${drifted} drifted`);
|
|
736
|
+
if (legacy) parts.push(`${legacy} pre-v3.18`);
|
|
737
|
+
summary = parts.join(', ');
|
|
738
|
+
status = 'warn';
|
|
739
|
+
}
|
|
740
|
+
console.log(statusLine('BALDART hooks', summary, status));
|
|
741
|
+
if (state.hooksMissing && state.hooksMissing.length)
|
|
742
|
+
console.log(` • missing: ${state.hooksMissing.join(', ')}`);
|
|
743
|
+
if (state.hooksDrifted && state.hooksDrifted.length)
|
|
744
|
+
console.log(` • drifted: ${state.hooksDrifted.join(', ')}`);
|
|
745
|
+
if (state.hooksLegacy && state.hooksLegacy.length)
|
|
746
|
+
console.log(` • pre-v3.18: ${state.hooksLegacy.join(', ')}`);
|
|
747
|
+
}
|
|
624
748
|
|
|
625
749
|
if (state.frameworkIgnored && state.frameworkIgnored.ignored) {
|
|
626
750
|
console.log(statusLine(
|
|
@@ -663,6 +787,7 @@ async function doctor(opts = {}) {
|
|
|
663
787
|
const offline = !!opts.offline;
|
|
664
788
|
|
|
665
789
|
const state = await detectState(cwd, { offline });
|
|
790
|
+
state.autoMode = auto;
|
|
666
791
|
renderDiagnostic(state);
|
|
667
792
|
|
|
668
793
|
const actions = planActions(state);
|
package/src/commands/update.js
CHANGED
|
@@ -400,13 +400,25 @@ async function update(options = {}) {
|
|
|
400
400
|
UI.warning(`Routines wizard skipped: ${err.message}`);
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
403
|
+
// BALDART hooks (multi-hook registry since v3.18.0) — backfill for
|
|
404
|
+
// consumers that predate any of the hooks shipped in the current
|
|
405
|
+
// framework version. Idempotent: no-op when every hook is already in
|
|
406
|
+
// place. Drift on an existing entry triggers an interactive prompt
|
|
407
|
+
// (Keep / Replace / Show diff); on --auto / -y the default is 'keep'.
|
|
406
408
|
try {
|
|
407
|
-
const res = Hooks.
|
|
408
|
-
|
|
409
|
-
|
|
409
|
+
const res = await Hooks.registerAll(process.cwd(), {
|
|
410
|
+
onDrift: Hooks.createDriftPrompt(UI, { autoYes }),
|
|
411
|
+
});
|
|
412
|
+
if (res.malformed) {
|
|
413
|
+
UI.warning('.claude/settings.json is malformed; skipped hook registration. Run `npx baldart doctor` after fixing it.');
|
|
414
|
+
} else {
|
|
415
|
+
for (const r of res.results) {
|
|
416
|
+
if (r.status === 'created') UI.success(`Registered hook \`${r.id}\` in .claude/settings.json (v3.18.0+ default).`);
|
|
417
|
+
else if (r.status === 'legacy-migrated') UI.success(`Migrated legacy hook to \`${r.id}\` (previous: ${r.previousCommand})`);
|
|
418
|
+
else if (r.status === 'drift-replaced') UI.success(`Replaced drifted hook \`${r.id}\` (was: ${r.previousCommand})`);
|
|
419
|
+
else if (r.status === 'drift-kept') UI.info(`Kept user-customized hook \`${r.id}\` as-is.`);
|
|
420
|
+
// 'already' is silent on update to avoid noise
|
|
421
|
+
}
|
|
410
422
|
}
|
|
411
423
|
} catch (err) {
|
|
412
424
|
UI.warning(`Hook registration skipped: ${err.message}`);
|