add-coder 0.3.18 → 0.3.20-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 +2 -0
- package/dist/index.js +460 -153
- package/package.json +3 -3
- package/templates/.add-coder-src-hash.json +12 -11
- package/templates/adapters/qoder/sync-policy.json +1 -1
- package/templates/core/scripts/mcp-server/resources/add-coder-version.ts +4 -3
- package/templates/core/scripts/mcp-server/shared/fs.ts +9 -4
- package/templates/core/scripts/mcp-server/shared/prisma.ts +10 -0
- package/templates/core/scripts/mcp-server/shared/run-command.ts +57 -0
- package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +4 -1
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +4 -5
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +4 -5
- package/templates/core/scripts/mcp-server/tools/gateway/helpers.ts +7 -0
- package/templates/core/templates/01-/346/236/266/346/236/204//343/200/212ADD/345/274/200/345/217/221/345/267/245/344/275/234/350/267/257/345/276/204/344/270/216/346/226/207/346/241/243/345/215/217/345/220/214/350/247/204/350/214/203/343/200/213.md +1 -1
- package/templates/core/vocabulary/add-governance-vocabulary.md +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/caijuehub/strategies/detect.strategy.ts
|
|
@@ -184,7 +184,7 @@ async function selectFiles(projectRoot, files) {
|
|
|
184
184
|
process.stdout.write(renderList(items, selected, scrollIdx, VISIBLE));
|
|
185
185
|
}
|
|
186
186
|
draw();
|
|
187
|
-
return new Promise((
|
|
187
|
+
return new Promise((resolve10) => {
|
|
188
188
|
input.on("keypress", (_char, key) => {
|
|
189
189
|
if (!key) return;
|
|
190
190
|
if (key.name === "up") {
|
|
@@ -225,7 +225,7 @@ async function selectFiles(projectRoot, files) {
|
|
|
225
225
|
if (selected.has(idx)) result.set(relPath, content);
|
|
226
226
|
idx++;
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
resolve10(result);
|
|
229
229
|
});
|
|
230
230
|
});
|
|
231
231
|
}
|
|
@@ -331,13 +331,34 @@ async function writeFiles2(projectRoot, files, options = {}) {
|
|
|
331
331
|
}
|
|
332
332
|
function parseSchemaBlocks(content) {
|
|
333
333
|
const blocks = /* @__PURE__ */ new Map();
|
|
334
|
-
const
|
|
335
|
-
let
|
|
336
|
-
while (
|
|
337
|
-
const
|
|
334
|
+
const lines = content.split("\n");
|
|
335
|
+
let i = 0;
|
|
336
|
+
while (i < lines.length) {
|
|
337
|
+
const head = lines[i].match(/^(model|enum)\s+(\w+)\s*\{/);
|
|
338
|
+
if (!head) {
|
|
339
|
+
i++;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const type = head[1];
|
|
343
|
+
const name = head[2];
|
|
344
|
+
const bodyLines = [lines[i]];
|
|
345
|
+
let depth = 1;
|
|
346
|
+
let j = i + 1;
|
|
347
|
+
while (j < lines.length && depth > 0) {
|
|
348
|
+
const line = lines[j];
|
|
349
|
+
bodyLines.push(line);
|
|
350
|
+
const commentless = line.split("//")[0];
|
|
351
|
+
for (const ch of commentless) {
|
|
352
|
+
if (ch === "{") depth++;
|
|
353
|
+
else if (ch === "}") depth--;
|
|
354
|
+
}
|
|
355
|
+
j++;
|
|
356
|
+
}
|
|
357
|
+
const body = bodyLines.join("\n");
|
|
358
|
+
const inner = bodyLines.slice(1, -1).join("\n");
|
|
338
359
|
let fields = [];
|
|
339
360
|
if (type === "enum") {
|
|
340
|
-
fields = inner.split("\n").map((l) => l.replace(/#.*$/, "").trim()).filter((l) => l.length > 0);
|
|
361
|
+
fields = inner.split("\n").map((l) => l.replace(/\/\/.*$/, "").replace(/#.*$/, "").trim()).filter((l) => l.length > 0);
|
|
341
362
|
} else {
|
|
342
363
|
const fieldRegex = /^\s*(\w+)\s+(\w+(?:\([^)]*\))?(?:\[\])?\??)/gm;
|
|
343
364
|
let fm;
|
|
@@ -345,7 +366,8 @@ function parseSchemaBlocks(content) {
|
|
|
345
366
|
fields.push(`${fm[1]}:${fm[2]}`);
|
|
346
367
|
}
|
|
347
368
|
}
|
|
348
|
-
blocks.set(`${type}:${name}`, { type, name, body
|
|
369
|
+
blocks.set(`${type}:${name}`, { type, name, body, fields });
|
|
370
|
+
i = j;
|
|
349
371
|
}
|
|
350
372
|
return blocks;
|
|
351
373
|
}
|
|
@@ -627,9 +649,38 @@ import { resolve as resolve4, dirname as dirname3 } from "path";
|
|
|
627
649
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
628
650
|
|
|
629
651
|
// src/caijuehub/strategies/prisma.strategy.ts
|
|
630
|
-
import { spawnSync } from "child_process";
|
|
631
652
|
import { copyFileSync, existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
632
653
|
import { resolve as resolve3 } from "path";
|
|
654
|
+
|
|
655
|
+
// src/lib/run-command.ts
|
|
656
|
+
import { spawnSync } from "child_process";
|
|
657
|
+
var CMD_EXTENSIONS = ["npm", "npx", "pnpm", "git"];
|
|
658
|
+
function runCommand(cmd, args, opts = {}) {
|
|
659
|
+
const platform = opts.platform ?? process.platform;
|
|
660
|
+
const needsCmdExt = platform === "win32" && CMD_EXTENSIONS.includes(cmd) && !opts.shell;
|
|
661
|
+
const effectiveCmd = needsCmdExt ? `${cmd}.cmd` : cmd;
|
|
662
|
+
const r = spawnSync(effectiveCmd, args, {
|
|
663
|
+
cwd: opts.cwd,
|
|
664
|
+
env: opts.env,
|
|
665
|
+
input: opts.input,
|
|
666
|
+
timeout: opts.timeout,
|
|
667
|
+
encoding: "utf-8",
|
|
668
|
+
shell: opts.shell,
|
|
669
|
+
stdio: opts.stdio ?? (opts.input !== void 0 ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"])
|
|
670
|
+
});
|
|
671
|
+
if (r.error) {
|
|
672
|
+
throw new Error(`\u547D\u4EE4\u4E0D\u53EF\u7528: ${cmd}\uFF08\u5E73\u53F0: ${platform}\uFF0C${r.error.message}\uFF09`);
|
|
673
|
+
}
|
|
674
|
+
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
675
|
+
}
|
|
676
|
+
function commandExists(cmd, platform) {
|
|
677
|
+
const p = platform ?? process.platform;
|
|
678
|
+
const probe = p === "win32" ? "where" : "which";
|
|
679
|
+
const r = spawnSync(probe, [cmd], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
680
|
+
return !r.error && r.status === 0;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/caijuehub/strategies/prisma.strategy.ts
|
|
633
684
|
var PRISMA_CONFIG = {
|
|
634
685
|
onMissing: "ask",
|
|
635
686
|
onExistingAddPrisma: "ask",
|
|
@@ -657,12 +708,10 @@ function ensurePrismaConfig(projectRoot) {
|
|
|
657
708
|
].join("\n") + "\n", "utf-8");
|
|
658
709
|
}
|
|
659
710
|
function backupAddTables(projectRoot) {
|
|
660
|
-
|
|
661
|
-
if (pgDump.status !== 0) return null;
|
|
711
|
+
if (!commandExists("pg_dump")) return null;
|
|
662
712
|
const bak = resolve3(projectRoot, `add-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.sql`);
|
|
663
|
-
const r =
|
|
713
|
+
const r = runCommand("pg_dump", ["--table=AddUser", "--table=DevOperation", "--table=AuditLog", "--if-exists"], {
|
|
664
714
|
cwd: projectRoot,
|
|
665
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
666
715
|
timeout: 3e4
|
|
667
716
|
});
|
|
668
717
|
if (r.stdout.length > 0) {
|
|
@@ -673,16 +722,18 @@ function backupAddTables(projectRoot) {
|
|
|
673
722
|
return null;
|
|
674
723
|
}
|
|
675
724
|
function runPrismaInit(projectRoot, provider, schemaPath) {
|
|
676
|
-
console.log("\u6267\u884C
|
|
725
|
+
console.log("\u6267\u884C prisma init ...");
|
|
677
726
|
const pm = detectPm(projectRoot);
|
|
678
|
-
const initArgs = pm === "pnpm" ? ["dlx", "prisma", "init", "--datasource-provider", provider] : ["prisma", "init", "--datasource-provider", provider];
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
727
|
+
const initArgs = pm === "pnpm" ? ["dlx", "prisma", "init", "--datasource-provider", provider] : ["exec", "prisma", "--", "init", "--datasource-provider", provider];
|
|
728
|
+
let initResult;
|
|
729
|
+
try {
|
|
730
|
+
initResult = runCommand(pm, initArgs, { cwd: projectRoot });
|
|
731
|
+
} catch (e) {
|
|
732
|
+
console.error(`\u2717 prisma init \u65E0\u6CD5\u6267\u884C: ${e instanceof Error ? e.message : String(e)}`);
|
|
733
|
+
initResult = { status: null, stdout: "", stderr: "" };
|
|
734
|
+
}
|
|
684
735
|
if (initResult.status !== 0 || !existsSync7(schemaPath)) {
|
|
685
|
-
console.
|
|
736
|
+
console.error(`\u26A0\uFE0F prisma init \u672A\u5B8C\u6210\uFF08\u9000\u51FA\u7801: ${initResult.status}\uFF09\uFF0C\u56DE\u9000\u624B\u52A8\u521B\u5EFA schema.prisma\u2014\u2014db push \u5C06\u9A8C\u8BC1\u5176\u53EF\u7528\u6027`);
|
|
686
737
|
const prismaDir = resolve3(projectRoot, "prisma");
|
|
687
738
|
if (!existsSync7(prismaDir)) mkdirSync3(prismaDir, { recursive: true });
|
|
688
739
|
const content = `generator client {
|
|
@@ -722,6 +773,28 @@ function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
|
|
|
722
773
|
}
|
|
723
774
|
copyFileSync(addPrismaTemplate, destPath);
|
|
724
775
|
console.log("\u5DF2\u590D\u5236 add.prisma");
|
|
776
|
+
patchGeneratorOutput(schemaPath);
|
|
777
|
+
}
|
|
778
|
+
function patchGeneratorOutput(schemaPath) {
|
|
779
|
+
if (!existsSync7(schemaPath)) return;
|
|
780
|
+
let content = readFileSync5(schemaPath, "utf-8");
|
|
781
|
+
const genBlock = content.match(/generator\s+\w+\s*\{[\s\S]*?\}/);
|
|
782
|
+
if (!genBlock) {
|
|
783
|
+
content += `
|
|
784
|
+
generator client {
|
|
785
|
+
provider = "prisma-client-js"
|
|
786
|
+
output = "../src/generated/prisma"
|
|
787
|
+
}
|
|
788
|
+
`;
|
|
789
|
+
writeFileSync3(schemaPath, content, "utf-8");
|
|
790
|
+
console.log("\u5DF2\u8FFD\u52A0 generator client\uFF08\u542B output \u2192 src/generated/prisma\uFF09");
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (genBlock[0].includes("output")) return;
|
|
794
|
+
const patched = genBlock[0].replace(/\}\s*$/, ` output = "../src/generated/prisma"
|
|
795
|
+
}`);
|
|
796
|
+
writeFileSync3(schemaPath, content.replace(genBlock[0], patched), "utf-8");
|
|
797
|
+
console.log("\u5DF2\u6CE8\u5165 generator output \u2192 src/generated/prisma");
|
|
725
798
|
}
|
|
726
799
|
async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
727
800
|
const C = PRISMA_CONFIG;
|
|
@@ -786,10 +859,10 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
786
859
|
ensurePrismaConfig(projectRoot);
|
|
787
860
|
backupAddTables(projectRoot);
|
|
788
861
|
const pm = detectPm(projectRoot);
|
|
789
|
-
const args = pm === "pnpm" ? ["dlx", "prisma", "db", "push"] : ["prisma", "db", "push"];
|
|
862
|
+
const args = pm === "pnpm" ? ["dlx", "prisma", "db", "push"] : ["exec", "prisma", "--", "db", "push"];
|
|
790
863
|
if (C.schemaArg) args.push(C.schemaArg);
|
|
791
864
|
console.log(`\u6267\u884C ${pm} ${args.join(" ")} ...`);
|
|
792
|
-
const r =
|
|
865
|
+
const r = runCommand(pm, args, { cwd: projectRoot });
|
|
793
866
|
if (r.status !== 0) throw new Error(`prisma db push \u9000\u51FA\u7801: ${r.status}`);
|
|
794
867
|
} catch (err) {
|
|
795
868
|
if (C.onMigrateFail === "keep") {
|
|
@@ -805,8 +878,14 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
805
878
|
}
|
|
806
879
|
if (C.autoGenerate) {
|
|
807
880
|
const pm = detectPm(projectRoot);
|
|
881
|
+
const genArgs = pm === "pnpm" ? ["dlx", "prisma", "generate"] : ["exec", "prisma", "--", "generate"];
|
|
808
882
|
console.log("\u6267\u884C prisma generate ...");
|
|
809
|
-
|
|
883
|
+
const g = runCommand(pm, genArgs, { cwd: projectRoot });
|
|
884
|
+
if (g.status !== 0) {
|
|
885
|
+
const detail = g.stderr.trim().split("\n").slice(0, 5).join("\n");
|
|
886
|
+
throw new Error(`prisma generate \u9000\u51FA\u7801: ${g.status}${detail ? `
|
|
887
|
+
${detail}` : ""}`);
|
|
888
|
+
}
|
|
810
889
|
}
|
|
811
890
|
console.log("ADD \u6CBB\u7406\u6A21\u578B\u5DF2\u5C31\u7EEA");
|
|
812
891
|
return true;
|
|
@@ -821,11 +900,88 @@ async function injectPrisma2(projectRoot, options = {}) {
|
|
|
821
900
|
}
|
|
822
901
|
|
|
823
902
|
// src/cli/commands/init.ts
|
|
824
|
-
import { readFileSync as
|
|
903
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
825
904
|
import { createHash } from "crypto";
|
|
826
|
-
import { resolve as
|
|
827
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
905
|
+
import { resolve as resolve6 } from "path";
|
|
828
906
|
import { createConnection } from "net";
|
|
907
|
+
|
|
908
|
+
// src/lib/model-predownload.ts
|
|
909
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
910
|
+
import { join as join5, resolve as resolve5 } from "path";
|
|
911
|
+
import { homedir } from "os";
|
|
912
|
+
import { parse as parse2 } from "smol-toml";
|
|
913
|
+
var DEFAULT_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
914
|
+
var TOML_CANDIDATES = [
|
|
915
|
+
resolve5(import.meta.dirname, "caijuehub/dps-scoring-rules.toml"),
|
|
916
|
+
resolve5(import.meta.dirname, "../caijuehub/dps-scoring-rules.toml")
|
|
917
|
+
];
|
|
918
|
+
function resolveEmbeddingModel() {
|
|
919
|
+
const tomlPath = TOML_CANDIDATES.find((p) => existsSync8(p));
|
|
920
|
+
if (!tomlPath) {
|
|
921
|
+
throw new Error(
|
|
922
|
+
`dps-scoring-rules.toml \u672A\u627E\u5230\uFF08\u671F\u671B\u8DEF\u5F84: ${TOML_CANDIDATES.join(" \u6216 ")}\uFF09`
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
const cfg = parse2(readFileSync6(tomlPath, "utf-8"));
|
|
926
|
+
const model = cfg.embedding?.model;
|
|
927
|
+
if (typeof model !== "string" || model.length === 0) {
|
|
928
|
+
throw new Error(`dps-scoring-rules.toml [embedding] model \u672A\u914D\u7F6E\uFF08${tomlPath}\uFF09`);
|
|
929
|
+
}
|
|
930
|
+
return model;
|
|
931
|
+
}
|
|
932
|
+
function resolveCacheDir() {
|
|
933
|
+
const hubCache = process.env.HF_HUB_CACHE;
|
|
934
|
+
if (hubCache) return hubCache;
|
|
935
|
+
const home = process.env.HF_HOME || join5(homedir(), ".cache", "huggingface");
|
|
936
|
+
return join5(home, "hub");
|
|
937
|
+
}
|
|
938
|
+
function modelCacheName(model) {
|
|
939
|
+
const parts = model.split("/");
|
|
940
|
+
const org = parts.length > 1 ? parts[0] : "models";
|
|
941
|
+
const name = parts[parts.length - 1];
|
|
942
|
+
return `models--${org}--${name}`;
|
|
943
|
+
}
|
|
944
|
+
function isModelCached(model) {
|
|
945
|
+
const cacheDir = resolveCacheDir();
|
|
946
|
+
return existsSync8(join5(cacheDir, modelCacheName(model), "snapshots"));
|
|
947
|
+
}
|
|
948
|
+
async function ensureEmbeddingModel(options) {
|
|
949
|
+
const force = options?.force ?? false;
|
|
950
|
+
const skip = options?.skip ?? false;
|
|
951
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_DOWNLOAD_TIMEOUT_MS;
|
|
952
|
+
if (skip) {
|
|
953
|
+
return { status: "skipped", model: "", cacheDir: "" };
|
|
954
|
+
}
|
|
955
|
+
const model = resolveEmbeddingModel();
|
|
956
|
+
const { pipeline, env } = await import("@huggingface/transformers");
|
|
957
|
+
env.cacheDir = resolveCacheDir();
|
|
958
|
+
const cacheDir = env.cacheDir;
|
|
959
|
+
const snapshotsDir = join5(cacheDir, modelCacheName(model), "snapshots");
|
|
960
|
+
if (!force && existsSync8(snapshotsDir)) {
|
|
961
|
+
return { status: "already-cached", model, cacheDir };
|
|
962
|
+
}
|
|
963
|
+
env.remoteHost = "https://hf-mirror.com";
|
|
964
|
+
env.remotePathTemplate = "{model}/resolve/{revision}/";
|
|
965
|
+
const run = (async () => {
|
|
966
|
+
const extractor = await pipeline("feature-extraction", model);
|
|
967
|
+
await extractor(["\u6D4B\u8BD5"], { pooling: "mean", normalize: true });
|
|
968
|
+
})();
|
|
969
|
+
let timer;
|
|
970
|
+
const timeout = new Promise((_, reject) => {
|
|
971
|
+
timer = setTimeout(
|
|
972
|
+
() => reject(new Error(`\u6A21\u578B\u4E0B\u8F7D\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`)),
|
|
973
|
+
timeoutMs
|
|
974
|
+
);
|
|
975
|
+
});
|
|
976
|
+
try {
|
|
977
|
+
await Promise.race([run, timeout]);
|
|
978
|
+
} finally {
|
|
979
|
+
if (timer) clearTimeout(timer);
|
|
980
|
+
}
|
|
981
|
+
return { status: "downloaded", model, cacheDir };
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/cli/commands/init.ts
|
|
829
985
|
var ADAPTER_RENDERERS = {
|
|
830
986
|
claude: renderAdapter,
|
|
831
987
|
qoder: renderAdapter2,
|
|
@@ -844,9 +1000,17 @@ async function initCommand(options) {
|
|
|
844
1000
|
console.log(`[dry-run] \u5C06\u5199\u5165 ${ctx.magicDir}/stack.json \u2192 ${ctx.stack}`);
|
|
845
1001
|
}
|
|
846
1002
|
const result = await renderAndWrite(ctx);
|
|
847
|
-
await deployDatabase(ctx);
|
|
1003
|
+
const dbFail = await deployDatabase(ctx);
|
|
848
1004
|
deployDocs(ctx);
|
|
849
|
-
finalize(ctx, result);
|
|
1005
|
+
finalize(ctx, result, dbFail);
|
|
1006
|
+
if (!options.dryRun) {
|
|
1007
|
+
try {
|
|
1008
|
+
const r = await ensureEmbeddingModel({ skip: options.skipModel });
|
|
1009
|
+
console.log(`\u6A21\u578B\u9884\u4E0B\u8F7D: ${r.status}${r.model ? ` (${r.model})` : ""}`);
|
|
1010
|
+
} catch (e) {
|
|
1011
|
+
console.warn(`\u26A0\uFE0F \u6A21\u578B\u9884\u4E0B\u8F7D\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u4E3B\u6D41\u7A0B\uFF0C\u9996\u6B21 DPS \u8C03\u7528\u4F1A\u81EA\u52A8\u8865\u4E0B\u8F7D\uFF09: ${e instanceof Error ? e.message : String(e)}`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
850
1014
|
}
|
|
851
1015
|
async function resolveAdapter(projectRoot, specified) {
|
|
852
1016
|
if (specified) {
|
|
@@ -901,27 +1065,31 @@ function portInUse(port) {
|
|
|
901
1065
|
}
|
|
902
1066
|
function hasPgIsready() {
|
|
903
1067
|
try {
|
|
904
|
-
const containers =
|
|
1068
|
+
const containers = runCommand("podman", ["ps", "--filter", "publish=5433", "--format", "{{.Names}}"], { timeout: 3e3 });
|
|
905
1069
|
const name = containers.stdout.toString().trim().split("\n")[0];
|
|
906
|
-
if (name &&
|
|
1070
|
+
if (name && runCommand("podman", ["exec", name, "pg_isready", "--version"], { timeout: 2e3 }).status === 0) return true;
|
|
907
1071
|
} catch {
|
|
908
1072
|
}
|
|
909
|
-
return
|
|
1073
|
+
return commandExists("pg_isready");
|
|
910
1074
|
}
|
|
911
1075
|
function testPostgresConnection(port, user, password, dbName) {
|
|
912
1076
|
if (!hasPgIsready()) {
|
|
913
1077
|
console.log(" \u26A0\uFE0F \u65E0\u6CD5\u9A8C\u8BC1\u51ED\u636E\uFF08\u5BB9\u5668\u672A\u8FD0\u884C\u4E14 pg_isready \u672A\u5B89\u88C5\uFF09\uFF0C\u4FE1\u4EFB\u8F93\u5165");
|
|
914
1078
|
return true;
|
|
915
1079
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1080
|
+
try {
|
|
1081
|
+
const containers = runCommand("podman", ["ps", "--filter", "publish=5433", "--format", "{{.Names}}"], { timeout: 3e3 });
|
|
1082
|
+
const containerName = containers.stdout.toString().trim().split("\n")[0];
|
|
1083
|
+
const args = containerName ? ["exec", containerName, "pg_isready", "-U", user, "-d", dbName] : ["-h", "localhost", "-p", port, "-U", user, "-d", dbName];
|
|
1084
|
+
const cmd = containerName ? "podman" : "pg_isready";
|
|
1085
|
+
const r = runCommand(cmd, args, {
|
|
1086
|
+
timeout: 5e3,
|
|
1087
|
+
env: containerName ? void 0 : { ...process.env, PGPASSWORD: password }
|
|
1088
|
+
});
|
|
1089
|
+
return r.status === 0;
|
|
1090
|
+
} catch {
|
|
1091
|
+
return false;
|
|
1092
|
+
}
|
|
925
1093
|
}
|
|
926
1094
|
async function resolveDbCredentials(force) {
|
|
927
1095
|
const d = { user: "admin", password: "change-me-in-production", port: "5433" };
|
|
@@ -984,8 +1152,8 @@ networks:
|
|
|
984
1152
|
`;
|
|
985
1153
|
}
|
|
986
1154
|
function writeSqliteExportScript(projectRoot, dryRun) {
|
|
987
|
-
const scriptsDir =
|
|
988
|
-
const scriptPath =
|
|
1155
|
+
const scriptsDir = resolve6(projectRoot, "scripts");
|
|
1156
|
+
const scriptPath = resolve6(scriptsDir, "export-db.ts");
|
|
989
1157
|
const content = `import { PrismaClient } from "@prisma/client";
|
|
990
1158
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
991
1159
|
import { resolve } from "path";
|
|
@@ -1009,18 +1177,18 @@ main().catch((e) => { console.error(e); process.exit(1); });
|
|
|
1009
1177
|
console.log(`[dry-run] \u5C06\u5199\u5165 ${scriptPath}`);
|
|
1010
1178
|
return;
|
|
1011
1179
|
}
|
|
1012
|
-
if (!
|
|
1180
|
+
if (!existsSync9(scriptsDir)) mkdirSync4(scriptsDir, { recursive: true });
|
|
1013
1181
|
writeFileSync4(scriptPath, content, "utf-8");
|
|
1014
1182
|
console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
|
|
1015
1183
|
}
|
|
1016
1184
|
function injectDbExportScript(projectRoot, dryRun) {
|
|
1017
|
-
const pkgPath =
|
|
1018
|
-
if (!
|
|
1185
|
+
const pkgPath = resolve6(projectRoot, "package.json");
|
|
1186
|
+
if (!existsSync9(pkgPath)) return;
|
|
1019
1187
|
if (dryRun) {
|
|
1020
1188
|
console.log("[dry-run] \u5C06\u6CE8\u5165 db:export");
|
|
1021
1189
|
return;
|
|
1022
1190
|
}
|
|
1023
|
-
const pkg = JSON.parse(
|
|
1191
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1024
1192
|
if (!pkg.scripts) pkg.scripts = {};
|
|
1025
1193
|
if (!pkg.scripts["db:export"]) {
|
|
1026
1194
|
pkg.scripts["db:export"] = "npx tsx scripts/export-db.ts";
|
|
@@ -1067,15 +1235,15 @@ function writeComposeEnv(ctx) {
|
|
|
1067
1235
|
if (db.engine !== "postgresql" || !db.container || db.container === "manual") return;
|
|
1068
1236
|
if (!db.reuseExisting) {
|
|
1069
1237
|
const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
|
|
1070
|
-
const composePath =
|
|
1071
|
-
if (!options.dryRun && (!
|
|
1238
|
+
const composePath = resolve6(projectRoot, composeName);
|
|
1239
|
+
if (!options.dryRun && (!existsSync9(composePath) || options.force)) {
|
|
1072
1240
|
writeFileSync4(composePath, composeContent(config.projectName || "add-project"), "utf-8");
|
|
1073
1241
|
console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
|
|
1074
1242
|
}
|
|
1075
1243
|
}
|
|
1076
|
-
const devEnvPath =
|
|
1077
|
-
if (!options.dryRun &&
|
|
1078
|
-
const existing =
|
|
1244
|
+
const devEnvPath = resolve6(projectRoot, ".env.development");
|
|
1245
|
+
if (!options.dryRun && existsSync9(devEnvPath)) {
|
|
1246
|
+
const existing = readFileSync7(devEnvPath, "utf-8");
|
|
1079
1247
|
if (!/^DATABASE_USER=/m.test(existing)) {
|
|
1080
1248
|
writeFileSync4(devEnvPath, existing + `
|
|
1081
1249
|
DATABASE_USER=${db.user || "admin"}
|
|
@@ -1114,8 +1282,8 @@ async function renderAndWrite(ctx) {
|
|
|
1114
1282
|
console.log(`claude adapter (via Agent Host): ${claudeFiles.size} \u6587\u4EF6`);
|
|
1115
1283
|
}
|
|
1116
1284
|
for (const d of [".add", magicDir]) {
|
|
1117
|
-
const reviewsDir =
|
|
1118
|
-
if (!
|
|
1285
|
+
const reviewsDir = resolve6(projectRoot, d, "reviews");
|
|
1286
|
+
if (!existsSync9(reviewsDir)) {
|
|
1119
1287
|
if (dry) {
|
|
1120
1288
|
console.log(`[dry-run] \u5C06\u521B\u5EFA ${reviewsDir}/`);
|
|
1121
1289
|
} else {
|
|
@@ -1127,16 +1295,16 @@ async function renderAndWrite(ctx) {
|
|
|
1127
1295
|
const hashMap = {};
|
|
1128
1296
|
let npmVer = "";
|
|
1129
1297
|
try {
|
|
1130
|
-
npmVer = JSON.parse(
|
|
1298
|
+
npmVer = JSON.parse(readFileSync7(resolve6(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
|
|
1131
1299
|
} catch {
|
|
1132
1300
|
}
|
|
1133
1301
|
for (const [rp, c] of allFiles) {
|
|
1134
1302
|
hashMap[rp] = createHash("sha256").update(c).digest("hex").slice(0, 8);
|
|
1135
1303
|
}
|
|
1136
|
-
const hashOut =
|
|
1304
|
+
const hashOut = resolve6(projectRoot, magicDir, ".add-coder-hash.json");
|
|
1137
1305
|
writeFileSync4(hashOut, JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
|
|
1138
1306
|
if (npmVer) {
|
|
1139
|
-
writeFileSync4(
|
|
1307
|
+
writeFileSync4(resolve6(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
|
|
1140
1308
|
}
|
|
1141
1309
|
console.log(`hash: ${Object.keys(hashMap).length} entries \u2192 ${magicDir}/.add-coder-hash.json`);
|
|
1142
1310
|
}
|
|
@@ -1144,22 +1312,37 @@ async function renderAndWrite(ctx) {
|
|
|
1144
1312
|
}
|
|
1145
1313
|
async function deployDatabase(ctx) {
|
|
1146
1314
|
const { projectRoot, options, magicDir, config, db } = ctx;
|
|
1147
|
-
if (options.dryRun) return;
|
|
1315
|
+
if (options.dryRun) return null;
|
|
1316
|
+
let fail = null;
|
|
1148
1317
|
if (db.engine === "postgresql" && db.container && db.container !== "manual") {
|
|
1149
|
-
const dbScript =
|
|
1318
|
+
const dbScript = resolve6(projectRoot, magicDir, "scripts", "db-ensure.sh");
|
|
1150
1319
|
const dbEnv = { ...process.env, DATABASE_USER: db.user, DATABASE_PASSWORD: db.password, DATABASE_PORT: db.port, PROJECT_NAME: config.projectName };
|
|
1151
1320
|
const mode = db.reuseExisting ? "manual" : db.container;
|
|
1152
1321
|
console.log(db.reuseExisting ? "\u590D\u7528\u5DF2\u6709 PostgreSQL ..." : `\u90E8\u7F72\u6570\u636E\u5E93 (${db.container}) ...`);
|
|
1153
|
-
|
|
1322
|
+
try {
|
|
1323
|
+
const bashRun = runCommand("bash", [dbScript, "postgresql", mode, "--migrate"], { cwd: projectRoot, env: dbEnv, stdio: "inherit" });
|
|
1324
|
+
if (bashRun.status !== 0) fail = `db-ensure.sh \u9000\u51FA\u7801: ${bashRun.status}${bashRun.stderr ? `
|
|
1325
|
+
${bashRun.stderr.trim().split("\n").slice(0, 5).join("\n")}` : ""}`;
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
fail = e instanceof Error ? e.message : String(e);
|
|
1328
|
+
}
|
|
1154
1329
|
try {
|
|
1155
1330
|
await injectPrisma2(projectRoot, { force: !!options.force });
|
|
1156
1331
|
} catch (e) {
|
|
1157
|
-
|
|
1332
|
+
fail = `Prisma \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
|
|
1158
1333
|
}
|
|
1159
1334
|
}
|
|
1160
1335
|
if (db.engine === "postgresql" && db.container === "manual") {
|
|
1161
|
-
const dbScript =
|
|
1162
|
-
if (
|
|
1336
|
+
const dbScript = resolve6(projectRoot, magicDir, "scripts", "db-ensure.sh");
|
|
1337
|
+
if (existsSync9(dbScript)) {
|
|
1338
|
+
try {
|
|
1339
|
+
const bashRun = runCommand("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
|
|
1340
|
+
if (bashRun.status !== 0) fail = `db-ensure.sh \u9000\u51FA\u7801: ${bashRun.status}${bashRun.stderr ? `
|
|
1341
|
+
${bashRun.stderr.trim().split("\n").slice(0, 5).join("\n")}` : ""}`;
|
|
1342
|
+
} catch (e) {
|
|
1343
|
+
fail = e instanceof Error ? e.message : String(e);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1163
1346
|
console.log([
|
|
1164
1347
|
"",
|
|
1165
1348
|
"\u2501".repeat(30),
|
|
@@ -1187,25 +1370,26 @@ async function deployDatabase(ctx) {
|
|
|
1187
1370
|
try {
|
|
1188
1371
|
await injectPrisma2(projectRoot, { force: !!options.force, datasource: "sqlite" });
|
|
1189
1372
|
} catch (e) {
|
|
1190
|
-
|
|
1373
|
+
fail = `SQLite \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
|
|
1191
1374
|
}
|
|
1192
1375
|
}
|
|
1376
|
+
return fail;
|
|
1193
1377
|
}
|
|
1194
1378
|
function deployDocs(ctx) {
|
|
1195
1379
|
const { projectRoot, options, config } = ctx;
|
|
1196
1380
|
if (options.dryRun) return;
|
|
1197
1381
|
const pn = config.projectName || "add-project";
|
|
1198
|
-
const docsBase =
|
|
1199
|
-
const groundingSrc =
|
|
1382
|
+
const docsBase = resolve6(projectRoot, "docs", pn, "knowledge");
|
|
1383
|
+
const groundingSrc = resolve6(import.meta.dirname, "../templates/core/templates");
|
|
1200
1384
|
for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
|
|
1201
|
-
const srcDir =
|
|
1202
|
-
const destDir =
|
|
1203
|
-
if (!
|
|
1204
|
-
if (!
|
|
1385
|
+
const srcDir = resolve6(groundingSrc, d);
|
|
1386
|
+
const destDir = resolve6(docsBase, d);
|
|
1387
|
+
if (!existsSync9(destDir)) mkdirSync4(destDir, { recursive: true });
|
|
1388
|
+
if (!existsSync9(srcDir)) continue;
|
|
1205
1389
|
for (const f of readdirSync2(srcDir)) {
|
|
1206
|
-
const src =
|
|
1207
|
-
const dest =
|
|
1208
|
-
if (
|
|
1390
|
+
const src = resolve6(srcDir, f);
|
|
1391
|
+
const dest = resolve6(destDir, f);
|
|
1392
|
+
if (existsSync9(dest)) continue;
|
|
1209
1393
|
try {
|
|
1210
1394
|
copyFileSync2(src, dest);
|
|
1211
1395
|
} catch {
|
|
@@ -1213,30 +1397,51 @@ function deployDocs(ctx) {
|
|
|
1213
1397
|
}
|
|
1214
1398
|
}
|
|
1215
1399
|
}
|
|
1216
|
-
function finalize(ctx, result) {
|
|
1400
|
+
function finalize(ctx, result, dbFail) {
|
|
1217
1401
|
const { projectRoot, options, db } = ctx;
|
|
1218
|
-
|
|
1402
|
+
if (options.dryRun) {
|
|
1403
|
+
console.log(`
|
|
1219
1404
|
\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}, \u8986\u76D6 ${result.overwritten}`);
|
|
1220
|
-
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1221
1407
|
if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
|
|
1222
|
-
const pkg = JSON.parse(
|
|
1408
|
+
const pkg = JSON.parse(readFileSync7(resolve6(import.meta.dirname, "../package.json"), "utf-8"));
|
|
1223
1409
|
const peerNames = Object.keys(pkg.peerDependencies || {});
|
|
1224
1410
|
if (peerNames.length > 0) {
|
|
1225
1411
|
console.log(`
|
|
1226
1412
|
\u5B89\u88C5 peer \u4F9D\u8D56 (${peerNames.join(" ")}) ...`);
|
|
1227
1413
|
const pm = detectPm(projectRoot);
|
|
1228
|
-
|
|
1414
|
+
const installArgs = pm === "pnpm" ? ["add", ...peerNames] : ["install", ...peerNames];
|
|
1415
|
+
try {
|
|
1416
|
+
const ir = runCommand(pm, installArgs, { cwd: projectRoot });
|
|
1417
|
+
if (ir.status !== 0) console.warn(`\u26A0\uFE0F peer \u4F9D\u8D56\u5B89\u88C5\u5931\u8D25\uFF08\u9000\u51FA\u7801: ${ir.status}\uFF09\uFF0C\u540E\u7EED MCP \u542F\u52A8\u53EF\u80FD\u62A5\u9519`);
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
console.warn(`\u26A0\uFE0F peer \u4F9D\u8D56\u5B89\u88C5\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
1420
|
+
}
|
|
1229
1421
|
}
|
|
1230
1422
|
if (db.engine !== "manual" && (db.engine !== "postgresql" || db.container !== "manual")) {
|
|
1231
1423
|
console.log("\u63D0\u793A: \u91CD\u542F IDE \u4EE5\u52A0\u8F7D hook \u914D\u7F6E");
|
|
1232
1424
|
}
|
|
1425
|
+
if (dbFail) {
|
|
1426
|
+
console.error(`
|
|
1427
|
+
\u2717 \u6CBB\u7406\u6A21\u578B\u672A\u5C31\u7EEA: ${dbFail}`);
|
|
1428
|
+
console.error(" \u8BF7\u6309\u9519\u8BEF\u63D0\u793A\u4FEE\u590D\u540E\u91CD\u65B0\u8FD0\u884C add-coder init\u3002");
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
console.log(`
|
|
1432
|
+
\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}, \u8986\u76D6 ${result.overwritten}`);
|
|
1233
1433
|
}
|
|
1234
1434
|
|
|
1235
1435
|
// src/cli/commands/sync.ts
|
|
1236
|
-
import { existsSync as
|
|
1237
|
-
import { resolve as
|
|
1436
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
|
|
1437
|
+
import { resolve as resolve7, dirname as dirname4 } from "path";
|
|
1238
1438
|
import { createHash as createHash2 } from "crypto";
|
|
1239
1439
|
|
|
1440
|
+
// src/lib/path-normalize.ts
|
|
1441
|
+
function normalizeRelPath(p) {
|
|
1442
|
+
return p.replaceAll("\\", "/");
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1240
1445
|
// src/caijuehub/strategies/sync.strategy.ts
|
|
1241
1446
|
var SYNC_CONFIG = {
|
|
1242
1447
|
PATCH_GUARD: [/[/]plans[/]/, /[/]specs[/]/, /[/]reviews[/]/, /[/]rules[/]profiles[/]/],
|
|
@@ -1280,32 +1485,65 @@ function resolveAdapter2(projectRoot, specified) {
|
|
|
1280
1485
|
return "qoder";
|
|
1281
1486
|
}
|
|
1282
1487
|
function isUserData(p) {
|
|
1283
|
-
return SYNC_CONFIG.PATCH_GUARD.some((r) => r.test(p));
|
|
1488
|
+
return SYNC_CONFIG.PATCH_GUARD.some((r) => r.test(normalizeRelPath(p)));
|
|
1284
1489
|
}
|
|
1285
1490
|
function hash8(c) {
|
|
1286
1491
|
return createHash2("sha256").update(c).digest("hex").slice(0, SYNC_CONFIG.HASH_HEX_LENGTH);
|
|
1287
1492
|
}
|
|
1288
1493
|
function loadHashFile(root, magic) {
|
|
1289
1494
|
try {
|
|
1290
|
-
|
|
1495
|
+
const raw = JSON.parse(readFileSync8(resolve7(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), "utf-8"));
|
|
1496
|
+
const normalized = {};
|
|
1497
|
+
for (const [k, v] of Object.entries(raw)) normalized[normalizeRelPath(k)] = v;
|
|
1498
|
+
return normalized;
|
|
1291
1499
|
} catch {
|
|
1292
1500
|
return {};
|
|
1293
1501
|
}
|
|
1294
1502
|
}
|
|
1295
1503
|
function loadVersionFile(root, magic) {
|
|
1296
1504
|
try {
|
|
1297
|
-
return
|
|
1505
|
+
return readFileSync8(resolve7(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
|
|
1298
1506
|
} catch {
|
|
1299
1507
|
return "";
|
|
1300
1508
|
}
|
|
1301
1509
|
}
|
|
1302
1510
|
function saveVersionFile(root, magic, version2) {
|
|
1303
|
-
writeFileSync5(
|
|
1511
|
+
writeFileSync5(resolve7(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
|
|
1304
1512
|
}
|
|
1305
1513
|
function saveHashFile(root, magic, files) {
|
|
1306
1514
|
const m = {};
|
|
1307
|
-
for (const [p, c] of files) m[p] =
|
|
1308
|
-
writeFileSync5(
|
|
1515
|
+
for (const [p, c] of files) m[p] = c;
|
|
1516
|
+
writeFileSync5(resolve7(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), JSON.stringify(m, null, 2) + "\n", "utf-8");
|
|
1517
|
+
}
|
|
1518
|
+
function mergeFullHash(outHash, candidates, readDiskHash) {
|
|
1519
|
+
const finalHash = /* @__PURE__ */ new Map();
|
|
1520
|
+
for (const [k, v] of Object.entries(outHash)) finalHash.set(k, v);
|
|
1521
|
+
for (const { relPath, absPath } of candidates) {
|
|
1522
|
+
const key = normalizeRelPath(relPath);
|
|
1523
|
+
const h = readDiskHash(absPath);
|
|
1524
|
+
if (h !== null) finalHash.set(key, h);
|
|
1525
|
+
}
|
|
1526
|
+
return finalHash;
|
|
1527
|
+
}
|
|
1528
|
+
async function maybeModelDownload(options) {
|
|
1529
|
+
let model;
|
|
1530
|
+
try {
|
|
1531
|
+
model = resolveEmbeddingModel();
|
|
1532
|
+
} catch (e) {
|
|
1533
|
+
console.warn(`\u26A0\uFE0F \u6A21\u578B\u914D\u7F6E\u7F3A\u5931\uFF08\u8DF3\u8FC7\u68C0\u6D4B\uFF09: ${e instanceof Error ? e.message : String(e)}`);
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
if (isModelCached(model)) return;
|
|
1537
|
+
if (options.model) {
|
|
1538
|
+
try {
|
|
1539
|
+
const r = await ensureEmbeddingModel();
|
|
1540
|
+
console.log(`\u6A21\u578B\u9884\u4E0B\u8F7D: ${r.status} (${r.model})`);
|
|
1541
|
+
} catch (e) {
|
|
1542
|
+
console.warn(`\u26A0\uFE0F \u6A21\u578B\u9884\u4E0B\u8F7D\u5931\u8D25\uFF08\u9996\u6B21 DPS \u8C03\u7528\u4F1A\u81EA\u52A8\u8865\u4E0B\u8F7D\uFF09: ${e instanceof Error ? e.message : String(e)}`);
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
console.log(`\u6A21\u578B\u672A\u9884\u4E0B\u8F7D: \u8FD0\u884C \`add-coder model:download\` \u63D0\u524D\u4E0B\u8F7D\uFF08\u9996\u6B21 DPS \u8C03\u7528\u4E5F\u4F1A\u81EA\u52A8\u4E0B\u8F7D\uFF09`);
|
|
1546
|
+
}
|
|
1309
1547
|
}
|
|
1310
1548
|
async function syncCommand(options = {}) {
|
|
1311
1549
|
const projectRoot = process.cwd();
|
|
@@ -1348,10 +1586,10 @@ async function syncCommand(options = {}) {
|
|
|
1348
1586
|
candidates.set(p, c);
|
|
1349
1587
|
}
|
|
1350
1588
|
const outHash = loadHashFile(projectRoot, magicDir);
|
|
1351
|
-
const srcHashPath =
|
|
1589
|
+
const srcHashPath = resolve7(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json");
|
|
1352
1590
|
let npmVersion = "";
|
|
1353
1591
|
try {
|
|
1354
|
-
npmVersion = JSON.parse(
|
|
1592
|
+
npmVersion = JSON.parse(readFileSync8(srcHashPath, "utf-8"))._version ?? "";
|
|
1355
1593
|
} catch {
|
|
1356
1594
|
}
|
|
1357
1595
|
const installedVersion = loadVersionFile(projectRoot, magicDir);
|
|
@@ -1370,18 +1608,19 @@ async function syncCommand(options = {}) {
|
|
|
1370
1608
|
const conflictFiles = /* @__PURE__ */ new Map();
|
|
1371
1609
|
let sameCount = 0;
|
|
1372
1610
|
for (const [relPath, content] of candidates) {
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1611
|
+
const key = normalizeRelPath(relPath);
|
|
1612
|
+
const absPath = resolve7(projectRoot, relPath);
|
|
1613
|
+
if (!existsSync10(absPath)) {
|
|
1614
|
+
missingFiles.set(key, content);
|
|
1376
1615
|
} else if (establishBaseline) {
|
|
1377
|
-
missingFiles.set(
|
|
1616
|
+
missingFiles.set(key, content);
|
|
1378
1617
|
} else {
|
|
1379
|
-
const curH = hash8(
|
|
1380
|
-
const storedH = outHash[
|
|
1618
|
+
const curH = hash8(readFileSync8(absPath, "utf-8"));
|
|
1619
|
+
const storedH = outHash[key];
|
|
1381
1620
|
if (storedH && curH === storedH) {
|
|
1382
1621
|
sameCount++;
|
|
1383
1622
|
} else {
|
|
1384
|
-
conflictFiles.set(
|
|
1623
|
+
conflictFiles.set(key, content);
|
|
1385
1624
|
}
|
|
1386
1625
|
}
|
|
1387
1626
|
}
|
|
@@ -1402,14 +1641,20 @@ async function syncCommand(options = {}) {
|
|
|
1402
1641
|
if (missingFiles.size === 0 && conflictFiles.size === 0) {
|
|
1403
1642
|
console.log(SYNC_CONFIG.PROMPT_PATCH_DONE);
|
|
1404
1643
|
}
|
|
1405
|
-
|
|
1644
|
+
const finalHash = mergeFullHash(
|
|
1645
|
+
outHash,
|
|
1646
|
+
[...candidates].map(([relPath]) => ({ relPath, absPath: resolve7(projectRoot, relPath) })),
|
|
1647
|
+
(absPath) => existsSync10(absPath) ? hash8(readFileSync8(absPath, "utf-8")) : null
|
|
1648
|
+
);
|
|
1649
|
+
saveHashFile(projectRoot, magicDir, finalHash);
|
|
1406
1650
|
saveVersionFile(projectRoot, magicDir, npmVersion);
|
|
1407
1651
|
await checkPrismaDiff(projectRoot, options);
|
|
1652
|
+
await maybeModelDownload(options);
|
|
1408
1653
|
return;
|
|
1409
1654
|
}
|
|
1410
1655
|
const missing = /* @__PURE__ */ new Map();
|
|
1411
1656
|
for (const [relPath, content] of allFiles) {
|
|
1412
|
-
if (!
|
|
1657
|
+
if (!existsSync10(resolve7(projectRoot, relPath))) {
|
|
1413
1658
|
missing.set(relPath, content);
|
|
1414
1659
|
}
|
|
1415
1660
|
}
|
|
@@ -1428,10 +1673,11 @@ async function syncCommand(options = {}) {
|
|
|
1428
1673
|
const result = await writeFiles2(projectRoot, filesToWrite, { yes: true });
|
|
1429
1674
|
console.log(`\u540C\u6B65\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}`);
|
|
1430
1675
|
await checkPrismaDiff(projectRoot, options);
|
|
1676
|
+
await maybeModelDownload(options);
|
|
1431
1677
|
}
|
|
1432
1678
|
function printMigrateGuidance(targetPath, changed) {
|
|
1433
1679
|
const g = SYNC_PRISMA_CONFIG.POST_SYNC;
|
|
1434
|
-
const isManaged =
|
|
1680
|
+
const isManaged = existsSync10(resolve7(dirname4(targetPath), "migrations"));
|
|
1435
1681
|
const actions = isManaged ? g.MANAGED_ACTIONS : g.UNMANAGED_ACTIONS;
|
|
1436
1682
|
console.log(` \u25B6 \u5DF2\u5199\u5165 ${changed} \u5904\u53D8\u66F4\uFF0C${g.HEADER}`);
|
|
1437
1683
|
for (const a of actions) {
|
|
@@ -1453,9 +1699,9 @@ function printMigrateGuidance(targetPath, changed) {
|
|
|
1453
1699
|
}
|
|
1454
1700
|
async function checkPrismaDiff(projectRoot, options) {
|
|
1455
1701
|
if (!options.patch) return;
|
|
1456
|
-
const basePath =
|
|
1457
|
-
const targetPath =
|
|
1458
|
-
if (!
|
|
1702
|
+
const basePath = resolve7(projectRoot, SYNC_PRISMA_CONFIG.BASE_SCHEMA);
|
|
1703
|
+
const targetPath = resolve7(projectRoot, SYNC_PRISMA_CONFIG.TARGET_PATTERN);
|
|
1704
|
+
if (!existsSync10(basePath)) {
|
|
1459
1705
|
console.log(`
|
|
1460
1706
|
\u26A0\uFE0F \u57FA\u51C6 schema \u4E0D\u5B58\u5728: ${basePath}`);
|
|
1461
1707
|
console.log(` \u8BF7\u786E\u4FDD add-coder \u5DF2\u6B63\u786E\u5B89\u88C5\u3002`);
|
|
@@ -1467,7 +1713,7 @@ async function checkPrismaDiff(projectRoot, options) {
|
|
|
1467
1713
|
\u2705 Prisma schema \u4E0E add-coder \u6807\u51C6\u4E00\u81F4\uFF0C\u65E0\u9700\u540C\u6B65\u3002`);
|
|
1468
1714
|
return;
|
|
1469
1715
|
}
|
|
1470
|
-
const targetExists =
|
|
1716
|
+
const targetExists = existsSync10(targetPath);
|
|
1471
1717
|
let modifiedCount = 0;
|
|
1472
1718
|
console.log(`
|
|
1473
1719
|
\u26A0\uFE0F Prisma schema \u5DEE\u5F02\u68C0\u6D4B:`);
|
|
@@ -1618,7 +1864,7 @@ async function handleDiffAction(action, ctx) {
|
|
|
1618
1864
|
}
|
|
1619
1865
|
}
|
|
1620
1866
|
function injectMissingModels(targetPath, models) {
|
|
1621
|
-
let content =
|
|
1867
|
+
let content = readFileSync8(targetPath, "utf-8");
|
|
1622
1868
|
content = content.replace(/\n*$/, "\n");
|
|
1623
1869
|
content += `
|
|
1624
1870
|
// ===== \u7531 add-coder sync --patch \u81EA\u52A8\u6CE8\u5165 (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}) =====
|
|
@@ -1631,44 +1877,77 @@ function injectMissingModels(targetPath, models) {
|
|
|
1631
1877
|
return models.length;
|
|
1632
1878
|
}
|
|
1633
1879
|
function getBaseFieldLines(basePath, modelName) {
|
|
1634
|
-
const content =
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1880
|
+
const content = readFileSync8(basePath, "utf-8");
|
|
1881
|
+
const blocks = parseSchemaBlocks(content);
|
|
1882
|
+
const block = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
|
|
1883
|
+
if (!block) return {};
|
|
1884
|
+
const fields = {};
|
|
1885
|
+
for (const line of block.body.split("\n")) {
|
|
1886
|
+
const clean = line.replace(/\/\/.*$/, "").trim();
|
|
1887
|
+
if (!clean) continue;
|
|
1888
|
+
if (block.type === "enum") {
|
|
1889
|
+
if (/^\w+$/.test(clean)) fields[clean] = line.trim();
|
|
1890
|
+
} else {
|
|
1641
1891
|
const fm = line.match(/^\s*(\w+)\s+/);
|
|
1642
1892
|
if (fm) fields[fm[1]] = line.trim();
|
|
1643
1893
|
}
|
|
1644
|
-
return fields;
|
|
1645
1894
|
}
|
|
1646
|
-
return
|
|
1895
|
+
return fields;
|
|
1647
1896
|
}
|
|
1648
1897
|
function injectFieldLines(targetPath, basePath, modelName, fieldKeys) {
|
|
1649
1898
|
const baseFields = getBaseFieldLines(basePath, modelName);
|
|
1650
|
-
if (Object.keys(baseFields).length === 0)
|
|
1651
|
-
|
|
1652
|
-
|
|
1899
|
+
if (Object.keys(baseFields).length === 0) {
|
|
1900
|
+
console.warn(`\u26A0\uFE0F \u6CE8\u5165\u5931\u8D25\uFF1A${modelName} \u5728\u57FA\u51C6\u4E2D\u672A\u627E\u5230\u5B57\u6BB5\u5B9A\u4E49\uFF08${fieldKeys.length} \u4E2A\u5B57\u6BB5\u672A\u5199\u5165\uFF09`);
|
|
1901
|
+
return 0;
|
|
1902
|
+
}
|
|
1903
|
+
const content = readFileSync8(targetPath, "utf-8");
|
|
1904
|
+
const lines = content.split("\n");
|
|
1905
|
+
const blocks = parseSchemaBlocks(content);
|
|
1906
|
+
const target = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
|
|
1907
|
+
if (!target) {
|
|
1908
|
+
console.warn(`\u26A0\uFE0F \u6CE8\u5165\u5931\u8D25\uFF1A\u76EE\u6807\u4E2D\u4E0D\u5B58\u5728 ${modelName} \u5757\uFF08${fieldKeys.length} \u4E2A\u5B57\u6BB5\u672A\u5199\u5165\uFF09`);
|
|
1909
|
+
return 0;
|
|
1910
|
+
}
|
|
1911
|
+
const bodyLines = target.body.split("\n");
|
|
1912
|
+
const startIdx = lines.findIndex((l) => l === bodyLines[0]);
|
|
1913
|
+
if (startIdx < 0) {
|
|
1914
|
+
console.warn(`\u26A0\uFE0F \u6CE8\u5165\u5931\u8D25\uFF1A\u65E0\u6CD5\u5B9A\u4F4D ${modelName} \u5757\u4F4D\u7F6E\uFF08${fieldKeys.length} \u4E2A\u5B57\u6BB5\u672A\u5199\u5165\uFF09`);
|
|
1915
|
+
return 0;
|
|
1916
|
+
}
|
|
1917
|
+
const endIdx = startIdx + bodyLines.length - 1;
|
|
1918
|
+
const existingNames = new Set(
|
|
1919
|
+
target.fields.map((f) => f.split(":")[0])
|
|
1920
|
+
);
|
|
1921
|
+
const newFieldLines = [];
|
|
1653
1922
|
for (const key of fieldKeys) {
|
|
1654
1923
|
const fieldName = key.split(":")[0];
|
|
1655
1924
|
const fieldLine = baseFields[fieldName];
|
|
1656
1925
|
if (!fieldLine) continue;
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
if (!m) continue;
|
|
1660
|
-
if (new RegExp(`^\\s*${fieldName}\\s+`, "m").test(m[2])) continue;
|
|
1661
|
-
content = content.replace(modelRegex, `$1$2
|
|
1662
|
-
${fieldLine}$3`);
|
|
1663
|
-
count++;
|
|
1926
|
+
if (existingNames.has(fieldName)) continue;
|
|
1927
|
+
newFieldLines.push(` ${fieldLine}`);
|
|
1664
1928
|
}
|
|
1665
|
-
if (
|
|
1666
|
-
|
|
1929
|
+
if (newFieldLines.length === 0) {
|
|
1930
|
+
if (fieldKeys.length > 0) {
|
|
1931
|
+
console.warn(`\u26A0\uFE0F \u6CE8\u5165\u5931\u8D25\uFF1A${modelName} \u7684 ${fieldKeys.length} \u4E2A\u5B57\u6BB5\u672A\u5199\u5165\uFF08\u53EF\u80FD\u5DF2\u5B58\u5728\u6216\u5B9A\u4E49\u4E0D\u5339\u914D\uFF09`);
|
|
1932
|
+
}
|
|
1933
|
+
return 0;
|
|
1934
|
+
}
|
|
1935
|
+
let insertIdx = endIdx;
|
|
1936
|
+
for (let k = startIdx + 1; k < endIdx; k++) {
|
|
1937
|
+
if (/^\s*@@/.test(lines[k])) insertIdx = k;
|
|
1938
|
+
}
|
|
1939
|
+
const merged = [
|
|
1940
|
+
...lines.slice(0, insertIdx),
|
|
1941
|
+
...newFieldLines,
|
|
1942
|
+
...lines.slice(insertIdx)
|
|
1943
|
+
];
|
|
1944
|
+
writeFileSync5(targetPath, merged.join("\n"), "utf-8");
|
|
1945
|
+
return newFieldLines.length;
|
|
1667
1946
|
}
|
|
1668
1947
|
function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
|
|
1669
1948
|
const baseFields = getBaseFieldLines(basePath, modelName);
|
|
1670
1949
|
if (Object.keys(baseFields).length === 0) return 0;
|
|
1671
|
-
let content =
|
|
1950
|
+
let content = readFileSync8(targetPath, "utf-8");
|
|
1672
1951
|
let count = 0;
|
|
1673
1952
|
for (const { fieldName } of conflicts) {
|
|
1674
1953
|
const baseLine = baseFields[fieldName];
|
|
@@ -1684,8 +1963,8 @@ function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
|
|
|
1684
1963
|
}
|
|
1685
1964
|
|
|
1686
1965
|
// src/cli/commands/status.ts
|
|
1687
|
-
import { existsSync as
|
|
1688
|
-
import { resolve as
|
|
1966
|
+
import { existsSync as existsSync11 } from "fs";
|
|
1967
|
+
import { resolve as resolve8 } from "path";
|
|
1689
1968
|
async function statusCommand() {
|
|
1690
1969
|
const projectRoot = process.cwd();
|
|
1691
1970
|
const config = await loadConfig(projectRoot);
|
|
@@ -1694,7 +1973,7 @@ async function statusCommand() {
|
|
|
1694
1973
|
const missing = [];
|
|
1695
1974
|
const present = [];
|
|
1696
1975
|
for (const [relPath] of coreFiles) {
|
|
1697
|
-
if (
|
|
1976
|
+
if (existsSync11(resolve8(projectRoot, relPath))) {
|
|
1698
1977
|
present.push(relPath);
|
|
1699
1978
|
} else {
|
|
1700
1979
|
missing.push(relPath);
|
|
@@ -1705,14 +1984,15 @@ async function statusCommand() {
|
|
|
1705
1984
|
if (missing.length > 0) {
|
|
1706
1985
|
console.log(` \u7F3A\u5931: ${missing.length} \u6587\u4EF6`);
|
|
1707
1986
|
missing.forEach((f) => console.log(` - ${f}`));
|
|
1987
|
+
process.exit(1);
|
|
1708
1988
|
} else {
|
|
1709
1989
|
console.log(" \u6240\u6709\u6587\u4EF6\u5B8C\u6574\u3002");
|
|
1710
1990
|
}
|
|
1711
1991
|
}
|
|
1712
1992
|
|
|
1713
1993
|
// src/cli/commands/stack.ts
|
|
1714
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
1715
|
-
import { resolve as
|
|
1994
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
|
|
1995
|
+
import { resolve as resolve9, join as join6 } from "path";
|
|
1716
1996
|
import { createHash as createHash3 } from "crypto";
|
|
1717
1997
|
var MAGIC_DIR_MAP3 = { claude: ".claude", qoder: ".qoder", vscode: ".vscode", trae: ".trae", codex: ".codex" };
|
|
1718
1998
|
var HASH_OUTPUT_FILE = ".add-coder-hash.json";
|
|
@@ -1729,14 +2009,14 @@ function resolveMagicDir(projectRoot, specified) {
|
|
|
1729
2009
|
return MAGIC_DIR_MAP3[adapter];
|
|
1730
2010
|
}
|
|
1731
2011
|
function listCustomProfiles(projectRoot, magicDir, registryNames) {
|
|
1732
|
-
const dir =
|
|
1733
|
-
if (!
|
|
2012
|
+
const dir = resolve9(projectRoot, magicDir, "rules", "profiles");
|
|
2013
|
+
if (!existsSync12(dir)) return [];
|
|
1734
2014
|
return readdirSync3(dir).filter((f) => f.endsWith(".md") && !registryNames.has(f.replace(/-profile\.md$/, ""))).sort();
|
|
1735
2015
|
}
|
|
1736
2016
|
function profileExists(projectRoot, magicDir, name) {
|
|
1737
2017
|
const registry = loadProfileRegistry();
|
|
1738
2018
|
if (registry.some((p) => p.name === name)) return true;
|
|
1739
|
-
return
|
|
2019
|
+
return existsSync12(resolve9(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`));
|
|
1740
2020
|
}
|
|
1741
2021
|
function stackCommand(sub, name, options = {}) {
|
|
1742
2022
|
const projectRoot = process.cwd();
|
|
@@ -1773,12 +2053,12 @@ function stackCommand(sub, name, options = {}) {
|
|
|
1773
2053
|
console.log("\u6280\u672F\u6808: \u672A\u8BBE\u7F6E\uFF08\u4E2D\u6027\uFF0C\u65E0\u6280\u672F\u6808\u5047\u8BBE\uFF09");
|
|
1774
2054
|
return;
|
|
1775
2055
|
}
|
|
1776
|
-
const profilePath =
|
|
1777
|
-
const stat =
|
|
2056
|
+
const profilePath = resolve9(projectRoot, magicDir, "rules", "profiles", `${current}-profile.md`);
|
|
2057
|
+
const stat = existsSync12(profilePath) ? readFileSync9(profilePath, "utf-8").length : 0;
|
|
1778
2058
|
console.log(`\u6280\u672F\u6808: ${current}`);
|
|
1779
|
-
console.log(`profile \u6587\u4EF6: ${profilePath}${
|
|
2059
|
+
console.log(`profile \u6587\u4EF6: ${profilePath}${existsSync12(profilePath) ? ` (${stat} \u5B57\u7B26)` : "\uFF08\u7F3A\u5931\uFF09"}`);
|
|
1780
2060
|
try {
|
|
1781
|
-
const raw = JSON.parse(
|
|
2061
|
+
const raw = JSON.parse(readFileSync9(resolve9(projectRoot, magicDir, "stack.json"), "utf-8"));
|
|
1782
2062
|
if (raw.updatedAt) console.log(`\u66F4\u65B0\u65F6\u95F4: ${raw.updatedAt}`);
|
|
1783
2063
|
} catch {
|
|
1784
2064
|
}
|
|
@@ -1803,9 +2083,9 @@ function buildConfig(projectRoot, magicDir, stack) {
|
|
|
1803
2083
|
stack
|
|
1804
2084
|
};
|
|
1805
2085
|
try {
|
|
1806
|
-
const pkgPath =
|
|
1807
|
-
if (
|
|
1808
|
-
const pkg = JSON.parse(
|
|
2086
|
+
const pkgPath = resolve9(projectRoot, "package.json");
|
|
2087
|
+
if (existsSync12(pkgPath)) {
|
|
2088
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
1809
2089
|
if (pkg.name) config.projectName = pkg.name;
|
|
1810
2090
|
}
|
|
1811
2091
|
} catch {
|
|
@@ -1814,9 +2094,9 @@ function buildConfig(projectRoot, magicDir, stack) {
|
|
|
1814
2094
|
}
|
|
1815
2095
|
function applyStack(projectRoot, magicDir, name) {
|
|
1816
2096
|
if (!profileExists(projectRoot, magicDir, name)) {
|
|
1817
|
-
const
|
|
2097
|
+
const registry2 = loadProfileRegistry();
|
|
1818
2098
|
console.error(`\u2717 profile \u4E0D\u5B58\u5728: ${name}`);
|
|
1819
|
-
console.error(` \u5185\u7F6E: ${
|
|
2099
|
+
console.error(` \u5185\u7F6E: ${registry2.map((p) => p.name).join(" | ")}\uFF08\u6216\u81EA\u5B9A\u4E49: ${magicDir}/rules/profiles/{name}-profile.md\uFF09`);
|
|
1820
2100
|
process.exit(1);
|
|
1821
2101
|
}
|
|
1822
2102
|
const config = buildConfig(projectRoot, magicDir, name);
|
|
@@ -1824,42 +2104,69 @@ function applyStack(projectRoot, magicDir, name) {
|
|
|
1824
2104
|
const coreFiles = renderCore(config, false);
|
|
1825
2105
|
const stackRelated = /* @__PURE__ */ new Map();
|
|
1826
2106
|
for (const [relPath, content] of coreFiles) {
|
|
1827
|
-
|
|
1828
|
-
|
|
2107
|
+
const rp = normalizeRelPath(relPath);
|
|
2108
|
+
if (rp.includes("/rules/profiles/") || rp.endsWith("/rules/project_rules.md")) {
|
|
2109
|
+
stackRelated.set(rp, content);
|
|
1829
2110
|
}
|
|
1830
2111
|
}
|
|
1831
2112
|
const hashMap = {};
|
|
1832
2113
|
try {
|
|
1833
|
-
Object.assign(hashMap, JSON.parse(
|
|
2114
|
+
Object.assign(hashMap, JSON.parse(readFileSync9(resolve9(projectRoot, magicDir, HASH_OUTPUT_FILE), "utf-8")));
|
|
1834
2115
|
} catch {
|
|
1835
2116
|
}
|
|
1836
2117
|
let written = 0;
|
|
1837
2118
|
for (const [relPath, content] of stackRelated) {
|
|
1838
2119
|
for (const t of [".add", magicDir]) {
|
|
1839
|
-
const targetPath =
|
|
1840
|
-
mkdirSync6(
|
|
2120
|
+
const targetPath = resolve9(projectRoot, relPath.replace(/^\.add/, t));
|
|
2121
|
+
mkdirSync6(join6(targetPath, ".."), { recursive: true });
|
|
1841
2122
|
writeFileSync6(targetPath, content, "utf-8");
|
|
1842
2123
|
hashMap[relPath.replace(/^\.add/, t)] = hash82(content);
|
|
1843
2124
|
written++;
|
|
1844
2125
|
}
|
|
1845
2126
|
}
|
|
1846
|
-
|
|
2127
|
+
const fail = (msg) => {
|
|
2128
|
+
console.error(`\u2717 stack set \u5931\u8D25: ${msg}`);
|
|
2129
|
+
process.exit(1);
|
|
2130
|
+
};
|
|
2131
|
+
const registry = loadProfileRegistry();
|
|
2132
|
+
const isBuiltin = registry.some((p) => p.name === name);
|
|
2133
|
+
const profilePathMagic = resolve9(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`);
|
|
2134
|
+
const profilePathAdd = resolve9(projectRoot, ".add", "rules", "profiles", `${name}-profile.md`);
|
|
2135
|
+
const projectRulesPath = resolve9(projectRoot, magicDir, "rules", "project_rules.md");
|
|
2136
|
+
const projectRulesContent = existsSync12(projectRulesPath) ? readFileSync9(projectRulesPath, "utf-8") : "";
|
|
2137
|
+
if (written === 0) fail(`\u672A\u6E32\u67D3\u4EFB\u4F55 stack \u76F8\u5173\u6587\u4EF6\uFF08${name}\uFF09\u2014\u2014Windows \u8DEF\u5F84\u5339\u914D\u5931\u6548\u9057\u7559\u95EE\u9898`);
|
|
2138
|
+
if (isBuiltin && !existsSync12(profilePathAdd)) fail(`profile \u672A\u5199\u5165 .add: ${profilePathAdd}`);
|
|
2139
|
+
if (!existsSync12(profilePathMagic)) fail(`profile \u672A\u5199\u5165 ${magicDir}: ${profilePathMagic}`);
|
|
2140
|
+
if (!projectRulesContent.includes("**\u5F53\u524D\u6280\u672F\u6808**") || !projectRulesContent.includes(name)) fail(`project_rules.md \u672A\u5305\u542B ${name} \u5F15\u7528`);
|
|
2141
|
+
writeFileSync6(resolve9(projectRoot, magicDir, HASH_OUTPUT_FILE), JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
|
|
1847
2142
|
console.log(`\u2705 \u6280\u672F\u6808\u5DF2\u8BBE\u7F6E\u4E3A ${name}`);
|
|
1848
2143
|
console.log(` ${magicDir}/stack.json \u2192 ${name}`);
|
|
1849
|
-
console.log(` ${magicDir}/rules/profiles/${name}-profile.md ${
|
|
2144
|
+
console.log(` ${magicDir}/rules/profiles/${name}-profile.md ${existsSync12(resolve9(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`)) ? "\u5DF2\u5C31\u4F4D" : "\uFF08\u81EA\u5B9A\u4E49 profile\uFF0C\u9879\u76EE\u4FA7\u6587\u4EF6\uFF09"}`);
|
|
1850
2145
|
console.log(` project_rules.md \u5F15\u7528\u884C\u5DF2\u66F4\u65B0 + hash \u5DF2\u5237\u65B0\uFF08${written} \u4E2A\u6587\u4EF6\uFF09`);
|
|
1851
2146
|
}
|
|
1852
2147
|
|
|
1853
2148
|
// src/cli/index.ts
|
|
1854
2149
|
var { version } = JSON.parse(
|
|
1855
|
-
|
|
2150
|
+
readFileSync10(new URL("../package.json", import.meta.url), "utf-8")
|
|
1856
2151
|
);
|
|
2152
|
+
async function modelDownloadCommand(options) {
|
|
2153
|
+
try {
|
|
2154
|
+
const r = await ensureEmbeddingModel({ force: options.force });
|
|
2155
|
+
console.log(`\u6A21\u578B\u9884\u4E0B\u8F7D: ${r.status}${r.model ? ` (${r.model})` : ""}`);
|
|
2156
|
+
if (r.cacheDir) console.log(`\u7F13\u5B58\u4F4D\u7F6E: ${r.cacheDir}`);
|
|
2157
|
+
console.log("\u63D0\u793A: \u8FD0\u884C\u65F6 DPS \u4F7F\u7528\u7684\u6A21\u578B\u914D\u7F6E\u4EE5 `add-coder generate` \u751F\u6210\u7684\u914D\u7F6E\u4E3A\u51C6");
|
|
2158
|
+
} catch (e) {
|
|
2159
|
+
console.error(`\u6A21\u578B\u9884\u4E0B\u8F7D\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
2160
|
+
process.exit(1);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
1857
2163
|
var program = new Command();
|
|
1858
2164
|
program.name("add-coder").description("\u521D\u59CB\u5316 ADD \u8303\u5F0F\u5DE5\u4F5C\u6D41\u6A21\u677F").version(version);
|
|
1859
|
-
program.command("init").description("\u521D\u59CB\u5316 ADD \u6A21\u677F\u5230\u5F53\u524D\u9879\u76EE").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode | trae | codex").option("--config <path>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option("--force", "\u8986\u76D6\u5DF2\u6709\u6587\u4EF6\uFF0C\u4E0D\u4EA4\u4E92").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4E0D\u5B9E\u9645\u5199\u5165").option("--stack <name>", "\u6280\u672F\u6808\u7EA6\u675F profile \u540D\uFF08\u5982 machineserver\uFF0C\u53EF\u9009\uFF09").action(initCommand);
|
|
1860
|
-
program.command("sync").description("\u589E\u91CF\u540C\u6B65\u7F3A\u5931\u6587\u4EF6\uFF08--patch \u8986\u76D6\u5DF2\u6709\u6A21\u677F\uFF09").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode | trae | codex").option("--patch", "\u8986\u76D6\u5DF2\u6709\u6A21\u677F\u6587\u4EF6\uFF08\u4E0D\u78B0 plans/specs/reviews\uFF09").option("-i, --interactive", "\u4EA4\u4E92\u5F0F\u9009\u62E9\u8981\u540C\u6B65\u7684\u6587\u4EF6").action(syncCommand);
|
|
2165
|
+
program.command("init").description("\u521D\u59CB\u5316 ADD \u6A21\u677F\u5230\u5F53\u524D\u9879\u76EE").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode | trae | codex").option("--config <path>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option("--force", "\u8986\u76D6\u5DF2\u6709\u6587\u4EF6\uFF0C\u4E0D\u4EA4\u4E92").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4E0D\u5B9E\u9645\u5199\u5165").option("--stack <name>", "\u6280\u672F\u6808\u7EA6\u675F profile \u540D\uFF08\u5982 machineserver\uFF0C\u53EF\u9009\uFF09").option("--skip-model", "\u8DF3\u8FC7 embedding \u6A21\u578B\u9884\u4E0B\u8F7D").action(initCommand);
|
|
2166
|
+
program.command("sync").description("\u589E\u91CF\u540C\u6B65\u7F3A\u5931\u6587\u4EF6\uFF08--patch \u8986\u76D6\u5DF2\u6709\u6A21\u677F\uFF09").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode | trae | codex").option("--patch", "\u8986\u76D6\u5DF2\u6709\u6A21\u677F\u6587\u4EF6\uFF08\u4E0D\u78B0 plans/specs/reviews\uFF09").option("-i, --interactive", "\u4EA4\u4E92\u5F0F\u9009\u62E9\u8981\u540C\u6B65\u7684\u6587\u4EF6").option("--model", "\u68C0\u6D4B\u5230\u7F3A\u5931\u65F6\u4E0B\u8F7D embedding \u6A21\u578B").action(syncCommand);
|
|
1861
2167
|
program.command("status").description("\u68C0\u67E5 ADD \u6A21\u677F\u5B8C\u6574\u6027").action(statusCommand);
|
|
1862
2168
|
program.command("stack").description("\u7BA1\u7406\u6280\u672F\u6808\u7EA6\u675F profile\uFF08list / set <name> / show / --clear\uFF09").argument("[sub]", "list | set | show").argument("[name]", "set <name>: profile \u540D\uFF08\u5185\u7F6E\u6216\u81EA\u5B9A\u4E49\uFF09").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode | trae | codex").option("--clear", "\u6E05\u9664\u6280\u672F\u6808\u8BBE\u7F6E\uFF08\u4E2D\u6027\uFF09").action(
|
|
1863
2169
|
(sub, name, options) => stackCommand(sub, name, options)
|
|
1864
2170
|
);
|
|
2171
|
+
program.command("model:download").description("\u9884\u4E0B\u8F7D embedding \u6A21\u578B\uFF08\u9996\u6B21 DPS \u8C03\u7528\u4F1A\u81EA\u52A8\u4E0B\u8F7D\uFF0C\u672C\u547D\u4EE4\u63D0\u524D\u62C9\u53D6\uFF09").option("--force", "\u5F3A\u5236\u91CD\u65B0\u4E0B\u8F7D\uFF08\u5373\u4F7F\u7F13\u5B58\u5DF2\u5B58\u5728\uFF09").action(modelDownloadCommand);
|
|
1865
2172
|
program.parse();
|