ccqa 1.8.1 → 1.8.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 +45 -38
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -3248,7 +3248,7 @@ function hashTriageUserPrompt(text) {
|
|
|
3248
3248
|
*/
|
|
3249
3249
|
const CHANGED_FILE_DIFF_TOOL = "mcp__diff__changed_file_diff";
|
|
3250
3250
|
function buildFailureAnalysisPrompt(input) {
|
|
3251
|
-
const { script, specYaml, failureLog, liveTranscriptExcerpt, diffPatch, changedFiles, baseRef, baseSource = null, range = null, driftIssues, artifactsDir = null, outputLanguage = "auto", triageUserPrompt, customPrompt } = input;
|
|
3251
|
+
const { script, specYaml, failureLog, liveTranscriptExcerpt, diffPatch, changedFiles, baseRef, baseSource = null, range = null, driftIssues, artifactsDir = null, outputLanguage = "auto", triageUserPrompt, customPrompt, baselineMissing = null } = input;
|
|
3252
3252
|
const lastGreen = baseSource === "last-green";
|
|
3253
3253
|
const triageUserPromptBlock = buildTriageUserPromptBlock(triageUserPrompt);
|
|
3254
3254
|
const customPromptBlock = buildCustomPromptBlock(customPrompt);
|
|
@@ -3257,7 +3257,13 @@ function buildFailureAnalysisPrompt(input) {
|
|
|
3257
3257
|
const baseLabel = lastGreen ? `this spec's last passing commit${baseRef && baseRef !== "last-green" ? ` (${baseRef})` : ""}` : baseRef ?? "base";
|
|
3258
3258
|
const rangeNote = range ? ` — spans ${range.commitCount} commit${range.commitCount === 1 ? "" : "s"} over ${range.days} day${range.days === 1 ? "" : "s"}` : "";
|
|
3259
3259
|
let diffBlock;
|
|
3260
|
-
if (
|
|
3260
|
+
if (baselineMissing) diffBlock = `## Source changes
|
|
3261
|
+
|
|
3262
|
+
No baseline exists for this spec (${baselineMissing}), so there is no source diff. Work from the current repository state instead:
|
|
3263
|
+
- Grep for the exact selector / text / aria-label the failing step targets. Absent or renamed while the user-visible flow the spec describes still exists → the test is stale. The flow itself no longer implemented → the spec is stale.
|
|
3264
|
+
- Without a change window you cannot attribute the failure to a specific change — do not claim a change "introduced" it. State what the current source shows.
|
|
3265
|
+
`;
|
|
3266
|
+
else if (diffPatch === null) diffBlock = `## Source changes
|
|
3261
3267
|
|
|
3262
3268
|
No diff context is available (the base ref could not be resolved, or there are no changes). Classify from the failure log, the spec, and what you can read in the repository — and be correspondingly more conservative: prefer UNKNOWN over a confident SPEC_CHANGE/PRODUCT_BUG call without diff evidence.
|
|
3263
3269
|
`;
|
|
@@ -3284,14 +3290,14 @@ A separate read-only audit compared the spec against the current source. Treat t
|
|
|
3284
3290
|
|
|
3285
3291
|
${driftIssues.map((i) => `- [${i.severity}] (${DRAFT_CATEGORY_LABEL[i.category]}${i.stepId ? `, step ${i.stepId}` : ""}) ${i.message}${i.detail ? ` — ${i.detail}` : ""}`).join("\n")}
|
|
3286
3292
|
` : "";
|
|
3287
|
-
return `You are analyzing a failing E2E regression test against the source changes since a known-good baseline. Your job is a root-cause CALL, not a fix: decide which of three categories explains the failure, using the source diff as your primary context
|
|
3293
|
+
return `${baselineMissing ? `You are analyzing a failing E2E regression test. No known-good baseline exists for this spec yet, so there is no source diff: your primary context is the failure evidence plus the CURRENT state of the repository, which you can inspect with the read-only tools. Your job is a root-cause CALL, not a fix: decide which of three categories explains the failure.` : `You are analyzing a failing E2E regression test against the source changes since a known-good baseline. Your job is a root-cause CALL, not a fix: decide which of three categories explains the failure, using the source diff as your primary context.`}
|
|
3288
3294
|
|
|
3289
3295
|
${languageBlock}## The three categories
|
|
3290
3296
|
|
|
3291
3297
|
The question that separates them: **is the behavior the spec describes still what the product intends?**
|
|
3292
3298
|
|
|
3293
3299
|
1. TEST_DRIFT — what the spec verifies is unchanged; only the test code drifted from the source. Typical: a selector/aria-label/placeholder rename, a timing change, an over-tight assertion. The diff shows a change that is invisible to the user's intent but visible to the test.
|
|
3294
|
-
2. SPEC_CHANGE — the thing being verified itself changed: the UI flow, the layout, the feature's intended behavior. The diff deliberately changes what the spec asserts. You MUST cite the diff hunk (file + what changed) as evidence for this label.
|
|
3300
|
+
2. SPEC_CHANGE — the thing being verified itself changed: the UI flow, the layout, the feature's intended behavior. ${baselineMissing ? "The current source deliberately implements something other than what the spec asserts. You MUST cite the source file you read as evidence for this label." : "The diff deliberately changes what the spec asserts. You MUST cite the diff hunk (file + what changed) as evidence for this label."}
|
|
3295
3301
|
3. PRODUCT_BUG — neither of the above: the failure is not explained by the diff nor by test staleness. The product regressed.
|
|
3296
3302
|
|
|
3297
3303
|
If the evidence is too weak to choose, answer UNKNOWN — a wrong confident call is worse than an honest UNKNOWN, because humans grade these predictions to measure accuracy.
|
|
@@ -3303,19 +3309,24 @@ You can call \`Grep\`, \`Glob\`, and \`Read\` against the current repository (po
|
|
|
3303
3309
|
- read the changed files in full when the truncated patch is not enough,
|
|
3304
3310
|
- check whether the element/flow the spec describes still exists in the source.
|
|
3305
3311
|
|
|
3306
|
-
You can also call \`${CHANGED_FILE_DIFF_TOOL}\` with a file path to fetch that file's diff hunk for this run's base...HEAD range. The inline patch below is scoped to this spec's relatedPaths — files OUTSIDE that scope still appear in "Changed files (name-status)" but their hunks are not inlined. Before blaming (or ruling out) such a file, fetch its diff with this tool; Read only shows you its post-change state, not what changed
|
|
3312
|
+
${baselineMissing ? `There is no diff range for this run, so the \`${CHANGED_FILE_DIFF_TOOL}\` tool has nothing to return — every conclusion must come from the current source state plus the failure evidence.` : `You can also call \`${CHANGED_FILE_DIFF_TOOL}\` with a file path to fetch that file's diff hunk for this run's base...HEAD range. The inline patch below is scoped to this spec's relatedPaths — files OUTSIDE that scope still appear in "Changed files (name-status)" but their hunks are not inlined. Before blaming (or ruling out) such a file, fetch its diff with this tool; Read only shows you its post-change state, not what changed.`}
|
|
3307
3313
|
${artifactsDir ? `\nThe test runner wrote this run's artifacts under \`${artifactsDir}\` (relative to the working directory). Read them for failure context the log tail above may not carry — e.g. a Playwright \`error-context.md\` holds the page's accessibility snapshot at the moment of failure, which often shows directly whether the awaited element was present. Do NOT open image/trace binaries.\n` : ""}
|
|
3308
3314
|
You have **up to 12 tool turns**. Do NOT write, edit, run shell commands, or hit the network.
|
|
3309
3315
|
|
|
3310
3316
|
## Decision guidance
|
|
3311
3317
|
|
|
3312
|
-
${
|
|
3318
|
+
${baselineMissing ? `There is no baseline, so there is no "what changed" evidence at all. Classify from the failure signature checked against the current source:
|
|
3319
|
+
|
|
3320
|
+
- The selector / text / attribute the failing step targets is absent or renamed in the current source, while the user-visible flow the spec describes still exists → TEST_DRIFT (cite the file:line where the renamed/replacement element lives).
|
|
3321
|
+
- The flow or feature the spec describes is no longer implemented — page gone, component removed, copy redefined, feature reworked → SPEC_CHANGE (cite the file you read that shows the new shape).
|
|
3322
|
+
- The flow exists and the test's selectors still match the source, but the observed behavior is wrong (error response, missing side effect, wrong data) → lean PRODUCT_BUG; FIRST rule out environment/data/timing causes (daemon not running, network down, missing credentials, stale test data) — those read as UNKNOWN with low confidence, not PRODUCT_BUG.
|
|
3323
|
+
- Without diff evidence, treat 0.7 as a practical confidence ceiling unless the current source alone is conclusive (e.g. the targeted selector is verifiably gone).` : `${lastGreen ? `The baseline is the commit where THIS spec last passed, so the range strictly covers the window in which it broke: the cause is either inside these changes or outside the code entirely (flaky timing, environment, an external service, test data). The range may mix several unrelated merges — most of the diff is noise; what matters is the specific change you can tie to the failing step.` : `The baseline is a fixed ref (typically the PR base): the spec is NOT guaranteed to have passed there, so the range is not guaranteed to contain the cause.`}
|
|
3313
3324
|
|
|
3314
3325
|
- Diff touches only attributes/identifiers the test selects on (labels, testids, class names, timing) while the user-visible flow is intact → TEST_DRIFT.
|
|
3315
3326
|
- Diff intentionally removes/reworks the UI or flow that a spec step verifies (component deleted, page restructured, copy redefined, feature flag flipped) → SPEC_CHANGE.
|
|
3316
3327
|
- Diff UNINTENTIONALLY breaks behavior the spec still intends — e.g. a refactor that drops a side effect, an inverted condition, a regression hiding inside a cleanup commit — → PRODUCT_BUG, citing the diff hunk as evidence. A product bug is often introduced BY the diff; what separates it from SPEC_CHANGE is intent: does the change read as a deliberate redesign of what the spec verifies, or as collateral damage?
|
|
3317
3328
|
${lastGreen ? `- No change in the range explains the failing step (after checking the inline patch, the name-status list, and any hunks you fetched) → the cause is outside the code: answer UNKNOWN with low confidence and name the suspected external cause (flaky timing, environment, external service, test data). Do NOT default to PRODUCT_BUG here — under this baseline a product regression must be tied to an in-range change.` : `- Diff is unrelated to the failing step (or there is no relevant diff) and the test was passing before → lean PRODUCT_BUG; first rule out timing/data flakiness and infrastructure errors (daemon not running, network down, missing credentials) — those read as UNKNOWN with low confidence, not PRODUCT_BUG.`}${range ? `
|
|
3318
|
-
- This range spans ${range.commitCount} commit${range.commitCount === 1 ? "" : "s"} over ${range.days} day${range.days === 1 ? "" : "s"}. The wider the range, the more unrelated changes are mixed in: SPEC_CHANGE and TEST_DRIFT still require citing the specific hunk — do not infer intent from the bulk of a large diff, and lower confidence when the evidence is spread thin.` : ""}
|
|
3329
|
+
- This range spans ${range.commitCount} commit${range.commitCount === 1 ? "" : "s"} over ${range.days} day${range.days === 1 ? "" : "s"}. The wider the range, the more unrelated changes are mixed in: SPEC_CHANGE and TEST_DRIFT still require citing the specific hunk — do not infer intent from the bulk of a large diff, and lower confidence when the evidence is spread thin.` : ""}`}
|
|
3319
3330
|
- The drift audit findings (when present) flag spec↔code mismatches; an ERROR there usually supports TEST_DRIFT or SPEC_CHANGE over PRODUCT_BUG.
|
|
3320
3331
|
|
|
3321
3332
|
## Sub-diagnosis vocabulary
|
|
@@ -3355,7 +3366,7 @@ Your **final** assistant message must start with \`{\` and end with \`}\` — a
|
|
|
3355
3366
|
- 0.4-0.7: plausible but another category could explain it
|
|
3356
3367
|
- < 0.4: answer UNKNOWN instead of guessing
|
|
3357
3368
|
|
|
3358
|
-
Evidence rules: TEST_DRIFT and SPEC_CHANGE require at least one concrete \`file\` reference (diff hunk or file:line you actually read). PRODUCT_BUG should cite the in-range change that unintentionally broke the behavior when one exists; ${lastGreen ? "under this last-green baseline, if no in-range change explains the failure, that is UNKNOWN (external cause), not PRODUCT_BUG" : "when no such change exists, explain why the diff does NOT account for the failure"}
|
|
3369
|
+
Evidence rules: TEST_DRIFT and SPEC_CHANGE require at least one concrete \`file\` reference (diff hunk or file:line you actually read). ${baselineMissing ? "With no baseline there is no in-range change to cite: PRODUCT_BUG must instead explain why current-state inspection rules out test staleness and spec change." : `PRODUCT_BUG should cite the in-range change that unintentionally broke the behavior when one exists; ${lastGreen ? "under this last-green baseline, if no in-range change explains the failure, that is UNKNOWN (external cause), not PRODUCT_BUG" : "when no such change exists, explain why the diff does NOT account for the failure"}.`}
|
|
3359
3370
|
|
|
3360
3371
|
## Test Spec (spec.yaml)
|
|
3361
3372
|
${specYaml}
|
|
@@ -5767,10 +5778,6 @@ function createFailureAnalysisPass(deps) {
|
|
|
5767
5778
|
...fields,
|
|
5768
5779
|
analysisSkipped: ANALYSIS_DISABLED
|
|
5769
5780
|
};
|
|
5770
|
-
if (!specDiffResult.ok) return {
|
|
5771
|
-
...fields,
|
|
5772
|
-
analysisSkipped: specDiffResult.skip
|
|
5773
|
-
};
|
|
5774
5781
|
if (!deps.auth.ok) return {
|
|
5775
5782
|
...fields,
|
|
5776
5783
|
analysisSkipped: deps.auth.reason
|
|
@@ -5779,16 +5786,18 @@ function createFailureAnalysisPass(deps) {
|
|
|
5779
5786
|
...fields,
|
|
5780
5787
|
analysisSkipped: "no spec.yaml found for this spec"
|
|
5781
5788
|
};
|
|
5782
|
-
|
|
5789
|
+
const baselineMissing = specDiffResult.ok ? null : specDiffResult.skip;
|
|
5790
|
+
info(`failure analysis: ${featureName}/${specName}${baselineMissing ? " (no baseline — classifying from current source)" : ""}`);
|
|
5783
5791
|
const outcome = await analyzeFailure({
|
|
5784
5792
|
script: await input.readScript(),
|
|
5785
5793
|
specYaml: input.specYaml,
|
|
5786
5794
|
failureLog: input.failureLog,
|
|
5787
|
-
diffPatch:
|
|
5788
|
-
changedFiles:
|
|
5789
|
-
baseRef:
|
|
5790
|
-
baseSource:
|
|
5791
|
-
range:
|
|
5795
|
+
diffPatch: specDiff?.patch ?? null,
|
|
5796
|
+
changedFiles: specDiff?.nameStatus ?? null,
|
|
5797
|
+
baseRef: specDiff?.base.ref ?? null,
|
|
5798
|
+
baseSource: specDiff?.base.source ?? null,
|
|
5799
|
+
range: specDiff?.range ?? null,
|
|
5800
|
+
...baselineMissing ? { baselineMissing } : {},
|
|
5792
5801
|
driftIssues: input.driftIssues,
|
|
5793
5802
|
...input.artifactsDir ? { artifactsDir: input.artifactsDir } : {},
|
|
5794
5803
|
...deps.language ? { outputLanguage: deps.language } : {},
|
|
@@ -5797,7 +5806,7 @@ function createFailureAnalysisPass(deps) {
|
|
|
5797
5806
|
}, {
|
|
5798
5807
|
...deps.model ? { model: deps.model } : {},
|
|
5799
5808
|
cwd: deps.cwd,
|
|
5800
|
-
getFileDiff:
|
|
5809
|
+
getFileDiff: specDiff?.fileDiff ?? (() => null)
|
|
5801
5810
|
});
|
|
5802
5811
|
if (!printedHeader) {
|
|
5803
5812
|
printedHeader = true;
|
|
@@ -8244,26 +8253,24 @@ async function analyzeOneLiveFailure(r, diffProvider, driftForSpec, auth, opts,
|
|
|
8244
8253
|
failureLogExcerpt: null,
|
|
8245
8254
|
diffExcerpt: null
|
|
8246
8255
|
};
|
|
8247
|
-
const
|
|
8256
|
+
const specDiffResult = await diffProvider.forSpec({
|
|
8248
8257
|
featureName: r.featureName,
|
|
8249
8258
|
specName: r.specName
|
|
8250
8259
|
});
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
diffExcerpt: null
|
|
8256
|
-
};
|
|
8257
|
-
if (specDiff.error) info(`failure analysis: source diff unavailable (${specDiff.error}) — analyzing without diff context`);
|
|
8260
|
+
const specDiff = specDiffResult.ok ? specDiffResult : null;
|
|
8261
|
+
const baselineMissing = specDiffResult.ok ? null : specDiffResult.skip;
|
|
8262
|
+
if (baselineMissing) info(`failure analysis: no baseline (${baselineMissing}) — classifying from current source`);
|
|
8263
|
+
else if (specDiff?.error) info(`failure analysis: source diff unavailable (${specDiff.error}) — analyzing without diff context`);
|
|
8258
8264
|
const customPrompt = resolveCustomPromptForTarget(opts.customPrompt, AGENT_BROWSER_TARGET);
|
|
8259
8265
|
const outcome = await analyzeFailure({
|
|
8260
8266
|
liveTranscriptExcerpt: excerpt,
|
|
8261
8267
|
specYaml: r.specYaml,
|
|
8262
|
-
diffPatch: specDiff
|
|
8263
|
-
changedFiles: specDiff
|
|
8264
|
-
baseRef: specDiff
|
|
8265
|
-
baseSource: specDiff
|
|
8266
|
-
range: specDiff
|
|
8268
|
+
diffPatch: specDiff?.patch ?? null,
|
|
8269
|
+
changedFiles: specDiff?.nameStatus ?? null,
|
|
8270
|
+
baseRef: specDiff?.base.ref ?? null,
|
|
8271
|
+
baseSource: specDiff?.base.source ?? null,
|
|
8272
|
+
range: specDiff?.range ?? null,
|
|
8273
|
+
...baselineMissing ? { baselineMissing } : {},
|
|
8267
8274
|
driftIssues: driftForSpec,
|
|
8268
8275
|
...opts.language ? { outputLanguage: opts.language } : {},
|
|
8269
8276
|
...opts.triageUserPrompt ? { triageUserPrompt: opts.triageUserPrompt } : {},
|
|
@@ -8271,7 +8278,7 @@ async function analyzeOneLiveFailure(r, diffProvider, driftForSpec, auth, opts,
|
|
|
8271
8278
|
}, {
|
|
8272
8279
|
...opts.model ? { model: opts.model } : {},
|
|
8273
8280
|
cwd,
|
|
8274
|
-
getFileDiff: specDiff
|
|
8281
|
+
getFileDiff: specDiff?.fileDiff ?? (() => null)
|
|
8275
8282
|
});
|
|
8276
8283
|
const pct = Math.round(outcome.analysis.confidence * 100);
|
|
8277
8284
|
const headline = outcome.analysis.headline.trim() || (outcome.analysis.reasoning.split("\n")[0] ?? "").trim();
|
|
@@ -8280,11 +8287,11 @@ async function analyzeOneLiveFailure(r, diffProvider, driftForSpec, auth, opts,
|
|
|
8280
8287
|
analysis: outcome.analysis,
|
|
8281
8288
|
analysisSkipped: null,
|
|
8282
8289
|
failureLogExcerpt: excerpt,
|
|
8283
|
-
diffExcerpt: specDiff
|
|
8284
|
-
analysisBase: {
|
|
8290
|
+
diffExcerpt: specDiff?.patch ?? null,
|
|
8291
|
+
...specDiff ? { analysisBase: {
|
|
8285
8292
|
ref: specDiff.base.ref,
|
|
8286
8293
|
sha: specDiff.base.sha
|
|
8287
|
-
},
|
|
8294
|
+
} } : {},
|
|
8288
8295
|
...customPrompt ? { customPromptVersion: customPrompt.customPromptVersion } : {}
|
|
8289
8296
|
};
|
|
8290
8297
|
}
|
|
@@ -11069,7 +11076,7 @@ function buildReportEnvelope(args) {
|
|
|
11069
11076
|
},
|
|
11070
11077
|
model: opts.model ?? null,
|
|
11071
11078
|
language: opts.language ?? null,
|
|
11072
|
-
promptVersion: "
|
|
11079
|
+
promptVersion: "8",
|
|
11073
11080
|
customPromptVersion,
|
|
11074
11081
|
...triageUserPromptHash !== null ? { triageUserPromptHash } : {}
|
|
11075
11082
|
};
|
|
@@ -19472,7 +19479,7 @@ function createLearningWorker(deps) {
|
|
|
19472
19479
|
const prevCustomPrompt = await loadStoredCustomPrompt(storage, job.project);
|
|
19473
19480
|
const customPrompt = {
|
|
19474
19481
|
schemaVersion: 1,
|
|
19475
|
-
basePromptVersion: "
|
|
19482
|
+
basePromptVersion: "8",
|
|
19476
19483
|
customPromptVersion: `${generatedAt}-c${fallbackCases.length}`,
|
|
19477
19484
|
generatedAt,
|
|
19478
19485
|
guidance: fallbackGuidance ?? "",
|
package/dist/package.json
CHANGED