@actiondock/builder 2.17.0 → 2.19.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/README.md CHANGED
@@ -46,8 +46,7 @@ ActionDock 2.x 构建规划、目录交付与技能导出包。
46
46
  - 显式外部依赖物化:支持 `bundleDeps` 选项。当显式开启且依赖闭包跨越外部已链接包时,自顶向下递归解析依赖链并将外部依赖包物理物化进产物根目录的 `packages/*` 子目录,展开为自包含的微型工作区产物结构;展开为复合形态时与过滤参数严格互斥(抛出 `FILTERS_UNSUPPORTED_FOR_COMPOSITE` 错误)。
47
47
  - 独立自包含 Node 目录型导出:支持 `standalone` 选项(等价于 `mode: "node"`),生成免全局 `ad` 命令行工具的自包含目录,内置独立运行脚本与说明书,配合 `vendorDeps` 可实现生产依赖完全内置。
48
48
  - 本地依赖完整性强校验:严格断言 Action 源码中的相对路径导入。若引用了未在 `actions` 且未在 `files` 字段中声明的本地模块,直接中断构建并抛出 `BuilderError`(错误码 `UNMET_LOCAL_DEPENDENCY`),彻底杜绝缺失依赖的产物分发。
49
- - 生成 SKILL.md 指令:提取 Playbook 规程内容与工具元数据,合成符合智能体规范的操作指南文档与声明头部。
50
- - 复合套件导出与说明书定制:支持 `exportCompositeSkill` 函数将工作区内的多个包统一导出为复合工作区技能套件;支持通过 `customMdPath` 注入插槽定制模板,通过 `skillMdOnly` 实现就地仅原位刷新说明书。
49
+ - 说明书定制与复合套件导出:支持单包与复合套件通过 `customMdPath` 注入插槽定制模板(或放置 `SKILL.custom.md` 自动发现);支持 `exportCompositeSkill` 函数将工作区内的多个包统一导出为复合工作区技能套件;支持通过 `skillMdOnly` 实现就地仅原位刷新说明书。
51
50
  - 压缩归档:支持 `archive` 选项将导出的技能目录打包为 `.zip` 或 `.tar.gz` 格式。
52
51
 
53
52
  ---
package/dist/build.js CHANGED
@@ -12,7 +12,7 @@ import { generateNodeHostEntrySource, generateNodeSupervisorEntrySource } from "
12
12
  import { vendorDependencies } from "./vendor.js";
13
13
  import { generateStandaloneSkillMd } from "./skill/templates.js";
14
14
  import { writeRuntimeReferenceFile } from "./export-source.js";
15
- import { buildConfigForTemplates, writeSkillMd, } from "./skill-md.js";
15
+ import { buildConfigForTemplates, resolveCustomSkillDeclaration, writeSkillMd, } from "./skill-md.js";
16
16
  import { createArchive, resolveArchiveFormat } from "./export-archive.js";
17
17
  export { copyPlanEntries } from "./stage-sources.js";
18
18
  /**
@@ -310,9 +310,22 @@ export async function exportNodeSkill(root, targetSkillDir, plan, options, pkgSl
310
310
  allowInstallScripts: options.allowInstallScripts,
311
311
  requireReproducible: options.requireReproducible,
312
312
  });
313
- const usedExistingSkillMd = writeSkillMd(root, targetSkillDir, { skipSkillMd: options.skipSkillMd, skillMdPath: options.skillMdPath, pkgSlug }, () => {
313
+ const customDeclaration = resolveCustomSkillDeclaration({
314
+ projectRoot: root,
315
+ customMdPath: options.customMdPath,
316
+ workspaceRoot: options.workspaceRoot,
317
+ });
318
+ const usedExistingSkillMd = writeSkillMd(root, targetSkillDir, {
319
+ skipSkillMd: options.skipSkillMd,
320
+ skillMdPath: options.skillMdPath,
321
+ pkgSlug,
322
+ hasCustomDeclaration: Boolean(customDeclaration),
323
+ }, () => {
314
324
  const configForTemplates = buildConfigForTemplates(plan, plan.actionsDir || "actions", plan.playbooksDir || "playbooks");
315
- return generateStandaloneSkillMd(configForTemplates, plan.actions, plan.playbooks, "node ./entry.mjs");
325
+ return generateStandaloneSkillMd(configForTemplates, plan.actions, plan.playbooks, "node ./entry.mjs", {
326
+ customSections: customDeclaration?.sections,
327
+ description: customDeclaration?.description,
328
+ });
316
329
  });
317
330
  // 仅在使用自动模板时生成默认运行参考文件;Node 目录型使用自身入口,不要求全局安装 ad
318
331
  if (usedExistingSkillMd === undefined && !options.skipSkillMd) {
@@ -114,7 +114,9 @@ export async function exportCompositeImpl(options, exportSubpackage) {
114
114
  playbooks: playbookIds,
115
115
  });
116
116
  }
117
- existingSkillPath = skillMdOnly ? undefined : findExistingCompositeSkillMd(options);
117
+ const shouldCheckExisting = !skillMdOnly &&
118
+ (Boolean(options.skillMdPath) || !customDeclaration);
119
+ existingSkillPath = shouldCheckExisting ? findExistingCompositeSkillMd(options) : undefined;
118
120
  const description = options.description ||
119
121
  customDeclaration?.description ||
120
122
  `ActionDock 复合技能套件,聚合 ${packageInfos.map((p) => p.config.name).join("、")}`;
@@ -8,7 +8,7 @@ import { getInternalDependencyVersion } from "./fs-utils.js";
8
8
  import { assertValidManifestActionIds, sanitizeExportDependencies, serializePlanManifest, } from "./manifest.js";
9
9
  import { copyPlanEntries } from "./stage-sources.js";
10
10
  import { isOwnAction } from "./types.js";
11
- import { buildConfigForTemplates, writeSkillMd, } from "./skill-md.js";
11
+ import { buildConfigForTemplates, resolveCustomSkillDeclaration, writeSkillMd, } from "./skill-md.js";
12
12
  /**
13
13
  * 源码型 Skill 导出装配层。
14
14
  *
@@ -21,8 +21,21 @@ import { buildConfigForTemplates, writeSkillMd, } from "./skill-md.js";
21
21
  export function stageSourceSkill(root, skillDir, plan, options, pkgSlug) {
22
22
  const actionsDir = plan.actionsDir || "actions";
23
23
  const playbooksDir = plan.playbooksDir || "playbooks";
24
+ const customDeclaration = resolveCustomSkillDeclaration({
25
+ projectRoot: root,
26
+ customMdPath: options.customMdPath,
27
+ workspaceRoot: options.workspaceRoot,
28
+ });
24
29
  const configForTemplates = buildConfigForTemplates(plan, actionsDir, playbooksDir);
25
- const usedExistingSkillMd = writeSkillMd(root, skillDir, { skipSkillMd: options.skipSkillMd, skillMdPath: options.skillMdPath, pkgSlug }, () => generateSourceSkillMd(configForTemplates, plan.actions.filter((a) => isOwnAction(a)), plan.playbooks));
30
+ const usedExistingSkillMd = writeSkillMd(root, skillDir, {
31
+ skipSkillMd: options.skipSkillMd,
32
+ skillMdPath: options.skillMdPath,
33
+ pkgSlug,
34
+ hasCustomDeclaration: Boolean(customDeclaration),
35
+ }, () => generateSourceSkillMd(configForTemplates, plan.actions.filter((a) => isOwnAction(a)), plan.playbooks, {
36
+ customSections: customDeclaration?.sections,
37
+ description: customDeclaration?.description,
38
+ }));
26
39
  // 导出精简后的 actiondock.json 项目配置(项目元数据与清单的单一事实源)
27
40
  const exportedConfig = serializePlanManifest(plan, {
28
41
  omitEmptyConfig: true,
package/dist/exporter.js CHANGED
@@ -24,6 +24,9 @@ export class SkillExporter {
24
24
  * @returns 导出产物详细描述
25
25
  */
26
26
  async export(options) {
27
+ if (options.skillMdPath && options.customMdPath) {
28
+ throw new BuilderError("Options 'skillMdPath' and 'customMdPath' cannot be used together. Specify 'skillMdPath' to reuse an existing static document, or 'customMdPath' to customize template generation with custom slots.", "INVALID_EXPORT_OPTIONS");
29
+ }
27
30
  const root = resolve(options.projectRoot);
28
31
  const mode = options.standalone === true || options.mode === "node" ? "node" : "source";
29
32
  // 调用 SelectionPlanner 执行声明式构建规划
@@ -122,6 +125,9 @@ export class SkillExporter {
122
125
  * 复合模式导出:将多个包聚合为一个统一的复合技能目录。
123
126
  */
124
127
  async exportComposite(options) {
128
+ if (options.skillMdPath && options.customMdPath) {
129
+ throw new BuilderError("Options 'skillMdPath' and 'customMdPath' cannot be used together. Specify 'skillMdPath' to reuse an existing static document, or 'customMdPath' to customize template generation with custom slots.", "INVALID_EXPORT_OPTIONS");
130
+ }
125
131
  return exportCompositeImpl(options, (opts) => this.export(opts));
126
132
  }
127
133
  }
package/dist/index.d.ts CHANGED
@@ -8,4 +8,6 @@ export { packProject } from "./pack.js";
8
8
  export { exportCompositeSkill, exportSkill, SkillExporter, } from "./exporter.js";
9
9
  export { allocatePackageDirName } from "./manifest.js";
10
10
  export { SelectionPlanner } from "./planner.js";
11
+ export { COMPOSITE_CUSTOM_DECLARATION_FILE, COMPOSITE_CUSTOM_SLOTS, SKILL_CUSTOM_DECLARATION_FILE, SKILL_CUSTOM_SLOTS, parseCustomSections, parseCustomSkillDeclaration, } from "./skill/index.js";
12
+ export type { CompositeCustomDeclaration, CompositeCustomSection, CompositeCustomSlot, SkillCustomDeclaration, SkillCustomSection, SkillCustomSlot, } from "./skill/index.js";
11
13
  export type { ArchiveFormat, BuildOptions, BuildPlan, BuildProjectWithPlanOptions, BuildResult, CompositeSkillExportOptions, CompositeSkillExportResult, ExportSkillOptions, ExportSkillResult, PackOptions, PackResult, SelectionPlan, SelectionPlannerOptions, SkillExporterOptions, SkillExportResult, } from "./types.js";
package/dist/index.js CHANGED
@@ -11,3 +11,5 @@ export { exportCompositeSkill, exportSkill, SkillExporter, } from "./exporter.js
11
11
  export { allocatePackageDirName } from "./manifest.js";
12
12
  // 构建规划器
13
13
  export { SelectionPlanner } from "./planner.js";
14
+ // 技能说明书自定义契约与解析
15
+ export { COMPOSITE_CUSTOM_DECLARATION_FILE, COMPOSITE_CUSTOM_SLOTS, SKILL_CUSTOM_DECLARATION_FILE, SKILL_CUSTOM_SLOTS, parseCustomSections, parseCustomSkillDeclaration, } from "./skill/index.js";
@@ -1,20 +1,26 @@
1
1
  /**
2
- * 复合技能自定义说明书(SKILL.custom.md)的槽位,
2
+ * 自定义技能说明书(SKILL.custom.md)的槽位,
3
3
  * 决定自定义段落插入到官方模板生成结果中的位置。
4
+ * 单包导出与复合套件导出共用同一套槽位体系。
4
5
  */
5
6
  export type CompositeCustomSlot = "intro" | "after-init" | "after-describe" | "after-actions" | "after-playbooks" | "after-invoke" | "append";
7
+ export type SkillCustomSlot = CompositeCustomSlot;
6
8
  export declare const COMPOSITE_CUSTOM_SLOTS: readonly CompositeCustomSlot[];
7
- /** 工作区内约定俗成的复合技能自定义说明书文件名。 */
9
+ export declare const SKILL_CUSTOM_SLOTS: readonly CompositeCustomSlot[];
10
+ /** 约定俗成的自定义技能说明书文件名(单包与复合套件通用)。 */
8
11
  export declare const COMPOSITE_CUSTOM_DECLARATION_FILE = "SKILL.custom.md";
12
+ export declare const SKILL_CUSTOM_DECLARATION_FILE = "SKILL.custom.md";
9
13
  export interface CompositeCustomSection {
10
14
  slot: CompositeCustomSlot;
11
15
  content: string;
12
16
  }
17
+ export type SkillCustomSection = CompositeCustomSection;
13
18
  export interface CompositeCustomDeclaration {
14
19
  /** 自定义说明书 frontmatter 中声明的 description 覆盖值 */
15
20
  description?: string;
16
21
  sections: CompositeCustomSection[];
17
22
  }
23
+ export type SkillCustomDeclaration = CompositeCustomDeclaration;
18
24
  export declare const CUSTOM_SLOT_MARKER_PATTERN: RegExp;
19
25
  /**
20
26
  * 解析自定义说明书正文:以 `<!-- actiondock:slot <name> -->` 标记行分段。
@@ -8,8 +8,10 @@ export const COMPOSITE_CUSTOM_SLOTS = [
8
8
  "after-invoke",
9
9
  "append",
10
10
  ];
11
- /** 工作区内约定俗成的复合技能自定义说明书文件名。 */
11
+ export const SKILL_CUSTOM_SLOTS = COMPOSITE_CUSTOM_SLOTS;
12
+ /** 约定俗成的自定义技能说明书文件名(单包与复合套件通用)。 */
12
13
  export const COMPOSITE_CUSTOM_DECLARATION_FILE = "SKILL.custom.md";
14
+ export const SKILL_CUSTOM_DECLARATION_FILE = COMPOSITE_CUSTOM_DECLARATION_FILE;
13
15
  export const CUSTOM_SLOT_MARKER_PATTERN = /^\s*<!--\s*actiondock:slot\s+([a-zA-Z][a-zA-Z0-9-]*)\s*-->\s*$/;
14
16
  /**
15
17
  * 解析自定义说明书正文:以 `<!-- actiondock:slot <name> -->` 标记行分段。
@@ -33,8 +33,14 @@ export declare function renderRuntimeReferenceContent(options: {
33
33
  }): string;
34
34
  /** 运行时参考文件的导出目录内相对路径(单一事实源) */
35
35
  export declare const RUNTIME_REFERENCE_REL_PATH = "references/actiondock-runtime.md";
36
- export declare function generateSourceSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: SkillPlaybookItem[]): string;
37
- export declare function generateStandaloneSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: SkillPlaybookItem[], binaryRelPath?: string): string;
36
+ export declare function generateSourceSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: SkillPlaybookItem[], options?: {
37
+ customSections?: CompositeCustomSection[];
38
+ description?: string;
39
+ }): string;
40
+ export declare function generateStandaloneSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: SkillPlaybookItem[], binaryRelPath?: string, options?: {
41
+ customSections?: CompositeCustomSection[];
42
+ description?: string;
43
+ }): string;
38
44
  export interface CompositeSkillPackageInfo {
39
45
  config: ProjectConfig;
40
46
  actions: Array<{
@@ -1,7 +1,7 @@
1
1
  import { basename } from "node:path";
2
- function getCleanSkillMetadata(config) {
2
+ function getCleanSkillMetadata(config, descriptionOverride) {
3
3
  const cleanName = config.id.replace(/[^a-zA-Z0-9-_]/g, "-").toLowerCase();
4
- const desc = config.description || `AI Agent skill for ${config.name} (${config.id})`;
4
+ const desc = descriptionOverride || config.description || `AI Agent skill for ${config.name} (${config.id})`;
5
5
  return { cleanName, desc };
6
6
  }
7
7
  /**
@@ -202,52 +202,53 @@ function renderTroubleshootingSection(referenceRelPath) {
202
202
  }
203
203
  /** 运行时参考文件的导出目录内相对路径(单一事实源) */
204
204
  export const RUNTIME_REFERENCE_REL_PATH = "references/actiondock-runtime.md";
205
- export function generateSourceSkillMd(config, actions, playbooks) {
206
- const { cleanName, desc } = getCleanSkillMetadata(config);
205
+ export function generateSourceSkillMd(config, actions, playbooks, options) {
206
+ const { cleanName, desc } = getCleanSkillMetadata(config, options?.description);
207
207
  const pkgId = config.id;
208
208
  const firstAction = actions[0]?.id || "sample.greet";
209
209
  const playbookSection = renderPlaybookSectionMarkdown(playbooks, config.playbooksDir || "playbooks");
210
210
  const actionListMd = renderActionListMarkdown(actions, { packageId: pkgId });
211
- return `---
211
+ const sIntro = `---
212
212
  name: ${cleanName}
213
213
  description: ${desc}
214
214
  ---
215
215
 
216
216
  # ${config.name} (${config.id})
217
217
 
218
- ${desc}
219
-
220
- ## ActionDock 运行时
218
+ ${desc}`;
219
+ const sInit = `## ActionDock 运行时
221
220
 
222
- 本技能为 **ActionDock 源码型技能包**。智能体可直接通过宿主环境中已安装的 ActionDock 命令行工具 \`ad\` 执行其中的 Action。
221
+ 本技能为 ActionDock 源码型技能包。智能体可直接通过宿主环境中已安装的 ActionDock 命令行工具 \`ad\` 执行其中的 Action。
223
222
 
224
- ### 注册与链接(全局调用)
223
+ ### 安装依赖与注册链接
225
224
 
226
- 本技能面向全局挂载使用:在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root>\` 并完成注册链接,之后可在任意工作目录通过完全限定 ID 全局调用:
225
+ 本技能面向全局挂载使用。在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root>\`。标准流程为先安装生产依赖,再执行注册链接,之后可在任意工作目录通过完全限定 ID 全局调用:
227
226
 
228
227
  \`\`\`bash
228
+ # 安装生产依赖
229
+ cd "<skill_root>" && npm install --omit=dev
230
+
231
+ # 注册并链接到 ActionDock
229
232
  ad link "<skill_root>"
230
233
  \`\`\`
231
234
 
232
- > \`ad link\` 天然具备幂等性,同一 Package 多次执行会直接更新路径,可安全重复调用。若初次运行提示依赖缺失,可在 \`<skill_root>\` 目录下执行 \`npm install --omit=dev\` 安装生产依赖。
233
-
234
- ### 动作参数契约按需调阅
235
+ > \`ad link\` 天然具备幂等性,同一 Package 多次执行会直接更新路径,可安全重复调用。若后续依赖发生变更,可重新执行上述安装依赖与链接命令更新环境。`;
236
+ const sDescribe = `### 动作参数契约按需调阅
235
237
 
236
238
  在调用未知参数的 Action 前,可在终端执行命令查阅该 Action 的输入输出模式与详细说明:
237
239
 
238
240
  \`\`\`bash
239
241
  ad describe ${pkgId}/${firstAction}
240
- \`\`\`
241
-
242
- ### 执行 Action
242
+ \`\`\``;
243
+ const sInvoke = `### 执行 Action
243
244
 
244
245
  为避免多技能之间的 Action ID 命名冲突,建议统一使用带有 Package 前缀的完全限定 ID。
245
246
 
246
247
  ${renderInvocationGuidance(`ad describe ${pkgId}/${firstAction}`, `ad run ${pkgId}/${firstAction}`)}
247
248
 
248
- ${renderJsonEnvelopeSection("envelope")}
249
- ${playbookSection}
250
- ---
249
+ ${renderJsonEnvelopeSection("envelope")}`;
250
+ const sPlaybooks = playbookSection.trim();
251
+ const sActions = `---
251
252
 
252
253
  ## Action 目录
253
254
 
@@ -267,41 +268,54 @@ ad config set KEY VALUE --package ${pkgId}
267
268
  # 查看与检索状态数据
268
269
  ad state list --package ${pkgId}
269
270
  ad state get KEY --package ${pkgId}
270
- \`\`\`
271
-
272
- ${renderTroubleshootingSection(RUNTIME_REFERENCE_REL_PATH)}
273
- `;
271
+ \`\`\``;
272
+ const sTroubleshooting = renderTroubleshootingSection(RUNTIME_REFERENCE_REL_PATH);
273
+ const parts = [
274
+ { slot: "intro", text: sIntro },
275
+ { slot: "after-init", text: sInit },
276
+ { slot: "after-describe", text: sDescribe },
277
+ { slot: "after-invoke", text: sInvoke },
278
+ { slot: "after-playbooks", text: sPlaybooks },
279
+ { slot: "after-actions", text: sActions },
280
+ { slot: "append", text: sTroubleshooting },
281
+ ];
282
+ const customSections = options?.customSections ?? [];
283
+ for (const section of [...customSections].reverse()) {
284
+ const anchorIndex = parts.findIndex((p) => p.slot === section.slot);
285
+ parts.splice(anchorIndex + 1, 0, { slot: section.slot, text: section.content });
286
+ }
287
+ return (parts
288
+ .filter((p) => p.text.trim().length > 0)
289
+ .map((p) => p.text.trim())
290
+ .join("\n\n") + "\n");
274
291
  }
275
- export function generateStandaloneSkillMd(config, actions, playbooks, binaryRelPath = "./bin/action-bin") {
276
- const { cleanName, desc } = getCleanSkillMetadata(config);
292
+ export function generateStandaloneSkillMd(config, actions, playbooks, binaryRelPath = "./bin/action-bin", options) {
293
+ const { cleanName, desc } = getCleanSkillMetadata(config, options?.description);
277
294
  const firstAction = actions[0]?.id || "sample.greet";
278
295
  const playbookSection = renderPlaybookSectionMarkdown(playbooks, config.playbooksDir || "playbooks");
279
296
  const actionListMd = renderActionListMarkdown(actions);
280
- return `---
297
+ const sIntro = `---
281
298
  name: ${cleanName}
282
299
  description: ${desc}
283
300
  ---
284
301
 
285
302
  # ${config.name} (${config.id})
286
303
 
287
- ${desc}
288
-
289
-
290
- ### 动作参数契约按需调阅
304
+ ${desc}`;
305
+ const sDescribe = `### 动作参数契约按需调阅
291
306
 
292
307
  在调用未知参数的 Action 前,可在终端执行命令查阅该 Action 的输入输出模式与详细说明:
293
308
 
294
309
  \`\`\`bash
295
310
  ${binaryRelPath} describe ${firstAction}
296
- \`\`\`
297
-
298
- ### 执行 Action
311
+ \`\`\``;
312
+ const sInvoke = `### 执行 Action
299
313
 
300
314
  ${renderInvocationGuidance(`${binaryRelPath} describe ${firstAction}`, `${binaryRelPath} run ${firstAction}`)}
301
315
 
302
- ${renderJsonEnvelopeSection("result")}
303
- ${playbookSection}
304
- ---
316
+ ${renderJsonEnvelopeSection("result")}`;
317
+ const sPlaybooks = playbookSection.trim();
318
+ const sActions = `---
305
319
 
306
320
  ## Action 目录
307
321
 
@@ -321,9 +335,8 @@ ${binaryRelPath} config set KEY VALUE
321
335
  # 查看与检索状态数据
322
336
  ${binaryRelPath} state list
323
337
  ${binaryRelPath} state get KEY
324
- \`\`\`
325
-
326
- ---
338
+ \`\`\``;
339
+ const sTroubleshooting = `---
327
340
 
328
341
  ## 故障排查与依赖自愈指引(按需查阅)
329
342
 
@@ -334,8 +347,25 @@ ${binaryRelPath} state get KEY
334
347
  npm install --omit=dev
335
348
  \`\`\`
336
349
 
337
- 完整环境安装与自愈指引按需查阅:[${RUNTIME_REFERENCE_REL_PATH}](${RUNTIME_REFERENCE_REL_PATH})
338
- `;
350
+ 完整环境安装与自愈指引按需查阅:[${RUNTIME_REFERENCE_REL_PATH}](${RUNTIME_REFERENCE_REL_PATH})`;
351
+ const parts = [
352
+ { slot: "intro", text: sIntro },
353
+ { slot: "after-init", text: "" },
354
+ { slot: "after-describe", text: sDescribe },
355
+ { slot: "after-invoke", text: sInvoke },
356
+ { slot: "after-playbooks", text: sPlaybooks },
357
+ { slot: "after-actions", text: sActions },
358
+ { slot: "append", text: sTroubleshooting },
359
+ ];
360
+ const customSections = options?.customSections ?? [];
361
+ for (const section of [...customSections].reverse()) {
362
+ const anchorIndex = parts.findIndex((p) => p.slot === section.slot);
363
+ parts.splice(anchorIndex + 1, 0, { slot: section.slot, text: section.content });
364
+ }
365
+ return (parts
366
+ .filter((p) => p.text.trim().length > 0)
367
+ .map((p) => p.text.trim())
368
+ .join("\n\n") + "\n");
339
369
  }
340
370
  /**
341
371
  * 生成多包聚合的复合模式 SKILL.md 文档。
@@ -375,6 +405,23 @@ description: ${description}
375
405
  # ${bundleName} 复合技能套件
376
406
 
377
407
  ${description}`;
408
+ const sInit = `## ActionDock 运行时
409
+
410
+ 本技能为 ActionDock 复合技能套件,聚合了多个功能包。智能体可直接通过宿主环境中已安装的 ActionDock 命令行工具 \`ad\` 执行其中的 Action。
411
+
412
+ ### 安装依赖与注册链接
413
+
414
+ 本技能面向全局挂载使用。在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root>\`。标准流程为先安装复合套件聚合生产依赖,再执行注册链接,之后可在任意工作目录通过完全限定 ID 全局调用:
415
+
416
+ \`\`\`bash
417
+ # 安装复合套件聚合依赖
418
+ cd "<skill_root>" && npm install --omit=dev
419
+
420
+ # 注册并链接工作区子包到 ActionDock
421
+ ad link "<skill_root>"
422
+ \`\`\`
423
+
424
+ > \`ad link\` 会自动识别并注册工作区下的所有子包,使其中的 Action 随时可以通过完全限定标识调用。\`ad link\` 天然具备幂等性,可安全重复调用。若后续依赖发生变更,可重新执行上述安装依赖与链接命令更新环境。`;
378
425
  const sDescribe = `## 动作参数契约按需调阅
379
426
 
380
427
  为节省上下文开销,各 Action 的详细参数结构不静态内嵌在说明书中。在调用未知参数的 Action 前,可在终端执行命令查阅输入输出约束:
@@ -404,12 +451,12 @@ ${renderJsonEnvelopeSection("envelope", { withDescriptions: false })}`;
404
451
  const sTroubleshooting = renderTroubleshootingSection(RUNTIME_REFERENCE_REL_PATH);
405
452
  const parts = [
406
453
  { slot: "intro", text: sIntro },
454
+ { slot: "after-init", text: sInit },
407
455
  { slot: "after-describe", text: sDescribe },
408
456
  { slot: "after-actions", text: sActions },
409
457
  { slot: "after-playbooks", text: sPlaybooks },
410
458
  { slot: "after-invoke", text: sInvoke },
411
- { slot: "after-init", text: sTroubleshooting },
412
- { slot: "append", text: "" },
459
+ { slot: "append", text: sTroubleshooting },
413
460
  ];
414
461
  // 自定义段落按文件顺序插入到对应槽位(倒序插入保证同槽位多段保持先后)
415
462
  const customSections = options?.customSections ?? [];
@@ -16,10 +16,15 @@ export declare function findExistingSingleSkillMd(projectRoot: string, explicitP
16
16
  */
17
17
  export declare function findExistingCompositeSkillMd(options: CompositeSkillExportOptions): string | undefined;
18
18
  /**
19
- * 解析复合技能自定义说明书:显式 customMdPath 优先(缺失即报错),
20
- * 否则按 `SKILL.custom.md` 约定名在工作区根目录与当前目录自动发现。
19
+ * 解析技能自定义说明书:显式 customMdPath 优先(缺失即报错),
20
+ * 否则单包导出按项目根目录,复合导出按工作区根目录与当前目录自动发现 `SKILL.custom.md`。
21
21
  */
22
- export declare function resolveCustomSkillDeclaration(options: CompositeSkillExportOptions): (CompositeCustomDeclaration & {
22
+ export declare function resolveCustomSkillDeclaration(options: {
23
+ projectRoot?: string;
24
+ customMdPath?: string;
25
+ workspaceRoot?: string;
26
+ projectRoots?: string[];
27
+ }): (CompositeCustomDeclaration & {
23
28
  file: string;
24
29
  }) | undefined;
25
30
  /**
@@ -30,6 +35,7 @@ export declare function writeSkillMd(root: string, destDir: string, options: {
30
35
  skipSkillMd?: boolean;
31
36
  skillMdPath?: string;
32
37
  pkgSlug: string;
38
+ hasCustomDeclaration?: boolean;
33
39
  }, generate: () => string): string | undefined;
34
40
  /**
35
41
  * 构造模板生成所需的 ProjectConfig 视图。
package/dist/skill-md.js CHANGED
@@ -13,7 +13,10 @@ import { BuilderError } from "./errors.js";
13
13
  * 检索单个 Action 项目目录中已存在的 SKILL.md 文件。
14
14
  */
15
15
  export function findExistingSingleSkillMd(projectRoot, explicitPath, pkgSlug) {
16
- if (explicitPath && existsSync(explicitPath)) {
16
+ if (explicitPath) {
17
+ if (!existsSync(explicitPath)) {
18
+ throw new BuilderError(`Skill document file not found: '${explicitPath}'`);
19
+ }
17
20
  try {
18
21
  if (statSync(explicitPath).isFile())
19
22
  return resolve(explicitPath);
@@ -21,6 +24,7 @@ export function findExistingSingleSkillMd(projectRoot, explicitPath, pkgSlug) {
21
24
  catch {
22
25
  // 忽略文件属性读取异常
23
26
  }
27
+ throw new BuilderError(`Skill document path is not a regular file: '${explicitPath}'`);
24
28
  }
25
29
  const candidates = [
26
30
  join(projectRoot, "SKILL.md"),
@@ -48,7 +52,10 @@ export function findExistingSingleSkillMd(projectRoot, explicitPath, pkgSlug) {
48
52
  * 检索复合套件工作区中已存在的 SKILL.md 文件。
49
53
  */
50
54
  export function findExistingCompositeSkillMd(options) {
51
- if (options.skillMdPath && existsSync(options.skillMdPath)) {
55
+ if (options.skillMdPath) {
56
+ if (!existsSync(options.skillMdPath)) {
57
+ throw new BuilderError(`Skill document file not found: '${options.skillMdPath}'`);
58
+ }
52
59
  try {
53
60
  if (statSync(options.skillMdPath).isFile())
54
61
  return resolve(options.skillMdPath);
@@ -56,6 +63,7 @@ export function findExistingCompositeSkillMd(options) {
56
63
  catch {
57
64
  // 忽略文件属性读取异常
58
65
  }
66
+ throw new BuilderError(`Skill document path is not a regular file: '${options.skillMdPath}'`);
59
67
  }
60
68
  const bundleSlug = getPackageSlug(options.bundleName);
61
69
  const searchDirs = new Set();
@@ -95,8 +103,8 @@ export function findExistingCompositeSkillMd(options) {
95
103
  return undefined;
96
104
  }
97
105
  /**
98
- * 解析复合技能自定义说明书:显式 customMdPath 优先(缺失即报错),
99
- * 否则按 `SKILL.custom.md` 约定名在工作区根目录与当前目录自动发现。
106
+ * 解析技能自定义说明书:显式 customMdPath 优先(缺失即报错),
107
+ * 否则单包导出按项目根目录,复合导出按工作区根目录与当前目录自动发现 `SKILL.custom.md`。
100
108
  */
101
109
  export function resolveCustomSkillDeclaration(options) {
102
110
  let filePath;
@@ -106,14 +114,52 @@ export function resolveCustomSkillDeclaration(options) {
106
114
  }
107
115
  filePath = resolve(options.customMdPath);
108
116
  }
117
+ else if (options.projectRoot) {
118
+ const root = resolve(options.projectRoot);
119
+ const candidates = [
120
+ join(root, COMPOSITE_CUSTOM_DECLARATION_FILE),
121
+ join(root, "skill.custom.md"),
122
+ join(root, "skills", COMPOSITE_CUSTOM_DECLARATION_FILE),
123
+ join(root, "skills", "skill.custom.md"),
124
+ ];
125
+ for (const candidate of candidates) {
126
+ if (existsSync(candidate)) {
127
+ try {
128
+ if (statSync(candidate).isFile()) {
129
+ filePath = candidate;
130
+ break;
131
+ }
132
+ }
133
+ catch {
134
+ // 忽略文件属性读取异常
135
+ }
136
+ }
137
+ }
138
+ }
109
139
  else {
110
140
  const searchDirs = [options.workspaceRoot, process.cwd()].filter(Boolean);
111
141
  for (const dir of searchDirs) {
112
- const candidate = join(resolve(dir), COMPOSITE_CUSTOM_DECLARATION_FILE);
113
- if (existsSync(candidate)) {
114
- filePath = candidate;
115
- break;
142
+ const candidates = [
143
+ join(resolve(dir), COMPOSITE_CUSTOM_DECLARATION_FILE),
144
+ join(resolve(dir), "skill.custom.md"),
145
+ join(resolve(dir), "skills", COMPOSITE_CUSTOM_DECLARATION_FILE),
146
+ join(resolve(dir), "skills", "skill.custom.md"),
147
+ ];
148
+ for (const candidate of candidates) {
149
+ if (existsSync(candidate)) {
150
+ try {
151
+ if (statSync(candidate).isFile()) {
152
+ filePath = candidate;
153
+ break;
154
+ }
155
+ }
156
+ catch {
157
+ // 忽略文件属性读取异常
158
+ }
159
+ }
116
160
  }
161
+ if (filePath)
162
+ break;
117
163
  }
118
164
  }
119
165
  if (!filePath) {
@@ -129,9 +175,11 @@ export function resolveCustomSkillDeclaration(options) {
129
175
  * 返回实际复用的已有 SKILL.md 路径(若发生复用)。
130
176
  */
131
177
  export function writeSkillMd(root, destDir, options, generate) {
132
- const existingSkill = options.skipSkillMd
133
- ? undefined
134
- : findExistingSingleSkillMd(root, options.skillMdPath, options.pkgSlug);
178
+ const shouldCheckExisting = !options.skipSkillMd &&
179
+ (Boolean(options.skillMdPath) || !options.hasCustomDeclaration);
180
+ const existingSkill = shouldCheckExisting
181
+ ? findExistingSingleSkillMd(root, options.skillMdPath, options.pkgSlug)
182
+ : undefined;
135
183
  if (existingSkill) {
136
184
  const destSkillMd = join(destDir, "SKILL.md");
137
185
  if (resolve(existingSkill) !== resolve(destSkillMd)) {
package/dist/types.d.ts CHANGED
@@ -340,7 +340,7 @@ export interface SkillExporterOptions {
340
340
  skipDependencyValidation?: boolean;
341
341
  /** 内部标识:是否作为复合套件子包导出(避免闭包再次触发嵌套 mini-workspace) */
342
342
  _isSubpackage?: boolean;
343
- /** 自定义复合技能说明书文件路径 */
343
+ /** 自定义技能说明书(SKILL.custom.md)文件路径;缺省时自动在项目或工作区根目录下发现同名文件 */
344
344
  customMdPath?: string;
345
345
  /** 所属 Workspace 根目录(可选) */
346
346
  workspaceRoot?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/builder",
3
- "version": "2.17.0",
3
+ "version": "2.19.0",
4
4
  "description": "ActionDock Build Planner, distribution packaging, and Skill exporter",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,8 +29,8 @@
29
29
  "test": "node ../../scripts/run-tests.ts"
30
30
  },
31
31
  "dependencies": {
32
- "@actiondock/core": "^2.17.0",
33
- "@actiondock/sdk": "^2.17.0"
32
+ "@actiondock/core": "^2.19.0",
33
+ "@actiondock/sdk": "^2.19.0"
34
34
  },
35
35
  "keywords": [
36
36
  "actiondock",