@actiondock/builder 2.0.10 → 2.0.11
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/package.json +3 -3
- package/src/exporter.ts +347 -61
- package/src/planner.ts +284 -6
- package/src/types.ts +75 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/builder",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
4
4
|
"description": "ActionDock Build Planner, Bun standalone compiler, and Skill exporter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"test": "bun test"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@actiondock/core": "^2.0.
|
|
31
|
-
"@actiondock/sdk": "^2.0.
|
|
30
|
+
"@actiondock/core": "^2.0.11",
|
|
31
|
+
"@actiondock/sdk": "^2.0.11",
|
|
32
32
|
"yaml": "^2.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
package/src/exporter.ts
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
generateCompositeSkillMd,
|
|
5
5
|
generateSourceSkillMd,
|
|
6
6
|
generateStandaloneEntrypoint,
|
|
7
7
|
generateStandaloneSkillMd,
|
|
8
8
|
getPackageSlug,
|
|
9
|
+
loadManifest,
|
|
10
|
+
loadPlaybooks,
|
|
11
|
+
loadProjectConfig,
|
|
12
|
+
type CompositeSkillPackageInfo,
|
|
9
13
|
type ProjectConfig,
|
|
10
14
|
} from "@actiondock/core";
|
|
11
15
|
import { createTarGzArchive, createZipArchive } from "./archive";
|
|
12
16
|
import { BunCompiler } from "./compiler";
|
|
13
17
|
import { BuilderError } from "./errors";
|
|
14
18
|
import { BuildPlanner } from "./planner";
|
|
15
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
ArchiveFormat,
|
|
21
|
+
BatchSkillExportOptions,
|
|
22
|
+
BatchSkillExportResult,
|
|
23
|
+
CompositeSkillExportOptions,
|
|
24
|
+
CompositeSkillExportResult,
|
|
25
|
+
SkillExporterOptions,
|
|
26
|
+
SkillExportResult,
|
|
27
|
+
} from "./types";
|
|
16
28
|
|
|
17
29
|
/**
|
|
18
30
|
* 递归扫描生成目录中的所有相对文件路径。
|
|
@@ -65,6 +77,105 @@ function createArchive(
|
|
|
65
77
|
return archivePath;
|
|
66
78
|
}
|
|
67
79
|
|
|
80
|
+
/**
|
|
81
|
+
* 检索单个 Action 项目目录中已存在的 SKILL.md 文件。
|
|
82
|
+
*/
|
|
83
|
+
export function findExistingSingleSkillMd(
|
|
84
|
+
projectRoot: string,
|
|
85
|
+
explicitPath?: string,
|
|
86
|
+
pkgSlug?: string
|
|
87
|
+
): string | undefined {
|
|
88
|
+
if (explicitPath && existsSync(explicitPath)) {
|
|
89
|
+
try {
|
|
90
|
+
if (statSync(explicitPath).isFile()) return resolve(explicitPath);
|
|
91
|
+
} catch {
|
|
92
|
+
// 忽略文件属性读取异常
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const candidates = [
|
|
97
|
+
join(projectRoot, "SKILL.md"),
|
|
98
|
+
join(projectRoot, "skill.md"),
|
|
99
|
+
join(projectRoot, "skills", "SKILL.md"),
|
|
100
|
+
join(projectRoot, "skills", "skill.md"),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
if (pkgSlug) {
|
|
104
|
+
candidates.push(
|
|
105
|
+
join(projectRoot, "skills", pkgSlug, "SKILL.md"),
|
|
106
|
+
join(projectRoot, "skills", pkgSlug, "skill.md")
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const candidate of candidates) {
|
|
111
|
+
if (existsSync(candidate)) {
|
|
112
|
+
try {
|
|
113
|
+
if (statSync(candidate).isFile()) return resolve(candidate);
|
|
114
|
+
} catch {
|
|
115
|
+
// 忽略
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 检索复合套件工作区中已存在的 SKILL.md 文件。
|
|
125
|
+
*/
|
|
126
|
+
export function findExistingCompositeSkillMd(
|
|
127
|
+
options: CompositeSkillExportOptions
|
|
128
|
+
): string | undefined {
|
|
129
|
+
if (options.skillMdPath && existsSync(options.skillMdPath)) {
|
|
130
|
+
try {
|
|
131
|
+
if (statSync(options.skillMdPath).isFile()) return resolve(options.skillMdPath);
|
|
132
|
+
} catch {
|
|
133
|
+
// 忽略
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const bundleSlug = getPackageSlug(options.bundleName);
|
|
138
|
+
const searchDirs = new Set<string>();
|
|
139
|
+
|
|
140
|
+
if (options.workspaceRoot && existsSync(options.workspaceRoot)) {
|
|
141
|
+
searchDirs.add(resolve(options.workspaceRoot));
|
|
142
|
+
}
|
|
143
|
+
searchDirs.add(process.cwd());
|
|
144
|
+
|
|
145
|
+
for (const root of options.projectRoots) {
|
|
146
|
+
const absRoot = resolve(root);
|
|
147
|
+
const parentDir = dirname(absRoot);
|
|
148
|
+
searchDirs.add(parentDir);
|
|
149
|
+
searchDirs.add(dirname(parentDir));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const candidateRelativePaths = [
|
|
153
|
+
join("skills", options.bundleName, "SKILL.md"),
|
|
154
|
+
join("skills", bundleSlug, "SKILL.md"),
|
|
155
|
+
join("skills", "SKILL.md"),
|
|
156
|
+
"SKILL.md",
|
|
157
|
+
join("skills", options.bundleName, "skill.md"),
|
|
158
|
+
join("skills", bundleSlug, "skill.md"),
|
|
159
|
+
join("skills", "skill.md"),
|
|
160
|
+
"skill.md",
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
for (const dir of searchDirs) {
|
|
164
|
+
for (const rel of candidateRelativePaths) {
|
|
165
|
+
const fullPath = join(dir, rel);
|
|
166
|
+
if (existsSync(fullPath)) {
|
|
167
|
+
try {
|
|
168
|
+
if (statSync(fullPath).isFile()) return fullPath;
|
|
169
|
+
} catch {
|
|
170
|
+
// 忽略
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
68
179
|
/**
|
|
69
180
|
* Agent Skill 导出器。
|
|
70
181
|
* 负责源码型 Skill 与独立二进制型 Skill 的构建、打包与归档分发。
|
|
@@ -115,36 +226,32 @@ export class SkillExporter {
|
|
|
115
226
|
config: plan.configDefs as any,
|
|
116
227
|
};
|
|
117
228
|
|
|
229
|
+
let usedExistingSkillMd: string | undefined;
|
|
230
|
+
|
|
118
231
|
if (mode === "source") {
|
|
119
232
|
// ----------------------------------------------------
|
|
120
233
|
// 源码 Skill 导出 (Source Skill Export)
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// 2. 生成 actiondock.skill.json 工具清单
|
|
133
|
-
const skillJsonContent = generateSkillJson(
|
|
134
|
-
configForTemplates,
|
|
135
|
-
plan.actions as any,
|
|
136
|
-
{
|
|
137
|
-
mode: "source",
|
|
138
|
-
playbooks: plan.playbooks as any,
|
|
234
|
+
// 优先复用当前动作目录下已有的 SKILL.md;若不存在且未显式跳过,则动态生成
|
|
235
|
+
const existingSkill = !options.skipSkillMd
|
|
236
|
+
? findExistingSingleSkillMd(root, options.skillMdPath, pkgSlug)
|
|
237
|
+
: undefined;
|
|
238
|
+
|
|
239
|
+
if (existingSkill) {
|
|
240
|
+
usedExistingSkillMd = existingSkill;
|
|
241
|
+
const destSkillMd = join(skillDir, "SKILL.md");
|
|
242
|
+
if (resolve(existingSkill) !== resolve(destSkillMd)) {
|
|
243
|
+
copyFileSync(existingSkill, destSkillMd);
|
|
139
244
|
}
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
245
|
+
} else if (!options.skipSkillMd) {
|
|
246
|
+
const skillMd = generateSourceSkillMd(
|
|
247
|
+
configForTemplates,
|
|
248
|
+
plan.actions as any,
|
|
249
|
+
plan.playbooks as any
|
|
250
|
+
);
|
|
251
|
+
writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
252
|
+
}
|
|
146
253
|
|
|
147
|
-
//
|
|
254
|
+
// - 导出精简后的 actiondock.manifest.json 清单
|
|
148
255
|
const manifestActions: Record<string, unknown> = {};
|
|
149
256
|
for (const a of plan.actions) {
|
|
150
257
|
manifestActions[a.id] = {
|
|
@@ -156,6 +263,12 @@ export class SkillExporter {
|
|
|
156
263
|
tags: a.tags,
|
|
157
264
|
annotations: a.annotations,
|
|
158
265
|
};
|
|
266
|
+
if (a.id.includes("/")) {
|
|
267
|
+
const shortId = a.id.slice(a.id.lastIndexOf("/") + 1);
|
|
268
|
+
if (!manifestActions[shortId]) {
|
|
269
|
+
manifestActions[shortId] = manifestActions[a.id];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
159
272
|
}
|
|
160
273
|
const exportedManifest = {
|
|
161
274
|
schemaVersion: 1,
|
|
@@ -168,7 +281,7 @@ export class SkillExporter {
|
|
|
168
281
|
"utf-8"
|
|
169
282
|
);
|
|
170
283
|
|
|
171
|
-
//
|
|
284
|
+
// - 导出精简后的 actiondock.json 项目配置
|
|
172
285
|
const exportedConfig = {
|
|
173
286
|
id: plan.packageId,
|
|
174
287
|
name: plan.packageName,
|
|
@@ -184,7 +297,7 @@ export class SkillExporter {
|
|
|
184
297
|
"utf-8"
|
|
185
298
|
);
|
|
186
299
|
|
|
187
|
-
//
|
|
300
|
+
// - 导出 package.json
|
|
188
301
|
const projectPkgPath = join(root, "package.json");
|
|
189
302
|
let exportedPkg: any;
|
|
190
303
|
if (existsSync(projectPkgPath)) {
|
|
@@ -225,13 +338,13 @@ export class SkillExporter {
|
|
|
225
338
|
"utf-8"
|
|
226
339
|
);
|
|
227
340
|
|
|
228
|
-
//
|
|
341
|
+
// - 拷贝 tsconfig.json(若存在)
|
|
229
342
|
const tsconfigPath = join(root, "tsconfig.json");
|
|
230
343
|
if (existsSync(tsconfigPath)) {
|
|
231
344
|
copyFileSync(tsconfigPath, join(skillDir, "tsconfig.json"));
|
|
232
345
|
}
|
|
233
346
|
|
|
234
|
-
//
|
|
347
|
+
// - 拷贝 Action 源码文件,完整保留相对路径
|
|
235
348
|
for (const act of plan.actions) {
|
|
236
349
|
if (existsSync(act.resolvedPath)) {
|
|
237
350
|
const destFile = join(skillDir, act.entry);
|
|
@@ -240,16 +353,16 @@ export class SkillExporter {
|
|
|
240
353
|
}
|
|
241
354
|
}
|
|
242
355
|
|
|
243
|
-
//
|
|
356
|
+
// - 拷贝静态资产与代码模块文件,完整保留相对路径
|
|
244
357
|
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
245
|
-
if (dep.type === "asset" && existsSync(dep.resolvedPath)) {
|
|
358
|
+
if ((dep.type === "asset" || dep.type === "module") && existsSync(dep.resolvedPath)) {
|
|
246
359
|
const destAsset = join(skillDir, dep.path);
|
|
247
360
|
mkdirSync(dirname(destAsset), { recursive: true });
|
|
248
361
|
copyFileSync(dep.resolvedPath, destAsset);
|
|
249
362
|
}
|
|
250
363
|
}
|
|
251
364
|
|
|
252
|
-
//
|
|
365
|
+
// - 拷贝 Playbook 规程文件
|
|
253
366
|
for (const pb of plan.playbooks) {
|
|
254
367
|
if (existsSync(pb.filePath)) {
|
|
255
368
|
const destPb = join(playbooksDestDir, basename(pb.filePath));
|
|
@@ -259,7 +372,7 @@ export class SkillExporter {
|
|
|
259
372
|
} else {
|
|
260
373
|
// ----------------------------------------------------
|
|
261
374
|
// 独立二进制 Skill 导出 (Standalone Binary Skill Export)
|
|
262
|
-
// 包含: SKILL.md,
|
|
375
|
+
// 包含: SKILL.md, bin/<binary>, playbooks/*, assets/*
|
|
263
376
|
// ----------------------------------------------------
|
|
264
377
|
const binDir = join(skillDir, "bin");
|
|
265
378
|
mkdirSync(binDir, { recursive: true });
|
|
@@ -304,33 +417,28 @@ export class SkillExporter {
|
|
|
304
417
|
|
|
305
418
|
const actualBinaryName = basename(compileRes.executablePath);
|
|
306
419
|
|
|
307
|
-
//
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const skillJsonContent = generateSkillJson(
|
|
318
|
-
configForTemplates,
|
|
319
|
-
plan.actions as any,
|
|
320
|
-
{
|
|
321
|
-
mode: "standalone",
|
|
322
|
-
executable: `./bin/${actualBinaryName}`,
|
|
323
|
-
target,
|
|
324
|
-
playbooks: plan.playbooks as any,
|
|
420
|
+
// - 生成独立模式 SKILL.md(若未显式跳过)
|
|
421
|
+
const existingSkill = !options.skipSkillMd
|
|
422
|
+
? findExistingSingleSkillMd(root, options.skillMdPath, pkgSlug)
|
|
423
|
+
: undefined;
|
|
424
|
+
|
|
425
|
+
if (existingSkill) {
|
|
426
|
+
usedExistingSkillMd = existingSkill;
|
|
427
|
+
const destSkillMd = join(skillDir, "SKILL.md");
|
|
428
|
+
if (resolve(existingSkill) !== resolve(destSkillMd)) {
|
|
429
|
+
copyFileSync(existingSkill, destSkillMd);
|
|
325
430
|
}
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
431
|
+
} else if (!options.skipSkillMd) {
|
|
432
|
+
const skillMd = generateStandaloneSkillMd(
|
|
433
|
+
configForTemplates,
|
|
434
|
+
plan.actions as any,
|
|
435
|
+
plan.playbooks as any,
|
|
436
|
+
`./bin/${actualBinaryName}`
|
|
437
|
+
);
|
|
438
|
+
writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
439
|
+
}
|
|
332
440
|
|
|
333
|
-
//
|
|
441
|
+
// - 拷贝 Playbook 规程文件
|
|
334
442
|
for (const pb of plan.playbooks) {
|
|
335
443
|
if (existsSync(pb.filePath)) {
|
|
336
444
|
const destPb = join(playbooksDestDir, basename(pb.filePath));
|
|
@@ -338,7 +446,7 @@ export class SkillExporter {
|
|
|
338
446
|
}
|
|
339
447
|
}
|
|
340
448
|
|
|
341
|
-
//
|
|
449
|
+
// - 拷贝静态资产
|
|
342
450
|
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
343
451
|
if (dep.type === "asset" && existsSync(dep.resolvedPath)) {
|
|
344
452
|
const destAsset = join(skillDir, dep.path);
|
|
@@ -374,9 +482,172 @@ export class SkillExporter {
|
|
|
374
482
|
actions: plan.actions.map((a) => a.id),
|
|
375
483
|
playbooks: plan.playbooks.map((p) => p.id),
|
|
376
484
|
files,
|
|
485
|
+
usedExistingSkillMd,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 批量导出多个 Skill。
|
|
491
|
+
*/
|
|
492
|
+
public async exportBatch(options: BatchSkillExportOptions): Promise<BatchSkillExportResult> {
|
|
493
|
+
if (!options.projectRoots || options.projectRoots.length === 0) {
|
|
494
|
+
throw new BuilderError("No project roots provided for batch skill export.");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const results: SkillExportResult[] = [];
|
|
498
|
+
const baseOutDir = resolve(options.outDir || join(process.cwd(), "dist", "skills"));
|
|
499
|
+
mkdirSync(baseOutDir, { recursive: true });
|
|
500
|
+
|
|
501
|
+
const usedDirNames = new Set<string>();
|
|
502
|
+
for (const projectRoot of options.projectRoots) {
|
|
503
|
+
const config = loadProjectConfig(projectRoot);
|
|
504
|
+
let pkgSlug = getPackageSlug(config.id);
|
|
505
|
+
if (usedDirNames.has(pkgSlug)) {
|
|
506
|
+
pkgSlug = config.id.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
|
|
507
|
+
}
|
|
508
|
+
usedDirNames.add(pkgSlug);
|
|
509
|
+
|
|
510
|
+
const pkgOutDir = join(baseOutDir, `${pkgSlug}-skill`);
|
|
511
|
+
|
|
512
|
+
const res = await this.export({
|
|
513
|
+
...options,
|
|
514
|
+
projectRoot,
|
|
515
|
+
outDir: pkgOutDir,
|
|
516
|
+
});
|
|
517
|
+
results.push(res);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const totalActions = results.reduce((sum, r) => sum + r.actionsCount, 0);
|
|
521
|
+
const totalPlaybooks = results.reduce((sum, r) => sum + r.playbooksCount, 0);
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
results,
|
|
525
|
+
outDir: baseOutDir,
|
|
526
|
+
totalActions,
|
|
527
|
+
totalPlaybooks,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* 静态辅助调用批量导出方法。
|
|
533
|
+
*/
|
|
534
|
+
public static async exportBatch(options: BatchSkillExportOptions): Promise<BatchSkillExportResult> {
|
|
535
|
+
const exporter = new SkillExporter();
|
|
536
|
+
return exporter.exportBatch(options);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* 复合模式导出:将多个包聚合为一个统一的复合技能目录。
|
|
541
|
+
*/
|
|
542
|
+
public async exportComposite(options: CompositeSkillExportOptions): Promise<CompositeSkillExportResult> {
|
|
543
|
+
if (!options.projectRoots || options.projectRoots.length === 0) {
|
|
544
|
+
throw new BuilderError("No project roots provided for composite skill export.");
|
|
545
|
+
}
|
|
546
|
+
if (!options.bundleName || !options.bundleName.trim()) {
|
|
547
|
+
throw new BuilderError("bundleName is required for composite skill export.");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const bundleSlug = getPackageSlug(options.bundleName);
|
|
551
|
+
const defaultSkillDir = join(process.cwd(), "dist", `${bundleSlug}-skill`);
|
|
552
|
+
const skillDir = resolve(options.outDir || defaultSkillDir);
|
|
553
|
+
|
|
554
|
+
mkdirSync(skillDir, { recursive: true });
|
|
555
|
+
const packagesDestDir = join(skillDir, "packages");
|
|
556
|
+
mkdirSync(packagesDestDir, { recursive: true });
|
|
557
|
+
|
|
558
|
+
const packageInfos: CompositeSkillPackageInfo[] = [];
|
|
559
|
+
const packageSummaries: Array<{ packageId: string; actions: string[]; playbooks: string[] }> = [];
|
|
560
|
+
|
|
561
|
+
const usedDirNames = new Set<string>();
|
|
562
|
+
for (const projectRoot of options.projectRoots) {
|
|
563
|
+
const config = loadProjectConfig(projectRoot);
|
|
564
|
+
let pkgSlug = getPackageSlug(config.id);
|
|
565
|
+
if (usedDirNames.has(pkgSlug)) {
|
|
566
|
+
pkgSlug = config.id.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
|
|
567
|
+
}
|
|
568
|
+
usedDirNames.add(pkgSlug);
|
|
569
|
+
|
|
570
|
+
const destPkgDir = join(packagesDestDir, pkgSlug);
|
|
571
|
+
|
|
572
|
+
const singleExport = await this.export({
|
|
573
|
+
projectRoot,
|
|
574
|
+
outDir: destPkgDir,
|
|
575
|
+
archive: false,
|
|
576
|
+
skipSkillMd: true,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const manifest = loadManifest(destPkgDir) || loadManifest(projectRoot);
|
|
580
|
+
const playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
|
|
581
|
+
|
|
582
|
+
const actionEntries = singleExport.actions.map((actId) => ({
|
|
583
|
+
id: actId,
|
|
584
|
+
description: manifest?.actions?.[actId]?.description,
|
|
585
|
+
}));
|
|
586
|
+
|
|
587
|
+
packageInfos.push({
|
|
588
|
+
config,
|
|
589
|
+
actions: actionEntries,
|
|
590
|
+
playbooks: Array.from(playbooks.values()),
|
|
591
|
+
packageDir: pkgSlug,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
packageSummaries.push({
|
|
595
|
+
packageId: config.id,
|
|
596
|
+
actions: singleExport.actions,
|
|
597
|
+
playbooks: singleExport.playbooks,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const existingSkillPath = findExistingCompositeSkillMd(options);
|
|
602
|
+
if (existingSkillPath) {
|
|
603
|
+
const destSkillMdPath = join(skillDir, "SKILL.md");
|
|
604
|
+
if (resolve(existingSkillPath) !== resolve(destSkillMdPath)) {
|
|
605
|
+
copyFileSync(existingSkillPath, destSkillMdPath);
|
|
606
|
+
}
|
|
607
|
+
} else {
|
|
608
|
+
const description =
|
|
609
|
+
options.description ||
|
|
610
|
+
`ActionDock 复合技能套件,聚合 ${packageInfos.map((p) => p.config.name).join("、")}`;
|
|
611
|
+
const compositeSkillMd = generateCompositeSkillMd(options.bundleName, description, packageInfos);
|
|
612
|
+
writeFileSync(join(skillDir, "SKILL.md"), compositeSkillMd, "utf-8");
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
let archivePath: string | undefined;
|
|
616
|
+
if (options.archive) {
|
|
617
|
+
let format: ArchiveFormat = "zip";
|
|
618
|
+
if (options.archiveFormat === "tar.gz" || options.archive === "tar.gz") {
|
|
619
|
+
format = "tar.gz";
|
|
620
|
+
} else if (options.archiveFormat === "zip" || options.archive === "zip") {
|
|
621
|
+
format = "zip";
|
|
622
|
+
}
|
|
623
|
+
archivePath = createArchive(skillDir, format);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const files = scanRelativeFiles(skillDir);
|
|
627
|
+
const totalActions = packageSummaries.reduce((sum, p) => sum + p.actions.length, 0);
|
|
628
|
+
const totalPlaybooks = packageSummaries.reduce((sum, p) => sum + p.playbooks.length, 0);
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
bundleName: options.bundleName,
|
|
632
|
+
skillDir,
|
|
633
|
+
archivePath,
|
|
634
|
+
packagesCount: packageInfos.length,
|
|
635
|
+
actionsCount: totalActions,
|
|
636
|
+
playbooksCount: totalPlaybooks,
|
|
637
|
+
packages: packageSummaries,
|
|
638
|
+
files,
|
|
639
|
+
usedExistingSkillMd: existingSkillPath,
|
|
377
640
|
};
|
|
378
641
|
}
|
|
379
642
|
|
|
643
|
+
/**
|
|
644
|
+
* 静态辅助调用复合导出方法。
|
|
645
|
+
*/
|
|
646
|
+
public static async exportComposite(options: CompositeSkillExportOptions): Promise<CompositeSkillExportResult> {
|
|
647
|
+
const exporter = new SkillExporter();
|
|
648
|
+
return exporter.exportComposite(options);
|
|
649
|
+
}
|
|
650
|
+
|
|
380
651
|
/**
|
|
381
652
|
* 静态辅助调用方法。
|
|
382
653
|
*/
|
|
@@ -392,3 +663,18 @@ export class SkillExporter {
|
|
|
392
663
|
export async function exportSkill(options: SkillExporterOptions): Promise<SkillExportResult> {
|
|
393
664
|
return SkillExporter.export(options);
|
|
394
665
|
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* 快捷批量导出多个 Skill 产物函数。
|
|
669
|
+
*/
|
|
670
|
+
export async function exportSkillBatch(options: BatchSkillExportOptions): Promise<BatchSkillExportResult> {
|
|
671
|
+
return SkillExporter.exportBatch(options);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* 快捷复合模式导出 Skill 产物函数。
|
|
676
|
+
*/
|
|
677
|
+
export async function exportCompositeSkill(options: CompositeSkillExportOptions): Promise<CompositeSkillExportResult> {
|
|
678
|
+
return SkillExporter.exportComposite(options);
|
|
679
|
+
}
|
|
680
|
+
|
package/src/planner.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
4
|
import {
|
|
5
5
|
type ActionDockManifest,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
loadProjectConfig,
|
|
9
9
|
type PlaybookDefinition,
|
|
10
10
|
type ProjectConfig,
|
|
11
|
+
resolveActionProjectSync,
|
|
11
12
|
validateManifest,
|
|
12
13
|
} from "@actiondock/core";
|
|
13
14
|
import { PlannerError } from "./errors";
|
|
@@ -40,6 +41,199 @@ function walkDirectory(dir: string): string[] {
|
|
|
40
41
|
return results;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* 忽略的模块路径与文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
46
|
+
*/
|
|
47
|
+
function isIgnoredModulePath(relPath: string): boolean {
|
|
48
|
+
const normalized = relPath.replace(/\\/g, "/");
|
|
49
|
+
return (
|
|
50
|
+
normalized.startsWith("node_modules/") ||
|
|
51
|
+
normalized.includes("/node_modules/") ||
|
|
52
|
+
normalized.startsWith(".git/") ||
|
|
53
|
+
normalized.startsWith("dist/") ||
|
|
54
|
+
normalized.startsWith(".actiondock/") ||
|
|
55
|
+
normalized.endsWith(".test.ts") ||
|
|
56
|
+
normalized.endsWith(".test.js") ||
|
|
57
|
+
normalized.endsWith(".test.tsx") ||
|
|
58
|
+
normalized.endsWith(".test.jsx") ||
|
|
59
|
+
normalized.endsWith(".spec.ts") ||
|
|
60
|
+
normalized.endsWith(".spec.js") ||
|
|
61
|
+
normalized.endsWith(".spec.tsx") ||
|
|
62
|
+
normalized.endsWith(".spec.jsx") ||
|
|
63
|
+
normalized.endsWith(".d.ts")
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 提取源码文本中的所有相对路径模块引用说明符(如 ./... 与 ../...)。
|
|
69
|
+
* 采用纯正则静态提取,杜绝动态 import 与代码执行。
|
|
70
|
+
*/
|
|
71
|
+
function extractRelativeImports(source: string): string[] {
|
|
72
|
+
// 移除多行注释与单行注释,避免注释中的无效相对路径被误提取
|
|
73
|
+
const stripped = source
|
|
74
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
75
|
+
.replace(/\/\/[^\n]*/g, "");
|
|
76
|
+
|
|
77
|
+
const specifiers = new Set<string>();
|
|
78
|
+
|
|
79
|
+
// 匹配 from "..."、from '...'、from `...`
|
|
80
|
+
const fromRegex = /\bfrom\s*["'`]([^"'`]+)["'`]/g;
|
|
81
|
+
let match: RegExpExecArray | null;
|
|
82
|
+
while ((match = fromRegex.exec(stripped)) !== null) {
|
|
83
|
+
const spec = match[1].trim();
|
|
84
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
85
|
+
specifiers.add(spec);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 匹配 import(...) 或 import "..."
|
|
90
|
+
const importRegex = /\bimport\s*(?:\(\s*)?["'`]([^"'`]+)["'`]/g;
|
|
91
|
+
while ((match = importRegex.exec(stripped)) !== null) {
|
|
92
|
+
const spec = match[1].trim();
|
|
93
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
94
|
+
specifiers.add(spec);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 匹配 require(...)
|
|
99
|
+
const requireRegex = /\brequire\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
100
|
+
while ((match = requireRegex.exec(stripped)) !== null) {
|
|
101
|
+
const spec = match[1].trim();
|
|
102
|
+
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
103
|
+
specifiers.add(spec);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Array.from(specifiers);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const CANDIDATE_EXTENSIONS = [
|
|
111
|
+
"",
|
|
112
|
+
".ts",
|
|
113
|
+
".tsx",
|
|
114
|
+
".js",
|
|
115
|
+
".jsx",
|
|
116
|
+
".mjs",
|
|
117
|
+
".cjs",
|
|
118
|
+
".mts",
|
|
119
|
+
".cts",
|
|
120
|
+
".json",
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 在文件系统上探测解析相对导入说明符所对应的物理源文件。
|
|
125
|
+
* 兼容 NodeNext 规范中将 .js / .mjs / .cjs 说明符解析映射到 .ts / .mts / .cts 源文件。
|
|
126
|
+
*/
|
|
127
|
+
function resolveLocalModulePath(baseDir: string, specifier: string): string | null {
|
|
128
|
+
const candidate = resolve(baseDir, specifier);
|
|
129
|
+
|
|
130
|
+
// 1. 若候选路径直接存在且为文件
|
|
131
|
+
if (existsSync(candidate)) {
|
|
132
|
+
try {
|
|
133
|
+
if (statSync(candidate).isFile()) {
|
|
134
|
+
return candidate;
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
// 忽略文件属性读取异常
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. NodeNext / ESM 规范映射:若导入说明符以 .js / .mjs / .cjs 结尾,优先尝试对应 TypeScript 源码文件
|
|
142
|
+
if (candidate.endsWith(".js")) {
|
|
143
|
+
const tsPath = candidate.slice(0, -3) + ".ts";
|
|
144
|
+
if (existsSync(tsPath)) return tsPath;
|
|
145
|
+
const tsxPath = candidate.slice(0, -3) + ".tsx";
|
|
146
|
+
if (existsSync(tsxPath)) return tsxPath;
|
|
147
|
+
} else if (candidate.endsWith(".mjs")) {
|
|
148
|
+
const mtsPath = candidate.slice(0, -4) + ".mts";
|
|
149
|
+
if (existsSync(mtsPath)) return mtsPath;
|
|
150
|
+
} else if (candidate.endsWith(".cjs")) {
|
|
151
|
+
const ctsPath = candidate.slice(0, -4) + ".cts";
|
|
152
|
+
if (existsSync(ctsPath)) return ctsPath;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 3. 尝试追加常见扩展名
|
|
156
|
+
for (const ext of CANDIDATE_EXTENSIONS) {
|
|
157
|
+
if (!ext) continue;
|
|
158
|
+
const withExt = candidate + ext;
|
|
159
|
+
if (existsSync(withExt)) {
|
|
160
|
+
try {
|
|
161
|
+
if (statSync(withExt).isFile()) {
|
|
162
|
+
return withExt;
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// 忽略
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 4. 若为目录,尝试 index 文件
|
|
171
|
+
if (existsSync(candidate)) {
|
|
172
|
+
try {
|
|
173
|
+
if (statSync(candidate).isDirectory()) {
|
|
174
|
+
for (const ext of [".ts", ".tsx", ".js", ".mjs", ".json"]) {
|
|
175
|
+
const indexFile = join(candidate, `index${ext}`);
|
|
176
|
+
if (existsSync(indexFile)) {
|
|
177
|
+
return indexFile;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
// 忽略
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 从一组入口文件出发,静态深度追踪所有相对路径引用的本地源码模块闭包。
|
|
191
|
+
*/
|
|
192
|
+
function traceLocalModuleDependencies(
|
|
193
|
+
entryFiles: string[],
|
|
194
|
+
projectRoot: string
|
|
195
|
+
): string[] {
|
|
196
|
+
const visited = new Set<string>();
|
|
197
|
+
const modules = new Set<string>();
|
|
198
|
+
const queue = [...entryFiles];
|
|
199
|
+
|
|
200
|
+
while (queue.length > 0) {
|
|
201
|
+
const current = queue.shift()!;
|
|
202
|
+
if (visited.has(current)) continue;
|
|
203
|
+
visited.add(current);
|
|
204
|
+
|
|
205
|
+
const relToRoot = relative(projectRoot, current);
|
|
206
|
+
if (relToRoot.startsWith("..") || isAbsolute(relToRoot)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let content = "";
|
|
211
|
+
try {
|
|
212
|
+
content = readFileSync(current, "utf-8");
|
|
213
|
+
} catch {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const specifiers = extractRelativeImports(content);
|
|
218
|
+
const dir = dirname(current);
|
|
219
|
+
|
|
220
|
+
for (const spec of specifiers) {
|
|
221
|
+
const resolved = resolveLocalModulePath(dir, spec);
|
|
222
|
+
if (resolved) {
|
|
223
|
+
const rel = relative(projectRoot, resolved);
|
|
224
|
+
if (!rel.startsWith("..") && !isAbsolute(rel) && !isIgnoredModulePath(rel)) {
|
|
225
|
+
modules.add(resolved);
|
|
226
|
+
if (!visited.has(resolved)) {
|
|
227
|
+
queue.push(resolved);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return Array.from(modules);
|
|
235
|
+
}
|
|
236
|
+
|
|
43
237
|
/**
|
|
44
238
|
* 纯文本解析 Playbook Markdown 文件,提取 YAML Frontmatter,不执行任何业务代码。
|
|
45
239
|
*/
|
|
@@ -281,9 +475,11 @@ export class BuildPlanner {
|
|
|
281
475
|
}
|
|
282
476
|
}
|
|
283
477
|
|
|
284
|
-
// 5. 进行依赖闭包(uses
|
|
478
|
+
// 5. 进行依赖闭包(uses)解析计算,自动处理环形依赖与传递依赖(支持当前清单与已链接外部包)
|
|
285
479
|
const resolvedActionIds = new Set<string>();
|
|
286
480
|
const queue = Array.from(initialActionIds);
|
|
481
|
+
const externalActionRoots = new Map<string, string>();
|
|
482
|
+
const externalActionEntries = new Map<string, ActionManifestEntry>();
|
|
287
483
|
|
|
288
484
|
while (queue.length > 0) {
|
|
289
485
|
const currentId = queue.shift()!;
|
|
@@ -291,14 +487,37 @@ export class BuildPlanner {
|
|
|
291
487
|
continue;
|
|
292
488
|
}
|
|
293
489
|
|
|
294
|
-
|
|
490
|
+
let entry = manifest.actions[currentId];
|
|
491
|
+
let entryRoot = root;
|
|
492
|
+
|
|
493
|
+
if (!entry) {
|
|
494
|
+
try {
|
|
495
|
+
const resolvedExternal = resolveActionProjectSync(currentId, root);
|
|
496
|
+
if (resolvedExternal && existsSync(resolvedExternal.projectRoot)) {
|
|
497
|
+
const externalManifest = loadManifest(resolvedExternal.projectRoot);
|
|
498
|
+
const externalEntry = externalManifest?.actions?.[resolvedExternal.actionId];
|
|
499
|
+
if (externalEntry) {
|
|
500
|
+
entry = externalEntry;
|
|
501
|
+
entryRoot = resolvedExternal.projectRoot;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
} catch {
|
|
505
|
+
// 忽略
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
295
509
|
if (!entry) {
|
|
296
510
|
throw new PlannerError(
|
|
297
|
-
`Action '${currentId}' referenced in dependency closure (uses) was not found in manifest`,
|
|
511
|
+
`Action '${currentId}' referenced in dependency closure (uses) was not found in manifest or linked packages`,
|
|
298
512
|
"MISSING_DEPENDENCY"
|
|
299
513
|
);
|
|
300
514
|
}
|
|
301
515
|
|
|
516
|
+
if (entryRoot !== root) {
|
|
517
|
+
externalActionRoots.set(currentId, entryRoot);
|
|
518
|
+
externalActionEntries.set(currentId, entry);
|
|
519
|
+
}
|
|
520
|
+
|
|
302
521
|
resolvedActionIds.add(currentId);
|
|
303
522
|
|
|
304
523
|
if (entry.uses && Array.isArray(entry.uses)) {
|
|
@@ -321,8 +540,15 @@ export class BuildPlanner {
|
|
|
321
540
|
// 7. 构造 Action 依赖结构
|
|
322
541
|
const actionDependencies: ActionDependency[] = [];
|
|
323
542
|
for (const actId of resolvedActionIds) {
|
|
324
|
-
const entry = manifest.actions[actId];
|
|
325
|
-
const
|
|
543
|
+
const entry = manifest.actions[actId] || externalActionEntries.get(actId);
|
|
544
|
+
const entryRoot = externalActionRoots.get(actId) || root;
|
|
545
|
+
if (!entry) {
|
|
546
|
+
throw new PlannerError(
|
|
547
|
+
`Action '${actId}' entry not found`,
|
|
548
|
+
"ENTRY_FILE_NOT_FOUND"
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
const resolvedPath = resolve(entryRoot, entry.entry);
|
|
326
552
|
if (!existsSync(resolvedPath)) {
|
|
327
553
|
throw new PlannerError(
|
|
328
554
|
`Action '${actId}' entry file not found on disk: ${entry.entry}`,
|
|
@@ -345,6 +571,58 @@ export class BuildPlanner {
|
|
|
345
571
|
// 8. 构造模块与资产依赖结构
|
|
346
572
|
const modulesAndAssets: AssetDependency[] = [];
|
|
347
573
|
const assetPathSet = new Set<string>();
|
|
574
|
+
const modulePathSet = new Set<string>();
|
|
575
|
+
const actionPathSet = new Set(actionDependencies.map((a) => a.resolvedPath));
|
|
576
|
+
|
|
577
|
+
// 静态递归追踪 Action 源码引用的本地模块代码(如 lib/、辅助工具等)
|
|
578
|
+
const rootActionsMap = new Map<string, string[]>();
|
|
579
|
+
for (const act of actionDependencies) {
|
|
580
|
+
const actRoot = externalActionRoots.get(act.id) || root;
|
|
581
|
+
if (!rootActionsMap.has(actRoot)) {
|
|
582
|
+
rootActionsMap.set(actRoot, []);
|
|
583
|
+
}
|
|
584
|
+
rootActionsMap.get(actRoot)!.push(act.resolvedPath);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
for (const [actRoot, files] of rootActionsMap) {
|
|
588
|
+
const tracedModulePaths = traceLocalModuleDependencies(files, actRoot);
|
|
589
|
+
for (const modPath of tracedModulePaths) {
|
|
590
|
+
if (!actionPathSet.has(modPath)) {
|
|
591
|
+
const rel = relative(actRoot, modPath).replace(/\\/g, "/");
|
|
592
|
+
if (!modulePathSet.has(rel) && !isIgnoredModulePath(rel)) {
|
|
593
|
+
modulePathSet.add(rel);
|
|
594
|
+
modulesAndAssets.push({
|
|
595
|
+
path: rel,
|
|
596
|
+
resolvedPath: modPath,
|
|
597
|
+
type: "module",
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 若未显式过滤 Action 与 Playbook(全量构建模式),且存在根目录 lib 目录,自动全量扫描 lib 源码
|
|
605
|
+
const isSelective =
|
|
606
|
+
Boolean(options?.actions && options.actions.length > 0) ||
|
|
607
|
+
Boolean(options?.playbooks && options.playbooks.length > 0);
|
|
608
|
+
|
|
609
|
+
if (!isSelective) {
|
|
610
|
+
const defaultLibDir = join(root, "lib");
|
|
611
|
+
if (existsSync(defaultLibDir)) {
|
|
612
|
+
const libFiles = walkDirectory(defaultLibDir);
|
|
613
|
+
for (const file of libFiles) {
|
|
614
|
+
const rel = relative(root, file).replace(/\\/g, "/");
|
|
615
|
+
if (!isIgnoredModulePath(rel) && !actionPathSet.has(file) && !modulePathSet.has(rel)) {
|
|
616
|
+
modulePathSet.add(rel);
|
|
617
|
+
modulesAndAssets.push({
|
|
618
|
+
path: rel,
|
|
619
|
+
resolvedPath: file,
|
|
620
|
+
type: "module",
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
348
626
|
|
|
349
627
|
// 清单声明资产
|
|
350
628
|
if (manifest.assets && Array.isArray(manifest.assets)) {
|
package/src/types.ts
CHANGED
|
@@ -231,6 +231,10 @@ export interface SkillExporterOptions {
|
|
|
231
231
|
config?: ProjectConfig;
|
|
232
232
|
/** 预置的声明式清单(可选) */
|
|
233
233
|
manifest?: ActionDockManifest;
|
|
234
|
+
/** 是否跳过生成 SKILL.md(多包聚合时避免子包重复生成,对外保持唯一定义) */
|
|
235
|
+
skipSkillMd?: boolean;
|
|
236
|
+
/** 显式指定的已有 SKILL.md 文件路径(若提供或存在则直接使用,不再动态生成) */
|
|
237
|
+
skillMdPath?: string;
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
/**
|
|
@@ -259,4 +263,75 @@ export interface SkillExportResult {
|
|
|
259
263
|
playbooks: string[];
|
|
260
264
|
/** 导出的文件相对路径清单 */
|
|
261
265
|
files: string[];
|
|
266
|
+
/** 若复用了已有的 SKILL.md 文件,返回该文件的绝对路径 */
|
|
267
|
+
usedExistingSkillMd?: string;
|
|
262
268
|
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 批量 Skill 导出选项。
|
|
272
|
+
*/
|
|
273
|
+
export interface BatchSkillExportOptions extends Omit<SkillExporterOptions, "projectRoot"> {
|
|
274
|
+
/** 待导出的项目根目录绝对路径列表 */
|
|
275
|
+
projectRoots: string[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 批量 Skill 导出结果。
|
|
280
|
+
*/
|
|
281
|
+
export interface BatchSkillExportResult {
|
|
282
|
+
/** 各包独立导出结果列表 */
|
|
283
|
+
results: SkillExportResult[];
|
|
284
|
+
/** 批量导出根目录 */
|
|
285
|
+
outDir: string;
|
|
286
|
+
/** 导出的总 Action 数量 */
|
|
287
|
+
totalActions: number;
|
|
288
|
+
/** 导出的总 Playbook 数量 */
|
|
289
|
+
totalPlaybooks: number;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 复合 Skill 套件导出选项。
|
|
294
|
+
*/
|
|
295
|
+
export interface CompositeSkillExportOptions {
|
|
296
|
+
/** 复合技能套件名称 */
|
|
297
|
+
bundleName: string;
|
|
298
|
+
/** 参与聚合的项目根目录绝对路径列表 */
|
|
299
|
+
projectRoots: string[];
|
|
300
|
+
/** 复合套件输出目标目录(默认输出至 dist 目录) */
|
|
301
|
+
outDir?: string;
|
|
302
|
+
/** 复合技能描述信息 */
|
|
303
|
+
description?: string;
|
|
304
|
+
/** 是否执行归档压缩,亦可直接指定归档格式 */
|
|
305
|
+
archive?: boolean | ArchiveFormat;
|
|
306
|
+
/** 归档压缩格式(zip 或 tar.gz) */
|
|
307
|
+
archiveFormat?: ArchiveFormat;
|
|
308
|
+
/** 工作区根目录(用于检索工作区现有的 SKILL.md 等资产,默认基于 cwd 与项目目录推导) */
|
|
309
|
+
workspaceRoot?: string;
|
|
310
|
+
/** 显式指定的已有 SKILL.md 文件路径(若存在则直接使用,不再动态生成) */
|
|
311
|
+
skillMdPath?: string;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 复合 Skill 套件导出结果。
|
|
316
|
+
*/
|
|
317
|
+
export interface CompositeSkillExportResult {
|
|
318
|
+
/** 复合技能套件名称 */
|
|
319
|
+
bundleName: string;
|
|
320
|
+
/** 生成的复合 Skill 目录绝对路径 */
|
|
321
|
+
skillDir: string;
|
|
322
|
+
/** 生成的归档文件绝对路径(若开启压缩) */
|
|
323
|
+
archivePath?: string;
|
|
324
|
+
/** 包含的 Package 数量 */
|
|
325
|
+
packagesCount: number;
|
|
326
|
+
/** 包含的总 Action 数量 */
|
|
327
|
+
actionsCount: number;
|
|
328
|
+
/** 包含的总 Playbook 数量 */
|
|
329
|
+
playbooksCount: number;
|
|
330
|
+
/** 各包的摘要明细 */
|
|
331
|
+
packages: Array<{ packageId: string; actions: string[]; playbooks: string[] }>;
|
|
332
|
+
/** 生成的文件清单 */
|
|
333
|
+
files: string[];
|
|
334
|
+
/** 若复用了已有的 SKILL.md 文件,返回该文件的绝对路径 */
|
|
335
|
+
usedExistingSkillMd?: string;
|
|
336
|
+
}
|
|
337
|
+
|