docs(781,799): re-measure the tooling audit from a derived population, and adopt serena (#862)
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 23s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m36s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m11s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m13s
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 23s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m36s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m11s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m13s
§5.3's verdicts rested on a single surface, which manufactured four false zeros: codex is driven through `codex exec` inside Bash, security-guidance and ralph-loop expose no tool at all and run as hooks (1,086 executions each), and feature-dev is used through its agents. The audit also compared current enablement against historical usage — six of the eight plugins it called "genuinely unused" were disabled for 16 of the 30 corpus days. The retirement half of #781 is answered *no* on evidence: the zeros split six ways and only one is grounds for removal. Eight plugins are kept by operator decision. #799's observation was correct and its cause is now established. serena was `false` in settings.json until 2026-08-14T12:31Z, when a concurrent session enabled it; its tools appear in no transcript before 12:42:54Z. #799's session started at 12:01Z and never reloaded, so its probe correctly found nothing while the settings file already said `true`. serena is adopted and documented as the third code-intelligence surface. Four review rounds, two independent cold reviewers (one cross-family); rounds 1-3 BLOCKED. fixes #781 fixes #799 Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
This commit was merged in pull request #862.
This commit is contained in:
+11
@@ -95,3 +95,14 @@ web/playwright-report/
|
||||
# plaintext Gitea credential and absolute /Users paths, so it is neither portable nor safe to
|
||||
# commit. See ersatztv#711 for the related merge-gate gap.
|
||||
.codex/
|
||||
|
||||
# serena's per-project state, written by `activate_project` (ersatztv#799): project.yml,
|
||||
# project.local.yml, a language-server cache, and memories/.
|
||||
#
|
||||
# This deliberately rejects serena's own versioning model. Its nested .serena/.gitignore excludes
|
||||
# only `cache` and `project.local.yml`, and project.local.yml says project.yml "is intended to be
|
||||
# versioned" — but activation here is per DIRECTORY, and every worktree generates a project.yml
|
||||
# whose project_name is that worktree's folder (e.g. `781-tooling`). A committed copy would name
|
||||
# the wrong project in every checkout but the one that produced it. memories/ is ignored with it:
|
||||
# it is serena's own written notes, and this repo's durable knowledge lives in docs/ instead.
|
||||
.serena/
|
||||
|
||||
+6
-6
@@ -34,7 +34,7 @@ doc below, or that changes which sections a task signal points to.**
|
||||
| Writing or editing any doc, or answering a review finding in prose | `docs/decisions/records/docs/no-session-narrative.md` — the doc records the END STATE; the path to it goes in the commit message. Apply the who-benefits test, and read the carve-out before you cut (dated measurements, stated snapshot boundaries and tested-and-rejected results stay) |
|
||||
| Adding / changing / deleting a guard file | `docs/guard-inventory.md` — every guard's row is machine-checked by `scripts/tests/test_guard_inventory.py`, so a new guard must acquire a row before the suite goes green, and a row graded `MUTATION` must also acquire a declared clause in `scripts/tests/mutation_manifest.py` |
|
||||
| Writing code that reads live Gitea/remote state and then acts on it | `docs/decisions/records/process/check-and-use-pins-a-version.md`, then `docs/remote-state-inventory.md` — a new executable under `scripts/` (**excluding `scripts/tests/`**), `.claude/hooks/`, `.husky/` or `.gitea/workflows/` must acquire a row there before `scripts/tests/test_remote_state_inventory.py` goes green |
|
||||
| Finding every site that references a symbol (multi-site fix/sweep) | `docs/local-lsp-tooling.md` — which surface answers, and why a delegated agent must be pointed at the `csharp-lsp` MCP tools rather than the `LSP` tool |
|
||||
| Finding every site that references a symbol (multi-site fix/sweep) | `docs/local-lsp-tooling.md` — which of the three surfaces answers, and why a delegated agent must be pointed at an MCP server (`csharp-lsp`, or `serena` after an `activate_project`) rather than the `LSP` tool, which no subagent has been observed to reach |
|
||||
| Live local run / Playwright-MCP verification | `docs/e2e-local.md` + `scripts/e2e-local.sh` |
|
||||
| Adding/changing a UI-E2E browser flow | `docs/e2e-local.md` → "UI-E2E harness" + `scripts/e2e-ui.sh` |
|
||||
| What does a test suite cover | `docs/testing.md` |
|
||||
@@ -77,11 +77,11 @@ bounds, what's mined per issue): `docs/handoffs/chicorytv-issue-queue.md` → "K
|
||||
- **`docs/spa-conventions.md`** — playbook for adding a screen to the ChicoryTV React SPA.
|
||||
- **`docs/e2e-local.md`** (+ `scripts/e2e-local.sh`) — how to run a live local instance for manual
|
||||
or Playwright-MCP verification.
|
||||
- **`docs/local-lsp-tooling.md`** — the code-intelligence surfaces (the `LSP` tool's three servers and
|
||||
the `csharp-lsp` MCP server): how each is configured, which one a **subagent** can actually reach,
|
||||
the traps (a cold server answers the first query with a confidently partial result), and
|
||||
`scripts/check-local-lsp.sh` to verify the preconditions. Read before briefing an agent to find
|
||||
every site referencing a symbol.
|
||||
- **`docs/local-lsp-tooling.md`** — the code-intelligence surfaces (the `LSP` tool's three servers,
|
||||
the `csharp-lsp` MCP server, and `serena`): how each is configured, which ones a **subagent** can
|
||||
actually reach, the traps (a cold server answers the first query with a confidently partial result;
|
||||
serena needs an `activate_project` per directory), and `scripts/check-local-lsp.sh` to verify the
|
||||
preconditions. Read before briefing an agent to find every site referencing a symbol.
|
||||
- **`docs/testing.md`** — testing map: what each `*.Tests` project / `web` suite covers,
|
||||
golden-file nets, the timezone-independence rule, how to run subsets, the per-PR verification
|
||||
gate.
|
||||
|
||||
+106
-51
@@ -485,56 +485,110 @@ list and the measured exclude matrix: `ci.python-lint-ruff-config-committed`.
|
||||
|
||||
### 5.3 Configured vs actually invoked
|
||||
|
||||
Measured over the session transcript corpus (811 files under
|
||||
`~/.claude/projects/-Users-timothy-ersatztv/`, this session excluded).
|
||||
**Measured 2026-08-28 (#781/#799).** Population derived from config, not listed by hand:
|
||||
`~/.claude/settings.json` `enabledPlugins`, `~/.claude.json` `mcpServers` (plus its per-project
|
||||
`projects[].mcpServers`), the project `.mcp.json`, and `.claude/settings.local.json`. Corpus: **486
|
||||
transcripts** under `~/.claude/projects/` via `rglob` (a top-level glob sees 209 and manufactures
|
||||
false zeros), this session excluded. Positive control on the same query shape: 22,927 `Bash`, 1,420
|
||||
`Read`, 1,116 `Edit`.
|
||||
|
||||
| Tool | Configured | Actually invoked | Verdict |
|
||||
**A count is only meaningful against the surface a thing is actually used through**, so each row
|
||||
below names its surface. Do not reason from a surface *count* — enumerate the event shapes and say
|
||||
which you measured, because the whole failure here is an audit that checked one and reported zeros
|
||||
for the rest. Measured: MCP tool calls (`mcp__<server>__*`), `Skill` invocations, `Agent`
|
||||
`subagent_type`, user-typed slash commands (`<command-name>`), `LSP` tool calls, hooks (which appear
|
||||
as `hookInfos` inside a `stop_hook_summary` record and *never* as a tool call), and CLIs invoked
|
||||
inside `Bash`. Measured at zero everywhere: MCP **resource** tools (`ListMcpResourcesTool`,
|
||||
`ReadMcpResourceTool`). **Not** separately identified: MCP prompts, and the `attributionSkill`
|
||||
records that also appear in transcripts — so a zero below is a zero on the shapes listed, not on
|
||||
every shape that exists.
|
||||
|
||||
| Entry | Scope / surface | Measured | Verdict |
|
||||
|---|---|---|---|
|
||||
| `gitea` MCP | project `.mcp.json` **and** user scope (duplicate, identical values) | Heavy, same-day | Keep; de-duplicate the config |
|
||||
| `ssh-mcp` | project `.mcp.json` | 16 transcripts, same-day | Keep |
|
||||
| `mempalace` MCP | user scope | 10 transcripts, **last hit 2026-07-25** (19 days) | Keep, but see below |
|
||||
| **`LSP` (all three servers)** | 3 plugins enabled + `csharp-lsp` in `enabledMcpjsonServers` | **0 calls in 811 transcripts** | Broken *and* unused — both C# and TS servers repaired 2026-08-14 (#777); the zero is the baseline a future measurement is compared against |
|
||||
| `context7` | enabled, `CONTEXT7_API_KEY` set | 0 calls | Dead |
|
||||
| `playwright` | enabled | 145 calls, last 2026-07-25 | Keep |
|
||||
| `superpowers` | enabled | 29 transcripts, last 2026-08-04 | Keep |
|
||||
| `feature-dev`, `ralph-loop`, `security-guidance` | enabled | 0 skill invocations | Dead as configured |
|
||||
| `codex` plugin | enabled | 0 *skill* invocations — but the documented workflow is `codex exec` via Bash, which this search cannot see | Not dead; measured the wrong surface |
|
||||
| `nuget`, `docker-mcp` | in `.mcp.json`, explicitly **disabled** | 0 (expected) | Correctly off |
|
||||
| `gitea` MCP | user `~/.claude.json` | **975 calls**, 105 transcripts, last 2026-08-28 | Keep — heaviest tool in the corpus |
|
||||
| `codex` plugin | `codex exec` inside `Bash`, plus `SessionStart`/`SessionEnd`/`Stop` hooks | **109–126 `Bash` inputs invoke it at command position** across ~21–26 transcripts, last 2026-08-28 — 109 under a quote- and heredoc-aware parse, rising toward 126 if wrappers such as `nohup` are admitted. The range is stated rather than resolved: three successive parsers gave three answers, which is the point at which a hand-written command-position grammar stops being worth another round. **258** inputs merely *contain* the string — `pgrep -f "codex exec"` polling loops and heredoc bodies mention it — so a substring count overstates it roughly twofold | Keep — heavily used, and invisible to a skill-name search |
|
||||
| `playwright` | plugin MCP | 64 calls, last 2026-08-27 | Keep |
|
||||
| `security-guidance` | **hooks** (`SessionStart`, `UserPromptSubmit`, `PostToolUse`, `Stop`) | **1,086 execution records** in `hookInfos` naming `security_reminder_hook`, across 78 transcripts / 23 UTC days, last 2026-08-28 | Keep — **not** dead; it exposes no tool at all |
|
||||
| `ssh-mcp` | project `.mcp.json` | 16 calls, 9 transcripts, last 2026-08-22 | Keep |
|
||||
| `superpowers` | `Skill` | 13 invocations (`systematic-debugging` 6, `brainstorming` 5, …), last 2026-08-26 | Keep |
|
||||
| `mempalace` | user `~/.claude.json` | 11 calls, 7 transcripts, last 2026-08-27 | Keep — in active use |
|
||||
| `feature-dev` | **`Agent` subagent_type** | `feature-dev:code-reviewer` ×2, last 2026-08-21 | Keep — **not** dead; used via agents, not skills |
|
||||
| `serena` | plugin MCP | **Disabled until 2026-08-14T12:31Z**; 0 invocations in the 14 days since it was enabled; **demonstrated working 2026-08-28** | Keep — see #799 and `docs/local-lsp-tooling.md` |
|
||||
| `LSP` tool (`csharp-lsp`, `pyright-lsp`, `typescript-lsp` plugins) | `LSP` | 13 calls, last 2026-08-14 — all §5.1/#777 probes | Repaired but unadopted; no post-repair use |
|
||||
| `csharp-lsp` MCP server | project `.mcp.json` | **0** since it became startable on 2026-08-14 | Reachable, unadopted |
|
||||
| `ralph-loop` | **hooks** (`Stop`) + slash command | Its `Stop` hook ran **1,086 times** across 78 transcripts / 23 days — the same triple as `security-guidance` at this snapshot, consistent with both registering a `Stop` hook in their `hooks.json`. The `/ralph-loop` command itself: **0** | Keep — the hook executes and exits when no loop state exists; the *feature* is unused, the plugin is not inert |
|
||||
| `context7` | plugin MCP | **0**, and it was enabled across the whole retained window | No observed use in the window. **Deliberately kept** — operator decision 2026-08-28 |
|
||||
| `code-review`, `code-simplifier`, `commit-commands`, `claude-md-management`, `skill-creator`, `frontend-design` | `Skill` / `Agent` / slash command | **0** — but all six were **disabled** until 2026-08-14T12:41Z, so the zero covers only the 14 days since | Not yet exercised rather than rejected. **Deliberately kept** — operator decision 2026-08-28 |
|
||||
| `nuget` | project `.mcp.json`, explicitly disabled | 0 (expected); `command` names `/usr/local/share/dotnet/dotnet`, **verified absent** | Entry removed 2026-08-28 |
|
||||
| `docker-mcp` | project `.mcp.json`, explicitly disabled | 0 (expected) | Correctly off |
|
||||
| `homeassistant` | `~/.claude.json` `projects[/Users/timothy/server-management]` | 129 calls | Not in this repo's population — scoped to another project |
|
||||
|
||||
The zero for `LSP` is verified rather than assumed: the same query shape returns 23,661 `Bash` calls
|
||||
and 5,102 `Read` calls over the same corpus, so the search was demonstrably capable of finding hits.
|
||||
**Re-derive it with that positive control**, not on its own — `rg -c` over multiple files prints
|
||||
`path:count`, so the obvious way to total it sums the paths and returns zero for everything.
|
||||
`claude-in-chrome` also appears in the corpus (154 calls) and is **not** configured anywhere in these
|
||||
files: it is harness-provided. An audit deriving its population from config alone will not see it.
|
||||
|
||||
**Re-measured 2026-08-14 (#781), and the table above needs two corrections plus seven additions.**
|
||||
The population here was assembled **by hand**; deriving it from the config files instead
|
||||
(`~/.claude/settings.json` `enabledPlugins`, `~/.claude.json` `mcpServers`, the project `.mcp.json`,
|
||||
`.claude/settings.local.json`) surfaces **seven enabled plugins the table omits** — `serena`,
|
||||
`code-review`, `code-simplifier`, `commit-commands`, `claude-md-management`, `skill-creator`,
|
||||
`frontend-design`. An audit whose completeness rests on remembering every entry is exactly the
|
||||
failure `testing.guard-derives-population-from-source` exists to prevent, applied to an audit.
|
||||
**Why "retire what is enabled and never invoked" was not carried out.** #781 assumed a zero means
|
||||
useless. Across three re-measurements the zeros here split **six** ways, and none of them is grounds
|
||||
for removal until you know which one you are looking at:
|
||||
|
||||
The corpus is also larger than counted: **964** transcripts via `rglob`, not 811 — a top-level-only
|
||||
glob sees 209 here and manufactures false zeros. Positive control on the same query: 24,762 `Bash`,
|
||||
5,275 `Read`, 4,037 `Edit`.
|
||||
1. **Broken** — `csharp-ls` and the `csharp-lsp` MCP server named a dotnet root this machine does not
|
||||
have (#777). Unreachable, not unwanted.
|
||||
2. **Measured on the wrong surface** — `codex` (used through `Bash`), `security-guidance` and
|
||||
`ralph-loop` (hooks, which never appear as a tool call), `feature-dev` (agents, not skills). Four
|
||||
separate instances of one mistake, and each looked like a confident zero.
|
||||
3. **Not enabled** — `serena` was `false` until 2026-08-14T12:31Z, and six of the plugins above until
|
||||
12:41Z the same day. *Installed* is not *enabled*: `installedAt` says March for all of them, and
|
||||
only `enabledPlugins` says whether a thing could have been used at all.
|
||||
4. **Enabled but never called** — `serena` since 2026-08-14, and the `csharp-lsp` MCP server since it
|
||||
became startable. This is the only shape that resembles the issue's premise.
|
||||
5. **Evidence aged out** — the corpus is a rolling window: 964 transcripts on 2026-08-14, **486** on
|
||||
2026-08-28, reaching back only to 2026-07-29. A zero can mean the calls expired, and an older
|
||||
measurement's counts are not reproducible from a later corpus.
|
||||
6. **No observed use in the window** — `context7`. Note the wording: the corpus starts 2026-07-29 and
|
||||
these plugins date to March, so the evidence cannot distinguish "never used" from "not used
|
||||
lately", and it cannot support "excess" as a historical claim.
|
||||
|
||||
| Row above | Re-measured |
|
||||
|---|---|
|
||||
| `mempalace` "last hit 2026-07-25 (19 days)" | **31 calls, last seen 2026-08-14.** In active use; the gap was a snapshot artifact, not disuse. |
|
||||
| `codex` "0 skill invocations" | **113 `codex exec` calls across 17 sessions** — the third-heaviest tool in the corpus. The caveat was right; this is the number. |
|
||||
| `LSP` "0 calls" | Not zero. The handful of calls are from sessions *testing whether the LSP works* — including §5.1's own probes. |
|
||||
| `gitea` "duplicate, identical values" | Duplicated but **not identical** — the user-scope entry carries `"type": "stdio"`. **Resolved:** the *project* copy was removed, not the user one, because `server-management` and `homelab-docs` have no `.mcp.json` and depend on user scope. |
|
||||
| *(absent)* `serena` | Enabled; starts and initializes standalone (Serena 1.28.1) but its tools never appear in a session and `ToolSearch` cannot resolve them. Cause unproven → **#799**. |
|
||||
| *(absent)* six further plugins | Three (`claude-md-management`, `skill-creator`, `frontend-design`) were enabled **on 2026-08-14**, so their zeros mean "not yet tried". The rest are reachable and simply unused. |
|
||||
Establishing *which* of the six applies is the precondition for acting, and doing so cost three
|
||||
sessions here. What was demonstrably wrong with this configuration was **duplication** and
|
||||
**breakage**; excess was never shown.
|
||||
|
||||
**The issue's framing does not survive the measurement, and that is the finding.** "Retire what is
|
||||
enabled and never invoked" treats a zero as evidence of uselessness. These zeros split **four** ways:
|
||||
**broken** (`csharp-lsp` MCP and two LSP servers — #777), **unreachable** (`serena`), **just enabled**
|
||||
(three plugins), and **measured on the wrong surface** (`codex`). Establishing *why* a counter is zero
|
||||
is a precondition for acting on it — and on this evidence almost nothing should be removed. What was
|
||||
actually wrong was **duplication** and **breakage**, not excess. The one entry still reading as
|
||||
coverage it does not provide is `nuget`, which names the same dead `/usr/local/share/dotnet` path
|
||||
#777 fixed elsewhere; it is inert only because it is explicitly disabled.
|
||||
**`serena` was disabled, and #799's observation was correct (#799).** The plugin was `false` in
|
||||
`~/.claude/settings.json` until **2026-08-14T12:31Z**, when a *concurrent* session enabled it —
|
||||
`~/.claude/settings.json.bak-doctor`, written moments earlier, still records `serena: false`. Its
|
||||
tools appear in **no transcript before 2026-08-14T12:42:54Z**, then in 6 transcripts that day and
|
||||
186 across the 10 days since, always in a harness-emitted availability listing rather than prose
|
||||
(counts as of the 2026-08-28 snapshot; the corpus is live and grows). The #799 session had started
|
||||
at 12:01Z **and never reloaded**, so its tool set never contained them; its `ToolSearch` probe at
|
||||
13:41Z correctly reported them absent, and that probe *did* carry a positive control — two other
|
||||
`ToolSearch` queries resolved in the same session minutes before.
|
||||
|
||||
**Enabling a plugin did not reach a session that was already running; a reload did.** Both sessions
|
||||
started *before* the 12:31Z toggle, so start time alone separates nothing — what separates them is
|
||||
the reload. The enabling session ran `/reload-plugins` at **12:54:32.694Z**, printed
|
||||
`Reloaded: 17 plugins`, and its next tool delta added serena **19 seconds later at 12:54:51.402Z**.
|
||||
The #799 session ran for 3h54m, emitted exactly one tool delta (12:01:27Z, no serena), and never
|
||||
reloaded. Its author read `serena: true` from the settings file and `serena: absent` from their own
|
||||
tool set, and both were true simultaneously.
|
||||
|
||||
Two sessions are not a proof of the harness's contract, so treat this as the operating rule rather
|
||||
than a mechanism: **do not assume a plugin a sibling session enabled is available to yours — reload
|
||||
or restart, then confirm the tool resolves.**
|
||||
|
||||
Two things follow for any future audit. **Read enablement as of the evidence, not as of now** — an
|
||||
audit that compares *current* config against *historical* usage will call a plugin unused for a
|
||||
window in which it was switched off. And **date transcript evidence by record timestamp, never by
|
||||
file mtime**: mtime is when a session last wrote, which can be hours or days after the event being
|
||||
proved, and here it is what made a first-appearance look like a pre-existing state.
|
||||
|
||||
The `activate_project` requirement is a separate, real trap, reachable only once the tools are
|
||||
present: without it the first query returns a "no active project" error naming the projects it does
|
||||
know. Both of #799's phrasings resolve today, and the demonstrated query is in
|
||||
`docs/local-lsp-tooling.md`.
|
||||
|
||||
`.mcp.json` is **gitignored**, so none of its content is reviewable from this repo and the
|
||||
de-duplication recorded below happened on one machine only. Two scopes defined `gitea` (the user
|
||||
entry additionally carrying `"type": "stdio"`); the **project** copy was removed rather than the user
|
||||
one, because `server-management` and `homelab-docs` have no `.mcp.json` and depend on user scope.
|
||||
Verified afterwards by starting the surviving server: 54 tools.
|
||||
|
||||
**Hooks are the good news.** All 13 scripts in `.claude/hooks/` are wired from either
|
||||
`.claude/settings.json` or `.husky/*`, and no settings entry points at a missing path — there are
|
||||
@@ -668,14 +722,15 @@ This issue is analysis; it spawns implementation rather than doing it.
|
||||
|
||||
| Issue | Detector / finding | Covers | Priority |
|
||||
|---|---|---|---|
|
||||
| #774 | **A** — a guard derives its population from the authoritative source and asserts set equality | Family A, 27 records (39%) | high |
|
||||
| #775 | **B** — every guard ships a mutation proof: delete that guard alone, see red | Family B, 9 records (13%) | high |
|
||||
| #776 | Make hooks report that they fired — PreToolUse/PostToolUse execution is unobservable | the whole hook layer | high — **DONE**, §5.4 is measured |
|
||||
| #777 | `csharp-lsp` and `typescript-lsp` are broken; the "workflow agents must use csharp-lsp" note is stale | partial mitigation for Family A | medium |
|
||||
| #778 | **D** — pin a version or use compare-and-set for read-then-write against live remote state | Family D, 5 records | medium |
|
||||
| #779 | **F + G** — test the deny path with the production config value; assert full-replace field lists | fail-open + destructive-replace, ~8 records | medium |
|
||||
| #780 | Reconcile Python tooling — commit a ruff config and enforce it, or stop claiming we do | environment divergence in our own toolchain | medium |
|
||||
| #781 | Retire enabled-but-never-invoked plugins/MCP servers; de-duplicate the `gitea` server | config that reads as coverage | low |
|
||||
| #774 | **A** — a guard derives its population from the authoritative source and asserts set equality | Family A, 27 records (39%) | high — **CLOSED** |
|
||||
| #775 | **B** — every guard ships a mutation proof: delete that guard alone, see red | Family B, 9 records (13%) | high — **CLOSED** |
|
||||
| #776 | Make hooks report that they fired — PreToolUse/PostToolUse execution is unobservable | the whole hook layer | high — **CLOSED**, §5.4 is measured |
|
||||
| #777 | `csharp-lsp` and `typescript-lsp` are broken; the "workflow agents must use csharp-lsp" note is stale | partial mitigation for Family A | medium — **CLOSED**, §5.1 |
|
||||
| #778 | **D** — pin a version or use compare-and-set for read-then-write against live remote state | Family D, 5 records | medium — **CLOSED** |
|
||||
| #779 | **F + G** — test the deny path with the production config value; assert full-replace field lists | fail-open + destructive-replace, ~8 records | medium — **CLOSED** |
|
||||
| #780 | Reconcile Python tooling — commit a ruff config and enforce it, or stop claiming we do | environment divergence in our own toolchain | medium — **CLOSED**, §5.2 |
|
||||
| #781 | Retire enabled-but-never-invoked plugins/MCP servers; de-duplicate the `gitea` server | config that reads as coverage | low — **DONE**, §5.3 is re-measured from a derived population; the retirement half was answered *no* on evidence |
|
||||
| #799 | `serena` is enabled but never invoked — establish why, then use it or disable it | one row of §5.3 | low — **DONE**, it had been *disabled* until 2026-08-14 and is now adopted (`docs/local-lsp-tooling.md`) |
|
||||
|
||||
**Detector C (dedup by construction) has no issue of its own on purpose.** It is not a thing to build;
|
||||
it is the shape the fixes in #774 and #778 should take when they find two copies of one rule — with
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Local code-intelligence tooling (LSP + the csharp-lsp MCP server)
|
||||
# Local code-intelligence tooling (LSP, the csharp-lsp MCP server, serena)
|
||||
|
||||
What is available for "find every site that references this symbol", how each surface is configured,
|
||||
and how to verify it rather than assume it. Run `scripts/check-local-lsp.sh` to check all of it at
|
||||
@@ -11,7 +11,7 @@ not cover the residue where the population is *sites in code* — #403 (5 of 6 d
|
||||
(a by-id handler covering 4 of 10 media types). Find-all-references is the tool for that residue, and
|
||||
it is overwhelmingly a C# problem here.
|
||||
|
||||
## The two surfaces, and which one a subagent can reach
|
||||
## The three surfaces, and which one a subagent can reach
|
||||
|
||||
This distinction is the whole reason both halves are documented together.
|
||||
|
||||
@@ -19,6 +19,7 @@ This distinction is the whole reason both halves are documented together.
|
||||
| --- | --- | --- |
|
||||
| The **`LSP` tool** (Claude Code plugins) | `csharp-ls`, `typescript-language-server`, `pyright-langserver` | **No dispatched subagent has been observed to reach it.** `ToolSearch` returns "No matching deferred tools found" for `select:LSP` in a subagent while the same query resolves in the main session — Claude Code 2.1.232, agent types `general-purpose` and `Explore`, 2026-08-14, plus the independent observation in `docs/defect-shapes-773.md` §5.1. Three observations on one harness version: treat it as measured behaviour to design around, not an architectural guarantee. |
|
||||
| The **`csharp-lsp` MCP server** (`.mcp.json`) | wraps `csharp-ls`; serves `csharp_references`, `csharp_diagnostics`, `csharp_hover`, `csharp_definition`, `csharp_symbols`, `csharp_completions`, `csharp_set_workspace`, … (16 tools) | **Main session and subagents.** Subagents demonstrably call MCP tools here: 326 MCP calls from inside subagent turns across the transcript corpus, spanning four servers (`gitea` 288, `playwright` 26, `ssh-mcp` 6, `mempalace` 6). Not yet demonstrated for `csharp-lsp` specifically — that server only became startable on 2026-08-14 — so this is the general MCP boundary, evidenced, rather than a per-server measurement. |
|
||||
| The **`serena` MCP server** (plugin `serena@claude-plugins-official`) | its own bundled Roslyn C# server; its generated config lists 73 selectable language-server ids, one or more chosen per project (first match wins per file) | **Main session** demonstrated. Subagent use is *expected* (it is MCP, so the boundary above applies) but **not yet demonstrated** — every serena call on record is from a main session. Requires an explicit `activate_project` before the first query — see below. |
|
||||
|
||||
So an instruction telling *delegated agents* to use C# code intelligence must point them at the
|
||||
**MCP tools**, never at the `LSP` tool. Briefing harder does not help: an agent that cannot resolve
|
||||
@@ -153,6 +154,72 @@ The alternative is to install the .NET 8 SDK and build upstream unmodified. Eith
|
||||
not fine is leaving it undocumented, because `scripts/check-local-lsp.sh` now *starts* the server, so
|
||||
a wrong pin or a missing patch surfaces as a failed smoke test rather than a false pass.
|
||||
|
||||
### The `serena` MCP server
|
||||
|
||||
`serena` is an enabled plugin (`serena@claude-plugins-official`) declaring one MCP server:
|
||||
|
||||
```json
|
||||
"serena": {
|
||||
"command": "uvx",
|
||||
"args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server"]
|
||||
}
|
||||
```
|
||||
|
||||
Nothing about it needs configuring in this repo. It answers the same find-all-references question as
|
||||
`csharp-lsp`, and being MCP it is *expected* to be reachable from dispatched subagents — the gap the
|
||||
`LSP` tool leaves — though that has not yet been demonstrated for serena specifically.
|
||||
|
||||
**Once it is available, it still requires an explicit project activation.** This was *not* the cause
|
||||
of #799 — that session never had serena's tools at all (see `docs/defect-shapes-773.md` §5.3) — it is
|
||||
a second prerequisite you meet only after the tools resolve. A fresh server has no active project,
|
||||
and the first real call fails with a message naming the projects it does know:
|
||||
|
||||
```
|
||||
No active project. Ask the user to provide the project path or to select a project
|
||||
from this list of known projects: ['adversarial-reviewer', 'server-management', ...]
|
||||
```
|
||||
|
||||
That error is the tell, and it is the system reporting this prerequisite rather than the caller
|
||||
inferring it from an empty result. Activate by absolute path, once per directory:
|
||||
|
||||
```
|
||||
mcp__plugin_serena_serena__activate_project project=/Users/timothy/ersatztv
|
||||
```
|
||||
|
||||
Demonstrated on 2026-08-28 against a worktree of this repo (`/Users/timothy/ersatztv-wt/781-tooling`
|
||||
— the path above is the canonical checkout, which is the one to activate for ordinary work) —
|
||||
`find_symbol` on `ChannelPlaylist`
|
||||
(`ErsatzTV.Core/Iptv/ChannelPlaylist.cs`) returns the class and its constructor, and
|
||||
`find_referencing_symbols` on `ErsatzTV.Core.Iptv/ChannelPlaylist` returns references across **7
|
||||
files** spanning `ErsatzTV.Core.Tests`, `ErsatzTV/Controllers`, `ErsatzTV/Formatters` and
|
||||
`ErsatzTV.Application` — the cross-project spread that makes it useful for the sites-in-code
|
||||
residue.
|
||||
|
||||
Traps specific to it:
|
||||
|
||||
- **Activation is per directory, not per repository, and it is a registration — not a session
|
||||
setting.** A git worktree is a different path, so it registers as a *separate* serena project
|
||||
named after the worktree directory. Activate each directory you work in the first time; a running
|
||||
server also has one *active* project at a time, so switching directories means activating again
|
||||
even if both are already registered.
|
||||
- **It writes `.serena/` into the project root** — `project.yml`, `project.local.yml` and a language
|
||||
server cache. Gitignored here since #799; activating in a checkout that predates that entry leaves
|
||||
an untracked directory.
|
||||
- **It starts its own Roslyn server**, `Microsoft.CodeAnalysis.LanguageServer.dll` under
|
||||
`~/.serena/language_servers/`, independent of `csharp-ls`. That is a *second* C# language server
|
||||
competing for the same cores as a `dotnet build` or a `csharp-lsp` solution load. Its warm-up cost
|
||||
has **not** been characterised: the one cold run on record (2026-08-28) activated in 1.7 s and
|
||||
answered `find_symbol` in 6.8 s and `find_referencing_symbols` in 13.1 s, which is far short of
|
||||
`csharp-ls`'s multi-minute solution load. One observation is not a warm-up profile — treat the
|
||||
cold-query trap below as applying until someone measures it.
|
||||
- **A truncated answer degrades into a summary, and says so only in its first line.**
|
||||
`find_referencing_symbols` with `max_answer_chars: 4000` on `ChannelPlaylist` returned *"The answer
|
||||
is too long (5758 characters)"* followed by a "References without surrounding lines" list. Re-run
|
||||
at `max_answer_chars: 60000`, the full answer names the **same 7 files and the same 17 reference
|
||||
entries across 16 distinct symbols** (`GetChannelPlaylistHandler/Handle` is referenced twice) — so
|
||||
on this query the degraded form lost only the `content_around_reference` snippets, not coverage. One comparison is not a guarantee for every query: raise the limit and diff the two
|
||||
before treating a truncated result as exhaustive.
|
||||
|
||||
## Traps
|
||||
|
||||
- **A query issued before the project graph is loaded is answered anyway, and answered wrongly.**
|
||||
|
||||
Reference in New Issue
Block a user