@niujiajie/freespec 0.0.0-stage → 1.15.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 (308) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +58 -2
  3. package/bin/freespec.js +5 -0
  4. package/dist/cli/index.d.ts +25 -0
  5. package/dist/cli/index.js +359 -0
  6. package/dist/commands/shared-output.d.ts +18 -0
  7. package/dist/commands/shared-output.js +61 -0
  8. package/dist/commands/workflow/default-schema.d.ts +2 -0
  9. package/dist/commands/workflow/default-schema.js +4 -0
  10. package/dist/commands/workflow/index.d.ts +13 -0
  11. package/dist/commands/workflow/index.js +10 -0
  12. package/dist/commands/workflow/instructions.d.ts +39 -0
  13. package/dist/commands/workflow/instructions.js +565 -0
  14. package/dist/commands/workflow/new-change.d.ts +19 -0
  15. package/dist/commands/workflow/new-change.js +135 -0
  16. package/dist/commands/workflow/shared.d.ts +94 -0
  17. package/dist/commands/workflow/shared.js +169 -0
  18. package/dist/commands/workflow/status.d.ts +18 -0
  19. package/dist/commands/workflow/status.js +201 -0
  20. package/dist/core/artifact-graph/graph.d.ts +72 -0
  21. package/dist/core/artifact-graph/graph.js +165 -0
  22. package/dist/core/artifact-graph/index.d.ts +9 -0
  23. package/dist/core/artifact-graph/index.js +14 -0
  24. package/dist/core/artifact-graph/instruction-loader.d.ts +208 -0
  25. package/dist/core/artifact-graph/instruction-loader.js +268 -0
  26. package/dist/core/artifact-graph/outputs.d.ts +19 -0
  27. package/dist/core/artifact-graph/outputs.js +351 -0
  28. package/dist/core/artifact-graph/resolver.d.ts +89 -0
  29. package/dist/core/artifact-graph/resolver.js +342 -0
  30. package/dist/core/artifact-graph/schema.d.ts +27 -0
  31. package/dist/core/artifact-graph/schema.js +160 -0
  32. package/dist/core/artifact-graph/state.d.ts +12 -0
  33. package/dist/core/artifact-graph/state.js +31 -0
  34. package/dist/core/artifact-graph/types.d.ts +40 -0
  35. package/dist/core/artifact-graph/types.js +59 -0
  36. package/dist/core/available-tools.d.ts +18 -0
  37. package/dist/core/available-tools.js +49 -0
  38. package/dist/core/change-layout.d.ts +17 -0
  39. package/dist/core/change-layout.js +30 -0
  40. package/dist/core/change-metadata/index.d.ts +2 -0
  41. package/dist/core/change-metadata/index.js +2 -0
  42. package/dist/core/change-metadata/schema.d.ts +23 -0
  43. package/dist/core/change-metadata/schema.js +46 -0
  44. package/dist/core/change-status-policy.d.ts +49 -0
  45. package/dist/core/change-status-policy.js +50 -0
  46. package/dist/core/command-generation/adapters/amazon-q.d.ts +17 -0
  47. package/dist/core/command-generation/adapters/amazon-q.js +32 -0
  48. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  49. package/dist/core/command-generation/adapters/antigravity.js +27 -0
  50. package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
  51. package/dist/core/command-generation/adapters/atomcode.js +46 -0
  52. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  53. package/dist/core/command-generation/adapters/auggie.js +28 -0
  54. package/dist/core/command-generation/adapters/bob.d.ts +18 -0
  55. package/dist/core/command-generation/adapters/bob.js +33 -0
  56. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  57. package/dist/core/command-generation/adapters/claude.js +32 -0
  58. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  59. package/dist/core/command-generation/adapters/cline.js +27 -0
  60. package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
  61. package/dist/core/command-generation/adapters/codeassistant.js +29 -0
  62. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  63. package/dist/core/command-generation/adapters/codebuddy.js +29 -0
  64. package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
  65. package/dist/core/command-generation/adapters/codestudio.js +27 -0
  66. package/dist/core/command-generation/adapters/command-code.d.ts +19 -0
  67. package/dist/core/command-generation/adapters/command-code.js +34 -0
  68. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  69. package/dist/core/command-generation/adapters/continue.js +33 -0
  70. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/costrict.js +28 -0
  72. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  73. package/dist/core/command-generation/adapters/crush.js +30 -0
  74. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  75. package/dist/core/command-generation/adapters/cursor.js +31 -0
  76. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  77. package/dist/core/command-generation/adapters/devin.js +36 -0
  78. package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
  79. package/dist/core/command-generation/adapters/easycode.js +28 -0
  80. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/factory.js +28 -0
  82. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/gemini.js +27 -0
  84. package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
  85. package/dist/core/command-generation/adapters/gigacode.js +31 -0
  86. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/github-copilot.js +27 -0
  88. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  89. package/dist/core/command-generation/adapters/iflow.js +30 -0
  90. package/dist/core/command-generation/adapters/index.d.ts +40 -0
  91. package/dist/core/command-generation/adapters/index.js +40 -0
  92. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  93. package/dist/core/command-generation/adapters/junie.js +27 -0
  94. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  95. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  96. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/kiro.js +27 -0
  98. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/lingma.js +30 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  101. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  102. package/dist/core/command-generation/adapters/opencode.d.ts +14 -0
  103. package/dist/core/command-generation/adapters/opencode.js +38 -0
  104. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  105. package/dist/core/command-generation/adapters/pi.js +39 -0
  106. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  107. package/dist/core/command-generation/adapters/qoder.js +30 -0
  108. package/dist/core/command-generation/adapters/qwen.d.ts +17 -0
  109. package/dist/core/command-generation/adapters/qwen.js +31 -0
  110. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  111. package/dist/core/command-generation/adapters/roocode.js +27 -0
  112. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/trae.js +28 -0
  114. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  115. package/dist/core/command-generation/adapters/zcode.js +33 -0
  116. package/dist/core/command-generation/generator.d.ts +29 -0
  117. package/dist/core/command-generation/generator.js +43 -0
  118. package/dist/core/command-generation/index.d.ts +22 -0
  119. package/dist/core/command-generation/index.js +24 -0
  120. package/dist/core/command-generation/invocation.d.ts +73 -0
  121. package/dist/core/command-generation/invocation.js +77 -0
  122. package/dist/core/command-generation/registry.d.ts +36 -0
  123. package/dist/core/command-generation/registry.js +114 -0
  124. package/dist/core/command-generation/toml.d.ts +19 -0
  125. package/dist/core/command-generation/toml.js +39 -0
  126. package/dist/core/command-generation/types.d.ts +63 -0
  127. package/dist/core/command-generation/types.js +8 -0
  128. package/dist/core/command-generation/yaml.d.ts +36 -0
  129. package/dist/core/command-generation/yaml.js +48 -0
  130. package/dist/core/command-surface.d.ts +15 -0
  131. package/dist/core/command-surface.js +33 -0
  132. package/dist/core/config-prompts.d.ts +9 -0
  133. package/dist/core/config-prompts.js +56 -0
  134. package/dist/core/config-schema.d.ts +98 -0
  135. package/dist/core/config-schema.js +308 -0
  136. package/dist/core/config.d.ts +50 -0
  137. package/dist/core/config.js +122 -0
  138. package/dist/core/file-state.d.ts +36 -0
  139. package/dist/core/file-state.js +144 -0
  140. package/dist/core/file-view.d.ts +13 -0
  141. package/dist/core/file-view.js +14 -0
  142. package/dist/core/global-config.d.ts +101 -0
  143. package/dist/core/global-config.js +182 -0
  144. package/dist/core/id.d.ts +17 -0
  145. package/dist/core/id.js +30 -0
  146. package/dist/core/index.d.ts +6 -0
  147. package/dist/core/index.js +7 -0
  148. package/dist/core/init.d.ts +57 -0
  149. package/dist/core/init.js +836 -0
  150. package/dist/core/list.d.ts +11 -0
  151. package/dist/core/list.js +197 -0
  152. package/dist/core/migration.d.ts +121 -0
  153. package/dist/core/migration.js +540 -0
  154. package/dist/core/onboarding-commands.d.ts +30 -0
  155. package/dist/core/onboarding-commands.js +38 -0
  156. package/dist/core/openspec-root.d.ts +47 -0
  157. package/dist/core/openspec-root.js +196 -0
  158. package/dist/core/planning-home.d.ts +16 -0
  159. package/dist/core/planning-home.js +67 -0
  160. package/dist/core/profile-sync-drift.d.ts +31 -0
  161. package/dist/core/profile-sync-drift.js +201 -0
  162. package/dist/core/profiles.d.ts +19 -0
  163. package/dist/core/profiles.js +33 -0
  164. package/dist/core/project-config.d.ts +132 -0
  165. package/dist/core/project-config.js +519 -0
  166. package/dist/core/references.d.ts +10 -0
  167. package/dist/core/references.js +46 -0
  168. package/dist/core/root-selection.d.ts +59 -0
  169. package/dist/core/root-selection.js +132 -0
  170. package/dist/core/shared/allowed-tools.d.ts +12 -0
  171. package/dist/core/shared/allowed-tools.js +12 -0
  172. package/dist/core/shared/ide-restart.d.ts +26 -0
  173. package/dist/core/shared/ide-restart.js +42 -0
  174. package/dist/core/shared/index.d.ts +11 -0
  175. package/dist/core/shared/index.js +11 -0
  176. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  177. package/dist/core/shared/skill-content-equivalence.js +45 -0
  178. package/dist/core/shared/skill-files.d.ts +7 -0
  179. package/dist/core/shared/skill-files.js +43 -0
  180. package/dist/core/shared/skill-generation.d.ts +49 -0
  181. package/dist/core/shared/skill-generation.js +125 -0
  182. package/dist/core/shared/skill-paths.d.ts +13 -0
  183. package/dist/core/shared/skill-paths.js +23 -0
  184. package/dist/core/shared/tool-detection.d.ts +99 -0
  185. package/dist/core/shared/tool-detection.js +318 -0
  186. package/dist/core/shared-skill-target.d.ts +51 -0
  187. package/dist/core/shared-skill-target.js +239 -0
  188. package/dist/core/store/errors.d.ts +20 -0
  189. package/dist/core/store/errors.js +22 -0
  190. package/dist/core/store/index.d.ts +2 -0
  191. package/dist/core/store/index.js +2 -0
  192. package/dist/core/styles/palette.d.ts +7 -0
  193. package/dist/core/styles/palette.js +8 -0
  194. package/dist/core/templates/optional-workflow.d.ts +69 -0
  195. package/dist/core/templates/optional-workflow.js +140 -0
  196. package/dist/core/templates/types.d.ts +19 -0
  197. package/dist/core/templates/types.js +5 -0
  198. package/dist/core/templates/workflows/personal.d.ts +22 -0
  199. package/dist/core/templates/workflows/personal.js +225 -0
  200. package/dist/core/update-plan.d.ts +127 -0
  201. package/dist/core/update-plan.js +390 -0
  202. package/dist/core/update.d.ts +154 -0
  203. package/dist/core/update.js +723 -0
  204. package/dist/core/version-check.d.ts +188 -0
  205. package/dist/core/version-check.js +906 -0
  206. package/dist/core/zod-issues.d.ts +4 -0
  207. package/dist/core/zod-issues.js +10 -0
  208. package/dist/index.d.ts +3 -0
  209. package/dist/index.js +3 -0
  210. package/dist/prompts/searchable-multi-select.d.ts +35 -0
  211. package/dist/prompts/searchable-multi-select.js +188 -0
  212. package/dist/telemetry/config.d.ts +37 -0
  213. package/dist/telemetry/config.js +148 -0
  214. package/dist/telemetry/index.d.ts +39 -0
  215. package/dist/telemetry/index.js +217 -0
  216. package/dist/telemetry/opt-out.d.ts +17 -0
  217. package/dist/telemetry/opt-out.js +27 -0
  218. package/dist/ui/ascii-patterns.d.ts +16 -0
  219. package/dist/ui/ascii-patterns.js +133 -0
  220. package/dist/ui/welcome-screen.d.ts +18 -0
  221. package/dist/ui/welcome-screen.js +199 -0
  222. package/dist/utils/change-metadata.d.ts +116 -0
  223. package/dist/utils/change-metadata.js +324 -0
  224. package/dist/utils/change-utils.d.ts +79 -0
  225. package/dist/utils/change-utils.js +121 -0
  226. package/dist/utils/ci.d.ts +12 -0
  227. package/dist/utils/ci.js +16 -0
  228. package/dist/utils/command-references.d.ts +104 -0
  229. package/dist/utils/command-references.js +190 -0
  230. package/dist/utils/date.d.ts +8 -0
  231. package/dist/utils/date.js +13 -0
  232. package/dist/utils/executable-path.d.ts +5 -0
  233. package/dist/utils/executable-path.js +44 -0
  234. package/dist/utils/file-system.d.ts +58 -0
  235. package/dist/utils/file-system.js +432 -0
  236. package/dist/utils/index.d.ts +6 -0
  237. package/dist/utils/index.js +9 -0
  238. package/dist/utils/interactive.d.ts +85 -0
  239. package/dist/utils/interactive.js +174 -0
  240. package/dist/utils/item-discovery.d.ts +15 -0
  241. package/dist/utils/item-discovery.js +33 -0
  242. package/dist/utils/line-endings.d.ts +34 -0
  243. package/dist/utils/line-endings.js +46 -0
  244. package/dist/utils/match.d.ts +3 -0
  245. package/dist/utils/match.js +22 -0
  246. package/dist/utils/nested-change.d.ts +35 -0
  247. package/dist/utils/nested-change.js +185 -0
  248. package/dist/utils/shell-detection.d.ts +20 -0
  249. package/dist/utils/shell-detection.js +94 -0
  250. package/dist/utils/task-progress.d.ts +67 -0
  251. package/dist/utils/task-progress.js +184 -0
  252. package/package.json +74 -4
  253. package/schemas//351/273/230/350/256/244/schema.yaml +68 -0
  254. package/schemas//351/273/230/350/256/244/templates//344/273/273/345/212/241/346/270/205/345/215/225.md +25 -0
  255. package/schemas//351/273/230/350/256/244/templates//346/265/213/350/257/225/347/224/250/344/276/213.md +34 -0
  256. package/schemas//351/273/230/350/256/244/templates//350/256/276/350/256/241/346/226/207/346/241/243.md +172 -0
  257. package/schemas//351/273/230/350/256/244/templates//351/234/200/346/261/202/346/226/207/346/241/243.md +19 -0
  258. package/skill-files//344/273/273/345/212/241/references//346/212/244/346/240/217.md +43 -0
  259. package/skill-files//344/273/273/345/212/241/references//347/262/222/345/272/246.md +23 -0
  260. package/skill-files//344/273/273/345/212/241/references//347/272/246/346/235/237.md +8 -0
  261. package/skill-files//344/273/273/345/212/241/scripts/check-tasks.mjs +83 -0
  262. package/skill-files//345/206/231Java/references/Java/350/247/204/350/214/203.md +25 -0
  263. package/skill-files//345/206/231Java/references//345/215/261/351/231/251/345/206/231/346/263/225.md +27 -0
  264. package/skill-files//345/206/231Java/references//345/256/214/346/210/220/345/257/271/347/205/247.md +15 -0
  265. package/skill-files//345/206/231Java/references//346/212/244/346/240/217.md +35 -0
  266. package/skill-files//345/206/231Java/references//347/272/246/346/235/237.md +8 -0
  267. package/skill-files//345/206/231Java/references//350/270/251/345/235/221.md +17 -0
  268. package/skill-files//345/256/241Java/references//346/212/244/346/240/217.md +19 -0
  269. package/skill-files//345/256/241Java/references//347/244/272/344/276/213.md +23 -0
  270. package/skill-files//345/256/241Java/references//347/272/246/346/235/237.md +7 -0
  271. package/skill-files//345/256/241Java/references//350/247/204/345/210/231.md +23 -0
  272. package/skill-files//345/256/241Java/references//351/242/206/345/237/237/346/243/200/351/241/271.md +58 -0
  273. package/skill-files//345/256/241/346/226/207/346/241/243/references//346/212/244/346/240/217.md +18 -0
  274. package/skill-files//345/256/241/346/226/207/346/241/243/references//347/272/246/346/235/237.md +8 -0
  275. package/skill-files//345/256/241/346/226/207/346/241/243/references//347/273/264/345/272/246.md +17 -0
  276. package/skill-files//345/256/241/346/226/207/346/241/243/references//350/270/251/345/235/221.md +11 -0
  277. package/skill-files//346/226/260/345/273/272/references//344/272/244/346/216/245.md +11 -0
  278. package/skill-files//346/226/260/345/273/272/references//346/212/244/346/240/217.md +14 -0
  279. package/skill-files//346/226/260/345/273/272/references//347/272/246/346/235/237.md +7 -0
  280. package/skill-files//346/267/261/346/200/235/config/prompts.yaml +114 -0
  281. package/skill-files//346/267/261/346/200/235/references//344/270/211/346/226/271/346/240/270/345/257/271.md +72 -0
  282. package/skill-files//346/267/261/346/200/235/references//344/276/213/345/255/220.md +39 -0
  283. package/skill-files//346/267/261/346/200/235/references//346/200/216/344/271/210/346/203/263.md +61 -0
  284. package/skill-files//346/267/261/346/200/235/references//346/212/244/346/240/217.md +26 -0
  285. package/skill-files//346/267/261/346/200/235/references//347/202/271/351/200/211/346/217/220/351/227/256.md +66 -0
  286. package/skill-files//346/267/261/346/200/235/references//347/231/273/350/256/260.md +45 -0
  287. package/skill-files//346/267/261/346/200/235/references//347/272/246/346/235/237.md +9 -0
  288. package/skill-files//347/224/250/344/276/213/references//346/200/216/344/271/210/345/206/231.md +13 -0
  289. package/skill-files//347/224/250/344/276/213/references//346/212/244/346/240/217.md +42 -0
  290. package/skill-files//347/224/250/344/276/213/references//347/272/246/346/235/237.md +5 -0
  291. package/skill-files//347/224/250/344/276/213/scripts/check-cases.mjs +70 -0
  292. package/skill-files//350/256/276/350/256/241/references//345/207/206/345/244/207.md +25 -0
  293. package/skill-files//350/256/276/350/256/241/references//346/212/244/346/240/217.md +134 -0
  294. package/skill-files//350/256/276/350/256/241/references//346/226/207/351/243/216.md +17 -0
  295. package/skill-files//350/256/276/350/256/241/references//347/225/214/351/235/242.md +18 -0
  296. package/skill-files//350/256/276/350/256/241/references//347/261/273/345/236/213.md +19 -0
  297. package/skill-files//350/256/276/350/256/241/references//347/272/246/346/235/237.md +25 -0
  298. package/skill-files//350/256/276/350/256/241/scripts/_smoke.mjs +72 -0
  299. package/skill-files//350/256/276/350/256/241/scripts/check-design.mjs +331 -0
  300. package/skill-files//351/234/200/346/261/202/references//346/212/244/346/240/217.md +26 -0
  301. package/skill-files//351/234/200/346/261/202/references//346/226/207/351/243/216.md +23 -0
  302. package/skill-files//351/234/200/346/261/202/references//346/267/261/346/200/235/346/221/204/345/205/245.md +21 -0
  303. package/skill-files//351/234/200/346/261/202/references//347/272/246/346/235/237.md +8 -0
  304. package/skill-files//351/234/200/346/261/202/references//351/234/200/346/261/202/345/276/205/345/256/232.md +13 -0
  305. package/skill-files//351/234/200/346/261/202/scripts/check-requirements.mjs +60 -0
  306. package/skill-files//351/252/214/346/224/266/references//346/212/244/346/240/217.md +27 -0
  307. package/skill-files//351/252/214/346/224/266/references//346/240/270/345/257/271.md +17 -0
  308. package/skill-files//351/252/214/346/224/266/references//347/272/246/346/235/237.md +7 -0
@@ -0,0 +1,836 @@
1
+ /**
2
+ * Init Command
3
+ *
4
+ * Sets up FreeSpec with Agent Skills and /freespec:* slash commands.
5
+ * This is the unified setup command that replaces both the old init and experimental commands.
6
+ */
7
+ import path from 'path';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import * as fs from 'fs';
11
+ import { createRequire } from 'module';
12
+ import { FileSystemUtils } from '../utils/file-system.js';
13
+ import { MAX_CONTEXT_SIZE, readProjectConfig, } from './project-config.js';
14
+ import { ANCHORED_OPENSPEC_DIRS, ensureDirectoryAnchor } from './openspec-root.js';
15
+ import { getSkillReferenceTransformer, getTransformerForTool, usesNaturalLanguageSkillReferences } from '../utils/command-references.js';
16
+ import { AI_TOOLS, getUniversalTool, universalToolFallbackHint, OPENSPEC_DIR_NAME, resolveToolIdAlias, } from './config.js';
17
+ import { PALETTE } from './styles/palette.js';
18
+ import { isInteractive } from '../utils/interactive.js';
19
+ import { serializeConfig } from './config-prompts.js';
20
+ import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
21
+ import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, copySkillExtras, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, formatIdeRestart, } from './shared/index.js';
22
+ import { getGlobalConfig } from './global-config.js';
23
+ import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
24
+ import { DEFAULT_SCHEMA } from './change-layout.js';
25
+ import { getAvailableTools } from './available-tools.js';
26
+ import { resolveSharedSkillWriters, sharedSkillRootOwner, writeSharedSkillTarget, } from './shared-skill-target.js';
27
+ import { migrateIfNeeded, migrateLegacyToolDirs, describeLegacyMigration, keptInPlaceNotice, hasMovableContent } from './migration.js';
28
+ import { resolveCommandSurfaceCapability, resolveCommandInvocation, shouldGenerateCommandsForTool, shouldGenerateSkillsForTool, shouldReconcileCommandFilesForTool, shouldRemoveSkillsForTool, } from './command-surface.js';
29
+ const require = createRequire(import.meta.url);
30
+ const { version: OPENSPEC_VERSION } = require('../../package.json');
31
+ // -----------------------------------------------------------------------------
32
+ // Constants
33
+ // -----------------------------------------------------------------------------
34
+ function formatLanguageContext(language) {
35
+ return [
36
+ `Language: ${language}`,
37
+ `All artifacts must be written in ${language}.`,
38
+ ].join('\n');
39
+ }
40
+ const PROGRESS_SPINNER = {
41
+ interval: 80,
42
+ frames: ['░░░', '▒░░', '▒▒░', '▒▒▒', '▓▒▒', '▓▓▒', '▓▓▓', '▒▓▓', '░▒▓'],
43
+ };
44
+ const WORKFLOW_TO_SKILL_DIR = {
45
+ '新建': 'freespec-新建',
46
+ '深思': 'freespec-深思',
47
+ '需求': 'freespec-需求',
48
+ '设计': 'freespec-设计',
49
+ '任务': 'freespec-任务',
50
+ '审文档': 'freespec-审文档',
51
+ '用例': 'freespec-用例',
52
+ '验收': 'freespec-验收',
53
+ '写Java': 'freespec-写Java',
54
+ '审Java': 'freespec-审Java',
55
+ };
56
+ // -----------------------------------------------------------------------------
57
+ // Init Command Class
58
+ // -----------------------------------------------------------------------------
59
+ export class InitCommand {
60
+ toolsArg;
61
+ language;
62
+ force;
63
+ interactiveOption;
64
+ profileOverride;
65
+ animation;
66
+ constructor(options = {}) {
67
+ this.toolsArg = options.tools;
68
+ this.language = this.normalizeLanguage(options.language);
69
+ this.force = options.force ?? false;
70
+ this.interactiveOption = options.interactive;
71
+ this.profileOverride = options.profile;
72
+ this.animation = options.animation ?? true;
73
+ }
74
+ async execute(targetPath) {
75
+ const projectPath = FileSystemUtils.canonicalizeExistingPath(targetPath);
76
+ const freespecDir = OPENSPEC_DIR_NAME;
77
+ const freespecPath = path.join(projectPath, freespecDir);
78
+ // Validation happens silently in the background
79
+ const extendMode = await this.validate(projectPath, freespecPath);
80
+ await this.assertLanguageCanBeApplied(projectPath, freespecPath);
81
+ // Migrate FreeSpec-managed skills left in renamed tool directories
82
+ // (e.g. .kimi -> .kimi-code) before detection so they stay recognized.
83
+ migrateLegacyToolDirs(projectPath);
84
+ // Detect available tools in the project (task 7.1)
85
+ const detectedTools = getAvailableTools(projectPath);
86
+ // Migration check: migrate existing projects to profile system (task 7.3)
87
+ if (extendMode) {
88
+ migrateIfNeeded(projectPath, detectedTools);
89
+ }
90
+ // Validate profile override early so invalid values fail before tool setup.
91
+ // The resolved value is consumed later when generation reads effective config.
92
+ // This runs ahead of the welcome screen so an invalid --profile does not make
93
+ // the user press Enter before seeing the error.
94
+ this.resolveProfileOverride();
95
+ // Show animated welcome screen (interactive mode only)
96
+ const canPrompt = this.canPromptInteractively();
97
+ if (canPrompt) {
98
+ const { showWelcomeScreen } = await import('../ui/welcome-screen.js');
99
+ await showWelcomeScreen(this.getActiveWorkflows(), { animate: this.animation });
100
+ }
101
+ // Get tool states before processing
102
+ const toolStates = getToolStates(projectPath);
103
+ // Get tool selection (pass detected tools for pre-selection)
104
+ const selectedToolIds = await this.getSelectedTools(toolStates, extendMode, detectedTools, projectPath);
105
+ // Validate selected tools
106
+ const validatedTools = this.validateTools(selectedToolIds, toolStates, projectPath);
107
+ // Selecting a renamed tool is consent to leave its former directory:
108
+ // init is about to write the current one, and leaving FreeSpec content
109
+ // behind would give the user two installs of the same tool.
110
+ for (const migration of migrateLegacyToolDirs(projectPath, validatedTools.map((tool) => tool.value))) {
111
+ if (hasMovableContent(migration)) {
112
+ console.log(chalk.dim(`Migrated ${describeLegacyMigration(migration)}: ${migration.from} → ${migration.to}`));
113
+ }
114
+ const kept = keptInPlaceNotice(migration);
115
+ if (kept)
116
+ console.log(chalk.dim(kept));
117
+ }
118
+ await this.createDirectoryStructure(freespecPath, extendMode);
119
+ const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
120
+ const configStatus = await this.createConfig(freespecPath, extendMode);
121
+ this.displaySuccessMessage(projectPath, validatedTools, results, configStatus);
122
+ if (results.failedTools.length > 0) {
123
+ throw new Error(`FreeSpec setup failed for: ${results.failedTools.map((tool) => tool.name).join(', ')}`);
124
+ }
125
+ }
126
+ // ═══════════════════════════════════════════════════════════
127
+ // VALIDATION & SETUP
128
+ // ═══════════════════════════════════════════════════════════
129
+ async validate(projectPath, freespecPath) {
130
+ const extendMode = await FileSystemUtils.directoryExists(freespecPath);
131
+ // Check write permissions
132
+ if (!(await FileSystemUtils.ensureWritePermissions(projectPath))) {
133
+ throw new Error(`Insufficient permissions to write to ${projectPath}`);
134
+ }
135
+ return extendMode;
136
+ }
137
+ canPromptInteractively() {
138
+ if (this.interactiveOption === false)
139
+ return false;
140
+ if (this.toolsArg !== undefined)
141
+ return false;
142
+ return isInteractive({ interactive: this.interactiveOption });
143
+ }
144
+ resolveProfileOverride() {
145
+ if (this.profileOverride === undefined) {
146
+ return undefined;
147
+ }
148
+ if (this.profileOverride === 'core' || this.profileOverride === 'custom') {
149
+ return this.profileOverride;
150
+ }
151
+ throw new Error(`Invalid profile "${this.profileOverride}". Available profiles: core, custom`);
152
+ }
153
+ /**
154
+ * Resolves the workflows the effective profile installs, so onboarding output
155
+ * only mentions commands that will actually exist.
156
+ */
157
+ getActiveWorkflows() {
158
+ const globalCfg = getGlobalConfig();
159
+ const activeProfile = this.resolveProfileOverride() ?? globalCfg.profile ?? 'core';
160
+ return [...getProfileWorkflows(activeProfile, globalCfg.workflows)];
161
+ }
162
+ // ═══════════════════════════════════════════════════════════
163
+ // TOOL SELECTION
164
+ // ═══════════════════════════════════════════════════════════
165
+ async getSelectedTools(toolStates, extendMode, detectedTools, projectPath) {
166
+ // Check for --tools flag first
167
+ const nonInteractiveSelection = this.resolveToolsArg();
168
+ if (nonInteractiveSelection !== null) {
169
+ return nonInteractiveSelection;
170
+ }
171
+ const validTools = getToolsWithSkillsDir();
172
+ const detectedToolIds = new Set(detectedTools.map((t) => t.value));
173
+ const configuredToolIds = new Set([...toolStates.entries()]
174
+ .filter(([, status]) => status.configured)
175
+ .map(([toolId]) => toolId));
176
+ const shouldPreselectDetected = !extendMode && configuredToolIds.size === 0;
177
+ const canPrompt = this.canPromptInteractively();
178
+ // Non-interactive mode: use detected tools as fallback (task 7.8)
179
+ if (!canPrompt) {
180
+ if (detectedToolIds.size > 0) {
181
+ return [...detectedToolIds];
182
+ }
183
+ const fallbackHint = universalToolFallbackHint(validTools);
184
+ throw new Error(`No tools detected and no --tools flag provided. Valid tools:\n ${validTools.join('\n ')}\n\nUse --tools all, --tools none, or --tools claude,cursor,...${fallbackHint ? `\n${fallbackHint}` : ''}`);
185
+ }
186
+ if (validTools.length === 0) {
187
+ throw new Error(`No tools available for skill generation.`);
188
+ }
189
+ // Interactive mode: show searchable multi-select
190
+ const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
191
+ // Build choices: pre-select configured tools; keep detected tools visible but unselected.
192
+ const sortedChoices = validTools
193
+ .map((toolId) => {
194
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
195
+ const status = toolStates.get(toolId);
196
+ const configured = status?.configured ?? false;
197
+ const detected = detectedToolIds.has(toolId);
198
+ return {
199
+ name: tool?.name || toolId,
200
+ value: toolId,
201
+ searchAliases: tool?.searchAliases,
202
+ configured,
203
+ detected: detected && !configured,
204
+ preSelected: configured || (shouldPreselectDetected && detected && !configured),
205
+ };
206
+ })
207
+ .sort((a, b) => {
208
+ // Configured tools first, then detected (not configured), then everything else.
209
+ if (a.configured && !b.configured)
210
+ return -1;
211
+ if (!a.configured && b.configured)
212
+ return 1;
213
+ if (a.detected && !b.detected)
214
+ return -1;
215
+ if (!a.detected && b.detected)
216
+ return 1;
217
+ return 0;
218
+ });
219
+ const configuredNames = validTools
220
+ .filter((toolId) => configuredToolIds.has(toolId))
221
+ .map((toolId) => AI_TOOLS.find((t) => t.value === toolId)?.name || toolId);
222
+ if (configuredNames.length > 0) {
223
+ console.log(`FreeSpec configured: ${configuredNames.join(', ')} (pre-selected)`);
224
+ }
225
+ const detectedOnlyNames = detectedTools
226
+ .filter((tool) => !configuredToolIds.has(tool.value))
227
+ .map((tool) => tool.name);
228
+ if (detectedOnlyNames.length > 0) {
229
+ const detectionLabel = shouldPreselectDetected
230
+ ? 'pre-selected for first-time setup'
231
+ : 'not pre-selected';
232
+ console.log(`Detected tool directories: ${detectedOnlyNames.join(', ')} (${detectionLabel})`);
233
+ }
234
+ // A search that matches nothing is where someone whose assistant is not on
235
+ // the list gives up (#653), so name the vendor-neutral entry right there.
236
+ const universalTool = getUniversalTool();
237
+ const universalHint = universalTool && validTools.includes(universalTool.value)
238
+ ? `Tool not listed? Clear the search and pick "${universalTool.name}".`
239
+ : undefined;
240
+ const selectedTools = await searchableMultiSelect({
241
+ message: `Select tools to set up (${validTools.length} available)`,
242
+ pageSize: 15,
243
+ choices: sortedChoices,
244
+ emptyHint: universalHint,
245
+ validate: (selected) => selected.length > 0 || 'Select at least one tool',
246
+ });
247
+ if (selectedTools.length === 0) {
248
+ throw new Error('At least one tool must be selected');
249
+ }
250
+ return selectedTools;
251
+ }
252
+ resolveToolsArg() {
253
+ if (typeof this.toolsArg === 'undefined') {
254
+ return null;
255
+ }
256
+ const raw = this.toolsArg.trim();
257
+ if (raw.length === 0) {
258
+ throw new Error('The --tools option requires a value. Use "all", "none", or a comma-separated list of tool IDs.');
259
+ }
260
+ const availableTools = getToolsWithSkillsDir();
261
+ const availableSet = new Set(availableTools);
262
+ const availableList = ['all', 'none', ...availableTools].join(', ');
263
+ const lowerRaw = raw.toLowerCase();
264
+ if (lowerRaw === 'all') {
265
+ return availableTools;
266
+ }
267
+ if (lowerRaw === 'none') {
268
+ return [];
269
+ }
270
+ const tokens = raw
271
+ .split(',')
272
+ .map((token) => token.trim())
273
+ .filter((token) => token.length > 0);
274
+ if (tokens.length === 0) {
275
+ throw new Error('The --tools option requires at least one tool ID when not using "all" or "none".');
276
+ }
277
+ // Retired ids resolve to their current tool, so a rebrand does not break
278
+ // an existing `--tools windsurf` in someone's setup script.
279
+ const normalizedTokens = tokens.map((token) => resolveToolIdAlias(token.toLowerCase()));
280
+ if (normalizedTokens.some((token) => token === 'all' || token === 'none')) {
281
+ throw new Error('Cannot combine reserved values "all" or "none" with specific tool IDs.');
282
+ }
283
+ const invalidTokens = tokens.filter((_token, index) => !availableSet.has(normalizedTokens[index]));
284
+ if (invalidTokens.length > 0) {
285
+ const fallbackHint = universalToolFallbackHint([...availableSet]);
286
+ throw new Error(`Invalid tool(s): ${invalidTokens.join(', ')}. Available values: ${availableList}${fallbackHint ? `\n${fallbackHint}` : ''}`);
287
+ }
288
+ // Deduplicate while preserving order
289
+ const deduped = [];
290
+ for (const token of normalizedTokens) {
291
+ if (!deduped.includes(token)) {
292
+ deduped.push(token);
293
+ }
294
+ }
295
+ return deduped;
296
+ }
297
+ validateTools(toolIds, toolStates, projectPath) {
298
+ const selectedTools = [];
299
+ for (const toolId of toolIds) {
300
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
301
+ if (!tool) {
302
+ const validToolIds = getToolsWithSkillsDir();
303
+ throw new Error(`Unknown tool '${toolId}'. Valid tools:\n ${validToolIds.join('\n ')}`);
304
+ }
305
+ if (!toolSupportsSkills(tool)) {
306
+ const validToolsWithSkills = getToolsWithSkillsDir();
307
+ throw new Error(`Tool '${toolId}' does not support skill generation.\nTools with skill generation support:\n ${validToolsWithSkills.join('\n ')}`);
308
+ }
309
+ selectedTools.push(tool);
310
+ }
311
+ // A selected tool may share its physical skills root with an already
312
+ // configured owner. Include that owner in the refresh without dropping the
313
+ // selected tool: it may still have an independent command surface.
314
+ const generationTools = [...selectedTools];
315
+ const delivery = getGlobalConfig().delivery ?? 'both';
316
+ for (const selected of selectedTools) {
317
+ if (!selected.skillsDir)
318
+ continue;
319
+ const selectedOwner = selected.value === 'codex' ||
320
+ !shouldGenerateSkillsForTool(selected.value, delivery)
321
+ ? undefined
322
+ : sharedSkillRootOwner(projectPath, selected.value);
323
+ for (const candidate of AI_TOOLS) {
324
+ if (candidate.skillsDir === selected.skillsDir &&
325
+ toolStates.get(candidate.value)?.configured &&
326
+ candidate.value === selectedOwner &&
327
+ !generationTools.includes(candidate)) {
328
+ generationTools.push(candidate);
329
+ }
330
+ }
331
+ }
332
+ const skillWriters = resolveSharedSkillWriters(projectPath, generationTools);
333
+ const sharedRoots = new Map();
334
+ for (const tool of generationTools) {
335
+ if (!tool.skillsDir)
336
+ continue;
337
+ const group = sharedRoots.get(tool.skillsDir) ?? [];
338
+ group.push(tool);
339
+ sharedRoots.set(tool.skillsDir, group);
340
+ }
341
+ for (const [root, group] of sharedRoots) {
342
+ if (group.length < 2)
343
+ continue;
344
+ const owner = group.find((tool) => skillWriters.has(tool.value));
345
+ console.log(chalk.dim(`${group.map((tool) => tool.name).join(', ')} share ${root}/skills; writing one tree for ${owner?.value}.`));
346
+ }
347
+ const validatedTools = [];
348
+ for (const tool of generationTools) {
349
+ if (!toolSupportsSkills(tool))
350
+ continue;
351
+ const preState = toolStates.get(tool.value);
352
+ const skillsPath = resolveToolSkillsDir(projectPath, tool);
353
+ const isGlobalSkillTarget = hasGlobalSkillTarget(tool);
354
+ validatedTools.push({
355
+ value: tool.value,
356
+ name: tool.name,
357
+ skillsDir: tool.skillsDir,
358
+ skillsPath,
359
+ skillsRoot: isGlobalSkillTarget ? skillsPath : projectPath,
360
+ isGlobalSkillTarget,
361
+ wasConfigured: preState?.configured ?? false,
362
+ writesSkills: !tool.skillsDir || skillWriters.has(tool.value),
363
+ });
364
+ }
365
+ return validatedTools;
366
+ }
367
+ // ═══════════════════════════════════════════════════════════
368
+ // DIRECTORY STRUCTURE
369
+ // ═══════════════════════════════════════════════════════════
370
+ async createDirectoryStructure(freespecPath, extendMode) {
371
+ const directories = [freespecPath];
372
+ if (extendMode) {
373
+ // In extend mode, just ensure directories exist without spinner
374
+ for (const dir of directories) {
375
+ FileSystemUtils.assertProjectArtifactPath(path.dirname(freespecPath), dir);
376
+ await FileSystemUtils.createDirectory(dir);
377
+ }
378
+ await this.writeGitkeepFiles(freespecPath);
379
+ return;
380
+ }
381
+ const spinner = this.startSpinner('Creating FreeSpec structure...');
382
+ for (const dir of directories) {
383
+ FileSystemUtils.assertProjectArtifactPath(path.dirname(freespecPath), dir);
384
+ await FileSystemUtils.createDirectory(dir);
385
+ }
386
+ await this.writeGitkeepFiles(freespecPath);
387
+ spinner.stopAndPersist({
388
+ symbol: PALETTE.white('▌'),
389
+ text: PALETTE.white('FreeSpec structure created'),
390
+ });
391
+ }
392
+ async writeGitkeepFiles(freespecPath) {
393
+ for (const relativeDir of ANCHORED_OPENSPEC_DIRS) {
394
+ await ensureDirectoryAnchor(path.dirname(freespecPath), relativeDir);
395
+ }
396
+ }
397
+ // ═══════════════════════════════════════════════════════════
398
+ // SKILL & COMMAND GENERATION
399
+ // ═══════════════════════════════════════════════════════════
400
+ /**
401
+ * Generates skill files and slash commands for each selected tool,
402
+ * honoring the configured delivery mode (skills, commands, or both).
403
+ *
404
+ * @param projectPath - Absolute path to the project root
405
+ * @param tools - Selected tools with their skill directory metadata
406
+ * @returns Created, refreshed, and failed tools plus removed artifact counts
407
+ */
408
+ async generateSkillsAndCommands(projectPath, tools) {
409
+ const createdTools = [];
410
+ const refreshedTools = [];
411
+ const failedTools = [];
412
+ const commandsSkipped = [];
413
+ const skillsInvocableCommandSkips = [];
414
+ let removedCommandCount = 0;
415
+ let removedSkillCount = 0;
416
+ // Read global config for profile and delivery settings (use --profile override if set)
417
+ const globalConfig = getGlobalConfig();
418
+ const profile = this.resolveProfileOverride() ?? globalConfig.profile ?? 'core';
419
+ const delivery = globalConfig.delivery ?? 'both';
420
+ const workflows = getProfileWorkflows(profile, globalConfig.workflows);
421
+ // Get skill and command templates filtered by profile workflows
422
+ const deliveryIncludesCommands = delivery !== 'skills';
423
+ const skillTemplates = getSkillTemplates(workflows);
424
+ const commandContents = getCommandContents(workflows);
425
+ // Process each tool
426
+ for (const tool of tools) {
427
+ const spinner = ora(`Setting up ${tool.name}...`).start();
428
+ try {
429
+ const shouldGenerateSkills = shouldGenerateSkillsForTool(tool.value, delivery);
430
+ const shouldGenerateCommands = shouldGenerateCommandsForTool(tool.value, delivery);
431
+ // Generate skill files if the selected delivery and tool capability allow skills
432
+ if (shouldGenerateSkills && tool.writesSkills) {
433
+ // Create skill directories and SKILL.md files
434
+ for (const { template, dirName } of skillTemplates) {
435
+ const skillDir = path.join(tool.skillsPath, dirName);
436
+ const skillFile = path.join(skillDir, 'SKILL.md');
437
+ // Generate SKILL.md content with YAML frontmatter including generatedBy
438
+ const transformer = getTransformerForTool(tool.value, delivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
439
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
440
+ // Write the skill file
441
+ FileSystemUtils.assertPathWithin(tool.skillsRoot, skillFile);
442
+ await FileSystemUtils.writeFile(skillFile, skillContent);
443
+ await copySkillExtras(skillDir, template.name);
444
+ }
445
+ writeSharedSkillTarget(projectPath, tool.value);
446
+ }
447
+ if (shouldRemoveSkillsForTool(tool.value, delivery) &&
448
+ tool.writesSkills &&
449
+ !tool.isGlobalSkillTarget) {
450
+ removedSkillCount += await this.removeSkillDirs(tool.skillsRoot, tool.skillsPath);
451
+ // Retain an explicit selection even when this delivery mode produces
452
+ // no skills, so a divergent legacy sibling cannot reclaim ownership.
453
+ writeSharedSkillTarget(projectPath, tool.value);
454
+ }
455
+ // Generate commands if delivery includes commands
456
+ if (shouldGenerateCommands) {
457
+ const adapter = CommandAdapterRegistry.get(tool.value);
458
+ if (adapter) {
459
+ const generatedCommands = generateCommands(commandContents, adapter);
460
+ for (const cmd of generatedCommands) {
461
+ const commandFile = FileSystemUtils.resolveProjectArtifactPath(projectPath, cmd.path);
462
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
463
+ }
464
+ }
465
+ }
466
+ else if (deliveryIncludesCommands) {
467
+ if (resolveCommandSurfaceCapability(tool.value) === 'skills-invocable') {
468
+ skillsInvocableCommandSkips.push(tool.value);
469
+ }
470
+ else {
471
+ commandsSkipped.push(tool.value);
472
+ }
473
+ }
474
+ if (shouldReconcileCommandFilesForTool(tool.value, delivery)) {
475
+ removedCommandCount += await this.removeCommandFiles(projectPath, tool.value);
476
+ }
477
+ spinner.succeed(`Setup complete for ${tool.name}`);
478
+ if (tool.wasConfigured) {
479
+ refreshedTools.push(tool);
480
+ }
481
+ else {
482
+ createdTools.push(tool);
483
+ }
484
+ }
485
+ catch (error) {
486
+ spinner.fail(`Failed for ${tool.name}`);
487
+ failedTools.push({ name: tool.name, error: error });
488
+ }
489
+ }
490
+ for (const tool of [...createdTools, ...refreshedTools]) {
491
+ for (const migration of migrateLegacyToolDirs(projectPath, [tool.value], 'after-generation')) {
492
+ if (hasMovableContent(migration)) {
493
+ console.log(chalk.dim(`Migrated ${describeLegacyMigration(migration)}: ${migration.from} → ${migration.to}`));
494
+ }
495
+ const kept = keptInPlaceNotice(migration);
496
+ if (kept)
497
+ console.log(chalk.dim(kept));
498
+ }
499
+ }
500
+ return {
501
+ createdTools,
502
+ refreshedTools,
503
+ failedTools,
504
+ commandsSkipped,
505
+ skillsInvocableCommandSkips,
506
+ removedCommandCount,
507
+ removedSkillCount,
508
+ };
509
+ }
510
+ // ═══════════════════════════════════════════════════════════
511
+ // CONFIG FILE
512
+ // ═══════════════════════════════════════════════════════════
513
+ normalizeLanguage(language) {
514
+ if (language === undefined)
515
+ return undefined;
516
+ const normalized = language.trim();
517
+ if (!normalized) {
518
+ throw new Error('The --language option requires a non-empty value.');
519
+ }
520
+ if (/\p{Cc}|\p{Bidi_Control}|[\u200B\u2028\u2029\uFEFF]/u.test(normalized)) {
521
+ throw new Error('The --language option must be a single line without control or invisible formatting characters.');
522
+ }
523
+ const serializedContext = `${formatLanguageContext(normalized)}\n`;
524
+ if (Buffer.byteLength(serializedContext, 'utf8') > MAX_CONTEXT_SIZE) {
525
+ throw new Error(`The --language option is too long for FreeSpec's ${MAX_CONTEXT_SIZE / 1024}KB project context limit.`);
526
+ }
527
+ return normalized;
528
+ }
529
+ languageContext() {
530
+ if (!this.language)
531
+ return undefined;
532
+ return formatLanguageContext(this.language);
533
+ }
534
+ async assertLanguageCanBeApplied(projectPath, freespecPath) {
535
+ const languageContext = this.languageContext();
536
+ if (!languageContext)
537
+ return;
538
+ const configPath = path.join(freespecPath, 'config.yaml');
539
+ const hasConfig = fs.existsSync(configPath) ||
540
+ fs.existsSync(path.join(freespecPath, 'config.yml'));
541
+ if (!hasConfig) {
542
+ try {
543
+ FileSystemUtils.assertProjectArtifactPath(projectPath, configPath);
544
+ }
545
+ catch (error) {
546
+ const reason = error instanceof Error ? `: ${error.message}` : '';
547
+ throw new Error(`Cannot create freespec/config.yaml for --language${reason}`);
548
+ }
549
+ if (!(await FileSystemUtils.canWriteFile(configPath))) {
550
+ throw new Error('Cannot create freespec/config.yaml for --language: the destination is not writable.');
551
+ }
552
+ return;
553
+ }
554
+ const existingContext = readProjectConfig(projectPath)?.context;
555
+ if (existingContext?.includes(languageContext))
556
+ return;
557
+ throw new Error('--language does not overwrite an existing FreeSpec config. ' +
558
+ 'Add the language instruction to its context field instead.');
559
+ }
560
+ async createConfig(freespecPath, extendMode) {
561
+ const configPath = path.join(freespecPath, 'config.yaml');
562
+ const configYmlPath = path.join(freespecPath, 'config.yml');
563
+ const configYamlExists = fs.existsSync(configPath);
564
+ const configYmlExists = fs.existsSync(configYmlPath);
565
+ if (configYamlExists || configYmlExists) {
566
+ return 'exists';
567
+ }
568
+ try {
569
+ const yamlContent = serializeConfig({
570
+ schema: DEFAULT_SCHEMA,
571
+ context: this.languageContext(),
572
+ });
573
+ FileSystemUtils.assertProjectArtifactPath(path.dirname(freespecPath), configPath);
574
+ await FileSystemUtils.writeFile(configPath, yamlContent);
575
+ return 'created';
576
+ }
577
+ catch (error) {
578
+ if (this.language) {
579
+ const reason = error instanceof Error ? `: ${error.message}` : '';
580
+ throw new Error(`Failed to create freespec/config.yaml for --language${reason}`);
581
+ }
582
+ return 'skipped';
583
+ }
584
+ }
585
+ // ═══════════════════════════════════════════════════════════
586
+ // UI & OUTPUT
587
+ // ═══════════════════════════════════════════════════════════
588
+ displaySuccessMessage(projectPath, tools, results, configStatus) {
589
+ console.log();
590
+ console.log(chalk.bold(results.failedTools.length > 0 ? 'FreeSpec Setup Incomplete' : 'FreeSpec Setup Complete'));
591
+ console.log();
592
+ // Show created vs refreshed tools
593
+ if (results.createdTools.length > 0) {
594
+ console.log(`Created: ${results.createdTools.map((t) => t.name).join(', ')}`);
595
+ }
596
+ if (results.refreshedTools.length > 0) {
597
+ console.log(`Refreshed: ${results.refreshedTools.map((t) => t.name).join(', ')}`);
598
+ }
599
+ // Show counts (respecting profile filter)
600
+ const successfulTools = [...results.createdTools, ...results.refreshedTools];
601
+ if (successfulTools.length > 0) {
602
+ const globalConfig = getGlobalConfig();
603
+ const profile = this.profileOverride ?? globalConfig.profile ?? 'core';
604
+ const delivery = globalConfig.delivery ?? 'both';
605
+ const workflows = getProfileWorkflows(profile, globalConfig.workflows);
606
+ const usesGlobalSkillTarget = successfulTools.some((tool) => tool.isGlobalSkillTarget);
607
+ if (!usesGlobalSkillTarget) {
608
+ const toolDirs = [
609
+ ...new Set(successfulTools
610
+ .map((tool) => tool.skillsDir)
611
+ .filter((skillsDir) => Boolean(skillsDir))),
612
+ ].join(', ');
613
+ const skillCount = successfulTools.some((tool) => shouldGenerateSkillsForTool(tool.value, delivery))
614
+ ? getSkillTemplates(workflows).length
615
+ : 0;
616
+ const commandCount = successfulTools.some((tool) => shouldGenerateCommandsForTool(tool.value, delivery))
617
+ ? getCommandContents(workflows).length
618
+ : 0;
619
+ if (skillCount > 0 && commandCount > 0) {
620
+ console.log(`${skillCount} skills and ${commandCount} commands in ${toolDirs}/`);
621
+ }
622
+ else if (skillCount > 0) {
623
+ console.log(`${skillCount} skills in ${toolDirs}/`);
624
+ }
625
+ else if (commandCount > 0) {
626
+ console.log(`${commandCount} commands in ${toolDirs}/`);
627
+ }
628
+ }
629
+ else {
630
+ const skillTools = successfulTools.filter((tool) => shouldGenerateSkillsForTool(tool.value, delivery));
631
+ const skillCount = skillTools.length * getSkillTemplates(workflows).length;
632
+ if (skillCount > 0) {
633
+ const skillDirs = [...new Set(skillTools.map((tool) => tool.skillsPath))];
634
+ console.log(`${skillCount} skills in ${skillDirs.join(', ')}`);
635
+ }
636
+ const commandContents = getCommandContents(workflows);
637
+ const commandTools = successfulTools.filter((tool) => shouldGenerateCommandsForTool(tool.value, delivery));
638
+ const commandCount = commandTools.length * commandContents.length;
639
+ if (commandCount > 0) {
640
+ const commandDirs = [
641
+ ...new Set(commandTools.flatMap((tool) => {
642
+ const adapter = CommandAdapterRegistry.get(tool.value);
643
+ if (!adapter)
644
+ return [];
645
+ return commandContents.map((command) => {
646
+ const commandPath = adapter.getFilePath(command.id);
647
+ const absolutePath = path.isAbsolute(commandPath)
648
+ ? commandPath
649
+ : path.join(projectPath, commandPath);
650
+ return path.dirname(absolutePath);
651
+ });
652
+ })),
653
+ ];
654
+ console.log(`${commandCount} commands in ${commandDirs.join(', ')}`);
655
+ }
656
+ }
657
+ }
658
+ // Show failures
659
+ if (results.failedTools.length > 0) {
660
+ console.log(chalk.red(`Failed: ${results.failedTools.map((f) => `${f.name} (${f.error.message})`).join(', ')}`));
661
+ }
662
+ // Show skipped commands
663
+ if (results.commandsSkipped.length > 0) {
664
+ console.log(chalk.dim(`Commands skipped for: ${results.commandsSkipped.join(', ')} (no adapter)`));
665
+ }
666
+ if (results.skillsInvocableCommandSkips.length > 0) {
667
+ console.log(chalk.dim(`Commands skipped for: ${results.skillsInvocableCommandSkips.join(', ')} (uses skills)`));
668
+ }
669
+ if (results.removedCommandCount > 0) {
670
+ console.log(chalk.dim(`Removed: ${results.removedCommandCount} command files (delivery: skills)`));
671
+ }
672
+ if (results.removedSkillCount > 0) {
673
+ console.log(chalk.dim(`Removed: ${results.removedSkillCount} skill directories (delivery: commands)`));
674
+ }
675
+ // Show manual setup notes for tools that need extra configuration
676
+ for (const tool of successfulTools) {
677
+ const setupNote = AI_TOOLS.find((t) => t.value === tool.value)?.setupNote;
678
+ if (setupNote) {
679
+ console.log(chalk.yellow(`Setup required for ${tool.name}: ${setupNote}`));
680
+ }
681
+ }
682
+ // Config status
683
+ if (configStatus === 'created') {
684
+ console.log(`Config: freespec/config.yaml (schema: ${DEFAULT_SCHEMA})`);
685
+ }
686
+ else if (configStatus === 'exists') {
687
+ // Show actual filename (config.yaml or config.yml)
688
+ const configYaml = path.join(projectPath, OPENSPEC_DIR_NAME, 'config.yaml');
689
+ const configYml = path.join(projectPath, OPENSPEC_DIR_NAME, 'config.yml');
690
+ const configName = fs.existsSync(configYaml) ? 'config.yaml' : fs.existsSync(configYml) ? 'config.yml' : 'config.yaml';
691
+ console.log(`Config: freespec/${configName} (exists)`);
692
+ }
693
+ else {
694
+ console.log(chalk.dim(`Config: skipped (non-interactive mode)`));
695
+ }
696
+ // Getting started (task 7.6: show propose if in profile)
697
+ const activeWorkflows = this.getActiveWorkflows();
698
+ // When no tool got /freespec:* commands, point at the skill instead of a
699
+ // command that does not exist.
700
+ const activeDelivery = getGlobalConfig().delivery ?? 'both';
701
+ const commandsGenerated = successfulTools.some((tool) => shouldGenerateCommandsForTool(tool.value, activeDelivery));
702
+ const skillsGenerated = successfulTools.some((tool) => shouldGenerateSkillsForTool(tool.value, activeDelivery));
703
+ // Each hint line must be a usable instruction for the tool it serves.
704
+ // Tools that generated commands are told the command name their files
705
+ // answer to (/freespec:* when namespaced under freespec/, /freespec-* when the
706
+ // filename is the command); tools that only got skills are told their
707
+ // documented skill invocation (Kimi Code: /skill:freespec-*; Codex CLI:
708
+ // $freespec-*; others: /freespec-*). Tools that got no artifacts are
709
+ // covered by the configuration correction instead. When the selection
710
+ // disagrees, print one line per distinct instruction, labeled with the
711
+ // tools it applies to.
712
+ const startHintLines = (command) => {
713
+ const hintToTools = new Map();
714
+ for (const tool of successfulTools) {
715
+ let hint;
716
+ if (shouldGenerateCommandsForTool(tool.value, activeDelivery)) {
717
+ const transformer = getTransformerForTool(tool.value, activeDelivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
718
+ hint = `Start your first change: ${transformer ? transformer(command) : command} "your idea"`;
719
+ }
720
+ else if (shouldGenerateSkillsForTool(tool.value, activeDelivery)) {
721
+ const skillReference = getSkillReferenceTransformer(tool.value)(command);
722
+ // Tools with no slash surface (e.g. Rovo Dev) reference skills as
723
+ // prose ("the freespec-propose skill"); phrase the hint so it reads
724
+ // as an instruction rather than a dead command with an argument.
725
+ if (usesNaturalLanguageSkillReferences(tool.value)) {
726
+ hint = `Start your first change: ask ${tool.name} to use ${skillReference} with "your idea"`;
727
+ }
728
+ else if (tool.value === 'codex') {
729
+ hint = `Start your first change: ${skillReference} "your idea" (Codex CLI or IDE); in the Codex desktop app, select ${skillReference.slice(1)} from Skills in the sidebar`;
730
+ }
731
+ else {
732
+ hint = `Start your first change: ${skillReference} "your idea"`;
733
+ }
734
+ }
735
+ else {
736
+ continue;
737
+ }
738
+ hintToTools.set(hint, [...(hintToTools.get(hint) ?? []), tool.name]);
739
+ }
740
+ if (hintToTools.size === 0) {
741
+ // No successful tools: keep the generic command hint
742
+ return [`Start your first change: ${command} "your idea"`];
743
+ }
744
+ if (hintToTools.size === 1) {
745
+ return [[...hintToTools.keys()][0]];
746
+ }
747
+ return [...hintToTools.entries()].map(([hint, toolNames]) => `${hint} (${toolNames.join(', ')})`);
748
+ };
749
+ const printStartHints = (command) => {
750
+ console.log(chalk.bold('Getting started:'));
751
+ for (const line of startHintLines(command)) {
752
+ console.log(` ${line}`);
753
+ }
754
+ };
755
+ console.log();
756
+ // delivery=commands with tools that only support skills: those tools get
757
+ // no artifacts at all, so print a per-tool configuration correction
758
+ // rather than leave them with a dead (or missing) instruction — even
759
+ // when other selected tools did get commands or skills.
760
+ const zeroArtifactTools = successfulTools.filter((tool) => !shouldGenerateSkillsForTool(tool.value, activeDelivery) &&
761
+ !shouldGenerateCommandsForTool(tool.value, activeDelivery));
762
+ if (zeroArtifactTools.length > 0) {
763
+ const names = zeroArtifactTools.map((tool) => tool.name).join(', ');
764
+ console.log(chalk.yellow(`No skills or commands were generated for ${names}: delivery is set to 'commands' but ` +
765
+ `${zeroArtifactTools.length === 1 ? 'it supports' : 'they support'} only skills. ` +
766
+ `Run 'freespec config set delivery both' to generate skills.`));
767
+ }
768
+ if (!(successfulTools.length > 0 && !commandsGenerated && !skillsGenerated)) {
769
+ if (activeWorkflows.includes('新建')) {
770
+ printStartHints('/freespec:新建');
771
+ }
772
+ else {
773
+ console.log("Done. Run 'freespec config profile' to configure your workflows.");
774
+ }
775
+ }
776
+ // Links
777
+ console.log();
778
+ console.log(`Learn more: ${chalk.cyan('https://gitee.com/niu_jiajie/openspec.git')}`);
779
+ console.log(`Feedback: ${chalk.cyan('https://gitee.com/niu_jiajie/openspec/issues')}`);
780
+ // Restart instruction for successfully configured IDE/editor-resident tools
781
+ // with a supported surface under the active delivery. The rule and wording live in
782
+ // formatIdeRestart so `update` says the same thing for the same event.
783
+ const restartHint = formatIdeRestart(successfulTools.map((tool) => tool.value), activeDelivery);
784
+ if (restartHint) {
785
+ console.log();
786
+ console.log(chalk.white(restartHint));
787
+ }
788
+ console.log();
789
+ }
790
+ startSpinner(text) {
791
+ return ora({
792
+ text,
793
+ stream: process.stdout,
794
+ color: 'gray',
795
+ spinner: PROGRESS_SPINNER,
796
+ }).start();
797
+ }
798
+ async removeSkillDirs(skillsRoot, skillsDir) {
799
+ let removed = 0;
800
+ for (const dirName of Object.values(WORKFLOW_TO_SKILL_DIR)) {
801
+ const skillDir = path.join(skillsDir, dirName);
802
+ if (!fs.existsSync(skillDir))
803
+ continue;
804
+ FileSystemUtils.assertPathWithin(skillsRoot, skillDir);
805
+ try {
806
+ await fs.promises.rm(skillDir, { recursive: true, force: true });
807
+ removed++;
808
+ }
809
+ catch {
810
+ // Ignore errors
811
+ }
812
+ }
813
+ return removed;
814
+ }
815
+ async removeCommandFiles(projectPath, toolId) {
816
+ let removed = 0;
817
+ const adapter = CommandAdapterRegistry.get(toolId);
818
+ if (!adapter)
819
+ return 0;
820
+ for (const workflow of ALL_WORKFLOWS) {
821
+ const cmdPath = adapter.getFilePath(workflow);
822
+ const fullPath = FileSystemUtils.resolveProjectArtifactPath(projectPath, cmdPath);
823
+ try {
824
+ if (fs.existsSync(fullPath)) {
825
+ await fs.promises.unlink(fullPath);
826
+ removed++;
827
+ }
828
+ }
829
+ catch {
830
+ // Ignore errors
831
+ }
832
+ }
833
+ return removed;
834
+ }
835
+ }
836
+ //# sourceMappingURL=init.js.map