Ark
An agent harness and development workflow designed to orchestrate AI-driven programming tasks.
Ark is a small CLI (ark) plus a directory layout (.ark/) that gives an AI coding agent a stable place to think before it edits. Tasks are markdown files. Specs are markdown files. The agent fills the templates; you review the diffs.
Status. Ark is experimental. The CLI surface marked semver-stable (
ark init,ark load,ark unload,ark remove,ark upgrade,ark context) won't break across patch releases. The internalark agentnamespace is not semver-covered — its contract is with Ark's own shipped templates, not external callers.
Why Ark?
AI coding agents work better with a harness.
- Right ceremony, right task. Three tiers — quick fix, feature, deep refactor — each with the minimum process that fits.
- Reviewed, not rubber-stamped. PLAN ↔ REVIEW iteration on deep work; a VERIFY gate before every archive.
- Plain markdown, no hidden magic. Tasks and specs live in
.ark/, diffable and git-tracked. Ark writes only what it tracks; round-trippingunload→loadpreserves user-edited and user-added files losslessly. - Multi-platform. Ships first-class integrations for Claude Code, Codex, and OpenCode out of the box.
What's in this book
This book has five parts:
- Getting Started walks you through install, the first
ark init, and a complete/ark:quicktask. - Workflow is the conceptual model: tiers, lifecycle, the spec system, and how worktrees fit in.
- Reference documents every top-level CLI command and the
.ark/config.tomlschema. - Platform Integrations covers what files Ark drops where, per platform.
- Contributing is for developers extending Ark itself: workspace layout, adding a slash command, adding a platform, and the release process.
If you're new, read parts 1–2 in order. If you're looking up syntax for a flag, jump to part 3.
Inspiration
Ark builds on several prior projects:
- Trellis — the workflow shape (tiers, PRD/PLAN/VERIFY) is heavily borrowed.
- Superpowers — slash-command-driven agent UX.
- OpenSpec and spec-kit — spec-first development conventions.
- humanize — multi-agent review loops.
Installation
Prebuilt binaries ship for macOS, Linux, and Windows on every tagged release. Pick whichever channel fits your environment.
via npm
npm install -g @anekoique/ark
The npm package wraps the prebuilt binary; no Rust toolchain needed.
via Cargo
cargo install --git https://github.com/Anekoique/ark ark-cli --locked
Requires a working Rust toolchain. The crate's rust-toolchain.toml pins nightly (used for rustfmt unstable options); cargo install reads it automatically when invoked from a clone, but installs from the registry compile cleanly on stable as well.
via prebuilt binary
Download the archive for your platform from the GitHub Releases page and put ark somewhere on your PATH.
Verifying the install
ark --version
Expected output: ark <version> matching your install. If ark isn't found, check that the install path (npm's global bin, Cargo's ~/.cargo/bin, or wherever you put the prebuilt binary) is on your shell's PATH.
Updating
- npm:
npm update -g @anekoique/ark - cargo:
cargo install --git https://github.com/Anekoique/ark ark-cli --locked --force - prebuilt: download the new archive and replace the binary.
After a CLI update, run ark upgrade inside each Ark-managed project to refresh the embedded templates. See ark upgrade for the conflict-resolution policy.
Quick Start
From the root of a project you want Ark in:
ark init
On first run, ark init prompts you to pick which AI platforms to integrate (Claude Code / Codex / OpenCode).
What gets scaffolded
.ark/
├── workflow.md # the rules of the game
├── templates/ # PRD, PLAN, REVIEW, VERIFY, SPEC
├── tasks/ # active + archived tasks
├── specs/
│ ├── project/ # user-authored project conventions
│ └── features/ # feature specs promoted from deep-tier tasks
├── config.toml # [worktree] section
└── .gitignore # ignores .ark/worktrees/
.claude/ # if Claude Code selected
├── commands/ark/ # /ark:quick, /ark:design, /ark:commit
└── settings.json # SessionStart hook entry
.codex/ # if Codex selected
├── skills/ark-quick/ # ark-quick, ark-design, ark-commit skills
├── config.toml # Codex hook config
└── hooks.json # SessionStart hook entry
.opencode/ # if OpenCode selected
├── commands/ark/ # /ark:quick, /ark:design, /ark:commit
└── plugins/ark-context.ts
CLAUDE.md / AGENTS.md # managed `<!-- ARK -->` block pointing at .ark/
What's user-owned vs. Ark-owned:
.ark/specs/project/,.ark/tasks/— yours. Ark won't touch them onupgrade..ark/templates/,.ark/workflow.md,.claude/commands/ark/,.codex/skills/,.opencode/commands/ark/— Ark-owned.upgraderefreshes them..ark/config.toml— yours after first creation.upgradewill not overwrite your edits.CLAUDE.md/AGENTS.md— yours, with a managed block delimited by<!-- ARK:START -->/<!-- ARK:END -->. Edit anywhere outside the markers;upgradere-renders inside them.
Start a task
Open Claude Code (or Codex, or OpenCode) in the project and trigger a slash command:
/ark:quick fix typo in readme
/ark:design add rate-limit middleware
/ark:design --deep refactor auth layer
/ark:commit # atomically close out the current task
Each command kicks off a workflow with the matching tier. The agent walks you through writing a PRD, then a PLAN (for standard/deep), then implements, then verifies. Tasks live in .ark/tasks/<slug>/ while active and move to .ark/tasks/archive/YYYY-MM/<slug>/ on close-out.
For the full mental model, jump to Workflow → Tiers. For a complete walkthrough of one task end-to-end, see Your First Task.
What's next
- Multiple in-flight tasks? Pass
--worktreeto scaffold each task in a separate git worktree under.ark/worktrees/<branch>/. See Worktrees. - Customizing the workflow? Drop a SPEC under
.ark/specs/project/<name>/SPEC.md. The agent reads every project SPEC before starting any task.
Your First Task
Walking through a complete /ark:quick end-to-end. Quick tier is the smallest ceremony — one artifact (PRD.md), no plan, no review. We'll fix a real issue: a typo in the project README.
This guide assumes you've run ark init and the project is open in Claude Code. The flow is identical in Codex (ark-quick skill) and OpenCode (/ark:quick).
Step 1: Trigger the slash command
/ark:quick fix typo in readme
Behind the scenes, the slash command:
- Calls
ark context --scope phase --for design --format jsonto read git state and project conventions. - Asks the agent to scaffold the task:
ark agent task new --slug fix-readme-typo --title "fix typo in readme" --tier quick. - Drops a
PRD.mdtemplate into.ark/tasks/fix-readme-typo/.
Slug is derived from the title — lowercase, hyphenated, ASCII, ≤40 chars.
Step 2: Fill the PRD
The agent opens .ark/tasks/fix-readme-typo/PRD.md and fills four sections:
[**What**]
Fix the typo "agentic" → "agent" in README.md, line 12.
[**Why**]
Reader-reported. Spelling.
[**Outcome**]
- README.md line 12 reads "agent" not "agentic".
- No other text changes.
[**Related Specs**]
None — typo fix.
Quick-tier PRDs are usually 4–6 lines per section. The Outcome doubles as the verification checklist; there's no separate VERIFY phase.
Step 3: Execute
The agent advances the phase:
ark agent task execute
This transitions task.toml.phase from design → execute. Now the agent edits the README. For a typo fix, that's literally one Edit call. The agent runs whatever checks the project enforces (cargo build, pytest, etc.), confirms they pass, and reports back.
Step 4: Commit
You decide when to close out — Ark deliberately stops at the end of execute and waits.
/ark:commit
This calls ark agent task commit, which lands the staged work in a single git commit (with rollback on any failure) and flips the task to phase = committed. The directory stays at .ark/tasks/fix-readme-typo/ until you bulk-archive it later via ark archive.
What you have at the end
- One commit on the branch (the typo fix itself, plus the
task.tomlflip). - A
phase = committedtask ready forark archiveto move it into.ark/tasks/archive/<YYYY-MM>/. - An archived
PRD.mdyou can grep for later.
What about the standard and deep tiers?
The flow is the same shape but with more artifacts:
- Standard adds a
PLAN.mdbetween design and execute, plus aVERIFY.mdbetween execute and archive. - Deep does what standard does, plus a
00_REVIEW.mdpaired with00_PLAN.md. If the review verdict isn't Approved, the agent copies both to01_*and iterates. On archive, the final PLAN's## Specsection gets promoted to.ark/specs/features/<name>/SPEC.md.
For details, see Lifecycle.
Tiers
Ark has three tiers. Pick the smallest that fits.
| Tier | Trigger | Artifacts | Path through states |
|---|---|---|---|
| Quick | /ark:quick | PRD.md | design → execute → archived |
| Standard | /ark:design | PRD.md, PLAN.md, VERIFY.md | design → plan → execute → verify → archived |
| Deep | /ark:design --deep | PRD.md, NN_PLAN.md, NN_REVIEW.md, VERIFY.md, promoted SPEC.md | design → plan ⇄ review → execute → verify → archived |
The same triggers exist for every supported platform: Claude Code slash commands (/ark:quick), Codex skills (ark-quick), OpenCode commands (/ark:quick).
When to use each
quick: reversible + no new abstractions
deep: breaking / cross-cutting / new subsystem
standard: everything else
Quick is for changes you'd land without a code review in a sane workflow: typo fixes, doc edits, dependency bumps, trivial bug fixes. One markdown file, one commit, done. No PLAN, no VERIFY.
Standard is the default for feature work. The PRD captures the what and why; the PLAN elaborates how with explicit Goals (G-1, G-2, …), Constraints (C-1, …), and a Validation table that maps every Goal to at least one test. VERIFY checks the shipped code against the PLAN's Validation matrix.
Deep adds a REVIEW step between PLAN and EXECUTE. The agent (or a separate reviewer model) writes a verdict — Approved / Approved with Revisions / Rejected — and findings (R-001, …). If revisions are needed, the agent copies the artifacts to 01_PLAN.md / 01_REVIEW.md and iterates. Loop continues until verdict is Approved with zero open CRITICAL findings. On archive, the final PLAN's ## Spec section is promoted to a permanent feature SPEC under .ark/specs/features/<name>/.
Tier promotion mid-flight
If you start standard and the change turns out to be deeper than you thought, promote without losing artifacts:
ark agent task promote --to deep
Demotion (--to standard) is also supported. The PRD and any existing PLAN survive; the only thing that changes is task.toml.tier and the workflow path the slash commands take from here.
Parallel tasks
Two in-flight tasks would collide on .ark/tasks/.current. To run them side by side, pass --worktree at scaffold time:
ark agent task new --slug foo --tier deep --worktree
This creates a git worktree at .ark/worktrees/feat/foo/ (override branch with --branch-type fix|refactor|... or --branch <full>) and scaffolds the task dir inside the worktree. The parent checkout's .current is untouched. --worktree is required for deep tier.
Configure copies and post-create hooks via .ark/config.toml's [worktree] section. After the branch merges, run ark agent task worktree cleanup --slug foo from the parent to remove the directory. Archive does NOT auto-clean the worktree.
See Worktrees for the full surface.
Lifecycle
Every task moves through the same states. Quick skips most of them; standard and deep walk all five.
┌────────────┐
│ /ark:* │ slash command starts a task
└─────┬──────┘
▼
┌────────────┐
│ DESIGN │ write PRD.md — What / Why / Outcome
└─────┬──────┘
│ (quick skips plan/review/verify)
▼
┌────────────┐
│ PLAN │ write NN_PLAN.md — elaborate how
└─────┬──────┘
│ (deep only — plan review loop)
│ ┌──────────────┐
├────────►│ REVIEW │ NN_REVIEW.md
│ └──────┬───────┘
│ ◄─── rejected ─┘
▼
┌────────────┐
│ EXECUTE │ implement; update PLAN's Spec section if gaps emerge
└─────┬──────┘
▼
┌────────────┐
│ VERIFY │ single-pass gate
└─────┬──────┘ rejected → halt for user decision
▼
┌────────────┐
│ COMMIT │ atomic close: deep extracts SPEC → specs/features/<name>/;
└─────┬──────┘ single git commit; rollback on any pre-commit failure
▼
┌────────────┐
│ ARCHIVE │ bulk-move Committed tasks to tasks/archive/YYYY-MM/
└────────────┘ via top-level `ark archive`
State is recorded in task.toml.phase. Each transition is mediated by a CLI command (ark agent task plan, ... review, ... execute, ... verify, ... commit, ... archive); illegal transitions error out with IllegalPhaseTransition rather than silently corrupting state.
DESIGN — capture what & why
Purpose. Write PRD.md covering What / Why / Outcome / Related Specs. Brainstorm matches the tier — quick = none, standard = ≤3 clarifying questions, deep = thorough.
Calls.
ark context --scope phase --for design— git, project specs, feature specs index, recent archive.ark agent task new --slug <s> --title "<t>" --tier {quick|standard|deep} [--worktree]— scaffolds the task dir + PRD +task.toml.--worktreebinds the task to a fresh git worktree at.ark/worktrees/<branch>/.
Gate. PRD drafted, Outcome stated. Quick → EXECUTE; standard/deep → PLAN.
PLAN — elaborate how
Purpose. Fill NN_PLAN.md from the embedded template (Spec, Runtime, Implementation, Trade-offs, Validation). Every Goal mapped to ≥1 Validation in the Acceptance Mapping table.
Calls.
ark context --scope phase --for plan— current PRD + related feature specs (filtered to the PRD's[**Related Specs**]) + project specs.ark agent task plan— transitions DESIGN → PLAN and seeds00_PLAN.md.
Gate. PLAN complete; Acceptance Mapping fills every Goal. Standard → EXECUTE; deep → REVIEW.
Rule. ## Spec must be self-contained every iteration (deltas go in ## Log). It's copied verbatim to specs/features/<name>/SPEC.md on archive (deep tier).
REVIEW — pre-execute gate (deep only, iterative)
Purpose. Evaluate the latest NN_PLAN.md against PRD and project specs; write NN_REVIEW.md with verdict + findings. Loop until verdict is Approved with zero open CRITICAL.
Calls.
ark context --scope phase --for review— current task, latest PLAN, related feature specs, project specs.ark agent task review— transitions PLAN → REVIEW and seedsNN_REVIEW.md.
Reject (HIGH) if the latest PLAN's ## Spec references prior iterations instead of restating in full.
Iteration. Copy NN_PLAN.md and NN_REVIEW.md to the next number, bump task.toml.iteration, reset phase = "plan". The state machine is small enough that this is a hand-edit (the agent does it, but no ark agent command wraps it).
Gate. Verdict Approved, zero open CRITICAL → EXECUTE.
EXECUTE — implement
Purpose. Work through the latest PLAN's Implementation phases. If implementation reveals design gaps, update the latest PLAN's ## Spec section to reflect reality. Don't silently diverge.
Calls.
ark context --scope phase --for execute— git dirty files + current task + latest PLAN + project specs.ark agent task execute— transitions to EXECUTE.
Gate. Implementation complete; project's checks pass; code committed.
Worktree note. If the task was created with --worktree, all phase commands operate on the worktree's .ark/. cd .ark/worktrees/<branch>/ and run them there. After merging the branch, run ark agent task worktree cleanup --slug <s> [--delete-branch] from the parent to remove the dir. Archive does NOT auto-clean.
VERIFY — post-execute gate (single-pass)
Purpose. Verify the shipped code against PRD's Outcome and PLAN's Validation. Apply the higher quality bar: plan fidelity, correctness, code quality, organization, abstraction, SPEC drift.
Calls.
ark context --scope phase --for verify— current task with PRD + latest PLAN + VERIFY.md (if exists) + git state.ark agent task verify— transitions to VERIFY and seedsVERIFY.md.
Gate. Verdict Approved or Approved with Follow-ups → tell the user to run /ark:commit. Rejected → halt for user decision.
VERIFY is single-pass. Unlike REVIEW, it doesn't loop. If the verdict is rejected, you decide: create fix tasks, promote tier with ark agent task promote, accept with acknowledgement, or discard.
COMMIT — atomic close
Purpose. Land all pending work plus the closing task.toml flip in a single git commit. Deep tier additionally extracts the final PLAN's ## Spec to specs/features/<name>/SPEC.md and registers it in the features INDEX inside the same commit. A scoped rollback restores every touched file if any pre-commit step fails.
Calls.
ark agent task commit— VERIFY gate, deep-tier SPEC extract + register, single git commit covering work +task.toml+ (deep) SPEC + features INDEX. Rolls back on any pre-commit failure.
Trigger. /ark:commit. The /ark:design and /ark:quick commands deliberately stop short of commit; you decide when to close out.
ARCHIVE — preserve as memory
Purpose. Move every phase = Committed task into tasks/archive/YYYY-MM/<slug>/, where the month is derived from the task's own committed_at timestamp. Side-effect-free: no SPEC promotion, no git work — that already happened at COMMIT time.
Calls.
ark archive(top-level, manager-only) — bulk-archives every committed task.--month YYYY-MMfilters;--dry-runlists candidates without moving anything.ark agent task archive --slug <s>— single-task variant, used internally and as a manual escape hatch.
Reopen. Move the archived dir back to .ark/tasks/<slug>/ and reset phase = "design" + clear archived_at in task.toml. Refuses if a same-slug active task already exists.
Specs
Specs are the long-lived source of truth Ark reads before any task. They live under .ark/specs/ in two layers:
- Project specs (
specs/project/<name>/SPEC.md) — user-authored conventions: language style, testing requirements, architectural decisions. Ark never edits these. - Feature specs (
specs/features/<name>/SPEC.md) — promoted from deep-tier task PLANs at archive time. Ark writes these.
Each layer has its own INDEX.md. Start there.
Project specs
Project specs encode rules that apply always, across every task. They're the reason an AI agent reading your repo behaves like a senior on the team rather than a contractor seeing it for the first time.
Examples of what belongs here:
- Language conventions: "We use
anyhowfor application errors andthiserrorfor library errors." - Testing requirements: "Every public API has a doc-test. Integration tests in
tests/, not undersrc/." - Architecture: "All filesystem I/O routes through
io::PathExt. No barestd::fs::*outsideio/." - Process: "Commit messages follow Conventional Commits. PR titles must reference an issue."
The agent reads every file listed in specs/project/INDEX.md before starting any task. Keep them tight; long, vague guidance gets ignored.
To add one:
.ark/specs/project/
├── INDEX.md
└── <name>/
└── SPEC.md
Then add a row to INDEX.md's ## Index table:
| Spec | Scope |
| ---------- | -------------------------------------------------- |
| `style` | Rust style: prefer `?`, no `.unwrap()` in prod. |
| `testing` | Doc-tests + integration tests, 80%+ coverage goal. |
Feature specs
Feature specs are the output of deep-tier work. Every time a deep-tier task archives, the final PLAN's ## Spec section is extracted verbatim to specs/features/<feature>/SPEC.md and registered in specs/features/INDEX.md's managed block.
You don't write feature specs by hand. The system writes them when a deep task completes. Subsequent tasks that touch the feature read the SPEC and either conform to it or — if they need to change it — append to its [**CHANGELOG**] section.
This is how Ark accumulates institutional memory. The feature SPEC for ark-context exists because someone ran a deep-tier task to design it; the next person extending the context system reads that SPEC before drafting their PRD.
To trace a feature back to the originating task: every entry in the features INDEX has a Promoted column with the date and source slug, and the archived task dir under .ark/tasks/archive/YYYY-MM/<slug>/ carries the full PRD/PLAN/REVIEW history.
Read pattern
Different phases read different slices of the spec system:
| Phase | Reads project specs | Reads feature specs |
|---|---|---|
| design | yes (full) | yes, full unfiltered + recent archive |
| plan | yes (full) | filtered to PRD's [**Related Specs**] |
| review | yes (full) | filtered to PRD's [**Related Specs**] |
| execute | yes (full) | none — code is the source of truth at this point |
| verify | yes (full) | none — VERIFY checks the code against PRD/PLAN, not specs |
The filtering happens automatically via ark context --scope phase --for <phase>. The slash commands run that command at each phase entry point and feed the structured output to the agent.
Divergence
If a PLAN contradicts an existing feature SPEC, the REVIEW phase flags it. Either the PLAN conforms, or the PLAN explicitly updates the SPEC. There's no third option — silent drift means the next task reading the SPEC gets stale information.
When a deep-tier task modifies an existing feature SPEC (rather than promoting a new one), ark agent task archive appends a [**CHANGELOG**] entry to that SPEC and rewrites the Promoted column in the features INDEX with the latest touch date.
Project SPEC ⇆ feature SPEC tradeoffs
The two layers serve different purposes:
| Question | Project SPEC | Feature SPEC |
|---|---|---|
| Who writes it? | You. | Deep-tier task archive promotes it. |
| When is it read? | Every task, every phase. | Phase-conditional, filtered by the PRD. |
| What's it for? | Norms that apply broadly. | Specific subsystem behavior + invariants. |
| What's the lifetime? | Long-lived; you maintain it. | Long-lived; updated by future deep tasks. |
| Does it appear in the slash command flow? | Read at start of every command's design phase. | Read when the PRD references it via Related Specs. |
If you find yourself wanting to write something between these two — a project-wide convention that touches a specific feature — just put it in the feature SPEC. The project layer is for things truly orthogonal to any single feature.
Worktrees
A git worktree is a second checkout of the same repository that shares the .git directory. Multiple branches can be checked out simultaneously, each in its own directory.
Ark uses worktrees to run multiple tasks in parallel without the per-checkout .ark/ state file colliding. When you pass --worktree at scaffold time:
ark agent task new --slug rate-limit --tier deep --worktree
Ark:
- Validates the branch name via
git check-ref-format --branch. - Resolves the branch: explicit
--branch <full>wins; else<--branch-type>/<slug>(e.g.fix/rate-limit); else<config.toml's [worktree].branch_prefix>/<slug>(defaultfeat/<slug>). - Refuses if the parent's
.ark/tasks/<slug>/already exists, or if some other worktree already owns the slug, or if the target directory exists, or if the branch is already checked out elsewhere. - Runs
git worktree add -b <branch> <worktree_path> <base_branch>. - Copies any files listed in
[worktree].copy(e.g..env). - Runs any commands in
[worktree].post_create(with cwd set to the worktree). - Scaffolds the task dir inside the worktree's
.ark/tasks/<slug>/.
The parent checkout's .ark/ is never modified by a --worktree task.
Deep tier requires --worktree. Standard and quick are opt-in.
Working in the worktree
After scaffold, cd .ark/worktrees/<branch>/. Run all subsequent phase commands from there:
cd .ark/worktrees/feat/rate-limit
ark agent task plan
# ... fill 00_PLAN.md ...
ark agent task review
# ... fill 00_REVIEW.md ...
ark agent task execute
# ... edit code, commit ...
ark agent task verify
# ... fill VERIFY.md ...
ark agent task commit
ark agent task archive
The worktree has its own .ark/ independent of the parent's. Slash commands run inside it act on that .ark/.
Cleanup after merge
Archive does not auto-clean the worktree. After the branch has been merged into your default branch, run from the parent checkout:
ark agent task worktree cleanup --slug rate-limit
ark agent task worktree cleanup --slug rate-limit --delete-branch # also delete the branch
cleanup refuses if the worktree is dirty unless you pass --force. --force also escalates git branch -d to git branch -D so unmerged-branch deletion works.
To enumerate active worktree-backed tasks:
ark agent task worktree list
Each row is one line: <slug> <branch> <path>. Empty stdout when there are zero rows.
Configuration
.ark/config.toml's [worktree] section:
[worktree]
worktree_dir = ".ark/worktrees" # where worktrees go (project-relative)
branch_prefix = "feat" # default branch prefix
copy = [".env", ".envrc"] # files to copy into each new worktree
post_create = [ # commands to run after `git worktree add`
"cargo build",
"npm install",
]
worktree_dir must stay project-relative; absolute paths and .. traversal are rejected. post_create commands run sequentially with cwd set to the new worktree; the first non-zero exit aborts the whole task new --worktree and rolls back the worktree dir.
CLI Overview
The ark binary has two visible top-level commands:
| Command | What it does |
|---|---|
ark init | Scaffold .ark/ and per-platform integrations from embedded templates. |
ark load | Restore from .ark.db snapshot, or scaffold like init if no snapshot exists. |
ark unload | Snapshot everything under .ark/ + managed blocks + Ark hooks into .ark.db. |
ark remove | Wipe Ark fully: .ark/, platform dirs, managed blocks, .ark.db, hooks. |
ark upgrade | Refresh embedded templates to the current CLI version. |
ark context | Print a structured snapshot of git + .ark/ workflow state. Read-only. |
ark archive | Bulk-move every phase = Committed task into its committed_at month bucket. Manager-only. |
Plus one hidden internal command:
| Command | Purpose |
|---|---|
ark agent | Task lifecycle and spec management. Not semver-stable. |
Stability policy
- Visible commands (the six above) are semver-stable. Flags don't disappear within a major version; output formats (especially
ark context --format json) follow additive-only schema versioning. ark agentis internal. Its callers are the shipped slash commands and the workflow doc. The CLI surface is not covered by semver — the binary and its shipped templates version together.
End users should drive workflow through slash commands, not by calling ark agent directly. See ark agent for when you do need to reach for it (mostly: rare lifecycle operations the slash commands don't cover).
Project discovery
Most commands find the project root by walking ancestors looking for a .ark/ directory:
ark context,ark unload,ark remove,ark upgrade,ark load(without--force) — walk up from cwd.ark initandark load --force— operate on the explicit target (defaulting to cwd if--diris omitted). They scaffold a project; they don't locate one.
Pass --dir <path> to override discovery. When provided, it always wins.
Conventions
- Errors print to stderr with
error: <message>and a chainedcaused by:for each source. Exit code 1 on any failure. --helpis supported on every level.ark --helplists top-level commands;ark <cmd> --helplists flags for that command.ark agent --helpis the only way to see the hidden namespace.- JSON output. Only
ark context --format jsonproduces machine-readable output; the rest are human-targeted summaries viaDisplay.
ark init
Scaffold .ark/ and the integrations for each selected platform.
Synopsis
ark init [OPTIONS]
Description
ark init writes the embedded templates into the host project, installs each selected platform's managed block (<!-- ARK --> in CLAUDE.md / AGENTS.md), records every artifact in .ark/.installed.json so later commands can clean up without touching user work, and re-applies each platform's SessionStart hook entry.
Safe to re-run: files that already match are left untouched. Files that differ are skipped unless --force is set.
Additive on the manifest: if a manifest already exists, only the platform-neutral .ark/ tree and the selected platforms' dirs are rewritten. Other-platform entries are preserved — ark init --codex on a Claude-installed project adds Codex without forgetting Claude.
Flags
| Flag | Description |
|---|---|
--dir <path> | Target directory. Defaults to current working directory. |
--force | Overwrite user-modified template files. Default behavior is to skip. |
--claude | Include Claude Code integration. |
--no-claude | Exclude Claude Code integration. |
--codex | Include Codex integration. |
--no-codex | Exclude Codex integration. |
--opencode | Include OpenCode integration. |
--no-opencode | Exclude OpenCode integration. |
Platform selection
If no positive flags are passed and stdin is a TTY, ark init prompts interactively to pick platforms. Non-TTY without flags errors out with a message naming all three flag pairs.
When platform flags are mixed:
- Positive flags select that platform.
- Negative flags exclude that platform.
--<flag> --no-<flag>for the same platform: negative wins (excluded).- At least one platform must remain after filtering, or init errors out.
Examples
# Interactive: prompts for platforms.
ark init
# All three platforms.
ark init --claude --codex --opencode
# Claude only.
ark init --no-codex --no-opencode
# Force overwrite of user-modified templates.
ark init --force
What gets written
See Quick Start → What gets scaffolded for the full layout.
Errors
init requires at least one platform— all platforms excluded after flag filtering.- File-conflict errors when re-running over user-modified files without
--force.
See also
ark load— restore from a snapshot or scaffold fresh.ark upgrade— refresh templates after a CLI update.
ark load
Restore Ark from a .ark.db snapshot, or scaffold fresh if no snapshot exists.
Synopsis
ark load [OPTIONS]
Description
ark load is the inverse of ark unload. It picks one of two paths:
.ark.dbexists → restore every captured file under owned dirs, every managed block, and every Ark-owned hook entry. The snapshot file is consumed (deleted) on success..ark.dbabsent → behave likeark initwithWriteMode::Force.
In both paths, after content is in place, each installed platform's hook entry is re-applied to the current canonical shape — so a snapshot from an older Ark version restores cleanly even if the hook schema (e.g. timeout value) has shifted. Older snapshots that pre-date the per-entry hook_bodies capture deserialize successfully (defaulting to empty) and the canonical entries are still re-applied.
Flags
| Flag | Description |
|---|---|
--dir <path> | Target directory. Without --force, walks ancestors looking for .ark/. With --force, treats --dir as the target. |
--force | Wipe the live footprint (owned dirs) before restoring. Required if .ark/ already exists. |
Refusal
Without --force, ark load refuses if .ark/ already exists at the target — the implication is that you want to merge restored state into a live install, which is more error-prone than starting fresh.
Examples
# Round-trip: capture, then restore.
ark unload
ark load # restores from .ark.db (which `ark unload` wrote)
# Re-load over an existing install.
ark load --force # wipes .ark/ then restores
# Initial bootstrap from a snapshot file.
ark load --dir /path/to/project --force
Round-trip preservation
User-edited and user-added files inside owned dirs (.ark/tasks/..., custom slash commands) survive unload → load losslessly. The snapshot captures full file contents; managed blocks (the parts of CLAUDE.md / AGENTS.md between <!-- ARK:START --> and <!-- ARK:END -->) are captured separately and re-spliced on restore.
User siblings of the Ark hook entry (e.g. a custom PreToolUse hook in .claude/settings.json) survive a round-trip too: unload only surgically removes the Ark SessionStart entry and load only surgically re-adds it. Other top-level keys and sibling hook entries stay on disk untouched.
See also
ark unload— capture into.ark.db.ark init— scaffold without a snapshot.ark remove— wipe completely.
ark unload
Capture every Ark-owned file, managed block, and hook entry into .ark.db, then remove the live footprint.
Synopsis
ark unload [OPTIONS]
Description
ark unload produces a portable snapshot of the entire Ark footprint:
- Files — every regular file under owned dirs (
.ark/,.claude/commands/ark/,.codex/,.opencode/), excluding.ark/worktrees/. - Managed blocks — every
<!-- ARK -->block recorded in.ark/.installed.json(or every shipping platform's managed-block target as a fallback if the manifest is missing). - Hook entries — every Ark-owned
SessionStartentry across every platform's hook file. Stage A captures known platforms' entries; Stage B scans every*.jsonfile under owned dirs for orphan Ark-identity entries (e.g. from a future-version platform whose plumbing this Ark binary doesn't know about).
After capture, the snapshot is written to .ark.db (TOML at the project root), then the live footprint is deleted. Sibling user content stays on disk: user-added files inside owned dirs are captured-and-then-deleted (they survive on round-trip via load), but user hook siblings (e.g. a PreToolUse entry in .claude/settings.json) stay live — only the Ark SessionStart entry is surgically removed.
Flags
| Flag | Description |
|---|---|
--dir <path> | Project root. Walks ancestors looking for .ark/. |
Worktree exclusion
unload skips .ark/worktrees/ to avoid recursing into per-task git checkouts (which would capture their target/, uncommitted edits, etc.). The skip path comes from [worktree].worktree_dir in .ark/config.toml, so a non-default setting is honored.
VCS
.ark.db is the user's responsibility to gitignore. Ark doesn't write to your .gitignore to add it. The file is a checkpoint, not a permanent artifact; commit it intentionally if you want to share state with collaborators (rare), or .gitignore it.
Examples
# Full capture.
ark unload
# Then restore.
ark load
Errors
NotLoaded—.ark/directory doesn't exist at the discovered project root.
See also
ark load— restore from snapshot.ark remove— destroy state without saving.
ark remove
Unconditionally wipe Ark from a project. No snapshot, no recovery.
Synopsis
ark remove [OPTIONS]
Description
ark remove removes:
.ark/(entire tree).- Each platform's owned dir (
.claude/commands/ark/,.codex/skills/,.codex/config.toml,.codex/hooks.json,.opencode/). - Every recorded
<!-- ARK -->managed block (inCLAUDE.md,AGENTS.md, etc.). The surrounding file is preserved; only the block content + delimiters are removed. Empty parents (e.g. anAGENTS.mdthat contained only the Ark block) are deleted. - The Ark
SessionStarthook entry from every platform's hook file. Sibling user hooks (PreToolUse, etc.) survive. .ark.dbif present.
This is the destructive twin of ark unload. Use unload if you want to come back; use remove if you don't.
Flags
| Flag | Description |
|---|---|
--dir <path> | Project root. Walks ancestors looking for .ark/. |
Idempotence
Safe to run on a clean repo. ark remove reports a no-op summary if there's nothing to remove.
Examples
ark remove # remove from current project
ark remove --dir /path/to/proj # remove from elsewhere
See also
ark unload— capture state into.ark.dbfirst.ark init— re-scaffold from scratch.
ark upgrade
Refresh embedded templates to the current CLI version.
Synopsis
ark upgrade [OPTIONS]
Description
When you install a newer ark binary, run ark upgrade inside each Ark-managed project to bring its template files up to date. The command compares the project's recorded template hashes (in .ark/.installed.json) against the embedded templates of the running binary and produces a Plan: add new files, auto-update unchanged-on-disk files, prompt for files the user has edited, delete files the new version removed.
Three things are always re-applied unconditionally (they're not hash-tracked):
- Each platform's
<!-- ARK -->managed block inCLAUDE.md/AGENTS.md. - Each platform's Ark
SessionStarthook entry. - The shipped
extra_filesfor any platform that has them (e.g. OpenCode'sark-context.tsplugin).
.ark/config.toml is never overwritten, even if its hash changed. User edits to [worktree] settings persist across upgrades.
Flags
| Flag | Description |
|---|---|
--dir <path> | Project root. Walks ancestors looking for .ark/. |
--force | On user-modified files, overwrite without prompting. Default behavior is to prompt. |
--skip-modified | On user-modified files, skip silently. Default behavior is to prompt. |
--create-new | On user-modified files, write the new version to <file>.new so you can diff manually. |
--allow-downgrade | Permit manifest.version > cli_version. Default behavior is to error with DowngradeRefused. |
--force / --skip-modified / --create-new are mutually exclusive.
Conflict resolution
For each file:
| State | Action |
|---|---|
| New file in CLI, absent from project | Add (recorded in manifest). |
| Existing, on-disk hash matches recorded | Auto-update to new template (recorded hash refreshed). |
| Existing, on-disk hash differs from recorded | User-modified → prompt unless --force / --skip-modified / --create-new is set. |
| Existing, recorded but missing from CLI | Delete (orphan template, e.g. removed in a CLI release). |
| Existing in old CLI, absent in new CLI | Drop manifest entry; file stays on disk if user-modified. |
The plan is sorted: writes (Add → AutoUpdate → Overwrite) first, then CreateNew, then RefreshHashOnly, then Preserve, then Delete. The manifest is flushed durably before any delete can fail, so a partial failure mid-delete leaves the manifest consistent with disk state.
Path safety
Every entry in manifest.files is normalized through Layout::resolve_safe before any I/O. A manifest pointing at ..//etc/passwd errors out before the upgrade does anything. Embedded templates can't trip this check because their paths are construction-time, not data.
Version policy
ark upgrade refuses to run if manifest.version > cli_version (you have a newer Ark recorded than the binary you're running). Override with --allow-downgrade if that's intentional.
Examples
ark upgrade # interactive on user-modified files
ark upgrade --skip-modified # leave user edits alone
ark upgrade --force # overwrite user edits
ark upgrade --create-new # write `.new` files for manual merge
See also
ark init— initial scaffold..ark/config.toml— preserved across upgrades.
ark context
Print a structured snapshot of git state and .ark/ workflow state. Read-only.
Synopsis
ark context [OPTIONS]
Description
ark context is the single entry point for orientation. Slash commands invoke it at every phase boundary; you can invoke it manually any time. It never mutates state.
The output covers:
- Git state: current branch, HEAD, dirty file count and a capped list of dirty paths, recent commits.
- Active task: slug, tier, phase, iteration, artifact paths.
- Other active tasks (across worktrees).
- Project specs index.
- Feature specs index, optionally filtered by the active PRD's
[**Related Specs**]. - Recent archive (last 5).
Flags
| Flag | Description |
|---|---|
--dir <path> | Project root. Walks ancestors looking for .ark/. |
--scope {session|phase} | What to project. Default session. Phase-targeted slices require --for. |
--for <phase> | Required iff --scope phase. One of design, plan, review, execute, verify. |
--format {text|json} | Output shape. Default text. Both modes derive from the same in-memory Context struct. |
Scopes
--scope session (default) returns full orientation:
## GIT STATUS
## CURRENT TASK
## ACTIVE TASKS
## SPECS
## ARCHIVE
Sections absent from the projection are omitted entirely.
--scope phase --for <phase> narrows to the slice that phase needs:
| Phase | Specs included | Archive | Tasks |
|---|---|---|---|
design | full project + features unfiltered | yes | none |
plan | full project; features filtered to PRD's Related Specs | no | none |
review | full project; features filtered to PRD's Related Specs | no | none |
execute | full project; features = [] | no | none |
verify | full project; features = [] | no | none |
The PRD's [**Related Specs**] parsing is by regex (specs/features/[a-z0-9_-]+/SPEC\.md); only paths that match are kept in the filtered list.
JSON schema
--format json produces a stable JSON document with "schema": 1 as the first field. Schema is additive-only going forward: new fields can appear; existing fields' names and types are stable across patch and minor releases. A field rename or removal requires bumping SCHEMA_VERSION.
The payload contains paths and summaries only — never file bodies. Artifacts appear as {kind, iteration?, path, lines}; specs as index-row data; archives as {slug, title, tier, archived_at, path}. Callers Read files they need.
--scope session --format json is wrapped in Claude Code's SessionStart hook envelope:
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "<stringified projection JSON>"
}
}
The inner additionalContext is the actual projection (still starts with "schema": 1), serialized as a JSON string. Every other (scope, format) combination emits raw output.
SessionStart hook
ark init / ark load / ark upgrade install a SessionStart hook into .claude/settings.json (and platform-equivalents) that runs ark context --scope session --format json. The output is injected as additional context at the start of every Claude / Codex / OpenCode session — so the agent sees the project's current state without any user interaction.
The hook entry's identity is the canonical command string ark context --scope session --format json. Sibling user hooks at the same SessionStart array are preserved across unload / load round-trips.
Examples
# Default human-readable session orientation.
ark context
# JSON for tooling.
ark context --format json
# Phase-targeted: what does the design phase need?
ark context --scope phase --for design --format json
# What does plan need (filtered features)?
ark context --scope phase --for plan
Caching
None. Every invocation re-reads state. Cost is dominated by git log -n 5 --oneline and a directory walk of .ark/specs/.
See also
ark agent
The hidden internal namespace that drives task lifecycle and spec management.
Stability
ark agent is not semver-stable. The contract is between the shipped binary and the shipped templates — they version together. End users should drive the workflow through slash commands (/ark:quick, /ark:design, /ark:commit) rather than calling ark agent directly. The sole reasons to reach for it manually:
- Rare lifecycle operations the slash commands don't wrap (e.g. tier promotion mid-flight).
- Debugging a stuck workflow.
- Building automation around Ark in CI.
ark --help does not list agent. To explore the namespace:
ark agent --help
The header includes a stability disclaimer.
Subcommand tree
ark agent
├── task
│ ├── new # scaffold a new task dir
│ ├── plan # transition design → plan
│ ├── review # transition plan → review (deep only)
│ ├── execute # transition plan/review → execute
│ ├── verify # transition execute → verify
│ ├── commit # atomically close: SPEC extract + single git commit
│ ├── archive # transition committed → archived (move dir)
│ ├── promote # change tier mid-flight
│ └── worktree
│ ├── list # enumerate worktree-backed tasks
│ └── cleanup # remove a worktree dir + optionally delete branch
└── spec
├── extract # extract PLAN's `## Spec` to specs/features/<name>/SPEC.md
└── register # add a row to specs/features/INDEX.md's managed block
Common patterns
Task lifecycle
# Quick tier.
ark agent task new --slug fix-typo --title "fix typo" --tier quick
# ... edit code ...
ark agent task execute
ark agent task commit
# Bulk-archive Committed tasks later via `ark archive`.
# Standard.
ark agent task new --slug rate-limit --title "add rate limit" --tier standard
# ... fill PRD ...
ark agent task plan
# ... fill 00_PLAN.md ...
ark agent task execute
# ... edit code, stage work ...
ark agent task verify
# ... fill VERIFY.md ...
ark agent task commit
# Deep — review loop adds two more.
ark agent task new --slug auth --title "auth refactor" --tier deep --worktree
cd .ark/worktrees/feat/auth
# ... fill PRD ...
ark agent task plan
# ... fill 00_PLAN.md ...
ark agent task review
# ... fill 00_REVIEW.md ...
# If revisions needed: copy NN_PLAN.md / NN_REVIEW.md to NN+1_*, bump iteration in task.toml,
# reset phase = "plan", and run `ark agent task review` again.
ark agent task execute
# ... edit, stage ...
ark agent task verify
ark agent task commit # promotes SPEC, registers in features INDEX, single git commit
Worktree management
# Create a worktree-backed task.
ark agent task new --slug rate-limit --tier deep --worktree
# Override the branch type from `feat` (default) to `fix`:
ark agent task new --slug ratelimit-bug --tier standard --worktree --branch-type fix
# List all worktree-backed tasks.
ark agent task worktree list
# Clean up after merge.
ark agent task worktree cleanup --slug rate-limit
ark agent task worktree cleanup --slug rate-limit --delete-branch
ark agent task worktree cleanup --slug rate-limit --force # for unmerged branches
Spec promotion
Normally invoked by ark agent task commit on deep tier; you can call it directly when reopening an archived task:
ark agent spec extract --slug auth # extract PLAN's `## Spec` → specs/features/auth/SPEC.md
ark agent spec register --slug auth # add row to specs/features/INDEX.md
Defaults
Every subcommand that takes --slug defaults to .ark/tasks/.current when omitted. Every command takes optional --dir <path> to override project discovery.
Errors
The state machine enforces legal transitions and rejects illegal ones. Common errors:
IllegalPhaseTransition— running e.g.ark agent task reviewon a quick-tier task, or on a task already past plan.WrongTier— attempting a deep-only operation (extract spec) on a standard or quick task.TaskExistsOnParent— creating a--worktreetask whose slug already exists in the parent's.ark/tasks/.NestedWorktreeForbidden— invokingtask new --worktreefrom inside an existing.ark/worktrees/<branch>/checkout.WorktreeDirty—worktree cleanupwithout--forceon a worktree with uncommitted changes.
Hand-edits to task.toml are sometimes the right escape hatch (specifically: bumping iteration, resetting phase to plan, or reopening an archived task). The state machine is small and the file is short; the agent does this when needed and the slash commands document when.
See also
.ark/config.toml
User-editable consolidated config. One file, sectioned by feature.
Synopsis
[worktree]
worktree_dir = ".ark/worktrees"
branch_prefix = "feat"
copy = []
post_create = []
Lifecycle
Created by ark init from the embedded template. Never overwritten by ark upgrade — your edits to [worktree] settings persist across CLI updates. To pick up a new default that ships in a later Ark version, edit your config.toml by hand.
If the file is missing entirely (e.g. the project predates this config), every section falls through to its default. If a specific [section] is absent, that feature uses defaults. If the file exists but is malformed TOML, the next operation that needs a config (e.g. ark agent task new --worktree) errors with WorktreeConfigCorrupt.
[worktree]
| Field | Type | Default | Description |
|---|---|---|---|
worktree_dir | string | .ark/worktrees | Project-relative path where worktrees go. Absolute paths and .. traversal rejected. |
branch_prefix | string | feat | Default branch prefix when neither --branch-type nor --branch is passed to task new --worktree. |
copy | []string | [] | Project-relative paths to copy into each new worktree on creation (e.g. .env, .envrc). |
post_create | []string | [] | Shell commands run in the worktree dir after git worktree add. Sequential. Abort on first non-zero. |
Validation. worktree_dir must stay inside the project. Absolute paths → InvalidConfigField. .. traversal → InvalidConfigField. The aim is to prevent a malicious or careless config from making task new --worktree write outside the project tree.
copy semantics. Sources missing on disk → WorktreeCopySourceMissing (hard fail; rolls back the worktree). Source paths are resolved relative to the parent checkout's project root. Destination is the same relative path under the new worktree.
post_create semantics. Each command runs via sh -c <cmd> with cwd set to the new worktree. Stdout/stderr inherit. The first non-zero exit aborts the rest of the list and rolls back the worktree dir (git worktree remove --force).
Examples
# A team that copies .env into each worktree and runs a build to warm caches.
[worktree]
copy = [".env"]
post_create = ["cargo build"]
Adding a new section
If you're extending Ark itself (see Contributing), each feature owns its own raw shape and falls through to its Default impl when its section is absent. Adding a new feature's section is independent of existing ones.
Claude Code
Claude Code integration ships first-class. It's the original target platform for Ark and the most thoroughly exercised.
What gets installed
.claude/
├── commands/ark/
│ ├── quick.md # /ark:quick $ARGUMENTS
│ ├── design.md # /ark:design [--deep] $ARGUMENTS
│ └── commit.md # /ark:commit
└── settings.json # contains Ark's SessionStart hook entry
CLAUDE.md # managed block pointing at .ark/
The .claude/commands/ark/ files are slash-command bodies. Each one carries Claude-specific YAML frontmatter (description:, argument-hint:) and a body that walks the agent through the corresponding tier's workflow.
Slash commands
| Command | Trigger | What it does |
|---|---|---|
/ark:quick | Any message starting with this | Quick-tier flow: PRD → execute → user invokes commit. |
/ark:design | /ark:design [--deep] <title> | Standard-tier flow (or deep with --deep): PRD → PLAN → review → execute → verify. |
/ark:commit | /ark:commit | Atomically close the current task in a single git commit. On deep tier, promotes SPEC. |
Each command body starts by calling ark context --scope phase --for <phase> --format json to orient itself, then guides the agent through the rest of the phase.
SessionStart hook
ark init installs a hook entry into .claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "ark context --scope session --format json",
"timeout": 5000
}
]
}
]
}
}
timeout is in milliseconds (Claude's hook schema). At session start, Claude runs the command and feeds its stdout (the JSON additionalContext envelope) into the model's context window — so the agent sees current git state, active task, specs, and recent archive without any user message.
The command field is the identity key Ark uses to detect its own entry across runs. A user-added sibling hook in the same SessionStart array (e.g. a PreToolUse hook for some other purpose) is preserved across unload / load round-trips and across ark upgrade. Ark's entry is surgically removed and re-added; nothing else in settings.json is touched.
CLAUDE.md managed block
ark init writes (or merges into existing) CLAUDE.md:
<!-- ARK:START -->
Ark is installed in this project. Use `/ark:quick` or `/ark:design` to start tasks.
See `.ark/workflow.md` for the full workflow.
@.ark/specs/INDEX.md
<!-- ARK:END -->
Edit anywhere outside the markers; ark upgrade re-renders inside them. The @.ark/specs/INDEX.md line is a Claude include directive — it inlines the SPEC index into the CLAUDE.md context every session.
If you don't want the block at all (e.g. you maintain your own CLAUDE.md from a template generator), ark remove deletes the block (and its delimiters) without touching surrounding content.
What unload / remove do
ark unloadcaptures every file under.claude/commands/ark/(full bytes), the<!-- ARK -->block inCLAUDE.md(block content + position), and theSessionStartArk entry fromsettings.json. Then deletes the live copies. Sibling content insettings.json(other top-level keys, otherSessionStartarray entries) stays on disk.ark removedoes the destructive twin: same removal, no capture. Empty parents are pruned (e.g. an empty.claude/commands/after theark/subtree is gone).
After unload, .claude/settings.json may shrink to {} or even be removed if it carried only the Ark entry — but if it had user content, the user content is preserved verbatim.
Round-trip preservation
unload → load is byte-identical for everything except snapshot timestamps. Specifically:
- User-added files under
.claude/commands/(e.g..claude/commands/my-team/foo.md) survive captured-and-restored. - User-modified
CLAUDE.mdcontent outside the managed block survives. - User-added
SessionStartsiblings (other entries in the array) are preserved on disk throughunload;loaddoes not need to re-add them becauseunloaddoesn't remove them.
Codex
Codex integration ships out of the box. The platform model is description-routed skills rather than slash commands; Ark's wrappers translate the Claude flow accordingly.
What gets installed
.codex/
├── skills/
│ ├── ark-quick/SKILL.md
│ ├── ark-design/SKILL.md
│ └── ark-commit/SKILL.md
├── config.toml # Codex hook config
└── hooks.json # Ark's SessionStart hook entry
AGENTS.md # managed block pointing at .ark/ (created if absent)
Skills
Codex doesn't have user-typed slash commands; instead, the user describes what they want and Codex routes to a matching skill based on its YAML description frontmatter. Each Ark skill carries a description Codex can match:
---
name: ark-quick
description: Use this skill when the user asks for a quick, reversible change with no
new abstractions (typo fixes, doc edits, trivial bug fixes, dependency bumps).
---
# ark-quick
... skill body walks the agent through the quick-tier flow ...
The body of each skill is a mechanical translation of the matching templates/claude/commands/ark/<name>.md body: drop Claude's frontmatter, prepend Codex's name/description frontmatter, rewrite any inline /ark:foo references to ark-foo skill names.
Body content parity between Claude commands and Codex skills is not mechanically asserted (Claude bodies use slash-invocation idioms that don't translate cleanly to Codex's description-routed model). Two structural tests catch the failure modes that matter:
every_claude_command_has_a_codex_skill_sibling— adding a Claude command without a Codex twin is a test-time failure.codex_skill_bodies_have_codex_frontmatter_not_claude_frontmatter— copy-paste regressions where a Claude body lands in the Codex tree fail this.
SessionStart hook
.codex/hooks.json:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "ark context --scope session --format json",
"timeout": 30
}
]
}
]
}
}
Note timeout is 30 seconds, not milliseconds — Codex's hook schema differs from Claude's (Codex uses seconds, defaults to 600 when omitted). 30 seconds gives ark context more than enough budget for any project size.
The same surgical-upsert / surgical-removal contract as Claude: sibling user hooks under any other hooks.<event> array are preserved across unload / load round-trips.
.codex/config.toml
This file enables Codex's SessionStart hook by pointing at hooks.json. It's not user-editable in any meaningful way; it ships as a shipped template and gets re-applied unconditionally on every ark init / ark load / ark upgrade (not hash-tracked).
AGENTS.md managed block
Codex reads AGENTS.md (parallel to Claude's CLAUDE.md). ark init creates the file if absent and writes the same managed block:
<!-- ARK:START -->
Ark is installed in this project. Use `/ark:quick` or `/ark:design` to start tasks.
See `.ark/workflow.md` for the full workflow.
@.ark/specs/INDEX.md
<!-- ARK:END -->
When both Codex and OpenCode are installed, they share the AGENTS.md file — the managed block is recorded once in the manifest ((file, marker) dedupe), and both platforms read the same block.
What unload / remove do
ark unloadcaptures the entire.codex/tree (skills, config, hooks), theAGENTS.mdmanaged block, and the CodexSessionStartentry fromhooks.json. Then deletes the live copies. Sibling user hook content (entries Ark didn't install) stays on disk.ark removeremoves the same live footprint without capturing. Empty parents are pruned.
A Codex-only project has an AGENTS.md but no CLAUDE.md; a both-platforms project has both, with the same managed block content in each.
Claude-only / Codex-only / both
ark init lets you pick. Per-platform flags (--claude / --no-claude / --codex / --no-codex) work additively:
ark init --no-codex # Claude only
ark init --no-claude # Codex only
ark init # interactive prompt (TTY) or all platforms (non-TTY)
A Claude-only project upgraded with a CLI version that knows about Codex stays Claude-only — ark upgrade does not silently install Codex artifacts. Run ark init --codex (additive, idempotent) to opt into Codex on an already-installed project.
OpenCode
OpenCode integration ships out of the box. Like Claude Code, OpenCode supports user-typed slash commands; unlike Claude, OpenCode injects context via a TypeScript plugin rather than a JSON hook.
What gets installed
.opencode/
├── commands/ark/
│ ├── quick.md # /ark:quick $ARGUMENTS
│ ├── design.md # /ark:design [--deep] $ARGUMENTS
│ └── commit.md # /ark:commit
└── plugins/
└── ark-context.ts # injects `ark context` output as additionalContext
AGENTS.md # managed block (shared with Codex if both are installed)
Slash commands
OpenCode commands look very similar to Claude's, but with OpenCode-specific frontmatter:
---
description: Run a quick-tier Ark task — reversible, no new abstractions.
---
# `/ark:quick $ARGUMENTS`
... body identical to Claude's, with the same flow ...
Two structural invariants are tested:
every_claude_command_has_an_opencode_command_sibling— adding a Claude command without an OpenCode twin fails the build.opencode_command_bodies_have_opencode_frontmatter_and_arguments_token— each OpenCode command body must open with---\ndescription:(noargument-hint:line — that's Claude-specific) and contain the verbatim heading# `/ark:<name> $ARGUMENTS`matching the Claude templates exactly.
Plugin-based context injection
OpenCode doesn't have a JSON hook schema like Claude or Codex. Instead, Ark ships a TypeScript plugin at .opencode/plugins/ark-context.ts that runs on session start, calls ark context --scope session --format json, and injects the output as additionalContext for the conversation.
The plugin is shipped via a separate mechanism (extra_files in the platform registry) rather than the embedded template tree, because:
- The plugin is a real TypeScript file with imports, not a markdown command body.
- The path is
.opencode/plugins/, parallel to but distinct from.opencode/commands/.
The plugin file is never hash-tracked — it's re-applied unconditionally on every ark init / ark load / ark upgrade. User edits to the plugin will be overwritten on the next upgrade.
Plugin internals
The plugin defines two pure helpers — buildEnvelopePrefix and shouldInject — as plain function declarations, not export function. OpenCode's plugin runtime treats every named export as a plugin factory and invokes it at load time with no arguments; exporting a parameterized helper crashes plugin loading. This invariant is locked down in templates.rs tests:
#![allow(unused)] fn main() { assert!( !body.contains("export function buildEnvelopePrefix("), "`buildEnvelopePrefix` must NOT be exported — opencode invokes every named export at \ load time and the helper takes parameters" ); }
If you're modifying the plugin, keep helpers internal and only export the actual plugin factory.
AGENTS.md managed block
OpenCode reads AGENTS.md (same file Codex uses). When both platforms are installed, the managed block is recorded once in the manifest ((file, marker) dedupe), and the on-disk file contains exactly one <!-- ARK:START --> marker.
What unload / remove do
ark unloadcaptures.opencode/commands/,.opencode/plugins/, and theAGENTS.mdmanaged block. There's no hook file to capture (OpenCode's plugin isn't hook-shaped). The whole.opencode/tree is deleted from disk.ark removeremoves the entire.opencode/directory (including any sibling subdirs the user added — there's no surgical hook removal because there's no hook).
OpenCode's removal_root is .opencode/ itself, not just .opencode/commands/. This is intentional: the .opencode/plugins/ subtree is also Ark-managed (the plugin file is the only thing in it), so removing the whole .opencode/ keeps the cleanup honest.
If you want to keep .opencode/ for some other purpose (e.g. you have non-Ark plugins or commands there), use ark unload to snapshot, then manually edit before load. Or don't install OpenCode integration in the first place: ark init --no-opencode.
Workspace Layout
This chapter is for contributors extending Ark itself. It walks through the Cargo workspace and the ark-core module map.
Cargo workspace
.
├── crates/
│ ├── ark-cli/ # thin clap adapter — keep it boring
│ └── ark-core/ # all logic lives here
├── templates/
│ ├── ark/ # files extracted into the host's .ark/
│ ├── claude/ # files extracted into the host's .claude/
│ ├── codex/ # files extracted into the host's .codex/
│ └── opencode/ # files extracted into the host's .opencode/
├── docs/
│ ├── book/ # this book
│ └── ... # roadmap, design notes
├── reference/ # third-party projects we draw from (READ-ONLY)
└── README.md # user-facing overview
Two crates: ark-cli is a thin clap adapter, ark-core is the library. The split exists so the library can be exercised without going through clap parsing — every test uses the library directly, not the binary.
ark-core module map
ark-core/src/
├── lib.rs # public re-exports
├── error.rs # Error enum, Result alias
├── layout.rs # Layout + project-root constants + discover_from
├── platforms.rs # Platform registry (Claude / Codex / OpenCode)
├── templates.rs # include_dir!() trees + walker
├── io/
│ ├── mod.rs # public surface
│ ├── path_ext.rs # PathExt trait wrapping std::fs
│ ├── fs.rs # write_file, walk_files, managed-block ops, hook-file editor
│ └── git.rs # the only sanctioned Command::new("git") site
├── state/
│ ├── manifest.rs # .ark/.installed.json
│ └── snapshot.rs # .ark.db capture/restore (incl. SnapshotHookBody)
└── commands/
├── init.rs # scaffold from templates
├── load.rs # restore from .ark.db OR scaffold
├── unload.rs # capture into .ark.db, remove live files
├── remove.rs # unconditional wipe
├── upgrade.rs # refresh embedded templates to current CLI version
├── tests_common.rs # shared `assert_source_clean` for source-scan invariants
├── context/ # `ark context` — read-only state snapshot
│ ├── model.rs # Context + sub-structs, schema=1
│ ├── gather.rs # one-pass collection (git + tasks + specs)
│ ├── projection.rs # Scope / PhaseFilter / project()
│ ├── render.rs # text-mode Display
│ └── related_specs.rs # PRD [**Related Specs**] parser
└── agent/ # `ark agent` namespace (hidden CLI, not semver)
├── state.rs # TaskToml + legal-transition table
├── task/ # task lifecycle (new/plan/review/execute/verify/archive/promote)
├── spec/ # feature SPEC extract + register
├── workspace/ # per-developer journal
└── template.rs # internal helper: extract embedded templates
The ark agent namespace
ark agent is a hidden top-level subcommand (#[command(hide = true)]) that packages the structural workflow mutations as typed Rust commands. Callers are the shipped slash commands and the workflow doc; end users prefer those.
Stability policy. The CLI surface under ark agent is not covered by semver. The contract is internal — the binary and its shipped templates version together, not against external callers. ark --help does not list agent; ark agent --help renders its children with a stability banner.
Responsibilities of this layer:
- Create task directories (
task new) and move them on archive (task archive) — whenever the operation touches filesystem structure that has to be correct. - Transition phases (
task plan/review/execute/verify/archive) — the state machine enforces legality per tier. - Extract SPEC bodies from PLANs and upsert rows in
specs/features/INDEX.md's managed block.
Not this layer's responsibility:
- Rare lifecycle operations — iteration and task reopening — the workflow doc tells the agent to hand-edit these. Tier promotion is supported mid-flight via
ark agent task promote; other ad hoc lifecycle edits remain manual. - Artifact content (PRD prose, PLAN sections, REVIEW verdicts) — agent's judgment.
- Git / GH operations — agent uses them directly.
- Consistency checks / doctoring — reviewer judgment.
Build, test, lint
cargo build --workspace
cargo test --workspace
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
CI runs all four; PRs that fail any won't merge. Run them before requesting review.
End-to-end smoke test
cargo build --release
TMP=$(mktemp -d)
./target/release/ark load --dir "$TMP"
./target/release/ark unload --dir "$TMP"
./target/release/ark load --dir "$TMP"
./target/release/ark remove --dir "$TMP"
rm -rf "$TMP"
Round-trip must preserve user-edited and user-added files under .ark/ and .claude/commands/ark/.
Code conventions
- Errors. Every fallible op returns
crate::error::Result<T>. Wrapstd::io::ErrorviaError::io(path, source). Neverunwrap()outside tests; reserveexpect("…")for documented invariants only. - Filesystem. Prefer the methods on
io::PathExtoverstd::fs::*so error paths stay structured. The trait is implemented for anyT: AsRef<Path>. Source-scan invariant tests incommands/init.rs,commands/upgrade.rs, etc. enforce this at compile-test time. - Managed blocks in text files are owned by
io::fs::{read,update,remove}_managed_block— don't hand-writeARK:START/ARK:ENDHTML-comment delimiters elsewhere. - Project paths. Route through
layout::Layout(ark_dir(),claude_md(),owned_dirs(), etc.). Don'troot.join(".ark")ad-hoc. The same source-scan tests catch hand-joined.ark/literals. - Commands return summaries that
impl Display. The CLI calls onerender(summary)per dispatch — don't add ad-hoc print logic. - Style. Functional combinators (
try_for_each,and_then,map_or) where they read more clearly; explicit imperative form where they don't.cargo fmtsettles all formatting debates. - Tests. Every module that does I/O has unit tests using
tempfile::tempdir(). Round-trip coverage lives incommands/load.rs::tests.
Reference material
reference/mirrors third-party projects we study (trellis, openspec, spec-kit, superpowers, agents-cli, etc.). Treat it as read-only; don't edit anything underreference/.- Design history and roadmap notes live in
docs/.
What not to do
- Don't add files just to host one function. Single-responsibility helpers belong in the module that owns the responsibility.
- Don't introduce
crate::*::*paths that bypasslayout::Layoutfor path computation. - Don't shell out to git,
mv, orrmfrom Rust code — usePathExt. The single sanctionedCommand::new("git")site isio/git.rs; everything else routes through it. - Don't mutate
reference/or commit anything fromtarget/.
Adding a Slash Command
Adding a new slash command (e.g. /ark:status) means writing parallel files under three template trees and letting the test suite enforce parity.
The three trees
templates/
├── claude/commands/ark/
│ └── status.md # Claude slash command
├── codex/skills/
│ └── ark-status/
│ └── SKILL.md # Codex skill (description-routed)
└── opencode/commands/ark/
└── status.md # OpenCode slash command
Each platform reads its own subtree. The structural tests in crates/ark-core/src/templates.rs enforce that the three trees stay in sync:
every_claude_command_has_a_codex_skill_sibling— everytemplates/claude/commands/ark/<name>.mdmust have a matchingtemplates/codex/skills/ark-<name>/SKILL.md.every_claude_command_has_an_opencode_command_sibling— same, buttemplates/opencode/commands/ark/<name>.md.
So: add a Claude command, the test fails until you add the Codex skill and OpenCode command. Adding a Codex skill or OpenCode command without a Claude origin is allowed but unusual.
Frontmatter contracts
Each platform's frontmatter has a different shape, and the tests check the right one is in the right tree:
Claude (templates/claude/commands/ark/status.md):
---
description: Print the current task status.
argument-hint: <optional argument hint>
---
# `/ark:status $ARGUMENTS`
... slash command body ...
Codex (templates/codex/skills/ark-status/SKILL.md):
---
name: ark-status
description: Use this skill when the user wants to know the current task status...
---
# ark-status
... description-routed skill body ...
OpenCode (templates/opencode/commands/ark/status.md):
---
description: Print the current task status.
---
# `/ark:status $ARGUMENTS`
... slash command body ...
The test codex_skill_bodies_have_codex_frontmatter_not_claude_frontmatter catches copy-paste regressions where you accidentally drop a Claude header into the Codex tree. The test opencode_command_bodies_have_opencode_frontmatter_and_arguments_token checks OpenCode commands open with description: (no argument-hint:) and contain the verbatim heading # `/ark:<name> $ARGUMENTS` (with the actual command name substituted in).
Body translation
The bodies aren't byte-equal across platforms — they can't be:
- Claude bodies use slash-invocation idioms (
# /ark:foo $ARGUMENTS). - Codex skills use description routing — the body explains what the skill does without referencing slash syntax.
- OpenCode bodies are closest to Claude's, but with OpenCode-specific frontmatter.
Body content parity is not mechanically asserted. It's policed at code review when you edit a template. The structural tests catch the failure modes that matter (missing twin, wrong frontmatter); deeper drift is a review concern.
A reasonable workflow:
- Write the Claude command body first, get the workflow shape right.
- Mechanically translate to OpenCode (drop
argument-hint:, keep everything else). - For Codex, rewrite the body to drop slash references and lead with description-routing language.
Backing CLI surface
Most slash commands wrap a chain of ark agent calls. If your new command needs new subcommands (e.g. ark agent task status), add them in crates/ark-core/src/commands/agent/ first, then have the slash command bodies invoke them.
If your command only reads state (like /ark:status would), prefer extending ark context over adding a new ark agent subcommand. ark context is the semver-stable read-only surface; ark agent is the not-semver-stable mutation surface.
Testing
After your three template files exist:
cargo test --package ark-core templates # parity tests
cargo build --workspace # confirms include_dir!() picks up the new files
If you also added an ark agent subcommand, write integration tests under crates/ark-cli/tests/agent_lifecycle.rs exercising the new command end-to-end.
Don't forget
ark upgrade— your new template files are included in the embedded tree on the next CLI release. Existing projects pick them up viaark upgrade.- The
templates.rstests run in CI. A missing twin will fail the build, not just a test. - Update the book. Add a row to Claude Code, Codex, and OpenCode, and document the new command's purpose in Quick Start if it's user-facing.
Adding a Platform
Adding a fourth platform (e.g. Cursor, Gemini Code) is mostly a registry entry plus a template tree. The command bodies in init.rs / unload.rs / load.rs / remove.rs / upgrade.rs iterate the same &[&Platform] slice; no command-body refactoring required.
The Platform struct
Defined in crates/ark-core/src/platforms.rs:
#![allow(unused)] fn main() { pub struct Platform { pub id: &'static str, // canonical id, e.g. "cursor" pub cli_flag: &'static str, // the --<flag> name, e.g. "cursor" pub templates: &'static include_dir::Dir<'static>, pub dest_dir: &'static str, // e.g. ".cursor/commands/ark" pub removal_root: &'static str, // e.g. ".cursor" pub managed_block_target: Option<&'static str>, // e.g. Some("CURSOR.md") or None pub hook_file: Option<HookFileSpec>, // None if the platform has no hook contract pub extra_files: &'static [(&'static str, &'static str)], // (relative_path, contents) } }
The template tree at templates/cursor/... is captured at compile time via include_dir!. The dest_dir and removal_root should be cousin paths: dest_dir is where templates are extracted (a leaf), removal_root is where ark remove stops (a parent — usually the platform's top-level dir).
Steps
-
Add a template tree. Create
templates/cursor/commands/ark/{quick,design,archive,record}.md. Each one carries Cursor's frontmatter shape (whatever that platform expects) and a body translated from the Claude originals. -
Add an
include_dir!constant. Incrates/ark-core/src/templates.rs, addpub static CURSOR_TEMPLATES: Dir = include_dir!("$CARGO_MANIFEST_DIR/../../templates/cursor/commands");. -
Add a
Platformconst. Incrates/ark-core/src/platforms.rs:#![allow(unused)] fn main() { pub const CURSOR_PLATFORM: Platform = Platform { id: "cursor", cli_flag: "cursor", templates: &crate::templates::CURSOR_TEMPLATES, dest_dir: ".cursor/commands/ark", removal_root: ".cursor", managed_block_target: Some(".cursor/RULES.md"), // or whatever Cursor reads hook_file: None, // or Some(HookFileSpec { ... }) if Cursor has hooks extra_files: &[], }; } -
Register it. Add
&CURSOR_PLATFORMto thePLATFORMSslice. Order matters — the canonical iteration order is preserved acrossinit/upgrade/unload/load/remove. New platforms append. -
Add CLI flags. In
crates/ark-cli/src/main.rs'sInitArgs, add--cursor/--no-cursorflags. Update theflags()mapping to route"cursor"to thePlatformFlagstate. -
Update parity tests. If
templates/cursor/commands/ark/<name>.mdshould track Claude commands 1:1, add a parity test incrates/ark-core/src/templates.rsmatching the existingevery_claude_command_has_a_codex_skill_siblingshape.
Hook file specs
If your platform has a hook contract (a JSON / YAML file Ark needs to install entries into):
#![allow(unused)] fn main() { pub const CURSOR_HOOK_SPEC: HookFileSpec = HookFileSpec { path: ".cursor/hooks.json", hooks_array_key: "SessionStart", // the array under root.hooks that carries entries identity_key: "command", // the field on each entry that identifies it entry_builder: ark_cursor_hook_entry, // fn() -> serde_json::Value }; }
update_hook_file and remove_hook_file are parameterized over (path, hooks_array_key, identity_key); they handle the JSON surgery without you touching serde_json directly.
The hook file is not hash-tracked — it's re-applied unconditionally on every ark init / ark load / ark upgrade, like Claude's settings.json and Codex's hooks.json.
Managed-block file
If your platform reads a markdown file that Ark needs to inject content into (parallel to Claude's CLAUDE.md and Codex/OpenCode's AGENTS.md):
#![allow(unused)] fn main() { managed_block_target: Some(".cursor/RULES.md"), }
init writes a <!-- ARK --> managed block to that file (or merges into existing content). unload captures the block; load restores it; remove deletes it surgically (delimiters and all, surrounding content preserved).
Multiple platforms can share a managed-block target. The manifest dedupes on (file, marker), so e.g. Codex and OpenCode both writing to AGENTS.md results in one block on disk and one entry in the manifest.
Extra files
Files that need to ship but don't fit the template-tree shape (e.g. OpenCode's TypeScript plugin):
#![allow(unused)] fn main() { extra_files: &[(".cursor/plugins/ark.ts", CURSOR_ARK_PLUGIN_TS)], }
Each tuple is (relative_path, contents). The contents are baked into the binary at compile time. init writes them; upgrade re-applies them unconditionally (not hash-tracked).
Testing
The source-scan invariant tests in commands/init.rs, commands/upgrade.rs, etc. enforce that:
- All filesystem ops route through
LayoutandPathExt. - No bare
std::fs::*calls. - No hand-joined
.cursor/(or.ark/, etc.) literal paths in command bodies.
If you add a new platform, you probably don't need to touch these tests — they scan command bodies, not platform definitions.
Add unit tests for your platform's shape (shape, apply_managed_state round-trip, capture_hook / remove_hook semantics) in platforms.rs's test module. Pattern-match the existing opencode_* tests.
What you don't have to touch
- The command bodies in
init.rs,unload.rs, etc. iteratePLATFORMSand call methods on each&Platform. They never name a platform directly. ark contextand the slash-command flow are platform-neutral once your hook is installed.- The book's Platforms section gets a new chapter (per platform), but no other docs need surgery.
The whole point of the registry is that adding a platform is a registry entry, not a refactor.
Release Process
Ark uses dist for release automation. Tagging triggers builds for every supported target, GitHub Releases publication, and npm package publishing. The book deploys to GitHub Pages on the same tag.
Configuration
Cargo.toml's [workspace.metadata.dist]:
[workspace.metadata.dist]
cargo-dist-version = "0.30.3"
ci = "github"
allow-dirty = ["ci"]
installers = ["shell", "powershell", "npm"]
npm-package = "ark"
npm-scope = "@anekoique"
pr-run-mode = "skip"
publish-jobs = ["npm"]
targets = [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
]
install-path = "CARGO_HOME"
install-updater = false
The dist-generated workflow is at .github/workflows/release.yml — don't hand-edit unless you know what you're doing (the workflow is regenerated by dist generate).
Cutting a release
- Bump version. Edit
[workspace.package].versioninCargo.toml. Both crates inherit from the workspace; you bump once. - Update lockfile.
cargo build --workspace(or any cargo command that touches the lockfile). - Commit.
git commit -am "chore: bump to vX.Y.Z". - Tag.
git tag vX.Y.Z. The leadingvmatters — the book deploy workflow triggers onv*. - Push.
git push && git push --tags.
The push of the tag triggers two GitHub Actions workflows:
release.yml(dist) — builds binaries for every target, publishes a GitHub Release with all artifacts attached, publishes the npm package.book.yml— buildsdocs/book/with mdBook, deploys to GitHub Pages.
Verifying
After CI completes:
- The Releases page should list the new tag with shell/powershell/npm installers.
npm view @anekoique/ark versionshould return the new version.https://anekoique.github.io/ark/should show the new book.npm install -g @anekoique/ark@<version>thenark --versionshould return the new version.
Versioning policy
Ark follows semver. The visible CLI surface — ark init, ark load, ark unload, ark remove, ark upgrade, ark context — is covered. ark agent is not.
What counts as a breaking change for the visible surface:
- Removing or renaming a flag.
- Changing JSON output schema in a non-additive way (rename, type change). Adding fields is fine.
- Changing the on-disk format of
.ark/.installed.jsonor.ark.dbin a way that older versions can't read. - Changing the file layout of
.ark/,.claude/commands/ark/,.codex/skills/, or.opencode/commands/ark/.
What's not breaking:
- Adding a new top-level command.
- Adding a new flag.
- Changing slash-command body content (templates).
- Changing the internal
ark agentnamespace at all. - Adding a new platform.
When in doubt, bump major. The cost of an over-eager major bump is small; the cost of breaking users on a minor is large.
Pre-release checklist
Before tagging:
cargo build --workspace
cargo test --workspace
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
mdbook build docs/book # confirm book builds clean
Plus the smoke test:
cargo build --release
TMP=$(mktemp -d)
./target/release/ark load --dir "$TMP"
./target/release/ark unload --dir "$TMP"
./target/release/ark load --dir "$TMP"
./target/release/ark remove --dir "$TMP"
rm -rf "$TMP"
After release
- If you bumped the embedded template version (template files added/removed), tell users to run
ark upgradein their projects. The CLAUDE.md / AGENTS.md managed blocks and the SessionStart hooks are re-applied unconditionally, but template files honor the user-modification protocol — seeark upgradefor the conflict-resolution policy. - Watch the Issues page for the first 24 hours. Common reports after a release: install path issues, hook contract changes (Claude / Codex / OpenCode upstream changes),
npm installissues on M1.
One-time GitHub Pages setup
The book.yml workflow uses actions/deploy-pages@v4, which requires Pages to be enabled in repo settings:
- Go to
Settings→Pages. - Set
SourcetoGitHub Actions(notDeploy from a branch). - Save.
This is a one-time manual step. Once enabled, every tag push deploys the book automatically.