Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
403 changes: 403 additions & 0 deletions docs/project/specs/active/plan-2026-06-13-agent-cli-ergonomics.md

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions packages/tbd/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ upgrading is safe and revertible.

### Features

- **Agent CLI ergonomics (Phase 1)**: `close`, `reopen`, and `update` now accept
multiple IDs and process them under one lock, printing a single summary line (or a
structured `--json` `{ results, summary, sync }` object) plus a visible
unsynced-changes hint.
Validation is fail-closed, with `--ignore-missing` to downgrade unknown IDs to skips;
single-ID behavior is unchanged.
Free-text bodies (`--reason`/`--reason-file`, `-d`/`-f`, `--notes`/`--notes-file`)
accept `-` to read stdin, so shell-sensitive text no longer needs careful quoting.
`--quiet` is now fully silent on success (it also suppresses incidental worktree-heal
and config-migration notices).
The legacy no-op global `--no-sync` flag has been removed — writes always stage
locally and `tbd sync` publishes.
- **Forkable docs** (`tbd docs fork` / `unfork` / `update` / `diff` / `status`): fork
any managed doc into a visible fork dir (`docs/tbd/`, laid out by kind with a
generated `README.md` index, tracked in git).
Expand Down
56 changes: 29 additions & 27 deletions packages/tbd/docs/tbd-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1621,7 +1621,7 @@ display:

# Runtime settings
settings:
auto_sync: false # Auto-sync after write operations
auto_sync: false # Reserved; not applied to issue writes (they stage locally; run `tbd sync`)
index_enabled: true # Enable search indexing
```

Expand All @@ -1640,7 +1640,7 @@ const ConfigSchema = z.object({
}),
settings: z
.object({
auto_sync: z.boolean().default(false),
auto_sync: z.boolean().default(false), // reserved; issue writes stage locally
index_enabled: z.boolean().default(true),
})
.default({}),
Expand Down Expand Up @@ -2598,14 +2598,13 @@ Options:
--from-file <path> Create from YAML+Markdown file (all fields)
--type <type> Issue type: bug, feature, task, epic, chore (default: task)
--priority <0-4> Priority (0=critical, 4=lowest, default: 2)
--description <text> Description
--file <path> Read description from file
--description <text> Description ("-" reads stdin)
--file <path> Read description from file ("-" reads stdin)
--assignee <name> Assignee
--due <date> Due date (ISO8601)
--defer <date> Defer until date (ISO8601)
--parent=<id> Parent issue ID
--label <label> Add label (repeatable)
--no-sync Don't sync after create
```

> **Note on `--type` flag:** The CLI flag `--type` sets the issue’s `kind` field, NOT
Expand Down Expand Up @@ -2882,27 +2881,31 @@ tbd update proj-a1b2 --from-file issue.md
#### Update

```bash
tbd update <id> [options]
tbd update <ids...> [options]

Options:
--from-file <path> Update all fields from YAML+Markdown file
--title <text> Set title
--status <status> Set status
--from-file <path> Update all fields from YAML+Markdown file (single ID)
--title <text> Set title (single ID)
--status <status> Set status (single ID)
--type <type> Set type
--priority <0-4> Set priority
--assignee <name> Set assignee
--description <text> Set description
--notes <text> Set working notes
--notes-file <path> Set notes from file
--description <text> Set description ("-" reads stdin; single ID)
--notes <text> Set working notes ("-" reads stdin; single ID)
--notes-file <path> Set notes from file ("-" reads stdin; single ID)
--due <date> Set due date
--defer <date> Set deferred until date
--add-label <label> Add label
--remove-label <label> Remove label
--parent=<id> Set parent
--child-order <ids> Set child ordering hints (comma-separated)
--no-sync Don't sync after update
--parent=<id> Set parent (single ID)
--child-order <ids> Set child ordering hints (comma-separated; single ID)
--ignore-missing Skip unknown IDs instead of failing (bulk)
```

With two or more IDs, only shared fields apply (`--type`, `--priority`, `--assignee`,
`--add-label`, `--remove-label`, `--due`, `--defer`); per-ID and lifecycle flags are
rejected. Use `tbd close`/`tbd reopen` for lifecycle changes.

**Examples:**

```bash
Expand Down Expand Up @@ -2936,11 +2939,12 @@ tbd update proj-a1b2 --from-file issue.md
#### Close

```bash
tbd close <id> [options]
tbd close <ids...> [options]

Options:
--reason <text> Close reason
--no-sync Don't sync after close
--reason <text> Close reason ("-" reads stdin)
--reason-file <path> Read close reason from a file ("-" reads stdin)
--ignore-missing Skip unknown IDs instead of failing (bulk)
```

**Examples:**
Expand All @@ -2953,11 +2957,12 @@ tbd close proj-a1b2 --reason "Fixed in commit abc123"
#### Reopen

```bash
tbd reopen <id> [options]
tbd reopen <ids...> [options]

Options:
--reason <text> Reopen reason
--no-sync Don't sync after reopen
--reason <text> Reopen reason ("-" reads stdin)
--reason-file <path> Read reopen reason from a file ("-" reads stdin)
--ignore-missing Skip unknown IDs instead of failing (bulk)
```

#### Ready
Expand Down Expand Up @@ -3571,7 +3576,6 @@ Available on all commands:
--version Show version
--db <path> Custom .tbd directory path (Beads compat alias)
--dir <path> Custom .tbd directory path (preferred)
--no-sync Disable auto-sync (per command)
--json JSON output
--color <when> Colorize output: auto, always, never (default: auto)
--actor <name> Override actor name (not yet implemented)
Expand Down Expand Up @@ -3727,7 +3731,6 @@ tbd attic restore <id> <timestamp> [options]

Options:
--dry-run Show what would be restored
--no-sync Don't sync after restore
```

**Example:**
Expand Down Expand Up @@ -3830,7 +3833,6 @@ Options:
--from-beads [path] Auto-detect from .beads/ directory (default: current dir)
--branch <name> Specific branch to import from (default: both main + sync)
--dry-run Show what would be imported without making changes
--no-sync Don't sync after import
--verbose Show detailed import progress
```

Expand Down Expand Up @@ -4145,7 +4147,7 @@ IMPORT_BEADS(jsonl_file):

4. Report: N new, M updated, K unchanged, J skipped (tbd newer)

5. Sync (unless --no-sync)
5. Stage locally (publish later with `tbd sync`)

extract_short_id(beads_id):
# "tbd-100" → "100"
Expand Down Expand Up @@ -4511,7 +4513,7 @@ These flags/behaviors are maintained for Beads script compatibility:
2. **No daemon**: Background sync must be manual or cron-based

3. **No auto-flush**: Beads auto-syncs on write
- tbd syncs on `tbd sync` or with `settings.auto_sync: true` in config
- tbd publishes issues on `tbd sync` (per-command auto-sync is not currently enabled)

4. **Tombstone issues**: Decide import behavior (skip/convert/attic)

Expand Down Expand Up @@ -5476,7 +5478,7 @@ This is sufficient for the `ready` command algorithm.
| `--help` | `--help` | ✅ Full | Help text |
| `--version` | `--version` | ✅ Full | Version info |
| `--db <path>` | `--db <path>` | ✅ Full | Custom .tbd path |
| `--no-sync` | `--no-sync` | ✅ Full | Skip auto-sync |
| `--no-sync` | *(n/a)* | ❌ Dropped | Removed; issue writes always stage locally (run `tbd sync` to publish) |
| `--actor <name>` | `--actor <name>` | 🔄 Future | Override actor |
| *(n/a)* | `--dry-run` | ✅ tbd | Preview changes |
| *(n/a)* | `--verbose` | ✅ tbd | Debug output |
Expand Down
72 changes: 61 additions & 11 deletions packages/tbd/docs/tbd-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,30 +343,83 @@ Options:
- `--spec <path>` - Set or clear spec path (empty string clears; validated and
normalized). When updating a parent issue’s spec, the new value propagates to children
whose `spec_path` was null or matched the old value.
- `--ignore-missing` - Skip unknown IDs instead of failing the batch

**Multiple IDs:** `tbd update A B C --priority 1 --add-label done` applies the same
field updates to every issue under one lock.
Per-ID-only flags (`--title`, `--description`, `--notes`/`--notes-file`) are rejected
with two or more IDs, and `--status closed` is rejected in bulk — use `tbd close`.
`--description` and `--notes` also accept `-` to read stdin.

### close

Close a completed issue.
Close one or more completed issues.
A single ID keeps the classic one-line output; two or more run as a bulk operation — see
**Bulk operations and the output contract** below.

```bash
tbd close proj-a7k2 # Close issue
tbd close proj-a7k2 --reason="Fixed in PR #42"
```

Options:
- `--reason <text>` - Reason for closing
- `--reason <text>` - Reason for closing (`-` reads stdin)
- `--reason-file <path>` - Read the close reason from a file (`-` reads stdin)
- `--ignore-missing` - Skip unknown IDs instead of failing the batch

### reopen

Reopen a closed issue.
Reopen one or more closed issues — see **Bulk operations and the output contract**
below.

```bash
tbd reopen proj-a7k2 # Reopen issue
tbd reopen proj-a7k2 --reason="Bug reappeared"
```

Options:
- `--reason <text>` - Reason for reopening
- `--reason <text>` - Reason for reopening (`-` reads stdin)
- `--reason-file <path>` - Read the reopen reason from a file (`-` reads stdin)
- `--ignore-missing` - Skip unknown IDs instead of failing the batch

### Bulk operations and the output contract

`close`, `reopen`, and `update` accept multiple IDs and process them together under a
single lock. The output is designed so agents never need `2>&1 | tail -1`:

- **One summary line** on success, e.g. `✓ Closed 3, skipped 1 (already closed): …`,
followed by a visible `• Unsynced changes — run tbd sync to publish.` hint.

- **Fail-closed validation**: if any ID is unknown the whole batch aborts before writing
anything and lists the bad IDs.
Add `--ignore-missing` to downgrade unknown IDs to skips instead.

- **Single-ID behavior is unchanged**: one ID behaves exactly as before (idempotent
close; reopening an already-open issue still errors).
The “already-done is a skip” rule applies only to multi-ID batches.

- **`--quiet`** is silent on success and also suppresses incidental notices (worktree
auto-heal, config migration), so output stays clean.

- **`--json`** replaces the summary line with a machine contract:

```json
{
"results": [{ "id": "proj-a7k2", "action": "closed", "ok": true }],
"summary": { "changed": 1, "skipped": 0, "missing": 0, "total": 1 },
"sync": { "pending": true, "hint": "Run `tbd sync` to publish." }
}
```

**Free-text bodies without quoting hazards.** Reasons, descriptions, and notes accept
the text inline, from a file (`--reason-file`, `-f`/`--file`, `--notes-file`), or from
stdin with the `-` convention (`--reason=-`, `-d -`, `--notes=-`), so shell-sensitive
text (`$`, backticks, quotes) round-trips verbatim instead of being mangled by the
shell.

**Sync is stage-then-publish.** Every write lands in the local `tbd-sync` worktree
immediately; nothing reaches the remote until you run `tbd sync`. There is no
per-command auto-sync, and the legacy no-op `--no-sync` flag has been removed.

### ready

Expand Down Expand Up @@ -811,7 +864,6 @@ tbd list --json # JSON output
tbd list --quiet # Suppress non-essential output
tbd list --verbose # Enable verbose output
tbd create "Test" --dry-run # Show what would happen
tbd close proj-a7k2 --no-sync # Skip automatic sync
tbd list --debug # Show internal IDs
tbd list --color=never # Disable colors
```
Expand All @@ -823,7 +875,6 @@ Options:
- `--quiet` - Suppress non-essential output
- `--json` - Output as JSON
- `--color <when>` - Colorize output: auto, always, never
- `--no-sync` - Skip automatic sync after write operations
- `--debug` - Show internal IDs alongside display IDs

## For AI Agents
Expand Down Expand Up @@ -1094,7 +1145,7 @@ display:
sync:
branch: tbd-sync # Sync branch name
remote: origin # Remote name
auto_sync: true # Auto-sync after writes
auto_sync: false # Reserved; issue writes stage locally — run `tbd sync` to publish

docs_cache:
files: # Docs synced into the cache: destination -> docref
Expand Down Expand Up @@ -1262,10 +1313,9 @@ Notes:
A bigger hammer also exists: deleting the entire `$GIT_COMMON_DIR/tbd/` directory is
recoverable (layout and the data-sync worktree re-materialize from the config and the
`tbd-sync` branch on the next command, or via `tbd doctor --fix`); **but only for
synced data**. Issue changes made with `--no-sync` since the last `tbd sync` live as
uncommitted files inside that worktree and would be lost, so run `tbd sync` first if
you must delete it. This is why the recipe deletes only `layout.yml`, never the whole
directory.
synced data**. Issue changes since the last `tbd sync` live as uncommitted files
inside that worktree and would be lost, so run `tbd sync` first if you must delete it.
This is why the recipe deletes only `layout.yml`, never the whole directory.
- **Interrupted upgrades self-heal.** If the process dies between the two stamp writes
(layout updated but not config, or config but not layout), the next command with the
new version completes the migration; the abort recipe above also works from either
Expand Down
1 change: 0 additions & 1 deletion packages/tbd/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ function createProgram(): Command {
.option('--quiet', 'Suppress non-essential output')
.option('--json', 'Output as JSON')
.option('--color <when>', 'Colorize output: auto, always, never', 'auto')
.option('--no-sync', 'Skip automatic sync after write operations')
.option('--debug', 'Show internal IDs alongside public IDs for debugging');

// Add commands in logical groups
Expand Down
Loading
Loading