@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 +15 -7
- package/cli/README.md +2 -0
- package/cli/src/cli.js +379 -10
- package/cli/test/init.test.js +73 -1
- package/core/README.md +3 -0
- package/core/capabilities/skill-mount/capability.md +13 -2
- package/core/skill-management-spec.md +572 -0
- package/docs/README.md +1 -1
- package/docs/agent-install-guide.md +14 -6
- package/docs/alpha-test-guide.md +11 -9
- package/docs/cli-skill-registry.html +733 -0
- package/docs/index.html +1 -0
- package/docs/roadmap.md +3 -2
- package/docs/v0.1-plan.md +1 -1
- package/package.json +1 -1
- package/skills/README.md +2 -0
- package/skills/neat-freak/SKILL.md +32 -0
- package/skills/neat-freak/agents/openai.yaml +2 -0
package/README.md
CHANGED
|
@@ -28,25 +28,32 @@ npx @jennie-shawn/starwork --help
|
|
|
28
28
|
|
|
29
29
|
## 安装 Skills
|
|
30
30
|
|
|
31
|
-
StarWork skills
|
|
31
|
+
StarWork skills 分两类管理:
|
|
32
32
|
|
|
33
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
-
-
|
|
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(
|
|
1925
|
-
actions.push(
|
|
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: "
|
|
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: "
|
|
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
|
}
|