Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_echo_the_client_name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"crates/devup-mcp/Cargo.toml": "Patch"
},
"note": "Half the runtime table is matched against client names observed in the wild rather than read out of source, because those clients are closed. A wrong guess there does not fail: the match simply never fires, the runtime reads `unknown`, every convention is written, and the result is safe and completely silent - indistinguishable from a client devup-mcp has genuinely never heard of. The report now echoes the name verbatim as `runtime.clientName` beside the verdict drawn from it, and when nothing matched it says what `unknown` means, that it is safe rather than wrong, and that a client with its own skill directory whose name is not in the table is exactly what should be reported. That is the only path by which the table gets corrected, and it is the same class of silent failure the rest of this branch exists to remove. `Lookup::new` takes the client name and derives the runtime from it rather than being handed a verdict, so the name and the conclusion cannot disagree.",
"date": "2026-09-22T15:31:59.219473100Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"crates/devup-mcp/Cargo.toml": "Minor"
},
"note": "`installed` meant `is_file()` and nothing more, so a `SKILL.md` written by a devup-mcp from six months ago was indistinguishable from the current document, and so was a one-line placeholder someone dropped in that directory. The practical sting was on the other side of the same check: `install` skipped anything already present, which meant there was no way to update a skill at all - the first install a machine ever did was the last one it would get, and every later call reported that stale copy as installed. Install state is now decided by comparing the bytes on disk against exactly what this build would write, and reported as `installState.revision`: `current` (identical, or fetched from upstream, which is at least as new as the vendored copy and cannot be compared further without the network), `older` (carries devup-mcp provenance but is not this build's), `incomplete` (the entry document is this build's but a file it links to is missing or differs - the shape that looks installed and whose links go nowhere), or `foreign` (no devup-mcp provenance note at all). An install now rewrites `older` and `incomplete` in place, at the roots where the stale copies actually are rather than where a fresh install would have chosen, and never touches `foreign`, because replacing a document this server did not write would destroy someone's work and an install is not the moment to decide that was a mistake; it is reported under `leftAlone` instead. `report` counts `outdated` apart from `missing` because the two need different words from the caller - one is a skill the agent has never seen, the other is one it is reading right now and being taught rules this binary no longer emits. The runtime table grew from three clients to twelve, with Cursor, Zed, Windsurf, VS Code/Copilot, Gemini CLI, Cline, Continue, Amp and Goose added from their own source or official documentation. Cline is the entry that justifies the table: it reads `.agents/skills` only from the home directory, so the shared project-local convention is one directory it never opens, and the previous fallback would have written exactly there. JetBrains AI Assistant is deliberately absent - it has no auto-discovered directory and a human must register one, so guessing a path would be worse than the fallback. An unidentified client now reads every convention devup-mcp knows while still writing only the three broad ones: reading narrowly was making a Cursor user who had installed once into `~/.cursor/skills` be told the skill was missing, with a second copy as the remedy. The gap moved into `devup_project_context` as well, scoped to what each scope actually found, because `devup.json` is devup-ui's theme format, the `openapi.json` under scope api is generated by vespera routes and the `models/*.json` under scope db are vespertide schemas, and an agent about to edit one of them had no way to learn it needed those rules. Three guide rules moved into `devup_figma_export`'s own description - screenshots verify rather than author, do not hand-interpret the node tree, never guess a UI value - because those three produce wrong code rather than a worse response when violated, and they lived only in a resource that Codex collapses into a single namespace description. `report` also hands over the machine-wide skill directory for this runtime under `machineWide`, as a path to run yourself: the home directory stays outside the allowed write root, and widening that so a design-to-code server can write to `$HOME` is not a trade worth making for convenience. Finally `autoUpdate.installedPathNote` explains that a version-named install directory belongs to the host and is fixed at install time, so a directory reading 0.9.0-dev around a binary reporting 0.10.1 is self-update working rather than a mismatch.",
"date": "2026-09-22T14:59:24.071914600Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"crates/devup-mcp/Cargo.toml": "Minor"
},
"note": "A skill installed where the connected runtime never looks reports success, sits on disk, and still never loads, and that silent failure is what the reports from Codex were. Codex reads project skills from `.agents/skills` and does not look at `.claude/skills`, but a fresh project has no skill root at all, so the choice fell through to the first known convention - which is `.claude/skills` - and the agent was told devup-ui was installed while it went on writing devup-ui from guesses. opencode never reported it because opencode scans `.claude/skills` for compatibility and so happened to work. The runtime is now read from the MCP `clientInfo` name sent at initialize and decides the directory; a client that does not name itself has every convention written, because with no name and no existing root there is nothing to choose on and picking one of three blind is a two-in-three chance of writing where nothing looks, against a cost of a few kilobytes inside a directory the project already owns. Install state now also reads the machine-wide roots - `~/.claude/skills`, `~/.codex/skills`, `~/.config/opencode/skill`, `~/.agents/skills` - because a skill there really is loaded: a workspace holding devup-ui in all four, every one of them live, was answering `installedCount: 0`, which the README itself calls the noise that teaches a reader to ignore the field. Those roots are read and never written, so the allowed-write-root boundary is unchanged. The symmetric mistake is refused too, and it is the quieter one: a file in a directory this runtime never opens is not counted as installed. `devup_skills` takes `projectRoot` like every other tool, because without it the report described the server's own write root, which is not the project unless the two were configured alike - a host granting one shared parent, an Orca worktree pool or a monorepo checkout, had every call reporting on that parent and would have installed there. The inducement moved to where agents actually read. The strongest sentence used to live on `devup_skills`'s own description, which is the tool an agent has no reason to call; it is now in `devup_figma_export`'s description, the one channel that reaches an agent which never sees the server `instructions` - a subagent handed tool schemas alone, and Codex, where `instructions` becomes a single namespace description rather than prompt text. The same gap is on the export response as `skillGap`, and deliberately not gated on `projectRoot`, since the caller who does not know devup-ui is precisely the caller who does not send it; `devup_ui_validate` keeps its own copy for the case where the code was already written and refused. Install state is resolved through one `Lookup` carrying project, home and runtime rather than three threaded parameters, which also makes the home injectable - reading the real one had these assertions passing in CI and failing on the machine of anyone who has devup-ui installed in their own home, which is everyone working on this repository.",
"date": "2026-09-22T14:09:05.375113900Z"
}
Loading
Loading