Add multi-issue and inclusive-range CLI inputs#9
Merged
Conversation
Two related additions:
1. Multiple positional issue IDs:
llm-cost EPAC-1940 EPAC-1921 FAC-67
When more than one ID is requested, the output is a summary table
with one row per issue plus an aggregate TOTAL row.
2. Inclusive ranges as a single positional:
llm-cost EPAC-1990-1999 # expands to EPAC-1990..EPAC-1999
llm-cost EPAC-1990-1999 FAC-60-70 # ranges + singletons mixed
Range syntax is unambiguous because Linear issue keys are strictly
`<TEAM>-<NUMBER>` (no other hyphens), so the second hyphen can only
mean range. The parser caps a single range at 10,000 expanded issues.
Multi-issue summary table:
COST ROLLUP — EPAC-1920-1925 (source: usage.jsonl)
6 issues requested, 4 had data
Issue Sessions Turns Tokens API cost
─────────────────────────────────────────────────────────
EPAC-1920 1 57 9.5M $6.79
EPAC-1921 1 3,048 476.0M $292.37
EPAC-1922 1 159 23.9M $19.20
EPAC-1923 1 1,718 266.7M $162.14
─────────────────────────────────────────────────────────
TOTAL 4 4,982 776.1M $480.51
(skipped: EPAC-1924, EPAC-1925 — no sessions)
Single-issue behavior is unchanged.
New library exports:
- expandIssueArg(arg) range/single → string[]
- expandAllIssueArgs(args) deduped, order-preserving, with count
- computeMultiIssueRollup(positionals, loader)
pluggable loader so caller picks the
data source (transcripts vs usage.jsonl)
Drive-by fix in attachPricingToRollup: when totals.models contains an
unknown marker like `<synthetic>` (Claude session-restart placeholder
that sorts alphabetically before real model names), the prior code
took models[0] and got null pricing. Now iterates until it finds a
model the rate table knows. FAC-67 now correctly prices instead of
showing — for API cost.
66 of 66 tests pass (was 51; +15 new multi-issue tests).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two related additions to the OSS `llm-cost` CLI:
Multiple positional issue IDs:
```bash
llm-cost EPAC-1940 EPAC-1921 FAC-67
```
Inclusive ranges as a single positional:
```bash
llm-cost EPAC-1990-1999 # 10 issues, EPAC-1990..EPAC-1999
llm-cost EPAC-1990-1999 FAC-60-70 EPAC-1940 # mix ranges + singletons
```
The range syntax is unambiguous because Linear issue keys are strictly `-` (no other hyphens are permitted), so the second hyphen can only mean range. Single ranges are capped at 10,000 expanded IDs.
Output
When more than one ID is requested, the output is a per-issue summary table plus a TOTAL row. From real data on this machine:
```
COST ROLLUP — EPAC-1920-1925 (source: usage.jsonl)
6 issues requested, 4 had data
Issue Sessions Turns Tokens API cost
─────────────────────────────────────────────────────────
EPAC-1920 1 57 9.5M $6.79
EPAC-1921 1 3,048 476.0M $292.37
EPAC-1922 1 159 23.9M $19.20
EPAC-1923 1 1,718 266.7M $162.14
─────────────────────────────────────────────────────────
TOTAL 4 4,982 776.1M $480.51
(skipped: EPAC-1924, EPAC-1925 — no sessions)
```
Single-issue behavior (the full per-bucket / quota / pricing block) is unchanged.
New library API
```js
import {
computeMultiIssueRollup,
expandIssueArg,
expandAllIssueArgs,
} from 'llm-cost-attribution';
expandIssueArg('EPAC-1990-1999')
// → ['EPAC-1990', 'EPAC-1991', ..., 'EPAC-1999']
const { ids, requestedCount } = expandAllIssueArgs(['EPAC-1990-1999', 'EPAC-1995']);
// ids.length === 10, requestedCount === 11 (range had 10 + 1 singleton, dedup)
const multi = await computeMultiIssueRollup(['EPAC-1990-1999'], loadOne);
// → { label, positionals, requestedIds, requestedCount, issues, missing, totals }
```
The loader is pluggable so the same function works against raw transcripts OR a usage.jsonl source — the CLI passes whichever loader matches `--from-usage`.
Drive-by fix
`attachPricingToRollup` previously took `totals.models[0]` (alphabetical sort) for the rate lookup, which is wrong when Claude includes `` markers (session-restart placeholders that sort before real model names). Now iterates the model list until it finds a name the rate table knows. FAC-67 now correctly prices at $9.15 instead of showing `—`.
Test plan
What's NOT in this PR
Per the scoping conversation, this PR does only multi-issue + range. Not added: