# Taskrail v0.4.0 ## Summary Taskrail `v0.4.0` continues two threads from `v0.3.0`. The first is a **task-authoring ergonomics** thread: make the CLI produce house-style task files by default. Today `task new --title "…"` writes a bare `T-` id and `T-.md` filename that ignore the title, so CLI-authored tasks are opaque amid slugged, human-scannable siblings, and there is no first-class way to re-slug a task after the fact without a manual, error-prone edit across the frontmatter id, the filename, and every inbound dependency reference. This version closes that gap deterministically. The second is the **strategic** thread deferred from `v0.3.0`: Spec-To-Task Decomposition and Gap Analysis. Both help derive or complete a task graph from spec intent. They stay exploratory and draft-only — reviewable output, never silently committed state — and remain deterministic and inspectable wherever possible, so they do not reopen the agent-framework or semantic-analysis creep the Caution and Explicitly Excluded sections forbid. Both threads keep Taskrail provider-independent and free of any LLM integration in the core, consistent with `v0.1.0` through `v0.3.0`. ## Goals - Make CLI-authored tasks match the human-authored, slugged house style by default. - Give a first-class, atomic way to re-slug or rename a task without dangling dependency edges. - Document the `filename == ".md"` slug-in-id invariant so the rename trap is discoverable rather than learned by hitting it. - Advance spec-to-task decomposition and gap analysis as reviewable, draft-only aids that keep planning grounded in the active spec. - Make `next` select new work from the active spec by default, so humans and agents do not silently drift into prior-version tasks. - Make active-spec task authoring less error-prone by letting authors name an active-spec area without retyping `STATE.md`'s spec path. - Let structural gap analysis reuse active-spec area scoping for focused review. - Make `status` expose active-spec drift at a glance and mirror the same active-spec next-task selection without writing state. - Surface the mechanical area-set delta between two spec versions so a `spec activate` migration shows which areas are new and which vanished. - Give a first-class way to re-point an open task's `spec_ref` onto the active spec without hand-editing frontmatter. - Keep every new capability deterministic, inspectable, and free of LLM calls in the core. ## Potential Features ### Slugged Task Creation `taskrail task new --title ""` must derive a slug from the title and write a **slugged** id with a matching filename, consistent with the existing corpus, so CLI-authored tasks are human-scannable by filename like their hand-authored siblings. Requirements: - Default slug source is `slugify(title)`: lowercased, non-alphanumeric runs collapsed to single hyphens, leading/trailing hyphens trimmed. The written id is `T-<n>-<slug>` and the filename is `T-<n>-<slug>.md`. - `--slug <curated-slug>` overrides the derived slug. Real corpora prefer short curated slugs over a full-title slug (title _"Curated league-strength coefficients for cross-league OVR comparability"_ → curated slug `league-strength-coefficients`), so an explicit `--slug` wins and `slugify(title)` is only the fallback. A supplied `--slug` is itself slugified/normalized so the result is always a valid id form. - Before slugifying, common Latin-1 letters are transliterated to ASCII (`ü`→`ue`, `ö`→`oe`, `ä`→`ae`, `ß`→`ss`, `é`→`e`, and the like) so accented titles yield readable slugs instead of silently dropped characters. - A title-derived slug is length-capped (roughly 50 characters, trimmed on a hyphen boundary so no word is cut mid-token) to keep ids and filenames bounded. An explicit `--slug` is taken verbatim after normalization and is not capped — the operator owns that choice. - If a supplied `--title` or `--slug` normalizes to an empty slug (for example a fully non-Latin title, or `--slug "!!!"`), the CLI still writes the legitimate bare `T-<n>` id but prints a warning to stderr, so the bare-id fallback is visible rather than silent. - With no `--title` and no `--slug`, keep the current bare `T-<n>` id and `T-<n>.md` filename. That is a legitimate id form `validate` already accepts. - Whatever id is written at creation must equal the filename stem. `validate` enforces `filename == "<id>.md"`, so the id and filename are two encodings of one identifier and the slug (if any) is baked into the id. - After creation `validate` passes with no further edits, for all three cases (`--title`, `--title --slug`, neither). - `--json` emits the created id and path in the existing machine-readable shape. Rationale: the id and filename are a single identifier under the `filename == "<id>.md"` rule, so a readable filename requires a slugged id — the CLI should produce that pairing directly instead of forcing a post-hoc cleanup. ### Task Rename And Re-Slug Add `taskrail task rename <id>`: rewrite a task's id (and therefore its slug and filename) and fix every reference to it, atomically. Requirements: - `--slug <slug>` sets the new slug directly; `--title <title>` derives it via the same `slugify` used by creation. Exactly one selector applies; the new id keeps the original numeric `T-<n>` and changes only the slug segment. - The command performs all coupled edits as one outcome: 1. rewrite the `id:` frontmatter field to the new slugged id, 2. rename the file to `<new-id>.md` (prefer `git mv` when under version control, fall back to a plain rename otherwise), 3. rewrite **every inbound `dependencies:` reference** in other task files from the old id to the new id, so no dependency edge is left dangling, 4. re-project `planning/STATE.md` (rename touches the aggregate the projection renders) and re-run `validate`. - `--dry-run` reports exactly what would change (frontmatter id, file rename, and each inbound dependency edit) and writes nothing, mirroring `repair`. - `--json` emits the planned or applied change set. - A `--slug`/`--title` that normalizes to an empty slug **de-slugs** the task to the bare `T-<n>` id — renaming `T-<n>-<slug>.md` to `T-<n>.md`, rewriting inbound references, and printing the same empty-slug warning to stderr as `task new`. This is symmetric with creation's bare-id fallback and lets an operator strip a bad slug rather than being forced to keep one. - If validation would fail (for example a target id that collides with an existing task), the command makes no partial change. Boundary: this is the first command that rewrites *other* tasks' content, so its commit-hygiene follows the existing rule — the rename, the rewritten inbound task files, and the regenerated `planning/STATE.md` land in one commit as a single logical outcome. It never advances status or fabricates work; it only re-encodes an identifier and the edges that name it. ### Slug-In-Id Invariant Documentation Document the slug-in-id model where operators meet it: the id and filename are two encodings of one identifier, `validate` enforces `filename == "<id>.md"`, and so a file cannot be renamed for readability without also changing its id (and every inbound dependency reference). Cover the `task new` slug behavior, the `task rename` flow, and the trap of a bare `git mv` producing `filename must be <id>.md`. This makes the invariant discoverable rather than learned by hitting a validation failure. Also document that `task rename` re-encodes the identifier only: it derives a new slug from `--title` but never rewrites the `title:` frontmatter field. Re-slugging a task and retitling its human-readable title are distinct operations, and there is no `task retitle` command in this version — so an operator who runs `task rename --title "New Title"` gets a new slug and an unchanged title, by design. ### Active-Spec Filtered Next Selection `taskrail next` must select new idle work from `STATE.md`'s active spec. It keeps the original deterministic ranking inside that scope — status, dependencies, priority, then stable task id — but filters `todo` candidates whose `spec_ref` path differs from `active_spec_path` before ranking. Requirements: - With no active task, `next` considers only `todo` tasks whose dependencies are resolved and whose `spec_ref` path matches `STATE.md`'s `active_spec_path`, then sorts by priority and stable task id. - Eligible tasks linked to older or other specs are skipped from idle selection; they remain visible through `status` drift reporting and `coverage` orphan/drift signals. - If no active-spec `todo` task is eligible, `next` reports no eligible task even when older-spec tasks are otherwise runnable. `next --json` exposes enough structured detail for agents to distinguish "no active-spec eligible task" from an empty backlog. - `taskrail next --include-off-spec` opts out of the active-spec filter for one invocation: it ranks eligible `todo` tasks across all specs with the original ranking (status, dependencies, priority, stable task id) and clearly flags any off-active-spec selection in both human and `--json` output. The default (no flag) stays active-spec-filtered. This gives agents a deterministic recovery path for older unfinished work without hand-editing state or having to know the target id in advance. It never writes state beyond the normal `next` selection probe and never bypasses `start`'s transition rules. - If `next` returns an already-active `in_progress` task and that task points outside the active spec, it must still return that task and print the existing warning. An active task owns the workflow slot; filtering must not suggest a second task that `start` would reject. - `status`, which computes the same next selection read-only, must use the same active-spec-filtered idle selection and surface the same active-task warning without writing `planning/STATE.md` or task files. - This filter must not make `validate` fail, rewrite old task files, auto-cancel old work, or hide older-spec open tasks from explicit commands like `start` when the operator names them directly and normal transition rules allow it. Rationale: `coverage` and `status` already report open tasks whose `spec_ref` points away from the active spec as advisory drift, but `next` is the command agents use to choose work. Filtering idle selection keeps the default work loop anchored to the active spec while still allowing explicit human recovery of older unfinished work. ### Active-Spec Selection Warnings > Superseded for idle selection by Active-Spec Filtered Next Selection. The warning behavior remains relevant when `next` returns an already-active `in_progress` task whose `spec_ref` path differs from `active_spec_path`. In that case `next` and read-only `status` must still surface a structured warning, because the active task owns the workflow slot and should be continued or explicitly resolved before another task is started. ### Active-Spec Task Authoring Shorthand Add `taskrail task new --area <anchor>` as a shorthand for authoring a task against the active spec. The command resolves the final `spec_ref` as `<active_spec_path>#<anchor>` from `STATE.md`, then uses the same `spec_ref` validation path as an explicit `--spec-ref`. Requirements: - `--area <anchor>` is mutually exclusive with `--spec-ref`; a task has one resolved spec reference. - The supplied anchor is matched against the active spec exactly as `spec show <active-version> --anchors` and `validate` compute anchors; it is not re-slugged from a title-like string. - Unknown anchors fail before writing a task file or refreshing `STATE.md`, and the error points the operator at `spec show <active-version> --anchors`. - `--follow-up` keeps its existing inheritance behavior. If `--area` is supplied with `--follow-up`, it explicitly overrides the inherited `spec_ref` in the same way a future explicit `--spec-ref` override would. - `--json` output keeps the existing created-task shape and reports the resolved `spec_ref`. Rationale: most new work targets the active spec. Letting authors type the area anchor avoids copying `specs/vN.N.N.md#...` by hand while preserving the active spec as the source of truth. ### Status Active-Spec Drift Breakdown Improve `taskrail status` so the read-only dashboard separates active-spec work from open work that still points elsewhere. This is reporting only; it must not filter selection or rewrite state. Requirements: - Human output includes a concise drift line with the count of open tasks linked to the active spec and the count of open tasks linked away from it. - When away-from-active work exists, status names the affected task ids and their `spec_ref` values in a short inspectable section. - `--json` mirrors the breakdown with structured counts and task/spec-ref pairs. - The computed `next` line uses the same active-spec-filtered idle selection as `next`; this status breakdown is the overview of open work outside that default selection scope. - Completed and cancelled tasks pointing at older specs remain delivered history and are excluded from away-from-active open-work counts, consistent with the coverage orphan rule. Rationale: the warning on `next` catches the selected task, but operators also need a glanceable answer to "how much open work is still outside the active spec?" without running a full coverage report. ### Spec Version Diff Add `taskrail spec diff <v1> <v2>`: a read-only, mechanical anchor-set diff between two versioned specs, reusing the same heading-anchor slug logic `coverage`, `validate`, and `spec show --anchors` already compute. It reports which coverable areas are **added** in `<v2>`, which are **removed**, and (best-effort) which are **renamed**, so advancing the active spec is an inspectable migration rather than a guess. Requirements: - Output lists added and removed areas by anchor. Added areas are the ones a migration must decompose into tasks; removed areas are the ones whose existing tasks become orphaned drift. - Rename detection is heuristic and best-effort only (for example an added and a removed anchor that share a normalized stem); it is clearly labeled as a candidate, never asserted as fact, and never guesses semantically. - Read-only and deterministic, side-effect-free like `coverage`/`validate`: it never writes `planning/STATE.md` or task files and never gates or makes `validate` fail. - `--json` mirrors the human output with structured added/removed/renamed lists. - Version arguments resolve the same way as the rest of the `spec` family; an unknown version fails before doing any work. Rationale: `coverage` and `status` report per-task drift against one active spec, but they cannot answer "what changed between these two spec versions?" A mechanical area-set delta turns a `spec activate` bump into a visible worklist of new areas to cover and old areas to re-point, without minting a new command vocabulary. Boundary: this is a reporting aid, not a migrator. It never creates tasks, never re-points `spec_ref`, and never advances status. A guided migration that acts on the delta stays out of scope here. ### Task Spec-Ref Re-pointing Add `taskrail task repoint <id>` with `--area <anchor>` (and `--spec-ref <path#anchor>`): rewrite an open task's `spec_ref` onto a new area without hand-editing frontmatter, using the same anchor resolution as `task new --area`. Requirements: - `--area <anchor>` resolves `spec_ref` to `<active_spec_path>#<anchor>` from `STATE.md`, matched against the active spec exactly as `task new --area` and `validate` compute anchors. `--spec-ref <path#anchor>` sets an explicit reference for the less common cross-spec case. `--area` and `--spec-ref` are mutually exclusive. - The command rewrites only the `spec_ref` frontmatter field, then re-projects `planning/STATE.md` and re-runs `validate`. It never changes the task id, slug, filename, title, status, or dependencies, and never touches other task files. - An unknown anchor fails before any write and points the operator at `spec show <active-version> --anchors`, mirroring `task new --area`. - `--dry-run` reports the old and new `spec_ref` and writes nothing; `--json` emits the planned or applied change. - Re-pointing targets open work. Completed and cancelled tasks are delivered history and are rejected, consistent with the coverage orphan rule that excludes terminal tasks from drift. Rationale: after `spec activate`, `next` skips off-spec open tasks, `status` lists them, and `next --include-off-spec` recovers one to run — but moving an open task *onto* the active spec still means hand-editing `spec_ref`, exactly the error-prone manual edit this version removes elsewhere. `repoint` is the small sibling of `task new --area` that closes the drift-recovery loop. Boundary: `repoint` re-encodes one reference field only. It is not a status mutator and not a bulk migrator; it never fabricates or advances work. ### Spec-To-Task Decomposition Support > Carried forward from `v0.3.0` — still exploratory. Help derive draft tasks from spec documents: a guided or import-style flow that turns spec headings and requirements into reviewable draft task files anchored to real `spec_ref` anchors. Output must remain reviewable draft tasks, never silently committed state. Keep the mechanical, deterministic parts in the binary and leave judgement-heavy decomposition to a prompt- or skill-assisted flow, consistent with the LLM stance below. ### Gap Analysis > Carried forward from `v0.3.0` — still exploratory. Help identify likely missing work — tests, verification tasks, docs, migration, rollout, edge cases, cleanup — as advisory, report-only signals over the existing task graph and active spec. Keep it mechanical and inspectable; surface candidates for a human or agent to promote into real tasks, never auto-created state. Deep semantic gap inference stays excluded (see Explicitly Excluded). Structural gap analysis should compose with active-spec area scoping. When `coverage --gaps` and `coverage --area <anchor>` are used together, the gap report is narrowed to the named coverable area using the same area resolution and rejection rules as normal coverage scoping. This keeps focused gap review aligned with the active spec without adding a separate filter vocabulary. Gap signals are advisory by default: `coverage --gaps` reports candidates and exits zero regardless of findings. A repository may explicitly select an operator policy with `--fail-on <category>`, which changes only the command exit code when a matching signal is present. This opt-in gate does not turn candidates into validation errors: the report remains read-only and unchanged, and gap findings never make `validate` fail. ### Version Skew Detection Taskrail ships behavior in two places that upgrade independently: the binary, and the skill files `init --with-skills` materializes into an adopter's repository. Installing skills is deliberately non-destructive, so upgrading the binary never refreshes an existing on-disk copy — the adopter keeps following the older skill's instructions with no error and no signal. Taskrail must make that skew visible. Requirements: - Materialized skill files carry the Taskrail version that wrote them, so skew is detectable at all. A skill installed by an older binary must be distinguishable from a current one without comparing file contents by hand. - When a Taskrail command runs in a repository whose installed skills were written by a different version than the running binary, it prints a warning to stderr naming the affected skills and the exact command that resolves it (`taskrail init --with-skills --force`). The warning names the two versions so the direction of the skew is obvious. - The warning is advisory and never gates: it goes to stderr, never corrupts `--json` stdout, never makes `validate` fail, and never blocks a transition. A stale skill is a missed improvement, not invalid state. - It must not become noise. A repository with no materialized skills is silent, and the check is cheap enough to run on ordinary commands — one read per skill file, from which it takes the recorded marker and, for a file carrying none, whether it is byte-identical to the copy embedded in the running binary. It never compares one install against another, and never inspects a difference beyond "identical or not". - A skill carrying no marker but byte-identical to the embedded package is not an unknown install: nothing recorded a version because nothing installed it — it was copied from the package the running binary carries. Such a copy is silent. A marker-free skill whose content diverges from the package still reports as unknown-version, without a remedy, because its version genuinely cannot be determined. - Detection is read-only. Taskrail never silently rewrites an adopter's skill files to resolve the skew; `--force` stays the explicit, backup-taking opt-in it is today. - The same principle covers the binary a workflow actually invokes. The packaged skills resolve it as `${TASKRAIL:-taskrail}`, which is correct for an installed release but silently wrong in a repository that builds Taskrail itself, where the intended binary is the working-tree build. Resolving to an unintended binary must fail loudly rather than write tracked-work state, and the guidance that reports it must name a remedy that actually resolves it. Rationale: the non-destructive install is the right default — it protects local edits — but paired with a silent upgrade it means an adopter can run an old workflow indefinitely while believing they upgraded. Every other version boundary in Taskrail (`layout_version`, `schema_version`) is checked and reported; skills are the one that is not. Boundary: this is detection and reporting only. Automatic skill migration, partial per-file upgrades, and any attempt to merge an adopter's local skill edits with the embedded copy stay out of scope. #### Layout Compatibility Beyond Init The `layout_version` refusal that keeps an older binary from mangling a newer repository layout is currently reached only through `init`. Every command that reads or writes the Taskrail layout must apply the same compatibility rule, so a repository recording a layout the running binary does not understand is refused consistently rather than only by the one command an adopter runs least often. Requirements: - A layout marker recording a `layout_version` newer than the running binary supports is refused by any command that loads the layout, with the existing "upgrade taskrail" wording, before any read-modify-write of state. - The rule stays a single shared check rather than a per-command copy, so the two cannot drift. - Read-only reporting commands refuse in the same way: reporting against a layout the binary cannot model is worse than refusing, because the output looks valid. Rationale: an unrecognized layout is exactly when an old binary is most likely to write something wrong, and today the guard is missing from every command that writes. ## Caution Do not turn `v0.4.0` into: - a generic PM system, - a hosted planning platform, - a giant agent framework. Keep it grounded in repo-native files, explicit structure, inspectable outputs, and human reviewability. In particular, `task rename` is a mechanical re-encoding of an identifier plus its inbound edges — it must stay atomic and validate-clean, and must not grow into a general refactoring or status-mutating surface. Decomposition and gap analysis stay draft-only and never write committed task state on their own. ## Recommendation About LLM Support Unchanged from `v0.2.0` and `v0.3.0`. Do not add direct LLM calls into Taskrail core. Preferred order remains: manual CLI and file model; prompts and skills for coding agents; an optional thin adapter layer only later, only if still needed. The decomposition and gap-analysis threads honor this: their deterministic parts live in the binary, their judgement-heavy parts live in prompts and skills. ## Explicitly Excluded Continue to exclude: - deep semantic drift detection, - deep semantic gap inference beyond mechanical, inspectable signals, - automatic planning loops, - background daemons, - complex multi-agent runtime logic, - a skill execution, scheduling, or orchestration runtime inside Taskrail, - repair or rename actions that advance task status or fabricate work, - decomposition or gap analysis that writes committed task state without human review, - historical/time-series statistics and any persistent event log backing them, - promoting manual-test artifacts to a required product invariant.