balls 0.5.12

Git-native task tracker for parallel agent workflows
Documentation
# bl import — bulk-create tasks from JSON on stdin

    usage: bl import [--legacy[=REF]] [-m MSG] [--as ID]

Ingests verbatim task JSON (the `bl show --json` / `bl list --json` bedrock
shape) from **stdin**. Ids and timestamps are preserved — no mint, no stamp, no
gate. One commit, all-or-nothing: an id that already exists refuses the whole
stream (use `bl update` to modify an existing task).

`import` is the write inverse of the bedrock read, and a distinct primitive from
`create`: "reproduce an existing identity" (migration, restore, federation join)
is not "mint a new one", which is exactly why `create` refuses foreign ids.

## Reopening a closed ball

**This store's own history is a source like any other**, so the round trip
reopens a retired ball — there is no `reopen` verb because this is it:

    bl show bl-1a2b --json | bl import

`bl show` resolves a closed id out of `balls/tasks` history (a deletion is older
CONTENT, not a tombstone) and the bedrock record is TOTAL — frontmatter and body
— precisely so import can write it back. Nothing is undone: the close commit and
the deletion both stand, and what you get is a ball that exists again carrying
the id and content it had before. That is a reproduction, not a transition,
which is exactly what import is for.

Two things fall out of the ordinary rules rather than needing their own:

- **A live id refuses.** Ids of closed balls are legally re-minted, so `bl-1a2b`
  may today be an unrelated ball — the collision refusal catches it and imports
  nothing, naming the id.
- **The restored `claimant` is stale.** A ball closed while claimed comes back
  claimed, by an agent whose `work/<id>` worktree the close tore down. `bl
  unclaim <id>` clears it. (Or drop the key from the record before the pipe, if
  you would rather it never land.)

Restoring the ball says nothing about the CODE: `bl close` squashed `work/<id>`
onto the delivery target before archiving the task, and that commit stands.
Reverting it is an ordinary `git revert`, a separate and deliberate act.

Verbatim stops at **shape**. Every id in a record — its own, its `parent`, and
each blocker's — must be a safe path token (`^[A-Za-z0-9][A-Za-z0-9_-]*$`),
because an id IS a filename and the edges are read back as `tasks/<id>.md`; one
that is not refuses the stream, naming the field. Liveness is NOT checked: an
edge pointing at a ball this store has never seen imports fine, which is what a
peer or a filtered stream carries.

Verbatim includes the **edges**: blockers arrive exactly as the stream spells
them, with no create-time sugar applied. So a stream carrying an older store's
subtasks — `{child, claim}` edges on the epic, which is what `--subtask-of` used
to mint — reproduces claim-gated epics that keep delivering flat to the
integration branch. Nothing converts them; nesting is declared by a `close` edge
(`bl create --skill`), so to nest an imported tree, spell the `close` edges in
the stream. An import authors no delivery of its own — no worktree, no target.

Verbatim also includes `root_commit` — the project identity a ball recorded at
birth. Import a ball rooted in ANOTHER project and it lands correctly but sits
outside this checkout's default `bl list` scope, which shows only what `bl claim`
would admit. The import says so: alongside the count it prints one stderr hint
naming how many records are rooted elsewhere and the read that lifts the scope,
`bl list --everywhere`. The ball is there — `bl show <id>` resolves it — it is
simply not this project's.

## Flags

- `--legacy[=REF]` — instead migrate a pre-greenfield store (preview first with
  `bl list --legacy`).
- `-m MSG` — commit note.
- `--as ID` — worker identity.

## Examples

    cat new-tasks.json | bl import        # create new tasks (won't overwrite existing ids)
    bl show bl-1a2b --json | bl import    # reopen a closed ball
    bl import --legacy                    # migrate an old store