qfai 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,189 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli/commands/analyze.ts
4
+ import { readFile } from "fs/promises";
5
+ import path2 from "path";
6
+
7
+ // src/core/fs.ts
8
+ import { access, readdir } from "fs/promises";
9
+ import path from "path";
10
+ import fg from "fast-glob";
11
+ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
12
+ "node_modules",
13
+ ".git",
14
+ "dist",
15
+ ".pnpm",
16
+ "tmp",
17
+ ".mcp-tools"
18
+ ]);
19
+ async function collectFiles(root, options = {}) {
20
+ const entries = [];
21
+ if (!await exists(root)) {
22
+ return entries;
23
+ }
24
+ const ignoreDirs = /* @__PURE__ */ new Set([
25
+ ...DEFAULT_IGNORE_DIRS,
26
+ ...options.ignoreDirs ?? []
27
+ ]);
28
+ const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
29
+ await walk(root, root, ignoreDirs, extensions, entries);
30
+ return entries;
31
+ }
32
+ async function collectFilesByGlobs(root, options) {
33
+ if (options.globs.length === 0) {
34
+ return [];
35
+ }
36
+ return fg(options.globs, {
37
+ cwd: root,
38
+ ignore: options.ignore ?? [],
39
+ onlyFiles: true,
40
+ absolute: true,
41
+ unique: true
42
+ });
43
+ }
44
+ async function walk(base, current, ignoreDirs, extensions, out) {
45
+ const items = await readdir(current, { withFileTypes: true });
46
+ for (const item of items) {
47
+ const fullPath = path.join(current, item.name);
48
+ if (item.isDirectory()) {
49
+ if (ignoreDirs.has(item.name)) {
50
+ continue;
51
+ }
52
+ await walk(base, fullPath, ignoreDirs, extensions, out);
53
+ continue;
54
+ }
55
+ if (item.isFile()) {
56
+ if (extensions.length > 0) {
57
+ const ext = path.extname(item.name).toLowerCase();
58
+ if (!extensions.includes(ext)) {
59
+ continue;
60
+ }
61
+ }
62
+ out.push(fullPath);
63
+ }
64
+ }
65
+ }
66
+ async function exists(target) {
67
+ try {
68
+ await access(target);
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ // src/cli/commands/analyze.ts
76
+ async function runAnalyze(options) {
77
+ const root = path2.resolve(options.root);
78
+ const localDir = path2.join(root, ".qfai", "prompts.local", "analyze");
79
+ const standardDir = path2.join(root, ".qfai", "prompts", "analyze");
80
+ const available = await listPromptNames([localDir, standardDir]);
81
+ const promptName = normalizePromptName(options.prompt);
82
+ if (!promptName || options.list) {
83
+ emitList(available);
84
+ return 0;
85
+ }
86
+ const resolved = await resolvePromptPath(promptName, [localDir, standardDir]);
87
+ if (!resolved) {
88
+ emitPromptNotFound(promptName, available);
89
+ return 1;
90
+ }
91
+ const content = await readFile(resolved, "utf-8");
92
+ process.stdout.write(content);
93
+ if (!content.endsWith("\n")) {
94
+ process.stdout.write("\n");
95
+ }
96
+ return 0;
97
+ }
98
+ function normalizePromptName(value) {
99
+ const trimmed = (value ?? "").trim();
100
+ if (!trimmed) {
101
+ return null;
102
+ }
103
+ return trimmed.endsWith(".md") ? trimmed.slice(0, -3) : trimmed;
104
+ }
105
+ async function listPromptNames(dirs) {
106
+ const byName = /* @__PURE__ */ new Map();
107
+ for (const dir of dirs) {
108
+ const files = await collectFiles(dir, { extensions: [".md"] });
109
+ for (const abs of files) {
110
+ const base = path2.basename(abs);
111
+ if (base.toLowerCase() === "readme.md") {
112
+ continue;
113
+ }
114
+ const name = base.slice(0, -3);
115
+ if (byName.has(name)) {
116
+ continue;
117
+ }
118
+ if (await isDeprecatedPrompt(abs)) {
119
+ continue;
120
+ }
121
+ byName.set(name, abs);
122
+ }
123
+ }
124
+ return [...byName.keys()].sort((a, b) => a.localeCompare(b));
125
+ }
126
+ function emitList(names) {
127
+ process.stdout.write("# qfai analyze: prompts\n\n");
128
+ if (names.length === 0) {
129
+ process.stdout.write(
130
+ "\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"
131
+ );
132
+ return;
133
+ }
134
+ process.stdout.write("\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7:\n\n");
135
+ for (const name of names) {
136
+ process.stdout.write(`- ${name}
137
+ `);
138
+ }
139
+ }
140
+ async function resolvePromptPath(promptName, dirs) {
141
+ const filename = `${promptName}.md`;
142
+ for (const dir of dirs) {
143
+ const full = path2.join(dir, filename);
144
+ try {
145
+ await readFile(full, "utf-8");
146
+ return full;
147
+ } catch {
148
+ }
149
+ }
150
+ return null;
151
+ }
152
+ async function isDeprecatedPrompt(filePath) {
153
+ try {
154
+ const content = await readFile(filePath, "utf-8");
155
+ const firstLine = firstLineOf(content);
156
+ return firstLine.trim() === "# Deprecated";
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+ function firstLineOf(content) {
162
+ return content.match(/^[^\r\n]*/)?.[0] ?? "";
163
+ }
164
+ function emitPromptNotFound(promptName, candidates) {
165
+ process.stderr.write(`qfai analyze: prompt not found: ${promptName}
166
+ `);
167
+ if (candidates.length > 0) {
168
+ process.stderr.write("candidates:\n");
169
+ for (const c of candidates) {
170
+ process.stderr.write(`- ${c}
171
+ `);
172
+ }
173
+ }
174
+ }
175
+
3
176
  // src/cli/commands/doctor.ts
4
177
  import { mkdir, writeFile } from "fs/promises";
5
- import path10 from "path";
178
+ import path11 from "path";
6
179
 
7
180
  // src/core/doctor.ts
8
181
  import { access as access4 } from "fs/promises";
9
- import path9 from "path";
182
+ import path10 from "path";
10
183
 
11
184
  // src/core/config.ts
12
- import { access, readFile } from "fs/promises";
13
- import path from "path";
185
+ import { access as access2, readFile as readFile2 } from "fs/promises";
186
+ import path3 from "path";
14
187
  import { parse as parseYaml } from "yaml";
15
188
  var defaultConfig = {
16
189
  paths: {
@@ -50,17 +223,17 @@ var defaultConfig = {
50
223
  }
51
224
  };
52
225
  function getConfigPath(root) {
53
- return path.join(root, "qfai.config.yaml");
226
+ return path3.join(root, "qfai.config.yaml");
54
227
  }
55
228
  async function findConfigRoot(startDir) {
56
- const resolvedStart = path.resolve(startDir);
229
+ const resolvedStart = path3.resolve(startDir);
57
230
  let current = resolvedStart;
58
231
  while (true) {
59
232
  const configPath = getConfigPath(current);
60
- if (await exists(configPath)) {
233
+ if (await exists2(configPath)) {
61
234
  return { root: current, configPath, found: true };
62
235
  }
63
- const parent = path.dirname(current);
236
+ const parent = path3.dirname(current);
64
237
  if (parent === current) {
65
238
  break;
66
239
  }
@@ -77,7 +250,7 @@ async function loadConfig(root) {
77
250
  const issues = [];
78
251
  let parsed;
79
252
  try {
80
- const raw = await readFile(configPath, "utf-8");
253
+ const raw = await readFile2(configPath, "utf-8");
81
254
  parsed = parseYaml(raw);
82
255
  } catch (error2) {
83
256
  if (isMissingFile(error2)) {
@@ -90,7 +263,7 @@ async function loadConfig(root) {
90
263
  return { config: normalized, issues, configPath };
91
264
  }
92
265
  function resolvePath(root, config, key) {
93
- return path.resolve(root, config.paths[key]);
266
+ return path3.resolve(root, config.paths[key]);
94
267
  }
95
268
  function normalizeConfig(raw, configPath, issues) {
96
269
  if (!isRecord(raw)) {
@@ -390,9 +563,9 @@ function isMissingFile(error2) {
390
563
  }
391
564
  return false;
392
565
  }
393
- async function exists(target) {
566
+ async function exists2(target) {
394
567
  try {
395
- await access(target);
568
+ await access2(target);
396
569
  return true;
397
570
  } catch {
398
571
  return false;
@@ -411,92 +584,24 @@ function isRecord(value) {
411
584
  // src/core/discovery.ts
412
585
  import { access as access3 } from "fs/promises";
413
586
 
414
- // src/core/fs.ts
415
- import { access as access2, readdir } from "fs/promises";
416
- import path2 from "path";
417
- import fg from "fast-glob";
418
- var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
419
- "node_modules",
420
- ".git",
421
- "dist",
422
- ".pnpm",
423
- "tmp",
424
- ".mcp-tools"
425
- ]);
426
- async function collectFiles(root, options = {}) {
427
- const entries = [];
428
- if (!await exists2(root)) {
429
- return entries;
430
- }
431
- const ignoreDirs = /* @__PURE__ */ new Set([
432
- ...DEFAULT_IGNORE_DIRS,
433
- ...options.ignoreDirs ?? []
434
- ]);
435
- const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
436
- await walk(root, root, ignoreDirs, extensions, entries);
437
- return entries;
438
- }
439
- async function collectFilesByGlobs(root, options) {
440
- if (options.globs.length === 0) {
441
- return [];
442
- }
443
- return fg(options.globs, {
444
- cwd: root,
445
- ignore: options.ignore ?? [],
446
- onlyFiles: true,
447
- absolute: true,
448
- unique: true
449
- });
450
- }
451
- async function walk(base, current, ignoreDirs, extensions, out) {
452
- const items = await readdir(current, { withFileTypes: true });
453
- for (const item of items) {
454
- const fullPath = path2.join(current, item.name);
455
- if (item.isDirectory()) {
456
- if (ignoreDirs.has(item.name)) {
457
- continue;
458
- }
459
- await walk(base, fullPath, ignoreDirs, extensions, out);
460
- continue;
461
- }
462
- if (item.isFile()) {
463
- if (extensions.length > 0) {
464
- const ext = path2.extname(item.name).toLowerCase();
465
- if (!extensions.includes(ext)) {
466
- continue;
467
- }
468
- }
469
- out.push(fullPath);
470
- }
471
- }
472
- }
473
- async function exists2(target) {
474
- try {
475
- await access2(target);
476
- return true;
477
- } catch {
478
- return false;
479
- }
480
- }
481
-
482
587
  // src/core/specLayout.ts
483
588
  import { readdir as readdir2 } from "fs/promises";
484
- import path3 from "path";
589
+ import path4 from "path";
485
590
  var SPEC_DIR_RE = /^spec-\d{4}$/;
486
591
  async function collectSpecEntries(specsRoot) {
487
592
  const dirs = await listSpecDirs(specsRoot);
488
593
  const entries = dirs.map((dir) => ({
489
594
  dir,
490
- specPath: path3.join(dir, "spec.md"),
491
- deltaPath: path3.join(dir, "delta.md"),
492
- scenarioPath: path3.join(dir, "scenario.md")
595
+ specPath: path4.join(dir, "spec.md"),
596
+ deltaPath: path4.join(dir, "delta.md"),
597
+ scenarioPath: path4.join(dir, "scenario.md")
493
598
  }));
494
599
  return entries.sort((a, b) => a.dir.localeCompare(b.dir));
495
600
  }
496
601
  async function listSpecDirs(specsRoot) {
497
602
  try {
498
603
  const items = await readdir2(specsRoot, { withFileTypes: true });
499
- return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => path3.join(specsRoot, name));
604
+ return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => path4.join(specsRoot, name));
500
605
  } catch (error2) {
501
606
  if (isMissingFileError(error2)) {
502
607
  return [];
@@ -560,15 +665,15 @@ async function exists3(target) {
560
665
  }
561
666
 
562
667
  // src/core/paths.ts
563
- import path4 from "path";
668
+ import path5 from "path";
564
669
  function toRelativePath(root, target) {
565
670
  if (!target) {
566
671
  return target;
567
672
  }
568
- if (!path4.isAbsolute(target)) {
673
+ if (!path5.isAbsolute(target)) {
569
674
  return toPosixPath(target);
570
675
  }
571
- const relative = path4.relative(root, target);
676
+ const relative = path5.relative(root, target);
572
677
  if (!relative) {
573
678
  return ".";
574
679
  }
@@ -579,8 +684,8 @@ function toPosixPath(value) {
579
684
  }
580
685
 
581
686
  // src/core/traceability.ts
582
- import { readFile as readFile2 } from "fs/promises";
583
- import path5 from "path";
687
+ import { readFile as readFile3 } from "fs/promises";
688
+ import path6 from "path";
584
689
 
585
690
  // src/core/gherkin/parse.ts
586
691
  import {
@@ -736,7 +841,7 @@ function extractAnnotatedScIds(text) {
736
841
  async function collectScIdsFromScenarioFiles(scenarioFiles) {
737
842
  const scIds = /* @__PURE__ */ new Set();
738
843
  for (const file of scenarioFiles) {
739
- const text = await readFile2(file, "utf-8");
844
+ const text = await readFile3(file, "utf-8");
740
845
  const { document, errors } = parseScenarioDocument(text, file);
741
846
  if (!document || errors.length > 0) {
742
847
  continue;
@@ -754,7 +859,7 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
754
859
  async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
755
860
  const sources = /* @__PURE__ */ new Map();
756
861
  for (const file of scenarioFiles) {
757
- const text = await readFile2(file, "utf-8");
862
+ const text = await readFile3(file, "utf-8");
758
863
  const { document, errors } = parseScenarioDocument(text, file);
759
864
  if (!document || errors.length > 0) {
760
865
  continue;
@@ -807,10 +912,10 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
807
912
  };
808
913
  }
809
914
  const normalizedFiles = Array.from(
810
- new Set(files.map((file) => path5.normalize(file)))
915
+ new Set(files.map((file) => path6.normalize(file)))
811
916
  );
812
917
  for (const file of normalizedFiles) {
813
- const text = await readFile2(file, "utf-8");
918
+ const text = await readFile3(file, "utf-8");
814
919
  const scIds = extractAnnotatedScIds(text);
815
920
  if (scIds.length === 0) {
816
921
  continue;
@@ -867,20 +972,20 @@ function formatError3(error2) {
867
972
  }
868
973
 
869
974
  // src/core/promptsIntegrity.ts
870
- import { readFile as readFile3 } from "fs/promises";
871
- import path7 from "path";
975
+ import { readFile as readFile4 } from "fs/promises";
976
+ import path8 from "path";
872
977
 
873
978
  // src/shared/assets.ts
874
979
  import { existsSync } from "fs";
875
- import path6 from "path";
980
+ import path7 from "path";
876
981
  import { fileURLToPath } from "url";
877
982
  function getInitAssetsDir() {
878
983
  const base = import.meta.url;
879
984
  const basePath = base.startsWith("file:") ? fileURLToPath(base) : base;
880
- const baseDir = path6.dirname(basePath);
985
+ const baseDir = path7.dirname(basePath);
881
986
  const candidates = [
882
- path6.resolve(baseDir, "../../../assets/init"),
883
- path6.resolve(baseDir, "../../assets/init")
987
+ path7.resolve(baseDir, "../../../assets/init"),
988
+ path7.resolve(baseDir, "../../assets/init")
884
989
  ];
885
990
  for (const candidate of candidates) {
886
991
  if (existsSync(candidate)) {
@@ -898,10 +1003,10 @@ function getInitAssetsDir() {
898
1003
 
899
1004
  // src/core/promptsIntegrity.ts
900
1005
  async function diffProjectPromptsAgainstInitAssets(root) {
901
- const promptsDir = path7.resolve(root, ".qfai", "prompts");
1006
+ const promptsDir = path8.resolve(root, ".qfai", "prompts");
902
1007
  let templateDir;
903
1008
  try {
904
- templateDir = path7.join(getInitAssetsDir(), ".qfai", "prompts");
1009
+ templateDir = path8.join(getInitAssetsDir(), ".qfai", "prompts");
905
1010
  } catch {
906
1011
  return {
907
1012
  status: "skipped_missing_assets",
@@ -954,8 +1059,8 @@ async function diffProjectPromptsAgainstInitAssets(root) {
954
1059
  }
955
1060
  try {
956
1061
  const [a, b] = await Promise.all([
957
- readFile3(templateAbs, "utf-8"),
958
- readFile3(projectAbs, "utf-8")
1062
+ readFile4(templateAbs, "utf-8"),
1063
+ readFile4(projectAbs, "utf-8")
959
1064
  ]);
960
1065
  if (normalizeNewlines(a) !== normalizeNewlines(b)) {
961
1066
  changed.push(rel);
@@ -978,7 +1083,7 @@ function normalizeNewlines(text) {
978
1083
  return text.replace(/\r\n/g, "\n");
979
1084
  }
980
1085
  function toRel(base, abs) {
981
- const rel = path7.relative(base, abs);
1086
+ const rel = path8.relative(base, abs);
982
1087
  return rel.replace(/[\\/]+/g, "/");
983
1088
  }
984
1089
  function intersectKeys(a, b) {
@@ -992,16 +1097,16 @@ function intersectKeys(a, b) {
992
1097
  }
993
1098
 
994
1099
  // src/core/version.ts
995
- import { readFile as readFile4 } from "fs/promises";
996
- import path8 from "path";
1100
+ import { readFile as readFile5 } from "fs/promises";
1101
+ import path9 from "path";
997
1102
  import { fileURLToPath as fileURLToPath2 } from "url";
998
1103
  async function resolveToolVersion() {
999
- if ("0.9.0".length > 0) {
1000
- return "0.9.0";
1104
+ if ("1.0.0".length > 0) {
1105
+ return "1.0.0";
1001
1106
  }
1002
1107
  try {
1003
1108
  const packagePath = resolvePackageJsonPath();
1004
- const raw = await readFile4(packagePath, "utf-8");
1109
+ const raw = await readFile5(packagePath, "utf-8");
1005
1110
  const parsed = JSON.parse(raw);
1006
1111
  const version = typeof parsed.version === "string" ? parsed.version : "";
1007
1112
  return version.length > 0 ? version : "unknown";
@@ -1012,7 +1117,7 @@ async function resolveToolVersion() {
1012
1117
  function resolvePackageJsonPath() {
1013
1118
  const base = import.meta.url;
1014
1119
  const basePath = base.startsWith("file:") ? fileURLToPath2(base) : base;
1015
- return path8.resolve(path8.dirname(basePath), "../../package.json");
1120
+ return path9.resolve(path9.dirname(basePath), "../../package.json");
1016
1121
  }
1017
1122
 
1018
1123
  // src/core/doctor.ts
@@ -1038,7 +1143,7 @@ function normalizeGlobs2(values) {
1038
1143
  return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
1039
1144
  }
1040
1145
  async function createDoctorData(options) {
1041
- const startDir = path9.resolve(options.startDir);
1146
+ const startDir = path10.resolve(options.startDir);
1042
1147
  const checks = [];
1043
1148
  const configPath = getConfigPath(startDir);
1044
1149
  const search = options.rootExplicit ? {
@@ -1101,9 +1206,9 @@ async function createDoctorData(options) {
1101
1206
  details: { path: toRelativePath(root, resolved) }
1102
1207
  });
1103
1208
  if (key === "promptsDir") {
1104
- const promptsLocalDir = path9.join(
1105
- path9.dirname(resolved),
1106
- `${path9.basename(resolved)}.local`
1209
+ const promptsLocalDir = path10.join(
1210
+ path10.dirname(resolved),
1211
+ `${path10.basename(resolved)}.local`
1107
1212
  );
1108
1213
  const found = await exists4(promptsLocalDir);
1109
1214
  addCheck(checks, {
@@ -1176,7 +1281,7 @@ async function createDoctorData(options) {
1176
1281
  message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
1177
1282
  details: { specPacks: entries.length, missingFiles }
1178
1283
  });
1179
- const validateJsonAbs = path9.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : path9.resolve(root, config.output.validateJsonPath);
1284
+ const validateJsonAbs = path10.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : path10.resolve(root, config.output.validateJsonPath);
1180
1285
  const validateJsonExists = await exists4(validateJsonAbs);
1181
1286
  addCheck(checks, {
1182
1287
  id: "output.validateJson",
@@ -1186,8 +1291,8 @@ async function createDoctorData(options) {
1186
1291
  details: { path: toRelativePath(root, validateJsonAbs) }
1187
1292
  });
1188
1293
  const outDirAbs = resolvePath(root, config, "outDir");
1189
- const rel = path9.relative(outDirAbs, validateJsonAbs);
1190
- const inside = rel !== "" && !rel.startsWith("..") && !path9.isAbsolute(rel);
1294
+ const rel = path10.relative(outDirAbs, validateJsonAbs);
1295
+ const inside = rel !== "" && !rel.startsWith("..") && !path10.isAbsolute(rel);
1191
1296
  addCheck(checks, {
1192
1297
  id: "output.pathAlignment",
1193
1298
  severity: inside ? "ok" : "warning",
@@ -1293,12 +1398,12 @@ async function detectOutDirCollisions(root) {
1293
1398
  ignore: DEFAULT_CONFIG_SEARCH_IGNORE_GLOBS
1294
1399
  });
1295
1400
  const configRoots = Array.from(
1296
- new Set(configPaths.map((configPath) => path9.dirname(configPath)))
1401
+ new Set(configPaths.map((configPath) => path10.dirname(configPath)))
1297
1402
  ).sort((a, b) => a.localeCompare(b));
1298
1403
  const outDirToRoots = /* @__PURE__ */ new Map();
1299
1404
  for (const configRoot of configRoots) {
1300
1405
  const { config } = await loadConfig(configRoot);
1301
- const outDir = path9.normalize(resolvePath(configRoot, config, "outDir"));
1406
+ const outDir = path10.normalize(resolvePath(configRoot, config, "outDir"));
1302
1407
  const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
1303
1408
  roots.add(configRoot);
1304
1409
  outDirToRoots.set(outDir, roots);
@@ -1315,20 +1420,20 @@ async function detectOutDirCollisions(root) {
1315
1420
  return { monorepoRoot, configRoots, collisions };
1316
1421
  }
1317
1422
  async function findMonorepoRoot(startDir) {
1318
- let current = path9.resolve(startDir);
1423
+ let current = path10.resolve(startDir);
1319
1424
  while (true) {
1320
- const gitPath = path9.join(current, ".git");
1321
- const workspacePath = path9.join(current, "pnpm-workspace.yaml");
1425
+ const gitPath = path10.join(current, ".git");
1426
+ const workspacePath = path10.join(current, "pnpm-workspace.yaml");
1322
1427
  if (await exists4(gitPath) || await exists4(workspacePath)) {
1323
1428
  return current;
1324
1429
  }
1325
- const parent = path9.dirname(current);
1430
+ const parent = path10.dirname(current);
1326
1431
  if (parent === current) {
1327
1432
  break;
1328
1433
  }
1329
1434
  current = parent;
1330
1435
  }
1331
- return path9.resolve(startDir);
1436
+ return path10.resolve(startDir);
1332
1437
  }
1333
1438
 
1334
1439
  // src/cli/lib/logger.ts
@@ -1370,8 +1475,8 @@ async function runDoctor(options) {
1370
1475
  const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
1371
1476
  const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
1372
1477
  if (options.outPath) {
1373
- const outAbs = path10.isAbsolute(options.outPath) ? options.outPath : path10.resolve(process.cwd(), options.outPath);
1374
- await mkdir(path10.dirname(outAbs), { recursive: true });
1478
+ const outAbs = path11.isAbsolute(options.outPath) ? options.outPath : path11.resolve(process.cwd(), options.outPath);
1479
+ await mkdir(path11.dirname(outAbs), { recursive: true });
1375
1480
  await writeFile(outAbs, `${output}
1376
1481
  `, "utf-8");
1377
1482
  info(`doctor: wrote ${outAbs}`);
@@ -1391,11 +1496,11 @@ function shouldFailDoctor(summary, failOn) {
1391
1496
  }
1392
1497
 
1393
1498
  // src/cli/commands/init.ts
1394
- import path12 from "path";
1499
+ import path13 from "path";
1395
1500
 
1396
1501
  // src/cli/lib/fs.ts
1397
1502
  import { access as access5, copyFile, mkdir as mkdir2, readdir as readdir3 } from "fs/promises";
1398
- import path11 from "path";
1503
+ import path12 from "path";
1399
1504
  async function copyTemplateTree(sourceRoot, destRoot, options) {
1400
1505
  const files = await collectTemplateFiles(sourceRoot);
1401
1506
  return copyFiles(files, sourceRoot, destRoot, options);
@@ -1403,7 +1508,7 @@ async function copyTemplateTree(sourceRoot, destRoot, options) {
1403
1508
  async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
1404
1509
  const allFiles = [];
1405
1510
  for (const relPath of relativePaths) {
1406
- const fullPath = path11.join(sourceRoot, relPath);
1511
+ const fullPath = path12.join(sourceRoot, relPath);
1407
1512
  const files = await collectTemplateFiles(fullPath);
1408
1513
  allFiles.push(...files);
1409
1514
  }
@@ -1413,13 +1518,13 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1413
1518
  const copied = [];
1414
1519
  const skipped = [];
1415
1520
  const conflicts = [];
1416
- const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path11.sep);
1417
- const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path11.sep);
1521
+ const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path12.sep);
1522
+ const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path12.sep);
1418
1523
  const isProtectedRelative = (relative) => {
1419
1524
  if (protectPrefixes.length === 0) {
1420
1525
  return false;
1421
1526
  }
1422
- const normalized = relative.replace(/[\\/]+/g, path11.sep);
1527
+ const normalized = relative.replace(/[\\/]+/g, path12.sep);
1423
1528
  return protectPrefixes.some(
1424
1529
  (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1425
1530
  );
@@ -1428,7 +1533,7 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1428
1533
  if (excludePrefixes.length === 0) {
1429
1534
  return false;
1430
1535
  }
1431
- const normalized = relative.replace(/[\\/]+/g, path11.sep);
1536
+ const normalized = relative.replace(/[\\/]+/g, path12.sep);
1432
1537
  return excludePrefixes.some(
1433
1538
  (prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
1434
1539
  );
@@ -1436,14 +1541,14 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1436
1541
  const conflictPolicy = options.conflictPolicy ?? "error";
1437
1542
  if (!options.force && conflictPolicy === "error") {
1438
1543
  for (const file of files) {
1439
- const relative = path11.relative(sourceRoot, file);
1544
+ const relative = path12.relative(sourceRoot, file);
1440
1545
  if (isExcludedRelative(relative)) {
1441
1546
  continue;
1442
1547
  }
1443
1548
  if (isProtectedRelative(relative)) {
1444
1549
  continue;
1445
1550
  }
1446
- const dest = path11.join(destRoot, relative);
1551
+ const dest = path12.join(destRoot, relative);
1447
1552
  if (!await shouldWrite(dest, options.force)) {
1448
1553
  conflicts.push(dest);
1449
1554
  }
@@ -1453,18 +1558,18 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
1453
1558
  }
1454
1559
  }
1455
1560
  for (const file of files) {
1456
- const relative = path11.relative(sourceRoot, file);
1561
+ const relative = path12.relative(sourceRoot, file);
1457
1562
  if (isExcludedRelative(relative)) {
1458
1563
  continue;
1459
1564
  }
1460
- const dest = path11.join(destRoot, relative);
1565
+ const dest = path12.join(destRoot, relative);
1461
1566
  const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
1462
1567
  if (!await shouldWrite(dest, forceForThisFile)) {
1463
1568
  skipped.push(dest);
1464
1569
  continue;
1465
1570
  }
1466
1571
  if (!options.dryRun) {
1467
- await mkdir2(path11.dirname(dest), { recursive: true });
1572
+ await mkdir2(path12.dirname(dest), { recursive: true });
1468
1573
  await copyFile(file, dest);
1469
1574
  }
1470
1575
  copied.push(dest);
@@ -1488,7 +1593,7 @@ async function collectTemplateFiles(root) {
1488
1593
  }
1489
1594
  const items = await readdir3(root, { withFileTypes: true });
1490
1595
  for (const item of items) {
1491
- const fullPath = path11.join(root, item.name);
1596
+ const fullPath = path12.join(root, item.name);
1492
1597
  if (item.isDirectory()) {
1493
1598
  const nested = await collectTemplateFiles(fullPath);
1494
1599
  entries.push(...nested);
@@ -1518,10 +1623,10 @@ async function exists5(target) {
1518
1623
  // src/cli/commands/init.ts
1519
1624
  async function runInit(options) {
1520
1625
  const assetsRoot = getInitAssetsDir();
1521
- const rootAssets = path12.join(assetsRoot, "root");
1522
- const qfaiAssets = path12.join(assetsRoot, ".qfai");
1523
- const destRoot = path12.resolve(options.dir);
1524
- const destQfai = path12.join(destRoot, ".qfai");
1626
+ const rootAssets = path13.join(assetsRoot, "root");
1627
+ const qfaiAssets = path13.join(assetsRoot, ".qfai");
1628
+ const destRoot = path13.resolve(options.dir);
1629
+ const destQfai = path13.join(destRoot, ".qfai");
1525
1630
  if (options.force) {
1526
1631
  info(
1527
1632
  "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"
@@ -1568,8 +1673,8 @@ function report(copied, skipped, dryRun, label) {
1568
1673
  }
1569
1674
 
1570
1675
  // src/cli/commands/report.ts
1571
- import { mkdir as mkdir3, readFile as readFile13, writeFile as writeFile2 } from "fs/promises";
1572
- import path19 from "path";
1676
+ import { mkdir as mkdir3, readFile as readFile14, writeFile as writeFile2 } from "fs/promises";
1677
+ import path20 from "path";
1573
1678
 
1574
1679
  // src/core/normalize.ts
1575
1680
  function normalizeIssuePaths(root, issues) {
@@ -1609,12 +1714,12 @@ function normalizeValidationResult(root, result) {
1609
1714
  }
1610
1715
 
1611
1716
  // src/core/report.ts
1612
- import { readFile as readFile12 } from "fs/promises";
1613
- import path18 from "path";
1717
+ import { readFile as readFile13 } from "fs/promises";
1718
+ import path19 from "path";
1614
1719
 
1615
1720
  // src/core/contractIndex.ts
1616
- import { readFile as readFile5 } from "fs/promises";
1617
- import path13 from "path";
1721
+ import { readFile as readFile6 } from "fs/promises";
1722
+ import path14 from "path";
1618
1723
 
1619
1724
  // src/core/contractsDecl.ts
1620
1725
  var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
@@ -1636,9 +1741,9 @@ function stripContractDeclarationLines(text) {
1636
1741
  // src/core/contractIndex.ts
1637
1742
  async function buildContractIndex(root, config) {
1638
1743
  const contractsRoot = resolvePath(root, config, "contractsDir");
1639
- const uiRoot = path13.join(contractsRoot, "ui");
1640
- const apiRoot = path13.join(contractsRoot, "api");
1641
- const dbRoot = path13.join(contractsRoot, "db");
1744
+ const uiRoot = path14.join(contractsRoot, "ui");
1745
+ const apiRoot = path14.join(contractsRoot, "api");
1746
+ const dbRoot = path14.join(contractsRoot, "db");
1642
1747
  const [uiFiles, apiFiles, dbFiles] = await Promise.all([
1643
1748
  collectUiContractFiles(uiRoot),
1644
1749
  collectApiContractFiles(apiRoot),
@@ -1656,7 +1761,7 @@ async function buildContractIndex(root, config) {
1656
1761
  }
1657
1762
  async function indexContractFiles(files, index) {
1658
1763
  for (const file of files) {
1659
- const text = await readFile5(file, "utf-8");
1764
+ const text = await readFile6(file, "utf-8");
1660
1765
  extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
1661
1766
  }
1662
1767
  }
@@ -1891,14 +1996,14 @@ function parseSpec(md, file) {
1891
1996
  }
1892
1997
 
1893
1998
  // src/core/validators/contracts.ts
1894
- import { readFile as readFile6 } from "fs/promises";
1895
- import path15 from "path";
1999
+ import { readFile as readFile7 } from "fs/promises";
2000
+ import path16 from "path";
1896
2001
 
1897
2002
  // src/core/contracts.ts
1898
- import path14 from "path";
2003
+ import path15 from "path";
1899
2004
  import { parse as parseYaml2 } from "yaml";
1900
2005
  function parseStructuredContract(file, text) {
1901
- const ext = path14.extname(file).toLowerCase();
2006
+ const ext = path15.extname(file).toLowerCase();
1902
2007
  if (ext === ".json") {
1903
2008
  return JSON.parse(text);
1904
2009
  }
@@ -1918,9 +2023,9 @@ var SQL_DANGEROUS_PATTERNS = [
1918
2023
  async function validateContracts(root, config) {
1919
2024
  const issues = [];
1920
2025
  const contractsRoot = resolvePath(root, config, "contractsDir");
1921
- issues.push(...await validateUiContracts(path15.join(contractsRoot, "ui")));
1922
- issues.push(...await validateApiContracts(path15.join(contractsRoot, "api")));
1923
- issues.push(...await validateDbContracts(path15.join(contractsRoot, "db")));
2026
+ issues.push(...await validateUiContracts(path16.join(contractsRoot, "ui")));
2027
+ issues.push(...await validateApiContracts(path16.join(contractsRoot, "api")));
2028
+ issues.push(...await validateDbContracts(path16.join(contractsRoot, "db")));
1924
2029
  const contractIndex = await buildContractIndex(root, config);
1925
2030
  issues.push(...validateDuplicateContractIds(contractIndex));
1926
2031
  return issues;
@@ -1940,7 +2045,7 @@ async function validateUiContracts(uiRoot) {
1940
2045
  }
1941
2046
  const issues = [];
1942
2047
  for (const file of files) {
1943
- const text = await readFile6(file, "utf-8");
2048
+ const text = await readFile7(file, "utf-8");
1944
2049
  const invalidIds = extractInvalidIds(text, [
1945
2050
  "SPEC",
1946
2051
  "BR",
@@ -1995,7 +2100,7 @@ async function validateApiContracts(apiRoot) {
1995
2100
  }
1996
2101
  const issues = [];
1997
2102
  for (const file of files) {
1998
- const text = await readFile6(file, "utf-8");
2103
+ const text = await readFile7(file, "utf-8");
1999
2104
  const invalidIds = extractInvalidIds(text, [
2000
2105
  "SPEC",
2001
2106
  "BR",
@@ -2063,7 +2168,7 @@ async function validateDbContracts(dbRoot) {
2063
2168
  }
2064
2169
  const issues = [];
2065
2170
  for (const file of files) {
2066
- const text = await readFile6(file, "utf-8");
2171
+ const text = await readFile7(file, "utf-8");
2067
2172
  const invalidIds = extractInvalidIds(text, [
2068
2173
  "SPEC",
2069
2174
  "BR",
@@ -2206,8 +2311,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
2206
2311
  }
2207
2312
 
2208
2313
  // src/core/validators/delta.ts
2209
- import { readFile as readFile7 } from "fs/promises";
2210
- import path16 from "path";
2314
+ import { readFile as readFile8 } from "fs/promises";
2315
+ import path17 from "path";
2211
2316
  var SECTION_RE = /^##\s+変更区分/m;
2212
2317
  var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
2213
2318
  var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
@@ -2221,10 +2326,10 @@ async function validateDeltas(root, config) {
2221
2326
  }
2222
2327
  const issues = [];
2223
2328
  for (const pack of packs) {
2224
- const deltaPath = path16.join(pack, "delta.md");
2329
+ const deltaPath = path17.join(pack, "delta.md");
2225
2330
  let text;
2226
2331
  try {
2227
- text = await readFile7(deltaPath, "utf-8");
2332
+ text = await readFile8(deltaPath, "utf-8");
2228
2333
  } catch (error2) {
2229
2334
  if (isMissingFileError2(error2)) {
2230
2335
  issues.push(
@@ -2300,8 +2405,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
2300
2405
  }
2301
2406
 
2302
2407
  // src/core/validators/ids.ts
2303
- import { readFile as readFile8 } from "fs/promises";
2304
- import path17 from "path";
2408
+ import { readFile as readFile9 } from "fs/promises";
2409
+ import path18 from "path";
2305
2410
  var SC_TAG_RE3 = /^SC-\d{4}$/;
2306
2411
  async function validateDefinedIds(root, config) {
2307
2412
  const issues = [];
@@ -2336,7 +2441,7 @@ async function validateDefinedIds(root, config) {
2336
2441
  }
2337
2442
  async function collectSpecDefinitionIds(files, out) {
2338
2443
  for (const file of files) {
2339
- const text = await readFile8(file, "utf-8");
2444
+ const text = await readFile9(file, "utf-8");
2340
2445
  const parsed = parseSpec(text, file);
2341
2446
  if (parsed.specId) {
2342
2447
  recordId(out, parsed.specId, file);
@@ -2346,7 +2451,7 @@ async function collectSpecDefinitionIds(files, out) {
2346
2451
  }
2347
2452
  async function collectScenarioDefinitionIds(files, out) {
2348
2453
  for (const file of files) {
2349
- const text = await readFile8(file, "utf-8");
2454
+ const text = await readFile9(file, "utf-8");
2350
2455
  const { document, errors } = parseScenarioDocument(text, file);
2351
2456
  if (!document || errors.length > 0) {
2352
2457
  continue;
@@ -2367,7 +2472,7 @@ function recordId(out, id, file) {
2367
2472
  }
2368
2473
  function formatFileList(files, root) {
2369
2474
  return files.map((file) => {
2370
- const relative = path17.relative(root, file);
2475
+ const relative = path18.relative(root, file);
2371
2476
  return relative.length > 0 ? relative : file;
2372
2477
  }).join(", ");
2373
2478
  }
@@ -2425,7 +2530,7 @@ async function validatePromptsIntegrity(root) {
2425
2530
  }
2426
2531
 
2427
2532
  // src/core/validators/scenario.ts
2428
- import { readFile as readFile9 } from "fs/promises";
2533
+ import { readFile as readFile10 } from "fs/promises";
2429
2534
  var GIVEN_PATTERN = /\bGiven\b/;
2430
2535
  var WHEN_PATTERN = /\bWhen\b/;
2431
2536
  var THEN_PATTERN = /\bThen\b/;
@@ -2451,7 +2556,7 @@ async function validateScenarios(root, config) {
2451
2556
  for (const entry of entries) {
2452
2557
  let text;
2453
2558
  try {
2454
- text = await readFile9(entry.scenarioPath, "utf-8");
2559
+ text = await readFile10(entry.scenarioPath, "utf-8");
2455
2560
  } catch (error2) {
2456
2561
  if (isMissingFileError3(error2)) {
2457
2562
  issues.push(
@@ -2625,7 +2730,7 @@ function isMissingFileError3(error2) {
2625
2730
  }
2626
2731
 
2627
2732
  // src/core/validators/spec.ts
2628
- import { readFile as readFile10 } from "fs/promises";
2733
+ import { readFile as readFile11 } from "fs/promises";
2629
2734
  async function validateSpecs(root, config) {
2630
2735
  const specsRoot = resolvePath(root, config, "specsDir");
2631
2736
  const entries = await collectSpecEntries(specsRoot);
@@ -2646,7 +2751,7 @@ async function validateSpecs(root, config) {
2646
2751
  for (const entry of entries) {
2647
2752
  let text;
2648
2753
  try {
2649
- text = await readFile10(entry.specPath, "utf-8");
2754
+ text = await readFile11(entry.specPath, "utf-8");
2650
2755
  } catch (error2) {
2651
2756
  if (isMissingFileError4(error2)) {
2652
2757
  issues.push(
@@ -2799,7 +2904,7 @@ function isMissingFileError4(error2) {
2799
2904
  }
2800
2905
 
2801
2906
  // src/core/validators/traceability.ts
2802
- import { readFile as readFile11 } from "fs/promises";
2907
+ import { readFile as readFile12 } from "fs/promises";
2803
2908
  var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
2804
2909
  var BR_TAG_RE2 = /^BR-\d{4}$/;
2805
2910
  async function validateTraceability(root, config) {
@@ -2819,7 +2924,7 @@ async function validateTraceability(root, config) {
2819
2924
  const contractIndex = await buildContractIndex(root, config);
2820
2925
  const contractIds = contractIndex.ids;
2821
2926
  for (const file of specFiles) {
2822
- const text = await readFile11(file, "utf-8");
2927
+ const text = await readFile12(file, "utf-8");
2823
2928
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2824
2929
  const parsed = parseSpec(text, file);
2825
2930
  if (parsed.specId) {
@@ -2892,7 +2997,7 @@ async function validateTraceability(root, config) {
2892
2997
  }
2893
2998
  }
2894
2999
  for (const file of scenarioFiles) {
2895
- const text = await readFile11(file, "utf-8");
3000
+ const text = await readFile12(file, "utf-8");
2896
3001
  extractAllIds(text).forEach((id) => upstreamIds.add(id));
2897
3002
  const scenarioContractRefs = parseContractRefs(text, {
2898
3003
  allowCommentPrefix: true
@@ -3214,7 +3319,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
3214
3319
  const pattern = buildIdPattern(Array.from(upstreamIds));
3215
3320
  let found = false;
3216
3321
  for (const file of targetFiles) {
3217
- const text = await readFile11(file, "utf-8");
3322
+ const text = await readFile12(file, "utf-8");
3218
3323
  if (pattern.test(text)) {
3219
3324
  found = true;
3220
3325
  break;
@@ -3306,15 +3411,15 @@ function countIssues(issues) {
3306
3411
  // src/core/report.ts
3307
3412
  var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
3308
3413
  async function createReportData(root, validation, configResult) {
3309
- const resolvedRoot = path18.resolve(root);
3414
+ const resolvedRoot = path19.resolve(root);
3310
3415
  const resolved = configResult ?? await loadConfig(resolvedRoot);
3311
3416
  const config = resolved.config;
3312
3417
  const configPath = resolved.configPath;
3313
3418
  const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
3314
3419
  const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
3315
- const apiRoot = path18.join(contractsRoot, "api");
3316
- const uiRoot = path18.join(contractsRoot, "ui");
3317
- const dbRoot = path18.join(contractsRoot, "db");
3420
+ const apiRoot = path19.join(contractsRoot, "api");
3421
+ const uiRoot = path19.join(contractsRoot, "ui");
3422
+ const dbRoot = path19.join(contractsRoot, "db");
3318
3423
  const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
3319
3424
  const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
3320
3425
  const specFiles = await collectSpecFiles(specsRoot);
@@ -3785,7 +3890,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
3785
3890
  idToSpecs.set(contractId, /* @__PURE__ */ new Set());
3786
3891
  }
3787
3892
  for (const file of specFiles) {
3788
- const text = await readFile12(file, "utf-8");
3893
+ const text = await readFile13(file, "utf-8");
3789
3894
  const parsed = parseSpec(text, file);
3790
3895
  const specKey = parsed.specId;
3791
3896
  if (!specKey) {
@@ -3826,7 +3931,7 @@ async function collectIds(files) {
3826
3931
  DB: /* @__PURE__ */ new Set()
3827
3932
  };
3828
3933
  for (const file of files) {
3829
- const text = await readFile12(file, "utf-8");
3934
+ const text = await readFile13(file, "utf-8");
3830
3935
  for (const prefix of ID_PREFIXES2) {
3831
3936
  const ids = extractIds(text, prefix);
3832
3937
  ids.forEach((id) => result[prefix].add(id));
@@ -3844,7 +3949,7 @@ async function collectIds(files) {
3844
3949
  async function collectUpstreamIds(files) {
3845
3950
  const ids = /* @__PURE__ */ new Set();
3846
3951
  for (const file of files) {
3847
- const text = await readFile12(file, "utf-8");
3952
+ const text = await readFile13(file, "utf-8");
3848
3953
  extractAllIds(text).forEach((id) => ids.add(id));
3849
3954
  }
3850
3955
  return ids;
@@ -3865,7 +3970,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
3865
3970
  }
3866
3971
  const pattern = buildIdPattern2(Array.from(upstreamIds));
3867
3972
  for (const file of targetFiles) {
3868
- const text = await readFile12(file, "utf-8");
3973
+ const text = await readFile13(file, "utf-8");
3869
3974
  if (pattern.test(text)) {
3870
3975
  return true;
3871
3976
  }
@@ -3957,7 +4062,7 @@ function buildHotspots(issues) {
3957
4062
 
3958
4063
  // src/cli/commands/report.ts
3959
4064
  async function runReport(options) {
3960
- const root = path19.resolve(options.root);
4065
+ const root = path20.resolve(options.root);
3961
4066
  const configResult = await loadConfig(root);
3962
4067
  let validation;
3963
4068
  if (options.runValidate) {
@@ -3974,7 +4079,7 @@ async function runReport(options) {
3974
4079
  validation = normalized;
3975
4080
  } else {
3976
4081
  const input = options.inputPath ?? configResult.config.output.validateJsonPath;
3977
- const inputPath = path19.isAbsolute(input) ? input : path19.resolve(root, input);
4082
+ const inputPath = path20.isAbsolute(input) ? input : path20.resolve(root, input);
3978
4083
  try {
3979
4084
  validation = await readValidationResult(inputPath);
3980
4085
  } catch (err) {
@@ -4000,10 +4105,10 @@ async function runReport(options) {
4000
4105
  const data = await createReportData(root, validation, configResult);
4001
4106
  const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
4002
4107
  const outRoot = resolvePath(root, configResult.config, "outDir");
4003
- const defaultOut = options.format === "json" ? path19.join(outRoot, "report.json") : path19.join(outRoot, "report.md");
4108
+ const defaultOut = options.format === "json" ? path20.join(outRoot, "report.json") : path20.join(outRoot, "report.md");
4004
4109
  const out = options.outPath ?? defaultOut;
4005
- const outPath = path19.isAbsolute(out) ? out : path19.resolve(root, out);
4006
- await mkdir3(path19.dirname(outPath), { recursive: true });
4110
+ const outPath = path20.isAbsolute(out) ? out : path20.resolve(root, out);
4111
+ await mkdir3(path20.dirname(outPath), { recursive: true });
4007
4112
  await writeFile2(outPath, `${output}
4008
4113
  `, "utf-8");
4009
4114
  info(
@@ -4012,7 +4117,7 @@ async function runReport(options) {
4012
4117
  info(`wrote report: ${outPath}`);
4013
4118
  }
4014
4119
  async function readValidationResult(inputPath) {
4015
- const raw = await readFile13(inputPath, "utf-8");
4120
+ const raw = await readFile14(inputPath, "utf-8");
4016
4121
  const parsed = JSON.parse(raw);
4017
4122
  if (!isValidationResult(parsed)) {
4018
4123
  throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
@@ -4068,15 +4173,15 @@ function isMissingFileError5(error2) {
4068
4173
  return record2.code === "ENOENT";
4069
4174
  }
4070
4175
  async function writeValidationResult(root, outputPath, result) {
4071
- const abs = path19.isAbsolute(outputPath) ? outputPath : path19.resolve(root, outputPath);
4072
- await mkdir3(path19.dirname(abs), { recursive: true });
4176
+ const abs = path20.isAbsolute(outputPath) ? outputPath : path20.resolve(root, outputPath);
4177
+ await mkdir3(path20.dirname(abs), { recursive: true });
4073
4178
  await writeFile2(abs, `${JSON.stringify(result, null, 2)}
4074
4179
  `, "utf-8");
4075
4180
  }
4076
4181
 
4077
4182
  // src/cli/commands/validate.ts
4078
4183
  import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
4079
- import path20 from "path";
4184
+ import path21 from "path";
4080
4185
 
4081
4186
  // src/cli/lib/failOn.ts
4082
4187
  function shouldFail(result, failOn) {
@@ -4091,7 +4196,7 @@ function shouldFail(result, failOn) {
4091
4196
 
4092
4197
  // src/cli/commands/validate.ts
4093
4198
  async function runValidate(options) {
4094
- const root = path20.resolve(options.root);
4199
+ const root = path21.resolve(options.root);
4095
4200
  const configResult = await loadConfig(root);
4096
4201
  const result = await validateProject(root, configResult);
4097
4202
  const normalized = normalizeValidationResult(root, result);
@@ -4215,12 +4320,12 @@ function issueKey(issue7) {
4215
4320
  }
4216
4321
  async function emitJson(result, root, jsonPath) {
4217
4322
  const abs = resolveJsonPath(root, jsonPath);
4218
- await mkdir4(path20.dirname(abs), { recursive: true });
4323
+ await mkdir4(path21.dirname(abs), { recursive: true });
4219
4324
  await writeFile3(abs, `${JSON.stringify(result, null, 2)}
4220
4325
  `, "utf-8");
4221
4326
  }
4222
4327
  function resolveJsonPath(root, jsonPath) {
4223
- return path20.isAbsolute(jsonPath) ? jsonPath : path20.resolve(root, jsonPath);
4328
+ return path21.isAbsolute(jsonPath) ? jsonPath : path21.resolve(root, jsonPath);
4224
4329
  }
4225
4330
  var GITHUB_ANNOTATION_LIMIT = 100;
4226
4331
 
@@ -4233,6 +4338,7 @@ function parseArgs(argv, cwd) {
4233
4338
  force: false,
4234
4339
  yes: false,
4235
4340
  dryRun: false,
4341
+ analyzeList: false,
4236
4342
  reportFormat: "md",
4237
4343
  reportRunValidate: false,
4238
4344
  doctorFormat: "text",
@@ -4242,6 +4348,7 @@ function parseArgs(argv, cwd) {
4242
4348
  };
4243
4349
  const args = [...argv];
4244
4350
  let command = args.shift() ?? null;
4351
+ let invalid = false;
4245
4352
  if (command === "--help" || command === "-h") {
4246
4353
  options.help = true;
4247
4354
  command = null;
@@ -4250,13 +4357,29 @@ function parseArgs(argv, cwd) {
4250
4357
  const arg = args[i];
4251
4358
  switch (arg) {
4252
4359
  case "--root":
4253
- options.root = args[i + 1] ?? options.root;
4254
- options.rootExplicit = true;
4255
- i += 1;
4360
+ {
4361
+ const next = readOptionValue(args, i);
4362
+ if (next === null) {
4363
+ invalid = true;
4364
+ options.help = true;
4365
+ break;
4366
+ }
4367
+ options.root = next;
4368
+ options.rootExplicit = true;
4369
+ i += 1;
4370
+ }
4256
4371
  break;
4257
4372
  case "--dir":
4258
- options.dir = args[i + 1] ?? options.dir;
4259
- i += 1;
4373
+ {
4374
+ const next = readOptionValue(args, i);
4375
+ if (next === null) {
4376
+ invalid = true;
4377
+ options.help = true;
4378
+ break;
4379
+ }
4380
+ options.dir = next;
4381
+ i += 1;
4382
+ }
4260
4383
  break;
4261
4384
  case "--force":
4262
4385
  options.force = true;
@@ -4267,8 +4390,25 @@ function parseArgs(argv, cwd) {
4267
4390
  case "--dry-run":
4268
4391
  options.dryRun = true;
4269
4392
  break;
4393
+ case "--list":
4394
+ options.analyzeList = true;
4395
+ break;
4396
+ case "--prompt":
4397
+ {
4398
+ const next = readOptionValue(args, i);
4399
+ if (next) {
4400
+ options.analyzePrompt = next;
4401
+ i += 1;
4402
+ }
4403
+ }
4404
+ break;
4270
4405
  case "--format": {
4271
- const next = args[i + 1];
4406
+ const next = readOptionValue(args, i);
4407
+ if (next === null) {
4408
+ invalid = true;
4409
+ options.help = true;
4410
+ break;
4411
+ }
4272
4412
  applyFormatOption(command, next, options);
4273
4413
  i += 1;
4274
4414
  break;
@@ -4277,35 +4417,44 @@ function parseArgs(argv, cwd) {
4277
4417
  options.strict = true;
4278
4418
  break;
4279
4419
  case "--fail-on": {
4280
- const next = args[i + 1];
4420
+ const next = readOptionValue(args, i);
4421
+ if (next === null) {
4422
+ invalid = true;
4423
+ options.help = true;
4424
+ break;
4425
+ }
4281
4426
  if (next === "never" || next === "warning" || next === "error") {
4282
4427
  options.failOn = next;
4283
4428
  }
4284
4429
  i += 1;
4285
4430
  break;
4286
4431
  }
4287
- case "--out":
4288
- {
4289
- const next = args[i + 1];
4290
- if (next) {
4291
- if (command === "doctor") {
4292
- options.doctorOut = next;
4293
- } else {
4294
- options.reportOut = next;
4295
- }
4296
- }
4432
+ case "--out": {
4433
+ const next = readOptionValue(args, i);
4434
+ if (next === null) {
4435
+ invalid = true;
4436
+ options.help = true;
4437
+ break;
4438
+ }
4439
+ if (command === "doctor") {
4440
+ options.doctorOut = next;
4441
+ } else {
4442
+ options.reportOut = next;
4297
4443
  }
4298
4444
  i += 1;
4299
4445
  break;
4300
- case "--in":
4301
- {
4302
- const next = args[i + 1];
4303
- if (next) {
4304
- options.reportIn = next;
4305
- }
4446
+ }
4447
+ case "--in": {
4448
+ const next = readOptionValue(args, i);
4449
+ if (next === null) {
4450
+ invalid = true;
4451
+ options.help = true;
4452
+ break;
4306
4453
  }
4454
+ options.reportIn = next;
4307
4455
  i += 1;
4308
4456
  break;
4457
+ }
4309
4458
  case "--run-validate":
4310
4459
  options.reportRunValidate = true;
4311
4460
  break;
@@ -4317,7 +4466,14 @@ function parseArgs(argv, cwd) {
4317
4466
  break;
4318
4467
  }
4319
4468
  }
4320
- return { command, options };
4469
+ return { command, invalid, options };
4470
+ }
4471
+ function readOptionValue(args, index) {
4472
+ const next = args[index + 1];
4473
+ if (!next || next.startsWith("--")) {
4474
+ return null;
4475
+ }
4476
+ return next;
4321
4477
  }
4322
4478
  function applyFormatOption(command, value, options) {
4323
4479
  if (!value) {
@@ -4351,9 +4507,12 @@ function applyFormatOption(command, value, options) {
4351
4507
 
4352
4508
  // src/cli/main.ts
4353
4509
  async function run(argv, cwd) {
4354
- const { command, options } = parseArgs(argv, cwd);
4510
+ const { command, invalid, options } = parseArgs(argv, cwd);
4355
4511
  if (!command || options.help) {
4356
4512
  info(usage());
4513
+ if (invalid) {
4514
+ process.exitCode = 1;
4515
+ }
4357
4516
  return;
4358
4517
  }
4359
4518
  switch (command) {
@@ -4365,6 +4524,17 @@ async function run(argv, cwd) {
4365
4524
  yes: options.yes
4366
4525
  });
4367
4526
  return;
4527
+ case "analyze":
4528
+ {
4529
+ const resolvedRoot = await resolveRoot(options);
4530
+ const exitCode = await runAnalyze({
4531
+ root: resolvedRoot,
4532
+ list: options.analyzeList,
4533
+ ...options.analyzePrompt !== void 0 ? { prompt: options.analyzePrompt } : {}
4534
+ });
4535
+ process.exitCode = exitCode;
4536
+ }
4537
+ return;
4368
4538
  case "validate":
4369
4539
  {
4370
4540
  const resolvedRoot = await resolveRoot(options);
@@ -4411,6 +4581,7 @@ function usage() {
4411
4581
 
4412
4582
  Commands:
4413
4583
  init \u30C6\u30F3\u30D7\u30EC\u3092\u751F\u6210
4584
+ analyze \u610F\u5473\u30EC\u30D9\u30EB\u306E\u30EC\u30D3\u30E5\u30FC\u88DC\u52A9\uFF08\u30D7\u30ED\u30F3\u30D7\u30C8\u51FA\u529B\uFF09
4414
4585
  validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
4415
4586
  report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
4416
4587
  doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
@@ -4421,6 +4592,8 @@ Options:
4421
4592
  --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
4422
4593
  --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
4423
4594
  --dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
4595
+ --list analyze: \u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7\u3092\u8868\u793A
4596
+ --prompt <name> analyze: \u6307\u5B9A\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08.md\u7701\u7565\u53EF\uFF09\u3092\u51FA\u529B
4424
4597
  --format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
4425
4598
  --format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
4426
4599
  --format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F