Skip to main content

TCW — Taxonomy · Capabilities · Work

A storage-abstracted framework for describing and evolving a software project along three axes, exposed through one CLI (tcw) with three subcommand groups.

Component Is Holds
Taxonomy the nouns the things an app deals with — domain entities, with a real glossary and ontology
Capabilities the user stories what a user can do with those things — each a miniature user story
Work the changes edits to capabilities (product), machinery (technical), or the project itself (meta)

The three link by loose, one-directional pointers (capability → term, work → capability/term) and never duplicate each other. Taxonomy is the vocabulary, capabilities are the user-facing surface, and work is the change layer that moves capabilities and machinery forward over time.


Why this exists

Most projects scatter their "what" and "why" across a dozen unsynchronized places: a tracker for tickets, a wiki for glossaries, a FOLLOWUPS.md that grows without bound, design docs that drift from code, and a planning flow where documents jump between unrelated trees with no "where is this right now?" spine. State lives everywhere and nowhere; reconstructing the current picture means reading all of it and trusting none of it.

TCW started as an effort to fix exactly that for an agent-driven SDLC, and the unlocking insight was that several separate-looking problems —

  • no single, durable source of truth for where a piece of work is,
  • a glossary nobody could point at,
  • capability docs that drifted from the code,
  • a follow-up log that rotted,
  • cross-repo coordination that lived only in prose —

were all facets of one missing thing: a durable, legible, per-node source of truth for a project's nouns, user stories, and changes. TCW is that source of truth, built on a deliberate stance:

  • State is the status, not a log. A work item's status is which directory it lives in; a transition is a git mv. The "board" is ls active/. There is no global ledger file to drift, double-count, or burn tokens re-summarizing.
  • Per-node, never global. Each item, term, and capability owns one bounded document. Nothing grows without limit; nothing has to be reconstructed from history.
  • Mechanism in the tool, judgment in the human/agent. Legal transitions, slug integrity, reference validity, and the Definition-of-Done gate are enforced by a deterministic CLI — not left to a prose checklist that gets followed only sometimes.
  • Co-located with the code it describes. The docs live in the repo. One atomic commit can carry a code change and its status/capability change together, reviewable in the same diff.

The shorthand for the work component is a "recursive, OS-native Jira" — and just as important is what it deliberately refuses: no sprints, no story points, no burndown charts, no SLAs, no estimation ceremony. Just items, statuses, legal transitions, and a done-gate.

Storage abstraction (the prime directive)

TCW ships a filesystem-native default, but the model is storage-abstracted so it can run against an external tracker (Jira, a wiki, a graph DB) where one is already in use. That portability is what makes it viable at enterprise scale. Every operation has to pass one test:

"Could a non-filesystem store implement this operation, even if less elegantly?" Yes → it belongs in the model (the abstract store interface). No → it's a filesystem-adapter detail, or it gets redesigned.

So the CLI talks to abstract store interfaces (TaxonomyStore, CapabilitiesStore, WorkStore); the shipped adapters (FsTaxonomyStore, FsCapabilitiesStore, FsWorkStore) realize them on the filesystem. The filesystem superpowers — co-located docs, atomic commits, grep/diff/PR legibility, mv-as-transition — are bonuses layered on top, never load-bearing assumptions of the model. The full rules live in AGENTS.md.

Who it's for

  • Agent-driven development, where an LLM needs a legible, enforced place to record what a project is and where its work stands — and where "told to follow the rules" isn't enough, because the invariants must be mechanically held.
  • Teams that want their domain glossary, feature inventory, and change log to live next to the code and move in the same commits and PRs, instead of in three drifting external tools.
  • Anyone who wants a no-ceremony work tracker that is just folders, files, and git, but can later be pointed at a real tracker without rewriting how the project is described.

Install

As a plugin (recommended)

In Claude Code:

/plugin marketplace add brocef/TCW
/plugin install tcw

Or, in the Claude web app or desktop app, open the plugin directory, add brocef/TCW as a marketplace, and install tcw from it — no terminal needed.

Then start a new session — the plugin installs the tcw CLI when a session begins, and one installed mid-session can't run until the next one starts.

This ships the tcw-work, tcw-capabilities, tcw-taxonomy, tcw-plugin, tcw-post-mortem, tcw-report, tcw-triage-issues, and documentation-sync skills; the /tcw-doctor, /tcw-process-inbox, /tcw-triage-issues, /tcw-plan-work, /tcw-drive-work-to-completion, /tcw-verify-work, /tcw-post-mortem, /tcw-audit-work-backlog, /tcw-consolidate-plans, /tcw-taxonomy-init, /tcw-capabilities-init, /tcw-docs-sync-setup, and /tcw-cut-version commands; and the read-only tcw-verifier and tcw-post-mortem agents. There is nothing to run afterwards: the plugin puts tcw on your PATH at session start by installing tcw-cli from PyPI (via pipx), and refreshes it the same way when a plugin update lands. It's the same package you'd install by hand, so if you already ran pipx install tcw-cli there's nothing to undo — the plugin installs over it rather than beside it. A development checkout installed with pip install -e . is left alone, and so is a machine without pipx — choosing a Python environment isn't something that should happen unasked at session start. Run /tcw-doctor any time tcw goes missing or looks wrong.

This needs network the first time. The plugin no longer carries a CLI to fall back on, so the first session after you install or update the plugin has to reach PyPI. If it can't, the session says so in one line and tcw won't be available until a session that can.

In Codex (no slash commands — skills only):

codex plugin marketplace add brocef/TCW --ref main
codex plugin add tcw@tcw

Codex has no session-start hook, so ask the agent to run the tcw-plugin setup — it installs the tcw CLI by running the same script Claude runs automatically.

As a Python package

pipx install tcw-cli        # recommended (isolated, on PATH)
pip install -e .            # development install from a clone

The package is called tcw-cli on PyPI because tcw was already taken by an unrelated project. The command it installs is still tcw, and the importable package is still tcw — only the name you type after pipx install differs.

This is the same install the plugin performs for you: if you use the plugin, you don't need to run this, and if you run it anyway the plugin will install over it rather than beside it. Use it when you want the CLI without an agent harness at all, or to move ahead of the plugin's refresh cycle with pipx upgrade tcw-cli.

tcw is a real Python package (entry point tcw = tcw.cli:main), not a symlink. Requires Python ≥ 3.11; the Python runtime dependency is PyYAML. tcw serve additionally requires Node.js ≥ 22.12. Other commands remain Python-only and work without Node installed. Released wheels contain the locked, prebuilt web server and client, so installed users do not need pnpm, node_modules, a network connection, or a frontend build step.

Quickstart

cd your-git-repo
tcw init --id my-project                    # scaffold all three components
tcw init --id my-project taxonomy work      # …or just named components
tcw work init --id my-project               # …or use a component mirror
tcw work init --id my-project --path ../orchestrator/CoreLib/work
tcw serve --no-open          # browse Work, Taxonomy, and Capabilities locally
tcw validate                # validate this project and all registered descendants
tcw --help                  # top-level groups: init | serve | validate | taxonomy | capabilities | work

tcw init --id <project-id> marks the current directory as a TCW node by writing a tcw-config.yaml sentinel with its canonical ID, then scaffolds docs/<component>/ skeletons. It refuses outside a git repo (write transitions need git), but the node folder can be anywhere inside the repo — not just the root. Each component is a tree of docs under docs/<component>/. Each component group also has its own init mirror — tcw taxonomy init, tcw capabilities init, tcw work init — identical to tcw init --id <project-id> <component>. Existing configured nodes may omit --id; legacy ID-less markers use it once to backfill their identity.

Scaffolding work also adds .gitignore rules for docs/work/completed/ and docs/work/discarded/, keeping each folder's .gitkeep tracked. Resolved items therefore stay on your disk and in the history that tracked them while they were live, without piling up in the tree forever. Delete the rules to track resolved work instead; on a node that predates them, re-run tcw work init to add them and git rm -r --cached docs/work/completed docs/work/discarded to drop what git already tracks.

To keep a project's work in another Git repository while preserving its own ID and lifecycle configuration, set work.path in its tcw-config.yaml or pass tcw work init --path <path> (tcw init --work-path <path> work). Relative paths are anchored to the owning project's primary checkout; absolute paths and symlinks are supported. TCW commits work-item changes in the target repository, while hooks and --worktree continue to use the owning code repository. Existing non-pristine stores are never moved automatically.

Connected projects

Projects may be nested, siblings, or anywhere else on the filesystem. Their canonical IDs are identity; filesystem paths are adapter locators only.

cd orchestrator && tcw init --id orchestrator
cd ../project-a && tcw init --id project-a

Each invocation still selects the nearest enclosing sentinel. Cross-project operations use only reciprocal registrations:

id: orchestrator
connected-projects:
    children:
        project-a: ../project-a
id: project-a
connected-projects:
    parent:
        orchestrator: ../orchestrator

Relative locators resolve from the declaring config; absolute locators are also allowed. children contains direct children only and parent has at most one entry. TCW derives deeper descendants and ancestors transitively, never by scanning directories to discover a project. tcw work list --include-descendants groups registered boards by project ID, and any work command accepts <descendant-project-id>/<slug>.

Inside a linked git worktree a relative locator would otherwise be off by the worktree's nesting depth, because it was written against the project's position in its primary checkout. TCW re-anchors it against the main worktree root — but only when the target leaves the worktree. A target that stays inside is a sibling on the same branch and stays with the worktree, so several projects in one repo behave the same inside a worktree as outside it. This is the one place git metadata is consulted, and it only re-points a locator: it never discovers a project or infers a relation. Projects outside a worktree, and projects not in a git repository at all, are unaffected.

Connections do not imply component inheritance. Each axis opts in explicitly:

# docs/taxonomy/config.yaml
extends:
    - orchestrator

The source project ID is also the inherited namespace. Inheritance is transitive: if one source extends another, both sources' terms are available under their own project IDs.


Usage

Every group has a --help and a check that validates the tree. Taxonomy and capabilities also have a bare-path shortcut (tcw taxonomy <path> == tcw taxonomy show <path>), except that path itself is reserved as the store- location command; use explicit show path to read an object with that name.

Print the absolute, resolved folders used by the filesystem stores with:

tcw taxonomy path
tcw capabilities path
tcw work path
tcw work inbox path

The work commands follow a configured work.path, so they report the physical external store and its inbox when work storage lives outside the project.

tcw serve — the local web viewer

tcw serve starts a local web app on 127.0.0.1 for the current TCW node:

tcw serve                    # http://127.0.0.1:8765/ and open a browser
tcw serve --no-open           # start the server without opening a browser
tcw serve --port 9000         # choose a different loopback port

This command requires Node.js 22.12 or newer. TCW checks the version before starting and reports an actionable error when Node is missing or too old. The Python CLI launches a private authenticated API sidecar and a packaged Fastify server; Fastify is the only browser-facing listener. The React client and server bundle are included in the Python package and work fully offline. pnpm is needed only by contributors rebuilding the committed web assets.

Contributor formatting is repository-wide and deterministic:

pnpm prettify          # format maintained source and documentation
pnpm prettify:check    # verify formatting without rewriting files

Dependencies, generated bundles and caches, closed work items (completed and discarded), and versioned release archives are excluded; current source, configuration, taxonomy, capabilities, active/backlog work, this README, and upcoming notes remain in the formatting surface. pnpm typecheck also runs the formatting check.

The Settings gear immediately after the Work tab controls appearance. Choose Light, Dark, or System; System is the default and follows operating- system appearance changes as they happen. The choice is stored only in the current browser, not in the TCW project or an API.

When the served node has descendant TCW nodes (the orchestrator/subproject pattern), tcw serve aggregates every descendant node's board alongside the current one automatically — the same items as tcw work list --include-descendants. Descendant items carry <project-id>/<slug> addresses, resolvable across the web app, and their URLs use the same project-ID namespace.

The app has tabs for the Taxonomy tree, Capabilities ledger, and Work board, and its URL reflects the current view (/taxonomy, /work/<slug>, …) so any state is deep-linkable and Back/Forward work. Any tcw:// reference in an object's body (see tcw:// links) renders as a clickable in-app link that navigates to the target object; a link to something this viewer isn't hosting renders inert. The list/detail divider and the editor/preview split are drag-resizable. The object list is a collapsible tree that mirrors each axis's hierarchy — nested paths for taxonomy terms and capabilities (a path segment with no item of its own is a plain folder label), parent/child relations for work items. Selecting or deep-linking a nested item expands its ancestors automatically, and the text filter prunes the tree to matches plus the ancestors needed to reach them. The list column scrolls independently, so a long tree stays navigable without moving the header or the detail pane. A clear control appears inside a non-empty filter, tree controls provide larger keyboard-accessible targets, and Work rows tint their full surface by backlog, active, completed, or discarded status. Each axis keeps its create control immediately above the object tree. Every Taxonomy, Capability, and Work entry shows its last-modified timestamp in both the tree row and detail header. Above that are multi-select category filters: on the Work board, Status and Tags dropdowns use a checkbox per value (select several to match any), and in the Taxonomy view a Kind dropdown covers Feature and Vocabulary. Backlog and active statuses are selected by default; completed and discarded are not. Work items can be sorted by name or last-modified time in either direction; the selected sort applies within the fixed active, backlog, completed, then discarded status groups. All of these compose with the text filter. Each work row has a button to copy its slug to the clipboard. Beyond browsing, you can create and edit any object directly from the browser:

  • Work items — create new items with all fields (title, priority, effort, complexity, tags, blockers, parent, initiative); view and edit Initial Request, Spec, and Implementation Plan in first-class content tabs; edit other lifecycle artifacts and the capabilities.yaml sidecar using a Markdown editor with live preview; and run lifecycle actions (start, complete, drop). Completing as done requires resolving blockers and acknowledging every Definition-of-Done item, plus a capabilities reconciliation reminder; discarding drops all three for a single confirmation.
  • Taxonomy entries — create Vocabulary or Feature entries; edit name, description, kind, and relations. Validation check failures are shown in the UI after saving.
  • Capabilities — create path-addressed capability folders and edit metadata and the Markdown body. Inherited (federated) capabilities show their origin. Check failures are surfaced in the UI.

Structured reference fields search the Work, Taxonomy, and Capability objects already loaded in the browser. Results show and highlight both the display name and canonical identifier; use Up/Down and Enter or point at a result to select it. Multi-value fields keep free-form entry for external or not-yet-registered references. After any object, lifecycle artifact, or sidecar is saved, TCW runs its standard validation rules against that saved object. Findings appear as a persistent Saved with validation issues notice and do not undo the save; fixing the object and saving again clears the notice.

All Markdown editing uses a raw-Markdown textarea paired with a live-rendered preview pane. Its renderer is included in the locked, prebuilt package assets; no runtime download or user-side build is required.

Local-first safety: the server binds only to 127.0.0.1 (loopback). Mutating requests (create, edit, lifecycle actions) additionally require Content-Type: application/json and a loopback Host/Origin header, blocking cross-origin or DNS-rebinding attacks. Request bodies are capped at 1 MiB. Concurrent stale edits are rejected (HTTP 409) so two editors never silently overwrite each other.

If tcw serve fails before printing its URL, run node --version and confirm it is at least v22.12.0. Reinstall TCW if the error reports missing packaged web assets. Port-collision errors can be resolved with --port <available-port>.

tcw:// links — reference a TCW object

Any object's body prose can point at another TCW object with a tcw:// link:

tcw://[<project-id>/]<axis>/<ref>
  • <axis> is T (Taxonomy), C (Capabilities), or W (Work).
  • <project-id> (optional) is a registered descendant for W, or a project explicitly listed by that axis's extends for T/C. Absent = local.
  • <ref> is the identifier within that axis (taxonomy slug/path, capability path, work slug).
See [Read a capability](tcw://C/capabilities/read-a-capability) and the
[reference](tcw://T/reference) term, or work item [tcw://W/2026-01-01-x](tcw://W/2026-01-01-x).

These are inline Markdown links, so they render as normal links in any viewer and become in-app navigation in tcw serve. They're additive — they don't replace the structured pointers (a capability's Subject/Feature, a work item's blocked_by). Stored Markdown is never rewritten.

tcw validate — one-pass soundness check

Bare tcw validate checks the active TCW project and every registered descendant project recursively. Use --no-recurse to check only the active project, or pass a path to run a bounded active-project scan (which also disables recursion):

tcw validate                    # active project + all registered descendants
tcw validate --no-recurse       # active project only
tcw validate docs/capabilities  # one active-project tree only

For each selected project it reports malformed YAML (including duplicate keys), a tcw:// link that doesn't resolve, and problems surfaced by each component's own check (taxonomy + capabilities + work). Recursive diagnostics include the project ID so matching relative paths remain distinguishable. It exits 0 with validate OK only when every selected project is clean; otherwise it prints the problems and exits 1. tcw:// examples inside Markdown code spans are ignored, so docs that teach the scheme don't fail themselves.

tcw taxonomy — the nouns

Taxonomy entries form a forest, and the slug is the path: admin/permission is a different entry from billing/permission, and addressing is by that path. Entries have two kinds: Vocabulary for the fundamental language of the project, and Feature for the user- or application-facing manifestations that operate on or involve vocabulary.

tcw taxonomy add Invoice "A bill issued to a customer."     # vocabulary by default
tcw taxonomy add Permission -p admin                        # -> admin/permission
tcw taxonomy add Note -p invoice -s memo                    # custom leaf slug
tcw taxonomy add "User Authentication" --kind feature --vocab user

tcw taxonomy list                  # the forest, indented, flagged by origin
tcw taxonomy list --local          # local terms only (hide imported)
tcw taxonomy show admin/permission # read one term (or: tcw taxonomy admin/permission)
tcw taxonomy search invoice        # match names + descriptions
tcw taxonomy check                 # validate inheritance + references

tcw taxonomy extends add acme-shared   # inherit a registered project
tcw taxonomy extends rm acme-shared    # drop the import

A taxonomy entry's body comes from the argument or from stdin (echo "..." | tcw taxonomy add Foo). Feature entries can carry repeatable --vocab <ref> links to the vocabulary they involve. A ref is a term path (admin/permission), a <project-id>/<path> into an inherited taxonomy, or a leaf slug that is unique across your own terms — which is stored as its full path. tcw taxonomy add refuses a ref that does not resolve, is ambiguous, or names a feature where a vocabulary entry is expected, and writes nothing when it refuses; so register vocabulary before the features that name it. tcw taxonomy check validates the same refs across the whole tree. Taxonomies can federate: tcw taxonomy extends add <project-id> writes the registered source ID to the extends list in config.yaml. Each project ID is its own namespace, including sources inherited transitively, and there is no silent merge — a local permission and an imported acme/permission stay distinct. Capabilities federate separately and additionally let a consumer override an inherited entry per-project (see tcw capabilities above).

To bootstrap a taxonomy or capabilities ledger on a project newly adopting TCW, run /tcw-taxonomy-init or /tcw-capabilities-init: the assistant studies your code, proposes a first draft, refines it with you, and writes it.

tcw capabilities — the user stories

A capability is a path-addressed folder (docs/capabilities/<path>/ holding meta.yaml + description.md) with an opaque stable id. It carries metadata fields — notably Subject: (a loose, multi-valued pointer to taxonomy entries), Feature: (a strong pointer to a taxonomy feature), and Planning doc: (the forward pointer to a work item).

tcw capabilities add billing/invoices "Download an invoice as PDF"   # mints a stable id
tcw capabilities add billing/invoices/bulk "Download many at once"    # nested path

tcw capabilities list                      # every capability, flagged by status + origin
tcw capabilities list --status Missing     # filter by status
tcw capabilities list --local-only         # hide inherited (federated) capabilities
tcw capabilities show billing/invoices     # read one capability by path
tcw capabilities search pdf
tcw capabilities check                     # paths, metadata vocab, Subject/Feature, federation
tcw capabilities drift                     # inherited-but-unreviewed + shipped-but-Missing (CI-usable)

tcw capabilities set billing/invoices --status Supported
tcw capabilities set billing/invoices --field "Subject=invoice,billing"   # multi-valued
tcw capabilities set billing/invoices --field "Planning doc=2026-06-19-pdf-export"

set updates a capability's status/fields in place (stage-only) — the mechanism the work→capability lifecycle uses to flip Missing → Supported at completion.

Status is one of Supported · Partial · Missing · Blocked · Omitted. check validates the metadata vocabulary, resolves each Subject: pointer against the taxonomy store, and verifies that each Feature: pointer resolves to a taxonomy feature. The tool never parses capability prose; it only follows pointers.

Federation. Capabilities can extends another project's — so a web frontend and a mobile app that drive the same server declare their shared user stories once:

tcw capabilities extends web-frontend       # inherit a registered project
tcw capabilities extends web-frontend --rm  # drop it

Inherited capabilities surface flagged by origin (web-frontend/<path>) and are read-only in structure — a project can't delete one, only override it. Set an inherited capability exactly like a local one, by any path show accepts:

tcw capabilities set web-frontend/auth/login --status Omitted

The override is written for you. It is a local folder whose meta.yaml has overrides: <upstream-id> plus the changes: metadata fields partial-merge (e.g. Status: Missing, or Status: Omitted for "we deliberately don't have this"; a YAML null clears a field), and the body composes as prependedDocs + (a local description.md, if present, else the upstream body) + appendedDocs — e.g. a mobile app appending "…or take a photo with the camera." That file shape is worth knowing (you can hand-author one anywhere, and set will keep using it), but set is the front door. Local sibling-repo paths only.

To undo an override and go back to the upstream value, reset it:

tcw capabilities reset shared/auth/login   # drop the local override, re-inherit upstream

reset removes only your local override folder (never the upstream node). It refuses with a clear message when there's nothing to drop — a standalone local capability (use remove) or a path that already inherits verbatim.

tcw work — the changes

Raw requests enter through a permissive inbox, then accepted requests become formal work in a single-node state machine where status is the folder a work item lives in and a transition is a move between folders:

raw inbox entry  --accept-->  backlog  --start-->  active  --submit-->  review
                                  |                    |                   |
                                  |                    |     <--rework-----+
                                  |                    |                   |
                                  |   --resolution done-+-------------------+--> completed
                                  |                    |                   |    ("we shipped it")
                                  +--- wontfix / duplicate / superseded ---+--> discarded
                                                            ("we closed it without shipping")
                         (drop deletes a backlog item outright)

The resolution picks the destination, so completed/ answers "what shipped?" on its own. A backlog item can be discarded directly — abandoning an idea never needed a throwaway start.

review means implemented, acceptance pending. It is not a finished state: an item sitting in review still blocks whatever depends on it and still holds its epic open, because verification can send it back. rework is the only reverse move in the machine — nothing ever leaves completed/ or discarded/.

Review is optional. A small change can still go straight from active to completed; tcw work complete just prints a note saying the verify step was skipped, and completes.

Blocked-ness is a derived overlay: an item is blocked when it has at least one unresolved blocker recorded in its data — there is no separate "blocked" folder or status.

Binding your own skills and commands to the lifecycle

The lifecycle has named stages (each producing one document) and named transitions (each moving status). A node can bind its own agent skills or shell commands to any of them:

# tcw-config.yaml
work:
    lifecycle:
        stages:
            spec: [{ skill: superpowers:brainstorming }]
        transitions:
            complete:
                pre: [{ command: "pytest -q" }]

A binding is a skill: or a command: — never a bare string, because guessing which one was meant is a class of bug bought for nothing. tcw validate rejects an unknown id, a malformed shape, a blank or duplicated reference, and a binding declaring neither or both.

pre hooks run before anything is written: a non-zero exit aborts the transition and the item does not move. post hooks run after, and a failure there never rolls back — the move already happened, so tcw reports it and exits non-zero while the item stays where it went. Commands run through the shell with the node root as the working directory and TCW_SLUG, TCW_STATUS, TCW_TRANSITION, and TCW_NODE_ROOT in the environment, with a 300-second default timeout (work.lifecycle.timeout).

Skill bindings are reported, never run — tcw cannot invoke a skill, only your agent can. Run tcw work lifecycle to see what is bound.

Two things worth knowing: tcw-config.yaml is a file in your own repository and is trusted exactly as much as any other file there — this is not a sandbox. And tcw serve does not run hooks, so a pre hook that would block a transition does not block it from the web app.

Every transition commits its own move. tcw work start, submit, rework, and complete each leave a commit recording just that item's status change — scoped to the item's own folders, so unrelated edits in your working tree are never swept in. Set work.auto-commit-transitions: false in tcw-config.yaml to turn it off and commit them yourself. work.trunk-branch: main adds a warning when you transition an item from some other branch; it is advisory only and never checks anything out.

The completion checklist is yours to set. tcw work complete --resolution done prints a Definition of Done and refuses until you re-run with --confirm. Write your own as a plain list in docs/work/dod.yaml:

- tests pass
- docs synced
- capabilities reconciled
- reviewed
- version offered

Two things to know. The file replaces the built-in list rather than adding to it — those five are the defaults, so a list that leaves one out drops that check from every completion, with no error. And it is printed only when the resolution is done: discarding an item (wontfix, duplicate, superseded) prints no checklist at all, so a line meant to cover those closures has nowhere to land.

If the item came from a GitHub issue — /tcw-triage-issues records it — closing the item out means answering that issue and usually closing it too. A checklist line is the natural place to be reminded.

tcw work init                          # docs/work/{inbox,backlog,active,review,completed,discarded}/

tcw work inbox list                    # list each raw file or folder entry
tcw work inbox show request.md         # inspect metadata, text, and resource manifest
tcw work inbox accept request.md       # consume it into a new backlog item; print the slug
tcw work inbox accept request.md --title "Clear title"

slug=$(tcw work new "Add PDF export")  # creates a backlog item, prints its slug
tcw work new "Add PDF export" --blocked-by other-slug --blocked-by "external: JIRA-123"
                                       # create with blockers pre-attached (flag is repeatable —
                                       # one blocker per flag, so its text may contain commas)
tcw work new "Urgent fix" --priority 5 # integer priority (higher = higher); default unspecified
tcw work new "Big rework" --effort high --complexity very-high
                                       # optional estimates (low|medium|high|very-high; L/M/H/VH shorthand ok)
tcw work new "Sub-task" --parent "$slug"  # a child item, nested inside the parent's folder

tcw work tags add bug tech-debt        # register a project's valid tags (in tcw-config.yaml)
tcw work tags list                     # print the registered tags
tcw work tags rm tech-debt             # unregister (warns about items still carrying it)
tcw work new "Login crash" --tag bug   # apply a registered tag (repeatable; unregistered → error)

tcw work list                          # the board: priority first, then topologically ordered
                                       # (hides completed and discarded)
tcw work list --status active          # filter to one column (backlog|active|review|completed|discarded)
tcw work list --tag bug                # only items carrying a tag (repeatable = match any)
tcw work list --all                    # include completed and discarded items too
tcw work list --status discarded       # only the items closed without shipping
tcw work list -i                       # descendant boards; --incl-desc and --include-descendants are aliases
tcw work lifecycle                     # the stage/transition contract + this node's bindings
tcw work lifecycle --json              # the same, machine-readable
tcw work lifecycle --stage spec --directive
                                       # one instruction line for an agent, or nothing if unbound

tcw work show "$slug"                  # state + body (includes blocked_by/type/initiative/effort/complexity/tags if set)
tcw work path                           # absolute, resolved work-store folder
tcw work path "$slug"                  # current filesystem path of the slug
tcw work inbox path                     # absolute, resolved inbox folder

tcw work start "$slug"                 # backlog → active (refused if blocked/gated)
tcw work start "$slug" --force         # override unresolved blockers or initiative gates

tcw work submit "$slug"                # active → review (implemented, acceptance pending)
tcw work rework "$slug"                # review → active (verification rejected the work;
                                       # refused while refined-outcome.md still says it passed)

tcw work edit "$slug" --blocked-by other-slug    # record a new blocker (repeatable)
tcw work edit "$slug" --blocks downstream-slug   # this item now blocks another
tcw work edit "$slug" --unblocked-by other-slug  # clear a resolved blocker (repeatable;
                                                 # accepts the "external: …" form show/list print,
                                                 # and fails if it matches no blocker)
tcw work edit "$slug" --title "A better title"   # rename the item (the slug never changes)
tcw work edit "$slug" --priority 9               # set/raise integer priority
tcw work edit "$slug" --effort medium --complexity low   # set effort/complexity estimates
tcw work edit "$slug" --tag bug --untag stale    # apply/remove tags (repeatable)

tcw work complete "$slug" --resolution done --confirm
tcw work complete "$slug" --resolution done --confirm --force   # override blockers, gates, or unreconciled capabilities
tcw work complete "$slug" --resolution done --confirm --already-integrated
                                       # the work branch was merged outside TCW (a merged PR):
                                       # skip the merge-back, keep every other gate
tcw work complete "$slug" --resolution wontfix --confirm        # → discarded/ (no Definition of Done; legal from backlog)
tcw work drop some-slug --confirm      # erase a mis-created item, leaving no record

complete enforces capability reconciliation: if the item's capabilities.yaml declares a new: capability that still reads Missing, or any declared path that no longer resolves, the completion is refused (flip it with tcw capabilities set, mark it Omitted, or --force past). For a --worktree item the check runs after the branch merges back, so a status flip made on the work branch counts.

A discard is not a shipment, so none of the shipping gates apply to one: no Definition-of-Done checklist, no capability enforcement (just a warning naming anything left Missing), no branch merge-back, and no blocker check — being blocked indefinitely is one of the best reasons to give up on something, so needing --force to act on it would be backwards. --confirm is still required, since closing is permanent. Discarding a --worktree item tears down the worktree but keeps the unmerged branch, naming it so you can delete it deliberately — deciding work isn't wanted is not the same as authorizing its destruction.

An epic is the one exception: open initiative children block closing it by either route, because a child can't start until its epic is active, so closing the epic would strand them.

Tags classify items for filtering. Each project registers its valid tag set centrally in tcw-config.yaml (tcw work tags add|rm|list); an item then carries zero or more of those tags via --tag on new/edit (and --untag to remove). Applying an unregistered tag is refused, and tcw validate flags any item still carrying a tag that was later unregistered. Tags don't affect board ordering.

After tcw work new and tcw work start, the CLI prints the next transition to run (e.g. "→ next: when you begin implementing, run tcw work start …") so the lifecycle is hard to skip — the slug still goes to stdout alone, the hint to stderr. tcw work new also prints an "→ edit: …/initial-request.md" line (stderr) pointing at the new item's body so you can open it for editing right away. Every command that moves an item also names where it now lives, as a path relative to the project root — tcw work start and tcw work complete on stdout ("started my-item → docs/work/active/my-item"), tcw work new and tcw work inbox accept on stderr beside their other hints, leaving their stdout the bare slug. Inbox entries are deliberately permissive. A direct child of docs/work/inbox/ may be any standalone file, or a folder with exactly one INDEX.md or INDEX.txt; other folder files become bounded attachments/ on acceptance. Hidden files and empty directories are ignored, symlinks are not followed, and binary contents are never printed. See the optional docs/work-inbox-template.md for a useful request shape; the command does not require or parse that template.

initial-request.md is always-present — it is the item body/overview surface and the canonical request lifecycle artifact, seeded with title, the three-axis scaffold (Product / Technical / Meta changes), and any piped stdin.

For large implementations, plan.md may optionally declare a bounded DAG of stage documents in YAML frontmatter. Each declaration has a lowercase kebab-case id, a title, and depends_on; optional effort, complexity, priority, and tags reuse the work item's controlled vocabularies. The corresponding document is stored as plan/<id>.md. This keeps plan.md concise so agents can read it first, then load only the relevant stage. Dependencies communicate ordering and parallelism but do not create stage statuses or block lifecycle transitions. Legacy single-file plans remain valid.

The board (tcw work list) prints a |-delimited row per item — slug | status | stages | priority | title (priority is the integer, or - when unspecified). stages is a compact lifecycle artifact string: R for initial-request.md, S for spec.md, P for plan.md, O for outcome.md, and F for refined-outcome.md; missing or empty artifacts do not contribute letters, and - means no lifecycle artifacts are present. The board shows the live columns (backlog and active) and hides both closed columns by default — pass --status completed or --status discarded to list one, or --all for everything. It sorts by priority first (higher integer above lower, unspecified-priority items keeping creation order), then topologically — blockers appear before the items they block, since a priority preference can't jump a hard dependency — and annotates blocked items with their unresolved blockers.

Pass -i, --incl-desc, or --include-descendants to list every registered descendant work node. The output is grouped by project ID (# . for the current node), and the same --status / --all filters apply to every group. Initiative tasks are indented beneath their visible owning epic, including tasks from descendant nodes; each descendant row keeps its project-qualified slug and is printed only once.

Descendant items are printed with a project-qualified slug — <project-id>/<slug> — so each printed slug is a usable address. You can pass that qualified slug to any work command from the enclosing node (tcw work show project-a/<slug>, start, edit, complete, drop, …). A bare slug still resolves against the current node only. (blocked-by: refs shown on a qualified row stay node-local — they are bare slugs within that descendant.)

A qualified slug addresses any node in the registered graph, in any direction — descendant, ancestor, or sibling — not just nodes below you. A child project can therefore address (and link) an epic that lives in its parent. Project IDs are canonical and connections must be reciprocal, so there is nothing ambiguous to resolve; an unregistered project, or a path-shaped qualifier such as some/folder/<slug>, still does not resolve. A qualifier that names no registered project reports no such project in this graph: <id> rather than a misleading "no such work item".

Note that tcw work list -i and tcw serve remain descendant-only — they aggregate boards downward. Addressing and linking are graph-wide; aggregation is not.

Two backlog chores are AI-driven reviews rather than CLI commands — they need judgment the CLI cannot supply, so the assistant runs them:

Auditing the backlog reviews items in board order and reports read-only cleanup recommendations: likely duplicates or already-finished work, broken file references, stale blockers, malformed capability deltas, vague items, and items that look like they belong in another TCW node. It reports evidence and suggested next actions and asks before changing anything. Ask the assistant to audit the backlog, or run /tcw-audit-work-backlog in Claude Code; the procedure lives in the tcw-work skill, so it works under either harness.

Consolidating external plans finds Markdown planning documents outside docs/work/ and migrates them into backlog items, writing initial-request.md with the source content and provenance and copying obvious spec/plan sections into spec.md and plan.md. It runs only when you ask for it, lists every source file it proposes to delete before deleting any, and deletes only files git has already committed — anything untracked or with uncommitted changes is reported and left alone. Ask the assistant to consolidate external plans, or run /tcw-consolidate-plans in Claude Code; the procedure lives in the tcw-work skill, so it works under either harness.

A large item can be decomposed into child items with tcw work new "<title>" --parent <slug>: the child's folder is created inside the parent's, and tcw work list renders children indented under their parent. A child shares its parent's status by living inside it — starting or completing the parent carries its children along, while transitioning a child on its own promotes it to a top-level item. (That keeps any one item small; for work spanning separate repos, use a cross-node epic instead — see below.)

Items are referenced by a stable slug, resolved to "wherever it now lives," so moves never break references. Only the legal transitions above are permitted — anything else is refused, not silently allowed.

Completion is gated. tcw work complete --resolution done prints the Definition of Done and refuses without --confirm (and without --force if unresolved blockers exist). A discard prints no checklist and is not blocker-gated, but still refuses without --confirm:

Definition of Done — acknowledge each item:
  [ ] tests pass
  [ ] docs synced
  [ ] capabilities reconciled
  [ ] reviewed
  [ ] version offered

Resolutions are done · wontfix · duplicate · superseded. The "capabilities reconciled" item is the structural link back to the capabilities axis: a work item declares its capability delta at creation and reconciles it at completion, so the standing capability ledger stays current by construction.

Cross-node recursion (epics across repos)

For cross-node discovery (tcw work nodes / epics / delegate / escalate), a node is a git repo with a docs/work/; "orchestrator" and "project" are relative roles. (The current node — where tcw operates day-to-day — is the nearest tcw-config.yaml ancestor, which may be a subfolder.) A node nested under another is a child, the enclosing one its parent. An epic is an ordinary work item that tasks in child nodes point at via an initiative: back-pointer.

tcw work nodes                              # show this node's parent + child nodes

epic=$(tcw work new "Redesign checkout" --epic)
tcw work new "Slice 1" --initiative "$epic" # in a child node: link a new task to the epic
tcw work edit "$slug" --initiative "$epic"  # …or link an existing one

tcw work reconcile "$epic"                  # follow registered descendants → rollup
tcw work reconcile "$epic" --commit         # …and commit it
tcw work reconcile "$epic" --complete-when-ready  # …and auto-close it if every child is resolved

echo "needs an API change" | tcw work delegate child-repo "Expose X"  # request DOWN to a child inbox/
echo "cross-repo scope"    | tcw work escalate "Coordinate the redesign" # request UP to the parent inbox/

reconcile consolidates every child task for an initiative into a managed rollup block in the epic's initial-request.md — a slice table, surfaced capability deltas, and the next ready actions — and is read-only on the capabilities ledger. delegate/escalate only ever write a request into the target node's inbox/, never its tracked work, respecting the node write-boundary.

Initiative transitions are relation-gated: a task with initiative: <epic> is refused at start until the epic is active, and an epic is refused at complete while related child tasks are still open. --force overrides these gates when the relationship cannot be resolved or the user intentionally deviates. Once every child is resolved, the epic is flagged ready-to-close in tcw work list and in its rollup, and it may be completed directly from backlog — a coordinator epic that never had its own spec/plan doesn't need a throwaway start just to close it (the Definition-of-Done and capability gates still apply).

Run an item in an isolated checkout with --worktree:

tcw work start "$slug" --worktree           # active on trunk + a git worktree/branch for the code

Status transitions stay on the node's primary checkout (the board is always ls active/); in-flight edits live on the work branch. complete merges that branch back into the primary checkout, then tears the worktree down — and if the merge conflicts it stops with the branch and worktree left intact, so committed work is never silently dropped.

Run complete from the primary checkout, not from inside the item's own worktree: both the merge-back and the teardown act on the primary checkout, and git worktree remove would be deleting the directory you are standing in. From inside, TCW refuses and names where to re-run it. Every other command works from either place.


Skills — the judgment layer

The CLI is the mechanism; seven skills in skills/ are the judgment that drives it (the work↔capability lifecycle the tool only enforces structurally):

  • tcw-work — plan a request or existing work item through initial-request.md, spec.md, and plan.md; drive implementation or epic coordination through outcome.md and user verification in refined-outcome.md; commit each lifecycle stage before beginning the next; triage docs/work/inbox; run the start/complete lifecycle; resume active work; and decompose work into a cross-node epic. For unusually large plans it can declare staged plan documents, load only the stage being executed, and use that stage's pre- and post-checks.
  • tcw-capabilities — the ## Capability changes planning check, contradiction-detection, the Missing → Supported ledger flip at completion, product-layer wording coordination, and bootstrapping a capabilities ledger (/tcw-capabilities-init).
  • tcw-taxonomy — declaring vocabulary and feature entries, linking features to vocabulary, relatesTo links, federating shared vocabulary (tcw taxonomy extends), and bootstrapping a taxonomy from an existing codebase (/tcw-taxonomy-init).
  • tcw-plugin — install/repair the tcw CLI from PyPI (pipx); the single source of the /tcw-doctor procedure and of the install the session-start hook performs automatically, and the Codex entry point for both.
  • tcw-report — how to report a tcw bug or send a suggestion upstream to the TCW project as a GitHub issue, with a ready-to-fill skeleton for each. Found a bug or have an idea? File it at github.com/brocef/TCW/issues.
  • tcw-triage-issues — the other direction: sweep the GitHub issues on your own project, triage them, and turn the ones worth doing into work items (/tcw-triage-issues). Most issues shouldn't become work items, so it decides first — duplicate, not worth doing, too vague to act on — and offers a reply to the reporter either way, which you approve before anything is posted.
  • documentation-sync — the cross-cutting process skill the work lifecycle invokes at its plan and completion gates: evaluate a project's ## Documentation Sync triggers so docs (README, changelog, release notes, driving skills) move with the code that changes them. It also sets a project's Documentation Sync section up in the first place (/tcw-docs-sync-setup) and runs the version cut when a change set is done (/tcw-cut-version) — including folding later work into a version that was cut locally but never pushed.

The six axis/plugin skills name tcw … commands (and, for tcw-plugin, pipx; for tcw-triage-issues, gh) and never reimplement tool logic — mechanism stays in the binary, judgment in the skills; documentation-sync is a cross-cutting process skill rather than a CLI driver.


Status

The single-node core is built. Phases 1–5 are complete: tcw installs and exposes init | taxonomy | capabilities | work; the three filesystem stores sit on a shared bounded-tree core; the test suite (pytest over throwaway git repos) is green.

Cross-node recursion is now built (work Spec 2): for cross-node discovery, any git repo with a docs/work/ is a "node;" "orchestrator" and "project" are relative roles, cross-node initiatives (epics) link by an initiative: back-pointer, tcw work reconcile rolls child tasks up into the epic, the inbox is the inter-node channel (delegate/escalate), and tcw work start --worktree isolates an item's code in its own checkout.

Sentinel-based node detection (work Spec 1): tcw init now marks the current directory a TCW node (writing a tcw-config.yaml sentinel), so a single git repo can hold multiple projects as subfolders. Taxonomy extends works across sibling subfolder projects by construction.

The skill layer is now built (work Spec 3): the tcw-work and tcw-capabilities skills drive the lifecycle, and tcw capabilities set flips the capability ledger as work completes.

Still deferred (Phase 6): remote (Jira/wiki/graph-DB) store adapters and tracker sync — additive on top of the interfaces that already exist.

Releasing

Releases publish themselves. scripts/cut_version.py bumps every version-bearing file, rotates the changelog and release-note working files, commits, and tags; pushing that tag is what ships it:

python scripts/cut_version.py <patch|minor|major|X.Y.Z>
git push origin main --tags

The v* tag triggers .github/workflows/release.yml, which runs the full test suite, checks that the tag matches the version in pyproject.toml, builds, and uploads to PyPI. There is no API token — PyPI mints a short-lived credential from GitHub's OIDC claim ("Trusted Publishing"), which is why the workflow declares id-token: write and environment: pypi.

Two things are configured once, outside the repo, and must match the workflow exactly or the upload fails authentication:

Where Setting
pypi.org → Publishing project tcw-cli, owner brocef, repository TCW, workflow release.yml, environment pypi
GitHub → Settings → Environments an environment named pypi

A version can only be uploaded to PyPI once. If the workflow fails after a successful upload, that version number is spent — recover with a patch bump, not by re-running the job.

Further reading

Release files for tcw-cli 0.21.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tcw-cli 0.21.0
File Size Uploaded
tcw_cli-0.21.0.tar.gz 773.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tcw-cli 0.21.0
File Interpreter ABI Platform
tcw_cli-0.21.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.4 MB

Release files / tcw_cli-0.21.0.tar.gz

Download URL tcw_cli-0.21.0.tar.gz
Size 773.4 kB
Tags Source
SHA-256 checksum
How to use checksums
6e7c430c84c1831a22b20cdd0368f8fe5e794376beec21e79556654cf2b99ca8
BLAKE2b-256 checksum
How to use checksums
96c83683dba330814d99be7f4f11b14e69369b84acb75a1db3a2dcb6dbef8fed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 12, 2026.

Transparency log

Release files / tcw_cli-0.21.0-py3-none-any.whl

Download URL tcw_cli-0.21.0-py3-none-any.whl
Size 621.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6e66883ced0f589bb02c1968a9dbf3d0d8d705cbd05f3eb111d3a105cbe7f33c
BLAKE2b-256 checksum
How to use checksums
101394846dd8ccffdbc9c9dad5becb50a8e3fb1a3abd6df33e5c1c3cfcc1db37
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 12, 2026.

Transparency log

Release history Release notifications | RSS feed

2.6.1

2 release files

2.6.0

2 release files

2.5.1

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.3

2 release files

2.1.2

2 release files

2.1.1

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.3

2 release files

1.2.2

2 release files

0.21.1

2 release files

This release

0.21.0 This release

2 release files

0.20.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page