ccqa 1.50.1 → 1.51.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
@@ -14664,32 +14664,42 @@ function requireReportToHubConnection(conn) {
14664
14664
  process.exit(2);
14665
14665
  }
14666
14666
  /**
14667
- * Open the run a `--report-to-hub` command patches into. Failure is fatal: a
14668
- * job that asked to publish and cannot reach the hub has not done what it was
14669
- * told. Thrown rather than exited, so a caller's `finally` still runs (the
14670
- * audit releases its spec claims there). Not retried: a dropped response after
14671
- * the hub committed would leave a second orphan running run.
14672
- */
14673
- async function openHubRun(kind, conn, cwd, profile) {
14674
- const [branch, gitHead] = await Promise.all([detectBranch(cwd), getGitHead(cwd)]);
14667
+ * Open the run a `--report-to-hub` command patches into, and name it on stderr
14668
+ * stdout belongs to the report (`--report-format json`), and a caller that
14669
+ * links to the run needs its id before the command ends (docs/hub.md).
14670
+ *
14671
+ * Failure is fatal: a job that asked to publish and cannot reach the hub has
14672
+ * not done what it was told. Thrown rather than exited, so a caller's
14673
+ * `finally` still runs (the audit releases its spec claims there). Not
14674
+ * retried: a dropped response after the hub committed would leave a second
14675
+ * orphan running run.
14676
+ */
14677
+ async function openHubRun(kind, conn, cwd, opts = {}) {
14678
+ const [branch, gitHead] = await Promise.all([detectBranch(cwd), opts.gitHead !== void 0 ? opts.gitHead : getGitHead(cwd)]);
14679
+ let opened;
14675
14680
  try {
14676
- const run = await conn.hub.openRun({
14681
+ opened = await conn.hub.openRun({
14677
14682
  project: conn.project,
14678
14683
  kind,
14679
14684
  ...branch ? { branch } : {},
14680
- ...profile ? { profile } : {},
14685
+ ...opts.profile ? { profile: opts.profile } : {},
14681
14686
  ...gitHead ? { gitHead } : {},
14687
+ ...opts.deployedSha ? { deployedSha: opts.deployedSha } : {},
14682
14688
  ...ciProvenance()
14683
14689
  });
14684
- return {
14685
- hub: conn.hub,
14686
- kind,
14687
- runId: run.id,
14688
- gitHead
14689
- };
14690
14690
  } catch (err) {
14691
14691
  throw new RunUsageError(`--report-to-hub: could not open a run on the hub (${errMessage(err)})`);
14692
14692
  }
14693
+ process.stderr.write(`[hub-run] ${JSON.stringify({
14694
+ id: opened.id,
14695
+ kind
14696
+ })}\n`);
14697
+ return {
14698
+ hub: conn.hub,
14699
+ kind,
14700
+ runId: opened.id,
14701
+ gitHead
14702
+ };
14693
14703
  }
14694
14704
  /**
14695
14705
  * Close an open run with its final rows and envelope, answering whether it
@@ -15437,25 +15447,17 @@ async function executeRun(targets, opts) {
15437
15447
  reportDir: null
15438
15448
  };
15439
15449
  }
15440
- const det = await runDeterministicSpecs(detSpecs, opts, cwd, reportDir, resources);
15441
15450
  let hubRunId = null;
15442
15451
  let hubSink;
15443
15452
  let hubPublishBroken = false;
15444
- if (hubCtx != null && opts.reportToHub) try {
15445
- const branch = await detectBranch(cwd);
15446
- const opened = await hubCtx.hub.openRun({
15447
- project: hubCtx.project,
15448
- ...branch ? { branch } : {},
15453
+ if (hubCtx != null && opts.reportToHub) {
15454
+ const { runId } = await openHubRun("run", hubCtx, cwd, {
15449
15455
  ...opts.hubProfile ? { profile: opts.hubProfile } : {},
15450
- ...git.head ? { gitHead: git.head } : {},
15451
- ...deployedSha ? { deployedSha } : {},
15452
- ...ciProvenance(),
15453
- kind: "run"
15456
+ gitHead: git.head,
15457
+ deployedSha
15454
15458
  });
15455
- hubRunId = opened.id;
15456
- info(`hub: incremental run opened (${opened.id})`);
15457
- await coverage?.linkHubRun(opened.id);
15458
- const runId = opened.id;
15459
+ hubRunId = runId;
15460
+ await coverage?.linkHubRun(runId);
15459
15461
  let hubPatchEverSucceeded = false;
15460
15462
  hubSink = { onUpsert: async (row) => {
15461
15463
  try {
@@ -15473,8 +15475,6 @@ async function executeRun(targets, opts) {
15473
15475
  } else warn(`hub: incremental push failed for ${row.feature}/${row.spec}: ${errMessage(err)}`);
15474
15476
  }
15475
15477
  } };
15476
- } catch (err) {
15477
- throw new RunUsageError(`--report-to-hub: could not open a run on the hub (${errMessage(err)})`);
15478
15478
  }
15479
15479
  const incrementalReport = createIncrementalReport(reportDir, buildReportEnvelope({
15480
15480
  git,
@@ -15499,6 +15499,7 @@ async function executeRun(targets, opts) {
15499
15499
  warn(`hub: could not finalize interrupted run ${hubRunId}: ${errMessage(err)}`);
15500
15500
  }
15501
15501
  });
15502
+ const det = await runDeterministicSpecs(detSpecs, opts, cwd, reportDir, resources);
15502
15503
  const externalRows = await runExternalSpecs(dispatch, {
15503
15504
  cwd,
15504
15505
  reportDir,
@@ -17986,8 +17987,7 @@ async function runRecord(specPath, opts) {
17986
17987
  }
17987
17988
  throw e;
17988
17989
  });
17989
- const push = pushConn ? await openHubRun("record", pushConn, cwdForProfile, opts.hubProfile) : null;
17990
- if (push) info(`hub: record run opened (${push.runId})`);
17990
+ const push = pushConn ? await openHubRun("record", pushConn, cwdForProfile, { ...opts.hubProfile ? { profile: opts.hubProfile } : {} }) : null;
17991
17991
  let recorded = false;
17992
17992
  let sealed = true;
17993
17993
  let tracingStep;
@@ -19065,10 +19065,7 @@ async function runAudit(specPath, opts) {
19065
19065
  let push = null;
19066
19066
  try {
19067
19067
  promptCtx = await resolveAuditPromptContext(opts, cwd);
19068
- if (pushConn) {
19069
- push = await openHubRun("drift", pushConn, cwd, opts.hubProfile);
19070
- if (format === "text") info(`hub: incremental drift run opened (${push.runId})`);
19071
- }
19068
+ if (pushConn) push = await openHubRun("drift", pushConn, cwd, { ...opts.hubProfile ? { profile: opts.hubProfile } : {} });
19072
19069
  results = await analyzeDrift({
19073
19070
  targets,
19074
19071
  cwd,
@@ -20613,7 +20610,7 @@ function createPatchRunHandler(config) {
20613
20610
  sendJson(ctx.res, 200, updated);
20614
20611
  };
20615
20612
  }
20616
- /** GET /api/v1/runs?project&branch&status&kind&since&until&limit */
20613
+ /** GET /api/v1/runs?project&branch&status&kind&ciRunId&since&until&limit */
20617
20614
  function createListRunsHandler(storage) {
20618
20615
  return async (ctx) => {
20619
20616
  const project = ctx.url.searchParams.get("project");
@@ -20621,11 +20618,13 @@ function createListRunsHandler(storage) {
20621
20618
  const status = ctx.url.searchParams.get("status");
20622
20619
  const limitRaw = ctx.url.searchParams.get("limit");
20623
20620
  const kindsRaw = ctx.url.searchParams.get("kind");
20621
+ const ciRunId = boundedParam(ctx.url.searchParams.get("ciRunId"), "ciRunId", 128);
20624
20622
  const runs = await storage.runs.list({
20625
20623
  ...project ? { project } : {},
20626
20624
  ...branch ? { branch } : {},
20627
20625
  ...status ? { status } : {},
20628
20626
  ...kindsRaw ? { kinds: kindsRaw.split(",").map(requireKind) } : {},
20627
+ ...ciRunId ? { ciRunId } : {},
20629
20628
  ...requireWindowParams(ctx.url),
20630
20629
  ...limitRaw ? { limit: Number(limitRaw) } : {}
20631
20630
  });
@@ -24817,12 +24816,15 @@ const CLIENT_JS = `
24817
24816
  // Disconnected: the full-screen login gate is the only thing to show.
24818
24817
  if (!state.token) { showAuthGate(false); return; }
24819
24818
  showAuthGate(true);
24819
+ // A run is addressed by its own id, not through a project, so a link from
24820
+ // outside (a chat message, a CI job) opens it even in a browser that has
24821
+ // never picked one — ahead of the gate below, which would drop the link.
24822
+ var m = parseRunHash(location.hash);
24823
+ if (m) { openRunDetail(m.runId, m.caseKey); return; }
24820
24824
  // With no project chosen yet, the Projects picker is the only useful view —
24821
24825
  // land there (e.g. right after login) instead of an empty Runs list, and
24822
- // gate any deep-linked #/runs or #/secrets to it too.
24826
+ // gate any deep-linked project-scoped view to it too.
24823
24827
  if (location.hash === "#/projects" || !state.project) { openProjects(); return; }
24824
- var m = parseRunHash(location.hash);
24825
- if (m) { openRunDetail(m.runId, m.caseKey); return; }
24826
24828
  if (location.hash === "#/perspectives") { openPerspectives(); return; }
24827
24829
  if (location.hash === "#/coverage") { openCoverage(); return; }
24828
24830
  if (location.hash === "#/secrets") { openSecrets(); return; }
@@ -26406,6 +26408,11 @@ const CLIENT_JS = `
26406
26408
  var runGone = false;
26407
26409
 
26408
26410
  apiFetch("/api/v1/runs/" + encodeURIComponent(runId)).then(function (run) {
26411
+ // Arrived by an outside link: the selected project is whatever the last
26412
+ // visit or the boot auto-select left, which need not be this run's.
26413
+ // Follow the run, so the nav around it answers for the project the page
26414
+ // is actually showing.
26415
+ if (run.project && run.project !== state.project) adoptProject(run.project);
26409
26416
  renderRunHead(run);
26410
26417
  }).catch(function (err) {
26411
26418
  runGone = err.status === 404;
@@ -28483,14 +28490,20 @@ const CLIENT_JS = `
28483
28490
  updateNavGate();
28484
28491
  }
28485
28492
 
28486
- // Scope to a project and land on its Runs view. Shared by the top menu, the
28487
- // Projects grid, and the "new project" flow. Switching project restores the
28488
- // profile last chosen for that project (or "default" if none was saved);
28489
- // the Secrets tab reloads the profile list when opened.
28490
- function chooseProject(p) {
28493
+ // Scope the UI to a project, restoring the profile last chosen for it (or
28494
+ // "default" if none was saved); the Secrets tab reloads the profile list when
28495
+ // opened. Separate from the navigation below because arriving on a run by
28496
+ // link adopts a project without going anywhere.
28497
+ function adoptProject(p) {
28491
28498
  setProject(p);
28492
28499
  storeProject(p);
28493
28500
  setProfile(storedProfileForProject(p) || "default");
28501
+ }
28502
+
28503
+ // Scope to a project and land on its Runs view. Shared by the top menu, the
28504
+ // Projects grid, and the "new project" flow.
28505
+ function chooseProject(p) {
28506
+ adoptProject(p);
28494
28507
  location.hash = "#/runs";
28495
28508
  route();
28496
28509
  }
@@ -30188,7 +30201,7 @@ function createFileRunStore(root) {
30188
30201
  async delete(id) {
30189
30202
  await serialize(runMetaPath(root, id), () => removePath(runDir(root, id)));
30190
30203
  },
30191
- async list({ project, branch, status, kinds, since, until, limit }) {
30204
+ async list({ project, branch, status, kinds, ciRunId, since, until, limit }) {
30192
30205
  const ids = await listSubdirsOrEmpty(runsDir(root));
30193
30206
  const inWindow = windowFilter({
30194
30207
  since,
@@ -30202,6 +30215,7 @@ function createFileRunStore(root) {
30202
30215
  if (branch !== void 0 && run.branch !== branch) continue;
30203
30216
  if (status !== void 0 && run.status !== status) continue;
30204
30217
  if (kinds !== void 0 && !kinds.includes(run.kind)) continue;
30218
+ if (ciRunId !== void 0 && run.ciRunId !== ciRunId) continue;
30205
30219
  if (!inWindow(run.createdAt)) continue;
30206
30220
  runs.push(run);
30207
30221
  }
@@ -1147,9 +1147,10 @@ interface HubClient {
1147
1147
  * `gitHead` stamps the run's commit at open time, so even a run that dies
1148
1148
  * before its final reconcile patch is attributable to a commit.
1149
1149
  *
1150
- * `deployedSha` does the same for the environment's commit. The hub would
1151
- * otherwise read its own deploy-log head here, and the open happens after
1152
- * the deterministic phase, not before the first spec.
1150
+ * `deployedSha` does the same for the environment's commit: the baseline the
1151
+ * caller selected against, which a `--rerun` inherits from an earlier run.
1152
+ * Left unset the hub reads its own deploy-log head, which by the time this
1153
+ * call lands can already name a later deploy.
1153
1154
  */
1154
1155
  openRun(meta: {
1155
1156
  project: string;
@@ -1165,11 +1166,13 @@ interface HubClient {
1165
1166
  }): Promise<Run>;
1166
1167
  /** Add finished spec rows (+ evidence) to a running run; `done` closes it. */
1167
1168
  patchRun(id: string, body: PatchRunRequest): Promise<Run>;
1169
+ /** `ciRunId` answers "which runs did this CI job create", whatever the window. */
1168
1170
  listRuns(q?: {
1169
1171
  project?: string;
1170
1172
  branch?: string;
1171
1173
  status?: RunStatus;
1172
1174
  kind?: Run["kind"];
1175
+ ciRunId?: string;
1173
1176
  limit?: number;
1174
1177
  }): Promise<Run[]>;
1175
1178
  getRun(id: string): Promise<Run>;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.50.1",
3
+ "version": "1.51.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.50.1",
3
+ "version": "1.51.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {