ccqa 1.40.7 → 1.40.8
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 +65 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -16360,6 +16360,53 @@ async function collectChangedSpecs(specs, opts) {
|
|
|
16360
16360
|
};
|
|
16361
16361
|
}
|
|
16362
16362
|
//#endregion
|
|
16363
|
+
//#region src/run/measure-backfill.ts
|
|
16364
|
+
/**
|
|
16365
|
+
* `ccqa run --measure-backfill <n>`: keep the measured-reach edges alive.
|
|
16366
|
+
*
|
|
16367
|
+
* Selection (ADR-0024) consumes each spec's most recent measured reach, and
|
|
16368
|
+
* an edge expires after `EDGE_MAX_AGE_MS`. Nothing else re-measures: an
|
|
16369
|
+
* unmeasured spec answers `unknown`, `unknown` marks nothing due (ADR-0023),
|
|
16370
|
+
* and a suite can settle into a state where no run ever fires — the seed
|
|
16371
|
+
* deadlock. Appending a few unmeasured-or-aging specs to every selected run
|
|
16372
|
+
* breaks that loop and keeps the whole suite inside the freshness window
|
|
16373
|
+
* without a scheduled full sweep.
|
|
16374
|
+
*/
|
|
16375
|
+
/**
|
|
16376
|
+
* Re-measure once an edge has spent half its lifetime. Half, not "expired":
|
|
16377
|
+
* a spec re-measured only after expiry answers `unknown` for the gap between
|
|
16378
|
+
* expiry and the next run, which is exactly the window this flag exists to
|
|
16379
|
+
* close.
|
|
16380
|
+
*/
|
|
16381
|
+
const REMEASURE_AFTER_MS = EDGE_MAX_AGE_MS / 2;
|
|
16382
|
+
/**
|
|
16383
|
+
* Picks up to `limit` specs from `inventory` worth re-measuring: ones with no
|
|
16384
|
+
* edge at all first (they cost an `unknown` verdict today), then the oldest
|
|
16385
|
+
* measured ones. Specs already selected for this run are never doubled.
|
|
16386
|
+
*/
|
|
16387
|
+
function chooseMeasureBackfill(inventory, selected, edges, limit, now) {
|
|
16388
|
+
const alreadyRunning = new Set(selected.map(specKey));
|
|
16389
|
+
const missing = [];
|
|
16390
|
+
const aging = [];
|
|
16391
|
+
for (const spec of inventory) {
|
|
16392
|
+
if (alreadyRunning.has(specKey(spec))) continue;
|
|
16393
|
+
const edge = edges.get(specKey(spec));
|
|
16394
|
+
if (edge === void 0) missing.push(spec);
|
|
16395
|
+
else if (now - edge.measuredAt > REMEASURE_AFTER_MS) aging.push({
|
|
16396
|
+
spec,
|
|
16397
|
+
measuredAt: edge.measuredAt
|
|
16398
|
+
});
|
|
16399
|
+
}
|
|
16400
|
+
aging.sort((a, b) => a.measuredAt - b.measuredAt);
|
|
16401
|
+
const specs = [...missing, ...aging.map((entry) => entry.spec)].slice(0, limit);
|
|
16402
|
+
const missingTaken = Math.min(missing.length, specs.length);
|
|
16403
|
+
return {
|
|
16404
|
+
specs,
|
|
16405
|
+
missing: missingTaken,
|
|
16406
|
+
aging: specs.length - missingTaken
|
|
16407
|
+
};
|
|
16408
|
+
}
|
|
16409
|
+
//#endregion
|
|
16363
16410
|
//#region src/run/pipeline.ts
|
|
16364
16411
|
async function resolveVitestConfig(cwd) {
|
|
16365
16412
|
const userConfig = resolve(cwd, ".ccqa/vitest.config.ts");
|
|
@@ -16538,6 +16585,10 @@ async function executeRun(targets, opts) {
|
|
|
16538
16585
|
if (rerunProfile !== null && hubCtx == null) throw new RunUsageError(needsHubConnection("--only-hub-rerun-needed"));
|
|
16539
16586
|
if (opts.reportToHub && hubCtx == null) throw new RunUsageError(REPORT_TO_HUB_NEEDS_CONNECTION);
|
|
16540
16587
|
if (opts.learnHubLivePrompt && hubCtx == null) throw new RunUsageError(needsHubConnection("--learn-hub-live-prompt"));
|
|
16588
|
+
if ((opts.measureBackfill ?? 0) > 0) {
|
|
16589
|
+
if (opts.coverage !== true) throw new RunUsageError("--measure-backfill does nothing without --coverage — there is no measurement to keep fresh");
|
|
16590
|
+
if (!filtering) throw new RunUsageError("--measure-backfill needs a selection flag (--only-hub-rerun-needed / --only-affected-by); an explicit spec list runs exactly what was asked");
|
|
16591
|
+
}
|
|
16541
16592
|
let coverageInbox;
|
|
16542
16593
|
if (opts.coverageInbox === "hub") {
|
|
16543
16594
|
if (opts.coverage !== true) throw new RunUsageError("--coverage-inbox hub does nothing without --coverage — there is no measurement to stream");
|
|
@@ -16586,6 +16637,7 @@ async function executeRun(targets, opts) {
|
|
|
16586
16637
|
};
|
|
16587
16638
|
const enumerateAll = () => listAllSpecsWithSpecFile(cwd);
|
|
16588
16639
|
let specs = dedupeSpecs((await Promise.all((targets.length ? targets : [void 0]).map((t) => resolveSpecTargets(t, enumerateAll, cwd)))).flat());
|
|
16640
|
+
const inventory = specs;
|
|
16589
16641
|
if (filtering) {
|
|
16590
16642
|
const before = specs.length;
|
|
16591
16643
|
let inProgress = 0;
|
|
@@ -16616,6 +16668,14 @@ async function executeRun(targets, opts) {
|
|
|
16616
16668
|
throw new RunUsageError("nothing was selected and no spec was cleared to run: exiting non-zero rather than reporting a green run that verified nothing");
|
|
16617
16669
|
}
|
|
16618
16670
|
}
|
|
16671
|
+
if (filtering && (opts.measureBackfill ?? 0) > 0 && hubCtx != null) {
|
|
16672
|
+
const edges = await loadCoverageEdges(hubCtx);
|
|
16673
|
+
const pick = chooseMeasureBackfill(inventory, specs, edges, opts.measureBackfill ?? 0, Date.now());
|
|
16674
|
+
if (pick.specs.length > 0) {
|
|
16675
|
+
specs = [...specs, ...pick.specs];
|
|
16676
|
+
meta("measure-backfill", `${pick.specs.length} spec(s) appended (${pick.missing} unmeasured / ${pick.aging} aging)`);
|
|
16677
|
+
}
|
|
16678
|
+
}
|
|
16619
16679
|
if (specs.length === 0) {
|
|
16620
16680
|
warn("no specs to run");
|
|
16621
16681
|
return {
|
|
@@ -17570,7 +17630,11 @@ const runCommand = addHubOptions(addProfileOption(addLanguageOption(new Command(
|
|
|
17570
17630
|
}, "text").option("--report-to-hub", "Incrementally push the run report to the hub as the run progresses (open → patch per spec → finalize). Requires --hub-url/--hub-token (or CCQA_HUB_URL/CCQA_HUB_TOKEN). Without it, hub credentials are used only to fetch variables/sessions/prompts, not to push.").option("--coverage", "Measure what each spec actually reached in the application under test, and record it on the spec's report row. Needs a `coverage:` block in .ccqa/config.yaml naming the instrumented origins the spec cookie may go to. The browser half attaches to the target's browser from outside (nothing is emitted into generated tests; needs node 22+); the server half needs the application running with ccqa-tools.").option("--coverage-inbox <where>", "With --coverage: where the measurement's two sides meet. 'local' (default) binds a loopback inbox on this machine for the run's duration; 'hub' appends every event to the hub's durable coverage inbox instead — nothing listens on the runner, report.json carries no coverage, and the hub resolves per-spec results on read (requires a hub connection).", (raw) => {
|
|
17571
17631
|
if (COVERAGE_INBOX_MODES.includes(raw)) return raw;
|
|
17572
17632
|
throw new Error(`--coverage-inbox must be one of ${COVERAGE_INBOX_MODES.join(" | ")}`);
|
|
17573
|
-
}, "local").
|
|
17633
|
+
}, "local").option("--measure-backfill <n>", "With --coverage and a selection flag (--only-hub-rerun-needed / --only-affected-by): also run up to <n> specs whose measured reach is missing or past half its freshness window, unmeasured-first then oldest-first. This is what keeps selection's edges alive — without it an unmeasured spec answers `unknown` forever, because `unknown` marks nothing due and nothing else re-measures.", (raw) => {
|
|
17634
|
+
const n = Number(raw);
|
|
17635
|
+
if (!Number.isInteger(n) || n < 0) throw new Error("--measure-backfill must be a non-negative integer");
|
|
17636
|
+
return n;
|
|
17637
|
+
}).optionsGroup("Learning:").option("--learn-hub-live-prompt", "(live only) After the run finishes, ask Claude to refresh the \"live.agent\" prompt on the hub from a summary of the run. Requires a hub connection.").optionsGroup("Environment and connection:").option("--cwd <path>", "Working directory containing the .ccqa/ tree (monorepo support). Defaults to the current directory.").option("--project <name>", "Project name for the hub. Defaults to the current directory's name.")))).action(async (targets, opts) => {
|
|
17574
17638
|
await runCliAction(targets, opts);
|
|
17575
17639
|
});
|
|
17576
17640
|
/** Parse --concurrency: a positive integer. Rejects 0, negatives, non-integers. */
|
package/dist/package.json
CHANGED