qfai 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/assets/init/.qfai/README.md +8 -1
- package/assets/init/.qfai/prompts/README.md +6 -1
- package/assets/init/.qfai/prompts/analyze/README.md +38 -0
- package/assets/init/.qfai/prompts/analyze/scenario_test_consistency.md +35 -0
- package/assets/init/.qfai/prompts/analyze/spec_contract_consistency.md +36 -0
- package/assets/init/.qfai/prompts/analyze/spec_scenario_consistency.md +35 -0
- package/assets/init/.qfai/prompts.local/README.md +2 -1
- package/assets/init/.qfai/samples/analyze/analysis.md +38 -0
- package/dist/cli/index.cjs +529 -193
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +517 -181
- 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.
|
|
893
|
-
return "0.
|
|
1018
|
+
if ("0.9.0".length > 0) {
|
|
1019
|
+
return "0.9.0";
|
|
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,51 @@ 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");
|
|
1544
|
+
if (options.force) {
|
|
1545
|
+
info(
|
|
1546
|
+
"NOTE: --force \u306F .qfai/prompts/** \u306E\u307F\u4E0A\u66F8\u304D\u3057\u307E\u3059\uFF08prompts.local \u306F\u4FDD\u8B77\u3055\u308C\u3001specs/contracts \u7B49\u306F\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093\uFF09\u3002"
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1375
1549
|
const rootResult = await copyTemplateTree(rootAssets, destRoot, {
|
|
1376
|
-
force:
|
|
1377
|
-
dryRun: options.dryRun
|
|
1550
|
+
force: false,
|
|
1551
|
+
dryRun: options.dryRun,
|
|
1552
|
+
conflictPolicy: "skip"
|
|
1378
1553
|
});
|
|
1379
1554
|
const qfaiResult = await copyTemplateTree(qfaiAssets, destQfai, {
|
|
1380
|
-
force:
|
|
1555
|
+
force: false,
|
|
1381
1556
|
dryRun: options.dryRun,
|
|
1382
|
-
|
|
1557
|
+
conflictPolicy: "skip",
|
|
1558
|
+
protect: ["prompts.local"],
|
|
1559
|
+
exclude: ["prompts"]
|
|
1383
1560
|
});
|
|
1561
|
+
const promptsResult = await copyTemplatePaths(
|
|
1562
|
+
qfaiAssets,
|
|
1563
|
+
destQfai,
|
|
1564
|
+
["prompts"],
|
|
1565
|
+
{
|
|
1566
|
+
force: options.force,
|
|
1567
|
+
dryRun: options.dryRun,
|
|
1568
|
+
conflictPolicy: "skip",
|
|
1569
|
+
protect: ["prompts.local"]
|
|
1570
|
+
}
|
|
1571
|
+
);
|
|
1384
1572
|
report(
|
|
1385
|
-
[...rootResult.copied, ...qfaiResult.copied],
|
|
1386
|
-
[...rootResult.skipped, ...qfaiResult.skipped],
|
|
1573
|
+
[...rootResult.copied, ...qfaiResult.copied, ...promptsResult.copied],
|
|
1574
|
+
[...rootResult.skipped, ...qfaiResult.skipped, ...promptsResult.skipped],
|
|
1387
1575
|
options.dryRun,
|
|
1388
1576
|
"init"
|
|
1389
1577
|
);
|
|
@@ -1399,8 +1587,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
1399
1587
|
}
|
|
1400
1588
|
|
|
1401
1589
|
// src/cli/commands/report.ts
|
|
1402
|
-
var
|
|
1403
|
-
var
|
|
1590
|
+
var import_promises19 = require("fs/promises");
|
|
1591
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
1404
1592
|
|
|
1405
1593
|
// src/core/normalize.ts
|
|
1406
1594
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1440,12 +1628,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1440
1628
|
}
|
|
1441
1629
|
|
|
1442
1630
|
// src/core/report.ts
|
|
1443
|
-
var
|
|
1444
|
-
var
|
|
1631
|
+
var import_promises18 = require("fs/promises");
|
|
1632
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
1445
1633
|
|
|
1446
1634
|
// src/core/contractIndex.ts
|
|
1447
|
-
var
|
|
1448
|
-
var
|
|
1635
|
+
var import_promises11 = require("fs/promises");
|
|
1636
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1449
1637
|
|
|
1450
1638
|
// src/core/contractsDecl.ts
|
|
1451
1639
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1467,9 +1655,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1467
1655
|
// src/core/contractIndex.ts
|
|
1468
1656
|
async function buildContractIndex(root, config) {
|
|
1469
1657
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1470
|
-
const uiRoot =
|
|
1471
|
-
const apiRoot =
|
|
1472
|
-
const dbRoot =
|
|
1658
|
+
const uiRoot = import_node_path13.default.join(contractsRoot, "ui");
|
|
1659
|
+
const apiRoot = import_node_path13.default.join(contractsRoot, "api");
|
|
1660
|
+
const dbRoot = import_node_path13.default.join(contractsRoot, "db");
|
|
1473
1661
|
const [uiFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1474
1662
|
collectUiContractFiles(uiRoot),
|
|
1475
1663
|
collectApiContractFiles(apiRoot),
|
|
@@ -1487,7 +1675,7 @@ async function buildContractIndex(root, config) {
|
|
|
1487
1675
|
}
|
|
1488
1676
|
async function indexContractFiles(files, index) {
|
|
1489
1677
|
for (const file of files) {
|
|
1490
|
-
const text = await (0,
|
|
1678
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1491
1679
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1492
1680
|
}
|
|
1493
1681
|
}
|
|
@@ -1722,14 +1910,14 @@ function parseSpec(md, file) {
|
|
|
1722
1910
|
}
|
|
1723
1911
|
|
|
1724
1912
|
// src/core/validators/contracts.ts
|
|
1725
|
-
var
|
|
1726
|
-
var
|
|
1913
|
+
var import_promises12 = require("fs/promises");
|
|
1914
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
1727
1915
|
|
|
1728
1916
|
// src/core/contracts.ts
|
|
1729
|
-
var
|
|
1917
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
1730
1918
|
var import_yaml2 = require("yaml");
|
|
1731
1919
|
function parseStructuredContract(file, text) {
|
|
1732
|
-
const ext =
|
|
1920
|
+
const ext = import_node_path14.default.extname(file).toLowerCase();
|
|
1733
1921
|
if (ext === ".json") {
|
|
1734
1922
|
return JSON.parse(text);
|
|
1735
1923
|
}
|
|
@@ -1749,9 +1937,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1749
1937
|
async function validateContracts(root, config) {
|
|
1750
1938
|
const issues = [];
|
|
1751
1939
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1752
|
-
issues.push(...await validateUiContracts(
|
|
1753
|
-
issues.push(...await validateApiContracts(
|
|
1754
|
-
issues.push(...await validateDbContracts(
|
|
1940
|
+
issues.push(...await validateUiContracts(import_node_path15.default.join(contractsRoot, "ui")));
|
|
1941
|
+
issues.push(...await validateApiContracts(import_node_path15.default.join(contractsRoot, "api")));
|
|
1942
|
+
issues.push(...await validateDbContracts(import_node_path15.default.join(contractsRoot, "db")));
|
|
1755
1943
|
const contractIndex = await buildContractIndex(root, config);
|
|
1756
1944
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
1757
1945
|
return issues;
|
|
@@ -1771,7 +1959,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
1771
1959
|
}
|
|
1772
1960
|
const issues = [];
|
|
1773
1961
|
for (const file of files) {
|
|
1774
|
-
const text = await (0,
|
|
1962
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
1775
1963
|
const invalidIds = extractInvalidIds(text, [
|
|
1776
1964
|
"SPEC",
|
|
1777
1965
|
"BR",
|
|
@@ -1826,7 +2014,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1826
2014
|
}
|
|
1827
2015
|
const issues = [];
|
|
1828
2016
|
for (const file of files) {
|
|
1829
|
-
const text = await (0,
|
|
2017
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
1830
2018
|
const invalidIds = extractInvalidIds(text, [
|
|
1831
2019
|
"SPEC",
|
|
1832
2020
|
"BR",
|
|
@@ -1894,7 +2082,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
1894
2082
|
}
|
|
1895
2083
|
const issues = [];
|
|
1896
2084
|
for (const file of files) {
|
|
1897
|
-
const text = await (0,
|
|
2085
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
1898
2086
|
const invalidIds = extractInvalidIds(text, [
|
|
1899
2087
|
"SPEC",
|
|
1900
2088
|
"BR",
|
|
@@ -2014,12 +2202,16 @@ function formatError4(error2) {
|
|
|
2014
2202
|
}
|
|
2015
2203
|
return String(error2);
|
|
2016
2204
|
}
|
|
2017
|
-
function issue(code, message, severity, file, rule, refs) {
|
|
2205
|
+
function issue(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2018
2206
|
const issue7 = {
|
|
2019
2207
|
code,
|
|
2020
2208
|
severity,
|
|
2209
|
+
category,
|
|
2021
2210
|
message
|
|
2022
2211
|
};
|
|
2212
|
+
if (suggested_action) {
|
|
2213
|
+
issue7.suggested_action = suggested_action;
|
|
2214
|
+
}
|
|
2023
2215
|
if (file) {
|
|
2024
2216
|
issue7.file = file;
|
|
2025
2217
|
}
|
|
@@ -2033,8 +2225,8 @@ function issue(code, message, severity, file, rule, refs) {
|
|
|
2033
2225
|
}
|
|
2034
2226
|
|
|
2035
2227
|
// src/core/validators/delta.ts
|
|
2036
|
-
var
|
|
2037
|
-
var
|
|
2228
|
+
var import_promises13 = require("fs/promises");
|
|
2229
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2038
2230
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
2039
2231
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
2040
2232
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -2048,10 +2240,10 @@ async function validateDeltas(root, config) {
|
|
|
2048
2240
|
}
|
|
2049
2241
|
const issues = [];
|
|
2050
2242
|
for (const pack of packs) {
|
|
2051
|
-
const deltaPath =
|
|
2243
|
+
const deltaPath = import_node_path16.default.join(pack, "delta.md");
|
|
2052
2244
|
let text;
|
|
2053
2245
|
try {
|
|
2054
|
-
text = await (0,
|
|
2246
|
+
text = await (0, import_promises13.readFile)(deltaPath, "utf-8");
|
|
2055
2247
|
} catch (error2) {
|
|
2056
2248
|
if (isMissingFileError2(error2)) {
|
|
2057
2249
|
issues.push(
|
|
@@ -2104,12 +2296,16 @@ function isMissingFileError2(error2) {
|
|
|
2104
2296
|
}
|
|
2105
2297
|
return error2.code === "ENOENT";
|
|
2106
2298
|
}
|
|
2107
|
-
function issue2(code, message, severity, file, rule, refs) {
|
|
2299
|
+
function issue2(code, message, severity, file, rule, refs, category = "change", suggested_action) {
|
|
2108
2300
|
const issue7 = {
|
|
2109
2301
|
code,
|
|
2110
2302
|
severity,
|
|
2303
|
+
category,
|
|
2111
2304
|
message
|
|
2112
2305
|
};
|
|
2306
|
+
if (suggested_action) {
|
|
2307
|
+
issue7.suggested_action = suggested_action;
|
|
2308
|
+
}
|
|
2113
2309
|
if (file) {
|
|
2114
2310
|
issue7.file = file;
|
|
2115
2311
|
}
|
|
@@ -2123,8 +2319,8 @@ function issue2(code, message, severity, file, rule, refs) {
|
|
|
2123
2319
|
}
|
|
2124
2320
|
|
|
2125
2321
|
// src/core/validators/ids.ts
|
|
2126
|
-
var
|
|
2127
|
-
var
|
|
2322
|
+
var import_promises14 = require("fs/promises");
|
|
2323
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2128
2324
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2129
2325
|
async function validateDefinedIds(root, config) {
|
|
2130
2326
|
const issues = [];
|
|
@@ -2159,7 +2355,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2159
2355
|
}
|
|
2160
2356
|
async function collectSpecDefinitionIds(files, out) {
|
|
2161
2357
|
for (const file of files) {
|
|
2162
|
-
const text = await (0,
|
|
2358
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2163
2359
|
const parsed = parseSpec(text, file);
|
|
2164
2360
|
if (parsed.specId) {
|
|
2165
2361
|
recordId(out, parsed.specId, file);
|
|
@@ -2169,7 +2365,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2169
2365
|
}
|
|
2170
2366
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2171
2367
|
for (const file of files) {
|
|
2172
|
-
const text = await (0,
|
|
2368
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2173
2369
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2174
2370
|
if (!document || errors.length > 0) {
|
|
2175
2371
|
continue;
|
|
@@ -2190,16 +2386,20 @@ function recordId(out, id, file) {
|
|
|
2190
2386
|
}
|
|
2191
2387
|
function formatFileList(files, root) {
|
|
2192
2388
|
return files.map((file) => {
|
|
2193
|
-
const relative =
|
|
2389
|
+
const relative = import_node_path17.default.relative(root, file);
|
|
2194
2390
|
return relative.length > 0 ? relative : file;
|
|
2195
2391
|
}).join(", ");
|
|
2196
2392
|
}
|
|
2197
|
-
function issue3(code, message, severity, file, rule, refs) {
|
|
2393
|
+
function issue3(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2198
2394
|
const issue7 = {
|
|
2199
2395
|
code,
|
|
2200
2396
|
severity,
|
|
2397
|
+
category,
|
|
2201
2398
|
message
|
|
2202
2399
|
};
|
|
2400
|
+
if (suggested_action) {
|
|
2401
|
+
issue7.suggested_action = suggested_action;
|
|
2402
|
+
}
|
|
2203
2403
|
if (file) {
|
|
2204
2404
|
issue7.file = file;
|
|
2205
2405
|
}
|
|
@@ -2212,8 +2412,39 @@ function issue3(code, message, severity, file, rule, refs) {
|
|
|
2212
2412
|
return issue7;
|
|
2213
2413
|
}
|
|
2214
2414
|
|
|
2415
|
+
// src/core/validators/promptsIntegrity.ts
|
|
2416
|
+
async function validatePromptsIntegrity(root) {
|
|
2417
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
2418
|
+
if (diff.status !== "modified") {
|
|
2419
|
+
return [];
|
|
2420
|
+
}
|
|
2421
|
+
const total = diff.missing.length + diff.extra.length + diff.changed.length;
|
|
2422
|
+
const hints = [
|
|
2423
|
+
diff.changed.length > 0 ? `\u5909\u66F4: ${diff.changed.length}` : null,
|
|
2424
|
+
diff.missing.length > 0 ? `\u524A\u9664: ${diff.missing.length}` : null,
|
|
2425
|
+
diff.extra.length > 0 ? `\u8FFD\u52A0: ${diff.extra.length}` : null
|
|
2426
|
+
].filter(Boolean).join(" / ");
|
|
2427
|
+
const sample = [...diff.changed, ...diff.missing, ...diff.extra].slice(0, 10);
|
|
2428
|
+
const sampleText = sample.length > 0 ? ` \u4F8B: ${sample.join(", ")}` : "";
|
|
2429
|
+
return [
|
|
2430
|
+
{
|
|
2431
|
+
code: "QFAI-PROMPTS-001",
|
|
2432
|
+
severity: "error",
|
|
2433
|
+
category: "change",
|
|
2434
|
+
message: `\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\uFF08${hints || `\u5DEE\u5206=${total}`}\uFF09\u3002${sampleText}`,
|
|
2435
|
+
suggested_action: [
|
|
2436
|
+
"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",
|
|
2437
|
+
"\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u5B9F\u65BD\u3057\u3066\u304F\u3060\u3055\u3044:",
|
|
2438
|
+
"- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
|
|
2439
|
+
"- \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"
|
|
2440
|
+
].join("\n"),
|
|
2441
|
+
rule: "prompts.integrity"
|
|
2442
|
+
}
|
|
2443
|
+
];
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2215
2446
|
// src/core/validators/scenario.ts
|
|
2216
|
-
var
|
|
2447
|
+
var import_promises15 = require("fs/promises");
|
|
2217
2448
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
2218
2449
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
2219
2450
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -2239,7 +2470,7 @@ async function validateScenarios(root, config) {
|
|
|
2239
2470
|
for (const entry of entries) {
|
|
2240
2471
|
let text;
|
|
2241
2472
|
try {
|
|
2242
|
-
text = await (0,
|
|
2473
|
+
text = await (0, import_promises15.readFile)(entry.scenarioPath, "utf-8");
|
|
2243
2474
|
} catch (error2) {
|
|
2244
2475
|
if (isMissingFileError3(error2)) {
|
|
2245
2476
|
issues.push(
|
|
@@ -2384,12 +2615,16 @@ function validateScenarioContent(text, file) {
|
|
|
2384
2615
|
}
|
|
2385
2616
|
return issues;
|
|
2386
2617
|
}
|
|
2387
|
-
function issue4(code, message, severity, file, rule, refs) {
|
|
2618
|
+
function issue4(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2388
2619
|
const issue7 = {
|
|
2389
2620
|
code,
|
|
2390
2621
|
severity,
|
|
2622
|
+
category,
|
|
2391
2623
|
message
|
|
2392
2624
|
};
|
|
2625
|
+
if (suggested_action) {
|
|
2626
|
+
issue7.suggested_action = suggested_action;
|
|
2627
|
+
}
|
|
2393
2628
|
if (file) {
|
|
2394
2629
|
issue7.file = file;
|
|
2395
2630
|
}
|
|
@@ -2409,7 +2644,7 @@ function isMissingFileError3(error2) {
|
|
|
2409
2644
|
}
|
|
2410
2645
|
|
|
2411
2646
|
// src/core/validators/spec.ts
|
|
2412
|
-
var
|
|
2647
|
+
var import_promises16 = require("fs/promises");
|
|
2413
2648
|
async function validateSpecs(root, config) {
|
|
2414
2649
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2415
2650
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -2430,7 +2665,7 @@ async function validateSpecs(root, config) {
|
|
|
2430
2665
|
for (const entry of entries) {
|
|
2431
2666
|
let text;
|
|
2432
2667
|
try {
|
|
2433
|
-
text = await (0,
|
|
2668
|
+
text = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
|
|
2434
2669
|
} catch (error2) {
|
|
2435
2670
|
if (isMissingFileError4(error2)) {
|
|
2436
2671
|
issues.push(
|
|
@@ -2554,12 +2789,16 @@ function validateSpecContent(text, file, requiredSections) {
|
|
|
2554
2789
|
}
|
|
2555
2790
|
return issues;
|
|
2556
2791
|
}
|
|
2557
|
-
function issue5(code, message, severity, file, rule, refs) {
|
|
2792
|
+
function issue5(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
2558
2793
|
const issue7 = {
|
|
2559
2794
|
code,
|
|
2560
2795
|
severity,
|
|
2796
|
+
category,
|
|
2561
2797
|
message
|
|
2562
2798
|
};
|
|
2799
|
+
if (suggested_action) {
|
|
2800
|
+
issue7.suggested_action = suggested_action;
|
|
2801
|
+
}
|
|
2563
2802
|
if (file) {
|
|
2564
2803
|
issue7.file = file;
|
|
2565
2804
|
}
|
|
@@ -2579,7 +2818,7 @@ function isMissingFileError4(error2) {
|
|
|
2579
2818
|
}
|
|
2580
2819
|
|
|
2581
2820
|
// src/core/validators/traceability.ts
|
|
2582
|
-
var
|
|
2821
|
+
var import_promises17 = require("fs/promises");
|
|
2583
2822
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
2584
2823
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
2585
2824
|
async function validateTraceability(root, config) {
|
|
@@ -2599,7 +2838,7 @@ async function validateTraceability(root, config) {
|
|
|
2599
2838
|
const contractIndex = await buildContractIndex(root, config);
|
|
2600
2839
|
const contractIds = contractIndex.ids;
|
|
2601
2840
|
for (const file of specFiles) {
|
|
2602
|
-
const text = await (0,
|
|
2841
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
2603
2842
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2604
2843
|
const parsed = parseSpec(text, file);
|
|
2605
2844
|
if (parsed.specId) {
|
|
@@ -2672,7 +2911,7 @@ async function validateTraceability(root, config) {
|
|
|
2672
2911
|
}
|
|
2673
2912
|
}
|
|
2674
2913
|
for (const file of scenarioFiles) {
|
|
2675
|
-
const text = await (0,
|
|
2914
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
2676
2915
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2677
2916
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
2678
2917
|
allowCommentPrefix: true
|
|
@@ -2994,7 +3233,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
2994
3233
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
2995
3234
|
let found = false;
|
|
2996
3235
|
for (const file of targetFiles) {
|
|
2997
|
-
const text = await (0,
|
|
3236
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
2998
3237
|
if (pattern.test(text)) {
|
|
2999
3238
|
found = true;
|
|
3000
3239
|
break;
|
|
@@ -3017,12 +3256,16 @@ function buildIdPattern(ids) {
|
|
|
3017
3256
|
const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
3018
3257
|
return new RegExp(`\\b(${escaped.join("|")})\\b`);
|
|
3019
3258
|
}
|
|
3020
|
-
function issue6(code, message, severity, file, rule, refs) {
|
|
3259
|
+
function issue6(code, message, severity, file, rule, refs, category = "compatibility", suggested_action) {
|
|
3021
3260
|
const issue7 = {
|
|
3022
3261
|
code,
|
|
3023
3262
|
severity,
|
|
3263
|
+
category,
|
|
3024
3264
|
message
|
|
3025
3265
|
};
|
|
3266
|
+
if (suggested_action) {
|
|
3267
|
+
issue7.suggested_action = suggested_action;
|
|
3268
|
+
}
|
|
3026
3269
|
if (file) {
|
|
3027
3270
|
issue7.file = file;
|
|
3028
3271
|
}
|
|
@@ -3041,6 +3284,7 @@ async function validateProject(root, configResult) {
|
|
|
3041
3284
|
const { config, issues: configIssues } = resolved;
|
|
3042
3285
|
const issues = [
|
|
3043
3286
|
...configIssues,
|
|
3287
|
+
...await validatePromptsIntegrity(root),
|
|
3044
3288
|
...await validateSpecs(root, config),
|
|
3045
3289
|
...await validateDeltas(root, config),
|
|
3046
3290
|
...await validateScenarios(root, config),
|
|
@@ -3081,15 +3325,15 @@ function countIssues(issues) {
|
|
|
3081
3325
|
// src/core/report.ts
|
|
3082
3326
|
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
|
|
3083
3327
|
async function createReportData(root, validation, configResult) {
|
|
3084
|
-
const resolvedRoot =
|
|
3328
|
+
const resolvedRoot = import_node_path18.default.resolve(root);
|
|
3085
3329
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3086
3330
|
const config = resolved.config;
|
|
3087
3331
|
const configPath = resolved.configPath;
|
|
3088
3332
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3089
3333
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3090
|
-
const apiRoot =
|
|
3091
|
-
const uiRoot =
|
|
3092
|
-
const dbRoot =
|
|
3334
|
+
const apiRoot = import_node_path18.default.join(contractsRoot, "api");
|
|
3335
|
+
const uiRoot = import_node_path18.default.join(contractsRoot, "ui");
|
|
3336
|
+
const dbRoot = import_node_path18.default.join(contractsRoot, "db");
|
|
3093
3337
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3094
3338
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3095
3339
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -3203,7 +3447,39 @@ function formatReportMarkdown(data) {
|
|
|
3203
3447
|
lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
|
|
3204
3448
|
lines.push(`- \u7248: ${data.version}`);
|
|
3205
3449
|
lines.push("");
|
|
3206
|
-
|
|
3450
|
+
const severityOrder = {
|
|
3451
|
+
error: 0,
|
|
3452
|
+
warning: 1,
|
|
3453
|
+
info: 2
|
|
3454
|
+
};
|
|
3455
|
+
const categoryOrder = {
|
|
3456
|
+
compatibility: 0,
|
|
3457
|
+
change: 1
|
|
3458
|
+
};
|
|
3459
|
+
const issuesByCategory = {
|
|
3460
|
+
compatibility: [],
|
|
3461
|
+
change: []
|
|
3462
|
+
};
|
|
3463
|
+
for (const issue7 of data.issues) {
|
|
3464
|
+
const cat = issue7.category;
|
|
3465
|
+
if (cat === "change") {
|
|
3466
|
+
issuesByCategory.change.push(issue7);
|
|
3467
|
+
} else {
|
|
3468
|
+
issuesByCategory.compatibility.push(issue7);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
const countIssuesBySeverity = (issues) => issues.reduce(
|
|
3472
|
+
(acc, i) => {
|
|
3473
|
+
acc[i.severity] += 1;
|
|
3474
|
+
return acc;
|
|
3475
|
+
},
|
|
3476
|
+
{ info: 0, warning: 0, error: 0 }
|
|
3477
|
+
);
|
|
3478
|
+
const compatCounts = countIssuesBySeverity(issuesByCategory.compatibility);
|
|
3479
|
+
const changeCounts = countIssuesBySeverity(issuesByCategory.change);
|
|
3480
|
+
lines.push("## Dashboard");
|
|
3481
|
+
lines.push("");
|
|
3482
|
+
lines.push("### Summary");
|
|
3207
3483
|
lines.push("");
|
|
3208
3484
|
lines.push(`- specs: ${data.summary.specs}`);
|
|
3209
3485
|
lines.push(`- scenarios: ${data.summary.scenarios}`);
|
|
@@ -3211,7 +3487,13 @@ function formatReportMarkdown(data) {
|
|
|
3211
3487
|
`- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
|
|
3212
3488
|
);
|
|
3213
3489
|
lines.push(
|
|
3214
|
-
`- issues: info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
3490
|
+
`- issues(total): info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
|
|
3491
|
+
);
|
|
3492
|
+
lines.push(
|
|
3493
|
+
`- issues(compatibility): info ${compatCounts.info} / warning ${compatCounts.warning} / error ${compatCounts.error}`
|
|
3494
|
+
);
|
|
3495
|
+
lines.push(
|
|
3496
|
+
`- issues(change): info ${changeCounts.info} / warning ${changeCounts.warning} / error ${changeCounts.error}`
|
|
3215
3497
|
);
|
|
3216
3498
|
lines.push(
|
|
3217
3499
|
`- fail-on=error: ${data.summary.counts.error > 0 ? "FAIL" : "PASS"}`
|
|
@@ -3220,49 +3502,65 @@ function formatReportMarkdown(data) {
|
|
|
3220
3502
|
`- fail-on=warning: ${data.summary.counts.error + data.summary.counts.warning > 0 ? "FAIL" : "PASS"}`
|
|
3221
3503
|
);
|
|
3222
3504
|
lines.push("");
|
|
3223
|
-
lines.push("
|
|
3224
|
-
lines.push("");
|
|
3225
|
-
lines.push("### Issues (by code)");
|
|
3505
|
+
lines.push("### Next Actions");
|
|
3226
3506
|
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)");
|
|
3507
|
+
if (data.summary.counts.error > 0) {
|
|
3508
|
+
lines.push(
|
|
3509
|
+
"- 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"
|
|
3510
|
+
);
|
|
3511
|
+
lines.push(
|
|
3512
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
3513
|
+
);
|
|
3514
|
+
} else if (data.summary.counts.warning > 0) {
|
|
3515
|
+
lines.push(
|
|
3516
|
+
"- 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"
|
|
3517
|
+
);
|
|
3518
|
+
lines.push(
|
|
3519
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor --fail-on error` \u2192 `qfai validate --fail-on error` \u2192 `qfai report`"
|
|
3520
|
+
);
|
|
3254
3521
|
} else {
|
|
3522
|
+
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
3523
|
lines.push(
|
|
3256
|
-
|
|
3524
|
+
"- \u6B21\u306E\u624B\u9806: `qfai doctor` \u2192 `qfai validate` \u2192 `qfai report`\uFF08\u5B9A\u671F\u7684\u306B\u5B9F\u884C\uFF09"
|
|
3257
3525
|
);
|
|
3258
3526
|
}
|
|
3259
3527
|
lines.push("");
|
|
3260
|
-
lines.push("###
|
|
3528
|
+
lines.push("### Index");
|
|
3261
3529
|
lines.push("");
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3530
|
+
lines.push("- [Compatibility Issues](#compatibility-issues)");
|
|
3531
|
+
lines.push("- [Change Issues](#change-issues)");
|
|
3532
|
+
lines.push("- [IDs](#ids)");
|
|
3533
|
+
lines.push("- [Traceability](#traceability)");
|
|
3534
|
+
lines.push("");
|
|
3535
|
+
const formatIssueSummaryTable = (issues) => {
|
|
3536
|
+
const issueKeyToCount = /* @__PURE__ */ new Map();
|
|
3537
|
+
for (const issue7 of issues) {
|
|
3538
|
+
const key = `${issue7.category}|${issue7.severity}|${issue7.code}`;
|
|
3539
|
+
const current = issueKeyToCount.get(key);
|
|
3540
|
+
if (current) {
|
|
3541
|
+
current.count += 1;
|
|
3542
|
+
continue;
|
|
3543
|
+
}
|
|
3544
|
+
issueKeyToCount.set(key, {
|
|
3545
|
+
category: issue7.category,
|
|
3546
|
+
severity: issue7.severity,
|
|
3547
|
+
code: issue7.code,
|
|
3548
|
+
count: 1
|
|
3549
|
+
});
|
|
3550
|
+
}
|
|
3551
|
+
const rows = Array.from(issueKeyToCount.values()).sort((a, b) => {
|
|
3552
|
+
const ca = categoryOrder[a.category] ?? 999;
|
|
3553
|
+
const cb = categoryOrder[b.category] ?? 999;
|
|
3554
|
+
if (ca !== cb) return ca - cb;
|
|
3555
|
+
const sa = severityOrder[a.severity] ?? 999;
|
|
3556
|
+
const sb = severityOrder[b.severity] ?? 999;
|
|
3557
|
+
if (sa !== sb) return sa - sb;
|
|
3558
|
+
return a.code.localeCompare(b.code);
|
|
3559
|
+
}).map((x) => [x.severity, x.code, String(x.count)]);
|
|
3560
|
+
return rows.length === 0 ? ["- (none)"] : formatMarkdownTable(["Severity", "Code", "Count"], rows);
|
|
3561
|
+
};
|
|
3562
|
+
const formatIssueCards = (issues) => {
|
|
3563
|
+
const sorted = [...issues].sort((a, b) => {
|
|
3266
3564
|
const sa = severityOrder[a.severity] ?? 999;
|
|
3267
3565
|
const sb = severityOrder[b.severity] ?? 999;
|
|
3268
3566
|
if (sa !== sb) return sa - sb;
|
|
@@ -3276,16 +3574,54 @@ function formatReportMarkdown(data) {
|
|
|
3276
3574
|
const lineB = b.loc?.line ?? 0;
|
|
3277
3575
|
return lineA - lineB;
|
|
3278
3576
|
});
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3577
|
+
if (sorted.length === 0) {
|
|
3578
|
+
return ["- (none)"];
|
|
3579
|
+
}
|
|
3580
|
+
const out = [];
|
|
3581
|
+
for (const item of sorted) {
|
|
3582
|
+
out.push(
|
|
3583
|
+
`#### ${item.severity.toUpperCase()} [${item.code}] ${item.message}`
|
|
3284
3584
|
);
|
|
3585
|
+
if (item.file) {
|
|
3586
|
+
const loc = item.loc?.line ? `:${item.loc.line}` : "";
|
|
3587
|
+
out.push(`- file: ${item.file}${loc}`);
|
|
3588
|
+
}
|
|
3589
|
+
if (item.rule) {
|
|
3590
|
+
out.push(`- rule: ${item.rule}`);
|
|
3591
|
+
}
|
|
3592
|
+
if (item.refs && item.refs.length > 0) {
|
|
3593
|
+
out.push(`- refs: ${item.refs.join(", ")}`);
|
|
3594
|
+
}
|
|
3595
|
+
if (item.suggested_action) {
|
|
3596
|
+
out.push("- suggested_action:");
|
|
3597
|
+
const actionLines = String(item.suggested_action).split("\n");
|
|
3598
|
+
for (const line of actionLines) {
|
|
3599
|
+
out.push(` ${line}`);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
out.push("");
|
|
3285
3603
|
}
|
|
3286
|
-
|
|
3604
|
+
return out;
|
|
3605
|
+
};
|
|
3606
|
+
lines.push("## Compatibility Issues");
|
|
3607
|
+
lines.push("");
|
|
3608
|
+
lines.push("### Summary");
|
|
3609
|
+
lines.push("");
|
|
3610
|
+
lines.push(...formatIssueSummaryTable(issuesByCategory.compatibility));
|
|
3611
|
+
lines.push("");
|
|
3612
|
+
lines.push("### Issues");
|
|
3613
|
+
lines.push("");
|
|
3614
|
+
lines.push(...formatIssueCards(issuesByCategory.compatibility));
|
|
3615
|
+
lines.push("## Change Issues");
|
|
3287
3616
|
lines.push("");
|
|
3288
|
-
lines.push("###
|
|
3617
|
+
lines.push("### Summary");
|
|
3618
|
+
lines.push("");
|
|
3619
|
+
lines.push(...formatIssueSummaryTable(issuesByCategory.change));
|
|
3620
|
+
lines.push("");
|
|
3621
|
+
lines.push("### Issues");
|
|
3622
|
+
lines.push("");
|
|
3623
|
+
lines.push(...formatIssueCards(issuesByCategory.change));
|
|
3624
|
+
lines.push("## IDs");
|
|
3289
3625
|
lines.push("");
|
|
3290
3626
|
lines.push(formatIdLine("SPEC", data.ids.spec));
|
|
3291
3627
|
lines.push(formatIdLine("BR", data.ids.br));
|
|
@@ -3294,7 +3630,7 @@ function formatReportMarkdown(data) {
|
|
|
3294
3630
|
lines.push(formatIdLine("API", data.ids.api));
|
|
3295
3631
|
lines.push(formatIdLine("DB", data.ids.db));
|
|
3296
3632
|
lines.push("");
|
|
3297
|
-
lines.push("
|
|
3633
|
+
lines.push("## Traceability");
|
|
3298
3634
|
lines.push("");
|
|
3299
3635
|
lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
|
|
3300
3636
|
lines.push(
|
|
@@ -3468,7 +3804,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
3468
3804
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
3469
3805
|
}
|
|
3470
3806
|
for (const file of specFiles) {
|
|
3471
|
-
const text = await (0,
|
|
3807
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3472
3808
|
const parsed = parseSpec(text, file);
|
|
3473
3809
|
const specKey = parsed.specId;
|
|
3474
3810
|
if (!specKey) {
|
|
@@ -3509,7 +3845,7 @@ async function collectIds(files) {
|
|
|
3509
3845
|
DB: /* @__PURE__ */ new Set()
|
|
3510
3846
|
};
|
|
3511
3847
|
for (const file of files) {
|
|
3512
|
-
const text = await (0,
|
|
3848
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3513
3849
|
for (const prefix of ID_PREFIXES2) {
|
|
3514
3850
|
const ids = extractIds(text, prefix);
|
|
3515
3851
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -3527,7 +3863,7 @@ async function collectIds(files) {
|
|
|
3527
3863
|
async function collectUpstreamIds(files) {
|
|
3528
3864
|
const ids = /* @__PURE__ */ new Set();
|
|
3529
3865
|
for (const file of files) {
|
|
3530
|
-
const text = await (0,
|
|
3866
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3531
3867
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
3532
3868
|
}
|
|
3533
3869
|
return ids;
|
|
@@ -3548,7 +3884,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
3548
3884
|
}
|
|
3549
3885
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
3550
3886
|
for (const file of targetFiles) {
|
|
3551
|
-
const text = await (0,
|
|
3887
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
3552
3888
|
if (pattern.test(text)) {
|
|
3553
3889
|
return true;
|
|
3554
3890
|
}
|
|
@@ -3640,7 +3976,7 @@ function buildHotspots(issues) {
|
|
|
3640
3976
|
|
|
3641
3977
|
// src/cli/commands/report.ts
|
|
3642
3978
|
async function runReport(options) {
|
|
3643
|
-
const root =
|
|
3979
|
+
const root = import_node_path19.default.resolve(options.root);
|
|
3644
3980
|
const configResult = await loadConfig(root);
|
|
3645
3981
|
let validation;
|
|
3646
3982
|
if (options.runValidate) {
|
|
@@ -3657,7 +3993,7 @@ async function runReport(options) {
|
|
|
3657
3993
|
validation = normalized;
|
|
3658
3994
|
} else {
|
|
3659
3995
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
3660
|
-
const inputPath =
|
|
3996
|
+
const inputPath = import_node_path19.default.isAbsolute(input) ? input : import_node_path19.default.resolve(root, input);
|
|
3661
3997
|
try {
|
|
3662
3998
|
validation = await readValidationResult(inputPath);
|
|
3663
3999
|
} catch (err) {
|
|
@@ -3683,11 +4019,11 @@ async function runReport(options) {
|
|
|
3683
4019
|
const data = await createReportData(root, validation, configResult);
|
|
3684
4020
|
const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
|
|
3685
4021
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
3686
|
-
const defaultOut = options.format === "json" ?
|
|
4022
|
+
const defaultOut = options.format === "json" ? import_node_path19.default.join(outRoot, "report.json") : import_node_path19.default.join(outRoot, "report.md");
|
|
3687
4023
|
const out = options.outPath ?? defaultOut;
|
|
3688
|
-
const outPath =
|
|
3689
|
-
await (0,
|
|
3690
|
-
await (0,
|
|
4024
|
+
const outPath = import_node_path19.default.isAbsolute(out) ? out : import_node_path19.default.resolve(root, out);
|
|
4025
|
+
await (0, import_promises19.mkdir)(import_node_path19.default.dirname(outPath), { recursive: true });
|
|
4026
|
+
await (0, import_promises19.writeFile)(outPath, `${output}
|
|
3691
4027
|
`, "utf-8");
|
|
3692
4028
|
info(
|
|
3693
4029
|
`report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
|
|
@@ -3695,7 +4031,7 @@ async function runReport(options) {
|
|
|
3695
4031
|
info(`wrote report: ${outPath}`);
|
|
3696
4032
|
}
|
|
3697
4033
|
async function readValidationResult(inputPath) {
|
|
3698
|
-
const raw = await (0,
|
|
4034
|
+
const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
|
|
3699
4035
|
const parsed = JSON.parse(raw);
|
|
3700
4036
|
if (!isValidationResult(parsed)) {
|
|
3701
4037
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -3751,15 +4087,15 @@ function isMissingFileError5(error2) {
|
|
|
3751
4087
|
return record2.code === "ENOENT";
|
|
3752
4088
|
}
|
|
3753
4089
|
async function writeValidationResult(root, outputPath, result) {
|
|
3754
|
-
const abs =
|
|
3755
|
-
await (0,
|
|
3756
|
-
await (0,
|
|
4090
|
+
const abs = import_node_path19.default.isAbsolute(outputPath) ? outputPath : import_node_path19.default.resolve(root, outputPath);
|
|
4091
|
+
await (0, import_promises19.mkdir)(import_node_path19.default.dirname(abs), { recursive: true });
|
|
4092
|
+
await (0, import_promises19.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
3757
4093
|
`, "utf-8");
|
|
3758
4094
|
}
|
|
3759
4095
|
|
|
3760
4096
|
// src/cli/commands/validate.ts
|
|
3761
|
-
var
|
|
3762
|
-
var
|
|
4097
|
+
var import_promises20 = require("fs/promises");
|
|
4098
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3763
4099
|
|
|
3764
4100
|
// src/cli/lib/failOn.ts
|
|
3765
4101
|
function shouldFail(result, failOn) {
|
|
@@ -3774,7 +4110,7 @@ function shouldFail(result, failOn) {
|
|
|
3774
4110
|
|
|
3775
4111
|
// src/cli/commands/validate.ts
|
|
3776
4112
|
async function runValidate(options) {
|
|
3777
|
-
const root =
|
|
4113
|
+
const root = import_node_path20.default.resolve(options.root);
|
|
3778
4114
|
const configResult = await loadConfig(root);
|
|
3779
4115
|
const result = await validateProject(root, configResult);
|
|
3780
4116
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -3898,12 +4234,12 @@ function issueKey(issue7) {
|
|
|
3898
4234
|
}
|
|
3899
4235
|
async function emitJson(result, root, jsonPath) {
|
|
3900
4236
|
const abs = resolveJsonPath(root, jsonPath);
|
|
3901
|
-
await (0,
|
|
3902
|
-
await (0,
|
|
4237
|
+
await (0, import_promises20.mkdir)(import_node_path20.default.dirname(abs), { recursive: true });
|
|
4238
|
+
await (0, import_promises20.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
3903
4239
|
`, "utf-8");
|
|
3904
4240
|
}
|
|
3905
4241
|
function resolveJsonPath(root, jsonPath) {
|
|
3906
|
-
return
|
|
4242
|
+
return import_node_path20.default.isAbsolute(jsonPath) ? jsonPath : import_node_path20.default.resolve(root, jsonPath);
|
|
3907
4243
|
}
|
|
3908
4244
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
3909
4245
|
|
|
@@ -4101,7 +4437,7 @@ Commands:
|
|
|
4101
4437
|
Options:
|
|
4102
4438
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
4103
4439
|
--dir <path> init \u306E\u51FA\u529B\u5148
|
|
4104
|
-
--force \u65E2\u5B58\
|
|
4440
|
+
--force init: .qfai/prompts \u306E\u307F\u4E0A\u66F8\u304D\uFF08\u305D\u308C\u4EE5\u5916\u306F\u65E2\u5B58\u304C\u3042\u308C\u3070\u30B9\u30AD\u30C3\u30D7\uFF09
|
|
4105
4441
|
--yes init: \u4E88\u7D04\u30D5\u30E9\u30B0\uFF08\u73FE\u72B6\u306F\u975E\u5BFE\u8A71\u306E\u305F\u3081\u6319\u52D5\u5DEE\u306A\u3057\u3002\u5C06\u6765\u306E\u5BFE\u8A71\u5C0E\u5165\u6642\u306B\u81EA\u52D5Yes\uFF09
|
|
4106
4442
|
--dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
|
|
4107
4443
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|