@compr/opscontext-mcp 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -602,7 +602,8 @@ import { activate, deactivate, getActivationStatus, gateCheck, } from "./activat
602
602
  import { syncTierA, syncTierB, loadCommunityStore, communityRulesToChunks, mergeWithDedup, STORE_PATH as COMMUNITY_STORE_PATH, } from "./community-sync.js";
603
603
  import { readAuditLog, verifyChain, filterByRange, toCsv, } from "./audit.js";
604
604
  import { loadRepoPolicy, parsePolicy, formatPolicySummary, formatValidationErrors, repoPolicyPath, } from "./policy.js";
605
- import { collectRuns, metricsFor, transcriptRoot, emptyTally, addTally, totalTokens, } from "./transcript-collector.js";
605
+ import { collectRuns, metricsFor, transcriptRoot, emptyTally, addTally, totalTokens, pricingStatus, pricingFor, } from "./transcript-collector.js";
606
+ import { DEFAULT_PRICING, DEFAULT_PRICING_ASOF } from "./default-pricing.js";
606
607
  import { runTranscriptHeuristics, DEFAULT_COST_THRESHOLDS, } from "./detector.js";
607
608
  import { getStagedFiles, runSecretScan, runDocCoverage, runCommitMessageRequired, runRuleParity, formatSecretViolations, formatDocCoverageViolations, formatSecretViolationsJson, formatDocCoverageViolationsJson, formatCommitMessageViolations, formatCommitMessageViolationsJson, formatRuleParityViolations, formatRuleParityViolationsJson, } from "./hooks.js";
608
609
  import { safeAppend } from "./audit.js";
@@ -2289,20 +2290,27 @@ function loadCostThresholds(cwd) {
2289
2290
  const res = loadRepoPolicy(cwd);
2290
2291
  if (res && res.ok && res.policy.agent_cost) {
2291
2292
  const a = res.policy.agent_cost;
2293
+ // [DEFAULT-RATES-SHIP-WITH-THE-PACKAGE] — an agent_cost block that omits
2294
+ // `pricing` must not silently price nothing.
2295
+ const hasOwnRates = a.pricing.length > 0;
2292
2296
  return {
2293
2297
  t: {
2294
2298
  billing_mode: a.billing_mode,
2295
- pricing: a.pricing,
2299
+ pricing: hasOwnRates ? a.pricing : DEFAULT_PRICING,
2296
2300
  min_cache_efficiency: a.min_cache_efficiency,
2297
2301
  max_tool_calls_per_agent: a.max_tool_calls_per_agent,
2298
2302
  max_cost_per_agent_usd: a.max_cost_per_agent_usd,
2299
2303
  min_fanout_for_canary: a.min_fanout_for_canary,
2300
2304
  max_failed_share: a.max_failed_share,
2301
2305
  },
2302
- source: ".contextengine/policy.json",
2306
+ source: ".contextengine/policy.json" +
2307
+ (hasOwnRates ? "" : ` (rates: built-in, as of ${DEFAULT_PRICING_ASOF})`),
2303
2308
  };
2304
2309
  }
2305
- return { t: DEFAULT_COST_THRESHOLDS, source: "built-in defaults (no agent_cost in policy.json)" };
2310
+ return {
2311
+ t: DEFAULT_COST_THRESHOLDS,
2312
+ source: `built-in defaults, rates as of ${DEFAULT_PRICING_ASOF} (no agent_cost in policy.json)`,
2313
+ };
2306
2314
  }
2307
2315
  async function cliCost(argv) {
2308
2316
  const flag = (name) => {
@@ -2355,7 +2363,17 @@ async function cliCost(argv) {
2355
2363
  let vol = emptyTally();
2356
2364
  let agents = 0, toolCalls = 0, failed = 0, capacity = 0, reported = 0;
2357
2365
  let cost = 0, withoutCache = 0, unpriced = 0;
2366
+ // Which models carried tokens but matched no rate — named in the output so
2367
+ // the fix is actionable instead of "something was unpriced".
2368
+ const unpricedModels = new Set();
2358
2369
  for (const { run, m } of scored) {
2370
+ for (const a of run.agents) {
2371
+ for (const [model, tally] of a.tokensByModel) {
2372
+ if (totalTokens(tally) > 0 && !pricingFor(model, t.pricing)) {
2373
+ unpricedModels.add(model ?? "(no model recorded)");
2374
+ }
2375
+ }
2376
+ }
2359
2377
  vol = addTally(vol, run.totals);
2360
2378
  agents += m.agents;
2361
2379
  toolCalls += m.toolCalls;
@@ -2383,12 +2401,6 @@ async function cliCost(argv) {
2383
2401
  console.log("");
2384
2402
  // ── 2. VALUED COST ──────────────────────────────────────────────────────
2385
2403
  const notional = t.billing_mode === "subscription";
2386
- console.log(`VALUED COST (API list prices)${notional ? " — NOTIONAL, NOT BILLED" : ""}`);
2387
- if (notional) {
2388
- console.log(" This machine runs Claude Code on a subscription: no dollar below is");
2389
- console.log(" debited. Use these figures to compare approaches, not as spend.");
2390
- }
2391
- const costRow = (label, n) => console.log(` ${label.padEnd(16)} ${("$" + n.toFixed(2)).padStart(8)} ${cost ? ((100 * n) / cost).toFixed(1).padStart(5) : " 0.0"}%`);
2392
2404
  let ci = 0, ccw = 0, ccr = 0, co = 0;
2393
2405
  for (const { m } of scored) {
2394
2406
  ci += m.cost.input;
@@ -2396,16 +2408,42 @@ async function cliCost(argv) {
2396
2408
  ccr += m.cost.cacheRead;
2397
2409
  co += m.cost.output;
2398
2410
  }
2399
- costRow("cache read", ccr);
2400
- costRow("cache write", ccw);
2401
- costRow("input (fresh)", ci);
2402
- costRow("output", co);
2403
- console.log(` ${"total".padEnd(16)} ${("$" + cost.toFixed(2)).padStart(8)}`);
2404
- console.log(` ${"without cache".padEnd(16)} ${("$" + withoutCache.toFixed(2)).padStart(8)} ` +
2405
- `caching saved $${(withoutCache - cost).toFixed(2)} (${withoutCache ? (100 * (1 - cost / withoutCache)).toFixed(0) : "0"}%)`);
2406
- if (unpriced > 0)
2407
- console.log(` ⚠ ${fmtTok(unpriced)} tokens UNPRICED (no matching model in policy) — not counted above`);
2408
- console.log("");
2411
+ const agg = {
2412
+ input: ci, cacheWrite: ccw, cacheRead: ccr, output: co,
2413
+ total: cost, withoutCache, unpricedTokens: unpriced,
2414
+ };
2415
+ const status = pricingStatus(agg);
2416
+ console.log(`VALUED COST (API list prices)${notional && status !== "unpriced" ? " — NOTIONAL, NOT BILLED" : ""}`);
2417
+ // [NEVER-RENDER-AN-UNKNOWN-AS-A-NUMBER] with nothing priced there is no
2418
+ // cost to show. Printing a $0.00 table here reads as "this run was free"
2419
+ // and "caching saved 0%", both false.
2420
+ if (status === "unpriced") {
2421
+ console.log(` UNPRICED — no rate matched any model in this data, so no cost can be`);
2422
+ console.log(` stated. ${fmtTok(unpriced)} tokens were moved. This is an unknown, not $0.`);
2423
+ console.log("");
2424
+ console.log(` Models seen without a rate: ${[...unpricedModels].sort().join(", ") || "(unknown)"}`);
2425
+ console.log(` Add them to .contextengine/policy.json → agent_cost.pricing.`);
2426
+ console.log("");
2427
+ }
2428
+ else {
2429
+ if (notional) {
2430
+ console.log(" This machine runs Claude Code on a subscription: no dollar below is");
2431
+ console.log(" debited. Use these figures to compare approaches, not as spend.");
2432
+ }
2433
+ const costRow = (label, n) => console.log(` ${label.padEnd(16)} ${("$" + n.toFixed(2)).padStart(8)} ${cost ? ((100 * n) / cost).toFixed(1).padStart(5) : " 0.0"}%`);
2434
+ costRow("cache read", ccr);
2435
+ costRow("cache write", ccw);
2436
+ costRow("input (fresh)", ci);
2437
+ costRow("output", co);
2438
+ console.log(` ${"total".padEnd(16)} ${("$" + cost.toFixed(2)).padStart(8)}`);
2439
+ console.log(` ${"without cache".padEnd(16)} ${("$" + withoutCache.toFixed(2)).padStart(8)} ` +
2440
+ `caching saved $${(withoutCache - cost).toFixed(2)} (${withoutCache ? (100 * (1 - cost / withoutCache)).toFixed(0) : "0"}%)`);
2441
+ if (status === "partial") {
2442
+ console.log(` ⚠ ${fmtTok(unpriced)} tokens UNPRICED and NOT in the figures above` +
2443
+ ` (${[...unpricedModels].sort().join(", ") || "unknown model"}) — the total is a floor, not the cost`);
2444
+ }
2445
+ console.log("");
2446
+ }
2409
2447
  // ── 3. INTENSITY (the capacity proxy) ───────────────────────────────────
2410
2448
  console.log(`INTENSITY (capacity proxy${notional ? " — the scarce resource here" : ""})`);
2411
2449
  console.log(` subagents ${String(agents).padStart(8)}`);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Built-in model rates, in dollars per million tokens.
3
+ *
4
+ * 🔒 LOCKED [DEFAULT-RATES-SHIP-WITH-THE-PACKAGE] — 2026-08-20
5
+ * ⛔ NEVER ship an empty default pricing table again.
6
+ * WHY: `[PRICING-LIVES-IN-POLICY]` was read as "ship no rates at all", so
7
+ * 2.5.0 shipped `pricing: []` as the default. Every user without an
8
+ * `agent_cost` block in their own policy.json got a VALUED COST panel
9
+ * reading `total $0.00` and `caching saved $0.00 (0%)` over 1.08 BILLION
10
+ * real tokens — a confident, wrong-looking verdict on the headline feature
11
+ * of the release. The LOCK's intent was "rates must be correctable without
12
+ * a release", not "the product ships priced at nothing".
13
+ * FIX: ship rates here, as DATA in their own module, never inline in the
14
+ * collector / detector / CLI. `.contextengine/policy.json` →
15
+ * `agent_cost.pricing` still wins outright when present, and this file
16
+ * compiles to plain readable JS in `dist/`, so a rate can be corrected in
17
+ * place without waiting for a release.
18
+ *
19
+ * Rates are Anthropic API list prices. Cache read is 0.1x input, cache write
20
+ * 5m is 1.25x input, cache write 1h is 2x input.
21
+ */
22
+ import type { ModelPricing } from "./transcript-collector.js";
23
+ /**
24
+ * When these rates were last checked against published pricing. Surfaced in
25
+ * `contextengine cost` output: a rate table with no date is a rate table
26
+ * nobody knows to distrust.
27
+ */
28
+ export declare const DEFAULT_PRICING_ASOF = "2026-08-20";
29
+ /**
30
+ * Longest-prefix matched, so dated ids (`claude-haiku-4-5-20251001`) resolve
31
+ * to their family. Deliberately NO `*` catch-all: a model absent from this
32
+ * table must report as UNPRICED, never be valued at a guessed rate
33
+ * (`[ABSENCE-IS-NOT-A-VERDICT]`).
34
+ */
35
+ export declare const DEFAULT_PRICING: ModelPricing[];
36
+ //# sourceMappingURL=default-pricing.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Built-in model rates, in dollars per million tokens.
3
+ *
4
+ * 🔒 LOCKED [DEFAULT-RATES-SHIP-WITH-THE-PACKAGE] — 2026-08-20
5
+ * ⛔ NEVER ship an empty default pricing table again.
6
+ * WHY: `[PRICING-LIVES-IN-POLICY]` was read as "ship no rates at all", so
7
+ * 2.5.0 shipped `pricing: []` as the default. Every user without an
8
+ * `agent_cost` block in their own policy.json got a VALUED COST panel
9
+ * reading `total $0.00` and `caching saved $0.00 (0%)` over 1.08 BILLION
10
+ * real tokens — a confident, wrong-looking verdict on the headline feature
11
+ * of the release. The LOCK's intent was "rates must be correctable without
12
+ * a release", not "the product ships priced at nothing".
13
+ * FIX: ship rates here, as DATA in their own module, never inline in the
14
+ * collector / detector / CLI. `.contextengine/policy.json` →
15
+ * `agent_cost.pricing` still wins outright when present, and this file
16
+ * compiles to plain readable JS in `dist/`, so a rate can be corrected in
17
+ * place without waiting for a release.
18
+ *
19
+ * Rates are Anthropic API list prices. Cache read is 0.1x input, cache write
20
+ * 5m is 1.25x input, cache write 1h is 2x input.
21
+ */
22
+ /**
23
+ * When these rates were last checked against published pricing. Surfaced in
24
+ * `contextengine cost` output: a rate table with no date is a rate table
25
+ * nobody knows to distrust.
26
+ */
27
+ export const DEFAULT_PRICING_ASOF = "2026-08-20";
28
+ function rate(model, input, output) {
29
+ return {
30
+ model,
31
+ input_per_mtok: input,
32
+ output_per_mtok: output,
33
+ cache_read_per_mtok: Number((input * 0.1).toFixed(4)),
34
+ cache_write_5m_per_mtok: Number((input * 1.25).toFixed(4)),
35
+ cache_write_1h_per_mtok: Number((input * 2).toFixed(4)),
36
+ };
37
+ }
38
+ /**
39
+ * Longest-prefix matched, so dated ids (`claude-haiku-4-5-20251001`) resolve
40
+ * to their family. Deliberately NO `*` catch-all: a model absent from this
41
+ * table must report as UNPRICED, never be valued at a guessed rate
42
+ * (`[ABSENCE-IS-NOT-A-VERDICT]`).
43
+ */
44
+ export const DEFAULT_PRICING = [
45
+ rate("claude-opus-5", 5, 25),
46
+ rate("claude-opus-4-8", 5, 25),
47
+ rate("claude-opus-4-7", 5, 25),
48
+ rate("claude-opus-4-6", 5, 25),
49
+ rate("claude-opus-4-5", 5, 25),
50
+ rate("claude-fable-5", 10, 50),
51
+ rate("claude-mythos-5", 10, 50),
52
+ rate("claude-sonnet-5", 3, 15),
53
+ rate("claude-sonnet-4-6", 3, 15),
54
+ rate("claude-sonnet-4-5", 3, 15),
55
+ rate("claude-haiku-4-5", 1, 5),
56
+ ];
57
+ //# sourceMappingURL=default-pricing.js.map
package/dist/detector.js CHANGED
@@ -334,9 +334,11 @@ export const _internal = {
334
334
  detectDrift, detectNoInsight, detectSilentFailure, detectStaleDocSignal,
335
335
  };
336
336
  import { metricsFor } from "./transcript-collector.js";
337
+ import { DEFAULT_PRICING } from "./default-pricing.js";
337
338
  export const DEFAULT_COST_THRESHOLDS = {
338
339
  billing_mode: "subscription",
339
- pricing: [],
340
+ // [DEFAULT-RATES-SHIP-WITH-THE-PACKAGE] — never [] again.
341
+ pricing: DEFAULT_PRICING,
340
342
  min_cache_efficiency: 3,
341
343
  max_tool_calls_per_agent: 2,
342
344
  max_cost_per_agent_usd: 3,
@@ -130,6 +130,24 @@ export declare function pricingFor(model: string | null, table: ModelPricing[]):
130
130
  * AND intensity — never one alone.
131
131
  */
132
132
  export declare function costOf(t: TokenTally, p: ModelPricing | null): CostBreakdown;
133
+ /**
134
+ * Whether a cost figure can be presented as money at all.
135
+ *
136
+ * 🔒 LOCKED [NEVER-RENDER-AN-UNKNOWN-AS-A-NUMBER] — 2026-08-20
137
+ * ⛔ NEVER print a $0.00 cost row, total, or "caching saved" figure while
138
+ * `unpricedTokens > 0`.
139
+ * WHY: 2.5.0 rendered a full VALUED COST table of $0.00 over 1.08 billion
140
+ * unpriced tokens, including "caching saved $0.00 (0%)" — which reads as
141
+ * "your caching achieves nothing" when the true reuse was 8x. The token
142
+ * accounting was right; the PRESENTATION layer turned "I have no rates"
143
+ * into a number. That is Session 21's rule at the display layer: any
144
+ * plausible-looking value returned from a branch meaning "I could not
145
+ * determine this" is the bug, however reasonable it looks.
146
+ * FIX: branch on this before formatting. `unpriced` must render the word
147
+ * UNPRICED, never a currency amount.
148
+ */
149
+ export type PricingStatus = "priced" | "partial" | "unpriced";
150
+ export declare function pricingStatus(c: CostBreakdown): PricingStatus;
133
151
  export declare function emptyTally(): TokenTally;
134
152
  export declare function addTally(a: TokenTally, b: TokenTally): TokenTally;
135
153
  export declare function totalTokens(t: TokenTally): number;
@@ -84,6 +84,11 @@ export function costOf(t, p) {
84
84
  t.output * p.output_per_mtok) / M;
85
85
  return { input, cacheWrite, cacheRead, output, total: input + cacheWrite + cacheRead + output, withoutCache, unpricedTokens: 0 };
86
86
  }
87
+ export function pricingStatus(c) {
88
+ if (c.unpricedTokens === 0)
89
+ return "priced";
90
+ return c.total === 0 ? "unpriced" : "partial";
91
+ }
87
92
  export function emptyTally() {
88
93
  return { input: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheRead: 0, output: 0 };
89
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compr/opscontext-mcp",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "OpsContext for AI Agents — read-only fleet visibility (PM2/nginx/Docker/git/cron) + tamper-evident audit log + policy-as-code hooks. The ops + compliance layer Claude Code can't grow natively.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",