qfai 0.8.1 → 0.9.2

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.
@@ -23,17 +23,190 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  mod
24
24
  ));
25
25
 
26
+ // src/cli/commands/analyze.ts
27
+ var import_promises2 = require("fs/promises");
28
+ var import_node_path2 = __toESM(require("path"), 1);
29
+
30
+ // src/core/fs.ts
31
+ var import_promises = require("fs/promises");
32
+ var import_node_path = __toESM(require("path"), 1);
33
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
34
+ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
35
+ "node_modules",
36
+ ".git",
37
+ "dist",
38
+ ".pnpm",
39
+ "tmp",
40
+ ".mcp-tools"
41
+ ]);
42
+ async function collectFiles(root, options = {}) {
43
+ const entries = [];
44
+ if (!await exists(root)) {
45
+ return entries;
46
+ }
47
+ const ignoreDirs = /* @__PURE__ */ new Set([
48
+ ...DEFAULT_IGNORE_DIRS,
49
+ ...options.ignoreDirs ?? []
50
+ ]);
51
+ const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
52
+ await walk(root, root, ignoreDirs, extensions, entries);
53
+ return entries;
54
+ }
55
+ async function collectFilesByGlobs(root, options) {
56
+ if (options.globs.length === 0) {
57
+ return [];
58
+ }
59
+ return (0, import_fast_glob.default)(options.globs, {
60
+ cwd: root,
61
+ ignore: options.ignore ?? [],
62
+ onlyFiles: true,
63
+ absolute: true,
64
+ unique: true
65
+ });
66
+ }
67
+ async function walk(base, current, ignoreDirs, extensions, out) {
68
+ const items = await (0, import_promises.readdir)(current, { withFileTypes: true });
69
+ for (const item of items) {
70
+ const fullPath = import_node_path.default.join(current, item.name);
71
+ if (item.isDirectory()) {
72
+ if (ignoreDirs.has(item.name)) {
73
+ continue;
74
+ }
75
+ await walk(base, fullPath, ignoreDirs, extensions, out);
76
+ continue;
77
+ }
78
+ if (item.isFile()) {
79
+ if (extensions.length > 0) {
80
+ const ext = import_node_path.default.extname(item.name).toLowerCase();
81
+ if (!extensions.includes(ext)) {
82
+ continue;
83
+ }
84
+ }
85
+ out.push(fullPath);
86
+ }
87
+ }
88
+ }
89
+ async function exists(target) {
90
+ try {
91
+ await (0, import_promises.access)(target);
92
+ return true;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ // src/cli/commands/analyze.ts
99
+ async function runAnalyze(options) {
100
+ const root = import_node_path2.default.resolve(options.root);
101
+ const localDir = import_node_path2.default.join(root, ".qfai", "prompts.local", "analyze");
102
+ const standardDir = import_node_path2.default.join(root, ".qfai", "prompts", "analyze");
103
+ const available = await listPromptNames([localDir, standardDir]);
104
+ const promptName = normalizePromptName(options.prompt);
105
+ if (!promptName || options.list) {
106
+ emitList(available);
107
+ return 0;
108
+ }
109
+ const resolved = await resolvePromptPath(promptName, [localDir, standardDir]);
110
+ if (!resolved) {
111
+ emitPromptNotFound(promptName, available);
112
+ return 1;
113
+ }
114
+ const content = await (0, import_promises2.readFile)(resolved, "utf-8");
115
+ process.stdout.write(content);
116
+ if (!content.endsWith("\n")) {
117
+ process.stdout.write("\n");
118
+ }
119
+ return 0;
120
+ }
121
+ function normalizePromptName(value) {
122
+ const trimmed = (value ?? "").trim();
123
+ if (!trimmed) {
124
+ return null;
125
+ }
126
+ return trimmed.endsWith(".md") ? trimmed.slice(0, -3) : trimmed;
127
+ }
128
+ async function listPromptNames(dirs) {
129
+ const byName = /* @__PURE__ */ new Map();
130
+ for (const dir of dirs) {
131
+ const files = await collectFiles(dir, { extensions: [".md"] });
132
+ for (const abs of files) {
133
+ const base = import_node_path2.default.basename(abs);
134
+ if (base.toLowerCase() === "readme.md") {
135
+ continue;
136
+ }
137
+ const name = base.slice(0, -3);
138
+ if (byName.has(name)) {
139
+ continue;
140
+ }
141
+ if (await isDeprecatedPrompt(abs)) {
142
+ continue;
143
+ }
144
+ byName.set(name, abs);
145
+ }
146
+ }
147
+ return [...byName.keys()].sort((a, b) => a.localeCompare(b));
148
+ }
149
+ function emitList(names) {
150
+ process.stdout.write("# qfai analyze: prompts\n\n");
151
+ if (names.length === 0) {
152
+ process.stdout.write(
153
+ "\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u307E\u305A `qfai init` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\n"
154
+ );
155
+ return;
156
+ }
157
+ process.stdout.write("\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7:\n\n");
158
+ for (const name of names) {
159
+ process.stdout.write(`- ${name}
160
+ `);
161
+ }
162
+ }
163
+ async function resolvePromptPath(promptName, dirs) {
164
+ const filename = `${promptName}.md`;
165
+ for (const dir of dirs) {
166
+ const full = import_node_path2.default.join(dir, filename);
167
+ try {
168
+ await (0, import_promises2.readFile)(full, "utf-8");
169
+ return full;
170
+ } catch {
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ async function isDeprecatedPrompt(filePath) {
176
+ try {
177
+ const content = await (0, import_promises2.readFile)(filePath, "utf-8");
178
+ const firstLine = firstLineOf(content);
179
+ return firstLine.trim() === "# Deprecated";
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+ function firstLineOf(content) {
185
+ return content.match(/^[^\r\n]*/)?.[0] ?? "";
186
+ }
187
+ function emitPromptNotFound(promptName, candidates) {
188
+ process.stderr.write(`qfai analyze: prompt not found: ${promptName}
189
+ `);
190
+ if (candidates.length > 0) {
191
+ process.stderr.write("candidates:\n");
192
+ for (const c of candidates) {
193
+ process.stderr.write(`- ${c}
194
+ `);
195
+ }
196
+ }
197
+ }
198
+
26
199
  // src/cli/commands/doctor.ts
27
- var import_promises9 = require("fs/promises");
28
- var import_node_path10 = __toESM(require("path"), 1);
200
+ var import_promises10 = require("fs/promises");
201
+ var import_node_path11 = __toESM(require("path"), 1);
29
202
 
30
203
  // src/core/doctor.ts
31
- var import_promises8 = require("fs/promises");
32
- var import_node_path9 = __toESM(require("path"), 1);
204
+ var import_promises9 = require("fs/promises");
205
+ var import_node_path10 = __toESM(require("path"), 1);
33
206
 
34
207
  // src/core/config.ts
35
- var import_promises = require("fs/promises");
36
- var import_node_path = __toESM(require("path"), 1);
208
+ var import_promises3 = require("fs/promises");
209
+ var import_node_path3 = __toESM(require("path"), 1);
37
210
  var import_yaml = require("yaml");
38
211
  var defaultConfig = {
39
212
  paths: {
@@ -73,17 +246,17 @@ var defaultConfig = {
73
246
  }
74
247
  };
75
248
  function getConfigPath(root) {
76
- return import_node_path.default.join(root, "qfai.config.yaml");
249
+ return import_node_path3.default.join(root, "qfai.config.yaml");
77
250
  }
78
251
  async function findConfigRoot(startDir) {
79
- const resolvedStart = import_node_path.default.resolve(startDir);
252
+ const resolvedStart = import_node_path3.default.resolve(startDir);
80
253
  let current = resolvedStart;
81
254
  while (true) {
82
255
  const configPath = getConfigPath(current);
83
- if (await exists(configPath)) {
256
+ if (await exists2(configPath)) {
84
257
  return { root: current, configPath, found: true };
85
258
  }
86
- const parent = import_node_path.default.dirname(current);
259
+ const parent = import_node_path3.default.dirname(current);
87
260
  if (parent === current) {
88
261
  break;
89
262
  }
@@ -100,7 +273,7 @@ async function loadConfig(root) {
100
273
  const issues = [];
101
274
  let parsed;
102
275
  try {
103
- const raw = await (0, import_promises.readFile)(configPath, "utf-8");
276
+ const raw = await (0, import_promises3.readFile)(configPath, "utf-8");
104
277
  parsed = (0, import_yaml.parse)(raw);
105
278
  } catch (error2) {
106
279
  if (isMissingFile(error2)) {
@@ -113,7 +286,7 @@ async function loadConfig(root) {
113
286
  return { config: normalized, issues, configPath };
114
287
  }
115
288
  function resolvePath(root, config, key) {
116
- return import_node_path.default.resolve(root, config.paths[key]);
289
+ return import_node_path3.default.resolve(root, config.paths[key]);
117
290
  }
118
291
  function normalizeConfig(raw, configPath, issues) {
119
292
  if (!isRecord(raw)) {
@@ -413,9 +586,9 @@ function isMissingFile(error2) {
413
586
  }
414
587
  return false;
415
588
  }
416
- async function exists(target) {
589
+ async function exists2(target) {
417
590
  try {
418
- await (0, import_promises.access)(target);
591
+ await (0, import_promises3.access)(target);
419
592
  return true;
420
593
  } catch {
421
594
  return false;
@@ -432,94 +605,26 @@ function isRecord(value) {
432
605
  }
433
606
 
434
607
  // src/core/discovery.ts
435
- var import_promises4 = require("fs/promises");
436
-
437
- // src/core/fs.ts
438
- var import_promises2 = require("fs/promises");
439
- var import_node_path2 = __toESM(require("path"), 1);
440
- var import_fast_glob = __toESM(require("fast-glob"), 1);
441
- var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
442
- "node_modules",
443
- ".git",
444
- "dist",
445
- ".pnpm",
446
- "tmp",
447
- ".mcp-tools"
448
- ]);
449
- async function collectFiles(root, options = {}) {
450
- const entries = [];
451
- if (!await exists2(root)) {
452
- return entries;
453
- }
454
- const ignoreDirs = /* @__PURE__ */ new Set([
455
- ...DEFAULT_IGNORE_DIRS,
456
- ...options.ignoreDirs ?? []
457
- ]);
458
- const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
459
- await walk(root, root, ignoreDirs, extensions, entries);
460
- return entries;
461
- }
462
- async function collectFilesByGlobs(root, options) {
463
- if (options.globs.length === 0) {
464
- return [];
465
- }
466
- return (0, import_fast_glob.default)(options.globs, {
467
- cwd: root,
468
- ignore: options.ignore ?? [],
469
- onlyFiles: true,
470
- absolute: true,
471
- unique: true
472
- });
473
- }
474
- async function walk(base, current, ignoreDirs, extensions, out) {
475
- const items = await (0, import_promises2.readdir)(current, { withFileTypes: true });
476
- for (const item of items) {
477
- const fullPath = import_node_path2.default.join(current, item.name);
478
- if (item.isDirectory()) {
479
- if (ignoreDirs.has(item.name)) {
480
- continue;
481
- }
482
- await walk(base, fullPath, ignoreDirs, extensions, out);
483
- continue;
484
- }
485
- if (item.isFile()) {
486
- if (extensions.length > 0) {
487
- const ext = import_node_path2.default.extname(item.name).toLowerCase();
488
- if (!extensions.includes(ext)) {
489
- continue;
490
- }
491
- }
492
- out.push(fullPath);
493
- }
494
- }
495
- }
496
- async function exists2(target) {
497
- try {
498
- await (0, import_promises2.access)(target);
499
- return true;
500
- } catch {
501
- return false;
502
- }
503
- }
608
+ var import_promises5 = require("fs/promises");
504
609
 
505
610
  // src/core/specLayout.ts
506
- var import_promises3 = require("fs/promises");
507
- var import_node_path3 = __toESM(require("path"), 1);
611
+ var import_promises4 = require("fs/promises");
612
+ var import_node_path4 = __toESM(require("path"), 1);
508
613
  var SPEC_DIR_RE = /^spec-\d{4}$/;
509
614
  async function collectSpecEntries(specsRoot) {
510
615
  const dirs = await listSpecDirs(specsRoot);
511
616
  const entries = dirs.map((dir) => ({
512
617
  dir,
513
- specPath: import_node_path3.default.join(dir, "spec.md"),
514
- deltaPath: import_node_path3.default.join(dir, "delta.md"),
515
- scenarioPath: import_node_path3.default.join(dir, "scenario.md")
618
+ specPath: import_node_path4.default.join(dir, "spec.md"),
619
+ deltaPath: import_node_path4.default.join(dir, "delta.md"),
620
+ scenarioPath: import_node_path4.default.join(dir, "scenario.md")
516
621
  }));
517
622
  return entries.sort((a, b) => a.dir.localeCompare(b.dir));
518
623
  }
519
624
  async function listSpecDirs(specsRoot) {
520
625
  try {
521
- const items = await (0, import_promises3.readdir)(specsRoot, { withFileTypes: true });
522
- return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => import_node_path3.default.join(specsRoot, name));
626
+ const items = await (0, import_promises4.readdir)(specsRoot, { withFileTypes: true });
627
+ return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => import_node_path4.default.join(specsRoot, name));
523
628
  } catch (error2) {
524
629
  if (isMissingFileError(error2)) {
525
630
  return [];
@@ -575,7 +680,7 @@ async function filterExisting(files) {
575
680
  }
576
681
  async function exists3(target) {
577
682
  try {
578
- await (0, import_promises4.access)(target);
683
+ await (0, import_promises5.access)(target);
579
684
  return true;
580
685
  } catch {
581
686
  return false;
@@ -583,15 +688,15 @@ async function exists3(target) {
583
688
  }
584
689
 
585
690
  // src/core/paths.ts
586
- var import_node_path4 = __toESM(require("path"), 1);
691
+ var import_node_path5 = __toESM(require("path"), 1);
587
692
  function toRelativePath(root, target) {
588
693
  if (!target) {
589
694
  return target;
590
695
  }
591
- if (!import_node_path4.default.isAbsolute(target)) {
696
+ if (!import_node_path5.default.isAbsolute(target)) {
592
697
  return toPosixPath(target);
593
698
  }
594
- const relative = import_node_path4.default.relative(root, target);
699
+ const relative = import_node_path5.default.relative(root, target);
595
700
  if (!relative) {
596
701
  return ".";
597
702
  }
@@ -602,8 +707,8 @@ function toPosixPath(value) {
602
707
  }
603
708
 
604
709
  // src/core/traceability.ts
605
- var import_promises5 = require("fs/promises");
606
- var import_node_path5 = __toESM(require("path"), 1);
710
+ var import_promises6 = require("fs/promises");
711
+ var import_node_path6 = __toESM(require("path"), 1);
607
712
 
608
713
  // src/core/gherkin/parse.ts
609
714
  var import_gherkin = require("@cucumber/gherkin");
@@ -755,7 +860,7 @@ function extractAnnotatedScIds(text) {
755
860
  async function collectScIdsFromScenarioFiles(scenarioFiles) {
756
861
  const scIds = /* @__PURE__ */ new Set();
757
862
  for (const file of scenarioFiles) {
758
- const text = await (0, import_promises5.readFile)(file, "utf-8");
863
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
759
864
  const { document, errors } = parseScenarioDocument(text, file);
760
865
  if (!document || errors.length > 0) {
761
866
  continue;
@@ -773,7 +878,7 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
773
878
  async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
774
879
  const sources = /* @__PURE__ */ new Map();
775
880
  for (const file of scenarioFiles) {
776
- const text = await (0, import_promises5.readFile)(file, "utf-8");
881
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
777
882
  const { document, errors } = parseScenarioDocument(text, file);
778
883
  if (!document || errors.length > 0) {
779
884
  continue;
@@ -826,10 +931,10 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
826
931
  };
827
932
  }
828
933
  const normalizedFiles = Array.from(
829
- new Set(files.map((file) => import_node_path5.default.normalize(file)))
934
+ new Set(files.map((file) => import_node_path6.default.normalize(file)))
830
935
  );
831
936
  for (const file of normalizedFiles) {
832
- const text = await (0, import_promises5.readFile)(file, "utf-8");
937
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
833
938
  const scIds = extractAnnotatedScIds(text);
834
939
  if (scIds.length === 0) {
835
940
  continue;
@@ -886,20 +991,20 @@ function formatError3(error2) {
886
991
  }
887
992
 
888
993
  // src/core/promptsIntegrity.ts
889
- var import_promises6 = require("fs/promises");
890
- var import_node_path7 = __toESM(require("path"), 1);
994
+ var import_promises7 = require("fs/promises");
995
+ var import_node_path8 = __toESM(require("path"), 1);
891
996
 
892
997
  // src/shared/assets.ts
893
998
  var import_node_fs = require("fs");
894
- var import_node_path6 = __toESM(require("path"), 1);
999
+ var import_node_path7 = __toESM(require("path"), 1);
895
1000
  var import_node_url = require("url");
896
1001
  function getInitAssetsDir() {
897
1002
  const base = __filename;
898
1003
  const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
899
- const baseDir = import_node_path6.default.dirname(basePath);
1004
+ const baseDir = import_node_path7.default.dirname(basePath);
900
1005
  const candidates = [
901
- import_node_path6.default.resolve(baseDir, "../../../assets/init"),
902
- import_node_path6.default.resolve(baseDir, "../../assets/init")
1006
+ import_node_path7.default.resolve(baseDir, "../../../assets/init"),
1007
+ import_node_path7.default.resolve(baseDir, "../../assets/init")
903
1008
  ];
904
1009
  for (const candidate of candidates) {
905
1010
  if ((0, import_node_fs.existsSync)(candidate)) {
@@ -917,10 +1022,10 @@ function getInitAssetsDir() {
917
1022
 
918
1023
  // src/core/promptsIntegrity.ts
919
1024
  async function diffProjectPromptsAgainstInitAssets(root) {
920
- const promptsDir = import_node_path7.default.resolve(root, ".qfai", "prompts");
1025
+ const promptsDir = import_node_path8.default.resolve(root, ".qfai", "prompts");
921
1026
  let templateDir;
922
1027
  try {
923
- templateDir = import_node_path7.default.join(getInitAssetsDir(), ".qfai", "prompts");
1028
+ templateDir = import_node_path8.default.join(getInitAssetsDir(), ".qfai", "prompts");
924
1029
  } catch {
925
1030
  return {
926
1031
  status: "skipped_missing_assets",
@@ -973,8 +1078,8 @@ async function diffProjectPromptsAgainstInitAssets(root) {
973
1078
  }
974
1079
  try {
975
1080
  const [a, b] = await Promise.all([
976
- (0, import_promises6.readFile)(templateAbs, "utf-8"),
977
- (0, import_promises6.readFile)(projectAbs, "utf-8")
1081
+ (0, import_promises7.readFile)(templateAbs, "utf-8"),
1082
+ (0, import_promises7.readFile)(projectAbs, "utf-8")
978
1083
  ]);
979
1084
  if (normalizeNewlines(a) !== normalizeNewlines(b)) {
980
1085
  changed.push(rel);
@@ -997,7 +1102,7 @@ function normalizeNewlines(text) {
997
1102
  return text.replace(/\r\n/g, "\n");
998
1103
  }
999
1104
  function toRel(base, abs) {
1000
- const rel = import_node_path7.default.relative(base, abs);
1105
+ const rel = import_node_path8.default.relative(base, abs);
1001
1106
  return rel.replace(/[\\/]+/g, "/");
1002
1107
  }
1003
1108
  function intersectKeys(a, b) {
@@ -1011,16 +1116,16 @@ function intersectKeys(a, b) {
1011
1116
  }
1012
1117
 
1013
1118
  // src/core/version.ts
1014
- var import_promises7 = require("fs/promises");
1015
- var import_node_path8 = __toESM(require("path"), 1);
1119
+ var import_promises8 = require("fs/promises");
1120
+ var import_node_path9 = __toESM(require("path"), 1);
1016
1121
  var import_node_url2 = require("url");
1017
1122
  async function resolveToolVersion() {
1018
- if ("0.8.1".length > 0) {
1019
- return "0.8.1";
1123
+ if ("0.9.2".length > 0) {
1124
+ return "0.9.2";
1020
1125
  }
1021
1126
  try {
1022
1127
  const packagePath = resolvePackageJsonPath();
1023
- const raw = await (0, import_promises7.readFile)(packagePath, "utf-8");
1128
+ const raw = await (0, import_promises8.readFile)(packagePath, "utf-8");
1024
1129
  const parsed = JSON.parse(raw);
1025
1130
  const version = typeof parsed.version === "string" ? parsed.version : "";
1026
1131
  return version.length > 0 ? version : "unknown";
@@ -1031,13 +1136,13 @@ async function resolveToolVersion() {
1031
1136
  function resolvePackageJsonPath() {
1032
1137
  const base = __filename;
1033
1138
  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");
1139
+ return import_node_path9.default.resolve(import_node_path9.default.dirname(basePath), "../../package.json");
1035
1140
  }
1036
1141
 
1037
1142
  // src/core/doctor.ts
1038
1143
  async function exists4(target) {
1039
1144
  try {
1040
- await (0, import_promises8.access)(target);
1145
+ await (0, import_promises9.access)(target);
1041
1146
  return true;
1042
1147
  } catch {
1043
1148
  return false;
@@ -1057,7 +1162,7 @@ function normalizeGlobs2(values) {
1057
1162
  return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
1058
1163
  }
1059
1164
  async function createDoctorData(options) {
1060
- const startDir = import_node_path9.default.resolve(options.startDir);
1165
+ const startDir = import_node_path10.default.resolve(options.startDir);
1061
1166
  const checks = [];
1062
1167
  const configPath = getConfigPath(startDir);
1063
1168
  const search = options.rootExplicit ? {
@@ -1120,9 +1225,9 @@ async function createDoctorData(options) {
1120
1225
  details: { path: toRelativePath(root, resolved) }
1121
1226
  });
1122
1227
  if (key === "promptsDir") {
1123
- const promptsLocalDir = import_node_path9.default.join(
1124
- import_node_path9.default.dirname(resolved),
1125
- `${import_node_path9.default.basename(resolved)}.local`
1228
+ const promptsLocalDir = import_node_path10.default.join(
1229
+ import_node_path10.default.dirname(resolved),
1230
+ `${import_node_path10.default.basename(resolved)}.local`
1126
1231
  );
1127
1232
  const found = await exists4(promptsLocalDir);
1128
1233
  addCheck(checks, {
@@ -1195,7 +1300,7 @@ async function createDoctorData(options) {
1195
1300
  message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
1196
1301
  details: { specPacks: entries.length, missingFiles }
1197
1302
  });
1198
- const validateJsonAbs = import_node_path9.default.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : import_node_path9.default.resolve(root, config.output.validateJsonPath);
1303
+ const validateJsonAbs = import_node_path10.default.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : import_node_path10.default.resolve(root, config.output.validateJsonPath);
1199
1304
  const validateJsonExists = await exists4(validateJsonAbs);
1200
1305
  addCheck(checks, {
1201
1306
  id: "output.validateJson",
@@ -1205,8 +1310,8 @@ async function createDoctorData(options) {
1205
1310
  details: { path: toRelativePath(root, validateJsonAbs) }
1206
1311
  });
1207
1312
  const outDirAbs = resolvePath(root, config, "outDir");
1208
- const rel = import_node_path9.default.relative(outDirAbs, validateJsonAbs);
1209
- const inside = rel !== "" && !rel.startsWith("..") && !import_node_path9.default.isAbsolute(rel);
1313
+ const rel = import_node_path10.default.relative(outDirAbs, validateJsonAbs);
1314
+ const inside = rel !== "" && !rel.startsWith("..") && !import_node_path10.default.isAbsolute(rel);
1210
1315
  addCheck(checks, {
1211
1316
  id: "output.pathAlignment",
1212
1317
  severity: inside ? "ok" : "warning",
@@ -1312,12 +1417,12 @@ async function detectOutDirCollisions(root) {
1312
1417
  ignore: DEFAULT_CONFIG_SEARCH_IGNORE_GLOBS
1313
1418
  });
1314
1419
  const configRoots = Array.from(
1315
- new Set(configPaths.map((configPath) => import_node_path9.default.dirname(configPath)))
1420
+ new Set(configPaths.map((configPath) => import_node_path10.default.dirname(configPath)))
1316
1421
  ).sort((a, b) => a.localeCompare(b));
1317
1422
  const outDirToRoots = /* @__PURE__ */ new Map();
1318
1423
  for (const configRoot of configRoots) {
1319
1424
  const { config } = await loadConfig(configRoot);
1320
- const outDir = import_node_path9.default.normalize(resolvePath(configRoot, config, "outDir"));
1425
+ const outDir = import_node_path10.default.normalize(resolvePath(configRoot, config, "outDir"));
1321
1426
  const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
1322
1427
  roots.add(configRoot);
1323
1428
  outDirToRoots.set(outDir, roots);
@@ -1334,20 +1439,20 @@ async function detectOutDirCollisions(root) {
1334
1439
  return { monorepoRoot, configRoots, collisions };
1335
1440
  }
1336
1441
  async function findMonorepoRoot(startDir) {
1337
- let current = import_node_path9.default.resolve(startDir);
1442
+ let current = import_node_path10.default.resolve(startDir);
1338
1443
  while (true) {
1339
- const gitPath = import_node_path9.default.join(current, ".git");
1340
- const workspacePath = import_node_path9.default.join(current, "pnpm-workspace.yaml");
1444
+ const gitPath = import_node_path10.default.join(current, ".git");
1445
+ const workspacePath = import_node_path10.default.join(current, "pnpm-workspace.yaml");
1341
1446
  if (await exists4(gitPath) || await exists4(workspacePath)) {
1342
1447
  return current;
1343
1448
  }
1344
- const parent = import_node_path9.default.dirname(current);
1449
+ const parent = import_node_path10.default.dirname(current);
1345
1450
  if (parent === current) {
1346
1451
  break;
1347
1452
  }
1348
1453
  current = parent;
1349
1454
  }
1350
- return import_node_path9.default.resolve(startDir);
1455
+ return import_node_path10.default.resolve(startDir);
1351
1456
  }
1352
1457
 
1353
1458
  // src/cli/lib/logger.ts
@@ -1389,9 +1494,9 @@ async function runDoctor(options) {
1389
1494
  const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
1390
1495
  const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
1391
1496
  if (options.outPath) {
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}
1497
+ const outAbs = import_node_path11.default.isAbsolute(options.outPath) ? options.outPath : import_node_path11.default.resolve(process.cwd(), options.outPath);
1498
+ await (0, import_promises10.mkdir)(import_node_path11.default.dirname(outAbs), { recursive: true });
1499
+ await (0, import_promises10.writeFile)(outAbs, `${output}
1395
1500
  `, "utf-8");
1396
1501
  info(`doctor: wrote ${outAbs}`);
1397
1502
  return exitCode;
@@ -1410,11 +1515,11 @@ function shouldFailDoctor(summary, failOn) {
1410
1515
  }
1411
1516
 
1412
1517
  // src/cli/commands/init.ts
1413
- var import_node_path12 = __toESM(require("path"), 1);
1518
+ var import_node_path13 = __toESM(require("path"), 1);
1414
1519
 
1415
1520
  // src/cli/lib/fs.ts
1416
- var import_promises10 = require("fs/promises");
1417
- var import_node_path11 = __toESM(require("path"), 1);
1521
+ var import_promises11 = require("fs/promises");
1522
+ var import_node_path12 = __toESM(require("path"), 1);
1418
1523
  async function copyTemplateTree(sourceRoot, destRoot, options) {
1419
1524
  const files = await collectTemplateFiles(sourceRoot);
1420
1525
  return copyFiles(files, sourceRoot, destRoot, options);
@@ -1422,7 +1527,7 @@ async function copyTemplateTree(sourceRoot, destRoot, options) {
1422
1527
  async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
1423
1528
  const allFiles = [];
1424
1529
  for (const relPath of relativePaths) {
1425
- const fullPath = import_node_path11.default.join(sourceRoot, relPath);
1530
+ const fullPath = import_node_path12.default.join(sourceRoot, relPath);
1426
1531
  const files = await collectTemplateFiles(fullPath);
1427
1532
  allFiles.push(...files);
1428
1533
  }
@@ -1432,13 +1537,13 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1432
1537
  const copied = [];
1433
1538
  const skipped = [];
1434
1539
  const conflicts = [];
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);
1540
+ const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path12.default.sep);
1541
+ const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path12.default.sep);
1437
1542
  const isProtectedRelative = (relative) => {
1438
1543
  if (protectPrefixes.length === 0) {
1439
1544
  return false;
1440
1545
  }
1441
- const normalized = relative.replace(/[\\/]+/g, import_node_path11.default.sep);
1546
+ const normalized = relative.replace(/[\\/]+/g, import_node_path12.default.sep);
1442
1547
  return protectPrefixes.some(
1443
1548
  (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1444
1549
  );
@@ -1447,7 +1552,7 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1447
1552
  if (excludePrefixes.length === 0) {
1448
1553
  return false;
1449
1554
  }
1450
- const normalized = relative.replace(/[\\/]+/g, import_node_path11.default.sep);
1555
+ const normalized = relative.replace(/[\\/]+/g, import_node_path12.default.sep);
1451
1556
  return excludePrefixes.some(
1452
1557
  (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1453
1558
  );
@@ -1455,14 +1560,14 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1455
1560
  const conflictPolicy = options.conflictPolicy ?? "error";
1456
1561
  if (!options.force && conflictPolicy === "error") {
1457
1562
  for (const file of files) {
1458
- const relative = import_node_path11.default.relative(sourceRoot, file);
1563
+ const relative = import_node_path12.default.relative(sourceRoot, file);
1459
1564
  if (isExcludedRelative(relative)) {
1460
1565
  continue;
1461
1566
  }
1462
1567
  if (isProtectedRelative(relative)) {
1463
1568
  continue;
1464
1569
  }
1465
- const dest = import_node_path11.default.join(destRoot, relative);
1570
+ const dest = import_node_path12.default.join(destRoot, relative);
1466
1571
  if (!await shouldWrite(dest, options.force)) {
1467
1572
  conflicts.push(dest);
1468
1573
  }
@@ -1472,19 +1577,19 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1472
1577
  }
1473
1578
  }
1474
1579
  for (const file of files) {
1475
- const relative = import_node_path11.default.relative(sourceRoot, file);
1580
+ const relative = import_node_path12.default.relative(sourceRoot, file);
1476
1581
  if (isExcludedRelative(relative)) {
1477
1582
  continue;
1478
1583
  }
1479
- const dest = import_node_path11.default.join(destRoot, relative);
1584
+ const dest = import_node_path12.default.join(destRoot, relative);
1480
1585
  const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
1481
1586
  if (!await shouldWrite(dest, forceForThisFile)) {
1482
1587
  skipped.push(dest);
1483
1588
  continue;
1484
1589
  }
1485
1590
  if (!options.dryRun) {
1486
- await (0, import_promises10.mkdir)(import_node_path11.default.dirname(dest), { recursive: true });
1487
- await (0, import_promises10.copyFile)(file, dest);
1591
+ await (0, import_promises11.mkdir)(import_node_path12.default.dirname(dest), { recursive: true });
1592
+ await (0, import_promises11.copyFile)(file, dest);
1488
1593
  }
1489
1594
  copied.push(dest);
1490
1595
  }
@@ -1505,9 +1610,9 @@ async function collectTemplateFiles(root) {
1505
1610
  if (!await exists5(root)) {
1506
1611
  return entries;
1507
1612
  }
1508
- const items = await (0, import_promises10.readdir)(root, { withFileTypes: true });
1613
+ const items = await (0, import_promises11.readdir)(root, { withFileTypes: true });
1509
1614
  for (const item of items) {
1510
- const fullPath = import_node_path11.default.join(root, item.name);
1615
+ const fullPath = import_node_path12.default.join(root, item.name);
1511
1616
  if (item.isDirectory()) {
1512
1617
  const nested = await collectTemplateFiles(fullPath);
1513
1618
  entries.push(...nested);
@@ -1527,7 +1632,7 @@ async function shouldWrite(target, force) {
1527
1632
  }
1528
1633
  async function exists5(target) {
1529
1634
  try {
1530
- await (0, import_promises10.access)(target);
1635
+ await (0, import_promises11.access)(target);
1531
1636
  return true;
1532
1637
  } catch {
1533
1638
  return false;
@@ -1537,10 +1642,15 @@ async function exists5(target) {
1537
1642
  // src/cli/commands/init.ts
1538
1643
  async function runInit(options) {
1539
1644
  const assetsRoot = getInitAssetsDir();
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");
1645
+ const rootAssets = import_node_path13.default.join(assetsRoot, "root");
1646
+ const qfaiAssets = import_node_path13.default.join(assetsRoot, ".qfai");
1647
+ const destRoot = import_node_path13.default.resolve(options.dir);
1648
+ const destQfai = import_node_path13.default.join(destRoot, ".qfai");
1649
+ if (options.force) {
1650
+ info(
1651
+ "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"
1652
+ );
1653
+ }
1544
1654
  const rootResult = await copyTemplateTree(rootAssets, destRoot, {
1545
1655
  force: false,
1546
1656
  dryRun: options.dryRun,
@@ -1582,8 +1692,8 @@ function report(copied, skipped, dryRun, label) {
1582
1692
  }
1583
1693
 
1584
1694
  // src/cli/commands/report.ts
1585
- var import_promises19 = require("fs/promises");
1586
- var import_node_path19 = __toESM(require("path"), 1);
1695
+ var import_promises20 = require("fs/promises");
1696
+ var import_node_path20 = __toESM(require("path"), 1);
1587
1697
 
1588
1698
  // src/core/normalize.ts
1589
1699
  function normalizeIssuePaths(root, issues) {
@@ -1623,12 +1733,12 @@ function normalizeValidationResult(root, result) {
1623
1733
  }
1624
1734
 
1625
1735
  // src/core/report.ts
1626
- var import_promises18 = require("fs/promises");
1627
- var import_node_path18 = __toESM(require("path"), 1);
1736
+ var import_promises19 = require("fs/promises");
1737
+ var import_node_path19 = __toESM(require("path"), 1);
1628
1738
 
1629
1739
  // src/core/contractIndex.ts
1630
- var import_promises11 = require("fs/promises");
1631
- var import_node_path13 = __toESM(require("path"), 1);
1740
+ var import_promises12 = require("fs/promises");
1741
+ var import_node_path14 = __toESM(require("path"), 1);
1632
1742
 
1633
1743
  // src/core/contractsDecl.ts
1634
1744
  var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
@@ -1650,9 +1760,9 @@ function stripContractDeclarationLines(text) {
1650
1760
  // src/core/contractIndex.ts
1651
1761
  async function buildContractIndex(root, config) {
1652
1762
  const contractsRoot = resolvePath(root, config, "contractsDir");
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");
1763
+ const uiRoot = import_node_path14.default.join(contractsRoot, "ui");
1764
+ const apiRoot = import_node_path14.default.join(contractsRoot, "api");
1765
+ const dbRoot = import_node_path14.default.join(contractsRoot, "db");
1656
1766
  const [uiFiles, apiFiles, dbFiles] = await Promise.all([
1657
1767
  collectUiContractFiles(uiRoot),
1658
1768
  collectApiContractFiles(apiRoot),
@@ -1670,7 +1780,7 @@ async function buildContractIndex(root, config) {
1670
1780
  }
1671
1781
  async function indexContractFiles(files, index) {
1672
1782
  for (const file of files) {
1673
- const text = await (0, import_promises11.readFile)(file, "utf-8");
1783
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1674
1784
  extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
1675
1785
  }
1676
1786
  }
@@ -1905,14 +2015,14 @@ function parseSpec(md, file) {
1905
2015
  }
1906
2016
 
1907
2017
  // src/core/validators/contracts.ts
1908
- var import_promises12 = require("fs/promises");
1909
- var import_node_path15 = __toESM(require("path"), 1);
2018
+ var import_promises13 = require("fs/promises");
2019
+ var import_node_path16 = __toESM(require("path"), 1);
1910
2020
 
1911
2021
  // src/core/contracts.ts
1912
- var import_node_path14 = __toESM(require("path"), 1);
2022
+ var import_node_path15 = __toESM(require("path"), 1);
1913
2023
  var import_yaml2 = require("yaml");
1914
2024
  function parseStructuredContract(file, text) {
1915
- const ext = import_node_path14.default.extname(file).toLowerCase();
2025
+ const ext = import_node_path15.default.extname(file).toLowerCase();
1916
2026
  if (ext === ".json") {
1917
2027
  return JSON.parse(text);
1918
2028
  }
@@ -1932,9 +2042,9 @@ var SQL_DANGEROUS_PATTERNS = [
1932
2042
  async function validateContracts(root, config) {
1933
2043
  const issues = [];
1934
2044
  const contractsRoot = resolvePath(root, config, "contractsDir");
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")));
2045
+ issues.push(...await validateUiContracts(import_node_path16.default.join(contractsRoot, "ui")));
2046
+ issues.push(...await validateApiContracts(import_node_path16.default.join(contractsRoot, "api")));
2047
+ issues.push(...await validateDbContracts(import_node_path16.default.join(contractsRoot, "db")));
1938
2048
  const contractIndex = await buildContractIndex(root, config);
1939
2049
  issues.push(...validateDuplicateContractIds(contractIndex));
1940
2050
  return issues;
@@ -1954,7 +2064,7 @@ async function validateUiContracts(uiRoot) {
1954
2064
  }
1955
2065
  const issues = [];
1956
2066
  for (const file of files) {
1957
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2067
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1958
2068
  const invalidIds = extractInvalidIds(text, [
1959
2069
  "SPEC",
1960
2070
  "BR",
@@ -2009,7 +2119,7 @@ async function validateApiContracts(apiRoot) {
2009
2119
  }
2010
2120
  const issues = [];
2011
2121
  for (const file of files) {
2012
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2122
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2013
2123
  const invalidIds = extractInvalidIds(text, [
2014
2124
  "SPEC",
2015
2125
  "BR",
@@ -2077,7 +2187,7 @@ async function validateDbContracts(dbRoot) {
2077
2187
  }
2078
2188
  const issues = [];
2079
2189
  for (const file of files) {
2080
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2190
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2081
2191
  const invalidIds = extractInvalidIds(text, [
2082
2192
  "SPEC",
2083
2193
  "BR",
@@ -2220,8 +2330,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
2220
2330
  }
2221
2331
 
2222
2332
  // src/core/validators/delta.ts
2223
- var import_promises13 = require("fs/promises");
2224
- var import_node_path16 = __toESM(require("path"), 1);
2333
+ var import_promises14 = require("fs/promises");
2334
+ var import_node_path17 = __toESM(require("path"), 1);
2225
2335
  var SECTION_RE = /^##\s+変更区分/m;
2226
2336
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
2227
2337
  var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
@@ -2235,10 +2345,10 @@ async function validateDeltas(root, config) {
2235
2345
  }
2236
2346
  const issues = [];
2237
2347
  for (const pack of packs) {
2238
- const deltaPath = import_node_path16.default.join(pack, "delta.md");
2348
+ const deltaPath = import_node_path17.default.join(pack, "delta.md");
2239
2349
  let text;
2240
2350
  try {
2241
- text = await (0, import_promises13.readFile)(deltaPath, "utf-8");
2351
+ text = await (0, import_promises14.readFile)(deltaPath, "utf-8");
2242
2352
  } catch (error2) {
2243
2353
  if (isMissingFileError2(error2)) {
2244
2354
  issues.push(
@@ -2314,8 +2424,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
2314
2424
  }
2315
2425
 
2316
2426
  // src/core/validators/ids.ts
2317
- var import_promises14 = require("fs/promises");
2318
- var import_node_path17 = __toESM(require("path"), 1);
2427
+ var import_promises15 = require("fs/promises");
2428
+ var import_node_path18 = __toESM(require("path"), 1);
2319
2429
  var SC_TAG_RE3 = /^SC-\d{4}$/;
2320
2430
  async function validateDefinedIds(root, config) {
2321
2431
  const issues = [];
@@ -2350,7 +2460,7 @@ async function validateDefinedIds(root, config) {
2350
2460
  }
2351
2461
  async function collectSpecDefinitionIds(files, out) {
2352
2462
  for (const file of files) {
2353
- const text = await (0, import_promises14.readFile)(file, "utf-8");
2463
+ const text = await (0, import_promises15.readFile)(file, "utf-8");
2354
2464
  const parsed = parseSpec(text, file);
2355
2465
  if (parsed.specId) {
2356
2466
  recordId(out, parsed.specId, file);
@@ -2360,7 +2470,7 @@ async function collectSpecDefinitionIds(files, out) {
2360
2470
  }
2361
2471
  async function collectScenarioDefinitionIds(files, out) {
2362
2472
  for (const file of files) {
2363
- const text = await (0, import_promises14.readFile)(file, "utf-8");
2473
+ const text = await (0, import_promises15.readFile)(file, "utf-8");
2364
2474
  const { document, errors } = parseScenarioDocument(text, file);
2365
2475
  if (!document || errors.length > 0) {
2366
2476
  continue;
@@ -2381,7 +2491,7 @@ function recordId(out, id, file) {
2381
2491
  }
2382
2492
  function formatFileList(files, root) {
2383
2493
  return files.map((file) => {
2384
- const relative = import_node_path17.default.relative(root, file);
2494
+ const relative = import_node_path18.default.relative(root, file);
2385
2495
  return relative.length > 0 ? relative : file;
2386
2496
  }).join(", ");
2387
2497
  }
@@ -2439,7 +2549,7 @@ async function validatePromptsIntegrity(root) {
2439
2549
  }
2440
2550
 
2441
2551
  // src/core/validators/scenario.ts
2442
- var import_promises15 = require("fs/promises");
2552
+ var import_promises16 = require("fs/promises");
2443
2553
  var GIVEN_PATTERN = /\bGiven\b/;
2444
2554
  var WHEN_PATTERN = /\bWhen\b/;
2445
2555
  var THEN_PATTERN = /\bThen\b/;
@@ -2465,7 +2575,7 @@ async function validateScenarios(root, config) {
2465
2575
  for (const entry of entries) {
2466
2576
  let text;
2467
2577
  try {
2468
- text = await (0, import_promises15.readFile)(entry.scenarioPath, "utf-8");
2578
+ text = await (0, import_promises16.readFile)(entry.scenarioPath, "utf-8");
2469
2579
  } catch (error2) {
2470
2580
  if (isMissingFileError3(error2)) {
2471
2581
  issues.push(
@@ -2639,7 +2749,7 @@ function isMissingFileError3(error2) {
2639
2749
  }
2640
2750
 
2641
2751
  // src/core/validators/spec.ts
2642
- var import_promises16 = require("fs/promises");
2752
+ var import_promises17 = require("fs/promises");
2643
2753
  async function validateSpecs(root, config) {
2644
2754
  const specsRoot = resolvePath(root, config, "specsDir");
2645
2755
  const entries = await collectSpecEntries(specsRoot);
@@ -2660,7 +2770,7 @@ async function validateSpecs(root, config) {
2660
2770
  for (const entry of entries) {
2661
2771
  let text;
2662
2772
  try {
2663
- text = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
2773
+ text = await (0, import_promises17.readFile)(entry.specPath, "utf-8");
2664
2774
  } catch (error2) {
2665
2775
  if (isMissingFileError4(error2)) {
2666
2776
  issues.push(
@@ -2813,7 +2923,7 @@ function isMissingFileError4(error2) {
2813
2923
  }
2814
2924
 
2815
2925
  // src/core/validators/traceability.ts
2816
- var import_promises17 = require("fs/promises");
2926
+ var import_promises18 = require("fs/promises");
2817
2927
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
2818
2928
  var BR_TAG_RE2 = /^BR-\d{4}$/;
2819
2929
  async function validateTraceability(root, config) {
@@ -2833,7 +2943,7 @@ async function validateTraceability(root, config) {
2833
2943
  const contractIndex = await buildContractIndex(root, config);
2834
2944
  const contractIds = contractIndex.ids;
2835
2945
  for (const file of specFiles) {
2836
- const text = await (0, import_promises17.readFile)(file, "utf-8");
2946
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
2837
2947
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2838
2948
  const parsed = parseSpec(text, file);
2839
2949
  if (parsed.specId) {
@@ -2906,7 +3016,7 @@ async function validateTraceability(root, config) {
2906
3016
  }
2907
3017
  }
2908
3018
  for (const file of scenarioFiles) {
2909
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3019
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
2910
3020
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2911
3021
  const scenarioContractRefs = parseContractRefs(text, {
2912
3022
  allowCommentPrefix: true
@@ -3228,7 +3338,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
3228
3338
  const pattern = buildIdPattern(Array.from(upstreamIds));
3229
3339
  let found = false;
3230
3340
  for (const file of targetFiles) {
3231
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3341
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3232
3342
  if (pattern.test(text)) {
3233
3343
  found = true;
3234
3344
  break;
@@ -3320,15 +3430,15 @@ function countIssues(issues) {
3320
3430
  // src/core/report.ts
3321
3431
  var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
3322
3432
  async function createReportData(root, validation, configResult) {
3323
- const resolvedRoot = import_node_path18.default.resolve(root);
3433
+ const resolvedRoot = import_node_path19.default.resolve(root);
3324
3434
  const resolved = configResult ?? await loadConfig(resolvedRoot);
3325
3435
  const config = resolved.config;
3326
3436
  const configPath = resolved.configPath;
3327
3437
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
3328
3438
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
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");
3439
+ const apiRoot = import_node_path19.default.join(contractsRoot, "api");
3440
+ const uiRoot = import_node_path19.default.join(contractsRoot, "ui");
3441
+ const dbRoot = import_node_path19.default.join(contractsRoot, "db");
3332
3442
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
3333
3443
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
3334
3444
  const specFiles = await collectSpecFiles(specsRoot);
@@ -3799,7 +3909,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
3799
3909
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
3800
3910
  }
3801
3911
  for (const file of specFiles) {
3802
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3912
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3803
3913
  const parsed = parseSpec(text, file);
3804
3914
  const specKey = parsed.specId;
3805
3915
  if (!specKey) {
@@ -3840,7 +3950,7 @@ async function collectIds(files) {
3840
3950
  DB: /* @__PURE__ */ new Set()
3841
3951
  };
3842
3952
  for (const file of files) {
3843
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3953
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3844
3954
  for (const prefix of ID_PREFIXES2) {
3845
3955
  const ids = extractIds(text, prefix);
3846
3956
  ids.forEach((id) => result[prefix].add(id));
@@ -3858,7 +3968,7 @@ async function collectIds(files) {
3858
3968
  async function collectUpstreamIds(files) {
3859
3969
  const ids = /* @__PURE__ */ new Set();
3860
3970
  for (const file of files) {
3861
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3971
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3862
3972
  extractAllIds(text).forEach((id) => ids.add(id));
3863
3973
  }
3864
3974
  return ids;
@@ -3879,7 +3989,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
3879
3989
  }
3880
3990
  const pattern = buildIdPattern2(Array.from(upstreamIds));
3881
3991
  for (const file of targetFiles) {
3882
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3992
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3883
3993
  if (pattern.test(text)) {
3884
3994
  return true;
3885
3995
  }
@@ -3971,7 +4081,7 @@ function buildHotspots(issues) {
3971
4081
 
3972
4082
  // src/cli/commands/report.ts
3973
4083
  async function runReport(options) {
3974
- const root = import_node_path19.default.resolve(options.root);
4084
+ const root = import_node_path20.default.resolve(options.root);
3975
4085
  const configResult = await loadConfig(root);
3976
4086
  let validation;
3977
4087
  if (options.runValidate) {
@@ -3988,7 +4098,7 @@ async function runReport(options) {
3988
4098
  validation = normalized;
3989
4099
  } else {
3990
4100
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
3991
- const inputPath = import_node_path19.default.isAbsolute(input) ? input : import_node_path19.default.resolve(root, input);
4101
+ const inputPath = import_node_path20.default.isAbsolute(input) ? input : import_node_path20.default.resolve(root, input);
3992
4102
  try {
3993
4103
  validation = await readValidationResult(inputPath);
3994
4104
  } catch (err) {
@@ -4014,11 +4124,11 @@ async function runReport(options) {
4014
4124
  const data = await createReportData(root, validation, configResult);
4015
4125
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
4016
4126
  const outRoot = resolvePath(root, configResult.config, "outDir");
4017
- const defaultOut = options.format === "json" ? import_node_path19.default.join(outRoot, "report.json") : import_node_path19.default.join(outRoot, "report.md");
4127
+ const defaultOut = options.format === "json" ? import_node_path20.default.join(outRoot, "report.json") : import_node_path20.default.join(outRoot, "report.md");
4018
4128
  const out = options.outPath ?? defaultOut;
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}
4129
+ const outPath = import_node_path20.default.isAbsolute(out) ? out : import_node_path20.default.resolve(root, out);
4130
+ await (0, import_promises20.mkdir)(import_node_path20.default.dirname(outPath), { recursive: true });
4131
+ await (0, import_promises20.writeFile)(outPath, `${output}
4022
4132
  `, "utf-8");
4023
4133
  info(
4024
4134
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -4026,7 +4136,7 @@ async function runReport(options) {
4026
4136
  info(`wrote report: ${outPath}`);
4027
4137
  }
4028
4138
  async function readValidationResult(inputPath) {
4029
- const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
4139
+ const raw = await (0, import_promises20.readFile)(inputPath, "utf-8");
4030
4140
  const parsed = JSON.parse(raw);
4031
4141
  if (!isValidationResult(parsed)) {
4032
4142
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -4082,15 +4192,15 @@ function isMissingFileError5(error2) {
4082
4192
  return record2.code === "ENOENT";
4083
4193
  }
4084
4194
  async function writeValidationResult(root, outputPath, result) {
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)}
4195
+ const abs = import_node_path20.default.isAbsolute(outputPath) ? outputPath : import_node_path20.default.resolve(root, outputPath);
4196
+ await (0, import_promises20.mkdir)(import_node_path20.default.dirname(abs), { recursive: true });
4197
+ await (0, import_promises20.writeFile)(abs, `${JSON.stringify(result, null, 2)}
4088
4198
  `, "utf-8");
4089
4199
  }
4090
4200
 
4091
4201
  // src/cli/commands/validate.ts
4092
- var import_promises20 = require("fs/promises");
4093
- var import_node_path20 = __toESM(require("path"), 1);
4202
+ var import_promises21 = require("fs/promises");
4203
+ var import_node_path21 = __toESM(require("path"), 1);
4094
4204
 
4095
4205
  // src/cli/lib/failOn.ts
4096
4206
  function shouldFail(result, failOn) {
@@ -4105,7 +4215,7 @@ function shouldFail(result, failOn) {
4105
4215
 
4106
4216
  // src/cli/commands/validate.ts
4107
4217
  async function runValidate(options) {
4108
- const root = import_node_path20.default.resolve(options.root);
4218
+ const root = import_node_path21.default.resolve(options.root);
4109
4219
  const configResult = await loadConfig(root);
4110
4220
  const result = await validateProject(root, configResult);
4111
4221
  const normalized = normalizeValidationResult(root, result);
@@ -4229,12 +4339,12 @@ function issueKey(issue7) {
4229
4339
  }
4230
4340
  async function emitJson(result, root, jsonPath) {
4231
4341
  const abs = resolveJsonPath(root, jsonPath);
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)}
4342
+ await (0, import_promises21.mkdir)(import_node_path21.default.dirname(abs), { recursive: true });
4343
+ await (0, import_promises21.writeFile)(abs, `${JSON.stringify(result, null, 2)}
4234
4344
  `, "utf-8");
4235
4345
  }
4236
4346
  function resolveJsonPath(root, jsonPath) {
4237
- return import_node_path20.default.isAbsolute(jsonPath) ? jsonPath : import_node_path20.default.resolve(root, jsonPath);
4347
+ return import_node_path21.default.isAbsolute(jsonPath) ? jsonPath : import_node_path21.default.resolve(root, jsonPath);
4238
4348
  }
4239
4349
  var GITHUB_ANNOTATION_LIMIT = 100;
4240
4350
 
@@ -4247,6 +4357,7 @@ function parseArgs(argv, cwd) {
4247
4357
  force: false,
4248
4358
  yes: false,
4249
4359
  dryRun: false,
4360
+ analyzeList: false,
4250
4361
  reportFormat: "md",
4251
4362
  reportRunValidate: false,
4252
4363
  doctorFormat: "text",
@@ -4256,6 +4367,7 @@ function parseArgs(argv, cwd) {
4256
4367
  };
4257
4368
  const args = [...argv];
4258
4369
  let command = args.shift() ?? null;
4370
+ let invalid = false;
4259
4371
  if (command === "--help" || command === "-h") {
4260
4372
  options.help = true;
4261
4373
  command = null;
@@ -4264,13 +4376,29 @@ function parseArgs(argv, cwd) {
4264
4376
  const arg = args[i];
4265
4377
  switch (arg) {
4266
4378
  case "--root":
4267
- options.root = args[i + 1] ?? options.root;
4268
- options.rootExplicit = true;
4269
- i += 1;
4379
+ {
4380
+ const next = readOptionValue(args, i);
4381
+ if (next === null) {
4382
+ invalid = true;
4383
+ options.help = true;
4384
+ break;
4385
+ }
4386
+ options.root = next;
4387
+ options.rootExplicit = true;
4388
+ i += 1;
4389
+ }
4270
4390
  break;
4271
4391
  case "--dir":
4272
- options.dir = args[i + 1] ?? options.dir;
4273
- i += 1;
4392
+ {
4393
+ const next = readOptionValue(args, i);
4394
+ if (next === null) {
4395
+ invalid = true;
4396
+ options.help = true;
4397
+ break;
4398
+ }
4399
+ options.dir = next;
4400
+ i += 1;
4401
+ }
4274
4402
  break;
4275
4403
  case "--force":
4276
4404
  options.force = true;
@@ -4281,8 +4409,25 @@ function parseArgs(argv, cwd) {
4281
4409
  case "--dry-run":
4282
4410
  options.dryRun = true;
4283
4411
  break;
4412
+ case "--list":
4413
+ options.analyzeList = true;
4414
+ break;
4415
+ case "--prompt":
4416
+ {
4417
+ const next = readOptionValue(args, i);
4418
+ if (next) {
4419
+ options.analyzePrompt = next;
4420
+ i += 1;
4421
+ }
4422
+ }
4423
+ break;
4284
4424
  case "--format": {
4285
- const next = args[i + 1];
4425
+ const next = readOptionValue(args, i);
4426
+ if (next === null) {
4427
+ invalid = true;
4428
+ options.help = true;
4429
+ break;
4430
+ }
4286
4431
  applyFormatOption(command, next, options);
4287
4432
  i += 1;
4288
4433
  break;
@@ -4291,35 +4436,44 @@ function parseArgs(argv, cwd) {
4291
4436
  options.strict = true;
4292
4437
  break;
4293
4438
  case "--fail-on": {
4294
- const next = args[i + 1];
4439
+ const next = readOptionValue(args, i);
4440
+ if (next === null) {
4441
+ invalid = true;
4442
+ options.help = true;
4443
+ break;
4444
+ }
4295
4445
  if (next === "never" || next === "warning" || next === "error") {
4296
4446
  options.failOn = next;
4297
4447
  }
4298
4448
  i += 1;
4299
4449
  break;
4300
4450
  }
4301
- case "--out":
4302
- {
4303
- const next = args[i + 1];
4304
- if (next) {
4305
- if (command === "doctor") {
4306
- options.doctorOut = next;
4307
- } else {
4308
- options.reportOut = next;
4309
- }
4310
- }
4451
+ case "--out": {
4452
+ const next = readOptionValue(args, i);
4453
+ if (next === null) {
4454
+ invalid = true;
4455
+ options.help = true;
4456
+ break;
4457
+ }
4458
+ if (command === "doctor") {
4459
+ options.doctorOut = next;
4460
+ } else {
4461
+ options.reportOut = next;
4311
4462
  }
4312
4463
  i += 1;
4313
4464
  break;
4314
- case "--in":
4315
- {
4316
- const next = args[i + 1];
4317
- if (next) {
4318
- options.reportIn = next;
4319
- }
4465
+ }
4466
+ case "--in": {
4467
+ const next = readOptionValue(args, i);
4468
+ if (next === null) {
4469
+ invalid = true;
4470
+ options.help = true;
4471
+ break;
4320
4472
  }
4473
+ options.reportIn = next;
4321
4474
  i += 1;
4322
4475
  break;
4476
+ }
4323
4477
  case "--run-validate":
4324
4478
  options.reportRunValidate = true;
4325
4479
  break;
@@ -4331,7 +4485,14 @@ function parseArgs(argv, cwd) {
4331
4485
  break;
4332
4486
  }
4333
4487
  }
4334
- return { command, options };
4488
+ return { command, invalid, options };
4489
+ }
4490
+ function readOptionValue(args, index) {
4491
+ const next = args[index + 1];
4492
+ if (!next || next.startsWith("--")) {
4493
+ return null;
4494
+ }
4495
+ return next;
4335
4496
  }
4336
4497
  function applyFormatOption(command, value, options) {
4337
4498
  if (!value) {
@@ -4365,9 +4526,12 @@ function applyFormatOption(command, value, options) {
4365
4526
 
4366
4527
  // src/cli/main.ts
4367
4528
  async function run(argv, cwd) {
4368
- const { command, options } = parseArgs(argv, cwd);
4529
+ const { command, invalid, options } = parseArgs(argv, cwd);
4369
4530
  if (!command || options.help) {
4370
4531
  info(usage());
4532
+ if (invalid) {
4533
+ process.exitCode = 1;
4534
+ }
4371
4535
  return;
4372
4536
  }
4373
4537
  switch (command) {
@@ -4379,6 +4543,17 @@ async function run(argv, cwd) {
4379
4543
  yes: options.yes
4380
4544
  });
4381
4545
  return;
4546
+ case "analyze":
4547
+ {
4548
+ const resolvedRoot = await resolveRoot(options);
4549
+ const exitCode = await runAnalyze({
4550
+ root: resolvedRoot,
4551
+ list: options.analyzeList,
4552
+ ...options.analyzePrompt !== void 0 ? { prompt: options.analyzePrompt } : {}
4553
+ });
4554
+ process.exitCode = exitCode;
4555
+ }
4556
+ return;
4382
4557
  case "validate":
4383
4558
  {
4384
4559
  const resolvedRoot = await resolveRoot(options);
@@ -4425,6 +4600,7 @@ function usage() {
4425
4600
 
4426
4601
  Commands:
4427
4602
  init \u30C6\u30F3\u30D7\u30EC\u3092\u751F\u6210
4603
+ analyze \u610F\u5473\u30EC\u30D9\u30EB\u306E\u30EC\u30D3\u30E5\u30FC\u88DC\u52A9\uFF08\u30D7\u30ED\u30F3\u30D7\u30C8\u51FA\u529B\uFF09
4428
4604
  validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
4429
4605
  report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
4430
4606
  doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
@@ -4432,9 +4608,11 @@ Commands:
4432
4608
  Options:
4433
4609
  --root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
4434
4610
  --dir <path> init \u306E\u51FA\u529B\u5148
4435
- --force \u65E2\u5B58\u30D5\u30A1\u30A4\u30EB\u3092\u4E0A\u66F8\u304D
4611
+ --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
4436
4612
  --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
4437
4613
  --dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
4614
+ --list analyze: \u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7\u3092\u8868\u793A
4615
+ --prompt <name> analyze: \u6307\u5B9A\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08.md\u7701\u7565\u53EF\uFF09\u3092\u51FA\u529B
4438
4616
  --format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
4439
4617
  --format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
4440
4618
  --format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F