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,680 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { ArchiveCommand } from '../../src/core/archive.js';
3
+ import { Validator } from '../../src/core/validation/validator.js';
4
+ import { promises as fs } from 'fs';
5
+ import path from 'path';
6
+ import os from 'os';
7
+
8
+ // Mock @inquirer/prompts
9
+ vi.mock('@inquirer/prompts', () => ({
10
+ select: vi.fn(),
11
+ confirm: vi.fn()
12
+ }));
13
+
14
+ describe('ArchiveCommand', () => {
15
+ let tempDir: string;
16
+ let archiveCommand: ArchiveCommand;
17
+ const originalConsoleLog = console.log;
18
+
19
+ beforeEach(async () => {
20
+ // Create temp directory
21
+ tempDir = path.join(os.tmpdir(), `openspec-archive-test-${Date.now()}`);
22
+ await fs.mkdir(tempDir, { recursive: true });
23
+
24
+ // Change to temp directory
25
+ process.chdir(tempDir);
26
+
27
+ // Create OpenSpec structure
28
+ const openspecDir = path.join(tempDir, 'openspec');
29
+ await fs.mkdir(path.join(openspecDir, 'changes'), { recursive: true });
30
+ await fs.mkdir(path.join(openspecDir, 'specs'), { recursive: true });
31
+ await fs.mkdir(path.join(openspecDir, 'changes', 'archive'), { recursive: true });
32
+
33
+ // Suppress console.log during tests
34
+ console.log = vi.fn();
35
+
36
+ archiveCommand = new ArchiveCommand();
37
+ });
38
+
39
+ afterEach(async () => {
40
+ // Restore console.log
41
+ console.log = originalConsoleLog;
42
+
43
+ // Clear mocks
44
+ vi.clearAllMocks();
45
+
46
+ // Clean up temp directory
47
+ try {
48
+ await fs.rm(tempDir, { recursive: true, force: true });
49
+ } catch (error) {
50
+ // Ignore cleanup errors
51
+ }
52
+ });
53
+
54
+ describe('execute', () => {
55
+ it('should archive a change successfully', async () => {
56
+ // Create a test change
57
+ const changeName = 'test-feature';
58
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
59
+ await fs.mkdir(changeDir, { recursive: true });
60
+
61
+ // Create tasks.md with completed tasks
62
+ const tasksContent = '- [x] Task 1\n- [x] Task 2';
63
+ await fs.writeFile(path.join(changeDir, 'tasks.md'), tasksContent);
64
+
65
+ // Execute archive with --yes flag
66
+ await archiveCommand.execute(changeName, { yes: true });
67
+
68
+ // Check that change was moved to archive
69
+ const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
70
+ const archives = await fs.readdir(archiveDir);
71
+
72
+ expect(archives.length).toBe(1);
73
+ expect(archives[0]).toMatch(new RegExp(`\\d{4}-\\d{2}-\\d{2}-${changeName}`));
74
+
75
+ // Verify original change directory no longer exists
76
+ await expect(fs.access(changeDir)).rejects.toThrow();
77
+ });
78
+
79
+ it('should warn about incomplete tasks', async () => {
80
+ const changeName = 'incomplete-feature';
81
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
82
+ await fs.mkdir(changeDir, { recursive: true });
83
+
84
+ // Create tasks.md with incomplete tasks
85
+ const tasksContent = '- [x] Task 1\n- [ ] Task 2\n- [ ] Task 3';
86
+ await fs.writeFile(path.join(changeDir, 'tasks.md'), tasksContent);
87
+
88
+ // Execute archive with --yes flag
89
+ await archiveCommand.execute(changeName, { yes: true });
90
+
91
+ // Verify warning was logged
92
+ expect(console.log).toHaveBeenCalledWith(
93
+ expect.stringContaining('Warning: 2 incomplete task(s) found')
94
+ );
95
+ });
96
+
97
+ it('should update specs when archiving (delta-based ADDED) and include change name in skeleton', async () => {
98
+ const changeName = 'spec-feature';
99
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
100
+ const changeSpecDir = path.join(changeDir, 'specs', 'test-capability');
101
+ await fs.mkdir(changeSpecDir, { recursive: true });
102
+
103
+ // Create delta-based change spec (ADDED requirement)
104
+ const specContent = `# Test Capability Spec - Changes
105
+
106
+ ## ADDED Requirements
107
+
108
+ ### Requirement: The system SHALL provide test capability
109
+
110
+ #### Scenario: Basic test
111
+ Given a test condition
112
+ When an action occurs
113
+ Then expected result happens`;
114
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), specContent);
115
+
116
+ // Execute archive with --yes flag and skip validation for speed
117
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
118
+
119
+ // Verify spec was created from skeleton and ADDED requirement applied
120
+ const mainSpecPath = path.join(tempDir, 'openspec', 'specs', 'test-capability', 'spec.md');
121
+ const updatedContent = await fs.readFile(mainSpecPath, 'utf-8');
122
+ expect(updatedContent).toContain('# test-capability Specification');
123
+ expect(updatedContent).toContain('## Purpose');
124
+ expect(updatedContent).toContain(`created by archiving change ${changeName}`);
125
+ expect(updatedContent).toContain('## Requirements');
126
+ expect(updatedContent).toContain('### Requirement: The system SHALL provide test capability');
127
+ expect(updatedContent).toContain('#### Scenario: Basic test');
128
+ });
129
+
130
+ it('should throw error if change does not exist', async () => {
131
+ await expect(
132
+ archiveCommand.execute('non-existent-change', { yes: true })
133
+ ).rejects.toThrow("Change 'non-existent-change' not found.");
134
+ });
135
+
136
+ it('should throw error if archive already exists', async () => {
137
+ const changeName = 'duplicate-feature';
138
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
139
+ await fs.mkdir(changeDir, { recursive: true });
140
+
141
+ // Create existing archive with same date
142
+ const date = new Date().toISOString().split('T')[0];
143
+ const archivePath = path.join(tempDir, 'openspec', 'changes', 'archive', `${date}-${changeName}`);
144
+ await fs.mkdir(archivePath, { recursive: true });
145
+
146
+ // Try to archive
147
+ await expect(
148
+ archiveCommand.execute(changeName, { yes: true })
149
+ ).rejects.toThrow(`Archive '${date}-${changeName}' already exists.`);
150
+ });
151
+
152
+ it('should handle changes without tasks.md', async () => {
153
+ const changeName = 'no-tasks-feature';
154
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
155
+ await fs.mkdir(changeDir, { recursive: true });
156
+
157
+ // Execute archive without tasks.md
158
+ await archiveCommand.execute(changeName, { yes: true });
159
+
160
+ // Should complete without warnings
161
+ expect(console.log).not.toHaveBeenCalledWith(
162
+ expect.stringContaining('incomplete task(s)')
163
+ );
164
+
165
+ // Verify change was archived
166
+ const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
167
+ const archives = await fs.readdir(archiveDir);
168
+ expect(archives.length).toBe(1);
169
+ });
170
+
171
+ it('should handle changes without specs', async () => {
172
+ const changeName = 'no-specs-feature';
173
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
174
+ await fs.mkdir(changeDir, { recursive: true });
175
+
176
+ // Execute archive without specs
177
+ await archiveCommand.execute(changeName, { yes: true });
178
+
179
+ // Should complete without spec updates
180
+ expect(console.log).not.toHaveBeenCalledWith(
181
+ expect.stringContaining('Specs to update')
182
+ );
183
+
184
+ // Verify change was archived
185
+ const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
186
+ const archives = await fs.readdir(archiveDir);
187
+ expect(archives.length).toBe(1);
188
+ });
189
+
190
+ it('should skip spec updates when --skip-specs flag is used', async () => {
191
+ const changeName = 'skip-specs-feature';
192
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
193
+ const changeSpecDir = path.join(changeDir, 'specs', 'test-capability');
194
+ await fs.mkdir(changeSpecDir, { recursive: true });
195
+
196
+ // Create spec in change
197
+ const specContent = '# Test Capability Spec\n\nTest content';
198
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), specContent);
199
+
200
+ // Execute archive with --skip-specs flag and noValidate to skip validation
201
+ await archiveCommand.execute(changeName, { yes: true, skipSpecs: true, noValidate: true });
202
+
203
+ // Verify skip message was logged
204
+ expect(console.log).toHaveBeenCalledWith(
205
+ 'Skipping spec updates (--skip-specs flag provided).'
206
+ );
207
+
208
+ // Verify spec was NOT copied to main specs
209
+ const mainSpecPath = path.join(tempDir, 'openspec', 'specs', 'test-capability', 'spec.md');
210
+ await expect(fs.access(mainSpecPath)).rejects.toThrow();
211
+
212
+ // Verify change was still archived
213
+ const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
214
+ const archives = await fs.readdir(archiveDir);
215
+ expect(archives.length).toBe(1);
216
+ expect(archives[0]).toMatch(new RegExp(`\\d{4}-\\d{2}-\\d{2}-${changeName}`));
217
+ });
218
+
219
+ it('should skip validation when commander sets validate to false (--no-validate)', async () => {
220
+ const changeName = 'skip-validation-flag';
221
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
222
+ const changeSpecDir = path.join(changeDir, 'specs', 'unstable-capability');
223
+ await fs.mkdir(changeSpecDir, { recursive: true });
224
+
225
+ const deltaSpec = `# Unstable Capability
226
+
227
+ ## ADDED Requirements
228
+
229
+ ### Requirement: Logging Feature
230
+ **ID**: REQ-LOG-001
231
+
232
+ The system will log all events.
233
+
234
+ #### Scenario: Event recorded
235
+ - **WHEN** an event occurs
236
+ - **THEN** it is captured`;
237
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), deltaSpec);
238
+ await fs.writeFile(path.join(changeDir, 'tasks.md'), '- [x] Task 1\n');
239
+
240
+ const deltaSpy = vi.spyOn(Validator.prototype, 'validateChangeDeltaSpecs');
241
+ const specContentSpy = vi.spyOn(Validator.prototype, 'validateSpecContent');
242
+
243
+ try {
244
+ await archiveCommand.execute(changeName, { yes: true, skipSpecs: true, validate: false });
245
+
246
+ expect(deltaSpy).not.toHaveBeenCalled();
247
+ expect(specContentSpy).not.toHaveBeenCalled();
248
+
249
+ const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
250
+ const archives = await fs.readdir(archiveDir);
251
+ expect(archives.length).toBe(1);
252
+ expect(archives[0]).toMatch(new RegExp(`\\d{4}-\\d{2}-\\d{2}-${changeName}`));
253
+ } finally {
254
+ deltaSpy.mockRestore();
255
+ specContentSpy.mockRestore();
256
+ }
257
+ });
258
+
259
+ it('should proceed with archive when user declines spec updates', async () => {
260
+ const { confirm } = await import('@inquirer/prompts');
261
+ const mockConfirm = confirm as unknown as ReturnType<typeof vi.fn>;
262
+
263
+ const changeName = 'decline-specs-feature';
264
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
265
+ const changeSpecDir = path.join(changeDir, 'specs', 'test-capability');
266
+ await fs.mkdir(changeSpecDir, { recursive: true });
267
+
268
+ // Create valid spec in change
269
+ const specContent = `# Test Capability Spec
270
+
271
+ ## Purpose
272
+ This is a test capability specification.
273
+
274
+ ## Requirements
275
+
276
+ ### The system SHALL provide test capability
277
+
278
+ #### Scenario: Basic test
279
+ Given a test condition
280
+ When an action occurs
281
+ Then expected result happens`;
282
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), specContent);
283
+
284
+ // Mock confirm to return false (decline spec updates)
285
+ mockConfirm.mockResolvedValueOnce(false);
286
+
287
+ // Execute archive without --yes flag
288
+ await archiveCommand.execute(changeName);
289
+
290
+ // Verify user was prompted about specs
291
+ expect(mockConfirm).toHaveBeenCalledWith({
292
+ message: 'Proceed with spec updates?',
293
+ default: true
294
+ });
295
+
296
+ // Verify skip message was logged
297
+ expect(console.log).toHaveBeenCalledWith(
298
+ 'Skipping spec updates. Proceeding with archive.'
299
+ );
300
+
301
+ // Verify spec was NOT copied to main specs
302
+ const mainSpecPath = path.join(tempDir, 'openspec', 'specs', 'test-capability', 'spec.md');
303
+ await expect(fs.access(mainSpecPath)).rejects.toThrow();
304
+
305
+ // Verify change was still archived
306
+ const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
307
+ const archives = await fs.readdir(archiveDir);
308
+ expect(archives.length).toBe(1);
309
+ expect(archives[0]).toMatch(new RegExp(`\\d{4}-\\d{2}-\\d{2}-${changeName}`));
310
+ });
311
+
312
+ it('should support header trim-only normalization for matching', async () => {
313
+ const changeName = 'normalize-headers';
314
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
315
+ const changeSpecDir = path.join(changeDir, 'specs', 'alpha');
316
+ await fs.mkdir(changeSpecDir, { recursive: true });
317
+
318
+ // Create existing main spec with a requirement (no extra trailing spaces)
319
+ const mainSpecDir = path.join(tempDir, 'openspec', 'specs', 'alpha');
320
+ await fs.mkdir(mainSpecDir, { recursive: true });
321
+ const mainContent = `# alpha Specification
322
+
323
+ ## Purpose
324
+ Alpha purpose.
325
+
326
+ ## Requirements
327
+
328
+ ### Requirement: Important Rule
329
+ Some details.`;
330
+ await fs.writeFile(path.join(mainSpecDir, 'spec.md'), mainContent);
331
+
332
+ // Change attempts to modify the same requirement but with trailing spaces after the name
333
+ const deltaContent = `# Alpha - Changes
334
+
335
+ ## MODIFIED Requirements
336
+
337
+ ### Requirement: Important Rule
338
+ Updated details.`;
339
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), deltaContent);
340
+
341
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
342
+
343
+ const updated = await fs.readFile(path.join(mainSpecDir, 'spec.md'), 'utf-8');
344
+ expect(updated).toContain('### Requirement: Important Rule');
345
+ expect(updated).toContain('Updated details.');
346
+ });
347
+
348
+ it('should apply operations in order: RENAMED → REMOVED → MODIFIED → ADDED', async () => {
349
+ const changeName = 'apply-order';
350
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
351
+ const changeSpecDir = path.join(changeDir, 'specs', 'beta');
352
+ await fs.mkdir(changeSpecDir, { recursive: true });
353
+
354
+ // Main spec with two requirements A and B
355
+ const mainSpecDir = path.join(tempDir, 'openspec', 'specs', 'beta');
356
+ await fs.mkdir(mainSpecDir, { recursive: true });
357
+ const mainContent = `# beta Specification
358
+
359
+ ## Purpose
360
+ Beta purpose.
361
+
362
+ ## Requirements
363
+
364
+ ### Requirement: A
365
+ content A
366
+
367
+ ### Requirement: B
368
+ content B`;
369
+ await fs.writeFile(path.join(mainSpecDir, 'spec.md'), mainContent);
370
+
371
+ // Rename A->C, Remove B, Modify C, Add D
372
+ const deltaContent = `# Beta - Changes
373
+
374
+ ## RENAMED Requirements
375
+ - FROM: \`### Requirement: A\`
376
+ - TO: \`### Requirement: C\`
377
+
378
+ ## REMOVED Requirements
379
+ ### Requirement: B
380
+
381
+ ## MODIFIED Requirements
382
+ ### Requirement: C
383
+ updated C
384
+
385
+ ## ADDED Requirements
386
+ ### Requirement: D
387
+ content D`;
388
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), deltaContent);
389
+
390
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
391
+
392
+ const updated = await fs.readFile(path.join(mainSpecDir, 'spec.md'), 'utf-8');
393
+ expect(updated).toContain('### Requirement: C');
394
+ expect(updated).toContain('updated C');
395
+ expect(updated).toContain('### Requirement: D');
396
+ expect(updated).not.toContain('### Requirement: A');
397
+ expect(updated).not.toContain('### Requirement: B');
398
+ });
399
+
400
+ it('should abort with error when MODIFIED/REMOVED reference non-existent requirements', async () => {
401
+ const changeName = 'validate-missing';
402
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
403
+ const changeSpecDir = path.join(changeDir, 'specs', 'gamma');
404
+ await fs.mkdir(changeSpecDir, { recursive: true });
405
+
406
+ // Main spec with no requirements
407
+ const mainSpecDir = path.join(tempDir, 'openspec', 'specs', 'gamma');
408
+ await fs.mkdir(mainSpecDir, { recursive: true });
409
+ const mainContent = `# gamma Specification
410
+
411
+ ## Purpose
412
+ Gamma purpose.
413
+
414
+ ## Requirements`;
415
+ await fs.writeFile(path.join(mainSpecDir, 'spec.md'), mainContent);
416
+
417
+ // Delta tries to modify and remove non-existent requirement
418
+ const deltaContent = `# Gamma - Changes
419
+
420
+ ## MODIFIED Requirements
421
+ ### Requirement: Missing
422
+ new text
423
+
424
+ ## REMOVED Requirements
425
+ ### Requirement: Another Missing`;
426
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), deltaContent);
427
+
428
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
429
+
430
+ // Should not change the main spec and should not archive the change dir
431
+ const still = await fs.readFile(path.join(mainSpecDir, 'spec.md'), 'utf-8');
432
+ expect(still).toBe(mainContent);
433
+ // Change dir should still exist since operation aborted
434
+ await expect(fs.access(changeDir)).resolves.not.toThrow();
435
+ });
436
+
437
+ it('should require MODIFIED to reference the NEW header when a rename exists (error format)', async () => {
438
+ const changeName = 'rename-modify-new-header';
439
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
440
+ const changeSpecDir = path.join(changeDir, 'specs', 'delta');
441
+ await fs.mkdir(changeSpecDir, { recursive: true });
442
+
443
+ // Main spec with Old
444
+ const mainSpecDir = path.join(tempDir, 'openspec', 'specs', 'delta');
445
+ await fs.mkdir(mainSpecDir, { recursive: true });
446
+ const mainContent = `# delta Specification
447
+
448
+ ## Purpose
449
+ Delta purpose.
450
+
451
+ ## Requirements
452
+
453
+ ### Requirement: Old
454
+ old body`;
455
+ await fs.writeFile(path.join(mainSpecDir, 'spec.md'), mainContent);
456
+
457
+ // Delta: rename Old->New, but MODIFIED references Old (should abort)
458
+ const badDelta = `# Delta - Changes
459
+
460
+ ## RENAMED Requirements
461
+ - FROM: \`### Requirement: Old\`
462
+ - TO: \`### Requirement: New\`
463
+
464
+ ## MODIFIED Requirements
465
+ ### Requirement: Old
466
+ new body`;
467
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), badDelta);
468
+
469
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
470
+ const unchanged = await fs.readFile(path.join(mainSpecDir, 'spec.md'), 'utf-8');
471
+ expect(unchanged).toBe(mainContent);
472
+ // Assert error message format and abort notice
473
+ expect(console.log).toHaveBeenCalledWith(
474
+ expect.stringContaining('delta validation failed')
475
+ );
476
+ expect(console.log).toHaveBeenCalledWith(
477
+ expect.stringContaining('Aborted. No files were changed.')
478
+ );
479
+
480
+ // Fix MODIFIED to reference New (should succeed)
481
+ const goodDelta = `# Delta - Changes
482
+
483
+ ## RENAMED Requirements
484
+ - FROM: \`### Requirement: Old\`
485
+ - TO: \`### Requirement: New\`
486
+
487
+ ## MODIFIED Requirements
488
+ ### Requirement: New
489
+ new body`;
490
+ await fs.writeFile(path.join(changeSpecDir, 'spec.md'), goodDelta);
491
+
492
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
493
+ const updated = await fs.readFile(path.join(mainSpecDir, 'spec.md'), 'utf-8');
494
+ expect(updated).toContain('### Requirement: New');
495
+ expect(updated).toContain('new body');
496
+ expect(updated).not.toContain('### Requirement: Old');
497
+ });
498
+
499
+ it('should process multiple specs atomically (any failure aborts all)', async () => {
500
+ const changeName = 'multi-spec-atomic';
501
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
502
+ const spec1Dir = path.join(changeDir, 'specs', 'epsilon');
503
+ const spec2Dir = path.join(changeDir, 'specs', 'zeta');
504
+ await fs.mkdir(spec1Dir, { recursive: true });
505
+ await fs.mkdir(spec2Dir, { recursive: true });
506
+
507
+ // Existing main specs
508
+ const epsilonMain = path.join(tempDir, 'openspec', 'specs', 'epsilon', 'spec.md');
509
+ await fs.mkdir(path.dirname(epsilonMain), { recursive: true });
510
+ await fs.writeFile(epsilonMain, `# epsilon Specification
511
+
512
+ ## Purpose
513
+ Epsilon purpose.
514
+
515
+ ## Requirements
516
+
517
+ ### Requirement: E1
518
+ e1`);
519
+
520
+ const zetaMain = path.join(tempDir, 'openspec', 'specs', 'zeta', 'spec.md');
521
+ await fs.mkdir(path.dirname(zetaMain), { recursive: true });
522
+ await fs.writeFile(zetaMain, `# zeta Specification
523
+
524
+ ## Purpose
525
+ Zeta purpose.
526
+
527
+ ## Requirements
528
+
529
+ ### Requirement: Z1
530
+ z1`);
531
+
532
+ // Delta: epsilon is valid modification; zeta tries to remove non-existent -> should abort both
533
+ await fs.writeFile(path.join(spec1Dir, 'spec.md'), `# Epsilon - Changes
534
+
535
+ ## MODIFIED Requirements
536
+ ### Requirement: E1
537
+ E1 updated`);
538
+
539
+ await fs.writeFile(path.join(spec2Dir, 'spec.md'), `# Zeta - Changes
540
+
541
+ ## REMOVED Requirements
542
+ ### Requirement: Missing`);
543
+
544
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
545
+
546
+ const e1 = await fs.readFile(epsilonMain, 'utf-8');
547
+ const z1 = await fs.readFile(zetaMain, 'utf-8');
548
+ expect(e1).toContain('### Requirement: E1');
549
+ expect(e1).not.toContain('E1 updated');
550
+ expect(z1).toContain('### Requirement: Z1');
551
+ // changeDir should still exist
552
+ await expect(fs.access(changeDir)).resolves.not.toThrow();
553
+ });
554
+
555
+ it('should display aggregated totals across multiple specs', async () => {
556
+ const changeName = 'multi-spec-totals';
557
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
558
+ const spec1Dir = path.join(changeDir, 'specs', 'omega');
559
+ const spec2Dir = path.join(changeDir, 'specs', 'psi');
560
+ await fs.mkdir(spec1Dir, { recursive: true });
561
+ await fs.mkdir(spec2Dir, { recursive: true });
562
+
563
+ // Existing main specs
564
+ const omegaMain = path.join(tempDir, 'openspec', 'specs', 'omega', 'spec.md');
565
+ await fs.mkdir(path.dirname(omegaMain), { recursive: true });
566
+ await fs.writeFile(omegaMain, `# omega Specification\n\n## Purpose\nOmega purpose.\n\n## Requirements\n\n### Requirement: O1\no1`);
567
+
568
+ const psiMain = path.join(tempDir, 'openspec', 'specs', 'psi', 'spec.md');
569
+ await fs.mkdir(path.dirname(psiMain), { recursive: true });
570
+ await fs.writeFile(psiMain, `# psi Specification\n\n## Purpose\nPsi purpose.\n\n## Requirements\n\n### Requirement: P1\np1`);
571
+
572
+ // Deltas: omega add one, psi rename and modify -> totals: +1, ~1, -0, →1
573
+ await fs.writeFile(path.join(spec1Dir, 'spec.md'), `# Omega - Changes\n\n## ADDED Requirements\n\n### Requirement: O2\nnew`);
574
+ await fs.writeFile(path.join(spec2Dir, 'spec.md'), `# Psi - Changes\n\n## RENAMED Requirements\n- FROM: \`### Requirement: P1\`\n- TO: \`### Requirement: P2\`\n\n## MODIFIED Requirements\n### Requirement: P2\nupdated`);
575
+
576
+ await archiveCommand.execute(changeName, { yes: true, noValidate: true });
577
+
578
+ // Verify aggregated totals line was printed
579
+ expect(console.log).toHaveBeenCalledWith(
580
+ expect.stringContaining('Totals: + 1, ~ 1, - 0, → 1')
581
+ );
582
+ });
583
+ });
584
+
585
+ describe('error handling', () => {
586
+ it('should throw error when openspec directory does not exist', async () => {
587
+ // Remove openspec directory
588
+ await fs.rm(path.join(tempDir, 'openspec'), { recursive: true });
589
+
590
+ await expect(
591
+ archiveCommand.execute('any-change', { yes: true })
592
+ ).rejects.toThrow("No OpenSpec changes directory found. Run 'openspec init' first.");
593
+ });
594
+ });
595
+
596
+ describe('interactive mode', () => {
597
+ it('should use select prompt for change selection', async () => {
598
+ const { select } = await import('@inquirer/prompts');
599
+ const mockSelect = select as unknown as ReturnType<typeof vi.fn>;
600
+
601
+ // Create test changes
602
+ const change1 = 'feature-a';
603
+ const change2 = 'feature-b';
604
+ await fs.mkdir(path.join(tempDir, 'openspec', 'changes', change1), { recursive: true });
605
+ await fs.mkdir(path.join(tempDir, 'openspec', 'changes', change2), { recursive: true });
606
+
607
+ // Mock select to return first change
608
+ mockSelect.mockResolvedValueOnce(change1);
609
+
610
+ // Execute without change name
611
+ await archiveCommand.execute(undefined, { yes: true });
612
+
613
+ // Verify select was called with correct options (values matter, names may include progress)
614
+ expect(mockSelect).toHaveBeenCalledWith(expect.objectContaining({
615
+ message: 'Select a change to archive',
616
+ choices: expect.arrayContaining([
617
+ expect.objectContaining({ value: change1 }),
618
+ expect.objectContaining({ value: change2 })
619
+ ])
620
+ }));
621
+
622
+ // Verify the selected change was archived
623
+ const archiveDir = path.join(tempDir, 'openspec', 'changes', 'archive');
624
+ const archives = await fs.readdir(archiveDir);
625
+ expect(archives[0]).toContain(change1);
626
+ });
627
+
628
+ it('should use confirm prompt for task warnings', async () => {
629
+ const { confirm } = await import('@inquirer/prompts');
630
+ const mockConfirm = confirm as unknown as ReturnType<typeof vi.fn>;
631
+
632
+ const changeName = 'incomplete-interactive';
633
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
634
+ await fs.mkdir(changeDir, { recursive: true });
635
+
636
+ // Create tasks.md with incomplete tasks
637
+ const tasksContent = '- [ ] Task 1';
638
+ await fs.writeFile(path.join(changeDir, 'tasks.md'), tasksContent);
639
+
640
+ // Mock confirm to return true (proceed)
641
+ mockConfirm.mockResolvedValueOnce(true);
642
+
643
+ // Execute without --yes flag
644
+ await archiveCommand.execute(changeName);
645
+
646
+ // Verify confirm was called
647
+ expect(mockConfirm).toHaveBeenCalledWith({
648
+ message: 'Warning: 1 incomplete task(s) found. Continue?',
649
+ default: false
650
+ });
651
+ });
652
+
653
+ it('should cancel when user declines task warning', async () => {
654
+ const { confirm } = await import('@inquirer/prompts');
655
+ const mockConfirm = confirm as unknown as ReturnType<typeof vi.fn>;
656
+
657
+ const changeName = 'cancel-test';
658
+ const changeDir = path.join(tempDir, 'openspec', 'changes', changeName);
659
+ await fs.mkdir(changeDir, { recursive: true });
660
+
661
+ // Create tasks.md with incomplete tasks
662
+ const tasksContent = '- [ ] Task 1';
663
+ await fs.writeFile(path.join(changeDir, 'tasks.md'), tasksContent);
664
+
665
+ // Mock confirm to return false (cancel) for validation skip
666
+ mockConfirm.mockResolvedValueOnce(false);
667
+ // Mock another false for task warning
668
+ mockConfirm.mockResolvedValueOnce(false);
669
+
670
+ // Execute without --yes flag but skip validation to test task warning
671
+ await archiveCommand.execute(changeName, { noValidate: true });
672
+
673
+ // Verify archive was cancelled
674
+ expect(console.log).toHaveBeenCalledWith('Archive cancelled.');
675
+
676
+ // Verify change was not archived
677
+ await expect(fs.access(changeDir)).resolves.not.toThrow();
678
+ });
679
+ });
680
+ });