openspec-cn 0.23.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 (235) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +153 -0
  3. package/bin/openspec.js +3 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +480 -0
  6. package/dist/commands/change.d.ts +35 -0
  7. package/dist/commands/change.js +277 -0
  8. package/dist/commands/completion.d.ts +72 -0
  9. package/dist/commands/completion.js +257 -0
  10. package/dist/commands/config.d.ts +8 -0
  11. package/dist/commands/config.js +198 -0
  12. package/dist/commands/feedback.d.ts +9 -0
  13. package/dist/commands/feedback.js +183 -0
  14. package/dist/commands/schema.d.ts +6 -0
  15. package/dist/commands/schema.js +869 -0
  16. package/dist/commands/show.d.ts +14 -0
  17. package/dist/commands/show.js +132 -0
  18. package/dist/commands/spec.d.ts +15 -0
  19. package/dist/commands/spec.js +225 -0
  20. package/dist/commands/validate.d.ts +24 -0
  21. package/dist/commands/validate.js +294 -0
  22. package/dist/commands/workflow/index.d.ts +17 -0
  23. package/dist/commands/workflow/index.js +12 -0
  24. package/dist/commands/workflow/instructions.d.ts +29 -0
  25. package/dist/commands/workflow/instructions.js +381 -0
  26. package/dist/commands/workflow/new-change.d.ts +11 -0
  27. package/dist/commands/workflow/new-change.js +44 -0
  28. package/dist/commands/workflow/schemas.d.ts +10 -0
  29. package/dist/commands/workflow/schemas.js +34 -0
  30. package/dist/commands/workflow/shared.d.ts +52 -0
  31. package/dist/commands/workflow/shared.js +111 -0
  32. package/dist/commands/workflow/status.d.ts +14 -0
  33. package/dist/commands/workflow/status.js +58 -0
  34. package/dist/commands/workflow/templates.d.ts +16 -0
  35. package/dist/commands/workflow/templates.js +68 -0
  36. package/dist/core/archive.d.ts +11 -0
  37. package/dist/core/archive.js +280 -0
  38. package/dist/core/artifact-graph/graph.d.ts +56 -0
  39. package/dist/core/artifact-graph/graph.js +141 -0
  40. package/dist/core/artifact-graph/index.d.ts +7 -0
  41. package/dist/core/artifact-graph/index.js +13 -0
  42. package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
  43. package/dist/core/artifact-graph/instruction-loader.js +214 -0
  44. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  45. package/dist/core/artifact-graph/resolver.js +257 -0
  46. package/dist/core/artifact-graph/schema.d.ts +13 -0
  47. package/dist/core/artifact-graph/schema.js +108 -0
  48. package/dist/core/artifact-graph/state.d.ts +12 -0
  49. package/dist/core/artifact-graph/state.js +54 -0
  50. package/dist/core/artifact-graph/types.d.ts +45 -0
  51. package/dist/core/artifact-graph/types.js +43 -0
  52. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  53. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  54. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  55. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  56. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  57. package/dist/core/command-generation/adapters/auggie.js +27 -0
  58. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  59. package/dist/core/command-generation/adapters/claude.js +50 -0
  60. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  61. package/dist/core/command-generation/adapters/cline.js +27 -0
  62. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  63. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  64. package/dist/core/command-generation/adapters/codex.d.ts +13 -0
  65. package/dist/core/command-generation/adapters/codex.js +27 -0
  66. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  67. package/dist/core/command-generation/adapters/continue.js +28 -0
  68. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  69. package/dist/core/command-generation/adapters/costrict.js +27 -0
  70. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/crush.js +30 -0
  72. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  73. package/dist/core/command-generation/adapters/cursor.js +44 -0
  74. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  75. package/dist/core/command-generation/adapters/factory.js +27 -0
  76. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  77. package/dist/core/command-generation/adapters/gemini.js +26 -0
  78. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  79. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  80. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/iflow.js +29 -0
  82. package/dist/core/command-generation/adapters/index.d.ts +27 -0
  83. package/dist/core/command-generation/adapters/index.js +27 -0
  84. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  85. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  86. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/opencode.js +26 -0
  88. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  89. package/dist/core/command-generation/adapters/qoder.js +30 -0
  90. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  91. package/dist/core/command-generation/adapters/qwen.js +26 -0
  92. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  93. package/dist/core/command-generation/adapters/roocode.js +27 -0
  94. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  95. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  96. package/dist/core/command-generation/generator.d.ts +21 -0
  97. package/dist/core/command-generation/generator.js +27 -0
  98. package/dist/core/command-generation/index.d.ts +22 -0
  99. package/dist/core/command-generation/index.js +24 -0
  100. package/dist/core/command-generation/registry.d.ts +36 -0
  101. package/dist/core/command-generation/registry.js +88 -0
  102. package/dist/core/command-generation/types.d.ts +55 -0
  103. package/dist/core/command-generation/types.js +8 -0
  104. package/dist/core/completions/command-registry.d.ts +7 -0
  105. package/dist/core/completions/command-registry.js +456 -0
  106. package/dist/core/completions/completion-provider.d.ts +60 -0
  107. package/dist/core/completions/completion-provider.js +102 -0
  108. package/dist/core/completions/factory.d.ts +64 -0
  109. package/dist/core/completions/factory.js +75 -0
  110. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  111. package/dist/core/completions/generators/bash-generator.js +174 -0
  112. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  113. package/dist/core/completions/generators/fish-generator.js +157 -0
  114. package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
  115. package/dist/core/completions/generators/powershell-generator.js +207 -0
  116. package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
  117. package/dist/core/completions/generators/zsh-generator.js +250 -0
  118. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  119. package/dist/core/completions/installers/bash-installer.js +318 -0
  120. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  121. package/dist/core/completions/installers/fish-installer.js +143 -0
  122. package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
  123. package/dist/core/completions/installers/powershell-installer.js +327 -0
  124. package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
  125. package/dist/core/completions/installers/zsh-installer.js +449 -0
  126. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  127. package/dist/core/completions/templates/bash-templates.js +24 -0
  128. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  129. package/dist/core/completions/templates/fish-templates.js +39 -0
  130. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  131. package/dist/core/completions/templates/powershell-templates.js +25 -0
  132. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  133. package/dist/core/completions/templates/zsh-templates.js +36 -0
  134. package/dist/core/completions/types.d.ts +79 -0
  135. package/dist/core/completions/types.js +2 -0
  136. package/dist/core/config-prompts.d.ts +9 -0
  137. package/dist/core/config-prompts.js +34 -0
  138. package/dist/core/config-schema.d.ts +76 -0
  139. package/dist/core/config-schema.js +200 -0
  140. package/dist/core/config.d.ts +17 -0
  141. package/dist/core/config.js +30 -0
  142. package/dist/core/converters/json-converter.d.ts +6 -0
  143. package/dist/core/converters/json-converter.js +51 -0
  144. package/dist/core/global-config.d.ts +39 -0
  145. package/dist/core/global-config.js +115 -0
  146. package/dist/core/index.d.ts +2 -0
  147. package/dist/core/index.js +3 -0
  148. package/dist/core/init.d.ts +32 -0
  149. package/dist/core/init.js +433 -0
  150. package/dist/core/legacy-cleanup.d.ts +162 -0
  151. package/dist/core/legacy-cleanup.js +501 -0
  152. package/dist/core/list.d.ts +9 -0
  153. package/dist/core/list.js +171 -0
  154. package/dist/core/parsers/change-parser.d.ts +13 -0
  155. package/dist/core/parsers/change-parser.js +193 -0
  156. package/dist/core/parsers/markdown-parser.d.ts +22 -0
  157. package/dist/core/parsers/markdown-parser.js +187 -0
  158. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  159. package/dist/core/parsers/requirement-blocks.js +201 -0
  160. package/dist/core/project-config.d.ts +64 -0
  161. package/dist/core/project-config.js +223 -0
  162. package/dist/core/schemas/base.schema.d.ts +13 -0
  163. package/dist/core/schemas/base.schema.js +13 -0
  164. package/dist/core/schemas/change.schema.d.ts +73 -0
  165. package/dist/core/schemas/change.schema.js +31 -0
  166. package/dist/core/schemas/index.d.ts +4 -0
  167. package/dist/core/schemas/index.js +4 -0
  168. package/dist/core/schemas/spec.schema.d.ts +18 -0
  169. package/dist/core/schemas/spec.schema.js +15 -0
  170. package/dist/core/shared/index.d.ts +8 -0
  171. package/dist/core/shared/index.js +8 -0
  172. package/dist/core/shared/skill-generation.d.ts +41 -0
  173. package/dist/core/shared/skill-generation.js +74 -0
  174. package/dist/core/shared/tool-detection.d.ts +66 -0
  175. package/dist/core/shared/tool-detection.js +140 -0
  176. package/dist/core/specs-apply.d.ts +73 -0
  177. package/dist/core/specs-apply.js +384 -0
  178. package/dist/core/styles/palette.d.ts +7 -0
  179. package/dist/core/styles/palette.js +8 -0
  180. package/dist/core/templates/index.d.ts +8 -0
  181. package/dist/core/templates/index.js +9 -0
  182. package/dist/core/templates/skill-templates.d.ts +112 -0
  183. package/dist/core/templates/skill-templates.js +2893 -0
  184. package/dist/core/update.d.ts +42 -0
  185. package/dist/core/update.js +306 -0
  186. package/dist/core/validation/constants.d.ts +34 -0
  187. package/dist/core/validation/constants.js +40 -0
  188. package/dist/core/validation/types.d.ts +18 -0
  189. package/dist/core/validation/types.js +2 -0
  190. package/dist/core/validation/validator.d.ts +33 -0
  191. package/dist/core/validation/validator.js +409 -0
  192. package/dist/core/view.d.ts +8 -0
  193. package/dist/core/view.js +168 -0
  194. package/dist/index.d.ts +3 -0
  195. package/dist/index.js +3 -0
  196. package/dist/prompts/searchable-multi-select.d.ts +27 -0
  197. package/dist/prompts/searchable-multi-select.js +149 -0
  198. package/dist/telemetry/config.d.ts +32 -0
  199. package/dist/telemetry/config.js +68 -0
  200. package/dist/telemetry/index.d.ts +31 -0
  201. package/dist/telemetry/index.js +145 -0
  202. package/dist/ui/ascii-patterns.d.ts +16 -0
  203. package/dist/ui/ascii-patterns.js +133 -0
  204. package/dist/ui/welcome-screen.d.ts +10 -0
  205. package/dist/ui/welcome-screen.js +146 -0
  206. package/dist/utils/change-metadata.d.ts +51 -0
  207. package/dist/utils/change-metadata.js +147 -0
  208. package/dist/utils/change-utils.d.ts +62 -0
  209. package/dist/utils/change-utils.js +121 -0
  210. package/dist/utils/file-system.d.ts +36 -0
  211. package/dist/utils/file-system.js +281 -0
  212. package/dist/utils/index.d.ts +5 -0
  213. package/dist/utils/index.js +7 -0
  214. package/dist/utils/interactive.d.ts +18 -0
  215. package/dist/utils/interactive.js +21 -0
  216. package/dist/utils/item-discovery.d.ts +4 -0
  217. package/dist/utils/item-discovery.js +72 -0
  218. package/dist/utils/match.d.ts +3 -0
  219. package/dist/utils/match.js +22 -0
  220. package/dist/utils/shell-detection.d.ts +20 -0
  221. package/dist/utils/shell-detection.js +41 -0
  222. package/dist/utils/task-progress.d.ts +8 -0
  223. package/dist/utils/task-progress.js +36 -0
  224. package/package.json +84 -0
  225. package/schemas/spec-driven/schema.yaml +148 -0
  226. package/schemas/spec-driven/templates/design.md +19 -0
  227. package/schemas/spec-driven/templates/proposal.md +23 -0
  228. package/schemas/spec-driven/templates/spec.md +8 -0
  229. package/schemas/spec-driven/templates/tasks.md +9 -0
  230. package/schemas/tdd/schema.yaml +213 -0
  231. package/schemas/tdd/templates/docs.md +15 -0
  232. package/schemas/tdd/templates/implementation.md +11 -0
  233. package/schemas/tdd/templates/spec.md +11 -0
  234. package/schemas/tdd/templates/test.md +11 -0
  235. package/scripts/postinstall.js +147 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Init Command
3
+ *
4
+ * Sets up OpenSpec with Agent Skills and /opsx:* slash commands.
5
+ * This is the unified setup command that replaces both the old init and experimental commands.
6
+ */
7
+ import path from 'path';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import * as fs from 'fs';
11
+ import { createRequire } from 'module';
12
+ import { FileSystemUtils } from '../utils/file-system.js';
13
+ import { AI_TOOLS, OPENSPEC_DIR_NAME, } from './config.js';
14
+ import { PALETTE } from './styles/palette.js';
15
+ import { isInteractive } from '../utils/interactive.js';
16
+ import { serializeConfig } from './config-prompts.js';
17
+ import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
18
+ import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, } from './legacy-cleanup.js';
19
+ import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, } from './shared/index.js';
20
+ const require = createRequire(import.meta.url);
21
+ const { version: OPENSPEC_VERSION } = require('../../package.json');
22
+ // -----------------------------------------------------------------------------
23
+ // Constants
24
+ // -----------------------------------------------------------------------------
25
+ const DEFAULT_SCHEMA = 'spec-driven';
26
+ const PROGRESS_SPINNER = {
27
+ interval: 80,
28
+ frames: ['░░░', '▒░░', '▒▒░', '▒▒▒', '▓▒▒', '▓▓▒', '▓▓▓', '▒▓▓', '░▒▓'],
29
+ };
30
+ // -----------------------------------------------------------------------------
31
+ // Init Command Class
32
+ // -----------------------------------------------------------------------------
33
+ export class InitCommand {
34
+ toolsArg;
35
+ force;
36
+ interactiveOption;
37
+ constructor(options = {}) {
38
+ this.toolsArg = options.tools;
39
+ this.force = options.force ?? false;
40
+ this.interactiveOption = options.interactive;
41
+ }
42
+ async execute(targetPath) {
43
+ const projectPath = path.resolve(targetPath);
44
+ const openspecDir = OPENSPEC_DIR_NAME;
45
+ const openspecPath = path.join(projectPath, openspecDir);
46
+ // Validation happens silently in the background
47
+ const extendMode = await this.validate(projectPath, openspecPath);
48
+ // Check for legacy artifacts and handle cleanup
49
+ await this.handleLegacyCleanup(projectPath, extendMode);
50
+ // Show animated welcome screen (interactive mode only)
51
+ const canPrompt = this.canPromptInteractively();
52
+ if (canPrompt) {
53
+ const { showWelcomeScreen } = await import('../ui/welcome-screen.js');
54
+ await showWelcomeScreen();
55
+ }
56
+ // Get tool states before processing
57
+ const toolStates = getToolStates(projectPath);
58
+ // Get tool selection
59
+ const selectedToolIds = await this.getSelectedTools(toolStates, extendMode);
60
+ // Validate selected tools
61
+ const validatedTools = this.validateTools(selectedToolIds, toolStates);
62
+ // Create directory structure and config
63
+ await this.createDirectoryStructure(openspecPath, extendMode);
64
+ // Generate skills and commands for each tool
65
+ const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
66
+ // Create config.yaml if needed
67
+ const configStatus = await this.createConfig(openspecPath, extendMode);
68
+ // Display success message
69
+ this.displaySuccessMessage(projectPath, validatedTools, results, configStatus);
70
+ }
71
+ // ═══════════════════════════════════════════════════════════
72
+ // VALIDATION & SETUP
73
+ // ═══════════════════════════════════════════════════════════
74
+ async validate(projectPath, openspecPath) {
75
+ const extendMode = await FileSystemUtils.directoryExists(openspecPath);
76
+ // Check write permissions
77
+ if (!(await FileSystemUtils.ensureWritePermissions(projectPath))) {
78
+ throw new Error(`Insufficient permissions to write to ${projectPath}`);
79
+ }
80
+ return extendMode;
81
+ }
82
+ canPromptInteractively() {
83
+ if (this.interactiveOption === false)
84
+ return false;
85
+ if (this.toolsArg !== undefined)
86
+ return false;
87
+ return isInteractive({ interactive: this.interactiveOption });
88
+ }
89
+ // ═══════════════════════════════════════════════════════════
90
+ // LEGACY CLEANUP
91
+ // ═══════════════════════════════════════════════════════════
92
+ async handleLegacyCleanup(projectPath, extendMode) {
93
+ // Detect legacy artifacts
94
+ const detection = await detectLegacyArtifacts(projectPath);
95
+ if (!detection.hasLegacyArtifacts) {
96
+ return; // No legacy artifacts found
97
+ }
98
+ // Show what was detected
99
+ console.log();
100
+ console.log(formatDetectionSummary(detection));
101
+ console.log();
102
+ const canPrompt = this.canPromptInteractively();
103
+ if (this.force) {
104
+ // --force flag: proceed with cleanup automatically
105
+ await this.performLegacyCleanup(projectPath, detection);
106
+ return;
107
+ }
108
+ if (!canPrompt) {
109
+ // Non-interactive mode without --force: abort
110
+ console.log(chalk.red('Legacy files detected in non-interactive mode.'));
111
+ console.log(chalk.dim('Run interactively to upgrade, or use --force to auto-cleanup.'));
112
+ process.exit(1);
113
+ }
114
+ // Interactive mode: prompt for confirmation
115
+ const { confirm } = await import('@inquirer/prompts');
116
+ const shouldCleanup = await confirm({
117
+ message: 'Upgrade and clean up legacy files?',
118
+ default: true,
119
+ });
120
+ if (!shouldCleanup) {
121
+ console.log(chalk.dim('Initialization cancelled.'));
122
+ console.log(chalk.dim('Run with --force to skip this prompt, or manually remove legacy files.'));
123
+ process.exit(0);
124
+ }
125
+ await this.performLegacyCleanup(projectPath, detection);
126
+ }
127
+ async performLegacyCleanup(projectPath, detection) {
128
+ const spinner = ora('Cleaning up legacy files...').start();
129
+ const result = await cleanupLegacyArtifacts(projectPath, detection);
130
+ spinner.succeed('Legacy files cleaned up');
131
+ const summary = formatCleanupSummary(result);
132
+ if (summary) {
133
+ console.log();
134
+ console.log(summary);
135
+ }
136
+ console.log();
137
+ }
138
+ // ═══════════════════════════════════════════════════════════
139
+ // TOOL SELECTION
140
+ // ═══════════════════════════════════════════════════════════
141
+ async getSelectedTools(toolStates, extendMode) {
142
+ // Check for --tools flag first
143
+ const nonInteractiveSelection = this.resolveToolsArg();
144
+ if (nonInteractiveSelection !== null) {
145
+ return nonInteractiveSelection;
146
+ }
147
+ const validTools = getToolsWithSkillsDir();
148
+ const canPrompt = this.canPromptInteractively();
149
+ if (!canPrompt || validTools.length === 0) {
150
+ throw new Error(`Missing required option --tools. Valid tools:\n ${validTools.join('\n ')}\n\nUse --tools all, --tools none, or --tools claude,cursor,...`);
151
+ }
152
+ // Interactive mode: show searchable multi-select
153
+ const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
154
+ // Build choices with configured status and sort configured tools first
155
+ const sortedChoices = validTools
156
+ .map((toolId) => {
157
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
158
+ const status = toolStates.get(toolId);
159
+ const configured = status?.configured ?? false;
160
+ return {
161
+ name: tool?.name || toolId,
162
+ value: toolId,
163
+ configured,
164
+ preSelected: configured, // Pre-select configured tools for easy refresh
165
+ };
166
+ })
167
+ .sort((a, b) => {
168
+ // Configured tools first
169
+ if (a.configured && !b.configured)
170
+ return -1;
171
+ if (!a.configured && b.configured)
172
+ return 1;
173
+ return 0;
174
+ });
175
+ const selectedTools = await searchableMultiSelect({
176
+ message: `Select tools to set up (${validTools.length} available)`,
177
+ pageSize: 15,
178
+ choices: sortedChoices,
179
+ validate: (selected) => selected.length > 0 || 'Select at least one tool',
180
+ });
181
+ if (selectedTools.length === 0) {
182
+ throw new Error('At least one tool must be selected');
183
+ }
184
+ return selectedTools;
185
+ }
186
+ resolveToolsArg() {
187
+ if (typeof this.toolsArg === 'undefined') {
188
+ return null;
189
+ }
190
+ const raw = this.toolsArg.trim();
191
+ if (raw.length === 0) {
192
+ throw new Error('The --tools option requires a value. Use "all", "none", or a comma-separated list of tool IDs.');
193
+ }
194
+ const availableTools = getToolsWithSkillsDir();
195
+ const availableSet = new Set(availableTools);
196
+ const availableList = ['all', 'none', ...availableTools].join(', ');
197
+ const lowerRaw = raw.toLowerCase();
198
+ if (lowerRaw === 'all') {
199
+ return availableTools;
200
+ }
201
+ if (lowerRaw === 'none') {
202
+ return [];
203
+ }
204
+ const tokens = raw
205
+ .split(',')
206
+ .map((token) => token.trim())
207
+ .filter((token) => token.length > 0);
208
+ if (tokens.length === 0) {
209
+ throw new Error('The --tools option requires at least one tool ID when not using "all" or "none".');
210
+ }
211
+ const normalizedTokens = tokens.map((token) => token.toLowerCase());
212
+ if (normalizedTokens.some((token) => token === 'all' || token === 'none')) {
213
+ throw new Error('Cannot combine reserved values "all" or "none" with specific tool IDs.');
214
+ }
215
+ const invalidTokens = tokens.filter((_token, index) => !availableSet.has(normalizedTokens[index]));
216
+ if (invalidTokens.length > 0) {
217
+ throw new Error(`Invalid tool(s): ${invalidTokens.join(', ')}. Available values: ${availableList}`);
218
+ }
219
+ // Deduplicate while preserving order
220
+ const deduped = [];
221
+ for (const token of normalizedTokens) {
222
+ if (!deduped.includes(token)) {
223
+ deduped.push(token);
224
+ }
225
+ }
226
+ return deduped;
227
+ }
228
+ validateTools(toolIds, toolStates) {
229
+ const validatedTools = [];
230
+ for (const toolId of toolIds) {
231
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
232
+ if (!tool) {
233
+ const validToolIds = getToolsWithSkillsDir();
234
+ throw new Error(`Unknown tool '${toolId}'. Valid tools:\n ${validToolIds.join('\n ')}`);
235
+ }
236
+ if (!tool.skillsDir) {
237
+ const validToolsWithSkills = getToolsWithSkillsDir();
238
+ throw new Error(`Tool '${toolId}' does not support skill generation.\nTools with skill generation support:\n ${validToolsWithSkills.join('\n ')}`);
239
+ }
240
+ const preState = toolStates.get(tool.value);
241
+ validatedTools.push({
242
+ value: tool.value,
243
+ name: tool.name,
244
+ skillsDir: tool.skillsDir,
245
+ wasConfigured: preState?.configured ?? false,
246
+ });
247
+ }
248
+ return validatedTools;
249
+ }
250
+ // ═══════════════════════════════════════════════════════════
251
+ // DIRECTORY STRUCTURE
252
+ // ═══════════════════════════════════════════════════════════
253
+ async createDirectoryStructure(openspecPath, extendMode) {
254
+ if (extendMode) {
255
+ // In extend mode, just ensure directories exist without spinner
256
+ const directories = [
257
+ openspecPath,
258
+ path.join(openspecPath, 'specs'),
259
+ path.join(openspecPath, 'changes'),
260
+ path.join(openspecPath, 'changes', 'archive'),
261
+ ];
262
+ for (const dir of directories) {
263
+ await FileSystemUtils.createDirectory(dir);
264
+ }
265
+ return;
266
+ }
267
+ const spinner = this.startSpinner('Creating OpenSpec structure...');
268
+ const directories = [
269
+ openspecPath,
270
+ path.join(openspecPath, 'specs'),
271
+ path.join(openspecPath, 'changes'),
272
+ path.join(openspecPath, 'changes', 'archive'),
273
+ ];
274
+ for (const dir of directories) {
275
+ await FileSystemUtils.createDirectory(dir);
276
+ }
277
+ spinner.stopAndPersist({
278
+ symbol: PALETTE.white('▌'),
279
+ text: PALETTE.white('OpenSpec structure created'),
280
+ });
281
+ }
282
+ // ═══════════════════════════════════════════════════════════
283
+ // SKILL & COMMAND GENERATION
284
+ // ═══════════════════════════════════════════════════════════
285
+ async generateSkillsAndCommands(projectPath, tools) {
286
+ const createdTools = [];
287
+ const refreshedTools = [];
288
+ const failedTools = [];
289
+ const commandsSkipped = [];
290
+ // Get skill and command templates once (shared across all tools)
291
+ const skillTemplates = getSkillTemplates();
292
+ const commandContents = getCommandContents();
293
+ // Process each tool
294
+ for (const tool of tools) {
295
+ const spinner = ora(`Setting up ${tool.name}...`).start();
296
+ try {
297
+ // Use tool-specific skillsDir
298
+ const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
299
+ // Create skill directories and SKILL.md files
300
+ for (const { template, dirName } of skillTemplates) {
301
+ const skillDir = path.join(skillsDir, dirName);
302
+ const skillFile = path.join(skillDir, 'SKILL.md');
303
+ // Generate SKILL.md content with YAML frontmatter including generatedBy
304
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION);
305
+ // Write the skill file
306
+ await FileSystemUtils.writeFile(skillFile, skillContent);
307
+ }
308
+ // Generate commands using the adapter system
309
+ const adapter = CommandAdapterRegistry.get(tool.value);
310
+ if (adapter) {
311
+ const generatedCommands = generateCommands(commandContents, adapter);
312
+ for (const cmd of generatedCommands) {
313
+ const commandFile = path.join(projectPath, cmd.path);
314
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
315
+ }
316
+ }
317
+ else {
318
+ commandsSkipped.push(tool.value);
319
+ }
320
+ spinner.succeed(`Setup complete for ${tool.name}`);
321
+ if (tool.wasConfigured) {
322
+ refreshedTools.push(tool);
323
+ }
324
+ else {
325
+ createdTools.push(tool);
326
+ }
327
+ }
328
+ catch (error) {
329
+ spinner.fail(`Failed for ${tool.name}`);
330
+ failedTools.push({ name: tool.name, error: error });
331
+ }
332
+ }
333
+ return { createdTools, refreshedTools, failedTools, commandsSkipped };
334
+ }
335
+ // ═══════════════════════════════════════════════════════════
336
+ // CONFIG FILE
337
+ // ═══════════════════════════════════════════════════════════
338
+ async createConfig(openspecPath, extendMode) {
339
+ const configPath = path.join(openspecPath, 'config.yaml');
340
+ const configYmlPath = path.join(openspecPath, 'config.yml');
341
+ const configYamlExists = fs.existsSync(configPath);
342
+ const configYmlExists = fs.existsSync(configYmlPath);
343
+ if (configYamlExists || configYmlExists) {
344
+ return 'exists';
345
+ }
346
+ // In non-interactive mode without --force, skip config creation
347
+ if (!this.canPromptInteractively() && !this.force) {
348
+ return 'skipped';
349
+ }
350
+ try {
351
+ const yamlContent = serializeConfig({ schema: DEFAULT_SCHEMA });
352
+ await FileSystemUtils.writeFile(configPath, yamlContent);
353
+ return 'created';
354
+ }
355
+ catch {
356
+ return 'skipped';
357
+ }
358
+ }
359
+ // ═══════════════════════════════════════════════════════════
360
+ // UI & OUTPUT
361
+ // ═══════════════════════════════════════════════════════════
362
+ displaySuccessMessage(projectPath, tools, results, configStatus) {
363
+ console.log();
364
+ console.log(chalk.bold('OpenSpec Setup Complete'));
365
+ console.log();
366
+ // Show created vs refreshed tools
367
+ if (results.createdTools.length > 0) {
368
+ console.log(`Created: ${results.createdTools.map((t) => t.name).join(', ')}`);
369
+ }
370
+ if (results.refreshedTools.length > 0) {
371
+ console.log(`Refreshed: ${results.refreshedTools.map((t) => t.name).join(', ')}`);
372
+ }
373
+ // Show counts
374
+ const successfulTools = [...results.createdTools, ...results.refreshedTools];
375
+ if (successfulTools.length > 0) {
376
+ const toolDirs = [...new Set(successfulTools.map((t) => t.skillsDir))].join(', ');
377
+ const hasCommands = results.commandsSkipped.length < successfulTools.length;
378
+ if (hasCommands) {
379
+ console.log(`${getSkillTemplates().length} skills and ${getCommandContents().length} commands in ${toolDirs}/`);
380
+ }
381
+ else {
382
+ console.log(`${getSkillTemplates().length} skills in ${toolDirs}/`);
383
+ }
384
+ }
385
+ // Show failures
386
+ if (results.failedTools.length > 0) {
387
+ console.log(chalk.red(`Failed: ${results.failedTools.map((f) => `${f.name} (${f.error.message})`).join(', ')}`));
388
+ }
389
+ // Show skipped commands
390
+ if (results.commandsSkipped.length > 0) {
391
+ console.log(chalk.dim(`Commands skipped for: ${results.commandsSkipped.join(', ')} (no adapter)`));
392
+ }
393
+ // Config status
394
+ if (configStatus === 'created') {
395
+ console.log(`Config: openspec/config.yaml (schema: ${DEFAULT_SCHEMA})`);
396
+ }
397
+ else if (configStatus === 'exists') {
398
+ // Show actual filename (config.yaml or config.yml)
399
+ const configYaml = path.join(projectPath, OPENSPEC_DIR_NAME, 'config.yaml');
400
+ const configYml = path.join(projectPath, OPENSPEC_DIR_NAME, 'config.yml');
401
+ const configName = fs.existsSync(configYaml) ? 'config.yaml' : fs.existsSync(configYml) ? 'config.yml' : 'config.yaml';
402
+ console.log(`Config: openspec/${configName} (exists)`);
403
+ }
404
+ else {
405
+ console.log(chalk.dim(`Config: skipped (non-interactive mode)`));
406
+ }
407
+ // Getting started
408
+ console.log();
409
+ console.log(chalk.bold('Getting started:'));
410
+ console.log(' /opsx:new Start a new change');
411
+ console.log(' /opsx:continue Create the next artifact');
412
+ console.log(' /opsx:apply Implement tasks');
413
+ // Links
414
+ console.log();
415
+ console.log(`Learn more: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec')}`);
416
+ console.log(`Feedback: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec/issues')}`);
417
+ // Restart instruction if any tools were configured
418
+ if (results.createdTools.length > 0 || results.refreshedTools.length > 0) {
419
+ console.log();
420
+ console.log(chalk.white('Restart your IDE for slash commands to take effect.'));
421
+ }
422
+ console.log();
423
+ }
424
+ startSpinner(text) {
425
+ return ora({
426
+ text,
427
+ stream: process.stdout,
428
+ color: 'gray',
429
+ spinner: PROGRESS_SPINNER,
430
+ }).start();
431
+ }
432
+ }
433
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Legacy cleanup module for detecting and removing OpenSpec artifacts
3
+ * from previous init versions during the migration to the skill-based workflow.
4
+ */
5
+ /**
6
+ * Legacy config file names from the old ToolRegistry.
7
+ * These were config files created at project root with OpenSpec markers.
8
+ */
9
+ export declare const LEGACY_CONFIG_FILES: readonly ["CLAUDE.md", "CLINE.md", "CODEBUDDY.md", "COSTRICT.md", "QODER.md", "IFLOW.md", "AGENTS.md", "QWEN.md"];
10
+ /**
11
+ * Legacy slash command patterns from the old SlashCommandRegistry.
12
+ * These map toolId to the path pattern where legacy commands were created.
13
+ * Some tools used a directory structure, others used individual files.
14
+ */
15
+ export declare const LEGACY_SLASH_COMMAND_PATHS: Record<string, LegacySlashCommandPattern>;
16
+ /**
17
+ * Pattern types for legacy slash commands
18
+ */
19
+ export interface LegacySlashCommandPattern {
20
+ type: 'directory' | 'files';
21
+ path?: string;
22
+ pattern?: string;
23
+ }
24
+ /**
25
+ * Result of legacy artifact detection
26
+ */
27
+ export interface LegacyDetectionResult {
28
+ /** Config files with OpenSpec markers detected */
29
+ configFiles: string[];
30
+ /** Config files to update (remove markers only, never delete) */
31
+ configFilesToUpdate: string[];
32
+ /** Legacy slash command directories found */
33
+ slashCommandDirs: string[];
34
+ /** Legacy slash command files found (for file-based tools) */
35
+ slashCommandFiles: string[];
36
+ /** Whether openspec/AGENTS.md exists */
37
+ hasOpenspecAgents: boolean;
38
+ /** Whether openspec/project.md exists (preserved, migration hint only) */
39
+ hasProjectMd: boolean;
40
+ /** Whether root AGENTS.md has OpenSpec markers */
41
+ hasRootAgentsWithMarkers: boolean;
42
+ /** Whether any legacy artifacts were found */
43
+ hasLegacyArtifacts: boolean;
44
+ }
45
+ /**
46
+ * Detects all legacy OpenSpec artifacts in a project.
47
+ *
48
+ * @param projectPath - The root path of the project
49
+ * @returns Detection result with all found legacy artifacts
50
+ */
51
+ export declare function detectLegacyArtifacts(projectPath: string): Promise<LegacyDetectionResult>;
52
+ /**
53
+ * Detects legacy config files with OpenSpec markers.
54
+ * All config files with markers are candidates for update (marker removal only).
55
+ * Config files are NEVER deleted - they belong to the user's project root.
56
+ *
57
+ * @param projectPath - The root path of the project
58
+ * @returns Object with all files found and files to update
59
+ */
60
+ export declare function detectLegacyConfigFiles(projectPath: string): Promise<{
61
+ allFiles: string[];
62
+ filesToUpdate: string[];
63
+ }>;
64
+ /**
65
+ * Detects legacy slash command directories and files.
66
+ *
67
+ * @param projectPath - The root path of the project
68
+ * @returns Object with directories and individual files found
69
+ */
70
+ export declare function detectLegacySlashCommands(projectPath: string): Promise<{
71
+ directories: string[];
72
+ files: string[];
73
+ }>;
74
+ /**
75
+ * Detects legacy OpenSpec structure files (AGENTS.md and project.md).
76
+ *
77
+ * @param projectPath - The root path of the project
78
+ * @returns Object with detection results for structure files
79
+ */
80
+ export declare function detectLegacyStructureFiles(projectPath: string): Promise<{
81
+ hasOpenspecAgents: boolean;
82
+ hasProjectMd: boolean;
83
+ hasRootAgentsWithMarkers: boolean;
84
+ }>;
85
+ /**
86
+ * Checks if content contains OpenSpec markers.
87
+ *
88
+ * @param content - File content to check
89
+ * @returns True if both start and end markers are present
90
+ */
91
+ export declare function hasOpenSpecMarkers(content: string): boolean;
92
+ /**
93
+ * Checks if file content is 100% OpenSpec content (only markers and whitespace outside).
94
+ *
95
+ * @param content - File content to check
96
+ * @returns True if content outside markers is only whitespace
97
+ */
98
+ export declare function isOnlyOpenSpecContent(content: string): boolean;
99
+ /**
100
+ * Removes the OpenSpec marker block from file content.
101
+ * Only removes markers that are on their own lines (ignores inline mentions).
102
+ * Cleans up double blank lines that may result from removal.
103
+ *
104
+ * @param content - File content with OpenSpec markers
105
+ * @returns Content with marker block removed
106
+ */
107
+ export declare function removeMarkerBlock(content: string): string;
108
+ /**
109
+ * Result of cleanup operation
110
+ */
111
+ export interface CleanupResult {
112
+ /** Files that were deleted entirely */
113
+ deletedFiles: string[];
114
+ /** Files that had marker blocks removed */
115
+ modifiedFiles: string[];
116
+ /** Directories that were deleted */
117
+ deletedDirs: string[];
118
+ /** Whether project.md exists and needs manual migration */
119
+ projectMdNeedsMigration: boolean;
120
+ /** Error messages if any operations failed */
121
+ errors: string[];
122
+ }
123
+ /**
124
+ * Cleans up legacy OpenSpec artifacts from a project.
125
+ * Preserves openspec/project.md (shows migration hint instead of deleting).
126
+ *
127
+ * @param projectPath - The root path of the project
128
+ * @param detection - Detection result from detectLegacyArtifacts
129
+ * @returns Cleanup result with summary of actions taken
130
+ */
131
+ export declare function cleanupLegacyArtifacts(projectPath: string, detection: LegacyDetectionResult): Promise<CleanupResult>;
132
+ /**
133
+ * Generates a cleanup summary message for display.
134
+ *
135
+ * @param result - Cleanup result from cleanupLegacyArtifacts
136
+ * @returns Formatted summary string for console output
137
+ */
138
+ export declare function formatCleanupSummary(result: CleanupResult): string;
139
+ /**
140
+ * Generates a detection summary message for display before cleanup.
141
+ * Groups files by action type: removals, updates, and manual migration.
142
+ *
143
+ * @param detection - Detection result from detectLegacyArtifacts
144
+ * @returns Formatted summary string showing what was found
145
+ */
146
+ export declare function formatDetectionSummary(detection: LegacyDetectionResult): string;
147
+ /**
148
+ * Extract tool IDs from detected legacy artifacts.
149
+ * Uses LEGACY_SLASH_COMMAND_PATHS to map paths back to tool IDs.
150
+ *
151
+ * @param detection - Detection result from detectLegacyArtifacts
152
+ * @returns Array of tool IDs that had legacy artifacts
153
+ */
154
+ export declare function getToolsFromLegacyArtifacts(detection: LegacyDetectionResult): string[];
155
+ /**
156
+ * Generates a migration hint message for project.md.
157
+ * This is shown when project.md exists and needs manual migration to config.yaml.
158
+ *
159
+ * @returns Formatted migration hint string for console output
160
+ */
161
+ export declare function formatProjectMdMigrationHint(): string;
162
+ //# sourceMappingURL=legacy-cleanup.d.ts.map