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