add-coder 0.3.16 → 0.3.19

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 (33) hide show
  1. package/dist/index.js +349 -62
  2. package/package.json +1 -1
  3. package/templates/.add-coder-src-hash.json +31 -24
  4. package/templates/adapters/claude/hooks/pre-tool-use.sh +6 -1
  5. package/templates/adapters/codex/hooks/pre-tool-use.sh +7 -1
  6. package/templates/adapters/qoder/hooks/pre-tool-use.sh +6 -1
  7. package/templates/adapters/trae/hooks/pre-tool-use.sh +7 -1
  8. package/templates/adapters/vscode/hooks/pre-tool-use.sh +6 -1
  9. package/templates/core/hooks/pre-tool-use.sh +7 -1
  10. package/templates/core/prisma/add.prisma +35 -0
  11. package/templates/core/rules/profiles/index.toml +12 -0
  12. package/templates/core/rules/profiles/machineserver-profile.md +22 -0
  13. package/templates/core/rules/profiles/webapp-profile.md +61 -0
  14. package/templates/core/rules/project_rules.md +6 -54
  15. package/templates/core/scripts/mcp-server/tools/audit.ts +23 -14
  16. package/templates/core/scripts/mcp-server/tools/context.ts +20 -2
  17. package/templates/core/scripts/mcp-server/tools/contract.ts +202 -0
  18. package/templates/core/scripts/mcp-server/tools/docs.ts +2 -2
  19. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_completeness.ts +2 -2
  20. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +2 -2
  21. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +2 -2
  22. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +2 -2
  23. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +2 -2
  24. package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -2
  25. package/templates/core/scripts/mcp-server/tools/hitl.ts +13 -5
  26. package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +2 -2
  27. package/templates/core/scripts/mcp-server/tools/index.ts +29 -10
  28. package/templates/core/scripts/mcp-server/tools/plan.ts +5 -2
  29. package/templates/core/scripts/mcp-server/tools/quality.ts +2 -2
  30. package/templates/core/scripts/mcp-server/tools/registrar.ts +11 -0
  31. package/templates/core/scripts/mcp-server/tools/review.ts +2 -2
  32. package/templates/core/templates/collab-contract-template.md +203 -0
  33. package/templates/core/templates/collab-contract-template.schema.json +133 -0
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 readFileSync8 } from "fs";
4
+ import { readFileSync as readFileSync9 } from "fs";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/caijuehub/strategies/detect.strategy.ts
@@ -63,6 +63,7 @@ var AddCoderConfigSchema = z.object({
63
63
  mcpServerCommand: z.string().default("tsx"),
64
64
  agentAuditImport: z.string().default("@/lib/agent-audit-logger"),
65
65
  magicDir: z.string(),
66
+ stack: z.string().optional(),
66
67
  adapters: z.array(AdapterEnum).default([]),
67
68
  overrides: z.record(z.string(), z.string()).default({})
68
69
  });
@@ -79,6 +80,7 @@ var defaults = {
79
80
  mcpServerCommand: "tsx",
80
81
  agentAuditImport: "@/lib/agent-audit-logger",
81
82
  magicDir: "",
83
+ stack: "",
82
84
  adapters: [],
83
85
  overrides: {}
84
86
  };
@@ -182,7 +184,7 @@ async function selectFiles(projectRoot, files) {
182
184
  process.stdout.write(renderList(items, selected, scrollIdx, VISIBLE));
183
185
  }
184
186
  draw();
185
- return new Promise((resolve8) => {
187
+ return new Promise((resolve9) => {
186
188
  input.on("keypress", (_char, key) => {
187
189
  if (!key) return;
188
190
  if (key.name === "up") {
@@ -223,7 +225,7 @@ async function selectFiles(projectRoot, files) {
223
225
  if (selected.has(idx)) result.set(relPath, content);
224
226
  idx++;
225
227
  }
226
- resolve8(result);
228
+ resolve9(result);
227
229
  });
228
230
  });
229
231
  }
@@ -314,7 +316,7 @@ var SYNC_PRISMA_CONFIG = {
314
316
  { label: "\u751F\u4EA7/CI\uFF08\u53EA\u5E94\u7528\u5DF2\u6709\u8FC1\u79FB\uFF09", cmd: "npx prisma migrate deploy" },
315
317
  { label: "\u4E0D\u786E\u5B9A\uFF1F\u5148\u67E5\u72B6\u6001", cmd: "npx prisma migrate status" }
316
318
  ],
317
- P3005: { hint: "\u82E5\u62A5 P3005\uFF08\u5E93\u6709\u8868\u4F46\u65E0\u8FC1\u79FB\u8BB0\u5F55\uFF09= \u9700\u5148\u57FA\u7EBF\u5316:", cmd: "npx prisma migrate resolve --applied <\u8FC1\u79FB\u540D>\uFF0C\u518D\u6267\u884C deploy" },
319
+ P3005: { hint: "\u82E5\u62A5 P3005\uFF08\u5E93\u6709\u8868\u4F46\u65E0\u8FC1\u79FB\u8BB0\u5F55\uFF09= \u9700\u5148\u57FA\u7EBF\u5316:", cmd: "npx prisma migrate resolve --applied <\uFFFD\uFFFD\u79FB\u540D>\uFF0C\u518D\u6267\u884C deploy" },
318
320
  UNMANAGED_ACTIONS: [
319
321
  { label: "\u9996\u6B21\u63A5\u5165\u8FC1\u79FB\u7BA1\u7406\uFF08\u7A7A\u5E93\uFF09", cmd: "npx prisma migrate dev --name init" },
320
322
  { label: "\u5E93\u5DF2\u6709\u8868\uFF08db push \u5EFA\u8FC7\uFF09", hint: "\u5148\u624B\u52A8\u57FA\u7EBF\u5316\uFF0C\u518D deploy:", steps: ["npx prisma migrate diff --from-empty --to-schema-datamodel <schema\u6587\u4EF6> --script", "\u5C06\u8F93\u51FA\u5199\u5165 prisma/migrations/<\u65F6\u95F4\u6233>_init/migration.sql", "npx prisma migrate resolve --applied <\u65F6\u95F4\u6233>_init"] }
@@ -329,13 +331,34 @@ async function writeFiles2(projectRoot, files, options = {}) {
329
331
  }
330
332
  function parseSchemaBlocks(content) {
331
333
  const blocks = /* @__PURE__ */ new Map();
332
- const blockRegex = /^(model|enum)\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gm;
333
- let match;
334
- while ((match = blockRegex.exec(content)) !== null) {
335
- const [, type, name, inner] = match;
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");
336
359
  let fields = [];
337
360
  if (type === "enum") {
338
- 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);
339
362
  } else {
340
363
  const fieldRegex = /^\s*(\w+)\s+(\w+(?:\([^)]*\))?(?:\[\])?\??)/gm;
341
364
  let fm;
@@ -343,7 +366,8 @@ function parseSchemaBlocks(content) {
343
366
  fields.push(`${fm[1]}:${fm[2]}`);
344
367
  }
345
368
  }
346
- blocks.set(`${type}:${name}`, { type, name, body: match[0], fields });
369
+ blocks.set(`${type}:${name}`, { type, name, body, fields });
370
+ i = j;
347
371
  }
348
372
  return blocks;
349
373
  }
@@ -400,7 +424,7 @@ function diffPrisma(basePath, targetPath) {
400
424
  }
401
425
 
402
426
  // src/core/renderer.ts
403
- import { readFileSync as readFileSync4, readdirSync, statSync, existsSync as existsSync5 } from "fs";
427
+ import { readFileSync as readFileSync4, readdirSync, statSync, existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
404
428
  import { join as join4, relative, dirname as dirname2 } from "path";
405
429
  import { fileURLToPath } from "url";
406
430
  import { parse } from "smol-toml";
@@ -418,6 +442,24 @@ var PLACEHOLDERS = {
418
442
  "{{mcpServerCommand}}": "mcpServerCommand",
419
443
  "{{agentAuditImport}}": "agentAuditImport"
420
444
  };
445
+ function renderStackPlaceholders(result, config) {
446
+ const stackName = config.stack || "\u672A\u8BBE\u7F6E";
447
+ const stackProfile = config.stack ? `${config.stack}-profile.md` : "\u672A\u8BBE\u7F6E";
448
+ let refLine;
449
+ if (config.stack) {
450
+ refLine = [
451
+ `\u672C\u9879\u76EE\u7684\u6280\u672F\u6808\u7EA6\u675F\u7531 \`${config.magicDir}/rules/profiles/${stackProfile}\` \u5B9A\u4E49\uFF08\u7531 \`add-coder stack set\` \u7BA1\u7406\uFF09\u3002`,
452
+ `- **\u5F53\u524D\u6280\u672F\u6808**: \`${stackName}\`\uFF08${stackProfile} \u5DF2\u751F\u6548\uFF0CAI \u5FC5\u987B\u9075\u5B88\u5176\u4E2D\u5168\u90E8\u7EA6\u675F\uFF09`
453
+ ].join("\n");
454
+ } else {
455
+ refLine = [
456
+ "\u672C\u9879\u76EE\u7684\u6280\u672F\u6808\u672A\u8BBE\u7F6E\uFF0C\u4E0D\u65BD\u52A0\u4EFB\u4F55\u6280\u672F\u6808\u5047\u8BBE\u3002",
457
+ "AI \u5FC5\u987B\u901A\u8FC7 `get_project_context` \u8BFB\u53D6\u9879\u76EE\u5B9E\u9645\u4EE3\u7801\u63A8\u65AD\u771F\u5B9E\u6280\u672F\u6808\uFF0C\u7981\u6B62\u5957\u7528\u6A21\u677F\u6216\u6848\u4F8B\u4E2D\u7684\u9ED8\u8BA4\u6280\u672F\u6808\u3002",
458
+ "\uFF08\u53EF\u7528 `add-coder stack set <name>` \u542F\u7528\u6280\u672F\u6808\u7EA6\u675F\uFF09"
459
+ ].join("\n");
460
+ }
461
+ return result.replaceAll("{{stackReferenceLine}}", refLine).replaceAll("{{stackName}}", stackName).replaceAll("{{stackProfile}}", stackProfile);
462
+ }
421
463
  function loadDpsThresholds() {
422
464
  try {
423
465
  const candidates = [
@@ -455,12 +497,44 @@ function render(content, config) {
455
497
  result = result.replaceAll("{{dpsPass}}", thresholds.pass);
456
498
  result = result.replaceAll("{{dpsWarn}}", thresholds.warn);
457
499
  }
500
+ result = renderStackPlaceholders(result, config);
458
501
  return result;
459
502
  }
460
503
  var TEMPLATES_ROOT = join4(__dirname, "../templates");
461
504
  var CORE_DIR = join4(TEMPLATES_ROOT, "core");
462
505
  var CORE_TARGET = ".add";
463
- var SKIP_DIRS = /* @__PURE__ */ new Set(["prisma"]);
506
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["prisma", "profiles"]);
507
+ function loadProfileRegistry() {
508
+ try {
509
+ const tomlPath = join4(CORE_DIR, "rules", "profiles", "index.toml");
510
+ if (!existsSync5(tomlPath)) return [];
511
+ const doc = parse(readFileSync4(tomlPath, "utf-8"));
512
+ return doc.profile || [];
513
+ } catch (e) {
514
+ const errorMessage = e instanceof Error ? e.message : String(e);
515
+ console.warn(`[renderer] profile \u6CE8\u518C\u8868\u8BFB\u53D6\u5931\u8D25: ${errorMessage}`);
516
+ return [];
517
+ }
518
+ }
519
+ function loadStack(projectRoot, magicDir) {
520
+ try {
521
+ const p = join4(projectRoot, magicDir, "stack.json");
522
+ if (!existsSync5(p)) return "";
523
+ const doc = JSON.parse(readFileSync4(p, "utf-8"));
524
+ return typeof doc.stack === "string" ? doc.stack : "";
525
+ } catch {
526
+ return "";
527
+ }
528
+ }
529
+ function saveStack(projectRoot, magicDir, stack) {
530
+ const dir = join4(projectRoot, magicDir);
531
+ mkdirSync2(dir, { recursive: true });
532
+ writeFileSync2(
533
+ join4(dir, "stack.json"),
534
+ JSON.stringify({ stack, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
535
+ "utf-8"
536
+ );
537
+ }
464
538
  function renderCore(config, dryRun) {
465
539
  const files = /* @__PURE__ */ new Map();
466
540
  function walk(dir, base) {
@@ -480,6 +554,17 @@ function renderCore(config, dryRun) {
480
554
  }
481
555
  }
482
556
  walk(CORE_DIR, "");
557
+ if (config.stack) {
558
+ const registry = loadProfileRegistry();
559
+ const profile = registry.find((p) => p.name === config.stack);
560
+ if (profile) {
561
+ const src = join4(CORE_DIR, "rules", "profiles", profile.file);
562
+ if (existsSync5(src)) {
563
+ const rendered = render(readFileSync4(src, "utf-8"), config);
564
+ files.set(join4(".add", "rules", "profiles", profile.file), rendered);
565
+ }
566
+ }
567
+ }
483
568
  if (dryRun) {
484
569
  console.log(`[dry-run] Core templates: ${files.size} files`);
485
570
  }
@@ -565,7 +650,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
565
650
 
566
651
  // src/caijuehub/strategies/prisma.strategy.ts
567
652
  import { spawnSync } from "child_process";
568
- import { copyFileSync, existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
653
+ import { copyFileSync, existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
569
654
  import { resolve as resolve3 } from "path";
570
655
  var PRISMA_CONFIG = {
571
656
  onMissing: "ask",
@@ -578,7 +663,7 @@ var PRISMA_CONFIG = {
578
663
  };
579
664
  function ensurePrismaConfig(projectRoot) {
580
665
  const configPath = resolve3(projectRoot, "prisma.config.ts");
581
- writeFileSync2(configPath, [
666
+ writeFileSync3(configPath, [
582
667
  'import dotenv from "dotenv";',
583
668
  'import { existsSync } from "fs";',
584
669
  'for (const f of [".env.development.local", ".env.development", ".env.local", ".env"]) {',
@@ -603,7 +688,7 @@ function backupAddTables(projectRoot) {
603
688
  timeout: 3e4
604
689
  });
605
690
  if (r.stdout.length > 0) {
606
- writeFileSync2(bak, r.stdout, "utf-8");
691
+ writeFileSync3(bak, r.stdout, "utf-8");
607
692
  console.log(`>>> \u5907\u4EFD ADD \u8868\u5230 ${bak}`);
608
693
  return bak;
609
694
  }
@@ -621,7 +706,7 @@ function runPrismaInit(projectRoot, provider, schemaPath) {
621
706
  if (initResult.status !== 0 || !existsSync7(schemaPath)) {
622
707
  console.log("prisma init \u5931\u8D25\uFF0C\u624B\u52A8\u521B\u5EFA schema.prisma ...");
623
708
  const prismaDir = resolve3(projectRoot, "prisma");
624
- if (!existsSync7(prismaDir)) mkdirSync2(prismaDir, { recursive: true });
709
+ if (!existsSync7(prismaDir)) mkdirSync3(prismaDir, { recursive: true });
625
710
  const content = `generator client {
626
711
  provider = "prisma-client-js"
627
712
  }
@@ -630,11 +715,11 @@ datasource db {
630
715
  provider = "${provider}"
631
716
  }
632
717
  `;
633
- writeFileSync2(schemaPath, content, "utf-8");
718
+ writeFileSync3(schemaPath, content, "utf-8");
634
719
  const devEnvPath = resolve3(projectRoot, ".env.development");
635
720
  if (!existsSync7(devEnvPath)) {
636
721
  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"';
637
- writeFileSync2(devEnvPath, defaultUrl + "\n", "utf-8");
722
+ writeFileSync3(devEnvPath, defaultUrl + "\n", "utf-8");
638
723
  console.log("\u5DF2\u521B\u5EFA .env.development");
639
724
  }
640
725
  return false;
@@ -650,7 +735,7 @@ function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
650
735
  if (dbUrl) {
651
736
  const existing = existsSync7(devEnvPath) ? readFileSync5(devEnvPath, "utf-8") : "";
652
737
  if (!existing.includes("DATABASE_URL=")) {
653
- writeFileSync2(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
738
+ writeFileSync3(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
654
739
  `, "utf-8");
655
740
  }
656
741
  if (existsSync7(envPath)) unlinkSync(envPath);
@@ -758,7 +843,7 @@ async function injectPrisma2(projectRoot, options = {}) {
758
843
  }
759
844
 
760
845
  // src/cli/commands/init.ts
761
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
846
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
762
847
  import { createHash } from "crypto";
763
848
  import { resolve as resolve5 } from "path";
764
849
  import { spawnSync as spawnSync2 } from "child_process";
@@ -774,6 +859,12 @@ var MAGIC_DIR_MAP = { claude: ".claude", qoder: ".qoder", vscode: ".vscode", tra
774
859
  async function initCommand(options) {
775
860
  const ctx = await prepare(options);
776
861
  writeComposeEnv(ctx);
862
+ if (!options.dryRun) {
863
+ saveStack(ctx.projectRoot, ctx.magicDir, ctx.stack);
864
+ if (ctx.stack) console.log(`\u6280\u672F\u6808\u72B6\u6001\u5DF2\u5199\u5165 ${ctx.magicDir}/stack.json \u2192 ${ctx.stack}`);
865
+ } else if (ctx.stack) {
866
+ console.log(`[dry-run] \u5C06\u5199\u5165 ${ctx.magicDir}/stack.json \u2192 ${ctx.stack}`);
867
+ }
777
868
  const result = await renderAndWrite(ctx);
778
869
  await deployDatabase(ctx);
779
870
  deployDocs(ctx);
@@ -791,10 +882,12 @@ async function resolveAdapter(projectRoot, specified) {
791
882
  return detected;
792
883
  }
793
884
  console.log("\u672A\u68C0\u6D4B\u5230 IDE \u73AF\u5883");
794
- const a = (await ask("\u8BF7\u9009\u62E9\u76EE\u6807 IDE: [1] Qoder [2] Claude [3] VS Code \u2192 ")).trim();
885
+ const a = (await ask("\u8BF7\u9009\u62E9\u76EE\u6807 IDE: [1] Qoder [2] Claude [3] VS Code [4] Trae [5] Codex \u2192 ")).trim();
795
886
  if (a === "1" || a === "qoder") return "qoder";
796
887
  if (a === "2" || a === "claude") return "claude";
797
888
  if (a === "3" || a === "vscode") return "vscode";
889
+ if (a === "4" || a === "trae") return "trae";
890
+ if (a === "5" || a === "codex") return "codex";
798
891
  console.log("\u8F93\u5165\u65E0\u6CD5\u8BC6\u522B\uFF0C\u9ED8\u8BA4 qoder");
799
892
  return "qoder";
800
893
  }
@@ -938,8 +1031,8 @@ main().catch((e) => { console.error(e); process.exit(1); });
938
1031
  console.log(`[dry-run] \u5C06\u5199\u5165 ${scriptPath}`);
939
1032
  return;
940
1033
  }
941
- if (!existsSync8(scriptsDir)) mkdirSync3(scriptsDir, { recursive: true });
942
- writeFileSync3(scriptPath, content, "utf-8");
1034
+ if (!existsSync8(scriptsDir)) mkdirSync4(scriptsDir, { recursive: true });
1035
+ writeFileSync4(scriptPath, content, "utf-8");
943
1036
  console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
944
1037
  }
945
1038
  function injectDbExportScript(projectRoot, dryRun) {
@@ -953,10 +1046,26 @@ function injectDbExportScript(projectRoot, dryRun) {
953
1046
  if (!pkg.scripts) pkg.scripts = {};
954
1047
  if (!pkg.scripts["db:export"]) {
955
1048
  pkg.scripts["db:export"] = "npx tsx scripts/export-db.ts";
956
- writeFileSync3(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1049
+ writeFileSync4(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
957
1050
  console.log("\u5DF2\u5728 package.json \u6CE8\u5165 db:export");
958
1051
  }
959
1052
  }
1053
+ async function resolveStack(options) {
1054
+ if (options.stack) {
1055
+ console.log(`\u6280\u672F\u6808\u7EA6\u675F: ${options.stack} (--stack)`);
1056
+ return options.stack;
1057
+ }
1058
+ if (options.force) return "";
1059
+ console.log(["", "\u6280\u672F\u6808\u7EA6\u675F\uFF08\u53EF\u9009\uFF09:", " [1] \u4E0D\u8BBE\u7F6E\uFF08\u4E2D\u6027\uFF0C\u63A8\u8350\uFF09", " [2] webapp", " [3] machineserver", " [4] \u81EA\u5B9A\u4E49\uFF08\u8F93\u5165 profile \u540D\uFF09"].join("\n"));
1060
+ const a = (await ask("\u8BF7\u9009\u62E9 [1/2/3/4] \u2192 ")).trim();
1061
+ if (a === "2" || a === "webapp") return "webapp";
1062
+ if (a === "3" || a === "machineserver") return "machineserver";
1063
+ if (a === "4" || a === "custom") {
1064
+ const name = (await ask("\u81EA\u5B9A\u4E49 profile \u540D\uFF08\u9700\u5DF2\u5728 .qoder/rules/profiles/ \u5B58\u5728\uFF09: ")).trim();
1065
+ return name || "";
1066
+ }
1067
+ return "";
1068
+ }
960
1069
  async function prepare(options) {
961
1070
  const projectRoot = process.cwd();
962
1071
  const target = await resolveAdapter(projectRoot, options.adapter);
@@ -964,6 +1073,8 @@ async function prepare(options) {
964
1073
  const config = await loadConfig(projectRoot, options.config, { force: options.force });
965
1074
  config.projectRoot = projectRoot;
966
1075
  config.magicDir = magicDir;
1076
+ const stack = await resolveStack(options);
1077
+ config.stack = stack;
967
1078
  const db = await resolveDbEngine(!!options.force);
968
1079
  if (db.engine === "postgresql") {
969
1080
  db.container = await resolveContainer(!!options.force);
@@ -971,7 +1082,7 @@ async function prepare(options) {
971
1082
  Object.assign(db, await resolveDbCredentials(!!options.force));
972
1083
  }
973
1084
  }
974
- return { projectRoot, options, target, magicDir, config, db };
1085
+ return { projectRoot, options, target, magicDir, config, db, stack };
975
1086
  }
976
1087
  function writeComposeEnv(ctx) {
977
1088
  const { projectRoot, options, config, db } = ctx;
@@ -980,7 +1091,7 @@ function writeComposeEnv(ctx) {
980
1091
  const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
981
1092
  const composePath = resolve5(projectRoot, composeName);
982
1093
  if (!options.dryRun && (!existsSync8(composePath) || options.force)) {
983
- writeFileSync3(composePath, composeContent(config.projectName || "add-project"), "utf-8");
1094
+ writeFileSync4(composePath, composeContent(config.projectName || "add-project"), "utf-8");
984
1095
  console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
985
1096
  }
986
1097
  }
@@ -988,7 +1099,7 @@ function writeComposeEnv(ctx) {
988
1099
  if (!options.dryRun && existsSync8(devEnvPath)) {
989
1100
  const existing = readFileSync6(devEnvPath, "utf-8");
990
1101
  if (!/^DATABASE_USER=/m.test(existing)) {
991
- writeFileSync3(devEnvPath, existing + `
1102
+ writeFileSync4(devEnvPath, existing + `
992
1103
  DATABASE_USER=${db.user || "admin"}
993
1104
  DATABASE_PASSWORD=${db.password || "change-me-in-production"}
994
1105
  DATABASE_PORT=${db.port || "5433"}
@@ -1030,7 +1141,7 @@ async function renderAndWrite(ctx) {
1030
1141
  if (dry) {
1031
1142
  console.log(`[dry-run] \u5C06\u521B\u5EFA ${reviewsDir}/`);
1032
1143
  } else {
1033
- mkdirSync3(reviewsDir, { recursive: true });
1144
+ mkdirSync4(reviewsDir, { recursive: true });
1034
1145
  }
1035
1146
  }
1036
1147
  }
@@ -1045,9 +1156,9 @@ async function renderAndWrite(ctx) {
1045
1156
  hashMap[rp] = createHash("sha256").update(c).digest("hex").slice(0, 8);
1046
1157
  }
1047
1158
  const hashOut = resolve5(projectRoot, magicDir, ".add-coder-hash.json");
1048
- writeFileSync3(hashOut, JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
1159
+ writeFileSync4(hashOut, JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
1049
1160
  if (npmVer) {
1050
- writeFileSync3(resolve5(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
1161
+ writeFileSync4(resolve5(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
1051
1162
  }
1052
1163
  console.log(`hash: ${Object.keys(hashMap).length} entries \u2192 ${magicDir}/.add-coder-hash.json`);
1053
1164
  }
@@ -1111,7 +1222,7 @@ function deployDocs(ctx) {
1111
1222
  for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
1112
1223
  const srcDir = resolve5(groundingSrc, d);
1113
1224
  const destDir = resolve5(docsBase, d);
1114
- if (!existsSync8(destDir)) mkdirSync3(destDir, { recursive: true });
1225
+ if (!existsSync8(destDir)) mkdirSync4(destDir, { recursive: true });
1115
1226
  if (!existsSync8(srcDir)) continue;
1116
1227
  for (const f of readdirSync2(srcDir)) {
1117
1228
  const src = resolve5(srcDir, f);
@@ -1144,13 +1255,13 @@ function finalize(ctx, result) {
1144
1255
  }
1145
1256
 
1146
1257
  // src/cli/commands/sync.ts
1147
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
1258
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
1148
1259
  import { resolve as resolve6, dirname as dirname4 } from "path";
1149
1260
  import { createHash as createHash2 } from "crypto";
1150
1261
 
1151
1262
  // src/caijuehub/strategies/sync.strategy.ts
1152
1263
  var SYNC_CONFIG = {
1153
- PATCH_GUARD: [/[/]plans[/]/, /[/]specs[/]/, /[/]reviews[/]/],
1264
+ PATCH_GUARD: [/[/]plans[/]/, /[/]specs[/]/, /[/]reviews[/]/, /[/]rules[/]profiles[/]/],
1154
1265
  HASH_OUTPUT_FILE: ".add-coder-hash.json",
1155
1266
  HASH_SRC_FILE: "templates/.add-coder-src-hash.json",
1156
1267
  HASH_HEX_LENGTH: 8,
@@ -1211,12 +1322,12 @@ function loadVersionFile(root, magic) {
1211
1322
  }
1212
1323
  }
1213
1324
  function saveVersionFile(root, magic, version2) {
1214
- writeFileSync4(resolve6(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
1325
+ writeFileSync5(resolve6(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
1215
1326
  }
1216
1327
  function saveHashFile(root, magic, files) {
1217
1328
  const m = {};
1218
1329
  for (const [p, c] of files) m[p] = hash8(c);
1219
- writeFileSync4(resolve6(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), JSON.stringify(m, null, 2) + "\n", "utf-8");
1330
+ writeFileSync5(resolve6(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), JSON.stringify(m, null, 2) + "\n", "utf-8");
1220
1331
  }
1221
1332
  async function syncCommand(options = {}) {
1222
1333
  const projectRoot = process.cwd();
@@ -1497,8 +1608,8 @@ async function checkPrismaDiff(projectRoot, options) {
1497
1608
  } else {
1498
1609
  console.log(`
1499
1610
  \u76EE\u6807 schema \u6587\u4EF6\u4E0D\u5B58\u5728: ${result.targetPath}`);
1500
- mkdirSync4(dirname4(targetPath), { recursive: true });
1501
- writeFileSync4(targetPath, "// add.prisma \u2014 ADD \u6CBB\u7406\u6A21\u578B\n\n", "utf-8");
1611
+ mkdirSync5(dirname4(targetPath), { recursive: true });
1612
+ writeFileSync5(targetPath, "// add.prisma \u2014 ADD \u6CBB\u7406\u6A21\u578B\n\n", "utf-8");
1502
1613
  const n = injectMissingModels(targetPath, result.missing);
1503
1614
  console.log(` \u2705 \u5DF2\u521B\u5EFA\u5E76\u6CE8\u5165 ${n} \u4E2A\u6A21\u578B/\u679A\u4E3E`);
1504
1615
  if (n > 0) {
@@ -1538,43 +1649,76 @@ function injectMissingModels(targetPath, models) {
1538
1649
  for (const m of models) {
1539
1650
  content += m.body + "\n\n";
1540
1651
  }
1541
- writeFileSync4(targetPath, content, "utf-8");
1652
+ writeFileSync5(targetPath, content, "utf-8");
1542
1653
  return models.length;
1543
1654
  }
1544
1655
  function getBaseFieldLines(basePath, modelName) {
1545
1656
  const content = readFileSync7(basePath, "utf-8");
1546
- const blockRegex = /^(model|enum)\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/gm;
1547
- let match;
1548
- while ((match = blockRegex.exec(content)) !== null) {
1549
- if (match[2] !== modelName) continue;
1550
- const fields = {};
1551
- for (const line of match[3].split("\n")) {
1657
+ const blocks = parseSchemaBlocks(content);
1658
+ const block = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
1659
+ if (!block) return {};
1660
+ const fields = {};
1661
+ for (const line of block.body.split("\n")) {
1662
+ const clean = line.replace(/\/\/.*$/, "").trim();
1663
+ if (!clean) continue;
1664
+ if (block.type === "enum") {
1665
+ if (/^\w+$/.test(clean)) fields[clean] = line.trim();
1666
+ } else {
1552
1667
  const fm = line.match(/^\s*(\w+)\s+/);
1553
1668
  if (fm) fields[fm[1]] = line.trim();
1554
1669
  }
1555
- return fields;
1556
1670
  }
1557
- return {};
1671
+ return fields;
1558
1672
  }
1559
1673
  function injectFieldLines(targetPath, basePath, modelName, fieldKeys) {
1560
1674
  const baseFields = getBaseFieldLines(basePath, modelName);
1561
- if (Object.keys(baseFields).length === 0) return 0;
1562
- let content = readFileSync7(targetPath, "utf-8");
1563
- let count = 0;
1675
+ if (Object.keys(baseFields).length === 0) {
1676
+ 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
+ return 0;
1678
+ }
1679
+ const content = readFileSync7(targetPath, "utf-8");
1680
+ const lines = content.split("\n");
1681
+ const blocks = parseSchemaBlocks(content);
1682
+ const target = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
1683
+ if (!target) {
1684
+ 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`);
1685
+ return 0;
1686
+ }
1687
+ const bodyLines = target.body.split("\n");
1688
+ const startIdx = lines.findIndex((l) => l === bodyLines[0]);
1689
+ if (startIdx < 0) {
1690
+ 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`);
1691
+ return 0;
1692
+ }
1693
+ const endIdx = startIdx + bodyLines.length - 1;
1694
+ const existingNames = new Set(
1695
+ target.fields.map((f) => f.split(":")[0])
1696
+ );
1697
+ const newFieldLines = [];
1564
1698
  for (const key of fieldKeys) {
1565
1699
  const fieldName = key.split(":")[0];
1566
1700
  const fieldLine = baseFields[fieldName];
1567
1701
  if (!fieldLine) continue;
1568
- const modelRegex = new RegExp(`(model\\s+${modelName}\\s*\\{)([^}]*?)(\\n\\s*\\})`, "m");
1569
- const m = content.match(modelRegex);
1570
- if (!m) continue;
1571
- if (new RegExp(`^\\s*${fieldName}\\s+`, "m").test(m[2])) continue;
1572
- content = content.replace(modelRegex, `$1$2
1573
- ${fieldLine}$3`);
1574
- count++;
1702
+ if (existingNames.has(fieldName)) continue;
1703
+ newFieldLines.push(` ${fieldLine}`);
1575
1704
  }
1576
- if (count > 0) writeFileSync4(targetPath, content, "utf-8");
1577
- return count;
1705
+ if (newFieldLines.length === 0) {
1706
+ if (fieldKeys.length > 0) {
1707
+ 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`);
1708
+ }
1709
+ return 0;
1710
+ }
1711
+ let insertIdx = endIdx;
1712
+ for (let k = startIdx + 1; k < endIdx; k++) {
1713
+ if (/^\s*@@/.test(lines[k])) insertIdx = k;
1714
+ }
1715
+ const merged = [
1716
+ ...lines.slice(0, insertIdx),
1717
+ ...newFieldLines,
1718
+ ...lines.slice(insertIdx)
1719
+ ];
1720
+ writeFileSync5(targetPath, merged.join("\n"), "utf-8");
1721
+ return newFieldLines.length;
1578
1722
  }
1579
1723
  function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
1580
1724
  const baseFields = getBaseFieldLines(basePath, modelName);
@@ -1590,7 +1734,7 @@ function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
1590
1734
  content = content.replace(fieldRegex, `${match[1]}${baseLine}`);
1591
1735
  count++;
1592
1736
  }
1593
- if (count > 0) writeFileSync4(targetPath, content, "utf-8");
1737
+ if (count > 0) writeFileSync5(targetPath, content, "utf-8");
1594
1738
  return count;
1595
1739
  }
1596
1740
 
@@ -1621,13 +1765,156 @@ async function statusCommand() {
1621
1765
  }
1622
1766
  }
1623
1767
 
1768
+ // 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";
1771
+ import { createHash as createHash3 } from "crypto";
1772
+ var MAGIC_DIR_MAP3 = { claude: ".claude", qoder: ".qoder", vscode: ".vscode", trae: ".trae", codex: ".codex" };
1773
+ var HASH_OUTPUT_FILE = ".add-coder-hash.json";
1774
+ function hash82(c) {
1775
+ return createHash3("sha256").update(c).digest("hex").slice(0, 8);
1776
+ }
1777
+ function resolveMagicDir(projectRoot, specified) {
1778
+ if (specified) {
1779
+ if (!MAGIC_DIR_MAP3[specified]) throw new Error(`\u672A\u77E5 adapter: ${specified}`);
1780
+ return MAGIC_DIR_MAP3[specified];
1781
+ }
1782
+ const detected = detectIDE2(projectRoot);
1783
+ const adapter = detected !== "auto" ? detected : "qoder";
1784
+ return MAGIC_DIR_MAP3[adapter];
1785
+ }
1786
+ function listCustomProfiles(projectRoot, magicDir, registryNames) {
1787
+ const dir = resolve8(projectRoot, magicDir, "rules", "profiles");
1788
+ if (!existsSync11(dir)) return [];
1789
+ return readdirSync3(dir).filter((f) => f.endsWith(".md") && !registryNames.has(f.replace(/-profile\.md$/, ""))).sort();
1790
+ }
1791
+ function profileExists(projectRoot, magicDir, name) {
1792
+ const registry = loadProfileRegistry();
1793
+ if (registry.some((p) => p.name === name)) return true;
1794
+ return existsSync11(resolve8(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`));
1795
+ }
1796
+ function stackCommand(sub, name, options = {}) {
1797
+ const projectRoot = process.cwd();
1798
+ const magicDir = resolveMagicDir(projectRoot, options.adapter);
1799
+ const registry = loadProfileRegistry();
1800
+ if (options.clear) {
1801
+ saveStack(projectRoot, magicDir, "");
1802
+ console.log(`\u5DF2\u6E05\u9664\u6280\u672F\u6808\u8BBE\u7F6E\uFF08${magicDir}/stack.json \u2192 \u4E2D\u6027\uFF09`);
1803
+ return;
1804
+ }
1805
+ if (sub === "list" || !sub) {
1806
+ const current = loadStack(projectRoot, magicDir);
1807
+ console.log("\u6280\u672F\u6808 profile \u5217\u8868:");
1808
+ console.log(" \u5185\u7F6E\uFF08\u6CE8\u518C\u8868\uFF09:");
1809
+ for (const p of registry) {
1810
+ const mark = p.name === current ? " \u2190 \u5F53\u524D" : "";
1811
+ console.log(` ${p.name} \u2014 ${p.description}${mark}`);
1812
+ }
1813
+ const custom = listCustomProfiles(projectRoot, magicDir, new Set(registry.map((p) => p.name)));
1814
+ if (custom.length > 0) {
1815
+ console.log(" \u81EA\u5B9A\u4E49\uFF08\u9879\u76EE\u4FA7\uFF09:");
1816
+ for (const c of custom) {
1817
+ const name2 = c.replace(/-profile\.md$/, "");
1818
+ const mark = name2 === current ? " \u2190 \u5F53\u524D" : "";
1819
+ console.log(` ${name2}${mark}`);
1820
+ }
1821
+ }
1822
+ if (!current) console.log("\n\u5F53\u524D: \u672A\u8BBE\u7F6E\uFF08\u4E0D\u65BD\u52A0\u4EFB\u4F55\u6280\u672F\u6808\u5047\u8BBE\uFF09");
1823
+ return;
1824
+ }
1825
+ if (sub === "show") {
1826
+ const current = loadStack(projectRoot, magicDir);
1827
+ if (!current) {
1828
+ console.log("\u6280\u672F\u6808: \u672A\u8BBE\u7F6E\uFF08\u4E2D\u6027\uFF0C\u65E0\u6280\u672F\u6808\u5047\u8BBE\uFF09");
1829
+ return;
1830
+ }
1831
+ const profilePath = resolve8(projectRoot, magicDir, "rules", "profiles", `${current}-profile.md`);
1832
+ const stat = existsSync11(profilePath) ? readFileSync8(profilePath, "utf-8").length : 0;
1833
+ console.log(`\u6280\u672F\u6808: ${current}`);
1834
+ console.log(`profile \u6587\u4EF6: ${profilePath}${existsSync11(profilePath) ? ` (${stat} \u5B57\u7B26)` : "\uFF08\u7F3A\u5931\uFF09"}`);
1835
+ try {
1836
+ const raw = JSON.parse(readFileSync8(resolve8(projectRoot, magicDir, "stack.json"), "utf-8"));
1837
+ if (raw.updatedAt) console.log(`\u66F4\u65B0\u65F6\u95F4: ${raw.updatedAt}`);
1838
+ } catch {
1839
+ }
1840
+ return;
1841
+ }
1842
+ if (sub === "set") {
1843
+ if (!name) {
1844
+ console.log("\u7528\u6CD5: add-coder stack set <name>\uFF08\u5185\u7F6E: " + registry.map((p) => p.name).join(" | ") + "\uFF0C\u6216\u81EA\u5B9A\u4E49 profile \u540D\uFF09");
1845
+ return;
1846
+ }
1847
+ applyStack(projectRoot, magicDir, name);
1848
+ return;
1849
+ }
1850
+ console.log(`\u672A\u77E5\u5B50\u547D\u4EE4: ${sub}\uFF08\u53EF\u7528: list / set <name> / show / --clear\uFF09`);
1851
+ }
1852
+ function buildConfig(projectRoot, magicDir, stack) {
1853
+ const config = {
1854
+ ...defaults,
1855
+ projectName: "add-project",
1856
+ projectRoot,
1857
+ magicDir,
1858
+ stack
1859
+ };
1860
+ try {
1861
+ const pkgPath = resolve8(projectRoot, "package.json");
1862
+ if (existsSync11(pkgPath)) {
1863
+ const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
1864
+ if (pkg.name) config.projectName = pkg.name;
1865
+ }
1866
+ } catch {
1867
+ }
1868
+ return config;
1869
+ }
1870
+ function applyStack(projectRoot, magicDir, name) {
1871
+ if (!profileExists(projectRoot, magicDir, name)) {
1872
+ const registry = loadProfileRegistry();
1873
+ 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`);
1875
+ process.exit(1);
1876
+ }
1877
+ const config = buildConfig(projectRoot, magicDir, name);
1878
+ saveStack(projectRoot, magicDir, name);
1879
+ const coreFiles = renderCore(config, false);
1880
+ const stackRelated = /* @__PURE__ */ new Map();
1881
+ for (const [relPath, content] of coreFiles) {
1882
+ if (relPath.includes("/rules/profiles/") || relPath.endsWith("/rules/project_rules.md")) {
1883
+ stackRelated.set(relPath, content);
1884
+ }
1885
+ }
1886
+ const hashMap = {};
1887
+ try {
1888
+ Object.assign(hashMap, JSON.parse(readFileSync8(resolve8(projectRoot, magicDir, HASH_OUTPUT_FILE), "utf-8")));
1889
+ } catch {
1890
+ }
1891
+ let written = 0;
1892
+ for (const [relPath, content] of stackRelated) {
1893
+ for (const t of [".add", magicDir]) {
1894
+ const targetPath = resolve8(projectRoot, relPath.replace(/^\.add/, t));
1895
+ mkdirSync6(join5(targetPath, ".."), { recursive: true });
1896
+ writeFileSync6(targetPath, content, "utf-8");
1897
+ hashMap[relPath.replace(/^\.add/, t)] = hash82(content);
1898
+ written++;
1899
+ }
1900
+ }
1901
+ writeFileSync6(resolve8(projectRoot, magicDir, HASH_OUTPUT_FILE), JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
1902
+ console.log(`\u2705 \u6280\u672F\u6808\u5DF2\u8BBE\u7F6E\u4E3A ${name}`);
1903
+ 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"}`);
1905
+ console.log(` project_rules.md \u5F15\u7528\u884C\u5DF2\u66F4\u65B0 + hash \u5DF2\u5237\u65B0\uFF08${written} \u4E2A\u6587\u4EF6\uFF09`);
1906
+ }
1907
+
1624
1908
  // src/cli/index.ts
1625
1909
  var { version } = JSON.parse(
1626
- readFileSync8(new URL("../package.json", import.meta.url), "utf-8")
1910
+ readFileSync9(new URL("../package.json", import.meta.url), "utf-8")
1627
1911
  );
1628
1912
  var program = new Command();
1629
1913
  program.name("add-coder").description("\u521D\u59CB\u5316 ADD \u8303\u5F0F\u5DE5\u4F5C\u6D41\u6A21\u677F").version(version);
1630
- program.command("init").description("\u521D\u59CB\u5316 ADD \u6A21\u677F\u5230\u5F53\u524D\u9879\u76EE").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode").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").action(initCommand);
1631
- 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").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);
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);
1632
1916
  program.command("status").description("\u68C0\u67E5 ADD \u6A21\u677F\u5B8C\u6574\u6027").action(statusCommand);
1917
+ 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
+ (sub, name, options) => stackCommand(sub, name, options)
1919
+ );
1633
1920
  program.parse();