@event4u/agent-config 2.19.0 → 2.20.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/.agent-src/commands/agent-status.md +29 -0
- package/.agent-src/commands/onboard.md +221 -81
- package/.agent-src/packs/README.md +49 -0
- package/.agent-src/packs/agency-delivery.yml +63 -0
- package/.agent-src/packs/content-engine.yml +53 -0
- package/.agent-src/packs/founder-mvp.yml +51 -0
- package/.agent-src/presets/README.md +26 -0
- package/.agent-src/presets/balanced.yml +34 -0
- package/.agent-src/presets/fast.yml +31 -0
- package/.agent-src/presets/strict.yml +38 -0
- package/.agent-src/profiles/README.md +29 -0
- package/.agent-src/profiles/agency.yml +27 -0
- package/.agent-src/profiles/content_creator.yml +25 -0
- package/.agent-src/profiles/developer.yml +26 -0
- package/.agent-src/profiles/finance.yml +24 -0
- package/.agent-src/profiles/founder.yml +25 -0
- package/.agent-src/profiles/ops.yml +25 -0
- package/.agent-src/rules/no-cheap-questions.md +25 -17
- package/.agent-src/skills/adr-create/SKILL.md +78 -68
- package/.agent-src/skills/subagent-orchestration/SKILL.md +33 -0
- package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/.agent-src/templates/skill-archive-note.md +101 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +73 -70
- package/README.md +68 -72
- package/config/agent-settings.template.yml +22 -0
- package/docs/adrs/caveman/0001-default-off-until-bench.md +93 -0
- package/docs/adrs/caveman/README.md +9 -0
- package/docs/adrs/cost/0001-hard-stop-hook.md +114 -0
- package/docs/adrs/cost/README.md +9 -0
- package/docs/adrs/memory/0001-consumer-side-snapshot.md +111 -0
- package/docs/adrs/memory/README.md +9 -0
- package/docs/adrs/router/0001-three-tier-routing.md +119 -0
- package/docs/adrs/router/README.md +9 -0
- package/docs/adrs/schema/0001-json-schema-frontmatter.md +102 -0
- package/docs/adrs/schema/README.md +9 -0
- package/docs/adrs/smoke/0001-per-tier-smoke-scripts.md +99 -0
- package/docs/adrs/smoke/README.md +9 -0
- package/docs/architecture/current-onboard-baseline.md +126 -0
- package/docs/architecture/current-safety-behavior.md +137 -0
- package/docs/archive/CHANGELOG-pre-2.16.0.md +48 -0
- package/docs/archive/CHANGELOG-pre-2.17.0.md +63 -0
- package/docs/contracts/adr-layout.md +108 -0
- package/docs/contracts/benchmark-corpus-spec.md +97 -0
- package/docs/contracts/benchmark-report-schema.md +111 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/command-taxonomy.md +137 -0
- package/docs/contracts/compression-default-kill-criterion.md +69 -0
- package/docs/contracts/config-presets.md +144 -0
- package/docs/contracts/cost-dashboard.md +143 -0
- package/docs/contracts/cost-enforcement.md +134 -0
- package/docs/contracts/file-ownership-matrix.json +0 -7
- package/docs/contracts/mcp-tool-inventory.md +53 -0
- package/docs/contracts/measurement-baseline.md +102 -0
- package/docs/contracts/namespace.md +125 -0
- package/docs/contracts/profile-system.md +142 -0
- package/docs/contracts/safety-model.md +129 -0
- package/docs/contracts/smoke-contracts.md +144 -0
- package/docs/contracts/workflow-packs.md +121 -0
- package/docs/decisions/ADR-010-profile-pack-preset-boundary.md +132 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/featured-commands.md +27 -0
- package/docs/parity/bench-ruflo.json +58 -0
- package/docs/parity/bench.json +41 -0
- package/docs/parity/ruflo.md +46 -0
- package/docs/profiles.md +91 -0
- package/package.json +1 -1
- package/scripts/_cli/cmd_explain.py +250 -0
- package/scripts/_lib/bench_cost.py +138 -0
- package/scripts/_lib/bench_quality.py +118 -0
- package/scripts/_lib/bench_report.py +150 -0
- package/scripts/agent-config +13 -0
- package/scripts/audit_adr_coverage.py +175 -0
- package/scripts/audit_mcp_tools.py +146 -0
- package/scripts/bench_baseline_ready.py +108 -0
- package/scripts/bench_drift_check.py +151 -0
- package/scripts/bench_per_tool.py +216 -0
- package/scripts/bench_run.py +155 -0
- package/scripts/config/__init__.py +9 -0
- package/scripts/config/presets.py +206 -0
- package/scripts/config/profiles.py +173 -0
- package/scripts/cost/budget.mjs +73 -12
- package/scripts/cost/preflight.mjs +89 -0
- package/scripts/lint_archived_skills.py +143 -0
- package/scripts/lint_bench_corpus.py +161 -0
- package/scripts/lint_namespace.py +135 -0
- package/scripts/lint_roadmap_complexity.py +3 -2
- package/scripts/skill_overlap.py +204 -0
- package/scripts/skill_usage_collect.py +191 -0
- package/scripts/skill_usage_report.py +162 -0
- package/scripts/smoke/kernel.sh +101 -0
- package/scripts/smoke/router.sh +129 -0
- package/scripts/smoke/schema.sh +71 -0
- package/scripts/smoke/skills.sh +101 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Featured Commands
|
|
2
|
+
|
|
3
|
+
A curated subset of the 124 active commands. Full set lives in
|
|
4
|
+
[`.agent-src/commands/`](../.agent-src/commands/) — see also
|
|
5
|
+
[`docs/catalog.md`](catalog.md) for the complete index.
|
|
6
|
+
|
|
7
|
+
## For developers
|
|
8
|
+
|
|
9
|
+
| Command | What it does |
|
|
10
|
+
|---|---|
|
|
11
|
+
| [`/implement-ticket`](../.agent-src/commands/implement-ticket.md) | Drive a Jira / Linear ticket end-to-end through refine → plan → implement → test → verify |
|
|
12
|
+
| [`/work`](../.agent-src/commands/work.md) | Same end-to-end loop for a free-form prompt — confidence-band gated |
|
|
13
|
+
| [`/fix ci`](../.agent-src/commands/fix.md) | Fetch CI failures from GitHub Actions and fix them |
|
|
14
|
+
| [`/review-changes`](../.agent-src/commands/review-changes.md) | Self-review local changes before creating a PR (five judges) |
|
|
15
|
+
| [`/create-pr`](../.agent-src/commands/create-pr.md) | Create a GitHub PR with Jira-linked description |
|
|
16
|
+
|
|
17
|
+
## For everyone
|
|
18
|
+
|
|
19
|
+
| Command | What it does |
|
|
20
|
+
|---|---|
|
|
21
|
+
| [`/research`](../.agent-src/commands/research.md) | Survey / benchmark / competitive scan scaffolder — picks objects, defines fields |
|
|
22
|
+
| [`po-discovery`](../.agent-src/skills/po-discovery/SKILL.md) | Shape a fuzzy product ask into a refined backlog item — problem framing, AC tightening |
|
|
23
|
+
| [`/ghostwriter:write`](../.agent-src/commands/ghostwriter/write.md) | Draft in a public-figure voice profile (mandatory disclosure footer) |
|
|
24
|
+
| [`/challenge-me`](../.agent-src/commands/challenge-me.md) | Interactive grill-style interview that sharpens a fuzzy plan into a copyable pitch |
|
|
25
|
+
| [`/fundraising-narrative`](../.agent-src/skills/fundraising-narrative/SKILL.md) | Shape a capital-raise pitch — why-now / why-us / why-this framing |
|
|
26
|
+
|
|
27
|
+
→ [Browse all 124 active commands](../.agent-src/commands/)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "parity-bench-ruflo-v1",
|
|
3
|
+
"status": "infrastructure_ready_awaiting_corpus_run",
|
|
4
|
+
"owner_roadmap": "agents/roadmaps/step-11-ruflo-parity.md",
|
|
5
|
+
"parity_doc": "docs/parity/ruflo.md",
|
|
6
|
+
"parent_bench": "docs/parity/bench.json",
|
|
7
|
+
"claim_under_test": {
|
|
8
|
+
"source": "agents/audit-2026-05-14-north-star/external-findings.md § 2",
|
|
9
|
+
"headline": "Average dollar cost per 25-prompt corpus run, separated by model tier (Haiku / Sonnet / Opus) and by token class (input / output / cache-read / cache-write).",
|
|
10
|
+
"comparison_target": "ruflo cost-tracker README (claimed upstream, not yet pulled into this repo)",
|
|
11
|
+
"type": "claimed_upstream_not_verified_in_repo"
|
|
12
|
+
},
|
|
13
|
+
"measurement_protocol": {
|
|
14
|
+
"corpus": "bench/corpus/* (25-prompt corpus owned by step-4-measurement-and-benchmark.md)",
|
|
15
|
+
"tracker": "scripts/cost/track.mjs",
|
|
16
|
+
"pricing": "bench/pricing.yaml",
|
|
17
|
+
"session_source": "~/.claude/projects/*/sessions/*.jsonl (Claude Code-native, no manual tracking)",
|
|
18
|
+
"tokens_to_dollars": "track.mjs multiplies input/output/cache-read/cache-write tokens by per-1M pricing from bench/pricing.yaml, separated by model id",
|
|
19
|
+
"headline_output": "average dollar cost per 25-prompt run, with min / max / p50 / p90 across N reports"
|
|
20
|
+
},
|
|
21
|
+
"current_window": {
|
|
22
|
+
"report_count": 0,
|
|
23
|
+
"verdict": "awaiting_first_corpus_run",
|
|
24
|
+
"notes": "Phase 1-5 of step-11 delivered the cost-tracking and bench infrastructure. Phase 6 Step 2 awaits the first end-to-end 25-prompt corpus run against the live tracker. Until then this file exists as a methodology contract, not a verdict surface."
|
|
25
|
+
},
|
|
26
|
+
"soak_inheritance": {
|
|
27
|
+
"follows": "docs/parity/bench.json",
|
|
28
|
+
"min_days": 60,
|
|
29
|
+
"min_reports": 30,
|
|
30
|
+
"earliest_flip": "2026-07-15",
|
|
31
|
+
"arbiter_command": "task bench:baseline-ready",
|
|
32
|
+
"notes": "bench-ruflo.json flips status to 'baseline_ready' only after the parent bench.json flips. No independent soak window — same corpus, same arbiter."
|
|
33
|
+
},
|
|
34
|
+
"redundancy_verdict": {
|
|
35
|
+
"status": "pending",
|
|
36
|
+
"criterion": "Once bench.json soak completes, this verdict is set by comparing the dollar cost in current_window vs ruflo's published table.",
|
|
37
|
+
"outcome_branches": {
|
|
38
|
+
"redundant": "Our cost-per-25-prompt-run sits within Ruflo's published range (or beats it). G5 redundancy gate row for cost surface flips green.",
|
|
39
|
+
"behind": "Our cost-per-run > Ruflo's. Follow-up issue filed; G5 stays open."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"fields_pending_first_run": [
|
|
43
|
+
"current_window.avg_cost_per_run_usd",
|
|
44
|
+
"current_window.cost_by_model.haiku_usd",
|
|
45
|
+
"current_window.cost_by_model.sonnet_usd",
|
|
46
|
+
"current_window.cost_by_model.opus_usd",
|
|
47
|
+
"current_window.cost_by_class.input_usd",
|
|
48
|
+
"current_window.cost_by_class.output_usd",
|
|
49
|
+
"current_window.cost_by_class.cache_read_usd",
|
|
50
|
+
"current_window.cost_by_class.cache_write_usd"
|
|
51
|
+
],
|
|
52
|
+
"decisions_pending": {},
|
|
53
|
+
"_meta": {
|
|
54
|
+
"created": "2026-05-16",
|
|
55
|
+
"created_by": "step-11-ruflo-parity.md Phase 6 Step 2",
|
|
56
|
+
"spec": "scripts/cost/track.mjs --bench-ruflo (planned wiring); for now the file is a methodology contract"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "parity-bench-v1",
|
|
3
|
+
"status": "soak_in_progress",
|
|
4
|
+
"owner_roadmap": "agents/roadmaps/step-4-measurement-and-benchmark.md",
|
|
5
|
+
"contract": "docs/contracts/measurement-baseline.md",
|
|
6
|
+
"soak": {
|
|
7
|
+
"start_date": "2026-05-16",
|
|
8
|
+
"min_days": 60,
|
|
9
|
+
"min_reports": 30,
|
|
10
|
+
"earliest_flip": "2026-07-15",
|
|
11
|
+
"arbiter_command": "task bench:baseline-ready"
|
|
12
|
+
},
|
|
13
|
+
"current_window": {
|
|
14
|
+
"report_count": 1,
|
|
15
|
+
"days_elapsed": 0,
|
|
16
|
+
"verdict": "warmup",
|
|
17
|
+
"notes": "First infrastructure-shakedown run only. Numbers below are not the baseline."
|
|
18
|
+
},
|
|
19
|
+
"shakedown_run": {
|
|
20
|
+
"report": "bench/reports/2026-05-16T06-13-07Z-dev-projection.md",
|
|
21
|
+
"corpus": "dev",
|
|
22
|
+
"selection_accuracy_augment": 0.5,
|
|
23
|
+
"selection_accuracy_claude": 0.5,
|
|
24
|
+
"projection_fidelity_claude": 1.0,
|
|
25
|
+
"projection_fidelity_cursor": "not_applicable",
|
|
26
|
+
"projection_fidelity_cline": "not_applicable",
|
|
27
|
+
"projection_fidelity_windsurf": "not_applicable"
|
|
28
|
+
},
|
|
29
|
+
"decisions_pending": {
|
|
30
|
+
"compression_default": {
|
|
31
|
+
"kill_criterion": "docs/contracts/compression-default-kill-criterion.md",
|
|
32
|
+
"verdict": "deferred_until_baseline_closes",
|
|
33
|
+
"decision_owner": "step-4 closeout phase"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"downstream_consumers": [
|
|
37
|
+
"agents/roadmaps/step-99-north-star-restructure.md#G1",
|
|
38
|
+
"agents/roadmaps/step-2-skill-inventory-rationalization.md#G0",
|
|
39
|
+
"docs/contracts/compression-default-kill-criterion.md"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Parity verdict — Ruflo
|
|
2
|
+
|
|
3
|
+
> Per-row verdict against the eight Ruflo measurement-governance patterns
|
|
4
|
+
> catalogued in
|
|
5
|
+
> [`external-findings.md § 2`](../../agents/audit-2026-05-14-north-star/external-findings.md).
|
|
6
|
+
> Owner roadmap: [`step-11-ruflo-parity.md`](../../agents/roadmaps/step-11-ruflo-parity.md)
|
|
7
|
+
> (Phase 6 Step 1). Cross-index lives at
|
|
8
|
+
> [`step-99-north-star-restructure.md`](../../agents/roadmaps/step-99-north-star-restructure.md)
|
|
9
|
+
> Phase 5 Step 2.
|
|
10
|
+
>
|
|
11
|
+
> **Verdict legend:** `[x] covered by <file:line>` · `[~] superseded by <approach>` · `[!] gap`.
|
|
12
|
+
> **Acceptance:** zero `[!]` rows. Closure flips the corresponding cell in the
|
|
13
|
+
> [composite scorecard](../../agents/audit-2026-05-14-north-star/external-findings.md#5-composite-scorecard--agent-config-vs-the-field)
|
|
14
|
+
> `vs Ruflo` column from `–` to `=` or `+`.
|
|
15
|
+
|
|
16
|
+
**Measured-vs-claimed disclaimer:** Each row cites the **mechanism** that
|
|
17
|
+
covers Ruflo's pattern. Numbers attached to those mechanisms (cost figures,
|
|
18
|
+
smoke baselines, ADR count) are claimed until the 25-prompt bench corpus
|
|
19
|
+
soak in [`bench.json`](bench.json) flips from `warmup` to `baseline_ready`
|
|
20
|
+
(min 60 days, ≥ 30 reports — earliest 2026-07-15).
|
|
21
|
+
|
|
22
|
+
## Verdict table
|
|
23
|
+
|
|
24
|
+
| # | Ruflo pattern | Verdict | Evidence |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| 1 | **Cost-tracker plugin** — real model pricing, per-1M, separated input/output/cache | `[x] covered by` | [`scripts/cost/track.mjs`](../../scripts/cost/track.mjs) + [`bench/pricing.yaml`](../../bench/pricing.yaml) (Haiku/Sonnet/Opus per-1M, input/output/cache-read/cache-write split). Step-11 Phase 1. |
|
|
27
|
+
| 2 | **Auto-capture from session jsonl** — reads Claude Code log, no manual tracking | `[x] covered by` | [`scripts/cost/track.mjs`](../../scripts/cost/track.mjs) reads `~/.claude/projects/*/sessions/*.jsonl` automatically. Step-11 Phase 1 Step 1. |
|
|
28
|
+
| 3 | **50/75/90/100 % budget ladder with hard stop** | `[x] covered by` | [`scripts/cost/budget.mjs`](../../scripts/cost/budget.mjs) — exit codes 0/1/2/3 per tier; opt-in fail-closed via `cost.enforcement` setting. Fixtures: `tests/fixtures/cost/budget/{under-50,at-100,over-100}/`. Step-11 Phase 2. |
|
|
29
|
+
| 4 | **Measured-vs-claimed disclaimer** — every percentage tagged "claimed upstream" | `[x] covered by` | One-line `**Measured-vs-claimed disclaimer:**` header block on all 9 active roadmaps in `agents/roadmaps/`. Verified 2026-05-16. Step-11 Phase 5 Step 4. |
|
|
30
|
+
| 5 | **Smoke test as contract** — `bash scripts/smoke.sh` with declared baseline | `[x] covered by` | Four per-tier smoke scripts: [`scripts/smoke/kernel.sh`](../../scripts/smoke/kernel.sh), [`router.sh`](../../scripts/smoke/router.sh), [`schema.sh`](../../scripts/smoke/schema.sh), [`skills.sh`](../../scripts/smoke/skills.sh). Declared baselines in [`docs/contracts/smoke-contracts.md`](../contracts/smoke-contracts.md). CI gate: [`.github/workflows/smoke.yml`](../../.github/workflows/smoke.yml). Step-11 Phase 3. |
|
|
31
|
+
| 6 | **Per-plugin ADR directory** — `docs/adrs/0001-*.md` co-located with subsystem | `[x] covered by` | Six bootstrap ADRs under [`docs/adrs/{cost,memory,router,schema,smoke,caveman}/`](../adrs/). Coverage gate: [`scripts/audit_adr_coverage.py`](../../scripts/audit_adr_coverage.py) (`task lint-adr-coverage`). Contract: [`docs/contracts/adr-layout.md`](../contracts/adr-layout.md). Step-11 Phase 4. |
|
|
32
|
+
| 7 | **Namespace contract** — `<stem>-<intent>` kebab-case, reserved-names list | `[x] covered by` | [`scripts/lint_namespace.py`](../../scripts/lint_namespace.py) enforces shape + length floors + reserved-names + skill-dir-matches-name across 430 names · 0 issues. Contract: [`docs/contracts/namespace.md`](../contracts/namespace.md). CI gate: `task lint-namespace`. Step-11 Phase 5 Step 1. |
|
|
33
|
+
| 8 | **Topology choices in swarm** — `hierarchical / mesh / star / adaptive` with anti-drift defaults | `[x] covered by` | [`.agent-src.uncompressed/skills/subagent-orchestration/SKILL.md`](../../.agent-src.uncompressed/skills/subagent-orchestration/SKILL.md) `Topology hints` subsection — 7-row table mapping each mode to topology + Ruflo anti-drift default (`hierarchical, 6–8 agents, raft consensus`). Step-11 Phase 5 Step 2. |
|
|
34
|
+
| 9 | **MCP-tool count + source-line refs** — every tool with `<file>:<line>` citation | `[x] covered by` | [`docs/contracts/mcp-tool-inventory.md`](../contracts/mcp-tool-inventory.md) — 20 tools (9 stdio-implemented · 11 discovery stubs) each with catalog `<file>:<line>` + handler `<file>:<line>`. Generator: [`scripts/audit_mcp_tools.py`](../../scripts/audit_mcp_tools.py). CI drift gate: `task lint-mcp-inventory`. Step-11 Phase 5 Step 3. |
|
|
35
|
+
|
|
36
|
+
## Open `[!]` rows
|
|
37
|
+
|
|
38
|
+
**Zero.** Every Ruflo pattern is mechanism-covered. Numbers behind those
|
|
39
|
+
mechanisms remain claimed until [`bench.json`](bench.json) soak completes
|
|
40
|
+
(see disclaimer above).
|
|
41
|
+
|
|
42
|
+
## Cross-references
|
|
43
|
+
|
|
44
|
+
- Composite scorecard refresh: owned by [`step-99-north-star-restructure.md`](../../agents/roadmaps/step-99-north-star-restructure.md) Phase 5 Step 4 (replaces [`external-findings.md § 5`](../../agents/audit-2026-05-14-north-star/external-findings.md)).
|
|
45
|
+
- Bench-ruflo redundancy verdict: [`bench-ruflo.json`](bench-ruflo.json) (step-11 Phase 6 Step 2).
|
|
46
|
+
- G5 redundancy gate cite: step-99 Acceptance Criteria row "G5 — external redundancy (Domination Mandate)".
|
package/docs/profiles.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Six entry paths — by `profile.id`
|
|
2
|
+
|
|
3
|
+
Each block below is the first-screen for one shipped profile. The
|
|
4
|
+
`profile.id` written by `/onboard` selects the anchor; the block names
|
|
5
|
+
the audience, the first three things the agent does for that role,
|
|
6
|
+
and the exact commands and skills wired into the profile YAML at
|
|
7
|
+
[`.agent-src.uncompressed/profiles/<id>.yml`](../.agent-src.uncompressed/profiles/).
|
|
8
|
+
|
|
9
|
+
The summary table at the top of [`README.md`](../README.md) is the
|
|
10
|
+
one-page index; the prose below is the deep version.
|
|
11
|
+
|
|
12
|
+
<a id="profile-developer"></a>
|
|
13
|
+
## 👩💻 `developer` — IC engineer
|
|
14
|
+
|
|
15
|
+
Implement a ticket end-to-end, fix CI red, run a self-review before
|
|
16
|
+
the PR. `/implement-ticket` refines the ticket, plans, edits, tests,
|
|
17
|
+
and verifies; `/work` is the free-form sibling; `/review-changes`
|
|
18
|
+
dispatches five judges (bug, security, tests, quality, architecture)
|
|
19
|
+
on the local diff. Stack-aware skills cover Laravel · Symfony ·
|
|
20
|
+
Next.js · React · Node. **Preset default: `balanced`.**
|
|
21
|
+
[Profile YAML](../.agent-src.uncompressed/profiles/developer.yml) ·
|
|
22
|
+
[Role guide](getting-started-by-role.md#developer-the-original-audience).
|
|
23
|
+
|
|
24
|
+
<a id="profile-content_creator"></a>
|
|
25
|
+
## ✍️ `content_creator` — writers, ghostwriters, marketers
|
|
26
|
+
|
|
27
|
+
Draft in someone else's voice, plan a quarter of content, ship a
|
|
28
|
+
launch announcement. `/ghostwriter` fetches and writes against a
|
|
29
|
+
public-figure voice profile; `/post-as` is the same primitive for
|
|
30
|
+
your own voice (`.agent-user.md`); `voice-and-tone-design` and
|
|
31
|
+
`messaging-architecture` lock the brand frame before any copy ships.
|
|
32
|
+
**Preset default: `balanced`.**
|
|
33
|
+
[Profile YAML](../.agent-src.uncompressed/profiles/content_creator.yml) ·
|
|
34
|
+
[Role guide](getting-started-by-role.md#creator-writer-marketer-indie-content-shop).
|
|
35
|
+
|
|
36
|
+
<a id="profile-founder"></a>
|
|
37
|
+
## 🚀 `founder` — solo / early-stage founder
|
|
38
|
+
|
|
39
|
+
Sharpen a fuzzy idea, rank what to build, write the why-now slide.
|
|
40
|
+
`/challenge-me` runs a grill-style interview that turns a vague plan
|
|
41
|
+
into a copyable pitch; `/council` polls external AIs for a neutral
|
|
42
|
+
second opinion; `rice-prioritization` ranks the backlog;
|
|
43
|
+
`vision-articulation` and `fundraising-narrative` shape the
|
|
44
|
+
internal-vs-external story. **Preset default: `fast`.**
|
|
45
|
+
[Profile YAML](../.agent-src.uncompressed/profiles/founder.yml) ·
|
|
46
|
+
[Role guide](getting-started-by-role.md#founder-early-stage-operator-wearing-every-hat).
|
|
47
|
+
|
|
48
|
+
<a id="profile-agency"></a>
|
|
49
|
+
## 🏛 `agency` — multi-client delivery shop
|
|
50
|
+
|
|
51
|
+
Refine a fuzzy client ask into an estimated, AC-tight ticket; turn a
|
|
52
|
+
phase into a roadmap; ship per client without losing decision
|
|
53
|
+
provenance. `/refine-ticket` rewrites the ticket and surfaces top-5
|
|
54
|
+
risks; `estimate-ticket` sizes and splits; `decision-record` anchors
|
|
55
|
+
the trade-off in an ADR before code starts. **Preset default:
|
|
56
|
+
`strict`.**
|
|
57
|
+
[Profile YAML](../.agent-src.uncompressed/profiles/agency.yml) ·
|
|
58
|
+
[Role guide](getting-started-by-role.md#consultant-advisory-freelance-fractional).
|
|
59
|
+
|
|
60
|
+
<a id="profile-finance"></a>
|
|
61
|
+
## 💼 `finance` — CFO / fractional finance / FP&A
|
|
62
|
+
|
|
63
|
+
Build a DCF, stress-test the plan, frame the runway call. `dcf-modeling`
|
|
64
|
+
walks the WACC / terminal-value / 5-year-hold reasoning; `forecasting`
|
|
65
|
+
reconciles top-down vs bottom-up; `scenario-modeling` produces the
|
|
66
|
+
base / upside / downside cuts; `runway-cognition` frames the
|
|
67
|
+
fundraise-vs-cut-vs-grow decision. **Preset default: `strict`.**
|
|
68
|
+
[Profile YAML](../.agent-src.uncompressed/profiles/finance.yml) ·
|
|
69
|
+
[Role guide](getting-started-by-role.md#finance--ops-cfo-controller-ops-lead-founder-finance).
|
|
70
|
+
|
|
71
|
+
<a id="profile-ops"></a>
|
|
72
|
+
## 🛡 `ops` — RevOps, support, SRE-adjacent
|
|
73
|
+
|
|
74
|
+
Threat-model a change before it ships, command the incident when it
|
|
75
|
+
breaks, build the dashboard that catches it next time.
|
|
76
|
+
`/threat-model` enumerates abuse cases and trust boundaries before
|
|
77
|
+
the first line of code; `incident-commander` frames severity and
|
|
78
|
+
post-mortem; `dashboard-design` chooses the right RED / USE / Golden
|
|
79
|
+
Signal panel. **Preset default: `strict`.**
|
|
80
|
+
[Profile YAML](../.agent-src.uncompressed/profiles/ops.yml) ·
|
|
81
|
+
[Role guide](getting-started-by-role.md#finance--ops-cfo-controller-ops-lead-founder-finance).
|
|
82
|
+
|
|
83
|
+
> **Universal AI Agent OS, not "for developers only".** The same
|
|
84
|
+
> orchestration core also drives non-software trades. Worked-example
|
|
85
|
+
> user types ship for [galabau](../.agent-src.uncompressed/user-types/galabau-field-crew.md),
|
|
86
|
+
> [metalworking](../.agent-src.uncompressed/user-types/metalworking-shop.md),
|
|
87
|
+
> [truck driving](../.agent-src.uncompressed/user-types/truck-driver.md) —
|
|
88
|
+
> with a [scaffold](../.agent-src.uncompressed/user-types/_template/) for
|
|
89
|
+
> contributing your own.
|
|
90
|
+
|
|
91
|
+
→ [Public catalog](catalog.md) (all rules, skills, commands, guidelines) · [Skills only](skills-catalog.md) · [llms.txt](../llms.txt)
|
package/package.json
CHANGED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""``agent-config explain`` — print the decision chain behind an outcome.
|
|
2
|
+
|
|
3
|
+
Step-15 Phase 1 item 3. Answers the silent "why did the agent do that?"
|
|
4
|
+
question by showing which inputs the loader / router consulted, in what
|
|
5
|
+
order, and which one won. Read-only; never edits state, never dispatches
|
|
6
|
+
network calls. Three subjects in the v1 surface:
|
|
7
|
+
|
|
8
|
+
* ``config`` — full resolution chain for the active profile +
|
|
9
|
+
preset. Uses :mod:`scripts.config.profiles` and
|
|
10
|
+
:mod:`scripts.config.presets`; surfaces source
|
|
11
|
+
(pack / profile / user / env / runtime / default)
|
|
12
|
+
and per-knob overrides.
|
|
13
|
+
* ``rule <name>`` — kernel vs tier-1 vs tier-2 placement plus the
|
|
14
|
+
declared trigger list from ``router.json``.
|
|
15
|
+
* ``route <text>`` — given prompt text, returns every tier-1 rule
|
|
16
|
+
whose trigger list matches plus kernel rules
|
|
17
|
+
(always active).
|
|
18
|
+
|
|
19
|
+
Exit codes: ``0`` clean, ``1`` not found / no match, ``2`` invocation
|
|
20
|
+
error (bad project root, malformed ``router.json``).
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
from scripts._lib.agent_settings import (
|
|
32
|
+
DEFAULT_PROJECT_FILE,
|
|
33
|
+
ProjectRootError,
|
|
34
|
+
load_agent_settings,
|
|
35
|
+
resolve_project_root,
|
|
36
|
+
)
|
|
37
|
+
from scripts.config import presets, profiles
|
|
38
|
+
|
|
39
|
+
ROUTER_FILENAME = "router.json"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _resolve_root(arg: str | None) -> tuple[Path, str]:
|
|
43
|
+
try:
|
|
44
|
+
return resolve_project_root(arg, cwd=Path.cwd())
|
|
45
|
+
except ProjectRootError as exc:
|
|
46
|
+
print(f"❌ explain: {exc}", file=sys.stderr)
|
|
47
|
+
raise SystemExit(2) from exc
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _load_user_settings(project_root: Path) -> dict[str, Any]:
|
|
51
|
+
path = project_root / DEFAULT_PROJECT_FILE
|
|
52
|
+
if not path.exists():
|
|
53
|
+
return {}
|
|
54
|
+
return load_agent_settings(project_path=path) or {}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _load_router(project_root: Path) -> dict[str, Any]:
|
|
58
|
+
path = project_root / ROUTER_FILENAME
|
|
59
|
+
if not path.exists():
|
|
60
|
+
return {}
|
|
61
|
+
try:
|
|
62
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
63
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
64
|
+
print(f"❌ explain: cannot read {path}: {exc}", file=sys.stderr)
|
|
65
|
+
raise SystemExit(2) from exc
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _explain_config(project_root: Path, *, as_json: bool) -> int:
|
|
69
|
+
settings = _load_user_settings(project_root)
|
|
70
|
+
resolved_profile = profiles.resolve_profile(
|
|
71
|
+
project_root=project_root,
|
|
72
|
+
user_settings=settings,
|
|
73
|
+
)
|
|
74
|
+
resolved_preset = presets.resolve_preset(
|
|
75
|
+
project_root=project_root,
|
|
76
|
+
user_settings=settings,
|
|
77
|
+
profile_preset_id=resolved_profile.preset_id,
|
|
78
|
+
)
|
|
79
|
+
payload = {
|
|
80
|
+
"project_root": str(project_root),
|
|
81
|
+
"profile": {
|
|
82
|
+
"id": resolved_profile.id,
|
|
83
|
+
"source": resolved_profile.source,
|
|
84
|
+
"preset_id": resolved_profile.preset_id,
|
|
85
|
+
"warning": resolved_profile.warning,
|
|
86
|
+
},
|
|
87
|
+
"preset": {
|
|
88
|
+
"id": resolved_preset.id,
|
|
89
|
+
"source": resolved_preset.source,
|
|
90
|
+
"overrides": list(resolved_preset.overrides),
|
|
91
|
+
"knobs": resolved_preset.knobs,
|
|
92
|
+
},
|
|
93
|
+
"env": {
|
|
94
|
+
profiles.PROFILE_ID_ENV: os.environ.get(profiles.PROFILE_ID_ENV),
|
|
95
|
+
presets.PRESET_ID_ENV: os.environ.get(presets.PRESET_ID_ENV),
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
if as_json:
|
|
99
|
+
json.dump(payload, sys.stdout, indent=2, sort_keys=True)
|
|
100
|
+
sys.stdout.write("\n")
|
|
101
|
+
return 0
|
|
102
|
+
print(f" 📍 project_root: {project_root}")
|
|
103
|
+
print()
|
|
104
|
+
print(f" profile.id: {resolved_profile.id} (source: {resolved_profile.source})")
|
|
105
|
+
if resolved_profile.warning:
|
|
106
|
+
print(f" ⚠️ {resolved_profile.warning}")
|
|
107
|
+
print(f" preset.id: {resolved_preset.id} (source: {resolved_preset.source})")
|
|
108
|
+
if resolved_preset.overrides:
|
|
109
|
+
print(f" overrides: {', '.join(resolved_preset.overrides)}")
|
|
110
|
+
cost = resolved_preset.knobs.get("cost", {})
|
|
111
|
+
if cost:
|
|
112
|
+
print(
|
|
113
|
+
f" cost caps: daily ${cost.get('daily_max_usd')} · "
|
|
114
|
+
f"weekly ${cost.get('weekly_max_usd')} · "
|
|
115
|
+
f"monthly ${cost.get('monthly_max_usd')}",
|
|
116
|
+
)
|
|
117
|
+
autonomy = resolved_preset.knobs.get("autonomy", {})
|
|
118
|
+
if autonomy:
|
|
119
|
+
print(f" autonomy: default={autonomy.get('default')}")
|
|
120
|
+
return 0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _find_rule(router: dict[str, Any], name: str) -> tuple[str, dict[str, Any]] | None:
|
|
124
|
+
if name in router.get("kernel", []):
|
|
125
|
+
return "kernel", {"id": name, "triggers": [{"always": True}]}
|
|
126
|
+
for tier in ("tier_1", "tier_2"):
|
|
127
|
+
for entry in router.get(tier, []):
|
|
128
|
+
if entry.get("id") == name:
|
|
129
|
+
return tier, entry
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _explain_rule(project_root: Path, name: str, *, as_json: bool) -> int:
|
|
134
|
+
router = _load_router(project_root)
|
|
135
|
+
found = _find_rule(router, name)
|
|
136
|
+
if found is None:
|
|
137
|
+
print(f"❌ explain: rule {name!r} not found in router", file=sys.stderr)
|
|
138
|
+
return 1
|
|
139
|
+
tier, entry = found
|
|
140
|
+
payload = {"rule": name, "tier": tier, "entry": entry}
|
|
141
|
+
if as_json:
|
|
142
|
+
json.dump(payload, sys.stdout, indent=2, sort_keys=True)
|
|
143
|
+
sys.stdout.write("\n")
|
|
144
|
+
return 0
|
|
145
|
+
print(f" rule: {name}")
|
|
146
|
+
print(f" tier: {tier}")
|
|
147
|
+
triggers = entry.get("triggers") or []
|
|
148
|
+
print(f" triggers ({len(triggers)}):")
|
|
149
|
+
for trig in triggers:
|
|
150
|
+
print(f" · {trig}")
|
|
151
|
+
routes = entry.get("routes_to") or []
|
|
152
|
+
if routes:
|
|
153
|
+
print(f" routes_to: {', '.join(routes)}")
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _matches_trigger(trigger: dict[str, Any], text: str, lowered: str) -> str | None:
|
|
158
|
+
"""Return a human-readable match reason, or ``None`` for no match."""
|
|
159
|
+
if "keyword" in trigger:
|
|
160
|
+
kw = str(trigger["keyword"]).lower()
|
|
161
|
+
if kw and kw in lowered:
|
|
162
|
+
return f"keyword: {kw}"
|
|
163
|
+
if "phrase" in trigger:
|
|
164
|
+
ph = str(trigger["phrase"]).lower()
|
|
165
|
+
if ph and ph in lowered:
|
|
166
|
+
return f"phrase: {ph}"
|
|
167
|
+
if "path_prefix" in trigger:
|
|
168
|
+
prefix = str(trigger["path_prefix"])
|
|
169
|
+
if prefix and prefix in text:
|
|
170
|
+
return f"path_prefix: {prefix}"
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _explain_route(project_root: Path, text: str, *, as_json: bool) -> int:
|
|
175
|
+
router = _load_router(project_root)
|
|
176
|
+
lowered = text.lower()
|
|
177
|
+
matches: list[dict[str, Any]] = []
|
|
178
|
+
for entry in router.get("tier_1", []):
|
|
179
|
+
for trig in entry.get("triggers", []) or []:
|
|
180
|
+
reason = _matches_trigger(trig, text, lowered)
|
|
181
|
+
if reason is not None:
|
|
182
|
+
matches.append({
|
|
183
|
+
"id": entry["id"], "tier": "tier_1", "reason": reason,
|
|
184
|
+
})
|
|
185
|
+
break
|
|
186
|
+
payload = {
|
|
187
|
+
"input": text,
|
|
188
|
+
"kernel_always": list(router.get("kernel", [])),
|
|
189
|
+
"tier_1_matches": matches,
|
|
190
|
+
}
|
|
191
|
+
if as_json:
|
|
192
|
+
json.dump(payload, sys.stdout, indent=2, sort_keys=True)
|
|
193
|
+
sys.stdout.write("\n")
|
|
194
|
+
return 0
|
|
195
|
+
print(f" input: {text!r}")
|
|
196
|
+
print()
|
|
197
|
+
print(f" kernel (always active, {len(payload['kernel_always'])}):")
|
|
198
|
+
for kid in payload["kernel_always"]:
|
|
199
|
+
print(f" · {kid}")
|
|
200
|
+
print()
|
|
201
|
+
print(f" tier-1 matches ({len(matches)}):")
|
|
202
|
+
if not matches:
|
|
203
|
+
print(" · (no trigger matched — only kernel rules active)")
|
|
204
|
+
return 1
|
|
205
|
+
for match in matches:
|
|
206
|
+
print(f" · {match['id']} ({match['reason']})")
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def main(argv: list[str] | None = None) -> int:
|
|
211
|
+
parser = argparse.ArgumentParser(
|
|
212
|
+
prog="agent-config explain",
|
|
213
|
+
description=(
|
|
214
|
+
"Print the decision chain behind a configuration or routing "
|
|
215
|
+
"outcome. Read-only; no network calls."
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
parser.add_argument(
|
|
219
|
+
"subject", choices=("config", "rule", "route"),
|
|
220
|
+
help="what to explain",
|
|
221
|
+
)
|
|
222
|
+
parser.add_argument(
|
|
223
|
+
"target", nargs="?", default=None,
|
|
224
|
+
help="rule name (for 'rule') or prompt text (for 'route')",
|
|
225
|
+
)
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
"--project", default=None,
|
|
228
|
+
help="project root (defaults to anchor walk from cwd)",
|
|
229
|
+
)
|
|
230
|
+
parser.add_argument(
|
|
231
|
+
"--json", action="store_true", dest="as_json",
|
|
232
|
+
help="emit JSON instead of human-readable text",
|
|
233
|
+
)
|
|
234
|
+
opts = parser.parse_args(argv)
|
|
235
|
+
project_root, _origin = _resolve_root(opts.project)
|
|
236
|
+
if opts.subject == "config":
|
|
237
|
+
return _explain_config(project_root, as_json=opts.as_json)
|
|
238
|
+
if opts.target is None:
|
|
239
|
+
print(
|
|
240
|
+
f"❌ explain: '{opts.subject}' requires a target argument",
|
|
241
|
+
file=sys.stderr,
|
|
242
|
+
)
|
|
243
|
+
return 2
|
|
244
|
+
if opts.subject == "rule":
|
|
245
|
+
return _explain_rule(project_root, opts.target, as_json=opts.as_json)
|
|
246
|
+
return _explain_route(project_root, opts.target, as_json=opts.as_json)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__": # pragma: no cover
|
|
250
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Cost capture for `scripts/bench_run.py` — step-4 Phase 2 Step 2.
|
|
2
|
+
#
|
|
3
|
+
# Reads Claude Code session jsonl summaries (one summary line per session)
|
|
4
|
+
# from agents/cost-tracking/sessions.jsonl — produced by scripts/cost/track.mjs
|
|
5
|
+
# — and aggregates totals using model rates from bench/pricing.yaml.
|
|
6
|
+
#
|
|
7
|
+
# Returns the dict shape declared in docs/contracts/benchmark-report-schema.md
|
|
8
|
+
# § JSON schema (v1) `cost`. When the source jsonl is missing, returns the
|
|
9
|
+
# `unavailable` sentinel block (NEVER silently drops, per schema invariant).
|
|
10
|
+
"""Cost capture helper for the bench runner."""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import yaml
|
|
19
|
+
except ImportError: # pragma: no cover — bench_run handles the same import
|
|
20
|
+
yaml = None # type: ignore[assignment]
|
|
21
|
+
|
|
22
|
+
UNKNOWN_TIER = "unknown"
|
|
23
|
+
TIER_KEYS = ("haiku", "sonnet", "opus", UNKNOWN_TIER)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_pricing(pricing_path: Path) -> tuple[dict[str, dict[str, float]], str | None]:
|
|
27
|
+
"""Return ({tier: rates}, oldest_sourced_on) from bench/pricing.yaml."""
|
|
28
|
+
if yaml is None or not pricing_path.is_file():
|
|
29
|
+
return {}, None
|
|
30
|
+
data = yaml.safe_load(pricing_path.read_text(encoding="utf-8")) or {}
|
|
31
|
+
rates: dict[str, dict[str, float]] = {}
|
|
32
|
+
oldest: str | None = None
|
|
33
|
+
for row in data.get("models", []):
|
|
34
|
+
tier = row.get("tier")
|
|
35
|
+
if not tier:
|
|
36
|
+
continue
|
|
37
|
+
rates[tier] = {
|
|
38
|
+
"input": float(row.get("input", 0.0)),
|
|
39
|
+
"output": float(row.get("output", 0.0)),
|
|
40
|
+
"cache_write": float(row.get("cache_write", 0.0)),
|
|
41
|
+
"cache_read": float(row.get("cache_read", 0.0)),
|
|
42
|
+
}
|
|
43
|
+
sourced = row.get("sourced_on")
|
|
44
|
+
# YAML 1.1 parses ISO dates to datetime.date; coerce to ISO string.
|
|
45
|
+
if sourced is not None and not isinstance(sourced, str):
|
|
46
|
+
sourced = sourced.isoformat() if hasattr(sourced, "isoformat") else str(sourced)
|
|
47
|
+
if isinstance(sourced, str) and (oldest is None or sourced < oldest):
|
|
48
|
+
oldest = sourced
|
|
49
|
+
return rates, oldest
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _empty_totals() -> dict[str, int | float]:
|
|
53
|
+
return {
|
|
54
|
+
"input_tokens": 0,
|
|
55
|
+
"output_tokens": 0,
|
|
56
|
+
"cache_read_input_tokens": 0,
|
|
57
|
+
"cache_creation_input_tokens": 0,
|
|
58
|
+
"total_cost_usd": 0.0,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _empty_per_tier() -> dict[str, dict[str, int | float]]:
|
|
63
|
+
return {t: {"messages": 0, "cost_usd": 0.0} for t in TIER_KEYS}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def unavailable_block(reason: str, source: str, pricing_sourced_on: str | None) -> dict[str, Any]:
|
|
67
|
+
"""Schema-compliant `cost` block when no session jsonl is readable."""
|
|
68
|
+
return {
|
|
69
|
+
"source": "unavailable",
|
|
70
|
+
"reason": reason,
|
|
71
|
+
"scanned_path": source,
|
|
72
|
+
"sessions_scanned": 0,
|
|
73
|
+
"totals": _empty_totals(),
|
|
74
|
+
"per_tier": _empty_per_tier(),
|
|
75
|
+
"pricing_sourced_on": pricing_sourced_on,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def aggregate_sessions(
|
|
80
|
+
sessions_jsonl: Path,
|
|
81
|
+
pricing_path: Path,
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
|
+
"""Read agents/cost-tracking/sessions.jsonl and aggregate per-tier totals."""
|
|
84
|
+
rates, pricing_sourced_on = load_pricing(pricing_path)
|
|
85
|
+
if not sessions_jsonl.is_file():
|
|
86
|
+
return unavailable_block(
|
|
87
|
+
reason="sessions_jsonl_missing",
|
|
88
|
+
source=str(sessions_jsonl),
|
|
89
|
+
pricing_sourced_on=pricing_sourced_on,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
totals = _empty_totals()
|
|
93
|
+
per_tier = _empty_per_tier()
|
|
94
|
+
sessions_scanned = 0
|
|
95
|
+
|
|
96
|
+
for line in sessions_jsonl.read_text(encoding="utf-8").splitlines():
|
|
97
|
+
if not line.strip():
|
|
98
|
+
continue
|
|
99
|
+
try:
|
|
100
|
+
summary = json.loads(line)
|
|
101
|
+
except json.JSONDecodeError:
|
|
102
|
+
continue
|
|
103
|
+
sessions_scanned += 1
|
|
104
|
+
for _model, slot in (summary.get("byModel") or {}).items():
|
|
105
|
+
tier = slot.get("tier", UNKNOWN_TIER)
|
|
106
|
+
if tier not in per_tier:
|
|
107
|
+
tier = UNKNOWN_TIER
|
|
108
|
+
totals["input_tokens"] += int(slot.get("input_tokens", 0))
|
|
109
|
+
totals["output_tokens"] += int(slot.get("output_tokens", 0))
|
|
110
|
+
totals["cache_read_input_tokens"] += int(slot.get("cache_read_input_tokens", 0))
|
|
111
|
+
totals["cache_creation_input_tokens"] += int(slot.get("cache_creation_input_tokens", 0))
|
|
112
|
+
cost = float(slot.get("cost_usd", 0.0))
|
|
113
|
+
# Recompute from rates if upstream cost is zero AND we have rates;
|
|
114
|
+
# otherwise trust the upstream attribution (it priced at capture time).
|
|
115
|
+
if cost == 0.0 and tier in rates:
|
|
116
|
+
r = rates[tier]
|
|
117
|
+
cost = (
|
|
118
|
+
int(slot.get("input_tokens", 0)) / 1e6 * r["input"]
|
|
119
|
+
+ int(slot.get("output_tokens", 0)) / 1e6 * r["output"]
|
|
120
|
+
+ int(slot.get("cache_creation_input_tokens", 0)) / 1e6 * r["cache_write"]
|
|
121
|
+
+ int(slot.get("cache_read_input_tokens", 0)) / 1e6 * r["cache_read"]
|
|
122
|
+
)
|
|
123
|
+
per_tier[tier]["messages"] += int(slot.get("messages", 0))
|
|
124
|
+
per_tier[tier]["cost_usd"] += cost
|
|
125
|
+
totals["total_cost_usd"] += cost
|
|
126
|
+
|
|
127
|
+
# Round currency to 6 decimals for stable diffs.
|
|
128
|
+
totals["total_cost_usd"] = round(float(totals["total_cost_usd"]), 6)
|
|
129
|
+
for t in per_tier.values():
|
|
130
|
+
t["cost_usd"] = round(float(t["cost_usd"]), 6)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
"source": str(sessions_jsonl),
|
|
134
|
+
"sessions_scanned": sessions_scanned,
|
|
135
|
+
"totals": totals,
|
|
136
|
+
"per_tier": per_tier,
|
|
137
|
+
"pricing_sourced_on": pricing_sourced_on,
|
|
138
|
+
}
|