add-coder 0.3.19 → 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/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 readFileSync9 } from "fs";
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((resolve9) => {
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
- resolve9(result);
228
+ resolve10(result);
229
229
  });
230
230
  });
231
231
  }
@@ -649,9 +649,38 @@ import { resolve as resolve4, dirname as dirname3 } from "path";
649
649
  import { fileURLToPath as fileURLToPath2 } from "url";
650
650
 
651
651
  // src/caijuehub/strategies/prisma.strategy.ts
652
- import { spawnSync } from "child_process";
653
652
  import { copyFileSync, existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
654
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
655
684
  var PRISMA_CONFIG = {
656
685
  onMissing: "ask",
657
686
  onExistingAddPrisma: "ask",
@@ -679,12 +708,10 @@ function ensurePrismaConfig(projectRoot) {
679
708
  ].join("\n") + "\n", "utf-8");
680
709
  }
681
710
  function backupAddTables(projectRoot) {
682
- const pgDump = spawnSync("which", ["pg_dump"], { timeout: 2e3 });
683
- if (pgDump.status !== 0) return null;
711
+ if (!commandExists("pg_dump")) return null;
684
712
  const bak = resolve3(projectRoot, `add-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.sql`);
685
- const r = spawnSync("pg_dump", ["--table=AddUser", "--table=DevOperation", "--table=AuditLog", "--if-exists"], {
713
+ const r = runCommand("pg_dump", ["--table=AddUser", "--table=DevOperation", "--table=AuditLog", "--if-exists"], {
686
714
  cwd: projectRoot,
687
- stdio: ["ignore", "pipe", "pipe"],
688
715
  timeout: 3e4
689
716
  });
690
717
  if (r.stdout.length > 0) {
@@ -695,16 +722,18 @@ function backupAddTables(projectRoot) {
695
722
  return null;
696
723
  }
697
724
  function runPrismaInit(projectRoot, provider, schemaPath) {
698
- console.log("\u6267\u884C npx prisma init ...");
725
+ console.log("\u6267\u884C prisma init ...");
699
726
  const pm = detectPm(projectRoot);
700
- const initArgs = pm === "pnpm" ? ["dlx", "prisma", "init", "--datasource-provider", provider] : ["prisma", "init", "--datasource-provider", provider];
701
- const initResult = spawnSync(pm, initArgs, {
702
- cwd: projectRoot,
703
- stdio: "inherit",
704
- shell: false
705
- });
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
+ }
706
735
  if (initResult.status !== 0 || !existsSync7(schemaPath)) {
707
- console.log("prisma init \u5931\u8D25\uFF0C\u624B\u52A8\u521B\u5EFA schema.prisma ...");
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`);
708
737
  const prismaDir = resolve3(projectRoot, "prisma");
709
738
  if (!existsSync7(prismaDir)) mkdirSync3(prismaDir, { recursive: true });
710
739
  const content = `generator client {
@@ -744,6 +773,28 @@ function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
744
773
  }
745
774
  copyFileSync(addPrismaTemplate, destPath);
746
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");
747
798
  }
748
799
  async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
749
800
  const C = PRISMA_CONFIG;
@@ -808,10 +859,10 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
808
859
  ensurePrismaConfig(projectRoot);
809
860
  backupAddTables(projectRoot);
810
861
  const pm = detectPm(projectRoot);
811
- const args = pm === "pnpm" ? ["dlx", "prisma", "db", "push"] : ["prisma", "db", "push"];
862
+ const args = pm === "pnpm" ? ["dlx", "prisma", "db", "push"] : ["exec", "prisma", "--", "db", "push"];
812
863
  if (C.schemaArg) args.push(C.schemaArg);
813
864
  console.log(`\u6267\u884C ${pm} ${args.join(" ")} ...`);
814
- const r = spawnSync(pm, args, { cwd: projectRoot, stdio: "inherit", shell: false });
865
+ const r = runCommand(pm, args, { cwd: projectRoot });
815
866
  if (r.status !== 0) throw new Error(`prisma db push \u9000\u51FA\u7801: ${r.status}`);
816
867
  } catch (err) {
817
868
  if (C.onMigrateFail === "keep") {
@@ -827,8 +878,14 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
827
878
  }
828
879
  if (C.autoGenerate) {
829
880
  const pm = detectPm(projectRoot);
881
+ const genArgs = pm === "pnpm" ? ["dlx", "prisma", "generate"] : ["exec", "prisma", "--", "generate"];
830
882
  console.log("\u6267\u884C prisma generate ...");
831
- spawnSync(pm, pm === "pnpm" ? ["dlx", "prisma", "generate"] : ["prisma", "generate"], { cwd: projectRoot, stdio: "inherit", shell: false });
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
+ }
832
889
  }
833
890
  console.log("ADD \u6CBB\u7406\u6A21\u578B\u5DF2\u5C31\u7EEA");
834
891
  return true;
@@ -843,11 +900,88 @@ async function injectPrisma2(projectRoot, options = {}) {
843
900
  }
844
901
 
845
902
  // src/cli/commands/init.ts
846
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
903
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
847
904
  import { createHash } from "crypto";
848
- import { resolve as resolve5 } from "path";
849
- import { spawnSync as spawnSync2 } from "child_process";
905
+ import { resolve as resolve6 } from "path";
850
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
851
985
  var ADAPTER_RENDERERS = {
852
986
  claude: renderAdapter,
853
987
  qoder: renderAdapter2,
@@ -866,9 +1000,17 @@ async function initCommand(options) {
866
1000
  console.log(`[dry-run] \u5C06\u5199\u5165 ${ctx.magicDir}/stack.json \u2192 ${ctx.stack}`);
867
1001
  }
868
1002
  const result = await renderAndWrite(ctx);
869
- await deployDatabase(ctx);
1003
+ const dbFail = await deployDatabase(ctx);
870
1004
  deployDocs(ctx);
871
- 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
+ }
872
1014
  }
873
1015
  async function resolveAdapter(projectRoot, specified) {
874
1016
  if (specified) {
@@ -923,27 +1065,31 @@ function portInUse(port) {
923
1065
  }
924
1066
  function hasPgIsready() {
925
1067
  try {
926
- const containers = spawnSync2("podman", ["ps", "--filter", "publish=5433", "--format", "{{.Names}}"], { timeout: 3e3 });
1068
+ const containers = runCommand("podman", ["ps", "--filter", "publish=5433", "--format", "{{.Names}}"], { timeout: 3e3 });
927
1069
  const name = containers.stdout.toString().trim().split("\n")[0];
928
- if (name && spawnSync2("podman", ["exec", name, "pg_isready", "--version"], { timeout: 2e3 }).status === 0) return true;
1070
+ if (name && runCommand("podman", ["exec", name, "pg_isready", "--version"], { timeout: 2e3 }).status === 0) return true;
929
1071
  } catch {
930
1072
  }
931
- return spawnSync2("which", ["pg_isready"], { timeout: 2e3 }).status === 0;
1073
+ return commandExists("pg_isready");
932
1074
  }
933
1075
  function testPostgresConnection(port, user, password, dbName) {
934
1076
  if (!hasPgIsready()) {
935
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");
936
1078
  return true;
937
1079
  }
938
- const containers = spawnSync2("podman", ["ps", "--filter", "publish=5433", "--format", "{{.Names}}"], { timeout: 3e3 });
939
- const containerName = containers.stdout.toString().trim().split("\n")[0];
940
- const args = containerName ? ["exec", containerName, "pg_isready", "-U", user, "-d", dbName] : ["-h", "localhost", "-p", port, "-U", user, "-d", dbName];
941
- const cmd = containerName ? "podman" : "pg_isready";
942
- const r = spawnSync2(cmd, args, {
943
- timeout: 5e3,
944
- env: containerName ? process.env : { ...process.env, PGPASSWORD: password }
945
- });
946
- return r.status === 0;
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
+ }
947
1093
  }
948
1094
  async function resolveDbCredentials(force) {
949
1095
  const d = { user: "admin", password: "change-me-in-production", port: "5433" };
@@ -1006,8 +1152,8 @@ networks:
1006
1152
  `;
1007
1153
  }
1008
1154
  function writeSqliteExportScript(projectRoot, dryRun) {
1009
- const scriptsDir = resolve5(projectRoot, "scripts");
1010
- const scriptPath = resolve5(scriptsDir, "export-db.ts");
1155
+ const scriptsDir = resolve6(projectRoot, "scripts");
1156
+ const scriptPath = resolve6(scriptsDir, "export-db.ts");
1011
1157
  const content = `import { PrismaClient } from "@prisma/client";
1012
1158
  import { writeFileSync, mkdirSync, existsSync } from "fs";
1013
1159
  import { resolve } from "path";
@@ -1031,18 +1177,18 @@ main().catch((e) => { console.error(e); process.exit(1); });
1031
1177
  console.log(`[dry-run] \u5C06\u5199\u5165 ${scriptPath}`);
1032
1178
  return;
1033
1179
  }
1034
- if (!existsSync8(scriptsDir)) mkdirSync4(scriptsDir, { recursive: true });
1180
+ if (!existsSync9(scriptsDir)) mkdirSync4(scriptsDir, { recursive: true });
1035
1181
  writeFileSync4(scriptPath, content, "utf-8");
1036
1182
  console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
1037
1183
  }
1038
1184
  function injectDbExportScript(projectRoot, dryRun) {
1039
- const pkgPath = resolve5(projectRoot, "package.json");
1040
- if (!existsSync8(pkgPath)) return;
1185
+ const pkgPath = resolve6(projectRoot, "package.json");
1186
+ if (!existsSync9(pkgPath)) return;
1041
1187
  if (dryRun) {
1042
1188
  console.log("[dry-run] \u5C06\u6CE8\u5165 db:export");
1043
1189
  return;
1044
1190
  }
1045
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1191
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1046
1192
  if (!pkg.scripts) pkg.scripts = {};
1047
1193
  if (!pkg.scripts["db:export"]) {
1048
1194
  pkg.scripts["db:export"] = "npx tsx scripts/export-db.ts";
@@ -1089,15 +1235,15 @@ function writeComposeEnv(ctx) {
1089
1235
  if (db.engine !== "postgresql" || !db.container || db.container === "manual") return;
1090
1236
  if (!db.reuseExisting) {
1091
1237
  const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
1092
- const composePath = resolve5(projectRoot, composeName);
1093
- if (!options.dryRun && (!existsSync8(composePath) || options.force)) {
1238
+ const composePath = resolve6(projectRoot, composeName);
1239
+ if (!options.dryRun && (!existsSync9(composePath) || options.force)) {
1094
1240
  writeFileSync4(composePath, composeContent(config.projectName || "add-project"), "utf-8");
1095
1241
  console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
1096
1242
  }
1097
1243
  }
1098
- const devEnvPath = resolve5(projectRoot, ".env.development");
1099
- if (!options.dryRun && existsSync8(devEnvPath)) {
1100
- const existing = readFileSync6(devEnvPath, "utf-8");
1244
+ const devEnvPath = resolve6(projectRoot, ".env.development");
1245
+ if (!options.dryRun && existsSync9(devEnvPath)) {
1246
+ const existing = readFileSync7(devEnvPath, "utf-8");
1101
1247
  if (!/^DATABASE_USER=/m.test(existing)) {
1102
1248
  writeFileSync4(devEnvPath, existing + `
1103
1249
  DATABASE_USER=${db.user || "admin"}
@@ -1136,8 +1282,8 @@ async function renderAndWrite(ctx) {
1136
1282
  console.log(`claude adapter (via Agent Host): ${claudeFiles.size} \u6587\u4EF6`);
1137
1283
  }
1138
1284
  for (const d of [".add", magicDir]) {
1139
- const reviewsDir = resolve5(projectRoot, d, "reviews");
1140
- if (!existsSync8(reviewsDir)) {
1285
+ const reviewsDir = resolve6(projectRoot, d, "reviews");
1286
+ if (!existsSync9(reviewsDir)) {
1141
1287
  if (dry) {
1142
1288
  console.log(`[dry-run] \u5C06\u521B\u5EFA ${reviewsDir}/`);
1143
1289
  } else {
@@ -1149,16 +1295,16 @@ async function renderAndWrite(ctx) {
1149
1295
  const hashMap = {};
1150
1296
  let npmVer = "";
1151
1297
  try {
1152
- npmVer = JSON.parse(readFileSync6(resolve5(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
1298
+ npmVer = JSON.parse(readFileSync7(resolve6(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
1153
1299
  } catch {
1154
1300
  }
1155
1301
  for (const [rp, c] of allFiles) {
1156
1302
  hashMap[rp] = createHash("sha256").update(c).digest("hex").slice(0, 8);
1157
1303
  }
1158
- const hashOut = resolve5(projectRoot, magicDir, ".add-coder-hash.json");
1304
+ const hashOut = resolve6(projectRoot, magicDir, ".add-coder-hash.json");
1159
1305
  writeFileSync4(hashOut, JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
1160
1306
  if (npmVer) {
1161
- writeFileSync4(resolve5(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
1307
+ writeFileSync4(resolve6(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
1162
1308
  }
1163
1309
  console.log(`hash: ${Object.keys(hashMap).length} entries \u2192 ${magicDir}/.add-coder-hash.json`);
1164
1310
  }
@@ -1166,22 +1312,37 @@ async function renderAndWrite(ctx) {
1166
1312
  }
1167
1313
  async function deployDatabase(ctx) {
1168
1314
  const { projectRoot, options, magicDir, config, db } = ctx;
1169
- if (options.dryRun) return;
1315
+ if (options.dryRun) return null;
1316
+ let fail = null;
1170
1317
  if (db.engine === "postgresql" && db.container && db.container !== "manual") {
1171
- const dbScript = resolve5(projectRoot, magicDir, "scripts", "db-ensure.sh");
1318
+ const dbScript = resolve6(projectRoot, magicDir, "scripts", "db-ensure.sh");
1172
1319
  const dbEnv = { ...process.env, DATABASE_USER: db.user, DATABASE_PASSWORD: db.password, DATABASE_PORT: db.port, PROJECT_NAME: config.projectName };
1173
1320
  const mode = db.reuseExisting ? "manual" : db.container;
1174
1321
  console.log(db.reuseExisting ? "\u590D\u7528\u5DF2\u6709 PostgreSQL ..." : `\u90E8\u7F72\u6570\u636E\u5E93 (${db.container}) ...`);
1175
- spawnSync2("bash", [dbScript, "postgresql", mode, "--migrate"], { cwd: projectRoot, stdio: "inherit", env: dbEnv });
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
+ }
1176
1329
  try {
1177
1330
  await injectPrisma2(projectRoot, { force: !!options.force });
1178
1331
  } catch (e) {
1179
- console.log(`Prisma \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
1332
+ fail = `Prisma \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
1180
1333
  }
1181
1334
  }
1182
1335
  if (db.engine === "postgresql" && db.container === "manual") {
1183
- const dbScript = resolve5(projectRoot, magicDir, "scripts", "db-ensure.sh");
1184
- if (existsSync8(dbScript)) spawnSync2("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
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
+ }
1185
1346
  console.log([
1186
1347
  "",
1187
1348
  "\u2501".repeat(30),
@@ -1209,25 +1370,26 @@ async function deployDatabase(ctx) {
1209
1370
  try {
1210
1371
  await injectPrisma2(projectRoot, { force: !!options.force, datasource: "sqlite" });
1211
1372
  } catch (e) {
1212
- console.log(`SQLite \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
1373
+ fail = `SQLite \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`;
1213
1374
  }
1214
1375
  }
1376
+ return fail;
1215
1377
  }
1216
1378
  function deployDocs(ctx) {
1217
1379
  const { projectRoot, options, config } = ctx;
1218
1380
  if (options.dryRun) return;
1219
1381
  const pn = config.projectName || "add-project";
1220
- const docsBase = resolve5(projectRoot, "docs", pn, "knowledge");
1221
- const groundingSrc = resolve5(import.meta.dirname, "../templates/core/templates");
1382
+ const docsBase = resolve6(projectRoot, "docs", pn, "knowledge");
1383
+ const groundingSrc = resolve6(import.meta.dirname, "../templates/core/templates");
1222
1384
  for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
1223
- const srcDir = resolve5(groundingSrc, d);
1224
- const destDir = resolve5(docsBase, d);
1225
- if (!existsSync8(destDir)) mkdirSync4(destDir, { recursive: true });
1226
- if (!existsSync8(srcDir)) continue;
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;
1227
1389
  for (const f of readdirSync2(srcDir)) {
1228
- const src = resolve5(srcDir, f);
1229
- const dest = resolve5(destDir, f);
1230
- if (existsSync8(dest)) continue;
1390
+ const src = resolve6(srcDir, f);
1391
+ const dest = resolve6(destDir, f);
1392
+ if (existsSync9(dest)) continue;
1231
1393
  try {
1232
1394
  copyFileSync2(src, dest);
1233
1395
  } catch {
@@ -1235,30 +1397,51 @@ function deployDocs(ctx) {
1235
1397
  }
1236
1398
  }
1237
1399
  }
1238
- function finalize(ctx, result) {
1400
+ function finalize(ctx, result, dbFail) {
1239
1401
  const { projectRoot, options, db } = ctx;
1240
- console.log(`
1402
+ if (options.dryRun) {
1403
+ console.log(`
1241
1404
  \u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}, \u8986\u76D6 ${result.overwritten}`);
1242
- if (options.dryRun) return;
1405
+ return;
1406
+ }
1243
1407
  if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
1244
- const pkg = JSON.parse(readFileSync6(resolve5(import.meta.dirname, "../package.json"), "utf-8"));
1408
+ const pkg = JSON.parse(readFileSync7(resolve6(import.meta.dirname, "../package.json"), "utf-8"));
1245
1409
  const peerNames = Object.keys(pkg.peerDependencies || {});
1246
1410
  if (peerNames.length > 0) {
1247
1411
  console.log(`
1248
1412
  \u5B89\u88C5 peer \u4F9D\u8D56 (${peerNames.join(" ")}) ...`);
1249
1413
  const pm = detectPm(projectRoot);
1250
- spawnSync2(pm, pm === "pnpm" ? ["add", ...peerNames] : ["install", ...peerNames], { cwd: projectRoot, stdio: "inherit" });
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
+ }
1251
1421
  }
1252
1422
  if (db.engine !== "manual" && (db.engine !== "postgresql" || db.container !== "manual")) {
1253
1423
  console.log("\u63D0\u793A: \u91CD\u542F IDE \u4EE5\u52A0\u8F7D hook \u914D\u7F6E");
1254
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}`);
1255
1433
  }
1256
1434
 
1257
1435
  // src/cli/commands/sync.ts
1258
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
1259
- import { resolve as resolve6, dirname as dirname4 } from "path";
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";
1260
1438
  import { createHash as createHash2 } from "crypto";
1261
1439
 
1440
+ // src/lib/path-normalize.ts
1441
+ function normalizeRelPath(p) {
1442
+ return p.replaceAll("\\", "/");
1443
+ }
1444
+
1262
1445
  // src/caijuehub/strategies/sync.strategy.ts
1263
1446
  var SYNC_CONFIG = {
1264
1447
  PATCH_GUARD: [/[/]plans[/]/, /[/]specs[/]/, /[/]reviews[/]/, /[/]rules[/]profiles[/]/],
@@ -1302,32 +1485,65 @@ function resolveAdapter2(projectRoot, specified) {
1302
1485
  return "qoder";
1303
1486
  }
1304
1487
  function isUserData(p) {
1305
- return SYNC_CONFIG.PATCH_GUARD.some((r) => r.test(p));
1488
+ return SYNC_CONFIG.PATCH_GUARD.some((r) => r.test(normalizeRelPath(p)));
1306
1489
  }
1307
1490
  function hash8(c) {
1308
1491
  return createHash2("sha256").update(c).digest("hex").slice(0, SYNC_CONFIG.HASH_HEX_LENGTH);
1309
1492
  }
1310
1493
  function loadHashFile(root, magic) {
1311
1494
  try {
1312
- return JSON.parse(readFileSync7(resolve6(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), "utf-8"));
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;
1313
1499
  } catch {
1314
1500
  return {};
1315
1501
  }
1316
1502
  }
1317
1503
  function loadVersionFile(root, magic) {
1318
1504
  try {
1319
- return readFileSync7(resolve6(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
1505
+ return readFileSync8(resolve7(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
1320
1506
  } catch {
1321
1507
  return "";
1322
1508
  }
1323
1509
  }
1324
1510
  function saveVersionFile(root, magic, version2) {
1325
- writeFileSync5(resolve6(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
1511
+ writeFileSync5(resolve7(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
1326
1512
  }
1327
1513
  function saveHashFile(root, magic, files) {
1328
1514
  const m = {};
1329
- for (const [p, c] of files) m[p] = hash8(c);
1330
- writeFileSync5(resolve6(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), JSON.stringify(m, null, 2) + "\n", "utf-8");
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
+ }
1331
1547
  }
1332
1548
  async function syncCommand(options = {}) {
1333
1549
  const projectRoot = process.cwd();
@@ -1370,10 +1586,10 @@ async function syncCommand(options = {}) {
1370
1586
  candidates.set(p, c);
1371
1587
  }
1372
1588
  const outHash = loadHashFile(projectRoot, magicDir);
1373
- const srcHashPath = resolve6(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json");
1589
+ const srcHashPath = resolve7(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json");
1374
1590
  let npmVersion = "";
1375
1591
  try {
1376
- npmVersion = JSON.parse(readFileSync7(srcHashPath, "utf-8"))._version ?? "";
1592
+ npmVersion = JSON.parse(readFileSync8(srcHashPath, "utf-8"))._version ?? "";
1377
1593
  } catch {
1378
1594
  }
1379
1595
  const installedVersion = loadVersionFile(projectRoot, magicDir);
@@ -1392,18 +1608,19 @@ async function syncCommand(options = {}) {
1392
1608
  const conflictFiles = /* @__PURE__ */ new Map();
1393
1609
  let sameCount = 0;
1394
1610
  for (const [relPath, content] of candidates) {
1395
- const absPath = resolve6(projectRoot, relPath);
1396
- if (!existsSync9(absPath)) {
1397
- missingFiles.set(relPath, content);
1611
+ const key = normalizeRelPath(relPath);
1612
+ const absPath = resolve7(projectRoot, relPath);
1613
+ if (!existsSync10(absPath)) {
1614
+ missingFiles.set(key, content);
1398
1615
  } else if (establishBaseline) {
1399
- missingFiles.set(relPath, content);
1616
+ missingFiles.set(key, content);
1400
1617
  } else {
1401
- const curH = hash8(readFileSync7(absPath, "utf-8"));
1402
- const storedH = outHash[relPath];
1618
+ const curH = hash8(readFileSync8(absPath, "utf-8"));
1619
+ const storedH = outHash[key];
1403
1620
  if (storedH && curH === storedH) {
1404
1621
  sameCount++;
1405
1622
  } else {
1406
- conflictFiles.set(relPath, content);
1623
+ conflictFiles.set(key, content);
1407
1624
  }
1408
1625
  }
1409
1626
  }
@@ -1424,14 +1641,20 @@ async function syncCommand(options = {}) {
1424
1641
  if (missingFiles.size === 0 && conflictFiles.size === 0) {
1425
1642
  console.log(SYNC_CONFIG.PROMPT_PATCH_DONE);
1426
1643
  }
1427
- saveHashFile(projectRoot, magicDir, new Map([...missingFiles, ...conflictFiles]));
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);
1428
1650
  saveVersionFile(projectRoot, magicDir, npmVersion);
1429
1651
  await checkPrismaDiff(projectRoot, options);
1652
+ await maybeModelDownload(options);
1430
1653
  return;
1431
1654
  }
1432
1655
  const missing = /* @__PURE__ */ new Map();
1433
1656
  for (const [relPath, content] of allFiles) {
1434
- if (!existsSync9(resolve6(projectRoot, relPath))) {
1657
+ if (!existsSync10(resolve7(projectRoot, relPath))) {
1435
1658
  missing.set(relPath, content);
1436
1659
  }
1437
1660
  }
@@ -1450,10 +1673,11 @@ async function syncCommand(options = {}) {
1450
1673
  const result = await writeFiles2(projectRoot, filesToWrite, { yes: true });
1451
1674
  console.log(`\u540C\u6B65\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}`);
1452
1675
  await checkPrismaDiff(projectRoot, options);
1676
+ await maybeModelDownload(options);
1453
1677
  }
1454
1678
  function printMigrateGuidance(targetPath, changed) {
1455
1679
  const g = SYNC_PRISMA_CONFIG.POST_SYNC;
1456
- const isManaged = existsSync9(resolve6(dirname4(targetPath), "migrations"));
1680
+ const isManaged = existsSync10(resolve7(dirname4(targetPath), "migrations"));
1457
1681
  const actions = isManaged ? g.MANAGED_ACTIONS : g.UNMANAGED_ACTIONS;
1458
1682
  console.log(` \u25B6 \u5DF2\u5199\u5165 ${changed} \u5904\u53D8\u66F4\uFF0C${g.HEADER}`);
1459
1683
  for (const a of actions) {
@@ -1475,9 +1699,9 @@ function printMigrateGuidance(targetPath, changed) {
1475
1699
  }
1476
1700
  async function checkPrismaDiff(projectRoot, options) {
1477
1701
  if (!options.patch) return;
1478
- const basePath = resolve6(projectRoot, SYNC_PRISMA_CONFIG.BASE_SCHEMA);
1479
- const targetPath = resolve6(projectRoot, SYNC_PRISMA_CONFIG.TARGET_PATTERN);
1480
- if (!existsSync9(basePath)) {
1702
+ const basePath = resolve7(projectRoot, SYNC_PRISMA_CONFIG.BASE_SCHEMA);
1703
+ const targetPath = resolve7(projectRoot, SYNC_PRISMA_CONFIG.TARGET_PATTERN);
1704
+ if (!existsSync10(basePath)) {
1481
1705
  console.log(`
1482
1706
  \u26A0\uFE0F \u57FA\u51C6 schema \u4E0D\u5B58\u5728: ${basePath}`);
1483
1707
  console.log(` \u8BF7\u786E\u4FDD add-coder \u5DF2\u6B63\u786E\u5B89\u88C5\u3002`);
@@ -1489,7 +1713,7 @@ async function checkPrismaDiff(projectRoot, options) {
1489
1713
  \u2705 Prisma schema \u4E0E add-coder \u6807\u51C6\u4E00\u81F4\uFF0C\u65E0\u9700\u540C\u6B65\u3002`);
1490
1714
  return;
1491
1715
  }
1492
- const targetExists = existsSync9(targetPath);
1716
+ const targetExists = existsSync10(targetPath);
1493
1717
  let modifiedCount = 0;
1494
1718
  console.log(`
1495
1719
  \u26A0\uFE0F Prisma schema \u5DEE\u5F02\u68C0\u6D4B:`);
@@ -1640,7 +1864,7 @@ async function handleDiffAction(action, ctx) {
1640
1864
  }
1641
1865
  }
1642
1866
  function injectMissingModels(targetPath, models) {
1643
- let content = readFileSync7(targetPath, "utf-8");
1867
+ let content = readFileSync8(targetPath, "utf-8");
1644
1868
  content = content.replace(/\n*$/, "\n");
1645
1869
  content += `
1646
1870
  // ===== \u7531 add-coder sync --patch \u81EA\u52A8\u6CE8\u5165 (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}) =====
@@ -1653,7 +1877,7 @@ function injectMissingModels(targetPath, models) {
1653
1877
  return models.length;
1654
1878
  }
1655
1879
  function getBaseFieldLines(basePath, modelName) {
1656
- const content = readFileSync7(basePath, "utf-8");
1880
+ const content = readFileSync8(basePath, "utf-8");
1657
1881
  const blocks = parseSchemaBlocks(content);
1658
1882
  const block = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
1659
1883
  if (!block) return {};
@@ -1676,7 +1900,7 @@ function injectFieldLines(targetPath, basePath, modelName, fieldKeys) {
1676
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`);
1677
1901
  return 0;
1678
1902
  }
1679
- const content = readFileSync7(targetPath, "utf-8");
1903
+ const content = readFileSync8(targetPath, "utf-8");
1680
1904
  const lines = content.split("\n");
1681
1905
  const blocks = parseSchemaBlocks(content);
1682
1906
  const target = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
@@ -1723,7 +1947,7 @@ function injectFieldLines(targetPath, basePath, modelName, fieldKeys) {
1723
1947
  function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
1724
1948
  const baseFields = getBaseFieldLines(basePath, modelName);
1725
1949
  if (Object.keys(baseFields).length === 0) return 0;
1726
- let content = readFileSync7(targetPath, "utf-8");
1950
+ let content = readFileSync8(targetPath, "utf-8");
1727
1951
  let count = 0;
1728
1952
  for (const { fieldName } of conflicts) {
1729
1953
  const baseLine = baseFields[fieldName];
@@ -1739,8 +1963,8 @@ function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
1739
1963
  }
1740
1964
 
1741
1965
  // src/cli/commands/status.ts
1742
- import { existsSync as existsSync10 } from "fs";
1743
- import { resolve as resolve7 } from "path";
1966
+ import { existsSync as existsSync11 } from "fs";
1967
+ import { resolve as resolve8 } from "path";
1744
1968
  async function statusCommand() {
1745
1969
  const projectRoot = process.cwd();
1746
1970
  const config = await loadConfig(projectRoot);
@@ -1749,7 +1973,7 @@ async function statusCommand() {
1749
1973
  const missing = [];
1750
1974
  const present = [];
1751
1975
  for (const [relPath] of coreFiles) {
1752
- if (existsSync10(resolve7(projectRoot, relPath))) {
1976
+ if (existsSync11(resolve8(projectRoot, relPath))) {
1753
1977
  present.push(relPath);
1754
1978
  } else {
1755
1979
  missing.push(relPath);
@@ -1760,14 +1984,15 @@ async function statusCommand() {
1760
1984
  if (missing.length > 0) {
1761
1985
  console.log(` \u7F3A\u5931: ${missing.length} \u6587\u4EF6`);
1762
1986
  missing.forEach((f) => console.log(` - ${f}`));
1987
+ process.exit(1);
1763
1988
  } else {
1764
1989
  console.log(" \u6240\u6709\u6587\u4EF6\u5B8C\u6574\u3002");
1765
1990
  }
1766
1991
  }
1767
1992
 
1768
1993
  // src/cli/commands/stack.ts
1769
- import { readdirSync as readdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
1770
- import { resolve as resolve8, join as join5 } from "path";
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";
1771
1996
  import { createHash as createHash3 } from "crypto";
1772
1997
  var MAGIC_DIR_MAP3 = { claude: ".claude", qoder: ".qoder", vscode: ".vscode", trae: ".trae", codex: ".codex" };
1773
1998
  var HASH_OUTPUT_FILE = ".add-coder-hash.json";
@@ -1784,14 +2009,14 @@ function resolveMagicDir(projectRoot, specified) {
1784
2009
  return MAGIC_DIR_MAP3[adapter];
1785
2010
  }
1786
2011
  function listCustomProfiles(projectRoot, magicDir, registryNames) {
1787
- const dir = resolve8(projectRoot, magicDir, "rules", "profiles");
1788
- if (!existsSync11(dir)) return [];
2012
+ const dir = resolve9(projectRoot, magicDir, "rules", "profiles");
2013
+ if (!existsSync12(dir)) return [];
1789
2014
  return readdirSync3(dir).filter((f) => f.endsWith(".md") && !registryNames.has(f.replace(/-profile\.md$/, ""))).sort();
1790
2015
  }
1791
2016
  function profileExists(projectRoot, magicDir, name) {
1792
2017
  const registry = loadProfileRegistry();
1793
2018
  if (registry.some((p) => p.name === name)) return true;
1794
- return existsSync11(resolve8(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`));
2019
+ return existsSync12(resolve9(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`));
1795
2020
  }
1796
2021
  function stackCommand(sub, name, options = {}) {
1797
2022
  const projectRoot = process.cwd();
@@ -1828,12 +2053,12 @@ function stackCommand(sub, name, options = {}) {
1828
2053
  console.log("\u6280\u672F\u6808: \u672A\u8BBE\u7F6E\uFF08\u4E2D\u6027\uFF0C\u65E0\u6280\u672F\u6808\u5047\u8BBE\uFF09");
1829
2054
  return;
1830
2055
  }
1831
- const profilePath = resolve8(projectRoot, magicDir, "rules", "profiles", `${current}-profile.md`);
1832
- const stat = existsSync11(profilePath) ? readFileSync8(profilePath, "utf-8").length : 0;
2056
+ const profilePath = resolve9(projectRoot, magicDir, "rules", "profiles", `${current}-profile.md`);
2057
+ const stat = existsSync12(profilePath) ? readFileSync9(profilePath, "utf-8").length : 0;
1833
2058
  console.log(`\u6280\u672F\u6808: ${current}`);
1834
- console.log(`profile \u6587\u4EF6: ${profilePath}${existsSync11(profilePath) ? ` (${stat} \u5B57\u7B26)` : "\uFF08\u7F3A\u5931\uFF09"}`);
2059
+ console.log(`profile \u6587\u4EF6: ${profilePath}${existsSync12(profilePath) ? ` (${stat} \u5B57\u7B26)` : "\uFF08\u7F3A\u5931\uFF09"}`);
1835
2060
  try {
1836
- const raw = JSON.parse(readFileSync8(resolve8(projectRoot, magicDir, "stack.json"), "utf-8"));
2061
+ const raw = JSON.parse(readFileSync9(resolve9(projectRoot, magicDir, "stack.json"), "utf-8"));
1837
2062
  if (raw.updatedAt) console.log(`\u66F4\u65B0\u65F6\u95F4: ${raw.updatedAt}`);
1838
2063
  } catch {
1839
2064
  }
@@ -1858,9 +2083,9 @@ function buildConfig(projectRoot, magicDir, stack) {
1858
2083
  stack
1859
2084
  };
1860
2085
  try {
1861
- const pkgPath = resolve8(projectRoot, "package.json");
1862
- if (existsSync11(pkgPath)) {
1863
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
2086
+ const pkgPath = resolve9(projectRoot, "package.json");
2087
+ if (existsSync12(pkgPath)) {
2088
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
1864
2089
  if (pkg.name) config.projectName = pkg.name;
1865
2090
  }
1866
2091
  } catch {
@@ -1869,9 +2094,9 @@ function buildConfig(projectRoot, magicDir, stack) {
1869
2094
  }
1870
2095
  function applyStack(projectRoot, magicDir, name) {
1871
2096
  if (!profileExists(projectRoot, magicDir, name)) {
1872
- const registry = loadProfileRegistry();
2097
+ const registry2 = loadProfileRegistry();
1873
2098
  console.error(`\u2717 profile \u4E0D\u5B58\u5728: ${name}`);
1874
- console.error(` \u5185\u7F6E: ${registry.map((p) => p.name).join(" | ")}\uFF08\u6216\u81EA\u5B9A\u4E49: ${magicDir}/rules/profiles/{name}-profile.md\uFF09`);
2099
+ console.error(` \u5185\u7F6E: ${registry2.map((p) => p.name).join(" | ")}\uFF08\u6216\u81EA\u5B9A\u4E49: ${magicDir}/rules/profiles/{name}-profile.md\uFF09`);
1875
2100
  process.exit(1);
1876
2101
  }
1877
2102
  const config = buildConfig(projectRoot, magicDir, name);
@@ -1879,42 +2104,69 @@ function applyStack(projectRoot, magicDir, name) {
1879
2104
  const coreFiles = renderCore(config, false);
1880
2105
  const stackRelated = /* @__PURE__ */ new Map();
1881
2106
  for (const [relPath, content] of coreFiles) {
1882
- if (relPath.includes("/rules/profiles/") || relPath.endsWith("/rules/project_rules.md")) {
1883
- stackRelated.set(relPath, content);
2107
+ const rp = normalizeRelPath(relPath);
2108
+ if (rp.includes("/rules/profiles/") || rp.endsWith("/rules/project_rules.md")) {
2109
+ stackRelated.set(rp, content);
1884
2110
  }
1885
2111
  }
1886
2112
  const hashMap = {};
1887
2113
  try {
1888
- Object.assign(hashMap, JSON.parse(readFileSync8(resolve8(projectRoot, magicDir, HASH_OUTPUT_FILE), "utf-8")));
2114
+ Object.assign(hashMap, JSON.parse(readFileSync9(resolve9(projectRoot, magicDir, HASH_OUTPUT_FILE), "utf-8")));
1889
2115
  } catch {
1890
2116
  }
1891
2117
  let written = 0;
1892
2118
  for (const [relPath, content] of stackRelated) {
1893
2119
  for (const t of [".add", magicDir]) {
1894
- const targetPath = resolve8(projectRoot, relPath.replace(/^\.add/, t));
1895
- mkdirSync6(join5(targetPath, ".."), { recursive: true });
2120
+ const targetPath = resolve9(projectRoot, relPath.replace(/^\.add/, t));
2121
+ mkdirSync6(join6(targetPath, ".."), { recursive: true });
1896
2122
  writeFileSync6(targetPath, content, "utf-8");
1897
2123
  hashMap[relPath.replace(/^\.add/, t)] = hash82(content);
1898
2124
  written++;
1899
2125
  }
1900
2126
  }
1901
- writeFileSync6(resolve8(projectRoot, magicDir, HASH_OUTPUT_FILE), JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
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");
1902
2142
  console.log(`\u2705 \u6280\u672F\u6808\u5DF2\u8BBE\u7F6E\u4E3A ${name}`);
1903
2143
  console.log(` ${magicDir}/stack.json \u2192 ${name}`);
1904
- console.log(` ${magicDir}/rules/profiles/${name}-profile.md ${existsSync11(resolve8(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`)) ? "\u5DF2\u5C31\u4F4D" : "\uFF08\u81EA\u5B9A\u4E49 profile\uFF0C\u9879\u76EE\u4FA7\u6587\u4EF6\uFF09"}`);
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"}`);
1905
2145
  console.log(` project_rules.md \u5F15\u7528\u884C\u5DF2\u66F4\u65B0 + hash \u5DF2\u5237\u65B0\uFF08${written} \u4E2A\u6587\u4EF6\uFF09`);
1906
2146
  }
1907
2147
 
1908
2148
  // src/cli/index.ts
1909
2149
  var { version } = JSON.parse(
1910
- readFileSync9(new URL("../package.json", import.meta.url), "utf-8")
2150
+ readFileSync10(new URL("../package.json", import.meta.url), "utf-8")
1911
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
+ }
1912
2163
  var program = new Command();
1913
2164
  program.name("add-coder").description("\u521D\u59CB\u5316 ADD \u8303\u5F0F\u5DE5\u4F5C\u6D41\u6A21\u677F").version(version);
1914
- 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);
1915
- 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);
1916
2167
  program.command("status").description("\u68C0\u67E5 ADD \u6A21\u677F\u5B8C\u6574\u6027").action(statusCommand);
1917
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(
1918
2169
  (sub, name, options) => stackCommand(sub, name, options)
1919
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);
1920
2172
  program.parse();