@superkou/openspec 1.4.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 (373) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +213 -0
  3. package/bin/openspec.js +5 -0
  4. package/dist/cli/index.d.ts +5 -0
  5. package/dist/cli/index.js +544 -0
  6. package/dist/commands/change.d.ts +35 -0
  7. package/dist/commands/change.js +277 -0
  8. package/dist/commands/completion.d.ts +72 -0
  9. package/dist/commands/completion.js +264 -0
  10. package/dist/commands/config.d.ts +36 -0
  11. package/dist/commands/config.js +611 -0
  12. package/dist/commands/context-store.d.ts +3 -0
  13. package/dist/commands/context-store.js +475 -0
  14. package/dist/commands/feedback.d.ts +9 -0
  15. package/dist/commands/feedback.js +183 -0
  16. package/dist/commands/initiative.d.ts +13 -0
  17. package/dist/commands/initiative.js +318 -0
  18. package/dist/commands/schema.d.ts +6 -0
  19. package/dist/commands/schema.js +869 -0
  20. package/dist/commands/show.d.ts +14 -0
  21. package/dist/commands/show.js +132 -0
  22. package/dist/commands/spec.d.ts +15 -0
  23. package/dist/commands/spec.js +225 -0
  24. package/dist/commands/validate.d.ts +24 -0
  25. package/dist/commands/validate.js +294 -0
  26. package/dist/commands/workflow/index.d.ts +19 -0
  27. package/dist/commands/workflow/index.js +13 -0
  28. package/dist/commands/workflow/initiative-link.d.ts +24 -0
  29. package/dist/commands/workflow/initiative-link.js +47 -0
  30. package/dist/commands/workflow/instructions.d.ts +29 -0
  31. package/dist/commands/workflow/instructions.js +344 -0
  32. package/dist/commands/workflow/new-change.d.ts +17 -0
  33. package/dist/commands/workflow/new-change.js +141 -0
  34. package/dist/commands/workflow/schemas.d.ts +10 -0
  35. package/dist/commands/workflow/schemas.js +34 -0
  36. package/dist/commands/workflow/set-change.d.ts +13 -0
  37. package/dist/commands/workflow/set-change.js +87 -0
  38. package/dist/commands/workflow/shared.d.ts +59 -0
  39. package/dist/commands/workflow/shared.js +116 -0
  40. package/dist/commands/workflow/status.d.ts +14 -0
  41. package/dist/commands/workflow/status.js +90 -0
  42. package/dist/commands/workflow/templates.d.ts +16 -0
  43. package/dist/commands/workflow/templates.js +69 -0
  44. package/dist/commands/workspace/context-status.d.ts +4 -0
  45. package/dist/commands/workspace/context-status.js +59 -0
  46. package/dist/commands/workspace/open-target-selection.d.ts +13 -0
  47. package/dist/commands/workspace/open-target-selection.js +146 -0
  48. package/dist/commands/workspace/open-view.d.ts +62 -0
  49. package/dist/commands/workspace/open-view.js +249 -0
  50. package/dist/commands/workspace/open.d.ts +37 -0
  51. package/dist/commands/workspace/open.js +110 -0
  52. package/dist/commands/workspace/opener-selection.d.ts +11 -0
  53. package/dist/commands/workspace/opener-selection.js +98 -0
  54. package/dist/commands/workspace/operations.d.ts +29 -0
  55. package/dist/commands/workspace/operations.js +543 -0
  56. package/dist/commands/workspace/prompt-theme.d.ts +29 -0
  57. package/dist/commands/workspace/prompt-theme.js +24 -0
  58. package/dist/commands/workspace/registration.d.ts +13 -0
  59. package/dist/commands/workspace/registration.js +84 -0
  60. package/dist/commands/workspace/selection.d.ts +8 -0
  61. package/dist/commands/workspace/selection.js +104 -0
  62. package/dist/commands/workspace/setup-prompts.d.ts +13 -0
  63. package/dist/commands/workspace/setup-prompts.js +121 -0
  64. package/dist/commands/workspace/types.d.ts +103 -0
  65. package/dist/commands/workspace/types.js +36 -0
  66. package/dist/commands/workspace.d.ts +5 -0
  67. package/dist/commands/workspace.js +577 -0
  68. package/dist/core/archive.d.ts +11 -0
  69. package/dist/core/archive.js +318 -0
  70. package/dist/core/artifact-graph/graph.d.ts +56 -0
  71. package/dist/core/artifact-graph/graph.js +141 -0
  72. package/dist/core/artifact-graph/index.d.ts +9 -0
  73. package/dist/core/artifact-graph/index.js +14 -0
  74. package/dist/core/artifact-graph/instruction-loader.d.ts +183 -0
  75. package/dist/core/artifact-graph/instruction-loader.js +256 -0
  76. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  77. package/dist/core/artifact-graph/outputs.js +39 -0
  78. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  79. package/dist/core/artifact-graph/resolver.js +257 -0
  80. package/dist/core/artifact-graph/schema.d.ts +13 -0
  81. package/dist/core/artifact-graph/schema.js +108 -0
  82. package/dist/core/artifact-graph/state.d.ts +12 -0
  83. package/dist/core/artifact-graph/state.js +31 -0
  84. package/dist/core/artifact-graph/types.d.ts +40 -0
  85. package/dist/core/artifact-graph/types.js +29 -0
  86. package/dist/core/available-tools.d.ts +17 -0
  87. package/dist/core/available-tools.js +43 -0
  88. package/dist/core/change-metadata/index.d.ts +2 -0
  89. package/dist/core/change-metadata/index.js +2 -0
  90. package/dist/core/change-metadata/schema.d.ts +18 -0
  91. package/dist/core/change-metadata/schema.js +28 -0
  92. package/dist/core/change-status-policy.d.ts +50 -0
  93. package/dist/core/change-status-policy.js +70 -0
  94. package/dist/core/collections/index.d.ts +3 -0
  95. package/dist/core/collections/index.js +3 -0
  96. package/dist/core/collections/initiatives/collection.d.ts +4 -0
  97. package/dist/core/collections/initiatives/collection.js +17 -0
  98. package/dist/core/collections/initiatives/index.d.ts +6 -0
  99. package/dist/core/collections/initiatives/index.js +6 -0
  100. package/dist/core/collections/initiatives/operations.d.ts +49 -0
  101. package/dist/core/collections/initiatives/operations.js +175 -0
  102. package/dist/core/collections/initiatives/resolution.d.ts +87 -0
  103. package/dist/core/collections/initiatives/resolution.js +374 -0
  104. package/dist/core/collections/initiatives/schema.d.ts +41 -0
  105. package/dist/core/collections/initiatives/schema.js +134 -0
  106. package/dist/core/collections/initiatives/templates.d.ts +12 -0
  107. package/dist/core/collections/initiatives/templates.js +90 -0
  108. package/dist/core/collections/runtime.d.ts +46 -0
  109. package/dist/core/collections/runtime.js +194 -0
  110. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  111. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  112. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  114. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  115. package/dist/core/command-generation/adapters/auggie.js +27 -0
  116. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  117. package/dist/core/command-generation/adapters/bob.js +45 -0
  118. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  119. package/dist/core/command-generation/adapters/claude.js +50 -0
  120. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  121. package/dist/core/command-generation/adapters/cline.js +27 -0
  122. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  123. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  124. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  125. package/dist/core/command-generation/adapters/codex.js +39 -0
  126. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  127. package/dist/core/command-generation/adapters/continue.js +28 -0
  128. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  129. package/dist/core/command-generation/adapters/costrict.js +27 -0
  130. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  131. package/dist/core/command-generation/adapters/crush.js +30 -0
  132. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  133. package/dist/core/command-generation/adapters/cursor.js +44 -0
  134. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  135. package/dist/core/command-generation/adapters/factory.js +27 -0
  136. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  137. package/dist/core/command-generation/adapters/gemini.js +26 -0
  138. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  139. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  140. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  141. package/dist/core/command-generation/adapters/iflow.js +29 -0
  142. package/dist/core/command-generation/adapters/index.d.ts +32 -0
  143. package/dist/core/command-generation/adapters/index.js +32 -0
  144. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  145. package/dist/core/command-generation/adapters/junie.js +26 -0
  146. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  147. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  148. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  149. package/dist/core/command-generation/adapters/kiro.js +26 -0
  150. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  151. package/dist/core/command-generation/adapters/lingma.js +30 -0
  152. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  153. package/dist/core/command-generation/adapters/opencode.js +29 -0
  154. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  155. package/dist/core/command-generation/adapters/pi.js +55 -0
  156. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  157. package/dist/core/command-generation/adapters/qoder.js +30 -0
  158. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  159. package/dist/core/command-generation/adapters/qwen.js +26 -0
  160. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  161. package/dist/core/command-generation/adapters/roocode.js +27 -0
  162. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  163. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  164. package/dist/core/command-generation/generator.d.ts +21 -0
  165. package/dist/core/command-generation/generator.js +27 -0
  166. package/dist/core/command-generation/index.d.ts +22 -0
  167. package/dist/core/command-generation/index.js +24 -0
  168. package/dist/core/command-generation/registry.d.ts +36 -0
  169. package/dist/core/command-generation/registry.js +98 -0
  170. package/dist/core/command-generation/types.d.ts +56 -0
  171. package/dist/core/command-generation/types.js +8 -0
  172. package/dist/core/completions/command-registry.d.ts +3 -0
  173. package/dist/core/completions/command-registry.js +961 -0
  174. package/dist/core/completions/completion-provider.d.ts +71 -0
  175. package/dist/core/completions/completion-provider.js +129 -0
  176. package/dist/core/completions/factory.d.ts +64 -0
  177. package/dist/core/completions/factory.js +75 -0
  178. package/dist/core/completions/generators/bash-generator.d.ts +35 -0
  179. package/dist/core/completions/generators/bash-generator.js +230 -0
  180. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  181. package/dist/core/completions/generators/fish-generator.js +160 -0
  182. package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
  183. package/dist/core/completions/generators/powershell-generator.js +266 -0
  184. package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
  185. package/dist/core/completions/generators/zsh-generator.js +274 -0
  186. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  187. package/dist/core/completions/installers/bash-installer.js +318 -0
  188. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  189. package/dist/core/completions/installers/fish-installer.js +143 -0
  190. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  191. package/dist/core/completions/installers/powershell-installer.js +387 -0
  192. package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
  193. package/dist/core/completions/installers/zsh-installer.js +421 -0
  194. package/dist/core/completions/shared-flags.d.ts +12 -0
  195. package/dist/core/completions/shared-flags.js +28 -0
  196. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  197. package/dist/core/completions/templates/bash-templates.js +30 -0
  198. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  199. package/dist/core/completions/templates/fish-templates.js +45 -0
  200. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  201. package/dist/core/completions/templates/powershell-templates.js +34 -0
  202. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  203. package/dist/core/completions/templates/zsh-templates.js +45 -0
  204. package/dist/core/completions/types.d.ts +101 -0
  205. package/dist/core/completions/types.js +2 -0
  206. package/dist/core/config-prompts.d.ts +9 -0
  207. package/dist/core/config-prompts.js +34 -0
  208. package/dist/core/config-schema.d.ts +86 -0
  209. package/dist/core/config-schema.js +213 -0
  210. package/dist/core/config.d.ts +18 -0
  211. package/dist/core/config.js +39 -0
  212. package/dist/core/context-store/binding.d.ts +53 -0
  213. package/dist/core/context-store/binding.js +197 -0
  214. package/dist/core/context-store/errors.d.ts +20 -0
  215. package/dist/core/context-store/errors.js +22 -0
  216. package/dist/core/context-store/foundation.d.ts +55 -0
  217. package/dist/core/context-store/foundation.js +321 -0
  218. package/dist/core/context-store/index.d.ts +6 -0
  219. package/dist/core/context-store/index.js +6 -0
  220. package/dist/core/context-store/operations.d.ts +85 -0
  221. package/dist/core/context-store/operations.js +528 -0
  222. package/dist/core/context-store/registry.d.ts +45 -0
  223. package/dist/core/context-store/registry.js +229 -0
  224. package/dist/core/converters/json-converter.d.ts +6 -0
  225. package/dist/core/converters/json-converter.js +51 -0
  226. package/dist/core/global-config.d.ts +49 -0
  227. package/dist/core/global-config.js +124 -0
  228. package/dist/core/index.d.ts +6 -0
  229. package/dist/core/index.js +7 -0
  230. package/dist/core/init.d.ts +37 -0
  231. package/dist/core/init.js +593 -0
  232. package/dist/core/legacy-cleanup.d.ts +162 -0
  233. package/dist/core/legacy-cleanup.js +514 -0
  234. package/dist/core/list.d.ts +9 -0
  235. package/dist/core/list.js +171 -0
  236. package/dist/core/migration.d.ts +23 -0
  237. package/dist/core/migration.js +108 -0
  238. package/dist/core/parsers/change-parser.d.ts +13 -0
  239. package/dist/core/parsers/change-parser.js +197 -0
  240. package/dist/core/parsers/markdown-parser.d.ts +26 -0
  241. package/dist/core/parsers/markdown-parser.js +227 -0
  242. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  243. package/dist/core/parsers/requirement-blocks.js +201 -0
  244. package/dist/core/parsers/spec-structure.d.ts +9 -0
  245. package/dist/core/parsers/spec-structure.js +88 -0
  246. package/dist/core/planning-home.d.ts +21 -0
  247. package/dist/core/planning-home.js +108 -0
  248. package/dist/core/profile-sync-drift.d.ts +38 -0
  249. package/dist/core/profile-sync-drift.js +200 -0
  250. package/dist/core/profiles.d.ts +26 -0
  251. package/dist/core/profiles.js +40 -0
  252. package/dist/core/project-config.d.ts +64 -0
  253. package/dist/core/project-config.js +223 -0
  254. package/dist/core/schemas/base.schema.d.ts +13 -0
  255. package/dist/core/schemas/base.schema.js +13 -0
  256. package/dist/core/schemas/change.schema.d.ts +73 -0
  257. package/dist/core/schemas/change.schema.js +31 -0
  258. package/dist/core/schemas/index.d.ts +4 -0
  259. package/dist/core/schemas/index.js +4 -0
  260. package/dist/core/schemas/spec.schema.d.ts +18 -0
  261. package/dist/core/schemas/spec.schema.js +15 -0
  262. package/dist/core/shared/index.d.ts +8 -0
  263. package/dist/core/shared/index.js +8 -0
  264. package/dist/core/shared/skill-generation.d.ts +49 -0
  265. package/dist/core/shared/skill-generation.js +96 -0
  266. package/dist/core/shared/tool-detection.d.ts +71 -0
  267. package/dist/core/shared/tool-detection.js +158 -0
  268. package/dist/core/specs-apply.d.ts +73 -0
  269. package/dist/core/specs-apply.js +392 -0
  270. package/dist/core/styles/palette.d.ts +7 -0
  271. package/dist/core/styles/palette.js +8 -0
  272. package/dist/core/templates/index.d.ts +8 -0
  273. package/dist/core/templates/index.js +9 -0
  274. package/dist/core/templates/skill-templates.d.ts +19 -0
  275. package/dist/core/templates/skill-templates.js +18 -0
  276. package/dist/core/templates/types.d.ts +19 -0
  277. package/dist/core/templates/types.js +5 -0
  278. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  279. package/dist/core/templates/workflows/apply-change.js +314 -0
  280. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  281. package/dist/core/templates/workflows/archive-change.js +277 -0
  282. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  283. package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
  284. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  285. package/dist/core/templates/workflows/continue-change.js +234 -0
  286. package/dist/core/templates/workflows/explore.d.ts +10 -0
  287. package/dist/core/templates/workflows/explore.js +459 -0
  288. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  289. package/dist/core/templates/workflows/feedback.js +108 -0
  290. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  291. package/dist/core/templates/workflows/ff-change.js +200 -0
  292. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  293. package/dist/core/templates/workflows/new-change.js +143 -0
  294. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  295. package/dist/core/templates/workflows/onboard.js +563 -0
  296. package/dist/core/templates/workflows/propose.d.ts +10 -0
  297. package/dist/core/templates/workflows/propose.js +218 -0
  298. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  299. package/dist/core/templates/workflows/sync-specs.js +290 -0
  300. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  301. package/dist/core/templates/workflows/verify-change.js +338 -0
  302. package/dist/core/update.d.ts +82 -0
  303. package/dist/core/update.js +557 -0
  304. package/dist/core/validation/constants.d.ts +34 -0
  305. package/dist/core/validation/constants.js +40 -0
  306. package/dist/core/validation/types.d.ts +18 -0
  307. package/dist/core/validation/types.js +2 -0
  308. package/dist/core/validation/validator.d.ts +43 -0
  309. package/dist/core/validation/validator.js +435 -0
  310. package/dist/core/view.d.ts +8 -0
  311. package/dist/core/view.js +168 -0
  312. package/dist/core/workspace/foundation.d.ts +67 -0
  313. package/dist/core/workspace/foundation.js +295 -0
  314. package/dist/core/workspace/index.d.ts +8 -0
  315. package/dist/core/workspace/index.js +8 -0
  316. package/dist/core/workspace/legacy-state.d.ts +28 -0
  317. package/dist/core/workspace/legacy-state.js +200 -0
  318. package/dist/core/workspace/link-input.d.ts +9 -0
  319. package/dist/core/workspace/link-input.js +32 -0
  320. package/dist/core/workspace/open-surface.d.ts +45 -0
  321. package/dist/core/workspace/open-surface.js +215 -0
  322. package/dist/core/workspace/openers.d.ts +21 -0
  323. package/dist/core/workspace/openers.js +124 -0
  324. package/dist/core/workspace/registry.d.ts +24 -0
  325. package/dist/core/workspace/registry.js +146 -0
  326. package/dist/core/workspace/skills.d.ts +57 -0
  327. package/dist/core/workspace/skills.js +334 -0
  328. package/dist/core/workspace/state-io.d.ts +10 -0
  329. package/dist/core/workspace/state-io.js +121 -0
  330. package/dist/index.d.ts +3 -0
  331. package/dist/index.js +3 -0
  332. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  333. package/dist/prompts/searchable-multi-select.js +159 -0
  334. package/dist/telemetry/config.d.ts +38 -0
  335. package/dist/telemetry/config.js +136 -0
  336. package/dist/telemetry/index.d.ts +31 -0
  337. package/dist/telemetry/index.js +164 -0
  338. package/dist/ui/ascii-patterns.d.ts +16 -0
  339. package/dist/ui/ascii-patterns.js +133 -0
  340. package/dist/ui/welcome-screen.d.ts +10 -0
  341. package/dist/ui/welcome-screen.js +146 -0
  342. package/dist/utils/change-metadata.d.ts +54 -0
  343. package/dist/utils/change-metadata.js +141 -0
  344. package/dist/utils/change-utils.d.ts +71 -0
  345. package/dist/utils/change-utils.js +123 -0
  346. package/dist/utils/command-references.d.ts +18 -0
  347. package/dist/utils/command-references.js +20 -0
  348. package/dist/utils/file-system.d.ts +41 -0
  349. package/dist/utils/file-system.js +301 -0
  350. package/dist/utils/index.d.ts +6 -0
  351. package/dist/utils/index.js +9 -0
  352. package/dist/utils/interactive.d.ts +18 -0
  353. package/dist/utils/interactive.js +21 -0
  354. package/dist/utils/item-discovery.d.ts +4 -0
  355. package/dist/utils/item-discovery.js +72 -0
  356. package/dist/utils/match.d.ts +3 -0
  357. package/dist/utils/match.js +22 -0
  358. package/dist/utils/shell-detection.d.ts +20 -0
  359. package/dist/utils/shell-detection.js +41 -0
  360. package/dist/utils/task-progress.d.ts +8 -0
  361. package/dist/utils/task-progress.js +36 -0
  362. package/package.json +85 -0
  363. package/schemas/spec-driven/schema.yaml +151 -0
  364. package/schemas/spec-driven/templates/design.md +19 -0
  365. package/schemas/spec-driven/templates/proposal.md +23 -0
  366. package/schemas/spec-driven/templates/spec.md +8 -0
  367. package/schemas/spec-driven/templates/tasks.md +9 -0
  368. package/schemas/workspace-planning/schema.yaml +72 -0
  369. package/schemas/workspace-planning/templates/design.md +33 -0
  370. package/schemas/workspace-planning/templates/proposal.md +28 -0
  371. package/schemas/workspace-planning/templates/spec.md +9 -0
  372. package/schemas/workspace-planning/templates/tasks.md +15 -0
  373. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,43 @@
1
+ import { ValidationReport } from './types.js';
2
+ export declare class Validator {
3
+ private strictMode;
4
+ constructor(strictMode?: boolean);
5
+ validateSpec(filePath: string): Promise<ValidationReport>;
6
+ /**
7
+ * Validate spec content from a string (used for pre-write validation of rebuilt specs)
8
+ */
9
+ validateSpecContent(specName: string, content: string): Promise<ValidationReport>;
10
+ validateChange(filePath: string): Promise<ValidationReport>;
11
+ /**
12
+ * Validate delta-formatted spec files under a change directory.
13
+ * Enforces:
14
+ * - At least one delta across all files
15
+ * - ADDED/MODIFIED: each requirement has SHALL/MUST and at least one scenario
16
+ * - REMOVED: names only; no scenario/description required
17
+ * - RENAMED: pairs well-formed
18
+ * - No duplicates within sections; no cross-section conflicts per spec
19
+ */
20
+ validateChangeDeltaSpecs(changeDir: string): Promise<ValidationReport>;
21
+ private convertZodErrors;
22
+ private applySpecRules;
23
+ private applyChangeRules;
24
+ private enrichTopLevelError;
25
+ private extractNameFromPath;
26
+ private createReport;
27
+ isValid(report: ValidationReport): boolean;
28
+ private extractRequirementText;
29
+ private containsNormativeKeyword;
30
+ /**
31
+ * 为缺少规范性关键词的需求块构建错误消息。
32
+ *
33
+ * 当 SHALL/MUST/必须/应当 已出现在需求标题中时(例如
34
+ * `### Requirement: The system SHALL ...`),原通用错误
35
+ * 会令人困惑,因为关键词在规格中可见。按 OpenSpec 约定,
36
+ * 关键词必须写在需求正文行(紧接标题之后的那行),
37
+ * 因此当关键词仅在标题中出现时,我们指引作者进行精确修复。
38
+ */
39
+ private buildMissingNormativeKeywordMessage;
40
+ private countScenarios;
41
+ private formatSectionList;
42
+ }
43
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1,435 @@
1
+ import { readFileSync, promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { SpecSchema, ChangeSchema } from '../schemas/index.js';
4
+ import { MarkdownParser } from '../parsers/markdown-parser.js';
5
+ import { ChangeParser } from '../parsers/change-parser.js';
6
+ import { MIN_PURPOSE_LENGTH, MAX_REQUIREMENT_TEXT_LENGTH, VALIDATION_MESSAGES } from './constants.js';
7
+ import { parseDeltaSpec, normalizeRequirementName } from '../parsers/requirement-blocks.js';
8
+ import { findMainSpecStructureIssues } from '../parsers/spec-structure.js';
9
+ import { FileSystemUtils } from '../../utils/file-system.js';
10
+ export class Validator {
11
+ strictMode;
12
+ constructor(strictMode = false) {
13
+ this.strictMode = strictMode;
14
+ }
15
+ async validateSpec(filePath) {
16
+ const issues = [];
17
+ const specName = this.extractNameFromPath(filePath);
18
+ try {
19
+ const content = readFileSync(filePath, 'utf-8');
20
+ const parser = new MarkdownParser(content);
21
+ const spec = parser.parseSpec(specName);
22
+ const result = SpecSchema.safeParse(spec);
23
+ if (!result.success) {
24
+ issues.push(...this.convertZodErrors(result.error));
25
+ }
26
+ issues.push(...this.applySpecRules(spec, content));
27
+ }
28
+ catch (error) {
29
+ const baseMessage = error instanceof Error ? error.message : 'Unknown error';
30
+ const enriched = this.enrichTopLevelError(specName, baseMessage);
31
+ issues.push({
32
+ level: 'ERROR',
33
+ path: 'file',
34
+ message: enriched,
35
+ });
36
+ }
37
+ return this.createReport(issues);
38
+ }
39
+ /**
40
+ * Validate spec content from a string (used for pre-write validation of rebuilt specs)
41
+ */
42
+ async validateSpecContent(specName, content) {
43
+ const issues = [];
44
+ try {
45
+ const parser = new MarkdownParser(content);
46
+ const spec = parser.parseSpec(specName);
47
+ const result = SpecSchema.safeParse(spec);
48
+ if (!result.success) {
49
+ issues.push(...this.convertZodErrors(result.error));
50
+ }
51
+ issues.push(...this.applySpecRules(spec, content));
52
+ }
53
+ catch (error) {
54
+ const baseMessage = error instanceof Error ? error.message : 'Unknown error';
55
+ const enriched = this.enrichTopLevelError(specName, baseMessage);
56
+ issues.push({ level: 'ERROR', path: 'file', message: enriched });
57
+ }
58
+ return this.createReport(issues);
59
+ }
60
+ async validateChange(filePath) {
61
+ const issues = [];
62
+ const changeName = this.extractNameFromPath(filePath);
63
+ try {
64
+ const content = readFileSync(filePath, 'utf-8');
65
+ const changeDir = path.dirname(filePath);
66
+ const parser = new ChangeParser(content, changeDir);
67
+ const change = await parser.parseChangeWithDeltas(changeName);
68
+ const result = ChangeSchema.safeParse(change);
69
+ if (!result.success) {
70
+ issues.push(...this.convertZodErrors(result.error));
71
+ }
72
+ issues.push(...this.applyChangeRules(change, content));
73
+ }
74
+ catch (error) {
75
+ const baseMessage = error instanceof Error ? error.message : 'Unknown error';
76
+ const enriched = this.enrichTopLevelError(changeName, baseMessage);
77
+ issues.push({
78
+ level: 'ERROR',
79
+ path: 'file',
80
+ message: enriched,
81
+ });
82
+ }
83
+ return this.createReport(issues);
84
+ }
85
+ /**
86
+ * Validate delta-formatted spec files under a change directory.
87
+ * Enforces:
88
+ * - At least one delta across all files
89
+ * - ADDED/MODIFIED: each requirement has SHALL/MUST and at least one scenario
90
+ * - REMOVED: names only; no scenario/description required
91
+ * - RENAMED: pairs well-formed
92
+ * - No duplicates within sections; no cross-section conflicts per spec
93
+ */
94
+ async validateChangeDeltaSpecs(changeDir) {
95
+ const issues = [];
96
+ const specsDir = path.join(changeDir, 'specs');
97
+ let totalDeltas = 0;
98
+ const missingHeaderSpecs = [];
99
+ const emptySectionSpecs = [];
100
+ try {
101
+ const entries = await fs.readdir(specsDir, { withFileTypes: true });
102
+ for (const entry of entries) {
103
+ if (!entry.isDirectory())
104
+ continue;
105
+ const specName = entry.name;
106
+ const specFile = path.join(specsDir, specName, 'spec.md');
107
+ let content;
108
+ try {
109
+ content = await fs.readFile(specFile, 'utf-8');
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ const plan = parseDeltaSpec(content);
115
+ const entryPath = `${specName}/spec.md`;
116
+ const sectionNames = [];
117
+ if (plan.sectionPresence.added)
118
+ sectionNames.push('## ADDED Requirements');
119
+ if (plan.sectionPresence.modified)
120
+ sectionNames.push('## MODIFIED Requirements');
121
+ if (plan.sectionPresence.removed)
122
+ sectionNames.push('## REMOVED Requirements');
123
+ if (plan.sectionPresence.renamed)
124
+ sectionNames.push('## RENAMED Requirements');
125
+ const hasSections = sectionNames.length > 0;
126
+ const hasEntries = plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length > 0;
127
+ if (!hasEntries) {
128
+ if (hasSections)
129
+ emptySectionSpecs.push({ path: entryPath, sections: sectionNames });
130
+ else
131
+ missingHeaderSpecs.push(entryPath);
132
+ }
133
+ const addedNames = new Set();
134
+ const modifiedNames = new Set();
135
+ const removedNames = new Set();
136
+ const renamedFrom = new Set();
137
+ const renamedTo = new Set();
138
+ // Validate ADDED
139
+ for (const block of plan.added) {
140
+ const key = normalizeRequirementName(block.name);
141
+ totalDeltas++;
142
+ if (addedNames.has(key)) {
143
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in ADDED: "${block.name}"` });
144
+ }
145
+ else {
146
+ addedNames.add(key);
147
+ }
148
+ const requirementText = this.extractRequirementText(block.raw);
149
+ if (!requirementText) {
150
+ issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" is missing requirement text` });
151
+ }
152
+ else if (!this.containsNormativeKeyword(requirementText)) {
153
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingNormativeKeywordMessage('ADDED', block.name) });
154
+ }
155
+ const scenarioCount = this.countScenarios(block.raw);
156
+ if (scenarioCount < 1) {
157
+ issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must include at least one scenario` });
158
+ }
159
+ }
160
+ // Validate MODIFIED
161
+ for (const block of plan.modified) {
162
+ const key = normalizeRequirementName(block.name);
163
+ totalDeltas++;
164
+ if (modifiedNames.has(key)) {
165
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in MODIFIED: "${block.name}"` });
166
+ }
167
+ else {
168
+ modifiedNames.add(key);
169
+ }
170
+ const requirementText = this.extractRequirementText(block.raw);
171
+ if (!requirementText) {
172
+ issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" is missing requirement text` });
173
+ }
174
+ else if (!this.containsNormativeKeyword(requirementText)) {
175
+ issues.push({ level: 'ERROR', path: entryPath, message: this.buildMissingNormativeKeywordMessage('MODIFIED', block.name) });
176
+ }
177
+ const scenarioCount = this.countScenarios(block.raw);
178
+ if (scenarioCount < 1) {
179
+ issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" must include at least one scenario` });
180
+ }
181
+ }
182
+ // Validate REMOVED (names only)
183
+ for (const name of plan.removed) {
184
+ const key = normalizeRequirementName(name);
185
+ totalDeltas++;
186
+ if (removedNames.has(key)) {
187
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in REMOVED: "${name}"` });
188
+ }
189
+ else {
190
+ removedNames.add(key);
191
+ }
192
+ }
193
+ // Validate RENAMED pairs
194
+ for (const { from, to } of plan.renamed) {
195
+ const fromKey = normalizeRequirementName(from);
196
+ const toKey = normalizeRequirementName(to);
197
+ totalDeltas++;
198
+ if (renamedFrom.has(fromKey)) {
199
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate FROM in RENAMED: "${from}"` });
200
+ }
201
+ else {
202
+ renamedFrom.add(fromKey);
203
+ }
204
+ if (renamedTo.has(toKey)) {
205
+ issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate TO in RENAMED: "${to}"` });
206
+ }
207
+ else {
208
+ renamedTo.add(toKey);
209
+ }
210
+ }
211
+ // Cross-section conflicts (within the same spec file)
212
+ for (const n of modifiedNames) {
213
+ if (removedNames.has(n)) {
214
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both MODIFIED and REMOVED: "${n}"` });
215
+ }
216
+ if (addedNames.has(n)) {
217
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both MODIFIED and ADDED: "${n}"` });
218
+ }
219
+ }
220
+ for (const n of addedNames) {
221
+ if (removedNames.has(n)) {
222
+ issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both ADDED and REMOVED: "${n}"` });
223
+ }
224
+ }
225
+ for (const { from, to } of plan.renamed) {
226
+ const fromKey = normalizeRequirementName(from);
227
+ const toKey = normalizeRequirementName(to);
228
+ if (modifiedNames.has(fromKey)) {
229
+ issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED references old name from RENAMED. Use new header for "${to}"` });
230
+ }
231
+ if (addedNames.has(toKey)) {
232
+ issues.push({ level: 'ERROR', path: entryPath, message: `RENAMED TO collides with ADDED for "${to}"` });
233
+ }
234
+ }
235
+ }
236
+ }
237
+ catch {
238
+ // If no specs dir, treat as no deltas
239
+ }
240
+ for (const { path: specPath, sections } of emptySectionSpecs) {
241
+ issues.push({
242
+ level: 'ERROR',
243
+ path: specPath,
244
+ 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).`,
245
+ });
246
+ }
247
+ for (const path of missingHeaderSpecs) {
248
+ issues.push({
249
+ level: 'ERROR',
250
+ path,
251
+ message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
252
+ });
253
+ }
254
+ if (totalDeltas === 0) {
255
+ issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
256
+ }
257
+ return this.createReport(issues);
258
+ }
259
+ convertZodErrors(error) {
260
+ return error.issues.map(err => {
261
+ let message = err.message;
262
+ if (message === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
263
+ message = `${message}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
264
+ }
265
+ return {
266
+ level: 'ERROR',
267
+ path: err.path.join('.'),
268
+ message,
269
+ };
270
+ });
271
+ }
272
+ applySpecRules(spec, content) {
273
+ const issues = [];
274
+ for (const structuralIssue of findMainSpecStructureIssues(content)) {
275
+ issues.push({
276
+ level: 'ERROR',
277
+ path: 'file',
278
+ line: structuralIssue.line,
279
+ message: structuralIssue.message,
280
+ });
281
+ }
282
+ if (spec.overview.length < MIN_PURPOSE_LENGTH) {
283
+ issues.push({
284
+ level: 'WARNING',
285
+ path: 'overview',
286
+ message: VALIDATION_MESSAGES.PURPOSE_TOO_BRIEF,
287
+ });
288
+ }
289
+ spec.requirements.forEach((req, index) => {
290
+ if (req.text.length > MAX_REQUIREMENT_TEXT_LENGTH) {
291
+ issues.push({
292
+ level: 'INFO',
293
+ path: `requirements[${index}]`,
294
+ message: VALIDATION_MESSAGES.REQUIREMENT_TOO_LONG,
295
+ });
296
+ }
297
+ if (req.scenarios.length === 0) {
298
+ issues.push({
299
+ level: 'WARNING',
300
+ path: `requirements[${index}].scenarios`,
301
+ message: `${VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS}. ${VALIDATION_MESSAGES.GUIDE_SCENARIO_FORMAT}`,
302
+ });
303
+ }
304
+ });
305
+ return issues;
306
+ }
307
+ applyChangeRules(change, content) {
308
+ const issues = [];
309
+ const MIN_DELTA_DESCRIPTION_LENGTH = 10;
310
+ change.deltas.forEach((delta, index) => {
311
+ if (!delta.description || delta.description.length < MIN_DELTA_DESCRIPTION_LENGTH) {
312
+ issues.push({
313
+ level: 'WARNING',
314
+ path: `deltas[${index}].description`,
315
+ message: VALIDATION_MESSAGES.DELTA_DESCRIPTION_TOO_BRIEF,
316
+ });
317
+ }
318
+ if ((delta.operation === 'ADDED' || delta.operation === 'MODIFIED') &&
319
+ (!delta.requirements || delta.requirements.length === 0)) {
320
+ issues.push({
321
+ level: 'WARNING',
322
+ path: `deltas[${index}].requirements`,
323
+ message: `${delta.operation} ${VALIDATION_MESSAGES.DELTA_MISSING_REQUIREMENTS}`,
324
+ });
325
+ }
326
+ });
327
+ return issues;
328
+ }
329
+ enrichTopLevelError(itemId, baseMessage) {
330
+ const msg = baseMessage.trim();
331
+ if (msg === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
332
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
333
+ }
334
+ if (msg.includes('Spec must have a Purpose section') || msg.includes('Spec must have a Requirements section')) {
335
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_SPEC_SECTIONS}`;
336
+ }
337
+ if (msg.includes('Change must have a Why section') || msg.includes('Change must have a What Changes section')) {
338
+ return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_CHANGE_SECTIONS}`;
339
+ }
340
+ return msg;
341
+ }
342
+ extractNameFromPath(filePath) {
343
+ const normalizedPath = FileSystemUtils.toPosixPath(filePath);
344
+ const parts = normalizedPath.split('/');
345
+ // Look for the directory name after 'specs' or 'changes'
346
+ for (let i = parts.length - 1; i >= 0; i--) {
347
+ if (parts[i] === 'specs' || parts[i] === 'changes') {
348
+ if (i < parts.length - 1) {
349
+ return parts[i + 1];
350
+ }
351
+ }
352
+ }
353
+ // Fallback to filename without extension if not in expected structure
354
+ const fileName = parts[parts.length - 1] ?? '';
355
+ const dotIndex = fileName.lastIndexOf('.');
356
+ return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
357
+ }
358
+ createReport(issues) {
359
+ const errors = issues.filter(i => i.level === 'ERROR').length;
360
+ const warnings = issues.filter(i => i.level === 'WARNING').length;
361
+ const info = issues.filter(i => i.level === 'INFO').length;
362
+ const valid = this.strictMode
363
+ ? errors === 0 && warnings === 0
364
+ : errors === 0;
365
+ return {
366
+ valid,
367
+ issues,
368
+ summary: {
369
+ errors,
370
+ warnings,
371
+ info,
372
+ },
373
+ };
374
+ }
375
+ isValid(report) {
376
+ return report.valid;
377
+ }
378
+ extractRequirementText(blockRaw) {
379
+ const lines = blockRaw.split('\n');
380
+ // Skip header line (index 0)
381
+ let i = 1;
382
+ // Find the first substantial text line, skipping metadata and blank lines
383
+ for (; i < lines.length; i++) {
384
+ const line = lines[i];
385
+ // Stop at scenario headers
386
+ if (/^####\s+/.test(line))
387
+ break;
388
+ const trimmed = line.trim();
389
+ // Skip blank lines
390
+ if (trimmed.length === 0)
391
+ continue;
392
+ // Skip metadata lines (lines starting with ** like **ID**, **Priority**, etc.)
393
+ if (/^\*\*[^*]+\*\*:/.test(trimmed))
394
+ continue;
395
+ // Found first non-metadata, non-blank line - this is the requirement text
396
+ return trimmed;
397
+ }
398
+ // No requirement text found
399
+ return undefined;
400
+ }
401
+ containsNormativeKeyword(text) {
402
+ // 支持英文 SHALL/MUST 和中文 必须/应当
403
+ return /\b(SHALL|MUST)\b/.test(text) || /(必须|应当)/.test(text);
404
+ }
405
+ /**
406
+ * 为缺少规范性关键词的需求块构建错误消息。
407
+ *
408
+ * 当 SHALL/MUST/必须/应当 已出现在需求标题中时(例如
409
+ * `### Requirement: The system SHALL ...`),原通用错误
410
+ * 会令人困惑,因为关键词在规格中可见。按 OpenSpec 约定,
411
+ * 关键词必须写在需求正文行(紧接标题之后的那行),
412
+ * 因此当关键词仅在标题中出现时,我们指引作者进行精确修复。
413
+ */
414
+ buildMissingNormativeKeywordMessage(action, blockName) {
415
+ const base = `${action} "${blockName}" 必须包含 SHALL、MUST、必须 或 应当`;
416
+ if (this.containsNormativeKeyword(blockName)) {
417
+ return `${base},且关键词需放在需求正文中,而非仅出现在标题中。请将 SHALL/MUST/必须/应当 声明移至紧接 "### Requirement: ..." 标题之后的正文行。`;
418
+ }
419
+ return base;
420
+ }
421
+ countScenarios(blockRaw) {
422
+ const matches = blockRaw.match(/^####\s+/gm);
423
+ return matches ? matches.length : 0;
424
+ }
425
+ formatSectionList(sections) {
426
+ if (sections.length === 0)
427
+ return '';
428
+ if (sections.length === 1)
429
+ return sections[0];
430
+ const head = sections.slice(0, -1);
431
+ const last = sections[sections.length - 1];
432
+ return `${head.join(', ')} and ${last}`;
433
+ }
434
+ }
435
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1,8 @@
1
+ export declare class ViewCommand {
2
+ execute(targetPath?: string): Promise<void>;
3
+ private getChangesData;
4
+ private getSpecsData;
5
+ private displaySummary;
6
+ private createProgressBar;
7
+ }
8
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1,168 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import { getTaskProgressForChange } from '../utils/task-progress.js';
5
+ import { MarkdownParser } from './parsers/markdown-parser.js';
6
+ export class ViewCommand {
7
+ async execute(targetPath = '.') {
8
+ const openspecDir = path.join(targetPath, 'openspec');
9
+ if (!fs.existsSync(openspecDir)) {
10
+ console.error(chalk.red('未找到 openspec 目录'));
11
+ process.exit(1);
12
+ }
13
+ console.log(chalk.bold('\nOpenSpec 仪表盘\n'));
14
+ console.log('═'.repeat(60));
15
+ // Get changes and specs data
16
+ const changesData = await this.getChangesData(openspecDir);
17
+ const specsData = await this.getSpecsData(openspecDir);
18
+ // Display summary metrics
19
+ this.displaySummary(changesData, specsData);
20
+ // Display draft changes
21
+ if (changesData.draft.length > 0) {
22
+ console.log(chalk.bold.gray('\n草稿变更'));
23
+ console.log('─'.repeat(60));
24
+ changesData.draft.forEach((change) => {
25
+ console.log(` ${chalk.gray('○')} ${change.name}`);
26
+ });
27
+ }
28
+ // Display active changes
29
+ if (changesData.active.length > 0) {
30
+ console.log(chalk.bold.cyan('\n进行中变更'));
31
+ console.log('─'.repeat(60));
32
+ changesData.active.forEach((change) => {
33
+ const progressBar = this.createProgressBar(change.progress.completed, change.progress.total);
34
+ const percentage = change.progress.total > 0
35
+ ? Math.round((change.progress.completed / change.progress.total) * 100)
36
+ : 0;
37
+ console.log(` ${chalk.yellow('◉')} ${chalk.bold(change.name.padEnd(30))} ${progressBar} ${chalk.dim(`${percentage}%`)}`);
38
+ });
39
+ }
40
+ // Display completed changes
41
+ if (changesData.completed.length > 0) {
42
+ console.log(chalk.bold.green('\n已完成变更'));
43
+ console.log('─'.repeat(60));
44
+ changesData.completed.forEach((change) => {
45
+ console.log(` ${chalk.green('✓')} ${change.name}`);
46
+ });
47
+ }
48
+ // Display specifications
49
+ if (specsData.length > 0) {
50
+ console.log(chalk.bold.blue('\n规格说明'));
51
+ console.log('─'.repeat(60));
52
+ // Sort specs by requirement count (descending)
53
+ specsData.sort((a, b) => b.requirementCount - a.requirementCount);
54
+ specsData.forEach(spec => {
55
+ const reqLabel = spec.requirementCount === 1 ? 'requirement' : 'requirements';
56
+ console.log(` ${chalk.blue('▪')} ${chalk.bold(spec.name.padEnd(30))} ${chalk.dim(`${spec.requirementCount} ${reqLabel}`)}`);
57
+ });
58
+ }
59
+ console.log('\n' + '═'.repeat(60));
60
+ console.log(chalk.dim(`\n使用 ${chalk.white('openspec list --changes')} 或 ${chalk.white('openspec list --specs')} 查看详情`));
61
+ }
62
+ async getChangesData(openspecDir) {
63
+ const changesDir = path.join(openspecDir, 'changes');
64
+ if (!fs.existsSync(changesDir)) {
65
+ return { draft: [], active: [], completed: [] };
66
+ }
67
+ const draft = [];
68
+ const active = [];
69
+ const completed = [];
70
+ const entries = fs.readdirSync(changesDir, { withFileTypes: true });
71
+ for (const entry of entries) {
72
+ if (entry.isDirectory() && entry.name !== 'archive') {
73
+ const progress = await getTaskProgressForChange(changesDir, entry.name);
74
+ if (progress.total === 0) {
75
+ // No tasks defined yet - still in planning/draft phase
76
+ draft.push({ name: entry.name });
77
+ }
78
+ else if (progress.completed === progress.total) {
79
+ // All tasks complete
80
+ completed.push({ name: entry.name });
81
+ }
82
+ else {
83
+ // Has tasks but not all complete
84
+ active.push({ name: entry.name, progress });
85
+ }
86
+ }
87
+ }
88
+ // Sort all categories by name for deterministic ordering
89
+ draft.sort((a, b) => a.name.localeCompare(b.name));
90
+ // Sort active changes by completion percentage (ascending) and then by name
91
+ active.sort((a, b) => {
92
+ const percentageA = a.progress.total > 0 ? a.progress.completed / a.progress.total : 0;
93
+ const percentageB = b.progress.total > 0 ? b.progress.completed / b.progress.total : 0;
94
+ if (percentageA < percentageB)
95
+ return -1;
96
+ if (percentageA > percentageB)
97
+ return 1;
98
+ return a.name.localeCompare(b.name);
99
+ });
100
+ completed.sort((a, b) => a.name.localeCompare(b.name));
101
+ return { draft, active, completed };
102
+ }
103
+ async getSpecsData(openspecDir) {
104
+ const specsDir = path.join(openspecDir, 'specs');
105
+ if (!fs.existsSync(specsDir)) {
106
+ return [];
107
+ }
108
+ const specs = [];
109
+ const entries = fs.readdirSync(specsDir, { withFileTypes: true });
110
+ for (const entry of entries) {
111
+ if (entry.isDirectory()) {
112
+ const specFile = path.join(specsDir, entry.name, 'spec.md');
113
+ if (fs.existsSync(specFile)) {
114
+ try {
115
+ const content = fs.readFileSync(specFile, 'utf-8');
116
+ const parser = new MarkdownParser(content);
117
+ const spec = parser.parseSpec(entry.name);
118
+ const requirementCount = spec.requirements.length;
119
+ specs.push({ name: entry.name, requirementCount });
120
+ }
121
+ catch (error) {
122
+ // If spec cannot be parsed, include with 0 count
123
+ specs.push({ name: entry.name, requirementCount: 0 });
124
+ }
125
+ }
126
+ }
127
+ }
128
+ return specs;
129
+ }
130
+ displaySummary(changesData, specsData) {
131
+ const totalChanges = changesData.draft.length + changesData.active.length + changesData.completed.length;
132
+ const totalSpecs = specsData.length;
133
+ const totalRequirements = specsData.reduce((sum, spec) => sum + spec.requirementCount, 0);
134
+ // Calculate total task progress
135
+ let totalTasks = 0;
136
+ let completedTasks = 0;
137
+ changesData.active.forEach((change) => {
138
+ totalTasks += change.progress.total;
139
+ completedTasks += change.progress.completed;
140
+ });
141
+ changesData.completed.forEach(() => {
142
+ // Completed changes count as 100% done (we don't know exact task count)
143
+ // This is a simplification
144
+ });
145
+ console.log(chalk.bold('概要:'));
146
+ console.log(` ${chalk.cyan('●')} 规格说明:${chalk.bold(totalSpecs)} 个规格,${chalk.bold(totalRequirements)} 个需求`);
147
+ if (changesData.draft.length > 0) {
148
+ console.log(` ${chalk.gray('●')} 草稿变更:${chalk.bold(changesData.draft.length)}`);
149
+ }
150
+ console.log(` ${chalk.yellow('●')} 进行中变更:${chalk.bold(changesData.active.length)} 个进行中`);
151
+ console.log(` ${chalk.green('●')} 已完成变更:${chalk.bold(changesData.completed.length)}`);
152
+ if (totalTasks > 0) {
153
+ const overallProgress = Math.round((completedTasks / totalTasks) * 100);
154
+ console.log(` ${chalk.magenta('●')} 任务进度:${chalk.bold(`${completedTasks}/${totalTasks}`)}(${overallProgress}% 完成)`);
155
+ }
156
+ }
157
+ createProgressBar(completed, total, width = 20) {
158
+ if (total === 0)
159
+ return chalk.dim('─'.repeat(width));
160
+ const percentage = completed / total;
161
+ const filled = Math.round(percentage * width);
162
+ const empty = width - filled;
163
+ const filledBar = chalk.green('█'.repeat(filled));
164
+ const emptyBar = chalk.dim('░'.repeat(empty));
165
+ return `[${filledBar}${emptyBar}]`;
166
+ }
167
+ }
168
+ //# sourceMappingURL=view.js.map