sauron-cli 1.1.3 → 1.3.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/.agents/rules/memory.md +156 -0
- package/.agents/skills/wiki/SKILL.md +172 -0
- package/.aider.instructions.md +152 -0
- package/.cursor/rules/sauron-memory.mdc +157 -0
- package/.genesis/prompts/prompt-onboarding-inteligente.md +45 -0
- package/.genesis/prompts/prompt-sauron-vs-openspec-arquitetura.md +106 -0
- package/.github/workflows/ci.yml +27 -0
- package/.sauron/.manifest.json +2 -1
- package/.sauron/wiki/history/implementacao-scanner-inteligente.md +29 -0
- package/.sauron/wiki/history/reestruturacao-arquitetural-core.md +29 -0
- package/.sauron/wiki/history/resiliencia-caminho-templates.md +29 -0
- package/.sauron/wiki/standards/typescript.rules.md +21 -0
- package/.sauron/wiki/summary.json +81 -0
- package/.windsurfrules +160 -0
- package/55ea973d-e255-4f08-a313-a5d68243bd2d.png +0 -0
- package/Evolu/303/247/303/243o Arquitetural do Sauron CLI.md" +97 -0
- package/OpenSpec-main/.actrc +1 -0
- package/OpenSpec-main/.changeset/README.md +97 -0
- package/OpenSpec-main/.changeset/config.json +15 -0
- package/OpenSpec-main/.coderabbit.yaml +11 -0
- package/OpenSpec-main/.devcontainer/README.md +92 -0
- package/OpenSpec-main/.devcontainer/devcontainer.json +68 -0
- package/OpenSpec-main/.github/CODEOWNERS +2 -0
- package/OpenSpec-main/.github/workflows/README.md +20 -0
- package/OpenSpec-main/.github/workflows/ci.yml +346 -0
- package/OpenSpec-main/.github/workflows/release-prepare.yml +60 -0
- package/OpenSpec-main/CHANGELOG.md +594 -0
- package/OpenSpec-main/LICENSE +22 -0
- package/OpenSpec-main/MAINTAINERS.md +17 -0
- package/OpenSpec-main/README.md +206 -0
- package/OpenSpec-main/README_OLD.md +475 -0
- package/OpenSpec-main/assets/openspec_bg.png +0 -0
- package/OpenSpec-main/assets/openspec_dashboard.png +0 -0
- package/OpenSpec-main/assets/openspec_pixel_dark.svg +89 -0
- package/OpenSpec-main/assets/openspec_pixel_light.svg +89 -0
- package/OpenSpec-main/bin/openspec.js +5 -0
- package/OpenSpec-main/build.js +31 -0
- package/OpenSpec-main/eslint.config.js +42 -0
- package/OpenSpec-main/flake.lock +27 -0
- package/OpenSpec-main/flake.nix +114 -0
- package/OpenSpec-main/openspec/changes/IMPLEMENTATION_ORDER.md +68 -0
- package/OpenSpec-main/openspec/changes/add-artifact-regeneration-support/proposal.md +136 -0
- package/OpenSpec-main/openspec/changes/add-change-stacking-awareness/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/add-change-stacking-awareness/proposal.md +93 -0
- package/OpenSpec-main/openspec/changes/add-change-stacking-awareness/specs/change-creation/spec.md +15 -0
- package/OpenSpec-main/openspec/changes/add-change-stacking-awareness/specs/change-stacking-workflow/spec.md +65 -0
- package/OpenSpec-main/openspec/changes/add-change-stacking-awareness/specs/cli-change/spec.md +27 -0
- package/OpenSpec-main/openspec/changes/add-change-stacking-awareness/specs/openspec-conventions/spec.md +29 -0
- package/OpenSpec-main/openspec/changes/add-change-stacking-awareness/tasks.md +39 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/design.md +161 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/proposal.md +101 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/specs/ai-tool-paths/spec.md +35 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/specs/cli-config/spec.md +21 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/specs/cli-init/spec.md +28 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/specs/cli-update/spec.md +34 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/specs/command-generation/spec.md +22 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/specs/global-config/spec.md +24 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/specs/installation-scope/spec.md +71 -0
- package/OpenSpec-main/openspec/changes/add-global-install-scope/tasks.md +61 -0
- package/OpenSpec-main/openspec/changes/add-qa-smoke-harness/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/add-qa-smoke-harness/proposal.md +45 -0
- package/OpenSpec-main/openspec/changes/add-qa-smoke-harness/specs/developer-qa-workflow/spec.md +49 -0
- package/OpenSpec-main/openspec/changes/add-tool-command-surface-capabilities/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/add-tool-command-surface-capabilities/proposal.md +111 -0
- package/OpenSpec-main/openspec/changes/add-tool-command-surface-capabilities/specs/cli-init/spec.md +121 -0
- package/OpenSpec-main/openspec/changes/add-tool-command-surface-capabilities/specs/cli-update/spec.md +48 -0
- package/OpenSpec-main/openspec/changes/add-tool-command-surface-capabilities/tasks.md +53 -0
- package/OpenSpec-main/openspec/changes/archive/2025-01-11-add-update-command/design.md +86 -0
- package/OpenSpec-main/openspec/changes/archive/2025-01-11-add-update-command/proposal.md +29 -0
- package/OpenSpec-main/openspec/changes/archive/2025-01-11-add-update-command/specs/cli-update/spec.md +59 -0
- package/OpenSpec-main/openspec/changes/archive/2025-01-11-add-update-command/tasks.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-01-13-add-list-command/proposal.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-01-13-add-list-command/specs/cli-list/spec.md +69 -0
- package/OpenSpec-main/openspec/changes/archive/2025-01-13-add-list-command/tasks.md +26 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-05-initialize-typescript-project/design.md +64 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-05-initialize-typescript-project/proposal.md +18 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-05-initialize-typescript-project/tasks.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-06-add-init-command/design.md +104 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-06-add-init-command/proposal.md +30 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-06-add-init-command/specs/cli-init/spec.md +148 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-06-add-init-command/tasks.md +38 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-06-adopt-future-state-storage/proposal.md +24 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-06-adopt-future-state-storage/specs/openspec-conventions/spec.md +120 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-06-adopt-future-state-storage/tasks.md +38 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-11-add-complexity-guidelines/proposal.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-11-add-complexity-guidelines/specs/openspec-docs/README.md +472 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-11-add-complexity-guidelines/tasks.md +9 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-13-add-archive-command/proposal.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-13-add-archive-command/specs/cli-archive/spec.md +111 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-13-add-archive-command/tasks.md +44 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-13-add-diff-command/proposal.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-13-add-diff-command/specs/cli-diff/spec.md +77 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-13-add-diff-command/tasks.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-change-commands/design.md +56 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-change-commands/proposal.md +17 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-change/spec.md +48 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-list/spec.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-change-commands/tasks.md +34 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-interactive-show-command/proposal.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-change/spec.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-show/spec.md +83 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-spec/spec.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-interactive-show-command/tasks.md +142 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/proposal.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/specs/cli-archive/spec.md +191 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/tasks.md +57 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-spec-commands/design.md +45 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-spec-commands/proposal.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-spec-commands/specs/cli-spec/spec.md +43 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-spec-commands/tasks.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-zod-validation/design.md +104 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-zod-validation/proposal.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-archive/spec.md +18 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-diff/spec.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-add-zod-validation/tasks.md +59 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/proposal.md +93 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-archive/spec.md +48 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-diff/spec.md +45 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/openspec-conventions/spec.md +101 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/tasks.md +55 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/design.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/proposal.md +67 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/cli-list/spec.md +57 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/openspec-conventions/spec.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/tasks.md +27 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/proposal.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-change/spec.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-spec/spec.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-validate/spec.md +149 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/tasks.md +81 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-fix-update-tool-selection/proposal.md +40 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-fix-update-tool-selection/specs/cli-update/spec.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-fix-update-tool-selection/tasks.md +21 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-improve-validate-error-messages/proposal.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-improve-validate-error-messages/specs/cli-validate/spec.md +55 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-improve-validate-error-messages/tasks.md +21 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-structured-spec-format/proposal.md +36 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-structured-spec-format/specs/openspec-conventions/spec.md +192 -0
- package/OpenSpec-main/openspec/changes/archive/2025-08-19-structured-spec-format/tasks.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-12-add-view-dashboard-command/proposal.md +38 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-12-add-view-dashboard-command/specs/cli-view/spec.md +109 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-12-add-view-dashboard-command/tasks.md +47 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-agents-md-config/proposal.md +28 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-init/spec.md +71 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-update/spec.md +41 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-agents-md-config/tasks.md +17 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-multi-agent-init/proposal.md +35 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-multi-agent-init/specs/cli-init/spec.md +45 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-multi-agent-init/tasks.md +16 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-slash-command-support/proposal.md +119 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-init/spec.md +21 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-update/spec.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-add-slash-command-support/tasks.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/proposal.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/tasks.md +9 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-improve-deterministic-tests/proposal.md +78 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-improve-deterministic-tests/tasks.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-improve-init-onboarding/proposal.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-improve-init-onboarding/specs/cli-init/spec.md +92 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-improve-init-onboarding/tasks.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-remove-diff-command/proposal.md +81 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-remove-diff-command/tasks.md +37 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/proposal.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/specs/cli-view/spec.md +9 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/tasks.md +8 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-file-name/proposal.md +29 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-init/spec.md +40 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-update/spec.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/openspec-conventions/spec.md +27 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-file-name/tasks.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-instructions/design.md +130 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-instructions/proposal.md +117 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-agent-instructions/tasks.md +69 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/proposal.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/specs/cli-validate/spec.md +9 -0
- package/OpenSpec-main/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/tasks.md +11 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/proposal.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-init/spec.md +56 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-update/spec.md +41 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/tasks.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/proposal.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-init/spec.md +48 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-update/spec.md +48 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/tasks.md +30 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-kilocode-workflows/proposal.md +17 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-init/spec.md +43 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-update/spec.md +27 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-kilocode-workflows/tasks.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/proposal.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/specs/cli-init/spec.md +39 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/tasks.md +17 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-windsurf-workflows/proposal.md +17 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-init/spec.md +42 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-update/spec.md +27 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-add-windsurf-workflows/tasks.md +17 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/proposal.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/specs/cli-validate/spec.md +39 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/tasks.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/proposal.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/specs/docs-agent-instructions/spec.md +33 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/tasks.md +11 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-slim-root-agents-file/proposal.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-slim-root-agents-file/tasks.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/proposal.md +14 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/specs/cli-init/spec.md +10 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/tasks.md +8 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/proposal.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-init/spec.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-update/spec.md +10 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/tasks.md +11 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-release-automation/proposal.md +49 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-14-update-release-automation/tasks.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-archive-command-arguments/proposal.md +17 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-archive-command-arguments/specs/cli-update/spec.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-archive-command-arguments/tasks.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-cline-support/proposal.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-cline-support/specs/cli-init/spec.md +97 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-cline-support/tasks.md +19 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-crush-support/proposal.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-crush-support/specs/cli-init/spec.md +67 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-crush-support/tasks.md +7 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-factory-slash-commands/proposal.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-init/spec.md +54 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-update/spec.md +54 -0
- package/OpenSpec-main/openspec/changes/archive/2025-10-22-add-factory-slash-commands/tasks.md +11 -0
- package/OpenSpec-main/openspec/changes/archive/2025-11-06-add-shell-completions/design.md +525 -0
- package/OpenSpec-main/openspec/changes/archive/2025-11-06-add-shell-completions/proposal.md +29 -0
- package/OpenSpec-main/openspec/changes/archive/2025-11-06-add-shell-completions/specs/cli-completion/spec.md +300 -0
- package/OpenSpec-main/openspec/changes/archive/2025-11-06-add-shell-completions/tasks.md +81 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-20-add-global-config-dir/design.md +105 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-20-add-global-config-dir/proposal.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-20-add-global-config-dir/specs/global-config/spec.md +76 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-20-add-global-config-dir/tasks.md +26 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-21-add-config-command/design.md +89 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-21-add-config-command/proposal.md +60 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-21-add-config-command/specs/cli-config/spec.md +213 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-21-add-config-command/tasks.md +28 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-23-extend-shell-completions/proposal.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-23-extend-shell-completions/specs/cli-completion/spec.md +328 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-23-extend-shell-completions/tasks.md +49 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-24-add-artifact-graph-core/design.md +197 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-24-add-artifact-graph-core/proposal.md +18 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-24-add-artifact-graph-core/specs/artifact-graph/spec.md +103 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-24-add-artifact-graph-core/tasks.md +61 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-25-add-change-manager/design.md +74 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-25-add-change-manager/proposal.md +45 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-25-add-change-manager/specs/change-creation/spec.md +63 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-25-add-change-manager/tasks.md +30 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-artifact-workflow-cli/design.md +112 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-artifact-workflow-cli/proposal.md +33 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-artifact-workflow-cli/specs/cli-artifact-workflow/spec.md +153 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-artifact-workflow-cli/tasks.md +48 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-instruction-loader/design.md +149 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-instruction-loader/proposal.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-instruction-loader/specs/instruction-loader/spec.md +70 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-add-instruction-loader/tasks.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-restructure-schema-directories/design.md +129 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-restructure-schema-directories/proposal.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-restructure-schema-directories/specs/artifact-graph/spec.md +49 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-28-restructure-schema-directories/tasks.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-29-unify-change-state-model/design.md +151 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-29-unify-change-state-model/proposal.md +101 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-29-unify-change-state-model/specs/cli-artifact-workflow/spec.md +109 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-29-unify-change-state-model/specs/cli-view/spec.md +60 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-29-unify-change-state-model/tasks.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-add-antigravity-support/proposal.md +11 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-add-antigravity-support/specs/cli-init/spec.md +105 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-add-antigravity-support/specs/cli-update/spec.md +92 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-add-antigravity-support/tasks.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-fix-cline-workflows-implementation/proposal.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-fix-cline-workflows-implementation/specs/cli-init/spec.md +105 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-fix-cline-workflows-implementation/specs/cli-update/spec.md +92 -0
- package/OpenSpec-main/openspec/changes/archive/2025-12-30-fix-cline-workflows-implementation/tasks.md +13 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-agent-schema-selection/proposal.md +26 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-agent-schema-selection/tasks.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-per-change-schema-metadata/design.md +147 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-per-change-schema-metadata/proposal.md +29 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-per-change-schema-metadata/specs/cli-artifact-workflow/spec.md +98 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-per-change-schema-metadata/tasks.md +29 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-specs-apply-command/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-specs-apply-command/design.md +77 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-specs-apply-command/proposal.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-specs-apply-command/specs/specs-sync-skill/spec.md +67 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-add-specs-apply-command/tasks.md +40 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-make-apply-instructions-schema-aware/proposal.md +138 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-make-apply-instructions-schema-aware/specs/cli-artifact-workflow/spec.md +60 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-make-apply-instructions-schema-aware/tasks.md +35 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-opsx-archive-command/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-opsx-archive-command/design.md +84 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-opsx-archive-command/proposal.md +28 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-opsx-archive-command/specs/opsx-archive-skill/spec.md +122 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-06-opsx-archive-command/tasks.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-07-add-nix-flake-support/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-07-add-nix-flake-support/design.md +94 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-07-add-nix-flake-support/proposal.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-07-add-nix-flake-support/specs/nix-flake-support/spec.md +79 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-07-add-nix-flake-support/tasks.md +65 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-flake-update-script/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-flake-update-script/design.md +117 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-flake-update-script/proposal.md +23 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-flake-update-script/specs/flake-update-script/spec.md +93 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-flake-update-script/tasks.md +55 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-posthog-analytics/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-posthog-analytics/design.md +175 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-posthog-analytics/proposal.md +37 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-posthog-analytics/specs/global-config/spec.md +21 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-posthog-analytics/specs/telemetry/spec.md +116 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-add-posthog-analytics/tasks.md +47 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-fix-codebuddy-frontmatter-fields/proposal.md +16 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-fix-codebuddy-frontmatter-fields/specs/cli-init/spec.md +75 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-fix-codebuddy-frontmatter-fields/specs/cli-update/spec.md +56 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-09-fix-codebuddy-frontmatter-fields/tasks.md +6 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-15-add-nix-ci-validation/design.md +206 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-15-add-nix-ci-validation/proposal.md +21 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-15-add-nix-ci-validation/specs/ci-nix-validation/spec.md +104 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-15-add-nix-ci-validation/tasks.md +49 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-30-opencode-command-references/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-30-opencode-command-references/README.md +3 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-30-opencode-command-references/design.md +70 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-30-opencode-command-references/proposal.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-30-opencode-command-references/specs/no-changes.md +9 -0
- package/OpenSpec-main/openspec/changes/archive/2026-01-30-opencode-command-references/tasks.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-feedback-command/proposal.md +20 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-feedback-command/specs/cli-feedback/spec.md +188 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-feedback-command/tasks.md +30 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-opsx-onboard-skill/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-opsx-onboard-skill/design.md +115 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-opsx-onboard-skill/proposal.md +27 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-opsx-onboard-skill/specs/opsx-onboard-skill/spec.md +162 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-opsx-onboard-skill/tasks.md +21 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-verify-skill/design.md +96 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-verify-skill/proposal.md +48 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-verify-skill/specs/opsx-verify-skill/spec.md +190 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-add-verify-skill/tasks.md +15 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-merge-init-experimental/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-merge-init-experimental/design.md +193 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-merge-init-experimental/proposal.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-merge-init-experimental/specs/cli-init/spec.md +176 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-merge-init-experimental/specs/legacy-cleanup/spec.md +158 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-merge-init-experimental/tasks.md +67 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-multi-provider-skill-generation/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-multi-provider-skill-generation/design.md +144 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-multi-provider-skill-generation/proposal.md +36 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-multi-provider-skill-generation/specs/ai-tool-paths/spec.md +63 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-multi-provider-skill-generation/specs/cli-artifact-workflow/spec.md +60 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-multi-provider-skill-generation/specs/command-generation/spec.md +98 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-multi-provider-skill-generation/tasks.md +55 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/design.md +665 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/proposal.md +774 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/specs/config-loading/spec.md +119 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/specs/context-injection/spec.md +51 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/specs/rules-injection/spec.md +99 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/specs/schema-resolution/spec.md +83 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-config/tasks.md +72 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-local-schemas/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-local-schemas/design.md +117 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-local-schemas/proposal.md +167 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-local-schemas/specs/schema-resolution/spec.md +88 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-project-local-schemas/tasks.md +28 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/design.md +113 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/proposal.md +55 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/specs/schema-fork-command/spec.md +66 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/specs/schema-init-command/spec.md +71 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/specs/schema-validate-command/spec.md +86 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/specs/schema-which-command/spec.md +65 -0
- package/OpenSpec-main/openspec/changes/archive/2026-02-17-schema-management-cli/tasks.md +67 -0
- package/OpenSpec-main/openspec/changes/archive/2026-04-23-add-kimi-cli-skills-only-support/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/archive/2026-04-23-add-kimi-cli-skills-only-support/README.md +3 -0
- package/OpenSpec-main/openspec/changes/archive/2026-04-23-add-kimi-cli-skills-only-support/design.md +85 -0
- package/OpenSpec-main/openspec/changes/archive/2026-04-23-add-kimi-cli-skills-only-support/proposal.md +38 -0
- package/OpenSpec-main/openspec/changes/archive/2026-04-23-add-kimi-cli-skills-only-support/specs/ai-tool-paths/spec.md +12 -0
- package/OpenSpec-main/openspec/changes/archive/2026-04-23-add-kimi-cli-skills-only-support/specs/cli-init/spec.md +37 -0
- package/OpenSpec-main/openspec/changes/archive/2026-04-23-add-kimi-cli-skills-only-support/tasks.md +22 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-04-workspace-foundation/design.md +208 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-04-workspace-foundation/proposal.md +142 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-04-workspace-foundation/specs/openspec-conventions/spec.md +29 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-04-workspace-foundation/specs/workspace-foundation/spec.md +199 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-04-workspace-foundation/tasks.md +56 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-create-and-register-repos/design.md +356 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-create-and-register-repos/proposal.md +128 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-create-and-register-repos/specs/cli-artifact-workflow/spec.md +24 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-create-and-register-repos/specs/workspace-foundation/spec.md +35 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-create-and-register-repos/specs/workspace-links/spec.md +356 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-create-and-register-repos/tasks.md +121 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-open-agent-context/design.md +266 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-open-agent-context/proposal.md +65 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-open-agent-context/specs/workspace-foundation/spec.md +76 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-open-agent-context/specs/workspace-open/spec.md +199 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-06-workspace-open-agent-context/tasks.md +89 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/design.md +242 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/proposal.md +78 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/artifact-graph/spec.md +36 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/change-creation/spec.md +42 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/cli-artifact-workflow/spec.md +100 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/cli-config/spec.md +55 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/cli-update/spec.md +21 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/openspec-conventions/spec.md +32 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/schema-resolution/spec.md +25 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/workspace-change-planning/spec.md +67 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/specs/workspace-links/spec.md +163 -0
- package/OpenSpec-main/openspec/changes/archive/2026-05-14-workspace-change-planning/tasks.md +133 -0
- package/OpenSpec-main/openspec/changes/fix-opencode-commands-directory/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/fix-opencode-commands-directory/design.md +48 -0
- package/OpenSpec-main/openspec/changes/fix-opencode-commands-directory/proposal.md +26 -0
- package/OpenSpec-main/openspec/changes/fix-opencode-commands-directory/specs/command-generation/spec.md +63 -0
- package/OpenSpec-main/openspec/changes/fix-opencode-commands-directory/tasks.md +19 -0
- package/OpenSpec-main/openspec/changes/graceful-status-no-changes/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/graceful-status-no-changes/design.md +38 -0
- package/OpenSpec-main/openspec/changes/graceful-status-no-changes/proposal.md +25 -0
- package/OpenSpec-main/openspec/changes/graceful-status-no-changes/specs/graceful-status-empty/spec.md +27 -0
- package/OpenSpec-main/openspec/changes/graceful-status-no-changes/tasks.md +16 -0
- package/OpenSpec-main/openspec/changes/schema-alias-support/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/schema-alias-support/proposal.md +28 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/design.md +288 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/proposal.md +202 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/specs/cli-init/spec.md +199 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/specs/cli-update/spec.md +177 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/specs/profiles/spec.md +142 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/specs/propose-workflow/spec.md +42 -0
- package/OpenSpec-main/openspec/changes/simplify-skill-installation/tasks.md +132 -0
- package/OpenSpec-main/openspec/changes/unify-template-generation-pipeline/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/unify-template-generation-pipeline/design.md +149 -0
- package/OpenSpec-main/openspec/changes/unify-template-generation-pipeline/proposal.md +47 -0
- package/OpenSpec-main/openspec/changes/unify-template-generation-pipeline/specs/template-artifact-pipeline/spec.md +89 -0
- package/OpenSpec-main/openspec/changes/unify-template-generation-pipeline/tasks.md +41 -0
- package/OpenSpec-main/openspec/changes/workspace-agent-guidance/.openspec.yaml +2 -0
- package/OpenSpec-main/openspec/changes/workspace-agent-guidance/proposal.md +100 -0
- package/OpenSpec-main/openspec/changes/workspace-apply-repo-slice/proposal.md +58 -0
- package/OpenSpec-main/openspec/changes/workspace-reimplementation-roadmap/HISTORICAL_DIRECTION.md +511 -0
- package/OpenSpec-main/openspec/changes/workspace-reimplementation-roadmap/POC_REFERENCE_GUIDE.md +266 -0
- package/OpenSpec-main/openspec/changes/workspace-reimplementation-roadmap/README.md +107 -0
- package/OpenSpec-main/openspec/changes/workspace-reimplementation-roadmap/START_HERE.md +105 -0
- package/OpenSpec-main/openspec/changes/workspace-reimplementation-roadmap/proposal.md +62 -0
- package/OpenSpec-main/openspec/changes/workspace-verify-and-archive/proposal.md +57 -0
- package/OpenSpec-main/openspec/config.yaml +36 -0
- package/OpenSpec-main/openspec/explorations/explore-workflow-ux.md +116 -0
- package/OpenSpec-main/openspec/explorations/workspace-architecture.md +857 -0
- package/OpenSpec-main/openspec/explorations/workspace-roadmap.md +367 -0
- package/OpenSpec-main/openspec/explorations/workspace-user-journeys.md +2259 -0
- package/OpenSpec-main/openspec/explorations/workspace-ux-simplification.md +491 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/.initiative.yaml +27 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/README.md +33 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/decisions.md +204 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/direction.md +447 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/questions.md +23 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/roadmap.md +759 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/tasks.md +308 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/01-lock-the-direction/evidence.md +154 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/01-lock-the-direction/plan.md +90 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/01-lock-the-direction/tasks.md +44 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/02-stabilize-workspace-as-local-view/evidence.md +68 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/02-stabilize-workspace-as-local-view/plan.md +80 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/02-stabilize-workspace-as-local-view/tasks.md +23 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/03-add-context-store-foundation/evidence.md +43 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/03-add-context-store-foundation/plan.md +85 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/03-add-context-store-foundation/tasks.md +17 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/04-add-collection-foundation/evidence.md +77 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/04-add-collection-foundation/plan.md +198 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/04-add-collection-foundation/tasks.md +14 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/05-ship-initiative-mvp/evidence.md +99 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/05-ship-initiative-mvp/plan.md +236 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/05-ship-initiative-mvp/tasks.md +21 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/06-add-minimal-context-store-ux/evidence.md +97 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/06-add-minimal-context-store-ux/plan.md +333 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/06-add-minimal-context-store-ux/tasks.md +29 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/07-add-agent-first-initiative-discovery/evidence.md +97 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/07-add-agent-first-initiative-discovery/plan.md +184 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/07-add-agent-first-initiative-discovery/tasks.md +27 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/08-connect-repo-local-changes-to-initiatives/evidence.md +239 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/08-connect-repo-local-changes-to-initiatives/plan.md +279 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/08-connect-repo-local-changes-to-initiatives/tasks.md +22 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/09-add-initiative-resolve/decision-review.md +64 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/09-add-initiative-resolve/evidence.md +106 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/09-add-initiative-resolve/plan.md +141 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/09-add-initiative-resolve/tasks.md +22 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/10-let-workspaces-open-initiatives/plan.md +430 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/10-let-workspaces-open-initiatives/tasks.md +43 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/11-manual-beta-reality-pass/notes.md +289 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/11-manual-beta-reality-pass/plan.md +39 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/11-manual-beta-reality-pass/tasks.md +8 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/12-context-store-first-run-and-cleanup-ux/evidence.md +45 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/12-context-store-first-run-and-cleanup-ux/plan.md +150 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/12-context-store-first-run-and-cleanup-ux/tasks.md +23 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/13-agent-handoff-output-and-delivery-polish/evidence.md +25 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/13-agent-handoff-output-and-delivery-polish/plan.md +98 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/13-agent-handoff-output-and-delivery-polish/tasks.md +25 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/14-workspaces-beta-guide-split/plan.md +37 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/14-workspaces-beta-guide-split/tasks.md +9 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/15-context-store-project-roots-and-schema-led-initiatives/evidence.md +140 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/15-context-store-project-roots-and-schema-led-initiatives/plan.md +344 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/15-context-store-project-roots-and-schema-led-initiatives/tasks.md +39 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/16-add-escalation-ux/plan.md +26 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/16-add-escalation-ux/tasks.md +7 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/17-harden-team-shared-coordination/plan.md +25 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/17-harden-team-shared-coordination/tasks.md +7 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/18-explore-initiative-hosted-target-bound-change-artifacts/evidence.md +397 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/18-explore-initiative-hosted-target-bound-change-artifacts/plan.md +180 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/18-explore-initiative-hosted-target-bound-change-artifacts/tasks.md +28 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/19-review-workspace-beta-compatibility-before-public-release/plan.md +62 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/19-review-workspace-beta-compatibility-before-public-release/tasks.md +16 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/proposed-initiative-next-agent-handoff-ux/evidence.md +47 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/proposed-initiative-next-agent-handoff-ux/plan.md +90 -0
- package/OpenSpec-main/openspec/initiatives/context-store-and-initiatives/work-items/proposed-initiative-next-agent-handoff-ux/tasks.md +18 -0
- package/OpenSpec-main/openspec/specs/ai-tool-paths/spec.md +63 -0
- package/OpenSpec-main/openspec/specs/artifact-graph/spec.md +165 -0
- package/OpenSpec-main/openspec/specs/change-creation/spec.md +108 -0
- package/OpenSpec-main/openspec/specs/ci-nix-validation/spec.md +107 -0
- package/OpenSpec-main/openspec/specs/cli-archive/spec.md +210 -0
- package/OpenSpec-main/openspec/specs/cli-artifact-workflow/spec.md +398 -0
- package/OpenSpec-main/openspec/specs/cli-change/spec.md +92 -0
- package/OpenSpec-main/openspec/specs/cli-completion/spec.md +432 -0
- package/OpenSpec-main/openspec/specs/cli-config/spec.md +318 -0
- package/OpenSpec-main/openspec/specs/cli-feedback/spec.md +193 -0
- package/OpenSpec-main/openspec/specs/cli-init/spec.md +269 -0
- package/OpenSpec-main/openspec/specs/cli-list/spec.md +103 -0
- package/OpenSpec-main/openspec/specs/cli-show/spec.md +86 -0
- package/OpenSpec-main/openspec/specs/cli-spec/spec.md +88 -0
- package/OpenSpec-main/openspec/specs/cli-update/spec.md +229 -0
- package/OpenSpec-main/openspec/specs/cli-validate/spec.md +219 -0
- package/OpenSpec-main/openspec/specs/cli-view/spec.md +129 -0
- package/OpenSpec-main/openspec/specs/command-generation/spec.md +97 -0
- package/OpenSpec-main/openspec/specs/config-loading/spec.md +122 -0
- package/OpenSpec-main/openspec/specs/context-injection/spec.md +53 -0
- package/OpenSpec-main/openspec/specs/docs-agent-instructions/spec.md +62 -0
- package/OpenSpec-main/openspec/specs/global-config/spec.md +101 -0
- package/OpenSpec-main/openspec/specs/instruction-loader/spec.md +70 -0
- package/OpenSpec-main/openspec/specs/legacy-cleanup/spec.md +163 -0
- package/OpenSpec-main/openspec/specs/openspec-conventions/spec.md +556 -0
- package/OpenSpec-main/openspec/specs/opsx-archive-skill/spec.md +128 -0
- package/OpenSpec-main/openspec/specs/opsx-onboard-skill/spec.md +167 -0
- package/OpenSpec-main/openspec/specs/opsx-verify-skill/spec.md +189 -0
- package/OpenSpec-main/openspec/specs/rules-injection/spec.md +102 -0
- package/OpenSpec-main/openspec/specs/schema-fork-command/spec.md +71 -0
- package/OpenSpec-main/openspec/specs/schema-init-command/spec.md +76 -0
- package/OpenSpec-main/openspec/specs/schema-resolution/spec.md +201 -0
- package/OpenSpec-main/openspec/specs/schema-validate-command/spec.md +91 -0
- package/OpenSpec-main/openspec/specs/schema-which-command/spec.md +70 -0
- package/OpenSpec-main/openspec/specs/specs-sync-skill/spec.md +72 -0
- package/OpenSpec-main/openspec/specs/telemetry/spec.md +122 -0
- package/OpenSpec-main/openspec/specs/workspace-change-planning/spec.md +71 -0
- package/OpenSpec-main/openspec/specs/workspace-foundation/spec.md +279 -0
- package/OpenSpec-main/openspec/specs/workspace-links/spec.md +529 -0
- package/OpenSpec-main/openspec/specs/workspace-open/spec.md +205 -0
- package/OpenSpec-main/openspec-parallel-merge-plan.md +98 -0
- package/OpenSpec-main/package-lock.json +4978 -0
- package/OpenSpec-main/package.json +84 -0
- package/OpenSpec-main/pnpm-lock.yaml +3187 -0
- package/OpenSpec-main/schemas/spec-driven/schema.yaml +153 -0
- package/OpenSpec-main/schemas/spec-driven/templates/design.md +19 -0
- package/OpenSpec-main/schemas/spec-driven/templates/proposal.md +23 -0
- package/OpenSpec-main/schemas/spec-driven/templates/spec.md +8 -0
- package/OpenSpec-main/schemas/spec-driven/templates/tasks.md +9 -0
- package/OpenSpec-main/schemas/workspace-planning/schema.yaml +72 -0
- package/OpenSpec-main/schemas/workspace-planning/templates/design.md +33 -0
- package/OpenSpec-main/schemas/workspace-planning/templates/proposal.md +28 -0
- package/OpenSpec-main/schemas/workspace-planning/templates/spec.md +9 -0
- package/OpenSpec-main/schemas/workspace-planning/templates/tasks.md +15 -0
- package/OpenSpec-main/scripts/README.md +37 -0
- package/OpenSpec-main/scripts/pack-version-check.mjs +111 -0
- package/OpenSpec-main/scripts/postinstall.js +83 -0
- package/OpenSpec-main/scripts/test-postinstall.sh +57 -0
- package/OpenSpec-main/scripts/update-flake.sh +128 -0
- package/OpenSpec-main/test/cli-e2e/basic.test.ts +205 -0
- package/OpenSpec-main/test/commands/artifact-workflow.test.ts +1063 -0
- package/OpenSpec-main/test/commands/change-initiative-link.test.ts +532 -0
- package/OpenSpec-main/test/commands/change.interactive-show.test.ts +45 -0
- package/OpenSpec-main/test/commands/change.interactive-validate.test.ts +48 -0
- package/OpenSpec-main/test/commands/completion.test.ts +278 -0
- package/OpenSpec-main/test/commands/config-profile.test.ts +532 -0
- package/OpenSpec-main/test/commands/config.test.ts +285 -0
- package/OpenSpec-main/test/commands/context-store.test.ts +692 -0
- package/OpenSpec-main/test/commands/feedback.test.ts +429 -0
- package/OpenSpec-main/test/commands/initiative.test.ts +907 -0
- package/OpenSpec-main/test/commands/schema.test.ts +467 -0
- package/OpenSpec-main/test/commands/show.test.ts +123 -0
- package/OpenSpec-main/test/commands/spec.interactive-show.test.ts +44 -0
- package/OpenSpec-main/test/commands/spec.interactive-validate.test.ts +44 -0
- package/OpenSpec-main/test/commands/spec.test.ts +324 -0
- package/OpenSpec-main/test/commands/validate.enriched-output.test.ts +49 -0
- package/OpenSpec-main/test/commands/validate.test.ts +147 -0
- package/OpenSpec-main/test/commands/workspace-initiative-open.test.ts +638 -0
- package/OpenSpec-main/test/commands/workspace-open.test.ts +123 -0
- package/OpenSpec-main/test/commands/workspace.interactive.test.ts +696 -0
- package/OpenSpec-main/test/commands/workspace.test.ts +1812 -0
- package/OpenSpec-main/test/core/archive.test.ts +869 -0
- package/OpenSpec-main/test/core/artifact-graph/graph.test.ts +268 -0
- package/OpenSpec-main/test/core/artifact-graph/instruction-loader.test.ts +609 -0
- package/OpenSpec-main/test/core/artifact-graph/outputs.test.ts +175 -0
- package/OpenSpec-main/test/core/artifact-graph/resolver.test.ts +651 -0
- package/OpenSpec-main/test/core/artifact-graph/schema.test.ts +207 -0
- package/OpenSpec-main/test/core/artifact-graph/state.test.ts +174 -0
- package/OpenSpec-main/test/core/artifact-graph/workflow.integration.test.ts +182 -0
- package/OpenSpec-main/test/core/available-tools.test.ts +167 -0
- package/OpenSpec-main/test/core/collections/initiatives/operations.test.ts +342 -0
- package/OpenSpec-main/test/core/collections/initiatives/resolution.test.ts +21 -0
- package/OpenSpec-main/test/core/collections/initiatives/schema.test.ts +201 -0
- package/OpenSpec-main/test/core/collections/initiatives/templates.test.ts +74 -0
- package/OpenSpec-main/test/core/collections/runtime.test.ts +214 -0
- package/OpenSpec-main/test/core/command-generation/adapters.test.ts +710 -0
- package/OpenSpec-main/test/core/command-generation/generator.test.ts +110 -0
- package/OpenSpec-main/test/core/command-generation/registry.test.ts +108 -0
- package/OpenSpec-main/test/core/command-generation/types.test.ts +79 -0
- package/OpenSpec-main/test/core/commands/change-command.list.test.ts +76 -0
- package/OpenSpec-main/test/core/commands/change-command.show-validate.test.ts +111 -0
- package/OpenSpec-main/test/core/completions/command-registry.test.ts +201 -0
- package/OpenSpec-main/test/core/completions/completion-provider.test.ts +288 -0
- package/OpenSpec-main/test/core/completions/generators/bash-generator.test.ts +586 -0
- package/OpenSpec-main/test/core/completions/generators/fish-generator.test.ts +549 -0
- package/OpenSpec-main/test/core/completions/generators/powershell-generator.test.ts +621 -0
- package/OpenSpec-main/test/core/completions/generators/zsh-generator.test.ts +425 -0
- package/OpenSpec-main/test/core/completions/installers/bash-installer.test.ts +484 -0
- package/OpenSpec-main/test/core/completions/installers/fish-installer.test.ts +321 -0
- package/OpenSpec-main/test/core/completions/installers/powershell-installer.test.ts +824 -0
- package/OpenSpec-main/test/core/completions/installers/zsh-installer.test.ts +750 -0
- package/OpenSpec-main/test/core/config-schema.test.ts +340 -0
- package/OpenSpec-main/test/core/context-store/foundation.test.ts +364 -0
- package/OpenSpec-main/test/core/context-store/registry.test.ts +599 -0
- package/OpenSpec-main/test/core/converters/json-converter.test.ts +184 -0
- package/OpenSpec-main/test/core/global-config.test.ts +371 -0
- package/OpenSpec-main/test/core/init.test.ts +786 -0
- package/OpenSpec-main/test/core/legacy-cleanup.test.ts +1162 -0
- package/OpenSpec-main/test/core/list.test.ts +165 -0
- package/OpenSpec-main/test/core/migration.test.ts +150 -0
- package/OpenSpec-main/test/core/parsers/change-parser.test.ts +52 -0
- package/OpenSpec-main/test/core/parsers/markdown-parser.test.ts +355 -0
- package/OpenSpec-main/test/core/parsers/requirement-blocks.test.ts +46 -0
- package/OpenSpec-main/test/core/planning-home.test.ts +120 -0
- package/OpenSpec-main/test/core/profile-sync-drift.test.ts +92 -0
- package/OpenSpec-main/test/core/profiles.test.ts +63 -0
- package/OpenSpec-main/test/core/project-config.test.ts +610 -0
- package/OpenSpec-main/test/core/shared/skill-generation.test.ts +301 -0
- package/OpenSpec-main/test/core/shared/tool-detection.test.ts +333 -0
- package/OpenSpec-main/test/core/templates/skill-templates-parity.test.ts +172 -0
- package/OpenSpec-main/test/core/update.test.ts +1810 -0
- package/OpenSpec-main/test/core/validation.enriched-messages.test.ts +74 -0
- package/OpenSpec-main/test/core/validation.test.ts +680 -0
- package/OpenSpec-main/test/core/view.test.ts +129 -0
- package/OpenSpec-main/test/core/workspace/foundation.test.ts +694 -0
- package/OpenSpec-main/test/core/workspace/legacy-state.test.ts +221 -0
- package/OpenSpec-main/test/core/workspace/skills.test.ts +69 -0
- package/OpenSpec-main/test/fixtures/tmp-init/openspec/changes/c1/proposal.md +7 -0
- package/OpenSpec-main/test/fixtures/tmp-init/openspec/changes/c1/specs/alpha/spec.md +8 -0
- package/OpenSpec-main/test/fixtures/tmp-init/openspec/specs/alpha/spec.md +12 -0
- package/OpenSpec-main/test/helpers/path-env.ts +26 -0
- package/OpenSpec-main/test/helpers/run-cli.ts +150 -0
- package/OpenSpec-main/test/prompts/searchable-multi-select.test.ts +220 -0
- package/OpenSpec-main/test/specs/source-specs-normalization.test.ts +63 -0
- package/OpenSpec-main/test/telemetry/config.test.ts +298 -0
- package/OpenSpec-main/test/telemetry/index.test.ts +219 -0
- package/OpenSpec-main/test/utils/change-metadata.test.ts +368 -0
- package/OpenSpec-main/test/utils/change-utils.test.ts +201 -0
- package/OpenSpec-main/test/utils/command-references.test.ts +83 -0
- package/OpenSpec-main/test/utils/file-system.test.ts +322 -0
- package/OpenSpec-main/test/utils/interactive.test.ts +125 -0
- package/OpenSpec-main/test/utils/marker-updates.test.ts +448 -0
- package/OpenSpec-main/test/utils/shell-detection.test.ts +185 -0
- package/OpenSpec-main/vitest.config.ts +47 -0
- package/OpenSpec-main/vitest.setup.ts +15 -0
- package/README.md +37 -4
- package/Scanner CLI Inteligente para Projetos.md +433 -0
- package/dist/index.js +1248 -161
- package/package.json +1 -1
- package/templates/wiki-recipes/nextjs.rules.md +21 -0
- package/templates/wiki-recipes/postgresql.rules.md +20 -0
- package/templates/wiki-recipes/react.rules.md +18 -0
- package/templates/wiki-recipes/tailwindcss.rules.md +18 -0
- package/templates/wiki-recipes/typescript.rules.md +21 -0
|
@@ -0,0 +1,1810 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { UpdateCommand, scanInstalledWorkflows } from '../../src/core/update.js';
|
|
3
|
+
import { InitCommand } from '../../src/core/init.js';
|
|
4
|
+
import { FileSystemUtils } from '../../src/utils/file-system.js';
|
|
5
|
+
import { OPENSPEC_MARKERS } from '../../src/core/config.js';
|
|
6
|
+
import type { GlobalConfig } from '../../src/core/global-config.js';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import { randomUUID } from 'crypto';
|
|
11
|
+
|
|
12
|
+
// Shared mutable mock config state
|
|
13
|
+
const mockState = {
|
|
14
|
+
config: {
|
|
15
|
+
featureFlags: {},
|
|
16
|
+
profile: 'core' as const,
|
|
17
|
+
delivery: 'both' as const,
|
|
18
|
+
} as GlobalConfig,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Mock global config module to isolate tests from the machine's actual config
|
|
22
|
+
vi.mock('../../src/core/global-config.js', async (importOriginal) => {
|
|
23
|
+
const actual = await importOriginal<typeof import('../../src/core/global-config.js')>();
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
...actual,
|
|
27
|
+
getGlobalConfig: () => ({ ...mockState.config }),
|
|
28
|
+
saveGlobalConfig: vi.fn(),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Helper to set mock config for tests
|
|
33
|
+
function setMockConfig(config: GlobalConfig) {
|
|
34
|
+
mockState.config = config;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resetMockConfig() {
|
|
38
|
+
mockState.config = { featureFlags: {}, profile: 'core', delivery: 'both' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('UpdateCommand', () => {
|
|
42
|
+
let testDir: string;
|
|
43
|
+
let updateCommand: UpdateCommand;
|
|
44
|
+
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
// Create a temporary test directory
|
|
47
|
+
testDir = path.join(os.tmpdir(), `openspec-test-${randomUUID()}`);
|
|
48
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
49
|
+
|
|
50
|
+
// Create openspec directory
|
|
51
|
+
const openspecDir = path.join(testDir, 'openspec');
|
|
52
|
+
await fs.mkdir(openspecDir, { recursive: true });
|
|
53
|
+
|
|
54
|
+
updateCommand = new UpdateCommand();
|
|
55
|
+
|
|
56
|
+
// Reset mock config to defaults
|
|
57
|
+
resetMockConfig();
|
|
58
|
+
|
|
59
|
+
// Clear all mocks before each test
|
|
60
|
+
vi.restoreAllMocks();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(async () => {
|
|
64
|
+
// Restore all mocks after each test
|
|
65
|
+
vi.restoreAllMocks();
|
|
66
|
+
|
|
67
|
+
// Clean up test directory
|
|
68
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('basic validation', () => {
|
|
72
|
+
it('should throw error if openspec directory does not exist', async () => {
|
|
73
|
+
// Remove openspec directory
|
|
74
|
+
await fs.rm(path.join(testDir, 'openspec'), {
|
|
75
|
+
recursive: true,
|
|
76
|
+
force: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await expect(updateCommand.execute(testDir)).rejects.toThrow(
|
|
80
|
+
"No OpenSpec directory found. Run 'openspec init' first."
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should report no configured tools when none exist', async () => {
|
|
85
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
86
|
+
|
|
87
|
+
await updateCommand.execute(testDir);
|
|
88
|
+
|
|
89
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
90
|
+
expect.stringContaining('No configured tools found')
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
consoleSpy.mockRestore();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('skill updates', () => {
|
|
98
|
+
it('should update skill files for configured Claude tool', async () => {
|
|
99
|
+
// Set up a configured Claude tool by creating skill directories
|
|
100
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
101
|
+
const exploreSkillDir = path.join(skillsDir, 'openspec-explore');
|
|
102
|
+
await fs.mkdir(exploreSkillDir, { recursive: true });
|
|
103
|
+
|
|
104
|
+
// Create an existing skill file
|
|
105
|
+
const oldSkillContent = `---
|
|
106
|
+
name: openspec-explore (old)
|
|
107
|
+
description: Old description
|
|
108
|
+
license: MIT
|
|
109
|
+
compatibility: Requires openspec CLI.
|
|
110
|
+
metadata:
|
|
111
|
+
author: openspec
|
|
112
|
+
version: "0.9"
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
Old instructions content
|
|
116
|
+
`;
|
|
117
|
+
await fs.writeFile(
|
|
118
|
+
path.join(exploreSkillDir, 'SKILL.md'),
|
|
119
|
+
oldSkillContent
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
123
|
+
|
|
124
|
+
await updateCommand.execute(testDir);
|
|
125
|
+
|
|
126
|
+
// Check skill file was updated
|
|
127
|
+
const updatedSkill = await fs.readFile(
|
|
128
|
+
path.join(exploreSkillDir, 'SKILL.md'),
|
|
129
|
+
'utf-8'
|
|
130
|
+
);
|
|
131
|
+
expect(updatedSkill).toContain('name: openspec-explore');
|
|
132
|
+
expect(updatedSkill).not.toContain('Old instructions content');
|
|
133
|
+
expect(updatedSkill).toContain('license: MIT');
|
|
134
|
+
|
|
135
|
+
// Check console output
|
|
136
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
137
|
+
expect.stringContaining('Updating 1 tool(s): claude')
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
consoleSpy.mockRestore();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should update core profile skill files when tool is configured', async () => {
|
|
144
|
+
// Set up a configured tool with one skill directory
|
|
145
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
146
|
+
|
|
147
|
+
// Create at least one skill to mark tool as configured
|
|
148
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
149
|
+
recursive: true,
|
|
150
|
+
});
|
|
151
|
+
await fs.writeFile(
|
|
152
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
153
|
+
'old content'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
await updateCommand.execute(testDir);
|
|
157
|
+
|
|
158
|
+
// Verify core profile skill files were created/updated (propose, explore, apply, sync, archive)
|
|
159
|
+
const coreSkillNames = [
|
|
160
|
+
'openspec-explore',
|
|
161
|
+
'openspec-apply-change',
|
|
162
|
+
'openspec-sync-specs',
|
|
163
|
+
'openspec-archive-change',
|
|
164
|
+
'openspec-propose',
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
for (const skillName of coreSkillNames) {
|
|
168
|
+
const skillFile = path.join(skillsDir, skillName, 'SKILL.md');
|
|
169
|
+
const exists = await FileSystemUtils.fileExists(skillFile);
|
|
170
|
+
expect(exists).toBe(true);
|
|
171
|
+
|
|
172
|
+
const content = await fs.readFile(skillFile, 'utf-8');
|
|
173
|
+
expect(content).toContain('---');
|
|
174
|
+
expect(content).toContain('name:');
|
|
175
|
+
expect(content).toContain('description:');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Verify non-core skills are NOT created
|
|
179
|
+
const nonCoreSkillNames = [
|
|
180
|
+
'openspec-new-change',
|
|
181
|
+
'openspec-continue-change',
|
|
182
|
+
'openspec-ff-change',
|
|
183
|
+
'openspec-bulk-archive-change',
|
|
184
|
+
'openspec-verify-change',
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
for (const skillName of nonCoreSkillNames) {
|
|
188
|
+
const skillFile = path.join(skillsDir, skillName, 'SKILL.md');
|
|
189
|
+
const exists = await FileSystemUtils.fileExists(skillFile);
|
|
190
|
+
expect(exists).toBe(false);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('command updates', () => {
|
|
196
|
+
it('should update opsx commands for configured Claude tool', async () => {
|
|
197
|
+
// Set up a configured Claude tool
|
|
198
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
199
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
200
|
+
recursive: true,
|
|
201
|
+
});
|
|
202
|
+
await fs.writeFile(
|
|
203
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
204
|
+
'old content'
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
await updateCommand.execute(testDir);
|
|
208
|
+
|
|
209
|
+
// Check opsx command files were created
|
|
210
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
211
|
+
const exploreCmd = path.join(commandsDir, 'explore.md');
|
|
212
|
+
const exists = await FileSystemUtils.fileExists(exploreCmd);
|
|
213
|
+
expect(exists).toBe(true);
|
|
214
|
+
|
|
215
|
+
const content = await fs.readFile(exploreCmd, 'utf-8');
|
|
216
|
+
expect(content).toContain('---');
|
|
217
|
+
expect(content).toContain('name:');
|
|
218
|
+
expect(content).toContain('description:');
|
|
219
|
+
expect(content).toContain('category:');
|
|
220
|
+
expect(content).toContain('tags:');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should update core profile opsx commands when tool is configured', async () => {
|
|
224
|
+
// Set up a configured tool
|
|
225
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
226
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
227
|
+
recursive: true,
|
|
228
|
+
});
|
|
229
|
+
await fs.writeFile(
|
|
230
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
231
|
+
'old content'
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
await updateCommand.execute(testDir);
|
|
235
|
+
|
|
236
|
+
// Verify core profile commands were created (propose, explore, apply, sync, archive)
|
|
237
|
+
const coreCommandIds = ['explore', 'apply', 'sync', 'archive', 'propose'];
|
|
238
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
239
|
+
for (const cmdId of coreCommandIds) {
|
|
240
|
+
const cmdFile = path.join(commandsDir, `${cmdId}.md`);
|
|
241
|
+
const exists = await FileSystemUtils.fileExists(cmdFile);
|
|
242
|
+
expect(exists).toBe(true);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Verify non-core commands are NOT created
|
|
246
|
+
const nonCoreCommandIds = ['new', 'continue', 'ff', 'bulk-archive', 'verify'];
|
|
247
|
+
for (const cmdId of nonCoreCommandIds) {
|
|
248
|
+
const cmdFile = path.join(commandsDir, `${cmdId}.md`);
|
|
249
|
+
const exists = await FileSystemUtils.fileExists(cmdFile);
|
|
250
|
+
expect(exists).toBe(false);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('multi-tool support', () => {
|
|
257
|
+
it('should update multiple configured tools', async () => {
|
|
258
|
+
// Set up Claude
|
|
259
|
+
const claudeSkillsDir = path.join(testDir, '.claude', 'skills');
|
|
260
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-explore'), {
|
|
261
|
+
recursive: true,
|
|
262
|
+
});
|
|
263
|
+
await fs.writeFile(
|
|
264
|
+
path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
265
|
+
'old'
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Set up Cursor
|
|
269
|
+
const cursorSkillsDir = path.join(testDir, '.cursor', 'skills');
|
|
270
|
+
await fs.mkdir(path.join(cursorSkillsDir, 'openspec-explore'), {
|
|
271
|
+
recursive: true,
|
|
272
|
+
});
|
|
273
|
+
await fs.writeFile(
|
|
274
|
+
path.join(cursorSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
275
|
+
'old'
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
279
|
+
|
|
280
|
+
await updateCommand.execute(testDir);
|
|
281
|
+
|
|
282
|
+
// Both tools should be updated
|
|
283
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
284
|
+
expect.stringContaining('Updating 2 tool(s)')
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Verify Claude skills updated
|
|
288
|
+
const claudeSkill = await fs.readFile(
|
|
289
|
+
path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
290
|
+
'utf-8'
|
|
291
|
+
);
|
|
292
|
+
expect(claudeSkill).toContain('name: openspec-explore');
|
|
293
|
+
|
|
294
|
+
// Verify Cursor skills updated
|
|
295
|
+
const cursorSkill = await fs.readFile(
|
|
296
|
+
path.join(cursorSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
297
|
+
'utf-8'
|
|
298
|
+
);
|
|
299
|
+
expect(cursorSkill).toContain('name: openspec-explore');
|
|
300
|
+
|
|
301
|
+
consoleSpy.mockRestore();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should update Qwen tool with correct command format', async () => {
|
|
305
|
+
// Set up Qwen
|
|
306
|
+
const qwenSkillsDir = path.join(testDir, '.qwen', 'skills');
|
|
307
|
+
await fs.mkdir(path.join(qwenSkillsDir, 'openspec-explore'), {
|
|
308
|
+
recursive: true,
|
|
309
|
+
});
|
|
310
|
+
await fs.writeFile(
|
|
311
|
+
path.join(qwenSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
312
|
+
'old'
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
await updateCommand.execute(testDir);
|
|
316
|
+
|
|
317
|
+
// Check Qwen command format (TOML) - Qwen uses flat path structure: opsx-<id>.toml
|
|
318
|
+
const qwenCmd = path.join(
|
|
319
|
+
testDir,
|
|
320
|
+
'.qwen',
|
|
321
|
+
'commands',
|
|
322
|
+
'opsx-explore.toml'
|
|
323
|
+
);
|
|
324
|
+
const exists = await FileSystemUtils.fileExists(qwenCmd);
|
|
325
|
+
expect(exists).toBe(true);
|
|
326
|
+
|
|
327
|
+
const content = await fs.readFile(qwenCmd, 'utf-8');
|
|
328
|
+
expect(content).toContain('description =');
|
|
329
|
+
expect(content).toContain('prompt =');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should update Windsurf tool with correct command format', async () => {
|
|
333
|
+
// Set up Windsurf
|
|
334
|
+
const windsurfSkillsDir = path.join(testDir, '.windsurf', 'skills');
|
|
335
|
+
await fs.mkdir(path.join(windsurfSkillsDir, 'openspec-explore'), {
|
|
336
|
+
recursive: true,
|
|
337
|
+
});
|
|
338
|
+
await fs.writeFile(
|
|
339
|
+
path.join(windsurfSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
340
|
+
'old'
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
await updateCommand.execute(testDir);
|
|
344
|
+
|
|
345
|
+
// Check Windsurf command format
|
|
346
|
+
const windsurfCmd = path.join(
|
|
347
|
+
testDir,
|
|
348
|
+
'.windsurf',
|
|
349
|
+
'workflows',
|
|
350
|
+
'opsx-explore.md'
|
|
351
|
+
);
|
|
352
|
+
const exists = await FileSystemUtils.fileExists(windsurfCmd);
|
|
353
|
+
expect(exists).toBe(true);
|
|
354
|
+
|
|
355
|
+
const content = await fs.readFile(windsurfCmd, 'utf-8');
|
|
356
|
+
expect(content).toContain('---');
|
|
357
|
+
expect(content).toContain('name:');
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe('error handling', () => {
|
|
362
|
+
it('should handle tool update failures gracefully', async () => {
|
|
363
|
+
// Set up a configured tool
|
|
364
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
365
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
366
|
+
recursive: true,
|
|
367
|
+
});
|
|
368
|
+
await fs.writeFile(
|
|
369
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
370
|
+
'old'
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
// Mock writeFile to fail for skills
|
|
374
|
+
const originalWriteFile = FileSystemUtils.writeFile.bind(FileSystemUtils);
|
|
375
|
+
const writeSpy = vi
|
|
376
|
+
.spyOn(FileSystemUtils, 'writeFile')
|
|
377
|
+
.mockImplementation(async (filePath, content) => {
|
|
378
|
+
if (filePath.includes('SKILL.md')) {
|
|
379
|
+
throw new Error('EACCES: permission denied');
|
|
380
|
+
}
|
|
381
|
+
return originalWriteFile(filePath, content);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
385
|
+
|
|
386
|
+
// Should not throw
|
|
387
|
+
await updateCommand.execute(testDir);
|
|
388
|
+
|
|
389
|
+
// Should report failure
|
|
390
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
391
|
+
expect.stringContaining('Failed')
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
writeSpy.mockRestore();
|
|
395
|
+
consoleSpy.mockRestore();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should continue updating other tools when one fails', async () => {
|
|
399
|
+
// Set up Claude and Cursor
|
|
400
|
+
const claudeSkillsDir = path.join(testDir, '.claude', 'skills');
|
|
401
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-explore'), {
|
|
402
|
+
recursive: true,
|
|
403
|
+
});
|
|
404
|
+
await fs.writeFile(
|
|
405
|
+
path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
406
|
+
'old'
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const cursorSkillsDir = path.join(testDir, '.cursor', 'skills');
|
|
410
|
+
await fs.mkdir(path.join(cursorSkillsDir, 'openspec-explore'), {
|
|
411
|
+
recursive: true,
|
|
412
|
+
});
|
|
413
|
+
await fs.writeFile(
|
|
414
|
+
path.join(cursorSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
415
|
+
'old'
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
// Mock writeFile to fail only for Claude
|
|
419
|
+
const originalWriteFile = FileSystemUtils.writeFile.bind(FileSystemUtils);
|
|
420
|
+
const writeSpy = vi
|
|
421
|
+
.spyOn(FileSystemUtils, 'writeFile')
|
|
422
|
+
.mockImplementation(async (filePath, content) => {
|
|
423
|
+
if (filePath.includes('.claude') && filePath.includes('SKILL.md')) {
|
|
424
|
+
throw new Error('EACCES: permission denied');
|
|
425
|
+
}
|
|
426
|
+
return originalWriteFile(filePath, content);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
430
|
+
|
|
431
|
+
await updateCommand.execute(testDir);
|
|
432
|
+
|
|
433
|
+
// Cursor should still be updated - check the actual format from ora spinner
|
|
434
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
435
|
+
expect.stringContaining('Updated: Cursor')
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Claude should be reported as failed
|
|
439
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
440
|
+
expect.stringContaining('Failed')
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
writeSpy.mockRestore();
|
|
444
|
+
consoleSpy.mockRestore();
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('tool detection', () => {
|
|
449
|
+
it('should detect tool as configured only when skill file exists', async () => {
|
|
450
|
+
// Create skills directory but no skill files
|
|
451
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
452
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
453
|
+
|
|
454
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
455
|
+
|
|
456
|
+
await updateCommand.execute(testDir);
|
|
457
|
+
|
|
458
|
+
// Should report no configured tools
|
|
459
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
460
|
+
expect.stringContaining('No configured tools found')
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
consoleSpy.mockRestore();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should detect tool when any single skill exists', async () => {
|
|
467
|
+
// Create only one skill file
|
|
468
|
+
const skillDir = path.join(
|
|
469
|
+
testDir,
|
|
470
|
+
'.claude',
|
|
471
|
+
'skills',
|
|
472
|
+
'openspec-archive-change'
|
|
473
|
+
);
|
|
474
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
475
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), 'old');
|
|
476
|
+
|
|
477
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
478
|
+
|
|
479
|
+
await updateCommand.execute(testDir);
|
|
480
|
+
|
|
481
|
+
// Should detect and update Claude
|
|
482
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
483
|
+
expect.stringContaining('Updating 1 tool(s): claude')
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
consoleSpy.mockRestore();
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
describe('skill content validation', () => {
|
|
491
|
+
it('should generate valid YAML frontmatter in skill files', async () => {
|
|
492
|
+
// Set up a configured tool
|
|
493
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
494
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
495
|
+
recursive: true,
|
|
496
|
+
});
|
|
497
|
+
await fs.writeFile(
|
|
498
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
499
|
+
'old'
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
await updateCommand.execute(testDir);
|
|
503
|
+
|
|
504
|
+
const skillContent = await fs.readFile(
|
|
505
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
506
|
+
'utf-8'
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
// Validate frontmatter structure
|
|
510
|
+
expect(skillContent).toMatch(/^---\n/);
|
|
511
|
+
expect(skillContent).toContain('name:');
|
|
512
|
+
expect(skillContent).toContain('description:');
|
|
513
|
+
expect(skillContent).toContain('license:');
|
|
514
|
+
expect(skillContent).toContain('compatibility:');
|
|
515
|
+
expect(skillContent).toContain('metadata:');
|
|
516
|
+
expect(skillContent).toContain('author:');
|
|
517
|
+
expect(skillContent).toContain('version:');
|
|
518
|
+
expect(skillContent).toMatch(/---\n\n/);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('should include proper instructions in skill files', async () => {
|
|
522
|
+
// Set up a configured tool with apply-change skill (which is in core profile)
|
|
523
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
524
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-apply-change'), {
|
|
525
|
+
recursive: true,
|
|
526
|
+
});
|
|
527
|
+
await fs.writeFile(
|
|
528
|
+
path.join(skillsDir, 'openspec-apply-change', 'SKILL.md'),
|
|
529
|
+
'old'
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
await updateCommand.execute(testDir);
|
|
533
|
+
|
|
534
|
+
const skillContent = await fs.readFile(
|
|
535
|
+
path.join(skillsDir, 'openspec-apply-change', 'SKILL.md'),
|
|
536
|
+
'utf-8'
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
// Apply skill should contain implementation instructions
|
|
540
|
+
expect(skillContent.toLowerCase()).toContain('task');
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
describe('success output', () => {
|
|
545
|
+
it('should display success message with tool name', async () => {
|
|
546
|
+
// Set up a configured tool
|
|
547
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
548
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
549
|
+
recursive: true,
|
|
550
|
+
});
|
|
551
|
+
await fs.writeFile(
|
|
552
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
553
|
+
'old'
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
557
|
+
|
|
558
|
+
await updateCommand.execute(testDir);
|
|
559
|
+
|
|
560
|
+
// The success output uses "✓ Updated: <name>"
|
|
561
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
562
|
+
expect.stringContaining('Updated: Claude Code')
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
consoleSpy.mockRestore();
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should suggest IDE restart after update', async () => {
|
|
569
|
+
// Set up a configured tool
|
|
570
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
571
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
572
|
+
recursive: true,
|
|
573
|
+
});
|
|
574
|
+
await fs.writeFile(
|
|
575
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
576
|
+
'old'
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
580
|
+
|
|
581
|
+
await updateCommand.execute(testDir);
|
|
582
|
+
|
|
583
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
584
|
+
expect.stringContaining('Restart your IDE')
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
consoleSpy.mockRestore();
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
describe('smart update detection', () => {
|
|
592
|
+
it('should show "up to date" message when skills have current version', async () => {
|
|
593
|
+
// Initialize full core profile output so there is no profile/delivery drift.
|
|
594
|
+
const initCommand = new InitCommand({ tools: 'claude', force: true });
|
|
595
|
+
await initCommand.execute(testDir);
|
|
596
|
+
|
|
597
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
598
|
+
|
|
599
|
+
await updateCommand.execute(testDir);
|
|
600
|
+
|
|
601
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
602
|
+
expect.stringContaining('up to date')
|
|
603
|
+
);
|
|
604
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
605
|
+
expect.stringContaining('--force')
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
consoleSpy.mockRestore();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('should detect update needed when generatedBy is missing', async () => {
|
|
612
|
+
// Set up a configured tool without generatedBy
|
|
613
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
614
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
615
|
+
recursive: true,
|
|
616
|
+
});
|
|
617
|
+
await fs.writeFile(
|
|
618
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
619
|
+
`---
|
|
620
|
+
name: openspec-explore
|
|
621
|
+
metadata:
|
|
622
|
+
author: openspec
|
|
623
|
+
version: "1.0"
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
Legacy content without generatedBy
|
|
627
|
+
`
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
631
|
+
|
|
632
|
+
await updateCommand.execute(testDir);
|
|
633
|
+
|
|
634
|
+
// Should show "unknown → version" in the update message
|
|
635
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
636
|
+
expect.stringContaining('unknown')
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
consoleSpy.mockRestore();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('should detect update needed when version differs', async () => {
|
|
643
|
+
// Set up a configured tool with old version
|
|
644
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
645
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
646
|
+
recursive: true,
|
|
647
|
+
});
|
|
648
|
+
await fs.writeFile(
|
|
649
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
650
|
+
`---
|
|
651
|
+
name: openspec-explore
|
|
652
|
+
metadata:
|
|
653
|
+
generatedBy: "0.1.0"
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
Old version content
|
|
657
|
+
`
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
661
|
+
|
|
662
|
+
await updateCommand.execute(testDir);
|
|
663
|
+
|
|
664
|
+
// Should show version transition
|
|
665
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
666
|
+
expect.stringContaining('0.1.0')
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
consoleSpy.mockRestore();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('should embed generatedBy in updated skill files', async () => {
|
|
673
|
+
// Set up a configured tool without generatedBy
|
|
674
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
675
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
676
|
+
recursive: true,
|
|
677
|
+
});
|
|
678
|
+
await fs.writeFile(
|
|
679
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
680
|
+
'old content without version'
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
await updateCommand.execute(testDir);
|
|
684
|
+
|
|
685
|
+
const updatedContent = await fs.readFile(
|
|
686
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
687
|
+
'utf-8'
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
// Should contain generatedBy field
|
|
691
|
+
expect(updatedContent).toMatch(/generatedBy:\s*["']\d+\.\d+\.\d+["']/);
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
describe('--force flag', () => {
|
|
696
|
+
it('should update when force is true even if up to date', async () => {
|
|
697
|
+
// Set up a configured tool with current version
|
|
698
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
699
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
700
|
+
recursive: true,
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
const { version } = await import('../../package.json');
|
|
704
|
+
await fs.writeFile(
|
|
705
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
706
|
+
`---
|
|
707
|
+
metadata:
|
|
708
|
+
generatedBy: "${version}"
|
|
709
|
+
---
|
|
710
|
+
Content
|
|
711
|
+
`
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
715
|
+
|
|
716
|
+
// Create update command with force option
|
|
717
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
718
|
+
await forceUpdateCommand.execute(testDir);
|
|
719
|
+
|
|
720
|
+
// Should show "Force updating" message
|
|
721
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
722
|
+
expect.stringContaining('Force updating')
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
// Should show updated message
|
|
726
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
727
|
+
expect.stringContaining('Updated: Claude Code')
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
consoleSpy.mockRestore();
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('should not show --force hint when force is used', async () => {
|
|
734
|
+
// Set up a configured tool
|
|
735
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
736
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
737
|
+
recursive: true,
|
|
738
|
+
});
|
|
739
|
+
await fs.writeFile(
|
|
740
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
741
|
+
'old content'
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
745
|
+
|
|
746
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
747
|
+
await forceUpdateCommand.execute(testDir);
|
|
748
|
+
|
|
749
|
+
// Get all console.log calls as strings
|
|
750
|
+
const allCalls = consoleSpy.mock.calls.map(call =>
|
|
751
|
+
call.map(arg => String(arg)).join(' ')
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
// Should not show "Use --force" since force was used
|
|
755
|
+
const hasForceHint = allCalls.some(call => call.includes('Use --force'));
|
|
756
|
+
expect(hasForceHint).toBe(false);
|
|
757
|
+
|
|
758
|
+
consoleSpy.mockRestore();
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('should update all tools when force is used with mixed versions', async () => {
|
|
762
|
+
// Set up Claude with current version
|
|
763
|
+
const { version } = await import('../../package.json');
|
|
764
|
+
const claudeSkillDir = path.join(testDir, '.claude', 'skills', 'openspec-explore');
|
|
765
|
+
await fs.mkdir(claudeSkillDir, { recursive: true });
|
|
766
|
+
await fs.writeFile(
|
|
767
|
+
path.join(claudeSkillDir, 'SKILL.md'),
|
|
768
|
+
`---
|
|
769
|
+
metadata:
|
|
770
|
+
generatedBy: "${version}"
|
|
771
|
+
---
|
|
772
|
+
`
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
// Set up Cursor with old version
|
|
776
|
+
const cursorSkillDir = path.join(testDir, '.cursor', 'skills', 'openspec-explore');
|
|
777
|
+
await fs.mkdir(cursorSkillDir, { recursive: true });
|
|
778
|
+
await fs.writeFile(
|
|
779
|
+
path.join(cursorSkillDir, 'SKILL.md'),
|
|
780
|
+
`---
|
|
781
|
+
metadata:
|
|
782
|
+
generatedBy: "0.1.0"
|
|
783
|
+
---
|
|
784
|
+
`
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
788
|
+
|
|
789
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
790
|
+
await forceUpdateCommand.execute(testDir);
|
|
791
|
+
|
|
792
|
+
// Should show both tools being force updated
|
|
793
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
794
|
+
expect.stringContaining('Force updating 2 tool(s)')
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
consoleSpy.mockRestore();
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
describe('version tracking', () => {
|
|
802
|
+
it('should show version in success message', async () => {
|
|
803
|
+
// Set up a configured tool
|
|
804
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
805
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
806
|
+
recursive: true,
|
|
807
|
+
});
|
|
808
|
+
await fs.writeFile(
|
|
809
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
810
|
+
'old'
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
814
|
+
|
|
815
|
+
await updateCommand.execute(testDir);
|
|
816
|
+
|
|
817
|
+
// Should show version in success message
|
|
818
|
+
const { version } = await import('../../package.json');
|
|
819
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
820
|
+
expect.stringContaining(`(v${version})`)
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
consoleSpy.mockRestore();
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it('should only update tools that need updating', async () => {
|
|
827
|
+
// Initialize both tools so Cursor is fully synced with profile/delivery.
|
|
828
|
+
const initCommand = new InitCommand({ tools: 'claude,cursor', force: true });
|
|
829
|
+
await initCommand.execute(testDir);
|
|
830
|
+
|
|
831
|
+
// Make Claude stale to force a version update.
|
|
832
|
+
const claudeSkillFile = path.join(testDir, '.claude', 'skills', 'openspec-explore', 'SKILL.md');
|
|
833
|
+
const claudeContent = await fs.readFile(claudeSkillFile, 'utf-8');
|
|
834
|
+
await fs.writeFile(
|
|
835
|
+
claudeSkillFile,
|
|
836
|
+
claudeContent.replace(/generatedBy:\s*["'][^"']+["']/, 'generatedBy: "0.1.0"')
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
840
|
+
|
|
841
|
+
await updateCommand.execute(testDir);
|
|
842
|
+
|
|
843
|
+
// Should show only Claude being updated
|
|
844
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
845
|
+
expect.stringContaining('Updating 1 tool(s)')
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
// Should mention Cursor is already up to date
|
|
849
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
850
|
+
expect.stringContaining('Already up to date: cursor')
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
consoleSpy.mockRestore();
|
|
854
|
+
});
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
describe('legacy cleanup', () => {
|
|
858
|
+
it('should detect and auto-cleanup legacy files with --force flag', async () => {
|
|
859
|
+
// Set up a configured tool
|
|
860
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
861
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
862
|
+
recursive: true,
|
|
863
|
+
});
|
|
864
|
+
await fs.writeFile(
|
|
865
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
866
|
+
'old'
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
// Create legacy CLAUDE.md with OpenSpec markers
|
|
870
|
+
const legacyContent = `${OPENSPEC_MARKERS.start}
|
|
871
|
+
# OpenSpec Instructions
|
|
872
|
+
|
|
873
|
+
These instructions are for AI assistants.
|
|
874
|
+
${OPENSPEC_MARKERS.end}
|
|
875
|
+
`;
|
|
876
|
+
await fs.writeFile(path.join(testDir, 'CLAUDE.md'), legacyContent);
|
|
877
|
+
|
|
878
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
879
|
+
|
|
880
|
+
// Create update command with force option
|
|
881
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
882
|
+
await forceUpdateCommand.execute(testDir);
|
|
883
|
+
|
|
884
|
+
// Should show v1 upgrade message
|
|
885
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
886
|
+
expect.stringContaining('Upgrading to the new OpenSpec')
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
// Should show marker removal message (config files are never deleted, only have markers removed)
|
|
890
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
891
|
+
expect.stringContaining('Removed OpenSpec markers from CLAUDE.md')
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
// Config file should still exist (never deleted)
|
|
895
|
+
const legacyExists = await FileSystemUtils.fileExists(
|
|
896
|
+
path.join(testDir, 'CLAUDE.md')
|
|
897
|
+
);
|
|
898
|
+
expect(legacyExists).toBe(true);
|
|
899
|
+
|
|
900
|
+
// File should have markers removed
|
|
901
|
+
const content = await fs.readFile(path.join(testDir, 'CLAUDE.md'), 'utf-8');
|
|
902
|
+
expect(content).not.toContain(OPENSPEC_MARKERS.start);
|
|
903
|
+
expect(content).not.toContain(OPENSPEC_MARKERS.end);
|
|
904
|
+
|
|
905
|
+
consoleSpy.mockRestore();
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
it('should warn but continue with update when legacy files found in non-interactive mode', async () => {
|
|
909
|
+
// Set up a configured tool
|
|
910
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
911
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
912
|
+
recursive: true,
|
|
913
|
+
});
|
|
914
|
+
await fs.writeFile(
|
|
915
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
916
|
+
'old'
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
// Create legacy CLAUDE.md with OpenSpec markers
|
|
920
|
+
const legacyContent = `${OPENSPEC_MARKERS.start}
|
|
921
|
+
# OpenSpec Instructions
|
|
922
|
+
${OPENSPEC_MARKERS.end}
|
|
923
|
+
`;
|
|
924
|
+
await fs.writeFile(path.join(testDir, 'CLAUDE.md'), legacyContent);
|
|
925
|
+
|
|
926
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
927
|
+
|
|
928
|
+
// Run without --force in non-interactive mode (CI environment)
|
|
929
|
+
await updateCommand.execute(testDir);
|
|
930
|
+
|
|
931
|
+
// Should show v1 upgrade message
|
|
932
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
933
|
+
expect.stringContaining('Upgrading to the new OpenSpec')
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
// Should show warning about --force
|
|
937
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
938
|
+
expect.stringContaining('Run with --force to auto-cleanup')
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
// Should continue with update
|
|
942
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
943
|
+
expect.stringContaining('Updated: Claude Code')
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
// Legacy file should still exist (not cleaned up)
|
|
947
|
+
const legacyExists = await FileSystemUtils.fileExists(
|
|
948
|
+
path.join(testDir, 'CLAUDE.md')
|
|
949
|
+
);
|
|
950
|
+
expect(legacyExists).toBe(true);
|
|
951
|
+
|
|
952
|
+
consoleSpy.mockRestore();
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
it('should cleanup legacy slash command directories with --force', async () => {
|
|
956
|
+
// Set up a configured tool
|
|
957
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
958
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
959
|
+
recursive: true,
|
|
960
|
+
});
|
|
961
|
+
await fs.writeFile(
|
|
962
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
963
|
+
'old'
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
// Create legacy slash command directory
|
|
967
|
+
const legacyCommandDir = path.join(testDir, '.claude', 'commands', 'openspec');
|
|
968
|
+
await fs.mkdir(legacyCommandDir, { recursive: true });
|
|
969
|
+
await fs.writeFile(
|
|
970
|
+
path.join(legacyCommandDir, 'old-command.md'),
|
|
971
|
+
'old command'
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
975
|
+
|
|
976
|
+
// Create update command with force option
|
|
977
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
978
|
+
await forceUpdateCommand.execute(testDir);
|
|
979
|
+
|
|
980
|
+
// Should show cleanup message for directory
|
|
981
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
982
|
+
expect.stringContaining('Removed .claude/commands/openspec/')
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
// Legacy directory should be deleted
|
|
986
|
+
const legacyDirExists = await FileSystemUtils.directoryExists(legacyCommandDir);
|
|
987
|
+
expect(legacyDirExists).toBe(false);
|
|
988
|
+
|
|
989
|
+
consoleSpy.mockRestore();
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
it('should cleanup legacy openspec/AGENTS.md with --force', async () => {
|
|
993
|
+
// Set up a configured tool
|
|
994
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
995
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
996
|
+
recursive: true,
|
|
997
|
+
});
|
|
998
|
+
await fs.writeFile(
|
|
999
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
1000
|
+
'old'
|
|
1001
|
+
);
|
|
1002
|
+
|
|
1003
|
+
// Create legacy openspec/AGENTS.md
|
|
1004
|
+
await fs.writeFile(
|
|
1005
|
+
path.join(testDir, 'openspec', 'AGENTS.md'),
|
|
1006
|
+
'# Old AGENTS.md content'
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1010
|
+
|
|
1011
|
+
// Create update command with force option
|
|
1012
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1013
|
+
await forceUpdateCommand.execute(testDir);
|
|
1014
|
+
|
|
1015
|
+
// Should show cleanup message
|
|
1016
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1017
|
+
expect.stringContaining('Removed openspec/AGENTS.md')
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
// Legacy file should be deleted
|
|
1021
|
+
const legacyExists = await FileSystemUtils.fileExists(
|
|
1022
|
+
path.join(testDir, 'openspec', 'AGENTS.md')
|
|
1023
|
+
);
|
|
1024
|
+
expect(legacyExists).toBe(false);
|
|
1025
|
+
|
|
1026
|
+
consoleSpy.mockRestore();
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
it('should not show legacy cleanup messages when no legacy files exist', async () => {
|
|
1030
|
+
// Set up a configured tool with no legacy files
|
|
1031
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1032
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
1033
|
+
recursive: true,
|
|
1034
|
+
});
|
|
1035
|
+
await fs.writeFile(
|
|
1036
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
1037
|
+
'old'
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1041
|
+
|
|
1042
|
+
await updateCommand.execute(testDir);
|
|
1043
|
+
|
|
1044
|
+
// Should not show v1 upgrade message (no legacy files)
|
|
1045
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1046
|
+
call.map(arg => String(arg)).join(' ')
|
|
1047
|
+
);
|
|
1048
|
+
const hasLegacyMessage = calls.some(call =>
|
|
1049
|
+
call.includes('Upgrading to the new OpenSpec')
|
|
1050
|
+
);
|
|
1051
|
+
expect(hasLegacyMessage).toBe(false);
|
|
1052
|
+
|
|
1053
|
+
consoleSpy.mockRestore();
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
it('should remove OpenSpec marker block from mixed content files', async () => {
|
|
1057
|
+
// Set up a configured tool
|
|
1058
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1059
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), {
|
|
1060
|
+
recursive: true,
|
|
1061
|
+
});
|
|
1062
|
+
await fs.writeFile(
|
|
1063
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
1064
|
+
'old'
|
|
1065
|
+
);
|
|
1066
|
+
|
|
1067
|
+
// Create CLAUDE.md with mixed content (user content + OpenSpec markers)
|
|
1068
|
+
const mixedContent = `# My Project
|
|
1069
|
+
|
|
1070
|
+
Some user-defined instructions here.
|
|
1071
|
+
|
|
1072
|
+
${OPENSPEC_MARKERS.start}
|
|
1073
|
+
# OpenSpec Instructions
|
|
1074
|
+
|
|
1075
|
+
These instructions are for AI assistants.
|
|
1076
|
+
${OPENSPEC_MARKERS.end}
|
|
1077
|
+
|
|
1078
|
+
More user content after markers.
|
|
1079
|
+
`;
|
|
1080
|
+
await fs.writeFile(path.join(testDir, 'CLAUDE.md'), mixedContent);
|
|
1081
|
+
|
|
1082
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1083
|
+
|
|
1084
|
+
// Create update command with force option
|
|
1085
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1086
|
+
await forceUpdateCommand.execute(testDir);
|
|
1087
|
+
|
|
1088
|
+
// Should show marker removal message
|
|
1089
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1090
|
+
expect.stringContaining('Removed OpenSpec markers from CLAUDE.md')
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
// File should still exist
|
|
1094
|
+
const fileExists = await FileSystemUtils.fileExists(
|
|
1095
|
+
path.join(testDir, 'CLAUDE.md')
|
|
1096
|
+
);
|
|
1097
|
+
expect(fileExists).toBe(true);
|
|
1098
|
+
|
|
1099
|
+
// File should have markers removed but preserve user content
|
|
1100
|
+
const updatedContent = await fs.readFile(
|
|
1101
|
+
path.join(testDir, 'CLAUDE.md'),
|
|
1102
|
+
'utf-8'
|
|
1103
|
+
);
|
|
1104
|
+
expect(updatedContent).toContain('# My Project');
|
|
1105
|
+
expect(updatedContent).toContain('Some user-defined instructions here');
|
|
1106
|
+
expect(updatedContent).toContain('More user content after markers');
|
|
1107
|
+
expect(updatedContent).not.toContain(OPENSPEC_MARKERS.start);
|
|
1108
|
+
expect(updatedContent).not.toContain(OPENSPEC_MARKERS.end);
|
|
1109
|
+
|
|
1110
|
+
consoleSpy.mockRestore();
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
describe('legacy tool upgrade', () => {
|
|
1115
|
+
it('should upgrade legacy tools to new skills with --force', async () => {
|
|
1116
|
+
// Create legacy slash command directory (no skills exist yet)
|
|
1117
|
+
const legacyCommandDir = path.join(testDir, '.claude', 'commands', 'openspec');
|
|
1118
|
+
await fs.mkdir(legacyCommandDir, { recursive: true });
|
|
1119
|
+
await fs.writeFile(
|
|
1120
|
+
path.join(legacyCommandDir, 'proposal.md'),
|
|
1121
|
+
'old command content'
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1125
|
+
|
|
1126
|
+
// Create update command with force option
|
|
1127
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1128
|
+
await forceUpdateCommand.execute(testDir);
|
|
1129
|
+
|
|
1130
|
+
// Should show detected tools message
|
|
1131
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1132
|
+
expect.stringContaining('Tools detected from legacy artifacts')
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
// Should show Claude Code being set up
|
|
1136
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1137
|
+
expect.stringContaining('Claude Code')
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
// Should show getting started message for newly configured tools
|
|
1141
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1142
|
+
expect.stringContaining('Getting started')
|
|
1143
|
+
);
|
|
1144
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1145
|
+
expect.stringContaining('/opsx:new')
|
|
1146
|
+
);
|
|
1147
|
+
|
|
1148
|
+
// Skills should be created
|
|
1149
|
+
const skillFile = path.join(testDir, '.claude', 'skills', 'openspec-explore', 'SKILL.md');
|
|
1150
|
+
const skillExists = await FileSystemUtils.fileExists(skillFile);
|
|
1151
|
+
expect(skillExists).toBe(true);
|
|
1152
|
+
|
|
1153
|
+
// Legacy directory should be deleted
|
|
1154
|
+
const legacyDirExists = await FileSystemUtils.directoryExists(legacyCommandDir);
|
|
1155
|
+
expect(legacyDirExists).toBe(false);
|
|
1156
|
+
|
|
1157
|
+
consoleSpy.mockRestore();
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
it('should upgrade multiple legacy tools with --force', async () => {
|
|
1161
|
+
// Create legacy command directories for Claude and Cursor
|
|
1162
|
+
await fs.mkdir(path.join(testDir, '.claude', 'commands', 'openspec'), { recursive: true });
|
|
1163
|
+
await fs.writeFile(
|
|
1164
|
+
path.join(testDir, '.claude', 'commands', 'openspec', 'proposal.md'),
|
|
1165
|
+
'content'
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
await fs.mkdir(path.join(testDir, '.cursor', 'commands'), { recursive: true });
|
|
1169
|
+
await fs.writeFile(
|
|
1170
|
+
path.join(testDir, '.cursor', 'commands', 'openspec-proposal.md'),
|
|
1171
|
+
'content'
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1175
|
+
|
|
1176
|
+
// Create update command with force option
|
|
1177
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1178
|
+
await forceUpdateCommand.execute(testDir);
|
|
1179
|
+
|
|
1180
|
+
// Should detect both tools
|
|
1181
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1182
|
+
expect.stringContaining('Tools detected from legacy artifacts')
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
// Both tools should have skills created
|
|
1186
|
+
const claudeSkillFile = path.join(testDir, '.claude', 'skills', 'openspec-explore', 'SKILL.md');
|
|
1187
|
+
const cursorSkillFile = path.join(testDir, '.cursor', 'skills', 'openspec-explore', 'SKILL.md');
|
|
1188
|
+
|
|
1189
|
+
expect(await FileSystemUtils.fileExists(claudeSkillFile)).toBe(true);
|
|
1190
|
+
expect(await FileSystemUtils.fileExists(cursorSkillFile)).toBe(true);
|
|
1191
|
+
|
|
1192
|
+
consoleSpy.mockRestore();
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
it('should not upgrade legacy tools already configured', async () => {
|
|
1196
|
+
// Set up a configured Claude tool with skills
|
|
1197
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1198
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1199
|
+
await fs.writeFile(
|
|
1200
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
1201
|
+
'existing skill'
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
// Also create legacy directory (simulating partial upgrade)
|
|
1205
|
+
const legacyCommandDir = path.join(testDir, '.claude', 'commands', 'openspec');
|
|
1206
|
+
await fs.mkdir(legacyCommandDir, { recursive: true });
|
|
1207
|
+
await fs.writeFile(
|
|
1208
|
+
path.join(legacyCommandDir, 'proposal.md'),
|
|
1209
|
+
'old command'
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1213
|
+
|
|
1214
|
+
// Create update command with force option
|
|
1215
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1216
|
+
await forceUpdateCommand.execute(testDir);
|
|
1217
|
+
|
|
1218
|
+
// Legacy cleanup should happen
|
|
1219
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1220
|
+
expect.stringContaining('Removed .claude/commands/openspec/')
|
|
1221
|
+
);
|
|
1222
|
+
|
|
1223
|
+
// Should NOT show "Tools detected from legacy artifacts" because claude is already configured
|
|
1224
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1225
|
+
call.map(arg => String(arg)).join(' ')
|
|
1226
|
+
);
|
|
1227
|
+
const hasDetectedMessage = calls.some(call =>
|
|
1228
|
+
call.includes('Tools detected from legacy artifacts')
|
|
1229
|
+
);
|
|
1230
|
+
expect(hasDetectedMessage).toBe(false);
|
|
1231
|
+
|
|
1232
|
+
// Should update existing skills (not "Getting started" for newly configured)
|
|
1233
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1234
|
+
expect.stringContaining('Updated: Claude Code')
|
|
1235
|
+
);
|
|
1236
|
+
|
|
1237
|
+
consoleSpy.mockRestore();
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
it('should upgrade only unconfigured legacy tools when mixed', async () => {
|
|
1241
|
+
// Set up configured Claude tool with skills
|
|
1242
|
+
const claudeSkillsDir = path.join(testDir, '.claude', 'skills');
|
|
1243
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-explore'), { recursive: true });
|
|
1244
|
+
await fs.writeFile(
|
|
1245
|
+
path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'),
|
|
1246
|
+
'existing skill'
|
|
1247
|
+
);
|
|
1248
|
+
|
|
1249
|
+
// Create legacy commands for both Claude (configured) and Cursor (not configured)
|
|
1250
|
+
await fs.mkdir(path.join(testDir, '.claude', 'commands', 'openspec'), { recursive: true });
|
|
1251
|
+
await fs.writeFile(
|
|
1252
|
+
path.join(testDir, '.claude', 'commands', 'openspec', 'proposal.md'),
|
|
1253
|
+
'content'
|
|
1254
|
+
);
|
|
1255
|
+
|
|
1256
|
+
await fs.mkdir(path.join(testDir, '.cursor', 'commands'), { recursive: true });
|
|
1257
|
+
await fs.writeFile(
|
|
1258
|
+
path.join(testDir, '.cursor', 'commands', 'openspec-proposal.md'),
|
|
1259
|
+
'content'
|
|
1260
|
+
);
|
|
1261
|
+
|
|
1262
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1263
|
+
|
|
1264
|
+
// Create update command with force option
|
|
1265
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1266
|
+
await forceUpdateCommand.execute(testDir);
|
|
1267
|
+
|
|
1268
|
+
// Should detect Cursor as a legacy tool to upgrade (but not Claude)
|
|
1269
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1270
|
+
expect.stringContaining('Tools detected from legacy artifacts')
|
|
1271
|
+
);
|
|
1272
|
+
|
|
1273
|
+
// Cursor skills should be created
|
|
1274
|
+
const cursorSkillFile = path.join(testDir, '.cursor', 'skills', 'openspec-explore', 'SKILL.md');
|
|
1275
|
+
expect(await FileSystemUtils.fileExists(cursorSkillFile)).toBe(true);
|
|
1276
|
+
|
|
1277
|
+
// Should show "Getting started" for newly configured Cursor
|
|
1278
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1279
|
+
expect.stringContaining('Getting started')
|
|
1280
|
+
);
|
|
1281
|
+
|
|
1282
|
+
consoleSpy.mockRestore();
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
it('should not show getting started message when no new tools configured', async () => {
|
|
1286
|
+
// Set up a configured tool (no legacy artifacts)
|
|
1287
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1288
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1289
|
+
await fs.writeFile(
|
|
1290
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
1291
|
+
'old skill'
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1295
|
+
|
|
1296
|
+
await updateCommand.execute(testDir);
|
|
1297
|
+
|
|
1298
|
+
// Should NOT show "Getting started" message
|
|
1299
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1300
|
+
call.map(arg => String(arg)).join(' ')
|
|
1301
|
+
);
|
|
1302
|
+
const hasGettingStarted = calls.some(call =>
|
|
1303
|
+
call.includes('Getting started')
|
|
1304
|
+
);
|
|
1305
|
+
expect(hasGettingStarted).toBe(false);
|
|
1306
|
+
|
|
1307
|
+
consoleSpy.mockRestore();
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
it('should create only effective profile skills when upgrading legacy tools', async () => {
|
|
1311
|
+
// Create legacy command directory
|
|
1312
|
+
await fs.mkdir(path.join(testDir, '.claude', 'commands', 'openspec'), { recursive: true });
|
|
1313
|
+
await fs.writeFile(
|
|
1314
|
+
path.join(testDir, '.claude', 'commands', 'openspec', 'proposal.md'),
|
|
1315
|
+
'content'
|
|
1316
|
+
);
|
|
1317
|
+
|
|
1318
|
+
// Create update command with force option
|
|
1319
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1320
|
+
await forceUpdateCommand.execute(testDir);
|
|
1321
|
+
|
|
1322
|
+
// Default profile is core, so only core workflows should be generated.
|
|
1323
|
+
const skillNames = [
|
|
1324
|
+
'openspec-propose',
|
|
1325
|
+
'openspec-explore',
|
|
1326
|
+
'openspec-apply-change',
|
|
1327
|
+
'openspec-sync-specs',
|
|
1328
|
+
'openspec-archive-change',
|
|
1329
|
+
];
|
|
1330
|
+
|
|
1331
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1332
|
+
for (const skillName of skillNames) {
|
|
1333
|
+
const skillFile = path.join(skillsDir, skillName, 'SKILL.md');
|
|
1334
|
+
const exists = await FileSystemUtils.fileExists(skillFile);
|
|
1335
|
+
expect(exists).toBe(true);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const nonCoreSkill = path.join(skillsDir, 'openspec-new-change', 'SKILL.md');
|
|
1339
|
+
expect(await FileSystemUtils.fileExists(nonCoreSkill)).toBe(false);
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
it('should create commands when upgrading legacy tools', async () => {
|
|
1343
|
+
// Create legacy command directory
|
|
1344
|
+
await fs.mkdir(path.join(testDir, '.claude', 'commands', 'openspec'), { recursive: true });
|
|
1345
|
+
await fs.writeFile(
|
|
1346
|
+
path.join(testDir, '.claude', 'commands', 'openspec', 'proposal.md'),
|
|
1347
|
+
'content'
|
|
1348
|
+
);
|
|
1349
|
+
|
|
1350
|
+
// Create update command with force option
|
|
1351
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1352
|
+
await forceUpdateCommand.execute(testDir);
|
|
1353
|
+
|
|
1354
|
+
// New opsx commands should be created
|
|
1355
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
1356
|
+
const exploreCmd = path.join(commandsDir, 'explore.md');
|
|
1357
|
+
const exists = await FileSystemUtils.fileExists(exploreCmd);
|
|
1358
|
+
expect(exists).toBe(true);
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
it('should not inject non-profile workflows when upgrading legacy tools', async () => {
|
|
1362
|
+
setMockConfig({
|
|
1363
|
+
featureFlags: {},
|
|
1364
|
+
profile: 'custom',
|
|
1365
|
+
delivery: 'both',
|
|
1366
|
+
workflows: ['explore'],
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
await fs.mkdir(path.join(testDir, '.claude', 'commands', 'openspec'), { recursive: true });
|
|
1370
|
+
await fs.writeFile(
|
|
1371
|
+
path.join(testDir, '.claude', 'commands', 'openspec', 'proposal.md'),
|
|
1372
|
+
'content'
|
|
1373
|
+
);
|
|
1374
|
+
|
|
1375
|
+
const forceUpdateCommand = new UpdateCommand({ force: true });
|
|
1376
|
+
await forceUpdateCommand.execute(testDir);
|
|
1377
|
+
|
|
1378
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1379
|
+
expect(await FileSystemUtils.fileExists(
|
|
1380
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md')
|
|
1381
|
+
)).toBe(true);
|
|
1382
|
+
expect(await FileSystemUtils.fileExists(
|
|
1383
|
+
path.join(skillsDir, 'openspec-propose', 'SKILL.md')
|
|
1384
|
+
)).toBe(false);
|
|
1385
|
+
|
|
1386
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
1387
|
+
expect(await FileSystemUtils.fileExists(
|
|
1388
|
+
path.join(commandsDir, 'explore.md')
|
|
1389
|
+
)).toBe(true);
|
|
1390
|
+
expect(await FileSystemUtils.fileExists(
|
|
1391
|
+
path.join(commandsDir, 'propose.md')
|
|
1392
|
+
)).toBe(false);
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
describe('profile-aware updates', () => {
|
|
1397
|
+
it('should generate only profile workflows when custom profile is set', async () => {
|
|
1398
|
+
// Set custom profile with only explore and new
|
|
1399
|
+
setMockConfig({
|
|
1400
|
+
featureFlags: {},
|
|
1401
|
+
profile: 'custom',
|
|
1402
|
+
delivery: 'both',
|
|
1403
|
+
workflows: ['explore', 'new'],
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
// Set up a configured tool
|
|
1407
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1408
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1409
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1410
|
+
|
|
1411
|
+
await updateCommand.execute(testDir);
|
|
1412
|
+
|
|
1413
|
+
// Should create explore and new skills
|
|
1414
|
+
expect(await FileSystemUtils.fileExists(
|
|
1415
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md')
|
|
1416
|
+
)).toBe(true);
|
|
1417
|
+
expect(await FileSystemUtils.fileExists(
|
|
1418
|
+
path.join(skillsDir, 'openspec-new-change', 'SKILL.md')
|
|
1419
|
+
)).toBe(true);
|
|
1420
|
+
|
|
1421
|
+
// Should NOT create non-profile skills
|
|
1422
|
+
expect(await FileSystemUtils.fileExists(
|
|
1423
|
+
path.join(skillsDir, 'openspec-apply-change', 'SKILL.md')
|
|
1424
|
+
)).toBe(false);
|
|
1425
|
+
expect(await FileSystemUtils.fileExists(
|
|
1426
|
+
path.join(skillsDir, 'openspec-propose', 'SKILL.md')
|
|
1427
|
+
)).toBe(false);
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
it('should suggest core preset when custom profile preserves the old core workflow set', async () => {
|
|
1431
|
+
setMockConfig({
|
|
1432
|
+
featureFlags: {},
|
|
1433
|
+
profile: 'custom',
|
|
1434
|
+
delivery: 'both',
|
|
1435
|
+
workflows: ['propose', 'explore', 'apply', 'archive'],
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
const initCommand = new InitCommand({ tools: 'claude', force: true });
|
|
1439
|
+
await initCommand.execute(testDir);
|
|
1440
|
+
|
|
1441
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1442
|
+
|
|
1443
|
+
await updateCommand.execute(testDir);
|
|
1444
|
+
|
|
1445
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1446
|
+
call.map(arg => String(arg)).join(' ')
|
|
1447
|
+
);
|
|
1448
|
+
expect(calls.some(call =>
|
|
1449
|
+
call.includes('The core profile now includes sync')
|
|
1450
|
+
)).toBe(true);
|
|
1451
|
+
expect(calls.some(call =>
|
|
1452
|
+
call.includes('openspec config profile core') && call.includes('openspec update')
|
|
1453
|
+
)).toBe(true);
|
|
1454
|
+
|
|
1455
|
+
expect(await FileSystemUtils.fileExists(
|
|
1456
|
+
path.join(testDir, '.claude', 'skills', 'openspec-sync-specs', 'SKILL.md')
|
|
1457
|
+
)).toBe(false);
|
|
1458
|
+
expect(await FileSystemUtils.fileExists(
|
|
1459
|
+
path.join(testDir, '.claude', 'commands', 'opsx', 'sync.md')
|
|
1460
|
+
)).toBe(false);
|
|
1461
|
+
|
|
1462
|
+
consoleSpy.mockRestore();
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
it('should respect skills-only delivery setting', async () => {
|
|
1466
|
+
setMockConfig({
|
|
1467
|
+
featureFlags: {},
|
|
1468
|
+
profile: 'core',
|
|
1469
|
+
delivery: 'skills',
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1473
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1474
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1475
|
+
|
|
1476
|
+
await updateCommand.execute(testDir);
|
|
1477
|
+
|
|
1478
|
+
// Skills should be created
|
|
1479
|
+
expect(await FileSystemUtils.fileExists(
|
|
1480
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md')
|
|
1481
|
+
)).toBe(true);
|
|
1482
|
+
|
|
1483
|
+
// Commands should NOT be created
|
|
1484
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
1485
|
+
expect(await FileSystemUtils.fileExists(
|
|
1486
|
+
path.join(commandsDir, 'explore.md')
|
|
1487
|
+
)).toBe(false);
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
it('should respect commands-only delivery setting', async () => {
|
|
1491
|
+
setMockConfig({
|
|
1492
|
+
featureFlags: {},
|
|
1493
|
+
profile: 'core',
|
|
1494
|
+
delivery: 'commands',
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1498
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1499
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1500
|
+
|
|
1501
|
+
await updateCommand.execute(testDir);
|
|
1502
|
+
|
|
1503
|
+
// Commands should be created
|
|
1504
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
1505
|
+
expect(await FileSystemUtils.fileExists(
|
|
1506
|
+
path.join(commandsDir, 'explore.md')
|
|
1507
|
+
)).toBe(true);
|
|
1508
|
+
|
|
1509
|
+
// Skills should be removed for commands-only delivery
|
|
1510
|
+
expect(await FileSystemUtils.fileExists(
|
|
1511
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md')
|
|
1512
|
+
)).toBe(false);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
it('should remove skills for configured tools without command adapters in commands-only delivery', async () => {
|
|
1516
|
+
setMockConfig({
|
|
1517
|
+
featureFlags: {},
|
|
1518
|
+
profile: 'core',
|
|
1519
|
+
delivery: 'commands',
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
const { AI_TOOLS } = await import('../../src/core/config.js');
|
|
1523
|
+
const { CommandAdapterRegistry } = await import('../../src/core/command-generation/index.js');
|
|
1524
|
+
const adapterlessTool = AI_TOOLS.find((tool) => tool.skillsDir && !CommandAdapterRegistry.get(tool.value));
|
|
1525
|
+
expect(adapterlessTool).toBeDefined();
|
|
1526
|
+
if (!adapterlessTool?.skillsDir) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
const skillsDir = path.join(testDir, adapterlessTool.skillsDir, 'skills');
|
|
1531
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1532
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1533
|
+
|
|
1534
|
+
await expect(updateCommand.execute(testDir)).resolves.toBeUndefined();
|
|
1535
|
+
|
|
1536
|
+
expect(await FileSystemUtils.fileExists(
|
|
1537
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md')
|
|
1538
|
+
)).toBe(false);
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
it('should apply config sync when templates are up to date', async () => {
|
|
1542
|
+
setMockConfig({
|
|
1543
|
+
featureFlags: {},
|
|
1544
|
+
profile: 'core',
|
|
1545
|
+
delivery: 'skills',
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1549
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1550
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
1551
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')) as { version: string };
|
|
1552
|
+
await fs.writeFile(
|
|
1553
|
+
path.join(skillsDir, 'openspec-explore', 'SKILL.md'),
|
|
1554
|
+
`---
|
|
1555
|
+
name: openspec-explore
|
|
1556
|
+
metadata:
|
|
1557
|
+
generatedBy: "${packageJson.version}"
|
|
1558
|
+
---
|
|
1559
|
+
content
|
|
1560
|
+
`
|
|
1561
|
+
);
|
|
1562
|
+
|
|
1563
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
1564
|
+
await fs.mkdir(commandsDir, { recursive: true });
|
|
1565
|
+
await fs.writeFile(path.join(commandsDir, 'explore.md'), 'old command');
|
|
1566
|
+
|
|
1567
|
+
await updateCommand.execute(testDir);
|
|
1568
|
+
|
|
1569
|
+
// Command files should be removed due to delivery change, even though skill version is current
|
|
1570
|
+
expect(await FileSystemUtils.fileExists(
|
|
1571
|
+
path.join(commandsDir, 'explore.md')
|
|
1572
|
+
)).toBe(false);
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
it('should detect commands-only tool configuration', async () => {
|
|
1576
|
+
setMockConfig({
|
|
1577
|
+
featureFlags: {},
|
|
1578
|
+
profile: 'core',
|
|
1579
|
+
delivery: 'commands',
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
1583
|
+
await fs.mkdir(commandsDir, { recursive: true });
|
|
1584
|
+
await fs.writeFile(path.join(commandsDir, 'explore.md'), 'existing command');
|
|
1585
|
+
|
|
1586
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1587
|
+
|
|
1588
|
+
await updateCommand.execute(testDir);
|
|
1589
|
+
|
|
1590
|
+
// Should not short-circuit with "No configured tools found"
|
|
1591
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1592
|
+
call.map(arg => String(arg)).join(' ')
|
|
1593
|
+
);
|
|
1594
|
+
const hasNoConfiguredMessage = calls.some(call =>
|
|
1595
|
+
call.includes('No configured tools found')
|
|
1596
|
+
);
|
|
1597
|
+
expect(hasNoConfiguredMessage).toBe(false);
|
|
1598
|
+
|
|
1599
|
+
// Commands should be updated/generated for the core profile
|
|
1600
|
+
expect(await FileSystemUtils.fileExists(
|
|
1601
|
+
path.join(commandsDir, 'propose.md')
|
|
1602
|
+
)).toBe(true);
|
|
1603
|
+
|
|
1604
|
+
consoleSpy.mockRestore();
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1607
|
+
it('should remove workflows outside profile during update sync', async () => {
|
|
1608
|
+
// Set core profile (propose, explore, apply, sync, archive)
|
|
1609
|
+
setMockConfig({
|
|
1610
|
+
featureFlags: {},
|
|
1611
|
+
profile: 'core',
|
|
1612
|
+
delivery: 'both',
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
// Set up tool with extra workflows beyond core profile
|
|
1616
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1617
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1618
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1619
|
+
|
|
1620
|
+
// Add a non-core workflow
|
|
1621
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-new-change'), { recursive: true });
|
|
1622
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-new-change', 'SKILL.md'), 'old');
|
|
1623
|
+
const extraCommandFile = path.join(testDir, '.claude', 'commands', 'opsx', 'new.md');
|
|
1624
|
+
await fs.mkdir(path.dirname(extraCommandFile), { recursive: true });
|
|
1625
|
+
await fs.writeFile(extraCommandFile, 'old');
|
|
1626
|
+
|
|
1627
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1628
|
+
|
|
1629
|
+
await updateCommand.execute(testDir);
|
|
1630
|
+
|
|
1631
|
+
// Deselected workflow artifacts should be removed for both delivery surfaces.
|
|
1632
|
+
expect(await FileSystemUtils.fileExists(
|
|
1633
|
+
path.join(skillsDir, 'openspec-new-change', 'SKILL.md')
|
|
1634
|
+
)).toBe(false);
|
|
1635
|
+
expect(await FileSystemUtils.fileExists(extraCommandFile)).toBe(false);
|
|
1636
|
+
|
|
1637
|
+
// Should report deselected workflow cleanup.
|
|
1638
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1639
|
+
call.map(arg => String(arg)).join(' ')
|
|
1640
|
+
);
|
|
1641
|
+
const hasDeselectedRemovalNote = calls.some(call =>
|
|
1642
|
+
call.includes('deselected workflows')
|
|
1643
|
+
);
|
|
1644
|
+
expect(hasDeselectedRemovalNote).toBe(true);
|
|
1645
|
+
|
|
1646
|
+
consoleSpy.mockRestore();
|
|
1647
|
+
});
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
describe('new tool detection', () => {
|
|
1651
|
+
it('should detect new tool directories not currently configured', async () => {
|
|
1652
|
+
// Set up a configured Claude tool
|
|
1653
|
+
const claudeSkillsDir = path.join(testDir, '.claude', 'skills');
|
|
1654
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-explore'), { recursive: true });
|
|
1655
|
+
await fs.writeFile(path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1656
|
+
|
|
1657
|
+
// Create a Cursor directory (not configured — no skills)
|
|
1658
|
+
await fs.mkdir(path.join(testDir, '.cursor'), { recursive: true });
|
|
1659
|
+
|
|
1660
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1661
|
+
|
|
1662
|
+
await updateCommand.execute(testDir);
|
|
1663
|
+
|
|
1664
|
+
// Should detect Cursor as a new tool
|
|
1665
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1666
|
+
call.map(arg => String(arg)).join(' ')
|
|
1667
|
+
);
|
|
1668
|
+
const hasNewToolMessage = calls.some(call =>
|
|
1669
|
+
call.includes("Detected new tool: Cursor. Run 'openspec init' to add it.")
|
|
1670
|
+
);
|
|
1671
|
+
expect(hasNewToolMessage).toBe(true);
|
|
1672
|
+
|
|
1673
|
+
consoleSpy.mockRestore();
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
it('should consolidate multiple new tools into one message', async () => {
|
|
1677
|
+
// Set up a configured Claude tool
|
|
1678
|
+
const claudeSkillsDir = path.join(testDir, '.claude', 'skills');
|
|
1679
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-explore'), { recursive: true });
|
|
1680
|
+
await fs.writeFile(path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1681
|
+
|
|
1682
|
+
// Create two unconfigured tool directories
|
|
1683
|
+
await fs.mkdir(path.join(testDir, '.github'), { recursive: true });
|
|
1684
|
+
await fs.writeFile(path.join(testDir, '.github', 'copilot-instructions.md'), '');
|
|
1685
|
+
await fs.mkdir(path.join(testDir, '.windsurf'), { recursive: true });
|
|
1686
|
+
|
|
1687
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1688
|
+
|
|
1689
|
+
await updateCommand.execute(testDir);
|
|
1690
|
+
|
|
1691
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1692
|
+
call.map(arg => String(arg)).join(' ')
|
|
1693
|
+
);
|
|
1694
|
+
|
|
1695
|
+
const consolidatedCalls = calls.filter(call =>
|
|
1696
|
+
call.includes('Detected new tools:')
|
|
1697
|
+
);
|
|
1698
|
+
expect(consolidatedCalls).toHaveLength(1);
|
|
1699
|
+
expect(consolidatedCalls[0]).toContain('GitHub Copilot');
|
|
1700
|
+
expect(consolidatedCalls[0]).toContain('Windsurf');
|
|
1701
|
+
expect(consolidatedCalls[0]).toContain("Run 'openspec init' to add them.");
|
|
1702
|
+
|
|
1703
|
+
const repeatedSingularCalls = calls.filter(call =>
|
|
1704
|
+
call.includes('Detected new tool:')
|
|
1705
|
+
);
|
|
1706
|
+
expect(repeatedSingularCalls).toHaveLength(0);
|
|
1707
|
+
|
|
1708
|
+
consoleSpy.mockRestore();
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
it('should not show new tool message when no new tools detected', async () => {
|
|
1712
|
+
// Set up a configured tool (only Claude, no other tool directories)
|
|
1713
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1714
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1715
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1716
|
+
|
|
1717
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1718
|
+
|
|
1719
|
+
await updateCommand.execute(testDir);
|
|
1720
|
+
|
|
1721
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1722
|
+
call.map(arg => String(arg)).join(' ')
|
|
1723
|
+
);
|
|
1724
|
+
const hasNewToolMessage = calls.some(call =>
|
|
1725
|
+
call.includes('Detected new tool')
|
|
1726
|
+
);
|
|
1727
|
+
expect(hasNewToolMessage).toBe(false);
|
|
1728
|
+
|
|
1729
|
+
consoleSpy.mockRestore();
|
|
1730
|
+
});
|
|
1731
|
+
});
|
|
1732
|
+
|
|
1733
|
+
describe('scanInstalledWorkflows', () => {
|
|
1734
|
+
it('should detect installed workflows across tools', async () => {
|
|
1735
|
+
// Create skills for Claude
|
|
1736
|
+
const claudeSkillsDir = path.join(testDir, '.claude', 'skills');
|
|
1737
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-explore'), { recursive: true });
|
|
1738
|
+
await fs.writeFile(path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'), 'content');
|
|
1739
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-apply-change'), { recursive: true });
|
|
1740
|
+
await fs.writeFile(path.join(claudeSkillsDir, 'openspec-apply-change', 'SKILL.md'), 'content');
|
|
1741
|
+
|
|
1742
|
+
const workflows = scanInstalledWorkflows(testDir, ['claude']);
|
|
1743
|
+
expect(workflows).toContain('explore');
|
|
1744
|
+
expect(workflows).toContain('apply');
|
|
1745
|
+
expect(workflows).not.toContain('propose');
|
|
1746
|
+
});
|
|
1747
|
+
|
|
1748
|
+
it('should return union of workflows across multiple tools', async () => {
|
|
1749
|
+
// Claude has explore
|
|
1750
|
+
const claudeSkillsDir = path.join(testDir, '.claude', 'skills');
|
|
1751
|
+
await fs.mkdir(path.join(claudeSkillsDir, 'openspec-explore'), { recursive: true });
|
|
1752
|
+
await fs.writeFile(path.join(claudeSkillsDir, 'openspec-explore', 'SKILL.md'), 'content');
|
|
1753
|
+
|
|
1754
|
+
// Cursor has apply
|
|
1755
|
+
const cursorSkillsDir = path.join(testDir, '.cursor', 'skills');
|
|
1756
|
+
await fs.mkdir(path.join(cursorSkillsDir, 'openspec-apply-change'), { recursive: true });
|
|
1757
|
+
await fs.writeFile(path.join(cursorSkillsDir, 'openspec-apply-change', 'SKILL.md'), 'content');
|
|
1758
|
+
|
|
1759
|
+
const workflows = scanInstalledWorkflows(testDir, ['claude', 'cursor']);
|
|
1760
|
+
expect(workflows).toContain('explore');
|
|
1761
|
+
expect(workflows).toContain('apply');
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
it('should only match workflows in ALL_WORKFLOWS', async () => {
|
|
1765
|
+
// Create a custom skill directory that doesn't match any workflow
|
|
1766
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1767
|
+
await fs.mkdir(path.join(skillsDir, 'my-custom-skill'), { recursive: true });
|
|
1768
|
+
await fs.writeFile(path.join(skillsDir, 'my-custom-skill', 'SKILL.md'), 'content');
|
|
1769
|
+
|
|
1770
|
+
const workflows = scanInstalledWorkflows(testDir, ['claude']);
|
|
1771
|
+
expect(workflows).toHaveLength(0);
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
it('should return empty array when no tools have skills', async () => {
|
|
1775
|
+
const workflows = scanInstalledWorkflows(testDir, ['claude']);
|
|
1776
|
+
expect(workflows).toHaveLength(0);
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
it('should detect installed workflows from managed command files', async () => {
|
|
1780
|
+
const commandsDir = path.join(testDir, '.claude', 'commands', 'opsx');
|
|
1781
|
+
await fs.mkdir(commandsDir, { recursive: true });
|
|
1782
|
+
await fs.writeFile(path.join(commandsDir, 'explore.md'), 'content');
|
|
1783
|
+
|
|
1784
|
+
const workflows = scanInstalledWorkflows(testDir, ['claude']);
|
|
1785
|
+
expect(workflows).toContain('explore');
|
|
1786
|
+
});
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
describe('tools output', () => {
|
|
1790
|
+
it('should list affected tools in output', async () => {
|
|
1791
|
+
const skillsDir = path.join(testDir, '.claude', 'skills');
|
|
1792
|
+
await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true });
|
|
1793
|
+
await fs.writeFile(path.join(skillsDir, 'openspec-explore', 'SKILL.md'), 'old');
|
|
1794
|
+
|
|
1795
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
1796
|
+
|
|
1797
|
+
await updateCommand.execute(testDir);
|
|
1798
|
+
|
|
1799
|
+
const calls = consoleSpy.mock.calls.map(call =>
|
|
1800
|
+
call.map(arg => String(arg)).join(' ')
|
|
1801
|
+
);
|
|
1802
|
+
const hasToolsList = calls.some(call =>
|
|
1803
|
+
call.includes('Tools:') && call.includes('Claude Code')
|
|
1804
|
+
);
|
|
1805
|
+
expect(hasToolsList).toBe(true);
|
|
1806
|
+
|
|
1807
|
+
consoleSpy.mockRestore();
|
|
1808
|
+
});
|
|
1809
|
+
});
|
|
1810
|
+
});
|