@warnyin/sdlc 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/lib/usage.mjs +49 -46
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 (2026-08-25)
|
|
4
|
+
|
|
5
|
+
- **Fix (cost)**: `costUsd()` never charged cache-write tokens, the highest-rate of
|
|
6
|
+
the four classes the usage parser collects. Every cost `/sdlc:observe` and the
|
|
7
|
+
session summary have printed was therefore low. A model priced without a
|
|
8
|
+
`cacheWrite` rate now charges nothing for that class rather than inferring one from
|
|
9
|
+
`input` — the module's rule is never to guess a price, and a guess reports as
|
|
10
|
+
confidently as a known rate. Existing journalled costs are left alone: backfilling
|
|
11
|
+
would rewrite history from a rate that was not in force at the time.
|
|
12
|
+
|
|
3
13
|
## 0.5.0 (2026-08-25)
|
|
4
14
|
|
|
5
15
|
- **`--auto` on every pipeline stage.** `/sdlc:auto` already ran the whole pipeline,
|
package/lib/usage.mjs
CHANGED
|
@@ -1,46 +1,49 @@
|
|
|
1
|
-
// Transcript-usage parser: Claude Code transcripts are JSONL; assistant
|
|
2
|
-
// entries carry `message.usage` and `message.model`. We sum per model and
|
|
3
|
-
// price via the optional table in sdlc/config.yaml. Never guess: when a
|
|
4
|
-
// price is unknown, cost stays null (reported as n/a).
|
|
5
|
-
|
|
6
|
-
export function parseTranscriptUsage(jsonlText) {
|
|
7
|
-
const byModel = new Map();
|
|
8
|
-
for (const line of (jsonlText ?? '').split('\n')) {
|
|
9
|
-
if (!line.trim()) continue;
|
|
10
|
-
let entry;
|
|
11
|
-
try { entry = JSON.parse(line); } catch { continue; }
|
|
12
|
-
const msg = entry?.message;
|
|
13
|
-
const usage = msg?.usage;
|
|
14
|
-
if (!usage || typeof usage !== 'object') continue;
|
|
15
|
-
const model = msg.model ?? 'unknown';
|
|
16
|
-
const acc = byModel.get(model) ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
17
|
-
acc.input += usage.input_tokens ?? 0;
|
|
18
|
-
acc.output += usage.output_tokens ?? 0;
|
|
19
|
-
acc.cacheRead += usage.cache_read_input_tokens ?? 0;
|
|
20
|
-
acc.cacheWrite += usage.cache_creation_input_tokens ?? 0;
|
|
21
|
-
byModel.set(model, acc);
|
|
22
|
-
}
|
|
23
|
-
const models = Object.fromEntries(byModel);
|
|
24
|
-
const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
25
|
-
for (const m of byModel.values()) {
|
|
26
|
-
totals.input += m.input; totals.output += m.output;
|
|
27
|
-
totals.cacheRead += m.cacheRead; totals.cacheWrite += m.cacheWrite;
|
|
28
|
-
}
|
|
29
|
-
return { models, totals };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// prices: { "<model>": { input, output, cacheRead } } in USD per 1M
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
// Transcript-usage parser: Claude Code transcripts are JSONL; assistant
|
|
2
|
+
// entries carry `message.usage` and `message.model`. We sum per model and
|
|
3
|
+
// price via the optional table in sdlc/config.yaml. Never guess: when a
|
|
4
|
+
// price is unknown, cost stays null (reported as n/a).
|
|
5
|
+
|
|
6
|
+
export function parseTranscriptUsage(jsonlText) {
|
|
7
|
+
const byModel = new Map();
|
|
8
|
+
for (const line of (jsonlText ?? '').split('\n')) {
|
|
9
|
+
if (!line.trim()) continue;
|
|
10
|
+
let entry;
|
|
11
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
12
|
+
const msg = entry?.message;
|
|
13
|
+
const usage = msg?.usage;
|
|
14
|
+
if (!usage || typeof usage !== 'object') continue;
|
|
15
|
+
const model = msg.model ?? 'unknown';
|
|
16
|
+
const acc = byModel.get(model) ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
17
|
+
acc.input += usage.input_tokens ?? 0;
|
|
18
|
+
acc.output += usage.output_tokens ?? 0;
|
|
19
|
+
acc.cacheRead += usage.cache_read_input_tokens ?? 0;
|
|
20
|
+
acc.cacheWrite += usage.cache_creation_input_tokens ?? 0;
|
|
21
|
+
byModel.set(model, acc);
|
|
22
|
+
}
|
|
23
|
+
const models = Object.fromEntries(byModel);
|
|
24
|
+
const totals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
25
|
+
for (const m of byModel.values()) {
|
|
26
|
+
totals.input += m.input; totals.output += m.output;
|
|
27
|
+
totals.cacheRead += m.cacheRead; totals.cacheWrite += m.cacheWrite;
|
|
28
|
+
}
|
|
29
|
+
return { models, totals };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// prices: { "<model>": { input, output, cacheRead, cacheWrite } } in USD per 1M
|
|
33
|
+
// tokens. A class the table omits is charged nothing — never inferred from another
|
|
34
|
+
// rate, since a guessed price reports as confidently as a known one.
|
|
35
|
+
export function costUsd(usage, prices) {
|
|
36
|
+
if (!prices) return null;
|
|
37
|
+
let usd = 0;
|
|
38
|
+
let priced = false;
|
|
39
|
+
for (const [model, u] of Object.entries(usage.models)) {
|
|
40
|
+
const p = prices[model];
|
|
41
|
+
if (!p) continue;
|
|
42
|
+
priced = true;
|
|
43
|
+
usd += (u.input * (p.input ?? 0)
|
|
44
|
+
+ u.output * (p.output ?? 0)
|
|
45
|
+
+ u.cacheRead * (p.cacheRead ?? 0)
|
|
46
|
+
+ u.cacheWrite * (p.cacheWrite ?? 0)) / 1_000_000;
|
|
47
|
+
}
|
|
48
|
+
return priced ? Number(usd.toFixed(4)) : null;
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warnyin/sdlc",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Spec-driven, AI-driven SDLC framework — token-lean specs, contract-first changes, autonomous pipeline with managed hooks. Operationalizes the Day-1 'New SDLC with Vibe Coding' work process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|