add-coder 0.3.4 → 0.3.6-feature-hitl-enhance-v1.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.
Files changed (27) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +358 -42
  3. package/package.json +2 -1
  4. package/templates/.add-coder-src-hash.json +24 -18
  5. package/templates/adapters/claude/hooks/pre-tool-use.sh +33 -1
  6. package/templates/adapters/codex/hooks/doc-format-guard.sh +6 -2
  7. package/templates/adapters/codex/hooks/lib/state-detect.sh +111 -0
  8. package/templates/adapters/codex/hooks/pre-tool-use.sh +33 -2
  9. package/templates/adapters/qoder/hooks/pre-tool-use.sh +33 -1
  10. package/templates/adapters/trae/hooks/doc-format-guard.sh +6 -2
  11. package/templates/adapters/trae/hooks/lib/state-detect.sh +111 -0
  12. package/templates/adapters/trae/hooks/pre-tool-use.sh +33 -2
  13. package/templates/adapters/vscode/hooks/pre-tool-use.sh +33 -1
  14. package/templates/core/hooks/doc-format-guard.sh +1 -0
  15. package/templates/core/hooks/pre-tool-use.sh +32 -1
  16. package/templates/core/prisma/add.prisma +77 -0
  17. package/templates/core/rules/project_rules.md +51 -0
  18. package/templates/core/scripts/mcp-server/shared/package.json +1 -0
  19. package/templates/core/scripts/mcp-server/shared/prisma.ts +6 -2
  20. package/templates/core/scripts/mcp-server/tools/gateway.ts +249 -39
  21. package/templates/core/scripts/mcp-server/tools/hitl.ts +435 -0
  22. package/templates/core/scripts/mcp-server/tools/index.ts +7 -1
  23. package/templates/core/scripts/mcp-server/tools/plan.ts +191 -0
  24. package/templates/core/scripts/mcp-server/tools/review.ts +199 -0
  25. package/templates/core/skills/add-paradigm/SKILL.md +81 -3
  26. package/templates/core/templates/hitl-template.md +30 -0
  27. package/templates/core/templates/hitl-template.schema.json +23 -0
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Make 0.75 into one.
4
4
 
5
+ ![npm downloads](https://img.shields.io/npm/dm/add-coder)
6
+
5
7
  > 🀄中文 | 🔤[English](#-english-readme)
6
8
 
7
9
  **AI 代码治理的落地方案** — [codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。 [NPM](https://www.npmjs.com/package/add-coder) · [GitHub](https://github.com/xiaomingming92/add-coder)
@@ -9,7 +11,7 @@ Make 0.75 into one.
9
11
 
10
12
  > 🧭 **从零上手实操?** 请参见 [GUIDE.md](https://github.com/xiaomingming92/add-coder/blob/main/GUIDE.md) — 包含触发词速查、需求转 Plan、完整链路演练。
11
13
  >
12
- > 📖 **想看 ADD 范式真实落地案例?** → [老设备续命工程](#-案例示范老安卓设备续命工程) — weather_proxy 实践。
14
+ > 📖 **想看 ADD 范式真实落地案例?** → [老设备续命工程](#-案例示范老安卓设备续命工程) — weather_proxy 实践。->或者add-coder项目本身就是很好的落地案例(自举成功)。
13
15
  >
14
16
  > 🔄 **add-coder 升级后担心更新本地Skills,rules,hooks,templates等等会很麻烦?** `npx add-coder sync --adapter=qoder --patch` — 实操看 [GUIDE.md §版本升级](https://github.com/xiaomingming92/add-coder/blob/main/GUIDE.md#%E4%B8%83add-coder-%E5%8D%87%E7%BA%A7%E5%90%8E%E6%80%8E%E4%B9%88%E6%9B%B4%E6%96%B0%E6%9C%AC%E5%9C%B0%E6%96%87%E4%BB%B6) | 原理看 [DEVELOPMENT.md](https://github.com/xiaomingming92/add-coder/blob/main/DEVELOPMENT.md)
15
17
 
package/dist/index.js CHANGED
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/cli/index.ts
4
- import { readFileSync as readFileSync7 } from "fs";
10
+ import { readFileSync as readFileSync8 } from "fs";
5
11
  import { Command } from "commander";
6
12
 
7
13
  // src/caijuehub/strategies/detect.strategy.ts
@@ -296,13 +302,97 @@ async function writeFiles(projectRoot, files, options = {}) {
296
302
  return { created, skipped, overwritten };
297
303
  }
298
304
 
305
+ // src/caijuehub/strategies/prisma-sync.strategy.ts
306
+ var SYNC_PRISMA_CONFIG = {
307
+ BASE_SCHEMA: "node_modules/add-coder/templates/core/prisma/add.prisma",
308
+ TARGET_PATTERN: "prisma/add.prisma",
309
+ SYNC_ITEMS: ["model", "enum"],
310
+ ON_MISSING_MODEL: "interactive",
311
+ ON_FIELD_CONFLICT: "skip",
312
+ ON_MISSING_FIELD: "interactive",
313
+ ON_EXTRA_FIELD: "ignore",
314
+ PROMPT: "add-coder \u6807\u51C6 add.prisma \u4E0E\u6D88\u8D39\u65B9 schema \u5B58\u5728\u5DEE\u5F02\u3002"
315
+ };
316
+
299
317
  // src/cli/writer.ts
318
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
300
319
  async function writeFiles2(projectRoot, files, options = {}) {
301
320
  return writeFiles(projectRoot, files, options);
302
321
  }
322
+ function parseSchemaBlocks(content) {
323
+ const blocks = /* @__PURE__ */ new Map();
324
+ const blockRegex = /^(model|enum)\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gm;
325
+ let match;
326
+ while ((match = blockRegex.exec(content)) !== null) {
327
+ const [, type, name, inner] = match;
328
+ let fields = [];
329
+ if (type === "enum") {
330
+ fields = inner.split("\n").map((l) => l.replace(/#.*$/, "").trim()).filter((l) => l.length > 0);
331
+ } else {
332
+ const fieldRegex = /^\s*(\w+)\s+(\w+(?:\([^)]*\))?(?:\[\])?\??)/gm;
333
+ let fm;
334
+ while ((fm = fieldRegex.exec(inner)) !== null) {
335
+ fields.push(`${fm[1]}:${fm[2]}`);
336
+ }
337
+ }
338
+ blocks.set(`${type}:${name}`, { type, name, body: match[0], fields });
339
+ }
340
+ return blocks;
341
+ }
342
+ function diffPrisma(basePath, targetPath) {
343
+ const baseContent = existsSync4(basePath) ? readFileSync3(basePath, "utf-8") : "";
344
+ const targetContent = existsSync4(targetPath) ? readFileSync3(targetPath, "utf-8") : "";
345
+ if (!baseContent) return { hasDiff: false, baseSchema: basePath, targetPath, missing: [], fieldDiffs: [] };
346
+ const baseBlocks = parseSchemaBlocks(baseContent);
347
+ const targetBlocks = parseSchemaBlocks(targetContent);
348
+ const missing = [];
349
+ const fieldDiffs = [];
350
+ const types = SYNC_PRISMA_CONFIG.SYNC_ITEMS;
351
+ for (const [key, block] of baseBlocks) {
352
+ if (!types.includes(block.type)) continue;
353
+ if (!targetBlocks.has(key)) {
354
+ missing.push(block);
355
+ } else {
356
+ const target = targetBlocks.get(key);
357
+ const baseByName = /* @__PURE__ */ new Map();
358
+ for (const f of block.fields) {
359
+ const [name] = f.split(":");
360
+ baseByName.set(name, f);
361
+ }
362
+ const targetByName = /* @__PURE__ */ new Map();
363
+ for (const f of target.fields) {
364
+ const [name] = f.split(":");
365
+ targetByName.set(name, f);
366
+ }
367
+ const conflicts = [];
368
+ const missingFields = [];
369
+ const extraFields = [];
370
+ for (const [name, baseDef] of baseByName) {
371
+ if (targetByName.has(name)) {
372
+ const targetDef = targetByName.get(name);
373
+ if (baseDef !== targetDef) {
374
+ conflicts.push({ fieldName: name, baseDef, targetDef });
375
+ }
376
+ } else {
377
+ missingFields.push(baseDef);
378
+ }
379
+ }
380
+ for (const [name, targetDef] of targetByName) {
381
+ if (!baseByName.has(name)) {
382
+ extraFields.push(targetDef);
383
+ }
384
+ }
385
+ if (conflicts.length > 0 || missingFields.length > 0 || extraFields.length > 0) {
386
+ fieldDiffs.push({ type: block.type, name: block.name, conflicts, missingFields, extraFields });
387
+ }
388
+ }
389
+ }
390
+ const hasDiff = missing.length > 0 || fieldDiffs.length > 0;
391
+ return { hasDiff, baseSchema: basePath, targetPath, missing, fieldDiffs };
392
+ }
303
393
 
304
394
  // src/core/renderer.ts
305
- import { readFileSync as readFileSync3, readdirSync, statSync } from "fs";
395
+ import { readFileSync as readFileSync4, readdirSync, statSync } from "fs";
306
396
  import { join as join4, relative, dirname as dirname2 } from "path";
307
397
  import { fileURLToPath } from "url";
308
398
  var __filename = fileURLToPath(import.meta.url);
@@ -339,7 +429,7 @@ function renderCore(config, dryRun) {
339
429
  if (statSync(full).isDirectory()) {
340
430
  if (!SKIP_DIRS.has(name)) walk(full, join4(base, name));
341
431
  } else {
342
- const content = readFileSync3(full, "utf-8");
432
+ const content = readFileSync4(full, "utf-8");
343
433
  const rendered = render(content, config);
344
434
  const targetRel = join4(CORE_TARGET, relative(CORE_DIR, full));
345
435
  files.set(targetRel, rendered);
@@ -365,7 +455,7 @@ function renderAdapterBase(config, magicPath, githubHooksToRoot, dryRun) {
365
455
  if (statSync(full).isDirectory()) {
366
456
  walk(full, join4(base, name), targetPrefix);
367
457
  } else {
368
- const content = readFileSync3(full, "utf-8");
458
+ const content = readFileSync4(full, "utf-8");
369
459
  const rendered = render(content, config);
370
460
  let targetRel;
371
461
  if (targetPrefix) {
@@ -414,7 +504,7 @@ function renderAdapter5(config, targetDir, dryRun, magicDir) {
414
504
 
415
505
  // src/lib/utils.ts
416
506
  import { createInterface as createInterface2 } from "readline";
417
- import { existsSync as existsSync4 } from "fs";
507
+ import { existsSync as existsSync5 } from "fs";
418
508
  import { resolve as resolve2 } from "path";
419
509
  function ask(q) {
420
510
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
@@ -426,7 +516,7 @@ function ask(q) {
426
516
  });
427
517
  }
428
518
  function detectPm(projectRoot) {
429
- return existsSync4(resolve2(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
519
+ return existsSync5(resolve2(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
430
520
  }
431
521
 
432
522
  // src/cli/prisma-injector.ts
@@ -435,7 +525,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
435
525
 
436
526
  // src/caijuehub/strategies/prisma.strategy.ts
437
527
  import { spawnSync } from "child_process";
438
- import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
528
+ import { copyFileSync, existsSync as existsSync6, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
439
529
  import { resolve as resolve3 } from "path";
440
530
  var PRISMA_CONFIG = {
441
531
  onMissing: "ask",
@@ -488,10 +578,10 @@ function runPrismaInit(projectRoot, provider, schemaPath) {
488
578
  stdio: "inherit",
489
579
  shell: false
490
580
  });
491
- if (initResult.status !== 0 || !existsSync5(schemaPath)) {
581
+ if (initResult.status !== 0 || !existsSync6(schemaPath)) {
492
582
  console.log("prisma init \u5931\u8D25\uFF0C\u624B\u52A8\u521B\u5EFA schema.prisma ...");
493
583
  const prismaDir = resolve3(projectRoot, "prisma");
494
- if (!existsSync5(prismaDir)) mkdirSync2(prismaDir, { recursive: true });
584
+ if (!existsSync6(prismaDir)) mkdirSync2(prismaDir, { recursive: true });
495
585
  const content = `generator client {
496
586
  provider = "prisma-client-js"
497
587
  }
@@ -502,7 +592,7 @@ datasource db {
502
592
  `;
503
593
  writeFileSync2(schemaPath, content, "utf-8");
504
594
  const devEnvPath = resolve3(projectRoot, ".env.development");
505
- if (!existsSync5(devEnvPath)) {
595
+ if (!existsSync6(devEnvPath)) {
506
596
  const defaultUrl = provider === "sqlite" ? 'DATABASE_URL="file:./data/dev.db"' : '# \u8BF7\u7F16\u8F91\u4E3A\u4F60\u7684\u6570\u636E\u5E93\u8FDE\u63A5\u4FE1\u606F\nDATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DB?schema=public"';
507
597
  writeFileSync2(devEnvPath, defaultUrl + "\n", "utf-8");
508
598
  console.log("\u5DF2\u521B\u5EFA .env.development");
@@ -514,16 +604,16 @@ datasource db {
514
604
  function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
515
605
  const envPath = resolve3(projectRoot, ".env");
516
606
  const devEnvPath = resolve3(projectRoot, ".env.development");
517
- if (existsSync5(envPath)) {
518
- const envContent = readFileSync4(envPath, "utf-8");
607
+ if (existsSync6(envPath)) {
608
+ const envContent = readFileSync5(envPath, "utf-8");
519
609
  const dbUrl = envContent.match(/DATABASE_URL=.*/);
520
610
  if (dbUrl) {
521
- const existing = existsSync5(devEnvPath) ? readFileSync4(devEnvPath, "utf-8") : "";
611
+ const existing = existsSync6(devEnvPath) ? readFileSync5(devEnvPath, "utf-8") : "";
522
612
  if (!existing.includes("DATABASE_URL=")) {
523
613
  writeFileSync2(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
524
614
  `, "utf-8");
525
615
  }
526
- if (existsSync5(envPath)) unlinkSync(envPath);
616
+ if (existsSync6(envPath)) unlinkSync(envPath);
527
617
  console.log("\u5DF2\u5C06 DATABASE_URL \u8FC1\u79FB\u5230 .env.development");
528
618
  }
529
619
  }
@@ -536,7 +626,7 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
536
626
  const schemaPath = resolve3(prismaDir, "schema.prisma");
537
627
  const destPath = resolve3(prismaDir, "add.prisma");
538
628
  let justInited = false;
539
- if (!existsSync5(prismaDir) || !existsSync5(schemaPath)) {
629
+ if (!existsSync6(prismaDir) || !existsSync6(schemaPath)) {
540
630
  if (C.onMissing === "skip") {
541
631
  console.log("\u8DF3\u8FC7\uFF1A\u7F3A\u5C11 Prisma");
542
632
  return true;
@@ -555,7 +645,7 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
555
645
  postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath);
556
646
  justInited = true;
557
647
  }
558
- if (existsSync5(destPath) && !justInited) {
648
+ if (existsSync6(destPath) && !justInited) {
559
649
  if (options.dryRun) {
560
650
  console.log("[dry-run] \u5DF2\u6709 add.prisma");
561
651
  return true;
@@ -572,8 +662,8 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
572
662
  console.log("\u8986\u76D6");
573
663
  } else if (choice === "d") {
574
664
  copyFileSync(destPath, destPath + ".bak");
575
- console.log("=== \u5F53\u524D\uFF08\u5DF2\u5907\u4EFD\uFF09===\n" + readFileSync4(destPath, "utf-8"));
576
- console.log("=== \u6A21\u677F ===\n" + readFileSync4(addPrismaTemplate, "utf-8"));
665
+ console.log("=== \u5F53\u524D\uFF08\u5DF2\u5907\u4EFD\uFF09===\n" + readFileSync5(destPath, "utf-8"));
666
+ console.log("=== \u6A21\u677F ===\n" + readFileSync5(addPrismaTemplate, "utf-8"));
577
667
  if (await ask("\u786E\u8BA4\u8986\u76D6\uFF1F[y/N] ") !== "y") {
578
668
  console.log("\u5DF2\u8DF3\u8FC7");
579
669
  return true;
@@ -628,7 +718,7 @@ async function injectPrisma2(projectRoot, options = {}) {
628
718
  }
629
719
 
630
720
  // src/cli/commands/init.ts
631
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
721
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
632
722
  import { createHash } from "crypto";
633
723
  import { resolve as resolve5 } from "path";
634
724
  import { spawnSync as spawnSync2 } from "child_process";
@@ -808,18 +898,18 @@ main().catch((e) => { console.error(e); process.exit(1); });
808
898
  console.log(`[dry-run] \u5C06\u5199\u5165 ${scriptPath}`);
809
899
  return;
810
900
  }
811
- if (!existsSync6(scriptsDir)) mkdirSync3(scriptsDir, { recursive: true });
901
+ if (!existsSync7(scriptsDir)) mkdirSync3(scriptsDir, { recursive: true });
812
902
  writeFileSync3(scriptPath, content, "utf-8");
813
903
  console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
814
904
  }
815
905
  function injectDbExportScript(projectRoot, dryRun) {
816
906
  const pkgPath = resolve5(projectRoot, "package.json");
817
- if (!existsSync6(pkgPath)) return;
907
+ if (!existsSync7(pkgPath)) return;
818
908
  if (dryRun) {
819
909
  console.log("[dry-run] \u5C06\u6CE8\u5165 db:export");
820
910
  return;
821
911
  }
822
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
912
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
823
913
  if (!pkg.scripts) pkg.scripts = {};
824
914
  if (!pkg.scripts["db:export"]) {
825
915
  pkg.scripts["db:export"] = "npx tsx scripts/export-db.ts";
@@ -849,14 +939,14 @@ function writeComposeEnv(ctx) {
849
939
  if (!db.reuseExisting) {
850
940
  const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
851
941
  const composePath = resolve5(projectRoot, composeName);
852
- if (!options.dryRun && (!existsSync6(composePath) || options.force)) {
942
+ if (!options.dryRun && (!existsSync7(composePath) || options.force)) {
853
943
  writeFileSync3(composePath, composeContent(config.projectName || "add-project"), "utf-8");
854
944
  console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
855
945
  }
856
946
  }
857
947
  const devEnvPath = resolve5(projectRoot, ".env.development");
858
- if (!options.dryRun && existsSync6(devEnvPath)) {
859
- const existing = readFileSync5(devEnvPath, "utf-8");
948
+ if (!options.dryRun && existsSync7(devEnvPath)) {
949
+ const existing = readFileSync6(devEnvPath, "utf-8");
860
950
  if (!/^DATABASE_USER=/m.test(existing)) {
861
951
  writeFileSync3(devEnvPath, existing + `
862
952
  DATABASE_USER=${db.user || "admin"}
@@ -896,7 +986,7 @@ async function renderAndWrite(ctx) {
896
986
  }
897
987
  for (const d of [".add", magicDir]) {
898
988
  const reviewsDir = resolve5(projectRoot, d, "reviews");
899
- if (!existsSync6(reviewsDir)) {
989
+ if (!existsSync7(reviewsDir)) {
900
990
  if (dry) {
901
991
  console.log(`[dry-run] \u5C06\u521B\u5EFA ${reviewsDir}/`);
902
992
  } else {
@@ -908,7 +998,7 @@ async function renderAndWrite(ctx) {
908
998
  const hashMap = {};
909
999
  let npmVer = "";
910
1000
  try {
911
- npmVer = JSON.parse(readFileSync5(resolve5(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
1001
+ npmVer = JSON.parse(readFileSync6(resolve5(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
912
1002
  } catch {
913
1003
  }
914
1004
  for (const [rp, c] of allFiles) {
@@ -940,7 +1030,7 @@ async function deployDatabase(ctx) {
940
1030
  }
941
1031
  if (db.engine === "postgresql" && db.container === "manual") {
942
1032
  const dbScript = resolve5(projectRoot, magicDir, "scripts", "db-ensure.sh");
943
- if (existsSync6(dbScript)) spawnSync2("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
1033
+ if (existsSync7(dbScript)) spawnSync2("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
944
1034
  console.log([
945
1035
  "",
946
1036
  "\u2501".repeat(30),
@@ -981,12 +1071,12 @@ function deployDocs(ctx) {
981
1071
  for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
982
1072
  const srcDir = resolve5(groundingSrc, d);
983
1073
  const destDir = resolve5(docsBase, d);
984
- if (!existsSync6(destDir)) mkdirSync3(destDir, { recursive: true });
985
- if (!existsSync6(srcDir)) continue;
1074
+ if (!existsSync7(destDir)) mkdirSync3(destDir, { recursive: true });
1075
+ if (!existsSync7(srcDir)) continue;
986
1076
  for (const f of readdirSync2(srcDir)) {
987
1077
  const src = resolve5(srcDir, f);
988
1078
  const dest = resolve5(destDir, f);
989
- if (existsSync6(dest)) continue;
1079
+ if (existsSync7(dest)) continue;
990
1080
  try {
991
1081
  copyFileSync2(src, dest);
992
1082
  } catch {
@@ -1000,7 +1090,7 @@ function finalize(ctx, result) {
1000
1090
  \u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}, \u8986\u76D6 ${result.overwritten}`);
1001
1091
  if (options.dryRun) return;
1002
1092
  if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
1003
- const pkg = JSON.parse(readFileSync5(resolve5(import.meta.dirname, "../package.json"), "utf-8"));
1093
+ const pkg = JSON.parse(readFileSync6(resolve5(import.meta.dirname, "../package.json"), "utf-8"));
1004
1094
  const peerNames = Object.keys(pkg.peerDependencies || {});
1005
1095
  if (peerNames.length > 0) {
1006
1096
  console.log(`
@@ -1014,7 +1104,7 @@ function finalize(ctx, result) {
1014
1104
  }
1015
1105
 
1016
1106
  // src/cli/commands/sync.ts
1017
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1107
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1018
1108
  import { resolve as resolve6 } from "path";
1019
1109
  import { createHash as createHash2 } from "crypto";
1020
1110
 
@@ -1068,14 +1158,14 @@ function hash8(c) {
1068
1158
  }
1069
1159
  function loadHashFile(root, magic) {
1070
1160
  try {
1071
- return JSON.parse(readFileSync6(resolve6(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), "utf-8"));
1161
+ return JSON.parse(readFileSync7(resolve6(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), "utf-8"));
1072
1162
  } catch {
1073
1163
  return {};
1074
1164
  }
1075
1165
  }
1076
1166
  function loadVersionFile(root, magic) {
1077
1167
  try {
1078
- return readFileSync6(resolve6(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
1168
+ return readFileSync7(resolve6(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
1079
1169
  } catch {
1080
1170
  return "";
1081
1171
  }
@@ -1132,7 +1222,7 @@ async function syncCommand(options = {}) {
1132
1222
  const srcHashPath = resolve6(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json");
1133
1223
  let npmVersion = "";
1134
1224
  try {
1135
- npmVersion = JSON.parse(readFileSync6(srcHashPath, "utf-8"))._version ?? "";
1225
+ npmVersion = JSON.parse(readFileSync7(srcHashPath, "utf-8"))._version ?? "";
1136
1226
  } catch {
1137
1227
  }
1138
1228
  const installedVersion = loadVersionFile(projectRoot, magicDir);
@@ -1152,12 +1242,12 @@ async function syncCommand(options = {}) {
1152
1242
  let sameCount = 0;
1153
1243
  for (const [relPath, content] of candidates) {
1154
1244
  const absPath = resolve6(projectRoot, relPath);
1155
- if (!existsSync7(absPath)) {
1245
+ if (!existsSync8(absPath)) {
1156
1246
  missingFiles.set(relPath, content);
1157
1247
  } else if (establishBaseline) {
1158
1248
  missingFiles.set(relPath, content);
1159
1249
  } else {
1160
- const curH = hash8(readFileSync6(absPath, "utf-8"));
1250
+ const curH = hash8(readFileSync7(absPath, "utf-8"));
1161
1251
  const storedH = outHash[relPath];
1162
1252
  if (storedH && curH === storedH) {
1163
1253
  sameCount++;
@@ -1185,11 +1275,12 @@ async function syncCommand(options = {}) {
1185
1275
  }
1186
1276
  saveHashFile(projectRoot, magicDir, new Map([...missingFiles, ...conflictFiles]));
1187
1277
  saveVersionFile(projectRoot, magicDir, npmVersion);
1278
+ await checkPrismaDiff(projectRoot, options);
1188
1279
  return;
1189
1280
  }
1190
1281
  const missing = /* @__PURE__ */ new Map();
1191
1282
  for (const [relPath, content] of allFiles) {
1192
- if (!existsSync7(resolve6(projectRoot, relPath))) {
1283
+ if (!existsSync8(resolve6(projectRoot, relPath))) {
1193
1284
  missing.set(relPath, content);
1194
1285
  }
1195
1286
  }
@@ -1207,10 +1298,235 @@ async function syncCommand(options = {}) {
1207
1298
  }
1208
1299
  const result = await writeFiles2(projectRoot, filesToWrite, { yes: true });
1209
1300
  console.log(`\u540C\u6B65\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}`);
1301
+ await checkPrismaDiff(projectRoot, options);
1302
+ }
1303
+ async function checkPrismaDiff(projectRoot, options) {
1304
+ if (!options.patch) return;
1305
+ const basePath = resolve6(projectRoot, SYNC_PRISMA_CONFIG.BASE_SCHEMA);
1306
+ const targetPath = resolve6(projectRoot, SYNC_PRISMA_CONFIG.TARGET_PATTERN);
1307
+ if (!existsSync8(basePath)) {
1308
+ console.log(`
1309
+ \u26A0\uFE0F \u57FA\u51C6 schema \u4E0D\u5B58\u5728: ${basePath}`);
1310
+ console.log(` \u8BF7\u786E\u4FDD add-coder \u5DF2\u6B63\u786E\u5B89\u88C5\u3002`);
1311
+ return;
1312
+ }
1313
+ const result = diffPrisma(basePath, targetPath);
1314
+ if (!result.hasDiff) {
1315
+ console.log(`
1316
+ \u2705 Prisma schema \u4E0E add-coder \u6807\u51C6\u4E00\u81F4\uFF0C\u65E0\u9700\u540C\u6B65\u3002`);
1317
+ return;
1318
+ }
1319
+ const targetExists = existsSync8(targetPath);
1320
+ console.log(`
1321
+ \u26A0\uFE0F Prisma schema \u5DEE\u5F02\u68C0\u6D4B:`);
1322
+ console.log(` \u57FA\u51C6: ${result.baseSchema} (add-coder \u6807\u51C6)`);
1323
+ if (targetExists) console.log(` \u76EE\u6807: ${result.targetPath} (\u6D88\u8D39\u65B9)`);
1324
+ if (result.missing.length > 0 && targetExists) {
1325
+ let selected = [];
1326
+ await handleDiffAction(SYNC_PRISMA_CONFIG.ON_MISSING_MODEL, {
1327
+ label: "\u7F3A\u5931\u6A21\u578B/\u679A\u4E3E",
1328
+ show() {
1329
+ console.log(`
1330
+ \u{1F534} \u7F3A\u5931\u6A21\u578B/\u679A\u4E3E\uFF08${result.missing.length} \u9879\uFF09:`);
1331
+ for (let i = 0; i < result.missing.length; i++) {
1332
+ const m = result.missing[i];
1333
+ console.log(` [${i + 1}] ${m.type} ${m.name} (${m.fields.length} \u5B57\u6BB5)`);
1334
+ }
1335
+ },
1336
+ async confirm() {
1337
+ const ans = await ask(`
1338
+ \u8F93\u5165\u7F16\u53F7\u9009\u62E9\u6027\u6CE8\u5165\uFF08\u5982 1,3\uFF09\uFF0Ca \u5168\u90E8\u6CE8\u5165\uFF0C\u56DE\u8F66\u8DF3\u8FC7: `);
1339
+ if (ans === "a" || ans === "all") {
1340
+ selected = [...result.missing];
1341
+ return true;
1342
+ }
1343
+ if (!ans.trim()) return false;
1344
+ const indices = ans.split(/[,,]+/).map((s) => parseInt(s.trim()) - 1).filter((i) => !isNaN(i) && i >= 0 && i < result.missing.length);
1345
+ if (indices.length === 0) return false;
1346
+ selected = indices.map((i) => result.missing[i]);
1347
+ return true;
1348
+ },
1349
+ async execute() {
1350
+ const n = injectMissingModels(result.targetPath, selected);
1351
+ console.log(` \u2705 \u5DF2\u5C06 ${n} \u4E2A\u6A21\u578B/\u679A\u4E3E\u6CE8\u5165 ${result.targetPath}`);
1352
+ return n;
1353
+ }
1354
+ });
1355
+ }
1356
+ if (result.fieldDiffs.length > 0 && targetExists) {
1357
+ console.log(`
1358
+ \u{1F7E1} \u5B57\u6BB5\u7EA7\u5DEE\u5F02\uFF08${result.fieldDiffs.length} \u4E2A\u6A21\u578B/\u679A\u4E3E\uFF09:`);
1359
+ for (const d of result.fieldDiffs) {
1360
+ console.log(`
1361
+ \u2500\u2500 ${d.type} ${d.name}:`);
1362
+ if (d.conflicts.length > 0) {
1363
+ await handleDiffAction(SYNC_PRISMA_CONFIG.ON_FIELD_CONFLICT, {
1364
+ label: `\u51B2\u7A81\u5B57\u6BB5`,
1365
+ show() {
1366
+ console.log(` \u26A1 \u51B2\u7A81\u5B57\u6BB5\uFF08\u540C\u540D\u5B57\u6BB5\u3001\u5B9A\u4E49\u4E0D\u540C\uFF09:`);
1367
+ for (const c of d.conflicts) {
1368
+ console.log(` \xB7 ${c.fieldName}`);
1369
+ console.log(` \u57FA\u51C6: ${c.baseDef}`);
1370
+ console.log(` \u6D88\u8D39\u65B9: ${c.targetDef}`);
1371
+ }
1372
+ },
1373
+ async confirm() {
1374
+ const ans = await ask(` \u662F\u5426\u7528\u57FA\u51C6\u5B9A\u4E49\u8986\u76D6\u8FD9\u4E9B\u51B2\u7A81\u5B57\u6BB5\uFF1F(y/n): `);
1375
+ return ans === "y" || ans === "yes";
1376
+ },
1377
+ async execute() {
1378
+ const n = overwriteFieldLines(targetPath, basePath, d.name, d.conflicts);
1379
+ console.log(` \u2705 \u5DF2\u8986\u76D6 ${n} \u4E2A\u51B2\u7A81\u5B57\u6BB5`);
1380
+ return n;
1381
+ }
1382
+ });
1383
+ }
1384
+ if (d.missingFields.length > 0) {
1385
+ await handleDiffAction(SYNC_PRISMA_CONFIG.ON_MISSING_FIELD, {
1386
+ label: `\u7F3A\u5931\u5B57\u6BB5`,
1387
+ show() {
1388
+ console.log(` \u{1F4CB} \u6D88\u8D39\u65B9\u7F3A\u5C11 ${d.missingFields.length} \u4E2A\u5B57\u6BB5:`);
1389
+ for (const f of d.missingFields) {
1390
+ console.log(` + ${f}`);
1391
+ }
1392
+ },
1393
+ async confirm() {
1394
+ const ans = await ask(` \u662F\u5426\u8865\u5145\u8FD9\u4E9B\u7F3A\u5931\u5B57\u6BB5\uFF1F(y/n): `);
1395
+ return ans === "y" || ans === "yes";
1396
+ },
1397
+ async execute() {
1398
+ const n = injectFieldLines(targetPath, basePath, d.name, d.missingFields);
1399
+ console.log(` \u2705 \u5DF2\u8865\u5145 ${n} \u4E2A\u5B57\u6BB5`);
1400
+ return n;
1401
+ }
1402
+ });
1403
+ }
1404
+ if (d.extraFields.length > 0) {
1405
+ await handleDiffAction(SYNC_PRISMA_CONFIG.ON_EXTRA_FIELD, {
1406
+ label: `\u6D88\u8D39\u65B9\u7279\u6709\u5B57\u6BB5`,
1407
+ show() {
1408
+ console.log(` \u2139\uFE0F \u6D88\u8D39\u65B9\u7279\u6709 ${d.extraFields.length} \u4E2A\u5B57\u6BB5\uFF08\u4E0D\u505A\u64CD\u4F5C\uFF09:`);
1409
+ for (const f of d.extraFields) {
1410
+ console.log(` - ${f}`);
1411
+ }
1412
+ },
1413
+ async confirm() {
1414
+ return false;
1415
+ },
1416
+ async execute() {
1417
+ return 0;
1418
+ }
1419
+ });
1420
+ }
1421
+ }
1422
+ }
1423
+ if (targetExists) {
1424
+ console.log(`
1425
+ ${SYNC_PRISMA_CONFIG.PROMPT}`);
1426
+ console.log(` \u25B6 \u8BF7\u8FD0\u884C: npx prisma migrate dev && npx prisma generate`);
1427
+ } else {
1428
+ console.log(`
1429
+ \u76EE\u6807 schema \u6587\u4EF6\u4E0D\u5B58\u5728: ${result.targetPath}`);
1430
+ const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync4 } = __require("fs");
1431
+ const { dirname: dirname4 } = __require("path");
1432
+ mkdirSync4(dirname4(targetPath), { recursive: true });
1433
+ writeFileSync5(targetPath, "// add.prisma \u2014 ADD \u6CBB\u7406\u6A21\u578B\n\n", "utf-8");
1434
+ const n = injectMissingModels(targetPath, result.missing);
1435
+ console.log(` \u2705 \u5DF2\u521B\u5EFA\u5E76\u6CE8\u5165 ${n} \u4E2A\u6A21\u578B/\u679A\u4E3E`);
1436
+ console.log(` \u25B6 \u8BF7\u8FD0\u884C: npx prisma migrate dev && npx prisma generate`);
1437
+ }
1438
+ }
1439
+ async function handleDiffAction(action, ctx) {
1440
+ if (action === "skip" || action === "ignore") return;
1441
+ ctx.show();
1442
+ if (action === "auto") {
1443
+ const n = await ctx.execute();
1444
+ console.log(` \u2705 ${ctx.label}: \u5DF2\u5904\u7406 ${n} \u9879`);
1445
+ return;
1446
+ }
1447
+ if (action === "interactive") {
1448
+ const ok = await ctx.confirm();
1449
+ if (!ok) {
1450
+ console.log(` \u5DF2\u8DF3\u8FC7\u3002`);
1451
+ return;
1452
+ }
1453
+ const n = await ctx.execute();
1454
+ console.log(` \u2705 ${ctx.label}: \u5DF2\u5904\u7406 ${n} \u9879`);
1455
+ return;
1456
+ }
1457
+ if (action === "block") {
1458
+ throw new Error(`\u26D4 Prisma \u540C\u6B65\u963B\u65AD: ${ctx.label}\uFF0C\u7B56\u7565\u4E3A block`);
1459
+ }
1460
+ }
1461
+ function injectMissingModels(targetPath, models) {
1462
+ let content = readFileSync7(targetPath, "utf-8");
1463
+ content = content.replace(/\n*$/, "\n");
1464
+ content += `
1465
+ // ===== \u7531 add-coder sync --patch \u81EA\u52A8\u6CE8\u5165 (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}) =====
1466
+
1467
+ `;
1468
+ for (const m of models) {
1469
+ content += m.body + "\n\n";
1470
+ }
1471
+ writeFileSync4(targetPath, content, "utf-8");
1472
+ return models.length;
1473
+ }
1474
+ function getBaseFieldLines(basePath, modelName) {
1475
+ const content = readFileSync7(basePath, "utf-8");
1476
+ const blockRegex = /^(model|enum)\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gm;
1477
+ let match;
1478
+ while ((match = blockRegex.exec(content)) !== null) {
1479
+ if (match[2] !== modelName) continue;
1480
+ const fields = {};
1481
+ for (const line of match[3].split("\n")) {
1482
+ const fm = line.match(/^\s*(\w+)\s+/);
1483
+ if (fm) fields[fm[1]] = line.trim();
1484
+ }
1485
+ return fields;
1486
+ }
1487
+ return {};
1488
+ }
1489
+ function injectFieldLines(targetPath, basePath, modelName, fieldKeys) {
1490
+ const baseFields = getBaseFieldLines(basePath, modelName);
1491
+ if (Object.keys(baseFields).length === 0) return 0;
1492
+ let content = readFileSync7(targetPath, "utf-8");
1493
+ let count = 0;
1494
+ for (const key of fieldKeys) {
1495
+ const fieldName = key.split(":")[0];
1496
+ const fieldLine = baseFields[fieldName];
1497
+ if (!fieldLine) continue;
1498
+ if (new RegExp(`^\\s*${fieldName}\\s+`, "m").test(content)) continue;
1499
+ const modelRegex = new RegExp(`(model\\s+${modelName}\\s*\\{[^}]*?)\\n(\\s*\\})`, "m");
1500
+ const m = content.match(modelRegex);
1501
+ if (!m) continue;
1502
+ content = content.replace(modelRegex, `$1
1503
+ ${fieldLine}
1504
+ $2`);
1505
+ count++;
1506
+ }
1507
+ if (count > 0) writeFileSync4(targetPath, content, "utf-8");
1508
+ return count;
1509
+ }
1510
+ function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
1511
+ const baseFields = getBaseFieldLines(basePath, modelName);
1512
+ if (Object.keys(baseFields).length === 0) return 0;
1513
+ let content = readFileSync7(targetPath, "utf-8");
1514
+ let count = 0;
1515
+ for (const { fieldName } of conflicts) {
1516
+ const baseLine = baseFields[fieldName];
1517
+ if (!baseLine) continue;
1518
+ const fieldRegex = new RegExp(`^(\\s*)(${fieldName}\\s+.*)$`, "m");
1519
+ const match = content.match(fieldRegex);
1520
+ if (!match) continue;
1521
+ content = content.replace(fieldRegex, `${match[1]}${baseLine}`);
1522
+ count++;
1523
+ }
1524
+ if (count > 0) writeFileSync4(targetPath, content, "utf-8");
1525
+ return count;
1210
1526
  }
1211
1527
 
1212
1528
  // src/cli/commands/status.ts
1213
- import { existsSync as existsSync8 } from "fs";
1529
+ import { existsSync as existsSync9 } from "fs";
1214
1530
  import { resolve as resolve7 } from "path";
1215
1531
  async function statusCommand() {
1216
1532
  const projectRoot = process.cwd();
@@ -1220,7 +1536,7 @@ async function statusCommand() {
1220
1536
  const missing = [];
1221
1537
  const present = [];
1222
1538
  for (const [relPath] of coreFiles) {
1223
- if (existsSync8(resolve7(projectRoot, relPath))) {
1539
+ if (existsSync9(resolve7(projectRoot, relPath))) {
1224
1540
  present.push(relPath);
1225
1541
  } else {
1226
1542
  missing.push(relPath);
@@ -1238,7 +1554,7 @@ async function statusCommand() {
1238
1554
 
1239
1555
  // src/cli/index.ts
1240
1556
  var { version } = JSON.parse(
1241
- readFileSync7(new URL("../package.json", import.meta.url), "utf-8")
1557
+ readFileSync8(new URL("../package.json", import.meta.url), "utf-8")
1242
1558
  );
1243
1559
  var program = new Command();
1244
1560
  program.name("add-coder").description("\u521D\u59CB\u5316 ADD \u8303\u5F0F\u5DE5\u4F5C\u6D41\u6A21\u677F").version(version);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-coder",
3
- "version": "0.3.4",
3
+ "version": "0.3.6-feature-hitl-enhance-v1.0",
4
4
  "private": false,
5
5
  "description": "[codein2027](https://github.com/xiaomingming92/codein2027) - A complete scaffolding for building the ADD programming paradigm, the implementation layer for AI code governance. Core principle: Audit as Infrastructure. Breaks the black-box programming process and cross-session amnesia, evolving programming paradigms into an auditable, traceable, and convergent new era. npx-ready.\n\n[codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架——AI 代码治理的落地方案。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。npx 即用,人人可体验。",
6
6
  "repository": {
@@ -43,6 +43,7 @@
43
43
  "@modelcontextprotocol/server": "2.0.0-beta.5",
44
44
  "commander": "^13.1.0",
45
45
  "smol-toml": "^1.7.0",
46
+ "vector-cosine-similarity": "^1.8.0",
46
47
  "zod": "^4.4.3"
47
48
  },
48
49
  "peerDependencies": {