stint

module
v0.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2026 License: MIT

README

stint — project manager for Claude agents

A little stint (Calidris minuta) standing on a pebbly shore
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 X atomically moves todo → 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) returns plan_not_approved. Pass --agents a,b,c to fan out N claims in one round-trip.
  • Reviewers do not re-claim a review-status task — the developer retains the claim, and stint task reject drops the task back to in_progress without changing hands. Reviewers only claim dedicated reviewer-role tasks (design docs, threat models). stint task approve and stint task reject only accept review as source; other sources return code:invalid pointing at the right verb.
  • stint task reopen <id> --body "…" is the only path from done or abandoned back to todo. It atomically opens a kind=test, severity=major issue (override with --severity), clears reviewed_by, and audit-logs reopen:<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 heartbeat to 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 to done.

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 metadata
  • task — full body + depends_on / blocks id lists
  • sibling_tasks — compact summaries of every task in this phase
  • phases_in_plan — compact phase outline
  • depends_on_detail / blocks_detail — compact upstream/downstream summaries
  • recent_notes — last 20 notes on this task
  • skills, 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:

  1. 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 to approved only when every perspective signs off.
  2. 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 subagent
    
    task next orders by (assignee-match, priority desc, phase/seq), so --assignee is a soft hint and --priority 2 (urgent) leapfrogs lower-priority work.
  3. 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
    
  4. Promote. When a developer transitions a task to review, dispatch a reviewer subagent. approve lands it at done and the next dependent task becomes eligible. reject sends it back to the developer with a mandatory body — the claim never transfers.
  5. 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 a progress note, and the task only transitions on exit 0. See "Executable acceptance" below.
  6. 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:

  1. --cmd given → it overrides everything for that one run.
  2. No --cmd, but the task has a stored acceptance_check → it runs.
  3. 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 0ok: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:

  1. All tasks done. Lagging tasks are named.
  2. Declared file scope. The commit set is the union of the phase tasks' kind=file links — and only that. Any tracked, modified file outside the set refuses the land and names the stray. phase land never git add -A.
  3. --check (optional). A non-zero exit refuses the land.
  4. Commit. Stages exactly the declared files with an explicit git add -- <paths> vector and runs git 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 under docs/ 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 create does 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:

  1. Refuse dev/dirty builds (no point on the release line to compare against) unless --force.
  2. Refuse package-manager-managed installs — a Homebrew or Scoop binary is owned by its manager, so stint upgrade redirects you to brew upgrade stint or scoop update stint.
  3. Download the latest GitHub release asset for the host OS/arch and verify its sha256 against checksums.txt (not bypassable by --force).
  4. Run the freshly downloaded binary's version to confirm it reports the tag we meant to install (not bypassable by --force).
  5. 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) → updated in 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: --db flag > STINT_DB env > walk up from cwd. A non-empty STINT_DB pointing at a missing file is code:not_found, not silent fall-through.
  • --actor <id> (or STINT_ACTOR env) — attributed to every mutation in the audit log. Agents should export 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 with attempt 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL