qfai 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.8.0".length > 0) {
893
- return "0.8.0";
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(
@@ -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) {
@@ -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
@@ -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("## Summary");
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,7 +3482,13 @@ 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}`
3215
3492
  );
3216
3493
  lines.push(
3217
3494
  `- fail-on=error: ${data.summary.counts.error > 0 ? "FAIL" : "PASS"}`
@@ -3220,49 +3497,65 @@ function formatReportMarkdown(data) {
3220
3497
  `- fail-on=warning: ${data.summary.counts.error + data.summary.counts.warning > 0 ? "FAIL" : "PASS"}`
3221
3498
  );
3222
3499
  lines.push("");
3223
- lines.push("## Findings");
3224
- lines.push("");
3225
- lines.push("### Issues (by code)");
3500
+ lines.push("### Next Actions");
3226
3501
  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)");
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
+ );
3254
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");
3255
3518
  lines.push(
3256
- ...formatMarkdownTable(["Severity", "Code", "Count"], issueSummaryRows)
3519
+ "- \u6B21\u306E\u624B\u9806: `qfai doctor` \u2192 `qfai validate` \u2192 `qfai report`\uFF08\u5B9A\u671F\u7684\u306B\u5B9F\u884C\uFF09"
3257
3520
  );
3258
3521
  }
3259
3522
  lines.push("");
3260
- lines.push("### Issues (list)");
3523
+ lines.push("### Index");
3261
3524
  lines.push("");
3262
- if (data.issues.length === 0) {
3263
- lines.push("- (none)");
3264
- } else {
3265
- const sortedIssues = [...data.issues].sort((a, b) => {
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) => {
3266
3559
  const sa = severityOrder[a.severity] ?? 999;
3267
3560
  const sb = severityOrder[b.severity] ?? 999;
3268
3561
  if (sa !== sb) return sa - sb;
@@ -3276,16 +3569,54 @@ function formatReportMarkdown(data) {
3276
3569
  const lineB = b.loc?.line ?? 0;
3277
3570
  return lineA - lineB;
3278
3571
  });
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}`
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}`
3284
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("");
3285
3598
  }
3286
- }
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");
3608
+ lines.push("");
3609
+ lines.push(...formatIssueCards(issuesByCategory.compatibility));
3610
+ lines.push("## Change Issues");
3287
3611
  lines.push("");
3288
- lines.push("### IDs");
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");
3289
3620
  lines.push("");
3290
3621
  lines.push(formatIdLine("SPEC", data.ids.spec));
3291
3622
  lines.push(formatIdLine("BR", data.ids.br));
@@ -3294,7 +3625,7 @@ function formatReportMarkdown(data) {
3294
3625
  lines.push(formatIdLine("API", data.ids.api));
3295
3626
  lines.push(formatIdLine("DB", data.ids.db));
3296
3627
  lines.push("");
3297
- lines.push("### Traceability");
3628
+ lines.push("## Traceability");
3298
3629
  lines.push("");
3299
3630
  lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
3300
3631
  lines.push(
@@ -3468,7 +3799,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
3468
3799
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
3469
3800
  }
3470
3801
  for (const file of specFiles) {
3471
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3802
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3472
3803
  const parsed = parseSpec(text, file);
3473
3804
  const specKey = parsed.specId;
3474
3805
  if (!specKey) {
@@ -3509,7 +3840,7 @@ async function collectIds(files) {
3509
3840
  DB: /* @__PURE__ */ new Set()
3510
3841
  };
3511
3842
  for (const file of files) {
3512
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3843
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3513
3844
  for (const prefix of ID_PREFIXES2) {
3514
3845
  const ids = extractIds(text, prefix);
3515
3846
  ids.forEach((id) => result[prefix].add(id));
@@ -3527,7 +3858,7 @@ async function collectIds(files) {
3527
3858
  async function collectUpstreamIds(files) {
3528
3859
  const ids = /* @__PURE__ */ new Set();
3529
3860
  for (const file of files) {
3530
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3861
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3531
3862
  extractAllIds(text).forEach((id) => ids.add(id));
3532
3863
  }
3533
3864
  return ids;
@@ -3548,7 +3879,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
3548
3879
  }
3549
3880
  const pattern = buildIdPattern2(Array.from(upstreamIds));
3550
3881
  for (const file of targetFiles) {
3551
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3882
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3552
3883
  if (pattern.test(text)) {
3553
3884
  return true;
3554
3885
  }
@@ -3640,7 +3971,7 @@ function buildHotspots(issues) {
3640
3971
 
3641
3972
  // src/cli/commands/report.ts
3642
3973
  async function runReport(options) {
3643
- const root = import_node_path18.default.resolve(options.root);
3974
+ const root = import_node_path19.default.resolve(options.root);
3644
3975
  const configResult = await loadConfig(root);
3645
3976
  let validation;
3646
3977
  if (options.runValidate) {
@@ -3657,7 +3988,7 @@ async function runReport(options) {
3657
3988
  validation = normalized;
3658
3989
  } else {
3659
3990
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
3660
- 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);
3661
3992
  try {
3662
3993
  validation = await readValidationResult(inputPath);
3663
3994
  } catch (err) {
@@ -3683,11 +4014,11 @@ async function runReport(options) {
3683
4014
  const data = await createReportData(root, validation, configResult);
3684
4015
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
3685
4016
  const outRoot = resolvePath(root, configResult.config, "outDir");
3686
- 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");
3687
4018
  const out = options.outPath ?? defaultOut;
3688
- const outPath = import_node_path18.default.isAbsolute(out) ? out : import_node_path18.default.resolve(root, out);
3689
- await (0, import_promises18.mkdir)(import_node_path18.default.dirname(outPath), { recursive: true });
3690
- 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}
3691
4022
  `, "utf-8");
3692
4023
  info(
3693
4024
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -3695,7 +4026,7 @@ async function runReport(options) {
3695
4026
  info(`wrote report: ${outPath}`);
3696
4027
  }
3697
4028
  async function readValidationResult(inputPath) {
3698
- const raw = await (0, import_promises18.readFile)(inputPath, "utf-8");
4029
+ const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
3699
4030
  const parsed = JSON.parse(raw);
3700
4031
  if (!isValidationResult(parsed)) {
3701
4032
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -3751,15 +4082,15 @@ function isMissingFileError5(error2) {
3751
4082
  return record2.code === "ENOENT";
3752
4083
  }
3753
4084
  async function writeValidationResult(root, outputPath, result) {
3754
- const abs = import_node_path18.default.isAbsolute(outputPath) ? outputPath : import_node_path18.default.resolve(root, outputPath);
3755
- await (0, import_promises18.mkdir)(import_node_path18.default.dirname(abs), { recursive: true });
3756
- 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)}
3757
4088
  `, "utf-8");
3758
4089
  }
3759
4090
 
3760
4091
  // src/cli/commands/validate.ts
3761
- var import_promises19 = require("fs/promises");
3762
- var import_node_path19 = __toESM(require("path"), 1);
4092
+ var import_promises20 = require("fs/promises");
4093
+ var import_node_path20 = __toESM(require("path"), 1);
3763
4094
 
3764
4095
  // src/cli/lib/failOn.ts
3765
4096
  function shouldFail(result, failOn) {
@@ -3774,7 +4105,7 @@ function shouldFail(result, failOn) {
3774
4105
 
3775
4106
  // src/cli/commands/validate.ts
3776
4107
  async function runValidate(options) {
3777
- const root = import_node_path19.default.resolve(options.root);
4108
+ const root = import_node_path20.default.resolve(options.root);
3778
4109
  const configResult = await loadConfig(root);
3779
4110
  const result = await validateProject(root, configResult);
3780
4111
  const normalized = normalizeValidationResult(root, result);
@@ -3898,12 +4229,12 @@ function issueKey(issue7) {
3898
4229
  }
3899
4230
  async function emitJson(result, root, jsonPath) {
3900
4231
  const abs = resolveJsonPath(root, jsonPath);
3901
- await (0, import_promises19.mkdir)(import_node_path19.default.dirname(abs), { recursive: true });
3902
- 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)}
3903
4234
  `, "utf-8");
3904
4235
  }
3905
4236
  function resolveJsonPath(root, jsonPath) {
3906
- 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);
3907
4238
  }
3908
4239
  var GITHUB_ANNOTATION_LIMIT = 100;
3909
4240