baldart 3.9.0 → 3.12.0
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 +93 -0
- package/README.md +15 -3
- package/VERSION +1 -1
- package/framework/.claude/agents/REGISTRY.md +3 -3
- package/framework/.claude/agents/code-reviewer.md +47 -11
- package/framework/.claude/agents/codebase-architect.md +8 -0
- package/framework/.claude/agents/ui-expert.md +355 -43
- package/framework/.claude/commands/design-review.md +7 -0
- package/framework/.claude/skills/bug/SKILL.md +5 -1
- package/framework/.claude/skills/context-primer/SKILL.md +4 -0
- package/framework/.claude/skills/design-system-init/SKILL.md +232 -0
- package/framework/.claude/skills/design-system-init/scripts/component-spec.template.md +79 -0
- package/framework/.claude/skills/frontend-design/SKILL.md +124 -0
- package/framework/.claude/skills/lsp-bootstrap/SKILL.md +113 -0
- package/framework/.claude/skills/motion-design/reference/accessibility-and-modern-apis.md +114 -0
- package/framework/.claude/skills/new/SKILL.md +1 -1
- package/framework/.claude/skills/prd/SKILL.md +3 -1
- package/framework/.claude/skills/simplify/SKILL.md +6 -0
- package/framework/.claude/skills/ui-design/SKILL.md +43 -0
- package/framework/agents/code-search-protocol.md +126 -0
- package/framework/agents/design-review.md +51 -0
- package/framework/agents/design-system-protocol.md +363 -0
- package/framework/agents/index.md +4 -0
- package/framework/docs/LSP-LAYER.md +405 -0
- package/framework/docs/PROJECT-CONFIGURATION.md +17 -0
- package/framework/docs/UPGRADE-3.12-UI-COHERENCE.md +268 -0
- package/framework/templates/baldart.config.template.yml +22 -0
- package/framework/templates/overlays/agents/codebase-architect.lsp-example.md +53 -0
- package/package.json +1 -1
- package/src/commands/configure.js +69 -0
- package/src/commands/doctor.js +55 -0
- package/src/utils/lsp-adapters/go.js +29 -0
- package/src/utils/lsp-adapters/index.js +49 -0
- package/src/utils/lsp-adapters/python.js +42 -0
- package/src/utils/lsp-adapters/ruby.js +30 -0
- package/src/utils/lsp-adapters/rust.js +26 -0
- package/src/utils/lsp-adapters/typescript.js +54 -0
- package/src/utils/lsp-installer.js +118 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Upgrade to v3.12.0 — UI Excellence & Registry Coherence
|
|
2
|
+
|
|
3
|
+
> **Audience**: Claude (or a human operator) working inside a **consumer
|
|
4
|
+
> repo** (any project that installed BALDART via `npx baldart add` and just
|
|
5
|
+
> ran `npx baldart update` to v3.12.0). This document is the alignment
|
|
6
|
+
> walkthrough — it does not replace the CHANGELOG (which describes *what*
|
|
7
|
+
> changed in the framework); it tells you *what to do* in this specific
|
|
8
|
+
> project to align an existing graphic organization with the new discipline.
|
|
9
|
+
>
|
|
10
|
+
> Run this once per project after updating to v3.12.0. It is idempotent —
|
|
11
|
+
> re-running on an already-aligned project produces an empty backlog.
|
|
12
|
+
|
|
13
|
+
## What changed in one paragraph
|
|
14
|
+
|
|
15
|
+
The `ui-expert` agent received a major upgrade (UI states taxonomy,
|
|
16
|
+
Performance Gates, modern CSS, AI-era patterns, 60+ categorized red flags),
|
|
17
|
+
and the registry-first discipline gained a **post-intervention coherence
|
|
18
|
+
gate**: every UI task that introduces, modifies, or surfaces a primitive or
|
|
19
|
+
token must reconcile the registry (`INDEX.md` + `components/<Name>.md` +
|
|
20
|
+
`tokens-reference.md`) **in the same change**. Drift can no longer wait for
|
|
21
|
+
Monday's `ds-drift` routine — it must be resolved at introduction. Three
|
|
22
|
+
layers now enforce coherence: per-task (in `ui-expert` / `ui-design` /
|
|
23
|
+
`frontend-design`) → per-merge (`code-reviewer`) → weekly safety net
|
|
24
|
+
(`ds-drift`). The full text-of-record lives in
|
|
25
|
+
`.framework/framework/agents/design-system-protocol.md` § "Post-Intervention
|
|
26
|
+
Coherence Check".
|
|
27
|
+
|
|
28
|
+
## Step 0 — Should you even run this walkthrough?
|
|
29
|
+
|
|
30
|
+
Run only if all three are true. Otherwise, exit cleanly.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[ ] baldart.config.yml has features.has_design_system: true
|
|
34
|
+
[ ] paths.design_system points to an existing directory
|
|
35
|
+
[ ] That directory contains at least an INDEX.md
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If `has_design_system: false` → there is nothing to align. The new gates
|
|
39
|
+
short-circuit. Optionally consider bootstrapping a registry via
|
|
40
|
+
`/design-system-init` (skill ships with v3.11.0+).
|
|
41
|
+
|
|
42
|
+
If `has_design_system: true` but `paths.design_system` does not exist or is
|
|
43
|
+
empty → run `/design-system-init` first; come back to this walkthrough
|
|
44
|
+
after.
|
|
45
|
+
|
|
46
|
+
## Step 1 — Surface the current state (no edits yet)
|
|
47
|
+
|
|
48
|
+
Before touching anything, build a snapshot. Do the four scans below and
|
|
49
|
+
emit one finding per item. **Do not fix anything yet** — fixing without a
|
|
50
|
+
baseline produces churn and hides the size of the gap.
|
|
51
|
+
|
|
52
|
+
### 1.1 — Silent primitives (`DS_INDEX_DRIFT` baseline)
|
|
53
|
+
|
|
54
|
+
Goal: find components that exist in the source tree but are NOT documented
|
|
55
|
+
in `INDEX.md`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# List candidate primitives in the project (adjust glob to match the project)
|
|
59
|
+
find $(yq '.paths.components_primitives' baldart.config.yml) \
|
|
60
|
+
-maxdepth 2 -type f \( -name "*.tsx" -o -name "*.vue" -o -name "*.svelte" \)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For each filename `<Name>.tsx`, grep `INDEX.md` for `<Name>` (case-sensitive).
|
|
64
|
+
Anything not found is a silent primitive. Record name + path + estimated
|
|
65
|
+
broadness (broadly-useful vs. surface-specific). Output:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
SILENT_PRIMITIVES:
|
|
69
|
+
- Badge | components/ui/Badge.tsx | broadly-useful
|
|
70
|
+
- PriceTag | components/marketing/Price.tsx | surface-specific
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 1.2 — Stale specs (`DS_COMPONENT_STALE` baseline)
|
|
75
|
+
|
|
76
|
+
For each `${paths.design_system}/components/<Name>.md`:
|
|
77
|
+
|
|
78
|
+
1. Locate the source file for `<Name>`.
|
|
79
|
+
2. Compare the spec's documented props/variants against the source's actual
|
|
80
|
+
exported types/props. Use `codebase-architect` if the project has LSP
|
|
81
|
+
enabled — it does this cheaply via go-to-definition.
|
|
82
|
+
3. Spec older than the source's last edit in git AND with mismatch on
|
|
83
|
+
props/variants → `DS_COMPONENT_STALE`.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
STALE_SPECS:
|
|
87
|
+
- Button.md | source mtime 2026-04-12, spec mtime 2026-01-05
|
|
88
|
+
| spec missing variants: "danger-outline", "icon-only"
|
|
89
|
+
| spec missing prop: leftIcon
|
|
90
|
+
...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 1.3 — Hardcoded tokens (`DS_TOKENS_DRIFT` baseline)
|
|
94
|
+
|
|
95
|
+
Scan the codebase for values that bypass `tokens-reference.md`:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Hardcoded hex/rgb in styled code (adjust path to actual sources)
|
|
99
|
+
grep -RInE '#[0-9a-fA-F]{3,8}\b|rgba?\([^)]*\)' \
|
|
100
|
+
$(yq '.paths.components_root' baldart.config.yml) \
|
|
101
|
+
--include='*.tsx' --include='*.css' --include='*.scss'
|
|
102
|
+
|
|
103
|
+
# Hardcoded box-shadow tuples
|
|
104
|
+
grep -RInE 'box-shadow:\s*[^v;]+;' \
|
|
105
|
+
$(yq '.paths.components_root' baldart.config.yml)
|
|
106
|
+
|
|
107
|
+
# Spacing values outside the scale (heuristic: any odd or > 64 px literal)
|
|
108
|
+
grep -RInE '(padding|margin|gap|inset)[^:]*:\s*[0-9]+px' \
|
|
109
|
+
$(yq '.paths.components_root' baldart.config.yml)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For each hit, classify:
|
|
113
|
+
- **Tokenizable** — exact value matches an existing token → trivial replace
|
|
114
|
+
- **Missing token** — value would belong to the system but no token exists →
|
|
115
|
+
propose adding to `tokens-reference.md`
|
|
116
|
+
- **Justified one-off** — surface-specific value with a documented reason →
|
|
117
|
+
leave + add a code comment citing the exception
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
HARDCODED_TOKENS:
|
|
121
|
+
- tokenizable: 37 hits across 12 files
|
|
122
|
+
- missing-token: 8 hits (suggest 3 new tokens)
|
|
123
|
+
- justified-one-off: 2 hits
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 1.4 — Overlay drift (only if `.baldart/overlays/ui-expert.md` exists)
|
|
127
|
+
|
|
128
|
+
The v3.12.0 base `ui-expert.md` now defines authoritatively things the
|
|
129
|
+
overlay may have previously codified. Likely conflict points:
|
|
130
|
+
|
|
131
|
+
| Overlay section likely to conflict | Why |
|
|
132
|
+
|---|---|
|
|
133
|
+
| Custom "Red Flags" list | Base now has 60+ categorized red flags — overlay items may be duplicates or contradictions |
|
|
134
|
+
| Custom workflow steps | Base now has step 10 (Review) and step 12 (Design) as BLOCKING completion gates — overlay must not skip them |
|
|
135
|
+
| Custom token rules | Base now references the Reference Tables in `design-system-protocol.md` — overlay numeric scales should defer to the protocol |
|
|
136
|
+
| Custom motion guidance | Base now delegates to `motion-design` skill — overlay should point there, not redefine durations/easings |
|
|
137
|
+
|
|
138
|
+
Read `.baldart/overlays/ui-expert.md` and report each section that:
|
|
139
|
+
- Uses `## [OVERRIDE]` marker on a section the base now owns
|
|
140
|
+
authoritatively (red flags, workflow steps, expertise enumeration).
|
|
141
|
+
- Uses `## [APPEND]` to add red flags that are now in the base set
|
|
142
|
+
(duplicates).
|
|
143
|
+
- Hardcodes scales / thresholds / durations the new protocol tables now
|
|
144
|
+
declare canonically.
|
|
145
|
+
|
|
146
|
+
Output:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
OVERLAY_DRIFT:
|
|
150
|
+
- [OVERRIDE] Your Expertise — conflicts with expanded base (consider [APPEND] instead)
|
|
151
|
+
- [APPEND] Red Flags — 4 of 6 items now in base list (deduplicate)
|
|
152
|
+
- hardcoded motion duration 250ms — defer to motion-design timing tables
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 1.5 — Emit the baseline report
|
|
156
|
+
|
|
157
|
+
Save the four sections above as:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
${paths.references_dir:-docs/references}/ds-coherence-baseline-YYYYMMDD.md
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Present a short summary to the user (totals per finding type + estimated
|
|
164
|
+
size of the reconciliation work). **Stop here for user confirmation** before
|
|
165
|
+
proceeding to Step 2.
|
|
166
|
+
|
|
167
|
+
## Step 2 — Prioritize reconciliation
|
|
168
|
+
|
|
169
|
+
Not all findings are equal. Rank them so the user gets quick wins first.
|
|
170
|
+
|
|
171
|
+
| Priority | Criteria | Action |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| **P0** | Silent primitive used in ≥ 3 places AND broadly-useful | Add INDEX entry + scaffold full `components/<Name>.md` spec |
|
|
174
|
+
| **P0** | Hardcoded token (tokenizable) in the design-system entry-point file or in a primitive | Replace with the existing token reference inline |
|
|
175
|
+
| **P1** | Silent primitive used in 1–2 places, broadly-useful | Add INDEX entry + stub spec (`status: needs-review`) |
|
|
176
|
+
| **P1** | Stale spec on a primitive used by ≥ 3 features | Update spec to match source |
|
|
177
|
+
| **P1** | Missing-token (hardcoded value that should be a token) used > 5 times | Propose new token + update `tokens-reference.md` + replace inline |
|
|
178
|
+
| **P2** | Surface-specific silent primitive | Add INDEX entry with `scope: local` + 2-line note |
|
|
179
|
+
| **P2** | Stale spec on a rarely-used primitive | Update spec OR open follow-up card |
|
|
180
|
+
| **P3** | Justified one-off hardcoded value | Add inline code comment citing the exception |
|
|
181
|
+
| **P3** | Overlay-base conflict that does not break the new gates | Refactor overlay to use `[APPEND]` instead of `[OVERRIDE]` |
|
|
182
|
+
|
|
183
|
+
Convert the prioritized findings into the project's backlog format. If
|
|
184
|
+
`features.has_backlog: true`:
|
|
185
|
+
|
|
186
|
+
- One card per P0 item.
|
|
187
|
+
- One card per **group** of P1 items (e.g. "Reconcile 8 stale specs in
|
|
188
|
+
`components/forms/`").
|
|
189
|
+
- One catch-all card for P2/P3 batched cleanup.
|
|
190
|
+
|
|
191
|
+
If no backlog feature → emit a markdown checklist under
|
|
192
|
+
`${paths.references_dir}/ds-coherence-followups-YYYYMMDD.md` and present it
|
|
193
|
+
to the user.
|
|
194
|
+
|
|
195
|
+
## Step 3 — Quick wins (apply now, with user approval)
|
|
196
|
+
|
|
197
|
+
Apply only the items that are **truly trivial and reversible**:
|
|
198
|
+
|
|
199
|
+
- Inline replacement of `tokenizable` hardcoded values (the token already
|
|
200
|
+
exists; the change is a pure substitution).
|
|
201
|
+
- Adding INDEX entries for surface-specific silent primitives (no spec
|
|
202
|
+
needed beyond the scope: local note).
|
|
203
|
+
- Removing overlay `[APPEND]` items that exactly duplicate the new base
|
|
204
|
+
red flags.
|
|
205
|
+
|
|
206
|
+
Group them in **one commit per category** (`chore(ds): replace hardcoded
|
|
207
|
+
tokens with existing references`, `docs(ds): index surface-specific
|
|
208
|
+
primitives`, etc.) so the diff is reviewable.
|
|
209
|
+
|
|
210
|
+
Anything more substantial (new specs, new tokens, overlay refactors, stale
|
|
211
|
+
spec updates) → backlog cards, not inline edits. Resist the urge to "just
|
|
212
|
+
clean it up while you're there" — that turns the alignment into a
|
|
213
|
+
multi-day refactor.
|
|
214
|
+
|
|
215
|
+
## Step 4 — Verify the new gates fire correctly
|
|
216
|
+
|
|
217
|
+
After the quick wins land, pick **one** small UI task that touches a
|
|
218
|
+
primitive (e.g. add a variant to an existing Button) and run it end-to-end:
|
|
219
|
+
|
|
220
|
+
1. Invoke `ui-expert` or the relevant skill.
|
|
221
|
+
2. Make the code change.
|
|
222
|
+
3. Observe that the agent's final output explicitly reports the
|
|
223
|
+
Post-Intervention Coherence Check result — either "no findings" or one
|
|
224
|
+
of the `DS_*_DRIFT` codes.
|
|
225
|
+
|
|
226
|
+
If the agent silently skips this step → the upgrade didn't propagate to
|
|
227
|
+
this project. Check:
|
|
228
|
+
|
|
229
|
+
- `npx baldart status` confirms v3.12.0 installed.
|
|
230
|
+
- `.framework/framework/agents/design-system-protocol.md` contains the
|
|
231
|
+
"Post-Intervention Coherence Check" section.
|
|
232
|
+
- `.framework/framework/.claude/agents/ui-expert.md` has step 10 (Review)
|
|
233
|
+
and step 12 (Design).
|
|
234
|
+
|
|
235
|
+
If any of those is stale → `npx baldart update` again, then re-test.
|
|
236
|
+
|
|
237
|
+
## Step 5 — Establish the rhythm
|
|
238
|
+
|
|
239
|
+
Going forward, the operator (you, or future Claude instances on this
|
|
240
|
+
project):
|
|
241
|
+
|
|
242
|
+
- Treats the coherence check at end-of-task as **non-negotiable**, same
|
|
243
|
+
category as "tests pass". A UI task without the coherence-check report
|
|
244
|
+
is incomplete.
|
|
245
|
+
- Treats `DS_*_DRIFT` findings surfaced by `code-reviewer` as merge
|
|
246
|
+
blockers — they indicate the upstream check was skipped.
|
|
247
|
+
- Reads the weekly `ds-drift` routine report when it lands and treats any
|
|
248
|
+
finding there as a **process bug** (somewhere the upstream gates were
|
|
249
|
+
bypassed), not just a content fix.
|
|
250
|
+
|
|
251
|
+
## Reference points (resolved by `npx baldart update`)
|
|
252
|
+
|
|
253
|
+
After update, these files exist locally and are authoritative:
|
|
254
|
+
|
|
255
|
+
| Concern | File |
|
|
256
|
+
|---|---|
|
|
257
|
+
| What "coherence check" means + decision matrix | `.framework/framework/agents/design-system-protocol.md` § "Post-Intervention Coherence Check" |
|
|
258
|
+
| Numeric reference tables (type / contrast / spacing / density) | `.framework/framework/agents/design-system-protocol.md` § "Reference Tables (embed)" |
|
|
259
|
+
| Token cascade primitive → semantic → component | `.framework/framework/agents/design-system-protocol.md` § "Token Cascade" |
|
|
260
|
+
| Upgraded `ui-expert` (states taxonomy, red flags, gates) | `.framework/framework/.claude/agents/ui-expert.md` |
|
|
261
|
+
| Code-reviewer rule 8 (merge gate) | `.framework/framework/.claude/agents/code-reviewer.md` § "Design System Compliance" rule 8 |
|
|
262
|
+
| `ui-design` Step H (design-time gate) | `.framework/framework/.claude/skills/ui-design/SKILL.md` |
|
|
263
|
+
| Performance Gates (CWV 2026, LCP/INP/CLS) | `.framework/framework/.claude/skills/frontend-design/SKILL.md` § "Performance Gates" |
|
|
264
|
+
| Motion + reduced-motion + View Transitions | `.framework/framework/.claude/skills/motion-design/reference/accessibility-and-modern-apis.md` |
|
|
265
|
+
| Weekly safety net | `.framework/framework/routines/ds-drift.routine.yml` |
|
|
266
|
+
|
|
267
|
+
The framework files are symlinked, so reading them via the paths above
|
|
268
|
+
gets the live, current version.
|
|
@@ -112,6 +112,28 @@ features:
|
|
|
112
112
|
# LLM-wiki overlay (paths.wiki_dir + capture/wiki-curator loop).
|
|
113
113
|
has_wiki_overlay: false
|
|
114
114
|
|
|
115
|
+
# LSP symbol-search layer. When true, agents and skills that explore code
|
|
116
|
+
# (codebase-architect, context-primer, bug, prd, new, simplify) prefer
|
|
117
|
+
# language-server "find references / go to definition" over Grep for
|
|
118
|
+
# identifier queries, falling back to Grep only when LSP is unavailable
|
|
119
|
+
# or the query isn't a symbol. See framework/agents/code-search-protocol.md.
|
|
120
|
+
# `baldart configure` installs the relevant language servers when enabled.
|
|
121
|
+
has_lsp_layer: false
|
|
122
|
+
|
|
123
|
+
# ─── LSP ─────────────────────────────────────────────────────────────────
|
|
124
|
+
# State of the LSP layer for this project. Populated by `baldart configure`
|
|
125
|
+
# (and by `npx baldart lsp install` / the `/lsp-bootstrap` skill). Leave
|
|
126
|
+
# empty if `features.has_lsp_layer: false`.
|
|
127
|
+
lsp:
|
|
128
|
+
# Languages whose language server BALDART has installed/verified locally.
|
|
129
|
+
# Identifiers map to adapters under src/utils/lsp-adapters/
|
|
130
|
+
# (typescript, python, go, rust, ruby — extend as needed).
|
|
131
|
+
installed_servers: []
|
|
132
|
+
|
|
133
|
+
# When true, `baldart doctor` re-verifies the binaries are reachable on
|
|
134
|
+
# every run. Disable on CI if the verify step is too noisy.
|
|
135
|
+
auto_verify: true
|
|
136
|
+
|
|
115
137
|
# ─── GIT ─────────────────────────────────────────────────────────────────
|
|
116
138
|
# Controls how worktree-manager (`/mw`) integrates a worktree's feature
|
|
117
139
|
# branch back into `develop`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
base_agent: codebase-architect
|
|
3
|
+
base_agent_version: 3.10.0
|
|
4
|
+
mode: extend
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> **How to use this example**
|
|
8
|
+
>
|
|
9
|
+
> Copy this file to `.baldart/overlays/agents/codebase-architect.md` (drop the
|
|
10
|
+
> `.lsp-example`) when you want to add project-specific LSP guidance on top of
|
|
11
|
+
> the shipped `agents/code-search-protocol.md`. The framework regenerates
|
|
12
|
+
> `.claude/agents/codebase-architect.md` from base + overlay on every
|
|
13
|
+
> `npx baldart update`.
|
|
14
|
+
>
|
|
15
|
+
> Section markers (see `framework/templates/overlays/README.md`):
|
|
16
|
+
> - `## [OVERRIDE] <heading>` — replace the entire matching H2 from the base.
|
|
17
|
+
> - `## [APPEND] <heading>` — add content after the base section.
|
|
18
|
+
> - `## [PREPEND] <heading>` — add content before the base section.
|
|
19
|
+
> - `## <heading>` with no marker — appended at the end as a new custom section.
|
|
20
|
+
>
|
|
21
|
+
> Skip this overlay entirely if the shipped protocol is enough — the base
|
|
22
|
+
> agent already wires LSP correctly when `features.has_lsp_layer: true`.
|
|
23
|
+
|
|
24
|
+
## [APPEND] LSP Symbol Search
|
|
25
|
+
|
|
26
|
+
Project-specific tuning on top of `framework/agents/code-search-protocol.md`:
|
|
27
|
+
|
|
28
|
+
- **Preferred entry points.** For identifier-shaped lookups in this repo,
|
|
29
|
+
start with `find-references` on the symbol exported from
|
|
30
|
+
`src/<your-canonical-module>/index.ts` (or your equivalent). The export
|
|
31
|
+
surface is small enough that the references list converges in 1–2 calls.
|
|
32
|
+
- **Per-language quirks.**
|
|
33
|
+
- TypeScript: monorepo packages under `packages/<name>` need
|
|
34
|
+
`tsserver` workspaces enabled. If LSP returns zero references for a
|
|
35
|
+
symbol you know is used cross-package, fall back to Grep scoped to
|
|
36
|
+
`packages/*/src/`.
|
|
37
|
+
- Python: pyright honors `extraPaths` from `pyrightconfig.json` — if
|
|
38
|
+
you re-rooted sources, double-check the config before reporting "no
|
|
39
|
+
references found".
|
|
40
|
+
- **Don't chase generated code.** Skip LSP results pointing into `dist/`,
|
|
41
|
+
`.next/`, `coverage/`, or any path matching the project's `.gitignore` —
|
|
42
|
+
treat them as noise.
|
|
43
|
+
|
|
44
|
+
## Project-only conventions
|
|
45
|
+
|
|
46
|
+
Custom rules with no equivalent in the base — appended as a new H2:
|
|
47
|
+
|
|
48
|
+
- All `find-references` outputs over 50 callsites trigger a refactor
|
|
49
|
+
conversation with the maintainer before any rename / signature change
|
|
50
|
+
proceeds.
|
|
51
|
+
- When LSP is unavailable, write the reason (binary missing / plugin not
|
|
52
|
+
loaded / language not yet adopted) into the analysis output so reviewers
|
|
53
|
+
understand why Grep was used instead.
|
package/package.json
CHANGED
|
@@ -3,6 +3,8 @@ const path = require('path');
|
|
|
3
3
|
const yaml = require('js-yaml');
|
|
4
4
|
const UI = require('../utils/ui');
|
|
5
5
|
const toolAdapters = require('../utils/tool-adapters');
|
|
6
|
+
const LspInstaller = require('../utils/lsp-installer');
|
|
7
|
+
const lspAdapters = require('../utils/lsp-adapters');
|
|
6
8
|
|
|
7
9
|
const CONFIG_FILE = 'baldart.config.yml';
|
|
8
10
|
// The subtree pull copies the entire BALDART repo (which itself has a
|
|
@@ -342,6 +344,9 @@ function detect(cwd = process.cwd()) {
|
|
|
342
344
|
has_adrs: countMatches('docs/decisions', /^ADR-.*\.md$/) > 0,
|
|
343
345
|
has_prd_workflow: exists('docs/prd'),
|
|
344
346
|
has_wiki_overlay: exists('docs/wiki'),
|
|
347
|
+
// LSP recommended when any supported language server adapter would
|
|
348
|
+
// detect this project. Drives the default for the configure prompt.
|
|
349
|
+
has_lsp_layer: lspAdapters.detectAll(cwd).length > 0,
|
|
345
350
|
},
|
|
346
351
|
tools: {
|
|
347
352
|
enabled: toolAdapters.defaultEnabled(cwd)
|
|
@@ -506,6 +511,7 @@ async function interactivePrompts(merged, detected) {
|
|
|
506
511
|
['has_adrs', 'Project uses ADR workflow?'],
|
|
507
512
|
['has_prd_workflow', 'Project uses PRD workflow (docs/prd/)?'],
|
|
508
513
|
['has_wiki_overlay', 'Project has LLM-wiki overlay (docs/wiki/)?'],
|
|
514
|
+
['has_lsp_layer', 'Enable LSP symbol-search layer? (recommended for large codebases)'],
|
|
509
515
|
]) {
|
|
510
516
|
const [key, question] = flag;
|
|
511
517
|
const currentVal = merged.features[key];
|
|
@@ -516,6 +522,27 @@ async function interactivePrompts(merged, detected) {
|
|
|
516
522
|
merged.features[key] = await promptForKey(question, proposedDefault, 'confirm');
|
|
517
523
|
}
|
|
518
524
|
|
|
525
|
+
// ---- Design-system bootstrap hint (since v3.11.0) ---------------------
|
|
526
|
+
// If the user said YES to has_design_system but autodetect found no path
|
|
527
|
+
// (i.e. they WANT a registry but it doesn't exist yet), surface the
|
|
528
|
+
// bootstrap skill so they don't end up with a flag that points at nothing.
|
|
529
|
+
// If they said NO, plant a soft pointer for the future.
|
|
530
|
+
if (merged.features.has_design_system === true && !detected.paths.design_system) {
|
|
531
|
+
UI.info(
|
|
532
|
+
'No design-system path was detected on disk. After configure, run\n' +
|
|
533
|
+
' /design-system-init (or invoke the skill)\n' +
|
|
534
|
+
'to scaffold INDEX.md + tokens-reference.md + per-component specs at\n' +
|
|
535
|
+
'docs/design-system/. The registry-first protocol (ui-expert, ui-design,\n' +
|
|
536
|
+
'frontend-design, /design-review) needs these files to enforce coherence.'
|
|
537
|
+
);
|
|
538
|
+
} else if (merged.features.has_design_system === false) {
|
|
539
|
+
UI.info(
|
|
540
|
+
'has_design_system=false — UI work will rely only on ui_guidelines.md.\n' +
|
|
541
|
+
'When you want visual coherence + component reuse enforced project-wide,\n' +
|
|
542
|
+
'run /design-system-init to scaffold a registry from your existing code.'
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
519
546
|
// Path prompts — only ask for paths whose feature is enabled and value missing
|
|
520
547
|
UI.section('Paths (autodetected — confirm or override)');
|
|
521
548
|
const pathPrompts = [
|
|
@@ -544,6 +571,48 @@ async function interactivePrompts(merged, detected) {
|
|
|
544
571
|
merged.paths[key] = await promptForKey(`${label}`, proposed);
|
|
545
572
|
}
|
|
546
573
|
|
|
574
|
+
// ---- LSP layer (since v3.10.0) ----------------------------------------
|
|
575
|
+
// Only runs when the user opted in above. Idempotent: re-running configure
|
|
576
|
+
// on an already-installed project re-detects, asks which servers to keep,
|
|
577
|
+
// and updates lsp.installed_servers accordingly.
|
|
578
|
+
merged.lsp = merged.lsp || { installed_servers: [], auto_verify: true };
|
|
579
|
+
if (merged.features.has_lsp_layer === true) {
|
|
580
|
+
UI.section('LSP layer (install language servers for symbol search)');
|
|
581
|
+
const installer = new LspInstaller(process.cwd());
|
|
582
|
+
const detectedLangs = installer.detectLanguages();
|
|
583
|
+
if (detectedLangs.length === 0) {
|
|
584
|
+
UI.warning('No supported languages detected (tsconfig.json, pyproject.toml, go.mod, Cargo.toml, Gemfile).');
|
|
585
|
+
UI.info('Skipping LSP install. You can run `/lsp-bootstrap` or `npx baldart configure` later.');
|
|
586
|
+
} else {
|
|
587
|
+
const recs = installer.recommend(detectedLangs);
|
|
588
|
+
const already = new Set(merged.lsp.installed_servers || []);
|
|
589
|
+
const toInstall = [];
|
|
590
|
+
for (const r of recs) {
|
|
591
|
+
const note = already.has(r.name) ? ' (already recorded)' : '';
|
|
592
|
+
UI.info(`${r.label} → ${r.binary} (${r.installMode})${note}`);
|
|
593
|
+
const want = await UI.confirm(`Install ${r.label}?`, true);
|
|
594
|
+
if (want) toInstall.push(r.name);
|
|
595
|
+
}
|
|
596
|
+
if (toInstall.length) {
|
|
597
|
+
const { installed, skipped, manual } = await installer.installServers(toInstall, { interactive: false });
|
|
598
|
+
for (const m of manual) {
|
|
599
|
+
UI.warning(`System-level install required for ${m.label}:`);
|
|
600
|
+
UI.code(m.command, `Recorded as pending — run \`baldart doctor\` after installing to verify.`);
|
|
601
|
+
}
|
|
602
|
+
const verify = installer.verifyServers(installed.map(i => i.name));
|
|
603
|
+
const verified = verify.filter(v => v.ok).map(v => v.name);
|
|
604
|
+
const newSet = new Set([...(merged.lsp.installed_servers || []), ...verified, ...manual.map(m => m.name)]);
|
|
605
|
+
merged.lsp.installed_servers = Array.from(newSet);
|
|
606
|
+
if (skipped.length) {
|
|
607
|
+
UI.warning(`Skipped: ${skipped.map(s => `${s.name} (${s.reason})`).join(', ')}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
// Feature disabled — wipe the state so `installed_servers` doesn't lie.
|
|
613
|
+
merged.lsp.installed_servers = [];
|
|
614
|
+
}
|
|
615
|
+
|
|
547
616
|
UI.section('Stack (autodetected from package.json — confirm or override)');
|
|
548
617
|
const charting = merged.stack.charting;
|
|
549
618
|
const chartingCanonical = await promptForKey(
|
package/src/commands/doctor.js
CHANGED
|
@@ -30,6 +30,7 @@ const GitUtils = require('../utils/git');
|
|
|
30
30
|
const UI = require('../utils/ui');
|
|
31
31
|
const State = require('../utils/state');
|
|
32
32
|
const Hooks = require('../utils/hooks');
|
|
33
|
+
const LspInstaller = require('../utils/lsp-installer');
|
|
33
34
|
|
|
34
35
|
const FRAMEWORK_DIR = '.framework';
|
|
35
36
|
const REPO_DEFAULT = 'antbald/BALDART';
|
|
@@ -196,6 +197,25 @@ async function detectState(cwd, opts = {}) {
|
|
|
196
197
|
|
|
197
198
|
state.editGateRegistered = false;
|
|
198
199
|
try { state.editGateRegistered = Hooks.isRegistered(cwd); } catch (_) {}
|
|
200
|
+
|
|
201
|
+
// ---- LSP layer (since v3.10.0) -------------------------------------
|
|
202
|
+
state.lspEnabled = false;
|
|
203
|
+
state.lspInstalled = [];
|
|
204
|
+
state.lspBroken = [];
|
|
205
|
+
try {
|
|
206
|
+
// Reuse the `config` already parsed above instead of re-reading the file.
|
|
207
|
+
if (config && !config.__malformed && config.features && config.features.has_lsp_layer === true) {
|
|
208
|
+
state.lspEnabled = true;
|
|
209
|
+
const installed = (config.lsp && config.lsp.installed_servers) || [];
|
|
210
|
+
state.lspInstalled = installed;
|
|
211
|
+
const autoVerify = !(config.lsp && config.lsp.auto_verify === false);
|
|
212
|
+
if (autoVerify && installed.length) {
|
|
213
|
+
const installer = new LspInstaller(cwd);
|
|
214
|
+
const results = installer.verifyServers(installed);
|
|
215
|
+
state.lspBroken = results.filter(r => !r.ok).map(r => r.name);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (_) { /* never block doctor on LSP probe */ }
|
|
199
219
|
}
|
|
200
220
|
|
|
201
221
|
return state;
|
|
@@ -306,6 +326,27 @@ function planActions(state) {
|
|
|
306
326
|
});
|
|
307
327
|
}
|
|
308
328
|
|
|
329
|
+
if (state.lspEnabled && state.lspBroken && state.lspBroken.length > 0) {
|
|
330
|
+
actions.push({
|
|
331
|
+
key: 'lsp-fix',
|
|
332
|
+
label: `Reinstall ${state.lspBroken.length} broken LSP server(s)`,
|
|
333
|
+
why: `LSP layer is enabled but these language servers are not reachable: ${state.lspBroken.join(', ')}. Symbol search will silently fall back to Grep until they are reinstalled.`,
|
|
334
|
+
autoOk: false, // touches dev-deps and is the kind of action a user may
|
|
335
|
+
// have made deliberate (switching from npm pyright to system pyright);
|
|
336
|
+
// keep the outer prompt + the per-server confirm both alive.
|
|
337
|
+
run: async () => {
|
|
338
|
+
const installer = new LspInstaller(state.cwd);
|
|
339
|
+
const { installed, manual, skipped } = await installer.installServers(state.lspBroken);
|
|
340
|
+
if (installed.length) UI.success(`Reinstalled: ${installed.map(i => i.name).join(', ')}`);
|
|
341
|
+
for (const m of manual) {
|
|
342
|
+
UI.warning(`System-level reinstall required for ${m.label}:`);
|
|
343
|
+
console.log(` ${m.command}`);
|
|
344
|
+
}
|
|
345
|
+
if (skipped.length) UI.warning(`Skipped: ${skipped.map(s => `${s.name} (${s.reason})`).join(', ')}`);
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
309
350
|
const remoteAhead = state.remote.fetched && state.remote.behind > 0;
|
|
310
351
|
const hasLocalWork = state.workingTreeDirty > 0 || state.commitsAhead > 0;
|
|
311
352
|
|
|
@@ -437,6 +478,20 @@ function renderDiagnostic(state) {
|
|
|
437
478
|
state.editGateRegistered ? 'ok' : 'warn'
|
|
438
479
|
));
|
|
439
480
|
|
|
481
|
+
if (state.lspEnabled) {
|
|
482
|
+
const total = state.lspInstalled.length;
|
|
483
|
+
const broken = state.lspBroken.length;
|
|
484
|
+
const value = total === 0
|
|
485
|
+
? 'enabled but no servers installed'
|
|
486
|
+
: broken === 0
|
|
487
|
+
? `${total} server(s) verified (${state.lspInstalled.join(', ')})`
|
|
488
|
+
: `${broken}/${total} broken (${state.lspBroken.join(', ')})`;
|
|
489
|
+
const severity = total === 0 || broken > 0 ? 'warn' : 'ok';
|
|
490
|
+
console.log(statusLine('LSP layer', value, severity));
|
|
491
|
+
} else {
|
|
492
|
+
console.log(statusLine('LSP layer', 'disabled', 'ok'));
|
|
493
|
+
}
|
|
494
|
+
|
|
440
495
|
console.log();
|
|
441
496
|
}
|
|
442
497
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Go LSP adapter — gopls.
|
|
6
|
+
*
|
|
7
|
+
* gopls is a Go binary; we can't install it into node_modules. installMode
|
|
8
|
+
* is 'system' which means the configure flow prints the canonical install
|
|
9
|
+
* command and the user runs it themselves. The verify step still checks
|
|
10
|
+
* that the binary is in PATH.
|
|
11
|
+
*/
|
|
12
|
+
class GoAdapter {
|
|
13
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
14
|
+
|
|
15
|
+
get name() { return 'go'; }
|
|
16
|
+
get label() { return 'Go (gopls)'; }
|
|
17
|
+
get binary() { return 'gopls'; }
|
|
18
|
+
get installMode() { return 'system'; }
|
|
19
|
+
|
|
20
|
+
installCommand() { return 'go install golang.org/x/tools/gopls@latest'; }
|
|
21
|
+
verifyCommand() { return 'gopls version'; }
|
|
22
|
+
claudePluginId() { return null; }
|
|
23
|
+
|
|
24
|
+
static detect(cwd = process.cwd()) {
|
|
25
|
+
return fs.existsSync(path.join(cwd, 'go.mod'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = GoAdapter;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const TypeScriptAdapter = require('./typescript');
|
|
2
|
+
const PythonAdapter = require('./python');
|
|
3
|
+
const GoAdapter = require('./go');
|
|
4
|
+
const RustAdapter = require('./rust');
|
|
5
|
+
const RubyAdapter = require('./ruby');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* LSP adapter registry.
|
|
9
|
+
*
|
|
10
|
+
* Adding a new language (Java, C/C++, PHP, Elixir, …):
|
|
11
|
+
* 1. Create `src/utils/lsp-adapters/<lang>.js` exporting a class with the
|
|
12
|
+
* same shape as TypeScriptAdapter (name, label, binary, installMode,
|
|
13
|
+
* installCommand, verifyCommand, claudePluginId, static detect).
|
|
14
|
+
* 2. Add it to the REGISTRY below.
|
|
15
|
+
* 3. (Optional) Wire a Claude Code plugin id if one exists.
|
|
16
|
+
*
|
|
17
|
+
* Adapters describe *how* to install/verify a language server; they do NOT
|
|
18
|
+
* speak LSP themselves. The actual symbol search at runtime happens through
|
|
19
|
+
* whatever Claude Code plugin or MCP server the user has loaded.
|
|
20
|
+
*/
|
|
21
|
+
const REGISTRY = {
|
|
22
|
+
typescript: TypeScriptAdapter,
|
|
23
|
+
python: PythonAdapter,
|
|
24
|
+
go: GoAdapter,
|
|
25
|
+
rust: RustAdapter,
|
|
26
|
+
ruby: RubyAdapter
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function listAdapters() { return Object.keys(REGISTRY); }
|
|
30
|
+
|
|
31
|
+
function getAdapter(name, cwd) {
|
|
32
|
+
const Cls = REGISTRY[name];
|
|
33
|
+
if (!Cls) throw new Error(`Unknown LSP adapter: ${name}. Available: ${listAdapters().join(', ')}`);
|
|
34
|
+
return new Cls(cwd);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Probe the cwd for every known language and return the names of adapters
|
|
39
|
+
* whose static detect() fires. Pure: no install side effects.
|
|
40
|
+
*/
|
|
41
|
+
function detectAll(cwd = process.cwd()) {
|
|
42
|
+
return Object.entries(REGISTRY)
|
|
43
|
+
.filter(([, Cls]) => {
|
|
44
|
+
try { return Cls.detect(cwd); } catch { return false; }
|
|
45
|
+
})
|
|
46
|
+
.map(([name]) => name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { REGISTRY, listAdapters, getAdapter, detectAll };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Python LSP adapter — Pyright.
|
|
6
|
+
*
|
|
7
|
+
* Pyright ships as both an npm package and a pip package. We default to the
|
|
8
|
+
* npm devDep route so installs stay project-local and version-pinned without
|
|
9
|
+
* touching the user's Python env. Users who already drive Python via pip/uv
|
|
10
|
+
* can override by removing pyright from devDeps and installing system-wide.
|
|
11
|
+
*
|
|
12
|
+
* Detection signal: pyproject.toml, setup.py, setup.cfg, requirements.txt,
|
|
13
|
+
* Pipfile, or a top-level .py source tree.
|
|
14
|
+
*/
|
|
15
|
+
class PythonAdapter {
|
|
16
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
17
|
+
|
|
18
|
+
get name() { return 'python'; }
|
|
19
|
+
get label() { return 'Python (Pyright)'; }
|
|
20
|
+
get binary() { return 'pyright'; }
|
|
21
|
+
get installMode() { return 'npm-dev'; }
|
|
22
|
+
get npmPackage() { return 'pyright'; }
|
|
23
|
+
|
|
24
|
+
installCommand() { return 'npm install --save-dev pyright'; }
|
|
25
|
+
verifyCommand() { return 'npx --no-install pyright --version'; }
|
|
26
|
+
claudePluginId() { return null; }
|
|
27
|
+
|
|
28
|
+
static detect(cwd = process.cwd()) {
|
|
29
|
+
const markers = ['pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile'];
|
|
30
|
+
for (const m of markers) {
|
|
31
|
+
if (fs.existsSync(path.join(cwd, m))) return true;
|
|
32
|
+
}
|
|
33
|
+
// Fallback: a `src/` or top-level *.py file is a strong enough signal.
|
|
34
|
+
try {
|
|
35
|
+
const entries = fs.readdirSync(cwd);
|
|
36
|
+
if (entries.some(e => e.endsWith('.py'))) return true;
|
|
37
|
+
} catch { /* ignore */ }
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = PythonAdapter;
|