ccqa 1.45.1 → 1.46.0

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
@@ -223,6 +223,7 @@ const AGENT_BROWSER_TARGET = "agent-browser";
223
223
  */
224
224
  const TestSpecSchema = z.object({
225
225
  title: z.string().min(1),
226
+ disabled: z.boolean().optional(),
226
227
  target: TargetIdSchema.optional(),
227
228
  mode: SpecModeSchema.optional(),
228
229
  session: SessionFieldSchema.optional(),
@@ -647,6 +648,21 @@ async function getTestScript(featureName, specName, cwd) {
647
648
  async function listAllSpecsWithSpecFile(cwd) {
648
649
  return listAllSpecsFilteredBy(SPEC_FILE, cwd);
649
650
  }
651
+ /**
652
+ * The active suite — the tree minus the specs marked `disabled` — which is
653
+ * what `ccqa run` expands "all specs" to. Kept separate from the enumeration
654
+ * above because `serialGroups` and `actors` validate that a spec *name*
655
+ * exists, and a disabled spec is still a name.
656
+ *
657
+ * A spec that will not read or parse stays in: unreadable is not the same as
658
+ * asking to be skipped.
659
+ */
660
+ async function listActiveSpecs(cwd) {
661
+ const all = await listAllSpecsWithSpecFile(cwd);
662
+ return (await Promise.all(all.map(async (ref) => {
663
+ return tryParseTestSpec(await tryReadSpecFile(ref.featureName, ref.specName, cwd))?.disabled === true ? null : ref;
664
+ }))).filter((ref) => ref !== null);
665
+ }
650
666
  async function listAllSpecsFilteredBy(requiredFilename, cwd) {
651
667
  const featuresDir = join(getCcqaDir(cwd), "features");
652
668
  const featureDirs = await readdir(featuresDir).catch(() => []);
@@ -676,13 +692,7 @@ async function resolveSpecTargets(target, enumerateAll, cwd) {
676
692
  specName
677
693
  }];
678
694
  }
679
- return (await listSpecsForFeature(target, cwd)).map((specName) => ({
680
- featureName: target,
681
- specName
682
- }));
683
- }
684
- async function listSpecsForFeature(featureName, cwd) {
685
- return readdir(join(getFeatureDir(featureName, cwd), "test-cases")).catch(() => []);
695
+ return (await listActiveSpecs(cwd)).filter((ref) => ref.featureName === target);
686
696
  }
687
697
  /**
688
698
  * Lists every feature/spec dir under .ccqa/features/, regardless of whether
@@ -4968,6 +4978,7 @@ const PerspectiveSpecSchema = z.object({
4968
4978
  steps: z.array(PerspectiveStepSchema).optional(),
4969
4979
  status: PerspectiveStatusSchema,
4970
4980
  changedAt: z.string().optional(),
4981
+ disabled: z.boolean().optional(),
4971
4982
  note: z.string().optional()
4972
4983
  }).strip();
4973
4984
  const PerspectiveFeatureSchema = z.object({
@@ -12166,6 +12177,7 @@ async function loadSpecInventory(cwd) {
12166
12177
  const content = await tryReadSpecFile(featureName, specName, cwd);
12167
12178
  if (content === null) return null;
12168
12179
  const spec = parseTestSpec(content, `${featureName}/${specName}/spec.yaml`);
12180
+ if (spec.disabled) return null;
12169
12181
  return {
12170
12182
  featureName,
12171
12183
  specName,
@@ -17096,7 +17108,7 @@ async function executeRun(targets, opts) {
17096
17108
  customPrompt,
17097
17109
  triageUserPrompt
17098
17110
  };
17099
- const enumerateAll = () => listAllSpecsWithSpecFile(cwd);
17111
+ const enumerateAll = () => listActiveSpecs(cwd);
17100
17112
  let specs = dedupeSpecs((await Promise.all((targets.length ? targets : [void 0]).map((t) => resolveSpecTargets(t, enumerateAll, cwd)))).flat());
17101
17113
  if (filtering) {
17102
17114
  const before = specs.length;
@@ -21128,13 +21140,15 @@ async function collectTargets(specPath, cwd) {
21128
21140
  specName
21129
21141
  }];
21130
21142
  }
21143
+ const active = new Set((await listActiveSpecs(cwd)).map(specKey));
21131
21144
  const out = [];
21132
21145
  for (const feature of tree) for (const spec of feature.specs) {
21133
21146
  if (!spec.hasSpecFile) continue;
21134
- out.push({
21147
+ const target = {
21135
21148
  featureName: feature.featureName,
21136
21149
  specName: spec.specName
21137
- });
21150
+ };
21151
+ if (active.has(specKey(target))) out.push(target);
21138
21152
  }
21139
21153
  return out;
21140
21154
  }
@@ -21446,7 +21460,7 @@ async function buildSkeleton(tree) {
21446
21460
  const config = await loadProjectConfig(process.cwd()).catch(() => null);
21447
21461
  const changedAt = await readSpecChangedAt(process.cwd());
21448
21462
  return (await Promise.all(tree.map(async (feature) => {
21449
- const specs = await Promise.all(feature.specs.filter((s) => s.hasSpecFile).map(async (s) => {
21463
+ const built = await Promise.all(feature.specs.filter((s) => s.hasSpecFile).map(async (s) => {
21450
21464
  const specYaml = await tryReadSpecFile(feature.featureName, s.specName);
21451
21465
  const meta = readSpecMeta(s.specName, specYaml);
21452
21466
  const plugin = resolveSpecTarget(specYaml, config);
@@ -21458,12 +21472,13 @@ async function buildSkeleton(tree) {
21458
21472
  summary: "",
21459
21473
  ...meta.steps.length > 0 ? { steps: meta.steps } : {},
21460
21474
  status,
21461
- ...lastEdit ? { changedAt: lastEdit } : {}
21475
+ ...lastEdit ? { changedAt: lastEdit } : {},
21476
+ ...meta.disabled ? { disabled: true } : {}
21462
21477
  };
21463
21478
  }));
21464
21479
  return {
21465
21480
  featureName: feature.featureName,
21466
- specs
21481
+ specs: built
21467
21482
  };
21468
21483
  }))).filter((f) => f.specs.length > 0).map((f) => ({
21469
21484
  featureName: f.featureName,
@@ -21526,13 +21541,19 @@ function withoutGeneratedAt(yamlText) {
21526
21541
  function noteKey(featureName, specName) {
21527
21542
  return `${featureName}/${specName}`;
21528
21543
  }
21529
- /** Lenient title/mode read from an already-loaded spec.yaml (null → defaults). */
21544
+ /**
21545
+ * Lenient read of an already-loaded spec.yaml. A file that is missing or will
21546
+ * not parse falls back to `defaults`, which reports the spec as enabled: a
21547
+ * spec nobody could read has not asked to be skipped.
21548
+ */
21530
21549
  function readSpecMeta(specName, specYaml) {
21531
- if (specYaml === null) return {
21550
+ const defaults = {
21532
21551
  title: specName,
21533
21552
  mode: DEFAULT_SPEC_MODE,
21534
- steps: []
21553
+ steps: [],
21554
+ disabled: false
21535
21555
  };
21556
+ if (specYaml === null) return defaults;
21536
21557
  try {
21537
21558
  const parsed = parse(specYaml);
21538
21559
  const title = typeof parsed.title === "string" && parsed.title.length > 0 ? parsed.title : specName;
@@ -21540,14 +21561,11 @@ function readSpecMeta(specName, specYaml) {
21540
21561
  return {
21541
21562
  title,
21542
21563
  mode: modeResult.success ? modeResult.data : DEFAULT_SPEC_MODE,
21543
- steps: transcribeSteps(parsed.steps)
21564
+ steps: transcribeSteps(parsed.steps),
21565
+ disabled: tryParseTestSpec(specYaml)?.disabled === true
21544
21566
  };
21545
21567
  } catch {
21546
- return {
21547
- title: specName,
21548
- mode: DEFAULT_SPEC_MODE,
21549
- steps: []
21550
- };
21568
+ return defaults;
21551
21569
  }
21552
21570
  }
21553
21571
  /**
@@ -23076,6 +23094,7 @@ function readSpecTargets(doc) {
23076
23094
  for (const spec of specs) {
23077
23095
  const specName = prop(spec, "specName");
23078
23096
  if (typeof specName !== "string") continue;
23097
+ if (prop(spec, "disabled") === true) continue;
23079
23098
  const changedAt = prop(spec, "changedAt");
23080
23099
  out.push({
23081
23100
  key: `${featureName}/${specName}`,
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "1.45.1",
3
+ "version": "1.46.0",
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.45.1",
3
+ "version": "1.46.0",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {