qfai 0.2.5

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.
Files changed (120) hide show
  1. package/README.md +28 -0
  2. package/assets/init/qfai/README.md +6 -0
  3. package/assets/init/qfai/contracts/api/api-0001-sample.yaml +14 -0
  4. package/assets/init/qfai/contracts/db/db-0001-sample.sql +5 -0
  5. package/assets/init/qfai/contracts/ui/ui-0001-sample.yaml +4 -0
  6. package/assets/init/qfai/prompts/makeBusinessFlow.md +34 -0
  7. package/assets/init/qfai/prompts/makeOverview.md +27 -0
  8. package/assets/init/qfai/spec/decisions/ADR-0001.md +7 -0
  9. package/assets/init/qfai/spec/scenarios.feature +6 -0
  10. package/assets/init/qfai/spec/spec-0001-sample.md +29 -0
  11. package/assets/init/root/.github/workflows/qfai.yml +22 -0
  12. package/assets/init/root/qfai.config.yaml +29 -0
  13. package/dist/cli/commands/init.d.ts +8 -0
  14. package/dist/cli/commands/init.d.ts.map +1 -0
  15. package/dist/cli/commands/init.js +30 -0
  16. package/dist/cli/commands/init.js.map +1 -0
  17. package/dist/cli/commands/report.d.ts +8 -0
  18. package/dist/cli/commands/report.d.ts.map +1 -0
  19. package/dist/cli/commands/report.js +83 -0
  20. package/dist/cli/commands/report.js.map +1 -0
  21. package/dist/cli/commands/validate.d.ts +10 -0
  22. package/dist/cli/commands/validate.d.ts.map +1 -0
  23. package/dist/cli/commands/validate.js +66 -0
  24. package/dist/cli/commands/validate.js.map +1 -0
  25. package/dist/cli/index.cjs +2003 -0
  26. package/dist/cli/index.cjs.map +1 -0
  27. package/dist/cli/index.d.cts +1 -0
  28. package/dist/cli/index.d.ts +3 -0
  29. package/dist/cli/index.d.ts.map +1 -0
  30. package/dist/cli/index.js +7 -0
  31. package/dist/cli/index.js.map +1 -0
  32. package/dist/cli/index.mjs +1980 -0
  33. package/dist/cli/index.mjs.map +1 -0
  34. package/dist/cli/lib/args.d.ts +19 -0
  35. package/dist/cli/lib/args.d.ts.map +1 -0
  36. package/dist/cli/lib/args.js +107 -0
  37. package/dist/cli/lib/args.js.map +1 -0
  38. package/dist/cli/lib/assets.d.ts +2 -0
  39. package/dist/cli/lib/assets.d.ts.map +1 -0
  40. package/dist/cli/lib/assets.js +8 -0
  41. package/dist/cli/lib/assets.js.map +1 -0
  42. package/dist/cli/lib/failOn.d.ts +5 -0
  43. package/dist/cli/lib/failOn.d.ts.map +1 -0
  44. package/dist/cli/lib/failOn.js +10 -0
  45. package/dist/cli/lib/failOn.js.map +1 -0
  46. package/dist/cli/lib/fs.d.ts +11 -0
  47. package/dist/cli/lib/fs.d.ts.map +1 -0
  48. package/dist/cli/lib/fs.js +91 -0
  49. package/dist/cli/lib/fs.js.map +1 -0
  50. package/dist/cli/lib/logger.d.ts +4 -0
  51. package/dist/cli/lib/logger.d.ts.map +1 -0
  52. package/dist/cli/lib/logger.js +10 -0
  53. package/dist/cli/lib/logger.js.map +1 -0
  54. package/dist/cli/main.d.ts +2 -0
  55. package/dist/cli/main.d.ts.map +1 -0
  56. package/dist/cli/main.js +73 -0
  57. package/dist/cli/main.js.map +1 -0
  58. package/dist/core/config.d.ts +46 -0
  59. package/dist/core/config.d.ts.map +1 -0
  60. package/dist/core/config.js +224 -0
  61. package/dist/core/config.js.map +1 -0
  62. package/dist/core/discovery.d.ts +11 -0
  63. package/dist/core/discovery.d.ts.map +1 -0
  64. package/dist/core/discovery.js +31 -0
  65. package/dist/core/discovery.js.map +1 -0
  66. package/dist/core/fs.d.ts +6 -0
  67. package/dist/core/fs.d.ts.map +1 -0
  68. package/dist/core/fs.js +55 -0
  69. package/dist/core/fs.js.map +1 -0
  70. package/dist/core/ids.d.ts +5 -0
  71. package/dist/core/ids.d.ts.map +1 -0
  72. package/dist/core/ids.js +49 -0
  73. package/dist/core/ids.js.map +1 -0
  74. package/dist/core/index.d.ts +11 -0
  75. package/dist/core/index.d.ts.map +1 -0
  76. package/dist/core/index.js +11 -0
  77. package/dist/core/index.js.map +1 -0
  78. package/dist/core/report.d.ts +41 -0
  79. package/dist/core/report.d.ts.map +1 -0
  80. package/dist/core/report.js +238 -0
  81. package/dist/core/report.js.map +1 -0
  82. package/dist/core/types.d.ts +27 -0
  83. package/dist/core/types.d.ts.map +1 -0
  84. package/dist/core/types.js +2 -0
  85. package/dist/core/types.js.map +1 -0
  86. package/dist/core/validate.d.ts +4 -0
  87. package/dist/core/validate.d.ts.map +1 -0
  88. package/dist/core/validate.js +32 -0
  89. package/dist/core/validate.js.map +1 -0
  90. package/dist/core/validators/contracts.d.ts +5 -0
  91. package/dist/core/validators/contracts.d.ts.map +1 -0
  92. package/dist/core/validators/contracts.js +157 -0
  93. package/dist/core/validators/contracts.js.map +1 -0
  94. package/dist/core/validators/scenario.d.ts +5 -0
  95. package/dist/core/validators/scenario.d.ts.map +1 -0
  96. package/dist/core/validators/scenario.js +82 -0
  97. package/dist/core/validators/scenario.js.map +1 -0
  98. package/dist/core/validators/spec.d.ts +5 -0
  99. package/dist/core/validators/spec.d.ts.map +1 -0
  100. package/dist/core/validators/spec.js +69 -0
  101. package/dist/core/validators/spec.js.map +1 -0
  102. package/dist/core/validators/traceability.d.ts +4 -0
  103. package/dist/core/validators/traceability.d.ts.map +1 -0
  104. package/dist/core/validators/traceability.js +148 -0
  105. package/dist/core/validators/traceability.js.map +1 -0
  106. package/dist/core/version.d.ts +2 -0
  107. package/dist/core/version.d.ts.map +1 -0
  108. package/dist/core/version.js +25 -0
  109. package/dist/core/version.js.map +1 -0
  110. package/dist/index.cjs +1579 -0
  111. package/dist/index.cjs.map +1 -0
  112. package/dist/index.d.cts +132 -0
  113. package/dist/index.d.ts +2 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +2 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/index.mjs +1523 -0
  118. package/dist/index.mjs.map +1 -0
  119. package/dist/tsconfig.tsbuildinfo +1 -0
  120. package/package.json +38 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1579 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ VALIDATION_SCHEMA_VERSION: () => VALIDATION_SCHEMA_VERSION,
34
+ createReportData: () => createReportData,
35
+ defaultConfig: () => defaultConfig,
36
+ extractAllIds: () => extractAllIds,
37
+ extractIds: () => extractIds,
38
+ extractInvalidIds: () => extractInvalidIds,
39
+ formatReportJson: () => formatReportJson,
40
+ formatReportMarkdown: () => formatReportMarkdown,
41
+ getConfigPath: () => getConfigPath,
42
+ lintSql: () => lintSql,
43
+ loadConfig: () => loadConfig,
44
+ resolvePath: () => resolvePath,
45
+ resolveToolVersion: () => resolveToolVersion,
46
+ validateContracts: () => validateContracts,
47
+ validateProject: () => validateProject,
48
+ validateScenarioContent: () => validateScenarioContent,
49
+ validateScenarios: () => validateScenarios,
50
+ validateSpecContent: () => validateSpecContent,
51
+ validateSpecs: () => validateSpecs,
52
+ validateTraceability: () => validateTraceability
53
+ });
54
+ module.exports = __toCommonJS(src_exports);
55
+
56
+ // src/core/config.ts
57
+ var import_promises = require("fs/promises");
58
+ var import_node_path = __toESM(require("path"), 1);
59
+ var import_yaml = require("yaml");
60
+ var defaultConfig = {
61
+ paths: {
62
+ specDir: "qfai/spec",
63
+ decisionsDir: "qfai/spec/decisions",
64
+ scenariosDir: "qfai/spec",
65
+ rulesDir: "qfai/rules",
66
+ contractsDir: "qfai/contracts",
67
+ uiContractsDir: "qfai/contracts/ui",
68
+ apiContractsDir: "qfai/contracts/api",
69
+ dataContractsDir: "qfai/contracts/db",
70
+ srcDir: "src",
71
+ testsDir: "tests"
72
+ },
73
+ validation: {
74
+ failOn: "error",
75
+ require: {
76
+ specSections: [
77
+ "\u80CC\u666F",
78
+ "\u30B9\u30B3\u30FC\u30D7",
79
+ "\u975E\u30B4\u30FC\u30EB",
80
+ "\u7528\u8A9E",
81
+ "\u524D\u63D0",
82
+ "\u6C7A\u5B9A\u4E8B\u9805",
83
+ "\u696D\u52D9\u30EB\u30FC\u30EB"
84
+ ]
85
+ },
86
+ traceability: {
87
+ brMustHaveSc: true,
88
+ scMustTouchContracts: true,
89
+ allowOrphanContracts: false
90
+ }
91
+ },
92
+ output: {
93
+ format: "text",
94
+ jsonPath: ".qfai/out/validate.json"
95
+ }
96
+ };
97
+ function getConfigPath(root) {
98
+ return import_node_path.default.join(root, "qfai.config.yaml");
99
+ }
100
+ async function loadConfig(root) {
101
+ const configPath = getConfigPath(root);
102
+ const issues = [];
103
+ let parsed;
104
+ try {
105
+ const raw = await (0, import_promises.readFile)(configPath, "utf-8");
106
+ parsed = (0, import_yaml.parse)(raw);
107
+ } catch (error) {
108
+ if (isMissingFile(error)) {
109
+ return { config: defaultConfig, issues, configPath };
110
+ }
111
+ issues.push(configIssue(configPath, formatError(error)));
112
+ return { config: defaultConfig, issues, configPath };
113
+ }
114
+ const normalized = normalizeConfig(parsed, configPath, issues);
115
+ return { config: normalized, issues, configPath };
116
+ }
117
+ function resolvePath(root, config, key) {
118
+ return import_node_path.default.resolve(root, config.paths[key]);
119
+ }
120
+ function normalizeConfig(raw, configPath, issues) {
121
+ if (!isRecord(raw)) {
122
+ issues.push(configIssue(configPath, "\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059\u3002"));
123
+ return defaultConfig;
124
+ }
125
+ return {
126
+ paths: normalizePaths(raw.paths, configPath, issues),
127
+ validation: normalizeValidation(raw.validation, configPath, issues),
128
+ output: normalizeOutput(raw.output, configPath, issues)
129
+ };
130
+ }
131
+ function normalizePaths(raw, configPath, issues) {
132
+ const base = defaultConfig.paths;
133
+ if (!raw) {
134
+ return base;
135
+ }
136
+ if (!isRecord(raw)) {
137
+ issues.push(
138
+ configIssue(configPath, "paths \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
139
+ );
140
+ return base;
141
+ }
142
+ return {
143
+ specDir: readString(
144
+ raw.specDir,
145
+ base.specDir,
146
+ "paths.specDir",
147
+ configPath,
148
+ issues
149
+ ),
150
+ decisionsDir: readString(
151
+ raw.decisionsDir,
152
+ base.decisionsDir,
153
+ "paths.decisionsDir",
154
+ configPath,
155
+ issues
156
+ ),
157
+ scenariosDir: readString(
158
+ raw.scenariosDir,
159
+ base.scenariosDir,
160
+ "paths.scenariosDir",
161
+ configPath,
162
+ issues
163
+ ),
164
+ rulesDir: readString(
165
+ raw.rulesDir,
166
+ base.rulesDir,
167
+ "paths.rulesDir",
168
+ configPath,
169
+ issues
170
+ ),
171
+ contractsDir: readString(
172
+ raw.contractsDir,
173
+ base.contractsDir,
174
+ "paths.contractsDir",
175
+ configPath,
176
+ issues
177
+ ),
178
+ uiContractsDir: readString(
179
+ raw.uiContractsDir,
180
+ base.uiContractsDir,
181
+ "paths.uiContractsDir",
182
+ configPath,
183
+ issues
184
+ ),
185
+ apiContractsDir: readString(
186
+ raw.apiContractsDir,
187
+ base.apiContractsDir,
188
+ "paths.apiContractsDir",
189
+ configPath,
190
+ issues
191
+ ),
192
+ dataContractsDir: readString(
193
+ raw.dataContractsDir,
194
+ base.dataContractsDir,
195
+ "paths.dataContractsDir",
196
+ configPath,
197
+ issues
198
+ ),
199
+ srcDir: readString(
200
+ raw.srcDir,
201
+ base.srcDir,
202
+ "paths.srcDir",
203
+ configPath,
204
+ issues
205
+ ),
206
+ testsDir: readString(
207
+ raw.testsDir,
208
+ base.testsDir,
209
+ "paths.testsDir",
210
+ configPath,
211
+ issues
212
+ )
213
+ };
214
+ }
215
+ function normalizeValidation(raw, configPath, issues) {
216
+ const base = defaultConfig.validation;
217
+ if (!raw) {
218
+ return base;
219
+ }
220
+ if (!isRecord(raw)) {
221
+ issues.push(
222
+ configIssue(
223
+ configPath,
224
+ "validation \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
225
+ )
226
+ );
227
+ return base;
228
+ }
229
+ let requireRaw;
230
+ if (raw.require === void 0) {
231
+ requireRaw = void 0;
232
+ } else if (isRecord(raw.require)) {
233
+ requireRaw = raw.require;
234
+ } else {
235
+ issues.push(
236
+ configIssue(
237
+ configPath,
238
+ "validation.require \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
239
+ )
240
+ );
241
+ requireRaw = void 0;
242
+ }
243
+ let traceabilityRaw;
244
+ if (raw.traceability === void 0) {
245
+ traceabilityRaw = void 0;
246
+ } else if (isRecord(raw.traceability)) {
247
+ traceabilityRaw = raw.traceability;
248
+ } else {
249
+ issues.push(
250
+ configIssue(
251
+ configPath,
252
+ "validation.traceability \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002"
253
+ )
254
+ );
255
+ traceabilityRaw = void 0;
256
+ }
257
+ return {
258
+ failOn: readFailOn(
259
+ raw.failOn,
260
+ base.failOn,
261
+ "validation.failOn",
262
+ configPath,
263
+ issues
264
+ ),
265
+ require: {
266
+ specSections: readStringArray(
267
+ requireRaw?.specSections,
268
+ base.require.specSections,
269
+ "validation.require.specSections",
270
+ configPath,
271
+ issues
272
+ )
273
+ },
274
+ traceability: {
275
+ brMustHaveSc: readBoolean(
276
+ traceabilityRaw?.brMustHaveSc,
277
+ base.traceability.brMustHaveSc,
278
+ "validation.traceability.brMustHaveSc",
279
+ configPath,
280
+ issues
281
+ ),
282
+ scMustTouchContracts: readBoolean(
283
+ traceabilityRaw?.scMustTouchContracts,
284
+ base.traceability.scMustTouchContracts,
285
+ "validation.traceability.scMustTouchContracts",
286
+ configPath,
287
+ issues
288
+ ),
289
+ allowOrphanContracts: readBoolean(
290
+ traceabilityRaw?.allowOrphanContracts,
291
+ base.traceability.allowOrphanContracts,
292
+ "validation.traceability.allowOrphanContracts",
293
+ configPath,
294
+ issues
295
+ )
296
+ }
297
+ };
298
+ }
299
+ function normalizeOutput(raw, configPath, issues) {
300
+ const base = defaultConfig.output;
301
+ if (!raw) {
302
+ return base;
303
+ }
304
+ if (!isRecord(raw)) {
305
+ issues.push(
306
+ configIssue(configPath, "output \u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002")
307
+ );
308
+ return base;
309
+ }
310
+ return {
311
+ format: readOutputFormat(
312
+ raw.format,
313
+ base.format,
314
+ "output.format",
315
+ configPath,
316
+ issues
317
+ ),
318
+ jsonPath: readString(
319
+ raw.jsonPath,
320
+ base.jsonPath,
321
+ "output.jsonPath",
322
+ configPath,
323
+ issues
324
+ )
325
+ };
326
+ }
327
+ function readString(value, fallback, label, configPath, issues) {
328
+ if (typeof value === "string" && value.trim().length > 0) {
329
+ return value;
330
+ }
331
+ if (value !== void 0) {
332
+ issues.push(
333
+ configIssue(configPath, `${label} \u306F\u6587\u5B57\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`)
334
+ );
335
+ }
336
+ return fallback;
337
+ }
338
+ function readStringArray(value, fallback, label, configPath, issues) {
339
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
340
+ return value;
341
+ }
342
+ if (value !== void 0) {
343
+ issues.push(
344
+ configIssue(configPath, `${label} \u306F\u6587\u5B57\u5217\u914D\u5217\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`)
345
+ );
346
+ }
347
+ return fallback;
348
+ }
349
+ function readBoolean(value, fallback, label, configPath, issues) {
350
+ if (typeof value === "boolean") {
351
+ return value;
352
+ }
353
+ if (value !== void 0) {
354
+ issues.push(
355
+ configIssue(configPath, `${label} \u306F\u771F\u507D\u5024\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`)
356
+ );
357
+ }
358
+ return fallback;
359
+ }
360
+ function readFailOn(value, fallback, label, configPath, issues) {
361
+ if (value === "never" || value === "warning" || value === "error") {
362
+ return value;
363
+ }
364
+ if (value !== void 0) {
365
+ issues.push(
366
+ configIssue(
367
+ configPath,
368
+ `${label} \u306F never|warning|error \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
369
+ )
370
+ );
371
+ }
372
+ return fallback;
373
+ }
374
+ function readOutputFormat(value, fallback, label, configPath, issues) {
375
+ if (value === "text" || value === "json" || value === "github") {
376
+ return value;
377
+ }
378
+ if (value !== void 0) {
379
+ issues.push(
380
+ configIssue(
381
+ configPath,
382
+ `${label} \u306F text|json|github \u306E\u3044\u305A\u308C\u304B\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002`
383
+ )
384
+ );
385
+ }
386
+ return fallback;
387
+ }
388
+ function configIssue(file, message) {
389
+ return {
390
+ code: "QFAI_CONFIG_INVALID",
391
+ severity: "error",
392
+ message,
393
+ file,
394
+ rule: "config.invalid"
395
+ };
396
+ }
397
+ function isMissingFile(error) {
398
+ if (error && typeof error === "object" && "code" in error) {
399
+ return error.code === "ENOENT";
400
+ }
401
+ return false;
402
+ }
403
+ function formatError(error) {
404
+ if (error instanceof Error) {
405
+ return error.message;
406
+ }
407
+ return String(error);
408
+ }
409
+ function isRecord(value) {
410
+ return value !== null && typeof value === "object" && !Array.isArray(value);
411
+ }
412
+
413
+ // src/core/ids.ts
414
+ var ID_PATTERNS = {
415
+ SPEC: /\bSPEC-[A-Z0-9-]+\b/g,
416
+ BR: /\bBR-[A-Z0-9-]+\b/g,
417
+ SC: /\bSC-[A-Z0-9-]+\b/g,
418
+ UI: /\bUI-[A-Z0-9-]+\b/g,
419
+ API: /\bAPI-[A-Z0-9-]+\b/g,
420
+ DATA: /\bDATA-[A-Z0-9-]+\b/g
421
+ };
422
+ var LOOSE_ID_PATTERNS = {
423
+ SPEC: /\bSPEC-[A-Za-z0-9_-]+\b/gi,
424
+ BR: /\bBR-[A-Za-z0-9_-]+\b/gi,
425
+ SC: /\bSC-[A-Za-z0-9_-]+\b/gi,
426
+ UI: /\bUI-[A-Za-z0-9_-]+\b/gi,
427
+ API: /\bAPI-[A-Za-z0-9_-]+\b/gi,
428
+ DATA: /\bDATA-[A-Za-z0-9_-]+\b/gi
429
+ };
430
+ function extractIds(text, prefix) {
431
+ const pattern = ID_PATTERNS[prefix];
432
+ const matches = text.match(pattern);
433
+ return unique(matches ?? []);
434
+ }
435
+ function extractAllIds(text) {
436
+ const all = [];
437
+ Object.keys(ID_PATTERNS).forEach((prefix) => {
438
+ all.push(...extractIds(text, prefix));
439
+ });
440
+ return unique(all);
441
+ }
442
+ function extractInvalidIds(text, prefixes) {
443
+ const invalid = [];
444
+ for (const prefix of prefixes) {
445
+ const candidates = text.match(LOOSE_ID_PATTERNS[prefix]) ?? [];
446
+ for (const candidate of candidates) {
447
+ if (!isValidId(candidate, prefix)) {
448
+ invalid.push(candidate);
449
+ }
450
+ }
451
+ }
452
+ return unique(invalid);
453
+ }
454
+ function unique(values) {
455
+ return Array.from(new Set(values));
456
+ }
457
+ function isValidId(value, prefix) {
458
+ const pattern = ID_PATTERNS[prefix];
459
+ const strict = new RegExp(pattern.source);
460
+ return strict.test(value);
461
+ }
462
+
463
+ // src/core/report.ts
464
+ var import_promises8 = require("fs/promises");
465
+
466
+ // src/core/discovery.ts
467
+ var import_node_path3 = __toESM(require("path"), 1);
468
+
469
+ // src/core/fs.ts
470
+ var import_promises2 = require("fs/promises");
471
+ var import_node_path2 = __toESM(require("path"), 1);
472
+ var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
473
+ "node_modules",
474
+ ".git",
475
+ "dist",
476
+ ".pnpm",
477
+ "tmp",
478
+ ".mcp-tools"
479
+ ]);
480
+ async function collectFiles(root, options = {}) {
481
+ const entries = [];
482
+ if (!await exists(root)) {
483
+ return entries;
484
+ }
485
+ const ignoreDirs = /* @__PURE__ */ new Set([
486
+ ...DEFAULT_IGNORE_DIRS,
487
+ ...options.ignoreDirs ?? []
488
+ ]);
489
+ const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
490
+ await walk(root, root, ignoreDirs, extensions, entries);
491
+ return entries;
492
+ }
493
+ async function walk(base, current, ignoreDirs, extensions, out) {
494
+ const items = await (0, import_promises2.readdir)(current, { withFileTypes: true });
495
+ for (const item of items) {
496
+ const fullPath = import_node_path2.default.join(current, item.name);
497
+ if (item.isDirectory()) {
498
+ if (ignoreDirs.has(item.name)) {
499
+ continue;
500
+ }
501
+ await walk(base, fullPath, ignoreDirs, extensions, out);
502
+ continue;
503
+ }
504
+ if (item.isFile()) {
505
+ if (extensions.length > 0) {
506
+ const ext = import_node_path2.default.extname(item.name).toLowerCase();
507
+ if (!extensions.includes(ext)) {
508
+ continue;
509
+ }
510
+ }
511
+ out.push(fullPath);
512
+ }
513
+ }
514
+ }
515
+ async function exists(target) {
516
+ try {
517
+ await (0, import_promises2.access)(target);
518
+ return true;
519
+ } catch {
520
+ return false;
521
+ }
522
+ }
523
+
524
+ // src/core/discovery.ts
525
+ var LEGACY_SPEC_NAME = "spec.md";
526
+ var SPEC_NAMED_PATTERN = /^spec-\d{4}-[^/\\]+\.md$/i;
527
+ async function collectSpecFiles(specRoot) {
528
+ const files = await collectFiles(specRoot, { extensions: [".md"] });
529
+ return files.filter((file) => isSpecFile(file));
530
+ }
531
+ async function collectUiContractFiles(uiRoot) {
532
+ return collectFiles(uiRoot, { extensions: [".yaml", ".yml"] });
533
+ }
534
+ async function collectApiContractFiles(apiRoot) {
535
+ return collectFiles(apiRoot, { extensions: [".yaml", ".yml", ".json"] });
536
+ }
537
+ async function collectDataContractFiles(dataRoot) {
538
+ return collectFiles(dataRoot, { extensions: [".sql"] });
539
+ }
540
+ async function collectContractFiles(uiRoot, apiRoot, dataRoot) {
541
+ const [ui, api, db] = await Promise.all([
542
+ collectUiContractFiles(uiRoot),
543
+ collectApiContractFiles(apiRoot),
544
+ collectDataContractFiles(dataRoot)
545
+ ]);
546
+ return { ui, api, db };
547
+ }
548
+ function isSpecFile(filePath) {
549
+ const name = import_node_path3.default.basename(filePath).toLowerCase();
550
+ return name === LEGACY_SPEC_NAME || SPEC_NAMED_PATTERN.test(name);
551
+ }
552
+
553
+ // src/core/types.ts
554
+ var VALIDATION_SCHEMA_VERSION = "0.2";
555
+
556
+ // src/core/version.ts
557
+ var import_promises3 = require("fs/promises");
558
+ var import_node_path4 = __toESM(require("path"), 1);
559
+ var import_node_url = require("url");
560
+ async function resolveToolVersion() {
561
+ if ("0.2.5".length > 0) {
562
+ return "0.2.5";
563
+ }
564
+ try {
565
+ const packagePath = resolvePackageJsonPath();
566
+ const raw = await (0, import_promises3.readFile)(packagePath, "utf-8");
567
+ const parsed = JSON.parse(raw);
568
+ const version = typeof parsed.version === "string" ? parsed.version : "";
569
+ return version.length > 0 ? version : "unknown";
570
+ } catch {
571
+ return "unknown";
572
+ }
573
+ }
574
+ function resolvePackageJsonPath() {
575
+ const base = __filename;
576
+ const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
577
+ return import_node_path4.default.resolve(import_node_path4.default.dirname(basePath), "../../package.json");
578
+ }
579
+
580
+ // src/core/validators/contracts.ts
581
+ var import_promises4 = require("fs/promises");
582
+ var import_node_path5 = __toESM(require("path"), 1);
583
+ var import_yaml2 = require("yaml");
584
+ var SQL_DANGEROUS_PATTERNS = [
585
+ { pattern: /\bDROP\s+TABLE\b/i, label: "DROP TABLE" },
586
+ { pattern: /\bDROP\s+DATABASE\b/i, label: "DROP DATABASE" },
587
+ { pattern: /\bTRUNCATE\b/i, label: "TRUNCATE" },
588
+ {
589
+ pattern: /\bALTER\s+TABLE\b[\s\S]*\bDROP\b/i,
590
+ label: "ALTER TABLE ... DROP"
591
+ }
592
+ ];
593
+ async function validateContracts(root, config) {
594
+ const issues = [];
595
+ issues.push(
596
+ ...await validateUiContracts(resolvePath(root, config, "uiContractsDir"))
597
+ );
598
+ issues.push(
599
+ ...await validateApiContracts(
600
+ resolvePath(root, config, "apiContractsDir")
601
+ )
602
+ );
603
+ issues.push(
604
+ ...await validateDataContracts(
605
+ resolvePath(root, config, "dataContractsDir")
606
+ )
607
+ );
608
+ return issues;
609
+ }
610
+ async function validateUiContracts(uiRoot) {
611
+ const files = await collectUiContractFiles(uiRoot);
612
+ if (files.length === 0) {
613
+ return [
614
+ issue(
615
+ "QFAI-UI-000",
616
+ "UI \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
617
+ "info",
618
+ uiRoot,
619
+ "contracts.ui.files"
620
+ )
621
+ ];
622
+ }
623
+ const issues = [];
624
+ for (const file of files) {
625
+ const text = await (0, import_promises4.readFile)(file, "utf-8");
626
+ const invalidIds = extractInvalidIds(text, [
627
+ "SPEC",
628
+ "BR",
629
+ "SC",
630
+ "UI",
631
+ "API",
632
+ "DATA"
633
+ ]);
634
+ if (invalidIds.length > 0) {
635
+ issues.push(
636
+ issue(
637
+ "QFAI_ID_INVALID_FORMAT",
638
+ `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
639
+ "error",
640
+ file,
641
+ "id.format",
642
+ invalidIds
643
+ )
644
+ );
645
+ }
646
+ try {
647
+ const doc = (0, import_yaml2.parse)(text);
648
+ const id = typeof doc.id === "string" ? doc.id : "";
649
+ if (!id.startsWith("UI-")) {
650
+ issues.push(
651
+ issue(
652
+ "QFAI-UI-001",
653
+ "UI \u5951\u7D04\u306E id \u306F UI- \u3067\u59CB\u307E\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
654
+ "error",
655
+ file,
656
+ "contracts.ui.id"
657
+ )
658
+ );
659
+ }
660
+ } catch (error) {
661
+ issues.push(
662
+ issue(
663
+ "QFAI-UI-002",
664
+ `UI YAML \u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError2(error)}`,
665
+ "error",
666
+ file,
667
+ "contracts.ui.parse"
668
+ )
669
+ );
670
+ }
671
+ }
672
+ return issues;
673
+ }
674
+ async function validateApiContracts(apiRoot) {
675
+ const files = await collectApiContractFiles(apiRoot);
676
+ if (files.length === 0) {
677
+ return [
678
+ issue(
679
+ "QFAI-API-000",
680
+ "API \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
681
+ "info",
682
+ apiRoot,
683
+ "contracts.api.files"
684
+ )
685
+ ];
686
+ }
687
+ const issues = [];
688
+ for (const file of files) {
689
+ const text = await (0, import_promises4.readFile)(file, "utf-8");
690
+ const invalidIds = extractInvalidIds(text, [
691
+ "SPEC",
692
+ "BR",
693
+ "SC",
694
+ "UI",
695
+ "API",
696
+ "DATA"
697
+ ]);
698
+ if (invalidIds.length > 0) {
699
+ issues.push(
700
+ issue(
701
+ "QFAI_ID_INVALID_FORMAT",
702
+ `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
703
+ "error",
704
+ file,
705
+ "id.format",
706
+ invalidIds
707
+ )
708
+ );
709
+ }
710
+ try {
711
+ const doc = parseStructured(file, text);
712
+ if (!doc || !hasOpenApi(doc)) {
713
+ issues.push(
714
+ issue(
715
+ "QFAI-API-001",
716
+ "OpenAPI \u5B9A\u7FA9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
717
+ "error",
718
+ file,
719
+ "contracts.api.openapi"
720
+ )
721
+ );
722
+ }
723
+ } catch (error) {
724
+ issues.push(
725
+ issue(
726
+ "QFAI-API-002",
727
+ `API \u5B9A\u7FA9\u306E\u89E3\u6790\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${formatError2(error)}`,
728
+ "error",
729
+ file,
730
+ "contracts.api.parse"
731
+ )
732
+ );
733
+ }
734
+ }
735
+ return issues;
736
+ }
737
+ async function validateDataContracts(dataRoot) {
738
+ const files = await collectDataContractFiles(dataRoot);
739
+ if (files.length === 0) {
740
+ return [
741
+ issue(
742
+ "QFAI-DATA-000",
743
+ "DATA \u5951\u7D04\u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
744
+ "info",
745
+ dataRoot,
746
+ "contracts.data.files"
747
+ )
748
+ ];
749
+ }
750
+ const issues = [];
751
+ for (const file of files) {
752
+ const text = await (0, import_promises4.readFile)(file, "utf-8");
753
+ const invalidIds = extractInvalidIds(text, [
754
+ "SPEC",
755
+ "BR",
756
+ "SC",
757
+ "UI",
758
+ "API",
759
+ "DATA"
760
+ ]);
761
+ if (invalidIds.length > 0) {
762
+ issues.push(
763
+ issue(
764
+ "QFAI_ID_INVALID_FORMAT",
765
+ `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
766
+ "error",
767
+ file,
768
+ "id.format",
769
+ invalidIds
770
+ )
771
+ );
772
+ }
773
+ issues.push(...lintSql(text, file));
774
+ }
775
+ return issues;
776
+ }
777
+ function lintSql(text, file) {
778
+ const issues = [];
779
+ for (const { pattern, label } of SQL_DANGEROUS_PATTERNS) {
780
+ if (pattern.test(text)) {
781
+ issues.push(
782
+ issue(
783
+ "QFAI-DATA-001",
784
+ `\u5371\u967A\u306A SQL \u64CD\u4F5C\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059: ${label}`,
785
+ "warning",
786
+ file,
787
+ "contracts.data.sql"
788
+ )
789
+ );
790
+ }
791
+ }
792
+ return issues;
793
+ }
794
+ function parseStructured(file, text) {
795
+ const ext = import_node_path5.default.extname(file).toLowerCase();
796
+ if (ext === ".json") {
797
+ return JSON.parse(text);
798
+ }
799
+ return (0, import_yaml2.parse)(text);
800
+ }
801
+ function hasOpenApi(doc) {
802
+ return typeof doc.openapi === "string" && doc.openapi.length > 0;
803
+ }
804
+ function formatError2(error) {
805
+ if (error instanceof Error) {
806
+ return error.message;
807
+ }
808
+ return String(error);
809
+ }
810
+ function issue(code, message, severity, file, rule, refs) {
811
+ const issue5 = {
812
+ code,
813
+ severity,
814
+ message
815
+ };
816
+ if (file) {
817
+ issue5.file = file;
818
+ }
819
+ if (rule) {
820
+ issue5.rule = rule;
821
+ }
822
+ if (refs && refs.length > 0) {
823
+ issue5.refs = refs;
824
+ }
825
+ return issue5;
826
+ }
827
+
828
+ // src/core/validators/scenario.ts
829
+ var import_promises5 = require("fs/promises");
830
+ var GIVEN_PATTERN = /\bGiven\b/;
831
+ var WHEN_PATTERN = /\bWhen\b/;
832
+ var THEN_PATTERN = /\bThen\b/;
833
+ async function validateScenarios(root, config) {
834
+ const scenariosRoot = resolvePath(root, config, "scenariosDir");
835
+ const files = await collectFiles(scenariosRoot, {
836
+ extensions: [".feature"]
837
+ });
838
+ if (files.length === 0) {
839
+ return [
840
+ issue2(
841
+ "QFAI-SC-000",
842
+ "Scenario \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
843
+ "info",
844
+ scenariosRoot,
845
+ "scenario.files"
846
+ )
847
+ ];
848
+ }
849
+ const issues = [];
850
+ for (const file of files) {
851
+ const text = await (0, import_promises5.readFile)(file, "utf-8");
852
+ issues.push(...validateScenarioContent(text, file));
853
+ }
854
+ return issues;
855
+ }
856
+ function validateScenarioContent(text, file) {
857
+ const issues = [];
858
+ const invalidIds = extractInvalidIds(text, [
859
+ "SPEC",
860
+ "BR",
861
+ "SC",
862
+ "UI",
863
+ "API",
864
+ "DATA"
865
+ ]);
866
+ if (invalidIds.length > 0) {
867
+ issues.push(
868
+ issue2(
869
+ "QFAI_ID_INVALID_FORMAT",
870
+ `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
871
+ "error",
872
+ file,
873
+ "id.format",
874
+ invalidIds
875
+ )
876
+ );
877
+ }
878
+ const scIds = extractIds(text, "SC");
879
+ if (scIds.length === 0) {
880
+ issues.push(
881
+ issue2(
882
+ "QFAI-SC-001",
883
+ "SC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
884
+ "error",
885
+ file,
886
+ "scenario.id"
887
+ )
888
+ );
889
+ }
890
+ const specIds = extractIds(text, "SPEC");
891
+ if (specIds.length === 0) {
892
+ issues.push(
893
+ issue2(
894
+ "QFAI-SC-002",
895
+ "SC \u306F SPEC \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
896
+ "error",
897
+ file,
898
+ "scenario.spec"
899
+ )
900
+ );
901
+ }
902
+ const brIds = extractIds(text, "BR");
903
+ if (brIds.length === 0) {
904
+ issues.push(
905
+ issue2(
906
+ "QFAI-SC-003",
907
+ "SC \u306F BR \u3092\u53C2\u7167\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002",
908
+ "error",
909
+ file,
910
+ "scenario.br"
911
+ )
912
+ );
913
+ }
914
+ const missingSteps = [];
915
+ if (!GIVEN_PATTERN.test(text)) {
916
+ missingSteps.push("Given");
917
+ }
918
+ if (!WHEN_PATTERN.test(text)) {
919
+ missingSteps.push("When");
920
+ }
921
+ if (!THEN_PATTERN.test(text)) {
922
+ missingSteps.push("Then");
923
+ }
924
+ if (missingSteps.length > 0) {
925
+ issues.push(
926
+ issue2(
927
+ "QFAI-SC-005",
928
+ `Given/When/Then \u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${missingSteps.join(", ")}`,
929
+ "warning",
930
+ file,
931
+ "scenario.steps"
932
+ )
933
+ );
934
+ }
935
+ return issues;
936
+ }
937
+ function issue2(code, message, severity, file, rule, refs) {
938
+ const issue5 = {
939
+ code,
940
+ severity,
941
+ message
942
+ };
943
+ if (file) {
944
+ issue5.file = file;
945
+ }
946
+ if (rule) {
947
+ issue5.rule = rule;
948
+ }
949
+ if (refs && refs.length > 0) {
950
+ issue5.refs = refs;
951
+ }
952
+ return issue5;
953
+ }
954
+
955
+ // src/core/validators/spec.ts
956
+ var import_promises6 = require("fs/promises");
957
+ async function validateSpecs(root, config) {
958
+ const specsRoot = resolvePath(root, config, "specDir");
959
+ const files = await collectSpecFiles(specsRoot);
960
+ if (files.length === 0) {
961
+ return [
962
+ issue3(
963
+ "QFAI-SPEC-000",
964
+ "Spec \u30D5\u30A1\u30A4\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
965
+ "info",
966
+ specsRoot,
967
+ "spec.files"
968
+ )
969
+ ];
970
+ }
971
+ const issues = [];
972
+ for (const file of files) {
973
+ const text = await (0, import_promises6.readFile)(file, "utf-8");
974
+ issues.push(
975
+ ...validateSpecContent(
976
+ text,
977
+ file,
978
+ config.validation.require.specSections
979
+ )
980
+ );
981
+ }
982
+ return issues;
983
+ }
984
+ function validateSpecContent(text, file, requiredSections) {
985
+ const issues = [];
986
+ const invalidIds = extractInvalidIds(text, [
987
+ "SPEC",
988
+ "BR",
989
+ "SC",
990
+ "UI",
991
+ "API",
992
+ "DATA"
993
+ ]);
994
+ if (invalidIds.length > 0) {
995
+ issues.push(
996
+ issue3(
997
+ "QFAI_ID_INVALID_FORMAT",
998
+ `ID \u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u304C\u4E0D\u6B63\u3067\u3059: ${invalidIds.join(", ")}`,
999
+ "error",
1000
+ file,
1001
+ "id.format",
1002
+ invalidIds
1003
+ )
1004
+ );
1005
+ }
1006
+ const specIds = extractIds(text, "SPEC");
1007
+ if (specIds.length === 0) {
1008
+ issues.push(
1009
+ issue3(
1010
+ "QFAI-SPEC-001",
1011
+ "SPEC ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1012
+ "error",
1013
+ file,
1014
+ "spec.id"
1015
+ )
1016
+ );
1017
+ }
1018
+ const brIds = extractIds(text, "BR");
1019
+ if (brIds.length === 0) {
1020
+ issues.push(
1021
+ issue3(
1022
+ "QFAI-SPEC-002",
1023
+ "BR ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1024
+ "error",
1025
+ file,
1026
+ "spec.br"
1027
+ )
1028
+ );
1029
+ }
1030
+ const scIds = extractIds(text, "SC");
1031
+ if (scIds.length > 0) {
1032
+ issues.push(
1033
+ issue3(
1034
+ "QFAI-SPEC-003",
1035
+ "Spec \u306F SC \u3092\u53C2\u7167\u3057\u306A\u3044\u30EB\u30FC\u30EB\u3067\u3059\u3002",
1036
+ "warning",
1037
+ file,
1038
+ "spec.noSc",
1039
+ scIds
1040
+ )
1041
+ );
1042
+ }
1043
+ for (const section of requiredSections) {
1044
+ if (!text.includes(section)) {
1045
+ issues.push(
1046
+ issue3(
1047
+ "QFAI-SPEC-004",
1048
+ `\u5FC5\u9808\u30BB\u30AF\u30B7\u30E7\u30F3\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: ${section}`,
1049
+ "error",
1050
+ file,
1051
+ "spec.requiredSection"
1052
+ )
1053
+ );
1054
+ }
1055
+ }
1056
+ return issues;
1057
+ }
1058
+ function issue3(code, message, severity, file, rule, refs) {
1059
+ const issue5 = {
1060
+ code,
1061
+ severity,
1062
+ message
1063
+ };
1064
+ if (file) {
1065
+ issue5.file = file;
1066
+ }
1067
+ if (rule) {
1068
+ issue5.rule = rule;
1069
+ }
1070
+ if (refs && refs.length > 0) {
1071
+ issue5.refs = refs;
1072
+ }
1073
+ return issue5;
1074
+ }
1075
+
1076
+ // src/core/validators/traceability.ts
1077
+ var import_promises7 = require("fs/promises");
1078
+ async function validateTraceability(root, config) {
1079
+ const issues = [];
1080
+ const specsRoot = resolvePath(root, config, "specDir");
1081
+ const decisionsRoot = resolvePath(root, config, "decisionsDir");
1082
+ const scenariosRoot = resolvePath(root, config, "scenariosDir");
1083
+ const srcRoot = resolvePath(root, config, "srcDir");
1084
+ const testsRoot = resolvePath(root, config, "testsDir");
1085
+ const specFiles = await collectSpecFiles(specsRoot);
1086
+ const decisionFiles = await collectFiles(decisionsRoot, {
1087
+ extensions: [".md"]
1088
+ });
1089
+ const scenarioFiles = await collectFiles(scenariosRoot, {
1090
+ extensions: [".feature"]
1091
+ });
1092
+ const upstreamIds = /* @__PURE__ */ new Set();
1093
+ const brIdsInSpecs = /* @__PURE__ */ new Set();
1094
+ const brIdsInScenarios = /* @__PURE__ */ new Set();
1095
+ const scIdsInScenarios = /* @__PURE__ */ new Set();
1096
+ const scenarioContractIds = /* @__PURE__ */ new Set();
1097
+ const scWithContracts = /* @__PURE__ */ new Set();
1098
+ for (const file of [...specFiles, ...decisionFiles]) {
1099
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1100
+ extractAllIds(text).forEach((id) => upstreamIds.add(id));
1101
+ extractIds(text, "BR").forEach((id) => brIdsInSpecs.add(id));
1102
+ }
1103
+ for (const file of scenarioFiles) {
1104
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1105
+ extractAllIds(text).forEach((id) => upstreamIds.add(id));
1106
+ const brIds = extractIds(text, "BR");
1107
+ brIds.forEach((id) => brIdsInScenarios.add(id));
1108
+ const scIds = extractIds(text, "SC");
1109
+ scIds.forEach((id) => scIdsInScenarios.add(id));
1110
+ const contractIds = [
1111
+ ...extractIds(text, "UI"),
1112
+ ...extractIds(text, "API"),
1113
+ ...extractIds(text, "DATA")
1114
+ ];
1115
+ contractIds.forEach((id) => scenarioContractIds.add(id));
1116
+ if (contractIds.length > 0) {
1117
+ scIds.forEach((id) => scWithContracts.add(id));
1118
+ }
1119
+ }
1120
+ if (upstreamIds.size === 0) {
1121
+ return [
1122
+ issue4(
1123
+ "QFAI-TRACE-000",
1124
+ "\u4E0A\u6D41 ID \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1125
+ "info",
1126
+ specsRoot,
1127
+ "traceability.upstream"
1128
+ )
1129
+ ];
1130
+ }
1131
+ if (config.validation.traceability.brMustHaveSc && brIdsInSpecs.size > 0) {
1132
+ const orphanBrIds = Array.from(brIdsInSpecs).filter(
1133
+ (id) => !brIdsInScenarios.has(id)
1134
+ );
1135
+ if (orphanBrIds.length > 0) {
1136
+ issues.push(
1137
+ issue4(
1138
+ "QFAI_TRACE_BR_ORPHAN",
1139
+ `BR \u304C SC \u306B\u7D10\u3065\u3044\u3066\u3044\u307E\u305B\u3093: ${orphanBrIds.join(", ")}`,
1140
+ "error",
1141
+ specsRoot,
1142
+ "traceability.brMustHaveSc",
1143
+ orphanBrIds
1144
+ )
1145
+ );
1146
+ }
1147
+ }
1148
+ if (config.validation.traceability.scMustTouchContracts && scIdsInScenarios.size > 0) {
1149
+ const scWithoutContracts = Array.from(scIdsInScenarios).filter(
1150
+ (id) => !scWithContracts.has(id)
1151
+ );
1152
+ if (scWithoutContracts.length > 0) {
1153
+ issues.push(
1154
+ issue4(
1155
+ "QFAI_TRACE_SC_NO_CONTRACT",
1156
+ `SC \u304C\u5951\u7D04(UI/API/DATA)\u306B\u63A5\u7D9A\u3057\u3066\u3044\u307E\u305B\u3093: ${scWithoutContracts.join(
1157
+ ", "
1158
+ )}`,
1159
+ "error",
1160
+ scenariosRoot,
1161
+ "traceability.scMustTouchContracts",
1162
+ scWithoutContracts
1163
+ )
1164
+ );
1165
+ }
1166
+ }
1167
+ if (!config.validation.traceability.allowOrphanContracts) {
1168
+ const contractIds = await collectContractIds(root, config);
1169
+ if (contractIds.size > 0) {
1170
+ const orphanContracts = Array.from(contractIds).filter(
1171
+ (id) => !scenarioContractIds.has(id)
1172
+ );
1173
+ if (orphanContracts.length > 0) {
1174
+ issues.push(
1175
+ issue4(
1176
+ "QFAI_CONTRACT_ORPHAN",
1177
+ `\u5951\u7D04\u304C SC \u304B\u3089\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093: ${orphanContracts.join(", ")}`,
1178
+ "error",
1179
+ scenariosRoot,
1180
+ "traceability.allowOrphanContracts",
1181
+ orphanContracts
1182
+ )
1183
+ );
1184
+ }
1185
+ }
1186
+ }
1187
+ issues.push(
1188
+ ...await validateCodeReferences(upstreamIds, srcRoot, testsRoot)
1189
+ );
1190
+ return issues;
1191
+ }
1192
+ async function collectContractIds(root, config) {
1193
+ const contractIds = /* @__PURE__ */ new Set();
1194
+ const uiRoot = resolvePath(root, config, "uiContractsDir");
1195
+ const apiRoot = resolvePath(root, config, "apiContractsDir");
1196
+ const dataRoot = resolvePath(root, config, "dataContractsDir");
1197
+ const uiFiles = await collectUiContractFiles(uiRoot);
1198
+ const apiFiles = await collectApiContractFiles(apiRoot);
1199
+ const dataFiles = await collectDataContractFiles(dataRoot);
1200
+ await collectIdsFromFiles(uiFiles, ["UI"], contractIds);
1201
+ await collectIdsFromFiles(apiFiles, ["API"], contractIds);
1202
+ await collectIdsFromFiles(dataFiles, ["DATA"], contractIds);
1203
+ return contractIds;
1204
+ }
1205
+ async function collectIdsFromFiles(files, prefixes, out) {
1206
+ for (const file of files) {
1207
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1208
+ for (const prefix of prefixes) {
1209
+ extractIds(text, prefix).forEach((id) => out.add(id));
1210
+ }
1211
+ }
1212
+ }
1213
+ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
1214
+ const issues = [];
1215
+ const codeFiles = await collectFiles(srcRoot, {
1216
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
1217
+ });
1218
+ const testFiles = await collectFiles(testsRoot, {
1219
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
1220
+ });
1221
+ const targetFiles = [...codeFiles, ...testFiles];
1222
+ if (targetFiles.length === 0) {
1223
+ issues.push(
1224
+ issue4(
1225
+ "QFAI-TRACE-001",
1226
+ "\u53C2\u7167\u5BFE\u8C61\u306E\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
1227
+ "info",
1228
+ srcRoot,
1229
+ "traceability.codeReferences"
1230
+ )
1231
+ );
1232
+ return issues;
1233
+ }
1234
+ const pattern = buildIdPattern(Array.from(upstreamIds));
1235
+ let found = false;
1236
+ for (const file of targetFiles) {
1237
+ const text = await (0, import_promises7.readFile)(file, "utf-8");
1238
+ if (pattern.test(text)) {
1239
+ found = true;
1240
+ break;
1241
+ }
1242
+ }
1243
+ if (!found) {
1244
+ issues.push(
1245
+ issue4(
1246
+ "QFAI-TRACE-002",
1247
+ "\u4E0A\u6D41 ID \u304C\u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u306B\u53C2\u7167\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
1248
+ "warning",
1249
+ srcRoot,
1250
+ "traceability.codeReferences"
1251
+ )
1252
+ );
1253
+ }
1254
+ return issues;
1255
+ }
1256
+ function buildIdPattern(ids) {
1257
+ const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1258
+ return new RegExp(`\\b(${escaped.join("|")})\\b`);
1259
+ }
1260
+ function issue4(code, message, severity, file, rule, refs) {
1261
+ const issue5 = {
1262
+ code,
1263
+ severity,
1264
+ message
1265
+ };
1266
+ if (file) {
1267
+ issue5.file = file;
1268
+ }
1269
+ if (rule) {
1270
+ issue5.rule = rule;
1271
+ }
1272
+ if (refs && refs.length > 0) {
1273
+ issue5.refs = refs;
1274
+ }
1275
+ return issue5;
1276
+ }
1277
+
1278
+ // src/core/validate.ts
1279
+ async function validateProject(root, configResult) {
1280
+ const resolved = configResult ?? await loadConfig(root);
1281
+ const { config, issues: configIssues } = resolved;
1282
+ const issues = [
1283
+ ...configIssues,
1284
+ ...await validateSpecs(root, config),
1285
+ ...await validateScenarios(root, config),
1286
+ ...await validateContracts(root, config),
1287
+ ...await validateTraceability(root, config)
1288
+ ];
1289
+ const toolVersion = await resolveToolVersion();
1290
+ return {
1291
+ schemaVersion: VALIDATION_SCHEMA_VERSION,
1292
+ toolVersion,
1293
+ issues,
1294
+ counts: countIssues(issues)
1295
+ };
1296
+ }
1297
+ function countIssues(issues) {
1298
+ return issues.reduce(
1299
+ (acc, issue5) => {
1300
+ acc[issue5.severity] += 1;
1301
+ return acc;
1302
+ },
1303
+ { info: 0, warning: 0, error: 0 }
1304
+ );
1305
+ }
1306
+
1307
+ // src/core/report.ts
1308
+ var ID_PREFIXES = ["SPEC", "BR", "SC", "UI", "API", "DATA"];
1309
+ async function createReportData(root, validation, configResult) {
1310
+ const resolved = configResult ?? await loadConfig(root);
1311
+ const config = resolved.config;
1312
+ const configPath = resolved.configPath;
1313
+ const specRoot = resolvePath(root, config, "specDir");
1314
+ const decisionsRoot = resolvePath(root, config, "decisionsDir");
1315
+ const scenariosRoot = resolvePath(root, config, "scenariosDir");
1316
+ const rulesRoot = resolvePath(root, config, "rulesDir");
1317
+ const apiRoot = resolvePath(root, config, "apiContractsDir");
1318
+ const uiRoot = resolvePath(root, config, "uiContractsDir");
1319
+ const dbRoot = resolvePath(root, config, "dataContractsDir");
1320
+ const srcRoot = resolvePath(root, config, "srcDir");
1321
+ const testsRoot = resolvePath(root, config, "testsDir");
1322
+ const specFiles = await collectSpecFiles(specRoot);
1323
+ const scenarioFiles = await collectFiles(scenariosRoot, {
1324
+ extensions: [".feature"]
1325
+ });
1326
+ const decisionFiles = await collectFiles(decisionsRoot, {
1327
+ extensions: [".md"]
1328
+ });
1329
+ const ruleFiles = await collectFiles(rulesRoot, { extensions: [".md"] });
1330
+ const {
1331
+ api: apiFiles,
1332
+ ui: uiFiles,
1333
+ db: dbFiles
1334
+ } = await collectContractFiles(uiRoot, apiRoot, dbRoot);
1335
+ const idsByPrefix = await collectIds([
1336
+ ...specFiles,
1337
+ ...scenarioFiles,
1338
+ ...decisionFiles,
1339
+ ...ruleFiles,
1340
+ ...apiFiles,
1341
+ ...uiFiles,
1342
+ ...dbFiles
1343
+ ]);
1344
+ const upstreamIds = await collectUpstreamIds([
1345
+ ...specFiles,
1346
+ ...scenarioFiles
1347
+ ]);
1348
+ const traceability = await evaluateTraceability(
1349
+ upstreamIds,
1350
+ srcRoot,
1351
+ testsRoot
1352
+ );
1353
+ const resolvedValidation = validation ?? await validateProject(root, resolved);
1354
+ const version = await resolveToolVersion();
1355
+ return {
1356
+ tool: "qfai",
1357
+ version,
1358
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1359
+ root,
1360
+ configPath,
1361
+ summary: {
1362
+ specs: specFiles.length,
1363
+ scenarios: scenarioFiles.length,
1364
+ decisions: decisionFiles.length,
1365
+ rules: ruleFiles.length,
1366
+ contracts: {
1367
+ api: apiFiles.length,
1368
+ ui: uiFiles.length,
1369
+ db: dbFiles.length
1370
+ },
1371
+ counts: resolvedValidation.counts
1372
+ },
1373
+ ids: {
1374
+ spec: idsByPrefix.SPEC,
1375
+ br: idsByPrefix.BR,
1376
+ sc: idsByPrefix.SC,
1377
+ ui: idsByPrefix.UI,
1378
+ api: idsByPrefix.API,
1379
+ data: idsByPrefix.DATA
1380
+ },
1381
+ traceability: {
1382
+ upstreamIdsFound: upstreamIds.size,
1383
+ referencedInCodeOrTests: traceability
1384
+ },
1385
+ issues: resolvedValidation.issues
1386
+ };
1387
+ }
1388
+ function formatReportMarkdown(data) {
1389
+ const lines = [];
1390
+ lines.push("# QFAI Report");
1391
+ lines.push(`- \u751F\u6210\u65E5\u6642: ${data.generatedAt}`);
1392
+ lines.push(`- \u30EB\u30FC\u30C8: ${data.root}`);
1393
+ lines.push(`- \u8A2D\u5B9A: ${data.configPath}`);
1394
+ lines.push(`- \u7248: ${data.version}`);
1395
+ lines.push("");
1396
+ lines.push("## \u6982\u8981");
1397
+ lines.push(`- specs: ${data.summary.specs}`);
1398
+ lines.push(`- scenarios: ${data.summary.scenarios}`);
1399
+ lines.push(`- decisions: ${data.summary.decisions}`);
1400
+ lines.push(`- rules: ${data.summary.rules}`);
1401
+ lines.push(
1402
+ `- contracts: api ${data.summary.contracts.api} / ui ${data.summary.contracts.ui} / db ${data.summary.contracts.db}`
1403
+ );
1404
+ lines.push(
1405
+ `- issues: info ${data.summary.counts.info} / warning ${data.summary.counts.warning} / error ${data.summary.counts.error}`
1406
+ );
1407
+ lines.push("");
1408
+ lines.push("## ID\u96C6\u8A08");
1409
+ lines.push(formatIdLine("SPEC", data.ids.spec));
1410
+ lines.push(formatIdLine("BR", data.ids.br));
1411
+ lines.push(formatIdLine("SC", data.ids.sc));
1412
+ lines.push(formatIdLine("UI", data.ids.ui));
1413
+ lines.push(formatIdLine("API", data.ids.api));
1414
+ lines.push(formatIdLine("DATA", data.ids.data));
1415
+ lines.push("");
1416
+ lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3");
1417
+ lines.push(`- \u4E0A\u6D41ID\u691C\u51FA\u6570: ${data.traceability.upstreamIdsFound}`);
1418
+ lines.push(
1419
+ `- \u30B3\u30FC\u30C9/\u30C6\u30B9\u30C8\u53C2\u7167: ${data.traceability.referencedInCodeOrTests ? "\u3042\u308A" : "\u306A\u3057"}`
1420
+ );
1421
+ lines.push("");
1422
+ lines.push("## Hotspots");
1423
+ const hotspots = buildHotspots(data.issues);
1424
+ if (hotspots.length === 0) {
1425
+ lines.push("- (none)");
1426
+ } else {
1427
+ for (const spot of hotspots) {
1428
+ lines.push(
1429
+ `- ${spot.file}: total ${spot.total} (error ${spot.error} / warning ${spot.warning} / info ${spot.info})`
1430
+ );
1431
+ }
1432
+ }
1433
+ lines.push("");
1434
+ lines.push("## \u30C8\u30EC\u30FC\u30B5\u30D3\u30EA\u30C6\u30A3\uFF08\u691C\u8A3C\uFF09");
1435
+ const traceIssues = data.issues.filter(
1436
+ (item) => item.rule?.startsWith("traceability.") || item.code.startsWith("QFAI_TRACE") || item.code === "QFAI_CONTRACT_ORPHAN"
1437
+ );
1438
+ if (traceIssues.length === 0) {
1439
+ lines.push("- (none)");
1440
+ } else {
1441
+ for (const item of traceIssues) {
1442
+ const location = item.file ? ` (${item.file})` : "";
1443
+ lines.push(
1444
+ `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}`
1445
+ );
1446
+ }
1447
+ }
1448
+ lines.push("");
1449
+ lines.push("## \u691C\u8A3C\u7D50\u679C");
1450
+ if (data.issues.length === 0) {
1451
+ lines.push("- (none)");
1452
+ } else {
1453
+ for (const item of data.issues) {
1454
+ const location = item.file ? ` (${item.file})` : "";
1455
+ const refs = item.refs && item.refs.length > 0 ? ` refs=${item.refs.join(",")}` : "";
1456
+ lines.push(
1457
+ `- ${item.severity.toUpperCase()} [${item.code}] ${item.message}${location}${refs}`
1458
+ );
1459
+ }
1460
+ }
1461
+ return lines.join("\n");
1462
+ }
1463
+ function formatReportJson(data) {
1464
+ return JSON.stringify(data, null, 2);
1465
+ }
1466
+ async function collectIds(files) {
1467
+ const result = {
1468
+ SPEC: /* @__PURE__ */ new Set(),
1469
+ BR: /* @__PURE__ */ new Set(),
1470
+ SC: /* @__PURE__ */ new Set(),
1471
+ UI: /* @__PURE__ */ new Set(),
1472
+ API: /* @__PURE__ */ new Set(),
1473
+ DATA: /* @__PURE__ */ new Set()
1474
+ };
1475
+ for (const file of files) {
1476
+ const text = await (0, import_promises8.readFile)(file, "utf-8");
1477
+ for (const prefix of ID_PREFIXES) {
1478
+ const ids = extractIds(text, prefix);
1479
+ ids.forEach((id) => result[prefix].add(id));
1480
+ }
1481
+ }
1482
+ return {
1483
+ SPEC: toSortedArray(result.SPEC),
1484
+ BR: toSortedArray(result.BR),
1485
+ SC: toSortedArray(result.SC),
1486
+ UI: toSortedArray(result.UI),
1487
+ API: toSortedArray(result.API),
1488
+ DATA: toSortedArray(result.DATA)
1489
+ };
1490
+ }
1491
+ async function collectUpstreamIds(files) {
1492
+ const ids = /* @__PURE__ */ new Set();
1493
+ for (const file of files) {
1494
+ const text = await (0, import_promises8.readFile)(file, "utf-8");
1495
+ extractAllIds(text).forEach((id) => ids.add(id));
1496
+ }
1497
+ return ids;
1498
+ }
1499
+ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
1500
+ if (upstreamIds.size === 0) {
1501
+ return false;
1502
+ }
1503
+ const codeFiles = await collectFiles(srcRoot, {
1504
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
1505
+ });
1506
+ const testFiles = await collectFiles(testsRoot, {
1507
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
1508
+ });
1509
+ const targetFiles = [...codeFiles, ...testFiles];
1510
+ if (targetFiles.length === 0) {
1511
+ return false;
1512
+ }
1513
+ const pattern = buildIdPattern2(Array.from(upstreamIds));
1514
+ for (const file of targetFiles) {
1515
+ const text = await (0, import_promises8.readFile)(file, "utf-8");
1516
+ if (pattern.test(text)) {
1517
+ return true;
1518
+ }
1519
+ }
1520
+ return false;
1521
+ }
1522
+ function buildIdPattern2(ids) {
1523
+ const escaped = ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1524
+ return new RegExp(`\\b(${escaped.join("|")})\\b`);
1525
+ }
1526
+ function formatIdLine(label, values) {
1527
+ if (values.length === 0) {
1528
+ return `- ${label}: (none)`;
1529
+ }
1530
+ return `- ${label}: ${values.join(", ")}`;
1531
+ }
1532
+ function toSortedArray(values) {
1533
+ return Array.from(values).sort((a, b) => a.localeCompare(b));
1534
+ }
1535
+ function buildHotspots(issues) {
1536
+ const map = /* @__PURE__ */ new Map();
1537
+ for (const issue5 of issues) {
1538
+ if (!issue5.file) {
1539
+ continue;
1540
+ }
1541
+ const current = map.get(issue5.file) ?? {
1542
+ file: issue5.file,
1543
+ total: 0,
1544
+ error: 0,
1545
+ warning: 0,
1546
+ info: 0
1547
+ };
1548
+ current.total += 1;
1549
+ current[issue5.severity] += 1;
1550
+ map.set(issue5.file, current);
1551
+ }
1552
+ return Array.from(map.values()).sort(
1553
+ (a, b) => b.total !== a.total ? b.total - a.total : a.file.localeCompare(b.file)
1554
+ );
1555
+ }
1556
+ // Annotate the CommonJS export names for ESM import in node:
1557
+ 0 && (module.exports = {
1558
+ VALIDATION_SCHEMA_VERSION,
1559
+ createReportData,
1560
+ defaultConfig,
1561
+ extractAllIds,
1562
+ extractIds,
1563
+ extractInvalidIds,
1564
+ formatReportJson,
1565
+ formatReportMarkdown,
1566
+ getConfigPath,
1567
+ lintSql,
1568
+ loadConfig,
1569
+ resolvePath,
1570
+ resolveToolVersion,
1571
+ validateContracts,
1572
+ validateProject,
1573
+ validateScenarioContent,
1574
+ validateScenarios,
1575
+ validateSpecContent,
1576
+ validateSpecs,
1577
+ validateTraceability
1578
+ });
1579
+ //# sourceMappingURL=index.cjs.map