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.
- package/dist/cli/index.cjs +523 -192
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +511 -180
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +346 -72
- 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 +346 -72
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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.8.
|
|
893
|
-
return "0.8.
|
|
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(
|
|
@@ -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) {
|
|
@@ -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
|
|
@@ -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,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("
|
|
3224
|
-
lines.push("");
|
|
3225
|
-
lines.push("### Issues (by code)");
|
|
3500
|
+
lines.push("### Next Actions");
|
|
3226
3501
|
lines.push("");
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
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
|
-
|
|
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("###
|
|
3523
|
+
lines.push("### Index");
|
|
3261
3524
|
lines.push("");
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
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
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
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("###
|
|
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("
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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 =
|
|
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" ?
|
|
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 =
|
|
3689
|
-
await (0,
|
|
3690
|
-
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}
|
|
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,
|
|
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 =
|
|
3755
|
-
await (0,
|
|
3756
|
-
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)}
|
|
3757
4088
|
`, "utf-8");
|
|
3758
4089
|
}
|
|
3759
4090
|
|
|
3760
4091
|
// src/cli/commands/validate.ts
|
|
3761
|
-
var
|
|
3762
|
-
var
|
|
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 =
|
|
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,
|
|
3902
|
-
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)}
|
|
3903
4234
|
`, "utf-8");
|
|
3904
4235
|
}
|
|
3905
4236
|
function resolveJsonPath(root, jsonPath) {
|
|
3906
|
-
return
|
|
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
|
|