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.
- package/.gitmodules +3 -0
- package/CHAT.md +3 -0
- package/package.json +12 -0
- package/references/openspec/.changeset/README.md +6 -0
- package/references/openspec/.changeset/config.json +12 -0
- package/references/openspec/.coderabbit.yaml +11 -0
- package/references/openspec/.devcontainer/README.md +92 -0
- package/references/openspec/.devcontainer/devcontainer.json +68 -0
- package/references/openspec/.github/CODEOWNERS +2 -0
- package/references/openspec/.github/workflows/ci.yml +222 -0
- package/references/openspec/.github/workflows/release-prepare.yml +50 -0
- package/references/openspec/AGENTS.md +18 -0
- package/references/openspec/CHANGELOG.md +205 -0
- package/references/openspec/LICENSE +22 -0
- package/references/openspec/README.md +374 -0
- package/references/openspec/assets/openspec_dashboard.png +0 -0
- package/references/openspec/assets/openspec_pixel_dark.svg +89 -0
- package/references/openspec/assets/openspec_pixel_light.svg +89 -0
- package/references/openspec/bin/openspec.js +3 -0
- package/references/openspec/build.js +31 -0
- package/references/openspec/openspec/AGENTS.md +454 -0
- package/references/openspec/openspec/changes/IMPLEMENTATION_ORDER.md +68 -0
- package/references/openspec/openspec/changes/add-antigravity-support/proposal.md +11 -0
- package/references/openspec/openspec/changes/add-antigravity-support/specs/cli-init/spec.md +9 -0
- package/references/openspec/openspec/changes/add-antigravity-support/specs/cli-update/spec.md +8 -0
- package/references/openspec/openspec/changes/add-antigravity-support/tasks.md +12 -0
- package/references/openspec/openspec/changes/add-scaffold-command/proposal.md +11 -0
- package/references/openspec/openspec/changes/add-scaffold-command/specs/cli-scaffold/spec.md +36 -0
- package/references/openspec/openspec/changes/add-scaffold-command/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/design.md +86 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/proposal.md +29 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/specs/cli-update/spec.md +59 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/tasks.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/proposal.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/specs/cli-list/spec.md +69 -0
- package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/tasks.md +26 -0
- package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/design.md +64 -0
- package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/proposal.md +18 -0
- package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/tasks.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/design.md +104 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/proposal.md +30 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/specs/cli-init/spec.md +148 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/tasks.md +38 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/proposal.md +24 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/specs/openspec-conventions/spec.md +120 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/tasks.md +38 -0
- package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/specs/openspec-docs/README.md +472 -0
- package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/tasks.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/proposal.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/specs/cli-archive/spec.md +111 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/tasks.md +44 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/specs/cli-diff/spec.md +77 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/tasks.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/design.md +56 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-change/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-list/spec.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/tasks.md +34 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/proposal.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-change/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-show/spec.md +83 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-spec/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/tasks.md +142 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/specs/cli-archive/spec.md +191 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/tasks.md +57 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/design.md +45 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/specs/cli-spec/spec.md +43 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/tasks.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/design.md +104 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/proposal.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-archive/spec.md +18 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-diff/spec.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/tasks.md +59 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/proposal.md +93 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-archive/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-diff/spec.md +45 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/openspec-conventions/spec.md +101 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/tasks.md +55 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/design.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/proposal.md +67 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/cli-list/spec.md +57 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/openspec-conventions/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/tasks.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/proposal.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-change/spec.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-spec/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-validate/spec.md +149 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/tasks.md +81 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/proposal.md +40 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/specs/cli-update/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/tasks.md +21 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/specs/cli-validate/spec.md +55 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/tasks.md +21 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/proposal.md +36 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/specs/openspec-conventions/spec.md +192 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/tasks.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/proposal.md +38 -0
- package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/specs/cli-view/spec.md +109 -0
- package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/tasks.md +47 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/proposal.md +28 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-init/spec.md +71 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-update/spec.md +41 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/tasks.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/proposal.md +35 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/specs/cli-init/spec.md +45 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/tasks.md +16 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/proposal.md +119 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-init/spec.md +21 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-update/spec.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/tasks.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/tasks.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-deterministic-tests/proposal.md +78 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-deterministic-tests/tasks.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/specs/cli-init/spec.md +92 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-remove-diff-command/proposal.md +81 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-remove-diff-command/tasks.md +37 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/specs/cli-view/spec.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/tasks.md +8 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/proposal.md +29 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-init/spec.md +40 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-update/spec.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/openspec-conventions/spec.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/tasks.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/design.md +130 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/proposal.md +117 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/tasks.md +69 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/specs/cli-validate/spec.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/tasks.md +11 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-init/spec.md +56 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-update/spec.md +41 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/tasks.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-init/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-update/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/tasks.md +30 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-init/spec.md +43 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-update/spec.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/tasks.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/specs/cli-init/spec.md +39 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/tasks.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-init/spec.md +42 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-update/spec.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/tasks.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/specs/cli-validate/spec.md +39 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/specs/docs-agent-instructions/spec.md +33 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/tasks.md +11 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-slim-root-agents-file/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-slim-root-agents-file/tasks.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/proposal.md +14 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/specs/cli-init/spec.md +10 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/tasks.md +8 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/proposal.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-init/spec.md +32 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-update/spec.md +10 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/tasks.md +11 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-release-automation/proposal.md +49 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-release-automation/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/specs/cli-update/spec.md +32 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/tasks.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/proposal.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/specs/cli-init/spec.md +97 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/tasks.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/specs/cli-init/spec.md +67 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/tasks.md +7 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-init/spec.md +54 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-update/spec.md +54 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/tasks.md +11 -0
- package/references/openspec/openspec/changes/fix-cline-workflows-implementation/proposal.md +13 -0
- package/references/openspec/openspec/changes/fix-cline-workflows-implementation/specs/cli-init/spec.md +11 -0
- package/references/openspec/openspec/changes/fix-cline-workflows-implementation/tasks.md +13 -0
- package/references/openspec/openspec/changes/make-validation-scope-aware/proposal.md +12 -0
- package/references/openspec/openspec/changes/make-validation-scope-aware/specs/cli-validate/spec.md +25 -0
- package/references/openspec/openspec/changes/make-validation-scope-aware/tasks.md +16 -0
- package/references/openspec/openspec/project.md +53 -0
- package/references/openspec/openspec/specs/cli-archive/spec.md +210 -0
- package/references/openspec/openspec/specs/cli-change/spec.md +91 -0
- package/references/openspec/openspec/specs/cli-init/spec.md +311 -0
- package/references/openspec/openspec/specs/cli-list/spec.md +103 -0
- package/references/openspec/openspec/specs/cli-show/spec.md +85 -0
- package/references/openspec/openspec/specs/cli-spec/spec.md +87 -0
- package/references/openspec/openspec/specs/cli-update/spec.md +190 -0
- package/references/openspec/openspec/specs/cli-validate/spec.md +218 -0
- package/references/openspec/openspec/specs/cli-view/spec.md +105 -0
- package/references/openspec/openspec/specs/docs-agent-instructions/spec.md +38 -0
- package/references/openspec/openspec/specs/openspec-conventions/spec.md +474 -0
- package/references/openspec/openspec-parallel-merge-plan.md +98 -0
- package/references/openspec/package.json +73 -0
- package/references/openspec/pnpm-lock.yaml +2324 -0
- package/references/openspec/scripts/pack-version-check.mjs +111 -0
- package/references/openspec/src/cli/index.ts +253 -0
- package/references/openspec/src/commands/change.ts +291 -0
- package/references/openspec/src/commands/show.ts +139 -0
- package/references/openspec/src/commands/spec.ts +250 -0
- package/references/openspec/src/commands/validate.ts +305 -0
- package/references/openspec/src/core/archive.ts +606 -0
- package/references/openspec/src/core/config.ts +41 -0
- package/references/openspec/src/core/configurators/agents.ts +23 -0
- package/references/openspec/src/core/configurators/base.ts +6 -0
- package/references/openspec/src/core/configurators/claude.ts +23 -0
- package/references/openspec/src/core/configurators/cline.ts +23 -0
- package/references/openspec/src/core/configurators/codebuddy.ts +24 -0
- package/references/openspec/src/core/configurators/costrict.ts +23 -0
- package/references/openspec/src/core/configurators/iflow.ts +23 -0
- package/references/openspec/src/core/configurators/qoder.ts +53 -0
- package/references/openspec/src/core/configurators/qwen.ts +47 -0
- package/references/openspec/src/core/configurators/registry.ts +49 -0
- package/references/openspec/src/core/configurators/slash/amazon-q.ts +51 -0
- package/references/openspec/src/core/configurators/slash/antigravity.ts +28 -0
- package/references/openspec/src/core/configurators/slash/auggie.ts +37 -0
- package/references/openspec/src/core/configurators/slash/base.ts +95 -0
- package/references/openspec/src/core/configurators/slash/claude.ts +42 -0
- package/references/openspec/src/core/configurators/slash/cline.ts +27 -0
- package/references/openspec/src/core/configurators/slash/codebuddy.ts +43 -0
- package/references/openspec/src/core/configurators/slash/codex.ts +126 -0
- package/references/openspec/src/core/configurators/slash/costrict.ts +36 -0
- package/references/openspec/src/core/configurators/slash/crush.ts +42 -0
- package/references/openspec/src/core/configurators/slash/cursor.ts +42 -0
- package/references/openspec/src/core/configurators/slash/factory.ts +41 -0
- package/references/openspec/src/core/configurators/slash/gemini.ts +27 -0
- package/references/openspec/src/core/configurators/slash/github-copilot.ts +39 -0
- package/references/openspec/src/core/configurators/slash/iflow.ts +42 -0
- package/references/openspec/src/core/configurators/slash/kilocode.ts +21 -0
- package/references/openspec/src/core/configurators/slash/opencode.ts +83 -0
- package/references/openspec/src/core/configurators/slash/qoder.ts +84 -0
- package/references/openspec/src/core/configurators/slash/qwen.ts +55 -0
- package/references/openspec/src/core/configurators/slash/registry.ts +81 -0
- package/references/openspec/src/core/configurators/slash/roocode.ts +27 -0
- package/references/openspec/src/core/configurators/slash/toml-base.ts +66 -0
- package/references/openspec/src/core/configurators/slash/windsurf.ts +27 -0
- package/references/openspec/src/core/converters/json-converter.ts +61 -0
- package/references/openspec/src/core/index.ts +2 -0
- package/references/openspec/src/core/init.ts +986 -0
- package/references/openspec/src/core/list.ts +104 -0
- package/references/openspec/src/core/parsers/change-parser.ts +234 -0
- package/references/openspec/src/core/parsers/markdown-parser.ts +237 -0
- package/references/openspec/src/core/parsers/requirement-blocks.ts +234 -0
- package/references/openspec/src/core/schemas/base.schema.ts +20 -0
- package/references/openspec/src/core/schemas/change.schema.ts +42 -0
- package/references/openspec/src/core/schemas/index.ts +20 -0
- package/references/openspec/src/core/schemas/spec.schema.ts +17 -0
- package/references/openspec/src/core/styles/palette.ts +8 -0
- package/references/openspec/src/core/templates/agents-root-stub.ts +16 -0
- package/references/openspec/src/core/templates/agents-template.ts +457 -0
- package/references/openspec/src/core/templates/claude-template.ts +1 -0
- package/references/openspec/src/core/templates/cline-template.ts +1 -0
- package/references/openspec/src/core/templates/costrict-template.ts +1 -0
- package/references/openspec/src/core/templates/index.ts +50 -0
- package/references/openspec/src/core/templates/project-template.ts +38 -0
- package/references/openspec/src/core/templates/slash-command-templates.ts +60 -0
- package/references/openspec/src/core/update.ts +129 -0
- package/references/openspec/src/core/validation/constants.ts +48 -0
- package/references/openspec/src/core/validation/types.ts +19 -0
- package/references/openspec/src/core/validation/validator.ts +448 -0
- package/references/openspec/src/core/view.ts +189 -0
- package/references/openspec/src/index.ts +2 -0
- package/references/openspec/src/utils/file-system.ts +187 -0
- package/references/openspec/src/utils/index.ts +2 -0
- package/references/openspec/src/utils/interactive.ts +7 -0
- package/references/openspec/src/utils/item-discovery.ts +45 -0
- package/references/openspec/src/utils/match.ts +26 -0
- package/references/openspec/src/utils/task-progress.ts +43 -0
- package/references/openspec/test/cli-e2e/basic.test.ts +156 -0
- package/references/openspec/test/commands/change.interactive-show.test.ts +45 -0
- package/references/openspec/test/commands/change.interactive-validate.test.ts +48 -0
- package/references/openspec/test/commands/show.test.ts +123 -0
- package/references/openspec/test/commands/spec.interactive-show.test.ts +44 -0
- package/references/openspec/test/commands/spec.interactive-validate.test.ts +44 -0
- package/references/openspec/test/commands/spec.test.ts +324 -0
- package/references/openspec/test/commands/validate.enriched-output.test.ts +49 -0
- package/references/openspec/test/commands/validate.test.ts +133 -0
- package/references/openspec/test/core/archive.test.ts +680 -0
- package/references/openspec/test/core/commands/change-command.list.test.ts +76 -0
- package/references/openspec/test/core/commands/change-command.show-validate.test.ts +111 -0
- package/references/openspec/test/core/converters/json-converter.test.ts +184 -0
- package/references/openspec/test/core/init.test.ts +1710 -0
- package/references/openspec/test/core/list.test.ts +165 -0
- package/references/openspec/test/core/parsers/change-parser.test.ts +52 -0
- package/references/openspec/test/core/parsers/markdown-parser.test.ts +291 -0
- package/references/openspec/test/core/update.test.ts +1642 -0
- package/references/openspec/test/core/validation.enriched-messages.test.ts +74 -0
- package/references/openspec/test/core/validation.test.ts +489 -0
- package/references/openspec/test/core/view.test.ts +79 -0
- package/references/openspec/test/fixtures/tmp-init/openspec/changes/c1/proposal.md +7 -0
- package/references/openspec/test/fixtures/tmp-init/openspec/changes/c1/specs/alpha/spec.md +8 -0
- package/references/openspec/test/fixtures/tmp-init/openspec/specs/alpha/spec.md +12 -0
- package/references/openspec/test/helpers/run-cli.ts +139 -0
- package/references/openspec/test/utils/file-system.test.ts +211 -0
- package/references/openspec/test/utils/marker-updates.test.ts +287 -0
- package/references/openspec/tsconfig.json +21 -0
- package/references/openspec/vitest.config.ts +25 -0
- 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
|
+
});
|