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.
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/assets/init/.qfai/README.md +4 -1
- package/assets/init/.qfai/promptpack/steering/compatibility-vs-change.md +34 -0
- package/assets/init/.qfai/prompts.local/README.md +5 -0
- package/dist/cli/index.cjs +598 -196
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +586 -184
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +408 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.mjs +408 -70
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -2
package/dist/cli/index.cjs
CHANGED
|
@@ -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
|
|
28
|
-
var
|
|
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
|
|
32
|
-
var
|
|
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/
|
|
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.
|
|
893
|
-
return "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,
|
|
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,
|
|
908
|
-
return
|
|
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,
|
|
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 =
|
|
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 =
|
|
998
|
-
|
|
999
|
-
`${
|
|
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 =
|
|
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 =
|
|
1040
|
-
const inside = rel !== "" && !rel.startsWith("..") && !
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
1337
|
+
let current = import_node_path9.default.resolve(startDir);
|
|
1169
1338
|
while (true) {
|
|
1170
|
-
const gitPath =
|
|
1171
|
-
const workspacePath =
|
|
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 =
|
|
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
|
|
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 =
|
|
1224
|
-
await (0,
|
|
1225
|
-
await (0,
|
|
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
|
|
1413
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1245
1414
|
|
|
1246
1415
|
// src/cli/lib/fs.ts
|
|
1247
|
-
var
|
|
1248
|
-
var
|
|
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 +
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1284
|
-
|
|
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,
|
|
1292
|
-
await (0,
|
|
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,
|
|
1508
|
+
const items = await (0, import_promises10.readdir)(root, { withFileTypes: true });
|
|
1314
1509
|
for (const item of items) {
|
|
1315
|
-
const fullPath =
|
|
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,
|
|
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 =
|
|
1372
|
-
const qfaiAssets =
|
|
1373
|
-
const destRoot =
|
|
1374
|
-
const destQfai =
|
|
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:
|
|
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:
|
|
1550
|
+
force: false,
|
|
1381
1551
|
dryRun: options.dryRun,
|
|
1382
|
-
|
|
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
|
|
1403
|
-
var
|
|
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
|
|
1444
|
-
var
|
|
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
|
|
1448
|
-
var
|
|
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 =
|
|
1471
|
-
const apiRoot =
|
|
1472
|
-
const dbRoot =
|
|
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,
|
|
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
|
|
1726
|
-
var
|
|
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
|
|
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 =
|
|
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(
|
|
1753
|
-
issues.push(...await validateApiContracts(
|
|
1754
|
-
issues.push(...await validateDbContracts(
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
2037
|
-
var
|
|
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 =
|
|
2238
|
+
const deltaPath = import_node_path16.default.join(pack, "delta.md");
|
|
2052
2239
|
let text;
|
|
2053
2240
|
try {
|
|
2054
|
-
text = await (0,
|
|
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
|
|
2127
|
-
var
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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 =
|
|
3091
|
-
const uiRoot =
|
|
3092
|
-
const dbRoot =
|
|
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
|
-
|
|
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(
|
|
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("##
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("##
|
|
3767
|
+
lines.push("## Guidance");
|
|
3366
3768
|
lines.push("");
|
|
3367
|
-
|
|
3368
|
-
|
|
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 (
|
|
3371
|
-
lines.push("-
|
|
3372
|
-
} else {
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
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
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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 =
|
|
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" ?
|
|
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 =
|
|
3625
|
-
await (0,
|
|
3626
|
-
await (0,
|
|
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,
|
|
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 =
|
|
3691
|
-
await (0,
|
|
3692
|
-
await (0,
|
|
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
|
|
3698
|
-
var
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
3831
|
-
await (0,
|
|
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
|
|
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
|
|