ccqa 1.21.0 → 1.22.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/dist/bin/ccqa.mjs CHANGED
@@ -6842,10 +6842,26 @@ const SpecLedgerEntrySchema = z.object({
6842
6842
  deployedSha: z.string().nullable().optional(),
6843
6843
  deployedShaAmbiguous: z.boolean().optional()
6844
6844
  });
6845
+ /**
6846
+ * A red-bucket entry: where the failure happened, plus what it was. The cause
6847
+ * is copied off the run report's `analysis` so a reader learns why a spec is
6848
+ * red without fetching the report of every red spec.
6849
+ *
6850
+ * Only the red bucket carries it. A pass has no cause, so putting these on
6851
+ * `SpecLedgerEntry` would invite writing them where they cannot exist.
6852
+ *
6853
+ * Both fields are optional and mean the same thing by their absence — nothing
6854
+ * is on record. Failure analysis is opt-in (`--on-fail-explain`), and entries
6855
+ * written before this release have neither field.
6856
+ */
6857
+ const SpecRedLedgerEntrySchema = SpecLedgerEntrySchema.extend({
6858
+ label: PredictedLabelSchema.optional(),
6859
+ headline: z.string().optional()
6860
+ });
6845
6861
  z.object({
6846
6862
  green: z.record(z.string(), SpecLedgerEntrySchema).default({}),
6847
6863
  run: z.record(z.string(), SpecLedgerEntrySchema).default({}),
6848
- red: z.record(z.string(), SpecLedgerEntrySchema).default({})
6864
+ red: z.record(z.string(), SpecRedLedgerEntrySchema).default({})
6849
6865
  });
6850
6866
  /** One deploy, as the consumer's deploy job reported it (ADR-0010). */
6851
6867
  const DeployEntrySchema = z.object({
@@ -7025,7 +7041,7 @@ const SpecRerunSchema = z.object({
7025
7041
  heldBy: SpecLockSchema.nullable(),
7026
7042
  lastRun: SpecLedgerEntrySchema.nullable(),
7027
7043
  lastGreen: SpecLedgerEntrySchema.nullable(),
7028
- lastRed: SpecLedgerEntrySchema.nullable(),
7044
+ lastRed: SpecRedLedgerEntrySchema.nullable(),
7029
7045
  touchedBy: z.array(z.string()).optional(),
7030
7046
  touchedByDeploy: DeployRefSchema.nullable().optional()
7031
7047
  });
@@ -7075,7 +7091,7 @@ z.object({
7075
7091
  z.object({
7076
7092
  entries: z.record(z.string(), SpecLedgerEntrySchema),
7077
7093
  lastRun: z.record(z.string(), SpecLedgerEntrySchema).default({}),
7078
- lastRed: z.record(z.string(), SpecLedgerEntrySchema).default({})
7094
+ lastRed: z.record(z.string(), SpecRedLedgerEntrySchema).default({})
7079
7095
  });
7080
7096
  /**
7081
7097
  * One spec's last drift audit, as recorded by `ccqa audit --report-to-hub`. Unlike the
@@ -16755,12 +16771,17 @@ function toLedger(raw) {
16755
16771
  }
16756
16772
  /** Fold `from` into `into` in place, per bucket and key, newest `at` winning. */
16757
16773
  function mergeLedgerInto(into, from) {
16758
- for (const name of BUCKET_NAMES) for (const [key, entry] of Object.entries(from[name])) {
16759
- const prev = into[name][key];
16760
- if (!prev || prev.at <= entry.at) into[name][key] = entry;
16761
- }
16774
+ mergeBucket(into.green, from.green);
16775
+ mergeBucket(into.run, from.run);
16776
+ mergeBucket(into.red, from.red);
16762
16777
  return into;
16763
16778
  }
16779
+ function mergeBucket(into, from) {
16780
+ for (const [key, entry] of Object.entries(from)) {
16781
+ const prev = into[key];
16782
+ if (!prev || prev.at <= entry.at) into[key] = entry;
16783
+ }
16784
+ }
16764
16785
  //#endregion
16765
16786
  //#region src/hub/api/validate.ts
16766
16787
  /**
@@ -16971,7 +16992,7 @@ async function updateSpecLedger(storage, run, results) {
16971
16992
  const key = `${row.feature}/${row.spec}`;
16972
16993
  ledger.run[key] = entry;
16973
16994
  if (row.status === "passed") ledger.green[key] = entry;
16974
- else ledger.red[key] = entry;
16995
+ else ledger.red[key] = redEntry(entry, row);
16975
16996
  }
16976
16997
  if (Object.keys(ledger.run).length === 0) return;
16977
16998
  try {
@@ -16981,6 +17002,23 @@ async function updateSpecLedger(storage, run, results) {
16981
17002
  }
16982
17003
  }
16983
17004
  /**
17005
+ * The red bucket's entry: the coordinate every bucket carries, plus what the
17006
+ * failure analysis concluded, so a reader of the ledger learns why a spec is
17007
+ * red without fetching its report.
17008
+ *
17009
+ * A field with nothing behind it is left out rather than written empty:
17010
+ * `analysis` is null when the run did not ask for one (`--on-fail-explain` is
17011
+ * opt-in), and a model that answered with no headline leaves `headline` "".
17012
+ */
17013
+ function redEntry(entry, row) {
17014
+ if (!row.analysis) return entry;
17015
+ return {
17016
+ ...entry,
17017
+ label: row.analysis.label,
17018
+ ...row.analysis.headline ? { headline: row.analysis.headline } : {}
17019
+ };
17020
+ }
17021
+ /**
16984
17022
  * Advance the drift ledger from a terminal `kind: "drift"` run: each row's
16985
17023
  * `analysis` (the diagnosis `driftResultsToReport` put there) becomes that
16986
17024
  * spec's newest audit entry. No profile — drift asks whether the spec still
@@ -22205,6 +22243,17 @@ const CLIENT_JS = `
22205
22243
  badge.appendChild(document.createTextNode(" " + t("perspectives.run.state." + runState)));
22206
22244
  td.appendChild(badge);
22207
22245
 
22246
+ // What the failure was, as the run's analysis called it — the same place
22247
+ // the audit column names its drift label. Absent on a red entry the run
22248
+ // never analyzed, and on entries written before the ledger carried it.
22249
+ if (runState === "failed" && rr.lastRed && rr.lastRed.label) {
22250
+ var cause = el("span", "cellsub", labelText(rr.lastRed.label));
22251
+ // The one-line conclusion, for a pointer. The detail panel shows it in
22252
+ // full, so nothing here depends on finding it.
22253
+ if (rr.lastRed.headline) cause.title = rr.lastRed.headline;
22254
+ td.appendChild(cause);
22255
+ }
22256
+
22208
22257
  // The sub-line is the coordinate of the result being reported — the same
22209
22258
  // "when is this from" evidence the audit column carries above, so the two
22210
22259
  // axes read as siblings. Why the currency matters belongs to the verdict
@@ -22423,6 +22472,21 @@ const CLIENT_JS = `
22423
22472
  return wrap;
22424
22473
  }
22425
22474
 
22475
+ // The failure: which run it was, then what the analysis concluded. The
22476
+ // headline is model output, already localized server-side, so it is shown as
22477
+ // written. A run made without failure analysis carries neither field, and the
22478
+ // row is then the coordinate alone — what it has always been.
22479
+ function rerunFailureValue(entry) {
22480
+ var wrap = el("div");
22481
+ wrap.appendChild(ledgerLine(entry));
22482
+ if (entry.label) {
22483
+ var line = el("div", "d-prose", labelText(entry.label));
22484
+ if (entry.headline) line.appendChild(document.createTextNode(" · " + entry.headline));
22485
+ wrap.appendChild(line);
22486
+ }
22487
+ return wrap;
22488
+ }
22489
+
22426
22490
  // Detail row: a definition list of the case's fields plus the note editor.
22427
22491
  // Built with createElement/textContent throughout — every field here is
22428
22492
  // API-derived, so none of it may go through innerHTML.
@@ -22456,7 +22520,7 @@ const CLIENT_JS = `
22456
22520
  var rr = ledgerEntryFor(perspState.rerun, feature, spec);
22457
22521
  if (rr) {
22458
22522
  row(rerunEvidenceLabelKey(rr), rerunEvidenceValue(rr));
22459
- if (rerunHasFailure(rr)) row("perspectives.d.lastRed", ledgerLine(rr.lastRed));
22523
+ if (rerunHasFailure(rr)) row("perspectives.d.lastRed", rerunFailureValue(rr.lastRed));
22460
22524
  }
22461
22525
  frag.appendChild(dl);
22462
22526
 
@@ -314,6 +314,14 @@ declare const RerunReportSchema: z.ZodObject<{
314
314
  at: z.ZodString;
315
315
  deployedSha: z.ZodOptional<z.ZodNullable<z.ZodString>>;
316
316
  deployedShaAmbiguous: z.ZodOptional<z.ZodBoolean>;
317
+ label: z.ZodOptional<z.ZodEnum<{
318
+ TEST_DRIFT: "TEST_DRIFT";
319
+ SPEC_CHANGE: "SPEC_CHANGE";
320
+ PRODUCT_BUG: "PRODUCT_BUG";
321
+ ENVIRONMENT: "ENVIRONMENT";
322
+ UNKNOWN: "UNKNOWN";
323
+ }>>;
324
+ headline: z.ZodOptional<z.ZodString>;
317
325
  }, z.core.$strip>>;
318
326
  touchedBy: z.ZodOptional<z.ZodArray<z.ZodString>>;
319
327
  touchedByDeploy: z.ZodOptional<z.ZodNullable<z.ZodObject<{
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {