A harness (the rules, agents, commands, skills, hooks and MCP servers that shape coding agents) kept as source in one Git repository, the Forge. Craftar composes it per team, writes each tool's own files and records them in a lockfile (P1, P2).
craftar (the CLI and this plugin) is built with Orca, which is why the example runs in it. Get the Orca plugin →
craftar (the CLI and this plugin) is built with Orca, which is why the example runs in it. Get the Orca plugin →
~/work/acme-portal
# the Forge moved: a rule was edited and committed in acme-forge$craftar statuscraftar status — profile acme · recipes base update 2 update .claude/rules/project-conventions.md rule/project-conventions update .kiro/steering/project-conventions.md rule/project-conventions$craftar synccraftar sync — profile acme · recipes base · targets claude-code, kiro wrote 2, removed 0 orphan(s), skipped 0 + .claude/rules/project-conventions.md + .kiro/steering/project-conventions.md
craftar 1.7.0 on a synthetic workspace; the comment line stands for the Forge commit.
~/work/acme-portal
# a line was added to .claude/rules/project-conventions.md by hand$craftar drift showdrift .claude/rules/project-conventions.md rule/project-conventions forge: same promote: yes$craftar drift promote .claude/rules/project-conventions.md --dry-runpromote .claude/rules/project-conventions.md → rule/project-conventions--acme (variant, profile acme) would write ingredients/rules/project-conventions--acme/ingredient.yaml would write ingredients/rules/project-conventions--acme/rule.md would edit recipes/base.yaml (ingredients) dry run — the Forge was not written$craftar drift promote .claude/rules/project-conventions.mdpromote .claude/rules/project-conventions.md → rule/project-conventions--acme (variant, profile acme) wrote ingredients/rules/project-conventions--acme/ingredient.yaml wrote ingredients/rules/project-conventions--acme/rule.md edited recipes/base.yaml (ingredients) the Forge is not committed — review with git, then commit and push it
craftar 1.7.0 on a synthetic workspace; the comment line stands for the hand edit.
Two problems, one root: the harness has no source and no record.
P1
One harness, T teams, K AI coders, A agents each: maintained by hand.
The same agents, rules, commands, hooks and MCP servers live as T copies in K formats (.claude/, .kiro/, AGENTS.md, .cursor/). A change costs T × K manual edits, with no record of what came from what.
requiresone source; emission per target; a record per file.
P2
One developer, many workspaces, separate remotes.
One workspace per team, each on that team's Git host, its harness copied in by hand. Nothing records which version each one carries, or whether a file was edited in place.
requiresa channel that is no team repository's remote; a lock per workspace; a registry.
state before Craftar: the resolved state of each is in Model
dashed edges are manual work; no edge records provenance. Each small block is one agent (reviewer, author, executor…). Faded = diverged or absent.
four copies of the harness, four ages, no common repository, no lock; the dashed links are the developer comparing folders by hand.
What breaks when the harness is duplicated: observed, not hypothetical.
Eight failures seen in two earlier generators and in hand-synced workspaces: FM-1 to FM-5 from P1, FM-6 to FM-8 from P2. Each becomes a constraint in Invariants.
id
Failure
Root cause
FM-1
Regeneration deletes hand-written agents and rules
The generator owned the whole .claude/ folder.
FM-2
Every file reads as hand-edited; the team lives on --force
Raw-byte hashes; a Windows checkout rewrites LF→CRLF.
FM-3
A deleted rule keeps loading into sessions
The generator only wrote, never removed.
FM-4
Kiro mirrors drift 4–52 lines from their source
Only rules were generated; the rest were hand mirrors.
FM-5
A rule and its reviewer agent contradict each other
Two files stating the same constraints, edited apart.
FM-6
A model pin or MCP version breaks in three places at once
Volatile values inlined instead of declared once.
FM-7
"Everything in English" contradicted by 18 files
Language treated as the workspace's, not the ingredient's.
FM-8
Real package-feed tokens in a staged settings.json
Settings carried values instead of variable references.
Source, resolution, emission.
Ingredients are source; recipes and profiles compose them; craftar sync resolves them for one workspace, writes each target's files and records them in a lockfile. Generated files are committed, so a team that only uses Kiro never needs Craftar installed.
P1 resolved: solid edges are generated and recorded in each workspace's lockfile; status reads it to report drift per workspace.
P2 resolved: one Forge, one lock per workspace, one registry
The Forge is a fifth repository that no team repository references; each workspace's craftar.yaml points to it, and its lock records the Forge commit that produced it. craftar workspaces reports each one as up to date, outdated (forge moved when the Forge has a newer commit) or drifted.
the Forge is never a remote of a team repository; generated files are committed inside each workspace.
~
$ craftar workspaces
craftar workspaces — 4 registered
team-a-workspace /home/dev/work/team-a-workspace
profile team-a · frontend=frontend-angular · claude-codeforge ../forge @ 7c1e9a20 · synced 2026-10-07 09:12 UTC ·up to date
team-b-workspace /home/dev/work/team-b-workspace
profile team-b · frontend=frontend-react · agents-mdforge ../forge @ 7c1e9a20 · synced 2026-10-07 09:14 UTC ·up to date
team-c-workspace /home/dev/work/team-c-workspace
profile team-c · frontend=frontend-angular · kiroforge ../forge @ 7c1e9a20 · synced 2026-10-02 17:40 UTC ·outdated, forge moved
team-d-workspace /home/dev/work/team-d-workspace
profile team-d · frontend=frontend-angular · agents-mdforge ../forge @ 7c1e9a20 · synced 2026-10-07 09:20 UTC ·drift$
From layers to files: how sync works.
Layers apply weakest first and the strongest wins; stack recipes that share a slot exclude each other, and {{params}} come from the winning layer. craftar explain <file> names the layer behind each output.
OpenCode.opencode/agents · commands · opencode.json · rules through AGENTS.md
Composed files carry a GENERATED banner; Claude Code files are written verbatim, so an existing workspace is adopted byte for byte. What Craftar owns is recorded in craftar.lock.
In practice: Forge → Workspace by stack
Above, the logical flow; below, how a real Forge for one stack becomes files. Pick a stack, then a target, to see what craftar sync writes.
dotnet-enterprise
A Forge for .NET backend teams following Clean Architecture: see how its ingredients map to each target's workspace structure.
Five more targets in one workspace (targets: [cursor, copilot, agent-skills, codex, antigravity]). Commands become skills under .agents/skills/, which Cursor, Copilot, Codex and Antigravity read. Codex takes the agents only and reads its rules from AGENTS.md; sync warns where Cursor and Antigravity drop an agent's tools and Codex its model and tools.
generated by Craftar client-owned (untouched)
node-fullstack
A Forge for Node.js teams using TypeScript: see how its ingredients map to each target's workspace structure.
Five more targets in one workspace (targets: [cursor, copilot, agent-skills, codex, antigravity]). Commands become skills under .agents/skills/, which Cursor, Copilot, Codex and Antigravity read. Codex takes the agents only and reads its rules from AGENTS.md; sync warns where Cursor and Antigravity drop an agent's tools and Codex its model and tools. Copilot writes the npm-audit server twice, for the Copilot CLI and for VS Code; Codex writes no MCP file, so sync warns and the server goes in .codex/config.toml by hand.
generated by Craftar client-owned (untouched)
iac-multicloud
A Forge for platform teams managing infrastructure as code: see how its ingredients map to each target's workspace structure.
Five more targets in one workspace (targets: [cursor, copilot, agent-skills, codex, antigravity]). Commands become skills under .agents/skills/, which Cursor, Copilot, Codex and Antigravity read. Codex takes the agents only and reads its rules from AGENTS.md; sync warns where Cursor and Antigravity drop an agent's tools and Codex its model and tools.
generated by Craftar client-owned (untouched)
The first three tabs show Claude Code, Kiro and AGENTS.md; Five more targets shows Cursor, Copilot, Agent Skills, Codex and Antigravity in one workspace. All eight are working; the capability matrix lists every path.
What syncs where
Craftar writes only each AI coder's own files: every path is in the capability matrix: and a root AGENTS.md, read by Codex, Cursor, Copilot, Antigravity, OpenCode, Warp and Kimi; plus .craftar/ when the recipes bring a local service. Folders like src/ or infra/ are never touched: only paths recorded in craftar.lock are written or removed. Outside the workspace and the Forge it writes only under ~/.craftar: the Forge cache and the registry.
Growing the Forge: a second client, one base
A second client imported into the Forge reuses every ingredient that matches; the ones that differ become variants named after the profile, so both workspaces still reproduce byte for byte. forge variants lists them, forge diff classifies each hunk, and forge unify resolves one, keep a side, move a differing value into the profile as a parameter, or a block as a section, after planning every registered workspace that reads the Forge.
one rule, two clients. forge unify refuses when another layer, profile or ingredient already uses the key; forge promote makes a profile's variant the base.
Forge
forge/ · one Git repo
Git repository holding every ingredient, recipe and profile; read by path, or by git URL at a ref.
Ingredient
ingredients/rules/frontend-angular/
Smallest reusable unit: a rule, agent, command, skill, hook, MCP server, script, steering file or local service.
Recipe
recipes/frontend-angular.yaml
Named set of ingredients with parameters; stack recipes declare a slot.
Profile
profiles/team-a/profile.yaml
One team: its recipes, targets, parameters and sections.
Workspace
craftar.yaml
A folder with craftar.yaml (profile, recipe changes, targets) and a git-ignored craftar.local.yaml per developer.
An AI coder to emit for; the matrix lists what each one gets.
Lockfile
craftar.lock
The Forge's source, ref and commit and a normalized hash per generated file: the authority on what Craftar owns.
Registry
~/.craftar/registry.json
Every workspace synced on this machine; craftar workspaces and craftar ui show them.
Twelve capabilities.
Each links to its entry in the documentation: the problem it addresses, every command it adds and how it works. working runs today and is tested; mvp marks the minimum first release (on a target, as for OpenCode in the matrix, it means loaded by the tool but not yet used by a model session).
This page was written by a language model, from the project's specification and the notes of the workspaces that motivated it, and reviewed by a person. It is also meant to be read by language models: every diagram carries a prose description, the metadata is structured, and a plain-text version lives at llms.txt.