qfai 1.0.3 → 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 -74
- package/assets/init/.qfai/README.md +17 -82
- 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 -57
- package/assets/init/root/.github/workflows/qfai.yml +1 -1
- package/assets/init/root/qfai.config.yaml +3 -4
- package/dist/cli/index.cjs +313 -472
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +295 -454
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +37 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +37 -63
- 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 -7
- package/assets/init/.qfai/promptpack/constitution.md +0 -15
- package/assets/init/.qfai/promptpack/modes/change.md +0 -5
- package/assets/init/.qfai/promptpack/modes/compatibility.md +0 -6
- 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/compatibility-vs-change.md +0 -42
- 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 -70
- 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-classify-change.md +0 -33
- 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 -41
- package/assets/init/.qfai/prompts.local/README.md +0 -31
- package/assets/init/.qfai/rules/conventions.md +0 -27
- package/assets/init/.qfai/rules/pnpm.md +0 -29
- package/assets/init/.qfai/samples/analyze/analysis.md +0 -38
- package/assets/init/.qfai/samples/analyze/input_bundle.md +0 -54
- package/assets/init/.qfai/specs/spec-0001/delta.md +0 -30
- 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,231 +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
|
-
|
|
249
|
-
|
|
250
|
-
promptsDir: ".qfai/prompts",
|
|
42
|
+
outDir: ".qfai/report",
|
|
43
|
+
promptsDir: ".qfai/assistant/prompts",
|
|
251
44
|
srcDir: "src",
|
|
252
45
|
testsDir: "tests"
|
|
253
46
|
},
|
|
@@ -275,21 +68,21 @@ var defaultConfig = {
|
|
|
275
68
|
}
|
|
276
69
|
},
|
|
277
70
|
output: {
|
|
278
|
-
validateJsonPath: ".qfai/
|
|
71
|
+
validateJsonPath: ".qfai/report/validate.json"
|
|
279
72
|
}
|
|
280
73
|
};
|
|
281
74
|
function getConfigPath(root) {
|
|
282
|
-
return
|
|
75
|
+
return import_node_path.default.join(root, "qfai.config.yaml");
|
|
283
76
|
}
|
|
284
77
|
async function findConfigRoot(startDir) {
|
|
285
|
-
const resolvedStart =
|
|
78
|
+
const resolvedStart = import_node_path.default.resolve(startDir);
|
|
286
79
|
let current = resolvedStart;
|
|
287
80
|
while (true) {
|
|
288
81
|
const configPath = getConfigPath(current);
|
|
289
|
-
if (await
|
|
82
|
+
if (await exists(configPath)) {
|
|
290
83
|
return { root: current, configPath, found: true };
|
|
291
84
|
}
|
|
292
|
-
const parent =
|
|
85
|
+
const parent = import_node_path.default.dirname(current);
|
|
293
86
|
if (parent === current) {
|
|
294
87
|
break;
|
|
295
88
|
}
|
|
@@ -306,7 +99,7 @@ async function loadConfig(root) {
|
|
|
306
99
|
const issues = [];
|
|
307
100
|
let parsed;
|
|
308
101
|
try {
|
|
309
|
-
const raw = await (0,
|
|
102
|
+
const raw = await (0, import_promises.readFile)(configPath, "utf-8");
|
|
310
103
|
parsed = (0, import_yaml.parse)(raw);
|
|
311
104
|
} catch (error2) {
|
|
312
105
|
if (isMissingFile(error2)) {
|
|
@@ -319,7 +112,7 @@ async function loadConfig(root) {
|
|
|
319
112
|
return { config: normalized, issues, configPath };
|
|
320
113
|
}
|
|
321
114
|
function resolvePath(root, config, key) {
|
|
322
|
-
return
|
|
115
|
+
return import_node_path.default.resolve(root, config.paths[key]);
|
|
323
116
|
}
|
|
324
117
|
function normalizeConfig(raw, configPath, issues) {
|
|
325
118
|
if (!isRecord(raw)) {
|
|
@@ -358,13 +151,6 @@ function normalizePaths(raw, configPath, issues) {
|
|
|
358
151
|
configPath,
|
|
359
152
|
issues
|
|
360
153
|
),
|
|
361
|
-
rulesDir: readString(
|
|
362
|
-
raw.rulesDir,
|
|
363
|
-
base.rulesDir,
|
|
364
|
-
"paths.rulesDir",
|
|
365
|
-
configPath,
|
|
366
|
-
issues
|
|
367
|
-
),
|
|
368
154
|
outDir: readString(
|
|
369
155
|
raw.outDir,
|
|
370
156
|
base.outDir,
|
|
@@ -619,9 +405,9 @@ function isMissingFile(error2) {
|
|
|
619
405
|
}
|
|
620
406
|
return false;
|
|
621
407
|
}
|
|
622
|
-
async function
|
|
408
|
+
async function exists(target) {
|
|
623
409
|
try {
|
|
624
|
-
await (0,
|
|
410
|
+
await (0, import_promises.access)(target);
|
|
625
411
|
return true;
|
|
626
412
|
} catch {
|
|
627
413
|
return false;
|
|
@@ -638,27 +424,128 @@ function isRecord(value) {
|
|
|
638
424
|
}
|
|
639
425
|
|
|
640
426
|
// src/core/discovery.ts
|
|
641
|
-
var import_promises5 = require("fs/promises");
|
|
642
|
-
var import_node_path5 = __toESM(require("path"), 1);
|
|
643
|
-
|
|
644
|
-
// src/core/specLayout.ts
|
|
645
427
|
var import_promises4 = require("fs/promises");
|
|
646
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);
|
|
647
534
|
var SPEC_DIR_RE = /^spec-\d{4}$/;
|
|
648
535
|
async function collectSpecEntries(specsRoot) {
|
|
649
536
|
const dirs = await listSpecDirs(specsRoot);
|
|
650
537
|
const entries = dirs.map((dir) => ({
|
|
651
538
|
dir,
|
|
652
|
-
specPath:
|
|
653
|
-
deltaPath:
|
|
654
|
-
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")
|
|
655
542
|
}));
|
|
656
543
|
return entries.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
657
544
|
}
|
|
658
545
|
async function listSpecDirs(specsRoot) {
|
|
659
546
|
try {
|
|
660
|
-
const items = await (0,
|
|
661
|
-
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));
|
|
662
549
|
} catch (error2) {
|
|
663
550
|
if (isMissingFileError(error2)) {
|
|
664
551
|
return [];
|
|
@@ -720,7 +607,7 @@ async function filterExisting(files) {
|
|
|
720
607
|
}
|
|
721
608
|
async function exists3(target) {
|
|
722
609
|
try {
|
|
723
|
-
await (0,
|
|
610
|
+
await (0, import_promises4.access)(target);
|
|
724
611
|
return true;
|
|
725
612
|
} catch {
|
|
726
613
|
return false;
|
|
@@ -729,20 +616,20 @@ async function exists3(target) {
|
|
|
729
616
|
function filterByBasenamePrefix(files, prefix) {
|
|
730
617
|
const lowerPrefix = prefix.toLowerCase();
|
|
731
618
|
return files.filter(
|
|
732
|
-
(file) =>
|
|
619
|
+
(file) => import_node_path4.default.basename(file).toLowerCase().startsWith(lowerPrefix)
|
|
733
620
|
);
|
|
734
621
|
}
|
|
735
622
|
|
|
736
623
|
// src/core/paths.ts
|
|
737
|
-
var
|
|
624
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
738
625
|
function toRelativePath(root, target) {
|
|
739
626
|
if (!target) {
|
|
740
627
|
return target;
|
|
741
628
|
}
|
|
742
|
-
if (!
|
|
629
|
+
if (!import_node_path5.default.isAbsolute(target)) {
|
|
743
630
|
return toPosixPath(target);
|
|
744
631
|
}
|
|
745
|
-
const relative =
|
|
632
|
+
const relative = import_node_path5.default.relative(root, target);
|
|
746
633
|
if (!relative) {
|
|
747
634
|
return ".";
|
|
748
635
|
}
|
|
@@ -753,8 +640,8 @@ function toPosixPath(value) {
|
|
|
753
640
|
}
|
|
754
641
|
|
|
755
642
|
// src/core/traceability.ts
|
|
756
|
-
var
|
|
757
|
-
var
|
|
643
|
+
var import_promises5 = require("fs/promises");
|
|
644
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
758
645
|
|
|
759
646
|
// src/core/gherkin/parse.ts
|
|
760
647
|
var import_gherkin = require("@cucumber/gherkin");
|
|
@@ -906,7 +793,7 @@ function extractAnnotatedScIds(text) {
|
|
|
906
793
|
async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
907
794
|
const scIds = /* @__PURE__ */ new Set();
|
|
908
795
|
for (const file of scenarioFiles) {
|
|
909
|
-
const text = await (0,
|
|
796
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
910
797
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
911
798
|
if (!document || errors.length > 0) {
|
|
912
799
|
continue;
|
|
@@ -924,7 +811,7 @@ async function collectScIdsFromScenarioFiles(scenarioFiles) {
|
|
|
924
811
|
async function collectScIdSourcesFromScenarioFiles(scenarioFiles) {
|
|
925
812
|
const sources = /* @__PURE__ */ new Map();
|
|
926
813
|
for (const file of scenarioFiles) {
|
|
927
|
-
const text = await (0,
|
|
814
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
928
815
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
929
816
|
if (!document || errors.length > 0) {
|
|
930
817
|
continue;
|
|
@@ -982,10 +869,10 @@ async function collectScTestReferences(root, globs, excludeGlobs) {
|
|
|
982
869
|
};
|
|
983
870
|
}
|
|
984
871
|
const normalizedFiles = Array.from(
|
|
985
|
-
new Set(scanResult.files.map((file) =>
|
|
872
|
+
new Set(scanResult.files.map((file) => import_node_path6.default.normalize(file)))
|
|
986
873
|
);
|
|
987
874
|
for (const file of normalizedFiles) {
|
|
988
|
-
const text = await (0,
|
|
875
|
+
const text = await (0, import_promises5.readFile)(file, "utf-8");
|
|
989
876
|
const scIds = extractAnnotatedScIds(text);
|
|
990
877
|
if (scIds.length === 0) {
|
|
991
878
|
continue;
|
|
@@ -1044,20 +931,20 @@ function formatError3(error2) {
|
|
|
1044
931
|
}
|
|
1045
932
|
|
|
1046
933
|
// src/core/promptsIntegrity.ts
|
|
1047
|
-
var
|
|
1048
|
-
var
|
|
934
|
+
var import_promises6 = require("fs/promises");
|
|
935
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
1049
936
|
|
|
1050
937
|
// src/shared/assets.ts
|
|
1051
938
|
var import_node_fs = require("fs");
|
|
1052
|
-
var
|
|
939
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1053
940
|
var import_node_url = require("url");
|
|
1054
941
|
function getInitAssetsDir() {
|
|
1055
942
|
const base = __filename;
|
|
1056
943
|
const basePath = base.startsWith("file:") ? (0, import_node_url.fileURLToPath)(base) : base;
|
|
1057
|
-
const baseDir =
|
|
944
|
+
const baseDir = import_node_path7.default.dirname(basePath);
|
|
1058
945
|
const candidates = [
|
|
1059
|
-
|
|
1060
|
-
|
|
946
|
+
import_node_path7.default.resolve(baseDir, "../../../assets/init"),
|
|
947
|
+
import_node_path7.default.resolve(baseDir, "../../assets/init")
|
|
1061
948
|
];
|
|
1062
949
|
for (const candidate of candidates) {
|
|
1063
950
|
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
@@ -1074,11 +961,25 @@ function getInitAssetsDir() {
|
|
|
1074
961
|
}
|
|
1075
962
|
|
|
1076
963
|
// src/core/promptsIntegrity.ts
|
|
1077
|
-
|
|
1078
|
-
|
|
964
|
+
var LEGACY_OK_EXTRA = /* @__PURE__ */ new Set(["qfai-classify-change.md"]);
|
|
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);
|
|
1079
968
|
let templateDir;
|
|
1080
969
|
try {
|
|
1081
|
-
|
|
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);
|
|
1082
983
|
} catch {
|
|
1083
984
|
return {
|
|
1084
985
|
status: "skipped_missing_assets",
|
|
@@ -1122,6 +1023,7 @@ async function diffProjectPromptsAgainstInitAssets(root) {
|
|
|
1122
1023
|
extra.push(rel);
|
|
1123
1024
|
}
|
|
1124
1025
|
}
|
|
1026
|
+
const filteredExtra = extra.filter((rel) => !LEGACY_OK_EXTRA.has(rel));
|
|
1125
1027
|
const common = intersectKeys(templateByRel, projectByRel);
|
|
1126
1028
|
for (const rel of common) {
|
|
1127
1029
|
const templateAbs = templateByRel.get(rel);
|
|
@@ -1131,8 +1033,8 @@ async function diffProjectPromptsAgainstInitAssets(root) {
|
|
|
1131
1033
|
}
|
|
1132
1034
|
try {
|
|
1133
1035
|
const [a, b] = await Promise.all([
|
|
1134
|
-
(0,
|
|
1135
|
-
(0,
|
|
1036
|
+
(0, import_promises6.readFile)(templateAbs, "utf-8"),
|
|
1037
|
+
(0, import_promises6.readFile)(projectAbs, "utf-8")
|
|
1136
1038
|
]);
|
|
1137
1039
|
if (normalizeNewlines(a) !== normalizeNewlines(b)) {
|
|
1138
1040
|
changed.push(rel);
|
|
@@ -1141,13 +1043,13 @@ async function diffProjectPromptsAgainstInitAssets(root) {
|
|
|
1141
1043
|
changed.push(rel);
|
|
1142
1044
|
}
|
|
1143
1045
|
}
|
|
1144
|
-
const status = missing.length > 0 ||
|
|
1046
|
+
const status = missing.length > 0 || filteredExtra.length > 0 || changed.length > 0 ? "modified" : "ok";
|
|
1145
1047
|
return {
|
|
1146
1048
|
status,
|
|
1147
1049
|
promptsDir,
|
|
1148
1050
|
templateDir,
|
|
1149
1051
|
missing: missing.sort(),
|
|
1150
|
-
extra:
|
|
1052
|
+
extra: filteredExtra.sort(),
|
|
1151
1053
|
changed: changed.sort()
|
|
1152
1054
|
};
|
|
1153
1055
|
}
|
|
@@ -1155,7 +1057,7 @@ function normalizeNewlines(text) {
|
|
|
1155
1057
|
return text.replace(/\r\n/g, "\n");
|
|
1156
1058
|
}
|
|
1157
1059
|
function toRel(base, abs) {
|
|
1158
|
-
const rel =
|
|
1060
|
+
const rel = import_node_path8.default.relative(base, abs);
|
|
1159
1061
|
return rel.replace(/[\\/]+/g, "/");
|
|
1160
1062
|
}
|
|
1161
1063
|
function intersectKeys(a, b) {
|
|
@@ -1169,16 +1071,16 @@ function intersectKeys(a, b) {
|
|
|
1169
1071
|
}
|
|
1170
1072
|
|
|
1171
1073
|
// src/core/version.ts
|
|
1172
|
-
var
|
|
1173
|
-
var
|
|
1074
|
+
var import_promises7 = require("fs/promises");
|
|
1075
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
1174
1076
|
var import_node_url2 = require("url");
|
|
1175
1077
|
async function resolveToolVersion() {
|
|
1176
|
-
if ("1.0.
|
|
1177
|
-
return "1.0.
|
|
1078
|
+
if ("1.0.5".length > 0) {
|
|
1079
|
+
return "1.0.5";
|
|
1178
1080
|
}
|
|
1179
1081
|
try {
|
|
1180
1082
|
const packagePath = resolvePackageJsonPath();
|
|
1181
|
-
const raw = await (0,
|
|
1083
|
+
const raw = await (0, import_promises7.readFile)(packagePath, "utf-8");
|
|
1182
1084
|
const parsed = JSON.parse(raw);
|
|
1183
1085
|
const version = typeof parsed.version === "string" ? parsed.version : "";
|
|
1184
1086
|
return version.length > 0 ? version : "unknown";
|
|
@@ -1189,13 +1091,13 @@ async function resolveToolVersion() {
|
|
|
1189
1091
|
function resolvePackageJsonPath() {
|
|
1190
1092
|
const base = __filename;
|
|
1191
1093
|
const basePath = base.startsWith("file:") ? (0, import_node_url2.fileURLToPath)(base) : base;
|
|
1192
|
-
return
|
|
1094
|
+
return import_node_path9.default.resolve(import_node_path9.default.dirname(basePath), "../../package.json");
|
|
1193
1095
|
}
|
|
1194
1096
|
|
|
1195
1097
|
// src/core/doctor.ts
|
|
1196
1098
|
async function exists4(target) {
|
|
1197
1099
|
try {
|
|
1198
|
-
await (0,
|
|
1100
|
+
await (0, import_promises8.access)(target);
|
|
1199
1101
|
return true;
|
|
1200
1102
|
} catch {
|
|
1201
1103
|
return false;
|
|
@@ -1215,7 +1117,7 @@ function normalizeGlobs2(values) {
|
|
|
1215
1117
|
return values.map((glob) => glob.trim()).filter((glob) => glob.length > 0);
|
|
1216
1118
|
}
|
|
1217
1119
|
async function createDoctorData(options) {
|
|
1218
|
-
const startDir =
|
|
1120
|
+
const startDir = import_node_path10.default.resolve(options.startDir);
|
|
1219
1121
|
const checks = [];
|
|
1220
1122
|
const configPath = getConfigPath(startDir);
|
|
1221
1123
|
const search = options.rootExplicit ? {
|
|
@@ -1264,7 +1166,6 @@ async function createDoctorData(options) {
|
|
|
1264
1166
|
"outDir",
|
|
1265
1167
|
"srcDir",
|
|
1266
1168
|
"testsDir",
|
|
1267
|
-
"rulesDir",
|
|
1268
1169
|
"promptsDir"
|
|
1269
1170
|
];
|
|
1270
1171
|
for (const key of pathKeys) {
|
|
@@ -1278,9 +1179,9 @@ async function createDoctorData(options) {
|
|
|
1278
1179
|
details: { path: toRelativePath(root, resolved) }
|
|
1279
1180
|
});
|
|
1280
1181
|
if (key === "promptsDir") {
|
|
1281
|
-
const promptsLocalDir =
|
|
1282
|
-
|
|
1283
|
-
`${
|
|
1182
|
+
const promptsLocalDir = import_node_path10.default.join(
|
|
1183
|
+
import_node_path10.default.dirname(resolved),
|
|
1184
|
+
`${import_node_path10.default.basename(resolved)}.local`
|
|
1284
1185
|
);
|
|
1285
1186
|
const found = await exists4(promptsLocalDir);
|
|
1286
1187
|
addCheck(checks, {
|
|
@@ -1290,12 +1191,12 @@ async function createDoctorData(options) {
|
|
|
1290
1191
|
message: found ? "prompts.local exists (overlay can be used)" : "prompts.local is optional (create it to override prompts)",
|
|
1291
1192
|
details: { path: toRelativePath(root, promptsLocalDir) }
|
|
1292
1193
|
});
|
|
1293
|
-
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
1194
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root, config);
|
|
1294
1195
|
if (diff.status === "skipped_missing_prompts") {
|
|
1295
1196
|
addCheck(checks, {
|
|
1296
1197
|
id: "prompts.integrity",
|
|
1297
1198
|
severity: "info",
|
|
1298
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1199
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1299
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",
|
|
1300
1201
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1301
1202
|
});
|
|
@@ -1303,7 +1204,7 @@ async function createDoctorData(options) {
|
|
|
1303
1204
|
addCheck(checks, {
|
|
1304
1205
|
id: "prompts.integrity",
|
|
1305
1206
|
severity: "info",
|
|
1306
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1207
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1307
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",
|
|
1308
1209
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1309
1210
|
});
|
|
@@ -1311,7 +1212,7 @@ async function createDoctorData(options) {
|
|
|
1311
1212
|
addCheck(checks, {
|
|
1312
1213
|
id: "prompts.integrity",
|
|
1313
1214
|
severity: "ok",
|
|
1314
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1215
|
+
title: "Prompts integrity (.qfai/assistant/prompts)",
|
|
1315
1216
|
message: "\u6A19\u6E96 assets \u3068\u4E00\u81F4\u3057\u3066\u3044\u307E\u3059",
|
|
1316
1217
|
details: { promptsDir: toRelativePath(root, diff.promptsDir) }
|
|
1317
1218
|
});
|
|
@@ -1319,15 +1220,15 @@ async function createDoctorData(options) {
|
|
|
1319
1220
|
addCheck(checks, {
|
|
1320
1221
|
id: "prompts.integrity",
|
|
1321
1222
|
severity: "error",
|
|
1322
|
-
title: "Prompts integrity (.qfai/prompts)",
|
|
1323
|
-
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",
|
|
1324
1225
|
details: {
|
|
1325
1226
|
promptsDir: toRelativePath(root, diff.promptsDir),
|
|
1326
1227
|
missing: diff.missing,
|
|
1327
1228
|
extra: diff.extra,
|
|
1328
1229
|
changed: diff.changed,
|
|
1329
1230
|
nextActions: [
|
|
1330
|
-
"\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",
|
|
1331
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"
|
|
1332
1233
|
]
|
|
1333
1234
|
}
|
|
@@ -1353,7 +1254,7 @@ async function createDoctorData(options) {
|
|
|
1353
1254
|
message: missingFiles === 0 ? `All spec packs have required files (count=${entries.length})` : `Missing required files in spec packs (missingFiles=${missingFiles})`,
|
|
1354
1255
|
details: { specPacks: entries.length, missingFiles }
|
|
1355
1256
|
});
|
|
1356
|
-
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);
|
|
1357
1258
|
const validateJsonExists = await exists4(validateJsonAbs);
|
|
1358
1259
|
addCheck(checks, {
|
|
1359
1260
|
id: "output.validateJson",
|
|
@@ -1363,8 +1264,8 @@ async function createDoctorData(options) {
|
|
|
1363
1264
|
details: { path: toRelativePath(root, validateJsonAbs) }
|
|
1364
1265
|
});
|
|
1365
1266
|
const outDirAbs = resolvePath(root, config, "outDir");
|
|
1366
|
-
const rel =
|
|
1367
|
-
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);
|
|
1368
1269
|
addCheck(checks, {
|
|
1369
1270
|
id: "output.pathAlignment",
|
|
1370
1271
|
severity: inside ? "ok" : "warning",
|
|
@@ -1487,12 +1388,12 @@ async function detectOutDirCollisions(root) {
|
|
|
1487
1388
|
});
|
|
1488
1389
|
const configPaths = configScan.files;
|
|
1489
1390
|
const configRoots = Array.from(
|
|
1490
|
-
new Set(configPaths.map((configPath) =>
|
|
1391
|
+
new Set(configPaths.map((configPath) => import_node_path10.default.dirname(configPath)))
|
|
1491
1392
|
).sort((a, b) => a.localeCompare(b));
|
|
1492
1393
|
const outDirToRoots = /* @__PURE__ */ new Map();
|
|
1493
1394
|
for (const configRoot of configRoots) {
|
|
1494
1395
|
const { config } = await loadConfig(configRoot);
|
|
1495
|
-
const outDir =
|
|
1396
|
+
const outDir = import_node_path10.default.normalize(resolvePath(configRoot, config, "outDir"));
|
|
1496
1397
|
const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
|
|
1497
1398
|
roots.add(configRoot);
|
|
1498
1399
|
outDirToRoots.set(outDir, roots);
|
|
@@ -1518,20 +1419,20 @@ async function detectOutDirCollisions(root) {
|
|
|
1518
1419
|
};
|
|
1519
1420
|
}
|
|
1520
1421
|
async function findMonorepoRoot(startDir) {
|
|
1521
|
-
let current =
|
|
1422
|
+
let current = import_node_path10.default.resolve(startDir);
|
|
1522
1423
|
while (true) {
|
|
1523
|
-
const gitPath =
|
|
1524
|
-
const workspacePath =
|
|
1424
|
+
const gitPath = import_node_path10.default.join(current, ".git");
|
|
1425
|
+
const workspacePath = import_node_path10.default.join(current, "pnpm-workspace.yaml");
|
|
1525
1426
|
if (await exists4(gitPath) || await exists4(workspacePath)) {
|
|
1526
1427
|
return current;
|
|
1527
1428
|
}
|
|
1528
|
-
const parent =
|
|
1429
|
+
const parent = import_node_path10.default.dirname(current);
|
|
1529
1430
|
if (parent === current) {
|
|
1530
1431
|
break;
|
|
1531
1432
|
}
|
|
1532
1433
|
current = parent;
|
|
1533
1434
|
}
|
|
1534
|
-
return
|
|
1435
|
+
return import_node_path10.default.resolve(startDir);
|
|
1535
1436
|
}
|
|
1536
1437
|
|
|
1537
1438
|
// src/cli/lib/logger.ts
|
|
@@ -1573,9 +1474,9 @@ async function runDoctor(options) {
|
|
|
1573
1474
|
const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
|
|
1574
1475
|
const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
|
|
1575
1476
|
if (options.outPath) {
|
|
1576
|
-
const outAbs =
|
|
1577
|
-
await (0,
|
|
1578
|
-
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}
|
|
1579
1480
|
`, "utf-8");
|
|
1580
1481
|
info(`doctor: wrote ${outAbs}`);
|
|
1581
1482
|
return exitCode;
|
|
@@ -1594,11 +1495,11 @@ function shouldFailDoctor(summary, failOn) {
|
|
|
1594
1495
|
}
|
|
1595
1496
|
|
|
1596
1497
|
// src/cli/commands/init.ts
|
|
1597
|
-
var
|
|
1498
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
1598
1499
|
|
|
1599
1500
|
// src/cli/lib/fs.ts
|
|
1600
|
-
var
|
|
1601
|
-
var
|
|
1501
|
+
var import_promises10 = require("fs/promises");
|
|
1502
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
1602
1503
|
async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
1603
1504
|
const files = await collectTemplateFiles(sourceRoot);
|
|
1604
1505
|
return copyFiles(files, sourceRoot, destRoot, options);
|
|
@@ -1606,7 +1507,7 @@ async function copyTemplateTree(sourceRoot, destRoot, options) {
|
|
|
1606
1507
|
async function copyTemplatePaths(sourceRoot, destRoot, relativePaths, options) {
|
|
1607
1508
|
const allFiles = [];
|
|
1608
1509
|
for (const relPath of relativePaths) {
|
|
1609
|
-
const fullPath =
|
|
1510
|
+
const fullPath = import_node_path12.default.join(sourceRoot, relPath);
|
|
1610
1511
|
const files = await collectTemplateFiles(fullPath);
|
|
1611
1512
|
allFiles.push(...files);
|
|
1612
1513
|
}
|
|
@@ -1616,13 +1517,13 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1616
1517
|
const copied = [];
|
|
1617
1518
|
const skipped = [];
|
|
1618
1519
|
const conflicts = [];
|
|
1619
|
-
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p +
|
|
1620
|
-
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);
|
|
1621
1522
|
const isProtectedRelative = (relative) => {
|
|
1622
1523
|
if (protectPrefixes.length === 0) {
|
|
1623
1524
|
return false;
|
|
1624
1525
|
}
|
|
1625
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
1526
|
+
const normalized = relative.replace(/[\\/]+/g, import_node_path12.default.sep);
|
|
1626
1527
|
return protectPrefixes.some(
|
|
1627
1528
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1628
1529
|
);
|
|
@@ -1631,7 +1532,7 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1631
1532
|
if (excludePrefixes.length === 0) {
|
|
1632
1533
|
return false;
|
|
1633
1534
|
}
|
|
1634
|
-
const normalized = relative.replace(/[\\/]+/g,
|
|
1535
|
+
const normalized = relative.replace(/[\\/]+/g, import_node_path12.default.sep);
|
|
1635
1536
|
return excludePrefixes.some(
|
|
1636
1537
|
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1637
1538
|
);
|
|
@@ -1639,14 +1540,14 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1639
1540
|
const conflictPolicy = options.conflictPolicy ?? "error";
|
|
1640
1541
|
if (!options.force && conflictPolicy === "error") {
|
|
1641
1542
|
for (const file of files) {
|
|
1642
|
-
const relative =
|
|
1543
|
+
const relative = import_node_path12.default.relative(sourceRoot, file);
|
|
1643
1544
|
if (isExcludedRelative(relative)) {
|
|
1644
1545
|
continue;
|
|
1645
1546
|
}
|
|
1646
1547
|
if (isProtectedRelative(relative)) {
|
|
1647
1548
|
continue;
|
|
1648
1549
|
}
|
|
1649
|
-
const dest =
|
|
1550
|
+
const dest = import_node_path12.default.join(destRoot, relative);
|
|
1650
1551
|
if (!await shouldWrite(dest, options.force)) {
|
|
1651
1552
|
conflicts.push(dest);
|
|
1652
1553
|
}
|
|
@@ -1656,19 +1557,19 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1656
1557
|
}
|
|
1657
1558
|
}
|
|
1658
1559
|
for (const file of files) {
|
|
1659
|
-
const relative =
|
|
1560
|
+
const relative = import_node_path12.default.relative(sourceRoot, file);
|
|
1660
1561
|
if (isExcludedRelative(relative)) {
|
|
1661
1562
|
continue;
|
|
1662
1563
|
}
|
|
1663
|
-
const dest =
|
|
1564
|
+
const dest = import_node_path12.default.join(destRoot, relative);
|
|
1664
1565
|
const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
|
|
1665
1566
|
if (!await shouldWrite(dest, forceForThisFile)) {
|
|
1666
1567
|
skipped.push(dest);
|
|
1667
1568
|
continue;
|
|
1668
1569
|
}
|
|
1669
1570
|
if (!options.dryRun) {
|
|
1670
|
-
await (0,
|
|
1671
|
-
await (0,
|
|
1571
|
+
await (0, import_promises10.mkdir)(import_node_path12.default.dirname(dest), { recursive: true });
|
|
1572
|
+
await (0, import_promises10.copyFile)(file, dest);
|
|
1672
1573
|
}
|
|
1673
1574
|
copied.push(dest);
|
|
1674
1575
|
}
|
|
@@ -1689,9 +1590,9 @@ async function collectTemplateFiles(root) {
|
|
|
1689
1590
|
if (!await exists5(root)) {
|
|
1690
1591
|
return entries;
|
|
1691
1592
|
}
|
|
1692
|
-
const items = await (0,
|
|
1593
|
+
const items = await (0, import_promises10.readdir)(root, { withFileTypes: true });
|
|
1693
1594
|
for (const item of items) {
|
|
1694
|
-
const fullPath =
|
|
1595
|
+
const fullPath = import_node_path12.default.join(root, item.name);
|
|
1695
1596
|
if (item.isDirectory()) {
|
|
1696
1597
|
const nested = await collectTemplateFiles(fullPath);
|
|
1697
1598
|
entries.push(...nested);
|
|
@@ -1711,7 +1612,7 @@ async function shouldWrite(target, force) {
|
|
|
1711
1612
|
}
|
|
1712
1613
|
async function exists5(target) {
|
|
1713
1614
|
try {
|
|
1714
|
-
await (0,
|
|
1615
|
+
await (0, import_promises10.access)(target);
|
|
1715
1616
|
return true;
|
|
1716
1617
|
} catch {
|
|
1717
1618
|
return false;
|
|
@@ -1721,13 +1622,13 @@ async function exists5(target) {
|
|
|
1721
1622
|
// src/cli/commands/init.ts
|
|
1722
1623
|
async function runInit(options) {
|
|
1723
1624
|
const assetsRoot = getInitAssetsDir();
|
|
1724
|
-
const rootAssets =
|
|
1725
|
-
const qfaiAssets =
|
|
1726
|
-
const destRoot =
|
|
1727
|
-
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");
|
|
1728
1629
|
if (options.force) {
|
|
1729
1630
|
info(
|
|
1730
|
-
"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"
|
|
1731
1632
|
);
|
|
1732
1633
|
}
|
|
1733
1634
|
const rootResult = await copyTemplateTree(rootAssets, destRoot, {
|
|
@@ -1739,18 +1640,18 @@ async function runInit(options) {
|
|
|
1739
1640
|
force: false,
|
|
1740
1641
|
dryRun: options.dryRun,
|
|
1741
1642
|
conflictPolicy: "skip",
|
|
1742
|
-
protect: ["prompts.local"],
|
|
1743
|
-
exclude: ["prompts"]
|
|
1643
|
+
protect: ["assistant/prompts.local"],
|
|
1644
|
+
exclude: ["assistant/prompts"]
|
|
1744
1645
|
});
|
|
1745
1646
|
const promptsResult = await copyTemplatePaths(
|
|
1746
1647
|
qfaiAssets,
|
|
1747
1648
|
destQfai,
|
|
1748
|
-
["prompts"],
|
|
1649
|
+
["assistant/prompts"],
|
|
1749
1650
|
{
|
|
1750
1651
|
force: options.force,
|
|
1751
1652
|
dryRun: options.dryRun,
|
|
1752
1653
|
conflictPolicy: "skip",
|
|
1753
|
-
protect: ["prompts.local"]
|
|
1654
|
+
protect: ["assistant/prompts.local"]
|
|
1754
1655
|
}
|
|
1755
1656
|
);
|
|
1756
1657
|
report(
|
|
@@ -1771,8 +1672,8 @@ function report(copied, skipped, dryRun, label) {
|
|
|
1771
1672
|
}
|
|
1772
1673
|
|
|
1773
1674
|
// src/cli/commands/report.ts
|
|
1774
|
-
var
|
|
1775
|
-
var
|
|
1675
|
+
var import_promises19 = require("fs/promises");
|
|
1676
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
1776
1677
|
|
|
1777
1678
|
// src/core/normalize.ts
|
|
1778
1679
|
function normalizeIssuePaths(root, issues) {
|
|
@@ -1812,12 +1713,12 @@ function normalizeValidationResult(root, result) {
|
|
|
1812
1713
|
}
|
|
1813
1714
|
|
|
1814
1715
|
// src/core/report.ts
|
|
1815
|
-
var
|
|
1816
|
-
var
|
|
1716
|
+
var import_promises18 = require("fs/promises");
|
|
1717
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
1817
1718
|
|
|
1818
1719
|
// src/core/contractIndex.ts
|
|
1819
|
-
var
|
|
1820
|
-
var
|
|
1720
|
+
var import_promises11 = require("fs/promises");
|
|
1721
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
1821
1722
|
|
|
1822
1723
|
// src/core/contractsDecl.ts
|
|
1823
1724
|
var CONTRACT_DECLARATION_RE = /^\s*(?:#|\/\/|--|\/\*+|\*+)?\s*QFAI-CONTRACT-ID:\s*((?:API|UI|DB)-\d{4}|THEMA-\d{3})\s*(?:\*\/)?\s*$/gm;
|
|
@@ -1839,9 +1740,9 @@ function stripContractDeclarationLines(text) {
|
|
|
1839
1740
|
// src/core/contractIndex.ts
|
|
1840
1741
|
async function buildContractIndex(root, config) {
|
|
1841
1742
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
1842
|
-
const uiRoot =
|
|
1843
|
-
const apiRoot =
|
|
1844
|
-
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");
|
|
1845
1746
|
const [uiFiles, themaFiles, apiFiles, dbFiles] = await Promise.all([
|
|
1846
1747
|
collectUiContractFiles(uiRoot),
|
|
1847
1748
|
collectThemaContractFiles(uiRoot),
|
|
@@ -1861,7 +1762,7 @@ async function buildContractIndex(root, config) {
|
|
|
1861
1762
|
}
|
|
1862
1763
|
async function indexContractFiles(files, index) {
|
|
1863
1764
|
for (const file of files) {
|
|
1864
|
-
const text = await (0,
|
|
1765
|
+
const text = await (0, import_promises11.readFile)(file, "utf-8");
|
|
1865
1766
|
extractDeclaredContractIds(text).forEach((id) => record(index, id, file));
|
|
1866
1767
|
}
|
|
1867
1768
|
}
|
|
@@ -2106,14 +2007,14 @@ function parseSpec(md, file) {
|
|
|
2106
2007
|
}
|
|
2107
2008
|
|
|
2108
2009
|
// src/core/validators/contracts.ts
|
|
2109
|
-
var
|
|
2110
|
-
var
|
|
2010
|
+
var import_promises12 = require("fs/promises");
|
|
2011
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2111
2012
|
|
|
2112
2013
|
// src/core/contracts.ts
|
|
2113
|
-
var
|
|
2014
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2114
2015
|
var import_yaml2 = require("yaml");
|
|
2115
2016
|
function parseStructuredContract(file, text) {
|
|
2116
|
-
const ext =
|
|
2017
|
+
const ext = import_node_path15.default.extname(file).toLowerCase();
|
|
2117
2018
|
if (ext === ".json") {
|
|
2118
2019
|
return JSON.parse(text);
|
|
2119
2020
|
}
|
|
@@ -2135,14 +2036,14 @@ async function validateContracts(root, config) {
|
|
|
2135
2036
|
const issues = [];
|
|
2136
2037
|
const contractIndex = await buildContractIndex(root, config);
|
|
2137
2038
|
const contractsRoot = resolvePath(root, config, "contractsDir");
|
|
2138
|
-
const uiRoot =
|
|
2039
|
+
const uiRoot = import_node_path16.default.join(contractsRoot, "ui");
|
|
2139
2040
|
const themaIds = new Set(
|
|
2140
2041
|
Array.from(contractIndex.ids).filter((id) => id.startsWith("THEMA-"))
|
|
2141
2042
|
);
|
|
2142
2043
|
issues.push(...await validateUiContracts(uiRoot, themaIds));
|
|
2143
2044
|
issues.push(...await validateThemaContracts(uiRoot));
|
|
2144
|
-
issues.push(...await validateApiContracts(
|
|
2145
|
-
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")));
|
|
2146
2047
|
issues.push(...validateDuplicateContractIds(contractIndex));
|
|
2147
2048
|
return issues;
|
|
2148
2049
|
}
|
|
@@ -2161,7 +2062,7 @@ async function validateUiContracts(uiRoot, themaIds) {
|
|
|
2161
2062
|
}
|
|
2162
2063
|
const issues = [];
|
|
2163
2064
|
for (const file of files) {
|
|
2164
|
-
const text = await (0,
|
|
2065
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2165
2066
|
const declaredIds = extractDeclaredContractIds(text);
|
|
2166
2067
|
issues.push(...validateDeclaredContractIds(declaredIds, file, "UI"));
|
|
2167
2068
|
let doc = null;
|
|
@@ -2215,7 +2116,7 @@ async function validateThemaContracts(uiRoot) {
|
|
|
2215
2116
|
}
|
|
2216
2117
|
const issues = [];
|
|
2217
2118
|
for (const file of files) {
|
|
2218
|
-
const text = await (0,
|
|
2119
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2219
2120
|
const invalidIds = extractInvalidIds(text, [
|
|
2220
2121
|
"SPEC",
|
|
2221
2122
|
"BR",
|
|
@@ -2349,7 +2250,7 @@ async function validateApiContracts(apiRoot) {
|
|
|
2349
2250
|
}
|
|
2350
2251
|
const issues = [];
|
|
2351
2252
|
for (const file of files) {
|
|
2352
|
-
const text = await (0,
|
|
2253
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2353
2254
|
const invalidIds = extractInvalidIds(text, [
|
|
2354
2255
|
"SPEC",
|
|
2355
2256
|
"BR",
|
|
@@ -2418,7 +2319,7 @@ async function validateDbContracts(dbRoot) {
|
|
|
2418
2319
|
}
|
|
2419
2320
|
const issues = [];
|
|
2420
2321
|
for (const file of files) {
|
|
2421
|
-
const text = await (0,
|
|
2322
|
+
const text = await (0, import_promises12.readFile)(file, "utf-8");
|
|
2422
2323
|
const invalidIds = extractInvalidIds(text, [
|
|
2423
2324
|
"SPEC",
|
|
2424
2325
|
"BR",
|
|
@@ -2615,9 +2516,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2615
2516
|
);
|
|
2616
2517
|
return issues;
|
|
2617
2518
|
}
|
|
2618
|
-
const packDir =
|
|
2619
|
-
const packRelative =
|
|
2620
|
-
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)) {
|
|
2621
2522
|
issues.push(
|
|
2622
2523
|
issue(
|
|
2623
2524
|
"QFAI-ASSET-001",
|
|
@@ -2643,7 +2544,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2643
2544
|
);
|
|
2644
2545
|
return issues;
|
|
2645
2546
|
}
|
|
2646
|
-
const assetsYamlPath =
|
|
2547
|
+
const assetsYamlPath = import_node_path16.default.join(packDir, "assets.yaml");
|
|
2647
2548
|
if (!await exists6(assetsYamlPath)) {
|
|
2648
2549
|
issues.push(
|
|
2649
2550
|
issue(
|
|
@@ -2658,7 +2559,7 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2658
2559
|
}
|
|
2659
2560
|
let manifest;
|
|
2660
2561
|
try {
|
|
2661
|
-
const manifestText = await (0,
|
|
2562
|
+
const manifestText = await (0, import_promises12.readFile)(assetsYamlPath, "utf-8");
|
|
2662
2563
|
manifest = parseStructuredContract(assetsYamlPath, manifestText);
|
|
2663
2564
|
} catch (error2) {
|
|
2664
2565
|
issues.push(
|
|
@@ -2731,9 +2632,9 @@ async function validateUiAssets(assets, file, uiRoot) {
|
|
|
2731
2632
|
);
|
|
2732
2633
|
continue;
|
|
2733
2634
|
}
|
|
2734
|
-
const assetPath =
|
|
2735
|
-
const assetRelative =
|
|
2736
|
-
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)) {
|
|
2737
2638
|
issues.push(
|
|
2738
2639
|
issue(
|
|
2739
2640
|
"QFAI-ASSET-004",
|
|
@@ -2774,7 +2675,7 @@ function shouldIgnoreInvalidId(value, doc) {
|
|
|
2774
2675
|
return false;
|
|
2775
2676
|
}
|
|
2776
2677
|
const normalized = packValue.replace(/\\/g, "/");
|
|
2777
|
-
const basename =
|
|
2678
|
+
const basename = import_node_path16.default.posix.basename(normalized);
|
|
2778
2679
|
if (!basename) {
|
|
2779
2680
|
return false;
|
|
2780
2681
|
}
|
|
@@ -2784,7 +2685,7 @@ function isSafeRelativePath(value) {
|
|
|
2784
2685
|
if (!value) {
|
|
2785
2686
|
return false;
|
|
2786
2687
|
}
|
|
2787
|
-
if (
|
|
2688
|
+
if (import_node_path16.default.isAbsolute(value)) {
|
|
2788
2689
|
return false;
|
|
2789
2690
|
}
|
|
2790
2691
|
const normalized = value.replace(/\\/g, "/");
|
|
@@ -2799,7 +2700,7 @@ function isSafeRelativePath(value) {
|
|
|
2799
2700
|
}
|
|
2800
2701
|
async function exists6(target) {
|
|
2801
2702
|
try {
|
|
2802
|
-
await (0,
|
|
2703
|
+
await (0, import_promises12.access)(target);
|
|
2803
2704
|
return true;
|
|
2804
2705
|
} catch {
|
|
2805
2706
|
return false;
|
|
@@ -2834,13 +2735,8 @@ function issue(code, message, severity, file, rule, refs, category = "compatibil
|
|
|
2834
2735
|
}
|
|
2835
2736
|
|
|
2836
2737
|
// src/core/validators/delta.ts
|
|
2837
|
-
var
|
|
2838
|
-
var
|
|
2839
|
-
var SECTION_RE = /^##\s+変更区分/m;
|
|
2840
|
-
var COMPAT_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Compatibility\b/m;
|
|
2841
|
-
var CHANGE_LINE_RE = /^\s*-\s*\[[ xX]\]\s*Change\/Improvement\b/m;
|
|
2842
|
-
var COMPAT_CHECKED_RE = /^\s*-\s*\[[xX]\]\s*Compatibility\b/m;
|
|
2843
|
-
var CHANGE_CHECKED_RE = /^\s*-\s*\[[xX]\]\s*Change\/Improvement\b/m;
|
|
2738
|
+
var import_promises13 = require("fs/promises");
|
|
2739
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
2844
2740
|
async function validateDeltas(root, config) {
|
|
2845
2741
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
2846
2742
|
const packs = await collectSpecPackDirs(specsRoot);
|
|
@@ -2849,10 +2745,9 @@ async function validateDeltas(root, config) {
|
|
|
2849
2745
|
}
|
|
2850
2746
|
const issues = [];
|
|
2851
2747
|
for (const pack of packs) {
|
|
2852
|
-
const deltaPath =
|
|
2853
|
-
let text;
|
|
2748
|
+
const deltaPath = import_node_path17.default.join(pack, "delta.md");
|
|
2854
2749
|
try {
|
|
2855
|
-
|
|
2750
|
+
await (0, import_promises13.readFile)(deltaPath, "utf-8");
|
|
2856
2751
|
} catch (error2) {
|
|
2857
2752
|
if (isMissingFileError2(error2)) {
|
|
2858
2753
|
issues.push(
|
|
@@ -2861,41 +2756,16 @@ async function validateDeltas(root, config) {
|
|
|
2861
2756
|
"delta.md \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002",
|
|
2862
2757
|
"error",
|
|
2863
2758
|
deltaPath,
|
|
2864
|
-
"delta.exists"
|
|
2759
|
+
"delta.exists",
|
|
2760
|
+
void 0,
|
|
2761
|
+
"change",
|
|
2762
|
+
"spec-xxxx/delta.md \u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30C6\u30F3\u30D7\u30EC\u306F init \u751F\u6210\u7269\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\uFF09\u3002"
|
|
2865
2763
|
)
|
|
2866
2764
|
);
|
|
2867
2765
|
continue;
|
|
2868
2766
|
}
|
|
2869
2767
|
throw error2;
|
|
2870
2768
|
}
|
|
2871
|
-
const hasSection = SECTION_RE.test(text);
|
|
2872
|
-
const hasCompatibility = COMPAT_LINE_RE.test(text);
|
|
2873
|
-
const hasChange = CHANGE_LINE_RE.test(text);
|
|
2874
|
-
if (!hasSection || !hasCompatibility || !hasChange) {
|
|
2875
|
-
issues.push(
|
|
2876
|
-
issue2(
|
|
2877
|
-
"QFAI-DELTA-002",
|
|
2878
|
-
"delta.md \u306E\u5909\u66F4\u533A\u5206\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059\u3002`## \u5909\u66F4\u533A\u5206` \u3068\u30C1\u30A7\u30C3\u30AF\u30DC\u30C3\u30AF\u30B9\uFF08Compatibility / Change/Improvement\uFF09\u3092\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
2879
|
-
"error",
|
|
2880
|
-
deltaPath,
|
|
2881
|
-
"delta.section"
|
|
2882
|
-
)
|
|
2883
|
-
);
|
|
2884
|
-
continue;
|
|
2885
|
-
}
|
|
2886
|
-
const compatibilityChecked = COMPAT_CHECKED_RE.test(text);
|
|
2887
|
-
const changeChecked = CHANGE_CHECKED_RE.test(text);
|
|
2888
|
-
if (compatibilityChecked === changeChecked) {
|
|
2889
|
-
issues.push(
|
|
2890
|
-
issue2(
|
|
2891
|
-
"QFAI-DELTA-003",
|
|
2892
|
-
"delta.md \u306E\u5909\u66F4\u533A\u5206\u306F\u3069\u3061\u3089\u304B1\u3064\u3060\u3051\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4E21\u65B9ON/\u4E21\u65B9OFF\u306F\u7121\u52B9\u3067\u3059\uFF09\u3002",
|
|
2893
|
-
"error",
|
|
2894
|
-
deltaPath,
|
|
2895
|
-
"delta.classification"
|
|
2896
|
-
)
|
|
2897
|
-
);
|
|
2898
|
-
}
|
|
2899
2769
|
}
|
|
2900
2770
|
return issues;
|
|
2901
2771
|
}
|
|
@@ -2928,8 +2798,8 @@ function issue2(code, message, severity, file, rule, refs, category = "change",
|
|
|
2928
2798
|
}
|
|
2929
2799
|
|
|
2930
2800
|
// src/core/validators/ids.ts
|
|
2931
|
-
var
|
|
2932
|
-
var
|
|
2801
|
+
var import_promises14 = require("fs/promises");
|
|
2802
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2933
2803
|
var SC_TAG_RE3 = /^SC-\d{4}$/;
|
|
2934
2804
|
async function validateDefinedIds(root, config) {
|
|
2935
2805
|
const issues = [];
|
|
@@ -2964,7 +2834,7 @@ async function validateDefinedIds(root, config) {
|
|
|
2964
2834
|
}
|
|
2965
2835
|
async function collectSpecDefinitionIds(files, out) {
|
|
2966
2836
|
for (const file of files) {
|
|
2967
|
-
const text = await (0,
|
|
2837
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2968
2838
|
const parsed = parseSpec(text, file);
|
|
2969
2839
|
if (parsed.specId) {
|
|
2970
2840
|
recordId(out, parsed.specId, file);
|
|
@@ -2974,7 +2844,7 @@ async function collectSpecDefinitionIds(files, out) {
|
|
|
2974
2844
|
}
|
|
2975
2845
|
async function collectScenarioDefinitionIds(files, out) {
|
|
2976
2846
|
for (const file of files) {
|
|
2977
|
-
const text = await (0,
|
|
2847
|
+
const text = await (0, import_promises14.readFile)(file, "utf-8");
|
|
2978
2848
|
const { document, errors } = parseScenarioDocument(text, file);
|
|
2979
2849
|
if (!document || errors.length > 0) {
|
|
2980
2850
|
continue;
|
|
@@ -2995,7 +2865,7 @@ function recordId(out, id, file) {
|
|
|
2995
2865
|
}
|
|
2996
2866
|
function formatFileList(files, root) {
|
|
2997
2867
|
return files.map((file) => {
|
|
2998
|
-
const relative =
|
|
2868
|
+
const relative = import_node_path18.default.relative(root, file);
|
|
2999
2869
|
return relative.length > 0 ? relative : file;
|
|
3000
2870
|
}).join(", ");
|
|
3001
2871
|
}
|
|
@@ -3022,8 +2892,8 @@ function issue3(code, message, severity, file, rule, refs, category = "compatibi
|
|
|
3022
2892
|
}
|
|
3023
2893
|
|
|
3024
2894
|
// src/core/validators/promptsIntegrity.ts
|
|
3025
|
-
async function validatePromptsIntegrity(root) {
|
|
3026
|
-
const diff = await diffProjectPromptsAgainstInitAssets(root);
|
|
2895
|
+
async function validatePromptsIntegrity(root, config) {
|
|
2896
|
+
const diff = await diffProjectPromptsAgainstInitAssets(root, config);
|
|
3027
2897
|
if (diff.status !== "modified") {
|
|
3028
2898
|
return [];
|
|
3029
2899
|
}
|
|
@@ -3040,11 +2910,11 @@ async function validatePromptsIntegrity(root) {
|
|
|
3040
2910
|
code: "QFAI-PROMPTS-001",
|
|
3041
2911
|
severity: "error",
|
|
3042
2912
|
category: "change",
|
|
3043
|
-
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}`,
|
|
3044
2914
|
suggested_action: [
|
|
3045
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",
|
|
3046
2916
|
"\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u5B9F\u65BD\u3057\u3066\u304F\u3060\u3055\u3044:",
|
|
3047
|
-
"- \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",
|
|
3048
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"
|
|
3049
2919
|
].join("\n"),
|
|
3050
2920
|
rule: "prompts.integrity"
|
|
@@ -3053,8 +2923,8 @@ async function validatePromptsIntegrity(root) {
|
|
|
3053
2923
|
}
|
|
3054
2924
|
|
|
3055
2925
|
// src/core/validators/scenario.ts
|
|
3056
|
-
var
|
|
3057
|
-
var
|
|
2926
|
+
var import_promises15 = require("fs/promises");
|
|
2927
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3058
2928
|
var GIVEN_PATTERN = /\bGiven\b/;
|
|
3059
2929
|
var WHEN_PATTERN = /\bWhen\b/;
|
|
3060
2930
|
var THEN_PATTERN = /\bThen\b/;
|
|
@@ -3077,7 +2947,7 @@ async function validateScenarios(root, config) {
|
|
|
3077
2947
|
}
|
|
3078
2948
|
const issues = [];
|
|
3079
2949
|
for (const entry of entries) {
|
|
3080
|
-
const legacyScenarioPath =
|
|
2950
|
+
const legacyScenarioPath = import_node_path19.default.join(entry.dir, "scenario.md");
|
|
3081
2951
|
if (await fileExists(legacyScenarioPath)) {
|
|
3082
2952
|
issues.push(
|
|
3083
2953
|
issue4(
|
|
@@ -3091,7 +2961,7 @@ async function validateScenarios(root, config) {
|
|
|
3091
2961
|
}
|
|
3092
2962
|
let text;
|
|
3093
2963
|
try {
|
|
3094
|
-
text = await (0,
|
|
2964
|
+
text = await (0, import_promises15.readFile)(entry.scenarioPath, "utf-8");
|
|
3095
2965
|
} catch (error2) {
|
|
3096
2966
|
if (isMissingFileError3(error2)) {
|
|
3097
2967
|
issues.push(
|
|
@@ -3266,7 +3136,7 @@ function isMissingFileError3(error2) {
|
|
|
3266
3136
|
}
|
|
3267
3137
|
async function fileExists(target) {
|
|
3268
3138
|
try {
|
|
3269
|
-
await (0,
|
|
3139
|
+
await (0, import_promises15.access)(target);
|
|
3270
3140
|
return true;
|
|
3271
3141
|
} catch {
|
|
3272
3142
|
return false;
|
|
@@ -3274,7 +3144,7 @@ async function fileExists(target) {
|
|
|
3274
3144
|
}
|
|
3275
3145
|
|
|
3276
3146
|
// src/core/validators/spec.ts
|
|
3277
|
-
var
|
|
3147
|
+
var import_promises16 = require("fs/promises");
|
|
3278
3148
|
async function validateSpecs(root, config) {
|
|
3279
3149
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
3280
3150
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -3295,7 +3165,7 @@ async function validateSpecs(root, config) {
|
|
|
3295
3165
|
for (const entry of entries) {
|
|
3296
3166
|
let text;
|
|
3297
3167
|
try {
|
|
3298
|
-
text = await (0,
|
|
3168
|
+
text = await (0, import_promises16.readFile)(entry.specPath, "utf-8");
|
|
3299
3169
|
} catch (error2) {
|
|
3300
3170
|
if (isMissingFileError4(error2)) {
|
|
3301
3171
|
issues.push(
|
|
@@ -3449,7 +3319,7 @@ function isMissingFileError4(error2) {
|
|
|
3449
3319
|
}
|
|
3450
3320
|
|
|
3451
3321
|
// src/core/validators/traceability.ts
|
|
3452
|
-
var
|
|
3322
|
+
var import_promises17 = require("fs/promises");
|
|
3453
3323
|
var SPEC_TAG_RE3 = /^SPEC-\d{4}$/;
|
|
3454
3324
|
var BR_TAG_RE2 = /^BR-\d{4}$/;
|
|
3455
3325
|
async function validateTraceability(root, config) {
|
|
@@ -3469,7 +3339,7 @@ async function validateTraceability(root, config) {
|
|
|
3469
3339
|
const contractIndex = await buildContractIndex(root, config);
|
|
3470
3340
|
const contractIds = contractIndex.ids;
|
|
3471
3341
|
for (const file of specFiles) {
|
|
3472
|
-
const text = await (0,
|
|
3342
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
3473
3343
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3474
3344
|
const parsed = parseSpec(text, file);
|
|
3475
3345
|
if (parsed.specId) {
|
|
@@ -3542,7 +3412,7 @@ async function validateTraceability(root, config) {
|
|
|
3542
3412
|
}
|
|
3543
3413
|
}
|
|
3544
3414
|
for (const file of scenarioFiles) {
|
|
3545
|
-
const text = await (0,
|
|
3415
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
3546
3416
|
extractAllIds(text).forEach((id) => upstreamIds.add(id));
|
|
3547
3417
|
const scenarioContractRefs = parseContractRefs(text, {
|
|
3548
3418
|
allowCommentPrefix: true
|
|
@@ -3864,7 +3734,7 @@ async function validateCodeReferences(upstreamIds, srcRoot, testsRoot) {
|
|
|
3864
3734
|
const pattern = buildIdPattern(Array.from(upstreamIds));
|
|
3865
3735
|
let found = false;
|
|
3866
3736
|
for (const file of targetFiles) {
|
|
3867
|
-
const text = await (0,
|
|
3737
|
+
const text = await (0, import_promises17.readFile)(file, "utf-8");
|
|
3868
3738
|
if (pattern.test(text)) {
|
|
3869
3739
|
found = true;
|
|
3870
3740
|
break;
|
|
@@ -3915,7 +3785,7 @@ async function validateProject(root, configResult) {
|
|
|
3915
3785
|
const { config, issues: configIssues } = resolved;
|
|
3916
3786
|
const issues = [
|
|
3917
3787
|
...configIssues,
|
|
3918
|
-
...await validatePromptsIntegrity(root),
|
|
3788
|
+
...await validatePromptsIntegrity(root, config),
|
|
3919
3789
|
...await validateSpecs(root, config),
|
|
3920
3790
|
...await validateDeltas(root, config),
|
|
3921
3791
|
...await validateScenarios(root, config),
|
|
@@ -3964,15 +3834,15 @@ var ID_PREFIXES2 = [
|
|
|
3964
3834
|
"THEMA"
|
|
3965
3835
|
];
|
|
3966
3836
|
async function createReportData(root, validation, configResult) {
|
|
3967
|
-
const resolvedRoot =
|
|
3837
|
+
const resolvedRoot = import_node_path20.default.resolve(root);
|
|
3968
3838
|
const resolved = configResult ?? await loadConfig(resolvedRoot);
|
|
3969
3839
|
const config = resolved.config;
|
|
3970
3840
|
const configPath = resolved.configPath;
|
|
3971
3841
|
const specsRoot = resolvePath(resolvedRoot, config, "specsDir");
|
|
3972
3842
|
const contractsRoot = resolvePath(resolvedRoot, config, "contractsDir");
|
|
3973
|
-
const apiRoot =
|
|
3974
|
-
const uiRoot =
|
|
3975
|
-
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");
|
|
3976
3846
|
const srcRoot = resolvePath(resolvedRoot, config, "srcDir");
|
|
3977
3847
|
const testsRoot = resolvePath(resolvedRoot, config, "testsDir");
|
|
3978
3848
|
const specFiles = await collectSpecFiles(specsRoot);
|
|
@@ -4437,11 +4307,9 @@ function formatReportMarkdown(data, options = {}) {
|
|
|
4437
4307
|
"- issue \u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u904B\u7528\u30C6\u30F3\u30D7\u30EC\u306B\u6CBF\u3063\u3066\u7D99\u7D9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
4438
4308
|
);
|
|
4439
4309
|
}
|
|
4310
|
+
lines.push("- \u5909\u66F4\u5185\u5BB9\u30FB\u53D7\u5165\u89B3\u70B9\u306F `.qfai/specs/*/delta.md` \u306B\u8A18\u9332\u3057\u307E\u3059\u3002");
|
|
4440
4311
|
lines.push(
|
|
4441
|
-
"- \
|
|
4442
|
-
);
|
|
4443
|
-
lines.push(
|
|
4444
|
-
"- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/promptpack/steering/traceability.md` / `.qfai/promptpack/steering/compatibility-vs-change.md`"
|
|
4312
|
+
"- \u53C2\u7167\u30EB\u30FC\u30EB\u306E\u6B63\u672C: `.qfai/assistant/instructions/constitution.md`"
|
|
4445
4313
|
);
|
|
4446
4314
|
return lines.join("\n");
|
|
4447
4315
|
}
|
|
@@ -4456,7 +4324,7 @@ async function collectSpecContractRefs(specFiles, contractIdList) {
|
|
|
4456
4324
|
idToSpecs.set(contractId, /* @__PURE__ */ new Set());
|
|
4457
4325
|
}
|
|
4458
4326
|
for (const file of specFiles) {
|
|
4459
|
-
const text = await (0,
|
|
4327
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4460
4328
|
const parsed = parseSpec(text, file);
|
|
4461
4329
|
const specKey = parsed.specId;
|
|
4462
4330
|
if (!specKey) {
|
|
@@ -4498,7 +4366,7 @@ async function collectIds(files) {
|
|
|
4498
4366
|
THEMA: /* @__PURE__ */ new Set()
|
|
4499
4367
|
};
|
|
4500
4368
|
for (const file of files) {
|
|
4501
|
-
const text = await (0,
|
|
4369
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4502
4370
|
for (const prefix of ID_PREFIXES2) {
|
|
4503
4371
|
const ids = extractIds(text, prefix);
|
|
4504
4372
|
ids.forEach((id) => result[prefix].add(id));
|
|
@@ -4517,7 +4385,7 @@ async function collectIds(files) {
|
|
|
4517
4385
|
async function collectUpstreamIds(files) {
|
|
4518
4386
|
const ids = /* @__PURE__ */ new Set();
|
|
4519
4387
|
for (const file of files) {
|
|
4520
|
-
const text = await (0,
|
|
4388
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4521
4389
|
extractAllIds(text).forEach((id) => ids.add(id));
|
|
4522
4390
|
}
|
|
4523
4391
|
return ids;
|
|
@@ -4538,7 +4406,7 @@ async function evaluateTraceability(upstreamIds, srcRoot, testsRoot) {
|
|
|
4538
4406
|
}
|
|
4539
4407
|
const pattern = buildIdPattern2(Array.from(upstreamIds));
|
|
4540
4408
|
for (const file of targetFiles) {
|
|
4541
|
-
const text = await (0,
|
|
4409
|
+
const text = await (0, import_promises18.readFile)(file, "utf-8");
|
|
4542
4410
|
if (pattern.test(text)) {
|
|
4543
4411
|
return true;
|
|
4544
4412
|
}
|
|
@@ -4675,7 +4543,7 @@ function warnIfTruncated(scan, context) {
|
|
|
4675
4543
|
|
|
4676
4544
|
// src/cli/commands/report.ts
|
|
4677
4545
|
async function runReport(options) {
|
|
4678
|
-
const root =
|
|
4546
|
+
const root = import_node_path21.default.resolve(options.root);
|
|
4679
4547
|
const configResult = await loadConfig(root);
|
|
4680
4548
|
let validation;
|
|
4681
4549
|
if (options.runValidate) {
|
|
@@ -4692,7 +4560,7 @@ async function runReport(options) {
|
|
|
4692
4560
|
validation = normalized;
|
|
4693
4561
|
} else {
|
|
4694
4562
|
const input = options.inputPath ?? configResult.config.output.validateJsonPath;
|
|
4695
|
-
const inputPath =
|
|
4563
|
+
const inputPath = import_node_path21.default.isAbsolute(input) ? input : import_node_path21.default.resolve(root, input);
|
|
4696
4564
|
try {
|
|
4697
4565
|
validation = await readValidationResult(inputPath);
|
|
4698
4566
|
} catch (err) {
|
|
@@ -4703,7 +4571,7 @@ async function runReport(options) {
|
|
|
4703
4571
|
"",
|
|
4704
4572
|
"\u307E\u305A qfai validate \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4F8B:",
|
|
4705
4573
|
" qfai validate",
|
|
4706
|
-
"\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",
|
|
4707
4575
|
"",
|
|
4708
4576
|
"\u307E\u305F\u306F report \u306B --run-validate \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
4709
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"
|
|
@@ -4719,11 +4587,11 @@ async function runReport(options) {
|
|
|
4719
4587
|
warnIfTruncated(data.traceability.testFiles, "report");
|
|
4720
4588
|
const output = options.format === "json" ? formatReportJson(data) : options.baseUrl ? formatReportMarkdown(data, { baseUrl: options.baseUrl }) : formatReportMarkdown(data);
|
|
4721
4589
|
const outRoot = resolvePath(root, configResult.config, "outDir");
|
|
4722
|
-
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");
|
|
4723
4591
|
const out = options.outPath ?? defaultOut;
|
|
4724
|
-
const outPath =
|
|
4725
|
-
await (0,
|
|
4726
|
-
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}
|
|
4727
4595
|
`, "utf-8");
|
|
4728
4596
|
info(
|
|
4729
4597
|
`report: info=${validation.counts.info} warning=${validation.counts.warning} error=${validation.counts.error}`
|
|
@@ -4731,7 +4599,7 @@ async function runReport(options) {
|
|
|
4731
4599
|
info(`wrote report: ${outPath}`);
|
|
4732
4600
|
}
|
|
4733
4601
|
async function readValidationResult(inputPath) {
|
|
4734
|
-
const raw = await (0,
|
|
4602
|
+
const raw = await (0, import_promises19.readFile)(inputPath, "utf-8");
|
|
4735
4603
|
const parsed = JSON.parse(raw);
|
|
4736
4604
|
if (!isValidationResult(parsed)) {
|
|
4737
4605
|
throw new Error(`validate.json \u306E\u5F62\u5F0F\u304C\u4E0D\u6B63\u3067\u3059: ${inputPath}`);
|
|
@@ -4787,15 +4655,15 @@ function isMissingFileError5(error2) {
|
|
|
4787
4655
|
return record2.code === "ENOENT";
|
|
4788
4656
|
}
|
|
4789
4657
|
async function writeValidationResult(root, outputPath, result) {
|
|
4790
|
-
const abs =
|
|
4791
|
-
await (0,
|
|
4792
|
-
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)}
|
|
4793
4661
|
`, "utf-8");
|
|
4794
4662
|
}
|
|
4795
4663
|
|
|
4796
4664
|
// src/cli/commands/validate.ts
|
|
4797
|
-
var
|
|
4798
|
-
var
|
|
4665
|
+
var import_promises20 = require("fs/promises");
|
|
4666
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
4799
4667
|
|
|
4800
4668
|
// src/cli/lib/failOn.ts
|
|
4801
4669
|
function shouldFail(result, failOn) {
|
|
@@ -4810,7 +4678,7 @@ function shouldFail(result, failOn) {
|
|
|
4810
4678
|
|
|
4811
4679
|
// src/cli/commands/validate.ts
|
|
4812
4680
|
async function runValidate(options) {
|
|
4813
|
-
const root =
|
|
4681
|
+
const root = import_node_path22.default.resolve(options.root);
|
|
4814
4682
|
const configResult = await loadConfig(root);
|
|
4815
4683
|
const result = await validateProject(root, configResult);
|
|
4816
4684
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4935,12 +4803,12 @@ function issueKey(issue7) {
|
|
|
4935
4803
|
}
|
|
4936
4804
|
async function emitJson(result, root, jsonPath) {
|
|
4937
4805
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4938
|
-
await (0,
|
|
4939
|
-
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)}
|
|
4940
4808
|
`, "utf-8");
|
|
4941
4809
|
}
|
|
4942
4810
|
function resolveJsonPath(root, jsonPath) {
|
|
4943
|
-
return
|
|
4811
|
+
return import_node_path22.default.isAbsolute(jsonPath) ? jsonPath : import_node_path22.default.resolve(root, jsonPath);
|
|
4944
4812
|
}
|
|
4945
4813
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4946
4814
|
|
|
@@ -4953,7 +4821,6 @@ function parseArgs(argv, cwd) {
|
|
|
4953
4821
|
force: false,
|
|
4954
4822
|
yes: false,
|
|
4955
4823
|
dryRun: false,
|
|
4956
|
-
analyzeList: false,
|
|
4957
4824
|
reportFormat: "md",
|
|
4958
4825
|
reportRunValidate: false,
|
|
4959
4826
|
doctorFormat: "text",
|
|
@@ -5005,18 +4872,6 @@ function parseArgs(argv, cwd) {
|
|
|
5005
4872
|
case "--dry-run":
|
|
5006
4873
|
options.dryRun = true;
|
|
5007
4874
|
break;
|
|
5008
|
-
case "--list":
|
|
5009
|
-
options.analyzeList = true;
|
|
5010
|
-
break;
|
|
5011
|
-
case "--prompt":
|
|
5012
|
-
{
|
|
5013
|
-
const next = readOptionValue(args, i);
|
|
5014
|
-
if (next) {
|
|
5015
|
-
options.analyzePrompt = next;
|
|
5016
|
-
i += 1;
|
|
5017
|
-
}
|
|
5018
|
-
}
|
|
5019
|
-
break;
|
|
5020
4875
|
case "--format": {
|
|
5021
4876
|
const next = readOptionValue(args, i);
|
|
5022
4877
|
if (next === null) {
|
|
@@ -5150,17 +5005,6 @@ async function run(argv, cwd) {
|
|
|
5150
5005
|
yes: options.yes
|
|
5151
5006
|
});
|
|
5152
5007
|
return;
|
|
5153
|
-
case "analyze":
|
|
5154
|
-
{
|
|
5155
|
-
const resolvedRoot = await resolveRoot(options);
|
|
5156
|
-
const exitCode = await runAnalyze({
|
|
5157
|
-
root: resolvedRoot,
|
|
5158
|
-
list: options.analyzeList,
|
|
5159
|
-
...options.analyzePrompt !== void 0 ? { prompt: options.analyzePrompt } : {}
|
|
5160
|
-
});
|
|
5161
|
-
process.exitCode = exitCode;
|
|
5162
|
-
}
|
|
5163
|
-
return;
|
|
5164
5008
|
case "validate":
|
|
5165
5009
|
{
|
|
5166
5010
|
const resolvedRoot = await resolveRoot(options);
|
|
@@ -5208,7 +5052,6 @@ function usage() {
|
|
|
5208
5052
|
|
|
5209
5053
|
Commands:
|
|
5210
5054
|
init \u30C6\u30F3\u30D7\u30EC\u3092\u751F\u6210
|
|
5211
|
-
analyze \u610F\u5473\u30EC\u30D9\u30EB\u306E\u30EC\u30D3\u30E5\u30FC\u88DC\u52A9\uFF08\u30D7\u30ED\u30F3\u30D7\u30C8\u51FA\u529B\uFF09
|
|
5212
5055
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
5213
5056
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
5214
5057
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
@@ -5216,11 +5059,9 @@ Commands:
|
|
|
5216
5059
|
Options:
|
|
5217
5060
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
5218
5061
|
--dir <path> init \u306E\u51FA\u529B\u5148
|
|
5219
|
-
--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
|
|
5220
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
|
|
5221
5064
|
--dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
|
|
5222
|
-
--list analyze: \u5229\u7528\u53EF\u80FD\u306A\u30D7\u30ED\u30F3\u30D7\u30C8\u4E00\u89A7\u3092\u8868\u793A
|
|
5223
|
-
--prompt <name> analyze: \u6307\u5B9A\u30D7\u30ED\u30F3\u30D7\u30C8\uFF08.md\u7701\u7565\u53EF\uFF09\u3092\u51FA\u529B
|
|
5224
5065
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|
|
5225
5066
|
--format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
|
|
5226
5067
|
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|