add-coder 0.1.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 (97) hide show
  1. package/README.md +50 -0
  2. package/bin/add-coder.js +2 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +576 -0
  5. package/package.json +64 -0
  6. package/templates/adapters/claude/hooks/doc-format-guard.sh +17 -0
  7. package/templates/adapters/claude/hooks/notification.sh +9 -0
  8. package/templates/adapters/claude/hooks/permission-gate.sh +18 -0
  9. package/templates/adapters/claude/hooks/post-tool-failure.sh +10 -0
  10. package/templates/adapters/claude/hooks/post-tool-use.sh +18 -0
  11. package/templates/adapters/claude/hooks/pre-compact.sh +14 -0
  12. package/templates/adapters/claude/hooks/pre-tool-use.sh +28 -0
  13. package/templates/adapters/claude/hooks/prompt-submit.sh +16 -0
  14. package/templates/adapters/claude/hooks/review-checklist.sh +10 -0
  15. package/templates/adapters/claude/hooks/session-start.sh +23 -0
  16. package/templates/adapters/claude/hooks/stop-check.sh +10 -0
  17. package/templates/adapters/claude/hooks/subagent-guard.sh +15 -0
  18. package/templates/adapters/claude/mcp.json +13 -0
  19. package/templates/adapters/claude/settings.json +125 -0
  20. package/templates/adapters/qoder/hooks/doc-format-guard.sh +164 -0
  21. package/templates/adapters/qoder/hooks/lib/context-inject.sh +96 -0
  22. package/templates/adapters/qoder/hooks/lib/state-detect.sh +104 -0
  23. package/templates/adapters/qoder/hooks/lib/vocabulary.sh +49 -0
  24. package/templates/adapters/qoder/hooks/notification.sh +22 -0
  25. package/templates/adapters/qoder/hooks/permission-gate.sh +8 -0
  26. package/templates/adapters/qoder/hooks/post-tool-failure.sh +8 -0
  27. package/templates/adapters/qoder/hooks/post-tool-use.sh +20 -0
  28. package/templates/adapters/qoder/hooks/pre-compact.sh +12 -0
  29. package/templates/adapters/qoder/hooks/pre-tool-use.sh +77 -0
  30. package/templates/adapters/qoder/hooks/prompt-submit.sh +72 -0
  31. package/templates/adapters/qoder/hooks/review-checklist.sh +157 -0
  32. package/templates/adapters/qoder/hooks/session-start.sh +16 -0
  33. package/templates/adapters/qoder/hooks/stop-check.sh +71 -0
  34. package/templates/adapters/qoder/hooks/subagent-guard.sh +11 -0
  35. package/templates/adapters/qoder/mcp.json +13 -0
  36. package/templates/adapters/qoder/settings.json +125 -0
  37. package/templates/adapters/qoder/sync-policy.json +18 -0
  38. package/templates/adapters/vscode/extensions.json +5 -0
  39. package/templates/adapters/vscode/launch.json +16 -0
  40. package/templates/adapters/vscode/settings.json +16 -0
  41. package/templates/adapters/vscode/tasks.json +38 -0
  42. package/templates/core/agents/add-flow-guardian.md +276 -0
  43. package/templates/core/agents/add-orchestrator.md +217 -0
  44. package/templates/core/plans/2026-07/08/farm-agent-add-coder-npm-package-add-route-v1.md +323 -0
  45. package/templates/core/plans/2026-07/08/farm-agent-add-coder-npm-package-handoff-v1.md +678 -0
  46. package/templates/core/plans/2026-07/08/farm-agent-add-coder-npm-package-plan-v1.md +785 -0
  47. package/templates/core/prisma/add.prisma +34 -0
  48. package/templates/core/reports/REPORT-WORKFLOW.md +250 -0
  49. package/templates/core/reports/boundary-runtime-report.md +134 -0
  50. package/templates/core/reports/code-review-combined-report.md +227 -0
  51. package/templates/core/reports/code-review-fix-verification-report.md +505 -0
  52. package/templates/core/reports/code-review-suggestions.md +66 -0
  53. package/templates/core/reports/index.md +57 -0
  54. package/templates/core/reports/runtime-report/gateway.md +741 -0
  55. package/templates/core/rules/project_rules.md +905 -0
  56. package/templates/core/rules/theory-practice-map.toml +105 -0
  57. package/templates/core/scripts/mcp-server.ts +3492 -0
  58. package/templates/core/skills/add-paradigm/SKILL.md +1086 -0
  59. package/templates/core/skills/session-init/SKILL.md +215 -0
  60. package/templates/core/specs/farm-agent-add-coder-npm-package/checklist.md +125 -0
  61. package/templates/core/specs/farm-agent-add-coder-npm-package/spec.md +343 -0
  62. package/templates/core/specs/farm-agent-add-coder-npm-package/tasks.md +203 -0
  63. package/templates/core/templates/01-/346/236/266/346/236/204//343/200/212ADD/345/274/200/345/217/221/345/267/245/344/275/234/350/267/257/345/276/204/344/270/216/346/226/207/346/241/243/345/215/217/345/220/214/350/247/204/350/214/203/343/200/213.md +386 -0
  64. package/templates/core/templates/TERMINOLOGY.md +81 -0
  65. package/templates/core/templates/add-route-template-heavyweight.md +288 -0
  66. package/templates/core/templates/add-route-template.md +242 -0
  67. package/templates/core/templates/add-route-template.schema.json +35 -0
  68. package/templates/core/templates/checklist-template.md +72 -0
  69. package/templates/core/templates/checklist-template.schema.json +20 -0
  70. package/templates/core/templates/fix-verification-template.md +132 -0
  71. package/templates/core/templates/fix-verification-template.schema.json +54 -0
  72. package/templates/core/templates/handoff-multi-round-template.md +295 -0
  73. package/templates/core/templates/handoff-multi-round.schema.json +48 -0
  74. package/templates/core/templates/handoff-single-round-template.md +145 -0
  75. package/templates/core/templates/handoff-single-round.schema.json +92 -0
  76. package/templates/core/templates/index.md +60 -0
  77. package/templates/core/templates/report-template.md +126 -0
  78. package/templates/core/templates/report-template.schema.json +69 -0
  79. package/templates/core/templates/review-implementation-template.md +66 -0
  80. package/templates/core/templates/review-implementation-template.schema.json +58 -0
  81. package/templates/core/templates/review-runtime-template.md +73 -0
  82. package/templates/core/templates/review-runtime-template.schema.json +47 -0
  83. package/templates/core/templates/review-template.md +37 -0
  84. package/templates/core/templates/review-template.schema.json +42 -0
  85. package/templates/core/templates/runtime-report-template.md +73 -0
  86. package/templates/core/templates/runtime-report-template.schema.json +48 -0
  87. package/templates/core/templates/simple-plan-template.md +166 -0
  88. package/templates/core/templates/simple-plan-template.schema.json +109 -0
  89. package/templates/core/templates/spec-template.md +22 -0
  90. package/templates/core/templates/spec-template.schema.json +41 -0
  91. package/templates/core/templates/standard-plan-template.md +96 -0
  92. package/templates/core/templates/standard-plan-template.schema.json +73 -0
  93. package/templates/core/templates/tasks-template.md +54 -0
  94. package/templates/core/templates/tasks-template.schema.json +43 -0
  95. package/templates/core/tools/README.md +361 -0
  96. package/templates/core/vocabulary/add-governance-vocabulary.md +370 -0
  97. package/templates/shared/hooks-lib/common.sh +22 -0
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # add-coder
2
+
3
+ 一键初始化 ADD(Audit-Driven Development)范式工作流模板,支持 Claude Code、Qoder、VS Code 三端。
4
+
5
+ ADD 范式将 AI 开发从"口述代码"升级为"可审计工程"——DPS/RAHS 双质量闸门 + ADD-7 审计追踪 + policy-update-loop 运行时自适应。本 npm 包为 [codein2027](https://github.com/xiaomingming92/codein2027) 第 8 轮架构合流闭包的落地产物。
6
+
7
+ ## 快速开始
8
+
9
+ ```bash
10
+ npx add-coder init
11
+ ```
12
+
13
+ 自动检测 IDE,零配置生成完整 ADD 模板。
14
+
15
+ ## 命令
16
+
17
+ | 命令 | 说明 |
18
+ |------|------|
19
+ | `init` | 初始化 ADD 模板,支持 `--adapter claude\|qoder\|vscode\|auto` |
20
+ | `sync` | 增量同步缺失文件 |
21
+ | `status` | 检查模板完整性 |
22
+
23
+ ### init 选项
24
+
25
+ | 选项 | 说明 |
26
+ |------|------|
27
+ | `--adapter <type>` | 目标 IDE:claude / qoder / vscode / auto(默认) |
28
+ | `--config <path>` | 指定配置文件 |
29
+ | `--yes` | 跳过交互,只创建新文件 |
30
+ | `--force` | 覆盖已有文件 |
31
+ | `--dry-run` | 预览模式,不写入 |
32
+
33
+ ## 生成内容
34
+
35
+ | 目录 | 内容 |
36
+ |------|------|
37
+ | `.add/` | ADD 共享核心(skills、agents、docs、scripts、rules 等) |
38
+ | `.claude/` | Claude Code 适配(hooks、settings.json) |
39
+ | `.qoder/` | Qoder 适配(hooks、settings.json、mcp.json) |
40
+ | `.vscode/` | VS Code 适配(settings.json、tasks.json) |
41
+
42
+ ## 边界说明
43
+
44
+ npm 包提供模板和 MCP 工具链骨架。Report 体系(gateway.md 边界监测)和 policy-update-loop 运行时自适应由宿主项目实现,不在 npm 包覆盖范围内。
45
+
46
+ ## 前置条件
47
+
48
+ - Node.js >= 20
49
+ - Prisma >= 6.9(用于 ADD-7 审计数据库,`init` 时自动检测)
50
+ - PostgreSQL(MCP 工具链依赖)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import("../dist/index.js");
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,576 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/caijuehub/strategies/detect.strategy.ts
7
+ import { existsSync } from "fs";
8
+ import { join } from "path";
9
+ var DETECT_RULES = [
10
+ { env: "QODER_CN_IDE", value: "qoder" },
11
+ { env: "QODERCN_AGENT", value: "qoder" },
12
+ { env: "QODERCN_PROJECT_DIR", value: "qoder" },
13
+ { env: "QODER_PROJECT_DIR", value: "qoder" },
14
+ { env: "CLAUDE_PROJECT_DIR", value: "claude" },
15
+ { env: "TERM_PROGRAM", match: "vscode", value: "vscode" },
16
+ { dir: ".claude", value: "claude" },
17
+ { dir: ".qoder", value: "qoder" },
18
+ { dir: ".vscode", value: "vscode" }
19
+ ];
20
+ var DETECT_FALLBACK = "auto";
21
+ function detectIDE(projectRoot) {
22
+ for (const rule of DETECT_RULES) {
23
+ if (rule.env && process.env[rule.env]) {
24
+ if (rule.match && process.env[rule.env] !== rule.match) continue;
25
+ return rule.value;
26
+ }
27
+ if (rule.dir && existsSync(join(projectRoot, rule.dir))) {
28
+ return rule.value;
29
+ }
30
+ }
31
+ return DETECT_FALLBACK;
32
+ }
33
+
34
+ // src/caijuehub/strategies/adapter.strategy.ts
35
+ var AUTO_DEPLOY_ADAPTERS = ["claude", "qoder", "vscode"];
36
+ function resolveAdapters(target) {
37
+ if (target === "auto") return AUTO_DEPLOY_ADAPTERS;
38
+ return [target];
39
+ }
40
+
41
+ // src/cli/detect.ts
42
+ function detectIDE2(projectRoot) {
43
+ return detectIDE(projectRoot);
44
+ }
45
+ function resolveAdapters2(target) {
46
+ return resolveAdapters(target);
47
+ }
48
+
49
+ // src/cli/config-loader.ts
50
+ import { existsSync as existsSync2, readFileSync } from "fs";
51
+ import { join as join2 } from "path";
52
+
53
+ // src/config/schema.ts
54
+ import { z } from "zod";
55
+ var AdapterEnum = z.enum(["claude", "qoder", "vscode", "auto"]);
56
+ var AddCoderConfigSchema = z.object({
57
+ projectName: z.string().min(1, "\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A"),
58
+ projectRoot: z.string().default(""),
59
+ sourceDir: z.string().default("src"),
60
+ docsDir: z.string().default("docs"),
61
+ logDir: z.string().default("logs"),
62
+ envFilePath: z.string().default(".env"),
63
+ auditLoggerPath: z.string().default("src/lib/agent-audit-logger.ts"),
64
+ mcpServerCommand: z.string().default("tsx"),
65
+ agentAuditImport: z.string().default("@/lib/agent-audit-logger"),
66
+ adapters: z.array(AdapterEnum).default(["auto"]),
67
+ overrides: z.record(z.string(), z.string()).default({})
68
+ });
69
+
70
+ // src/config/defaults.ts
71
+ var defaults = {
72
+ projectName: "",
73
+ projectRoot: "",
74
+ sourceDir: "src",
75
+ docsDir: "docs",
76
+ logDir: "logs",
77
+ envFilePath: ".env",
78
+ auditLoggerPath: "src/lib/agent-audit-logger.ts",
79
+ mcpServerCommand: "tsx",
80
+ agentAuditImport: "@/lib/agent-audit-logger",
81
+ adapters: ["auto"],
82
+ overrides: {}
83
+ };
84
+
85
+ // src/cli/config-loader.ts
86
+ async function loadConfig(projectRoot, configPath, options) {
87
+ let config = { ...defaults };
88
+ const pkgPath = join2(projectRoot, "package.json");
89
+ if (existsSync2(pkgPath)) {
90
+ try {
91
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
92
+ if (pkg.name && !config.projectName) {
93
+ config.projectName = pkg.name;
94
+ }
95
+ } catch {
96
+ }
97
+ }
98
+ if (configPath && existsSync2(configPath)) {
99
+ try {
100
+ const userConfig = await import(configPath);
101
+ config = { ...config, ...userConfig.default };
102
+ } catch {
103
+ }
104
+ }
105
+ const result = AddCoderConfigSchema.safeParse(config);
106
+ if (!result.success) {
107
+ const errors = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
108
+ throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25:
109
+ ${errors}`);
110
+ }
111
+ return result.data;
112
+ }
113
+
114
+ // src/caijuehub/strategies/writer.strategy.ts
115
+ import { existsSync as existsSync3, mkdirSync, writeFileSync, chmodSync } from "fs";
116
+ import { join as join3, dirname } from "path";
117
+ import { createInterface } from "readline";
118
+ var WRITER_CONFIG = {
119
+ onExisting: "ask",
120
+ jsonMerge: "deep",
121
+ shellChmod: true
122
+ };
123
+ function ask(q) {
124
+ 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());
129
+ });
130
+ });
131
+ }
132
+ async function writeFiles(projectRoot, files, options = {}) {
133
+ const C = WRITER_CONFIG;
134
+ let created = 0, skipped = 0, overwritten = 0;
135
+ for (const [relPath, content] of files) {
136
+ const dest = join3(projectRoot, relPath);
137
+ if (options.dryRun) {
138
+ console.log(`[dry-run] ${existsSync3(dest) ? "\u8986\u76D6" : "\u65B0\u5EFA"}: ${relPath}`);
139
+ created++;
140
+ continue;
141
+ }
142
+ if (existsSync3(dest)) {
143
+ if (options.force) {
144
+ overwritten++;
145
+ } else if (options.yes || C.onExisting === "skip") {
146
+ skipped++;
147
+ continue;
148
+ } else {
149
+ const choice = await ask(`\u6587\u4EF6\u5DF2\u5B58\u5728 ${relPath}\uFF1A[s]\u8DF3\u8FC7 / [o]\u8986\u76D6\uFF08\u9ED8\u8BA4 s\uFF09: `);
150
+ if (choice !== "o" && choice !== "overwrite") {
151
+ skipped++;
152
+ continue;
153
+ }
154
+ overwritten++;
155
+ }
156
+ } else {
157
+ created++;
158
+ }
159
+ mkdirSync(dirname(dest), { recursive: true });
160
+ writeFileSync(dest, content, "utf-8");
161
+ if (relPath.endsWith(".sh") && C.shellChmod) {
162
+ try {
163
+ chmodSync(dest, 493);
164
+ } catch {
165
+ }
166
+ }
167
+ }
168
+ return { created, skipped, overwritten };
169
+ }
170
+
171
+ // src/cli/writer.ts
172
+ async function writeFiles2(projectRoot, files, options = {}) {
173
+ return writeFiles(projectRoot, files, options);
174
+ }
175
+
176
+ // src/core/renderer.ts
177
+ import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
178
+ import { join as join4, relative, dirname as dirname2 } from "path";
179
+ import { fileURLToPath } from "url";
180
+ var __filename = fileURLToPath(import.meta.url);
181
+ var __dirname = dirname2(__filename);
182
+ var PLACEHOLDERS = {
183
+ "{{projectName}}": "projectName",
184
+ "{{projectRoot}}": "projectRoot",
185
+ "{{sourceDir}}": "sourceDir",
186
+ "{{docsDir}}": "docsDir",
187
+ "{{logDir}}": "logDir",
188
+ "{{envFilePath}}": "envFilePath",
189
+ "{{auditLoggerPath}}": "auditLoggerPath",
190
+ "{{mcpServerCommand}}": "mcpServerCommand",
191
+ "{{agentAuditImport}}": "agentAuditImport"
192
+ };
193
+ function render(content, config) {
194
+ let result = content;
195
+ for (const [placeholder, key] of Object.entries(PLACEHOLDERS)) {
196
+ result = result.replaceAll(placeholder, config[key]);
197
+ }
198
+ return result;
199
+ }
200
+ var TEMPLATES_ROOT = join4(__dirname, "../templates");
201
+ var CORE_DIR = join4(TEMPLATES_ROOT, "core");
202
+ var CORE_TARGET = ".add";
203
+ function renderCore(config, dryRun) {
204
+ const files = /* @__PURE__ */ new Map();
205
+ function walk(dir, base) {
206
+ for (const name of readdirSync(dir)) {
207
+ const full = join4(dir, name);
208
+ if (statSync(full).isDirectory()) {
209
+ walk(full, join4(base, name));
210
+ } else {
211
+ const content = readFileSync2(full, "utf-8");
212
+ const rendered = render(content, config);
213
+ const targetRel = join4(CORE_TARGET, relative(CORE_DIR, full));
214
+ files.set(targetRel, rendered);
215
+ }
216
+ }
217
+ }
218
+ walk(CORE_DIR, "");
219
+ if (dryRun) {
220
+ console.log(`[dry-run] Core templates: ${files.size} files`);
221
+ }
222
+ return files;
223
+ }
224
+
225
+ // src/adapters/claude/renderer.ts
226
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
227
+ import { join as join5, relative as relative2, dirname as dirname3 } from "path";
228
+ import { fileURLToPath as fileURLToPath2 } from "url";
229
+ var __filename2 = fileURLToPath2(import.meta.url);
230
+ var __dirname2 = dirname3(__filename2);
231
+ var TEMPLATES_ROOT2 = join5(__dirname2, "../templates");
232
+ var ADAPTER_DIR = join5(TEMPLATES_ROOT2, "adapters", "claude");
233
+ var TARGET_MAGIC_PATH = ".claude";
234
+ function renderAdapter(config, targetDir, dryRun) {
235
+ const files = /* @__PURE__ */ new Map();
236
+ function walk(dir, base) {
237
+ for (const name of readdirSync2(dir)) {
238
+ const full = join5(dir, name);
239
+ if (statSync2(full).isDirectory()) {
240
+ walk(full, join5(base, name));
241
+ } else {
242
+ const content = readFileSync3(full, "utf-8");
243
+ const rendered = render(content, config);
244
+ const targetRel = join5(TARGET_MAGIC_PATH, relative2(ADAPTER_DIR, full));
245
+ files.set(targetRel, rendered);
246
+ }
247
+ }
248
+ }
249
+ walk(ADAPTER_DIR, "");
250
+ if (dryRun) {
251
+ console.log(`[dry-run] Claude adapter: ${files.size} files`);
252
+ }
253
+ return files;
254
+ }
255
+
256
+ // src/adapters/qoder/renderer.ts
257
+ import { readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
258
+ import { join as join6, relative as relative3, dirname as dirname4 } from "path";
259
+ import { fileURLToPath as fileURLToPath3 } from "url";
260
+ var __filename3 = fileURLToPath3(import.meta.url);
261
+ var __dirname3 = dirname4(__filename3);
262
+ var TEMPLATES_ROOT3 = join6(__dirname3, "../templates");
263
+ var ADAPTER_DIR2 = join6(TEMPLATES_ROOT3, "adapters", "qoder");
264
+ var TARGET_MAGIC_PATH2 = ".qoder";
265
+ function renderAdapter2(config, targetDir, dryRun) {
266
+ const files = /* @__PURE__ */ new Map();
267
+ function walk(dir, base) {
268
+ for (const name of readdirSync3(dir)) {
269
+ const full = join6(dir, name);
270
+ if (statSync3(full).isDirectory()) {
271
+ walk(full, join6(base, name));
272
+ } else {
273
+ const content = readFileSync4(full, "utf-8");
274
+ const rendered = render(content, config);
275
+ const targetRel = join6(TARGET_MAGIC_PATH2, relative3(ADAPTER_DIR2, full));
276
+ files.set(targetRel, rendered);
277
+ }
278
+ }
279
+ }
280
+ walk(ADAPTER_DIR2, "");
281
+ if (dryRun) {
282
+ console.log(`[dry-run] Qoder adapter: ${files.size} files`);
283
+ }
284
+ return files;
285
+ }
286
+
287
+ // src/adapters/vscode/renderer.ts
288
+ import { readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
289
+ import { join as join7, relative as relative4, dirname as dirname5 } from "path";
290
+ import { fileURLToPath as fileURLToPath4 } from "url";
291
+ var __filename4 = fileURLToPath4(import.meta.url);
292
+ var __dirname4 = dirname5(__filename4);
293
+ var TEMPLATES_ROOT4 = join7(__dirname4, "../templates");
294
+ var ADAPTER_DIR3 = join7(TEMPLATES_ROOT4, "adapters", "vscode");
295
+ var TARGET_MAGIC_PATH3 = ".vscode";
296
+ function renderAdapter3(config, targetDir, dryRun) {
297
+ const files = /* @__PURE__ */ new Map();
298
+ function walk(dir, base) {
299
+ for (const name of readdirSync4(dir)) {
300
+ const full = join7(dir, name);
301
+ if (statSync4(full).isDirectory()) {
302
+ walk(full, join7(base, name));
303
+ } else {
304
+ const content = readFileSync5(full, "utf-8");
305
+ const rendered = render(content, config);
306
+ const targetRel = join7(TARGET_MAGIC_PATH3, relative4(ADAPTER_DIR3, full));
307
+ files.set(targetRel, rendered);
308
+ }
309
+ }
310
+ }
311
+ walk(ADAPTER_DIR3, "");
312
+ if (dryRun) {
313
+ console.log(`[dry-run] VS Code adapter: ${files.size} files`);
314
+ }
315
+ return files;
316
+ }
317
+
318
+ // src/cli/prisma-injector.ts
319
+ import { resolve as resolve2, dirname as dirname6 } from "path";
320
+ import { fileURLToPath as fileURLToPath5 } from "url";
321
+
322
+ // src/caijuehub/strategies/prisma.strategy.ts
323
+ import { execSync } from "child_process";
324
+ import { copyFileSync, existsSync as existsSync4, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
325
+ import { resolve } from "path";
326
+ import { createInterface as createInterface2 } from "readline";
327
+ var PRISMA_CONFIG = {
328
+ onMissing: "ask",
329
+ onExistingAddPrisma: "ask",
330
+ onMigrateFail: "rollback",
331
+ autoGenerate: true,
332
+ migrationName: "add_workflow_init",
333
+ schemaArg: "--schema=prisma/",
334
+ requiresUserModel: true
335
+ };
336
+ function ask2(q) {
337
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
338
+ return new Promise((r) => {
339
+ rl.question(q, (a) => {
340
+ rl.close();
341
+ r(a.trim().toLowerCase());
342
+ });
343
+ });
344
+ }
345
+ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
346
+ const C = PRISMA_CONFIG;
347
+ const prismaDir = resolve(projectRoot, "prisma");
348
+ const schemaPath = resolve(prismaDir, "schema.prisma");
349
+ const destPath = resolve(prismaDir, "add.prisma");
350
+ if (!existsSync4(prismaDir) || !existsSync4(schemaPath)) {
351
+ if (C.onMissing === "skip") {
352
+ console.log("\u8DF3\u8FC7\uFF1A\u7F3A\u5C11 Prisma");
353
+ return true;
354
+ }
355
+ if (C.onMissing === "ask" && !options.force && !options.yes) {
356
+ const a = await ask2("\u9879\u76EE\u7F3A\u5C11 Prisma\uFF0C\u662F\u5426\u6267\u884C prisma init\uFF1F[Y/n] ");
357
+ if (a !== "n" && a !== "no") {
358
+ console.log("\u6267\u884C npx prisma init ...");
359
+ execSync("npx prisma init --datasource-provider postgresql", { cwd: projectRoot, stdio: "inherit" });
360
+ const envPath = resolve(projectRoot, ".env");
361
+ const devEnvPath = resolve(projectRoot, ".env.development");
362
+ if (existsSync4(envPath)) {
363
+ const envContent = readFileSync6(envPath, "utf-8");
364
+ const dbUrl = envContent.match(/DATABASE_URL=.*/);
365
+ if (dbUrl) {
366
+ const existing = existsSync4(devEnvPath) ? readFileSync6(devEnvPath, "utf-8") : "";
367
+ if (!existing.includes("DATABASE_URL=")) {
368
+ writeFileSync2(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
369
+ `, "utf-8");
370
+ }
371
+ execSync("rm -f .env", { cwd: projectRoot });
372
+ console.log("\u5DF2\u5C06 DATABASE_URL \u8FC1\u79FB\u5230 .env.development");
373
+ }
374
+ }
375
+ console.log("\u8BF7\u7F16\u8F91 .env.development \u914D\u7F6E DATABASE_URL\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C add-coder init \u5B8C\u6210\u8FC1\u79FB");
376
+ console.log(" \u53EF\u9009\u6587\u4EF6\u4F18\u5148\u7EA7: .env.development.local > .env.development > .env.local > .env");
377
+ const schemaContent = readFileSync6(schemaPath, "utf-8");
378
+ if (!/model\s+User\s*\{/.test(schemaContent)) {
379
+ writeFileSync2(schemaPath, "model User {\n id String @id @default(cuid())\n}\n\n" + schemaContent, "utf-8");
380
+ console.log("\u5DF2\u6CE8\u5165 User \u6A21\u578B");
381
+ }
382
+ copyFileSync(addPrismaTemplate, destPath);
383
+ console.log("\u5DF2\u590D\u5236 add.prisma");
384
+ return true;
385
+ }
386
+ }
387
+ throw new Error("\u9879\u76EE\u7F3A\u5C11 Prisma \u914D\u7F6E\u3002ADD \u5DE5\u4F5C\u6D41\u4F9D\u8D56 Prisma + PostgreSQL\u3002");
388
+ }
389
+ if (C.requiresUserModel && !/model\s+User\s*\{/.test(readFileSync6(schemaPath, "utf-8"))) {
390
+ const userModel = "model User {\n id String @id @default(cuid())\n}\n";
391
+ if (options.yes || options.force) {
392
+ const schema = readFileSync6(schemaPath, "utf-8");
393
+ writeFileSync2(schemaPath, userModel + "\n" + schema, "utf-8");
394
+ console.log("\u5DF2\u6CE8\u5165 User \u6A21\u578B\u5230 schema.prisma");
395
+ } else {
396
+ const a = await ask2("schema.prisma \u7F3A\u5C11 User \u6A21\u578B\uFF0C\u662F\u5426\u81EA\u52A8\u6CE8\u5165\uFF1F[Y/n] ");
397
+ if (a !== "n" && a !== "no") {
398
+ const schema = readFileSync6(schemaPath, "utf-8");
399
+ writeFileSync2(schemaPath, userModel + "\n" + schema, "utf-8");
400
+ console.log("\u5DF2\u6CE8\u5165 User \u6A21\u578B");
401
+ } else {
402
+ throw new Error("\u9700\u8981 User \u6A21\u578B\uFF08id: String\uFF09\uFF0C\u8BF7\u5728 prisma/schema.prisma \u4E2D\u521B\u5EFA\u540E\u91CD\u8BD5\u3002");
403
+ }
404
+ }
405
+ }
406
+ if (existsSync4(destPath)) {
407
+ if (options.dryRun) {
408
+ console.log("[dry-run] \u5DF2\u6709 add.prisma");
409
+ return true;
410
+ }
411
+ const action = options.force ? "overwrite" : options.yes ? "skip" : C.onExistingAddPrisma;
412
+ if (action === "overwrite") {
413
+ console.log("\u8986\u76D6\u5DF2\u6709 add.prisma");
414
+ } else if (action === "skip") {
415
+ console.log("\u8DF3\u8FC7");
416
+ return true;
417
+ } else {
418
+ const choice = await ask2("\u5DF2\u6709 prisma/add.prisma\uFF1A[s]\u8DF3\u8FC7 / [o]\u8986\u76D6 / [d]diff\uFF08\u9ED8\u8BA4 s\uFF09: ");
419
+ if (choice === "o") {
420
+ console.log("\u8986\u76D6");
421
+ } else if (choice === "d") {
422
+ copyFileSync(destPath, destPath + ".bak");
423
+ console.log("=== \u5F53\u524D\uFF08\u5DF2\u5907\u4EFD\uFF09===\n" + readFileSync6(destPath, "utf-8"));
424
+ console.log("=== \u6A21\u677F ===\n" + readFileSync6(addPrismaTemplate, "utf-8"));
425
+ if (await ask2("\u786E\u8BA4\u8986\u76D6\uFF1F[y/N] ") !== "y") {
426
+ console.log("\u5DF2\u8DF3\u8FC7");
427
+ return true;
428
+ }
429
+ } else {
430
+ console.log("\u5DF2\u8DF3\u8FC7");
431
+ return true;
432
+ }
433
+ }
434
+ }
435
+ if (options.dryRun) {
436
+ console.log("[dry-run] \u5C06\u6267\u884C prisma migrate");
437
+ return true;
438
+ }
439
+ copyFileSync(addPrismaTemplate, destPath);
440
+ console.log("\u5DF2\u590D\u5236 add.prisma");
441
+ try {
442
+ const cmd = `npx prisma migrate dev --name ${C.migrationName} ${C.schemaArg}`;
443
+ console.log(`\u6267\u884C ${cmd} ...`);
444
+ execSync(cmd, { cwd: projectRoot, stdio: "inherit" });
445
+ } catch (err) {
446
+ if (C.onMigrateFail === "keep") {
447
+ console.log("\u8FC1\u79FB\u5931\u8D25\uFF0C\u4FDD\u7559\u6587\u4EF6");
448
+ return true;
449
+ }
450
+ unlinkSync(destPath);
451
+ console.log("\u5DF2\u56DE\u6EDA");
452
+ throw new Error(`\u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
453
+ }
454
+ if (C.autoGenerate) {
455
+ console.log("\u6267\u884C prisma generate ...");
456
+ execSync("npx prisma generate", { cwd: projectRoot, stdio: "inherit" });
457
+ }
458
+ console.log("ADD \u6CBB\u7406\u6A21\u578B\u5DF2\u5C31\u7EEA");
459
+ return true;
460
+ }
461
+
462
+ // src/cli/prisma-injector.ts
463
+ var __filename5 = fileURLToPath5(import.meta.url);
464
+ var __dirname5 = dirname6(__filename5);
465
+ var ADD_PRISMA_TEMPLATE = resolve2(__dirname5, "../templates/core/prisma/add.prisma");
466
+ async function injectPrisma2(projectRoot, options = {}) {
467
+ return injectPrisma(projectRoot, ADD_PRISMA_TEMPLATE, options);
468
+ }
469
+
470
+ // src/cli/commands/init.ts
471
+ var ADAPTER_RENDERERS = {
472
+ claude: renderAdapter,
473
+ qoder: renderAdapter2,
474
+ vscode: renderAdapter3
475
+ };
476
+ async function initCommand(options) {
477
+ const projectRoot = process.cwd();
478
+ const specified = options.adapter && options.adapter !== "auto" ? options.adapter : null;
479
+ const target = specified || detectIDE2(projectRoot);
480
+ console.log(`\u68C0\u6D4B\u5230 IDE: ${target}${specified ? " (--adapter)" : " (\u81EA\u52A8)"}`);
481
+ const config = await loadConfig(projectRoot, options.config, { yes: options.yes, force: options.force });
482
+ config.projectRoot = projectRoot;
483
+ if (!options.dryRun) {
484
+ await injectPrisma2(projectRoot, { yes: options.yes, force: options.force, dryRun: options.dryRun });
485
+ }
486
+ const coreFiles = renderCore(config, !!options.dryRun);
487
+ console.log(`Core \u6A21\u677F: ${coreFiles.size} \u6587\u4EF6`);
488
+ const CORE_TARGETS = [".add", ".qoder", ".claude"];
489
+ const allFiles = /* @__PURE__ */ new Map();
490
+ for (const [relPath, content] of coreFiles) {
491
+ for (const target2 of CORE_TARGETS) {
492
+ const targetPath = relPath.replace(/^\.add/, target2);
493
+ if (!allFiles.has(targetPath)) {
494
+ allFiles.set(targetPath, content);
495
+ }
496
+ }
497
+ }
498
+ const adapters = resolveAdapters2(target);
499
+ for (const adapter of adapters) {
500
+ const renderFn = ADAPTER_RENDERERS[adapter];
501
+ if (renderFn) {
502
+ const adapterFiles = renderFn(config, projectRoot, !!options.dryRun);
503
+ for (const [path, content] of adapterFiles) {
504
+ allFiles.set(path, content);
505
+ }
506
+ console.log(`${adapter} adapter: ${adapterFiles.size} \u6587\u4EF6`);
507
+ }
508
+ }
509
+ const result = await writeFiles2(projectRoot, allFiles, {
510
+ yes: options.yes,
511
+ force: options.force,
512
+ dryRun: options.dryRun
513
+ });
514
+ console.log(`
515
+ \u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}, \u8986\u76D6 ${result.overwritten}`);
516
+ if (!options.dryRun) {
517
+ console.log("\u63D0\u793A: \u91CD\u542F IDE \u4EE5\u52A0\u8F7D hook \u914D\u7F6E");
518
+ }
519
+ }
520
+
521
+ // src/cli/commands/sync.ts
522
+ import { existsSync as existsSync5 } from "fs";
523
+ import { resolve as resolve3 } from "path";
524
+ async function syncCommand() {
525
+ const projectRoot = process.cwd();
526
+ const config = await loadConfig(projectRoot);
527
+ config.projectRoot = projectRoot;
528
+ const coreFiles = renderCore(config, false);
529
+ const missing = /* @__PURE__ */ new Map();
530
+ for (const [relPath, content] of coreFiles) {
531
+ if (!existsSync5(resolve3(projectRoot, relPath))) {
532
+ missing.set(relPath, content);
533
+ }
534
+ }
535
+ if (missing.size === 0) {
536
+ console.log("\u6240\u6709 ADD \u6A21\u677F\u6587\u4EF6\u5DF2\u5C31\u4F4D\u3002");
537
+ return;
538
+ }
539
+ const result = await writeFiles2(projectRoot, missing, { yes: true });
540
+ console.log(`\u540C\u6B65\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}`);
541
+ }
542
+
543
+ // src/cli/commands/status.ts
544
+ import { existsSync as existsSync6 } from "fs";
545
+ import { resolve as resolve4 } from "path";
546
+ async function statusCommand() {
547
+ const projectRoot = process.cwd();
548
+ const config = await loadConfig(projectRoot);
549
+ config.projectRoot = projectRoot;
550
+ const coreFiles = renderCore(config, true);
551
+ const missing = [];
552
+ const present = [];
553
+ for (const [relPath] of coreFiles) {
554
+ if (existsSync6(resolve4(projectRoot, relPath))) {
555
+ present.push(relPath);
556
+ } else {
557
+ missing.push(relPath);
558
+ }
559
+ }
560
+ console.log(`ADD \u6A21\u677F\u5B8C\u6574\u6027\u68C0\u67E5:`);
561
+ console.log(` \u5DF2\u5C31\u4F4D: ${present.length} \u6587\u4EF6`);
562
+ if (missing.length > 0) {
563
+ console.log(` \u7F3A\u5931: ${missing.length} \u6587\u4EF6`);
564
+ missing.forEach((f) => console.log(` - ${f}`));
565
+ } else {
566
+ console.log(` \u6240\u6709\u6587\u4EF6\u5B8C\u6574\u3002`);
567
+ }
568
+ }
569
+
570
+ // src/cli/index.ts
571
+ var program = new Command();
572
+ program.name("add-coder").description("\u521D\u59CB\u5316 ADD \u8303\u5F0F\u5DE5\u4F5C\u6D41\u6A21\u677F").version("0.1.0");
573
+ program.command("init").description("\u521D\u59CB\u5316 ADD \u6A21\u677F\u5230\u5F53\u524D\u9879\u76EE").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode | auto", "auto").option("--config <path>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option("--yes", "\u8DF3\u8FC7\u4EA4\u4E92\uFF0C\u53EA\u521B\u5EFA\u65B0\u6587\u4EF6").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);
574
+ program.command("sync").description("\u589E\u91CF\u540C\u6B65\u7F3A\u5931\u6587\u4EF6").action(syncCommand);
575
+ program.command("status").description("\u68C0\u67E5 ADD \u6A21\u677F\u5B8C\u6574\u6027").action(statusCommand);
576
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "add-coder",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Initialize ADD paradigm workflow templates for Claude Code, Qoder, and VS Code.",
6
+ "type": "module",
7
+ "bin": {
8
+ "add-coder": "bin/add-coder.js"
9
+ },
10
+ "exports": {
11
+ ".": "./dist/index.js",
12
+ "./config": "./dist/config/schema.js",
13
+ "./renderer": "./dist/core/renderer.js"
14
+ },
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "packageManager": "pnpm@11.9.0",
19
+ "files": [
20
+ "bin",
21
+ "dist",
22
+ "templates"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "prepare": "tsup",
27
+ "dev": "tsup --watch",
28
+ "generate": "tsx src/caijuehub/transcribe.ts",
29
+ "test": "node bin/add-coder.js --help"
30
+ },
31
+ "keywords": [
32
+ "add",
33
+ "add-paradigm",
34
+ "claude",
35
+ "qoder",
36
+ "vscode",
37
+ "mcp",
38
+ "workflow"
39
+ ],
40
+ "author": "xiaomingming",
41
+ "email": "wujixmm@gmail.com",
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "commander": "^13.1.0",
45
+ "smol-toml": "^1.7.0",
46
+ "zod": "^3.24.0"
47
+ },
48
+ "peerDependencies": {
49
+ "@prisma/client": "^6.9.0 || ^7.0.0",
50
+ "prisma": "^6.9.0 || ^7.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "@prisma/client": {
54
+ "optional": false
55
+ },
56
+ "prisma": {
57
+ "optional": false
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "tsup": "^8.4.0",
62
+ "typescript": "^5.7.0"
63
+ }
64
+ }