cmyr-template-cli 1.45.5 → 1.46.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.
- package/dist/index.js +319 -3
- package/dist/plopfile.js +573 -78
- package/package.json +3 -3
- package/templates/AGENTS.md.ejs +0 -73
package/dist/index.js
CHANGED
|
@@ -23,13 +23,318 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/index.ts
|
|
26
|
-
var
|
|
26
|
+
var import_path4 = __toESM(require("path"));
|
|
27
27
|
var import_plop = require("plop");
|
|
28
28
|
var import_commander = require("commander");
|
|
29
29
|
var import_minimist = __toESM(require("minimist"));
|
|
30
|
+
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
31
|
+
|
|
32
|
+
// src/utils/config.ts
|
|
33
|
+
var import_path = __toESM(require("path"));
|
|
34
|
+
var import_os = __toESM(require("os"));
|
|
30
35
|
var import_fs_extra = __toESM(require("fs-extra"));
|
|
36
|
+
var import_lodash = require("lodash");
|
|
37
|
+
async function loadTemplateCliConfig() {
|
|
38
|
+
const paths = [process.cwd(), import_os.default.homedir()].map((e) => import_path.default.join(e, ".ctrc"));
|
|
39
|
+
const [local, home] = (await Promise.all(paths.map(async (p) => {
|
|
40
|
+
try {
|
|
41
|
+
if (await import_fs_extra.default.pathExists(p)) {
|
|
42
|
+
return await import_fs_extra.default.readJSON(p);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}))).filter(Boolean);
|
|
50
|
+
return (0, import_lodash.mergeWith)(home, local, (objValue, srcValue) => {
|
|
51
|
+
if (typeof objValue === "string" && srcValue === "") {
|
|
52
|
+
return objValue;
|
|
53
|
+
}
|
|
54
|
+
if (typeof srcValue !== "undefined" && srcValue !== null) {
|
|
55
|
+
return srcValue;
|
|
56
|
+
}
|
|
57
|
+
return objValue;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/commands/ai-update.ts
|
|
62
|
+
var import_path3 = __toESM(require("path"));
|
|
63
|
+
|
|
64
|
+
// src/utils/ai-scaffolding.ts
|
|
65
|
+
var import_path2 = __toESM(require("path"));
|
|
66
|
+
var import_os2 = __toESM(require("os"));
|
|
67
|
+
var import_crypto = __toESM(require("crypto"));
|
|
68
|
+
var import_ora = __toESM(require("ora"));
|
|
69
|
+
var import_axios = __toESM(require("axios"));
|
|
70
|
+
var import_adm_zip = __toESM(require("adm-zip"));
|
|
71
|
+
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
72
|
+
|
|
73
|
+
// src/utils/constants.ts
|
|
74
|
+
var GITHUB_API_URL = "https://api.github.com";
|
|
75
|
+
var REMOTES = [
|
|
76
|
+
"https://github.com",
|
|
77
|
+
"https://gh.flyinbug.top/gh/https://github.com",
|
|
78
|
+
"https://cors.isteed.cc/github.com",
|
|
79
|
+
"https://kgithub.com",
|
|
80
|
+
"https://ghproxy.1888866.xyz/https://github.com",
|
|
81
|
+
"https://hub.gitmirror.com/https://github.com",
|
|
82
|
+
"https://ghproxy.cfd/https://github.com",
|
|
83
|
+
"https://github.boki.moe/https://github.com",
|
|
84
|
+
"https://gh-proxy.net/https://github.com",
|
|
85
|
+
"https://gh.monlor.com/https://github.com",
|
|
86
|
+
"https://fastgit.cc/https://github.com",
|
|
87
|
+
"https://github.tbedu.top/https://github.com",
|
|
88
|
+
"https://ghfile.geekertao.top/https://github.com",
|
|
89
|
+
"https://ghp.keleyaa.com/https://github.com",
|
|
90
|
+
"https://ghpxy.hwinzniej.top/https://github.com",
|
|
91
|
+
"https://cdn.crashmc.com/https://github.com",
|
|
92
|
+
"https://git.yylx.win/https://github.com",
|
|
93
|
+
"https://gitproxy.mrhjx.cn/https://github.com",
|
|
94
|
+
"https://ghproxy.cxkpro.top/https://github.com",
|
|
95
|
+
"https://gh.xxooo.cf/https://github.com",
|
|
96
|
+
"https://gh.llkk.cc/https://github.com",
|
|
97
|
+
"https://raw.ihtw.moe/github.com",
|
|
98
|
+
"https://dgithub.xyz",
|
|
99
|
+
"https://gh.nxnow.top/https://github.com",
|
|
100
|
+
"https://gh.zwy.one/https://github.com",
|
|
101
|
+
"https://ghproxy.monkeyray.net/https://github.com",
|
|
102
|
+
"https://ghproxy.net/https://github.com",
|
|
103
|
+
"https://ghfast.top/https://github.com",
|
|
104
|
+
"https://wget.la/https://github.com",
|
|
105
|
+
"https://wget.la/https://github.com",
|
|
106
|
+
"https://hk.gh-proxy.com/https://github.com",
|
|
107
|
+
"https://ghfast.top/https://github.com",
|
|
108
|
+
"https://ghproxy.net/https://github.com"
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
// src/utils/ai-scaffolding.ts
|
|
112
|
+
var DEFAULT_AI_SKILLS_REPOSITORY = "CaoMeiYouRen/cmyr-skills-agents";
|
|
113
|
+
var AI_MANIFEST_RELATIVE_PATH = ".ai/manifest.json";
|
|
114
|
+
function getAiSourceConfig(config) {
|
|
115
|
+
const localPath = config?.AI_SKILLS_LOCAL_PATH?.trim();
|
|
116
|
+
if (localPath) {
|
|
117
|
+
return {
|
|
118
|
+
type: "local",
|
|
119
|
+
localPath: import_path2.default.resolve(localPath)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
type: "github",
|
|
124
|
+
repository: config?.AI_SKILLS_REPOSITORY?.trim() || DEFAULT_AI_SKILLS_REPOSITORY
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function getGitHubLatestCommit(repository) {
|
|
128
|
+
try {
|
|
129
|
+
const response = await import_axios.default.get(`${GITHUB_API_URL}/repos/${repository}/commits/master`, {
|
|
130
|
+
headers: {
|
|
131
|
+
Accept: "application/vnd.github+json"
|
|
132
|
+
},
|
|
133
|
+
timeout: 15 * 1e3
|
|
134
|
+
});
|
|
135
|
+
return response.data?.sha || null;
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function prepareAgentsSource(source) {
|
|
141
|
+
if (source.type === "local") {
|
|
142
|
+
const localPath = source.localPath || "";
|
|
143
|
+
if (!await import_fs_extra2.default.pathExists(localPath)) {
|
|
144
|
+
throw new Error(`AI 技能资产本地路径不存在: ${localPath}`);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
sourceDir: localPath,
|
|
148
|
+
source: { ...source },
|
|
149
|
+
cleanup: async () => void 0
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const repository = source.repository || DEFAULT_AI_SKILLS_REPOSITORY;
|
|
153
|
+
const tempDir = import_path2.default.join(import_os2.default.tmpdir(), `cmyr-skills-agents-${(0, import_crypto.randomUUID)()}`);
|
|
154
|
+
try {
|
|
155
|
+
await downloadAgentsRepositoryZip(repository, tempDir);
|
|
156
|
+
const commit = await getGitHubLatestCommit(repository);
|
|
157
|
+
return {
|
|
158
|
+
sourceDir: tempDir,
|
|
159
|
+
source: { ...source, commit },
|
|
160
|
+
cleanup: async () => {
|
|
161
|
+
await import_fs_extra2.default.remove(tempDir);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
await import_fs_extra2.default.remove(tempDir);
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function downloadAgentsRepositoryZip(repository, destination) {
|
|
170
|
+
const loading = (0, import_ora.default)("正在下载 AI 技能资产仓库……").start();
|
|
171
|
+
try {
|
|
172
|
+
const candidates = REMOTES.map((remote) => `${remote}/${repository}/archive/refs/heads/master.zip`);
|
|
173
|
+
const response = await Promise.any(
|
|
174
|
+
candidates.map((url) => import_axios.default.get(url, {
|
|
175
|
+
responseType: "arraybuffer",
|
|
176
|
+
timeout: 30 * 1e3
|
|
177
|
+
}))
|
|
178
|
+
);
|
|
179
|
+
const zip = new import_adm_zip.default(Buffer.from(response.data));
|
|
180
|
+
const entries = zip.getEntries();
|
|
181
|
+
const topDir = entries[0]?.entryName.split("/")[0];
|
|
182
|
+
if (!topDir) {
|
|
183
|
+
throw new Error("无效的 zip 文件");
|
|
184
|
+
}
|
|
185
|
+
await import_fs_extra2.default.ensureDir(destination);
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
if (entry.entryName.startsWith(`${topDir}/`)) {
|
|
188
|
+
const relativePath = entry.entryName.slice(topDir.length + 1);
|
|
189
|
+
if (!relativePath) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const targetPath = import_path2.default.join(destination, relativePath);
|
|
193
|
+
const resolvedTarget = import_path2.default.resolve(targetPath);
|
|
194
|
+
const resolvedDestination = import_path2.default.resolve(destination);
|
|
195
|
+
const relative = import_path2.default.relative(resolvedDestination, resolvedTarget);
|
|
196
|
+
if (relative.startsWith("..") || import_path2.default.isAbsolute(relative)) {
|
|
197
|
+
throw new Error(`路径遍历攻击检测: ${relativePath}`);
|
|
198
|
+
}
|
|
199
|
+
if (entry.isDirectory) {
|
|
200
|
+
await import_fs_extra2.default.ensureDir(targetPath);
|
|
201
|
+
} else {
|
|
202
|
+
await import_fs_extra2.default.ensureDir(import_path2.default.dirname(targetPath));
|
|
203
|
+
await import_fs_extra2.default.writeFile(targetPath, entry.getData());
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
loading.succeed("AI 技能资产仓库下载成功!");
|
|
208
|
+
} catch (error) {
|
|
209
|
+
loading.fail("AI 技能资产仓库下载失败!");
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function readL0Selection(sourceDir) {
|
|
214
|
+
const manifestPath = import_path2.default.join(sourceDir, "manifest.json");
|
|
215
|
+
const manifest = await import_fs_extra2.default.readJSON(manifestPath);
|
|
216
|
+
const l0 = manifest?.l0Selection;
|
|
217
|
+
if (!l0 || !Array.isArray(l0.skills) || !Array.isArray(l0.agents)) {
|
|
218
|
+
throw new Error("AI 技能资产仓库 manifest.json 缺少有效的 l0Selection");
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
files: Array.isArray(l0.files) ? l0.files : [],
|
|
222
|
+
skills: l0.skills,
|
|
223
|
+
agents: l0.agents
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async function computeFileHash(filePath) {
|
|
227
|
+
const content = await import_fs_extra2.default.readFile(filePath);
|
|
228
|
+
return import_crypto.default.createHash("sha256").update(content).digest("hex");
|
|
229
|
+
}
|
|
230
|
+
async function copyL0Selection(sourceDir, projectPath, l0) {
|
|
231
|
+
const hashes = {};
|
|
232
|
+
const githubSkillsDir = import_path2.default.join(projectPath, ".github/skills");
|
|
233
|
+
const githubAgentsDir = import_path2.default.join(projectPath, ".github/agents");
|
|
234
|
+
await import_fs_extra2.default.ensureDir(githubSkillsDir);
|
|
235
|
+
await import_fs_extra2.default.ensureDir(githubAgentsDir);
|
|
236
|
+
for (const skillName of l0.skills) {
|
|
237
|
+
const sourceSkillDir = import_path2.default.join(sourceDir, "skills", skillName);
|
|
238
|
+
if (!await import_fs_extra2.default.pathExists(sourceSkillDir)) {
|
|
239
|
+
console.warn(`警告: 技能 ${skillName} 在资产仓库中不存在,已跳过`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const targetSkillDir = import_path2.default.join(githubSkillsDir, skillName);
|
|
243
|
+
await import_fs_extra2.default.copy(sourceSkillDir, targetSkillDir);
|
|
244
|
+
await collectHashes(targetSkillDir, import_path2.default.join(".github/skills", skillName), hashes);
|
|
245
|
+
}
|
|
246
|
+
for (const agentName of l0.agents) {
|
|
247
|
+
const sourceAgentFile = import_path2.default.join(sourceDir, "agents", `${agentName}.agent.md`);
|
|
248
|
+
if (!await import_fs_extra2.default.pathExists(sourceAgentFile)) {
|
|
249
|
+
console.warn(`警告: 代理 ${agentName} 在资产仓库中不存在,已跳过`);
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const targetAgentFile = import_path2.default.join(githubAgentsDir, `${agentName}.agent.md`);
|
|
253
|
+
await import_fs_extra2.default.copyFile(sourceAgentFile, targetAgentFile);
|
|
254
|
+
const relativePath = `.github/agents/${agentName}.agent.md`;
|
|
255
|
+
hashes[relativePath] = await computeFileHash(targetAgentFile);
|
|
256
|
+
}
|
|
257
|
+
return hashes;
|
|
258
|
+
}
|
|
259
|
+
async function collectHashes(dir, dirRelativePath, hashes) {
|
|
260
|
+
const entries = await import_fs_extra2.default.readdir(dir, { withFileTypes: true });
|
|
261
|
+
for (const entry of entries) {
|
|
262
|
+
const fullPath = import_path2.default.join(dir, entry.name);
|
|
263
|
+
const relativePath = import_path2.default.join(dirRelativePath, entry.name).split(import_path2.default.sep).join("/");
|
|
264
|
+
if (entry.isDirectory()) {
|
|
265
|
+
await collectHashes(fullPath, relativePath, hashes);
|
|
266
|
+
} else if (entry.isFile()) {
|
|
267
|
+
hashes[relativePath] = await computeFileHash(fullPath);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async function writeAiManifest(projectPath, manifest) {
|
|
272
|
+
const manifestPath = import_path2.default.join(projectPath, AI_MANIFEST_RELATIVE_PATH);
|
|
273
|
+
await import_fs_extra2.default.ensureDir(import_path2.default.dirname(manifestPath));
|
|
274
|
+
await import_fs_extra2.default.writeJSON(manifestPath, manifest, { spaces: 4 });
|
|
275
|
+
}
|
|
276
|
+
async function readAiManifest(projectPath) {
|
|
277
|
+
const manifestPath = import_path2.default.join(projectPath, AI_MANIFEST_RELATIVE_PATH);
|
|
278
|
+
if (!await import_fs_extra2.default.pathExists(manifestPath)) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
return await import_fs_extra2.default.readJSON(manifestPath);
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function updateAiScaffolding(projectPath, config) {
|
|
288
|
+
const loading = (0, import_ora.default)("正在更新 AI 基建快照……").start();
|
|
289
|
+
const oldManifest = await readAiManifest(projectPath);
|
|
290
|
+
if (!oldManifest) {
|
|
291
|
+
loading.fail("未找到 .ai/manifest.json,请先通过脚手架初始化 AI 基建");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const source = getAiSourceConfig(config);
|
|
296
|
+
const prepared = await prepareAgentsSource(source);
|
|
297
|
+
try {
|
|
298
|
+
const l0 = await readL0Selection(prepared.sourceDir);
|
|
299
|
+
const oldSkills = oldManifest.l0Selection?.skills || [];
|
|
300
|
+
const oldAgents = oldManifest.l0Selection?.agents || [];
|
|
301
|
+
for (const skillName of oldSkills) {
|
|
302
|
+
if (!l0.skills.includes(skillName)) {
|
|
303
|
+
await import_fs_extra2.default.remove(import_path2.default.join(projectPath, ".github/skills", skillName));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const agentName of oldAgents) {
|
|
307
|
+
if (!l0.agents.includes(agentName)) {
|
|
308
|
+
await import_fs_extra2.default.remove(import_path2.default.join(projectPath, ".github/agents", `${agentName}.agent.md`));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const hashes = await copyL0Selection(prepared.sourceDir, projectPath, l0);
|
|
312
|
+
await writeAiManifest(projectPath, {
|
|
313
|
+
version: 1,
|
|
314
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
315
|
+
source: prepared.source,
|
|
316
|
+
l0Selection: l0,
|
|
317
|
+
links: oldManifest.links || [],
|
|
318
|
+
hashes
|
|
319
|
+
});
|
|
320
|
+
loading.succeed("AI 基建快照更新成功!");
|
|
321
|
+
} finally {
|
|
322
|
+
await prepared.cleanup();
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
loading.fail("AI 基建快照更新失败!");
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/commands/ai-update.ts
|
|
331
|
+
async function aiUpdateCommand(projectPath, config) {
|
|
332
|
+
await updateAiScaffolding(import_path3.default.resolve(projectPath), config);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/index.ts
|
|
31
336
|
var program = new import_commander.Command("ct").description("草梅项目创建器");
|
|
32
|
-
var pkg =
|
|
337
|
+
var pkg = import_fs_extra3.default.readJSONSync(import_path4.default.join(__dirname, "../package.json"));
|
|
33
338
|
program.version(pkg?.version || "1.0.0", "-v, --version");
|
|
34
339
|
var args = process.argv.slice(2);
|
|
35
340
|
if (args.length === 0) {
|
|
@@ -42,7 +347,7 @@ var create = new import_commander.Command("create").description("创建项目").
|
|
|
42
347
|
import_plop.Plop.launch(
|
|
43
348
|
{
|
|
44
349
|
cwd: argv.cwd,
|
|
45
|
-
configPath:
|
|
350
|
+
configPath: import_path4.default.resolve(__dirname, "./plopfile.js"),
|
|
46
351
|
require: argv.require,
|
|
47
352
|
completion: argv.completion
|
|
48
353
|
},
|
|
@@ -50,6 +355,17 @@ var create = new import_commander.Command("create").description("创建项目").
|
|
|
50
355
|
);
|
|
51
356
|
});
|
|
52
357
|
program.addCommand(create);
|
|
358
|
+
var aiUpdate = new import_commander.Command("ai-update").description("更新 AI 基建快照(技能/代理/植入清单)").option("-p, --path <path>", "项目路径(默认当前目录)", process.cwd()).action(async (opts2) => {
|
|
359
|
+
try {
|
|
360
|
+
const config = await loadTemplateCliConfig();
|
|
361
|
+
await aiUpdateCommand(opts2.path, config);
|
|
362
|
+
process.exit(0);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error(error);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
program.addCommand(aiUpdate);
|
|
53
369
|
program.parse(process.argv);
|
|
54
370
|
var opts = program.opts();
|
|
55
371
|
if (opts.debug) {
|
package/dist/plopfile.js
CHANGED
|
@@ -23,9 +23,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/plopfile.ts
|
|
26
|
-
var
|
|
27
|
-
var
|
|
28
|
-
var
|
|
26
|
+
var import_path13 = __toESM(require("path"));
|
|
27
|
+
var import_fs_extra14 = __toESM(require("fs-extra"));
|
|
28
|
+
var import_ora12 = __toESM(require("ora"));
|
|
29
29
|
|
|
30
30
|
// src/config/env.ts
|
|
31
31
|
var env = process.env;
|
|
@@ -33,12 +33,12 @@ var __DEV__ = env.NODE_ENV === "development";
|
|
|
33
33
|
var PACKAGE_MANAGER = "pnpm";
|
|
34
34
|
|
|
35
35
|
// src/utils/utils.ts
|
|
36
|
-
var
|
|
36
|
+
var import_path12 = __toESM(require("path"));
|
|
37
37
|
var import_colors3 = __toESM(require("@colors/colors"));
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
-
var
|
|
41
|
-
var
|
|
38
|
+
var import_ora11 = __toESM(require("ora"));
|
|
39
|
+
var import_axios3 = __toESM(require("axios"));
|
|
40
|
+
var import_adm_zip2 = __toESM(require("adm-zip"));
|
|
41
|
+
var import_fs_extra13 = __toESM(require("fs-extra"));
|
|
42
42
|
|
|
43
43
|
// src/utils/constants.ts
|
|
44
44
|
var GITHUB_API_URL = "https://api.github.com";
|
|
@@ -2157,15 +2157,372 @@ async function initTest(projectPath, answers) {
|
|
|
2157
2157
|
}
|
|
2158
2158
|
|
|
2159
2159
|
// src/core/ai.ts
|
|
2160
|
+
var import_path11 = __toESM(require("path"));
|
|
2161
|
+
var import_ora10 = __toESM(require("ora"));
|
|
2162
|
+
var import_fs_extra12 = __toESM(require("fs-extra"));
|
|
2163
|
+
|
|
2164
|
+
// src/utils/symlink.ts
|
|
2160
2165
|
var import_path9 = __toESM(require("path"));
|
|
2161
|
-
var import_ora9 = __toESM(require("ora"));
|
|
2162
2166
|
var import_fs_extra10 = __toESM(require("fs-extra"));
|
|
2163
|
-
|
|
2164
|
-
|
|
2167
|
+
function toSymlinkTarget(linkPath, targetPath, method) {
|
|
2168
|
+
if (process.platform === "win32" && method === "junction") {
|
|
2169
|
+
return import_path9.default.win32.resolve(targetPath);
|
|
2170
|
+
}
|
|
2171
|
+
return import_path9.default.relative(import_path9.default.dirname(linkPath), targetPath);
|
|
2172
|
+
}
|
|
2173
|
+
async function isSameLinkTarget(linkPath, targetPath) {
|
|
2174
|
+
try {
|
|
2175
|
+
const resolvedLink = await import_fs_extra10.default.realpath(linkPath);
|
|
2176
|
+
const resolvedTarget = await import_fs_extra10.default.realpath(targetPath);
|
|
2177
|
+
return resolvedLink === resolvedTarget;
|
|
2178
|
+
} catch {
|
|
2179
|
+
return false;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
async function createDirSymlink(linkPath, targetPath) {
|
|
2183
|
+
await import_fs_extra10.default.ensureDir(import_path9.default.dirname(linkPath));
|
|
2184
|
+
try {
|
|
2185
|
+
if (await isSameLinkTarget(linkPath, targetPath)) {
|
|
2186
|
+
return { linkPath, targetPath, method: "symlink", status: "existing" };
|
|
2187
|
+
}
|
|
2188
|
+
const existing = await import_fs_extra10.default.lstat(linkPath);
|
|
2189
|
+
if (existing.isDirectory() || existing.isFile()) {
|
|
2190
|
+
return { linkPath, targetPath, method: "symlink", status: "skipped" };
|
|
2191
|
+
}
|
|
2192
|
+
} catch (error) {
|
|
2193
|
+
if (error?.code !== "ENOENT") {
|
|
2194
|
+
throw error;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
let method = "symlink";
|
|
2198
|
+
if (process.platform === "win32") {
|
|
2199
|
+
try {
|
|
2200
|
+
await import_fs_extra10.default.symlink(import_path9.default.relative(import_path9.default.dirname(linkPath), targetPath), linkPath, "dir");
|
|
2201
|
+
return { linkPath, targetPath, method, status: "created" };
|
|
2202
|
+
} catch {
|
|
2203
|
+
method = "junction";
|
|
2204
|
+
await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, method), linkPath, "junction");
|
|
2205
|
+
return { linkPath, targetPath, method, status: "created" };
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, method), linkPath, "dir");
|
|
2209
|
+
return { linkPath, targetPath, method, status: "created" };
|
|
2210
|
+
}
|
|
2211
|
+
async function createFileSymlink(linkPath, targetPath) {
|
|
2212
|
+
await import_fs_extra10.default.ensureDir(import_path9.default.dirname(linkPath));
|
|
2213
|
+
try {
|
|
2214
|
+
if (await isSameLinkTarget(linkPath, targetPath)) {
|
|
2215
|
+
return { linkPath, targetPath, method: "symlink", status: "existing" };
|
|
2216
|
+
}
|
|
2217
|
+
const existing = await import_fs_extra10.default.lstat(linkPath);
|
|
2218
|
+
if (existing.isDirectory() || existing.isFile()) {
|
|
2219
|
+
return { linkPath, targetPath, method: "symlink", status: "skipped" };
|
|
2220
|
+
}
|
|
2221
|
+
} catch (error) {
|
|
2222
|
+
if (error?.code !== "ENOENT") {
|
|
2223
|
+
throw error;
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
if (process.platform === "win32") {
|
|
2227
|
+
try {
|
|
2228
|
+
await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, "symlink"), linkPath, "file");
|
|
2229
|
+
return { linkPath, targetPath, method: "symlink", status: "created" };
|
|
2230
|
+
} catch {
|
|
2231
|
+
await import_fs_extra10.default.copyFile(targetPath, linkPath);
|
|
2232
|
+
return { linkPath, targetPath, method: "copy", status: "created" };
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, "symlink"), linkPath, "file");
|
|
2236
|
+
return { linkPath, targetPath, method: "symlink", status: "created" };
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
// src/utils/ai-scaffolding.ts
|
|
2240
|
+
var import_path10 = __toESM(require("path"));
|
|
2241
|
+
var import_os2 = __toESM(require("os"));
|
|
2242
|
+
var import_crypto = __toESM(require("crypto"));
|
|
2243
|
+
var import_ora9 = __toESM(require("ora"));
|
|
2244
|
+
var import_axios2 = __toESM(require("axios"));
|
|
2245
|
+
var import_adm_zip = __toESM(require("adm-zip"));
|
|
2246
|
+
var import_fs_extra11 = __toESM(require("fs-extra"));
|
|
2247
|
+
var DEFAULT_AI_SKILLS_REPOSITORY = "CaoMeiYouRen/cmyr-skills-agents";
|
|
2248
|
+
var AI_MANIFEST_RELATIVE_PATH = ".ai/manifest.json";
|
|
2249
|
+
function getAiSourceConfig(config) {
|
|
2250
|
+
const localPath = config?.AI_SKILLS_LOCAL_PATH?.trim();
|
|
2251
|
+
if (localPath) {
|
|
2252
|
+
return {
|
|
2253
|
+
type: "local",
|
|
2254
|
+
localPath: import_path10.default.resolve(localPath)
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
return {
|
|
2258
|
+
type: "github",
|
|
2259
|
+
repository: config?.AI_SKILLS_REPOSITORY?.trim() || DEFAULT_AI_SKILLS_REPOSITORY
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
async function getGitHubLatestCommit(repository) {
|
|
2263
|
+
try {
|
|
2264
|
+
const response = await import_axios2.default.get(`${GITHUB_API_URL}/repos/${repository}/commits/master`, {
|
|
2265
|
+
headers: {
|
|
2266
|
+
Accept: "application/vnd.github+json"
|
|
2267
|
+
},
|
|
2268
|
+
timeout: 15 * 1e3
|
|
2269
|
+
});
|
|
2270
|
+
return response.data?.sha || null;
|
|
2271
|
+
} catch {
|
|
2272
|
+
return null;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
async function prepareAgentsSource(source) {
|
|
2276
|
+
if (source.type === "local") {
|
|
2277
|
+
const localPath = source.localPath || "";
|
|
2278
|
+
if (!await import_fs_extra11.default.pathExists(localPath)) {
|
|
2279
|
+
throw new Error(`AI 技能资产本地路径不存在: ${localPath}`);
|
|
2280
|
+
}
|
|
2281
|
+
return {
|
|
2282
|
+
sourceDir: localPath,
|
|
2283
|
+
source: { ...source },
|
|
2284
|
+
cleanup: async () => void 0
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
const repository = source.repository || DEFAULT_AI_SKILLS_REPOSITORY;
|
|
2288
|
+
const tempDir = import_path10.default.join(import_os2.default.tmpdir(), `cmyr-skills-agents-${(0, import_crypto.randomUUID)()}`);
|
|
2289
|
+
try {
|
|
2290
|
+
await downloadAgentsRepositoryZip(repository, tempDir);
|
|
2291
|
+
const commit = await getGitHubLatestCommit(repository);
|
|
2292
|
+
return {
|
|
2293
|
+
sourceDir: tempDir,
|
|
2294
|
+
source: { ...source, commit },
|
|
2295
|
+
cleanup: async () => {
|
|
2296
|
+
await import_fs_extra11.default.remove(tempDir);
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
await import_fs_extra11.default.remove(tempDir);
|
|
2301
|
+
throw error;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
async function downloadAgentsRepositoryZip(repository, destination) {
|
|
2305
|
+
const loading = (0, import_ora9.default)("正在下载 AI 技能资产仓库……").start();
|
|
2306
|
+
try {
|
|
2307
|
+
const candidates = REMOTES.map((remote) => `${remote}/${repository}/archive/refs/heads/master.zip`);
|
|
2308
|
+
const response = await Promise.any(
|
|
2309
|
+
candidates.map((url) => import_axios2.default.get(url, {
|
|
2310
|
+
responseType: "arraybuffer",
|
|
2311
|
+
timeout: 30 * 1e3
|
|
2312
|
+
}))
|
|
2313
|
+
);
|
|
2314
|
+
const zip = new import_adm_zip.default(Buffer.from(response.data));
|
|
2315
|
+
const entries = zip.getEntries();
|
|
2316
|
+
const topDir = entries[0]?.entryName.split("/")[0];
|
|
2317
|
+
if (!topDir) {
|
|
2318
|
+
throw new Error("无效的 zip 文件");
|
|
2319
|
+
}
|
|
2320
|
+
await import_fs_extra11.default.ensureDir(destination);
|
|
2321
|
+
for (const entry of entries) {
|
|
2322
|
+
if (entry.entryName.startsWith(`${topDir}/`)) {
|
|
2323
|
+
const relativePath = entry.entryName.slice(topDir.length + 1);
|
|
2324
|
+
if (!relativePath) {
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
const targetPath = import_path10.default.join(destination, relativePath);
|
|
2328
|
+
const resolvedTarget = import_path10.default.resolve(targetPath);
|
|
2329
|
+
const resolvedDestination = import_path10.default.resolve(destination);
|
|
2330
|
+
const relative = import_path10.default.relative(resolvedDestination, resolvedTarget);
|
|
2331
|
+
if (relative.startsWith("..") || import_path10.default.isAbsolute(relative)) {
|
|
2332
|
+
throw new Error(`路径遍历攻击检测: ${relativePath}`);
|
|
2333
|
+
}
|
|
2334
|
+
if (entry.isDirectory) {
|
|
2335
|
+
await import_fs_extra11.default.ensureDir(targetPath);
|
|
2336
|
+
} else {
|
|
2337
|
+
await import_fs_extra11.default.ensureDir(import_path10.default.dirname(targetPath));
|
|
2338
|
+
await import_fs_extra11.default.writeFile(targetPath, entry.getData());
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
loading.succeed("AI 技能资产仓库下载成功!");
|
|
2343
|
+
} catch (error) {
|
|
2344
|
+
loading.fail("AI 技能资产仓库下载失败!");
|
|
2345
|
+
throw error;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
async function readL0Selection(sourceDir) {
|
|
2349
|
+
const manifestPath = import_path10.default.join(sourceDir, "manifest.json");
|
|
2350
|
+
const manifest = await import_fs_extra11.default.readJSON(manifestPath);
|
|
2351
|
+
const l0 = manifest?.l0Selection;
|
|
2352
|
+
if (!l0 || !Array.isArray(l0.skills) || !Array.isArray(l0.agents)) {
|
|
2353
|
+
throw new Error("AI 技能资产仓库 manifest.json 缺少有效的 l0Selection");
|
|
2354
|
+
}
|
|
2355
|
+
return {
|
|
2356
|
+
files: Array.isArray(l0.files) ? l0.files : [],
|
|
2357
|
+
skills: l0.skills,
|
|
2358
|
+
agents: l0.agents
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
async function readAgentsTemplate(sourceDir) {
|
|
2362
|
+
const templatePath = import_path10.default.join(sourceDir, "global/AGENTS.template.md");
|
|
2363
|
+
if (!await import_fs_extra11.default.pathExists(templatePath)) {
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2366
|
+
return (await import_fs_extra11.default.readFile(templatePath, "utf8")).toString();
|
|
2367
|
+
}
|
|
2368
|
+
async function computeFileHash(filePath) {
|
|
2369
|
+
const content = await import_fs_extra11.default.readFile(filePath);
|
|
2370
|
+
return import_crypto.default.createHash("sha256").update(content).digest("hex");
|
|
2371
|
+
}
|
|
2372
|
+
async function copyL0Selection(sourceDir, projectPath, l0) {
|
|
2373
|
+
const hashes = {};
|
|
2374
|
+
const githubSkillsDir = import_path10.default.join(projectPath, ".github/skills");
|
|
2375
|
+
const githubAgentsDir = import_path10.default.join(projectPath, ".github/agents");
|
|
2376
|
+
await import_fs_extra11.default.ensureDir(githubSkillsDir);
|
|
2377
|
+
await import_fs_extra11.default.ensureDir(githubAgentsDir);
|
|
2378
|
+
for (const skillName of l0.skills) {
|
|
2379
|
+
const sourceSkillDir = import_path10.default.join(sourceDir, "skills", skillName);
|
|
2380
|
+
if (!await import_fs_extra11.default.pathExists(sourceSkillDir)) {
|
|
2381
|
+
console.warn(`警告: 技能 ${skillName} 在资产仓库中不存在,已跳过`);
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
const targetSkillDir = import_path10.default.join(githubSkillsDir, skillName);
|
|
2385
|
+
await import_fs_extra11.default.copy(sourceSkillDir, targetSkillDir);
|
|
2386
|
+
await collectHashes(targetSkillDir, import_path10.default.join(".github/skills", skillName), hashes);
|
|
2387
|
+
}
|
|
2388
|
+
for (const agentName of l0.agents) {
|
|
2389
|
+
const sourceAgentFile = import_path10.default.join(sourceDir, "agents", `${agentName}.agent.md`);
|
|
2390
|
+
if (!await import_fs_extra11.default.pathExists(sourceAgentFile)) {
|
|
2391
|
+
console.warn(`警告: 代理 ${agentName} 在资产仓库中不存在,已跳过`);
|
|
2392
|
+
continue;
|
|
2393
|
+
}
|
|
2394
|
+
const targetAgentFile = import_path10.default.join(githubAgentsDir, `${agentName}.agent.md`);
|
|
2395
|
+
await import_fs_extra11.default.copyFile(sourceAgentFile, targetAgentFile);
|
|
2396
|
+
const relativePath = `.github/agents/${agentName}.agent.md`;
|
|
2397
|
+
hashes[relativePath] = await computeFileHash(targetAgentFile);
|
|
2398
|
+
}
|
|
2399
|
+
return hashes;
|
|
2400
|
+
}
|
|
2401
|
+
async function collectHashes(dir, dirRelativePath, hashes) {
|
|
2402
|
+
const entries = await import_fs_extra11.default.readdir(dir, { withFileTypes: true });
|
|
2403
|
+
for (const entry of entries) {
|
|
2404
|
+
const fullPath = import_path10.default.join(dir, entry.name);
|
|
2405
|
+
const relativePath = import_path10.default.join(dirRelativePath, entry.name).split(import_path10.default.sep).join("/");
|
|
2406
|
+
if (entry.isDirectory()) {
|
|
2407
|
+
await collectHashes(fullPath, relativePath, hashes);
|
|
2408
|
+
} else if (entry.isFile()) {
|
|
2409
|
+
hashes[relativePath] = await computeFileHash(fullPath);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
async function writeAiManifest(projectPath, manifest) {
|
|
2414
|
+
const manifestPath = import_path10.default.join(projectPath, AI_MANIFEST_RELATIVE_PATH);
|
|
2415
|
+
await import_fs_extra11.default.ensureDir(import_path10.default.dirname(manifestPath));
|
|
2416
|
+
await import_fs_extra11.default.writeJSON(manifestPath, manifest, { spaces: 4 });
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// src/pure/agents-md.ts
|
|
2420
|
+
function replaceAgentsTemplateTodos(content, projectInfo) {
|
|
2421
|
+
let result = content;
|
|
2422
|
+
const description = projectInfo.projectDescription || projectInfo.description || "";
|
|
2423
|
+
if (description) {
|
|
2424
|
+
result = result.replace("<!-- TODO: 一句话描述项目目的 -->", description);
|
|
2425
|
+
}
|
|
2426
|
+
const testFramework = projectInfo.isInitTest;
|
|
2427
|
+
if (testFramework === "vitest" || testFramework === "jest") {
|
|
2428
|
+
result = result.replace("<!-- TODO: 指定最低覆盖率阈值,如 80% -->", "80%");
|
|
2429
|
+
}
|
|
2430
|
+
return result;
|
|
2431
|
+
}
|
|
2432
|
+
function buildAgentsMdL1Section(projectInfo, l0Skills = [], projectSummary) {
|
|
2433
|
+
const {
|
|
2434
|
+
language,
|
|
2435
|
+
runtime,
|
|
2436
|
+
vueVersion
|
|
2437
|
+
} = projectInfo.templateMeta || {};
|
|
2438
|
+
const packageManager = projectInfo.packageManager || "npm";
|
|
2439
|
+
const devCommand = projectInfo.devCommand;
|
|
2440
|
+
const testCommand = projectInfo.testCommand;
|
|
2441
|
+
const buildCommand = projectInfo.buildCommand;
|
|
2442
|
+
const lintCommand = projectInfo.lintCommand;
|
|
2443
|
+
const startCommand = projectInfo.startCommand;
|
|
2444
|
+
const commitCommand = projectInfo.commitCommand;
|
|
2445
|
+
const techStackLines = [
|
|
2446
|
+
`- 主要语言: ${language || "typescript"}`,
|
|
2447
|
+
`- 运行时: ${runtime || "nodejs"}`
|
|
2448
|
+
];
|
|
2449
|
+
if (vueVersion === 2) {
|
|
2450
|
+
techStackLines.push("- 框架: Vue 2");
|
|
2451
|
+
} else if (vueVersion === 3) {
|
|
2452
|
+
techStackLines.push("- 框架: Vue 3");
|
|
2453
|
+
}
|
|
2454
|
+
techStackLines.push(`- 包管理器: ${packageManager}`);
|
|
2455
|
+
const commandLines = [
|
|
2456
|
+
`- 安装依赖: \`${packageManager} install\``
|
|
2457
|
+
];
|
|
2458
|
+
if (devCommand) {
|
|
2459
|
+
commandLines.push(`- 启动开发环境: \`${devCommand}\``);
|
|
2460
|
+
}
|
|
2461
|
+
if (testCommand) {
|
|
2462
|
+
commandLines.push(`- 运行测试: \`${testCommand}\``);
|
|
2463
|
+
}
|
|
2464
|
+
if (buildCommand) {
|
|
2465
|
+
commandLines.push(`- 构建项目: \`${buildCommand}\``);
|
|
2466
|
+
}
|
|
2467
|
+
if (lintCommand) {
|
|
2468
|
+
commandLines.push(`- 代码检查: \`${lintCommand}\``);
|
|
2469
|
+
}
|
|
2470
|
+
if (startCommand) {
|
|
2471
|
+
commandLines.push(`- 启动生产或本地预览: \`${startCommand}\``);
|
|
2472
|
+
}
|
|
2473
|
+
if (commitCommand) {
|
|
2474
|
+
commandLines.push(`- 生成提交: \`${commitCommand}\``);
|
|
2475
|
+
}
|
|
2476
|
+
const skillIndexLines = l0Skills.length > 0 ? l0Skills.map((skill) => `- ${skill}`) : ["- 未植入技能(L0 精选清单为空)"];
|
|
2477
|
+
const summary = projectSummary?.summary || projectInfo.projectDescription || projectInfo.description || "";
|
|
2478
|
+
const featureLines = projectSummary?.features?.length ? projectSummary.features.map((feature) => `- ${feature}`) : [];
|
|
2479
|
+
const overviewLines = summary ? [summary, ...featureLines.length > 0 ? ["", "主要特性:", ...featureLines] : []] : ["(项目概述待补充)"];
|
|
2480
|
+
return [
|
|
2481
|
+
"---",
|
|
2482
|
+
"",
|
|
2483
|
+
"## 项目信息(由 cmyr-template-cli 生成)",
|
|
2484
|
+
"",
|
|
2485
|
+
"> 本节内容由脚手架基于 ProjectInfo 参数注入,如需调整请直接修改。",
|
|
2486
|
+
"",
|
|
2487
|
+
"### 项目概述",
|
|
2488
|
+
...overviewLines,
|
|
2489
|
+
"",
|
|
2490
|
+
"### 技术栈",
|
|
2491
|
+
...techStackLines,
|
|
2492
|
+
"",
|
|
2493
|
+
"### 常用命令",
|
|
2494
|
+
...commandLines,
|
|
2495
|
+
"",
|
|
2496
|
+
"### AI 技能索引",
|
|
2497
|
+
"",
|
|
2498
|
+
"> 技能与代理已快照植入 `.github/skills/`、`.github/agents/`,并被链接到各 AI 工具目录(.claude/.opencode/.agents 等)。",
|
|
2499
|
+
...skillIndexLines,
|
|
2500
|
+
"",
|
|
2501
|
+
"### 植入来源",
|
|
2502
|
+
"",
|
|
2503
|
+
"> 快照来源与校验记录见 `.ai/manifest.json`;更新命令:`ct ai-update`。",
|
|
2504
|
+
""
|
|
2505
|
+
].join("\n");
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
// src/core/ai.ts
|
|
2509
|
+
var AI_DIR_LINK_MAPPINGS = [
|
|
2510
|
+
{ linkRelPath: ".claude/skills", targetRelPath: ".github/skills" },
|
|
2511
|
+
{ linkRelPath: ".claude/agents", targetRelPath: ".github/agents" },
|
|
2512
|
+
{ linkRelPath: ".opencode/skills", targetRelPath: ".github/skills" },
|
|
2513
|
+
{ linkRelPath: ".opencode/agents", targetRelPath: ".github/agents" },
|
|
2514
|
+
{ linkRelPath: ".agents/skills", targetRelPath: ".github/skills" },
|
|
2515
|
+
{ linkRelPath: ".agents/agents", targetRelPath: ".github/agents" }
|
|
2516
|
+
];
|
|
2517
|
+
var AI_FILE_LINK_MAPPINGS = [
|
|
2518
|
+
{ linkRelPath: "CLAUDE.md", targetRelPath: "AGENTS.md" }
|
|
2519
|
+
];
|
|
2520
|
+
async function initAIScaffolding(projectPath, projectInfo, config) {
|
|
2521
|
+
const loading = (0, import_ora10.default)("正在初始化 AI 开发配置……").start();
|
|
2165
2522
|
try {
|
|
2166
2523
|
const aiTools = projectInfo.aiTools ?? ["claude", "copilot"];
|
|
2167
2524
|
if (aiTools.includes("claude")) {
|
|
2168
|
-
await
|
|
2525
|
+
await initAgentsMdScaffolding(projectPath, projectInfo, config);
|
|
2169
2526
|
await initClaudeDirectory(projectPath);
|
|
2170
2527
|
}
|
|
2171
2528
|
if (aiTools.includes("copilot")) {
|
|
@@ -2184,59 +2541,118 @@ async function initAIScaffolding(projectPath, projectInfo) {
|
|
|
2184
2541
|
console.error(error);
|
|
2185
2542
|
}
|
|
2186
2543
|
}
|
|
2187
|
-
async function
|
|
2188
|
-
|
|
2544
|
+
async function initAgentsMdScaffolding(projectPath, projectInfo, config) {
|
|
2545
|
+
let prepared = null;
|
|
2189
2546
|
try {
|
|
2190
|
-
|
|
2191
|
-
|
|
2547
|
+
prepared = await prepareAgentsSource(getAiSourceConfig(config));
|
|
2548
|
+
const l0 = await readL0Selection(prepared.sourceDir);
|
|
2549
|
+
await initAgentsMd(projectPath, projectInfo, prepared.sourceDir, l0);
|
|
2550
|
+
const hashes = await initSkillsSnapshot(projectPath, prepared.sourceDir, l0);
|
|
2551
|
+
const links = await initAgentLinkDirs(projectPath, projectInfo);
|
|
2552
|
+
await writeAiManifest(projectPath, {
|
|
2553
|
+
version: 1,
|
|
2554
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2555
|
+
source: prepared.source,
|
|
2556
|
+
l0Selection: l0,
|
|
2557
|
+
links,
|
|
2558
|
+
hashes
|
|
2559
|
+
});
|
|
2560
|
+
} finally {
|
|
2561
|
+
if (prepared) {
|
|
2562
|
+
await prepared.cleanup();
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
async function initAgentsMd(projectPath, projectInfo, sourceDir, l0) {
|
|
2567
|
+
const loading = (0, import_ora10.default)("正在生成 AGENTS.md……").start();
|
|
2568
|
+
try {
|
|
2569
|
+
const outputPath = import_path11.default.join(projectPath, "AGENTS.md");
|
|
2570
|
+
if (await import_fs_extra12.default.pathExists(outputPath)) {
|
|
2192
2571
|
loading.stopAndPersist({
|
|
2193
2572
|
text: "AGENTS.md 已存在,跳过生成",
|
|
2194
2573
|
symbol: "⊙"
|
|
2195
2574
|
});
|
|
2196
2575
|
return;
|
|
2197
2576
|
}
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
devCommand: projectInfo.devCommand,
|
|
2211
|
-
testCommand: projectInfo.testCommand,
|
|
2212
|
-
buildCommand: projectInfo.buildCommand,
|
|
2213
|
-
lintCommand: projectInfo.lintCommand,
|
|
2214
|
-
startCommand: projectInfo.startCommand,
|
|
2215
|
-
commitCommand: projectInfo.commitCommand
|
|
2216
|
-
};
|
|
2217
|
-
await ejsRender(templatePath, templateData, outputPath);
|
|
2577
|
+
if (!sourceDir) {
|
|
2578
|
+
throw new Error("未提供 AI 技能资产源,无法生成 AGENTS.md");
|
|
2579
|
+
}
|
|
2580
|
+
const templateContent = await readAgentsTemplate(sourceDir);
|
|
2581
|
+
if (!templateContent) {
|
|
2582
|
+
throw new Error("资产仓库缺少 global/AGENTS.template.md,无法生成 AGENTS.md");
|
|
2583
|
+
}
|
|
2584
|
+
const l0Content = replaceAgentsTemplateTodos(templateContent, projectInfo);
|
|
2585
|
+
const l1Section = buildAgentsMdL1Section(projectInfo, l0?.skills || [], projectInfo.aiGeneratedSummary);
|
|
2586
|
+
await import_fs_extra12.default.writeFile(outputPath, `${l0Content.trimEnd()}
|
|
2587
|
+
|
|
2588
|
+
${l1Section}`);
|
|
2218
2589
|
loading.succeed("AGENTS.md 生成成功!");
|
|
2219
2590
|
} catch (error) {
|
|
2220
2591
|
loading.fail("AGENTS.md 生成失败!");
|
|
2221
2592
|
throw error;
|
|
2222
2593
|
}
|
|
2223
2594
|
}
|
|
2595
|
+
async function initSkillsSnapshot(projectPath, sourceDir, l0) {
|
|
2596
|
+
const loading = (0, import_ora10.default)("正在快照植入 AI 技能与代理……").start();
|
|
2597
|
+
try {
|
|
2598
|
+
const hashes = await copyL0Selection(sourceDir, projectPath, l0);
|
|
2599
|
+
loading.succeed("AI 技能与代理快照植入成功!");
|
|
2600
|
+
return hashes;
|
|
2601
|
+
} catch (error) {
|
|
2602
|
+
loading.fail("AI 技能与代理快照植入失败!");
|
|
2603
|
+
throw error;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
async function initAgentLinkDirs(projectPath, projectInfo) {
|
|
2607
|
+
const loading = (0, import_ora10.default)("正在创建跨 agent 链接……").start();
|
|
2608
|
+
try {
|
|
2609
|
+
const links = [];
|
|
2610
|
+
for (const mapping of AI_DIR_LINK_MAPPINGS) {
|
|
2611
|
+
const linkPath = import_path11.default.join(projectPath, mapping.linkRelPath);
|
|
2612
|
+
const targetPath = import_path11.default.join(projectPath, mapping.targetRelPath);
|
|
2613
|
+
const result = await createDirSymlink(linkPath, targetPath);
|
|
2614
|
+
links.push({
|
|
2615
|
+
linkRelPath: mapping.linkRelPath,
|
|
2616
|
+
targetRelPath: mapping.targetRelPath,
|
|
2617
|
+
method: result.method
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
const isClaude = projectInfo.aiTools?.includes("claude");
|
|
2621
|
+
if (isClaude) {
|
|
2622
|
+
for (const mapping of AI_FILE_LINK_MAPPINGS) {
|
|
2623
|
+
const linkPath = import_path11.default.join(projectPath, mapping.linkRelPath);
|
|
2624
|
+
const targetPath = import_path11.default.join(projectPath, mapping.targetRelPath);
|
|
2625
|
+
const result = await createFileSymlink(linkPath, targetPath);
|
|
2626
|
+
links.push({
|
|
2627
|
+
linkRelPath: mapping.linkRelPath,
|
|
2628
|
+
targetRelPath: mapping.targetRelPath,
|
|
2629
|
+
method: result.method
|
|
2630
|
+
});
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
loading.succeed("跨 agent 链接创建成功!");
|
|
2634
|
+
return links;
|
|
2635
|
+
} catch (error) {
|
|
2636
|
+
loading.fail("跨 agent 链接创建失败!");
|
|
2637
|
+
throw error;
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2224
2640
|
async function initCopilotInstructions(projectPath) {
|
|
2225
|
-
const loading = (0,
|
|
2641
|
+
const loading = (0, import_ora10.default)("正在生成 .github/copilot-instructions.md……").start();
|
|
2226
2642
|
try {
|
|
2227
|
-
const outputPath =
|
|
2228
|
-
if (await
|
|
2643
|
+
const outputPath = import_path11.default.join(projectPath, ".github/copilot-instructions.md");
|
|
2644
|
+
if (await import_fs_extra12.default.pathExists(outputPath)) {
|
|
2229
2645
|
loading.stopAndPersist({
|
|
2230
2646
|
text: ".github/copilot-instructions.md 已存在,跳过生成",
|
|
2231
2647
|
symbol: "⊙"
|
|
2232
2648
|
});
|
|
2233
2649
|
return;
|
|
2234
2650
|
}
|
|
2235
|
-
const githubDir =
|
|
2236
|
-
if (!await
|
|
2237
|
-
await
|
|
2651
|
+
const githubDir = import_path11.default.join(projectPath, ".github");
|
|
2652
|
+
if (!await import_fs_extra12.default.pathExists(githubDir)) {
|
|
2653
|
+
await import_fs_extra12.default.mkdirp(githubDir);
|
|
2238
2654
|
}
|
|
2239
|
-
const templatePath =
|
|
2655
|
+
const templatePath = import_path11.default.join(__dirname, "../templates/.github/copilot-instructions.md.ejs");
|
|
2240
2656
|
await ejsRender(templatePath, {}, outputPath);
|
|
2241
2657
|
loading.succeed(".github/copilot-instructions.md 生成成功!");
|
|
2242
2658
|
} catch (error) {
|
|
@@ -2245,17 +2661,17 @@ async function initCopilotInstructions(projectPath) {
|
|
|
2245
2661
|
}
|
|
2246
2662
|
}
|
|
2247
2663
|
async function initCursorRules(projectPath) {
|
|
2248
|
-
const loading = (0,
|
|
2664
|
+
const loading = (0, import_ora10.default)("正在生成 .cursorrules……").start();
|
|
2249
2665
|
try {
|
|
2250
|
-
const outputPath =
|
|
2251
|
-
if (await
|
|
2666
|
+
const outputPath = import_path11.default.join(projectPath, ".cursorrules");
|
|
2667
|
+
if (await import_fs_extra12.default.pathExists(outputPath)) {
|
|
2252
2668
|
loading.stopAndPersist({
|
|
2253
2669
|
text: ".cursorrules 已存在,跳过生成",
|
|
2254
2670
|
symbol: "⊙"
|
|
2255
2671
|
});
|
|
2256
2672
|
return;
|
|
2257
2673
|
}
|
|
2258
|
-
const templatePath =
|
|
2674
|
+
const templatePath = import_path11.default.join(__dirname, "../templates/.cursorrules.ejs");
|
|
2259
2675
|
await ejsRender(templatePath, {}, outputPath);
|
|
2260
2676
|
loading.succeed(".cursorrules 生成成功!");
|
|
2261
2677
|
} catch (error) {
|
|
@@ -2264,17 +2680,17 @@ async function initCursorRules(projectPath) {
|
|
|
2264
2680
|
}
|
|
2265
2681
|
}
|
|
2266
2682
|
async function initWindsurfRules(projectPath) {
|
|
2267
|
-
const loading = (0,
|
|
2683
|
+
const loading = (0, import_ora10.default)("正在生成 .windsurfrules……").start();
|
|
2268
2684
|
try {
|
|
2269
|
-
const outputPath =
|
|
2270
|
-
if (await
|
|
2685
|
+
const outputPath = import_path11.default.join(projectPath, ".windsurfrules");
|
|
2686
|
+
if (await import_fs_extra12.default.pathExists(outputPath)) {
|
|
2271
2687
|
loading.stopAndPersist({
|
|
2272
2688
|
text: ".windsurfrules 已存在,跳过生成",
|
|
2273
2689
|
symbol: "⊙"
|
|
2274
2690
|
});
|
|
2275
2691
|
return;
|
|
2276
2692
|
}
|
|
2277
|
-
const templatePath =
|
|
2693
|
+
const templatePath = import_path11.default.join(__dirname, "../templates/.windsurfrules.ejs");
|
|
2278
2694
|
await ejsRender(templatePath, {}, outputPath);
|
|
2279
2695
|
loading.succeed(".windsurfrules 生成成功!");
|
|
2280
2696
|
} catch (error) {
|
|
@@ -2283,18 +2699,17 @@ async function initWindsurfRules(projectPath) {
|
|
|
2283
2699
|
}
|
|
2284
2700
|
}
|
|
2285
2701
|
async function initClaudeDirectory(projectPath) {
|
|
2286
|
-
const loading = (0,
|
|
2702
|
+
const loading = (0, import_ora10.default)("正在初始化 .claude/ 目录……").start();
|
|
2287
2703
|
try {
|
|
2288
|
-
const
|
|
2289
|
-
if (await
|
|
2704
|
+
const settingsPath = import_path11.default.join(projectPath, ".claude/settings.json");
|
|
2705
|
+
if (await import_fs_extra12.default.pathExists(settingsPath)) {
|
|
2290
2706
|
loading.stopAndPersist({
|
|
2291
|
-
text: ".claude/
|
|
2707
|
+
text: ".claude/settings.json 已存在,跳过初始化",
|
|
2292
2708
|
symbol: "⊙"
|
|
2293
2709
|
});
|
|
2294
2710
|
return;
|
|
2295
2711
|
}
|
|
2296
|
-
await
|
|
2297
|
-
await import_fs_extra10.default.mkdirp(import_path9.default.join(claudeDir, "agents"));
|
|
2712
|
+
await import_fs_extra12.default.mkdirp(import_path11.default.join(projectPath, ".claude"));
|
|
2298
2713
|
const files = [".claude/settings.json"];
|
|
2299
2714
|
await copyFilesFromTemplates(projectPath, files, true);
|
|
2300
2715
|
loading.succeed(".claude/ 目录初始化成功!");
|
|
@@ -2304,17 +2719,17 @@ async function initClaudeDirectory(projectPath) {
|
|
|
2304
2719
|
}
|
|
2305
2720
|
}
|
|
2306
2721
|
async function initCursorDirectory(projectPath) {
|
|
2307
|
-
const loading = (0,
|
|
2722
|
+
const loading = (0, import_ora10.default)("正在初始化 .cursor/ 目录……").start();
|
|
2308
2723
|
try {
|
|
2309
|
-
const cursorDir =
|
|
2310
|
-
if (await
|
|
2724
|
+
const cursorDir = import_path11.default.join(projectPath, ".cursor", "rules");
|
|
2725
|
+
if (await import_fs_extra12.default.pathExists(cursorDir)) {
|
|
2311
2726
|
loading.stopAndPersist({
|
|
2312
2727
|
text: ".cursor/ 目录已存在,跳过初始化",
|
|
2313
2728
|
symbol: "⊙"
|
|
2314
2729
|
});
|
|
2315
2730
|
return;
|
|
2316
2731
|
}
|
|
2317
|
-
await
|
|
2732
|
+
await import_fs_extra12.default.mkdirp(cursorDir);
|
|
2318
2733
|
loading.succeed(".cursor/ 目录初始化成功!");
|
|
2319
2734
|
} catch (error) {
|
|
2320
2735
|
loading.fail(".cursor/ 目录初始化失败!");
|
|
@@ -2325,7 +2740,7 @@ async function initCursorDirectory(projectPath) {
|
|
|
2325
2740
|
// src/utils/utils.ts
|
|
2326
2741
|
async function downloadGitRepo(repository, destination) {
|
|
2327
2742
|
const fastRepo = await getFastGitRepo(repository);
|
|
2328
|
-
const loading = (0,
|
|
2743
|
+
const loading = (0, import_ora11.default)(`正在下载模板 - ${repository}`);
|
|
2329
2744
|
loading.start();
|
|
2330
2745
|
return Promise.any([
|
|
2331
2746
|
downloadAndExtractZip(fastRepo, destination, loading, repository),
|
|
@@ -2334,31 +2749,31 @@ async function downloadGitRepo(repository, destination) {
|
|
|
2334
2749
|
}
|
|
2335
2750
|
async function downloadAndExtractZip(url, destination, loading, repository) {
|
|
2336
2751
|
try {
|
|
2337
|
-
const response = await
|
|
2752
|
+
const response = await import_axios3.default.get(url, { responseType: "arraybuffer" });
|
|
2338
2753
|
const buffer = Buffer.from(response.data);
|
|
2339
|
-
const zip = new
|
|
2754
|
+
const zip = new import_adm_zip2.default(buffer);
|
|
2340
2755
|
const entries = zip.getEntries();
|
|
2341
2756
|
const topDir = entries[0]?.entryName.split("/")[0];
|
|
2342
2757
|
if (!topDir) {
|
|
2343
2758
|
throw new Error("无效的 zip 文件");
|
|
2344
2759
|
}
|
|
2345
|
-
await
|
|
2760
|
+
await import_fs_extra13.default.ensureDir(destination);
|
|
2346
2761
|
for (const entry of entries) {
|
|
2347
2762
|
if (entry.entryName.startsWith(`${topDir}/`)) {
|
|
2348
2763
|
const relativePath = entry.entryName.slice(topDir.length + 1);
|
|
2349
2764
|
if (!relativePath) {
|
|
2350
2765
|
continue;
|
|
2351
2766
|
}
|
|
2352
|
-
const targetPath =
|
|
2353
|
-
const resolvedPath =
|
|
2354
|
-
if (!resolvedPath.startsWith(
|
|
2767
|
+
const targetPath = import_path12.default.join(destination, relativePath);
|
|
2768
|
+
const resolvedPath = import_path12.default.resolve(targetPath);
|
|
2769
|
+
if (!resolvedPath.startsWith(import_path12.default.resolve(destination))) {
|
|
2355
2770
|
throw new Error(`路径遍历攻击检测: ${relativePath}`);
|
|
2356
2771
|
}
|
|
2357
2772
|
if (entry.isDirectory) {
|
|
2358
|
-
await
|
|
2773
|
+
await import_fs_extra13.default.ensureDir(targetPath);
|
|
2359
2774
|
} else {
|
|
2360
|
-
await
|
|
2361
|
-
await
|
|
2775
|
+
await import_fs_extra13.default.ensureDir(import_path12.default.dirname(targetPath));
|
|
2776
|
+
await import_fs_extra13.default.writeFile(targetPath, entry.getData());
|
|
2362
2777
|
}
|
|
2363
2778
|
}
|
|
2364
2779
|
}
|
|
@@ -2370,7 +2785,7 @@ async function downloadAndExtractZip(url, destination, loading, repository) {
|
|
|
2370
2785
|
}
|
|
2371
2786
|
}
|
|
2372
2787
|
async function getFastGitRepo(repository) {
|
|
2373
|
-
const loading = (0,
|
|
2788
|
+
const loading = (0, import_ora11.default)(`正在选择镜像源 - ${repository}`);
|
|
2374
2789
|
loading.start();
|
|
2375
2790
|
try {
|
|
2376
2791
|
const fastUrl = await getFastUrl(REMOTES.map((remote) => `${remote}/${repository}/archive/refs/heads/master.zip`));
|
|
@@ -2385,7 +2800,7 @@ async function getFastGitRepo(repository) {
|
|
|
2385
2800
|
async function initProject(answers) {
|
|
2386
2801
|
const typedAnswers = answers;
|
|
2387
2802
|
const { name, template } = typedAnswers;
|
|
2388
|
-
const projectPath =
|
|
2803
|
+
const projectPath = import_path12.default.join(process.cwd(), name);
|
|
2389
2804
|
await downloadGitRepo(`CaoMeiYouRen/${template}`, projectPath);
|
|
2390
2805
|
await init(projectPath, typedAnswers);
|
|
2391
2806
|
return "- 下载项目模板成功!";
|
|
@@ -2537,7 +2952,7 @@ async function getGitUserName() {
|
|
|
2537
2952
|
}
|
|
2538
2953
|
|
|
2539
2954
|
// src/utils/ai-api.ts
|
|
2540
|
-
var
|
|
2955
|
+
var import_axios4 = __toESM(require("axios"));
|
|
2541
2956
|
|
|
2542
2957
|
// src/pure/ai.ts
|
|
2543
2958
|
var MAX_USER_INPUT_LENGTH = 500;
|
|
@@ -2596,6 +3011,49 @@ function parseAIResponse(response) {
|
|
|
2596
3011
|
return null;
|
|
2597
3012
|
}
|
|
2598
3013
|
}
|
|
3014
|
+
function buildProjectSummaryPrompt(projectInfo) {
|
|
3015
|
+
if (typeof projectInfo.description !== "string" || !projectInfo.description.trim()) {
|
|
3016
|
+
throw new Error("buildProjectSummaryPrompt: projectInfo.description is required");
|
|
3017
|
+
}
|
|
3018
|
+
return `你是一个项目文档撰写助手。基于以下项目信息,生成 README 项目简介:
|
|
3019
|
+
|
|
3020
|
+
项目名称: ${projectInfo.name}
|
|
3021
|
+
项目描述: ${projectInfo.description}
|
|
3022
|
+
关键词: ${projectInfo.keywords.join(", ") || "无"}
|
|
3023
|
+
|
|
3024
|
+
要求:
|
|
3025
|
+
1. **summary**:一段精炼的中文项目简介(80-150 字),突出项目价值,适合 README 引言
|
|
3026
|
+
2. **features**:3-5 个主要特性,每条不超过 15 字
|
|
3027
|
+
|
|
3028
|
+
请以 JSON 格式返回(不要包含 markdown 代码块标记):
|
|
3029
|
+
{
|
|
3030
|
+
"summary": "项目简介",
|
|
3031
|
+
"features": ["特性1", "特性2"]
|
|
3032
|
+
}`;
|
|
3033
|
+
}
|
|
3034
|
+
function parseProjectSummary(response) {
|
|
3035
|
+
if (typeof response !== "string") {
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
try {
|
|
3039
|
+
let jsonStr = response.trim();
|
|
3040
|
+
const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/;
|
|
3041
|
+
const match = jsonStr.match(codeBlockRegex);
|
|
3042
|
+
if (match && match[1]) {
|
|
3043
|
+
jsonStr = match[1].trim();
|
|
3044
|
+
}
|
|
3045
|
+
const parsed = JSON.parse(jsonStr);
|
|
3046
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.summary !== "string" || parsed.summary.trim().length === 0 || !Array.isArray(parsed.features) || parsed.features.length === 0) {
|
|
3047
|
+
return null;
|
|
3048
|
+
}
|
|
3049
|
+
return {
|
|
3050
|
+
summary: parsed.summary,
|
|
3051
|
+
features: parsed.features.filter((feature) => typeof feature === "string").slice(0, 5)
|
|
3052
|
+
};
|
|
3053
|
+
} catch {
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
2599
3057
|
|
|
2600
3058
|
// src/utils/ai-api.ts
|
|
2601
3059
|
var AI_TIMEOUT = 30 * 1e3;
|
|
@@ -2620,7 +3078,7 @@ async function chatCompletion(request) {
|
|
|
2620
3078
|
throw new Error("AI_API_BASE must use HTTPS for security (except for localhost)");
|
|
2621
3079
|
}
|
|
2622
3080
|
try {
|
|
2623
|
-
const response = await
|
|
3081
|
+
const response = await import_axios4.default.post(
|
|
2624
3082
|
endpoint,
|
|
2625
3083
|
{
|
|
2626
3084
|
model,
|
|
@@ -2646,7 +3104,7 @@ async function chatCompletion(request) {
|
|
|
2646
3104
|
}
|
|
2647
3105
|
return content;
|
|
2648
3106
|
} catch (error) {
|
|
2649
|
-
if (
|
|
3107
|
+
if (import_axios4.default.isAxiosError(error)) {
|
|
2650
3108
|
const axiosError = error;
|
|
2651
3109
|
if (axiosError.code === "ECONNABORTED" || axiosError.message.includes("timeout")) {
|
|
2652
3110
|
throw new Error(`AI API request timed out after ${AI_TIMEOUT / 1e3} seconds. Please try again.`);
|
|
@@ -2691,6 +3149,29 @@ async function getAIProjectSuggestion(userInput, config) {
|
|
|
2691
3149
|
}
|
|
2692
3150
|
return suggestion;
|
|
2693
3151
|
}
|
|
3152
|
+
async function getAIProjectSummary(projectInfo, config) {
|
|
3153
|
+
const { AI_API_BASE, AI_API_KEY, AI_MODEL } = config;
|
|
3154
|
+
if (!AI_API_KEY) {
|
|
3155
|
+
throw new Error(
|
|
3156
|
+
'AI_API_KEY is not configured. Please add it to your .ctrc file:\n "AI_API_KEY": "your-api-key-here"'
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3159
|
+
const prompt = buildProjectSummaryPrompt(projectInfo);
|
|
3160
|
+
const response = await chatCompletion({
|
|
3161
|
+
prompt,
|
|
3162
|
+
apiKey: AI_API_KEY,
|
|
3163
|
+
apiBase: AI_API_BASE,
|
|
3164
|
+
model: AI_MODEL,
|
|
3165
|
+
temperature: 0.7
|
|
3166
|
+
});
|
|
3167
|
+
const summary = parseProjectSummary(response);
|
|
3168
|
+
if (!summary) {
|
|
3169
|
+
throw new Error(
|
|
3170
|
+
"Failed to parse AI project summary. The AI may have returned an invalid format. Please try again or contact support if the issue persists."
|
|
3171
|
+
);
|
|
3172
|
+
}
|
|
3173
|
+
return summary;
|
|
3174
|
+
}
|
|
2694
3175
|
|
|
2695
3176
|
// src/plopfile.ts
|
|
2696
3177
|
module.exports = function(plop) {
|
|
@@ -2700,6 +3181,7 @@ module.exports = function(plop) {
|
|
|
2700
3181
|
async prompts(inquirer) {
|
|
2701
3182
|
const config = await loadTemplateCliConfig();
|
|
2702
3183
|
let aiSuggestion = null;
|
|
3184
|
+
let aiSummary = null;
|
|
2703
3185
|
const questions = [
|
|
2704
3186
|
// ===== AI 引导模式(必须最先询问) =====
|
|
2705
3187
|
{
|
|
@@ -2724,7 +3206,7 @@ module.exports = function(plop) {
|
|
|
2724
3206
|
message: "",
|
|
2725
3207
|
async when(answers2) {
|
|
2726
3208
|
if (answers2.isAIAssisted && answers2.aiUserInput) {
|
|
2727
|
-
const spinner = (0,
|
|
3209
|
+
const spinner = (0, import_ora12.default)("AI 正在生成项目建议...").start();
|
|
2728
3210
|
try {
|
|
2729
3211
|
aiSuggestion = await getAIProjectSuggestion(answers2.aiUserInput, config);
|
|
2730
3212
|
spinner.succeed("AI 已生成项目建议");
|
|
@@ -2735,6 +3217,16 @@ module.exports = function(plop) {
|
|
|
2735
3217
|
console.log(` 关键词: ${aiSuggestion.keywords.join(", ")}`);
|
|
2736
3218
|
console.log(` 推荐模板: ${aiSuggestion.template}`);
|
|
2737
3219
|
console.log("");
|
|
3220
|
+
try {
|
|
3221
|
+
const summary = await getAIProjectSummary({
|
|
3222
|
+
name: aiSuggestion.names[0],
|
|
3223
|
+
description: aiSuggestion.description,
|
|
3224
|
+
keywords: aiSuggestion.keywords
|
|
3225
|
+
}, config);
|
|
3226
|
+
aiSummary = summary;
|
|
3227
|
+
spinner.succeed("AI 已生成项目简介");
|
|
3228
|
+
} catch {
|
|
3229
|
+
}
|
|
2738
3230
|
} catch (error) {
|
|
2739
3231
|
spinner.fail(`AI 引导失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
2740
3232
|
console.log("回退到标准问答流程\n");
|
|
@@ -2899,7 +3391,7 @@ module.exports = function(plop) {
|
|
|
2899
3391
|
name: "license",
|
|
2900
3392
|
message: "请选择开源协议",
|
|
2901
3393
|
async choices() {
|
|
2902
|
-
return
|
|
3394
|
+
return import_fs_extra14.default.readdir(import_path13.default.join(__dirname, "../templates/licenses/"));
|
|
2903
3395
|
},
|
|
2904
3396
|
default: "MIT",
|
|
2905
3397
|
when(answers2) {
|
|
@@ -3076,6 +3568,9 @@ module.exports = function(plop) {
|
|
|
3076
3568
|
answers.aiGeneratedKeywords = suggestion.keywords;
|
|
3077
3569
|
answers.aiRecommendedTemplate = suggestion.template;
|
|
3078
3570
|
}
|
|
3571
|
+
if (aiSummary) {
|
|
3572
|
+
answers.aiGeneratedSummary = aiSummary;
|
|
3573
|
+
}
|
|
3079
3574
|
delete answers._aiTrigger;
|
|
3080
3575
|
return answers;
|
|
3081
3576
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cmyr-template-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.0",
|
|
4
4
|
"description": "草梅友仁自制的项目模板创建器",
|
|
5
5
|
"author": "CaoMeiYouRen",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@types/node": "^26.0.1",
|
|
50
50
|
"@vitest/coverage-v8": "^4.1.6",
|
|
51
51
|
"commitizen": "^4.3.1",
|
|
52
|
-
"commitlint-config-cmyr": "1.0.
|
|
52
|
+
"commitlint-config-cmyr": "1.0.1",
|
|
53
53
|
"conventional-changelog-cmyr-config": "^3.0.0",
|
|
54
54
|
"conventional-changelog-writer": "^9.1.0",
|
|
55
55
|
"cross-env": "^10.1.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"husky": "^9.0.5",
|
|
61
61
|
"lint-staged": "^17.0.4",
|
|
62
62
|
"rimraf": "^6.1.3",
|
|
63
|
-
"semantic-release": "25.0.
|
|
63
|
+
"semantic-release": "25.0.8",
|
|
64
64
|
"semantic-release-cmyr-config": "1.0.2",
|
|
65
65
|
"tsup": "^8.5.1",
|
|
66
66
|
"tsx": "^4.22.0",
|
package/templates/AGENTS.md.ejs
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
## 项目概述
|
|
4
|
-
<%= projectDescription %>
|
|
5
|
-
|
|
6
|
-
## 角色与目标
|
|
7
|
-
- 本文件用于约束 AI 代理与协作者在当前项目中的默认行为。
|
|
8
|
-
- 任何更具体的项目规范、目录说明、设计决策或工作流要求,应以项目内的 README、开发规范、设计文档和配置文件为准。
|
|
9
|
-
- 当下文内容与项目实际实现冲突时,以代码、配置和项目规范文档中的事实为准。
|
|
10
|
-
|
|
11
|
-
## 技术栈
|
|
12
|
-
- 主要语言: <%= language %>
|
|
13
|
-
- 运行时: <%= runtime %>
|
|
14
|
-
<% if (vueVersion === 3) { %>- 框架: Vue 3<% } %>
|
|
15
|
-
<% if (vueVersion === 2) { %>- 框架: Vue 2<% } %>
|
|
16
|
-
- 包管理器: <%= packageManager %>
|
|
17
|
-
|
|
18
|
-
<% if (repositoryUrl || documentationUrl || issuesUrl || contributingUrl) { %>## 仓库信息
|
|
19
|
-
<% if (repositoryUrl) { %>- 仓库地址: <%= repositoryUrl %><% } %>
|
|
20
|
-
<% if (documentationUrl) { %>- 项目文档: <%= documentationUrl %><% } %>
|
|
21
|
-
<% if (issuesUrl) { %>- Issue 地址: <%= issuesUrl %><% } %>
|
|
22
|
-
<% if (contributingUrl) { %>- 贡献指南: <%= contributingUrl %><% } %>
|
|
23
|
-
<% } %>
|
|
24
|
-
|
|
25
|
-
## 项目结构
|
|
26
|
-
> 以下目录为模板默认结构;具体项目可在生成后按实际情况增删。
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
src/ # 源代码
|
|
30
|
-
<% if (isInitTest !== 'none') { %>tests/ # 测试文件
|
|
31
|
-
<% } %><% if (devCommand) { %>playground/ # 本地调试或演示代码(如有)
|
|
32
|
-
<% } %>docs/ # 文档与规范(如有)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## 常用命令
|
|
36
|
-
- 安装依赖: `<%= packageManager %> install`
|
|
37
|
-
<% if (devCommand) { %>- 启动开发环境: `<%= devCommand %>`<% } %>
|
|
38
|
-
<% if (testCommand) { %>- 运行测试: `<%= testCommand %>`<% } %>
|
|
39
|
-
<% if (buildCommand) { %>- 构建项目: `<%= buildCommand %>`<% } %>
|
|
40
|
-
<% if (lintCommand) { %>- 代码检查: `<%= lintCommand %>`<% } %>
|
|
41
|
-
<% if (startCommand) { %>- 启动生产或本地预览: `<%= startCommand %>`<% } %>
|
|
42
|
-
<% if (commitCommand) { %>- 生成提交: `<%= commitCommand %>`<% } %>
|
|
43
|
-
|
|
44
|
-
## 编码与协作约定
|
|
45
|
-
- 优先遵循项目现有代码风格、目录约定和命名规范,不要擅自引入新的体系。
|
|
46
|
-
- 使用与项目一致的语言特性和类型策略;如果项目启用了 TypeScript,则保持类型检查可通过。
|
|
47
|
-
- 变更应尽量保持最小范围,避免顺手重构无关模块。
|
|
48
|
-
- 需要新增或调整依赖时,优先确认项目已有方案是否已覆盖。
|
|
49
|
-
- 提交信息遵循 Conventional Commits 规范。
|
|
50
|
-
|
|
51
|
-
## 质量门禁
|
|
52
|
-
<% if (isInitTest === 'vitest') { %>
|
|
53
|
-
- 测试框架: Vitest
|
|
54
|
-
- 目标覆盖率: >= 80%
|
|
55
|
-
<% } else if (isInitTest === 'jest') { %>
|
|
56
|
-
- 测试框架: Jest
|
|
57
|
-
- 目标覆盖率: >= 80%
|
|
58
|
-
<% } else { %>
|
|
59
|
-
- 当前未配置测试框架;如后续补充测试,应同步更新本文件。
|
|
60
|
-
<% } %>
|
|
61
|
-
- 代码变更后,按项目实际可用命令完成 lint、typecheck、test 等必要校验。
|
|
62
|
-
- 如果某项校验在当前项目中不存在,应在说明中明确标注,而不是假定其存在。
|
|
63
|
-
|
|
64
|
-
## 安全与避免事项
|
|
65
|
-
- 不要硬编码 API Key、Token、密码或其他敏感信息。
|
|
66
|
-
- 不要直接修改构建产物、发布产物或生成目录中的文件。
|
|
67
|
-
- 不要跳过 TypeScript 类型检查或项目规定的静态检查。
|
|
68
|
-
- 不要使用 `var` 声明变量,优先使用 `const` 和 `let`。
|
|
69
|
-
- 对环境变量、密钥文件和部署配置的修改应格外谨慎。
|
|
70
|
-
|
|
71
|
-
## 可选补充
|
|
72
|
-
- 如果项目存在更细的 AI 规则、目录约定或角色分工,可在此处继续补充,但不要与项目事实相冲突。
|
|
73
|
-
- 如果项目已经定义了更强的安全、测试或发布流程,本文件应只保留入口级约束。
|