ccqa 1.56.0 → 1.57.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/bin/ccqa.mjs CHANGED
@@ -8200,52 +8200,15 @@ function exportedNames(source) {
8200
8200
  function reviewEmittedFiles(input) {
8201
8201
  const locators = collectLocators(input.files);
8202
8202
  const said = input.caseText.join("\n");
8203
- return [
8204
- ...decidesNothing(input),
8205
- ...unassertedPath(input),
8206
- ...[...input.files].flatMap(([file, source]) => [
8207
- ...toolIdentifiers(file, source),
8208
- ...containerOfPageText(file, source),
8209
- ...unjustifiedFirst(file, source),
8210
- ...describeEcho(file, source),
8211
- ...unaskedAssertions(file, source, locators, said),
8212
- ...weakerTwin(file, source),
8213
- ...unreached(file, source, input)
8214
- ])
8215
- ];
8216
- }
8217
- /**
8218
- * A path with a placeholder segment in it — `/orders/{orderId}`. Writing one
8219
- * in prose is a statement about the address, not about the page's contents:
8220
- * there is no other reason to name the part that changes per run.
8221
- */
8222
- const PLACEHOLDER_PATH = /\/[A-Za-z0-9_\-]+(?:\/[A-Za-z0-9_\-]+)*\/\{[A-Za-z0-9_]+\}/;
8223
- /**
8224
- * A case that says where the run must end up, and a test that never looks at
8225
- * the address.
8226
- *
8227
- * The row being on screen is not the same claim as being on the screen that
8228
- * shows it — a product that rendered the row without navigating passes, and
8229
- * that is the failure the expectation was written to catch. Scoped to
8230
- * placeholder paths because those are unambiguous; a path mentioned as scenery
8231
- * ("on /settings, the button is shown") is not a claim about the address.
8232
- *
8233
- * Calibrated against a small set of hand-written case definitions: a few
8234
- * name a placeholder path in their expectations, most of those already
8235
- * assert on the URL, and the one that does not is the defect this was
8236
- * written for.
8237
- */
8238
- function unassertedPath(input) {
8239
- const source = input.files.get(input.testPath);
8240
- if (source === void 0) return [];
8241
- const stated = input.caseText.find((text) => PLACEHOLDER_PATH.test(text));
8242
- if (stated === void 0 || /\btoHaveURL\b/.test(source)) return [];
8243
- return [{
8244
- file: input.testPath,
8245
- line: 1,
8246
- rule: "unasserted-path",
8247
- message: `the case says where the run ends up — "${PLACEHOLDER_PATH.exec(stated)?.[0]}" — and nothing here looks at the address. What the screen shows can be right while the screen is wrong. Assert the URL where the case says it changes`
8248
- }];
8203
+ return [...decidesNothing(input), ...[...input.files].flatMap(([file, source]) => [
8204
+ ...toolIdentifiers(file, source),
8205
+ ...containerOfPageText(file, source),
8206
+ ...unjustifiedFirst(file, source),
8207
+ ...describeEcho(file, source),
8208
+ ...unaskedAssertions(file, source, locators, said),
8209
+ ...weakerTwin(file, source),
8210
+ ...unreached(file, source, input)
8211
+ ])];
8249
8212
  }
8250
8213
  /** `readonly name =`, `name(...): Locator {`, and `export … name`. */
8251
8214
  const DECLARED = /(?:readonly\s+([A-Za-z0-9_]+)\s*=|\b([A-Za-z0-9_]+)\([^)]*\)\s*:\s*Locator\b|export\s+(?:async\s+)?(?:const|let|function|class)\s+([A-Za-z0-9_]+))/g;
@@ -28084,7 +28047,7 @@ async function checkSpec(target, opts) {
28084
28047
  locators,
28085
28048
  systemPrompt,
28086
28049
  userPrompt
28087
- }).then((path) => meta("inputs", path), (err) => warn(`${name}: could not write the audit inputs (${err.message})`));
28050
+ }).catch((err) => warn(`${name}: could not write the audit inputs (${err.message})`));
28088
28051
  const MAX_ATTEMPTS = 2;
28089
28052
  let lastError = "";
28090
28053
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
@@ -28155,12 +28118,12 @@ async function checkSpec(target, opts) {
28155
28118
  //#endregion
28156
28119
  //#region src/drift/format.ts
28157
28120
  /**
28158
- * Render drift results as a string. The CLI commands and the `run` failure
28159
- * hook are the only callers; both want the formatted output returned so
28160
- * they can prefix / interleave / pipe it as needed.
28121
+ * Render drift results for a terminal. `json` is not one of these: it prints
28122
+ * the same payload the report file holds, which is assembled asynchronously
28123
+ * and so is built once by the caller rather than rendered from results here
28124
+ * (`buildAuditReport`).
28161
28125
  */
28162
28126
  function renderDrift(results, format, cwd) {
28163
- if (format === "json") return renderJson(results);
28164
28127
  if (format === "github") return renderGithub(results, cwd);
28165
28128
  return renderText$1(results);
28166
28129
  }
@@ -28195,16 +28158,6 @@ function renderText$1(results) {
28195
28158
  out.push("");
28196
28159
  return out.join("\n");
28197
28160
  }
28198
- function renderJson(results) {
28199
- const payload = { specs: results.map((r) => ({
28200
- feature: r.target.featureName,
28201
- spec: r.target.specName,
28202
- ok: r.ok,
28203
- ...r.error ? { error: r.error } : {},
28204
- drift: r.drift
28205
- })) };
28206
- return `${JSON.stringify(payload, null, 2)}\n`;
28207
- }
28208
28161
  function renderGithub(results, cwd) {
28209
28162
  const repoRoot = process.env["GITHUB_WORKSPACE"] ?? process.cwd();
28210
28163
  const lines = [];
@@ -28335,41 +28288,49 @@ function driftResultsToReport(results, meta) {
28335
28288
  };
28336
28289
  }
28337
28290
  //#endregion
28338
- //#region src/drift/brief.ts
28291
+ //#region src/drift/audit-report.ts
28339
28292
  /**
28340
- * Write one JSON file per finding, named by the case id below `dir`. Returns
28341
- * the paths written, in the order the results came in.
28293
+ * What `ccqa audit` leaves behind for a machine.
28294
+ *
28295
+ * The audit has three audiences: the person reading the terminal, the hub's
28296
+ * ledger, and whatever repairs the test. This is the third, and it is one
28297
+ * payload — written to `<report-dir>/audit.json` by every completed sweep and
28298
+ * printed verbatim by `--report-format json` (ADR-0036).
28342
28299
  */
28343
- async function writeAuditBriefs(input) {
28344
- const findings = input.results.filter((r) => r.drift !== null);
28345
- if (findings.length === 0) return [];
28346
- const ctx = input.context ?? await loadSpecArtifactsContext(input.cwd);
28347
- const dirAbs = resolve(input.cwd, input.dir);
28348
- return Promise.all(findings.map(async (result) => {
28349
- const brief = await buildBrief(result, input.cwd, ctx);
28350
- const path = join(dirAbs, `${brief.case}.json`);
28351
- await mkdir(dirname(path), { recursive: true });
28352
- await writeFile(path, `${JSON.stringify(brief, null, 2)}\n`, "utf8");
28353
- return path;
28354
- }));
28300
+ /** The file the report is written as, inside the report directory. */
28301
+ const AUDIT_REPORT_FILE = "audit.json";
28302
+ /**
28303
+ * The payload, for both the file and `--report-format json`. Each row's reads
28304
+ * are independent, so they resolve together rather than one case at a time.
28305
+ */
28306
+ async function buildAuditReport(results, cwd, ctx) {
28307
+ return { specs: await Promise.all(results.map((r) => buildRow(r, cwd, ctx))) };
28355
28308
  }
28356
- async function buildBrief(result, cwd, ctx) {
28309
+ /** Write the report as `<dirAbs>/audit.json`, creating the directory. Returns its path. */
28310
+ async function writeAuditReport(report, dirAbs) {
28311
+ await mkdir(dirAbs, { recursive: true });
28312
+ const path = join(dirAbs, AUDIT_REPORT_FILE);
28313
+ await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, "utf8");
28314
+ return path;
28315
+ }
28316
+ async function buildRow(result, cwd, ctx) {
28357
28317
  const drift = result.drift;
28358
- const id = caseIdOf(result.target);
28318
+ const row = {
28319
+ feature: result.target.featureName,
28320
+ spec: result.target.specName,
28321
+ case: caseIdOf(result.target),
28322
+ ok: result.ok,
28323
+ ...result.error ? { error: result.error } : {},
28324
+ drift
28325
+ };
28326
+ if (drift === null) return row;
28359
28327
  const test = result.live ? null : await caseTestPath(result.target, ctx);
28360
28328
  const rewrite = drift.label === "TEST_DRIFT" ? (result.renames ?? []).filter((r) => r.inDocument).map(({ from, to }) => ({
28361
28329
  from,
28362
28330
  to
28363
28331
  })) : [];
28364
28332
  return {
28365
- case: id,
28366
- kind: drift.label,
28367
- surface: drift.surface,
28368
- confidence: drift.confidence,
28369
- headline: drift.headline,
28370
- recommendation: drift.recommendation,
28371
- reasoning: drift.reasoning,
28372
- evidence: drift.evidence,
28333
+ ...row,
28373
28334
  test,
28374
28335
  document: documentOf(result, cwd),
28375
28336
  repair: await buildRepair(result, test, rewrite, cwd, ctx)
@@ -28637,7 +28598,7 @@ function selectSpecsNeedingAudit(targets, report, stillDrifted = /* @__PURE__ */
28637
28598
  //#endregion
28638
28599
  //#region src/cli/audit.ts
28639
28600
  const DEFAULT_CONCURRENCY = 3;
28640
- const auditCommand = addProfileOption(addLanguageOption(new Command("audit").argument("[feature/spec]", "Optional spec id. If omitted, every spec under .ccqa/features/ is checked.").description("Read each spec against the code it describes and report where the two have drifted. Static: no browser is run, so this is the cheap check to put in front of `ccqa run`.").optionsGroup("Which specs to audit:").option("--only-affected-by <ref>", "Only specs `ccqa select-specs` decides the diff against <ref> reaches (e.g. origin/main), by intersecting it with each spec's measured coverage from the hub. In pull_request CI, pass $GITHUB_BASE_REF. Specs without a measurement are audited rather than skipped.").option("--only-hub-audit-needed", "Only specs the hub says a deploy has landed on since the audit last read them. A spec that was never audited is always included, one the hub cannot answer for is audited rather than skipped, and one whose drift entry is still open is always re-audited — a merged fix changes only the spec tree, which no deploy answer covers. No git diff involved. Requires a hub connection and --hub-profile.").optionsGroup("How to run it:").option("--concurrency <n>", `Parallel spec checks (default: ${DEFAULT_CONCURRENCY})`).option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").optionsGroup("What to do with the results:").option("--report-format <fmt>", "Output format: text | json | github", "text").option("--report-to-hub", "Push the result to a ccqa hub as a run (kind: drift), which is what updates the drift ledger. A spec it finds drifted answers `needsRepair` to `ccqa run --only-hub-rerun-needed`, and is not run until a person repairs it.").option("--exit-on <level>", "Exit non-zero on this severity or higher: warn | error", "error").option("--brief <dir>", "Also write one JSON file per finding under <dir>, named by case id: the verdict, its citations, which repair the case needs (regenerate, re-record, rewrite its document, or a person), and any renamed strings to apply to that document first. What reads them is outside ccqa.").option("--dump-inputs <dir>", "Also write what each case's audit was given — its document, every file it was handed and the import that reached it, the source roots, and the prompt — as one markdown file per case under <dir>. For telling 'this never reached the audit' apart from 'it did, and nothing was reported'.").optionsGroup("Environment and connection:").option("--cwd <path>", "Working directory used as both the .ccqa root and the codebase Claude reads. Useful for monorepos. Defaults to process.cwd().").option("--project <name>", "Logical project name for the pushed run. Defaults to the current directory's name.").option(...hubUrlOption).option(...hubTokenOption).option(...hubHeaderOption))).action(withUsageErrors(async (specPath, opts) => {
28601
+ const auditCommand = addProfileOption(addLanguageOption(new Command("audit").argument("[feature/spec]", "Optional spec id. If omitted, every spec under .ccqa/features/ is checked.").description("Read each spec against the code it describes and report where the two have drifted. Static: no browser is run, so this is the cheap check to put in front of `ccqa run`.").optionsGroup("Which specs to audit:").option("--only-affected-by <ref>", "Only specs `ccqa select-specs` decides the diff against <ref> reaches (e.g. origin/main), by intersecting it with each spec's measured coverage from the hub. In pull_request CI, pass $GITHUB_BASE_REF. Specs without a measurement are audited rather than skipped.").option("--only-hub-audit-needed", "Only specs the hub says a deploy has landed on since the audit last read them. A spec that was never audited is always included, one the hub cannot answer for is audited rather than skipped, and one whose drift entry is still open is always re-audited — a merged fix changes only the spec tree, which no deploy answer covers. No git diff involved. Requires a hub connection and --hub-profile.").optionsGroup("How to run it:").option("--concurrency <n>", `Parallel spec checks (default: ${DEFAULT_CONCURRENCY})`).option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").optionsGroup("What to do with the results:").option("--report-dir <dir>", `Directory for the structured audit results (audit.json), which are always written. Default: ${DEFAULT_REPORT_DIR}/.`).option("--report-format <fmt>", "Terminal output format: text | json | github. 'json' prints the same payload audit.json holds, and nothing else.", "text").option("--report-to-hub", "Push the result to a ccqa hub as a run (kind: drift), which is what updates the drift ledger. A spec it finds drifted answers `needsRepair` to `ccqa run --only-hub-rerun-needed`, and is not run until a person repairs it.").option("--exit-on <level>", "Exit non-zero on this severity or higher: warn | error", "error").option("--dump-inputs <dir>", "Also write what each case's audit was given — its document, every file it was handed and the import that reached it, the source roots, and the prompt — as one markdown file per case under <dir>. For telling 'this never reached the audit' apart from 'it did, and nothing was reported'.").optionsGroup("Environment and connection:").option("--cwd <path>", "Working directory used as both the .ccqa root and the codebase Claude reads. Useful for monorepos. Defaults to process.cwd().").option("--project <name>", "Logical project name for the pushed run. Defaults to the current directory's name.").option(...hubUrlOption).option(...hubTokenOption).option(...hubHeaderOption))).action(withUsageErrors(async (specPath, opts) => {
28641
28602
  await withCostReporting("audit", () => runAudit(specPath, opts));
28642
28603
  }));
28643
28604
  async function runAudit(specPath, opts) {
@@ -28645,6 +28606,11 @@ async function runAudit(specPath, opts) {
28645
28606
  const threshold = parseSeverity(opts.exitOn);
28646
28607
  const concurrency = parseConcurrency(opts.concurrency);
28647
28608
  const cwd = resolveCwd(opts.cwd);
28609
+ const reportDir = resolve(cwd, opts.reportDir ?? "ccqa-report");
28610
+ await rm(join(reportDir, AUDIT_REPORT_FILE), {
28611
+ force: true,
28612
+ recursive: true
28613
+ }).catch(() => {});
28648
28614
  await ensureCcqaDir(cwd);
28649
28615
  if (opts.onlyAffectedBy && specPath) {
28650
28616
  error("--only-affected-by and an explicit spec id cannot be combined; it only applies to a full sweep");
@@ -28678,11 +28644,18 @@ async function runAudit(specPath, opts) {
28678
28644
  const intent = artifactsContext.reader.target;
28679
28645
  const sourceRoots = await resolveSourceRoots(cwd, config.sourceRoots);
28680
28646
  let targets = await collectTargets(specPath, cwd, artifactsContext.reader);
28681
- if (targets.length === 0) exitWithNoSpecs(format, "noSpecsFound", `no test cases found under ${intent ? intent.module : ".ccqa/features/"}`);
28647
+ if (targets.length === 0) {
28648
+ const where = intent ? intent.module : ".ccqa/features/";
28649
+ await exitWithNoSpecs({
28650
+ format,
28651
+ reportDir
28652
+ }, "noSpecsFound", `no test cases found under ${where}`);
28653
+ }
28682
28654
  if (format === "text") {
28683
28655
  header("audit", specPath ?? `${targets.length} case${targets.length > 1 ? "s" : ""}`);
28684
28656
  if (opts.cwd) meta("cwd", cwd);
28685
28657
  for (const root of sourceRoots) meta("source", root.abs);
28658
+ if (opts.dumpInputs) meta("inputs", resolve(cwd, opts.dumpInputs));
28686
28659
  }
28687
28660
  if (opts.onlyHubAuditNeeded) {
28688
28661
  const total = targets.length;
@@ -28697,12 +28670,18 @@ async function runAudit(specPath, opts) {
28697
28670
  meta("hub", selection.summary);
28698
28671
  meta("scoped", `${targets.length} of ${total} spec${total > 1 ? "s" : ""}`);
28699
28672
  }
28700
- if (targets.length === 0) exitWithNoSpecs(format, "allCurrent", "every spec has been audited since the last deploy that reached it");
28673
+ if (targets.length === 0) await exitWithNoSpecs({
28674
+ format,
28675
+ reportDir
28676
+ }, "allCurrent", "every spec has been audited since the last deploy that reached it");
28701
28677
  holder = randomUUID();
28702
28678
  const claimed = await claimSpecs(hub, hubProject, opts.hubProfile, targets, holder);
28703
28679
  if (claimed.length < targets.length && format === "text") meta("held-elsewhere", `${targets.length - claimed.length} spec(s) another job is auditing`);
28704
28680
  targets = claimed;
28705
- if (targets.length === 0) exitWithNoSpecs(format, "allHeld", "every spec that needs auditing is already being audited by another job");
28681
+ if (targets.length === 0) await exitWithNoSpecs({
28682
+ format,
28683
+ reportDir
28684
+ }, "allHeld", "every spec that needs auditing is already being audited by another job");
28706
28685
  }
28707
28686
  let baseRef = null;
28708
28687
  if (opts.onlyAffectedBy) {
@@ -28716,7 +28695,10 @@ async function runAudit(specPath, opts) {
28716
28695
  targets = selection.specs;
28717
28696
  baseRef = selection.base.ref;
28718
28697
  if (format === "text") meta("scoped", `${targets.length} of ${total} spec${total > 1 ? "s" : ""}`);
28719
- if (targets.length === 0) exitWithNoSpecs(format, "noDiffIntersection", "no specs intersect the changed file set; nothing to check");
28698
+ if (targets.length === 0) await exitWithNoSpecs({
28699
+ format,
28700
+ reportDir
28701
+ }, "noDiffIntersection", "no specs intersect the changed file set; nothing to check");
28720
28702
  }
28721
28703
  const blocks = await loadAvailableBlocks(cwd);
28722
28704
  let pushConn = null;
@@ -28757,7 +28739,10 @@ async function runAudit(specPath, opts) {
28757
28739
  } finally {
28758
28740
  if (holder) await releaseSpecs(hub, hubProject, opts.hubProfile, holder);
28759
28741
  }
28760
- process.stdout.write(renderDrift(results, format, cwd));
28742
+ const report = await buildAuditReport(results, cwd, artifactsContext);
28743
+ if (format === "json") process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
28744
+ else process.stdout.write(renderDrift(results, format, cwd));
28745
+ const written = await tryWriteReport(report, reportDir, format);
28761
28746
  if (push) await sealDriftPush(push, {
28762
28747
  results,
28763
28748
  threshold,
@@ -28766,18 +28751,22 @@ async function runAudit(specPath, opts) {
28766
28751
  baseRef,
28767
28752
  promptCtx
28768
28753
  });
28769
- if (opts.brief) try {
28770
- const written = await writeAuditBriefs({
28771
- results,
28772
- cwd,
28773
- dir: opts.brief,
28774
- context: artifactsContext
28775
- });
28776
- if (format === "text") meta("briefs", `${written.length} written to ${opts.brief}`);
28754
+ process.exit(written ? determineExitCode(results, threshold) : 2);
28755
+ }
28756
+ /**
28757
+ * Write the report, saying whether it landed. A sweep whose results never
28758
+ * landed has to fail: the next reader of `audit.json` would otherwise act on
28759
+ * the previous invocation's findings with nothing saying so.
28760
+ */
28761
+ async function tryWriteReport(report, dirAbs, format) {
28762
+ try {
28763
+ const path = await writeAuditReport(report, dirAbs);
28764
+ if (format === "text") meta("report", path);
28765
+ return true;
28777
28766
  } catch (e) {
28778
- error(`could not write briefs to ${opts.brief}: ${errMessage(e)}`);
28767
+ error(`could not write the audit report to ${dirAbs}: ${errMessage(e)}`);
28768
+ return false;
28779
28769
  }
28780
- process.exit(determineExitCode(results, threshold));
28781
28770
  }
28782
28771
  /** Longer than the audit's own timeout, so a sweep cannot outlive its claim. */
28783
28772
  const AUDIT_LOCK_TTL_SECONDS = 5400;
@@ -28903,14 +28892,21 @@ async function resolveAuditPromptContext(opts, cwd, resolveCtx = resolveAuditHub
28903
28892
  function asHubReadError(err) {
28904
28893
  throw new RunUsageError(`could not read from the hub: ${errMessage(err)}`);
28905
28894
  }
28906
- function exitWithNoSpecs(format, reason, message) {
28907
- if (format === "json") process.stdout.write(`${JSON.stringify({
28895
+ /**
28896
+ * Nothing to audit — still a result, and still written. A reader that found
28897
+ * yesterday's `audit.json` where this sweep left none would act on findings
28898
+ * nothing had re-checked.
28899
+ */
28900
+ async function exitWithNoSpecs(ctx, reason, message) {
28901
+ const { format, reportDir } = ctx;
28902
+ const report = {
28908
28903
  specs: [],
28909
28904
  skipped: reason
28910
- }, null, 2)}\n`);
28905
+ };
28906
+ if (format === "json") process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
28911
28907
  else if (format === "text") info(message);
28912
28908
  else if (format === "github" && reason === "noSpecsFound") process.stdout.write(`::warning::${message}\n`);
28913
- process.exit(0);
28909
+ process.exit(await tryWriteReport(report, reportDir, format) ? 0 : 2);
28914
28910
  }
28915
28911
  /**
28916
28912
  * The cases this sweep audits.
@@ -35,9 +35,9 @@ declare const RunSchema: z.ZodObject<{
35
35
  running: "running";
36
36
  }>;
37
37
  kind: z.ZodDefault<z.ZodEnum<{
38
- record: "record";
39
38
  run: "run";
40
39
  drift: "drift";
40
+ record: "record";
41
41
  }>>;
42
42
  drift: z.ZodDefault<z.ZodNullable<z.ZodObject<{
43
43
  specs: z.ZodNumber;
@@ -831,9 +831,9 @@ type ReportSpecResult = z.infer<typeof ReportSpecResultSchema>;
831
831
  declare const RunReportDataSchema: z.ZodObject<{
832
832
  schemaVersion: z.ZodLiteral<1>;
833
833
  kind: z.ZodDefault<z.ZodEnum<{
834
- record: "record";
835
834
  run: "run";
836
835
  drift: "drift";
836
+ record: "record";
837
837
  }>>;
838
838
  createdAt: z.ZodString;
839
839
  runId: z.ZodNullable<z.ZodString>;
@@ -35,9 +35,9 @@ declare const RunSchema: z.ZodObject<{
35
35
  running: "running";
36
36
  }>;
37
37
  kind: z.ZodDefault<z.ZodEnum<{
38
- record: "record";
39
38
  run: "run";
40
39
  drift: "drift";
40
+ record: "record";
41
41
  }>>;
42
42
  drift: z.ZodDefault<z.ZodNullable<z.ZodObject<{
43
43
  specs: z.ZodNumber;
@@ -831,9 +831,9 @@ type ReportSpecResult = z.infer<typeof ReportSpecResultSchema>;
831
831
  declare const RunReportDataSchema: z.ZodObject<{
832
832
  schemaVersion: z.ZodLiteral<1>;
833
833
  kind: z.ZodDefault<z.ZodEnum<{
834
- record: "record";
835
834
  run: "run";
836
835
  drift: "drift";
836
+ record: "record";
837
837
  }>>;
838
838
  createdAt: z.ZodString;
839
839
  runId: z.ZodNullable<z.ZodString>;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.56.0",
3
+ "version": "1.57.1",
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.56.0",
3
+ "version": "1.57.1",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {