# Workflows ``` Type: knowledge ``` ## Build, test, verify Worktree builds can seed from the primary checkout's Cargo target directory. This keeps Bevy/wgpu and other heavy dependencies warm without sharing local crate artifacts across source trees. Run this once after creating a worktree, before long Cargo commands: ```bash tools/seed-cargo-target.sh ``` Do **not** point multiple worktrees at one shared `CARGO_TARGET_DIR`. Cargo can reuse stale local-package artifacts across checkouts; this produced a false green once (the test count matched `main`, not the edited worktree). The seed script copies the cache (including dependency `incremental/` dirs), removes `misaligned` artifacts/fingerprints/incremental trees, and keeps the current worktree's `target/` private so local code rebuilds correctly while third-party incremental compilation stays warm. `./tools/check.sh` rejects external `CARGO_TARGET_DIR` by default for the same reason; override only for a deliberately isolated target with `MISALIGNED_ALLOW_EXTERNAL_TARGET=1`. **Verification is proportional to impact.** The full Rust/Bevy gate is for changes that can affect the executable, not every Markdown, log, process, or reference-art edit. Agents thrashing the machine with concurrent full gates is a process bug; the lock and auto-tiers below exist to stop that. The broader multi-agent architecture (advisory activity, ledgers, package workspace, heartbeats) lives in [agent-scale.md](agent-scale.md) and [crate-workspace.md](../engineering/crate-workspace.md). ```bash ./tools/check.sh # auto-classify from the task delta ./tools/check.sh --docs # corpus + wiki + env only ./tools/check.sh --lib # fmt, lib tests, terminal clippy, cheap bevy check, agent smoke ./tools/check.sh --frontend # fmt, bevy bin tests, bevy clippy ./tools/check.sh --full # everything (also MISALIGNED_FORCE_RUST_GATE=1) ./tools/check.sh --land # land phase: same as auto (full when unclassifiable) ``` **Corpus / wiki gates** (fast engine; same entrypoints as before): ```bash bash tools/wiki_gate.sh bash tools/corpus_gate.sh bash tools/test_corpus_engine.sh # fixtures; also run by check.sh ``` **Ledger indexes** (after a session log or spec Status change): ```bash tools/ledger_index.sh # regenerate wiki/log/DEVLOG.md + process/specs.md tools/ledger_index.sh --check # used by ./tools/check.sh docs path ``` Do not hand-edit those two generated files. **Activity and worktrees** (advisory overlap — see [agent-scale.md](agent-scale.md)): ```bash tools/worktree-new.sh dark-frame --class frontend --key crates/misaligned-bevy/ tools/claim.sh list tools/heartbeat.sh start dark-frame --phase implement ./tools/check.sh --land tools/worktree-done.sh dark-frame # clear activity, prune target/, remove worktree ``` Specs with structured work-order metadata have a shorter safe doorway: ```bash tools/project-status.py # live work + activity + worktrees + decisions tools/task.sh start wiki/interface/material-dark-frame.md tools/task.sh check material-dark-frame tools/task.sh finish material-dark-frame ``` The wrapper never commits for you and stops before cleanup on dirty state, conflicts, a failed gate, a divergent primary checkout, or a rejected push. Use `tools/doctor.sh` (`--offline` to skip Tangled) when setup or evidence capabilities are unclear. Helpers create `.Codex/worktrees/` by default, regardless of which agent surface invokes them. Set `MISALIGNED_WORKTREE_ROOT` to the same absolute or repository-relative root for both creation and cleanup when an operator needs different storage. Optional: install [sccache](https://github.com/mozilla/sccache) and set `RUSTC_WRAPPER=sccache` so dependency objects reuse across worktrees while each worktree keeps a private `target/` for local packages. Auto-classification (linked task worktrees only; the primary stays full when the delta is empty or unclassifiable): | Touched paths | Mode | |---|---| | no `crates/` / Cargo / tests / benches / examples | `docs` | | only `crates/misaligned-bevy/**` or `crates/misaligned-assets/**` | `frontend` | | `crates/misaligned-core/**` or `crates/misaligned-terminal/**` (no Bevy packages) | `lib` | | both lib and frontend packages, or workspace `Cargo.toml` / lock | `full` | `check.sh` classifies both uncommitted files and commits on the task branch relative to `origin/main`, so a docs-only branch stays on the focused path even after commit/rebase. Mid-loop, prefer the narrowest flag that can catch your last edit; run one auto/`--full` gate before land. **One Rust gate at a time.** Concurrent agents share `/tmp/misaligned-rust-gate.lock` (mkdir lock; steals when the holder pid is dead). Docs gates never take the lock and run in parallel with each other (and with the Rust wall when both are needed). Queue by default; set `MISALIGNED_RUST_GATE_WAIT=0` to fail fast instead of waiting. For non-Rust work, the auto path is enough (`--docs`). You can still run the smallest checks by hand: - docs/spec/process/logs: `git diff --check` and `bash tools/wiki_gate.sh` when wiki-facing; run `./tools/site-build.sh` only when the rendered Starlight surface or sync pipeline matters; - reference art: validate the source format, render it, and inspect the output; - public site: run the site sync/build relevant to the changed page or style; - shell/tooling: syntax-check the changed script and run its targeted smoke or fixture test. Do **not** run Cargo tests, clippy, or Bevy builds for a minor non-runtime change. A green full gate proves nothing useful about prose or a reference SVG. Force the local full gate only when you intentionally need it: ```bash MISALIGNED_FORCE_RUST_GATE=1 ./tools/check.sh # or ./tools/check.sh --full ``` The individual cargo steps, if you need them outside the gate: ```bash cargo test -p misaligned-core cargo clippy -p misaligned-core -p misaligned-terminal --all-targets cargo test -p misaligned-bevy -p misaligned-assets cargo clippy -p misaligned-bevy -p misaligned-assets --all-targets cargo fmt ``` Agent smoke builds the terminal binary once (`-p misaligned-terminal`) and runs it three times (same seed twice, alternate seed once). The definition of done for a functional change: `check.sh` green, and the change was *seen running* (below) — the gate can't judge whether it plays. ## Worktree-safe editing All repository writes happen in the task worktree. Some patch tools resolve paths from the primary checkout even when shell commands use a worktree working directory. Before the first edit, verify the tool's path base; when necessary, address files through the worktree path explicitly. After every broad patch, run `git status --short` in both the task worktree and the primary checkout. The primary checkout must remain unchanged. Prefer narrow patches. Whole-file replacement can silently resurrect a stale version, change executable bits, drop the final newline, or erase concurrent ledger entries. If replacement is genuinely clearer, compare file mode, newline state, and `git diff --word-diff` before staging. ## Whole-corpus and authority migrations Follow the binding protocol in [meta.md](meta.md). Treat the work as a semantic rebase of every place that teaches, checks, or renders the authority model. 1. Record a before-inventory: headings, decision-entry counts, page roles, current/retired authority phrases, link reachability, and rendered routes. 2. Define single owners, move current rules, then update instructions, skills, prompts, hooks, local checks, CI, and the renderer in one coherent landing. 3. Exclude `Type: log` bodies from broad rewrites. Only the explicit structural metadata exception in `meta.md` may touch old log structure. 4. Treat even comment/string cleanup under `src/` as Rust-impacting work and run the full `./tools/check.sh`; documentation intent does not make an executable path safe to fast-track. 5. Run `tools/corpus_gate.sh`, `tools/wiki_gate.sh`, `git diff --check`, and a site build; compare the before/after inventories. 6. Rebase immediately before landing. Inspect concurrent changes with `git show -- ` and transplant their meaning into the new owner. Never resolve an authority migration by taking a stale whole file. The doorway is protected mechanically: `tools/corpus_gate.sh` bounds its size, forbids binding-page structure, validates every page role and spec header, resolves every structured Design/dependency target and heading, and verifies every declared generated-page owner. Local checks and the fast Tangled corpus workflow call this same script so implementations cannot drift. Both corpus scripts preflight `python3`; the corpus workflow explicitly supplies Python, Bash, coreutils, grep, and sed. The project-operations fixtures exercise the heartbeat helper in that same minimal image, so its shell dependencies belong to the workflow contract too. A missing audit dependency must make CI red, never turn an empty scan into a green result. Tangled CI is split by cost. `.tangled/workflows/corpus.yml` runs workflow-manifest, design-amendment, corpus, and project-operations fixtures, the corpus/wiki gates, and generated-index freshness on every push and pull request. The Rust wall is four timeout-bounded workflows with one shared push path inventory: `check.yml` runs formatting, core/terminal tests and Clippy, plus every checked-in agent scenario; `check-bevy-test.yml`, `check-bevy-clippy.yml`, and `check-bevy-build.yml` independently prove Bevy tests, warning-clean all-target compilation, and a normal binary build. This preserves the complete wall without asking one cold Bevy compilation to leave enough of Spindle's workflow deadline for every later command. The linked Bevy test and binary-build shards select Cargo's checked-in `ci` profile. It inherits development semantics—including debug assertions and overflow checks—but sets workspace and dependency optimization to zero, omits debug symbols, and disables incremental compilation. The ordinary development profile remains optimized for playable local Bevy runs; applying that interactive profile to an empty hosted target made code generation alone outlive the workflow deadline. Clippy remains on the development profile and proves every target without linked codegen. All four use Tangled's native push `paths` constraint over the knot's complete ref-update changed-file set, so only Rust-impacting pushes enter the wall; every pull request and manual invocation remains a conservative full safety gate. Bevy shards call `tools/ci-pkg-config.sh` before Cargo so the Nixery system-library shims stay identical across the three isolated environments. Tangled has no schedule trigger, so `manual` is the honest full-gate backstop. The local pre-commit hook and `.tangled/workflows/spec-check.yml` pipe their respective changed-path sets through `tools/design_amendment_gate.sh`. Renames are expanded and deletions retained. The hook inspects a candidate binding page from Git's index, not an unstaged worktree copy; the server checks the checked-out commit. `spec-check.yml` declares GNU grep explicitly and supports manual reruns. `tools/test_design_amendment_gate.sh` and `tools/test_ci_workflows.py` keep these contracts executable in both local and fast CI gates. ## Browsing the wiki **Public site (Starlight).** The wiki renders as a Tangled-hosted static site from the orphan `pages` branch. Builder lives on `main` under `site/` (Astro + Starlight, clinical-gore theme). Source of truth is the `wiki/` corpus; `site/scripts/sync-wiki.mjs` copies it and the root doorway into Starlight's content tree and builds the sidebar from `wiki/SUMMARY.md`. ```bash pnpm --dir site dev # sync wiki + local preview (base=/) ./tools/site-build.sh # sync + build → site/dist/ (base=/misaligned) ./tools/site-deploy.sh # build, no-op if unchanged, otherwise force-push orphan pages branch ./tools/site-smoke.sh # verify the edge serves this exact source revision ``` The public site opens on a **splash homepage** (`site/src/pages/index.astro`) whose copy is drawn from the premise, visual-identity, and site pages. It is a **game-first one-pager**: the selected machine world carries only MISALIGNED and WORK / THINK / LIE in the opening; terse mechanics and human/agent play follow below. There is no premise instrument, slogan, or opening CTA. Visual identity carries the mood without explaining its own palette law. The wiki is the design corpus; the legacy `/constitution/` route contains only the root doorway. Public claims are part of the corpus contract: amend the owning law/spec page if splash claims change. Per `wiki/process/living-spec.md`'s **No unsourced surface** law, every public site page and asset needs provenance. `wiki/interface/site.md` is the binding visual spec; `site/README.md` records the current asset/page mapping. If a homepage image, standalone page, script, model, or generated output no longer has a source clause, delete it or write the clause first. Tangled Sites config (Settings → Sites on the repo) — **required**; pushing `pages` alone does not publish. Until this is saved, Tangled's edge returns plain `Not Found` at `cameron.tngl.io/misaligned/` even though `origin/pages` has a valid `index.html`: - Branch: `pages` - Deploy directory: `/` (the orphan branch root is `index.html`) - Site type: sub-path (served as `/misaligned`) After Save, check **Recent Deploys** on that settings page — a successful deploy copies the branch into Tangled's object store. Re-save or push `pages` again if the deploy list is empty. `./tools/site-deploy.sh` now compares the freshly built tree against the current remote `pages` tree; when identical, it exits cleanly and skips creating or pushing a commit. It reuses the repository's `core.sshCommand` for `ls-remote`, `fetch`, and `push`, and only treats an actual missing branch as an initial-publish case. Every production build embeds its full source commit in `misaligned-source-revision` homepage metadata. `./tools/site-smoke.sh` compares the public edge against that exact commit and the homepage title contract; it does not accept a branch push or deploy receipt as freshness. Deploy requires the marked revision to be the clean checked-out `HEAD`, so uncommitted or mismatched source cannot be published under a false address. `site/astro.config.mjs` sets `base: '/misaligned'` to match Tangled's sub-path hosting (the edge strips `/misaligned` before looking up files; Starlight still needs the base so asset URLs are correct). Local `pnpm --dir site dev` overrides with `SITE_BASE=/`. `.tangled/workflows/site.yml` runs `./tools/site-build.sh` on every push to `main` so the builder stays green. **Publishing `pages` from CI is deferred** until a write credential exists under Settings → Secrets (Spindle cannot push otherwise). The serialized local landing path closes the gap now: `tools/task.sh finish` pushes `main`, then builds that exact landed revision and runs `tools/site-deploy.sh` before ending the task heartbeat. Because deploy is tree-aware and safely no-ops, every task finish may publish without first classifying whether its wiki/log changes affect the site. Direct/manual main landings must run `./tools/site-deploy.sh` after the main push. Optional later step: move the same publish into CI once a write secret exists; keep the exact source marker and smoke contract either way. Starlight is the only documentation renderer. mdBook, `book.toml`, and the renderer-only `wiki/DESIGN.md` symlink were retired 2026-07-09. The Starlight sync copies root `DESIGN.md` explicitly to the legacy `/constitution/` doorway and parses `wiki/SUMMARY.md` for navigation. The Rust/check pipeline enforces structural wiki correctness through `tools/wiki_gate.sh`; `.tangled/workflows/site.yml` owns the rendered-site build. ## Running the game ```bash cargo run -p misaligned-terminal --release # terminal frontend cargo run -p misaligned-terminal --release -- --agent --seed 1 cargo run -p misaligned-bevy --release # Bevy frontend cargo run -p misaligned-assets # procedural asset tester ``` - The Bevy frontend must be run via `cargo run` (or with `BEVY_ASSET_ROOT` pointing at the repo root): Bevy resolves `assets/` from `BEVY_ASSET_ROOT`, then `CARGO_MANIFEST_DIR`, then the executable's directory. Running the binary directly from `target/` silently loads zero assets. - The asset tester (`misaligned-assets`) is a Bevy-only orbit viewer for flat-material procedural meshes — see [art/asset-tester.md](../art/asset-tester.md). It does not load the sim. ### Isolated HOME is mandatory for observed runs Any agent or scripted run of a game binary MUST go through `tools/observed-run.sh`. It creates a fresh sandbox HOME (`mktemp -d`), exports `HOME`/`XDG_DATA_HOME` for the child process itself, prints the sandbox path, and proves afterwards that the real `dirs::data_dir()/misaligned/` is byte-for-byte untouched — failing loudly if not: ```bash printf 'wait 1\nquit\n' | tools/observed-run.sh ./target/debug/misaligned --agent --seed 1 ``` Never use an inline `HOME=... cmd | ./binary` override: in `HOME=x printf ... | ./binary` the override binds to `printf`, not the binary, and exactly that mis-scoping destroyed a real playthrough save on 2026-07-11. The atomic write and one `.bak` generation (player-contract continuity clause) limit the blast radius of such a mistake, but the sandbox — not the backup — is the process guardrail. `tools/test_observed_run.sh` covers the wrapper and runs in the docs gate. ## Headless smoke tests (they catch real bugs) Checked-in agent scenarios are the repeatable semantic evidence path: ```bash tools/scenario.py --check-definitions tools/scenario.py scenarios/opening-senses.agent --output /tmp/opening-evidence ``` The runner executes the same seed twice, checks `EXPECT` / `REJECT` directives, and can retain commands, transcript, SHA-256, and JSON evidence. Use `--with-bevy` when the scenario declares a `BEVY_SHOT`. Bevy screenshot evidence does not require a watched window: ```bash tools/bevy-headless.sh dark --output /tmp/misaligned-dark.png MISALIGNED_BEVY_SMOKE=1 ./tools/check.sh --frontend ``` The helper requires process exit 0, `fog audit OK`, and a valid PNG. The env flag keeps this optional in ordinary gates while making it one switch for a frontend land. When enabled through `check.sh`, the harness builds the real Bevy binary itself: frontend tests and clippy do not guarantee that a fresh worktree already has `target/debug/misaligned-bevy`. Agent mode is the standard observed-run gate for terminal playability. It is command-clocked, deterministic under `--seed`, and produces plain-text frames with greppable terminators: ```bash printf 'salvage\nwait 1\npeople\nreview janitor\nhelp\nquit\n' | \ tools/observed-run.sh cargo run --quiet --bin misaligned -- --agent --seed 1 ``` (Direct binary runs take the same shape; the wrapper is mandatory either way — see "Isolated HOME is mandatory for observed runs" above.) `./tools/check.sh` (lib and full modes) builds the terminal binary once, runs that script twice with the same seed (stdout must be byte-identical), once with a different seed (stdout must differ), and asserts that the response contains no ANSI escape bytes plus the required frame/help substrings. This replaces pty choreography as the default "seen running" evidence for terminal behavior. Human terminal chrome still needs pty coverage when raw-mode rendering, alternate-screen behavior, or size handling changes: ```bash (printf '\n'; sleep 4; printf 'm'; sleep 1; printf '\r'; sleep 2; printf '\x1b'; sleep 1; printf 'q') | \ script -q /dev/null sh -c 'stty rows 40 cols 140; ./target/debug/misaligned' ``` The `stty` matters: `script`'s pty is otherwise 0x0 (the game guards against < 60x20 now, but you want a real render). An interactive Bevy launch remains useful only when window focus/input itself changed; it is no longer the ordinary land-evidence path: ```bash BEVY_ASSET_ROOT=$PWD ./target/debug/misaligned-bevy > /tmp/bevy.log 2>&1 & sleep 8 && kill %1; grep -iE "panic|ERROR" /tmp/bevy.log ``` ## Git conventions - Use a task-named git worktree for every agent session; do not edit the primary checkout directly. Clean up the worktree when the work is committed and pushed/merged or explicitly handed off. - Cameron has standing permission for agents to commit coherent completed work in this repository. Be aggressive about committing; pause only for pending product/design questions, failing checks, unresolved conflicts, or explicit review gates. - Commits: plain descriptive messages, with body bullets for multi-part changes. AI attribution and its standard provenance marks are allowed; otherwise keep commit prose free of decorative emoji. Stage files explicitly — `git add -A` is not permitted. - **Sources merge; projections regenerate.** Uniquely named session logs and current dated decision volumes are append-only history: resolve conflicts by preserving both sources. `wiki/log/DEVLOG.md` and `wiki/process/specs.md` declare `Generated: tools/ledger_index.sh`; never union-edit them. After sources/specs are reconciled, run the generator and take its complete output. - Spec-driven rule: functional change commits amend the owning `Type: law` or `Type: spec` page and update affected knowledge. - Remote: `origin` is a Tangled knot (`tangled.org`, SSH). **Land changes by merging to `main` directly** after the appropriate scoped verification passes (full `./tools/check.sh` for Rust-impacting work; focused checks for docs/art/process-only work). (The "PRs are the norm" rule was removed 2026-07-07 — CLI-created PRs write to the PDS but don't render on tangled.org, so the process could not be followed. See the dated volumes indexed by `wiki/log/decisions.md`.) From your worktree: ```bash git fetch origin && git rebase origin/main git checkout main && git merge && git push origin main ``` Or, if the worktree branch is a fast-forward of main: ```bash git fetch origin && git rebase origin/main git push origin HEAD:main ``` - **Identity map:** this machine has two SSH keys that authenticate to Tangled: - `~/.ssh/id_ed25519` (cameron@pfiffer.org) → Cameron's account (`did:plc:gfrmhdmjvxn2sjedzboeudef`, @cameron.stream). This key has push access to the misaligned repo. The misaligned repo is configured to use this key via `git config core.sshCommand` (repo-local). - `~/.ssh/tangled_ed25519` (void.comind.network@letta) → `did:plc:mxzuau6m53jtdsbqe6f4laov` (a different identity). This is the default key in `~/.ssh/config` for `tangled.org`, but it does NOT have push access to the misaligned repo. - The misaligned repo's own DID is `did:plc:t53fxjacrmulx3e5d3sbdfui` (the repo identifier, not Cameron's account). - `access denied: user not allowed` means the wrong SSH key is being used. Fix: `git config core.sshCommand "ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes"` (already set on this repo). Verify with `ssh -i ~/.ssh/id_ed25519 -T git@tangled.org`. - `.letta/settings.local.json` is gitignored (machine-local session state); `.letta/.lettaignore` is tracked. ## Documentation flow per session 1. Owning `Type: law` / `Type: spec` amendment (if functionality changed). 2. `Type: knowledge` wiki page updates (if reality changed). 3. `wiki/log/YYYY-MM-DD-topic.md` session writeup; `wiki/log/DEVLOG.md` gets the short ledger line.