qfai 0.8.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -5
- package/assets/init/.qfai/README.md +9 -1
- package/assets/init/.qfai/prompts/README.md +6 -1
- package/assets/init/.qfai/prompts/analyze/README.md +21 -0
- package/assets/init/.qfai/prompts/analyze/scenario_test_consistency.md +8 -0
- package/assets/init/.qfai/prompts/analyze/scenario_to_test.md +56 -0
- package/assets/init/.qfai/prompts/analyze/spec_contract_consistency.md +8 -0
- package/assets/init/.qfai/prompts/analyze/spec_scenario_consistency.md +8 -0
- package/assets/init/.qfai/prompts/analyze/spec_to_contract.md +54 -0
- package/assets/init/.qfai/prompts/analyze/spec_to_scenario.md +56 -0
- package/assets/init/.qfai/prompts.local/README.md +2 -1
- package/assets/init/.qfai/samples/analyze/analysis.md +38 -0
- package/assets/init/.qfai/samples/analyze/input_bundle.md +54 -0
- package/dist/cli/index.cjs +421 -243
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +405 -227
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -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
|
|
178
|
+
import path11 from "path";
|
|
6
179
|
|
|
7
180
|
// src/core/doctor.ts
|
|
8
181
|
import { access as access4 } from "fs/promises";
|
|
9
|
-
import
|
|
182
|
+
import path10 from "path";
|
|
10
183
|
|
|
11
184
|
// src/core/config.ts
|
|
12
|
-
import { access, readFile } from "fs/promises";
|
|
13
|
-
import
|
|
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
|
|
226
|
+
return path3.join(root, "qfai.config.yaml");
|
|
54
227
|
}
|
|
55
228
|
async function findConfigRoot(startDir) {
|
|
56
|
-
const resolvedStart =
|
|
229
|
+
const resolvedStart = path3.resolve(startDir);
|
|
57
230
|
let current = resolvedStart;
|
|
58
231
|
while (true) {
|
|
59
232
|
const configPath = getConfigPath(current);
|
|
60
|
-
if (await
|
|
233
|
+
if (await exists2(configPath)) {
|
|
61
234
|
return { root: current, configPath, found: true };
|
|
62
235
|
}
|
|
63
|
-
const parent =
|
|
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
|
|
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
|
|
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
|
|
566
|
+
async function exists2(target) {
|
|
394
567
|
try {
|
|
395
|
-
await
|
|
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
|
|
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:
|
|
491
|
-
deltaPath:
|
|
492
|
-
scenarioPath:
|
|
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) =>
|
|
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
|
|
668
|
+
import path5 from "path";
|
|
564
669
|
function toRelativePath(root, target) {
|
|
565
670
|
if (!target) {
|
|
566
671
|
return target;
|
|
567
672
|
}
|
|
568
|
-
if (!
|
|
673
|
+
if (!path5.isAbsolute(target)) {
|
|
569
674
|
return toPosixPath(target);
|
|
570
675
|
}
|
|
571
|
-
const relative =
|
|
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
|
|
583
|
-
import
|
|
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
|
|
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
|
|
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) =>
|
|
915
|
+
new Set(files.map((file) => path6.normalize(file)))
|
|
811
916
|
);
|
|
812
917
|
for (const file of normalizedFiles) {
|
|
813
|
-
const text = await
|
|
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
|
|
871
|
-
import
|
|
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
|
|
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 =
|
|
985
|
+
const baseDir = path7.dirname(basePath);
|
|
881
986
|
const candidates = [
|
|
882
|
-
|
|
883
|
-
|
|
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 =
|
|
1006
|
+
const promptsDir = path8.resolve(root, ".qfai", "prompts");
|
|
902
1007
|
let templateDir;
|
|
903
1008
|
try {
|
|
904
|
-
templateDir =
|
|
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
|
-
|
|
958
|
-
|
|
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 =
|
|
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
|
|
996
|
-
import
|
|
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.
|
|
1000
|
-
return "0.
|
|
1104
|
+
if ("0.9.2".length > 0) {
|
|
1105
|
+
return "0.9.2";
|
|
1001
1106
|
}
|
|
1002
1107
|
try {
|
|
1003
1108
|
const packagePath = resolvePackageJsonPath();
|
|
1004
|
-
const raw = await
|
|
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
|
|
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 =
|
|
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 =
|
|
1105
|
-
|
|
1106
|
-
`${
|
|
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 =
|
|
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 =
|
|
1190
|
-
const inside = rel !== "" && !rel.startsWith("..") && !
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
1423
|
+
let current = path10.resolve(startDir);
|
|
1319
1424
|
while (true) {
|
|
1320
|
-
const gitPath =
|
|
1321
|
-
const workspacePath =
|
|
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 =
|
|
1430
|
+
const parent = path10.dirname(current);
|
|
1326
1431
|
if (parent === current) {
|
|
1327
1432
|
break;
|
|
1328
1433
|
}
|
|
1329
1434
|
current = parent;
|
|
1330
1435
|
}
|
|
1331
|
-
return
|
|
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 =
|
|
1374
|
-
await mkdir(
|
|
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
|
|
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
|
|
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 =
|
|
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 +
|
|
1417
|
-
const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
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,
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1561
|
+
const relative = path12.relative(sourceRoot, file);
|
|
1457
1562
|
if (isExcludedRelative(relative)) {
|
|
1458
1563
|
continue;
|
|
1459
1564
|
}
|
|
1460
|
-
const dest =
|
|
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(
|
|
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 =
|
|
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,15 @@ 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 =
|
|
1522
|
-
const qfaiAssets =
|
|
1523
|
-
const destRoot =
|
|
1524
|
-
const destQfai =
|
|
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");
|
|
1630
|
+
if (options.force) {
|
|
1631
|
+
info(
|
|
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"
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1525
1635
|
const rootResult = await copyTemplateTree(rootAssets, destRoot, {
|
|
1526
1636
|
force: false,
|
|
1527
1637
|
dryRun: options.dryRun,
|
|
@@ -1563,8 +1673,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
1563
1673
|
}
|
|
1564
1674
|
|
|
1565
1675
|
// src/cli/commands/report.ts
|
|
1566
|
-
import { mkdir as mkdir3, readFile as
|
|
1567
|
-
import
|
|
1676
|
+
import { mkdir as mkdir3, readFile as readFile14, writeFile as writeFile2 } from "fs/promises";
|
|
1677
|
+
import path20 from "path";
|
|
1568
1678
|
|
|
1569
1679
|
// src/core/normalize.ts
|
|
1570
1680
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1604,12 +1714,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1604
1714
|
}
|
|
1605
1715
|
|
|
1606
1716
|
// src/core/report.ts
|
|
1607
|
-
import { readFile as
|
|
1608
|
-
import
|
|
1717
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
1718
|
+
import path19 from "path";
|
|
1609
1719
|
|
|
1610
1720
|
// src/core/contractIndex.ts
|
|
1611
|
-
import { readFile as
|
|
1612
|
-
import
|
|
1721
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1722
|
+
import path14 from "path";
|
|
1613
1723
|
|
|
1614
1724
|
// src/core/contractsDecl.ts
|
|
1615
1725
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1631,9 +1741,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1631
1741
|
// src/core/contractIndex.ts
|
|
1632
1742
|
async function buildContractIndex(root, config) {
|
|
1633
1743
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1634
|
-
const uiRoot =
|
|
1635
|
-
const apiRoot =
|
|
1636
|
-
const dbRoot =
|
|
1744
|
+
const uiRoot = path14.join(contractsRoot, "ui");
|
|
1745
|
+
const apiRoot = path14.join(contractsRoot, "api");
|
|
1746
|
+
const dbRoot = path14.join(contractsRoot, "db");
|
|
1637
1747
|
const [uiFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1638
1748
|
collectUiContractFiles(uiRoot),
|
|
1639
1749
|
collectApiContractFiles(apiRoot),
|
|
@@ -1651,7 +1761,7 @@ async function buildContractIndex(root, config) {
|
|
|
1651
1761
|
}
|
|
1652
1762
|
async function indexContractFiles(files, index) {
|
|
1653
1763
|
for (const file of files) {
|
|
1654
|
-
const text = await
|
|
1764
|
+
const text = await readFile6(file, "utf-8");
|
|
1655
1765
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1656
1766
|
}
|
|
1657
1767
|
}
|
|
@@ -1886,14 +1996,14 @@ function parseSpec(md, file) {
|
|
|
1886
1996
|
}
|
|
1887
1997
|
|
|
1888
1998
|
// src/core/validators/contracts.ts
|
|
1889
|
-
import { readFile as
|
|
1890
|
-
import
|
|
1999
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
2000
|
+
import path16 from "path";
|
|
1891
2001
|
|
|
1892
2002
|
// src/core/contracts.ts
|
|
1893
|
-
import
|
|
2003
|
+
import path15 from "path";
|
|
1894
2004
|
import { parse as parseYaml2 } from "yaml";
|
|
1895
2005
|
function parseStructuredContract(file, text) {
|
|
1896
|
-
const ext =
|
|
2006
|
+
const ext = path15.extname(file).toLowerCase();
|
|
1897
2007
|
if (ext === ".json") {
|
|
1898
2008
|
return JSON.parse(text);
|
|
1899
2009
|
}
|
|
@@ -1913,9 +2023,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1913
2023
|
async function validateContracts(root, config) {
|
|
1914
2024
|
const issues = [];
|
|
1915
2025
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1916
|
-
issues.push(...await validateUiContracts(
|
|
1917
|
-
issues.push(...await validateApiContracts(
|
|
1918
|
-
issues.push(...await validateDbContracts(
|
|
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")));
|
|
1919
2029
|
const contractIndex = await buildContractIndex(root, config);
|
|
1920
2030
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
1921
2031
|
return issues;
|
|
@@ -1935,7 +2045,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
1935
2045
|
}
|
|
1936
2046
|
const issues = [];
|
|
1937
2047
|
for (const file of files) {
|
|
1938
|
-
const text = await
|
|
2048
|
+
const text = await readFile7(file, "utf-8");
|
|
1939
2049
|
const invalidIds = extractInvalidIds(text, [
|
|
1940
2050
|
"SPEC",
|
|
1941
2051
|
"BR",
|
|
@@ -1990,7 +2100,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1990
2100
|
}
|
|
1991
2101
|
const issues = [];
|
|
1992
2102
|
for (const file of files) {
|
|
1993
|
-
const text = await
|
|
2103
|
+
const text = await readFile7(file, "utf-8");
|
|
1994
2104
|
const invalidIds = extractInvalidIds(text, [
|
|
1995
2105
|
"SPEC",
|
|
1996
2106
|
"BR",
|
|
@@ -2058,7 +2168,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
2058
2168
|
}
|
|
2059
2169
|
const issues = [];
|
|
2060
2170
|
for (const file of files) {
|
|
2061
|
-
const text = await
|
|
2171
|
+
const text = await readFile7(file, "utf-8");
|
|
2062
2172
|
const invalidIds = extractInvalidIds(text, [
|
|
2063
2173
|
"SPEC",
|
|
2064
2174
|
"BR",
|
|
@@ -2201,8 +2311,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
2201
2311
|
}
|
|
2202
2312
|
|
|
2203
2313
|
// src/core/validators/delta.ts
|
|
2204
|
-
import { readFile as
|
|
2205
|
-
import
|
|
2314
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2315
|
+
import path17 from "path";
|
|
2206
2316
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
2207
2317
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
2208
2318
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -2216,10 +2326,10 @@ async function validateDeltas(root, config) {
|
|
|
2216
2326
|
}
|
|
2217
2327
|
const issues = [];
|
|
2218
2328
|
for (const pack of packs) {
|
|
2219
|
-
const deltaPath =
|
|
2329
|
+
const deltaPath = path17.join(pack, "delta.md");
|
|
2220
2330
|
let text;
|
|
2221
2331
|
try {
|
|
2222
|
-
text = await
|
|
2332
|
+
text = await readFile8(deltaPath, "utf-8");
|
|
2223
2333
|
} catch (error2) {
|
|
2224
2334
|
if (isMissingFileError2(error2)) {
|
|
2225
2335
|
issues.push(
|
|
@@ -2295,8 +2405,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
2295
2405
|
}
|
|
2296
2406
|
|
|
2297
2407
|
// src/core/validators/ids.ts
|
|
2298
|
-
import { readFile as
|
|
2299
|
-
import
|
|
2408
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2409
|
+
import path18 from "path";
|
|
2300
2410
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2301
2411
|
async function validateDefinedIds(root, config) {
|
|
2302
2412
|
const issues = [];
|
|
@@ -2331,7 +2441,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2331
2441
|
}
|
|
2332
2442
|
async function collectSpecDefinitionIds(files, out) {
|
|
2333
2443
|
for (const file of files) {
|
|
2334
|
-
const text = await
|
|
2444
|
+
const text = await readFile9(file, "utf-8");
|
|
2335
2445
|
const parsed = parseSpec(text, file);
|
|
2336
2446
|
if (parsed.specId) {
|
|
2337
2447
|
recordId(out, parsed.specId, file);
|
|
@@ -2341,7 +2451,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2341
2451
|
}
|
|
2342
2452
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2343
2453
|
for (const file of files) {
|
|
2344
|
-
const text = await
|
|
2454
|
+
const text = await readFile9(file, "utf-8");
|
|
2345
2455
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2346
2456
|
if (!document || errors.length > 0) {
|
|
2347
2457
|
continue;
|
|
@@ -2362,7 +2472,7 @@ function recordId(out, id, file) {
|
|
|
2362
2472
|
}
|
|
2363
2473
|
function formatFileList(files, root) {
|
|
2364
2474
|
return files.map((file) => {
|
|
2365
|
-
const relative =
|
|
2475
|
+
const relative = path18.relative(root, file);
|
|
2366
2476
|
return relative.length > 0 ? relative : file;
|
|
2367
2477
|
}).join(", ");
|
|
2368
2478
|
}
|
|
@@ -2420,7 +2530,7 @@ async function validatePromptsIntegrity(root) {
|
|
|
2420
2530
|
}
|
|
2421
2531
|
|
|
2422
2532
|
// src/core/validators/scenario.ts
|
|
2423
|
-
import { readFile as
|
|
2533
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2424
2534
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
2425
2535
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
2426
2536
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -2446,7 +2556,7 @@ async function validateScenarios(root, config) {
|
|
|
2446
2556
|
for (const entry of entries) {
|
|
2447
2557
|
let text;
|
|
2448
2558
|
try {
|
|
2449
|
-
text = await
|
|
2559
|
+
text = await readFile10(entry.scenarioPath, "utf-8");
|
|
2450
2560
|
} catch (error2) {
|
|
2451
2561
|
if (isMissingFileError3(error2)) {
|
|
2452
2562
|
issues.push(
|
|
@@ -2620,7 +2730,7 @@ function isMissingFileError3(error2) {
|
|
|
2620
2730
|
}
|
|
2621
2731
|
|
|
2622
2732
|
// src/core/validators/spec.ts
|
|
2623
|
-
import { readFile as
|
|
2733
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
2624
2734
|
async function validateSpecs(root, config) {
|
|
2625
2735
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2626
2736
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -2641,7 +2751,7 @@ async function validateSpecs(root, config) {
|
|
|
2641
2751
|
for (const entry of entries) {
|
|
2642
2752
|
let text;
|
|
2643
2753
|
try {
|
|
2644
|
-
text = await
|
|
2754
|
+
text = await readFile11(entry.specPath, "utf-8");
|
|
2645
2755
|
} catch (error2) {
|
|
2646
2756
|
if (isMissingFileError4(error2)) {
|
|
2647
2757
|
issues.push(
|
|
@@ -2794,7 +2904,7 @@ function isMissingFileError4(error2) {
|
|
|
2794
2904
|
}
|
|
2795
2905
|
|
|
2796
2906
|
// src/core/validators/traceability.ts
|
|
2797
|
-
import { readFile as
|
|
2907
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
2798
2908
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
2799
2909
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
2800
2910
|
async function validateTraceability(root, config) {
|
|
@@ -2814,7 +2924,7 @@ async function validateTraceability(root, config) {
|
|
|
2814
2924
|
const contractIndex = await buildContractIndex(root, config);
|
|
2815
2925
|
const contractIds = contractIndex.ids;
|
|
2816
2926
|
for (const file of specFiles) {
|
|
2817
|
-
const text = await
|
|
2927
|
+
const text = await readFile12(file, "utf-8");
|
|
2818
2928
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2819
2929
|
const parsed = parseSpec(text, file);
|
|
2820
2930
|
if (parsed.specId) {
|
|
@@ -2887,7 +2997,7 @@ async function validateTraceability(root, config) {
|
|
|
2887
2997
|
}
|
|
2888
2998
|
}
|
|
2889
2999
|
for (const file of scenarioFiles) {
|
|
2890
|
-
const text = await
|
|
3000
|
+
const text = await readFile12(file, "utf-8");
|
|
2891
3001
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2892
3002
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
2893
3003
|
allowCommentPrefix: true
|
|
@@ -3209,7 +3319,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
3209
3319
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
3210
3320
|
let found = false;
|
|
3211
3321
|
for (const file of targetFiles) {
|
|
3212
|
-
const text = await
|
|
3322
|
+
const text = await readFile12(file, "utf-8");
|
|
3213
3323
|
if (pattern.test(text)) {
|
|
3214
3324
|
found = true;
|
|
3215
3325
|
break;
|
|
@@ -3301,15 +3411,15 @@ function countIssues(issues) {
|
|
|
3301
3411
|
// src/core/report.ts
|
|
3302
3412
|
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
|
|
3303
3413
|
async function createReportData(root, validation, configResult) {
|
|
3304
|
-
const resolvedRoot =
|
|
3414
|
+
const resolvedRoot = path19.resolve(root);
|
|
3305
3415
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3306
3416
|
const config = resolved.config;
|
|
3307
3417
|
const configPath = resolved.configPath;
|
|
3308
3418
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3309
3419
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3310
|
-
const apiRoot =
|
|
3311
|
-
const uiRoot =
|
|
3312
|
-
const dbRoot =
|
|
3420
|
+
const apiRoot = path19.join(contractsRoot, "api");
|
|
3421
|
+
const uiRoot = path19.join(contractsRoot, "ui");
|
|
3422
|
+
const dbRoot = path19.join(contractsRoot, "db");
|
|
3313
3423
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3314
3424
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3315
3425
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -3780,7 +3890,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
3780
3890
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
3781
3891
|
}
|
|
3782
3892
|
for (const file of specFiles) {
|
|
3783
|
-
const text = await
|
|
3893
|
+
const text = await readFile13(file, "utf-8");
|
|
3784
3894
|
const parsed = parseSpec(text, file);
|
|
3785
3895
|
const specKey = parsed.specId;
|
|
3786
3896
|
if (!specKey) {
|
|
@@ -3821,7 +3931,7 @@ async function collectIds(files) {
|
|
|
3821
3931
|
DB: /* @__PURE__ */ new Set()
|
|
3822
3932
|
};
|
|
3823
3933
|
for (const file of files) {
|
|
3824
|
-
const text = await
|
|
3934
|
+
const text = await readFile13(file, "utf-8");
|
|
3825
3935
|
for (const prefix of ID_PREFIXES2) {
|
|
3826
3936
|
const ids = extractIds(text, prefix);
|
|
3827
3937
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -3839,7 +3949,7 @@ async function collectIds(files) {
|
|
|
3839
3949
|
async function collectUpstreamIds(files) {
|
|
3840
3950
|
const ids = /* @__PURE__ */ new Set();
|
|
3841
3951
|
for (const file of files) {
|
|
3842
|
-
const text = await
|
|
3952
|
+
const text = await readFile13(file, "utf-8");
|
|
3843
3953
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
3844
3954
|
}
|
|
3845
3955
|
return ids;
|
|
@@ -3860,7 +3970,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
3860
3970
|
}
|
|
3861
3971
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
3862
3972
|
for (const file of targetFiles) {
|
|
3863
|
-
const text = await
|
|
3973
|
+
const text = await readFile13(file, "utf-8");
|
|
3864
3974
|
if (pattern.test(text)) {
|
|
3865
3975
|
return true;
|
|
3866
3976
|
}
|
|
@@ -3952,7 +4062,7 @@ function buildHotspots(issues) {
|
|
|
3952
4062
|
|
|
3953
4063
|
// src/cli/commands/report.ts
|
|
3954
4064
|
async function runReport(options) {
|
|
3955
|
-
const root =
|
|
4065
|
+
const root = path20.resolve(options.root);
|
|
3956
4066
|
const configResult = await loadConfig(root);
|
|
3957
4067
|
let validation;
|
|
3958
4068
|
if (options.runValidate) {
|
|
@@ -3969,7 +4079,7 @@ async function runReport(options) {
|
|
|
3969
4079
|
validation = normalized;
|
|
3970
4080
|
} else {
|
|
3971
4081
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
3972
|
-
const inputPath =
|
|
4082
|
+
const inputPath = path20.isAbsolute(input) ? input : path20.resolve(root, input);
|
|
3973
4083
|
try {
|
|
3974
4084
|
validation = await readValidationResult(inputPath);
|
|
3975
4085
|
} catch (err) {
|
|
@@ -3995,10 +4105,10 @@ async function runReport(options) {
|
|
|
3995
4105
|
const data = await createReportData(root, validation, configResult);
|
|
3996
4106
|
const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
|
|
3997
4107
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
3998
|
-
const defaultOut = options.format === "json" ?
|
|
4108
|
+
const defaultOut = options.format === "json" ? path20.join(outRoot, "report.json") : path20.join(outRoot, "report.md");
|
|
3999
4109
|
const out = options.outPath ?? defaultOut;
|
|
4000
|
-
const outPath =
|
|
4001
|
-
await mkdir3(
|
|
4110
|
+
const outPath = path20.isAbsolute(out) ? out : path20.resolve(root, out);
|
|
4111
|
+
await mkdir3(path20.dirname(outPath), { recursive: true });
|
|
4002
4112
|
await writeFile2(outPath, `${output}
|
|
4003
4113
|
`, "utf-8");
|
|
4004
4114
|
info(
|
|
@@ -4007,7 +4117,7 @@ async function runReport(options) {
|
|
|
4007
4117
|
info(`wrote report: ${outPath}`);
|
|
4008
4118
|
}
|
|
4009
4119
|
async function readValidationResult(inputPath) {
|
|
4010
|
-
const raw = await
|
|
4120
|
+
const raw = await readFile14(inputPath, "utf-8");
|
|
4011
4121
|
const parsed = JSON.parse(raw);
|
|
4012
4122
|
if (!isValidationResult(parsed)) {
|
|
4013
4123
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -4063,15 +4173,15 @@ function isMissingFileError5(error2) {
|
|
|
4063
4173
|
return record2.code === "ENOENT";
|
|
4064
4174
|
}
|
|
4065
4175
|
async function writeValidationResult(root, outputPath, result) {
|
|
4066
|
-
const abs =
|
|
4067
|
-
await mkdir3(
|
|
4176
|
+
const abs = path20.isAbsolute(outputPath) ? outputPath : path20.resolve(root, outputPath);
|
|
4177
|
+
await mkdir3(path20.dirname(abs), { recursive: true });
|
|
4068
4178
|
await writeFile2(abs, `${JSON.stringify(result, null, 2)}
|
|
4069
4179
|
`, "utf-8");
|
|
4070
4180
|
}
|
|
4071
4181
|
|
|
4072
4182
|
// src/cli/commands/validate.ts
|
|
4073
4183
|
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
4074
|
-
import
|
|
4184
|
+
import path21 from "path";
|
|
4075
4185
|
|
|
4076
4186
|
// src/cli/lib/failOn.ts
|
|
4077
4187
|
function shouldFail(result, failOn) {
|
|
@@ -4086,7 +4196,7 @@ function shouldFail(result, failOn) {
|
|
|
4086
4196
|
|
|
4087
4197
|
// src/cli/commands/validate.ts
|
|
4088
4198
|
async function runValidate(options) {
|
|
4089
|
-
const root =
|
|
4199
|
+
const root = path21.resolve(options.root);
|
|
4090
4200
|
const configResult = await loadConfig(root);
|
|
4091
4201
|
const result = await validateProject(root, configResult);
|
|
4092
4202
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4210,12 +4320,12 @@ function issueKey(issue7) {
|
|
|
4210
4320
|
}
|
|
4211
4321
|
async function emitJson(result, root, jsonPath) {
|
|
4212
4322
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4213
|
-
await mkdir4(
|
|
4323
|
+
await mkdir4(path21.dirname(abs), { recursive: true });
|
|
4214
4324
|
await writeFile3(abs, `${JSON.stringify(result, null, 2)}
|
|
4215
4325
|
`, "utf-8");
|
|
4216
4326
|
}
|
|
4217
4327
|
function resolveJsonPath(root, jsonPath) {
|
|
4218
|
-
return
|
|
4328
|
+
return path21.isAbsolute(jsonPath) ? jsonPath : path21.resolve(root, jsonPath);
|
|
4219
4329
|
}
|
|
4220
4330
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4221
4331
|
|
|
@@ -4228,6 +4338,7 @@ function parseArgs(argv, cwd) {
|
|
|
4228
4338
|
force: false,
|
|
4229
4339
|
yes: false,
|
|
4230
4340
|
dryRun: false,
|
|
4341
|
+
analyzeList: false,
|
|
4231
4342
|
reportFormat: "md",
|
|
4232
4343
|
reportRunValidate: false,
|
|
4233
4344
|
doctorFormat: "text",
|
|
@@ -4237,6 +4348,7 @@ function parseArgs(argv, cwd) {
|
|
|
4237
4348
|
};
|
|
4238
4349
|
const args = [...argv];
|
|
4239
4350
|
let command = args.shift() ?? null;
|
|
4351
|
+
let invalid = false;
|
|
4240
4352
|
if (command === "--help" || command === "-h") {
|
|
4241
4353
|
options.help = true;
|
|
4242
4354
|
command = null;
|
|
@@ -4245,13 +4357,29 @@ function parseArgs(argv, cwd) {
|
|
|
4245
4357
|
const arg = args[i];
|
|
4246
4358
|
switch (arg) {
|
|
4247
4359
|
case "--root":
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
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
|
+
}
|
|
4251
4371
|
break;
|
|
4252
4372
|
case "--dir":
|
|
4253
|
-
|
|
4254
|
-
|
|
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
|
+
}
|
|
4255
4383
|
break;
|
|
4256
4384
|
case "--force":
|
|
4257
4385
|
options.force = true;
|
|
@@ -4262,8 +4390,25 @@ function parseArgs(argv, cwd) {
|
|
|
4262
4390
|
case "--dry-run":
|
|
4263
4391
|
options.dryRun = true;
|
|
4264
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;
|
|
4265
4405
|
case "--format": {
|
|
4266
|
-
const next = args
|
|
4406
|
+
const next = readOptionValue(args, i);
|
|
4407
|
+
if (next === null) {
|
|
4408
|
+
invalid = true;
|
|
4409
|
+
options.help = true;
|
|
4410
|
+
break;
|
|
4411
|
+
}
|
|
4267
4412
|
applyFormatOption(command, next, options);
|
|
4268
4413
|
i += 1;
|
|
4269
4414
|
break;
|
|
@@ -4272,35 +4417,44 @@ function parseArgs(argv, cwd) {
|
|
|
4272
4417
|
options.strict = true;
|
|
4273
4418
|
break;
|
|
4274
4419
|
case "--fail-on": {
|
|
4275
|
-
const next = args
|
|
4420
|
+
const next = readOptionValue(args, i);
|
|
4421
|
+
if (next === null) {
|
|
4422
|
+
invalid = true;
|
|
4423
|
+
options.help = true;
|
|
4424
|
+
break;
|
|
4425
|
+
}
|
|
4276
4426
|
if (next === "never" || next === "warning" || next === "error") {
|
|
4277
4427
|
options.failOn = next;
|
|
4278
4428
|
}
|
|
4279
4429
|
i += 1;
|
|
4280
4430
|
break;
|
|
4281
4431
|
}
|
|
4282
|
-
case "--out":
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
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;
|
|
4292
4443
|
}
|
|
4293
4444
|
i += 1;
|
|
4294
4445
|
break;
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4446
|
+
}
|
|
4447
|
+
case "--in": {
|
|
4448
|
+
const next = readOptionValue(args, i);
|
|
4449
|
+
if (next === null) {
|
|
4450
|
+
invalid = true;
|
|
4451
|
+
options.help = true;
|
|
4452
|
+
break;
|
|
4301
4453
|
}
|
|
4454
|
+
options.reportIn = next;
|
|
4302
4455
|
i += 1;
|
|
4303
4456
|
break;
|
|
4457
|
+
}
|
|
4304
4458
|
case "--run-validate":
|
|
4305
4459
|
options.reportRunValidate = true;
|
|
4306
4460
|
break;
|
|
@@ -4312,7 +4466,14 @@ function parseArgs(argv, cwd) {
|
|
|
4312
4466
|
break;
|
|
4313
4467
|
}
|
|
4314
4468
|
}
|
|
4315
|
-
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;
|
|
4316
4477
|
}
|
|
4317
4478
|
function applyFormatOption(command, value, options) {
|
|
4318
4479
|
if (!value) {
|
|
@@ -4346,9 +4507,12 @@ function applyFormatOption(command, value, options) {
|
|
|
4346
4507
|
|
|
4347
4508
|
// src/cli/main.ts
|
|
4348
4509
|
async function run(argv, cwd) {
|
|
4349
|
-
const { command, options } = parseArgs(argv, cwd);
|
|
4510
|
+
const { command, invalid, options } = parseArgs(argv, cwd);
|
|
4350
4511
|
if (!command || options.help) {
|
|
4351
4512
|
info(usage());
|
|
4513
|
+
if (invalid) {
|
|
4514
|
+
process.exitCode = 1;
|
|
4515
|
+
}
|
|
4352
4516
|
return;
|
|
4353
4517
|
}
|
|
4354
4518
|
switch (command) {
|
|
@@ -4360,6 +4524,17 @@ async function run(argv, cwd) {
|
|
|
4360
4524
|
yes: options.yes
|
|
4361
4525
|
});
|
|
4362
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;
|
|
4363
4538
|
case "validate":
|
|
4364
4539
|
{
|
|
4365
4540
|
const resolvedRoot = await resolveRoot(options);
|
|
@@ -4406,6 +4581,7 @@ function usage() {
|
|
|
4406
4581
|
|
|
4407
4582
|
Commands:
|
|
4408
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
|
|
4409
4585
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
4410
4586
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
4411
4587
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
@@ -4413,9 +4589,11 @@ Commands:
|
|
|
4413
4589
|
Options:
|
|
4414
4590
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
4415
4591
|
--dir <path> init \u306E\u51FA\u529B\u5148
|
|
4416
|
-
--force \u65E2\u5B58\
|
|
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
|
|
4417
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
|
|
4418
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
|
|
4419
4597
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4420
4598
|
--format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4421
4599
|
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|