spex-init¶
Provenance¶
- Source:
.spec/spexcode/spec-cli/footprint/spex-init/spec.md - Source SHA-256:
35622bd7a7d52d6ca94a26683473b84edb936c57ad97e2b8b99de4ec05a20857
spex init [targetDir] (default: cwd) bootstraps a fresh repo into SpexCode. Adoption is data, not
code: every prompt/contract the command plants is a real spec.md shipped as a template file and
copied — no prompt string is ever embedded in the CLI source. The seed is therefore edited the same
way any spec is: by editing the template files, not the code.
What it plants, both resolved from the CLI package's OWN location via import.meta.url (so init works
when the package is installed outside the dogfood repo — never a hardcoded repo path):
- The seed spec tree —
templates/spec/*copied into<dir>/.spec/: a rootprojectnode plus a default.pluginsof dev-flow plugins, each carrying asurfacefield (thesystemcontractcoreflat + the auxiliary contracts under theprompts/shelf, thecommandpresets undercommands/, theskillplugins underskills/, andcore's lifecyclehookchildren), a projection of the dogfood.pluginsnode so a fresh adopt ships the current set. That default.pluginsis the default preset; with--preset <name>a named non-default package undertemplates/presets/<name>/would be copied in on top — cumulative — though no non-default tier ships today. The spexcode-only plugins live only in the dogfood.plugins, never in the template, so they are never seeded. [[init-preset]] owns which sets exist; this command owns the copy. - The git hooks —
templates/hooks/*(the main-guard + footprint-surgery pre-commit, the footprint-refresh post-checkout/post-merge anchors ([[commit-surgery]]), and the session-stamp prepare-commit-msg) copied into the target's resolved common hooks dir. This is the one canonical hook source:scripts/install-hooks.sh(the monorepo'snpm run hooks) installs the very same files, so the two paths can't drift (see [[main-guard]]). They ship inside the package so a relocated install still carries them. - A starter
spexcode.json—templates/spexcode.jsoncopied to<dir>/spexcode.json. Without it an adopter inherits SpexCode's own [[spec-lint]] defaults, whosegovernedRootsname this repo's dirs; absent in the adopter's tree, lint would silently govern nothing and read falsely-clean. The starter shipsgovernedRoots: ["."]— the zero-config safe default:.governs the whole project, but only git-tracked source (so node_modules/build/nested worktrees never count) minus tests, so a fresh repo just works and a mature one can still curate explicit roots. The planted file also carries the CHOSENharnessesset (next paragraph) and seeds an ordinary [[launcher-select]] launcher for each SELECTED harness (from the template's per-harness pool,sessions.defaultLauncher= the first). Interactive harnesses seed their plain command, preserving the tool's normal permission model; auth wrappers and automatic-permission flags remain explicit user or host-local launcher definitions for those adapters. The independentopencode-headlessadapter is the deliberate exception: its runnable non-interactive form isopencode --auto, so that exact command is its seed rather than a plain command that would reopen the TUI. Thus session-create works out of the box without seeding launchers for tools the adopter never picked. The same starter explicitly plantsdashboard.showHeadlessLaunchers: false, [[launcher-visibility]]'s portable default. The template is also the one numeric-default source for theuploadstransfer policy; its portable values can be committed as-is or locally overridden through the normalspexcode.local.jsonoverlay, never through an upload-specific config file. Adoption also records the root checkout's current branch asmainBranch: this is the one moment detection is authoritative. Later ordinarygit switchoperations cannot redefine a feature branch as trunk. A re-init preserves an explicit value and fills a missing one without changing the surrounding config.
What init prints is TRUE of what it planted. The success message and the next-steps read the
governedRoots value back from the just-planted (or pre-existing) file and interpolate it — never a string
literal restated in the code, which is how the message once claimed a ["src"] starter while the template
seeded ["."] (the first-minute lie a real field adoption hit). Harness-artifact reporting follows the same
rule: materialize returns a receipt of the contract, shim, skill/agent, plugin, and trust artifacts its selected
adapters actually asserted, and init renders that receipt. A Claude-only init therefore cannot claim AGENTS,
Codex shims, or Codex trust; a Codex-only init cannot claim CLAUDE or Claude shims.
The seeded .spec/ tree and spexcode.json are project source of truth, so init names them as files to add
and commit. Generated harness files such as .codex/, .claude/, and AGENTS.md are machine-local and
remain untracked. Until the project data is tracked, the existing spex spec lint gate reports an integrity
error rather than treating the untracked seed as a clean graph; it gives the ordinary Git repair command and
does no separate adoption workflow.
Adoption asks no footprint question. The retired --render vote is gone: materialized artifacts are
never tracked
([[residence]]), so init's own materialize covers a host-TRACKED contract file with the clean/smudge
filter on the spot — clean status, no "mystery M", no decision hint — and hides wholly-ours artifacts in
the per-clone exclude without touching the host's .gitignore. A lingering render/private field in a
pre-existing config is ignored with a loud non-fatal notice; nothing about it is ever fatal to adoption.
The harness delivery choice is REQUIRED, up front. --harness <id[,id]|plugin:<folder>> names which
harnesses [[harness-select]] delivers into; init stamps it into spexcode.json as the persistent harnesses
field (an explicit --harness on a re-init restamps that field of an existing config; the same write also
fills a missing stable mainBranch, preserving every other field). A pre-existing explicit field satisfies
the requirement without the flag. Neither → init aborts
BEFORE writing anything, like the git precondition — there is deliberately no default set, because with many
registered harnesses "deliver to all" would litter the adopter's tree and global tool configs with artifacts
for CLIs they never installed. An ILLEGAL set (unknown id, plugin paired with a native, plugin with no
landing folder, empty list) fails just as loud, up front — never a soft "materialize skipped" warning.
A git work tree is a precondition, checked first. SpexCode is git-backed — git is the version
database and the hooks live in .git — so a non-git target would leave a half-state: specs on disk but
no history, no hooks, no sessions. init therefore rejects a non-git target before writing anything,
with one actionable error pointing at git init. It deliberately does not run git init itself:
creating a repo is a side effect beyond init's remit (a subdir, a dir not meant as a repo root), and the
repair is one command. When mainBranch is not already explicit, the root checkout must name a branch;
detached adoption fails loud with the repair instead of silently stamping a guessed trunk.
All of those adoption Git queries use [[git-exec]]'s resolved executable for their inherited PATH, so the
same selected Git binary serves the precondition, common-hooks lookup and branch read without each child
repeating PATH resolution.
Adoption is additive and preserves user ownership. An existing <dir>/.spec aborts the spec phase with
a warning. A user-owned hook is never executed as a probe and never overwritten. SpexCode-owned hook
snapshots carry a managed header that proves ownership, so re-init atomically refreshes that snapshot to the
current protocol. This is necessary
when a protocol moves work between hooks: leaving an old SpexCode pre-commit beside new arm/consume hooks
would keep judging HEAD first and block the repair before the candidate gate can see it. A modified or
unknown hook remains the user's and is left byte-for-byte untouched; the canonical pre-commit detects that
collision statically and retains the old HEAD lint rather than silently removing local coverage. On success
init prints what it installed, refreshed, and preserved, then the next steps — install the packages, edit
project/spec.md, run the backend, confirm spex lint is clean.