"Onboard a software project — new or existing — as a dark-factory orchestrator target. Use whenever the user wants to 'factory-init' a repo, 'set up' or 'wire up' a project for the orchestrator, 'make this repo a dark-factory target', 'register a new project', or 'onboard <repo> into the factory' — or points at a directory and asks to get it ready for autonomous development, even if they don't say 'factory-init'. It confirms dark-factory is installed (installing it is out of scope), ensures a git repo, picks a hyphen-free project_id and a free escalation port, writes the per-project config, and registers the project with fused-memory reconciliation, then routes to review-briefing→review (existing code) or a goals discussion→/prd batch (greenfield), optionally adding a supervised systemd unit. NOT for: installing dark-factory itself (a separate skill), running the orchestrator (/orchestrate), writing a single PRD (/prd), creating a review briefing (/review-briefing), or unblocking a task (/unblock)."
Recursos
3Instal·la
npx skillscat add leodearden/dark-factory/factory-init Instal·la des del registre de SkillsCat.
Factory Init
Turn a repository — greenfield or one with an existing codebase — into a first-class dark-factory orchestrator target: pick its identity and port, write its config, register it with reconciliation, and hand off to the skill that makes sense for its state. The goal is that when you finish, the user can run /orchestrate against the project and it Just Works, with reconciliation and the dashboard recognising it.
Installing dark-factory itself is out of scope. This skill assumes a working dark-factory (services up, fused-memory live) and only adds a project to it. If dark-factory isn't installed, stop and say so — a separate skill will own that.
What "done" looks like
Hold this end-state in mind; every stage moves toward it:
- dark-factory is present and its backing services are healthy.
- The target is a git repo (the orchestrator branches task worktrees from
main— no repo, no worktrees). - A canonical
project_idis chosen — lowercase, no hyphens — and pinned where the directory name would otherwise disagree with it. - A unique escalation port is chosen and recorded.
- The target repo contains
dark-factory-orchestrator.yaml,.mcp.json,.envrc, a.gitignorecovering orchestrator scratch dirs, a.jcodemunch.jsoncindexer config, a.claude/settings.jsonwith theBASH_MAX_TIMEOUT_MSenv (escalation-watcher waits need it), and (where needed) aCLAUDE.mdproject_id pin. - The project root is registered in
DASHBOARD_KNOWN_PROJECT_ROOTS(reconciliation, restarted + verified — before any task is queued) and added to thejcodemunch-watcher--reposlist (re-indexed). - The right next step is launched: existing code →
/spawn /review-briefingthen/spawn /reviewthen offer/spawn /prd; greenfield → a goals discussion, then/spawn /prdrun 1–5× serially to queue the first task batch.
Operating principles
- This skill mutates a live, shared system. Writing config files into the target repo is reversible — just do it. But restarting fused-memory and spawning sessions are not things to do silently: confirm before them. The user has authorised "full auto with confirm" — honour the confirm.
- Derive, don't guess, and show your work. The
project_idand the escalation port are both mechanical. Compute them and show the evidence (the derivation rule, the table of ports already in use) so the user can sanity-check rather than trust. - Register before you queue — this one is load-bearing. fused-memory's write path accepts any
project_id, but the reconciliation loop hard-rejects ids it doesn't know and does not quarantine them. A single task status-change on an unregistered or mistyped id poisons the recon event buffer permanently. So registration + restart (Stage 6) must complete before any/prdrun files a task (Stage 7). Getting the order wrong is not a cosmetic bug.
Stage 0 — Preflight: is dark-factory installed and healthy?
Installing dark-factory is out of scope; you are only confirming it's there to build on.
- Locate the dark-factory root. Default
/home/leo/src/dark-factory; honour$DARK_FACTORY_ROOTif set; or derive it from this skill's own path (…/skills/factory-init→ repo root). Confirmorchestrator/andscripts/fused-memory.service.templateexist. - Check the backing services without starting or restarting anything:
- FalkorDB on
6379and Qdrant on6333— probe withss -ltnornc. - fused-memory on
8002— probe its/health. curl is broken on this host (libcurl leaks even underenv -i); use pythonurllib.requestinstead.
- FalkorDB on
- If dark-factory or any service is missing → stop. Tell the user dark-factory isn't fully installed, that installing it is out of scope for this skill, and don't attempt to install or start anything. Point them at the (future) install skill.
Stage 1 — Identify the target project
The target is the path the user named (an argument like ../my-solar-challenge) or, failing that, the current working directory. Resolve it to an absolute path and confirm it with the user. It must not be the dark-factory repo itself.
Stage 2 — Ensure a git repo
git -C <target> rev-parse --is-inside-work-tree. If it isn't a repo, offer git init plus an initial commit of whatever's there. The orchestrator creates each task's worktree by branching from main, so a repo with at least one commit on main is a hard prerequisite.
Stage 3 — Choose the project_id (no hyphens)
The canonical id is the directory basename, lowercased, with hyphens replaced by underscores — this is exactly what resolve_project_id does, so matching it avoids a write/read mismatch (my-solar-challenge → my_solar_challenge).
- Collision check. The id must not already belong to another project. Scan the existing project configs and
DASHBOARD_KNOWN_PROJECT_ROOTS(Stage 4's script surfaces these). If it's taken, pick a distinct name with the user. - Hyphen handling. When the directory name contains hyphens, the dashboard label (hyphenated) will differ from the canonical underscore id, and an interactive session left to its own devices may improvise the wrong form. Pin the canonical id in the target's
CLAUDE.md(Stage 5) so spawned sessions and MCP writes always use the underscore form. For a brand-new project, prefer a hyphen-free directory name from the outset and sidestep the whole issue.
Stage 4 — Choose an unused escalation port
All projects share the single fused-memory server on 8002; only the escalation port is unique per project. Run the bundled finder:
python3 <skill>/scripts/find_escalation_port.py --exclude-root <target>It reads the known project roots (from the live fused-memory unit and /home/leo/src/*), parses each project's escalation.port, reserves 8002 (fused-memory) and 8103 (shared reconciliation queue), checks what's actually bound, and prints an evidence table plus the lowest free port ≥ 8100. Show the table to the user and use the chosen port.
Stage 5 — Write the per-project config (in the TARGET repo)
Exact templates, the verify-command cookbook, and merge-don't-clobber guidance live in references/config-templates.md — read it before writing. In brief, create/merge in the target repo:
dark-factory-orchestrator.yaml—project_root,fused_memory{project_id, url: http://127.0.0.1:8002},escalation{queue_dir: data/escalations, port: <chosen>}, agitblock, andtest_command/lint_command/type_check_commandmatched to the detected stack. Detect the stack from the repo (pyproject → pytest/mypy/ruff; Cargo.toml → cargo; package.json → npm/eslint/tsc) and confirm the commands with the user — especially when a linter or type-checker isn't configured. Prefer a documented no-op ("true") over a command that will always fail, and say why in a comment..mcp.json—fused-memory→8002,escalation→ the chosen port. The escalation URL must matchdark-factory-orchestrator.yamlso interactive sessions and the orchestrator share one server. Addplaywrightif the project has a browser surface..envrc—export ORCH_CONFIG_PATH="<absolute path to dark-factory-orchestrator.yaml>". direnv may not be installed (don't auto-install — offer it); without direnv this file is inert and the orchestrator needs--configexplicitly..gitignore— ensure.worktrees/,.task/,.taskmaster/,data/escalations/,data/queue/, anddata/reconciliation/are ignored..task/is critical: if it ever lands onmainit contaminates every future worktree.data/queue/anddata/reconciliation/hold the managed fused-memory server's runtime SQLite state (write_queue.db, reconciliation.db, tickets.db); if tracked, that state dirties the tree on every write, tripping the startup dirty-tree-guard and poisoning warm-lane GC (task 2439). Merge into any existing.gitignorerather than overwriting.CLAUDE.md— if a project_id pin is needed (hyphenated dir), add it plus the dark-factory routing conventions (route all task ops through fused-memory MCP withproject_root=<target>; write-tag withproject_id=<id>). Merge into an existingCLAUDE.md, never clobber it..jcodemunch.jsonc— the per-project code-indexer config (languages, ignore patterns) so the orchestrator's agents get structured code retrieval over this repo. Detect the language(s) from the repo. Crucial: add the project's own heavy dirs toextra_ignore_patterns— especially a non-dotfile virtualenv likevenv/, which the global ignore list (~/.code-index/config.jsonc, which only knows.venv/) won't catch, so the indexer would otherwise crawl thousands of installed library files..claude/settings.json— merge{"env": {"BASH_MAX_TIMEOUT_MS": "3900000"}}into the target repo's project settings (create the file if absent; merge, never clobber). Every dark-factory project runs interactive escalation-watcher sessions whose canonical watcher waits use--timeout 3600; a foreground wait that long needs the Claude Code Bash-tool ceiling raised to ≥ 3660000 ms — without this setting, explicit foreground timeouts are capped at 600000 ms and an omitted timeout dies at the 2-minute default. Seeskills/escalation-watcher/SKILL.md§"Bash-tool timeout contract". Project-level settings ship with the repo so every operator inherits the requirement.mkdir -p <target>/data/escalations.
Validate before registering. From the dark-factory repo, run a status check against the new config:
cd <dark-factory> && uv run --project orchestrator orchestrator status --config <target>/dark-factory-orchestrator.yamlExpect "No tasks found." (or an empty tree) — not a config error. This confirms the YAML parses and satisfies the loader before you touch the shared service.
Stage 6 — Register with dark-factory's shared services
Two registrations. The reconciliation one is destructive (restarts a shared service) and load-bearing for correctness; the jcodemunch one is cheap and safe.
6a. Reconciliation (DESTRUCTIVE — confirm first)
Full procedure and rationale in references/recon-registration.md. The shape:
- Registration is a local setting — do NOT commit it. The committed templates (
scripts/fused-memory.service.template,scripts/dashboard.service.template) intentionally default to this repo only; the maintainer's other projects don't belong in source control (a fresh clone won't have them). - Append
,<target>to theDASHBOARD_KNOWN_PROJECT_ROOTS=line in the installed units with a surgical one-line edit —~/.config/systemd/user/fused-memory.service(governs reconciliation) and, for parity,~/.config/systemd/user/dark-factory-dashboard.service(dashboard aggregation) — thensystemctl --user daemon-reload. Do not hand-roll asedre-render: it strips this host's other roots, and if partial it leaves__UV_PATH__literal soExecStartfails. If you do need a full re-render,scripts/render_systemd_unit.pyno longer strips this host'sDASHBOARD_KNOWN_PROJECT_ROOTS(tasks 4793, 4796) — seereferences/recon-registration.mdfor the invocation. Re-runningscripts/setup-host.shis not a substitute for the surgical edit: it no longer strips the extra roots either, but it also restarts fused-memory (severing this session's MCP tools — see step 3) without the confirmation step 3 requires, and re-provisions the whole host besides. - Confirm with the user, then
systemctl --user restart fused-memory. ⚠️ This severs this session's fused-memory MCP tools — they do not reconnect. That's acceptable: nothing after this point needs MCP in this session, and the sessions you spawn in Stage 7 get fresh connections to the restarted server. - Verify: a python-urllib probe to
8002/healthreturns healthy, andjournalctl --user -u fused-memoryshows the new project in theproject_prefix_registryline. Detection is heuristic (it may not claim ambiguous prefixes likesrc/), so confirm in the log rather than assuming.
6b. jcodemunch code indexer (safe)
So the orchestrator's agents get structured code retrieval over the new repo. There's one global jcodemunch-watcher.service that watches a multi-repo --repos list; the live unit (not the single-repo template) is the source of truth for that list.
- Append
<target>to the--reposline in~/.config/systemd/user/jcodemunch-watcher.service(space-separated), thensystemctl --user daemon-reload. systemctl --user restart jcodemunch-watcher. Unlike 6a this is cheap and non-disruptive — it just re-indexes and does not sever any MCP (the user-scope jcodemunch retrieval MCP is a separate process from the watcher). No confirmation needed beyond the user already wanting jcodemunch.- Verify the project is being indexed:
journalctl --user -u jcodemunch-watcher --since '1 min ago'mentions<target>.
(The global ~/.code-index/config.jsonc and the user-scope jcodemunch MCP already exist from the dark-factory install — verify, don't recreate.)
Stage 7 — Route by code presence
Decide whether the repo already has substantive source (real modules under src//packages, not just README + config). Spawn sessions with the bundled /spawn helper (<dark-factory>/skills/spawn/spawn-claude.sh <cwd> <skip_perms> '<title>' '<prompt>', run in background — it blocks until the spawned session exits, so the background task's completion is your "done" signal). Use cwd=<target>. Pass a convention-shaped <title> per skills/spawn/SKILL.md (<role>:<project> <short-slug>, <project> being the chosen project_id — these are project-level spawns, so there's no #<task-id> segment, but <short-slug> is still included), e.g. review-briefing:<proj> context-doc, review:<proj> existing-code-audit, prd:<proj> new-feature.
Existing code:
/spawn /review-briefing(titlereview-briefing:<proj> context-doc) — buildsreview/briefing.yaml, the durable project context/reviewconsumes.- When it completes →
/spawn /review(titlereview:<proj> existing-code-audit) — surfaces the real state of the existing code and files tasks for the gaps it finds. - Then offer (don't auto-run)
/spawn /prd(titleprd:<proj> new-feature) for net-new feature work.
Greenfield (no code yet):
- Discuss with the user to get a crisp outline of the project's goals and a rough split into 1–5 PRD-sized slices.
- For each slice,
/spawn /prd "<framing of this slice>"(titleprd:<proj> <slice-slug>, replacing<slice-slug>with a short hyphenated summary of that slice, e.g.prd:acme auth-flow) serially — wait for each to finish before starting the next. Serial because PRDs share seams (the /prd skill's G4 cross-PRD ownership gate), so later PRDs reference what earlier ones established. - The result is an initial batch of queued tasks, ready for
/orchestrate.
Stage 8 — Go unattended (optional, gated, post-queueing)
Only if the user wants the project in the always-on autonomous workload — a deliberate commitment (it spends budget and changes code without a human in the loop). Full procedure in references/supervised-unit.md. Three layers:
- The unit —
orchestrator-<name>.service(modelled onscripts/orchestrator-reify.service):WorkingDirectory=<dark-factory>,ExecStartPrewaits for port 8002,ExecStart=… orchestrator run --config <target>/dark-factory-orchestrator.yaml,Wants=fused-memory,Restart=on-failure, StartLimit guard.enable --nowmakes it run. - Persistence — copy the unit into
<dark-factory>/scripts/and addcp+enablelines tosetup-host.shso a host re-provision keeps it. Commit both. - Watchdog (optional) — add
(<escalation_port>, unit)toWATCHEDinorchestrator-watchdog.pyand update its drift test. Skippable:autopilot-videoruns without it, relying onRestart=on-failure.
⚠️ Timing is load-bearing: only enable --now (and only add to the watchdog) after the project has pending tasks. An orchestrator started against an empty/all-done tree exits "No pending tasks found"; under the watchdog that becomes a 60s crash-loop. Confirm orchestrator status --config <target>/dark-factory-orchestrator.yaml shows ≥1 pending first. The watchdog skips disabled units, so installing-but-leaving-disabled before queueing is the safe fallback.
Offer anything else
Surface these as options once the core onboarding is done; don't do them unprompted:
- Commit the new config to the repo so it's tracked (worth doing — untracked config referenced by tooling is a known foot-gun).
- direnv install (
sudo apt install direnv+ shell hook) — one-time, makes.envrclive so you don't need--config. Confirm before any sudo. - A PRD project overlay at
<target>/.claude/skills/prd/project.mdif the user wants project-specific G2/G3 vocabulary for decomposition.
Pitfalls (most are in memory for a reason)
- Recon storm — register the project_id (Stage 6) before any task is queued (Stage 7); an unknown/mistyped id poisons the recon buffer for good.
- MCP death on restart — the Stage 6 restart kills this session's fused-memory tools; that's expected and fine here.
- curl is broken on this host — use python
urllibfor all HTTP probes. - Hyphen → underscore — pin the canonical id in CLAUDE.md whenever the directory name has a hyphen.
.task/contamination — must be gitignored; never let it reachmain.- No global
--configdefault — the orchestrator refuses to run without--config/ORCH_CONFIG_PATH; that's a safety feature (it once ran 12h on the wrong project). The.envrcis the ergonomic fix. - venv-in-worktree (Python) — a project editable-installed into a local
venv/won't have that env inside a task worktree (it's gitignored), so barepytest/mypyverify commands fail there. Flag this and propose a worktree-safe strategy (averify.shthat provisions a per-worktree env, oruv run) — don't ship verify commands that silently can't work under the orchestrator.