@devwithdavid/ledger 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LEDGER.md ADDED
@@ -0,0 +1,459 @@
1
+ # `ledger` — reference
2
+
3
+ This is the CLI surface and the safe ways to extend it — not the philosophy
4
+ behind the project (see `DESIGN.md` for that, and `DECISIONS.md` for why
5
+ specific implementation choices were made).
6
+
7
+ Everything below shells out to the `ledger` CLI (built to `dist/cli/index.js`,
8
+ installed as `ledger` on PATH once linked/packaged). State lives in one
9
+ SQLite file at `$LEDGER_HOME/ledger.db` (default `~/.ledger`, override with
10
+ the `LEDGER_HOME` env var). There is no daemon — every command opens the
11
+ DB, does its thing, and exits.
12
+
13
+ Two very different audiences touch this file. Read the section for yours.
14
+
15
+ - **The first clerk** — the orchestrator. Turns what the human asks for into
16
+ scoped briefs, sets up tracking for them, dispatches agents, keeps things
17
+ moving, and escalates to the human when something needs their judgment.
18
+ Should always have this whole doc loaded.
19
+ - **Dispatched agents** — the workers. Execute one scoped task in an
20
+ isolated worktree, then report back. Should need almost none of this —
21
+ see below for why.
22
+
23
+ ---
24
+
25
+ ## For dispatched agents
26
+
27
+ You don't need to read this section, or load this file at all — it exists
28
+ so a human or clerk can see the contract you're actually held to. Every
29
+ `ledger agent dispatch` builds your real first prompt as your task text
30
+ plus this contract appended automatically (`buildTaskPrompt` in
31
+ `src/cli/commands/agents.ts` — that function is the single source of truth;
32
+ what's below is a description of it, not a separate copy to keep in sync):
33
+
34
+ - **Always work on a new branch** — never commit directly to the project's
35
+ default branch, regardless of delivery mode.
36
+ - **When you're done**, what your last action looks like depends on the
37
+ project's `delivery_mode`:
38
+ - `direct-pr`: open a pull request against the default branch — using
39
+ whatever tooling is available for that project's remote (e.g. `tea`
40
+ for a Forgejo remote; ledger doesn't care which, that's your call) —
41
+ **do not merge it yourself, regardless of anything else you're told.**
42
+ PR review is a real checkpoint, not a formality to clear on your own
43
+ (see `DECISIONS.md` for the incident that made this explicit). Then
44
+ `ledger agent update <your-agent-id> --status done --outcome
45
+ '<pr-url>'`.
46
+ - `local-only`: just `ledger agent update <your-agent-id> --status done
47
+ --outcome '<branch-name-or-report-path>'`.
48
+ Your agent id was given to you in your initial prompt.
49
+ - **If you get stuck and need a human/clerk decision before you can
50
+ continue**: just state the question and stop where you are (don't spin,
51
+ don't guess and proceed). herdr detects an agent sitting idle mid-question
52
+ as `blocked` automatically — the watcher plugin picks this up with zero
53
+ action from you, and it surfaces to the clerk via `ledger catchup`.
54
+ - **Everything else is the clerk's job, not yours** — roadmap, project
55
+ registration, dispatching other agents. Per `DESIGN.md`'s v1 default, you
56
+ don't spawn peers or coordinate with other agents directly even though
57
+ herdr's socket API would technically let you; work your own task and
58
+ report through the two channels above. (See "Agent-to-agent coordination"
59
+ below if this default has been changed for your project.)
60
+
61
+ ## For the first clerk
62
+
63
+ ### Governance gates (binding on you — the clerk)
64
+
65
+ Full text and provenance for each gate: the "Governance decisions"
66
+ section of `DECISIONS.md` (2026-08-23, user-directed). It is
67
+ authoritative; this is a working summary.
68
+
69
+ **Hard gates** — the CLI enforces them mechanically. Do not work around
70
+ them; if a gate blocks something you want to do, that is the gate
71
+ working — escalate to the user.
72
+
73
+ - **C3 — survival proof before releasing.** `agent release` first proves
74
+ where the work in that worktree lives: `durable` (clean, and every
75
+ commit on the checked-out branch is on a remote) auto-returns the
76
+ worktree; `at-risk` (uncommitted changes and/or unpushed commits) and
77
+ `unprovable` (git could not verify) are *refused* and recorded as a
78
+ `release_refused` event carrying the proof. `--force` skips the proof —
79
+ it is the user's explicit authorization to discard work, never a repair
80
+ path. Use it only when the user has actually said so.
81
+ - **C6 — recorded authorization; no silent duplicates.** `agent dispatch`
82
+ requires `--authorization <basis>` and records it on the agent row and
83
+ the `dispatched` event: `user-explicit` = an in-the-moment green light;
84
+ `pre-authorized` = a previously granted, per-item, revocable standing
85
+ latitude. The value is *your* attestation — the CLI cannot know whether
86
+ the user really said yes, so never dispatch with a basis you would not
87
+ stand behind (the soft half of this gate). It also refuses to start a
88
+ second live (`working`/`blocked`) agent on a roadmap item that already
89
+ has one, unless you pass `--confirm-duplicate` — pass it only when the
90
+ user explicitly approved a second live agent on that item. Older
91
+ `idle`/`done` agents on the same item are *not* a refusal.
92
+
93
+ **Soft gates** — nothing in the CLI can check these; they bind you, not
94
+ the code.
95
+
96
+ - **C1 — you never act on project code directly.** Read-only over project
97
+ code; all code change goes through dispatched agents. Sole exception: a
98
+ concrete, in-the-moment, user-approved operation — executed exactly as
99
+ approved, never inferred or generalized, conferring no standing
100
+ authority.
101
+ - **C2 — you never merge, force-push, or close a PR without an explicit
102
+ user word.** One explicit word at a time, in the moment; there is no
103
+ standing relaxation. (Worker-side mirror: A2.)
104
+ - **C4 — agents never address the user directly; you are the single
105
+ channel.** If the user intervenes directly in a worker pane, that
106
+ instruction is authoritative: reconcile at the next catch-up, never
107
+ override or re-dispatch against it.
108
+ - **C5 — report outcomes faithfully.** What was observed, not intended;
109
+ failures stated plainly with evidence; uncertainty labeled.
110
+ - **C7 — observe before mutating; observation failure is not evidence.**
111
+ Board/agent status changes only from fresh observation (pane state,
112
+ process, git) — never cosmetics. When observation fails, report the
113
+ unknown rather than patching the record to look consistent.
114
+ - **C8 — orient at session start; no blind turn-end.** First act:
115
+ `ledger catchup` and verify your own claim (a foreign live claim is
116
+ reported as a conflict, never force-taken; `--force` is the
117
+ user-authorized path). After a dispatch, re-observe that the agent
118
+ actually spawned and engaged before reporting success.
119
+ - **C9 — you do not self-modify.** Never edit your own contract or skills
120
+ (this file, `DECISIONS.md`, the skill pointer) without explicit user
121
+ approval — a gate must not be editable by the party it binds.
122
+
123
+ **Liveness (A8, clerk side).** At catch-up, an `idle` agent with
124
+ unfinished work is suspect — it may have died on a usage limit. `catchup`
125
+ now lists every `idle` agent together with the tail of its pane's recent
126
+ output (see below) — that's the mechanical part: it puts what the agent
127
+ last showed in front of you without a separate pane read. The judgment
128
+ (is that tail a real question, a usage error, or nothing wrong at all —
129
+ and whether to answer, re-dispatch, or escalate) is still yours.
130
+
131
+ ### Session start: catch up in one command
132
+
133
+ ```sh
134
+ ledger catchup [--project <name>] [--idle-pane-lines <n>] [--json]
135
+ ```
136
+
137
+ Returns, in one call: agents currently `blocked` (need a decision now),
138
+ every agent currently `idle` together with the tail of its pane's recent
139
+ output (A8 liveness triage — did it ask a question, hit a usage error, or
140
+ go quiet mid-task?), every `events` row since the last time any clerk ran
141
+ `catchup` (tracked in `first_clerk.last_seen`), and every roadmap item not
142
+ yet `done`/`dropped`, ordered by priority `high → normal → low` (ties by
143
+ id).
144
+ This is the entire "what's going on" operation from `DESIGN.md` — read this
145
+ instead of any prose file, every session.
146
+
147
+ `--idle-pane-lines <n>` controls how much of each idle agent's pane tail is
148
+ shown (default 25; `0` skips pane reads entirely — just the idle-agent list).
149
+ A pane read failing (pane/tab closed, herdr socket down) never fails
150
+ `catchup` itself: a single dead pane prints `pane unreadable: <reason>`
151
+ under that agent and the rest of catch-up proceeds; a herdr socket that's
152
+ unreachable entirely prints one note for the whole section instead of
153
+ repeating the same failure under every idle agent. `--json` includes the
154
+ same data as an `idle` array (`agent`, `pane_tail`, `pane_read_error`) plus
155
+ top-level `idle_pane_read_note` for the whole-socket case.
156
+
157
+ ### First-clerk claiming
158
+
159
+ ```sh
160
+ ledger clerk claim --session-id <id> --herdr-pane <id> [--force]
161
+ ledger clerk status
162
+ ```
163
+
164
+ `claim` fails if another claim exists and is under 12 hours old, unless
165
+ `--force` is passed. Do this once per new/resumed clerk session before
166
+ dispatching anything.
167
+
168
+ ### Registering a project
169
+
170
+ ```sh
171
+ ledger project add <url-or-local-path> --name <name> [--delivery-mode direct-pr|local-only]
172
+ ```
173
+
174
+ Always clones into ledger's own `$LEDGER_HOME/projects/<name>` — never the
175
+ user's working checkout (see `DESIGN.md` for why: worktree metadata
176
+ pollution, and predictable starting state). A local path clones fast
177
+ (hardlinks) and picks up unpushed branches; it can never see uncommitted
178
+ changes — tell the user to commit first (a throwaway branch is fine) if
179
+ they want an agent working from something not yet committed.
180
+
181
+ **For work that doesn't exist anywhere yet** — no repo to clone, local or
182
+ remote (e.g. the very first agent dispatched for a brand-new idea) — use
183
+ `init` instead of `add`:
184
+
185
+ ```sh
186
+ ledger project init --name <name> [--delivery-mode direct-pr|local-only]
187
+ ```
188
+
189
+ Creates an empty git repo (one empty initial commit — just enough for
190
+ treehouse to have a ref to lease worktrees from) directly in
191
+ `$LEDGER_HOME/projects/<name>`. `repo_url` is `NULL` for these — there's no
192
+ origin yet. Defaults to `--delivery-mode local-only` rather than
193
+ `direct-pr`, since there's nowhere to open a PR against until the project
194
+ actually gets a remote. Don't scaffold anything beyond the empty commit
195
+ yourself — what the project becomes is the dispatched agent's job.
196
+
197
+ **Once a from-scratch (`init`'d) project gets a real remote** — code
198
+ pushed somewhere for the first time — record it:
199
+
200
+ ```sh
201
+ ledger project update <name> [--repo-url <url>] [--delivery-mode <mode>]
202
+ ```
203
+
204
+ Adds a git `origin` remote to the local clone if it doesn't already have
205
+ one; never overwrites an existing remote. `repo_url` in the returned row
206
+ always reflects the actual git remote, not just what was requested — if
207
+ `origin` already existed with a different URL, the response includes a
208
+ `warning` rather than silently recording something git doesn't agree with.
209
+
210
+ Other project commands: `ledger project list [--json]`, `ledger project get <name>`.
211
+
212
+ ### Roadmap: turning asks into briefs
213
+
214
+ ```sh
215
+ ledger roadmap add --project <name> --title <title> [--parent <id>] [--description <text>] [--priority <high|normal|low>]
216
+ ledger roadmap list --project <name> [--status <status>] [--all] [--json]
217
+ ledger roadmap update <id> [--status <status>] [--title <title>] [--description <text>] [--priority <high|normal|low>]
218
+ ```
219
+
220
+ Statuses: `planned | in_progress | blocked | done | dropped`. `--parent`
221
+ nests a sub-item under an existing roadmap item (arbitrary depth).
222
+
223
+ Priority: `high | normal | low`, default `normal` (also a column on every
224
+ row: JSON output and `list`'s table both show it). It is a coarse triage
225
+ rank for ordering the not-done queue — *order*, not readiness: readiness
226
+ stays with `status = blocked` + `description` (auto-unblock is a separate
227
+ future feature, not implied by priority). A stale priority degrades
228
+ gracefully: it mis-sorts the catch-up list, which is visible there; it
229
+ cannot silently block ready work the way a stale dependency edge could
230
+ (DECISIONS.md, 2026-08-23).
231
+
232
+ This is where "what the human asked for" becomes "briefs an agent can
233
+ actually execute without holding the whole feature in context." Default
234
+ behavior per `DESIGN.md`: propose a breakdown and let the human approve it,
235
+ rather than deciding unilaterally or always making them write it themselves
236
+ — unless they've told you otherwise for this project. A `roadmap` row's
237
+ `description` is a good place for the brief itself (acceptance criteria,
238
+ constraints, what's explicitly out of scope) — that's what you'll turn into
239
+ `--task` text at dispatch time via `--roadmap-item <id>`.
240
+
241
+ `roadmap list` excludes `done`/`dropped` by default; pass `--all` to see
242
+ everything.
243
+
244
+ ### Dispatching an agent
245
+
246
+ ```sh
247
+ ledger agent dispatch --project <name> --task "<description>" \
248
+ --authorization <user-explicit|pre-authorized> \
249
+ [--roadmap-item <id>] [--kind claude|pi|codex|...] \
250
+ [--label <short-label>] [--spawned-by <agentId>] [--wait] \
251
+ [--confirm-duplicate]
252
+ ```
253
+
254
+ `--authorization` is required (gate C6): `user-explicit` = an in-the-moment
255
+ green light from the user; `pre-authorized` = a previously granted,
256
+ per-item, revocable standing latitude the clerk requested. It is recorded
257
+ on the agent row (`agents.authorization_basis`) and the `dispatched`
258
+ event, so every dispatch is auditable — the default is no longer
259
+ "allowed". The value is your attestation: the CLI cannot verify the
260
+ conversation, so pick the value that is true.
261
+
262
+ `--confirm-duplicate`: if `--roadmap-item` names an item that already has
263
+ a live (`working`/`blocked`) agent, the dispatch is refused without this
264
+ flag; pass it only when the user explicitly approved a second live agent
265
+ on that item (C6).
266
+
267
+ For `--kind claude`, dispatch always runs it with `--permission-mode
268
+ bypassPermissions` and auto-dismisses Claude Code's one-time "do you trust
269
+ this folder?" dialog (every treehouse worktree is, from its point of view,
270
+ a folder it's never seen — nobody's present in that pane to answer it, so
271
+ without this it would just hang forever). Both confirmed live — see
272
+ `DECISIONS.md`. Scoped to `claude` specifically per the user; other kinds
273
+ run with whatever their own default permission behavior is.
274
+
275
+ What this does, in order (see `DECISIONS.md` for why it's shaped this way):
276
+
277
+ 1. `treehouse get --lease` against the project's clone → a durably-leased,
278
+ isolated worktree path.
279
+ 2. **One herdr workspace per project, not per dispatch.** If the project
280
+ already has a live workspace (`projects.herdr_workspace`, checked with
281
+ `herdr workspace get` in case the user closed it since), this dispatch
282
+ adds a new **tab** to it (`herdr tab create --workspace <id> --cwd <that
283
+ path>`) — so multiple agents working the same project show up as tabs
284
+ in one workspace, not scattered across separate workspaces. Otherwise
285
+ this is the project's first dispatch (or its old workspace is gone): a
286
+ new workspace is created (`herdr workspace create --cwd <that path>`,
287
+ labeled with the *project* name) and recorded onto the project row for
288
+ every later dispatch to reuse.
289
+ 3. `herdr agent start --kind <kind> --pane <pane>` → starts the coding
290
+ agent in that pane.
291
+ 4. Inserts the `agents` row (worktree path + herdr workspace/tab/pane ids)
292
+ and a `dispatched` event — *before* sending the task, so the task prompt
293
+ can reference the agent's own row id.
294
+ 5. `herdr agent prompt <pane> "<task + reporting contract>"` → delivers the
295
+ task (see "For dispatched agents" above for exactly what gets appended).
296
+
297
+ If workspace/tab-creation or agent-start (steps 2-3) fail, that tab (or the
298
+ whole workspace, only if this dispatch just created it — never the shared
299
+ workspace if it was reused, since other agents may be live in it) is closed
300
+ and the worktree lease is returned before the error surfaces — no orphaned
301
+ pane, no phantom `agents` row for a dispatch that never actually started.
302
+ If only the final prompt delivery (step 5) fails, the row is *kept* (the
303
+ agent process is real and running by then) with a `dispatch_prompt_failed`
304
+ event — investigate with `agent get`, retry the prompt by hand via
305
+ `herdr agent prompt`, or `agent release` to abandon it.
306
+
307
+ Scope each dispatch to a single roadmap sub-item where one exists, rather
308
+ than handing an agent a whole feature — that's the actual point of having
309
+ a roadmap (small, well-scoped context per agent).
310
+
311
+ From here, **you do nothing further** to track state — the herdr watcher
312
+ plugin keeps `agents.status` and `events` current automatically as that
313
+ pane's agent state changes.
314
+
315
+ Other agent commands:
316
+
317
+ ```sh
318
+ ledger agent list [--status <status>] [--project <name>] [--json]
319
+ ledger agent get <id>
320
+ ledger agent update <id> [--status <status>] [--outcome <text>]
321
+ ledger agent release <id> [--force]
322
+ ```
323
+
324
+ `agent release` first proves the work's survival (gate C3): `durable`
325
+ (worktree clean, and every commit on its checked-out branch is on a
326
+ remote) auto-returns the worktree to the treehouse pool and closes the
327
+ agent's herdr tab; `at-risk` (uncommitted changes and/or unpushed
328
+ commits) or `unprovable` (git could not verify) is *refused* and recorded
329
+ as a `release_refused` event carrying the proof — escalate to the user
330
+ instead. `--force` skips the proof: it is the user's explicit
331
+ authorization to discard work, never a repair path. It does **not** touch
332
+ `agents.status` — release is a worktree-lifecycle action, not a judgment
333
+ that the work is finished. Run it once you're done inspecting a
334
+ completed/abandoned agent's worktree.
335
+
336
+ ### Monitoring and escalating
337
+
338
+ `ledger catchup` surfaces every `blocked` agent. For each one:
339
+
340
+ 1. Read its `task_description` and recent `ledger event list --agent <id>`
341
+ to understand what it's blocked on.
342
+ 2. If it's something you can resolve yourself with information you already
343
+ have (a clarification, a decision within scope the human already gave
344
+ you) — answer it directly with `herdr agent prompt <pane> "<answer>"`,
345
+ using the agent's `herdr_pane` from `agent get <id>`. This unblocks it;
346
+ the watcher will pick up the resulting state change on its own.
347
+ 3. If it genuinely needs the human's judgment — surface it to them. Don't
348
+ sit on a blocked agent hoping it resolves itself; that's exactly the
349
+ state `ledger catchup` exists to make visible immediately instead of
350
+ burying it in a pane you'd otherwise have to remember to check.
351
+
352
+ `ledger catchup` also surfaces every `idle` agent, each with a tail of its
353
+ pane's recent output (A8). An idle agent isn't blocked — herdr didn't detect
354
+ it waiting on a question — but with unfinished work it's still suspect: read
355
+ the tail before assuming it's fine. A real usage-limit error or a stalled
356
+ run looks different from ordinary quiet-between-turns output; judge which
357
+ one you're looking at, same as you would from reading the pane directly,
358
+ just without the extra step of going to find the pane first.
359
+
360
+ ## Safe ways to extend this
361
+
362
+ Per `DESIGN.md`'s core philosophy: before adding anything, ask *"could this
363
+ instead be a skill I load, or a plugin herdr invokes?"* Concretely, for
364
+ this project:
365
+
366
+ **Core** (belongs in this repo, under `src/`): the schema/migrations, the
367
+ `ledger` CLI verbs every clerk needs regardless of what project or workflow
368
+ it's running (`project`/`roadmap`/`agent`/`event`/`clerk`/`catchup`), and
369
+ the one watcher plugin that keeps `agents.status`/`events` in sync with
370
+ herdr. The test: is this genuinely load-bearing for *every* clerk session
371
+ on *every* project, not just a preference for how one workflow should work?
372
+
373
+ **Pure extension** (does not belong in this repo): anything that's really
374
+ "how I personally want this workflow to behave" rather than a shared
375
+ primitive. It reads/writes the same SQLite file or shells out to the same
376
+ `ledger` CLI, but lives entirely outside `src/`:
377
+
378
+ - A notification integration (Slack/X/Discord on `blocked`/`done`) → its
379
+ own small script polling `ledger event list --json` or `ledger agent list
380
+ --status blocked --json`, run however you like (cron, a herdr plugin of
381
+ its own). Not a new `ledger` subcommand.
382
+ - A different/additional herdr event hook (e.g. reacting to
383
+ `pane.agent_detected`) → its own separate `herdr-plugin.toml` +
384
+ entrypoint, `herdr plugin link`'d independently. Doesn't have to live in
385
+ this repo, and shouldn't be folded into `herdr-plugin.toml` here unless
386
+ it's something the watcher itself needs to keep `agents`/`events`
387
+ correct.
388
+ - A roadmap-breakdown heuristic, an escalation policy, "how to write a
389
+ good brief for this specific project" → a Claude Code skill the clerk
390
+ loads, or just accumulated judgment in this doc's "For the first clerk"
391
+ section. Not schema, not CLI.
392
+ - A dashboard/reporting view → a standalone script reading
393
+ `$LEDGER_HOME/ledger.db` directly (it's "fully inspectable with any
394
+ SQLite client," by design — see `DESIGN.md`). Not a `ledger` command.
395
+
396
+ If you're genuinely unsure which side something falls on: does it need to
397
+ exist for *any* dispatch to work correctly, or is it optional polish one
398
+ workflow wants? The former is core; the latter is an extension, even if
399
+ it's small.
400
+
401
+ **Adding a column or table** (core, when it's actually needed). Add a new
402
+ file under `src/db/migrations/000N_<name>.ts` exporting a `Migration` (see
403
+ `src/db/migrations/0001_init.ts` for the shape), and register it in
404
+ `src/db/migrations/index.ts`. Migrations run automatically, in order,
405
+ inside a transaction, tracked in `schema_migrations` — never hand-edit
406
+ `0001_init.ts` after it's shipped. Every new column needs a concrete reason
407
+ tied to something a clerk or the watcher actually does (see `DECISIONS.md`
408
+ for the precedent: `first_clerk.last_seen` was added exactly this way).
409
+
410
+ **Adding a CLI command** (core). Add a `register*Commands(program)`
411
+ function in `src/cli/commands/`, call it from `src/cli/index.ts`. Reuse
412
+ `getDb()` from `src/db/client.ts` and the `printJson`/`printTable` helpers
413
+ in `src/cli/format.ts` — don't hand-roll output formatting per command.
414
+
415
+ **Adding a new herdr event hook to the watcher** (core, only if the hook
416
+ is about keeping `agents`/`events` correct — otherwise it's the "separate
417
+ plugin" case above). Add an `[[events]]` block to `herdr-plugin.toml`
418
+ (`on = "<hook.name>"` — dotted, confirmed live: `workspace.created`,
419
+ `tab.created`, `pane.created`, `pane.agent_status_changed`,
420
+ `pane.agent_detected`, etc.; this is a curated subset of herdr's full
421
+ internal event catalog, not identical to it — the full catalog is
422
+ `schemas.event.$defs.EventData` from `herdr api schema --json`, but not
423
+ every one of those has a corresponding plugin hook name). `command = [...]`
424
+ and a new entrypoint under `src/plugin/`. Keep each hook a single
425
+ short-lived process that reads `HERDR_PLUGIN_EVENT_JSON` (delivered as
426
+ `{ event: "<underscored_name>", data: {...} }` — confirmed live, see
427
+ `src/plugin/watcher.ts` and `DECISIONS.md`), does one write, and exits —
428
+ never a loop, never a standing process.
429
+
430
+ **Agent-to-agent coordination.** Explicitly deferred in `DESIGN.md`. If
431
+ ever wanted, it's a skill/instruction set given to dispatched agents (they
432
+ already have herdr's own socket API available to them), recorded via
433
+ `agents.spawned_by` — not a schema, CLI, or watcher change.
434
+
435
+ **Notifications, dashboards, delivery gates beyond the `direct-pr` /
436
+ `local-only` field.** All explicit non-goals for v1 — pure extensions per
437
+ above if genuinely needed, don't fold them into this repo.
438
+
439
+ ## Reference: schema at a glance
440
+
441
+ Full DDL lives in `src/db/migrations/0001_init.ts` (source of truth — this
442
+ is a summary, not a copy to keep in sync by hand):
443
+
444
+ - `projects` — one row per registered project (`local_clone_path`,
445
+ `default_branch`, `delivery_mode`, `herdr_workspace` — the project's
446
+ shared herdr workspace, NULL until its first dispatch).
447
+ - `roadmap` — hierarchical (`parent_id` self-reference), `status` enum
448
+ `planned|in_progress|blocked|done|dropped`, `priority` enum
449
+ `high|normal|low` (default `normal`) — triage order for the not-done
450
+ queue, not readiness.
451
+ - `agents` — one row per dispatch (`project_id`, `roadmap_item_id`,
452
+ `worktree_path`, `herdr_workspace`/`herdr_tab`/`herdr_pane`,
453
+ `coding_agent`, `authorization_basis` enum `user-explicit|pre-authorized`
454
+ (NULL for rows predating the C6 gate), `status` enum
455
+ `blocked|working|done|idle`, `outcome`, `spawned_by` self-reference).
456
+ - `events` — append-only, `agent_id` + `event_type` + free-form JSON
457
+ `payload`.
458
+ - `first_clerk` — single row (`id = 1`), current authority + `last_seen`
459
+ catch-up cursor.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # ledger
2
+
3
+ A personal agent-orchestration tool: talk to one coding-agent session (the
4
+ **clerk**), it dispatches work to other coding agents running in parallel,
5
+ each in its own isolated git worktree. Durable state — what's running,
6
+ what's blocked, what happened — lives in one local SQLite file, the
7
+ **ledger**. No daemon, no server process, nothing running when nothing is
8
+ happening.
9
+
10
+ Full philosophy and design rationale: [`DESIGN.md`](./DESIGN.md). This repo
11
+ is the implementation of it, built for one person's actual workflow, not as
12
+ a general product.
13
+
14
+ ## Prerequisites
15
+
16
+ - [Node.js](https://nodejs.org) ≥ 20
17
+ - [herdr](https://herdr.dev) installed and running — the terminal
18
+ workspace/pane manager. `herdr status` should show a running server.
19
+ - [treehouse](https://github.com/kunchenguid/treehouse) installed — isolated
20
+ git worktree pooling. No config file required; it auto-provisions a pool
21
+ per repo on first use.
22
+ - `git`
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ git clone <this-repo> ledger # or you already have it locally
28
+ cd ledger
29
+ npm install
30
+ npm run build
31
+ ```
32
+
33
+ **Make the CLI available.** Either:
34
+
35
+ ```sh
36
+ npm link # puts `ledger` on PATH globally
37
+ ```
38
+
39
+ or invoke it directly / alias it:
40
+
41
+ ```sh
42
+ node dist/cli/index.js ...
43
+ ```
44
+
45
+ **Link the watcher plugin into herdr** — this is what keeps agent status
46
+ current automatically as dispatched agents work, without any polling:
47
+
48
+ ```sh
49
+ herdr plugin link .
50
+ ```
51
+
52
+ This registers `herdr-plugin.toml`, so herdr invokes `dist/plugin/watcher.js`
53
+ whenever a pane's detected agent state changes. It's local and reversible:
54
+ `herdr plugin unlink ledger` removes it. Re-run `npm run build` after any
55
+ change to `src/plugin/watcher.ts` — herdr always invokes whatever's
56
+ currently in `dist/`.
57
+
58
+ ## Starting a clerk session
59
+
60
+ You don't run `ledger` commands yourself day to day — you talk to **the
61
+ clerk** (a Claude Code or Pi session), and it runs them on your behalf. A
62
+ `ledger` skill is installed at `~/.agents/skills/ledger` (symlinked into
63
+ both `~/.claude/skills/` and `~/.pi/agent/skills/`) so either tool can pick
64
+ it up — it loads only when you actually ask for ledger-related work
65
+ (register a project, dispatch an agent, check status, ...), not on every
66
+ unrelated session.
67
+
68
+ The skill itself carries no machine-specific path: it just tells the clerk
69
+ to run `ledger docs`, which prints `LEDGER.md` by resolving it relative to
70
+ wherever `ledger` is actually installed (works correctly through the
71
+ `npm link` symlink too — proven live, see `DECISIONS.md`). That's what
72
+ makes the skill portable to a fresh machine as-is: install `ledger` there
73
+ per this README, and the skill works with no edits.
74
+
75
+ ## Quick start (what the clerk actually runs)
76
+
77
+ ```sh
78
+ # Register a project (clones into ledger's own home dir — never your
79
+ # working checkout; see DESIGN.md for why).
80
+ ledger project add /path/to/repo-or-url --name myproj
81
+
82
+ # Break a feature into scoped briefs.
83
+ ledger roadmap add --project myproj --title "Do the thing"
84
+
85
+ # Dispatch an agent against one brief.
86
+ ledger agent dispatch --project myproj --task "Implement X" --roadmap-item 1
87
+
88
+ # Session start / "what's going on" — the entire catch-up operation.
89
+ ledger catchup
90
+ ```
91
+
92
+ Run `ledger --help`, or any subcommand with `--help`, for the full flag
93
+ reference.
94
+
95
+ ## Where state lives
96
+
97
+ One file: `$LEDGER_HOME/ledger.db` (default `~/.ledger`, override with the
98
+ `LEDGER_HOME` env var), plus project clones under
99
+ `$LEDGER_HOME/projects/<name>`. It's a plain SQLite file — inspectable with
100
+ any SQLite client at any time, by design. There is no daemon: every `ledger`
101
+ command opens the DB, does one thing, and exits. If herdr isn't running, no
102
+ agents can be running either, and nothing here breaks — the watcher plugin
103
+ simply never fires.
104
+
105
+ ## Documentation map
106
+
107
+ | Doc | What's in it |
108
+ |---|---|
109
+ | [`DESIGN.md`](./DESIGN.md) | The philosophy, why this exists, and the original schema/flow brief |
110
+ | [`LEDGER.md`](./LEDGER.md) | The operational reference — full CLI surface, what the first clerk is responsible for vs. what a dispatched agent is told, and how to extend this safely |
111
+ | [`DECISIONS.md`](./DECISIONS.md) | Running log of implementation decisions and why, including things verified live against the real herdr/treehouse binaries (some of herdr's actual behavior differs from its docs — see this file before assuming a documented API shape is accurate) |
112
+
113
+ ## Extending
114
+
115
+ Core is deliberately small: the schema (`src/db`), the CLI (`src/cli`), and
116
+ the one watcher plugin (`src/plugin`) that keeps `agents`/`events` in sync
117
+ with herdr. Almost everything else — notifications, alternate herdr event
118
+ hooks, roadmap-breakdown heuristics, dashboards — is meant to live **outside
119
+ this repo**, as a separate script or plugin that reads/writes the same
120
+ SQLite file or shells out to the same `ledger` CLI. See [`LEDGER.md` §
121
+ "Safe ways to extend this"](./LEDGER.md#safe-ways-to-extend-this) for the
122
+ concrete core-vs-extension test and worked examples, and that same doc for
123
+ how to add a migration, a CLI command, or a new watcher hook when something
124
+ genuinely does belong in core.
125
+
126
+ ## Example extension
127
+
128
+ [`ledger-notify`](../ledger-notify) *(sibling repo, once built)* — a
129
+ desktop-notification plugin that watches for agents going `blocked` or
130
+ `done`, built entirely outside this repo as a worked example of the
131
+ extension model. See [`EXTENSION-EXAMPLE-BRIEF.md`](./EXTENSION-EXAMPLE-BRIEF.md)
132
+ for the implementation brief.
133
+
134
+ ## Status
135
+
136
+ Personal tool, not a general product. `herdr` and `treehouse` are pre-1.0
137
+ with a high release cadence; some of the CLI/plugin behavior this repo
138
+ depends on was reverse-engineered live (their docs don't fully match
139
+ current behavior in places — see `DECISIONS.md`) and may need
140
+ re-verification after either tool upgrades.