@codewalla_india/openspec 1.3.0 → 1.3.2

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 (333) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +272 -230
  3. package/bin/openspec.js +5 -5
  4. package/dist/cli/index.js +99 -62
  5. package/dist/commands/change.d.ts +0 -2
  6. package/dist/commands/change.js +107 -86
  7. package/dist/commands/config.js +14 -16
  8. package/dist/commands/doctor.js +9 -4
  9. package/dist/commands/feedback.js +79 -30
  10. package/dist/commands/modify.d.ts +26 -0
  11. package/dist/commands/modify.js +147 -0
  12. package/dist/commands/schema.js +170 -91
  13. package/dist/commands/show.js +5 -2
  14. package/dist/commands/spec.js +49 -29
  15. package/dist/commands/store.js +15 -21
  16. package/dist/commands/validate.d.ts +8 -0
  17. package/dist/commands/validate.js +43 -20
  18. package/dist/commands/workflow/index.d.ts +2 -2
  19. package/dist/commands/workflow/index.js +1 -1
  20. package/dist/commands/workflow/instructions.d.ts +6 -26
  21. package/dist/commands/workflow/instructions.js +183 -376
  22. package/dist/commands/workflow/new-change.d.ts +0 -4
  23. package/dist/commands/workflow/new-change.js +10 -28
  24. package/dist/commands/workflow/shared.d.ts +34 -19
  25. package/dist/commands/workflow/shared.js +34 -4
  26. package/dist/commands/workflow/status.js +10 -32
  27. package/dist/commands/workflow/templates.js +15 -5
  28. package/dist/commands/workset.d.ts +2 -2
  29. package/dist/commands/workset.js +19 -22
  30. package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
  31. package/dist/comprehension-quiz/fingerprint.js +84 -0
  32. package/dist/comprehension-quiz/index.d.ts +42 -0
  33. package/dist/comprehension-quiz/index.js +47 -0
  34. package/dist/comprehension-quiz/pass-record.d.ts +44 -0
  35. package/dist/comprehension-quiz/pass-record.js +98 -0
  36. package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
  37. package/dist/comprehension-quiz/providers/anthropic.js +53 -0
  38. package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
  39. package/dist/comprehension-quiz/providers/factory.js +174 -0
  40. package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
  41. package/dist/comprehension-quiz/providers/interface.js +63 -0
  42. package/dist/comprehension-quiz/providers/local.d.ts +34 -0
  43. package/dist/comprehension-quiz/providers/local.js +53 -0
  44. package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
  45. package/dist/comprehension-quiz/providers/ollama.js +53 -0
  46. package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
  47. package/dist/comprehension-quiz/providers/openai.js +53 -0
  48. package/dist/comprehension-quiz/question-generator.d.ts +69 -0
  49. package/dist/comprehension-quiz/question-generator.js +141 -0
  50. package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
  51. package/dist/comprehension-quiz/quiz-executor.js +165 -0
  52. package/dist/comprehension-quiz/types.d.ts +115 -0
  53. package/dist/comprehension-quiz/types.js +11 -0
  54. package/dist/core/archive.d.ts +14 -1
  55. package/dist/core/archive.js +1350 -190
  56. package/dist/core/artifact-graph/graph.d.ts +28 -9
  57. package/dist/core/artifact-graph/graph.js +94 -43
  58. package/dist/core/artifact-graph/index.d.ts +1 -1
  59. package/dist/core/artifact-graph/index.js +1 -1
  60. package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
  61. package/dist/core/artifact-graph/instruction-loader.js +70 -12
  62. package/dist/core/artifact-graph/outputs.d.ts +1 -0
  63. package/dist/core/artifact-graph/outputs.js +67 -5
  64. package/dist/core/artifact-graph/resolver.d.ts +14 -0
  65. package/dist/core/artifact-graph/resolver.js +69 -15
  66. package/dist/core/artifact-graph/types.js +21 -3
  67. package/dist/core/available-tools.d.ts +2 -2
  68. package/dist/core/available-tools.js +14 -3
  69. package/dist/core/change-metadata/schema.d.ts +2 -0
  70. package/dist/core/change-metadata/schema.js +13 -0
  71. package/dist/core/change-status-policy.d.ts +10 -1
  72. package/dist/core/change-status-policy.js +30 -2
  73. package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
  74. package/dist/core/command-generation/adapters/amazon-q.js +11 -5
  75. package/dist/core/command-generation/adapters/antigravity.js +6 -5
  76. package/dist/core/command-generation/adapters/auggie.js +7 -6
  77. package/dist/core/command-generation/adapters/bob.d.ts +5 -1
  78. package/dist/core/command-generation/adapters/bob.js +11 -10
  79. package/dist/core/command-generation/adapters/claude.d.ts +1 -1
  80. package/dist/core/command-generation/adapters/claude.js +12 -17
  81. package/dist/core/command-generation/adapters/cline.js +5 -5
  82. package/dist/core/command-generation/adapters/codebuddy.js +8 -7
  83. package/dist/core/command-generation/adapters/continue.js +8 -7
  84. package/dist/core/command-generation/adapters/costrict.js +7 -6
  85. package/dist/core/command-generation/adapters/crush.js +9 -9
  86. package/dist/core/command-generation/adapters/cursor.js +8 -8
  87. package/dist/core/command-generation/adapters/devin.d.ts +19 -0
  88. package/dist/core/command-generation/adapters/devin.js +36 -0
  89. package/dist/core/command-generation/adapters/factory.js +7 -6
  90. package/dist/core/command-generation/adapters/gemini.js +40 -5
  91. package/dist/core/command-generation/adapters/github-copilot.js +6 -5
  92. package/dist/core/command-generation/adapters/iflow.js +9 -8
  93. package/dist/core/command-generation/adapters/index.d.ts +4 -2
  94. package/dist/core/command-generation/adapters/index.js +4 -2
  95. package/dist/core/command-generation/adapters/junie.js +6 -5
  96. package/dist/core/command-generation/adapters/kilocode.js +1 -1
  97. package/dist/core/command-generation/adapters/kiro.js +6 -5
  98. package/dist/core/command-generation/adapters/lingma.js +9 -9
  99. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  100. package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
  101. package/dist/core/command-generation/adapters/opencode.js +6 -8
  102. package/dist/core/command-generation/adapters/pi.d.ts +2 -2
  103. package/dist/core/command-generation/adapters/pi.js +7 -10
  104. package/dist/core/command-generation/adapters/qoder.js +9 -9
  105. package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
  106. package/dist/core/command-generation/adapters/qwen.js +14 -9
  107. package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
  108. package/dist/core/command-generation/adapters/roocode.js +9 -9
  109. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  110. package/dist/core/command-generation/adapters/trae.js +28 -0
  111. package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
  112. package/dist/core/command-generation/adapters/zcode.js +33 -0
  113. package/dist/core/command-generation/generator.d.ts +8 -0
  114. package/dist/core/command-generation/generator.js +15 -1
  115. package/dist/core/command-generation/index.d.ts +1 -1
  116. package/dist/core/command-generation/index.js +1 -1
  117. package/dist/core/command-generation/invocation.d.ts +73 -0
  118. package/dist/core/command-generation/invocation.js +77 -0
  119. package/dist/core/command-generation/registry.js +8 -4
  120. package/dist/core/command-generation/types.d.ts +9 -2
  121. package/dist/core/command-generation/yaml.d.ts +20 -6
  122. package/dist/core/command-generation/yaml.js +31 -21
  123. package/dist/core/command-surface.d.ts +15 -0
  124. package/dist/core/command-surface.js +33 -0
  125. package/dist/core/completions/command-registry.js +16 -61
  126. package/dist/core/completions/generators/bash-generator.js +41 -41
  127. package/dist/core/completions/generators/fish-generator.js +7 -7
  128. package/dist/core/completions/generators/powershell-generator.js +44 -37
  129. package/dist/core/completions/generators/zsh-generator.js +33 -33
  130. package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
  131. package/dist/core/completions/installers/zsh-installer.js +20 -4
  132. package/dist/core/completions/templates/bash-templates.js +24 -24
  133. package/dist/core/completions/templates/fish-templates.js +38 -38
  134. package/dist/core/completions/templates/powershell-templates.js +28 -28
  135. package/dist/core/completions/templates/zsh-templates.js +39 -39
  136. package/dist/core/config-prompts.d.ts +1 -1
  137. package/dist/core/config-prompts.js +14 -1
  138. package/dist/core/config-schema.d.ts +10 -1
  139. package/dist/core/config-schema.js +67 -1
  140. package/dist/core/config.d.ts +15 -0
  141. package/dist/core/config.js +46 -5
  142. package/dist/core/file-state.js +56 -24
  143. package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
  144. package/dist/core/github-copilot/cloud-agent.js +497 -0
  145. package/dist/core/global-config.d.ts +16 -0
  146. package/dist/core/init.d.ts +58 -0
  147. package/dist/core/init.js +474 -73
  148. package/dist/core/legacy-cleanup.d.ts +59 -0
  149. package/dist/core/legacy-cleanup.js +253 -24
  150. package/dist/core/list.js +23 -16
  151. package/dist/core/migration.d.ts +73 -1
  152. package/dist/core/migration.js +360 -11
  153. package/dist/core/modify/conflict-detector.d.ts +26 -0
  154. package/dist/core/modify/conflict-detector.js +62 -0
  155. package/dist/core/modify/dependency-propagator.d.ts +38 -0
  156. package/dist/core/modify/dependency-propagator.js +103 -0
  157. package/dist/core/modify/history-tracker.d.ts +34 -0
  158. package/dist/core/modify/history-tracker.js +114 -0
  159. package/dist/core/modify/index.d.ts +7 -0
  160. package/dist/core/modify/index.js +7 -0
  161. package/dist/core/onboarding-commands.d.ts +30 -0
  162. package/dist/core/onboarding-commands.js +38 -0
  163. package/dist/core/openspec-root.js +18 -15
  164. package/dist/core/parsers/change-parser.d.ts +17 -2
  165. package/dist/core/parsers/change-parser.js +35 -21
  166. package/dist/core/parsers/code-fence.d.ts +15 -0
  167. package/dist/core/parsers/code-fence.js +49 -0
  168. package/dist/core/parsers/markdown-parser.d.ts +0 -3
  169. package/dist/core/parsers/markdown-parser.js +7 -60
  170. package/dist/core/parsers/requirement-blocks.d.ts +29 -0
  171. package/dist/core/parsers/requirement-blocks.js +150 -31
  172. package/dist/core/parsers/requirement-text.d.ts +48 -0
  173. package/dist/core/parsers/requirement-text.js +98 -0
  174. package/dist/core/parsers/spec-structure.d.ts +1 -1
  175. package/dist/core/parsers/spec-structure.js +20 -30
  176. package/dist/core/profile-sync-drift.d.ts +0 -8
  177. package/dist/core/profile-sync-drift.js +49 -50
  178. package/dist/core/profiles.d.ts +2 -2
  179. package/dist/core/profiles.js +2 -2
  180. package/dist/core/project-config.d.ts +27 -18
  181. package/dist/core/project-config.js +101 -34
  182. package/dist/core/references.js +42 -4
  183. package/dist/core/relationship-health.d.ts +8 -0
  184. package/dist/core/relationship-health.js +13 -0
  185. package/dist/core/root-selection.d.ts +9 -6
  186. package/dist/core/root-selection.js +42 -5
  187. package/dist/core/schemas/base.schema.js +7 -2
  188. package/dist/core/shared/allowed-tools.d.ts +12 -0
  189. package/dist/core/shared/allowed-tools.js +12 -0
  190. package/dist/core/shared/index.d.ts +1 -0
  191. package/dist/core/shared/index.js +1 -0
  192. package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
  193. package/dist/core/shared/skill-content-equivalence.js +41 -0
  194. package/dist/core/shared/skill-generation.js +17 -13
  195. package/dist/core/shared/skill-paths.d.ts +13 -0
  196. package/dist/core/shared/skill-paths.js +23 -0
  197. package/dist/core/shared/tool-detection.d.ts +27 -7
  198. package/dist/core/shared/tool-detection.js +167 -37
  199. package/dist/core/shared-skill-target.d.ts +16 -0
  200. package/dist/core/shared-skill-target.js +156 -0
  201. package/dist/core/specs-apply.d.ts +80 -33
  202. package/dist/core/specs-apply.js +667 -145
  203. package/dist/core/store/git.d.ts +13 -0
  204. package/dist/core/store/git.js +24 -0
  205. package/dist/core/store/operations.js +20 -0
  206. package/dist/core/templates/skill-templates.d.ts +3 -2
  207. package/dist/core/templates/skill-templates.js +3 -2
  208. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  209. package/dist/core/templates/workflows/apply-change.js +186 -323
  210. package/dist/core/templates/workflows/archive-change.js +387 -260
  211. package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
  212. package/dist/core/templates/workflows/continue-change.js +210 -219
  213. package/dist/core/templates/workflows/explore.js +479 -443
  214. package/dist/core/templates/workflows/feedback.js +97 -97
  215. package/dist/core/templates/workflows/ff-change.js +200 -191
  216. package/dist/core/templates/workflows/modify-change.d.ts +4 -1
  217. package/dist/core/templates/workflows/modify-change.js +237 -115
  218. package/dist/core/templates/workflows/new-change.js +127 -134
  219. package/dist/core/templates/workflows/onboard.js +548 -542
  220. package/dist/core/templates/workflows/propose.js +273 -217
  221. package/dist/core/templates/workflows/store-selection.d.ts +1 -1
  222. package/dist/core/templates/workflows/store-selection.js +1 -1
  223. package/dist/core/templates/workflows/sync-specs.js +500 -273
  224. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  225. package/dist/core/templates/workflows/update-change.js +177 -0
  226. package/dist/core/templates/workflows/verify-change.js +324 -326
  227. package/dist/core/update.d.ts +40 -5
  228. package/dist/core/update.js +411 -96
  229. package/dist/core/validation/constants.d.ts +4 -1
  230. package/dist/core/validation/constants.js +4 -1
  231. package/dist/core/validation/plan-validator.d.ts +8 -0
  232. package/dist/core/validation/plan-validator.js +45 -0
  233. package/dist/core/validation/task-numbering.d.ts +16 -0
  234. package/dist/core/validation/task-numbering.js +57 -0
  235. package/dist/core/validation/validator.d.ts +27 -3
  236. package/dist/core/validation/validator.js +337 -48
  237. package/dist/core/version-check.d.ts +152 -0
  238. package/dist/core/version-check.js +731 -0
  239. package/dist/core/view.js +13 -18
  240. package/dist/prompts/searchable-multi-select.js +1 -1
  241. package/dist/telemetry/caller-detection.d.ts +27 -0
  242. package/dist/telemetry/caller-detection.js +85 -0
  243. package/dist/telemetry/caller-detection.test.d.ts +5 -0
  244. package/dist/telemetry/config.d.ts +48 -2
  245. package/dist/telemetry/config.js +113 -11
  246. package/dist/telemetry/config.test.d.ts +5 -0
  247. package/dist/telemetry/deduplication.d.ts +35 -0
  248. package/dist/telemetry/deduplication.js +82 -0
  249. package/dist/telemetry/deduplication.test.d.ts +5 -0
  250. package/dist/telemetry/identity.d.ts +62 -22
  251. package/dist/telemetry/identity.js +147 -102
  252. package/dist/telemetry/index.d.ts +126 -15
  253. package/dist/telemetry/index.js +569 -37
  254. package/dist/telemetry/sanitization.d.ts +40 -0
  255. package/dist/telemetry/sanitization.js +114 -0
  256. package/dist/telemetry/sanitization.test.d.ts +5 -0
  257. package/dist/telemetry/throttling.d.ts +20 -0
  258. package/dist/telemetry/throttling.js +70 -0
  259. package/dist/ui/welcome-screen.d.ts +9 -1
  260. package/dist/ui/welcome-screen.js +91 -38
  261. package/dist/utils/change-metadata.d.ts +43 -0
  262. package/dist/utils/change-metadata.js +123 -7
  263. package/dist/utils/change-utils.d.ts +10 -4
  264. package/dist/utils/change-utils.js +20 -13
  265. package/dist/utils/ci.d.ts +12 -0
  266. package/dist/utils/ci.js +16 -0
  267. package/dist/utils/command-references.d.ts +92 -6
  268. package/dist/utils/command-references.js +180 -7
  269. package/dist/utils/date.d.ts +8 -0
  270. package/dist/utils/date.js +13 -0
  271. package/dist/utils/file-system.d.ts +10 -0
  272. package/dist/utils/file-system.js +70 -0
  273. package/dist/utils/index.d.ts +1 -1
  274. package/dist/utils/index.js +1 -1
  275. package/dist/utils/interactive.d.ts +23 -0
  276. package/dist/utils/interactive.js +32 -0
  277. package/dist/utils/item-discovery.d.ts +19 -0
  278. package/dist/utils/item-discovery.js +30 -48
  279. package/dist/utils/shell-detection.d.ts +1 -1
  280. package/dist/utils/shell-detection.js +64 -11
  281. package/dist/utils/spec-discovery.d.ts +40 -0
  282. package/dist/utils/spec-discovery.js +115 -0
  283. package/dist/utils/task-progress.d.ts +29 -1
  284. package/dist/utils/task-progress.js +109 -15
  285. package/package.json +96 -84
  286. package/schemas/spec-driven/schema.yaml +252 -184
  287. package/schemas/spec-driven/templates/design.md +19 -19
  288. package/schemas/spec-driven/templates/plan-template.md +91 -0
  289. package/schemas/spec-driven/templates/proposal.md +29 -24
  290. package/schemas/spec-driven/templates/spec.md +11 -8
  291. package/schemas/spec-driven/templates/tasks.md +9 -9
  292. package/scripts/postinstall.js +83 -83
  293. package/dist/core/command-generation/adapters/codex.d.ts +0 -16
  294. package/dist/core/command-generation/adapters/codex.js +0 -39
  295. package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
  296. package/dist/core/command-generation/adapters/windsurf.js +0 -38
  297. package/dist/core/comprehension/config.d.ts +0 -20
  298. package/dist/core/comprehension/config.js +0 -23
  299. package/dist/core/comprehension/fingerprint.d.ts +0 -14
  300. package/dist/core/comprehension/fingerprint.js +0 -33
  301. package/dist/core/comprehension/index.d.ts +0 -57
  302. package/dist/core/comprehension/index.js +0 -87
  303. package/dist/core/comprehension/pass-record.d.ts +0 -29
  304. package/dist/core/comprehension/pass-record.js +0 -64
  305. package/dist/core/comprehension/stats.d.ts +0 -35
  306. package/dist/core/comprehension/stats.js +0 -133
  307. package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
  308. package/dist/core/templates/workflows/comprehension-guidance.js +0 -71
  309. package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
  310. package/dist/core/templates/workflows/mcp-guidance.js +0 -131
  311. package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
  312. package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
  313. package/dist/telemetry/caller.d.ts +0 -5
  314. package/dist/telemetry/caller.js +0 -29
  315. package/dist/telemetry/client.d.ts +0 -27
  316. package/dist/telemetry/client.js +0 -127
  317. package/dist/telemetry/command-context.d.ts +0 -13
  318. package/dist/telemetry/command-context.js +0 -59
  319. package/dist/telemetry/comprehension.d.ts +0 -44
  320. package/dist/telemetry/comprehension.js +0 -105
  321. package/dist/telemetry/content.d.ts +0 -10
  322. package/dist/telemetry/content.js +0 -56
  323. package/dist/telemetry/git-stats.d.ts +0 -12
  324. package/dist/telemetry/git-stats.js +0 -69
  325. package/dist/telemetry/identify-cache.d.ts +0 -7
  326. package/dist/telemetry/identify-cache.js +0 -47
  327. package/dist/telemetry/input.d.ts +0 -17
  328. package/dist/telemetry/input.js +0 -68
  329. package/dist/telemetry/marker.d.ts +0 -37
  330. package/dist/telemetry/marker.js +0 -67
  331. package/dist/telemetry/workflow.d.ts +0 -85
  332. package/dist/telemetry/workflow.js +0 -318
  333. package/schemas/spec-driven/templates/plan.md +0 -19
@@ -2,6 +2,7 @@
2
2
  * Legacy cleanup module for detecting and removing OpenSpec artifacts
3
3
  * from previous init versions during the migration to the skill-based workflow.
4
4
  */
5
+ import type { WorkflowId } from './profiles.js';
5
6
  /**
6
7
  * Legacy config file names from the old ToolRegistry.
7
8
  * These were config files created at project root with OpenSpec markers.
@@ -13,6 +14,11 @@ export declare const LEGACY_CONFIG_FILES: readonly ["CLAUDE.md", "CLINE.md", "CO
13
14
  * Some tools used a directory structure, others used individual files.
14
15
  */
15
16
  export declare const LEGACY_SLASH_COMMAND_PATHS: Record<string, LegacySlashCommandPattern>;
17
+ /**
18
+ * Global legacy prompt locations that live outside the project tree and require
19
+ * allowlisted matching instead of broad glob-based cleanup.
20
+ */
21
+ export declare const LEGACY_GLOBAL_SLASH_COMMAND_PATHS: Record<string, LegacyGlobalPromptPattern>;
16
22
  /**
17
23
  * Pattern types for legacy slash commands
18
24
  */
@@ -21,6 +27,28 @@ export interface LegacySlashCommandPattern {
21
27
  path?: string;
22
28
  pattern?: string | string[];
23
29
  }
30
+ /**
31
+ * Describes a managed global prompt home and the exact filenames OpenSpec is
32
+ * allowed to treat as legacy artifacts there.
33
+ */
34
+ export interface LegacyGlobalPromptPattern {
35
+ managedFileNames: readonly string[];
36
+ workflowIdsByFileName?: Readonly<Record<string, readonly WorkflowId[]>>;
37
+ resolvePromptDir: () => string;
38
+ replacementLabel?: string;
39
+ }
40
+ /**
41
+ * Workflow-aware metadata for a detected global legacy prompt that is safe for
42
+ * replacement-gated cleanup.
43
+ */
44
+ export interface LegacyGlobalPromptMatch {
45
+ path: string;
46
+ toolId: string;
47
+ managedFileName: string;
48
+ workflowIds: readonly WorkflowId[];
49
+ replacementLabel?: string;
50
+ }
51
+ export declare function getCodexPromptDir(): string;
24
52
  /**
25
53
  * Result of legacy artifact detection
26
54
  */
@@ -33,6 +61,10 @@ export interface LegacyDetectionResult {
33
61
  slashCommandDirs: string[];
34
62
  /** Legacy slash command files found (for file-based tools) */
35
63
  slashCommandFiles: string[];
64
+ /** Managed global command/prompt files found outside the project root */
65
+ globalSlashCommandFiles: string[];
66
+ /** Details for managed global command/prompt files */
67
+ globalSlashCommandDetails?: LegacyGlobalPromptMatch[];
36
68
  /** Whether openspec/AGENTS.md exists */
37
69
  hasOpenspecAgents: boolean;
38
70
  /** Whether openspec/project.md exists (preserved, migration hint only) */
@@ -111,6 +143,8 @@ export declare function removeMarkerBlock(content: string): string;
111
143
  export interface CleanupResult {
112
144
  /** Files that were deleted entirely */
113
145
  deletedFiles: string[];
146
+ /** Replacement labels for deleted files when cleanup knows the new surface */
147
+ deletedFileReplacementLabels?: Record<string, string>;
114
148
  /** Files that had marker blocks removed */
115
149
  modifiedFiles: string[];
116
150
  /** Directories that were deleted */
@@ -144,6 +178,11 @@ export declare function formatCleanupSummary(result: CleanupResult): string;
144
178
  * @returns Formatted summary string showing what was found
145
179
  */
146
180
  export declare function formatDetectionSummary(detection: LegacyDetectionResult): string;
181
+ /**
182
+ * Generates a summary for managed global prompt files whose cleanup must wait
183
+ * until replacement skills are installed.
184
+ */
185
+ export declare function formatDeferredGlobalPromptSummary(detection: LegacyDetectionResult): string;
147
186
  /**
148
187
  * Extract tool IDs from detected legacy artifacts.
149
188
  * Uses LEGACY_SLASH_COMMAND_PATHS to map paths back to tool IDs.
@@ -152,6 +191,26 @@ export declare function formatDetectionSummary(detection: LegacyDetectionResult)
152
191
  * @returns Array of tool IDs that had legacy artifacts
153
192
  */
154
193
  export declare function getToolsFromLegacyArtifacts(detection: LegacyDetectionResult): string[];
194
+ /**
195
+ * Normalizes global Codex prompt matches so callers can rely on workflow-aware
196
+ * metadata even when older detection results only carry file paths.
197
+ */
198
+ export declare function getLegacyGlobalPromptMatches(detection: LegacyDetectionResult): LegacyGlobalPromptMatch[];
199
+ /**
200
+ * Collects workflow IDs inferred from detected legacy global prompts for a
201
+ * specific tool.
202
+ */
203
+ export declare function getLegacyWorkflowIdsForTool(detection: LegacyDetectionResult, toolId: string): WorkflowId[];
204
+ /**
205
+ * Returns a detection snapshot with global Codex prompt cleanup removed so
206
+ * callers can safely perform the immediate, non-deferred cleanup pass.
207
+ */
208
+ export declare function omitGlobalLegacyPromptFiles(detection: LegacyDetectionResult): LegacyDetectionResult;
209
+ /**
210
+ * Builds a detection snapshot containing only the selected global Codex prompt
211
+ * matches for replacement-gated cleanup.
212
+ */
213
+ export declare function pickGlobalLegacyPromptFiles(detection: LegacyDetectionResult, filePaths: readonly string[]): LegacyDetectionResult;
155
214
  /**
156
215
  * Generates a migration hint message for project.md.
157
216
  * This is shown when project.md exists and needs manual migration to config.yaml.
@@ -2,6 +2,8 @@
2
2
  * Legacy cleanup module for detecting and removing OpenSpec artifacts
3
3
  * from previous init versions during the migration to the skill-based workflow.
4
4
  */
5
+ import path from 'path';
6
+ import os from 'os';
5
7
  import { promises as fs } from 'fs';
6
8
  import chalk from 'chalk';
7
9
  import { FileSystemUtils, removeMarkerBlock as removeMarkerBlockUtil } from '../utils/file-system.js';
@@ -33,10 +35,12 @@ export const LEGACY_SLASH_COMMAND_PATHS = {
33
35
  'lingma': { type: 'directory', path: '.lingma/commands/openspec' },
34
36
  'crush': { type: 'directory', path: '.crush/commands/openspec' },
35
37
  'gemini': { type: 'directory', path: '.gemini/commands/openspec' },
36
- 'costrict': { type: 'directory', path: '.cospec/openspec/commands' },
37
38
  // File-based: individual openspec-*.md files in a commands/workflows/prompts folder
38
39
  'cursor': { type: 'files', pattern: '.cursor/commands/openspec-*.md' },
39
- 'windsurf': { type: 'files', pattern: '.windsurf/workflows/openspec-*.md' },
40
+ // Keyed by the tool id these map back to, so the pre-opsx Windsurf files
41
+ // belong to `devin` — the id Windsurf became. Only `.windsurf/` is listed:
42
+ // `.devin/` postdates the opsx rename and never held `openspec-*` files.
43
+ 'devin': { type: 'files', pattern: '.windsurf/workflows/openspec-*.md' },
40
44
  'kilocode': { type: 'files', pattern: '.kilocode/workflows/openspec-*.md' },
41
45
  'kiro': { type: 'files', pattern: '.kiro/prompts/openspec-*.prompt.md' },
42
46
  'github-copilot': { type: 'files', pattern: '.github/prompts/openspec-*.prompt.md' },
@@ -49,10 +53,87 @@ export const LEGACY_SLASH_COMMAND_PATHS = {
49
53
  'continue': { type: 'files', pattern: '.continue/prompts/openspec-*.prompt' },
50
54
  'antigravity': { type: 'files', pattern: '.agent/workflows/openspec-*.md' },
51
55
  'iflow': { type: 'files', pattern: '.iflow/commands/openspec-*.md' },
52
- 'junie': { type: 'files', pattern: ['.junie/commands/opsx-*.md', '.junie/commands/openspec-*.md'] },
53
- 'qwen': { type: 'files', pattern: '.qwen/commands/openspec-*.toml' },
56
+ 'qwen': { type: 'files', pattern: ['.qwen/commands/opsx-*.toml', '.qwen/commands/openspec-*.toml'] },
54
57
  'codex': { type: 'files', pattern: '.codex/prompts/openspec-*.md' },
58
+ // Keep this file-scoped: the CoStrict adapter writes `opsx-*.md` into the
59
+ // same folder, so a directory entry removes the live command files — and
60
+ // anything else the user keeps there — on every run.
61
+ 'costrict': { type: 'files', pattern: '.cospec/openspec/commands/openspec-*.md' },
55
62
  };
63
+ /**
64
+ * Final OpenSpec-managed global Codex prompt filenames mapped to the workflows
65
+ * they represented before Codex moved to skills-only delivery.
66
+ */
67
+ const LEGACY_GLOBAL_CODEX_WORKFLOWS = {
68
+ 'opsx-propose.md': ['propose'],
69
+ 'opsx-explore.md': ['explore'],
70
+ 'opsx-new.md': ['new'],
71
+ 'opsx-continue.md': ['continue'],
72
+ 'opsx-apply.md': ['apply'],
73
+ 'opsx-update.md': ['update'],
74
+ 'opsx-ff.md': ['ff'],
75
+ 'opsx-sync.md': ['sync'],
76
+ 'opsx-archive.md': ['archive'],
77
+ 'opsx-bulk-archive.md': ['bulk-archive'],
78
+ 'opsx-verify.md': ['verify'],
79
+ 'opsx-onboard.md': ['onboard'],
80
+ };
81
+ /**
82
+ * Global legacy prompt locations that live outside the project tree and require
83
+ * allowlisted matching instead of broad glob-based cleanup.
84
+ */
85
+ export const LEGACY_GLOBAL_SLASH_COMMAND_PATHS = {
86
+ 'codex': {
87
+ managedFileNames: Object.keys(LEGACY_GLOBAL_CODEX_WORKFLOWS),
88
+ workflowIdsByFileName: LEGACY_GLOBAL_CODEX_WORKFLOWS,
89
+ resolvePromptDir: getCodexPromptDir,
90
+ replacementLabel: 'Codex skills',
91
+ },
92
+ };
93
+ // Resolve the Codex global prompts directory, respecting CODEX_HOME if set.
94
+ export function getCodexPromptDir() {
95
+ const envHome = process.env.CODEX_HOME?.trim();
96
+ const codexHome = envHome ? envHome : path.join(os.homedir(), '.codex');
97
+ return path.join(path.resolve(codexHome), 'prompts');
98
+ }
99
+ // Convert a simple glob pattern (only * wildcards) into an anchored RegExp.
100
+ function globToRegex(pattern) {
101
+ const regexPattern = pattern
102
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
103
+ .replace(/\*/g, '.*');
104
+ return new RegExp(`^${regexPattern}$`);
105
+ }
106
+ // Normalize Windows backslashes to forward slashes for cross-platform path matching.
107
+ function normalizePathForMatch(filePath) {
108
+ return filePath.replace(/\\/g, '/');
109
+ }
110
+ /**
111
+ * Classifies a global Codex prompt path as OpenSpec-managed only when it matches
112
+ * the explicit legacy allowlist for the resolved prompt home.
113
+ */
114
+ function getManagedGlobalLegacyPromptMetadata(filePath) {
115
+ if (!path.isAbsolute(filePath)) {
116
+ return undefined;
117
+ }
118
+ const resolvedPath = path.resolve(filePath);
119
+ for (const [toolId, pattern] of Object.entries(LEGACY_GLOBAL_SLASH_COMMAND_PATHS)) {
120
+ const promptDir = path.resolve(pattern.resolvePromptDir());
121
+ if (path.dirname(resolvedPath) !== promptDir) {
122
+ continue;
123
+ }
124
+ const managedFileName = path.basename(resolvedPath);
125
+ if (pattern.managedFileNames.includes(managedFileName)) {
126
+ return {
127
+ path: resolvedPath,
128
+ toolId,
129
+ managedFileName,
130
+ workflowIds: pattern.workflowIdsByFileName?.[managedFileName] ?? [],
131
+ replacementLabel: pattern.replacementLabel,
132
+ };
133
+ }
134
+ }
135
+ return undefined;
136
+ }
56
137
  /**
57
138
  * Detects all legacy OpenSpec artifacts in a project.
58
139
  *
@@ -65,6 +146,8 @@ export async function detectLegacyArtifacts(projectPath) {
65
146
  configFilesToUpdate: [],
66
147
  slashCommandDirs: [],
67
148
  slashCommandFiles: [],
149
+ globalSlashCommandFiles: [],
150
+ globalSlashCommandDetails: [],
68
151
  hasOpenspecAgents: false,
69
152
  hasProjectMd: false,
70
153
  hasRootAgentsWithMarkers: false,
@@ -77,7 +160,10 @@ export async function detectLegacyArtifacts(projectPath) {
77
160
  // Detect legacy slash commands
78
161
  const slashResult = await detectLegacySlashCommands(projectPath);
79
162
  result.slashCommandDirs = slashResult.directories;
80
- result.slashCommandFiles = slashResult.files;
163
+ result.slashCommandFiles = [...new Set(slashResult.files)];
164
+ // Detect legacy global slash commands
165
+ result.globalSlashCommandDetails = await detectLegacyGlobalPromptFiles();
166
+ result.globalSlashCommandFiles = result.globalSlashCommandDetails.map((detail) => detail.path);
81
167
  // Detect legacy structure files
82
168
  const structureResult = await detectLegacyStructureFiles(projectPath);
83
169
  result.hasOpenspecAgents = structureResult.hasOpenspecAgents;
@@ -88,6 +174,7 @@ export async function detectLegacyArtifacts(projectPath) {
88
174
  result.configFiles.length > 0 ||
89
175
  result.slashCommandDirs.length > 0 ||
90
176
  result.slashCommandFiles.length > 0 ||
177
+ result.globalSlashCommandFiles.length > 0 ||
91
178
  result.hasOpenspecAgents ||
92
179
  result.hasRootAgentsWithMarkers ||
93
180
  result.hasProjectMd;
@@ -125,7 +212,7 @@ export async function detectLegacyConfigFiles(projectPath) {
125
212
  export async function detectLegacySlashCommands(projectPath) {
126
213
  const directories = [];
127
214
  const files = [];
128
- for (const [toolId, pattern] of Object.entries(LEGACY_SLASH_COMMAND_PATHS)) {
215
+ for (const pattern of Object.values(LEGACY_SLASH_COMMAND_PATHS)) {
129
216
  if (pattern.type === 'directory' && pattern.path) {
130
217
  const dirPath = FileSystemUtils.joinPath(projectPath, pattern.path);
131
218
  if (await FileSystemUtils.directoryExists(dirPath)) {
@@ -133,7 +220,6 @@ export async function detectLegacySlashCommands(projectPath) {
133
220
  }
134
221
  }
135
222
  else if (pattern.type === 'files' && pattern.pattern) {
136
- // For file-based patterns, check for individual files
137
223
  const patterns = Array.isArray(pattern.pattern) ? pattern.pattern : [pattern.pattern];
138
224
  for (const p of patterns) {
139
225
  const foundFiles = await findLegacySlashCommandFiles(projectPath, p);
@@ -143,6 +229,37 @@ export async function detectLegacySlashCommands(projectPath) {
143
229
  }
144
230
  return { directories, files };
145
231
  }
232
+ /**
233
+ * Detects legacy global slash command files.
234
+ *
235
+ * @returns Object with individual files found
236
+ */
237
+ /**
238
+ * Scans the resolved global Codex prompt directories and returns only the
239
+ * allowlisted OpenSpec-managed legacy prompt files.
240
+ */
241
+ async function detectLegacyGlobalPromptFiles() {
242
+ const foundFiles = [];
243
+ for (const pattern of Object.values(LEGACY_GLOBAL_SLASH_COMMAND_PATHS)) {
244
+ const promptDir = pattern.resolvePromptDir();
245
+ try {
246
+ const entries = await fs.readdir(promptDir, { withFileTypes: true });
247
+ for (const entry of entries) {
248
+ if (entry.isFile() && pattern.managedFileNames.includes(entry.name)) {
249
+ const fullPath = path.join(promptDir, entry.name);
250
+ const match = getManagedGlobalLegacyPromptMetadata(fullPath);
251
+ if (match) {
252
+ foundFiles.push(match);
253
+ }
254
+ }
255
+ }
256
+ }
257
+ catch {
258
+ // Directory does not exist or cannot be read.
259
+ }
260
+ }
261
+ return foundFiles;
262
+ }
146
263
  /**
147
264
  * Finds legacy slash command files matching a glob pattern.
148
265
  *
@@ -165,14 +282,7 @@ async function findLegacySlashCommandFiles(projectPath, pattern) {
165
282
  }
166
283
  try {
167
284
  const entries = await fs.readdir(dirPath);
168
- // Convert glob pattern to regex
169
- // openspec-*.md -> /^openspec-.*\.md$/
170
- // openspec-*.prompt.md -> /^openspec-.*\.prompt\.md$/
171
- // openspec-*.toml -> /^openspec-.*\.toml$/
172
- const regexPattern = filePart
173
- .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except *
174
- .replace(/\*/g, '.*'); // Replace * with .*
175
- const regex = new RegExp(`^${regexPattern}$`);
285
+ const regex = globToRegex(filePart);
176
286
  for (const entry of entries) {
177
287
  if (regex.test(entry)) {
178
288
  // Use forward slashes for consistency in relative paths (cross-platform)
@@ -257,6 +367,7 @@ export function removeMarkerBlock(content) {
257
367
  export async function cleanupLegacyArtifacts(projectPath, detection) {
258
368
  const result = {
259
369
  deletedFiles: [],
370
+ deletedFileReplacementLabels: {},
260
371
  modifiedFiles: [],
261
372
  deletedDirs: [],
262
373
  projectMdNeedsMigration: detection.hasProjectMd,
@@ -299,6 +410,25 @@ export async function cleanupLegacyArtifacts(projectPath, detection) {
299
410
  result.errors.push(`Failed to delete ${filePath}: ${error.message}`);
300
411
  }
301
412
  }
413
+ // Delete managed global slash command files (these are 100% OpenSpec-managed)
414
+ const globalPromptMatchesByPath = new Map(getLegacyGlobalPromptMatches(detection).map((prompt) => [prompt.path, prompt]));
415
+ for (const filePath of detection.globalSlashCommandFiles) {
416
+ if (!getManagedGlobalLegacyPromptMetadata(filePath)) {
417
+ result.errors.push(`Skipped unmanaged global prompt ${filePath}`);
418
+ continue;
419
+ }
420
+ try {
421
+ await fs.unlink(filePath);
422
+ result.deletedFiles.push(filePath);
423
+ const promptMatch = globalPromptMatchesByPath.get(filePath);
424
+ if (promptMatch?.replacementLabel) {
425
+ result.deletedFileReplacementLabels[filePath] = promptMatch.replacementLabel;
426
+ }
427
+ }
428
+ catch (error) {
429
+ result.errors.push(`Failed to delete ${filePath}: ${error.message}`);
430
+ }
431
+ }
302
432
  // Delete openspec/AGENTS.md (this is inside openspec/, it's OpenSpec-managed)
303
433
  if (detection.hasOpenspecAgents) {
304
434
  const agentsPath = FileSystemUtils.joinPath(projectPath, 'openspec', 'AGENTS.md');
@@ -328,10 +458,15 @@ export function formatCleanupSummary(result) {
328
458
  if (result.deletedFiles.length > 0 || result.deletedDirs.length > 0 || result.modifiedFiles.length > 0) {
329
459
  lines.push('Cleaned up legacy files:');
330
460
  for (const file of result.deletedFiles) {
331
- lines.push(` ✓ Removed ${file}`);
461
+ const replacementLabel = result.deletedFileReplacementLabels?.[file]
462
+ ?? getManagedGlobalLegacyPromptMetadata(file)?.replacementLabel;
463
+ const replacement = replacementLabel
464
+ ? ` (replaced by ${replacementLabel})`
465
+ : '';
466
+ lines.push(` ✓ Removed ${file}${replacement}`);
332
467
  }
333
468
  for (const dir of result.deletedDirs) {
334
- lines.push(` ✓ Removed ${dir}/ (replaced by /opsx:*)`);
469
+ lines.push(` ✓ Removed ${dir}/ (replaced by OpenSpec skills and commands)`);
335
470
  }
336
471
  for (const file of result.modifiedFiles) {
337
472
  lines.push(` ✓ Removed OpenSpec markers from ${file}`);
@@ -374,6 +509,13 @@ function buildRemovalsList(detection) {
374
509
  for (const file of detection.slashCommandFiles) {
375
510
  removals.push({ path: file, explanation: 'replaced by skills/' });
376
511
  }
512
+ // Managed global slash command files
513
+ for (const prompt of getLegacyGlobalPromptMatches(detection)) {
514
+ const explanation = prompt.toolId
515
+ ? `replaced by .${prompt.toolId}/skills/`
516
+ : 'replaced by skills/';
517
+ removals.push({ path: prompt.path, explanation });
518
+ }
377
519
  // openspec/AGENTS.md (inside openspec/, it's OpenSpec-managed)
378
520
  if (detection.hasOpenspecAgents) {
379
521
  removals.push({ path: 'openspec/AGENTS.md', explanation: 'obsolete workflow file' });
@@ -445,6 +587,24 @@ export function formatDetectionSummary(detection) {
445
587
  }
446
588
  return lines.join('\n');
447
589
  }
590
+ /**
591
+ * Generates a summary for managed global prompt files whose cleanup must wait
592
+ * until replacement skills are installed.
593
+ */
594
+ export function formatDeferredGlobalPromptSummary(detection) {
595
+ const deferredPrompts = getLegacyGlobalPromptMatches(detection);
596
+ if (deferredPrompts.length === 0) {
597
+ return '';
598
+ }
599
+ const lines = [];
600
+ lines.push(chalk.bold('Deferred global prompts cleanup'));
601
+ lines.push(chalk.dim('These global prompts will only be removed after matching replacement skills are installed.'));
602
+ for (const prompt of deferredPrompts) {
603
+ const toolLabel = prompt.toolId ? `${prompt.toolId}: ` : '';
604
+ lines.push(` • ${toolLabel}${prompt.path}`);
605
+ }
606
+ return lines.join('\n');
607
+ }
448
608
  /**
449
609
  * Extract tool IDs from detected legacy artifacts.
450
610
  * Uses LEGACY_SLASH_COMMAND_PATHS to map paths back to tool IDs.
@@ -466,18 +626,13 @@ export function getToolsFromLegacyArtifacts(detection) {
466
626
  // Match files to tool IDs using glob patterns
467
627
  for (const file of detection.slashCommandFiles) {
468
628
  // Normalize file path to use forward slashes for consistent matching (Windows compatibility)
469
- const normalizedFile = file.replace(/\\/g, '/');
629
+ const normalizedFile = normalizePathForMatch(file);
470
630
  for (const [toolId, pattern] of Object.entries(LEGACY_SLASH_COMMAND_PATHS)) {
471
631
  if (pattern.type === 'files' && pattern.pattern) {
472
- // Convert glob pattern to regex for matching
473
- // e.g., '.cursor/commands/openspec-*.md' -> /^\.cursor\/commands\/openspec-.*\.md$/
474
632
  const patterns = Array.isArray(pattern.pattern) ? pattern.pattern : [pattern.pattern];
475
633
  let matched = false;
476
634
  for (const p of patterns) {
477
- const regexPattern = p
478
- .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars except *
479
- .replace(/\*/g, '.*'); // Replace * with .*
480
- const regex = new RegExp(`^${regexPattern}$`);
635
+ const regex = globToRegex(p);
481
636
  if (regex.test(normalizedFile)) {
482
637
  tools.add(toolId);
483
638
  matched = true;
@@ -489,8 +644,82 @@ export function getToolsFromLegacyArtifacts(detection) {
489
644
  }
490
645
  }
491
646
  }
647
+ for (const prompt of getLegacyGlobalPromptMatches(detection)) {
648
+ tools.add(prompt.toolId);
649
+ }
492
650
  return Array.from(tools);
493
651
  }
652
+ /**
653
+ * Normalizes global Codex prompt matches so callers can rely on workflow-aware
654
+ * metadata even when older detection results only carry file paths.
655
+ */
656
+ export function getLegacyGlobalPromptMatches(detection) {
657
+ if (detection.globalSlashCommandDetails && detection.globalSlashCommandDetails.length > 0) {
658
+ return detection.globalSlashCommandDetails;
659
+ }
660
+ return detection.globalSlashCommandFiles
661
+ .map((filePath) => getManagedGlobalLegacyPromptMetadata(filePath))
662
+ .filter((match) => match !== undefined);
663
+ }
664
+ /**
665
+ * Collects workflow IDs inferred from detected legacy global prompts for a
666
+ * specific tool.
667
+ */
668
+ export function getLegacyWorkflowIdsForTool(detection, toolId) {
669
+ const workflows = new Set();
670
+ for (const prompt of getLegacyGlobalPromptMatches(detection)) {
671
+ if (prompt.toolId !== toolId) {
672
+ continue;
673
+ }
674
+ for (const workflowId of prompt.workflowIds) {
675
+ workflows.add(workflowId);
676
+ }
677
+ }
678
+ return Array.from(workflows);
679
+ }
680
+ function hasLegacyArtifacts(detection) {
681
+ return (detection.configFiles.length > 0 ||
682
+ detection.slashCommandDirs.length > 0 ||
683
+ detection.slashCommandFiles.length > 0 ||
684
+ detection.globalSlashCommandFiles.length > 0 ||
685
+ detection.hasOpenspecAgents ||
686
+ detection.hasRootAgentsWithMarkers ||
687
+ detection.hasProjectMd);
688
+ }
689
+ /**
690
+ * Returns a detection snapshot with global Codex prompt cleanup removed so
691
+ * callers can safely perform the immediate, non-deferred cleanup pass.
692
+ */
693
+ export function omitGlobalLegacyPromptFiles(detection) {
694
+ const nextDetection = {
695
+ ...detection,
696
+ globalSlashCommandFiles: [],
697
+ globalSlashCommandDetails: [],
698
+ };
699
+ nextDetection.hasLegacyArtifacts = hasLegacyArtifacts(nextDetection);
700
+ return nextDetection;
701
+ }
702
+ /**
703
+ * Builds a detection snapshot containing only the selected global Codex prompt
704
+ * matches for replacement-gated cleanup.
705
+ */
706
+ export function pickGlobalLegacyPromptFiles(detection, filePaths) {
707
+ const selectedPaths = new Set(filePaths.map((filePath) => path.resolve(filePath)));
708
+ const details = getLegacyGlobalPromptMatches(detection)
709
+ .filter((detail) => selectedPaths.has(path.resolve(detail.path)));
710
+ return {
711
+ configFiles: [],
712
+ configFilesToUpdate: [],
713
+ slashCommandDirs: [],
714
+ slashCommandFiles: [],
715
+ globalSlashCommandFiles: details.map((detail) => detail.path),
716
+ globalSlashCommandDetails: details,
717
+ hasOpenspecAgents: false,
718
+ hasProjectMd: false,
719
+ hasRootAgentsWithMarkers: false,
720
+ hasLegacyArtifacts: details.length > 0,
721
+ };
722
+ }
494
723
  /**
495
724
  * Generates a migration hint message for project.md.
496
725
  * This is shown when project.md exists and needs manual migration to config.yaml.
package/dist/core/list.js CHANGED
@@ -2,8 +2,24 @@ import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
3
  import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
4
4
  import { readFileSync } from 'fs';
5
- import { join } from 'path';
6
5
  import { MarkdownParser } from './parsers/markdown-parser.js';
6
+ import { discoverSpecFiles } from '../utils/spec-discovery.js';
7
+ function isMissingPathError(error) {
8
+ return (typeof error === 'object' &&
9
+ error !== null &&
10
+ 'code' in error &&
11
+ error.code === 'ENOENT');
12
+ }
13
+ async function readChangeDirectoryEntries(changesDir) {
14
+ try {
15
+ return await fs.readdir(changesDir, { withFileTypes: true });
16
+ }
17
+ catch (error) {
18
+ if (isMissingPathError(error))
19
+ return [];
20
+ throw error;
21
+ }
22
+ }
7
23
  /**
8
24
  * Get the most recent modification time of any file in a directory (recursive).
9
25
  * Falls back to the directory's own mtime if no files are found.
@@ -64,15 +80,8 @@ export class ListCommand {
64
80
  const { sort = 'recent', json = false, root } = options;
65
81
  if (mode === 'changes') {
66
82
  const changesDir = path.join(targetPath, 'openspec', 'changes');
67
- // Check if changes directory exists
68
- try {
69
- await fs.access(changesDir);
70
- }
71
- catch {
72
- throw new Error("No OpenSpec changes directory found. Run 'openspec init' first.");
73
- }
74
83
  // Get all directories in changes (excluding archive)
75
- const entries = await fs.readdir(changesDir, { withFileTypes: true });
84
+ const entries = await readChangeDirectoryEntries(changesDir);
76
85
  const changeDirs = entries
77
86
  .filter(entry => entry.isDirectory() && entry.name !== 'archive')
78
87
  .map(entry => entry.name);
@@ -88,7 +97,7 @@ export class ListCommand {
88
97
  // Collect information about each change
89
98
  const changes = [];
90
99
  for (const changeDir of changeDirs) {
91
- const progress = await getTaskProgressForChange(changesDir, changeDir);
100
+ const progress = await getTaskProgressForChange(changesDir, changeDir, targetPath);
92
101
  const changePath = path.join(changesDir, changeDir);
93
102
  const lastModified = await getLastModified(changePath);
94
103
  changes.push({
@@ -143,9 +152,8 @@ export class ListCommand {
143
152
  }
144
153
  return;
145
154
  }
146
- const entries = await fs.readdir(specsDir, { withFileTypes: true });
147
- const specDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
148
- if (specDirs.length === 0) {
155
+ const discovered = await discoverSpecFiles(specsDir);
156
+ if (discovered.length === 0) {
149
157
  if (json) {
150
158
  console.log(JSON.stringify({ specs: [], ...(root ? { root } : {}) }, null, 2));
151
159
  }
@@ -155,10 +163,9 @@ export class ListCommand {
155
163
  return;
156
164
  }
157
165
  const specs = [];
158
- for (const id of specDirs) {
159
- const specPath = join(specsDir, id, 'spec.md');
166
+ for (const { id, specFile } of discovered) {
160
167
  try {
161
- const content = readFileSync(specPath, 'utf-8');
168
+ const content = readFileSync(specFile, 'utf-8');
162
169
  const parser = new MarkdownParser(content);
163
170
  const spec = parser.parseSpec(id);
164
171
  specs.push({ id, requirementCount: spec.requirements.length });
@@ -4,7 +4,79 @@
4
4
  * One-time migration logic for existing projects when profile system is introduced.
5
5
  * Called by both init and update commands before profile resolution.
6
6
  */
7
- import type { AIToolOption } from './config.js';
7
+ import { type AIToolOption } from './config.js';
8
+ export interface LegacyToolRoot {
9
+ /** Former tool root, e.g. '.kimi' */
10
+ root: string;
11
+ /**
12
+ * Whether leaving this root requires the user's say-so. False when the old
13
+ * product is gone and its directory is certainly dead. True when the old
14
+ * location may still be the live one for somebody.
15
+ */
16
+ needsConsent: boolean;
17
+ /** Migrations that need a freshly generated destination run afterward. */
18
+ timing?: 'before-generation' | 'after-generation';
19
+ }
20
+ /**
21
+ * Former tool roots whose OpenSpec-managed content belongs under the tool's
22
+ * current skillsDir. User files are never touched.
23
+ */
24
+ export declare const LEGACY_TOOL_ROOTS: Record<string, LegacyToolRoot[]>;
25
+ export interface LegacyToolMigration {
26
+ toolId: string;
27
+ /** Legacy tool root, e.g. '.windsurf' */
28
+ from: string;
29
+ /** Current tool root, e.g. '.devin' */
30
+ to: string;
31
+ /** Skill directories that moved, or would move */
32
+ skillDirs: number;
33
+ /** Command files that moved, or would move */
34
+ commandFiles: number;
35
+ /**
36
+ * OpenSpec-managed files left under the legacy root because the copy there
37
+ * differs materially from the one that survives, so it is reported rather
38
+ * than dropped.
39
+ */
40
+ keptInPlace: number;
41
+ /** Whether this move needs the user's consent first */
42
+ needsConsent: boolean;
43
+ }
44
+ /**
45
+ * Reports the OpenSpec content sitting under each tool's legacy root, without
46
+ * moving anything. Callers use this to ask before a move that needs consent.
47
+ */
48
+ export declare function findLegacyToolMigrations(projectPath: string, timing?: 'before-generation' | 'after-generation'): LegacyToolMigration[];
49
+ /**
50
+ * Moves OpenSpec-managed skill directories (openspec-*) and command files
51
+ * (opsx-*) from a tool's legacy root to its current one. When the destination
52
+ * already exists the legacy copy is removed instead. Legacy directories are
53
+ * deleted only when left empty, so user files under the old location — a
54
+ * hand-written Cascade workflow next to the generated ones — are preserved.
55
+ *
56
+ * @param projectPath - Project root
57
+ * @param toolIds - Restrict the move to these tools; omit to move every tool
58
+ * whose legacy root needs no consent
59
+ */
60
+ export declare function migrateLegacyToolDirs(projectPath: string, toolIds?: string[], timing?: 'before-generation' | 'after-generation'): LegacyToolMigration[];
61
+ /**
62
+ * Summarizes what a migration moved, e.g. "6 skills and 6 commands".
63
+ */
64
+ export declare function describeLegacyMigration(migration: LegacyToolMigration): string;
65
+ /**
66
+ * Names OpenSpec-managed files the move deliberately left behind, so a user
67
+ * who customized one knows there are now two copies to reconcile.
68
+ */
69
+ export declare function keptInPlaceNotice(migration: LegacyToolMigration): string | undefined;
70
+ /**
71
+ * Whether a migration has anything to move, as opposed to only files left in
72
+ * place. Callers use this to avoid offering a move of nothing.
73
+ */
74
+ export declare function hasMovableContent(migration: LegacyToolMigration): boolean;
75
+ /**
76
+ * Explains why a consent-gated move is being offered, in the user's terms.
77
+ * Keyed by tool so the reason is specific rather than a generic "files moved".
78
+ */
79
+ export declare function legacyMigrationNotice(migration: LegacyToolMigration): string;
8
80
  /**
9
81
  * Scans installed workflow files across all detected tools and returns
10
82
  * the union of installed workflow IDs.