qfai 1.0.4 → 1.0.6
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 +58 -67
- package/assets/init/.qfai/README.md +17 -76
- package/assets/init/.qfai/assistant/README.md +9 -0
- package/assets/init/.qfai/assistant/agents/README.md +36 -0
- package/assets/init/.qfai/assistant/agents/architect.md +73 -0
- package/assets/init/.qfai/assistant/agents/backend-engineer.md +73 -0
- package/assets/init/.qfai/assistant/agents/code-reviewer.md +73 -0
- package/assets/init/.qfai/assistant/agents/contract-designer.md +73 -0
- package/assets/init/.qfai/assistant/agents/devops-ci-engineer.md +73 -0
- package/assets/init/.qfai/assistant/agents/facilitator.md +74 -0
- package/assets/init/.qfai/assistant/agents/frontend-engineer.md +73 -0
- package/assets/init/.qfai/assistant/agents/interviewer.md +72 -0
- package/assets/init/.qfai/assistant/agents/planner.md +73 -0
- package/assets/init/.qfai/assistant/agents/qa-engineer.md +73 -0
- package/assets/init/.qfai/assistant/agents/requirements-analyst.md +73 -0
- package/assets/init/.qfai/assistant/agents/test-engineer.md +73 -0
- package/assets/init/.qfai/assistant/instructions/README.md +10 -0
- package/assets/init/.qfai/assistant/instructions/agent-selection.md +28 -0
- package/assets/init/.qfai/assistant/instructions/communication.md +27 -0
- package/assets/init/.qfai/assistant/instructions/constitution.md +131 -0
- package/assets/init/.qfai/assistant/instructions/quality.md +27 -0
- package/assets/init/.qfai/assistant/instructions/thinking.md +29 -0
- package/assets/init/.qfai/assistant/instructions/workflow.md +75 -0
- package/assets/init/.qfai/assistant/prompts/README.md +19 -0
- package/assets/init/.qfai/assistant/prompts/qfai-discuss.md +173 -0
- package/assets/init/.qfai/assistant/prompts/qfai-implement.md +230 -0
- package/assets/init/.qfai/assistant/prompts/qfai-pr.md +209 -0
- package/assets/init/.qfai/assistant/prompts/qfai-require.md +264 -0
- package/assets/init/.qfai/assistant/prompts/qfai-scenario-test.md +220 -0
- package/assets/init/.qfai/assistant/prompts/qfai-spec.md +291 -0
- package/assets/init/.qfai/assistant/prompts/qfai-unit-test.md +193 -0
- package/assets/init/.qfai/assistant/prompts/qfai-verify.md +222 -0
- package/assets/init/.qfai/assistant/prompts.local/README.md +8 -0
- package/assets/init/.qfai/assistant/steering/README.md +40 -0
- package/assets/init/.qfai/assistant/steering/product.md +32 -0
- package/assets/init/.qfai/assistant/steering/structure.md +34 -0
- package/assets/init/.qfai/assistant/steering/tech.md +37 -0
- package/assets/init/.qfai/contracts/README.md +7 -87
- package/assets/init/.qfai/contracts/api/README.md +8 -0
- package/assets/init/.qfai/contracts/db/README.md +25 -0
- package/assets/init/.qfai/contracts/ui/README.md +8 -0
- package/assets/init/.qfai/report/README.md +13 -0
- package/assets/init/.qfai/require/README.md +4 -26
- package/assets/init/.qfai/require/require.md +74 -0
- package/assets/init/.qfai/specs/README.md +7 -55
- package/assets/init/root/.github/workflows/qfai.yml +1 -1
- package/assets/init/root/qfai.config.yaml +3 -3
- package/dist/cli/index.cjs +306 -423
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +288 -405
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +29 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +29 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/assets/init/.qfai/contracts/api/api-0001-sample.yaml +0 -15
- package/assets/init/.qfai/contracts/db/db-0001-sample.sql +0 -7
- package/assets/init/.qfai/contracts/ui/assets/thema-001-facebook-like/assets.yaml +0 -6
- package/assets/init/.qfai/contracts/ui/assets/thema-001-facebook-like/palette.png +0 -0
- package/assets/init/.qfai/contracts/ui/assets/ui-0001-sample/assets.yaml +0 -6
- package/assets/init/.qfai/contracts/ui/assets/ui-0001-sample/snapshots/login__desktop__light__default.png +0 -0
- package/assets/init/.qfai/contracts/ui/thema-001-facebook-like.yml +0 -13
- package/assets/init/.qfai/contracts/ui/ui-0001-sample.yaml +0 -17
- package/assets/init/.qfai/out/README.md +0 -17
- package/assets/init/.qfai/promptpack/commands/implement.md +0 -8
- package/assets/init/.qfai/promptpack/commands/plan.md +0 -11
- package/assets/init/.qfai/promptpack/commands/release.md +0 -6
- package/assets/init/.qfai/promptpack/commands/review.md +0 -6
- package/assets/init/.qfai/promptpack/constitution.md +0 -15
- package/assets/init/.qfai/promptpack/roles/qa.md +0 -4
- package/assets/init/.qfai/promptpack/roles/spec.md +0 -4
- package/assets/init/.qfai/promptpack/roles/test.md +0 -4
- package/assets/init/.qfai/promptpack/steering/naming.md +0 -7
- package/assets/init/.qfai/promptpack/steering/traceability.md +0 -25
- package/assets/init/.qfai/prompts/README.md +0 -68
- package/assets/init/.qfai/prompts/analyze/README.md +0 -21
- package/assets/init/.qfai/prompts/analyze/scenario_test_consistency.md +0 -8
- package/assets/init/.qfai/prompts/analyze/scenario_to_test.md +0 -56
- package/assets/init/.qfai/prompts/analyze/spec_contract_consistency.md +0 -8
- package/assets/init/.qfai/prompts/analyze/spec_scenario_consistency.md +0 -8
- package/assets/init/.qfai/prompts/analyze/spec_to_contract.md +0 -54
- package/assets/init/.qfai/prompts/analyze/spec_to_scenario.md +0 -56
- package/assets/init/.qfai/prompts/makeBusinessFlow.md +0 -34
- package/assets/init/.qfai/prompts/makeOverview.md +0 -27
- package/assets/init/.qfai/prompts/qfai-generate-test-globs.md +0 -29
- package/assets/init/.qfai/prompts/qfai-maintain-contracts.md +0 -35
- package/assets/init/.qfai/prompts/qfai-maintain-traceability.md +0 -36
- package/assets/init/.qfai/prompts/require-to-spec.md +0 -40
- package/assets/init/.qfai/prompts.local/README.md +0 -31
- package/assets/init/.qfai/specs/spec-0001/delta.md +0 -25
- package/assets/init/.qfai/specs/spec-0001/scenario.feature +0 -11
- package/assets/init/.qfai/specs/spec-0001/spec.md +0 -40
- package/assets/init/root/tests/qfai-traceability.sample.test.ts +0 -2
package/dist/cli/index.mjs
CHANGED
|
@@ -1,229 +1,23 @@
|
|
|
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
|
-
var DEFAULT_GLOB_FILE_LIMIT = 2e4;
|
|
20
|
-
async function collectFiles(root, options = {}) {
|
|
21
|
-
const entries = [];
|
|
22
|
-
if (!await exists(root)) {
|
|
23
|
-
return entries;
|
|
24
|
-
}
|
|
25
|
-
const ignoreDirs = /* @__PURE__ */ new Set([
|
|
26
|
-
...DEFAULT_IGNORE_DIRS,
|
|
27
|
-
...options.ignoreDirs ?? []
|
|
28
|
-
]);
|
|
29
|
-
const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
|
|
30
|
-
await walk(root, root, ignoreDirs, extensions, entries);
|
|
31
|
-
return entries;
|
|
32
|
-
}
|
|
33
|
-
async function collectFilesByGlobs(root, options) {
|
|
34
|
-
const limit = normalizeLimit(options.limit);
|
|
35
|
-
if (options.globs.length === 0) {
|
|
36
|
-
return { files: [], truncated: false, matchedFileCount: 0, limit };
|
|
37
|
-
}
|
|
38
|
-
const stream = fg.stream(options.globs, {
|
|
39
|
-
cwd: root,
|
|
40
|
-
ignore: options.ignore ?? [],
|
|
41
|
-
onlyFiles: true,
|
|
42
|
-
absolute: true,
|
|
43
|
-
unique: true
|
|
44
|
-
});
|
|
45
|
-
const files = [];
|
|
46
|
-
let truncated = false;
|
|
47
|
-
for await (const entry of stream) {
|
|
48
|
-
if (files.length >= limit) {
|
|
49
|
-
truncated = true;
|
|
50
|
-
destroyStream(stream);
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
files.push(String(entry));
|
|
54
|
-
}
|
|
55
|
-
const matchedFileCount = files.length;
|
|
56
|
-
return { files, truncated, matchedFileCount, limit };
|
|
57
|
-
}
|
|
58
|
-
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
59
|
-
const items = await readdir(current, { withFileTypes: true });
|
|
60
|
-
for (const item of items) {
|
|
61
|
-
const fullPath = path.join(current, item.name);
|
|
62
|
-
if (item.isDirectory()) {
|
|
63
|
-
if (ignoreDirs.has(item.name)) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
await walk(base, fullPath, ignoreDirs, extensions, out);
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (item.isFile()) {
|
|
70
|
-
if (extensions.length > 0) {
|
|
71
|
-
const ext = path.extname(item.name).toLowerCase();
|
|
72
|
-
if (!extensions.includes(ext)) {
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
out.push(fullPath);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
async function exists(target) {
|
|
81
|
-
try {
|
|
82
|
-
await access(target);
|
|
83
|
-
return true;
|
|
84
|
-
} catch {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function normalizeLimit(value) {
|
|
89
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
90
|
-
return DEFAULT_GLOB_FILE_LIMIT;
|
|
91
|
-
}
|
|
92
|
-
const flooredValue = Math.floor(value);
|
|
93
|
-
if (flooredValue <= 0) {
|
|
94
|
-
return DEFAULT_GLOB_FILE_LIMIT;
|
|
95
|
-
}
|
|
96
|
-
return flooredValue;
|
|
97
|
-
}
|
|
98
|
-
function destroyStream(stream) {
|
|
99
|
-
if (!stream || typeof stream !== "object") {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const record2 = stream;
|
|
103
|
-
if (typeof record2.destroy === "function") {
|
|
104
|
-
record2.destroy();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// src/cli/commands/analyze.ts
|
|
109
|
-
async function runAnalyze(options) {
|
|
110
|
-
const root = path2.resolve(options.root);
|
|
111
|
-
const localDir = path2.join(root, ".qfai", "prompts.local", "analyze");
|
|
112
|
-
const standardDir = path2.join(root, ".qfai", "prompts", "analyze");
|
|
113
|
-
const available = await listPromptNames([localDir, standardDir]);
|
|
114
|
-
const promptName = normalizePromptName(options.prompt);
|
|
115
|
-
if (!promptName || options.list) {
|
|
116
|
-
emitList(available);
|
|
117
|
-
return 0;
|
|
118
|
-
}
|
|
119
|
-
const resolved = await resolvePromptPath(promptName, [localDir, standardDir]);
|
|
120
|
-
if (!resolved) {
|
|
121
|
-
emitPromptNotFound(promptName, available);
|
|
122
|
-
return 1;
|
|
123
|
-
}
|
|
124
|
-
const content = await readFile(resolved, "utf-8");
|
|
125
|
-
process.stdout.write(content);
|
|
126
|
-
if (!content.endsWith("\n")) {
|
|
127
|
-
process.stdout.write("\n");
|
|
128
|
-
}
|
|
129
|
-
return 0;
|
|
130
|
-
}
|
|
131
|
-
function normalizePromptName(value) {
|
|
132
|
-
const trimmed = (value ?? "").trim();
|
|
133
|
-
if (!trimmed) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
return trimmed.endsWith(".md") ? trimmed.slice(0, -3) : trimmed;
|
|
137
|
-
}
|
|
138
|
-
async function listPromptNames(dirs) {
|
|
139
|
-
const byName = /* @__PURE__ */ new Map();
|
|
140
|
-
for (const dir of dirs) {
|
|
141
|
-
const files = await collectFiles(dir, { extensions: [".md"] });
|
|
142
|
-
for (const abs of files) {
|
|
143
|
-
const base = path2.basename(abs);
|
|
144
|
-
if (base.toLowerCase() === "readme.md") {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const name = base.slice(0, -3);
|
|
148
|
-
if (byName.has(name)) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (await isDeprecatedPrompt(abs)) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
byName.set(name, abs);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return [...byName.keys()].sort((a, b) => a.localeCompare(b));
|
|
158
|
-
}
|
|
159
|
-
function emitList(names) {
|
|
160
|
-
process.stdout.write("# qfai analyze: prompts\n\n");
|
|
161
|
-
if (names.length === 0) {
|
|
162
|
-
process.stdout.write(
|
|
163
|
-
"\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"
|
|
164
|
-
);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
process.stdout.write("\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7:\n\n");
|
|
168
|
-
for (const name of names) {
|
|
169
|
-
process.stdout.write(`- ${name}
|
|
170
|
-
`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
async function resolvePromptPath(promptName, dirs) {
|
|
174
|
-
const filename = `${promptName}.md`;
|
|
175
|
-
for (const dir of dirs) {
|
|
176
|
-
const full = path2.join(dir, filename);
|
|
177
|
-
try {
|
|
178
|
-
await readFile(full, "utf-8");
|
|
179
|
-
return full;
|
|
180
|
-
} catch {
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
async function isDeprecatedPrompt(filePath) {
|
|
186
|
-
try {
|
|
187
|
-
const content = await readFile(filePath, "utf-8");
|
|
188
|
-
const firstLine = firstLineOf(content);
|
|
189
|
-
return firstLine.trim() === "# Deprecated";
|
|
190
|
-
} catch {
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
function firstLineOf(content) {
|
|
195
|
-
return content.match(/^[^\r\n]*/)?.[0] ?? "";
|
|
196
|
-
}
|
|
197
|
-
function emitPromptNotFound(promptName, candidates) {
|
|
198
|
-
process.stderr.write(`qfai analyze: prompt not found: ${promptName}
|
|
199
|
-
`);
|
|
200
|
-
if (candidates.length > 0) {
|
|
201
|
-
process.stderr.write("candidates:\n");
|
|
202
|
-
for (const c of candidates) {
|
|
203
|
-
process.stderr.write(`- ${c}
|
|
204
|
-
`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
3
|
// src/cli/commands/doctor.ts
|
|
210
4
|
import { mkdir, writeFile } from "fs/promises";
|
|
211
|
-
import
|
|
5
|
+
import path11 from "path";
|
|
212
6
|
|
|
213
7
|
// src/core/doctor.ts
|
|
214
8
|
import { access as access4 } from "fs/promises";
|
|
215
|
-
import
|
|
9
|
+
import path10 from "path";
|
|
216
10
|
|
|
217
11
|
// src/core/config.ts
|
|
218
|
-
import { access
|
|
219
|
-
import
|
|
12
|
+
import { access, readFile } from "fs/promises";
|
|
13
|
+
import path from "path";
|
|
220
14
|
import { parse as parseYaml } from "yaml";
|
|
221
15
|
var defaultConfig = {
|
|
222
16
|
paths: {
|
|
223
17
|
contractsDir: ".qfai/contracts",
|
|
224
18
|
specsDir: ".qfai/specs",
|
|
225
|
-
outDir: ".qfai/
|
|
226
|
-
promptsDir: ".qfai/prompts",
|
|
19
|
+
outDir: ".qfai/report",
|
|
20
|
+
promptsDir: ".qfai/assistant/prompts",
|
|
227
21
|
srcDir: "src",
|
|
228
22
|
testsDir: "tests"
|
|
229
23
|
},
|
|
@@ -251,21 +45,21 @@ var defaultConfig = {
|
|
|
251
45
|
}
|
|
252
46
|
},
|
|
253
47
|
output: {
|
|
254
|
-
validateJsonPath: ".qfai/
|
|
48
|
+
validateJsonPath: ".qfai/report/validate.json"
|
|
255
49
|
}
|
|
256
50
|
};
|
|
257
51
|
function getConfigPath(root) {
|
|
258
|
-
return
|
|
52
|
+
return path.join(root, "qfai.config.yaml");
|
|
259
53
|
}
|
|
260
54
|
async function findConfigRoot(startDir) {
|
|
261
|
-
const resolvedStart =
|
|
55
|
+
const resolvedStart = path.resolve(startDir);
|
|
262
56
|
let current = resolvedStart;
|
|
263
57
|
while (true) {
|
|
264
58
|
const configPath = getConfigPath(current);
|
|
265
|
-
if (await
|
|
59
|
+
if (await exists(configPath)) {
|
|
266
60
|
return { root: current, configPath, found: true };
|
|
267
61
|
}
|
|
268
|
-
const parent =
|
|
62
|
+
const parent = path.dirname(current);
|
|
269
63
|
if (parent === current) {
|
|
270
64
|
break;
|
|
271
65
|
}
|
|
@@ -282,7 +76,7 @@ async function loadConfig(root) {
|
|
|
282
76
|
const issues = [];
|
|
283
77
|
let parsed;
|
|
284
78
|
try {
|
|
285
|
-
const raw = await
|
|
79
|
+
const raw = await readFile(configPath, "utf-8");
|
|
286
80
|
parsed = parseYaml(raw);
|
|
287
81
|
} catch (error2) {
|
|
288
82
|
if (isMissingFile(error2)) {
|
|
@@ -295,7 +89,7 @@ async function loadConfig(root) {
|
|
|
295
89
|
return { config: normalized, issues, configPath };
|
|
296
90
|
}
|
|
297
91
|
function resolvePath(root, config, key) {
|
|
298
|
-
return
|
|
92
|
+
return path.resolve(root, config.paths[key]);
|
|
299
93
|
}
|
|
300
94
|
function normalizeConfig(raw, configPath, issues) {
|
|
301
95
|
if (!isRecord(raw)) {
|
|
@@ -588,9 +382,9 @@ function isMissingFile(error2) {
|
|
|
588
382
|
}
|
|
589
383
|
return false;
|
|
590
384
|
}
|
|
591
|
-
async function
|
|
385
|
+
async function exists(target) {
|
|
592
386
|
try {
|
|
593
|
-
await
|
|
387
|
+
await access(target);
|
|
594
388
|
return true;
|
|
595
389
|
} catch {
|
|
596
390
|
return false;
|
|
@@ -608,26 +402,127 @@ function isRecord(value) {
|
|
|
608
402
|
|
|
609
403
|
// src/core/discovery.ts
|
|
610
404
|
import { access as access3 } from "fs/promises";
|
|
611
|
-
import
|
|
405
|
+
import path4 from "path";
|
|
406
|
+
|
|
407
|
+
// src/core/fs.ts
|
|
408
|
+
import { access as access2, readdir } from "fs/promises";
|
|
409
|
+
import path2 from "path";
|
|
410
|
+
import fg from "fast-glob";
|
|
411
|
+
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
412
|
+
"node_modules",
|
|
413
|
+
".git",
|
|
414
|
+
"dist",
|
|
415
|
+
".pnpm",
|
|
416
|
+
"tmp",
|
|
417
|
+
".mcp-tools"
|
|
418
|
+
]);
|
|
419
|
+
var DEFAULT_GLOB_FILE_LIMIT = 2e4;
|
|
420
|
+
async function collectFiles(root, options = {}) {
|
|
421
|
+
const entries = [];
|
|
422
|
+
if (!await exists2(root)) {
|
|
423
|
+
return entries;
|
|
424
|
+
}
|
|
425
|
+
const ignoreDirs = /* @__PURE__ */ new Set([
|
|
426
|
+
...DEFAULT_IGNORE_DIRS,
|
|
427
|
+
...options.ignoreDirs ?? []
|
|
428
|
+
]);
|
|
429
|
+
const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
|
|
430
|
+
await walk(root, root, ignoreDirs, extensions, entries);
|
|
431
|
+
return entries;
|
|
432
|
+
}
|
|
433
|
+
async function collectFilesByGlobs(root, options) {
|
|
434
|
+
const limit = normalizeLimit(options.limit);
|
|
435
|
+
if (options.globs.length === 0) {
|
|
436
|
+
return { files: [], truncated: false, matchedFileCount: 0, limit };
|
|
437
|
+
}
|
|
438
|
+
const stream = fg.stream(options.globs, {
|
|
439
|
+
cwd: root,
|
|
440
|
+
ignore: options.ignore ?? [],
|
|
441
|
+
onlyFiles: true,
|
|
442
|
+
absolute: true,
|
|
443
|
+
unique: true
|
|
444
|
+
});
|
|
445
|
+
const files = [];
|
|
446
|
+
let truncated = false;
|
|
447
|
+
for await (const entry of stream) {
|
|
448
|
+
if (files.length >= limit) {
|
|
449
|
+
truncated = true;
|
|
450
|
+
destroyStream(stream);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
files.push(String(entry));
|
|
454
|
+
}
|
|
455
|
+
const matchedFileCount = files.length;
|
|
456
|
+
return { files, truncated, matchedFileCount, limit };
|
|
457
|
+
}
|
|
458
|
+
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
459
|
+
const items = await readdir(current, { withFileTypes: true });
|
|
460
|
+
for (const item of items) {
|
|
461
|
+
const fullPath = path2.join(current, item.name);
|
|
462
|
+
if (item.isDirectory()) {
|
|
463
|
+
if (ignoreDirs.has(item.name)) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
await walk(base, fullPath, ignoreDirs, extensions, out);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (item.isFile()) {
|
|
470
|
+
if (extensions.length > 0) {
|
|
471
|
+
const ext = path2.extname(item.name).toLowerCase();
|
|
472
|
+
if (!extensions.includes(ext)) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
out.push(fullPath);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
async function exists2(target) {
|
|
481
|
+
try {
|
|
482
|
+
await access2(target);
|
|
483
|
+
return true;
|
|
484
|
+
} catch {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function normalizeLimit(value) {
|
|
489
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
490
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
491
|
+
}
|
|
492
|
+
const flooredValue = Math.floor(value);
|
|
493
|
+
if (flooredValue <= 0) {
|
|
494
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
495
|
+
}
|
|
496
|
+
return flooredValue;
|
|
497
|
+
}
|
|
498
|
+
function destroyStream(stream) {
|
|
499
|
+
if (!stream || typeof stream !== "object") {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const record2 = stream;
|
|
503
|
+
if (typeof record2.destroy === "function") {
|
|
504
|
+
record2.destroy();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
612
507
|
|
|
613
508
|
// src/core/specLayout.ts
|
|
614
509
|
import { readdir as readdir2 } from "fs/promises";
|
|
615
|
-
import
|
|
510
|
+
import path3 from "path";
|
|
616
511
|
var SPEC_DIR_RE = /^spec-\d{4}$/;
|
|
617
512
|
async function collectSpecEntries(specsRoot) {
|
|
618
513
|
const dirs = await listSpecDirs(specsRoot);
|
|
619
514
|
const entries = dirs.map((dir) => ({
|
|
620
515
|
dir,
|
|
621
|
-
specPath:
|
|
622
|
-
deltaPath:
|
|
623
|
-
scenarioPath:
|
|
516
|
+
specPath: path3.join(dir, "spec.md"),
|
|
517
|
+
deltaPath: path3.join(dir, "delta.md"),
|
|
518
|
+
scenarioPath: path3.join(dir, "scenario.feature")
|
|
624
519
|
}));
|
|
625
520
|
return entries.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
626
521
|
}
|
|
627
522
|
async function listSpecDirs(specsRoot) {
|
|
628
523
|
try {
|
|
629
524
|
const items = await readdir2(specsRoot, { withFileTypes: true });
|
|
630
|
-
return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) =>
|
|
525
|
+
return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => path3.join(specsRoot, name));
|
|
631
526
|
} catch (error2) {
|
|
632
527
|
if (isMissingFileError(error2)) {
|
|
633
528
|
return [];
|
|
@@ -698,20 +593,20 @@ async function exists3(target) {
|
|
|
698
593
|
function filterByBasenamePrefix(files, prefix) {
|
|
699
594
|
const lowerPrefix = prefix.toLowerCase();
|
|
700
595
|
return files.filter(
|
|
701
|
-
(file) =>
|
|
596
|
+
(file) => path4.basename(file).toLowerCase().startsWith(lowerPrefix)
|
|
702
597
|
);
|
|
703
598
|
}
|
|
704
599
|
|
|
705
600
|
// src/core/paths.ts
|
|
706
|
-
import
|
|
601
|
+
import path5 from "path";
|
|
707
602
|
function toRelativePath(root, target) {
|
|
708
603
|
if (!target) {
|
|
709
604
|
return target;
|
|
710
605
|
}
|
|
711
|
-
if (!
|
|
606
|
+
if (!path5.isAbsolute(target)) {
|
|
712
607
|
return toPosixPath(target);
|
|
713
608
|
}
|
|
714
|
-
const relative =
|
|
609
|
+
const relative = path5.relative(root, target);
|
|
715
610
|
if (!relative) {
|
|
716
611
|
return ".";
|
|
717
612
|
}
|
|
@@ -722,8 +617,8 @@ function toPosixPath(value) {
|
|
|
722
617
|
}
|
|
723
618
|
|
|
724
619
|
// src/core/traceability.ts
|
|
725
|
-
import { readFile as
|
|
726
|
-
import
|
|
620
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
621
|
+
import path6 from "path";
|
|
727
622
|
|
|
728
623
|
// src/core/gherkin/parse.ts
|
|
729
624
|
import {
|
|
@@ -879,7 +774,7 @@ function extractAnnotatedScIds(text) {
|
|
|
879
774
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
880
775
|
const scIds = /* @__PURE__ */ new Set();
|
|
881
776
|
for (const file of scenarioFiles) {
|
|
882
|
-
const text = await
|
|
777
|
+
const text = await readFile2(file, "utf-8");
|
|
883
778
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
884
779
|
if (!document || errors.length > 0) {
|
|
885
780
|
continue;
|
|
@@ -897,7 +792,7 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
897
792
|
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
898
793
|
const sources = /* @__PURE__ */ new Map();
|
|
899
794
|
for (const file of scenarioFiles) {
|
|
900
|
-
const text = await
|
|
795
|
+
const text = await readFile2(file, "utf-8");
|
|
901
796
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
902
797
|
if (!document || errors.length > 0) {
|
|
903
798
|
continue;
|
|
@@ -955,10 +850,10 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
955
850
|
};
|
|
956
851
|
}
|
|
957
852
|
const normalizedFiles = Array.from(
|
|
958
|
-
new Set(scanResult.files.map((file) =>
|
|
853
|
+
new Set(scanResult.files.map((file) => path6.normalize(file)))
|
|
959
854
|
);
|
|
960
855
|
for (const file of normalizedFiles) {
|
|
961
|
-
const text = await
|
|
856
|
+
const text = await readFile2(file, "utf-8");
|
|
962
857
|
const scIds = extractAnnotatedScIds(text);
|
|
963
858
|
if (scIds.length === 0) {
|
|
964
859
|
continue;
|
|
@@ -1017,20 +912,20 @@ function formatError3(error2) {
|
|
|
1017
912
|
}
|
|
1018
913
|
|
|
1019
914
|
// src/core/promptsIntegrity.ts
|
|
1020
|
-
import { readFile as
|
|
1021
|
-
import
|
|
915
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
916
|
+
import path8 from "path";
|
|
1022
917
|
|
|
1023
918
|
// src/shared/assets.ts
|
|
1024
919
|
import { existsSync } from "fs";
|
|
1025
|
-
import
|
|
920
|
+
import path7 from "path";
|
|
1026
921
|
import { fileURLToPath } from "url";
|
|
1027
922
|
function getInitAssetsDir() {
|
|
1028
923
|
const base = import.meta.url;
|
|
1029
924
|
const basePath = base.startsWith("file:") ? fileURLToPath(base) : base;
|
|
1030
|
-
const baseDir =
|
|
925
|
+
const baseDir = path7.dirname(basePath);
|
|
1031
926
|
const candidates = [
|
|
1032
|
-
|
|
1033
|
-
|
|
927
|
+
path7.resolve(baseDir, "../../../assets/init"),
|
|
928
|
+
path7.resolve(baseDir, "../../assets/init")
|
|
1034
929
|
];
|
|
1035
930
|
for (const candidate of candidates) {
|
|
1036
931
|
if (existsSync(candidate)) {
|
|
@@ -1048,11 +943,24 @@ function getInitAssetsDir() {
|
|
|
1048
943
|
|
|
1049
944
|
// src/core/promptsIntegrity.ts
|
|
1050
945
|
var LEGACY_OK_EXTRA = /* @__PURE__ */ new Set(["qfai-classify-change.md"]);
|
|
1051
|
-
async function diffProjectPromptsAgainstInitAssets(root) {
|
|
1052
|
-
const
|
|
946
|
+
async function diffProjectPromptsAgainstInitAssets(root, config) {
|
|
947
|
+
const promptsDirConfig = config.paths.promptsDir;
|
|
948
|
+
const promptsDir = path8.isAbsolute(promptsDirConfig) ? promptsDirConfig : path8.resolve(root, promptsDirConfig);
|
|
1053
949
|
let templateDir;
|
|
1054
950
|
try {
|
|
1055
|
-
|
|
951
|
+
const rel = path8.isAbsolute(promptsDirConfig) ? path8.relative(root, promptsDirConfig) : promptsDirConfig;
|
|
952
|
+
const normalized = rel.replace(/^[\\/]+/, "");
|
|
953
|
+
if (normalized.length === 0 || normalized.startsWith("..")) {
|
|
954
|
+
return {
|
|
955
|
+
status: "skipped_missing_assets",
|
|
956
|
+
promptsDir,
|
|
957
|
+
templateDir: "",
|
|
958
|
+
missing: [],
|
|
959
|
+
extra: [],
|
|
960
|
+
changed: []
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
templateDir = path8.join(getInitAssetsDir(), normalized);
|
|
1056
964
|
} catch {
|
|
1057
965
|
return {
|
|
1058
966
|
status: "skipped_missing_assets",
|
|
@@ -1106,8 +1014,8 @@ async function diffProjectPromptsAgainstInitAssets(root) {
|
|
|
1106
1014
|
}
|
|
1107
1015
|
try {
|
|
1108
1016
|
const [a, b] = await Promise.all([
|
|
1109
|
-
|
|
1110
|
-
|
|
1017
|
+
readFile3(templateAbs, "utf-8"),
|
|
1018
|
+
readFile3(projectAbs, "utf-8")
|
|
1111
1019
|
]);
|
|
1112
1020
|
if (normalizeNewlines(a) !== normalizeNewlines(b)) {
|
|
1113
1021
|
changed.push(rel);
|
|
@@ -1130,7 +1038,7 @@ function normalizeNewlines(text) {
|
|
|
1130
1038
|
return text.replace(/\r\n/g, "\n");
|
|
1131
1039
|
}
|
|
1132
1040
|
function toRel(base, abs) {
|
|
1133
|
-
const rel =
|
|
1041
|
+
const rel = path8.relative(base, abs);
|
|
1134
1042
|
return rel.replace(/[\\/]+/g, "/");
|
|
1135
1043
|
}
|
|
1136
1044
|
function intersectKeys(a, b) {
|
|
@@ -1144,16 +1052,16 @@ function intersectKeys(a, b) {
|
|
|
1144
1052
|
}
|
|
1145
1053
|
|
|
1146
1054
|
// src/core/version.ts
|
|
1147
|
-
import { readFile as
|
|
1148
|
-
import
|
|
1055
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1056
|
+
import path9 from "path";
|
|
1149
1057
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1150
1058
|
async function resolveToolVersion() {
|
|
1151
|
-
if ("1.0.
|
|
1152
|
-
return "1.0.
|
|
1059
|
+
if ("1.0.6".length > 0) {
|
|
1060
|
+
return "1.0.6";
|
|
1153
1061
|
}
|
|
1154
1062
|
try {
|
|
1155
1063
|
const packagePath = resolvePackageJsonPath();
|
|
1156
|
-
const raw = await
|
|
1064
|
+
const raw = await readFile4(packagePath, "utf-8");
|
|
1157
1065
|
const parsed = JSON.parse(raw);
|
|
1158
1066
|
const version = typeof parsed.version === "string" ? parsed.version : "";
|
|
1159
1067
|
return version.length > 0 ? version : "unknown";
|
|
@@ -1164,7 +1072,7 @@ async function resolveToolVersion() {
|
|
|
1164
1072
|
function resolvePackageJsonPath() {
|
|
1165
1073
|
const base = import.meta.url;
|
|
1166
1074
|
const basePath = base.startsWith("file:") ? fileURLToPath2(base) : base;
|
|
1167
|
-
return
|
|
1075
|
+
return path9.resolve(path9.dirname(basePath), "../../package.json");
|
|
1168
1076
|
}
|
|
1169
1077
|
|
|
1170
1078
|
// src/core/doctor.ts
|
|
@@ -1190,7 +1098,7 @@ function normalizeGlobs2(values) {
|
|
|
1190
1098
|
return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
1191
1099
|
}
|
|
1192
1100
|
async function createDoctorData(options) {
|
|
1193
|
-
const startDir =
|
|
1101
|
+
const startDir = path10.resolve(options.startDir);
|
|
1194
1102
|
const checks = [];
|
|
1195
1103
|
const configPath = getConfigPath(startDir);
|
|
1196
1104
|
const search = options.rootExplicit ? {
|
|
@@ -1252,9 +1160,9 @@ async function createDoctorData(options) {
|
|
|
1252
1160
|
details: { path: toRelativePath(root, resolved) }
|
|
1253
1161
|
});
|
|
1254
1162
|
if (key === "promptsDir") {
|
|
1255
|
-
const promptsLocalDir =
|
|
1256
|
-
|
|
1257
|
-
`${
|
|
1163
|
+
const promptsLocalDir = path10.join(
|
|
1164
|
+
path10.dirname(resolved),
|
|
1165
|
+
`${path10.basename(resolved)}.local`
|
|
1258
1166
|
);
|
|
1259
1167
|
const found = await exists4(promptsLocalDir);
|
|
1260
1168
|
addCheck(checks, {
|
|
@@ -1264,12 +1172,12 @@ async function createDoctorData(options) {
|
|
|
1264
1172
|
message: found ? "prompts.local exists (overlay can be used)" : "prompts.local is optional (create it to override prompts)",
|
|
1265
1173
|
details: { path: toRelativePath(root, promptsLocalDir) }
|
|
1266
1174
|
});
|
|
1267
|
-
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
1175
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root, config);
|
|
1268
1176
|
if (diff.status === "skipped_missing_prompts") {
|
|
1269
1177
|
addCheck(checks, {
|
|
1270
1178
|
id: "prompts.integrity",
|
|
1271
1179
|
severity: "info",
|
|
1272
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1180
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1273
1181
|
message: "prompts \u304C\u672A\u4F5C\u6210\u306E\u305F\u3081\u691C\u67FB\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F\uFF08'qfai init' \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
|
|
1274
1182
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1275
1183
|
});
|
|
@@ -1277,7 +1185,7 @@ async function createDoctorData(options) {
|
|
|
1277
1185
|
addCheck(checks, {
|
|
1278
1186
|
id: "prompts.integrity",
|
|
1279
1187
|
severity: "info",
|
|
1280
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1188
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1281
1189
|
message: "init assets \u304C\u898B\u3064\u304B\u3089\u306A\u3044\u305F\u3081\u691C\u67FB\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F\uFF08\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\uFF09",
|
|
1282
1190
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1283
1191
|
});
|
|
@@ -1285,7 +1193,7 @@ async function createDoctorData(options) {
|
|
|
1285
1193
|
addCheck(checks, {
|
|
1286
1194
|
id: "prompts.integrity",
|
|
1287
1195
|
severity: "ok",
|
|
1288
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1196
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1289
1197
|
message: "\u6A19\u6E96 assets \u3068\u4E00\u81F4\u3057\u3066\u3044\u307E\u3059",
|
|
1290
1198
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1291
1199
|
});
|
|
@@ -1293,15 +1201,15 @@ async function createDoctorData(options) {
|
|
|
1293
1201
|
addCheck(checks, {
|
|
1294
1202
|
id: "prompts.integrity",
|
|
1295
1203
|
severity: "error",
|
|
1296
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1297
|
-
message: "\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\u3002prompts \u306E\u76F4\u7DE8\u96C6\u306F\u975E\u63A8\u5968\u3067\u3059\uFF08\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8/\u518D init \u3067\u4E0A\u66F8\u304D\u3055\u308C\u5F97\u307E\u3059\uFF09\u3002",
|
|
1204
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1205
|
+
message: "\u6A19\u6E96\u8CC7\u7523 '.qfai/assistant/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\u3002prompts \u306E\u76F4\u7DE8\u96C6\u306F\u975E\u63A8\u5968\u3067\u3059\uFF08\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8/\u518D init \u3067\u4E0A\u66F8\u304D\u3055\u308C\u5F97\u307E\u3059\uFF09\u3002",
|
|
1298
1206
|
details: {
|
|
1299
1207
|
promptsDir: toRelativePath(root, diff.promptsDir),
|
|
1300
1208
|
missing: diff.missing,
|
|
1301
1209
|
extra: diff.extra,
|
|
1302
1210
|
changed: diff.changed,
|
|
1303
1211
|
nextActions: [
|
|
1304
|
-
"\u5909\u66F4\u5185\u5BB9\u3092 .qfai/prompts.local/** \u306B\u79FB\u3059\uFF08\u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067\u914D\u7F6E\uFF09",
|
|
1212
|
+
"\u5909\u66F4\u5185\u5BB9\u3092 .qfai/assistant/prompts.local/** \u306B\u79FB\u3059\uFF08\u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067\u914D\u7F6E\uFF09",
|
|
1305
1213
|
"\u5FC5\u8981\u306A\u3089 qfai init --force \u3067 prompts \u3092\u6A19\u6E96\u72B6\u614B\u3078\u623B\u3059\uFF08prompts.local \u306F\u4FDD\u8B77\u3055\u308C\u307E\u3059\uFF09"
|
|
1306
1214
|
]
|
|
1307
1215
|
}
|
|
@@ -1327,7 +1235,7 @@ async function createDoctorData(options) {
|
|
|
1327
1235
|
message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
|
|
1328
1236
|
details: { specPacks: entries.length, missingFiles }
|
|
1329
1237
|
});
|
|
1330
|
-
const validateJsonAbs =
|
|
1238
|
+
const validateJsonAbs = path10.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : path10.resolve(root, config.output.validateJsonPath);
|
|
1331
1239
|
const validateJsonExists = await exists4(validateJsonAbs);
|
|
1332
1240
|
addCheck(checks, {
|
|
1333
1241
|
id: "output.validateJson",
|
|
@@ -1337,8 +1245,8 @@ async function createDoctorData(options) {
|
|
|
1337
1245
|
details: { path: toRelativePath(root, validateJsonAbs) }
|
|
1338
1246
|
});
|
|
1339
1247
|
const outDirAbs = resolvePath(root, config, "outDir");
|
|
1340
|
-
const rel =
|
|
1341
|
-
const inside = rel !== "" && !rel.startsWith("..") && !
|
|
1248
|
+
const rel = path10.relative(outDirAbs, validateJsonAbs);
|
|
1249
|
+
const inside = rel !== "" && !rel.startsWith("..") && !path10.isAbsolute(rel);
|
|
1342
1250
|
addCheck(checks, {
|
|
1343
1251
|
id: "output.pathAlignment",
|
|
1344
1252
|
severity: inside ? "ok" : "warning",
|
|
@@ -1461,12 +1369,12 @@ async function detectOutDirCollisions(root) {
|
|
|
1461
1369
|
});
|
|
1462
1370
|
const configPaths = configScan.files;
|
|
1463
1371
|
const configRoots = Array.from(
|
|
1464
|
-
new Set(configPaths.map((configPath) =>
|
|
1372
|
+
new Set(configPaths.map((configPath) => path10.dirname(configPath)))
|
|
1465
1373
|
).sort((a, b) => a.localeCompare(b));
|
|
1466
1374
|
const outDirToRoots = /* @__PURE__ */ new Map();
|
|
1467
1375
|
for (const configRoot of configRoots) {
|
|
1468
1376
|
const { config } = await loadConfig(configRoot);
|
|
1469
|
-
const outDir =
|
|
1377
|
+
const outDir = path10.normalize(resolvePath(configRoot, config, "outDir"));
|
|
1470
1378
|
const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
|
|
1471
1379
|
roots.add(configRoot);
|
|
1472
1380
|
outDirToRoots.set(outDir, roots);
|
|
@@ -1492,20 +1400,20 @@ async function detectOutDirCollisions(root) {
|
|
|
1492
1400
|
};
|
|
1493
1401
|
}
|
|
1494
1402
|
async function findMonorepoRoot(startDir) {
|
|
1495
|
-
let current =
|
|
1403
|
+
let current = path10.resolve(startDir);
|
|
1496
1404
|
while (true) {
|
|
1497
|
-
const gitPath =
|
|
1498
|
-
const workspacePath =
|
|
1405
|
+
const gitPath = path10.join(current, ".git");
|
|
1406
|
+
const workspacePath = path10.join(current, "pnpm-workspace.yaml");
|
|
1499
1407
|
if (await exists4(gitPath) || await exists4(workspacePath)) {
|
|
1500
1408
|
return current;
|
|
1501
1409
|
}
|
|
1502
|
-
const parent =
|
|
1410
|
+
const parent = path10.dirname(current);
|
|
1503
1411
|
if (parent === current) {
|
|
1504
1412
|
break;
|
|
1505
1413
|
}
|
|
1506
1414
|
current = parent;
|
|
1507
1415
|
}
|
|
1508
|
-
return
|
|
1416
|
+
return path10.resolve(startDir);
|
|
1509
1417
|
}
|
|
1510
1418
|
|
|
1511
1419
|
// src/cli/lib/logger.ts
|
|
@@ -1547,8 +1455,8 @@ async function runDoctor(options) {
|
|
|
1547
1455
|
const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
|
|
1548
1456
|
const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
|
|
1549
1457
|
if (options.outPath) {
|
|
1550
|
-
const outAbs =
|
|
1551
|
-
await mkdir(
|
|
1458
|
+
const outAbs = path11.isAbsolute(options.outPath) ? options.outPath : path11.resolve(process.cwd(), options.outPath);
|
|
1459
|
+
await mkdir(path11.dirname(outAbs), { recursive: true });
|
|
1552
1460
|
await writeFile(outAbs, `${output}
|
|
1553
1461
|
`, "utf-8");
|
|
1554
1462
|
info(`doctor: wrote ${outAbs}`);
|
|
@@ -1568,11 +1476,11 @@ function shouldFailDoctor(summary, failOn) {
|
|
|
1568
1476
|
}
|
|
1569
1477
|
|
|
1570
1478
|
// src/cli/commands/init.ts
|
|
1571
|
-
import
|
|
1479
|
+
import path13 from "path";
|
|
1572
1480
|
|
|
1573
1481
|
// src/cli/lib/fs.ts
|
|
1574
1482
|
import { access as access5, copyFile, mkdir as mkdir2, readdir as readdir3 } from "fs/promises";
|
|
1575
|
-
import
|
|
1483
|
+
import path12 from "path";
|
|
1576
1484
|
async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
1577
1485
|
const files = await collectTemplateFiles(sourceRoot);
|
|
1578
1486
|
return copyFiles(files, sourceRoot, destRoot, options);
|
|
@@ -1580,7 +1488,7 @@ async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
|
1580
1488
|
async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
|
|
1581
1489
|
const allFiles = [];
|
|
1582
1490
|
for (const relPath of relativePaths) {
|
|
1583
|
-
const fullPath =
|
|
1491
|
+
const fullPath = path12.join(sourceRoot, relPath);
|
|
1584
1492
|
const files = await collectTemplateFiles(fullPath);
|
|
1585
1493
|
allFiles.push(...files);
|
|
1586
1494
|
}
|
|
@@ -1590,13 +1498,13 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1590
1498
|
const copied = [];
|
|
1591
1499
|
const skipped = [];
|
|
1592
1500
|
const conflicts = [];
|
|
1593
|
-
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
1594
|
-
const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
1501
|
+
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path12.sep);
|
|
1502
|
+
const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + path12.sep);
|
|
1595
1503
|
const isProtectedRelative = (relative) => {
|
|
1596
1504
|
if (protectPrefixes.length === 0) {
|
|
1597
1505
|
return false;
|
|
1598
1506
|
}
|
|
1599
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
1507
|
+
const normalized = relative.replace(/[\\/]+/g, path12.sep);
|
|
1600
1508
|
return protectPrefixes.some(
|
|
1601
1509
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1602
1510
|
);
|
|
@@ -1605,7 +1513,7 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1605
1513
|
if (excludePrefixes.length === 0) {
|
|
1606
1514
|
return false;
|
|
1607
1515
|
}
|
|
1608
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
1516
|
+
const normalized = relative.replace(/[\\/]+/g, path12.sep);
|
|
1609
1517
|
return excludePrefixes.some(
|
|
1610
1518
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1611
1519
|
);
|
|
@@ -1613,14 +1521,14 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1613
1521
|
const conflictPolicy = options.conflictPolicy ?? "error";
|
|
1614
1522
|
if (!options.force && conflictPolicy === "error") {
|
|
1615
1523
|
for (const file of files) {
|
|
1616
|
-
const relative =
|
|
1524
|
+
const relative = path12.relative(sourceRoot, file);
|
|
1617
1525
|
if (isExcludedRelative(relative)) {
|
|
1618
1526
|
continue;
|
|
1619
1527
|
}
|
|
1620
1528
|
if (isProtectedRelative(relative)) {
|
|
1621
1529
|
continue;
|
|
1622
1530
|
}
|
|
1623
|
-
const dest =
|
|
1531
|
+
const dest = path12.join(destRoot, relative);
|
|
1624
1532
|
if (!await shouldWrite(dest, options.force)) {
|
|
1625
1533
|
conflicts.push(dest);
|
|
1626
1534
|
}
|
|
@@ -1630,18 +1538,18 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1630
1538
|
}
|
|
1631
1539
|
}
|
|
1632
1540
|
for (const file of files) {
|
|
1633
|
-
const relative =
|
|
1541
|
+
const relative = path12.relative(sourceRoot, file);
|
|
1634
1542
|
if (isExcludedRelative(relative)) {
|
|
1635
1543
|
continue;
|
|
1636
1544
|
}
|
|
1637
|
-
const dest =
|
|
1545
|
+
const dest = path12.join(destRoot, relative);
|
|
1638
1546
|
const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
|
|
1639
1547
|
if (!await shouldWrite(dest, forceForThisFile)) {
|
|
1640
1548
|
skipped.push(dest);
|
|
1641
1549
|
continue;
|
|
1642
1550
|
}
|
|
1643
1551
|
if (!options.dryRun) {
|
|
1644
|
-
await mkdir2(
|
|
1552
|
+
await mkdir2(path12.dirname(dest), { recursive: true });
|
|
1645
1553
|
await copyFile(file, dest);
|
|
1646
1554
|
}
|
|
1647
1555
|
copied.push(dest);
|
|
@@ -1665,7 +1573,7 @@ async function collectTemplateFiles(root) {
|
|
|
1665
1573
|
}
|
|
1666
1574
|
const items = await readdir3(root, { withFileTypes: true });
|
|
1667
1575
|
for (const item of items) {
|
|
1668
|
-
const fullPath =
|
|
1576
|
+
const fullPath = path12.join(root, item.name);
|
|
1669
1577
|
if (item.isDirectory()) {
|
|
1670
1578
|
const nested = await collectTemplateFiles(fullPath);
|
|
1671
1579
|
entries.push(...nested);
|
|
@@ -1695,13 +1603,13 @@ async function exists5(target) {
|
|
|
1695
1603
|
// src/cli/commands/init.ts
|
|
1696
1604
|
async function runInit(options) {
|
|
1697
1605
|
const assetsRoot = getInitAssetsDir();
|
|
1698
|
-
const rootAssets =
|
|
1699
|
-
const qfaiAssets =
|
|
1700
|
-
const destRoot =
|
|
1701
|
-
const destQfai =
|
|
1606
|
+
const rootAssets = path13.join(assetsRoot, "root");
|
|
1607
|
+
const qfaiAssets = path13.join(assetsRoot, ".qfai");
|
|
1608
|
+
const destRoot = path13.resolve(options.dir);
|
|
1609
|
+
const destQfai = path13.join(destRoot, ".qfai");
|
|
1702
1610
|
if (options.force) {
|
|
1703
1611
|
info(
|
|
1704
|
-
"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"
|
|
1612
|
+
"NOTE: --force \u306F .qfai/assistant/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"
|
|
1705
1613
|
);
|
|
1706
1614
|
}
|
|
1707
1615
|
const rootResult = await copyTemplateTree(rootAssets, destRoot, {
|
|
@@ -1713,18 +1621,18 @@ async function runInit(options) {
|
|
|
1713
1621
|
force: false,
|
|
1714
1622
|
dryRun: options.dryRun,
|
|
1715
1623
|
conflictPolicy: "skip",
|
|
1716
|
-
protect: ["prompts.local"],
|
|
1717
|
-
exclude: ["prompts"]
|
|
1624
|
+
protect: ["assistant/prompts.local"],
|
|
1625
|
+
exclude: ["assistant/prompts"]
|
|
1718
1626
|
});
|
|
1719
1627
|
const promptsResult = await copyTemplatePaths(
|
|
1720
1628
|
qfaiAssets,
|
|
1721
1629
|
destQfai,
|
|
1722
|
-
["prompts"],
|
|
1630
|
+
["assistant/prompts"],
|
|
1723
1631
|
{
|
|
1724
1632
|
force: options.force,
|
|
1725
1633
|
dryRun: options.dryRun,
|
|
1726
1634
|
conflictPolicy: "skip",
|
|
1727
|
-
protect: ["prompts.local"]
|
|
1635
|
+
protect: ["assistant/prompts.local"]
|
|
1728
1636
|
}
|
|
1729
1637
|
);
|
|
1730
1638
|
report(
|
|
@@ -1745,8 +1653,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
1745
1653
|
}
|
|
1746
1654
|
|
|
1747
1655
|
// src/cli/commands/report.ts
|
|
1748
|
-
import { mkdir as mkdir3, readFile as
|
|
1749
|
-
import
|
|
1656
|
+
import { mkdir as mkdir3, readFile as readFile13, writeFile as writeFile2 } from "fs/promises";
|
|
1657
|
+
import path21 from "path";
|
|
1750
1658
|
|
|
1751
1659
|
// src/core/normalize.ts
|
|
1752
1660
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1786,12 +1694,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1786
1694
|
}
|
|
1787
1695
|
|
|
1788
1696
|
// src/core/report.ts
|
|
1789
|
-
import { readFile as
|
|
1790
|
-
import
|
|
1697
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
1698
|
+
import path20 from "path";
|
|
1791
1699
|
|
|
1792
1700
|
// src/core/contractIndex.ts
|
|
1793
|
-
import { readFile as
|
|
1794
|
-
import
|
|
1701
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1702
|
+
import path14 from "path";
|
|
1795
1703
|
|
|
1796
1704
|
// src/core/contractsDecl.ts
|
|
1797
1705
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1813,9 +1721,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1813
1721
|
// src/core/contractIndex.ts
|
|
1814
1722
|
async function buildContractIndex(root, config) {
|
|
1815
1723
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1816
|
-
const uiRoot =
|
|
1817
|
-
const apiRoot =
|
|
1818
|
-
const dbRoot =
|
|
1724
|
+
const uiRoot = path14.join(contractsRoot, "ui");
|
|
1725
|
+
const apiRoot = path14.join(contractsRoot, "api");
|
|
1726
|
+
const dbRoot = path14.join(contractsRoot, "db");
|
|
1819
1727
|
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1820
1728
|
collectUiContractFiles(uiRoot),
|
|
1821
1729
|
collectThemaContractFiles(uiRoot),
|
|
@@ -1835,7 +1743,7 @@ async function buildContractIndex(root, config) {
|
|
|
1835
1743
|
}
|
|
1836
1744
|
async function indexContractFiles(files, index) {
|
|
1837
1745
|
for (const file of files) {
|
|
1838
|
-
const text = await
|
|
1746
|
+
const text = await readFile5(file, "utf-8");
|
|
1839
1747
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1840
1748
|
}
|
|
1841
1749
|
}
|
|
@@ -2080,14 +1988,14 @@ function parseSpec(md, file) {
|
|
|
2080
1988
|
}
|
|
2081
1989
|
|
|
2082
1990
|
// src/core/validators/contracts.ts
|
|
2083
|
-
import { access as access6, readFile as
|
|
2084
|
-
import
|
|
1991
|
+
import { access as access6, readFile as readFile6 } from "fs/promises";
|
|
1992
|
+
import path16 from "path";
|
|
2085
1993
|
|
|
2086
1994
|
// src/core/contracts.ts
|
|
2087
|
-
import
|
|
1995
|
+
import path15 from "path";
|
|
2088
1996
|
import { parse as parseYaml2 } from "yaml";
|
|
2089
1997
|
function parseStructuredContract(file, text) {
|
|
2090
|
-
const ext =
|
|
1998
|
+
const ext = path15.extname(file).toLowerCase();
|
|
2091
1999
|
if (ext === ".json") {
|
|
2092
2000
|
return JSON.parse(text);
|
|
2093
2001
|
}
|
|
@@ -2109,14 +2017,14 @@ async function validateContracts(root, config) {
|
|
|
2109
2017
|
const issues = [];
|
|
2110
2018
|
const contractIndex = await buildContractIndex(root, config);
|
|
2111
2019
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2112
|
-
const uiRoot =
|
|
2020
|
+
const uiRoot = path16.join(contractsRoot, "ui");
|
|
2113
2021
|
const themaIds = new Set(
|
|
2114
2022
|
Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
|
|
2115
2023
|
);
|
|
2116
2024
|
issues.push(...await validateUiContracts(uiRoot, themaIds));
|
|
2117
2025
|
issues.push(...await validateThemaContracts(uiRoot));
|
|
2118
|
-
issues.push(...await validateApiContracts(
|
|
2119
|
-
issues.push(...await validateDbContracts(
|
|
2026
|
+
issues.push(...await validateApiContracts(path16.join(contractsRoot, "api")));
|
|
2027
|
+
issues.push(...await validateDbContracts(path16.join(contractsRoot, "db")));
|
|
2120
2028
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
2121
2029
|
return issues;
|
|
2122
2030
|
}
|
|
@@ -2135,7 +2043,7 @@ async function validateUiContracts(uiRoot, themaIds) {
|
|
|
2135
2043
|
}
|
|
2136
2044
|
const issues = [];
|
|
2137
2045
|
for (const file of files) {
|
|
2138
|
-
const text = await
|
|
2046
|
+
const text = await readFile6(file, "utf-8");
|
|
2139
2047
|
const declaredIds = extractDeclaredContractIds(text);
|
|
2140
2048
|
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
2141
2049
|
let doc = null;
|
|
@@ -2189,7 +2097,7 @@ async function validateThemaContracts(uiRoot) {
|
|
|
2189
2097
|
}
|
|
2190
2098
|
const issues = [];
|
|
2191
2099
|
for (const file of files) {
|
|
2192
|
-
const text = await
|
|
2100
|
+
const text = await readFile6(file, "utf-8");
|
|
2193
2101
|
const invalidIds = extractInvalidIds(text, [
|
|
2194
2102
|
"SPEC",
|
|
2195
2103
|
"BR",
|
|
@@ -2323,7 +2231,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
2323
2231
|
}
|
|
2324
2232
|
const issues = [];
|
|
2325
2233
|
for (const file of files) {
|
|
2326
|
-
const text = await
|
|
2234
|
+
const text = await readFile6(file, "utf-8");
|
|
2327
2235
|
const invalidIds = extractInvalidIds(text, [
|
|
2328
2236
|
"SPEC",
|
|
2329
2237
|
"BR",
|
|
@@ -2392,7 +2300,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
2392
2300
|
}
|
|
2393
2301
|
const issues = [];
|
|
2394
2302
|
for (const file of files) {
|
|
2395
|
-
const text = await
|
|
2303
|
+
const text = await readFile6(file, "utf-8");
|
|
2396
2304
|
const invalidIds = extractInvalidIds(text, [
|
|
2397
2305
|
"SPEC",
|
|
2398
2306
|
"BR",
|
|
@@ -2589,9 +2497,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2589
2497
|
);
|
|
2590
2498
|
return issues;
|
|
2591
2499
|
}
|
|
2592
|
-
const packDir =
|
|
2593
|
-
const packRelative =
|
|
2594
|
-
if (packRelative.startsWith("..") ||
|
|
2500
|
+
const packDir = path16.resolve(uiRoot, packValue);
|
|
2501
|
+
const packRelative = path16.relative(uiRoot, packDir);
|
|
2502
|
+
if (packRelative.startsWith("..") || path16.isAbsolute(packRelative)) {
|
|
2595
2503
|
issues.push(
|
|
2596
2504
|
issue(
|
|
2597
2505
|
"QFAI-ASSET-001",
|
|
@@ -2617,7 +2525,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2617
2525
|
);
|
|
2618
2526
|
return issues;
|
|
2619
2527
|
}
|
|
2620
|
-
const assetsYamlPath =
|
|
2528
|
+
const assetsYamlPath = path16.join(packDir, "assets.yaml");
|
|
2621
2529
|
if (!await exists6(assetsYamlPath)) {
|
|
2622
2530
|
issues.push(
|
|
2623
2531
|
issue(
|
|
@@ -2632,7 +2540,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2632
2540
|
}
|
|
2633
2541
|
let manifest;
|
|
2634
2542
|
try {
|
|
2635
|
-
const manifestText = await
|
|
2543
|
+
const manifestText = await readFile6(assetsYamlPath, "utf-8");
|
|
2636
2544
|
manifest = parseStructuredContract(assetsYamlPath, manifestText);
|
|
2637
2545
|
} catch (error2) {
|
|
2638
2546
|
issues.push(
|
|
@@ -2705,9 +2613,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2705
2613
|
);
|
|
2706
2614
|
continue;
|
|
2707
2615
|
}
|
|
2708
|
-
const assetPath =
|
|
2709
|
-
const assetRelative =
|
|
2710
|
-
if (assetRelative.startsWith("..") ||
|
|
2616
|
+
const assetPath = path16.resolve(packDir, entry.path);
|
|
2617
|
+
const assetRelative = path16.relative(packDir, assetPath);
|
|
2618
|
+
if (assetRelative.startsWith("..") || path16.isAbsolute(assetRelative)) {
|
|
2711
2619
|
issues.push(
|
|
2712
2620
|
issue(
|
|
2713
2621
|
"QFAI-ASSET-004",
|
|
@@ -2748,7 +2656,7 @@ function shouldIgnoreInvalidId(value, doc) {
|
|
|
2748
2656
|
return false;
|
|
2749
2657
|
}
|
|
2750
2658
|
const normalized = packValue.replace(/\\/g, "/");
|
|
2751
|
-
const basename =
|
|
2659
|
+
const basename = path16.posix.basename(normalized);
|
|
2752
2660
|
if (!basename) {
|
|
2753
2661
|
return false;
|
|
2754
2662
|
}
|
|
@@ -2758,7 +2666,7 @@ function isSafeRelativePath(value) {
|
|
|
2758
2666
|
if (!value) {
|
|
2759
2667
|
return false;
|
|
2760
2668
|
}
|
|
2761
|
-
if (
|
|
2669
|
+
if (path16.isAbsolute(value)) {
|
|
2762
2670
|
return false;
|
|
2763
2671
|
}
|
|
2764
2672
|
const normalized = value.replace(/\\/g, "/");
|
|
@@ -2808,8 +2716,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
2808
2716
|
}
|
|
2809
2717
|
|
|
2810
2718
|
// src/core/validators/delta.ts
|
|
2811
|
-
import { readFile as
|
|
2812
|
-
import
|
|
2719
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
2720
|
+
import path17 from "path";
|
|
2813
2721
|
async function validateDeltas(root, config) {
|
|
2814
2722
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2815
2723
|
const packs = await collectSpecPackDirs(specsRoot);
|
|
@@ -2818,9 +2726,9 @@ async function validateDeltas(root, config) {
|
|
|
2818
2726
|
}
|
|
2819
2727
|
const issues = [];
|
|
2820
2728
|
for (const pack of packs) {
|
|
2821
|
-
const deltaPath =
|
|
2729
|
+
const deltaPath = path17.join(pack, "delta.md");
|
|
2822
2730
|
try {
|
|
2823
|
-
await
|
|
2731
|
+
await readFile7(deltaPath, "utf-8");
|
|
2824
2732
|
} catch (error2) {
|
|
2825
2733
|
if (isMissingFileError2(error2)) {
|
|
2826
2734
|
issues.push(
|
|
@@ -2871,8 +2779,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
2871
2779
|
}
|
|
2872
2780
|
|
|
2873
2781
|
// src/core/validators/ids.ts
|
|
2874
|
-
import { readFile as
|
|
2875
|
-
import
|
|
2782
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2783
|
+
import path18 from "path";
|
|
2876
2784
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2877
2785
|
async function validateDefinedIds(root, config) {
|
|
2878
2786
|
const issues = [];
|
|
@@ -2907,7 +2815,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2907
2815
|
}
|
|
2908
2816
|
async function collectSpecDefinitionIds(files, out) {
|
|
2909
2817
|
for (const file of files) {
|
|
2910
|
-
const text = await
|
|
2818
|
+
const text = await readFile8(file, "utf-8");
|
|
2911
2819
|
const parsed = parseSpec(text, file);
|
|
2912
2820
|
if (parsed.specId) {
|
|
2913
2821
|
recordId(out, parsed.specId, file);
|
|
@@ -2917,7 +2825,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2917
2825
|
}
|
|
2918
2826
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2919
2827
|
for (const file of files) {
|
|
2920
|
-
const text = await
|
|
2828
|
+
const text = await readFile8(file, "utf-8");
|
|
2921
2829
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2922
2830
|
if (!document || errors.length > 0) {
|
|
2923
2831
|
continue;
|
|
@@ -2938,7 +2846,7 @@ function recordId(out, id, file) {
|
|
|
2938
2846
|
}
|
|
2939
2847
|
function formatFileList(files, root) {
|
|
2940
2848
|
return files.map((file) => {
|
|
2941
|
-
const relative =
|
|
2849
|
+
const relative = path18.relative(root, file);
|
|
2942
2850
|
return relative.length > 0 ? relative : file;
|
|
2943
2851
|
}).join(", ");
|
|
2944
2852
|
}
|
|
@@ -2965,8 +2873,8 @@ function issue3(code, message, severity, file, rule, refs, category = "compatibi
|
|
|
2965
2873
|
}
|
|
2966
2874
|
|
|
2967
2875
|
// src/core/validators/promptsIntegrity.ts
|
|
2968
|
-
async function validatePromptsIntegrity(root) {
|
|
2969
|
-
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
2876
|
+
async function validatePromptsIntegrity(root, config) {
|
|
2877
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root, config);
|
|
2970
2878
|
if (diff.status !== "modified") {
|
|
2971
2879
|
return [];
|
|
2972
2880
|
}
|
|
@@ -2983,11 +2891,11 @@ async function validatePromptsIntegrity(root) {
|
|
|
2983
2891
|
code: "QFAI-PROMPTS-001",
|
|
2984
2892
|
severity: "error",
|
|
2985
2893
|
category: "change",
|
|
2986
|
-
message: `\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\uFF08${hints || `\u5DEE\u5206=${total}`}\uFF09\u3002${sampleText}`,
|
|
2894
|
+
message: `\u6A19\u6E96\u8CC7\u7523 '.qfai/assistant/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\uFF08${hints || `\u5DEE\u5206=${total}`}\uFF09\u3002${sampleText}`,
|
|
2987
2895
|
suggested_action: [
|
|
2988
2896
|
"prompts \u306E\u76F4\u7DE8\u96C6\u306F\u975E\u63A8\u5968\u3067\u3059\uFF08\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8/\u518D init \u3067\u4E0A\u66F8\u304D\u3055\u308C\u5F97\u307E\u3059\uFF09\u3002",
|
|
2989
2897
|
"\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u5B9F\u65BD\u3057\u3066\u304F\u3060\u3055\u3044:",
|
|
2990
|
-
"- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
|
|
2898
|
+
"- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/assistant/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
|
|
2991
2899
|
"- \u6A19\u6E96\u72B6\u614B\u3078\u623B\u3059\u5834\u5408: 'qfai init --force' \u3092\u5B9F\u884C\uFF08prompts \u306E\u307F\u4E0A\u66F8\u304D\u3001prompts.local \u306F\u4FDD\u8B77\uFF09"
|
|
2992
2900
|
].join("\n"),
|
|
2993
2901
|
rule: "prompts.integrity"
|
|
@@ -2996,8 +2904,8 @@ async function validatePromptsIntegrity(root) {
|
|
|
2996
2904
|
}
|
|
2997
2905
|
|
|
2998
2906
|
// src/core/validators/scenario.ts
|
|
2999
|
-
import { access as access7, readFile as
|
|
3000
|
-
import
|
|
2907
|
+
import { access as access7, readFile as readFile9 } from "fs/promises";
|
|
2908
|
+
import path19 from "path";
|
|
3001
2909
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
3002
2910
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
3003
2911
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -3020,7 +2928,7 @@ async function validateScenarios(root, config) {
|
|
|
3020
2928
|
}
|
|
3021
2929
|
const issues = [];
|
|
3022
2930
|
for (const entry of entries) {
|
|
3023
|
-
const legacyScenarioPath =
|
|
2931
|
+
const legacyScenarioPath = path19.join(entry.dir, "scenario.md");
|
|
3024
2932
|
if (await fileExists(legacyScenarioPath)) {
|
|
3025
2933
|
issues.push(
|
|
3026
2934
|
issue4(
|
|
@@ -3034,7 +2942,7 @@ async function validateScenarios(root, config) {
|
|
|
3034
2942
|
}
|
|
3035
2943
|
let text;
|
|
3036
2944
|
try {
|
|
3037
|
-
text = await
|
|
2945
|
+
text = await readFile9(entry.scenarioPath, "utf-8");
|
|
3038
2946
|
} catch (error2) {
|
|
3039
2947
|
if (isMissingFileError3(error2)) {
|
|
3040
2948
|
issues.push(
|
|
@@ -3217,7 +3125,7 @@ async function fileExists(target) {
|
|
|
3217
3125
|
}
|
|
3218
3126
|
|
|
3219
3127
|
// src/core/validators/spec.ts
|
|
3220
|
-
import { readFile as
|
|
3128
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
3221
3129
|
async function validateSpecs(root, config) {
|
|
3222
3130
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
3223
3131
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -3238,7 +3146,7 @@ async function validateSpecs(root, config) {
|
|
|
3238
3146
|
for (const entry of entries) {
|
|
3239
3147
|
let text;
|
|
3240
3148
|
try {
|
|
3241
|
-
text = await
|
|
3149
|
+
text = await readFile10(entry.specPath, "utf-8");
|
|
3242
3150
|
} catch (error2) {
|
|
3243
3151
|
if (isMissingFileError4(error2)) {
|
|
3244
3152
|
issues.push(
|
|
@@ -3392,7 +3300,7 @@ function isMissingFileError4(error2) {
|
|
|
3392
3300
|
}
|
|
3393
3301
|
|
|
3394
3302
|
// src/core/validators/traceability.ts
|
|
3395
|
-
import { readFile as
|
|
3303
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3396
3304
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
3397
3305
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
3398
3306
|
async function validateTraceability(root, config) {
|
|
@@ -3412,7 +3320,7 @@ async function validateTraceability(root, config) {
|
|
|
3412
3320
|
const contractIndex = await buildContractIndex(root, config);
|
|
3413
3321
|
const contractIds = contractIndex.ids;
|
|
3414
3322
|
for (const file of specFiles) {
|
|
3415
|
-
const text = await
|
|
3323
|
+
const text = await readFile11(file, "utf-8");
|
|
3416
3324
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3417
3325
|
const parsed = parseSpec(text, file);
|
|
3418
3326
|
if (parsed.specId) {
|
|
@@ -3485,7 +3393,7 @@ async function validateTraceability(root, config) {
|
|
|
3485
3393
|
}
|
|
3486
3394
|
}
|
|
3487
3395
|
for (const file of scenarioFiles) {
|
|
3488
|
-
const text = await
|
|
3396
|
+
const text = await readFile11(file, "utf-8");
|
|
3489
3397
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3490
3398
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
3491
3399
|
allowCommentPrefix: true
|
|
@@ -3807,7 +3715,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
3807
3715
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
3808
3716
|
let found = false;
|
|
3809
3717
|
for (const file of targetFiles) {
|
|
3810
|
-
const text = await
|
|
3718
|
+
const text = await readFile11(file, "utf-8");
|
|
3811
3719
|
if (pattern.test(text)) {
|
|
3812
3720
|
found = true;
|
|
3813
3721
|
break;
|
|
@@ -3858,7 +3766,7 @@ async function validateProject(root, configResult) {
|
|
|
3858
3766
|
const { config, issues: configIssues } = resolved;
|
|
3859
3767
|
const issues = [
|
|
3860
3768
|
...configIssues,
|
|
3861
|
-
...await validatePromptsIntegrity(root),
|
|
3769
|
+
...await validatePromptsIntegrity(root, config),
|
|
3862
3770
|
...await validateSpecs(root, config),
|
|
3863
3771
|
...await validateDeltas(root, config),
|
|
3864
3772
|
...await validateScenarios(root, config),
|
|
@@ -3907,15 +3815,15 @@ var ID_PREFIXES2 = [
|
|
|
3907
3815
|
"THEMA"
|
|
3908
3816
|
];
|
|
3909
3817
|
async function createReportData(root, validation, configResult) {
|
|
3910
|
-
const resolvedRoot =
|
|
3818
|
+
const resolvedRoot = path20.resolve(root);
|
|
3911
3819
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3912
3820
|
const config = resolved.config;
|
|
3913
3821
|
const configPath = resolved.configPath;
|
|
3914
3822
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3915
3823
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3916
|
-
const apiRoot =
|
|
3917
|
-
const uiRoot =
|
|
3918
|
-
const dbRoot =
|
|
3824
|
+
const apiRoot = path20.join(contractsRoot, "api");
|
|
3825
|
+
const uiRoot = path20.join(contractsRoot, "ui");
|
|
3826
|
+
const dbRoot = path20.join(contractsRoot, "db");
|
|
3919
3827
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3920
3828
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3921
3829
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -4381,7 +4289,9 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
4381
4289
|
);
|
|
4382
4290
|
}
|
|
4383
4291
|
lines.push("- \u5909\u66F4\u5185\u5BB9\u30FB\u53D7\u5165\u89B3\u70B9\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002");
|
|
4384
|
-
lines.push(
|
|
4292
|
+
lines.push(
|
|
4293
|
+
"- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/assistant/instructions/constitution.md`"
|
|
4294
|
+
);
|
|
4385
4295
|
return lines.join("\n");
|
|
4386
4296
|
}
|
|
4387
4297
|
function formatReportJson(data) {
|
|
@@ -4395,7 +4305,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
4395
4305
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
4396
4306
|
}
|
|
4397
4307
|
for (const file of specFiles) {
|
|
4398
|
-
const text = await
|
|
4308
|
+
const text = await readFile12(file, "utf-8");
|
|
4399
4309
|
const parsed = parseSpec(text, file);
|
|
4400
4310
|
const specKey = parsed.specId;
|
|
4401
4311
|
if (!specKey) {
|
|
@@ -4437,7 +4347,7 @@ async function collectIds(files) {
|
|
|
4437
4347
|
THEMA: /* @__PURE__ */ new Set()
|
|
4438
4348
|
};
|
|
4439
4349
|
for (const file of files) {
|
|
4440
|
-
const text = await
|
|
4350
|
+
const text = await readFile12(file, "utf-8");
|
|
4441
4351
|
for (const prefix of ID_PREFIXES2) {
|
|
4442
4352
|
const ids = extractIds(text, prefix);
|
|
4443
4353
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -4456,7 +4366,7 @@ async function collectIds(files) {
|
|
|
4456
4366
|
async function collectUpstreamIds(files) {
|
|
4457
4367
|
const ids = /* @__PURE__ */ new Set();
|
|
4458
4368
|
for (const file of files) {
|
|
4459
|
-
const text = await
|
|
4369
|
+
const text = await readFile12(file, "utf-8");
|
|
4460
4370
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
4461
4371
|
}
|
|
4462
4372
|
return ids;
|
|
@@ -4477,7 +4387,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
4477
4387
|
}
|
|
4478
4388
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
4479
4389
|
for (const file of targetFiles) {
|
|
4480
|
-
const text = await
|
|
4390
|
+
const text = await readFile12(file, "utf-8");
|
|
4481
4391
|
if (pattern.test(text)) {
|
|
4482
4392
|
return true;
|
|
4483
4393
|
}
|
|
@@ -4614,7 +4524,7 @@ function warnIfTruncated(scan, context) {
|
|
|
4614
4524
|
|
|
4615
4525
|
// src/cli/commands/report.ts
|
|
4616
4526
|
async function runReport(options) {
|
|
4617
|
-
const root =
|
|
4527
|
+
const root = path21.resolve(options.root);
|
|
4618
4528
|
const configResult = await loadConfig(root);
|
|
4619
4529
|
let validation;
|
|
4620
4530
|
if (options.runValidate) {
|
|
@@ -4631,7 +4541,7 @@ async function runReport(options) {
|
|
|
4631
4541
|
validation = normalized;
|
|
4632
4542
|
} else {
|
|
4633
4543
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
4634
|
-
const inputPath =
|
|
4544
|
+
const inputPath = path21.isAbsolute(input) ? input : path21.resolve(root, input);
|
|
4635
4545
|
try {
|
|
4636
4546
|
validation = await readValidationResult(inputPath);
|
|
4637
4547
|
} catch (err) {
|
|
@@ -4642,7 +4552,7 @@ async function runReport(options) {
|
|
|
4642
4552
|
"",
|
|
4643
4553
|
"\u307E\u305A qfai validate \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4F8B:",
|
|
4644
4554
|
" qfai validate",
|
|
4645
|
-
"\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u51FA\u529B\u5148: .qfai/
|
|
4555
|
+
"\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u51FA\u529B\u5148: .qfai/report/validate.json\uFF09",
|
|
4646
4556
|
"",
|
|
4647
4557
|
"\u307E\u305F\u306F report \u306B --run-validate \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
4648
4558
|
"GitHub Actions \u30C6\u30F3\u30D7\u30EC\u3092\u4F7F\u3063\u3066\u3044\u308B\u5834\u5408\u306F\u3001workflow \u306E validate \u30B8\u30E7\u30D6\u3092\u5148\u306B\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
@@ -4658,10 +4568,10 @@ async function runReport(options) {
|
|
|
4658
4568
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
4659
4569
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
4660
4570
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
4661
|
-
const defaultOut = options.format === "json" ?
|
|
4571
|
+
const defaultOut = options.format === "json" ? path21.join(outRoot, "report.json") : path21.join(outRoot, "report.md");
|
|
4662
4572
|
const out = options.outPath ?? defaultOut;
|
|
4663
|
-
const outPath =
|
|
4664
|
-
await mkdir3(
|
|
4573
|
+
const outPath = path21.isAbsolute(out) ? out : path21.resolve(root, out);
|
|
4574
|
+
await mkdir3(path21.dirname(outPath), { recursive: true });
|
|
4665
4575
|
await writeFile2(outPath, `${output}
|
|
4666
4576
|
`, "utf-8");
|
|
4667
4577
|
info(
|
|
@@ -4670,7 +4580,7 @@ async function runReport(options) {
|
|
|
4670
4580
|
info(`wrote report: ${outPath}`);
|
|
4671
4581
|
}
|
|
4672
4582
|
async function readValidationResult(inputPath) {
|
|
4673
|
-
const raw = await
|
|
4583
|
+
const raw = await readFile13(inputPath, "utf-8");
|
|
4674
4584
|
const parsed = JSON.parse(raw);
|
|
4675
4585
|
if (!isValidationResult(parsed)) {
|
|
4676
4586
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -4726,15 +4636,15 @@ function isMissingFileError5(error2) {
|
|
|
4726
4636
|
return record2.code === "ENOENT";
|
|
4727
4637
|
}
|
|
4728
4638
|
async function writeValidationResult(root, outputPath, result) {
|
|
4729
|
-
const abs =
|
|
4730
|
-
await mkdir3(
|
|
4639
|
+
const abs = path21.isAbsolute(outputPath) ? outputPath : path21.resolve(root, outputPath);
|
|
4640
|
+
await mkdir3(path21.dirname(abs), { recursive: true });
|
|
4731
4641
|
await writeFile2(abs, `${JSON.stringify(result, null, 2)}
|
|
4732
4642
|
`, "utf-8");
|
|
4733
4643
|
}
|
|
4734
4644
|
|
|
4735
4645
|
// src/cli/commands/validate.ts
|
|
4736
4646
|
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
4737
|
-
import
|
|
4647
|
+
import path22 from "path";
|
|
4738
4648
|
|
|
4739
4649
|
// src/cli/lib/failOn.ts
|
|
4740
4650
|
function shouldFail(result, failOn) {
|
|
@@ -4749,7 +4659,7 @@ function shouldFail(result, failOn) {
|
|
|
4749
4659
|
|
|
4750
4660
|
// src/cli/commands/validate.ts
|
|
4751
4661
|
async function runValidate(options) {
|
|
4752
|
-
const root =
|
|
4662
|
+
const root = path22.resolve(options.root);
|
|
4753
4663
|
const configResult = await loadConfig(root);
|
|
4754
4664
|
const result = await validateProject(root, configResult);
|
|
4755
4665
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4874,12 +4784,12 @@ function issueKey(issue7) {
|
|
|
4874
4784
|
}
|
|
4875
4785
|
async function emitJson(result, root, jsonPath) {
|
|
4876
4786
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4877
|
-
await mkdir4(
|
|
4787
|
+
await mkdir4(path22.dirname(abs), { recursive: true });
|
|
4878
4788
|
await writeFile3(abs, `${JSON.stringify(result, null, 2)}
|
|
4879
4789
|
`, "utf-8");
|
|
4880
4790
|
}
|
|
4881
4791
|
function resolveJsonPath(root, jsonPath) {
|
|
4882
|
-
return
|
|
4792
|
+
return path22.isAbsolute(jsonPath) ? jsonPath : path22.resolve(root, jsonPath);
|
|
4883
4793
|
}
|
|
4884
4794
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4885
4795
|
|
|
@@ -4892,7 +4802,6 @@ function parseArgs(argv, cwd) {
|
|
|
4892
4802
|
force: false,
|
|
4893
4803
|
yes: false,
|
|
4894
4804
|
dryRun: false,
|
|
4895
|
-
analyzeList: false,
|
|
4896
4805
|
reportFormat: "md",
|
|
4897
4806
|
reportRunValidate: false,
|
|
4898
4807
|
doctorFormat: "text",
|
|
@@ -4944,18 +4853,6 @@ function parseArgs(argv, cwd) {
|
|
|
4944
4853
|
case "--dry-run":
|
|
4945
4854
|
options.dryRun = true;
|
|
4946
4855
|
break;
|
|
4947
|
-
case "--list":
|
|
4948
|
-
options.analyzeList = true;
|
|
4949
|
-
break;
|
|
4950
|
-
case "--prompt":
|
|
4951
|
-
{
|
|
4952
|
-
const next = readOptionValue(args, i);
|
|
4953
|
-
if (next) {
|
|
4954
|
-
options.analyzePrompt = next;
|
|
4955
|
-
i += 1;
|
|
4956
|
-
}
|
|
4957
|
-
}
|
|
4958
|
-
break;
|
|
4959
4856
|
case "--format": {
|
|
4960
4857
|
const next = readOptionValue(args, i);
|
|
4961
4858
|
if (next === null) {
|
|
@@ -5089,17 +4986,6 @@ async function run(argv, cwd) {
|
|
|
5089
4986
|
yes: options.yes
|
|
5090
4987
|
});
|
|
5091
4988
|
return;
|
|
5092
|
-
case "analyze":
|
|
5093
|
-
{
|
|
5094
|
-
const resolvedRoot = await resolveRoot(options);
|
|
5095
|
-
const exitCode = await runAnalyze({
|
|
5096
|
-
root: resolvedRoot,
|
|
5097
|
-
list: options.analyzeList,
|
|
5098
|
-
...options.analyzePrompt !== void 0 ? { prompt: options.analyzePrompt } : {}
|
|
5099
|
-
});
|
|
5100
|
-
process.exitCode = exitCode;
|
|
5101
|
-
}
|
|
5102
|
-
return;
|
|
5103
4989
|
case "validate":
|
|
5104
4990
|
{
|
|
5105
4991
|
const resolvedRoot = await resolveRoot(options);
|
|
@@ -5147,7 +5033,6 @@ function usage() {
|
|
|
5147
5033
|
|
|
5148
5034
|
Commands:
|
|
5149
5035
|
init \u30C6\u30F3\u30D7\u30EC\u3092\u751F\u6210
|
|
5150
|
-
analyze \u610F\u5473\u30EC\u30D9\u30EB\u306E\u30EC\u30D3\u30E5\u30FC\u88DC\u52A9\uFF08\u30D7\u30ED\u30F3\u30D7\u30C8\u51FA\u529B\uFF09
|
|
5151
5036
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
5152
5037
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
5153
5038
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
@@ -5155,11 +5040,9 @@ Commands:
|
|
|
5155
5040
|
Options:
|
|
5156
5041
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
5157
5042
|
--dir <path> init \u306E\u51FA\u529B\u5148
|
|
5158
|
-
--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
|
|
5043
|
+
--force init: .qfai/assistant/prompts \u306E\u307F\u4E0A\u66F8\u304D\uFF08\u305D\u308C\u4EE5\u5916\u306F\u65E2\u5B58\u304C\u3042\u308C\u3070\u30B9\u30AD\u30C3\u30D7\uFF09
|
|
5159
5044
|
--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
|
|
5160
5045
|
--dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
|
|
5161
|
-
--list analyze: \u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7\u3092\u8868\u793A
|
|
5162
|
-
--prompt <name> analyze: \u6307\u5B9A\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08.md\u7701\u7565\u53EF\uFF09\u3092\u51FA\u529B
|
|
5163
5046
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|
|
5164
5047
|
--format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
|
|
5165
5048
|
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|