Skip to content

Porting .claude/ tooling to .factory/ (Droid) — executable checklist

Porting the .claude/ tooling to .factory/ — executable checklist

Section titled “Porting the .claude/ tooling to .factory/ — executable checklist”

Approved by Chris 2026-09-30 (“yes port the skills”), as Factory-native copies, not symlinks. Rationale and what it unblocks: 2026-09-30-factory-missions-catalog.md §1.1. This file is the step-by-step; read it first and work top to bottom.

.factory/ is now a generated tree, not a hand-maintained one. That is the one structural change from the plan below, and it is what makes Step 4’s parity guard enforceable rather than aspirational:

  • scripts/lib/claude-to-factory.mjs — the single shared, deterministic transform (substitution table, protected fragments, the disable-model-invocation / Claude-only / model-invocable classifications, the droid frontmatter table).
  • scripts/port-claude-to-factory.mjs — writes .factory/skills/ + .factory/droids/ and prunes anything it would not generate. --check exits 1 instead of writing.
  • scripts/test/skills-parity.test.mjs — regenerates in memory and diffs. Picked up by scripts/gate.sh through the existing node --test scripts/test/*.test.mjs glob; no wiring change was needed. Edit the .claude/ original and re-run the port script; a hand edit inside .factory/ fails the gate.

Four deviations from the steps below, each deliberate:

  1. 21 skills, not 20. .claude/skills/bh-deploy-mac-dev.md was a bare .md at the skills root, so neither harness ever discovered it — it has been git mvd to bh-deploy-mac-dev/SKILL.md, which fixes it for Claude Code too.
  2. 9 skills get disable-model-invocation: true, not 8 — bh-cloud-offload spends on remote compute and was already flagged in the table below. Every skill is now classified into exactly one of DISABLE_MODEL_INVOCATION / MODEL_INVOCABLE, and the test fails on any new skill that is in neither, so the decision cannot be skipped by default.
  3. Four skills carry a “Claude Code only” banner (Chris’s call when the gap surfaced): bh-investigation-loop, bh-overnight-audit-loop, bh-overnight-swarm, bh-cloud-offload are built on Workflow/ScheduleWakeup/RemoteTrigger, which Droid has no equivalent for. They are ported for completeness with a banner naming the specific missing primitive. The rewrite onto Task fan-out + Loop/CreateAutomation is still open work.
  4. Two stale claims in the .claude/ sources were fixed rather than propagated, since the port would otherwise have copied them into a second tree: bh-area-background and bh-investigation-loop both asserted .claude/skills/ is gitignored. It is tracked (the repo .gitignore re-includes it), so both sections were rewritten around “uncommitted”, which is the hazard that actually still exists.

Step 5’s items 3-7 are interactive and still need a human in a fresh session (/skills, /droids). Everything scriptable is green: 32/32 node tests pass.

Droid discovers project skills in .factory/skills/, .agents/skills/, .agent/skills/ and personal ~/.factory/skills/; custom droids in .factory/droids/. This repo’s tooling lives in .claude/, so every bh-* skill and agent is invisible to every Droid session — including Mission workers, which inherit skills and custom droids but cannot see these. Claude Code sessions keep using .claude/ unchanged; nothing there gets deleted or moved.

Use the installed port-claude-code-artifact-to-droid skill for the general mapping rules; this doc carries the repo-specific decisions on top of it.

Step 0 — already done (2026-09-30, pre-clear)

Section titled “Step 0 — already done (2026-09-30, pre-clear)”
  • .gitignore: .factory/ needs no re-include block (unlike .claude/, it is not in ~/.gitignore_global, verified with git check-ignore). Added /.factory/settings.local.json only.

Copy .claude/skills/<name>/ → .factory/skills/<name>/, including supporting files, then apply the body edits in Step 3.

Skill Size Supporting files disable-model-invocation
bh-add-content 7.1 KB — no
bh-appstore-release 24.7 KB — yes (uploads a store build)
bh-area-background 8.9 KB — no
bh-cloud-offload 4.3 KB — yes (spends on remote compute)
bh-deploy 18.9 KB — yes (installs to device / publishes web demo)
bh-determinism-repin 6.5 KB — yes (a re-pin must be a human call)
bh-dev-chunk 6.5 KB — no
bh-diagnose-render-timing 5.7 KB — no
bh-economy-sim 5.9 KB — no
bh-godot-engine-upgrade 10.3 KB — yes (swaps the engine under the project)
bh-investigation-loop 12.3 KB 4 no
bh-live-debug-deploy 5.3 KB — yes (device deploy)
bh-overnight-audit-loop 11.3 KB 1 yes (long unattended loop)
bh-overnight-swarm 11.7 KB — yes (long unattended loop)
bh-polish-audit 11.3 KB — no
bh-sdd-concurrent 10.0 KB — no
bh-telemetry 16.7 KB — no
bh-tunable-batch 4.6 KB — no
bh-vfx-iteration 11.1 KB — no
bh-write-test 6.1 KB — no

disable-model-invocation: true keeps the skill user-invocable (/bh-deploy) while preventing Droid — or a Mission worker running at --auto high — from choosing it on its own. That is the mechanism standing between an autonomous run and a live App Store upload.

Also present: .claude/skills/bh-deploy-mac-dev.md, a bare .md at the skills root. It is not in a directory with a SKILL.md, so it is not a skill in either harness — it is a reference doc bh-deploy points at. Decide: either give it a proper bh-deploy-mac-dev/SKILL.md home in .factory/skills/, or leave it where it is and keep the cross-reference. Do not silently drop it.

.claude/agents/<name>.md → .factory/droids/<name>.md.

Agent Claude model Droid model Claude tools Droid tools
bh-implementer opus claude-opus-5-5 (unset = all) omit the field entirely
bh-reviewer opus claude-opus-5-5 Read, Grep, Glob, Bash ["Read","Grep","Glob","Execute"]
bh-visual-qa sonnet claude-sonnet-5-5 (unset = all) omit the field entirely
bh-feedback-triage sonnet claude-sonnet-5-5 Read, Grep, Glob, Bash ["Read","Grep","Glob","Execute"]

Load-time rules that will fail validation if ignored:

  • TodoWrite and Skill are always granted; never list them.
  • ExitSpecMode and GenerateDroid cannot be enabled — listing either is a hard error.
  • tools: all is rejected; omit the field to allow everything.
  • Tool IDs are case-sensitive.
  • A droid that pins a model blocked by policy silently falls back to the parent’s model, so confirm the pin took effect rather than assuming it.

/droids has an Import from Claude Code flow that does this mechanically, but it maps a model family to “the first available model in that family”, which can land on claude-opus-4-5 rather than 5.5. Either hand-write the four files, or import and then fix every model: line by hand.

bh-visual-qa needs image input. Verify the pinned model actually has vision before trusting it — glm-5.3 does not, which is exactly how this class of gap goes unnoticed.

The instruction bodies are Claude-Code-flavoured. Swap, in every ported file:

Find Replace with
Bash tool references Execute
Write tool references Create
Task/subagent dispatch by subagent_type same tool name, but .factory/droids/ paths
model: "opus" / "sonnet" in dispatch examples claude-opus-5-5 / claude-sonnet-5-5
.claude/agents/…, .claude/skills/… paths .factory/droids/…, .factory/skills/…
~/.claude/… personal paths ~/.factory/… where the artifact was ported; leave alone where it genuinely still lives under ~/.claude (memory stores do)
CLAUDE_CODE_* env vars drop; Droid has no equivalent (subagent models are set in subagentModelSettings)
“Claude” as the agent’s name “Droid”

Leave alone: CLAUDE.md itself (Droid reads it natively), memory-store paths under ~/.claude/projects/… (still the live convention), and every game/engine/Godot detail.

Three repo-specific notes worth carrying into the ported bodies while you are in there:

  • Commit messages go via git commit -F <file>, never chained heredocs, and a transient .git/index.lock can abort a chain (memory bullet-heaven-droid-tooling-gotchas).
  • A worktree-isolated session refuses complex shell (loops, sed -i '' chains, heredoc pipes) — write a script file and run it plainly.
  • Mission/droid exec autonomy: --auto medium blocks git push; --auto high does not.

Two real copies of 20 documents will diverge. Add scripts/test/skills-parity.test.mjs:

  • For each .factory/skills/<n>/SKILL.md, find the .claude/skills/<n>/SKILL.md twin.
  • Compare the bodies (everything after the frontmatter fence), normalised for the Step 3 substitution table so an intended swap is not a diff.
  • Fail with the list of drifted skills; a skill present in one tree and absent from the other is also a failure.
  • Same for .claude/agents/ ↔ .factory/droids/.

Wire it into node --test scripts/test/*.test.mjs (the directory form fails MODULE_NOT_FOUND on Node 22 — use the glob). This is what converts a silent fork into a red gate.

Step 5 — verification (none of this is optional)

Section titled “Step 5 — verification (none of this is optional)”
  1. node --test scripts/test/*.test.mjs — parity test green.
  2. node --test scripts/test/docsite-frontmatter.test.mjs — still green (this doc is wired).
  3. Start a fresh Droid session in the repo (skills are discovered at session start).
  4. /skills → Effective tab: all 20 bh-* present and Enabled, none Invalid. Check the winning source is Project for each.
  5. /droids: all 4 listed; open each detail view and confirm the resolved model is the 5.5 pin, not a fallback, and that the tools list matches the table in Step 2.
  6. Ask for one read-only skill by name and confirm it loads, e.g. /bh-write-test.
  7. Confirm a disable-model-invocation skill still works when you invoke it but is not offered spontaneously.
  8. git status — only .factory/**, .gitignore, scripts/test/skills-parity.test.mjs and docs changed. Nothing under .claude/ modified.

One commit, message shaped like the repo’s existing ones:

chore(droid): port .claude skills + agents to .factory (Droid-native copies)

Include Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>. Pull/rebase first — Toby shares main — and push immediately after the merge rather than at session end, per the standing rule in CLAUDE.md.

The port unblocks Missions. Next step after this lands is 2026-09-30-factory-missions-catalog.md §1.2: scripts/mission-qa.sh, then M3 (tunable batch) as the smoke-test mission.