@trazum/cli 1.50.8 → 1.50.9

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/index.js CHANGED
@@ -4,7 +4,7 @@ import { open, readdir, readFile, stat, writeFile } from 'node:fs/promises';
4
4
  import { dirname, join, resolve as resolvePath } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { gunzipSync } from 'node:zlib';
7
- import { applyRewrites, BASELINE_FILENAME, BASELINE_VERSION, breaches, cacheableMinimum, analyzeCachePrefix, billLevers, bucketedCacheEconomics, bucketedProfile, buildHistory, buildPlan, connectorFor, CONNECTORS, normalizeAnthropicUsage, normalizeOpenAIUsage, bucketsFromRecords, evaluateWatch, firedKey, pruneRecords, recordsFromBuckets, storeInventory, storedReportFrom, verifyPlan, cacheEconomics, cacheHitRate, contextPressure, comparePrompts, compareToBaseline, computeSavings, countTokensAnthropic, DEFAULT_USAGE, budgetPositions, conform, BREAK_EVEN_BAND, runExperiment, qualityGate, ladderPosition, validateLadder, outcomeReport, rankPerOutcome, FAILURE_POLICIES, detectFromSource, matchLocale, parsePlanDocument, waiverDay, waiverHistory, proposeInit, MIN_RATE_DAYS, parseConfig, coverageDrift, driversBetween, explainGateFailure, assignSources, fleetRollup, labelCoverage, measuredUsage, gateMargin, GATE_MARGIN_TIGHT, estimateTokens, evaluate, extractPrompts, findExamples, formatBaseline, formatSignedUsd, formatUsd, getMessages, getModel, hasMarker, LOCALES, MAX_BASELINE_BYTES, moneyIsComparable, mostSpecificMatch, nearestName, optimize, parseBaseline, PHRASE_LANGUAGES, plannedCalls, profilePrompt, profileToCsv, profileUsage, promptId, providerFromEnv, pruneExamples, refineWithLlm, rejectionText, reorderForCache, repriceProfile, reviewAgeDays, reviewExamples, RULES, sharedPrefixes, sharesOf, SOURCE_EXTENSIONS, suggestRewrites, toOtlpMetrics, toPromptfoo, TTL_1H_MS, UNLABELLED, withExactTokenCounts, } from '@trazum/core';
7
+ import { applyRewrites, BASELINE_FILENAME, BASELINE_VERSION, breaches, cacheableMinimum, analyzeCachePrefix, billLevers, bucketedCacheEconomics, bucketedProfile, buildHistory, buildPlan, connectorFor, CONNECTORS, normalizeAnthropicUsage, normalizeOpenAIUsage, bucketsFromRecords, evaluateWatch, firedKey, pruneRecords, recordsFromBuckets, storeInventory, storedReportFrom, verifyPlan, cacheEconomics, cacheHitRate, contextPressure, comparePrompts, compareToBaseline, computeSavings, countTokensAnthropic, DEFAULT_USAGE, budgetPositions, conform, BREAK_EVEN_BAND, runExperiment, qualityGate, semanticPassCost, verifySemanticProposals, SEMANTIC_SYSTEM_PROMPT, ladderPosition, validateLadder, outcomeReport, rankPerOutcome, FAILURE_POLICIES, detectFromSource, matchLocale, parsePlanDocument, waiverDay, waiverHistory, proposeInit, MIN_RATE_DAYS, parseConfig, coverageDrift, driversBetween, explainGateFailure, assignSources, fleetRollup, labelCoverage, measuredUsage, gateMargin, GATE_MARGIN_TIGHT, estimateTokens, evaluate, extractPrompts, findExamples, formatBaseline, formatSignedUsd, formatUsd, getMessages, getModel, hasMarker, LOCALES, MAX_BASELINE_BYTES, moneyIsComparable, mostSpecificMatch, nearestName, optimize, parseBaseline, PHRASE_LANGUAGES, plannedCalls, profilePrompt, profileToCsv, profileUsage, promptId, providerFromEnv, pruneExamples, refineWithLlm, rejectionText, reorderForCache, repriceProfile, reviewAgeDays, reviewExamples, RULES, sharedPrefixes, sharesOf, SOURCE_EXTENSIONS, suggestRewrites, toOtlpMetrics, toPromptfoo, TTL_1H_MS, UNLABELLED, withExactTokenCounts, } from '@trazum/core';
8
8
  import { cacheDir, cacheStats, cachingProvider, clearCache } from './suggest-cache.js';
9
9
  import { dayOf, formatGap, median, spanDays } from './time.js';
10
10
  // Everything that reads the filesystem, on its own entry point so the web
@@ -326,6 +326,7 @@ const COMMAND_FLAGS = {
326
326
  ladder: ['pricing', 'pricing-live', 'since', 'until', 'label'],
327
327
  experiment: ['a', 'b', 'min-outcomes', 'pricing', 'pricing-live'],
328
328
  quality: ['label', 'at', 'gate', 'pricing', 'pricing-live'],
329
+ semantic: ['yes', 'model', 'pricing', 'pricing-live'],
329
330
  where: [],
330
331
  rules: [],
331
332
  blame: ['limit', 'model', 'calls', 'output-tokens', 'batch', 'prompt', 'markdown-out'],
@@ -1966,6 +1967,95 @@ async function commandQuality(args, config, pricing, t) {
1966
1967
  }
1967
1968
  console.log();
1968
1969
  }
1970
+ /**
1971
+ * `trazum semantic <prompt> [--yes]` — the findings a dictionary cannot see.
1972
+ *
1973
+ * The rules engine has deferred these since 0.1.0 for one honest reason: a
1974
+ * dictionary cannot see meaning, and a model that hallucinates a finding is
1975
+ * worse than a rule that misses one.
1976
+ *
1977
+ * **The price is printed before anything is sent, and `--yes` is required.** A
1978
+ * tool that spends somebody's money to tell them how to spend less has to be
1979
+ * the first thing audited by its own arithmetic, and it has to ask.
1980
+ */
1981
+ async function commandSemantic(args, config, pricing, t) {
1982
+ const prompt = await readInput(args.positional[0], t);
1983
+ const modelId = stringFlag(args, 'model') ?? config.usage?.model ?? DEFAULT_USAGE.model;
1984
+ const model = pricing.byId.get(modelId) ?? getModel(DEFAULT_USAGE.model);
1985
+ const rates = { inputPerMTok: model.inputPerMTok, outputPerMTok: model.outputPerMTok };
1986
+ const cost = semanticPassCost(prompt, rates);
1987
+ const n = (value) => value.toLocaleString(t.numberLocale);
1988
+ console.log();
1989
+ console.log(c.bold(t.semantic.heading(args.positional[0] ?? '-')));
1990
+ console.log();
1991
+ console.log(` ${wrap(t.semantic.willCost(formatUsd(cost.usd), n(cost.inputTokens), n(cost.outputTokens), model.displayName), 74, ' ')}`);
1992
+ if (!boolFlag(args, 'yes')) {
1993
+ // Nothing has been sent at this point, and nothing will be. The price
1994
+ // above is the whole output of a run without --yes.
1995
+ console.log();
1996
+ console.log(` ${c.dim(t.semantic.needsYes())}`);
1997
+ console.log();
1998
+ return;
1999
+ }
2000
+ const provider = providerFromEnv();
2001
+ if (!provider)
2002
+ throw new Error(t.errors.llmNotConfigured());
2003
+ const answer = await provider.complete({ system: SEMANTIC_SYSTEM_PROMPT, user: prompt });
2004
+ let proposals = [];
2005
+ try {
2006
+ const parsed = JSON.parse(/^(?:```|~~~)[a-zA-Z]*\n([\s\S]*?)\n?(?:```|~~~)$/.exec(answer.trim())?.[1] ?? answer.trim());
2007
+ /**
2008
+ * A response that is not the shape asked for is **no proposals**, never a
2009
+ * crash and never a partial read. The model was told exactly what to
2010
+ * return; anything else is a response this layer cannot check, and an
2011
+ * unchecked finding is the one thing this whole module exists to prevent.
2012
+ */
2013
+ if (Array.isArray(parsed)) {
2014
+ proposals = parsed.filter((entry) => typeof entry === 'object' &&
2015
+ entry !== null &&
2016
+ Array.isArray(entry.spans) &&
2017
+ entry.spans.length === 2 &&
2018
+ entry.spans.every((span) => typeof span === 'string'));
2019
+ }
2020
+ }
2021
+ catch {
2022
+ proposals = [];
2023
+ }
2024
+ const result = verifySemanticProposals(prompt, proposals);
2025
+ const lineOf = (offset) => prompt.slice(0, offset).split('\n').length;
2026
+ console.log();
2027
+ if (result.findings.length === 0) {
2028
+ console.log(` ${c.dim(t.semantic.nothingFound())}`);
2029
+ }
2030
+ for (const finding of result.findings) {
2031
+ console.log(` ${c.bold(t.semantic.finding(finding.kind, finding.because))}`);
2032
+ finding.spans.forEach((span, index) => {
2033
+ const shown = span.length > 90 ? `${span.slice(0, 87)}…` : span;
2034
+ console.log(` ${c.dim(t.semantic.span(String(lineOf(finding.offsets[index] ?? 0)), shown))}`);
2035
+ });
2036
+ console.log(` ${c.dim(wrap(finding.ceilingTokens > 0 ? t.semantic.ceiling(n(finding.ceilingTokens)) : t.semantic.noCeiling(), 70, ' '))}`);
2037
+ console.log();
2038
+ }
2039
+ /**
2040
+ * What did **not** survive, counted and reasoned.
2041
+ *
2042
+ * A pass that showed only its accepted findings would hide its own hit
2043
+ * rate, and the hit rate is the most useful thing a reader can know about
2044
+ * whether to run it again.
2045
+ */
2046
+ if (result.rejected.length > 0) {
2047
+ console.log(` ${c.dim(t.semantic.rejected(n(result.rejected.length)))}`);
2048
+ for (const { proposal, reason } of result.rejected.slice(0, 5)) {
2049
+ const span = proposal.spans[0];
2050
+ const shown = span.length > 50 ? `${span.slice(0, 47)}…` : span;
2051
+ console.log(` ${c.dim(t.semantic.rejectedLine(reason, shown))}`);
2052
+ }
2053
+ console.log();
2054
+ }
2055
+ console.log(` ${c.dim(wrap(t.semantic.disposes(), 74, ' '))}`);
2056
+ console.log(` ${c.dim(wrap(t.semantic.optIn(), 74, ' '))}`);
2057
+ console.log();
2058
+ }
1969
2059
  function commandModels(t, pricing) {
1970
2060
  const n = (value) => value.toLocaleString(t.numberLocale);
1971
2061
  const col = t.models.columns;
@@ -7013,6 +7103,9 @@ async function main() {
7013
7103
  case 'models':
7014
7104
  commandModels(t, pricing);
7015
7105
  break;
7106
+ case 'semantic':
7107
+ await commandSemantic(args, config, pricing, t);
7108
+ break;
7016
7109
  case 'quality':
7017
7110
  await commandQuality(args, config, pricing, t);
7018
7111
  break;