qfai 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -65
- package/assets/init/.qfai/README.md +17 -77
- package/assets/init/.qfai/assistant/README.md +9 -0
- package/assets/init/.qfai/assistant/agents/README.md +34 -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 +6 -0
- package/assets/init/.qfai/assistant/instructions/constitution.md +131 -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 +239 -0
- package/assets/init/.qfai/assistant/prompts/qfai-pr.md +218 -0
- package/assets/init/.qfai/assistant/prompts/qfai-require.md +273 -0
- package/assets/init/.qfai/assistant/prompts/qfai-scenario-test.md +229 -0
- package/assets/init/.qfai/assistant/prompts/qfai-spec.md +287 -0
- package/assets/init/.qfai/assistant/prompts/qfai-unit-test.md +202 -0
- package/assets/init/.qfai/assistant/prompts/qfai-verify.md +231 -0
- package/assets/init/.qfai/assistant/prompts.local/README.md +6 -0
- package/assets/init/.qfai/assistant/steering/README.md +33 -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 +8 -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 +6 -56
- 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/dist/cli/index.cjs
CHANGED
|
@@ -23,230 +23,24 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
mod
|
|
24
24
|
));
|
|
25
25
|
|
|
26
|
-
// src/cli/commands/analyze.ts
|
|
27
|
-
var import_promises2 = require("fs/promises");
|
|
28
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
29
|
-
|
|
30
|
-
// src/core/fs.ts
|
|
31
|
-
var import_promises = require("fs/promises");
|
|
32
|
-
var import_node_path = __toESM(require("path"), 1);
|
|
33
|
-
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
34
|
-
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
35
|
-
"node_modules",
|
|
36
|
-
".git",
|
|
37
|
-
"dist",
|
|
38
|
-
".pnpm",
|
|
39
|
-
"tmp",
|
|
40
|
-
".mcp-tools"
|
|
41
|
-
]);
|
|
42
|
-
var DEFAULT_GLOB_FILE_LIMIT = 2e4;
|
|
43
|
-
async function collectFiles(root, options = {}) {
|
|
44
|
-
const entries = [];
|
|
45
|
-
if (!await exists(root)) {
|
|
46
|
-
return entries;
|
|
47
|
-
}
|
|
48
|
-
const ignoreDirs = /* @__PURE__ */ new Set([
|
|
49
|
-
...DEFAULT_IGNORE_DIRS,
|
|
50
|
-
...options.ignoreDirs ?? []
|
|
51
|
-
]);
|
|
52
|
-
const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
|
|
53
|
-
await walk(root, root, ignoreDirs, extensions, entries);
|
|
54
|
-
return entries;
|
|
55
|
-
}
|
|
56
|
-
async function collectFilesByGlobs(root, options) {
|
|
57
|
-
const limit = normalizeLimit(options.limit);
|
|
58
|
-
if (options.globs.length === 0) {
|
|
59
|
-
return { files: [], truncated: false, matchedFileCount: 0, limit };
|
|
60
|
-
}
|
|
61
|
-
const stream = import_fast_glob.default.stream(options.globs, {
|
|
62
|
-
cwd: root,
|
|
63
|
-
ignore: options.ignore ?? [],
|
|
64
|
-
onlyFiles: true,
|
|
65
|
-
absolute: true,
|
|
66
|
-
unique: true
|
|
67
|
-
});
|
|
68
|
-
const files = [];
|
|
69
|
-
let truncated = false;
|
|
70
|
-
for await (const entry of stream) {
|
|
71
|
-
if (files.length >= limit) {
|
|
72
|
-
truncated = true;
|
|
73
|
-
destroyStream(stream);
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
files.push(String(entry));
|
|
77
|
-
}
|
|
78
|
-
const matchedFileCount = files.length;
|
|
79
|
-
return { files, truncated, matchedFileCount, limit };
|
|
80
|
-
}
|
|
81
|
-
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
82
|
-
const items = await (0, import_promises.readdir)(current, { withFileTypes: true });
|
|
83
|
-
for (const item of items) {
|
|
84
|
-
const fullPath = import_node_path.default.join(current, item.name);
|
|
85
|
-
if (item.isDirectory()) {
|
|
86
|
-
if (ignoreDirs.has(item.name)) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
await walk(base, fullPath, ignoreDirs, extensions, out);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (item.isFile()) {
|
|
93
|
-
if (extensions.length > 0) {
|
|
94
|
-
const ext = import_node_path.default.extname(item.name).toLowerCase();
|
|
95
|
-
if (!extensions.includes(ext)) {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
out.push(fullPath);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
async function exists(target) {
|
|
104
|
-
try {
|
|
105
|
-
await (0, import_promises.access)(target);
|
|
106
|
-
return true;
|
|
107
|
-
} catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function normalizeLimit(value) {
|
|
112
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
113
|
-
return DEFAULT_GLOB_FILE_LIMIT;
|
|
114
|
-
}
|
|
115
|
-
const flooredValue = Math.floor(value);
|
|
116
|
-
if (flooredValue <= 0) {
|
|
117
|
-
return DEFAULT_GLOB_FILE_LIMIT;
|
|
118
|
-
}
|
|
119
|
-
return flooredValue;
|
|
120
|
-
}
|
|
121
|
-
function destroyStream(stream) {
|
|
122
|
-
if (!stream || typeof stream !== "object") {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const record2 = stream;
|
|
126
|
-
if (typeof record2.destroy === "function") {
|
|
127
|
-
record2.destroy();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// src/cli/commands/analyze.ts
|
|
132
|
-
async function runAnalyze(options) {
|
|
133
|
-
const root = import_node_path2.default.resolve(options.root);
|
|
134
|
-
const localDir = import_node_path2.default.join(root, ".qfai", "prompts.local", "analyze");
|
|
135
|
-
const standardDir = import_node_path2.default.join(root, ".qfai", "prompts", "analyze");
|
|
136
|
-
const available = await listPromptNames([localDir, standardDir]);
|
|
137
|
-
const promptName = normalizePromptName(options.prompt);
|
|
138
|
-
if (!promptName || options.list) {
|
|
139
|
-
emitList(available);
|
|
140
|
-
return 0;
|
|
141
|
-
}
|
|
142
|
-
const resolved = await resolvePromptPath(promptName, [localDir, standardDir]);
|
|
143
|
-
if (!resolved) {
|
|
144
|
-
emitPromptNotFound(promptName, available);
|
|
145
|
-
return 1;
|
|
146
|
-
}
|
|
147
|
-
const content = await (0, import_promises2.readFile)(resolved, "utf-8");
|
|
148
|
-
process.stdout.write(content);
|
|
149
|
-
if (!content.endsWith("\n")) {
|
|
150
|
-
process.stdout.write("\n");
|
|
151
|
-
}
|
|
152
|
-
return 0;
|
|
153
|
-
}
|
|
154
|
-
function normalizePromptName(value) {
|
|
155
|
-
const trimmed = (value ?? "").trim();
|
|
156
|
-
if (!trimmed) {
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
return trimmed.endsWith(".md") ? trimmed.slice(0, -3) : trimmed;
|
|
160
|
-
}
|
|
161
|
-
async function listPromptNames(dirs) {
|
|
162
|
-
const byName = /* @__PURE__ */ new Map();
|
|
163
|
-
for (const dir of dirs) {
|
|
164
|
-
const files = await collectFiles(dir, { extensions: [".md"] });
|
|
165
|
-
for (const abs of files) {
|
|
166
|
-
const base = import_node_path2.default.basename(abs);
|
|
167
|
-
if (base.toLowerCase() === "readme.md") {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const name = base.slice(0, -3);
|
|
171
|
-
if (byName.has(name)) {
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
if (await isDeprecatedPrompt(abs)) {
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
byName.set(name, abs);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return [...byName.keys()].sort((a, b) => a.localeCompare(b));
|
|
181
|
-
}
|
|
182
|
-
function emitList(names) {
|
|
183
|
-
process.stdout.write("# qfai analyze: prompts\n\n");
|
|
184
|
-
if (names.length === 0) {
|
|
185
|
-
process.stdout.write(
|
|
186
|
-
"\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"
|
|
187
|
-
);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
process.stdout.write("\u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7:\n\n");
|
|
191
|
-
for (const name of names) {
|
|
192
|
-
process.stdout.write(`- ${name}
|
|
193
|
-
`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
async function resolvePromptPath(promptName, dirs) {
|
|
197
|
-
const filename = `${promptName}.md`;
|
|
198
|
-
for (const dir of dirs) {
|
|
199
|
-
const full = import_node_path2.default.join(dir, filename);
|
|
200
|
-
try {
|
|
201
|
-
await (0, import_promises2.readFile)(full, "utf-8");
|
|
202
|
-
return full;
|
|
203
|
-
} catch {
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
async function isDeprecatedPrompt(filePath) {
|
|
209
|
-
try {
|
|
210
|
-
const content = await (0, import_promises2.readFile)(filePath, "utf-8");
|
|
211
|
-
const firstLine = firstLineOf(content);
|
|
212
|
-
return firstLine.trim() === "# Deprecated";
|
|
213
|
-
} catch {
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
function firstLineOf(content) {
|
|
218
|
-
return content.match(/^[^\r\n]*/)?.[0] ?? "";
|
|
219
|
-
}
|
|
220
|
-
function emitPromptNotFound(promptName, candidates) {
|
|
221
|
-
process.stderr.write(`qfai analyze: prompt not found: ${promptName}
|
|
222
|
-
`);
|
|
223
|
-
if (candidates.length > 0) {
|
|
224
|
-
process.stderr.write("candidates:\n");
|
|
225
|
-
for (const c of candidates) {
|
|
226
|
-
process.stderr.write(`- ${c}
|
|
227
|
-
`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
26
|
// src/cli/commands/doctor.ts
|
|
233
|
-
var import_promises10 = require("fs/promises");
|
|
234
|
-
var import_node_path12 = __toESM(require("path"), 1);
|
|
235
|
-
|
|
236
|
-
// src/core/doctor.ts
|
|
237
27
|
var import_promises9 = require("fs/promises");
|
|
238
28
|
var import_node_path11 = __toESM(require("path"), 1);
|
|
239
29
|
|
|
30
|
+
// src/core/doctor.ts
|
|
31
|
+
var import_promises8 = require("fs/promises");
|
|
32
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
33
|
+
|
|
240
34
|
// src/core/config.ts
|
|
241
|
-
var
|
|
242
|
-
var
|
|
35
|
+
var import_promises = require("fs/promises");
|
|
36
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
243
37
|
var import_yaml = require("yaml");
|
|
244
38
|
var defaultConfig = {
|
|
245
39
|
paths: {
|
|
246
40
|
contractsDir: ".qfai/contracts",
|
|
247
41
|
specsDir: ".qfai/specs",
|
|
248
|
-
outDir: ".qfai/
|
|
249
|
-
promptsDir: ".qfai/prompts",
|
|
42
|
+
outDir: ".qfai/report",
|
|
43
|
+
promptsDir: ".qfai/assistant/prompts",
|
|
250
44
|
srcDir: "src",
|
|
251
45
|
testsDir: "tests"
|
|
252
46
|
},
|
|
@@ -274,21 +68,21 @@ var defaultConfig = {
|
|
|
274
68
|
}
|
|
275
69
|
},
|
|
276
70
|
output: {
|
|
277
|
-
validateJsonPath: ".qfai/
|
|
71
|
+
validateJsonPath: ".qfai/report/validate.json"
|
|
278
72
|
}
|
|
279
73
|
};
|
|
280
74
|
function getConfigPath(root) {
|
|
281
|
-
return
|
|
75
|
+
return import_node_path.default.join(root, "qfai.config.yaml");
|
|
282
76
|
}
|
|
283
77
|
async function findConfigRoot(startDir) {
|
|
284
|
-
const resolvedStart =
|
|
78
|
+
const resolvedStart = import_node_path.default.resolve(startDir);
|
|
285
79
|
let current = resolvedStart;
|
|
286
80
|
while (true) {
|
|
287
81
|
const configPath = getConfigPath(current);
|
|
288
|
-
if (await
|
|
82
|
+
if (await exists(configPath)) {
|
|
289
83
|
return { root: current, configPath, found: true };
|
|
290
84
|
}
|
|
291
|
-
const parent =
|
|
85
|
+
const parent = import_node_path.default.dirname(current);
|
|
292
86
|
if (parent === current) {
|
|
293
87
|
break;
|
|
294
88
|
}
|
|
@@ -305,7 +99,7 @@ async function loadConfig(root) {
|
|
|
305
99
|
const issues = [];
|
|
306
100
|
let parsed;
|
|
307
101
|
try {
|
|
308
|
-
const raw = await (0,
|
|
102
|
+
const raw = await (0, import_promises.readFile)(configPath, "utf-8");
|
|
309
103
|
parsed = (0, import_yaml.parse)(raw);
|
|
310
104
|
} catch (error2) {
|
|
311
105
|
if (isMissingFile(error2)) {
|
|
@@ -318,7 +112,7 @@ async function loadConfig(root) {
|
|
|
318
112
|
return { config: normalized, issues, configPath };
|
|
319
113
|
}
|
|
320
114
|
function resolvePath(root, config, key) {
|
|
321
|
-
return
|
|
115
|
+
return import_node_path.default.resolve(root, config.paths[key]);
|
|
322
116
|
}
|
|
323
117
|
function normalizeConfig(raw, configPath, issues) {
|
|
324
118
|
if (!isRecord(raw)) {
|
|
@@ -611,9 +405,9 @@ function isMissingFile(error2) {
|
|
|
611
405
|
}
|
|
612
406
|
return false;
|
|
613
407
|
}
|
|
614
|
-
async function
|
|
408
|
+
async function exists(target) {
|
|
615
409
|
try {
|
|
616
|
-
await (0,
|
|
410
|
+
await (0, import_promises.access)(target);
|
|
617
411
|
return true;
|
|
618
412
|
} catch {
|
|
619
413
|
return false;
|
|
@@ -630,27 +424,128 @@ function isRecord(value) {
|
|
|
630
424
|
}
|
|
631
425
|
|
|
632
426
|
// src/core/discovery.ts
|
|
633
|
-
var import_promises5 = require("fs/promises");
|
|
634
|
-
var import_node_path5 = __toESM(require("path"), 1);
|
|
635
|
-
|
|
636
|
-
// src/core/specLayout.ts
|
|
637
427
|
var import_promises4 = require("fs/promises");
|
|
638
428
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
429
|
+
|
|
430
|
+
// src/core/fs.ts
|
|
431
|
+
var import_promises2 = require("fs/promises");
|
|
432
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
433
|
+
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
434
|
+
var DEFAULT_IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
435
|
+
"node_modules",
|
|
436
|
+
".git",
|
|
437
|
+
"dist",
|
|
438
|
+
".pnpm",
|
|
439
|
+
"tmp",
|
|
440
|
+
".mcp-tools"
|
|
441
|
+
]);
|
|
442
|
+
var DEFAULT_GLOB_FILE_LIMIT = 2e4;
|
|
443
|
+
async function collectFiles(root, options = {}) {
|
|
444
|
+
const entries = [];
|
|
445
|
+
if (!await exists2(root)) {
|
|
446
|
+
return entries;
|
|
447
|
+
}
|
|
448
|
+
const ignoreDirs = /* @__PURE__ */ new Set([
|
|
449
|
+
...DEFAULT_IGNORE_DIRS,
|
|
450
|
+
...options.ignoreDirs ?? []
|
|
451
|
+
]);
|
|
452
|
+
const extensions = options.extensions?.map((ext) => ext.toLowerCase()) ?? [];
|
|
453
|
+
await walk(root, root, ignoreDirs, extensions, entries);
|
|
454
|
+
return entries;
|
|
455
|
+
}
|
|
456
|
+
async function collectFilesByGlobs(root, options) {
|
|
457
|
+
const limit = normalizeLimit(options.limit);
|
|
458
|
+
if (options.globs.length === 0) {
|
|
459
|
+
return { files: [], truncated: false, matchedFileCount: 0, limit };
|
|
460
|
+
}
|
|
461
|
+
const stream = import_fast_glob.default.stream(options.globs, {
|
|
462
|
+
cwd: root,
|
|
463
|
+
ignore: options.ignore ?? [],
|
|
464
|
+
onlyFiles: true,
|
|
465
|
+
absolute: true,
|
|
466
|
+
unique: true
|
|
467
|
+
});
|
|
468
|
+
const files = [];
|
|
469
|
+
let truncated = false;
|
|
470
|
+
for await (const entry of stream) {
|
|
471
|
+
if (files.length >= limit) {
|
|
472
|
+
truncated = true;
|
|
473
|
+
destroyStream(stream);
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
files.push(String(entry));
|
|
477
|
+
}
|
|
478
|
+
const matchedFileCount = files.length;
|
|
479
|
+
return { files, truncated, matchedFileCount, limit };
|
|
480
|
+
}
|
|
481
|
+
async function walk(base, current, ignoreDirs, extensions, out) {
|
|
482
|
+
const items = await (0, import_promises2.readdir)(current, { withFileTypes: true });
|
|
483
|
+
for (const item of items) {
|
|
484
|
+
const fullPath = import_node_path2.default.join(current, item.name);
|
|
485
|
+
if (item.isDirectory()) {
|
|
486
|
+
if (ignoreDirs.has(item.name)) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
await walk(base, fullPath, ignoreDirs, extensions, out);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
if (item.isFile()) {
|
|
493
|
+
if (extensions.length > 0) {
|
|
494
|
+
const ext = import_node_path2.default.extname(item.name).toLowerCase();
|
|
495
|
+
if (!extensions.includes(ext)) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
out.push(fullPath);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async function exists2(target) {
|
|
504
|
+
try {
|
|
505
|
+
await (0, import_promises2.access)(target);
|
|
506
|
+
return true;
|
|
507
|
+
} catch {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function normalizeLimit(value) {
|
|
512
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
513
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
514
|
+
}
|
|
515
|
+
const flooredValue = Math.floor(value);
|
|
516
|
+
if (flooredValue <= 0) {
|
|
517
|
+
return DEFAULT_GLOB_FILE_LIMIT;
|
|
518
|
+
}
|
|
519
|
+
return flooredValue;
|
|
520
|
+
}
|
|
521
|
+
function destroyStream(stream) {
|
|
522
|
+
if (!stream || typeof stream !== "object") {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const record2 = stream;
|
|
526
|
+
if (typeof record2.destroy === "function") {
|
|
527
|
+
record2.destroy();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/core/specLayout.ts
|
|
532
|
+
var import_promises3 = require("fs/promises");
|
|
533
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
639
534
|
var SPEC_DIR_RE = /^spec-\d{4}$/;
|
|
640
535
|
async function collectSpecEntries(specsRoot) {
|
|
641
536
|
const dirs = await listSpecDirs(specsRoot);
|
|
642
537
|
const entries = dirs.map((dir) => ({
|
|
643
538
|
dir,
|
|
644
|
-
specPath:
|
|
645
|
-
deltaPath:
|
|
646
|
-
scenarioPath:
|
|
539
|
+
specPath: import_node_path3.default.join(dir, "spec.md"),
|
|
540
|
+
deltaPath: import_node_path3.default.join(dir, "delta.md"),
|
|
541
|
+
scenarioPath: import_node_path3.default.join(dir, "scenario.feature")
|
|
647
542
|
}));
|
|
648
543
|
return entries.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
649
544
|
}
|
|
650
545
|
async function listSpecDirs(specsRoot) {
|
|
651
546
|
try {
|
|
652
|
-
const items = await (0,
|
|
653
|
-
return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) =>
|
|
547
|
+
const items = await (0, import_promises3.readdir)(specsRoot, { withFileTypes: true });
|
|
548
|
+
return items.filter((item) => item.isDirectory()).map((item) => item.name).filter((name) => SPEC_DIR_RE.test(name.toLowerCase())).map((name) => import_node_path3.default.join(specsRoot, name));
|
|
654
549
|
} catch (error2) {
|
|
655
550
|
if (isMissingFileError(error2)) {
|
|
656
551
|
return [];
|
|
@@ -712,7 +607,7 @@ async function filterExisting(files) {
|
|
|
712
607
|
}
|
|
713
608
|
async function exists3(target) {
|
|
714
609
|
try {
|
|
715
|
-
await (0,
|
|
610
|
+
await (0, import_promises4.access)(target);
|
|
716
611
|
return true;
|
|
717
612
|
} catch {
|
|
718
613
|
return false;
|
|
@@ -721,20 +616,20 @@ async function exists3(target) {
|
|
|
721
616
|
function filterByBasenamePrefix(files, prefix) {
|
|
722
617
|
const lowerPrefix = prefix.toLowerCase();
|
|
723
618
|
return files.filter(
|
|
724
|
-
(file) =>
|
|
619
|
+
(file) => import_node_path4.default.basename(file).toLowerCase().startsWith(lowerPrefix)
|
|
725
620
|
);
|
|
726
621
|
}
|
|
727
622
|
|
|
728
623
|
// src/core/paths.ts
|
|
729
|
-
var
|
|
624
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
730
625
|
function toRelativePath(root, target) {
|
|
731
626
|
if (!target) {
|
|
732
627
|
return target;
|
|
733
628
|
}
|
|
734
|
-
if (!
|
|
629
|
+
if (!import_node_path5.default.isAbsolute(target)) {
|
|
735
630
|
return toPosixPath(target);
|
|
736
631
|
}
|
|
737
|
-
const relative =
|
|
632
|
+
const relative = import_node_path5.default.relative(root, target);
|
|
738
633
|
if (!relative) {
|
|
739
634
|
return ".";
|
|
740
635
|
}
|
|
@@ -745,8 +640,8 @@ function toPosixPath(value) {
|
|
|
745
640
|
}
|
|
746
641
|
|
|
747
642
|
// src/core/traceability.ts
|
|
748
|
-
var
|
|
749
|
-
var
|
|
643
|
+
var import_promises5 = require("fs/promises");
|
|
644
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
750
645
|
|
|
751
646
|
// src/core/gherkin/parse.ts
|
|
752
647
|
var import_gherkin = require("@cucumber/gherkin");
|
|
@@ -898,7 +793,7 @@ function extractAnnotatedScIds(text) {
|
|
|
898
793
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
899
794
|
const scIds = /* @__PURE__ */ new Set();
|
|
900
795
|
for (const file of scenarioFiles) {
|
|
901
|
-
const text = await (0,
|
|
796
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
902
797
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
903
798
|
if (!document || errors.length > 0) {
|
|
904
799
|
continue;
|
|
@@ -916,7 +811,7 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
916
811
|
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
917
812
|
const sources = /* @__PURE__ */ new Map();
|
|
918
813
|
for (const file of scenarioFiles) {
|
|
919
|
-
const text = await (0,
|
|
814
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
920
815
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
921
816
|
if (!document || errors.length > 0) {
|
|
922
817
|
continue;
|
|
@@ -974,10 +869,10 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
974
869
|
};
|
|
975
870
|
}
|
|
976
871
|
const normalizedFiles = Array.from(
|
|
977
|
-
new Set(scanResult.files.map((file) =>
|
|
872
|
+
new Set(scanResult.files.map((file) => import_node_path6.default.normalize(file)))
|
|
978
873
|
);
|
|
979
874
|
for (const file of normalizedFiles) {
|
|
980
|
-
const text = await (0,
|
|
875
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
981
876
|
const scIds = extractAnnotatedScIds(text);
|
|
982
877
|
if (scIds.length === 0) {
|
|
983
878
|
continue;
|
|
@@ -1036,20 +931,20 @@ function formatError3(error2) {
|
|
|
1036
931
|
}
|
|
1037
932
|
|
|
1038
933
|
// src/core/promptsIntegrity.ts
|
|
1039
|
-
var
|
|
1040
|
-
var
|
|
934
|
+
var import_promises6 = require("fs/promises");
|
|
935
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1041
936
|
|
|
1042
937
|
// src/shared/assets.ts
|
|
1043
938
|
var import_node_fs = require("fs");
|
|
1044
|
-
var
|
|
939
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1045
940
|
var import_node_url = require("url");
|
|
1046
941
|
function getInitAssetsDir() {
|
|
1047
942
|
const base = __filename;
|
|
1048
943
|
const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
|
|
1049
|
-
const baseDir =
|
|
944
|
+
const baseDir = import_node_path7.default.dirname(basePath);
|
|
1050
945
|
const candidates = [
|
|
1051
|
-
|
|
1052
|
-
|
|
946
|
+
import_node_path7.default.resolve(baseDir, "../../../assets/init"),
|
|
947
|
+
import_node_path7.default.resolve(baseDir, "../../assets/init")
|
|
1053
948
|
];
|
|
1054
949
|
for (const candidate of candidates) {
|
|
1055
950
|
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
@@ -1067,11 +962,24 @@ function getInitAssetsDir() {
|
|
|
1067
962
|
|
|
1068
963
|
// src/core/promptsIntegrity.ts
|
|
1069
964
|
var LEGACY_OK_EXTRA = /* @__PURE__ */ new Set(["qfai-classify-change.md"]);
|
|
1070
|
-
async function diffProjectPromptsAgainstInitAssets(root) {
|
|
1071
|
-
const
|
|
965
|
+
async function diffProjectPromptsAgainstInitAssets(root, config) {
|
|
966
|
+
const promptsDirConfig = config.paths.promptsDir;
|
|
967
|
+
const promptsDir = import_node_path8.default.isAbsolute(promptsDirConfig) ? promptsDirConfig : import_node_path8.default.resolve(root, promptsDirConfig);
|
|
1072
968
|
let templateDir;
|
|
1073
969
|
try {
|
|
1074
|
-
|
|
970
|
+
const rel = import_node_path8.default.isAbsolute(promptsDirConfig) ? import_node_path8.default.relative(root, promptsDirConfig) : promptsDirConfig;
|
|
971
|
+
const normalized = rel.replace(/^[\\/]+/, "");
|
|
972
|
+
if (normalized.length === 0 || normalized.startsWith("..")) {
|
|
973
|
+
return {
|
|
974
|
+
status: "skipped_missing_assets",
|
|
975
|
+
promptsDir,
|
|
976
|
+
templateDir: "",
|
|
977
|
+
missing: [],
|
|
978
|
+
extra: [],
|
|
979
|
+
changed: []
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
templateDir = import_node_path8.default.join(getInitAssetsDir(), normalized);
|
|
1075
983
|
} catch {
|
|
1076
984
|
return {
|
|
1077
985
|
status: "skipped_missing_assets",
|
|
@@ -1125,8 +1033,8 @@ async function diffProjectPromptsAgainstInitAssets(root) {
|
|
|
1125
1033
|
}
|
|
1126
1034
|
try {
|
|
1127
1035
|
const [a, b] = await Promise.all([
|
|
1128
|
-
(0,
|
|
1129
|
-
(0,
|
|
1036
|
+
(0, import_promises6.readFile)(templateAbs, "utf-8"),
|
|
1037
|
+
(0, import_promises6.readFile)(projectAbs, "utf-8")
|
|
1130
1038
|
]);
|
|
1131
1039
|
if (normalizeNewlines(a) !== normalizeNewlines(b)) {
|
|
1132
1040
|
changed.push(rel);
|
|
@@ -1149,7 +1057,7 @@ function normalizeNewlines(text) {
|
|
|
1149
1057
|
return text.replace(/\r\n/g, "\n");
|
|
1150
1058
|
}
|
|
1151
1059
|
function toRel(base, abs) {
|
|
1152
|
-
const rel =
|
|
1060
|
+
const rel = import_node_path8.default.relative(base, abs);
|
|
1153
1061
|
return rel.replace(/[\\/]+/g, "/");
|
|
1154
1062
|
}
|
|
1155
1063
|
function intersectKeys(a, b) {
|
|
@@ -1163,16 +1071,16 @@ function intersectKeys(a, b) {
|
|
|
1163
1071
|
}
|
|
1164
1072
|
|
|
1165
1073
|
// src/core/version.ts
|
|
1166
|
-
var
|
|
1167
|
-
var
|
|
1074
|
+
var import_promises7 = require("fs/promises");
|
|
1075
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1168
1076
|
var import_node_url2 = require("url");
|
|
1169
1077
|
async function resolveToolVersion() {
|
|
1170
|
-
if ("1.0.
|
|
1171
|
-
return "1.0.
|
|
1078
|
+
if ("1.0.5".length > 0) {
|
|
1079
|
+
return "1.0.5";
|
|
1172
1080
|
}
|
|
1173
1081
|
try {
|
|
1174
1082
|
const packagePath = resolvePackageJsonPath();
|
|
1175
|
-
const raw = await (0,
|
|
1083
|
+
const raw = await (0, import_promises7.readFile)(packagePath, "utf-8");
|
|
1176
1084
|
const parsed = JSON.parse(raw);
|
|
1177
1085
|
const version = typeof parsed.version === "string" ? parsed.version : "";
|
|
1178
1086
|
return version.length > 0 ? version : "unknown";
|
|
@@ -1183,13 +1091,13 @@ async function resolveToolVersion() {
|
|
|
1183
1091
|
function resolvePackageJsonPath() {
|
|
1184
1092
|
const base = __filename;
|
|
1185
1093
|
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
1186
|
-
return
|
|
1094
|
+
return import_node_path9.default.resolve(import_node_path9.default.dirname(basePath), "../../package.json");
|
|
1187
1095
|
}
|
|
1188
1096
|
|
|
1189
1097
|
// src/core/doctor.ts
|
|
1190
1098
|
async function exists4(target) {
|
|
1191
1099
|
try {
|
|
1192
|
-
await (0,
|
|
1100
|
+
await (0, import_promises8.access)(target);
|
|
1193
1101
|
return true;
|
|
1194
1102
|
} catch {
|
|
1195
1103
|
return false;
|
|
@@ -1209,7 +1117,7 @@ function normalizeGlobs2(values) {
|
|
|
1209
1117
|
return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
1210
1118
|
}
|
|
1211
1119
|
async function createDoctorData(options) {
|
|
1212
|
-
const startDir =
|
|
1120
|
+
const startDir = import_node_path10.default.resolve(options.startDir);
|
|
1213
1121
|
const checks = [];
|
|
1214
1122
|
const configPath = getConfigPath(startDir);
|
|
1215
1123
|
const search = options.rootExplicit ? {
|
|
@@ -1271,9 +1179,9 @@ async function createDoctorData(options) {
|
|
|
1271
1179
|
details: { path: toRelativePath(root, resolved) }
|
|
1272
1180
|
});
|
|
1273
1181
|
if (key === "promptsDir") {
|
|
1274
|
-
const promptsLocalDir =
|
|
1275
|
-
|
|
1276
|
-
`${
|
|
1182
|
+
const promptsLocalDir = import_node_path10.default.join(
|
|
1183
|
+
import_node_path10.default.dirname(resolved),
|
|
1184
|
+
`${import_node_path10.default.basename(resolved)}.local`
|
|
1277
1185
|
);
|
|
1278
1186
|
const found = await exists4(promptsLocalDir);
|
|
1279
1187
|
addCheck(checks, {
|
|
@@ -1283,12 +1191,12 @@ async function createDoctorData(options) {
|
|
|
1283
1191
|
message: found ? "prompts.local exists (overlay can be used)" : "prompts.local is optional (create it to override prompts)",
|
|
1284
1192
|
details: { path: toRelativePath(root, promptsLocalDir) }
|
|
1285
1193
|
});
|
|
1286
|
-
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
1194
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root, config);
|
|
1287
1195
|
if (diff.status === "skipped_missing_prompts") {
|
|
1288
1196
|
addCheck(checks, {
|
|
1289
1197
|
id: "prompts.integrity",
|
|
1290
1198
|
severity: "info",
|
|
1291
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1199
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1292
1200
|
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",
|
|
1293
1201
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1294
1202
|
});
|
|
@@ -1296,7 +1204,7 @@ async function createDoctorData(options) {
|
|
|
1296
1204
|
addCheck(checks, {
|
|
1297
1205
|
id: "prompts.integrity",
|
|
1298
1206
|
severity: "info",
|
|
1299
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1207
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1300
1208
|
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",
|
|
1301
1209
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1302
1210
|
});
|
|
@@ -1304,7 +1212,7 @@ async function createDoctorData(options) {
|
|
|
1304
1212
|
addCheck(checks, {
|
|
1305
1213
|
id: "prompts.integrity",
|
|
1306
1214
|
severity: "ok",
|
|
1307
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1215
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1308
1216
|
message: "\u6A19\u6E96 assets \u3068\u4E00\u81F4\u3057\u3066\u3044\u307E\u3059",
|
|
1309
1217
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1310
1218
|
});
|
|
@@ -1312,15 +1220,15 @@ async function createDoctorData(options) {
|
|
|
1312
1220
|
addCheck(checks, {
|
|
1313
1221
|
id: "prompts.integrity",
|
|
1314
1222
|
severity: "error",
|
|
1315
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1316
|
-
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",
|
|
1223
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1224
|
+
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",
|
|
1317
1225
|
details: {
|
|
1318
1226
|
promptsDir: toRelativePath(root, diff.promptsDir),
|
|
1319
1227
|
missing: diff.missing,
|
|
1320
1228
|
extra: diff.extra,
|
|
1321
1229
|
changed: diff.changed,
|
|
1322
1230
|
nextActions: [
|
|
1323
|
-
"\u5909\u66F4\u5185\u5BB9\u3092 .qfai/prompts.local/** \u306B\u79FB\u3059\uFF08\u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067\u914D\u7F6E\uFF09",
|
|
1231
|
+
"\u5909\u66F4\u5185\u5BB9\u3092 .qfai/assistant/prompts.local/** \u306B\u79FB\u3059\uFF08\u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067\u914D\u7F6E\uFF09",
|
|
1324
1232
|
"\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"
|
|
1325
1233
|
]
|
|
1326
1234
|
}
|
|
@@ -1346,7 +1254,7 @@ async function createDoctorData(options) {
|
|
|
1346
1254
|
message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
|
|
1347
1255
|
details: { specPacks: entries.length, missingFiles }
|
|
1348
1256
|
});
|
|
1349
|
-
const validateJsonAbs =
|
|
1257
|
+
const validateJsonAbs = import_node_path10.default.isAbsolute(config.output.validateJsonPath) ? config.output.validateJsonPath : import_node_path10.default.resolve(root, config.output.validateJsonPath);
|
|
1350
1258
|
const validateJsonExists = await exists4(validateJsonAbs);
|
|
1351
1259
|
addCheck(checks, {
|
|
1352
1260
|
id: "output.validateJson",
|
|
@@ -1356,8 +1264,8 @@ async function createDoctorData(options) {
|
|
|
1356
1264
|
details: { path: toRelativePath(root, validateJsonAbs) }
|
|
1357
1265
|
});
|
|
1358
1266
|
const outDirAbs = resolvePath(root, config, "outDir");
|
|
1359
|
-
const rel =
|
|
1360
|
-
const inside = rel !== "" && !rel.startsWith("..") && !
|
|
1267
|
+
const rel = import_node_path10.default.relative(outDirAbs, validateJsonAbs);
|
|
1268
|
+
const inside = rel !== "" && !rel.startsWith("..") && !import_node_path10.default.isAbsolute(rel);
|
|
1361
1269
|
addCheck(checks, {
|
|
1362
1270
|
id: "output.pathAlignment",
|
|
1363
1271
|
severity: inside ? "ok" : "warning",
|
|
@@ -1480,12 +1388,12 @@ async function detectOutDirCollisions(root) {
|
|
|
1480
1388
|
});
|
|
1481
1389
|
const configPaths = configScan.files;
|
|
1482
1390
|
const configRoots = Array.from(
|
|
1483
|
-
new Set(configPaths.map((configPath) =>
|
|
1391
|
+
new Set(configPaths.map((configPath) => import_node_path10.default.dirname(configPath)))
|
|
1484
1392
|
).sort((a, b) => a.localeCompare(b));
|
|
1485
1393
|
const outDirToRoots = /* @__PURE__ */ new Map();
|
|
1486
1394
|
for (const configRoot of configRoots) {
|
|
1487
1395
|
const { config } = await loadConfig(configRoot);
|
|
1488
|
-
const outDir =
|
|
1396
|
+
const outDir = import_node_path10.default.normalize(resolvePath(configRoot, config, "outDir"));
|
|
1489
1397
|
const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
|
|
1490
1398
|
roots.add(configRoot);
|
|
1491
1399
|
outDirToRoots.set(outDir, roots);
|
|
@@ -1511,20 +1419,20 @@ async function detectOutDirCollisions(root) {
|
|
|
1511
1419
|
};
|
|
1512
1420
|
}
|
|
1513
1421
|
async function findMonorepoRoot(startDir) {
|
|
1514
|
-
let current =
|
|
1422
|
+
let current = import_node_path10.default.resolve(startDir);
|
|
1515
1423
|
while (true) {
|
|
1516
|
-
const gitPath =
|
|
1517
|
-
const workspacePath =
|
|
1424
|
+
const gitPath = import_node_path10.default.join(current, ".git");
|
|
1425
|
+
const workspacePath = import_node_path10.default.join(current, "pnpm-workspace.yaml");
|
|
1518
1426
|
if (await exists4(gitPath) || await exists4(workspacePath)) {
|
|
1519
1427
|
return current;
|
|
1520
1428
|
}
|
|
1521
|
-
const parent =
|
|
1429
|
+
const parent = import_node_path10.default.dirname(current);
|
|
1522
1430
|
if (parent === current) {
|
|
1523
1431
|
break;
|
|
1524
1432
|
}
|
|
1525
1433
|
current = parent;
|
|
1526
1434
|
}
|
|
1527
|
-
return
|
|
1435
|
+
return import_node_path10.default.resolve(startDir);
|
|
1528
1436
|
}
|
|
1529
1437
|
|
|
1530
1438
|
// src/cli/lib/logger.ts
|
|
@@ -1566,9 +1474,9 @@ async function runDoctor(options) {
|
|
|
1566
1474
|
const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
|
|
1567
1475
|
const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
|
|
1568
1476
|
if (options.outPath) {
|
|
1569
|
-
const outAbs =
|
|
1570
|
-
await (0,
|
|
1571
|
-
await (0,
|
|
1477
|
+
const outAbs = import_node_path11.default.isAbsolute(options.outPath) ? options.outPath : import_node_path11.default.resolve(process.cwd(), options.outPath);
|
|
1478
|
+
await (0, import_promises9.mkdir)(import_node_path11.default.dirname(outAbs), { recursive: true });
|
|
1479
|
+
await (0, import_promises9.writeFile)(outAbs, `${output}
|
|
1572
1480
|
`, "utf-8");
|
|
1573
1481
|
info(`doctor: wrote ${outAbs}`);
|
|
1574
1482
|
return exitCode;
|
|
@@ -1587,11 +1495,11 @@ function shouldFailDoctor(summary, failOn) {
|
|
|
1587
1495
|
}
|
|
1588
1496
|
|
|
1589
1497
|
// src/cli/commands/init.ts
|
|
1590
|
-
var
|
|
1498
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1591
1499
|
|
|
1592
1500
|
// src/cli/lib/fs.ts
|
|
1593
|
-
var
|
|
1594
|
-
var
|
|
1501
|
+
var import_promises10 = require("fs/promises");
|
|
1502
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1595
1503
|
async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
1596
1504
|
const files = await collectTemplateFiles(sourceRoot);
|
|
1597
1505
|
return copyFiles(files, sourceRoot, destRoot, options);
|
|
@@ -1599,7 +1507,7 @@ async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
|
1599
1507
|
async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
|
|
1600
1508
|
const allFiles = [];
|
|
1601
1509
|
for (const relPath of relativePaths) {
|
|
1602
|
-
const fullPath =
|
|
1510
|
+
const fullPath = import_node_path12.default.join(sourceRoot, relPath);
|
|
1603
1511
|
const files = await collectTemplateFiles(fullPath);
|
|
1604
1512
|
allFiles.push(...files);
|
|
1605
1513
|
}
|
|
@@ -1609,13 +1517,13 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1609
1517
|
const copied = [];
|
|
1610
1518
|
const skipped = [];
|
|
1611
1519
|
const conflicts = [];
|
|
1612
|
-
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
1613
|
-
const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
1520
|
+
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path12.default.sep);
|
|
1521
|
+
const excludePrefixes = (options.exclude ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path12.default.sep);
|
|
1614
1522
|
const isProtectedRelative = (relative) => {
|
|
1615
1523
|
if (protectPrefixes.length === 0) {
|
|
1616
1524
|
return false;
|
|
1617
1525
|
}
|
|
1618
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
1526
|
+
const normalized = relative.replace(/[\\/]+/g, import_node_path12.default.sep);
|
|
1619
1527
|
return protectPrefixes.some(
|
|
1620
1528
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1621
1529
|
);
|
|
@@ -1624,7 +1532,7 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1624
1532
|
if (excludePrefixes.length === 0) {
|
|
1625
1533
|
return false;
|
|
1626
1534
|
}
|
|
1627
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
1535
|
+
const normalized = relative.replace(/[\\/]+/g, import_node_path12.default.sep);
|
|
1628
1536
|
return excludePrefixes.some(
|
|
1629
1537
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1630
1538
|
);
|
|
@@ -1632,14 +1540,14 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1632
1540
|
const conflictPolicy = options.conflictPolicy ?? "error";
|
|
1633
1541
|
if (!options.force && conflictPolicy === "error") {
|
|
1634
1542
|
for (const file of files) {
|
|
1635
|
-
const relative =
|
|
1543
|
+
const relative = import_node_path12.default.relative(sourceRoot, file);
|
|
1636
1544
|
if (isExcludedRelative(relative)) {
|
|
1637
1545
|
continue;
|
|
1638
1546
|
}
|
|
1639
1547
|
if (isProtectedRelative(relative)) {
|
|
1640
1548
|
continue;
|
|
1641
1549
|
}
|
|
1642
|
-
const dest =
|
|
1550
|
+
const dest = import_node_path12.default.join(destRoot, relative);
|
|
1643
1551
|
if (!await shouldWrite(dest, options.force)) {
|
|
1644
1552
|
conflicts.push(dest);
|
|
1645
1553
|
}
|
|
@@ -1649,19 +1557,19 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1649
1557
|
}
|
|
1650
1558
|
}
|
|
1651
1559
|
for (const file of files) {
|
|
1652
|
-
const relative =
|
|
1560
|
+
const relative = import_node_path12.default.relative(sourceRoot, file);
|
|
1653
1561
|
if (isExcludedRelative(relative)) {
|
|
1654
1562
|
continue;
|
|
1655
1563
|
}
|
|
1656
|
-
const dest =
|
|
1564
|
+
const dest = import_node_path12.default.join(destRoot, relative);
|
|
1657
1565
|
const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
|
|
1658
1566
|
if (!await shouldWrite(dest, forceForThisFile)) {
|
|
1659
1567
|
skipped.push(dest);
|
|
1660
1568
|
continue;
|
|
1661
1569
|
}
|
|
1662
1570
|
if (!options.dryRun) {
|
|
1663
|
-
await (0,
|
|
1664
|
-
await (0,
|
|
1571
|
+
await (0, import_promises10.mkdir)(import_node_path12.default.dirname(dest), { recursive: true });
|
|
1572
|
+
await (0, import_promises10.copyFile)(file, dest);
|
|
1665
1573
|
}
|
|
1666
1574
|
copied.push(dest);
|
|
1667
1575
|
}
|
|
@@ -1682,9 +1590,9 @@ async function collectTemplateFiles(root) {
|
|
|
1682
1590
|
if (!await exists5(root)) {
|
|
1683
1591
|
return entries;
|
|
1684
1592
|
}
|
|
1685
|
-
const items = await (0,
|
|
1593
|
+
const items = await (0, import_promises10.readdir)(root, { withFileTypes: true });
|
|
1686
1594
|
for (const item of items) {
|
|
1687
|
-
const fullPath =
|
|
1595
|
+
const fullPath = import_node_path12.default.join(root, item.name);
|
|
1688
1596
|
if (item.isDirectory()) {
|
|
1689
1597
|
const nested = await collectTemplateFiles(fullPath);
|
|
1690
1598
|
entries.push(...nested);
|
|
@@ -1704,7 +1612,7 @@ async function shouldWrite(target, force) {
|
|
|
1704
1612
|
}
|
|
1705
1613
|
async function exists5(target) {
|
|
1706
1614
|
try {
|
|
1707
|
-
await (0,
|
|
1615
|
+
await (0, import_promises10.access)(target);
|
|
1708
1616
|
return true;
|
|
1709
1617
|
} catch {
|
|
1710
1618
|
return false;
|
|
@@ -1714,13 +1622,13 @@ async function exists5(target) {
|
|
|
1714
1622
|
// src/cli/commands/init.ts
|
|
1715
1623
|
async function runInit(options) {
|
|
1716
1624
|
const assetsRoot = getInitAssetsDir();
|
|
1717
|
-
const rootAssets =
|
|
1718
|
-
const qfaiAssets =
|
|
1719
|
-
const destRoot =
|
|
1720
|
-
const destQfai =
|
|
1625
|
+
const rootAssets = import_node_path13.default.join(assetsRoot, "root");
|
|
1626
|
+
const qfaiAssets = import_node_path13.default.join(assetsRoot, ".qfai");
|
|
1627
|
+
const destRoot = import_node_path13.default.resolve(options.dir);
|
|
1628
|
+
const destQfai = import_node_path13.default.join(destRoot, ".qfai");
|
|
1721
1629
|
if (options.force) {
|
|
1722
1630
|
info(
|
|
1723
|
-
"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"
|
|
1631
|
+
"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"
|
|
1724
1632
|
);
|
|
1725
1633
|
}
|
|
1726
1634
|
const rootResult = await copyTemplateTree(rootAssets, destRoot, {
|
|
@@ -1732,18 +1640,18 @@ async function runInit(options) {
|
|
|
1732
1640
|
force: false,
|
|
1733
1641
|
dryRun: options.dryRun,
|
|
1734
1642
|
conflictPolicy: "skip",
|
|
1735
|
-
protect: ["prompts.local"],
|
|
1736
|
-
exclude: ["prompts"]
|
|
1643
|
+
protect: ["assistant/prompts.local"],
|
|
1644
|
+
exclude: ["assistant/prompts"]
|
|
1737
1645
|
});
|
|
1738
1646
|
const promptsResult = await copyTemplatePaths(
|
|
1739
1647
|
qfaiAssets,
|
|
1740
1648
|
destQfai,
|
|
1741
|
-
["prompts"],
|
|
1649
|
+
["assistant/prompts"],
|
|
1742
1650
|
{
|
|
1743
1651
|
force: options.force,
|
|
1744
1652
|
dryRun: options.dryRun,
|
|
1745
1653
|
conflictPolicy: "skip",
|
|
1746
|
-
protect: ["prompts.local"]
|
|
1654
|
+
protect: ["assistant/prompts.local"]
|
|
1747
1655
|
}
|
|
1748
1656
|
);
|
|
1749
1657
|
report(
|
|
@@ -1764,8 +1672,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
1764
1672
|
}
|
|
1765
1673
|
|
|
1766
1674
|
// src/cli/commands/report.ts
|
|
1767
|
-
var
|
|
1768
|
-
var
|
|
1675
|
+
var import_promises19 = require("fs/promises");
|
|
1676
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
1769
1677
|
|
|
1770
1678
|
// src/core/normalize.ts
|
|
1771
1679
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1805,12 +1713,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1805
1713
|
}
|
|
1806
1714
|
|
|
1807
1715
|
// src/core/report.ts
|
|
1808
|
-
var
|
|
1809
|
-
var
|
|
1716
|
+
var import_promises18 = require("fs/promises");
|
|
1717
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
1810
1718
|
|
|
1811
1719
|
// src/core/contractIndex.ts
|
|
1812
|
-
var
|
|
1813
|
-
var
|
|
1720
|
+
var import_promises11 = require("fs/promises");
|
|
1721
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
1814
1722
|
|
|
1815
1723
|
// src/core/contractsDecl.ts
|
|
1816
1724
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1832,9 +1740,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1832
1740
|
// src/core/contractIndex.ts
|
|
1833
1741
|
async function buildContractIndex(root, config) {
|
|
1834
1742
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1835
|
-
const uiRoot =
|
|
1836
|
-
const apiRoot =
|
|
1837
|
-
const dbRoot =
|
|
1743
|
+
const uiRoot = import_node_path14.default.join(contractsRoot, "ui");
|
|
1744
|
+
const apiRoot = import_node_path14.default.join(contractsRoot, "api");
|
|
1745
|
+
const dbRoot = import_node_path14.default.join(contractsRoot, "db");
|
|
1838
1746
|
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1839
1747
|
collectUiContractFiles(uiRoot),
|
|
1840
1748
|
collectThemaContractFiles(uiRoot),
|
|
@@ -1854,7 +1762,7 @@ async function buildContractIndex(root, config) {
|
|
|
1854
1762
|
}
|
|
1855
1763
|
async function indexContractFiles(files, index) {
|
|
1856
1764
|
for (const file of files) {
|
|
1857
|
-
const text = await (0,
|
|
1765
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1858
1766
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1859
1767
|
}
|
|
1860
1768
|
}
|
|
@@ -2099,14 +2007,14 @@ function parseSpec(md, file) {
|
|
|
2099
2007
|
}
|
|
2100
2008
|
|
|
2101
2009
|
// src/core/validators/contracts.ts
|
|
2102
|
-
var
|
|
2103
|
-
var
|
|
2010
|
+
var import_promises12 = require("fs/promises");
|
|
2011
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2104
2012
|
|
|
2105
2013
|
// src/core/contracts.ts
|
|
2106
|
-
var
|
|
2014
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2107
2015
|
var import_yaml2 = require("yaml");
|
|
2108
2016
|
function parseStructuredContract(file, text) {
|
|
2109
|
-
const ext =
|
|
2017
|
+
const ext = import_node_path15.default.extname(file).toLowerCase();
|
|
2110
2018
|
if (ext === ".json") {
|
|
2111
2019
|
return JSON.parse(text);
|
|
2112
2020
|
}
|
|
@@ -2128,14 +2036,14 @@ async function validateContracts(root, config) {
|
|
|
2128
2036
|
const issues = [];
|
|
2129
2037
|
const contractIndex = await buildContractIndex(root, config);
|
|
2130
2038
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2131
|
-
const uiRoot =
|
|
2039
|
+
const uiRoot = import_node_path16.default.join(contractsRoot, "ui");
|
|
2132
2040
|
const themaIds = new Set(
|
|
2133
2041
|
Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
|
|
2134
2042
|
);
|
|
2135
2043
|
issues.push(...await validateUiContracts(uiRoot, themaIds));
|
|
2136
2044
|
issues.push(...await validateThemaContracts(uiRoot));
|
|
2137
|
-
issues.push(...await validateApiContracts(
|
|
2138
|
-
issues.push(...await validateDbContracts(
|
|
2045
|
+
issues.push(...await validateApiContracts(import_node_path16.default.join(contractsRoot, "api")));
|
|
2046
|
+
issues.push(...await validateDbContracts(import_node_path16.default.join(contractsRoot, "db")));
|
|
2139
2047
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
2140
2048
|
return issues;
|
|
2141
2049
|
}
|
|
@@ -2154,7 +2062,7 @@ async function validateUiContracts(uiRoot, themaIds) {
|
|
|
2154
2062
|
}
|
|
2155
2063
|
const issues = [];
|
|
2156
2064
|
for (const file of files) {
|
|
2157
|
-
const text = await (0,
|
|
2065
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2158
2066
|
const declaredIds = extractDeclaredContractIds(text);
|
|
2159
2067
|
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
2160
2068
|
let doc = null;
|
|
@@ -2208,7 +2116,7 @@ async function validateThemaContracts(uiRoot) {
|
|
|
2208
2116
|
}
|
|
2209
2117
|
const issues = [];
|
|
2210
2118
|
for (const file of files) {
|
|
2211
|
-
const text = await (0,
|
|
2119
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2212
2120
|
const invalidIds = extractInvalidIds(text, [
|
|
2213
2121
|
"SPEC",
|
|
2214
2122
|
"BR",
|
|
@@ -2342,7 +2250,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
2342
2250
|
}
|
|
2343
2251
|
const issues = [];
|
|
2344
2252
|
for (const file of files) {
|
|
2345
|
-
const text = await (0,
|
|
2253
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2346
2254
|
const invalidIds = extractInvalidIds(text, [
|
|
2347
2255
|
"SPEC",
|
|
2348
2256
|
"BR",
|
|
@@ -2411,7 +2319,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
2411
2319
|
}
|
|
2412
2320
|
const issues = [];
|
|
2413
2321
|
for (const file of files) {
|
|
2414
|
-
const text = await (0,
|
|
2322
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2415
2323
|
const invalidIds = extractInvalidIds(text, [
|
|
2416
2324
|
"SPEC",
|
|
2417
2325
|
"BR",
|
|
@@ -2608,9 +2516,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2608
2516
|
);
|
|
2609
2517
|
return issues;
|
|
2610
2518
|
}
|
|
2611
|
-
const packDir =
|
|
2612
|
-
const packRelative =
|
|
2613
|
-
if (packRelative.startsWith("..") ||
|
|
2519
|
+
const packDir = import_node_path16.default.resolve(uiRoot, packValue);
|
|
2520
|
+
const packRelative = import_node_path16.default.relative(uiRoot, packDir);
|
|
2521
|
+
if (packRelative.startsWith("..") || import_node_path16.default.isAbsolute(packRelative)) {
|
|
2614
2522
|
issues.push(
|
|
2615
2523
|
issue(
|
|
2616
2524
|
"QFAI-ASSET-001",
|
|
@@ -2636,7 +2544,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2636
2544
|
);
|
|
2637
2545
|
return issues;
|
|
2638
2546
|
}
|
|
2639
|
-
const assetsYamlPath =
|
|
2547
|
+
const assetsYamlPath = import_node_path16.default.join(packDir, "assets.yaml");
|
|
2640
2548
|
if (!await exists6(assetsYamlPath)) {
|
|
2641
2549
|
issues.push(
|
|
2642
2550
|
issue(
|
|
@@ -2651,7 +2559,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2651
2559
|
}
|
|
2652
2560
|
let manifest;
|
|
2653
2561
|
try {
|
|
2654
|
-
const manifestText = await (0,
|
|
2562
|
+
const manifestText = await (0, import_promises12.readFile)(assetsYamlPath, "utf-8");
|
|
2655
2563
|
manifest = parseStructuredContract(assetsYamlPath, manifestText);
|
|
2656
2564
|
} catch (error2) {
|
|
2657
2565
|
issues.push(
|
|
@@ -2724,9 +2632,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2724
2632
|
);
|
|
2725
2633
|
continue;
|
|
2726
2634
|
}
|
|
2727
|
-
const assetPath =
|
|
2728
|
-
const assetRelative =
|
|
2729
|
-
if (assetRelative.startsWith("..") ||
|
|
2635
|
+
const assetPath = import_node_path16.default.resolve(packDir, entry.path);
|
|
2636
|
+
const assetRelative = import_node_path16.default.relative(packDir, assetPath);
|
|
2637
|
+
if (assetRelative.startsWith("..") || import_node_path16.default.isAbsolute(assetRelative)) {
|
|
2730
2638
|
issues.push(
|
|
2731
2639
|
issue(
|
|
2732
2640
|
"QFAI-ASSET-004",
|
|
@@ -2767,7 +2675,7 @@ function shouldIgnoreInvalidId(value, doc) {
|
|
|
2767
2675
|
return false;
|
|
2768
2676
|
}
|
|
2769
2677
|
const normalized = packValue.replace(/\\/g, "/");
|
|
2770
|
-
const basename =
|
|
2678
|
+
const basename = import_node_path16.default.posix.basename(normalized);
|
|
2771
2679
|
if (!basename) {
|
|
2772
2680
|
return false;
|
|
2773
2681
|
}
|
|
@@ -2777,7 +2685,7 @@ function isSafeRelativePath(value) {
|
|
|
2777
2685
|
if (!value) {
|
|
2778
2686
|
return false;
|
|
2779
2687
|
}
|
|
2780
|
-
if (
|
|
2688
|
+
if (import_node_path16.default.isAbsolute(value)) {
|
|
2781
2689
|
return false;
|
|
2782
2690
|
}
|
|
2783
2691
|
const normalized = value.replace(/\\/g, "/");
|
|
@@ -2792,7 +2700,7 @@ function isSafeRelativePath(value) {
|
|
|
2792
2700
|
}
|
|
2793
2701
|
async function exists6(target) {
|
|
2794
2702
|
try {
|
|
2795
|
-
await (0,
|
|
2703
|
+
await (0, import_promises12.access)(target);
|
|
2796
2704
|
return true;
|
|
2797
2705
|
} catch {
|
|
2798
2706
|
return false;
|
|
@@ -2827,8 +2735,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
2827
2735
|
}
|
|
2828
2736
|
|
|
2829
2737
|
// src/core/validators/delta.ts
|
|
2830
|
-
var
|
|
2831
|
-
var
|
|
2738
|
+
var import_promises13 = require("fs/promises");
|
|
2739
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2832
2740
|
async function validateDeltas(root, config) {
|
|
2833
2741
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2834
2742
|
const packs = await collectSpecPackDirs(specsRoot);
|
|
@@ -2837,9 +2745,9 @@ async function validateDeltas(root, config) {
|
|
|
2837
2745
|
}
|
|
2838
2746
|
const issues = [];
|
|
2839
2747
|
for (const pack of packs) {
|
|
2840
|
-
const deltaPath =
|
|
2748
|
+
const deltaPath = import_node_path17.default.join(pack, "delta.md");
|
|
2841
2749
|
try {
|
|
2842
|
-
await (0,
|
|
2750
|
+
await (0, import_promises13.readFile)(deltaPath, "utf-8");
|
|
2843
2751
|
} catch (error2) {
|
|
2844
2752
|
if (isMissingFileError2(error2)) {
|
|
2845
2753
|
issues.push(
|
|
@@ -2890,8 +2798,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
2890
2798
|
}
|
|
2891
2799
|
|
|
2892
2800
|
// src/core/validators/ids.ts
|
|
2893
|
-
var
|
|
2894
|
-
var
|
|
2801
|
+
var import_promises14 = require("fs/promises");
|
|
2802
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2895
2803
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2896
2804
|
async function validateDefinedIds(root, config) {
|
|
2897
2805
|
const issues = [];
|
|
@@ -2926,7 +2834,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2926
2834
|
}
|
|
2927
2835
|
async function collectSpecDefinitionIds(files, out) {
|
|
2928
2836
|
for (const file of files) {
|
|
2929
|
-
const text = await (0,
|
|
2837
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2930
2838
|
const parsed = parseSpec(text, file);
|
|
2931
2839
|
if (parsed.specId) {
|
|
2932
2840
|
recordId(out, parsed.specId, file);
|
|
@@ -2936,7 +2844,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2936
2844
|
}
|
|
2937
2845
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2938
2846
|
for (const file of files) {
|
|
2939
|
-
const text = await (0,
|
|
2847
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2940
2848
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2941
2849
|
if (!document || errors.length > 0) {
|
|
2942
2850
|
continue;
|
|
@@ -2957,7 +2865,7 @@ function recordId(out, id, file) {
|
|
|
2957
2865
|
}
|
|
2958
2866
|
function formatFileList(files, root) {
|
|
2959
2867
|
return files.map((file) => {
|
|
2960
|
-
const relative =
|
|
2868
|
+
const relative = import_node_path18.default.relative(root, file);
|
|
2961
2869
|
return relative.length > 0 ? relative : file;
|
|
2962
2870
|
}).join(", ");
|
|
2963
2871
|
}
|
|
@@ -2984,8 +2892,8 @@ function issue3(code, message, severity, file, rule, refs, category = "compatibi
|
|
|
2984
2892
|
}
|
|
2985
2893
|
|
|
2986
2894
|
// src/core/validators/promptsIntegrity.ts
|
|
2987
|
-
async function validatePromptsIntegrity(root) {
|
|
2988
|
-
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
2895
|
+
async function validatePromptsIntegrity(root, config) {
|
|
2896
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root, config);
|
|
2989
2897
|
if (diff.status !== "modified") {
|
|
2990
2898
|
return [];
|
|
2991
2899
|
}
|
|
@@ -3002,11 +2910,11 @@ async function validatePromptsIntegrity(root) {
|
|
|
3002
2910
|
code: "QFAI-PROMPTS-001",
|
|
3003
2911
|
severity: "error",
|
|
3004
2912
|
category: "change",
|
|
3005
|
-
message: `\u6A19\u6E96\u8CC7\u7523 '.qfai/prompts/**' \u304C\u6539\u5909\u3055\u308C\u3066\u3044\u307E\u3059\uFF08${hints || `\u5DEE\u5206=${total}`}\uFF09\u3002${sampleText}`,
|
|
2913
|
+
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}`,
|
|
3006
2914
|
suggested_action: [
|
|
3007
2915
|
"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",
|
|
3008
2916
|
"\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u5B9F\u65BD\u3057\u3066\u304F\u3060\u3055\u3044:",
|
|
3009
|
-
"- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
|
|
2917
|
+
"- \u5909\u66F4\u3057\u305F\u3044\u5834\u5408: \u540C\u4E00\u76F8\u5BFE\u30D1\u30B9\u3067 '.qfai/assistant/prompts.local/**' \u306B\u7F6E\u3044\u3066 overlay",
|
|
3010
2918
|
"- \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"
|
|
3011
2919
|
].join("\n"),
|
|
3012
2920
|
rule: "prompts.integrity"
|
|
@@ -3015,8 +2923,8 @@ async function validatePromptsIntegrity(root) {
|
|
|
3015
2923
|
}
|
|
3016
2924
|
|
|
3017
2925
|
// src/core/validators/scenario.ts
|
|
3018
|
-
var
|
|
3019
|
-
var
|
|
2926
|
+
var import_promises15 = require("fs/promises");
|
|
2927
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3020
2928
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
3021
2929
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
3022
2930
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -3039,7 +2947,7 @@ async function validateScenarios(root, config) {
|
|
|
3039
2947
|
}
|
|
3040
2948
|
const issues = [];
|
|
3041
2949
|
for (const entry of entries) {
|
|
3042
|
-
const legacyScenarioPath =
|
|
2950
|
+
const legacyScenarioPath = import_node_path19.default.join(entry.dir, "scenario.md");
|
|
3043
2951
|
if (await fileExists(legacyScenarioPath)) {
|
|
3044
2952
|
issues.push(
|
|
3045
2953
|
issue4(
|
|
@@ -3053,7 +2961,7 @@ async function validateScenarios(root, config) {
|
|
|
3053
2961
|
}
|
|
3054
2962
|
let text;
|
|
3055
2963
|
try {
|
|
3056
|
-
text = await (0,
|
|
2964
|
+
text = await (0, import_promises15.readFile)(entry.scenarioPath, "utf-8");
|
|
3057
2965
|
} catch (error2) {
|
|
3058
2966
|
if (isMissingFileError3(error2)) {
|
|
3059
2967
|
issues.push(
|
|
@@ -3228,7 +3136,7 @@ function isMissingFileError3(error2) {
|
|
|
3228
3136
|
}
|
|
3229
3137
|
async function fileExists(target) {
|
|
3230
3138
|
try {
|
|
3231
|
-
await (0,
|
|
3139
|
+
await (0, import_promises15.access)(target);
|
|
3232
3140
|
return true;
|
|
3233
3141
|
} catch {
|
|
3234
3142
|
return false;
|
|
@@ -3236,7 +3144,7 @@ async function fileExists(target) {
|
|
|
3236
3144
|
}
|
|
3237
3145
|
|
|
3238
3146
|
// src/core/validators/spec.ts
|
|
3239
|
-
var
|
|
3147
|
+
var import_promises16 = require("fs/promises");
|
|
3240
3148
|
async function validateSpecs(root, config) {
|
|
3241
3149
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
3242
3150
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -3257,7 +3165,7 @@ async function validateSpecs(root, config) {
|
|
|
3257
3165
|
for (const entry of entries) {
|
|
3258
3166
|
let text;
|
|
3259
3167
|
try {
|
|
3260
|
-
text = await (0,
|
|
3168
|
+
text = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
|
|
3261
3169
|
} catch (error2) {
|
|
3262
3170
|
if (isMissingFileError4(error2)) {
|
|
3263
3171
|
issues.push(
|
|
@@ -3411,7 +3319,7 @@ function isMissingFileError4(error2) {
|
|
|
3411
3319
|
}
|
|
3412
3320
|
|
|
3413
3321
|
// src/core/validators/traceability.ts
|
|
3414
|
-
var
|
|
3322
|
+
var import_promises17 = require("fs/promises");
|
|
3415
3323
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
3416
3324
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
3417
3325
|
async function validateTraceability(root, config) {
|
|
@@ -3431,7 +3339,7 @@ async function validateTraceability(root, config) {
|
|
|
3431
3339
|
const contractIndex = await buildContractIndex(root, config);
|
|
3432
3340
|
const contractIds = contractIndex.ids;
|
|
3433
3341
|
for (const file of specFiles) {
|
|
3434
|
-
const text = await (0,
|
|
3342
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
3435
3343
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3436
3344
|
const parsed = parseSpec(text, file);
|
|
3437
3345
|
if (parsed.specId) {
|
|
@@ -3504,7 +3412,7 @@ async function validateTraceability(root, config) {
|
|
|
3504
3412
|
}
|
|
3505
3413
|
}
|
|
3506
3414
|
for (const file of scenarioFiles) {
|
|
3507
|
-
const text = await (0,
|
|
3415
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
3508
3416
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3509
3417
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
3510
3418
|
allowCommentPrefix: true
|
|
@@ -3826,7 +3734,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
3826
3734
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
3827
3735
|
let found = false;
|
|
3828
3736
|
for (const file of targetFiles) {
|
|
3829
|
-
const text = await (0,
|
|
3737
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
3830
3738
|
if (pattern.test(text)) {
|
|
3831
3739
|
found = true;
|
|
3832
3740
|
break;
|
|
@@ -3877,7 +3785,7 @@ async function validateProject(root, configResult) {
|
|
|
3877
3785
|
const { config, issues: configIssues } = resolved;
|
|
3878
3786
|
const issues = [
|
|
3879
3787
|
...configIssues,
|
|
3880
|
-
...await validatePromptsIntegrity(root),
|
|
3788
|
+
...await validatePromptsIntegrity(root, config),
|
|
3881
3789
|
...await validateSpecs(root, config),
|
|
3882
3790
|
...await validateDeltas(root, config),
|
|
3883
3791
|
...await validateScenarios(root, config),
|
|
@@ -3926,15 +3834,15 @@ var ID_PREFIXES2 = [
|
|
|
3926
3834
|
"THEMA"
|
|
3927
3835
|
];
|
|
3928
3836
|
async function createReportData(root, validation, configResult) {
|
|
3929
|
-
const resolvedRoot =
|
|
3837
|
+
const resolvedRoot = import_node_path20.default.resolve(root);
|
|
3930
3838
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3931
3839
|
const config = resolved.config;
|
|
3932
3840
|
const configPath = resolved.configPath;
|
|
3933
3841
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3934
3842
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3935
|
-
const apiRoot =
|
|
3936
|
-
const uiRoot =
|
|
3937
|
-
const dbRoot =
|
|
3843
|
+
const apiRoot = import_node_path20.default.join(contractsRoot, "api");
|
|
3844
|
+
const uiRoot = import_node_path20.default.join(contractsRoot, "ui");
|
|
3845
|
+
const dbRoot = import_node_path20.default.join(contractsRoot, "db");
|
|
3938
3846
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3939
3847
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3940
3848
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -4400,7 +4308,9 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
4400
4308
|
);
|
|
4401
4309
|
}
|
|
4402
4310
|
lines.push("- \u5909\u66F4\u5185\u5BB9\u30FB\u53D7\u5165\u89B3\u70B9\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002");
|
|
4403
|
-
lines.push(
|
|
4311
|
+
lines.push(
|
|
4312
|
+
"- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/assistant/instructions/constitution.md`"
|
|
4313
|
+
);
|
|
4404
4314
|
return lines.join("\n");
|
|
4405
4315
|
}
|
|
4406
4316
|
function formatReportJson(data) {
|
|
@@ -4414,7 +4324,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
4414
4324
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
4415
4325
|
}
|
|
4416
4326
|
for (const file of specFiles) {
|
|
4417
|
-
const text = await (0,
|
|
4327
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4418
4328
|
const parsed = parseSpec(text, file);
|
|
4419
4329
|
const specKey = parsed.specId;
|
|
4420
4330
|
if (!specKey) {
|
|
@@ -4456,7 +4366,7 @@ async function collectIds(files) {
|
|
|
4456
4366
|
THEMA: /* @__PURE__ */ new Set()
|
|
4457
4367
|
};
|
|
4458
4368
|
for (const file of files) {
|
|
4459
|
-
const text = await (0,
|
|
4369
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4460
4370
|
for (const prefix of ID_PREFIXES2) {
|
|
4461
4371
|
const ids = extractIds(text, prefix);
|
|
4462
4372
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -4475,7 +4385,7 @@ async function collectIds(files) {
|
|
|
4475
4385
|
async function collectUpstreamIds(files) {
|
|
4476
4386
|
const ids = /* @__PURE__ */ new Set();
|
|
4477
4387
|
for (const file of files) {
|
|
4478
|
-
const text = await (0,
|
|
4388
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4479
4389
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
4480
4390
|
}
|
|
4481
4391
|
return ids;
|
|
@@ -4496,7 +4406,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
4496
4406
|
}
|
|
4497
4407
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
4498
4408
|
for (const file of targetFiles) {
|
|
4499
|
-
const text = await (0,
|
|
4409
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4500
4410
|
if (pattern.test(text)) {
|
|
4501
4411
|
return true;
|
|
4502
4412
|
}
|
|
@@ -4633,7 +4543,7 @@ function warnIfTruncated(scan, context) {
|
|
|
4633
4543
|
|
|
4634
4544
|
// src/cli/commands/report.ts
|
|
4635
4545
|
async function runReport(options) {
|
|
4636
|
-
const root =
|
|
4546
|
+
const root = import_node_path21.default.resolve(options.root);
|
|
4637
4547
|
const configResult = await loadConfig(root);
|
|
4638
4548
|
let validation;
|
|
4639
4549
|
if (options.runValidate) {
|
|
@@ -4650,7 +4560,7 @@ async function runReport(options) {
|
|
|
4650
4560
|
validation = normalized;
|
|
4651
4561
|
} else {
|
|
4652
4562
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
4653
|
-
const inputPath =
|
|
4563
|
+
const inputPath = import_node_path21.default.isAbsolute(input) ? input : import_node_path21.default.resolve(root, input);
|
|
4654
4564
|
try {
|
|
4655
4565
|
validation = await readValidationResult(inputPath);
|
|
4656
4566
|
} catch (err) {
|
|
@@ -4661,7 +4571,7 @@ async function runReport(options) {
|
|
|
4661
4571
|
"",
|
|
4662
4572
|
"\u307E\u305A qfai validate \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4F8B:",
|
|
4663
4573
|
" qfai validate",
|
|
4664
|
-
"\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u51FA\u529B\u5148: .qfai/
|
|
4574
|
+
"\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u51FA\u529B\u5148: .qfai/report/validate.json\uFF09",
|
|
4665
4575
|
"",
|
|
4666
4576
|
"\u307E\u305F\u306F report \u306B --run-validate \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
4667
4577
|
"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"
|
|
@@ -4677,11 +4587,11 @@ async function runReport(options) {
|
|
|
4677
4587
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
4678
4588
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
4679
4589
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
4680
|
-
const defaultOut = options.format === "json" ?
|
|
4590
|
+
const defaultOut = options.format === "json" ? import_node_path21.default.join(outRoot, "report.json") : import_node_path21.default.join(outRoot, "report.md");
|
|
4681
4591
|
const out = options.outPath ?? defaultOut;
|
|
4682
|
-
const outPath =
|
|
4683
|
-
await (0,
|
|
4684
|
-
await (0,
|
|
4592
|
+
const outPath = import_node_path21.default.isAbsolute(out) ? out : import_node_path21.default.resolve(root, out);
|
|
4593
|
+
await (0, import_promises19.mkdir)(import_node_path21.default.dirname(outPath), { recursive: true });
|
|
4594
|
+
await (0, import_promises19.writeFile)(outPath, `${output}
|
|
4685
4595
|
`, "utf-8");
|
|
4686
4596
|
info(
|
|
4687
4597
|
`report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
|
|
@@ -4689,7 +4599,7 @@ async function runReport(options) {
|
|
|
4689
4599
|
info(`wrote report: ${outPath}`);
|
|
4690
4600
|
}
|
|
4691
4601
|
async function readValidationResult(inputPath) {
|
|
4692
|
-
const raw = await (0,
|
|
4602
|
+
const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
|
|
4693
4603
|
const parsed = JSON.parse(raw);
|
|
4694
4604
|
if (!isValidationResult(parsed)) {
|
|
4695
4605
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -4745,15 +4655,15 @@ function isMissingFileError5(error2) {
|
|
|
4745
4655
|
return record2.code === "ENOENT";
|
|
4746
4656
|
}
|
|
4747
4657
|
async function writeValidationResult(root, outputPath, result) {
|
|
4748
|
-
const abs =
|
|
4749
|
-
await (0,
|
|
4750
|
-
await (0,
|
|
4658
|
+
const abs = import_node_path21.default.isAbsolute(outputPath) ? outputPath : import_node_path21.default.resolve(root, outputPath);
|
|
4659
|
+
await (0, import_promises19.mkdir)(import_node_path21.default.dirname(abs), { recursive: true });
|
|
4660
|
+
await (0, import_promises19.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
4751
4661
|
`, "utf-8");
|
|
4752
4662
|
}
|
|
4753
4663
|
|
|
4754
4664
|
// src/cli/commands/validate.ts
|
|
4755
|
-
var
|
|
4756
|
-
var
|
|
4665
|
+
var import_promises20 = require("fs/promises");
|
|
4666
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
4757
4667
|
|
|
4758
4668
|
// src/cli/lib/failOn.ts
|
|
4759
4669
|
function shouldFail(result, failOn) {
|
|
@@ -4768,7 +4678,7 @@ function shouldFail(result, failOn) {
|
|
|
4768
4678
|
|
|
4769
4679
|
// src/cli/commands/validate.ts
|
|
4770
4680
|
async function runValidate(options) {
|
|
4771
|
-
const root =
|
|
4681
|
+
const root = import_node_path22.default.resolve(options.root);
|
|
4772
4682
|
const configResult = await loadConfig(root);
|
|
4773
4683
|
const result = await validateProject(root, configResult);
|
|
4774
4684
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4893,12 +4803,12 @@ function issueKey(issue7) {
|
|
|
4893
4803
|
}
|
|
4894
4804
|
async function emitJson(result, root, jsonPath) {
|
|
4895
4805
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4896
|
-
await (0,
|
|
4897
|
-
await (0,
|
|
4806
|
+
await (0, import_promises20.mkdir)(import_node_path22.default.dirname(abs), { recursive: true });
|
|
4807
|
+
await (0, import_promises20.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
4898
4808
|
`, "utf-8");
|
|
4899
4809
|
}
|
|
4900
4810
|
function resolveJsonPath(root, jsonPath) {
|
|
4901
|
-
return
|
|
4811
|
+
return import_node_path22.default.isAbsolute(jsonPath) ? jsonPath : import_node_path22.default.resolve(root, jsonPath);
|
|
4902
4812
|
}
|
|
4903
4813
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4904
4814
|
|
|
@@ -4911,7 +4821,6 @@ function parseArgs(argv, cwd) {
|
|
|
4911
4821
|
force: false,
|
|
4912
4822
|
yes: false,
|
|
4913
4823
|
dryRun: false,
|
|
4914
|
-
analyzeList: false,
|
|
4915
4824
|
reportFormat: "md",
|
|
4916
4825
|
reportRunValidate: false,
|
|
4917
4826
|
doctorFormat: "text",
|
|
@@ -4963,18 +4872,6 @@ function parseArgs(argv, cwd) {
|
|
|
4963
4872
|
case "--dry-run":
|
|
4964
4873
|
options.dryRun = true;
|
|
4965
4874
|
break;
|
|
4966
|
-
case "--list":
|
|
4967
|
-
options.analyzeList = true;
|
|
4968
|
-
break;
|
|
4969
|
-
case "--prompt":
|
|
4970
|
-
{
|
|
4971
|
-
const next = readOptionValue(args, i);
|
|
4972
|
-
if (next) {
|
|
4973
|
-
options.analyzePrompt = next;
|
|
4974
|
-
i += 1;
|
|
4975
|
-
}
|
|
4976
|
-
}
|
|
4977
|
-
break;
|
|
4978
4875
|
case "--format": {
|
|
4979
4876
|
const next = readOptionValue(args, i);
|
|
4980
4877
|
if (next === null) {
|
|
@@ -5108,17 +5005,6 @@ async function run(argv, cwd) {
|
|
|
5108
5005
|
yes: options.yes
|
|
5109
5006
|
});
|
|
5110
5007
|
return;
|
|
5111
|
-
case "analyze":
|
|
5112
|
-
{
|
|
5113
|
-
const resolvedRoot = await resolveRoot(options);
|
|
5114
|
-
const exitCode = await runAnalyze({
|
|
5115
|
-
root: resolvedRoot,
|
|
5116
|
-
list: options.analyzeList,
|
|
5117
|
-
...options.analyzePrompt !== void 0 ? { prompt: options.analyzePrompt } : {}
|
|
5118
|
-
});
|
|
5119
|
-
process.exitCode = exitCode;
|
|
5120
|
-
}
|
|
5121
|
-
return;
|
|
5122
5008
|
case "validate":
|
|
5123
5009
|
{
|
|
5124
5010
|
const resolvedRoot = await resolveRoot(options);
|
|
@@ -5166,7 +5052,6 @@ function usage() {
|
|
|
5166
5052
|
|
|
5167
5053
|
Commands:
|
|
5168
5054
|
init \u30C6\u30F3\u30D7\u30EC\u3092\u751F\u6210
|
|
5169
|
-
analyze \u610F\u5473\u30EC\u30D9\u30EB\u306E\u30EC\u30D3\u30E5\u30FC\u88DC\u52A9\uFF08\u30D7\u30ED\u30F3\u30D7\u30C8\u51FA\u529B\uFF09
|
|
5170
5055
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
5171
5056
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
5172
5057
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
@@ -5174,11 +5059,9 @@ Commands:
|
|
|
5174
5059
|
Options:
|
|
5175
5060
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
5176
5061
|
--dir <path> init \u306E\u51FA\u529B\u5148
|
|
5177
|
-
--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
|
|
5062
|
+
--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
|
|
5178
5063
|
--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
|
|
5179
5064
|
--dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
|
|
5180
|
-
--list analyze: \u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7\u3092\u8868\u793A
|
|
5181
|
-
--prompt <name> analyze: \u6307\u5B9A\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08.md\u7701\u7565\u53EF\uFF09\u3092\u51FA\u529B
|
|
5182
5065
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|
|
5183
5066
|
--format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
|
|
5184
5067
|
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|