Skip to content

feat(ai): teach AI agents to use devstack via MCP, skills and embedded docs - #124

Merged
gustavobertoi merged 3 commits into
mainfrom
feat/ai-agent-integration
Aug 24, 2026
Merged

feat(ai): teach AI agents to use devstack via MCP, skills and embedded docs#124
gustavobertoi merged 3 commits into
mainfrom
feat/ai-agent-integration

Conversation

@gustavobertoi

Copy link
Copy Markdown
Contributor

Why

An AI coding agent dropped into a devstack workspace recognises Docker, does not
recognise devstack, and reaches for docker compose up or hand-writes a
docker-compose.yaml. Both are wrong here — files under .devstack/ are
generated 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's description: (a hard lint
error, since metadata keys are parsed unrendered) or writes ${ref.foo} instead
of ${ref:foo}.

Everything needed to prevent that already existed — template list --json is a
catalog, Describe reads metadata without rendering, every headline command has
--json, and there are 9,400 lines of documentation. What was missing was
discovery.

What this adds

devstack ai — one corpus, three surfaces, nothing re-authored.

Command Does
ai docs [slug] Read/search the documentation corpus, now compiled into the binary
ai commands The command tree as data, derived from the live cobra tree
ai mcp Serve MCP over stdio — 25 tools, 5 prompts, 71 resources
ai install / ai check Emit (and drift-check) the skills, AGENTS.md block and MCP registration
config schema The published JSON Schema for devstack.yaml / workspace.yaml

Three design choices carry the weight:

The MCP tools are the CLI. Each handler builds a fresh command tree with
--json and captures stdout. No parallel API to drift, tool semantics match the
CLI 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's
whole-file write would destroy hand-written content. Skills are owned outright;
AGENTS.md/CLAUDE.md use the marker-fence idiom already in production in
internal/dns for /etc/hosts; .mcp.json sets one key and compares it
semantically, 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 no
TTY, so a mutating tool must inject --yes, and absence is a guarantee where an
annotation is only a hint. The secrets group is never registered at any setting.

Defects found and fixed along the way

  • A live 404. internal/ide emitted a $schema URL to
    schemas/devstack.schema.json — a path that existed at no tag. Every
    .vscode/settings.json devstack has ever generated pointed at nothing. It also
    mapped workspace.yaml to the project schema.
  • Two dangling doc links (../CLAUDE.md from docs/specs/ resolves to
    docs/CLAUDE.md). All 1,188 relative links are now checked.
  • command-reference.md was missing shell-init and others; now asserted
    against the cobra tree in both directions.
  • CI skipped doc-only PRs — untenable once docs compile into the binary.
  • ai install could write into $HOME. Workspace discovery walks up, so
    one stray workspace.yaml in a home directory scattered AGENTS.md,
    CLAUDE.md and .mcp.json there and installed skills machine-wide. Caught
    while dogfooding; now refused, with two regression tests.
  • .mcp.json drift on cosmetic reformats (second commit) — also caught by
    the new gate, during this PR's own dogfood commit.

Testing

111 new tests. Notable ones:

  • MCP server driven over the SDK's in-memory transports with a fake Deps — no
    Docker, 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.
  • An e2e test drives the real binary as an MCP server and parses the entire
    session as JSON-RPC — stdout purity is the most common way a Go MCP server
    ships broken.
  • Schema/struct parity by reflection in both directions, plus a fixture
    round-trip through the schema and config.Load.
  • devstack dogfoods its own emitter: this repo commits what ai install writes,
    and make ai-check is now part of make ci.

Verified: make ci, make determinism, make smoke, go test -tags=e2e.

One pre-existing failure is unrelated and untouched: TestRunUninstallSequence
fails on a clean main too (verified via git stash) because it doesn't
isolate XDG_BIN_HOME and picks up a real ~/.local/bin/ds symlink.

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.json points at devstack ai mcp, so a build without it would ship a
broken registration.

New dependency github.com/modelcontextprotocol/go-sdk (pure Go, CGO_ENABLED=0
holds) is pinned, wrapped by internal/mcpserve as its single seam, and added to
the DECISIONS dependency-risk register.

Docs

Spec 32 · DECISIONS D20 · guide page 21 (ai-agents.md) · new Q-AI-SCOPE
and Q-AI-PLUGIN open questions (global install and a Claude Code plugin are
both deliberately deferred, with reasoning).

🤖 Generated with Claude Code

gustavobertoi and others added 3 commits August 23, 2026 23:50
…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
gustavobertoi merged commit 23ab722 into main Aug 24, 2026
4 checks passed
@gustavobertoi
gustavobertoi deleted the feat/ai-agent-integration branch August 24, 2026 03:24
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant