ccqa 1.60.2 → 1.60.4

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 CHANGED
@@ -338,6 +338,14 @@ function validateTestPathTemplate(template) {
338
338
  if (!template.includes("{spec}") && !template.includes("{case}")) return "testPath must contain {spec} or {case}, or every case would generate onto the same file";
339
339
  return null;
340
340
  }
341
+ /**
342
+ * The directory every test the template names sits under: its path up to the
343
+ * first placeholder's directory. Empty when the template opens with one.
344
+ */
345
+ function testPathRoot(template) {
346
+ const head = template.split("{")[0];
347
+ return head.slice(0, head.lastIndexOf("/") + 1).replace(/\/$/, "");
348
+ }
341
349
  /** What a recording beside its test is called: whose file it is, and what it is. */
342
350
  const RECORDING_EXTENSION = ".ccqa.ir.json";
343
351
  /**
@@ -9344,8 +9352,12 @@ const ViolationsSchema = z.object({ ruleViolations: z.array(z.unknown()) });
9344
9352
  * treats it as one it could not obtain.
9345
9353
  */
9346
9354
  const MAX_REVIEW_TURNS = 80;
9347
- /** A wedged call, not a slow one: a review still silent after this is stuck. */
9348
- const REVIEW_TIMEOUT_MS = 10 * 6e4;
9355
+ /**
9356
+ * A wedged call, not a slow one. A review of a real suite reads the test, its
9357
+ * page objects and many of the suite's files to count conventions, which runs
9358
+ * past ten minutes; one that is still going after twenty is stuck.
9359
+ */
9360
+ const REVIEW_TIMEOUT_MS = 20 * 6e4;
9349
9361
  /** Read-only: a reviewer reads the repository and changes nothing in it. */
9350
9362
  const REVIEW_TOOLS = [
9351
9363
  "Read",
@@ -14788,7 +14800,7 @@ const LLM_GEN_ALLOWED_TOOLS = [
14788
14800
  const LlmFileSchema = z.object({
14789
14801
  path: z.string().min(1),
14790
14802
  contents: z.string(),
14791
- kind: z.string().default("test")
14803
+ kind: z.string().optional()
14792
14804
  });
14793
14805
  const LlmOutputSchema = z.object({
14794
14806
  files: z.array(LlmFileSchema),
@@ -14820,21 +14832,29 @@ function parseLlmGenOutput(raw) {
14820
14832
  const issues = res.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`);
14821
14833
  throw new Error(`reply JSON does not match {files:[{path,contents,kind}],summary}: ${issues.join("; ")}`);
14822
14834
  }
14823
- const kindWarnings = [];
14824
14835
  return {
14825
- files: res.data.files.map((f) => {
14826
- if (f.kind === "test" || f.kind === "support") return {
14827
- ...f,
14828
- kind: f.kind
14829
- };
14830
- kindWarnings.push(`file "${f.path}": unknown kind "${f.kind}" coerced to "test"`);
14831
- return {
14832
- ...f,
14833
- kind: "test"
14834
- };
14835
- }),
14836
- summary: res.data.summary,
14837
- kindWarnings
14836
+ files: res.data.files.map((f) => ({
14837
+ path: f.path,
14838
+ contents: f.contents,
14839
+ kind: f.kind === "support" ? "support" : "test"
14840
+ })),
14841
+ summary: res.data.summary
14842
+ };
14843
+ }
14844
+ /**
14845
+ * The file at the configured test path is the test and every other file is
14846
+ * support — whatever the reply labelled them. The path already says which one
14847
+ * a file is, and a model that labels a page object "test" would otherwise fail
14848
+ * the whole generation over a word.
14849
+ */
14850
+ function kindByPath(output, policy) {
14851
+ const testAbs = resolve(policy.cwd, policy.testPath);
14852
+ return {
14853
+ ...output,
14854
+ files: output.files.map((f) => ({
14855
+ ...f,
14856
+ kind: resolve(policy.cwd, f.path) === testAbs ? "test" : "support"
14857
+ }))
14838
14858
  };
14839
14859
  }
14840
14860
  /**
@@ -14994,7 +15014,6 @@ async function generateWithLlmEngine(req) {
14994
15014
  requireTestFile: true,
14995
15015
  allowEmpty: false
14996
15016
  }), "run");
14997
- warnings.push(...output.kindWarnings);
14998
15017
  return finalizeAndVerify({
14999
15018
  ctx,
15000
15019
  target: req.target,
@@ -15480,10 +15499,12 @@ async function identifiersInProject(ctx, writeRoots, emitted) {
15480
15499
  const names = [...source.matchAll(/\bexport\s+(?:default\s+)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/g)].map((m) => m[1]);
15481
15500
  owners.set(rel, names);
15482
15501
  }
15502
+ const testsRoot = ctx.targetConfig.testPath ? testPathRoot(ctx.targetConfig.testPath) : "";
15483
15503
  const roots = new Set([
15484
15504
  ...writeRoots,
15485
15505
  ...ctx.resources.map((r) => "path" in r ? r.path : ""),
15486
- dirname(ctx.testPath)
15506
+ dirname(ctx.testPath),
15507
+ testsRoot
15487
15508
  ].filter((r) => r.length > 0).map((r) => resolve(ctx.cwd, r)));
15488
15509
  const found = new Map([...emitted.keys()].map((rel) => [rel, /* @__PURE__ */ new Set()]));
15489
15510
  const seen = /* @__PURE__ */ new Set();
@@ -15559,8 +15580,7 @@ async function invokeForFiles(p) {
15559
15580
  model: p.ctx.model
15560
15581
  }, () => {});
15561
15582
  if (isError) throw new Error(`Claude invocation failed: ${tail(result)}`);
15562
- const output = parseLlmGenOutput(result);
15563
- for (const w of output.kindWarnings) warn(w);
15583
+ const output = kindByPath(parseLlmGenOutput(result), p.policy);
15564
15584
  const errors = validateOutput(output, p);
15565
15585
  if (errors.length > 0) throw new Error(errors.join("; "));
15566
15586
  return output;
@@ -15603,8 +15623,7 @@ function validateOutput(output, p) {
15603
15623
  if (fileError) errors.push(fileError);
15604
15624
  }
15605
15625
  const tests = output.files.filter((f) => f.kind === "test");
15606
- if (p.requireTestFile && tests.length === 0) errors.push("output contains no \"kind\": \"test\" file");
15607
- if (tests.length > 1) errors.push(`output contains ${tests.length} "kind": "test" files; a spec has exactly one`);
15626
+ if (p.requireTestFile && tests.length === 0) errors.push(`the test file must be written to ${p.policy.testPath}; the reply has no file there`);
15608
15627
  return errors;
15609
15628
  }
15610
15629
  function toPromptResource(r) {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.60.2",
3
+ "version": "1.60.4",
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": "1.60.2",
3
+ "version": "1.60.4",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {