claude-dev-env 8.14.0 → 8.15.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/.agents/agents/test_agent_frontmatter.py +0 -72
- package/.agents/agents-archived/code-quality-agent.md +16 -16
- package/.agents/skills/_shared/end-of-run-gotcha-recommendations.md +5 -21
- package/.agents/skills/e-code-review/SKILL.md +1 -3
- package/.agents/skills/e-simplify/SKILL.md +6 -6
- package/.agents/skills/eli5/SKILL.md +4 -14
- package/.agents/skills/eli5/reference/task-seeds.md +3 -4
- package/.agents/skills/everything-search/SKILL.md +3 -3
- package/.agents/skills/fresh-branch/SKILL.md +2 -3
- package/.agents/skills/grok-spawn/SKILL.md +3 -8
- package/.agents/skills/issue-tracker/SKILL.md +4 -6
- package/.agents/skills/orchestrator-refresh/SKILL.md +7 -7
- package/.agents/skills/plugin-eval-standalone-skill/SKILL.md +3 -3
- package/.agents/skills/privacy-hygiene/SKILL.md +6 -10
- package/.agents/skills/privacy-hygiene/reference/sweep-procedure.md +1 -1
- package/.agents/skills/pull-request/SKILL.md +11 -14
- package/.agents/skills/pull-request/reference/publication-tasks.md +2 -4
- package/.agents/skills/recovering-codex-startup/SKILL.md +3 -6
- package/.agents/skills/recovering-codex-startup/reference/task-seeds.md +1 -1
- package/.agents/skills/repairing-hook-boundaries/SKILL.md +4 -7
- package/.agents/skills/repairing-hook-boundaries/reference/task-seeds.md +1 -2
- package/.agents/skills/second-account-workers/.claude/CLAUDE.md +1 -0
- package/.agents/skills/second-account-workers/AGENTS.md +1 -0
- package/.agents/skills/second-account-workers/SKILL.md +65 -0
- package/.agents/skills/syncing-submodules/SKILL.md +7 -17
- package/.agents/skills/syncing-submodules/reference/run-tasks.md +1 -1
- package/.agents/skills/task-build/SKILL.md +1 -1
- package/.agents/skills/task-build/reference/tool-routing.md +1 -1
- package/.agents/skills/team-advisor/SKILL.md +2 -4
- package/.agents/skills/usage-pause/SKILL.md +1 -1
- package/.agents/skills/windows-scheduled-task/SKILL.md +1 -1
- package/AGENTS.md +1 -1
- package/_shared/advisor/advisor-protocol.md +4 -4
- package/_shared/advisor/reference/consult-format.md +1 -1
- package/_shared/advisor/reference/identity.md +2 -2
- package/_shared/advisor/reference/warm-up.md +1 -1
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -16
- package/bin/ever-shipped-skills.mjs +1 -0
- package/bin/install.mjs +1 -0
- package/bin/install.prompt-context.test.mjs +46 -0
- package/bin/install.test.mjs +3 -2
- package/commands/sr-loop.md +2 -2
- package/docs/CODE_RULES.md +2 -2
- package/docs/DJANGO_PATTERNS.md +5 -5
- package/docs/REACT_PATTERNS.md +6 -6
- package/docs/TEST_QUALITY.md +10 -28
- package/docs/agent-spawn-protocol.md +4 -4
- package/docs/codex-compatibility.md +4 -33
- package/docs/references/team-advisor-skill.md +0 -1
- package/docs/second-claude-account.md +4 -0
- package/docs/worker-completion-gate.md +0 -1
- package/hooks/advisory/migration_safety_advisor.py +3 -2
- package/hooks/advisory/refactor_guard.py +3 -3
- package/hooks/hooks.json +0 -5
- package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +1 -2
- package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +1 -2
- package/package.json +1 -1
- package/rules/explore-thoroughly.md +0 -1
- package/rules/filesystem-search.md +1 -1
- package/rules/git-workflow.md +5 -5
- package/rules/long-horizon-autonomy.md +1 -5
- package/rules/re-stage-before-commit.md +0 -2
- package/rules/research-mode.md +1 -1
- package/rules/shell-invocation.md +1 -1
- package/scripts/claude_account_worker.py +347 -0
- package/scripts/claude_account_worker_process.py +169 -0
- package/scripts/claude_account_worker_report.py +170 -0
- package/scripts/dev_env_scripts_constants/claude_account_worker_constants.py +139 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +8 -2
- package/scripts/grok_headless_runner.py +2 -5
- package/scripts/policy_lint/config/constants.py +1 -0
- package/scripts/policy_lint/engine.py +5 -2
- package/scripts/repository_checks/config/constants.py +1 -1
- package/scripts/spawn_grok_batch.py +12 -1
- package/scripts/subprocess_stream_text.py +28 -0
- package/scripts/task_usage_events.py +387 -0
- package/scripts/task_usage_report.py +391 -0
- package/scripts/test_claude_account_worker.py +331 -0
- package/scripts/test_spawn_grok_batch.py +22 -0
- package/scripts/test_task_usage_events.py +47 -0
- package/scripts/test_task_usage_report.py +603 -0
- package/scripts/tests/test_banned_prose_words.py +1 -1
- package/scripts/tests/test_policy_lint_rules_archive.py +16 -0
- package/scripts/tests/test_policy_lint_rules_engine.py +2 -2
- package/scripts/tests/test_rules.py +9 -9
- package/system-prompts/software-engineer.xml +15 -30
- package/audit-rubrics/audit-categories.json +0 -700
- package/audit-rubrics/category_rubrics/category-a-api-contracts.md +0 -90
- package/audit-rubrics/category_rubrics/category-b-selector-engine-compat.md +0 -36
- package/audit-rubrics/category_rubrics/category-c-resource-cleanup.md +0 -35
- package/audit-rubrics/category_rubrics/category-d-scoping-and-ordering.md +0 -35
- package/audit-rubrics/category_rubrics/category-e-dead-code.md +0 -40
- package/audit-rubrics/category_rubrics/category-f-silent-failures.md +0 -41
- package/audit-rubrics/category_rubrics/category-g-bounds-and-overflow.md +0 -38
- package/audit-rubrics/category_rubrics/category-h-security-boundaries.md +0 -40
- package/audit-rubrics/category_rubrics/category-i-concurrency.md +0 -38
- package/audit-rubrics/category_rubrics/category-j-code-rules-compliance.md +0 -59
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +0 -66
- package/audit-rubrics/category_rubrics/category-l-behavior-equivalence.md +0 -48
- package/audit-rubrics/category_rubrics/category-m-producer-consumer-cardinality.md +0 -44
- package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +0 -54
- package/audit-rubrics/category_rubrics/category-p-name-vs-behavior-contract.md +0 -59
- package/audit-rubrics/category_rubrics/category-q-cross-surface-claims.md +0 -46
- package/audit-rubrics/prompts/category-a-api-contracts.md +0 -407
- package/audit-rubrics/prompts/category-b-selector-engine-compat.md +0 -401
- package/audit-rubrics/prompts/category-c-resource-cleanup.md +0 -420
- package/audit-rubrics/prompts/category-d-scoping-and-ordering.md +0 -414
- package/audit-rubrics/prompts/category-e-dead-code.md +0 -434
- package/audit-rubrics/prompts/category-f-silent-failures.md +0 -432
- package/audit-rubrics/prompts/category-g-bounds-and-overflow.md +0 -383
- package/audit-rubrics/prompts/category-h-security-boundaries.md +0 -423
- package/audit-rubrics/prompts/category-i-concurrency.md +0 -429
- package/audit-rubrics/prompts/category-j-code-rules-compliance.md +0 -458
- package/audit-rubrics/prompts/category-k-codebase-conflicts.md +0 -338
- package/audit-rubrics/prompts/category-l-behavior-equivalence.md +0 -131
- package/audit-rubrics/prompts/category-m-producer-consumer-cardinality.md +0 -129
- package/audit-rubrics/prompts/category-n-test-name-scenario-verifier.md +0 -139
- package/audit-rubrics/prompts/category-p-name-vs-behavior-contract.md +0 -89
- package/audit-rubrics/prompts/category-q-cross-surface-claims.md +0 -51
- package/hooks/hooks_constants/style_reminder_prompt_constants.py +0 -9
- package/hooks/session/style_reminder_prompt.py +0 -41
- package/hooks/session/test_style_reminder_prompt.py +0 -32
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""Check task usage reporting through its command line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from decimal import Decimal
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
SCRIPT_PATH = Path(__file__).with_name("task_usage_report.py")
|
|
14
|
+
CODEX_STREAM_FIXTURE = (
|
|
15
|
+
Path(__file__).resolve().parent.parent
|
|
16
|
+
/ "_shared"
|
|
17
|
+
/ "pr-loop"
|
|
18
|
+
/ "scripts"
|
|
19
|
+
/ "fixtures"
|
|
20
|
+
/ "success_stream_v0.144.3.jsonl"
|
|
21
|
+
)
|
|
22
|
+
RUN_ARGUMENTS = [
|
|
23
|
+
"run",
|
|
24
|
+
"--task-id",
|
|
25
|
+
"task-7",
|
|
26
|
+
"--run-id",
|
|
27
|
+
"run-1",
|
|
28
|
+
"--repository",
|
|
29
|
+
"team/project",
|
|
30
|
+
"--outcome",
|
|
31
|
+
"pass",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def invoke_cli(
|
|
36
|
+
arguments: list[str], stdin_text: str
|
|
37
|
+
) -> subprocess.CompletedProcess[str]:
|
|
38
|
+
return subprocess.run(
|
|
39
|
+
[sys.executable, str(SCRIPT_PATH), *arguments],
|
|
40
|
+
input=stdin_text,
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
check=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def report_for(source_format: str, source_payload: object) -> dict[str, object]:
|
|
48
|
+
if source_format == "codex-json":
|
|
49
|
+
stdin_text = "\n".join(
|
|
50
|
+
map(
|
|
51
|
+
json.dumps,
|
|
52
|
+
[{"type": "thread.started", "thread_id": "thread-1"}, source_payload],
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
stdin_text = json.dumps(source_payload)
|
|
57
|
+
completed = invoke_cli(
|
|
58
|
+
[*RUN_ARGUMENTS, "--source-format", source_format], stdin_text
|
|
59
|
+
)
|
|
60
|
+
assert completed.returncode == 0, completed.stderr
|
|
61
|
+
return json.loads(completed.stdout)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_claude_json_emits_only_allowlisted_fields_and_uses_model_usage_once() -> None:
|
|
65
|
+
source_payload = {
|
|
66
|
+
"type": "result",
|
|
67
|
+
"result": "SECRET_PROMPT_AND_ANSWER",
|
|
68
|
+
"session_id": "SECRET_SESSION",
|
|
69
|
+
"total_cost_usd": 0.125,
|
|
70
|
+
"usage": {
|
|
71
|
+
"input_tokens": 17,
|
|
72
|
+
"cache_creation_input_tokens": 11,
|
|
73
|
+
"cache_read_input_tokens": 31,
|
|
74
|
+
"output_tokens": 7,
|
|
75
|
+
},
|
|
76
|
+
"modelUsage": {
|
|
77
|
+
"claude-opus": {
|
|
78
|
+
"inputTokens": 17,
|
|
79
|
+
"cacheCreationInputTokens": 11,
|
|
80
|
+
"cacheReadInputTokens": 31,
|
|
81
|
+
"outputTokens": 7,
|
|
82
|
+
"costUSD": 0.125,
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
report = report_for("claude-json", source_payload)
|
|
87
|
+
serialized_report = json.dumps(report)
|
|
88
|
+
assert "SECRET_PROMPT_AND_ANSWER" not in serialized_report
|
|
89
|
+
assert "SECRET_SESSION" not in serialized_report
|
|
90
|
+
assert report["uncached_input_tokens"] == 17
|
|
91
|
+
assert report["cache_write_tokens"] == 11
|
|
92
|
+
assert report["cache_read_tokens"] == 31
|
|
93
|
+
assert report["output_tokens"] == 7
|
|
94
|
+
assert report["cost_usd"] == "0.125"
|
|
95
|
+
assert report["cost_basis"] == "provider_estimate"
|
|
96
|
+
assert report["models"] == ["claude-opus"]
|
|
97
|
+
assert report["event_count"] == 1
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_claude_json_keeps_missing_usage_unknown() -> None:
|
|
101
|
+
report = report_for(
|
|
102
|
+
"claude-json", {"type": "result", "total_cost_usd": 0.03, "result": "secret"}
|
|
103
|
+
)
|
|
104
|
+
assert report["uncached_input_tokens"] is None
|
|
105
|
+
assert report["cache_write_tokens"] is None
|
|
106
|
+
assert report["cost_usd"] == "0.03"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_claude_otel_deduplicates_request_ids_and_keeps_models_separate() -> None:
|
|
110
|
+
first_event = {
|
|
111
|
+
"body": "claude_code.api_request",
|
|
112
|
+
"attributes": {
|
|
113
|
+
"request_id": "request-1",
|
|
114
|
+
"session.id": "session-1",
|
|
115
|
+
"model": "claude-opus",
|
|
116
|
+
"input_tokens": 2,
|
|
117
|
+
"cache_creation_tokens": 3,
|
|
118
|
+
"cache_read_tokens": 5,
|
|
119
|
+
"output_tokens": 7,
|
|
120
|
+
"cost_usd_micros": 12_500,
|
|
121
|
+
"prompt": "SECRET_PROMPT",
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
second_event = {
|
|
125
|
+
"body": "claude_code.api_request",
|
|
126
|
+
"attributes": {
|
|
127
|
+
"request_id": "request-2",
|
|
128
|
+
"session.id": "session-1",
|
|
129
|
+
"model": "claude-sonnet",
|
|
130
|
+
"input_tokens": 11,
|
|
131
|
+
"cache_creation_tokens": 13,
|
|
132
|
+
"cache_read_tokens": 17,
|
|
133
|
+
"output_tokens": 19,
|
|
134
|
+
"cost_usd": 0.0375,
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
stdin_text = "\n".join(map(json.dumps, [first_event, first_event, second_event]))
|
|
138
|
+
completed = invoke_cli(
|
|
139
|
+
[*RUN_ARGUMENTS, "--source-format", "claude-otel"], stdin_text
|
|
140
|
+
)
|
|
141
|
+
assert completed.returncode == 0, completed.stderr
|
|
142
|
+
report = json.loads(completed.stdout)
|
|
143
|
+
assert report["event_count"] == 2
|
|
144
|
+
assert report["uncached_input_tokens"] == 13
|
|
145
|
+
assert report["cache_write_tokens"] == 16
|
|
146
|
+
assert report["cache_read_tokens"] == 22
|
|
147
|
+
assert report["output_tokens"] == 26
|
|
148
|
+
assert Decimal(report["cost_usd"]) == Decimal("0.0500")
|
|
149
|
+
assert report["models"] == ["claude-opus", "claude-sonnet"]
|
|
150
|
+
assert "SECRET_PROMPT" not in completed.stdout
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_claude_otel_accepts_typed_attributes_and_ignores_other_events() -> None:
|
|
154
|
+
source_payload = {
|
|
155
|
+
"resourceLogs": [
|
|
156
|
+
{
|
|
157
|
+
"scopeLogs": [
|
|
158
|
+
{
|
|
159
|
+
"logRecords": [
|
|
160
|
+
{
|
|
161
|
+
"body": {"stringValue": "claude_code.user_prompt"},
|
|
162
|
+
"attributes": [
|
|
163
|
+
{
|
|
164
|
+
"key": "session.id",
|
|
165
|
+
"value": {"stringValue": "session-1"},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"key": "prompt",
|
|
169
|
+
"value": {"stringValue": "SECRET_PROMPT"},
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"body": {"stringValue": "claude_code.api_request"},
|
|
175
|
+
"attributes": [
|
|
176
|
+
{
|
|
177
|
+
"key": "request_id",
|
|
178
|
+
"value": {"stringValue": "r1"},
|
|
179
|
+
},
|
|
180
|
+
{"key": "model", "value": {"stringValue": "opus"}},
|
|
181
|
+
{
|
|
182
|
+
"key": "session.id",
|
|
183
|
+
"value": {"stringValue": "session-1"},
|
|
184
|
+
},
|
|
185
|
+
{"key": "input_tokens", "value": {"intValue": "4"}},
|
|
186
|
+
{
|
|
187
|
+
"key": "cache_creation_tokens",
|
|
188
|
+
"value": {"intValue": "0"},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"key": "cache_read_tokens",
|
|
192
|
+
"value": {"intValue": "8"},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"key": "output_tokens",
|
|
196
|
+
"value": {"intValue": "2"},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
report = report_for("claude-otel", source_payload)
|
|
207
|
+
assert report["event_count"] == 1
|
|
208
|
+
assert report["uncached_input_tokens"] == 4
|
|
209
|
+
assert report["cache_read_tokens"] == 8
|
|
210
|
+
assert report["cost_usd"] is None
|
|
211
|
+
assert "SECRET_PROMPT" not in json.dumps(report)
|
|
212
|
+
assert "session-1" not in json.dumps(report)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_codex_uses_cached_and_reasoning_counts_as_subsets() -> None:
|
|
216
|
+
source_payload = [
|
|
217
|
+
{"type": "thread.started", "thread_id": "SECRET_THREAD"},
|
|
218
|
+
{
|
|
219
|
+
"type": "turn.completed",
|
|
220
|
+
"turn_id": "turn-1",
|
|
221
|
+
"model": "gpt-x",
|
|
222
|
+
"usage": {
|
|
223
|
+
"input_tokens": 100,
|
|
224
|
+
"cached_input_tokens": 20,
|
|
225
|
+
"cache_write_input_tokens": 5,
|
|
226
|
+
"output_tokens": 30,
|
|
227
|
+
"reasoning_output_tokens": 10,
|
|
228
|
+
},
|
|
229
|
+
"text": "SECRET_ANSWER",
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
stdin_text = "\n".join(map(json.dumps, source_payload))
|
|
233
|
+
completed = invoke_cli(
|
|
234
|
+
[
|
|
235
|
+
*RUN_ARGUMENTS,
|
|
236
|
+
"--source-format",
|
|
237
|
+
"codex-json",
|
|
238
|
+
"--rate",
|
|
239
|
+
"gpt-x=1,2,0.1,4",
|
|
240
|
+
"--rate-source",
|
|
241
|
+
"vendor-price-card",
|
|
242
|
+
"--rate-effective-date",
|
|
243
|
+
"2026-09-24",
|
|
244
|
+
],
|
|
245
|
+
stdin_text,
|
|
246
|
+
)
|
|
247
|
+
assert completed.returncode == 0, completed.stderr
|
|
248
|
+
report = json.loads(completed.stdout)
|
|
249
|
+
assert report["uncached_input_tokens"] == 75
|
|
250
|
+
assert report["cache_write_tokens"] == 5
|
|
251
|
+
assert report["cache_read_tokens"] == 20
|
|
252
|
+
assert report["output_tokens"] == 30
|
|
253
|
+
assert report["reasoning_output_tokens"] == 10
|
|
254
|
+
assert report["cost_usd"] == "0.000207"
|
|
255
|
+
assert report["cost_basis"] == "caller_rates"
|
|
256
|
+
assert report["rate_source"] == "vendor-price-card"
|
|
257
|
+
assert report["rate_effective_date"] == "2026-09-24"
|
|
258
|
+
assert "SECRET_THREAD" not in completed.stdout
|
|
259
|
+
assert "SECRET_ANSWER" not in completed.stdout
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_codex_deduplicates_turn_ids_and_requires_rates_for_cost() -> None:
|
|
263
|
+
turn_event = {
|
|
264
|
+
"type": "turn.completed",
|
|
265
|
+
"turn_id": "turn-1",
|
|
266
|
+
"usage": {
|
|
267
|
+
"input_tokens": 20,
|
|
268
|
+
"cached_input_tokens": 5,
|
|
269
|
+
"cache_write_input_tokens": 0,
|
|
270
|
+
"output_tokens": 3,
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
stdin_text = "\n".join(
|
|
274
|
+
map(
|
|
275
|
+
json.dumps,
|
|
276
|
+
[
|
|
277
|
+
{"type": "thread.started", "thread_id": "thread-1"},
|
|
278
|
+
turn_event,
|
|
279
|
+
turn_event,
|
|
280
|
+
],
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
completed = invoke_cli(
|
|
284
|
+
[*RUN_ARGUMENTS, "--source-format", "codex-json"], stdin_text
|
|
285
|
+
)
|
|
286
|
+
assert completed.returncode == 0, completed.stderr
|
|
287
|
+
report = json.loads(completed.stdout)
|
|
288
|
+
assert report["event_count"] == 1
|
|
289
|
+
assert report["uncached_input_tokens"] == 15
|
|
290
|
+
assert report["cost_usd"] is None
|
|
291
|
+
assert report["cost_basis"] == "unknown"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def test_codex_missing_usage_does_not_become_zero() -> None:
|
|
295
|
+
report = report_for("codex-json", {"type": "turn.completed"})
|
|
296
|
+
assert report["event_count"] == 1
|
|
297
|
+
assert report["uncached_input_tokens"] is None
|
|
298
|
+
assert report["output_tokens"] is None
|
|
299
|
+
assert report["cost_usd"] is None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@pytest.mark.parametrize("bad_count", [-1, 1.5, "oops", None, True])
|
|
303
|
+
def test_malformed_numeric_usage_fails_without_echoing_event(bad_count: object) -> None:
|
|
304
|
+
source_payload = {
|
|
305
|
+
"type": "turn.completed",
|
|
306
|
+
"usage": {
|
|
307
|
+
"input_tokens": bad_count,
|
|
308
|
+
"cached_input_tokens": 0,
|
|
309
|
+
"cache_write_input_tokens": 0,
|
|
310
|
+
"output_tokens": 1,
|
|
311
|
+
},
|
|
312
|
+
"text": "SECRET_ANSWER",
|
|
313
|
+
}
|
|
314
|
+
completed = invoke_cli(
|
|
315
|
+
[*RUN_ARGUMENTS, "--source-format", "codex-json"],
|
|
316
|
+
"\n".join(
|
|
317
|
+
map(
|
|
318
|
+
json.dumps,
|
|
319
|
+
[{"type": "thread.started", "thread_id": "thread-1"}, source_payload],
|
|
320
|
+
)
|
|
321
|
+
),
|
|
322
|
+
)
|
|
323
|
+
assert completed.returncode != 0
|
|
324
|
+
assert completed.stdout == ""
|
|
325
|
+
assert "SECRET_ANSWER" not in completed.stderr
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def test_codex_rejects_cached_input_exceeding_total_input() -> None:
|
|
329
|
+
source_payload = {
|
|
330
|
+
"type": "turn.completed",
|
|
331
|
+
"usage": {
|
|
332
|
+
"input_tokens": 10,
|
|
333
|
+
"cached_input_tokens": 11,
|
|
334
|
+
"cache_write_input_tokens": 0,
|
|
335
|
+
"output_tokens": 1,
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
completed = invoke_cli(
|
|
339
|
+
[*RUN_ARGUMENTS, "--source-format", "codex-json"],
|
|
340
|
+
"\n".join(
|
|
341
|
+
map(
|
|
342
|
+
json.dumps,
|
|
343
|
+
[{"type": "thread.started", "thread_id": "thread-1"}, source_payload],
|
|
344
|
+
)
|
|
345
|
+
),
|
|
346
|
+
)
|
|
347
|
+
assert completed.returncode != 0
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def test_summary_groups_resumed_runs_and_includes_failed_attempt_cost() -> None:
|
|
351
|
+
first_report = {
|
|
352
|
+
"task_id": "task-7",
|
|
353
|
+
"run_id": "run-1",
|
|
354
|
+
"repository": "team/project",
|
|
355
|
+
"outcome": "fail",
|
|
356
|
+
"cost_usd": "0.20",
|
|
357
|
+
}
|
|
358
|
+
second_report = {
|
|
359
|
+
"task_id": "task-7",
|
|
360
|
+
"run_id": "run-2",
|
|
361
|
+
"repository": "team/project",
|
|
362
|
+
"outcome": "pass",
|
|
363
|
+
"cost_usd": "0.30",
|
|
364
|
+
}
|
|
365
|
+
stdin_text = "\n".join(map(json.dumps, [first_report, second_report]))
|
|
366
|
+
completed = invoke_cli(["summary"], stdin_text)
|
|
367
|
+
assert completed.returncode == 0, completed.stderr
|
|
368
|
+
summary = json.loads(completed.stdout)
|
|
369
|
+
assert summary["task_count"] == 1
|
|
370
|
+
assert summary["passed_task_count"] == 1
|
|
371
|
+
assert Decimal(summary["total_cost_usd"]) == Decimal("0.50")
|
|
372
|
+
assert Decimal(summary["cost_per_passed_task_usd"]) == Decimal("0.50")
|
|
373
|
+
assert summary["tasks"][0]["run_ids"] == ["run-1", "run-2"]
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@pytest.mark.parametrize("missing_field", ["cost_usd", "outcome"])
|
|
377
|
+
def test_summary_marks_missing_cost_or_outcome_unknown(missing_field: str) -> None:
|
|
378
|
+
source_report = {
|
|
379
|
+
"task_id": "task-7",
|
|
380
|
+
"run_id": "run-1",
|
|
381
|
+
"repository": "team/project",
|
|
382
|
+
"outcome": "pass",
|
|
383
|
+
"cost_usd": "0.20",
|
|
384
|
+
}
|
|
385
|
+
del source_report[missing_field]
|
|
386
|
+
completed = invoke_cli(["summary"], json.dumps(source_report))
|
|
387
|
+
assert completed.returncode == 0, completed.stderr
|
|
388
|
+
summary = json.loads(completed.stdout)
|
|
389
|
+
assert summary["cost_per_passed_task_usd"] is None
|
|
390
|
+
assert summary["status"] == "unknown"
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def test_summary_deduplicates_identical_run_rows() -> None:
|
|
394
|
+
source_report = {
|
|
395
|
+
"task_id": "task-7",
|
|
396
|
+
"run_id": "run-1",
|
|
397
|
+
"repository": "team/project",
|
|
398
|
+
"outcome": "pass",
|
|
399
|
+
"cost_usd": "0.20",
|
|
400
|
+
}
|
|
401
|
+
completed = invoke_cli(
|
|
402
|
+
["summary"], "\n".join(map(json.dumps, [source_report, source_report]))
|
|
403
|
+
)
|
|
404
|
+
assert completed.returncode == 0, completed.stderr
|
|
405
|
+
summary = json.loads(completed.stdout)
|
|
406
|
+
assert Decimal(summary["total_cost_usd"]) == Decimal("0.20")
|
|
407
|
+
assert summary["tasks"][0]["run_ids"] == ["run-1"]
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def test_claude_otel_accepts_flat_event_attributes() -> None:
|
|
411
|
+
source_payload = {
|
|
412
|
+
"event.name": "api_request",
|
|
413
|
+
"session.id": "session-1",
|
|
414
|
+
"model": "claude-sonnet",
|
|
415
|
+
"input_tokens": 4,
|
|
416
|
+
"cache_creation_tokens": 2,
|
|
417
|
+
"cache_read_tokens": 3,
|
|
418
|
+
"output_tokens": 1,
|
|
419
|
+
"cost_usd": 0.002,
|
|
420
|
+
}
|
|
421
|
+
report = report_for("claude-otel", source_payload)
|
|
422
|
+
assert report["uncached_input_tokens"] == 4
|
|
423
|
+
assert report["cache_write_tokens"] == 2
|
|
424
|
+
assert report["cost_usd"] == "0.002"
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_codex_mixed_models_need_each_models_rate() -> None:
|
|
428
|
+
all_events = [
|
|
429
|
+
{
|
|
430
|
+
"type": "turn.completed",
|
|
431
|
+
"turn_id": f"turn-{each_index}",
|
|
432
|
+
"model": each_model,
|
|
433
|
+
"usage": {
|
|
434
|
+
"input_tokens": 1_000_000,
|
|
435
|
+
"cached_input_tokens": 0,
|
|
436
|
+
"cache_write_input_tokens": 0,
|
|
437
|
+
"output_tokens": 0,
|
|
438
|
+
},
|
|
439
|
+
}
|
|
440
|
+
for each_index, each_model in enumerate(("gpt-small", "gpt-large"))
|
|
441
|
+
]
|
|
442
|
+
stdin_text = "\n".join(
|
|
443
|
+
map(
|
|
444
|
+
json.dumps,
|
|
445
|
+
[{"type": "thread.started", "thread_id": "thread-1"}, *all_events],
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
base_arguments = [*RUN_ARGUMENTS, "--source-format", "codex-json"]
|
|
449
|
+
rate_provenance = [
|
|
450
|
+
"--rate-source",
|
|
451
|
+
"vendor-price-card",
|
|
452
|
+
"--rate-effective-date",
|
|
453
|
+
"2026-09-24",
|
|
454
|
+
]
|
|
455
|
+
partial = invoke_cli(
|
|
456
|
+
[*base_arguments, "--rate", "gpt-small=1,0,0,0", *rate_provenance],
|
|
457
|
+
stdin_text,
|
|
458
|
+
)
|
|
459
|
+
complete = invoke_cli(
|
|
460
|
+
[
|
|
461
|
+
*base_arguments,
|
|
462
|
+
"--rate",
|
|
463
|
+
"gpt-small=1,0,0,0",
|
|
464
|
+
"--rate",
|
|
465
|
+
"gpt-large=3,0,0,0",
|
|
466
|
+
*rate_provenance,
|
|
467
|
+
],
|
|
468
|
+
stdin_text,
|
|
469
|
+
)
|
|
470
|
+
assert partial.returncode == 0, partial.stderr
|
|
471
|
+
assert complete.returncode == 0, complete.stderr
|
|
472
|
+
assert json.loads(partial.stdout)["cost_usd"] is None
|
|
473
|
+
complete_report = json.loads(complete.stdout)
|
|
474
|
+
assert complete_report["cost_usd"] == "4"
|
|
475
|
+
assert complete_report["models"] == ["gpt-large", "gpt-small"]
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def test_otel_rejects_mixed_sessions_without_leaking_identifiers() -> None:
|
|
479
|
+
all_events = [
|
|
480
|
+
{
|
|
481
|
+
"body": "claude_code.api_request",
|
|
482
|
+
"attributes": {
|
|
483
|
+
"session.id": each_session,
|
|
484
|
+
"request_id": f"request-{each_index}",
|
|
485
|
+
"model": "claude-opus",
|
|
486
|
+
"input_tokens": 1,
|
|
487
|
+
"cache_creation_tokens": 0,
|
|
488
|
+
"cache_read_tokens": 0,
|
|
489
|
+
"output_tokens": 1,
|
|
490
|
+
"cost_usd": 0.01,
|
|
491
|
+
},
|
|
492
|
+
}
|
|
493
|
+
for each_index, each_session in enumerate(
|
|
494
|
+
("SECRET_SESSION_A", "SECRET_SESSION_B")
|
|
495
|
+
)
|
|
496
|
+
]
|
|
497
|
+
completed = invoke_cli(
|
|
498
|
+
[*RUN_ARGUMENTS, "--source-format", "claude-otel"],
|
|
499
|
+
"\n".join(map(json.dumps, all_events)),
|
|
500
|
+
)
|
|
501
|
+
assert completed.returncode != 0
|
|
502
|
+
assert completed.stdout == ""
|
|
503
|
+
assert "SECRET_SESSION" not in completed.stderr
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def test_otel_rejects_non_api_record_from_another_session() -> None:
|
|
507
|
+
all_records = [
|
|
508
|
+
{
|
|
509
|
+
"body": "claude_code.api_request",
|
|
510
|
+
"attributes": {"session.id": "SECRET_SESSION_A"},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"body": "claude_code.user_prompt",
|
|
514
|
+
"attributes": {
|
|
515
|
+
"session.id": "SECRET_SESSION_B",
|
|
516
|
+
"prompt": "SECRET_PROMPT",
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
]
|
|
520
|
+
completed = invoke_cli(
|
|
521
|
+
[*RUN_ARGUMENTS, "--source-format", "claude-otel"], json.dumps(all_records)
|
|
522
|
+
)
|
|
523
|
+
assert completed.returncode != 0
|
|
524
|
+
assert completed.stdout == ""
|
|
525
|
+
assert "one session ID" in completed.stderr
|
|
526
|
+
assert "SECRET_SESSION" not in completed.stderr
|
|
527
|
+
assert "SECRET_PROMPT" not in completed.stderr
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def test_otel_rejects_capture_without_api_request() -> None:
|
|
531
|
+
source_payload = {
|
|
532
|
+
"body": "claude_code.user_prompt",
|
|
533
|
+
"attributes": {"session.id": "session-1"},
|
|
534
|
+
}
|
|
535
|
+
completed = invoke_cli(
|
|
536
|
+
[*RUN_ARGUMENTS, "--source-format", "claude-otel"], json.dumps(source_payload)
|
|
537
|
+
)
|
|
538
|
+
assert completed.returncode != 0
|
|
539
|
+
assert completed.stdout == ""
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def test_codex_rejects_concatenated_threads() -> None:
|
|
543
|
+
all_events = [
|
|
544
|
+
{"type": "thread.started", "thread_id": "thread-1"},
|
|
545
|
+
{"type": "turn.completed", "usage": {"input_tokens": 1}},
|
|
546
|
+
{"type": "thread.started", "thread_id": "thread-2"},
|
|
547
|
+
{"type": "turn.completed", "usage": {"input_tokens": 1}},
|
|
548
|
+
]
|
|
549
|
+
completed = invoke_cli(
|
|
550
|
+
[*RUN_ARGUMENTS, "--source-format", "codex-json"],
|
|
551
|
+
"\n".join(map(json.dumps, all_events)),
|
|
552
|
+
)
|
|
553
|
+
assert completed.returncode != 0
|
|
554
|
+
assert completed.stdout == ""
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_codex_rate_requires_source_and_effective_date() -> None:
|
|
558
|
+
all_events = [
|
|
559
|
+
{"type": "thread.started", "thread_id": "thread-1"},
|
|
560
|
+
{
|
|
561
|
+
"type": "turn.completed",
|
|
562
|
+
"model": "gpt-x",
|
|
563
|
+
"usage": {
|
|
564
|
+
"input_tokens": 1,
|
|
565
|
+
"cached_input_tokens": 0,
|
|
566
|
+
"cache_write_input_tokens": 0,
|
|
567
|
+
"output_tokens": 1,
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
]
|
|
571
|
+
completed = invoke_cli(
|
|
572
|
+
[*RUN_ARGUMENTS, "--source-format", "codex-json", "--rate", "gpt-x=1,0,0,1"],
|
|
573
|
+
"\n".join(map(json.dumps, all_events)),
|
|
574
|
+
)
|
|
575
|
+
assert completed.returncode != 0
|
|
576
|
+
assert completed.stdout == ""
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def test_codex_fixture_without_cache_write_field_uses_zero_write_cost() -> None:
|
|
580
|
+
completed = invoke_cli(
|
|
581
|
+
[
|
|
582
|
+
*RUN_ARGUMENTS,
|
|
583
|
+
"--source-format",
|
|
584
|
+
"codex-json",
|
|
585
|
+
"--model",
|
|
586
|
+
"gpt-x",
|
|
587
|
+
"--rate",
|
|
588
|
+
"gpt-x=1,2,0.1,4",
|
|
589
|
+
"--rate-source",
|
|
590
|
+
"vendor-price-card",
|
|
591
|
+
"--rate-effective-date",
|
|
592
|
+
"2026-09-24",
|
|
593
|
+
],
|
|
594
|
+
CODEX_STREAM_FIXTURE.read_text(encoding="utf-8"),
|
|
595
|
+
)
|
|
596
|
+
assert completed.returncode == 0, completed.stderr
|
|
597
|
+
report = json.loads(completed.stdout)
|
|
598
|
+
assert report["uncached_input_tokens"] == 12
|
|
599
|
+
assert report["cache_write_tokens"] == 0
|
|
600
|
+
assert report["cache_read_tokens"] == 0
|
|
601
|
+
assert report["output_tokens"] == 6
|
|
602
|
+
assert report["cost_usd"] == "0.000036"
|
|
603
|
+
assert report["cost_basis"] == "caller_rates"
|
|
@@ -86,7 +86,7 @@ def test_governed_paths_lists_the_shipped_surfaces(tmp_path: Path) -> None:
|
|
|
86
86
|
def test_governed_paths_cover_instruction_surfaces_and_skip_archives() -> None:
|
|
87
87
|
assert governs_path("rules/git-workflow.md")
|
|
88
88
|
assert governs_path(".agents/skills/eli5/SKILL.md")
|
|
89
|
-
assert governs_path("audit-rubrics/prompts/category-
|
|
89
|
+
assert governs_path("audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md")
|
|
90
90
|
assert governs_path("system-prompts/software-engineer.xml")
|
|
91
91
|
assert not governs_path("rules-archived/manifest.md")
|
|
92
92
|
assert not governs_path("scripts/banned_prose_words.py")
|
|
@@ -134,3 +134,19 @@ def test_archived_hook_moves_leave_the_automatic_checks(tmp_path: Path) -> None:
|
|
|
134
134
|
assert runtime_set.documents == ()
|
|
135
135
|
assert runtime_set.deleted_paths == (live_hook_path,)
|
|
136
136
|
assert runtime_set.renamed_paths == ()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_archived_audit_rubric_moves_leave_the_automatic_checks(tmp_path: Path) -> None:
|
|
140
|
+
live_rubric_path = PurePosixPath("packages/tool/audit-rubrics/prompts/category-k.md")
|
|
141
|
+
archived_rubric_path = PurePosixPath("packages/tool/audit-rubrics-archived/prompts/category-k.md")
|
|
142
|
+
document_set = DocumentSet(
|
|
143
|
+
(Document.from_text(archived_rubric_path, "archived"),),
|
|
144
|
+
SelectionKind.BASE,
|
|
145
|
+
tmp_path,
|
|
146
|
+
(),
|
|
147
|
+
((live_rubric_path, archived_rubric_path),),
|
|
148
|
+
)
|
|
149
|
+
runtime_set = _runtime_document_set(document_set)
|
|
150
|
+
assert runtime_set.documents == ()
|
|
151
|
+
assert runtime_set.deleted_paths == (live_rubric_path,)
|
|
152
|
+
assert runtime_set.renamed_paths == ()
|
|
@@ -209,7 +209,7 @@ def test_changed_line_filter_drops_other_located_line(tmp_path: Path) -> None:
|
|
|
209
209
|
assert lint_report.diagnostics == ()
|
|
210
210
|
|
|
211
211
|
|
|
212
|
-
def
|
|
212
|
+
def test_lint_drops_located_findings_when_a_change_only_deletes_lines(
|
|
213
213
|
tmp_path: Path,
|
|
214
214
|
) -> None:
|
|
215
215
|
repository_root = tmp_path.resolve()
|
|
@@ -233,7 +233,7 @@ def test_lint_keeps_located_findings_when_changed_lines_are_empty(
|
|
|
233
233
|
(document,),
|
|
234
234
|
(_changed_document_rule("probe", collect_diagnostics),),
|
|
235
235
|
)
|
|
236
|
-
assert
|
|
236
|
+
assert lint_report.diagnostics == ()
|
|
237
237
|
|
|
238
238
|
|
|
239
239
|
def test_code_rule_diagnostics_includes_comment_policy_without_prior_text(
|