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 +34 -10
- package/dist/hub-client/index.d.mts +2 -2
- package/dist/package.json +1 -1
- package/package.json +1 -1
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
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
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
|
|
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,
|
|
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
|
|
16581
|
+
const ctx = {
|
|
16559
16582
|
hub,
|
|
16560
16583
|
project: hubProject
|
|
16561
|
-
}
|
|
16562
|
-
const
|
|
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