ccqa 0.3.7 → 0.3.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/README.md +2 -0
- package/dist/bin/ccqa.mjs +43 -28
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -267,6 +267,8 @@ ccqa generate-setup <name> Generate and validate setup test script
|
|
|
267
267
|
--auto / --no-interactive Same semantics as `generate`
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
+
All Claude-driven commands (`trace`, `trace-setup`, `generate`, `generate-setup`) accept `-m, --model <name>` to select the Claude model — pass an alias (`sonnet` | `opus` | `haiku`) or a full model ID (e.g. `claude-opus-4-7`). The flag overrides the `CCQA_MODEL` environment variable; when both are unset, the Claude Code CLI default is used. Authentication is handled by your local Claude Code login — no `ANTHROPIC_API_KEY` is required.
|
|
271
|
+
|
|
270
272
|
`<feature/spec>` is a 2-segment alias for the on-disk path `.ccqa/features/<feature>/test-cases/<spec>/`. Pass the alias, not the full directory path.
|
|
271
273
|
|
|
272
274
|
## File structure
|
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -339,8 +339,14 @@ async function timedPhase(label, fn, scope = "fix") {
|
|
|
339
339
|
}
|
|
340
340
|
//#endregion
|
|
341
341
|
//#region src/claude/invoke.ts
|
|
342
|
+
function resolveModel(explicit) {
|
|
343
|
+
if (explicit) return explicit;
|
|
344
|
+
const envModel = process.env["CCQA_MODEL"];
|
|
345
|
+
return envModel && envModel.length > 0 ? envModel : void 0;
|
|
346
|
+
}
|
|
342
347
|
async function invokeClaudeStreaming(options, onEvent) {
|
|
343
|
-
const { prompt, systemPrompt, allowedTools, disableBuiltinTools = false, maxTurns, env, onAbAction, onAbActionFailed } = options;
|
|
348
|
+
const { prompt, systemPrompt, allowedTools, disableBuiltinTools = false, maxTurns, env, model, onAbAction, onAbActionFailed } = options;
|
|
349
|
+
const resolvedModel = resolveModel(model);
|
|
344
350
|
let lastAbToolUseId = null;
|
|
345
351
|
const sdkOptions = {
|
|
346
352
|
systemPrompt,
|
|
@@ -348,6 +354,7 @@ async function invokeClaudeStreaming(options, onEvent) {
|
|
|
348
354
|
allowedTools: allowedTools ?? ["Bash(*)"],
|
|
349
355
|
permissionMode: "bypassPermissions",
|
|
350
356
|
allowDangerouslySkipPermissions: true,
|
|
357
|
+
...resolvedModel ? { model: resolvedModel } : {},
|
|
351
358
|
...env ? { env: {
|
|
352
359
|
...process.env,
|
|
353
360
|
...env
|
|
@@ -908,11 +915,11 @@ function envRefsToJsExpression(value) {
|
|
|
908
915
|
}
|
|
909
916
|
//#endregion
|
|
910
917
|
//#region src/cli/trace.ts
|
|
911
|
-
const traceCommand = new Command("trace").argument("<feature/spec>", "Spec id in '<feature>/<spec>' form (resolves to .ccqa/features/<feature>/test-cases/<spec>/)").description("Run agent-browser, verify assertions, and record structured actions").action(async (specPath) => {
|
|
918
|
+
const traceCommand = new Command("trace").argument("<feature/spec>", "Spec id in '<feature>/<spec>' form (resolves to .ccqa/features/<feature>/test-cases/<spec>/)").description("Run agent-browser, verify assertions, and record structured actions").option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").action(async (specPath, opts) => {
|
|
912
919
|
const { featureName, specName } = parseSpecPath(specPath);
|
|
913
|
-
await runTrace(featureName, specName);
|
|
920
|
+
await runTrace(featureName, specName, opts.model);
|
|
914
921
|
});
|
|
915
|
-
async function runTrace(featureName, specName) {
|
|
922
|
+
async function runTrace(featureName, specName, model) {
|
|
916
923
|
header("trace", `${featureName}/${specName}`);
|
|
917
924
|
await ensureCcqaDir();
|
|
918
925
|
const spec = parseTestSpec(await readSpecFile(featureName, specName));
|
|
@@ -951,6 +958,7 @@ async function runTrace(featureName, specName) {
|
|
|
951
958
|
AGENT_BROWSER_SESSION: sessionName,
|
|
952
959
|
PATH: pathWithAgentBrowserShim(process.env["PATH"])
|
|
953
960
|
},
|
|
961
|
+
model,
|
|
954
962
|
onAbAction: (abAction) => {
|
|
955
963
|
const action = parseAbAction(abAction);
|
|
956
964
|
if (action) traceActions.push(action);
|
|
@@ -1516,7 +1524,7 @@ ${snapshot}
|
|
|
1516
1524
|
}
|
|
1517
1525
|
//#endregion
|
|
1518
1526
|
//#region src/diagnose/diagnose.ts
|
|
1519
|
-
async function diagnose(input) {
|
|
1527
|
+
async function diagnose(input, options = {}) {
|
|
1520
1528
|
const { result: raw, isError } = await invokeClaudeStreaming({
|
|
1521
1529
|
prompt: buildDiagnosePrompt(input),
|
|
1522
1530
|
allowedTools: [
|
|
@@ -1524,7 +1532,8 @@ async function diagnose(input) {
|
|
|
1524
1532
|
"Grep",
|
|
1525
1533
|
"Glob"
|
|
1526
1534
|
],
|
|
1527
|
-
maxTurns: 10
|
|
1535
|
+
maxTurns: 10,
|
|
1536
|
+
model: options.model
|
|
1528
1537
|
}, () => {});
|
|
1529
1538
|
if (isError) return {
|
|
1530
1539
|
result: null,
|
|
@@ -1889,7 +1898,7 @@ const DEFAULT_CONFIDENCE_THRESHOLD = .8;
|
|
|
1889
1898
|
* or the diagnose loop chose to bail out early.
|
|
1890
1899
|
*/
|
|
1891
1900
|
async function runAutoFixLoop(input) {
|
|
1892
|
-
const { scriptPath, initialRun, specMarkdown, actions, maxRetries, mode, runVitest, agentBrowserSession, outputLanguage } = input;
|
|
1901
|
+
const { scriptPath, initialRun, specMarkdown, actions, maxRetries, mode, runVitest, agentBrowserSession, outputLanguage, model } = input;
|
|
1893
1902
|
let { exitCode, output, currentScript } = initialRun;
|
|
1894
1903
|
if (exitCode === 0) return true;
|
|
1895
1904
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -1905,7 +1914,8 @@ async function runAutoFixLoop(input) {
|
|
|
1905
1914
|
failureLog: output,
|
|
1906
1915
|
pageSnapshot: pageSnapshot ?? void 0,
|
|
1907
1916
|
mode,
|
|
1908
|
-
outputLanguage
|
|
1917
|
+
outputLanguage,
|
|
1918
|
+
model
|
|
1909
1919
|
});
|
|
1910
1920
|
if (!fixed) {
|
|
1911
1921
|
fix("bailed out; see diagnosis above");
|
|
@@ -1920,7 +1930,7 @@ async function runAutoFixLoop(input) {
|
|
|
1920
1930
|
return false;
|
|
1921
1931
|
}
|
|
1922
1932
|
async function diagnoseAndFix(input) {
|
|
1923
|
-
const { script, specMarkdown, actions, failureLog, pageSnapshot, mode, outputLanguage } = input;
|
|
1933
|
+
const { script, specMarkdown, actions, failureLog, pageSnapshot, mode, outputLanguage, model } = input;
|
|
1924
1934
|
const outcome = await timedPhase("diagnose", () => diagnose({
|
|
1925
1935
|
script,
|
|
1926
1936
|
specMarkdown,
|
|
@@ -1928,11 +1938,11 @@ async function diagnoseAndFix(input) {
|
|
|
1928
1938
|
failureLog,
|
|
1929
1939
|
pageSnapshot,
|
|
1930
1940
|
outputLanguage
|
|
1931
|
-
}), "fix");
|
|
1941
|
+
}, { model }), "fix");
|
|
1932
1942
|
if (outcome.sdkError) {
|
|
1933
1943
|
fix("diagnose: SDK error talking to Claude");
|
|
1934
1944
|
if (outcome.raw) fix(`diagnose raw: ${truncateForLog(outcome.raw)}`);
|
|
1935
|
-
hint("re-run later, or check
|
|
1945
|
+
hint("re-run later, or check your Claude Code login / network connectivity");
|
|
1936
1946
|
return null;
|
|
1937
1947
|
}
|
|
1938
1948
|
if (!outcome.result) {
|
|
@@ -2057,13 +2067,13 @@ function resolveMode(opts) {
|
|
|
2057
2067
|
}
|
|
2058
2068
|
//#endregion
|
|
2059
2069
|
//#region src/cli/generate.ts
|
|
2060
|
-
const generateCommand = new Command("generate").argument("<feature/spec>", "Spec id in '<feature>/<spec>' form (resolves to .ccqa/features/<feature>/test-cases/<spec>/)").description("Generate agent-browser test script from recorded trace actions. test.spec.ts is regenerated from actions.json on every run; pass --force to overwrite manual edits.").option("--max-retries <n>", "Maximum number of auto-fix retries", "3").option("--auto", "Apply auto-fixes without confirmation regardless of confidence (CI use)").option("--no-interactive", "Never prompt; only auto-apply when confidence is high, otherwise give up").option("--force", "Overwrite an existing test.spec.ts without warning").option("--no-snapshot", "Don't pin AGENT_BROWSER_SESSION / capture page snapshots after a failure (debug toggle)").option("--language <bcp47>", "Language for diagnose reasoning / hint text (e.g. 'en', 'ja')", "en").action(async (specPath, opts) => {
|
|
2070
|
+
const generateCommand = new Command("generate").argument("<feature/spec>", "Spec id in '<feature>/<spec>' form (resolves to .ccqa/features/<feature>/test-cases/<spec>/)").description("Generate agent-browser test script from recorded trace actions. test.spec.ts is regenerated from actions.json on every run; pass --force to overwrite manual edits.").option("--max-retries <n>", "Maximum number of auto-fix retries", "3").option("--auto", "Apply auto-fixes without confirmation regardless of confidence (CI use)").option("--no-interactive", "Never prompt; only auto-apply when confidence is high, otherwise give up").option("--force", "Overwrite an existing test.spec.ts without warning").option("--no-snapshot", "Don't pin AGENT_BROWSER_SESSION / capture page snapshots after a failure (debug toggle)").option("--language <bcp47>", "Language for diagnose reasoning / hint text (e.g. 'en', 'ja')", "en").option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").action(async (specPath, opts) => {
|
|
2061
2071
|
const { featureName, specName } = parseSpecPath(specPath);
|
|
2062
2072
|
const mode = resolveMode(opts);
|
|
2063
2073
|
const useSnapshot = opts.snapshot !== false;
|
|
2064
|
-
await runGenerate(featureName, specName, parseInt(opts.maxRetries, 10), mode, opts.force ?? false, useSnapshot, opts.language ?? "en");
|
|
2074
|
+
await runGenerate(featureName, specName, parseInt(opts.maxRetries, 10), mode, opts.force ?? false, useSnapshot, opts.language ?? "en", opts.model);
|
|
2065
2075
|
});
|
|
2066
|
-
async function runGenerate(featureName, specName, maxRetries, mode, force, useSnapshot, outputLanguage) {
|
|
2076
|
+
async function runGenerate(featureName, specName, maxRetries, mode, force, useSnapshot, outputLanguage, model) {
|
|
2067
2077
|
header("generate", `${featureName}/${specName}`);
|
|
2068
2078
|
await ensureCcqaDir();
|
|
2069
2079
|
const existingScriptPath = await getTestScript(featureName, specName);
|
|
@@ -2083,7 +2093,7 @@ async function runGenerate(featureName, specName, maxRetries, mode, force, useSn
|
|
|
2083
2093
|
meta("fix-mode", mode);
|
|
2084
2094
|
meta("language", outputLanguage);
|
|
2085
2095
|
blank();
|
|
2086
|
-
const cleanedActions = await cleanupActions$1(actions);
|
|
2096
|
+
const cleanedActions = await cleanupActions$1(actions, model);
|
|
2087
2097
|
if (cleanedActions.length !== actions.length) meta("cleaned", cleanedActions.length);
|
|
2088
2098
|
const scriptPath = await saveTestScript(featureName, specName, actionsToScript(cleanedActions, spec.title, setupScripts.length > 0 ? setupScripts : void 0));
|
|
2089
2099
|
meta("saved", scriptPath);
|
|
@@ -2114,7 +2124,8 @@ async function runGenerate(featureName, specName, maxRetries, mode, force, useSn
|
|
|
2114
2124
|
mode,
|
|
2115
2125
|
runVitest: runVitestForSession,
|
|
2116
2126
|
agentBrowserSession,
|
|
2117
|
-
outputLanguage
|
|
2127
|
+
outputLanguage,
|
|
2128
|
+
model
|
|
2118
2129
|
})) {
|
|
2119
2130
|
hint(`run 'ccqa run ${featureName}/${specName}' to execute the test`);
|
|
2120
2131
|
return;
|
|
@@ -2225,12 +2236,13 @@ async function runVitest$1(scriptPath, agentBrowserSession) {
|
|
|
2225
2236
|
currentScript
|
|
2226
2237
|
};
|
|
2227
2238
|
}
|
|
2228
|
-
async function cleanupActions$1(actions) {
|
|
2239
|
+
async function cleanupActions$1(actions, model) {
|
|
2229
2240
|
try {
|
|
2230
2241
|
const { result, isError } = await invokeClaudeStreaming({
|
|
2231
2242
|
prompt: buildCleanupPrompt(actions),
|
|
2232
2243
|
disableBuiltinTools: true,
|
|
2233
|
-
maxTurns: 1
|
|
2244
|
+
maxTurns: 1,
|
|
2245
|
+
model
|
|
2234
2246
|
}, () => {});
|
|
2235
2247
|
if (isError || !result) return actions;
|
|
2236
2248
|
const json = result.trim().replace(/^```(?:json)?\n?([\s\S]*?)\n?```$/, "$1").trim();
|
|
@@ -2409,10 +2421,10 @@ async function resolveSpecs(target) {
|
|
|
2409
2421
|
}
|
|
2410
2422
|
//#endregion
|
|
2411
2423
|
//#region src/cli/trace-setup.ts
|
|
2412
|
-
const traceSetupCommand = new Command("trace-setup").argument("<name>", "Setup name to trace (e.g. login)").description("Trace a setup procedure using dummy placeholder values").action(async (name) => {
|
|
2413
|
-
await runTraceSetup(name);
|
|
2424
|
+
const traceSetupCommand = new Command("trace-setup").argument("<name>", "Setup name to trace (e.g. login)").description("Trace a setup procedure using dummy placeholder values").option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").action(async (name, opts) => {
|
|
2425
|
+
await runTraceSetup(name, opts.model);
|
|
2414
2426
|
});
|
|
2415
|
-
async function runTraceSetup(name) {
|
|
2427
|
+
async function runTraceSetup(name, model) {
|
|
2416
2428
|
header("trace-setup", name);
|
|
2417
2429
|
await ensureCcqaDir();
|
|
2418
2430
|
const spec = parseSetupSpec(await readSetupSpecFile(name));
|
|
@@ -2442,6 +2454,7 @@ async function runTraceSetup(name) {
|
|
|
2442
2454
|
PATH: pathWithAgentBrowserShim(process.env["PATH"]),
|
|
2443
2455
|
ANTHROPIC_API_KEY: ""
|
|
2444
2456
|
},
|
|
2457
|
+
model,
|
|
2445
2458
|
onAbAction: (abAction) => {
|
|
2446
2459
|
const action = parseAbAction(scrubSecrets(abAction, secretsToScrub));
|
|
2447
2460
|
if (action) traceActions.push(action);
|
|
@@ -2537,11 +2550,11 @@ function scrubSecrets(line, secrets) {
|
|
|
2537
2550
|
}
|
|
2538
2551
|
//#endregion
|
|
2539
2552
|
//#region src/cli/generate-setup.ts
|
|
2540
|
-
const generateSetupCommand = new Command("generate-setup").argument("<name>", "Setup name to generate (e.g. login)").description("Clean up, validate, and templatize setup actions").option("--max-retries <n>", "Maximum number of auto-fix retries", "3").option("--from-dummy", "Resume from existing test.dummy.spec.ts (after manual fix)").option("--auto", "Apply auto-fixes without confirmation regardless of confidence (CI use)").option("--no-interactive", "Never prompt; only auto-apply when confidence is high, otherwise give up").option("--language <bcp47>", "Language for diagnose reasoning / hint text (e.g. 'en', 'ja')", "en").action(async (name, opts) => {
|
|
2553
|
+
const generateSetupCommand = new Command("generate-setup").argument("<name>", "Setup name to generate (e.g. login)").description("Clean up, validate, and templatize setup actions").option("--max-retries <n>", "Maximum number of auto-fix retries", "3").option("--from-dummy", "Resume from existing test.dummy.spec.ts (after manual fix)").option("--auto", "Apply auto-fixes without confirmation regardless of confidence (CI use)").option("--no-interactive", "Never prompt; only auto-apply when confidence is high, otherwise give up").option("--language <bcp47>", "Language for diagnose reasoning / hint text (e.g. 'en', 'ja')", "en").option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").action(async (name, opts) => {
|
|
2541
2554
|
const mode = resolveMode(opts);
|
|
2542
|
-
await runGenerateSetup(name, parseInt(opts.maxRetries, 10), opts.fromDummy ?? false, mode, opts.language ?? "en");
|
|
2555
|
+
await runGenerateSetup(name, parseInt(opts.maxRetries, 10), opts.fromDummy ?? false, mode, opts.language ?? "en", opts.model);
|
|
2543
2556
|
});
|
|
2544
|
-
async function runGenerateSetup(name, maxRetries, fromDummy, mode, outputLanguage) {
|
|
2557
|
+
async function runGenerateSetup(name, maxRetries, fromDummy, mode, outputLanguage, model) {
|
|
2545
2558
|
header("generate-setup", name);
|
|
2546
2559
|
await ensureCcqaDir();
|
|
2547
2560
|
const specContent = await readSetupSpecFile(name);
|
|
@@ -2562,7 +2575,7 @@ async function runGenerateSetup(name, maxRetries, fromDummy, mode, outputLanguag
|
|
|
2562
2575
|
meta("fix-mode", mode);
|
|
2563
2576
|
meta("language", outputLanguage);
|
|
2564
2577
|
blank();
|
|
2565
|
-
cleanedActions = await cleanupActions(actions);
|
|
2578
|
+
cleanedActions = await cleanupActions(actions, model);
|
|
2566
2579
|
if (cleanedActions.length !== actions.length) meta("cleaned", cleanedActions.length);
|
|
2567
2580
|
await writeFile(dummyPath, actionsToScript(cleanedActions, spec.title), "utf-8");
|
|
2568
2581
|
meta("saved", dummyPath);
|
|
@@ -2588,7 +2601,8 @@ async function runGenerateSetup(name, maxRetries, fromDummy, mode, outputLanguag
|
|
|
2588
2601
|
mode,
|
|
2589
2602
|
runVitest: runVitestForSession,
|
|
2590
2603
|
agentBrowserSession,
|
|
2591
|
-
outputLanguage
|
|
2604
|
+
outputLanguage,
|
|
2605
|
+
model
|
|
2592
2606
|
});
|
|
2593
2607
|
if (!passed) {
|
|
2594
2608
|
warn("auto-fix exhausted; setup test still failing");
|
|
@@ -2665,12 +2679,13 @@ async function runVitestResolved(scriptPath, agentBrowserSession) {
|
|
|
2665
2679
|
await unlink(tmpPath).catch(() => {});
|
|
2666
2680
|
}
|
|
2667
2681
|
}
|
|
2668
|
-
async function cleanupActions(actions) {
|
|
2682
|
+
async function cleanupActions(actions, model) {
|
|
2669
2683
|
try {
|
|
2670
2684
|
const { result, isError } = await invokeClaudeStreaming({
|
|
2671
2685
|
prompt: buildCleanupPrompt(actions),
|
|
2672
2686
|
disableBuiltinTools: true,
|
|
2673
|
-
maxTurns: 1
|
|
2687
|
+
maxTurns: 1,
|
|
2688
|
+
model
|
|
2674
2689
|
}, () => {});
|
|
2675
2690
|
if (isError || !result) return actions;
|
|
2676
2691
|
const json = result.trim().replace(/^```(?:json)?\n?([\s\S]*?)\n?```$/, "$1").trim();
|
package/dist/package.json
CHANGED