README
¶
stint — project manager for Claude agents
The little stint — a tiny, tireless migratory wader. Each agent takes its stint, then hands off.
stint is a small SQLite-backed CLI that lets a fleet of Claude agents
(planners, plan reviewers, developers, reviewers, testers, commit
gatekeepers) collaborate on a project without stepping on each other. The
project manager (you, in Claude Code) orchestrates them by reading and
writing project state through this CLI.
Every command goes through a single binary, stint, that writes to one
SQLite file per project at .stint/stint.db. Pure-Go SQLite (no CGO);
single static binary; agents read JSON, humans can ask for tables.
Why
Agents need:
- a shared, durable view of the work,
- a way to claim a task atomically (no double-grabs),
- the whole plan as context, not just the line they're implementing,
- a gated plan review so design mistakes don't ride into implementation,
- a parseable interface (JSON envelope, predictable error codes).
Install
stint is a single static binary (pure-Go SQLite — no system dependencies).
Homebrew (macOS / Linux):
brew install icento/tap/stint
Scoop (Windows):
scoop bucket add icento https://github.com/icento/scoop-bucket
scoop install stint
Install script (Linux / macOS / WSL):
curl -sSL https://raw.githubusercontent.com/icento/stint/main/install.sh | sh
Windows (PowerShell):
irm https://raw.githubusercontent.com/icento/stint/main/install.ps1 | iex
Go (needs Go 1.26+):
go install github.com/icento/stint/cmd/stint@latest
Prebuilt binaries: grab a .tar.gz / .zip for your platform from the
latest release, verify it
against checksums.txt, extract, and put stint on your PATH.
Verify any install with stint version.
Five-minute tour
cd path/to/your/project
stint init --name "my-thing" --description "what it is"
# 1. a planner agent drafts a plan (or you do it by hand)
stint plan create --title "MVP" --perspective "infra" --by planner-1
stint phase create --plan mvp --title "Schema"
stint phase create --plan mvp --title "API"
stint task create --phase 1 --title "Add tables" \
--role developer --description "users, sessions, audit_log" \
--acceptance "migration runs clean; smoke test passes"
stint task create --phase 2 --title "Build handlers" \
--role developer --description "POST /login, POST /logout" \
--acceptance "200 on happy path; 401 on bad creds" --deps 1
# 2. open the multi-perspective review gate (see "Plan review gate")
stint plan submit mvp
# 3. an agent picks up the next thing it can work on, atomically
stint task next --role developer --agent dev-7
# 4. the agent grabs full context — or a ready-to-paste prompt
stint context 2
stint task brief 2 # rendered prompt + <stint-context> fence
--plan accepts a slug or a numeric id; slugs survive migrations and
are preferred in scripts.
Hierarchy
project (1 per DB)
└── plan* (multiple — planner perspectives or sequential plans)
└── phase* (ordered)
└── task* (ordered, can depend on other tasks across phases)
└── note* (append-only log: progress, review, blocker, decision)
A plan is a perspective or a sequenced roadmap. A phase groups related work inside a plan. A task is the atomic unit an agent claims.
Agent roles
planner, developer, reviewer, tester, commit. (A sixth, the
plan reviewer, gates plans rather than claiming tasks — see "Plan
review gate".) Roles are stamped on tasks (--role) so the CLI can hand
the right work to the right agent via stint task next --role ….
Plan review gate
A plan does not become claimable just because someone drafted it. Plans have their own lifecycle:
draft ─→ in_review ─→ approved (claimable terminus)
↑ ↓
└── rejected ──(stint plan amend)─→ draft
│
└── archived | abandoned (terminal)
The orchestrator opens the gate with stint plan submit <id-or-slug>; the
plan's review_perspectives column (e.g. ["pragmatic", "security", "perf"]) names which lenses must each sign off. Plan reviewers — one per
perspective, run in parallel — call stint plan approve --perspective P
or stint plan reject --perspective P --reason "…". The plan flips to
approved only once every declared perspective has approved (an
AND-gate); a single rejection flips the whole plan to rejected
immediately. A plan with no declared perspectives short-circuits to
approved on submit.
stint task next refuses to claim tasks from non-approved plans and
returns code: plan_not_approved so the orchestrator surfaces the gate
rather than silently stalling. To start over after a rejection, run
stint plan amend --reason "…", edit the plan, and stint plan submit
again. stint plan review-list shows the per-perspective verdict matrix.
Task lifecycle
todo → in_progress → review → done
↘ blocked / abandoned
| Edge | Verb |
|---|---|
todo → in_progress |
stint task claim |
in_progress → todo |
stint task release |
in_progress → review |
stint task update --status review (developer hand-off) |
review → done |
stint task approve |
review → in_progress |
stint task reject |
done → todo |
stint task reopen |
abandoned → todo |
stint task reopen |
stint task claim <id> --agent Xatomically movestodo → in_progress. A second agent racing for the same task gets{"ok": false, "code": "conflict"}and exit code 1.stint task next --role <r> --agent <id>finds the lowest-seq ready task, claims it in one shot, and (if the only candidates live in non-approved plans) returnsplan_not_approved. Pass--agents a,b,cto fan out N claims in one round-trip.- Reviewers do not re-claim a
review-status task — the developer retains the claim, andstint task rejectdrops the task back toin_progresswithout changing hands. Reviewers only claim dedicated reviewer-role tasks (design docs, threat models).stint task approveandstint task rejectonly acceptreviewas source; other sources returncode:invalidpointing at the right verb. stint task reopen <id> --body "…"is the only path fromdoneorabandonedback totodo. It atomically opens akind=test,severity=majorissue (override with--severity), clearsreviewed_by, and audit-logsreopen:<note-id>.
task_deps is a DAG; adding a cycle is rejected up-front. A tester-role
task counts a dependency as satisfied when the upstream is done or
review — testers can write tests against just-handed-off code so the
reviewer sees code + tests together. Every other role keeps strict
done-only semantics.
Heartbeats and stale claims
Any mutation by the claimant (notes, links, status changes, dep edits)
auto-refreshes heartbeat_at. An agent that goes genuinely silent for
10 minutes can call
stint task heartbeatto forestall reclamation, but the explicit call is rarely needed. To GC crashed agents:
stint task reclaim --ttl 10m # ttl must be > 0; --ttl 0 is rejected
Phase / plan rollup
When a task moves to done, the parent phase recomputes: if every task in
the phase is done or abandoned, the phase flips to done in the same
transaction. The plan rolls up the same way to archived when every
phase is done. Rollup is one-way (reopening a task does not demote a
phase you set manually), and refuses to promote while any task carries an
open kind=blocker, severity=blocker issue — the audit log records
rollup_blocked:<csv-of-note-ids> so the orchestrator can see which
issues to resolve.
abandoned does not trigger rollup. A phase whose last task was abandoned
will linger until another task transitions to done.
Notes and issues
Notes are an append-only log on every task (progress, review,
blocker, decision). A note with a non-empty severity is also an
issue — same row, queryable as a resolvable workflow item.
Severity rubric
info— observation, no action required.minor— small fix; can ship without it but should be tracked.major— must be fixed before this task's review re-approval.blocker— must be fixed before the parent phase can roll up todone.
Severity is mandatory on stint issue open (empty severity returns
code:invalid) so an issue can never sit unrouted.
stint issue open --task <id> --kind <review|blocker|test> --severity <s> --body "..."
stint issue list [--open|--closed] [--task N] [--severity ...]
stint issue resolve <note-id> --body "..." # double-resolve returns code:invalid
stint issue link <note-id> --to-task <other-id> # cross-task ref via task_links (kind=issue)
stint issue list --open hits a partial index so the open-issue queue
stays fast as the notes log grows.
The context bundle and the brief
stint context <task-id> returns one JSON blob shaped for agents — the
task body plus everything an agent needs around it:
project,plan,phase— identifying metadatatask— full body +depends_on/blocksid listssibling_tasks— compact summaries of every task in this phasephases_in_plan— compact phase outlinedepends_on_detail/blocks_detail— compact upstream/downstream summariesrecent_notes— last 20 notes on this taskskills,docs— bound playbooks and design docs (see "Plan import")
stint task brief <id> goes one step further: it emits a ready-to-paste
subagent prompt to stdout (plain text, not JSON) — a hat header naming
the role, a STINT_ACTOR reminder, the acceptance bullets, the context
JSON inside <stint-context> fences, and the standard return-footer
contract every subagent honors. This is the one-shot dispatch primitive
the orchestrator uses to hand work off.
Orchestrator playbook
The orchestrator (Claude in the project-manager seat) drives a project by delegating to subagents — never by doing role work inline. A typical loop:
- Plan. Dispatch a planner subagent to draft a plan, then submit it
to the review gate:
stint plan submit <slug>. Fan out one plan reviewer per perspective in parallel; the plan flips toapprovedonly when every perspective signs off. - Dispatch. For each ready task, render a brief and spawn the role's
subagent:
stint orchestrate next # best single task to dispatch stint orchestrate next-batch --max 4 # maximal scope-disjoint batch stint task next --role developer --agents dev-a,dev-b,dev-c stint task brief <task-id> | pbcopy # or pipe to the subagenttask nextorders by(assignee-match, priority desc, phase/seq), so--assigneeis a soft hint and--priority 2(urgent) leapfrogs lower-priority work. - Track. Stream the audit log instead of polling:
stint events --follow --max-events 100 --max-runtime 30m stint task watch <id> --until review,done --timeout 30m - Promote. When a developer transitions a task to
review, dispatch a reviewer subagent.approvelands it atdoneand the next dependent task becomes eligible.rejectsends it back to the developer with a mandatory body — the claim never transfers. - Verify. Committer/tester roles can prove their work by running the
suite through
stint task verify <id> --transition review— exit code and tail-of-output land as aprogressnote, and the task only transitions on exit 0. See "Executable acceptance" below. - Land. Wrap up a finished phase as one commit with
stint phase land <phase>— see "Landing a phase as one commit".
Parallelism: scope, locks, worktrees
stint lets several agents run at once, but does not merge their edits. Three layers keep collisions from ever happening:
Scope-aware batches
A task carries an optional scope — a JSON array of path/package
tokens ("which files this task touches"), set at plan-import time. The
orchestrator dispatches tasks whose scopes are pairwise disjoint.
stint orchestrate next-batch # up to 8 disjoint ready tasks
stint orchestrate next-batch --role developer --max 4
stint orchestrate overlap-check --tasks 41,42,43 # vet a wave: empty pairs = safe
Two tasks conflict when either has an empty scope (unknown footprint,
treated as conflicting with everything) or any path-prefix token overlap:
internal/scaffold collides with internal/scaffold/sub (ancestor) but
not with internal/scaffolding (sibling). A root token . or / names
the whole tree and conflicts with everything. --max defaults to 8,
clamped to 1..200; --tasks is capped at 100.
Semantic locks
For collisions that aren't path-shaped (a migration that touches the live
DB; a release branch), tasks declare locks at plan-import time
alongside scope. overlap-check reports a shared lock "x" reason when
two tasks hold the same lock token, regardless of file footprint.
One DB across git worktrees
The DB file is resolved in strict precedence: --db flag > STINT_DB
env > walk-up for .stint/stint.db. A non-empty STINT_DB pointing at a
missing file is code:not_found (not silent fall-through). For parallel
worktrees, bootstrap from the main tree's DB:
export STINT_DB="$(cd /path/to/main-tree && stint root | jq -r .data.db_path)"
# every stint command in this worktree now shares the one project DB
stint root emits {project_root, db_path} without opening the DB,
so a script can resolve and re-export before any command runs.
Out of scope: region-level merge. stint does not merge two agents' concurrent edits to the same file. Scope/lock overlap is computed at path granularity; if two tasks genuinely must touch the same file, sequence them with a dependency rather than parallelizing and merging.
Executable acceptance
A task carries an optional acceptance_check — a shell command, set
at import time — that turns prose acceptance criteria into a tool-run
gate. stint task verify runs it with a strict three-tier precedence:
--cmdgiven → it overrides everything for that one run.- No
--cmd, but the task has a storedacceptance_check→ it runs. - Neither → fall back to the project verify template.
stint task verify 42 # run stored acceptance_check
stint task verify 42 --cmd "go vet ./..." # ad-hoc override
stint task verify 42 --transition review # on exit 0, also move the task
Exit 0 → ok:true; non-zero exit → ok:false, code:invalid with the
run captured under data.verify. If no tier produces a command you
get ok:false, code:invalid naming the gap ("nothing to run") — a
gate that never ran is deliberately distinct from one that ran and
failed. The note header records which source ran (--cmd, stored acceptance_check, or scope template) so a one-off re-run isn't
mistaken for the task's own recorded gate.
The project verify template
The third tier lets one project-wide command stand in for tasks that haven't authored their own check. Set it once; substitute the task's scope per run:
stint config set verify-template "npx tsc --noEmit && npx prettier --check {scope}"
stint config set verify-template "ruff check {scope} && pytest"
stint config get verify-template
stint substitutes {scope} with the task's path.Clean'd, space-joined
scope tokens. Two guardrails: a template without {scope} is
scope-blind and refused (it would run identically for every task — put a
whole-tree gate in each task's acceptance_check instead); a task with
empty scope leaves nothing to substitute and reports "nothing to run".
Landing a phase as one commit
stint phase land <phase> is a gated git commit for a whole phase —
the one verb that mutates your working tree rather than just the DB:
stint phase land 7 # generated message
stint phase land 7 --check "go test ./..." # gate on a command first
stint phase land 7 --message "Land schema" # override the generated subject
stint phase land 7 --dry-run # report the would-commit set
Four gates in increasing cost order; refuses (code:invalid) at the
first that fails:
- All tasks done. Lagging tasks are named.
- Declared file scope. The commit set is the union of the phase
tasks'
kind=filelinks — and only that. Any tracked, modified file outside the set refuses the land and names the stray.phase landnevergit add -A. --check(optional). A non-zero exit refuses the land.- Commit. Stages exactly the declared files with an explicit
git add -- <paths>vector and runsgit commit(no--no-verify, so pre-commit hooks still run).
On success it records the resulting SHA as a kind=commit link on every
task in the phase. File-link paths that are absolute or escape the repo
via .. are rejected before staging; every git call uses an explicit
argument vector (no shell).
Plan import
A planner emits one JSON blob; stint plan import resolves the whole tree
in one transaction. Dependencies use string keys that get translated to
real ids; tasks carry the rich fields the orchestrator needs downstream.
stint plan import --file - <<'JSON'
{
"title": "MVP",
"perspective": "pragmatic",
"review_perspectives": ["pragmatic", "security"],
"phases": [
{ "title": "Schema", "tasks": [
{ "key": "db",
"title": "Add tables",
"role": "developer",
"acceptance": "migration runs clean",
"scope": ["internal/db"],
"locks": ["schema"],
"acceptance_check": "go test ./internal/db/...",
"skills": ["stint-developing"],
"docs": ["ARCHITECTURE.md"]
}
]},
{ "title": "API", "tasks": [
{ "key": "api",
"title": "Build handlers",
"role": "developer",
"deps": ["db"],
"scope": ["internal/api"]
}
]}
]
}
JSON
Per-task fields beyond the basics:
scope/locks— see "Parallelism".acceptance_check— see "Executable acceptance".skills— slugs of skills under.claude/skills/to attach to the task's brief. Each appears in the context bundle.docs— names of design docs underdocs/to attach.priority(-1|0|1|2),assignee,seq.
--dry-run validates the DAG without writing. --merge folds the tree
into an existing same-title plan additively — new phases/tasks appended,
matched ones left alone, DB-only items reported as removed_candidates
(never auto-deleted).
Note:
stint task createdoes not accept--scope,--locks,--acceptance_check,--skills, or--docs. Those are plan-import-only fields. Adding them after the fact means either editing the row directly or re-importing with--merge.
Agent prompt presets
stint init drops a coherent agent team into the project — hand-authored
source of truth lives in
internal/scaffold/assets/, and stint init copies it verbatim.
Subagents at .claude/agents/ — one per role:
| File | Role |
|---|---|
stint-planner.md |
drafts plans |
stint-plan-reviewer.md |
gates one perspective of a plan |
stint-developer.md |
implements a task |
stint-reviewer.md |
gates a task at review |
stint-tester.md |
writes tests against acceptance |
stint-committer.md |
runs the phase-land gate |
Each is a self-contained role playbook: the exact stint calls it makes,
how to handle every error code, and the return-footer contract.
Skills at .claude/skills/ — a family that maps
to the agents above, plus shared and tailoring infrastructure:
| Skill | Where it runs |
|---|---|
stint |
Orchestrator playbook (Claude in the PM seat) |
stint-execution |
Shared task-execution lifecycle (claim → heartbeat → hand-off → footer) |
stint-planning |
Planner technique (also has the planner agent baked in) |
stint-plan-reviewing |
Plan-reviewer technique, one perspective at a time |
stint-developing |
Developer technique |
stint-reviewing |
Reviewer technique + multi-perspective sensitive surfaces |
stint-testing |
Tester technique (happy / boundary / invalid / state-transition / red-team) |
stint-committing |
Committer technique + the canonical refusal rubric |
stint-documenting |
How to write docs/* and skill frontmatter |
stint-tailor |
Add project-specific subagents/skills to the team |
project-architecture |
Create and maintain docs/ARCHITECTURE.md |
Each role's technique skill is the judgment-and-style layer; the hard boundaries ("never commit", "never self-approve", "never edit code") live in the agent prompts themselves so they cannot be skipped.
stint docs list enumerates the skills, agents, and design docs visible
to the current project — the self-describing index agents read to find
their own bound playbooks.
Multi-target scaffold: Claude and Codex
stint init --target <selector> chooses which agent ecosystem(s) the
scaffold is emitted for. The selector is claude (the default), codex,
or a comma list such as claude,codex. Tokens are case-insensitive; an
unknown token aborts before touching disk with code:invalid naming the
offender.
stint init --target codex # Codex layout only
stint init --target claude,codex # both ecosystems side by side
stint init # claude-only (historical default)
For Codex, stint emits four destinations — each the Codex equivalent of
a Claude scaffold artifact:
| Codex destination | Claude counterpart | What it is |
|---|---|---|
AGENTS.md (repo root) |
CLAUDE.md |
The TigerStyle guide, byte-identical to CLAUDE.md — Codex reads AGENTS.md. |
.codex/agents/stint-<role>.toml |
.claude/agents/stint-<role>.md |
One TOML per role with name, description, developer_instructions — translated from the same hand-authored source. |
.codex/config.toml |
.mcp.json |
MCP servers as [mcp_servers.<id>] tables, translated from .mcp.json. |
.agents/skills/<name>/SKILL.md |
.claude/skills/<name>/SKILL.md |
The skills tree in the identical agentskills.io SKILL.md format — Codex reads .agents/skills, not .codex/skills. |
The chosen targets are persisted in .stint/scaffold.lock (schema v2,
targets array). stint upgrade takes no --target flag — it
reads the persisted set, so a Codex project stays a Codex project
across syncs. Re-running stint init --target … is additive (the
new set is unioned in, never subtracted).
v1 caveats. The prose in skills/agents still says .claude/… and
"subagent" (behavior is correct for Codex, only wording is Claude-shaped);
.codex/config.toml is skip-existing, not merged; targets cannot be
removed once added.
Upgrading
stint upgrade keeps both halves of an install current: the binary
itself and the scaffold it dropped into your project (CLAUDE.md,
.claude/, .mcp.json — plus the Codex equivalents if you opted in).
stint upgrade # download + verify + reconcile + swap
stint upgrade --check # report current/latest/update_available, write nothing
stint upgrade --scaffold-only # refresh scaffold only, no binary swap
stint upgrade --scaffold-only --dry-run # preview buckets, write nothing
stint upgrade --force # upgrade from a dev/dirty build (gates still apply)
The full self-update runs five gates before the atomic binary swap, so a failure never leaves a half-installed binary:
- Refuse dev/dirty builds (no point on the release line to compare
against) unless
--force. - Refuse package-manager-managed installs — a Homebrew or Scoop binary
is owned by its manager, so
stint upgraderedirects you tobrew upgrade stintorscoop update stint. - Download the latest GitHub release asset for the host OS/arch and
verify its sha256 against
checksums.txt(not bypassable by--force). - Run the freshly downloaded binary's
versionto confirm it reports the tag we meant to install (not bypassable by--force). - Run the new binary's scaffold reconcile in your project (so a release that changed scaffold assets installs the new versions).
If you're already on the latest release, the download and swap are skipped; the scaffold still reconciles in place.
Scaffold reconcile policy
For each scaffold file, reconcile compares its current bytes against the
manifest in .stint/scaffold.lock:
- bytes equal the incoming →
unchanged. - bytes differ but match the recorded manifest hash (provably stint's own
prior output, unedited) →
updatedin place, no backup. - bytes differ and do not match the manifest (you edited it), or have no
manifest entry → the live bytes are copied to
<file>.bak(overwriting any prior.bak) and then the incoming bytes are written. Your edits are never silently lost.
.stint/scaffold.lock is committed (not gitignored) — only the churning
SQLite files are excluded — so the lock travels with the repo and
reconcile works after a fresh clone.
stint upgrade --scaffold-only is also the right verb after a manual
go install or a hand-built binary swap. It is the canonical (and
only) verb for scaffold reconciliation: broader than a per-file copy
(agents + skills + CLAUDE.md + .mcp.json + AGENTS.md + .codex/)
and the single "pull latest from embed" entry point.
JSON envelope, exit codes, global flags
Every command emits the same shape on stdout — including usage errors:
{ "ok": true, "data": ... }
{ "ok": false, "error": "...", "code": "not_found|conflict|invalid|internal|usage|plan_not_approved" }
Exit codes: 0 success, 1 operational error (including conflicts so
shell pipelines can short-circuit), 2 usage error. Usage errors also
mirror to stderr as a plain line so shell users see the hint without
parsing JSON; agents should read stdout.
Global flags are accepted anywhere in argv — stint --format table task list and stint task list --format table are equivalent.
--db <path>— override the DB file location. Resolution precedence:--dbflag >STINT_DBenv > walk up from cwd. A non-emptySTINT_DBpointing at a missing file iscode:not_found, not silent fall-through.--actor <id>(orSTINT_ACTORenv) — attributed to every mutation in the audit log. Agents shouldexport STINT_ACTOR=<their-id>once.--format json|table— JSON envelope (default, for agents) or a human-readable table (for orchestrators glancing at state).--readonly— open SQLite in read-only mode; any mutating subcommand fails at the SQLite layer withattempt to write a readonly database.
Pass -- to stop global-flag scanning if a subcommand value would
otherwise shadow one of the above.
Harness security-classifier noise on approve
When stint runs under an agent harness that screens tool calls, a
reviewer's stint task approve (and other status-flipping mutations) can
trip the harness's "external write" classifier. This is expected noise,
not a stint bug. approve/reject/update write to the project's
local .stint/stint.db — exactly the side effect those verbs exist to
perform. There is nothing for stint to fix here: the warning is the
harness's, the write is intentional and local, and approving the call
lets the review proceed. Investigate only if the call targets a DB
outside the project (a stray --db / STINT_DB).
Audit log
Every state-changing operation writes a row to task_events. Stream
globally with stint events [--follow]; for one task, use
stint task events <id>.
stint events --since 5m --limit 100 # last 5 minutes
stint events --since 2026-05-24T10:00:00Z # RFC3339 lower bound
stint events --follow --max-events 200 --max-runtime 30m # JSONL until cap
--since dispatches by shape: pure digits = event id (0 from the
beginning); a Go duration (5m, 1h30m) = relative window; an RFC3339
timestamp = wall-clock lower bound.
Event kinds: created, claimed, released, status_changed,
updated, dep_added, dep_removed, link_added, link_removed,
reclaimed, deleted. Plan-review events also flow (plan_submitted,
plan_approved, plan_rejected, plan_amended).
Dashboard
stint serve starts a read-only web dashboard backed by the same
.stint/stint.db. The DB is opened in SQLite's query_only mode
regardless of flags — there is no path by which the server mutates state.
stint serve # http://127.0.0.1:7777
stint serve --bind 127.0.0.1:9000 # explicit port
stint --db /path/to/stint.db serve # non-default DB
The server refuses to bind a non-loopback address by default; pass
--bind-any to acknowledge that the dashboard exposes the full task DB.
Assets are embedded in the binary (HTML + CSS + JS, ~28KB total) — no
Node toolchain.
The JSON API mirrors the CLI envelope:
curl -s localhost:7777/api/status | jq .data.task_counts_by_status
curl -s localhost:7777/api/plans/4 | jq '.data.phases[].title'
curl -N localhost:7777/api/stream # SSE feed of task_events (live)
Endpoints: GET /api/status, GET /api/plans, GET /api/plans/{id},
GET /api/tasks/{id}, GET /api/stream. Non-GET returns HTTP 405 with
the standard JSON envelope.
Command reference
stint init --name --description --path [--target claude|codex|claude,codex] [--no-scaffold]
stint root # emit {project_root, db_path} WITHOUT opening the DB
stint status # project status overview
stint docs list # enumerate bound skills/agents/docs
stint version # {version, commit, go_version}
stint plan create --title --description --perspective --by --status
stint plan list [--status …] [--limit N]
stint plan show <id-or-slug>
stint plan update <id-or-slug> [--title …] [--description …] [--perspective …] [--status …]
stint plan delete <id-or-slug> [--force] # cascades; --force required if plan has children
stint plan import [--file path] [--dry-run] [--merge] # batch import a tree (JSON); see "Plan import"
stint plan submit <id-or-slug> # draft|rejected -> in_review (opens the gate)
stint plan approve <id-or-slug> --perspective P [--body …] # one perspective signs off
stint plan reject <id-or-slug> --perspective P --reason "…" # any perspective rejects -> rejected
stint plan amend <id-or-slug> --reason "…" # rejected -> draft (reset for fresh review)
stint plan review-list <id-or-slug> # per-perspective verdict matrix
stint phase create --plan --title --description [--seq N]
stint phase list --plan <id-or-slug>
stint phase show <id>
stint phase update <id> [--title …] [--description …] [--status …] [--seq N]
stint phase delete <id>
stint phase land <id> [--check "cmd"] [--message "…"] [--dry-run]
# gated git commit for a whole phase (see "Landing a phase as one commit")
stint task create --phase --title --description --acceptance --role
[--seq N] [--deps 1,2,3] [--phase-deps] [--phase-deps-auto]
[--priority -1|0|1|2] [--assignee <agent-id>]
# --scope/--locks/--acceptance_check/--skills/--docs are import-only fields
stint task list [--plan …] [--phase …] [--status …] [--role …] [--limit N]
stint task show <id> [--bare] # full context bundle by default; --bare for the task row
stint task brief <id> [--role R] [--actor A]
# ready-to-paste subagent prompt (plain text, not JSON) — see "The context bundle"
stint task watch <id> --until <csv> [--timeout 30m] [--interval 1s]
# block until task.status matches one of the listed values
stint task update <id> [--title …] [--description …] [--acceptance …] [--status …]
[--role …] [--seq N] [--priority N] [--assignee <id>]
# rejects boundary-edge transitions; use the dedicated verb (claim/approve/…)
stint task amend <id> --reason "…" # planner fixup with audit payload "amend:<reason>"
[--title …] [--description …] [--acceptance …]
stint task delete <id>
stint task claim <id> --agent <id> # todo -> in_progress
stint task release <id> [--agent <id>] [--admin]
stint task next --role <r> (--agent <id> | --agents a,b,c) [--with-context]
# not_found includes data.reason: no_tasks_for_role|all_blocked|all_claimed|all_done
# returns code:plan_not_approved if every candidate sits in a non-approved plan
stint task approve <id> [--body "…"] # review -> done
stint task reject <id> --body "…" # review -> in_progress (mandatory body)
stint task verify <id> [--cmd "…"] [--transition review|done] [--timeout 10m] [--max-output N] [--cwd PATH]
# see "Executable acceptance"
stint task heartbeat <id> --agent <id> # rarely needed: mutations auto-heartbeat
stint task reclaim [--ttl 10m] # admin: free stale claims (ttl must be > 0)
stint task events <id> [--limit N]
stint task reopen <id> --body "…" [--severity major]
# done|abandoned -> todo; opens a kind=test/severity=major issue, clears reviewed_by
stint task dep add <id> --on <dep-id>
stint task dep remove <id> --on <dep-id>
stint task link add <id> --kind pr|file|doc|url|commit|issue --value <v> [--label …]
stint task link list <task-id>
stint task link remove <link-id>
stint note add --task --author --kind --body
stint note list --task [--limit N]
stint note delete <id>
stint issue open --task <id> --kind <review|blocker|test> --severity <info|minor|major|blocker> --body "…"
stint issue list [--open|--closed] [--task N] [--severity …]
stint issue resolve <note-id> --body "…" # double-resolve returns code:invalid
stint issue link <note-id> --to-task <task-id>
stint context <task-id> # JSON context bundle
stint ready [--role …] [--limit N] # tasks whose deps are all satisfied
stint config set verify-template "<template with {scope}>"
stint config get verify-template
stint orchestrate next # best single task to dispatch (read-only)
stint orchestrate next-batch [--role R] [--max N] # maximal scope-disjoint ready set (read-only, default --max 8)
stint orchestrate overlap-check --tasks 1,2,3 # vet a wave: report scope-colliding pairs
stint events [--task <id>] [--since <id|duration|RFC3339>] [--limit N]
[--follow [--interval 1s] [--max-events N] [--max-runtime 30m]]
stint agent register --id <agent-id> --role <role> [--label …]
stint agent list [--role …]
stint export plan <id> # render plan tree as markdown
stint serve [--bind 127.0.0.1:7777] [--bind-any]
stint upgrade [--check | --scaffold-only [--dry-run] | [--force]]
# see "Upgrading"
Build from source
make build # stamped binary at ./stint (git version/commit ldflags)
make install # build + install to ~/.local/bin (override with BINDIR=...)
go build -o stint ./cmd/stint # unstamped build
Pure-Go SQLite (modernc.org/sqlite) — no CGO, no system libs. Single
static binary; drop it next to your project or on $PATH.
Repo layout
cmd/stint/ CLI entrypoint
internal/db/ SQLite open + schema.sql (embedded)
internal/model/ Plain structs that match the schema
internal/store/ Typed CRUD over the schema
internal/cli/ Subcommand handlers + JSON envelope
internal/cli/assets/ Embedded dashboard HTML/CSS/JS (served by `stint serve`)
internal/scaffold/assets/ Source of truth for the agent/skill files `stint init` ships
Built with the TigerStyle guidance in CLAUDE.md: small dep surface, no
recursion, bounded loops, explicit control flow, JSON output for agents.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
stint
command
Command stint is the project-management CLI used by Claude agents.
|
Command stint is the project-management CLI used by Claude agents. |
|
internal
|
|
|
cli
Package cli implements the user-facing CLI commands.
|
Package cli implements the user-facing CLI commands. |
|
cli/serve
Package serve implements the read-only HTTP dashboard for a stint project.
|
Package serve implements the read-only HTTP dashboard for a stint project. |
|
db
Package db opens the project SQLite file and applies the schema.
|
Package db opens the project SQLite file and applies the schema. |
|
model
Package model defines the persistent entities of a stint project.
|
Package model defines the persistent entities of a stint project. |
|
release
download.go is the fetch-and-verify layer of package release: the security-sensitive step that turns a download URL (from resolve.go's LatestRelease) into a trusted, on-disk stint binary.
|
download.go is the fetch-and-verify layer of package release: the security-sensitive step that turns a download URL (from resolve.go's LatestRelease) into a trusted, on-disk stint binary. |
|
scaffold
Package scaffold installs the stint-managed `.claude/` skills, agents, and the TigerStyle CLAUDE.md into a project root.
|
Package scaffold installs the stint-managed `.claude/` skills, agents, and the TigerStyle CLAUDE.md into a project root. |
|
slug
Package slug derives URL-safe plan slugs from human titles.
|
Package slug derives URL-safe plan slugs from human titles. |
|
store
Package store implements typed CRUD over the stint SQLite schema.
|
Package store implements typed CRUD over the stint SQLite schema. |