Skip to content

Dev container: init writes it, skills stop rotting, publishing is manual - #175

Merged
lloydtabb merged 3 commits into
mainfrom
claude/malloyyo-codespaces-container-jk5gdq
Sep 13, 2026
Merged

lloydtabb merged 3 commits into
mainfrom
claude/malloyyo-codespaces-container-jk5gdq

Conversation

@lloydtabb

Copy link
Copy Markdown
Collaborator

Three follow-ups to the dev container that shipped in #174, each one closing a gap that showed up once it was real.

1. malloyyo init writes .devcontainer/devcontainer.json (f0d6319)

The container existed, but opting a model repo into it meant knowing it existed, finding the template, and getting the path right. init is already where a model repo goes to be made ready:

malloyyo init   →  .mcp.json
                   index.malloy (scaffold)
                   .claude/skills/, .claude/settings.json
                   .devcontainer/devcontainer.json   ← new

Never over an existing file, like everything else init writes — which matters more than politeness here, because malloyyo init is the container's own postCreateCommand, so from the second rebuild on it always runs against a repo that already has one.

The template moved from devcontainer/devcontainer.json to packages/cli/src/templates/devcontainer/, beside the skill templates, riding into dist/ through the same copy step — one copy, owned by the thing that writes it. devcontainer/ keeps the Dockerfile, which is what the image workflow builds.

2. Skills become stubs; their procedures move to yo_help (c00698b)

init copied 520 lines of procedure into every model repo and then never touched them again — installSkills skips any directory that exists, so a skill was frozen the day it was created. No version stamp, no update command, nothing to tell a reader which vintage they had. A fix shipped in the CLI reached nobody who had already run init.

That made them the one guidance channel that could rot. The engine's other 29 topics live in content/help/** and update with the installed CLI — and the skills themselves kept saying "lean on the author MCP, call yo_help, don't guess" while being the exception.

So the content moved to where it updates:

content/help/site/data-site.md        228 lines  ← malloyyo-data-site/SKILL.md
content/help/site/auto-update.md      112 lines  ← malloyyo-auto-update/SKILL.md
content/help/site/data-to-parquet.md  109 lines  ← its reference/
content/help/site/github-pages.md      61 lines  ← its reference/

Each SKILL.md is now ~16 lines: unchanged front matter, plus a pointer to yo_help("site/…"). A skill fires on intent — which is what a copied file is actually for; yo_help serves the body — which is what needs to stay current. A stale stub still works, because it routes to whatever is installed; a stale 233-line procedure misleads.

No code changed: content/help/** is auto-indexed, so the four are topics because of where they are. 33 topics, up from 29.

3. Publishing the image is manual (cfdad54)

The image rebuilt weekly and on every merge touching devcontainer/. Both are wrong for 5.5GB that every consuming codespace pulls — and most of what lands here can't change it anyway, since the CLI inside is @malloydata/malloyyo from npm and reaches people through a normal release.

Publishing is now workflow_dispatch and nothing else. PRs still build and smoke-test.

Manual publishing has a trap — change the Dockerfile, CI goes green, PR merges, :latest is still old — so a PR run now writes "Built and verified — but NOT published" into its own step summary, since the green check is exactly what makes it feel finished.

The docs stop promising currency they no longer deliver: the image is a floor, its versions frozen at the last dispatch, and a CLI release does not flow into an image that already exists. The common case needs no new image — npm i -g @malloydata/malloyyo@latest inside the container, which is why the npm prefix is vscode-owned.

Verification

  • engine 126 tests, CLI 186 tests, lint, typecheck
  • 3 new tests: the written devcontainer parses as JSONC and names the published image; an existing one is never overwritten; a second init is a no-op
  • the real round trip in a scratch repo — init writes the stubs, and yo_help("site/data-site") over a live malloyyo mcp --develop stdio session returns the 228-line procedure with its cross-references rewritten to topic names
  • the built bundle run from dist/, which is the path a published npm i -g uses

After merge

Already-initialized repos keep their fat skill copies (init still never clobbers) — rm -rf .claude/skills/malloyyo-* and re-run init to pick up the stubs. And nothing republishes the image; dispatch the workflow when you want the floor to move.

lloydtabb and others added 3 commits September 13, 2026 13:54
The container shipped last week, but opting a model repo into it was a manual
copy from this repo — which means knowing it exists, finding the file, and
getting the path right. `init` is where a model repo already goes to be made
ready; this makes the codespace part of that.

  malloyyo init   →  .mcp.json
                     index.malloy (scaffold)
                     .claude/skills/, .claude/settings.json
                     .devcontainer/devcontainer.json   ← new

Never over an existing file, like everything else `init` writes. That is not
just politeness: `malloyyo init` is the container's own postCreateCommand, so
from the second rebuild onward it always runs against a repo that already has
one, and a repo that pinned a digest or added a feature keeps it.

The template moves from devcontainer/devcontainer.json to
packages/cli/src/templates/devcontainer/, beside the skill templates, and rides
into dist/ through the same copy step — so there is one copy of it, owned by
the thing that writes it, rather than a root file that could drift from what
the CLI hands out. devcontainer/ keeps the Dockerfile, which is what the image
workflow builds.

Verified: typecheck, 186 CLI tests (3 new — the file parses as JSONC and names
the published image, an existing one is never overwritten, a second init is a
no-op), lint, and the built bundle run for real in a scratch model repo, which
is what exercises the dist/ template path a published `npm i -g` uses.

Also corrects the repository this repo's own comments name as an example:
babynames lives at malloydata/malloyyo-babynames, not lloydtabb/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5k5Hwc5GsqxepPmqXtXMx
Signed-off-by: lloyd tabb <1093458+lloydtabb@users.noreply.github.com>
`malloyyo init` copied 520 lines of procedure into every model repo and then
never touched them again — installSkills skips any directory that exists, so a
skill was frozen the day it was created. No version stamp, no update command,
nothing to tell a reader which vintage they had. A fix shipped in the CLI
reached nobody who had already run init.

That made them the one guidance channel that could rot. The engine's other 29
topics ship in content/help/** and update with the installed CLI; the skills
even told Claude to prefer them ("lean on the author MCP, call yo_help, don't
guess") while being the exception themselves.

So the content moves to where it updates, and what stays in the repo is only
the part a file has to hold — the trigger:

  content/help/site/data-site.md        ← malloyyo-data-site/SKILL.md
  content/help/site/auto-update.md      ← malloyyo-auto-update/SKILL.md
  content/help/site/data-to-parquet.md  ← its reference/
  content/help/site/github-pages.md     ← its reference/

Each SKILL.md is now ~16 lines: unchanged front matter, and a pointer to
yo_help("site/…"). A skill fires on intent, which is what a copied file is
actually for; yo_help serves the body, which is what needs to stay current.
A stale stub still works — it routes to whatever is installed. A stale
233-line procedure misleads. Cross-references between the four became topic
names, since they no longer sit in one directory.

No code changed: content/help/** is auto-indexed, so the four are topics
because of where they are. 33 topics now, up from 29.

Verified end to end — engine 126 tests, CLI 186, then the real round trip in a
scratch repo: init writes the stubs, and yo_help("site/data-site") over a live
`malloyyo mcp --develop` stdio session returns the 228-line procedure with the
rewritten links.

Already-initialized repos keep their fat copies (init still never clobbers);
`rm -rf .claude/skills/malloyyo-*` and re-run init to pick up the stubs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: lloyd tabb <1093458+lloydtabb@users.noreply.github.com>
The image rebuilt weekly and on every merge that touched devcontainer/. Both
were wrong for what it is: 5.5GB that every consuming codespace pulls, reissued
on a calendar — and reissued for changes that cannot affect it, since the CLI
inside is `@malloydata/malloyyo` from npm and reaches people through a normal
release.

Publishing is now `workflow_dispatch` and nothing else. PRs that touch
devcontainer/ still build and run every smoke test, so a broken Dockerfile is
still caught where it is written.

The cost of manual publishing is a trap, so it is named rather than left
implicit: you change the Dockerfile, CI goes green, the PR merges, and the
image everyone pulls is still the old one. A PR run now writes "Built and
verified — but NOT published" into its own step summary, because the green
check is exactly what makes it feel finished.

Docs stop promising currency they no longer deliver. The image is a FLOOR: its
versions are whatever was current at the last dispatch, and a CLI release does
not flow into an image that already exists. The fix for the common case doesn't
need a new image at all — `npm i -g @malloydata/malloyyo@latest` inside the
container, which is why the npm prefix is owned by `vscode`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: lloyd tabb <1093458+lloydtabb@users.noreply.github.com>
@lloydtabb
lloydtabb merged commit 98a2eaa into main Sep 13, 2026
4 checks passed
@lloydtabb
lloydtabb deleted the claude/malloyyo-codespaces-container-jk5gdq branch September 13, 2026 15:29
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