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,139 @@
1
+ import { spawn } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const projectRoot = path.resolve(__dirname, '..', '..');
10
+ const cliEntry = path.join(projectRoot, 'dist', 'cli', 'index.js');
11
+
12
+ let buildPromise: Promise<void> | undefined;
13
+
14
+ interface RunCommandOptions {
15
+ cwd?: string;
16
+ env?: NodeJS.ProcessEnv;
17
+ }
18
+
19
+ interface RunCLIOptions {
20
+ cwd?: string;
21
+ env?: NodeJS.ProcessEnv;
22
+ input?: string;
23
+ timeoutMs?: number;
24
+ }
25
+
26
+ export interface RunCLIResult {
27
+ exitCode: number | null;
28
+ signal: NodeJS.Signals | null;
29
+ stdout: string;
30
+ stderr: string;
31
+ timedOut: boolean;
32
+ command: string;
33
+ }
34
+
35
+ function runCommand(command: string, args: string[], options: RunCommandOptions = {}) {
36
+ return new Promise<void>((resolve, reject) => {
37
+ const child = spawn(command, args, {
38
+ cwd: options.cwd ?? projectRoot,
39
+ env: { ...process.env, ...options.env },
40
+ stdio: 'inherit',
41
+ shell: process.platform === 'win32',
42
+ });
43
+
44
+ child.on('error', (error) => reject(error));
45
+ child.on('close', (code, signal) => {
46
+ if (code === 0) {
47
+ resolve();
48
+ } else {
49
+ const reason = signal ? `signal ${signal}` : `exit code ${code}`;
50
+ reject(new Error(`Command failed (${reason}): ${command} ${args.join(' ')}`));
51
+ }
52
+ });
53
+ });
54
+ }
55
+
56
+ export async function ensureCliBuilt() {
57
+ if (existsSync(cliEntry)) {
58
+ return;
59
+ }
60
+
61
+ if (!buildPromise) {
62
+ buildPromise = runCommand('pnpm', ['run', 'build']).catch((error) => {
63
+ buildPromise = undefined;
64
+ throw error;
65
+ });
66
+ }
67
+
68
+ await buildPromise;
69
+
70
+ if (!existsSync(cliEntry)) {
71
+ throw new Error('CLI entry point missing after build. Expected dist/cli/index.js');
72
+ }
73
+ }
74
+
75
+ export async function runCLI(args: string[] = [], options: RunCLIOptions = {}): Promise<RunCLIResult> {
76
+ await ensureCliBuilt();
77
+
78
+ const finalArgs = Array.isArray(args) ? args : [args];
79
+ const invocation = [cliEntry, ...finalArgs].join(' ');
80
+
81
+ return new Promise<RunCLIResult>((resolve, reject) => {
82
+ const child = spawn(process.execPath, [cliEntry, ...finalArgs], {
83
+ cwd: options.cwd ?? projectRoot,
84
+ env: {
85
+ ...process.env,
86
+ OPEN_SPEC_INTERACTIVE: '0',
87
+ ...options.env,
88
+ },
89
+ stdio: ['pipe', 'pipe', 'pipe'],
90
+ windowsHide: true,
91
+ });
92
+
93
+ let stdout = '';
94
+ let stderr = '';
95
+ let timedOut = false;
96
+
97
+ const timeout = options.timeoutMs
98
+ ? setTimeout(() => {
99
+ timedOut = true;
100
+ child.kill('SIGKILL');
101
+ }, options.timeoutMs)
102
+ : undefined;
103
+
104
+ child.stdout?.setEncoding('utf-8');
105
+ child.stdout?.on('data', (chunk) => {
106
+ stdout += chunk;
107
+ });
108
+
109
+ child.stderr?.setEncoding('utf-8');
110
+ child.stderr?.on('data', (chunk) => {
111
+ stderr += chunk;
112
+ });
113
+
114
+ child.on('error', (error) => {
115
+ if (timeout) clearTimeout(timeout);
116
+ reject(error);
117
+ });
118
+
119
+ child.on('close', (code, signal) => {
120
+ if (timeout) clearTimeout(timeout);
121
+ resolve({
122
+ exitCode: code,
123
+ signal,
124
+ stdout,
125
+ stderr,
126
+ timedOut,
127
+ command: `node ${invocation}`,
128
+ });
129
+ });
130
+
131
+ if (options.input && child.stdin) {
132
+ child.stdin.end(options.input);
133
+ } else if (child.stdin) {
134
+ child.stdin.end();
135
+ }
136
+ });
137
+ }
138
+
139
+ export const cliProjectRoot = projectRoot;
@@ -0,0 +1,211 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { randomUUID } from 'crypto';
6
+ import { FileSystemUtils } from '../../src/utils/file-system.js';
7
+
8
+ describe('FileSystemUtils', () => {
9
+ let testDir: string;
10
+
11
+ beforeEach(async () => {
12
+ testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`);
13
+ await fs.mkdir(testDir, { recursive: true });
14
+ });
15
+
16
+ afterEach(async () => {
17
+ await fs.rm(testDir, { recursive: true, force: true });
18
+ });
19
+
20
+ describe('createDirectory', () => {
21
+ it('should create a directory', async () => {
22
+ const dirPath = path.join(testDir, 'new-dir');
23
+ await FileSystemUtils.createDirectory(dirPath);
24
+
25
+ const stats = await fs.stat(dirPath);
26
+ expect(stats.isDirectory()).toBe(true);
27
+ });
28
+
29
+ it('should create nested directories', async () => {
30
+ const dirPath = path.join(testDir, 'nested', 'deep', 'dir');
31
+ await FileSystemUtils.createDirectory(dirPath);
32
+
33
+ const stats = await fs.stat(dirPath);
34
+ expect(stats.isDirectory()).toBe(true);
35
+ });
36
+
37
+ it('should not throw if directory already exists', async () => {
38
+ const dirPath = path.join(testDir, 'existing-dir');
39
+ await fs.mkdir(dirPath);
40
+
41
+ await expect(FileSystemUtils.createDirectory(dirPath)).resolves.not.toThrow();
42
+ });
43
+ });
44
+
45
+ describe('fileExists', () => {
46
+ it('should return true for existing file', async () => {
47
+ const filePath = path.join(testDir, 'test.txt');
48
+ await fs.writeFile(filePath, 'test content');
49
+
50
+ const exists = await FileSystemUtils.fileExists(filePath);
51
+ expect(exists).toBe(true);
52
+ });
53
+
54
+ it('should return false for non-existing file', async () => {
55
+ const filePath = path.join(testDir, 'non-existent.txt');
56
+
57
+ const exists = await FileSystemUtils.fileExists(filePath);
58
+ expect(exists).toBe(false);
59
+ });
60
+
61
+ it('should return false for directory path', async () => {
62
+ const dirPath = path.join(testDir, 'dir');
63
+ await fs.mkdir(dirPath);
64
+
65
+ const exists = await FileSystemUtils.fileExists(dirPath);
66
+ expect(exists).toBe(true); // fs.access doesn't distinguish between files and directories
67
+ });
68
+ });
69
+
70
+ describe('directoryExists', () => {
71
+ it('should return true for existing directory', async () => {
72
+ const dirPath = path.join(testDir, 'test-dir');
73
+ await fs.mkdir(dirPath);
74
+
75
+ const exists = await FileSystemUtils.directoryExists(dirPath);
76
+ expect(exists).toBe(true);
77
+ });
78
+
79
+ it('should return false for non-existing directory', async () => {
80
+ const dirPath = path.join(testDir, 'non-existent-dir');
81
+
82
+ const exists = await FileSystemUtils.directoryExists(dirPath);
83
+ expect(exists).toBe(false);
84
+ });
85
+
86
+ it('should return false for file path', async () => {
87
+ const filePath = path.join(testDir, 'file.txt');
88
+ await fs.writeFile(filePath, 'content');
89
+
90
+ const exists = await FileSystemUtils.directoryExists(filePath);
91
+ expect(exists).toBe(false);
92
+ });
93
+ });
94
+
95
+ describe('writeFile', () => {
96
+ it('should write content to file', async () => {
97
+ const filePath = path.join(testDir, 'output.txt');
98
+ const content = 'Hello, World!';
99
+
100
+ await FileSystemUtils.writeFile(filePath, content);
101
+
102
+ const readContent = await fs.readFile(filePath, 'utf-8');
103
+ expect(readContent).toBe(content);
104
+ });
105
+
106
+ it('should create directory if it does not exist', async () => {
107
+ const filePath = path.join(testDir, 'nested', 'dir', 'output.txt');
108
+ const content = 'Nested content';
109
+
110
+ await FileSystemUtils.writeFile(filePath, content);
111
+
112
+ const readContent = await fs.readFile(filePath, 'utf-8');
113
+ expect(readContent).toBe(content);
114
+ });
115
+
116
+ it('should overwrite existing file', async () => {
117
+ const filePath = path.join(testDir, 'existing.txt');
118
+ await fs.writeFile(filePath, 'old content');
119
+
120
+ const newContent = 'new content';
121
+ await FileSystemUtils.writeFile(filePath, newContent);
122
+
123
+ const readContent = await fs.readFile(filePath, 'utf-8');
124
+ expect(readContent).toBe(newContent);
125
+ });
126
+ });
127
+
128
+ describe('readFile', () => {
129
+ it('should read file content', async () => {
130
+ const filePath = path.join(testDir, 'input.txt');
131
+ const content = 'Test content';
132
+ await fs.writeFile(filePath, content);
133
+
134
+ const readContent = await FileSystemUtils.readFile(filePath);
135
+ expect(readContent).toBe(content);
136
+ });
137
+
138
+ it('should throw for non-existing file', async () => {
139
+ const filePath = path.join(testDir, 'non-existent.txt');
140
+
141
+ await expect(FileSystemUtils.readFile(filePath)).rejects.toThrow();
142
+ });
143
+ });
144
+
145
+ describe('ensureWritePermissions', () => {
146
+ it('should return true for writable directory', async () => {
147
+ const hasPermission = await FileSystemUtils.ensureWritePermissions(testDir);
148
+ expect(hasPermission).toBe(true);
149
+ });
150
+
151
+ it('should return true for non-existing directory with writable parent', async () => {
152
+ const dirPath = path.join(testDir, 'new-dir');
153
+ const hasPermission = await FileSystemUtils.ensureWritePermissions(dirPath);
154
+ expect(hasPermission).toBe(true);
155
+ });
156
+
157
+ it('should handle deeply nested non-existing directories', async () => {
158
+ const dirPath = path.join(testDir, 'a', 'b', 'c', 'd');
159
+ const hasPermission = await FileSystemUtils.ensureWritePermissions(dirPath);
160
+ expect(hasPermission).toBe(true);
161
+ });
162
+ });
163
+
164
+ describe('joinPath', () => {
165
+ it('should join POSIX-style paths', () => {
166
+ const result = FileSystemUtils.joinPath(
167
+ '/tmp/project',
168
+ '.claude/commands/openspec/proposal.md'
169
+ );
170
+ expect(result).toBe('/tmp/project/.claude/commands/openspec/proposal.md');
171
+ });
172
+
173
+ it('should join Linux home directory paths', () => {
174
+ const result = FileSystemUtils.joinPath(
175
+ '/home/dev/workspace/openspec',
176
+ '.cursor/commands/install.md'
177
+ );
178
+ expect(result).toBe('/home/dev/workspace/openspec/.cursor/commands/install.md');
179
+ });
180
+
181
+ it('should join Windows drive-letter paths with backslashes', () => {
182
+ const result = FileSystemUtils.joinPath(
183
+ 'C:\\Users\\dev\\project',
184
+ '.claude/commands/openspec/proposal.md'
185
+ );
186
+ expect(result).toBe(
187
+ 'C:\\Users\\dev\\project\\.claude\\commands\\openspec\\proposal.md'
188
+ );
189
+ });
190
+
191
+ it('should join Windows paths that use forward slashes', () => {
192
+ const result = FileSystemUtils.joinPath(
193
+ 'D:/workspace/app',
194
+ '.cursor/commands/openspec-apply.md'
195
+ );
196
+ expect(result).toBe(
197
+ 'D:\\workspace\\app\\.cursor\\commands\\openspec-apply.md'
198
+ );
199
+ });
200
+
201
+ it('should join UNC-style Windows paths', () => {
202
+ const result = FileSystemUtils.joinPath(
203
+ '\\server\\share\\repo',
204
+ '.windsurf/workflows/openspec-archive.md'
205
+ );
206
+ expect(result).toBe(
207
+ '\\server\\share\\repo\\.windsurf\\workflows\\openspec-archive.md'
208
+ );
209
+ });
210
+ });
211
+ });
@@ -0,0 +1,287 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { FileSystemUtils } from '../../src/utils/file-system.js';
6
+
7
+ describe('FileSystemUtils.updateFileWithMarkers', () => {
8
+ let testDir: string;
9
+ const START_MARKER = '<!-- OPENSPEC:START -->';
10
+ const END_MARKER = '<!-- OPENSPEC:END -->';
11
+
12
+ beforeEach(async () => {
13
+ testDir = path.join(os.tmpdir(), `openspec-marker-test-${Date.now()}`);
14
+ await fs.mkdir(testDir, { recursive: true });
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await fs.rm(testDir, { recursive: true, force: true });
19
+ });
20
+
21
+ describe('new file creation', () => {
22
+ it('should create new file with markers and content', async () => {
23
+ const filePath = path.join(testDir, 'new-file.md');
24
+ const content = 'OpenSpec content';
25
+
26
+ await FileSystemUtils.updateFileWithMarkers(
27
+ filePath,
28
+ content,
29
+ START_MARKER,
30
+ END_MARKER
31
+ );
32
+
33
+ const result = await fs.readFile(filePath, 'utf-8');
34
+ expect(result).toBe(`${START_MARKER}\n${content}\n${END_MARKER}`);
35
+ });
36
+ });
37
+
38
+ describe('existing file without markers', () => {
39
+ it('should prepend markers and content to existing file', async () => {
40
+ const filePath = path.join(testDir, 'existing.md');
41
+ const existingContent = '# Existing Content\nUser content here';
42
+ await fs.writeFile(filePath, existingContent);
43
+
44
+ const newContent = 'OpenSpec content';
45
+ await FileSystemUtils.updateFileWithMarkers(
46
+ filePath,
47
+ newContent,
48
+ START_MARKER,
49
+ END_MARKER
50
+ );
51
+
52
+ const result = await fs.readFile(filePath, 'utf-8');
53
+ expect(result).toBe(
54
+ `${START_MARKER}\n${newContent}\n${END_MARKER}\n\n${existingContent}`
55
+ );
56
+ });
57
+ });
58
+
59
+ describe('existing file with markers', () => {
60
+ it('should replace content between markers', async () => {
61
+ const filePath = path.join(testDir, 'with-markers.md');
62
+ const beforeContent = '# Before\nSome content before';
63
+ const oldManagedContent = 'Old OpenSpec content';
64
+ const afterContent = '# After\nSome content after';
65
+
66
+ const existingFile = `${beforeContent}\n${START_MARKER}\n${oldManagedContent}\n${END_MARKER}\n${afterContent}`;
67
+ await fs.writeFile(filePath, existingFile);
68
+
69
+ const newContent = 'New OpenSpec content';
70
+ await FileSystemUtils.updateFileWithMarkers(
71
+ filePath,
72
+ newContent,
73
+ START_MARKER,
74
+ END_MARKER
75
+ );
76
+
77
+ const result = await fs.readFile(filePath, 'utf-8');
78
+ expect(result).toBe(
79
+ `${beforeContent}\n${START_MARKER}\n${newContent}\n${END_MARKER}\n${afterContent}`
80
+ );
81
+ });
82
+
83
+ it('should preserve content before and after markers', async () => {
84
+ const filePath = path.join(testDir, 'preserve.md');
85
+ const userContentBefore = '# User Content Before\nImportant user notes';
86
+ const userContentAfter = '## User Content After\nMore user notes';
87
+
88
+ const existingFile = `${userContentBefore}\n${START_MARKER}\nOld content\n${END_MARKER}\n${userContentAfter}`;
89
+ await fs.writeFile(filePath, existingFile);
90
+
91
+ const newContent = 'Updated content';
92
+ await FileSystemUtils.updateFileWithMarkers(
93
+ filePath,
94
+ newContent,
95
+ START_MARKER,
96
+ END_MARKER
97
+ );
98
+
99
+ const result = await fs.readFile(filePath, 'utf-8');
100
+ expect(result).toContain(userContentBefore);
101
+ expect(result).toContain(userContentAfter);
102
+ expect(result).toContain(newContent);
103
+ expect(result).not.toContain('Old content');
104
+ });
105
+
106
+ it('should handle markers at the beginning of file', async () => {
107
+ const filePath = path.join(testDir, 'markers-at-start.md');
108
+ const afterContent = 'User content after markers';
109
+
110
+ const existingFile = `${START_MARKER}\nOld content\n${END_MARKER}\n${afterContent}`;
111
+ await fs.writeFile(filePath, existingFile);
112
+
113
+ const newContent = 'New content';
114
+ await FileSystemUtils.updateFileWithMarkers(
115
+ filePath,
116
+ newContent,
117
+ START_MARKER,
118
+ END_MARKER
119
+ );
120
+
121
+ const result = await fs.readFile(filePath, 'utf-8');
122
+ expect(result).toBe(`${START_MARKER}\n${newContent}\n${END_MARKER}\n${afterContent}`);
123
+ });
124
+
125
+ it('should handle markers at the end of file', async () => {
126
+ const filePath = path.join(testDir, 'markers-at-end.md');
127
+ const beforeContent = 'User content before markers';
128
+
129
+ const existingFile = `${beforeContent}\n${START_MARKER}\nOld content\n${END_MARKER}`;
130
+ await fs.writeFile(filePath, existingFile);
131
+
132
+ const newContent = 'New content';
133
+ await FileSystemUtils.updateFileWithMarkers(
134
+ filePath,
135
+ newContent,
136
+ START_MARKER,
137
+ END_MARKER
138
+ );
139
+
140
+ const result = await fs.readFile(filePath, 'utf-8');
141
+ expect(result).toBe(`${beforeContent}\n${START_MARKER}\n${newContent}\n${END_MARKER}`);
142
+ });
143
+ });
144
+
145
+ describe('invalid marker states', () => {
146
+ it('should throw error if only start marker exists', async () => {
147
+ const filePath = path.join(testDir, 'invalid-start.md');
148
+ const existingFile = `Some content\n${START_MARKER}\nManaged content\nNo end marker`;
149
+ await fs.writeFile(filePath, existingFile);
150
+
151
+ await expect(
152
+ FileSystemUtils.updateFileWithMarkers(
153
+ filePath,
154
+ 'New content',
155
+ START_MARKER,
156
+ END_MARKER
157
+ )
158
+ ).rejects.toThrow(/Invalid marker state/);
159
+ });
160
+
161
+ it('should throw error if only end marker exists', async () => {
162
+ const filePath = path.join(testDir, 'invalid-end.md');
163
+ const existingFile = `Some content\nNo start marker\nManaged content\n${END_MARKER}`;
164
+ await fs.writeFile(filePath, existingFile);
165
+
166
+ await expect(
167
+ FileSystemUtils.updateFileWithMarkers(
168
+ filePath,
169
+ 'New content',
170
+ START_MARKER,
171
+ END_MARKER
172
+ )
173
+ ).rejects.toThrow(/Invalid marker state/);
174
+ });
175
+ });
176
+
177
+ describe('idempotency', () => {
178
+ it('should produce same result when called multiple times with same content', async () => {
179
+ const filePath = path.join(testDir, 'idempotent.md');
180
+ const content = 'Consistent content';
181
+
182
+ await FileSystemUtils.updateFileWithMarkers(
183
+ filePath,
184
+ content,
185
+ START_MARKER,
186
+ END_MARKER
187
+ );
188
+
189
+ const firstResult = await fs.readFile(filePath, 'utf-8');
190
+
191
+ await FileSystemUtils.updateFileWithMarkers(
192
+ filePath,
193
+ content,
194
+ START_MARKER,
195
+ END_MARKER
196
+ );
197
+
198
+ const secondResult = await fs.readFile(filePath, 'utf-8');
199
+ expect(secondResult).toBe(firstResult);
200
+ });
201
+ });
202
+
203
+ describe('edge cases', () => {
204
+ it('should handle empty content', async () => {
205
+ const filePath = path.join(testDir, 'empty-content.md');
206
+
207
+ await FileSystemUtils.updateFileWithMarkers(
208
+ filePath,
209
+ '',
210
+ START_MARKER,
211
+ END_MARKER
212
+ );
213
+
214
+ const result = await fs.readFile(filePath, 'utf-8');
215
+ expect(result).toBe(`${START_MARKER}\n\n${END_MARKER}`);
216
+ });
217
+
218
+ it('should handle content with special characters', async () => {
219
+ const filePath = path.join(testDir, 'special-chars.md');
220
+ const content = '# Special chars: ${}[]()<>|\\`*_~';
221
+
222
+ await FileSystemUtils.updateFileWithMarkers(
223
+ filePath,
224
+ content,
225
+ START_MARKER,
226
+ END_MARKER
227
+ );
228
+
229
+ const result = await fs.readFile(filePath, 'utf-8');
230
+ expect(result).toContain(content);
231
+ });
232
+
233
+ it('should handle multi-line content', async () => {
234
+ const filePath = path.join(testDir, 'multi-line.md');
235
+ const content = `Line 1
236
+ Line 2
237
+ Line 3
238
+
239
+ Line 5 with gap`;
240
+
241
+ await FileSystemUtils.updateFileWithMarkers(
242
+ filePath,
243
+ content,
244
+ START_MARKER,
245
+ END_MARKER
246
+ );
247
+
248
+ const result = await fs.readFile(filePath, 'utf-8');
249
+ expect(result).toContain(content);
250
+ });
251
+
252
+ it('should ignore inline mentions of markers when updating content', async () => {
253
+ const filePath = path.join(testDir, 'inline-mentions.md');
254
+ const existingFile = `Intro referencing markers like ${START_MARKER} and ${END_MARKER} inside text.
255
+
256
+ ${START_MARKER}
257
+ Original content
258
+ ${END_MARKER}
259
+ `;
260
+
261
+ await fs.writeFile(filePath, existingFile);
262
+
263
+ await FileSystemUtils.updateFileWithMarkers(
264
+ filePath,
265
+ 'Updated content',
266
+ START_MARKER,
267
+ END_MARKER
268
+ );
269
+
270
+ const firstResult = await fs.readFile(filePath, 'utf-8');
271
+ expect(firstResult).toContain('Intro referencing markers like');
272
+ expect(firstResult).toContain('Updated content');
273
+ expect(firstResult.match(new RegExp(START_MARKER, 'g'))?.length).toBe(2);
274
+ expect(firstResult.match(new RegExp(END_MARKER, 'g'))?.length).toBe(2);
275
+
276
+ await FileSystemUtils.updateFileWithMarkers(
277
+ filePath,
278
+ 'Updated content',
279
+ START_MARKER,
280
+ END_MARKER
281
+ );
282
+
283
+ const secondResult = await fs.readFile(filePath, 'utf-8');
284
+ expect(secondResult).toBe(firstResult);
285
+ });
286
+ });
287
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "NodeNext",
7
+ "rootDir": "./src",
8
+ "outDir": "./dist",
9
+ "esModuleInterop": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "strict": true,
12
+ "skipLibCheck": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true,
17
+ "allowSyntheticDefaultImports": true
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "test"]
21
+ }
@@ -0,0 +1,25 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ globalSetup: './vitest.setup.ts',
8
+ // Keep default pool settings; some tests rely on process.chdir,
9
+ // which is not supported in worker threads
10
+ include: ['test/**/*.test.ts'],
11
+ coverage: {
12
+ reporter: ['text', 'json', 'html'],
13
+ exclude: [
14
+ 'node_modules/',
15
+ 'dist/',
16
+ 'bin/',
17
+ '*.config.ts',
18
+ 'build.js',
19
+ 'test/**'
20
+ ]
21
+ },
22
+ testTimeout: 10000,
23
+ hookTimeout: 10000
24
+ }
25
+ });
@@ -0,0 +1,6 @@
1
+ import { ensureCliBuilt } from './test/helpers/run-cli.js';
2
+
3
+ // Ensure the CLI bundle exists before tests execute
4
+ export async function setup() {
5
+ await ensureCliBuilt();
6
+ }