@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,419 @@
1
+ import { readFileSync, promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { SpecSchema, ChangeSchema } from '../schemas/index.js';
4
+ import { MarkdownParser } from '../parsers/markdown-parser.js';
5
+ import { ChangeParser } from '../parsers/change-parser.js';
6
+ import { MIN_PURPOSE_LENGTH, MAX_REQUIREMENT_TEXT_LENGTH, VALIDATION_MESSAGES } from './constants.js';
7
+ import { parseDeltaSpec, normalizeRequirementName } from '../parsers/requirement-blocks.js';
8
+ import { findMainSpecStructureIssues } from '../parsers/spec-structure.js';
9
+ import { FileSystemUtils } from '../../utils/file-system.js';
10
+ import { VALIDATOR_MESSAGES } from '../../messages/index.js';
11
+ export class Validator {
12
+ strictMode;
13
+ constructor(strictMode = false) {
14
+ this.strictMode = strictMode;
15
+ }
16
+ async validateSpec(filePath) {
17
+ const issues = [];
18
+ const specName = this.extractNameFromPath(filePath);
19
+ try {
20
+ const content = readFileSync(filePath, 'utf-8');
21
+ const parser = new MarkdownParser(content);
22
+ const spec = parser.parseSpec(specName);
23
+ const result = SpecSchema.safeParse(spec);
24
+ if (!result.success) {
25
+ issues.push(...this.convertZodErrors(result.error));
26
+ }
27
+ issues.push(...this.applySpecRules(spec, content));
28
+ }
29
+ catch (error) {
30
+ const baseMessage = error instanceof Error ? error.message : VALIDATOR_MESSAGES.unknownError;
31
+ const enriched = this.enrichTopLevelError(specName, baseMessage);
32
+ issues.push({
33
+ level: 'ERROR',
34
+ path: 'file',
35
+ message: enriched,
36
+ });
37
+ }
38
+ return this.createReport(issues);
39
+ }
40
+ /**
41
+ * Validate spec content from a string (used for pre-write validation of rebuilt specs)
42
+ */
43
+ async validateSpecContent(specName, content) {
44
+ const issues = [];
45
+ try {
46
+ const parser = new MarkdownParser(content);
47
+ const spec = parser.parseSpec(specName);
48
+ const result = SpecSchema.safeParse(spec);
49
+ if (!result.success) {
50
+ issues.push(...this.convertZodErrors(result.error));
51
+ }
52
+ issues.push(...this.applySpecRules(spec, content));
53
+ }
54
+ catch (error) {
55
+ const baseMessage = error instanceof Error ? error.message : VALIDATOR_MESSAGES.unknownError;
56
+ const enriched = this.enrichTopLevelError(specName, baseMessage);
57
+ issues.push({ level: 'ERROR', path: 'file', message: enriched });
58
+ }
59
+ return this.createReport(issues);
60
+ }
61
+ async validateChange(filePath) {
62
+ const issues = [];
63
+ const changeName = this.extractNameFromPath(filePath);
64
+ try {
65
+ const content = readFileSync(filePath, 'utf-8');
66
+ const changeDir = path.dirname(filePath);
67
+ const parser = new ChangeParser(content, changeDir);
68
+ const change = await parser.parseChangeWithDeltas(changeName);
69
+ const result = ChangeSchema.safeParse(change);
70
+ if (!result.success) {
71
+ issues.push(...this.convertZodErrors(result.error));
72
+ }
73
+ issues.push(...this.applyChangeRules(change, content));
74
+ }
75
+ catch (error) {
76
+ const baseMessage = error instanceof Error ? error.message : VALIDATOR_MESSAGES.unknownError;
77
+ const enriched = this.enrichTopLevelError(changeName, baseMessage);
78
+ issues.push({
79
+ level: 'ERROR',
80
+ path: 'file',
81
+ message: enriched,
82
+ });
83
+ }
84
+ return this.createReport(issues);
85
+ }
86
+ /**
87
+ * Validate delta-formatted spec files under a change directory.
88
+ * Enforces:
89
+ * - At least one delta across all files
90
+ * - ADDED/MODIFIED: each requirement has SHALL/MUST and at least one scenario
91
+ * - REMOVED: names only; no scenario/description required
92
+ * - RENAMED: pairs well-formed
93
+ * - No duplicates within sections; no cross-section conflicts per spec
94
+ */
95
+ async validateChangeDeltaSpecs(changeDir) {
96
+ const issues = [];
97
+ const specsDir = path.join(changeDir, 'specs');
98
+ let totalDeltas = 0;
99
+ const missingHeaderSpecs = [];
100
+ const emptySectionSpecs = [];
101
+ try {
102
+ const entries = await fs.readdir(specsDir, { withFileTypes: true });
103
+ for (const entry of entries) {
104
+ if (!entry.isDirectory())
105
+ continue;
106
+ const specName = entry.name;
107
+ const specFile = path.join(specsDir, specName, 'spec.md');
108
+ let content;
109
+ try {
110
+ content = await fs.readFile(specFile, 'utf-8');
111
+ }
112
+ catch {
113
+ continue;
114
+ }
115
+ const plan = parseDeltaSpec(content);
116
+ const entryPath = `${specName}/spec.md`;
117
+ const sectionNames = [];
118
+ if (plan.sectionPresence.added)
119
+ sectionNames.push('## ADDED Requirements');
120
+ if (plan.sectionPresence.modified)
121
+ sectionNames.push('## MODIFIED Requirements');
122
+ if (plan.sectionPresence.removed)
123
+ sectionNames.push('## REMOVED Requirements');
124
+ if (plan.sectionPresence.renamed)
125
+ sectionNames.push('## RENAMED Requirements');
126
+ const hasSections = sectionNames.length > 0;
127
+ const hasEntries = plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length > 0;
128
+ if (!hasEntries) {
129
+ if (hasSections)
130
+ emptySectionSpecs.push({ path: entryPath, sections: sectionNames });
131
+ else
132
+ missingHeaderSpecs.push(entryPath);
133
+ }
134
+ const addedNames = new Set();
135
+ const modifiedNames = new Set();
136
+ const removedNames = new Set();
137
+ const renamedFrom = new Set();
138
+ const renamedTo = new Set();
139
+ // Validate ADDED
140
+ for (const block of plan.added) {
141
+ const key = normalizeRequirementName(block.name);
142
+ totalDeltas++;
143
+ if (addedNames.has(key)) {
144
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.duplicateRequirementAdded(block.name) });
145
+ }
146
+ else {
147
+ addedNames.add(key);
148
+ }
149
+ const requirementText = this.extractRequirementText(block.raw);
150
+ if (!requirementText) {
151
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.missingRequirementTextAdded(block.name) });
152
+ }
153
+ else if (!this.containsShallOrMust(requirementText)) {
154
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.missingShallOrMustAdded(block.name) });
155
+ }
156
+ const scenarioCount = this.countScenarios(block.raw);
157
+ if (scenarioCount < 1) {
158
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.missingScenarioAdded(block.name) });
159
+ }
160
+ }
161
+ // Validate MODIFIED
162
+ for (const block of plan.modified) {
163
+ const key = normalizeRequirementName(block.name);
164
+ totalDeltas++;
165
+ if (modifiedNames.has(key)) {
166
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.duplicateRequirementModified(block.name) });
167
+ }
168
+ else {
169
+ modifiedNames.add(key);
170
+ }
171
+ const requirementText = this.extractRequirementText(block.raw);
172
+ if (!requirementText) {
173
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.missingRequirementTextModified(block.name) });
174
+ }
175
+ else if (!this.containsShallOrMust(requirementText)) {
176
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.missingShallOrMustModified(block.name) });
177
+ }
178
+ const scenarioCount = this.countScenarios(block.raw);
179
+ if (scenarioCount < 1) {
180
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.missingScenarioModified(block.name) });
181
+ }
182
+ }
183
+ // Validate REMOVED (names only)
184
+ for (const name of plan.removed) {
185
+ const key = normalizeRequirementName(name);
186
+ totalDeltas++;
187
+ if (removedNames.has(key)) {
188
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.duplicateRequirementRemoved(name) });
189
+ }
190
+ else {
191
+ removedNames.add(key);
192
+ }
193
+ }
194
+ // Validate RENAMED pairs
195
+ for (const { from, to } of plan.renamed) {
196
+ const fromKey = normalizeRequirementName(from);
197
+ const toKey = normalizeRequirementName(to);
198
+ totalDeltas++;
199
+ if (renamedFrom.has(fromKey)) {
200
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.duplicateFromRenamed(from) });
201
+ }
202
+ else {
203
+ renamedFrom.add(fromKey);
204
+ }
205
+ if (renamedTo.has(toKey)) {
206
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.duplicateToRenamed(to) });
207
+ }
208
+ else {
209
+ renamedTo.add(toKey);
210
+ }
211
+ }
212
+ // Cross-section conflicts (within the same spec file)
213
+ for (const n of modifiedNames) {
214
+ if (removedNames.has(n)) {
215
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.requirementInModifiedAndRemoved(n) });
216
+ }
217
+ if (addedNames.has(n)) {
218
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.requirementInModifiedAndAdded(n) });
219
+ }
220
+ }
221
+ for (const n of addedNames) {
222
+ if (removedNames.has(n)) {
223
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.requirementInAddedAndRemoved(n) });
224
+ }
225
+ }
226
+ for (const { from, to } of plan.renamed) {
227
+ const fromKey = normalizeRequirementName(from);
228
+ const toKey = normalizeRequirementName(to);
229
+ if (modifiedNames.has(fromKey)) {
230
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.modifiedReferencesOldRenamed(to) });
231
+ }
232
+ if (addedNames.has(toKey)) {
233
+ issues.push({ level: 'ERROR', path: entryPath, message: VALIDATOR_MESSAGES.renamedToCollidesAdded(to) });
234
+ }
235
+ }
236
+ }
237
+ }
238
+ catch {
239
+ // If no specs dir, treat as no deltas
240
+ }
241
+ for (const { path: specPath, sections } of emptySectionSpecs) {
242
+ issues.push({
243
+ level: 'ERROR',
244
+ path: specPath,
245
+ message: VALIDATOR_MESSAGES.deltaSectionsEmpty(this.formatSectionList(sections)),
246
+ });
247
+ }
248
+ for (const path of missingHeaderSpecs) {
249
+ issues.push({
250
+ level: 'ERROR',
251
+ path,
252
+ message: VALIDATOR_MESSAGES.noDeltaSectionsFound,
253
+ });
254
+ }
255
+ if (totalDeltas === 0) {
256
+ issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
257
+ }
258
+ return this.createReport(issues);
259
+ }
260
+ convertZodErrors(error) {
261
+ return error.issues.map(err => {
262
+ let message = err.message;
263
+ if (message === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
264
+ message = `${message}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
265
+ }
266
+ return {
267
+ level: 'ERROR',
268
+ path: err.path.join('.'),
269
+ message,
270
+ };
271
+ });
272
+ }
273
+ applySpecRules(spec, content) {
274
+ const issues = [];
275
+ for (const structuralIssue of findMainSpecStructureIssues(content)) {
276
+ issues.push({
277
+ level: 'ERROR',
278
+ path: 'file',
279
+ line: structuralIssue.line,
280
+ message: structuralIssue.message,
281
+ });
282
+ }
283
+ if (spec.overview.length < MIN_PURPOSE_LENGTH) {
284
+ issues.push({
285
+ level: 'WARNING',
286
+ path: 'overview',
287
+ message: VALIDATION_MESSAGES.PURPOSE_TOO_BRIEF,
288
+ });
289
+ }
290
+ spec.requirements.forEach((req, index) => {
291
+ if (req.text.length > MAX_REQUIREMENT_TEXT_LENGTH) {
292
+ issues.push({
293
+ level: 'INFO',
294
+ path: `requirements[${index}]`,
295
+ message: VALIDATION_MESSAGES.REQUIREMENT_TOO_LONG,
296
+ });
297
+ }
298
+ if (req.scenarios.length === 0) {
299
+ issues.push({
300
+ level: 'WARNING',
301
+ path: `requirements[${index}].scenarios`,
302
+ message: `${VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS}. ${VALIDATION_MESSAGES.GUIDE_SCENARIO_FORMAT}`,
303
+ });
304
+ }
305
+ });
306
+ return issues;
307
+ }
308
+ applyChangeRules(change, content) {
309
+ const issues = [];
310
+ const MIN_DELTA_DESCRIPTION_LENGTH = 10;
311
+ change.deltas.forEach((delta, index) => {
312
+ if (!delta.description || delta.description.length < MIN_DELTA_DESCRIPTION_LENGTH) {
313
+ issues.push({
314
+ level: 'WARNING',
315
+ path: `deltas[${index}].description`,
316
+ message: VALIDATION_MESSAGES.DELTA_DESCRIPTION_TOO_BRIEF,
317
+ });
318
+ }
319
+ if ((delta.operation === 'ADDED' || delta.operation === 'MODIFIED') &&
320
+ (!delta.requirements || delta.requirements.length === 0)) {
321
+ issues.push({
322
+ level: 'WARNING',
323
+ path: `deltas[${index}].requirements`,
324
+ message: `${delta.operation} ${VALIDATION_MESSAGES.DELTA_MISSING_REQUIREMENTS}`,
325
+ });
326
+ }
327
+ });
328
+ return issues;
329
+ }
330
+ enrichTopLevelError(itemId, baseMessage) {
331
+ const msg = baseMessage.trim();
332
+ if (msg === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
333
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
334
+ }
335
+ if (msg.includes('A especificação deve ter uma seção Purpose') || msg.includes('A especificação deve ter uma seção Requirements')) {
336
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_SPEC_SECTIONS}`;
337
+ }
338
+ if (msg.includes('A alteração deve ter uma seção Why') || msg.includes('A alteração deve ter uma seção What Changes')) {
339
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_CHANGE_SECTIONS}`;
340
+ }
341
+ return msg;
342
+ }
343
+ extractNameFromPath(filePath) {
344
+ const normalizedPath = FileSystemUtils.toPosixPath(filePath);
345
+ const parts = normalizedPath.split('/');
346
+ // Look for the directory name after 'specs' or 'changes'
347
+ for (let i = parts.length - 1; i >= 0; i--) {
348
+ if (parts[i] === 'specs' || parts[i] === 'changes') {
349
+ if (i < parts.length - 1) {
350
+ return parts[i + 1];
351
+ }
352
+ }
353
+ }
354
+ // Fallback to filename without extension if not in expected structure
355
+ const fileName = parts[parts.length - 1] ?? '';
356
+ const dotIndex = fileName.lastIndexOf('.');
357
+ return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
358
+ }
359
+ createReport(issues) {
360
+ const errors = issues.filter(i => i.level === 'ERROR').length;
361
+ const warnings = issues.filter(i => i.level === 'WARNING').length;
362
+ const info = issues.filter(i => i.level === 'INFO').length;
363
+ const valid = this.strictMode
364
+ ? errors === 0 && warnings === 0
365
+ : errors === 0;
366
+ return {
367
+ valid,
368
+ issues,
369
+ summary: {
370
+ errors,
371
+ warnings,
372
+ info,
373
+ },
374
+ };
375
+ }
376
+ isValid(report) {
377
+ return report.valid;
378
+ }
379
+ extractRequirementText(blockRaw) {
380
+ const lines = blockRaw.split('\n');
381
+ // Skip header line (index 0)
382
+ let i = 1;
383
+ // Find the first substantial text line, skipping metadata and blank lines
384
+ for (; i < lines.length; i++) {
385
+ const line = lines[i];
386
+ // Stop at scenario headers
387
+ if (/^####\s+/.test(line))
388
+ break;
389
+ const trimmed = line.trim();
390
+ // Skip blank lines
391
+ if (trimmed.length === 0)
392
+ continue;
393
+ // Skip metadata lines (lines starting with ** like **ID**, **Priority**, etc.)
394
+ if (/^\*\*[^*]+\*\*:/.test(trimmed))
395
+ continue;
396
+ // Found first non-metadata, non-blank line - this is the requirement text
397
+ return trimmed;
398
+ }
399
+ // No requirement text found
400
+ return undefined;
401
+ }
402
+ containsShallOrMust(text) {
403
+ return /\b(SHALL|MUST)\b/.test(text);
404
+ }
405
+ countScenarios(blockRaw) {
406
+ const matches = blockRaw.match(/^####\s+/gm);
407
+ return matches ? matches.length : 0;
408
+ }
409
+ formatSectionList(sections) {
410
+ if (sections.length === 0)
411
+ return '';
412
+ if (sections.length === 1)
413
+ return sections[0];
414
+ const head = sections.slice(0, -1);
415
+ const last = sections[sections.length - 1];
416
+ return `${head.join(', ')} e ${last}`;
417
+ }
418
+ }
419
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1,8 @@
1
+ export declare class ViewCommand {
2
+ execute(targetPath?: string): Promise<void>;
3
+ private getChangesData;
4
+ private getSpecsData;
5
+ private displaySummary;
6
+ private createProgressBar;
7
+ }
8
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1,169 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import { getTaskProgressForChange } from '../utils/task-progress.js';
5
+ import { MarkdownParser } from './parsers/markdown-parser.js';
6
+ import { VIEW_MESSAGES } from '../messages/index.js';
7
+ export class ViewCommand {
8
+ async execute(targetPath = '.') {
9
+ const openspecDir = path.join(targetPath, 'openspec');
10
+ if (!fs.existsSync(openspecDir)) {
11
+ console.error(chalk.red(VIEW_MESSAGES.noOpenspecDir));
12
+ process.exit(1);
13
+ }
14
+ console.log(chalk.bold(`\n${VIEW_MESSAGES.dashboardTitle}\n`));
15
+ console.log('═'.repeat(60));
16
+ // Get changes and specs data
17
+ const changesData = await this.getChangesData(openspecDir);
18
+ const specsData = await this.getSpecsData(openspecDir);
19
+ // Display summary metrics
20
+ this.displaySummary(changesData, specsData);
21
+ // Display draft changes
22
+ if (changesData.draft.length > 0) {
23
+ console.log(chalk.bold.gray(`\n${VIEW_MESSAGES.draftChanges}`));
24
+ console.log('─'.repeat(60));
25
+ changesData.draft.forEach((change) => {
26
+ console.log(` ${chalk.gray('○')} ${change.name}`);
27
+ });
28
+ }
29
+ // Display active changes
30
+ if (changesData.active.length > 0) {
31
+ console.log(chalk.bold.cyan(`\n${VIEW_MESSAGES.activeChanges}`));
32
+ console.log('─'.repeat(60));
33
+ changesData.active.forEach((change) => {
34
+ const progressBar = this.createProgressBar(change.progress.completed, change.progress.total);
35
+ const percentage = change.progress.total > 0
36
+ ? Math.round((change.progress.completed / change.progress.total) * 100)
37
+ : 0;
38
+ console.log(` ${chalk.yellow('◉')} ${chalk.bold(change.name.padEnd(30))} ${progressBar} ${chalk.dim(`${percentage}%`)}`);
39
+ });
40
+ }
41
+ // Display completed changes
42
+ if (changesData.completed.length > 0) {
43
+ console.log(chalk.bold.green(`\n${VIEW_MESSAGES.completedChanges}`));
44
+ console.log('─'.repeat(60));
45
+ changesData.completed.forEach((change) => {
46
+ console.log(` ${chalk.green('✓')} ${change.name}`);
47
+ });
48
+ }
49
+ // Display specifications
50
+ if (specsData.length > 0) {
51
+ console.log(chalk.bold.blue(`\n${VIEW_MESSAGES.specifications}`));
52
+ console.log('─'.repeat(60));
53
+ // Sort specs by requirement count (descending)
54
+ specsData.sort((a, b) => b.requirementCount - a.requirementCount);
55
+ specsData.forEach(spec => {
56
+ const reqLabel = VIEW_MESSAGES.requirementLabel(spec.requirementCount);
57
+ console.log(` ${chalk.blue('▪')} ${chalk.bold(spec.name.padEnd(30))} ${chalk.dim(`${spec.requirementCount} ${reqLabel}`)}`);
58
+ });
59
+ }
60
+ console.log('\n' + '═'.repeat(60));
61
+ console.log(chalk.dim(`\n${VIEW_MESSAGES.listHintCommands(chalk.white('openspec list --changes'), chalk.white('openspec list --specs'))}`));
62
+ }
63
+ async getChangesData(openspecDir) {
64
+ const changesDir = path.join(openspecDir, 'changes');
65
+ if (!fs.existsSync(changesDir)) {
66
+ return { draft: [], active: [], completed: [] };
67
+ }
68
+ const draft = [];
69
+ const active = [];
70
+ const completed = [];
71
+ const entries = fs.readdirSync(changesDir, { withFileTypes: true });
72
+ for (const entry of entries) {
73
+ if (entry.isDirectory() && entry.name !== 'archive') {
74
+ const progress = await getTaskProgressForChange(changesDir, entry.name);
75
+ if (progress.total === 0) {
76
+ // No tasks defined yet - still in planning/draft phase
77
+ draft.push({ name: entry.name });
78
+ }
79
+ else if (progress.completed === progress.total) {
80
+ // All tasks complete
81
+ completed.push({ name: entry.name });
82
+ }
83
+ else {
84
+ // Has tasks but not all complete
85
+ active.push({ name: entry.name, progress });
86
+ }
87
+ }
88
+ }
89
+ // Sort all categories by name for deterministic ordering
90
+ draft.sort((a, b) => a.name.localeCompare(b.name));
91
+ // Sort active changes by completion percentage (ascending) and then by name
92
+ active.sort((a, b) => {
93
+ const percentageA = a.progress.total > 0 ? a.progress.completed / a.progress.total : 0;
94
+ const percentageB = b.progress.total > 0 ? b.progress.completed / b.progress.total : 0;
95
+ if (percentageA < percentageB)
96
+ return -1;
97
+ if (percentageA > percentageB)
98
+ return 1;
99
+ return a.name.localeCompare(b.name);
100
+ });
101
+ completed.sort((a, b) => a.name.localeCompare(b.name));
102
+ return { draft, active, completed };
103
+ }
104
+ async getSpecsData(openspecDir) {
105
+ const specsDir = path.join(openspecDir, 'specs');
106
+ if (!fs.existsSync(specsDir)) {
107
+ return [];
108
+ }
109
+ const specs = [];
110
+ const entries = fs.readdirSync(specsDir, { withFileTypes: true });
111
+ for (const entry of entries) {
112
+ if (entry.isDirectory()) {
113
+ const specFile = path.join(specsDir, entry.name, 'spec.md');
114
+ if (fs.existsSync(specFile)) {
115
+ try {
116
+ const content = fs.readFileSync(specFile, 'utf-8');
117
+ const parser = new MarkdownParser(content);
118
+ const spec = parser.parseSpec(entry.name);
119
+ const requirementCount = spec.requirements.length;
120
+ specs.push({ name: entry.name, requirementCount });
121
+ }
122
+ catch (error) {
123
+ // If spec cannot be parsed, include with 0 count
124
+ specs.push({ name: entry.name, requirementCount: 0 });
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return specs;
130
+ }
131
+ displaySummary(changesData, specsData) {
132
+ const totalChanges = changesData.draft.length + changesData.active.length + changesData.completed.length;
133
+ const totalSpecs = specsData.length;
134
+ const totalRequirements = specsData.reduce((sum, spec) => sum + spec.requirementCount, 0);
135
+ // Calculate total task progress
136
+ let totalTasks = 0;
137
+ let completedTasks = 0;
138
+ changesData.active.forEach((change) => {
139
+ totalTasks += change.progress.total;
140
+ completedTasks += change.progress.completed;
141
+ });
142
+ changesData.completed.forEach(() => {
143
+ // Completed changes count as 100% done (we don't know exact task count)
144
+ // This is a simplification
145
+ });
146
+ console.log(chalk.bold(VIEW_MESSAGES.summary));
147
+ console.log(` ${chalk.cyan('●')} ${VIEW_MESSAGES.specsSummary(totalSpecs, totalRequirements)}`);
148
+ if (changesData.draft.length > 0) {
149
+ console.log(` ${chalk.gray('●')} ${VIEW_MESSAGES.draftChangesCount(changesData.draft.length)}`);
150
+ }
151
+ console.log(` ${chalk.yellow('●')} ${VIEW_MESSAGES.activeChangesCount(changesData.active.length)}`);
152
+ console.log(` ${chalk.green('●')} ${VIEW_MESSAGES.completedChangesCount(changesData.completed.length)}`);
153
+ if (totalTasks > 0) {
154
+ const overallProgress = Math.round((completedTasks / totalTasks) * 100);
155
+ console.log(` ${chalk.magenta('●')} ${VIEW_MESSAGES.taskProgress(completedTasks, totalTasks, overallProgress)}`);
156
+ }
157
+ }
158
+ createProgressBar(completed, total, width = 20) {
159
+ if (total === 0)
160
+ return chalk.dim('─'.repeat(width));
161
+ const percentage = completed / total;
162
+ const filled = Math.round(percentage * width);
163
+ const empty = width - filled;
164
+ const filledBar = chalk.green('█'.repeat(filled));
165
+ const emptyBar = chalk.dim('░'.repeat(empty));
166
+ return `[${filledBar}${emptyBar}]`;
167
+ }
168
+ }
169
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1,3 @@
1
+ export * from './cli/index.js';
2
+ export * from './core/index.js';
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './cli/index.js';
2
+ export * from './core/index.js';
3
+ //# sourceMappingURL=index.js.map