guide¶
Provenance¶
- Source:
.spec/spexcode/spec-cli/guide/spec.md - Source SHA-256:
06b292c21c1c2603b35d318a990e1d0042573688b1dc1d7c26602baa39ed2a2c
spex guide is SpexCode's reference surface as a command, not buried docs. It serves the human and
the agent from one verb, picked by an optional topic:
- no topic → the human SETUP workflow. The model it teaches is install once, then let an agent
drive — one global install (
npm i -g spexcode, the [[packaging]] contract) serves every project. It requires Node >= 22. Each adopted repo runs its ownspex servefrom that repo's cwd and publishes its endpoint into the current user's host registry; one host-levelspex dashboardserves the shared gateway/UI, continuously discovers backends that are already running or start later, exposes/projectsfor global switching and management, and scopes each project's dashboard under/p/:id/. There is no per-project UI process or API/UI port pairing. Each step names the real seam, not internals: cwd picks the repo a backend serves, backend--portavoids listen collisions, andspexcode.jsongoverns lint's layout. The source-checkout path (repo-rootnpm link,npm run api, and the Vite/HMRnpm run web) stays a contributor footnote, never the installed-user headline: teaching the maintainer's path as the install was exactly the drift the packaging node's arrival made stale. The adopt step represents every supported harness, privileging none: itsspex init --harnessexample lists the full built-in registry (the prose says to drop what you don't use — any one id or comma-separated subset is valid, required with no default). A registry-derived docs assertion (docs-quickstart.test.ts) holds this page and both READMEs' Quick start to exactly that set, so the example can neither regress to one privileged harness nor silently drift from the registry. spec/eval→ the agent-facing FILE-FORMAT manual. The whole detail of the two authored artifacts — spec.md (frontmatter, body, the rules lint enforces) and eval.md (the scenario schema, how loss is measured and filed) — so an agent looks the format up on demand instead of reverse-engineering it. Compact always-on prompts point here for their operational detail:evalcarries a bug fix's same-scenario A/B sequence (old-commit failing reading, verified-tree commit, then passing reading anchored to that commit), whilespeccarries comment altitude and the shared-checkout mid-merge recovery rule. The eval page is prescriptive about evidence: step-unfolding evidence carries a step-map — named steps on the evidence's own axis, emitted by the run that produced it, never eyeballed off the artefact. A step name is a short human label for its moment, never a metadata channel — the run's identity, verdict, and extent all have canonical homes (the scenario'stest:field, the reading's verdict, the evidence itself), and the manual says so, because the one free-text field that rides with the evidence is exactly where an emitter author is tempted to smuggle provenance (a real adopter bakedrunner start: <file> :: <case title>into every step and turned the dashboard's step ruler into noise). The concept is tool-neutral (Playwright is one emitter);--timelineis axis-tagged (a video'stime, a transcript'sline, a still sequence'sframe, a data export'sindex— legacytMsmaps read astime), and a filing's axis must match an attached evidence entry's kind.
It is prescriptive about the READING as well as the evidence, and for the same reason: the ways a
measurement lies are not obvious from the schema, so the manual is where they get named. Two are stated
because both fail silently and both were reached by measuring rather than reasoning. A universal expected
is vacuously true over an empty set, so the reading reports its population as N of N rather than a bare
verdict — and the denominator is counted off a surface that can disagree with the numerator, because a ratio
whose halves share one source only asserts that what was selected was selected. And a browser reading goes
through the rendered box, never computed style: an ancestor CSS transform leaves computed style at the
authored size while the screen shows the scaled one, so a plausible-looking geometric reading passes while
measuring something that is not on screen. That second rule is also the honest reason a geometric claim
ships with its --image — a rect can be computed wrong where legibility is human-judgeable.
Neither rule is enforced anywhere, and the manual says so instead of implying a gate. The escalation these
two sit inside is real: a precondition sentence depends on the author remembering it, a printed denominator
depends on a reader noticing it, and only a refusal at filing time depends on nobody — but refusing needs a
population the schema does not carry, so this page prescribes and does not pretend to bind. The escalation's
own terminus is stated too, because it is cheaper than every rung above it: a claim restated over something
the product cannot make empty has no population to report, arrange, or get wrong. "Every active node's name
is readable" needs activity someone must arrange; "the rendered size never falls below the authored size" is
a property of the viewport, true of a one-node graph. The ladder's goal was never a rule that gets
remembered — it is a rule that cannot be broken.
The always-on system prompt is the clue that the format exists; this manual carries the detail. An
unknown topic fails loud, naming every registered topic and never a silent setup dump — and that list
is DERIVED from the topic registry rather than re-typed beside it. A hand-kept enumeration of the topics is
the same wrong-population defect the reading rules above describe, one layer down, and it had already
happened: files and web were registered as real pages while the unknown-topic error still named four.
Nothing about the shorter list looked wrong, because an enumeration cannot report what it is missing.
- settings → the agent-facing RUNTIME-SETTINGS manual. SpexCode's own settings are self-documenting
through this same primitive rather than a new mechanism: spex guide settings prints every spexcode.json
/ spexcode.local.json field (launchers, dashboard icon, upload transfer policy, deterministic lint policy,
doctor health budgets, layout overrides) with a working
example — crucially teaching which of the two files each belongs in: the committed, portable
spexcode.json vs. the gitignored, host-specific spexcode.local.json (absolute launcher paths,
secrets). Deterministic lint policy and the doctor's altitude/breadth health budgets remain separate
owners. Its launcher table mirrors [[launcher-select]]: interactive clean-init profiles use ordinary
commands that preserve the harness permission model, while the independent [[opencode-headless]] profile
is the one explicit opencode --auto seed required by its terminal-free runtime. Other automatic-permission
commands remain authored profiles, never silent defaults. The sessions section names the worker cap's default, precedence,
and the important meaning of "active": it counts compute slots, not total session rows, so human-waiting
sessions do not block launches.
It mirrors the project Config type in layout.ts (the single source of truth — the manual
restates the type's own field comments, it does not invent fields, and it omits fields the type keeps
only as retired compat for the loud notice). Its uploads section names every transfer number, says that
templates/spexcode.json is the sole numeric-default source, and teaches the ordinary local-over-portable
override rather than inventing an upload settings command. It also names [[identity-config]]'s one separate host-level
gateway icon at SPEXCODE_HOME/config.json, so an agent can configure SpexCode
for a user who doesn't know the schema by editing the JSON directly. There is deliberately no imperative
spex config set — the guide + a direct edit is the whole surface.
- footprint → the residence MODEL manual. The [[residence]] model as an operator's handbook: the
four artifact kinds and their fixed track facts (materialized artifacts never tracked), the migration
recipe for a legacy untracked spec tree (git add .spec spexcode.json with the pushed-history WARN),
how the [[content-filter]] behaves on a host-tracked contract file, and the forgetting-law guarantees
(any-order switching, spex uninstall as the empty policy).
- files → the agent-to-human PATH handoff. The three session files verbs, the live and host-local
meaning of a posted absolute path, and the dashboard's click-time preview/download. It states the safe
preview types and 2 MiB refusal ceiling rather than implying every file can render, and distinguishes this
from [[file-attach]] so an agent does not upload an artifact merely to hand it back.
- web → the agent-to-human LOOPBACK handoff. The three session web verbs, their explicit-port
loopback URL requirement, and the dashboard's click-time same-origin proxy. It makes clear that posting
neither starts nor copies the service, so an agent keeps its local server alive and uses relative web asset
paths rather than mistaking a live page for an uploaded artifact.
Every page describes the PRESENT model. A retired knob is absent from the active field list; when silently ignoring a stale authored field would leave its owner ambiguous, one concise migration note names the live replacement and the runtime diagnosis repeats that repair. History remains git's job, not a static retirement catalogue.
guide is the SKILL layer of the help journey ([[cli-surface]]): help answers "what do I type",
guide answers "how do I work". Command usage — the map (spex help) and each command's own page
(spex help <cmd> / spex <cmd> --help) — lives in help.ts; every guide page footers back to those
layers and the help layers name the guide's topics, so neither surface dead-ends. The --help
interception's safety contract is unchanged: it prints and EXITS before the verb runs — the flag
used to be an ignored no-op that fell through to the verb's side effect, so probing a STREAMING verb
(spex session watch stream --help started a watch that never exits) or a MUTATING one (spex session new --help
created a stray session) detonated the very command the user was only asking about. A help probe must
never fire a side effect, and the help it prints must read its own caveats honestly: a verb that blocks
forever (watch) says so and points at the one-shot alternative.
The narration is static help text (the spirit of printHelp and spex init's next-steps), now living in
its own guide.ts module rather than the shared cli.ts hub — not a planted .spec template the way
[[spex-init]]'s contracts are, and not routed through the dashboard's i18n catalogs ([[settings]]),
which translate the browser UI, not operator-facing CLI output. guide tells you the loop and the
formats; [[spex-init]] performs the first step of it.
This node's stake in cli.ts is now a thin dispatch (process.argv[3] → guideText, plus guideTopics()
for the unknown-topic list so that list cannot drift from the registry); the content lives
in guide.ts. cli.ts is the shared command hub every verb routes through, so a sibling verb's churn
there is that feature's, not guide's drift.