Darkwing's follow-up to the T3 thread source: manifest 382f5bb0 pins t3.mjs, ledger.test.mjs and README.md. Filbert approved it (review 6fd693b6). Ledger 51/51; the eight suites pass on the index. Co-Authored-By: Claude Opus 5.5 <[email protected]>
158 lines
9.6 KiB
Markdown
158 lines
9.6 KiB
Markdown
# Ledger
|
|
|
|
Read-only counts from local `refactor` commit subjects, one Gitea issue-list
|
|
request through `scripts/gitea-api.sh`, repo seats' Pi session logs, and T3's
|
|
thread messages in `~/.t3/userdata/state.sqlite`.
|
|
No board changes, data-root writes, fleet reads, transcript output, or scheduler.
|
|
|
|
```sh
|
|
node packages/ledger/src/cli.mjs --since 2026-09-06 --until 2026-09-12
|
|
node packages/ledger/src/cli.mjs --since 2026-09-06 --until 2026-09-12 --json
|
|
node packages/ledger/src/cli.mjs --since 2026-09-06 --no-issues
|
|
node packages/ledger/src/cli.mjs --since 2026-09-06 --no-t3
|
|
node packages/ledger/src/cli.mjs --since 2026-09-06 --t3-db /tmp/fixture.sqlite
|
|
node --test packages/ledger/tests/
|
|
```
|
|
|
|
Dates include both endpoints in UTC. Omitted `--until` means today in UTC.
|
|
The checkout containing this package supplies the sources, not the shell's cwd.
|
|
No install, build, service restart, or configuration change is needed.
|
|
|
|
## Counting rules
|
|
|
|
- Git uses committer timestamps and every commit reachable from local `refactor`,
|
|
including merges. It reads subjects only. A repeated `#N` in one subject counts
|
|
once. A commit naming two issues counts for both rows but once in total commits.
|
|
Untagged commits do not count. Issue numbers are literal references to
|
|
`mosaicstack/stack`, with no attempt to remap archived repositories' numbers.
|
|
This can associate historical references with unrelated same-number issues.
|
|
- Table 1 includes issues with a tagged commit or a `closed_at` in range.
|
|
Opened is `created_at`; hours open is `closed_at - created_at`, rounded to one
|
|
decimal, or `open` if not closed. It is not age as of `--until`. Reopen history
|
|
is unavailable from the issue-list response. Median hours open uses only
|
|
issues closed in range and rounds after computing the median.
|
|
- Follow-ups are `max(commits in range - 1, 0)` per issue, not a lifetime count
|
|
and not a quality assessment. Follow-ups per issue divides their sum by all
|
|
Table 1 rows, including close-only rows.
|
|
- Table 2 counts user-message entries in `.pi/state/<seat>/sessions/*.jsonl`
|
|
where `<seat>` is a real directory in `agents/`. All matching files count;
|
|
duplicated entries in copied logs are not deduplicated. No transcript content
|
|
leaves the parser. Assistant messages and logs outside repo seats do not count.
|
|
Symlink source directories are refused and symlink files are not followed.
|
|
A line ends at `\n` only. A U+2028 or U+2029 inside a JSON string does not
|
|
split a record.
|
|
- Table 2 also counts T3 thread messages with role `user`. The T3 source
|
|
follows. A seat's row sums its Pi and T3 counts; the JSON keeps the split in
|
|
`pi` (Pi rows) and `t3.seats` (T3 rows).
|
|
- The first text line alone classifies a message. Two addressing forms count:
|
|
the tmux preamble `[host:session -> host:session]` that `agent-send.sh`
|
|
writes, and the T3 header `[from: role (thread-id) -> to: role (thread-id)]`
|
|
from `docs/guides/T3-AGENT-COMMS.md`. Either may carry ` class=<class>` before
|
|
the closing bracket. A preamble whose sender is `control-board` (tmux session
|
|
or T3 role) is board; any other valid preamble is agent; otherwise human.
|
|
The class may be in either case: seats send `class=DECISION`.
|
|
This is a format count, not proof of who typed the message. Text blocks are
|
|
joined with newlines. The entry timestamp is used, falling back to the
|
|
message timestamp.
|
|
- Seats with no in-range user messages are omitted. Issue seats come from `#N`
|
|
mentions anywhere in in-range user text, including quoted text.
|
|
- Human messages per closed issue divides Table 2's human sum by issues closed
|
|
in range. A zero denominator with human messages is `unknown`; a truly empty
|
|
report has zero totals. JSON keeps numeric values as numbers; text displays
|
|
ratios and durations with one decimal. Titles truncate to 48 characters in
|
|
text only. Missing evidence is the literal string `unknown`.
|
|
|
|
## T3 source
|
|
|
|
The rules come from `docs/plans/2026-09-26_ledger-t3-source.md` (Gate F).
|
|
The source is on by default. `--no-t3` skips it, and the report then says
|
|
`T3: not read (--no-t3)`. `--t3-db <path>` reads another database file with
|
|
the same checks. The JSON records the database path and whether it was the
|
|
default. When it wasn't, the text report prints the path, so a fixture result
|
|
can't pass for a live one. The two flags can't be combined.
|
|
|
|
The reader opens `state.sqlite` read-only through `node:sqlite` and reads no
|
|
other file in `~/.t3`. It runs every query in one read transaction with a 5 s
|
|
busy timeout. It never writes the main database file. Like any SQLite
|
|
connection it may create `-wal` and `-shm` beside it, so a directory that
|
|
isn't writable refuses when SQLite needs them.
|
|
|
|
- **Project.** Only threads in the one non-deleted T3 project whose
|
|
`workspace_root` equals this checkout's root byte for byte. The root is the
|
|
realpath of the package, so a project opened through the compatibility
|
|
symlink `~/src/mosaic-stack-dev-test` does not match, and the report refuses
|
|
with no project.
|
|
- **Thread to seat.** A thread belongs to seat `<s>` when `<s>` is a real
|
|
directory in `agents/` and the lower-cased title equals `<s>` or starts
|
|
with `<s>` and a space. "Dewey in Claude" maps to `dewey`; "Sagebrush" maps
|
|
to nothing. Several threads can map to one seat. Threads that map to no
|
|
seat share one row, `t3:unmapped`, so their human messages still reach the
|
|
totals. `t3.seats` and `t3.unmapped` in the JSON list the thread ids and
|
|
titles behind each row.
|
|
- **Titles are current state.** T3 titles an unnamed thread from its first
|
|
prompt, and a rename moves a thread's whole history to another row. This
|
|
moves counts between rows, never out of the totals.
|
|
- **Header check.** A user message whose T3 header is addressed to its own
|
|
thread id must name that thread's seat as the `to:` role (compared lower
|
|
case). In an unmapped thread the `to:` role must not be a seat. A conflict
|
|
exits 1 and names the thread, its title and both roles. A header addressed
|
|
to another thread isn't checked. The check misses a renamed thread that no
|
|
agent writes to. Such a thread can only add human counts to a row.
|
|
- **Excluded.** Imported threads (id prefix `import:`) are partial copies of
|
|
Claude Code sessions, not T3 traffic; every T3 event marked `historyImport`
|
|
sits in one today. Deleted threads don't count; archived threads do.
|
|
`t3.excluded` gives both thread counts.
|
|
- **Blind spot.** Threads in other T3 projects are not counted, even if they
|
|
worked on this repository. Live, there is a project at `/home/jwoltje` and
|
|
a deleted one at `/mnt/storage/src`.
|
|
- **Diagnostic.** `t3.diagnostic.humanSentThroughApi` counts in-range user
|
|
messages the header rule calls human that T3 recorded as sent through its
|
|
API (no `appVersion` in the event's origin). Those are seat messages whose
|
|
header the rule doesn't accept, such as the older free-text Discord Bot
|
|
headers, and would show the next format drift. `humanWithoutEvent` counts
|
|
human messages with no `thread.message-sent` event. This is T3's internal
|
|
metadata, so it feeds no table or total. If `orchestration_events` or a
|
|
column it needs is missing, or an event doesn't parse, both read `unknown`.
|
|
|
|
These refuse the report with exit 1, and the ones about the database name
|
|
`--no-t3`: a missing, unreadable or unopenable database (including a busy
|
|
lock past the timeout); a symlink at `~/.t3`, `~/.t3/userdata` or
|
|
`state.sqlite` (with `--t3-db`, the file or its directory); a missing table or
|
|
column the counts need; no project or more than one for this root; a message
|
|
in a counted thread with a role other than `user` or `assistant`, non-text
|
|
content, or a `created_at` that doesn't parse; a header conflict. A missing Pi
|
|
directory means no Pi seats ran here; a missing T3 database means the path or
|
|
T3 changed, so it refuses instead of counting zero. Error messages name ids
|
|
and paths, never message text.
|
|
|
|
## One Gitea call and missing evidence
|
|
|
|
The client requests issues updated since the start date, all states, first page,
|
|
limit 50. This includes issues closed in range, even if later updated. Gitea caps
|
|
responses at 50; a full page fails rather than silently reporting partial totals.
|
|
Use a narrower range or `--no-issues`, not hidden pagination. A commit-linked
|
|
issue not returned by the updated-since query still has a row, with unknown
|
|
metadata. This is the cost of the brief's one-call boundary.
|
|
|
|
Exit 0 means a report was computed. Exit 1 means bad arguments or unreadable git,
|
|
session or T3 evidence. Malformed JSONL, including a partially written last line,
|
|
refuses the report; rerun after the seat finishes writing. Exit 2 means issue
|
|
credentials, API, payload, or completeness failure. The CLI never prints API
|
|
error bodies or reads authentication files itself. `--no-issues` makes no API
|
|
call, keeps commit-derived rows, and shows unknown issue metadata, closed counts,
|
|
median duration, and human-per-closed ratio. It cannot invent close-only rows.
|
|
|
|
For fixtures, a fake `gitea-api.sh` can be placed first on PATH. Otherwise the
|
|
repository scripts directory is appended to PATH for the issue request.
|
|
Tests use only temporary repositories, logs, T3 databases and fake API tools,
|
|
with no real credentials or network. Every CLI run in the tests sets `HOME` to
|
|
a temporary directory, so no test opens the real `~/.t3`. The helper regression stubs Node before any credential
|
|
read and checks successful GET, successful POST, and failed HTTP status.
|
|
|
|
## Acceptance
|
|
|
|
Gate D is Jason's: run the requested week, choose a number to move next week,
|
|
and write the sentence and number into `docs/plans/CURRENT.md`. Automated tests
|
|
and publication do not pass that gate. Remove the package to stop using it;
|
|
there is no persistent ledger state to migrate or restore.
|