feat(ai): teach AI agents to use devstack via MCP, skills and embedded docs - #124
Merged
Conversation
…d docs Adds `devstack ai` — one command group that makes devstack self-describing to AI coding tools, so an agent working in a devstack repo stops reaching for `docker compose` and stops hand-writing files under `.devstack/`. One corpus, three surfaces, nothing re-authored: * `docs/` is now go:embed'ed. `ai docs` lists/prints/searches it, `ai commands` emits the command tree as data derived from the live cobra tree. Both work with no workspace — which is when an agent has to learn `devstack init`. * `ai mcp` serves the Model Context Protocol over stdio: 25 tools, 5 prompts, 71 resources. The tools ARE the CLI — each handler builds a fresh command tree with --json and captures stdout, so there is no parallel API to drift and the flock is taken/released inside each call, leaving the long-lived process stateless (ARCHITECTURE's no-daemon model is preserved). * `ai install` emits 3 skills, a fenced AGENTS.md block, a CLAUDE.md import and the .mcp.json key. Three merge modes: skills are owned outright, the markdown files use the /etc/hosts marker fence from internal/dns, .mcp.json sets one key — so a user's own content and other MCP servers survive regeneration. Emitted content is workspace-independent and version-independent: it describes devstack, never a snapshot of the project list or the binary's version stamp, so committed files never go stale on a release or when someone adds a project. Safety: write tools are on by default, but irreversible verbs (workspace destroy, db drop, db reset) are ABSENT unless --allow-destructive, because MCP has no TTY and a mutating tool must inject --yes. The secrets group is never registered at any setting. Also lands the JSON Schemas spec 01 / D16 mandate, hand-authored per D16 (the validator/v10 tag vocabulary does not round-trip), guarded by a fixture round-trip test and a reflection test asserting struct/schema parity in both directions. Fixes found along the way: * internal/ide emitted a $schema URL to schemas/devstack.schema.json — a path that existed at no tag, so every generated .vscode/settings.json pointed at a 404. It also mapped workspace.yaml to the project schema. Now per-kind, and a dev build falls back to main. * Two dangling doc links (`../CLAUDE.md` from docs/specs/ resolves to docs/CLAUDE.md). All 1,188 relative links are now checked. * docs/guide/command-reference.md was missing `shell-init` and others; it is now asserted against the cobra tree in both directions. * CI skipped every check for doc-only PRs — untenable now that docs compile into the binary. paths-ignore is narrowed to files that are genuinely not build inputs. * `ai install` refuses $HOME and any ancestor: workspace discovery walks UP, so one stray workspace.yaml in a home directory would scatter AGENTS.md, CLAUDE.md and .mcp.json across it and install skills machine-wide. Binary grows 2.6 MiB stripped (45.2 -> 47.9): ~845 KiB embedded content, ~1.8 MiB MCP SDK. Not gated behind a build tag — the emitted .mcp.json points at `devstack ai mcp`, so a build without it would ship a broken registration. devstack dogfoods its own emitter: this repo commits the files `ai install` writes, and `make ai-check` is part of `make ci`. Spec 32 · DECISIONS D20 · guide page 21 · Q-AI-SCOPE / Q-AI-PLUGIN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
devstack owns ONE KEY in .mcp.json, not the file's formatting, but --check byte-compared the whole file. Any reformat by an editor, a formatter or another tool therefore marked the artifact stale forever — and since `ai check` gates `make ci`, a purely cosmetic change would fail the build while `ai install` fought the other tool on every commit. This is not hypothetical: it happened during the dogfood commit. The JSON-key mode now compares the value at the key path semantically and rewrites the file only when that value actually differs, so a user's formatting survives. MergeWhole and MergeFence keep byte comparison, which is correct there because devstack owns exactly the bytes it writes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gustavobertoi
added a commit
that referenced
this pull request
Aug 24, 2026
The README still described six JS templates and made no mention of the AI-agent surface that landed in #124. * Replaces the framework-templates bullet with the real catalogue: apps across Go, Rust, Python, Node, Bun, Deno, PHP, Ruby, Elixir, Java and .NET plus the Vite family, and a second bullet listing the 25 shared engines. * Adds the `ai` group to the command table and `config schema` alongside `config validate/show`. * Documents that the whole docs corpus is readable from the binary, and links the new AI-agents guide. * Bumps the spec range to 32 and folds spec 32 into the status paragraph. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
An AI coding agent dropped into a devstack workspace recognises Docker, does not
recognise devstack, and reaches for
docker compose upor hand-writes adocker-compose.yaml. Both are wrong here — files under.devstack/aregenerated and get overwritten, and the compose project name, labels and external
network are tool-owned, so driving compose directly forks a parallel stack. The
same agent puts a
[[ ]]action in a template'sdescription:(a hard linterror, since metadata keys are parsed unrendered) or writes
${ref.foo}insteadof
${ref:foo}.Everything needed to prevent that already existed —
template list --jsonis acatalog,
Describereads metadata without rendering, every headline command has--json, and there are 9,400 lines of documentation. What was missing wasdiscovery.
What this adds
devstack ai— one corpus, three surfaces, nothing re-authored.ai docs [slug]ai commandsai mcpai install/ai checkconfig schemadevstack.yaml/workspace.yamlThree design choices carry the weight:
The MCP tools are the CLI. Each handler builds a fresh command tree with
--jsonand captures stdout. No parallel API to drift, tool semantics match theCLI by construction, and the flock is taken and released inside each call — so
the long-lived server never holds it and the no-daemon architecture is preserved.
Emitted files are workspace- and version-independent. They describe devstack,
never a snapshot of the project list or the binary's version stamp. Committed
files never go stale on a release or when someone adds a project. Live facts come
from
status --json, which the agent runs.Three merge modes, because two targets are user-owned.
internal/ide'swhole-file write would destroy hand-written content. Skills are owned outright;
AGENTS.md/CLAUDE.mduse the marker-fence idiom already in production ininternal/dnsfor/etc/hosts;.mcp.jsonsets one key and compares itsemantically, so another tool's formatting survives.
Safety
Write tools are on by default, but irreversible verbs (
workspace destroy,db drop,db reset) are absent unless--allow-destructive— MCP has noTTY, so a mutating tool must inject
--yes, and absence is a guarantee where anannotation is only a hint. The
secretsgroup is never registered at any setting.Defects found and fixed along the way
internal/ideemitted a$schemaURL toschemas/devstack.schema.json— a path that existed at no tag. Every.vscode/settings.jsondevstack has ever generated pointed at nothing. It alsomapped
workspace.yamlto the project schema.../CLAUDE.mdfromdocs/specs/resolves todocs/CLAUDE.md). All 1,188 relative links are now checked.command-reference.mdwas missingshell-initand others; now assertedagainst the cobra tree in both directions.
ai installcould write into$HOME. Workspace discovery walks up, soone stray
workspace.yamlin a home directory scatteredAGENTS.md,CLAUDE.mdand.mcp.jsonthere and installed skills machine-wide. Caughtwhile dogfooding; now refused, with two regression tests.
.mcp.jsondrift on cosmetic reformats (second commit) — also caught bythe new gate, during this PR's own dogfood commit.
Testing
111 new tests. Notable ones:
Deps— noDocker, no subprocess. The tool set is pinned per option mode, so changing
what a model can do to someone's machine is a test-breaking act.
session as JSON-RPC — stdout purity is the most common way a Go MCP server
ships broken.
round-trip through the schema and
config.Load.ai installwrites,and
make ai-checkis now part ofmake ci.Verified:
make ci,make determinism,make smoke,go test -tags=e2e.Cost
Binary grows 2.6 MiB stripped (45.2 → 47.9 MiB): ~845 KiB embedded content,
~1.8 MiB MCP SDK. Deliberately not gated behind a build tag — the emitted
.mcp.jsonpoints atdevstack ai mcp, so a build without it would ship abroken registration.
New dependency
github.com/modelcontextprotocol/go-sdk(pure Go,CGO_ENABLED=0holds) is pinned, wrapped by
internal/mcpserveas its single seam, and added tothe DECISIONS dependency-risk register.
Docs
Spec 32 · DECISIONS D20 · guide page 21 (
ai-agents.md) · newQ-AI-SCOPEand
Q-AI-PLUGINopen questions (global install and a Claude Code plugin areboth deliberately deferred, with reasoning).
🤖 Generated with Claude Code