openspecui 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitmodules +3 -0
- package/CHAT.md +3 -0
- package/package.json +12 -0
- package/references/openspec/.changeset/README.md +6 -0
- package/references/openspec/.changeset/config.json +12 -0
- package/references/openspec/.coderabbit.yaml +11 -0
- package/references/openspec/.devcontainer/README.md +92 -0
- package/references/openspec/.devcontainer/devcontainer.json +68 -0
- package/references/openspec/.github/CODEOWNERS +2 -0
- package/references/openspec/.github/workflows/ci.yml +222 -0
- package/references/openspec/.github/workflows/release-prepare.yml +50 -0
- package/references/openspec/AGENTS.md +18 -0
- package/references/openspec/CHANGELOG.md +205 -0
- package/references/openspec/LICENSE +22 -0
- package/references/openspec/README.md +374 -0
- package/references/openspec/assets/openspec_dashboard.png +0 -0
- package/references/openspec/assets/openspec_pixel_dark.svg +89 -0
- package/references/openspec/assets/openspec_pixel_light.svg +89 -0
- package/references/openspec/bin/openspec.js +3 -0
- package/references/openspec/build.js +31 -0
- package/references/openspec/openspec/AGENTS.md +454 -0
- package/references/openspec/openspec/changes/IMPLEMENTATION_ORDER.md +68 -0
- package/references/openspec/openspec/changes/add-antigravity-support/proposal.md +11 -0
- package/references/openspec/openspec/changes/add-antigravity-support/specs/cli-init/spec.md +9 -0
- package/references/openspec/openspec/changes/add-antigravity-support/specs/cli-update/spec.md +8 -0
- package/references/openspec/openspec/changes/add-antigravity-support/tasks.md +12 -0
- package/references/openspec/openspec/changes/add-scaffold-command/proposal.md +11 -0
- package/references/openspec/openspec/changes/add-scaffold-command/specs/cli-scaffold/spec.md +36 -0
- package/references/openspec/openspec/changes/add-scaffold-command/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/design.md +86 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/proposal.md +29 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/specs/cli-update/spec.md +59 -0
- package/references/openspec/openspec/changes/archive/2025-01-11-add-update-command/tasks.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/proposal.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/specs/cli-list/spec.md +69 -0
- package/references/openspec/openspec/changes/archive/2025-01-13-add-list-command/tasks.md +26 -0
- package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/design.md +64 -0
- package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/proposal.md +18 -0
- package/references/openspec/openspec/changes/archive/2025-08-05-initialize-typescript-project/tasks.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/design.md +104 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/proposal.md +30 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/specs/cli-init/spec.md +148 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-add-init-command/tasks.md +38 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/proposal.md +24 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/specs/openspec-conventions/spec.md +120 -0
- package/references/openspec/openspec/changes/archive/2025-08-06-adopt-future-state-storage/tasks.md +38 -0
- package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/specs/openspec-docs/README.md +472 -0
- package/references/openspec/openspec/changes/archive/2025-08-11-add-complexity-guidelines/tasks.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/proposal.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/specs/cli-archive/spec.md +111 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-archive-command/tasks.md +44 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/specs/cli-diff/spec.md +77 -0
- package/references/openspec/openspec/changes/archive/2025-08-13-add-diff-command/tasks.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/design.md +56 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-change/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/specs/cli-list/spec.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-change-commands/tasks.md +34 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/proposal.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-change/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-show/spec.md +83 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/specs/cli-spec/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-interactive-show-command/tasks.md +142 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/specs/cli-archive/spec.md +191 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-skip-specs-archive-option/tasks.md +57 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/design.md +45 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/specs/cli-spec/spec.md +43 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-spec-commands/tasks.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/design.md +104 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/proposal.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-archive/spec.md +18 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/specs/cli-diff/spec.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-add-zod-validation/tasks.md +59 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/proposal.md +93 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-archive/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/cli-diff/spec.md +45 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/specs/openspec-conventions/spec.md +101 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-delta-based-changes/tasks.md +55 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/design.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/proposal.md +67 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/cli-list/spec.md +57 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/specs/openspec-conventions/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-adopt-verb-noun-cli-structure/tasks.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/proposal.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-change/spec.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-spec/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/specs/cli-validate/spec.md +149 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-bulk-validation-interactive-selection/tasks.md +81 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/proposal.md +40 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/specs/cli-update/spec.md +23 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-fix-update-tool-selection/tasks.md +21 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/specs/cli-validate/spec.md +55 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-improve-validate-error-messages/tasks.md +21 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/proposal.md +36 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/specs/openspec-conventions/spec.md +192 -0
- package/references/openspec/openspec/changes/archive/2025-08-19-structured-spec-format/tasks.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/proposal.md +38 -0
- package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/specs/cli-view/spec.md +109 -0
- package/references/openspec/openspec/changes/archive/2025-09-12-add-view-dashboard-command/tasks.md +47 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/proposal.md +28 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-init/spec.md +71 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/specs/cli-update/spec.md +41 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-agents-md-config/tasks.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/proposal.md +35 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/specs/cli-init/spec.md +45 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-multi-agent-init/tasks.md +16 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/proposal.md +119 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-init/spec.md +21 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/specs/cli-update/spec.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-add-slash-command-support/tasks.md +20 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-cli-e2e-plan/tasks.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-deterministic-tests/proposal.md +78 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-deterministic-tests/tasks.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/specs/cli-init/spec.md +92 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-improve-init-onboarding/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-remove-diff-command/proposal.md +81 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-remove-diff-command/tasks.md +37 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/specs/cli-view/spec.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-sort-active-changes-by-progress/tasks.md +8 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/proposal.md +29 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-init/spec.md +40 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/cli-update/spec.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/specs/openspec-conventions/spec.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-file-name/tasks.md +22 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/design.md +130 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/proposal.md +117 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-agent-instructions/tasks.md +69 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/proposal.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/specs/cli-validate/spec.md +9 -0
- package/references/openspec/openspec/changes/archive/2025-09-29-update-markdown-parser-crlf/tasks.md +11 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-init/spec.md +56 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/specs/cli-update/spec.md +41 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-codex-slash-command-support/tasks.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/proposal.md +25 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-init/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/specs/cli-update/spec.md +48 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-github-copilot-prompts/tasks.md +30 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-init/spec.md +43 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/specs/cli-update/spec.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-kilocode-workflows/tasks.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/specs/cli-init/spec.md +39 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-non-interactive-init-options/tasks.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-init/spec.md +42 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/specs/cli-update/spec.md +27 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-add-windsurf-workflows/tasks.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/specs/cli-validate/spec.md +39 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-enhance-validation-error-messages/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/specs/docs-agent-instructions/spec.md +33 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-improve-agent-instruction-usability/tasks.md +11 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-slim-root-agents-file/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-slim-root-agents-file/tasks.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/proposal.md +14 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/specs/cli-init/spec.md +10 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-enter-selection/tasks.md +8 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/proposal.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-init/spec.md +32 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/specs/cli-update/spec.md +10 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-cli-init-root-agents/tasks.md +11 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-release-automation/proposal.md +49 -0
- package/references/openspec/openspec/changes/archive/2025-10-14-update-release-automation/tasks.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/proposal.md +17 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/specs/cli-update/spec.md +32 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-archive-command-arguments/tasks.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/proposal.md +15 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/specs/cli-init/spec.md +97 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-cline-support/tasks.md +19 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/proposal.md +13 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/specs/cli-init/spec.md +67 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-crush-support/tasks.md +7 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/proposal.md +12 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-init/spec.md +54 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/specs/cli-update/spec.md +54 -0
- package/references/openspec/openspec/changes/archive/2025-10-22-add-factory-slash-commands/tasks.md +11 -0
- package/references/openspec/openspec/changes/fix-cline-workflows-implementation/proposal.md +13 -0
- package/references/openspec/openspec/changes/fix-cline-workflows-implementation/specs/cli-init/spec.md +11 -0
- package/references/openspec/openspec/changes/fix-cline-workflows-implementation/tasks.md +13 -0
- package/references/openspec/openspec/changes/make-validation-scope-aware/proposal.md +12 -0
- package/references/openspec/openspec/changes/make-validation-scope-aware/specs/cli-validate/spec.md +25 -0
- package/references/openspec/openspec/changes/make-validation-scope-aware/tasks.md +16 -0
- package/references/openspec/openspec/project.md +53 -0
- package/references/openspec/openspec/specs/cli-archive/spec.md +210 -0
- package/references/openspec/openspec/specs/cli-change/spec.md +91 -0
- package/references/openspec/openspec/specs/cli-init/spec.md +311 -0
- package/references/openspec/openspec/specs/cli-list/spec.md +103 -0
- package/references/openspec/openspec/specs/cli-show/spec.md +85 -0
- package/references/openspec/openspec/specs/cli-spec/spec.md +87 -0
- package/references/openspec/openspec/specs/cli-update/spec.md +190 -0
- package/references/openspec/openspec/specs/cli-validate/spec.md +218 -0
- package/references/openspec/openspec/specs/cli-view/spec.md +105 -0
- package/references/openspec/openspec/specs/docs-agent-instructions/spec.md +38 -0
- package/references/openspec/openspec/specs/openspec-conventions/spec.md +474 -0
- package/references/openspec/openspec-parallel-merge-plan.md +98 -0
- package/references/openspec/package.json +73 -0
- package/references/openspec/pnpm-lock.yaml +2324 -0
- package/references/openspec/scripts/pack-version-check.mjs +111 -0
- package/references/openspec/src/cli/index.ts +253 -0
- package/references/openspec/src/commands/change.ts +291 -0
- package/references/openspec/src/commands/show.ts +139 -0
- package/references/openspec/src/commands/spec.ts +250 -0
- package/references/openspec/src/commands/validate.ts +305 -0
- package/references/openspec/src/core/archive.ts +606 -0
- package/references/openspec/src/core/config.ts +41 -0
- package/references/openspec/src/core/configurators/agents.ts +23 -0
- package/references/openspec/src/core/configurators/base.ts +6 -0
- package/references/openspec/src/core/configurators/claude.ts +23 -0
- package/references/openspec/src/core/configurators/cline.ts +23 -0
- package/references/openspec/src/core/configurators/codebuddy.ts +24 -0
- package/references/openspec/src/core/configurators/costrict.ts +23 -0
- package/references/openspec/src/core/configurators/iflow.ts +23 -0
- package/references/openspec/src/core/configurators/qoder.ts +53 -0
- package/references/openspec/src/core/configurators/qwen.ts +47 -0
- package/references/openspec/src/core/configurators/registry.ts +49 -0
- package/references/openspec/src/core/configurators/slash/amazon-q.ts +51 -0
- package/references/openspec/src/core/configurators/slash/antigravity.ts +28 -0
- package/references/openspec/src/core/configurators/slash/auggie.ts +37 -0
- package/references/openspec/src/core/configurators/slash/base.ts +95 -0
- package/references/openspec/src/core/configurators/slash/claude.ts +42 -0
- package/references/openspec/src/core/configurators/slash/cline.ts +27 -0
- package/references/openspec/src/core/configurators/slash/codebuddy.ts +43 -0
- package/references/openspec/src/core/configurators/slash/codex.ts +126 -0
- package/references/openspec/src/core/configurators/slash/costrict.ts +36 -0
- package/references/openspec/src/core/configurators/slash/crush.ts +42 -0
- package/references/openspec/src/core/configurators/slash/cursor.ts +42 -0
- package/references/openspec/src/core/configurators/slash/factory.ts +41 -0
- package/references/openspec/src/core/configurators/slash/gemini.ts +27 -0
- package/references/openspec/src/core/configurators/slash/github-copilot.ts +39 -0
- package/references/openspec/src/core/configurators/slash/iflow.ts +42 -0
- package/references/openspec/src/core/configurators/slash/kilocode.ts +21 -0
- package/references/openspec/src/core/configurators/slash/opencode.ts +83 -0
- package/references/openspec/src/core/configurators/slash/qoder.ts +84 -0
- package/references/openspec/src/core/configurators/slash/qwen.ts +55 -0
- package/references/openspec/src/core/configurators/slash/registry.ts +81 -0
- package/references/openspec/src/core/configurators/slash/roocode.ts +27 -0
- package/references/openspec/src/core/configurators/slash/toml-base.ts +66 -0
- package/references/openspec/src/core/configurators/slash/windsurf.ts +27 -0
- package/references/openspec/src/core/converters/json-converter.ts +61 -0
- package/references/openspec/src/core/index.ts +2 -0
- package/references/openspec/src/core/init.ts +986 -0
- package/references/openspec/src/core/list.ts +104 -0
- package/references/openspec/src/core/parsers/change-parser.ts +234 -0
- package/references/openspec/src/core/parsers/markdown-parser.ts +237 -0
- package/references/openspec/src/core/parsers/requirement-blocks.ts +234 -0
- package/references/openspec/src/core/schemas/base.schema.ts +20 -0
- package/references/openspec/src/core/schemas/change.schema.ts +42 -0
- package/references/openspec/src/core/schemas/index.ts +20 -0
- package/references/openspec/src/core/schemas/spec.schema.ts +17 -0
- package/references/openspec/src/core/styles/palette.ts +8 -0
- package/references/openspec/src/core/templates/agents-root-stub.ts +16 -0
- package/references/openspec/src/core/templates/agents-template.ts +457 -0
- package/references/openspec/src/core/templates/claude-template.ts +1 -0
- package/references/openspec/src/core/templates/cline-template.ts +1 -0
- package/references/openspec/src/core/templates/costrict-template.ts +1 -0
- package/references/openspec/src/core/templates/index.ts +50 -0
- package/references/openspec/src/core/templates/project-template.ts +38 -0
- package/references/openspec/src/core/templates/slash-command-templates.ts +60 -0
- package/references/openspec/src/core/update.ts +129 -0
- package/references/openspec/src/core/validation/constants.ts +48 -0
- package/references/openspec/src/core/validation/types.ts +19 -0
- package/references/openspec/src/core/validation/validator.ts +448 -0
- package/references/openspec/src/core/view.ts +189 -0
- package/references/openspec/src/index.ts +2 -0
- package/references/openspec/src/utils/file-system.ts +187 -0
- package/references/openspec/src/utils/index.ts +2 -0
- package/references/openspec/src/utils/interactive.ts +7 -0
- package/references/openspec/src/utils/item-discovery.ts +45 -0
- package/references/openspec/src/utils/match.ts +26 -0
- package/references/openspec/src/utils/task-progress.ts +43 -0
- package/references/openspec/test/cli-e2e/basic.test.ts +156 -0
- package/references/openspec/test/commands/change.interactive-show.test.ts +45 -0
- package/references/openspec/test/commands/change.interactive-validate.test.ts +48 -0
- package/references/openspec/test/commands/show.test.ts +123 -0
- package/references/openspec/test/commands/spec.interactive-show.test.ts +44 -0
- package/references/openspec/test/commands/spec.interactive-validate.test.ts +44 -0
- package/references/openspec/test/commands/spec.test.ts +324 -0
- package/references/openspec/test/commands/validate.enriched-output.test.ts +49 -0
- package/references/openspec/test/commands/validate.test.ts +133 -0
- package/references/openspec/test/core/archive.test.ts +680 -0
- package/references/openspec/test/core/commands/change-command.list.test.ts +76 -0
- package/references/openspec/test/core/commands/change-command.show-validate.test.ts +111 -0
- package/references/openspec/test/core/converters/json-converter.test.ts +184 -0
- package/references/openspec/test/core/init.test.ts +1710 -0
- package/references/openspec/test/core/list.test.ts +165 -0
- package/references/openspec/test/core/parsers/change-parser.test.ts +52 -0
- package/references/openspec/test/core/parsers/markdown-parser.test.ts +291 -0
- package/references/openspec/test/core/update.test.ts +1642 -0
- package/references/openspec/test/core/validation.enriched-messages.test.ts +74 -0
- package/references/openspec/test/core/validation.test.ts +489 -0
- package/references/openspec/test/core/view.test.ts +79 -0
- package/references/openspec/test/fixtures/tmp-init/openspec/changes/c1/proposal.md +7 -0
- package/references/openspec/test/fixtures/tmp-init/openspec/changes/c1/specs/alpha/spec.md +8 -0
- package/references/openspec/test/fixtures/tmp-init/openspec/specs/alpha/spec.md +12 -0
- package/references/openspec/test/helpers/run-cli.ts +139 -0
- package/references/openspec/test/utils/file-system.test.ts +211 -0
- package/references/openspec/test/utils/marker-updates.test.ts +287 -0
- package/references/openspec/tsconfig.json +21 -0
- package/references/openspec/vitest.config.ts +25 -0
- package/references/openspec/vitest.setup.ts +6 -0
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import {
|
|
3
|
+
createPrompt,
|
|
4
|
+
isBackspaceKey,
|
|
5
|
+
isDownKey,
|
|
6
|
+
isEnterKey,
|
|
7
|
+
isSpaceKey,
|
|
8
|
+
isUpKey,
|
|
9
|
+
useKeypress,
|
|
10
|
+
usePagination,
|
|
11
|
+
useState,
|
|
12
|
+
} from '@inquirer/core';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import ora from 'ora';
|
|
15
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
16
|
+
import { TemplateManager, ProjectContext } from './templates/index.js';
|
|
17
|
+
import { ToolRegistry } from './configurators/registry.js';
|
|
18
|
+
import { SlashCommandRegistry } from './configurators/slash/registry.js';
|
|
19
|
+
import {
|
|
20
|
+
OpenSpecConfig,
|
|
21
|
+
AI_TOOLS,
|
|
22
|
+
OPENSPEC_DIR_NAME,
|
|
23
|
+
AIToolOption,
|
|
24
|
+
OPENSPEC_MARKERS,
|
|
25
|
+
} from './config.js';
|
|
26
|
+
import { PALETTE } from './styles/palette.js';
|
|
27
|
+
|
|
28
|
+
const PROGRESS_SPINNER = {
|
|
29
|
+
interval: 80,
|
|
30
|
+
frames: ['░░░', '▒░░', '▒▒░', '▒▒▒', '▓▒▒', '▓▓▒', '▓▓▓', '▒▓▓', '░▒▓'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const LETTER_MAP: Record<string, string[]> = {
|
|
34
|
+
O: [' ████ ', '██ ██', '██ ██', '██ ██', ' ████ '],
|
|
35
|
+
P: ['█████ ', '██ ██', '█████ ', '██ ', '██ '],
|
|
36
|
+
E: ['██████', '██ ', '█████ ', '██ ', '██████'],
|
|
37
|
+
N: ['██ ██', '███ ██', '██ ███', '██ ██', '██ ██'],
|
|
38
|
+
S: [' █████', '██ ', ' ████ ', ' ██', '█████ '],
|
|
39
|
+
C: [' █████', '██ ', '██ ', '██ ', ' █████'],
|
|
40
|
+
' ': [' ', ' ', ' ', ' ', ' '],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type ToolLabel = {
|
|
44
|
+
primary: string;
|
|
45
|
+
annotation?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const sanitizeToolLabel = (raw: string): string =>
|
|
49
|
+
raw.replace(/✅/gu, '✔').trim();
|
|
50
|
+
|
|
51
|
+
const parseToolLabel = (raw: string): ToolLabel => {
|
|
52
|
+
const sanitized = sanitizeToolLabel(raw);
|
|
53
|
+
const match = sanitized.match(/^(.*?)\s*\((.+)\)$/u);
|
|
54
|
+
if (!match) {
|
|
55
|
+
return { primary: sanitized };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
primary: match[1].trim(),
|
|
59
|
+
annotation: match[2].trim(),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isSelectableChoice = (
|
|
64
|
+
choice: ToolWizardChoice
|
|
65
|
+
): choice is Extract<ToolWizardChoice, { selectable: true }> => choice.selectable;
|
|
66
|
+
|
|
67
|
+
type ToolWizardChoice =
|
|
68
|
+
| {
|
|
69
|
+
kind: 'heading' | 'info';
|
|
70
|
+
value: string;
|
|
71
|
+
label: ToolLabel;
|
|
72
|
+
selectable: false;
|
|
73
|
+
}
|
|
74
|
+
| {
|
|
75
|
+
kind: 'option';
|
|
76
|
+
value: string;
|
|
77
|
+
label: ToolLabel;
|
|
78
|
+
configured: boolean;
|
|
79
|
+
selectable: true;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type ToolWizardConfig = {
|
|
83
|
+
extendMode: boolean;
|
|
84
|
+
baseMessage: string;
|
|
85
|
+
choices: ToolWizardChoice[];
|
|
86
|
+
initialSelected?: string[];
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type WizardStep = 'intro' | 'select' | 'review';
|
|
90
|
+
|
|
91
|
+
type ToolSelectionPrompt = (config: ToolWizardConfig) => Promise<string[]>;
|
|
92
|
+
|
|
93
|
+
type RootStubStatus = 'created' | 'updated' | 'skipped';
|
|
94
|
+
|
|
95
|
+
const ROOT_STUB_CHOICE_VALUE = '__root_stub__';
|
|
96
|
+
|
|
97
|
+
const OTHER_TOOLS_HEADING_VALUE = '__heading-other__';
|
|
98
|
+
const LIST_SPACER_VALUE = '__list-spacer__';
|
|
99
|
+
|
|
100
|
+
const toolSelectionWizard = createPrompt<string[], ToolWizardConfig>(
|
|
101
|
+
(config, done) => {
|
|
102
|
+
const totalSteps = 3;
|
|
103
|
+
const [step, setStep] = useState<WizardStep>('intro');
|
|
104
|
+
const selectableChoices = config.choices.filter(isSelectableChoice);
|
|
105
|
+
const initialCursorIndex = config.choices.findIndex((choice) =>
|
|
106
|
+
choice.selectable
|
|
107
|
+
);
|
|
108
|
+
const [cursor, setCursor] = useState<number>(
|
|
109
|
+
initialCursorIndex === -1 ? 0 : initialCursorIndex
|
|
110
|
+
);
|
|
111
|
+
const [selected, setSelected] = useState<string[]>(() => {
|
|
112
|
+
const initial = new Set(
|
|
113
|
+
(config.initialSelected ?? []).filter((value) =>
|
|
114
|
+
selectableChoices.some((choice) => choice.value === value)
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
return selectableChoices
|
|
118
|
+
.map((choice) => choice.value)
|
|
119
|
+
.filter((value) => initial.has(value));
|
|
120
|
+
});
|
|
121
|
+
const [error, setError] = useState<string | null>(null);
|
|
122
|
+
|
|
123
|
+
const selectedSet = new Set(selected);
|
|
124
|
+
const pageSize = Math.max(config.choices.length, 1);
|
|
125
|
+
|
|
126
|
+
const updateSelected = (next: Set<string>) => {
|
|
127
|
+
const ordered = selectableChoices
|
|
128
|
+
.map((choice) => choice.value)
|
|
129
|
+
.filter((value) => next.has(value));
|
|
130
|
+
setSelected(ordered);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const page = usePagination({
|
|
134
|
+
items: config.choices,
|
|
135
|
+
active: cursor,
|
|
136
|
+
pageSize,
|
|
137
|
+
loop: false,
|
|
138
|
+
renderItem: ({ item, isActive }) => {
|
|
139
|
+
if (!item.selectable) {
|
|
140
|
+
const prefix = item.kind === 'info' ? ' ' : '';
|
|
141
|
+
const textColor =
|
|
142
|
+
item.kind === 'heading' ? PALETTE.lightGray : PALETTE.midGray;
|
|
143
|
+
return `${PALETTE.midGray(' ')} ${PALETTE.midGray(' ')} ${textColor(
|
|
144
|
+
`${prefix}${item.label.primary}`
|
|
145
|
+
)}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const isSelected = selectedSet.has(item.value);
|
|
149
|
+
const cursorSymbol = isActive
|
|
150
|
+
? PALETTE.white('›')
|
|
151
|
+
: PALETTE.midGray(' ');
|
|
152
|
+
const indicator = isSelected
|
|
153
|
+
? PALETTE.white('◉')
|
|
154
|
+
: PALETTE.midGray('○');
|
|
155
|
+
const nameColor = isActive ? PALETTE.white : PALETTE.midGray;
|
|
156
|
+
const annotation = item.label.annotation
|
|
157
|
+
? PALETTE.midGray(` (${item.label.annotation})`)
|
|
158
|
+
: '';
|
|
159
|
+
const configuredNote = item.configured
|
|
160
|
+
? PALETTE.midGray(' (already configured)')
|
|
161
|
+
: '';
|
|
162
|
+
const label = `${nameColor(item.label.primary)}${annotation}${configuredNote}`;
|
|
163
|
+
return `${cursorSymbol} ${indicator} ${label}`;
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const moveCursor = (direction: 1 | -1) => {
|
|
168
|
+
if (selectableChoices.length === 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let nextIndex = cursor;
|
|
173
|
+
while (true) {
|
|
174
|
+
nextIndex = nextIndex + direction;
|
|
175
|
+
if (nextIndex < 0 || nextIndex >= config.choices.length) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (config.choices[nextIndex]?.selectable) {
|
|
180
|
+
setCursor(nextIndex);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
useKeypress((key) => {
|
|
187
|
+
if (step === 'intro') {
|
|
188
|
+
if (isEnterKey(key)) {
|
|
189
|
+
setStep('select');
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (step === 'select') {
|
|
195
|
+
if (isUpKey(key)) {
|
|
196
|
+
moveCursor(-1);
|
|
197
|
+
setError(null);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (isDownKey(key)) {
|
|
202
|
+
moveCursor(1);
|
|
203
|
+
setError(null);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isSpaceKey(key)) {
|
|
208
|
+
const current = config.choices[cursor];
|
|
209
|
+
if (!current || !current.selectable) return;
|
|
210
|
+
|
|
211
|
+
const next = new Set(selected);
|
|
212
|
+
if (next.has(current.value)) {
|
|
213
|
+
next.delete(current.value);
|
|
214
|
+
} else {
|
|
215
|
+
next.add(current.value);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
updateSelected(next);
|
|
219
|
+
setError(null);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (isEnterKey(key)) {
|
|
224
|
+
const current = config.choices[cursor];
|
|
225
|
+
if (
|
|
226
|
+
current &&
|
|
227
|
+
current.selectable &&
|
|
228
|
+
!selectedSet.has(current.value)
|
|
229
|
+
) {
|
|
230
|
+
const next = new Set(selected);
|
|
231
|
+
next.add(current.value);
|
|
232
|
+
updateSelected(next);
|
|
233
|
+
}
|
|
234
|
+
setStep('review');
|
|
235
|
+
setError(null);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (key.name === 'escape') {
|
|
240
|
+
const next = new Set<string>();
|
|
241
|
+
updateSelected(next);
|
|
242
|
+
setError(null);
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (step === 'review') {
|
|
248
|
+
if (isEnterKey(key)) {
|
|
249
|
+
const finalSelection = config.choices
|
|
250
|
+
.map((choice) => choice.value)
|
|
251
|
+
.filter(
|
|
252
|
+
(value) =>
|
|
253
|
+
selectedSet.has(value) && value !== ROOT_STUB_CHOICE_VALUE
|
|
254
|
+
);
|
|
255
|
+
done(finalSelection);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (isBackspaceKey(key) || key.name === 'escape') {
|
|
260
|
+
setStep('select');
|
|
261
|
+
setError(null);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const rootStubChoice = selectableChoices.find(
|
|
267
|
+
(choice) => choice.value === ROOT_STUB_CHOICE_VALUE
|
|
268
|
+
);
|
|
269
|
+
const rootStubSelected = rootStubChoice
|
|
270
|
+
? selectedSet.has(ROOT_STUB_CHOICE_VALUE)
|
|
271
|
+
: false;
|
|
272
|
+
const nativeChoices = selectableChoices.filter(
|
|
273
|
+
(choice) => choice.value !== ROOT_STUB_CHOICE_VALUE
|
|
274
|
+
);
|
|
275
|
+
const selectedNativeChoices = nativeChoices.filter((choice) =>
|
|
276
|
+
selectedSet.has(choice.value)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const formatSummaryLabel = (
|
|
280
|
+
choice: Extract<ToolWizardChoice, { selectable: true }>
|
|
281
|
+
) => {
|
|
282
|
+
const annotation = choice.label.annotation
|
|
283
|
+
? PALETTE.midGray(` (${choice.label.annotation})`)
|
|
284
|
+
: '';
|
|
285
|
+
const configuredNote = choice.configured
|
|
286
|
+
? PALETTE.midGray(' (already configured)')
|
|
287
|
+
: '';
|
|
288
|
+
return `${PALETTE.white(choice.label.primary)}${annotation}${configuredNote}`;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const stepIndex = step === 'intro' ? 1 : step === 'select' ? 2 : 3;
|
|
292
|
+
const lines: string[] = [];
|
|
293
|
+
lines.push(PALETTE.midGray(`Step ${stepIndex}/${totalSteps}`));
|
|
294
|
+
lines.push('');
|
|
295
|
+
|
|
296
|
+
if (step === 'intro') {
|
|
297
|
+
const introHeadline = config.extendMode
|
|
298
|
+
? 'Extend your OpenSpec tooling'
|
|
299
|
+
: 'Configure your OpenSpec tooling';
|
|
300
|
+
const introBody = config.extendMode
|
|
301
|
+
? 'We detected an existing setup. We will help you refresh or add integrations.'
|
|
302
|
+
: "Let's get your AI assistants connected so they understand OpenSpec.";
|
|
303
|
+
|
|
304
|
+
lines.push(PALETTE.white(introHeadline));
|
|
305
|
+
lines.push(PALETTE.midGray(introBody));
|
|
306
|
+
lines.push('');
|
|
307
|
+
lines.push(PALETTE.midGray('Press Enter to continue.'));
|
|
308
|
+
} else if (step === 'select') {
|
|
309
|
+
lines.push(PALETTE.white(config.baseMessage));
|
|
310
|
+
lines.push(
|
|
311
|
+
PALETTE.midGray(
|
|
312
|
+
'Use ↑/↓ to move · Space to toggle · Enter selects highlighted tool and reviews.'
|
|
313
|
+
)
|
|
314
|
+
);
|
|
315
|
+
lines.push('');
|
|
316
|
+
lines.push(page);
|
|
317
|
+
lines.push('');
|
|
318
|
+
lines.push(PALETTE.midGray('Selected configuration:'));
|
|
319
|
+
if (rootStubSelected && rootStubChoice) {
|
|
320
|
+
lines.push(
|
|
321
|
+
` ${PALETTE.white('-')} ${formatSummaryLabel(rootStubChoice)}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (selectedNativeChoices.length === 0) {
|
|
325
|
+
lines.push(
|
|
326
|
+
` ${PALETTE.midGray('- No natively supported providers selected')}`
|
|
327
|
+
);
|
|
328
|
+
} else {
|
|
329
|
+
selectedNativeChoices.forEach((choice) => {
|
|
330
|
+
lines.push(
|
|
331
|
+
` ${PALETTE.white('-')} ${formatSummaryLabel(choice)}`
|
|
332
|
+
);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
lines.push(PALETTE.white('Review selections'));
|
|
337
|
+
lines.push(
|
|
338
|
+
PALETTE.midGray('Press Enter to confirm or Backspace to adjust.')
|
|
339
|
+
);
|
|
340
|
+
lines.push('');
|
|
341
|
+
|
|
342
|
+
if (rootStubSelected && rootStubChoice) {
|
|
343
|
+
lines.push(
|
|
344
|
+
`${PALETTE.white('▌')} ${formatSummaryLabel(rootStubChoice)}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (selectedNativeChoices.length === 0) {
|
|
349
|
+
lines.push(
|
|
350
|
+
PALETTE.midGray(
|
|
351
|
+
'No natively supported providers selected. Universal instructions will still be applied.'
|
|
352
|
+
)
|
|
353
|
+
);
|
|
354
|
+
} else {
|
|
355
|
+
selectedNativeChoices.forEach((choice) => {
|
|
356
|
+
lines.push(
|
|
357
|
+
`${PALETTE.white('▌')} ${formatSummaryLabel(choice)}`
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (error) {
|
|
364
|
+
return [lines.join('\n'), chalk.red(error)];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return lines.join('\n');
|
|
368
|
+
}
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
type InitCommandOptions = {
|
|
372
|
+
prompt?: ToolSelectionPrompt;
|
|
373
|
+
tools?: string;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
export class InitCommand {
|
|
377
|
+
private readonly prompt: ToolSelectionPrompt;
|
|
378
|
+
private readonly toolsArg?: string;
|
|
379
|
+
|
|
380
|
+
constructor(options: InitCommandOptions = {}) {
|
|
381
|
+
this.prompt = options.prompt ?? ((config) => toolSelectionWizard(config));
|
|
382
|
+
this.toolsArg = options.tools;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async execute(targetPath: string): Promise<void> {
|
|
386
|
+
const projectPath = path.resolve(targetPath);
|
|
387
|
+
const openspecDir = OPENSPEC_DIR_NAME;
|
|
388
|
+
const openspecPath = path.join(projectPath, openspecDir);
|
|
389
|
+
|
|
390
|
+
// Validation happens silently in the background
|
|
391
|
+
const extendMode = await this.validate(projectPath, openspecPath);
|
|
392
|
+
const existingToolStates = await this.getExistingToolStates(projectPath, extendMode);
|
|
393
|
+
|
|
394
|
+
this.renderBanner(extendMode);
|
|
395
|
+
|
|
396
|
+
// Get configuration (after validation to avoid prompts if validation fails)
|
|
397
|
+
const config = await this.getConfiguration(existingToolStates, extendMode);
|
|
398
|
+
|
|
399
|
+
const availableTools = AI_TOOLS.filter((tool) => tool.available);
|
|
400
|
+
const selectedIds = new Set(config.aiTools);
|
|
401
|
+
const selectedTools = availableTools.filter((tool) =>
|
|
402
|
+
selectedIds.has(tool.value)
|
|
403
|
+
);
|
|
404
|
+
const created = selectedTools.filter(
|
|
405
|
+
(tool) => !existingToolStates[tool.value]
|
|
406
|
+
);
|
|
407
|
+
const refreshed = selectedTools.filter(
|
|
408
|
+
(tool) => existingToolStates[tool.value]
|
|
409
|
+
);
|
|
410
|
+
const skippedExisting = availableTools.filter(
|
|
411
|
+
(tool) => !selectedIds.has(tool.value) && existingToolStates[tool.value]
|
|
412
|
+
);
|
|
413
|
+
const skipped = availableTools.filter(
|
|
414
|
+
(tool) => !selectedIds.has(tool.value) && !existingToolStates[tool.value]
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// Step 1: Create directory structure
|
|
418
|
+
if (!extendMode) {
|
|
419
|
+
const structureSpinner = this.startSpinner(
|
|
420
|
+
'Creating OpenSpec structure...'
|
|
421
|
+
);
|
|
422
|
+
await this.createDirectoryStructure(openspecPath);
|
|
423
|
+
await this.generateFiles(openspecPath, config);
|
|
424
|
+
structureSpinner.stopAndPersist({
|
|
425
|
+
symbol: PALETTE.white('▌'),
|
|
426
|
+
text: PALETTE.white('OpenSpec structure created'),
|
|
427
|
+
});
|
|
428
|
+
} else {
|
|
429
|
+
ora({ stream: process.stdout }).info(
|
|
430
|
+
PALETTE.midGray(
|
|
431
|
+
'ℹ OpenSpec already initialized. Checking for missing files...'
|
|
432
|
+
)
|
|
433
|
+
);
|
|
434
|
+
await this.createDirectoryStructure(openspecPath);
|
|
435
|
+
await this.ensureTemplateFiles(openspecPath, config);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Step 2: Configure AI tools
|
|
439
|
+
const toolSpinner = this.startSpinner('Configuring AI tools...');
|
|
440
|
+
const rootStubStatus = await this.configureAITools(
|
|
441
|
+
projectPath,
|
|
442
|
+
openspecDir,
|
|
443
|
+
config.aiTools
|
|
444
|
+
);
|
|
445
|
+
toolSpinner.stopAndPersist({
|
|
446
|
+
symbol: PALETTE.white('▌'),
|
|
447
|
+
text: PALETTE.white('AI tools configured'),
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Success message
|
|
451
|
+
this.displaySuccessMessage(
|
|
452
|
+
selectedTools,
|
|
453
|
+
created,
|
|
454
|
+
refreshed,
|
|
455
|
+
skippedExisting,
|
|
456
|
+
skipped,
|
|
457
|
+
extendMode,
|
|
458
|
+
rootStubStatus
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private async validate(
|
|
463
|
+
projectPath: string,
|
|
464
|
+
_openspecPath: string
|
|
465
|
+
): Promise<boolean> {
|
|
466
|
+
const extendMode = await FileSystemUtils.directoryExists(_openspecPath);
|
|
467
|
+
|
|
468
|
+
// Check write permissions
|
|
469
|
+
if (!(await FileSystemUtils.ensureWritePermissions(projectPath))) {
|
|
470
|
+
throw new Error(`Insufficient permissions to write to ${projectPath}`);
|
|
471
|
+
}
|
|
472
|
+
return extendMode;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async getConfiguration(
|
|
476
|
+
existingTools: Record<string, boolean>,
|
|
477
|
+
extendMode: boolean
|
|
478
|
+
): Promise<OpenSpecConfig> {
|
|
479
|
+
const selectedTools = await this.getSelectedTools(existingTools, extendMode);
|
|
480
|
+
return { aiTools: selectedTools };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private async getSelectedTools(
|
|
484
|
+
existingTools: Record<string, boolean>,
|
|
485
|
+
extendMode: boolean
|
|
486
|
+
): Promise<string[]> {
|
|
487
|
+
const nonInteractiveSelection = this.resolveToolsArg();
|
|
488
|
+
if (nonInteractiveSelection !== null) {
|
|
489
|
+
return nonInteractiveSelection;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Fall back to interactive mode
|
|
493
|
+
return this.promptForAITools(existingTools, extendMode);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private resolveToolsArg(): string[] | null {
|
|
497
|
+
if (typeof this.toolsArg === 'undefined') {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const raw = this.toolsArg.trim();
|
|
502
|
+
if (raw.length === 0) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
'The --tools option requires a value. Use "all", "none", or a comma-separated list of tool IDs.'
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const availableTools = AI_TOOLS.filter((tool) => tool.available);
|
|
509
|
+
const availableValues = availableTools.map((tool) => tool.value);
|
|
510
|
+
const availableSet = new Set(availableValues);
|
|
511
|
+
const availableList = ['all', 'none', ...availableValues].join(', ');
|
|
512
|
+
|
|
513
|
+
const lowerRaw = raw.toLowerCase();
|
|
514
|
+
if (lowerRaw === 'all') {
|
|
515
|
+
return availableValues;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (lowerRaw === 'none') {
|
|
519
|
+
return [];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const tokens = raw
|
|
523
|
+
.split(',')
|
|
524
|
+
.map((token) => token.trim())
|
|
525
|
+
.filter((token) => token.length > 0);
|
|
526
|
+
|
|
527
|
+
if (tokens.length === 0) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
'The --tools option requires at least one tool ID when not using "all" or "none".'
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const normalizedTokens = tokens.map((token) => token.toLowerCase());
|
|
534
|
+
|
|
535
|
+
if (normalizedTokens.some((token) => token === 'all' || token === 'none')) {
|
|
536
|
+
throw new Error('Cannot combine reserved values "all" or "none" with specific tool IDs.');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const invalidTokens = tokens.filter(
|
|
540
|
+
(_token, index) => !availableSet.has(normalizedTokens[index])
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
if (invalidTokens.length > 0) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`Invalid tool(s): ${invalidTokens.join(', ')}. Available values: ${availableList}`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const deduped: string[] = [];
|
|
550
|
+
for (const token of normalizedTokens) {
|
|
551
|
+
if (!deduped.includes(token)) {
|
|
552
|
+
deduped.push(token);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return deduped;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private async promptForAITools(
|
|
560
|
+
existingTools: Record<string, boolean>,
|
|
561
|
+
extendMode: boolean
|
|
562
|
+
): Promise<string[]> {
|
|
563
|
+
const availableTools = AI_TOOLS.filter((tool) => tool.available);
|
|
564
|
+
|
|
565
|
+
const baseMessage = extendMode
|
|
566
|
+
? 'Which natively supported AI tools would you like to add or refresh?'
|
|
567
|
+
: 'Which natively supported AI tools do you use?';
|
|
568
|
+
const initialNativeSelection = extendMode
|
|
569
|
+
? availableTools
|
|
570
|
+
.filter((tool) => existingTools[tool.value])
|
|
571
|
+
.map((tool) => tool.value)
|
|
572
|
+
: [];
|
|
573
|
+
|
|
574
|
+
const initialSelected = Array.from(new Set(initialNativeSelection));
|
|
575
|
+
|
|
576
|
+
const choices: ToolWizardChoice[] = [
|
|
577
|
+
{
|
|
578
|
+
kind: 'heading',
|
|
579
|
+
value: '__heading-native__',
|
|
580
|
+
label: {
|
|
581
|
+
primary:
|
|
582
|
+
'Natively supported providers (✔ OpenSpec custom slash commands available)',
|
|
583
|
+
},
|
|
584
|
+
selectable: false,
|
|
585
|
+
},
|
|
586
|
+
...availableTools.map<ToolWizardChoice>((tool) => ({
|
|
587
|
+
kind: 'option',
|
|
588
|
+
value: tool.value,
|
|
589
|
+
label: parseToolLabel(tool.name),
|
|
590
|
+
configured: Boolean(existingTools[tool.value]),
|
|
591
|
+
selectable: true,
|
|
592
|
+
})),
|
|
593
|
+
...(availableTools.length
|
|
594
|
+
? ([
|
|
595
|
+
{
|
|
596
|
+
kind: 'info' as const,
|
|
597
|
+
value: LIST_SPACER_VALUE,
|
|
598
|
+
label: { primary: '' },
|
|
599
|
+
selectable: false,
|
|
600
|
+
},
|
|
601
|
+
] as ToolWizardChoice[])
|
|
602
|
+
: []),
|
|
603
|
+
{
|
|
604
|
+
kind: 'heading',
|
|
605
|
+
value: OTHER_TOOLS_HEADING_VALUE,
|
|
606
|
+
label: {
|
|
607
|
+
primary:
|
|
608
|
+
'Other tools (use Universal AGENTS.md for Amp, VS Code, GitHub Copilot, …)',
|
|
609
|
+
},
|
|
610
|
+
selectable: false,
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
kind: 'option',
|
|
614
|
+
value: ROOT_STUB_CHOICE_VALUE,
|
|
615
|
+
label: {
|
|
616
|
+
primary: 'Universal AGENTS.md',
|
|
617
|
+
annotation: 'always available',
|
|
618
|
+
},
|
|
619
|
+
configured: extendMode,
|
|
620
|
+
selectable: true,
|
|
621
|
+
},
|
|
622
|
+
];
|
|
623
|
+
|
|
624
|
+
return this.prompt({
|
|
625
|
+
extendMode,
|
|
626
|
+
baseMessage,
|
|
627
|
+
choices,
|
|
628
|
+
initialSelected,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private async getExistingToolStates(
|
|
633
|
+
projectPath: string,
|
|
634
|
+
extendMode: boolean
|
|
635
|
+
): Promise<Record<string, boolean>> {
|
|
636
|
+
// Fresh initialization - no tools configured yet
|
|
637
|
+
if (!extendMode) {
|
|
638
|
+
return Object.fromEntries(AI_TOOLS.map(t => [t.value, false]));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Extend mode - check all tools in parallel for better performance
|
|
642
|
+
const entries = await Promise.all(
|
|
643
|
+
AI_TOOLS.map(async (t) => [t.value, await this.isToolConfigured(projectPath, t.value)] as const)
|
|
644
|
+
);
|
|
645
|
+
return Object.fromEntries(entries);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private async isToolConfigured(
|
|
649
|
+
projectPath: string,
|
|
650
|
+
toolId: string
|
|
651
|
+
): Promise<boolean> {
|
|
652
|
+
// A tool is only considered "configured by OpenSpec" if its files contain OpenSpec markers.
|
|
653
|
+
// For tools with both config files and slash commands, BOTH must have markers.
|
|
654
|
+
// For slash commands, at least one file with markers is sufficient (not all required).
|
|
655
|
+
|
|
656
|
+
// Helper to check if a file exists and contains OpenSpec markers
|
|
657
|
+
const fileHasMarkers = async (absolutePath: string): Promise<boolean> => {
|
|
658
|
+
try {
|
|
659
|
+
const content = await FileSystemUtils.readFile(absolutePath);
|
|
660
|
+
return content.includes(OPENSPEC_MARKERS.start) && content.includes(OPENSPEC_MARKERS.end);
|
|
661
|
+
} catch {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
let hasConfigFile = false;
|
|
667
|
+
let hasSlashCommands = false;
|
|
668
|
+
|
|
669
|
+
// Check if the tool has a config file with OpenSpec markers
|
|
670
|
+
const configFile = ToolRegistry.get(toolId)?.configFileName;
|
|
671
|
+
if (configFile) {
|
|
672
|
+
const configPath = path.join(projectPath, configFile);
|
|
673
|
+
hasConfigFile = (await FileSystemUtils.fileExists(configPath)) && (await fileHasMarkers(configPath));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Check if any slash command file exists with OpenSpec markers
|
|
677
|
+
const slashConfigurator = SlashCommandRegistry.get(toolId);
|
|
678
|
+
if (slashConfigurator) {
|
|
679
|
+
for (const target of slashConfigurator.getTargets()) {
|
|
680
|
+
const absolute = slashConfigurator.resolveAbsolutePath(projectPath, target.id);
|
|
681
|
+
if ((await FileSystemUtils.fileExists(absolute)) && (await fileHasMarkers(absolute))) {
|
|
682
|
+
hasSlashCommands = true;
|
|
683
|
+
break; // At least one file with markers is sufficient
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Tool is only configured if BOTH exist with markers
|
|
689
|
+
// OR if the tool has no config file requirement (slash commands only)
|
|
690
|
+
// OR if the tool has no slash commands requirement (config file only)
|
|
691
|
+
const hasConfigFileRequirement = configFile !== undefined;
|
|
692
|
+
const hasSlashCommandRequirement = slashConfigurator !== undefined;
|
|
693
|
+
|
|
694
|
+
if (hasConfigFileRequirement && hasSlashCommandRequirement) {
|
|
695
|
+
// Both are required - both must be present with markers
|
|
696
|
+
return hasConfigFile && hasSlashCommands;
|
|
697
|
+
} else if (hasConfigFileRequirement) {
|
|
698
|
+
// Only config file required
|
|
699
|
+
return hasConfigFile;
|
|
700
|
+
} else if (hasSlashCommandRequirement) {
|
|
701
|
+
// Only slash commands required
|
|
702
|
+
return hasSlashCommands;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
private async createDirectoryStructure(openspecPath: string): Promise<void> {
|
|
709
|
+
const directories = [
|
|
710
|
+
openspecPath,
|
|
711
|
+
path.join(openspecPath, 'specs'),
|
|
712
|
+
path.join(openspecPath, 'changes'),
|
|
713
|
+
path.join(openspecPath, 'changes', 'archive'),
|
|
714
|
+
];
|
|
715
|
+
|
|
716
|
+
for (const dir of directories) {
|
|
717
|
+
await FileSystemUtils.createDirectory(dir);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private async generateFiles(
|
|
722
|
+
openspecPath: string,
|
|
723
|
+
config: OpenSpecConfig
|
|
724
|
+
): Promise<void> {
|
|
725
|
+
await this.writeTemplateFiles(openspecPath, config, false);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private async ensureTemplateFiles(
|
|
729
|
+
openspecPath: string,
|
|
730
|
+
config: OpenSpecConfig
|
|
731
|
+
): Promise<void> {
|
|
732
|
+
await this.writeTemplateFiles(openspecPath, config, true);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private async writeTemplateFiles(
|
|
736
|
+
openspecPath: string,
|
|
737
|
+
config: OpenSpecConfig,
|
|
738
|
+
skipExisting: boolean
|
|
739
|
+
): Promise<void> {
|
|
740
|
+
const context: ProjectContext = {
|
|
741
|
+
// Could be enhanced with prompts for project details
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const templates = TemplateManager.getTemplates(context);
|
|
745
|
+
|
|
746
|
+
for (const template of templates) {
|
|
747
|
+
const filePath = path.join(openspecPath, template.path);
|
|
748
|
+
|
|
749
|
+
// Skip if file exists and we're in skipExisting mode
|
|
750
|
+
if (skipExisting && (await FileSystemUtils.fileExists(filePath))) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const content =
|
|
755
|
+
typeof template.content === 'function'
|
|
756
|
+
? template.content(context)
|
|
757
|
+
: template.content;
|
|
758
|
+
|
|
759
|
+
await FileSystemUtils.writeFile(filePath, content);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private async configureAITools(
|
|
764
|
+
projectPath: string,
|
|
765
|
+
openspecDir: string,
|
|
766
|
+
toolIds: string[]
|
|
767
|
+
): Promise<RootStubStatus> {
|
|
768
|
+
const rootStubStatus = await this.configureRootAgentsStub(
|
|
769
|
+
projectPath,
|
|
770
|
+
openspecDir
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
for (const toolId of toolIds) {
|
|
774
|
+
const configurator = ToolRegistry.get(toolId);
|
|
775
|
+
if (configurator && configurator.isAvailable) {
|
|
776
|
+
await configurator.configure(projectPath, openspecDir);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const slashConfigurator = SlashCommandRegistry.get(toolId);
|
|
780
|
+
if (slashConfigurator && slashConfigurator.isAvailable) {
|
|
781
|
+
await slashConfigurator.generateAll(projectPath, openspecDir);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return rootStubStatus;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
private async configureRootAgentsStub(
|
|
789
|
+
projectPath: string,
|
|
790
|
+
openspecDir: string
|
|
791
|
+
): Promise<RootStubStatus> {
|
|
792
|
+
const configurator = ToolRegistry.get('agents');
|
|
793
|
+
if (!configurator || !configurator.isAvailable) {
|
|
794
|
+
return 'skipped';
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const stubPath = path.join(projectPath, configurator.configFileName);
|
|
798
|
+
const existed = await FileSystemUtils.fileExists(stubPath);
|
|
799
|
+
|
|
800
|
+
await configurator.configure(projectPath, openspecDir);
|
|
801
|
+
|
|
802
|
+
return existed ? 'updated' : 'created';
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
private displaySuccessMessage(
|
|
806
|
+
selectedTools: AIToolOption[],
|
|
807
|
+
created: AIToolOption[],
|
|
808
|
+
refreshed: AIToolOption[],
|
|
809
|
+
skippedExisting: AIToolOption[],
|
|
810
|
+
skipped: AIToolOption[],
|
|
811
|
+
extendMode: boolean,
|
|
812
|
+
rootStubStatus: RootStubStatus
|
|
813
|
+
): void {
|
|
814
|
+
console.log(); // Empty line for spacing
|
|
815
|
+
const successHeadline = extendMode
|
|
816
|
+
? 'OpenSpec tool configuration updated!'
|
|
817
|
+
: 'OpenSpec initialized successfully!';
|
|
818
|
+
ora().succeed(PALETTE.white(successHeadline));
|
|
819
|
+
|
|
820
|
+
console.log();
|
|
821
|
+
console.log(PALETTE.lightGray('Tool summary:'));
|
|
822
|
+
const summaryLines = [
|
|
823
|
+
rootStubStatus === 'created'
|
|
824
|
+
? `${PALETTE.white('▌')} ${PALETTE.white(
|
|
825
|
+
'Root AGENTS.md stub created for other assistants'
|
|
826
|
+
)}`
|
|
827
|
+
: null,
|
|
828
|
+
rootStubStatus === 'updated'
|
|
829
|
+
? `${PALETTE.lightGray('▌')} ${PALETTE.lightGray(
|
|
830
|
+
'Root AGENTS.md stub refreshed for other assistants'
|
|
831
|
+
)}`
|
|
832
|
+
: null,
|
|
833
|
+
created.length
|
|
834
|
+
? `${PALETTE.white('▌')} ${PALETTE.white(
|
|
835
|
+
'Created:'
|
|
836
|
+
)} ${this.formatToolNames(created)}`
|
|
837
|
+
: null,
|
|
838
|
+
refreshed.length
|
|
839
|
+
? `${PALETTE.lightGray('▌')} ${PALETTE.lightGray(
|
|
840
|
+
'Refreshed:'
|
|
841
|
+
)} ${this.formatToolNames(refreshed)}`
|
|
842
|
+
: null,
|
|
843
|
+
skippedExisting.length
|
|
844
|
+
? `${PALETTE.midGray('▌')} ${PALETTE.midGray(
|
|
845
|
+
'Skipped (already configured):'
|
|
846
|
+
)} ${this.formatToolNames(skippedExisting)}`
|
|
847
|
+
: null,
|
|
848
|
+
skipped.length
|
|
849
|
+
? `${PALETTE.darkGray('▌')} ${PALETTE.darkGray(
|
|
850
|
+
'Skipped:'
|
|
851
|
+
)} ${this.formatToolNames(skipped)}`
|
|
852
|
+
: null,
|
|
853
|
+
].filter((line): line is string => Boolean(line));
|
|
854
|
+
for (const line of summaryLines) {
|
|
855
|
+
console.log(line);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
console.log();
|
|
859
|
+
console.log(
|
|
860
|
+
PALETTE.midGray(
|
|
861
|
+
'Use `openspec update` to refresh shared OpenSpec instructions in the future.'
|
|
862
|
+
)
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
// Show restart instruction if any tools were configured
|
|
866
|
+
if (created.length > 0 || refreshed.length > 0) {
|
|
867
|
+
console.log();
|
|
868
|
+
console.log(PALETTE.white('Important: Restart your IDE'));
|
|
869
|
+
console.log(
|
|
870
|
+
PALETTE.midGray(
|
|
871
|
+
'Slash commands are loaded at startup. Please restart your coding assistant'
|
|
872
|
+
)
|
|
873
|
+
);
|
|
874
|
+
console.log(
|
|
875
|
+
PALETTE.midGray(
|
|
876
|
+
'to ensure the new /openspec commands appear in your command palette.'
|
|
877
|
+
)
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Get the selected tool name(s) for display
|
|
882
|
+
const toolName = this.formatToolNames(selectedTools);
|
|
883
|
+
|
|
884
|
+
console.log();
|
|
885
|
+
console.log(`Next steps - Copy these prompts to ${toolName}:`);
|
|
886
|
+
console.log(
|
|
887
|
+
chalk.gray('────────────────────────────────────────────────────────────')
|
|
888
|
+
);
|
|
889
|
+
console.log(PALETTE.white('1. Populate your project context:'));
|
|
890
|
+
console.log(
|
|
891
|
+
PALETTE.lightGray(
|
|
892
|
+
' "Please read openspec/project.md and help me fill it out'
|
|
893
|
+
)
|
|
894
|
+
);
|
|
895
|
+
console.log(
|
|
896
|
+
PALETTE.lightGray(
|
|
897
|
+
' with details about my project, tech stack, and conventions"\n'
|
|
898
|
+
)
|
|
899
|
+
);
|
|
900
|
+
console.log(PALETTE.white('2. Create your first change proposal:'));
|
|
901
|
+
console.log(
|
|
902
|
+
PALETTE.lightGray(
|
|
903
|
+
' "I want to add [YOUR FEATURE HERE]. Please create an'
|
|
904
|
+
)
|
|
905
|
+
);
|
|
906
|
+
console.log(
|
|
907
|
+
PALETTE.lightGray(' OpenSpec change proposal for this feature"\n')
|
|
908
|
+
);
|
|
909
|
+
console.log(PALETTE.white('3. Learn the OpenSpec workflow:'));
|
|
910
|
+
console.log(
|
|
911
|
+
PALETTE.lightGray(
|
|
912
|
+
' "Please explain the OpenSpec workflow from openspec/AGENTS.md'
|
|
913
|
+
)
|
|
914
|
+
);
|
|
915
|
+
console.log(
|
|
916
|
+
PALETTE.lightGray(' and how I should work with you on this project"')
|
|
917
|
+
);
|
|
918
|
+
console.log(
|
|
919
|
+
PALETTE.darkGray(
|
|
920
|
+
'────────────────────────────────────────────────────────────\n'
|
|
921
|
+
)
|
|
922
|
+
);
|
|
923
|
+
|
|
924
|
+
// Codex heads-up: prompts installed globally
|
|
925
|
+
const selectedToolIds = new Set(selectedTools.map((t) => t.value));
|
|
926
|
+
if (selectedToolIds.has('codex')) {
|
|
927
|
+
console.log(PALETTE.white('Codex setup note'));
|
|
928
|
+
console.log(
|
|
929
|
+
PALETTE.midGray('Prompts installed to ~/.codex/prompts (or $CODEX_HOME/prompts).')
|
|
930
|
+
);
|
|
931
|
+
console.log();
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
private formatToolNames(tools: AIToolOption[]): string {
|
|
936
|
+
const names = tools
|
|
937
|
+
.map((tool) => tool.successLabel ?? tool.name)
|
|
938
|
+
.filter((name): name is string => Boolean(name));
|
|
939
|
+
|
|
940
|
+
if (names.length === 0)
|
|
941
|
+
return PALETTE.lightGray('your AGENTS.md-compatible assistant');
|
|
942
|
+
if (names.length === 1) return PALETTE.white(names[0]);
|
|
943
|
+
|
|
944
|
+
const base = names.slice(0, -1).map((name) => PALETTE.white(name));
|
|
945
|
+
const last = PALETTE.white(names[names.length - 1]);
|
|
946
|
+
|
|
947
|
+
return `${base.join(PALETTE.midGray(', '))}${
|
|
948
|
+
base.length ? PALETTE.midGray(', and ') : ''
|
|
949
|
+
}${last}`;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
private renderBanner(_extendMode: boolean): void {
|
|
953
|
+
const rows = ['', '', '', '', ''];
|
|
954
|
+
for (const char of 'OPENSPEC') {
|
|
955
|
+
const glyph = LETTER_MAP[char] ?? LETTER_MAP[' '];
|
|
956
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
957
|
+
rows[i] += `${glyph[i]} `;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const rowStyles = [
|
|
962
|
+
PALETTE.white,
|
|
963
|
+
PALETTE.lightGray,
|
|
964
|
+
PALETTE.midGray,
|
|
965
|
+
PALETTE.lightGray,
|
|
966
|
+
PALETTE.white,
|
|
967
|
+
];
|
|
968
|
+
|
|
969
|
+
console.log();
|
|
970
|
+
rows.forEach((row, index) => {
|
|
971
|
+
console.log(rowStyles[index](row.replace(/\s+$/u, '')));
|
|
972
|
+
});
|
|
973
|
+
console.log();
|
|
974
|
+
console.log(PALETTE.white('Welcome to OpenSpec!'));
|
|
975
|
+
console.log();
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
private startSpinner(text: string) {
|
|
979
|
+
return ora({
|
|
980
|
+
text,
|
|
981
|
+
stream: process.stdout,
|
|
982
|
+
color: 'gray',
|
|
983
|
+
spinner: PROGRESS_SPINNER,
|
|
984
|
+
}).start();
|
|
985
|
+
}
|
|
986
|
+
}
|