ledger: count issue-tagged commits and repo seat messages (#1506)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user