qfai 0.7.2 → 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.
@@ -24,12 +24,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli/commands/doctor.ts
27
- var import_promises8 = require("fs/promises");
28
- var import_node_path8 = __toESM(require("path"), 1);
27
+ var import_promises9 = require("fs/promises");
28
+ var import_node_path10 = __toESM(require("path"), 1);
29
29
 
30
30
  // src/core/doctor.ts
31
- var import_promises7 = require("fs/promises");
32
- var import_node_path7 = __toESM(require("path"), 1);
31
+ var import_promises8 = require("fs/promises");
32
+ var import_node_path9 = __toESM(require("path"), 1);
33
33
 
34
34
  // src/core/config.ts
35
35
  var import_promises = require("fs/promises");
@@ -401,6 +401,7 @@ function configIssue(file, message) {
401
401
  return {
402
402
  code: "QFAI_CONFIG_INVALID",
403
403
  severity: "error",
404
+ category: "compatibility",
404
405
  message,
405
406
  file,
406
407
  rule: "config.invalid"
@@ -884,17 +885,142 @@ function formatError3(error2) {
884
885
  return String(error2);
885
886
  }
886
887
 
887
- // src/core/version.ts
888
+ // src/core/promptsIntegrity.ts
888
889
  var import_promises6 = require("fs/promises");
890
+ var import_node_path7 = __toESM(require("path"), 1);
891
+
892
+ // src/shared/assets.ts
893
+ var import_node_fs = require("fs");
889
894
  var import_node_path6 = __toESM(require("path"), 1);
890
895
  var import_node_url = require("url");
896
+ function getInitAssetsDir() {
897
+ const base = __filename;
898
+ const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
899
+ const baseDir = import_node_path6.default.dirname(basePath);
900
+ const candidates = [
901
+ import_node_path6.default.resolve(baseDir, "../../../assets/init"),
902
+ import_node_path6.default.resolve(baseDir, "../../assets/init")
903
+ ];
904
+ for (const candidate of candidates) {
905
+ if ((0, import_node_fs.existsSync)(candidate)) {
906
+ return candidate;
907
+ }
908
+ }
909
+ throw new Error(
910
+ [
911
+ "init \u7528\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002Template assets not found.",
912
+ "\u78BA\u8A8D\u3057\u305F\u30D1\u30B9 / Checked paths:",
913
+ ...candidates.map((candidate) => `- ${candidate}`)
914
+ ].join("\n")
915
+ );
916
+ }
917
+
918
+ // src/core/promptsIntegrity.ts
919
+ async function diffProjectPromptsAgainstInitAssets(root) {
920
+ const promptsDir = import_node_path7.default.resolve(root, ".qfai", "prompts");
921
+ let templateDir;
922
+ try {
923
+ templateDir = import_node_path7.default.join(getInitAssetsDir(), ".qfai", "prompts");
924
+ } catch {
925
+ return {
926
+ status: "skipped_missing_assets",
927
+ promptsDir,
928
+ templateDir: "",
929
+ missing: [],
930
+ extra: [],
931
+ changed: []
932
+ };
933
+ }
934
+ const projectFiles = await collectFiles(promptsDir);
935
+ if (projectFiles.length === 0) {
936
+ return {
937
+ status: "skipped_missing_prompts",
938
+ promptsDir,
939
+ templateDir,
940
+ missing: [],
941
+ extra: [],
942
+ changed: []
943
+ };
944
+ }
945
+ const templateFiles = await collectFiles(templateDir);
946
+ const templateByRel = /* @__PURE__ */ new Map();
947
+ for (const abs of templateFiles) {
948
+ templateByRel.set(toRel(templateDir, abs), abs);
949
+ }
950
+ const projectByRel = /* @__PURE__ */ new Map();
951
+ for (const abs of projectFiles) {
952
+ projectByRel.set(toRel(promptsDir, abs), abs);
953
+ }
954
+ const missing = [];
955
+ const extra = [];
956
+ const changed = [];
957
+ for (const rel of templateByRel.keys()) {
958
+ if (!projectByRel.has(rel)) {
959
+ missing.push(rel);
960
+ }
961
+ }
962
+ for (const rel of projectByRel.keys()) {
963
+ if (!templateByRel.has(rel)) {
964
+ extra.push(rel);
965
+ }
966
+ }
967
+ const common = intersectKeys(templateByRel, projectByRel);
968
+ for (const rel of common) {
969
+ const templateAbs = templateByRel.get(rel);
970
+ const projectAbs = projectByRel.get(rel);
971
+ if (!templateAbs || !projectAbs) {
972
+ continue;
973
+ }
974
+ try {
975
+ const [a, b] = await Promise.all([
976
+ (0, import_promises6.readFile)(templateAbs, "utf-8"),
977
+ (0, import_promises6.readFile)(projectAbs, "utf-8")
978
+ ]);
979
+ if (normalizeNewlines(a) !== normalizeNewlines(b)) {
980
+ changed.push(rel);
981
+ }
982
+ } catch {
983
+ changed.push(rel);
984
+ }
985
+ }
986
+ const status = missing.length > 0 || extra.length > 0 || changed.length > 0 ? "modified" : "ok";
987
+ return {
988
+ status,
989
+ promptsDir,
990
+ templateDir,
991
+ missing: missing.sort(),
992
+ extra: extra.sort(),
993
+ changed: changed.sort()
994
+ };
995
+ }
996
+ function normalizeNewlines(text) {
997
+ return text.replace(/\r\n/g, "\n");
998
+ }
999
+ function toRel(base, abs) {
1000
+ const rel = import_node_path7.default.relative(base, abs);
1001
+ return rel.replace(/[\\/]+/g, "/");
1002
+ }
1003
+ function intersectKeys(a, b) {
1004
+ const out = [];
1005
+ for (const key of a.keys()) {
1006
+ if (b.has(key)) {
1007
+ out.push(key);
1008
+ }
1009
+ }
1010
+ return out;
1011
+ }
1012
+
1013
+ // src/core/version.ts
1014
+ var import_promises7 = require("fs/promises");
1015
+ var import_node_path8 = __toESM(require("path"), 1);
1016
+ var import_node_url2 = require("url");
891
1017
  async function resolveToolVersion() {
892
- if ("0.7.2".length > 0) {
893
- return "0.7.2";
1018
+ if ("0.8.1".length > 0) {
1019
+ return "0.8.1";
894
1020
  }
895
1021
  try {
896
1022
  const packagePath = resolvePackageJsonPath();
897
- const raw = await (0, import_promises6.readFile)(packagePath, "utf-8");
1023
+ const raw = await (0, import_promises7.readFile)(packagePath, "utf-8");
898
1024
  const parsed = JSON.parse(raw);
899
1025
  const version = typeof parsed.version === "string" ? parsed.version : "";
900
1026
  return version.length > 0 ? version : "unknown";
@@ -904,14 +1030,14 @@ async function resolveToolVersion() {
904
1030
  }
905
1031
  function resolvePackageJsonPath() {
906
1032
  const base = __filename;
907
- const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
908
- return import_node_path6.default.resolve(import_node_path6.default.dirname(basePath), "../../package.json");
1033
+ const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
1034
+ return import_node_path8.default.resolve(import_node_path8.default.dirname(basePath), "../../package.json");
909
1035
  }
910
1036
 
911
1037
  // src/core/doctor.ts
912
1038
  async function exists4(target) {
913
1039
  try {
914
- await (0, import_promises7.access)(target);
1040
+ await (0, import_promises8.access)(target);
915
1041
  return true;
916
1042
  } catch {
917
1043
  return false;
@@ -931,7 +1057,7 @@ function normalizeGlobs2(values) {
931
1057
  return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
932
1058
  }
933
1059
  async function createDoctorData(options) {
934
- const startDir = import_node_path7.default.resolve(options.startDir);
1060
+ const startDir = import_node_path9.default.resolve(options.startDir);
935
1061
  const checks = [];
936
1062
  const configPath = getConfigPath(startDir);
937
1063
  const search = options.rootExplicit ? {
@@ -994,9 +1120,9 @@ async function createDoctorData(options) {
994
1120
  details: { path: toRelativePath(root, resolved) }
995
1121
  });
996
1122
  if (key === "promptsDir") {
997
- const promptsLocalDir = import_node_path7.default.join(
998
- import_node_path7.default.dirname(resolved),
999
- `${import_node_path7.default.basename(resolved)}.local`
1123
+ const promptsLocalDir = import_node_path9.default.join(
1124
+ import_node_path9.default.dirname(resolved),
1125
+ `${import_node_path9.default.basename(resolved)}.local`
1000
1126
  );
1001
1127
  const found = await exists4(promptsLocalDir);
1002
1128
  addCheck(checks, {
@@ -1006,6 +1132,49 @@ async function createDoctorData(options) {
1006
1132
  message: found ? "prompts.local exists (overlay can be used)" : "prompts.local is optional (create it to override prompts)",
1007
1133
  details: { path: toRelativePath(root, promptsLocalDir) }
1008
1134
  });
1135
+ const diff = await diffProjectPromptsAgainstInitAssets(root);
1136
+ if (diff.status === "skipped_missing_prompts") {
1137
+ addCheck(checks, {
1138
+ id: "prompts.integrity",
1139
+ severity: "info",
1140
+ title: "Prompts integrity (.qfai/prompts)",
1141
+ message: "prompts \u304C\u672A\u4F5C\u6210\u306E\u305F\u3081\u691C\u67FB\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F\uFF08'qfai init' \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
1142
+ details: { promptsDir: toRelativePath(root, diff.promptsDir) }
1143
+ });
1144
+ } else if (diff.status === "skipped_missing_assets") {
1145
+ addCheck(checks, {
1146
+ id: "prompts.integrity",
1147
+ severity: "info",
1148
+ title: "Prompts integrity (.qfai/prompts)",
1149
+ message: "init assets \u304C\u898B\u3064\u304B\u3089\u306A\u3044\u305F\u3081\u691C\u67FB\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F\uFF08\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
1150
+ details: { promptsDir: toRelativePath(root, diff.promptsDir) }
1151
+ });
1152
+ } else if (diff.status === "ok") {
1153
+ addCheck(checks, {
1154
+ id: "prompts.integrity",
1155
+ severity: "ok",
1156
+ title: "Prompts integrity (.qfai/prompts)",
1157
+ message: "\u6A19\u6E96 assets \u3068\u4E00\u81F4\u3057\u3066\u3044\u307E\u3059",
1158
+ details: { promptsDir: toRelativePath(root, diff.promptsDir) }
1159
+ });
1160
+ } else {
1161
+ addCheck(checks, {
1162
+ id: "prompts.integrity",
1163
+ severity: "error",
1164
+ title: "Prompts integrity (.qfai/prompts)",
1165
+ message: "\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\u3002prompts \u306E\u76F4\u7DE8\u96C6\u306F\u975E\u63A8\u5968\u3067\u3059\uFF08\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8/\u518D init \u3067\u4E0A\u66F8\u304D\u3055\u308C\u5F97\u307E\u3059\uFF09\u3002",
1166
+ details: {
1167
+ promptsDir: toRelativePath(root, diff.promptsDir),
1168
+ missing: diff.missing,
1169
+ extra: diff.extra,
1170
+ changed: diff.changed,
1171
+ nextActions: [
1172
+ "\u5909\u66F4\u5185\u5BB9\u3092 .qfai/prompts.local/** \u306B\u79FB\u3059\uFF08\u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067\u914D\u7F6E\uFF09",
1173
+ "\u5FC5\u8981\u306A\u3089 qfai init --force \u3067 prompts \u3092\u6A19\u6E96\u72B6\u614B\u3078\u623B\u3059\uFF08prompts.local \u306F\u4FDD\u8B77\u3055\u308C\u307E\u3059\uFF09"
1174
+ ]
1175
+ }
1176
+ });
1177
+ }
1009
1178
  }
1010
1179
  }
1011
1180
  const specsRoot = resolvePath(root, config, "specsDir");
@@ -1026,7 +1195,7 @@ async function createDoctorData(options) {
1026
1195
  message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
1027
1196
  details: { specPacks: entries.length, missingFiles }
1028
1197
  });
1029
- const validateJsonAbs = import_node_path7.default.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : import_node_path7.default.resolve(root, config.output.validateJsonPath);
1198
+ const validateJsonAbs = import_node_path9.default.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : import_node_path9.default.resolve(root, config.output.validateJsonPath);
1030
1199
  const validateJsonExists = await exists4(validateJsonAbs);
1031
1200
  addCheck(checks, {
1032
1201
  id: "output.validateJson",
@@ -1036,8 +1205,8 @@ async function createDoctorData(options) {
1036
1205
  details: { path: toRelativePath(root, validateJsonAbs) }
1037
1206
  });
1038
1207
  const outDirAbs = resolvePath(root, config, "outDir");
1039
- const rel = import_node_path7.default.relative(outDirAbs, validateJsonAbs);
1040
- const inside = rel !== "" && !rel.startsWith("..") && !import_node_path7.default.isAbsolute(rel);
1208
+ const rel = import_node_path9.default.relative(outDirAbs, validateJsonAbs);
1209
+ const inside = rel !== "" && !rel.startsWith("..") && !import_node_path9.default.isAbsolute(rel);
1041
1210
  addCheck(checks, {
1042
1211
  id: "output.pathAlignment",
1043
1212
  severity: inside ? "ok" : "warning",
@@ -1143,12 +1312,12 @@ async function detectOutDirCollisions(root) {
1143
1312
  ignore: DEFAULT_CONFIG_SEARCH_IGNORE_GLOBS
1144
1313
  });
1145
1314
  const configRoots = Array.from(
1146
- new Set(configPaths.map((configPath) => import_node_path7.default.dirname(configPath)))
1315
+ new Set(configPaths.map((configPath) => import_node_path9.default.dirname(configPath)))
1147
1316
  ).sort((a, b) => a.localeCompare(b));
1148
1317
  const outDirToRoots = /* @__PURE__ */ new Map();
1149
1318
  for (const configRoot of configRoots) {
1150
1319
  const { config } = await loadConfig(configRoot);
1151
- const outDir = import_node_path7.default.normalize(resolvePath(configRoot, config, "outDir"));
1320
+ const outDir = import_node_path9.default.normalize(resolvePath(configRoot, config, "outDir"));
1152
1321
  const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
1153
1322
  roots.add(configRoot);
1154
1323
  outDirToRoots.set(outDir, roots);
@@ -1165,20 +1334,20 @@ async function detectOutDirCollisions(root) {
1165
1334
  return { monorepoRoot, configRoots, collisions };
1166
1335
  }
1167
1336
  async function findMonorepoRoot(startDir) {
1168
- let current = import_node_path7.default.resolve(startDir);
1337
+ let current = import_node_path9.default.resolve(startDir);
1169
1338
  while (true) {
1170
- const gitPath = import_node_path7.default.join(current, ".git");
1171
- const workspacePath = import_node_path7.default.join(current, "pnpm-workspace.yaml");
1339
+ const gitPath = import_node_path9.default.join(current, ".git");
1340
+ const workspacePath = import_node_path9.default.join(current, "pnpm-workspace.yaml");
1172
1341
  if (await exists4(gitPath) || await exists4(workspacePath)) {
1173
1342
  return current;
1174
1343
  }
1175
- const parent = import_node_path7.default.dirname(current);
1344
+ const parent = import_node_path9.default.dirname(current);
1176
1345
  if (parent === current) {
1177
1346
  break;
1178
1347
  }
1179
1348
  current = parent;
1180
1349
  }
1181
- return import_node_path7.default.resolve(startDir);
1350
+ return import_node_path9.default.resolve(startDir);
1182
1351
  }
1183
1352
 
1184
1353
  // src/cli/lib/logger.ts
@@ -1220,9 +1389,9 @@ async function runDoctor(options) {
1220
1389
  const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
1221
1390
  const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
1222
1391
  if (options.outPath) {
1223
- const outAbs = import_node_path8.default.isAbsolute(options.outPath) ? options.outPath : import_node_path8.default.resolve(process.cwd(), options.outPath);
1224
- await (0, import_promises8.mkdir)(import_node_path8.default.dirname(outAbs), { recursive: true });
1225
- await (0, import_promises8.writeFile)(outAbs, `${output}
1392
+ const outAbs = import_node_path10.default.isAbsolute(options.outPath) ? options.outPath : import_node_path10.default.resolve(process.cwd(), options.outPath);
1393
+ await (0, import_promises9.mkdir)(import_node_path10.default.dirname(outAbs), { recursive: true });
1394
+ await (0, import_promises9.writeFile)(outAbs, `${output}
1226
1395
  `, "utf-8");
1227
1396
  info(`doctor: wrote ${outAbs}`);
1228
1397
  return exitCode;
@@ -1241,36 +1410,59 @@ function shouldFailDoctor(summary, failOn) {
1241
1410
  }
1242
1411
 
1243
1412
  // src/cli/commands/init.ts
1244
- var import_node_path11 = __toESM(require("path"), 1);
1413
+ var import_node_path12 = __toESM(require("path"), 1);
1245
1414
 
1246
1415
  // src/cli/lib/fs.ts
1247
- var import_promises9 = require("fs/promises");
1248
- var import_node_path9 = __toESM(require("path"), 1);
1416
+ var import_promises10 = require("fs/promises");
1417
+ var import_node_path11 = __toESM(require("path"), 1);
1249
1418
  async function copyTemplateTree(sourceRoot, destRoot, options) {
1250
1419
  const files = await collectTemplateFiles(sourceRoot);
1251
1420
  return copyFiles(files, sourceRoot, destRoot, options);
1252
1421
  }
1422
+ async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
1423
+ const allFiles = [];
1424
+ for (const relPath of relativePaths) {
1425
+ const fullPath = import_node_path11.default.join(sourceRoot, relPath);
1426
+ const files = await collectTemplateFiles(fullPath);
1427
+ allFiles.push(...files);
1428
+ }
1429
+ return copyFiles(allFiles, sourceRoot, destRoot, options);
1430
+ }
1253
1431
  async function copyFiles(files, sourceRoot, destRoot, options) {
1254
1432
  const copied = [];
1255
1433
  const skipped = [];
1256
1434
  const conflicts = [];
1257
- const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path9.default.sep);
1435
+ const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path11.default.sep);
1436
+ const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path11.default.sep);
1258
1437
  const isProtectedRelative = (relative) => {
1259
1438
  if (protectPrefixes.length === 0) {
1260
1439
  return false;
1261
1440
  }
1262
- const normalized = relative.replace(/[\\/]+/g, import_node_path9.default.sep);
1441
+ const normalized = relative.replace(/[\\/]+/g, import_node_path11.default.sep);
1263
1442
  return protectPrefixes.some(
1264
1443
  (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1265
1444
  );
1266
1445
  };
1267
- if (!options.force) {
1446
+ const isExcludedRelative = (relative) => {
1447
+ if (excludePrefixes.length === 0) {
1448
+ return false;
1449
+ }
1450
+ const normalized = relative.replace(/[\\/]+/g, import_node_path11.default.sep);
1451
+ return excludePrefixes.some(
1452
+ (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1453
+ );
1454
+ };
1455
+ const conflictPolicy = options.conflictPolicy ?? "error";
1456
+ if (!options.force && conflictPolicy === "error") {
1268
1457
  for (const file of files) {
1269
- const relative = import_node_path9.default.relative(sourceRoot, file);
1458
+ const relative = import_node_path11.default.relative(sourceRoot, file);
1459
+ if (isExcludedRelative(relative)) {
1460
+ continue;
1461
+ }
1270
1462
  if (isProtectedRelative(relative)) {
1271
1463
  continue;
1272
1464
  }
1273
- const dest = import_node_path9.default.join(destRoot, relative);
1465
+ const dest = import_node_path11.default.join(destRoot, relative);
1274
1466
  if (!await shouldWrite(dest, options.force)) {
1275
1467
  conflicts.push(dest);
1276
1468
  }
@@ -1280,16 +1472,19 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1280
1472
  }
1281
1473
  }
1282
1474
  for (const file of files) {
1283
- const relative = import_node_path9.default.relative(sourceRoot, file);
1284
- const dest = import_node_path9.default.join(destRoot, relative);
1475
+ const relative = import_node_path11.default.relative(sourceRoot, file);
1476
+ if (isExcludedRelative(relative)) {
1477
+ continue;
1478
+ }
1479
+ const dest = import_node_path11.default.join(destRoot, relative);
1285
1480
  const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
1286
1481
  if (!await shouldWrite(dest, forceForThisFile)) {
1287
1482
  skipped.push(dest);
1288
1483
  continue;
1289
1484
  }
1290
1485
  if (!options.dryRun) {
1291
- await (0, import_promises9.mkdir)(import_node_path9.default.dirname(dest), { recursive: true });
1292
- await (0, import_promises9.copyFile)(file, dest);
1486
+ await (0, import_promises10.mkdir)(import_node_path11.default.dirname(dest), { recursive: true });
1487
+ await (0, import_promises10.copyFile)(file, dest);
1293
1488
  }
1294
1489
  copied.push(dest);
1295
1490
  }
@@ -1310,9 +1505,9 @@ async function collectTemplateFiles(root) {
1310
1505
  if (!await exists5(root)) {
1311
1506
  return entries;
1312
1507
  }
1313
- const items = await (0, import_promises9.readdir)(root, { withFileTypes: true });
1508
+ const items = await (0, import_promises10.readdir)(root, { withFileTypes: true });
1314
1509
  for (const item of items) {
1315
- const fullPath = import_node_path9.default.join(root, item.name);
1510
+ const fullPath = import_node_path11.default.join(root, item.name);
1316
1511
  if (item.isDirectory()) {
1317
1512
  const nested = await collectTemplateFiles(fullPath);
1318
1513
  entries.push(...nested);
@@ -1332,58 +1527,46 @@ async function shouldWrite(target, force) {
1332
1527
  }
1333
1528
  async function exists5(target) {
1334
1529
  try {
1335
- await (0, import_promises9.access)(target);
1530
+ await (0, import_promises10.access)(target);
1336
1531
  return true;
1337
1532
  } catch {
1338
1533
  return false;
1339
1534
  }
1340
1535
  }
1341
1536
 
1342
- // src/cli/lib/assets.ts
1343
- var import_node_fs = require("fs");
1344
- var import_node_path10 = __toESM(require("path"), 1);
1345
- var import_node_url2 = require("url");
1346
- function getInitAssetsDir() {
1347
- const base = __filename;
1348
- const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
1349
- const baseDir = import_node_path10.default.dirname(basePath);
1350
- const candidates = [
1351
- import_node_path10.default.resolve(baseDir, "../../../assets/init"),
1352
- import_node_path10.default.resolve(baseDir, "../../assets/init")
1353
- ];
1354
- for (const candidate of candidates) {
1355
- if ((0, import_node_fs.existsSync)(candidate)) {
1356
- return candidate;
1357
- }
1358
- }
1359
- throw new Error(
1360
- [
1361
- "init \u7528\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002Template assets not found.",
1362
- "\u78BA\u8A8D\u3057\u305F\u30D1\u30B9 / Checked paths:",
1363
- ...candidates.map((candidate) => `- ${candidate}`)
1364
- ].join("\n")
1365
- );
1366
- }
1367
-
1368
1537
  // src/cli/commands/init.ts
1369
1538
  async function runInit(options) {
1370
1539
  const assetsRoot = getInitAssetsDir();
1371
- const rootAssets = import_node_path11.default.join(assetsRoot, "root");
1372
- const qfaiAssets = import_node_path11.default.join(assetsRoot, ".qfai");
1373
- const destRoot = import_node_path11.default.resolve(options.dir);
1374
- const destQfai = import_node_path11.default.join(destRoot, ".qfai");
1540
+ const rootAssets = import_node_path12.default.join(assetsRoot, "root");
1541
+ const qfaiAssets = import_node_path12.default.join(assetsRoot, ".qfai");
1542
+ const destRoot = import_node_path12.default.resolve(options.dir);
1543
+ const destQfai = import_node_path12.default.join(destRoot, ".qfai");
1375
1544
  const rootResult = await copyTemplateTree(rootAssets, destRoot, {
1376
- force: options.force,
1377
- dryRun: options.dryRun
1545
+ force: false,
1546
+ dryRun: options.dryRun,
1547
+ conflictPolicy: "skip"
1378
1548
  });
1379
1549
  const qfaiResult = await copyTemplateTree(qfaiAssets, destQfai, {
1380
- force: options.force,
1550
+ force: false,
1381
1551
  dryRun: options.dryRun,
1382
- protect: ["prompts.local"]
1552
+ conflictPolicy: "skip",
1553
+ protect: ["prompts.local"],
1554
+ exclude: ["prompts"]
1383
1555
  });
1556
+ const promptsResult = await copyTemplatePaths(
1557
+ qfaiAssets,
1558
+ destQfai,
1559
+ ["prompts"],
1560
+ {
1561
+ force: options.force,
1562
+ dryRun: options.dryRun,
1563
+ conflictPolicy: "skip",
1564
+ protect: ["prompts.local"]
1565
+ }
1566
+ );
1384
1567
  report(
1385
- [...rootResult.copied, ...qfaiResult.copied],
1386
- [...rootResult.skipped, ...qfaiResult.skipped],
1568
+ [...rootResult.copied, ...qfaiResult.copied, ...promptsResult.copied],
1569
+ [...rootResult.skipped, ...qfaiResult.skipped, ...promptsResult.skipped],
1387
1570
  options.dryRun,
1388
1571
  "init"
1389
1572
  );
@@ -1399,8 +1582,8 @@ function report(copied, skipped, dryRun, label) {
1399
1582
  }
1400
1583
 
1401
1584
  // src/cli/commands/report.ts
1402
- var import_promises18 = require("fs/promises");
1403
- var import_node_path18 = __toESM(require("path"), 1);
1585
+ var import_promises19 = require("fs/promises");
1586
+ var import_node_path19 = __toESM(require("path"), 1);
1404
1587
 
1405
1588
  // src/core/normalize.ts
1406
1589
  function normalizeIssuePaths(root, issues) {
@@ -1440,12 +1623,12 @@ function normalizeValidationResult(root, result) {
1440
1623
  }
1441
1624
 
1442
1625
  // src/core/report.ts
1443
- var import_promises17 = require("fs/promises");
1444
- var import_node_path17 = __toESM(require("path"), 1);
1626
+ var import_promises18 = require("fs/promises");
1627
+ var import_node_path18 = __toESM(require("path"), 1);
1445
1628
 
1446
1629
  // src/core/contractIndex.ts
1447
- var import_promises10 = require("fs/promises");
1448
- var import_node_path12 = __toESM(require("path"), 1);
1630
+ var import_promises11 = require("fs/promises");
1631
+ var import_node_path13 = __toESM(require("path"), 1);
1449
1632
 
1450
1633
  // src/core/contractsDecl.ts
1451
1634
  var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
@@ -1467,9 +1650,9 @@ function stripContractDeclarationLines(text) {
1467
1650
  // src/core/contractIndex.ts
1468
1651
  async function buildContractIndex(root, config) {
1469
1652
  const contractsRoot = resolvePath(root, config, "contractsDir");
1470
- const uiRoot = import_node_path12.default.join(contractsRoot, "ui");
1471
- const apiRoot = import_node_path12.default.join(contractsRoot, "api");
1472
- const dbRoot = import_node_path12.default.join(contractsRoot, "db");
1653
+ const uiRoot = import_node_path13.default.join(contractsRoot, "ui");
1654
+ const apiRoot = import_node_path13.default.join(contractsRoot, "api");
1655
+ const dbRoot = import_node_path13.default.join(contractsRoot, "db");
1473
1656
  const [uiFiles, apiFiles, dbFiles] = await Promise.all([
1474
1657
  collectUiContractFiles(uiRoot),
1475
1658
  collectApiContractFiles(apiRoot),
@@ -1487,7 +1670,7 @@ async function buildContractIndex(root, config) {
1487
1670
  }
1488
1671
  async function indexContractFiles(files, index) {
1489
1672
  for (const file of files) {
1490
- const text = await (0, import_promises10.readFile)(file, "utf-8");
1673
+ const text = await (0, import_promises11.readFile)(file, "utf-8");
1491
1674
  extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
1492
1675
  }
1493
1676
  }
@@ -1722,14 +1905,14 @@ function parseSpec(md, file) {
1722
1905
  }
1723
1906
 
1724
1907
  // src/core/validators/contracts.ts
1725
- var import_promises11 = require("fs/promises");
1726
- var import_node_path14 = __toESM(require("path"), 1);
1908
+ var import_promises12 = require("fs/promises");
1909
+ var import_node_path15 = __toESM(require("path"), 1);
1727
1910
 
1728
1911
  // src/core/contracts.ts
1729
- var import_node_path13 = __toESM(require("path"), 1);
1912
+ var import_node_path14 = __toESM(require("path"), 1);
1730
1913
  var import_yaml2 = require("yaml");
1731
1914
  function parseStructuredContract(file, text) {
1732
- const ext = import_node_path13.default.extname(file).toLowerCase();
1915
+ const ext = import_node_path14.default.extname(file).toLowerCase();
1733
1916
  if (ext === ".json") {
1734
1917
  return JSON.parse(text);
1735
1918
  }
@@ -1749,9 +1932,9 @@ var SQL_DANGEROUS_PATTERNS = [
1749
1932
  async function validateContracts(root, config) {
1750
1933
  const issues = [];
1751
1934
  const contractsRoot = resolvePath(root, config, "contractsDir");
1752
- issues.push(...await validateUiContracts(import_node_path14.default.join(contractsRoot, "ui")));
1753
- issues.push(...await validateApiContracts(import_node_path14.default.join(contractsRoot, "api")));
1754
- issues.push(...await validateDbContracts(import_node_path14.default.join(contractsRoot, "db")));
1935
+ issues.push(...await validateUiContracts(import_node_path15.default.join(contractsRoot, "ui")));
1936
+ issues.push(...await validateApiContracts(import_node_path15.default.join(contractsRoot, "api")));
1937
+ issues.push(...await validateDbContracts(import_node_path15.default.join(contractsRoot, "db")));
1755
1938
  const contractIndex = await buildContractIndex(root, config);
1756
1939
  issues.push(...validateDuplicateContractIds(contractIndex));
1757
1940
  return issues;
@@ -1771,7 +1954,7 @@ async function validateUiContracts(uiRoot) {
1771
1954
  }
1772
1955
  const issues = [];
1773
1956
  for (const file of files) {
1774
- const text = await (0, import_promises11.readFile)(file, "utf-8");
1957
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1775
1958
  const invalidIds = extractInvalidIds(text, [
1776
1959
  "SPEC",
1777
1960
  "BR",
@@ -1826,7 +2009,7 @@ async function validateApiContracts(apiRoot) {
1826
2009
  }
1827
2010
  const issues = [];
1828
2011
  for (const file of files) {
1829
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2012
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1830
2013
  const invalidIds = extractInvalidIds(text, [
1831
2014
  "SPEC",
1832
2015
  "BR",
@@ -1894,7 +2077,7 @@ async function validateDbContracts(dbRoot) {
1894
2077
  }
1895
2078
  const issues = [];
1896
2079
  for (const file of files) {
1897
- const text = await (0, import_promises11.readFile)(file, "utf-8");
2080
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1898
2081
  const invalidIds = extractInvalidIds(text, [
1899
2082
  "SPEC",
1900
2083
  "BR",
@@ -2014,12 +2197,16 @@ function formatError4(error2) {
2014
2197
  }
2015
2198
  return String(error2);
2016
2199
  }
2017
- function issue(code, message, severity, file, rule, refs) {
2200
+ function issue(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
2018
2201
  const issue7 = {
2019
2202
  code,
2020
2203
  severity,
2204
+ category,
2021
2205
  message
2022
2206
  };
2207
+ if (suggested_action) {
2208
+ issue7.suggested_action = suggested_action;
2209
+ }
2023
2210
  if (file) {
2024
2211
  issue7.file = file;
2025
2212
  }
@@ -2033,8 +2220,8 @@ function issue(code, message, severity, file, rule, refs) {
2033
2220
  }
2034
2221
 
2035
2222
  // src/core/validators/delta.ts
2036
- var import_promises12 = require("fs/promises");
2037
- var import_node_path15 = __toESM(require("path"), 1);
2223
+ var import_promises13 = require("fs/promises");
2224
+ var import_node_path16 = __toESM(require("path"), 1);
2038
2225
  var SECTION_RE = /^##\s+変更区分/m;
2039
2226
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
2040
2227
  var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
@@ -2048,10 +2235,10 @@ async function validateDeltas(root, config) {
2048
2235
  }
2049
2236
  const issues = [];
2050
2237
  for (const pack of packs) {
2051
- const deltaPath = import_node_path15.default.join(pack, "delta.md");
2238
+ const deltaPath = import_node_path16.default.join(pack, "delta.md");
2052
2239
  let text;
2053
2240
  try {
2054
- text = await (0, import_promises12.readFile)(deltaPath, "utf-8");
2241
+ text = await (0, import_promises13.readFile)(deltaPath, "utf-8");
2055
2242
  } catch (error2) {
2056
2243
  if (isMissingFileError2(error2)) {
2057
2244
  issues.push(
@@ -2074,7 +2261,7 @@ async function validateDeltas(root, config) {
2074
2261
  issues.push(
2075
2262
  issue2(
2076
2263
  "QFAI-DELTA-002",
2077
- "delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002",
2264
+ "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
2265
  "error",
2079
2266
  deltaPath,
2080
2267
  "delta.section"
@@ -2104,12 +2291,16 @@ function isMissingFileError2(error2) {
2104
2291
  }
2105
2292
  return error2.code === "ENOENT";
2106
2293
  }
2107
- function issue2(code, message, severity, file, rule, refs) {
2294
+ function issue2(code, message, severity, file, rule, refs, category = "change", suggested_action) {
2108
2295
  const issue7 = {
2109
2296
  code,
2110
2297
  severity,
2298
+ category,
2111
2299
  message
2112
2300
  };
2301
+ if (suggested_action) {
2302
+ issue7.suggested_action = suggested_action;
2303
+ }
2113
2304
  if (file) {
2114
2305
  issue7.file = file;
2115
2306
  }
@@ -2123,8 +2314,8 @@ function issue2(code, message, severity, file, rule, refs) {
2123
2314
  }
2124
2315
 
2125
2316
  // src/core/validators/ids.ts
2126
- var import_promises13 = require("fs/promises");
2127
- var import_node_path16 = __toESM(require("path"), 1);
2317
+ var import_promises14 = require("fs/promises");
2318
+ var import_node_path17 = __toESM(require("path"), 1);
2128
2319
  var SC_TAG_RE3 = /^SC-\d{4}$/;
2129
2320
  async function validateDefinedIds(root, config) {
2130
2321
  const issues = [];
@@ -2159,7 +2350,7 @@ async function validateDefinedIds(root, config) {
2159
2350
  }
2160
2351
  async function collectSpecDefinitionIds(files, out) {
2161
2352
  for (const file of files) {
2162
- const text = await (0, import_promises13.readFile)(file, "utf-8");
2353
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2163
2354
  const parsed = parseSpec(text, file);
2164
2355
  if (parsed.specId) {
2165
2356
  recordId(out, parsed.specId, file);
@@ -2169,7 +2360,7 @@ async function collectSpecDefinitionIds(files, out) {
2169
2360
  }
2170
2361
  async function collectScenarioDefinitionIds(files, out) {
2171
2362
  for (const file of files) {
2172
- const text = await (0, import_promises13.readFile)(file, "utf-8");
2363
+ const text = await (0, import_promises14.readFile)(file, "utf-8");
2173
2364
  const { document, errors } = parseScenarioDocument(text, file);
2174
2365
  if (!document || errors.length > 0) {
2175
2366
  continue;
@@ -2190,16 +2381,20 @@ function recordId(out, id, file) {
2190
2381
  }
2191
2382
  function formatFileList(files, root) {
2192
2383
  return files.map((file) => {
2193
- const relative = import_node_path16.default.relative(root, file);
2384
+ const relative = import_node_path17.default.relative(root, file);
2194
2385
  return relative.length > 0 ? relative : file;
2195
2386
  }).join(", ");
2196
2387
  }
2197
- function issue3(code, message, severity, file, rule, refs) {
2388
+ function issue3(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
2198
2389
  const issue7 = {
2199
2390
  code,
2200
2391
  severity,
2392
+ category,
2201
2393
  message
2202
2394
  };
2395
+ if (suggested_action) {
2396
+ issue7.suggested_action = suggested_action;
2397
+ }
2203
2398
  if (file) {
2204
2399
  issue7.file = file;
2205
2400
  }
@@ -2212,8 +2407,39 @@ function issue3(code, message, severity, file, rule, refs) {
2212
2407
  return issue7;
2213
2408
  }
2214
2409
 
2410
+ // src/core/validators/promptsIntegrity.ts
2411
+ async function validatePromptsIntegrity(root) {
2412
+ const diff = await diffProjectPromptsAgainstInitAssets(root);
2413
+ if (diff.status !== "modified") {
2414
+ return [];
2415
+ }
2416
+ const total = diff.missing.length + diff.extra.length + diff.changed.length;
2417
+ const hints = [
2418
+ diff.changed.length > 0 ? `\u5909\u66F4: ${diff.changed.length}` : null,
2419
+ diff.missing.length > 0 ? `\u524A\u9664: ${diff.missing.length}` : null,
2420
+ diff.extra.length > 0 ? `\u8FFD\u52A0: ${diff.extra.length}` : null
2421
+ ].filter(Boolean).join(" / ");
2422
+ const sample = [...diff.changed, ...diff.missing, ...diff.extra].slice(0, 10);
2423
+ const sampleText = sample.length > 0 ? ` \u4F8B: ${sample.join(", ")}` : "";
2424
+ return [
2425
+ {
2426
+ code: "QFAI-PROMPTS-001",
2427
+ severity: "error",
2428
+ category: "change",
2429
+ message: `\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\uFF08${hints || `\u5DEE\u5206=${total}`}\uFF09\u3002${sampleText}`,
2430
+ suggested_action: [
2431
+ "prompts \u306E\u76F4\u7DE8\u96C6\u306F\u975E\u63A8\u5968\u3067\u3059\uFF08\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8/\u518D init \u3067\u4E0A\u66F8\u304D\u3055\u308C\u5F97\u307E\u3059\uFF09\u3002",
2432
+ "\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u5B9F\u65BD\u3057\u3066\u304F\u3060\u3055\u3044:",
2433
+ "- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
2434
+ "- \u6A19\u6E96\u72B6\u614B\u3078\u623B\u3059\u5834\u5408: 'qfai init --force' \u3092\u5B9F\u884C\uFF08prompts \u306E\u307F\u4E0A\u66F8\u304D\u3001prompts.local \u306F\u4FDD\u8B77\uFF09"
2435
+ ].join("\n"),
2436
+ rule: "prompts.integrity"
2437
+ }
2438
+ ];
2439
+ }
2440
+
2215
2441
  // src/core/validators/scenario.ts
2216
- var import_promises14 = require("fs/promises");
2442
+ var import_promises15 = require("fs/promises");
2217
2443
  var GIVEN_PATTERN = /\bGiven\b/;
2218
2444
  var WHEN_PATTERN = /\bWhen\b/;
2219
2445
  var THEN_PATTERN = /\bThen\b/;
@@ -2239,7 +2465,7 @@ async function validateScenarios(root, config) {
2239
2465
  for (const entry of entries) {
2240
2466
  let text;
2241
2467
  try {
2242
- text = await (0, import_promises14.readFile)(entry.scenarioPath, "utf-8");
2468
+ text = await (0, import_promises15.readFile)(entry.scenarioPath, "utf-8");
2243
2469
  } catch (error2) {
2244
2470
  if (isMissingFileError3(error2)) {
2245
2471
  issues.push(
@@ -2384,12 +2610,16 @@ function validateScenarioContent(text, file) {
2384
2610
  }
2385
2611
  return issues;
2386
2612
  }
2387
- function issue4(code, message, severity, file, rule, refs) {
2613
+ function issue4(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
2388
2614
  const issue7 = {
2389
2615
  code,
2390
2616
  severity,
2617
+ category,
2391
2618
  message
2392
2619
  };
2620
+ if (suggested_action) {
2621
+ issue7.suggested_action = suggested_action;
2622
+ }
2393
2623
  if (file) {
2394
2624
  issue7.file = file;
2395
2625
  }
@@ -2409,7 +2639,7 @@ function isMissingFileError3(error2) {
2409
2639
  }
2410
2640
 
2411
2641
  // src/core/validators/spec.ts
2412
- var import_promises15 = require("fs/promises");
2642
+ var import_promises16 = require("fs/promises");
2413
2643
  async function validateSpecs(root, config) {
2414
2644
  const specsRoot = resolvePath(root, config, "specsDir");
2415
2645
  const entries = await collectSpecEntries(specsRoot);
@@ -2430,7 +2660,7 @@ async function validateSpecs(root, config) {
2430
2660
  for (const entry of entries) {
2431
2661
  let text;
2432
2662
  try {
2433
- text = await (0, import_promises15.readFile)(entry.specPath, "utf-8");
2663
+ text = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
2434
2664
  } catch (error2) {
2435
2665
  if (isMissingFileError4(error2)) {
2436
2666
  issues.push(
@@ -2554,12 +2784,16 @@ function validateSpecContent(text, file, requiredSections) {
2554
2784
  }
2555
2785
  return issues;
2556
2786
  }
2557
- function issue5(code, message, severity, file, rule, refs) {
2787
+ function issue5(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
2558
2788
  const issue7 = {
2559
2789
  code,
2560
2790
  severity,
2791
+ category,
2561
2792
  message
2562
2793
  };
2794
+ if (suggested_action) {
2795
+ issue7.suggested_action = suggested_action;
2796
+ }
2563
2797
  if (file) {
2564
2798
  issue7.file = file;
2565
2799
  }
@@ -2579,7 +2813,7 @@ function isMissingFileError4(error2) {
2579
2813
  }
2580
2814
 
2581
2815
  // src/core/validators/traceability.ts
2582
- var import_promises16 = require("fs/promises");
2816
+ var import_promises17 = require("fs/promises");
2583
2817
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
2584
2818
  var BR_TAG_RE2 = /^BR-\d{4}$/;
2585
2819
  async function validateTraceability(root, config) {
@@ -2599,7 +2833,7 @@ async function validateTraceability(root, config) {
2599
2833
  const contractIndex = await buildContractIndex(root, config);
2600
2834
  const contractIds = contractIndex.ids;
2601
2835
  for (const file of specFiles) {
2602
- const text = await (0, import_promises16.readFile)(file, "utf-8");
2836
+ const text = await (0, import_promises17.readFile)(file, "utf-8");
2603
2837
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2604
2838
  const parsed = parseSpec(text, file);
2605
2839
  if (parsed.specId) {
@@ -2617,7 +2851,7 @@ async function validateTraceability(root, config) {
2617
2851
  issues.push(
2618
2852
  issue6(
2619
2853
  "QFAI-TRACE-020",
2620
- "Spec \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
2854
+ "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
2855
  "error",
2622
2856
  file,
2623
2857
  "traceability.specContractRefRequired"
@@ -2628,7 +2862,7 @@ async function validateTraceability(root, config) {
2628
2862
  issues.push(
2629
2863
  issue6(
2630
2864
  "QFAI-TRACE-023",
2631
- "Spec \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
2865
+ "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
2866
  "error",
2633
2867
  file,
2634
2868
  "traceability.specContractRefFormat"
@@ -2641,7 +2875,7 @@ async function validateTraceability(root, config) {
2641
2875
  "QFAI-TRACE-021",
2642
2876
  `Spec \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${contractRefs.invalidTokens.join(
2643
2877
  ", "
2644
- )}`,
2878
+ )} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
2645
2879
  "error",
2646
2880
  file,
2647
2881
  "traceability.specContractRefFormat",
@@ -2672,7 +2906,7 @@ async function validateTraceability(root, config) {
2672
2906
  }
2673
2907
  }
2674
2908
  for (const file of scenarioFiles) {
2675
- const text = await (0, import_promises16.readFile)(file, "utf-8");
2909
+ const text = await (0, import_promises17.readFile)(file, "utf-8");
2676
2910
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2677
2911
  const scenarioContractRefs = parseContractRefs(text, {
2678
2912
  allowCommentPrefix: true
@@ -2681,7 +2915,7 @@ async function validateTraceability(root, config) {
2681
2915
  issues.push(
2682
2916
  issue6(
2683
2917
  "QFAI-TRACE-031",
2684
- "Scenario \u306B QFAI-CONTRACT-REF \u304C\u3042\u308A\u307E\u305B\u3093\u3002",
2918
+ "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
2919
  "error",
2686
2920
  file,
2687
2921
  "traceability.scenarioContractRefRequired"
@@ -2692,7 +2926,7 @@ async function validateTraceability(root, config) {
2692
2926
  issues.push(
2693
2927
  issue6(
2694
2928
  "QFAI-TRACE-033",
2695
- "Scenario \u306E QFAI-CONTRACT-REF \u306B none \u3068\u5951\u7D04 ID \u304C\u6DF7\u5728\u3057\u3066\u3044\u307E\u3059\u3002",
2929
+ "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
2930
  "error",
2697
2931
  file,
2698
2932
  "traceability.scenarioContractRefFormat"
@@ -2705,7 +2939,7 @@ async function validateTraceability(root, config) {
2705
2939
  "QFAI-TRACE-032",
2706
2940
  `Scenario \u306E\u5951\u7D04 ID \u304C\u4E0D\u6B63\u3067\u3059: ${scenarioContractRefs.invalidTokens.join(
2707
2941
  ", "
2708
- )}`,
2942
+ )} (\u4F8B: UI-0001 / API-0001 / DB-0001)`,
2709
2943
  "error",
2710
2944
  file,
2711
2945
  "traceability.scenarioContractRefFormat",
@@ -2994,7 +3228,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
2994
3228
  const pattern = buildIdPattern(Array.from(upstreamIds));
2995
3229
  let found = false;
2996
3230
  for (const file of targetFiles) {
2997
- const text = await (0, import_promises16.readFile)(file, "utf-8");
3231
+ const text = await (0, import_promises17.readFile)(file, "utf-8");
2998
3232
  if (pattern.test(text)) {
2999
3233
  found = true;
3000
3234
  break;
@@ -3017,12 +3251,16 @@ function buildIdPattern(ids) {
3017
3251
  const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
3018
3252
  return new RegExp(`\\b(${escaped.join("|")})\\b`);
3019
3253
  }
3020
- function issue6(code, message, severity, file, rule, refs) {
3254
+ function issue6(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
3021
3255
  const issue7 = {
3022
3256
  code,
3023
3257
  severity,
3258
+ category,
3024
3259
  message
3025
3260
  };
3261
+ if (suggested_action) {
3262
+ issue7.suggested_action = suggested_action;
3263
+ }
3026
3264
  if (file) {
3027
3265
  issue7.file = file;
3028
3266
  }
@@ -3041,6 +3279,7 @@ async function validateProject(root, configResult) {
3041
3279
  const { config, issues: configIssues } = resolved;
3042
3280
  const issues = [
3043
3281
  ...configIssues,
3282
+ ...await validatePromptsIntegrity(root),
3044
3283
  ...await validateSpecs(root, config),
3045
3284
  ...await validateDeltas(root, config),
3046
3285
  ...await validateScenarios(root, config),
@@ -3081,15 +3320,15 @@ function countIssues(issues) {
3081
3320
  // src/core/report.ts
3082
3321
  var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
3083
3322
  async function createReportData(root, validation, configResult) {
3084
- const resolvedRoot = import_node_path17.default.resolve(root);
3323
+ const resolvedRoot = import_node_path18.default.resolve(root);
3085
3324
  const resolved = configResult ?? await loadConfig(resolvedRoot);
3086
3325
  const config = resolved.config;
3087
3326
  const configPath = resolved.configPath;
3088
3327
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
3089
3328
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
3090
- const apiRoot = import_node_path17.default.join(contractsRoot, "api");
3091
- const uiRoot = import_node_path17.default.join(contractsRoot, "ui");
3092
- const dbRoot = import_node_path17.default.join(contractsRoot, "db");
3329
+ const apiRoot = import_node_path18.default.join(contractsRoot, "api");
3330
+ const uiRoot = import_node_path18.default.join(contractsRoot, "ui");
3331
+ const dbRoot = import_node_path18.default.join(contractsRoot, "db");
3093
3332
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
3094
3333
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
3095
3334
  const specFiles = await collectSpecFiles(specsRoot);
@@ -3203,7 +3442,39 @@ function formatReportMarkdown(data) {
3203
3442
  lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
3204
3443
  lines.push(`- \u7248: ${data.version}`);
3205
3444
  lines.push("");
3206
- lines.push("## \u6982\u8981");
3445
+ const severityOrder = {
3446
+ error: 0,
3447
+ warning: 1,
3448
+ info: 2
3449
+ };
3450
+ const categoryOrder = {
3451
+ compatibility: 0,
3452
+ change: 1
3453
+ };
3454
+ const issuesByCategory = {
3455
+ compatibility: [],
3456
+ change: []
3457
+ };
3458
+ for (const issue7 of data.issues) {
3459
+ const cat = issue7.category;
3460
+ if (cat === "change") {
3461
+ issuesByCategory.change.push(issue7);
3462
+ } else {
3463
+ issuesByCategory.compatibility.push(issue7);
3464
+ }
3465
+ }
3466
+ const countIssuesBySeverity = (issues) => issues.reduce(
3467
+ (acc, i) => {
3468
+ acc[i.severity] += 1;
3469
+ return acc;
3470
+ },
3471
+ { info: 0, warning: 0, error: 0 }
3472
+ );
3473
+ const compatCounts = countIssuesBySeverity(issuesByCategory.compatibility);
3474
+ const changeCounts = countIssuesBySeverity(issuesByCategory.change);
3475
+ lines.push("## Dashboard");
3476
+ lines.push("");
3477
+ lines.push("### Summary");
3207
3478
  lines.push("");
3208
3479
  lines.push(`- specs: ${data.summary.specs}`);
3209
3480
  lines.push(`- scenarios: ${data.summary.scenarios}`);
@@ -3211,10 +3482,141 @@ function formatReportMarkdown(data) {
3211
3482
  `- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
3212
3483
  );
3213
3484
  lines.push(
3214
- `- issues: info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
3485
+ `- issues(total): info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
3486
+ );
3487
+ lines.push(
3488
+ `- issues(compatibility): info ${compatCounts.info} / warning ${compatCounts.warning} / error ${compatCounts.error}`
3489
+ );
3490
+ lines.push(
3491
+ `- issues(change): info ${changeCounts.info} / warning ${changeCounts.warning} / error ${changeCounts.error}`
3492
+ );
3493
+ lines.push(
3494
+ `- fail-on=error: ${data.summary.counts.error > 0 ? "FAIL" : "PASS"}`
3215
3495
  );
3496
+ lines.push(
3497
+ `- fail-on=warning: ${data.summary.counts.error + data.summary.counts.warning > 0 ? "FAIL" : "PASS"}`
3498
+ );
3499
+ lines.push("");
3500
+ lines.push("### Next Actions");
3501
+ lines.push("");
3502
+ if (data.summary.counts.error > 0) {
3503
+ lines.push(
3504
+ "- error \u304C\u3042\u308B\u305F\u3081\u3001\u307E\u305A `qfai validate --fail-on error` \u3092\u901A\u308B\u307E\u3067\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3505
+ );
3506
+ lines.push(
3507
+ "- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
3508
+ );
3509
+ } else if (data.summary.counts.warning > 0) {
3510
+ lines.push(
3511
+ "- warning \u306E\u6271\u3044\u306F\u30C1\u30FC\u30E0\u5224\u65AD\u3067\u3059\u3002`--fail-on warning` \u904B\u7528\u306A\u3089\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
3512
+ );
3513
+ lines.push(
3514
+ "- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
3515
+ );
3516
+ } else {
3517
+ lines.push("- issue \u306F\u3042\u308A\u307E\u305B\u3093\u3002\u904B\u7528\u30C6\u30F3\u30D7\u30EC\u306B\u6CBF\u3063\u3066\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
3518
+ lines.push(
3519
+ "- \u6B21\u306E\u624B\u9806: `qfai doctor` \u2192 `qfai validate` \u2192 `qfai report`\uFF08\u5B9A\u671F\u7684\u306B\u5B9F\u884C\uFF09"
3520
+ );
3521
+ }
3522
+ lines.push("");
3523
+ lines.push("### Index");
3524
+ lines.push("");
3525
+ lines.push("- [Compatibility Issues](#compatibility-issues)");
3526
+ lines.push("- [Change Issues](#change-issues)");
3527
+ lines.push("- [IDs](#ids)");
3528
+ lines.push("- [Traceability](#traceability)");
3529
+ lines.push("");
3530
+ const formatIssueSummaryTable = (issues) => {
3531
+ const issueKeyToCount = /* @__PURE__ */ new Map();
3532
+ for (const issue7 of issues) {
3533
+ const key = `${issue7.category}|${issue7.severity}|${issue7.code}`;
3534
+ const current = issueKeyToCount.get(key);
3535
+ if (current) {
3536
+ current.count += 1;
3537
+ continue;
3538
+ }
3539
+ issueKeyToCount.set(key, {
3540
+ category: issue7.category,
3541
+ severity: issue7.severity,
3542
+ code: issue7.code,
3543
+ count: 1
3544
+ });
3545
+ }
3546
+ const rows = Array.from(issueKeyToCount.values()).sort((a, b) => {
3547
+ const ca = categoryOrder[a.category] ?? 999;
3548
+ const cb = categoryOrder[b.category] ?? 999;
3549
+ if (ca !== cb) return ca - cb;
3550
+ const sa = severityOrder[a.severity] ?? 999;
3551
+ const sb = severityOrder[b.severity] ?? 999;
3552
+ if (sa !== sb) return sa - sb;
3553
+ return a.code.localeCompare(b.code);
3554
+ }).map((x) => [x.severity, x.code, String(x.count)]);
3555
+ return rows.length === 0 ? ["- (none)"] : formatMarkdownTable(["Severity", "Code", "Count"], rows);
3556
+ };
3557
+ const formatIssueCards = (issues) => {
3558
+ const sorted = [...issues].sort((a, b) => {
3559
+ const sa = severityOrder[a.severity] ?? 999;
3560
+ const sb = severityOrder[b.severity] ?? 999;
3561
+ if (sa !== sb) return sa - sb;
3562
+ const code = a.code.localeCompare(b.code);
3563
+ if (code !== 0) return code;
3564
+ const fileA = a.file ?? "";
3565
+ const fileB = b.file ?? "";
3566
+ const file = fileA.localeCompare(fileB);
3567
+ if (file !== 0) return file;
3568
+ const lineA = a.loc?.line ?? 0;
3569
+ const lineB = b.loc?.line ?? 0;
3570
+ return lineA - lineB;
3571
+ });
3572
+ if (sorted.length === 0) {
3573
+ return ["- (none)"];
3574
+ }
3575
+ const out = [];
3576
+ for (const item of sorted) {
3577
+ out.push(
3578
+ `#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
3579
+ );
3580
+ if (item.file) {
3581
+ const loc = item.loc?.line ? `:${item.loc.line}` : "";
3582
+ out.push(`- file: ${item.file}${loc}`);
3583
+ }
3584
+ if (item.rule) {
3585
+ out.push(`- rule: ${item.rule}`);
3586
+ }
3587
+ if (item.refs && item.refs.length > 0) {
3588
+ out.push(`- refs: ${item.refs.join(", ")}`);
3589
+ }
3590
+ if (item.suggested_action) {
3591
+ out.push("- suggested_action:");
3592
+ const actionLines = String(item.suggested_action).split("\n");
3593
+ for (const line of actionLines) {
3594
+ out.push(` ${line}`);
3595
+ }
3596
+ }
3597
+ out.push("");
3598
+ }
3599
+ return out;
3600
+ };
3601
+ lines.push("## Compatibility Issues");
3602
+ lines.push("");
3603
+ lines.push("### Summary");
3604
+ lines.push("");
3605
+ lines.push(...formatIssueSummaryTable(issuesByCategory.compatibility));
3606
+ lines.push("");
3607
+ lines.push("### Issues");
3216
3608
  lines.push("");
3217
- lines.push("## ID\u96C6\u8A08");
3609
+ lines.push(...formatIssueCards(issuesByCategory.compatibility));
3610
+ lines.push("## Change Issues");
3611
+ lines.push("");
3612
+ lines.push("### Summary");
3613
+ lines.push("");
3614
+ lines.push(...formatIssueSummaryTable(issuesByCategory.change));
3615
+ lines.push("");
3616
+ lines.push("### Issues");
3617
+ lines.push("");
3618
+ lines.push(...formatIssueCards(issuesByCategory.change));
3619
+ lines.push("## IDs");
3218
3620
  lines.push("");
3219
3621
  lines.push(formatIdLine("SPEC", data.ids.spec));
3220
3622
  lines.push(formatIdLine("BR", data.ids.br));
@@ -3223,14 +3625,14 @@ function formatReportMarkdown(data) {
3223
3625
  lines.push(formatIdLine("API", data.ids.api));
3224
3626
  lines.push(formatIdLine("DB", data.ids.db));
3225
3627
  lines.push("");
3226
- lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3");
3628
+ lines.push("## Traceability");
3227
3629
  lines.push("");
3228
3630
  lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
3229
3631
  lines.push(
3230
3632
  `- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
3231
3633
  );
3232
3634
  lines.push("");
3233
- lines.push("## \u5951\u7D04\u30AB\u30D0\u30EC\u30C3\u30B8");
3635
+ lines.push("### Contract Coverage");
3234
3636
  lines.push("");
3235
3637
  lines.push(`- total: ${data.traceability.contracts.total}`);
3236
3638
  lines.push(`- referenced: ${data.traceability.contracts.referenced}`);
@@ -3239,7 +3641,7 @@ function formatReportMarkdown(data) {
3239
3641
  `- specContractRefMissing: ${data.traceability.specs.contractRefMissing}`
3240
3642
  );
3241
3643
  lines.push("");
3242
- lines.push("## \u5951\u7D04\u2192Spec");
3644
+ lines.push("### Contract \u2192 Spec");
3243
3645
  lines.push("");
3244
3646
  const contractToSpecs = data.traceability.contracts.idToSpecs;
3245
3647
  const contractIds = Object.keys(contractToSpecs).sort(
@@ -3258,7 +3660,7 @@ function formatReportMarkdown(data) {
3258
3660
  }
3259
3661
  }
3260
3662
  lines.push("");
3261
- lines.push("## Spec\u2192\u5951\u7D04");
3663
+ lines.push("### Spec \u2192 Contracts");
3262
3664
  lines.push("");
3263
3665
  const specToContracts = data.traceability.specs.specToContracts;
3264
3666
  const specIds = Object.keys(specToContracts).sort(
@@ -3276,7 +3678,7 @@ function formatReportMarkdown(data) {
3276
3678
  lines.push(...formatMarkdownTable(["Spec", "Status", "Contracts"], rows));
3277
3679
  }
3278
3680
  lines.push("");
3279
- lines.push("## Spec\u3067 contract-ref \u672A\u5BA3\u8A00");
3681
+ lines.push("### Specs missing contract-ref");
3280
3682
  lines.push("");
3281
3683
  const missingRefSpecs = data.traceability.specs.missingRefSpecs;
3282
3684
  if (missingRefSpecs.length === 0) {
@@ -3287,7 +3689,7 @@ function formatReportMarkdown(data) {
3287
3689
  }
3288
3690
  }
3289
3691
  lines.push("");
3290
- lines.push("## SC\u30AB\u30D0\u30EC\u30C3\u30B8");
3692
+ lines.push("### SC coverage");
3291
3693
  lines.push("");
3292
3694
  lines.push(`- total: ${data.traceability.sc.total}`);
3293
3695
  lines.push(`- covered: ${data.traceability.sc.covered}`);
@@ -3317,7 +3719,7 @@ function formatReportMarkdown(data) {
3317
3719
  lines.push(`- missingIds: ${missingWithSources.join(", ")}`);
3318
3720
  }
3319
3721
  lines.push("");
3320
- lines.push("## SC\u2192\u53C2\u7167\u30C6\u30B9\u30C8");
3722
+ lines.push("### SC \u2192 referenced tests");
3321
3723
  lines.push("");
3322
3724
  const scRefs = data.traceability.sc.refs;
3323
3725
  const scIds = Object.keys(scRefs).sort((a, b) => a.localeCompare(b));
@@ -3334,7 +3736,7 @@ function formatReportMarkdown(data) {
3334
3736
  }
3335
3737
  }
3336
3738
  lines.push("");
3337
- lines.push("## Spec:SC=1:1 \u9055\u53CD");
3739
+ lines.push("### Spec:SC=1:1 violations");
3338
3740
  lines.push("");
3339
3741
  const specScIssues = data.issues.filter(
3340
3742
  (item) => item.code === "QFAI-TRACE-012"
@@ -3349,7 +3751,7 @@ function formatReportMarkdown(data) {
3349
3751
  }
3350
3752
  }
3351
3753
  lines.push("");
3352
- lines.push("## Hotspots");
3754
+ lines.push("### Hotspots");
3353
3755
  lines.push("");
3354
3756
  const hotspots = buildHotspots(data.issues);
3355
3757
  if (hotspots.length === 0) {
@@ -3362,35 +3764,28 @@ function formatReportMarkdown(data) {
3362
3764
  }
3363
3765
  }
3364
3766
  lines.push("");
3365
- lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
3767
+ lines.push("## Guidance");
3366
3768
  lines.push("");
3367
- const traceIssues = data.issues.filter(
3368
- (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code.startsWith("QFAI-TRACE-")
3769
+ lines.push(
3770
+ "- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
3369
3771
  );
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)");
3772
+ if (data.summary.counts.error > 0) {
3773
+ lines.push("- error \u304C\u3042\u308B\u305F\u3081\u3001\u307E\u305A error \u304B\u3089\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
3774
+ } else if (data.summary.counts.warning > 0) {
3775
+ lines.push(
3776
+ "- warning \u306E\u6271\u3044\uFF08Hard Gate \u306B\u3059\u308B\u304B\uFF09\u306F\u904B\u7528\u3067\u6C7A\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
3777
+ );
3385
3778
  } 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
- }
3779
+ lines.push(
3780
+ "- 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"
3781
+ );
3393
3782
  }
3783
+ lines.push(
3784
+ "- \u5909\u66F4\u533A\u5206\uFF08Compatibility / Change/Improvement\uFF09\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002"
3785
+ );
3786
+ lines.push(
3787
+ "- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md` / `.qfai/promptpack/steering/compatibility-vs-change.md`"
3788
+ );
3394
3789
  return lines.join("\n");
3395
3790
  }
3396
3791
  function formatReportJson(data) {
@@ -3404,7 +3799,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
3404
3799
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
3405
3800
  }
3406
3801
  for (const file of specFiles) {
3407
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3802
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3408
3803
  const parsed = parseSpec(text, file);
3409
3804
  const specKey = parsed.specId;
3410
3805
  if (!specKey) {
@@ -3445,7 +3840,7 @@ async function collectIds(files) {
3445
3840
  DB: /* @__PURE__ */ new Set()
3446
3841
  };
3447
3842
  for (const file of files) {
3448
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3843
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3449
3844
  for (const prefix of ID_PREFIXES2) {
3450
3845
  const ids = extractIds(text, prefix);
3451
3846
  ids.forEach((id) => result[prefix].add(id));
@@ -3463,7 +3858,7 @@ async function collectIds(files) {
3463
3858
  async function collectUpstreamIds(files) {
3464
3859
  const ids = /* @__PURE__ */ new Set();
3465
3860
  for (const file of files) {
3466
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3861
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3467
3862
  extractAllIds(text).forEach((id) => ids.add(id));
3468
3863
  }
3469
3864
  return ids;
@@ -3484,7 +3879,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
3484
3879
  }
3485
3880
  const pattern = buildIdPattern2(Array.from(upstreamIds));
3486
3881
  for (const file of targetFiles) {
3487
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3882
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3488
3883
  if (pattern.test(text)) {
3489
3884
  return true;
3490
3885
  }
@@ -3576,7 +3971,7 @@ function buildHotspots(issues) {
3576
3971
 
3577
3972
  // src/cli/commands/report.ts
3578
3973
  async function runReport(options) {
3579
- const root = import_node_path18.default.resolve(options.root);
3974
+ const root = import_node_path19.default.resolve(options.root);
3580
3975
  const configResult = await loadConfig(root);
3581
3976
  let validation;
3582
3977
  if (options.runValidate) {
@@ -3593,7 +3988,7 @@ async function runReport(options) {
3593
3988
  validation = normalized;
3594
3989
  } else {
3595
3990
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
3596
- const inputPath = import_node_path18.default.isAbsolute(input) ? input : import_node_path18.default.resolve(root, input);
3991
+ const inputPath = import_node_path19.default.isAbsolute(input) ? input : import_node_path19.default.resolve(root, input);
3597
3992
  try {
3598
3993
  validation = await readValidationResult(inputPath);
3599
3994
  } catch (err) {
@@ -3619,11 +4014,11 @@ async function runReport(options) {
3619
4014
  const data = await createReportData(root, validation, configResult);
3620
4015
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
3621
4016
  const outRoot = resolvePath(root, configResult.config, "outDir");
3622
- const defaultOut = options.format === "json" ? import_node_path18.default.join(outRoot, "report.json") : import_node_path18.default.join(outRoot, "report.md");
4017
+ const defaultOut = options.format === "json" ? import_node_path19.default.join(outRoot, "report.json") : import_node_path19.default.join(outRoot, "report.md");
3623
4018
  const out = options.outPath ?? defaultOut;
3624
- const outPath = import_node_path18.default.isAbsolute(out) ? out : import_node_path18.default.resolve(root, out);
3625
- await (0, import_promises18.mkdir)(import_node_path18.default.dirname(outPath), { recursive: true });
3626
- await (0, import_promises18.writeFile)(outPath, `${output}
4019
+ const outPath = import_node_path19.default.isAbsolute(out) ? out : import_node_path19.default.resolve(root, out);
4020
+ await (0, import_promises19.mkdir)(import_node_path19.default.dirname(outPath), { recursive: true });
4021
+ await (0, import_promises19.writeFile)(outPath, `${output}
3627
4022
  `, "utf-8");
3628
4023
  info(
3629
4024
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -3631,7 +4026,7 @@ async function runReport(options) {
3631
4026
  info(`wrote report: ${outPath}`);
3632
4027
  }
3633
4028
  async function readValidationResult(inputPath) {
3634
- const raw = await (0, import_promises18.readFile)(inputPath, "utf-8");
4029
+ const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
3635
4030
  const parsed = JSON.parse(raw);
3636
4031
  if (!isValidationResult(parsed)) {
3637
4032
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -3687,15 +4082,15 @@ function isMissingFileError5(error2) {
3687
4082
  return record2.code === "ENOENT";
3688
4083
  }
3689
4084
  async function writeValidationResult(root, outputPath, result) {
3690
- const abs = import_node_path18.default.isAbsolute(outputPath) ? outputPath : import_node_path18.default.resolve(root, outputPath);
3691
- await (0, import_promises18.mkdir)(import_node_path18.default.dirname(abs), { recursive: true });
3692
- await (0, import_promises18.writeFile)(abs, `${JSON.stringify(result, null, 2)}
4085
+ const abs = import_node_path19.default.isAbsolute(outputPath) ? outputPath : import_node_path19.default.resolve(root, outputPath);
4086
+ await (0, import_promises19.mkdir)(import_node_path19.default.dirname(abs), { recursive: true });
4087
+ await (0, import_promises19.writeFile)(abs, `${JSON.stringify(result, null, 2)}
3693
4088
  `, "utf-8");
3694
4089
  }
3695
4090
 
3696
4091
  // src/cli/commands/validate.ts
3697
- var import_promises19 = require("fs/promises");
3698
- var import_node_path19 = __toESM(require("path"), 1);
4092
+ var import_promises20 = require("fs/promises");
4093
+ var import_node_path20 = __toESM(require("path"), 1);
3699
4094
 
3700
4095
  // src/cli/lib/failOn.ts
3701
4096
  function shouldFail(result, failOn) {
@@ -3710,10 +4105,12 @@ function shouldFail(result, failOn) {
3710
4105
 
3711
4106
  // src/cli/commands/validate.ts
3712
4107
  async function runValidate(options) {
3713
- const root = import_node_path19.default.resolve(options.root);
4108
+ const root = import_node_path20.default.resolve(options.root);
3714
4109
  const configResult = await loadConfig(root);
3715
4110
  const result = await validateProject(root, configResult);
3716
4111
  const normalized = normalizeValidationResult(root, result);
4112
+ const failOn = resolveFailOn(options, configResult.config.validation.failOn);
4113
+ const willFail = shouldFail(normalized, failOn);
3717
4114
  const format = options.format ?? "text";
3718
4115
  if (format === "text") {
3719
4116
  emitText(normalized);
@@ -3723,11 +4120,10 @@ async function runValidate(options) {
3723
4120
  root,
3724
4121
  configResult.config.output.validateJsonPath
3725
4122
  );
3726
- emitGitHubOutput(normalized, root, jsonPath);
4123
+ emitGitHubOutput(normalized, root, jsonPath, { failOn, willFail });
3727
4124
  }
3728
4125
  await emitJson(normalized, root, configResult.config.output.validateJsonPath);
3729
- const failOn = resolveFailOn(options, configResult.config.validation.failOn);
3730
- return shouldFail(normalized, failOn) ? 1 : 0;
4126
+ return willFail ? 1 : 0;
3731
4127
  }
3732
4128
  function resolveFailOn(options, fallback) {
3733
4129
  if (options.failOn) {
@@ -3752,7 +4148,7 @@ function emitText(result) {
3752
4148
  `
3753
4149
  );
3754
4150
  }
3755
- function emitGitHubOutput(result, root, jsonPath) {
4151
+ function emitGitHubOutput(result, root, jsonPath, status) {
3756
4152
  const deduped = dedupeIssues(result.issues);
3757
4153
  const omitted = Math.max(deduped.length - GITHUB_ANNOTATION_LIMIT, 0);
3758
4154
  const dropped = Math.max(result.issues.length - deduped.length, 0);
@@ -3761,7 +4157,8 @@ function emitGitHubOutput(result, root, jsonPath) {
3761
4157
  omitted,
3762
4158
  dropped,
3763
4159
  jsonPath,
3764
- root
4160
+ root,
4161
+ ...status
3765
4162
  });
3766
4163
  const issues = deduped.slice(0, GITHUB_ANNOTATION_LIMIT);
3767
4164
  for (const issue7 of issues) {
@@ -3785,7 +4182,9 @@ function emitGitHubSummary(result, options) {
3785
4182
  `error=${result.counts.error}`,
3786
4183
  `warning=${result.counts.warning}`,
3787
4184
  `info=${result.counts.info}`,
3788
- `annotations=${Math.min(options.total, GITHUB_ANNOTATION_LIMIT)}/${options.total}`
4185
+ `annotations=${Math.min(options.total, GITHUB_ANNOTATION_LIMIT)}/${options.total}`,
4186
+ `failOn=${options.failOn}`,
4187
+ `result=${options.willFail ? "FAIL" : "PASS"}`
3789
4188
  ].join(" ");
3790
4189
  process.stdout.write(`${summary}
3791
4190
  `);
@@ -3803,6 +4202,9 @@ function emitGitHubSummary(result, options) {
3803
4202
  `qfai validate note: \u8A73\u7D30\u306F ${relative} \u307E\u305F\u306F --format text \u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
3804
4203
  `
3805
4204
  );
4205
+ process.stdout.write(
4206
+ "qfai validate note: \u6B21\u306F qfai report \u3067 report.md \u3092\u751F\u6210\u3067\u304D\u307E\u3059\uFF08\u4F8B: qfai report\uFF09\u3002\n"
4207
+ );
3806
4208
  }
3807
4209
  function dedupeIssues(issues) {
3808
4210
  const seen = /* @__PURE__ */ new Set();
@@ -3827,12 +4229,12 @@ function issueKey(issue7) {
3827
4229
  }
3828
4230
  async function emitJson(result, root, jsonPath) {
3829
4231
  const abs = resolveJsonPath(root, jsonPath);
3830
- await (0, import_promises19.mkdir)(import_node_path19.default.dirname(abs), { recursive: true });
3831
- await (0, import_promises19.writeFile)(abs, `${JSON.stringify(result, null, 2)}
4232
+ await (0, import_promises20.mkdir)(import_node_path20.default.dirname(abs), { recursive: true });
4233
+ await (0, import_promises20.writeFile)(abs, `${JSON.stringify(result, null, 2)}
3832
4234
  `, "utf-8");
3833
4235
  }
3834
4236
  function resolveJsonPath(root, jsonPath) {
3835
- return import_node_path19.default.isAbsolute(jsonPath) ? jsonPath : import_node_path19.default.resolve(root, jsonPath);
4237
+ return import_node_path20.default.isAbsolute(jsonPath) ? jsonPath : import_node_path20.default.resolve(root, jsonPath);
3836
4238
  }
3837
4239
  var GITHUB_ANNOTATION_LIMIT = 100;
3838
4240