@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,390 @@
1
+ /**
2
+ * Update Plan
3
+ *
4
+ * The write seam `update` uses for every file it generates or removes.
5
+ *
6
+ * A real run forwards to the filesystem. A dry run records what the same call
7
+ * would have done and writes nothing, so the preview and the real update come
8
+ * from one code path instead of two descriptions of it.
9
+ */
10
+ import path from 'path';
11
+ import * as fs from 'fs';
12
+ import os from 'os';
13
+ import { FileSystemUtils } from '../utils/file-system.js';
14
+ export const realFileOps = {
15
+ async writeFile(filePath, content) {
16
+ await FileSystemUtils.writeFile(filePath, content);
17
+ },
18
+ async removeFile(filePath) {
19
+ await fs.promises.unlink(filePath);
20
+ },
21
+ async removeDirectory(dirPath) {
22
+ await fs.promises.rm(dirPath, { recursive: true, force: true });
23
+ },
24
+ async removeEmptyDirectory(dirPath) {
25
+ await fs.promises.rmdir(dirPath);
26
+ },
27
+ };
28
+ export class UpdatePlanRecorder {
29
+ projectPath;
30
+ notes = [];
31
+ failures = [];
32
+ /**
33
+ * The tree as the real update would have it so far: absolute path to planned
34
+ * content, a moved directory, or null once planned away (a removed directory
35
+ * takes everything under it). Steps that read what an earlier step left
36
+ * behind read this through `view`.
37
+ */
38
+ staged = new Map();
39
+ /** Every file path the plan has touched, with the tool that touched it last. */
40
+ touched = new Map();
41
+ constructor(projectPath) {
42
+ this.projectPath = projectPath;
43
+ }
44
+ async writeFile(filePath, content, tool) {
45
+ const target = path.resolve(filePath);
46
+ this.probeWrite(target);
47
+ this.stage(target, content, tool);
48
+ }
49
+ async removeFile(filePath, tool) {
50
+ const target = path.resolve(filePath);
51
+ this.probeRemove(target);
52
+ this.stage(target, null, tool);
53
+ }
54
+ async removeEmptyDirectory(dirPath) {
55
+ // An empty directory holds no file, so there is nothing to list.
56
+ this.staged.set(path.resolve(dirPath), null);
57
+ }
58
+ async removeDirectory(dirPath, tool) {
59
+ const target = path.resolve(dirPath);
60
+ // A symlinked skills directory - what a dotfile manager leaves behind - is
61
+ // removed as the link itself: `fs.rm` with `recursive` unlinks it and never
62
+ // touches the tree it points at, so the link is the one entry that goes.
63
+ let stats = null;
64
+ try {
65
+ stats = fs.lstatSync(target);
66
+ }
67
+ catch {
68
+ stats = null;
69
+ }
70
+ if (stats?.isSymbolicLink()) {
71
+ this.stage(target, null, tool);
72
+ return;
73
+ }
74
+ // Otherwise every file under it goes - on disk, and any the plan put there.
75
+ // Each is cleared on its own: a file's own entry outranks its directory's.
76
+ for (const filePath of this.filesUnder(target))
77
+ this.stage(filePath, null, tool);
78
+ this.staged.set(target, null);
79
+ }
80
+ /**
81
+ * A rename, as the legacy-root migration makes it: whatever the entry is
82
+ * moves, a directory with everything in it.
83
+ */
84
+ async moveFile(source, destination, tool) {
85
+ const from = path.resolve(source);
86
+ const to = path.resolve(destination);
87
+ this.probeCreatableIn(path.dirname(to));
88
+ if (this.entryKind(from).kind === 'directory') {
89
+ if (this.entryKind(to).kind !== 'missing') {
90
+ throw errnoError('ENOTEMPTY', 'directory not empty', 'rename', to);
91
+ }
92
+ const files = this.filesUnder(from);
93
+ const contents = files.map((filePath) => [filePath, this.view.read(filePath)]);
94
+ for (const filePath of files)
95
+ this.stage(filePath, null, tool);
96
+ this.staged.set(from, null);
97
+ this.staged.set(to, STAGED_DIRECTORY);
98
+ for (const [filePath, content] of contents) {
99
+ this.stage(path.join(to, path.relative(from, filePath)), content, tool);
100
+ }
101
+ return;
102
+ }
103
+ const content = this.view.read(from);
104
+ this.probeWrite(to);
105
+ this.probeRemove(from);
106
+ this.stage(from, null, tool);
107
+ this.stage(to, content, tool);
108
+ }
109
+ /**
110
+ * The tree with every planned write, move and removal applied. A path the
111
+ * plan has not touched answers exactly as the disk does.
112
+ */
113
+ view = {
114
+ exists: (filePath) => this.entryKind(path.resolve(filePath)).kind !== 'missing',
115
+ isDirectory: (filePath) => this.entryKind(path.resolve(filePath)).kind === 'directory',
116
+ read: (filePath) => {
117
+ const target = path.resolve(filePath);
118
+ const staged = this.stagedEntry(target);
119
+ if (staged === undefined)
120
+ return fs.readFileSync(target, 'utf-8');
121
+ if (staged === null)
122
+ throw errnoError('ENOENT', 'no such file or directory', 'open', target);
123
+ if (staged === STAGED_DIRECTORY) {
124
+ throw errnoError('EISDIR', 'illegal operation on a directory', 'read', target);
125
+ }
126
+ return staged;
127
+ },
128
+ };
129
+ addNote(code, message) {
130
+ if (this.notes.some((note) => note.code === code))
131
+ return;
132
+ this.notes.push({ code, message });
133
+ }
134
+ addFailure(tool, error) {
135
+ this.failures.push({ tool, error });
136
+ }
137
+ /**
138
+ * Each touched path, compared as it is on disk now with how the plan leaves
139
+ * it. A file written and then removed, or rewritten with the bytes it
140
+ * already had, is no change at all.
141
+ */
142
+ getPlan() {
143
+ const byPath = new Map();
144
+ for (const [filePath, tool] of this.touched) {
145
+ const before = originalEntry(filePath);
146
+ const after = this.stagedEntry(filePath);
147
+ if (after === undefined || after === STAGED_DIRECTORY)
148
+ continue;
149
+ let action;
150
+ if (after === null) {
151
+ if (before !== undefined)
152
+ action = 'delete';
153
+ }
154
+ else if (before === undefined) {
155
+ action = 'add';
156
+ }
157
+ else if (before !== after) {
158
+ action = 'update';
159
+ }
160
+ if (!action)
161
+ continue;
162
+ const displayPath = this.toDisplayPath(filePath);
163
+ byPath.set(displayPath, { path: displayPath, action, tool });
164
+ }
165
+ const changes = [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
166
+ return { changes, notes: [...this.notes], failures: [...this.failures] };
167
+ }
168
+ stage(target, entry, tool) {
169
+ this.staged.set(target, entry);
170
+ this.touched.set(target, tool);
171
+ }
172
+ /**
173
+ * What the plan has put at a path: content, a moved directory, null once
174
+ * planned away, or undefined when the plan has not touched it.
175
+ */
176
+ stagedEntry(target) {
177
+ for (let current = target;; current = path.dirname(current)) {
178
+ const staged = this.staged.get(current);
179
+ if (staged === null)
180
+ return null;
181
+ if (staged !== undefined && current === target)
182
+ return staged;
183
+ if (path.dirname(current) === current)
184
+ return undefined;
185
+ }
186
+ }
187
+ /** Whether the plan has put any file or directory below `directory`. */
188
+ hasStagedDescendant(directory) {
189
+ const prefix = directory.endsWith(path.sep) ? directory : directory + path.sep;
190
+ for (const key of this.staged.keys()) {
191
+ if (!key.startsWith(prefix))
192
+ continue;
193
+ const entry = this.stagedEntry(key);
194
+ if (entry !== null && entry !== undefined)
195
+ return true;
196
+ }
197
+ return false;
198
+ }
199
+ /** Files under a directory in the tree as planned: those on disk, and those the plan added. */
200
+ filesUnder(directory) {
201
+ const files = new Set();
202
+ for (const filePath of listFilesRecursively(directory)) {
203
+ if (this.stagedEntry(filePath) !== null)
204
+ files.add(filePath);
205
+ }
206
+ const prefix = directory + path.sep;
207
+ for (const key of this.staged.keys()) {
208
+ if (key.startsWith(prefix) && typeof this.stagedEntry(key) === 'string')
209
+ files.add(key);
210
+ }
211
+ return [...files];
212
+ }
213
+ /**
214
+ * What is at a path in the tree as planned, and whether that is what is on
215
+ * disk (so its permissions are real).
216
+ */
217
+ entryKind(target) {
218
+ const staged = this.stagedEntry(target);
219
+ if (staged === null) {
220
+ // Removed, but the plan may have written into it again since.
221
+ return this.hasStagedDescendant(target)
222
+ ? { kind: 'directory', onDisk: false }
223
+ : { kind: 'missing', onDisk: false };
224
+ }
225
+ if (staged === STAGED_DIRECTORY)
226
+ return { kind: 'directory', onDisk: false };
227
+ if (typeof staged === 'string') {
228
+ return { kind: 'file', onDisk: fs.existsSync(target) };
229
+ }
230
+ try {
231
+ return { kind: fs.statSync(target).isDirectory() ? 'directory' : 'file', onDisk: true };
232
+ }
233
+ catch (error) {
234
+ const code = error.code;
235
+ if (code !== 'ENOENT' && code !== 'ENOTDIR')
236
+ throw error;
237
+ }
238
+ return this.hasStagedDescendant(target)
239
+ ? { kind: 'directory', onDisk: false }
240
+ : { kind: 'missing', onDisk: false };
241
+ }
242
+ /**
243
+ * Fails the way the real write would, without writing: a directory where the
244
+ * file belongs, a file where one of its directories belongs, or no
245
+ * permission. Recording the write instead would plan past the point where
246
+ * the real update stops for this tool, listing files it never writes.
247
+ */
248
+ probeWrite(target) {
249
+ const entry = this.entryKind(target);
250
+ if (entry.kind === 'directory') {
251
+ throw errnoError('EISDIR', 'illegal operation on a directory', 'open', target);
252
+ }
253
+ if (entry.kind === 'file') {
254
+ if (entry.onDisk)
255
+ fs.accessSync(target, fs.constants.W_OK);
256
+ return;
257
+ }
258
+ this.probeCreatableIn(path.dirname(target));
259
+ }
260
+ /** Fails the way unlinking the file would. */
261
+ probeRemove(target) {
262
+ const entry = this.entryKind(target);
263
+ if (entry.kind === 'directory') {
264
+ throw errnoError('EISDIR', 'illegal operation on a directory', 'unlink', target);
265
+ }
266
+ if (entry.kind === 'file' && entry.onDisk) {
267
+ fs.accessSync(path.dirname(target), fs.constants.W_OK);
268
+ }
269
+ }
270
+ /**
271
+ * The nearest existing ancestor has to be a directory the update can create
272
+ * entries in; anything it would create below that starts out empty.
273
+ */
274
+ probeCreatableIn(directory) {
275
+ for (let current = directory;; current = path.dirname(current)) {
276
+ const entry = this.entryKind(current);
277
+ if (entry.kind === 'file') {
278
+ throw errnoError('ENOTDIR', 'not a directory', 'mkdir', current);
279
+ }
280
+ if (entry.kind === 'directory') {
281
+ if (entry.onDisk)
282
+ fs.accessSync(current, fs.constants.W_OK);
283
+ return;
284
+ }
285
+ if (path.dirname(current) === current)
286
+ return;
287
+ }
288
+ }
289
+ toDisplayPath(filePath) {
290
+ const absolute = path.resolve(filePath);
291
+ const relative = path.relative(this.projectPath, absolute);
292
+ if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
293
+ return FileSystemUtils.toPosixPath(relative);
294
+ }
295
+ // Same home resolution the skill paths use, so a global skill is named
296
+ // against the home the generator wrote it to.
297
+ const home = process.env.USERPROFILE ?? process.env.HOME ?? os.homedir();
298
+ const fromHome = path.relative(home, absolute);
299
+ if (fromHome && !fromHome.startsWith('..') && !path.isAbsolute(fromHome)) {
300
+ return `~/${FileSystemUtils.toPosixPath(fromHome)}`;
301
+ }
302
+ return FileSystemUtils.toPosixPath(absolute);
303
+ }
304
+ }
305
+ /** Marks a path the plan has moved a whole directory to. */
306
+ const STAGED_DIRECTORY = Symbol('staged directory');
307
+ /**
308
+ * A path as it is on disk now: a file's content (read through a symlink that
309
+ * points at a file), a symlink to anything else as an entry of its own, or
310
+ * undefined when absent or a directory.
311
+ *
312
+ * The file is judged on the handle it is read from, so nothing can swap it
313
+ * between the check and the read. O_NONBLOCK keeps a FIFO from hanging the
314
+ * plan; Windows has no such flag and no FIFOs to worry about.
315
+ */
316
+ function originalEntry(target) {
317
+ let descriptor;
318
+ try {
319
+ descriptor = fs.openSync(target, fs.constants.O_RDONLY | (fs.constants.O_NONBLOCK ?? 0));
320
+ if (fs.fstatSync(descriptor).isFile()) {
321
+ return fs.readFileSync(descriptor, 'utf-8');
322
+ }
323
+ }
324
+ catch {
325
+ // Missing, a dangling link, or a directory on a platform that refuses to
326
+ // open one: decided below.
327
+ }
328
+ finally {
329
+ if (descriptor !== undefined)
330
+ fs.closeSync(descriptor);
331
+ }
332
+ try {
333
+ return `symlink:${fs.readlinkSync(target)}`;
334
+ }
335
+ catch {
336
+ return undefined;
337
+ }
338
+ }
339
+ /** An error shaped like the one Node's own fs call would throw. */
340
+ function errnoError(code, description, syscall, target) {
341
+ return Object.assign(new Error(`${code}: ${description}, ${syscall} '${target}'`), {
342
+ code,
343
+ syscall,
344
+ path: target,
345
+ });
346
+ }
347
+ function listFilesRecursively(dirPath) {
348
+ let entries;
349
+ try {
350
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
351
+ }
352
+ catch {
353
+ return [];
354
+ }
355
+ const files = [];
356
+ for (const entry of entries) {
357
+ const full = path.join(dirPath, entry.name);
358
+ if (entry.isDirectory()) {
359
+ files.push(...listFilesRecursively(full));
360
+ }
361
+ else {
362
+ files.push(full);
363
+ }
364
+ }
365
+ return files;
366
+ }
367
+ /** Human-readable rendering of a dry-run plan. */
368
+ export function formatUpdatePlan(plan) {
369
+ const lines = [];
370
+ if (plan.changes.length === 0) {
371
+ lines.push('No changes: skills and commands are up to date.');
372
+ }
373
+ else {
374
+ lines.push(`${plan.changes.length} file(s) would change:`);
375
+ const width = Math.max(...plan.changes.map((change) => change.action.length));
376
+ for (const change of plan.changes) {
377
+ lines.push(` ${change.action.padEnd(width)} ${change.path} (${change.tool})`);
378
+ }
379
+ }
380
+ for (const note of plan.notes) {
381
+ lines.push('');
382
+ lines.push(`Note: ${note.message}`);
383
+ }
384
+ for (const failure of plan.failures) {
385
+ lines.push('');
386
+ lines.push(`Would fail: ${failure.tool} (${failure.error})`);
387
+ }
388
+ return lines.join('\n');
389
+ }
390
+ //# sourceMappingURL=update-plan.js.map
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Update Command
3
+ *
4
+ * Refreshes FreeSpec skills and commands for configured tools.
5
+ * Supports profile-aware updates, delivery changes, migration, and smart update detection.
6
+ */
7
+ import { type FileView } from './file-view.js';
8
+ import { type UpdatePlan } from './update-plan.js';
9
+ /**
10
+ * Options for the update command.
11
+ */
12
+ export interface UpdateCommandOptions {
13
+ /** Force update even when tools are up to date */
14
+ force?: boolean;
15
+ /**
16
+ * Record what the update would change and write nothing. Set by `plan()`;
17
+ * callers use that instead of passing this directly.
18
+ */
19
+ dryRun?: boolean;
20
+ }
21
+ /**
22
+ * Scans installed workflow artifacts (skills and managed commands) across all configured tools.
23
+ * Returns the union of detected workflow IDs that match ALL_WORKFLOWS.
24
+ *
25
+ * Wrapper around the shared migration module's scanInstalledWorkflows that accepts tool IDs.
26
+ */
27
+ export declare function scanInstalledWorkflows(projectPath: string, toolIds: string[], view?: FileView): string[];
28
+ export declare class UpdateCommand {
29
+ private readonly force;
30
+ private readonly dryRun;
31
+ private recorder;
32
+ private ops;
33
+ constructor(options?: UpdateCommandOptions);
34
+ /**
35
+ * Reports what `execute` would add, change or delete, writing nothing.
36
+ *
37
+ * Same code path as the real update: the plan is recorded by the write seam
38
+ * the generation loop already goes through, so it cannot drift from it.
39
+ */
40
+ plan(projectPath: string): Promise<UpdatePlan>;
41
+ /** Prints progress on a real run, stays quiet while planning. */
42
+ private log;
43
+ /** A spinner on a real run, a no-op while planning. */
44
+ private startSpinner;
45
+ /**
46
+ * Refreshes FreeSpec skills and commands for all configured tools,
47
+ * regenerating artifacts according to the effective profile and delivery mode.
48
+ *
49
+ * @param projectPath - Path to the project root containing the freespec directory
50
+ */
51
+ execute(projectPath: string): Promise<void>;
52
+ /**
53
+ * Records the shared-skills-root owner through the write seam, so a dry run
54
+ * lists the marker file a real update would write.
55
+ */
56
+ private writeSharedSkillTarget;
57
+ /**
58
+ * Where a real update throws for failed tools, it also stops. A dry run
59
+ * stops at the same point - so it plans nothing the real update would not
60
+ * reach - but returns instead of throwing: the failures are already in the
61
+ * plan, and the caller still gets every change planned before them.
62
+ *
63
+ * @returns whether the update stops here.
64
+ */
65
+ private stopIfUpdateFailed;
66
+ /** The tree as the real update would see it at this point. */
67
+ private get view();
68
+ /**
69
+ * Plans the moves out of former tool roots that a non-interactive update
70
+ * makes, against the tree as planned so far.
71
+ */
72
+ private planLegacyToolMoves;
73
+ /**
74
+ * Tools whose SKILL.md bodies no longer match what this CLI generates.
75
+ *
76
+ * Skill freshness was decided solely by the `generatedBy:` line, so a body
77
+ * edited after generation — a "helpful" PR touching `.claude/skills/**`, a
78
+ * dotfile sync, another agent — left `update` reporting the install healthy.
79
+ * Command files never had that gap: `areCommandFilesUpToDate` content-
80
+ * compares them, and the same comparison belongs on the higher-authority
81
+ * surface. A missing skill file is left to the profile-sync check, which
82
+ * already knows what a partial install means.
83
+ */
84
+ private findToolsWithDriftedSkills;
85
+ /**
86
+ * Display message when all tools are up to date.
87
+ */
88
+ private displayUpToDateMessage;
89
+ /**
90
+ * Display the update plan showing which tools need updating.
91
+ */
92
+ private displayUpdatePlan;
93
+ /**
94
+ * Shows manual setup notes for configured tools that need extra
95
+ * configuration before they pick up generated files.
96
+ */
97
+ private displaySetupNotes;
98
+ /**
99
+ * Detects new tool directories that aren't currently configured and displays a hint.
100
+ */
101
+ private detectNewTools;
102
+ /**
103
+ * Prints the profile notes, in order, with one pointer at
104
+ * `freespec config profile` rather than three.
105
+ *
106
+ * Every note is evaluated: reading them as one short-circuited `||` chain
107
+ * would swallow whichever ran second.
108
+ */
109
+ private displayProfileNotes;
110
+ /**
111
+ * Displays a note about extra workflows installed that aren't in the current profile.
112
+ */
113
+ private displayExtraWorkflowsNote;
114
+ /**
115
+ * Point out core workflows a custom profile is missing, so releases that
116
+ * grow CORE_WORKFLOWS stay discoverable. Keep custom profiles user-owned;
117
+ * do not mutate them.
118
+ */
119
+ private displayMissingCoreWorkflowsNote;
120
+ /**
121
+ * Removes skill directories for workflows when delivery changed to commands-only.
122
+ * Returns the number of directories removed.
123
+ */
124
+ private removeSkillDirs;
125
+ /**
126
+ * Removes skill directories for workflows that are no longer selected in the active profile.
127
+ * Returns the number of directories removed.
128
+ */
129
+ private removeUnselectedSkillDirs;
130
+ /**
131
+ * Removes command files for workflows when delivery changed to skills-only.
132
+ * Returns the number of files removed.
133
+ */
134
+ private removeCommandFiles;
135
+ /**
136
+ * Removes command files for workflows that are no longer selected in the active profile.
137
+ * Returns the number of files removed.
138
+ */
139
+ private removeUnselectedCommandFiles;
140
+ /**
141
+ * Offers to move FreeSpec content out of a renamed tool's former directory
142
+ * when the old location might still be the live one — today, Windsurf's
143
+ * `.windsurf/` after the Devin Desktop rebrand.
144
+ *
145
+ * Interactive runs are asked, because nothing on disk distinguishes a user
146
+ * who took the rebrand from one still on a pre-rebrand Windsurf build that
147
+ * reads only `.windsurf/`. `--force` and non-interactive runs migrate, which
148
+ * is what an unattended upgrade wants.
149
+ */
150
+ /** Surfaces files the move left behind rather than overwriting. */
151
+ private reportKeptInPlace;
152
+ private offerConsentedLegacyMigrations;
153
+ }
154
+ //# sourceMappingURL=update.d.ts.map