Imported from Zaba505/infra (
.claude/skills/plan-tech-design/SKILL.md). Install upstream withnpx skills add Zaba505/infra --skill plan-tech-design. Copyright stays with the author.
Plan the Tech Design for a Capability
This skill composes tech-design.md for a capability — a skeleton narrative that names the components, maps every TR-NN through an accepted ADR to a component, and surfaces gaps. It does not write the per-component detail (sequence flows, schemas, API contracts). Each component gets a GitHub issue so define-component-design can author its design doc one at a time.
This is Step 9 of the capability development lifecycle. It runs once all the ADRs in adrs/ are accepted, and produces both an artifact (tech-design.md) and a manifest of follow-up work (component issues + gap issues).
Why this matters
A tech design is the seam between architecture and implementation. The architecture (ADRs) says what was decided and why; the implementation (component designs) says exactly what we will build. The tech-design.md is what makes that seam navigable: it lists the components a future engineer will need to read about, and it carries the audit trail showing every requirement traces through a decision into a component.
The skeleton-only scope of this skill is intentional. Per-component detail belongs in per-component docs because component formats differ — a database table needs columns and indexes, an API service needs endpoints and contracts, a Terraform module needs inputs and outputs. Bundling them into one giant tech-design.md hides the differences and makes the doc unrebuildable when one component changes. Splitting them out also makes the work parallelizable across humans and across sessions.
The hard discipline of this skill is gap-surfacing. While composing the skeleton, missing pieces become visible — an API contract that no ADR specifies, a database schema that no component owns, a flow that two ADRs would split inconsistently. These are not failures; they are the value of composition. The skill's job is to file them as issues (or resolve them inline) before declaring the tech design complete.
Preconditions — refuse to run without them
Before composing anything:
-
Find the capability at
docs/content/capabilities/{name}/_index.md(page-bundle form). -
Read the capability doc, every UX doc, and
tech-requirements.mdend-to-end. -
Check the review gate on tech-requirements.
reviewed_at:must be an ISO date newer than the file's last modification time. Ifreviewed_at: nullor older, stop. -
Read every ADR under
adrs/. Verify each hasstatus: accepted(orsupersededwith the superseder accepted). If any ADR isproposed, stop and list them:"Cannot compose tech-design.md — these ADRs are still proposed: {list}. Either accept them via
define-adr, or revise them. I won't compose a design against unsettled decisions." -
Read
docs/content/r&d/adrs/for prior shared decisions referenced by capability ADRs (cloud provider, network topology, error format, identifier standard). The composed design must respect them. -
Read
CLAUDE.mdfor repo house patterns. Components proposed must fitservices/{name}/,cloud/{module}/,pkg/shape unless an ADR justifies otherwise.
Goal
Produce two things:
docs/content/capabilities/{name}/tech-design.mdfromassets/template.mdwith: overview paragraph, component diagram, component inventory (one entry per component, linking to the issue filed below), data & state summary, and the TR → ADR → component audit trail.- Two sets of GitHub issues, after the human approves both:
- One issue per component, filed against
define-component-design(Step 10), with titlestory(component): {component name} — {capability-name}. - One issue per surfaced gap, with title
story(gap): {gap name} — {capability-name}. Gap issues block the tech design from being considered complete.
- One issue per component, filed against
Tech-design.md is not complete until every gap issue is resolved. Tell the user this explicitly at the end.
Step 1 — Identify components from ADRs
Walk every accepted ADR's Realization section. Each names one or more components — a service, a Terraform module, a pkg/ package, an external system. Cluster them:
- Services —
services/{name}/Go applications. - Modules —
cloud/{module}/Terraform reusable modules. - Packages —
pkg/{name}/shared Go libraries. - External systems — Cloudflare, GCP-hosted services, GitHub, etc.
Components may appear in multiple ADRs (e.g. a tenant-registry service might be established by ADR-0001 and used by ADR-0003). That's fine — the inventory entry cites every ADR it derives from.
If a component appears in no ADR but feels obvious (e.g. "we'll need an _index.md for the section"), that's a sign it's not an architectural decision — it's a structural one. Mention it in the overview but don't file a component issue for it.
Step 2 — Surface gaps before composing
While walking the ADRs and TRs, look for:
- TRs without ADRs. Every TR must trace through some ADR. If TR-08 has no ADR addressing it, that's a gap — either the ADR set is incomplete (file a follow-up to
plan-adrs) or TR-08 was abandoned (return todefine-technical-requirementsto remove or amend). - ADRs without realizations. An accepted ADR with no
Realizationsection, or with a vacuous one, has no component to put it into. The ADR may need amending. - Implementation-detail gaps that ADRs assume but don't specify. Examples: an ADR says "tenants are addressed by tenant ID" but no shared ADR defines the tenant ID format; an ADR says "the tenant API exposes CRUD over tenants" but no contract names the actual endpoints; an ADR says "tenant data is stored as protobuf-encoded records" but no schema defines the records. These are not ADR-worthy (deciding to call an API is architectural; deciding what API to call is implementation), but they must be specified before a component design can be written.
- Two ADRs in tension. ADR-0003's component depends on a behavior ADR-0005 has retired, etc. These need amending ADRs, not papering-over prose.
For each gap: state it specifically, name what would resolve it (a follow-up define-technical-requirements run, an amending ADR via define-adr, an immediate inline answer from the user, or a per-component spec deferred to define-component-design), and add it to the gap list. Do not invent solutions in tech-design.md.
Step 3 — Compose the skeleton
Use assets/template.md. Fill:
{{overview_paragraph}}— one paragraph summarizing what gets built. No surprises here; this is a digest of the ADR outcomes.{{component_mermaid}}— Mermaid component diagram. Boxes for components, edges for the principal data/control flows derived from ADRs. Keep it digestible — at most ~10 boxes; if the system has more, group sub-components.{{component_inventory}}— one entry per component. Each entry: name, repo location, Established-by ADR list, one-sentence responsibility, and a link to the component-design issue filed in Step 5. No deeper detail. That belongs in the per-component design doc.{{data_state}}— one short paragraph naming what data exists, who owns it, lifecycle. Detailed schemas live in per-component docs; this is just enough for a reader to know which component to click into.{{requirement_realization_table}}— one row per TR-NN. Columns: TR, ADR(s), Realized in (component(s)). Every TR must appear. If a row would be empty in any column, it's a gap — return to Step 2.{{deferred}}— gaps the user explicitly chose to defer plus any out-of-scope items.
Do not write ## Key flows content beyond a placeholder pointing at the component issues. Per-UX sequence diagrams are a per-component-design concern (or deserve their own follow-up issues if they cross multiple components).
Step 4 — Mirror back, get explicit approval
State out loud, before any issues are filed:
- The component list (with one-sentence summaries).
- The gap list (one line per gap).
- The audit-trail row count vs. TR count (must match).
Ask:
"I've composed the skeleton at
tech-design.mdand identified {N} components and {M} gaps. Before I file issues: do the components look right? Do you want any gap resolved inline now? Once you say go, I'll file {N} component issues fordefine-component-designand {M} gap issues."
Wait for explicit approval before filing.
Where dependencies, grouping and sequence live
Dependencies are GitHub issue relationships, not prose. This repo does not carry a
Depends on section in issue bodies — it was removed from .github/ISSUE_TEMPLATE/story.yaml
and every edge that existed in prose was converted to a typed blocked by relationship. Write
ordering with gh issue edit --add-blocked-by; never reintroduce a ### Depends on heading.
Grouping is the project board's Area field, not a label. The epic:* labels were
deleted. Every issue this skill files belongs on the repo's project board with Area set.
Milestones are a delivery sequence, not a grouping. They run 00 · Authoring pipeline
through 08 · Media storage as tenant, each named for a demonstrable end state. The invariant:
every blocked by edge must point backward or stay inside its own milestone. So the
milestone question and the dependency question are one question — placing an issue in an early
milestone while making it blocked by something in a later one is the mistake to watch for. After
pass 2, check the edges you wrote against the milestones you chose; if one points forward, say
so and ask the user which of the two moves.
Resolve the board rather than hard-coding it — a number written into a skill is a number that survives a rename by describing the wrong thing:
OWNER=$(gh repo view --json owner --jq .owner.login)
REPO_NAME=$(gh repo view --json name --jq .name)
PROJECT=$(gh project list --owner "$OWNER" --format json \
--jq ".projects[] | select(.title==\"$REPO_NAME\") | .number")
gh project field-list "$PROJECT" --owner "$OWNER" # confirms the Area options
This needs the project token scope (gh auth refresh -s project); repo alone does not
include it.
Step 5 — File issues
Once approved, file via gh issue create:
- One component issue per component, title
story(component): {component name} — {capability-name}. Body includes: capability link, the ADR(s) that established this component, the responsibility, and a pointer todefine-component-designas the authoring skill. - One gap issue per gap, title
story(gap): {gap name} — {capability-name}. Body includes: the gap statement, what type of resolution is needed (define-technical-requirements, amendingdefine-adr, or per-component spec), and a link back to the parent capability planning issue.
Milestone and Area: before filing anything, ask the user which milestone and which Area value applies. Offer the existing ones via gh api repos/{owner}/{repo}/milestones --jq '.[].title' and gh project field-list "$PROJECT" --owner "$OWNER". Never invent a milestone or an Area value — a single-select rejects one anyway; if none fits, ask the user to add it explicitly.
File in three passes — create every issue, then wire the ordering, then put them on the board:
# Pass 1 — create. Keep a slug -> issue-number map as you go.
gh issue create \
--title "story(component): {component name} — {capability-name}" \
--body-file "{tmp}/{slug}.md" \
--label documentation \
--milestone "{milestone}"
# Pass 2 — wire the ordering, using the real numbers from the map.
gh issue edit {issue} --add-blocked-by {prereq},{prereq}
# Pass 3 — put them on the board, then set Area on each item.
gh project item-add "$PROJECT" --owner "$OWNER" --url {issue-url}
Splitting creation from wiring is what removes the old topological-order constraint: no body cites a sibling, so gaps and components can be created in any order and the edges written afterwards.
Never write a #-prefixed token for a sibling issue in a body — #4 is live GitHub syntax and links to issue 4, not to the fourth component you planned. While drafting, name a sibling by its component or gap title in prose.
Gap issues generally block the components they touch. In pass 2 that is gh issue edit {component} --add-blocked-by {gap} — the edge hangs off the component, pointing at the gap.
Print the issue numbers/URLs back as a manifest, in dependency order, and state which blocked by edges you wrote.
Component issue body template
### Component
**Location:** `{repo-path}` (e.g. `services/tenant-registry/`)
**Type:** {service / module / package / external-system}
**Responsibility:** {one sentence}
### Established by
- [ADR-{NNNN}](../adrs/{NNNN}-{slug}.md) — {short title}
- [ADR-{NNNN}](../adrs/{NNNN}-{slug}.md) — {short title}
### Authoring
This component's design doc will be authored via `define-component-design` — one invocation per component. The doc format is type-specific (e.g. table definition vs. API service). The composed `tech-design.md` will be updated to link to the component design once written.
<!-- Ordering, if any, is a `blocked by` edge written in pass 2 — not a body
section. Hard prerequisites ONLY: unresolved gaps that block this
component, or components whose contract this one consumes. Components
that merely sit near each other in the topology are NOT dependencies —
leave those in prose. -->
### Parent capability
[{capability-name}](../docs/content/capabilities/{name}/_index.md)
### Related
#{parent-capability-issue-or-722}
Gap issue body template
### Gap
{One paragraph describing the gap and why it blocks tech-design completeness.}
### Type of resolution needed
- [ ] Amending TR via `define-technical-requirements`
- [ ] Amending ADR via `define-adr`
- [ ] Per-component spec via `define-component-design`
- [ ] Inline answer (resolved in conversation)
### Surfaced during
Composition of `tech-design.md` for [{capability-name}](../docs/content/capabilities/{name}/_index.md).
<!-- Most gaps are blocked by nothing — they are what everything else waits
on. If one genuinely is, that is a `blocked by` edge written in pass 2,
not a body section. -->
### Related
#{parent-capability-issue-or-722}
Completion
After filing, tell the user explicitly:
"I filed {N} component issues and {M} gap issues. The tech design is not complete until all gap issues are resolved —
plan-implementation(Step 11) won't run against an incomplete tech design. Each component issue is the input to onedefine-component-designinvocation. Run them in any order; the audit trail intech-design.mdwill be updated as components are designed."
Conversation discipline
- Announce gates before doing anything else. "Tech-requirements is reviewed; ADRs 0001..0007 all accepted. Composing." Or "Stopping — ADR-0003 is still proposed."
- Don't write per-component detail. If you find yourself writing endpoint paths, table columns, or sequence diagrams beyond the audit-trail level, you're doing
define-component-design's job. Stop and file the issue. - Don't paper over gaps. Prose is not a substitute for a missing decision or a missing spec. Surface, don't smooth.
- The audit trail is non-negotiable. Every TR appears in the table; every ADR with
status: acceptedis realized in some component. If you cannot make these true, return toplan-adrs/define-adr.
Validate the rendered page
After saving tech-design.md (and before filing component/gap issues), run cd docs && hugo server and open the page in a browser to confirm it renders correctly — every ref link to TRs, ADRs, and UXs must resolve, the component Mermaid diagram must render, and the audit-trail table must format cleanly. A clean build is necessary but not sufficient; pages can build with no errors yet render broken (missing/incorrect front-matter type, unresolved ref shortcode, malformed Mermaid). The doc is not done until the rendered page has been visually validated. If you cannot run the preview in this session, say so explicitly to the user rather than reporting the doc as done.
Output format
tech-design.md is plain markdown with Hugo/Docsy frontmatter. Mermaid renders natively in Docsy. Component-inventory and audit-trail tables use standard markdown tables. No code fences around the whole document.
GitHub issues are filed via gh issue create after explicit user approval, one per component and one per gap, then wired with gh issue edit --add-blocked-by and added to the project board.
Examples of good behavior
Example 1 — gap surfaced during composition:
Response: "Walking ADR-0003: it says tenants are addressed by tenant ID. The shared ADR r&d/adrs/0006-resource-identifier-standard defines an ID format, but neither ADR specifies how the tenant ID is derived from the GitHub onboarding issue (auto-incremented? hash of the capability name? operator chooses?). That's an implementation gap — not ADR-worthy, but a per-component spec is needed. Filing a gap issue, type 'per-component spec via define-component-design'."
Example 2 — premature per-component detail caught:
User: "While you're at it, write out the tenant-registry service's API endpoints — I know what they should be."
Response: "Hold on — that's define-component-design's job. I'll file a component issue for tenant-registry now and you can run define-component-design against it next; it'll capture endpoint design with the right API-service format. If I write endpoints into tech-design.md, it bypasses the per-component review and makes this doc unrebuildable when the API changes."
Example 3 — uncovered TR detected:
Response: "Building the audit trail surfaced a gap: TR-08 ('graceful degradation when a GCP region is unreachable') is in tech-requirements.md but no ADR addresses it. I'm not going to invent a story in tech-design.md. Two paths: (a) accept that TR-08 is unaddressed and file an ADR issue for plan-adrs/define-adr to handle later, or (b) drop TR-08 from tech-requirements.md if the constraint isn't real. Which?"