qfai 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -7
- package/assets/init/.qfai/README.md +4 -3
- package/assets/init/.qfai/prompts/README.md +3 -3
- package/assets/init/.qfai/prompts/analyze/README.md +8 -25
- package/assets/init/.qfai/prompts/analyze/scenario_test_consistency.md +5 -32
- package/assets/init/.qfai/prompts/analyze/scenario_to_test.md +56 -0
- package/assets/init/.qfai/prompts/analyze/spec_contract_consistency.md +5 -33
- package/assets/init/.qfai/prompts/analyze/spec_scenario_consistency.md +5 -32
- 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/samples/analyze/analysis.md +3 -3
- package/assets/init/.qfai/samples/analyze/input_bundle.md +54 -0
- package/dist/cli/index.cjs +415 -242
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +399 -226
- 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.9.
|
|
1000
|
-
return "0.9.
|
|
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,10 @@ async function exists5(target) {
|
|
|
1518
1623
|
// src/cli/commands/init.ts
|
|
1519
1624
|
async function runInit(options) {
|
|
1520
1625
|
const assetsRoot = getInitAssetsDir();
|
|
1521
|
-
const rootAssets =
|
|
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");
|
|
1525
1630
|
if (options.force) {
|
|
1526
1631
|
info(
|
|
1527
1632
|
"NOTE: --force \u306F .qfai/prompts/** \u306E\u307F\u4E0A\u66F8\u304D\u3057\u307E\u3059\uFF08prompts.local \u306F\u4FDD\u8B77\u3055\u308C\u3001specs/contracts \u7B49\u306F\u4E0A\u66F8\u304D\u3057\u307E\u305B\u3093\uFF09\u3002"
|
|
@@ -1568,8 +1673,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
1568
1673
|
}
|
|
1569
1674
|
|
|
1570
1675
|
// src/cli/commands/report.ts
|
|
1571
|
-
import { mkdir as mkdir3, readFile as
|
|
1572
|
-
import
|
|
1676
|
+
import { mkdir as mkdir3, readFile as readFile14, writeFile as writeFile2 } from "fs/promises";
|
|
1677
|
+
import path20 from "path";
|
|
1573
1678
|
|
|
1574
1679
|
// src/core/normalize.ts
|
|
1575
1680
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1609,12 +1714,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1609
1714
|
}
|
|
1610
1715
|
|
|
1611
1716
|
// src/core/report.ts
|
|
1612
|
-
import { readFile as
|
|
1613
|
-
import
|
|
1717
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
1718
|
+
import path19 from "path";
|
|
1614
1719
|
|
|
1615
1720
|
// src/core/contractIndex.ts
|
|
1616
|
-
import { readFile as
|
|
1617
|
-
import
|
|
1721
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1722
|
+
import path14 from "path";
|
|
1618
1723
|
|
|
1619
1724
|
// src/core/contractsDecl.ts
|
|
1620
1725
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1636,9 +1741,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1636
1741
|
// src/core/contractIndex.ts
|
|
1637
1742
|
async function buildContractIndex(root, config) {
|
|
1638
1743
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1639
|
-
const uiRoot =
|
|
1640
|
-
const apiRoot =
|
|
1641
|
-
const dbRoot =
|
|
1744
|
+
const uiRoot = path14.join(contractsRoot, "ui");
|
|
1745
|
+
const apiRoot = path14.join(contractsRoot, "api");
|
|
1746
|
+
const dbRoot = path14.join(contractsRoot, "db");
|
|
1642
1747
|
const [uiFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1643
1748
|
collectUiContractFiles(uiRoot),
|
|
1644
1749
|
collectApiContractFiles(apiRoot),
|
|
@@ -1656,7 +1761,7 @@ async function buildContractIndex(root, config) {
|
|
|
1656
1761
|
}
|
|
1657
1762
|
async function indexContractFiles(files, index) {
|
|
1658
1763
|
for (const file of files) {
|
|
1659
|
-
const text = await
|
|
1764
|
+
const text = await readFile6(file, "utf-8");
|
|
1660
1765
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1661
1766
|
}
|
|
1662
1767
|
}
|
|
@@ -1891,14 +1996,14 @@ function parseSpec(md, file) {
|
|
|
1891
1996
|
}
|
|
1892
1997
|
|
|
1893
1998
|
// src/core/validators/contracts.ts
|
|
1894
|
-
import { readFile as
|
|
1895
|
-
import
|
|
1999
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
2000
|
+
import path16 from "path";
|
|
1896
2001
|
|
|
1897
2002
|
// src/core/contracts.ts
|
|
1898
|
-
import
|
|
2003
|
+
import path15 from "path";
|
|
1899
2004
|
import { parse as parseYaml2 } from "yaml";
|
|
1900
2005
|
function parseStructuredContract(file, text) {
|
|
1901
|
-
const ext =
|
|
2006
|
+
const ext = path15.extname(file).toLowerCase();
|
|
1902
2007
|
if (ext === ".json") {
|
|
1903
2008
|
return JSON.parse(text);
|
|
1904
2009
|
}
|
|
@@ -1918,9 +2023,9 @@ var SQL_DANGEROUS_PATTERNS = [
|
|
|
1918
2023
|
async function validateContracts(root, config) {
|
|
1919
2024
|
const issues = [];
|
|
1920
2025
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1921
|
-
issues.push(...await validateUiContracts(
|
|
1922
|
-
issues.push(...await validateApiContracts(
|
|
1923
|
-
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")));
|
|
1924
2029
|
const contractIndex = await buildContractIndex(root, config);
|
|
1925
2030
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
1926
2031
|
return issues;
|
|
@@ -1940,7 +2045,7 @@ async function validateUiContracts(uiRoot) {
|
|
|
1940
2045
|
}
|
|
1941
2046
|
const issues = [];
|
|
1942
2047
|
for (const file of files) {
|
|
1943
|
-
const text = await
|
|
2048
|
+
const text = await readFile7(file, "utf-8");
|
|
1944
2049
|
const invalidIds = extractInvalidIds(text, [
|
|
1945
2050
|
"SPEC",
|
|
1946
2051
|
"BR",
|
|
@@ -1995,7 +2100,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
1995
2100
|
}
|
|
1996
2101
|
const issues = [];
|
|
1997
2102
|
for (const file of files) {
|
|
1998
|
-
const text = await
|
|
2103
|
+
const text = await readFile7(file, "utf-8");
|
|
1999
2104
|
const invalidIds = extractInvalidIds(text, [
|
|
2000
2105
|
"SPEC",
|
|
2001
2106
|
"BR",
|
|
@@ -2063,7 +2168,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
2063
2168
|
}
|
|
2064
2169
|
const issues = [];
|
|
2065
2170
|
for (const file of files) {
|
|
2066
|
-
const text = await
|
|
2171
|
+
const text = await readFile7(file, "utf-8");
|
|
2067
2172
|
const invalidIds = extractInvalidIds(text, [
|
|
2068
2173
|
"SPEC",
|
|
2069
2174
|
"BR",
|
|
@@ -2206,8 +2311,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
2206
2311
|
}
|
|
2207
2312
|
|
|
2208
2313
|
// src/core/validators/delta.ts
|
|
2209
|
-
import { readFile as
|
|
2210
|
-
import
|
|
2314
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2315
|
+
import path17 from "path";
|
|
2211
2316
|
var SECTION_RE = /^##\s+変更区分/m;
|
|
2212
2317
|
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
2213
2318
|
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
@@ -2221,10 +2326,10 @@ async function validateDeltas(root, config) {
|
|
|
2221
2326
|
}
|
|
2222
2327
|
const issues = [];
|
|
2223
2328
|
for (const pack of packs) {
|
|
2224
|
-
const deltaPath =
|
|
2329
|
+
const deltaPath = path17.join(pack, "delta.md");
|
|
2225
2330
|
let text;
|
|
2226
2331
|
try {
|
|
2227
|
-
text = await
|
|
2332
|
+
text = await readFile8(deltaPath, "utf-8");
|
|
2228
2333
|
} catch (error2) {
|
|
2229
2334
|
if (isMissingFileError2(error2)) {
|
|
2230
2335
|
issues.push(
|
|
@@ -2300,8 +2405,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
2300
2405
|
}
|
|
2301
2406
|
|
|
2302
2407
|
// src/core/validators/ids.ts
|
|
2303
|
-
import { readFile as
|
|
2304
|
-
import
|
|
2408
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2409
|
+
import path18 from "path";
|
|
2305
2410
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2306
2411
|
async function validateDefinedIds(root, config) {
|
|
2307
2412
|
const issues = [];
|
|
@@ -2336,7 +2441,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2336
2441
|
}
|
|
2337
2442
|
async function collectSpecDefinitionIds(files, out) {
|
|
2338
2443
|
for (const file of files) {
|
|
2339
|
-
const text = await
|
|
2444
|
+
const text = await readFile9(file, "utf-8");
|
|
2340
2445
|
const parsed = parseSpec(text, file);
|
|
2341
2446
|
if (parsed.specId) {
|
|
2342
2447
|
recordId(out, parsed.specId, file);
|
|
@@ -2346,7 +2451,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2346
2451
|
}
|
|
2347
2452
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2348
2453
|
for (const file of files) {
|
|
2349
|
-
const text = await
|
|
2454
|
+
const text = await readFile9(file, "utf-8");
|
|
2350
2455
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2351
2456
|
if (!document || errors.length > 0) {
|
|
2352
2457
|
continue;
|
|
@@ -2367,7 +2472,7 @@ function recordId(out, id, file) {
|
|
|
2367
2472
|
}
|
|
2368
2473
|
function formatFileList(files, root) {
|
|
2369
2474
|
return files.map((file) => {
|
|
2370
|
-
const relative =
|
|
2475
|
+
const relative = path18.relative(root, file);
|
|
2371
2476
|
return relative.length > 0 ? relative : file;
|
|
2372
2477
|
}).join(", ");
|
|
2373
2478
|
}
|
|
@@ -2425,7 +2530,7 @@ async function validatePromptsIntegrity(root) {
|
|
|
2425
2530
|
}
|
|
2426
2531
|
|
|
2427
2532
|
// src/core/validators/scenario.ts
|
|
2428
|
-
import { readFile as
|
|
2533
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2429
2534
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
2430
2535
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
2431
2536
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -2451,7 +2556,7 @@ async function validateScenarios(root, config) {
|
|
|
2451
2556
|
for (const entry of entries) {
|
|
2452
2557
|
let text;
|
|
2453
2558
|
try {
|
|
2454
|
-
text = await
|
|
2559
|
+
text = await readFile10(entry.scenarioPath, "utf-8");
|
|
2455
2560
|
} catch (error2) {
|
|
2456
2561
|
if (isMissingFileError3(error2)) {
|
|
2457
2562
|
issues.push(
|
|
@@ -2625,7 +2730,7 @@ function isMissingFileError3(error2) {
|
|
|
2625
2730
|
}
|
|
2626
2731
|
|
|
2627
2732
|
// src/core/validators/spec.ts
|
|
2628
|
-
import { readFile as
|
|
2733
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
2629
2734
|
async function validateSpecs(root, config) {
|
|
2630
2735
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2631
2736
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -2646,7 +2751,7 @@ async function validateSpecs(root, config) {
|
|
|
2646
2751
|
for (const entry of entries) {
|
|
2647
2752
|
let text;
|
|
2648
2753
|
try {
|
|
2649
|
-
text = await
|
|
2754
|
+
text = await readFile11(entry.specPath, "utf-8");
|
|
2650
2755
|
} catch (error2) {
|
|
2651
2756
|
if (isMissingFileError4(error2)) {
|
|
2652
2757
|
issues.push(
|
|
@@ -2799,7 +2904,7 @@ function isMissingFileError4(error2) {
|
|
|
2799
2904
|
}
|
|
2800
2905
|
|
|
2801
2906
|
// src/core/validators/traceability.ts
|
|
2802
|
-
import { readFile as
|
|
2907
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
2803
2908
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
2804
2909
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
2805
2910
|
async function validateTraceability(root, config) {
|
|
@@ -2819,7 +2924,7 @@ async function validateTraceability(root, config) {
|
|
|
2819
2924
|
const contractIndex = await buildContractIndex(root, config);
|
|
2820
2925
|
const contractIds = contractIndex.ids;
|
|
2821
2926
|
for (const file of specFiles) {
|
|
2822
|
-
const text = await
|
|
2927
|
+
const text = await readFile12(file, "utf-8");
|
|
2823
2928
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2824
2929
|
const parsed = parseSpec(text, file);
|
|
2825
2930
|
if (parsed.specId) {
|
|
@@ -2892,7 +2997,7 @@ async function validateTraceability(root, config) {
|
|
|
2892
2997
|
}
|
|
2893
2998
|
}
|
|
2894
2999
|
for (const file of scenarioFiles) {
|
|
2895
|
-
const text = await
|
|
3000
|
+
const text = await readFile12(file, "utf-8");
|
|
2896
3001
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
2897
3002
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
2898
3003
|
allowCommentPrefix: true
|
|
@@ -3214,7 +3319,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
3214
3319
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
3215
3320
|
let found = false;
|
|
3216
3321
|
for (const file of targetFiles) {
|
|
3217
|
-
const text = await
|
|
3322
|
+
const text = await readFile12(file, "utf-8");
|
|
3218
3323
|
if (pattern.test(text)) {
|
|
3219
3324
|
found = true;
|
|
3220
3325
|
break;
|
|
@@ -3306,15 +3411,15 @@ function countIssues(issues) {
|
|
|
3306
3411
|
// src/core/report.ts
|
|
3307
3412
|
var ID_PREFIXES2 = ["SPEC", "BR", "SC", "UI", "API", "DB"];
|
|
3308
3413
|
async function createReportData(root, validation, configResult) {
|
|
3309
|
-
const resolvedRoot =
|
|
3414
|
+
const resolvedRoot = path19.resolve(root);
|
|
3310
3415
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3311
3416
|
const config = resolved.config;
|
|
3312
3417
|
const configPath = resolved.configPath;
|
|
3313
3418
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3314
3419
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3315
|
-
const apiRoot =
|
|
3316
|
-
const uiRoot =
|
|
3317
|
-
const dbRoot =
|
|
3420
|
+
const apiRoot = path19.join(contractsRoot, "api");
|
|
3421
|
+
const uiRoot = path19.join(contractsRoot, "ui");
|
|
3422
|
+
const dbRoot = path19.join(contractsRoot, "db");
|
|
3318
3423
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3319
3424
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3320
3425
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -3785,7 +3890,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
3785
3890
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
3786
3891
|
}
|
|
3787
3892
|
for (const file of specFiles) {
|
|
3788
|
-
const text = await
|
|
3893
|
+
const text = await readFile13(file, "utf-8");
|
|
3789
3894
|
const parsed = parseSpec(text, file);
|
|
3790
3895
|
const specKey = parsed.specId;
|
|
3791
3896
|
if (!specKey) {
|
|
@@ -3826,7 +3931,7 @@ async function collectIds(files) {
|
|
|
3826
3931
|
DB: /* @__PURE__ */ new Set()
|
|
3827
3932
|
};
|
|
3828
3933
|
for (const file of files) {
|
|
3829
|
-
const text = await
|
|
3934
|
+
const text = await readFile13(file, "utf-8");
|
|
3830
3935
|
for (const prefix of ID_PREFIXES2) {
|
|
3831
3936
|
const ids = extractIds(text, prefix);
|
|
3832
3937
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -3844,7 +3949,7 @@ async function collectIds(files) {
|
|
|
3844
3949
|
async function collectUpstreamIds(files) {
|
|
3845
3950
|
const ids = /* @__PURE__ */ new Set();
|
|
3846
3951
|
for (const file of files) {
|
|
3847
|
-
const text = await
|
|
3952
|
+
const text = await readFile13(file, "utf-8");
|
|
3848
3953
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
3849
3954
|
}
|
|
3850
3955
|
return ids;
|
|
@@ -3865,7 +3970,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
3865
3970
|
}
|
|
3866
3971
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
3867
3972
|
for (const file of targetFiles) {
|
|
3868
|
-
const text = await
|
|
3973
|
+
const text = await readFile13(file, "utf-8");
|
|
3869
3974
|
if (pattern.test(text)) {
|
|
3870
3975
|
return true;
|
|
3871
3976
|
}
|
|
@@ -3957,7 +4062,7 @@ function buildHotspots(issues) {
|
|
|
3957
4062
|
|
|
3958
4063
|
// src/cli/commands/report.ts
|
|
3959
4064
|
async function runReport(options) {
|
|
3960
|
-
const root =
|
|
4065
|
+
const root = path20.resolve(options.root);
|
|
3961
4066
|
const configResult = await loadConfig(root);
|
|
3962
4067
|
let validation;
|
|
3963
4068
|
if (options.runValidate) {
|
|
@@ -3974,7 +4079,7 @@ async function runReport(options) {
|
|
|
3974
4079
|
validation = normalized;
|
|
3975
4080
|
} else {
|
|
3976
4081
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
3977
|
-
const inputPath =
|
|
4082
|
+
const inputPath = path20.isAbsolute(input) ? input : path20.resolve(root, input);
|
|
3978
4083
|
try {
|
|
3979
4084
|
validation = await readValidationResult(inputPath);
|
|
3980
4085
|
} catch (err) {
|
|
@@ -4000,10 +4105,10 @@ async function runReport(options) {
|
|
|
4000
4105
|
const data = await createReportData(root, validation, configResult);
|
|
4001
4106
|
const output = options.format === "json" ? formatReportJson(data) : formatReportMarkdown(data);
|
|
4002
4107
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
4003
|
-
const defaultOut = options.format === "json" ?
|
|
4108
|
+
const defaultOut = options.format === "json" ? path20.join(outRoot, "report.json") : path20.join(outRoot, "report.md");
|
|
4004
4109
|
const out = options.outPath ?? defaultOut;
|
|
4005
|
-
const outPath =
|
|
4006
|
-
await mkdir3(
|
|
4110
|
+
const outPath = path20.isAbsolute(out) ? out : path20.resolve(root, out);
|
|
4111
|
+
await mkdir3(path20.dirname(outPath), { recursive: true });
|
|
4007
4112
|
await writeFile2(outPath, `${output}
|
|
4008
4113
|
`, "utf-8");
|
|
4009
4114
|
info(
|
|
@@ -4012,7 +4117,7 @@ async function runReport(options) {
|
|
|
4012
4117
|
info(`wrote report: ${outPath}`);
|
|
4013
4118
|
}
|
|
4014
4119
|
async function readValidationResult(inputPath) {
|
|
4015
|
-
const raw = await
|
|
4120
|
+
const raw = await readFile14(inputPath, "utf-8");
|
|
4016
4121
|
const parsed = JSON.parse(raw);
|
|
4017
4122
|
if (!isValidationResult(parsed)) {
|
|
4018
4123
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -4068,15 +4173,15 @@ function isMissingFileError5(error2) {
|
|
|
4068
4173
|
return record2.code === "ENOENT";
|
|
4069
4174
|
}
|
|
4070
4175
|
async function writeValidationResult(root, outputPath, result) {
|
|
4071
|
-
const abs =
|
|
4072
|
-
await mkdir3(
|
|
4176
|
+
const abs = path20.isAbsolute(outputPath) ? outputPath : path20.resolve(root, outputPath);
|
|
4177
|
+
await mkdir3(path20.dirname(abs), { recursive: true });
|
|
4073
4178
|
await writeFile2(abs, `${JSON.stringify(result, null, 2)}
|
|
4074
4179
|
`, "utf-8");
|
|
4075
4180
|
}
|
|
4076
4181
|
|
|
4077
4182
|
// src/cli/commands/validate.ts
|
|
4078
4183
|
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
4079
|
-
import
|
|
4184
|
+
import path21 from "path";
|
|
4080
4185
|
|
|
4081
4186
|
// src/cli/lib/failOn.ts
|
|
4082
4187
|
function shouldFail(result, failOn) {
|
|
@@ -4091,7 +4196,7 @@ function shouldFail(result, failOn) {
|
|
|
4091
4196
|
|
|
4092
4197
|
// src/cli/commands/validate.ts
|
|
4093
4198
|
async function runValidate(options) {
|
|
4094
|
-
const root =
|
|
4199
|
+
const root = path21.resolve(options.root);
|
|
4095
4200
|
const configResult = await loadConfig(root);
|
|
4096
4201
|
const result = await validateProject(root, configResult);
|
|
4097
4202
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4215,12 +4320,12 @@ function issueKey(issue7) {
|
|
|
4215
4320
|
}
|
|
4216
4321
|
async function emitJson(result, root, jsonPath) {
|
|
4217
4322
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4218
|
-
await mkdir4(
|
|
4323
|
+
await mkdir4(path21.dirname(abs), { recursive: true });
|
|
4219
4324
|
await writeFile3(abs, `${JSON.stringify(result, null, 2)}
|
|
4220
4325
|
`, "utf-8");
|
|
4221
4326
|
}
|
|
4222
4327
|
function resolveJsonPath(root, jsonPath) {
|
|
4223
|
-
return
|
|
4328
|
+
return path21.isAbsolute(jsonPath) ? jsonPath : path21.resolve(root, jsonPath);
|
|
4224
4329
|
}
|
|
4225
4330
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4226
4331
|
|
|
@@ -4233,6 +4338,7 @@ function parseArgs(argv, cwd) {
|
|
|
4233
4338
|
force: false,
|
|
4234
4339
|
yes: false,
|
|
4235
4340
|
dryRun: false,
|
|
4341
|
+
analyzeList: false,
|
|
4236
4342
|
reportFormat: "md",
|
|
4237
4343
|
reportRunValidate: false,
|
|
4238
4344
|
doctorFormat: "text",
|
|
@@ -4242,6 +4348,7 @@ function parseArgs(argv, cwd) {
|
|
|
4242
4348
|
};
|
|
4243
4349
|
const args = [...argv];
|
|
4244
4350
|
let command = args.shift() ?? null;
|
|
4351
|
+
let invalid = false;
|
|
4245
4352
|
if (command === "--help" || command === "-h") {
|
|
4246
4353
|
options.help = true;
|
|
4247
4354
|
command = null;
|
|
@@ -4250,13 +4357,29 @@ function parseArgs(argv, cwd) {
|
|
|
4250
4357
|
const arg = args[i];
|
|
4251
4358
|
switch (arg) {
|
|
4252
4359
|
case "--root":
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4360
|
+
{
|
|
4361
|
+
const next = readOptionValue(args, i);
|
|
4362
|
+
if (next === null) {
|
|
4363
|
+
invalid = true;
|
|
4364
|
+
options.help = true;
|
|
4365
|
+
break;
|
|
4366
|
+
}
|
|
4367
|
+
options.root = next;
|
|
4368
|
+
options.rootExplicit = true;
|
|
4369
|
+
i += 1;
|
|
4370
|
+
}
|
|
4256
4371
|
break;
|
|
4257
4372
|
case "--dir":
|
|
4258
|
-
|
|
4259
|
-
|
|
4373
|
+
{
|
|
4374
|
+
const next = readOptionValue(args, i);
|
|
4375
|
+
if (next === null) {
|
|
4376
|
+
invalid = true;
|
|
4377
|
+
options.help = true;
|
|
4378
|
+
break;
|
|
4379
|
+
}
|
|
4380
|
+
options.dir = next;
|
|
4381
|
+
i += 1;
|
|
4382
|
+
}
|
|
4260
4383
|
break;
|
|
4261
4384
|
case "--force":
|
|
4262
4385
|
options.force = true;
|
|
@@ -4267,8 +4390,25 @@ function parseArgs(argv, cwd) {
|
|
|
4267
4390
|
case "--dry-run":
|
|
4268
4391
|
options.dryRun = true;
|
|
4269
4392
|
break;
|
|
4393
|
+
case "--list":
|
|
4394
|
+
options.analyzeList = true;
|
|
4395
|
+
break;
|
|
4396
|
+
case "--prompt":
|
|
4397
|
+
{
|
|
4398
|
+
const next = readOptionValue(args, i);
|
|
4399
|
+
if (next) {
|
|
4400
|
+
options.analyzePrompt = next;
|
|
4401
|
+
i += 1;
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
break;
|
|
4270
4405
|
case "--format": {
|
|
4271
|
-
const next = args
|
|
4406
|
+
const next = readOptionValue(args, i);
|
|
4407
|
+
if (next === null) {
|
|
4408
|
+
invalid = true;
|
|
4409
|
+
options.help = true;
|
|
4410
|
+
break;
|
|
4411
|
+
}
|
|
4272
4412
|
applyFormatOption(command, next, options);
|
|
4273
4413
|
i += 1;
|
|
4274
4414
|
break;
|
|
@@ -4277,35 +4417,44 @@ function parseArgs(argv, cwd) {
|
|
|
4277
4417
|
options.strict = true;
|
|
4278
4418
|
break;
|
|
4279
4419
|
case "--fail-on": {
|
|
4280
|
-
const next = args
|
|
4420
|
+
const next = readOptionValue(args, i);
|
|
4421
|
+
if (next === null) {
|
|
4422
|
+
invalid = true;
|
|
4423
|
+
options.help = true;
|
|
4424
|
+
break;
|
|
4425
|
+
}
|
|
4281
4426
|
if (next === "never" || next === "warning" || next === "error") {
|
|
4282
4427
|
options.failOn = next;
|
|
4283
4428
|
}
|
|
4284
4429
|
i += 1;
|
|
4285
4430
|
break;
|
|
4286
4431
|
}
|
|
4287
|
-
case "--out":
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4432
|
+
case "--out": {
|
|
4433
|
+
const next = readOptionValue(args, i);
|
|
4434
|
+
if (next === null) {
|
|
4435
|
+
invalid = true;
|
|
4436
|
+
options.help = true;
|
|
4437
|
+
break;
|
|
4438
|
+
}
|
|
4439
|
+
if (command === "doctor") {
|
|
4440
|
+
options.doctorOut = next;
|
|
4441
|
+
} else {
|
|
4442
|
+
options.reportOut = next;
|
|
4297
4443
|
}
|
|
4298
4444
|
i += 1;
|
|
4299
4445
|
break;
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4446
|
+
}
|
|
4447
|
+
case "--in": {
|
|
4448
|
+
const next = readOptionValue(args, i);
|
|
4449
|
+
if (next === null) {
|
|
4450
|
+
invalid = true;
|
|
4451
|
+
options.help = true;
|
|
4452
|
+
break;
|
|
4306
4453
|
}
|
|
4454
|
+
options.reportIn = next;
|
|
4307
4455
|
i += 1;
|
|
4308
4456
|
break;
|
|
4457
|
+
}
|
|
4309
4458
|
case "--run-validate":
|
|
4310
4459
|
options.reportRunValidate = true;
|
|
4311
4460
|
break;
|
|
@@ -4317,7 +4466,14 @@ function parseArgs(argv, cwd) {
|
|
|
4317
4466
|
break;
|
|
4318
4467
|
}
|
|
4319
4468
|
}
|
|
4320
|
-
return { command, options };
|
|
4469
|
+
return { command, invalid, options };
|
|
4470
|
+
}
|
|
4471
|
+
function readOptionValue(args, index) {
|
|
4472
|
+
const next = args[index + 1];
|
|
4473
|
+
if (!next || next.startsWith("--")) {
|
|
4474
|
+
return null;
|
|
4475
|
+
}
|
|
4476
|
+
return next;
|
|
4321
4477
|
}
|
|
4322
4478
|
function applyFormatOption(command, value, options) {
|
|
4323
4479
|
if (!value) {
|
|
@@ -4351,9 +4507,12 @@ function applyFormatOption(command, value, options) {
|
|
|
4351
4507
|
|
|
4352
4508
|
// src/cli/main.ts
|
|
4353
4509
|
async function run(argv, cwd) {
|
|
4354
|
-
const { command, options } = parseArgs(argv, cwd);
|
|
4510
|
+
const { command, invalid, options } = parseArgs(argv, cwd);
|
|
4355
4511
|
if (!command || options.help) {
|
|
4356
4512
|
info(usage());
|
|
4513
|
+
if (invalid) {
|
|
4514
|
+
process.exitCode = 1;
|
|
4515
|
+
}
|
|
4357
4516
|
return;
|
|
4358
4517
|
}
|
|
4359
4518
|
switch (command) {
|
|
@@ -4365,6 +4524,17 @@ async function run(argv, cwd) {
|
|
|
4365
4524
|
yes: options.yes
|
|
4366
4525
|
});
|
|
4367
4526
|
return;
|
|
4527
|
+
case "analyze":
|
|
4528
|
+
{
|
|
4529
|
+
const resolvedRoot = await resolveRoot(options);
|
|
4530
|
+
const exitCode = await runAnalyze({
|
|
4531
|
+
root: resolvedRoot,
|
|
4532
|
+
list: options.analyzeList,
|
|
4533
|
+
...options.analyzePrompt !== void 0 ? { prompt: options.analyzePrompt } : {}
|
|
4534
|
+
});
|
|
4535
|
+
process.exitCode = exitCode;
|
|
4536
|
+
}
|
|
4537
|
+
return;
|
|
4368
4538
|
case "validate":
|
|
4369
4539
|
{
|
|
4370
4540
|
const resolvedRoot = await resolveRoot(options);
|
|
@@ -4411,6 +4581,7 @@ function usage() {
|
|
|
4411
4581
|
|
|
4412
4582
|
Commands:
|
|
4413
4583
|
init \u30C6\u30F3\u30D7\u30EC\u3092\u751F\u6210
|
|
4584
|
+
analyze \u610F\u5473\u30EC\u30D9\u30EB\u306E\u30EC\u30D3\u30E5\u30FC\u88DC\u52A9\uFF08\u30D7\u30ED\u30F3\u30D7\u30C8\u51FA\u529B\uFF09
|
|
4414
4585
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
4415
4586
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
4416
4587
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
@@ -4421,6 +4592,8 @@ Options:
|
|
|
4421
4592
|
--force init: .qfai/prompts \u306E\u307F\u4E0A\u66F8\u304D\uFF08\u305D\u308C\u4EE5\u5916\u306F\u65E2\u5B58\u304C\u3042\u308C\u3070\u30B9\u30AD\u30C3\u30D7\uFF09
|
|
4422
4593
|
--yes init: \u4E88\u7D04\u30D5\u30E9\u30B0\uFF08\u73FE\u72B6\u306F\u975E\u5BFE\u8A71\u306E\u305F\u3081\u6319\u52D5\u5DEE\u306A\u3057\u3002\u5C06\u6765\u306E\u5BFE\u8A71\u5C0E\u5165\u6642\u306B\u81EA\u52D5Yes\uFF09
|
|
4423
4594
|
--dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
|
|
4595
|
+
--list analyze: \u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7\u3092\u8868\u793A
|
|
4596
|
+
--prompt <name> analyze: \u6307\u5B9A\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08.md\u7701\u7565\u53EF\uFF09\u3092\u51FA\u529B
|
|
4424
4597
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4425
4598
|
--format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4426
4599
|
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|