ccqa 1.45.1 → 1.46.1

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
  }
@@ -21358,6 +21372,7 @@ function comparePerspectivesSkeleton(local, remote) {
21358
21372
  }
21359
21373
  const fields = [];
21360
21374
  if (remoteSpec.title !== spec.title) fields.push("title");
21375
+ if ((remoteSpec.disabled ?? false) !== (spec.disabled ?? false)) fields.push("disabled");
21361
21376
  if (remoteSpec.status.mode !== spec.status.mode || remoteSpec.status.traced !== spec.status.traced || remoteSpec.status.generated !== spec.status.generated || remoteSpec.status.target !== spec.status.target) fields.push(`status (local: ${formatStatus(spec.status)}, hub: ${formatStatus(remoteSpec.status)})`);
21362
21377
  if (fields.length > 0) issues.push(`${key}: out of date — ${fields.join(", ")}`);
21363
21378
  }
@@ -21446,7 +21461,7 @@ async function buildSkeleton(tree) {
21446
21461
  const config = await loadProjectConfig(process.cwd()).catch(() => null);
21447
21462
  const changedAt = await readSpecChangedAt(process.cwd());
21448
21463
  return (await Promise.all(tree.map(async (feature) => {
21449
- const specs = await Promise.all(feature.specs.filter((s) => s.hasSpecFile).map(async (s) => {
21464
+ const built = await Promise.all(feature.specs.filter((s) => s.hasSpecFile).map(async (s) => {
21450
21465
  const specYaml = await tryReadSpecFile(feature.featureName, s.specName);
21451
21466
  const meta = readSpecMeta(s.specName, specYaml);
21452
21467
  const plugin = resolveSpecTarget(specYaml, config);
@@ -21458,12 +21473,13 @@ async function buildSkeleton(tree) {
21458
21473
  summary: "",
21459
21474
  ...meta.steps.length > 0 ? { steps: meta.steps } : {},
21460
21475
  status,
21461
- ...lastEdit ? { changedAt: lastEdit } : {}
21476
+ ...lastEdit ? { changedAt: lastEdit } : {},
21477
+ ...meta.disabled ? { disabled: true } : {}
21462
21478
  };
21463
21479
  }));
21464
21480
  return {
21465
21481
  featureName: feature.featureName,
21466
- specs
21482
+ specs: built
21467
21483
  };
21468
21484
  }))).filter((f) => f.specs.length > 0).map((f) => ({
21469
21485
  featureName: f.featureName,
@@ -21526,13 +21542,19 @@ function withoutGeneratedAt(yamlText) {
21526
21542
  function noteKey(featureName, specName) {
21527
21543
  return `${featureName}/${specName}`;
21528
21544
  }
21529
- /** Lenient title/mode read from an already-loaded spec.yaml (null → defaults). */
21545
+ /**
21546
+ * Lenient read of an already-loaded spec.yaml. A file that is missing or will
21547
+ * not parse falls back to `defaults`, which reports the spec as enabled: a
21548
+ * spec nobody could read has not asked to be skipped.
21549
+ */
21530
21550
  function readSpecMeta(specName, specYaml) {
21531
- if (specYaml === null) return {
21551
+ const defaults = {
21532
21552
  title: specName,
21533
21553
  mode: DEFAULT_SPEC_MODE,
21534
- steps: []
21554
+ steps: [],
21555
+ disabled: false
21535
21556
  };
21557
+ if (specYaml === null) return defaults;
21536
21558
  try {
21537
21559
  const parsed = parse(specYaml);
21538
21560
  const title = typeof parsed.title === "string" && parsed.title.length > 0 ? parsed.title : specName;
@@ -21540,14 +21562,11 @@ function readSpecMeta(specName, specYaml) {
21540
21562
  return {
21541
21563
  title,
21542
21564
  mode: modeResult.success ? modeResult.data : DEFAULT_SPEC_MODE,
21543
- steps: transcribeSteps(parsed.steps)
21565
+ steps: transcribeSteps(parsed.steps),
21566
+ disabled: tryParseTestSpec(specYaml)?.disabled === true
21544
21567
  };
21545
21568
  } catch {
21546
- return {
21547
- title: specName,
21548
- mode: DEFAULT_SPEC_MODE,
21549
- steps: []
21550
- };
21569
+ return defaults;
21551
21570
  }
21552
21571
  }
21553
21572
  /**
@@ -23076,6 +23095,7 @@ function readSpecTargets(doc) {
23076
23095
  for (const spec of specs) {
23077
23096
  const specName = prop(spec, "specName");
23078
23097
  if (typeof specName !== "string") continue;
23098
+ if (prop(spec, "disabled") === true) continue;
23079
23099
  const changedAt = prop(spec, "changedAt");
23080
23100
  out.push({
23081
23101
  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.1",
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.1",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {