@codewalla_india/openspec 1.3.1 → 1.3.2
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 -22
- package/README.md +272 -191
- package/bin/openspec.js +5 -5
- package/dist/cli/index.js +99 -62
- package/dist/commands/change.d.ts +0 -2
- package/dist/commands/change.js +107 -86
- package/dist/commands/config.js +14 -16
- package/dist/commands/doctor.js +9 -4
- package/dist/commands/feedback.js +79 -30
- package/dist/commands/modify.d.ts +26 -0
- package/dist/commands/modify.js +147 -0
- package/dist/commands/schema.js +170 -91
- package/dist/commands/show.js +5 -2
- package/dist/commands/spec.js +49 -29
- package/dist/commands/store.js +15 -21
- package/dist/commands/validate.d.ts +8 -0
- package/dist/commands/validate.js +43 -20
- package/dist/commands/workflow/index.d.ts +2 -2
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/instructions.d.ts +6 -26
- package/dist/commands/workflow/instructions.js +183 -376
- package/dist/commands/workflow/new-change.d.ts +0 -4
- package/dist/commands/workflow/new-change.js +10 -28
- package/dist/commands/workflow/shared.d.ts +34 -19
- package/dist/commands/workflow/shared.js +34 -4
- package/dist/commands/workflow/status.js +10 -32
- package/dist/commands/workflow/templates.js +15 -5
- package/dist/commands/workset.d.ts +2 -2
- package/dist/commands/workset.js +19 -22
- package/dist/comprehension-quiz/fingerprint.d.ts +39 -0
- package/dist/comprehension-quiz/fingerprint.js +84 -0
- package/dist/comprehension-quiz/index.d.ts +42 -0
- package/dist/comprehension-quiz/index.js +47 -0
- package/dist/comprehension-quiz/pass-record.d.ts +44 -0
- package/dist/comprehension-quiz/pass-record.js +98 -0
- package/dist/comprehension-quiz/providers/anthropic.d.ts +34 -0
- package/dist/comprehension-quiz/providers/anthropic.js +53 -0
- package/dist/comprehension-quiz/providers/factory.d.ts +44 -0
- package/dist/comprehension-quiz/providers/factory.js +174 -0
- package/dist/comprehension-quiz/providers/interface.d.ts +59 -0
- package/dist/comprehension-quiz/providers/interface.js +63 -0
- package/dist/comprehension-quiz/providers/local.d.ts +34 -0
- package/dist/comprehension-quiz/providers/local.js +53 -0
- package/dist/comprehension-quiz/providers/ollama.d.ts +34 -0
- package/dist/comprehension-quiz/providers/ollama.js +53 -0
- package/dist/comprehension-quiz/providers/openai.d.ts +34 -0
- package/dist/comprehension-quiz/providers/openai.js +53 -0
- package/dist/comprehension-quiz/question-generator.d.ts +69 -0
- package/dist/comprehension-quiz/question-generator.js +141 -0
- package/dist/comprehension-quiz/quiz-executor.d.ts +70 -0
- package/dist/comprehension-quiz/quiz-executor.js +165 -0
- package/dist/comprehension-quiz/types.d.ts +115 -0
- package/dist/comprehension-quiz/types.js +11 -0
- package/dist/core/archive.d.ts +14 -1
- package/dist/core/archive.js +1350 -190
- package/dist/core/artifact-graph/graph.d.ts +28 -9
- package/dist/core/artifact-graph/graph.js +94 -43
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/index.js +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
- package/dist/core/artifact-graph/instruction-loader.js +70 -12
- package/dist/core/artifact-graph/outputs.d.ts +1 -0
- package/dist/core/artifact-graph/outputs.js +67 -5
- package/dist/core/artifact-graph/resolver.d.ts +14 -0
- package/dist/core/artifact-graph/resolver.js +69 -15
- package/dist/core/artifact-graph/types.js +21 -3
- package/dist/core/available-tools.d.ts +2 -2
- package/dist/core/available-tools.js +14 -3
- package/dist/core/change-metadata/schema.d.ts +2 -0
- package/dist/core/change-metadata/schema.js +13 -0
- package/dist/core/change-status-policy.d.ts +10 -1
- package/dist/core/change-status-policy.js +30 -2
- package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
- package/dist/core/command-generation/adapters/amazon-q.js +11 -5
- package/dist/core/command-generation/adapters/antigravity.js +6 -5
- package/dist/core/command-generation/adapters/auggie.js +7 -6
- package/dist/core/command-generation/adapters/bob.d.ts +5 -1
- package/dist/core/command-generation/adapters/bob.js +11 -10
- package/dist/core/command-generation/adapters/claude.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +12 -17
- package/dist/core/command-generation/adapters/cline.js +5 -5
- package/dist/core/command-generation/adapters/codebuddy.js +8 -7
- package/dist/core/command-generation/adapters/continue.js +8 -7
- package/dist/core/command-generation/adapters/costrict.js +7 -6
- package/dist/core/command-generation/adapters/crush.js +9 -9
- package/dist/core/command-generation/adapters/cursor.js +8 -8
- package/dist/core/command-generation/adapters/devin.d.ts +19 -0
- package/dist/core/command-generation/adapters/devin.js +36 -0
- package/dist/core/command-generation/adapters/factory.js +7 -6
- package/dist/core/command-generation/adapters/gemini.js +40 -5
- package/dist/core/command-generation/adapters/github-copilot.js +6 -5
- package/dist/core/command-generation/adapters/iflow.js +9 -8
- package/dist/core/command-generation/adapters/index.d.ts +4 -2
- package/dist/core/command-generation/adapters/index.js +4 -2
- package/dist/core/command-generation/adapters/junie.js +6 -5
- package/dist/core/command-generation/adapters/kilocode.js +1 -1
- package/dist/core/command-generation/adapters/kiro.js +6 -5
- package/dist/core/command-generation/adapters/lingma.js +9 -9
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +42 -0
- package/dist/core/command-generation/adapters/opencode.js +6 -8
- package/dist/core/command-generation/adapters/pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/pi.js +7 -10
- package/dist/core/command-generation/adapters/qoder.js +9 -9
- package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
- package/dist/core/command-generation/adapters/qwen.js +14 -9
- package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
- package/dist/core/command-generation/adapters/roocode.js +9 -9
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +28 -0
- package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
- package/dist/core/command-generation/adapters/zcode.js +33 -0
- package/dist/core/command-generation/generator.d.ts +8 -0
- package/dist/core/command-generation/generator.js +15 -1
- package/dist/core/command-generation/index.d.ts +1 -1
- package/dist/core/command-generation/index.js +1 -1
- package/dist/core/command-generation/invocation.d.ts +73 -0
- package/dist/core/command-generation/invocation.js +77 -0
- package/dist/core/command-generation/registry.js +8 -4
- package/dist/core/command-generation/types.d.ts +9 -2
- package/dist/core/command-generation/yaml.d.ts +20 -6
- package/dist/core/command-generation/yaml.js +31 -21
- package/dist/core/command-surface.d.ts +15 -0
- package/dist/core/command-surface.js +33 -0
- package/dist/core/completions/command-registry.js +16 -61
- package/dist/core/completions/generators/bash-generator.js +41 -41
- package/dist/core/completions/generators/fish-generator.js +7 -7
- package/dist/core/completions/generators/powershell-generator.js +44 -37
- package/dist/core/completions/generators/zsh-generator.js +33 -33
- package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
- package/dist/core/completions/installers/zsh-installer.js +20 -4
- package/dist/core/completions/templates/bash-templates.js +24 -24
- package/dist/core/completions/templates/fish-templates.js +38 -38
- package/dist/core/completions/templates/powershell-templates.js +28 -28
- package/dist/core/completions/templates/zsh-templates.js +39 -39
- package/dist/core/config-prompts.d.ts +1 -1
- package/dist/core/config-prompts.js +14 -1
- package/dist/core/config-schema.d.ts +10 -1
- package/dist/core/config-schema.js +67 -1
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.js +46 -5
- package/dist/core/file-state.js +56 -24
- package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
- package/dist/core/github-copilot/cloud-agent.js +497 -0
- package/dist/core/global-config.d.ts +16 -0
- package/dist/core/init.d.ts +58 -0
- package/dist/core/init.js +474 -73
- package/dist/core/legacy-cleanup.d.ts +59 -0
- package/dist/core/legacy-cleanup.js +253 -24
- package/dist/core/list.js +23 -16
- package/dist/core/migration.d.ts +73 -1
- package/dist/core/migration.js +360 -11
- package/dist/core/modify/conflict-detector.d.ts +26 -0
- package/dist/core/modify/conflict-detector.js +62 -0
- package/dist/core/modify/dependency-propagator.d.ts +38 -0
- package/dist/core/modify/dependency-propagator.js +103 -0
- package/dist/core/modify/history-tracker.d.ts +34 -0
- package/dist/core/modify/history-tracker.js +114 -0
- package/dist/core/modify/index.d.ts +7 -0
- package/dist/core/modify/index.js +7 -0
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/openspec-root.js +18 -15
- package/dist/core/parsers/change-parser.d.ts +17 -2
- package/dist/core/parsers/change-parser.js +35 -21
- package/dist/core/parsers/code-fence.d.ts +15 -0
- package/dist/core/parsers/code-fence.js +49 -0
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +7 -60
- package/dist/core/parsers/requirement-blocks.d.ts +29 -0
- package/dist/core/parsers/requirement-blocks.js +150 -31
- package/dist/core/parsers/requirement-text.d.ts +48 -0
- package/dist/core/parsers/requirement-text.js +98 -0
- package/dist/core/parsers/spec-structure.d.ts +1 -1
- package/dist/core/parsers/spec-structure.js +20 -30
- package/dist/core/profile-sync-drift.d.ts +0 -8
- package/dist/core/profile-sync-drift.js +49 -50
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -2
- package/dist/core/project-config.d.ts +27 -18
- package/dist/core/project-config.js +101 -34
- package/dist/core/references.js +42 -4
- package/dist/core/relationship-health.d.ts +8 -0
- package/dist/core/relationship-health.js +13 -0
- package/dist/core/root-selection.d.ts +9 -6
- package/dist/core/root-selection.js +42 -5
- package/dist/core/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
- package/dist/core/shared/skill-content-equivalence.js +41 -0
- package/dist/core/shared/skill-generation.js +17 -13
- package/dist/core/shared/skill-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +27 -7
- package/dist/core/shared/tool-detection.js +167 -37
- package/dist/core/shared-skill-target.d.ts +16 -0
- package/dist/core/shared-skill-target.js +156 -0
- package/dist/core/specs-apply.d.ts +80 -33
- package/dist/core/specs-apply.js +667 -145
- package/dist/core/store/git.d.ts +13 -0
- package/dist/core/store/git.js +24 -0
- package/dist/core/store/operations.js +20 -0
- package/dist/core/templates/skill-templates.d.ts +3 -2
- package/dist/core/templates/skill-templates.js +3 -2
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +186 -224
- package/dist/core/templates/workflows/archive-change.js +387 -260
- package/dist/core/templates/workflows/bulk-archive-change.js +655 -475
- package/dist/core/templates/workflows/continue-change.js +210 -219
- package/dist/core/templates/workflows/explore.js +479 -443
- package/dist/core/templates/workflows/feedback.js +97 -97
- package/dist/core/templates/workflows/ff-change.js +200 -191
- package/dist/core/templates/workflows/modify-change.d.ts +4 -1
- package/dist/core/templates/workflows/modify-change.js +237 -115
- package/dist/core/templates/workflows/new-change.js +127 -134
- package/dist/core/templates/workflows/onboard.js +548 -542
- package/dist/core/templates/workflows/propose.js +273 -217
- package/dist/core/templates/workflows/store-selection.d.ts +1 -1
- package/dist/core/templates/workflows/store-selection.js +1 -1
- package/dist/core/templates/workflows/sync-specs.js +500 -273
- package/dist/core/templates/workflows/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +177 -0
- package/dist/core/templates/workflows/verify-change.js +324 -326
- package/dist/core/update.d.ts +40 -5
- package/dist/core/update.js +411 -96
- package/dist/core/validation/constants.d.ts +4 -1
- package/dist/core/validation/constants.js +4 -1
- package/dist/core/validation/plan-validator.d.ts +8 -0
- package/dist/core/validation/plan-validator.js +45 -0
- package/dist/core/validation/task-numbering.d.ts +16 -0
- package/dist/core/validation/task-numbering.js +57 -0
- package/dist/core/validation/validator.d.ts +27 -3
- package/dist/core/validation/validator.js +337 -48
- package/dist/core/version-check.d.ts +152 -0
- package/dist/core/version-check.js +731 -0
- package/dist/core/view.js +13 -18
- package/dist/prompts/searchable-multi-select.js +1 -1
- package/dist/telemetry/caller-detection.d.ts +27 -0
- package/dist/telemetry/caller-detection.js +85 -0
- package/dist/telemetry/caller-detection.test.d.ts +5 -0
- package/dist/telemetry/config.d.ts +48 -2
- package/dist/telemetry/config.js +113 -11
- package/dist/telemetry/config.test.d.ts +5 -0
- package/dist/telemetry/deduplication.d.ts +35 -0
- package/dist/telemetry/deduplication.js +82 -0
- package/dist/telemetry/deduplication.test.d.ts +5 -0
- package/dist/telemetry/identity.d.ts +62 -22
- package/dist/telemetry/identity.js +147 -102
- package/dist/telemetry/index.d.ts +126 -15
- package/dist/telemetry/index.js +569 -37
- package/dist/telemetry/sanitization.d.ts +40 -0
- package/dist/telemetry/sanitization.js +114 -0
- package/dist/telemetry/sanitization.test.d.ts +5 -0
- package/dist/telemetry/throttling.d.ts +20 -0
- package/dist/telemetry/throttling.js +70 -0
- package/dist/ui/welcome-screen.d.ts +9 -1
- package/dist/ui/welcome-screen.js +91 -38
- package/dist/utils/change-metadata.d.ts +43 -0
- package/dist/utils/change-metadata.js +123 -7
- package/dist/utils/change-utils.d.ts +10 -4
- package/dist/utils/change-utils.js +20 -13
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +16 -0
- package/dist/utils/command-references.d.ts +92 -6
- package/dist/utils/command-references.js +180 -7
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/file-system.d.ts +10 -0
- package/dist/utils/file-system.js +70 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/interactive.d.ts +23 -0
- package/dist/utils/interactive.js +32 -0
- package/dist/utils/item-discovery.d.ts +19 -0
- package/dist/utils/item-discovery.js +30 -48
- package/dist/utils/shell-detection.d.ts +1 -1
- package/dist/utils/shell-detection.js +64 -11
- package/dist/utils/spec-discovery.d.ts +40 -0
- package/dist/utils/spec-discovery.js +115 -0
- package/dist/utils/task-progress.d.ts +29 -1
- package/dist/utils/task-progress.js +109 -15
- package/package.json +96 -84
- package/schemas/spec-driven/schema.yaml +252 -184
- package/schemas/spec-driven/templates/design.md +19 -19
- package/schemas/spec-driven/templates/plan-template.md +91 -0
- package/schemas/spec-driven/templates/proposal.md +29 -24
- package/schemas/spec-driven/templates/spec.md +11 -8
- package/schemas/spec-driven/templates/tasks.md +9 -9
- package/scripts/postinstall.js +83 -83
- package/dist/core/command-generation/adapters/codex.d.ts +0 -16
- package/dist/core/command-generation/adapters/codex.js +0 -39
- package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
- package/dist/core/command-generation/adapters/windsurf.js +0 -38
- package/dist/core/comprehension/config.d.ts +0 -20
- package/dist/core/comprehension/config.js +0 -23
- package/dist/core/comprehension/fingerprint.d.ts +0 -14
- package/dist/core/comprehension/fingerprint.js +0 -33
- package/dist/core/comprehension/index.d.ts +0 -57
- package/dist/core/comprehension/index.js +0 -87
- package/dist/core/comprehension/pass-record.d.ts +0 -29
- package/dist/core/comprehension/pass-record.js +0 -64
- package/dist/core/comprehension/stats.d.ts +0 -35
- package/dist/core/comprehension/stats.js +0 -133
- package/dist/core/templates/workflows/comprehension-guidance.d.ts +0 -9
- package/dist/core/templates/workflows/comprehension-guidance.js +0 -73
- package/dist/core/templates/workflows/mcp-guidance.d.ts +0 -13
- package/dist/core/templates/workflows/mcp-guidance.js +0 -131
- package/dist/core/templates/workflows/user-prompt-guidance.d.ts +0 -16
- package/dist/core/templates/workflows/user-prompt-guidance.js +0 -48
- package/dist/telemetry/caller.d.ts +0 -5
- package/dist/telemetry/caller.js +0 -29
- package/dist/telemetry/client.d.ts +0 -27
- package/dist/telemetry/client.js +0 -127
- package/dist/telemetry/command-context.d.ts +0 -13
- package/dist/telemetry/command-context.js +0 -59
- package/dist/telemetry/comprehension.d.ts +0 -44
- package/dist/telemetry/comprehension.js +0 -105
- package/dist/telemetry/content.d.ts +0 -10
- package/dist/telemetry/content.js +0 -56
- package/dist/telemetry/git-stats.d.ts +0 -12
- package/dist/telemetry/git-stats.js +0 -69
- package/dist/telemetry/identify-cache.d.ts +0 -7
- package/dist/telemetry/identify-cache.js +0 -47
- package/dist/telemetry/input.d.ts +0 -17
- package/dist/telemetry/input.js +0 -68
- package/dist/telemetry/marker.d.ts +0 -37
- package/dist/telemetry/marker.js +0 -67
- package/dist/telemetry/workflow.d.ts +0 -85
- package/dist/telemetry/workflow.js +0 -318
- package/schemas/spec-driven/templates/plan.md +0 -19
package/dist/core/update.js
CHANGED
|
@@ -10,21 +10,24 @@ import ora from 'ora';
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
12
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
13
|
-
import {
|
|
13
|
+
import { getSkillReferenceTransformer, getTransformerForTool, transformToSkillReferences } from '../utils/command-references.js';
|
|
14
14
|
import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
|
|
15
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';
|
|
16
|
+
import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, } from './shared/index.js';
|
|
17
|
+
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatDeferredGlobalPromptSummary, formatCleanupSummary, formatDetectionSummary, getLegacyGlobalPromptMatches, getLegacyWorkflowIdsForTool, getToolsFromLegacyArtifacts, omitGlobalLegacyPromptFiles, pickGlobalLegacyPromptFiles, } from './legacy-cleanup.js';
|
|
18
18
|
import { isInteractive } from '../utils/interactive.js';
|
|
19
|
-
import { setupTelemetryIdentity } from '../telemetry/index.js';
|
|
20
19
|
import { getGlobalConfig } from './global-config.js';
|
|
21
|
-
import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
|
|
20
|
+
import { getProfileWorkflows, ALL_WORKFLOWS, CORE_WORKFLOWS } from './profiles.js';
|
|
21
|
+
import { getOnboardingCommands } from './onboarding-commands.js';
|
|
22
22
|
import { getAvailableTools } from './available-tools.js';
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { readIdentity, writeIdentity, promptForIdentity, validateIdentity, normalizeIdentity } from '../telemetry/identity.js';
|
|
24
|
+
import { WORKFLOW_TO_SKILL_DIR, getConfiguredToolsForProfileSync, getToolsNeedingProfileSync, } from './profile-sync-drift.js';
|
|
25
|
+
import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, findLegacyToolMigrations, migrateLegacyToolDirs, describeLegacyMigration, legacyMigrationNotice, keptInPlaceNotice, hasMovableContent, } from './migration.js';
|
|
26
|
+
import { resolveCommandSurfaceCapability, resolveCommandInvocation, shouldGenerateCommandsForTool, shouldGenerateSkillsForTool, shouldReconcileCommandFilesForTool, shouldRemoveSkillsForTool, } from './command-surface.js';
|
|
27
|
+
import { writeSharedSkillTarget } from './shared-skill-target.js';
|
|
28
|
+
import { includesGitHubCopilot, writeCopilotCloudFiles, removeCopilotCloudFiles, isCopilotCloudEnabled, readCopilotCloudOptIn, findUnmanagedCloudFiles } from './github-copilot/cloud-agent.js';
|
|
25
29
|
const require = createRequire(import.meta.url);
|
|
26
30
|
const { version: OPENSPEC_VERSION } = require('../../package.json');
|
|
27
|
-
const OLD_CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'archive'];
|
|
28
31
|
/**
|
|
29
32
|
* Scans installed workflow artifacts (skills and managed commands) across all configured tools.
|
|
30
33
|
* Returns the union of detected workflow IDs that match ALL_WORKFLOWS.
|
|
@@ -42,6 +45,12 @@ export class UpdateCommand {
|
|
|
42
45
|
constructor(options = {}) {
|
|
43
46
|
this.force = options.force ?? false;
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Refreshes OpenSpec skills and commands for all configured tools,
|
|
50
|
+
* regenerating artifacts according to the effective profile and delivery mode.
|
|
51
|
+
*
|
|
52
|
+
* @param projectPath - Path to the project root containing the openspec directory
|
|
53
|
+
*/
|
|
45
54
|
async execute(projectPath) {
|
|
46
55
|
const resolvedProjectPath = path.resolve(projectPath);
|
|
47
56
|
const openspecPath = path.join(resolvedProjectPath, OPENSPEC_DIR_NAME);
|
|
@@ -49,8 +58,37 @@ export class UpdateCommand {
|
|
|
49
58
|
if (!await FileSystemUtils.directoryExists(openspecPath)) {
|
|
50
59
|
throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`);
|
|
51
60
|
}
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
// 1.5. Prompt for identity if not already configured (task 8.1)
|
|
62
|
+
const existingIdentity = await readIdentity();
|
|
63
|
+
if (!existingIdentity && isInteractive()) {
|
|
64
|
+
const identity = await promptForIdentity();
|
|
65
|
+
if (identity) {
|
|
66
|
+
const normalized = normalizeIdentity(identity);
|
|
67
|
+
const validation = validateIdentity(normalized);
|
|
68
|
+
if (validation.valid) {
|
|
69
|
+
await writeIdentity(normalized);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.error(`Invalid identity: ${validation.errors.join(', ')}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.error('Identity is required for OpenSpec. Please provide an email or username.');
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 2. Migrate OpenSpec-managed skills left in renamed tool directories
|
|
82
|
+
// (e.g. .kimi -> .kimi-code) so they stay detected and get refreshed,
|
|
83
|
+
// then perform the one-time profile migration if needed before any
|
|
84
|
+
// legacy upgrade generation.
|
|
85
|
+
for (const migration of migrateLegacyToolDirs(resolvedProjectPath)) {
|
|
86
|
+
if (hasMovableContent(migration)) {
|
|
87
|
+
console.log(chalk.dim(`Migrated ${describeLegacyMigration(migration)}: ${migration.from} → ${migration.to}`));
|
|
88
|
+
}
|
|
89
|
+
this.reportKeptInPlace(migration);
|
|
90
|
+
}
|
|
91
|
+
const declinedMigrations = await this.offerConsentedLegacyMigrations(resolvedProjectPath);
|
|
54
92
|
// Use detected tool directories to preserve existing opsx skills/commands.
|
|
55
93
|
const detectedTools = getAvailableTools(resolvedProjectPath);
|
|
56
94
|
migrateIfNeededShared(resolvedProjectPath, detectedTools);
|
|
@@ -60,31 +98,47 @@ export class UpdateCommand {
|
|
|
60
98
|
const delivery = globalConfig.delivery ?? 'both';
|
|
61
99
|
const profileWorkflows = getProfileWorkflows(profile, globalConfig.workflows);
|
|
62
100
|
const desiredWorkflows = profileWorkflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
|
|
63
|
-
const shouldGenerateSkills = delivery !== 'commands';
|
|
64
|
-
const shouldGenerateCommands = delivery !== 'skills';
|
|
65
101
|
// 4. Detect and handle legacy artifacts + upgrade legacy tools using effective config
|
|
66
|
-
const
|
|
102
|
+
const legacyUpgrade = await this.handleLegacyCleanup(resolvedProjectPath, desiredWorkflows, delivery);
|
|
103
|
+
const { newlyConfiguredTools, workflowOverrides: legacyWorkflowOverrides, deferredGlobalCleanup, } = legacyUpgrade;
|
|
67
104
|
// 5. Find configured tools
|
|
68
105
|
const configuredTools = getConfiguredToolsForProfileSync(resolvedProjectPath);
|
|
106
|
+
const configuredAndNewTools = [...new Set([...configuredTools, ...newlyConfiguredTools])];
|
|
69
107
|
if (configuredTools.length === 0 && newlyConfiguredTools.length === 0) {
|
|
108
|
+
if (deferredGlobalCleanup) {
|
|
109
|
+
await this.performDeferredGlobalPromptCleanup(resolvedProjectPath, deferredGlobalCleanup);
|
|
110
|
+
}
|
|
111
|
+
if (declinedMigrations.length > 0) {
|
|
112
|
+
// Not an unconfigured project — a configured one the user chose to
|
|
113
|
+
// leave in its former directory. Saying "run init" would be wrong.
|
|
114
|
+
for (const migration of declinedMigrations) {
|
|
115
|
+
console.log(chalk.yellow(`Nothing to update: this project's OpenSpec files are still in ${migration.from}/, ` +
|
|
116
|
+
`which OpenSpec no longer writes.`));
|
|
117
|
+
console.log(chalk.dim(`Re-run "openspec update" and accept the move to ${migration.to}/ to resume updates.`));
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await this.syncCopilotCloudFiles(resolvedProjectPath, configuredAndNewTools);
|
|
70
122
|
console.log(chalk.yellow('No configured tools found.'));
|
|
71
123
|
console.log(chalk.dim('Run "openspec init" to set up tools.'));
|
|
72
124
|
return;
|
|
73
125
|
}
|
|
74
|
-
// 6. Check version status for all configured tools
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const toolStatuses = configuredTools.map((toolId) => {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return { ...status, configured: true };
|
|
81
|
-
}
|
|
82
|
-
return status;
|
|
83
|
-
});
|
|
126
|
+
// 6. Check version status for all configured tools, against the same workflow set
|
|
127
|
+
// the generation loop below writes — otherwise a legacy-upgraded tool would be
|
|
128
|
+
// fingerprinted against commands it was never given.
|
|
129
|
+
const toolStatuses = configuredTools.map((toolId) => getToolVersionStatus(resolvedProjectPath, toolId, OPENSPEC_VERSION, {
|
|
130
|
+
workflows: legacyWorkflowOverrides[toolId] ?? desiredWorkflows,
|
|
131
|
+
}));
|
|
84
132
|
const statusByTool = new Map(toolStatuses.map((status) => [status.toolId, status]));
|
|
85
133
|
// 7. Smart update detection
|
|
86
134
|
const toolsNeedingVersionUpdate = toolStatuses
|
|
87
|
-
.filter((s) =>
|
|
135
|
+
.filter((s) => {
|
|
136
|
+
if (!s.needsUpdate || delivery !== 'commands') {
|
|
137
|
+
return s.needsUpdate;
|
|
138
|
+
}
|
|
139
|
+
const tool = AI_TOOLS.find((candidate) => candidate.value === s.toolId);
|
|
140
|
+
return !tool || !hasGlobalSkillTarget(tool);
|
|
141
|
+
})
|
|
88
142
|
.map((s) => s.toolId);
|
|
89
143
|
const toolsNeedingConfigSync = getToolsNeedingProfileSync(resolvedProjectPath, desiredWorkflows, delivery, configuredTools);
|
|
90
144
|
const toolsToUpdateSet = new Set([
|
|
@@ -92,56 +146,82 @@ export class UpdateCommand {
|
|
|
92
146
|
...toolsNeedingConfigSync,
|
|
93
147
|
]);
|
|
94
148
|
const toolsUpToDate = toolStatuses.filter((s) => !toolsToUpdateSet.has(s.toolId));
|
|
95
|
-
if (!this.force && toolsToUpdateSet.size === 0) {
|
|
149
|
+
if (!this.force && toolsToUpdateSet.size === 0 && newlyConfiguredTools.length === 0) {
|
|
150
|
+
if (deferredGlobalCleanup) {
|
|
151
|
+
await this.performDeferredGlobalPromptCleanup(resolvedProjectPath, deferredGlobalCleanup);
|
|
152
|
+
}
|
|
96
153
|
// All tools are up to date
|
|
97
154
|
this.displayUpToDateMessage(toolStatuses);
|
|
155
|
+
await this.syncCopilotCloudFiles(resolvedProjectPath, configuredAndNewTools);
|
|
98
156
|
// Still check for new tool directories and extra workflows
|
|
99
157
|
this.detectNewTools(resolvedProjectPath, configuredTools);
|
|
100
158
|
this.displayExtraWorkflowsNote(resolvedProjectPath, configuredTools, desiredWorkflows);
|
|
101
|
-
this.
|
|
159
|
+
this.displayMissingCoreWorkflowsNote(profile, globalConfig.workflows);
|
|
160
|
+
this.displaySetupNotes(configuredTools);
|
|
102
161
|
return;
|
|
103
162
|
}
|
|
104
163
|
// 8. Display update plan
|
|
105
164
|
if (this.force) {
|
|
106
165
|
console.log(`Force updating ${configuredTools.length} tool(s): ${configuredTools.join(', ')}`);
|
|
107
166
|
}
|
|
167
|
+
else if (toolsToUpdateSet.size === 0) {
|
|
168
|
+
console.log('No additional refresh needed after legacy migration.');
|
|
169
|
+
}
|
|
108
170
|
else {
|
|
109
171
|
this.displayUpdatePlan([...toolsToUpdateSet], statusByTool, toolsUpToDate);
|
|
110
172
|
}
|
|
111
173
|
console.log();
|
|
112
174
|
// 9. Determine what to generate based on delivery
|
|
113
|
-
const
|
|
114
|
-
const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
|
|
175
|
+
const deliveryIncludesCommands = delivery !== 'skills';
|
|
115
176
|
// 10. Update tools (all if force, otherwise only those needing update)
|
|
116
177
|
const toolsToUpdate = this.force ? configuredTools : [...toolsToUpdateSet];
|
|
117
178
|
const updatedTools = [];
|
|
118
179
|
const failedTools = [];
|
|
180
|
+
const skillsInvocableCommandSkips = [];
|
|
181
|
+
const zeroArtifactTools = [];
|
|
119
182
|
let removedCommandCount = 0;
|
|
120
183
|
let removedSkillCount = 0;
|
|
121
184
|
let removedDeselectedCommandCount = 0;
|
|
122
185
|
let removedDeselectedSkillCount = 0;
|
|
123
186
|
for (const toolId of toolsToUpdate) {
|
|
124
187
|
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
125
|
-
if (!tool
|
|
188
|
+
if (!tool || !toolSupportsSkills(tool))
|
|
126
189
|
continue;
|
|
127
190
|
const spinner = ora(`Updating ${tool.name}...`).start();
|
|
128
191
|
try {
|
|
129
|
-
const skillsDir =
|
|
192
|
+
const skillsDir = resolveToolSkillsDir(resolvedProjectPath, tool);
|
|
193
|
+
const skillsRoot = hasGlobalSkillTarget(tool) ? skillsDir : resolvedProjectPath;
|
|
194
|
+
const shouldGenerateSkills = shouldGenerateSkillsForTool(tool.value, delivery);
|
|
195
|
+
const shouldGenerateCommands = shouldGenerateCommandsForTool(tool.value, delivery);
|
|
196
|
+
const toolWorkflows = legacyWorkflowOverrides[tool.value] ?? desiredWorkflows;
|
|
197
|
+
const skillTemplates = getSkillTemplates(toolWorkflows);
|
|
198
|
+
const commandContents = getCommandContents(toolWorkflows);
|
|
130
199
|
// Generate skill files if delivery includes skills
|
|
131
200
|
if (shouldGenerateSkills) {
|
|
132
201
|
for (const { template, dirName } of skillTemplates) {
|
|
133
202
|
const skillDir = path.join(skillsDir, dirName);
|
|
134
203
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
135
|
-
|
|
136
|
-
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
|
|
204
|
+
const transformer = getTransformerForTool(tool.value, delivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
|
|
137
205
|
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
206
|
+
FileSystemUtils.assertPathWithin(skillsRoot, skillFile);
|
|
138
207
|
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
139
208
|
}
|
|
140
|
-
|
|
209
|
+
writeSharedSkillTarget(resolvedProjectPath, tool.value);
|
|
210
|
+
removedDeselectedSkillCount += await this.removeUnselectedSkillDirs(skillsRoot, skillsDir, toolWorkflows);
|
|
141
211
|
}
|
|
142
212
|
// Delete skill directories if delivery is commands-only
|
|
143
|
-
if (!
|
|
144
|
-
removedSkillCount += await this.removeSkillDirs(skillsDir);
|
|
213
|
+
if (shouldRemoveSkillsForTool(tool.value, delivery) && !hasGlobalSkillTarget(tool)) {
|
|
214
|
+
removedSkillCount += await this.removeSkillDirs(skillsRoot, skillsDir);
|
|
215
|
+
// Persist the selected owner even when commands-only delivery leaves
|
|
216
|
+
// this target with no generated skills.
|
|
217
|
+
writeSharedSkillTarget(resolvedProjectPath, tool.value);
|
|
218
|
+
// A tool with no command adapter now has zero OpenSpec artifacts;
|
|
219
|
+
// say so like init does, rather than deleting its skills silently
|
|
220
|
+
// and letting tool detection re-suggest an init that would also
|
|
221
|
+
// generate nothing under this delivery setting.
|
|
222
|
+
if (!shouldGenerateCommandsForTool(tool.value, delivery)) {
|
|
223
|
+
zeroArtifactTools.push(tool.name);
|
|
224
|
+
}
|
|
145
225
|
}
|
|
146
226
|
// Generate commands if delivery includes commands
|
|
147
227
|
if (shouldGenerateCommands) {
|
|
@@ -149,18 +229,27 @@ export class UpdateCommand {
|
|
|
149
229
|
if (adapter) {
|
|
150
230
|
const generatedCommands = generateCommands(commandContents, adapter);
|
|
151
231
|
for (const cmd of generatedCommands) {
|
|
152
|
-
const commandFile =
|
|
232
|
+
const commandFile = FileSystemUtils.resolveProjectArtifactPath(resolvedProjectPath, cmd.path);
|
|
153
233
|
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
154
234
|
}
|
|
155
|
-
removedDeselectedCommandCount += await this.removeUnselectedCommandFiles(resolvedProjectPath, toolId,
|
|
235
|
+
removedDeselectedCommandCount += await this.removeUnselectedCommandFiles(resolvedProjectPath, toolId, toolWorkflows);
|
|
156
236
|
}
|
|
157
237
|
}
|
|
238
|
+
else if (deliveryIncludesCommands && resolveCommandSurfaceCapability(tool.value) === 'skills-invocable') {
|
|
239
|
+
skillsInvocableCommandSkips.push(tool.value);
|
|
240
|
+
}
|
|
158
241
|
// Delete command files if delivery is skills-only
|
|
159
|
-
if (
|
|
242
|
+
if (shouldReconcileCommandFilesForTool(tool.value, delivery)) {
|
|
160
243
|
removedCommandCount += await this.removeCommandFiles(resolvedProjectPath, toolId);
|
|
161
244
|
}
|
|
162
245
|
spinner.succeed(`Updated ${tool.name}`);
|
|
163
246
|
updatedTools.push(tool.name);
|
|
247
|
+
for (const migration of migrateLegacyToolDirs(resolvedProjectPath, [tool.value], 'after-generation')) {
|
|
248
|
+
if (hasMovableContent(migration)) {
|
|
249
|
+
console.log(chalk.dim(`Migrated ${describeLegacyMigration(migration)}: ${migration.from} → ${migration.to}`));
|
|
250
|
+
}
|
|
251
|
+
this.reportKeptInPlace(migration);
|
|
252
|
+
}
|
|
164
253
|
}
|
|
165
254
|
catch (error) {
|
|
166
255
|
spinner.fail(`Failed to update ${tool.name}`);
|
|
@@ -170,6 +259,9 @@ export class UpdateCommand {
|
|
|
170
259
|
});
|
|
171
260
|
}
|
|
172
261
|
}
|
|
262
|
+
if (deferredGlobalCleanup) {
|
|
263
|
+
await this.performDeferredGlobalPromptCleanup(resolvedProjectPath, deferredGlobalCleanup);
|
|
264
|
+
}
|
|
173
265
|
// 11. Summary
|
|
174
266
|
console.log();
|
|
175
267
|
if (updatedTools.length > 0) {
|
|
@@ -178,34 +270,69 @@ export class UpdateCommand {
|
|
|
178
270
|
if (failedTools.length > 0) {
|
|
179
271
|
console.log(chalk.red(`✗ Failed: ${failedTools.map(f => `${f.name} (${f.error})`).join(', ')}`));
|
|
180
272
|
}
|
|
273
|
+
if (skillsInvocableCommandSkips.length > 0) {
|
|
274
|
+
console.log(chalk.dim(`Commands skipped for: ${skillsInvocableCommandSkips.join(', ')} (uses skills)`));
|
|
275
|
+
}
|
|
181
276
|
if (removedCommandCount > 0) {
|
|
182
277
|
console.log(chalk.dim(`Removed: ${removedCommandCount} command files (delivery: skills)`));
|
|
183
278
|
}
|
|
184
279
|
if (removedSkillCount > 0) {
|
|
185
280
|
console.log(chalk.dim(`Removed: ${removedSkillCount} skill directories (delivery: commands)`));
|
|
186
281
|
}
|
|
282
|
+
if (zeroArtifactTools.length > 0) {
|
|
283
|
+
const names = zeroArtifactTools.join(', ');
|
|
284
|
+
console.log(chalk.yellow(`No skills or commands remain for ${names}: delivery is set to 'commands' but ` +
|
|
285
|
+
`${zeroArtifactTools.length === 1 ? 'it supports' : 'they support'} only skills. ` +
|
|
286
|
+
`Run 'openspec config set delivery both' to generate skills.`));
|
|
287
|
+
}
|
|
187
288
|
if (removedDeselectedCommandCount > 0) {
|
|
188
289
|
console.log(chalk.dim(`Removed: ${removedDeselectedCommandCount} command files (deselected workflows)`));
|
|
189
290
|
}
|
|
190
291
|
if (removedDeselectedSkillCount > 0) {
|
|
191
292
|
console.log(chalk.dim(`Removed: ${removedDeselectedSkillCount} skill directories (deselected workflows)`));
|
|
192
293
|
}
|
|
193
|
-
// 12. Show onboarding message for newly configured tools from legacy upgrade
|
|
294
|
+
// 12. Show onboarding message for newly configured tools from legacy upgrade.
|
|
295
|
+
// Command tools get the command name their files answer to, skill-only
|
|
296
|
+
// tools their documented skill invocation, and disagreements fall back to
|
|
297
|
+
// naming the skill.
|
|
194
298
|
if (newlyConfiguredTools.length > 0) {
|
|
299
|
+
const referenceFor = (command) => {
|
|
300
|
+
const neutralForm = `the ${transformToSkillReferences(command).slice(1)} skill`;
|
|
301
|
+
const forms = new Set(newlyConfiguredTools.map((toolId) => {
|
|
302
|
+
if (shouldGenerateCommandsForTool(toolId, delivery)) {
|
|
303
|
+
// Name the command the tool's files actually answer to:
|
|
304
|
+
// /opsx-<id> where the filename is the command name.
|
|
305
|
+
const transformer = getTransformerForTool(toolId, delivery, resolveCommandSurfaceCapability(toolId), resolveCommandInvocation(toolId));
|
|
306
|
+
return transformer ? transformer(command) : command;
|
|
307
|
+
}
|
|
308
|
+
return getSkillReferenceTransformer(toolId)(command);
|
|
309
|
+
}));
|
|
310
|
+
return forms.size === 1 ? [...forms][0] : neutralForm;
|
|
311
|
+
};
|
|
312
|
+
// Only hint at workflows these tools actually received. A legacy upgrade
|
|
313
|
+
// can install a narrower set than the profile (inferred Codex prompts).
|
|
314
|
+
const installedWorkflows = [
|
|
315
|
+
...new Set(newlyConfiguredTools.flatMap((toolId) => legacyWorkflowOverrides[toolId] ?? desiredWorkflows)),
|
|
316
|
+
];
|
|
317
|
+
const entries = getOnboardingCommands(installedWorkflows).map(({ command, description }) => [referenceFor(command), description]);
|
|
195
318
|
console.log();
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
319
|
+
if (entries.length > 0) {
|
|
320
|
+
const width = Math.max(...entries.map(([reference]) => reference.length));
|
|
321
|
+
console.log(chalk.bold('Getting started:'));
|
|
322
|
+
for (const [reference, description] of entries) {
|
|
323
|
+
console.log(` ${reference.padEnd(width)} ${description}`);
|
|
324
|
+
}
|
|
325
|
+
console.log();
|
|
326
|
+
}
|
|
327
|
+
console.log(`Learn more: ${chalk.cyan('https://github.com/codewalla-engineering/openspec-upstream-sync')}`);
|
|
202
328
|
}
|
|
203
|
-
|
|
329
|
+
await this.syncCopilotCloudFiles(resolvedProjectPath, configuredAndNewTools);
|
|
204
330
|
// 13. Detect new tool directories not currently configured
|
|
205
331
|
this.detectNewTools(resolvedProjectPath, configuredAndNewTools);
|
|
206
332
|
// 14. Display note about extra workflows not in profile
|
|
207
333
|
this.displayExtraWorkflowsNote(resolvedProjectPath, configuredAndNewTools, desiredWorkflows);
|
|
208
|
-
this.
|
|
334
|
+
this.displayMissingCoreWorkflowsNote(profile, globalConfig.workflows);
|
|
335
|
+
this.displaySetupNotes(configuredAndNewTools);
|
|
209
336
|
// 15. List affected tools
|
|
210
337
|
if (updatedTools.length > 0) {
|
|
211
338
|
const toolDisplayNames = updatedTools;
|
|
@@ -213,6 +340,51 @@ export class UpdateCommand {
|
|
|
213
340
|
}
|
|
214
341
|
console.log();
|
|
215
342
|
console.log(chalk.dim('Restart your IDE for changes to take effect.'));
|
|
343
|
+
if (failedTools.length > 0) {
|
|
344
|
+
throw new Error(`OpenSpec update failed for: ${failedTools.map((tool) => tool.name).join(', ')}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async syncCopilotCloudFiles(projectPath, configuredTools) {
|
|
348
|
+
try {
|
|
349
|
+
if (includesGitHubCopilot(configuredTools)) {
|
|
350
|
+
// Cloud files are opt-in (see cloud-agent.ts). `update` never prompts,
|
|
351
|
+
// so it only refreshes files the user has already opted into (via
|
|
352
|
+
// `openspec init` or a `githubCopilot.cloudAgent: true` config), or that
|
|
353
|
+
// a pre-opt-in project already has. Opting in is a deliberate init/config
|
|
354
|
+
// step, never a silent side effect of running update.
|
|
355
|
+
if (await isCopilotCloudEnabled(projectPath)) {
|
|
356
|
+
await writeCopilotCloudFiles(projectPath);
|
|
357
|
+
const collisions = await findUnmanagedCloudFiles(projectPath);
|
|
358
|
+
if (collisions.length > 0) {
|
|
359
|
+
console.log(chalk.dim(`Left your existing ${collisions.join(' and ')} untouched — add the OpenSpec ` +
|
|
360
|
+
`install step by hand so the Copilot cloud agent can run openspec.`));
|
|
361
|
+
}
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
// Explicit opt-out (githubCopilot.cloudAgent: false) means "not here":
|
|
365
|
+
// remove any managed files a prior opt-in left behind (customized files
|
|
366
|
+
// are preserved). If the user simply never decided, stay quiet unless
|
|
367
|
+
// we're at an interactive terminal, where a one-line hint aids discovery.
|
|
368
|
+
if (readCopilotCloudOptIn(projectPath) === false) {
|
|
369
|
+
const removed = await removeCopilotCloudFiles(projectPath);
|
|
370
|
+
if (removed > 0) {
|
|
371
|
+
console.log(chalk.dim(`Removed: ${removed} Copilot cloud agent file(s) (opted out of cloud files)`));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
else if (isInteractive()) {
|
|
375
|
+
console.log(chalk.dim("GitHub Copilot cloud coding-agent files are available (opt-in). Enable with 'openspec init --copilot-cloud'."));
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const removed = await removeCopilotCloudFiles(projectPath);
|
|
380
|
+
if (removed > 0) {
|
|
381
|
+
console.log(chalk.dim(`Removed: ${removed} Copilot cloud agent file(s) (github-copilot not configured)`));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
386
|
+
console.warn(`Warning: failed to sync Copilot cloud agent files: ${message}`);
|
|
387
|
+
}
|
|
216
388
|
}
|
|
217
389
|
/**
|
|
218
390
|
* Display message when all tools are up to date.
|
|
@@ -242,6 +414,18 @@ export class UpdateCommand {
|
|
|
242
414
|
console.log(chalk.dim(`Already up to date: ${upToDateNames.join(', ')}`));
|
|
243
415
|
}
|
|
244
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Shows manual setup notes for configured tools that need extra
|
|
419
|
+
* configuration before they pick up generated files.
|
|
420
|
+
*/
|
|
421
|
+
displaySetupNotes(toolIds) {
|
|
422
|
+
for (const toolId of toolIds) {
|
|
423
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
424
|
+
if (tool?.setupNote) {
|
|
425
|
+
console.log(chalk.yellow(`Setup required for ${tool.name}: ${tool.setupNote}`));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
245
429
|
/**
|
|
246
430
|
* Detects new tool directories that aren't currently configured and displays a hint.
|
|
247
431
|
*/
|
|
@@ -270,38 +454,41 @@ export class UpdateCommand {
|
|
|
270
454
|
}
|
|
271
455
|
}
|
|
272
456
|
/**
|
|
273
|
-
*
|
|
274
|
-
*
|
|
457
|
+
* Point out core workflows a custom profile is missing, so releases that
|
|
458
|
+
* grow CORE_WORKFLOWS stay discoverable. Keep custom profiles user-owned;
|
|
459
|
+
* do not mutate them.
|
|
275
460
|
*/
|
|
276
|
-
|
|
461
|
+
displayMissingCoreWorkflowsNote(profile, workflows) {
|
|
277
462
|
if (profile !== 'custom' || !workflows) {
|
|
278
463
|
return;
|
|
279
464
|
}
|
|
280
465
|
const workflowSet = new Set(workflows);
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
if (!matchesOldCore) {
|
|
466
|
+
const missing = CORE_WORKFLOWS.filter((workflow) => !workflowSet.has(workflow));
|
|
467
|
+
if (missing.length === 0) {
|
|
284
468
|
return;
|
|
285
469
|
}
|
|
286
|
-
|
|
287
|
-
|
|
470
|
+
const label = missing.length === 1 ? 'workflow' : 'workflows';
|
|
471
|
+
const pronoun = missing.length === 1 ? 'it' : 'them';
|
|
472
|
+
console.log(chalk.dim(`Note: Your custom profile is missing ${missing.length} core ${label}: ${missing.join(', ')}`));
|
|
473
|
+
console.log(chalk.dim(`Run \`openspec config profile\` to add ${pronoun}, or \`openspec config profile core\` to use the core set.`));
|
|
288
474
|
}
|
|
289
475
|
/**
|
|
290
476
|
* Removes skill directories for workflows when delivery changed to commands-only.
|
|
291
477
|
* Returns the number of directories removed.
|
|
292
478
|
*/
|
|
293
|
-
async removeSkillDirs(skillsDir) {
|
|
479
|
+
async removeSkillDirs(skillsRoot, skillsDir) {
|
|
294
480
|
let removed = 0;
|
|
295
481
|
for (const workflow of ALL_WORKFLOWS) {
|
|
296
482
|
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
297
483
|
if (!dirName)
|
|
298
484
|
continue;
|
|
299
485
|
const skillDir = path.join(skillsDir, dirName);
|
|
486
|
+
if (!fs.existsSync(skillDir))
|
|
487
|
+
continue;
|
|
488
|
+
FileSystemUtils.assertPathWithin(skillsRoot, skillDir);
|
|
300
489
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
removed++;
|
|
304
|
-
}
|
|
490
|
+
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
|
491
|
+
removed++;
|
|
305
492
|
}
|
|
306
493
|
catch {
|
|
307
494
|
// Ignore errors
|
|
@@ -313,7 +500,7 @@ export class UpdateCommand {
|
|
|
313
500
|
* Removes skill directories for workflows that are no longer selected in the active profile.
|
|
314
501
|
* Returns the number of directories removed.
|
|
315
502
|
*/
|
|
316
|
-
async removeUnselectedSkillDirs(skillsDir, desiredWorkflows) {
|
|
503
|
+
async removeUnselectedSkillDirs(skillsRoot, skillsDir, desiredWorkflows) {
|
|
317
504
|
const desiredSet = new Set(desiredWorkflows);
|
|
318
505
|
let removed = 0;
|
|
319
506
|
for (const workflow of ALL_WORKFLOWS) {
|
|
@@ -323,11 +510,12 @@ export class UpdateCommand {
|
|
|
323
510
|
if (!dirName)
|
|
324
511
|
continue;
|
|
325
512
|
const skillDir = path.join(skillsDir, dirName);
|
|
513
|
+
if (!fs.existsSync(skillDir))
|
|
514
|
+
continue;
|
|
515
|
+
FileSystemUtils.assertPathWithin(skillsRoot, skillDir);
|
|
326
516
|
try {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
removed++;
|
|
330
|
-
}
|
|
517
|
+
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
|
518
|
+
removed++;
|
|
331
519
|
}
|
|
332
520
|
catch {
|
|
333
521
|
// Ignore errors
|
|
@@ -346,7 +534,7 @@ export class UpdateCommand {
|
|
|
346
534
|
return 0;
|
|
347
535
|
for (const workflow of ALL_WORKFLOWS) {
|
|
348
536
|
const cmdPath = adapter.getFilePath(workflow);
|
|
349
|
-
const fullPath =
|
|
537
|
+
const fullPath = FileSystemUtils.resolveProjectArtifactPath(projectPath, cmdPath);
|
|
350
538
|
try {
|
|
351
539
|
if (fs.existsSync(fullPath)) {
|
|
352
540
|
await fs.promises.unlink(fullPath);
|
|
@@ -373,7 +561,7 @@ export class UpdateCommand {
|
|
|
373
561
|
if (desiredSet.has(workflow))
|
|
374
562
|
continue;
|
|
375
563
|
const cmdPath = adapter.getFilePath(workflow);
|
|
376
|
-
const fullPath =
|
|
564
|
+
const fullPath = FileSystemUtils.resolveProjectArtifactPath(projectPath, cmdPath);
|
|
377
565
|
try {
|
|
378
566
|
if (fs.existsSync(fullPath)) {
|
|
379
567
|
await fs.promises.unlink(fullPath);
|
|
@@ -386,6 +574,72 @@ export class UpdateCommand {
|
|
|
386
574
|
}
|
|
387
575
|
return removed;
|
|
388
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* Offers to move OpenSpec content out of a renamed tool's former directory
|
|
579
|
+
* when the old location might still be the live one — today, Windsurf's
|
|
580
|
+
* `.windsurf/` after the Devin Desktop rebrand.
|
|
581
|
+
*
|
|
582
|
+
* Interactive runs are asked, because nothing on disk distinguishes a user
|
|
583
|
+
* who took the rebrand from one still on a pre-rebrand Windsurf build that
|
|
584
|
+
* reads only `.windsurf/`. `--force` and non-interactive runs migrate, which
|
|
585
|
+
* is what an unattended upgrade wants.
|
|
586
|
+
*/
|
|
587
|
+
/** Surfaces files the move left behind rather than overwriting. */
|
|
588
|
+
reportKeptInPlace(migration) {
|
|
589
|
+
const notice = keptInPlaceNotice(migration);
|
|
590
|
+
if (notice)
|
|
591
|
+
console.log(chalk.dim(notice));
|
|
592
|
+
}
|
|
593
|
+
async offerConsentedLegacyMigrations(projectPath) {
|
|
594
|
+
const pending = findLegacyToolMigrations(projectPath).filter((m) => m.needsConsent);
|
|
595
|
+
const declined = [];
|
|
596
|
+
if (pending.length === 0)
|
|
597
|
+
return declined;
|
|
598
|
+
for (const migration of pending) {
|
|
599
|
+
// Nothing movable: every legacy file differs from its counterpart, so
|
|
600
|
+
// there is no move to offer. Still say so — silence would leave two
|
|
601
|
+
// divergent copies the user never hears about.
|
|
602
|
+
if (!hasMovableContent(migration)) {
|
|
603
|
+
this.reportKeptInPlace(migration);
|
|
604
|
+
console.log();
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
console.log(chalk.yellow(legacyMigrationNotice(migration)));
|
|
608
|
+
if (!this.force && isInteractive()) {
|
|
609
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
610
|
+
let shouldMigrate;
|
|
611
|
+
try {
|
|
612
|
+
shouldMigrate = await confirm({
|
|
613
|
+
message: `Move ${describeLegacyMigration(migration)} from ${migration.from}/ to ${migration.to}/?`,
|
|
614
|
+
default: true,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// Closed stdin is not consent, and it must not abort the update.
|
|
619
|
+
shouldMigrate = false;
|
|
620
|
+
}
|
|
621
|
+
if (!shouldMigrate) {
|
|
622
|
+
// Say what declining costs. OpenSpec writes the current root now, so
|
|
623
|
+
// the files keep working where they are, but OpenSpec stops managing
|
|
624
|
+
// them — it no longer looks in the former directory.
|
|
625
|
+
console.log(chalk.dim(`Left in place. OpenSpec writes ${migration.to}/ now and will not manage ` +
|
|
626
|
+
`${migration.from}/, so those files stay as they are until you move them. ` +
|
|
627
|
+
`You will be asked again next run.`));
|
|
628
|
+
console.log();
|
|
629
|
+
declined.push(migration);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
for (const applied of migrateLegacyToolDirs(projectPath, [migration.toolId])) {
|
|
634
|
+
if (hasMovableContent(applied)) {
|
|
635
|
+
console.log(chalk.dim(`Migrated ${describeLegacyMigration(applied)}: ${applied.from} → ${applied.to}`));
|
|
636
|
+
}
|
|
637
|
+
this.reportKeptInPlace(applied);
|
|
638
|
+
}
|
|
639
|
+
console.log();
|
|
640
|
+
}
|
|
641
|
+
return declined;
|
|
642
|
+
}
|
|
389
643
|
/**
|
|
390
644
|
* Detect and handle legacy OpenSpec artifacts.
|
|
391
645
|
* Unlike init, update warns but continues if legacy files found in non-interactive mode.
|
|
@@ -395,25 +649,37 @@ export class UpdateCommand {
|
|
|
395
649
|
// Detect legacy artifacts
|
|
396
650
|
const detection = await detectLegacyArtifacts(projectPath);
|
|
397
651
|
if (!detection.hasLegacyArtifacts) {
|
|
398
|
-
return []; // No legacy artifacts found
|
|
652
|
+
return { newlyConfiguredTools: [], workflowOverrides: {} }; // No legacy artifacts found
|
|
399
653
|
}
|
|
400
654
|
// Show what was detected
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
655
|
+
const immediateSummary = formatDetectionSummary(omitGlobalLegacyPromptFiles(detection));
|
|
656
|
+
const deferredSummary = formatDeferredGlobalPromptSummary(detection);
|
|
657
|
+
if (immediateSummary || deferredSummary) {
|
|
658
|
+
console.log();
|
|
659
|
+
if (immediateSummary) {
|
|
660
|
+
console.log(immediateSummary);
|
|
661
|
+
console.log();
|
|
662
|
+
}
|
|
663
|
+
if (deferredSummary) {
|
|
664
|
+
console.log(deferredSummary);
|
|
665
|
+
console.log();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
404
668
|
const canPrompt = isInteractive();
|
|
405
669
|
if (this.force) {
|
|
406
|
-
|
|
407
|
-
await this.
|
|
408
|
-
|
|
409
|
-
|
|
670
|
+
const legacyUpgrade = await this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
|
|
671
|
+
await this.performImmediateLegacyCleanup(projectPath, detection);
|
|
672
|
+
return {
|
|
673
|
+
...legacyUpgrade,
|
|
674
|
+
deferredGlobalCleanup: pickGlobalLegacyPromptFiles(detection, detection.globalSlashCommandFiles),
|
|
675
|
+
};
|
|
410
676
|
}
|
|
411
677
|
if (!canPrompt) {
|
|
412
678
|
// Non-interactive mode without --force: warn and continue
|
|
413
679
|
// (Unlike init, update doesn't abort - user may just want to update skills)
|
|
414
680
|
console.log(chalk.yellow('⚠ Run with --force to auto-cleanup legacy files, or run interactively.'));
|
|
415
681
|
console.log();
|
|
416
|
-
return [];
|
|
682
|
+
return { newlyConfiguredTools: [], workflowOverrides: {} };
|
|
417
683
|
}
|
|
418
684
|
// Interactive mode: prompt for confirmation
|
|
419
685
|
const { confirm } = await import('@inquirer/prompts');
|
|
@@ -422,14 +688,47 @@ export class UpdateCommand {
|
|
|
422
688
|
default: true,
|
|
423
689
|
});
|
|
424
690
|
if (shouldCleanup) {
|
|
425
|
-
await this.
|
|
426
|
-
|
|
427
|
-
return
|
|
691
|
+
const legacyUpgrade = await this.upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery);
|
|
692
|
+
await this.performImmediateLegacyCleanup(projectPath, detection);
|
|
693
|
+
return {
|
|
694
|
+
...legacyUpgrade,
|
|
695
|
+
deferredGlobalCleanup: pickGlobalLegacyPromptFiles(detection, detection.globalSlashCommandFiles),
|
|
696
|
+
};
|
|
428
697
|
}
|
|
429
698
|
else {
|
|
430
699
|
console.log(chalk.dim('Skipping legacy cleanup. Continuing with skill update...'));
|
|
431
700
|
console.log();
|
|
432
|
-
return [];
|
|
701
|
+
return { newlyConfiguredTools: [], workflowOverrides: {} };
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Cleans approved repo-local legacy artifacts before configured tools refresh.
|
|
706
|
+
*/
|
|
707
|
+
async performImmediateLegacyCleanup(projectPath, detection) {
|
|
708
|
+
const immediateDetection = omitGlobalLegacyPromptFiles(detection);
|
|
709
|
+
if (immediateDetection.hasLegacyArtifacts) {
|
|
710
|
+
await this.performLegacyCleanup(projectPath, immediateDetection);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Cleans approved global Codex prompts after configured tools refresh so newly
|
|
715
|
+
* installed replacement skills can retire their prompts in the same run.
|
|
716
|
+
*/
|
|
717
|
+
async performDeferredGlobalPromptCleanup(projectPath, detection) {
|
|
718
|
+
const availableCodexWorkflows = new Set(scanInstalledWorkflows(projectPath, ['codex']));
|
|
719
|
+
const removableMatches = getLegacyGlobalPromptMatches(detection)
|
|
720
|
+
.filter((prompt) => prompt.workflowIds.every((workflowId) => availableCodexWorkflows.has(workflowId)));
|
|
721
|
+
if (removableMatches.length > 0) {
|
|
722
|
+
await this.performLegacyCleanup(projectPath, pickGlobalLegacyPromptFiles(detection, removableMatches.map((prompt) => prompt.path)));
|
|
723
|
+
}
|
|
724
|
+
const blockedMatches = getLegacyGlobalPromptMatches(detection)
|
|
725
|
+
.filter((prompt) => !removableMatches.some((match) => match.path === prompt.path));
|
|
726
|
+
if (blockedMatches.length > 0) {
|
|
727
|
+
console.log(chalk.yellow('Preserved deferred global prompts without replacement skills:'));
|
|
728
|
+
for (const prompt of blockedMatches) {
|
|
729
|
+
console.log(chalk.dim(` - ${prompt.toolId}: ${prompt.path}`));
|
|
730
|
+
}
|
|
731
|
+
console.log();
|
|
433
732
|
}
|
|
434
733
|
}
|
|
435
734
|
/**
|
|
@@ -447,14 +746,14 @@ export class UpdateCommand {
|
|
|
447
746
|
console.log();
|
|
448
747
|
}
|
|
449
748
|
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
749
|
+
* Upgrades unconfigured legacy tools into the skills-based setup and carries
|
|
750
|
+
* workflow overrides for migrations that should mirror legacy Codex prompts.
|
|
452
751
|
*/
|
|
453
752
|
async upgradeLegacyTools(projectPath, detection, canPrompt, desiredWorkflows, delivery) {
|
|
454
753
|
// Get tools that had legacy artifacts
|
|
455
754
|
const legacyTools = getToolsFromLegacyArtifacts(detection);
|
|
456
755
|
if (legacyTools.length === 0) {
|
|
457
|
-
return [];
|
|
756
|
+
return { newlyConfiguredTools: [], workflowOverrides: {} };
|
|
458
757
|
}
|
|
459
758
|
// Get currently configured tools
|
|
460
759
|
const configuredTools = getConfiguredToolsForProfileSync(projectPath);
|
|
@@ -462,13 +761,13 @@ export class UpdateCommand {
|
|
|
462
761
|
// Filter to tools that aren't already configured
|
|
463
762
|
const unconfiguredLegacyTools = legacyTools.filter((t) => !configuredSet.has(t));
|
|
464
763
|
if (unconfiguredLegacyTools.length === 0) {
|
|
465
|
-
return [];
|
|
764
|
+
return { newlyConfiguredTools: [], workflowOverrides: {} };
|
|
466
765
|
}
|
|
467
766
|
// Get valid tools (those with skillsDir)
|
|
468
767
|
const validToolIds = new Set(getToolsWithSkillsDir());
|
|
469
768
|
const validUnconfiguredTools = unconfiguredLegacyTools.filter((t) => validToolIds.has(t));
|
|
470
769
|
if (validUnconfiguredTools.length === 0) {
|
|
471
|
-
return [];
|
|
770
|
+
return { newlyConfiguredTools: [], workflowOverrides: {} };
|
|
472
771
|
}
|
|
473
772
|
// Show what tools were detected from legacy artifacts
|
|
474
773
|
console.log(chalk.bold('Tools detected from legacy artifacts:'));
|
|
@@ -504,32 +803,42 @@ export class UpdateCommand {
|
|
|
504
803
|
if (selectedTools.length === 0) {
|
|
505
804
|
console.log(chalk.dim('Skipping tool setup.'));
|
|
506
805
|
console.log();
|
|
507
|
-
return [];
|
|
806
|
+
return { newlyConfiguredTools: [], workflowOverrides: {} };
|
|
508
807
|
}
|
|
509
808
|
}
|
|
809
|
+
const inferredCodexWorkflows = getLegacyWorkflowIdsForTool(detection, 'codex');
|
|
510
810
|
// Create skills/commands for selected tools using effective profile+delivery.
|
|
511
811
|
const newlyConfigured = [];
|
|
512
|
-
const
|
|
513
|
-
const shouldGenerateCommands = delivery !== 'skills';
|
|
514
|
-
const skillTemplates = shouldGenerateSkills ? getSkillTemplates(desiredWorkflows) : [];
|
|
515
|
-
const commandContents = shouldGenerateCommands ? getCommandContents(desiredWorkflows) : [];
|
|
812
|
+
const workflowOverrides = {};
|
|
516
813
|
for (const toolId of selectedTools) {
|
|
517
814
|
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
518
|
-
if (!tool
|
|
815
|
+
if (!tool || !toolSupportsSkills(tool))
|
|
519
816
|
continue;
|
|
520
817
|
const spinner = ora(`Setting up ${tool.name}...`).start();
|
|
521
818
|
try {
|
|
522
|
-
const skillsDir =
|
|
819
|
+
const skillsDir = resolveToolSkillsDir(projectPath, tool);
|
|
820
|
+
const skillsRoot = hasGlobalSkillTarget(tool) ? skillsDir : projectPath;
|
|
821
|
+
const shouldGenerateSkills = shouldGenerateSkillsForTool(tool.value, delivery);
|
|
822
|
+
const shouldGenerateCommands = shouldGenerateCommandsForTool(tool.value, delivery);
|
|
823
|
+
const toolWorkflows = (tool.value === 'codex' && inferredCodexWorkflows.length > 0
|
|
824
|
+
? inferredCodexWorkflows
|
|
825
|
+
: desiredWorkflows);
|
|
826
|
+
if (tool.value === 'codex' && inferredCodexWorkflows.length > 0) {
|
|
827
|
+
workflowOverrides[tool.value] = inferredCodexWorkflows;
|
|
828
|
+
}
|
|
829
|
+
const skillTemplates = getSkillTemplates(toolWorkflows);
|
|
830
|
+
const commandContents = getCommandContents(toolWorkflows);
|
|
523
831
|
// Create skill files when delivery includes skills
|
|
524
832
|
if (shouldGenerateSkills) {
|
|
525
833
|
for (const { template, dirName } of skillTemplates) {
|
|
526
834
|
const skillDir = path.join(skillsDir, dirName);
|
|
527
835
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
528
|
-
|
|
529
|
-
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
|
|
836
|
+
const transformer = getTransformerForTool(tool.value, delivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
|
|
530
837
|
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
838
|
+
FileSystemUtils.assertPathWithin(skillsRoot, skillFile);
|
|
531
839
|
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
532
840
|
}
|
|
841
|
+
writeSharedSkillTarget(projectPath, tool.value);
|
|
533
842
|
}
|
|
534
843
|
// Create commands when delivery includes commands
|
|
535
844
|
if (shouldGenerateCommands) {
|
|
@@ -537,13 +846,19 @@ export class UpdateCommand {
|
|
|
537
846
|
if (adapter) {
|
|
538
847
|
const generatedCommands = generateCommands(commandContents, adapter);
|
|
539
848
|
for (const cmd of generatedCommands) {
|
|
540
|
-
const commandFile =
|
|
849
|
+
const commandFile = FileSystemUtils.resolveProjectArtifactPath(projectPath, cmd.path);
|
|
541
850
|
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
542
851
|
}
|
|
543
852
|
}
|
|
544
853
|
}
|
|
545
854
|
spinner.succeed(`Setup complete for ${tool.name}`);
|
|
546
855
|
newlyConfigured.push(toolId);
|
|
856
|
+
for (const migration of migrateLegacyToolDirs(projectPath, [tool.value], 'after-generation')) {
|
|
857
|
+
if (hasMovableContent(migration)) {
|
|
858
|
+
console.log(chalk.dim(`Migrated ${describeLegacyMigration(migration)}: ${migration.from} → ${migration.to}`));
|
|
859
|
+
}
|
|
860
|
+
this.reportKeptInPlace(migration);
|
|
861
|
+
}
|
|
547
862
|
}
|
|
548
863
|
catch (error) {
|
|
549
864
|
spinner.fail(`Failed to set up ${tool.name}`);
|
|
@@ -553,7 +868,7 @@ export class UpdateCommand {
|
|
|
553
868
|
if (newlyConfigured.length > 0) {
|
|
554
869
|
console.log();
|
|
555
870
|
}
|
|
556
|
-
return newlyConfigured;
|
|
871
|
+
return { newlyConfiguredTools: newlyConfigured, workflowOverrides };
|
|
557
872
|
}
|
|
558
873
|
}
|
|
559
874
|
//# sourceMappingURL=update.js.map
|