@actiondock/builder 2.14.0 → 2.16.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.
@@ -8,15 +8,8 @@ import type { SelectionPlan } from "./types.js";
8
8
  * 按需加载业务实现;跨包外部 Action 不混入本包源码,依赖调用继续经 Host 路由;
9
9
  * - 入口路径以产物包根目录为基准(import.meta.dirname),不依赖调用者当前工作目录。
10
10
  */
11
- export declare function generateNodeHostEntrySource(plan: SelectionPlan | undefined, options?: {
12
- localPackages: Array<{
13
- packageId: string;
14
- relativeRoot: string;
15
- }>;
16
- }): string;
11
+ export declare function generateNodeHostEntrySource(plan: SelectionPlan): string;
17
12
  /**
18
13
  * 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
19
14
  */
20
- export declare function generateNodeSupervisorEntrySource(plan: Pick<SelectionPlan, "packageId" | "version" | "description">, options?: {
21
- multiPackage?: boolean;
22
- }): string;
15
+ export declare function generateNodeSupervisorEntrySource(plan: SelectionPlan): string;
@@ -1,5 +1,4 @@
1
1
  import { STANDALONE_ASYNC_UNSUPPORTED } from "@actiondock/core";
2
- import { BuilderError } from "./errors.js";
3
2
  import { serializePlanManifest } from "./manifest.js";
4
3
  /**
5
4
  * 生成 Host 子进程入口脚本源码(负责运行 ActionDockHost,通过 Node IPC 暴露 Target)。
@@ -10,49 +9,24 @@ import { serializePlanManifest } from "./manifest.js";
10
9
  * 按需加载业务实现;跨包外部 Action 不混入本包源码,依赖调用继续经 Host 路由;
11
10
  * - 入口路径以产物包根目录为基准(import.meta.dirname),不依赖调用者当前工作目录。
12
11
  */
13
- export function generateNodeHostEntrySource(plan, options) {
12
+ export function generateNodeHostEntrySource(plan) {
14
13
  // 复用 SelectionPlan 的清单序列化单一事实源(含包内动作筛选与 Schema 字段映射),
15
14
  // 不单独复制一份 Schema 字段映射
16
- if (!options && !plan)
17
- throw new BuilderError("A plan or explicit local packages are required for the Host entry");
18
- const manifestActions = options ? undefined : serializePlanManifest(plan).actions;
19
- const packageEntries = options
20
- ? options.localPackages.map((pkg) => `{
21
- packageRoot: resolve(packageRoot, ${JSON.stringify(pkg.relativeRoot)}),
22
- projectConfig: loadLocalConfig(${JSON.stringify(pkg.packageId)}, ${JSON.stringify(pkg.relativeRoot)}),
23
- dataDir,
24
- configOverrides,
25
- }`).join(",\n")
26
- : `{
27
- packageRoot, dataDir, configOverrides,
28
- projectConfig: ${JSON.stringify({
29
- id: plan.packageId, name: plan.packageName, version: plan.version,
30
- description: plan.description || "", config: plan.configDefs || {}, actions: manifestActions,
31
- })},
32
- }`;
15
+ const manifestActions = serializePlanManifest(plan).actions;
33
16
  return `#!/usr/bin/env node
34
17
  // AUTO-GENERATED HOST ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
35
- import { resolve } from "node:path";
36
18
  import {
37
- loadProjectConfig,
38
19
  createActionDock,
39
20
  createNodePlatform,
40
- validateLocalSkillPackages,
41
21
  } from "@actiondock/core";
42
22
  import { serveParentIpc } from "@actiondock/core/server";
43
23
 
44
24
  // 产物包根目录:以入口文件自身位置为基准,不依赖调用者当前工作目录
45
25
  const packageRoot = import.meta.dirname;
46
- ${options ? `function loadLocalConfig(expectedId, relativeRoot) {
47
- const config = loadProjectConfig(resolve(packageRoot, relativeRoot));
48
- if (config.id !== expectedId) throw new Error("Local package ID mismatch: expected " + expectedId + ", found " + config.id);
49
- return config;
50
- }` : ""}
51
26
 
52
27
  let dataDir;
53
28
  const configOverrides = {};
54
29
  const args = process.argv.slice(2);
55
- const commandArgs = [];
56
30
  for (let i = 0; i < args.length; i++) {
57
31
  if (args[i] === "--data-dir" && i + 1 < args.length) {
58
32
  dataDir = args[++i];
@@ -66,28 +40,33 @@ for (let i = 0; i < args.length; i++) {
66
40
  const raw = args[i].slice(9);
67
41
  const [k, ...v] = raw.split("=");
68
42
  if (k) configOverrides[k] = v.join("=");
69
- } else {
70
- commandArgs.push(args[i]);
71
43
  }
72
44
  }
73
45
 
74
46
  // 复用既有命令分发结果确定访问性质:发现命令(list/describe/show)为旁观初始化,
75
47
  // 不创建数据库、不取目录锁、不收割遗留运行记录;执行与写入命令为持有者初始化
76
- const command = commandArgs.find((a) => !a.startsWith("-")) || "";
48
+ const command = args.find((a) => !a.startsWith("-")) || "";
77
49
  const DISCOVERY_COMMANDS = new Set(["list", "describe", "show"]);
78
50
  const recoverOrphans = DISCOVERY_COMMANDS.has(command) ? false : undefined;
79
51
 
80
- const localPackages = [${packageEntries}];
81
- validateLocalSkillPackages(localPackages.map((p) => ({
82
- packageId: p.projectConfig.id,
83
- packageRoot: p.packageRoot,
84
- projectConfig: p.projectConfig,
85
- })));
86
-
87
52
  const service = await createActionDock({
88
53
  dataDir,
89
54
  ...(recoverOrphans === false ? { recoverOrphans: false } : {}),
90
- packages: localPackages,
55
+ packages: [
56
+ {
57
+ packageRoot,
58
+ dataDir,
59
+ configOverrides,
60
+ projectConfig: {
61
+ id: ${JSON.stringify(plan.packageId)},
62
+ name: ${JSON.stringify(plan.packageName)},
63
+ version: ${JSON.stringify(plan.version)},
64
+ description: ${JSON.stringify(plan.description || "")},
65
+ config: ${JSON.stringify(plan.configDefs || {})},
66
+ actions: ${JSON.stringify(manifestActions)},
67
+ },
68
+ },
69
+ ],
91
70
  platform: createNodePlatform({ dataDir }),
92
71
  autoLoadCurrentProject: false,
93
72
  scanLinkedPackages: false,
@@ -98,7 +77,7 @@ await serveParentIpc(service);
98
77
  /**
99
78
  * 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
100
79
  */
101
- export function generateNodeSupervisorEntrySource(plan, options) {
80
+ export function generateNodeSupervisorEntrySource(plan) {
102
81
  return `#!/usr/bin/env node
103
82
  // AUTO-GENERATED SUPERVISOR ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
104
83
  import { spawn } from "node:child_process";
@@ -162,8 +141,8 @@ if (argv.includes("-h") || argv.includes("--help") || argv[0] === "help") {
162
141
  console.log(" <cmd> run <id> [--input '<json>'] Execute action with JSON input");
163
142
  console.log(" <cmd> run <id> [--stdin-field <field>] [-- <assignments...>]");
164
143
  console.log(" Bind raw stdin text to an input field");
165
- ${options?.multiPackage ? 'console.log(" Multi-package config/state management is unavailable; use --config overrides.");'
166
- : 'console.log(" <cmd> config list/get/set/delete Manage package configuration");\n console.log(" <cmd> state list/get/set/delete Manage shared state store");'}
144
+ console.log(" <cmd> config list/get/set/delete Manage package configuration");
145
+ console.log(" <cmd> state list/get/set/delete Manage shared state store");
167
146
  console.log("\\nGlobal options:");
168
147
  console.log(" --data-dir <path> Custom runtime database directory");
169
148
  console.log(" --config <KEY=val> Temporary config override");
@@ -212,7 +191,6 @@ const dispatcher = new StandaloneDispatcher({
212
191
  version: METADATA.version,
213
192
  description: METADATA.description,
214
193
  service,
215
- multiPackage: ${JSON.stringify(options?.multiPackage === true)},
216
194
  });
217
195
 
218
196
  try {
@@ -3,7 +3,7 @@ import type { CompositeSkillExportOptions, CompositeSkillExportResult, SkillExpo
3
3
  * 复合套件导出装配层。
4
4
  *
5
5
  * 职责:将多个包聚合为一个统一的复合技能目录(mini-workspace 形态),
6
- * 保留各子包 npm 依赖并生成复合根 package.json,并生成或复用复合 SKILL.md。
6
+ * 聚合各子包依赖生成复合根 package.json,并生成或复用复合 SKILL.md。
7
7
  */
8
8
  /** 子包导出回调:由 SkillExporter 注入自身导出能力,避免模块间循环依赖 */
9
9
  export type SubpackageExporter = (options: SkillExporterOptions) => Promise<SkillExportResult>;
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
3
  import { loadProjectConfig, } from "@actiondock/core";
4
4
  import { getPackageSlug, loadManifest, loadPlaybooks, } from "@actiondock/core/project";
@@ -6,10 +6,9 @@ import { generateCompositeSkillMd, RUNTIME_REFERENCE_REL_PATH } from "./skill/in
6
6
  import { writeRuntimeReferenceFile } from "./export-source.js";
7
7
  import { BuilderError } from "./errors.js";
8
8
  import { collectRelativeFiles, getInternalDependencyVersion, replaceDirAtomic } from "./fs-utils.js";
9
- import { allocatePackageDirName } from "./manifest.js";
10
- import { createSourcePackageGraph } from "./source-graph.js";
11
- import { generateNodeHostEntrySource, generateNodeSupervisorEntrySource } from "./entry-generators.js";
12
- import { findExistingCompositeSkillMd, resolveCustomSkillDeclaration, appendLocalExecutionContract, } from "./skill-md.js";
9
+ import { allocatePackageDirName, sanitizeExportDependencies } from "./manifest.js";
10
+ import { SelectionPlanner } from "./planner.js";
11
+ import { findExistingCompositeSkillMd, resolveCustomSkillDeclaration, } from "./skill-md.js";
13
12
  import { createArchive, resolveArchiveFormat } from "./export-archive.js";
14
13
  /**
15
14
  * 执行复合模式导出:将多个包聚合为一个统一的复合技能目录。
@@ -39,36 +38,30 @@ export async function exportCompositeImpl(options, exportSubpackage) {
39
38
  mkdirSync(packagesDestDir, { recursive: true });
40
39
  }
41
40
  const packageInfos = [];
42
- const localPackages = [];
43
41
  const packageSummaries = [];
44
42
  let existingSkillPath;
45
43
  try {
46
- const packageGraph = skillMdOnly ? undefined : options.packageGraph ?? createSourcePackageGraph(options.projectRoots);
47
- const effectiveRoots = skillMdOnly
48
- ? new Set(options.projectRoots.map((root) => resolve(root)))
49
- : new Set(Array.from(packageGraph.packages.values(), (pkg) => pkg.root));
50
- const usedDirNames = new Set();
51
- const usedNpmNames = new Set();
52
- const sourceNpmNames = new Map();
53
- if (!skillMdOnly) {
54
- // 先保留全部真实 npm 名,缺省名不得覆盖尚未导出的兄弟包身份。
55
- for (const projectRoot of effectiveRoots) {
56
- const pkgJsonPath = join(projectRoot, "package.json");
57
- const name = existsSync(pkgJsonPath) ? readWorkspacePackageName(pkgJsonPath) : undefined;
58
- sourceNpmNames.set(projectRoot, name);
59
- if (name) {
60
- if (usedNpmNames.has(name)) {
61
- throw new BuilderError(`Duplicate npm workspace package name '${name}'.`, "DUPLICATE_WORKSPACE_PACKAGE_NAME");
44
+ // 收集并展开所有包含在依赖闭包中的外部项目根目录,确保 bundle 整包携带齐全部依赖
45
+ const effectiveRoots = new Set();
46
+ for (const r of options.projectRoots) {
47
+ effectiveRoots.add(resolve(r));
48
+ if (!skillMdOnly) {
49
+ try {
50
+ const p = SelectionPlanner.plan({ projectRoot: r });
51
+ if (p.externalProjectRoots) {
52
+ for (const extRoot of p.externalProjectRoots) {
53
+ effectiveRoots.add(resolve(extRoot));
54
+ }
62
55
  }
63
- usedNpmNames.add(name);
56
+ }
57
+ catch {
58
+ // 忽略规划解析异常,由主循环处理
64
59
  }
65
60
  }
66
61
  }
62
+ const usedDirNames = new Set();
67
63
  for (const projectRoot of effectiveRoots) {
68
- const graphManifest = packageGraph && Array.from(packageGraph.packages.values()).find((p) => resolve(p.root) === resolve(projectRoot))?.manifest;
69
- const config = graphManifest
70
- ? { ...graphManifest, name: graphManifest.name || graphManifest.id, version: graphManifest.version || "0.1.0" }
71
- : loadProjectConfig(projectRoot);
64
+ const config = loadProjectConfig(projectRoot);
72
65
  let pkgSlug;
73
66
  if (skillMdOnly) {
74
67
  // 就地生成:规程链接基于工作区实际子目录名
@@ -78,49 +71,34 @@ export async function exportCompositeImpl(options, exportSubpackage) {
78
71
  // 目录名分配统一复用 manifest 的 allocatePackageDirName,回退后仍冲突会追加数字后缀
79
72
  pkgSlug = allocatePackageDirName(usedDirNames, config.id);
80
73
  }
81
- let defaultPackageName;
82
- if (!skillMdOnly && !sourceNpmNames.get(projectRoot)) {
83
- const baseName = normalizeNpmPackageName(pkgSlug);
84
- let candidate = baseName;
85
- let suffix = 2;
86
- while (usedNpmNames.has(candidate))
87
- candidate = `${baseName}-${suffix++}`;
88
- defaultPackageName = candidate;
89
- usedNpmNames.add(candidate);
90
- }
91
74
  let actionIds;
92
- let playbooks;
75
+ let playbookIds;
93
76
  let manifest;
94
77
  if (skillMdOnly) {
95
78
  // 仅生成 SKILL.md:直接读取清单获取 Action 契约描述,不拷贝子包产物
96
79
  manifest = loadManifest(projectRoot);
97
80
  actionIds = manifest?.actions ? Object.keys(manifest.actions) : [];
98
- playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
81
+ playbookIds = Array.from(loadPlaybooks(projectRoot, config.playbooksDir).keys());
99
82
  }
100
83
  else {
101
84
  const destPkgDir = join(packagesDestDir, pkgSlug);
102
85
  const singleExport = await exportSubpackage({
103
86
  projectRoot,
104
- packageGraph,
105
- config,
106
- manifest: graphManifest || undefined,
107
87
  outDir: destPkgDir,
108
88
  archive: false,
109
89
  skipSkillMd: true,
110
90
  mode: "source",
111
91
  _isSubpackage: true,
112
- defaultPackageName,
113
92
  });
114
- manifest = loadManifest(destPkgDir);
93
+ manifest = loadManifest(destPkgDir) || loadManifest(projectRoot);
115
94
  actionIds = singleExport.actions;
116
- playbooks = loadPlaybooks(destPkgDir, manifest?.playbooksDir, manifest);
95
+ playbookIds = singleExport.playbooks;
117
96
  }
118
- const playbookIds = Array.from(playbooks.keys());
97
+ const playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
119
98
  const actionEntries = actionIds.map((actId) => ({
120
99
  id: actId,
121
100
  description: manifest?.actions?.[actId]?.description,
122
101
  }));
123
- localPackages.push({ packageId: config.id, relativeRoot: `packages/${pkgSlug}` });
124
102
  packageInfos.push({
125
103
  config,
126
104
  actions: actionEntries,
@@ -140,7 +118,6 @@ export async function exportCompositeImpl(options, exportSubpackage) {
140
118
  const compositeSkillMd = generateCompositeSkillMd(options.bundleName, description, packageInfos, {
141
119
  customSections: customDeclaration?.sections,
142
120
  packagesBaseDir: skillMdOnly ? "." : "packages",
143
- invocationStyle: skillMdOnly ? "global-ad" : "source-ad",
144
121
  });
145
122
  if (skillMdOnly) {
146
123
  // 就地仅重生成 SKILL.md:始终重新生成(忽略已有 SKILL.md 复用逻辑),不产出完整套件目录
@@ -176,10 +153,9 @@ export async function exportCompositeImpl(options, exportSubpackage) {
176
153
  }
177
154
  if (existingSkillPath) {
178
155
  const destSkillMdPath = join(stagingDir, "SKILL.md");
179
- writeFileSync(destSkillMdPath, readFileSync(existingSkillPath, "utf-8"), "utf-8");
180
- appendLocalExecutionContract(destSkillMdPath, packageInfos.map((p) => ({
181
- packageId: p.config.id, actions: p.actions,
182
- })));
156
+ if (resolve(existingSkillPath) !== resolve(destSkillMdPath)) {
157
+ copyFileSync(existingSkillPath, destSkillMdPath);
158
+ }
183
159
  }
184
160
  else {
185
161
  writeFileSync(join(stagingDir, "SKILL.md"), compositeSkillMd, "utf-8");
@@ -188,19 +164,11 @@ export async function exportCompositeImpl(options, exportSubpackage) {
188
164
  dependencyStepLabel: "安装复合技能聚合依赖",
189
165
  relinkStepLabel: "完成安装后重新链接复合技能",
190
166
  compositeLinkNote: true,
191
- invocationStyle: "source-ad",
192
- entryRelPath: 'node "<skill_root>/entry.mjs"',
167
+ invocationStyle: "global-ad",
193
168
  });
194
169
  }
195
- if (packagesDestDir) {
196
- assertUniqueWorkspacePackageNames(packagesDestDir);
197
- }
198
- // npm workspaces 保留各包依赖约束,根目录仅声明入口运行依赖。
199
- writeCompositePkgJson(stagingDir, bundleSlug, options);
200
- const entryMetadata = { packageId: options.bundleName, version: "0.1.0", description };
201
- writeFileSync(join(stagingDir, "entry.mjs"), generateNodeSupervisorEntrySource(entryMetadata, { multiPackage: true }), "utf-8");
202
- // Host 生成仍复用单包生成器,但源码包元数据只从自身相对路径读取。
203
- writeFileSync(join(stagingDir, "entry-host.js"), generateNodeHostEntrySource(undefined, { localPackages }), "utf-8");
170
+ // 聚合所有子包依赖生成复合根目录 package.json
171
+ writeCompositePkgJson(stagingDir, packagesDestDir, packageInfos, bundleSlug, options);
204
172
  // 原子替换目标复合技能目录
205
173
  await replaceDirAtomic(stagingDir, targetSkillDir);
206
174
  }
@@ -227,77 +195,35 @@ export async function exportCompositeImpl(options, exportSubpackage) {
227
195
  };
228
196
  }
229
197
  /**
230
- * 复合根目录声明入口运行依赖,业务依赖保留在各工作区包中,由 npm 安装解析。
198
+ * 复合套件聚合各子包依赖并生成复合根目录 package.json。
199
+ * 依赖清洗统一复用 manifest 的 sanitizeExportDependencies 单一入口,与单包导出口径一致。
231
200
  */
232
- function writeCompositePkgJson(stagingDir, bundleSlug, options) {
233
- // 子包保留各自依赖约束,由 npm workspaces 正常安装与多版本解析,不聚合覆盖。
234
- const runtimeDeps = {
235
- "@actiondock/core": getInternalDependencyVersion(),
201
+ function writeCompositePkgJson(stagingDir, packagesDestDir, packageInfos, bundleSlug, options) {
202
+ const aggregatedDeps = {
236
203
  "@actiondock/sdk": getInternalDependencyVersion(),
237
204
  };
205
+ for (const info of packageInfos) {
206
+ const pkgJsonPath = join(packagesDestDir, info.packageDir, "package.json");
207
+ if (existsSync(pkgJsonPath)) {
208
+ try {
209
+ const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
210
+ Object.assign(aggregatedDeps, sanitizeExportDependencies(join(packagesDestDir, info.packageDir), parsed.dependencies, "exported skill packages", false));
211
+ }
212
+ catch (err) {
213
+ if (err instanceof BuilderError)
214
+ throw err;
215
+ // 复合导出缺依赖影响面大:读文件与解析失败必须显式报错,严禁静默吞掉导致聚合依赖缺失
216
+ throw new BuilderError(`Failed to read or parse package.json of exported subpackage '${info.packageDir}' (${pkgJsonPath}): ${err instanceof Error ? err.message : String(err)}`);
217
+ }
218
+ }
219
+ }
238
220
  const compositePkg = {
239
221
  name: `${bundleSlug}-composite-skill`,
240
222
  version: "0.1.0",
241
223
  description: options.description || `Composite Skill suite: ${options.bundleName}`,
242
224
  type: "module",
243
225
  workspaces: ["packages/*"],
244
- dependencies: runtimeDeps,
226
+ dependencies: aggregatedDeps,
245
227
  };
246
228
  writeFileSync(join(stagingDir, "package.json"), JSON.stringify(compositePkg, null, 2) + "\n", "utf-8");
247
229
  }
248
- /**
249
- * 规范化包目录标识为合法 npm 包名(小写字母、数字、连字符、下划线与点号)。
250
- */
251
- function normalizeNpmPackageName(name) {
252
- const normalized = name
253
- .trim()
254
- .toLowerCase()
255
- .replace(/[^a-z0-9-_.]/g, "-")
256
- .replace(/^[._]+/, "");
257
- return normalized || "package";
258
- }
259
- /** 工作区清单读取失败必须透明报告,只有缺省 name 才允许自动分配。 */
260
- function readWorkspacePackageName(path) {
261
- const raw = readFileSync(path, "utf-8");
262
- let parsed;
263
- try {
264
- parsed = JSON.parse(raw);
265
- }
266
- catch (err) {
267
- throw new BuilderError(`Invalid package.json at '${path}': ${String(err)}`, "INVALID_PACKAGE_JSON");
268
- }
269
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
270
- throw new BuilderError(`Invalid package.json at '${path}': expected an object.`, "INVALID_PACKAGE_JSON");
271
- }
272
- const name = parsed.name;
273
- if (name === undefined || name === "")
274
- return undefined;
275
- if (typeof name !== "string" || !name.trim() || name !== name.trim()) {
276
- throw new BuilderError(`Invalid npm workspace package name at '${path}'.`, "INVALID_PACKAGE_JSON");
277
- }
278
- return name;
279
- }
280
- /**
281
- * 校验复合暂存目录中所有子包导出的 package.json.name 唯一性。
282
- * 若已有真实 npm 名确实冲突,在发布产物前明确抛出 BuilderError。
283
- */
284
- function assertUniqueWorkspacePackageNames(packagesDir) {
285
- const seenNames = new Map();
286
- const entries = readdirSync(packagesDir, { withFileTypes: true });
287
- for (const entry of entries) {
288
- if (!entry.isDirectory())
289
- continue;
290
- const pkgJsonPath = join(packagesDir, entry.name, "package.json");
291
- if (!existsSync(pkgJsonPath))
292
- continue;
293
- const name = readWorkspacePackageName(pkgJsonPath);
294
- if (!name) {
295
- throw new BuilderError(`Missing npm workspace package name at '${pkgJsonPath}'.`, "INVALID_PACKAGE_JSON");
296
- }
297
- const existing = seenNames.get(name);
298
- if (existing) {
299
- throw new BuilderError(`Duplicate npm workspace package name '${name}' found in packages/${existing} and packages/${entry.name}.`, "DUPLICATE_WORKSPACE_PACKAGE_NAME");
300
- }
301
- seenNames.set(name, entry.name);
302
- }
303
- }
@@ -1,11 +1,8 @@
1
1
  import { copyFileSync, existsSync, readFileSync, writeFileSync, } from "node:fs";
2
2
  import { basename, dirname, join } from "node:path";
3
3
  import { mkdirSync } from "node:fs";
4
- import { generateNodeHostEntrySource, generateNodeSupervisorEntrySource } from "./entry-generators.js";
5
- import { appendLocalExecutionContract } from "./skill-md.js";
6
4
  import { generateSourceSkillMd } from "./skill/index.js";
7
5
  import { RUNTIME_REFERENCE_REL_PATH, renderRuntimeReferenceContent, } from "./skill/templates.js";
8
- import { loadManifest } from "@actiondock/core/project";
9
6
  import { BuilderError } from "./errors.js";
10
7
  import { getInternalDependencyVersion } from "./fs-utils.js";
11
8
  import { assertValidManifestActionIds, sanitizeExportDependencies, serializePlanManifest, } from "./manifest.js";
@@ -24,24 +21,18 @@ export function stageSourceSkill(root, skillDir, plan, options, pkgSlug) {
24
21
  const actionsDir = plan.actionsDir || "actions";
25
22
  const playbooksDir = plan.playbooksDir || "playbooks";
26
23
  const configForTemplates = buildConfigForTemplates(plan, actionsDir, playbooksDir);
27
- const usedExistingSkillMd = writeSkillMd(root, skillDir, { skipSkillMd: options.skipSkillMd, skillMdPath: options.skillMdPath, pkgSlug }, () => generateSourceSkillMd(configForTemplates, plan.actions.filter((a) => !a.isExternal), plan.playbooks));
24
+ const usedExistingSkillMd = writeSkillMd(root, skillDir, { skipSkillMd: options.skipSkillMd, skillMdPath: options.skillMdPath, pkgSlug }, () => generateSourceSkillMd(configForTemplates, plan.actions, plan.playbooks));
28
25
  // 导出精简后的 actiondock.json 项目配置(项目元数据与清单的单一事实源)
29
26
  const exportedConfig = serializePlanManifest(plan, {
30
27
  omitEmptyConfig: true,
31
28
  includeDirs: { actionsDir, playbooksDir },
32
29
  });
33
- const originalManifest = options.manifest || loadManifest(root);
34
- if (originalManifest?.dependencies)
35
- exportedConfig.dependencies = originalManifest.dependencies;
36
- const packageUses = originalManifest?.uses;
37
- if (packageUses)
38
- exportedConfig.uses = packageUses;
39
30
  if (exportedConfig.actions && typeof exportedConfig.actions === "object") {
40
31
  assertValidManifestActionIds(exportedConfig.actions);
41
32
  }
42
33
  writeFileSync(join(skillDir, "actiondock.json"), JSON.stringify(exportedConfig, null, 2) + "\n", "utf-8");
43
34
  // 导出 package.json
44
- writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug, options);
35
+ writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug);
45
36
  // 拷贝 tsconfig.json(若存在)
46
37
  const tsconfigPath = join(root, "tsconfig.json");
47
38
  if (existsSync(tsconfigPath)) {
@@ -51,27 +42,13 @@ export function stageSourceSkill(root, skillDir, plan, options, pkgSlug) {
51
42
  copyPlanEntries(skillDir, plan, {
52
43
  playbookRelPath: (pb) => join(playbooksDir, basename(pb.filePath)),
53
44
  });
54
- if (!options._isSubpackage) {
55
- for (const name of ["entry.mjs", "entry-host.js"]) {
56
- if (existsSync(join(skillDir, name))) {
57
- throw new BuilderError(`Source Skill entry conflict: '${name}' is reserved for the local runtime entry; refusing to overwrite a declared file.`, "SOURCE_ENTRY_CONFLICT");
58
- }
59
- }
60
- writeFileSync(join(skillDir, "entry.mjs"), generateNodeSupervisorEntrySource(plan), "utf-8");
61
- writeFileSync(join(skillDir, "entry-host.js"), generateNodeHostEntrySource(undefined, {
62
- localPackages: [{ packageId: plan.packageId, relativeRoot: "." }],
63
- }), "utf-8");
64
- if (usedExistingSkillMd)
65
- appendLocalExecutionContract(join(skillDir, "SKILL.md"), [plan]);
66
- }
67
45
  // 声明资产拷贝完成后再物化默认运行参考文件:仅在使用自动模板时生成;
68
46
  // 与项目声明资产同路径冲突时报错不覆盖(拷贝在前,冲突检测能真实命中)
69
47
  if (usedExistingSkillMd === undefined && !options.skipSkillMd) {
70
48
  writeRuntimeReferenceFile(skillDir, {
71
49
  dependencyStepLabel: "安装技能源码依赖",
72
50
  relinkStepLabel: "完成安装后重新链接本技能",
73
- invocationStyle: "source-ad",
74
- entryRelPath: 'node "<skill_root>/entry.mjs"',
51
+ invocationStyle: "global-ad",
75
52
  });
76
53
  }
77
54
  return usedExistingSkillMd;
@@ -88,16 +65,15 @@ export function writeRuntimeReferenceFile(destDir, options) {
88
65
  mkdirSync(dirname(referencePath), { recursive: true });
89
66
  writeFileSync(referencePath, renderRuntimeReferenceContent(options), "utf-8");
90
67
  }
91
- function writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug, options) {
68
+ function writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug) {
92
69
  let exportedPkg;
93
- const fallbackName = options?.defaultPackageName || pkgSlug;
94
70
  const projectPkgPath = join(root, "package.json");
95
71
  if (existsSync(projectPkgPath)) {
96
72
  try {
97
73
  const raw = readFileSync(projectPkgPath, "utf-8");
98
74
  const parsed = JSON.parse(raw);
99
75
  exportedPkg = {
100
- name: parsed.name || fallbackName,
76
+ name: parsed.name || pkgSlug,
101
77
  version: plan.version || parsed.version || "0.1.0",
102
78
  description: plan.description || parsed.description,
103
79
  type: "module",
@@ -108,13 +84,12 @@ function writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug, options) {
108
84
  if (err instanceof BuilderError) {
109
85
  throw err;
110
86
  }
111
- exportedPkg = fallbackSkillPkg(plan, fallbackName);
87
+ exportedPkg = fallbackSkillPkg(plan, pkgSlug);
112
88
  }
113
89
  }
114
90
  else {
115
- exportedPkg = fallbackSkillPkg(plan, fallbackName);
91
+ exportedPkg = fallbackSkillPkg(plan, pkgSlug);
116
92
  }
117
- exportedPkg.dependencies["@actiondock/core"] ??= getInternalDependencyVersion();
118
93
  writeFileSync(join(skillDir, "package.json"), JSON.stringify(exportedPkg, null, 2) + "\n", "utf-8");
119
94
  }
120
95
  /** 无 package.json 或解析失败时的兜底导出描述 */
package/dist/exporter.js CHANGED
@@ -1,11 +1,9 @@
1
1
  import { existsSync, mkdirSync, rmSync } from "node:fs";
2
2
  import { basename, dirname, join, resolve } from "node:path";
3
- import { getPackageSlug, loadManifest } from "@actiondock/core/project";
3
+ import { getPackageSlug } from "@actiondock/core/project";
4
4
  import { BuilderError } from "./errors.js";
5
5
  import { replaceDirAtomic } from "./fs-utils.js";
6
6
  import { collectRelativeFiles } from "./fs-utils.js";
7
- import { createSourcePackageGraph } from "./source-graph.js";
8
- import { deriveEffectivePackageGraph } from "./derived-graph.js";
9
7
  import { SelectionPlanner } from "./planner.js";
10
8
  import { isOwnAction } from "./types.js";
11
9
  import { createArchive, resolveArchiveFormat } from "./export-archive.js";
@@ -28,35 +26,8 @@ export class SkillExporter {
28
26
  async export(options) {
29
27
  const root = resolve(options.projectRoot);
30
28
  const mode = options.mode === "node" ? "node" : "source";
31
- // 共享图、规划与源码清单使用同一有效声明;显式 manifest 不从磁盘继承依赖。
32
- if (mode === "source" && (options.config || options.manifest)) {
33
- const graphNode = options.packageGraph && Array.from(options.packageGraph.packages.values()).find((p) => resolve(p.root) === root);
34
- const baseManifest = options.manifest || graphNode?.manifest || loadManifest(root) || {};
35
- const manifest = {
36
- ...baseManifest,
37
- ...options.config,
38
- ...options.manifest,
39
- // 保持既有 config 元数据优先级;动作、dependencies、uses 使用有效清单声明。
40
- ...Object.fromEntries([
41
- "id", "name", "version", "description", "config", "actionsDir", "playbooksDir",
42
- ].map((key) => [key, options.config?.[key]]).filter(([, value]) => value !== undefined)),
43
- };
44
- options = {
45
- ...options,
46
- manifest,
47
- config: { ...manifest, name: manifest.name || manifest.id, version: manifest.version || "0.1.0" },
48
- };
49
- }
50
29
  // 调用 SelectionPlanner 执行声明式构建规划
51
- const packageGraph = mode === "source" && options.packageGraph
52
- ? deriveEffectivePackageGraph(options.packageGraph, root, options.manifest)
53
- : options.packageGraph ?? (mode === "source" ? createSourcePackageGraph([root], options.manifest) : undefined);
54
- options = {
55
- ...options,
56
- packageGraph,
57
- };
58
30
  const plan = SelectionPlanner.plan({
59
- packageGraph,
60
31
  projectRoot: root,
61
32
  config: options.config,
62
33
  manifest: options.manifest,
@@ -68,7 +39,7 @@ export class SkillExporter {
68
39
  const defaultFolderName = `${pkgSlug}-skill`;
69
40
  const defaultSkillDir = join(root, "dist", defaultFolderName);
70
41
  const targetSkillDir = resolve(options.outDir || defaultSkillDir);
71
- const hasExternalDeps = Boolean((packageGraph && packageGraph.packages.size > 1) || (plan.externalProjectRoots && plan.externalProjectRoots.length > 0));
42
+ const hasExternalDeps = Boolean(plan.externalProjectRoots && plan.externalProjectRoots.length > 0);
72
43
  // 单包源码导出若依赖闭包含外部包,导成 mini-workspace 形态(packages/ 下带齐依赖包),和 bundle 统一成一个交付形状
73
44
  // 该复合分支会展开全部依赖包,无法保留调用方传入的 actions/playbooks 过滤选项;
74
45
  // 静默丢弃过滤会导致产物全量导出,必须显式拒绝并给出调整指引(CLI 侧已有 roots>1 守卫,此处补齐 composite 分支守卫)
@@ -79,9 +50,8 @@ export class SkillExporter {
79
50
  throw new BuilderError("Filtering options (--actions, --playbook) cannot be combined with a source export whose dependency closure spans external linked packages (composite mini-workspace output). Remove the filtering options to export the full closure, or adjust the 'uses' declarations so the closure stays within a single package.", "FILTERS_UNSUPPORTED_FOR_COMPOSITE");
80
51
  }
81
52
  if (willUseComposite) {
82
- const allProjectRoots = Array.from(new Set([root, ...Array.from(packageGraph.packages.values(), (p) => p.root)]));
53
+ const allProjectRoots = Array.from(new Set([root, ...plan.externalProjectRoots]));
83
54
  const compositeRes = await this.exportComposite({
84
- packageGraph,
85
55
  bundleName: plan.packageName || plan.packageId,
86
56
  projectRoots: allProjectRoots,
87
57
  outDir: targetSkillDir,
package/dist/planner.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { basename, join, relative, resolve } from "node:path";
3
- import { ActionDockError, ACTION_NOT_FOUND, loadProjectConfig, } from "@actiondock/core";
3
+ import { ActionDockError, ACTION_NOT_FOUND, UNDECLARED_ACTION_DEPENDENCY, loadProjectConfig, } from "@actiondock/core";
4
4
  import { assertPathWithinRoot, traverseDirectory, loadManifest, loadPlaybooks, validateManifest, } from "@actiondock/core/project";
5
5
  import { DefaultActionCatalog, PackageGraphBuilder, resolveAction, } from "@actiondock/core/graph";
6
6
  import { PlannerError } from "./errors.js";
@@ -150,16 +150,13 @@ export class SelectionPlanner {
150
150
  selectedPlaybooks = Array.from(playbooksMap.values());
151
151
  }
152
152
  const manifestActions = manifest.actions || {};
153
- // 若显式指定了 Action 列表,加入集合(保持本包短名与完全限定名解析契约)
153
+ // 若显式指定了 Action 列表,加入集合
154
154
  if (options?.actions && options.actions.length > 0) {
155
155
  for (const actId of options.actions) {
156
- const localId = actId.startsWith(`${config.id}/`)
157
- ? actId.slice(config.id.length + 1)
158
- : actId;
159
- if (!manifestActions[localId] && !manifestActions[actId]) {
156
+ if (!manifestActions[actId]) {
160
157
  throw new PlannerError(`Action '${actId}' specified in build options was not found in manifest`, "ACTION_NOT_FOUND");
161
158
  }
162
- initialActionIds.add(manifestActions[localId] ? localId : actId);
159
+ initialActionIds.add(actId);
163
160
  }
164
161
  }
165
162
  // 若均未显式指定,默认包含清单中的全部 Action
@@ -168,87 +165,94 @@ export class SelectionPlanner {
168
165
  initialActionIds.add(actId);
169
166
  }
170
167
  }
168
+ // 当前包在 actiondock.json 中声明的顶层 uses 直接依赖
169
+ const currentPkgUses = config?.uses;
170
+ if (Array.isArray(currentPkgUses)) {
171
+ for (const depId of currentPkgUses) {
172
+ if (typeof depId === "string" && depId.trim()) {
173
+ initialActionIds.add(depId.trim());
174
+ }
175
+ }
176
+ }
171
177
  return { initialActionIds, selectedPlaybooks };
172
178
  }
173
179
  /**
174
180
  * 进行依赖闭包(uses)解析计算,自动处理环形依赖与传递依赖(支持当前清单与已链接外部包)。
175
181
  */
176
- resolveClosure(root, manifest, config, initialActionIds, packageGraph) {
182
+ resolveClosure(root, manifest, config, initialActionIds) {
177
183
  const resolvedActionIds = new Set();
184
+ // queued 与 resolvedActionIds 联合判重,避免高入度节点在 uses 图中被重复入队导致 O(边数) 重复解析
185
+ const queued = new Set(initialActionIds);
186
+ const queue = Array.from(initialActionIds);
178
187
  const externalActionRoots = new Map();
179
188
  const externalActionEntries = new Map();
180
- const graph = packageGraph ?? new PackageGraphBuilder({ root, manifest, allowDevLinks: true }).buildSync();
189
+ const manifestActions = manifest.actions || {};
190
+ const graph = new PackageGraphBuilder({ root, allowDevLinks: true }).buildSync();
181
191
  const catalog = new DefaultActionCatalog(graph);
182
- const queue = [];
183
- const visitedPackages = new Set();
184
- const expandedPackages = new Set();
185
- for (const id of initialActionIds) {
186
- queue.push({ id, caller: config.id, isInitialAction: true });
187
- }
188
- const rawRootUses = manifest.uses ?? config.uses ?? [];
189
- visitedPackages.add(config.id);
190
- for (const use of rawRootUses) {
191
- if (typeof use === "string" && use.trim()) {
192
- queue.push({ id: use.trim(), caller: config.id, isInitialAction: false });
193
- }
194
- }
195
192
  while (queue.length > 0) {
196
- const { id, caller, isInitialAction } = queue.shift();
197
- // uses 支持包名与包级通配符;将其展开为该包的完整动作集合。
198
- // 明确选中的初始动作不按包名展开;uses 中的裸包名和通配符仍按包级引用处理。
199
- const isWildcard = id.endsWith("/*");
200
- const packageId = isWildcard
201
- ? id.slice(0, -2)
202
- : !isInitialAction && graph.hasPackage(id)
203
- ? id
204
- : undefined;
205
- if (packageId) {
206
- const node = graph.getPackage(packageId);
207
- if (!node)
208
- throw new PlannerError(`Package '${packageId}' referenced by uses was not found`, "PACKAGE_NOT_FOUND");
209
- if (expandedPackages.has(packageId))
210
- continue;
211
- expandedPackages.add(packageId);
212
- if (!visitedPackages.has(packageId)) {
213
- visitedPackages.add(packageId);
214
- for (const use of node.manifest.uses || []) {
215
- queue.push({ id: use, caller: packageId, isInitialAction: false });
193
+ const currentId = queue.shift();
194
+ queued.delete(currentId);
195
+ if (resolvedActionIds.has(currentId)) {
196
+ continue;
197
+ }
198
+ let entry = manifestActions[currentId] || config?.actions?.[currentId];
199
+ let entryRoot = root;
200
+ if (!entry) {
201
+ const resolved = resolveAction(currentId, { graph, catalog, caller: config?.id });
202
+ const resolvedPkgId = resolved.package.id;
203
+ const resolvedActionId = resolved.ref.actionId;
204
+ const extNode = graph.packages.get(resolvedPkgId);
205
+ if (extNode && existsSync(extNode.root)) {
206
+ const extRoot = extNode.root;
207
+ let extConfig;
208
+ try {
209
+ extConfig = loadProjectConfig(extRoot);
210
+ }
211
+ catch {
212
+ extConfig = {
213
+ id: resolvedPkgId,
214
+ name: resolvedPkgId,
215
+ version: "0.1.0",
216
+ };
217
+ }
218
+ const externalManifest = loadManifest(extRoot);
219
+ const externalEntry = externalManifest?.actions?.[resolvedActionId] ||
220
+ extConfig?.actions?.[resolvedActionId];
221
+ if (!externalEntry) {
222
+ throw new ActionDockError(UNDECLARED_ACTION_DEPENDENCY, `UNDECLARED_ACTION_DEPENDENCY: Action '${resolvedActionId}' is not declared in package '${resolvedPkgId}' manifest`);
223
+ }
224
+ entry = externalEntry;
225
+ entryRoot = extRoot;
226
+ // 依赖包 actiondock.json 中声明的传递依赖闭包(入队前查重,避免重复膨胀)
227
+ const extPkgUses = extConfig?.uses;
228
+ if (Array.isArray(extPkgUses)) {
229
+ for (const u of extPkgUses) {
230
+ const trimmedUse = typeof u === "string" ? u.trim() : "";
231
+ if (trimmedUse &&
232
+ !resolvedActionIds.has(trimmedUse) &&
233
+ !queued.has(trimmedUse)) {
234
+ queued.add(trimmedUse);
235
+ queue.push(trimmedUse);
236
+ }
237
+ }
216
238
  }
217
239
  }
218
- for (const actionId of Object.keys(node.manifest.actions || {})) {
219
- queue.push({ id: `${packageId}/${actionId}`, caller: packageId, isInitialAction: false });
220
- }
221
- continue;
222
240
  }
223
- // 保持显式 config/manifest 的单包规划语义,不让磁盘图覆盖调用方清单。
224
- const localId = id.startsWith(`${config.id}/`) ? id.slice(config.id.length + 1) : id;
225
- const isLocal = (caller === config.id || id.startsWith(`${config.id}/`)) &&
226
- Boolean(manifest.actions?.[localId] || config.actions?.[localId]);
227
- const resolved = isLocal ? undefined : resolveAction(id, { graph, catalog, caller });
228
- const pkgId = resolved?.package.id || config.id;
229
- const actionId = resolved?.ref.actionId || localId;
230
- const node = graph.getPackage(pkgId);
231
- const canonicalId = pkgId === config.id ? actionId : `${pkgId}/${actionId}`;
232
- if (resolvedActionIds.has(canonicalId))
233
- continue;
234
- const entry = pkgId === config.id
235
- ? manifest.actions?.[actionId] || config.actions?.[actionId]
236
- : node.manifest.actions?.[actionId];
237
- if (!entry)
238
- throw new ActionDockError(ACTION_NOT_FOUND, `Action '${id}' is not declared in manifest`);
239
- resolvedActionIds.add(canonicalId);
240
- if (pkgId !== config.id) {
241
- externalActionRoots.set(canonicalId, node.root);
242
- externalActionEntries.set(canonicalId, entry);
241
+ if (!entry) {
242
+ throw new ActionDockError(ACTION_NOT_FOUND, `Action '${currentId}' referenced in dependency closure (uses) was not found in manifest or linked packages`);
243
243
  }
244
- if (!visitedPackages.has(pkgId)) {
245
- visitedPackages.add(pkgId);
246
- for (const use of node?.manifest.uses || []) {
247
- queue.push({ id: use, caller: pkgId, isInitialAction: false });
248
- }
244
+ if (entryRoot !== root) {
245
+ externalActionRoots.set(currentId, entryRoot);
246
+ externalActionEntries.set(currentId, entry);
249
247
  }
250
- for (const use of entry.uses || []) {
251
- queue.push({ id: use, caller: pkgId, isInitialAction: false });
248
+ resolvedActionIds.add(currentId);
249
+ if (entry.uses && Array.isArray(entry.uses)) {
250
+ for (const depId of entry.uses) {
251
+ if (!resolvedActionIds.has(depId) && !queued.has(depId)) {
252
+ queued.add(depId);
253
+ queue.push(depId);
254
+ }
255
+ }
252
256
  }
253
257
  }
254
258
  return { resolvedActionIds, externalActionRoots, externalActionEntries };
@@ -447,7 +451,7 @@ export class SelectionPlanner {
447
451
  const playbooksMap = this.collectPlaybooks(root, config, manifest);
448
452
  const { initialActionIds, selectedPlaybooks } = this.resolveInitialSelection(options, manifest, config, playbooksMap);
449
453
  // 依赖闭包解析与 Action 依赖结构物化
450
- const closure = this.resolveClosure(root, manifest, config, initialActionIds, options?.packageGraph);
454
+ const closure = this.resolveClosure(root, manifest, config, initialActionIds);
451
455
  let finalPlaybooks = selectedPlaybooks;
452
456
  // 若指定了 Action 但未指定 Playbook,反向裁剪排除无法满足依赖的 Playbook
453
457
  if (options?.actions && options.actions.length > 0 && (!options.playbooks || options.playbooks.length === 0)) {
@@ -26,9 +26,9 @@ export declare function renderRuntimeReferenceContent(options: {
26
26
  relinkStepLabel: string;
27
27
  /** 是否在重新链接步骤末尾附加复合链接语义说明 */
28
28
  compositeLinkNote?: boolean;
29
- /** 调用方式说明:源码型(source-ad)、Node 目录型(node-entry)与开发全局模式(global-ad) */
30
- invocationStyle: "global-ad" | "source-ad" | "node-entry";
31
- /** Node 目录型的入口相对路径(invocationStyle 为 node-entry 或 source-ad 时使用) */
29
+ /** 调用方式说明:源码型与 Node 目录型分化(如是否需要全局 ad) */
30
+ invocationStyle: "global-ad" | "node-entry";
31
+ /** Node 目录型的入口相对路径(invocationStyle 为 node-entry 时使用) */
32
32
  entryRelPath?: string;
33
33
  }): string;
34
34
  /** 运行时参考文件的导出目录内相对路径(单一事实源) */
@@ -54,8 +54,6 @@ export interface CompositeSkillPackageInfo {
54
54
  */
55
55
  export declare function generateCompositeSkillMd(bundleName: string, description: string, packages: CompositeSkillPackageInfo[], options?: {
56
56
  customSections?: CompositeCustomSection[];
57
- /** 调用方式:开发全局模式(global-ad)、源码 Skill(source-ad)与独立 Node 目录型(node-entry) */
58
- invocationStyle?: "global-ad" | "source-ad" | "node-entry";
59
57
  /** 规程链接的子包目录前缀:导出布局为 "packages"(默认);就地生成时传 "." 使用工作区实际目录 */
60
58
  packagesBaseDir?: string;
61
59
  }): string;
@@ -13,7 +13,7 @@ function renderActionListMarkdown(actions, options = {}) {
13
13
  .map((a) => {
14
14
  const aDesc = a.description ? ` - ${a.description}` : "";
15
15
  const idLabel = options.packageId
16
- ? `\`${options.packageId}/${a.id}\``
16
+ ? `\`${options.packageId}/${a.id}\` (或 \`${a.id}\`)`
17
17
  : `\`${a.id}\``;
18
18
  const lines = [`- ${idLabel}${aDesc}`];
19
19
  // 标注元数据解析(安全标注不得从主说明移走)
@@ -124,60 +124,30 @@ function renderJsonEnvelopeSection(wording, options) {
124
124
  * 由主说明按需链接调阅,不在每个技能正文重复展开整套安装与框架管理教程。
125
125
  */
126
126
  export function renderRuntimeReferenceContent(options) {
127
- if (options.invocationStyle === "node-entry") {
128
- const entry = options.entryRelPath || "node ./entry.mjs";
129
- return `# ActionDock 运行时环境参考
130
-
131
- 本技能为独立 Node.js 运行目录交付,直接使用随附的 \`${entry}\` 入口执行,不要求全局安装 ActionDock 命令行工具,不读取 Registry 或当前工作目录项目。
132
-
133
- ## 调用方式
134
-
135
- \`\`\`bash
136
- ${entry} list
137
- ${entry} describe <package-id>/<action-id>
138
- ${entry} run <package-id>/<action-id> -- ASSIGNMENT...
139
- \`\`\`
140
-
141
- ## 按需排查与依赖安装
142
-
143
- 正常执行直接调用入口,不做前置体检。仅在模块缺失或运行环境报错时修复:
144
-
145
- - Node.js 版本要求大于等于 24.12.0。
146
- - ${options.dependencyStepLabel}:在技能根目录执行 \`npm install --omit=dev\`,由 npm 安装生产依赖。
147
- - 默认共享统一数据存储。仅在需要数据隔离等特殊场景下追加 \`--data-dir <path>\`。
148
- - 验证就绪:执行 \`${entry} list\`。
149
- `;
150
- }
151
- if (options.invocationStyle === "source-ad") {
152
- return `# ActionDock 运行时环境参考
153
-
154
- 本技能通过 \`ad ... -P "<skill_root>"\` 执行,封闭加载本地包集合,不读取全局 Registry 或当前工作目录项目。
155
-
156
- ## 调用方式
127
+ const relinkNote = options.compositeLinkNote
128
+ ? `\`ad link\` 会自动识别并注册工作区下的所有子包,使其中的 Action 随时可以通过完全限定标识调用。`
129
+ : "";
130
+ const invocationSection = options.invocationStyle === "node-entry"
131
+ ? `本技能为 Node 目录型交付,可直接使用自身入口执行,不要求全局安装 \`ad\` 命令行工具:
157
132
 
158
133
  \`\`\`bash
159
- ad list -P "<skill_root>"
160
- ad describe <package-id>/<action-id> -P "<skill_root>"
161
- ad run <package-id>/<action-id> -P "<skill_root>" -- ASSIGNMENT...
134
+ ${options.entryRelPath || "node ./entry.mjs"} list
135
+ ${options.entryRelPath || "node ./entry.mjs"} describe <action-id>
136
+ ${options.entryRelPath || "node ./entry.mjs"} run <action-id> -- ASSIGNMENT...
162
137
  \`\`\`
163
138
 
164
- 本地封闭执行不支持 \`--async\`。默认共享统一数据存储,仅在需要数据隔离等特殊场景下追加 \`--data-dir <path>\`。
165
-
166
- ## 按需排查与依赖安装
167
-
168
- 正常执行直接调用入口,不做前置体检。仅在模块缺失或运行环境报错时修复:
169
-
170
- - Node.js 版本要求大于等于 24.12.0。
171
- - ${options.dependencyStepLabel}:在技能根目录执行 \`npm install --omit=dev\`,由 npm 安装生产依赖。
172
- - 验证就绪:执行 \`ad list -P "<skill_root>"\`。
173
- `;
174
- }
175
- const relinkNote = options.compositeLinkNote
176
- ? `\`ad link\` 会自动识别并注册工作区下的所有子包,使其中的 Action 随时可以通过完全限定标识调用。`
177
- : "";
178
- const invocationSection = `本说明书用于开发工作区,通过全局安装的 \`ad\` 命令行工具执行,不代表完整技能导出。`;
179
- const probePrinciple = `默认宿主环境中已预置 \`ad\` 命令行工具与 Node.js 运行环境。正常执行流程直接调用上述 Action 即可,严禁在任务启动前盲目进行前置环境检查或体检;仅在终端明确报错提示命令不存在或依赖缺失时,方可按本文指引安装初始化。`;
180
- const cliInstallSection = `- **环境要求**:Node.js 版本大于等于 24.12.0(执行 \`node -v\` 确认)。
139
+ 若宿主环境已全局安装 \`ad\`,亦可将其链接后统一调度。`
140
+ : `本技能为源码型交付,通过全局安装的 \`ad\` 命令行工具执行。`;
141
+ const isNodeEntry = options.invocationStyle === "node-entry";
142
+ const probePrinciple = isNodeEntry
143
+ ? `默认宿主环境中已预置 Node.js 运行环境。本目录型交付直接使用自身入口执行,不依赖全局 \`ad\`;正常执行流程直接调用上述 Action 即可,严禁在任务启动前盲目进行前置环境检查或体检;仅在入口报错提示模块缺失(如 \`Cannot find package\`)或 Node.js 版本不满足时,方可按本文指引修复。`
144
+ : `默认宿主环境中已预置 \`ad\` 命令行工具与 Node.js 运行环境。正常执行流程直接调用上述 Action 即可,严禁在任务启动前盲目进行前置环境检查或体检;仅在终端明确报错提示命令不存在(如 \`ad: command not found\`)或提示依赖缺失时,方可按本文指引安装初始化。`;
145
+ const cliInstallSection = isNodeEntry
146
+ ? `- **Node.js 环境要求**:Node.js 版本大于等于 24.12.0(执行 \`node -v\` 确认)。目录型交付不需要全局安装 \`ad\`;若需要接入全局调度,可另行安装:
147
+ \`\`\`bash
148
+ npm install -g @actiondock/cli
149
+ \`\`\``
150
+ : `- **环境要求**:Node.js 版本大于等于 24.12.0(执行 \`node -v\` 确认)。
181
151
  - **全局安装 ActionDock 命令行工具**:
182
152
  \`\`\`bash
183
153
  npm install -g @actiondock/cli
@@ -205,7 +175,7 @@ ${cliInstallSection}
205
175
  cd "<skill_root>" && npm install --omit=dev
206
176
  \`\`\`
207
177
  - **验证就绪**:
208
- ad --version
178
+ ${isNodeEntry ? `${options.entryRelPath || "node ./entry.mjs"} list` : "ad --version"}
209
179
  - **环境诊断与体检**(仅全局接入 \`ad\` 后适用):
210
180
  安装完成后若仍遇到异常,执行体检命令排查:
211
181
  \`\`\`bash
@@ -234,9 +204,10 @@ function renderTroubleshootingSection(referenceRelPath) {
234
204
  export const RUNTIME_REFERENCE_REL_PATH = "references/actiondock-runtime.md";
235
205
  export function generateSourceSkillMd(config, actions, playbooks) {
236
206
  const { cleanName, desc } = getCleanSkillMetadata(config);
237
- const firstAction = `${config.id}/${actions[0]?.id || "sample.greet"}`;
238
- const defaultDescribe = `ad describe ${firstAction} -P "<skill_root>"`;
239
- const defaultRun = `ad run ${firstAction} -P "<skill_root>"`;
207
+ const pkgId = config.id;
208
+ const firstAction = actions[0]?.id || "sample.greet";
209
+ const playbookSection = renderPlaybookSectionMarkdown(playbooks, config.playbooksDir || "playbooks");
210
+ const actionListMd = renderActionListMarkdown(actions, { packageId: pkgId });
240
211
  return `---
241
212
  name: ${cleanName}
242
213
  description: ${desc}
@@ -246,32 +217,56 @@ description: ${desc}
246
217
 
247
218
  ${desc}
248
219
 
249
- ## 本地执行
220
+ ## ActionDock 运行时
221
+
222
+ 本技能为 **ActionDock 源码型技能包**。智能体可直接通过宿主环境中已安装的 ActionDock 命令行工具 \`ad\` 执行其中的 Action。
223
+
224
+ ### 注册与链接(全局调用)
225
+
226
+ 本技能面向全局挂载使用:在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root>\` 并完成注册链接,之后可在任意工作目录通过完全限定 ID 全局调用:
227
+
228
+ \`\`\`bash
229
+ ad link "<skill_root>"
230
+ \`\`\`
231
+
232
+ > \`ad link\` 天然具备幂等性,同一 Package 多次执行会直接更新路径,可安全重复调用。若初次运行提示依赖缺失,可在 \`<skill_root>\` 目录下执行 \`npm install --omit=dev\` 安装生产依赖。
250
233
 
251
- 将本文件所在目录解析为 \`<skill_root>\`。默认通过 \`ad ... -P "<skill_root>"\` 执行,只加载随附的确定版本包,不读取 Registry 或当前工作目录项目,不需要 \`ad link\`。
252
- 首次缺少依赖时,在技能根目录执行 \`npm install --omit=dev\`。
253
- 默认共享统一数据存储,仅在需要数据隔离等特殊场景下追加 \`--data-dir <path>\`。
254
- 本地封闭执行不支持 \`--async\`。
234
+ ### 动作参数契约按需调阅
255
235
 
256
- ${renderInvocationGuidance(defaultDescribe, defaultRun)}
236
+ 在调用未知参数的 Action 前,可在终端执行命令查阅该 Action 的输入输出模式与详细说明:
237
+
238
+ \`\`\`bash
239
+ ad describe ${pkgId}/${firstAction}
240
+ \`\`\`
241
+
242
+ ### 执行 Action
243
+
244
+ 为避免多技能之间的 Action ID 命名冲突,建议统一使用带有 Package 前缀的完全限定 ID。
245
+
246
+ ${renderInvocationGuidance(`ad describe ${pkgId}/${firstAction}`, `ad run ${pkgId}/${firstAction}`)}
257
247
 
258
248
  ${renderJsonEnvelopeSection("envelope")}
259
- ${renderPlaybookSectionMarkdown(playbooks, config.playbooksDir || "playbooks")}
249
+ ${playbookSection}
250
+ ---
260
251
 
261
252
  ## Action 目录
262
253
 
263
- ${renderActionListMarkdown(actions, { packageId: config.id })}
254
+ ${actionListMd}
255
+
256
+ ---
264
257
 
265
258
  ## 运行时配置与持久化状态
266
259
 
260
+ 如需检查或配置该 Package 的运行时参数与持久化数据:
261
+
267
262
  \`\`\`bash
268
263
  # 查看与设置配置项
269
- ad config list -P "<skill_root>"
270
- ad config set KEY VALUE -P "<skill_root>"
264
+ ad config list --package ${pkgId}
265
+ ad config set KEY VALUE --package ${pkgId}
271
266
 
272
267
  # 查看与检索状态数据
273
- ad state list -P "<skill_root>"
274
- ad state get KEY -P "<skill_root>"
268
+ ad state list --package ${pkgId}
269
+ ad state get KEY --package ${pkgId}
275
270
  \`\`\`
276
271
 
277
272
  ${renderTroubleshootingSection(RUNTIME_REFERENCE_REL_PATH)}
@@ -367,8 +362,8 @@ export function generateCompositeSkillMd(bundleName, description, packages, opti
367
362
  for (const pkg of packages) {
368
363
  for (const pb of pkg.playbooks) {
369
364
  const relPath = packagesBaseDir === "."
370
- ? `./${pkg.packageDir}/${pkg.config.playbooksDir || "playbooks"}/${basename(pb.filePath)}`
371
- : `packages/${pkg.packageDir}/${pkg.config.playbooksDir || "playbooks"}/${basename(pb.filePath)}`;
365
+ ? `./${pkg.packageDir}/playbooks/${basename(pb.filePath)}`
366
+ : `packages/${pkg.packageDir}/playbooks/${basename(pb.filePath)}`;
372
367
  playbookEntries.push(`- [${pb.name || pb.id}](${relPath}): ${pb.description || "标准操作规程"}`);
373
368
  }
374
369
  }
@@ -380,36 +375,12 @@ description: ${description}
380
375
  # ${bundleName} 复合技能套件
381
376
 
382
377
  ${description}`;
383
- const invocationStyle = options?.invocationStyle ?? "source-ad";
384
- const isGlobalAd = invocationStyle === "global-ad";
385
- const isNodeEntry = invocationStyle === "node-entry";
386
- const describeCmd = isGlobalAd
387
- ? `ad describe ${sampleActionId}`
388
- : isNodeEntry
389
- ? `node "<skill_root>/entry.mjs" describe ${sampleActionId}`
390
- : `ad describe ${sampleActionId} -P "<skill_root>"`;
391
- const runCmd = isGlobalAd
392
- ? `ad run ${sampleActionId}`
393
- : isNodeEntry
394
- ? `node "<skill_root>/entry.mjs" run ${sampleActionId}`
395
- : `ad run ${sampleActionId} -P "<skill_root>"`;
396
- const localGuidance = isGlobalAd
397
- ? ""
398
- : isNodeEntry
399
- ? `将本文件所在目录解析为 \`<skill_root>\`。直接使用随附的 \`node "<skill_root>/entry.mjs"\` 入口执行,不要求全局安装 \`ad\` 命令行工具,不读取 Registry 或当前工作目录项目。
400
- 缺少依赖时,在技能根目录执行 \`npm install --omit=dev\`。默认共享统一数据存储,仅在需要数据隔离等特殊场景下追加 \`--data-dir <path>\`。
401
- 本地封闭执行不支持 \`--async\`;复合技能不提供配置与状态管理子命令,配置覆盖使用 \`--config KEY=VALUE\`。`
402
- : `将本文件所在目录解析为 \`<skill_root>\`。默认通过 \`ad ... -P "<skill_root>"\` 执行,仅使用本技能封闭包含的本地包集合,不通过 Registry 或当前工作目录项目执行,亦不需要 \`ad link\`。此执行约定优先于自定义正文中的历史调用说明。
403
- 缺少依赖时,在技能根目录执行 \`npm install --omit=dev\`。默认共享统一数据存储,仅在需要数据隔离等特殊场景下追加 \`--data-dir <path>\`。
404
- 本地封闭执行不支持 \`--async\`;复合技能不提供配置与状态管理子命令,配置覆盖使用 \`--config KEY=VALUE\`。`;
405
378
  const sDescribe = `## 动作参数契约按需调阅
406
379
 
407
- ${localGuidance}
408
-
409
380
  为节省上下文开销,各 Action 的详细参数结构不静态内嵌在说明书中。在调用未知参数的 Action 前,可在终端执行命令查阅输入输出约束:
410
381
 
411
382
  \`\`\`bash
412
- ${describeCmd}
383
+ ad describe ${sampleActionId}
413
384
  \`\`\``;
414
385
  const sActions = `## 可用 Action 工具清单
415
386
 
@@ -426,8 +397,8 @@ ${playbookEntries.join("\n")}
426
397
  ---`
427
398
  : "";
428
399
  const sInvoke = `## 标准调用命令
429
-
430
- ${renderInvocationGuidance(describeCmd, runCmd)}
400
+
401
+ ${renderInvocationGuidance(`ad describe ${sampleActionId}`, `ad run ${sampleActionId}`)}
431
402
 
432
403
  ${renderJsonEnvelopeSection("envelope", { withDescriptions: false })}`;
433
404
  const sTroubleshooting = renderTroubleshootingSection(RUNTIME_REFERENCE_REL_PATH);
@@ -35,11 +35,3 @@ export declare function writeSkillMd(root: string, destDir: string, options: {
35
35
  * 构造模板生成所需的 ProjectConfig 视图。
36
36
  */
37
37
  export declare function buildConfigForTemplates(plan: SelectionPlan, actionsDir: string, playbooksDir: string): ProjectConfig;
38
- /** 刷新有边界标记的本地执行约定,保留生成块之外的用户正文。 */
39
- export declare function appendLocalExecutionContract(file: string, packages: Array<{
40
- packageId: string;
41
- actions: Array<{
42
- id: string;
43
- isExternal?: boolean;
44
- }>;
45
- }>): void;
package/dist/skill-md.js CHANGED
@@ -159,49 +159,3 @@ export function buildConfigForTemplates(plan, actionsDir, playbooksDir) {
159
159
  config: plan.configDefs,
160
160
  };
161
161
  }
162
- /** 刷新有边界标记的本地执行约定,保留生成块之外的用户正文。 */
163
- export function appendLocalExecutionContract(file, packages) {
164
- const commands = packages.flatMap((pkg) => pkg.actions
165
- .filter((action) => !action.isExternal)
166
- .map((action) => `- \`${pkg.packageId}/${action.id}\``));
167
- const content = readFileSync(file, "utf-8");
168
- const block = `<!-- actiondock:local-execution -->
169
- ## 本地执行约定
170
-
171
- 本节为导出器根据实际包清单生成的执行约定,优先于上文保留的历史调用或初始化说明。
172
- 将本文件所在目录解析为 \`<skill_root>\`。默认使用 \`ad ... -P "<skill_root>"\` 执行,仅加载本技能封闭包含的本地包集合,不通过 Registry、当前工作目录项目或 \`ad link\` 调度。
173
-
174
- \`\`\`bash
175
- cd "<skill_root>" && npm install --omit=dev
176
- ad list -P "<skill_root>"
177
- ad describe <package-id>/<action-id> -P "<skill_root>"
178
- ad run <package-id>/<action-id> -P "<skill_root>" -- ASSIGNMENT...
179
- \`\`\`
180
-
181
- 默认共享统一数据存储,仅在需要数据隔离等特殊场景下追加 \`--data-dir <path>\`;封闭执行不支持 \`--async\`。
182
-
183
- ### 本次导出的可用动作
184
-
185
- ${commands.join("\n")}
186
- <!-- actiondock:local-execution:end -->`;
187
- const startMarker = "<!-- actiondock:local-execution -->";
188
- const endMarker = "<!-- actiondock:local-execution:end -->";
189
- let cursor = 0;
190
- let refreshed = "";
191
- let found = false;
192
- while (true) {
193
- const start = content.indexOf(startMarker, cursor);
194
- const end = content.indexOf(endMarker, cursor);
195
- if (start < 0 && end < 0)
196
- break;
197
- const nextStart = start < 0 ? -1 : content.indexOf(startMarker, start + startMarker.length);
198
- if (start < 0 || end < start || (nextStart >= 0 && nextStart < end)) {
199
- throw new BuilderError("Incomplete or misordered local execution block markers; refusing to replace user content");
200
- }
201
- refreshed += content.slice(cursor, start) + (found ? "" : block);
202
- found = true;
203
- cursor = end + endMarker.length;
204
- }
205
- refreshed += content.slice(cursor);
206
- writeFileSync(file, found ? refreshed : content + "\n\n" + block + "\n", "utf-8");
207
- }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { ProjectConfig } from "@actiondock/core";
2
- import type { PackageGraph } from "@actiondock/core/graph";
3
2
  import type { ActionDockManifest } from "@actiondock/core/project";
4
3
  /**
5
4
  * 规划输入侧的项目配置扩展字段。
@@ -151,8 +150,6 @@ export type SelectionPlan = BuildPlan;
151
150
  * 声明式选择规划器配置选项。
152
151
  */
153
152
  export interface SelectionPlannerOptions {
154
- /** 显式共享包图,提供时不再发现 Registry 或当前工程。 */
155
- packageGraph?: PackageGraph;
156
153
  /** 项目根目录绝对路径 */
157
154
  projectRoot: string;
158
155
  /** 显式传入的项目配置(若未提供则从 actiondock.json 读取) */
@@ -307,8 +304,6 @@ export interface PackResult {
307
304
  * Skill 导出选项。
308
305
  */
309
306
  export interface SkillExporterOptions {
310
- /** 复合导出共享的明确包集合。 */
311
- packageGraph?: PackageGraph;
312
307
  /** 源码项目根目录绝对路径 */
313
308
  projectRoot: string;
314
309
  /** 导出模式:source 源码型 Skill 或 node 目录型 Skill(默认 source) */
@@ -341,8 +336,6 @@ export interface SkillExporterOptions {
341
336
  skipDependencyValidation?: boolean;
342
337
  /** 内部标识:是否作为复合套件子包导出(避免闭包再次触发嵌套 mini-workspace) */
343
338
  _isSubpackage?: boolean;
344
- /** 复合套件子包缺省 npm 包名(当子包无 package.json 或缺少 name 时使用) */
345
- defaultPackageName?: string;
346
339
  /** 自定义复合技能说明书文件路径 */
347
340
  customMdPath?: string;
348
341
  /** 所属 Workspace 根目录(可选) */
@@ -387,8 +380,6 @@ export type ExportSkillResult = SkillExportResult;
387
380
  * 复合 Skill 套件导出选项。
388
381
  */
389
382
  export interface CompositeSkillExportOptions {
390
- /** 复用源码导出已解析的有效包图,避免丢失显式清单。 */
391
- packageGraph?: PackageGraph;
392
383
  /** 复合技能套件名称 */
393
384
  bundleName: string;
394
385
  /** 参与聚合的项目根目录绝对路径列表 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/builder",
3
- "version": "2.14.0",
3
+ "version": "2.16.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.14.0",
33
- "@actiondock/sdk": "^2.14.0"
32
+ "@actiondock/core": "^2.16.0",
33
+ "@actiondock/sdk": "^2.16.0"
34
34
  },
35
35
  "keywords": [
36
36
  "actiondock",
@@ -1,4 +0,0 @@
1
- import { type ActionDockManifest } from "@actiondock/core/project";
2
- import { type PackageGraph } from "@actiondock/core/graph";
3
- /** 仅从共享包集合派生有效清单图,不修改调用方图或重新发现外部包。 */
4
- export declare function deriveEffectivePackageGraph(baseGraph: PackageGraph, root: string, effectiveManifest?: ActionDockManifest): PackageGraph;
@@ -1,30 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { computeManifestDigest } from "@actiondock/core/project";
3
- import { PackageGraphBuilder } from "@actiondock/core/graph";
4
- import { BuilderError } from "./errors.js";
5
- /** 仅从共享包集合派生有效清单图,不修改调用方图或重新发现外部包。 */
6
- export function deriveEffectivePackageGraph(baseGraph, root, effectiveManifest) {
7
- const packages = Array.from(baseGraph.packages.values());
8
- const target = packages.find((pkg) => resolve(pkg.root) === resolve(root));
9
- if (!target) {
10
- throw new BuilderError(`Project root '${root}' not found in provided packageGraph.`, "PACKAGE_NOT_FOUND");
11
- }
12
- if (!effectiveManifest)
13
- return baseGraph;
14
- if (effectiveManifest.id !== target.packageId) {
15
- throw new BuilderError(`Cannot change package identity from '${target.packageId}' to '${effectiveManifest.id}' in shared package graph.`, "CANNOT_CHANGE_PACKAGE_IDENTITY");
16
- }
17
- // 合并配置可能仅创建等价对象;相同声明继续复用共享图。
18
- if (computeManifestDigest(effectiveManifest) === target.manifestDigest)
19
- return baseGraph;
20
- return new PackageGraphBuilder({
21
- packages: packages.map((pkg) => ({
22
- id: pkg.packageId,
23
- root: pkg.root,
24
- manifest: pkg === target ? effectiveManifest : pkg.manifest,
25
- isCurrentProject: pkg === target,
26
- })),
27
- root: target.root,
28
- validateDependencies: true,
29
- }).buildSync();
30
- }
@@ -1,4 +0,0 @@
1
- import { type ActionDockManifest } from "@actiondock/core/project";
2
- import { type PackageGraph } from "@actiondock/core/graph";
3
- /** 源码导出只使用显式包与正式安装依赖;显式兄弟包优先于任何依赖查找。 */
4
- export declare function createSourcePackageGraph(projectRoots: string[], rootManifest?: ActionDockManifest): PackageGraph;
@@ -1,33 +0,0 @@
1
- import { realpathSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import { ActionDockError } from "@actiondock/core";
4
- import { loadManifest } from "@actiondock/core/project";
5
- import { PackageGraphBuilder } from "@actiondock/core/graph";
6
- import { BuilderError } from "./errors.js";
7
- /** 源码导出只使用显式包与正式安装依赖;显式兄弟包优先于任何依赖查找。 */
8
- export function createSourcePackageGraph(projectRoots, rootManifest) {
9
- const packages = new Map();
10
- const register = (pkg) => {
11
- const existing = packages.get(pkg.id);
12
- if (existing && realpathSync(existing.root) !== realpathSync(pkg.root)) {
13
- throw new ActionDockError("PACKAGE_ID_CONFLICT", `Package ID '${pkg.id}' is declared by multiple directories: '${existing.root}' and '${pkg.root}'`);
14
- }
15
- packages.set(pkg.id, pkg);
16
- };
17
- for (const [index, root] of projectRoots.entries()) {
18
- const absRoot = resolve(root);
19
- const manifest = index === 0 && rootManifest ? rootManifest : loadManifest(absRoot);
20
- if (!manifest)
21
- throw new BuilderError(`Missing actiondock.json at '${absRoot}'`);
22
- register({ id: manifest.id, root: absRoot, manifest });
23
- }
24
- const packageRoots = Object.fromEntries(Array.from(packages.values(), (p) => [p.id, p.root]));
25
- // 每个显式包的正式依赖均纳入闭包,统一查重后再构造供所有规划共享的图。
26
- for (const pkg of Array.from(packages.values())) {
27
- const graph = new PackageGraphBuilder({ root: pkg.root, manifest: pkg.manifest, packageRoots, allowDevLinks: false }).buildSync();
28
- for (const node of graph.packages.values()) {
29
- register({ id: node.packageId, root: node.root, manifest: node.manifest });
30
- }
31
- }
32
- return new PackageGraphBuilder({ packages: Array.from(packages.values()), validateDependencies: true }).buildSync();
33
- }