Skip to content

Releases: geuben/tdd-cli

v0.14.1

Choose a tag to compare

@geuben-dispatch geuben-dispatch released this 25 Sep 10:37
Immutable release. Only release title and notes can be modified.
9c6489e

Fixed

  • vitest ids from named vitest projects (#163). Whole-suite collection read vitest list
    text, which prefixes [<project>] to every line of a named vitest project and prints paths
    relative to that project's root, so it collected ids no run ever reports (and, via the
    per-file loop, a second spelling of every test). It now reads vitest list --json and roots
    each id at the test's own file. tdd appends --json itself; a collect command must not.
  • The vitest override-isolation check sees named vitest projects (#163). tdd doctor's
    check that the default config does not reach an override's files read the same text
    listing, so a [<project>] prefix meant it could never match. It now reads
    vitest list --json, and a listing that is not JSON fails the check, naming the command.
  • Adoption never picks another cycle's test (#163). When a cycle's declared test was
    missing, tdd advance adopted any test new since the run started, including an earlier
    cycle's target, so a premature advance handed cycle 1's test to cycle 2. Every target in
    the run is now excluded, compared by normalised id.
  • tdd target accepts the plan's spelling of a test (#163). It matched only the exact
    collected id, so it could not name the declared vitest id (src/a.test.ts > math > adds
    for a collected frontend::src/a.test.ts > math adds). It now qualifies and normalises
    its argument as a plan declaration is, and stores the collected id.
  • An adopted target the run cannot find is refused (#163). A test that collection
    listed but no run reported was recorded as the target and came back not_found on every
    advance, re-adopting a different test each time. The adoption is now evaluated first; on
    not_found the declared target is kept, adopted_target_not_found is recorded, and
    tdd advance answers resolve_blocker.

v0.14.0

Choose a tag to compare

@geuben-dispatch geuben-dispatch released this 24 Sep 21:02
Immutable release. Only release title and notes can be modified.
0821790

Added

  • tdd run abandon: end a run that will not be finished (#150). tdd run abandon --reason <text> ends the worktree's live or blocked run with outcome = abandoned;
    --run <id> does the same for a run whose worktree is gone. It records the reason and
    who did it (account and executor) in a new abandonment table, counts as a human
    intervention, and releases the worktree's claims. tdd fleet stops listing the run,
    tdd metrics reports it with an abandoned: {reason, by} entry, and a new run can
    start at the same path. The table is added on open; the ledger schema version is
    unchanged.

v0.13.0

Choose a tag to compare

@geuben-dispatch geuben-dispatch released this 23 Sep 10:16
Immutable release. Only release title and notes can be modified.
20eb12a

Added

  • Where a run's time went (#147). tdd metrics gains a per-run time object:
    wall_clock_s, suite_s, suite_share and by_phase (runs and suite seconds for
    every phase recorded). A live run's wall clock runs to now. The friction log gains a
    ## Time section with the same split, a per-phase table and a per-cycle table
    (wall clock, suite time and suite runs). Nothing new is recorded: these are the
    ledger's existing timestamps and invocation.duration_ms.
  • Split mode: a ledger the agent's account cannot open (#142). With
    /etc/tdd-cli/runner.toml in place, a separate runner account owns the ledger
    in a mode-700 directory. The agent's tdd forwards every verb except docs
    and init to it through sudo, and the runner executes every suite, gate,
    hook and git command as the agent. Executor identity is recorded as operator
    when the runner's config assigns the calling account a model, and claimed
    otherwise. tdd runner import <ledger> brings an existing ledger under the
    runner, marked pre_split_import, which tdd metrics reports. Setup and a
    verification checklist: tdd docs split.
  • tdd doctor reports mode (single or split). In split mode it probes,
    live and as the agent, that the uid drop works, that the agent cannot read the
    ledger, and that it cannot write the install.

Changed

  • GREEN runs the target first (#149). An AWAITING_IMPL advance whose target
    still fails now says so after running only the target. A passing target is followed
    by the whole-suite run that decides GREEN, as before. tdd metrics counts
    impl_attempts from each advance's target-only run only, and still counts every
    AWAITING_IMPL run in cycles recorded before this change.
  • tdd doctor on a single-user machine no longer lets "ledger outside
    worktree" stand for isolation: a ledger isolation notice says the ledger is
    owned by the same uid that runs the agent. The README and SECURITY.md say the
    same. Single-user installs behave exactly as before.
  • RED runs only the target test (#148). AWAITING_TEST, AWAITING_PIN and
    tdd sensitivity check now run the target alone: pytest runs the owning suite's
    collect command with the node id, and vitest filters to the file and an anchored,
    escaped -t name. A failure elsewhere no longer blocks RED. It is caught at
    GREEN, which runs the whole suite for every adapter. A collection error in
    the target's own file is still not_collected. A target adopted in place of the
    declared id is run in the same advance. The ledger is now schema 12:
    invocation.others_observed is 0 for a target-only run.

Fixed

  • GREEN ran only the target on cargo, gradle, xctest and exec (#153), so a
    regression elsewhere reached the close sweep unseen, and the sweep could skip the
    cycle's own project without the whole suite ever running. These adapters now
    narrow only when asked for a target-only run.

  • The close sweep re-ran a tree that had just passed (#146): gitutil.tree_hash hashed git
    state (index entries plus the unstaged diff), so the same content hashed differently before
    and after the GREEN commit, and the §6.1 skip of a cycle's own suites never fired. It now
    hashes working-tree content through a throwaway index, independent of what is staged or
    committed. A skipped close sweep still runs the cycle project's lint/typecheck gates; before
    this fix, the skip would have dropped them too. --reuse-baselines cache entries written by
    earlier versions no longer match and are re-probed once.

v0.12.2

Choose a tag to compare

@geuben-dispatch geuben-dispatch released this 17 Sep 10:08
Immutable release. Only release title and notes can be modified.
1900e95

Fixed

  • A wildcard directory in test_paths matched nothing (#136): a pattern ending in / was
    compared as a literal prefix by Project.is_test_file and joined as a literal path by the cargo
    adapter's _test_files, so crates/*/tests/ — how a workspace declares one pattern instead of
    one per crate — matched no file at all. Every test file in such a project classified as
    implementation, so a RED commit touching only tests was recorded as implementation_during_red,
    reporting the agent for writing production code it had not written; _test_files returned an
    empty list, leaving the unaccounted-files check with nothing to compare. A directory pattern is
    now matched per path segment (crates/*/tests/ covers crates/x/tests/main.rs, not
    crates/x/src/tests/main.rs), and the adapter globs it.

v0.12.1

Choose a tag to compare

@geuben-dispatch geuben-dispatch released this 17 Sep 09:44
Immutable release. Only release title and notes can be modified.
03cc9c2

Fixed

  • cargo: a test target whose name differs from its file's stem (#134): the adapter derived the
    target from the Running tests/<file>.rs header's path, which is only the target's name under
    cargo's default of one binary per tests/*.rs. A crate that sets autotests = false and declares
    its own [[test]] — many files compiled into one binary named something else — was unreachable:
    collection ran --test <stem> and cargo answered no test target named `<stem>` in default-run packages, so every cycle target came back not_collected, whatever id the plan declared. The
    target is now read from the artifact the header names (…/deps/bridge_tests-<hash>), which is
    also the only thing distinguishing two crates in a workspace whose test files are both
    tests/main.rs. The crate manifests supply the mapping back: target → the file it is built from
    for target_path and the collection file set, and the declared spelling for --test, since cargo
    writes a target's - as _ in the artifact. Manifests are located by walking up from the declared
    test_paths, not from _test_files(), which does not expand a wildcard directory pattern such as
    crates/*/tests/. Under the default layout target and stem still agree and nothing changes.

v0.12.0

Choose a tag to compare

@geuben-dispatch geuben-dispatch released this 17 Sep 06:33
Immutable release. Only release title and notes can be modified.
a25cb2d

Changed

  • Close sweep runs all gates before any suite (#129): lint and typecheck now run for every
    project in the sweep set before any project's suite executes. A failing gate short-circuits the
    sweep immediately — no suite runs, and next_action returns fix_regression with the failing
    gate entry. Previously each project ran its suite first and only then checked its gates.
  • Adapter._gate stops at the first failing command (#129): the first non-zero exit ends the
    gate; subsequent commands in the same gate list are not executed.
  • Gate memoisation (#129): a gate whose project tree hash is unchanged since it last passed
    within the same run is skipped (no command executed) and recorded with skipped = 1 in
    gate_result. A failed gate or a changed tree always re-runs.
  • fix_regression detail wording (#129): the reply for a gate failure now reads
    "Close sweep stopped before the suite: lint/typecheck gates failed." (was "Close sweep is green but lint/typecheck gates failed.").
  • Schema v11 (#129): gate_result gains tree_hash TEXT and
    skipped INTEGER NOT NULL DEFAULT 0. Existing ledgers are migrated on open via MIGRATIONS[10].

v0.11.0

Choose a tag to compare

@geuben-dispatch geuben-dispatch released this 16 Sep 11:22
Immutable release. Only release title and notes can be modified.
f45fd86

Added

  • tdd fleet reports baseline-collector and advance-holder liveness (follow-up to #115,
    fixes #122): fleet --json now includes stale and pid on every collecting row,
    matching the fields already on tdd status / tdd progress result objects. A new
    top-level advancing list exposes in-flight tdd advance claims with the same liveness
    fields. Human text gains — collector (pid N) is dead / — holder (pid N) is dead
    suffixes; no active runs is suppressed when an advance claim is held.

  • run.start_sha (schema v10, MIGRATIONS[9]): the HEAD commit at run start is now
    recorded on the run row and used as the reference point for late probes and
    --accept-failures gating.

  • gitutil.temporary_worktree: context manager that checks out a given sha into a
    detached temporary worktree and removes it on exit; used by the late-probe and
    --accept-failures implementations.

  • baseline_late_probe / baseline_late_probe_unobserved integrity events: emitted
    when the close sweep late-probes an un-baselined project at start_sha (observed) or
    cannot observe it (unobserved).

  • resume result refused_from_baseline: lists per-project tests that --accept-failures
    refused because they pass at start_sha.

  • New baseline_amended event detail shape: {project: {start_sha, accepted: {id: verdict}, refused: {id: verdict}}} with per-test verdicts.

  • Friction-log lines for amended baselines: accepted/refused tests appear under Human
    interventions; the run header gains a "Late baselines" line separate from "Baseline failures
    at start".

  • tdd docs [topic] prints the documentation shipped with this version. The wheel now
    carries docs/harness-integration.md, both example skills, the hooks README, the example
    plan, and the README under tddcli/_docs/. An agent meeting the tool in an unfamiliar
    project can read the protocol without network access — and without the version hazard of
    fetching a skill from main that targets a different verb_set_version than the installed
    binary emits. tdd docs with no topic returns a machine-readable index; tdd --help and
    tdd init now point at it.

  • artifact_regenerate_failed integrity event. When a regenerate hook exits non-zero,
    an artifact_regenerate_failed event is emitted on the cycle (or run, for run start)
    with the artifact name, exit code, and last 2000 chars of stderr. It is listed in the
    friction log under its cycle via the existing per-cycle event renderer.

  • artifact_check.regenerate_failed column (ledger schema v9). A non-zero hook exit
    sets this flag on the artifact_check row. Existing ledgers are migrated on open via
    MIGRATIONS[8].

Fixed

  • --accept-failures is now gated on run.start_sha: only tests that also fail at the
    start sha are accepted into the baseline; tests that pass there are refused and listed in
    result.refused_from_baseline. This prevents laundering run-introduced regressions.
  • Un-baselined projects are late-probed at start_sha in the close sweep instead of
    accepting HEAD failures verbatim. An observed probe inserts a late_probe baseline row and
    subtracts it like a normal baseline; an unobservable probe blocks with resolve_blocker
    and no longer recommends --accept-failures.
  • The no_baseline_for_project reply no longer recommends --accept-failures for
    unobservable projects.
  • A dead baseline collector no longer bricks the worktree. When a tdd run start
    process is killed during baseline collection, the stale baseline_claim row was left
    behind. tdd status and tdd progress computed stale: true correctly but emitted
    await_baseline anyway — a non-terminal verb that instructed autonomous executors to
    poll forever. Both commands now emit confirm_cycle_applicable with result.stale == true
    and result.pid when the collector pid is dead, and the detail names the recovery
    action: re-run tdd run start --plan <path>. The collecting_baseline result body always
    carries stale and pid so callers can inspect liveness without a separate tdd fleet
    call. (Fixes #115.)
  • check_artifacts: a failed regenerate hook is now a hard close-sweep failure.
    Previously, a regenerate hook that exited non-zero left the artifact path untouched
    (tree hash unchanged), so the staleness probe reported "fresh" and tdd advance replied
    complete. The exit code is now captured; a non-zero exit surfaces fix_regression with
    result.artifact_failures listing the affected artifact(s) and their stderr.
  • run start refuses when a regenerate hook fails before the first cycle. The run row
    is ended with outcome = "refused" so tdd status reports no active run.

v0.10.1

Choose a tag to compare

@geuben geuben released this 06 Sep 20:24
Immutable release. Only release title and notes can be modified.
3aac4a1

Fixed

  • pytest adapter: --dist loadgroup node ids. pytest-xdist reports a test marked
    xdist_group("<g>") as <nodeid>@<g> and pytest-json-report records that spelling, so a
    grouped target was not_found on every suite run even though it was collected and
    passed, and its passed/failed ids never matched collected ids. The adapter now strips
    the trailing @<group> when reading report and --collect-only ids. Only an @ after
    the last ] is treated as the suffix, so parametrised ids such as
    test_x[user@example.com] are left intact.

v0.10.0

Choose a tag to compare

@geuben geuben released this 05 Sep 19:32
Immutable release. Only release title and notes can be modified.
718a0df

[0.10.0] - 2026-09-05

Added

  • cargo adapter — Rust driver through cargo test. Ids are <target>::<path>
    (lib::… for unit tests, <tests-file-stem>::… for integration tests) and compose
    to --lib/--test <name> + -- --exact <path> for targeted runs. Verdicts are
    parsed from cargo's per-test console lines, attributed to targets by the
    Running … headers with stderr merged; doc-tests are excluded. A compile error maps
    to not_collected, matching gradle/xctest. Collection is cargo test --tests -- --list with a per-file --test <stem> fallback; tdd doctor's collectable gate is
    cargo test --no-run. Target lint requires the :: separator.

v0.9.0

Choose a tag to compare

@geuben geuben released this 03 Sep 22:16
Immutable release. Only release title and notes can be modified.
8c801f3

Added

  • tdd note — an executor-narrative channel. tdd note "<text>" attaches
    a phase-stamped note to the current cycle, or a run-level note once the run
    has ended (it falls back to the latest run, so no active run is required).
    Cycle notes render in the friction log as blockquote claims under their cycle;
    run-level notes render under a new ## Executor narrative section, which is
    omitted when there are none. Envelopes that carry an integrity event now nudge
    for a note while the reason is fresh (silenced once the cycle has one), and
    the terminal COMPLETE envelope — via advance or a final-cycle skip — asks
    for a closing narrative before rendering. Stored in a new note table via a
    v7→v8 ledger migration (#77, #94).

  • Baseline sanity gate. run start refuses a baseline whose failing ratio
    exceeds a threshold (default 0.5, suites under 10 collected tests exempt) with
    reason: "baseline_implausible", on the grounds that a mostly-red suite means
    the environment is broken rather than the code. A per-project
    baseline_max_failure_ratio in tdd.toml overrides the default, and
    --accept-baseline bypasses the gate and logs a baseline_accepted integrity
    event (#67, #84).

  • Standing-failure delta. A non-empty baseline is now compared against the
    previous run's baseline for the same worktree and a baseline_standing_delta
    event partitions the failing set into new versus inherited failures, so a
    pre-existing red test is distinguishable from one that broke since the last
    run (#67, #84).

  • Per-project health_command. An optional command in tdd.toml that is
    probed before baseline capture; if it fails, run start refuses with
    reason: "services_unreachable" instead of recording a baseline against a
    down dependency (#67, #84).

  • Declared-target lint. plan register and run start now lint every
    declared target against the adapter's id grammar (pytest ::, vitest >,
    gradle Class/method, xctest Bundle/Class/testMethod) and against project
    roots: a target path that duplicates a non-. project root prefix is refused
    unless the nested path genuinely exists in the worktree. Findings are returned
    under reason: "target_lint"; run start re-lints the stored contract
    against the current config before claiming a baseline (#71, #88).

  • Per-adapter sensitivity evidence line. Each adapter now extracts a
    target_evidence line from a failing target — the first E line for pytest
    (skipping the xdist worker header), the first : error: line in the test's
    window for xctest, failureMessages[0] for vitest, the junit failure message
    for gradle, and the last non-empty output line for exec. The sensitivity check
    persists it as sensitivity_check.evidence_line (v6→v7 migration) and the
    friction log's observed snippet prefers it over the raw first line, rendering
    <no assertion line captured> when empty and keeping the tail of over-long
    lines. Legacy rows with no stored evidence keep the first-line fallback (#68,
    #90).

  • Diagnosable executor attribution. TDD_EXECUTOR_MODEL lets a harness
    declare the executor identity (source: declared), taking precedence over
    transcript detection. When identity cannot be resolved, Executor.reason
    says why — session id unset, transcript not found, or transcript without a
    model record — and run start logs an executor_unknown event and surfaces
    executor_warning in its envelope. tdd doctor gains an informational
    executor-identity check (#74, #92).

Changed

  • Target adoption is evaluated in the same advance. When a cycle's
    declared target is missing and exactly one new test appears, advance
    adopts it (logging declared_test_mismatch) and judges RED — or drives the
    sensitivity check when it passed — from the suite run that already happened,
    instead of asking for a re-run. A declared id that differs
    from a single same-file candidate only by separator normalisation is
    disambiguated and adopted without asking; two same-file candidates still
    require an explicit tdd target (#72, #91).

  • CI now tests on Python 3.11 and 3.14 only (#87).