@dynamicworks/br-openspec 1.3.1

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 (291) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +210 -0
  3. package/README.pt-BR.md +212 -0
  4. package/bin/openspec.js +3 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +484 -0
  7. package/dist/commands/change.d.ts +35 -0
  8. package/dist/commands/change.js +278 -0
  9. package/dist/commands/completion.d.ts +72 -0
  10. package/dist/commands/completion.js +258 -0
  11. package/dist/commands/config.d.ts +36 -0
  12. package/dist/commands/config.js +553 -0
  13. package/dist/commands/feedback.d.ts +9 -0
  14. package/dist/commands/feedback.js +184 -0
  15. package/dist/commands/schema.d.ts +6 -0
  16. package/dist/commands/schema.js +869 -0
  17. package/dist/commands/show.d.ts +14 -0
  18. package/dist/commands/show.js +133 -0
  19. package/dist/commands/spec.d.ts +15 -0
  20. package/dist/commands/spec.js +226 -0
  21. package/dist/commands/tools.d.ts +11 -0
  22. package/dist/commands/tools.js +252 -0
  23. package/dist/commands/validate.d.ts +24 -0
  24. package/dist/commands/validate.js +295 -0
  25. package/dist/commands/workflow/index.d.ts +17 -0
  26. package/dist/commands/workflow/index.js +12 -0
  27. package/dist/commands/workflow/instructions.d.ts +29 -0
  28. package/dist/commands/workflow/instructions.js +328 -0
  29. package/dist/commands/workflow/new-change.d.ts +11 -0
  30. package/dist/commands/workflow/new-change.js +44 -0
  31. package/dist/commands/workflow/schemas.d.ts +10 -0
  32. package/dist/commands/workflow/schemas.js +35 -0
  33. package/dist/commands/workflow/shared.d.ts +57 -0
  34. package/dist/commands/workflow/shared.js +117 -0
  35. package/dist/commands/workflow/status.d.ts +14 -0
  36. package/dist/commands/workflow/status.js +76 -0
  37. package/dist/commands/workflow/templates.d.ts +16 -0
  38. package/dist/commands/workflow/templates.js +70 -0
  39. package/dist/core/archive.d.ts +11 -0
  40. package/dist/core/archive.js +322 -0
  41. package/dist/core/artifact-graph/graph.d.ts +56 -0
  42. package/dist/core/artifact-graph/graph.js +141 -0
  43. package/dist/core/artifact-graph/index.d.ts +8 -0
  44. package/dist/core/artifact-graph/index.js +14 -0
  45. package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
  46. package/dist/core/artifact-graph/instruction-loader.js +217 -0
  47. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  48. package/dist/core/artifact-graph/outputs.js +39 -0
  49. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  50. package/dist/core/artifact-graph/resolver.js +258 -0
  51. package/dist/core/artifact-graph/schema.d.ts +13 -0
  52. package/dist/core/artifact-graph/schema.js +108 -0
  53. package/dist/core/artifact-graph/state.d.ts +12 -0
  54. package/dist/core/artifact-graph/state.js +31 -0
  55. package/dist/core/artifact-graph/types.d.ts +45 -0
  56. package/dist/core/artifact-graph/types.js +43 -0
  57. package/dist/core/available-tools.d.ts +17 -0
  58. package/dist/core/available-tools.js +43 -0
  59. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  60. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  61. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  62. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  63. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  64. package/dist/core/command-generation/adapters/auggie.js +27 -0
  65. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  66. package/dist/core/command-generation/adapters/bob.js +45 -0
  67. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  68. package/dist/core/command-generation/adapters/claude.js +50 -0
  69. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  70. package/dist/core/command-generation/adapters/cline.js +27 -0
  71. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  72. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  73. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  74. package/dist/core/command-generation/adapters/codex.js +39 -0
  75. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  76. package/dist/core/command-generation/adapters/continue.js +28 -0
  77. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  78. package/dist/core/command-generation/adapters/costrict.js +27 -0
  79. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  80. package/dist/core/command-generation/adapters/crush.js +30 -0
  81. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  82. package/dist/core/command-generation/adapters/cursor.js +44 -0
  83. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  84. package/dist/core/command-generation/adapters/factory.js +27 -0
  85. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  86. package/dist/core/command-generation/adapters/gemini.js +26 -0
  87. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  88. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  89. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  90. package/dist/core/command-generation/adapters/iflow.js +29 -0
  91. package/dist/core/command-generation/adapters/index.d.ts +32 -0
  92. package/dist/core/command-generation/adapters/index.js +32 -0
  93. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  94. package/dist/core/command-generation/adapters/junie.js +26 -0
  95. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  96. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  97. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  98. package/dist/core/command-generation/adapters/kiro.js +26 -0
  99. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  100. package/dist/core/command-generation/adapters/lingma.js +30 -0
  101. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  102. package/dist/core/command-generation/adapters/opencode.js +29 -0
  103. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  104. package/dist/core/command-generation/adapters/pi.js +55 -0
  105. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  106. package/dist/core/command-generation/adapters/qoder.js +30 -0
  107. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  108. package/dist/core/command-generation/adapters/qwen.js +26 -0
  109. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  110. package/dist/core/command-generation/adapters/roocode.js +27 -0
  111. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  112. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  113. package/dist/core/command-generation/generator.d.ts +21 -0
  114. package/dist/core/command-generation/generator.js +27 -0
  115. package/dist/core/command-generation/index.d.ts +22 -0
  116. package/dist/core/command-generation/index.js +24 -0
  117. package/dist/core/command-generation/registry.d.ts +36 -0
  118. package/dist/core/command-generation/registry.js +98 -0
  119. package/dist/core/command-generation/types.d.ts +56 -0
  120. package/dist/core/command-generation/types.js +8 -0
  121. package/dist/core/completions/command-registry.d.ts +7 -0
  122. package/dist/core/completions/command-registry.js +462 -0
  123. package/dist/core/completions/completion-provider.d.ts +60 -0
  124. package/dist/core/completions/completion-provider.js +102 -0
  125. package/dist/core/completions/factory.d.ts +64 -0
  126. package/dist/core/completions/factory.js +75 -0
  127. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  128. package/dist/core/completions/generators/bash-generator.js +174 -0
  129. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  130. package/dist/core/completions/generators/fish-generator.js +157 -0
  131. package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
  132. package/dist/core/completions/generators/powershell-generator.js +208 -0
  133. package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
  134. package/dist/core/completions/generators/zsh-generator.js +250 -0
  135. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  136. package/dist/core/completions/installers/bash-installer.js +319 -0
  137. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  138. package/dist/core/completions/installers/fish-installer.js +143 -0
  139. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  140. package/dist/core/completions/installers/powershell-installer.js +400 -0
  141. package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
  142. package/dist/core/completions/installers/zsh-installer.js +450 -0
  143. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  144. package/dist/core/completions/templates/bash-templates.js +24 -0
  145. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  146. package/dist/core/completions/templates/fish-templates.js +39 -0
  147. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  148. package/dist/core/completions/templates/powershell-templates.js +25 -0
  149. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  150. package/dist/core/completions/templates/zsh-templates.js +36 -0
  151. package/dist/core/completions/types.d.ts +79 -0
  152. package/dist/core/completions/types.js +2 -0
  153. package/dist/core/config-prompts.d.ts +9 -0
  154. package/dist/core/config-prompts.js +34 -0
  155. package/dist/core/config-schema.d.ts +86 -0
  156. package/dist/core/config-schema.js +213 -0
  157. package/dist/core/config.d.ts +18 -0
  158. package/dist/core/config.js +38 -0
  159. package/dist/core/converters/json-converter.d.ts +6 -0
  160. package/dist/core/converters/json-converter.js +51 -0
  161. package/dist/core/global-config.d.ts +44 -0
  162. package/dist/core/global-config.js +125 -0
  163. package/dist/core/index.d.ts +2 -0
  164. package/dist/core/index.js +3 -0
  165. package/dist/core/init.d.ts +37 -0
  166. package/dist/core/init.js +549 -0
  167. package/dist/core/is-project-initialized.d.ts +12 -0
  168. package/dist/core/is-project-initialized.js +18 -0
  169. package/dist/core/legacy-cleanup.d.ts +162 -0
  170. package/dist/core/legacy-cleanup.js +515 -0
  171. package/dist/core/list.d.ts +9 -0
  172. package/dist/core/list.js +172 -0
  173. package/dist/core/migration.d.ts +23 -0
  174. package/dist/core/migration.js +109 -0
  175. package/dist/core/parsers/change-parser.d.ts +13 -0
  176. package/dist/core/parsers/change-parser.js +197 -0
  177. package/dist/core/parsers/markdown-parser.d.ts +26 -0
  178. package/dist/core/parsers/markdown-parser.js +228 -0
  179. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  180. package/dist/core/parsers/requirement-blocks.js +201 -0
  181. package/dist/core/parsers/spec-structure.d.ts +9 -0
  182. package/dist/core/parsers/spec-structure.js +88 -0
  183. package/dist/core/profile-sync-drift.d.ts +38 -0
  184. package/dist/core/profile-sync-drift.js +200 -0
  185. package/dist/core/profiles.d.ts +26 -0
  186. package/dist/core/profiles.js +40 -0
  187. package/dist/core/project-config.d.ts +64 -0
  188. package/dist/core/project-config.js +224 -0
  189. package/dist/core/schemas/base.schema.d.ts +13 -0
  190. package/dist/core/schemas/base.schema.js +13 -0
  191. package/dist/core/schemas/change.schema.d.ts +73 -0
  192. package/dist/core/schemas/change.schema.js +31 -0
  193. package/dist/core/schemas/index.d.ts +4 -0
  194. package/dist/core/schemas/index.js +4 -0
  195. package/dist/core/schemas/spec.schema.d.ts +18 -0
  196. package/dist/core/schemas/spec.schema.js +15 -0
  197. package/dist/core/shared/index.d.ts +8 -0
  198. package/dist/core/shared/index.js +8 -0
  199. package/dist/core/shared/skill-generation.d.ts +49 -0
  200. package/dist/core/shared/skill-generation.js +96 -0
  201. package/dist/core/shared/tool-detection.d.ts +71 -0
  202. package/dist/core/shared/tool-detection.js +158 -0
  203. package/dist/core/specs-apply.d.ts +73 -0
  204. package/dist/core/specs-apply.js +393 -0
  205. package/dist/core/styles/palette.d.ts +7 -0
  206. package/dist/core/styles/palette.js +8 -0
  207. package/dist/core/templates/index.d.ts +8 -0
  208. package/dist/core/templates/index.js +9 -0
  209. package/dist/core/templates/skill-templates.d.ts +20 -0
  210. package/dist/core/templates/skill-templates.js +19 -0
  211. package/dist/core/templates/types.d.ts +19 -0
  212. package/dist/core/templates/types.js +5 -0
  213. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  214. package/dist/core/templates/workflows/apply-change.js +308 -0
  215. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  216. package/dist/core/templates/workflows/archive-change.js +271 -0
  217. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  218. package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
  219. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  220. package/dist/core/templates/workflows/continue-change.js +232 -0
  221. package/dist/core/templates/workflows/explore.d.ts +10 -0
  222. package/dist/core/templates/workflows/explore.js +463 -0
  223. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  224. package/dist/core/templates/workflows/feedback.js +108 -0
  225. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  226. package/dist/core/templates/workflows/ff-change.js +198 -0
  227. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  228. package/dist/core/templates/workflows/new-change.js +21 -0
  229. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  230. package/dist/core/templates/workflows/onboard.js +21 -0
  231. package/dist/core/templates/workflows/propose.d.ts +10 -0
  232. package/dist/core/templates/workflows/propose.js +216 -0
  233. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  234. package/dist/core/templates/workflows/sync-specs.js +272 -0
  235. package/dist/core/templates/workflows/upstream-sync.d.ts +10 -0
  236. package/dist/core/templates/workflows/upstream-sync.js +116 -0
  237. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  238. package/dist/core/templates/workflows/verify-change.js +21 -0
  239. package/dist/core/tools-manager.d.ts +56 -0
  240. package/dist/core/tools-manager.js +215 -0
  241. package/dist/core/update.d.ts +77 -0
  242. package/dist/core/update.js +538 -0
  243. package/dist/core/validation/constants.d.ts +34 -0
  244. package/dist/core/validation/constants.js +40 -0
  245. package/dist/core/validation/types.d.ts +18 -0
  246. package/dist/core/validation/types.js +2 -0
  247. package/dist/core/validation/validator.d.ts +33 -0
  248. package/dist/core/validation/validator.js +419 -0
  249. package/dist/core/view.d.ts +8 -0
  250. package/dist/core/view.js +169 -0
  251. package/dist/index.d.ts +3 -0
  252. package/dist/index.js +3 -0
  253. package/dist/messages/index.d.ts +867 -0
  254. package/dist/messages/index.js +1960 -0
  255. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  256. package/dist/prompts/searchable-multi-select.js +160 -0
  257. package/dist/telemetry/config.d.ts +38 -0
  258. package/dist/telemetry/config.js +136 -0
  259. package/dist/telemetry/index.d.ts +31 -0
  260. package/dist/telemetry/index.js +165 -0
  261. package/dist/ui/ascii-patterns.d.ts +16 -0
  262. package/dist/ui/ascii-patterns.js +133 -0
  263. package/dist/ui/welcome-screen.d.ts +10 -0
  264. package/dist/ui/welcome-screen.js +147 -0
  265. package/dist/utils/change-metadata.d.ts +51 -0
  266. package/dist/utils/change-metadata.js +147 -0
  267. package/dist/utils/change-utils.d.ts +62 -0
  268. package/dist/utils/change-utils.js +121 -0
  269. package/dist/utils/command-references.d.ts +18 -0
  270. package/dist/utils/command-references.js +20 -0
  271. package/dist/utils/file-system.d.ts +41 -0
  272. package/dist/utils/file-system.js +302 -0
  273. package/dist/utils/index.d.ts +6 -0
  274. package/dist/utils/index.js +9 -0
  275. package/dist/utils/interactive.d.ts +18 -0
  276. package/dist/utils/interactive.js +21 -0
  277. package/dist/utils/item-discovery.d.ts +4 -0
  278. package/dist/utils/item-discovery.js +72 -0
  279. package/dist/utils/match.d.ts +3 -0
  280. package/dist/utils/match.js +22 -0
  281. package/dist/utils/shell-detection.d.ts +20 -0
  282. package/dist/utils/shell-detection.js +41 -0
  283. package/dist/utils/task-progress.d.ts +8 -0
  284. package/dist/utils/task-progress.js +37 -0
  285. package/package.json +84 -0
  286. package/schemas/spec-driven/schema.yaml +153 -0
  287. package/schemas/spec-driven/templates/design.md +19 -0
  288. package/schemas/spec-driven/templates/proposal.md +23 -0
  289. package/schemas/spec-driven/templates/spec.md +8 -0
  290. package/schemas/spec-driven/templates/tasks.md +9 -0
  291. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,538 @@
1
+ /**
2
+ * Update Command
3
+ *
4
+ * Refreshes BR-OpenSpec skills and commands for configured tools.
5
+ * Supports profile-aware updates, delivery changes, migration, and smart update detection.
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 { transformToHyphenCommands } from '../utils/command-references.js';
14
+ import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
15
+ import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
16
+ import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, } from './shared/index.js';
17
+ import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, getToolsFromLegacyArtifacts, } from './legacy-cleanup.js';
18
+ import { isInteractive } from '../utils/interactive.js';
19
+ import { getGlobalConfig } from './global-config.js';
20
+ import { UPDATE_MESSAGES } from '../messages/index.js';
21
+ import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
22
+ import { getAvailableTools } from './available-tools.js';
23
+ import { WORKFLOW_TO_SKILL_DIR, getCommandConfiguredTools, getConfiguredToolsForProfileSync, getToolsNeedingProfileSync, } from './profile-sync-drift.js';
24
+ import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, } from './migration.js';
25
+ const require = createRequire(import.meta.url);
26
+ const { version: OPENSPEC_VERSION } = require('../../package.json');
27
+ /**
28
+ * Scans installed workflow artifacts (skills and managed commands) across all configured tools.
29
+ * Returns the union of detected workflow IDs that match ALL_WORKFLOWS.
30
+ *
31
+ * Wrapper around the shared migration module's scanInstalledWorkflows that accepts tool IDs.
32
+ */
33
+ export function scanInstalledWorkflows(projectPath, toolIds) {
34
+ const tools = toolIds
35
+ .map((id) => AI_TOOLS.find((t) => t.value === id))
36
+ .filter((t) => t != null);
37
+ return scanInstalledWorkflowsShared(projectPath, tools);
38
+ }
39
+ export class UpdateCommand {
40
+ force;
41
+ constructor(options = {}) {
42
+ this.force = options.force ?? false;
43
+ }
44
+ async execute(projectPath) {
45
+ const resolvedProjectPath = path.resolve(projectPath);
46
+ const openspecPath = path.join(resolvedProjectPath, OPENSPEC_DIR_NAME);
47
+ // 1. Check openspec directory exists
48
+ if (!await FileSystemUtils.directoryExists(openspecPath)) {
49
+ throw new Error(UPDATE_MESSAGES.noOpenspecDir);
50
+ }
51
+ // 2. Perform one-time migration if needed before any legacy upgrade generation.
52
+ // Use detected tool directories to preserve existing opsx skills/commands.
53
+ const detectedTools = getAvailableTools(resolvedProjectPath);
54
+ migrateIfNeededShared(resolvedProjectPath, detectedTools);
55
+ // 3. Read global config for profile/delivery
56
+ const globalConfig = getGlobalConfig();
57
+ const profile = globalConfig.profile ?? 'core';
58
+ const delivery = globalConfig.delivery ?? 'both';
59
+ const profileWorkflows = getProfileWorkflows(profile, globalConfig.workflows);
60
+ const desiredWorkflows = profileWorkflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
61
+ const shouldGenerateSkills = delivery !== 'commands';
62
+ const shouldGenerateCommands = delivery !== 'skills';
63
+ // 4. Detect and handle legacy artifacts + upgrade legacy tools using effective config
64
+ const newlyConfiguredTools = await this.handleLegacyCleanup(resolvedProjectPath, desiredWorkflows, delivery);
65
+ // 5. Find configured tools
66
+ const configuredTools = getConfiguredToolsForProfileSync(resolvedProjectPath);
67
+ if (configuredTools.length === 0 && newlyConfiguredTools.length === 0) {
68
+ console.log(chalk.yellow(UPDATE_MESSAGES.noConfiguredTools));
69
+ console.log(chalk.dim(UPDATE_MESSAGES.runInitHint));
70
+ return;
71
+ }
72
+ // 6. Check version status for all configured tools
73
+ const commandConfiguredTools = getCommandConfiguredTools(resolvedProjectPath);
74
+ const commandConfiguredSet = new Set(commandConfiguredTools);
75
+ const toolStatuses = configuredTools.map((toolId) => {
76
+ const status = getToolVersionStatus(resolvedProjectPath, toolId, OPENSPEC_VERSION);
77
+ if (!status.configured && commandConfiguredSet.has(toolId)) {
78
+ return { ...status, configured: true };
79
+ }
80
+ return status;
81
+ });
82
+ const statusByTool = new Map(toolStatuses.map((status) => [status.toolId, status]));
83
+ // 7. Smart update detection
84
+ const toolsNeedingVersionUpdate = toolStatuses
85
+ .filter((s) => s.needsUpdate)
86
+ .map((s) => s.toolId);
87
+ const toolsNeedingConfigSync = getToolsNeedingProfileSync(resolvedProjectPath, desiredWorkflows, delivery, configuredTools);
88
+ const toolsToUpdateSet = new Set([
89
+ ...toolsNeedingVersionUpdate,
90
+ ...toolsNeedingConfigSync,
91
+ ]);
92
+ const toolsUpToDate = toolStatuses.filter((s) => !toolsToUpdateSet.has(s.toolId));
93
+ if (!this.force && toolsToUpdateSet.size === 0) {
94
+ // All tools are up to date
95
+ this.displayUpToDateMessage(toolStatuses);
96
+ // Still check for new tool directories and extra workflows
97
+ this.detectNewTools(resolvedProjectPath, configuredTools);
98
+ this.displayExtraWorkflowsNote(resolvedProjectPath, configuredTools, desiredWorkflows);
99
+ return;
100
+ }
101
+ // 8. Display update plan
102
+ if (this.force) {
103
+ console.log(UPDATE_MESSAGES.forceUpdating(configuredTools.length, configuredTools.join(', ')));
104
+ }
105
+ else {
106
+ this.displayUpdatePlan([...toolsToUpdateSet], statusByTool, toolsUpToDate);
107
+ }
108
+ console.log();
109
+ // 9. Determine what to generate based on delivery
110
+ const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
111
+ const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
112
+ // 10. Update tools (all if force, otherwise only those needing update)
113
+ const toolsToUpdate = this.force ? configuredTools : [...toolsToUpdateSet];
114
+ const updatedTools = [];
115
+ const failedTools = [];
116
+ let removedCommandCount = 0;
117
+ let removedSkillCount = 0;
118
+ let removedDeselectedCommandCount = 0;
119
+ let removedDeselectedSkillCount = 0;
120
+ for (const toolId of toolsToUpdate) {
121
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
122
+ if (!tool?.skillsDir)
123
+ continue;
124
+ const spinner = ora(UPDATE_MESSAGES.updatingTool(tool.name)).start();
125
+ try {
126
+ const skillsDir = path.join(resolvedProjectPath, tool.skillsDir, 'skills');
127
+ // Generate skill files if delivery includes skills
128
+ if (shouldGenerateSkills) {
129
+ for (const { template, dirName } of skillTemplates) {
130
+ const skillDir = path.join(skillsDir, dirName);
131
+ const skillFile = path.join(skillDir, 'SKILL.md');
132
+ // Use hyphen-based command references for OpenCode
133
+ const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
134
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
135
+ await FileSystemUtils.writeFile(skillFile, skillContent);
136
+ }
137
+ removedDeselectedSkillCount += await this.removeUnselectedSkillDirs(skillsDir, desiredWorkflows);
138
+ }
139
+ // Delete skill directories if delivery is commands-only
140
+ if (!shouldGenerateSkills) {
141
+ removedSkillCount += await this.removeSkillDirs(skillsDir);
142
+ }
143
+ // Generate commands if delivery includes commands
144
+ if (shouldGenerateCommands) {
145
+ const adapter = CommandAdapterRegistry.get(tool.value);
146
+ if (adapter) {
147
+ const generatedCommands = generateCommands(commandContents, adapter);
148
+ for (const cmd of generatedCommands) {
149
+ const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(resolvedProjectPath, cmd.path);
150
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
151
+ }
152
+ removedDeselectedCommandCount += await this.removeUnselectedCommandFiles(resolvedProjectPath, toolId, desiredWorkflows);
153
+ }
154
+ }
155
+ // Delete command files if delivery is skills-only
156
+ if (!shouldGenerateCommands) {
157
+ removedCommandCount += await this.removeCommandFiles(resolvedProjectPath, toolId);
158
+ }
159
+ spinner.succeed(UPDATE_MESSAGES.updatedTool(tool.name));
160
+ updatedTools.push(tool.name);
161
+ }
162
+ catch (error) {
163
+ spinner.fail(UPDATE_MESSAGES.failedToUpdate(tool.name));
164
+ failedTools.push({
165
+ name: tool.name,
166
+ error: error instanceof Error ? error.message : String(error)
167
+ });
168
+ }
169
+ }
170
+ // 11. Summary
171
+ console.log();
172
+ if (updatedTools.length > 0) {
173
+ console.log(chalk.green(UPDATE_MESSAGES.updated(updatedTools.join(', '), OPENSPEC_VERSION)));
174
+ }
175
+ if (failedTools.length > 0) {
176
+ console.log(chalk.red(UPDATE_MESSAGES.failed(failedTools.map(f => `${f.name} (${f.error})`).join(', '))));
177
+ }
178
+ if (removedCommandCount > 0) {
179
+ console.log(chalk.dim(UPDATE_MESSAGES.removedCommands(removedCommandCount)));
180
+ }
181
+ if (removedSkillCount > 0) {
182
+ console.log(chalk.dim(UPDATE_MESSAGES.removedSkills(removedSkillCount)));
183
+ }
184
+ if (removedDeselectedCommandCount > 0) {
185
+ console.log(chalk.dim(UPDATE_MESSAGES.removedDeselectedCommands(removedDeselectedCommandCount)));
186
+ }
187
+ if (removedDeselectedSkillCount > 0) {
188
+ console.log(chalk.dim(UPDATE_MESSAGES.removedDeselectedSkills(removedDeselectedSkillCount)));
189
+ }
190
+ // 12. Show onboarding message for newly configured tools from legacy upgrade
191
+ if (newlyConfiguredTools.length > 0) {
192
+ console.log();
193
+ console.log(chalk.bold(UPDATE_MESSAGES.gettingStarted));
194
+ console.log(UPDATE_MESSAGES.cmdNew);
195
+ console.log(UPDATE_MESSAGES.cmdContinue);
196
+ console.log(UPDATE_MESSAGES.cmdApply);
197
+ console.log();
198
+ console.log(UPDATE_MESSAGES.learnMore(chalk.cyan('https://github.com/dynamicworks-com-br/BR-OpenSpec')));
199
+ }
200
+ const configuredAndNewTools = [...new Set([...configuredTools, ...newlyConfiguredTools])];
201
+ // 13. Detect new tool directories not currently configured
202
+ this.detectNewTools(resolvedProjectPath, configuredAndNewTools);
203
+ // 14. Display note about extra workflows not in profile
204
+ this.displayExtraWorkflowsNote(resolvedProjectPath, configuredAndNewTools, desiredWorkflows);
205
+ // 15. List affected tools
206
+ if (updatedTools.length > 0) {
207
+ const toolDisplayNames = updatedTools;
208
+ console.log(chalk.dim(UPDATE_MESSAGES.toolsList(toolDisplayNames.join(', '))));
209
+ }
210
+ console.log();
211
+ console.log(chalk.dim(UPDATE_MESSAGES.restartIDE));
212
+ }
213
+ /**
214
+ * Display message when all tools are up to date.
215
+ */
216
+ displayUpToDateMessage(toolStatuses) {
217
+ const toolNames = toolStatuses.map((s) => s.toolId);
218
+ console.log(chalk.green(UPDATE_MESSAGES.allUpToDate(toolStatuses.length, OPENSPEC_VERSION)));
219
+ console.log(chalk.dim(UPDATE_MESSAGES.toolsList(toolNames.join(', '))));
220
+ console.log();
221
+ console.log(chalk.dim(UPDATE_MESSAGES.useForceHint));
222
+ }
223
+ /**
224
+ * Display the update plan showing which tools need updating.
225
+ */
226
+ displayUpdatePlan(toolsToUpdate, statusByTool, upToDate) {
227
+ const updates = toolsToUpdate.map((toolId) => {
228
+ const status = statusByTool.get(toolId);
229
+ if (status?.needsUpdate) {
230
+ const fromVersion = status.generatedByVersion ?? 'unknown';
231
+ return `${status.toolId} (${fromVersion} → ${OPENSPEC_VERSION})`;
232
+ }
233
+ return `${toolId} (config sync)`;
234
+ });
235
+ console.log(UPDATE_MESSAGES.updatingPlan(toolsToUpdate.length, updates.join(', ')));
236
+ if (upToDate.length > 0) {
237
+ const upToDateNames = upToDate.map((s) => s.toolId);
238
+ console.log(chalk.dim(UPDATE_MESSAGES.alreadyUpToDate(upToDateNames.join(', '))));
239
+ }
240
+ }
241
+ /**
242
+ * Detects new tool directories that aren't currently configured and displays a hint.
243
+ */
244
+ detectNewTools(projectPath, configuredTools) {
245
+ const availableTools = getAvailableTools(projectPath);
246
+ const configuredSet = new Set(configuredTools);
247
+ const newTools = availableTools.filter((t) => !configuredSet.has(t.value));
248
+ if (newTools.length > 0) {
249
+ const newToolNames = newTools.map((tool) => tool.name);
250
+ const isSingleTool = newToolNames.length === 1;
251
+ const toolNoun = isSingleTool ? UPDATE_MESSAGES.toolNoun : UPDATE_MESSAGES.toolsNoun;
252
+ const pronoun = isSingleTool ? UPDATE_MESSAGES.it : UPDATE_MESSAGES.them;
253
+ console.log();
254
+ console.log(chalk.yellow(UPDATE_MESSAGES.detectedNewTools(toolNoun, newToolNames.join(', '), pronoun)));
255
+ }
256
+ }
257
+ /**
258
+ * Displays a note about extra workflows installed that aren't in the current profile.
259
+ */
260
+ displayExtraWorkflowsNote(projectPath, configuredTools, profileWorkflows) {
261
+ const installedWorkflows = scanInstalledWorkflows(projectPath, configuredTools);
262
+ const profileSet = new Set(profileWorkflows);
263
+ const extraWorkflows = installedWorkflows.filter((w) => !profileSet.has(w));
264
+ if (extraWorkflows.length > 0) {
265
+ console.log(chalk.dim(UPDATE_MESSAGES.extraWorkflowsNote(extraWorkflows.length)));
266
+ }
267
+ }
268
+ /**
269
+ * Removes skill directories for workflows when delivery changed to commands-only.
270
+ * Returns the number of directories removed.
271
+ */
272
+ async removeSkillDirs(skillsDir) {
273
+ let removed = 0;
274
+ for (const workflow of ALL_WORKFLOWS) {
275
+ const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
276
+ if (!dirName)
277
+ continue;
278
+ const skillDir = path.join(skillsDir, dirName);
279
+ try {
280
+ if (fs.existsSync(skillDir)) {
281
+ await fs.promises.rm(skillDir, { recursive: true, force: true });
282
+ removed++;
283
+ }
284
+ }
285
+ catch {
286
+ // Ignore errors
287
+ }
288
+ }
289
+ return removed;
290
+ }
291
+ /**
292
+ * Removes skill directories for workflows that are no longer selected in the active profile.
293
+ * Returns the number of directories removed.
294
+ */
295
+ async removeUnselectedSkillDirs(skillsDir, desiredWorkflows) {
296
+ const desiredSet = new Set(desiredWorkflows);
297
+ let removed = 0;
298
+ for (const workflow of ALL_WORKFLOWS) {
299
+ if (desiredSet.has(workflow))
300
+ continue;
301
+ const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
302
+ if (!dirName)
303
+ continue;
304
+ const skillDir = path.join(skillsDir, dirName);
305
+ try {
306
+ if (fs.existsSync(skillDir)) {
307
+ await fs.promises.rm(skillDir, { recursive: true, force: true });
308
+ removed++;
309
+ }
310
+ }
311
+ catch {
312
+ // Ignore errors
313
+ }
314
+ }
315
+ return removed;
316
+ }
317
+ /**
318
+ * Removes command files for workflows when delivery changed to skills-only.
319
+ * Returns the number of files removed.
320
+ */
321
+ async removeCommandFiles(projectPath, toolId) {
322
+ let removed = 0;
323
+ const adapter = CommandAdapterRegistry.get(toolId);
324
+ if (!adapter)
325
+ return 0;
326
+ for (const workflow of ALL_WORKFLOWS) {
327
+ const cmdPath = adapter.getFilePath(workflow);
328
+ const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
329
+ try {
330
+ if (fs.existsSync(fullPath)) {
331
+ await fs.promises.unlink(fullPath);
332
+ removed++;
333
+ }
334
+ }
335
+ catch {
336
+ // Ignore errors
337
+ }
338
+ }
339
+ return removed;
340
+ }
341
+ /**
342
+ * Removes command files for workflows that are no longer selected in the active profile.
343
+ * Returns the number of files removed.
344
+ */
345
+ async removeUnselectedCommandFiles(projectPath, toolId, desiredWorkflows) {
346
+ let removed = 0;
347
+ const adapter = CommandAdapterRegistry.get(toolId);
348
+ if (!adapter)
349
+ return 0;
350
+ const desiredSet = new Set(desiredWorkflows);
351
+ for (const workflow of ALL_WORKFLOWS) {
352
+ if (desiredSet.has(workflow))
353
+ continue;
354
+ const cmdPath = adapter.getFilePath(workflow);
355
+ const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
356
+ try {
357
+ if (fs.existsSync(fullPath)) {
358
+ await fs.promises.unlink(fullPath);
359
+ removed++;
360
+ }
361
+ }
362
+ catch {
363
+ // Ignore errors
364
+ }
365
+ }
366
+ return removed;
367
+ }
368
+ /**
369
+ * Detect and handle legacy BR-OpenSpec artifacts.
370
+ * Unlike init, update warns but continues if legacy files found in non-interactive mode.
371
+ * Returns array of tool IDs that were newly configured during legacy upgrade.
372
+ */
373
+ async handleLegacyCleanup(projectPath, desiredWorkflows, delivery) {
374
+ // Detect legacy artifacts
375
+ const detection = await detectLegacyArtifacts(projectPath);
376
+ if (!detection.hasLegacyArtifacts) {
377
+ return []; // No legacy artifacts found
378
+ }
379
+ // Show what was detected
380
+ console.log();
381
+ console.log(formatDetectionSummary(detection));
382
+ console.log();
383
+ const canPrompt = isInteractive();
384
+ if (this.force) {
385
+ // --force flag: proceed with cleanup automatically
386
+ await this.performLegacyCleanup(projectPath, detection);
387
+ // Then upgrade legacy tools to new skills
388
+ return this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
389
+ }
390
+ if (!canPrompt) {
391
+ // Non-interactive mode without --force: warn and continue
392
+ // (Unlike init, update doesn't abort - user may just want to update skills)
393
+ console.log(chalk.yellow(UPDATE_MESSAGES.forceLegacyHint));
394
+ console.log();
395
+ return [];
396
+ }
397
+ // Interactive mode: prompt for confirmation
398
+ const { confirm } = await import('@inquirer/prompts');
399
+ const shouldCleanup = await confirm({
400
+ message: UPDATE_MESSAGES.upgradeLegacyPrompt,
401
+ default: true,
402
+ });
403
+ if (shouldCleanup) {
404
+ await this.performLegacyCleanup(projectPath, detection);
405
+ // Then upgrade legacy tools to new skills
406
+ return this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
407
+ }
408
+ else {
409
+ console.log(chalk.dim(UPDATE_MESSAGES.skippingLegacyCleanup));
410
+ console.log();
411
+ return [];
412
+ }
413
+ }
414
+ /**
415
+ * Perform cleanup of legacy artifacts.
416
+ */
417
+ async performLegacyCleanup(projectPath, detection) {
418
+ const spinner = ora(UPDATE_MESSAGES.cleaningLegacy).start();
419
+ const result = await cleanupLegacyArtifacts(projectPath, detection);
420
+ spinner.succeed(UPDATE_MESSAGES.legacyCleaned);
421
+ const summary = formatCleanupSummary(result);
422
+ if (summary) {
423
+ console.log();
424
+ console.log(summary);
425
+ }
426
+ console.log();
427
+ }
428
+ /**
429
+ * Upgrade legacy tools to new skills system.
430
+ * Returns array of tool IDs that were newly configured.
431
+ */
432
+ async upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery) {
433
+ // Get tools that had legacy artifacts
434
+ const legacyTools = getToolsFromLegacyArtifacts(detection);
435
+ if (legacyTools.length === 0) {
436
+ return [];
437
+ }
438
+ // Get currently configured tools
439
+ const configuredTools = getConfiguredToolsForProfileSync(projectPath);
440
+ const configuredSet = new Set(configuredTools);
441
+ // Filter to tools that aren't already configured
442
+ const unconfiguredLegacyTools = legacyTools.filter((t) => !configuredSet.has(t));
443
+ if (unconfiguredLegacyTools.length === 0) {
444
+ return [];
445
+ }
446
+ // Get valid tools (those with skillsDir)
447
+ const validToolIds = new Set(getToolsWithSkillsDir());
448
+ const validUnconfiguredTools = unconfiguredLegacyTools.filter((t) => validToolIds.has(t));
449
+ if (validUnconfiguredTools.length === 0) {
450
+ return [];
451
+ }
452
+ // Show what tools were detected from legacy artifacts
453
+ console.log(chalk.bold(UPDATE_MESSAGES.toolsDetectedFromLegacy));
454
+ for (const toolId of validUnconfiguredTools) {
455
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
456
+ console.log(` • ${tool?.name || toolId}`);
457
+ }
458
+ console.log();
459
+ let selectedTools;
460
+ if (this.force || !canPrompt) {
461
+ // Non-interactive with --force: auto-select detected tools
462
+ selectedTools = validUnconfiguredTools;
463
+ console.log(UPDATE_MESSAGES.setupSkillsFor(selectedTools.join(', ')));
464
+ }
465
+ else {
466
+ // Interactive mode: prompt for tool selection with detected tools pre-selected
467
+ const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
468
+ const sortedChoices = validUnconfiguredTools.map((toolId) => {
469
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
470
+ return {
471
+ name: tool?.name || toolId,
472
+ value: toolId,
473
+ configured: false,
474
+ preSelected: true, // Pre-select all detected legacy tools
475
+ };
476
+ });
477
+ selectedTools = await searchableMultiSelect({
478
+ message: UPDATE_MESSAGES.selectToolsNewSkillSystem,
479
+ pageSize: 15,
480
+ choices: sortedChoices,
481
+ validate: (_selected) => true, // Allow empty selection (user can skip)
482
+ });
483
+ if (selectedTools.length === 0) {
484
+ console.log(chalk.dim(UPDATE_MESSAGES.skippingToolSetup));
485
+ console.log();
486
+ return [];
487
+ }
488
+ }
489
+ // Create skills/commands for selected tools using effective profile+delivery.
490
+ const newlyConfigured = [];
491
+ const shouldGenerateSkills = delivery !== 'commands';
492
+ const shouldGenerateCommands = delivery !== 'skills';
493
+ const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
494
+ const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
495
+ for (const toolId of selectedTools) {
496
+ const tool = AI_TOOLS.find((t) => t.value === toolId);
497
+ if (!tool?.skillsDir)
498
+ continue;
499
+ const spinner = ora(UPDATE_MESSAGES.settingUp(tool.name)).start();
500
+ try {
501
+ const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
502
+ // Create skill files when delivery includes skills
503
+ if (shouldGenerateSkills) {
504
+ for (const { template, dirName } of skillTemplates) {
505
+ const skillDir = path.join(skillsDir, dirName);
506
+ const skillFile = path.join(skillDir, 'SKILL.md');
507
+ // Use hyphen-based command references for OpenCode
508
+ const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
509
+ const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
510
+ await FileSystemUtils.writeFile(skillFile, skillContent);
511
+ }
512
+ }
513
+ // Create commands when delivery includes commands
514
+ if (shouldGenerateCommands) {
515
+ const adapter = CommandAdapterRegistry.get(tool.value);
516
+ if (adapter) {
517
+ const generatedCommands = generateCommands(commandContents, adapter);
518
+ for (const cmd of generatedCommands) {
519
+ const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
520
+ await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
521
+ }
522
+ }
523
+ }
524
+ spinner.succeed(UPDATE_MESSAGES.setupComplete(tool.name));
525
+ newlyConfigured.push(toolId);
526
+ }
527
+ catch (error) {
528
+ spinner.fail(UPDATE_MESSAGES.failedToSetup(tool.name));
529
+ console.log(chalk.red(` ${error instanceof Error ? error.message : String(error)}`));
530
+ }
531
+ }
532
+ if (newlyConfigured.length > 0) {
533
+ console.log();
534
+ }
535
+ return newlyConfigured;
536
+ }
537
+ }
538
+ //# 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: "O texto do cenário não pode estar vazio";
11
+ readonly REQUIREMENT_EMPTY: "O texto do requisito não pode estar vazio";
12
+ readonly REQUIREMENT_NO_SHALL: "O requisito deve conter as palavras-chave SHALL ou MUST";
13
+ readonly REQUIREMENT_NO_SCENARIOS: "O requisito deve ter pelo menos um cenário";
14
+ readonly SPEC_NAME_EMPTY: "O nome da especificação não pode estar vazio";
15
+ readonly SPEC_PURPOSE_EMPTY: "A seção Purpose não pode estar vazia";
16
+ readonly SPEC_NO_REQUIREMENTS: "A especificação deve ter pelo menos um requisito";
17
+ readonly CHANGE_NAME_EMPTY: "O nome da alteração não pode estar vazio";
18
+ readonly CHANGE_WHY_TOO_SHORT: "A seção Why deve ter pelo menos 50 caracteres";
19
+ readonly CHANGE_WHY_TOO_LONG: "A seção Why não deve exceder 1000 caracteres";
20
+ readonly CHANGE_WHAT_EMPTY: "A seção What Changes não pode estar vazia";
21
+ readonly CHANGE_NO_DELTAS: "A alteração deve ter pelo menos um delta";
22
+ readonly CHANGE_TOO_MANY_DELTAS: "Considere dividir alterações com mais de 10 deltas";
23
+ readonly DELTA_SPEC_EMPTY: "O nome da especificação não pode estar vazio";
24
+ readonly DELTA_DESCRIPTION_EMPTY: "A descrição do delta não pode estar vazia";
25
+ readonly PURPOSE_TOO_BRIEF: "A seção Purpose é muito breve (menos de 50 caracteres)";
26
+ readonly REQUIREMENT_TOO_LONG: "O texto do requisito é muito longo (>500 caracteres). Considere dividi-lo.";
27
+ readonly DELTA_DESCRIPTION_TOO_BRIEF: "A descrição do delta é muito breve";
28
+ readonly DELTA_MISSING_REQUIREMENTS: "O delta deve incluir requisitos";
29
+ readonly GUIDE_NO_DELTAS: "Nenhum delta encontrado. Certifique-se de que a alteração possui um diretório specs/ com pastas de capacidade (ex: specs/http-server/spec.md) contendo arquivos .md que usam cabeçalhos de delta (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) e que cada requisito inclui pelo menos um bloco \"#### Scenario:\". Dica: execute \"openspec change show <change-id> --json --deltas-only\" para inspecionar os deltas analisados.";
30
+ readonly GUIDE_MISSING_SPEC_SECTIONS: "Seções obrigatórias ausentes. Cabeçalhos esperados: \"## Purpose\" e \"## Requirements\". Exemplo:\n## Purpose\n[breve propósito]\n\n## Requirements\n### Requirement: Declaração clara de requisito\nUsers SHALL ...\n\n#### Scenario: Nome descritivo\n- **WHEN** ...\n- **THEN** ...";
31
+ readonly GUIDE_MISSING_CHANGE_SECTIONS: "Seções obrigatórias ausentes. Cabeçalhos esperados: \"## Why\" e \"## What Changes\". Certifique-se de que os deltas estão documentados em specs/ usando cabeçalhos de delta.";
32
+ readonly GUIDE_SCENARIO_FORMAT: "Os cenários devem usar cabeçalhos de nível 4. Converta listas em:\n#### Scenario: Nome curto\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: 'O texto do cenário não pode estar vazio',
15
+ REQUIREMENT_EMPTY: 'O texto do requisito não pode estar vazio',
16
+ REQUIREMENT_NO_SHALL: 'O requisito deve conter as palavras-chave SHALL ou MUST',
17
+ REQUIREMENT_NO_SCENARIOS: 'O requisito deve ter pelo menos um cenário',
18
+ SPEC_NAME_EMPTY: 'O nome da especificação não pode estar vazio',
19
+ SPEC_PURPOSE_EMPTY: 'A seção Purpose não pode estar vazia',
20
+ SPEC_NO_REQUIREMENTS: 'A especificação deve ter pelo menos um requisito',
21
+ CHANGE_NAME_EMPTY: 'O nome da alteração não pode estar vazio',
22
+ CHANGE_WHY_TOO_SHORT: `A seção Why deve ter pelo menos ${MIN_WHY_SECTION_LENGTH} caracteres`,
23
+ CHANGE_WHY_TOO_LONG: `A seção Why não deve exceder ${MAX_WHY_SECTION_LENGTH} caracteres`,
24
+ CHANGE_WHAT_EMPTY: 'A seção What Changes não pode estar vazia',
25
+ CHANGE_NO_DELTAS: 'A alteração deve ter pelo menos um delta',
26
+ CHANGE_TOO_MANY_DELTAS: `Considere dividir alterações com mais de ${MAX_DELTAS_PER_CHANGE} deltas`,
27
+ DELTA_SPEC_EMPTY: 'O nome da especificação não pode estar vazio',
28
+ DELTA_DESCRIPTION_EMPTY: 'A descrição do delta não pode estar vazia',
29
+ // Warnings
30
+ PURPOSE_TOO_BRIEF: `A seção Purpose é muito breve (menos de ${MIN_PURPOSE_LENGTH} caracteres)`,
31
+ REQUIREMENT_TOO_LONG: `O texto do requisito é muito longo (>${MAX_REQUIREMENT_TEXT_LENGTH} caracteres). Considere dividi-lo.`,
32
+ DELTA_DESCRIPTION_TOO_BRIEF: 'A descrição do delta é muito breve',
33
+ DELTA_MISSING_REQUIREMENTS: 'O delta deve incluir requisitos',
34
+ // Guidance snippets (appended to primary messages for remediation)
35
+ GUIDE_NO_DELTAS: 'Nenhum delta encontrado. Certifique-se de que a alteração possui um diretório specs/ com pastas de capacidade (ex: specs/http-server/spec.md) contendo arquivos .md que usam cabeçalhos de delta (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) e que cada requisito inclui pelo menos um bloco "#### Scenario:". Dica: execute "openspec change show <change-id> --json --deltas-only" para inspecionar os deltas analisados.',
36
+ GUIDE_MISSING_SPEC_SECTIONS: 'Seções obrigatórias ausentes. Cabeçalhos esperados: "## Purpose" e "## Requirements". Exemplo:\n## Purpose\n[breve propósito]\n\n## Requirements\n### Requirement: Declaração clara de requisito\nUsers SHALL ...\n\n#### Scenario: Nome descritivo\n- **WHEN** ...\n- **THEN** ...',
37
+ GUIDE_MISSING_CHANGE_SECTIONS: 'Seções obrigatórias ausentes. Cabeçalhos esperados: "## Why" e "## What Changes". Certifique-se de que os deltas estão documentados em specs/ usando cabeçalhos de delta.',
38
+ GUIDE_SCENARIO_FORMAT: 'Os cenários devem usar cabeçalhos de nível 4. Converta listas em:\n#### Scenario: Nome curto\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