add-coder 0.2.2 → 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.
- package/dist/index.js +370 -155
- package/package.json +1 -1
- package/templates/adapters/claude/hooks/doc-format-guard.sh +24 -13
- package/templates/adapters/claude/hooks/lib/common.sh +261 -0
- package/templates/adapters/claude/hooks/lib/context-inject.sh +96 -0
- package/templates/adapters/claude/hooks/lib/preload-templates.sh +187 -0
- package/templates/adapters/claude/hooks/lib/session-end.sh +76 -0
- package/templates/adapters/claude/hooks/lib/state-detect.sh +117 -0
- package/templates/adapters/claude/hooks/lib/subagent-stop.sh +90 -0
- package/templates/adapters/claude/hooks/lib/vocabulary.sh +56 -0
- package/templates/adapters/claude/hooks/notification.sh +8 -1
- package/templates/adapters/codex/hooks/doc-format-guard.sh +238 -0
- package/templates/adapters/codex/hooks/lib/common.sh +261 -0
- package/templates/adapters/codex/hooks/lib/context-inject.sh +96 -0
- package/templates/adapters/codex/hooks/lib/preload-templates.sh +187 -0
- package/templates/adapters/codex/hooks/lib/session-end.sh +76 -0
- package/templates/adapters/codex/hooks/lib/state-detect.sh +6 -0
- package/templates/adapters/codex/hooks/lib/subagent-stop.sh +90 -0
- package/templates/adapters/codex/hooks/lib/vocabulary.sh +56 -0
- package/templates/adapters/codex/hooks/notification.sh +8 -1
- package/templates/adapters/codex/hooks/permission-gate.sh +18 -0
- package/templates/adapters/codex/hooks/post-tool-failure.sh +10 -0
- package/templates/adapters/codex/hooks/pre-compact.sh +37 -0
- package/templates/adapters/codex/hooks/pre-tool-use.sh +112 -59
- package/templates/adapters/codex/hooks/prompt-submit.sh +32 -35
- package/templates/adapters/codex/hooks/review-checklist.sh +10 -0
- package/templates/adapters/codex/hooks/session-end.sh +38 -0
- package/templates/adapters/codex/hooks/subagent-guard.sh +15 -0
- package/templates/adapters/codex/hooks/subagent-stop.sh +47 -0
- package/templates/adapters/qoder/hooks/doc-format-guard.sh +84 -15
- package/templates/adapters/qoder/hooks/lib/common.sh +261 -0
- package/templates/adapters/qoder/hooks/lib/preload-templates.sh +187 -0
- package/templates/adapters/qoder/hooks/lib/session-end.sh +76 -0
- package/templates/adapters/qoder/hooks/lib/state-detect.sh +17 -4
- package/templates/adapters/qoder/hooks/lib/subagent-stop.sh +90 -0
- package/templates/adapters/qoder/hooks/lib/vocabulary.sh +8 -1
- package/templates/adapters/qoder/hooks/pre-compact.sh +1 -1
- package/templates/adapters/trae/hooks/doc-format-guard.sh +238 -0
- package/templates/adapters/trae/hooks/lib/common.sh +261 -0
- package/templates/adapters/trae/hooks/lib/context-inject.sh +96 -0
- package/templates/adapters/trae/hooks/lib/preload-templates.sh +187 -0
- package/templates/adapters/trae/hooks/lib/session-end.sh +76 -0
- package/templates/adapters/trae/hooks/lib/state-detect.sh +6 -0
- package/templates/adapters/trae/hooks/lib/subagent-stop.sh +90 -0
- package/templates/adapters/trae/hooks/lib/vocabulary.sh +56 -0
- package/templates/adapters/trae/hooks/notification.sh +8 -1
- package/templates/adapters/trae/hooks/permission-gate.sh +18 -0
- package/templates/adapters/trae/hooks/pre-compact.sh +1 -1
- package/templates/adapters/trae/hooks/pre-tool-use.sh +112 -59
- package/templates/adapters/trae/hooks/prompt-submit.sh +32 -35
- package/templates/adapters/trae/hooks/review-checklist.sh +10 -0
- package/templates/adapters/trae/hooks/session-end.sh +31 -14
- package/templates/adapters/trae/hooks/subagent-guard.sh +8 -29
- package/templates/adapters/trae/hooks/subagent-stop.sh +40 -13
- package/templates/adapters/vscode/hooks/doc-format-guard.sh +183 -0
- package/templates/adapters/vscode/hooks/lib/common.sh +261 -0
- package/templates/adapters/vscode/hooks/lib/context-inject.sh +96 -0
- package/templates/adapters/vscode/hooks/lib/preload-templates.sh +187 -0
- package/templates/adapters/vscode/hooks/lib/session-end.sh +76 -0
- package/templates/adapters/vscode/hooks/lib/state-detect.sh +117 -0
- package/templates/adapters/vscode/hooks/lib/subagent-stop.sh +90 -0
- package/templates/adapters/vscode/hooks/lib/vocabulary.sh +56 -0
- package/templates/adapters/vscode/hooks/notification.sh +18 -10
- package/templates/adapters/vscode/hooks/permission-gate.sh +18 -0
- package/templates/adapters/vscode/hooks/review-checklist.sh +10 -0
- package/templates/core/hooks/doc-format-guard.sh +85 -16
- package/templates/core/hooks/lib/state-detect.sh +17 -4
- package/templates/core/hooks/lib/vocabulary.sh +8 -1
- package/templates/core/hooks/notification.sh +8 -1
- package/templates/core/hooks/pre-compact.sh +1 -1
- package/templates/core/hooks/pre-tool-use.sh +1 -1
- package/templates/core/rules/project_rules.md +12 -6
- package/templates/core/skills/add-paradigm/SKILL.md +31 -13
- package/templates/core/templates/prd-incremental-template.md +6 -1
- package/templates/core/templates/prd-standard-template.md +8 -3
- package/templates/core/templates/review-implementation-template.md +14 -0
- package/templates/core/templates/review-implementation-template.schema.json +6 -0
- package/templates/core/templates/review-template.md +15 -0
- package/templates/core/templates/review-template.schema.json +6 -0
- package/templates/core/templates/simple-plan-template.md +30 -3
- package/templates/core/templates/simple-plan-template.schema.json +6 -0
- package/templates/core/templates/standard-plan-template.md +18 -0
- package/templates/core/templates/standard-plan-template.schema.json +6 -0
- 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
|
|
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
|
-
|
|
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
|
|
118
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync, chmodSync } from "fs";
|
|
117
119
|
import { join as join3, dirname } from "path";
|
|
118
120
|
|
|
119
|
-
// src/lib/
|
|
120
|
-
import { createInterface } from "readline";
|
|
121
|
-
import {
|
|
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
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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 (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
244
|
+
if (existsSync3(dest)) {
|
|
245
|
+
conflicts.set(relPath, content);
|
|
246
|
+
} else {
|
|
247
|
+
newFiles.set(relPath, content);
|
|
152
248
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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(
|
|
176
|
-
writeFileSync(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
309
|
-
import { resolve as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
386
|
-
const devEnvPath =
|
|
515
|
+
const envPath = resolve3(projectRoot, ".env");
|
|
516
|
+
const devEnvPath = resolve3(projectRoot, ".env.development");
|
|
387
517
|
if (existsSync5(envPath)) {
|
|
388
|
-
const envContent =
|
|
518
|
+
const envContent = readFileSync4(envPath, "utf-8");
|
|
389
519
|
const dbUrl = envContent.match(/DATABASE_URL=.*/);
|
|
390
520
|
if (dbUrl) {
|
|
391
|
-
const existing = existsSync5(devEnvPath) ?
|
|
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 =
|
|
406
|
-
const schemaPath =
|
|
407
|
-
const destPath =
|
|
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" +
|
|
446
|
-
console.log("=== \u6A21\u677F ===\n" +
|
|
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 =
|
|
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
|
|
502
|
-
import { resolve as
|
|
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 =
|
|
648
|
-
const scriptPath =
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
725
|
-
}
|
|
866
|
+
console.log("\u5DF2\u5C06\u51ED\u636E\u8FFD\u52A0\u5230 .env.development");
|
|
726
867
|
}
|
|
727
868
|
}
|
|
728
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
776
|
-
|
|
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,
|
|
781
|
-
injectDbExportScript(projectRoot,
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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 (
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
|
831
|
-
|
|
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
|
|
1021
|
+
const CORE_TARGETS = [".add", magicDir];
|
|
1022
|
+
const allFiles = /* @__PURE__ */ new Map();
|
|
837
1023
|
for (const [relPath, content] of coreFiles) {
|
|
838
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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();
|