a75a821a29
The plan-extract step pulled every requested task's verbatim block through a single schema-bound agent() response. On a large plan that response self-limits and returns a well-formed PREFIX (commonly just task 1); the only guard rejected an empty list, so a truncated non-empty subset ran to a clean DONE with the missing tasks never attempted, yet the end-report read as a completion. Replace the single-shot dump with an enumerate pass + per-task extraction, guarded by cardinality checks (issue #22 suggested either direction; this does both, plus a backstop the issue did not scope): - plan-index enumerates ids + one-line titles only (bounded output, self-limits far later than a verbatim dump) and reports total_tasks, the whole-plan count computed from the fully-readable plan text independently of the emitted list. - plan-extract:<id> carries one task's verbatim block per agent() call, removing the across-all-tasks output ceiling. Calls fan out via parallel() (which honours the concurrency cap on a big plan); the requested id is bound inside each thunk and results are re-associated by it, never by array position, so the mapping holds regardless of the order parallel() resolves in. - Cardinality guards: (a) the whole-plan enumerate must list exactly total_tasks (the only backstop the no-range path can have without a caller range); (b) a task_range must be well-formed and covered exactly by the plan; (c) every expected id must extract non-empty text. Any breach is a hard BLOCKED with a precise "got N of M" reason, never a silent short run. Known residual (documented in-code): a single task whose own verbatim body overflows its dedicated response still returns an accepted prefix — bounded only by the planner's bite-sized-task invariant, far narrower than the closed vector. Verified by driving the real script through 11 extraction scenarios in a stubbed harness plus an order- and self-id-independence association test, and an adversarial three-lens review of the diff. closes #22