qfai 0.7.0 → 0.7.1
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 +13 -2
- package/assets/init/.qfai/README.md +9 -1
- package/assets/init/.qfai/prompts/README.md +10 -0
- package/assets/init/.qfai/prompts.local/README.md +25 -0
- package/dist/cli/index.cjs +41 -285
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +41 -285
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ npx qfai report
|
|
|
48
48
|
|
|
49
49
|
## できること
|
|
50
50
|
|
|
51
|
-
- `npx qfai init` によるテンプレート生成(specs/contracts に加え、`.qfai/require/README.md`、`.qfai/rules/pnpm.md`、`.qfai/prompts
|
|
51
|
+
- `npx qfai init` によるテンプレート生成(specs/contracts に加え、`.qfai/require/README.md`、`.qfai/rules/pnpm.md`、`.qfai/prompts/**`、`.qfai/prompts.local/README.md`、`.qfai/promptpack/` を含む)
|
|
52
52
|
- `npx qfai validate` による `.qfai/` 内ドキュメントの整合性・トレーサビリティ検査
|
|
53
53
|
- `npx qfai validate` による SC→Test 参照の検証(`validation.traceability.testFileGlobs` に一致するテストファイルから `QFAI:SC-xxxx` を抽出)
|
|
54
54
|
- `npx qfai doctor` による設定/探索/パス/glob/validate.json の事前診断
|
|
@@ -61,6 +61,15 @@ npx qfai report
|
|
|
61
61
|
`validate` は `--fail-on` / `--strict` によって CI ゲート化できます。`validate` は常に `.qfai/out/validate.json`(`output.validateJsonPath`)へ JSON を出力します。`--format` は画面表示(text/github)のみを制御します。`--format github` はアノテーションの上限と重複排除を行い、先頭にサマリを出します(全量は `validate.json` か `--format text` を参照)。
|
|
62
62
|
`report` は `.qfai/out/validate.json` を既定入力とし、`--in` で上書きできます(優先順位: CLI > config)。`--run-validate` を指定すると validate を実行してから report を生成します。出力先は `--out` で変更できます(`--format json` の場合は `.qfai/out/report.json`)。
|
|
63
63
|
`doctor` は validate/report の前段で設定/探索/パス/glob/validate.json を診断します。`--format text|json`、`--out` をサポートし、診断のみ(修復はしません)。`--fail-on warning|error` を指定すると該当 severity 以上で exit 1(未指定は常に exit 0)になります。
|
|
64
|
+
|
|
65
|
+
### Prompts Overlay(v0.7 以降の方針)
|
|
66
|
+
|
|
67
|
+
QFAI が提供するプロンプト資産は次の 2 つに分離します。
|
|
68
|
+
|
|
69
|
+
- `.qfai/prompts/**`: QFAI 標準資産(更新や `qfai init` 再実行で上書きされ得る。利用者編集は非推奨・非サポート)
|
|
70
|
+
- `.qfai/prompts.local/**`: 利用者カスタム資産(QFAI はここを上書きしない)
|
|
71
|
+
|
|
72
|
+
同じ相対パスのファイルがある場合は `.qfai/prompts.local` を優先して参照する運用とします。
|
|
64
73
|
`report.json` は非契約(experimental / internal)として扱います。外部 consumer は依存しないでください。フィールドは例であり固定ではありません。短い例:
|
|
65
74
|
|
|
66
75
|
```json
|
|
@@ -80,7 +89,7 @@ doctor(text)の例:
|
|
|
80
89
|
```text
|
|
81
90
|
qfai doctor: root=. config=qfai.config.yaml (found)
|
|
82
91
|
[ok] config.search: qfai.config.yaml found
|
|
83
|
-
summary: ok=10 warning=2 error=0
|
|
92
|
+
summary: ok=10 info=1 warning=2 error=0
|
|
84
93
|
```
|
|
85
94
|
|
|
86
95
|
doctor の JSON も非契約(内部形式。将来予告なく変更あり)です。フィールドは例であり固定ではありません。短い例:
|
|
@@ -221,6 +230,8 @@ qfai.config.yaml
|
|
|
221
230
|
qfai-maintain-traceability.md
|
|
222
231
|
qfai-maintain-contracts.md
|
|
223
232
|
qfai-classify-change.md
|
|
233
|
+
prompts.local/
|
|
234
|
+
README.md
|
|
224
235
|
contracts/
|
|
225
236
|
README.md
|
|
226
237
|
api/
|
|
@@ -24,7 +24,8 @@ npx qfai report
|
|
|
24
24
|
- `contracts/` : UI / API / DB 契約を置く場所
|
|
25
25
|
- `require/` : 既存要件の集約(validate 対象外)
|
|
26
26
|
- `rules/` : 規約・運用ルール
|
|
27
|
-
- `prompts/` :
|
|
27
|
+
- `prompts/` : QFAI 標準のプロンプト資産(自動読取はしない。更新や再 init で上書きされ得る)
|
|
28
|
+
- `prompts.local/` : 利用者カスタムのプロンプト資産(存在する場合は overlay でこちらを優先して読む運用)
|
|
28
29
|
- `promptpack/` : PromptPack(SSOT、運用ルール/観点の正本)
|
|
29
30
|
- `out/` : `validate` / `report` の出力先(gitignore 推奨)
|
|
30
31
|
|
|
@@ -36,6 +37,7 @@ npx qfai report
|
|
|
36
37
|
- `rules/conventions.md`
|
|
37
38
|
- `rules/pnpm.md`
|
|
38
39
|
- `prompts/README.md`
|
|
40
|
+
- `prompts.local/README.md`
|
|
39
41
|
- `prompts/require-to-spec.md`
|
|
40
42
|
- `prompts/qfai-generate-test-globs.md`
|
|
41
43
|
- `prompts/qfai-maintain-traceability.md`
|
|
@@ -54,6 +56,12 @@ npx qfai report
|
|
|
54
56
|
|
|
55
57
|
`prompts/` は **人間が手動で使う資産**です。現時点では自動読取は行いません(将来のバージョンで CLI 連携を検討します)。
|
|
56
58
|
|
|
59
|
+
v0.7 以降、プロンプト資産のカスタマイズは `.qfai/prompts.local/**` に集約します(overlay 運用)。
|
|
60
|
+
|
|
61
|
+
- `.qfai/prompts/**` は QFAI 標準資産であり、更新や `qfai init` 再実行で上書きされ得ます
|
|
62
|
+
- 利用者が `.qfai/prompts/**` を直接編集することは非推奨・非サポートです
|
|
63
|
+
- 変更したい場合は同一相対パスで `.qfai/prompts.local/**` に置いて上書きしてください
|
|
64
|
+
|
|
57
65
|
例:
|
|
58
66
|
|
|
59
67
|
- Copilot: `.github/copilot-instructions.md` に要旨を転記
|
|
@@ -2,12 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
このディレクトリのプロンプトは **手動で使う資産**です。現時点では自動読取は行いません(将来のバージョンで CLI 連携を検討します)。
|
|
4
4
|
|
|
5
|
+
重要: `.qfai/prompts/**` は **QFAI 標準資産**です。QFAI のアップデートや `qfai init` の再実行により **上書きされ得ます**。
|
|
6
|
+
利用者が直接編集することは **非推奨・非サポート(ほぼ禁止)**です。変更したい場合は `.qfai/prompts.local/**` に同一相対パスで置いて overlay してください。
|
|
7
|
+
|
|
5
8
|
## 目的
|
|
6
9
|
|
|
7
10
|
- Spec から overview / Business Flow を生成するための素材
|
|
8
11
|
- トレーサビリティ/契約/変更区分の運用支援(CIで止めない領域)
|
|
9
12
|
- 将来(v0.9)の adapter/emit 実装に備えた配布物
|
|
10
13
|
|
|
14
|
+
## Overlay(prompts.local)
|
|
15
|
+
|
|
16
|
+
カスタムしたい場合は、次の優先順位で参照する運用とします。
|
|
17
|
+
|
|
18
|
+
1. `.qfai/prompts.local/<relativePath>`
|
|
19
|
+
2. `.qfai/prompts/<relativePath>`
|
|
20
|
+
|
|
11
21
|
## プロンプト一覧
|
|
12
22
|
|
|
13
23
|
- `makeOverview.md`: Spec 一覧(overview)生成
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Prompts Local(利用者カスタム / Overlay)
|
|
2
|
+
|
|
3
|
+
このディレクトリは **利用者が自由に編集・追加できるプロンプト資産**の置き場です。
|
|
4
|
+
|
|
5
|
+
QFAI v0.7 以降は、プロンプト資産のカスタマイズ手段を **overlay(prompts.local 優先)**に一本化します。
|
|
6
|
+
|
|
7
|
+
## Overlay ルール(重要)
|
|
8
|
+
|
|
9
|
+
同じ相対パスのファイルが存在する場合、次の優先順位で参照する運用とします。
|
|
10
|
+
|
|
11
|
+
1. `.qfai/prompts.local/<relativePath>`(存在すればこちらを優先)
|
|
12
|
+
2. `.qfai/prompts/<relativePath>`(無ければ base を参照)
|
|
13
|
+
|
|
14
|
+
例:
|
|
15
|
+
|
|
16
|
+
- base: `.qfai/prompts/require-to-spec.md`
|
|
17
|
+
- local override: `.qfai/prompts.local/require-to-spec.md`
|
|
18
|
+
|
|
19
|
+
→ `prompts.local` に同じ相対パスで置けば、以降は local を読む運用にできます。
|
|
20
|
+
|
|
21
|
+
## 重要な注意(サポート境界)
|
|
22
|
+
|
|
23
|
+
- `.qfai/prompts/**` は **QFAI 標準資産**です(更新や `qfai init` の再実行で上書きされ得ます)。
|
|
24
|
+
- 利用者が `.qfai/prompts/**` を直接編集することは **非推奨・非サポート(ほぼ禁止)**です。
|
|
25
|
+
- 変更したい場合は、対象ファイルを `prompts.local` にコピーして上書きしてください。
|
package/dist/cli/index.cjs
CHANGED
|
@@ -889,8 +889,8 @@ var import_promises6 = require("fs/promises");
|
|
|
889
889
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
890
890
|
var import_node_url = require("url");
|
|
891
891
|
async function resolveToolVersion() {
|
|
892
|
-
if ("0.7.
|
|
893
|
-
return "0.7.
|
|
892
|
+
if ("0.7.1".length > 0) {
|
|
893
|
+
return "0.7.1";
|
|
894
894
|
}
|
|
895
895
|
try {
|
|
896
896
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -921,7 +921,7 @@ function addCheck(checks, check) {
|
|
|
921
921
|
checks.push(check);
|
|
922
922
|
}
|
|
923
923
|
function summarize(checks) {
|
|
924
|
-
const summary = { ok: 0, warning: 0, error: 0 };
|
|
924
|
+
const summary = { ok: 0, info: 0, warning: 0, error: 0 };
|
|
925
925
|
for (const check of checks) {
|
|
926
926
|
summary[check.severity] += 1;
|
|
927
927
|
}
|
|
@@ -993,6 +993,20 @@ async function createDoctorData(options) {
|
|
|
993
993
|
message: ok ? `${key} exists` : `${key} is missing (did you run 'qfai init'?)`,
|
|
994
994
|
details: { path: toRelativePath(root, resolved) }
|
|
995
995
|
});
|
|
996
|
+
if (key === "promptsDir") {
|
|
997
|
+
const promptsLocalDir = import_node_path7.default.join(
|
|
998
|
+
import_node_path7.default.dirname(resolved),
|
|
999
|
+
`${import_node_path7.default.basename(resolved)}.local`
|
|
1000
|
+
);
|
|
1001
|
+
const found = await exists4(promptsLocalDir);
|
|
1002
|
+
addCheck(checks, {
|
|
1003
|
+
id: "paths.promptsLocalDir",
|
|
1004
|
+
severity: "info",
|
|
1005
|
+
title: "Prompts overlay (prompts.local)",
|
|
1006
|
+
message: found ? "prompts.local exists (overlay can be used)" : "prompts.local is optional (create it to override prompts)",
|
|
1007
|
+
details: { path: toRelativePath(root, promptsLocalDir) }
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
996
1010
|
}
|
|
997
1011
|
const specsRoot = resolvePath(root, config, "specsDir");
|
|
998
1012
|
const entries = await collectSpecEntries(specsRoot);
|
|
@@ -1191,7 +1205,7 @@ function formatDoctorText(data) {
|
|
|
1191
1205
|
lines.push(`[${check.severity}] ${check.id}: ${check.message}`);
|
|
1192
1206
|
}
|
|
1193
1207
|
lines.push(
|
|
1194
|
-
`summary: ok=${data.summary.ok} warning=${data.summary.warning} error=${data.summary.error}`
|
|
1208
|
+
`summary: ok=${data.summary.ok} info=${data.summary.info} warning=${data.summary.warning} error=${data.summary.error}`
|
|
1195
1209
|
);
|
|
1196
1210
|
return lines.join("\n");
|
|
1197
1211
|
}
|
|
@@ -1240,9 +1254,22 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1240
1254
|
const copied = [];
|
|
1241
1255
|
const skipped = [];
|
|
1242
1256
|
const conflicts = [];
|
|
1257
|
+
const protectPrefixes = (options.protect ?? []).map((p) => p.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "")).filter((p) => p.length > 0).map((p) => p + import_node_path9.default.sep);
|
|
1258
|
+
const isProtectedRelative = (relative) => {
|
|
1259
|
+
if (protectPrefixes.length === 0) {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
const normalized = relative.replace(/[\\/]+/g, import_node_path9.default.sep);
|
|
1263
|
+
return protectPrefixes.some(
|
|
1264
|
+
(prefix) => normalized === prefix.slice(0, -1) || normalized.startsWith(prefix)
|
|
1265
|
+
);
|
|
1266
|
+
};
|
|
1243
1267
|
if (!options.force) {
|
|
1244
1268
|
for (const file of files) {
|
|
1245
1269
|
const relative = import_node_path9.default.relative(sourceRoot, file);
|
|
1270
|
+
if (isProtectedRelative(relative)) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1246
1273
|
const dest = import_node_path9.default.join(destRoot, relative);
|
|
1247
1274
|
if (!await shouldWrite(dest, options.force)) {
|
|
1248
1275
|
conflicts.push(dest);
|
|
@@ -1255,7 +1282,8 @@ async function copyFiles(files, sourceRoot, destRoot, options) {
|
|
|
1255
1282
|
for (const file of files) {
|
|
1256
1283
|
const relative = import_node_path9.default.relative(sourceRoot, file);
|
|
1257
1284
|
const dest = import_node_path9.default.join(destRoot, relative);
|
|
1258
|
-
|
|
1285
|
+
const forceForThisFile = isProtectedRelative(relative) ? false : options.force;
|
|
1286
|
+
if (!await shouldWrite(dest, forceForThisFile)) {
|
|
1259
1287
|
skipped.push(dest);
|
|
1260
1288
|
continue;
|
|
1261
1289
|
}
|
|
@@ -1350,7 +1378,8 @@ async function runInit(options) {
|
|
|
1350
1378
|
});
|
|
1351
1379
|
const qfaiResult = await copyTemplateTree(qfaiAssets, destQfai, {
|
|
1352
1380
|
force: options.force,
|
|
1353
|
-
dryRun: options.dryRun
|
|
1381
|
+
dryRun: options.dryRun,
|
|
1382
|
+
protect: ["prompts.local"]
|
|
1354
1383
|
});
|
|
1355
1384
|
report(
|
|
1356
1385
|
[...rootResult.copied, ...qfaiResult.copied],
|
|
@@ -3664,235 +3693,9 @@ async function writeValidationResult(root, outputPath, result) {
|
|
|
3664
3693
|
`, "utf-8");
|
|
3665
3694
|
}
|
|
3666
3695
|
|
|
3667
|
-
// src/
|
|
3696
|
+
// src/cli/commands/validate.ts
|
|
3668
3697
|
var import_promises19 = require("fs/promises");
|
|
3669
3698
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
3670
|
-
var import_node_crypto2 = require("crypto");
|
|
3671
|
-
var import_node_url3 = require("url");
|
|
3672
|
-
async function exists6(target) {
|
|
3673
|
-
try {
|
|
3674
|
-
await (0, import_promises19.access)(target);
|
|
3675
|
-
return true;
|
|
3676
|
-
} catch {
|
|
3677
|
-
return false;
|
|
3678
|
-
}
|
|
3679
|
-
}
|
|
3680
|
-
async function computeFileHash(filePath) {
|
|
3681
|
-
const content = await (0, import_promises19.readFile)(filePath);
|
|
3682
|
-
return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex");
|
|
3683
|
-
}
|
|
3684
|
-
async function collectFilesRecursive(dir, base) {
|
|
3685
|
-
const result = [];
|
|
3686
|
-
if (!await exists6(dir)) {
|
|
3687
|
-
return result;
|
|
3688
|
-
}
|
|
3689
|
-
const entries = await (0, import_promises19.readdir)(dir, { withFileTypes: true });
|
|
3690
|
-
for (const entry of entries) {
|
|
3691
|
-
const fullPath = import_node_path19.default.join(dir, entry.name);
|
|
3692
|
-
if (entry.isDirectory()) {
|
|
3693
|
-
const nested = await collectFilesRecursive(fullPath, base);
|
|
3694
|
-
result.push(...nested);
|
|
3695
|
-
} else if (entry.isFile()) {
|
|
3696
|
-
const relative = import_node_path19.default.relative(base, fullPath);
|
|
3697
|
-
result.push(relative);
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
return result;
|
|
3701
|
-
}
|
|
3702
|
-
function resolveAssetsPath() {
|
|
3703
|
-
const base = __filename;
|
|
3704
|
-
const basePath = base.startsWith("file:") ? (0, import_node_url3.fileURLToPath)(base) : base;
|
|
3705
|
-
return import_node_path19.default.resolve(
|
|
3706
|
-
import_node_path19.default.dirname(basePath),
|
|
3707
|
-
"../../assets/init/.qfai/promptpack"
|
|
3708
|
-
);
|
|
3709
|
-
}
|
|
3710
|
-
async function copyDirRecursive(srcDir, destDir) {
|
|
3711
|
-
await (0, import_promises19.mkdir)(destDir, { recursive: true });
|
|
3712
|
-
const entries = await (0, import_promises19.readdir)(srcDir, { withFileTypes: true });
|
|
3713
|
-
for (const entry of entries) {
|
|
3714
|
-
const srcPath = import_node_path19.default.join(srcDir, entry.name);
|
|
3715
|
-
const destPath = import_node_path19.default.join(destDir, entry.name);
|
|
3716
|
-
if (entry.isDirectory()) {
|
|
3717
|
-
await copyDirRecursive(srcPath, destPath);
|
|
3718
|
-
} else if (entry.isFile()) {
|
|
3719
|
-
await (0, import_promises19.copyFile)(srcPath, destPath);
|
|
3720
|
-
}
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
|
-
async function createSyncData(options) {
|
|
3724
|
-
const root = import_node_path19.default.resolve(options.root);
|
|
3725
|
-
const version = await resolveToolVersion();
|
|
3726
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3727
|
-
const scope = "promptpack";
|
|
3728
|
-
const assetsPromptPackPath = resolveAssetsPath();
|
|
3729
|
-
const projectPromptPackPath = import_node_path19.default.join(root, ".qfai", "promptpack");
|
|
3730
|
-
const diffs = [];
|
|
3731
|
-
const assetsFiles = await collectFilesRecursive(
|
|
3732
|
-
assetsPromptPackPath,
|
|
3733
|
-
assetsPromptPackPath
|
|
3734
|
-
);
|
|
3735
|
-
const projectFiles = await collectFilesRecursive(
|
|
3736
|
-
projectPromptPackPath,
|
|
3737
|
-
projectPromptPackPath
|
|
3738
|
-
);
|
|
3739
|
-
const assetsSet = new Set(assetsFiles);
|
|
3740
|
-
const projectSet = new Set(projectFiles);
|
|
3741
|
-
for (const relativePath of assetsFiles) {
|
|
3742
|
-
const assetsFilePath = import_node_path19.default.join(assetsPromptPackPath, relativePath);
|
|
3743
|
-
const projectFilePath = import_node_path19.default.join(projectPromptPackPath, relativePath);
|
|
3744
|
-
if (!projectSet.has(relativePath)) {
|
|
3745
|
-
diffs.push({
|
|
3746
|
-
filePath: relativePath,
|
|
3747
|
-
status: "added",
|
|
3748
|
-
reason: "File exists in assets but not in project"
|
|
3749
|
-
});
|
|
3750
|
-
} else {
|
|
3751
|
-
const assetsHash = await computeFileHash(assetsFilePath);
|
|
3752
|
-
const projectHash = await computeFileHash(projectFilePath);
|
|
3753
|
-
if (assetsHash !== projectHash) {
|
|
3754
|
-
diffs.push({
|
|
3755
|
-
filePath: relativePath,
|
|
3756
|
-
status: "changed",
|
|
3757
|
-
reason: "Content differs between assets and project"
|
|
3758
|
-
});
|
|
3759
|
-
} else {
|
|
3760
|
-
diffs.push({
|
|
3761
|
-
filePath: relativePath,
|
|
3762
|
-
status: "unchanged"
|
|
3763
|
-
});
|
|
3764
|
-
}
|
|
3765
|
-
}
|
|
3766
|
-
}
|
|
3767
|
-
for (const relativePath of projectFiles) {
|
|
3768
|
-
if (!assetsSet.has(relativePath)) {
|
|
3769
|
-
diffs.push({
|
|
3770
|
-
filePath: relativePath,
|
|
3771
|
-
status: "removed",
|
|
3772
|
-
reason: "File exists in project but not in assets (local extension)"
|
|
3773
|
-
});
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
diffs.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
3777
|
-
const summary = {
|
|
3778
|
-
added: diffs.filter((d) => d.status === "added").length,
|
|
3779
|
-
removed: diffs.filter((d) => d.status === "removed").length,
|
|
3780
|
-
changed: diffs.filter((d) => d.status === "changed").length,
|
|
3781
|
-
unchanged: diffs.filter((d) => d.status === "unchanged").length
|
|
3782
|
-
};
|
|
3783
|
-
let exportPath;
|
|
3784
|
-
if (options.mode === "export") {
|
|
3785
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3786
|
-
const baseTimestamp = `${timestamp}-${Date.now()}`;
|
|
3787
|
-
const defaultOutDir = import_node_path19.default.join(root, ".qfai", ".sync");
|
|
3788
|
-
const outBase = options.outPath ? import_node_path19.default.isAbsolute(options.outPath) ? options.outPath : import_node_path19.default.resolve(root, options.outPath) : defaultOutDir;
|
|
3789
|
-
let exportDir;
|
|
3790
|
-
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
3791
|
-
const uniqueTimestamp = attempt === 0 ? baseTimestamp : `${baseTimestamp}-${attempt}`;
|
|
3792
|
-
const exportParent = import_node_path19.default.join(outBase, uniqueTimestamp);
|
|
3793
|
-
const candidate = import_node_path19.default.join(exportParent, "promptpack");
|
|
3794
|
-
await (0, import_promises19.mkdir)(exportParent, { recursive: true });
|
|
3795
|
-
try {
|
|
3796
|
-
await (0, import_promises19.mkdir)(candidate);
|
|
3797
|
-
exportDir = candidate;
|
|
3798
|
-
break;
|
|
3799
|
-
} catch (err) {
|
|
3800
|
-
const code = err?.code;
|
|
3801
|
-
if (code === "EEXIST") {
|
|
3802
|
-
continue;
|
|
3803
|
-
}
|
|
3804
|
-
throw err;
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
if (!exportDir) {
|
|
3808
|
-
throw new Error("Failed to allocate unique export directory");
|
|
3809
|
-
}
|
|
3810
|
-
await copyDirRecursive(assetsPromptPackPath, exportDir);
|
|
3811
|
-
exportPath = toRelativePath(root, exportDir);
|
|
3812
|
-
}
|
|
3813
|
-
return {
|
|
3814
|
-
tool: "qfai",
|
|
3815
|
-
version,
|
|
3816
|
-
generatedAt,
|
|
3817
|
-
root: toRelativePath(process.cwd(), root),
|
|
3818
|
-
mode: options.mode,
|
|
3819
|
-
scope,
|
|
3820
|
-
summary,
|
|
3821
|
-
diffs,
|
|
3822
|
-
...exportPath ? { exportPath } : {}
|
|
3823
|
-
};
|
|
3824
|
-
}
|
|
3825
|
-
function computeExitCode(data) {
|
|
3826
|
-
const hasDiff = data.summary.added > 0 || data.summary.removed > 0 || data.summary.changed > 0;
|
|
3827
|
-
return hasDiff ? 1 : 0;
|
|
3828
|
-
}
|
|
3829
|
-
|
|
3830
|
-
// src/cli/commands/sync.ts
|
|
3831
|
-
var import_node_path20 = __toESM(require("path"), 1);
|
|
3832
|
-
function formatSyncText(data) {
|
|
3833
|
-
const lines = [];
|
|
3834
|
-
lines.push(
|
|
3835
|
-
`qfai sync: root=${data.root} mode=${data.mode} scope=${data.scope}`
|
|
3836
|
-
);
|
|
3837
|
-
lines.push("");
|
|
3838
|
-
const diffs = data.diffs.filter((d) => d.status !== "unchanged");
|
|
3839
|
-
if (diffs.length === 0) {
|
|
3840
|
-
lines.push(
|
|
3841
|
-
"No differences found. Project promptpack is in sync with assets."
|
|
3842
|
-
);
|
|
3843
|
-
} else {
|
|
3844
|
-
lines.push("Differences:");
|
|
3845
|
-
for (const diff of diffs) {
|
|
3846
|
-
const statusMark = diff.status === "added" ? "[+]" : diff.status === "removed" ? "[-]" : "[~]";
|
|
3847
|
-
lines.push(` ${statusMark} ${diff.filePath}`);
|
|
3848
|
-
}
|
|
3849
|
-
}
|
|
3850
|
-
lines.push("");
|
|
3851
|
-
lines.push(
|
|
3852
|
-
`summary: added=${data.summary.added} removed=${data.summary.removed} changed=${data.summary.changed} unchanged=${data.summary.unchanged}`
|
|
3853
|
-
);
|
|
3854
|
-
if (data.exportPath) {
|
|
3855
|
-
lines.push("");
|
|
3856
|
-
lines.push(`exported to: ${data.exportPath}`);
|
|
3857
|
-
lines.push("");
|
|
3858
|
-
lines.push("Next steps:");
|
|
3859
|
-
const absRoot = import_node_path20.default.resolve(process.cwd(), data.root);
|
|
3860
|
-
const absExportPath = import_node_path20.default.resolve(absRoot, data.exportPath);
|
|
3861
|
-
lines.push(
|
|
3862
|
-
` git diff --no-index ${import_node_path20.default.join(absRoot, ".qfai", "promptpack")} ${absExportPath}`
|
|
3863
|
-
);
|
|
3864
|
-
} else if (data.summary.added + data.summary.changed > 0) {
|
|
3865
|
-
lines.push("");
|
|
3866
|
-
lines.push("To export sync candidates:");
|
|
3867
|
-
lines.push(" qfai sync --mode export");
|
|
3868
|
-
}
|
|
3869
|
-
return lines.join("\n");
|
|
3870
|
-
}
|
|
3871
|
-
function formatSyncJson(data) {
|
|
3872
|
-
return JSON.stringify(data, null, 2);
|
|
3873
|
-
}
|
|
3874
|
-
async function runSync(options) {
|
|
3875
|
-
try {
|
|
3876
|
-
const data = await createSyncData({
|
|
3877
|
-
root: options.root,
|
|
3878
|
-
mode: options.mode,
|
|
3879
|
-
...options.outPath !== void 0 ? { outPath: options.outPath } : {}
|
|
3880
|
-
});
|
|
3881
|
-
const output = options.format === "json" ? formatSyncJson(data) : formatSyncText(data);
|
|
3882
|
-
info(output);
|
|
3883
|
-
if (options.mode === "export") {
|
|
3884
|
-
return 0;
|
|
3885
|
-
}
|
|
3886
|
-
return computeExitCode(data);
|
|
3887
|
-
} catch (err) {
|
|
3888
|
-
error(`sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3889
|
-
return 2;
|
|
3890
|
-
}
|
|
3891
|
-
}
|
|
3892
|
-
|
|
3893
|
-
// src/cli/commands/validate.ts
|
|
3894
|
-
var import_promises20 = require("fs/promises");
|
|
3895
|
-
var import_node_path21 = __toESM(require("path"), 1);
|
|
3896
3699
|
|
|
3897
3700
|
// src/cli/lib/failOn.ts
|
|
3898
3701
|
function shouldFail(result, failOn) {
|
|
@@ -3907,7 +3710,7 @@ function shouldFail(result, failOn) {
|
|
|
3907
3710
|
|
|
3908
3711
|
// src/cli/commands/validate.ts
|
|
3909
3712
|
async function runValidate(options) {
|
|
3910
|
-
const root =
|
|
3713
|
+
const root = import_node_path19.default.resolve(options.root);
|
|
3911
3714
|
const configResult = await loadConfig(root);
|
|
3912
3715
|
const result = await validateProject(root, configResult);
|
|
3913
3716
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -4024,12 +3827,12 @@ function issueKey(issue7) {
|
|
|
4024
3827
|
}
|
|
4025
3828
|
async function emitJson(result, root, jsonPath) {
|
|
4026
3829
|
const abs = resolveJsonPath(root, jsonPath);
|
|
4027
|
-
await (0,
|
|
4028
|
-
await (0,
|
|
3830
|
+
await (0, import_promises19.mkdir)(import_node_path19.default.dirname(abs), { recursive: true });
|
|
3831
|
+
await (0, import_promises19.writeFile)(abs, `${JSON.stringify(result, null, 2)}
|
|
4029
3832
|
`, "utf-8");
|
|
4030
3833
|
}
|
|
4031
3834
|
function resolveJsonPath(root, jsonPath) {
|
|
4032
|
-
return
|
|
3835
|
+
return import_node_path19.default.isAbsolute(jsonPath) ? jsonPath : import_node_path19.default.resolve(root, jsonPath);
|
|
4033
3836
|
}
|
|
4034
3837
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
4035
3838
|
|
|
@@ -4046,8 +3849,6 @@ function parseArgs(argv, cwd) {
|
|
|
4046
3849
|
reportRunValidate: false,
|
|
4047
3850
|
doctorFormat: "text",
|
|
4048
3851
|
validateFormat: "text",
|
|
4049
|
-
syncFormat: "text",
|
|
4050
|
-
syncMode: "check",
|
|
4051
3852
|
strict: false,
|
|
4052
3853
|
help: false
|
|
4053
3854
|
};
|
|
@@ -4101,8 +3902,6 @@ function parseArgs(argv, cwd) {
|
|
|
4101
3902
|
if (next) {
|
|
4102
3903
|
if (command === "doctor") {
|
|
4103
3904
|
options.doctorOut = next;
|
|
4104
|
-
} else if (command === "sync") {
|
|
4105
|
-
options.syncOut = next;
|
|
4106
3905
|
} else {
|
|
4107
3906
|
options.reportOut = next;
|
|
4108
3907
|
}
|
|
@@ -4110,28 +3909,6 @@ function parseArgs(argv, cwd) {
|
|
|
4110
3909
|
}
|
|
4111
3910
|
i += 1;
|
|
4112
3911
|
break;
|
|
4113
|
-
case "--mode":
|
|
4114
|
-
{
|
|
4115
|
-
const next = args[i + 1];
|
|
4116
|
-
if (!next) {
|
|
4117
|
-
throw new Error(
|
|
4118
|
-
'--mode option requires a value of "check" or "export"'
|
|
4119
|
-
);
|
|
4120
|
-
}
|
|
4121
|
-
if (command !== "sync") {
|
|
4122
|
-
throw new Error(
|
|
4123
|
-
'--mode option is only supported for the "sync" command'
|
|
4124
|
-
);
|
|
4125
|
-
}
|
|
4126
|
-
if (next !== "check" && next !== "export") {
|
|
4127
|
-
throw new Error(
|
|
4128
|
-
`Invalid value for --mode: "${next}". Expected "check" or "export".`
|
|
4129
|
-
);
|
|
4130
|
-
}
|
|
4131
|
-
options.syncMode = next;
|
|
4132
|
-
}
|
|
4133
|
-
i += 1;
|
|
4134
|
-
break;
|
|
4135
3912
|
case "--in":
|
|
4136
3913
|
{
|
|
4137
3914
|
const next = args[i + 1];
|
|
@@ -4176,12 +3953,6 @@ function applyFormatOption(command, value, options) {
|
|
|
4176
3953
|
}
|
|
4177
3954
|
return;
|
|
4178
3955
|
}
|
|
4179
|
-
if (command === "sync") {
|
|
4180
|
-
if (value === "text" || value === "json") {
|
|
4181
|
-
options.syncFormat = value;
|
|
4182
|
-
}
|
|
4183
|
-
return;
|
|
4184
|
-
}
|
|
4185
3956
|
if (value === "md" || value === "json") {
|
|
4186
3957
|
options.reportFormat = value;
|
|
4187
3958
|
}
|
|
@@ -4241,18 +4012,6 @@ async function run(argv, cwd) {
|
|
|
4241
4012
|
process.exitCode = exitCode;
|
|
4242
4013
|
}
|
|
4243
4014
|
return;
|
|
4244
|
-
case "sync":
|
|
4245
|
-
{
|
|
4246
|
-
const resolvedRoot = await resolveRoot(options);
|
|
4247
|
-
const exitCode = await runSync({
|
|
4248
|
-
root: resolvedRoot,
|
|
4249
|
-
mode: options.syncMode,
|
|
4250
|
-
format: options.syncFormat,
|
|
4251
|
-
...options.syncOut !== void 0 ? { outPath: options.syncOut } : {}
|
|
4252
|
-
});
|
|
4253
|
-
process.exitCode = exitCode;
|
|
4254
|
-
}
|
|
4255
|
-
return;
|
|
4256
4015
|
default:
|
|
4257
4016
|
error(`Unknown command: ${command}`);
|
|
4258
4017
|
info(usage());
|
|
@@ -4267,7 +4026,6 @@ Commands:
|
|
|
4267
4026
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
4268
4027
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
4269
4028
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
4270
|
-
sync PromptPack \u306E\u5DEE\u5206\u691C\u77E5\u30FB\u540C\u671F\u5019\u88DC\u66F8\u304D\u51FA\u3057
|
|
4271
4029
|
|
|
4272
4030
|
Options:
|
|
4273
4031
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
@@ -4277,13 +4035,11 @@ Options:
|
|
|
4277
4035
|
--dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
|
|
4278
4036
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4279
4037
|
--format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4280
|
-
--format <text|json> doctor
|
|
4038
|
+
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4281
4039
|
--strict validate: warning \u4EE5\u4E0A\u3067 exit 1
|
|
4282
4040
|
--fail-on <error|warning|never> validate: \u5931\u6557\u6761\u4EF6
|
|
4283
4041
|
--fail-on <error|warning> doctor: \u5931\u6557\u6761\u4EF6
|
|
4284
|
-
--mode <check|export> sync: \u52D5\u4F5C\u30E2\u30FC\u30C9\uFF08default: check\uFF09
|
|
4285
4042
|
--out <path> report/doctor: \u51FA\u529B\u5148
|
|
4286
|
-
--out <dir> sync: export \u306E\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF08\u76F8\u5BFE/\u7D76\u5BFE\u3001export \u306E\u307F\uFF09
|
|
4287
4043
|
--in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
|
|
4288
4044
|
--run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
|
|
4289
4045
|
-h, --help \u30D8\u30EB\u30D7\u8868\u793A
|