@jennie-shawn/starwork 0.1.0-alpha.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.
Files changed (228) hide show
  1. package/README.md +18 -0
  2. package/adapters/README.md +5 -0
  3. package/cli/README.md +52 -0
  4. package/cli/adapt-spec.md +61 -0
  5. package/cli/bin/starwork.js +8 -0
  6. package/cli/doctor-spec.md +516 -0
  7. package/cli/init-spec.md +435 -0
  8. package/cli/pack-install-spec.md +63 -0
  9. package/cli/spawn-blueprint-spec.md +657 -0
  10. package/cli/spawn-spec.md +439 -0
  11. package/cli/src/cli.js +1955 -0
  12. package/cli/test/init.test.js +436 -0
  13. package/core/README.md +51 -0
  14. package/core/baseline/README.md +12 -0
  15. package/core/baseline/file-boundaries.md +25 -0
  16. package/core/baseline/health-check.md +27 -0
  17. package/core/baseline/roles.md +26 -0
  18. package/core/baseline/spec.md +47 -0
  19. package/core/capabilities/README.md +15 -0
  20. package/core/capabilities/decisions/capability.md +26 -0
  21. package/core/capabilities/local-identity/capability.md +15 -0
  22. package/core/capabilities/local-identity/templates/identity/README.md +5 -0
  23. package/core/capabilities/local-lessons/capability.md +15 -0
  24. package/core/capabilities/local-lessons/templates/lessons/README.md +5 -0
  25. package/core/capabilities/main-repo-sync/capability.md +133 -0
  26. package/core/capabilities/main-repo-sync/templates/.core-sync.example.json +31 -0
  27. package/core/capabilities/main-repo-sync/templates/.internal/README.md +5 -0
  28. package/core/capabilities/matter-mode/capability.md +46 -0
  29. package/core/capabilities/matter-mode/templates/matters/_matter-template/README.md +17 -0
  30. package/core/capabilities/matter-mode/templates/matters/_matter-template/drafts/.gitkeep +1 -0
  31. package/core/capabilities/matter-mode/templates/matters/_matter-template/handoff.md +3 -0
  32. package/core/capabilities/matter-mode/templates/matters/_matter-template/notes.md +5 -0
  33. package/core/capabilities/matter-mode/templates/matters/_matter-template/progress.md +5 -0
  34. package/core/capabilities/matter-mode/templates/matters/registry.md +16 -0
  35. package/core/capabilities/skill-mount/capability.md +24 -0
  36. package/core/capabilities/starter-outputs/capability.md +25 -0
  37. package/core/capabilities/starter-outputs/templates/outputs/drafts/README.md +5 -0
  38. package/core/capabilities/starter-outputs/templates/outputs/final/README.md +5 -0
  39. package/core/capabilities/starter-outputs/templates/references/README.md +5 -0
  40. package/core/core-v0.1-protocol.md +201 -0
  41. package/core/kits/README.md +15 -0
  42. package/core/kits/hub/.incoming/README.md +5 -0
  43. package/core/kits/hub/AGENTS.md +26 -0
  44. package/core/kits/hub/README.md +25 -0
  45. package/core/kits/hub/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//351/241/271/347/233/256/347/212/266/346/200/201.md +24 -0
  46. package/core/kits/hub/_/347/263/273/347/273/237//344/273/273/345/212/241//345/275/223/345/211/215/345/267/245/344/275/234.md +17 -0
  47. package/core/kits/hub/identity/README.md +5 -0
  48. package/core/kits/hub/lessons/README.md +5 -0
  49. package/core/kits/hub/skills/README.md +5 -0
  50. package/core/kits/hub//347/237/245/350/257/206/README.md +5 -0
  51. package/core/kits/hub//351/241/271/347/233/256/README.md +5 -0
  52. package/core/kits/hub//351/241/271/347/233/256/registry.json +4 -0
  53. package/core/kits/hub//351/241/271/347/233/256//350/201/224/347/273/234/README.md +5 -0
  54. package/core/kits/kit-structure-reference.md +253 -0
  55. package/core/kits/local-matter/AGENTS.md +23 -0
  56. package/core/kits/local-matter/README.md +22 -0
  57. package/core/kits/local-matter/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//345/206/263/347/255/226.md +7 -0
  58. package/core/kits/local-matter/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//351/241/271/347/233/256/347/212/266/346/200/201.md +25 -0
  59. package/core/kits/local-matter/_/347/263/273/347/273/237//344/273/273/345/212/241//345/275/223/345/211/215/345/267/245/344/275/234.md +17 -0
  60. package/core/kits/local-matter/_/347/263/273/347/273/237//346/225/231/350/256/255/README.md +5 -0
  61. package/core/kits/local-matter/_/347/263/273/347/273/237//350/272/253/344/273/275/README.md +5 -0
  62. package/core/kits/local-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277/README.md +17 -0
  63. package/core/kits/local-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//344/272/244/346/216/245.md +3 -0
  64. package/core/kits/local-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//347/254/224/350/256/260.md +5 -0
  65. package/core/kits/local-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//350/215/211/347/250/277/.gitkeep +1 -0
  66. package/core/kits/local-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//350/277/233/345/272/246.md +5 -0
  67. package/core/kits/local-matter//344/272/213/351/241/271//346/263/250/345/206/214/350/241/250.md +16 -0
  68. package/core/kits/local-matter//345/217/202/350/200/203/350/265/204/346/226/231/README.md +5 -0
  69. package/core/kits/local-matter//350/276/223/345/207/272//347/241/256/350/256/244/346/210/220/346/236/234/README.md +5 -0
  70. package/core/kits/local-matter//350/276/223/345/207/272//350/215/211/347/250/277/README.md +5 -0
  71. package/core/kits/local-starter/AGENTS.md +23 -0
  72. package/core/kits/local-starter/README.md +23 -0
  73. package/core/kits/local-starter/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//351/241/271/347/233/256/347/212/266/346/200/201.md +25 -0
  74. package/core/kits/local-starter/_/347/263/273/347/273/237//344/273/273/345/212/241//345/275/223/345/211/215/345/267/245/344/275/234.md +17 -0
  75. package/core/kits/local-starter/_/347/263/273/347/273/237//346/225/231/350/256/255/README.md +5 -0
  76. package/core/kits/local-starter/_/347/263/273/347/273/237//350/272/253/344/273/275/README.md +5 -0
  77. package/core/kits/local-starter//345/217/202/350/200/203/350/265/204/346/226/231/README.md +5 -0
  78. package/core/kits/local-starter//350/276/223/345/207/272//347/241/256/350/256/244/346/210/220/346/236/234/README.md +5 -0
  79. package/core/kits/local-starter//350/276/223/345/207/272//350/215/211/347/250/277/README.md +5 -0
  80. package/core/kits/satellite-matter/.agents/skills/README.md +5 -0
  81. package/core/kits/satellite-matter/.claude/skills/README.md +5 -0
  82. package/core/kits/satellite-matter/.core-sync.json +31 -0
  83. package/core/kits/satellite-matter/.internal/README.md +5 -0
  84. package/core/kits/satellite-matter/.obsidian/README.md +5 -0
  85. package/core/kits/satellite-matter/AGENTS.md +27 -0
  86. package/core/kits/satellite-matter/CLAUDE.md +5 -0
  87. package/core/kits/satellite-matter/README.md +40 -0
  88. package/core/kits/satellite-matter/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//345/206/263/347/255/226.md +7 -0
  89. package/core/kits/satellite-matter/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//345/275/223/345/211/215/351/241/271/347/233/256.md +29 -0
  90. package/core/kits/satellite-matter/_/347/263/273/347/273/237//344/270/273/345/272/223/345/220/214/346/255/245/README.md +42 -0
  91. package/core/kits/satellite-matter/_/347/263/273/347/273/237//344/273/273/345/212/241//345/275/223/345/211/215/345/267/245/344/275/234.md +17 -0
  92. package/core/kits/satellite-matter/_/347/263/273/347/273/237//346/225/231/350/256/255/README.md +8 -0
  93. package/core/kits/satellite-matter/_/347/263/273/347/273/237//350/267/250/351/241/271/347/233/256/README.md +9 -0
  94. package/core/kits/satellite-matter/_/347/263/273/347/273/237//350/267/250/351/241/271/347/233/256/inbox/README.md +5 -0
  95. package/core/kits/satellite-matter/_/347/263/273/347/273/237//350/267/250/351/241/271/347/233/256/outbox/README.md +5 -0
  96. package/core/kits/satellite-matter/_/347/263/273/347/273/237//350/272/253/344/273/275/README.md +5 -0
  97. package/core/kits/satellite-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277/README.md +17 -0
  98. package/core/kits/satellite-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//344/272/244/346/216/245.md +3 -0
  99. package/core/kits/satellite-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//347/254/224/350/256/260.md +5 -0
  100. package/core/kits/satellite-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//350/215/211/347/250/277/.gitkeep +1 -0
  101. package/core/kits/satellite-matter//344/272/213/351/241/271/_/344/272/213/351/241/271/346/250/241/346/235/277//350/277/233/345/272/246.md +5 -0
  102. package/core/kits/satellite-matter//344/272/213/351/241/271//346/263/250/345/206/214/350/241/250.md +16 -0
  103. package/core/kits/satellite-matter//345/217/202/350/200/203/350/265/204/346/226/231/README.md +5 -0
  104. package/core/kits/satellite-matter//347/237/245/350/257/206/README.md +5 -0
  105. package/core/kits/satellite-matter//350/276/223/345/207/272//347/241/256/350/256/244/346/210/220/346/236/234/README.md +5 -0
  106. package/core/kits/satellite-matter//350/276/223/345/207/272//350/215/211/347/250/277/README.md +5 -0
  107. package/core/kits/satellite-starter/.agents/skills/README.md +5 -0
  108. package/core/kits/satellite-starter/.claude/skills/README.md +5 -0
  109. package/core/kits/satellite-starter/.core-sync.json +31 -0
  110. package/core/kits/satellite-starter/.internal/README.md +5 -0
  111. package/core/kits/satellite-starter/.obsidian/README.md +5 -0
  112. package/core/kits/satellite-starter/AGENTS.md +25 -0
  113. package/core/kits/satellite-starter/CLAUDE.md +5 -0
  114. package/core/kits/satellite-starter/README.md +42 -0
  115. package/core/kits/satellite-starter/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//345/275/223/345/211/215/351/241/271/347/233/256.md +29 -0
  116. package/core/kits/satellite-starter/_/347/263/273/347/273/237//344/270/273/345/272/223/345/220/214/346/255/245/README.md +40 -0
  117. package/core/kits/satellite-starter/_/347/263/273/347/273/237//344/273/273/345/212/241//345/275/223/345/211/215/345/267/245/344/275/234.md +17 -0
  118. package/core/kits/satellite-starter/_/347/263/273/347/273/237//346/225/231/350/256/255/README.md +8 -0
  119. package/core/kits/satellite-starter/_/347/263/273/347/273/237//350/267/250/351/241/271/347/233/256/README.md +9 -0
  120. package/core/kits/satellite-starter/_/347/263/273/347/273/237//350/267/250/351/241/271/347/233/256/inbox/README.md +5 -0
  121. package/core/kits/satellite-starter/_/347/263/273/347/273/237//350/267/250/351/241/271/347/233/256/outbox/README.md +5 -0
  122. package/core/kits/satellite-starter/_/347/263/273/347/273/237//350/272/253/344/273/275/README.md +5 -0
  123. package/core/kits/satellite-starter//345/217/202/350/200/203/350/265/204/346/226/231/README.md +5 -0
  124. package/core/kits/satellite-starter//347/237/245/350/257/206/README.md +5 -0
  125. package/core/kits/satellite-starter//350/276/223/345/207/272//347/241/256/350/256/244/346/210/220/346/236/234/README.md +5 -0
  126. package/core/kits/satellite-starter//350/276/223/345/207/272//350/215/211/347/250/277/README.md +5 -0
  127. package/core/presets/README.md +26 -0
  128. package/core/presets/hub.yaml +10 -0
  129. package/core/presets/local-matter.yaml +14 -0
  130. package/core/presets/local-starter.yaml +12 -0
  131. package/core/presets/satellite-matter.yaml +17 -0
  132. package/core/presets/satellite-starter.yaml +15 -0
  133. package/core/profiles/README.md +18 -0
  134. package/core/profiles/en/labels.json +26 -0
  135. package/core/profiles/en/paths.json +21 -0
  136. package/core/profiles/en/profile.md +22 -0
  137. package/core/profiles/en/reference-kits/local-starter/AGENTS.md +23 -0
  138. package/core/profiles/en/reference-kits/local-starter/README.md +23 -0
  139. package/core/profiles/en/reference-kits/local-starter/_system/context/project-status.md +25 -0
  140. package/core/profiles/en/reference-kits/local-starter/_system/identity/README.md +5 -0
  141. package/core/profiles/en/reference-kits/local-starter/_system/lessons/README.md +5 -0
  142. package/core/profiles/en/reference-kits/local-starter/_system/tasks/current-work.md +17 -0
  143. package/core/profiles/en/reference-kits/local-starter/outputs/drafts/README.md +5 -0
  144. package/core/profiles/en/reference-kits/local-starter/outputs/final/README.md +5 -0
  145. package/core/profiles/en/reference-kits/local-starter/references/README.md +5 -0
  146. package/core/profiles/en/reference-presets/local-starter.yaml +12 -0
  147. package/core/profiles/en/templates/AGENTS.md +23 -0
  148. package/core/profiles/en/templates/_system/context/project-status.md +25 -0
  149. package/core/profiles/en/templates/_system/tasks/current-work.md +17 -0
  150. package/core/profiles/zh/labels.json +26 -0
  151. package/core/profiles/zh/paths.json +21 -0
  152. package/core/profiles/zh/profile.md +22 -0
  153. package/core/profiles/zh/templates/AGENTS.md +23 -0
  154. package/core/profiles/zh/templates/_/347/263/273/347/273/237//344/270/212/344/270/213/346/226/207//351/241/271/347/233/256/347/212/266/346/200/201.md +25 -0
  155. package/core/profiles/zh/templates/_/347/263/273/347/273/237//344/273/273/345/212/241//345/275/223/345/211/215/345/267/245/344/275/234.md +17 -0
  156. package/docs/README.md +18 -0
  157. package/docs/alpha-test-guide.md +124 -0
  158. package/docs/cli-capabilities.html +1088 -0
  159. package/docs/hub-management.html +750 -0
  160. package/docs/index.html +228 -0
  161. package/docs/product-direction.md +64 -0
  162. package/docs/product-shape-business-model.html +1024 -0
  163. package/docs/roadmap.html +899 -0
  164. package/docs/roadmap.md +272 -0
  165. package/docs/v0.1-plan.md +109 -0
  166. package/examples/README.md +3 -0
  167. package/package.json +32 -0
  168. package/packs/README.md +24 -0
  169. package/packs/content-creator/README.md +38 -0
  170. package/packs/content-creator/languages/en.json +40 -0
  171. package/packs/content-creator/languages/zh.json +40 -0
  172. package/packs/content-creator/pack.json +41 -0
  173. package/packs/content-creator/rules/en/file-boundaries.md +10 -0
  174. package/packs/content-creator/rules/en/overview.md +9 -0
  175. package/packs/content-creator/rules/en/review.md +13 -0
  176. package/packs/content-creator/rules/en/workflow.md +8 -0
  177. package/packs/content-creator/rules/zh/file-boundaries.md +10 -0
  178. package/packs/content-creator/rules/zh/overview.md +9 -0
  179. package/packs/content-creator/rules/zh/review.md +13 -0
  180. package/packs/content-creator/rules/zh/workflow.md +8 -0
  181. package/packs/content-creator/seed/en/account-profile/README.md +3 -0
  182. package/packs/content-creator/seed/en/analytics-review/README.md +3 -0
  183. package/packs/content-creator/seed/en/drafts-and-scripts/README.md +3 -0
  184. package/packs/content-creator/seed/en/ideas/README.md +3 -0
  185. package/packs/content-creator/seed/en/materials/README.md +3 -0
  186. package/packs/content-creator/seed/en/published/README.md +3 -0
  187. package/packs/content-creator/seed/zh//345/217/221/345/270/203/350/256/260/345/275/225/README.md +5 -0
  188. package/packs/content-creator/seed/zh//346/225/260/346/215/256/345/244/215/347/233/230/README.md +3 -0
  189. package/packs/content-creator/seed/zh//347/264/240/346/235/220/345/272/223/README.md +5 -0
  190. package/packs/content-creator/seed/zh//350/215/211/347/250/277/344/270/216/350/204/232/346/234/254/README.md +5 -0
  191. package/packs/content-creator/seed/zh//350/264/246/345/217/267/345/256/232/344/275/215/README.md +3 -0
  192. package/packs/content-creator/seed/zh//351/200/211/351/242/230/346/261/240/README.md +3 -0
  193. package/packs/content-creator/templates/en/content-brief.md +21 -0
  194. package/packs/content-creator/templates/en/publish-record.md +23 -0
  195. package/packs/content-creator/templates/en/weekly-review.md +21 -0
  196. package/packs/content-creator/templates/zh/content-brief.md +30 -0
  197. package/packs/content-creator/templates/zh/publish-record.md +21 -0
  198. package/packs/content-creator/templates/zh/weekly-review.md +22 -0
  199. package/packs/general/README.md +5 -0
  200. package/packs/general/languages/en.json +19 -0
  201. package/packs/general/languages/zh.json +19 -0
  202. package/packs/general/pack.json +20 -0
  203. package/packs/general/rules/en/overview.md +7 -0
  204. package/packs/general/rules/en/workflow.md +6 -0
  205. package/packs/general/rules/zh/overview.md +7 -0
  206. package/packs/general/rules/zh/workflow.md +6 -0
  207. package/packs/hub-management/README.md +5 -0
  208. package/packs/hub-management/languages/en.json +27 -0
  209. package/packs/hub-management/languages/zh.json +27 -0
  210. package/packs/hub-management/pack.json +28 -0
  211. package/packs/hub-management/rules/en/overview.md +7 -0
  212. package/packs/hub-management/rules/en/workflow.md +6 -0
  213. package/packs/hub-management/rules/zh/overview.md +7 -0
  214. package/packs/hub-management/rules/zh/workflow.md +6 -0
  215. package/packs/hub-management/seed/en/.incoming/README.md +3 -0
  216. package/packs/hub-management/seed/en/projects/coordination/README.md +5 -0
  217. package/packs/hub-management/seed/en/projects/registry.json +4 -0
  218. package/packs/hub-management/seed/zh/.incoming/README.md +5 -0
  219. package/packs/hub-management/seed/zh//351/241/271/347/233/256/registry.json +4 -0
  220. package/packs/hub-management/seed/zh//351/241/271/347/233/256//350/201/224/347/273/234/README.md +5 -0
  221. package/packs/pack-structure-spec.md +315 -0
  222. package/schemas/README.md +3 -0
  223. package/skills/README.md +17 -0
  224. package/skills/starworkInit/SKILL.md +289 -0
  225. package/skills/starworkInit/agents/openai.yaml +7 -0
  226. package/skills/starworkInit-spec.md +585 -0
  227. package/skills/starworkSpawn/SKILL.md +136 -0
  228. package/skills/starworkSpawn/agents/openai.yaml +7 -0
package/cli/src/cli.js ADDED
@@ -0,0 +1,1955 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const readline = require("readline");
4
+
5
+ const PRODUCT_ROOT = path.resolve(__dirname, "..", "..");
6
+
7
+ const WORKSPACE_TYPES = {
8
+ "single-light": {
9
+ label: "轻量单项目",
10
+ kit: "local-starter",
11
+ defaultPack: "general",
12
+ description: "适合放资料、写草稿、整理最终成果。"
13
+ },
14
+ "single-matter": {
15
+ label: "长期单项目",
16
+ kit: "local-matter",
17
+ defaultPack: "general",
18
+ description: "适合需要事项追踪、跨会话接力、长期沉淀过程的项目。"
19
+ },
20
+ hub: {
21
+ label: "多项目管理中枢",
22
+ kit: "hub",
23
+ defaultPack: "hub-management",
24
+ description: "适合统一管理身份、教训、知识、skills 和多个项目。"
25
+ }
26
+ };
27
+
28
+ const SPAWN_MODES = {
29
+ starter: {
30
+ label: "轻量项目",
31
+ workspaceType: "satellite-starter",
32
+ kit: "satellite-starter",
33
+ formalSource: "输出/确认成果/",
34
+ businessWorkArea: "参考资料/"
35
+ },
36
+ matter: {
37
+ label: "事项型项目",
38
+ workspaceType: "satellite-matter",
39
+ kit: "satellite-matter",
40
+ formalSource: "输出/确认成果/",
41
+ businessWorkArea: "事项/"
42
+ }
43
+ };
44
+
45
+ const PACK_LABELS = {
46
+ general: "通用工作",
47
+ "content-creator": "自媒体内容创作",
48
+ "hub-management": "多项目中枢管理"
49
+ };
50
+
51
+ const ADAPTERS = {
52
+ codex: {
53
+ label: "Codex",
54
+ path: null
55
+ },
56
+ claude: {
57
+ label: "Claude Code",
58
+ path: "CLAUDE.md"
59
+ },
60
+ cursor: {
61
+ label: "Cursor",
62
+ path: path.join(".cursor", "rules", "starwork.mdc")
63
+ },
64
+ trae: {
65
+ label: "Trae",
66
+ path: path.join(".trae", "rules", "starwork.md")
67
+ }
68
+ };
69
+
70
+ async function run(argv) {
71
+ const command = argv[0];
72
+ if (!command || command === "--help" || command === "-h") {
73
+ printHelp();
74
+ return;
75
+ }
76
+
77
+ if (command === "init") {
78
+ await init(argv.slice(1));
79
+ return;
80
+ }
81
+
82
+ if (command === "doctor") {
83
+ const result = doctor(argv.slice(1));
84
+ process.exitCode = result.exitCode;
85
+ return;
86
+ }
87
+
88
+ if (command === "spawn") {
89
+ await spawnWorkspace(argv.slice(1));
90
+ return;
91
+ }
92
+
93
+ if (command === "adapt") {
94
+ await adapt(argv.slice(1));
95
+ return;
96
+ }
97
+
98
+ if (command === "pack") {
99
+ await packCommand(argv.slice(1));
100
+ return;
101
+ }
102
+
103
+ throw new Error(`未知命令:${command}`);
104
+ }
105
+
106
+ async function init(argv) {
107
+ const options = parseArgs(argv);
108
+ if (options.help) {
109
+ printHelp();
110
+ return;
111
+ }
112
+ const targetDir = path.resolve(options.target || process.cwd());
113
+
114
+ if (findWorkspaceRoot(targetDir)) {
115
+ console.log("当前目录看起来已经位于 StarWork 工作台内。");
116
+ console.log("你可以运行 starwork doctor 检查状态;v0.1 的 init 暂不处理升级。");
117
+ return;
118
+ }
119
+
120
+ const workspaceType = options.type || await chooseWorkspaceType(options);
121
+ const workspaceConfig = WORKSPACE_TYPES[workspaceType];
122
+ if (!workspaceConfig) {
123
+ throw new Error(`不支持的工作区类型:${workspaceType}`);
124
+ }
125
+
126
+ const packId = options.pack || await choosePack(workspaceType, workspaceConfig, options);
127
+ const language = options.language || getKitLanguage(workspaceConfig.kit);
128
+ const pack = loadPack(packId, language);
129
+ validatePack(pack, workspaceType);
130
+
131
+ const workspaceName = options.name || path.basename(targetDir);
132
+ const formalSource = options.formalSource || pack.overrides?.formal_source || getKitDefaultFormalSource(workspaceConfig.kit);
133
+
134
+ const plan = buildInitPlan({
135
+ targetDir,
136
+ workspaceName,
137
+ workspaceType,
138
+ workspaceConfig,
139
+ pack,
140
+ formalSource
141
+ });
142
+
143
+ printPlan(plan, options.dryRun);
144
+
145
+ if (options.dryRun) {
146
+ return;
147
+ }
148
+
149
+ if (!options.yes && process.stdin.isTTY) {
150
+ const ok = await confirm("是否执行初始化?", true);
151
+ if (!ok) {
152
+ console.log("已取消,没有写入任何文件。");
153
+ return;
154
+ }
155
+ } else if (!options.yes && !process.stdin.isTTY) {
156
+ throw new Error("非交互环境需要传入 --yes 或 --dry-run。");
157
+ }
158
+
159
+ applyPlan(plan);
160
+ console.log("");
161
+ console.log("StarWork 工作台已创建。");
162
+ console.log("");
163
+ console.log("下一步建议:");
164
+ console.log("1. 运行 starwork doctor 检查工作区。");
165
+ console.log("2. 打开 AGENTS.md,确认 Agent 入口规则。");
166
+ console.log("3. 如需生成特定 Agent 适配文件,后续运行 starwork adapt。");
167
+ }
168
+
169
+ async function spawnWorkspace(argv) {
170
+ const options = parseArgs(argv);
171
+ if (options.help) {
172
+ printSpawnHelp();
173
+ return;
174
+ }
175
+ if (options.pack) {
176
+ throw new Error("spawn v0.1 暂不支持 --pack。请先创建项目,再运行 starwork pack install。");
177
+ }
178
+ if (!options.target) {
179
+ throw new Error("spawn 需要指定 --target <path>,避免误把新项目写进当前目录。");
180
+ }
181
+
182
+ const hubRoot = resolveHubRoot(options.hub || process.cwd());
183
+ const hubState = readWorkspaceState(hubRoot);
184
+ assertHealthyHub(hubRoot, hubState);
185
+
186
+ const blueprint = options.blueprint ? loadSpawnBlueprint(options.blueprint) : null;
187
+ const projectName = options.name || blueprint?.name || path.basename(path.resolve(options.target || process.cwd()));
188
+ const targetDir = path.resolve(options.target || path.join(process.cwd(), slugifyProjectId(projectName) || "project"));
189
+ assertSpawnTargetIsEmpty(targetDir);
190
+
191
+ const mode = options.mode || blueprint?.base?.mode || "matter";
192
+ const baseModeConfig = SPAWN_MODES[mode];
193
+ if (!baseModeConfig) {
194
+ throw new Error(`不支持的 spawn 模式:${mode}。可选值:starter、matter。`);
195
+ }
196
+ validateSpawnBlueprintForMode(blueprint, mode, baseModeConfig);
197
+ const modeConfig = applySpawnBlueprintModeConfig(baseModeConfig, blueprint);
198
+
199
+ const status = options.status || "active";
200
+ if (!["active", "paused"].includes(status)) {
201
+ throw new Error("--status 只支持 active 或 paused。");
202
+ }
203
+
204
+ const projectId = options.id || blueprint?.project_id || slugifyProjectId(projectName) || slugifyProjectId(path.basename(targetDir)) || "project";
205
+ const plan = buildSpawnPlan({
206
+ hubRoot,
207
+ hubState,
208
+ targetDir,
209
+ projectName,
210
+ projectId,
211
+ status,
212
+ mode,
213
+ modeConfig,
214
+ blueprint
215
+ });
216
+
217
+ printSpawnPlan(plan, options.dryRun);
218
+ if (options.dryRun) return;
219
+
220
+ await confirmOrThrow(options, "是否从中枢生成新项目工作台?");
221
+ applyPlan(plan);
222
+ console.log("");
223
+ console.log("StarWork 项目工作台已生成。");
224
+ console.log("");
225
+ console.log("下一步建议:");
226
+ console.log(`1. 运行 starwork doctor --target ${plan.targetDir}`);
227
+ console.log("2. 打开 _系统/上下文/当前项目.md,补充项目目标和近期重点。");
228
+ }
229
+
230
+ function parseArgs(argv) {
231
+ const options = {};
232
+ for (let i = 0; i < argv.length; i += 1) {
233
+ const arg = argv[i];
234
+ if (arg === "--yes" || arg === "-y") {
235
+ options.yes = true;
236
+ } else if (arg === "--dry-run") {
237
+ options.dryRun = true;
238
+ } else if (arg === "--json") {
239
+ options.json = true;
240
+ } else if (arg === "--strict") {
241
+ options.strict = true;
242
+ } else if (arg === "--verbose") {
243
+ options.verbose = true;
244
+ } else if (arg === "--agent") {
245
+ options.agent = readValue(argv, ++i, arg);
246
+ } else if (arg === "--hub") {
247
+ options.hub = readValue(argv, ++i, arg);
248
+ } else if (arg === "--blueprint") {
249
+ options.blueprint = readValue(argv, ++i, arg);
250
+ } else if (arg === "--mode") {
251
+ options.mode = readValue(argv, ++i, arg);
252
+ } else if (arg === "--id") {
253
+ options.id = readValue(argv, ++i, arg);
254
+ } else if (arg === "--status") {
255
+ options.status = readValue(argv, ++i, arg);
256
+ } else if (arg === "--type") {
257
+ options.type = readValue(argv, ++i, arg);
258
+ } else if (arg === "--pack") {
259
+ options.pack = readValue(argv, ++i, arg);
260
+ } else if (arg === "--name") {
261
+ options.name = readValue(argv, ++i, arg);
262
+ } else if (arg === "--formal-source") {
263
+ options.formalSource = readValue(argv, ++i, arg);
264
+ } else if (arg === "--language") {
265
+ options.language = readValue(argv, ++i, arg);
266
+ } else if (arg === "--target") {
267
+ options.target = readValue(argv, ++i, arg);
268
+ } else if (arg === "--help" || arg === "-h") {
269
+ options.help = true;
270
+ } else if (!arg.startsWith("-")) {
271
+ if (!options._) options._ = [];
272
+ options._.push(arg);
273
+ } else {
274
+ throw new Error(`未知参数:${arg}`);
275
+ }
276
+ }
277
+ return options;
278
+ }
279
+
280
+ async function adapt(argv) {
281
+ const options = parseArgs(argv);
282
+ if (options.help) {
283
+ printAdaptHelp();
284
+ return;
285
+ }
286
+
287
+ const targetDir = path.resolve(options.target || process.cwd());
288
+ const workspaceRoot = requireWorkspaceRoot(targetDir);
289
+ const state = readWorkspaceState(workspaceRoot);
290
+ const agent = options.agent || options._?.[0] || "codex";
291
+ const agents = agent === "all" ? Object.keys(ADAPTERS) : [agent];
292
+
293
+ for (const id of agents) {
294
+ if (!ADAPTERS[id]) {
295
+ throw new Error(`不支持的 Agent 适配目标:${id}`);
296
+ }
297
+ }
298
+
299
+ const health = doctorCollect(workspaceRoot);
300
+ if (health.summary.fail > 0) {
301
+ throw new Error("当前工作台未通过 doctor 检查,请先修复阻塞问题。");
302
+ }
303
+
304
+ const plan = buildAdaptPlan({ workspaceRoot, state, agents });
305
+ printGenericPlan(options.dryRun ? "适配预览(dry run):" : "适配计划:", plan.actions);
306
+
307
+ if (options.dryRun) return;
308
+ await confirmOrThrow(options, "是否执行适配?");
309
+ applyPlan(plan);
310
+ console.log("");
311
+ console.log("StarWork Agent 适配已完成。");
312
+ console.log("下一步建议:运行 starwork doctor 再检查一次工作台。");
313
+ }
314
+
315
+ async function packCommand(argv) {
316
+ const subcommand = argv[0];
317
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
318
+ printPackHelp();
319
+ return;
320
+ }
321
+ if (subcommand !== "install") {
322
+ throw new Error(`未知 pack 子命令:${subcommand}`);
323
+ }
324
+ await packInstall(argv.slice(1));
325
+ }
326
+
327
+ async function packInstall(argv) {
328
+ const options = parseArgs(argv);
329
+ if (options.help) {
330
+ printPackInstallHelp();
331
+ return;
332
+ }
333
+
334
+ const packId = options.pack || options._?.[0];
335
+ if (!packId) {
336
+ throw new Error("pack install 需要指定 Pack ID。");
337
+ }
338
+
339
+ const targetDir = path.resolve(options.target || process.cwd());
340
+ const workspaceRoot = requireWorkspaceRoot(targetDir);
341
+ const state = readWorkspaceState(workspaceRoot);
342
+
343
+ if (state.packs?.some((pack) => pack.id === packId)) {
344
+ console.log(`Pack ${packId} 已安装,无需重复安装。`);
345
+ return;
346
+ }
347
+
348
+ const health = doctorCollect(workspaceRoot);
349
+ if (health.summary.fail > 0) {
350
+ throw new Error("当前工作台未通过 doctor 检查,请先修复阻塞问题。");
351
+ }
352
+
353
+ const pack = loadPack(packId, state.language || "zh");
354
+ validatePack(pack, state.workspace_type);
355
+ const plan = buildPackInstallPlan({ workspaceRoot, state, pack });
356
+
357
+ printGenericPlan(options.dryRun ? "Pack 安装预览(dry run):" : "Pack 安装计划:", plan.actions);
358
+ if (options.dryRun) return;
359
+
360
+ await confirmOrThrow(options, `是否安装 Pack ${pack.id}?`);
361
+ applyPlan(plan);
362
+ console.log("");
363
+ console.log(`Pack ${pack.name || pack.id} 已安装。`);
364
+ console.log("下一步建议:运行 starwork doctor 检查 Pack 落地结果。");
365
+ }
366
+
367
+ function doctor(argv) {
368
+ const options = parseArgs(argv);
369
+ if (options.help) {
370
+ printDoctorHelp();
371
+ return { exitCode: 0 };
372
+ }
373
+
374
+ const targetDir = path.resolve(options.target || process.cwd());
375
+ const result = createDoctorResult(targetDir);
376
+
377
+ if (!fs.existsSync(targetDir)) {
378
+ addCheck(result, "workspace.target.exists", "fail", `目标目录不存在:${targetDir}`);
379
+ return finishDoctor(result, options);
380
+ }
381
+
382
+ const workspaceRoot = findWorkspaceRoot(targetDir);
383
+ if (!workspaceRoot) {
384
+ const trace = findStarWorkTrace(targetDir);
385
+ if (trace) {
386
+ addCheck(result, "workspace.state.exists", "fail", "疑似 StarWork 工作台,但缺少 .starwork/workspace.json。", trace);
387
+ } else {
388
+ addCheck(result, "workspace.state.exists", "fail", "当前目录不是 StarWork 工作台。请先运行 starwork init。");
389
+ }
390
+ return finishDoctor(result, options);
391
+ }
392
+
393
+ result.workspace_root = workspaceRoot;
394
+ const statePath = path.join(workspaceRoot, ".starwork", "workspace.json");
395
+ addCheck(result, "workspace.state.exists", "pass", ".starwork/workspace.json exists", ".starwork/workspace.json");
396
+
397
+ let state;
398
+ try {
399
+ state = JSON.parse(fs.readFileSync(statePath, "utf8"));
400
+ } catch (error) {
401
+ addCheck(result, "workspace.state.parse", "fail", `无法解析 workspace state:${error.message}`, ".starwork/workspace.json");
402
+ return finishDoctor(result, options);
403
+ }
404
+
405
+ result.workspace = {
406
+ core: state.core || null,
407
+ workspace_type: state.workspace_type || null,
408
+ kit: state.kit || null,
409
+ language: state.language || null,
410
+ packs: Array.isArray(state.packs) ? state.packs.map((pack) => pack.id).filter(Boolean) : []
411
+ };
412
+
413
+ checkWorkspaceState(result, state);
414
+ checkKit(result, workspaceRoot, state);
415
+ checkCoreRoles(result, workspaceRoot, state);
416
+ checkPackInstallations(result, workspaceRoot, state);
417
+ checkBlueprintCustomization(result, workspaceRoot, state);
418
+
419
+ return finishDoctor(result, options);
420
+ }
421
+
422
+ function doctorCollect(targetDir) {
423
+ const result = createDoctorResult(targetDir);
424
+ const workspaceRoot = findWorkspaceRoot(targetDir);
425
+ if (!workspaceRoot) {
426
+ addCheck(result, "workspace.state.exists", "fail", "当前目录不是 StarWork 工作台。请先运行 starwork init。");
427
+ return result;
428
+ }
429
+ result.workspace_root = workspaceRoot;
430
+ const statePath = path.join(workspaceRoot, ".starwork", "workspace.json");
431
+ let state;
432
+ try {
433
+ state = JSON.parse(fs.readFileSync(statePath, "utf8"));
434
+ } catch (error) {
435
+ addCheck(result, "workspace.state.parse", "fail", `无法解析 workspace state:${error.message}`, ".starwork/workspace.json");
436
+ return result;
437
+ }
438
+ result.workspace = {
439
+ core: state.core || null,
440
+ workspace_type: state.workspace_type || null,
441
+ kit: state.kit || null,
442
+ language: state.language || null,
443
+ packs: Array.isArray(state.packs) ? state.packs.map((pack) => pack.id).filter(Boolean) : []
444
+ };
445
+ checkWorkspaceState(result, state);
446
+ checkKit(result, workspaceRoot, state);
447
+ checkCoreRoles(result, workspaceRoot, state);
448
+ checkPackInstallations(result, workspaceRoot, state);
449
+ checkBlueprintCustomization(result, workspaceRoot, state);
450
+ result.ok = result.summary.fail === 0;
451
+ result.strict_ok = result.ok;
452
+ result.exitCode = result.ok ? 0 : 1;
453
+ return result;
454
+ }
455
+
456
+ function createDoctorResult(targetDir) {
457
+ return {
458
+ schema: "starwork.doctor.result.v0.1",
459
+ ok: false,
460
+ strict_ok: false,
461
+ workspace_root: null,
462
+ target: targetDir,
463
+ workspace: null,
464
+ summary: {
465
+ pass: 0,
466
+ info: 0,
467
+ warn: 0,
468
+ fail: 0
469
+ },
470
+ checks: [],
471
+ exitCode: 1
472
+ };
473
+ }
474
+
475
+ function requireWorkspaceRoot(targetDir) {
476
+ if (!fs.existsSync(targetDir)) {
477
+ throw new Error(`目标目录不存在:${targetDir}`);
478
+ }
479
+ const workspaceRoot = findWorkspaceRoot(targetDir);
480
+ if (!workspaceRoot) {
481
+ throw new Error("当前目录不是 StarWork 工作台。请先运行 starwork init。");
482
+ }
483
+ return workspaceRoot;
484
+ }
485
+
486
+ function readWorkspaceState(workspaceRoot) {
487
+ const statePath = path.join(workspaceRoot, ".starwork", "workspace.json");
488
+ try {
489
+ return JSON.parse(fs.readFileSync(statePath, "utf8"));
490
+ } catch (error) {
491
+ throw new Error(`无法读取 workspace state:${error.message}`);
492
+ }
493
+ }
494
+
495
+ async function confirmOrThrow(options, question) {
496
+ if (options.dryRun) return;
497
+ if (!options.yes && process.stdin.isTTY) {
498
+ const ok = await confirm(question, true);
499
+ if (!ok) {
500
+ throw new Error("已取消,没有写入任何文件。");
501
+ }
502
+ } else if (!options.yes && !process.stdin.isTTY) {
503
+ throw new Error("非交互环境需要传入 --yes 或 --dry-run。");
504
+ }
505
+ }
506
+
507
+ function buildAdaptPlan({ workspaceRoot, state, agents }) {
508
+ const actions = [];
509
+ for (const agent of agents) {
510
+ const config = ADAPTERS[agent];
511
+ if (!config.path) continue;
512
+ actions.push(fileAction(workspaceRoot, config.path, renderAdapterContent(agent, state)));
513
+ }
514
+
515
+ const nextState = {
516
+ ...state,
517
+ adapters: mergeInstalledRecords(state.adapters, agents)
518
+ };
519
+ actions.push(overwriteFileAction(workspaceRoot, path.join(".starwork", "workspace.json"), `${JSON.stringify(nextState, null, 2)}\n`));
520
+
521
+ return {
522
+ targetDir: workspaceRoot,
523
+ actions: dedupeActions(actions)
524
+ };
525
+ }
526
+
527
+ function renderAdapterContent(agent, state) {
528
+ const rolePaths = getCoreRolePaths(state);
529
+ const adapterName = ADAPTERS[agent].label;
530
+ if (agent === "cursor") {
531
+ return `---\ndescription: StarWork workspace rules\nalwaysApply: true\n---\n\n# StarWork Adapter for ${adapterName}\n\nThis workspace follows StarWork Core ${state.core || "0.1"}.\n\nRead first:\n\n1. AGENTS.md\n2. ${rolePaths.projectStatus}\n3. ${rolePaths.currentWork}\n\nFollow AGENTS.md as the source of truth. Do not overwrite user content silently.\n`;
532
+ }
533
+ return `# StarWork Adapter for ${adapterName}\n\nThis workspace follows StarWork Core ${state.core || "0.1"}.\n\n## Read First\n\n1. AGENTS.md\n2. ${rolePaths.projectStatus}\n3. ${rolePaths.currentWork}\n\n## Rule\n\nAGENTS.md is the source of truth. This file is only an adapter entrypoint for ${adapterName}.\n\nDo not overwrite user content silently. When unsure, ask before changing identity, lessons, shared knowledge, formal outputs, or synced repository content.\n`;
534
+ }
535
+
536
+ function buildPackInstallPlan({ workspaceRoot, state, pack }) {
537
+ const variables = {
538
+ workspace: {
539
+ name: path.basename(workspaceRoot),
540
+ type: state.workspace_type
541
+ },
542
+ pack,
543
+ paths: pack.paths || {},
544
+ overrides: pack.overrides || {}
545
+ };
546
+ const actions = [];
547
+
548
+ for (const rolePath of Object.values(pack.paths || {})) {
549
+ actions.push(directoryAction(workspaceRoot, rolePath));
550
+ }
551
+
552
+ for (const seed of pack.seed || []) {
553
+ const source = path.join(pack.__dir, seed.from);
554
+ if (!fs.existsSync(source)) {
555
+ throw new Error(`Pack seed 不存在:${pack.id}/${seed.from}`);
556
+ }
557
+ const content = renderText(fs.readFileSync(source, "utf8"), variables);
558
+ actions.push(fileAction(workspaceRoot, seed.to, content));
559
+ }
560
+
561
+ for (const template of pack.templates || []) {
562
+ const source = path.join(pack.__dir, template.from);
563
+ if (!fs.existsSync(source)) {
564
+ throw new Error(`Pack template 不存在:${pack.id}/${template.from}`);
565
+ }
566
+ const target = path.join(".starwork", "packs", pack.id, "templates", path.basename(template.from));
567
+ const content = renderText(fs.readFileSync(source, "utf8"), variables);
568
+ actions.push(fileAction(workspaceRoot, target, content));
569
+ }
570
+
571
+ const agentsPath = path.join(workspaceRoot, "AGENTS.md");
572
+ if (!fs.existsSync(agentsPath)) {
573
+ throw new Error("缺少 AGENTS.md,无法安装 Pack 规则。");
574
+ }
575
+ const agents = fs.readFileSync(agentsPath, "utf8");
576
+ const rulesSection = renderInstalledPackRules(pack, variables);
577
+ if (rulesSection.trim() && !agents.includes(`Pack: ${pack.id}`)) {
578
+ actions.push(overwriteFileAction(workspaceRoot, "AGENTS.md", `${agents.trim()}\n\n${rulesSection.trim()}\n`));
579
+ }
580
+
581
+ const nextState = {
582
+ ...state,
583
+ packs: [
584
+ ...(Array.isArray(state.packs) ? state.packs : []),
585
+ {
586
+ id: pack.id,
587
+ version: pack.version || "0.1.0",
588
+ installed_at: new Date().toISOString()
589
+ }
590
+ ],
591
+ paths: {
592
+ ...(state.paths || {}),
593
+ formal_source: pack.overrides?.formal_source || state.paths?.formal_source,
594
+ business_work_area: pack.overrides?.business_work_area || state.paths?.business_work_area
595
+ }
596
+ };
597
+ actions.push(overwriteFileAction(workspaceRoot, path.join(".starwork", "workspace.json"), `${JSON.stringify(nextState, null, 2)}\n`));
598
+
599
+ return {
600
+ targetDir: workspaceRoot,
601
+ actions: dedupeActions(actions)
602
+ };
603
+ }
604
+
605
+ function renderInstalledPackRules(pack, variables) {
606
+ const rules = renderPackRules(pack, variables);
607
+ if (!rules.trim()) return "";
608
+ return `## 场景规则\n\n<!-- StarWork Pack: ${pack.id} -->\n\n${rules.trim()}`;
609
+ }
610
+
611
+ function mergeInstalledRecords(existing, ids) {
612
+ const current = Array.isArray(existing) ? existing.filter((item) => item?.id) : [];
613
+ const seen = new Set(current.map((item) => item.id));
614
+ const merged = [...current];
615
+ for (const id of ids) {
616
+ if (seen.has(id)) continue;
617
+ merged.push({
618
+ id,
619
+ installed_at: new Date().toISOString()
620
+ });
621
+ seen.add(id);
622
+ }
623
+ return merged;
624
+ }
625
+
626
+ function checkWorkspaceState(result, state) {
627
+ if (state.schema === "starwork.workspace.v0.1") {
628
+ addCheck(result, "workspace.state.schema", "pass", "workspace schema is starwork.workspace.v0.1", ".starwork/workspace.json");
629
+ } else {
630
+ addCheck(result, "workspace.state.schema", "fail", "workspace schema 不是 starwork.workspace.v0.1。", ".starwork/workspace.json");
631
+ }
632
+
633
+ if (state.core === "0.1") {
634
+ addCheck(result, "workspace.state.core", "pass", "Core version is 0.1", ".starwork/workspace.json");
635
+ } else {
636
+ addCheck(result, "workspace.state.core", "fail", "workspace core 必须兼容 0.1。", ".starwork/workspace.json");
637
+ }
638
+
639
+ if (["single-light", "single-matter", "hub", "satellite-starter", "satellite-matter"].includes(state.workspace_type)) {
640
+ addCheck(result, "workspace.state.type", "pass", `workspace_type is ${state.workspace_type}`, ".starwork/workspace.json");
641
+ } else {
642
+ addCheck(result, "workspace.state.type", "fail", "workspace_type 必须是 single-light、single-matter、hub、satellite-starter 或 satellite-matter。", ".starwork/workspace.json");
643
+ }
644
+
645
+ if (state.kit) {
646
+ addCheck(result, "workspace.state.kit", "pass", `kit is ${state.kit}`, ".starwork/workspace.json");
647
+ } else {
648
+ addCheck(result, "workspace.state.kit", "fail", "workspace state 缺少 kit。", ".starwork/workspace.json");
649
+ }
650
+
651
+ if (state.language) {
652
+ addCheck(result, "workspace.state.language", "pass", `language is ${state.language}`, ".starwork/workspace.json");
653
+ } else {
654
+ addCheck(result, "workspace.state.language", "fail", "workspace state 缺少 language。", ".starwork/workspace.json");
655
+ }
656
+
657
+ if (Array.isArray(state.packs)) {
658
+ addCheck(result, "workspace.state.packs", "pass", `packs count is ${state.packs.length}`, ".starwork/workspace.json");
659
+ } else {
660
+ addCheck(result, "workspace.state.packs", "fail", "workspace state 的 packs 必须是数组。", ".starwork/workspace.json");
661
+ }
662
+
663
+ if (state.paths?.formal_source) {
664
+ addCheck(result, "workspace.state.formal_source", "pass", `formal source is ${state.paths.formal_source}`, ".starwork/workspace.json");
665
+ } else {
666
+ addCheck(result, "workspace.state.formal_source", "fail", "workspace state 缺少 paths.formal_source。", ".starwork/workspace.json");
667
+ }
668
+
669
+ if (state.paths?.business_work_area) {
670
+ addCheck(result, "workspace.state.business_work_area", "pass", `business work area is ${state.paths.business_work_area}`, ".starwork/workspace.json");
671
+ } else {
672
+ addCheck(result, "workspace.state.business_work_area", "fail", "workspace state 缺少 paths.business_work_area。", ".starwork/workspace.json");
673
+ }
674
+ }
675
+
676
+ function checkKit(result, workspaceRoot, state) {
677
+ if (!state.kit) return;
678
+
679
+ const kitDir = path.join(PRODUCT_ROOT, "core", "kits", state.kit);
680
+ if (!fs.existsSync(kitDir)) {
681
+ addCheck(result, "kit.source.exists", "fail", `找不到 Kit 源目录:${state.kit}`);
682
+ return;
683
+ }
684
+ addCheck(result, "kit.source.exists", "pass", `Kit source exists: ${state.kit}`);
685
+
686
+ const allowedKits = {
687
+ "single-light": ["local-starter"],
688
+ "single-matter": ["local-matter"],
689
+ hub: ["hub"],
690
+ "satellite-starter": ["satellite-starter"],
691
+ "satellite-matter": ["satellite-matter"]
692
+ };
693
+ const allowed = allowedKits[state.workspace_type];
694
+ if (allowed && allowed.includes(state.kit)) {
695
+ addCheck(result, "kit.workspace_type.match", "pass", `${state.kit} matches ${state.workspace_type}`);
696
+ } else {
697
+ addCheck(result, "kit.workspace_type.match", "fail", `Kit ${state.kit} 与工作区类型 ${state.workspace_type || "(missing)"} 不匹配。`);
698
+ }
699
+
700
+ const files = walkFiles(kitDir);
701
+ const missing = [];
702
+ for (const source of files) {
703
+ const relativePath = path.relative(kitDir, source);
704
+ if (!fs.existsSync(path.join(workspaceRoot, relativePath))) {
705
+ missing.push(relativePath);
706
+ }
707
+ }
708
+ if (missing.length === 0) {
709
+ addCheck(result, "kit.files.complete", "pass", `Kit files are complete: ${state.kit}`);
710
+ } else {
711
+ addCheck(result, "kit.files.complete", "fail", `Kit 缺少 ${missing.length} 个文件。`, missing.slice(0, 5).join(", "));
712
+ }
713
+ }
714
+
715
+ function checkCoreRoles(result, workspaceRoot, state) {
716
+ checkPathExists(result, workspaceRoot, "AGENTS.md", "core.entry_rules.exists", "Agent entry rules exist", "缺少 Agent 入口规则 AGENTS.md。");
717
+
718
+ const rolePaths = getCoreRolePaths(state);
719
+ checkPathExists(result, workspaceRoot, rolePaths.projectStatus, "core.project_status.exists", "Project status exists", "缺少项目状态文件。");
720
+ checkPathExists(result, workspaceRoot, rolePaths.currentWork, "core.current_work.exists", "Current work exists", "缺少当前工作入口文件。");
721
+
722
+ if (state.paths?.formal_source) {
723
+ checkPathExists(result, workspaceRoot, state.paths.formal_source, "core.formal_source.exists", "Formal source exists", "缺少 workspace state 声明的正式事实源。");
724
+ }
725
+
726
+ if (state.paths?.business_work_area) {
727
+ checkPathExists(result, workspaceRoot, state.paths.business_work_area, "core.business_work_area.exists", "Business work area exists", "缺少 workspace state 声明的业务工作区。");
728
+ }
729
+ }
730
+
731
+ function getCoreRolePaths(state) {
732
+ const kit = state.kit || "";
733
+ if (kit.startsWith("satellite-")) {
734
+ return {
735
+ projectStatus: "_系统/上下文/当前项目.md",
736
+ currentWork: "_系统/任务/当前工作.md"
737
+ };
738
+ }
739
+ return {
740
+ projectStatus: "_系统/上下文/项目状态.md",
741
+ currentWork: "_系统/任务/当前工作.md"
742
+ };
743
+ }
744
+
745
+ function checkPackInstallations(result, workspaceRoot, state) {
746
+ if (!Array.isArray(state.packs)) return;
747
+ for (const installedPack of state.packs) {
748
+ if (!installedPack?.id) {
749
+ addCheck(result, "pack.id.exists", "fail", "已安装 Pack 缺少 id。", ".starwork/workspace.json");
750
+ continue;
751
+ }
752
+
753
+ let pack;
754
+ try {
755
+ pack = loadPack(installedPack.id, state.language || "zh");
756
+ } catch (error) {
757
+ addCheck(result, "pack.source.exists", "fail", `无法读取 Pack ${installedPack.id}:${error.message}`);
758
+ continue;
759
+ }
760
+
761
+ addCheck(result, "pack.source.exists", "pass", `Pack source exists: ${installedPack.id}`);
762
+
763
+ if (pack.compatible_core === state.core) {
764
+ addCheck(result, "pack.core.compatible", "pass", `${installedPack.id} is compatible with Core ${state.core}`);
765
+ } else {
766
+ addCheck(result, "pack.core.compatible", "fail", `Pack ${installedPack.id} 不兼容 Core ${state.core || "(missing)"}。`);
767
+ }
768
+
769
+ if (pack.supports_workspace_types?.includes(state.workspace_type)) {
770
+ addCheck(result, "pack.workspace_type.supported", "pass", `${installedPack.id} supports ${state.workspace_type}`);
771
+ } else {
772
+ addCheck(result, "pack.workspace_type.supported", "fail", `Pack ${installedPack.id} 不支持工作区类型 ${state.workspace_type || "(missing)"}。`);
773
+ }
774
+
775
+ for (const rolePath of Object.values(pack.paths || {})) {
776
+ checkPathExists(result, workspaceRoot, rolePath, "pack.paths.exist", `Pack path exists: ${rolePath}`, `Pack ${installedPack.id} 缺少目录:${rolePath}`);
777
+ }
778
+
779
+ for (const seed of pack.seed || []) {
780
+ checkPathExists(result, workspaceRoot, seed.to, "pack.seed.installed", `Pack seed exists: ${seed.to}`, `Pack ${installedPack.id} 缺少 seed 文件:${seed.to}`);
781
+ }
782
+
783
+ for (const template of pack.templates || []) {
784
+ const relativePath = path.join(".starwork", "packs", pack.id, "templates", path.basename(template.from));
785
+ checkPathExists(result, workspaceRoot, relativePath, "pack.templates.installed", `Pack template exists: ${relativePath}`, `Pack ${installedPack.id} 缺少模板:${relativePath}`);
786
+ }
787
+ }
788
+ }
789
+
790
+ function checkBlueprintCustomization(result, workspaceRoot, state) {
791
+ const customization = state.customization;
792
+ if (!customization) return;
793
+ if (customization.type !== "spawn_blueprint") return;
794
+
795
+ if (customization.schema === "starwork.spawn_blueprint.v0.1") {
796
+ addCheck(result, "blueprint.schema", "pass", "Blueprint schema is starwork.spawn_blueprint.v0.1", ".starwork/workspace.json");
797
+ } else {
798
+ addCheck(result, "blueprint.schema", "fail", "Blueprint customization schema 不正确。", ".starwork/workspace.json");
799
+ }
800
+
801
+ for (const folder of customization.folders || []) {
802
+ checkPathExists(result, workspaceRoot, folder, "blueprint.folder.exists", `Blueprint folder exists: ${folder}`, `Blueprint 缺少定制目录:${folder}`);
803
+ }
804
+
805
+ for (const seed of customization.seed || []) {
806
+ if (!seed?.to) continue;
807
+ checkPathExists(result, workspaceRoot, seed.to, "blueprint.seed.exists", `Blueprint seed exists: ${seed.to}`, `Blueprint 缺少 seed 文件:${seed.to}`);
808
+ }
809
+
810
+ const agentsPath = path.join(workspaceRoot, "AGENTS.md");
811
+ const agents = fs.existsSync(agentsPath) ? fs.readFileSync(agentsPath, "utf8") : "";
812
+ for (const rule of customization.agent_rules || []) {
813
+ if (!rule?.slot) continue;
814
+ if (agents.includes(`StarWork Blueprint: ${rule.slot}`)) {
815
+ addCheck(result, "blueprint.rule.injected", "pass", `Blueprint rule injected: ${rule.slot}`, "AGENTS.md");
816
+ } else {
817
+ addCheck(result, "blueprint.rule.injected", "fail", `Blueprint 规则未注入 AGENTS.md:${rule.slot}`, "AGENTS.md");
818
+ }
819
+ }
820
+ }
821
+
822
+ function checkPathExists(result, workspaceRoot, relativePath, id, passMessage, failMessage) {
823
+ const normalized = normalizeRelativePath(relativePath);
824
+ if (fs.existsSync(path.join(workspaceRoot, normalized))) {
825
+ addCheck(result, id, "pass", passMessage, normalized);
826
+ } else {
827
+ addCheck(result, id, "fail", failMessage, normalized);
828
+ }
829
+ }
830
+
831
+ function normalizeRelativePath(relativePath) {
832
+ return String(relativePath || "").replace(/\\/g, "/").replace(/^\/+/, "");
833
+ }
834
+
835
+ function normalizeSafeRelativePath(relativePath, label) {
836
+ if (typeof relativePath !== "string" || !relativePath.trim()) {
837
+ throw new Error(`${label} 必须是非空相对路径。`);
838
+ }
839
+ const raw = relativePath.replace(/\\/g, "/").trim();
840
+ if (path.isAbsolute(raw) || raw.startsWith("~")) {
841
+ throw new Error(`${label} 不能使用绝对路径或 ~:${relativePath}`);
842
+ }
843
+ const normalized = path.posix.normalize(raw.replace(/^\.\/+/, ""));
844
+ if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
845
+ throw new Error(`${label} 不能跳出工作区:${relativePath}`);
846
+ }
847
+ if (normalized === ".git" || normalized.startsWith(".git/")) {
848
+ throw new Error(`${label} 不能写入 .git:${relativePath}`);
849
+ }
850
+ return normalized;
851
+ }
852
+
853
+ function normalizeSafeSourcePath(relativePath, sourceRoot, label) {
854
+ const normalized = normalizeSafeRelativePath(relativePath, label);
855
+ const resolvedRoot = path.resolve(sourceRoot);
856
+ const resolved = path.resolve(resolvedRoot, normalized);
857
+ if (resolved !== resolvedRoot && !resolved.startsWith(`${resolvedRoot}${path.sep}`)) {
858
+ throw new Error(`${label} 不能跳出 blueprint 目录:${relativePath}`);
859
+ }
860
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
861
+ throw new Error(`${label} 文件不存在:${relativePath}`);
862
+ }
863
+ return resolved;
864
+ }
865
+
866
+ function findStarWorkTrace(dir) {
867
+ const traces = [
868
+ "AGENTS.md",
869
+ "_系统",
870
+ "_system",
871
+ "事项",
872
+ "matters"
873
+ ];
874
+ return traces.find((trace) => fs.existsSync(path.join(dir, trace))) || null;
875
+ }
876
+
877
+ function addCheck(result, id, level, message, checkPath) {
878
+ result.summary[level] += 1;
879
+ result.checks.push({
880
+ id,
881
+ level,
882
+ message,
883
+ path: checkPath || null
884
+ });
885
+ }
886
+
887
+ function finishDoctor(result, options) {
888
+ result.ok = result.summary.fail === 0;
889
+ result.strict_ok = result.ok && (!options.strict || result.summary.warn === 0);
890
+ result.exitCode = result.strict_ok ? 0 : 1;
891
+ if (options.json) {
892
+ console.log(JSON.stringify(doctorPublicResult(result), null, 2));
893
+ } else {
894
+ printDoctorResult(result, options);
895
+ }
896
+ return result;
897
+ }
898
+
899
+ function doctorPublicResult(result) {
900
+ const { exitCode, target, ...publicResult } = result;
901
+ return publicResult;
902
+ }
903
+
904
+ function printDoctorResult(result, options) {
905
+ console.log("StarWork Doctor");
906
+ console.log("");
907
+ console.log(`Workspace: ${result.workspace_root || result.target}`);
908
+ if (result.workspace) {
909
+ console.log(`Core: ${result.workspace.core || "(unknown)"}`);
910
+ console.log(`Type: ${result.workspace.workspace_type || "(unknown)"}`);
911
+ console.log(`Kit: ${result.workspace.kit || "(unknown)"}`);
912
+ console.log(`Packs: ${result.workspace.packs.length ? result.workspace.packs.join(", ") : "(none)"}`);
913
+ }
914
+ console.log("");
915
+ console.log("Summary:");
916
+ console.log(` pass: ${result.summary.pass}`);
917
+ console.log(` info: ${result.summary.info}`);
918
+ console.log(` warn: ${result.summary.warn}`);
919
+ console.log(` fail: ${result.summary.fail}`);
920
+ console.log("");
921
+
922
+ const visibleChecks = options.verbose
923
+ ? result.checks
924
+ : result.checks.filter((check) => check.level !== "pass");
925
+ if (visibleChecks.length) {
926
+ console.log("Checks:");
927
+ for (const check of visibleChecks) {
928
+ console.log(` [${check.level}] ${check.id}`);
929
+ console.log(` ${check.message}`);
930
+ if (check.path) {
931
+ console.log(` ${check.path}`);
932
+ }
933
+ console.log("");
934
+ }
935
+ }
936
+
937
+ console.log("Result:");
938
+ if (result.summary.fail > 0) {
939
+ console.log(" Workspace has blocking issues.");
940
+ } else if (result.summary.warn > 0) {
941
+ console.log(" Workspace is usable, with warnings.");
942
+ } else {
943
+ console.log(" Workspace is healthy.");
944
+ }
945
+ }
946
+
947
+ function readValue(argv, index, flag) {
948
+ const value = argv[index];
949
+ if (!value || value.startsWith("--")) {
950
+ throw new Error(`${flag} 需要一个值。`);
951
+ }
952
+ return value;
953
+ }
954
+
955
+ async function chooseWorkspaceType(options) {
956
+ if (options.yes || !process.stdin.isTTY) {
957
+ return "single-light";
958
+ }
959
+ return choose("你要建立哪种工作区?", [
960
+ ["single-light", "轻量单项目:放资料、写草稿、整理最终成果"],
961
+ ["single-matter", "长期单项目:事项追踪、跨会话接力"],
962
+ ["hub", "多项目管理中枢:统一管理身份、教训、知识、skills 和项目联络"]
963
+ ]);
964
+ }
965
+
966
+ async function choosePack(workspaceType, workspaceConfig, options) {
967
+ if (workspaceType === "hub") {
968
+ return workspaceConfig.defaultPack;
969
+ }
970
+
971
+ if (options.yes || !process.stdin.isTTY) {
972
+ return workspaceConfig.defaultPack;
973
+ }
974
+
975
+ return choose("你准备用这个工作台做什么?", [
976
+ ["general", "通用工作:资料整理、草稿输出、项目推进"],
977
+ ["content-creator", "自媒体内容创作:账号定位、选题、素材、草稿、发布、复盘"]
978
+ ]);
979
+ }
980
+
981
+ async function choose(question, choices) {
982
+ console.log("");
983
+ console.log(question);
984
+ choices.forEach(([_, label], index) => {
985
+ console.log(`${index + 1}. ${label}`);
986
+ });
987
+
988
+ const answer = await ask("请输入序号:");
989
+ const index = Number(answer.trim()) - 1;
990
+ if (!choices[index]) {
991
+ console.log("未识别选择,使用第一项。");
992
+ return choices[0][0];
993
+ }
994
+ return choices[index][0];
995
+ }
996
+
997
+ async function confirm(question, defaultValue) {
998
+ const suffix = defaultValue ? "Y/n" : "y/N";
999
+ const answer = await ask(`${question} (${suffix}) `);
1000
+ const normalized = answer.trim().toLowerCase();
1001
+ if (!normalized) return defaultValue;
1002
+ return normalized === "y" || normalized === "yes";
1003
+ }
1004
+
1005
+ function ask(question) {
1006
+ const rl = readline.createInterface({
1007
+ input: process.stdin,
1008
+ output: process.stdout
1009
+ });
1010
+ return new Promise((resolve) => {
1011
+ rl.question(question, (answer) => {
1012
+ rl.close();
1013
+ resolve(answer);
1014
+ });
1015
+ });
1016
+ }
1017
+
1018
+ function buildInitPlan({ targetDir, workspaceName, workspaceType, workspaceConfig, pack, formalSource }) {
1019
+ const kitDir = path.join(PRODUCT_ROOT, "core", "kits", workspaceConfig.kit);
1020
+ if (!fs.existsSync(kitDir)) {
1021
+ throw new Error(`找不到 Kit:${workspaceConfig.kit}`);
1022
+ }
1023
+
1024
+ const actions = [];
1025
+ const variables = {
1026
+ workspace: {
1027
+ name: workspaceName,
1028
+ type: workspaceType
1029
+ },
1030
+ pack,
1031
+ paths: pack.paths || {},
1032
+ overrides: {
1033
+ ...(pack.overrides || {}),
1034
+ formal_source: formalSource
1035
+ }
1036
+ };
1037
+ const packRules = renderPackRules(pack, variables);
1038
+
1039
+ for (const source of walkFiles(kitDir)) {
1040
+ const relativePath = path.relative(kitDir, source);
1041
+ let content = fs.readFileSync(source, "utf8");
1042
+ content = renderText(content, variables);
1043
+ if (relativePath === "AGENTS.md" && packRules.trim()) {
1044
+ content = `${content.trim()}\n\n## 场景规则\n\n${packRules.trim()}\n`;
1045
+ }
1046
+ actions.push(fileAction(targetDir, relativePath, content));
1047
+ }
1048
+
1049
+ for (const rolePath of Object.values(pack.paths || {})) {
1050
+ actions.push(directoryAction(targetDir, rolePath));
1051
+ }
1052
+
1053
+ for (const seed of pack.seed || []) {
1054
+ const source = path.join(pack.__dir, seed.from);
1055
+ if (!fs.existsSync(source)) {
1056
+ throw new Error(`Pack seed 不存在:${pack.id}/${seed.from}`);
1057
+ }
1058
+ const content = renderText(fs.readFileSync(source, "utf8"), variables);
1059
+ actions.push(fileAction(targetDir, seed.to, content));
1060
+ }
1061
+
1062
+ for (const template of pack.templates || []) {
1063
+ const source = path.join(pack.__dir, template.from);
1064
+ if (!fs.existsSync(source)) {
1065
+ throw new Error(`Pack template 不存在:${pack.id}/${template.from}`);
1066
+ }
1067
+ const target = path.join(".starwork", "packs", pack.id, "templates", path.basename(template.from));
1068
+ const content = renderText(fs.readFileSync(source, "utf8"), variables);
1069
+ actions.push(fileAction(targetDir, target, content));
1070
+ }
1071
+
1072
+ const workspaceState = {
1073
+ schema: "starwork.workspace.v0.1",
1074
+ core: "0.1",
1075
+ workspace_type: workspaceType,
1076
+ kit: workspaceConfig.kit,
1077
+ packs: [
1078
+ {
1079
+ id: pack.id,
1080
+ version: pack.version || "0.1.0",
1081
+ installed_at: new Date().toISOString()
1082
+ }
1083
+ ],
1084
+ language: pack.language || "zh",
1085
+ paths: {
1086
+ formal_source: formalSource,
1087
+ business_work_area: pack.overrides?.business_work_area || formalSource
1088
+ },
1089
+ created_by: "starwork init"
1090
+ };
1091
+ actions.push(fileAction(targetDir, path.join(".starwork", "workspace.json"), `${JSON.stringify(workspaceState, null, 2)}\n`));
1092
+
1093
+ return {
1094
+ targetDir,
1095
+ workspaceName,
1096
+ workspaceType,
1097
+ workspaceLabel: workspaceConfig.label,
1098
+ kit: workspaceConfig.kit,
1099
+ pack,
1100
+ formalSource,
1101
+ actions: dedupeActions(actions)
1102
+ };
1103
+ }
1104
+
1105
+ function resolveHubRoot(hubPath) {
1106
+ const resolved = path.resolve(hubPath);
1107
+ return requireWorkspaceRoot(resolved);
1108
+ }
1109
+
1110
+ function assertHealthyHub(hubRoot, hubState) {
1111
+ if (hubState.workspace_type !== "hub" || hubState.kit !== "hub") {
1112
+ throw new Error("spawn 必须从多项目管理中枢工作台执行。请先运行 starwork init --type hub。");
1113
+ }
1114
+ const health = doctorCollect(hubRoot);
1115
+ if (health.summary.fail > 0) {
1116
+ throw new Error("Hub 工作台未通过 doctor 检查,请先修复阻塞问题。");
1117
+ }
1118
+ const required = [
1119
+ "项目/registry.json",
1120
+ "identity",
1121
+ "lessons",
1122
+ "skills",
1123
+ "知识"
1124
+ ];
1125
+ for (const relativePath of required) {
1126
+ if (!fs.existsSync(path.join(hubRoot, relativePath))) {
1127
+ throw new Error(`Hub 缺少必要资源:${relativePath}`);
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+ function assertSpawnTargetIsEmpty(targetDir) {
1133
+ const existingWorkspace = fs.existsSync(targetDir) ? findWorkspaceRoot(targetDir) : null;
1134
+ if (existingWorkspace) {
1135
+ throw new Error("目标目录已经位于 StarWork 工作台内,请换一个空目录。");
1136
+ }
1137
+ if (!fs.existsSync(targetDir)) return;
1138
+ if (!fs.statSync(targetDir).isDirectory()) {
1139
+ throw new Error("spawn 目标必须是目录。");
1140
+ }
1141
+ const entries = fs.readdirSync(targetDir).filter((entry) => entry !== ".DS_Store");
1142
+ if (entries.length > 0) {
1143
+ throw new Error("spawn v0.1 只写入不存在或空目录,目标目录已有内容。");
1144
+ }
1145
+ }
1146
+
1147
+ function loadSpawnBlueprint(blueprintPath) {
1148
+ const filePath = path.resolve(blueprintPath);
1149
+ let blueprint;
1150
+ try {
1151
+ blueprint = JSON.parse(fs.readFileSync(filePath, "utf8"));
1152
+ } catch (error) {
1153
+ throw new Error(`无法读取 blueprint:${error.message}`);
1154
+ }
1155
+ if (blueprint.schema !== "starwork.spawn_blueprint.v0.1") {
1156
+ throw new Error("blueprint schema 必须是 starwork.spawn_blueprint.v0.1。");
1157
+ }
1158
+ if (!blueprint.name || typeof blueprint.name !== "string") {
1159
+ throw new Error("blueprint 缺少项目名称 name。");
1160
+ }
1161
+ if (!blueprint.base || typeof blueprint.base !== "object") {
1162
+ throw new Error("blueprint 缺少 base 配置。");
1163
+ }
1164
+ if (blueprint.base.language && blueprint.base.language !== "zh") {
1165
+ throw new Error("spawn blueprint v0.1 只支持 language=zh。");
1166
+ }
1167
+ return {
1168
+ ...blueprint,
1169
+ __path: filePath,
1170
+ __dir: path.dirname(filePath)
1171
+ };
1172
+ }
1173
+
1174
+ function validateSpawnBlueprintForMode(blueprint, mode, modeConfig) {
1175
+ if (!blueprint) return;
1176
+ if (blueprint.base.mode && blueprint.base.mode !== mode) {
1177
+ throw new Error(`blueprint base.mode (${blueprint.base.mode}) 与本次 spawn 模式 (${mode}) 不一致。`);
1178
+ }
1179
+ if (blueprint.base.kit && blueprint.base.kit !== modeConfig.kit) {
1180
+ throw new Error(`blueprint base.kit (${blueprint.base.kit}) 与模式 ${mode} 的 Kit (${modeConfig.kit}) 不匹配。`);
1181
+ }
1182
+ if (blueprint.renames && Object.keys(blueprint.renames).length > 0) {
1183
+ throw new Error("spawn blueprint v0.1 暂不支持 renames。");
1184
+ }
1185
+ if (blueprint.removals && blueprint.removals.length > 0) {
1186
+ throw new Error("spawn blueprint v0.1 暂不支持 removals。");
1187
+ }
1188
+ for (const relativePath of Object.values(blueprint.paths || {})) {
1189
+ normalizeSafeRelativePath(relativePath, "blueprint.paths");
1190
+ }
1191
+ for (const folder of blueprint.folders || []) {
1192
+ normalizeSafeRelativePath(folder, "blueprint.folders");
1193
+ }
1194
+ for (const rule of blueprint.agent_rules || []) {
1195
+ normalizeSafeSourcePath(rule.from, blueprint.__dir, "blueprint.agent_rules.from");
1196
+ if (!rule.slot || typeof rule.slot !== "string") {
1197
+ throw new Error("blueprint agent_rules 每一项都必须包含 slot。");
1198
+ }
1199
+ }
1200
+ for (const seed of blueprint.seed || []) {
1201
+ normalizeSafeSourcePath(seed.from, blueprint.__dir, "blueprint.seed.from");
1202
+ normalizeSafeRelativePath(seed.to, "blueprint.seed.to");
1203
+ const conflict = seed.on_conflict || "error";
1204
+ if (!["error", "skip", "create_new"].includes(conflict)) {
1205
+ throw new Error("blueprint seed.on_conflict 只支持 error、skip 或 create_new。");
1206
+ }
1207
+ }
1208
+ }
1209
+
1210
+ function applySpawnBlueprintModeConfig(modeConfig, blueprint) {
1211
+ if (!blueprint) return modeConfig;
1212
+ return {
1213
+ ...modeConfig,
1214
+ formalSource: blueprint.paths?.formal_source || modeConfig.formalSource,
1215
+ businessWorkArea: blueprint.paths?.business_work_area || modeConfig.businessWorkArea
1216
+ };
1217
+ }
1218
+
1219
+ function buildSpawnPlan({ hubRoot, hubState, targetDir, projectName, projectId, status, mode, modeConfig, blueprint }) {
1220
+ const kitDir = path.join(PRODUCT_ROOT, "core", "kits", modeConfig.kit);
1221
+ if (!fs.existsSync(kitDir)) {
1222
+ throw new Error(`找不到 Kit:${modeConfig.kit}`);
1223
+ }
1224
+
1225
+ const registryPath = path.join(hubRoot, "项目", "registry.json");
1226
+ const registry = readProjectRegistry(registryPath);
1227
+ const targetPath = path.resolve(targetDir);
1228
+ ensureProjectCanBeRegistered(registry, projectId, targetPath);
1229
+
1230
+ const now = new Date().toISOString();
1231
+ const actions = [];
1232
+
1233
+ for (const source of walkFiles(kitDir)) {
1234
+ const relativePath = path.relative(kitDir, source);
1235
+ if (shouldSpawnOverrideKitFile(relativePath)) continue;
1236
+ let content = fs.readFileSync(source, "utf8");
1237
+ if (normalizeRelativePath(relativePath) === "AGENTS.md") {
1238
+ content = appendBlueprintRulesToAgents(content, blueprint, {
1239
+ projectName,
1240
+ projectId,
1241
+ mode,
1242
+ modeConfig
1243
+ });
1244
+ }
1245
+ actions.push(fileAction(targetDir, relativePath, content));
1246
+ }
1247
+
1248
+ if (blueprint) {
1249
+ for (const folder of blueprint.folders || []) {
1250
+ actions.push(directoryAction(targetDir, normalizeSafeRelativePath(folder, "blueprint.folders")));
1251
+ }
1252
+ actions.push(directoryAction(targetDir, normalizeSafeRelativePath(modeConfig.formalSource, "paths.formal_source")));
1253
+ actions.push(directoryAction(targetDir, normalizeSafeRelativePath(modeConfig.businessWorkArea, "paths.business_work_area")));
1254
+ actions.push(...buildBlueprintSeedActions(targetDir, blueprint, {
1255
+ projectName,
1256
+ projectId,
1257
+ mode,
1258
+ modeConfig
1259
+ }));
1260
+ }
1261
+
1262
+ actions.push(...copyDirectoryFiles(hubRoot, "identity", targetDir, path.join("_系统", "身份")));
1263
+ actions.push(...copyDirectoryFiles(hubRoot, "lessons", targetDir, path.join("_系统", "教训")));
1264
+ if (fs.existsSync(path.join(hubRoot, ".internal"))) {
1265
+ actions.push(...copyDirectoryFiles(hubRoot, ".internal", targetDir, ".internal"));
1266
+ }
1267
+ if (fs.existsSync(path.join(hubRoot, ".obsidian"))) {
1268
+ actions.push(...copyDirectoryFiles(hubRoot, ".obsidian", targetDir, ".obsidian"));
1269
+ }
1270
+
1271
+ actions.push(symlinkAction(targetDir, "知识", path.join(hubRoot, "知识")));
1272
+ actions.push(symlinkAction(targetDir, path.join(".agents", "skills"), path.join(hubRoot, "skills")));
1273
+ actions.push(symlinkAction(targetDir, path.join(".claude", "skills"), path.join(hubRoot, "skills")));
1274
+
1275
+ const workspaceState = {
1276
+ schema: "starwork.workspace.v0.1",
1277
+ core: "0.1",
1278
+ workspace_type: modeConfig.workspaceType,
1279
+ kit: modeConfig.kit,
1280
+ packs: [],
1281
+ language: hubState.language || "zh",
1282
+ paths: {
1283
+ formal_source: modeConfig.formalSource,
1284
+ business_work_area: modeConfig.businessWorkArea
1285
+ },
1286
+ ...(blueprint ? {
1287
+ customization: {
1288
+ type: "spawn_blueprint",
1289
+ schema: blueprint.schema,
1290
+ source: path.basename(blueprint.__path),
1291
+ folders: (blueprint.folders || []).map((folder) => normalizeSafeRelativePath(folder, "blueprint.folders")),
1292
+ agent_rules: (blueprint.agent_rules || []).map((rule) => ({
1293
+ slot: rule.slot,
1294
+ from: normalizeSafeRelativePath(rule.from, "blueprint.agent_rules.from")
1295
+ })),
1296
+ seed: (blueprint.seed || []).map((seed) => ({
1297
+ from: normalizeSafeRelativePath(seed.from, "blueprint.seed.from"),
1298
+ to: normalizeSafeRelativePath(seed.to, "blueprint.seed.to")
1299
+ }))
1300
+ }
1301
+ } : {}),
1302
+ hub: {
1303
+ path: hubRoot,
1304
+ project_id: projectId
1305
+ },
1306
+ created_by: blueprint ? "starwork spawn --blueprint" : "starwork spawn"
1307
+ };
1308
+
1309
+ const coreSync = {
1310
+ schema: "starwork.core_sync.v0.1",
1311
+ hub_path: hubRoot,
1312
+ project_id: projectId,
1313
+ project_name: projectName,
1314
+ core: "0.1",
1315
+ mode,
1316
+ created_at: now,
1317
+ last_sync_at: now,
1318
+ resources: {
1319
+ identity: {
1320
+ source: "identity/",
1321
+ target: "_系统/身份/",
1322
+ mode: "snapshot"
1323
+ },
1324
+ lessons: {
1325
+ source: "lessons/",
1326
+ target: "_系统/教训/",
1327
+ mode: "snapshot"
1328
+ },
1329
+ knowledge: {
1330
+ source: "知识/",
1331
+ target: "知识/",
1332
+ mode: "readonly-link"
1333
+ },
1334
+ skills: {
1335
+ source: "skills/",
1336
+ target: [".agents/skills/", ".claude/skills/"],
1337
+ mode: "symlink"
1338
+ }
1339
+ }
1340
+ };
1341
+
1342
+ actions.push(fileAction(targetDir, path.join(".starwork", "workspace.json"), `${JSON.stringify(workspaceState, null, 2)}\n`));
1343
+ actions.push(fileAction(targetDir, ".core-sync.json", `${JSON.stringify(coreSync, null, 2)}\n`));
1344
+ actions.push(fileAction(targetDir, path.join("_系统", "上下文", "当前项目.md"), renderSpawnProjectStatus({
1345
+ projectName,
1346
+ projectId,
1347
+ hubRoot,
1348
+ mode,
1349
+ modeConfig,
1350
+ blueprint
1351
+ })));
1352
+
1353
+ const nextRegistry = {
1354
+ ...registry,
1355
+ schema: registry.schema || "starwork.projects.registry.v0.1",
1356
+ projects: [
1357
+ ...(Array.isArray(registry.projects) ? registry.projects : []),
1358
+ {
1359
+ id: projectId,
1360
+ name: projectName,
1361
+ path: targetPath,
1362
+ status,
1363
+ core: "0.1",
1364
+ kit: modeConfig.kit,
1365
+ mode,
1366
+ customized: Boolean(blueprint),
1367
+ created_at: now,
1368
+ last_sync_at: now,
1369
+ sync: {
1370
+ identity: "snapshot",
1371
+ lessons: "snapshot",
1372
+ knowledge: "readonly-link",
1373
+ skills: "symlink"
1374
+ }
1375
+ }
1376
+ ]
1377
+ };
1378
+ actions.push(overwriteFileAction(hubRoot, path.join("项目", "registry.json"), `${JSON.stringify(nextRegistry, null, 2)}\n`));
1379
+
1380
+ return {
1381
+ hubRoot,
1382
+ targetDir,
1383
+ projectName,
1384
+ projectId,
1385
+ status,
1386
+ mode,
1387
+ modeLabel: modeConfig.label,
1388
+ kit: modeConfig.kit,
1389
+ blueprint,
1390
+ actions: dedupeActions(actions)
1391
+ };
1392
+ }
1393
+
1394
+ function shouldSpawnOverrideKitFile(relativePath) {
1395
+ const normalized = normalizeRelativePath(relativePath);
1396
+ return normalized === ".core-sync.json"
1397
+ || normalized === "_系统/上下文/当前项目.md"
1398
+ || normalized.startsWith("_系统/身份/")
1399
+ || normalized.startsWith("_系统/教训/")
1400
+ || normalized.startsWith("知识/")
1401
+ || normalized.startsWith(".agents/skills/")
1402
+ || normalized.startsWith(".claude/skills/");
1403
+ }
1404
+
1405
+ function appendBlueprintRulesToAgents(content, blueprint, variables) {
1406
+ if (!blueprint || !blueprint.agent_rules?.length) return content;
1407
+ const parts = [];
1408
+ for (const rule of blueprint.agent_rules) {
1409
+ const source = normalizeSafeSourcePath(rule.from, blueprint.__dir, "blueprint.agent_rules.from");
1410
+ const ruleContent = renderText(fs.readFileSync(source, "utf8"), buildBlueprintVariables(blueprint, variables)).trim();
1411
+ if (!ruleContent) continue;
1412
+ parts.push(`<!-- StarWork Blueprint: ${rule.slot} -->\n\n${ruleContent}`);
1413
+ }
1414
+ if (!parts.length) return content;
1415
+ return `${content.trim()}\n\n## 项目定制规则\n\n${parts.join("\n\n")}\n`;
1416
+ }
1417
+
1418
+ function buildBlueprintSeedActions(targetDir, blueprint, variables) {
1419
+ const actions = [];
1420
+ for (const seed of blueprint.seed || []) {
1421
+ const source = normalizeSafeSourcePath(seed.from, blueprint.__dir, "blueprint.seed.from");
1422
+ const target = normalizeSafeRelativePath(seed.to, "blueprint.seed.to");
1423
+ const content = renderText(fs.readFileSync(source, "utf8"), buildBlueprintVariables(blueprint, variables));
1424
+ const targetPath = path.join(targetDir, target);
1425
+ const conflict = seed.on_conflict || "error";
1426
+ if (fs.existsSync(targetPath)) {
1427
+ if (conflict === "skip") continue;
1428
+ if (conflict === "create_new") {
1429
+ const alternate = nextAvailableSibling(targetPath);
1430
+ actions.push({ type: "file", mode: "create-new", target: alternate, originalTarget: targetPath, relativePath: path.relative(targetDir, alternate), content });
1431
+ continue;
1432
+ }
1433
+ throw new Error(`blueprint seed 目标已存在:${target}`);
1434
+ }
1435
+ actions.push(fileAction(targetDir, target, content));
1436
+ }
1437
+ return actions;
1438
+ }
1439
+
1440
+ function buildBlueprintVariables(blueprint, { projectName, projectId, mode, modeConfig }) {
1441
+ return {
1442
+ blueprint,
1443
+ workspace: {
1444
+ name: projectName,
1445
+ type: modeConfig.workspaceType
1446
+ },
1447
+ project: {
1448
+ id: projectId,
1449
+ name: projectName
1450
+ },
1451
+ spawn: {
1452
+ mode
1453
+ },
1454
+ paths: {
1455
+ formal_source: modeConfig.formalSource,
1456
+ business_work_area: modeConfig.businessWorkArea
1457
+ }
1458
+ };
1459
+ }
1460
+
1461
+ function copyDirectoryFiles(sourceRoot, sourceRelativeDir, targetRoot, targetRelativeDir) {
1462
+ const sourceDir = path.join(sourceRoot, sourceRelativeDir);
1463
+ if (!fs.existsSync(sourceDir)) return [];
1464
+ return walkFiles(sourceDir).map((source) => {
1465
+ const relativePath = path.relative(sourceDir, source);
1466
+ const targetRelativePath = path.join(targetRelativeDir, relativePath);
1467
+ return fileAction(targetRoot, targetRelativePath, fs.readFileSync(source, "utf8"));
1468
+ });
1469
+ }
1470
+
1471
+ function readProjectRegistry(registryPath) {
1472
+ try {
1473
+ const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
1474
+ if (!Array.isArray(registry.projects)) {
1475
+ throw new Error("projects 必须是数组");
1476
+ }
1477
+ return registry;
1478
+ } catch (error) {
1479
+ throw new Error(`无法读取 Hub 项目注册表:${error.message}`);
1480
+ }
1481
+ }
1482
+
1483
+ function ensureProjectCanBeRegistered(registry, projectId, targetPath) {
1484
+ const projects = Array.isArray(registry.projects) ? registry.projects : [];
1485
+ if (projects.some((project) => project?.id === projectId)) {
1486
+ throw new Error(`Hub registry 已存在项目 ID:${projectId}`);
1487
+ }
1488
+ if (projects.some((project) => project?.path && path.resolve(project.path) === targetPath)) {
1489
+ throw new Error(`Hub registry 已存在目标路径:${targetPath}`);
1490
+ }
1491
+ }
1492
+
1493
+ function renderSpawnProjectStatus({ projectName, projectId, hubRoot, mode, modeConfig, blueprint }) {
1494
+ const description = blueprint?.description
1495
+ ? `\n## 项目定位\n\n${blueprint.description}\n`
1496
+ : "";
1497
+ const customization = blueprint
1498
+ ? `\n## 工作区定制\n\n- Blueprint:${path.basename(blueprint.__path)}\n- 正式事实源:\`${modeConfig.formalSource}\`\n- 当前工作区:\`${modeConfig.businessWorkArea}\`\n- 定制目录:${(blueprint.folders || []).map((folder) => `\`${normalizeSafeRelativePath(folder, "blueprint.folders")}\``).join("、") || "无"}\n`
1499
+ : "";
1500
+ return `# 当前项目状态
1501
+
1502
+ ## 项目目标
1503
+
1504
+ ${projectName}
1505
+ ${description}
1506
+
1507
+ ## 项目信息
1508
+
1509
+ - 项目 ID:${projectId}
1510
+ - 工作区类型:${modeConfig.workspaceType}
1511
+ - Kit:${modeConfig.kit}
1512
+ - 模式:${mode}
1513
+ - Hub:${hubRoot}
1514
+ ${customization}
1515
+
1516
+ ## 当前阶段
1517
+
1518
+ 刚由 \`starwork spawn\` 创建,等待补充项目目标、近期重点和执行边界。
1519
+
1520
+ ## 近期重点
1521
+
1522
+ - 补充项目目标。
1523
+ - 确认正式事实源:\`${modeConfig.formalSource}\`。
1524
+ - 确认当前工作区:\`${modeConfig.businessWorkArea}\`。
1525
+ - 确认当前工作入口:\`_系统/任务/当前工作.md\`。
1526
+
1527
+ ## 主要风险
1528
+
1529
+ - 不要把 Hub 的项目注册表当成项目进度正文。
1530
+ - 主库同步资源默认只读,项目内更新应走跨项目联络或回写流程。
1531
+
1532
+ ## 正式事实源
1533
+
1534
+ \`${modeConfig.formalSource}\`
1535
+
1536
+ ## 下一步
1537
+
1538
+ - 运行 \`starwork doctor\` 检查工作台。
1539
+ - 根据项目实际情况更新本文件。
1540
+
1541
+ ## 兼容说明
1542
+
1543
+ 当前 Hub + 项目模式读取 \`_系统/上下文/当前项目.md\`。如果未来迁移到其他命名,只能保留一个状态事实源,另一个应作为别名、指针或生成副本。
1544
+ `;
1545
+ }
1546
+
1547
+ function slugifyProjectId(value) {
1548
+ return String(value || "")
1549
+ .trim()
1550
+ .toLowerCase()
1551
+ .replace(/['"]/g, "")
1552
+ .replace(/[^a-z0-9]+/g, "-")
1553
+ .replace(/^-+|-+$/g, "");
1554
+ }
1555
+
1556
+ function loadPack(packIdOrPath, language = "zh") {
1557
+ const packPath = path.isAbsolute(packIdOrPath) || packIdOrPath.startsWith(".")
1558
+ ? path.resolve(packIdOrPath)
1559
+ : path.join(PRODUCT_ROOT, "packs", packIdOrPath);
1560
+ const jsonPath = path.join(packPath, "pack.json");
1561
+ if (!fs.existsSync(jsonPath)) {
1562
+ throw new Error(`找不到 Pack 声明:${jsonPath}`);
1563
+ }
1564
+ const basePack = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
1565
+ const languagePack = loadPackLanguage(packPath, language);
1566
+ const pack = mergePackLanguage(basePack, languagePack, language);
1567
+ pack.__dir = packPath;
1568
+ return pack;
1569
+ }
1570
+
1571
+ function loadPackLanguage(packPath, language) {
1572
+ const languagesDir = path.join(packPath, "languages");
1573
+ if (!fs.existsSync(languagesDir)) {
1574
+ return null;
1575
+ }
1576
+ const languagePath = path.join(languagesDir, `${language}.json`);
1577
+ if (!fs.existsSync(languagePath)) {
1578
+ throw new Error(`Pack 缺少语言配置:${path.relative(PRODUCT_ROOT, languagePath)}`);
1579
+ }
1580
+ return JSON.parse(fs.readFileSync(languagePath, "utf8"));
1581
+ }
1582
+
1583
+ function mergePackLanguage(basePack, languagePack, requestedLanguage) {
1584
+ if (!languagePack) {
1585
+ return {
1586
+ ...basePack,
1587
+ language: requestedLanguage
1588
+ };
1589
+ }
1590
+ return {
1591
+ ...basePack,
1592
+ language: languagePack.language || requestedLanguage,
1593
+ name: languagePack.name || basePack.name || basePack.id,
1594
+ paths: languagePack.paths || basePack.paths || {},
1595
+ overrides: {
1596
+ ...(basePack.overrides || {}),
1597
+ ...(languagePack.overrides || {})
1598
+ },
1599
+ rules: languagePack.rules || basePack.rules || [],
1600
+ templates: languagePack.templates || basePack.templates || [],
1601
+ seed: languagePack.seed || basePack.seed || []
1602
+ };
1603
+ }
1604
+
1605
+ function validatePack(pack, workspaceType) {
1606
+ if (!pack.supports_workspace_types?.includes(workspaceType)) {
1607
+ throw new Error(`Pack ${pack.id} 不支持工作区类型 ${workspaceType}。`);
1608
+ }
1609
+ if (!pack.paths || Object.keys(pack.paths).length === 0) {
1610
+ throw new Error(`Pack ${pack.id} 缺少语言化路径配置。`);
1611
+ }
1612
+ }
1613
+
1614
+ function renderPackRules(pack, variables) {
1615
+ const parts = [];
1616
+ for (const rule of pack.rules || []) {
1617
+ const source = path.join(pack.__dir, rule.from);
1618
+ if (!fs.existsSync(source)) {
1619
+ throw new Error(`Pack rule 不存在:${pack.id}/${rule.from}`);
1620
+ }
1621
+ parts.push(renderText(fs.readFileSync(source, "utf8"), variables).trim());
1622
+ }
1623
+ return parts.filter(Boolean).join("\n\n");
1624
+ }
1625
+
1626
+ function renderText(text, variables) {
1627
+ return text.replace(/\{\{([^}]+)\}\}/g, (_, expression) => {
1628
+ const value = getPath(variables, expression.trim());
1629
+ return value == null ? "" : String(value);
1630
+ });
1631
+ }
1632
+
1633
+ function getPath(object, expression) {
1634
+ return expression.split(".").reduce((current, key) => {
1635
+ if (current == null) return undefined;
1636
+ return current[key];
1637
+ }, object);
1638
+ }
1639
+
1640
+ function fileAction(targetDir, relativePath, content) {
1641
+ const target = path.join(targetDir, relativePath);
1642
+ if (!fs.existsSync(target)) {
1643
+ return { type: "file", mode: "create", target, relativePath, content };
1644
+ }
1645
+ const existing = fs.readFileSync(target, "utf8");
1646
+ if (!existing.trim()) {
1647
+ return { type: "file", mode: "overwrite-empty", target, relativePath, content };
1648
+ }
1649
+ const alternate = nextAvailableSibling(target);
1650
+ return { type: "file", mode: "create-new", target: alternate, originalTarget: target, relativePath: path.relative(targetDir, alternate), content };
1651
+ }
1652
+
1653
+ function overwriteFileAction(targetDir, relativePath, content) {
1654
+ const target = path.join(targetDir, relativePath);
1655
+ return { type: "file", mode: "overwrite", target, relativePath, content };
1656
+ }
1657
+
1658
+ function directoryAction(targetDir, relativePath) {
1659
+ const target = path.join(targetDir, relativePath);
1660
+ return { type: "directory", mode: fs.existsSync(target) ? "exists" : "create", target, relativePath };
1661
+ }
1662
+
1663
+ function symlinkAction(targetDir, relativePath, sourcePath) {
1664
+ const target = path.join(targetDir, relativePath);
1665
+ return { type: "symlink", mode: "create", target, relativePath, sourcePath };
1666
+ }
1667
+
1668
+ function nextAvailableSibling(target) {
1669
+ const ext = path.extname(target);
1670
+ const base = target.slice(0, target.length - ext.length);
1671
+ let candidate = `${base}.starwork-new${ext}`;
1672
+ let index = 2;
1673
+ while (fs.existsSync(candidate)) {
1674
+ candidate = `${base}.starwork-new-${index}${ext}`;
1675
+ index += 1;
1676
+ }
1677
+ return candidate;
1678
+ }
1679
+
1680
+ function dedupeActions(actions) {
1681
+ const seen = new Set();
1682
+ const result = [];
1683
+ for (const action of actions) {
1684
+ const key = `${action.type}:${action.target}`;
1685
+ if (seen.has(key)) continue;
1686
+ seen.add(key);
1687
+ result.push(action);
1688
+ }
1689
+ return result;
1690
+ }
1691
+
1692
+ function applyPlan(plan) {
1693
+ for (const action of plan.actions) {
1694
+ if (action.mode === "exists") continue;
1695
+ if (action.type === "directory") {
1696
+ fs.mkdirSync(action.target, { recursive: true });
1697
+ continue;
1698
+ }
1699
+ if (action.type === "symlink") {
1700
+ fs.mkdirSync(path.dirname(action.target), { recursive: true });
1701
+ if (!fs.existsSync(action.target)) {
1702
+ fs.symlinkSync(action.sourcePath, action.target, "dir");
1703
+ }
1704
+ continue;
1705
+ }
1706
+ fs.mkdirSync(path.dirname(action.target), { recursive: true });
1707
+ fs.writeFileSync(action.target, action.content, "utf8");
1708
+ }
1709
+ }
1710
+
1711
+ function printGenericPlan(title, actions) {
1712
+ const creates = actions.filter((action) => action.mode === "create");
1713
+ const overwrites = actions.filter((action) => action.mode === "overwrite" || action.mode === "overwrite-empty");
1714
+ const createNew = actions.filter((action) => action.mode === "create-new");
1715
+ const dirs = actions.filter((action) => action.type === "directory" && action.mode === "create");
1716
+
1717
+ console.log("");
1718
+ console.log(title);
1719
+ console.log("");
1720
+ if (dirs.length) {
1721
+ console.log("将创建目录:");
1722
+ dirs.forEach((action) => console.log(`- ${action.relativePath}`));
1723
+ console.log("");
1724
+ }
1725
+ if (creates.length) {
1726
+ console.log("将创建文件:");
1727
+ creates.forEach((action) => console.log(`- ${action.relativePath}`));
1728
+ console.log("");
1729
+ }
1730
+ if (overwrites.length) {
1731
+ console.log("将更新文件:");
1732
+ overwrites.forEach((action) => console.log(`- ${action.relativePath}`));
1733
+ console.log("");
1734
+ }
1735
+ if (createNew.length) {
1736
+ console.log("不会覆盖已有内容,将生成旁路文件:");
1737
+ createNew.forEach((action) => console.log(`- ${path.relative(path.dirname(action.originalTarget), action.originalTarget)} -> ${action.relativePath}`));
1738
+ console.log("");
1739
+ }
1740
+ }
1741
+
1742
+ function printPlan(plan, dryRun) {
1743
+ const creates = plan.actions.filter((action) => action.mode === "create");
1744
+ const emptyUpdates = plan.actions.filter((action) => action.mode === "overwrite-empty");
1745
+ const createNew = plan.actions.filter((action) => action.mode === "create-new");
1746
+
1747
+ console.log("");
1748
+ console.log(dryRun ? "初始化预览(dry run):" : "初始化计划:");
1749
+ console.log("");
1750
+ console.log(`工作区类型:${plan.workspaceLabel} (${plan.workspaceType})`);
1751
+ console.log(`Kit:${plan.kit}`);
1752
+ console.log(`Pack:${plan.pack.name || PACK_LABELS[plan.pack.id] || plan.pack.id} (${plan.pack.id})`);
1753
+ console.log(`工作台名称:${plan.workspaceName}`);
1754
+ console.log(`正式成果位置:${plan.formalSource}`);
1755
+ console.log("");
1756
+
1757
+ if (creates.length) {
1758
+ console.log("将创建:");
1759
+ creates.slice(0, 40).forEach((action) => console.log(`- ${action.relativePath}`));
1760
+ if (creates.length > 40) console.log(`- ... 另有 ${creates.length - 40} 项`);
1761
+ console.log("");
1762
+ }
1763
+ if (emptyUpdates.length) {
1764
+ console.log("将写入空文件:");
1765
+ emptyUpdates.forEach((action) => console.log(`- ${action.relativePath}`));
1766
+ console.log("");
1767
+ }
1768
+ if (createNew.length) {
1769
+ console.log("不会覆盖已有内容,将生成旁路文件:");
1770
+ createNew.forEach((action) => console.log(`- ${path.relative(plan.targetDir, action.originalTarget)} -> ${action.relativePath}`));
1771
+ console.log("");
1772
+ }
1773
+ }
1774
+
1775
+ function printSpawnPlan(plan, dryRun) {
1776
+ const creates = plan.actions.filter((action) => action.type === "file" && action.mode === "create");
1777
+ const overwrites = plan.actions.filter((action) => action.type === "file" && action.mode === "overwrite");
1778
+ const links = plan.actions.filter((action) => action.type === "symlink");
1779
+ const dirs = plan.actions.filter((action) => action.type === "directory" && action.mode === "create");
1780
+
1781
+ console.log("");
1782
+ console.log(dryRun ? "生成项目预览(dry run):" : "生成项目计划:");
1783
+ console.log("");
1784
+ console.log(`Hub:${plan.hubRoot}`);
1785
+ console.log(`项目名称:${plan.projectName}`);
1786
+ console.log(`项目 ID:${plan.projectId}`);
1787
+ console.log(`目标目录:${plan.targetDir}`);
1788
+ console.log(`模式:${plan.modeLabel} (${plan.mode})`);
1789
+ console.log(`Kit:${plan.kit}`);
1790
+ if (plan.blueprint) {
1791
+ console.log(`Blueprint:${plan.blueprint.__path}`);
1792
+ console.log(`正式事实源:${plan.blueprint.paths?.formal_source || "(默认)"}`);
1793
+ console.log(`当前工作区:${plan.blueprint.paths?.business_work_area || "(默认)"}`);
1794
+ }
1795
+ console.log("");
1796
+
1797
+ if (creates.length) {
1798
+ console.log("将在新项目中创建:");
1799
+ creates.slice(0, 40).forEach((action) => console.log(`- ${action.relativePath}`));
1800
+ if (creates.length > 40) console.log(`- ... 另有 ${creates.length - 40} 项`);
1801
+ console.log("");
1802
+ }
1803
+ if (dirs.length) {
1804
+ console.log("将在新项目中创建目录:");
1805
+ dirs.forEach((action) => console.log(`- ${action.relativePath}`));
1806
+ console.log("");
1807
+ }
1808
+ if (links.length) {
1809
+ console.log("将挂载 Hub 共享资源:");
1810
+ links.forEach((action) => console.log(`- ${action.relativePath} -> ${action.sourcePath}`));
1811
+ console.log("");
1812
+ }
1813
+ if (overwrites.length) {
1814
+ console.log("将在 Hub 中更新:");
1815
+ overwrites.forEach((action) => console.log(`- ${action.relativePath}`));
1816
+ console.log("");
1817
+ }
1818
+ }
1819
+
1820
+ function walkFiles(dir) {
1821
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1822
+ const result = [];
1823
+ for (const entry of entries) {
1824
+ const fullPath = path.join(dir, entry.name);
1825
+ if (entry.isDirectory()) {
1826
+ result.push(...walkFiles(fullPath));
1827
+ } else if (entry.isFile()) {
1828
+ result.push(fullPath);
1829
+ }
1830
+ }
1831
+ return result;
1832
+ }
1833
+
1834
+ function findWorkspaceRoot(startDir) {
1835
+ let current = path.resolve(startDir);
1836
+ while (true) {
1837
+ if (fs.existsSync(path.join(current, ".starwork", "workspace.json"))) {
1838
+ return current;
1839
+ }
1840
+ const parent = path.dirname(current);
1841
+ if (parent === current) return null;
1842
+ current = parent;
1843
+ }
1844
+ }
1845
+
1846
+ function getKitDefaultFormalSource(kit) {
1847
+ if (kit === "hub") return "项目/";
1848
+ return "输出/确认成果/";
1849
+ }
1850
+
1851
+ function getKitLanguage(kit) {
1852
+ return "zh";
1853
+ }
1854
+
1855
+ function printHelp() {
1856
+ console.log(`StarWork CLI
1857
+
1858
+ Usage:
1859
+ starwork init [options]
1860
+ starwork spawn [options]
1861
+ starwork doctor [options]
1862
+ starwork adapt [agent] [options]
1863
+ starwork pack install <pack> [options]
1864
+
1865
+ Options:
1866
+ --type <single-light|single-matter|hub>
1867
+ --hub <path>
1868
+ --blueprint <path>
1869
+ --mode <starter|matter>
1870
+ --id <project-id>
1871
+ --status <active|paused>
1872
+ --pack <general|content-creator|hub-management|path>
1873
+ --name <name>
1874
+ --formal-source <path>
1875
+ --language <zh|en>
1876
+ --target <path>
1877
+ --dry-run
1878
+ --json
1879
+ --strict
1880
+ --verbose
1881
+ --agent <codex|claude|cursor|trae|all>
1882
+ --yes, -y
1883
+ `);
1884
+ }
1885
+
1886
+ function printSpawnHelp() {
1887
+ console.log(`StarWork Spawn
1888
+
1889
+ Usage:
1890
+ starwork spawn --hub <hub-path> --name <project-name> --target <path> [options]
1891
+ starwork spawn --hub <hub-path> --target <path> --blueprint <blueprint.json> [options]
1892
+
1893
+ Options:
1894
+ --hub <path>
1895
+ --blueprint <path>
1896
+ --name <name>
1897
+ --target <path>
1898
+ --mode <starter|matter>
1899
+ --id <project-id>
1900
+ --status <active|paused>
1901
+ --dry-run
1902
+ --yes, -y
1903
+ `);
1904
+ }
1905
+
1906
+ function printDoctorHelp() {
1907
+ console.log(`StarWork Doctor
1908
+
1909
+ Usage:
1910
+ starwork doctor [options]
1911
+
1912
+ Options:
1913
+ --target <path>
1914
+ --json
1915
+ --strict
1916
+ --verbose
1917
+ `);
1918
+ }
1919
+
1920
+ function printAdaptHelp() {
1921
+ console.log(`StarWork Adapt
1922
+
1923
+ Usage:
1924
+ starwork adapt [codex|claude|cursor|trae|all] [options]
1925
+
1926
+ Options:
1927
+ --agent <codex|claude|cursor|trae|all>
1928
+ --target <path>
1929
+ --dry-run
1930
+ --yes, -y
1931
+ `);
1932
+ }
1933
+
1934
+ function printPackHelp() {
1935
+ console.log(`StarWork Pack
1936
+
1937
+ Usage:
1938
+ starwork pack install <pack> [options]
1939
+ `);
1940
+ }
1941
+
1942
+ function printPackInstallHelp() {
1943
+ console.log(`StarWork Pack Install
1944
+
1945
+ Usage:
1946
+ starwork pack install <general|content-creator|hub-management> [options]
1947
+
1948
+ Options:
1949
+ --target <path>
1950
+ --dry-run
1951
+ --yes, -y
1952
+ `);
1953
+ }
1954
+
1955
+ module.exports = { run };