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 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 ANTHROPIC_API_KEY / network connectivity");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
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": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {