A standalone task-management engine — and a taskman CLI — over a plain JSONL
plan ledger (default .taskman/plans/, configurable via .taskmanrc). It is
the core extracted from
@dreki-gg/pi-plan-mode, so any harness (not just pi) can drive
the same plans, initiatives, and tasks.
Planning agents need durable, file-based task state that survives across
sessions and tools. taskman owns that state machine — task status, plan and
initiative lifecycle, and the projection rules that keep them consistent — with
no dependency on any specific agent harness. Use it from a shell, a CI job,
a different agent, or as a library.
Everything lives under the plans root — .taskman/plans/ in the current
working directory by default:
<root>/plans.jsonl— the plan registry.<root>/initiatives.jsonl— the initiative registry (initiatives group plans).<root>/<plan>/tasks.jsonl— one plan's task list (first line is metadata).<root>/<plan>/HANDOFF.md,<root>/<initiative>/INITIATIVE.md— prose docs.
Drop a .taskmanrc JSON file in the working directory to relocate the ledger —
useful when another workflow already claims a similar folder:
{ "plans-root": "some/dir" }The value IS the ledger folder (some/dir/plans.jsonl, some/dir/<plan>/).
Resolution is cwd-only by design — no directory walk-up, no environment
variable — so you (and agents) can always predict which folder a command
targets. taskman root (add --json for machines) prints the resolved root
and whether it came from .taskmanrc or the default.
Three invariants are worth knowing; everything else is mechanism:
- Status is a projection, not a flag. A plan is
donewhen its active tasks are all resolved (and no follow-ups remain); an initiative isdonewhen every member plan is terminal. Writing task state re-derives the registry — you do not set plan status by hand for the normal path. - Plan resolution is stateless. A command targets a plan via
--plan <name>(a path prefix like.taskman/plans/<name>is stripped), else the single in-progress plan. Ambiguous or missing → it exits non-zero and lists the candidates. - Terminal statuses set manually are never auto-reverted.
reconcileonly movesin-progress ⇄ done; it never resurrects asuperseded/abandonedplan or regresses a finished one.
taskman --help # full, always-current command list
taskman <command> --help # flags + arguments for one command--help is the source of truth for commands and flags — this README does not
duplicate it (so it cannot drift). Every command prints human text by default
and accepts --json for machine consumption.
Create a plan from any harness (handoff/tasks accept an inline value, a --*-file <path>, or piped stdin):
echo "$MARKDOWN" | taskman create-plan --name my-plan --title "My Plan" \
--handoff-file - --tasks '[{"description":"do it"}]'
taskman create-handoff --plan my-plan --file HANDOFF.md # write/replace proseCreate an initiative, then link member plans; revise a plan in place when follow-up changes arrive (omitted fields stay as-is; matching task ids keep status/notes):
taskman create-initiative --name auth-overhaul --title "Auth Overhaul" --overview-file INITIATIVE.md
taskman create-plan --name auth-api --title "Auth API" --initiative auth-overhaul \
--handoff "..." --tasks '[{"description":"scaffold"}]'
taskman revise-plan --plan auth-api --title "Auth API v2" --tasks '[{"id":"t-001","description":"scaffold"},{"id":"t-002","description":"tests"}]'A typical execution loop:
taskman status # what's the active plan and its tasks?
taskman update-task t-003 done # mark progress (auto-reconciles the plan)
taskman add-task "handle empty case" --reason "found gap while implementing"
taskman reconcile --apply # repair safe status driftimport {
makePlanRuntime,
resolveLedgerRoot,
resolvePlanByName,
setTaskStatus,
} from '@dreki-gg/taskman';
// Honour a .taskmanrc if present (the library never reads it implicitly);
// makePlanRuntime() alone uses the default root, .taskman/plans.
const run = makePlanRuntime(resolveLedgerRoot().root);
const { planDir } = await run(resolvePlanByName({ name: 'my-plan' }));
await run(setTaskStatus(planDir!, 't-001', 'done'));makePlanRuntime(root) takes the ledger folder itself — storage programs use
ledger-relative paths, so the root places the whole registry.
The public surface (storage, schema, reconcile, initiative projection,
resolution, and composite write flows) is exported from the package root. The
engine is built on Effect with a single FileSystem
seam, so it is straightforward to test and to run against an alternate backend.
This package ships a TanStack Intent skill
(skills/taskman/core) — versioned guidance that AI coding agents discover from
node_modules. If you use an AI agent, run:
npx @tanstack/intent@latest installMIT
No changesets here — plain npm: npm version minor && npm publish && git push --follow-tags. Requires npm 2FA.
The canonical agent-facing skill for this CLI lives in jalbarrang/skills/taskman (npx skills add jalbarrang/skills/taskman). The copy under skills/taskman/core ships inside the npm package for pi consumers and mirrors it.