qfai 0.7.1 → 0.8.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.
@@ -870,8 +870,8 @@ import { readFile as readFile3 } from "fs/promises";
870
870
  import path6 from "path";
871
871
  import { fileURLToPath } from "url";
872
872
  async function resolveToolVersion() {
873
- if ("0.7.1".length > 0) {
874
- return "0.7.1";
873
+ if ("0.8.0".length > 0) {
874
+ return "0.8.0";
875
875
  }
876
876
  try {
877
877
  const packagePath = resolvePackageJsonPath();
@@ -2055,7 +2055,7 @@ async function validateDeltas(root, config) {
2055
2055
  issues.push(
2056
2056
  issue2(
2057
2057
  "QFAI-DELTA-002",
2058
- "delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002",
2058
+ "delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002`## \u5909\u66F4\u533A\u5206` \u3068\u30C1\u30A7\u30C3\u30AF\u30DC\u30C3\u30AF\u30B9\uFF08Compatibility / Change/Improvement\uFF09\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
2059
2059
  "error",
2060
2060
  deltaPath,
2061
2061
  "delta.section"
@@ -2598,7 +2598,7 @@ async function validateTraceability(root, config) {
2598
2598
  issues.push(
2599
2599
  issue6(
2600
2600
  "QFAI-TRACE-020",
2601
- "Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
2601
+ "Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002\u4F8B: `QFAI-CONTRACT-REF: none` \u307E\u305F\u306F `QFAI-CONTRACT-REF: UI-0001`",
2602
2602
  "error",
2603
2603
  file,
2604
2604
  "traceability.specContractRefRequired"
@@ -2609,7 +2609,7 @@ async function validateTraceability(root, config) {
2609
2609
  issues.push(
2610
2610
  issue6(
2611
2611
  "QFAI-TRACE-023",
2612
- "Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
2612
+ "Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002none \u304B \u5951\u7D04ID \u306E\u3069\u3061\u3089\u304B\u4E00\u65B9\u3060\u3051\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
2613
2613
  "error",
2614
2614
  file,
2615
2615
  "traceability.specContractRefFormat"
@@ -2622,7 +2622,7 @@ async function validateTraceability(root, config) {
2622
2622
  "QFAI-TRACE-021",
2623
2623
  `Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
2624
2624
  ", "
2625
- )}`,
2625
+ )} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
2626
2626
  "error",
2627
2627
  file,
2628
2628
  "traceability.specContractRefFormat",
@@ -2662,7 +2662,7 @@ async function validateTraceability(root, config) {
2662
2662
  issues.push(
2663
2663
  issue6(
2664
2664
  "QFAI-TRACE-031",
2665
- "Scenario \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
2665
+ "Scenario \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002\u4F8B: `# QFAI-CONTRACT-REF: none` \u307E\u305F\u306F `# QFAI-CONTRACT-REF: UI-0001`",
2666
2666
  "error",
2667
2667
  file,
2668
2668
  "traceability.scenarioContractRefRequired"
@@ -2673,7 +2673,7 @@ async function validateTraceability(root, config) {
2673
2673
  issues.push(
2674
2674
  issue6(
2675
2675
  "QFAI-TRACE-033",
2676
- "Scenario \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
2676
+ "Scenario \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002none \u304B \u5951\u7D04ID \u306E\u3069\u3061\u3089\u304B\u4E00\u65B9\u3060\u3051\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
2677
2677
  "error",
2678
2678
  file,
2679
2679
  "traceability.scenarioContractRefFormat"
@@ -2686,7 +2686,7 @@ async function validateTraceability(root, config) {
2686
2686
  "QFAI-TRACE-032",
2687
2687
  `Scenario \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${scenarioContractRefs.invalidTokens.join(
2688
2688
  ", "
2689
- )}`,
2689
+ )} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
2690
2690
  "error",
2691
2691
  file,
2692
2692
  "traceability.scenarioContractRefFormat",
@@ -3184,7 +3184,7 @@ function formatReportMarkdown(data) {
3184
3184
  lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
3185
3185
  lines.push(`- \u7248: ${data.version}`);
3186
3186
  lines.push("");
3187
- lines.push("## \u6982\u8981");
3187
+ lines.push("## Summary");
3188
3188
  lines.push("");
3189
3189
  lines.push(`- specs: ${data.summary.specs}`);
3190
3190
  lines.push(`- scenarios: ${data.summary.scenarios}`);
@@ -3194,8 +3194,79 @@ function formatReportMarkdown(data) {
3194
3194
  lines.push(
3195
3195
  `- issues: info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
3196
3196
  );
3197
+ lines.push(
3198
+ `- fail-on=error: ${data.summary.counts.error > 0 ? "FAIL" : "PASS"}`
3199
+ );
3200
+ lines.push(
3201
+ `- fail-on=warning: ${data.summary.counts.error + data.summary.counts.warning > 0 ? "FAIL" : "PASS"}`
3202
+ );
3203
+ lines.push("");
3204
+ lines.push("## Findings");
3205
+ lines.push("");
3206
+ lines.push("### Issues (by code)");
3207
+ lines.push("");
3208
+ const severityOrder = {
3209
+ error: 0,
3210
+ warning: 1,
3211
+ info: 2
3212
+ };
3213
+ const issueKeyToCount = /* @__PURE__ */ new Map();
3214
+ for (const issue7 of data.issues) {
3215
+ const key = `${issue7.severity}|${issue7.code}`;
3216
+ const current = issueKeyToCount.get(key);
3217
+ if (current) {
3218
+ current.count += 1;
3219
+ continue;
3220
+ }
3221
+ issueKeyToCount.set(key, {
3222
+ severity: issue7.severity,
3223
+ code: issue7.code,
3224
+ count: 1
3225
+ });
3226
+ }
3227
+ const issueSummaryRows = Array.from(issueKeyToCount.values()).sort((a, b) => {
3228
+ const sa = severityOrder[a.severity] ?? 999;
3229
+ const sb = severityOrder[b.severity] ?? 999;
3230
+ if (sa !== sb) return sa - sb;
3231
+ return a.code.localeCompare(b.code);
3232
+ }).map((x) => [x.severity, x.code, String(x.count)]);
3233
+ if (issueSummaryRows.length === 0) {
3234
+ lines.push("- (none)");
3235
+ } else {
3236
+ lines.push(
3237
+ ...formatMarkdownTable(["Severity", "Code", "Count"], issueSummaryRows)
3238
+ );
3239
+ }
3240
+ lines.push("");
3241
+ lines.push("### Issues (list)");
3242
+ lines.push("");
3243
+ if (data.issues.length === 0) {
3244
+ lines.push("- (none)");
3245
+ } else {
3246
+ const sortedIssues = [...data.issues].sort((a, b) => {
3247
+ const sa = severityOrder[a.severity] ?? 999;
3248
+ const sb = severityOrder[b.severity] ?? 999;
3249
+ if (sa !== sb) return sa - sb;
3250
+ const code = a.code.localeCompare(b.code);
3251
+ if (code !== 0) return code;
3252
+ const fileA = a.file ?? "";
3253
+ const fileB = b.file ?? "";
3254
+ const file = fileA.localeCompare(fileB);
3255
+ if (file !== 0) return file;
3256
+ const lineA = a.loc?.line ?? 0;
3257
+ const lineB = b.loc?.line ?? 0;
3258
+ return lineA - lineB;
3259
+ });
3260
+ for (const item of sortedIssues) {
3261
+ const location = item.file ? ` (${item.file})` : "";
3262
+ const refs = item.refs && item.refs.length > 0 ? ` refs=${item.refs.join(",")}` : "";
3263
+ lines.push(
3264
+ `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}${refs}`
3265
+ );
3266
+ }
3267
+ }
3197
3268
  lines.push("");
3198
- lines.push("## ID\u96C6\u8A08");
3269
+ lines.push("### IDs");
3199
3270
  lines.push("");
3200
3271
  lines.push(formatIdLine("SPEC", data.ids.spec));
3201
3272
  lines.push(formatIdLine("BR", data.ids.br));
@@ -3204,14 +3275,14 @@ function formatReportMarkdown(data) {
3204
3275
  lines.push(formatIdLine("API", data.ids.api));
3205
3276
  lines.push(formatIdLine("DB", data.ids.db));
3206
3277
  lines.push("");
3207
- lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3");
3278
+ lines.push("### Traceability");
3208
3279
  lines.push("");
3209
3280
  lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
3210
3281
  lines.push(
3211
3282
  `- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
3212
3283
  );
3213
3284
  lines.push("");
3214
- lines.push("## \u5951\u7D04\u30AB\u30D0\u30EC\u30C3\u30B8");
3285
+ lines.push("### Contract Coverage");
3215
3286
  lines.push("");
3216
3287
  lines.push(`- total: ${data.traceability.contracts.total}`);
3217
3288
  lines.push(`- referenced: ${data.traceability.contracts.referenced}`);
@@ -3220,7 +3291,7 @@ function formatReportMarkdown(data) {
3220
3291
  `- specContractRefMissing: ${data.traceability.specs.contractRefMissing}`
3221
3292
  );
3222
3293
  lines.push("");
3223
- lines.push("## \u5951\u7D04\u2192Spec");
3294
+ lines.push("### Contract \u2192 Spec");
3224
3295
  lines.push("");
3225
3296
  const contractToSpecs = data.traceability.contracts.idToSpecs;
3226
3297
  const contractIds = Object.keys(contractToSpecs).sort(
@@ -3239,7 +3310,7 @@ function formatReportMarkdown(data) {
3239
3310
  }
3240
3311
  }
3241
3312
  lines.push("");
3242
- lines.push("## Spec\u2192\u5951\u7D04");
3313
+ lines.push("### Spec \u2192 Contracts");
3243
3314
  lines.push("");
3244
3315
  const specToContracts = data.traceability.specs.specToContracts;
3245
3316
  const specIds = Object.keys(specToContracts).sort(
@@ -3257,7 +3328,7 @@ function formatReportMarkdown(data) {
3257
3328
  lines.push(...formatMarkdownTable(["Spec", "Status", "Contracts"], rows));
3258
3329
  }
3259
3330
  lines.push("");
3260
- lines.push("## Spec\u3067 contract-ref \u672A\u5BA3\u8A00");
3331
+ lines.push("### Specs missing contract-ref");
3261
3332
  lines.push("");
3262
3333
  const missingRefSpecs = data.traceability.specs.missingRefSpecs;
3263
3334
  if (missingRefSpecs.length === 0) {
@@ -3268,7 +3339,7 @@ function formatReportMarkdown(data) {
3268
3339
  }
3269
3340
  }
3270
3341
  lines.push("");
3271
- lines.push("## SC\u30AB\u30D0\u30EC\u30C3\u30B8");
3342
+ lines.push("### SC coverage");
3272
3343
  lines.push("");
3273
3344
  lines.push(`- total: ${data.traceability.sc.total}`);
3274
3345
  lines.push(`- covered: ${data.traceability.sc.covered}`);
@@ -3298,7 +3369,7 @@ function formatReportMarkdown(data) {
3298
3369
  lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
3299
3370
  }
3300
3371
  lines.push("");
3301
- lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
3372
+ lines.push("### SC \u2192 referenced tests");
3302
3373
  lines.push("");
3303
3374
  const scRefs = data.traceability.sc.refs;
3304
3375
  const scIds = Object.keys(scRefs).sort((a, b) => a.localeCompare(b));
@@ -3315,7 +3386,7 @@ function formatReportMarkdown(data) {
3315
3386
  }
3316
3387
  }
3317
3388
  lines.push("");
3318
- lines.push("## Spec:SC=1:1 \u9055\u53CD");
3389
+ lines.push("### Spec:SC=1:1 violations");
3319
3390
  lines.push("");
3320
3391
  const specScIssues = data.issues.filter(
3321
3392
  (item) => item.code === "QFAI-TRACE-012"
@@ -3330,7 +3401,7 @@ function formatReportMarkdown(data) {
3330
3401
  }
3331
3402
  }
3332
3403
  lines.push("");
3333
- lines.push("## Hotspots");
3404
+ lines.push("### Hotspots");
3334
3405
  lines.push("");
3335
3406
  const hotspots = buildHotspots(data.issues);
3336
3407
  if (hotspots.length === 0) {
@@ -3343,35 +3414,28 @@ function formatReportMarkdown(data) {
3343
3414
  }
3344
3415
  }
3345
3416
  lines.push("");
3346
- lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
3417
+ lines.push("## Guidance");
3347
3418
  lines.push("");
3348
- const traceIssues = data.issues.filter(
3349
- (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-")
3419
+ lines.push(
3420
+ "- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
3350
3421
  );
3351
- if (traceIssues.length === 0) {
3352
- lines.push("- (none)");
3353
- } else {
3354
- for (const item of traceIssues) {
3355
- const location = item.file ? ` (${item.file})` : "";
3356
- lines.push(
3357
- `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}`
3358
- );
3359
- }
3360
- }
3361
- lines.push("");
3362
- lines.push("## \u691C\u8A3C\u7D50\u679C");
3363
- lines.push("");
3364
- if (data.issues.length === 0) {
3365
- lines.push("- (none)");
3422
+ if (data.summary.counts.error > 0) {
3423
+ lines.push("- error \u304C\u3042\u308B\u305F\u3081\u3001\u307E\u305A error \u304B\u3089\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
3424
+ } else if (data.summary.counts.warning > 0) {
3425
+ lines.push(
3426
+ "- warning \u306E\u6271\u3044\uFF08Hard Gate \u306B\u3059\u308B\u304B\uFF09\u306F\u904B\u7528\u3067\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
3427
+ );
3366
3428
  } else {
3367
- for (const item of data.issues) {
3368
- const location = item.file ? ` (${item.file})` : "";
3369
- const refs = item.refs && item.refs.length > 0 ? ` refs=${item.refs.join(",")}` : "";
3370
- lines.push(
3371
- `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}${refs}`
3372
- );
3373
- }
3429
+ lines.push(
3430
+ "- issue \u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u904B\u7528\u30C6\u30F3\u30D7\u30EC\u306B\u6CBF\u3063\u3066\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3431
+ );
3374
3432
  }
3433
+ lines.push(
3434
+ "- \u5909\u66F4\u533A\u5206\uFF08Compatibility / Change/Improvement\uFF09\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002"
3435
+ );
3436
+ lines.push(
3437
+ "- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md` / `.qfai/promptpack/steering/compatibility-vs-change.md`"
3438
+ );
3375
3439
  return lines.join("\n");
3376
3440
  }
3377
3441
  function formatReportJson(data) {
@@ -3695,6 +3759,8 @@ async function runValidate(options) {
3695
3759
  const configResult = await loadConfig(root);
3696
3760
  const result = await validateProject(root, configResult);
3697
3761
  const normalized = normalizeValidationResult(root, result);
3762
+ const failOn = resolveFailOn(options, configResult.config.validation.failOn);
3763
+ const willFail = shouldFail(normalized, failOn);
3698
3764
  const format = options.format ?? "text";
3699
3765
  if (format === "text") {
3700
3766
  emitText(normalized);
@@ -3704,11 +3770,10 @@ async function runValidate(options) {
3704
3770
  root,
3705
3771
  configResult.config.output.validateJsonPath
3706
3772
  );
3707
- emitGitHubOutput(normalized, root, jsonPath);
3773
+ emitGitHubOutput(normalized, root, jsonPath, { failOn, willFail });
3708
3774
  }
3709
3775
  await emitJson(normalized, root, configResult.config.output.validateJsonPath);
3710
- const failOn = resolveFailOn(options, configResult.config.validation.failOn);
3711
- return shouldFail(normalized, failOn) ? 1 : 0;
3776
+ return willFail ? 1 : 0;
3712
3777
  }
3713
3778
  function resolveFailOn(options, fallback) {
3714
3779
  if (options.failOn) {
@@ -3733,7 +3798,7 @@ function emitText(result) {
3733
3798
  `
3734
3799
  );
3735
3800
  }
3736
- function emitGitHubOutput(result, root, jsonPath) {
3801
+ function emitGitHubOutput(result, root, jsonPath, status) {
3737
3802
  const deduped = dedupeIssues(result.issues);
3738
3803
  const omitted = Math.max(deduped.length - GITHUB_ANNOTATION_LIMIT, 0);
3739
3804
  const dropped = Math.max(result.issues.length - deduped.length, 0);
@@ -3742,7 +3807,8 @@ function emitGitHubOutput(result, root, jsonPath) {
3742
3807
  omitted,
3743
3808
  dropped,
3744
3809
  jsonPath,
3745
- root
3810
+ root,
3811
+ ...status
3746
3812
  });
3747
3813
  const issues = deduped.slice(0, GITHUB_ANNOTATION_LIMIT);
3748
3814
  for (const issue7 of issues) {
@@ -3766,7 +3832,9 @@ function emitGitHubSummary(result, options) {
3766
3832
  `error=${result.counts.error}`,
3767
3833
  `warning=${result.counts.warning}`,
3768
3834
  `info=${result.counts.info}`,
3769
- `annotations=${Math.min(options.total, GITHUB_ANNOTATION_LIMIT)}/${options.total}`
3835
+ `annotations=${Math.min(options.total, GITHUB_ANNOTATION_LIMIT)}/${options.total}`,
3836
+ `failOn=${options.failOn}`,
3837
+ `result=${options.willFail ? "FAIL" : "PASS"}`
3770
3838
  ].join(" ");
3771
3839
  process.stdout.write(`${summary}
3772
3840
  `);
@@ -3784,6 +3852,9 @@ function emitGitHubSummary(result, options) {
3784
3852
  `qfai validate note: \u8A73\u7D30\u306F ${relative} \u307E\u305F\u306F --format text \u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
3785
3853
  `
3786
3854
  );
3855
+ process.stdout.write(
3856
+ "qfai validate note: \u6B21\u306F qfai report \u3067 report.md \u3092\u751F\u6210\u3067\u304D\u307E\u3059\uFF08\u4F8B: qfai report\uFF09\u3002\n"
3857
+ );
3787
3858
  }
3788
3859
  function dedupeIssues(issues) {
3789
3860
  const seen = /* @__PURE__ */ new Set();