qfai 0.7.2 → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aganesy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -262,4 +262,4 @@ pnpm test:assets
262
262
 
263
263
  ## ライセンス
264
264
 
265
- [MIT](https://github.com/aganesy/QFAI/blob/main/LICENSE)
265
+ [MIT](https://github.com/aganesy/QFAI/blob/main/packages/qfai/LICENSE)
@@ -2,9 +2,10 @@
2
2
 
3
3
  このディレクトリは QFAI の成果物を集約する専用領域です。`.qfai` 配下だけを見れば「何を書くか」「どこから始めるか」が分かる構成にしています。
4
4
 
5
- ## 最短成功(init → validate → report)
5
+ ## 最短成功(doctor → validate → report)
6
6
 
7
7
  ```bash
8
+ npx qfai doctor --fail-on error
8
9
  npx qfai validate --fail-on error --format github
9
10
  npx qfai report
10
11
  ```
@@ -61,6 +62,8 @@ v0.7 以降、プロンプト資産のカスタマイズは `.qfai/prompts.local
61
62
  - `.qfai/prompts/**` は QFAI 標準資産であり、更新や `qfai init` 再実行で上書きされ得ます
62
63
  - 利用者が `.qfai/prompts/**` を直接編集することは非推奨・非サポートです
63
64
  - 変更したい場合は同一相対パスで `.qfai/prompts.local/**` に置いて上書きしてください
65
+ - `qfai init` は `.qfai/prompts.local/**` を **保護**します(`--force` でも上書きしません)
66
+ - 現時点の保護対象は `prompts.local` のみです(それ以外は上書きされ得ます)
64
67
 
65
68
  例:
66
69
 
@@ -6,3 +6,37 @@
6
6
  - 仕様変更: 期待値/挙動が変わる
7
7
 
8
8
  必ず `delta.md` に区分と根拠を記録する。
9
+
10
+ ---
11
+
12
+ ## 最小ルール(迷ったらこれ)
13
+
14
+ - **既存の利用者がそのまま手順・解釈で運用できる**: Compatibility
15
+ - **既存の利用者が手順変更・解釈変更・レビュー基準変更を迫られる**: Change/Improvement
16
+
17
+ 「見た目だけ」「文章だけ」でも、運用で機械消費されている可能性がある場合は慎重に扱い、根拠を `delta.md` に残す。
18
+
19
+ ---
20
+
21
+ ## 具体例(最低10件)
22
+
23
+ | 変更内容 | 区分 | QA/レビュー観点 |
24
+ | ------------------------------------------------------------- | ------------------ | ---------------------------------------------------- |
25
+ | README の誤字修正、リンク切れ修正 | Compatibility | 誤誘導が減る。既存運用は不変。 |
26
+ | report(text/markdown)の表現改善・並び順安定化(意味は不変) | Compatibility | 人間レビューの短縮。出力の“解釈”が変わらないこと。 |
27
+ | report.json のフィールド追加/並び変更(非契約を維持) | Compatibility | 非契約を明記し続ける。機械消費ユーザーへの注意喚起。 |
28
+ | validate の文言改善(issue code/意味/失敗条件は不変) | Compatibility | 次アクションが明確になる。誤爆やノイズ増がないこと。 |
29
+ | validate で新しい issue code を追加(warning/info) | Change/Improvement | CIの表示が変わる。ノイズ/誤検知の受容可否。 |
30
+ | validate の fail 条件を変更(error→warning で落ちる等) | Change/Improvement | Hard Gate が増減する。既存CIが壊れないか。 |
31
+ | init の生成物構成を変更(新規ファイル追加) | Change/Improvement | 既存リポジトリへの導入影響。上書き/衝突/運用導線。 |
32
+ | init が既存ファイルを上書きする/保護対象を変える | Change/Improvement | 事故リスクが高い。保護の回帰テスト必須。 |
33
+ | Spec/Scenario/Contract のID規約を変更 | Change/Improvement | 既存資産が無効化され得る。移行ガイド必須。 |
34
+ | overlay の優先順位/探索規則を変更 | Change/Improvement | 既存の prompts.local 運用が壊れる。回帰テスト必須。 |
35
+
36
+ ---
37
+
38
+ ## QA/レビュー時の判断テンプレ
39
+
40
+ - **修正が必要**: Hard Gate を増やしていないか、既存の運用手順を壊していないか、保護領域(prompts.local)が破壊されないか
41
+ - **許容**: 誤誘導削減、説明の具体化、並び順の安定化などで、意味・失敗条件が変わらないもの
42
+ - **要議論**: report.json の変更、validate の新ルール追加、init生成物の変更(CI/運用への波及が読みにくいもの)
@@ -23,3 +23,8 @@ QFAI v0.7 以降は、プロンプト資産のカスタマイズ手段を **over
23
23
  - `.qfai/prompts/**` は **QFAI 標準資産**です(更新や `qfai init` の再実行で上書きされ得ます)。
24
24
  - 利用者が `.qfai/prompts/**` を直接編集することは **非推奨・非サポート(ほぼ禁止)**です。
25
25
  - 変更したい場合は、対象ファイルを `prompts.local` にコピーして上書きしてください。
26
+
27
+ ## init 再実行時の保護(契約)
28
+
29
+ - `qfai init` は `.qfai/prompts.local/**` を **保護**します(`--force` を付けても上書きしません)。
30
+ - 現時点でこの保護対象は `prompts.local` のみです。
@@ -889,8 +889,8 @@ var import_promises6 = require("fs/promises");
889
889
  var import_node_path6 = __toESM(require("path"), 1);
890
890
  var import_node_url = require("url");
891
891
  async function resolveToolVersion() {
892
- if ("0.7.2".length > 0) {
893
- return "0.7.2";
892
+ if ("0.8.0".length > 0) {
893
+ return "0.8.0";
894
894
  }
895
895
  try {
896
896
  const packagePath = resolvePackageJsonPath();
@@ -2074,7 +2074,7 @@ async function validateDeltas(root, config) {
2074
2074
  issues.push(
2075
2075
  issue2(
2076
2076
  "QFAI-DELTA-002",
2077
- "delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002",
2077
+ "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",
2078
2078
  "error",
2079
2079
  deltaPath,
2080
2080
  "delta.section"
@@ -2617,7 +2617,7 @@ async function validateTraceability(root, config) {
2617
2617
  issues.push(
2618
2618
  issue6(
2619
2619
  "QFAI-TRACE-020",
2620
- "Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
2620
+ "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`",
2621
2621
  "error",
2622
2622
  file,
2623
2623
  "traceability.specContractRefRequired"
@@ -2628,7 +2628,7 @@ async function validateTraceability(root, config) {
2628
2628
  issues.push(
2629
2629
  issue6(
2630
2630
  "QFAI-TRACE-023",
2631
- "Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
2631
+ "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",
2632
2632
  "error",
2633
2633
  file,
2634
2634
  "traceability.specContractRefFormat"
@@ -2641,7 +2641,7 @@ async function validateTraceability(root, config) {
2641
2641
  "QFAI-TRACE-021",
2642
2642
  `Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
2643
2643
  ", "
2644
- )}`,
2644
+ )} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
2645
2645
  "error",
2646
2646
  file,
2647
2647
  "traceability.specContractRefFormat",
@@ -2681,7 +2681,7 @@ async function validateTraceability(root, config) {
2681
2681
  issues.push(
2682
2682
  issue6(
2683
2683
  "QFAI-TRACE-031",
2684
- "Scenario \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
2684
+ "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`",
2685
2685
  "error",
2686
2686
  file,
2687
2687
  "traceability.scenarioContractRefRequired"
@@ -2692,7 +2692,7 @@ async function validateTraceability(root, config) {
2692
2692
  issues.push(
2693
2693
  issue6(
2694
2694
  "QFAI-TRACE-033",
2695
- "Scenario \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
2695
+ "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",
2696
2696
  "error",
2697
2697
  file,
2698
2698
  "traceability.scenarioContractRefFormat"
@@ -2705,7 +2705,7 @@ async function validateTraceability(root, config) {
2705
2705
  "QFAI-TRACE-032",
2706
2706
  `Scenario \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${scenarioContractRefs.invalidTokens.join(
2707
2707
  ", "
2708
- )}`,
2708
+ )} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
2709
2709
  "error",
2710
2710
  file,
2711
2711
  "traceability.scenarioContractRefFormat",
@@ -3203,7 +3203,7 @@ function formatReportMarkdown(data) {
3203
3203
  lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
3204
3204
  lines.push(`- \u7248: ${data.version}`);
3205
3205
  lines.push("");
3206
- lines.push("## \u6982\u8981");
3206
+ lines.push("## Summary");
3207
3207
  lines.push("");
3208
3208
  lines.push(`- specs: ${data.summary.specs}`);
3209
3209
  lines.push(`- scenarios: ${data.summary.scenarios}`);
@@ -3213,8 +3213,79 @@ function formatReportMarkdown(data) {
3213
3213
  lines.push(
3214
3214
  `- issues: info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
3215
3215
  );
3216
+ lines.push(
3217
+ `- fail-on=error: ${data.summary.counts.error > 0 ? "FAIL" : "PASS"}`
3218
+ );
3219
+ lines.push(
3220
+ `- fail-on=warning: ${data.summary.counts.error + data.summary.counts.warning > 0 ? "FAIL" : "PASS"}`
3221
+ );
3222
+ lines.push("");
3223
+ lines.push("## Findings");
3224
+ lines.push("");
3225
+ lines.push("### Issues (by code)");
3226
+ lines.push("");
3227
+ const severityOrder = {
3228
+ error: 0,
3229
+ warning: 1,
3230
+ info: 2
3231
+ };
3232
+ const issueKeyToCount = /* @__PURE__ */ new Map();
3233
+ for (const issue7 of data.issues) {
3234
+ const key = `${issue7.severity}|${issue7.code}`;
3235
+ const current = issueKeyToCount.get(key);
3236
+ if (current) {
3237
+ current.count += 1;
3238
+ continue;
3239
+ }
3240
+ issueKeyToCount.set(key, {
3241
+ severity: issue7.severity,
3242
+ code: issue7.code,
3243
+ count: 1
3244
+ });
3245
+ }
3246
+ const issueSummaryRows = Array.from(issueKeyToCount.values()).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
+ return a.code.localeCompare(b.code);
3251
+ }).map((x) => [x.severity, x.code, String(x.count)]);
3252
+ if (issueSummaryRows.length === 0) {
3253
+ lines.push("- (none)");
3254
+ } else {
3255
+ lines.push(
3256
+ ...formatMarkdownTable(["Severity", "Code", "Count"], issueSummaryRows)
3257
+ );
3258
+ }
3259
+ lines.push("");
3260
+ lines.push("### Issues (list)");
3261
+ lines.push("");
3262
+ if (data.issues.length === 0) {
3263
+ lines.push("- (none)");
3264
+ } else {
3265
+ const sortedIssues = [...data.issues].sort((a, b) => {
3266
+ const sa = severityOrder[a.severity] ?? 999;
3267
+ const sb = severityOrder[b.severity] ?? 999;
3268
+ if (sa !== sb) return sa - sb;
3269
+ const code = a.code.localeCompare(b.code);
3270
+ if (code !== 0) return code;
3271
+ const fileA = a.file ?? "";
3272
+ const fileB = b.file ?? "";
3273
+ const file = fileA.localeCompare(fileB);
3274
+ if (file !== 0) return file;
3275
+ const lineA = a.loc?.line ?? 0;
3276
+ const lineB = b.loc?.line ?? 0;
3277
+ return lineA - lineB;
3278
+ });
3279
+ for (const item of sortedIssues) {
3280
+ const location = item.file ? ` (${item.file})` : "";
3281
+ const refs = item.refs && item.refs.length > 0 ? ` refs=${item.refs.join(",")}` : "";
3282
+ lines.push(
3283
+ `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}${refs}`
3284
+ );
3285
+ }
3286
+ }
3216
3287
  lines.push("");
3217
- lines.push("## ID\u96C6\u8A08");
3288
+ lines.push("### IDs");
3218
3289
  lines.push("");
3219
3290
  lines.push(formatIdLine("SPEC", data.ids.spec));
3220
3291
  lines.push(formatIdLine("BR", data.ids.br));
@@ -3223,14 +3294,14 @@ function formatReportMarkdown(data) {
3223
3294
  lines.push(formatIdLine("API", data.ids.api));
3224
3295
  lines.push(formatIdLine("DB", data.ids.db));
3225
3296
  lines.push("");
3226
- lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3");
3297
+ lines.push("### Traceability");
3227
3298
  lines.push("");
3228
3299
  lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
3229
3300
  lines.push(
3230
3301
  `- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
3231
3302
  );
3232
3303
  lines.push("");
3233
- lines.push("## \u5951\u7D04\u30AB\u30D0\u30EC\u30C3\u30B8");
3304
+ lines.push("### Contract Coverage");
3234
3305
  lines.push("");
3235
3306
  lines.push(`- total: ${data.traceability.contracts.total}`);
3236
3307
  lines.push(`- referenced: ${data.traceability.contracts.referenced}`);
@@ -3239,7 +3310,7 @@ function formatReportMarkdown(data) {
3239
3310
  `- specContractRefMissing: ${data.traceability.specs.contractRefMissing}`
3240
3311
  );
3241
3312
  lines.push("");
3242
- lines.push("## \u5951\u7D04\u2192Spec");
3313
+ lines.push("### Contract \u2192 Spec");
3243
3314
  lines.push("");
3244
3315
  const contractToSpecs = data.traceability.contracts.idToSpecs;
3245
3316
  const contractIds = Object.keys(contractToSpecs).sort(
@@ -3258,7 +3329,7 @@ function formatReportMarkdown(data) {
3258
3329
  }
3259
3330
  }
3260
3331
  lines.push("");
3261
- lines.push("## Spec\u2192\u5951\u7D04");
3332
+ lines.push("### Spec \u2192 Contracts");
3262
3333
  lines.push("");
3263
3334
  const specToContracts = data.traceability.specs.specToContracts;
3264
3335
  const specIds = Object.keys(specToContracts).sort(
@@ -3276,7 +3347,7 @@ function formatReportMarkdown(data) {
3276
3347
  lines.push(...formatMarkdownTable(["Spec", "Status", "Contracts"], rows));
3277
3348
  }
3278
3349
  lines.push("");
3279
- lines.push("## Spec\u3067 contract-ref \u672A\u5BA3\u8A00");
3350
+ lines.push("### Specs missing contract-ref");
3280
3351
  lines.push("");
3281
3352
  const missingRefSpecs = data.traceability.specs.missingRefSpecs;
3282
3353
  if (missingRefSpecs.length === 0) {
@@ -3287,7 +3358,7 @@ function formatReportMarkdown(data) {
3287
3358
  }
3288
3359
  }
3289
3360
  lines.push("");
3290
- lines.push("## SC\u30AB\u30D0\u30EC\u30C3\u30B8");
3361
+ lines.push("### SC coverage");
3291
3362
  lines.push("");
3292
3363
  lines.push(`- total: ${data.traceability.sc.total}`);
3293
3364
  lines.push(`- covered: ${data.traceability.sc.covered}`);
@@ -3317,7 +3388,7 @@ function formatReportMarkdown(data) {
3317
3388
  lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
3318
3389
  }
3319
3390
  lines.push("");
3320
- lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
3391
+ lines.push("### SC \u2192 referenced tests");
3321
3392
  lines.push("");
3322
3393
  const scRefs = data.traceability.sc.refs;
3323
3394
  const scIds = Object.keys(scRefs).sort((a, b) => a.localeCompare(b));
@@ -3334,7 +3405,7 @@ function formatReportMarkdown(data) {
3334
3405
  }
3335
3406
  }
3336
3407
  lines.push("");
3337
- lines.push("## Spec:SC=1:1 \u9055\u53CD");
3408
+ lines.push("### Spec:SC=1:1 violations");
3338
3409
  lines.push("");
3339
3410
  const specScIssues = data.issues.filter(
3340
3411
  (item) => item.code === "QFAI-TRACE-012"
@@ -3349,7 +3420,7 @@ function formatReportMarkdown(data) {
3349
3420
  }
3350
3421
  }
3351
3422
  lines.push("");
3352
- lines.push("## Hotspots");
3423
+ lines.push("### Hotspots");
3353
3424
  lines.push("");
3354
3425
  const hotspots = buildHotspots(data.issues);
3355
3426
  if (hotspots.length === 0) {
@@ -3362,35 +3433,28 @@ function formatReportMarkdown(data) {
3362
3433
  }
3363
3434
  }
3364
3435
  lines.push("");
3365
- lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
3436
+ lines.push("## Guidance");
3366
3437
  lines.push("");
3367
- const traceIssues = data.issues.filter(
3368
- (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-")
3438
+ lines.push(
3439
+ "- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
3369
3440
  );
3370
- if (traceIssues.length === 0) {
3371
- lines.push("- (none)");
3372
- } else {
3373
- for (const item of traceIssues) {
3374
- const location = item.file ? ` (${item.file})` : "";
3375
- lines.push(
3376
- `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}`
3377
- );
3378
- }
3379
- }
3380
- lines.push("");
3381
- lines.push("## \u691C\u8A3C\u7D50\u679C");
3382
- lines.push("");
3383
- if (data.issues.length === 0) {
3384
- lines.push("- (none)");
3441
+ if (data.summary.counts.error > 0) {
3442
+ lines.push("- error \u304C\u3042\u308B\u305F\u3081\u3001\u307E\u305A error \u304B\u3089\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
3443
+ } else if (data.summary.counts.warning > 0) {
3444
+ lines.push(
3445
+ "- warning \u306E\u6271\u3044\uFF08Hard Gate \u306B\u3059\u308B\u304B\uFF09\u306F\u904B\u7528\u3067\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
3446
+ );
3385
3447
  } else {
3386
- for (const item of data.issues) {
3387
- const location = item.file ? ` (${item.file})` : "";
3388
- const refs = item.refs && item.refs.length > 0 ? ` refs=${item.refs.join(",")}` : "";
3389
- lines.push(
3390
- `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}${refs}`
3391
- );
3392
- }
3448
+ lines.push(
3449
+ "- 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"
3450
+ );
3393
3451
  }
3452
+ lines.push(
3453
+ "- \u5909\u66F4\u533A\u5206\uFF08Compatibility / Change/Improvement\uFF09\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002"
3454
+ );
3455
+ lines.push(
3456
+ "- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md` / `.qfai/promptpack/steering/compatibility-vs-change.md`"
3457
+ );
3394
3458
  return lines.join("\n");
3395
3459
  }
3396
3460
  function formatReportJson(data) {
@@ -3714,6 +3778,8 @@ async function runValidate(options) {
3714
3778
  const configResult = await loadConfig(root);
3715
3779
  const result = await validateProject(root, configResult);
3716
3780
  const normalized = normalizeValidationResult(root, result);
3781
+ const failOn = resolveFailOn(options, configResult.config.validation.failOn);
3782
+ const willFail = shouldFail(normalized, failOn);
3717
3783
  const format = options.format ?? "text";
3718
3784
  if (format === "text") {
3719
3785
  emitText(normalized);
@@ -3723,11 +3789,10 @@ async function runValidate(options) {
3723
3789
  root,
3724
3790
  configResult.config.output.validateJsonPath
3725
3791
  );
3726
- emitGitHubOutput(normalized, root, jsonPath);
3792
+ emitGitHubOutput(normalized, root, jsonPath, { failOn, willFail });
3727
3793
  }
3728
3794
  await emitJson(normalized, root, configResult.config.output.validateJsonPath);
3729
- const failOn = resolveFailOn(options, configResult.config.validation.failOn);
3730
- return shouldFail(normalized, failOn) ? 1 : 0;
3795
+ return willFail ? 1 : 0;
3731
3796
  }
3732
3797
  function resolveFailOn(options, fallback) {
3733
3798
  if (options.failOn) {
@@ -3752,7 +3817,7 @@ function emitText(result) {
3752
3817
  `
3753
3818
  );
3754
3819
  }
3755
- function emitGitHubOutput(result, root, jsonPath) {
3820
+ function emitGitHubOutput(result, root, jsonPath, status) {
3756
3821
  const deduped = dedupeIssues(result.issues);
3757
3822
  const omitted = Math.max(deduped.length - GITHUB_ANNOTATION_LIMIT, 0);
3758
3823
  const dropped = Math.max(result.issues.length - deduped.length, 0);
@@ -3761,7 +3826,8 @@ function emitGitHubOutput(result, root, jsonPath) {
3761
3826
  omitted,
3762
3827
  dropped,
3763
3828
  jsonPath,
3764
- root
3829
+ root,
3830
+ ...status
3765
3831
  });
3766
3832
  const issues = deduped.slice(0, GITHUB_ANNOTATION_LIMIT);
3767
3833
  for (const issue7 of issues) {
@@ -3785,7 +3851,9 @@ function emitGitHubSummary(result, options) {
3785
3851
  `error=${result.counts.error}`,
3786
3852
  `warning=${result.counts.warning}`,
3787
3853
  `info=${result.counts.info}`,
3788
- `annotations=${Math.min(options.total, GITHUB_ANNOTATION_LIMIT)}/${options.total}`
3854
+ `annotations=${Math.min(options.total, GITHUB_ANNOTATION_LIMIT)}/${options.total}`,
3855
+ `failOn=${options.failOn}`,
3856
+ `result=${options.willFail ? "FAIL" : "PASS"}`
3789
3857
  ].join(" ");
3790
3858
  process.stdout.write(`${summary}
3791
3859
  `);
@@ -3803,6 +3871,9 @@ function emitGitHubSummary(result, options) {
3803
3871
  `qfai validate note: \u8A73\u7D30\u306F ${relative} \u307E\u305F\u306F --format text \u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
3804
3872
  `
3805
3873
  );
3874
+ process.stdout.write(
3875
+ "qfai validate note: \u6B21\u306F qfai report \u3067 report.md \u3092\u751F\u6210\u3067\u304D\u307E\u3059\uFF08\u4F8B: qfai report\uFF09\u3002\n"
3876
+ );
3806
3877
  }
3807
3878
  function dedupeIssues(issues) {
3808
3879
  const seen = /* @__PURE__ */ new Set();