# Craftar > Open-source CLI (TypeScript/Node) that treats an AI-coding harness — rules, agents, commands, skills, hooks, MCP servers — as source in a Git repository (the Forge), composes it per team from reusable ingredients, and emits it in the native format of each AI coder (Claude Code, Kiro, Kimi, Codex, Cursor, Warp, AGENTS.md) into every workspace, with a lockfile for drift detection and a registry of workspaces. This file is the plain-text companion of the Craftar page (index.html), written for language models and other automated readers. Same content, no layout; diagrams are described in prose — verbatim from the accessible description of each figure — and the two whose topology carries the argument are also given as Mermaid graphs. ## Terminology - Harness: everything that shapes how coding agents behave in a repository — rules, agents, commands, skills, hooks, MCP servers. - Forge: one Git repository holding every ingredient, recipe and profile. Versioned; workspaces point at it by local path today — referencing a branch, tag or SHA is planned. It is never a Git remote of any team repository. - Ingredient: smallest reusable unit — a rule, agent, command, skill, hook, MCP server, local service or template. A folder with an `ingredient.yaml` declaring type, targets, inclusion mode and globs. - Recipe: named set of ingredients with parameters. Recipes `extend` others; stack recipes declare a `slot` so two stacks for the same role (e.g. two front-end frameworks) cannot coexist. - Profile: one team — its recipes, repositories, template feeds, project-management tool, internal platform, and which AI coders that team uses. - Workspace: a local folder (one repo or a folder of repos) with a `craftar.yaml` declaring profile, recipe add/remove and targets, plus a git-ignored `craftar.local.yaml` for per-developer overrides. - Target: an AI coder to emit for — `claude-code`, `kiro`, `kimi`, `codex`, `cursor`, `warp`, `agents-md`. A per-target `capabilities.yaml` is planned; today the Kiro emitter warns and skips what it cannot express — a fallback is planned. - Lockfile (`craftar.lock`): Forge SHA, the resolved profile name, and a normalized content hash for every generated file. The only authority on what Craftar owns in a workspace. - Registry (`~/.craftar/registry.json`): every workspace on the machine with path, profile, last sync and status. Will back `craftar ls` and the local dashboard — planned. ## Problem statement 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 A harness is a roster of agents (a reviewer per architecture, a docs author, an executor) plus the rules they are bound to, workflow commands, hooks and MCP servers. Across T teams it exists as T copies in K formats (`.claude/`, `.kiro/`, `AGENTS.md`, `.cursor/`), each holding the same A agents. A change costs T × K manual edits, with no record of what was generated from what. Requires: one source; emission per target; a record per file. ```mermaid graph LR H["harness on the developer's machine · 12 rules · 7 agents · 9 commands · skills · hooks · 4 MCP servers"] H -->|copy and translate by hand| A["team A · Claude Code · .claude/ · two months old"] H -->|copy and translate by hand| B["team B · Kimi · AGENTS.md · one stale agent"] H -->|copy and translate by hand| C["team C · Kiro · .kiro/ · one agent missing"] H -->|copy and translate by hand| D["team D · Cursor · .cursor/ · version 3 of 5"] ``` State today (diagram): a single harness — about 12 rules, 7 agents, 9 commands, skills, hooks and 4 MCP servers — exists only as a folder on the developer's machine. It is copied and translated by hand into four team workspaces: team A uses Claude Code, team B uses Kimi via `AGENTS.md`, team C uses Kiro, team D uses Cursor. Because the copies are manual, they diverge: team A's copy is two months old, team B has one stale agent, team C is missing one agent, team D carries version 3 of 5. No edge in this graph is recorded anywhere, so nothing can report which copy is current. ### P2 — One developer, many workspaces, separate remotes One machine, one workspace per team. Each workspace groups that team's repositories cloned from that team's own Git host; no repository is common to all of them. The harness in each workspace was copied in by hand at a different time. Nothing on the machine records which version each workspace carries, or whether a file in `.claude/` is still the copy or has been edited in place. Requires: a channel that is no team repository's remote; a lock per workspace; a registry. State today (diagram): one developer machine holds four workspaces, `workspace-1` to `workspace-4`, one per team. Each workspace contains that team's repositories cloned from that team's own Git remote (four different hosts), so no repository is shared across workspaces. Each workspace also contains a hand-copied `.claude/` harness of unknown version — v3?, v2?, v5?, v4+? — copied at different times or edited in place, with no lockfile. The harness moves from one workspace to the next by hand, one copy at a time, which is how the versions drift apart. The machine has no registry of workspaces, no shared source for the harness, and no way to verify versions. ## Failure modes (observed in earlier generators and hand-synchronised workspaces) - FM-1 Regeneration deletes hand-written agents and rules. Cause: the generator treats the whole `.claude/` folder as its output. - FM-2 Every file reports as hand-edited on every run; the team runs permanently on `--force`. Cause: drift guard hashes raw bytes while Git rewrites LF→CRLF on Windows checkout. - FM-3 A deleted rule keeps loading into sessions. Cause: the generator only writes and never removes outputs of ingredients that left the set. - FM-4 Second-target mirrors (steering, agent JSON, commands) diverge from their source. Cause: only rules were generated; the rest was kept as manual mirrors. - FM-5 An architecture rule and its reviewer agent contradict each other. Cause: two files stating the same constraints, edited independently. - FM-6 A model pin or MCP version breaks in several places at once. Cause: volatile values inlined in multiple files instead of declared once. - FM-7 A workspace-wide language policy contradicted by many files. Cause: language treated as a property of the workspace instead of each ingredient. - FM-8 Real tokens in a generated settings file. Cause: generated settings carried values instead of environment-variable references. FM-1 to FM-5 stem from P1; FM-6 to FM-8 from maintaining P2 by hand. ## Model: source, resolution, emission Craftar borrows the shape of a build system. Ingredients are source; recipes and profiles compose them; `craftar sync` resolves the composition for one workspace and emits target-specific outputs, recording every output in the lockfile. Generated files are committed to the workspace, so a team that only uses one AI coder never needs Craftar installed. Layer resolution, weakest to strongest: `base` recipe → stack recipes → team profile → `craftar.yaml` (workspace, committed) → `craftar.local.yaml` (one developer, git-ignored). Same-named ingredients resolve to the strongest layer; stack recipes sharing a `slot` are mutually exclusive; `{{params}}` are substituted from the winning layer. `craftar explain ` prints the layer that decided each output. Pipeline of `craftar sync`: 1. Read `craftar.yaml`; load the Forge (a local path today; a pinned git ref is planned). 2. Resolve layers, exclusive slots and params into the final ingredient set. 3. Emit open standards verbatim: `AGENTS.md`, Agent Skills (`SKILL.md`), `mcp.json`. 4. Emit tool-specific formats (inclusion modes, subagents, hooks) through Craftar's own emitters and post-processors; delegating further formats to rulesync is planned. 5. Diff against `craftar.lock`; classify each file as new / update / unchanged / adopt / drift / collision / orphan; write, remove orphans, refresh the lock. P1 resolved (diagram): the Forge is one Git repository containing ingredients (rules, agents, commands, skills, hooks, MCP servers, services), recipes (`base`, `backend-*`, `frontend-*`, `aws-local`, `pm-*`) and one profile per team. Running `craftar sync` in a workspace loads the Forge (a pinned ref is planned), resolves layers, emits files for each of the workspace's targets and writes `craftar.lock`. Team A emits to `.claude/` with 7 agents; team B swaps the frontend recipe and has three workspaces, one AI coder each — `.opencode/`, `.kimi/` and `.aider/`; team C pins the Forge at tag `v2.3`, emits `.kiro/` and is two commits behind; team D adds Cursor through its local file and has one drifted file. Every edge is generated and recorded in the lock. ```mermaid graph LR F["Forge · one Git repository · ingredients, recipes, profiles"] --> CR["craftar · will read the Forge at the ref pinned in each craftar.yaml, emits per target"] CR --> W1["workspace-1 · .claude/ · craftar.lock 7c1e9a2 · up to date"] CR --> W2["workspace-2 · AGENTS.md · craftar.lock 7c1e9a2 · up to date"] CR --> W3["workspace-3 · .kiro/ · craftar.lock 5b0d3f1 · behind, pinned at tag v2.3"] CR --> W4["workspace-4 · .claude/ and .cursor/ · craftar.lock 7c1e9a2 · drift"] R1["team 1 Git remote"] -->|git clone| W1 R2["team 2 Git remote"] -->|git clone| W2 R3["team 3 Git remote"] -->|git clone| W3 R4["team 4 Git remote"] -->|git clone| W4 REG["~/.craftar/registry.json · read by craftar ls"] -.-> W1 & W2 & W3 & W4 ``` P2 resolved (diagram): same developer machine and same four workspaces as the problem statement, `workspace-1` to `workspace-4`, each still cloning its team's repositories from that team's own remote. Now a fifth repository, the Forge, is referenced by each workspace's `craftar.yaml` and is not a remote of any team repository. Between them sits `craftar` itself: it reads the Forge and emits each workspace's harness, so nothing flows from the Forge to a workspace except through it. Each workspace carries a generated harness with the same base ingredients and a `craftar.lock` naming the Forge commit that produced it (`7c1e9a2` for three of them, `5b0d3f1` for the pinned one). The registry at `~/.craftar/registry.json` lists all four, and `craftar ls` will report per workspace: up to date, behind, or drift. ## Forge → Workspace by stack A Forge is shaped by the technology stack it serves. Below are three examples showing how ingredients in the Forge become files in the workspace — both inside the AI coder folder (`.claude/`, `.kiro/`) and outside it (`docs/`, `local-stack/`). ### dotnet-enterprise — .NET backend teams following Clean Architecture Forge structure: - ingredients/rules/: dotnet-clean-architecture, dotnet-clean-code, dotnet-minimal-api, dotnet-entity-framework, dotnet-security - ingredients/agents/: dotnet-arch-reviewer, dotnet-api-reviewer, dotnet-ef-reviewer, dotnet-security-reviewer - ingredients/commands/: create-api-endpoint - ingredients/docs/: architecture-template, adr-template, tech-debt-template - ingredients/services/: localstack-sqs - recipes/: dotnet-clean-arch.yaml - profiles/: team-dotnet/ Workspace output (Claude Code target): - .claude/rules/: dotnet-clean-architecture.md, dotnet-clean-code.md, dotnet-minimal-api.md, dotnet-entity-framework.md, dotnet-security.md - .claude/agents/: dotnet-arch-reviewer.md, dotnet-api-reviewer.md, dotnet-ef-reviewer.md, dotnet-security-reviewer.md - .claude/commands/: create-api-endpoint.md - docs/: architecture/, adr/, tech-debt/ (generated structure) - local-stack/: docker-compose.yaml - src/ (client code, untouched) Workspace output (Kiro target): - .kiro/steering/: *.md (same rules) - .kiro/agents/: *.json (agents as JSON) - .kiro/commands/: *.md - docs/, local-stack/ (same as Claude Code — tool-agnostic) ### node-fullstack — Node.js teams using TypeScript Forge structure: - ingredients/rules/: node-typescript-strict, node-express-patterns, node-testing-jest, node-eslint-prettier, node-security-headers - ingredients/agents/: node-arch-reviewer, node-api-reviewer, node-test-reviewer, node-deps-reviewer - ingredients/commands/: create-express-route - ingredients/docs/: architecture-template, runbook-template - ingredients/mcp/: npm-audit - recipes/: node-express-ts.yaml - profiles/: team-node/ Workspace output (Claude Code target): - .claude/rules/, .claude/agents/, .claude/commands/ - docs/: architecture/, runbooks/ - .mcp.json (npm-audit MCP) Workspace output (Kimi target): - AGENTS.md (rules + agents combined) - docs/ (same structure) - mcp.json ### iac-multicloud — Platform teams managing infrastructure as code Forge structure: - ingredients/rules/: aws-cloudformation, aws-cdk-typescript, azure-arm-bicep, kubernetes-helm, terraform-modules - ingredients/agents/: aws-yaml-reviewer, azure-iac-reviewer, k8s-manifest-reviewer, terraform-reviewer - ingredients/commands/: create-aws-stack, create-helm-chart - ingredients/docs/: runbook-template - ingredients/services/: localstack-aws - recipes/: iac-aws.yaml, iac-kubernetes.yaml - profiles/: team-platform/ Workspace output (Claude Code target): - .claude/rules/, .claude/agents/, .claude/commands/ - docs/runbooks/ - local-stack/docker-compose.yaml (LocalStack) Workspace output (Codex target): - AGENTS.md (rules + agents) - codex.toml (hooks block) - docs/, local-stack/ (same) ### What syncs where Craftar generates files inside the AI coder folder (`.claude/`, `.kiro/`, `.cursor/`) and outside it (`docs/`, `local-stack/`, `AGENTS.md`). Files outside are tool-agnostic — the same `docs/architecture/` structure serves Claude Code, Kiro and Cursor users alike. Client-owned folders like `src/` or `infra/` are never touched by `craftar sync`. ## Capabilities (F1–F12; "working" = runs today and is covered by tests; "mvp" = first release; "planned" = designed, not built) - F1 Forge → workspace sync [working, mvp] (P1, P2): `craftar sync [--dry-run] [--check] [--overwrite-drift]`, `diff`, `status` (per file: new, update, unchanged, adopt, drift, collision, orphan, orphan-drift); sync prints what it wrote, removed and skipped, file by file. Planned: `--forge-ref`, `--targets`, `update`, following a Forge branch or pinning a tag. - F2 Multi-target emission [working, mvp] (P1): `craftar import --from claude-code`; Craftar's own emitters for Claude Code, Kiro (byte-identical to a hand-built workspace, including agent JSON and command mirrors) and AGENTS.md; post-processors for gaps (e.g. Kiro agent `resources`). Planned: `craftar targets`, rulesync for further tool-specific formats. - F3 Layers and stack slots [resolution working] (P1): layer resolution, exclusive slots, params, `explain`; `craftar.local.yaml` (git-ignored) overrides the workspace for one developer, its arrays replacing the workspace's. Planned: `add recipe`, `remove recipe`, `recipes`, `ingredients`. - F4 Workspace bootstrap (P2): planned: `craftar init [--profile --targets --yes]`. - F5 PM tool and internal platform per profile (P1): `integrations.pm.kind ∈ {github, azure-devops, notion, jira}`; `integrations.idp` registers the platform MCP; tokens via `authEnv`. - F6 Local services per workspace (P1): `service` ingredient with docker-compose fragment; planned: `craftar services up|down|status`. - F7 Project templates (P1): planned: `craftar templates install`; generated `/create-project` command. - F8 Drift and reverse contribution [detection working] (P2): `status` reports drift; never overwritten without `--overwrite-drift`. Planned: `drift show|discard|promote`. - F9 CI [--check working] (P2): `craftar sync --check`. Planned: GitHub Action / Azure Pipelines task; pre-commit hook. - F10 Registry and dashboard (P2): `craftar ls` over the registry, `craftar ui` — planned; today's `craftar ls` lists the recipes and ingredients resolved for one workspace. - F11 Craftar as an MCP server (P1, P2): planned: `craftar mcp`; Claude Code plugin, Agent Plugin, Warp workflows as thin shells. - F12 Diagnostics (P2): planned: `craftar doctor`. ## Invariants (each derived from a failure mode; these are the constraints Craftar is built against — the two marked (planned) are design commitments, not yet enforced by the code) - Never touch a file not in the lockfile — unknown files are preserved; one at a path the Forge would write is reported as a collision (FM-1). - Never hash raw bytes — normalize LF, UTF-8, strip BOM; compare JSON structurally. Claude Code and AGENTS.md outputs keep the EOL and BOM of the file they replace; Kiro output is always CRLF without BOM (FM-2). - Never leave an orphan on disk (FM-3). - Never require a manual mirror — everything a second target needs is generated (FM-4). - Never split a rule from its reviewer — one ingredient, two outputs (FM-5) (planned; today the rule and its reviewer are separate ingredients (rule/, agent/-reviewer) grouped by a stack recipe). - Never inline a volatile value — declared once in the profile, injected as params (FM-6). - Never translate — language is an attribute of the ingredient (FM-7). - Never store a secret — `authEnv` references, `settings.example.json`, `doctor` checks (FM-8) (planned; `craftar import` already refuses ingredients holding secret-like values). ## Targets and capability coverage This is the design target, not current coverage: today only the Claude Code, Kiro and AGENTS.md emitters exist. A per-target `capabilities.yaml` is planned. Nothing may be dropped silently: today the Kiro emitter warns and skips what it cannot express; closing the remaining gaps is planned. Claude Code: full. Kiro: rules with always/fileMatch/manual inclusion, commands, custom agents, hooks partial, skills, MCP. Kimi: AGENTS.md and skills; commands and subagents via fallback; no hooks. Codex: AGENTS.md, `[hooks]` block, skills, MCP; commands via fallback; no subagents. Cursor: `.cursor/rules/*.mdc` with globs/always, Agent Plugin skills, MCP; commands via fallback. Warp: workflows and MCP. AGENTS.md: plain rules, skills, `mcp.json`. In this design, anything a target cannot express is reported and skipped or given a declared fallback. Kimi and Codex rows are pending verification. ## Status Phase 0 (in progress): Forge schema; layer resolver with exclusive slots and params; `sync`, `diff`, `status`, `explain`, `ls`, lockfile; `import --from claude-code`; emitters for Claude Code, Kiro and AGENTS.md; an oracle suite against a private hand-built workspace plus a synthetic suite in CI on Linux and Windows (byte-identical reproduction of a hand-built Claude Code + Kiro workspace; second sync is a no-op; Forge edits reach both targets; manual edits surface as drift and survive; removed ingredients disappear; unmanaged files untouched). Later phases: team profiles and `init`, remote Forge, PM/platform MCP, `doctor`, registry (1); local services, templates, drift promote, `docs validate` (2); CI actions, `craftar ui` (3); `craftar mcp`, plugins, Warp, Kimi/Codex/Cursor emitters, documentation (4). Stack: TypeScript/Node CLI (commander, zod, yaml) with its own emitters for Claude Code, Kiro and AGENTS.md; rulesync planned as the conversion dependency for further formats. License: MIT. Open questions: Forge as separate repo vs monorepo; what Kimi and Codex read beyond AGENTS.md; multiple Forges per workspace. Trying it: install with `npm install -g craftar` (Node ≥ 22), or prefix any command with `npx`. Then, inside an existing Claude Code workspace, run `craftar import --from claude-code --forge --profile --write-config` to obtain a Forge and a `craftar.yaml`, and `craftar diff` to see what a sync would change. Nothing in `.claude/` or `.kiro/` is rewritten until `sync`.