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,42 @@
1
+ /**
2
+ * Update Command
3
+ *
4
+ * Refreshes OpenSpec skills and commands for configured tools.
5
+ * Supports smart update detection to skip updates when already current.
6
+ */
7
+ /**
8
+ * Options for the update command.
9
+ */
10
+ export interface UpdateCommandOptions {
11
+ /** Force update even when tools are up to date */
12
+ force?: boolean;
13
+ }
14
+ export declare class UpdateCommand {
15
+ private readonly force;
16
+ constructor(options?: UpdateCommandOptions);
17
+ execute(projectPath: string): Promise<void>;
18
+ /**
19
+ * Display message when all tools are up to date.
20
+ */
21
+ private displayUpToDateMessage;
22
+ /**
23
+ * Display the update plan showing which tools need updating.
24
+ */
25
+ private displayUpdatePlan;
26
+ /**
27
+ * Detect and handle legacy OpenSpec artifacts.
28
+ * Unlike init, update warns but continues if legacy files found in non-interactive mode.
29
+ * Returns array of tool IDs that were newly configured during legacy upgrade.
30
+ */
31
+ private handleLegacyCleanup;
32
+ /**
33
+ * Perform cleanup of legacy artifacts.
34
+ */
35
+ private performLegacyCleanup;
36
+ /**
37
+ * Upgrade legacy tools to new skills system.
38
+ * Returns array of tool IDs that were newly configured.
39
+ */
40
+ private upgradeLegacyTools;
41
+ }
42
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Update Command
3
+ *
4
+ * Refreshes OpenSpec skills and commands for configured tools.
5
+ * Supports smart update detection to skip updates when already current.
6
+ */
7
+ import path from 'path';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import { createRequire } from 'module';
11
+ import { FileSystemUtils } from '../utils/file-system.js';
12
+ import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
13
+ import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
14
+ import { getConfiguredTools, getAllToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, } from './shared/index.js';
15
+ import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, getToolsFromLegacyArtifacts, } from './legacy-cleanup.js';
16
+ import { isInteractive } from '../utils/interactive.js';
17
+ const require = createRequire(import.meta.url);
18
+ const { version: OPENSPEC_VERSION } = require('../../package.json');
19
+ export class UpdateCommand {
20
+ force;
21
+ constructor(options = {}) {
22
+ this.force = options.force ?? false;
23
+ }
24
+ async execute(projectPath) {
25
+ const resolvedProjectPath = path.resolve(projectPath);
26
+ const openspecPath = path.join(resolvedProjectPath, OPENSPEC_DIR_NAME);
27
+ // 1. Check openspec directory exists
28
+ if (!await FileSystemUtils.directoryExists(openspecPath)) {
29
+ throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`);
30
+ }
31
+ // 2. Detect and handle legacy artifacts + upgrade legacy tools to new skills
32
+ const newlyConfiguredTools = await this.handleLegacyCleanup(resolvedProjectPath);
33
+ // 3. Find configured tools
34
+ const configuredTools = getConfiguredTools(resolvedProjectPath);
35
+ if (configuredTools.length === 0 && newlyConfiguredTools.length === 0) {
36
+ console.log(chalk.yellow('No configured tools found.'));
37
+ console.log(chalk.dim('Run "openspec init" to set up tools.'));
38
+ return;
39
+ }
40
+ // 4. Check version status for all configured tools
41
+ const toolStatuses = getAllToolVersionStatus(resolvedProjectPath, OPENSPEC_VERSION);
42
+ // 5. Smart update detection
43
+ const toolsNeedingUpdate = toolStatuses.filter((s) => s.needsUpdate);
44
+ const toolsUpToDate = toolStatuses.filter((s) => !s.needsUpdate);
45
+ if (!this.force && toolsNeedingUpdate.length === 0) {
46
+ // All tools are up to date
47
+ this.displayUpToDateMessage(toolStatuses);
48
+ return;
49
+ }
50
+ // 6. Display update plan
51
+ if (this.force) {
52
+ console.log(`Force updating ${configuredTools.length} tool(s): ${configuredTools.join(', ')}`);
53
+ }
54
+ else {
55
+ this.displayUpdatePlan(toolsNeedingUpdate, toolsUpToDate);
56
+ }
57
+ console.log();
58
+ // 7. Prepare templates
59
+ const skillTemplates = getSkillTemplates();
60
+ const commandContents = getCommandContents();
61
+ // 8. Update tools (all if force, otherwise only those needing update)
62
+ const toolsToUpdate = this.force ? configuredTools : toolsNeedingUpdate.map((s) => s.toolId);
63
+ const updatedTools = [];
64
+ const failedTools = [];
65
+ for (const toolId of toolsToUpdate) {
66
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
67
+ if (!tool?.skillsDir)
68
+ continue;
69
+ const spinner = ora(`Updating ${tool.name}...`).start();
70
+ try {
71
+ const skillsDir = path.join(resolvedProjectPath, tool.skillsDir, 'skills');
72
+ // Update skill files
73
+ for (const { template, dirName } of skillTemplates) {
74
+ const skillDir = path.join(skillsDir, dirName);
75
+ const skillFile = path.join(skillDir, 'SKILL.md');
76
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION);
77
+ await FileSystemUtils.writeFile(skillFile, skillContent);
78
+ }
79
+ // Update commands
80
+ const adapter = CommandAdapterRegistry.get(tool.value);
81
+ if (adapter) {
82
+ const generatedCommands = generateCommands(commandContents, adapter);
83
+ for (const cmd of generatedCommands) {
84
+ const commandFile = path.join(resolvedProjectPath, cmd.path);
85
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
86
+ }
87
+ }
88
+ spinner.succeed(`Updated ${tool.name}`);
89
+ updatedTools.push(tool.name);
90
+ }
91
+ catch (error) {
92
+ spinner.fail(`Failed to update ${tool.name}`);
93
+ failedTools.push({
94
+ name: tool.name,
95
+ error: error instanceof Error ? error.message : String(error)
96
+ });
97
+ }
98
+ }
99
+ // 9. Summary
100
+ console.log();
101
+ if (updatedTools.length > 0) {
102
+ console.log(chalk.green(`✓ Updated: ${updatedTools.join(', ')} (v${OPENSPEC_VERSION})`));
103
+ }
104
+ if (failedTools.length > 0) {
105
+ console.log(chalk.red(`✗ Failed: ${failedTools.map(f => `${f.name} (${f.error})`).join(', ')}`));
106
+ }
107
+ // 10. Show onboarding message for newly configured tools from legacy upgrade
108
+ if (newlyConfiguredTools.length > 0) {
109
+ console.log();
110
+ console.log(chalk.bold('Getting started:'));
111
+ console.log(' /opsx:new Start a new change');
112
+ console.log(' /opsx:continue Create the next artifact');
113
+ console.log(' /opsx:apply Implement tasks');
114
+ console.log();
115
+ console.log(`Learn more: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec')}`);
116
+ }
117
+ console.log();
118
+ console.log(chalk.dim('Restart your IDE for changes to take effect.'));
119
+ }
120
+ /**
121
+ * Display message when all tools are up to date.
122
+ */
123
+ displayUpToDateMessage(toolStatuses) {
124
+ const toolNames = toolStatuses.map((s) => s.toolId);
125
+ console.log(chalk.green(`✓ All ${toolStatuses.length} tool(s) up to date (v${OPENSPEC_VERSION})`));
126
+ console.log(chalk.dim(` Tools: ${toolNames.join(', ')}`));
127
+ console.log();
128
+ console.log(chalk.dim('Use --force to refresh skills anyway.'));
129
+ }
130
+ /**
131
+ * Display the update plan showing which tools need updating.
132
+ */
133
+ displayUpdatePlan(needingUpdate, upToDate) {
134
+ const updates = needingUpdate.map((s) => {
135
+ const fromVersion = s.generatedByVersion ?? 'unknown';
136
+ return `${s.toolId} (${fromVersion} → ${OPENSPEC_VERSION})`;
137
+ });
138
+ console.log(`Updating ${needingUpdate.length} tool(s): ${updates.join(', ')}`);
139
+ if (upToDate.length > 0) {
140
+ const upToDateNames = upToDate.map((s) => s.toolId);
141
+ console.log(chalk.dim(`Already up to date: ${upToDateNames.join(', ')}`));
142
+ }
143
+ }
144
+ /**
145
+ * Detect and handle legacy OpenSpec artifacts.
146
+ * Unlike init, update warns but continues if legacy files found in non-interactive mode.
147
+ * Returns array of tool IDs that were newly configured during legacy upgrade.
148
+ */
149
+ async handleLegacyCleanup(projectPath) {
150
+ // Detect legacy artifacts
151
+ const detection = await detectLegacyArtifacts(projectPath);
152
+ if (!detection.hasLegacyArtifacts) {
153
+ return []; // No legacy artifacts found
154
+ }
155
+ // Show what was detected
156
+ console.log();
157
+ console.log(formatDetectionSummary(detection));
158
+ console.log();
159
+ const canPrompt = isInteractive();
160
+ if (this.force) {
161
+ // --force flag: proceed with cleanup automatically
162
+ await this.performLegacyCleanup(projectPath, detection);
163
+ // Then upgrade legacy tools to new skills
164
+ return this.upgradeLegacyTools(projectPath, detection, canPrompt);
165
+ }
166
+ if (!canPrompt) {
167
+ // Non-interactive mode without --force: warn and continue
168
+ // (Unlike init, update doesn't abort - user may just want to update skills)
169
+ console.log(chalk.yellow('⚠ Run with --force to auto-cleanup legacy files, or run interactively.'));
170
+ console.log();
171
+ return [];
172
+ }
173
+ // Interactive mode: prompt for confirmation
174
+ const { confirm } = await import('@inquirer/prompts');
175
+ const shouldCleanup = await confirm({
176
+ message: 'Upgrade and clean up legacy files?',
177
+ default: true,
178
+ });
179
+ if (shouldCleanup) {
180
+ await this.performLegacyCleanup(projectPath, detection);
181
+ // Then upgrade legacy tools to new skills
182
+ return this.upgradeLegacyTools(projectPath, detection, canPrompt);
183
+ }
184
+ else {
185
+ console.log(chalk.dim('Skipping legacy cleanup. Continuing with skill update...'));
186
+ console.log();
187
+ return [];
188
+ }
189
+ }
190
+ /**
191
+ * Perform cleanup of legacy artifacts.
192
+ */
193
+ async performLegacyCleanup(projectPath, detection) {
194
+ const spinner = ora('Cleaning up legacy files...').start();
195
+ const result = await cleanupLegacyArtifacts(projectPath, detection);
196
+ spinner.succeed('Legacy files cleaned up');
197
+ const summary = formatCleanupSummary(result);
198
+ if (summary) {
199
+ console.log();
200
+ console.log(summary);
201
+ }
202
+ console.log();
203
+ }
204
+ /**
205
+ * Upgrade legacy tools to new skills system.
206
+ * Returns array of tool IDs that were newly configured.
207
+ */
208
+ async upgradeLegacyTools(projectPath, detection, canPrompt) {
209
+ // Get tools that had legacy artifacts
210
+ const legacyTools = getToolsFromLegacyArtifacts(detection);
211
+ if (legacyTools.length === 0) {
212
+ return [];
213
+ }
214
+ // Get currently configured tools
215
+ const configuredTools = getConfiguredTools(projectPath);
216
+ const configuredSet = new Set(configuredTools);
217
+ // Filter to tools that aren't already configured
218
+ const unconfiguredLegacyTools = legacyTools.filter((t) => !configuredSet.has(t));
219
+ if (unconfiguredLegacyTools.length === 0) {
220
+ return [];
221
+ }
222
+ // Get valid tools (those with skillsDir)
223
+ const validToolIds = new Set(getToolsWithSkillsDir());
224
+ const validUnconfiguredTools = unconfiguredLegacyTools.filter((t) => validToolIds.has(t));
225
+ if (validUnconfiguredTools.length === 0) {
226
+ return [];
227
+ }
228
+ // Show what tools were detected from legacy artifacts
229
+ console.log(chalk.bold('Tools detected from legacy artifacts:'));
230
+ for (const toolId of validUnconfiguredTools) {
231
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
232
+ console.log(` • ${tool?.name || toolId}`);
233
+ }
234
+ console.log();
235
+ let selectedTools;
236
+ if (this.force || !canPrompt) {
237
+ // Non-interactive with --force: auto-select detected tools
238
+ selectedTools = validUnconfiguredTools;
239
+ console.log(`Setting up skills for: ${selectedTools.join(', ')}`);
240
+ }
241
+ else {
242
+ // Interactive mode: prompt for tool selection with detected tools pre-selected
243
+ const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
244
+ const sortedChoices = validUnconfiguredTools.map((toolId) => {
245
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
246
+ return {
247
+ name: tool?.name || toolId,
248
+ value: toolId,
249
+ configured: false,
250
+ preSelected: true, // Pre-select all detected legacy tools
251
+ };
252
+ });
253
+ selectedTools = await searchableMultiSelect({
254
+ message: 'Select tools to set up with the new skill system:',
255
+ pageSize: 15,
256
+ choices: sortedChoices,
257
+ validate: (_selected) => true, // Allow empty selection (user can skip)
258
+ });
259
+ if (selectedTools.length === 0) {
260
+ console.log(chalk.dim('Skipping tool setup.'));
261
+ console.log();
262
+ return [];
263
+ }
264
+ }
265
+ // Create skills for selected tools
266
+ const newlyConfigured = [];
267
+ const skillTemplates = getSkillTemplates();
268
+ const commandContents = getCommandContents();
269
+ for (const toolId of selectedTools) {
270
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
271
+ if (!tool?.skillsDir)
272
+ continue;
273
+ const spinner = ora(`Setting up ${tool.name}...`).start();
274
+ try {
275
+ const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
276
+ // Create skill files
277
+ for (const { template, dirName } of skillTemplates) {
278
+ const skillDir = path.join(skillsDir, dirName);
279
+ const skillFile = path.join(skillDir, 'SKILL.md');
280
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION);
281
+ await FileSystemUtils.writeFile(skillFile, skillContent);
282
+ }
283
+ // Create commands
284
+ const adapter = CommandAdapterRegistry.get(tool.value);
285
+ if (adapter) {
286
+ const generatedCommands = generateCommands(commandContents, adapter);
287
+ for (const cmd of generatedCommands) {
288
+ const commandFile = path.join(projectPath, cmd.path);
289
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
290
+ }
291
+ }
292
+ spinner.succeed(`Setup complete for ${tool.name}`);
293
+ newlyConfigured.push(toolId);
294
+ }
295
+ catch (error) {
296
+ spinner.fail(`Failed to set up ${tool.name}`);
297
+ console.log(chalk.red(` ${error instanceof Error ? error.message : String(error)}`));
298
+ }
299
+ }
300
+ if (newlyConfigured.length > 0) {
301
+ console.log();
302
+ }
303
+ return newlyConfigured;
304
+ }
305
+ }
306
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Validation threshold constants
3
+ */
4
+ export declare const MIN_WHY_SECTION_LENGTH = 50;
5
+ export declare const MIN_PURPOSE_LENGTH = 50;
6
+ export declare const MAX_WHY_SECTION_LENGTH = 1000;
7
+ export declare const MAX_REQUIREMENT_TEXT_LENGTH = 500;
8
+ export declare const MAX_DELTAS_PER_CHANGE = 10;
9
+ export declare const VALIDATION_MESSAGES: {
10
+ readonly SCENARIO_EMPTY: "Scenario text cannot be empty";
11
+ readonly REQUIREMENT_EMPTY: "Requirement text cannot be empty";
12
+ readonly REQUIREMENT_NO_SHALL: "Requirement must contain SHALL or MUST keyword";
13
+ readonly REQUIREMENT_NO_SCENARIOS: "Requirement must have at least one scenario";
14
+ readonly SPEC_NAME_EMPTY: "Spec name cannot be empty";
15
+ readonly SPEC_PURPOSE_EMPTY: "Purpose section cannot be empty";
16
+ readonly SPEC_NO_REQUIREMENTS: "Spec must have at least one requirement";
17
+ readonly CHANGE_NAME_EMPTY: "Change name cannot be empty";
18
+ readonly CHANGE_WHY_TOO_SHORT: "Why section must be at least 50 characters";
19
+ readonly CHANGE_WHY_TOO_LONG: "Why section should not exceed 1000 characters";
20
+ readonly CHANGE_WHAT_EMPTY: "What Changes section cannot be empty";
21
+ readonly CHANGE_NO_DELTAS: "Change must have at least one delta";
22
+ readonly CHANGE_TOO_MANY_DELTAS: "Consider splitting changes with more than 10 deltas";
23
+ readonly DELTA_SPEC_EMPTY: "Spec name cannot be empty";
24
+ readonly DELTA_DESCRIPTION_EMPTY: "Delta description cannot be empty";
25
+ readonly PURPOSE_TOO_BRIEF: "Purpose section is too brief (less than 50 characters)";
26
+ readonly REQUIREMENT_TOO_LONG: "Requirement text is very long (>500 characters). Consider breaking it down.";
27
+ readonly DELTA_DESCRIPTION_TOO_BRIEF: "Delta description is too brief";
28
+ readonly DELTA_MISSING_REQUIREMENTS: "Delta should include requirements";
29
+ readonly GUIDE_NO_DELTAS: "No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one \"#### Scenario:\" block. Tip: run \"openspec change show <change-id> --json --deltas-only\" to inspect parsed deltas.";
30
+ readonly GUIDE_MISSING_SPEC_SECTIONS: "Missing required sections. Expected headers: \"## Purpose\" and \"## Requirements\". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...";
31
+ readonly GUIDE_MISSING_CHANGE_SECTIONS: "Missing required sections. Expected headers: \"## Why\" and \"## What Changes\". Ensure deltas are documented in specs/ using delta headers.";
32
+ readonly GUIDE_SCENARIO_FORMAT: "Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...";
33
+ };
34
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Validation threshold constants
3
+ */
4
+ // Minimum character lengths
5
+ export const MIN_WHY_SECTION_LENGTH = 50;
6
+ export const MIN_PURPOSE_LENGTH = 50;
7
+ // Maximum character/item limits
8
+ export const MAX_WHY_SECTION_LENGTH = 1000;
9
+ export const MAX_REQUIREMENT_TEXT_LENGTH = 500;
10
+ export const MAX_DELTAS_PER_CHANGE = 10;
11
+ // Validation messages
12
+ export const VALIDATION_MESSAGES = {
13
+ // Required content
14
+ SCENARIO_EMPTY: 'Scenario text cannot be empty',
15
+ REQUIREMENT_EMPTY: 'Requirement text cannot be empty',
16
+ REQUIREMENT_NO_SHALL: 'Requirement must contain SHALL or MUST keyword',
17
+ REQUIREMENT_NO_SCENARIOS: 'Requirement must have at least one scenario',
18
+ SPEC_NAME_EMPTY: 'Spec name cannot be empty',
19
+ SPEC_PURPOSE_EMPTY: 'Purpose section cannot be empty',
20
+ SPEC_NO_REQUIREMENTS: 'Spec must have at least one requirement',
21
+ CHANGE_NAME_EMPTY: 'Change name cannot be empty',
22
+ CHANGE_WHY_TOO_SHORT: `Why section must be at least ${MIN_WHY_SECTION_LENGTH} characters`,
23
+ CHANGE_WHY_TOO_LONG: `Why section should not exceed ${MAX_WHY_SECTION_LENGTH} characters`,
24
+ CHANGE_WHAT_EMPTY: 'What Changes section cannot be empty',
25
+ CHANGE_NO_DELTAS: 'Change must have at least one delta',
26
+ CHANGE_TOO_MANY_DELTAS: `Consider splitting changes with more than ${MAX_DELTAS_PER_CHANGE} deltas`,
27
+ DELTA_SPEC_EMPTY: 'Spec name cannot be empty',
28
+ DELTA_DESCRIPTION_EMPTY: 'Delta description cannot be empty',
29
+ // Warnings
30
+ PURPOSE_TOO_BRIEF: `Purpose section is too brief (less than ${MIN_PURPOSE_LENGTH} characters)`,
31
+ REQUIREMENT_TOO_LONG: `Requirement text is very long (>${MAX_REQUIREMENT_TEXT_LENGTH} characters). Consider breaking it down.`,
32
+ DELTA_DESCRIPTION_TOO_BRIEF: 'Delta description is too brief',
33
+ DELTA_MISSING_REQUIREMENTS: 'Delta should include requirements',
34
+ // Guidance snippets (appended to primary messages for remediation)
35
+ GUIDE_NO_DELTAS: 'No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "openspec change show <change-id> --json --deltas-only" to inspect parsed deltas.',
36
+ GUIDE_MISSING_SPEC_SECTIONS: 'Missing required sections. Expected headers: "## Purpose" and "## Requirements". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...',
37
+ GUIDE_MISSING_CHANGE_SECTIONS: 'Missing required sections. Expected headers: "## Why" and "## What Changes". Ensure deltas are documented in specs/ using delta headers.',
38
+ GUIDE_SCENARIO_FORMAT: 'Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...',
39
+ };
40
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,18 @@
1
+ export type ValidationLevel = 'ERROR' | 'WARNING' | 'INFO';
2
+ export interface ValidationIssue {
3
+ level: ValidationLevel;
4
+ path: string;
5
+ message: string;
6
+ line?: number;
7
+ column?: number;
8
+ }
9
+ export interface ValidationReport {
10
+ valid: boolean;
11
+ issues: ValidationIssue[];
12
+ summary: {
13
+ errors: number;
14
+ warnings: number;
15
+ info: number;
16
+ };
17
+ }
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,33 @@
1
+ import { ValidationReport } from './types.js';
2
+ export declare class Validator {
3
+ private strictMode;
4
+ constructor(strictMode?: boolean);
5
+ validateSpec(filePath: string): Promise<ValidationReport>;
6
+ /**
7
+ * Validate spec content from a string (used for pre-write validation of rebuilt specs)
8
+ */
9
+ validateSpecContent(specName: string, content: string): Promise<ValidationReport>;
10
+ validateChange(filePath: string): Promise<ValidationReport>;
11
+ /**
12
+ * Validate delta-formatted spec files under a change directory.
13
+ * Enforces:
14
+ * - At least one delta across all files
15
+ * - ADDED/MODIFIED: each requirement has SHALL/MUST and at least one scenario
16
+ * - REMOVED: names only; no scenario/description required
17
+ * - RENAMED: pairs well-formed
18
+ * - No duplicates within sections; no cross-section conflicts per spec
19
+ */
20
+ validateChangeDeltaSpecs(changeDir: string): Promise<ValidationReport>;
21
+ private convertZodErrors;
22
+ private applySpecRules;
23
+ private applyChangeRules;
24
+ private enrichTopLevelError;
25
+ private extractNameFromPath;
26
+ private createReport;
27
+ isValid(report: ValidationReport): boolean;
28
+ private extractRequirementText;
29
+ private containsShallOrMust;
30
+ private countScenarios;
31
+ private formatSectionList;
32
+ }
33
+ //# sourceMappingURL=validator.d.ts.map