Compare commits

..

1 Commits

Author SHA1 Message Date
Robert Egoian 46e2d58795 fix(cost-tracker): prefer harness cost.total_cost_usd when statusline writes it
The transcript-sum estimate diverges from the harness's API-billed cost on
long sessions and after --resume:
- the hard-coded rate table cannot represent Opus 4.7's >200K-token 2x tier
  or the 1h-cache 2x tier (under-counts);
- summing the full transcript double-counts work across --resume boundaries
  while cost.total_cost_usd is per-process.

Both made the bridge file overshoot real cost by 4-5x, which tripped
ecc-context-monitor's $50 "critical" threshold and made agents halt
prematurely (observed: $73.99 hook value at $15.61 real harness cost).

This change adds an opt-in contract: if a statusline writes
{ts, cost_usd} to <tmpdir>/harness-cost-<session_id>.json on each render,
cost-tracker prefers that authoritative value when fresh (<=300s).
Behaviour is strictly additive — absent a writer, the transcript-sum path
is unchanged.

Tests cover both the prefer-fresh-cache and ignore-stale-cache paths.
2026-05-25 20:29:56 +03:00
19 changed files with 155 additions and 1929 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
**Language:** English | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md) | [Türkçe](docs/tr/README.md) | [Русский](docs/ru/README.md) | [Tiếng Việt](docs/vi-VN/README.md) | [ไทย](docs/th/README.md) | [Deutsch](docs/de-DE/README.md)
**Language:** English | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md) | [Türkçe](docs/tr/README.md) | [Русский](docs/ru/README.md) | [Tiếng Việt](docs/vi-VN/README.md) | [ไทย](docs/th/README.md)
# ECC
@@ -28,7 +28,7 @@
**Language / 语言 / 語言 / Dil / Язык / Ngôn ngữ**
[**English**](README.md) | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md)
| [Türkçe](docs/tr/README.md) | [Русский](docs/ru/README.md) | [Tiếng Việt](docs/vi-VN/README.md) | [ไทย](docs/th/README.md) | [Deutsch](docs/de-DE/README.md)
| [Türkçe](docs/tr/README.md) | [Русский](docs/ru/README.md) | [Tiếng Việt](docs/vi-VN/README.md) | [ไทย](docs/th/README.md)
</div>
+1 -1
View File
@@ -23,7 +23,7 @@
**Language / 语言 / 語言 / Dil / Язык / Ngôn ngữ**
[**English**](README.md) | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md) | [Türkçe](docs/tr/README.md) | [Русский](docs/ru/README.md) | [Tiếng Việt](docs/vi-VN/README.md) | [ไทย](docs/th/README.md) | [Deutsch](docs/de-DE/README.md)
[**English**](README.md) | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.md) | [한국어](docs/ko-KR/README.md) | [Türkçe](docs/tr/README.md) | [Русский](docs/ru/README.md) | [Tiếng Việt](docs/vi-VN/README.md) | [ไทย](docs/th/README.md)
</div>
-67
View File
@@ -1,67 +0,0 @@
# Glossar / Glossary
Einheitliches Terminologie-Glossar für die deutsche (de-DE) Übersetzung von ECC.
Leitlinie: Etablierte englische Fachbegriffe und ECC-Oberflächennamen (`agents/`, `skills/`,
`commands/`, `hooks/`, `rules/`) bleiben **englisch** — sie sind im deutschsprachigen
Entwickleralltag Standard und entsprechen Verzeichnis-/Befehlsnamen im Repo. Begriffe mit
einer klaren, gebräuchlichen deutschen Entsprechung werden **übersetzt**.
| English | Deutsch | Notiz |
|---------|---------|-------|
| Agent | Agent | bleibt englisch — ECC-Oberfläche (`agents/`) |
| Skill | Skill | bleibt englisch — ECC-Oberfläche (`skills/`) |
| Hook | Hook | bleibt englisch — ECC-Oberfläche (`hooks/`) |
| Command | Command | bleibt englisch als ECC-Oberfläche (`commands/`); generisch sonst „Befehl“ |
| Rule | Rule | bleibt englisch als ECC-Oberfläche (`rules/`); generisch sonst „Regel“ |
| Harness | Harness | bleibt englisch — keine etablierte deutsche Entsprechung |
| Instinct | Instinct | bleibt englisch — ECC-Begriff aus Continuous Learning |
| Plugin | Plugin | bleibt englisch |
| Marketplace | Marketplace | bleibt englisch — Anthropic-Produktbegriff |
| Worktree | Worktree | bleibt englisch — Git-Fachbegriff |
| Subagent | Subagent | bleibt englisch |
| Frontmatter | Frontmatter | bleibt englisch; YAML-Feldnamen bleiben englisch |
| Continuous Learning | Continuous Learning | ECC-Feature-Name bleibt englisch; beschreibend „kontinuierliches Lernen“ |
| Memory | Memory | als ECC-Konzept englisch; generisch „Speicher“ |
| Context window | Kontextfenster | |
| Token | Token | |
| Coverage | Coverage | „Testabdeckung“, wo beschreibend |
| Test-Driven Development | testgetriebene Entwicklung | Kürzel TDD beibehalten |
| Code review | Code-Review | |
| Refactoring | Refactoring | |
| Pull request | Pull Request | |
| Commit | Commit | |
| Branch | Branch | |
| Merge | Merge / zusammenführen | je nach Kontext |
| Build | Build | |
| Deploy | Deployment / deployen | |
| Pipeline | Pipeline | |
| Orchestration | Orchestrierung | |
| Repository | Repository | kurz „Repo“ zulässig |
| Dependency | Abhängigkeit | |
| Edge case | Grenzfall | |
| Best practice | Best Practice | |
| Anti-pattern | Anti-Pattern | |
| Middleware | Middleware | |
| Endpoint | Endpoint | |
| Schema | Schema | |
| Payload | Payload | |
| Callback | Callback | |
| Checkpoint | Checkpoint | |
| Linter | Linter | |
| Formatter | Formatter | |
| Staging | Staging | |
| Production | Produktion / Produktivumgebung | je nach Kontext |
| Debugging | Debugging | |
| Logging | Logging | |
| Monitoring | Monitoring | |
| Rate limit | Rate-Limit | |
| Retry | Retry / Wiederholung | |
| Fallback | Fallback | |
| Graceful degradation | Graceful Degradation | |
| Sandboxing | Sandboxing | |
| Sanitization | Sanitisierung | |
| Selective install | selektive Installation | |
| Profile | Profil | Installationsprofil |
| Component | Komponente | Installationskomponente |
| Module | Modul | Installationsmodul |
-1762
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,4 +1,4 @@
**言語:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
**言語:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
# Everything Claude Code
@@ -21,7 +21,7 @@
**言語 / Language / 語言 / Dil / Язык / Ngôn ngữ**
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
</div>
+2 -2
View File
@@ -1,4 +1,4 @@
**언어:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | 한국어 | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
**언어:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | 한국어 | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
# Everything Claude Code
@@ -24,7 +24,7 @@
**Language / 语言 / 語言 / 언어 / Dil / Язык / Ngôn ngữ**
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
</div>
+2 -2
View File
@@ -1,4 +1,4 @@
**Idioma:** [English](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | Português (Brasil) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
**Idioma:** [English](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | Português (Brasil) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
# Everything Claude Code
@@ -24,7 +24,7 @@
**Idioma / Language / 语言 / Dil / Язык / Ngôn ngữ**
[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Português (Brasil)](README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
[**English**](../../README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Português (Brasil)](README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
</div>
+2 -2
View File
@@ -1,4 +1,4 @@
**Язык:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | **Русский** | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
**Язык:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | **Русский** | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
# Everything Claude Code
@@ -27,7 +27,7 @@
**Язык / 语言 / 語言 / Dil / Ngôn ngữ**
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | **Русский** | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | **Русский** | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
</div>
+2 -2
View File
@@ -1,4 +1,4 @@
**ภาษา:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | **ไทย** | [Deutsch](../de-DE/README.md)
**ภาษา:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | **ไทย**
# Everything Claude Code
@@ -18,7 +18,7 @@
**ภาษา / Language / 语言 / 語言 / Dil / Язык / Ngôn ngữ**
[English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | **ไทย** | [Deutsch](../de-DE/README.md)
[English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | **ไทย**
</div>
+1 -1
View File
@@ -23,7 +23,7 @@
**Dil / Language / 语言 / 語言 / Язык / Ngôn ngữ**
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [**Türkçe**](README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [**Türkçe**](README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
</div>
+2 -2
View File
@@ -1,4 +1,4 @@
**Ngôn ngữ:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | **Tiếng Việt** | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
**Ngôn ngữ:** [English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | **Tiếng Việt** | [ไทย](../th/README.md)
# Everything Claude Code
@@ -18,7 +18,7 @@
**Ngôn ngữ / Language / 语言 / 語言 / Dil / Язык**
[English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | **Tiếng Việt** | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
[English](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | [繁體中文](../zh-TW/README.md) | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | **Tiếng Việt** | [ไทย](../th/README.md)
</div>
+1 -1
View File
@@ -13,7 +13,7 @@
**Language / 语言 / 語言 / Dil / Язык / Ngôn ngữ**
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | **繁體中文** | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md) | [Deutsch](../de-DE/README.md)
[**English**](../../README.md) | [Português (Brasil)](../pt-BR/README.md) | [简体中文](../../README.zh-CN.md) | **繁體中文** | [日本語](../ja-JP/README.md) | [한국어](../ko-KR/README.md) | [Türkçe](../tr/README.md) | [Русский](../ru/README.md) | [Tiếng Việt](../vi-VN/README.md) | [ไทย](../th/README.md)
</div>
-8
View File
@@ -589,14 +589,6 @@
"modules": [
"docs-zh-tw"
]
},
{
"id": "locale:de-de",
"family": "locale",
"description": "German (de-DE) translated reference docs installed to ~/.claude/docs/de-DE/.",
"modules": [
"docs-de-de"
]
}
]
}
-16
View File
@@ -843,22 +843,6 @@
"defaultInstall": false,
"cost": "heavy",
"stability": "stable"
},
{
"id": "docs-de-de",
"kind": "docs",
"description": "German (de-DE) translated reference docs for agents, commands, skills, and rules.",
"paths": [
"docs/de-DE"
],
"targets": [
"claude",
"claude-project"
],
"dependencies": [],
"defaultInstall": false,
"cost": "heavy",
"stability": "stable"
}
]
}
-1
View File
@@ -56,7 +56,6 @@
"agent.yaml",
"agents/",
"commands/",
"docs/de-DE/",
"docs/ja-JP/",
"docs/ko-KR/",
"docs/pt-BR/",
+50 -1
View File
@@ -20,15 +20,54 @@
* Each row therefore represents the cumulative session total up to that point.
* To get per-session cost, take the last row per session_id. To get per-day
* spend, aggregate.
*
* Harness-cost contract (optional, opt-in by the statusline):
* If the user's statusline (which receives `cost.total_cost_usd` directly
* from Claude Code) writes `{ts, cost_usd}` to
* `<os.tmpdir()>/harness-cost-<session_id>.json` on each render, this hook
* prefers that authoritative value over the transcript-sum estimate when
* the cache is fresh ( 300s). The transcript-sum is kept as a safe
* fallback because:
* - the hard-coded rate table cannot represent Opus 4.7's >200K-token
* 2x tier or the 1h-cache 2x tier (under-counts on long sessions);
* - summing the full transcript double-counts work done across
* `--resume` boundaries while `cost.total_cost_usd` is per-process.
* Absent a writer, behavior is unchanged.
*/
'use strict';
const fs = require('fs');
const os = require('os');
const path = require('path');
const { ensureDir, appendFile, getClaudeDir } = require('../lib/utils');
const { sanitizeSessionId } = require('../lib/session-bridge');
const HARNESS_COST_MAX_AGE_SECONDS = 300;
/**
* Read authoritative harness cost from the per-session cache file.
* @param {string} sessionId
* @param {number} maxAgeSeconds
* @returns {number|null} cost in USD, or null on miss / stale / parse error
*/
function readHarnessCost(sessionId, maxAgeSeconds) {
if (!sessionId) return null;
try {
const fp = path.join(os.tmpdir(), `harness-cost-${sessionId}.json`);
if (!fs.existsSync(fp)) return null;
const obj = JSON.parse(fs.readFileSync(fp, 'utf8'));
const ts = Number(obj && obj.ts);
const cost = Number(obj && obj.cost_usd);
if (!Number.isFinite(ts) || !Number.isFinite(cost) || cost < 0) return null;
const age = Math.floor(Date.now() / 1000) - ts;
if (age < 0 || age > maxAgeSeconds) return null;
return cost;
} catch {
return null;
}
}
// Approximate per-1M-token billing rates (USD).
// Cache creation: 1.25x input rate. Cache read: 0.1x input rate.
const RATE_TABLE = {
@@ -125,13 +164,23 @@ process.stdin.on('end', () => {
} = usageTotals || {};
const rates = getRates(model);
const estimatedCostUsd = Math.round((
const transcriptCostUsd = Math.round((
(inputTokens / 1e6) * rates.in +
(outputTokens / 1e6) * rates.out +
(cacheWriteTokens / 1e6) * rates.cacheWrite +
(cacheReadTokens / 1e6) * rates.cacheRead
) * 1e6) / 1e6;
// Prefer the harness's authoritative `cost.total_cost_usd` when the
// statusline has written it to the per-session cache (see contract in
// the file header). The harness number reflects API-billed truth
// (correct rates, 1h-cache 2x, >200K tier 2x) and is per-process so it
// does not drift across `--resume`. Cache miss → transcript-sum.
const harnessCost = readHarnessCost(sessionId, HARNESS_COST_MAX_AGE_SECONDS);
const estimatedCostUsd = harnessCost !== null
? Math.round(harnessCost * 1e6) / 1e6
: transcriptCostUsd;
const metricsDir = path.join(getClaudeDir(), 'metrics');
ensureDir(metricsDir);
+1 -3
View File
@@ -14,7 +14,7 @@ const COMPONENT_FAMILY_PREFIXES = {
skill: 'skill:',
locale: 'locale:',
};
const SUPPORTED_LOCALES = Object.freeze(['ja', 'zh-CN', 'ko-KR', 'pt-BR', 'ru', 'tr', 'vi-VN', 'zh-TW', 'de-DE']);
const SUPPORTED_LOCALES = Object.freeze(['ja', 'zh-CN', 'ko-KR', 'pt-BR', 'ru', 'tr', 'vi-VN', 'zh-TW']);
const LOCALE_ALIAS_TO_COMPONENT_ID = Object.freeze({
'ja': 'locale:ja',
'ja-JP': 'locale:ja',
@@ -29,8 +29,6 @@ const LOCALE_ALIAS_TO_COMPONENT_ID = Object.freeze({
'vi-VN': 'locale:vi-vn',
'vi': 'locale:vi-vn',
'zh-TW': 'locale:zh-tw',
'de-DE': 'locale:de-de',
'de': 'locale:de-de',
});
function listSupportedLocales() {
+87
View File
@@ -215,6 +215,93 @@ function runTests() {
fs.rmSync(tmpHome, { recursive: true, force: true });
}) ? passed++ : failed++);
// 8. Prefers harness-cost cache value over transcript-sum when fresh
(test('prefers fresh harness-cost cache over transcript estimate', () => {
const tmpHome = makeTempDir();
const sessionId = 'harness-fresh-' + Date.now();
const transcriptPath = path.join(tmpHome, 'session.jsonl');
writeTranscript(transcriptPath, [
{
type: 'assistant',
message: {
model: 'claude-opus-4-20250514',
usage: {
input_tokens: 10000,
output_tokens: 5000,
cache_creation_input_tokens: 200000,
cache_read_input_tokens: 1000000,
},
},
},
]);
const harnessCachePath = path.join(os.tmpdir(), `harness-cost-${sessionId}.json`);
const nowEpoch = Math.floor(Date.now() / 1000);
fs.writeFileSync(
harnessCachePath,
JSON.stringify({ ts: nowEpoch, cost_usd: 1.23 }),
'utf8'
);
try {
const result = runScript(
{ session_id: sessionId, transcript_path: transcriptPath },
withTempHome(tmpHome)
);
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl');
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
assert.strictEqual(row.estimated_cost_usd, 1.23, 'Expected harness cost to win');
// Token totals still reflect the transcript scan
assert.strictEqual(row.input_tokens, 10000, 'Token totals should still come from transcript');
assert.strictEqual(row.output_tokens, 5000, 'Token totals should still come from transcript');
} finally {
try { fs.unlinkSync(harnessCachePath); } catch { /* best-effort */ }
fs.rmSync(tmpHome, { recursive: true, force: true });
}
}) ? passed++ : failed++);
// 9. Ignores stale harness-cost cache and falls back to transcript estimate
(test('ignores stale harness-cost cache (>300s) and uses transcript estimate', () => {
const tmpHome = makeTempDir();
const sessionId = 'harness-stale-' + Date.now();
const transcriptPath = path.join(tmpHome, 'session.jsonl');
writeTranscript(transcriptPath, [
{
type: 'assistant',
message: {
model: 'claude-sonnet-4-20250514',
usage: { input_tokens: 1000, output_tokens: 500 },
},
},
]);
const harnessCachePath = path.join(os.tmpdir(), `harness-cost-${sessionId}.json`);
const staleEpoch = Math.floor(Date.now() / 1000) - 3600;
fs.writeFileSync(
harnessCachePath,
JSON.stringify({ ts: staleEpoch, cost_usd: 999.99 }),
'utf8'
);
try {
const result = runScript(
{ session_id: sessionId, transcript_path: transcriptPath },
withTempHome(tmpHome)
);
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'costs.jsonl');
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
assert.notStrictEqual(row.estimated_cost_usd, 999.99, 'Stale cache must not win');
assert.ok(row.estimated_cost_usd > 0, 'Expected fallback transcript estimate to be positive');
// Sonnet rates: 1000/1e6*3 + 500/1e6*15 ≈ $0.011 — well below the 999.99 stale value
assert.ok(row.estimated_cost_usd < 1, 'Expected small transcript estimate, not the stale 999.99');
} finally {
try { fs.unlinkSync(harnessCachePath); } catch { /* best-effort */ }
fs.rmSync(tmpHome, { recursive: true, force: true });
}
}) ? passed++ : failed++);
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}
-54
View File
@@ -50,7 +50,6 @@ function runTests() {
const components = listInstallComponents({ family: 'locale' });
assert.ok(components.some(component => component.id === 'locale:ja'));
assert.ok(components.some(component => component.id === 'locale:zh-cn'));
assert.ok(components.some(component => component.id === 'locale:de-de'));
assert.ok(components.every(component => component.family === 'locale'));
})) passed++; else failed++;
@@ -76,59 +75,6 @@ function runTests() {
}
})) passed++; else failed++;
if (test('locale:de-de resolves to the German translated docs module', () => {
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-plan-de-'));
try {
const plan = resolveInstallPlan({
includeComponentIds: ['locale:de-de'],
target: 'claude',
homeDir,
});
assert.deepStrictEqual(plan.selectedModuleIds, ['docs-de-de']);
assert.ok(
plan.operations.some(operation => (
normalizePlanPath(operation.sourceRelativePath) === 'docs/de-DE'
&& normalizePlanPath(operation.destinationPath).endsWith('/.claude/docs/de-DE')
)),
'Should map docs/de-DE to ~/.claude/docs/de-DE'
);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('end-to-end: --locale de dry-run includes docs-de-de operations', () => {
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-dry-run-de-'));
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-dry-run-de-project-'));
try {
const output = runInstallApply([
'--locale', 'de',
'--dry-run',
'--json',
], {
cwd: projectDir,
env: { HOME: homeDir },
});
const json = JSON.parse(output);
assert.strictEqual(json.plan.mode, 'manifest');
assert.deepStrictEqual(json.plan.includedComponentIds, ['locale:de-de']);
assert.deepStrictEqual(json.plan.selectedModuleIds, ['docs-de-de']);
assert.ok(
json.plan.operations.some(operation => (
normalizePlanPath(operation.sourceRelativePath) === 'docs/de-DE/README.md'
&& normalizePlanPath(operation.destinationPath).endsWith('/.claude/docs/de-DE/README.md')
)),
'Should copy translated README into ~/.claude/docs/de-DE'
);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
fs.rmSync(projectDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('end-to-end: --locale ja dry-run includes docs-ja-jp operations', () => {
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-dry-run-'));
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'locale-dry-run-project-'));