@seanmars/tospec 0.4.2

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 (402) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/assets/dashboard/app.js +1032 -0
  4. package/assets/dashboard/chart.umd.js +14 -0
  5. package/assets/dashboard/index.html +30 -0
  6. package/assets/dashboard/style.css +675 -0
  7. package/bin/tospec.js +5 -0
  8. package/dist/cli/index.d.ts +5 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +330 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/commands/config.d.ts +36 -0
  13. package/dist/commands/config.d.ts.map +1 -0
  14. package/dist/commands/config.js +486 -0
  15. package/dist/commands/config.js.map +1 -0
  16. package/dist/commands/dashboard.d.ts +40 -0
  17. package/dist/commands/dashboard.d.ts.map +1 -0
  18. package/dist/commands/dashboard.js +385 -0
  19. package/dist/commands/dashboard.js.map +1 -0
  20. package/dist/commands/shared-output.d.ts +24 -0
  21. package/dist/commands/shared-output.d.ts.map +1 -0
  22. package/dist/commands/shared-output.js +51 -0
  23. package/dist/commands/shared-output.js.map +1 -0
  24. package/dist/commands/show.d.ts +17 -0
  25. package/dist/commands/show.d.ts.map +1 -0
  26. package/dist/commands/show.js +164 -0
  27. package/dist/commands/show.js.map +1 -0
  28. package/dist/commands/validate.d.ts +51 -0
  29. package/dist/commands/validate.d.ts.map +1 -0
  30. package/dist/commands/validate.js +416 -0
  31. package/dist/commands/validate.js.map +1 -0
  32. package/dist/commands/workflow/index.d.ts +17 -0
  33. package/dist/commands/workflow/index.d.ts.map +1 -0
  34. package/dist/commands/workflow/index.js +12 -0
  35. package/dist/commands/workflow/index.js.map +1 -0
  36. package/dist/commands/workflow/instructions.d.ts +35 -0
  37. package/dist/commands/workflow/instructions.d.ts.map +1 -0
  38. package/dist/commands/workflow/instructions.js +359 -0
  39. package/dist/commands/workflow/instructions.js.map +1 -0
  40. package/dist/commands/workflow/new-change.d.ts +17 -0
  41. package/dist/commands/workflow/new-change.d.ts.map +1 -0
  42. package/dist/commands/workflow/new-change.js +97 -0
  43. package/dist/commands/workflow/new-change.js.map +1 -0
  44. package/dist/commands/workflow/schemas.d.ts +10 -0
  45. package/dist/commands/workflow/schemas.d.ts.map +1 -0
  46. package/dist/commands/workflow/schemas.js +33 -0
  47. package/dist/commands/workflow/schemas.js.map +1 -0
  48. package/dist/commands/workflow/shared.d.ts +63 -0
  49. package/dist/commands/workflow/shared.d.ts.map +1 -0
  50. package/dist/commands/workflow/shared.js +111 -0
  51. package/dist/commands/workflow/shared.js.map +1 -0
  52. package/dist/commands/workflow/status.d.ts +14 -0
  53. package/dist/commands/workflow/status.d.ts.map +1 -0
  54. package/dist/commands/workflow/status.js +77 -0
  55. package/dist/commands/workflow/status.js.map +1 -0
  56. package/dist/commands/workflow/templates.d.ts +16 -0
  57. package/dist/commands/workflow/templates.d.ts.map +1 -0
  58. package/dist/commands/workflow/templates.js +60 -0
  59. package/dist/commands/workflow/templates.js.map +1 -0
  60. package/dist/core/archive.d.ts +33 -0
  61. package/dist/core/archive.d.ts.map +1 -0
  62. package/dist/core/archive.js +585 -0
  63. package/dist/core/archive.js.map +1 -0
  64. package/dist/core/artifact-graph/graph.d.ts +56 -0
  65. package/dist/core/artifact-graph/graph.d.ts.map +1 -0
  66. package/dist/core/artifact-graph/graph.js +141 -0
  67. package/dist/core/artifact-graph/graph.js.map +1 -0
  68. package/dist/core/artifact-graph/index.d.ts +9 -0
  69. package/dist/core/artifact-graph/index.d.ts.map +1 -0
  70. package/dist/core/artifact-graph/index.js +14 -0
  71. package/dist/core/artifact-graph/index.js.map +1 -0
  72. package/dist/core/artifact-graph/instruction-loader.d.ts +188 -0
  73. package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -0
  74. package/dist/core/artifact-graph/instruction-loader.js +243 -0
  75. package/dist/core/artifact-graph/instruction-loader.js.map +1 -0
  76. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  77. package/dist/core/artifact-graph/outputs.d.ts.map +1 -0
  78. package/dist/core/artifact-graph/outputs.js +39 -0
  79. package/dist/core/artifact-graph/outputs.js.map +1 -0
  80. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  81. package/dist/core/artifact-graph/resolver.d.ts.map +1 -0
  82. package/dist/core/artifact-graph/resolver.js +257 -0
  83. package/dist/core/artifact-graph/resolver.js.map +1 -0
  84. package/dist/core/artifact-graph/schema.d.ts +13 -0
  85. package/dist/core/artifact-graph/schema.d.ts.map +1 -0
  86. package/dist/core/artifact-graph/schema.js +108 -0
  87. package/dist/core/artifact-graph/schema.js.map +1 -0
  88. package/dist/core/artifact-graph/state.d.ts +12 -0
  89. package/dist/core/artifact-graph/state.d.ts.map +1 -0
  90. package/dist/core/artifact-graph/state.js +31 -0
  91. package/dist/core/artifact-graph/state.js.map +1 -0
  92. package/dist/core/artifact-graph/types.d.ts +55 -0
  93. package/dist/core/artifact-graph/types.d.ts.map +1 -0
  94. package/dist/core/artifact-graph/types.js +38 -0
  95. package/dist/core/artifact-graph/types.js.map +1 -0
  96. package/dist/core/available-tools.d.ts +17 -0
  97. package/dist/core/available-tools.d.ts.map +1 -0
  98. package/dist/core/available-tools.js +43 -0
  99. package/dist/core/available-tools.js.map +1 -0
  100. package/dist/core/change-metadata/index.d.ts +2 -0
  101. package/dist/core/change-metadata/index.d.ts.map +1 -0
  102. package/dist/core/change-metadata/index.js +2 -0
  103. package/dist/core/change-metadata/index.js.map +1 -0
  104. package/dist/core/change-metadata/schema.d.ts +12 -0
  105. package/dist/core/change-metadata/schema.d.ts.map +1 -0
  106. package/dist/core/change-metadata/schema.js +15 -0
  107. package/dist/core/change-metadata/schema.js.map +1 -0
  108. package/dist/core/change-presenter.d.ts +24 -0
  109. package/dist/core/change-presenter.d.ts.map +1 -0
  110. package/dist/core/change-presenter.js +135 -0
  111. package/dist/core/change-presenter.js.map +1 -0
  112. package/dist/core/change-status-policy.d.ts +35 -0
  113. package/dist/core/change-status-policy.d.ts.map +1 -0
  114. package/dist/core/change-status-policy.js +34 -0
  115. package/dist/core/change-status-policy.js.map +1 -0
  116. package/dist/core/command-generation/adapters/claude.d.ts +14 -0
  117. package/dist/core/command-generation/adapters/claude.d.ts.map +1 -0
  118. package/dist/core/command-generation/adapters/claude.js +38 -0
  119. package/dist/core/command-generation/adapters/claude.js.map +1 -0
  120. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  121. package/dist/core/command-generation/adapters/cursor.d.ts.map +1 -0
  122. package/dist/core/command-generation/adapters/cursor.js +31 -0
  123. package/dist/core/command-generation/adapters/cursor.js.map +1 -0
  124. package/dist/core/command-generation/adapters/index.d.ts +8 -0
  125. package/dist/core/command-generation/adapters/index.d.ts.map +1 -0
  126. package/dist/core/command-generation/adapters/index.js +8 -0
  127. package/dist/core/command-generation/adapters/index.js.map +1 -0
  128. package/dist/core/command-generation/generator.d.ts +21 -0
  129. package/dist/core/command-generation/generator.d.ts.map +1 -0
  130. package/dist/core/command-generation/generator.js +27 -0
  131. package/dist/core/command-generation/generator.js.map +1 -0
  132. package/dist/core/command-generation/index.d.ts +22 -0
  133. package/dist/core/command-generation/index.d.ts.map +1 -0
  134. package/dist/core/command-generation/index.js +24 -0
  135. package/dist/core/command-generation/index.js.map +1 -0
  136. package/dist/core/command-generation/registry.d.ts +24 -0
  137. package/dist/core/command-generation/registry.d.ts.map +1 -0
  138. package/dist/core/command-generation/registry.js +38 -0
  139. package/dist/core/command-generation/registry.js.map +1 -0
  140. package/dist/core/command-generation/types.d.ts +56 -0
  141. package/dist/core/command-generation/types.d.ts.map +1 -0
  142. package/dist/core/command-generation/types.js +8 -0
  143. package/dist/core/command-generation/types.js.map +1 -0
  144. package/dist/core/command-generation/yaml.d.ts +22 -0
  145. package/dist/core/command-generation/yaml.d.ts.map +1 -0
  146. package/dist/core/command-generation/yaml.js +38 -0
  147. package/dist/core/command-generation/yaml.js.map +1 -0
  148. package/dist/core/config-prompts.d.ts +9 -0
  149. package/dist/core/config-prompts.d.ts.map +1 -0
  150. package/dist/core/config-prompts.js +34 -0
  151. package/dist/core/config-prompts.js.map +1 -0
  152. package/dist/core/config-schema.d.ts +87 -0
  153. package/dist/core/config-schema.d.ts.map +1 -0
  154. package/dist/core/config-schema.js +239 -0
  155. package/dist/core/config-schema.js.map +1 -0
  156. package/dist/core/config.d.ts +19 -0
  157. package/dist/core/config.d.ts.map +1 -0
  158. package/dist/core/config.js +14 -0
  159. package/dist/core/config.js.map +1 -0
  160. package/dist/core/converters/json-converter.d.ts +6 -0
  161. package/dist/core/converters/json-converter.d.ts.map +1 -0
  162. package/dist/core/converters/json-converter.js +51 -0
  163. package/dist/core/converters/json-converter.js.map +1 -0
  164. package/dist/core/dashboard-activity.d.ts +50 -0
  165. package/dist/core/dashboard-activity.d.ts.map +1 -0
  166. package/dist/core/dashboard-activity.js +147 -0
  167. package/dist/core/dashboard-activity.js.map +1 -0
  168. package/dist/core/dashboard-data.d.ts +100 -0
  169. package/dist/core/dashboard-data.d.ts.map +1 -0
  170. package/dist/core/dashboard-data.js +368 -0
  171. package/dist/core/dashboard-data.js.map +1 -0
  172. package/dist/core/global-config.d.ts +51 -0
  173. package/dist/core/global-config.d.ts.map +1 -0
  174. package/dist/core/global-config.js +124 -0
  175. package/dist/core/global-config.js.map +1 -0
  176. package/dist/core/id.d.ts +17 -0
  177. package/dist/core/id.d.ts.map +1 -0
  178. package/dist/core/id.js +30 -0
  179. package/dist/core/id.js.map +1 -0
  180. package/dist/core/init.d.ts +34 -0
  181. package/dist/core/init.d.ts.map +1 -0
  182. package/dist/core/init.js +495 -0
  183. package/dist/core/init.js.map +1 -0
  184. package/dist/core/list.d.ts +12 -0
  185. package/dist/core/list.d.ts.map +1 -0
  186. package/dist/core/list.js +192 -0
  187. package/dist/core/list.js.map +1 -0
  188. package/dist/core/migrate.d.ts +52 -0
  189. package/dist/core/migrate.d.ts.map +1 -0
  190. package/dist/core/migrate.js +300 -0
  191. package/dist/core/migrate.js.map +1 -0
  192. package/dist/core/parsers/change-parser.d.ts +19 -0
  193. package/dist/core/parsers/change-parser.d.ts.map +1 -0
  194. package/dist/core/parsers/change-parser.js +204 -0
  195. package/dist/core/parsers/change-parser.js.map +1 -0
  196. package/dist/core/parsers/markdown-parser.d.ts +29 -0
  197. package/dist/core/parsers/markdown-parser.d.ts.map +1 -0
  198. package/dist/core/parsers/markdown-parser.js +181 -0
  199. package/dist/core/parsers/markdown-parser.js.map +1 -0
  200. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  201. package/dist/core/parsers/requirement-blocks.d.ts.map +1 -0
  202. package/dist/core/parsers/requirement-blocks.js +201 -0
  203. package/dist/core/parsers/requirement-blocks.js.map +1 -0
  204. package/dist/core/parsers/requirement-text.d.ts +55 -0
  205. package/dist/core/parsers/requirement-text.d.ts.map +1 -0
  206. package/dist/core/parsers/requirement-text.js +136 -0
  207. package/dist/core/parsers/requirement-text.js.map +1 -0
  208. package/dist/core/parsers/spec-structure.d.ts +9 -0
  209. package/dist/core/parsers/spec-structure.d.ts.map +1 -0
  210. package/dist/core/parsers/spec-structure.js +88 -0
  211. package/dist/core/parsers/spec-structure.js.map +1 -0
  212. package/dist/core/planning-home.d.ts +16 -0
  213. package/dist/core/planning-home.d.ts.map +1 -0
  214. package/dist/core/planning-home.js +67 -0
  215. package/dist/core/planning-home.js.map +1 -0
  216. package/dist/core/profiles.d.ts +24 -0
  217. package/dist/core/profiles.d.ts.map +1 -0
  218. package/dist/core/profiles.js +27 -0
  219. package/dist/core/profiles.js.map +1 -0
  220. package/dist/core/project-config.d.ts +100 -0
  221. package/dist/core/project-config.d.ts.map +1 -0
  222. package/dist/core/project-config.js +389 -0
  223. package/dist/core/project-config.js.map +1 -0
  224. package/dist/core/references.d.ts +40 -0
  225. package/dist/core/references.d.ts.map +1 -0
  226. package/dist/core/references.js +11 -0
  227. package/dist/core/references.js.map +1 -0
  228. package/dist/core/root-selection.d.ts +57 -0
  229. package/dist/core/root-selection.d.ts.map +1 -0
  230. package/dist/core/root-selection.js +98 -0
  231. package/dist/core/root-selection.js.map +1 -0
  232. package/dist/core/schemas/base.schema.d.ts +13 -0
  233. package/dist/core/schemas/base.schema.d.ts.map +1 -0
  234. package/dist/core/schemas/base.schema.js +18 -0
  235. package/dist/core/schemas/base.schema.js.map +1 -0
  236. package/dist/core/schemas/change.schema.d.ts +73 -0
  237. package/dist/core/schemas/change.schema.d.ts.map +1 -0
  238. package/dist/core/schemas/change.schema.js +31 -0
  239. package/dist/core/schemas/change.schema.js.map +1 -0
  240. package/dist/core/schemas/index.d.ts +4 -0
  241. package/dist/core/schemas/index.d.ts.map +1 -0
  242. package/dist/core/schemas/index.js +4 -0
  243. package/dist/core/schemas/index.js.map +1 -0
  244. package/dist/core/schemas/spec.schema.d.ts +18 -0
  245. package/dist/core/schemas/spec.schema.d.ts.map +1 -0
  246. package/dist/core/schemas/spec.schema.js +15 -0
  247. package/dist/core/schemas/spec.schema.js.map +1 -0
  248. package/dist/core/shared/index.d.ts +8 -0
  249. package/dist/core/shared/index.d.ts.map +1 -0
  250. package/dist/core/shared/index.js +8 -0
  251. package/dist/core/shared/index.js.map +1 -0
  252. package/dist/core/shared/skill-generation.d.ts +63 -0
  253. package/dist/core/shared/skill-generation.d.ts.map +1 -0
  254. package/dist/core/shared/skill-generation.js +125 -0
  255. package/dist/core/shared/skill-generation.js.map +1 -0
  256. package/dist/core/shared/tool-detection.d.ts +96 -0
  257. package/dist/core/shared/tool-detection.d.ts.map +1 -0
  258. package/dist/core/shared/tool-detection.js +207 -0
  259. package/dist/core/shared/tool-detection.js.map +1 -0
  260. package/dist/core/spec-presenter.d.ts +17 -0
  261. package/dist/core/spec-presenter.d.ts.map +1 -0
  262. package/dist/core/spec-presenter.js +98 -0
  263. package/dist/core/spec-presenter.js.map +1 -0
  264. package/dist/core/specs-apply.d.ts +78 -0
  265. package/dist/core/specs-apply.d.ts.map +1 -0
  266. package/dist/core/specs-apply.js +429 -0
  267. package/dist/core/specs-apply.js.map +1 -0
  268. package/dist/core/templates/index.d.ts +8 -0
  269. package/dist/core/templates/index.d.ts.map +1 -0
  270. package/dist/core/templates/index.js +9 -0
  271. package/dist/core/templates/index.js.map +1 -0
  272. package/dist/core/templates/skill-templates.d.ts +14 -0
  273. package/dist/core/templates/skill-templates.d.ts.map +1 -0
  274. package/dist/core/templates/skill-templates.js +13 -0
  275. package/dist/core/templates/skill-templates.js.map +1 -0
  276. package/dist/core/templates/types.d.ts +20 -0
  277. package/dist/core/templates/types.d.ts.map +1 -0
  278. package/dist/core/templates/types.js +5 -0
  279. package/dist/core/templates/types.js.map +1 -0
  280. package/dist/core/templates/workflows/apply.d.ts +10 -0
  281. package/dist/core/templates/workflows/apply.d.ts.map +1 -0
  282. package/dist/core/templates/workflows/apply.js +81 -0
  283. package/dist/core/templates/workflows/apply.js.map +1 -0
  284. package/dist/core/templates/workflows/archive.d.ts +10 -0
  285. package/dist/core/templates/workflows/archive.d.ts.map +1 -0
  286. package/dist/core/templates/workflows/archive.js +75 -0
  287. package/dist/core/templates/workflows/archive.js.map +1 -0
  288. package/dist/core/templates/workflows/explore.d.ts +10 -0
  289. package/dist/core/templates/workflows/explore.d.ts.map +1 -0
  290. package/dist/core/templates/workflows/explore.js +75 -0
  291. package/dist/core/templates/workflows/explore.js.map +1 -0
  292. package/dist/core/templates/workflows/issue.d.ts +10 -0
  293. package/dist/core/templates/workflows/issue.d.ts.map +1 -0
  294. package/dist/core/templates/workflows/issue.js +89 -0
  295. package/dist/core/templates/workflows/issue.js.map +1 -0
  296. package/dist/core/templates/workflows/propose.d.ts +10 -0
  297. package/dist/core/templates/workflows/propose.d.ts.map +1 -0
  298. package/dist/core/templates/workflows/propose.js +77 -0
  299. package/dist/core/templates/workflows/propose.js.map +1 -0
  300. package/dist/core/templates/workflows/sync.d.ts +14 -0
  301. package/dist/core/templates/workflows/sync.d.ts.map +1 -0
  302. package/dist/core/templates/workflows/sync.js +86 -0
  303. package/dist/core/templates/workflows/sync.js.map +1 -0
  304. package/dist/core/templates/workflows/update.d.ts +10 -0
  305. package/dist/core/templates/workflows/update.d.ts.map +1 -0
  306. package/dist/core/templates/workflows/update.js +99 -0
  307. package/dist/core/templates/workflows/update.js.map +1 -0
  308. package/dist/core/templates/workflows/verify.d.ts +9 -0
  309. package/dist/core/templates/workflows/verify.d.ts.map +1 -0
  310. package/dist/core/templates/workflows/verify.js +44 -0
  311. package/dist/core/templates/workflows/verify.js.map +1 -0
  312. package/dist/core/update.d.ts +46 -0
  313. package/dist/core/update.d.ts.map +1 -0
  314. package/dist/core/update.js +296 -0
  315. package/dist/core/update.js.map +1 -0
  316. package/dist/core/validation/constants.d.ts +34 -0
  317. package/dist/core/validation/constants.d.ts.map +1 -0
  318. package/dist/core/validation/constants.js +40 -0
  319. package/dist/core/validation/constants.js.map +1 -0
  320. package/dist/core/validation/section-validator.d.ts +19 -0
  321. package/dist/core/validation/section-validator.d.ts.map +1 -0
  322. package/dist/core/validation/section-validator.js +59 -0
  323. package/dist/core/validation/section-validator.js.map +1 -0
  324. package/dist/core/validation/types.d.ts +18 -0
  325. package/dist/core/validation/types.d.ts.map +1 -0
  326. package/dist/core/validation/types.js +2 -0
  327. package/dist/core/validation/types.js.map +1 -0
  328. package/dist/core/validation/validator.d.ts +51 -0
  329. package/dist/core/validation/validator.d.ts.map +1 -0
  330. package/dist/core/validation/validator.js +469 -0
  331. package/dist/core/validation/validator.js.map +1 -0
  332. package/dist/index.d.ts +2 -0
  333. package/dist/index.d.ts.map +1 -0
  334. package/dist/index.js +2 -0
  335. package/dist/index.js.map +1 -0
  336. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  337. package/dist/prompts/searchable-multi-select.d.ts.map +1 -0
  338. package/dist/prompts/searchable-multi-select.js +158 -0
  339. package/dist/prompts/searchable-multi-select.js.map +1 -0
  340. package/dist/ui/welcome-screen.d.ts +5 -0
  341. package/dist/ui/welcome-screen.d.ts.map +1 -0
  342. package/dist/ui/welcome-screen.js +22 -0
  343. package/dist/ui/welcome-screen.js.map +1 -0
  344. package/dist/utils/change-metadata.d.ts +55 -0
  345. package/dist/utils/change-metadata.d.ts.map +1 -0
  346. package/dist/utils/change-metadata.js +141 -0
  347. package/dist/utils/change-metadata.js.map +1 -0
  348. package/dist/utils/change-utils.d.ts +73 -0
  349. package/dist/utils/change-utils.d.ts.map +1 -0
  350. package/dist/utils/change-utils.js +194 -0
  351. package/dist/utils/change-utils.js.map +1 -0
  352. package/dist/utils/command-references.d.ts +18 -0
  353. package/dist/utils/command-references.d.ts.map +1 -0
  354. package/dist/utils/command-references.js +20 -0
  355. package/dist/utils/command-references.js.map +1 -0
  356. package/dist/utils/file-system.d.ts +41 -0
  357. package/dist/utils/file-system.d.ts.map +1 -0
  358. package/dist/utils/file-system.js +320 -0
  359. package/dist/utils/file-system.js.map +1 -0
  360. package/dist/utils/index.d.ts +6 -0
  361. package/dist/utils/index.d.ts.map +1 -0
  362. package/dist/utils/index.js +9 -0
  363. package/dist/utils/index.js.map +1 -0
  364. package/dist/utils/interactive.d.ts +18 -0
  365. package/dist/utils/interactive.d.ts.map +1 -0
  366. package/dist/utils/interactive.js +21 -0
  367. package/dist/utils/interactive.js.map +1 -0
  368. package/dist/utils/item-discovery.d.ts +14 -0
  369. package/dist/utils/item-discovery.d.ts.map +1 -0
  370. package/dist/utils/item-discovery.js +104 -0
  371. package/dist/utils/item-discovery.js.map +1 -0
  372. package/dist/utils/link.d.ts +22 -0
  373. package/dist/utils/link.d.ts.map +1 -0
  374. package/dist/utils/link.js +85 -0
  375. package/dist/utils/link.js.map +1 -0
  376. package/dist/utils/match.d.ts +3 -0
  377. package/dist/utils/match.d.ts.map +1 -0
  378. package/dist/utils/match.js +22 -0
  379. package/dist/utils/match.js.map +1 -0
  380. package/dist/utils/shell-detection.d.ts +20 -0
  381. package/dist/utils/shell-detection.d.ts.map +1 -0
  382. package/dist/utils/shell-detection.js +41 -0
  383. package/dist/utils/shell-detection.js.map +1 -0
  384. package/dist/utils/task-progress.d.ts +32 -0
  385. package/dist/utils/task-progress.d.ts.map +1 -0
  386. package/dist/utils/task-progress.js +129 -0
  387. package/dist/utils/task-progress.js.map +1 -0
  388. package/dist/utils/timestamp.d.ts +9 -0
  389. package/dist/utils/timestamp.d.ts.map +1 -0
  390. package/dist/utils/timestamp.js +18 -0
  391. package/dist/utils/timestamp.js.map +1 -0
  392. package/package.json +55 -0
  393. package/schemas/issue/schema.yaml +34 -0
  394. package/schemas/issue/templates/spec.md +13 -0
  395. package/schemas/issue/templates/task.md +21 -0
  396. package/schemas/issue/templates/ticket.md +2 -0
  397. package/schemas/sdd/schema.yaml +55 -0
  398. package/schemas/sdd/templates/design.md +15 -0
  399. package/schemas/sdd/templates/proposal.md +15 -0
  400. package/schemas/sdd/templates/spec.md +13 -0
  401. package/schemas/sdd/templates/tasks.md +11 -0
  402. package/schemas/sdd/templates/ticket.md +2 -0
@@ -0,0 +1,19 @@
1
+ import type { ValidationIssue } from './types.js';
2
+ export interface SectionValidationRules {
3
+ requiredSections: string[];
4
+ minSectionLength: Record<string, number>;
5
+ }
6
+ export interface SectionValidationContext {
7
+ /** Reported as ValidationIssue.path (typically the file's relative path). */
8
+ path: string;
9
+ /** Shown in the "missing section" message so authors know where to look. */
10
+ templateRef?: string;
11
+ }
12
+ /**
13
+ * Validates a document's headings against schema-declared rules, for
14
+ * artifacts (ticket.md, design.md, task.md, ...) that have no Requirement
15
+ * grammar to check structurally. Rules live in schema.yaml so new document
16
+ * types don't require validator code changes.
17
+ */
18
+ export declare function validateSections(content: string, rules: SectionValidationRules, context: SectionValidationContext): ValidationIssue[];
19
+ //# sourceMappingURL=section-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACvC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,sBAAsB,EAC7B,OAAO,EAAE,wBAAwB,GAChC,eAAe,EAAE,CAwCnB"}
@@ -0,0 +1,59 @@
1
+ import { MarkdownParser } from '../parsers/markdown-parser.js';
2
+ function findSectionByTitle(sections, title) {
3
+ const target = title.trim().toLowerCase();
4
+ for (const section of sections) {
5
+ if (section.title.trim().toLowerCase() === target) {
6
+ return section;
7
+ }
8
+ const found = findSectionByTitle(section.children, title);
9
+ if (found) {
10
+ return found;
11
+ }
12
+ }
13
+ return undefined;
14
+ }
15
+ /**
16
+ * Validates a document's headings against schema-declared rules, for
17
+ * artifacts (ticket.md, design.md, task.md, ...) that have no Requirement
18
+ * grammar to check structurally. Rules live in schema.yaml so new document
19
+ * types don't require validator code changes.
20
+ */
21
+ export function validateSections(content, rules, context) {
22
+ const issues = [];
23
+ const sections = new MarkdownParser(content).getSections();
24
+ for (const requiredTitle of rules.requiredSections) {
25
+ const section = findSectionByTitle(sections, requiredTitle);
26
+ if (!section) {
27
+ const hint = context.templateRef ? ` (see template: ${context.templateRef})` : '';
28
+ issues.push({
29
+ level: 'ERROR',
30
+ path: context.path,
31
+ message: `Missing required section "${requiredTitle}"${hint}`,
32
+ });
33
+ continue;
34
+ }
35
+ if (!section.content.trim()) {
36
+ issues.push({
37
+ level: 'WARNING',
38
+ path: context.path,
39
+ message: `Section "${requiredTitle}" is empty`,
40
+ });
41
+ }
42
+ }
43
+ for (const [sectionTitle, minLength] of Object.entries(rules.minSectionLength)) {
44
+ const section = findSectionByTitle(sections, sectionTitle);
45
+ if (!section) {
46
+ continue; // absence is already reported above when also required
47
+ }
48
+ const length = section.content.trim().length;
49
+ if (length > 0 && length < minLength) {
50
+ issues.push({
51
+ level: 'WARNING',
52
+ path: context.path,
53
+ message: `Section "${sectionTitle}" is shorter than the recommended ${minLength} characters (found ${length})`,
54
+ });
55
+ }
56
+ }
57
+ return issues;
58
+ }
59
+ //# sourceMappingURL=section-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-validator.js","sourceRoot":"","sources":["../../../src/core/validation/section-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAgB,MAAM,+BAA+B,CAAC;AAe7E,SAAS,kBAAkB,CAAC,QAAmB,EAAE,KAAa;IAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YAClD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC1D,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,KAA6B,EAC7B,OAAiC;IAEjC,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IAE3D,KAAK,MAAM,aAAa,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,kBAAkB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,mBAAmB,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,6BAA6B,aAAa,IAAI,IAAI,EAAE;aAC9D,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,SAAS;gBAChB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,YAAY,aAAa,YAAY;aAC/C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC/E,MAAM,OAAO,GAAG,kBAAkB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC3D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,SAAS,CAAC,uDAAuD;QACnE,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC;QAC7C,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,SAAS,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,SAAS;gBAChB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,YAAY,YAAY,qCAAqC,SAAS,sBAAsB,MAAM,GAAG;aAC/G,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -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 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":""}
@@ -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 Tospec 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 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/validator.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAoC,MAAM,YAAY,CAAC;AAehF,qBAAa,SAAS;IACpB,OAAO,CAAC,UAAU,CAAU;gBAEhB,UAAU,GAAE,OAAe;IAIjC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8B/D;;OAEG;IACG,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkBjF,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+BjE;;;;;;;;OAQG;IACG,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkK5E;;;;;OAKG;YACW,kBAAkB;IAsBhC,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,cAAc;IA2DtB,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO;IAI1C,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,mBAAmB;IAI3B;;;;;;;;;OASG;IACH,OAAO,CAAC,8BAA8B;IAQtC,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,iBAAiB;CAO1B"}
@@ -0,0 +1,469 @@
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 sdd
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
+ 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: `Duplicate requirement in ADDED: "${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: `ADDED "${block.name}" is missing requirement text` });
152
+ }
153
+ else if (!this.containsShallOrMust(requirementText)) {
154
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage(`ADDED "${block.name}"`, block.name) });
155
+ }
156
+ const scenarioCount = this.countScenarios(block.raw);
157
+ if (scenarioCount < 1) {
158
+ issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must include at least one scenario` });
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: `Duplicate requirement in MODIFIED: "${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: `MODIFIED "${block.name}" is missing requirement text` });
174
+ }
175
+ else if (!this.containsShallOrMust(requirementText)) {
176
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingShallOrMustMessage(`MODIFIED "${block.name}"`, block.name) });
177
+ }
178
+ const scenarioCount = this.countScenarios(block.raw);
179
+ if (scenarioCount < 1) {
180
+ issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" must include at least one scenario` });
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: `Duplicate requirement in REMOVED: "${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: `Duplicate FROM in RENAMED: "${from}"` });
201
+ }
202
+ else {
203
+ renamedFrom.add(fromKey);
204
+ }
205
+ if (renamedTo.has(toKey)) {
206
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate TO in RENAMED: "${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: `Requirement present in both MODIFIED and REMOVED: "${n}"` });
216
+ }
217
+ if (addedNames.has(n)) {
218
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both MODIFIED and ADDED: "${n}"` });
219
+ }
220
+ }
221
+ for (const n of addedNames) {
222
+ if (removedNames.has(n)) {
223
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both ADDED and REMOVED: "${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: `MODIFIED references old name from RENAMED. Use new header for "${to}"` });
231
+ }
232
+ if (addedNames.has(toKey)) {
233
+ issues.push({ level: 'ERROR', path: entryPath, message: `RENAMED TO collides with ADDED for "${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: `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).`,
246
+ });
247
+ }
248
+ for (const path of missingHeaderSpecs) {
249
+ issues.push({
250
+ level: 'ERROR',
251
+ path,
252
+ message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
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
+ /**
261
+ * Recursively collect every delta `spec.md` under a change's specs directory,
262
+ * so both the one-level (specs/<capability>/spec.md) and nested multi-area
263
+ * (specs/<area>/<capability>/spec.md) layouts are discovered (#1182b).
264
+ * Returns absolute paths, sorted for deterministic issue ordering.
265
+ */
266
+ async findDeltaSpecFiles(specsDir) {
267
+ const results = [];
268
+ const walk = async (dir) => {
269
+ let entries;
270
+ try {
271
+ entries = await fs.readdir(dir, { withFileTypes: true });
272
+ }
273
+ catch {
274
+ return;
275
+ }
276
+ for (const entry of entries) {
277
+ const full = path.join(dir, entry.name);
278
+ if (entry.isDirectory()) {
279
+ await walk(full);
280
+ }
281
+ else if (entry.isFile() && entry.name === 'spec.md') {
282
+ results.push(full);
283
+ }
284
+ }
285
+ };
286
+ await walk(specsDir);
287
+ return results.sort();
288
+ }
289
+ convertZodErrors(error) {
290
+ return error.issues.map(err => {
291
+ let message = err.message;
292
+ if (message === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
293
+ message = `${message}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
294
+ }
295
+ return {
296
+ level: 'ERROR',
297
+ path: err.path.join('.'),
298
+ message,
299
+ };
300
+ });
301
+ }
302
+ applySpecRules(spec, content) {
303
+ const issues = [];
304
+ for (const structuralIssue of findMainSpecStructureIssues(content)) {
305
+ issues.push({
306
+ level: 'ERROR',
307
+ path: 'file',
308
+ line: structuralIssue.line,
309
+ message: structuralIssue.message,
310
+ });
311
+ }
312
+ if (spec.overview.length < MIN_PURPOSE_LENGTH) {
313
+ issues.push({
314
+ level: 'WARNING',
315
+ path: 'overview',
316
+ message: VALIDATION_MESSAGES.PURPOSE_TOO_BRIEF,
317
+ });
318
+ }
319
+ spec.requirements.forEach((req, index) => {
320
+ if (req.text.length > MAX_REQUIREMENT_TEXT_LENGTH) {
321
+ issues.push({
322
+ level: 'INFO',
323
+ path: `requirements[${index}]`,
324
+ message: VALIDATION_MESSAGES.REQUIREMENT_TOO_LONG,
325
+ });
326
+ }
327
+ if (req.scenarios.length === 0) {
328
+ issues.push({
329
+ level: 'WARNING',
330
+ path: `requirements[${index}].scenarios`,
331
+ message: `${VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS}. ${VALIDATION_MESSAGES.GUIDE_SCENARIO_FORMAT}`,
332
+ });
333
+ }
334
+ });
335
+ // SHALL/MUST body-keyword enforcement for main specs (#1156). The main-spec
336
+ // parser collapses the requirement header into `text`, so we recover the
337
+ // header+body pairs here (the same source the delta path trusts) and reuse
338
+ // the delta detection: a body that omits the keyword errors, with the
339
+ // targeted "move it to the body line" hint when the keyword is in the header
340
+ // only and the generic message otherwise. Emitted exactly once per
341
+ // requirement (the Zod refine that used to emit a generic error is removed).
342
+ extractRequirementsSection(content).bodyBlocks.forEach((block, index) => {
343
+ const requirementText = this.extractRequirementText(block.raw);
344
+ if (!requirementText || !this.containsShallOrMust(requirementText)) {
345
+ issues.push({
346
+ level: 'ERROR',
347
+ path: `requirements[${index}]`,
348
+ message: this.buildMissingShallOrMustMessage(`Requirement "${block.name}"`, block.name),
349
+ });
350
+ }
351
+ });
352
+ return issues;
353
+ }
354
+ applyChangeRules(change, content) {
355
+ const issues = [];
356
+ const MIN_DELTA_DESCRIPTION_LENGTH = 10;
357
+ change.deltas.forEach((delta, index) => {
358
+ if (!delta.description || delta.description.length < MIN_DELTA_DESCRIPTION_LENGTH) {
359
+ issues.push({
360
+ level: 'WARNING',
361
+ path: `deltas[${index}].description`,
362
+ message: VALIDATION_MESSAGES.DELTA_DESCRIPTION_TOO_BRIEF,
363
+ });
364
+ }
365
+ if ((delta.operation === 'ADDED' || delta.operation === 'MODIFIED') &&
366
+ (!delta.requirements || delta.requirements.length === 0)) {
367
+ issues.push({
368
+ level: 'WARNING',
369
+ path: `deltas[${index}].requirements`,
370
+ message: `${delta.operation} ${VALIDATION_MESSAGES.DELTA_MISSING_REQUIREMENTS}`,
371
+ });
372
+ }
373
+ });
374
+ return issues;
375
+ }
376
+ enrichTopLevelError(itemId, baseMessage) {
377
+ const msg = baseMessage.trim();
378
+ if (msg === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
379
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
380
+ }
381
+ if (msg.includes('Spec must have a Purpose section') || msg.includes('Spec must have a Requirements section')) {
382
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_SPEC_SECTIONS}`;
383
+ }
384
+ if (msg.includes('Change must have a Why section') || msg.includes('Change must have a What Changes section')) {
385
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_CHANGE_SECTIONS}`;
386
+ }
387
+ return msg;
388
+ }
389
+ extractNameFromPath(filePath) {
390
+ const normalizedPath = FileSystemUtils.toPosixPath(filePath);
391
+ const parts = normalizedPath.split('/');
392
+ // Look for the directory name after 'specs' or 'changes'
393
+ for (let i = parts.length - 1; i >= 0; i--) {
394
+ if (parts[i] === 'specs' || parts[i] === 'changes') {
395
+ if (i < parts.length - 1) {
396
+ return parts[i + 1];
397
+ }
398
+ }
399
+ }
400
+ // Fallback to filename without extension if not in expected structure
401
+ const fileName = parts[parts.length - 1] ?? '';
402
+ const dotIndex = fileName.lastIndexOf('.');
403
+ return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
404
+ }
405
+ createReport(issues) {
406
+ const errors = issues.filter(i => i.level === 'ERROR').length;
407
+ const warnings = issues.filter(i => i.level === 'WARNING').length;
408
+ const info = issues.filter(i => i.level === 'INFO').length;
409
+ const valid = this.strictMode
410
+ ? errors === 0 && warnings === 0
411
+ : errors === 0;
412
+ return {
413
+ valid,
414
+ issues,
415
+ summary: {
416
+ errors,
417
+ warnings,
418
+ info,
419
+ },
420
+ };
421
+ }
422
+ isValid(report) {
423
+ return report.valid;
424
+ }
425
+ extractRequirementText(blockRaw) {
426
+ // Delegate to the shared, fence-/metadata-/multi-line-aware body reader.
427
+ // Validation intentionally does not use the parser/display header-title
428
+ // fallback for canonical `### Requirement:` blocks: a SHALL/MUST that
429
+ // appears only in the header must still receive the body-keyword hint.
430
+ // Line 0 is the `### Requirement: ...` header.
431
+ const [, ...bodyLines] = blockRaw.split('\n');
432
+ return extractRequirementBodyShared(bodyLines) || undefined;
433
+ }
434
+ containsShallOrMust(text) {
435
+ return containsShallOrMustShared(text);
436
+ }
437
+ /**
438
+ * Build an error message for a requirement block whose body lacks SHALL/MUST.
439
+ *
440
+ * When the SHALL/MUST keyword already appears in the requirement header (e.g.
441
+ * `### Requirement: The system SHALL ...`) the original generic error
442
+ * ("must contain SHALL or MUST") is confusing because the keyword is visibly
443
+ * present in the spec. Per the Tospec conventions the keyword has to live
444
+ * on the requirement body line (the line right after the header), so we point
445
+ * the author at that exact fix when the keyword is found in the header only.
446
+ */
447
+ buildMissingShallOrMustMessage(prefix, blockName) {
448
+ const base = `${prefix} must contain SHALL or MUST`;
449
+ if (this.containsShallOrMust(blockName)) {
450
+ return `${base} in the requirement body, not only in the header. Move the SHALL/MUST statement to the line immediately after the "### Requirement: ..." header.`;
451
+ }
452
+ return base;
453
+ }
454
+ countScenarios(blockRaw) {
455
+ // Fence-aware count via the shared reader: a `#### Scenario:` inside a fenced
456
+ // example is not a real scenario. Drop the header line (index 0).
457
+ return countScenariosShared(blockRaw.split('\n').slice(1));
458
+ }
459
+ formatSectionList(sections) {
460
+ if (sections.length === 0)
461
+ return '';
462
+ if (sections.length === 1)
463
+ return sections[0];
464
+ const head = sections.slice(0, -1);
465
+ const last = sections[sections.length - 1];
466
+ return `${head.join(', ')} and ${last}`;
467
+ }
468
+ }
469
+ //# sourceMappingURL=validator.js.map