ccqa 1.31.1 → 1.31.2

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
@@ -16487,19 +16487,42 @@ async function fetchAuditNeed(ctx, profile) {
16487
16487
  throw new RunUsageError(`${FLAG}: could not ask the hub which specs need auditing: ${errMessage(err)}`);
16488
16488
  }
16489
16489
  }
16490
+ /**
16491
+ * Specs whose drift-ledger entry is still open. The hub's audit-need answer is
16492
+ * deploy-based, and a merged fix changes only the spec tree — no deploy lands
16493
+ * on the spec, so the hub would never call it due again and the entry would
16494
+ * stay open forever. A drifted spec is due until the audit itself clears it.
16495
+ * Unreadable ledger degrades to the deploy-based answer alone: the sweep must
16496
+ * not die over the supplementary question.
16497
+ */
16498
+ async function fetchStillDrifted(ctx) {
16499
+ try {
16500
+ const ledger = await ctx.hub.getDriftLedger(ctx.project);
16501
+ return new Set(Object.entries(ledger.specs ?? {}).filter(([, entry]) => entry.label != null).map(([key]) => key));
16502
+ } catch {
16503
+ return /* @__PURE__ */ new Set();
16504
+ }
16505
+ }
16490
16506
  /** Worst-known-first, so the line leads with what has never been looked at. */
16491
16507
  const SUMMARY_ORDER = rankedOrder({
16492
16508
  neverAudited: 0,
16493
- cannotTell: 1,
16494
- deployReached: 2,
16495
- held: 3,
16496
- current: 4
16509
+ stillDrifted: 1,
16510
+ cannotTell: 2,
16511
+ deployReached: 3,
16512
+ held: 4,
16513
+ current: 5
16497
16514
  });
16498
- function selectSpecsNeedingAudit(targets, report) {
16515
+ function selectSpecsNeedingAudit(targets, report, stillDrifted = /* @__PURE__ */ new Set()) {
16499
16516
  const counts = /* @__PURE__ */ new Map();
16500
16517
  const selected = [];
16501
16518
  for (const target of targets) {
16502
- const need = report.specs[specKey(target)] ?? { because: "neverAudited" };
16519
+ const key = specKey(target);
16520
+ if (stillDrifted.has(key)) {
16521
+ counts.set("stillDrifted", (counts.get("stillDrifted") ?? 0) + 1);
16522
+ selected.push(target);
16523
+ continue;
16524
+ }
16525
+ const need = report.specs[key] ?? { because: "neverAudited" };
16503
16526
  counts.set(need.because, (counts.get(need.because) ?? 0) + 1);
16504
16527
  if (needsAudit(need)) selected.push(target);
16505
16528
  }
@@ -16511,7 +16534,7 @@ function selectSpecsNeedingAudit(targets, report) {
16511
16534
  //#endregion
16512
16535
  //#region src/cli/audit.ts
16513
16536
  const DEFAULT_CONCURRENCY = 3;
16514
- 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` judges reached by the diff against <ref> (e.g. origin/main). In pull_request CI, pass $GITHUB_BASE_REF. Costs one model call; specs it cannot decide 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, and one the hub cannot answer for is audited rather than skipped. 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").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) => {
16537
+ 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` judges reached by the diff against <ref> (e.g. origin/main). In pull_request CI, pass $GITHUB_BASE_REF. Costs one model call; specs it cannot decide 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").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) => {
16515
16538
  await withCostReporting("audit", () => runAudit(specPath, opts));
16516
16539
  }));
16517
16540
  async function runAudit(specPath, opts) {
@@ -16555,11 +16578,12 @@ async function runAudit(specPath, opts) {
16555
16578
  }
16556
16579
  if (opts.onlyHubAuditNeeded) {
16557
16580
  const total = targets.length;
16558
- const report = await fetchAuditNeed({
16581
+ const ctx = {
16559
16582
  hub,
16560
16583
  project: hubProject
16561
- }, opts.hubProfile);
16562
- const selection = selectSpecsNeedingAudit(targets, report);
16584
+ };
16585
+ const [report, stillDrifted] = await Promise.all([fetchAuditNeed(ctx, opts.hubProfile), fetchStillDrifted(ctx)]);
16586
+ const selection = selectSpecsNeedingAudit(targets, report, stillDrifted);
16563
16587
  targets = selection.selected;
16564
16588
  if (format === "text") {
16565
16589
  meta("hub", selection.summary);
@@ -481,8 +481,8 @@ declare const ReportSpecResultSchema: z.ZodObject<{
481
481
  title: z.ZodNullable<z.ZodString>;
482
482
  target: z.ZodOptional<z.ZodString>;
483
483
  mode: z.ZodOptional<z.ZodEnum<{
484
- deterministic: "deterministic";
485
484
  live: "live";
485
+ deterministic: "deterministic";
486
486
  }>>;
487
487
  status: z.ZodEnum<{
488
488
  passed: "passed";
@@ -665,8 +665,8 @@ declare const RunReportDataSchema: z.ZodObject<{
665
665
  title: z.ZodNullable<z.ZodString>;
666
666
  target: z.ZodOptional<z.ZodString>;
667
667
  mode: z.ZodOptional<z.ZodEnum<{
668
- deterministic: "deterministic";
669
668
  live: "live";
669
+ deterministic: "deterministic";
670
670
  }>>;
671
671
  status: z.ZodEnum<{
672
672
  passed: "passed";
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.31.1",
3
+ "version": "1.31.2",
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.31.1",
3
+ "version": "1.31.2",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {