ADR-008: Native Per-Tool Artifacts for AI Assistant Configuration¶
| Field | Value |
|---|---|
| Date | 2026-07-20 |
| Status | Accepted |
| Deciders | Holger Zahnleiter |
| Supersedes | — |
| Superseded by | — |
Context and Problem Statement¶
Claude Code configuration (CLAUDE.md, .claude/agents/, .claude/commands/, .claude/skills/,
.claude/rules/, .claude/settings.json) is generated through the capability registry described
in ADR-007: dedicated Capability constants,
project-type-aware, always on. GitHub Copilot, GitLab Duo, and Cursor support — selected via the
optional, repeatable --assistant flag — deliberately sits outside that registry, on its own axis
(internal.AssistantType), handled by a single finalizeExtras function
(internal/templating/general_generator.go). Until now, each opted-in assistant received exactly
one file: a hand-written, condensed digest of a subset of the guideline directives
(.github/copilot-instructions.md, .cursor/rules/guidelines.md, .gitlab/duo_instructions.md) —
no per-guideline scoped rules, no commands, no agents, no MCP config beyond the always-on
.mcp.json. The digests duplicated content already mechanically extracted from
specs/guidelines/*.md's ## Directives sections into .claude/rules/*.md, with no mechanism
keeping the two in sync — the same "hand-maintained mapping with no drift protection" problem
ADR-007 solved for the Language/ProjectType axis, recurring one level down for the assistant axis.
GitHub Copilot and Cursor have since grown native mechanisms that map reasonably well onto what
Claude Code already provides for a generated project: scoped, glob-fronted instruction/rule files
(.github/instructions/*.instructions.md, .cursor/rules/*.mdc), reusable prompt/command files
(.github/prompts/*.prompt.md, .cursor/commands/*.md), and, for Copilot, custom chat modes
(.github/chatmodes/*.chatmode.md) approximating an agent persona. Neither tool has a confirmed
native equivalent to Claude Code's isolated-context subagents (context: fork skills backed by a
.claude/agents/*.md definition), and neither has a repo-portable equivalent of
.claude/settings.json's command-approval policy.
Decision Drivers¶
- Single source of truth: a guideline's rule content must not be able to drift between
.claude/rules/,.github/instructions/, and.cursor/rules/— they describe the same rule - Respect the ADR-007 boundary: the assistant axis (
[]AssistantType) is orthogonal to the (Language, ProjectType) axis the capability registry models; folding assistant selection into that registry would mix two independent axes into one table, the exact nested-map failure mode ADR-007 rejected in its Option 3, one level removed - Honesty about capability gaps: Copilot and Cursor genuinely lack an isolated-review mechanism and a repo-portable approval-policy format; generating a misleading artifact (or silently degrading feature parity without saying so) is worse than a documented gap
- Incremental, reviewable delivery: two independently useful, independently revertible units of work (Copilot, then Cursor), not one large cross-cutting change
Considered Options¶
- Keep the status quo — one hand-maintained condensed digest per assistant
- Fold assistant selection into the (Language, ProjectType) capability registry as a third axis
- Keep assistant generation on its own axis (
finalizeExtras), but generalize the guideline extraction engine so a single extraction feeds multiple tool-native rule targets, and add per-tool translations of the existing Claude commands/skills/agents ← chosen
Decision Outcome¶
Chosen option: Option 3. engine.GenerateSpec.RulesTargetDir (one target) became
RuleTargets []RuleTarget (any number of targets, each with its own directory, filename, and
frontmatter-rendering function). .claude/rules/*.md is one RuleTarget among what is now up to
three; .github/instructions/*.instructions.md and .cursor/rules/*.mdc are the other two,
selected by general.GuidelineRuleTargets reading project.Assistants — the same
*internal.ProjectDescriptor every capability Action already receives, so no registry change
was needed to reach it. All three are always extracted from the same rendered
specs/guidelines/*.md content by the same extractDirectives; only the frontmatter shape
differs per tool (paths: YAML list, applyTo: "<glob>", globs:/alwaysApply:).
Commands and skills are translated by dedicated functions (general.WriteCopilotPrompts,
general.WriteCursorCommands) that render the existing Claude command/skill templates and
mechanically strip/replace what doesn't carry over ($ARGUMENTS → Copilot's named
${input:argument} or dropped outright for Cursor's free-text argument style; the "start in plan
mode" instruction; Claude-specific YAML frontmatter). review/security-review are generated in
degraded form — the same checklist plus an explicit disclaimer that the isolation guarantee
does not hold — rather than silently omitted, so users are not misled about parity but also are
not left with strictly less. Chat modes (.github/chatmodes/) are the closest available Copilot
approximation of the code-reviewer/security-auditor Claude agents, again with an explicit
in-file note that they share conversation history rather than running isolated. Cursor has no
confirmed equivalent, so no artifact is generated for it — Option 3 treats an honestly documented
gap as an acceptable, and preferable, outcome to inventing a speculative file format.
Positive Consequences¶
.claude/rules/foo.md,.github/instructions/foo.instructions.md, and.cursor/rules/foo.mdcare now provably identical in body content (asserted directly ininternal/templating/general/capabilities_test.go) — a guideline edit can never desync them- Adding a fourth assistant with a similar rule-file mechanism means one more
RuleTargetbranch inGuidelineRuleTargetsand one more frontmatter function — nospecs.go/generate.gochange, mirroring the additive-extension property ADR-007 established for the other axis - The two commits (Copilot, then Cursor) were independently buildable, testable, and revertible; Cursor's commit touched no engine code, only additive wiring, confirming the mechanism actually generalized rather than merely relocating Copilot-specific logic
- Documented gaps (
.claude/settings.jsonequivalents, subagent isolation) live in the generated files themselves (copilot-instructions.md,.cursor/rules/guidelines.md), not only in this ADR or a commit message — a developer reads the gap where they'd look for the feature
Negative Consequences / Risks¶
- The condensed root digests (
copilot-instructions.md,.cursor/rules/guidelines.md) still exist as a second, smaller hand-maintained surface (project structure/architecture prose) — trimmed rather than eliminated, since that prose has no other generated source of truth to derive from; a future project-metadata capability could absorb it, not attempted here - Copilot chat-mode tool-restriction frontmatter keys were authored from best available knowledge
of VS Code's schema, flagged in-file for verification, not from a live-tested integration —
lower confidence than the rest of this change, which is exercised end-to-end via
repo init - Degrading
review/security-reviewinstead of omitting them means Copilot/Cursor users get a prompt that reads like a full skill but performs worse than Claude's isolated version if the disclaimer is skimmed past — mitigated by placing the disclaimer as the first line of the file, not a footnote
Pros and Cons of the Options¶
Option 1 — Status quo (one condensed digest per assistant)¶
- Pro: Simplest possible implementation; a single static file per assistant
- Con: Duplicated, hand-maintained content with no drift protection against
specs/guidelines/*.mdchanges — the exact problem ADR-007 solved one axis over - Con: No commands, skills, or agent approximation — large capability gap vs. Claude Code that neither tool's actual feature set justifies
Option 2 — Fold assistant selection into the capability registry¶
- Pro: One dispatch mechanism instead of two (
Registry+finalizeExtras) - Con: Mixes two independent axes ((Language, ProjectType) and
[]AssistantType) into one table — profiles would needLanguage × ProjectType × AssistantTypemembership, or the registry would need per-capability assistant-conditionals, undermining the "no Action branches on the profile axis" invariant ADR-007 established - Con:
[]AssistantTypeis a set (0 or more selected simultaneously), not a single enum value likeLanguage/ProjectType— a poor fit forProfileKey's single-value-per-axis shape
Option 3 — Own axis, generalized extraction, per-tool translation (chosen)¶
- Pro: Reuses
project.Assistants, already available to every capabilityAction, without registry changes - Pro: One extraction (
extractDirectives), many frontmatter shapes — single source of truth by construction, not by convention - Pro: Degrade-with-disclaimer for gaps keeps the generated output honest without silently reducing functionality
- Con: Two dispatch mechanisms remain (
Registryfor Language/ProjectType,finalizeExtras/GuidelineRuleTargetsfor assistants) — accepted as the correct reflection of two genuinely independent axes, not a shortcoming to fix
Links¶
internal/templating/engine/{specs.go,generate.go,frontmatter.go}—RuleTarget, the generalized extraction loop, and the three frontmatter buildersinternal/templating/general/capabilities.go—GuidelineRuleTargets,HasAssistantinternal/templating/general/{copilot.go,cursor.go}— command/skill translationinternal/templating/general_generator.go—finalizeExtrasspecs/usage.md— user-facing description of what each--assistantvalue generates- ADR-007 — the capability-registry boundary this decision deliberately stays outside of