baldart 3.12.0 → 3.14.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 +68 -0
- package/README.md +24 -0
- package/VERSION +1 -1
- package/bin/baldart.js +14 -0
- package/framework/docs/LSP-LAYER.md +66 -9
- package/package.json +1 -1
- package/src/commands/add.js +24 -0
- package/src/commands/configure.js +44 -0
- package/src/commands/doctor.js +166 -8
- package/src/commands/push.js +0 -0
- package/src/commands/update.js +38 -1
- package/src/commands/version.js +5 -1
- package/src/utils/git.js +93 -0
- package/src/utils/lsp-adapters/go.js +1 -1
- package/src/utils/lsp-adapters/python.js +1 -1
- package/src/utils/lsp-adapters/ruby.js +1 -1
- package/src/utils/lsp-adapters/rust.js +1 -1
- package/src/utils/lsp-adapters/typescript.js +8 -2
- package/src/utils/lsp-installer.js +243 -0
- package/src/utils/state.js +27 -0
- package/src/utils/update-notifier.js +181 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,74 @@ 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.14.0] - 2026-05-23
|
|
9
|
+
|
|
10
|
+
BALDART closes the **LSP install completeness gap** AND adds a long-overdue **`.framework/` gitignore self-heal** so install / update / push never trip over a stale or pre-existing ignore rule.
|
|
11
|
+
|
|
12
|
+
### LSP install completeness
|
|
13
|
+
|
|
14
|
+
Since v3.10.0, opting into `features.has_lsp_layer: true` installed the language-server binary (typescript-language-server, pyright, gopls, rust-analyzer, ruby-lsp) into the consumer repo — but the matching native Claude Code plugin (`<lang>-lsp@claude-plugins-official`) was left for the user to install manually via `/plugin install`. Almost nobody did, so Claude itself never invoked the LSP and code-exploration silently fell back to Grep. The "opt-in to LSP" UX was effectively broken: users thought they had it, agents behaved as if they didn't. v3.14.0 wires the two layers together — when the user opts into LSP, BALDART now installs the binary AND enables the native plugin in the same `configure` step, idempotently and gated on `claude` being in `tools.enabled`.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`claudePluginId()` returns real plugin ids** in every LSP adapter (`src/utils/lsp-adapters/{typescript,python,go,rust,ruby}.js`). Previously a `null` placeholder; now resolves to `typescript-lsp@claude-plugins-official`, `pyright-lsp@claude-plugins-official`, `gopls-lsp@claude-plugins-official`, `rust-analyzer-lsp@claude-plugins-official`, `ruby-lsp@claude-plugins-official` respectively. All plugins live in the Anthropic-published [`anthropics/claude-plugins-official`](https://github.com/anthropics/claude-plugins-official) marketplace.
|
|
19
|
+
- **`LspInstaller.installClaudePlugins(serverNames, opts)`** in `src/utils/lsp-installer.js` — high-level installer that (1) verifies the `claude` CLI is on PATH, (2) idempotently registers the `claude-plugins-official` marketplace via `claude plugin marketplace add anthropics/claude-plugins-official --scope user` (no-op when already registered — list is parsed first), (3) reads `claude plugin list --json` to skip plugins already installed at any scope, (4) runs `claude plugin install <id> --scope user` per missing plugin. Defaults to `--scope user` so a single install is reused across every project on the machine (LSP plugins are machine-wide useful — project scope would create N copies). Always returns `{ installed, skipped, manual, marketplace }`; never throws.
|
|
20
|
+
- **`LspInstaller.ensureClaudeMarketplace()`, `isClaudeCliAvailable()`, `listInstalledClaudePlugins()`, `verifyClaudePlugins(serverNames)`** — supporting primitives, all idempotent and degrade-open (parsing failure returns empty set → next install is attempted rather than incorrectly skipped).
|
|
21
|
+
- **`baldart configure` companion-plugin step** — after installing the language servers, the configure flow now calls `installClaudePlugins(merged.lsp.installed_servers)`. Surfaces clear messaging for each branch: success (✓ Installed plugin ids), idempotent skip ("Already installed"), Claude CLI missing (manual install commands printed verbatim per plugin), or `claude` opted out of `tools.enabled` (silent skip — Codex-only users don't want Claude marketplace mutation).
|
|
22
|
+
- **`baldart doctor` companion-plugin diagnostic** — `detectState` now calls `verifyClaudePlugins` when LSP is enabled and the `claude` CLI is available, populating `state.lspMissingClaudePlugins`. The `LSP layer` row in the diagnostic table reports `N server(s) verified, M Claude plugin(s) missing (…)` when the layer is half-installed. Severity `warn`.
|
|
23
|
+
- **`lsp-install-claude-plugins` action** in the doctor planner — `autoOk: true` (user-scope plugin install is idempotent and fully reversible via `claude plugin uninstall`). Backfills missing plugins for installs predating v3.14.0 and recovers from manual plugin uninstalls. Pairs with other actions; never short-circuits.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **LSP opt-in is now end-to-end.** Pre-v3.14.0: `features.has_lsp_layer: true` installed the binary but the integration was dormant until the user manually enabled the plugin. Post-v3.14.0: opting in installs both layers in the same step. The framework's "always-ask, never-assume" contract still applies — when `claude` is not in `tools.enabled` OR the `claude` CLI is missing, the install gracefully degrades (no error, clear hint, deferred until the prerequisites are in place). `baldart doctor` then surfaces the half-install state on every subsequent invocation.
|
|
28
|
+
|
|
29
|
+
### Notes
|
|
30
|
+
|
|
31
|
+
- **No new `baldart.config.yml` keys.** The plugin install reuses `features.has_lsp_layer`, `lsp.installed_servers`, and `tools.enabled` already in place — so the [schema-change propagation rule](../.claude/projects/-Users-antoniobaldassarre-BALDART/memory/feedback_schema_change_propagation.md) does not require a new template / configure prompt / update detector for this release. Scope is hard-coded to `user` (machine-wide LSP plugins) rather than exposed as `lsp.claude_plugin_scope` — the decision is opinionated and consistent with how the official marketplace tracks its own installs. If a future release needs per-project plugin scope, *that* would trigger the propagation rule (template + prompt + update detector + this changelog).
|
|
32
|
+
- **Marketplace and plugins live at `--scope user`, not project.** Three reasons: (1) LSP plugins are machine-wide useful — typescript-lsp installed once at user scope is reused by every TS project on the machine, not duplicated N times; (2) marketplace install at user scope means subsequent `baldart configure` runs in sibling projects skip the marketplace add entirely (idempotent); (3) project-scope plugins live under `.claude/` in the consumer repo, which would either need committing (noisy diffs) or `.gitignore` adjustments (extra protocol burden). User scope avoids all three.
|
|
33
|
+
- **Idempotency is checked twice.** Marketplace add reads `claude plugin marketplace list --json` first; plugin install reads `claude plugin list --json` first. Re-running `baldart configure` (or `baldart doctor` with the action accepted) is a no-op when everything is in place — no spurious install commands, no error noise.
|
|
34
|
+
- **Gate on `claude` in `tools.enabled`.** When the consumer has `tools.enabled: [codex]` (Codex-only), the plugin install is a silent no-op — Codex has no equivalent plugin model and Claude marketplace mutation would be off-target. Doctor diagnostics for missing plugins are similarly gated.
|
|
35
|
+
- **No framework payload changes.** All work is inside `src/` — adapters, installer, configure command, doctor command. No framework agent/skill/command/protocol module touched, so existing consumers see the new behavior the next time they run `baldart configure` or `baldart doctor` after upgrading the CLI (`npm i -g baldart@latest`).
|
|
36
|
+
|
|
37
|
+
### `.framework/` gitignore self-heal
|
|
38
|
+
|
|
39
|
+
`.framework/` is a git subtree — it MUST be tracked. If anything in the gitignore chain (project `.gitignore`, `.git/info/exclude`, global `core.excludesFile`) matches `.framework`, every subsequent `git add` on files under that directory fails with `paths are ignored by one of your .gitignore files` — and `git subtree pull` itself errors out. Pre-v3.14.0 we relied on the user noticing and removing the rule manually; v3.14.0 detects and self-heals.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **`GitUtils.checkFrameworkIgnored()` and `ensureFrameworkNotIgnored()`** in `src/utils/git.js` — `check-ignore -v --no-index .framework` to detect the offending source / line / pattern, then idempotently strip standalone `.framework` rules from the project `.gitignore` and append a one-time `!.framework` + `!.framework/**` negation block marked with `# BALDART: .framework/ is a git subtree — never ignore (auto-managed)`. Re-checks after writing — if the rule lives in a location we cannot rewrite from here (parent-dir `.gitignore` outside the repo, non-standard `core.excludesFile`), surfaces the leftover state via `{ stillIgnored: true, recheck }` so the caller can hard-fail with actionable output.
|
|
44
|
+
- **Self-heal call in `baldart add`** (before `git subtree add`), **`baldart update`** (before `git subtree pull`), **`baldart push`** (before any `git add`). All three flows degrade gracefully — if `check-ignore` itself fails, the heal step prints a warning and continues; if the rule persists after heal, the command exits 1 with the resolution path printed.
|
|
45
|
+
|
|
46
|
+
### Notes (gitignore)
|
|
47
|
+
|
|
48
|
+
- **Idempotent across re-runs.** The negation marker is checked before appending; the strip step is a `filter` over current lines. Running `add` / `update` / `push` repeatedly on a healthy repo is a no-op.
|
|
49
|
+
- **No false positives on .framework-related paths.** The strip filter matches only the six exact forms (`.framework`, `.framework/`, `/.framework`, `/.framework/`, `.framework/*`, `/.framework/*`) — `.frameworks/` or `framework/something.bak` patterns are preserved untouched.
|
|
50
|
+
|
|
51
|
+
## [3.13.0] - 2026-05-23
|
|
52
|
+
|
|
53
|
+
BALDART closes **two drift visibility gaps in one release**. (1) The CLI binary (installed globally via `npm i -g baldart`) and the framework payload (`.framework/` inside each consumer repo, updated by `baldart update`) drifted silently — users on an old CLI never learned that new flags, new doctor checks, or new adapters had shipped. (2) The framework payload on disk and the `.baldart/state.json` ledger could drift silently when the ledger write failed (the catch in `update.js` was silent until now), when an update flow exited mid-way, or when a manual commit excluded `.baldart/state.json` — `baldart doctor` had no way to detect or fix this. Both gaps are now closed: a **non-invasive CLI self-update notifier** for the first, and **ledger drift detection + self-heal** in the doctor for the second. The unifying theme is "no silent drift" — every layer that can fall out of sync now reports it and offers a one-step fix.
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- **`src/utils/update-notifier.js`** — best-effort CLI self-update check. HTTPS GET to `https://registry.npmjs.org/baldart/latest` with a 1.5s timeout, cached at `~/.baldart/cli-update-check.json` (TTL 24h). Banner is printed from the cache on subsequent invocations (the very first run shows nothing — that is intentional; the check never blocks the command). The background refresh fire-and-forget: its timer is `unref()`'d and the underlying socket dies with the process, so the check can never hold the event loop open. Suppression: `BALDART_NO_UPDATE_CHECK=1`, `CI=true`, `NODE_ENV=test`, non-TTY stdout, or `--offline` on `baldart` / `baldart doctor` / `baldart version`.
|
|
58
|
+
- **Top-of-run banner in `bin/baldart.js`** — when a newer baldart npm package has been detected on a previous run, every command prints a 3-line hint at startup (yellow arrow + bold version, then two gray lines): `↑ baldart X.Y.Z available (you have A.B.C)` + `Update with: npm i -g baldart@latest` + `Suppress with: BALDART_NO_UPDATE_CHECK=1`. Zero-latency (reads cache only); no banner if cache is missing or up-to-date.
|
|
59
|
+
- **`CLI` row in `baldart doctor` diagnostic table** — always shown (independent of git/framework state), surfaces drift inline alongside framework version / config / overlays / hooks so the doctor remains the single pane of glass. Severity `warn` when an update is available, `ok` otherwise.
|
|
60
|
+
- **CLI drift in `baldart version` box** — the existing `CLI: vX.Y.Z` line now appends `→ vA.B.C available (npm i -g baldart@latest)` when a newer version was detected.
|
|
61
|
+
- **`State.reconcileInstalledVersion({ to, reason })` in `src/utils/state.js`** — new ledger reconciliation helper. Idempotent (no-op when `from === to`), audit-trailed (writes a `ledger-reconcile` history entry distinct from real `update` events), and reversible (pure local file write). Used by the doctor's new `reconcile-state-ledger` action and available for direct call from future recovery flows.
|
|
62
|
+
- **Ledger drift detection in `baldart doctor`** — `detectState` now compares `.framework/VERSION` against `.baldart/state.json:installed_version` and surfaces a `stateLedgerDrift` state field when they diverge. The action planner inserts a `reconcile-state-ledger` action (`autoOk: true`, idempotent, no remote effects) that one-shot fixes the drift via `State.reconcileInstalledVersion()`. Drift is non-blocking — it pairs with other proposed actions instead of short-circuiting them.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **`src/commands/update.js`** — replaced the silent `try { State.recordUpdate(...) } catch (_) {}` on the ledger write with a visible warning + actionable next step. When the write fails or returns an unexpected `installed_version`, the user now sees a yellow warning explaining the drift (framework on disk is at vNEW but ledger still records vOLD) and is told to run `baldart doctor` to reconcile. This is the root cause of most "my doctor says I'm on the wrong version" reports — silent catches were hiding write failures (permissions, EBUSY) for weeks. New behavior makes the failure immediately visible and self-healable.
|
|
67
|
+
|
|
68
|
+
### Notes
|
|
69
|
+
|
|
70
|
+
- **CLI vs framework — two independent layers.** `npm i -g baldart@latest` updates the binary on disk (this package); `baldart update` updates the *framework payload* inside the consumer repo (the `.framework/` subtree). They are *independent* by design — a consumer may want to update the framework without touching the global CLI (or vice versa). The notifier surfaces drift on the *CLI* layer; the existing `Remote` row in doctor (and the `Remote:` line in `version`) surfaces drift on the *framework* layer. Both layers are now visible at a glance from the same diagnostic surface.
|
|
71
|
+
- **Why not auto-install.** Three reasons we deliberately reject `npx baldart` auto-running `npm i -g`: (1) blast radius — global install impacts *every* project on the user's machine, not just the one they invoked from; (2) permissions — `npm i -g` often needs sudo (without nvm/volta), and a no-args `baldart` cannot trigger sudo prompts silently; (3) reproducibility — `--auto` runs in CI must be deterministic, so auto-updating the CLI mid-run would change behavior unpredictably. The notifier is the right level: visible, dismissible, never invasive.
|
|
72
|
+
- **No new `baldart.config.yml` keys.** The notifier cache lives in `~/.baldart/cli-update-check.json` (per-user, not per-project), so the schema-change propagation rule (per `feedback_schema_change_propagation`) does not apply to this release. Suppression is via environment variable (`BALDART_NO_UPDATE_CHECK=1`) which is intentionally global — disabling the notifier in one project but enabling it in another is not a use case worth modeling.
|
|
73
|
+
- **Backwards-compatible.** Pre-3.13 consumers see the new banner on their next `baldart` run after the npm package upgrades. Consumers that never want the check set `BALDART_NO_UPDATE_CHECK=1` in their shell rc; CI runs are auto-suppressed.
|
|
74
|
+
- **No framework payload changes.** All work is inside `src/` and `bin/` — the framework agents/skills/commands/protocols are unchanged. This is purely a CLI-layer improvement; no consumer files are touched by `baldart update`.
|
|
75
|
+
|
|
8
76
|
## [3.12.0] - 2026-05-23
|
|
9
77
|
|
|
10
78
|
BALDART completes the **UI excellence loop**: the `ui-expert` agent is upgraded from a "good but generic" baseline to a world-class UI/UX reviewer/designer, and — crucially — the registry-first discipline gains a **post-intervention coherence gate** so visual coherence is enforced not only when work begins (BLOCKING reads) but also when work ends (BLOCKING completion check). The asymmetry of v3.11.0 (registry read before, no verification after) is closed: every UI change introduced by `ui-expert`, the `ui-design` skill, the `frontend-design` skill, or caught by `code-reviewer` must now reconcile the registry (INDEX + per-component specs + tokens-reference) in the **same change**, never deferring drift to the weekly `ds-drift` routine. This release contains zero schema changes — the cascade reuses `features.has_design_system` + `paths.*` already in place.
|
package/README.md
CHANGED
|
@@ -156,6 +156,30 @@ last-push info. Use `--offline` to skip the upstream fetch.
|
|
|
156
156
|
|
|
157
157
|
State updates happen automatically — no manual bookkeeping required.
|
|
158
158
|
|
|
159
|
+
### CLI self-update notifier (new in v3.13.0)
|
|
160
|
+
|
|
161
|
+
The CLI binary (`baldart`, installed globally via `npm i -g baldart`) and the
|
|
162
|
+
framework payload (`.framework/` inside each repo, updated by `baldart update`)
|
|
163
|
+
are two independent layers. Until now the CLI drifted silently — users on an
|
|
164
|
+
old CLI never learned about new flags or new doctor checks.
|
|
165
|
+
|
|
166
|
+
Now every `baldart` run does a best-effort check against `registry.npmjs.org`
|
|
167
|
+
(cached at `~/.baldart/cli-update-check.json` with a 24h TTL, 1.5s network
|
|
168
|
+
timeout, fire-and-forget). When a newer version is available, a one-shot
|
|
169
|
+
banner appears at the top of the run:
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
↑ baldart 3.13.0 available (you have 3.12.0)
|
|
173
|
+
Update with: npm i -g baldart@latest
|
|
174
|
+
Suppress with: BALDART_NO_UPDATE_CHECK=1
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The notifier never installs anything automatically — global installs can
|
|
178
|
+
require sudo and affect every other project on your machine, so the choice
|
|
179
|
+
stays with you. CLI drift also appears inline in `baldart doctor` (new `CLI`
|
|
180
|
+
row) and in `baldart version`. Auto-suppressed in CI, in non-TTY runs, with
|
|
181
|
+
`--offline`, with `NODE_ENV=test`, or via `BALDART_NO_UPDATE_CHECK=1`.
|
|
182
|
+
|
|
159
183
|
### Project Configuration (new in v3.0.0)
|
|
160
184
|
|
|
161
185
|
BALDART skills are **portable across projects**. Instead of hard-coding paths
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.14.0
|
package/bin/baldart.js
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const packageJson = require('../package.json');
|
|
6
|
+
const updateNotifier = require('../src/utils/update-notifier');
|
|
7
|
+
|
|
8
|
+
// CLI self-update notifier (v3.13.0+). Best-effort; non-blocking; suppressed
|
|
9
|
+
// in CI / non-TTY / via BALDART_NO_UPDATE_CHECK=1 / when --offline is passed.
|
|
10
|
+
// Prints a one-shot hint at the top of the run if a newer npm version was
|
|
11
|
+
// detected on a previous invocation, then kicks off a background refresh.
|
|
12
|
+
// First-ever run shows nothing — that is intentional; we never block the
|
|
13
|
+
// command on a network call.
|
|
14
|
+
const rawArgsForNotifier = process.argv.slice(2);
|
|
15
|
+
const offlineForNotifier = rawArgsForNotifier.includes('--offline');
|
|
16
|
+
updateNotifier.announceAndRefresh({
|
|
17
|
+
currentVersion: packageJson.version,
|
|
18
|
+
offline: offlineForNotifier,
|
|
19
|
+
});
|
|
6
20
|
|
|
7
21
|
const program = new Command();
|
|
8
22
|
|
|
@@ -45,7 +45,7 @@ so existing behavior is preserved).
|
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
48
|
-
## 2. The
|
|
48
|
+
## 2. The five moving parts
|
|
49
49
|
|
|
50
50
|
```
|
|
51
51
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
@@ -53,17 +53,30 @@ so existing behavior is preserved).
|
|
|
53
53
|
│ features.has_lsp_layer: true │
|
|
54
54
|
│ lsp.installed_servers: [typescript, python] │
|
|
55
55
|
│ lsp.auto_verify: true │
|
|
56
|
+
│ tools.enabled: [claude] ← gates native plugin install │
|
|
56
57
|
└──────────────┬──────────────────────────────────────────────────────┘
|
|
57
58
|
│ read by
|
|
58
59
|
▼
|
|
59
60
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
60
61
|
│ Plumbing layer (CLI, this repo) │
|
|
61
62
|
│ src/utils/lsp-adapters/<lang>.js ← per-language recipes │
|
|
62
|
-
│ src/utils/lsp-installer.js ←
|
|
63
|
+
│ src/utils/lsp-installer.js ← server + plugin installer │
|
|
63
64
|
│ src/commands/configure.js (prompt + install on opt-in) │
|
|
64
|
-
│ src/commands/doctor.js (verify + lsp-fix
|
|
65
|
+
│ src/commands/doctor.js (verify + lsp-fix + plugin-fix) │
|
|
65
66
|
└──────────────┬──────────────────────────────────────────────────────┘
|
|
66
|
-
│ writes installed_servers;
|
|
67
|
+
│ writes installed_servers; runs `claude plugin install`
|
|
68
|
+
▼
|
|
69
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
70
|
+
│ Native Claude Code integration (since v3.14.0) │
|
|
71
|
+
│ Marketplace: anthropics/claude-plugins-official (user scope) │
|
|
72
|
+
│ Plugins: │
|
|
73
|
+
│ typescript-lsp@claude-plugins-official │
|
|
74
|
+
│ pyright-lsp@claude-plugins-official │
|
|
75
|
+
│ gopls-lsp@claude-plugins-official │
|
|
76
|
+
│ rust-analyzer-lsp@claude-plugins-official │
|
|
77
|
+
│ ruby-lsp@claude-plugins-official │
|
|
78
|
+
└──────────────┬──────────────────────────────────────────────────────┘
|
|
79
|
+
│ surfaces LSP to Claude Code at runtime
|
|
67
80
|
▼
|
|
68
81
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
69
82
|
│ Protocol layer (framework/) │
|
|
@@ -85,9 +98,13 @@ so existing behavior is preserved).
|
|
|
85
98
|
```
|
|
86
99
|
|
|
87
100
|
Each layer is independently testable and individually replaceable. The
|
|
88
|
-
adapters
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
adapters describe *how to install* the language server AND *which native
|
|
102
|
+
plugin* surfaces it to Claude. Pre-v3.14.0 the second half was a manual
|
|
103
|
+
step the user almost always skipped — opting into LSP put a binary on
|
|
104
|
+
disk that Claude never invoked. v3.14.0 closes that gap: the configure
|
|
105
|
+
flow installs both halves in one step, gated on `claude` being in
|
|
106
|
+
`tools.enabled` (Codex-only consumers are unaffected — the gate degrades
|
|
107
|
+
to a silent no-op).
|
|
91
108
|
|
|
92
109
|
---
|
|
93
110
|
|
|
@@ -123,6 +140,23 @@ When the user confirms, for each detected language:
|
|
|
123
140
|
`baldart doctor` re-verifies on the next run and flags the mismatch if the
|
|
124
141
|
user hasn't run the printed command yet.
|
|
125
142
|
|
|
143
|
+
**Then (v3.14.0+) the native plugin install runs.** For every server in
|
|
144
|
+
`installed_servers` whose adapter declares a `claudePluginId()`, configure:
|
|
145
|
+
|
|
146
|
+
1. Verifies the `claude` CLI is on PATH (skipped silently if not — manual
|
|
147
|
+
install commands are printed instead).
|
|
148
|
+
2. Idempotently registers the `claude-plugins-official` marketplace at user
|
|
149
|
+
scope (`claude plugin marketplace add anthropics/claude-plugins-official
|
|
150
|
+
--scope user` — no-op when already registered).
|
|
151
|
+
3. Reads `claude plugin list --json` to skip plugins already installed at
|
|
152
|
+
any scope.
|
|
153
|
+
4. Runs `claude plugin install <id> --scope user` for each missing plugin
|
|
154
|
+
(e.g. `typescript-lsp@claude-plugins-official`). User scope = a single
|
|
155
|
+
install is reused by every project on the machine.
|
|
156
|
+
|
|
157
|
+
Gated on `claude` being in `tools.enabled` — Codex-only consumers see a
|
|
158
|
+
silent skip, not a failure.
|
|
159
|
+
|
|
126
160
|
### 3.2 Update from pre-v3.10
|
|
127
161
|
|
|
128
162
|
```bash
|
|
@@ -238,7 +272,7 @@ Every adapter is a class exporting:
|
|
|
238
272
|
| `npmPackage` | string (npm-dev only) | What goes after `npm install --save-dev` |
|
|
239
273
|
| `installCommand()` | string | The exact shell command (printed or executed) |
|
|
240
274
|
| `verifyCommand()` | string | A no-side-effect probe (e.g. `--version`) |
|
|
241
|
-
| `claudePluginId()` | string \| null |
|
|
275
|
+
| `claudePluginId()` | string \| null | Native Claude Code plugin id (e.g. `typescript-lsp@claude-plugins-official`) — installed by the configure flow alongside the language server (v3.14.0+). `null` = no companion plugin |
|
|
242
276
|
| `static detect(cwd)` | boolean | Pure: does the language live in this repo? |
|
|
243
277
|
|
|
244
278
|
The contract is enforced by convention, not by an interface check. When you
|
|
@@ -249,14 +283,37 @@ to the REGISTRY in `lsp-adapters/index.js`.
|
|
|
249
283
|
|
|
250
284
|
`LspInstaller` exposes:
|
|
251
285
|
|
|
286
|
+
**Language-server layer (since v3.10.0):**
|
|
287
|
+
|
|
252
288
|
- `detectLanguages()` → string[] — pure, no I/O beyond filesystem reads
|
|
253
289
|
- `recommend(langs?)` → entries with install metadata for UI consumption
|
|
254
290
|
- `installServers(names, { interactive=true, dryRun=false })` →
|
|
255
291
|
`{ installed, skipped, manual }` — returns rather than throws
|
|
256
292
|
- `verifyServers(names)` → `[{ name, ok, output|error }]` — never throws
|
|
257
293
|
|
|
294
|
+
**Native Claude Code plugin layer (since v3.14.0):**
|
|
295
|
+
|
|
296
|
+
- `isClaudeCliAvailable()` → boolean — `claude --version` probe with 5s timeout
|
|
297
|
+
- `ensureClaudeMarketplace()` → `{ ok, alreadyPresent, error? }` — idempotently
|
|
298
|
+
registers `anthropics/claude-plugins-official` at user scope; reads the
|
|
299
|
+
existing marketplace list first so re-runs are no-ops
|
|
300
|
+
- `listInstalledClaudePlugins()` → `Set<string>` of `<name>@<marketplace>` ids
|
|
301
|
+
pulled from `claude plugin list --json`; empty set on any failure (degrade
|
|
302
|
+
open — we'd rather attempt a redundant install than incorrectly skip)
|
|
303
|
+
- `installClaudePlugins(names, { toolsEnabled=['claude'], scope='user', interactive=false })`
|
|
304
|
+
→ `{ installed, skipped, manual, marketplace, reason? }` — gated on
|
|
305
|
+
`claude` being in `toolsEnabled` AND the CLI being available; per-plugin
|
|
306
|
+
idempotency check; defaults to `--scope user` so a single install is reused
|
|
307
|
+
across every project on the machine
|
|
308
|
+
- `verifyClaudePlugins(names)` → `[{ name, pluginId, installed }]` —
|
|
309
|
+
consumed by `baldart doctor` to surface the half-installed state where the
|
|
310
|
+
language-server binary is on PATH but the matching plugin is missing
|
|
311
|
+
|
|
258
312
|
All side-effecting methods are gated by `interactive` and a per-server
|
|
259
|
-
confirm; doctor passes `interactive: true`
|
|
313
|
+
confirm; doctor passes `interactive: true` for the server layer and
|
|
314
|
+
`interactive: false` for the plugin layer (the plugin install is idempotent
|
|
315
|
+
and user-scope, so the outer doctor prompt + per-action `autoOk: true` is
|
|
316
|
+
sufficient).
|
|
260
317
|
|
|
261
318
|
---
|
|
262
319
|
|
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -79,6 +79,30 @@ async function add(repo, options) {
|
|
|
79
79
|
// Step 4: Download framework
|
|
80
80
|
UI.header('STEP 4/5: Download Framework');
|
|
81
81
|
|
|
82
|
+
// `.framework/` MUST be trackable — `git subtree add` itself fails if a
|
|
83
|
+
// pre-existing `.gitignore` rule matches the path. Heal proactively.
|
|
84
|
+
// (since v3.14.0)
|
|
85
|
+
try {
|
|
86
|
+
const heal = await git.ensureFrameworkNotIgnored();
|
|
87
|
+
if (heal.wasFixed) {
|
|
88
|
+
UI.warning(`.framework/ was ignored by ${heal.source || 'a gitignore'} — auto-healed before subtree add.`);
|
|
89
|
+
if (heal.removedLines > 0) {
|
|
90
|
+
UI.info(` Removed ${heal.removedLines} stale .framework line(s) from .gitignore.`);
|
|
91
|
+
}
|
|
92
|
+
if (heal.appendedNegation) {
|
|
93
|
+
UI.info(' Appended `!.framework` negation block to .gitignore (idempotent).');
|
|
94
|
+
}
|
|
95
|
+
if (heal.stillIgnored) {
|
|
96
|
+
UI.error('.framework/ is STILL ignored after auto-heal — install would fail.');
|
|
97
|
+
UI.info(`Source: ${heal.recheck.source}:${heal.recheck.line} (${heal.recheck.pattern})`);
|
|
98
|
+
UI.info('Resolve by removing that rule, then re-run `baldart add`.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
UI.warning(`Could not check .gitignore for .framework: ${err.message}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
82
106
|
const downloadSpinner = UI.spinner(`Downloading from ${repo}...`).start();
|
|
83
107
|
|
|
84
108
|
try {
|
|
@@ -607,6 +607,50 @@ async function interactivePrompts(merged, detected) {
|
|
|
607
607
|
UI.warning(`Skipped: ${skipped.map(s => `${s.name} (${s.reason})`).join(', ')}`);
|
|
608
608
|
}
|
|
609
609
|
}
|
|
610
|
+
|
|
611
|
+
// ---- Native Claude Code companion plugins (since v3.14.0) ----------
|
|
612
|
+
// Install the official LSP plugin alongside each language server. Without
|
|
613
|
+
// this step, opting into the LSP layer only puts the binary on disk —
|
|
614
|
+
// Claude itself never sees it and agents fall back to Grep silently.
|
|
615
|
+
// Gated on `claude` being in `tools.enabled` and the `claude` CLI being
|
|
616
|
+
// on PATH; degrades gracefully otherwise.
|
|
617
|
+
const allLspServers = Array.from(new Set([
|
|
618
|
+
...(merged.lsp.installed_servers || [])
|
|
619
|
+
]));
|
|
620
|
+
if (allLspServers.length) {
|
|
621
|
+
const pluginResult = await installer.installClaudePlugins(allLspServers, {
|
|
622
|
+
toolsEnabled: merged.tools.enabled,
|
|
623
|
+
interactive: false,
|
|
624
|
+
scope: 'user'
|
|
625
|
+
});
|
|
626
|
+
if (pluginResult.reason === 'claude not in tools.enabled') {
|
|
627
|
+
UI.info('Skipping native Claude plugin install (claude not in tools.enabled).');
|
|
628
|
+
} else if (pluginResult.reason === 'claude CLI not on PATH') {
|
|
629
|
+
UI.warning('`claude` CLI not on PATH — native LSP plugins not installed.');
|
|
630
|
+
UI.info('Install them manually after adding the CLI:');
|
|
631
|
+
for (const m of pluginResult.skipped) {
|
|
632
|
+
// We don't have manual[] entries here (no targets resolved past the
|
|
633
|
+
// gate), so synthesize the command from the adapter directly.
|
|
634
|
+
try {
|
|
635
|
+
const a = lspAdapters.getAdapter(m.name, process.cwd());
|
|
636
|
+
const pid = a.claudePluginId();
|
|
637
|
+
if (pid) UI.code(`claude plugin install ${pid} --scope user`, m.name);
|
|
638
|
+
} catch { /* unknown adapter */ }
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
if (pluginResult.installed.length) {
|
|
642
|
+
UI.success(`Installed Claude LSP plugin(s): ${pluginResult.installed.map(i => i.pluginId).join(', ')}`);
|
|
643
|
+
}
|
|
644
|
+
const alreadyOnes = pluginResult.skipped.filter(s => s.reason === 'plugin already installed');
|
|
645
|
+
if (alreadyOnes.length) {
|
|
646
|
+
UI.info(`Already installed: ${alreadyOnes.map(s => s.name).join(', ')}`);
|
|
647
|
+
}
|
|
648
|
+
for (const m of pluginResult.manual) {
|
|
649
|
+
UI.warning(`Could not auto-install ${m.label} plugin — run manually:`);
|
|
650
|
+
UI.code(m.command, '');
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
610
654
|
}
|
|
611
655
|
} else {
|
|
612
656
|
// Feature disabled — wipe the state so `installed_servers` doesn't lie.
|
package/src/commands/doctor.js
CHANGED
|
@@ -31,6 +31,8 @@ const UI = require('../utils/ui');
|
|
|
31
31
|
const State = require('../utils/state');
|
|
32
32
|
const Hooks = require('../utils/hooks');
|
|
33
33
|
const LspInstaller = require('../utils/lsp-installer');
|
|
34
|
+
const UpdateNotifier = require('../utils/update-notifier');
|
|
35
|
+
const cliPackageJson = require('../../package.json');
|
|
34
36
|
|
|
35
37
|
const FRAMEWORK_DIR = '.framework';
|
|
36
38
|
const REPO_DEFAULT = 'antbald/BALDART';
|
|
@@ -146,6 +148,8 @@ async function detectState(cwd, opts = {}) {
|
|
|
146
148
|
const git = new GitUtils(cwd);
|
|
147
149
|
const state = {
|
|
148
150
|
cwd,
|
|
151
|
+
cliVersion: cliPackageJson.version,
|
|
152
|
+
cliUpdate: UpdateNotifier.hint({ currentVersion: cliPackageJson.version }),
|
|
149
153
|
isGitRepo: await git.isGitRepo(),
|
|
150
154
|
frameworkPresent: await git.frameworkExists(),
|
|
151
155
|
legacyLayout: false,
|
|
@@ -188,6 +192,19 @@ async function detectState(cwd, opts = {}) {
|
|
|
188
192
|
? `v${ledger.last_pushed_version} (${(ledger.last_push_date || '').slice(0, 10) || '—'})`
|
|
189
193
|
: null;
|
|
190
194
|
|
|
195
|
+
// Ledger drift detection (v3.13.0+): the framework on disk says one thing,
|
|
196
|
+
// the ledger says another. Caused by interrupted updates, manual commits
|
|
197
|
+
// that excluded state.json, or filesystem write failures the old silent
|
|
198
|
+
// catch in update.js used to hide. Self-healable via `reconcile-state-ledger`.
|
|
199
|
+
state.stateLedgerDrift = null;
|
|
200
|
+
if (state.frameworkVersion && ledger.installed_version &&
|
|
201
|
+
state.frameworkVersion !== ledger.installed_version) {
|
|
202
|
+
state.stateLedgerDrift = {
|
|
203
|
+
payload: state.frameworkVersion,
|
|
204
|
+
ledger: ledger.installed_version
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
191
208
|
state.remote = await describeRemote(git, ledger.framework_repo || REPO_DEFAULT, !!opts.offline);
|
|
192
209
|
state.commitsAhead = await commitsAheadOfRemote(git);
|
|
193
210
|
state.workingTreeDirty = await localFrameworkChanges(git);
|
|
@@ -198,10 +215,18 @@ async function detectState(cwd, opts = {}) {
|
|
|
198
215
|
state.editGateRegistered = false;
|
|
199
216
|
try { state.editGateRegistered = Hooks.isRegistered(cwd); } catch (_) {}
|
|
200
217
|
|
|
218
|
+
// `.framework/` is a git subtree — gitignore matching it breaks every
|
|
219
|
+
// future `git add` under that path (the failure mode that hit v3.13.0
|
|
220
|
+
// consumers during push). Detect any gitignore rule that catches the
|
|
221
|
+
// path so the planner can heal it before any flow needs to stage files.
|
|
222
|
+
state.frameworkIgnored = { ignored: false };
|
|
223
|
+
try { state.frameworkIgnored = await git.checkFrameworkIgnored(); } catch (_) {}
|
|
224
|
+
|
|
201
225
|
// ---- LSP layer (since v3.10.0) -------------------------------------
|
|
202
226
|
state.lspEnabled = false;
|
|
203
227
|
state.lspInstalled = [];
|
|
204
228
|
state.lspBroken = [];
|
|
229
|
+
state.lspMissingClaudePlugins = []; // [{ name, pluginId }] — since v3.14.0
|
|
205
230
|
try {
|
|
206
231
|
// Reuse the `config` already parsed above instead of re-reading the file.
|
|
207
232
|
if (config && !config.__malformed && config.features && config.features.has_lsp_layer === true) {
|
|
@@ -213,6 +238,20 @@ async function detectState(cwd, opts = {}) {
|
|
|
213
238
|
const installer = new LspInstaller(cwd);
|
|
214
239
|
const results = installer.verifyServers(installed);
|
|
215
240
|
state.lspBroken = results.filter(r => !r.ok).map(r => r.name);
|
|
241
|
+
|
|
242
|
+
// Companion Claude plugin check (v3.14.0+). Gated on `claude` being
|
|
243
|
+
// in tools.enabled AND the `claude` CLI being available; otherwise
|
|
244
|
+
// we don't report missing plugins (manual install hint would be
|
|
245
|
+
// misleading without the CLI).
|
|
246
|
+
const toolsEnabled = (config.tools && Array.isArray(config.tools.enabled))
|
|
247
|
+
? config.tools.enabled
|
|
248
|
+
: ['claude'];
|
|
249
|
+
if (toolsEnabled.includes('claude') && installer.isClaudeCliAvailable()) {
|
|
250
|
+
const checks = installer.verifyClaudePlugins(installed);
|
|
251
|
+
state.lspMissingClaudePlugins = checks
|
|
252
|
+
.filter(c => c.installed === false)
|
|
253
|
+
.map(c => ({ name: c.name, pluginId: c.pluginId }));
|
|
254
|
+
}
|
|
216
255
|
}
|
|
217
256
|
}
|
|
218
257
|
} catch (_) { /* never block doctor on LSP probe */ }
|
|
@@ -313,6 +352,64 @@ function planActions(state) {
|
|
|
313
352
|
// Drift is non-blocking — keep checking for other actions after.
|
|
314
353
|
}
|
|
315
354
|
|
|
355
|
+
if (state.stateLedgerDrift) {
|
|
356
|
+
actions.push({
|
|
357
|
+
key: 'reconcile-state-ledger',
|
|
358
|
+
label: `Reconcile state ledger (${state.stateLedgerDrift.ledger} → ${state.stateLedgerDrift.payload})`,
|
|
359
|
+
why: `.framework/VERSION is v${state.stateLedgerDrift.payload} but .baldart/state.json records installed_version=${state.stateLedgerDrift.ledger}. Typical causes: an update flow that exited mid-way (configure declined, stash conflict), a manual commit that excluded .baldart/state.json, or a write failure that the old silent catch in update.js used to hide (fixed in v3.13.0).`,
|
|
360
|
+
autoOk: true, // pure local file write, idempotent, fully reversible
|
|
361
|
+
run: () => {
|
|
362
|
+
const res = State.reconcileInstalledVersion({
|
|
363
|
+
to: state.stateLedgerDrift.payload,
|
|
364
|
+
reason: 'doctor reconcile — payload/ledger drift'
|
|
365
|
+
});
|
|
366
|
+
if (res.changed) {
|
|
367
|
+
UI.success(`Ledger updated: installed_version ${res.from} → ${res.to}`);
|
|
368
|
+
UI.info('History entry "ledger-reconcile" recorded for audit. Commit .baldart/state.json when ready.');
|
|
369
|
+
} else {
|
|
370
|
+
UI.info('No change needed — ledger already matches payload.');
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
// Drift is non-blocking — keep checking for other actions after.
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Heal `.gitignore` BEFORE any other action that may stage files under
|
|
378
|
+
// `.framework/`. Without this, every downstream `git add` (push, auto-commit
|
|
379
|
+
// during update, lsp-install commits, …) would fail with "paths are ignored
|
|
380
|
+
// by one of your .gitignore files". (since v3.14.0)
|
|
381
|
+
if (state.frameworkIgnored && state.frameworkIgnored.ignored) {
|
|
382
|
+
actions.push({
|
|
383
|
+
key: 'unignore-framework',
|
|
384
|
+
label: 'Heal .gitignore so .framework/ is trackable',
|
|
385
|
+
why: `.framework/ is a git subtree managed by BALDART — it MUST be tracked. Currently ignored by ${state.frameworkIgnored.source}:${state.frameworkIgnored.line} (pattern: ${state.frameworkIgnored.pattern}). Any subsequent \`git add\` under that path would fail.`,
|
|
386
|
+
autoOk: true, // pure local file rewrite, idempotent, fully reversible
|
|
387
|
+
run: async () => {
|
|
388
|
+
const git = new GitUtils(state.cwd);
|
|
389
|
+
const heal = await git.ensureFrameworkNotIgnored();
|
|
390
|
+
if (!heal.wasFixed) {
|
|
391
|
+
UI.info('.gitignore already healthy — nothing to do.');
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
UI.success('.gitignore healed:');
|
|
395
|
+
if (heal.removedLines > 0) {
|
|
396
|
+
UI.info(` Removed ${heal.removedLines} stale .framework line(s).`);
|
|
397
|
+
}
|
|
398
|
+
if (heal.appendedNegation) {
|
|
399
|
+
UI.info(' Appended `!.framework` negation block (idempotent).');
|
|
400
|
+
}
|
|
401
|
+
if (heal.stillIgnored) {
|
|
402
|
+
UI.warning(' .framework/ is STILL ignored — likely a parent-dir .gitignore outside this repo.');
|
|
403
|
+
UI.info(` Source: ${heal.recheck.source}:${heal.recheck.line} (${heal.recheck.pattern})`);
|
|
404
|
+
UI.info(' Resolve by removing that rule manually.');
|
|
405
|
+
} else {
|
|
406
|
+
UI.info(' Commit the .gitignore change when you next push.');
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
// Non-blocking — keep looking for other actions to run in the same pass.
|
|
411
|
+
}
|
|
412
|
+
|
|
316
413
|
if (state.editGateRegistered === false) {
|
|
317
414
|
actions.push({
|
|
318
415
|
key: 'register-edit-gate',
|
|
@@ -347,6 +444,40 @@ function planActions(state) {
|
|
|
347
444
|
});
|
|
348
445
|
}
|
|
349
446
|
|
|
447
|
+
// Native Claude LSP plugins (v3.14.0+). The server binary alone is not
|
|
448
|
+
// enough — without the matching `<lang>-lsp@claude-plugins-official` plugin
|
|
449
|
+
// installed, Claude Code never invokes the LSP and agents silently fall back
|
|
450
|
+
// to Grep. This action backfills the plugins for installs predating v3.14.0
|
|
451
|
+
// and recovers from manual plugin uninstalls.
|
|
452
|
+
if (state.lspEnabled && state.lspMissingClaudePlugins && state.lspMissingClaudePlugins.length > 0) {
|
|
453
|
+
const missing = state.lspMissingClaudePlugins;
|
|
454
|
+
actions.push({
|
|
455
|
+
key: 'lsp-install-claude-plugins',
|
|
456
|
+
label: `Install ${missing.length} native Claude LSP plugin(s)`,
|
|
457
|
+
why: `LSP layer is enabled and the language server(s) are present, but the matching Claude Code plugin(s) are not installed: ${missing.map(m => m.pluginId).join(', ')}. Without them Claude never sees the LSP and the layer is effectively dead. Standard since v3.14.0; missing on installs predating that version.`,
|
|
458
|
+
autoOk: true, // user-scope plugin install, idempotent, fully reversible via `claude plugin uninstall`
|
|
459
|
+
run: async () => {
|
|
460
|
+
const installer = new LspInstaller(state.cwd);
|
|
461
|
+
const res = await installer.installClaudePlugins(missing.map(m => m.name), {
|
|
462
|
+
toolsEnabled: ['claude'], // we already checked tools.enabled in detectState
|
|
463
|
+
interactive: false,
|
|
464
|
+
scope: 'user',
|
|
465
|
+
});
|
|
466
|
+
if (res.installed.length) {
|
|
467
|
+
UI.success(`Installed: ${res.installed.map(i => i.pluginId).join(', ')}`);
|
|
468
|
+
}
|
|
469
|
+
for (const m of res.manual || []) {
|
|
470
|
+
UI.warning(`Could not auto-install ${m.label}:`);
|
|
471
|
+
console.log(` ${m.command}`);
|
|
472
|
+
}
|
|
473
|
+
if (res.skipped.length) {
|
|
474
|
+
const real = res.skipped.filter(s => s.reason !== 'plugin already installed');
|
|
475
|
+
if (real.length) UI.warning(`Skipped: ${real.map(s => `${s.name} (${s.reason})`).join(', ')}`);
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
350
481
|
const remoteAhead = state.remote.fetched && state.remote.behind > 0;
|
|
351
482
|
const hasLocalWork = state.workingTreeDirty > 0 || state.commitsAhead > 0;
|
|
352
483
|
|
|
@@ -394,6 +525,17 @@ function renderDiagnostic(state) {
|
|
|
394
525
|
console.log(`Repository: ${state.cwd}`);
|
|
395
526
|
console.log();
|
|
396
527
|
|
|
528
|
+
// CLI version line — always shown (independent of git repo state). When a
|
|
529
|
+
// newer baldart npm package was detected on a previous run, surface it here
|
|
530
|
+
// so the doctor table is the single pane of glass for "what's stale".
|
|
531
|
+
console.log(statusLine(
|
|
532
|
+
'CLI',
|
|
533
|
+
state.cliUpdate
|
|
534
|
+
? `v${state.cliVersion} → v${state.cliUpdate.latest} available (npm i -g baldart@latest)`
|
|
535
|
+
: `v${state.cliVersion}`,
|
|
536
|
+
state.cliUpdate ? 'warn' : 'ok'
|
|
537
|
+
));
|
|
538
|
+
|
|
397
539
|
console.log(statusLine('Git repo', state.isGitRepo ? 'yes' : 'no', state.isGitRepo ? 'ok' : 'err'));
|
|
398
540
|
|
|
399
541
|
if (!state.isGitRepo) return;
|
|
@@ -429,9 +571,11 @@ function renderDiagnostic(state) {
|
|
|
429
571
|
console.log(statusLine(
|
|
430
572
|
'State ledger',
|
|
431
573
|
state.statePresent
|
|
432
|
-
?
|
|
574
|
+
? (state.stateLedgerDrift
|
|
575
|
+
? `drift: ledger=v${state.stateLedgerDrift.ledger}, payload=v${state.stateLedgerDrift.payload}`
|
|
576
|
+
: `installed v${state.stateInstalled} (${fmtDate(state.stateInstallDate)})`)
|
|
433
577
|
: 'not seeded',
|
|
434
|
-
state.statePresent ? 'ok' : 'warn'
|
|
578
|
+
state.stateLedgerDrift ? 'warn' : (state.statePresent ? 'ok' : 'warn')
|
|
435
579
|
));
|
|
436
580
|
|
|
437
581
|
if (state.stateLastUpdate) {
|
|
@@ -478,15 +622,29 @@ function renderDiagnostic(state) {
|
|
|
478
622
|
state.editGateRegistered ? 'ok' : 'warn'
|
|
479
623
|
));
|
|
480
624
|
|
|
625
|
+
if (state.frameworkIgnored && state.frameworkIgnored.ignored) {
|
|
626
|
+
console.log(statusLine(
|
|
627
|
+
'.gitignore',
|
|
628
|
+
`IGNORES .framework/ (${state.frameworkIgnored.source}:${state.frameworkIgnored.line}) — will be auto-healed`,
|
|
629
|
+
'warn'
|
|
630
|
+
));
|
|
631
|
+
}
|
|
632
|
+
|
|
481
633
|
if (state.lspEnabled) {
|
|
482
634
|
const total = state.lspInstalled.length;
|
|
483
635
|
const broken = state.lspBroken.length;
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
636
|
+
const missingPlugins = (state.lspMissingClaudePlugins || []).length;
|
|
637
|
+
let value;
|
|
638
|
+
if (total === 0) {
|
|
639
|
+
value = 'enabled but no servers installed';
|
|
640
|
+
} else if (broken > 0) {
|
|
641
|
+
value = `${broken}/${total} broken (${state.lspBroken.join(', ')})`;
|
|
642
|
+
} else if (missingPlugins > 0) {
|
|
643
|
+
value = `${total} server(s) verified, ${missingPlugins} Claude plugin(s) missing (${state.lspMissingClaudePlugins.map(m => m.pluginId).join(', ')})`;
|
|
644
|
+
} else {
|
|
645
|
+
value = `${total} server(s) verified (${state.lspInstalled.join(', ')})`;
|
|
646
|
+
}
|
|
647
|
+
const severity = total === 0 || broken > 0 || missingPlugins > 0 ? 'warn' : 'ok';
|
|
490
648
|
console.log(statusLine('LSP layer', value, severity));
|
|
491
649
|
} else {
|
|
492
650
|
console.log(statusLine('LSP layer', 'disabled', 'ok'));
|
package/src/commands/push.js
CHANGED
|
Binary file
|