Files
stack/packages/ledger/README.md
T

81 lines
4.7 KiB
Markdown

# Ledger
Read-only counts from local `refactor` commit subjects, one Gitea issue-list
request through `scripts/gitea-api.sh`, and repo seats' Pi session logs.
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 --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.
- The first text line alone classifies a message. A bracketed addressing
preamble whose source session is `control-board` is board; any other valid
addressing preamble is agent; otherwise human. 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`.
## 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
or session 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, and fake API tools, with no real
credentials or network. 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.