Skip to content

Add multi-issue and inclusive-range CLI inputs#9

Merged
github-actions[bot] merged 1 commit into
mainfrom
sunny/llm-cost-multi-issue
May 28, 2026
Merged

Add multi-issue and inclusive-range CLI inputs#9
github-actions[bot] merged 1 commit into
mainfrom
sunny/llm-cost-multi-issue

Conversation

@riddim-developer-bot
Copy link
Copy Markdown
Contributor

Summary

Two related additions to the OSS `llm-cost` CLI:

  1. Multiple positional issue IDs:
    ```bash
    llm-cost EPAC-1940 EPAC-1921 FAC-67
    ```

  2. 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

  • 66 of 66 tests pass via `node --test` (was 51; +15 new multi-issue tests covering expansion, dedup, range bounds, mixed inputs, missing-issue separation, label generation, and aggregate pricing)
  • `node --check` clean on every .mjs
  • Range expansion verified against real data: `llm-cost EPAC-1920-1925 --from-usage ` → 4 of 6 have data, totals $480.51
  • Mixed positionals verified: `llm-cost EPAC-1940 FAC-67 WEB-175 --from-usage ` → totals $100.11
  • Single-issue invocation produces byte-identical output to the previous version

What's NOT in this PR

Per the scoping conversation, this PR does only multi-issue + range. Not added:

  • `--team` flag (sugar for "all EPAC-* issues")
  • `--repo` flag (filter by workspacePath)
  • Linear project-level rollup (would need a tracker integration; out of scope for the OSS package)

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).
@github-actions github-actions Bot enabled auto-merge (squash) May 28, 2026 13:04
@github-actions github-actions Bot merged commit da398d3 into main May 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant