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,129 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
3
|
+
import { OPENSPEC_DIR_NAME } from './config.js';
|
|
4
|
+
import { ToolRegistry } from './configurators/registry.js';
|
|
5
|
+
import { SlashCommandRegistry } from './configurators/slash/registry.js';
|
|
6
|
+
import { agentsTemplate } from './templates/agents-template.js';
|
|
7
|
+
|
|
8
|
+
export class UpdateCommand {
|
|
9
|
+
async execute(projectPath: string): Promise<void> {
|
|
10
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
11
|
+
const openspecDirName = OPENSPEC_DIR_NAME;
|
|
12
|
+
const openspecPath = path.join(resolvedProjectPath, openspecDirName);
|
|
13
|
+
|
|
14
|
+
// 1. Check openspec directory exists
|
|
15
|
+
if (!await FileSystemUtils.directoryExists(openspecPath)) {
|
|
16
|
+
throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 2. Update AGENTS.md (full replacement)
|
|
20
|
+
const agentsPath = path.join(openspecPath, 'AGENTS.md');
|
|
21
|
+
|
|
22
|
+
await FileSystemUtils.writeFile(agentsPath, agentsTemplate);
|
|
23
|
+
|
|
24
|
+
// 3. Update existing AI tool configuration files only
|
|
25
|
+
const configurators = ToolRegistry.getAll();
|
|
26
|
+
const slashConfigurators = SlashCommandRegistry.getAll();
|
|
27
|
+
const updatedFiles: string[] = [];
|
|
28
|
+
const createdFiles: string[] = [];
|
|
29
|
+
const failedFiles: string[] = [];
|
|
30
|
+
const updatedSlashFiles: string[] = [];
|
|
31
|
+
const failedSlashTools: string[] = [];
|
|
32
|
+
|
|
33
|
+
for (const configurator of configurators) {
|
|
34
|
+
const configFilePath = path.join(
|
|
35
|
+
resolvedProjectPath,
|
|
36
|
+
configurator.configFileName
|
|
37
|
+
);
|
|
38
|
+
const fileExists = await FileSystemUtils.fileExists(configFilePath);
|
|
39
|
+
const shouldConfigure =
|
|
40
|
+
fileExists || configurator.configFileName === 'AGENTS.md';
|
|
41
|
+
|
|
42
|
+
if (!shouldConfigure) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
if (fileExists && !await FileSystemUtils.canWriteFile(configFilePath)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Insufficient permissions to modify ${configurator.configFileName}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await configurator.configure(resolvedProjectPath, openspecPath);
|
|
54
|
+
updatedFiles.push(configurator.configFileName);
|
|
55
|
+
|
|
56
|
+
if (!fileExists) {
|
|
57
|
+
createdFiles.push(configurator.configFileName);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
failedFiles.push(configurator.configFileName);
|
|
61
|
+
console.error(
|
|
62
|
+
`Failed to update ${configurator.configFileName}: ${
|
|
63
|
+
error instanceof Error ? error.message : String(error)
|
|
64
|
+
}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const slashConfigurator of slashConfigurators) {
|
|
70
|
+
if (!slashConfigurator.isAvailable) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const updated = await slashConfigurator.updateExisting(
|
|
76
|
+
resolvedProjectPath,
|
|
77
|
+
openspecPath
|
|
78
|
+
);
|
|
79
|
+
updatedSlashFiles.push(...updated);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
failedSlashTools.push(slashConfigurator.toolId);
|
|
82
|
+
console.error(
|
|
83
|
+
`Failed to update slash commands for ${slashConfigurator.toolId}: ${
|
|
84
|
+
error instanceof Error ? error.message : String(error)
|
|
85
|
+
}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const summaryParts: string[] = [];
|
|
91
|
+
const instructionFiles: string[] = ['openspec/AGENTS.md'];
|
|
92
|
+
|
|
93
|
+
if (updatedFiles.includes('AGENTS.md')) {
|
|
94
|
+
instructionFiles.push(
|
|
95
|
+
createdFiles.includes('AGENTS.md') ? 'AGENTS.md (created)' : 'AGENTS.md'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
summaryParts.push(
|
|
100
|
+
`Updated OpenSpec instructions (${instructionFiles.join(', ')})`
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const aiToolFiles = updatedFiles.filter((file) => file !== 'AGENTS.md');
|
|
104
|
+
if (aiToolFiles.length > 0) {
|
|
105
|
+
summaryParts.push(`Updated AI tool files: ${aiToolFiles.join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (updatedSlashFiles.length > 0) {
|
|
109
|
+
// Normalize to forward slashes for cross-platform log consistency
|
|
110
|
+
const normalized = updatedSlashFiles.map((p) => p.replace(/\\/g, '/'));
|
|
111
|
+
summaryParts.push(`Updated slash commands: ${normalized.join(', ')}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const failedItems = [
|
|
115
|
+
...failedFiles,
|
|
116
|
+
...failedSlashTools.map(
|
|
117
|
+
(toolId) => `slash command refresh (${toolId})`
|
|
118
|
+
),
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
if (failedItems.length > 0) {
|
|
122
|
+
summaryParts.push(`Failed to update: ${failedItems.join(', ')}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(summaryParts.join(' | '));
|
|
126
|
+
|
|
127
|
+
// No additional notes
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation threshold constants
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Minimum character lengths
|
|
6
|
+
export const MIN_WHY_SECTION_LENGTH = 50;
|
|
7
|
+
export const MIN_PURPOSE_LENGTH = 50;
|
|
8
|
+
|
|
9
|
+
// Maximum character/item limits
|
|
10
|
+
export const MAX_WHY_SECTION_LENGTH = 1000;
|
|
11
|
+
export const MAX_REQUIREMENT_TEXT_LENGTH = 500;
|
|
12
|
+
export const MAX_DELTAS_PER_CHANGE = 10;
|
|
13
|
+
|
|
14
|
+
// Validation messages
|
|
15
|
+
export const VALIDATION_MESSAGES = {
|
|
16
|
+
// Required content
|
|
17
|
+
SCENARIO_EMPTY: 'Scenario text cannot be empty',
|
|
18
|
+
REQUIREMENT_EMPTY: 'Requirement text cannot be empty',
|
|
19
|
+
REQUIREMENT_NO_SHALL: 'Requirement must contain SHALL or MUST keyword',
|
|
20
|
+
REQUIREMENT_NO_SCENARIOS: 'Requirement must have at least one scenario',
|
|
21
|
+
SPEC_NAME_EMPTY: 'Spec name cannot be empty',
|
|
22
|
+
SPEC_PURPOSE_EMPTY: 'Purpose section cannot be empty',
|
|
23
|
+
SPEC_NO_REQUIREMENTS: 'Spec must have at least one requirement',
|
|
24
|
+
CHANGE_NAME_EMPTY: 'Change name cannot be empty',
|
|
25
|
+
CHANGE_WHY_TOO_SHORT: `Why section must be at least ${MIN_WHY_SECTION_LENGTH} characters`,
|
|
26
|
+
CHANGE_WHY_TOO_LONG: `Why section should not exceed ${MAX_WHY_SECTION_LENGTH} characters`,
|
|
27
|
+
CHANGE_WHAT_EMPTY: 'What Changes section cannot be empty',
|
|
28
|
+
CHANGE_NO_DELTAS: 'Change must have at least one delta',
|
|
29
|
+
CHANGE_TOO_MANY_DELTAS: `Consider splitting changes with more than ${MAX_DELTAS_PER_CHANGE} deltas`,
|
|
30
|
+
DELTA_SPEC_EMPTY: 'Spec name cannot be empty',
|
|
31
|
+
DELTA_DESCRIPTION_EMPTY: 'Delta description cannot be empty',
|
|
32
|
+
|
|
33
|
+
// Warnings
|
|
34
|
+
PURPOSE_TOO_BRIEF: `Purpose section is too brief (less than ${MIN_PURPOSE_LENGTH} characters)`,
|
|
35
|
+
REQUIREMENT_TOO_LONG: `Requirement text is very long (>${MAX_REQUIREMENT_TEXT_LENGTH} characters). Consider breaking it down.`,
|
|
36
|
+
DELTA_DESCRIPTION_TOO_BRIEF: 'Delta description is too brief',
|
|
37
|
+
DELTA_MISSING_REQUIREMENTS: 'Delta should include requirements',
|
|
38
|
+
|
|
39
|
+
// Guidance snippets (appended to primary messages for remediation)
|
|
40
|
+
GUIDE_NO_DELTAS:
|
|
41
|
+
'No deltas found. Ensure your change has a specs/ directory with capability folders (e.g. specs/http-server/spec.md) containing .md files that use delta headers (## ADDED/MODIFIED/REMOVED/RENAMED Requirements) and that each requirement includes at least one "#### Scenario:" block. Tip: run "openspec change show <change-id> --json --deltas-only" to inspect parsed deltas.',
|
|
42
|
+
GUIDE_MISSING_SPEC_SECTIONS:
|
|
43
|
+
'Missing required sections. Expected headers: "## Purpose" and "## Requirements". Example:\n## Purpose\n[brief purpose]\n\n## Requirements\n### Requirement: Clear requirement statement\nUsers SHALL ...\n\n#### Scenario: Descriptive name\n- **WHEN** ...\n- **THEN** ...',
|
|
44
|
+
GUIDE_MISSING_CHANGE_SECTIONS:
|
|
45
|
+
'Missing required sections. Expected headers: "## Why" and "## What Changes". Ensure deltas are documented in specs/ using delta headers.',
|
|
46
|
+
GUIDE_SCENARIO_FORMAT:
|
|
47
|
+
'Scenarios must use level-4 headers. Convert bullet lists into:\n#### Scenario: Short name\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...',
|
|
48
|
+
} as const;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ValidationLevel = 'ERROR' | 'WARNING' | 'INFO';
|
|
2
|
+
|
|
3
|
+
export interface ValidationIssue {
|
|
4
|
+
level: ValidationLevel;
|
|
5
|
+
path: string;
|
|
6
|
+
message: string;
|
|
7
|
+
line?: number;
|
|
8
|
+
column?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ValidationReport {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
issues: ValidationIssue[];
|
|
14
|
+
summary: {
|
|
15
|
+
errors: number;
|
|
16
|
+
warnings: number;
|
|
17
|
+
info: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { z, ZodError } from 'zod';
|
|
2
|
+
import { readFileSync, promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { SpecSchema, ChangeSchema, Spec, Change } from '../schemas/index.js';
|
|
5
|
+
import { MarkdownParser } from '../parsers/markdown-parser.js';
|
|
6
|
+
import { ChangeParser } from '../parsers/change-parser.js';
|
|
7
|
+
import { ValidationReport, ValidationIssue, ValidationLevel } from './types.js';
|
|
8
|
+
import {
|
|
9
|
+
MIN_PURPOSE_LENGTH,
|
|
10
|
+
MAX_REQUIREMENT_TEXT_LENGTH,
|
|
11
|
+
VALIDATION_MESSAGES
|
|
12
|
+
} from './constants.js';
|
|
13
|
+
import { parseDeltaSpec, normalizeRequirementName } from '../parsers/requirement-blocks.js';
|
|
14
|
+
|
|
15
|
+
export class Validator {
|
|
16
|
+
private strictMode: boolean;
|
|
17
|
+
|
|
18
|
+
constructor(strictMode: boolean = false) {
|
|
19
|
+
this.strictMode = strictMode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async validateSpec(filePath: string): Promise<ValidationReport> {
|
|
23
|
+
const issues: ValidationIssue[] = [];
|
|
24
|
+
const specName = this.extractNameFromPath(filePath);
|
|
25
|
+
try {
|
|
26
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
27
|
+
const parser = new MarkdownParser(content);
|
|
28
|
+
|
|
29
|
+
const spec = parser.parseSpec(specName);
|
|
30
|
+
|
|
31
|
+
const result = SpecSchema.safeParse(spec);
|
|
32
|
+
|
|
33
|
+
if (!result.success) {
|
|
34
|
+
issues.push(...this.convertZodErrors(result.error));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
issues.push(...this.applySpecRules(spec, content));
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const baseMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
41
|
+
const enriched = this.enrichTopLevelError(specName, baseMessage);
|
|
42
|
+
issues.push({
|
|
43
|
+
level: 'ERROR',
|
|
44
|
+
path: 'file',
|
|
45
|
+
message: enriched,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this.createReport(issues);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate spec content from a string (used for pre-write validation of rebuilt specs)
|
|
54
|
+
*/
|
|
55
|
+
async validateSpecContent(specName: string, content: string): Promise<ValidationReport> {
|
|
56
|
+
const issues: ValidationIssue[] = [];
|
|
57
|
+
try {
|
|
58
|
+
const parser = new MarkdownParser(content);
|
|
59
|
+
const spec = parser.parseSpec(specName);
|
|
60
|
+
const result = SpecSchema.safeParse(spec);
|
|
61
|
+
if (!result.success) {
|
|
62
|
+
issues.push(...this.convertZodErrors(result.error));
|
|
63
|
+
}
|
|
64
|
+
issues.push(...this.applySpecRules(spec, content));
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const baseMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
67
|
+
const enriched = this.enrichTopLevelError(specName, baseMessage);
|
|
68
|
+
issues.push({ level: 'ERROR', path: 'file', message: enriched });
|
|
69
|
+
}
|
|
70
|
+
return this.createReport(issues);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async validateChange(filePath: string): Promise<ValidationReport> {
|
|
74
|
+
const issues: ValidationIssue[] = [];
|
|
75
|
+
const changeName = this.extractNameFromPath(filePath);
|
|
76
|
+
try {
|
|
77
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
78
|
+
const changeDir = path.dirname(filePath);
|
|
79
|
+
const parser = new ChangeParser(content, changeDir);
|
|
80
|
+
|
|
81
|
+
const change = await parser.parseChangeWithDeltas(changeName);
|
|
82
|
+
|
|
83
|
+
const result = ChangeSchema.safeParse(change);
|
|
84
|
+
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
issues.push(...this.convertZodErrors(result.error));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
issues.push(...this.applyChangeRules(change, content));
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
const baseMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
93
|
+
const enriched = this.enrichTopLevelError(changeName, baseMessage);
|
|
94
|
+
issues.push({
|
|
95
|
+
level: 'ERROR',
|
|
96
|
+
path: 'file',
|
|
97
|
+
message: enriched,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return this.createReport(issues);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validate delta-formatted spec files under a change directory.
|
|
106
|
+
* Enforces:
|
|
107
|
+
* - At least one delta across all files
|
|
108
|
+
* - ADDED/MODIFIED: each requirement has SHALL/MUST and at least one scenario
|
|
109
|
+
* - REMOVED: names only; no scenario/description required
|
|
110
|
+
* - RENAMED: pairs well-formed
|
|
111
|
+
* - No duplicates within sections; no cross-section conflicts per spec
|
|
112
|
+
*/
|
|
113
|
+
async validateChangeDeltaSpecs(changeDir: string): Promise<ValidationReport> {
|
|
114
|
+
const issues: ValidationIssue[] = [];
|
|
115
|
+
const specsDir = path.join(changeDir, 'specs');
|
|
116
|
+
let totalDeltas = 0;
|
|
117
|
+
const missingHeaderSpecs: string[] = [];
|
|
118
|
+
const emptySectionSpecs: Array<{ path: string; sections: string[] }> = [];
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const entries = await fs.readdir(specsDir, { withFileTypes: true });
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
if (!entry.isDirectory()) continue;
|
|
124
|
+
const specName = entry.name;
|
|
125
|
+
const specFile = path.join(specsDir, specName, 'spec.md');
|
|
126
|
+
let content: string | undefined;
|
|
127
|
+
try {
|
|
128
|
+
content = await fs.readFile(specFile, 'utf-8');
|
|
129
|
+
} catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const plan = parseDeltaSpec(content);
|
|
134
|
+
const entryPath = `${specName}/spec.md`;
|
|
135
|
+
const sectionNames: string[] = [];
|
|
136
|
+
if (plan.sectionPresence.added) sectionNames.push('## ADDED Requirements');
|
|
137
|
+
if (plan.sectionPresence.modified) sectionNames.push('## MODIFIED Requirements');
|
|
138
|
+
if (plan.sectionPresence.removed) sectionNames.push('## REMOVED Requirements');
|
|
139
|
+
if (plan.sectionPresence.renamed) sectionNames.push('## RENAMED Requirements');
|
|
140
|
+
const hasSections = sectionNames.length > 0;
|
|
141
|
+
const hasEntries = plan.added.length + plan.modified.length + plan.removed.length + plan.renamed.length > 0;
|
|
142
|
+
if (!hasEntries) {
|
|
143
|
+
if (hasSections) emptySectionSpecs.push({ path: entryPath, sections: sectionNames });
|
|
144
|
+
else missingHeaderSpecs.push(entryPath);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const addedNames = new Set<string>();
|
|
148
|
+
const modifiedNames = new Set<string>();
|
|
149
|
+
const removedNames = new Set<string>();
|
|
150
|
+
const renamedFrom = new Set<string>();
|
|
151
|
+
const renamedTo = new Set<string>();
|
|
152
|
+
|
|
153
|
+
// Validate ADDED
|
|
154
|
+
for (const block of plan.added) {
|
|
155
|
+
const key = normalizeRequirementName(block.name);
|
|
156
|
+
totalDeltas++;
|
|
157
|
+
if (addedNames.has(key)) {
|
|
158
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in ADDED: "${block.name}"` });
|
|
159
|
+
} else {
|
|
160
|
+
addedNames.add(key);
|
|
161
|
+
}
|
|
162
|
+
const requirementText = this.extractRequirementText(block.raw);
|
|
163
|
+
if (!requirementText) {
|
|
164
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" is missing requirement text` });
|
|
165
|
+
} else if (!this.containsShallOrMust(requirementText)) {
|
|
166
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must contain SHALL or MUST` });
|
|
167
|
+
}
|
|
168
|
+
const scenarioCount = this.countScenarios(block.raw);
|
|
169
|
+
if (scenarioCount < 1) {
|
|
170
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `ADDED "${block.name}" must include at least one scenario` });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Validate MODIFIED
|
|
175
|
+
for (const block of plan.modified) {
|
|
176
|
+
const key = normalizeRequirementName(block.name);
|
|
177
|
+
totalDeltas++;
|
|
178
|
+
if (modifiedNames.has(key)) {
|
|
179
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in MODIFIED: "${block.name}"` });
|
|
180
|
+
} else {
|
|
181
|
+
modifiedNames.add(key);
|
|
182
|
+
}
|
|
183
|
+
const requirementText = this.extractRequirementText(block.raw);
|
|
184
|
+
if (!requirementText) {
|
|
185
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" is missing requirement text` });
|
|
186
|
+
} else if (!this.containsShallOrMust(requirementText)) {
|
|
187
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" must contain SHALL or MUST` });
|
|
188
|
+
}
|
|
189
|
+
const scenarioCount = this.countScenarios(block.raw);
|
|
190
|
+
if (scenarioCount < 1) {
|
|
191
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED "${block.name}" must include at least one scenario` });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Validate REMOVED (names only)
|
|
196
|
+
for (const name of plan.removed) {
|
|
197
|
+
const key = normalizeRequirementName(name);
|
|
198
|
+
totalDeltas++;
|
|
199
|
+
if (removedNames.has(key)) {
|
|
200
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate requirement in REMOVED: "${name}"` });
|
|
201
|
+
} else {
|
|
202
|
+
removedNames.add(key);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Validate RENAMED pairs
|
|
207
|
+
for (const { from, to } of plan.renamed) {
|
|
208
|
+
const fromKey = normalizeRequirementName(from);
|
|
209
|
+
const toKey = normalizeRequirementName(to);
|
|
210
|
+
totalDeltas++;
|
|
211
|
+
if (renamedFrom.has(fromKey)) {
|
|
212
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate FROM in RENAMED: "${from}"` });
|
|
213
|
+
} else {
|
|
214
|
+
renamedFrom.add(fromKey);
|
|
215
|
+
}
|
|
216
|
+
if (renamedTo.has(toKey)) {
|
|
217
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Duplicate TO in RENAMED: "${to}"` });
|
|
218
|
+
} else {
|
|
219
|
+
renamedTo.add(toKey);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Cross-section conflicts (within the same spec file)
|
|
224
|
+
for (const n of modifiedNames) {
|
|
225
|
+
if (removedNames.has(n)) {
|
|
226
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both MODIFIED and REMOVED: "${n}"` });
|
|
227
|
+
}
|
|
228
|
+
if (addedNames.has(n)) {
|
|
229
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both MODIFIED and ADDED: "${n}"` });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const n of addedNames) {
|
|
233
|
+
if (removedNames.has(n)) {
|
|
234
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `Requirement present in both ADDED and REMOVED: "${n}"` });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const { from, to } of plan.renamed) {
|
|
238
|
+
const fromKey = normalizeRequirementName(from);
|
|
239
|
+
const toKey = normalizeRequirementName(to);
|
|
240
|
+
if (modifiedNames.has(fromKey)) {
|
|
241
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `MODIFIED references old name from RENAMED. Use new header for "${to}"` });
|
|
242
|
+
}
|
|
243
|
+
if (addedNames.has(toKey)) {
|
|
244
|
+
issues.push({ level: 'ERROR', path: entryPath, message: `RENAMED TO collides with ADDED for "${to}"` });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
// If no specs dir, treat as no deltas
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const { path: specPath, sections } of emptySectionSpecs) {
|
|
253
|
+
issues.push({
|
|
254
|
+
level: 'ERROR',
|
|
255
|
+
path: specPath,
|
|
256
|
+
message: `Delta sections ${this.formatSectionList(sections)} were found, but no requirement entries parsed. Ensure each section includes at least one "### Requirement:" block (REMOVED may use bullet list syntax).`,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
for (const path of missingHeaderSpecs) {
|
|
260
|
+
issues.push({
|
|
261
|
+
level: 'ERROR',
|
|
262
|
+
path,
|
|
263
|
+
message: 'No delta sections found. Add headers such as "## ADDED Requirements" or move non-delta notes outside specs/.',
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (totalDeltas === 0) {
|
|
268
|
+
issues.push({ level: 'ERROR', path: 'file', message: this.enrichTopLevelError('change', VALIDATION_MESSAGES.CHANGE_NO_DELTAS) });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return this.createReport(issues);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private convertZodErrors(error: ZodError): ValidationIssue[] {
|
|
275
|
+
return error.issues.map(err => {
|
|
276
|
+
let message = err.message;
|
|
277
|
+
if (message === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
|
|
278
|
+
message = `${message}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
level: 'ERROR' as ValidationLevel,
|
|
282
|
+
path: err.path.join('.'),
|
|
283
|
+
message,
|
|
284
|
+
};
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private applySpecRules(spec: Spec, content: string): ValidationIssue[] {
|
|
289
|
+
const issues: ValidationIssue[] = [];
|
|
290
|
+
|
|
291
|
+
if (spec.overview.length < MIN_PURPOSE_LENGTH) {
|
|
292
|
+
issues.push({
|
|
293
|
+
level: 'WARNING',
|
|
294
|
+
path: 'overview',
|
|
295
|
+
message: VALIDATION_MESSAGES.PURPOSE_TOO_BRIEF,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
spec.requirements.forEach((req, index) => {
|
|
300
|
+
if (req.text.length > MAX_REQUIREMENT_TEXT_LENGTH) {
|
|
301
|
+
issues.push({
|
|
302
|
+
level: 'INFO',
|
|
303
|
+
path: `requirements[${index}]`,
|
|
304
|
+
message: VALIDATION_MESSAGES.REQUIREMENT_TOO_LONG,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (req.scenarios.length === 0) {
|
|
309
|
+
issues.push({
|
|
310
|
+
level: 'WARNING',
|
|
311
|
+
path: `requirements[${index}].scenarios`,
|
|
312
|
+
message: `${VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS}. ${VALIDATION_MESSAGES.GUIDE_SCENARIO_FORMAT}`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return issues;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private applyChangeRules(change: Change, content: string): ValidationIssue[] {
|
|
321
|
+
const issues: ValidationIssue[] = [];
|
|
322
|
+
|
|
323
|
+
const MIN_DELTA_DESCRIPTION_LENGTH = 10;
|
|
324
|
+
|
|
325
|
+
change.deltas.forEach((delta, index) => {
|
|
326
|
+
if (!delta.description || delta.description.length < MIN_DELTA_DESCRIPTION_LENGTH) {
|
|
327
|
+
issues.push({
|
|
328
|
+
level: 'WARNING',
|
|
329
|
+
path: `deltas[${index}].description`,
|
|
330
|
+
message: VALIDATION_MESSAGES.DELTA_DESCRIPTION_TOO_BRIEF,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if ((delta.operation === 'ADDED' || delta.operation === 'MODIFIED') &&
|
|
335
|
+
(!delta.requirements || delta.requirements.length === 0)) {
|
|
336
|
+
issues.push({
|
|
337
|
+
level: 'WARNING',
|
|
338
|
+
path: `deltas[${index}].requirements`,
|
|
339
|
+
message: `${delta.operation} ${VALIDATION_MESSAGES.DELTA_MISSING_REQUIREMENTS}`,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
return issues;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private enrichTopLevelError(itemId: string, baseMessage: string): string {
|
|
348
|
+
const msg = baseMessage.trim();
|
|
349
|
+
if (msg === VALIDATION_MESSAGES.CHANGE_NO_DELTAS) {
|
|
350
|
+
return `${msg}. ${VALIDATION_MESSAGES.GUIDE_NO_DELTAS}`;
|
|
351
|
+
}
|
|
352
|
+
if (msg.includes('Spec must have a Purpose section') || msg.includes('Spec must have a Requirements section')) {
|
|
353
|
+
return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_SPEC_SECTIONS}`;
|
|
354
|
+
}
|
|
355
|
+
if (msg.includes('Change must have a Why section') || msg.includes('Change must have a What Changes section')) {
|
|
356
|
+
return `${msg}. ${VALIDATION_MESSAGES.GUIDE_MISSING_CHANGE_SECTIONS}`;
|
|
357
|
+
}
|
|
358
|
+
return msg;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private extractNameFromPath(filePath: string): string {
|
|
362
|
+
const normalizedPath = filePath.replaceAll('\\', '/');
|
|
363
|
+
const parts = normalizedPath.split('/');
|
|
364
|
+
|
|
365
|
+
// Look for the directory name after 'specs' or 'changes'
|
|
366
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
367
|
+
if (parts[i] === 'specs' || parts[i] === 'changes') {
|
|
368
|
+
if (i < parts.length - 1) {
|
|
369
|
+
return parts[i + 1];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Fallback to filename without extension if not in expected structure
|
|
375
|
+
const fileName = parts[parts.length - 1] ?? '';
|
|
376
|
+
const dotIndex = fileName.lastIndexOf('.');
|
|
377
|
+
return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private createReport(issues: ValidationIssue[]): ValidationReport {
|
|
381
|
+
const errors = issues.filter(i => i.level === 'ERROR').length;
|
|
382
|
+
const warnings = issues.filter(i => i.level === 'WARNING').length;
|
|
383
|
+
const info = issues.filter(i => i.level === 'INFO').length;
|
|
384
|
+
|
|
385
|
+
const valid = this.strictMode
|
|
386
|
+
? errors === 0 && warnings === 0
|
|
387
|
+
: errors === 0;
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
valid,
|
|
391
|
+
issues,
|
|
392
|
+
summary: {
|
|
393
|
+
errors,
|
|
394
|
+
warnings,
|
|
395
|
+
info,
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
isValid(report: ValidationReport): boolean {
|
|
401
|
+
return report.valid;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private extractRequirementText(blockRaw: string): string | undefined {
|
|
405
|
+
const lines = blockRaw.split('\n');
|
|
406
|
+
// Skip header line (index 0)
|
|
407
|
+
let i = 1;
|
|
408
|
+
|
|
409
|
+
// Find the first substantial text line, skipping metadata and blank lines
|
|
410
|
+
for (; i < lines.length; i++) {
|
|
411
|
+
const line = lines[i];
|
|
412
|
+
|
|
413
|
+
// Stop at scenario headers
|
|
414
|
+
if (/^####\s+/.test(line)) break;
|
|
415
|
+
|
|
416
|
+
const trimmed = line.trim();
|
|
417
|
+
|
|
418
|
+
// Skip blank lines
|
|
419
|
+
if (trimmed.length === 0) continue;
|
|
420
|
+
|
|
421
|
+
// Skip metadata lines (lines starting with ** like **ID**, **Priority**, etc.)
|
|
422
|
+
if (/^\*\*[^*]+\*\*:/.test(trimmed)) continue;
|
|
423
|
+
|
|
424
|
+
// Found first non-metadata, non-blank line - this is the requirement text
|
|
425
|
+
return trimmed;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// No requirement text found
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private containsShallOrMust(text: string): boolean {
|
|
433
|
+
return /\b(SHALL|MUST)\b/.test(text);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private countScenarios(blockRaw: string): number {
|
|
437
|
+
const matches = blockRaw.match(/^####\s+/gm);
|
|
438
|
+
return matches ? matches.length : 0;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private formatSectionList(sections: string[]): string {
|
|
442
|
+
if (sections.length === 0) return '';
|
|
443
|
+
if (sections.length === 1) return sections[0];
|
|
444
|
+
const head = sections.slice(0, -1);
|
|
445
|
+
const last = sections[sections.length - 1];
|
|
446
|
+
return `${head.join(', ')} and ${last}`;
|
|
447
|
+
}
|
|
448
|
+
}
|