openspecui 0.0.0

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 (311) hide show
  1. package/.gitmodules +3 -0
  2. package/CHAT.md +3 -0
  3. package/package.json +12 -0
  4. package/references/openspec/.changeset/README.md +6 -0
  5. package/references/openspec/.changeset/config.json +12 -0
  6. package/references/openspec/.coderabbit.yaml +11 -0
  7. package/references/openspec/.devcontainer/README.md +92 -0
  8. package/references/openspec/.devcontainer/devcontainer.json +68 -0
  9. package/references/openspec/.github/CODEOWNERS +2 -0
  10. package/references/openspec/.github/workflows/ci.yml +222 -0
  11. package/references/openspec/.github/workflows/release-prepare.yml +50 -0
  12. package/references/openspec/AGENTS.md +18 -0
  13. package/references/openspec/CHANGELOG.md +205 -0
  14. package/references/openspec/LICENSE +22 -0
  15. package/references/openspec/README.md +374 -0
  16. package/references/openspec/assets/openspec_dashboard.png +0 -0
  17. package/references/openspec/assets/openspec_pixel_dark.svg +89 -0
  18. package/references/openspec/assets/openspec_pixel_light.svg +89 -0
  19. package/references/openspec/bin/openspec.js +3 -0
  20. package/references/openspec/build.js +31 -0
  21. package/references/openspec/openspec/AGENTS.md +454 -0
  22. package/references/openspec/openspec/changes/IMPLEMENTATION_ORDER.md +68 -0
  23. package/references/openspec/openspec/changes/add-antigravity-support/proposal.md +11 -0
  24. package/references/openspec/openspec/changes/add-antigravity-support/specs/cli-init/spec.md +9 -0
  25. package/references/openspec/openspec/changes/add-antigravity-support/specs/cli-update/spec.md +8 -0
  26. package/references/openspec/openspec/changes/add-antigravity-support/tasks.md +12 -0
  27. package/references/openspec/openspec/changes/add-scaffold-command/proposal.md +11 -0
  28. package/references/openspec/openspec/changes/add-scaffold-command/specs/cli-scaffold/spec.md +36 -0
  29. package/references/openspec/openspec/changes/add-scaffold-command/tasks.md +12 -0
  30. package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/design.md +86 -0
  31. package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/proposal.md +29 -0
  32. package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/specs/cli-update/spec.md +59 -0
  33. package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/tasks.md +20 -0
  34. package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/proposal.md +20 -0
  35. package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/specs/cli-list/spec.md +69 -0
  36. package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/tasks.md +26 -0
  37. package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/design.md +64 -0
  38. package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/proposal.md +18 -0
  39. package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/tasks.md +25 -0
  40. package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/design.md +104 -0
  41. package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/proposal.md +30 -0
  42. package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/specs/cli-init/spec.md +148 -0
  43. package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/tasks.md +38 -0
  44. package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/proposal.md +24 -0
  45. package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/specs/openspec-conventions/spec.md +120 -0
  46. package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/tasks.md +38 -0
  47. package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/proposal.md +13 -0
  48. package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/specs/openspec-docs/README.md +472 -0
  49. package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/tasks.md +9 -0
  50. package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/proposal.md +15 -0
  51. package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/specs/cli-archive/spec.md +111 -0
  52. package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/tasks.md +44 -0
  53. package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/proposal.md +19 -0
  54. package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/specs/cli-diff/spec.md +77 -0
  55. package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/tasks.md +23 -0
  56. package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/design.md +56 -0
  57. package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/proposal.md +17 -0
  58. package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-change/spec.md +48 -0
  59. package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-list/spec.md +12 -0
  60. package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/tasks.md +34 -0
  61. package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/proposal.md +20 -0
  62. package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-change/spec.md +23 -0
  63. package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-show/spec.md +83 -0
  64. package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-spec/spec.md +23 -0
  65. package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/tasks.md +142 -0
  66. package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/proposal.md +13 -0
  67. package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/specs/cli-archive/spec.md +191 -0
  68. package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/tasks.md +57 -0
  69. package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/design.md +45 -0
  70. package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/proposal.md +19 -0
  71. package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/specs/cli-spec/spec.md +43 -0
  72. package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/tasks.md +22 -0
  73. package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/design.md +104 -0
  74. package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/proposal.md +22 -0
  75. package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-archive/spec.md +18 -0
  76. package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-diff/spec.md +12 -0
  77. package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/tasks.md +59 -0
  78. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/proposal.md +93 -0
  79. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-archive/spec.md +48 -0
  80. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-diff/spec.md +45 -0
  81. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/openspec-conventions/spec.md +101 -0
  82. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/tasks.md +55 -0
  83. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/design.md +19 -0
  84. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/proposal.md +67 -0
  85. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/cli-list/spec.md +57 -0
  86. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/openspec-conventions/spec.md +23 -0
  87. package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/tasks.md +27 -0
  88. package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/proposal.md +20 -0
  89. package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-change/spec.md +22 -0
  90. package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-spec/spec.md +23 -0
  91. package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-validate/spec.md +149 -0
  92. package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/tasks.md +81 -0
  93. package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/proposal.md +40 -0
  94. package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/specs/cli-update/spec.md +23 -0
  95. package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/tasks.md +21 -0
  96. package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/proposal.md +25 -0
  97. package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/specs/cli-validate/spec.md +55 -0
  98. package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/tasks.md +21 -0
  99. package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/proposal.md +36 -0
  100. package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/specs/openspec-conventions/spec.md +192 -0
  101. package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/tasks.md +19 -0
  102. package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/proposal.md +38 -0
  103. package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/specs/cli-view/spec.md +109 -0
  104. package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/tasks.md +47 -0
  105. package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/proposal.md +28 -0
  106. package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-init/spec.md +71 -0
  107. package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-update/spec.md +41 -0
  108. package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/tasks.md +17 -0
  109. package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/proposal.md +35 -0
  110. package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/specs/cli-init/spec.md +45 -0
  111. package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/tasks.md +16 -0
  112. package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/proposal.md +119 -0
  113. package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-init/spec.md +21 -0
  114. package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-update/spec.md +22 -0
  115. package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/tasks.md +20 -0
  116. package/references/openspec/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/proposal.md +19 -0
  117. package/references/openspec/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/tasks.md +9 -0
  118. package/references/openspec/openspec/changes/archive/2025-09-29-improve-deterministic-tests/proposal.md +78 -0
  119. package/references/openspec/openspec/changes/archive/2025-09-29-improve-deterministic-tests/tasks.md +25 -0
  120. package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/proposal.md +13 -0
  121. package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/specs/cli-init/spec.md +92 -0
  122. package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/tasks.md +12 -0
  123. package/references/openspec/openspec/changes/archive/2025-09-29-remove-diff-command/proposal.md +81 -0
  124. package/references/openspec/openspec/changes/archive/2025-09-29-remove-diff-command/tasks.md +37 -0
  125. package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/proposal.md +25 -0
  126. package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/specs/cli-view/spec.md +9 -0
  127. package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/tasks.md +8 -0
  128. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/proposal.md +29 -0
  129. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-init/spec.md +40 -0
  130. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-update/spec.md +22 -0
  131. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/openspec-conventions/spec.md +27 -0
  132. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/tasks.md +22 -0
  133. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/design.md +130 -0
  134. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/proposal.md +117 -0
  135. package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/tasks.md +69 -0
  136. package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/proposal.md +19 -0
  137. package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/specs/cli-validate/spec.md +9 -0
  138. package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/tasks.md +11 -0
  139. package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/proposal.md +25 -0
  140. package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-init/spec.md +56 -0
  141. package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-update/spec.md +41 -0
  142. package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/tasks.md +19 -0
  143. package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/proposal.md +25 -0
  144. package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-init/spec.md +48 -0
  145. package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-update/spec.md +48 -0
  146. package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/tasks.md +30 -0
  147. package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/proposal.md +17 -0
  148. package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-init/spec.md +43 -0
  149. package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-update/spec.md +27 -0
  150. package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/tasks.md +15 -0
  151. package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/proposal.md +12 -0
  152. package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/specs/cli-init/spec.md +39 -0
  153. package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/tasks.md +17 -0
  154. package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/proposal.md +17 -0
  155. package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-init/spec.md +42 -0
  156. package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-update/spec.md +27 -0
  157. package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/tasks.md +17 -0
  158. package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/proposal.md +12 -0
  159. package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/specs/cli-validate/spec.md +39 -0
  160. package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/tasks.md +12 -0
  161. package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/proposal.md +12 -0
  162. package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/specs/docs-agent-instructions/spec.md +33 -0
  163. package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/tasks.md +11 -0
  164. package/references/openspec/openspec/changes/archive/2025-10-14-slim-root-agents-file/proposal.md +13 -0
  165. package/references/openspec/openspec/changes/archive/2025-10-14-slim-root-agents-file/tasks.md +15 -0
  166. package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/proposal.md +14 -0
  167. package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/specs/cli-init/spec.md +10 -0
  168. package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/tasks.md +8 -0
  169. package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/proposal.md +15 -0
  170. package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-init/spec.md +32 -0
  171. package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-update/spec.md +10 -0
  172. package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/tasks.md +11 -0
  173. package/references/openspec/openspec/changes/archive/2025-10-14-update-release-automation/proposal.md +49 -0
  174. package/references/openspec/openspec/changes/archive/2025-10-14-update-release-automation/tasks.md +12 -0
  175. package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/proposal.md +17 -0
  176. package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/specs/cli-update/spec.md +32 -0
  177. package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/tasks.md +15 -0
  178. package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/proposal.md +15 -0
  179. package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/specs/cli-init/spec.md +97 -0
  180. package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/tasks.md +19 -0
  181. package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/proposal.md +13 -0
  182. package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/specs/cli-init/spec.md +67 -0
  183. package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/tasks.md +7 -0
  184. package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/proposal.md +12 -0
  185. package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-init/spec.md +54 -0
  186. package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-update/spec.md +54 -0
  187. package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/tasks.md +11 -0
  188. package/references/openspec/openspec/changes/fix-cline-workflows-implementation/proposal.md +13 -0
  189. package/references/openspec/openspec/changes/fix-cline-workflows-implementation/specs/cli-init/spec.md +11 -0
  190. package/references/openspec/openspec/changes/fix-cline-workflows-implementation/tasks.md +13 -0
  191. package/references/openspec/openspec/changes/make-validation-scope-aware/proposal.md +12 -0
  192. package/references/openspec/openspec/changes/make-validation-scope-aware/specs/cli-validate/spec.md +25 -0
  193. package/references/openspec/openspec/changes/make-validation-scope-aware/tasks.md +16 -0
  194. package/references/openspec/openspec/project.md +53 -0
  195. package/references/openspec/openspec/specs/cli-archive/spec.md +210 -0
  196. package/references/openspec/openspec/specs/cli-change/spec.md +91 -0
  197. package/references/openspec/openspec/specs/cli-init/spec.md +311 -0
  198. package/references/openspec/openspec/specs/cli-list/spec.md +103 -0
  199. package/references/openspec/openspec/specs/cli-show/spec.md +85 -0
  200. package/references/openspec/openspec/specs/cli-spec/spec.md +87 -0
  201. package/references/openspec/openspec/specs/cli-update/spec.md +190 -0
  202. package/references/openspec/openspec/specs/cli-validate/spec.md +218 -0
  203. package/references/openspec/openspec/specs/cli-view/spec.md +105 -0
  204. package/references/openspec/openspec/specs/docs-agent-instructions/spec.md +38 -0
  205. package/references/openspec/openspec/specs/openspec-conventions/spec.md +474 -0
  206. package/references/openspec/openspec-parallel-merge-plan.md +98 -0
  207. package/references/openspec/package.json +73 -0
  208. package/references/openspec/pnpm-lock.yaml +2324 -0
  209. package/references/openspec/scripts/pack-version-check.mjs +111 -0
  210. package/references/openspec/src/cli/index.ts +253 -0
  211. package/references/openspec/src/commands/change.ts +291 -0
  212. package/references/openspec/src/commands/show.ts +139 -0
  213. package/references/openspec/src/commands/spec.ts +250 -0
  214. package/references/openspec/src/commands/validate.ts +305 -0
  215. package/references/openspec/src/core/archive.ts +606 -0
  216. package/references/openspec/src/core/config.ts +41 -0
  217. package/references/openspec/src/core/configurators/agents.ts +23 -0
  218. package/references/openspec/src/core/configurators/base.ts +6 -0
  219. package/references/openspec/src/core/configurators/claude.ts +23 -0
  220. package/references/openspec/src/core/configurators/cline.ts +23 -0
  221. package/references/openspec/src/core/configurators/codebuddy.ts +24 -0
  222. package/references/openspec/src/core/configurators/costrict.ts +23 -0
  223. package/references/openspec/src/core/configurators/iflow.ts +23 -0
  224. package/references/openspec/src/core/configurators/qoder.ts +53 -0
  225. package/references/openspec/src/core/configurators/qwen.ts +47 -0
  226. package/references/openspec/src/core/configurators/registry.ts +49 -0
  227. package/references/openspec/src/core/configurators/slash/amazon-q.ts +51 -0
  228. package/references/openspec/src/core/configurators/slash/antigravity.ts +28 -0
  229. package/references/openspec/src/core/configurators/slash/auggie.ts +37 -0
  230. package/references/openspec/src/core/configurators/slash/base.ts +95 -0
  231. package/references/openspec/src/core/configurators/slash/claude.ts +42 -0
  232. package/references/openspec/src/core/configurators/slash/cline.ts +27 -0
  233. package/references/openspec/src/core/configurators/slash/codebuddy.ts +43 -0
  234. package/references/openspec/src/core/configurators/slash/codex.ts +126 -0
  235. package/references/openspec/src/core/configurators/slash/costrict.ts +36 -0
  236. package/references/openspec/src/core/configurators/slash/crush.ts +42 -0
  237. package/references/openspec/src/core/configurators/slash/cursor.ts +42 -0
  238. package/references/openspec/src/core/configurators/slash/factory.ts +41 -0
  239. package/references/openspec/src/core/configurators/slash/gemini.ts +27 -0
  240. package/references/openspec/src/core/configurators/slash/github-copilot.ts +39 -0
  241. package/references/openspec/src/core/configurators/slash/iflow.ts +42 -0
  242. package/references/openspec/src/core/configurators/slash/kilocode.ts +21 -0
  243. package/references/openspec/src/core/configurators/slash/opencode.ts +83 -0
  244. package/references/openspec/src/core/configurators/slash/qoder.ts +84 -0
  245. package/references/openspec/src/core/configurators/slash/qwen.ts +55 -0
  246. package/references/openspec/src/core/configurators/slash/registry.ts +81 -0
  247. package/references/openspec/src/core/configurators/slash/roocode.ts +27 -0
  248. package/references/openspec/src/core/configurators/slash/toml-base.ts +66 -0
  249. package/references/openspec/src/core/configurators/slash/windsurf.ts +27 -0
  250. package/references/openspec/src/core/converters/json-converter.ts +61 -0
  251. package/references/openspec/src/core/index.ts +2 -0
  252. package/references/openspec/src/core/init.ts +986 -0
  253. package/references/openspec/src/core/list.ts +104 -0
  254. package/references/openspec/src/core/parsers/change-parser.ts +234 -0
  255. package/references/openspec/src/core/parsers/markdown-parser.ts +237 -0
  256. package/references/openspec/src/core/parsers/requirement-blocks.ts +234 -0
  257. package/references/openspec/src/core/schemas/base.schema.ts +20 -0
  258. package/references/openspec/src/core/schemas/change.schema.ts +42 -0
  259. package/references/openspec/src/core/schemas/index.ts +20 -0
  260. package/references/openspec/src/core/schemas/spec.schema.ts +17 -0
  261. package/references/openspec/src/core/styles/palette.ts +8 -0
  262. package/references/openspec/src/core/templates/agents-root-stub.ts +16 -0
  263. package/references/openspec/src/core/templates/agents-template.ts +457 -0
  264. package/references/openspec/src/core/templates/claude-template.ts +1 -0
  265. package/references/openspec/src/core/templates/cline-template.ts +1 -0
  266. package/references/openspec/src/core/templates/costrict-template.ts +1 -0
  267. package/references/openspec/src/core/templates/index.ts +50 -0
  268. package/references/openspec/src/core/templates/project-template.ts +38 -0
  269. package/references/openspec/src/core/templates/slash-command-templates.ts +60 -0
  270. package/references/openspec/src/core/update.ts +129 -0
  271. package/references/openspec/src/core/validation/constants.ts +48 -0
  272. package/references/openspec/src/core/validation/types.ts +19 -0
  273. package/references/openspec/src/core/validation/validator.ts +448 -0
  274. package/references/openspec/src/core/view.ts +189 -0
  275. package/references/openspec/src/index.ts +2 -0
  276. package/references/openspec/src/utils/file-system.ts +187 -0
  277. package/references/openspec/src/utils/index.ts +2 -0
  278. package/references/openspec/src/utils/interactive.ts +7 -0
  279. package/references/openspec/src/utils/item-discovery.ts +45 -0
  280. package/references/openspec/src/utils/match.ts +26 -0
  281. package/references/openspec/src/utils/task-progress.ts +43 -0
  282. package/references/openspec/test/cli-e2e/basic.test.ts +156 -0
  283. package/references/openspec/test/commands/change.interactive-show.test.ts +45 -0
  284. package/references/openspec/test/commands/change.interactive-validate.test.ts +48 -0
  285. package/references/openspec/test/commands/show.test.ts +123 -0
  286. package/references/openspec/test/commands/spec.interactive-show.test.ts +44 -0
  287. package/references/openspec/test/commands/spec.interactive-validate.test.ts +44 -0
  288. package/references/openspec/test/commands/spec.test.ts +324 -0
  289. package/references/openspec/test/commands/validate.enriched-output.test.ts +49 -0
  290. package/references/openspec/test/commands/validate.test.ts +133 -0
  291. package/references/openspec/test/core/archive.test.ts +680 -0
  292. package/references/openspec/test/core/commands/change-command.list.test.ts +76 -0
  293. package/references/openspec/test/core/commands/change-command.show-validate.test.ts +111 -0
  294. package/references/openspec/test/core/converters/json-converter.test.ts +184 -0
  295. package/references/openspec/test/core/init.test.ts +1710 -0
  296. package/references/openspec/test/core/list.test.ts +165 -0
  297. package/references/openspec/test/core/parsers/change-parser.test.ts +52 -0
  298. package/references/openspec/test/core/parsers/markdown-parser.test.ts +291 -0
  299. package/references/openspec/test/core/update.test.ts +1642 -0
  300. package/references/openspec/test/core/validation.enriched-messages.test.ts +74 -0
  301. package/references/openspec/test/core/validation.test.ts +489 -0
  302. package/references/openspec/test/core/view.test.ts +79 -0
  303. package/references/openspec/test/fixtures/tmp-init/openspec/changes/c1/proposal.md +7 -0
  304. package/references/openspec/test/fixtures/tmp-init/openspec/changes/c1/specs/alpha/spec.md +8 -0
  305. package/references/openspec/test/fixtures/tmp-init/openspec/specs/alpha/spec.md +12 -0
  306. package/references/openspec/test/helpers/run-cli.ts +139 -0
  307. package/references/openspec/test/utils/file-system.test.ts +211 -0
  308. package/references/openspec/test/utils/marker-updates.test.ts +287 -0
  309. package/references/openspec/tsconfig.json +21 -0
  310. package/references/openspec/vitest.config.ts +25 -0
  311. package/references/openspec/vitest.setup.ts +6 -0
@@ -0,0 +1,189 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
5
+ import { MarkdownParser } from './parsers/markdown-parser.js';
6
+
7
+ export class ViewCommand {
8
+ async execute(targetPath: string = '.'): Promise<void> {
9
+ const openspecDir = path.join(targetPath, 'openspec');
10
+
11
+ if (!fs.existsSync(openspecDir)) {
12
+ console.error(chalk.red('No openspec directory found'));
13
+ process.exit(1);
14
+ }
15
+
16
+ console.log(chalk.bold('\nOpenSpec Dashboard\n'));
17
+ console.log('═'.repeat(60));
18
+
19
+ // Get changes and specs data
20
+ const changesData = await this.getChangesData(openspecDir);
21
+ const specsData = await this.getSpecsData(openspecDir);
22
+
23
+ // Display summary metrics
24
+ this.displaySummary(changesData, specsData);
25
+
26
+ // Display active changes
27
+ if (changesData.active.length > 0) {
28
+ console.log(chalk.bold.cyan('\nActive Changes'));
29
+ console.log('─'.repeat(60));
30
+ changesData.active.forEach(change => {
31
+ const progressBar = this.createProgressBar(change.progress.completed, change.progress.total);
32
+ const percentage = change.progress.total > 0
33
+ ? Math.round((change.progress.completed / change.progress.total) * 100)
34
+ : 0;
35
+
36
+ console.log(
37
+ ` ${chalk.yellow('◉')} ${chalk.bold(change.name.padEnd(30))} ${progressBar} ${chalk.dim(`${percentage}%`)}`
38
+ );
39
+ });
40
+ }
41
+
42
+ // Display completed changes
43
+ if (changesData.completed.length > 0) {
44
+ console.log(chalk.bold.green('\nCompleted Changes'));
45
+ console.log('─'.repeat(60));
46
+ changesData.completed.forEach(change => {
47
+ console.log(` ${chalk.green('✓')} ${change.name}`);
48
+ });
49
+ }
50
+
51
+ // Display specifications
52
+ if (specsData.length > 0) {
53
+ console.log(chalk.bold.blue('\nSpecifications'));
54
+ console.log('─'.repeat(60));
55
+
56
+ // Sort specs by requirement count (descending)
57
+ specsData.sort((a, b) => b.requirementCount - a.requirementCount);
58
+
59
+ specsData.forEach(spec => {
60
+ const reqLabel = spec.requirementCount === 1 ? 'requirement' : 'requirements';
61
+ console.log(
62
+ ` ${chalk.blue('▪')} ${chalk.bold(spec.name.padEnd(30))} ${chalk.dim(`${spec.requirementCount} ${reqLabel}`)}`
63
+ );
64
+ });
65
+ }
66
+
67
+ console.log('\n' + '═'.repeat(60));
68
+ console.log(chalk.dim(`\nUse ${chalk.white('openspec list --changes')} or ${chalk.white('openspec list --specs')} for detailed views`));
69
+ }
70
+
71
+ private async getChangesData(openspecDir: string): Promise<{
72
+ active: Array<{ name: string; progress: { total: number; completed: number } }>;
73
+ completed: Array<{ name: string }>;
74
+ }> {
75
+ const changesDir = path.join(openspecDir, 'changes');
76
+
77
+ if (!fs.existsSync(changesDir)) {
78
+ return { active: [], completed: [] };
79
+ }
80
+
81
+ const active: Array<{ name: string; progress: { total: number; completed: number } }> = [];
82
+ const completed: Array<{ name: string }> = [];
83
+
84
+ const entries = fs.readdirSync(changesDir, { withFileTypes: true });
85
+
86
+ for (const entry of entries) {
87
+ if (entry.isDirectory() && entry.name !== 'archive') {
88
+ const progress = await getTaskProgressForChange(changesDir, entry.name);
89
+
90
+ if (progress.total === 0 || progress.completed === progress.total) {
91
+ completed.push({ name: entry.name });
92
+ } else {
93
+ active.push({ name: entry.name, progress });
94
+ }
95
+ }
96
+ }
97
+
98
+ // Sort active changes by completion percentage (ascending) and then by name for deterministic ordering
99
+ active.sort((a, b) => {
100
+ const percentageA = a.progress.total > 0 ? a.progress.completed / a.progress.total : 0;
101
+ const percentageB = b.progress.total > 0 ? b.progress.completed / b.progress.total : 0;
102
+
103
+ if (percentageA < percentageB) return -1;
104
+ if (percentageA > percentageB) return 1;
105
+ return a.name.localeCompare(b.name);
106
+ });
107
+ completed.sort((a, b) => a.name.localeCompare(b.name));
108
+
109
+ return { active, completed };
110
+ }
111
+
112
+ private async getSpecsData(openspecDir: string): Promise<Array<{ name: string; requirementCount: number }>> {
113
+ const specsDir = path.join(openspecDir, 'specs');
114
+
115
+ if (!fs.existsSync(specsDir)) {
116
+ return [];
117
+ }
118
+
119
+ const specs: Array<{ name: string; requirementCount: number }> = [];
120
+ const entries = fs.readdirSync(specsDir, { withFileTypes: true });
121
+
122
+ for (const entry of entries) {
123
+ if (entry.isDirectory()) {
124
+ const specFile = path.join(specsDir, entry.name, 'spec.md');
125
+
126
+ if (fs.existsSync(specFile)) {
127
+ try {
128
+ const content = fs.readFileSync(specFile, 'utf-8');
129
+ const parser = new MarkdownParser(content);
130
+ const spec = parser.parseSpec(entry.name);
131
+ const requirementCount = spec.requirements.length;
132
+ specs.push({ name: entry.name, requirementCount });
133
+ } catch (error) {
134
+ // If spec cannot be parsed, include with 0 count
135
+ specs.push({ name: entry.name, requirementCount: 0 });
136
+ }
137
+ }
138
+ }
139
+ }
140
+
141
+ return specs;
142
+ }
143
+
144
+ private displaySummary(
145
+ changesData: { active: any[]; completed: any[] },
146
+ specsData: any[]
147
+ ): void {
148
+ const totalChanges = changesData.active.length + changesData.completed.length;
149
+ const totalSpecs = specsData.length;
150
+ const totalRequirements = specsData.reduce((sum, spec) => sum + spec.requirementCount, 0);
151
+
152
+ // Calculate total task progress
153
+ let totalTasks = 0;
154
+ let completedTasks = 0;
155
+
156
+ changesData.active.forEach(change => {
157
+ totalTasks += change.progress.total;
158
+ completedTasks += change.progress.completed;
159
+ });
160
+
161
+ changesData.completed.forEach(() => {
162
+ // Completed changes count as 100% done (we don't know exact task count)
163
+ // This is a simplification
164
+ });
165
+
166
+ console.log(chalk.bold('Summary:'));
167
+ console.log(` ${chalk.cyan('●')} Specifications: ${chalk.bold(totalSpecs)} specs, ${chalk.bold(totalRequirements)} requirements`);
168
+ console.log(` ${chalk.yellow('●')} Active Changes: ${chalk.bold(changesData.active.length)} in progress`);
169
+ console.log(` ${chalk.green('●')} Completed Changes: ${chalk.bold(changesData.completed.length)}`);
170
+
171
+ if (totalTasks > 0) {
172
+ const overallProgress = Math.round((completedTasks / totalTasks) * 100);
173
+ console.log(` ${chalk.magenta('●')} Task Progress: ${chalk.bold(`${completedTasks}/${totalTasks}`)} (${overallProgress}% complete)`);
174
+ }
175
+ }
176
+
177
+ private createProgressBar(completed: number, total: number, width: number = 20): string {
178
+ if (total === 0) return chalk.dim('─'.repeat(width));
179
+
180
+ const percentage = completed / total;
181
+ const filled = Math.round(percentage * width);
182
+ const empty = width - filled;
183
+
184
+ const filledBar = chalk.green('█'.repeat(filled));
185
+ const emptyBar = chalk.dim('░'.repeat(empty));
186
+
187
+ return `[${filledBar}${emptyBar}]`;
188
+ }
189
+ }
@@ -0,0 +1,2 @@
1
+ export * from './cli/index.js';
2
+ export * from './core/index.js';
@@ -0,0 +1,187 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+
4
+ function isMarkerOnOwnLine(content: string, markerIndex: number, markerLength: number): boolean {
5
+ let leftIndex = markerIndex - 1;
6
+ while (leftIndex >= 0 && content[leftIndex] !== '\n') {
7
+ const char = content[leftIndex];
8
+ if (char !== ' ' && char !== '\t' && char !== '\r') {
9
+ return false;
10
+ }
11
+ leftIndex--;
12
+ }
13
+
14
+ let rightIndex = markerIndex + markerLength;
15
+ while (rightIndex < content.length && content[rightIndex] !== '\n') {
16
+ const char = content[rightIndex];
17
+ if (char !== ' ' && char !== '\t' && char !== '\r') {
18
+ return false;
19
+ }
20
+ rightIndex++;
21
+ }
22
+
23
+ return true;
24
+ }
25
+
26
+ function findMarkerIndex(
27
+ content: string,
28
+ marker: string,
29
+ fromIndex = 0
30
+ ): number {
31
+ let currentIndex = content.indexOf(marker, fromIndex);
32
+
33
+ while (currentIndex !== -1) {
34
+ if (isMarkerOnOwnLine(content, currentIndex, marker.length)) {
35
+ return currentIndex;
36
+ }
37
+
38
+ currentIndex = content.indexOf(marker, currentIndex + marker.length);
39
+ }
40
+
41
+ return -1;
42
+ }
43
+
44
+ export class FileSystemUtils {
45
+ private static isWindowsBasePath(basePath: string): boolean {
46
+ return /^[A-Za-z]:[\\/]/.test(basePath) || basePath.startsWith('\\');
47
+ }
48
+
49
+ private static normalizeSegments(segments: string[]): string[] {
50
+ return segments
51
+ .flatMap((segment) => segment.split(/[\\/]+/u))
52
+ .filter((part) => part.length > 0);
53
+ }
54
+
55
+ static joinPath(basePath: string, ...segments: string[]): string {
56
+ const normalizedSegments = this.normalizeSegments(segments);
57
+
58
+ if (this.isWindowsBasePath(basePath)) {
59
+ const normalizedBasePath = path.win32.normalize(basePath);
60
+ return normalizedSegments.length
61
+ ? path.win32.join(normalizedBasePath, ...normalizedSegments)
62
+ : normalizedBasePath;
63
+ }
64
+
65
+ const posixBasePath = basePath.replace(/\\/g, '/');
66
+
67
+ return normalizedSegments.length
68
+ ? path.posix.join(posixBasePath, ...normalizedSegments)
69
+ : path.posix.normalize(posixBasePath);
70
+ }
71
+
72
+ static async createDirectory(dirPath: string): Promise<void> {
73
+ await fs.mkdir(dirPath, { recursive: true });
74
+ }
75
+
76
+ static async fileExists(filePath: string): Promise<boolean> {
77
+ try {
78
+ await fs.access(filePath);
79
+ return true;
80
+ } catch (error: any) {
81
+ if (error.code !== 'ENOENT') {
82
+ console.debug(`Unable to check if file exists at ${filePath}: ${error.message}`);
83
+ }
84
+ return false;
85
+ }
86
+ }
87
+
88
+ static async canWriteFile(filePath: string): Promise<boolean> {
89
+ try {
90
+ const stats = await fs.stat(filePath);
91
+
92
+ if (!stats.isFile()) {
93
+ return true;
94
+ }
95
+
96
+ return (stats.mode & 0o222) !== 0;
97
+ } catch (error: any) {
98
+ if (error.code === 'ENOENT') {
99
+ return true;
100
+ }
101
+
102
+ console.debug(`Unable to determine write permissions for ${filePath}: ${error.message}`);
103
+ return false;
104
+ }
105
+ }
106
+
107
+ static async directoryExists(dirPath: string): Promise<boolean> {
108
+ try {
109
+ const stats = await fs.stat(dirPath);
110
+ return stats.isDirectory();
111
+ } catch (error: any) {
112
+ if (error.code !== 'ENOENT') {
113
+ console.debug(`Unable to check if directory exists at ${dirPath}: ${error.message}`);
114
+ }
115
+ return false;
116
+ }
117
+ }
118
+
119
+ static async writeFile(filePath: string, content: string): Promise<void> {
120
+ const dir = path.dirname(filePath);
121
+ await this.createDirectory(dir);
122
+ await fs.writeFile(filePath, content, 'utf-8');
123
+ }
124
+
125
+ static async readFile(filePath: string): Promise<string> {
126
+ return await fs.readFile(filePath, 'utf-8');
127
+ }
128
+
129
+ static async updateFileWithMarkers(
130
+ filePath: string,
131
+ content: string,
132
+ startMarker: string,
133
+ endMarker: string
134
+ ): Promise<void> {
135
+ let existingContent = '';
136
+
137
+ if (await this.fileExists(filePath)) {
138
+ existingContent = await this.readFile(filePath);
139
+
140
+ const startIndex = findMarkerIndex(existingContent, startMarker);
141
+ const endIndex = startIndex !== -1
142
+ ? findMarkerIndex(existingContent, endMarker, startIndex + startMarker.length)
143
+ : findMarkerIndex(existingContent, endMarker);
144
+
145
+ if (startIndex !== -1 && endIndex !== -1) {
146
+ if (endIndex < startIndex) {
147
+ throw new Error(
148
+ `Invalid marker state in ${filePath}. End marker appears before start marker.`
149
+ );
150
+ }
151
+
152
+ const before = existingContent.substring(0, startIndex);
153
+ const after = existingContent.substring(endIndex + endMarker.length);
154
+ existingContent = before + startMarker + '\n' + content + '\n' + endMarker + after;
155
+ } else if (startIndex === -1 && endIndex === -1) {
156
+ existingContent = startMarker + '\n' + content + '\n' + endMarker + '\n\n' + existingContent;
157
+ } else {
158
+ throw new Error(`Invalid marker state in ${filePath}. Found start: ${startIndex !== -1}, Found end: ${endIndex !== -1}`);
159
+ }
160
+ } else {
161
+ existingContent = startMarker + '\n' + content + '\n' + endMarker;
162
+ }
163
+
164
+ await this.writeFile(filePath, existingContent);
165
+ }
166
+
167
+ static async ensureWritePermissions(dirPath: string): Promise<boolean> {
168
+ try {
169
+ // If directory doesn't exist, check parent directory permissions
170
+ if (!await this.directoryExists(dirPath)) {
171
+ const parentDir = path.dirname(dirPath);
172
+ if (!await this.directoryExists(parentDir)) {
173
+ await this.createDirectory(parentDir);
174
+ }
175
+ return await this.ensureWritePermissions(parentDir);
176
+ }
177
+
178
+ const testFile = path.join(dirPath, '.openspec-test-' + Date.now());
179
+ await fs.writeFile(testFile, '');
180
+ await fs.unlink(testFile);
181
+ return true;
182
+ } catch (error: any) {
183
+ console.debug(`Insufficient permissions to write to ${dirPath}: ${error.message}`);
184
+ return false;
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,2 @@
1
+ // Shared utilities will be implemented here
2
+ export {};
@@ -0,0 +1,7 @@
1
+ export function isInteractive(noInteractiveFlag?: boolean): boolean {
2
+ if (noInteractiveFlag) return false;
3
+ if (process.env.OPEN_SPEC_INTERACTIVE === '0') return false;
4
+ return !!process.stdin.isTTY;
5
+ }
6
+
7
+
@@ -0,0 +1,45 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+
4
+ export async function getActiveChangeIds(root: string = process.cwd()): Promise<string[]> {
5
+ const changesPath = path.join(root, 'openspec', 'changes');
6
+ try {
7
+ const entries = await fs.readdir(changesPath, { withFileTypes: true });
8
+ const result: string[] = [];
9
+ for (const entry of entries) {
10
+ if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'archive') continue;
11
+ const proposalPath = path.join(changesPath, entry.name, 'proposal.md');
12
+ try {
13
+ await fs.access(proposalPath);
14
+ result.push(entry.name);
15
+ } catch {
16
+ // skip directories without proposal.md
17
+ }
18
+ }
19
+ return result.sort();
20
+ } catch {
21
+ return [];
22
+ }
23
+ }
24
+
25
+ export async function getSpecIds(root: string = process.cwd()): Promise<string[]> {
26
+ const specsPath = path.join(root, 'openspec', 'specs');
27
+ const result: string[] = [];
28
+ try {
29
+ const entries = await fs.readdir(specsPath, { withFileTypes: true });
30
+ for (const entry of entries) {
31
+ if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
32
+ const specFile = path.join(specsPath, entry.name, 'spec.md');
33
+ try {
34
+ await fs.access(specFile);
35
+ result.push(entry.name);
36
+ } catch {
37
+ // ignore
38
+ }
39
+ }
40
+ } catch {
41
+ // ignore
42
+ }
43
+ return result.sort();
44
+ }
45
+
@@ -0,0 +1,26 @@
1
+ export function nearestMatches(input: string, candidates: string[], max: number = 5): string[] {
2
+ const scored = candidates.map(candidate => ({ candidate, distance: levenshtein(input, candidate) }));
3
+ scored.sort((a, b) => a.distance - b.distance);
4
+ return scored.slice(0, max).map(s => s.candidate);
5
+ }
6
+
7
+ export function levenshtein(a: string, b: string): number {
8
+ const m = a.length;
9
+ const n = b.length;
10
+ const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
11
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
12
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
13
+ for (let i = 1; i <= m; i++) {
14
+ for (let j = 1; j <= n; j++) {
15
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
16
+ dp[i][j] = Math.min(
17
+ dp[i - 1][j] + 1,
18
+ dp[i][j - 1] + 1,
19
+ dp[i - 1][j - 1] + cost
20
+ );
21
+ }
22
+ }
23
+ return dp[m][n];
24
+ }
25
+
26
+
@@ -0,0 +1,43 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+
4
+ const TASK_PATTERN = /^[-*]\s+\[[\sx]\]/i;
5
+ const COMPLETED_TASK_PATTERN = /^[-*]\s+\[x\]/i;
6
+
7
+ export interface TaskProgress {
8
+ total: number;
9
+ completed: number;
10
+ }
11
+
12
+ export function countTasksFromContent(content: string): TaskProgress {
13
+ const lines = content.split('\n');
14
+ let total = 0;
15
+ let completed = 0;
16
+ for (const line of lines) {
17
+ if (line.match(TASK_PATTERN)) {
18
+ total++;
19
+ if (line.match(COMPLETED_TASK_PATTERN)) {
20
+ completed++;
21
+ }
22
+ }
23
+ }
24
+ return { total, completed };
25
+ }
26
+
27
+ export async function getTaskProgressForChange(changesDir: string, changeName: string): Promise<TaskProgress> {
28
+ const tasksPath = path.join(changesDir, changeName, 'tasks.md');
29
+ try {
30
+ const content = await fs.readFile(tasksPath, 'utf-8');
31
+ return countTasksFromContent(content);
32
+ } catch {
33
+ return { total: 0, completed: 0 };
34
+ }
35
+ }
36
+
37
+ export function formatTaskStatus(progress: TaskProgress): string {
38
+ if (progress.total === 0) return 'No tasks';
39
+ if (progress.completed === progress.total) return '✓ Complete';
40
+ return `${progress.completed}/${progress.total} tasks`;
41
+ }
42
+
43
+
@@ -0,0 +1,156 @@
1
+ import { afterAll, describe, it, expect } from 'vitest';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { runCLI, cliProjectRoot } from '../helpers/run-cli.js';
6
+ import { AI_TOOLS } from '../../src/core/config.js';
7
+
8
+ async function fileExists(filePath: string): Promise<boolean> {
9
+ try {
10
+ await fs.access(filePath);
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ const tempRoots: string[] = [];
18
+
19
+ async function prepareFixture(fixtureName: string): Promise<string> {
20
+ const base = await fs.mkdtemp(path.join(tmpdir(), 'openspec-cli-e2e-'));
21
+ tempRoots.push(base);
22
+ const projectDir = path.join(base, 'project');
23
+ await fs.mkdir(projectDir, { recursive: true });
24
+ const fixtureDir = path.join(cliProjectRoot, 'test', 'fixtures', fixtureName);
25
+ await fs.cp(fixtureDir, projectDir, { recursive: true });
26
+ return projectDir;
27
+ }
28
+
29
+ afterAll(async () => {
30
+ await Promise.all(tempRoots.map((dir) => fs.rm(dir, { recursive: true, force: true })));
31
+ });
32
+
33
+ describe('openspec CLI e2e basics', () => {
34
+ it('shows help output', async () => {
35
+ const result = await runCLI(['--help']);
36
+ expect(result.exitCode).toBe(0);
37
+ expect(result.stdout).toContain('Usage: openspec');
38
+ expect(result.stderr).toBe('');
39
+
40
+ });
41
+
42
+ it('shows dynamic tool ids in init help', async () => {
43
+ const result = await runCLI(['init', '--help']);
44
+ expect(result.exitCode).toBe(0);
45
+
46
+ const expectedTools = AI_TOOLS.filter((tool) => tool.available)
47
+ .map((tool) => tool.value)
48
+ .join(', ');
49
+ const normalizedOutput = result.stdout.replace(/\s+/g, ' ').trim();
50
+ expect(normalizedOutput).toContain(
51
+ `Use "all", "none", or a comma-separated list of: ${expectedTools}`
52
+ );
53
+ });
54
+
55
+ it('reports the package version', async () => {
56
+ const pkgRaw = await fs.readFile(path.join(cliProjectRoot, 'package.json'), 'utf-8');
57
+ const pkg = JSON.parse(pkgRaw);
58
+ const result = await runCLI(['--version']);
59
+ expect(result.exitCode).toBe(0);
60
+ expect(result.stdout.trim()).toBe(pkg.version);
61
+ });
62
+
63
+ it('validates the tmp-init fixture with --all --json', async () => {
64
+ const projectDir = await prepareFixture('tmp-init');
65
+ const result = await runCLI(['validate', '--all', '--json'], { cwd: projectDir });
66
+ expect(result.exitCode).toBe(0);
67
+ const output = result.stdout.trim();
68
+ expect(output).not.toBe('');
69
+ const json = JSON.parse(output);
70
+ expect(json.summary?.totals?.failed).toBe(0);
71
+ expect(json.items.some((item: any) => item.id === 'c1' && item.type === 'change')).toBe(true);
72
+ });
73
+
74
+ it('returns an error for unknown items in the fixture', async () => {
75
+ const projectDir = await prepareFixture('tmp-init');
76
+ const result = await runCLI(['validate', 'does-not-exist'], { cwd: projectDir });
77
+ expect(result.exitCode).toBe(1);
78
+ expect(result.stderr).toContain("Unknown item 'does-not-exist'");
79
+ });
80
+
81
+ describe('init command non-interactive options', () => {
82
+ it('initializes with --tools all option', async () => {
83
+ const projectDir = await prepareFixture('tmp-init');
84
+ const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
85
+ await fs.mkdir(emptyProjectDir, { recursive: true });
86
+
87
+ const codexHome = path.join(emptyProjectDir, '.codex');
88
+ const result = await runCLI(['init', '--tools', 'all'], {
89
+ cwd: emptyProjectDir,
90
+ env: { CODEX_HOME: codexHome },
91
+ });
92
+ expect(result.exitCode).toBe(0);
93
+ expect(result.stdout).toContain('Tool summary:');
94
+
95
+ // Check that tool configurations were created
96
+ const claudePath = path.join(emptyProjectDir, 'CLAUDE.md');
97
+ const cursorProposal = path.join(emptyProjectDir, '.cursor/commands/openspec-proposal.md');
98
+ expect(await fileExists(claudePath)).toBe(true);
99
+ expect(await fileExists(cursorProposal)).toBe(true);
100
+ });
101
+
102
+ it('initializes with --tools list option', async () => {
103
+ const projectDir = await prepareFixture('tmp-init');
104
+ const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
105
+ await fs.mkdir(emptyProjectDir, { recursive: true });
106
+
107
+ const result = await runCLI(['init', '--tools', 'claude'], { cwd: emptyProjectDir });
108
+ expect(result.exitCode).toBe(0);
109
+ expect(result.stdout).toContain('Tool summary:');
110
+
111
+ const claudePath = path.join(emptyProjectDir, 'CLAUDE.md');
112
+ const cursorProposal = path.join(emptyProjectDir, '.cursor/commands/openspec-proposal.md');
113
+ expect(await fileExists(claudePath)).toBe(true);
114
+ expect(await fileExists(cursorProposal)).toBe(false); // Not selected
115
+ });
116
+
117
+ it('initializes with --tools none option', async () => {
118
+ const projectDir = await prepareFixture('tmp-init');
119
+ const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
120
+ await fs.mkdir(emptyProjectDir, { recursive: true });
121
+
122
+ const result = await runCLI(['init', '--tools', 'none'], { cwd: emptyProjectDir });
123
+ expect(result.exitCode).toBe(0);
124
+ expect(result.stdout).toContain('Tool summary:');
125
+
126
+ const claudePath = path.join(emptyProjectDir, 'CLAUDE.md');
127
+ const cursorProposal = path.join(emptyProjectDir, '.cursor/commands/openspec-proposal.md');
128
+ const rootAgentsPath = path.join(emptyProjectDir, 'AGENTS.md');
129
+
130
+ expect(await fileExists(rootAgentsPath)).toBe(true);
131
+ expect(await fileExists(claudePath)).toBe(false);
132
+ expect(await fileExists(cursorProposal)).toBe(false);
133
+ });
134
+
135
+ it('returns error for invalid tool names', async () => {
136
+ const projectDir = await prepareFixture('tmp-init');
137
+ const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
138
+ await fs.mkdir(emptyProjectDir, { recursive: true });
139
+
140
+ const result = await runCLI(['init', '--tools', 'invalid-tool'], { cwd: emptyProjectDir });
141
+ expect(result.exitCode).toBe(1);
142
+ expect(result.stderr).toContain('Invalid tool(s): invalid-tool');
143
+ expect(result.stderr).toContain('Available values:');
144
+ });
145
+
146
+ it('returns error when combining reserved keywords with explicit ids', async () => {
147
+ const projectDir = await prepareFixture('tmp-init');
148
+ const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
149
+ await fs.mkdir(emptyProjectDir, { recursive: true });
150
+
151
+ const result = await runCLI(['init', '--tools', 'all,claude'], { cwd: emptyProjectDir });
152
+ expect(result.exitCode).toBe(1);
153
+ expect(result.stderr).toContain('Cannot combine reserved values "all" or "none" with specific tool IDs');
154
+ });
155
+ });
156
+ });