From daggerverse
Standardized 9-step workflow for designing a new daggerverse module from scratch — picking a name, researching prior art, proposing types/funcs and tests, drafting story issues against `.github/ISSUE_TEMPLATE/story.yaml`, and creating them with `gh issue create`. Use this whenever the user wants to start a new daggerverse module, kicks off a new module design under `daggerverse/`, or asks to "scope out", "plan", "design", or "propose issues for" a new module (Redis, Vault, MongoDB, NATS, anything). Use it even when the user types `/plan-dagger-module` without naming a module — drive the workflow and ask. Skip only when the user is *implementing* an already-scoped module (issue already exists) or making changes to an existing module's API.
How this skill is triggered — by the user, by Claude, or both
Slash command
/daggerverse:plan-dagger-moduleThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A new daggerverse module starts as a design conversation, not a code patch. This skill paces that conversation: name → research → API → tests → issues → `gh issue create`. The point is to *converge with the user* before any module code is written, so the eventual implementation PR has a real spec to land against.
A new daggerverse module starts as a design conversation, not a code patch. This skill paces that conversation: name → research → API → tests → issues → gh issue create. The point is to converge with the user before any module code is written, so the eventual implementation PR has a real spec to land against.
You are not the decider here. The user is. Your job is to surface options, encode constraints, and stop for input — not to charge through to a final answer.
*dagger.Secret instead of a string), say so in the proposal rather than burying it.daggerverse/ before proposing types. Cite the analogy in your proposal (e.g. "modeled after kafka.Cluster — controller + broker services with +cache=\"never\" chained methods").+cache= story is: pure deterministic helpers get no directive (7-day default is fine); anything that spins up services, exposes randomness, or chains queries needs +cache="never" on every method that gets chained off it. Tests that exist to prove non-caching use +cache="session".Ci builder is the canonical item that gets mentioned and then lost (it had to be retrofitted onto zig and others as a later story instead of falling out of the original design pass); treat it as a first-class follow-up issue every time it comes up. The step-9 completeness gate enforces this.Ask the user what the module is for, then propose two or three lowercase, kebab-case names with one-line rationales. Names should:
redis, vault, nats), or describe the capability (certificate-management, grafana-stack).db, infra, tools) — daggerverse modules are scoped to a single product or capability.daggerverse/. List existing module names so the user can see the field.Stop. Ask the user to pick one (or propose their own). Do not proceed without an explicit name.
With the chosen name in hand, do the research before writing any API. Search in parallel:
daggerverse/ for the closest existing module — read its main.go, dagger.json, and tests/main.go. Note the topology (single struct vs. nested objects), how it handles services, and what it returned to callers.gh issue list --search "story" --state all --limit 20 and gh pr list --state merged --limit 10. Skim the bodies of the two or three most relevant ones for the shape (#11, #16, #17, #23 are good anchors — they show how the team writes proposals).daggerverse/CLAUDE.md once (function caching, regeneration rules, module layout, name mangling).Output a short research summary: closest analog module, upstream image + version, defaults that matter, anything surprising. Keep it tight — half a page, not a dissertation.
Stop. Ask "anything I'm missing before I propose the API?" Wait for the user's response.
Draft the module's public surface as a code-shaped sketch (Go signatures, no bodies). Cover:
type Redis struct{}).// +private.+default= / +optional= directives where relevant, return type, and a one-line doc comment explaining what it does.+cache= directive on every function and every method that gets chained off a returned object (per the chained-method rule — see Constraints).*dagger.File / *dagger.Secret are used at module boundaries instead of strings, and why.tests/ subpackage with a Tests struct, All(ctx) aggregator with // +check, and the planned test method names (bodies come in step 5).Cite the analog module(s) you modeled this after and call out anywhere the design diverges from the analog and why.
Stop. Ask for feedback on the API shape. Do not enumerate tests yet — that is step 5.
Apply the user's feedback. If they ask for a change, restate the affected part of the API with the change applied, and re-ask. Loop here — keep refining until the user explicitly says some variant of "looks good, move on", "approved", or "ship it for tests now". Do not advance to step 5 on ambiguous responses; ask.
Now design the tests/ package. Output a list of test methods on the Tests struct, each with:
UuidV4ShouldNotBeCached). Methods must be exported or Dagger will not expose them as functions. The CLI name is derived by name mangling (UuidV4ShouldNotBeCached → uuid-v-4-should-not-be-cached); don't try to control the CLI name by lower-casing the Go method.Required coverage to consider — propose tests for whichever apply to this module:
+cache="never" needs a XShouldNotBeCached test that calls it twice and asserts different results (for random) or that side effects ran (for services). random/tests/main.go is the template.controllers > 1 rejection).dag.CurrentModule().WorkdirFile so the file is materialized in the engine and re-readable.Test IDs and names should not bake in caller-supplied secrets, passwords, or other constants — generate them with dag.Random().Sha256(ctx) at test time (see Constraints / no hardcoded secrets).
Stop. Ask for feedback on the test list.
Same loop as step 4. Apply feedback, restate the affected entries, re-ask. Wait for explicit approval before moving on.
Render the work as one or more story issues. Most new modules ship as one main issue covering the whole API and a separate follow-up issue for each clearly separable extension (TLS support, additional security profiles, optional integrations, the chained Ci builder). When in doubt about whether a chunk belongs in the main issue or its own, that's a question for the user — but it must land in some issue. Issue #11 / #16 (kafka main) and #17 (kafka isolated-defaults follow-up) are good shape references.
Every separable extension gets its own fully drafted issue — title + complete body — not a sentence buried in the main issue's Description. The recurring failure this skill exists to prevent: flagging something like "a chained Ci builder would be a natural follow-up" in prose, then never creating an issue for it. The Ci builder is the canonical offender; it has been deferred-and-forgotten across multiple module designs and only got tracked when retrofitted after the fact. If you mention an extension as future work anywhere in steps 2–7, you owe it a drafted issue here (or an explicit drop from the user at the step-9 gate).
Follow-up title format: story(issue-<main#>): <short description>, where <main#> is the main issue's number. That number isn't known until the main issue is actually created — so draft follow-up titles with an issue-<main#> placeholder now, and step 9 fills in the real number after creating the main issue. Each follow-up's ### Related Issues section references the main issue.
Title format — exactly: story(<subject>): <short description>
<subject> is usually daggerverse for a brand-new module, or issue-<N> if this story closes a specific prior issue. Match the convention of existing issues (#11 uses daggerverse, #16 uses issue-11, #17 uses daggerverse).<short description> is lowercase, no trailing period, under ~70 chars total title length.Body — match .github/ISSUE_TEMPLATE/story.yaml exactly:
### Description
<End-user-perspective paragraph: what the module does and who would use
it. Then a "#### <subsection>" for each logical chunk of the API —
factories, services, clients, security profiles — with the Go signatures
in a single fenced block per subsection. Call out defaults and any
explicit rejections. End with the security/TLS posture for this story
("plaintext only; TLS in a follow-up") if applicable.>
### Acceptance Criteria
- [ ] <Each criterion is a concrete, testable outcome>
- [ ] <Functions listed in Description exist and pass `dagger functions`>
- [ ] <Each test from step 6 exists and passes individually>
- [ ] <Specific behaviors: defaults work, rejected inputs are rejected,
cache directives propagate on chained methods, etc.>
### Related Issues
<Reference any prior issue numbers this story builds on or closes, or
`_None._` if standalone.>
Output the full proposed title and body for each issue. Stop. Ask for feedback on each issue's title, body, and split.
Same loop as steps 4 and 6. Apply feedback to title/body/split, restate, re-ask. Wait for explicit approval — the main issue and every follow-up must each be individually approved before step 9; blanket "looks good" on the bundle is fine only if you restate the full list (main + each follow-up) so the user is approving every item knowingly. If the user wants to drop one of the proposed issues, drop it (record it as an explicit drop for the step-9 gate); if they want to add one, add it and run it through step 7's format.
First, the completeness gate. Do this before any gh issue create. Restate the full list of every work item identified across steps 2–7 — the main API surface, each separable extension, the chained Ci builder, and anything you flagged as future/follow-up work in prose anywhere in the design conversation. For each item, force a binary outcome:
Ci builder for now").Do not proceed while any item is unresolved, and do not let an item slip by unmentioned. If you're unsure whether something counts as work, list it and ask — the cost of an extra line in the gate is trivial; the cost of a silently-lost follow-up is a retrofit story months later. This gate is the whole point of the skill: nothing surfaced during design disappears without a decision.
Once every item is either an approved issue or an explicit drop, create the issues. Create the main issue first, capture its number from the returned URL, then create each approved follow-up — substituting the real main issue number into its story(issue-<main#>): ... title and its ### Related Issues reference.
gh issue create --title "story(<subject>): <description>" --body "$(cat <<'EOF'
### Description
...
### Acceptance Criteria
- [ ] ...
### Related Issues
...
EOF
)"
Use a HEREDOC so markdown formatting (lists, code fences) survives shell quoting. Do not pass --assignee, --label, or --milestone unless the user asked for them. Print the returned issue URL after each gh issue create succeeds.
If gh issue create fails, surface the error to the user and ask how to proceed — do not retry blindly or fall back to curl against the GitHub API.
The flow is complete only when every gated item is either a printed issue URL or an explicit drop. Declaring the design done while a flagged extension still lives only in prose is the exact failure mode this skill exists to prevent — do not do it.
These are non-negotiable. Every proposal in steps 3, 5, 7 must respect them; if a user request would violate one, surface the conflict explicitly rather than silently compromising.
dag.Random().Sha256(ctx) (preferred when the dep is already in scope) or crypto/rand in Go module code. Literal credential strings never enter git, not even as placeholders.+cache="never" must repeat on every chained method. If a function returns an object and callers will chain methods off that object, every method on that returned type also needs +cache="never" — otherwise repeated chained queries serve stale cached values. The kafka Cluster and Client are the canonical example.*dagger.File and *dagger.Secret across boundaries and re-load via the dep's Load* helpers inside the receiving module.*dagger.File: file.Export(ctx, localPath) materializes it onto the module's local filesystem, then read it with os.Open / os.ReadFile. See daggerverse/crypto/main.go:digestFile and daggerverse/certificate-management/main.go for the pattern.*dagger.File: write the bytes under a subdir of dag.CurrentModule().Workdir with os.WriteFile (or equivalent), then return dag.CurrentModule().WorkdirFile(relPath). See the writeWorkdirFile helper in daggerverse/crypto/main.go and daggerverse/grafana-stack/main.go. Do not use Export for outputs — that is the input direction.gopkg.in/yaml.v3. Any function that produces YAML (config files, compose-style fragments) uses yaml.v3's Marshal or Encoder — never fmt.Fprintf or string concatenation. Hand-rolled YAML mishandles quoting and escaping of caller-supplied strings.+cache= directives go on their own line in the doc comment block above the function. Place above the signature; do not inline.Sha256ShouldNotBeCached becomes sha-256-should-not-be-cached on the CLI; UuidV4 becomes UUIDV4(ctx) on the dag client (acronyms uppercase in generated bindings). Account for this when discussing CLI invocation in the issue body.dagger develop regenerates bindings. After signature changes, both the module and any module that depends on it (tests/ depends on ..) need dagger develop re-run. Mention this in acceptance criteria when the module has a tests/ subpackage.When asked about external traces or runs, use dagger trace <id> --progress=plain — never curl against dagger.cloud.
The deeper reference for cache rules, regeneration, layout, and name mangling is daggerverse/CLAUDE.md. Read it once during step 2 research.
Read whichever applies during step 2 research.
daggerverse/random/main.go — simplest module: pure helpers, +cache="never" on each, no services. Good template for any module that's "just functions".daggerverse/crypto/main.go — helpers that consume secrets and emit files; good template for *dagger.Secret + *dagger.File at boundaries.daggerverse/certificate-management/main.go — multiple cooperating types (CA + cert + key), Load*-style entry points.daggerverse/grafana-stack/main.go — multi-service module with provisioned configs rendered via yaml.v3.daggerverse/kafka/main.go — services + pure-Go client, +cache="never" propagation on chained methods, runtime file output via dag.CurrentModule().WorkdirFile, explicit input rejection.gh issue view <N> during step 7.A common failure mode is "I'll do steps 3 and 4 together because the answer felt obvious." Don't. The user explicitly invokes this skill because they want a paced design conversation; collapsing steps defeats the purpose and erodes their ability to course-correct cheaply. If a step's output feels trivially small, that's fine — output it, stop, and let the user tell you to move on.
When you stop, end the turn with a single clear question like:
Step 3 done — API shape above. Move on to test design, or refine first?
Not five questions, not a paragraph of caveats. One question. Wait.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub z5labs/devex --plugin daggerverse