@kbdavis07/openspec 1.5.0-nov.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 (352) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +239 -0
  3. package/bin/openspec.js +5 -0
  4. package/dist/cli/index.d.ts +10 -0
  5. package/dist/cli/index.js +544 -0
  6. package/dist/commands/change.d.ts +38 -0
  7. package/dist/commands/change.js +245 -0
  8. package/dist/commands/completion.d.ts +72 -0
  9. package/dist/commands/completion.js +264 -0
  10. package/dist/commands/config.d.ts +36 -0
  11. package/dist/commands/config.js +552 -0
  12. package/dist/commands/context.d.ts +3 -0
  13. package/dist/commands/context.js +155 -0
  14. package/dist/commands/doctor.d.ts +8 -0
  15. package/dist/commands/doctor.js +163 -0
  16. package/dist/commands/feedback.d.ts +9 -0
  17. package/dist/commands/feedback.js +183 -0
  18. package/dist/commands/schema.d.ts +6 -0
  19. package/dist/commands/schema.js +869 -0
  20. package/dist/commands/shared-gather.d.ts +14 -0
  21. package/dist/commands/shared-gather.js +31 -0
  22. package/dist/commands/shared-output.d.ts +18 -0
  23. package/dist/commands/shared-output.js +61 -0
  24. package/dist/commands/show.d.ts +19 -0
  25. package/dist/commands/show.js +177 -0
  26. package/dist/commands/spec.d.ts +19 -0
  27. package/dist/commands/spec.js +236 -0
  28. package/dist/commands/store.d.ts +3 -0
  29. package/dist/commands/store.js +547 -0
  30. package/dist/commands/validate.d.ts +34 -0
  31. package/dist/commands/validate.js +342 -0
  32. package/dist/commands/workflow/index.d.ts +17 -0
  33. package/dist/commands/workflow/index.js +12 -0
  34. package/dist/commands/workflow/instructions.d.ts +39 -0
  35. package/dist/commands/workflow/instructions.js +393 -0
  36. package/dist/commands/workflow/new-change.d.ts +20 -0
  37. package/dist/commands/workflow/new-change.js +109 -0
  38. package/dist/commands/workflow/schemas.d.ts +10 -0
  39. package/dist/commands/workflow/schemas.js +34 -0
  40. package/dist/commands/workflow/shared.d.ts +74 -0
  41. package/dist/commands/workflow/shared.js +170 -0
  42. package/dist/commands/workflow/status.d.ts +16 -0
  43. package/dist/commands/workflow/status.js +92 -0
  44. package/dist/commands/workflow/templates.d.ts +16 -0
  45. package/dist/commands/workflow/templates.js +69 -0
  46. package/dist/commands/workset-input.d.ts +19 -0
  47. package/dist/commands/workset-input.js +112 -0
  48. package/dist/commands/workset-prompts.d.ts +12 -0
  49. package/dist/commands/workset-prompts.js +143 -0
  50. package/dist/commands/workset.d.ts +25 -0
  51. package/dist/commands/workset.js +446 -0
  52. package/dist/core/archive.d.ts +22 -0
  53. package/dist/core/archive.js +496 -0
  54. package/dist/core/artifact-graph/graph.d.ts +56 -0
  55. package/dist/core/artifact-graph/graph.js +141 -0
  56. package/dist/core/artifact-graph/index.d.ts +9 -0
  57. package/dist/core/artifact-graph/index.js +14 -0
  58. package/dist/core/artifact-graph/instruction-loader.d.ts +188 -0
  59. package/dist/core/artifact-graph/instruction-loader.js +233 -0
  60. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  61. package/dist/core/artifact-graph/outputs.js +39 -0
  62. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  63. package/dist/core/artifact-graph/resolver.js +257 -0
  64. package/dist/core/artifact-graph/schema.d.ts +13 -0
  65. package/dist/core/artifact-graph/schema.js +108 -0
  66. package/dist/core/artifact-graph/state.d.ts +12 -0
  67. package/dist/core/artifact-graph/state.js +31 -0
  68. package/dist/core/artifact-graph/types.d.ts +40 -0
  69. package/dist/core/artifact-graph/types.js +29 -0
  70. package/dist/core/available-tools.d.ts +17 -0
  71. package/dist/core/available-tools.js +43 -0
  72. package/dist/core/change-metadata/index.d.ts +2 -0
  73. package/dist/core/change-metadata/index.js +2 -0
  74. package/dist/core/change-metadata/schema.d.ts +19 -0
  75. package/dist/core/change-metadata/schema.js +30 -0
  76. package/dist/core/change-status-policy.d.ts +37 -0
  77. package/dist/core/change-status-policy.js +35 -0
  78. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  79. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  80. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  82. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/auggie.js +27 -0
  84. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  85. package/dist/core/command-generation/adapters/bob.js +32 -0
  86. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/claude.js +39 -0
  88. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  89. package/dist/core/command-generation/adapters/cline.js +27 -0
  90. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  91. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  92. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  93. package/dist/core/command-generation/adapters/codex.js +39 -0
  94. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  95. package/dist/core/command-generation/adapters/continue.js +28 -0
  96. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/costrict.js +27 -0
  98. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/crush.js +30 -0
  100. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  101. package/dist/core/command-generation/adapters/cursor.js +31 -0
  102. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  103. package/dist/core/command-generation/adapters/factory.js +27 -0
  104. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  105. package/dist/core/command-generation/adapters/gemini.js +26 -0
  106. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  107. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  108. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  109. package/dist/core/command-generation/adapters/iflow.js +29 -0
  110. package/dist/core/command-generation/adapters/index.d.ts +34 -0
  111. package/dist/core/command-generation/adapters/index.js +34 -0
  112. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/junie.js +26 -0
  114. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  115. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  116. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  117. package/dist/core/command-generation/adapters/kiro.js +26 -0
  118. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  119. package/dist/core/command-generation/adapters/lingma.js +30 -0
  120. package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
  121. package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
  122. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  123. package/dist/core/command-generation/adapters/opencode.js +29 -0
  124. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  125. package/dist/core/command-generation/adapters/pi.js +42 -0
  126. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  127. package/dist/core/command-generation/adapters/qoder.js +30 -0
  128. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  129. package/dist/core/command-generation/adapters/qwen.js +26 -0
  130. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  131. package/dist/core/command-generation/adapters/roocode.js +27 -0
  132. package/dist/core/command-generation/adapters/trae.d.ts +13 -0
  133. package/dist/core/command-generation/adapters/trae.js +48 -0
  134. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  135. package/dist/core/command-generation/adapters/windsurf.js +38 -0
  136. package/dist/core/command-generation/generator.d.ts +21 -0
  137. package/dist/core/command-generation/generator.js +27 -0
  138. package/dist/core/command-generation/index.d.ts +22 -0
  139. package/dist/core/command-generation/index.js +24 -0
  140. package/dist/core/command-generation/registry.d.ts +36 -0
  141. package/dist/core/command-generation/registry.js +102 -0
  142. package/dist/core/command-generation/types.d.ts +56 -0
  143. package/dist/core/command-generation/types.js +8 -0
  144. package/dist/core/command-generation/yaml.d.ts +22 -0
  145. package/dist/core/command-generation/yaml.js +38 -0
  146. package/dist/core/completions/command-registry.d.ts +3 -0
  147. package/dist/core/completions/command-registry.js +759 -0
  148. package/dist/core/completions/completion-provider.d.ts +71 -0
  149. package/dist/core/completions/completion-provider.js +129 -0
  150. package/dist/core/completions/factory.d.ts +64 -0
  151. package/dist/core/completions/factory.js +75 -0
  152. package/dist/core/completions/generators/bash-generator.d.ts +35 -0
  153. package/dist/core/completions/generators/bash-generator.js +230 -0
  154. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  155. package/dist/core/completions/generators/fish-generator.js +160 -0
  156. package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
  157. package/dist/core/completions/generators/powershell-generator.js +266 -0
  158. package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
  159. package/dist/core/completions/generators/zsh-generator.js +276 -0
  160. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  161. package/dist/core/completions/installers/bash-installer.js +321 -0
  162. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  163. package/dist/core/completions/installers/fish-installer.js +151 -0
  164. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  165. package/dist/core/completions/installers/powershell-installer.js +415 -0
  166. package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
  167. package/dist/core/completions/installers/zsh-installer.js +424 -0
  168. package/dist/core/completions/shared-flags.d.ts +13 -0
  169. package/dist/core/completions/shared-flags.js +33 -0
  170. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  171. package/dist/core/completions/templates/bash-templates.js +30 -0
  172. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  173. package/dist/core/completions/templates/fish-templates.js +45 -0
  174. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  175. package/dist/core/completions/templates/powershell-templates.js +34 -0
  176. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  177. package/dist/core/completions/templates/zsh-templates.js +45 -0
  178. package/dist/core/completions/types.d.ts +101 -0
  179. package/dist/core/completions/types.js +2 -0
  180. package/dist/core/config-prompts.d.ts +9 -0
  181. package/dist/core/config-prompts.js +34 -0
  182. package/dist/core/config-schema.d.ts +87 -0
  183. package/dist/core/config-schema.js +239 -0
  184. package/dist/core/config.d.ts +18 -0
  185. package/dist/core/config.js +40 -0
  186. package/dist/core/converters/json-converter.d.ts +6 -0
  187. package/dist/core/converters/json-converter.js +51 -0
  188. package/dist/core/file-state.d.ts +36 -0
  189. package/dist/core/file-state.js +112 -0
  190. package/dist/core/global-config.d.ts +51 -0
  191. package/dist/core/global-config.js +124 -0
  192. package/dist/core/id.d.ts +17 -0
  193. package/dist/core/id.js +30 -0
  194. package/dist/core/index.d.ts +6 -0
  195. package/dist/core/index.js +7 -0
  196. package/dist/core/init.d.ts +37 -0
  197. package/dist/core/init.js +614 -0
  198. package/dist/core/legacy-cleanup.d.ts +162 -0
  199. package/dist/core/legacy-cleanup.js +514 -0
  200. package/dist/core/list.d.ts +11 -0
  201. package/dist/core/list.js +214 -0
  202. package/dist/core/migration.d.ts +23 -0
  203. package/dist/core/migration.js +108 -0
  204. package/dist/core/openers.d.ts +77 -0
  205. package/dist/core/openers.js +251 -0
  206. package/dist/core/openspec-root.d.ts +45 -0
  207. package/dist/core/openspec-root.js +195 -0
  208. package/dist/core/parsers/change-parser.d.ts +13 -0
  209. package/dist/core/parsers/change-parser.js +198 -0
  210. package/dist/core/parsers/markdown-parser.d.ts +23 -0
  211. package/dist/core/parsers/markdown-parser.js +173 -0
  212. package/dist/core/parsers/requirement-blocks.d.ts +48 -0
  213. package/dist/core/parsers/requirement-blocks.js +235 -0
  214. package/dist/core/parsers/requirement-text.d.ts +55 -0
  215. package/dist/core/parsers/requirement-text.js +136 -0
  216. package/dist/core/parsers/spec-structure.d.ts +9 -0
  217. package/dist/core/parsers/spec-structure.js +88 -0
  218. package/dist/core/planning-home.d.ts +16 -0
  219. package/dist/core/planning-home.js +67 -0
  220. package/dist/core/profile-sync-drift.d.ts +38 -0
  221. package/dist/core/profile-sync-drift.js +201 -0
  222. package/dist/core/profiles.d.ts +26 -0
  223. package/dist/core/profiles.js +41 -0
  224. package/dist/core/project-config.d.ts +107 -0
  225. package/dist/core/project-config.js +373 -0
  226. package/dist/core/references.d.ts +63 -0
  227. package/dist/core/references.js +310 -0
  228. package/dist/core/relationship-health.d.ts +65 -0
  229. package/dist/core/relationship-health.js +64 -0
  230. package/dist/core/root-selection.d.ts +122 -0
  231. package/dist/core/root-selection.js +337 -0
  232. package/dist/core/schemas/base.schema.d.ts +13 -0
  233. package/dist/core/schemas/base.schema.js +18 -0
  234. package/dist/core/schemas/change.schema.d.ts +73 -0
  235. package/dist/core/schemas/change.schema.js +31 -0
  236. package/dist/core/schemas/index.d.ts +4 -0
  237. package/dist/core/schemas/index.js +4 -0
  238. package/dist/core/schemas/spec.schema.d.ts +18 -0
  239. package/dist/core/schemas/spec.schema.js +15 -0
  240. package/dist/core/shared/allowed-tools.d.ts +12 -0
  241. package/dist/core/shared/allowed-tools.js +12 -0
  242. package/dist/core/shared/index.d.ts +8 -0
  243. package/dist/core/shared/index.js +8 -0
  244. package/dist/core/shared/skill-generation.d.ts +49 -0
  245. package/dist/core/shared/skill-generation.js +100 -0
  246. package/dist/core/shared/tool-detection.d.ts +71 -0
  247. package/dist/core/shared/tool-detection.js +160 -0
  248. package/dist/core/specs-apply.d.ts +78 -0
  249. package/dist/core/specs-apply.js +428 -0
  250. package/dist/core/store/errors.d.ts +20 -0
  251. package/dist/core/store/errors.js +22 -0
  252. package/dist/core/store/foundation.d.ts +56 -0
  253. package/dist/core/store/foundation.js +251 -0
  254. package/dist/core/store/git.d.ts +23 -0
  255. package/dist/core/store/git.js +137 -0
  256. package/dist/core/store/index.d.ts +5 -0
  257. package/dist/core/store/index.js +5 -0
  258. package/dist/core/store/operations.d.ts +114 -0
  259. package/dist/core/store/operations.js +803 -0
  260. package/dist/core/store/registry.d.ts +58 -0
  261. package/dist/core/store/registry.js +275 -0
  262. package/dist/core/styles/palette.d.ts +7 -0
  263. package/dist/core/styles/palette.js +8 -0
  264. package/dist/core/templates/index.d.ts +8 -0
  265. package/dist/core/templates/index.js +9 -0
  266. package/dist/core/templates/skill-templates.d.ts +20 -0
  267. package/dist/core/templates/skill-templates.js +19 -0
  268. package/dist/core/templates/types.d.ts +19 -0
  269. package/dist/core/templates/types.js +5 -0
  270. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  271. package/dist/core/templates/workflows/apply-change.js +315 -0
  272. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  273. package/dist/core/templates/workflows/archive-change.js +278 -0
  274. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  275. package/dist/core/templates/workflows/bulk-archive-change.js +493 -0
  276. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  277. package/dist/core/templates/workflows/continue-change.js +239 -0
  278. package/dist/core/templates/workflows/explore.d.ts +10 -0
  279. package/dist/core/templates/workflows/explore.js +464 -0
  280. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  281. package/dist/core/templates/workflows/feedback.js +108 -0
  282. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  283. package/dist/core/templates/workflows/ff-change.js +205 -0
  284. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  285. package/dist/core/templates/workflows/new-change.js +148 -0
  286. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  287. package/dist/core/templates/workflows/onboard.js +566 -0
  288. package/dist/core/templates/workflows/propose.d.ts +10 -0
  289. package/dist/core/templates/workflows/propose.js +223 -0
  290. package/dist/core/templates/workflows/store-selection.d.ts +8 -0
  291. package/dist/core/templates/workflows/store-selection.js +8 -0
  292. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  293. package/dist/core/templates/workflows/sync-specs.js +291 -0
  294. package/dist/core/templates/workflows/update-change.d.ts +10 -0
  295. package/dist/core/templates/workflows/update-change.js +167 -0
  296. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  297. package/dist/core/templates/workflows/verify-change.js +339 -0
  298. package/dist/core/update.d.ts +82 -0
  299. package/dist/core/update.js +557 -0
  300. package/dist/core/validation/constants.d.ts +34 -0
  301. package/dist/core/validation/constants.js +40 -0
  302. package/dist/core/validation/types.d.ts +18 -0
  303. package/dist/core/validation/types.js +2 -0
  304. package/dist/core/validation/validator.d.ts +51 -0
  305. package/dist/core/validation/validator.js +498 -0
  306. package/dist/core/view.d.ts +8 -0
  307. package/dist/core/view.js +168 -0
  308. package/dist/core/working-set.d.ts +47 -0
  309. package/dist/core/working-set.js +43 -0
  310. package/dist/core/worksets.d.ts +75 -0
  311. package/dist/core/worksets.js +245 -0
  312. package/dist/core/zod-issues.d.ts +4 -0
  313. package/dist/core/zod-issues.js +10 -0
  314. package/dist/index.d.ts +3 -0
  315. package/dist/index.js +3 -0
  316. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  317. package/dist/prompts/searchable-multi-select.js +159 -0
  318. package/dist/telemetry/config.d.ts +38 -0
  319. package/dist/telemetry/config.js +136 -0
  320. package/dist/telemetry/index.d.ts +31 -0
  321. package/dist/telemetry/index.js +164 -0
  322. package/dist/ui/ascii-patterns.d.ts +16 -0
  323. package/dist/ui/ascii-patterns.js +133 -0
  324. package/dist/ui/welcome-screen.d.ts +10 -0
  325. package/dist/ui/welcome-screen.js +146 -0
  326. package/dist/utils/change-metadata.d.ts +55 -0
  327. package/dist/utils/change-metadata.js +141 -0
  328. package/dist/utils/change-utils.d.ts +85 -0
  329. package/dist/utils/change-utils.js +173 -0
  330. package/dist/utils/command-references.d.ts +18 -0
  331. package/dist/utils/command-references.js +20 -0
  332. package/dist/utils/file-system.d.ts +41 -0
  333. package/dist/utils/file-system.js +320 -0
  334. package/dist/utils/index.d.ts +6 -0
  335. package/dist/utils/index.js +9 -0
  336. package/dist/utils/interactive.d.ts +18 -0
  337. package/dist/utils/interactive.js +21 -0
  338. package/dist/utils/item-discovery.d.ts +4 -0
  339. package/dist/utils/item-discovery.js +72 -0
  340. package/dist/utils/match.d.ts +3 -0
  341. package/dist/utils/match.js +22 -0
  342. package/dist/utils/shell-detection.d.ts +20 -0
  343. package/dist/utils/shell-detection.js +41 -0
  344. package/dist/utils/task-progress.d.ts +17 -0
  345. package/dist/utils/task-progress.js +100 -0
  346. package/package.json +85 -0
  347. package/schemas/spec-driven/schema.yaml +153 -0
  348. package/schemas/spec-driven/templates/design.md +19 -0
  349. package/schemas/spec-driven/templates/proposal.md +23 -0
  350. package/schemas/spec-driven/templates/spec.md +8 -0
  351. package/schemas/spec-driven/templates/tasks.md +9 -0
  352. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,51 @@
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
+ /**
22
+ * Recursively collect every delta `spec.md` under a change's specs directory,
23
+ * so both the one-level (specs/<capability>/spec.md) and nested multi-area
24
+ * (specs/<area>/<capability>/spec.md) layouts are discovered (#1182b).
25
+ * Returns absolute paths, sorted for deterministic issue ordering.
26
+ */
27
+ private findDeltaSpecFiles;
28
+ private convertZodErrors;
29
+ private applySpecRules;
30
+ private applyChangeRules;
31
+ private enrichTopLevelError;
32
+ private extractNameFromPath;
33
+ private createReport;
34
+ isValid(report: ValidationReport): boolean;
35
+ private extractRequirementText;
36
+ private containsShallOrMust;
37
+ /**
38
+ * Build an error message for a requirement block whose body lacks SHALL/MUST.
39
+ *
40
+ * When the SHALL/MUST keyword already appears in the requirement header (e.g.
41
+ * `### Requirement: The system SHALL ...`) the original generic error
42
+ * ("must contain SHALL or MUST") is confusing because the keyword is visibly
43
+ * present in the spec. Per the OpenSpec conventions the keyword has to live
44
+ * on the requirement body line (the line right after the header), so we point
45
+ * the author at that exact fix when the keyword is found in the header only.
46
+ */
47
+ private buildMissingShallOrMustMessage;
48
+ private countScenarios;
49
+ private formatSectionList;
50
+ }
51
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1,498 @@
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, extractRequirementsSection } from '../parsers/requirement-blocks.js';
8
+ import { extractRequirementBody as extractRequirementBodyShared, containsShallOrMust as containsShallOrMustShared, countScenarios as countScenariosShared, } from '../parsers/requirement-text.js';
9
+ import { findMainSpecStructureIssues } from '../parsers/spec-structure.js';
10
+ import { FileSystemUtils } from '../../utils/file-system.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 : 'Unknown error';
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 : 'Unknown error';
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 : 'Unknown error';
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
+ // Discover delta specs at any depth so the nested multi-area layout
103
+ // (specs/<area>/<capability>/spec.md) is validated, not just the
104
+ // one-level specs/<capability>/spec.md layout (#1182b). The spec-driven
105
+ // specs glob is specs/**/*.md; delta files are always named spec.md.
106
+ const specFiles = await this.findDeltaSpecFiles(specsDir);
107
+ for (const specFile of specFiles) {
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 = FileSystemUtils.toPosixPath(path.relative(specsDir, specFile));
117
+ // Surface (as INFO, never a failure) the non-canonical level-3 headers
118
+ // the delta reader skipped while parsing ADDED/MODIFIED sections —
119
+ // without this note a stray divider like "### Documentation
120
+ // Requirements" would pass validate <change> while failing
121
+ // archive/validate <spec>. The list comes from the parse itself, so it
122
+ // reflects exactly what the reader skipped.
123
+ for (const stray of plan.skippedHeaders) {
124
+ const nameless = /^requirement:?$/i.test(stray.header);
125
+ issues.push({
126
+ level: 'INFO',
127
+ path: entryPath,
128
+ line: stray.line,
129
+ message: nameless
130
+ ? `Header "### ${stray.header}" in ${stray.section} is missing a requirement name and is ignored by validation. Add a name, e.g. "### Requirement: <name>".`
131
+ : `Header "### ${stray.header}" in ${stray.section} is not a "### Requirement:" header and is ignored by validation. Use "### Requirement: ${stray.header}" if it should be validated as a requirement.`,
132
+ });
133
+ }
134
+ const sectionNames = [];
135
+ if (plan.sectionPresence.added)
136
+ sectionNames.push('## ADDED Requirements');
137
+ if (plan.sectionPresence.modified)
138
+ sectionNames.push('## MODIFIED Requirements');
139
+ if (plan.sectionPresence.removed)
140
+ sectionNames.push('## REMOVED Requirements');
141
+ if (plan.sectionPresence.renamed)
142
+ sectionNames.push('## RENAMED Requirements');
143
+ const hasSections = sectionNames.length > 0;
144
+ const hasEntries = plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length > 0;
145
+ if (!hasEntries) {
146
+ if (hasSections)
147
+ emptySectionSpecs.push({ path: entryPath, sections: sectionNames });
148
+ else
149
+ missingHeaderSpecs.push(entryPath);
150
+ }
151
+ const addedNames = new Set();
152
+ const modifiedNames = new Set();
153
+ const removedNames = new Set();
154
+ const renamedFrom = new Set();
155
+ const renamedTo = new Set();
156
+ // Validate ADDED
157
+ for (const block of plan.added) {
158
+ const key = normalizeRequirementName(block.name);
159
+ totalDeltas++;
160
+ if (addedNames.has(key)) {
161
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in ADDED: "${block.name}"` });
162
+ }
163
+ else {
164
+ addedNames.add(key);
165
+ }
166
+ const requirementText = this.extractRequirementText(block.raw);
167
+ if (!requirementText) {
168
+ issues.push({
169
+ level: 'ERROR',
170
+ path: entryPath,
171
+ message: this.containsShallOrMust(block.name)
172
+ ? this.buildMissingShallOrMustMessage(`ADDED "${block.name}"`, block.name)
173
+ : `ADDED "${block.name}" is missing requirement text`,
174
+ });
175
+ }
176
+ else if (!this.containsShallOrMust(requirementText)) {
177
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage(`ADDED "${block.name}"`, block.name) });
178
+ }
179
+ const scenarioCount = this.countScenarios(block.raw);
180
+ if (scenarioCount < 1) {
181
+ issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must include at least one scenario` });
182
+ }
183
+ }
184
+ // Validate MODIFIED
185
+ for (const block of plan.modified) {
186
+ const key = normalizeRequirementName(block.name);
187
+ totalDeltas++;
188
+ if (modifiedNames.has(key)) {
189
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in MODIFIED: "${block.name}"` });
190
+ }
191
+ else {
192
+ modifiedNames.add(key);
193
+ }
194
+ const requirementText = this.extractRequirementText(block.raw);
195
+ if (!requirementText) {
196
+ issues.push({
197
+ level: 'ERROR',
198
+ path: entryPath,
199
+ message: this.containsShallOrMust(block.name)
200
+ ? this.buildMissingShallOrMustMessage(`MODIFIED "${block.name}"`, block.name)
201
+ : `MODIFIED "${block.name}" is missing requirement text`,
202
+ });
203
+ }
204
+ else if (!this.containsShallOrMust(requirementText)) {
205
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage(`MODIFIED "${block.name}"`, block.name) });
206
+ }
207
+ const scenarioCount = this.countScenarios(block.raw);
208
+ if (scenarioCount < 1) {
209
+ issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" must include at least one scenario` });
210
+ }
211
+ }
212
+ // Validate REMOVED (names only)
213
+ for (const name of plan.removed) {
214
+ const key = normalizeRequirementName(name);
215
+ totalDeltas++;
216
+ if (removedNames.has(key)) {
217
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in REMOVED: "${name}"` });
218
+ }
219
+ else {
220
+ removedNames.add(key);
221
+ }
222
+ }
223
+ // Validate RENAMED pairs
224
+ for (const { from, to } of plan.renamed) {
225
+ const fromKey = normalizeRequirementName(from);
226
+ const toKey = normalizeRequirementName(to);
227
+ totalDeltas++;
228
+ if (renamedFrom.has(fromKey)) {
229
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate FROM in RENAMED: "${from}"` });
230
+ }
231
+ else {
232
+ renamedFrom.add(fromKey);
233
+ }
234
+ if (renamedTo.has(toKey)) {
235
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate TO in RENAMED: "${to}"` });
236
+ }
237
+ else {
238
+ renamedTo.add(toKey);
239
+ }
240
+ }
241
+ // Cross-section conflicts (within the same spec file)
242
+ for (const n of modifiedNames) {
243
+ if (removedNames.has(n)) {
244
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both MODIFIED and REMOVED: "${n}"` });
245
+ }
246
+ if (addedNames.has(n)) {
247
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both MODIFIED and ADDED: "${n}"` });
248
+ }
249
+ }
250
+ for (const n of addedNames) {
251
+ if (removedNames.has(n)) {
252
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both ADDED and REMOVED: "${n}"` });
253
+ }
254
+ }
255
+ for (const { from, to } of plan.renamed) {
256
+ const fromKey = normalizeRequirementName(from);
257
+ const toKey = normalizeRequirementName(to);
258
+ if (modifiedNames.has(fromKey)) {
259
+ issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED references old name from RENAMED. Use new header for "${to}"` });
260
+ }
261
+ if (addedNames.has(toKey)) {
262
+ issues.push({ level: 'ERROR', path: entryPath, message: `RENAMED TO collides with ADDED for "${to}"` });
263
+ }
264
+ }
265
+ }
266
+ }
267
+ catch {
268
+ // If no specs dir, treat as no deltas
269
+ }
270
+ for (const { path: specPath, sections } of emptySectionSpecs) {
271
+ issues.push({
272
+ level: 'ERROR',
273
+ path: specPath,
274
+ message: `Delta sections ${this.formatSectionList(sections)} were found, but no requirement entries parsed. Ensure each section includes at least one "### Requirement:" block (REMOVED may use bullet list syntax).`,
275
+ });
276
+ }
277
+ for (const path of missingHeaderSpecs) {
278
+ issues.push({
279
+ level: 'ERROR',
280
+ path,
281
+ message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
282
+ });
283
+ }
284
+ if (totalDeltas === 0) {
285
+ issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
286
+ }
287
+ return this.createReport(issues);
288
+ }
289
+ /**
290
+ * Recursively collect every delta `spec.md` under a change's specs directory,
291
+ * so both the one-level (specs/<capability>/spec.md) and nested multi-area
292
+ * (specs/<area>/<capability>/spec.md) layouts are discovered (#1182b).
293
+ * Returns absolute paths, sorted for deterministic issue ordering.
294
+ */
295
+ async findDeltaSpecFiles(specsDir) {
296
+ const results = [];
297
+ const walk = async (dir) => {
298
+ let entries;
299
+ try {
300
+ entries = await fs.readdir(dir, { withFileTypes: true });
301
+ }
302
+ catch {
303
+ return;
304
+ }
305
+ for (const entry of entries) {
306
+ const full = path.join(dir, entry.name);
307
+ if (entry.isDirectory()) {
308
+ await walk(full);
309
+ }
310
+ else if (entry.isFile() && entry.name === 'spec.md') {
311
+ results.push(full);
312
+ }
313
+ }
314
+ };
315
+ await walk(specsDir);
316
+ return results.sort();
317
+ }
318
+ convertZodErrors(error) {
319
+ return error.issues.map(err => {
320
+ let message = err.message;
321
+ if (message === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
322
+ message = `${message}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
323
+ }
324
+ return {
325
+ level: 'ERROR',
326
+ path: err.path.join('.'),
327
+ message,
328
+ };
329
+ });
330
+ }
331
+ applySpecRules(spec, content) {
332
+ const issues = [];
333
+ for (const structuralIssue of findMainSpecStructureIssues(content)) {
334
+ issues.push({
335
+ level: 'ERROR',
336
+ path: 'file',
337
+ line: structuralIssue.line,
338
+ message: structuralIssue.message,
339
+ });
340
+ }
341
+ if (spec.overview.length < MIN_PURPOSE_LENGTH) {
342
+ issues.push({
343
+ level: 'WARNING',
344
+ path: 'overview',
345
+ message: VALIDATION_MESSAGES.PURPOSE_TOO_BRIEF,
346
+ });
347
+ }
348
+ spec.requirements.forEach((req, index) => {
349
+ if (req.text.length > MAX_REQUIREMENT_TEXT_LENGTH) {
350
+ issues.push({
351
+ level: 'INFO',
352
+ path: `requirements[${index}]`,
353
+ message: VALIDATION_MESSAGES.REQUIREMENT_TOO_LONG,
354
+ });
355
+ }
356
+ if (req.scenarios.length === 0) {
357
+ issues.push({
358
+ level: 'WARNING',
359
+ path: `requirements[${index}].scenarios`,
360
+ message: `${VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS}. ${VALIDATION_MESSAGES.GUIDE_SCENARIO_FORMAT}`,
361
+ });
362
+ }
363
+ });
364
+ // SHALL/MUST body-keyword enforcement for main specs (#1156). The main-spec
365
+ // parser collapses the requirement header into `text`, so we recover the
366
+ // header+body pairs here (the same source the delta path trusts) and reuse
367
+ // the delta detection: a body that omits the keyword errors, with the
368
+ // targeted "move it to the body line" hint when the keyword is in the header
369
+ // only and the generic message otherwise. Emitted exactly once per
370
+ // requirement (the Zod refine that used to emit a generic error is removed).
371
+ extractRequirementsSection(content).bodyBlocks.forEach((block, index) => {
372
+ const requirementText = this.extractRequirementText(block.raw);
373
+ if (!requirementText || !this.containsShallOrMust(requirementText)) {
374
+ issues.push({
375
+ level: 'ERROR',
376
+ path: `requirements[${index}]`,
377
+ message: this.buildMissingShallOrMustMessage(`Requirement "${block.name}"`, block.name),
378
+ });
379
+ }
380
+ });
381
+ return issues;
382
+ }
383
+ applyChangeRules(change, content) {
384
+ const issues = [];
385
+ const MIN_DELTA_DESCRIPTION_LENGTH = 10;
386
+ change.deltas.forEach((delta, index) => {
387
+ if (!delta.description || delta.description.length < MIN_DELTA_DESCRIPTION_LENGTH) {
388
+ issues.push({
389
+ level: 'WARNING',
390
+ path: `deltas[${index}].description`,
391
+ message: VALIDATION_MESSAGES.DELTA_DESCRIPTION_TOO_BRIEF,
392
+ });
393
+ }
394
+ if ((delta.operation === 'ADDED' || delta.operation === 'MODIFIED') &&
395
+ (!delta.requirements || delta.requirements.length === 0)) {
396
+ issues.push({
397
+ level: 'WARNING',
398
+ path: `deltas[${index}].requirements`,
399
+ message: `${delta.operation} ${VALIDATION_MESSAGES.DELTA_MISSING_REQUIREMENTS}`,
400
+ });
401
+ }
402
+ });
403
+ return issues;
404
+ }
405
+ enrichTopLevelError(itemId, baseMessage) {
406
+ const msg = baseMessage.trim();
407
+ if (msg === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
408
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
409
+ }
410
+ if (msg.includes('Spec must have a Purpose section') || msg.includes('Spec must have a Requirements section')) {
411
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_SPEC_SECTIONS}`;
412
+ }
413
+ if (msg.includes('Change must have a Why section') || msg.includes('Change must have a What Changes section')) {
414
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_CHANGE_SECTIONS}`;
415
+ }
416
+ return msg;
417
+ }
418
+ extractNameFromPath(filePath) {
419
+ const normalizedPath = FileSystemUtils.toPosixPath(filePath);
420
+ const parts = normalizedPath.split('/');
421
+ // Look for the directory name after 'specs' or 'changes'
422
+ for (let i = parts.length - 1; i >= 0; i--) {
423
+ if (parts[i] === 'specs' || parts[i] === 'changes') {
424
+ if (i < parts.length - 1) {
425
+ return parts[i + 1];
426
+ }
427
+ }
428
+ }
429
+ // Fallback to filename without extension if not in expected structure
430
+ const fileName = parts[parts.length - 1] ?? '';
431
+ const dotIndex = fileName.lastIndexOf('.');
432
+ return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
433
+ }
434
+ createReport(issues) {
435
+ const errors = issues.filter(i => i.level === 'ERROR').length;
436
+ const warnings = issues.filter(i => i.level === 'WARNING').length;
437
+ const info = issues.filter(i => i.level === 'INFO').length;
438
+ const valid = this.strictMode
439
+ ? errors === 0 && warnings === 0
440
+ : errors === 0;
441
+ return {
442
+ valid,
443
+ issues,
444
+ summary: {
445
+ errors,
446
+ warnings,
447
+ info,
448
+ },
449
+ };
450
+ }
451
+ isValid(report) {
452
+ return report.valid;
453
+ }
454
+ extractRequirementText(blockRaw) {
455
+ // Delegate to the shared, fence-/metadata-/multi-line-aware body reader.
456
+ // Validation intentionally does not use the parser/display header-title
457
+ // fallback for canonical `### Requirement:` blocks: #1280 requires a
458
+ // SHALL/MUST that appears only in the header to receive the body-keyword
459
+ // hint. Line 0 is the `### Requirement: ...` header.
460
+ const [, ...bodyLines] = blockRaw.split('\n');
461
+ return extractRequirementBodyShared(bodyLines) || undefined;
462
+ }
463
+ containsShallOrMust(text) {
464
+ return containsShallOrMustShared(text);
465
+ }
466
+ /**
467
+ * Build an error message for a requirement block whose body lacks SHALL/MUST.
468
+ *
469
+ * When the SHALL/MUST keyword already appears in the requirement header (e.g.
470
+ * `### Requirement: The system SHALL ...`) the original generic error
471
+ * ("must contain SHALL or MUST") is confusing because the keyword is visibly
472
+ * present in the spec. Per the OpenSpec conventions the keyword has to live
473
+ * on the requirement body line (the line right after the header), so we point
474
+ * the author at that exact fix when the keyword is found in the header only.
475
+ */
476
+ buildMissingShallOrMustMessage(prefix, blockName) {
477
+ const base = `${prefix} must contain SHALL or MUST`;
478
+ if (this.containsShallOrMust(blockName)) {
479
+ return `${base} in the requirement body, not only in the header. Move the SHALL/MUST statement to the line immediately after the "### Requirement: ..." header.`;
480
+ }
481
+ return base;
482
+ }
483
+ countScenarios(blockRaw) {
484
+ // Fence-aware count via the shared reader: a `#### Scenario:` inside a fenced
485
+ // example is not a real scenario. Drop the header line (index 0).
486
+ return countScenariosShared(blockRaw.split('\n').slice(1));
487
+ }
488
+ formatSectionList(sections) {
489
+ if (sections.length === 0)
490
+ return '';
491
+ if (sections.length === 1)
492
+ return sections[0];
493
+ const head = sections.slice(0, -1);
494
+ const last = sections[sections.length - 1];
495
+ return `${head.join(', ')} and ${last}`;
496
+ }
497
+ }
498
+ //# 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