@trazum/cli 1.46.0 → 1.47.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trazum/cli",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "description": "Trazum CLI: find where your LLM bill goes, price every finding per month, and enforce token budgets in CI.",
5
5
  "license": "MIT",
6
6
  "author": "David Mu\u00f1oz Rey",
@@ -37,7 +37,7 @@
37
37
  "prepublishOnly": "npm run build && npm test"
38
38
  },
39
39
  "dependencies": {
40
- "@trazum/core": "1.46.0"
40
+ "@trazum/core": "1.47.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^26.2.0",
package/src/i18n/en.ts CHANGED
@@ -1814,8 +1814,18 @@ ${bold('EXAMPLES')}
1814
1814
  'Point this at a saved plan and a newer log: trazum verify plan.json --against usage.jsonl. It says, per action, whether the change arrived, did not arrive, or cannot be told — and never fewer than those three.',
1815
1815
  needsAgainst: () =>
1816
1816
  '--against <newer.jsonl|dir> is required. A plan can only be verified against a log that came after it; without one there is nothing to hold the prediction to.',
1817
- badPlan: (path) =>
1818
- `${path} is not a plan document this tool can verify expected the JSON that "trazum plan -o" writes (schemaVersion 1, with an actions array).`,
1817
+ badPlan: (path, why) =>
1818
+ `${path} is not a plan document this tool can verify: ${why}. Expected the JSON that "trazum plan -o" writes.`,
1819
+ planRefusal: (why) =>
1820
+ why.kind === 'not-json'
1821
+ ? 'it is not valid JSON'
1822
+ : why.kind === 'not-an-object'
1823
+ ? 'the top level is not a JSON object'
1824
+ : why.kind === 'wrong-schema-version'
1825
+ ? `schemaVersion is ${JSON.stringify(why.found)} rather than 1`
1826
+ : why.kind === 'actions-not-a-list'
1827
+ ? 'there is no actions array'
1828
+ : `action ${why.index + 1} is malformed (${why.because})`,
1819
1829
  heading: (actions, planDate) =>
1820
1830
  planDate === null
1821
1831
  ? `Did it work? ${actions} actions from an undated plan, against this log`
package/src/i18n/es.ts CHANGED
@@ -1844,8 +1844,18 @@ ${bold('EJEMPLOS')}
1844
1844
  'Apunta esto a un plan guardado y a un registro posterior: trazum verify plan.json --against usage.jsonl. Dice, por acción, si el cambio llegó, no llegó o no se puede saber — y nunca menos de esos tres.',
1845
1845
  needsAgainst: () =>
1846
1846
  '--against <nuevo.jsonl|dir> es obligatorio. Un plan solo puede verificarse contra un registro posterior; sin uno no hay nada a lo que someter la predicción.',
1847
- badPlan: (path) =>
1848
- `${path} no es un documento de plan que esta herramienta pueda verificar se esperaba el JSON que escribe "trazum plan -o" (schemaVersion 1, con un array actions).`,
1847
+ badPlan: (path, why) =>
1848
+ `${path} no es un documento de plan que esta herramienta pueda verificar: ${why}. Se esperaba el JSON que escribe "trazum plan -o".`,
1849
+ planRefusal: (why) =>
1850
+ why.kind === 'not-json'
1851
+ ? 'no es JSON válido'
1852
+ : why.kind === 'not-an-object'
1853
+ ? 'el nivel superior no es un objeto JSON'
1854
+ : why.kind === 'wrong-schema-version'
1855
+ ? `schemaVersion es ${JSON.stringify(why.found)} en vez de 1`
1856
+ : why.kind === 'actions-not-a-list'
1857
+ ? 'no hay un array actions'
1858
+ : `la acción ${why.index + 1} está mal formada (${why.because})`,
1849
1859
  heading: (actions, planDate) =>
1850
1860
  planDate === null
1851
1861
  ? `¿Funcionó? ${actions} acciones de un plan sin fecha, contra este registro`
package/src/i18n/types.ts CHANGED
@@ -1269,7 +1269,22 @@ export interface CliMessages {
1269
1269
  noTarget(): string;
1270
1270
  needsAgainst(): string;
1271
1271
  /** Not a plan document: wrong shape, wrong version, or not JSON at all. */
1272
- badPlan(path: string): string;
1272
+ /**
1273
+ * `why` is the typed reason from `parsePlanDocument`, rendered so the
1274
+ * refusal names what is wrong rather than only that something is. A file
1275
+ * that is valid JSON and not a plan and a plan with one bad action are
1276
+ * different problems with different fixes.
1277
+ */
1278
+ badPlan(path: string, why: string): string;
1279
+ /** The typed refusal from `parsePlanDocument`, in one clause. */
1280
+ planRefusal(
1281
+ why:
1282
+ | { kind: 'not-json' }
1283
+ | { kind: 'not-an-object' }
1284
+ | { kind: 'wrong-schema-version'; found: unknown }
1285
+ | { kind: 'actions-not-a-list' }
1286
+ | { kind: 'action-malformed'; index: number; because: string },
1287
+ ): string;
1273
1288
  heading(actions: string, planDate: string | null): string;
1274
1289
  counts(arrived: string, notArrived: string, cannotTell: string): string;
1275
1290
  /** Two price lists are two measurements; every dollar line inherits this. */
package/src/index.ts CHANGED
@@ -37,6 +37,7 @@ import {
37
37
  DEFAULT_USAGE,
38
38
  detectFromSource,
39
39
  matchLocale,
40
+ parsePlanDocument,
40
41
  proposeInit,
41
42
  MIN_RATE_DAYS,
42
43
  parseConfig,
@@ -3546,17 +3547,21 @@ async function commandVerify(
3546
3547
  const againstPath = stringFlag(args, 'against');
3547
3548
  if (againstPath === undefined) throw new Error(t.verify.needsAgainst());
3548
3549
 
3549
- let plan: PlanDocument & { createdAt?: string };
3550
- try {
3551
- const parsed = JSON.parse(await readFile(planPath, 'utf8'));
3552
- if (parsed?.schemaVersion !== 1 || !Array.isArray(parsed.actions)) {
3553
- throw new Error(t.verify.badPlan(planPath));
3554
- }
3555
- plan = parsed;
3556
- } catch (error) {
3557
- if (error instanceof SyntaxError) throw new Error(t.verify.badPlan(planPath));
3558
- throw error;
3550
+ /**
3551
+ * One validator, shared with the browser since 1.47.
3552
+ *
3553
+ * The check here used to be `schemaVersion === 1 && Array.isArray(actions)`
3554
+ * and nothing more, which accepts a file whose actions are arbitrary
3555
+ * objects — `verifyPlan` would then read `label` off `undefined`, match it
3556
+ * against no slice, and report `cannot-tell: workload-vanished` for every
3557
+ * one. A verification of a document that was never a plan, rendered exactly
3558
+ * like a real one.
3559
+ */
3560
+ const parsed = parsePlanDocument(await readFile(planPath, 'utf8'));
3561
+ if (!parsed.ok) {
3562
+ throw new Error(t.verify.badPlan(planPath, t.verify.planRefusal(parsed.why)));
3559
3563
  }
3564
+ const plan = parsed.plan;
3560
3565
 
3561
3566
  const GZ = LOG_EXTENSIONS.map((ext) => `${ext}.gz`);
3562
3567
  const READABLE = [...LOG_EXTENSIONS, ...GZ];