add-coder 0.2.1 → 0.2.3

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 (88) hide show
  1. package/dist/index.js +370 -155
  2. package/package.json +1 -1
  3. package/templates/adapters/claude/hooks/doc-format-guard.sh +25 -14
  4. package/templates/adapters/claude/hooks/lib/common.sh +261 -0
  5. package/templates/adapters/claude/hooks/lib/context-inject.sh +96 -0
  6. package/templates/adapters/claude/hooks/lib/preload-templates.sh +187 -0
  7. package/templates/adapters/claude/hooks/lib/session-end.sh +76 -0
  8. package/templates/adapters/claude/hooks/lib/state-detect.sh +117 -0
  9. package/templates/adapters/claude/hooks/lib/subagent-stop.sh +90 -0
  10. package/templates/adapters/claude/hooks/lib/vocabulary.sh +56 -0
  11. package/templates/adapters/claude/hooks/notification.sh +8 -1
  12. package/templates/adapters/codex/hooks/doc-format-guard.sh +238 -0
  13. package/templates/adapters/codex/hooks/lib/common.sh +261 -0
  14. package/templates/adapters/codex/hooks/lib/context-inject.sh +96 -0
  15. package/templates/adapters/codex/hooks/lib/preload-templates.sh +187 -0
  16. package/templates/adapters/codex/hooks/lib/session-end.sh +76 -0
  17. package/templates/adapters/codex/hooks/lib/state-detect.sh +6 -0
  18. package/templates/adapters/codex/hooks/lib/subagent-stop.sh +90 -0
  19. package/templates/adapters/codex/hooks/lib/vocabulary.sh +56 -0
  20. package/templates/adapters/codex/hooks/notification.sh +8 -1
  21. package/templates/adapters/codex/hooks/permission-gate.sh +18 -0
  22. package/templates/adapters/codex/hooks/post-tool-failure.sh +10 -0
  23. package/templates/adapters/codex/hooks/pre-compact.sh +37 -0
  24. package/templates/adapters/codex/hooks/pre-tool-use.sh +112 -59
  25. package/templates/adapters/codex/hooks/prompt-submit.sh +32 -35
  26. package/templates/adapters/codex/hooks/review-checklist.sh +10 -0
  27. package/templates/adapters/codex/hooks/session-end.sh +38 -0
  28. package/templates/adapters/codex/hooks/subagent-guard.sh +15 -0
  29. package/templates/adapters/codex/hooks/subagent-stop.sh +47 -0
  30. package/templates/adapters/qoder/hooks/doc-format-guard.sh +85 -16
  31. package/templates/adapters/qoder/hooks/lib/common.sh +261 -0
  32. package/templates/adapters/qoder/hooks/lib/preload-templates.sh +187 -0
  33. package/templates/adapters/qoder/hooks/lib/session-end.sh +76 -0
  34. package/templates/adapters/qoder/hooks/lib/state-detect.sh +17 -4
  35. package/templates/adapters/qoder/hooks/lib/subagent-stop.sh +90 -0
  36. package/templates/adapters/qoder/hooks/lib/vocabulary.sh +8 -1
  37. package/templates/adapters/qoder/hooks/pre-compact.sh +1 -1
  38. package/templates/adapters/trae/hooks/doc-format-guard.sh +238 -0
  39. package/templates/adapters/trae/hooks/lib/common.sh +261 -0
  40. package/templates/adapters/trae/hooks/lib/context-inject.sh +96 -0
  41. package/templates/adapters/trae/hooks/lib/preload-templates.sh +187 -0
  42. package/templates/adapters/trae/hooks/lib/session-end.sh +76 -0
  43. package/templates/adapters/trae/hooks/lib/state-detect.sh +6 -0
  44. package/templates/adapters/trae/hooks/lib/subagent-stop.sh +90 -0
  45. package/templates/adapters/trae/hooks/lib/vocabulary.sh +56 -0
  46. package/templates/adapters/trae/hooks/notification.sh +8 -1
  47. package/templates/adapters/trae/hooks/permission-gate.sh +18 -0
  48. package/templates/adapters/trae/hooks/pre-compact.sh +1 -1
  49. package/templates/adapters/trae/hooks/pre-tool-use.sh +112 -59
  50. package/templates/adapters/trae/hooks/prompt-submit.sh +32 -35
  51. package/templates/adapters/trae/hooks/review-checklist.sh +10 -0
  52. package/templates/adapters/trae/hooks/session-end.sh +31 -14
  53. package/templates/adapters/trae/hooks/subagent-guard.sh +8 -29
  54. package/templates/adapters/trae/hooks/subagent-stop.sh +40 -13
  55. package/templates/adapters/vscode/hooks/doc-format-guard.sh +183 -0
  56. package/templates/adapters/vscode/hooks/lib/common.sh +261 -0
  57. package/templates/adapters/vscode/hooks/lib/context-inject.sh +96 -0
  58. package/templates/adapters/vscode/hooks/lib/preload-templates.sh +187 -0
  59. package/templates/adapters/vscode/hooks/lib/session-end.sh +76 -0
  60. package/templates/adapters/vscode/hooks/lib/state-detect.sh +117 -0
  61. package/templates/adapters/vscode/hooks/lib/subagent-stop.sh +90 -0
  62. package/templates/adapters/vscode/hooks/lib/vocabulary.sh +56 -0
  63. package/templates/adapters/vscode/hooks/notification.sh +18 -10
  64. package/templates/adapters/vscode/hooks/permission-gate.sh +18 -0
  65. package/templates/adapters/vscode/hooks/review-checklist.sh +10 -0
  66. package/templates/core/hooks/doc-format-guard.sh +86 -17
  67. package/templates/core/hooks/lib/context-inject.sh +96 -0
  68. package/templates/core/hooks/lib/state-detect.sh +117 -0
  69. package/templates/core/hooks/lib/vocabulary.sh +56 -0
  70. package/templates/core/hooks/notification.sh +8 -1
  71. package/templates/core/hooks/pre-compact.sh +1 -1
  72. package/templates/core/hooks/pre-tool-use.sh +112 -59
  73. package/templates/core/hooks/prompt-submit.sh +32 -35
  74. package/templates/core/rules/project_rules.md +12 -6
  75. package/templates/core/scripts/gen-plan-index.sh +84 -0
  76. package/templates/core/skills/add-paradigm/SKILL.md +31 -13
  77. package/templates/core/templates/prd-incremental-template.md +6 -1
  78. package/templates/core/templates/prd-standard-template.md +8 -3
  79. package/templates/core/templates/review-implementation-template.md +14 -0
  80. package/templates/core/templates/review-implementation-template.schema.json +6 -0
  81. package/templates/core/templates/review-template.md +15 -0
  82. package/templates/core/templates/review-template.schema.json +6 -0
  83. package/templates/core/templates/simple-plan-template.md +30 -3
  84. package/templates/core/templates/simple-plan-template.schema.json +11 -5
  85. package/templates/core/templates/spec-template.schema.json +0 -1
  86. package/templates/core/templates/standard-plan-template.md +18 -0
  87. package/templates/core/templates/standard-plan-template.schema.json +9 -4
  88. package/templates/core/vocabulary/add-governance-vocabulary.md +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { readFileSync as readFileSync5 } from "fs";
4
+ import { readFileSync as readFileSync6 } from "fs";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/caijuehub/strategies/detect.strategy.ts
@@ -99,7 +99,9 @@ async function loadConfig(projectRoot, configPath, _options) {
99
99
  if (configPath && existsSync2(configPath)) {
100
100
  try {
101
101
  const userConfig = await import(configPath);
102
- config = { ...config, ...userConfig.default };
102
+ if (userConfig.default) {
103
+ config = { ...config, ...userConfig.default };
104
+ }
103
105
  } catch {
104
106
  }
105
107
  }
@@ -113,25 +115,118 @@ ${errors}`);
113
115
  }
114
116
 
115
117
  // src/caijuehub/strategies/writer.strategy.ts
116
- import { existsSync as existsSync4, mkdirSync, writeFileSync, chmodSync } from "fs";
118
+ import { existsSync as existsSync3, mkdirSync, writeFileSync, chmodSync } from "fs";
117
119
  import { join as join3, dirname } from "path";
118
120
 
119
- // src/lib/utils.ts
120
- import { createInterface } from "readline";
121
- import { existsSync as existsSync3 } from "fs";
121
+ // src/lib/select-files.ts
122
+ import { createInterface, emitKeypressEvents } from "readline";
123
+ import { readFileSync as readFileSync2 } from "fs";
122
124
  import { resolve } from "path";
123
- function ask(q) {
125
+ function diffLines(oldText, newText) {
126
+ const oldLines = oldText.split("\n");
127
+ const newLines = newText.split("\n");
128
+ let changes = 0;
129
+ const maxLen = Math.max(oldLines.length, newLines.length);
130
+ for (let i = 0; i < maxLen; i++) {
131
+ if (oldLines[i] !== newLines[i]) changes++;
132
+ }
133
+ return changes;
134
+ }
135
+ function statusIcon(item) {
136
+ return item.status === "new" ? `\x1B[32m+${item.lines}\x1B[0m \u884C (\u65B0\u5EFA)` : `\x1B[33m~${item.lines}\x1B[0m \u884C (\u8986\u76D6)`;
137
+ }
138
+ function renderList(items, selected, startIdx, height) {
139
+ let out = `
140
+ \u{1F4CB} \u5F85\u5904\u7406\u6587\u4EF6 (${items.length} \u4E2A):
141
+
142
+ `;
143
+ const endIdx = Math.min(startIdx + height, items.length);
144
+ for (let i = startIdx; i < endIdx; i++) {
145
+ const item = items[i];
146
+ const mark = selected.has(i) ? "\x1B[32m[\u2713]\x1B[0m" : "[ ]";
147
+ const num = String(i + 1).padStart(String(items.length).length, " ");
148
+ const path = item.path.length > 55 ? "..." + item.path.slice(-52) : item.path.padEnd(55, " ");
149
+ out += ` ${mark} ${num}. ${path} ${statusIcon(item)}
150
+ `;
151
+ }
152
+ if (items.length > height) {
153
+ out += `
154
+ \u2500\u2500 ${startIdx + 1}-${endIdx} / ${items.length} (\u2191\u2193 \u6EDA\u52A8) \u2500\u2500
155
+ `;
156
+ }
157
+ out += `
158
+ \u547D\u4EE4: [a]\u5168\u9009 [n]\u53D6\u6D88\u5168\u9009 [1-${items.length}]\u5207\u6362 [\u2191\u2193]\u6EDA\u52A8 [Enter]\u786E\u8BA4 [q]\u9000\u51FA
159
+ `;
160
+ return out;
161
+ }
162
+ async function selectFiles(projectRoot, files) {
163
+ const items = [];
164
+ for (const [relPath, content] of files) {
165
+ const absPath = resolve(projectRoot, relPath);
166
+ try {
167
+ const existing = readFileSync2(absPath, "utf-8");
168
+ items.push({ path: relPath, status: "overwrite", lines: diffLines(existing, content) });
169
+ } catch {
170
+ items.push({ path: relPath, status: "new", lines: content.split("\n").length });
171
+ }
172
+ }
173
+ const selected = new Set(items.map((_, i) => i));
174
+ const VISIBLE = Math.min(items.length, 12);
175
+ let scrollIdx = 0;
176
+ process.stdout.write("\x1B[?25l");
177
+ emitKeypressEvents(process.stdin);
124
178
  const rl = createInterface({ input: process.stdin, output: process.stdout });
125
- return new Promise((r) => {
126
- rl.question(q, (a) => {
127
- rl.close();
128
- r(a.trim().toLowerCase());
179
+ const input = process.stdin;
180
+ function draw() {
181
+ process.stdout.write("\x1B[2J\x1B[H");
182
+ process.stdout.write(renderList(items, selected, scrollIdx, VISIBLE));
183
+ }
184
+ draw();
185
+ return new Promise((resolve8) => {
186
+ input.on("keypress", (_char, key) => {
187
+ if (!key) return;
188
+ if (key.name === "up") {
189
+ scrollIdx = Math.max(0, scrollIdx - 1);
190
+ } else if (key.name === "down") {
191
+ scrollIdx = Math.min(items.length - VISIBLE, scrollIdx + 1);
192
+ } else if (key.name === "return") {
193
+ rl.close();
194
+ } else {
195
+ return;
196
+ }
197
+ draw();
198
+ });
199
+ rl.on("line", (input2) => {
200
+ const trimmed = input2.trim().toLowerCase();
201
+ if (trimmed === "q" || trimmed === "quit") {
202
+ rl.close();
203
+ } else if (trimmed === "a") {
204
+ items.forEach((_, i) => selected.add(i));
205
+ } else if (trimmed === "n") {
206
+ selected.clear();
207
+ } else {
208
+ const num = parseInt(trimmed, 10);
209
+ if (!isNaN(num) && num >= 1 && num <= items.length) {
210
+ const idx = num - 1;
211
+ if (selected.has(idx)) selected.delete(idx);
212
+ else selected.add(idx);
213
+ }
214
+ }
215
+ draw();
216
+ });
217
+ rl.on("close", () => {
218
+ process.stdout.write("\x1B[?25h");
219
+ process.stdout.write("\x1B[2J\x1B[H");
220
+ const result = /* @__PURE__ */ new Map();
221
+ let idx = 0;
222
+ for (const [relPath, content] of files) {
223
+ if (selected.has(idx)) result.set(relPath, content);
224
+ idx++;
225
+ }
226
+ resolve8(result);
129
227
  });
130
228
  });
131
229
  }
132
- function detectPm(projectRoot) {
133
- return existsSync3(resolve(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
134
- }
135
230
 
136
231
  // src/caijuehub/strategies/writer.strategy.ts
137
232
  var WRITER_CONFIG = {
@@ -142,44 +237,61 @@ var WRITER_CONFIG = {
142
237
  async function writeFiles(projectRoot, files, options = {}) {
143
238
  const C = WRITER_CONFIG;
144
239
  let created = 0, skipped = 0, overwritten = 0;
145
- let skipAll = false;
240
+ const newFiles = /* @__PURE__ */ new Map();
241
+ const conflicts = /* @__PURE__ */ new Map();
146
242
  for (const [relPath, content] of files) {
147
243
  const dest = join3(projectRoot, relPath);
148
- if (options.dryRun) {
149
- console.log(`[dry-run] ${existsSync4(dest) ? "\u8986\u76D6" : "\u65B0\u5EFA"}: ${relPath}`);
150
- created++;
151
- continue;
244
+ if (existsSync3(dest)) {
245
+ conflicts.set(relPath, content);
246
+ } else {
247
+ newFiles.set(relPath, content);
152
248
  }
153
- if (existsSync4(dest)) {
154
- if (options.force) {
155
- overwritten++;
156
- } else if (options.yes || skipAll || C.onExisting === "skip") {
157
- skipped++;
158
- continue;
159
- } else {
160
- const choice = await ask(`\u6587\u4EF6\u5DF2\u5B58\u5728 ${relPath}\uFF1A[s]\u8DF3\u8FC7 / [o]\u8986\u76D6 / [a]\u5168\u90E8\u8DF3\u8FC7\uFF08\u9ED8\u8BA4 s\uFF09: `);
161
- if (choice === "a") {
162
- skipAll = true;
163
- skipped++;
164
- continue;
165
- }
166
- if (choice !== "o" && choice !== "overwrite") {
167
- skipped++;
168
- continue;
169
- }
170
- overwritten++;
171
- }
249
+ }
250
+ let overwriteSet = /* @__PURE__ */ new Set();
251
+ if (conflicts.size > 0 && !options.dryRun) {
252
+ if (options.force) {
253
+ overwriteSet = new Set(conflicts.keys());
254
+ } else if (options.yes || C.onExisting === "skip") {
172
255
  } else {
256
+ const selected = await selectFiles(projectRoot, conflicts);
257
+ overwriteSet = new Set(selected.keys());
258
+ }
259
+ }
260
+ for (const [relPath, content] of newFiles) {
261
+ if (options.dryRun) {
262
+ console.log(`[dry-run] \u65B0\u5EFA: ${relPath}`);
173
263
  created++;
264
+ continue;
174
265
  }
175
- mkdirSync(dirname(dest), { recursive: true });
176
- writeFileSync(dest, content, "utf-8");
266
+ mkdirSync(dirname(join3(projectRoot, relPath)), { recursive: true });
267
+ writeFileSync(join3(projectRoot, relPath), content, "utf-8");
177
268
  if (relPath.endsWith(".sh") && C.shellChmod) {
178
269
  try {
179
- chmodSync(dest, 493);
270
+ chmodSync(join3(projectRoot, relPath), 493);
180
271
  } catch {
181
272
  }
182
273
  }
274
+ created++;
275
+ }
276
+ for (const [relPath, content] of conflicts) {
277
+ if (options.dryRun) {
278
+ console.log(`[dry-run] ${overwriteSet.has(relPath) ? "\u8986\u76D6" : "\u8DF3\u8FC7"}: ${relPath}`);
279
+ created++;
280
+ continue;
281
+ }
282
+ if (overwriteSet.has(relPath)) {
283
+ mkdirSync(dirname(join3(projectRoot, relPath)), { recursive: true });
284
+ writeFileSync(join3(projectRoot, relPath), content, "utf-8");
285
+ if (relPath.endsWith(".sh") && C.shellChmod) {
286
+ try {
287
+ chmodSync(join3(projectRoot, relPath), 493);
288
+ } catch {
289
+ }
290
+ }
291
+ overwritten++;
292
+ } else {
293
+ skipped++;
294
+ }
183
295
  }
184
296
  return { created, skipped, overwritten };
185
297
  }
@@ -190,7 +302,7 @@ async function writeFiles2(projectRoot, files, options = {}) {
190
302
  }
191
303
 
192
304
  // src/core/renderer.ts
193
- import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
305
+ import { readFileSync as readFileSync3, readdirSync, statSync } from "fs";
194
306
  import { join as join4, relative, dirname as dirname2 } from "path";
195
307
  import { fileURLToPath } from "url";
196
308
  var __filename = fileURLToPath(import.meta.url);
@@ -210,7 +322,8 @@ var PLACEHOLDERS = {
210
322
  function render(content, config) {
211
323
  let result = content;
212
324
  for (const [placeholder, key] of Object.entries(PLACEHOLDERS)) {
213
- result = result.replaceAll(placeholder, String(config[key]));
325
+ const value = config[key];
326
+ result = result.replaceAll(placeholder, value);
214
327
  }
215
328
  return result;
216
329
  }
@@ -226,7 +339,7 @@ function renderCore(config, dryRun) {
226
339
  if (statSync(full).isDirectory()) {
227
340
  if (!SKIP_DIRS.has(name)) walk(full, join4(base, name));
228
341
  } else {
229
- const content = readFileSync2(full, "utf-8");
342
+ const content = readFileSync3(full, "utf-8");
230
343
  const rendered = render(content, config);
231
344
  const targetRel = join4(CORE_TARGET, relative(CORE_DIR, full));
232
345
  files.set(targetRel, rendered);
@@ -252,7 +365,7 @@ function renderAdapterBase(config, magicPath, githubHooksToRoot, dryRun) {
252
365
  if (statSync(full).isDirectory()) {
253
366
  walk(full, join4(base, name), targetPrefix);
254
367
  } else {
255
- const content = readFileSync2(full, "utf-8");
368
+ const content = readFileSync3(full, "utf-8");
256
369
  const rendered = render(content, config);
257
370
  let targetRel;
258
371
  if (targetPrefix) {
@@ -299,14 +412,31 @@ function renderAdapter5(config, targetDir, dryRun, magicDir) {
299
412
  return renderAdapterBase(config, magicDir, magicDir === ".vscode", dryRun);
300
413
  }
301
414
 
415
+ // src/lib/utils.ts
416
+ import { createInterface as createInterface2 } from "readline";
417
+ import { existsSync as existsSync4 } from "fs";
418
+ import { resolve as resolve2 } from "path";
419
+ function ask(q) {
420
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
421
+ return new Promise((r) => {
422
+ rl.question(q, (a) => {
423
+ rl.close();
424
+ r(a.trim().toLowerCase());
425
+ });
426
+ });
427
+ }
428
+ function detectPm(projectRoot) {
429
+ return existsSync4(resolve2(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
430
+ }
431
+
302
432
  // src/cli/prisma-injector.ts
303
- import { resolve as resolve3, dirname as dirname3 } from "path";
433
+ import { resolve as resolve4, dirname as dirname3 } from "path";
304
434
  import { fileURLToPath as fileURLToPath2 } from "url";
305
435
 
306
436
  // src/caijuehub/strategies/prisma.strategy.ts
307
437
  import { spawnSync } from "child_process";
308
- import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
309
- import { resolve as resolve2 } from "path";
438
+ import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
439
+ import { resolve as resolve3 } from "path";
310
440
  var PRISMA_CONFIG = {
311
441
  onMissing: "ask",
312
442
  onExistingAddPrisma: "ask",
@@ -317,7 +447,7 @@ var PRISMA_CONFIG = {
317
447
  requiresUserModel: false
318
448
  };
319
449
  function ensurePrismaConfig(projectRoot) {
320
- const configPath = resolve2(projectRoot, "prisma.config.ts");
450
+ const configPath = resolve3(projectRoot, "prisma.config.ts");
321
451
  writeFileSync2(configPath, [
322
452
  'import dotenv from "dotenv";',
323
453
  'import { existsSync } from "fs";',
@@ -336,7 +466,7 @@ function ensurePrismaConfig(projectRoot) {
336
466
  function backupAddTables(projectRoot) {
337
467
  const pgDump = spawnSync("which", ["pg_dump"], { timeout: 2e3 });
338
468
  if (pgDump.status !== 0) return null;
339
- const bak = resolve2(projectRoot, `add-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.sql`);
469
+ const bak = resolve3(projectRoot, `add-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.sql`);
340
470
  const r = spawnSync("pg_dump", ["--table=AddUser", "--table=DevOperation", "--table=AuditLog", "--if-exists"], {
341
471
  cwd: projectRoot,
342
472
  stdio: ["ignore", "pipe", "pipe"],
@@ -360,7 +490,7 @@ function runPrismaInit(projectRoot, provider, schemaPath) {
360
490
  });
361
491
  if (initResult.status !== 0 || !existsSync5(schemaPath)) {
362
492
  console.log("prisma init \u5931\u8D25\uFF0C\u624B\u52A8\u521B\u5EFA schema.prisma ...");
363
- const prismaDir = resolve2(projectRoot, "prisma");
493
+ const prismaDir = resolve3(projectRoot, "prisma");
364
494
  if (!existsSync5(prismaDir)) mkdirSync2(prismaDir, { recursive: true });
365
495
  const content = `generator client {
366
496
  provider = "prisma-client-js"
@@ -371,7 +501,7 @@ datasource db {
371
501
  }
372
502
  `;
373
503
  writeFileSync2(schemaPath, content, "utf-8");
374
- const devEnvPath = resolve2(projectRoot, ".env.development");
504
+ const devEnvPath = resolve3(projectRoot, ".env.development");
375
505
  if (!existsSync5(devEnvPath)) {
376
506
  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"';
377
507
  writeFileSync2(devEnvPath, defaultUrl + "\n", "utf-8");
@@ -382,13 +512,13 @@ datasource db {
382
512
  return true;
383
513
  }
384
514
  function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
385
- const envPath = resolve2(projectRoot, ".env");
386
- const devEnvPath = resolve2(projectRoot, ".env.development");
515
+ const envPath = resolve3(projectRoot, ".env");
516
+ const devEnvPath = resolve3(projectRoot, ".env.development");
387
517
  if (existsSync5(envPath)) {
388
- const envContent = readFileSync3(envPath, "utf-8");
518
+ const envContent = readFileSync4(envPath, "utf-8");
389
519
  const dbUrl = envContent.match(/DATABASE_URL=.*/);
390
520
  if (dbUrl) {
391
- const existing = existsSync5(devEnvPath) ? readFileSync3(devEnvPath, "utf-8") : "";
521
+ const existing = existsSync5(devEnvPath) ? readFileSync4(devEnvPath, "utf-8") : "";
392
522
  if (!existing.includes("DATABASE_URL=")) {
393
523
  writeFileSync2(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
394
524
  `, "utf-8");
@@ -402,9 +532,9 @@ function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
402
532
  }
403
533
  async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
404
534
  const C = PRISMA_CONFIG;
405
- const prismaDir = resolve2(projectRoot, "prisma");
406
- const schemaPath = resolve2(prismaDir, "schema.prisma");
407
- const destPath = resolve2(prismaDir, "add.prisma");
535
+ const prismaDir = resolve3(projectRoot, "prisma");
536
+ const schemaPath = resolve3(prismaDir, "schema.prisma");
537
+ const destPath = resolve3(prismaDir, "add.prisma");
408
538
  let justInited = false;
409
539
  if (!existsSync5(prismaDir) || !existsSync5(schemaPath)) {
410
540
  if (C.onMissing === "skip") {
@@ -442,8 +572,8 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
442
572
  console.log("\u8986\u76D6");
443
573
  } else if (choice === "d") {
444
574
  copyFileSync(destPath, destPath + ".bak");
445
- console.log("=== \u5F53\u524D\uFF08\u5DF2\u5907\u4EFD\uFF09===\n" + readFileSync3(destPath, "utf-8"));
446
- console.log("=== \u6A21\u677F ===\n" + readFileSync3(addPrismaTemplate, "utf-8"));
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"));
447
577
  if (await ask("\u786E\u8BA4\u8986\u76D6\uFF1F[y/N] ") !== "y") {
448
578
  console.log("\u5DF2\u8DF3\u8FC7");
449
579
  return true;
@@ -492,14 +622,14 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
492
622
  // src/cli/prisma-injector.ts
493
623
  var __filename2 = fileURLToPath2(import.meta.url);
494
624
  var __dirname2 = dirname3(__filename2);
495
- var ADD_PRISMA_TEMPLATE = resolve3(__dirname2, "../templates/core/prisma/add.prisma");
625
+ var ADD_PRISMA_TEMPLATE = resolve4(__dirname2, "../templates/core/prisma/add.prisma");
496
626
  async function injectPrisma2(projectRoot, options = {}) {
497
627
  return injectPrisma(projectRoot, ADD_PRISMA_TEMPLATE, options);
498
628
  }
499
629
 
500
630
  // src/cli/commands/init.ts
501
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
502
- import { resolve as resolve4 } from "path";
631
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2, readdirSync as readdirSync2 } from "fs";
632
+ import { resolve as resolve5 } from "path";
503
633
  import { spawnSync as spawnSync2 } from "child_process";
504
634
  import { createConnection } from "net";
505
635
  var ADAPTER_RENDERERS = {
@@ -510,6 +640,14 @@ var ADAPTER_RENDERERS = {
510
640
  codex: renderAdapter5
511
641
  };
512
642
  var MAGIC_DIR_MAP = { claude: ".claude", qoder: ".qoder", vscode: ".vscode", trae: ".trae", codex: ".codex" };
643
+ async function initCommand(options) {
644
+ const ctx = await prepare(options);
645
+ writeComposeEnv(ctx);
646
+ const result = await renderAndWrite(ctx);
647
+ await deployDatabase(ctx);
648
+ deployDocs(ctx);
649
+ finalize(ctx, result);
650
+ }
513
651
  async function resolveAdapter(projectRoot, specified) {
514
652
  if (specified) {
515
653
  if (!MAGIC_DIR_MAP[specified]) throw new Error(`\u672A\u77E5 adapter: ${specified}`);
@@ -644,8 +782,8 @@ networks:
644
782
  `;
645
783
  }
646
784
  function writeSqliteExportScript(projectRoot, dryRun) {
647
- const scriptsDir = resolve4(projectRoot, "scripts");
648
- const scriptPath = resolve4(scriptsDir, "export-db.ts");
785
+ const scriptsDir = resolve5(projectRoot, "scripts");
786
+ const scriptPath = resolve5(scriptsDir, "export-db.ts");
649
787
  const content = `import { PrismaClient } from "@prisma/client";
650
788
  import { writeFileSync, mkdirSync, existsSync } from "fs";
651
789
  import { resolve } from "path";
@@ -674,13 +812,13 @@ main().catch((e) => { console.error(e); process.exit(1); });
674
812
  console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
675
813
  }
676
814
  function injectDbExportScript(projectRoot, dryRun) {
677
- const pkgPath = resolve4(projectRoot, "package.json");
815
+ const pkgPath = resolve5(projectRoot, "package.json");
678
816
  if (!existsSync6(pkgPath)) return;
679
817
  if (dryRun) {
680
818
  console.log("[dry-run] \u5C06\u6CE8\u5165 db:export");
681
819
  return;
682
820
  }
683
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
821
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
684
822
  if (!pkg.scripts) pkg.scripts = {};
685
823
  if (!pkg.scripts["db:export"]) {
686
824
  pkg.scripts["db:export"] = "npx tsx scripts/export-db.ts";
@@ -688,7 +826,7 @@ function injectDbExportScript(projectRoot, dryRun) {
688
826
  console.log("\u5DF2\u5728 package.json \u6CE8\u5165 db:export");
689
827
  }
690
828
  }
691
- async function initCommand(options) {
829
+ async function prepare(options) {
692
830
  const projectRoot = process.cwd();
693
831
  const target = await resolveAdapter(projectRoot, options.adapter);
694
832
  const magicDir = MAGIC_DIR_MAP[target];
@@ -702,35 +840,41 @@ async function initCommand(options) {
702
840
  Object.assign(db, await resolveDbCredentials(!!options.force));
703
841
  }
704
842
  }
705
- if (db.engine === "postgresql" && db.container && db.container !== "manual") {
706
- if (!db.reuseExisting) {
707
- const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
708
- const composePath = resolve4(projectRoot, composeName);
709
- if (!options.dryRun && (!existsSync6(composePath) || options.force)) {
710
- writeFileSync3(composePath, composeContent(config.projectName || "add-project"), "utf-8");
711
- console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
712
- }
843
+ return { projectRoot, options, target, magicDir, config, db };
844
+ }
845
+ function writeComposeEnv(ctx) {
846
+ const { projectRoot, options, config, db } = ctx;
847
+ if (db.engine !== "postgresql" || !db.container || db.container === "manual") return;
848
+ if (!db.reuseExisting) {
849
+ const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
850
+ const composePath = resolve5(projectRoot, composeName);
851
+ if (!options.dryRun && (!existsSync6(composePath) || options.force)) {
852
+ writeFileSync3(composePath, composeContent(config.projectName || "add-project"), "utf-8");
853
+ console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
713
854
  }
714
- const devEnvPath = resolve4(projectRoot, ".env.development");
715
- if (!options.dryRun && existsSync6(devEnvPath)) {
716
- const existing = readFileSync4(devEnvPath, "utf-8");
717
- if (!/^DATABASE_USER=/m.test(existing)) {
718
- writeFileSync3(devEnvPath, existing + `
855
+ }
856
+ const devEnvPath = resolve5(projectRoot, ".env.development");
857
+ if (!options.dryRun && existsSync6(devEnvPath)) {
858
+ const existing = readFileSync5(devEnvPath, "utf-8");
859
+ if (!/^DATABASE_USER=/m.test(existing)) {
860
+ writeFileSync3(devEnvPath, existing + `
719
861
  DATABASE_USER=${db.user || "admin"}
720
862
  DATABASE_PASSWORD=${db.password || "change-me-in-production"}
721
863
  DATABASE_PORT=${db.port || "5433"}
722
864
  PROJECT_NAME=${config.projectName || "add-project"}
723
865
  `, "utf-8");
724
- console.log("\u5DF2\u5C06\u51ED\u636E\u8FFD\u52A0\u5230 .env.development");
725
- }
866
+ console.log("\u5DF2\u5C06\u51ED\u636E\u8FFD\u52A0\u5230 .env.development");
726
867
  }
727
868
  }
728
- const coreFiles = renderCore(config, !!options.dryRun);
869
+ }
870
+ async function renderAndWrite(ctx) {
871
+ const { projectRoot, options, magicDir, config, target } = ctx;
872
+ const dry = !!options.dryRun;
873
+ const coreFiles = renderCore(config, dry);
729
874
  console.log(`Core \u6A21\u677F: ${coreFiles.size} \u6587\u4EF6`);
730
- const CORE_TARGETS = [".add", magicDir];
731
875
  const allFiles = /* @__PURE__ */ new Map();
732
876
  for (const [relPath, content] of coreFiles) {
733
- for (const t of CORE_TARGETS) {
877
+ for (const t of [".add", magicDir]) {
734
878
  const targetPath = relPath.replace(/^\.add/, t);
735
879
  if (!allFiles.has(targetPath)) allFiles.set(targetPath, content);
736
880
  }
@@ -739,103 +883,166 @@ PROJECT_NAME=${config.projectName || "add-project"}
739
883
  for (const adapter of resolved) {
740
884
  const renderFn = ADAPTER_RENDERERS[adapter];
741
885
  if (renderFn) {
742
- const adapterFiles = renderFn(config, projectRoot, !!options.dryRun, magicDir);
886
+ const adapterFiles = renderFn(config, projectRoot, dry, magicDir);
743
887
  for (const [p, c] of adapterFiles) allFiles.set(p, c);
744
888
  console.log(`${adapter} adapter: ${adapterFiles.size} \u6587\u4EF6`);
745
889
  }
746
890
  }
747
891
  if (resolved.includes("vscode") || resolved.includes("trae") || resolved.includes("codex")) {
748
- const claudeFiles = renderAdapter(config, projectRoot, !!options.dryRun, ".claude");
892
+ const claudeFiles = renderAdapter(config, projectRoot, dry, ".claude");
749
893
  for (const [p, c] of claudeFiles) allFiles.set(p, c);
750
894
  console.log(`claude adapter (via Agent Host): ${claudeFiles.size} \u6587\u4EF6`);
751
895
  }
752
- const result = await writeFiles2(projectRoot, allFiles, { force: options.force, dryRun: options.dryRun });
896
+ return await writeFiles2(projectRoot, allFiles, { force: options.force, dryRun: options.dryRun });
897
+ }
898
+ async function deployDatabase(ctx) {
899
+ const { projectRoot, options, magicDir, config, db } = ctx;
900
+ if (options.dryRun) return;
753
901
  if (db.engine === "postgresql" && db.container && db.container !== "manual") {
754
- if (!options.dryRun) {
755
- const dbScript = resolve4(projectRoot, magicDir, "scripts", "db-ensure.sh");
756
- const dbEnv = { ...process.env, DATABASE_USER: db.user, DATABASE_PASSWORD: db.password, DATABASE_PORT: db.port, PROJECT_NAME: config.projectName };
757
- const mode = db.reuseExisting ? "manual" : db.container;
758
- console.log(db.reuseExisting ? "\u590D\u7528\u5DF2\u6709 PostgreSQL ..." : `\u90E8\u7F72\u6570\u636E\u5E93 (${db.container}) ...`);
759
- spawnSync2("bash", [dbScript, "postgresql", mode, "--migrate"], { cwd: projectRoot, stdio: "inherit", env: dbEnv });
760
- try {
761
- await injectPrisma2(projectRoot, { force: !!options.force });
762
- } catch (e) {
763
- console.log(`Prisma \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
764
- }
902
+ const dbScript = resolve5(projectRoot, magicDir, "scripts", "db-ensure.sh");
903
+ const dbEnv = { ...process.env, DATABASE_USER: db.user, DATABASE_PASSWORD: db.password, DATABASE_PORT: db.port, PROJECT_NAME: config.projectName };
904
+ const mode = db.reuseExisting ? "manual" : db.container;
905
+ console.log(db.reuseExisting ? "\u590D\u7528\u5DF2\u6709 PostgreSQL ..." : `\u90E8\u7F72\u6570\u636E\u5E93 (${db.container}) ...`);
906
+ spawnSync2("bash", [dbScript, "postgresql", mode, "--migrate"], { cwd: projectRoot, stdio: "inherit", env: dbEnv });
907
+ try {
908
+ await injectPrisma2(projectRoot, { force: !!options.force });
909
+ } catch (e) {
910
+ console.log(`Prisma \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
765
911
  }
766
912
  }
767
913
  if (db.engine === "postgresql" && db.container === "manual") {
768
- if (!options.dryRun) {
769
- const dbScript = resolve4(projectRoot, magicDir, "scripts", "db-ensure.sh");
770
- if (existsSync6(dbScript)) spawnSync2("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
771
- console.log(["", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501", "ADD \u6A21\u677F\u5DF2\u5C31\u4F4D\u3002\u5728\u5B8C\u6210\u4EE5\u4E0B\u64CD\u4F5C\u524D MCP \u4E0D\u53EF\u7528\uFF1A", "", "1. \u7F16\u8F91 .env.development\uFF0C\u914D\u7F6E DATABASE_URL", "2. \u91CD\u65B0\u8FD0\u884C add-coder init \u5B8C\u6210\u8FC1\u79FB", "", `\u26A0\uFE0F \u975E PG \u6570\u636E\u5E93\u9700\u7F16\u8F91 ${magicDir}/scripts/mcp-server.ts \u624B\u52A8\u914D Prisma 7 adapter`, "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"].join("\n"));
772
- }
914
+ const dbScript = resolve5(projectRoot, magicDir, "scripts", "db-ensure.sh");
915
+ if (existsSync6(dbScript)) spawnSync2("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
916
+ console.log([
917
+ "",
918
+ "\u2501".repeat(30),
919
+ "ADD \u6A21\u677F\u5DF2\u5C31\u4F4D\u3002\u5728\u5B8C\u6210\u4EE5\u4E0B\u64CD\u4F5C\u524D MCP \u4E0D\u53EF\u7528\uFF1A",
920
+ "",
921
+ "1. \u7F16\u8F91 .env.development\uFF0C\u914D\u7F6E DATABASE_URL",
922
+ "2. \u91CD\u65B0\u8FD0\u884C add-coder init \u5B8C\u6210\u8FC1\u79FB",
923
+ "",
924
+ `\u26A0\uFE0F \u975E PG \u6570\u636E\u5E93\u9700\u7F16\u8F91 ${magicDir}/scripts/mcp-server.ts \u624B\u52A8\u914D Prisma 7 adapter`,
925
+ "\u2501".repeat(30)
926
+ ].join("\n"));
773
927
  }
774
928
  if (db.engine === "manual") {
775
- if (!options.dryRun) {
776
- console.log(["", "Prisma \u652F\u6301\u7684 datasource: postgresql / mysql / sqlite / sqlserver / cockroachdb", "\u81EA\u884C prisma init + \u7F16\u8F91 .env.development\uFF0C\u91CD\u65B0 run init \u5B8C\u6210\u8FC1\u79FB\u3002", "", `\u26A0\uFE0F Prisma 7 adapter \u9700\u624B\u52A8\u914D \u2192 ${magicDir}/scripts/mcp-server.ts`].join("\n"));
777
- }
929
+ console.log([
930
+ "",
931
+ "Prisma \u652F\u6301\u7684 datasource: postgresql / mysql / sqlite / sqlserver / cockroachdb",
932
+ "\u81EA\u884C prisma init + \u7F16\u8F91 .env.development\uFF0C\u91CD\u65B0 run init \u5B8C\u6210\u8FC1\u79FB\u3002",
933
+ "",
934
+ `\u26A0\uFE0F Prisma 7 adapter \u9700\u624B\u52A8\u914D \u2192 ${magicDir}/scripts/mcp-server.ts`
935
+ ].join("\n"));
778
936
  }
779
937
  if (db.engine === "sqlite") {
780
- writeSqliteExportScript(projectRoot, !!options.dryRun);
781
- injectDbExportScript(projectRoot, !!options.dryRun);
782
- if (!options.dryRun) {
783
- try {
784
- await injectPrisma2(projectRoot, { force: !!options.force, datasource: "sqlite" });
785
- } catch (e) {
786
- console.log(`SQLite \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
787
- }
938
+ writeSqliteExportScript(projectRoot, false);
939
+ injectDbExportScript(projectRoot, false);
940
+ try {
941
+ await injectPrisma2(projectRoot, { force: !!options.force, datasource: "sqlite" });
942
+ } catch (e) {
943
+ console.log(`SQLite \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
788
944
  }
789
945
  }
790
- if (!options.dryRun) {
791
- const pn = config.projectName || "add-project";
792
- const docsBase = resolve4(projectRoot, "docs", pn, "knowledge");
793
- const groundingSrc = resolve4(import.meta.dirname, "../templates/core/templates");
794
- for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
795
- const srcDir = resolve4(groundingSrc, d);
796
- const destDir = resolve4(docsBase, d);
797
- if (!existsSync6(destDir)) mkdirSync3(destDir, { recursive: true });
798
- if (!existsSync6(srcDir)) continue;
799
- for (const f of readdirSync2(srcDir)) {
800
- const src = resolve4(srcDir, f);
801
- const dest = resolve4(destDir, f);
802
- if (existsSync6(dest)) continue;
803
- try {
804
- copyFileSync2(src, dest);
805
- } catch {
806
- }
946
+ }
947
+ function deployDocs(ctx) {
948
+ const { projectRoot, options, config } = ctx;
949
+ if (options.dryRun) return;
950
+ const pn = config.projectName || "add-project";
951
+ const docsBase = resolve5(projectRoot, "docs", pn, "knowledge");
952
+ const groundingSrc = resolve5(import.meta.dirname, "../templates/core/templates");
953
+ for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
954
+ const srcDir = resolve5(groundingSrc, d);
955
+ const destDir = resolve5(docsBase, d);
956
+ if (!existsSync6(destDir)) mkdirSync3(destDir, { recursive: true });
957
+ if (!existsSync6(srcDir)) continue;
958
+ for (const f of readdirSync2(srcDir)) {
959
+ const src = resolve5(srcDir, f);
960
+ const dest = resolve5(destDir, f);
961
+ if (existsSync6(dest)) continue;
962
+ try {
963
+ copyFileSync2(src, dest);
964
+ } catch {
807
965
  }
808
966
  }
809
967
  }
968
+ }
969
+ function finalize(ctx, result) {
970
+ const { projectRoot, options, db } = ctx;
810
971
  console.log(`
811
972
  \u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}, \u8986\u76D6 ${result.overwritten}`);
812
- if (!options.dryRun) {
813
- if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
814
- const pkg = JSON.parse(readFileSync4(resolve4(import.meta.dirname, "../package.json"), "utf-8"));
815
- const peerNames = Object.keys(pkg.peerDependencies || {});
816
- if (peerNames.length > 0) {
817
- console.log(`
973
+ if (options.dryRun) return;
974
+ if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
975
+ const pkg = JSON.parse(readFileSync5(resolve5(import.meta.dirname, "../package.json"), "utf-8"));
976
+ const peerNames = Object.keys(pkg.peerDependencies || {});
977
+ if (peerNames.length > 0) {
978
+ console.log(`
818
979
  \u5B89\u88C5 peer \u4F9D\u8D56 (${peerNames.join(" ")}) ...`);
819
- const pm = detectPm(projectRoot);
820
- spawnSync2(pm, pm === "pnpm" ? ["add", ...peerNames] : ["install", ...peerNames], { cwd: projectRoot, stdio: "inherit" });
821
- }
822
- if (db.engine !== "manual" && (db.engine !== "postgresql" || db.container !== "manual")) {
823
- console.log("\u63D0\u793A: \u91CD\u542F IDE \u4EE5\u52A0\u8F7D hook \u914D\u7F6E");
824
- }
980
+ const pm = detectPm(projectRoot);
981
+ spawnSync2(pm, pm === "pnpm" ? ["add", ...peerNames] : ["install", ...peerNames], { cwd: projectRoot, stdio: "inherit" });
982
+ }
983
+ if (db.engine !== "manual" && (db.engine !== "postgresql" || db.container !== "manual")) {
984
+ console.log("\u63D0\u793A: \u91CD\u542F IDE \u4EE5\u52A0\u8F7D hook \u914D\u7F6E");
825
985
  }
826
986
  }
827
987
 
828
988
  // src/cli/commands/sync.ts
829
989
  import { existsSync as existsSync7 } from "fs";
830
- import { resolve as resolve5 } from "path";
831
- async function syncCommand() {
990
+ import { resolve as resolve6 } from "path";
991
+ var ADAPTER_RENDERERS2 = {
992
+ claude: renderAdapter,
993
+ qoder: renderAdapter2,
994
+ vscode: renderAdapter3,
995
+ trae: renderAdapter4,
996
+ codex: renderAdapter5
997
+ };
998
+ var MAGIC_DIR_MAP2 = { claude: ".claude", qoder: ".qoder", vscode: ".vscode", trae: ".trae", codex: ".codex" };
999
+ function resolveAdapter2(projectRoot, specified) {
1000
+ if (specified) {
1001
+ if (!MAGIC_DIR_MAP2[specified]) throw new Error(`\u672A\u77E5 adapter: ${specified}`);
1002
+ console.log(`\u76EE\u6807 IDE: ${specified} (--adapter)`);
1003
+ return specified;
1004
+ }
1005
+ const detected = detectIDE2(projectRoot);
1006
+ if (detected !== "auto") {
1007
+ console.log(`\u68C0\u6D4B\u5230 IDE: ${detected} (\u81EA\u52A8)`);
1008
+ return detected;
1009
+ }
1010
+ console.log("\u672A\u68C0\u6D4B\u5230 IDE \u73AF\u5883\uFF0C\u9ED8\u8BA4 qoder");
1011
+ return "qoder";
1012
+ }
1013
+ async function syncCommand(options = {}) {
832
1014
  const projectRoot = process.cwd();
1015
+ const target = resolveAdapter2(projectRoot, options.adapter);
1016
+ const magicDir = MAGIC_DIR_MAP2[target];
833
1017
  const config = await loadConfig(projectRoot);
834
1018
  config.projectRoot = projectRoot;
1019
+ config.magicDir = magicDir;
835
1020
  const coreFiles = renderCore(config, false);
836
- const missing = /* @__PURE__ */ new Map();
1021
+ const CORE_TARGETS = [".add", magicDir];
1022
+ const allFiles = /* @__PURE__ */ new Map();
837
1023
  for (const [relPath, content] of coreFiles) {
838
- if (!existsSync7(resolve5(projectRoot, relPath))) {
1024
+ for (const t of CORE_TARGETS) {
1025
+ const targetPath = relPath.replace(/^\.add/, t);
1026
+ if (!allFiles.has(targetPath)) allFiles.set(targetPath, content);
1027
+ }
1028
+ }
1029
+ const resolved = resolveAdapters2(target);
1030
+ for (const adapter of resolved) {
1031
+ const renderFn = ADAPTER_RENDERERS2[adapter];
1032
+ if (renderFn) {
1033
+ const adapterFiles = renderFn(config, projectRoot, false, magicDir);
1034
+ for (const [p, c] of adapterFiles) allFiles.set(p, c);
1035
+ console.log(`${adapter} adapter: ${adapterFiles.size} \u6587\u4EF6`);
1036
+ }
1037
+ }
1038
+ if (resolved.includes("vscode") || resolved.includes("trae") || resolved.includes("codex")) {
1039
+ const claudeFiles = renderAdapter(config, projectRoot, false, ".claude");
1040
+ for (const [p, c] of claudeFiles) allFiles.set(p, c);
1041
+ console.log(`claude adapter (via Agent Host): ${claudeFiles.size} \u6587\u4EF6`);
1042
+ }
1043
+ const missing = /* @__PURE__ */ new Map();
1044
+ for (const [relPath, content] of allFiles) {
1045
+ if (!existsSync7(resolve6(projectRoot, relPath))) {
839
1046
  missing.set(relPath, content);
840
1047
  }
841
1048
  }
@@ -843,13 +1050,21 @@ async function syncCommand() {
843
1050
  console.log("\u6240\u6709 ADD \u6A21\u677F\u6587\u4EF6\u5DF2\u5C31\u4F4D\u3002");
844
1051
  return;
845
1052
  }
846
- const result = await writeFiles2(projectRoot, missing, {});
1053
+ let filesToWrite = missing;
1054
+ if (options.interactive) {
1055
+ filesToWrite = await selectFiles(projectRoot, missing);
1056
+ if (filesToWrite.size === 0) {
1057
+ console.log("\u672A\u9009\u62E9\u4EFB\u4F55\u6587\u4EF6\uFF0C\u5DF2\u53D6\u6D88\u3002");
1058
+ return;
1059
+ }
1060
+ }
1061
+ const result = await writeFiles2(projectRoot, filesToWrite, { yes: true });
847
1062
  console.log(`\u540C\u6B65\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}`);
848
1063
  }
849
1064
 
850
1065
  // src/cli/commands/status.ts
851
1066
  import { existsSync as existsSync8 } from "fs";
852
- import { resolve as resolve6 } from "path";
1067
+ import { resolve as resolve7 } from "path";
853
1068
  async function statusCommand() {
854
1069
  const projectRoot = process.cwd();
855
1070
  const config = await loadConfig(projectRoot);
@@ -858,7 +1073,7 @@ async function statusCommand() {
858
1073
  const missing = [];
859
1074
  const present = [];
860
1075
  for (const [relPath] of coreFiles) {
861
- if (existsSync8(resolve6(projectRoot, relPath))) {
1076
+ if (existsSync8(resolve7(projectRoot, relPath))) {
862
1077
  present.push(relPath);
863
1078
  } else {
864
1079
  missing.push(relPath);
@@ -876,11 +1091,11 @@ async function statusCommand() {
876
1091
 
877
1092
  // src/cli/index.ts
878
1093
  var { version } = JSON.parse(
879
- readFileSync5(new URL("../package.json", import.meta.url), "utf-8")
1094
+ readFileSync6(new URL("../package.json", import.meta.url), "utf-8")
880
1095
  );
881
1096
  var program = new Command();
882
1097
  program.name("add-coder").description("\u521D\u59CB\u5316 ADD \u8303\u5F0F\u5DE5\u4F5C\u6D41\u6A21\u677F").version(version);
883
1098
  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);
884
- program.command("sync").description("\u589E\u91CF\u540C\u6B65\u7F3A\u5931\u6587\u4EF6").action(syncCommand);
1099
+ program.command("sync").description("\u589E\u91CF\u540C\u6B65\u7F3A\u5931\u6587\u4EF6").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode").option("-i, --interactive", "\u4EA4\u4E92\u5F0F\u9009\u62E9\u8981\u540C\u6B65\u7684\u6587\u4EF6").action(syncCommand);
885
1100
  program.command("status").description("\u68C0\u67E5 ADD \u6A21\u677F\u5B8C\u6574\u6027").action(statusCommand);
886
1101
  program.parse();