@jennie-shawn/starwork 0.1.0-alpha.6 → 0.1.0-alpha.7

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
@@ -28,25 +28,32 @@ npx @jennie-shawn/starwork --help
28
28
 
29
29
  ## 安装 Skills
30
30
 
31
- StarWork skills 通过 GitHub 仓库和 `skills` CLI 分发。
31
+ StarWork skills 分两类管理:
32
32
 
33
- Codex 安装全部 StarWork skills
33
+ - 系统 Skill 通过 GitHub 仓库和 `skills` CLI 安装到 Agent 全局环境。
34
+ - Kit 自带 Skill 跟着工作台走,例如 Hub Kit 自带 `starworkSpawn`,单项目 Kit 自带 `neat-freak`。
35
+
36
+ 给 Codex 安装 StarWork 系统 Skill:
34
37
 
35
38
  ```bash
36
- npx skills add jennie-shawn/starwork --skill '*' -g -a codex -y
39
+ npx skills add jennie-shawn/starwork --skill starworkInit -g -a codex -y
40
+ npx skills add jennie-shawn/starwork --skill starworkDoctor -g -a codex -y
41
+ npx skills add jennie-shawn/starwork --skill starworkUpgrade -g -a codex -y
37
42
  ```
38
43
 
39
- 单独安装某个 skill:
44
+ 如果你只想先安装初始化助手:
40
45
 
41
46
  ```bash
42
47
  npx skills add jennie-shawn/starwork --skill starworkInit -g -a codex -y
43
- npx skills add jennie-shawn/starwork --skill starworkSpawn -g -a codex -y
44
48
  ```
45
49
 
46
50
  当前 skills:
47
51
 
48
52
  - `starworkInit`:帮助 Agent 判断工作台类型、语言、是否需要事项,并生成友好的 `starwork init` 初始化方案。
49
- - `starworkSpawn`:帮助 Agent 为已有 Hub 设计 `starwork spawn --blueprint` 工作台定制单。
53
+ - `starworkDoctor`:帮助 Agent 基于 `starwork doctor --json` 做目录逻辑诊断。
54
+ - `starworkUpgrade`:帮助 Agent 生成 `starwork upgrade --blueprint` 升级蓝图。
55
+ - `starworkSpawn`:Hub Kit 自带 Skill,帮助已有 Hub 设计 `starwork spawn --blueprint` 工作台定制单。
56
+ - `neat-freak`:单项目 Kit 自带 Skill,帮助项目收尾、整理和归档。
50
57
 
51
58
  如果你希望让 Agent 帮你完成安装,可以把 [Agent 安装指南](docs/agent-install-guide.md) 里的提示词发给你的 Agent。
52
59
 
@@ -138,7 +145,8 @@ starwork pack install
138
145
  - `init` 创建的工作台结构是否容易理解。
139
146
  - `doctor` 的检查结果是否能指导修复问题。
140
147
  - Hub + Satellite 工作流是否自然。
141
- - `starworkInit` `starworkSpawn` 是否能被 Agent 正确识别和调用。
148
+ - 系统 Skill 是否能被 Agent 正确识别和调用。
149
+ - Hub Kit 自带的 `starworkSpawn`、单项目 Kit 自带的 `neat-freak` 是否能在对应工作台内被正确发现。
142
150
 
143
151
  更完整的测试脚本见 [A 测安装指南](docs/alpha-test-guide.md)。
144
152
 
package/cli/README.md CHANGED
@@ -29,9 +29,11 @@ v0.1 只覆盖最小可用安装和适配能力:
29
29
  - `starwork upgrade` 第一版:可以读取 `starworkUpgrade` skill 生成的升级蓝图,把历史模板或非标准目录安全升级为 StarWork 工作台;v0.1 只支持 `--blueprint`,不自动判断升级方案。
30
30
  - `starwork adapt` 第一版:可以为 Codex、Claude Code、Cursor、Trae 生成或登记轻量适配入口。
31
31
  - `starwork pack install` 第一版:可以在健康工作台上补装 Pack,并更新路径、规则、模板和 workspace state。
32
+ - Skill 管理与分发第一版:Kit 可以自带 Skill,Hub 可以托管用户常用 Skill;`init` 写入 `.starwork/skills.json`,`spawn` 按 Hub registry 选择性分发 Skill,`doctor` 暴露 Skill manifest / registry / mount 事实。
32
33
 
33
34
  后续规划:
34
35
 
36
+ - Pack 自带 Skill 与 upgrade Skill actions:按 [`StarWork Skill 管理与分发机制 SPEC`](../core/skill-management-spec.md) 继续扩展。
35
37
  - `starwork update`:面向已经是 StarWork 的工作台,处理未来 Core / Kit / Pack 版本迁移;与 `upgrade` 分开设计。
36
38
 
37
39
  CLI 不在 v0.1 阶段处理账号、授权、消息平台 gateway 或复杂商业系统。
package/cli/src/cli.js CHANGED
@@ -67,6 +67,55 @@ const ADAPTERS = {
67
67
  }
68
68
  };
69
69
 
70
+ const KIT_BUNDLED_SKILLS = {
71
+ hub: [
72
+ {
73
+ id: "starworkSpawn",
74
+ name: "StarWork Spawn",
75
+ source: path.join("skills", "starworkSpawn"),
76
+ sourceKind: "kit",
77
+ type: "kit-bundled",
78
+ distribution: "copy",
79
+ reason: "Hub Kit 自带:用于生成和定制卫星项目。",
80
+ install: [
81
+ { agent: "hub", path: path.join("skills", "starworkSpawn"), mode: "copy" },
82
+ { agent: "codex", path: path.join(".agents", "skills", "starworkSpawn"), mode: "symlink", source: path.join("skills", "starworkSpawn") },
83
+ { agent: "claude", path: path.join(".claude", "skills", "starworkSpawn"), mode: "symlink", source: path.join("skills", "starworkSpawn") }
84
+ ]
85
+ }
86
+ ],
87
+ "local-starter": [
88
+ {
89
+ id: "neat-freak",
90
+ name: "Neat Freak",
91
+ source: path.join("skills", "neat-freak"),
92
+ sourceKind: "kit",
93
+ type: "kit-bundled",
94
+ distribution: "copy",
95
+ reason: "单项目 Kit 自带:用于阶段性清理、收尾和归档。",
96
+ install: [
97
+ { agent: "codex", path: path.join(".agents", "skills", "neat-freak"), mode: "copy" },
98
+ { agent: "claude", path: path.join(".claude", "skills", "neat-freak"), mode: "copy" }
99
+ ]
100
+ }
101
+ ],
102
+ "local-matter": [
103
+ {
104
+ id: "neat-freak",
105
+ name: "Neat Freak",
106
+ source: path.join("skills", "neat-freak"),
107
+ sourceKind: "kit",
108
+ type: "kit-bundled",
109
+ distribution: "copy",
110
+ reason: "长期单项目 Kit 自带:用于事项收尾、项目记忆清理和归档。",
111
+ install: [
112
+ { agent: "codex", path: path.join(".agents", "skills", "neat-freak"), mode: "copy" },
113
+ { agent: "claude", path: path.join(".claude", "skills", "neat-freak"), mode: "copy" }
114
+ ]
115
+ }
116
+ ]
117
+ };
118
+
70
119
  async function run(argv) {
71
120
  const command = argv[0];
72
121
  if (!command || command === "--help" || command === "-h") {
@@ -142,7 +191,8 @@ async function init(argv) {
142
191
  workspaceType,
143
192
  workspaceConfig,
144
193
  pack,
145
- formalSource
194
+ formalSource,
195
+ includeSkills: !options.noSkills
146
196
  });
147
197
 
148
198
  printPlan(plan, options.dryRun);
@@ -246,6 +296,8 @@ function parseArgs(argv) {
246
296
  options.strict = true;
247
297
  } else if (arg === "--verbose") {
248
298
  options.verbose = true;
299
+ } else if (arg === "--no-skills") {
300
+ options.noSkills = true;
249
301
  } else if (arg === "--inventory-depth") {
250
302
  options.inventoryDepth = readValue(argv, ++i, arg);
251
303
  } else if (arg === "--inventory-limit") {
@@ -480,6 +532,7 @@ function doctor(argv) {
480
532
  checkCoreRoles(result, workspaceRoot, state);
481
533
  checkPackInstallations(result, workspaceRoot, state);
482
534
  checkBlueprintCustomization(result, workspaceRoot, state);
535
+ checkSkillInstallations(result, workspaceRoot, state);
483
536
 
484
537
  return finishDoctor(result, options);
485
538
  }
@@ -512,6 +565,7 @@ function doctorCollect(targetDir) {
512
565
  checkCoreRoles(result, workspaceRoot, state);
513
566
  checkPackInstallations(result, workspaceRoot, state);
514
567
  checkBlueprintCustomization(result, workspaceRoot, state);
568
+ checkSkillInstallations(result, workspaceRoot, state);
515
569
  result.ok = result.summary.fail === 0;
516
570
  result.strict_ok = result.ok;
517
571
  result.exitCode = result.ok ? 0 : 1;
@@ -526,6 +580,7 @@ function createDoctorResult(targetDir) {
526
580
  workspace_root: null,
527
581
  target: targetDir,
528
582
  workspace: null,
583
+ skills: null,
529
584
  upgrade: null,
530
585
  inventory: null,
531
586
  signals: null,
@@ -1122,6 +1177,99 @@ function checkBlueprintCustomization(result, workspaceRoot, state) {
1122
1177
  }
1123
1178
  }
1124
1179
 
1180
+ function checkSkillInstallations(result, workspaceRoot, state) {
1181
+ const manifestPath = path.join(workspaceRoot, ".starwork", "skills.json");
1182
+ const skills = {
1183
+ project_manifest: {
1184
+ exists: fs.existsSync(manifestPath),
1185
+ path: ".starwork/skills.json",
1186
+ count: 0
1187
+ },
1188
+ registry: null,
1189
+ mounts: []
1190
+ };
1191
+ result.skills = skills;
1192
+
1193
+ if (!skills.project_manifest.exists) {
1194
+ addCheck(result, "skills.project_manifest.exists", "warn", "缺少项目 Skill 清单 .starwork/skills.json。", ".starwork/skills.json");
1195
+ } else {
1196
+ addCheck(result, "skills.project_manifest.exists", "pass", "Project skill manifest exists", ".starwork/skills.json");
1197
+ let manifest;
1198
+ try {
1199
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
1200
+ } catch (error) {
1201
+ addCheck(result, "skills.project_manifest.parse", "fail", `无法解析项目 Skill 清单:${error.message}`, ".starwork/skills.json");
1202
+ manifest = null;
1203
+ }
1204
+ if (manifest) {
1205
+ if (manifest.schema === "starwork.project_skills.v0.1") {
1206
+ addCheck(result, "skills.project_manifest.schema", "pass", "Project skill manifest schema is valid", ".starwork/skills.json");
1207
+ } else {
1208
+ addCheck(result, "skills.project_manifest.schema", "fail", "项目 Skill 清单 schema 不正确。", ".starwork/skills.json");
1209
+ }
1210
+ const manifestSkills = Array.isArray(manifest.skills) ? manifest.skills : [];
1211
+ skills.project_manifest.count = manifestSkills.length;
1212
+ for (const skill of manifestSkills) {
1213
+ if (!skill?.id) {
1214
+ addCheck(result, "skills.id.exists", "fail", "项目 Skill 清单中存在缺少 id 的条目。", ".starwork/skills.json");
1215
+ continue;
1216
+ }
1217
+ for (const mount of skill.mounts || []) {
1218
+ if (!mount?.path) continue;
1219
+ const normalized = normalizeRelativePath(mount.path);
1220
+ const exists = fs.existsSync(path.join(workspaceRoot, normalized));
1221
+ skills.mounts.push({
1222
+ id: skill.id,
1223
+ path: normalized,
1224
+ mode: mount.mode || null,
1225
+ status: exists ? "ok" : "missing"
1226
+ });
1227
+ if (exists) {
1228
+ addCheck(result, "skills.mount.exists", "pass", `Skill mount exists: ${skill.id}`, normalized);
1229
+ } else {
1230
+ addCheck(result, "skills.mount.exists", "fail", `Skill ${skill.id} 缺少挂载路径:${normalized}`, normalized);
1231
+ }
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1236
+
1237
+ if (state.workspace_type === "hub") {
1238
+ const registryPath = path.join(workspaceRoot, "skills", "registry.json");
1239
+ skills.registry = {
1240
+ exists: fs.existsSync(registryPath),
1241
+ path: "skills/registry.json",
1242
+ count: 0
1243
+ };
1244
+ if (!skills.registry.exists) {
1245
+ addCheck(result, "skills.registry.exists", "warn", "Hub 缺少托管 Skill 注册表。", "skills/registry.json");
1246
+ return;
1247
+ }
1248
+ addCheck(result, "skills.registry.exists", "pass", "Hub skill registry exists", "skills/registry.json");
1249
+ let registry;
1250
+ try {
1251
+ registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
1252
+ } catch (error) {
1253
+ addCheck(result, "skills.registry.parse", "fail", `无法解析 Hub Skill registry:${error.message}`, "skills/registry.json");
1254
+ return;
1255
+ }
1256
+ if (registry.schema === "starwork.skill_registry.v0.1") {
1257
+ addCheck(result, "skills.registry.schema", "pass", "Hub skill registry schema is valid", "skills/registry.json");
1258
+ } else {
1259
+ addCheck(result, "skills.registry.schema", "fail", "Hub Skill registry schema 不正确。", "skills/registry.json");
1260
+ }
1261
+ const registrySkills = Array.isArray(registry.skills) ? registry.skills : [];
1262
+ skills.registry.count = registrySkills.length;
1263
+ for (const skill of registrySkills) {
1264
+ if (!skill?.id) {
1265
+ addCheck(result, "skills.registry.id.exists", "fail", "Hub Skill registry 中存在缺少 id 的条目。", "skills/registry.json");
1266
+ continue;
1267
+ }
1268
+ checkPathExists(result, workspaceRoot, path.join("skills", skill.id), "skills.registry.source.exists", `Hub skill source exists: ${skill.id}`, `Hub 托管 Skill 缺少目录:skills/${skill.id}`);
1269
+ }
1270
+ }
1271
+ }
1272
+
1125
1273
  function checkPathExists(result, workspaceRoot, relativePath, id, passMessage, failMessage) {
1126
1274
  const normalized = normalizeRelativePath(relativePath);
1127
1275
  if (fs.existsSync(path.join(workspaceRoot, normalized))) {
@@ -1667,7 +1815,7 @@ function ask(question) {
1667
1815
  });
1668
1816
  }
1669
1817
 
1670
- function buildInitPlan({ targetDir, workspaceName, workspaceType, workspaceConfig, pack, formalSource }) {
1818
+ function buildInitPlan({ targetDir, workspaceName, workspaceType, workspaceConfig, pack, formalSource, includeSkills = true }) {
1671
1819
  const kitDir = path.join(PRODUCT_ROOT, "core", "kits", workspaceConfig.kit);
1672
1820
  if (!fs.existsSync(kitDir)) {
1673
1821
  throw new Error(`找不到 Kit:${workspaceConfig.kit}`);
@@ -1721,6 +1869,14 @@ function buildInitPlan({ targetDir, workspaceName, workspaceType, workspaceConfi
1721
1869
  actions.push(fileAction(targetDir, target, content));
1722
1870
  }
1723
1871
 
1872
+ const kitSkillPlan = includeSkills
1873
+ ? buildKitSkillPlan({ targetDir, kit: workspaceConfig.kit, installedBy: "starwork init" })
1874
+ : { actions: [], records: [] };
1875
+ actions.push(...kitSkillPlan.actions);
1876
+ if (workspaceType === "hub") {
1877
+ actions.push(fileAction(targetDir, path.join("skills", "registry.json"), renderHubSkillRegistry([])));
1878
+ }
1879
+
1724
1880
  const workspaceState = {
1725
1881
  schema: "starwork.workspace.v0.1",
1726
1882
  core: "0.1",
@@ -1741,6 +1897,7 @@ function buildInitPlan({ targetDir, workspaceName, workspaceType, workspaceConfi
1741
1897
  created_by: "starwork init"
1742
1898
  };
1743
1899
  actions.push(fileAction(targetDir, path.join(".starwork", "workspace.json"), `${JSON.stringify(workspaceState, null, 2)}\n`));
1900
+ actions.push(fileAction(targetDir, path.join(".starwork", "skills.json"), renderProjectSkillsManifest(kitSkillPlan.records)));
1744
1901
 
1745
1902
  return {
1746
1903
  targetDir,
@@ -1750,10 +1907,191 @@ function buildInitPlan({ targetDir, workspaceName, workspaceType, workspaceConfi
1750
1907
  kit: workspaceConfig.kit,
1751
1908
  pack,
1752
1909
  formalSource,
1910
+ skills: kitSkillPlan.records,
1753
1911
  actions: dedupeActions(actions)
1754
1912
  };
1755
1913
  }
1756
1914
 
1915
+ function buildKitSkillPlan({ targetDir, kit, installedBy }) {
1916
+ const now = new Date().toISOString();
1917
+ const actions = [];
1918
+ const records = [];
1919
+ const skills = KIT_BUNDLED_SKILLS[kit] || [];
1920
+
1921
+ for (const skill of skills) {
1922
+ const sourceDir = path.join(PRODUCT_ROOT, skill.source);
1923
+ if (!fs.existsSync(sourceDir)) {
1924
+ throw new Error(`Kit ${kit} 声明的 Skill 不存在:${skill.id}`);
1925
+ }
1926
+
1927
+ const mounts = [];
1928
+ for (const install of skill.install || []) {
1929
+ if (install.mode === "copy") {
1930
+ actions.push(...copyDirectoryFiles(PRODUCT_ROOT, skill.source, targetDir, install.path));
1931
+ } else if (install.mode === "symlink") {
1932
+ if (!install.source) {
1933
+ throw new Error(`Skill ${skill.id} 的 symlink 安装缺少 source。`);
1934
+ }
1935
+ actions.push(symlinkAction(targetDir, install.path, path.join(targetDir, install.source)));
1936
+ } else {
1937
+ throw new Error(`Skill ${skill.id} 不支持安装模式:${install.mode}`);
1938
+ }
1939
+ mounts.push({
1940
+ agent: install.agent,
1941
+ path: normalizeRelativePath(install.path),
1942
+ mode: install.mode
1943
+ });
1944
+ }
1945
+
1946
+ records.push({
1947
+ id: skill.id,
1948
+ name: skill.name,
1949
+ type: skill.type || "kit-bundled",
1950
+ source: {
1951
+ kind: skill.sourceKind || "kit",
1952
+ kit,
1953
+ manifest_id: skill.id
1954
+ },
1955
+ distribution: skill.distribution || "copy",
1956
+ mounts,
1957
+ reason: skill.reason,
1958
+ installed_by: installedBy,
1959
+ installed_at: now
1960
+ });
1961
+ }
1962
+
1963
+ return { actions, records };
1964
+ }
1965
+
1966
+ function renderProjectSkillsManifest(records) {
1967
+ return `${JSON.stringify({
1968
+ schema: "starwork.project_skills.v0.1",
1969
+ updated_at: new Date().toISOString(),
1970
+ skills: records
1971
+ }, null, 2)}\n`;
1972
+ }
1973
+
1974
+ function renderHubSkillRegistry(skills) {
1975
+ return `${JSON.stringify({
1976
+ schema: "starwork.skill_registry.v0.1",
1977
+ owner: "hub",
1978
+ updated_at: new Date().toISOString(),
1979
+ skills
1980
+ }, null, 2)}\n`;
1981
+ }
1982
+
1983
+ function buildSpawnSkillPlan({ hubRoot, targetDir, blueprint, kit, installedBy }) {
1984
+ const registry = readHubSkillRegistry(hubRoot);
1985
+ const registrySkills = Array.isArray(registry.skills) ? registry.skills : [];
1986
+ const kitPlan = buildKitSkillPlan({ targetDir, kit, installedBy });
1987
+ const kitSkillIds = new Set(kitPlan.records.map((record) => record.id));
1988
+ for (const requestedSkill of blueprint?.skills || []) {
1989
+ if (requestedSkill.source === "kit" && !kitSkillIds.has(requestedSkill.id)) {
1990
+ throw new Error(`Kit ${kit} 未声明自带 Skill:${requestedSkill.id}`);
1991
+ }
1992
+ }
1993
+ const selected = selectSpawnSkills(registrySkills, blueprint);
1994
+ const now = new Date().toISOString();
1995
+ const actions = [...kitPlan.actions];
1996
+ const records = [...kitPlan.records];
1997
+
1998
+ for (const selectedSkill of selected) {
1999
+ const registrySkill = registrySkills.find((skill) => skill.id === selectedSkill.id);
2000
+ if (!registrySkill) {
2001
+ throw new Error(`Hub 托管 Skill 未登记:${selectedSkill.id}`);
2002
+ }
2003
+ const distribution = selectedSkill.distribution || registrySkill.distribution?.mode || "symlink";
2004
+ if (!["symlink", "copy"].includes(distribution)) {
2005
+ throw new Error(`Hub 托管 Skill ${selectedSkill.id} 的分发模式不支持:${distribution}`);
2006
+ }
2007
+ const sourceDir = path.join(hubRoot, "skills", selectedSkill.id);
2008
+ if (!fs.existsSync(sourceDir)) {
2009
+ throw new Error(`Hub 托管 Skill 缺少目录:skills/${selectedSkill.id}`);
2010
+ }
2011
+
2012
+ const mounts = [];
2013
+ for (const agent of ["codex", "claude"]) {
2014
+ const base = agent === "codex" ? path.join(".agents", "skills") : path.join(".claude", "skills");
2015
+ const target = path.join(base, selectedSkill.id);
2016
+ if (distribution === "symlink") {
2017
+ actions.push(symlinkAction(targetDir, target, sourceDir));
2018
+ } else {
2019
+ actions.push(...copyDirectoryFiles(hubRoot, path.join("skills", selectedSkill.id), targetDir, target));
2020
+ }
2021
+ mounts.push({
2022
+ agent,
2023
+ path: normalizeRelativePath(target),
2024
+ mode: distribution
2025
+ });
2026
+ }
2027
+
2028
+ records.push({
2029
+ id: selectedSkill.id,
2030
+ name: registrySkill.name || selectedSkill.id,
2031
+ type: registrySkill.type || "hub-managed",
2032
+ source: {
2033
+ kind: "hub",
2034
+ hub_path: hubRoot,
2035
+ registry_id: selectedSkill.id
2036
+ },
2037
+ distribution,
2038
+ mounts,
2039
+ reason: selectedSkill.reason || registrySkill.description || "Hub 托管 Skill 按本次 spawn 选择分发。",
2040
+ installed_by: installedBy,
2041
+ installed_at: now
2042
+ });
2043
+ }
2044
+
2045
+ return { actions, records };
2046
+ }
2047
+
2048
+ function selectSpawnSkills(registrySkills, blueprint) {
2049
+ const selected = [];
2050
+ const seen = new Set();
2051
+ for (const skill of blueprint?.skills || []) {
2052
+ if (skill.source && skill.source !== "hub") continue;
2053
+ selected.push(skill);
2054
+ seen.add(skill.id);
2055
+ }
2056
+ for (const skill of registrySkills) {
2057
+ if (!skill?.id || seen.has(skill.id)) continue;
2058
+ if (skill.distribution?.default_for_spawn) {
2059
+ selected.push({
2060
+ id: skill.id,
2061
+ source: "hub",
2062
+ distribution: skill.distribution.mode,
2063
+ reason: skill.description
2064
+ });
2065
+ seen.add(skill.id);
2066
+ }
2067
+ }
2068
+ return selected;
2069
+ }
2070
+
2071
+ function readHubSkillRegistry(hubRoot) {
2072
+ const registryPath = path.join(hubRoot, "skills", "registry.json");
2073
+ if (!fs.existsSync(registryPath)) {
2074
+ return {
2075
+ schema: "starwork.skill_registry.v0.1",
2076
+ owner: "hub",
2077
+ skills: []
2078
+ };
2079
+ }
2080
+ let registry;
2081
+ try {
2082
+ registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
2083
+ } catch (error) {
2084
+ throw new Error(`无法读取 Hub Skill registry:${error.message}`);
2085
+ }
2086
+ if (registry.schema !== "starwork.skill_registry.v0.1") {
2087
+ throw new Error("Hub Skill registry schema 必须是 starwork.skill_registry.v0.1。");
2088
+ }
2089
+ if (!Array.isArray(registry.skills)) {
2090
+ throw new Error("Hub Skill registry 的 skills 必须是数组。");
2091
+ }
2092
+ return registry;
2093
+ }
2094
+
1757
2095
  function resolveHubRoot(hubPath) {
1758
2096
  const resolved = path.resolve(hubPath);
1759
2097
  return requireWorkspaceRoot(resolved);
@@ -1857,6 +2195,17 @@ function validateSpawnBlueprintForMode(blueprint, mode, modeConfig) {
1857
2195
  throw new Error("blueprint seed.on_conflict 只支持 error、skip 或 create_new。");
1858
2196
  }
1859
2197
  }
2198
+ for (const skill of blueprint.skills || []) {
2199
+ if (!skill?.id || typeof skill.id !== "string") {
2200
+ throw new Error("blueprint skills 每一项都必须包含 id。");
2201
+ }
2202
+ if (skill.source && !["hub", "kit"].includes(skill.source)) {
2203
+ throw new Error("blueprint skill.source 只支持 hub 或 kit。");
2204
+ }
2205
+ if (skill.distribution && !["symlink", "copy"].includes(skill.distribution)) {
2206
+ throw new Error("blueprint skill.distribution 只支持 symlink 或 copy。");
2207
+ }
2208
+ }
1860
2209
  }
1861
2210
 
1862
2211
  function applySpawnBlueprintModeConfig(modeConfig, blueprint) {
@@ -1921,8 +2270,16 @@ function buildSpawnPlan({ hubRoot, hubState, targetDir, projectName, projectId,
1921
2270
  }
1922
2271
 
1923
2272
  actions.push(symlinkAction(targetDir, "知识", path.join(hubRoot, "知识")));
1924
- actions.push(symlinkAction(targetDir, path.join(".agents", "skills"), path.join(hubRoot, "skills")));
1925
- actions.push(symlinkAction(targetDir, path.join(".claude", "skills"), path.join(hubRoot, "skills")));
2273
+ actions.push(directoryAction(targetDir, path.join(".agents", "skills")));
2274
+ actions.push(directoryAction(targetDir, path.join(".claude", "skills")));
2275
+ const skillPlan = buildSpawnSkillPlan({
2276
+ hubRoot,
2277
+ targetDir,
2278
+ blueprint,
2279
+ kit: modeConfig.kit,
2280
+ installedBy: blueprint ? "starwork spawn --blueprint" : "starwork spawn"
2281
+ });
2282
+ actions.push(...skillPlan.actions);
1926
2283
 
1927
2284
  const workspaceState = {
1928
2285
  schema: "starwork.workspace.v0.1",
@@ -1984,14 +2341,20 @@ function buildSpawnPlan({ hubRoot, hubState, targetDir, projectName, projectId,
1984
2341
  mode: "readonly-link"
1985
2342
  },
1986
2343
  skills: {
1987
- source: "skills/",
2344
+ source: "skills/registry.json",
1988
2345
  target: [".agents/skills/", ".claude/skills/"],
1989
- mode: "symlink"
2346
+ mode: "selected",
2347
+ items: skillPlan.records.map((record) => ({
2348
+ id: record.id,
2349
+ distribution: record.distribution,
2350
+ mounts: record.mounts
2351
+ }))
1990
2352
  }
1991
2353
  }
1992
2354
  };
1993
2355
 
1994
2356
  actions.push(fileAction(targetDir, path.join(".starwork", "workspace.json"), `${JSON.stringify(workspaceState, null, 2)}\n`));
2357
+ actions.push(fileAction(targetDir, path.join(".starwork", "skills.json"), renderProjectSkillsManifest(skillPlan.records)));
1995
2358
  actions.push(fileAction(targetDir, ".core-sync.json", `${JSON.stringify(coreSync, null, 2)}\n`));
1996
2359
  actions.push(fileAction(targetDir, path.join("_系统", "上下文", "当前项目.md"), renderSpawnProjectStatus({
1997
2360
  projectName,
@@ -2022,7 +2385,7 @@ function buildSpawnPlan({ hubRoot, hubState, targetDir, projectName, projectId,
2022
2385
  identity: "snapshot",
2023
2386
  lessons: "snapshot",
2024
2387
  knowledge: "readonly-link",
2025
- skills: "symlink"
2388
+ skills: "selected"
2026
2389
  }
2027
2390
  }
2028
2391
  ]
@@ -2039,6 +2402,7 @@ function buildSpawnPlan({ hubRoot, hubState, targetDir, projectName, projectId,
2039
2402
  modeLabel: modeConfig.label,
2040
2403
  kit: modeConfig.kit,
2041
2404
  blueprint,
2405
+ skills: skillPlan.records,
2042
2406
  actions: dedupeActions(actions)
2043
2407
  };
2044
2408
  }
@@ -2049,9 +2413,7 @@ function shouldSpawnOverrideKitFile(relativePath) {
2049
2413
  || normalized === "_系统/上下文/当前项目.md"
2050
2414
  || normalized.startsWith("_系统/身份/")
2051
2415
  || normalized.startsWith("_系统/教训/")
2052
- || normalized.startsWith("知识/")
2053
- || normalized.startsWith(".agents/skills/")
2054
- || normalized.startsWith(".claude/skills/");
2416
+ || normalized.startsWith("知识/");
2055
2417
  }
2056
2418
 
2057
2419
  function appendBlueprintRulesToAgents(content, blueprint, variables) {
@@ -2412,6 +2774,9 @@ function printPlan(plan, dryRun) {
2412
2774
  console.log(`Pack:${plan.pack.name || PACK_LABELS[plan.pack.id] || plan.pack.id} (${plan.pack.id})`);
2413
2775
  console.log(`工作台名称:${plan.workspaceName}`);
2414
2776
  console.log(`正式成果位置:${plan.formalSource}`);
2777
+ if (plan.skills?.length) {
2778
+ console.log(`Kit 自带 Skill:${plan.skills.map((skill) => skill.id).join(", ")}`);
2779
+ }
2415
2780
  console.log("");
2416
2781
 
2417
2782
  if (creates.length) {
@@ -2447,6 +2812,9 @@ function printSpawnPlan(plan, dryRun) {
2447
2812
  console.log(`目标目录:${plan.targetDir}`);
2448
2813
  console.log(`模式:${plan.modeLabel} (${plan.mode})`);
2449
2814
  console.log(`Kit:${plan.kit}`);
2815
+ if (plan.skills?.length) {
2816
+ console.log(`分发 Skill:${plan.skills.map((skill) => skill.id).join(", ")}`);
2817
+ }
2450
2818
  if (plan.blueprint) {
2451
2819
  console.log(`Blueprint:${plan.blueprint.__path}`);
2452
2820
  console.log(`正式事实源:${plan.blueprint.paths?.formal_source || "(默认)"}`);
@@ -2635,6 +3003,7 @@ Options:
2635
3003
  --strict
2636
3004
  --verbose
2637
3005
  --agent <codex|claude|cursor|trae|all>
3006
+ --no-skills
2638
3007
  --yes, -y
2639
3008
  `);
2640
3009
  }