A Rust workspace of six binaries that coordinate local development for a monorepo. Devkit provides flock'd port and file-lock registries (both served from memory by an optional devkitd supervisor daemon), a supervised dev-app runner with baseline A/B comparison, and a single issue command covering the whole issue lifecycle (setup, triage, cleanup, PR status, dashboard, review). All project-specific details live in devkit.toml; the engine itself is project-agnostic.
Maintains a shared port registry so concurrent callers never collide on port allocation. State lives in ~/.local/state/devkit/ports.json, guarded by an advisory file lock. Reservation rows are written before any process binds, which prevents the allocation race across concurrent callers.
portm status # table of reserved/live ports
portm alloc <apps…> [--holder <path>] [--role issue|baseline] # alias: reserve
portm release [apps…] [--holder <path>] [--role …]
portm prune # remove stale reservations
--holder defaults to the current worktree's root (git rev-parse --show-toplevel). release with app names frees only those apps' reservations; it never signals processes (devrun down is stop-and-release).
Launches and supervises dev servers for one or more apps. Apps not explicitly named are auto-detected by diffing git diff <baseline_ref>...HEAD. When any webapp is selected, api is added automatically and FOUNDRY_API_BASE_URL is wired to the local api port. Each app's launch command is run verbatim with {{ port }} substituted; wrap it in doppler run in the config if the app needs Doppler-injected secrets. --role both runs the issue branch and a fresh origin/staging baseline side-by-side on separate ports for direct A/B comparison.
devrun up [apps…] [--role issue|baseline|both] [--env K=V] [--env-file F] [--dry-run]
devrun down [--role …] [--all | --others | --holder <path>] [--app …] [--older-than …] [--batch] [selector]
devrun status [--all]
devrun reap [--all]
devrun logs <app> [-f]
devrun config show [--origin] [--json]
devrun config apps [--json]
devrun config tasks [--json]
devrun task [<name>] [--env K=V] [--env-file F] [--dry-run]
-
config show: prints the effective merged config as TOML.--originannotates each value with the file it was resolved from (or# (default)for serde defaults);--jsonemits JSON instead of TOML.--origin --jsonemits{ "config": …, "origins": { "dotted.path": "file" } }. -
config apps: lists the configured apps from the merged config (columns: name, port, path, provides_url, url_env, launch).--jsonemits a structured array. A pure config readout with no live readiness — for running state usedevrun status. -
config tasks: lists the configured[tasks]from the merged config (columns: name, kind, app, description) — the same listing as a baredevrun task.--jsonemits a structured array. -
down: stops servers and releases their ports. By default stops every server in the current worktree. Reaching another worktree requires an explicit scope flag and a confirmation read from a terminal:Command Effect devrun downstop all servers in this worktree devrun down --role baselinethis worktree, baseline only devrun down apithis worktree, fuzzy-match apiacross columnsdevrun down --allevery server, every worktree (one batch prompt) devrun down --othersevery server in every other worktree devrun down --others apiapiin other worktrees (per-worktree prompts)devrun down --holder ../wt/feat-xone specific worktree devrun down --all --app api --older-than 1hprecise filter, all worktrees A positional selector substring-matches across
HOLDER/APP/PORT/ROLE/PIDand is mutually exclusive with the column filters (--app,--port,--role,--pid,--listening/--not-listening,--older-than).--older-thanaccepts90s,30m,2h,1d(bare number = seconds). Any selection that reaches outside the current worktree prints a preview and prompts; with no interactive terminal it is refused.--all/--batchcollapse the per-worktree prompts into one. -
status: lists tracked servers for this worktree (--allfor every worktree). Below the tracked table it shows an untracked (outside devrun) section: dev servers detected listening on a configured app's port band, or matching a configured app's launch signature, that the registry doesn't own — i.e. started outsidedevrun up. Read-only; reaping is a separate command. -
reap: kills dev servers found running outside devrun (this worktree by default;--allreaches every worktree). It prints the matched process trees, then requires an interactive terminal and a confirmation before sending SIGTERM (escalating to SIGKILL). There is no--yes/--forcebypass — with no terminal it refuses and kills nothing, so an agent (no PTY) can never reap. Detection is also available read-only to agents via theports.straysMCP action; killing is not. -
task: run a canned[tasks]oneshot or sequence (no name lists them).
One command covering the whole issue lifecycle. Global -C/--dir and --config flags sit on issue itself, before the subcommand (e.g. issue -C ~/Git/acme/monorepo status).
issue setup <ID> --slug <slug> --apps <a,b> [--dry-run] # id also accepted as --issue <ID>
issue checkout-pr <PR_LINEAR_ID_URL> [WORKTREE_PATH] [--setup [--apps a,b]]
issue status [ids…] # read-only triage table (also the bare `issue`)
issue info [selector] [--json] [--cache-only] # one worktree's PR number + Linear id (defaults to current)
issue end [ids…] [-y] [--force] [--pr-only] [--clean-worktree]
issue prs [-m|--mine] [-r|--reviews] [-R owner/repo] [--no-cache] [--batch-size <N>] [--retries <N>]
issue dashboard [--bucket auto|day|week|month] [--chart bar|line] [--mode absolute|proportional] [--all-roles] [--author <email>] [--no-plots] [--no-cache]
issue review request [<body>] --to <alias|#channel> [--base <branch>] [--pr-title <t>] [--pr-body <b>] [--no-push] [--arg k=v]
issue review finish [<body>] [--pr <number>] [--to <alias|#channel>] [--arg k=v]
setup: mechanical start of a Linear issue. Creates a worktree off the baseline ref, symlinks env files, runsbun install, and prints a JSON summary. It does not reserve ports —devrun upallocates them dynamically when the worktree's servers start.checkout-pr: checks out an existing PR branch into a new worktree (unlikesetup, which creates a new branch). The target may be a GitHub PR number (#3340), a bare number (3340— probed against both GitHub and Linear when a Linear key is configured, prompting on a real collision in a TTY and erroring if ambiguous without one; treated as a GitHub PR when no Linear key is present), a Linear id (ENG-3340, whose attached PR is used — an issue with no attached PR is an error), or a GitHub/Linear URL. The worktree directory is named by thetemplates.checkout_worktree_dirtemplate (variables:pr_number,pr_title,linear_id,linear_title; titles are slugified;linear_*are empty on the PR-only path); the default renders e.g.3340-fix-login, or3340-fix-login_[ENG-42]when reached via Linear. Pass[WORKTREE_PATH]to override the placement. The PR's own branch name is kept; the template governs only the directory. Add--setup [--apps a,b]to also run the per-app prep pipeline, exactly asissue setupdoes. The worktree gets a.devkit/issue.tomlrecord soissue status/issue endrecognise it.status(the default when you run bareissue): triage table of every issue worktree. A worktree is FINISHED only when its PR is MERGED, its Linear issue is Done, and the working tree is clean.info: shows one worktree's PR number and Linear id. The optional selector is an issue id, branch, worktree basename, or path; omit it for the current worktree.--jsonemits a single machine-readable object (theIssueWorktreestruct, withpr_number/issue_idfor scripts).--cache-onlyskips the network — the PR number comes from the per-worktree cache at<worktree>/.devkit/pr.jsonand Linear state renders as—. A live run writes the PR through to that cache, whichgit worktree removedeletes with the worktree.end: removes FINISHED worktrees.--pr-onlyignores the Linear and issue-id gates (finished = PR merged + clean, even without a Linear-style branch name);--clean-worktreetargets explicit selections;--forceoverrides the dirty-tree guard;-yskips confirmation.prs: GitHub PR triage of your open PRs and PRs awaiting your review, with a per-repo diff cache that rendersold → newfor anything changed since the last run. The three searches (authored, review-requested, reviewed-by) run concurrently, each paged at--batch-size(default 25) and followed to exhaustion, so the table is complete however many PRs are open. Lower the batch size if GitHub answers a page with HTTP 504 — the per-PR check and review selections are what make a page expensive.--retries <N>(default 0) re-attempts a failed page with backoff.dashboard: the triage + PR tables, plus terminal timelines of your Linear issues by status, PRs opened/merged, and commits over time (--chart barorline). The timeline fetches (Linear + GitHub) are cached under~/.cache/devkit/dashboardfor a few minutes so reruns are fast; the live triage/PR panel is never cached.--no-plotsshows only the tables;--no-cacheforces a fresh fetch.review: two subcommands —review request(push, open/reuse PR, add GitHub reviewers, Slack them) andreview finish(Slack the PR author when you are done reviewing). See below.
On a TTY, issue and issue info draw the triage table immediately and fill in each cell with an animated braille spinner as git, GitHub, and Linear data land. issue prs shows the previous run's tables dimmed with a fetch spinner below noting they are as of the last run (stale-while-revalidate), then swaps the fresh tables in place — the two renders are line-for-line parallel, so the screen does not shift. The step-driven commands (checkout-pr, setup, end, review) keep every completed step on screen as a numbered ✓ log line with its elapsed time. All of this live rendering goes to stderr and is TTY-gated — stdout, piped output, and redirected output are unaffected.
Push the branch, open or reuse the PR, request review on GitHub, and Slack the reviewers.
issue review request "ready for a look" --to igor
issue review request --to igor --to '#eng' --arg team=infra # body optional; channel + people
issue review request # re-ping the PR's existing reviewers--to <alias|#channel>(repeatable). People are added as GitHub reviewers (those with agithubhandle) and Slacked;#channelsare Slack-only. Omit--toto re-request and Slack the PR's current human reviewers.--base,--pr-title,--pr-body,--no-pushas before.--arg key=value(repeatable) overrides a variable declared in[templates.variables].
Announce over Slack that you finished reviewing. Posts nothing to GitHub.
issue review finish "LGTM, merging after CI" # inside the PR's worktree → notifies the author
issue review finish --pr 1234 --to lev # from anywhere, explicit PR + recipient- Resolves the PR from the current branch, or
--pr <number>when run outside a worktree. - Defaults to notifying the PR author;
--tooverrides (repeatable, people or#channels). --arg key=valueas above.
Templates: review_request and review_finish under [templates]. Per-recipient render fields: name (alias or channel), slack_id (user id, empty for channels), plus pr_url, pr_title, input (and author for finish).
issue and devrun accept a global --timing flag that prints a per-operation
breakdown of subprocess and network IO to stderr on exit:
--timing(or--timing=summary) — a table of ops (git fetch,github REST,linear graphql, …) with count / total / max / p50, plus a headline showing wall time, IO-busy time, serial sum, and the concurrency factor the parallel fan-outs achieve.--timing=trace— additionally lists every op with its start offset, thread, and full command line.--timing-log <FILE>— streams one JSON record per op (op,detail,start_ms,dur_ms,thread) for comparing runs.
DEVKIT_TIMING=summary|trace enables the summary/trace form without the flag.
stdout (tables, --json) is never affected. Example:
issue status --timing
Advisory locks on paths so parallel sessions sharing one checkout (where per-session
worktrees are too expensive) don't edit the same files at once. A flock-guarded
registry of claims keyed by path, the file-level twin of portm. Locks are
exclusive and overlap by path component, so locking a directory conflicts with
locking a file inside it.
lockm acquire <paths…> [--as <id>] [--note <msg>] [--ttl <secs>] [--json]
lockm release <paths…> [--as <id>] # or: release --all
lockm check <paths…> [--json] # read-only: would acquire succeed?
lockm status [--all] [--json] # alias: list
lockm prune
Sessions identify themselves by (in priority order) --as <id>, $DEVKIT_SESSION,
$TMUX_PANE (zero-config and unique per tmux pane), the controlling tty, or the
parent pid. Conflicts fail fast: acquire/check exit 1 and report who holds the
path. Locks expire after their TTL (default 30 min, --ttl 0 disables) or when a
recorded anchor pid dies; release frees them explicitly. For non-interactive agent
sessions, pass a stable --as/$DEVKIT_SESSION so acquire and release agree.
Configures and diagnoses the toolkit itself. auth validates a Linear or Slack
credential against the live API and stores it in ~/.config/devkit/secrets.toml
(0600); doctor reports where each credential resolves from and whether it is
valid. Tokens always resolve env-first, so a shell export or Doppler-injected var
still wins.
devkit auth <linear|slack> [--token <value>] # validate + store; prompts (no echo) by default
devkit doctor [--json] # check configured credentials
devkit brief # compact project brief (apps, tasks, live servers)
devkit completions <shell>
auth: prompts for the token without echo (or reads--token/piped stdin), validates it, and saves it. For Linear it also stores the workspace slug derived from the API, so issue links work without settingLINEAR_WORKSPACE.doctor: one row per credential — source (env/file/unset) and live validity. Exits non-zero only when a credential that is set fails validation. Also warns when the installed binaries are older than the newest devkit plugin checkout in~/.claude/plugins/cache(skewed binaries make agents follow docs for features the binaries lack), when servers run outside devrun, and when the docs cache holds unreferenced checkouts.brief: prints a compact orientation for the current checkout — configured apps, the[tasks]table, and this worktree's live servers — and prints nothing outside a devkit-managed project (no projectdevkit.tomlfound and no configured app directory present under the worktree root). The plugin'sSessionStarthook runs it so coding-agent sessions start already knowing the project's apps and tasks.
Version-correct local library checkouts backing the devkit:docs skill.
Register a library once; every lookup resolves the version the current
project's lockfile pins and materializes a detached worktree for it under
~/.local/share/devkit/docs/.
docm add tokio # registry lookup (crates.io/npm/PyPI)
docm add https://github.com/godotengine/godot --ref 4.3-stable
docm add react --project # write to this repo's devkit.toml [docs]
docm info tokio # path + version + layout map + notes
docm path tokio # just the checkout path
docm sync # fetch clones, move default worktrees
docm prune # drop checkouts no live project referencesGlobal manifest: ~/.config/devkit/docs.toml. Per-project overlay:
[[docs.libs]] entries in devkit.toml (same fields; partial entries
override the global entry field-by-field). Resolution: manual ref pin →
lockfile version (Cargo.lock, pnpm-lock.yaml, package-lock.json,
bun.lock, uv.lock) → git tag → default branch fallback.
devkit-mcp exposes devkit's port and file-lock coordination to MCP-capable
coding agents over stdio. It presents two tools:
devkit_describe: list the available actions, or fetch one action's argument schema ({"action": "locks.acquire"}).devkit_call: invoke an action, e.g.{"action": "locks.acquire", "args": {"root": "/path/to/repo", "paths": ["src/a.rs"]}}.
v1 actions: ports.{status,alloc,release,prune} and
locks.{acquire,check,release,status,prune}. Pass root (the project path) on
every lock call and on ports.alloc/ports.release. For locks, holder is a
session identity minted from $DEVKIT_SESSION (or a per-process id). For ports,
holder defaults to root (the worktree path the registry uses to track liveness).
Either can be overridden per call.
Phase-2 devrun actions: devrun.status (tracked servers for a worktree, or
all), devrun.up (start servers, non-blocking: returns each server
starting; poll devrun.status for readiness), devrun.down (stop + release
a worktree's servers), and devrun.logs (tail a tracked app's log). All take
root (the worktree); up is issue-role only and starts servers under a
running devkitd when present, else detached.
The MCP server also exposes two read-only issue actions: issue.status lists
the issue worktrees for a directory (root, default .; optional ids filter)
with each one's PR state, Linear state, and a finished/not-finished verdict;
issue.prs triages your GitHub PRs (mine, reviews, neither set means both;
optional repo). Both return structured JSON with the verdicts and next-action
labels pre-computed. They never mutate; issue review/issue end stay CLI-only.
See Installing for coding agents below for how to register it — either bundled in the devkit plugin or as a standalone MCP server, per host.
devkit ships two things for agents:
- a plugin — the
using-devkitskill, session-start hooks, and thedevkit-mcpserver, bundled together; and - the
devkit-mcpserver on its own, for hosts without a plugin system.
Either way the binaries must be on your PATH — the plugin's MCP entry and
every config below invoke devkit-mcp by name (see Install for
feature flags).
The plugin bootstraps them for you. Claude Code, Codex, and Cursor have no
install-time hook, so a session-start hook checks for devkit, lockm, and
devkit-mcp and, when they are missing, runs the dist
installer for the GitHub release matching the plugin's own version — so the
binaries stay in lockstep with the hooks and MCP server that drive them. It
re-runs on plugin update, when the version moves.
Two cases where it stays out of the way:
- Binaries already on
PATHthat the hook did not install (cargo install, a distro package, a source build) are never overwritten — the hook records them as externally managed and leaves upgrades to you. DEVKIT_NO_BOOTSTRAP=1disables it outright.
A failed install (offline, say) never blocks the session; it warns and does not
retry until you resolve it or delete
${XDG_STATE_HOME:-~/.local/state}/devkit/bootstrap-failed.
On Windows the hook runs under Git Bash when it is present and under PowerShell otherwise, so it does not depend on a bash being installed. Both paths resolve the same state directory, so gaining Git Bash later does not reinstall.
To install the binaries yourself instead, see Install:
cargo install --path .Installing the plugin registers the skill, the hooks, and the MCP server in one
step — the plugin manifest points at .mcp.json, so enabling the plugin starts
the server automatically.
From the terminal:
claude plugin marketplace add AbysmalBiscuit/devkit # or a local path to this repo
claude plugin install devkit@devkitOr in a session, same arguments (/plugin alone opens the interactive browser):
/plugin marketplace add AbysmalBiscuit/devkit
/plugin install devkit@devkit
Restart Claude Code so the hooks load, then run /mcp to confirm the devkit
server is active and devkit_describe/devkit_call are listed.
MCP server only (no skill/hooks): the repo ships .mcp.json at the root, so
opening this repo in Claude Code registers the devkit server project-scoped.
From the terminal:
codex plugin marketplace add AbysmalBiscuit/devkit # or a local path / git URL
codex plugin add devkit@devkitCodex registers the using-devkit skill natively from the plugin manifest, so
it is announced in every fresh session, and starts the bundled devkit MCP
server. Confirm with codex plugin list and codex mcp list.
MCP server only (no skill/hooks): the repo ships .codex/config.toml with
[mcp_servers.devkit], registering it project-scoped. Project MCP servers load
only in trusted projects, so trust this repo when Codex prompts.
Cursor has no git-repo plugin install from the CLI. Install the plugin from the
Customize panel in the sidebar, or, for a team, from Dashboard → Plugins →
Team Marketplaces → Add Marketplace → Import from Repo
(AbysmalBiscuit/devkit). For local development, symlink the checkout:
ln -s "$(pwd)" ~/.cursor/plugins/local/devkitMCP server only (no skill/hooks): the repo ships .cursor/mcp.json (same
mcpServers shape as Claude Code's), registering it project-scoped.
No plugin manifest exists for these. Register devkit-mcp as a stdio MCP server
in the host's own config — the command is just devkit-mcp (on PATH after
cargo install) — and point the agent at AGENTS.md for context. Zed reads
AGENTS.md directly.
After wiring up any host, confirm devkit_describe and devkit_call appear.
Config is layered. Every devkit.toml from the filesystem root down to the cwd is merged, with ~/.config/devkit/config.toml as the lowest-precedence base layer beneath them all. Deeper files override shallower ones per value: tables merge key-by-key, while scalars and arrays replace wholesale. devrun config show prints the merged result; --origin traces each value to the file it came from.
Two escapes bypass the walk:
[config] root = truein adevkit.tomlstops the upward walk at that file and drops every shallower layer, including the home config — full isolation.--config <path>or$DEVKIT_CONFIGselects a single file verbatim, with no layering or home base.
App path is normally inferred from the monorepo's doppler.yaml; individual [apps.<name>] sections may override it with an explicit path. launch is run verbatim, so a Doppler wrapper lives in each app's launch; devkit refuses to start a Doppler launch whose config resolves to prd, so it can't run against production secrets.
App conventions are config-driven, not hardcoded:
provides_url = truemarks the app that serves the URL other apps consume (the API). Consumer apps name that variable in their ownurl_env;devrunwires it to the provider's local port and auto-includes the provider when a consumer is run.prep_filesdeclares files written into an app's directory duringissue setup(before itssetupcommands). Each is{ path, content, overwrite };contentis rendered as a minijinja template with the issue context (prefix,issue,slug,apps,app,branch,worktree), and existing files are kept unlessoverwrite = true.defaults.apps_dir(defaultapps) is the repo-relative directory apps live under; it drives path inference and diff-based app detection.
The config is personal (worktree paths, app catalog, teammate handles, local
secrets) and is not distributed; keep it out of version control. See
docs/configuration.md for the full config reference
and a sanitized example. Copy that example to your config location and edit it:
mkdir -p ~/.config/devkit
$EDITOR ~/.config/devkit/config.toml # paste & adapt the example from docs/configuration.mdissue setup and issue review render five strings from optional minijinja
templates under [templates]. Each unset key falls back to a default that
matches the historical hardcoded output.
[templates]
branch = "{{ prefix }}{{ issue }}-{{ slug }}"
worktree_dir = "{{ slug }}"
pr_title = "{{ issue }}: {{ input }}"
pr_body = "Closes {{ issue }}.\n\n{{ input }}"
review_request = "{{ input }} {{ pr_url }}"
review_finish = "{{ input }} {{ pr_url }}"
[templates.variables] # constants; a context field of the same name wins
team = "platform"| Key | Default | Context |
|---|---|---|
branch, worktree_dir |
{{ prefix }}{{ slug }}, {{ slug }} |
prefix, issue, slug, apps |
checkout_worktree_dir |
{{ pr_number }}-{{ pr_title }} (or {{ pr_number }}-{{ pr_title }}_[{{ linear_id }}] via Linear) |
pr_number, pr_title, linear_id, linear_title |
pr_title |
{{ input }} |
review base + input = --pr-title |
pr_body |
{{ input }} |
review base + input = --pr-body, pr_title |
review_request |
{{ input }} {{ pr_url }} |
review base + input = body arg, pr_title, pr_url, name, slack_id |
review_finish |
{{ input }} {{ pr_url }} |
pr_url, pr_title, author, input, name, slack_id |
Review base context for review_request: branch, issue/slug/apps from
the .devkit/issue.toml record issue setup writes in the worktree, plus
pr_url, pr_title, and per-recipient name/slack_id. issue setup also
adds .devkit/ to your global gitignore (--no-gitignore skips it).
An undefined variable is an error (strict mode), so typos surface immediately.
The quickest path — the dist-generated
installer downloads the matching binaries from the latest GitHub release,
verifies checksums, and puts them on your PATH:
# Linux / macOS / WSL
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/AbysmalBiscuit/devkit/releases/latest/download/devkit-installer.sh | sh# Windows (PowerShell)
irm https://github.com/AbysmalBiscuit/devkit/releases/latest/download/devkit-installer.ps1 | iexPin a specific release by swapping latest/download for download/v0.8.0.
Prebuilt targets are Linux x86_64 (gnu + musl) and arm64, macOS x86_64 and
arm64, and Windows x86_64 and arm64. Upgrade in place later with devkit-update.
The installer places only the binaries. To use devkit inside a coding agent, register the plugin afterward — see Installing for coding agents.
Install all binaries (portm, devrun, issue, lockm, devkit,
devkit-mcp, devkitd) into ~/.cargo/bin with one command — from a clone:
cargo install --path .or straight from GitHub without cloning:
cargo install --git https://github.com/AbysmalBiscuit/devkit --forceThis builds with default features, which include the devkitd supervisor daemon.
devkitd serves both the port registry (ports.sock) and the lock registry
(locks.sock) from memory, writing through to the files, and is used by
devrun up --supervise. To skip the daemon, build a lean set with
--no-default-features (omits devkitd and devrun's --supervise support).
Or just build into target/release without installing:
cargo build --releaseThe CLIs generate their own completion scripts via a completions <shell>
subcommand (bash, zsh, fish, elvish, powershell). For example:
issue completions zsh > ~/.zfunc/_issue
devrun completions zsh > ~/.zfunc/_devrun
portm completions zsh > ~/.zfunc/_portm
lockm completions zsh > ~/.zfunc/_lockm
# bash:
issue completions bash > ~/.local/share/bash-completion/completions/issue| Data | Path |
|---|---|
| Port registry | ~/.local/state/devkit/ports.json |
| Server logs | ~/.local/state/devkit/logs/ |
| File-lock registry | ~/.local/state/devkit/locks.json |
PR diff cache (issue prs) |
$XDG_CACHE_HOME/devkit/pr-status/ (or ~/.cache/devkit/pr-status/) |
| Docs library manifest | ~/.config/devkit/docs.toml |
| Docs library checkouts | $XDG_DATA_HOME/devkit/docs/ (or ~/.local/share/devkit/docs/) |
The state home honors $XDG_STATE_HOME (default ~/.local/state). A legacy
~/.claude/state/devkit home is migrated automatically on first run.
Required:
gitgh(GitHub CLI, authenticated)
Optional:
doppler: only if an app'slaunchwraps its command indoppler run(see docs/configuration.md)$LINEAR_API_KEY: enables the Linear issue-Done gate inissue status/issue endand the issue timeline inissue dashboard$LINEAR_WORKSPACE: enables clickable Linear issue links inissue status$SLACK_TOKEN: letsissue reviewpost the reviewer message directly (otherwise it emits aSlackIntentJSON object)
Each of these resolves env-first, then from ~/.config/devkit/secrets.toml. Run
devkit auth <linear|slack> to store them, or devkit doctor to check them.
Recoverable failures print the full error context chain. On a panic, the binary
prints a bug report with the location and a backtrace. For a backtrace on either,
set RUST_BACKTRACE=1.