ccqa 0.8.0 → 0.8.2
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 +72 -14
- package/dist/package.json +1 -1
- package/package.json +1 -1
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
|
-
*
|
|
873
|
-
*
|
|
874
|
-
* written by Claude — these
|
|
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 {
|
|
9341
|
+
if (raw === null) return {
|
|
9342
|
+
title: specName,
|
|
9343
|
+
mode: DEFAULT_SPEC_MODE
|
|
9344
|
+
};
|
|
9330
9345
|
try {
|
|
9331
9346
|
const parsed = parse(raw);
|
|
9332
|
-
|
|
9333
|
-
|
|
9334
|
-
|
|
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
|
-
|
|
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,11 +9591,15 @@ 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)} |`);
|
|
9543
9599
|
const specPath = specRelPathFromCategory(spec.specName);
|
|
9544
9600
|
lines.push(`| spec | [${specPath}](${specPath}) |`);
|
|
9601
|
+
lines.push(`| ${labels.modeLabel} | ${mdCell(modeLabel(spec.status, labels))} |`);
|
|
9602
|
+
lines.push(`| ${labels.statusCol} | ${mdCell(statusLabel(spec.status, labels))} |`);
|
|
9545
9603
|
if (spec.relatedPaths && spec.relatedPaths.length > 0) lines.push(`| ${labels.relatedCode} | ${spec.relatedPaths.map((p) => `\`${p}\``).join("<br>")} |`);
|
|
9546
9604
|
if (spec.note) lines.push(`| 📝 note | ${mdCell(spec.note)} |`);
|
|
9547
9605
|
lines.push("");
|
package/dist/package.json
CHANGED