qfai 0.6.2 → 0.7.0
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 +206 -22
- package/dist/cli/index.cjs +281 -8
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +281 -8
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -870,8 +870,8 @@ import { readFile as readFile3 } from "fs/promises";
|
|
|
870
870
|
import path6 from "path";
|
|
871
871
|
import { fileURLToPath } from "url";
|
|
872
872
|
async function resolveToolVersion() {
|
|
873
|
-
if ("0.
|
|
874
|
-
return "0.
|
|
873
|
+
if ("0.7.0".length > 0) {
|
|
874
|
+
return "0.7.0";
|
|
875
875
|
}
|
|
876
876
|
try {
|
|
877
877
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -3645,9 +3645,235 @@ async function writeValidationResult(root, outputPath, result) {
|
|
|
3645
3645
|
`, "utf-8");
|
|
3646
3646
|
}
|
|
3647
3647
|
|
|
3648
|
-
// src/
|
|
3649
|
-
import { mkdir as mkdir4,
|
|
3648
|
+
// src/core/sync.ts
|
|
3649
|
+
import { access as access6, mkdir as mkdir4, readdir as readdir4, readFile as readFile13, copyFile as copyFile2 } from "fs/promises";
|
|
3650
3650
|
import path19 from "path";
|
|
3651
|
+
import { createHash } from "crypto";
|
|
3652
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3653
|
+
async function exists6(target) {
|
|
3654
|
+
try {
|
|
3655
|
+
await access6(target);
|
|
3656
|
+
return true;
|
|
3657
|
+
} catch {
|
|
3658
|
+
return false;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
async function computeFileHash(filePath) {
|
|
3662
|
+
const content = await readFile13(filePath);
|
|
3663
|
+
return createHash("sha256").update(content).digest("hex");
|
|
3664
|
+
}
|
|
3665
|
+
async function collectFilesRecursive(dir, base) {
|
|
3666
|
+
const result = [];
|
|
3667
|
+
if (!await exists6(dir)) {
|
|
3668
|
+
return result;
|
|
3669
|
+
}
|
|
3670
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
3671
|
+
for (const entry of entries) {
|
|
3672
|
+
const fullPath = path19.join(dir, entry.name);
|
|
3673
|
+
if (entry.isDirectory()) {
|
|
3674
|
+
const nested = await collectFilesRecursive(fullPath, base);
|
|
3675
|
+
result.push(...nested);
|
|
3676
|
+
} else if (entry.isFile()) {
|
|
3677
|
+
const relative = path19.relative(base, fullPath);
|
|
3678
|
+
result.push(relative);
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
return result;
|
|
3682
|
+
}
|
|
3683
|
+
function resolveAssetsPath() {
|
|
3684
|
+
const base = import.meta.url;
|
|
3685
|
+
const basePath = base.startsWith("file:") ? fileURLToPath3(base) : base;
|
|
3686
|
+
return path19.resolve(
|
|
3687
|
+
path19.dirname(basePath),
|
|
3688
|
+
"../../assets/init/.qfai/promptpack"
|
|
3689
|
+
);
|
|
3690
|
+
}
|
|
3691
|
+
async function copyDirRecursive(srcDir, destDir) {
|
|
3692
|
+
await mkdir4(destDir, { recursive: true });
|
|
3693
|
+
const entries = await readdir4(srcDir, { withFileTypes: true });
|
|
3694
|
+
for (const entry of entries) {
|
|
3695
|
+
const srcPath = path19.join(srcDir, entry.name);
|
|
3696
|
+
const destPath = path19.join(destDir, entry.name);
|
|
3697
|
+
if (entry.isDirectory()) {
|
|
3698
|
+
await copyDirRecursive(srcPath, destPath);
|
|
3699
|
+
} else if (entry.isFile()) {
|
|
3700
|
+
await copyFile2(srcPath, destPath);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
async function createSyncData(options) {
|
|
3705
|
+
const root = path19.resolve(options.root);
|
|
3706
|
+
const version = await resolveToolVersion();
|
|
3707
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3708
|
+
const scope = "promptpack";
|
|
3709
|
+
const assetsPromptPackPath = resolveAssetsPath();
|
|
3710
|
+
const projectPromptPackPath = path19.join(root, ".qfai", "promptpack");
|
|
3711
|
+
const diffs = [];
|
|
3712
|
+
const assetsFiles = await collectFilesRecursive(
|
|
3713
|
+
assetsPromptPackPath,
|
|
3714
|
+
assetsPromptPackPath
|
|
3715
|
+
);
|
|
3716
|
+
const projectFiles = await collectFilesRecursive(
|
|
3717
|
+
projectPromptPackPath,
|
|
3718
|
+
projectPromptPackPath
|
|
3719
|
+
);
|
|
3720
|
+
const assetsSet = new Set(assetsFiles);
|
|
3721
|
+
const projectSet = new Set(projectFiles);
|
|
3722
|
+
for (const relativePath of assetsFiles) {
|
|
3723
|
+
const assetsFilePath = path19.join(assetsPromptPackPath, relativePath);
|
|
3724
|
+
const projectFilePath = path19.join(projectPromptPackPath, relativePath);
|
|
3725
|
+
if (!projectSet.has(relativePath)) {
|
|
3726
|
+
diffs.push({
|
|
3727
|
+
filePath: relativePath,
|
|
3728
|
+
status: "added",
|
|
3729
|
+
reason: "File exists in assets but not in project"
|
|
3730
|
+
});
|
|
3731
|
+
} else {
|
|
3732
|
+
const assetsHash = await computeFileHash(assetsFilePath);
|
|
3733
|
+
const projectHash = await computeFileHash(projectFilePath);
|
|
3734
|
+
if (assetsHash !== projectHash) {
|
|
3735
|
+
diffs.push({
|
|
3736
|
+
filePath: relativePath,
|
|
3737
|
+
status: "changed",
|
|
3738
|
+
reason: "Content differs between assets and project"
|
|
3739
|
+
});
|
|
3740
|
+
} else {
|
|
3741
|
+
diffs.push({
|
|
3742
|
+
filePath: relativePath,
|
|
3743
|
+
status: "unchanged"
|
|
3744
|
+
});
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
for (const relativePath of projectFiles) {
|
|
3749
|
+
if (!assetsSet.has(relativePath)) {
|
|
3750
|
+
diffs.push({
|
|
3751
|
+
filePath: relativePath,
|
|
3752
|
+
status: "removed",
|
|
3753
|
+
reason: "File exists in project but not in assets (local extension)"
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
diffs.sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
3758
|
+
const summary = {
|
|
3759
|
+
added: diffs.filter((d) => d.status === "added").length,
|
|
3760
|
+
removed: diffs.filter((d) => d.status === "removed").length,
|
|
3761
|
+
changed: diffs.filter((d) => d.status === "changed").length,
|
|
3762
|
+
unchanged: diffs.filter((d) => d.status === "unchanged").length
|
|
3763
|
+
};
|
|
3764
|
+
let exportPath;
|
|
3765
|
+
if (options.mode === "export") {
|
|
3766
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3767
|
+
const baseTimestamp = `${timestamp}-${Date.now()}`;
|
|
3768
|
+
const defaultOutDir = path19.join(root, ".qfai", ".sync");
|
|
3769
|
+
const outBase = options.outPath ? path19.isAbsolute(options.outPath) ? options.outPath : path19.resolve(root, options.outPath) : defaultOutDir;
|
|
3770
|
+
let exportDir;
|
|
3771
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
3772
|
+
const uniqueTimestamp = attempt === 0 ? baseTimestamp : `${baseTimestamp}-${attempt}`;
|
|
3773
|
+
const exportParent = path19.join(outBase, uniqueTimestamp);
|
|
3774
|
+
const candidate = path19.join(exportParent, "promptpack");
|
|
3775
|
+
await mkdir4(exportParent, { recursive: true });
|
|
3776
|
+
try {
|
|
3777
|
+
await mkdir4(candidate);
|
|
3778
|
+
exportDir = candidate;
|
|
3779
|
+
break;
|
|
3780
|
+
} catch (err) {
|
|
3781
|
+
const code = err?.code;
|
|
3782
|
+
if (code === "EEXIST") {
|
|
3783
|
+
continue;
|
|
3784
|
+
}
|
|
3785
|
+
throw err;
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
if (!exportDir) {
|
|
3789
|
+
throw new Error("Failed to allocate unique export directory");
|
|
3790
|
+
}
|
|
3791
|
+
await copyDirRecursive(assetsPromptPackPath, exportDir);
|
|
3792
|
+
exportPath = toRelativePath(root, exportDir);
|
|
3793
|
+
}
|
|
3794
|
+
return {
|
|
3795
|
+
tool: "qfai",
|
|
3796
|
+
version,
|
|
3797
|
+
generatedAt,
|
|
3798
|
+
root: toRelativePath(process.cwd(), root),
|
|
3799
|
+
mode: options.mode,
|
|
3800
|
+
scope,
|
|
3801
|
+
summary,
|
|
3802
|
+
diffs,
|
|
3803
|
+
...exportPath ? { exportPath } : {}
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
function computeExitCode(data) {
|
|
3807
|
+
const hasDiff = data.summary.added > 0 || data.summary.removed > 0 || data.summary.changed > 0;
|
|
3808
|
+
return hasDiff ? 1 : 0;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
// src/cli/commands/sync.ts
|
|
3812
|
+
import path20 from "path";
|
|
3813
|
+
function formatSyncText(data) {
|
|
3814
|
+
const lines = [];
|
|
3815
|
+
lines.push(
|
|
3816
|
+
`qfai sync: root=${data.root} mode=${data.mode} scope=${data.scope}`
|
|
3817
|
+
);
|
|
3818
|
+
lines.push("");
|
|
3819
|
+
const diffs = data.diffs.filter((d) => d.status !== "unchanged");
|
|
3820
|
+
if (diffs.length === 0) {
|
|
3821
|
+
lines.push(
|
|
3822
|
+
"No differences found. Project promptpack is in sync with assets."
|
|
3823
|
+
);
|
|
3824
|
+
} else {
|
|
3825
|
+
lines.push("Differences:");
|
|
3826
|
+
for (const diff of diffs) {
|
|
3827
|
+
const statusMark = diff.status === "added" ? "[+]" : diff.status === "removed" ? "[-]" : "[~]";
|
|
3828
|
+
lines.push(` ${statusMark} ${diff.filePath}`);
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
lines.push("");
|
|
3832
|
+
lines.push(
|
|
3833
|
+
`summary: added=${data.summary.added} removed=${data.summary.removed} changed=${data.summary.changed} unchanged=${data.summary.unchanged}`
|
|
3834
|
+
);
|
|
3835
|
+
if (data.exportPath) {
|
|
3836
|
+
lines.push("");
|
|
3837
|
+
lines.push(`exported to: ${data.exportPath}`);
|
|
3838
|
+
lines.push("");
|
|
3839
|
+
lines.push("Next steps:");
|
|
3840
|
+
const absRoot = path20.resolve(process.cwd(), data.root);
|
|
3841
|
+
const absExportPath = path20.resolve(absRoot, data.exportPath);
|
|
3842
|
+
lines.push(
|
|
3843
|
+
` git diff --no-index ${path20.join(absRoot, ".qfai", "promptpack")} ${absExportPath}`
|
|
3844
|
+
);
|
|
3845
|
+
} else if (data.summary.added + data.summary.changed > 0) {
|
|
3846
|
+
lines.push("");
|
|
3847
|
+
lines.push("To export sync candidates:");
|
|
3848
|
+
lines.push(" qfai sync --mode export");
|
|
3849
|
+
}
|
|
3850
|
+
return lines.join("\n");
|
|
3851
|
+
}
|
|
3852
|
+
function formatSyncJson(data) {
|
|
3853
|
+
return JSON.stringify(data, null, 2);
|
|
3854
|
+
}
|
|
3855
|
+
async function runSync(options) {
|
|
3856
|
+
try {
|
|
3857
|
+
const data = await createSyncData({
|
|
3858
|
+
root: options.root,
|
|
3859
|
+
mode: options.mode,
|
|
3860
|
+
...options.outPath !== void 0 ? { outPath: options.outPath } : {}
|
|
3861
|
+
});
|
|
3862
|
+
const output = options.format === "json" ? formatSyncJson(data) : formatSyncText(data);
|
|
3863
|
+
info(output);
|
|
3864
|
+
if (options.mode === "export") {
|
|
3865
|
+
return 0;
|
|
3866
|
+
}
|
|
3867
|
+
return computeExitCode(data);
|
|
3868
|
+
} catch (err) {
|
|
3869
|
+
error(`sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3870
|
+
return 2;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
// src/cli/commands/validate.ts
|
|
3875
|
+
import { mkdir as mkdir5, writeFile as writeFile3 } from "fs/promises";
|
|
3876
|
+
import path21 from "path";
|
|
3651
3877
|
|
|
3652
3878
|
// src/cli/lib/failOn.ts
|
|
3653
3879
|
function shouldFail(result, failOn) {
|
|
@@ -3662,7 +3888,7 @@ function shouldFail(result, failOn) {
|
|
|
3662
3888
|
|
|
3663
3889
|
// src/cli/commands/validate.ts
|
|
3664
3890
|
async function runValidate(options) {
|
|
3665
|
-
const root =
|
|
3891
|
+
const root = path21.resolve(options.root);
|
|
3666
3892
|
const configResult = await loadConfig(root);
|
|
3667
3893
|
const result = await validateProject(root, configResult);
|
|
3668
3894
|
const normalized = normalizeValidationResult(root, result);
|
|
@@ -3779,12 +4005,12 @@ function issueKey(issue7) {
|
|
|
3779
4005
|
}
|
|
3780
4006
|
async function emitJson(result, root, jsonPath) {
|
|
3781
4007
|
const abs = resolveJsonPath(root, jsonPath);
|
|
3782
|
-
await
|
|
4008
|
+
await mkdir5(path21.dirname(abs), { recursive: true });
|
|
3783
4009
|
await writeFile3(abs, `${JSON.stringify(result, null, 2)}
|
|
3784
4010
|
`, "utf-8");
|
|
3785
4011
|
}
|
|
3786
4012
|
function resolveJsonPath(root, jsonPath) {
|
|
3787
|
-
return
|
|
4013
|
+
return path21.isAbsolute(jsonPath) ? jsonPath : path21.resolve(root, jsonPath);
|
|
3788
4014
|
}
|
|
3789
4015
|
var GITHUB_ANNOTATION_LIMIT = 100;
|
|
3790
4016
|
|
|
@@ -3801,6 +4027,8 @@ function parseArgs(argv, cwd) {
|
|
|
3801
4027
|
reportRunValidate: false,
|
|
3802
4028
|
doctorFormat: "text",
|
|
3803
4029
|
validateFormat: "text",
|
|
4030
|
+
syncFormat: "text",
|
|
4031
|
+
syncMode: "check",
|
|
3804
4032
|
strict: false,
|
|
3805
4033
|
help: false
|
|
3806
4034
|
};
|
|
@@ -3854,6 +4082,8 @@ function parseArgs(argv, cwd) {
|
|
|
3854
4082
|
if (next) {
|
|
3855
4083
|
if (command === "doctor") {
|
|
3856
4084
|
options.doctorOut = next;
|
|
4085
|
+
} else if (command === "sync") {
|
|
4086
|
+
options.syncOut = next;
|
|
3857
4087
|
} else {
|
|
3858
4088
|
options.reportOut = next;
|
|
3859
4089
|
}
|
|
@@ -3861,6 +4091,28 @@ function parseArgs(argv, cwd) {
|
|
|
3861
4091
|
}
|
|
3862
4092
|
i += 1;
|
|
3863
4093
|
break;
|
|
4094
|
+
case "--mode":
|
|
4095
|
+
{
|
|
4096
|
+
const next = args[i + 1];
|
|
4097
|
+
if (!next) {
|
|
4098
|
+
throw new Error(
|
|
4099
|
+
'--mode option requires a value of "check" or "export"'
|
|
4100
|
+
);
|
|
4101
|
+
}
|
|
4102
|
+
if (command !== "sync") {
|
|
4103
|
+
throw new Error(
|
|
4104
|
+
'--mode option is only supported for the "sync" command'
|
|
4105
|
+
);
|
|
4106
|
+
}
|
|
4107
|
+
if (next !== "check" && next !== "export") {
|
|
4108
|
+
throw new Error(
|
|
4109
|
+
`Invalid value for --mode: "${next}". Expected "check" or "export".`
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
options.syncMode = next;
|
|
4113
|
+
}
|
|
4114
|
+
i += 1;
|
|
4115
|
+
break;
|
|
3864
4116
|
case "--in":
|
|
3865
4117
|
{
|
|
3866
4118
|
const next = args[i + 1];
|
|
@@ -3905,6 +4157,12 @@ function applyFormatOption(command, value, options) {
|
|
|
3905
4157
|
}
|
|
3906
4158
|
return;
|
|
3907
4159
|
}
|
|
4160
|
+
if (command === "sync") {
|
|
4161
|
+
if (value === "text" || value === "json") {
|
|
4162
|
+
options.syncFormat = value;
|
|
4163
|
+
}
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
3908
4166
|
if (value === "md" || value === "json") {
|
|
3909
4167
|
options.reportFormat = value;
|
|
3910
4168
|
}
|
|
@@ -3964,6 +4222,18 @@ async function run(argv, cwd) {
|
|
|
3964
4222
|
process.exitCode = exitCode;
|
|
3965
4223
|
}
|
|
3966
4224
|
return;
|
|
4225
|
+
case "sync":
|
|
4226
|
+
{
|
|
4227
|
+
const resolvedRoot = await resolveRoot(options);
|
|
4228
|
+
const exitCode = await runSync({
|
|
4229
|
+
root: resolvedRoot,
|
|
4230
|
+
mode: options.syncMode,
|
|
4231
|
+
format: options.syncFormat,
|
|
4232
|
+
...options.syncOut !== void 0 ? { outPath: options.syncOut } : {}
|
|
4233
|
+
});
|
|
4234
|
+
process.exitCode = exitCode;
|
|
4235
|
+
}
|
|
4236
|
+
return;
|
|
3967
4237
|
default:
|
|
3968
4238
|
error(`Unknown command: ${command}`);
|
|
3969
4239
|
info(usage());
|
|
@@ -3978,6 +4248,7 @@ Commands:
|
|
|
3978
4248
|
validate \u4ED5\u69D8/\u5951\u7D04/\u53C2\u7167\u306E\u691C\u67FB
|
|
3979
4249
|
report \u691C\u8A3C\u7D50\u679C\u3068\u96C6\u8A08\u3092\u51FA\u529B
|
|
3980
4250
|
doctor \u8A2D\u5B9A/\u30D1\u30B9/\u51FA\u529B\u524D\u63D0\u306E\u8A3A\u65AD
|
|
4251
|
+
sync PromptPack \u306E\u5DEE\u5206\u691C\u77E5\u30FB\u540C\u671F\u5019\u88DC\u66F8\u304D\u51FA\u3057
|
|
3981
4252
|
|
|
3982
4253
|
Options:
|
|
3983
4254
|
--root <path> \u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA
|
|
@@ -3987,11 +4258,13 @@ Options:
|
|
|
3987
4258
|
--dry-run \u5909\u66F4\u3092\u884C\u308F\u305A\u8868\u793A\u306E\u307F
|
|
3988
4259
|
--format <text|github> validate \u306E\u51FA\u529B\u5F62\u5F0F
|
|
3989
4260
|
--format <md|json> report \u306E\u51FA\u529B\u5F62\u5F0F
|
|
3990
|
-
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|
|
4261
|
+
--format <text|json> doctor/sync \u306E\u51FA\u529B\u5F62\u5F0F
|
|
3991
4262
|
--strict validate: warning \u4EE5\u4E0A\u3067 exit 1
|
|
3992
4263
|
--fail-on <error|warning|never> validate: \u5931\u6557\u6761\u4EF6
|
|
3993
4264
|
--fail-on <error|warning> doctor: \u5931\u6557\u6761\u4EF6
|
|
4265
|
+
--mode <check|export> sync: \u52D5\u4F5C\u30E2\u30FC\u30C9\uFF08default: check\uFF09
|
|
3994
4266
|
--out <path> report/doctor: \u51FA\u529B\u5148
|
|
4267
|
+
--out <dir> sync: export \u306E\u51FA\u529B\u5148\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\uFF08\u76F8\u5BFE/\u7D76\u5BFE\u3001export \u306E\u307F\uFF09
|
|
3995
4268
|
--in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
|
|
3996
4269
|
--run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
|
|
3997
4270
|
-h, --help \u30D8\u30EB\u30D7\u8868\u793A
|