ccqa 0.8.0 → 0.8.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
@@ -869,11 +869,23 @@ async function timedPhase(label, fn, scope = "fix") {
869
869
  * prioritisation; that is a separate, later concern.
870
870
  */
871
871
  /**
872
- * Whether the spec has been traced / generated. Both are derived mechanically
873
- * by the CLI from on-disk artifacts (actions.json / test.spec.ts), never
874
- * written by Claude — these are facts and must not drift.
872
+ * Mechanically-derived facts about the spec's recording state, transcribed by
873
+ * the CLI from spec.yaml + on-disk artifacts (actions.json / test.spec.ts).
874
+ * Never written by Claude — these must not drift.
875
+ *
876
+ * `mode` mirrors spec.yaml's `mode:` field (defaulting to `deterministic`).
877
+ * Its meaning shapes how `traced` / `generated` should be interpreted:
878
+ *
879
+ * - `mode: deterministic` — `traced` and `generated` are the spec's
880
+ * completeness signal. `generated: false` means "spec.yaml exists but
881
+ * `ccqa record` hasn't been run yet" — i.e. the case has not been
882
+ * materialised into a runnable test.
883
+ * - `mode: live` — the live runner skips codegen entirely, so `traced` and
884
+ * `generated` carry no completeness meaning here. Reports should not flag
885
+ * `generated: false` as incomplete for live specs.
875
886
  */
876
887
  const PerspectiveStatusSchema = z.object({
888
+ mode: SpecModeSchema,
877
889
  traced: z.boolean(),
878
890
  generated: z.boolean()
879
891
  }).strict();
@@ -9243,7 +9255,7 @@ async function buildSkeleton(tree) {
9243
9255
  return (await Promise.all(tree.map(async (feature) => {
9244
9256
  const specs = await Promise.all(feature.specs.filter((s) => s.hasSpecFile).map(async (s) => {
9245
9257
  const spec = await readSpecMeta(feature.featureName, s.specName);
9246
- const status = await deriveStatus(feature.featureName, s.specName);
9258
+ const status = await deriveStatus(feature.featureName, s.specName, spec.mode);
9247
9259
  const entry = {
9248
9260
  specName: s.specName,
9249
9261
  title: spec.title,
@@ -9326,15 +9338,28 @@ function noteKey(featureName, specName) {
9326
9338
  }
9327
9339
  async function readSpecMeta(featureName, specName) {
9328
9340
  const raw = await tryReadSpecFile(featureName, specName);
9329
- if (raw === null) return { title: specName };
9341
+ if (raw === null) return {
9342
+ title: specName,
9343
+ mode: DEFAULT_SPEC_MODE
9344
+ };
9330
9345
  try {
9331
9346
  const parsed = parse(raw);
9332
- if (typeof parsed.title === "string" && parsed.title.length > 0) return { title: parsed.title };
9333
- } catch {}
9334
- return { title: specName };
9347
+ const title = typeof parsed.title === "string" && parsed.title.length > 0 ? parsed.title : specName;
9348
+ const modeResult = SpecModeSchema.safeParse(parsed.mode);
9349
+ return {
9350
+ title,
9351
+ mode: modeResult.success ? modeResult.data : DEFAULT_SPEC_MODE
9352
+ };
9353
+ } catch {
9354
+ return {
9355
+ title: specName,
9356
+ mode: DEFAULT_SPEC_MODE
9357
+ };
9358
+ }
9335
9359
  }
9336
- async function deriveStatus(featureName, specName) {
9360
+ async function deriveStatus(featureName, specName, mode) {
9337
9361
  return {
9362
+ mode,
9338
9363
  traced: await stat(join(getSpecDir(featureName, specName), "actions.json")).then(() => true).catch(() => false),
9339
9364
  generated: await getTestScript(featureName, specName) !== null
9340
9365
  };
@@ -9426,20 +9451,34 @@ const LABELS_JA = {
9426
9451
  caseCol: "ケース",
9427
9452
  itemCol: "項目",
9428
9453
  valueCol: "内容",
9454
+ modeCol: "モード",
9455
+ statusCol: "状態",
9456
+ modeLabel: "モード",
9429
9457
  summary: "検証内容",
9430
9458
  preconditions: "前提条件",
9431
9459
  startScreen: "開始画面",
9432
- relatedCode: "関連コード"
9460
+ relatedCode: "関連コード",
9461
+ modeDeterministic: "deterministic",
9462
+ modeLive: "live",
9463
+ notRunnable: "⚠️ 未record",
9464
+ runnable: "✅ 実行可能"
9433
9465
  };
9434
9466
  const LABELS_EN = {
9435
9467
  indexTitle: "Test Perspectives (perspectives)",
9436
9468
  caseCol: "Case",
9437
9469
  itemCol: "Item",
9438
9470
  valueCol: "Value",
9471
+ modeCol: "Mode",
9472
+ statusCol: "Status",
9473
+ modeLabel: "Mode",
9439
9474
  summary: "Verifies",
9440
9475
  preconditions: "Preconditions",
9441
9476
  startScreen: "Start screen",
9442
- relatedCode: "Related code"
9477
+ relatedCode: "Related code",
9478
+ modeDeterministic: "deterministic",
9479
+ modeLive: "live",
9480
+ notRunnable: "⚠️ not recorded",
9481
+ runnable: "✅ runnable"
9443
9482
  };
9444
9483
  /**
9445
9484
  * Pick the label set for a `--language` value. Only an explicit English tag
@@ -9451,6 +9490,19 @@ function labelsFor(language) {
9451
9490
  return /^en\b/i.test(language?.trim() ?? "") ? LABELS_EN : LABELS_JA;
9452
9491
  }
9453
9492
  /**
9493
+ * Whether the spec is runnable from the reviewer's perspective. Live specs
9494
+ * are always runnable (no codegen step); a deterministic spec is runnable
9495
+ * only once `test.spec.ts` exists.
9496
+ */
9497
+ function statusLabel(status, labels) {
9498
+ if (status.mode === "live") return labels.runnable;
9499
+ return status.generated ? labels.runnable : labels.notRunnable;
9500
+ }
9501
+ /** The spec's execution mode (deterministic or live), per spec.yaml. */
9502
+ function modeLabel(status, labels) {
9503
+ return status.mode === "live" ? labels.modeLive : labels.modeDeterministic;
9504
+ }
9505
+ /**
9454
9506
  * Path to a spec.yaml relative to the **root** `.ccqa/perspectives.md`
9455
9507
  * (i.e. relative to the `.ccqa/` dir). Used for the category index links.
9456
9508
  */
@@ -9493,11 +9545,13 @@ function renderIndexMarkdown(perspectives, labels = LABELS_JA) {
9493
9545
  const detailLink = featureDetailRelPathFromRoot(feature.featureName);
9494
9546
  lines.push(`## [${feature.featureName}](${detailLink})`);
9495
9547
  lines.push("");
9496
- lines.push(`| ${labels.caseCol} | spec |`);
9497
- lines.push("| --- | --- |");
9548
+ lines.push(`| ${labels.caseCol} | ${labels.modeCol} | ${labels.statusCol} | spec |`);
9549
+ lines.push("| --- | --- | --- | --- |");
9498
9550
  for (const spec of feature.specs) {
9499
9551
  const specLink = specRelPathFromRoot(feature.featureName, spec.specName);
9500
- lines.push(`| ${mdCell(spec.title)} | [spec](${specLink}) |`);
9552
+ const mode = mdCell(modeLabel(spec.status, labels));
9553
+ const status = mdCell(statusLabel(spec.status, labels));
9554
+ lines.push(`| ${mdCell(spec.title)} | ${mode} | ${status} | [spec](${specLink}) |`);
9501
9555
  }
9502
9556
  lines.push("");
9503
9557
  }
@@ -9537,6 +9591,8 @@ function renderSpecMarkdown(spec, labels = LABELS_JA) {
9537
9591
  lines.push("");
9538
9592
  lines.push(`| ${labels.itemCol} | ${labels.valueCol} |`);
9539
9593
  lines.push("| --- | --- |");
9594
+ lines.push(`| ${labels.modeLabel} | ${mdCell(modeLabel(spec.status, labels))} |`);
9595
+ lines.push(`| ${labels.statusCol} | ${mdCell(statusLabel(spec.status, labels))} |`);
9540
9596
  if (spec.summary) lines.push(`| ${labels.summary} | ${mdCell(spec.summary)} |`);
9541
9597
  if (spec.preconditions && spec.preconditions.length > 0) lines.push(`| ${labels.preconditions} | ${spec.preconditions.map(mdCell).join("<br>")} |`);
9542
9598
  if (spec.startScreen) lines.push(`| ${labels.startScreen} | ${mdCell(spec.startScreen)} |`);
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccqa",
3
- "version": "0.8.0",
3
+ "version": "0.8.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": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "Browser test recorder powered by Claude Code and agent-browser",
6
6
  "repository": {