taskflow

module
v0.23.0 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: MIT

README

taskflow

Home of tskflwctl — a local-first planning CLI over markdown+frontmatter task/Thread/epic/audit/research files. It dogfoods on its own planning under planning/.

Threads preview. The workflow is ready for real CLI and TUI dogfooding. The CLI owns guarded Thread mutations, repair, and bulk apply; the TUI provides shared read projections plus v dependency-rank and spatial graph views, including a one-hop focus lens. Persisted and machine contracts are protected during preview, but command ergonomics and presentation may still evolve. Preview graduation is evidence-gated—not date- or version-gated; see the compatibility matrix and graduation checklist. Thread mutation from the TUI, critical-path analysis, forecasting, and slack remain outside the current feature.

Demos

The interactive TUI (tskflwctl ui) — navigate registered planning spaces from the atlas, then tab across tasks, epics, Threads, audits, and research; status glyphs, epic rollup bars, and an audit's segmented finding bar over its status-grouped finding tree:

the tskflwctl TUI

Run it from outside any planning repo and it opens the atlas instead — one card per planning identity, every registered entry point beneath it, and navigation into a space and back without restarting:

the tskflwctl atlas

Threads turn repository-global task dependencies into a project-sized execution graph. The focused topology view groups work into explanatory dependency ranks—not execution barriers—keeps external gates visible, and opens every node through its stable task identity:

a touring-bike release Thread in the tskflwctl TUI

…and the same vocabulary on the CLI:

tskflwctl status — counts, in-progress, epic bars, open audits status
tskflwctl audit show <id> — segmented finding bar + finding tree audit show
tskflwctl task new with no --epic — on a TTY it prompts: an epic picker, then tags epic picker

▸ All demos, how they're recorded, and the demo fixture → assets/README.md — rendered with vhs against a curated planning tree; regenerate with just gifs.

Map

Path Purpose
cmd/tskflwctl/ The CLI entrypoint (thin composition root).
internal/ domain (pure) · core (use cases) · store (markdown adapter) · cli (cobra) · tui (Bubble Tea) · config/userconfig · spacehealth · spacestore/workspacestore.
planning/ This repo's own epics, tasks, Threads, and research (self-hosted).
docs/ARCHITECTURE.md Package map and detailed architecture reference.

Install

Distribution is GitHub Releases only — three paths, no Homebrew/external channels:

# 1) go install (needs Go toolchain)
go install github.com/andy-esch/taskflow/cmd/tskflwctl@latest   # or @vX.Y.Z

# 2) Prebuilt binary (no Go toolchain) — download the archive for your platform
#    (darwin/linux × amd64/arm64) from the GitHub Releases page, then:
tar xzf tskflwctl_*_linux_arm64.tar.gz && ./tskflwctl version

# 3) From a checkout
just install            # → go install onto $PATH (version-stamped)
GitHub Actions

Validate planning trees, frontmatter schemas, and the task dependency DAG in CI using the official composite action (actions/lint):

name: Planning Lint
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Validate planning tree
        uses: andy-esch/taskflow/actions/lint@v0.22.0 # or @latest

Inputs:

  • version: Version of tskflwctl to install (latest by default, or an explicit tag like v0.22.0).
  • links: Check markdown body cross-links for dangling targets (false by default; equivalent to --links).
  • working-directory: Directory containing .tskflwctl.toml or planning/ (. by default).
  • args: Extra arguments passed to tskflwctl lint.

See actions/README.md for full action reference and options. Pre-compiled release binaries are downloaded automatically for fast execution (~1s).

Build / dev

just build              # → bin/tskflwctl
just run task list      # run without installing
just install            # put tskflwctl on $PATH
just release-validate   # complete clean-candidate gate, including an isolated snapshot

Releases are cut by pushing a tag (vX.Y.Z), which runs .github/workflows/release.yml (goreleaser). A manual workflow_dispatch run builds a --snapshot and uploads the binaries as workflow artifacts without minting a Release. The release guide also provides the same gate in a pinned, non-root Linux container and separates automated qualification from manual CLI/TUI dogfood and publication checks. Thread contract changes and removal of the preview notice additionally follow the Threads compatibility and graduation checklist. Tag only the fully validated preview-removal candidate, then verify the published artifacts against that immutable tag.

Daily workflow

tskflwctl anchors to the nearest planning repo (walks up for tasks/; use -C for a path or --space <label> for a registered entry point). All commands take --json for scripting/agents, and mutating commands take --dry-run to preview the write (full validation runs; nothing is written) — interactive editors have no preview and reject --dry-run.

tskflwctl init                         # scaffold here and register this checkout as a space
tskflwctl init --taskflow-root planning  # ...or put it in a subdir, config at the repo root
tskflwctl init --planning-repo ../x-planning  # point at an external planning repo instead
tskflwctl init --no-register           # bootstrap only; leave this machine's registry alone
tskflwctl config show                  # raw repo/user scopes + effective values and sources
tskflwctl config migrate --dry-run     # preview safe config/id upgrades; omit flag to apply
tskflwctl config edit                  # typed theme/pager editor (human TTY only)
tskflwctl config doctor                # linkback + home space-registry health
tskflwctl status                       # at-a-glance board: counts, in-progress, epic progress
tskflwctl status --all                 # compact summaries + one in-progress rail across spaces

# create
tskflwctl task new "Add retry backoff" --epic <epic-id> --tags net
tskflwctl task new "Triage flake" --epic <epic-id> --tags ci --description "is CI red?" --start  # straight to in-progress (--next/--start need --description)
echo "$BODY" | tskflwctl task new "Long writeup" --epic <epic-id> --tags x --body-file -  # body from stdin/file
tskflwctl thread new "Release path" --description "Coordinate the release" --goal "Ship it" --task <task>  # repeat --task; empty Threads are valid
tskflwctl thread compose --from thread.yml --out thread.apply.yml  # strict existing-task graph → durable recovery plan
tskflwctl thread apply thread.apply.yml --dry-run                  # preview pending edge/Thread operations
tskflwctl thread apply thread.apply.yml                            # dependency files first, Thread last; safe to retry
tskflwctl epic new "Billing overhaul" --description "Replace legacy pipeline"
tskflwctl audit new dispatcher          # → audits/<id>-YYYY-MM-DD-dispatcher.md (--date to override)
tskflwctl audit new auth --template security  # pick a body scaffold (default|security); --template is shell-completable
tskflwctl research new "Compare theming libs" --tags tui --description "Weighed three libs"
tskflwctl research new "Storage options" --created 2026-06-24  # backdate: the id is minted from --created

# read
tskflwctl task list                    # active tasks (--all / --status / --epic / --tag)
tskflwctl task list --revisit-due      # deferred tasks whose snooze date has arrived
tskflwctl task list --unblocked        # active tasks whose derived dependency gate is clear
tskflwctl task show <slug>             # metadata + body (--section <name> / --frontmatter-only to narrow)
tskflwctl task info <slug> --json      # token-cheap metadata: path, status, epic, ac:{checked,total} (no body)
tskflwctl task path <slug>             # just the absolute file path — $EDITOR "$(tskflwctl task path <slug>)"
tskflwctl task blockers <slug>         # actionable blocker frontier (--causal for the full closure)
tskflwctl task unblocks <slug>         # all transitive downstream tasks and their current graph state
tskflwctl thread list                  # nominal/sound rollups + graph/projection health
tskflwctl thread show <thread>         # members, direct external gates, frontier, and body
tskflwctl thread frontier <thread>     # in-flight context, then clear-gated next-up/ready members
tskflwctl thread plan <thread>         # explanatory member waves and external gates; not dispatch authorization
tskflwctl thread graph <thread>        # full compact Mermaid; add --format dot or --details
tskflwctl thread graph <thread> --around <task> --depth 1  # truthful review-sized neighborhood
tskflwctl thread path <thread>         # local parse-free path for direct repair
tskflwctl epic list                    # rollup: done/total per epic
tskflwctl epic show <id> --section goal # epic body section (or --frontmatter-only); epic path <id> for the file
tskflwctl audit list                   # open audits (--all / --closed / --deferred)
tskflwctl audit show <slug> --section findings  # audit body section (or --frontmatter-only)
tskflwctl audit info <slug> --json     # token-cheap: path, bucket, findings:{total,open,in_progress,done,dropped}
tskflwctl audit path <slug>            # just the absolute file path (like task path)
tskflwctl audit findings --status open --effort XS,S --json  # query findings across audits
tskflwctl audit finding new <slug> "Finding title" --band H --effort S --urgency soon  # allocate + render a canonical finding
tskflwctl audit lint                   # validate findings + candidate-tasks:v1 mirrors + bucket↔state
tskflwctl research list                # the whole corpus, newest first (--tag to filter)
tskflwctl research show <slug>         # metadata + body (--section / --frontmatter-only; research path <slug> for the file)
tskflwctl schema                       # the tool's contract for agents (statuses, fields, codes)
tskflwctl schema task --json           # how to author a task: sections, fields, conventions
tskflwctl template list                # body scaffolds `new --template` can use (--kind to filter)
tskflwctl template show audit security # inspect a template's rendered body (--json for the envelope)
tskflwctl ui                           # Bubble Tea browser; a / :atlas navigates spaces

# update + lifecycle
tskflwctl task set <slug> --priority high --tags a,b
tskflwctl task edit <slug>                          # content/frontmatter edit; status is preservation-only
echo "## Findings" | tskflwctl task append <slug> --body-file -  # add a section (agent; atomic)
tskflwctl task set <slug> --body-file notes.md      # replace the body (agent; its own call)
tskflwctl task ac <slug>                            # numbered acceptance criteria; --check/--uncheck <n> to flip one
tskflwctl task ac <slug> --tracked 3 --reason "carried by <id>"  # …or --defer/--wontfix/--na: why it is unmet, not just that it is
tskflwctl task start|next|ready|complete|defer|deprecate <slug>...   # defer takes --until <date>
tskflwctl task start <slug> --force                 # bypass dependency gate only; receipt retains blockers
tskflwctl task defer <slug> --until 2026-09-01      # snooze (revisit_at); on a TTY, prompts for the date
tskflwctl task depend add <slug> --on <prerequisite>...     # guarded repository-global edge add
tskflwctl task depend remove <slug> --on <prerequisite>...  # idempotent guarded edge removal
tskflwctl task depend migrate                            # convert safe legacy dependency fields repo-wide
tskflwctl task depend repair                             # diagnose broken graph-owned source declarations
tskflwctl task depend repair --auto --dry-run            # preview safe dedupe/self/empty-legacy cleanup
tskflwctl thread add <thread> <task>...                  # atomic guarded membership add
tskflwctl thread remove <thread> <task>...               # atomic guarded membership removal
tskflwctl thread start|complete|cancel|reopen <thread>    # explicit Thread lifecycle
tskflwctl audit finding <slug> <code> --status "tracked by <id>" --note "how" --candidate "Follow-up"  # status + resolution + optional managed candidate, one atomic write
tskflwctl audit close|reopen|defer <slug>...
tskflwctl research set <slug> --description "…" --tags a,b   # settable fields only; `schema research` lists them
tskflwctl research edit|append <slug>                        # same human/agent pair as task edit|append

# hygiene
tskflwctl lint                         # validate entities, Thread membership, cross-kind IDs, and the task DAG
tskflwctl lint --fix                   # auto-repair frontmatter (quote ':' values, normalize lists, backfill ids)

Task lifecycle writes, dependency writes, and Thread creation/mutation share the repository guard and fail closed unless the canonical task graph and Thread set are healthy. Thread creation always persists unstarted; bulk membership edits are atomic per command, and complete requires every live member to be soundly drained. Task lifecycle receipts name every Thread projection they change without rewriting Thread documents. Diagnose invalid graph-owned frontmatter with task depend repair; its explicit source selectors and optional manifest feed the sole guarded removal-only recovery path. A rare cleanup error after a durable lifecycle or Thread write is reported explicitly as committed with an inspection receipt; inspect current state instead of blindly retrying it.

Mutation impact warnings and remedy guidance are core-owned and shared by human and JSON output. JSON impacts expose newly_unsafe (tasks) and newly_inconsistent (Threads); these classify the proposed before/after state, not durability. Check dry_run, lifecycle committed, or dependency applied_task_ids/remaining_task_ids before treating a change as persisted. An already unsafe task does not trigger a new warning unless its non-clear gate changes or it becomes inconsistent; this is an inspection signal, not a severity ranking.

board, status, and their JSON envelopes report a non-healthy task graph before a write encounters that guard; the TUI overview and cross-space atlas carry the same summary signal. These read dashboards remain informational. lint is the validation gate: safe resolvable legacy fields are visible advisories with exit zero, while missing, ambiguous, or structurally unsafe dependencies fail lint. Run task depend repair for broken declarations, then task depend migrate for any safely resolved legacy fields that remain.

Completed Thread drift is explanatory: machine and human views name empty, undrained, outstanding-gate, and unhealthy-evidence reasons. lint --fix may repair ordinary Thread scalar syntax and a missing filename-owned ID, but Thread membership remains a deliberate guarded repair.

Multiple planning repos

The home space registry is an advisory inventory of planning entry points on this machine. It lives at $XDG_CONFIG_HOME/tskflwctl/spaces.toml, falling back to ~/.config/tskflwctl/spaces.toml, separate from both the repo-local .tskflwctl.toml and the user-preference config.toml. Entry points that resolve to the same durable planning id form one logical space: a direct planning checkout and its implementation-repo pointers are grouped rather than presented as duplicate plans. The relationship is derived from the repo configs; spaces.toml does not duplicate parent/child state. Registering an entry point never changes ordinary cwd/-C discovery, and forgetting one never touches its repo.

tskflwctl space add                         # register the current checkout as an entry point
tskflwctl space add ../other --id other     # or name another checkout explicitly
tskflwctl space list                        # grouped spaces + entry-point health
tskflwctl space list --json                 # flat entries with role + planning_id
tskflwctl --space other status              # run against that exact registered entry point
tskflwctl status --all                      # all logical spaces + combined in-progress work
tskflwctl status --all --json               # one versioned envelope: per-space summaries + diagnoses
tskflwctl --space other task list --json    # works from any directory
TSKFLW_SPACE=other tskflwctl workspace      # environment twin; receipt names the selector
tskflwctl config doctor                     # linkback + space-registry health audit
tskflwctl space forget other --dry-run      # preview; the repo itself is never deleted
tskflwctl space forget other

The former top-level tskflwctl doctor remains as a hidden compatibility alias for the current minor-release window; new usage should prefer config doctor.

Fresh scaffold and pointer init runs register their checkout automatically, using the directory that contains .tskflwctl.toml rather than a nested planning root. Registration is best-effort: a registry conflict or filesystem failure warns on stderr but never rolls back or fails the new topology. Use --no-register or TSKFLW_NO_REGISTER=1 to opt out; use space add for an existing checkout, because bare init and config migrate never silently mutate home state. --space <label> and its TSKFLW_SPACE twin make those local labels exact checkout targets now, even when several labels share one planning identity. --space and -C are mutually exclusive; an unknown, broken, or durable-id-mismatched entry fails loudly rather than falling back to cwd discovery. Machine mutation receipts carry the selected label as workspace.space. status --all is the registry-wide exception to cwd anchoring: it runs from anywhere, loads each logical planning identity once (healthy direct checkout first, then the first healthy pointer), and keeps broken entry-point diagnoses inline without failing healthy spaces. Registry health remains informational, but unreadable planning files or a selected tree that cannot be loaded produce a non-zero partial-failure exit after every available summary has rendered. With no registered spaces it emits the ordinary single-repo status output. The same grouped projection now powers the TUI atlas. Standing in a planning repo, tskflwctl ui opens that repo; run it anywhere else and it lands on the atlas instead, opening a registered space behind it so every surface stays live — the top rail reads [atlas] rather than naming a space you have not chosen. j/k select a space, h/l choose among its direct and pointer entry points, and Enter opens it without restarting the TUI. Entry paths stay visible in dim text. Cards default to name order; o cycles name/activity/registration order and O reverses it. Press a, run :atlas, or use the command palette to return. The atlas uses the home/global theme rather than whichever repo launched it. Each entry point keeps its own tab, cursor, filter, dashboard, and navigation state across round trips; only the active space is watched for filesystem changes. Broken entries remain visible but cannot be entered, and a failed open leaves the current session untouched. If a refresh overlaps a guarded mutation, unaffected spaces update immediately while the busy space retains its last coherent summary as visibly stale and receives one bounded retry after the quiet period. TSKFLW_CONFIG_HOME overrides the home-config directory, which is useful for an isolated trial before populating the real registry.

Configuration lifecycle

init is bootstrap-only: it creates a new local planning tree or a pointer to an external planning repo. Running bare init where .tskflwctl.toml already exists reports the current topology and exits without changing it. If an older file needs a durable id or planning_repo_id, it points to the explicit, idempotent migration instead:

tskflwctl config migrate --dry-run    # exact planned keys and values; writes nothing
tskflwctl config migrate              # atomic, comment-preserving application
tskflwctl config migrate              # current files are a byte-for-byte no-op

For a legacy pointer whose planning target also lacks an id, migrate the target first, then the implementation repo. Relative planning_repo spelling, comments, unknown keys, and key order are preserved.

config show keeps the raw repository and user scopes separate, then prints the effective theme and pager values with their provenance. Theme precedence is --theme → TSKFLW_THEME → repository config → user config → built-in default. Pager-command precedence is TSKFLW_PAGER → repository config → user config → $PAGER → less -FRX; pager enablement is controlled by --no-pager / --paginate, then repository config, then user config, then the default.

config edit is a typed TTY interface rather than a raw TOML editor. It starts in user scope; press s to choose an explicit repository override. Theme changes repaint the existing palette preview before save, pager enablement remains inherit/on/off, and u removes the selected override to restore inheritance. Durable IDs, planning topology, and spaces.toml are deliberately read-only there. The Config/About entry on the tskflwctl ui Overview screen (also available as :config and in ? help) embeds the same component and uses the same application service and writer.

Tasks, audits, and research are stored flat and id-led (tasks/<id>-<slug>.md, audits/<id>-<slug>.md, research/<id>-<slug>.md); status: / bucket: is authoritative in frontmatter, with no mirror directory. Lifecycle verbs edit that field in place and stamp the dates atomically — no file moves (lint --fix re-normalizes a hand-edited drift). Errors carry semantic exit codes — 10 not-found, 11 validation, 13 ambiguous, 14 conflict (e.g. a name already taken). schema --json publishes the complete process taxonomy and meanings, including 0 success, 1 generic error, 130 interactive abort, and retired code 12 as reserved rather than emitted. It also publishes every runnable command path with its enforced read-only or mutating safety capability, so automation can inspect side-effect potential without scraping help text.

Task-dependency graph. Task frontmatter and JSON may carry depends_on, a sorted set of stable task IDs representing repository-global prerequisites. Guarded task depend add/remove operations resolve references inside one authoritative repository snapshot, reject broken or cyclic results, support authoritative dry runs, and emit structured receipts. task depend repair is a separate capability and the only guarded mutation admitted to an already-broken graph. Its bare form diagnoses exact source, field, raw value, occurrence, and projected edge; --auto is limited to canonical dedupe, self-edge removal, and empty legacy keys, while --drop, --dedupe, and --plan express explicit convergent intent. It removes source declarations surgically, proves structural progress and preservation independently, CAS-protects task and Thread evidence, and reports residual defects plus readable Thread impacts even when a repair legitimately leaves the graph broken. task depend migrate converts safe legacy blocked_by/dependencies/blocks fields repository-wide and can resume after a reported sound durable prefix; it also removes explicitly empty legacy keys. Resolved legacy edges already participate in blocker/downstream reads and derived gates, while health remains degraded until migration makes them canonical. Generic task creation, task set (even --force), task edit, and lint --fix cannot add, remove, or reinterpret graph-owned fields. Use task blockers, task unblocks, and task list --unblocked for explanatory and dispatch-oriented reads. Ordinary dependency and lifecycle writes share the repository guard and fail closed unless the canonical graph is healthy; guarded repair and the explicit legacy migration are narrow recovery exceptions with different source preconditions. Every first-party transition into in-progress enforces the same eligibility policy, while task start --force bypasses only the dependency gate and returns the outstanding blockers. Diagnostic queries report the queried task's derived state, health, and attributable problems. Ordinary lint reports every graph defect. See ADR-0006 for the model and rollout.

Research is the thinnest kind, and the omissions are the point: no status and no lifecycle verbs (a later doc supersedes an earlier one — a decision that needs a lifecycle is an ADR), and no epic: field, so provenance is ordinary body links rather than a rollup. Its id is minted from created, so listing by id lists by authorship date; that also makes created un-settable. See ADR-0001 for the research/ADR boundary and tskflwctl schema research for the fields.

Body templates. Each kind ships named body scaffolds; <entity> new --template <name> picks one (omit it for default). Names are shell-completable and an unknown one fails with exit 11 listing what's available. audit ships a security template (threat model + checklist) alongside default. --template is mutually exclusive with --body/--body-file (pick a scaffold or supply your own). Discover what's available with template list (--kind to filter, --json for agents) and template show <kind> [name]. Repo-local and custom templates are the next step (see planning/epics/22-selectable-template-library.md).

Human output is colorized with status glyphs on a terminal and falls back to plain text when piped. Control it with --color=auto|always|never, --no-color, or the env vars NO_COLOR / FORCE_COLOR (the latter forces color even off a TTY — handy for agents). --json is always plain. tskflwctl version / --version report the build.

Pipelines

The projectable entity queries (task/thread/epic/audit/research list and audit findings) share one output-format flag, -o/--output, plus an orthogonal column selector:

-o Output For
human (default) colorized table reading on a terminal
name concise command handles (slug, id, or finding ref), one per line … | xargs
table tab-separated, header row, absolute dates, no color/truncation cut/awk; stable across versions
csv RFC 4180 comma-separated, header row spreadsheets; cells with commas are quoted
json full records + schema_version jq

-q/--quiet is shorthand for -o name; --json (on every command) equals -o json. -c/--columns slug,status,… projects table, csv, or --json to the fields you name, in order (and implies -o table when no format is pinned). Projected JSON uses raw field values and avoids display-only fallbacks. Canonical selectors emit canonical field keys; the legacy updated and open selectors remain accepted and retain their legacy projected keys for compatibility. Formats and canonical column names are shell-completable. -o table is a documented contract under the one monotonic schema_version (ADR-0008; it is not SemVer), so a column add/reorder advances and classifies the contract revision. It always emits the header row — even with zero results — so a consumer gets a stable schema and detects "no rows" by line count. Recipes:

# start every next-up or ready-to-start task tagged `tui`
tskflwctl task list --unblocked -q --tag tui | xargs tskflwctl task start

# audits with open findings, projected to slug + open count
tskflwctl audit list --all -o table -c slug,open_findings | awk -F'\t' 'NR>1 && $2>0 {print $1}'

# compact Thread health/progress without loading its full topology envelope
tskflwctl thread list --json -c id,slug,status,done,total,frontier,projection_health

# in-progress slugs via jq
tskflwctl task list --status in-progress -o json | jq -r '.tasks[].slug'

stdout is data, stderr is diagnostics — per-item transition failures, file-read problems, and prompts go to stderr, so a partial … | xargs never interleaves errors into the data stream.

Interactivity: one capability, two faces

When a required input is missing, tskflwctl picks the face based on the terminal, never the capability (clig.dev):

  • On a TTY (a human): it prompts — task new without --epic opens an epic picker; a bare task start opens a picker over pending active tasks. Guarded authorization accepts next-up and ready-to-start only when their dependency gate is clear. Prompts render to stderr, so stdout stays byte-identical to the flag-driven run.
  • Off a TTY (a pipe, an agent, --json, or --no-input / TSKFLW_NO_INPUT=1): it never prompts — it fails with today's exit code (11) naming the flag to pass.

So every prompt has a flag twin and nothing interactive can ever block a script. Ctrl-C out of a prompt exits 130 (the SIGINT convention) with a quiet aborted, not an error.

Long human output pages the same way: show / schema pipe through a pager (like git) only on a TTY — never under a pipe, --json, or --no-input, so machine output stays byte-identical. See config show for the effective pager command, enablement, and the source of each value.

Shell completion

tskflwctl ships cobra-generated completion for bash/zsh/fish. For zsh:

just install         # put tskflwctl on $PATH (completion shells out to it)
just completion-zsh  # writes ~/.zsh/completions/_tskflwctl + prints the one-time setup

If ~/.zsh/completions isn't already on your $fpath, add once to ~/.zshrc:

fpath=(~/.zsh/completions $fpath)
autoload -Uz compinit && compinit

Other shells: just completion bash / just completion fish print the script to stdout (see tskflwctl completion --help). Completion covers the command tree, flags, registered --space labels, and task/thread/audit/epic/research references. For example, task show <TAB> and thread show <TAB> offer readable aliases when safe, or canonical IDs when aliases collide or shadow an ID. Completion respects -C/--space and still offers malformed frontmatter; duplicate canonical IDs are omitted—use lint to diagnose them.

Development

just wraps the common tasks:

  • just build — build bin/tskflwctl
  • just run *ARGS — go run ./cmd/tskflwctl …
  • just test — go test ./...
  • just lint — golangci-lint run ./...
  • just fmt — gofmt + lint formatting
  • just tidy — go mod tidy
  • just release-validate — validate a clean release candidate without changing tracked files

Design rationale lives in docs/ARCHITECTURE.md. For planning-data changes, start with its change checklist.

Interactive TUI (tskflwctl ui)

A Bubble Tea browser over the same core the CLI uses (a second primary adapter — never the filesystem directly). Launched outside any planning repo, the atlas is the landing screen: j/k select a space, h/l select a registered entry point, o/O change card ordering, Enter navigates into it, and a / :atlas returns. Launched inside one, it opens that repo and the atlas stays one keystroke away. Inside a space, two panes show an entity list (tasks / epics / Threads / audits / research) and a detail preview rendered as glamour markdown (R toggles raw). Vim-first keys: ctrl+p command palette (fuzzy-jump to any entity or run a command), : command-jump, / filter (label/id/metadata), F toggles the filter between fuzzy and substring, o/O sort, s/S status views where available, [/] tabs, m move (lifecycle: start/complete/defer/…), e to edit a task's fields inline, E to open the whole local file in $EDITOR (when that entity exposes a local-path capability; re-read on save via live-reload), f to follow a reference (task ⇄ epic) with ctrl+o to jump back, y/Y to copy the selection's slug / file path to the system clipboard (a native tool — pbcopy/wl-copy/xclip — when available, else OSC 52 so it still works over SSH), /+n/N find-in-body when the detail is focused, ? for the full keymap, r to refresh. The detail pane's title is a click-to-open link (OSC 8) to the entity's file, and the terminal window/tab title tracks the current selection. The read-only Threads tab keeps persisted lifecycle separate from graph/projection health, nominal versus sound progress, in-flight work, dispatchable frontier, external gates, and diagnostics; with its detail focused, v cycles through terminal-native dependency-rank and spatial graph views over the same core projection. A dependency rank expresses prerequisite depth, not a synchronized execution phase. The spatial view uses hjkl for stable-identity navigation, z to toggle a one-hop neighborhood around the selected task, and Enter to open it; ambiguous horizontal fan-in/fan-out opens a chooser instead of guessing. f remains the fast stable-identity picker for members and immediate external gates. Membership order is not presented as dependency or dispatch order. It live-reloads via fsnotify — edits from your editor or a CLI task move in another terminal show up within ~200ms, cursor preserved. Configured entity directories remain desired even when absent at launch: parent watches discover them when they appear and recover directory replacement, while the footer reports degraded or unavailable coverage honestly. Symlink-backed planning layouts remain deliberately degraded because filesystem backends cannot portably report same-name symlink retargets; manual refresh (r) re-resolves the layout and moves the watches to the current target. See planning/epics/18-tui-bubble-tea-interactive-planning-browser.md.

Clickable titles under tmux + Ghostty. The detail-title link is correct OSC 8 and works in a bare terminal as-is; tmux needs two things. (1) Let tmux pass the hyperlink through — tmux ≥ 3.4 with set -as terminal-features ',xterm-ghostty:hyperlinks' (match your real $TERM), then a full tmux kill-server — a source-file reload often does not apply terminal-features. Confirm with tmux info | grep -i hyperlink. (2) Click through tmux's mouse capture — with mouse on, open links via shift+cmd+click (Shift bypasses tmux's grab; make sure Ghostty's mouse-shift-capture isn't true), or set -g mouse off for plain cmd+click (you lose tmux mouse scroll/select). Quick isolate, in a tmux pane: printf '\e]8;;https://example.com\e\\Click Me\e]8;;\e\\\n' — if Click Me is underlined, rendering already works and it's only the click modifier (step 2); if it's plain text, tmux isn't passing OSC 8 yet (step 1).

Directories

Path Synopsis
cmd
tskflwctl command
Command tskflwctl is the local-first planning CLI.
Command tskflwctl is the local-first planning CLI.
internal
appwiring
Package appwiring is the binary's local-adapter composition boundary.
Package appwiring is the binary's local-adapter composition boundary.
cli
Package cli is the primary adapter: the cobra command tree over the core.
Package cli is the primary adapter: the cobra command tree over the core.
cli/ports
Package ports defines the CLI's invocation-level composition contracts.
Package ports defines the CLI's invocation-level composition contracts.
cli/prompt
Package prompt is the CLI adapter's human-recovery face: it acquires missing required input interactively, but ONLY on a TTY.
Package prompt is the CLI adapter's human-recovery face: it acquires missing required input interactively, but ONLY on a TTY.
cli/render
Package render turns typed core results into CLI output.
Package render turns typed core results into CLI output.
config
Package config locates the planning data within a single repo.
Package config locates the planning data within a single repo.
configstore
Package configstore is the filesystem secondary adapter for core's configuration service.
Package configstore is the filesystem secondary adapter for core's configuration service.
configui
Package configui is the reusable Bubble Tea primary adapter for configuration.
Package configui is the reusable Bubble Tea primary adapter for configuration.
core
Package core holds the application use cases (the Service) and the ports the core needs.
Package core holds the application use cases (the Service) and the ports the core needs.
design
Package design is the single source of truth for the project's CONCRETE color decisions: the named themes and the palette tokens every surface draws from.
Package design is the single source of truth for the project's CONCRETE color decisions: the named themes and the palette tokens every surface draws from.
domain
Package domain holds the pure planning entities and their invariants.
Package domain holds the pure planning entities and their invariants.
editor
Package editor resolves and launches the user's text editor — the shared contract behind the CLI's `task edit` and the TUI's `E` (open in $EDITOR), so the two faces pick the same editor and split its command the same way.
Package editor resolves and launches the user's text editor — the shared contract behind the CLI's `task edit` and the TUI's `E` (open in $EDITOR), so the two faces pick the same editor and split its command the same way.
graphfmt
Package graphfmt contains pure textual output adapters for the neutral core Thread graph projection.
Package graphfmt contains pure textual output adapters for the neutral core Thread graph projection.
id
Package id mints and validates the stable identifier shared by tasks, epics, and audits (ADR-0003): a 12-character, lexically sortable string.
Package id mints and validates the stable identifier shared by tasks, epics, and audits (ADR-0003): a 12-character, lexically sortable string.
listfilter
Package listfilter is the shared substring matcher for bubbles/list.
Package listfilter is the shared substring matcher for bubbles/list.
progressbar
Package progressbar renders the rollup completion bar shared by both surfaces — the CLI `status` dashboard (via render.Style.Bar) and the TUI epic rows/detail (via tui.miniBar).
Package progressbar renders the rollup completion bar shared by both surfaces — the CLI `status` dashboard (via render.Style.Bar) and the TUI epic rows/detail (via tui.miniBar).
spacehealth
Package spacehealth diagnoses the filesystem health of home-registry entries.
Package spacehealth diagnoses the filesystem health of home-registry entries.
spacestore
Package spacestore is the filesystem secondary adapter for the home registry and cross-space planning-tree opener.
Package spacestore is the filesystem secondary adapter for the home registry and cross-space planning-tree opener.
store
Package store is the secondary adapter: planning tasks as markdown+frontmatter files on disk, behind the core's TaskStore port.
Package store is the secondary adapter: planning tasks as markdown+frontmatter files on disk, behind the core's TaskStore port.
testutil
Package testutil builds throwaway planning trees for tests across packages.
Package testutil builds throwaway planning trees for tests across packages.
theme
Package theme is the single source of truth for the *semantic* presentation of domain values — which glyph and which color represent a status, bucket, priority, or completion level.
Package theme is the single source of truth for the *semantic* presentation of domain values — which glyph and which color represent a status, bucket, priority, or completion level.
themepreview
Package themepreview projects a design palette into the stable, ordered sample set used by every interactive and non-interactive theme preview.
Package themepreview projects a design palette into the stable, ordered sample set used by every interactive and non-interactive theme preview.
tomledit
Package tomledit provides small, comment-preserving edits for configuration files.
Package tomledit provides small, comment-preserving edits for configuration files.
tools/docgen command
Command docgen regenerates the CLI reference under docs/cli/ from the cobra command tree — one markdown page per command, so the reference can never drift from the actual flags/examples.
Command docgen regenerates the CLI reference under docs/cli/ from the cobra command tree — one markdown page per command, so the reference can never drift from the actual flags/examples.
tools/flatmigrate command
Command flatmigrate performs the one-time, ADR-0003 §6 cutover of a planning tree to the flat, id-led layout:
Command flatmigrate performs the one-time, ADR-0003 §6 cutover of a planning tree to the flat, id-led layout:
tools/mangen command
Command mangen regenerates the roff manpage for tskflwctl from the cobra command tree, using mango — the same generator fang's runtime `man` command uses.
Command mangen regenerates the roff manpage for tskflwctl from the cobra command tree, using mango — the same generator fang's runtime `man` command uses.
tools/researchmigrate command
Command researchmigrate performs the one-time cutover of `research/` to the flat, id-led layout research docs now use as a first-class entity (epic 28):
Command researchmigrate performs the one-time cutover of `research/` to the flat, id-led layout research docs now use as a first-class entity (epic 28):
tools/schemacomments command
Command schemacomments regenerates the embedded Go-doc comment map that populates field descriptions in `tskflwctl schema --json-schema`.
Command schemacomments regenerates the embedded Go-doc comment map that populates field descriptions in `tskflwctl schema --json-schema`.
tools/wikimigrate command
Command wikimigrate converts Obsidian-style `[[slug]]` wikilinks in a planning tree to GitHub-clickable relative-path markdown links — `[slug](<relative path to the target .md>)` — the Scheme-2 reference form (ADR-0003 / epic 24).
Command wikimigrate converts Obsidian-style `[[slug]]` wikilinks in a planning tree to GitHub-clickable relative-path markdown links — `[slug](<relative path to the target .md>)` — the Scheme-2 reference form (ADR-0003 / epic 24).
tui
command_dispatch.go: the `:` command bar + ctrl+p palette half — word dispatch, completion options, and the palette index/keys.
command_dispatch.go: the `:` command bar + ctrl+p palette half — word dispatch, completion options, and the palette index/keys.
userconfig
Package userconfig loads the USER-scoped (home) configuration — the tier above the per-repo .tskflwctl.toml.
Package userconfig loads the USER-scoped (home) configuration — the tier above the per-repo .tskflwctl.toml.
wire
Package wire is the machine JSON contract for tskflwctl — the --json envelopes, the per-entity DTOs + their mappers, the SchemaVersion, and the reflected JSON Schema.
Package wire is the machine JSON contract for tskflwctl — the --json envelopes, the per-entity DTOs + their mappers, the SchemaVersion, and the reflected JSON Schema.
workspacestore
Package workspacestore is the local-filesystem secondary adapter for opening one planning workspace.
Package workspacestore is the local-filesystem secondary adapter for opening one planning workspace.

Jump to

Keyboard shortcuts

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