@superkou/openspec 1.4.1
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/LICENSE +22 -0
- package/README.md +213 -0
- package/bin/openspec.js +5 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +544 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +277 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +264 -0
- package/dist/commands/config.d.ts +36 -0
- package/dist/commands/config.js +611 -0
- package/dist/commands/context-store.d.ts +3 -0
- package/dist/commands/context-store.js +475 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +183 -0
- package/dist/commands/initiative.d.ts +13 -0
- package/dist/commands/initiative.js +318 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -0
- package/dist/commands/show.d.ts +14 -0
- package/dist/commands/show.js +132 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +225 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +294 -0
- package/dist/commands/workflow/index.d.ts +19 -0
- package/dist/commands/workflow/index.js +13 -0
- package/dist/commands/workflow/initiative-link.d.ts +24 -0
- package/dist/commands/workflow/initiative-link.js +47 -0
- package/dist/commands/workflow/instructions.d.ts +29 -0
- package/dist/commands/workflow/instructions.js +344 -0
- package/dist/commands/workflow/new-change.d.ts +17 -0
- package/dist/commands/workflow/new-change.js +141 -0
- package/dist/commands/workflow/schemas.d.ts +10 -0
- package/dist/commands/workflow/schemas.js +34 -0
- package/dist/commands/workflow/set-change.d.ts +13 -0
- package/dist/commands/workflow/set-change.js +87 -0
- package/dist/commands/workflow/shared.d.ts +59 -0
- package/dist/commands/workflow/shared.js +116 -0
- package/dist/commands/workflow/status.d.ts +14 -0
- package/dist/commands/workflow/status.js +90 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +69 -0
- package/dist/commands/workspace/context-status.d.ts +4 -0
- package/dist/commands/workspace/context-status.js +59 -0
- package/dist/commands/workspace/open-target-selection.d.ts +13 -0
- package/dist/commands/workspace/open-target-selection.js +146 -0
- package/dist/commands/workspace/open-view.d.ts +62 -0
- package/dist/commands/workspace/open-view.js +249 -0
- package/dist/commands/workspace/open.d.ts +37 -0
- package/dist/commands/workspace/open.js +110 -0
- package/dist/commands/workspace/opener-selection.d.ts +11 -0
- package/dist/commands/workspace/opener-selection.js +98 -0
- package/dist/commands/workspace/operations.d.ts +29 -0
- package/dist/commands/workspace/operations.js +543 -0
- package/dist/commands/workspace/prompt-theme.d.ts +29 -0
- package/dist/commands/workspace/prompt-theme.js +24 -0
- package/dist/commands/workspace/registration.d.ts +13 -0
- package/dist/commands/workspace/registration.js +84 -0
- package/dist/commands/workspace/selection.d.ts +8 -0
- package/dist/commands/workspace/selection.js +104 -0
- package/dist/commands/workspace/setup-prompts.d.ts +13 -0
- package/dist/commands/workspace/setup-prompts.js +121 -0
- package/dist/commands/workspace/types.d.ts +103 -0
- package/dist/commands/workspace/types.js +36 -0
- package/dist/commands/workspace.d.ts +5 -0
- package/dist/commands/workspace.js +577 -0
- package/dist/core/archive.d.ts +11 -0
- package/dist/core/archive.js +318 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +9 -0
- package/dist/core/artifact-graph/index.js +14 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +183 -0
- package/dist/core/artifact-graph/instruction-loader.js +256 -0
- package/dist/core/artifact-graph/outputs.d.ts +14 -0
- package/dist/core/artifact-graph/outputs.js +39 -0
- package/dist/core/artifact-graph/resolver.d.ts +81 -0
- package/dist/core/artifact-graph/resolver.js +257 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +31 -0
- package/dist/core/artifact-graph/types.d.ts +40 -0
- package/dist/core/artifact-graph/types.js +29 -0
- package/dist/core/available-tools.d.ts +17 -0
- package/dist/core/available-tools.js +43 -0
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +18 -0
- package/dist/core/change-metadata/schema.js +28 -0
- package/dist/core/change-status-policy.d.ts +50 -0
- package/dist/core/change-status-policy.js +70 -0
- package/dist/core/collections/index.d.ts +3 -0
- package/dist/core/collections/index.js +3 -0
- package/dist/core/collections/initiatives/collection.d.ts +4 -0
- package/dist/core/collections/initiatives/collection.js +17 -0
- package/dist/core/collections/initiatives/index.d.ts +6 -0
- package/dist/core/collections/initiatives/index.js +6 -0
- package/dist/core/collections/initiatives/operations.d.ts +49 -0
- package/dist/core/collections/initiatives/operations.js +175 -0
- package/dist/core/collections/initiatives/resolution.d.ts +87 -0
- package/dist/core/collections/initiatives/resolution.js +374 -0
- package/dist/core/collections/initiatives/schema.d.ts +41 -0
- package/dist/core/collections/initiatives/schema.js +134 -0
- package/dist/core/collections/initiatives/templates.d.ts +12 -0
- package/dist/core/collections/initiatives/templates.js +90 -0
- package/dist/core/collections/runtime.d.ts +46 -0
- package/dist/core/collections/runtime.js +194 -0
- package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
- package/dist/core/command-generation/adapters/amazon-q.js +26 -0
- package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
- package/dist/core/command-generation/adapters/antigravity.js +26 -0
- package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
- package/dist/core/command-generation/adapters/auggie.js +27 -0
- package/dist/core/command-generation/adapters/bob.d.ts +14 -0
- package/dist/core/command-generation/adapters/bob.js +45 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +50 -0
- package/dist/core/command-generation/adapters/cline.d.ts +14 -0
- package/dist/core/command-generation/adapters/cline.js +27 -0
- package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
- package/dist/core/command-generation/adapters/codebuddy.js +28 -0
- package/dist/core/command-generation/adapters/codex.d.ts +16 -0
- package/dist/core/command-generation/adapters/codex.js +39 -0
- package/dist/core/command-generation/adapters/continue.d.ts +13 -0
- package/dist/core/command-generation/adapters/continue.js +28 -0
- package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
- package/dist/core/command-generation/adapters/costrict.js +27 -0
- package/dist/core/command-generation/adapters/crush.d.ts +13 -0
- package/dist/core/command-generation/adapters/crush.js +30 -0
- package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
- package/dist/core/command-generation/adapters/cursor.js +44 -0
- package/dist/core/command-generation/adapters/factory.d.ts +13 -0
- package/dist/core/command-generation/adapters/factory.js +27 -0
- package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
- package/dist/core/command-generation/adapters/gemini.js +26 -0
- package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
- package/dist/core/command-generation/adapters/github-copilot.js +26 -0
- package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
- package/dist/core/command-generation/adapters/iflow.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +32 -0
- package/dist/core/command-generation/adapters/index.js +32 -0
- package/dist/core/command-generation/adapters/junie.d.ts +13 -0
- package/dist/core/command-generation/adapters/junie.js +26 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/kilocode.js +23 -0
- package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
- package/dist/core/command-generation/adapters/kiro.js +26 -0
- package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
- package/dist/core/command-generation/adapters/lingma.js +30 -0
- package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
- package/dist/core/command-generation/adapters/opencode.js +29 -0
- package/dist/core/command-generation/adapters/pi.d.ts +18 -0
- package/dist/core/command-generation/adapters/pi.js +55 -0
- package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
- package/dist/core/command-generation/adapters/qoder.js +30 -0
- package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
- package/dist/core/command-generation/adapters/qwen.js +26 -0
- package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/roocode.js +27 -0
- package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
- package/dist/core/command-generation/adapters/windsurf.js +51 -0
- package/dist/core/command-generation/generator.d.ts +21 -0
- package/dist/core/command-generation/generator.js +27 -0
- package/dist/core/command-generation/index.d.ts +22 -0
- package/dist/core/command-generation/index.js +24 -0
- package/dist/core/command-generation/registry.d.ts +36 -0
- package/dist/core/command-generation/registry.js +98 -0
- package/dist/core/command-generation/types.d.ts +56 -0
- package/dist/core/command-generation/types.js +8 -0
- package/dist/core/completions/command-registry.d.ts +3 -0
- package/dist/core/completions/command-registry.js +961 -0
- package/dist/core/completions/completion-provider.d.ts +71 -0
- package/dist/core/completions/completion-provider.js +129 -0
- package/dist/core/completions/factory.d.ts +64 -0
- package/dist/core/completions/factory.js +75 -0
- package/dist/core/completions/generators/bash-generator.d.ts +35 -0
- package/dist/core/completions/generators/bash-generator.js +230 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +160 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
- package/dist/core/completions/generators/powershell-generator.js +266 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
- package/dist/core/completions/generators/zsh-generator.js +274 -0
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +318 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +143 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
- package/dist/core/completions/installers/powershell-installer.js +387 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
- package/dist/core/completions/installers/zsh-installer.js +421 -0
- package/dist/core/completions/shared-flags.d.ts +12 -0
- package/dist/core/completions/shared-flags.js +28 -0
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +30 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +45 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +34 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +45 -0
- package/dist/core/completions/types.d.ts +101 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +34 -0
- package/dist/core/config-schema.d.ts +86 -0
- package/dist/core/config-schema.js +213 -0
- package/dist/core/config.d.ts +18 -0
- package/dist/core/config.js +39 -0
- package/dist/core/context-store/binding.d.ts +53 -0
- package/dist/core/context-store/binding.js +197 -0
- package/dist/core/context-store/errors.d.ts +20 -0
- package/dist/core/context-store/errors.js +22 -0
- package/dist/core/context-store/foundation.d.ts +55 -0
- package/dist/core/context-store/foundation.js +321 -0
- package/dist/core/context-store/index.d.ts +6 -0
- package/dist/core/context-store/index.js +6 -0
- package/dist/core/context-store/operations.d.ts +85 -0
- package/dist/core/context-store/operations.js +528 -0
- package/dist/core/context-store/registry.d.ts +45 -0
- package/dist/core/context-store/registry.js +229 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +49 -0
- package/dist/core/global-config.js +124 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +7 -0
- package/dist/core/init.d.ts +37 -0
- package/dist/core/init.js +593 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +514 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -0
- package/dist/core/migration.d.ts +23 -0
- package/dist/core/migration.js +108 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +197 -0
- package/dist/core/parsers/markdown-parser.d.ts +26 -0
- package/dist/core/parsers/markdown-parser.js +227 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -0
- package/dist/core/parsers/spec-structure.d.ts +9 -0
- package/dist/core/parsers/spec-structure.js +88 -0
- package/dist/core/planning-home.d.ts +21 -0
- package/dist/core/planning-home.js +108 -0
- package/dist/core/profile-sync-drift.d.ts +38 -0
- package/dist/core/profile-sync-drift.js +200 -0
- package/dist/core/profiles.d.ts +26 -0
- package/dist/core/profiles.js +40 -0
- package/dist/core/project-config.d.ts +64 -0
- package/dist/core/project-config.js +223 -0
- package/dist/core/schemas/base.schema.d.ts +13 -0
- package/dist/core/schemas/base.schema.js +13 -0
- package/dist/core/schemas/change.schema.d.ts +73 -0
- package/dist/core/schemas/change.schema.js +31 -0
- package/dist/core/schemas/index.d.ts +4 -0
- package/dist/core/schemas/index.js +4 -0
- package/dist/core/schemas/spec.schema.d.ts +18 -0
- package/dist/core/schemas/spec.schema.js +15 -0
- package/dist/core/shared/index.d.ts +8 -0
- package/dist/core/shared/index.js +8 -0
- package/dist/core/shared/skill-generation.d.ts +49 -0
- package/dist/core/shared/skill-generation.js +96 -0
- package/dist/core/shared/tool-detection.d.ts +71 -0
- package/dist/core/shared/tool-detection.js +158 -0
- package/dist/core/specs-apply.d.ts +73 -0
- package/dist/core/specs-apply.js +392 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/index.d.ts +8 -0
- package/dist/core/templates/index.js +9 -0
- package/dist/core/templates/skill-templates.d.ts +19 -0
- package/dist/core/templates/skill-templates.js +18 -0
- package/dist/core/templates/types.d.ts +19 -0
- package/dist/core/templates/types.js +5 -0
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +314 -0
- package/dist/core/templates/workflows/archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/archive-change.js +277 -0
- package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
- package/dist/core/templates/workflows/continue-change.d.ts +10 -0
- package/dist/core/templates/workflows/continue-change.js +234 -0
- package/dist/core/templates/workflows/explore.d.ts +10 -0
- package/dist/core/templates/workflows/explore.js +459 -0
- package/dist/core/templates/workflows/feedback.d.ts +9 -0
- package/dist/core/templates/workflows/feedback.js +108 -0
- package/dist/core/templates/workflows/ff-change.d.ts +10 -0
- package/dist/core/templates/workflows/ff-change.js +200 -0
- package/dist/core/templates/workflows/new-change.d.ts +10 -0
- package/dist/core/templates/workflows/new-change.js +143 -0
- package/dist/core/templates/workflows/onboard.d.ts +10 -0
- package/dist/core/templates/workflows/onboard.js +563 -0
- package/dist/core/templates/workflows/propose.d.ts +10 -0
- package/dist/core/templates/workflows/propose.js +218 -0
- package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
- package/dist/core/templates/workflows/sync-specs.js +290 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +338 -0
- package/dist/core/update.d.ts +82 -0
- package/dist/core/update.js +557 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +43 -0
- package/dist/core/validation/validator.js +435 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/core/workspace/foundation.d.ts +67 -0
- package/dist/core/workspace/foundation.js +295 -0
- package/dist/core/workspace/index.d.ts +8 -0
- package/dist/core/workspace/index.js +8 -0
- package/dist/core/workspace/legacy-state.d.ts +28 -0
- package/dist/core/workspace/legacy-state.js +200 -0
- package/dist/core/workspace/link-input.d.ts +9 -0
- package/dist/core/workspace/link-input.js +32 -0
- package/dist/core/workspace/open-surface.d.ts +45 -0
- package/dist/core/workspace/open-surface.js +215 -0
- package/dist/core/workspace/openers.d.ts +21 -0
- package/dist/core/workspace/openers.js +124 -0
- package/dist/core/workspace/registry.d.ts +24 -0
- package/dist/core/workspace/registry.js +146 -0
- package/dist/core/workspace/skills.d.ts +57 -0
- package/dist/core/workspace/skills.js +334 -0
- package/dist/core/workspace/state-io.d.ts +10 -0
- package/dist/core/workspace/state-io.js +121 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +28 -0
- package/dist/prompts/searchable-multi-select.js +159 -0
- package/dist/telemetry/config.d.ts +38 -0
- package/dist/telemetry/config.js +136 -0
- package/dist/telemetry/index.d.ts +31 -0
- package/dist/telemetry/index.js +164 -0
- package/dist/ui/ascii-patterns.d.ts +16 -0
- package/dist/ui/ascii-patterns.js +133 -0
- package/dist/ui/welcome-screen.d.ts +10 -0
- package/dist/ui/welcome-screen.js +146 -0
- package/dist/utils/change-metadata.d.ts +54 -0
- package/dist/utils/change-metadata.js +141 -0
- package/dist/utils/change-utils.d.ts +71 -0
- package/dist/utils/change-utils.js +123 -0
- package/dist/utils/command-references.d.ts +18 -0
- package/dist/utils/command-references.js +20 -0
- package/dist/utils/file-system.d.ts +41 -0
- package/dist/utils/file-system.js +301 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/interactive.d.ts +18 -0
- package/dist/utils/interactive.js +21 -0
- package/dist/utils/item-discovery.d.ts +4 -0
- package/dist/utils/item-discovery.js +72 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/dist/utils/task-progress.d.ts +8 -0
- package/dist/utils/task-progress.js +36 -0
- package/package.json +85 -0
- package/schemas/spec-driven/schema.yaml +151 -0
- package/schemas/spec-driven/templates/design.md +19 -0
- package/schemas/spec-driven/templates/proposal.md +23 -0
- package/schemas/spec-driven/templates/spec.md +8 -0
- package/schemas/spec-driven/templates/tasks.md +9 -0
- package/schemas/workspace-planning/schema.yaml +72 -0
- package/schemas/workspace-planning/templates/design.md +33 -0
- package/schemas/workspace-planning/templates/proposal.md +28 -0
- package/schemas/workspace-planning/templates/spec.md +9 -0
- package/schemas/workspace-planning/templates/tasks.md +15 -0
- package/scripts/postinstall.js +83 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Command
|
|
3
|
+
*
|
|
4
|
+
* Refreshes OpenSpec skills and commands for configured tools.
|
|
5
|
+
* Supports profile-aware updates, delivery changes, migration, and smart update detection.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
13
|
+
import { transformToHyphenCommands } from '../utils/command-references.js';
|
|
14
|
+
import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
|
|
15
|
+
import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
|
|
16
|
+
import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, } from './shared/index.js';
|
|
17
|
+
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, getToolsFromLegacyArtifacts, } from './legacy-cleanup.js';
|
|
18
|
+
import { isInteractive } from '../utils/interactive.js';
|
|
19
|
+
import { getGlobalConfig } from './global-config.js';
|
|
20
|
+
import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
|
|
21
|
+
import { getAvailableTools } from './available-tools.js';
|
|
22
|
+
import { WORKFLOW_TO_SKILL_DIR, getCommandConfiguredTools, getConfiguredToolsForProfileSync, getToolsNeedingProfileSync, } from './profile-sync-drift.js';
|
|
23
|
+
import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, } from './migration.js';
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
const { version: OPENSPEC_VERSION } = require('../../package.json');
|
|
26
|
+
const OLD_CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'archive'];
|
|
27
|
+
/**
|
|
28
|
+
* Scans installed workflow artifacts (skills and managed commands) across all configured tools.
|
|
29
|
+
* Returns the union of detected workflow IDs that match ALL_WORKFLOWS.
|
|
30
|
+
*
|
|
31
|
+
* Wrapper around the shared migration module's scanInstalledWorkflows that accepts tool IDs.
|
|
32
|
+
*/
|
|
33
|
+
export function scanInstalledWorkflows(projectPath, toolIds) {
|
|
34
|
+
const tools = toolIds
|
|
35
|
+
.map((id) => AI_TOOLS.find((t) => t.value === id))
|
|
36
|
+
.filter((t) => t != null);
|
|
37
|
+
return scanInstalledWorkflowsShared(projectPath, tools);
|
|
38
|
+
}
|
|
39
|
+
export class UpdateCommand {
|
|
40
|
+
force;
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
this.force = options.force ?? false;
|
|
43
|
+
}
|
|
44
|
+
async execute(projectPath) {
|
|
45
|
+
const resolvedProjectPath = path.resolve(projectPath);
|
|
46
|
+
const openspecPath = path.join(resolvedProjectPath, OPENSPEC_DIR_NAME);
|
|
47
|
+
// 1. Check openspec directory exists
|
|
48
|
+
if (!await FileSystemUtils.directoryExists(openspecPath)) {
|
|
49
|
+
throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`);
|
|
50
|
+
}
|
|
51
|
+
// 2. Perform one-time migration if needed before any legacy upgrade generation.
|
|
52
|
+
// Use detected tool directories to preserve existing opsx skills/commands.
|
|
53
|
+
const detectedTools = getAvailableTools(resolvedProjectPath);
|
|
54
|
+
migrateIfNeededShared(resolvedProjectPath, detectedTools);
|
|
55
|
+
// 3. Read global config for profile/delivery
|
|
56
|
+
const globalConfig = getGlobalConfig();
|
|
57
|
+
const profile = globalConfig.profile ?? 'core';
|
|
58
|
+
const delivery = globalConfig.delivery ?? 'both';
|
|
59
|
+
const profileWorkflows = getProfileWorkflows(profile, globalConfig.workflows);
|
|
60
|
+
const desiredWorkflows = profileWorkflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
|
|
61
|
+
const shouldGenerateSkills = delivery !== 'commands';
|
|
62
|
+
const shouldGenerateCommands = delivery !== 'skills';
|
|
63
|
+
// 4. Detect and handle legacy artifacts + upgrade legacy tools using effective config
|
|
64
|
+
const newlyConfiguredTools = await this.handleLegacyCleanup(resolvedProjectPath, desiredWorkflows, delivery);
|
|
65
|
+
// 5. Find configured tools
|
|
66
|
+
const configuredTools = getConfiguredToolsForProfileSync(resolvedProjectPath);
|
|
67
|
+
if (configuredTools.length === 0 && newlyConfiguredTools.length === 0) {
|
|
68
|
+
console.log(chalk.yellow('未发现已配置的工具。'));
|
|
69
|
+
console.log(chalk.dim('运行 "openspec init" 来设置工具。'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// 6. Check version status for all configured tools
|
|
73
|
+
const commandConfiguredTools = getCommandConfiguredTools(resolvedProjectPath);
|
|
74
|
+
const commandConfiguredSet = new Set(commandConfiguredTools);
|
|
75
|
+
const toolStatuses = configuredTools.map((toolId) => {
|
|
76
|
+
const status = getToolVersionStatus(resolvedProjectPath, toolId, OPENSPEC_VERSION);
|
|
77
|
+
if (!status.configured && commandConfiguredSet.has(toolId)) {
|
|
78
|
+
return { ...status, configured: true };
|
|
79
|
+
}
|
|
80
|
+
return status;
|
|
81
|
+
});
|
|
82
|
+
const statusByTool = new Map(toolStatuses.map((status) => [status.toolId, status]));
|
|
83
|
+
// 7. Smart update detection
|
|
84
|
+
const toolsNeedingVersionUpdate = toolStatuses
|
|
85
|
+
.filter((s) => s.needsUpdate)
|
|
86
|
+
.map((s) => s.toolId);
|
|
87
|
+
const toolsNeedingConfigSync = getToolsNeedingProfileSync(resolvedProjectPath, desiredWorkflows, delivery, configuredTools);
|
|
88
|
+
const toolsToUpdateSet = new Set([
|
|
89
|
+
...toolsNeedingVersionUpdate,
|
|
90
|
+
...toolsNeedingConfigSync,
|
|
91
|
+
]);
|
|
92
|
+
const toolsUpToDate = toolStatuses.filter((s) => !toolsToUpdateSet.has(s.toolId));
|
|
93
|
+
if (!this.force && toolsToUpdateSet.size === 0) {
|
|
94
|
+
// All tools are up to date
|
|
95
|
+
this.displayUpToDateMessage(toolStatuses);
|
|
96
|
+
// Still check for new tool directories and extra workflows
|
|
97
|
+
this.detectNewTools(resolvedProjectPath, configuredTools);
|
|
98
|
+
this.displayExtraWorkflowsNote(resolvedProjectPath, configuredTools, desiredWorkflows);
|
|
99
|
+
this.displayOldCoreCustomProfileNote(profile, globalConfig.workflows);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// 8. Display update plan
|
|
103
|
+
if (this.force) {
|
|
104
|
+
console.log(`正在强制更新 ${configuredTools.length} 个工具:${configuredTools.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.displayUpdatePlan([...toolsToUpdateSet], statusByTool, toolsUpToDate);
|
|
108
|
+
}
|
|
109
|
+
console.log();
|
|
110
|
+
// 9. Determine what to generate based on delivery
|
|
111
|
+
const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
|
|
112
|
+
const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
|
|
113
|
+
// 10. Update tools (all if force, otherwise only those needing update)
|
|
114
|
+
const toolsToUpdate = this.force ? configuredTools : [...toolsToUpdateSet];
|
|
115
|
+
const updatedTools = [];
|
|
116
|
+
const failedTools = [];
|
|
117
|
+
let removedCommandCount = 0;
|
|
118
|
+
let removedSkillCount = 0;
|
|
119
|
+
let removedDeselectedCommandCount = 0;
|
|
120
|
+
let removedDeselectedSkillCount = 0;
|
|
121
|
+
for (const toolId of toolsToUpdate) {
|
|
122
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
123
|
+
if (!tool?.skillsDir)
|
|
124
|
+
continue;
|
|
125
|
+
const spinner = ora(`正在更新 ${tool.name}...`).start();
|
|
126
|
+
try {
|
|
127
|
+
const skillsDir = path.join(resolvedProjectPath, tool.skillsDir, 'skills');
|
|
128
|
+
// Generate skill files if delivery includes skills
|
|
129
|
+
if (shouldGenerateSkills) {
|
|
130
|
+
for (const { template, dirName } of skillTemplates) {
|
|
131
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
132
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
133
|
+
// Use hyphen-based command references for OpenCode
|
|
134
|
+
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
|
|
135
|
+
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
136
|
+
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
137
|
+
}
|
|
138
|
+
removedDeselectedSkillCount += await this.removeUnselectedSkillDirs(skillsDir, desiredWorkflows);
|
|
139
|
+
}
|
|
140
|
+
// Delete skill directories if delivery is commands-only
|
|
141
|
+
if (!shouldGenerateSkills) {
|
|
142
|
+
removedSkillCount += await this.removeSkillDirs(skillsDir);
|
|
143
|
+
}
|
|
144
|
+
// Generate commands if delivery includes commands
|
|
145
|
+
if (shouldGenerateCommands) {
|
|
146
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
147
|
+
if (adapter) {
|
|
148
|
+
const generatedCommands = generateCommands(commandContents, adapter);
|
|
149
|
+
for (const cmd of generatedCommands) {
|
|
150
|
+
const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(resolvedProjectPath, cmd.path);
|
|
151
|
+
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
152
|
+
}
|
|
153
|
+
removedDeselectedCommandCount += await this.removeUnselectedCommandFiles(resolvedProjectPath, toolId, desiredWorkflows);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Delete command files if delivery is skills-only
|
|
157
|
+
if (!shouldGenerateCommands) {
|
|
158
|
+
removedCommandCount += await this.removeCommandFiles(resolvedProjectPath, toolId);
|
|
159
|
+
}
|
|
160
|
+
spinner.succeed(`已更新 ${tool.name}`);
|
|
161
|
+
updatedTools.push(tool.name);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
spinner.fail(`更新 ${tool.name} 失败`);
|
|
165
|
+
failedTools.push({
|
|
166
|
+
name: tool.name,
|
|
167
|
+
error: error instanceof Error ? error.message : String(error)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// 11. Summary
|
|
172
|
+
console.log();
|
|
173
|
+
if (updatedTools.length > 0) {
|
|
174
|
+
console.log(chalk.green(`✓ 已更新:${updatedTools.join(', ')}(v${OPENSPEC_VERSION})`));
|
|
175
|
+
}
|
|
176
|
+
if (failedTools.length > 0) {
|
|
177
|
+
console.log(chalk.red(`✗ 失败:${failedTools.map(f => `${f.name}(${f.error})`).join(', ')}`));
|
|
178
|
+
}
|
|
179
|
+
if (removedCommandCount > 0) {
|
|
180
|
+
console.log(chalk.dim(`已移除:${removedCommandCount} 个命令文件(delivery 为 skills)`));
|
|
181
|
+
}
|
|
182
|
+
if (removedSkillCount > 0) {
|
|
183
|
+
console.log(chalk.dim(`已移除:${removedSkillCount} 个技能目录(delivery 为 commands)`));
|
|
184
|
+
}
|
|
185
|
+
if (removedDeselectedCommandCount > 0) {
|
|
186
|
+
console.log(chalk.dim(`已移除:${removedDeselectedCommandCount} 个命令文件(未选中的 workflows)`));
|
|
187
|
+
}
|
|
188
|
+
if (removedDeselectedSkillCount > 0) {
|
|
189
|
+
console.log(chalk.dim(`已移除:${removedDeselectedSkillCount} 个技能目录(未选中的 workflows)`));
|
|
190
|
+
}
|
|
191
|
+
// 12. Show onboarding message for newly configured tools from legacy upgrade
|
|
192
|
+
if (newlyConfiguredTools.length > 0) {
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(chalk.bold('快速上手:'));
|
|
195
|
+
console.log(' /opsx:new 开始新变更');
|
|
196
|
+
console.log(' /opsx:continue 创建下一个 artifact');
|
|
197
|
+
console.log(' /opsx:apply 实现任务');
|
|
198
|
+
console.log();
|
|
199
|
+
console.log(`了解更多:${chalk.cyan('https://github.com/Fission-AI/OpenSpec')}`);
|
|
200
|
+
}
|
|
201
|
+
const configuredAndNewTools = [...new Set([...configuredTools, ...newlyConfiguredTools])];
|
|
202
|
+
// 13. Detect new tool directories not currently configured
|
|
203
|
+
this.detectNewTools(resolvedProjectPath, configuredAndNewTools);
|
|
204
|
+
// 14. Display note about extra workflows not in profile
|
|
205
|
+
this.displayExtraWorkflowsNote(resolvedProjectPath, configuredAndNewTools, desiredWorkflows);
|
|
206
|
+
this.displayOldCoreCustomProfileNote(profile, globalConfig.workflows);
|
|
207
|
+
// 15. List affected tools
|
|
208
|
+
if (updatedTools.length > 0) {
|
|
209
|
+
const toolDisplayNames = updatedTools;
|
|
210
|
+
console.log(chalk.dim(`工具:${toolDisplayNames.join(', ')}`));
|
|
211
|
+
}
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(chalk.dim('重启你的 IDE 以使更改生效。'));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Display message when all tools are up to date.
|
|
217
|
+
*/
|
|
218
|
+
displayUpToDateMessage(toolStatuses) {
|
|
219
|
+
const toolNames = toolStatuses.map((s) => s.toolId);
|
|
220
|
+
console.log(chalk.green(`✓ 所有 ${toolStatuses.length} 个工具已是最新(v${OPENSPEC_VERSION})`));
|
|
221
|
+
console.log(chalk.dim(` 工具:${toolNames.join(', ')}`));
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(chalk.dim('使用 --force 强制刷新文件。'));
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Display the update plan showing which tools need updating.
|
|
227
|
+
*/
|
|
228
|
+
displayUpdatePlan(toolsToUpdate, statusByTool, upToDate) {
|
|
229
|
+
const updates = toolsToUpdate.map((toolId) => {
|
|
230
|
+
const status = statusByTool.get(toolId);
|
|
231
|
+
if (status?.needsUpdate) {
|
|
232
|
+
const fromVersion = status.generatedByVersion ?? '未知版本';
|
|
233
|
+
return `${status.toolId} (${fromVersion} → ${OPENSPEC_VERSION})`;
|
|
234
|
+
}
|
|
235
|
+
return `${toolId}(配置同步)`;
|
|
236
|
+
});
|
|
237
|
+
console.log(`正在更新 ${toolsToUpdate.length} 个工具:${updates.join(', ')}`);
|
|
238
|
+
if (upToDate.length > 0) {
|
|
239
|
+
const upToDateNames = upToDate.map((s) => s.toolId);
|
|
240
|
+
console.log(chalk.dim(`已是最新:${upToDateNames.join(', ')}`));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Detects new tool directories that aren't currently configured and displays a hint.
|
|
245
|
+
*/
|
|
246
|
+
detectNewTools(projectPath, configuredTools) {
|
|
247
|
+
const availableTools = getAvailableTools(projectPath);
|
|
248
|
+
const configuredSet = new Set(configuredTools);
|
|
249
|
+
const newTools = availableTools.filter((t) => !configuredSet.has(t.value));
|
|
250
|
+
if (newTools.length > 0) {
|
|
251
|
+
const newToolNames = newTools.map((tool) => tool.name);
|
|
252
|
+
const isSingleTool = newToolNames.length === 1;
|
|
253
|
+
const toolNoun = isSingleTool ? 'tool' : 'tools';
|
|
254
|
+
const pronoun = isSingleTool ? 'it' : 'them';
|
|
255
|
+
console.log();
|
|
256
|
+
console.log(chalk.yellow(`检测到新的工具:${newToolNames.join(', ')}。运行 'openspec init' 添加。`));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Displays a note about extra workflows installed that aren't in the current profile.
|
|
261
|
+
*/
|
|
262
|
+
displayExtraWorkflowsNote(projectPath, configuredTools, profileWorkflows) {
|
|
263
|
+
const installedWorkflows = scanInstalledWorkflows(projectPath, configuredTools);
|
|
264
|
+
const profileSet = new Set(profileWorkflows);
|
|
265
|
+
const extraWorkflows = installedWorkflows.filter((w) => !profileSet.has(w));
|
|
266
|
+
if (extraWorkflows.length > 0) {
|
|
267
|
+
console.log(chalk.dim(`提示:${extraWorkflows.length} 个额外 workflows 不在当前 profile 中(使用 \`openspec config profile\` 管理)`));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Suggest opting back into core when a custom profile still matches the old
|
|
272
|
+
* pre-sync core set. Keep custom profiles user-owned; do not mutate them.
|
|
273
|
+
*/
|
|
274
|
+
displayOldCoreCustomProfileNote(profile, workflows) {
|
|
275
|
+
if (profile !== 'custom' || !workflows) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const workflowSet = new Set(workflows);
|
|
279
|
+
const matchesOldCore = workflowSet.size === OLD_CORE_WORKFLOWS.length &&
|
|
280
|
+
OLD_CORE_WORKFLOWS.every((workflow) => workflowSet.has(workflow));
|
|
281
|
+
if (!matchesOldCore) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
console.log(chalk.dim('提示:核心 profile 现已包含 sync。你的自定义 profile 保留的是旧的核心 workflow 集合。'));
|
|
285
|
+
console.log(chalk.dim('运行 `openspec config profile core` 再执行 `openspec update` 以添加 sync。'));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Removes skill directories for workflows when delivery changed to commands-only.
|
|
289
|
+
* Returns the number of directories removed.
|
|
290
|
+
*/
|
|
291
|
+
async removeSkillDirs(skillsDir) {
|
|
292
|
+
let removed = 0;
|
|
293
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
294
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
295
|
+
if (!dirName)
|
|
296
|
+
continue;
|
|
297
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
298
|
+
try {
|
|
299
|
+
if (fs.existsSync(skillDir)) {
|
|
300
|
+
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
|
301
|
+
removed++;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// Ignore errors
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return removed;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Removes skill directories for workflows that are no longer selected in the active profile.
|
|
312
|
+
* Returns the number of directories removed.
|
|
313
|
+
*/
|
|
314
|
+
async removeUnselectedSkillDirs(skillsDir, desiredWorkflows) {
|
|
315
|
+
const desiredSet = new Set(desiredWorkflows);
|
|
316
|
+
let removed = 0;
|
|
317
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
318
|
+
if (desiredSet.has(workflow))
|
|
319
|
+
continue;
|
|
320
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
321
|
+
if (!dirName)
|
|
322
|
+
continue;
|
|
323
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
324
|
+
try {
|
|
325
|
+
if (fs.existsSync(skillDir)) {
|
|
326
|
+
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
|
327
|
+
removed++;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// Ignore errors
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return removed;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Removes command files for workflows when delivery changed to skills-only.
|
|
338
|
+
* Returns the number of files removed.
|
|
339
|
+
*/
|
|
340
|
+
async removeCommandFiles(projectPath, toolId) {
|
|
341
|
+
let removed = 0;
|
|
342
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
343
|
+
if (!adapter)
|
|
344
|
+
return 0;
|
|
345
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
346
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
347
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
348
|
+
try {
|
|
349
|
+
if (fs.existsSync(fullPath)) {
|
|
350
|
+
await fs.promises.unlink(fullPath);
|
|
351
|
+
removed++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Ignore errors
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return removed;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Removes command files for workflows that are no longer selected in the active profile.
|
|
362
|
+
* Returns the number of files removed.
|
|
363
|
+
*/
|
|
364
|
+
async removeUnselectedCommandFiles(projectPath, toolId, desiredWorkflows) {
|
|
365
|
+
let removed = 0;
|
|
366
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
367
|
+
if (!adapter)
|
|
368
|
+
return 0;
|
|
369
|
+
const desiredSet = new Set(desiredWorkflows);
|
|
370
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
371
|
+
if (desiredSet.has(workflow))
|
|
372
|
+
continue;
|
|
373
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
374
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
375
|
+
try {
|
|
376
|
+
if (fs.existsSync(fullPath)) {
|
|
377
|
+
await fs.promises.unlink(fullPath);
|
|
378
|
+
removed++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Ignore errors
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return removed;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Detect and handle legacy OpenSpec artifacts.
|
|
389
|
+
* Unlike init, update warns but continues if legacy files found in non-interactive mode.
|
|
390
|
+
* Returns array of tool IDs that were newly configured during legacy upgrade.
|
|
391
|
+
*/
|
|
392
|
+
async handleLegacyCleanup(projectPath, desiredWorkflows, delivery) {
|
|
393
|
+
// Detect legacy artifacts
|
|
394
|
+
const detection = await detectLegacyArtifacts(projectPath);
|
|
395
|
+
if (!detection.hasLegacyArtifacts) {
|
|
396
|
+
return []; // No legacy artifacts found
|
|
397
|
+
}
|
|
398
|
+
// Show what was detected
|
|
399
|
+
console.log();
|
|
400
|
+
console.log(formatDetectionSummary(detection));
|
|
401
|
+
console.log();
|
|
402
|
+
const canPrompt = isInteractive();
|
|
403
|
+
if (this.force) {
|
|
404
|
+
// --force flag: proceed with cleanup automatically
|
|
405
|
+
await this.performLegacyCleanup(projectPath, detection);
|
|
406
|
+
// Then upgrade legacy tools to new skills
|
|
407
|
+
return this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
|
|
408
|
+
}
|
|
409
|
+
if (!canPrompt) {
|
|
410
|
+
// Non-interactive mode without --force: warn and continue
|
|
411
|
+
// (Unlike init, update doesn't abort - user may just want to update skills)
|
|
412
|
+
console.log(chalk.yellow('⚠ 使用 --force 自动清理旧版文件,或者以交互模式运行。'));
|
|
413
|
+
console.log();
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
// Interactive mode: prompt for confirmation
|
|
417
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
418
|
+
const shouldCleanup = await confirm({
|
|
419
|
+
message: '升级并清理旧版文件?',
|
|
420
|
+
default: true,
|
|
421
|
+
});
|
|
422
|
+
if (shouldCleanup) {
|
|
423
|
+
await this.performLegacyCleanup(projectPath, detection);
|
|
424
|
+
// Then upgrade legacy tools to new skills
|
|
425
|
+
return this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
console.log(chalk.dim('跳过旧版清理。继续更新技能...'));
|
|
429
|
+
console.log();
|
|
430
|
+
return [];
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Perform cleanup of legacy artifacts.
|
|
435
|
+
*/
|
|
436
|
+
async performLegacyCleanup(projectPath, detection) {
|
|
437
|
+
const spinner = ora('正在清理旧版文件...').start();
|
|
438
|
+
const result = await cleanupLegacyArtifacts(projectPath, detection);
|
|
439
|
+
spinner.succeed('旧版文件已清理');
|
|
440
|
+
const summary = formatCleanupSummary(result);
|
|
441
|
+
if (summary) {
|
|
442
|
+
console.log();
|
|
443
|
+
console.log(summary);
|
|
444
|
+
}
|
|
445
|
+
console.log();
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Upgrade legacy tools to new skills system.
|
|
449
|
+
* Returns array of tool IDs that were newly configured.
|
|
450
|
+
*/
|
|
451
|
+
async upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery) {
|
|
452
|
+
// Get tools that had legacy artifacts
|
|
453
|
+
const legacyTools = getToolsFromLegacyArtifacts(detection);
|
|
454
|
+
if (legacyTools.length === 0) {
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
// Get currently configured tools
|
|
458
|
+
const configuredTools = getConfiguredToolsForProfileSync(projectPath);
|
|
459
|
+
const configuredSet = new Set(configuredTools);
|
|
460
|
+
// Filter to tools that aren't already configured
|
|
461
|
+
const unconfiguredLegacyTools = legacyTools.filter((t) => !configuredSet.has(t));
|
|
462
|
+
if (unconfiguredLegacyTools.length === 0) {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
// Get valid tools (those with skillsDir)
|
|
466
|
+
const validToolIds = new Set(getToolsWithSkillsDir());
|
|
467
|
+
const validUnconfiguredTools = unconfiguredLegacyTools.filter((t) => validToolIds.has(t));
|
|
468
|
+
if (validUnconfiguredTools.length === 0) {
|
|
469
|
+
return [];
|
|
470
|
+
}
|
|
471
|
+
// Show what tools were detected from legacy artifacts
|
|
472
|
+
console.log(chalk.bold('从旧版 artifact 检测到以下工具:'));
|
|
473
|
+
for (const toolId of validUnconfiguredTools) {
|
|
474
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
475
|
+
console.log(` • ${tool?.name || toolId}`);
|
|
476
|
+
}
|
|
477
|
+
console.log();
|
|
478
|
+
let selectedTools;
|
|
479
|
+
if (this.force || !canPrompt) {
|
|
480
|
+
// Non-interactive with --force: auto-select detected tools
|
|
481
|
+
selectedTools = validUnconfiguredTools;
|
|
482
|
+
console.log(`正在为以下工具设置技能:${selectedTools.join(', ')}`);
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
// Interactive mode: prompt for tool selection with detected tools pre-selected
|
|
486
|
+
const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
|
|
487
|
+
const sortedChoices = validUnconfiguredTools.map((toolId) => {
|
|
488
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
489
|
+
return {
|
|
490
|
+
name: tool?.name || toolId,
|
|
491
|
+
value: toolId,
|
|
492
|
+
configured: false,
|
|
493
|
+
preSelected: true, // Pre-select all detected legacy tools
|
|
494
|
+
};
|
|
495
|
+
});
|
|
496
|
+
selectedTools = await searchableMultiSelect({
|
|
497
|
+
message: '选择要使用新技能系统设置的工具:',
|
|
498
|
+
pageSize: 15,
|
|
499
|
+
choices: sortedChoices,
|
|
500
|
+
validate: (_selected) => true, // Allow empty selection (user can skip)
|
|
501
|
+
});
|
|
502
|
+
if (selectedTools.length === 0) {
|
|
503
|
+
console.log(chalk.dim('跳过工具设置。'));
|
|
504
|
+
console.log();
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// Create skills/commands for selected tools using effective profile+delivery.
|
|
509
|
+
const newlyConfigured = [];
|
|
510
|
+
const shouldGenerateSkills = delivery !== 'commands';
|
|
511
|
+
const shouldGenerateCommands = delivery !== 'skills';
|
|
512
|
+
const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
|
|
513
|
+
const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
|
|
514
|
+
for (const toolId of selectedTools) {
|
|
515
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
516
|
+
if (!tool?.skillsDir)
|
|
517
|
+
continue;
|
|
518
|
+
const spinner = ora(`正在设置 ${tool.name}...`).start();
|
|
519
|
+
try {
|
|
520
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
521
|
+
// Create skill files when delivery includes skills
|
|
522
|
+
if (shouldGenerateSkills) {
|
|
523
|
+
for (const { template, dirName } of skillTemplates) {
|
|
524
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
525
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
526
|
+
// Use hyphen-based command references for OpenCode
|
|
527
|
+
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
|
|
528
|
+
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
529
|
+
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// Create commands when delivery includes commands
|
|
533
|
+
if (shouldGenerateCommands) {
|
|
534
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
535
|
+
if (adapter) {
|
|
536
|
+
const generatedCommands = generateCommands(commandContents, adapter);
|
|
537
|
+
for (const cmd of generatedCommands) {
|
|
538
|
+
const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
|
|
539
|
+
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
spinner.succeed(`${tool.name} 设置完成`);
|
|
544
|
+
newlyConfigured.push(toolId);
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
spinner.fail(`${tool.name} 设置失败`);
|
|
548
|
+
console.log(chalk.red(` ${error instanceof Error ? error.message : String(error)}`));
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (newlyConfigured.length > 0) {
|
|
552
|
+
console.log();
|
|
553
|
+
}
|
|
554
|
+
return newlyConfigured;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation threshold constants
|
|
3
|
+
*/
|
|
4
|
+
export declare const MIN_WHY_SECTION_LENGTH = 50;
|
|
5
|
+
export declare const MIN_PURPOSE_LENGTH = 50;
|
|
6
|
+
export declare const MAX_WHY_SECTION_LENGTH = 1000;
|
|
7
|
+
export declare const MAX_REQUIREMENT_TEXT_LENGTH = 500;
|
|
8
|
+
export declare const MAX_DELTAS_PER_CHANGE = 10;
|
|
9
|
+
export declare const VALIDATION_MESSAGES: {
|
|
10
|
+
readonly SCENARIO_EMPTY: "场景描述不能为空";
|
|
11
|
+
readonly REQUIREMENT_EMPTY: "需求描述不能为空";
|
|
12
|
+
readonly REQUIREMENT_NO_SHALL: "需求必须包含 SHALL、MUST、必须 或 应当";
|
|
13
|
+
readonly REQUIREMENT_NO_SCENARIOS: "每个需求至少需要一个场景";
|
|
14
|
+
readonly SPEC_NAME_EMPTY: "Spec 名称不能为空";
|
|
15
|
+
readonly SPEC_PURPOSE_EMPTY: "Purpose 段落不能为空";
|
|
16
|
+
readonly SPEC_NO_REQUIREMENTS: "Spec 至少需要一个需求";
|
|
17
|
+
readonly CHANGE_NAME_EMPTY: "变更名称不能为空";
|
|
18
|
+
readonly CHANGE_WHY_TOO_SHORT: "Why 段落至少需要 50 个字符";
|
|
19
|
+
readonly CHANGE_WHY_TOO_LONG: "Why 段落不应超过 1000 个字符";
|
|
20
|
+
readonly CHANGE_WHAT_EMPTY: "What Changes 段落不能为空";
|
|
21
|
+
readonly CHANGE_NO_DELTAS: "变更必须至少包含一个 delta";
|
|
22
|
+
readonly CHANGE_TOO_MANY_DELTAS: "建议将超过 10 个 delta 的变更拆分";
|
|
23
|
+
readonly DELTA_SPEC_EMPTY: "Spec 名称不能为空";
|
|
24
|
+
readonly DELTA_DESCRIPTION_EMPTY: "Delta 描述不能为空";
|
|
25
|
+
readonly PURPOSE_TOO_BRIEF: "Purpose 段落过短(不足 50 个字符)";
|
|
26
|
+
readonly REQUIREMENT_TOO_LONG: "需求文本过长(超过 500 个字符),建议拆分";
|
|
27
|
+
readonly DELTA_DESCRIPTION_TOO_BRIEF: "Delta 描述过短";
|
|
28
|
+
readonly DELTA_MISSING_REQUIREMENTS: "Delta 应包含需求描述";
|
|
29
|
+
readonly GUIDE_NO_DELTAS: "未找到 delta。请确保变更的 specs/ 目录下有能力子目录(如 specs/http-server/spec.md),其中的 .md 文件使用了 delta 标题(## ADDED/MODIFIED/REMOVED/RENAMED Requirements),且每个需求至少包含一个 \"#### Scenario:\" 块。提示:运行 \"openspec change show <change-id> --json --deltas-only\" 可检查解析结果。";
|
|
30
|
+
readonly GUIDE_MISSING_SPEC_SECTIONS: "缺少必需段落。预期标题: \"## Purpose\" 和 \"## Requirements\"。示例:\n## Purpose\n[简述目的]\n\n## Requirements\n### Requirement: 明确的需求描述\n系统 SHALL ...\n\n#### Scenario: 场景名称\n- **WHEN** ...\n- **THEN** ...";
|
|
31
|
+
readonly GUIDE_MISSING_CHANGE_SECTIONS: "缺少必需段落。预期标题: \"## Why\" 和 \"## What Changes\"。请确保变更在 specs/ 中使用 delta 标题记录了变更。";
|
|
32
|
+
readonly GUIDE_SCENARIO_FORMAT: "场景必须使用四级标题。请将列表转换为:\n#### Scenario: 简短名称\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...";
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation threshold constants
|
|
3
|
+
*/
|
|
4
|
+
// Minimum character lengths
|
|
5
|
+
export const MIN_WHY_SECTION_LENGTH = 50;
|
|
6
|
+
export const MIN_PURPOSE_LENGTH = 50;
|
|
7
|
+
// Maximum character/item limits
|
|
8
|
+
export const MAX_WHY_SECTION_LENGTH = 1000;
|
|
9
|
+
export const MAX_REQUIREMENT_TEXT_LENGTH = 500;
|
|
10
|
+
export const MAX_DELTAS_PER_CHANGE = 10;
|
|
11
|
+
// Validation messages
|
|
12
|
+
export const VALIDATION_MESSAGES = {
|
|
13
|
+
// Required content
|
|
14
|
+
SCENARIO_EMPTY: '场景描述不能为空',
|
|
15
|
+
REQUIREMENT_EMPTY: '需求描述不能为空',
|
|
16
|
+
REQUIREMENT_NO_SHALL: '需求必须包含 SHALL、MUST、必须 或 应当',
|
|
17
|
+
REQUIREMENT_NO_SCENARIOS: '每个需求至少需要一个场景',
|
|
18
|
+
SPEC_NAME_EMPTY: 'Spec 名称不能为空',
|
|
19
|
+
SPEC_PURPOSE_EMPTY: 'Purpose 段落不能为空',
|
|
20
|
+
SPEC_NO_REQUIREMENTS: 'Spec 至少需要一个需求',
|
|
21
|
+
CHANGE_NAME_EMPTY: '变更名称不能为空',
|
|
22
|
+
CHANGE_WHY_TOO_SHORT: `Why 段落至少需要 ${MIN_WHY_SECTION_LENGTH} 个字符`,
|
|
23
|
+
CHANGE_WHY_TOO_LONG: `Why 段落不应超过 ${MAX_WHY_SECTION_LENGTH} 个字符`,
|
|
24
|
+
CHANGE_WHAT_EMPTY: 'What Changes 段落不能为空',
|
|
25
|
+
CHANGE_NO_DELTAS: '变更必须至少包含一个 delta',
|
|
26
|
+
CHANGE_TOO_MANY_DELTAS: `建议将超过 ${MAX_DELTAS_PER_CHANGE} 个 delta 的变更拆分`,
|
|
27
|
+
DELTA_SPEC_EMPTY: 'Spec 名称不能为空',
|
|
28
|
+
DELTA_DESCRIPTION_EMPTY: 'Delta 描述不能为空',
|
|
29
|
+
// Warnings
|
|
30
|
+
PURPOSE_TOO_BRIEF: `Purpose 段落过短(不足 ${MIN_PURPOSE_LENGTH} 个字符)`,
|
|
31
|
+
REQUIREMENT_TOO_LONG: `需求文本过长(超过 ${MAX_REQUIREMENT_TEXT_LENGTH} 个字符),建议拆分`,
|
|
32
|
+
DELTA_DESCRIPTION_TOO_BRIEF: 'Delta 描述过短',
|
|
33
|
+
DELTA_MISSING_REQUIREMENTS: 'Delta 应包含需求描述',
|
|
34
|
+
// Guidance snippets (appended to primary messages for remediation)
|
|
35
|
+
GUIDE_NO_DELTAS: '未找到 delta。请确保变更的 specs/ 目录下有能力子目录(如 specs/http-server/spec.md),其中的 .md 文件使用了 delta 标题(## ADDED/MODIFIED/REMOVED/RENAMED Requirements),且每个需求至少包含一个 "#### Scenario:" 块。提示:运行 "openspec change show <change-id> --json --deltas-only" 可检查解析结果。',
|
|
36
|
+
GUIDE_MISSING_SPEC_SECTIONS: '缺少必需段落。预期标题: "## Purpose" 和 "## Requirements"。示例:\n## Purpose\n[简述目的]\n\n## Requirements\n### Requirement: 明确的需求描述\n系统 SHALL ...\n\n#### Scenario: 场景名称\n- **WHEN** ...\n- **THEN** ...',
|
|
37
|
+
GUIDE_MISSING_CHANGE_SECTIONS: '缺少必需段落。预期标题: "## Why" 和 "## What Changes"。请确保变更在 specs/ 中使用 delta 标题记录了变更。',
|
|
38
|
+
GUIDE_SCENARIO_FORMAT: '场景必须使用四级标题。请将列表转换为:\n#### Scenario: 简短名称\n- **WHEN** ...\n- **THEN** ...\n- **AND** ...',
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ValidationLevel = 'ERROR' | 'WARNING' | 'INFO';
|
|
2
|
+
export interface ValidationIssue {
|
|
3
|
+
level: ValidationLevel;
|
|
4
|
+
path: string;
|
|
5
|
+
message: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
column?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ValidationReport {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
issues: ValidationIssue[];
|
|
12
|
+
summary: {
|
|
13
|
+
errors: number;
|
|
14
|
+
warnings: number;
|
|
15
|
+
info: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=types.d.ts.map
|