@actiondock/builder 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/manifest.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- import { ACTION_ID_REGEX } from "@actiondock/core";
3
+ import { ACTION_ID_REGEX, getPackageSlug } from "@actiondock/core/project";
4
4
  import { BuilderError } from "./errors.js";
5
5
  import { getInternalDependencyVersion } from "./fs-utils.js";
6
+ import { isOwnAction } from "./types.js";
6
7
  /**
7
8
  * 校验清单 actions 字典中的所有 Action ID 是否符合规范。
8
9
  * 强制写入端契约自检,杜绝非法 Action ID 落盘。
@@ -57,7 +58,7 @@ export function serializePlanManifest(plan, options = {}) {
57
58
  const manifestActions = {};
58
59
  for (const act of plan.actions) {
59
60
  // 仅序列化包自有 Action,跨包依赖不写入包清单
60
- if (act.isExternal || act.id.includes("/")) {
61
+ if (!isOwnAction(act)) {
61
62
  continue;
62
63
  }
63
64
  const serialized = serializeManifestAction(act);
@@ -174,6 +175,70 @@ export function resolveWorkspaceDepVersion(root, depName, ver) {
174
175
  }
175
176
  throw new BuilderError(`Failed to resolve workspace dependency '${depName}' (${ver}) in '${root}'. Target package version could not be found.`);
176
177
  }
178
+ /**
179
+ * 为导出子包分配互不冲突的目录名。
180
+ *
181
+ * 首选 Package ID 尾段(getPackageSlug);冲突时回退为 ID 全量的安全形式,
182
+ * 回退后仍冲突(如两个同尾段 ID 回退值相同)则追加数字后缀直至唯一,
183
+ * 严禁仅加入 used 集合而不二次消歧导致目录互相覆盖。
184
+ *
185
+ * @param used 已占用的目录名集合(函数内部会登记分配结果)
186
+ * @param configId 待分配目录名的 Package ID
187
+ */
188
+ export function allocatePackageDirName(used, configId) {
189
+ let name = getPackageSlug(configId);
190
+ if (used.has(name)) {
191
+ name = configId.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
192
+ }
193
+ if (!name) {
194
+ name = getPackageSlug(configId) || "package";
195
+ }
196
+ if (used.has(name)) {
197
+ let suffix = 2;
198
+ while (used.has(`${name}-${suffix}`)) {
199
+ suffix++;
200
+ }
201
+ name = `${name}-${suffix}`;
202
+ }
203
+ used.add(name);
204
+ return name;
205
+ }
206
+ /**
207
+ * 导出产物依赖清洗单一入口。
208
+ *
209
+ * 清洗规则(单包源码导出与复合套件聚合导出共用同一事实源):
210
+ * - file: 本地协议拒绝(经 assertNoFileProtocolDeps);
211
+ * - @actiondock/* 内部依赖覆写为内部版本规则;
212
+ * - workspace: 前缀解析为真实版本(经 resolveWorkspaceDepVersion);
213
+ * - 其余依赖原样保留。
214
+ *
215
+ * @param root 解析 workspace 依赖时的参照根目录
216
+ * @param deps 待清洗的依赖字典(可选)
217
+ * @param context 错误消息中的场景描述
218
+ * @param ensureSdk 是否保证 @actiondock/sdk 依赖存在(单包导出需要,复合根目录已预置)
219
+ */
220
+ export function sanitizeExportDependencies(root, deps, context, ensureSdk = true) {
221
+ const result = {};
222
+ assertNoFileProtocolDeps(deps, context);
223
+ if (deps && typeof deps === "object") {
224
+ for (const [depName, depVer] of Object.entries(deps)) {
225
+ const verStr = String(depVer);
226
+ if (depName.startsWith("@actiondock/")) {
227
+ result[depName] = getInternalDependencyVersion();
228
+ }
229
+ else if (verStr.startsWith("workspace:")) {
230
+ result[depName] = resolveWorkspaceDepVersion(root, depName, verStr);
231
+ }
232
+ else {
233
+ result[depName] = verStr;
234
+ }
235
+ }
236
+ }
237
+ if (ensureSdk && !result["@actiondock/sdk"]) {
238
+ result["@actiondock/sdk"] = getInternalDependencyVersion();
239
+ }
240
+ return result;
241
+ }
177
242
  /**
178
243
  * 读取并解析 package.json,解析失败抛出 BuilderError。
179
244
  */
package/dist/pack.js CHANGED
@@ -3,12 +3,15 @@ import { createHash } from "node:crypto";
3
3
  import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import { tmpdir } from "node:os";
6
- import { basename, dirname, join, relative, resolve } from "node:path";
7
- import { getPackageSlug, loadProjectConfig } from "@actiondock/core";
6
+ import { basename, join, relative, resolve } from "node:path";
7
+ import { loadProjectConfig } from "@actiondock/core";
8
+ import { getPackageSlug } from "@actiondock/core/project";
8
9
  import { BuilderError } from "./errors.js";
9
10
  import { assertNoFileProtocolDeps, assertValidManifestActionIds, normalizePkgExportsAndEngines, readPackageJson, serializePlanManifest, } from "./manifest.js";
10
11
  import { collectRelativeFiles } from "./fs-utils.js";
11
12
  import { SelectionPlanner } from "./planner.js";
13
+ import { isOwnAction } from "./types.js";
14
+ import { copyPlanEntries } from "./stage-sources.js";
12
15
  /**
13
16
  * 对 Action Package 执行打包并生成标准 npm Action 压缩包(.tgz)。
14
17
  *
@@ -158,7 +161,7 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
158
161
  const ts = await resolveTypeScriptCompiler(root);
159
162
  const tsSourceFiles = new Set();
160
163
  for (const act of plan.actions) {
161
- if (act.isExternal || act.id.includes("/"))
164
+ if (!isOwnAction(act))
162
165
  continue;
163
166
  if (isTypeScriptSource(act.entry)) {
164
167
  tsSourceFiles.add(act.resolvedPath);
@@ -187,27 +190,34 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
187
190
  if (existsSync(tsconfigPath)) {
188
191
  try {
189
192
  const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
190
- if (!configFile.error) {
191
- const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, root);
192
- // tsc 不会改写 import 说明符:paths 路径别名在产物中无法解析,必须显式拒绝而非静默编译出坏产物
193
- if (parsedConfig.options.paths && Object.keys(parsedConfig.options.paths).length > 0) {
194
- throw new BuilderError(`tsconfig.json 'paths' aliases are not supported in packed output: TypeScript does not rewrite import specifiers, so the packed artifact would contain unresolvable module specifiers. Replace path aliases with relative imports, or pre-build the project and pack the compiled output instead.`, "PATHS_ALIAS_UNSUPPORTED");
195
- }
196
- Object.assign(compilerOptions, parsedConfig.options, {
197
- outDir: stagingPkgDir,
198
- rootDir: root,
199
- declaration: true,
200
- emitDeclarationOnly: false,
201
- rewriteRelativeImportExtensions: true,
202
- noEmit: false,
203
- });
193
+ if (configFile.error) {
194
+ throw new Error(ts.formatDiagnostic(configFile.error));
204
195
  }
196
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, root);
197
+ if (parsedConfig.errors && parsedConfig.errors.length > 0) {
198
+ throw new Error(ts.formatDiagnostics(parsedConfig.errors));
199
+ }
200
+ // tsc 不会改写 import 说明符:paths 路径别名在产物中无法解析,必须显式拒绝而非静默编译出坏产物
201
+ if (parsedConfig.options.paths && Object.keys(parsedConfig.options.paths).length > 0) {
202
+ throw new BuilderError(`tsconfig.json 'paths' aliases are not supported in packed output: TypeScript does not rewrite import specifiers, so the packed artifact would contain unresolvable module specifiers. Replace path aliases with relative imports, or pre-build the project and pack the compiled output instead.`, "PATHS_ALIAS_UNSUPPORTED");
203
+ }
204
+ Object.assign(compilerOptions, parsedConfig.options, {
205
+ outDir: stagingPkgDir,
206
+ rootDir: root,
207
+ declaration: true,
208
+ emitDeclarationOnly: false,
209
+ rewriteRelativeImportExtensions: true,
210
+ noEmit: false,
211
+ });
205
212
  }
206
213
  catch (err) {
207
214
  if (err instanceof BuilderError) {
208
215
  throw err;
209
216
  }
210
- // 忽略 tsconfig 解析失败,回退到标准 compilerOptions
217
+ // tsconfig 解析失败回退默认 compilerOptions,但必须输出显著告警:
218
+ // 用户配置的 strict、target 等被丢弃可能编译出行为不同的产物,严禁无提示
219
+ const reason = err instanceof Error ? err.message : String(err);
220
+ console.warn(`[actiondock] Warning: Failed to parse tsconfig.json at ${tsconfigPath}; falling back to default compiler options for packed output. Root cause: ${reason}`);
211
221
  }
212
222
  }
213
223
  const host = ts.createCompilerHost(compilerOptions);
@@ -228,36 +238,16 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
228
238
  }
229
239
  /**
230
240
  * 拷贝声明的非 TypeScript 模块、静态资产、原生 JavaScript 入口、Playbook 与基础文档到暂存目录。
241
+ * 拷贝内核统一复用 stage-sources 的 copyPlanEntries,仅通过谓词分化编译排除差异。
231
242
  */
232
243
  function stageSources(root, stagingPkgDir, plan) {
233
- // 拷贝声明的非 TypeScript 模块与静态资产
234
- for (const dep of plan.dependencies.modulesAndAssets) {
235
- if ((dep.type === "asset" || dep.type === "module" || dep.type === "file") &&
236
- existsSync(dep.resolvedPath)) {
237
- if (isTypeScriptSource(dep.path)) {
238
- continue; // 已由 TypeScript 编译生成 .js 与 .d.ts
239
- }
240
- const destPath = join(stagingPkgDir, dep.path);
241
- mkdirSync(dirname(destPath), { recursive: true });
242
- copyFileSync(dep.resolvedPath, destPath);
243
- }
244
- }
245
- // 拷贝原生 JavaScript Action 入口(若存在)
246
- for (const act of plan.actions) {
247
- if (!isTypeScriptSource(act.entry)) {
248
- const destPath = join(stagingPkgDir, act.entry);
249
- mkdirSync(dirname(destPath), { recursive: true });
250
- copyFileSync(act.resolvedPath, destPath);
251
- }
252
- }
253
- // 拷贝 Playbook 规程文件
254
- for (const pb of plan.playbooks) {
255
- if (existsSync(pb.filePath)) {
256
- const destPb = join(stagingPkgDir, relative(root, pb.filePath));
257
- mkdirSync(dirname(destPb), { recursive: true });
258
- copyFileSync(pb.filePath, destPb);
259
- }
260
- }
244
+ copyPlanEntries(stagingPkgDir, plan, {
245
+ // TypeScript 入口已由编译阶段生成 .js 与 .d.ts,不重复物化源文件
246
+ copyAction: (act) => !isTypeScriptSource(act.entry),
247
+ copyModule: (dep) => !isTypeScriptSource(dep.path),
248
+ // pack 产物保留源工程相对路径(与编译输出的目录结构一致)
249
+ playbookRelPath: (pb) => relative(root, pb.filePath),
250
+ });
261
251
  // 拷贝 README 等基础文档(若存在)
262
252
  for (const doc of ["README.md", "readme.md", "LICENSE", "license"]) {
263
253
  const srcDoc = join(root, doc);
@@ -285,7 +275,7 @@ function writeManifestAndPkgJson(stagingPkgDir, plan, sourcePkgJson) {
285
275
  // 构建编译后的 Action 清单字典并严格校验入口扩展名
286
276
  const compiledEntries = new Map();
287
277
  for (const act of plan.actions) {
288
- if (act.isExternal || act.id.includes("/"))
278
+ if (!isOwnAction(act))
289
279
  continue;
290
280
  const destEntry = compiledEntryFor(act);
291
281
  if (!destEntry.endsWith(".js") && !destEntry.endsWith(".mjs")) {
package/dist/planner.js CHANGED
@@ -1,64 +1,33 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { basename, join, relative, resolve } from "node:path";
4
- import { assertPathWithinRoot, loadManifest, loadPlaybooks, loadProjectConfig, resolveActionProjectSync, validateManifest, } from "@actiondock/core";
4
+ import { loadProjectConfig, } from "@actiondock/core";
5
+ import { assertPathWithinRoot, traverseDirectory, loadManifest, loadPlaybooks, validateManifest, } from "@actiondock/core/project";
6
+ import { DefaultActionCatalog, PackageGraphBuilder, resolveAction, } from "@actiondock/core/graph";
5
7
  import { PlannerError } from "./errors.js";
6
8
  import { assertRelativeDependenciesIntegrity } from "./dependency-check.js";
7
9
  /**
8
10
  * 递归扫描指定目录下的文件,返回绝对路径列表。
9
- * 通过安全路径边界能力校验,阻止越界并避免符号链接逃逸。
11
+ * 目录遍历统一复用 core 的 traverseDirectory 单一事实源,
12
+ * 自带软链接越界防护与循环拦截。
10
13
  */
11
- function walkDirectory(dir, rootDir = dir, visited = new Set()) {
12
- if (!existsSync(dir))
13
- return [];
14
- const results = [];
15
- const entries = readdirSync(dir);
16
- for (const entry of entries) {
17
- const fullPath = join(dir, entry);
18
- if (rootDir) {
19
- try {
20
- assertPathWithinRoot(rootDir, fullPath, "planner path");
21
- }
22
- catch {
23
- continue;
24
- }
25
- }
26
- let real;
27
- try {
28
- real = existsSync(fullPath) ? realpathSync(fullPath) : fullPath;
29
- }
30
- catch {
31
- continue;
32
- }
33
- if (rootDir) {
34
- try {
35
- assertPathWithinRoot(rootDir, real, "planner path");
36
- }
37
- catch {
38
- // 忽略并跳过指向项目根目录外部的软链接
39
- continue;
40
- }
41
- }
42
- if (visited.has(real)) {
43
- continue;
44
- }
45
- visited.add(real);
46
- try {
47
- const stat = statSync(fullPath);
48
- if (stat.isDirectory()) {
49
- results.push(...walkDirectory(fullPath, rootDir, visited));
50
- }
51
- else if (stat.isFile()) {
52
- results.push(fullPath);
53
- }
54
- }
55
- catch {
56
- // 忽略无法访问或损坏的文件/符号链接
57
- continue;
58
- }
59
- }
60
- return results;
14
+ function walkDirectory(dir) {
15
+ return traverseDirectory(dir).map((entry) => entry.fullPath);
61
16
  }
17
+ /**
18
+ * 测试与类型声明文件后缀常量(供 fallback 清单过滤与 isIgnoredPath 共享同一口径)。
19
+ */
20
+ const TEST_FILE_SUFFIXES = [
21
+ ".test.ts",
22
+ ".test.js",
23
+ ".test.tsx",
24
+ ".test.jsx",
25
+ ".spec.ts",
26
+ ".spec.js",
27
+ ".spec.tsx",
28
+ ".spec.jsx",
29
+ ".d.ts",
30
+ ];
62
31
  /**
63
32
  * 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
64
33
  */
@@ -69,15 +38,7 @@ function isIgnoredPath(relPath) {
69
38
  normalized.startsWith(".git/") ||
70
39
  normalized.startsWith("dist/") ||
71
40
  normalized.startsWith(".actiondock/") ||
72
- normalized.endsWith(".test.ts") ||
73
- normalized.endsWith(".test.js") ||
74
- normalized.endsWith(".test.tsx") ||
75
- normalized.endsWith(".test.jsx") ||
76
- normalized.endsWith(".spec.ts") ||
77
- normalized.endsWith(".spec.js") ||
78
- normalized.endsWith(".spec.tsx") ||
79
- normalized.endsWith(".spec.jsx") ||
80
- normalized.endsWith(".d.ts"));
41
+ TEST_FILE_SUFFIXES.some((suffix) => normalized.endsWith(suffix)));
81
42
  }
82
43
  /**
83
44
  * 支持的包管理器锁文件候选列表。
@@ -122,8 +83,9 @@ function computeLockfileInfo(projectRoot, preferredLockfile) {
122
83
  };
123
84
  }
124
85
  }
125
- catch {
126
- continue;
86
+ catch (err) {
87
+ // 存在但不可读:显式报错而非静默跳过,否则构建在无锁文件摘要状态下继续,可复现性校验形同虚设
88
+ throw new PlannerError(`Lockfile '${lockFileName}' exists but could not be read in ${projectRoot}: ${err?.message || String(err)}`, "LOCKFILE_READ_ERROR");
127
89
  }
128
90
  }
129
91
  }
@@ -153,12 +115,13 @@ function generateFallbackManifest(projectRoot, actionsDir = "actions", config) {
153
115
  }
154
116
  }
155
117
  }
156
- // 若 actions 目录存在,基于文件名建立默认映射
118
+ // 若 actions 目录存在,基于文件名建立默认映射(过滤规则与 isIgnoredPath 保持同一口径)
157
119
  if (existsSync(dir)) {
158
- const files = walkDirectory(dir, projectRoot).filter((f) => (f.endsWith(".ts") || f.endsWith(".js")) &&
159
- !f.endsWith(".d.ts") &&
160
- !f.endsWith(".test.ts") &&
161
- !f.endsWith(".spec.ts"));
120
+ const files = walkDirectory(dir).filter((f) => {
121
+ const rel = relative(projectRoot, f).replace(/\\/g, "/");
122
+ return ((f.endsWith(".ts") || f.endsWith(".js")) &&
123
+ !isIgnoredPath(rel));
124
+ });
162
125
  for (const file of files) {
163
126
  const relPath = relative(projectRoot, file).replace(/\\/g, "/");
164
127
  const filename = basename(file);
@@ -180,38 +143,43 @@ function generateFallbackManifest(projectRoot, actionsDir = "actions", config) {
180
143
  }
181
144
  /**
182
145
  * 收集项目根目录 package.json 中声明的外部 npm 依赖。
146
+ *
147
+ * 防御与透明原则:文件不存在返回空集合(合法的无依赖工程);
148
+ * 文件存在但不可读或 JSON 损坏时抛 PlannerError 并携带路径与原因,
149
+ * 严禁静默吞掉导致后续 build 的生产依赖、pack 的产物依赖与 vendorDeps 全部无声缺失。
183
150
  */
184
151
  function extractExternalDependencies(projectRoot) {
185
152
  const pkgPath = join(projectRoot, "package.json");
186
153
  if (!existsSync(pkgPath))
187
154
  return [];
155
+ let parsed;
188
156
  try {
189
157
  const raw = readFileSync(pkgPath, "utf-8");
190
- const parsed = JSON.parse(raw);
191
- const deps = [];
192
- if (parsed.dependencies && typeof parsed.dependencies === "object") {
193
- for (const [name, versionRange] of Object.entries(parsed.dependencies)) {
194
- deps.push({
195
- name,
196
- versionRange: String(versionRange),
197
- isDev: false,
198
- });
199
- }
200
- }
201
- if (parsed.devDependencies && typeof parsed.devDependencies === "object") {
202
- for (const [name, versionRange] of Object.entries(parsed.devDependencies)) {
203
- deps.push({
204
- name,
205
- versionRange: String(versionRange),
206
- isDev: true,
207
- });
208
- }
158
+ parsed = JSON.parse(raw);
159
+ }
160
+ catch (err) {
161
+ throw new PlannerError(`Failed to read or parse package.json at ${pkgPath}: ${err?.message || String(err)}`, "EXTRACT_DEPS_ERROR");
162
+ }
163
+ const deps = [];
164
+ if (parsed.dependencies && typeof parsed.dependencies === "object") {
165
+ for (const [name, versionRange] of Object.entries(parsed.dependencies)) {
166
+ deps.push({
167
+ name,
168
+ versionRange: String(versionRange),
169
+ isDev: false,
170
+ });
209
171
  }
210
- return deps;
211
172
  }
212
- catch {
213
- return [];
173
+ if (parsed.devDependencies && typeof parsed.devDependencies === "object") {
174
+ for (const [name, versionRange] of Object.entries(parsed.devDependencies)) {
175
+ deps.push({
176
+ name,
177
+ versionRange: String(versionRange),
178
+ isDev: true,
179
+ });
180
+ }
214
181
  }
182
+ return deps;
215
183
  }
216
184
  /**
217
185
  * 声明式选择集与构建规划器 (SelectionPlanner)。
@@ -335,6 +303,8 @@ export class SelectionPlanner {
335
303
  const externalActionRoots = new Map();
336
304
  const externalActionEntries = new Map();
337
305
  const manifestActions = manifest.actions || {};
306
+ const graph = new PackageGraphBuilder({ root, allowDevLinks: true }).buildSync();
307
+ const catalog = new DefaultActionCatalog(graph);
338
308
  while (queue.length > 0) {
339
309
  const currentId = queue.shift();
340
310
  queued.delete(currentId);
@@ -345,26 +315,27 @@ export class SelectionPlanner {
345
315
  let entryRoot = root;
346
316
  if (!entry) {
347
317
  try {
348
- const resolvedExternal = resolveActionProjectSync(currentId, root);
349
- if (resolvedExternal && existsSync(resolvedExternal.projectRoot)) {
350
- const extRoot = resolvedExternal.projectRoot;
318
+ const resolved = resolveAction(currentId, { graph, catalog, caller: config?.id });
319
+ const extNode = graph.packages.get(resolved.package.id);
320
+ if (extNode && existsSync(extNode.root)) {
321
+ const extRoot = extNode.root;
351
322
  let extConfig;
352
323
  try {
353
324
  extConfig = loadProjectConfig(extRoot);
354
325
  }
355
326
  catch {
356
327
  extConfig = {
357
- id: resolvedExternal.packageId,
358
- name: resolvedExternal.packageId,
328
+ id: resolved.package.id,
329
+ name: resolved.package.id,
359
330
  version: "0.1.0",
360
331
  };
361
332
  }
362
333
  const externalManifest = loadManifest(extRoot);
363
- let externalEntry = externalManifest?.actions?.[resolvedExternal.actionId] ||
364
- extConfig?.actions?.[resolvedExternal.actionId];
334
+ let externalEntry = externalManifest?.actions?.[resolved.ref.actionId] ||
335
+ extConfig?.actions?.[resolved.ref.actionId];
365
336
  if (!externalEntry) {
366
337
  const staticManifest = generateFallbackManifest(extRoot, extConfig.actionsDir || "actions", extConfig);
367
- externalEntry = staticManifest.actions?.[resolvedExternal.actionId];
338
+ externalEntry = staticManifest.actions?.[resolved.ref.actionId];
368
339
  }
369
340
  if (externalEntry) {
370
341
  entry = externalEntry;
@@ -470,7 +441,7 @@ export class SelectionPlanner {
470
441
  }
471
442
  const stat = statSync(resolvedFile);
472
443
  if (stat.isDirectory()) {
473
- const walked = walkDirectory(resolvedFile, root);
444
+ const walked = walkDirectory(resolvedFile);
474
445
  for (const f of walked) {
475
446
  const rel = relative(root, f).replace(/\\/g, "/");
476
447
  if (!isIgnoredPath(rel) && !actionPathSet.has(f) && !filePathSet.has(rel)) {
@@ -517,7 +488,7 @@ export class SelectionPlanner {
517
488
  }
518
489
  const stat = statSync(resolvedAsset);
519
490
  if (stat.isDirectory()) {
520
- const walked = walkDirectory(resolvedAsset, root);
491
+ const walked = walkDirectory(resolvedAsset);
521
492
  for (const f of walked) {
522
493
  const rel = relative(root, f).replace(/\\/g, "/");
523
494
  if (!assetPathSet.has(rel)) {
@@ -545,7 +516,7 @@ export class SelectionPlanner {
545
516
  // 默认 assets 目录扫描
546
517
  const defaultAssetsDir = join(root, "assets");
547
518
  if (existsSync(defaultAssetsDir)) {
548
- const assetFiles = walkDirectory(defaultAssetsDir, root);
519
+ const assetFiles = walkDirectory(defaultAssetsDir);
549
520
  for (const file of assetFiles) {
550
521
  const rel = relative(root, file).replace(/\\/g, "/");
551
522
  if (!assetPathSet.has(rel)) {
@@ -0,0 +1 @@
1
+ export * from "./templates.js";
@@ -0,0 +1 @@
1
+ export * from "./templates.js";
@@ -0,0 +1,60 @@
1
+ import type { ActionSpec, ProjectConfig } from "@actiondock/core";
2
+ import type { PlaybookDefinition } from "@actiondock/core/project";
3
+ export type SkillActionItem = ActionSpec | {
4
+ id: string;
5
+ description?: string;
6
+ inputSchema?: any;
7
+ outputSchema?: any;
8
+ annotations?: any;
9
+ };
10
+ export declare function generateSourceSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: PlaybookDefinition[]): string;
11
+ export declare function generateStandaloneSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: PlaybookDefinition[], binaryRelPath?: string): string;
12
+ export interface CompositeSkillPackageInfo {
13
+ config: ProjectConfig;
14
+ actions: Array<{
15
+ id: string;
16
+ description?: string;
17
+ }>;
18
+ playbooks: Array<{
19
+ id: string;
20
+ name?: string;
21
+ description?: string;
22
+ filePath: string;
23
+ }>;
24
+ packageDir: string;
25
+ }
26
+ /**
27
+ * 复合技能自定义说明书(SKILL.custom.md)的槽位,
28
+ * 决定自定义段落插入到官方模板生成结果中的位置。
29
+ */
30
+ export type CompositeCustomSlot = "intro" | "after-init" | "after-describe" | "after-actions" | "after-playbooks" | "after-invoke" | "append";
31
+ export declare const COMPOSITE_CUSTOM_SLOTS: readonly CompositeCustomSlot[];
32
+ /** 工作区内约定俗成的复合技能自定义说明书文件名。 */
33
+ export declare const COMPOSITE_CUSTOM_DECLARATION_FILE = "SKILL.custom.md";
34
+ export interface CompositeCustomSection {
35
+ slot: CompositeCustomSlot;
36
+ content: string;
37
+ }
38
+ export interface CompositeCustomDeclaration {
39
+ /** 自定义说明书 frontmatter 中声明的 description 覆盖值 */
40
+ description?: string;
41
+ sections: CompositeCustomSection[];
42
+ }
43
+ /**
44
+ * 解析自定义说明书正文:以 `<!-- actiondock:slot <name> -->` 标记行分段。
45
+ * 首个标记之前的内容归入 `append` 槽位;未知槽位名直接抛错,避免拼写错误被静默吞掉。
46
+ */
47
+ export declare function parseCustomSections(source: string): CompositeCustomSection[];
48
+ /**
49
+ * 解析复合技能自定义说明书文件:支持 YAML frontmatter 中的 `description` 覆盖,
50
+ * 正文按槽位标记解析为自定义段落。
51
+ */
52
+ export declare function parseCustomSkillDeclaration(source: string): CompositeCustomDeclaration;
53
+ /**
54
+ * 生成多包聚合的复合模式 SKILL.md 文档。
55
+ */
56
+ export declare function generateCompositeSkillMd(bundleName: string, description: string, packages: CompositeSkillPackageInfo[], options?: {
57
+ customSections?: CompositeCustomSection[];
58
+ /** 规程链接的子包目录前缀:导出布局为 "packages"(默认);就地生成时传 "." 使用工作区实际目录 */
59
+ packagesBaseDir?: string;
60
+ }): string;