qfai 0.9.0 → 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.9.0".length > 0) {
1019
- return "0.9.0";
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,10 @@ 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");
1544
1649
  if (options.force) {
1545
1650
  info(
1546
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"
@@ -1587,8 +1692,8 @@ function report(copied, skipped, dryRun, label) {
1587
1692
  }
1588
1693
 
1589
1694
  // src/cli/commands/report.ts
1590
- var import_promises19 = require("fs/promises");
1591
- var import_node_path19 = __toESM(require("path"), 1);
1695
+ var import_promises20 = require("fs/promises");
1696
+ var import_node_path20 = __toESM(require("path"), 1);
1592
1697
 
1593
1698
  // src/core/normalize.ts
1594
1699
  function normalizeIssuePaths(root, issues) {
@@ -1628,12 +1733,12 @@ function normalizeValidationResult(root, result) {
1628
1733
  }
1629
1734
 
1630
1735
  // src/core/report.ts
1631
- var import_promises18 = require("fs/promises");
1632
- var import_node_path18 = __toESM(require("path"), 1);
1736
+ var import_promises19 = require("fs/promises");
1737
+ var import_node_path19 = __toESM(require("path"), 1);
1633
1738
 
1634
1739
  // src/core/contractIndex.ts
1635
- var import_promises11 = require("fs/promises");
1636
- var import_node_path13 = __toESM(require("path"), 1);
1740
+ var import_promises12 = require("fs/promises");
1741
+ var import_node_path14 = __toESM(require("path"), 1);
1637
1742
 
1638
1743
  // src/core/contractsDecl.ts
1639
1744
  var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
@@ -1655,9 +1760,9 @@ function stripContractDeclarationLines(text) {
1655
1760
  // src/core/contractIndex.ts
1656
1761
  async function buildContractIndex(root, config) {
1657
1762
  const contractsRoot = resolvePath(root, config, "contractsDir");
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");
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");
1661
1766
  const [uiFiles, apiFiles, dbFiles] = await Promise.all([
1662
1767
  collectUiContractFiles(uiRoot),
1663
1768
  collectApiContractFiles(apiRoot),
@@ -1675,7 +1780,7 @@ async function buildContractIndex(root, config) {
1675
1780
  }
1676
1781
  async function indexContractFiles(files, index) {
1677
1782
  for (const file of files) {
1678
- const text = await (0, import_promises11.readFile)(file, "utf-8");
1783
+ const text = await (0, import_promises12.readFile)(file, "utf-8");
1679
1784
  extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
1680
1785
  }
1681
1786
  }
@@ -1910,14 +2015,14 @@ function parseSpec(md, file) {
1910
2015
  }
1911
2016
 
1912
2017
  // src/core/validators/contracts.ts
1913
- var import_promises12 = require("fs/promises");
1914
- var import_node_path15 = __toESM(require("path"), 1);
2018
+ var import_promises13 = require("fs/promises");
2019
+ var import_node_path16 = __toESM(require("path"), 1);
1915
2020
 
1916
2021
  // src/core/contracts.ts
1917
- var import_node_path14 = __toESM(require("path"), 1);
2022
+ var import_node_path15 = __toESM(require("path"), 1);
1918
2023
  var import_yaml2 = require("yaml");
1919
2024
  function parseStructuredContract(file, text) {
1920
- const ext = import_node_path14.default.extname(file).toLowerCase();
2025
+ const ext = import_node_path15.default.extname(file).toLowerCase();
1921
2026
  if (ext === ".json") {
1922
2027
  return JSON.parse(text);
1923
2028
  }
@@ -1937,9 +2042,9 @@ var SQL_DANGEROUS_PATTERNS = [
1937
2042
  async function validateContracts(root, config) {
1938
2043
  const issues = [];
1939
2044
  const contractsRoot = resolvePath(root, config, "contractsDir");
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")));
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")));
1943
2048
  const contractIndex = await buildContractIndex(root, config);
1944
2049
  issues.push(...validateDuplicateContractIds(contractIndex));
1945
2050
  return issues;
@@ -1959,7 +2064,7 @@ async function validateUiContracts(uiRoot) {
1959
2064
  }
1960
2065
  const issues = [];
1961
2066
  for (const file of files) {
1962
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2067
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
1963
2068
  const invalidIds = extractInvalidIds(text, [
1964
2069
  "SPEC",
1965
2070
  "BR",
@@ -2014,7 +2119,7 @@ async function validateApiContracts(apiRoot) {
2014
2119
  }
2015
2120
  const issues = [];
2016
2121
  for (const file of files) {
2017
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2122
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2018
2123
  const invalidIds = extractInvalidIds(text, [
2019
2124
  "SPEC",
2020
2125
  "BR",
@@ -2082,7 +2187,7 @@ async function validateDbContracts(dbRoot) {
2082
2187
  }
2083
2188
  const issues = [];
2084
2189
  for (const file of files) {
2085
- const text = await (0, import_promises12.readFile)(file, "utf-8");
2190
+ const text = await (0, import_promises13.readFile)(file, "utf-8");
2086
2191
  const invalidIds = extractInvalidIds(text, [
2087
2192
  "SPEC",
2088
2193
  "BR",
@@ -2225,8 +2330,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
2225
2330
  }
2226
2331
 
2227
2332
  // src/core/validators/delta.ts
2228
- var import_promises13 = require("fs/promises");
2229
- var import_node_path16 = __toESM(require("path"), 1);
2333
+ var import_promises14 = require("fs/promises");
2334
+ var import_node_path17 = __toESM(require("path"), 1);
2230
2335
  var SECTION_RE = /^##\s+変更区分/m;
2231
2336
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
2232
2337
  var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
@@ -2240,10 +2345,10 @@ async function validateDeltas(root, config) {
2240
2345
  }
2241
2346
  const issues = [];
2242
2347
  for (const pack of packs) {
2243
- const deltaPath = import_node_path16.default.join(pack, "delta.md");
2348
+ const deltaPath = import_node_path17.default.join(pack, "delta.md");
2244
2349
  let text;
2245
2350
  try {
2246
- text = await (0, import_promises13.readFile)(deltaPath, "utf-8");
2351
+ text = await (0, import_promises14.readFile)(deltaPath, "utf-8");
2247
2352
  } catch (error2) {
2248
2353
  if (isMissingFileError2(error2)) {
2249
2354
  issues.push(
@@ -2319,8 +2424,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
2319
2424
  }
2320
2425
 
2321
2426
  // src/core/validators/ids.ts
2322
- var import_promises14 = require("fs/promises");
2323
- var import_node_path17 = __toESM(require("path"), 1);
2427
+ var import_promises15 = require("fs/promises");
2428
+ var import_node_path18 = __toESM(require("path"), 1);
2324
2429
  var SC_TAG_RE3 = /^SC-\d{4}$/;
2325
2430
  async function validateDefinedIds(root, config) {
2326
2431
  const issues = [];
@@ -2355,7 +2460,7 @@ async function validateDefinedIds(root, config) {
2355
2460
  }
2356
2461
  async function collectSpecDefinitionIds(files, out) {
2357
2462
  for (const file of files) {
2358
- const text = await (0, import_promises14.readFile)(file, "utf-8");
2463
+ const text = await (0, import_promises15.readFile)(file, "utf-8");
2359
2464
  const parsed = parseSpec(text, file);
2360
2465
  if (parsed.specId) {
2361
2466
  recordId(out, parsed.specId, file);
@@ -2365,7 +2470,7 @@ async function collectSpecDefinitionIds(files, out) {
2365
2470
  }
2366
2471
  async function collectScenarioDefinitionIds(files, out) {
2367
2472
  for (const file of files) {
2368
- const text = await (0, import_promises14.readFile)(file, "utf-8");
2473
+ const text = await (0, import_promises15.readFile)(file, "utf-8");
2369
2474
  const { document, errors } = parseScenarioDocument(text, file);
2370
2475
  if (!document || errors.length > 0) {
2371
2476
  continue;
@@ -2386,7 +2491,7 @@ function recordId(out, id, file) {
2386
2491
  }
2387
2492
  function formatFileList(files, root) {
2388
2493
  return files.map((file) => {
2389
- const relative = import_node_path17.default.relative(root, file);
2494
+ const relative = import_node_path18.default.relative(root, file);
2390
2495
  return relative.length > 0 ? relative : file;
2391
2496
  }).join(", ");
2392
2497
  }
@@ -2444,7 +2549,7 @@ async function validatePromptsIntegrity(root) {
2444
2549
  }
2445
2550
 
2446
2551
  // src/core/validators/scenario.ts
2447
- var import_promises15 = require("fs/promises");
2552
+ var import_promises16 = require("fs/promises");
2448
2553
  var GIVEN_PATTERN = /\bGiven\b/;
2449
2554
  var WHEN_PATTERN = /\bWhen\b/;
2450
2555
  var THEN_PATTERN = /\bThen\b/;
@@ -2470,7 +2575,7 @@ async function validateScenarios(root, config) {
2470
2575
  for (const entry of entries) {
2471
2576
  let text;
2472
2577
  try {
2473
- text = await (0, import_promises15.readFile)(entry.scenarioPath, "utf-8");
2578
+ text = await (0, import_promises16.readFile)(entry.scenarioPath, "utf-8");
2474
2579
  } catch (error2) {
2475
2580
  if (isMissingFileError3(error2)) {
2476
2581
  issues.push(
@@ -2644,7 +2749,7 @@ function isMissingFileError3(error2) {
2644
2749
  }
2645
2750
 
2646
2751
  // src/core/validators/spec.ts
2647
- var import_promises16 = require("fs/promises");
2752
+ var import_promises17 = require("fs/promises");
2648
2753
  async function validateSpecs(root, config) {
2649
2754
  const specsRoot = resolvePath(root, config, "specsDir");
2650
2755
  const entries = await collectSpecEntries(specsRoot);
@@ -2665,7 +2770,7 @@ async function validateSpecs(root, config) {
2665
2770
  for (const entry of entries) {
2666
2771
  let text;
2667
2772
  try {
2668
- text = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
2773
+ text = await (0, import_promises17.readFile)(entry.specPath, "utf-8");
2669
2774
  } catch (error2) {
2670
2775
  if (isMissingFileError4(error2)) {
2671
2776
  issues.push(
@@ -2818,7 +2923,7 @@ function isMissingFileError4(error2) {
2818
2923
  }
2819
2924
 
2820
2925
  // src/core/validators/traceability.ts
2821
- var import_promises17 = require("fs/promises");
2926
+ var import_promises18 = require("fs/promises");
2822
2927
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
2823
2928
  var BR_TAG_RE2 = /^BR-\d{4}$/;
2824
2929
  async function validateTraceability(root, config) {
@@ -2838,7 +2943,7 @@ async function validateTraceability(root, config) {
2838
2943
  const contractIndex = await buildContractIndex(root, config);
2839
2944
  const contractIds = contractIndex.ids;
2840
2945
  for (const file of specFiles) {
2841
- const text = await (0, import_promises17.readFile)(file, "utf-8");
2946
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
2842
2947
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2843
2948
  const parsed = parseSpec(text, file);
2844
2949
  if (parsed.specId) {
@@ -2911,7 +3016,7 @@ async function validateTraceability(root, config) {
2911
3016
  }
2912
3017
  }
2913
3018
  for (const file of scenarioFiles) {
2914
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3019
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
2915
3020
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2916
3021
  const scenarioContractRefs = parseContractRefs(text, {
2917
3022
  allowCommentPrefix: true
@@ -3233,7 +3338,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
3233
3338
  const pattern = buildIdPattern(Array.from(upstreamIds));
3234
3339
  let found = false;
3235
3340
  for (const file of targetFiles) {
3236
- const text = await (0, import_promises17.readFile)(file, "utf-8");
3341
+ const text = await (0, import_promises18.readFile)(file, "utf-8");
3237
3342
  if (pattern.test(text)) {
3238
3343
  found = true;
3239
3344
  break;
@@ -3325,15 +3430,15 @@ function countIssues(issues) {
3325
3430
  // src/core/report.ts
3326
3431
  var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
3327
3432
  async function createReportData(root, validation, configResult) {
3328
- const resolvedRoot = import_node_path18.default.resolve(root);
3433
+ const resolvedRoot = import_node_path19.default.resolve(root);
3329
3434
  const resolved = configResult ?? await loadConfig(resolvedRoot);
3330
3435
  const config = resolved.config;
3331
3436
  const configPath = resolved.configPath;
3332
3437
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
3333
3438
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
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");
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");
3337
3442
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
3338
3443
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
3339
3444
  const specFiles = await collectSpecFiles(specsRoot);
@@ -3804,7 +3909,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
3804
3909
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
3805
3910
  }
3806
3911
  for (const file of specFiles) {
3807
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3912
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3808
3913
  const parsed = parseSpec(text, file);
3809
3914
  const specKey = parsed.specId;
3810
3915
  if (!specKey) {
@@ -3845,7 +3950,7 @@ async function collectIds(files) {
3845
3950
  DB: /* @__PURE__ */ new Set()
3846
3951
  };
3847
3952
  for (const file of files) {
3848
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3953
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3849
3954
  for (const prefix of ID_PREFIXES2) {
3850
3955
  const ids = extractIds(text, prefix);
3851
3956
  ids.forEach((id) => result[prefix].add(id));
@@ -3863,7 +3968,7 @@ async function collectIds(files) {
3863
3968
  async function collectUpstreamIds(files) {
3864
3969
  const ids = /* @__PURE__ */ new Set();
3865
3970
  for (const file of files) {
3866
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3971
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3867
3972
  extractAllIds(text).forEach((id) => ids.add(id));
3868
3973
  }
3869
3974
  return ids;
@@ -3884,7 +3989,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
3884
3989
  }
3885
3990
  const pattern = buildIdPattern2(Array.from(upstreamIds));
3886
3991
  for (const file of targetFiles) {
3887
- const text = await (0, import_promises18.readFile)(file, "utf-8");
3992
+ const text = await (0, import_promises19.readFile)(file, "utf-8");
3888
3993
  if (pattern.test(text)) {
3889
3994
  return true;
3890
3995
  }
@@ -3976,7 +4081,7 @@ function buildHotspots(issues) {
3976
4081
 
3977
4082
  // src/cli/commands/report.ts
3978
4083
  async function runReport(options) {
3979
- const root = import_node_path19.default.resolve(options.root);
4084
+ const root = import_node_path20.default.resolve(options.root);
3980
4085
  const configResult = await loadConfig(root);
3981
4086
  let validation;
3982
4087
  if (options.runValidate) {
@@ -3993,7 +4098,7 @@ async function runReport(options) {
3993
4098
  validation = normalized;
3994
4099
  } else {
3995
4100
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
3996
- 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);
3997
4102
  try {
3998
4103
  validation = await readValidationResult(inputPath);
3999
4104
  } catch (err) {
@@ -4019,11 +4124,11 @@ async function runReport(options) {
4019
4124
  const data = await createReportData(root, validation, configResult);
4020
4125
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
4021
4126
  const outRoot = resolvePath(root, configResult.config, "outDir");
4022
- 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");
4023
4128
  const out = options.outPath ?? defaultOut;
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}
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}
4027
4132
  `, "utf-8");
4028
4133
  info(
4029
4134
  `report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
@@ -4031,7 +4136,7 @@ async function runReport(options) {
4031
4136
  info(`wrote report: ${outPath}`);
4032
4137
  }
4033
4138
  async function readValidationResult(inputPath) {
4034
- const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
4139
+ const raw = await (0, import_promises20.readFile)(inputPath, "utf-8");
4035
4140
  const parsed = JSON.parse(raw);
4036
4141
  if (!isValidationResult(parsed)) {
4037
4142
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -4087,15 +4192,15 @@ function isMissingFileError5(error2) {
4087
4192
  return record2.code === "ENOENT";
4088
4193
  }
4089
4194
  async function writeValidationResult(root, outputPath, result) {
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)}
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)}
4093
4198
  `, "utf-8");
4094
4199
  }
4095
4200
 
4096
4201
  // src/cli/commands/validate.ts
4097
- var import_promises20 = require("fs/promises");
4098
- var import_node_path20 = __toESM(require("path"), 1);
4202
+ var import_promises21 = require("fs/promises");
4203
+ var import_node_path21 = __toESM(require("path"), 1);
4099
4204
 
4100
4205
  // src/cli/lib/failOn.ts
4101
4206
  function shouldFail(result, failOn) {
@@ -4110,7 +4215,7 @@ function shouldFail(result, failOn) {
4110
4215
 
4111
4216
  // src/cli/commands/validate.ts
4112
4217
  async function runValidate(options) {
4113
- const root = import_node_path20.default.resolve(options.root);
4218
+ const root = import_node_path21.default.resolve(options.root);
4114
4219
  const configResult = await loadConfig(root);
4115
4220
  const result = await validateProject(root, configResult);
4116
4221
  const normalized = normalizeValidationResult(root, result);
@@ -4234,12 +4339,12 @@ function issueKey(issue7) {
4234
4339
  }
4235
4340
  async function emitJson(result, root, jsonPath) {
4236
4341
  const abs = resolveJsonPath(root, jsonPath);
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)}
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)}
4239
4344
  `, "utf-8");
4240
4345
  }
4241
4346
  function resolveJsonPath(root, jsonPath) {
4242
- 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);
4243
4348
  }
4244
4349
  var GITHUB_ANNOTATION_LIMIT = 100;
4245
4350
 
@@ -4252,6 +4357,7 @@ function parseArgs(argv, cwd) {
4252
4357
  force: false,
4253
4358
  yes: false,
4254
4359
  dryRun: false,
4360
+ analyzeList: false,
4255
4361
  reportFormat: "md",
4256
4362
  reportRunValidate: false,
4257
4363
  doctorFormat: "text",
@@ -4261,6 +4367,7 @@ function parseArgs(argv, cwd) {
4261
4367
  };
4262
4368
  const args = [...argv];
4263
4369
  let command = args.shift() ?? null;
4370
+ let invalid = false;
4264
4371
  if (command === "--help" || command === "-h") {
4265
4372
  options.help = true;
4266
4373
  command = null;
@@ -4269,13 +4376,29 @@ function parseArgs(argv, cwd) {
4269
4376
  const arg = args[i];
4270
4377
  switch (arg) {
4271
4378
  case "--root":
4272
- options.root = args[i + 1] ?? options.root;
4273
- options.rootExplicit = true;
4274
- 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
+ }
4275
4390
  break;
4276
4391
  case "--dir":
4277
- options.dir = args[i + 1] ?? options.dir;
4278
- 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
+ }
4279
4402
  break;
4280
4403
  case "--force":
4281
4404
  options.force = true;
@@ -4286,8 +4409,25 @@ function parseArgs(argv, cwd) {
4286
4409
  case "--dry-run":
4287
4410
  options.dryRun = true;
4288
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;
4289
4424
  case "--format": {
4290
- 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
+ }
4291
4431
  applyFormatOption(command, next, options);
4292
4432
  i += 1;
4293
4433
  break;
@@ -4296,35 +4436,44 @@ function parseArgs(argv, cwd) {
4296
4436
  options.strict = true;
4297
4437
  break;
4298
4438
  case "--fail-on": {
4299
- 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
+ }
4300
4445
  if (next === "never" || next === "warning" || next === "error") {
4301
4446
  options.failOn = next;
4302
4447
  }
4303
4448
  i += 1;
4304
4449
  break;
4305
4450
  }
4306
- case "--out":
4307
- {
4308
- const next = args[i + 1];
4309
- if (next) {
4310
- if (command === "doctor") {
4311
- options.doctorOut = next;
4312
- } else {
4313
- options.reportOut = next;
4314
- }
4315
- }
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;
4316
4462
  }
4317
4463
  i += 1;
4318
4464
  break;
4319
- case "--in":
4320
- {
4321
- const next = args[i + 1];
4322
- if (next) {
4323
- options.reportIn = next;
4324
- }
4465
+ }
4466
+ case "--in": {
4467
+ const next = readOptionValue(args, i);
4468
+ if (next === null) {
4469
+ invalid = true;
4470
+ options.help = true;
4471
+ break;
4325
4472
  }
4473
+ options.reportIn = next;
4326
4474
  i += 1;
4327
4475
  break;
4476
+ }
4328
4477
  case "--run-validate":
4329
4478
  options.reportRunValidate = true;
4330
4479
  break;
@@ -4336,7 +4485,14 @@ function parseArgs(argv, cwd) {
4336
4485
  break;
4337
4486
  }
4338
4487
  }
4339
- 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;
4340
4496
  }
4341
4497
  function applyFormatOption(command, value, options) {
4342
4498
  if (!value) {
@@ -4370,9 +4526,12 @@ function applyFormatOption(command, value, options) {
4370
4526
 
4371
4527
  // src/cli/main.ts
4372
4528
  async function run(argv, cwd) {
4373
- const { command, options } = parseArgs(argv, cwd);
4529
+ const { command, invalid, options } = parseArgs(argv, cwd);
4374
4530
  if (!command || options.help) {
4375
4531
  info(usage());
4532
+ if (invalid) {
4533
+ process.exitCode = 1;
4534
+ }
4376
4535
  return;
4377
4536
  }
4378
4537
  switch (command) {
@@ -4384,6 +4543,17 @@ async function run(argv, cwd) {
4384
4543
  yes: options.yes
4385
4544
  });
4386
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;
4387
4557
  case "validate":
4388
4558
  {
4389
4559
  const resolvedRoot = await resolveRoot(options);
@@ -4430,6 +4600,7 @@ function usage() {
4430
4600
 
4431
4601
  Commands:
4432
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
4433
4604
  validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
4434
4605
  report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
4435
4606
  doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
@@ -4440,6 +4611,8 @@ Options:
4440
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
4441
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
4442
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
4443
4616
  --format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
4444
4617
  --format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
4445
4618
  --format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F