@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/init.js
CHANGED
|
@@ -12,19 +12,22 @@ import { createRequire } from 'module';
|
|
|
12
12
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
13
13
|
import { classifyOpenSpecDir, storePointerProblem } from './project-config.js';
|
|
14
14
|
import { findRepoPlanningRootSync } from './planning-home.js';
|
|
15
|
-
import {
|
|
16
|
-
import { AI_TOOLS, OPENSPEC_DIR_NAME, } from './config.js';
|
|
15
|
+
import { getSkillReferenceTransformer, getTransformerForTool, usesNaturalLanguageSkillReferences } from '../utils/command-references.js';
|
|
16
|
+
import { AI_TOOLS, OPENSPEC_DIR_NAME, resolveToolIdAlias, } from './config.js';
|
|
17
17
|
import { PALETTE } from './styles/palette.js';
|
|
18
18
|
import { isInteractive } from '../utils/interactive.js';
|
|
19
19
|
import { serializeConfig } from './config-prompts.js';
|
|
20
20
|
import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
|
|
21
|
-
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, } from './legacy-cleanup.js';
|
|
22
|
-
import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, } from './shared/index.js';
|
|
21
|
+
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDeferredGlobalPromptSummary, formatDetectionSummary, getLegacyGlobalPromptMatches, omitGlobalLegacyPromptFiles, pickGlobalLegacyPromptFiles, } from './legacy-cleanup.js';
|
|
22
|
+
import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, } from './shared/index.js';
|
|
23
23
|
import { getGlobalConfig } from './global-config.js';
|
|
24
24
|
import { getProfileWorkflows, ALL_WORKFLOWS } from './profiles.js';
|
|
25
25
|
import { getAvailableTools } from './available-tools.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
26
|
+
import { writeSharedSkillTarget } from './shared-skill-target.js';
|
|
27
|
+
import { migrateIfNeeded, migrateLegacyToolDirs, describeLegacyMigration, keptInPlaceNotice, hasMovableContent, scanInstalledWorkflows as scanInstalledWorkflowsShared } from './migration.js';
|
|
28
|
+
import { readIdentity, writeIdentity, promptForIdentity, validateIdentity, normalizeIdentity } from '../telemetry/identity.js';
|
|
29
|
+
import { resolveCommandSurfaceCapability, resolveCommandInvocation, shouldGenerateCommandsForTool, shouldGenerateSkillsForTool, shouldReconcileCommandFilesForTool, shouldRemoveSkillsForTool, } from './command-surface.js';
|
|
30
|
+
import { writeCopilotCloudFiles, readCopilotCloudOptIn, hasExistingManagedCloudFiles, persistCopilotCloudOptIn, removeCopilotCloudFiles, findUnmanagedCloudFiles, listManagedCloudFiles, } from './github-copilot/cloud-agent.js';
|
|
28
31
|
const require = createRequire(import.meta.url);
|
|
29
32
|
const { version: OPENSPEC_VERSION } = require('../../package.json');
|
|
30
33
|
// -----------------------------------------------------------------------------
|
|
@@ -40,6 +43,7 @@ const WORKFLOW_TO_SKILL_DIR = {
|
|
|
40
43
|
'new': 'openspec-new-change',
|
|
41
44
|
'continue': 'openspec-continue-change',
|
|
42
45
|
'apply': 'openspec-apply-change',
|
|
46
|
+
'update': 'openspec-update-change',
|
|
43
47
|
'ff': 'openspec-ff-change',
|
|
44
48
|
'sync': 'openspec-sync-specs',
|
|
45
49
|
'archive': 'openspec-archive-change',
|
|
@@ -56,11 +60,15 @@ export class InitCommand {
|
|
|
56
60
|
force;
|
|
57
61
|
interactiveOption;
|
|
58
62
|
profileOverride;
|
|
63
|
+
animation;
|
|
64
|
+
copilotCloudOption;
|
|
59
65
|
constructor(options = {}) {
|
|
60
66
|
this.toolsArg = options.tools;
|
|
61
67
|
this.force = options.force ?? false;
|
|
62
68
|
this.interactiveOption = options.interactive;
|
|
63
69
|
this.profileOverride = options.profile;
|
|
70
|
+
this.animation = options.animation ?? true;
|
|
71
|
+
this.copilotCloudOption = options.copilotCloud;
|
|
64
72
|
}
|
|
65
73
|
async execute(targetPath) {
|
|
66
74
|
const projectPath = path.resolve(targetPath);
|
|
@@ -91,37 +99,124 @@ export class InitCommand {
|
|
|
91
99
|
}
|
|
92
100
|
}
|
|
93
101
|
// Check for legacy artifacts and handle cleanup
|
|
94
|
-
await this.handleLegacyCleanup(projectPath, extendMode);
|
|
102
|
+
const deferredLegacyCleanup = await this.handleLegacyCleanup(projectPath, extendMode);
|
|
103
|
+
// Migrate OpenSpec-managed skills left in renamed tool directories
|
|
104
|
+
// (e.g. .kimi -> .kimi-code) before detection so they stay recognized.
|
|
105
|
+
migrateLegacyToolDirs(projectPath);
|
|
95
106
|
// Detect available tools in the project (task 7.1)
|
|
96
107
|
const detectedTools = getAvailableTools(projectPath);
|
|
97
108
|
// Migration check: migrate existing projects to profile system (task 7.3)
|
|
98
109
|
if (extendMode) {
|
|
99
110
|
migrateIfNeeded(projectPath, detectedTools);
|
|
100
111
|
}
|
|
112
|
+
// Validate profile override early so invalid values fail before tool setup.
|
|
113
|
+
// The resolved value is consumed later when generation reads effective config.
|
|
114
|
+
// This runs ahead of the welcome screen so an invalid --profile does not make
|
|
115
|
+
// the user press Enter before seeing the error.
|
|
116
|
+
this.resolveProfileOverride();
|
|
101
117
|
// Show animated welcome screen (interactive mode only)
|
|
102
118
|
const canPrompt = this.canPromptInteractively();
|
|
103
119
|
if (canPrompt) {
|
|
104
120
|
const { showWelcomeScreen } = await import('../ui/welcome-screen.js');
|
|
105
|
-
await showWelcomeScreen();
|
|
121
|
+
await showWelcomeScreen(this.getActiveWorkflows(), { animate: this.animation });
|
|
122
|
+
}
|
|
123
|
+
// Prompt for identity if not already configured (task 7.1)
|
|
124
|
+
const existingIdentity = await readIdentity();
|
|
125
|
+
if (!existingIdentity) {
|
|
126
|
+
const identity = await promptForIdentity();
|
|
127
|
+
if (identity) {
|
|
128
|
+
const normalized = normalizeIdentity(identity);
|
|
129
|
+
const validation = validateIdentity(normalized);
|
|
130
|
+
if (validation.valid) {
|
|
131
|
+
await writeIdentity(normalized);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.error(`Invalid identity: ${validation.errors.join(', ')}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.error('Identity is required for OpenSpec. Please provide an email or username.');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
106
142
|
}
|
|
107
|
-
await setupTelemetryIdentity({ interactive: canPrompt });
|
|
108
|
-
// Validate profile override early so invalid values fail before tool setup.
|
|
109
|
-
// The resolved value is consumed later when generation reads effective config.
|
|
110
|
-
this.resolveProfileOverride();
|
|
111
143
|
// Get tool states before processing
|
|
112
144
|
const toolStates = getToolStates(projectPath);
|
|
113
145
|
// Get tool selection (pass detected tools for pre-selection)
|
|
114
146
|
const selectedToolIds = await this.getSelectedTools(toolStates, extendMode, detectedTools, projectPath);
|
|
115
147
|
// Validate selected tools
|
|
116
|
-
const validatedTools = this.validateTools(selectedToolIds, toolStates);
|
|
148
|
+
const validatedTools = this.validateTools(selectedToolIds, toolStates, projectPath);
|
|
149
|
+
// Selecting a renamed tool is consent to leave its former directory:
|
|
150
|
+
// init is about to write the current one, and leaving OpenSpec content
|
|
151
|
+
// behind would give the user two installs of the same tool.
|
|
152
|
+
for (const migration of migrateLegacyToolDirs(projectPath, validatedTools.map((tool) => tool.value))) {
|
|
153
|
+
if (hasMovableContent(migration)) {
|
|
154
|
+
console.log(chalk.dim(`Migrated ${describeLegacyMigration(migration)}: ${migration.from} → ${migration.to}`));
|
|
155
|
+
}
|
|
156
|
+
const kept = keptInPlaceNotice(migration);
|
|
157
|
+
if (kept)
|
|
158
|
+
console.log(chalk.dim(kept));
|
|
159
|
+
}
|
|
160
|
+
// Decide whether to generate GitHub Copilot cloud files. This is opt-in
|
|
161
|
+
// (see cloud-agent.ts): selecting the Copilot tool no longer silently
|
|
162
|
+
// writes a GitHub Actions workflow into the user's .github/. The decision
|
|
163
|
+
// is made before generation so the write can be gated, and persisted after
|
|
164
|
+
// config.yaml exists so future non-interactive updates honor it.
|
|
165
|
+
const copilotDecision = await this.resolveCopilotCloudDecision(projectPath, validatedTools);
|
|
117
166
|
// Create directory structure and config
|
|
118
167
|
await this.createDirectoryStructure(openspecPath, extendMode);
|
|
119
168
|
// Generate skills and commands for each tool
|
|
120
|
-
const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
|
|
169
|
+
const results = await this.generateSkillsAndCommands(projectPath, validatedTools, copilotDecision.write);
|
|
170
|
+
// Legacy cleanup was deferred to avoid interfering with skill/command generation;
|
|
171
|
+
// now that outputs are written, finalize the cleanup (e.g. remove stale files).
|
|
172
|
+
if (deferredLegacyCleanup) {
|
|
173
|
+
await this.finalizeDeferredLegacyCleanup(projectPath, deferredLegacyCleanup);
|
|
174
|
+
}
|
|
121
175
|
// Create config.yaml if needed
|
|
122
176
|
const configStatus = await this.createConfig(openspecPath, extendMode);
|
|
177
|
+
// Persist an explicit Copilot cloud decision so `openspec update` (which
|
|
178
|
+
// never prompts) honors it. Best-effort: a config-write failure must not
|
|
179
|
+
// fail an otherwise-successful init.
|
|
180
|
+
if (copilotDecision.persist !== undefined) {
|
|
181
|
+
try {
|
|
182
|
+
await persistCopilotCloudOptIn(projectPath, copilotDecision.persist);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Non-fatal: the files (if any) were still written correctly.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// An explicit opt-out means "no cloud files here": clean up any that a
|
|
189
|
+
// previous run (or an older OpenSpec) generated. Only OpenSpec-managed
|
|
190
|
+
// files are removed — a user-customized file is preserved.
|
|
191
|
+
let copilotRemoved = 0;
|
|
192
|
+
if (copilotDecision.optedOut) {
|
|
193
|
+
try {
|
|
194
|
+
copilotRemoved = await removeCopilotCloudFiles(projectPath);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Non-fatal: removal targets files from a prior run; a failure here
|
|
198
|
+
// just leaves them for the next `openspec update` to clean up.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Report the cloud outcome from what is actually on disk after the write,
|
|
202
|
+
// not from the decision alone: writing over a user-owned file is a no-op,
|
|
203
|
+
// and the alternate-agent path can remove a managed file — so list only
|
|
204
|
+
// managed files that exist, and separately flag any left-untouched ones.
|
|
205
|
+
const copilotSucceeded = [...results.createdTools, ...results.refreshedTools].some((tool) => tool.value === 'github-copilot');
|
|
206
|
+
const wroteCloud = copilotDecision.write && copilotSucceeded;
|
|
207
|
+
const copilotPresent = wroteCloud ? await listManagedCloudFiles(projectPath) : [];
|
|
208
|
+
const copilotCollisions = wroteCloud ? await findUnmanagedCloudFiles(projectPath) : [];
|
|
123
209
|
// Display success message
|
|
124
|
-
this.displaySuccessMessage(projectPath, validatedTools, results, configStatus
|
|
210
|
+
this.displaySuccessMessage(projectPath, validatedTools, results, configStatus, {
|
|
211
|
+
write: copilotDecision.write,
|
|
212
|
+
skippedUndecided: copilotDecision.skippedUndecided,
|
|
213
|
+
present: copilotPresent,
|
|
214
|
+
collisions: copilotCollisions,
|
|
215
|
+
removed: copilotRemoved,
|
|
216
|
+
});
|
|
217
|
+
if (results.failedTools.length > 0) {
|
|
218
|
+
throw new Error(`OpenSpec setup failed for: ${results.failedTools.map((tool) => tool.name).join(', ')}`);
|
|
219
|
+
}
|
|
125
220
|
}
|
|
126
221
|
// ═══════════════════════════════════════════════════════════
|
|
127
222
|
// VALIDATION & SETUP
|
|
@@ -141,6 +236,59 @@ export class InitCommand {
|
|
|
141
236
|
return false;
|
|
142
237
|
return isInteractive({ interactive: this.interactiveOption });
|
|
143
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Decide whether to generate GitHub Copilot cloud files, and whether to
|
|
241
|
+
* persist that decision. Precedence:
|
|
242
|
+
* 1. `--copilot-cloud` / `--no-copilot-cloud` flag (explicit this run)
|
|
243
|
+
* 2. persisted opt-in in config.yaml
|
|
244
|
+
* 3. managed files already present (migration for pre-opt-in projects)
|
|
245
|
+
* 4. interactive confirm (default No)
|
|
246
|
+
* 5. non-interactive with no signal: skip, and don't persist a default
|
|
247
|
+
*
|
|
248
|
+
* @returns `write` — generate the files this run; `persist` — value to write
|
|
249
|
+
* back to config (undefined = leave config untouched); `optedOut` — the user
|
|
250
|
+
* explicitly declined, so any already-generated managed files should be
|
|
251
|
+
* removed; `skippedUndecided` — selected but no signal and couldn't ask, so
|
|
252
|
+
* the caller can hint that the opt-in exists.
|
|
253
|
+
*/
|
|
254
|
+
async resolveCopilotCloudDecision(projectPath, tools) {
|
|
255
|
+
const copilotSelected = tools.some((tool) => tool.value === 'github-copilot');
|
|
256
|
+
if (!copilotSelected) {
|
|
257
|
+
// A flag that can't apply is a likely mistake — say so rather than no-op.
|
|
258
|
+
if (this.copilotCloudOption !== undefined) {
|
|
259
|
+
console.log(chalk.yellow('--copilot-cloud/--no-copilot-cloud was ignored because the github-copilot tool was not selected.'));
|
|
260
|
+
}
|
|
261
|
+
return { write: false, optedOut: false, skippedUndecided: false };
|
|
262
|
+
}
|
|
263
|
+
if (this.copilotCloudOption !== undefined) {
|
|
264
|
+
return {
|
|
265
|
+
write: this.copilotCloudOption,
|
|
266
|
+
persist: this.copilotCloudOption,
|
|
267
|
+
optedOut: !this.copilotCloudOption,
|
|
268
|
+
skippedUndecided: false,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const persistedOptIn = readCopilotCloudOptIn(projectPath);
|
|
272
|
+
if (typeof persistedOptIn === 'boolean') {
|
|
273
|
+
return { write: persistedOptIn, optedOut: !persistedOptIn, skippedUndecided: false };
|
|
274
|
+
}
|
|
275
|
+
if (await hasExistingManagedCloudFiles(projectPath)) {
|
|
276
|
+
return { write: true, optedOut: false, skippedUndecided: false };
|
|
277
|
+
}
|
|
278
|
+
if (this.canPromptInteractively()) {
|
|
279
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
280
|
+
const answer = await confirm({
|
|
281
|
+
message: 'Set up GitHub Copilot cloud coding-agent files? This is for the GitHub-hosted ' +
|
|
282
|
+
'Copilot coding agent (github.com), not Copilot in your editor. It writes two files: ' +
|
|
283
|
+
'.github/workflows/copilot-setup-steps.yml and .github/agents/openspec.agent.md.',
|
|
284
|
+
default: false,
|
|
285
|
+
});
|
|
286
|
+
return { write: answer, persist: answer, optedOut: !answer, skippedUndecided: false };
|
|
287
|
+
}
|
|
288
|
+
// Non-interactive with no explicit signal: don't write, and leave the
|
|
289
|
+
// decision unpersisted so a later interactive run can still prompt.
|
|
290
|
+
return { write: false, optedOut: false, skippedUndecided: true };
|
|
291
|
+
}
|
|
144
292
|
resolveProfileOverride() {
|
|
145
293
|
if (this.profileOverride === undefined) {
|
|
146
294
|
return undefined;
|
|
@@ -150,26 +298,50 @@ export class InitCommand {
|
|
|
150
298
|
}
|
|
151
299
|
throw new Error(`Invalid profile "${this.profileOverride}". Available profiles: core, custom`);
|
|
152
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Resolves the workflows the effective profile installs, so onboarding output
|
|
303
|
+
* only mentions commands that will actually exist.
|
|
304
|
+
*/
|
|
305
|
+
getActiveWorkflows() {
|
|
306
|
+
const globalCfg = getGlobalConfig();
|
|
307
|
+
const activeProfile = this.resolveProfileOverride() ?? globalCfg.profile ?? 'core';
|
|
308
|
+
return [...getProfileWorkflows(activeProfile, globalCfg.workflows)];
|
|
309
|
+
}
|
|
153
310
|
// ═══════════════════════════════════════════════════════════
|
|
154
311
|
// LEGACY CLEANUP
|
|
155
312
|
// ═══════════════════════════════════════════════════════════
|
|
313
|
+
/**
|
|
314
|
+
* Cleans repo-local legacy artifacts immediately and defers global Codex prompt
|
|
315
|
+
* cleanup until replacement skills have been installed.
|
|
316
|
+
*/
|
|
156
317
|
async handleLegacyCleanup(projectPath, extendMode) {
|
|
157
318
|
// Detect legacy artifacts
|
|
158
319
|
const detection = await detectLegacyArtifacts(projectPath);
|
|
159
320
|
if (!detection.hasLegacyArtifacts) {
|
|
160
|
-
return; // No legacy artifacts found
|
|
321
|
+
return null; // No legacy artifacts found
|
|
161
322
|
}
|
|
323
|
+
const immediateDetection = omitGlobalLegacyPromptFiles(detection);
|
|
162
324
|
// Show what was detected
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
325
|
+
const immediateSummary = formatDetectionSummary(immediateDetection);
|
|
326
|
+
if (immediateSummary) {
|
|
327
|
+
console.log();
|
|
328
|
+
console.log(immediateSummary);
|
|
329
|
+
console.log();
|
|
330
|
+
}
|
|
331
|
+
// Show which global prompts are deferred — they'll only be removed once
|
|
332
|
+
// the corresponding replacement skills are installed during generation.
|
|
333
|
+
const deferredSummary = formatDeferredGlobalPromptSummary(detection);
|
|
334
|
+
if (deferredSummary) {
|
|
335
|
+
console.log(deferredSummary);
|
|
336
|
+
console.log();
|
|
337
|
+
}
|
|
166
338
|
const canPrompt = this.canPromptInteractively();
|
|
167
339
|
if (this.force || !canPrompt) {
|
|
168
340
|
// --force flag or non-interactive mode: proceed with cleanup automatically.
|
|
169
341
|
// Legacy slash commands are 100% OpenSpec-managed, and config file cleanup
|
|
170
342
|
// only removes markers (never deletes files), so auto-cleanup is safe.
|
|
171
|
-
await this.
|
|
172
|
-
return;
|
|
343
|
+
await this.performImmediateLegacyCleanup(projectPath, detection);
|
|
344
|
+
return detection.globalSlashCommandFiles.length > 0 ? { detection } : null;
|
|
173
345
|
}
|
|
174
346
|
// Interactive mode: prompt for confirmation
|
|
175
347
|
const { confirm } = await import('@inquirer/prompts');
|
|
@@ -182,7 +354,51 @@ export class InitCommand {
|
|
|
182
354
|
console.log(chalk.dim('Run with --force to skip this prompt, or manually remove legacy files.'));
|
|
183
355
|
process.exit(0);
|
|
184
356
|
}
|
|
185
|
-
await this.
|
|
357
|
+
await this.performImmediateLegacyCleanup(projectPath, detection);
|
|
358
|
+
return detection.globalSlashCommandFiles.length > 0 ? { detection } : null;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Applies the safe subset of legacy cleanup that does not depend on newly
|
|
362
|
+
* generated Codex skills.
|
|
363
|
+
*/
|
|
364
|
+
async performImmediateLegacyCleanup(projectPath, detection) {
|
|
365
|
+
const immediateDetection = omitGlobalLegacyPromptFiles(detection);
|
|
366
|
+
if (!immediateDetection.hasLegacyArtifacts) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
await this.performLegacyCleanup(projectPath, immediateDetection);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Removes only the legacy global Codex prompts whose workflows now have
|
|
373
|
+
* replacement skills in the project.
|
|
374
|
+
*/
|
|
375
|
+
async finalizeDeferredLegacyCleanup(projectPath, deferredCleanup) {
|
|
376
|
+
const availableCodexWorkflows = await this.getInstalledWorkflowsForTool(projectPath, 'codex');
|
|
377
|
+
const removableMatches = getLegacyGlobalPromptMatches(deferredCleanup.detection)
|
|
378
|
+
.filter((prompt) => prompt.workflowIds.every((workflowId) => availableCodexWorkflows.has(workflowId)));
|
|
379
|
+
if (removableMatches.length > 0) {
|
|
380
|
+
await this.performLegacyCleanup(projectPath, pickGlobalLegacyPromptFiles(deferredCleanup.detection, removableMatches.map((prompt) => prompt.path)));
|
|
381
|
+
}
|
|
382
|
+
const blockedMatches = getLegacyGlobalPromptMatches(deferredCleanup.detection)
|
|
383
|
+
.filter((prompt) => !removableMatches.some((match) => match.path === prompt.path));
|
|
384
|
+
if (blockedMatches.length > 0) {
|
|
385
|
+
console.log(chalk.yellow('Preserved deferred global prompts without replacement skills:'));
|
|
386
|
+
for (const prompt of blockedMatches) {
|
|
387
|
+
console.log(chalk.dim(` - ${prompt.toolId}: ${prompt.path}`));
|
|
388
|
+
}
|
|
389
|
+
console.log();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Reads the currently installed workflow IDs for a single tool from the
|
|
394
|
+
* generated skill layout on disk.
|
|
395
|
+
*/
|
|
396
|
+
async getInstalledWorkflowsForTool(projectPath, toolId) {
|
|
397
|
+
const tool = AI_TOOLS.find((candidate) => candidate.value === toolId);
|
|
398
|
+
if (!tool) {
|
|
399
|
+
return new Set();
|
|
400
|
+
}
|
|
401
|
+
return new Set(scanInstalledWorkflowsShared(projectPath, [tool]));
|
|
186
402
|
}
|
|
187
403
|
async performLegacyCleanup(projectPath, detection) {
|
|
188
404
|
const spinner = ora('Cleaning up legacy files...').start();
|
|
@@ -301,7 +517,9 @@ export class InitCommand {
|
|
|
301
517
|
if (tokens.length === 0) {
|
|
302
518
|
throw new Error('The --tools option requires at least one tool ID when not using "all" or "none".');
|
|
303
519
|
}
|
|
304
|
-
|
|
520
|
+
// Retired ids resolve to their current tool, so a rebrand does not break
|
|
521
|
+
// an existing `--tools windsurf` in someone's setup script.
|
|
522
|
+
const normalizedTokens = tokens.map((token) => resolveToolIdAlias(token.toLowerCase()));
|
|
305
523
|
if (normalizedTokens.some((token) => token === 'all' || token === 'none')) {
|
|
306
524
|
throw new Error('Cannot combine reserved values "all" or "none" with specific tool IDs.');
|
|
307
525
|
}
|
|
@@ -318,23 +536,34 @@ export class InitCommand {
|
|
|
318
536
|
}
|
|
319
537
|
return deduped;
|
|
320
538
|
}
|
|
321
|
-
validateTools(toolIds, toolStates) {
|
|
539
|
+
validateTools(toolIds, toolStates, projectPath) {
|
|
322
540
|
const validatedTools = [];
|
|
323
|
-
|
|
541
|
+
const reconciledToolIds = toolIds.includes('codex') && toolIds.includes('agents')
|
|
542
|
+
? toolIds.filter((toolId) => toolId !== 'agents')
|
|
543
|
+
: toolIds;
|
|
544
|
+
if (reconciledToolIds.length !== toolIds.length) {
|
|
545
|
+
console.log(chalk.dim('Codex and agents share .agents/skills; writing one tree with Codex and generic skill references.'));
|
|
546
|
+
}
|
|
547
|
+
for (const toolId of reconciledToolIds) {
|
|
324
548
|
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
325
549
|
if (!tool) {
|
|
326
550
|
const validToolIds = getToolsWithSkillsDir();
|
|
327
551
|
throw new Error(`Unknown tool '${toolId}'. Valid tools:\n ${validToolIds.join('\n ')}`);
|
|
328
552
|
}
|
|
329
|
-
if (!tool
|
|
553
|
+
if (!toolSupportsSkills(tool)) {
|
|
330
554
|
const validToolsWithSkills = getToolsWithSkillsDir();
|
|
331
555
|
throw new Error(`Tool '${toolId}' does not support skill generation.\nTools with skill generation support:\n ${validToolsWithSkills.join('\n ')}`);
|
|
332
556
|
}
|
|
333
557
|
const preState = toolStates.get(tool.value);
|
|
558
|
+
const skillsPath = resolveToolSkillsDir(projectPath, tool);
|
|
559
|
+
const isGlobalSkillTarget = hasGlobalSkillTarget(tool);
|
|
334
560
|
validatedTools.push({
|
|
335
561
|
value: tool.value,
|
|
336
562
|
name: tool.name,
|
|
337
563
|
skillsDir: tool.skillsDir,
|
|
564
|
+
skillsPath,
|
|
565
|
+
skillsRoot: isGlobalSkillTarget ? skillsPath : projectPath,
|
|
566
|
+
isGlobalSkillTarget,
|
|
338
567
|
wasConfigured: preState?.configured ?? false,
|
|
339
568
|
});
|
|
340
569
|
}
|
|
@@ -353,6 +582,7 @@ export class InitCommand {
|
|
|
353
582
|
path.join(openspecPath, 'changes', 'archive'),
|
|
354
583
|
];
|
|
355
584
|
for (const dir of directories) {
|
|
585
|
+
FileSystemUtils.assertProjectArtifactPath(path.dirname(openspecPath), dir);
|
|
356
586
|
await FileSystemUtils.createDirectory(dir);
|
|
357
587
|
}
|
|
358
588
|
return;
|
|
@@ -365,6 +595,7 @@ export class InitCommand {
|
|
|
365
595
|
path.join(openspecPath, 'changes', 'archive'),
|
|
366
596
|
];
|
|
367
597
|
for (const dir of directories) {
|
|
598
|
+
FileSystemUtils.assertProjectArtifactPath(path.dirname(openspecPath), dir);
|
|
368
599
|
await FileSystemUtils.createDirectory(dir);
|
|
369
600
|
}
|
|
370
601
|
spinner.stopAndPersist({
|
|
@@ -375,11 +606,20 @@ export class InitCommand {
|
|
|
375
606
|
// ═══════════════════════════════════════════════════════════
|
|
376
607
|
// SKILL & COMMAND GENERATION
|
|
377
608
|
// ═══════════════════════════════════════════════════════════
|
|
378
|
-
|
|
609
|
+
/**
|
|
610
|
+
* Generates skill files and slash commands for each selected tool,
|
|
611
|
+
* honoring the configured delivery mode (skills, commands, or both).
|
|
612
|
+
*
|
|
613
|
+
* @param projectPath - Absolute path to the project root
|
|
614
|
+
* @param tools - Selected tools with their skill directory metadata
|
|
615
|
+
* @returns Created, refreshed, and failed tools plus removed artifact counts
|
|
616
|
+
*/
|
|
617
|
+
async generateSkillsAndCommands(projectPath, tools, writeCopilotCloud) {
|
|
379
618
|
const createdTools = [];
|
|
380
619
|
const refreshedTools = [];
|
|
381
620
|
const failedTools = [];
|
|
382
621
|
const commandsSkipped = [];
|
|
622
|
+
const skillsInvocableCommandSkips = [];
|
|
383
623
|
let removedCommandCount = 0;
|
|
384
624
|
let removedSkillCount = 0;
|
|
385
625
|
// Read global config for profile and delivery settings (use --profile override if set)
|
|
@@ -388,33 +628,35 @@ export class InitCommand {
|
|
|
388
628
|
const delivery = globalConfig.delivery ?? 'both';
|
|
389
629
|
const workflows = getProfileWorkflows(profile, globalConfig.workflows);
|
|
390
630
|
// Get skill and command templates filtered by profile workflows
|
|
391
|
-
const
|
|
392
|
-
const
|
|
393
|
-
const
|
|
394
|
-
const commandContents = shouldGenerateCommands ? getCommandContents(workflows) : [];
|
|
631
|
+
const deliveryIncludesCommands = delivery !== 'skills';
|
|
632
|
+
const skillTemplates = getSkillTemplates(workflows);
|
|
633
|
+
const commandContents = getCommandContents(workflows);
|
|
395
634
|
// Process each tool
|
|
396
635
|
for (const tool of tools) {
|
|
397
636
|
const spinner = ora(`Setting up ${tool.name}...`).start();
|
|
398
637
|
try {
|
|
399
|
-
|
|
638
|
+
const shouldGenerateSkills = shouldGenerateSkillsForTool(tool.value, delivery);
|
|
639
|
+
const shouldGenerateCommands = shouldGenerateCommandsForTool(tool.value, delivery);
|
|
640
|
+
// Generate skill files if the selected delivery and tool capability allow skills
|
|
400
641
|
if (shouldGenerateSkills) {
|
|
401
|
-
// Use tool-specific skillsDir
|
|
402
|
-
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
403
642
|
// Create skill directories and SKILL.md files
|
|
404
643
|
for (const { template, dirName } of skillTemplates) {
|
|
405
|
-
const skillDir = path.join(
|
|
644
|
+
const skillDir = path.join(tool.skillsPath, dirName);
|
|
406
645
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
407
646
|
// Generate SKILL.md content with YAML frontmatter including generatedBy
|
|
408
|
-
|
|
409
|
-
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
|
|
647
|
+
const transformer = getTransformerForTool(tool.value, delivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
|
|
410
648
|
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
|
|
411
649
|
// Write the skill file
|
|
650
|
+
FileSystemUtils.assertPathWithin(tool.skillsRoot, skillFile);
|
|
412
651
|
await FileSystemUtils.writeFile(skillFile, skillContent);
|
|
413
652
|
}
|
|
653
|
+
writeSharedSkillTarget(projectPath, tool.value);
|
|
414
654
|
}
|
|
415
|
-
if (!
|
|
416
|
-
|
|
417
|
-
|
|
655
|
+
if (shouldRemoveSkillsForTool(tool.value, delivery) && !tool.isGlobalSkillTarget) {
|
|
656
|
+
removedSkillCount += await this.removeSkillDirs(tool.skillsRoot, tool.skillsPath);
|
|
657
|
+
// Retain an explicit selection even when this delivery mode produces
|
|
658
|
+
// no skills, so a divergent legacy sibling cannot reclaim ownership.
|
|
659
|
+
writeSharedSkillTarget(projectPath, tool.value);
|
|
418
660
|
}
|
|
419
661
|
// Generate commands if delivery includes commands
|
|
420
662
|
if (shouldGenerateCommands) {
|
|
@@ -422,17 +664,25 @@ export class InitCommand {
|
|
|
422
664
|
if (adapter) {
|
|
423
665
|
const generatedCommands = generateCommands(commandContents, adapter);
|
|
424
666
|
for (const cmd of generatedCommands) {
|
|
425
|
-
const commandFile =
|
|
667
|
+
const commandFile = FileSystemUtils.resolveProjectArtifactPath(projectPath, cmd.path);
|
|
426
668
|
await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
|
|
427
669
|
}
|
|
428
670
|
}
|
|
671
|
+
}
|
|
672
|
+
else if (deliveryIncludesCommands) {
|
|
673
|
+
if (resolveCommandSurfaceCapability(tool.value) === 'skills-invocable') {
|
|
674
|
+
skillsInvocableCommandSkips.push(tool.value);
|
|
675
|
+
}
|
|
429
676
|
else {
|
|
430
677
|
commandsSkipped.push(tool.value);
|
|
431
678
|
}
|
|
432
679
|
}
|
|
433
|
-
if (
|
|
680
|
+
if (shouldReconcileCommandFilesForTool(tool.value, delivery)) {
|
|
434
681
|
removedCommandCount += await this.removeCommandFiles(projectPath, tool.value);
|
|
435
682
|
}
|
|
683
|
+
if (tool.value === 'github-copilot' && writeCopilotCloud) {
|
|
684
|
+
await writeCopilotCloudFiles(projectPath);
|
|
685
|
+
}
|
|
436
686
|
spinner.succeed(`Setup complete for ${tool.name}`);
|
|
437
687
|
if (tool.wasConfigured) {
|
|
438
688
|
refreshedTools.push(tool);
|
|
@@ -446,11 +696,22 @@ export class InitCommand {
|
|
|
446
696
|
failedTools.push({ name: tool.name, error: error });
|
|
447
697
|
}
|
|
448
698
|
}
|
|
699
|
+
for (const tool of [...createdTools, ...refreshedTools]) {
|
|
700
|
+
for (const migration of migrateLegacyToolDirs(projectPath, [tool.value], 'after-generation')) {
|
|
701
|
+
if (hasMovableContent(migration)) {
|
|
702
|
+
console.log(chalk.dim(`Migrated ${describeLegacyMigration(migration)}: ${migration.from} → ${migration.to}`));
|
|
703
|
+
}
|
|
704
|
+
const kept = keptInPlaceNotice(migration);
|
|
705
|
+
if (kept)
|
|
706
|
+
console.log(chalk.dim(kept));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
449
709
|
return {
|
|
450
710
|
createdTools,
|
|
451
711
|
refreshedTools,
|
|
452
712
|
failedTools,
|
|
453
713
|
commandsSkipped,
|
|
714
|
+
skillsInvocableCommandSkips,
|
|
454
715
|
removedCommandCount,
|
|
455
716
|
removedSkillCount,
|
|
456
717
|
};
|
|
@@ -468,6 +729,7 @@ export class InitCommand {
|
|
|
468
729
|
}
|
|
469
730
|
try {
|
|
470
731
|
const yamlContent = serializeConfig({ schema: DEFAULT_SCHEMA });
|
|
732
|
+
FileSystemUtils.assertProjectArtifactPath(path.dirname(openspecPath), configPath);
|
|
471
733
|
await FileSystemUtils.writeFile(configPath, yamlContent);
|
|
472
734
|
return 'created';
|
|
473
735
|
}
|
|
@@ -478,9 +740,9 @@ export class InitCommand {
|
|
|
478
740
|
// ═══════════════════════════════════════════════════════════
|
|
479
741
|
// UI & OUTPUT
|
|
480
742
|
// ═══════════════════════════════════════════════════════════
|
|
481
|
-
displaySuccessMessage(projectPath, tools, results, configStatus) {
|
|
743
|
+
displaySuccessMessage(projectPath, tools, results, configStatus, copilot) {
|
|
482
744
|
console.log();
|
|
483
|
-
console.log(chalk.bold('OpenSpec Setup Complete'));
|
|
745
|
+
console.log(chalk.bold(results.failedTools.length > 0 ? 'OpenSpec Setup Incomplete' : 'OpenSpec Setup Complete'));
|
|
484
746
|
console.log();
|
|
485
747
|
// Show created vs refreshed tools
|
|
486
748
|
if (results.createdTools.length > 0) {
|
|
@@ -496,17 +758,56 @@ export class InitCommand {
|
|
|
496
758
|
const profile = this.profileOverride ?? globalConfig.profile ?? 'core';
|
|
497
759
|
const delivery = globalConfig.delivery ?? 'both';
|
|
498
760
|
const workflows = getProfileWorkflows(profile, globalConfig.workflows);
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
761
|
+
const usesGlobalSkillTarget = successfulTools.some((tool) => tool.isGlobalSkillTarget);
|
|
762
|
+
if (!usesGlobalSkillTarget) {
|
|
763
|
+
const toolDirs = [
|
|
764
|
+
...new Set(successfulTools
|
|
765
|
+
.map((tool) => tool.skillsDir)
|
|
766
|
+
.filter((skillsDir) => Boolean(skillsDir))),
|
|
767
|
+
].join(', ');
|
|
768
|
+
const skillCount = successfulTools.some((tool) => shouldGenerateSkillsForTool(tool.value, delivery))
|
|
769
|
+
? getSkillTemplates(workflows).length
|
|
770
|
+
: 0;
|
|
771
|
+
const commandCount = successfulTools.some((tool) => shouldGenerateCommandsForTool(tool.value, delivery))
|
|
772
|
+
? getCommandContents(workflows).length
|
|
773
|
+
: 0;
|
|
774
|
+
if (skillCount > 0 && commandCount > 0) {
|
|
775
|
+
console.log(`${skillCount} skills and ${commandCount} commands in ${toolDirs}/`);
|
|
776
|
+
}
|
|
777
|
+
else if (skillCount > 0) {
|
|
778
|
+
console.log(`${skillCount} skills in ${toolDirs}/`);
|
|
779
|
+
}
|
|
780
|
+
else if (commandCount > 0) {
|
|
781
|
+
console.log(`${commandCount} commands in ${toolDirs}/`);
|
|
782
|
+
}
|
|
507
783
|
}
|
|
508
|
-
else
|
|
509
|
-
|
|
784
|
+
else {
|
|
785
|
+
const skillTools = successfulTools.filter((tool) => shouldGenerateSkillsForTool(tool.value, delivery));
|
|
786
|
+
const skillCount = skillTools.length * getSkillTemplates(workflows).length;
|
|
787
|
+
if (skillCount > 0) {
|
|
788
|
+
const skillDirs = [...new Set(skillTools.map((tool) => tool.skillsPath))];
|
|
789
|
+
console.log(`${skillCount} skills in ${skillDirs.join(', ')}`);
|
|
790
|
+
}
|
|
791
|
+
const commandContents = getCommandContents(workflows);
|
|
792
|
+
const commandTools = successfulTools.filter((tool) => shouldGenerateCommandsForTool(tool.value, delivery));
|
|
793
|
+
const commandCount = commandTools.length * commandContents.length;
|
|
794
|
+
if (commandCount > 0) {
|
|
795
|
+
const commandDirs = [
|
|
796
|
+
...new Set(commandTools.flatMap((tool) => {
|
|
797
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
798
|
+
if (!adapter)
|
|
799
|
+
return [];
|
|
800
|
+
return commandContents.map((command) => {
|
|
801
|
+
const commandPath = adapter.getFilePath(command.id);
|
|
802
|
+
const absolutePath = path.isAbsolute(commandPath)
|
|
803
|
+
? commandPath
|
|
804
|
+
: path.join(projectPath, commandPath);
|
|
805
|
+
return path.dirname(absolutePath);
|
|
806
|
+
});
|
|
807
|
+
})),
|
|
808
|
+
];
|
|
809
|
+
console.log(`${commandCount} commands in ${commandDirs.join(', ')}`);
|
|
810
|
+
}
|
|
510
811
|
}
|
|
511
812
|
}
|
|
512
813
|
// Show failures
|
|
@@ -517,12 +818,42 @@ export class InitCommand {
|
|
|
517
818
|
if (results.commandsSkipped.length > 0) {
|
|
518
819
|
console.log(chalk.dim(`Commands skipped for: ${results.commandsSkipped.join(', ')} (no adapter)`));
|
|
519
820
|
}
|
|
821
|
+
if (results.skillsInvocableCommandSkips.length > 0) {
|
|
822
|
+
console.log(chalk.dim(`Commands skipped for: ${results.skillsInvocableCommandSkips.join(', ')} (uses skills)`));
|
|
823
|
+
}
|
|
520
824
|
if (results.removedCommandCount > 0) {
|
|
521
825
|
console.log(chalk.dim(`Removed: ${results.removedCommandCount} command files (delivery: skills)`));
|
|
522
826
|
}
|
|
523
827
|
if (results.removedSkillCount > 0) {
|
|
524
828
|
console.log(chalk.dim(`Removed: ${results.removedSkillCount} skill directories (delivery: commands)`));
|
|
525
829
|
}
|
|
830
|
+
// GitHub Copilot cloud files are opt-in — report what is actually on disk:
|
|
831
|
+
// list the managed files that now exist (never files we didn't write), flag
|
|
832
|
+
// any user-owned file we left untouched, note an opt-out cleanup, or (when
|
|
833
|
+
// skipped for want of a signal) say how to turn them on.
|
|
834
|
+
const copilotSucceeded = successfulTools.some((tool) => tool.value === 'github-copilot');
|
|
835
|
+
if (copilotSucceeded && copilot.write) {
|
|
836
|
+
if (copilot.present.length > 0) {
|
|
837
|
+
console.log(`GitHub Copilot cloud files: ${copilot.present.join(', ')}`);
|
|
838
|
+
}
|
|
839
|
+
if (copilot.collisions.length > 0) {
|
|
840
|
+
console.log(chalk.dim(`Left your existing ${copilot.collisions.join(' and ')} untouched — add the OpenSpec ` +
|
|
841
|
+
`install step by hand so the Copilot cloud agent can run openspec.`));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
else if (copilotSucceeded && copilot.removed > 0) {
|
|
845
|
+
console.log(chalk.dim(`Removed: ${copilot.removed} Copilot cloud agent file(s) (opted out of cloud files)`));
|
|
846
|
+
}
|
|
847
|
+
else if (copilotSucceeded && copilot.skippedUndecided) {
|
|
848
|
+
console.log(chalk.dim("Skipped GitHub Copilot cloud files (opt-in). Enable with 'openspec init --copilot-cloud'."));
|
|
849
|
+
}
|
|
850
|
+
// Show manual setup notes for tools that need extra configuration
|
|
851
|
+
for (const tool of successfulTools) {
|
|
852
|
+
const setupNote = AI_TOOLS.find((t) => t.value === tool.value)?.setupNote;
|
|
853
|
+
if (setupNote) {
|
|
854
|
+
console.log(chalk.yellow(`Setup required for ${tool.name}: ${setupNote}`));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
526
857
|
// Config status
|
|
527
858
|
if (configStatus === 'created') {
|
|
528
859
|
console.log(`Config: openspec/config.yaml (schema: ${DEFAULT_SCHEMA})`);
|
|
@@ -538,29 +869,98 @@ export class InitCommand {
|
|
|
538
869
|
console.log(chalk.dim(`Config: skipped (non-interactive mode)`));
|
|
539
870
|
}
|
|
540
871
|
// Getting started (task 7.6: show propose if in profile)
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
872
|
+
const activeWorkflows = this.getActiveWorkflows();
|
|
873
|
+
// When no tool got /opsx:* commands, point at the skill instead of a
|
|
874
|
+
// command that does not exist.
|
|
875
|
+
const activeDelivery = getGlobalConfig().delivery ?? 'both';
|
|
876
|
+
const commandsGenerated = successfulTools.some((tool) => shouldGenerateCommandsForTool(tool.value, activeDelivery));
|
|
877
|
+
const skillsGenerated = successfulTools.some((tool) => shouldGenerateSkillsForTool(tool.value, activeDelivery));
|
|
878
|
+
// Each hint line must be a usable instruction for the tool it serves.
|
|
879
|
+
// Tools that generated commands are told the command name their files
|
|
880
|
+
// answer to (/opsx:* when namespaced under opsx/, /opsx-* when the
|
|
881
|
+
// filename is the command); tools that only got skills are told their
|
|
882
|
+
// documented skill invocation (Kimi Code: /skill:openspec-*; Codex CLI:
|
|
883
|
+
// $openspec-*; others: /openspec-*). Tools that got no artifacts are
|
|
884
|
+
// covered by the configuration correction instead. When the selection
|
|
885
|
+
// disagrees, print one line per distinct instruction, labeled with the
|
|
886
|
+
// tools it applies to.
|
|
887
|
+
const startHintLines = (command) => {
|
|
888
|
+
const hintToTools = new Map();
|
|
889
|
+
for (const tool of successfulTools) {
|
|
890
|
+
let hint;
|
|
891
|
+
if (shouldGenerateCommandsForTool(tool.value, activeDelivery)) {
|
|
892
|
+
const transformer = getTransformerForTool(tool.value, activeDelivery, resolveCommandSurfaceCapability(tool.value), resolveCommandInvocation(tool.value));
|
|
893
|
+
hint = `Start your first change: ${transformer ? transformer(command) : command} "your idea"`;
|
|
894
|
+
}
|
|
895
|
+
else if (shouldGenerateSkillsForTool(tool.value, activeDelivery)) {
|
|
896
|
+
const skillReference = getSkillReferenceTransformer(tool.value)(command);
|
|
897
|
+
// Tools with no slash surface (e.g. Rovo Dev) reference skills as
|
|
898
|
+
// prose ("the openspec-propose skill"); phrase the hint so it reads
|
|
899
|
+
// as an instruction rather than a dead command with an argument.
|
|
900
|
+
hint = usesNaturalLanguageSkillReferences(tool.value)
|
|
901
|
+
? `Start your first change: ask ${tool.name} to use ${skillReference} with "your idea"`
|
|
902
|
+
: `Start your first change: ${skillReference} "your idea"`;
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
hintToTools.set(hint, [...(hintToTools.get(hint) ?? []), tool.name]);
|
|
908
|
+
}
|
|
909
|
+
if (hintToTools.size === 0) {
|
|
910
|
+
// No successful tools: keep the generic command hint
|
|
911
|
+
return [`Start your first change: ${command} "your idea"`];
|
|
912
|
+
}
|
|
913
|
+
if (hintToTools.size === 1) {
|
|
914
|
+
return [[...hintToTools.keys()][0]];
|
|
915
|
+
}
|
|
916
|
+
return [...hintToTools.entries()].map(([hint, toolNames]) => `${hint} (${toolNames.join(', ')})`);
|
|
917
|
+
};
|
|
918
|
+
const printStartHints = (command) => {
|
|
546
919
|
console.log(chalk.bold('Getting started:'));
|
|
547
|
-
|
|
920
|
+
for (const line of startHintLines(command)) {
|
|
921
|
+
console.log(` ${line}`);
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
console.log();
|
|
925
|
+
// delivery=commands with tools that only support skills: those tools get
|
|
926
|
+
// no artifacts at all, so print a per-tool configuration correction
|
|
927
|
+
// rather than leave them with a dead (or missing) instruction — even
|
|
928
|
+
// when other selected tools did get commands or skills.
|
|
929
|
+
const zeroArtifactTools = successfulTools.filter((tool) => !shouldGenerateSkillsForTool(tool.value, activeDelivery) &&
|
|
930
|
+
!shouldGenerateCommandsForTool(tool.value, activeDelivery));
|
|
931
|
+
if (zeroArtifactTools.length > 0) {
|
|
932
|
+
const names = zeroArtifactTools.map((tool) => tool.name).join(', ');
|
|
933
|
+
console.log(chalk.yellow(`No skills or commands were generated for ${names}: delivery is set to 'commands' but ` +
|
|
934
|
+
`${zeroArtifactTools.length === 1 ? 'it supports' : 'they support'} only skills. ` +
|
|
935
|
+
`Run 'openspec config set delivery both' to generate skills.`));
|
|
936
|
+
}
|
|
937
|
+
if (successfulTools.length > 0 && !commandsGenerated && !skillsGenerated) {
|
|
938
|
+
// Nothing was generated for any tool: the correction above is the
|
|
939
|
+
// whole story, so don't advertise an invocation that doesn't exist.
|
|
940
|
+
}
|
|
941
|
+
else if (activeWorkflows.includes('propose')) {
|
|
942
|
+
printStartHints('/opsx:propose');
|
|
548
943
|
}
|
|
549
944
|
else if (activeWorkflows.includes('new')) {
|
|
550
|
-
|
|
551
|
-
console.log(' Start your first change: /opsx:new "your idea"');
|
|
945
|
+
printStartHints('/opsx:new');
|
|
552
946
|
}
|
|
553
947
|
else {
|
|
554
948
|
console.log("Done. Run 'openspec config profile' to configure your workflows.");
|
|
555
949
|
}
|
|
556
950
|
// Links
|
|
557
951
|
console.log();
|
|
558
|
-
console.log(`Learn more: ${chalk.cyan('https://github.com/codewalla-engineering/
|
|
559
|
-
console.log(`Feedback: ${chalk.cyan('https://github.com/codewalla-engineering/
|
|
560
|
-
// Restart instruction if any tools were configured
|
|
561
|
-
|
|
952
|
+
console.log(`Learn more: ${chalk.cyan('https://github.com/codewalla-engineering/openspec-upstream-sync')}`);
|
|
953
|
+
console.log(`Feedback: ${chalk.cyan('https://github.com/codewalla-engineering/openspec-upstream-sync/issues')}`);
|
|
954
|
+
// Restart instruction if any tools were configured and got a surface
|
|
955
|
+
// (when nothing was generated there is nothing a restart would pick up);
|
|
956
|
+
// only mention commands when commands were actually generated. Not "slash
|
|
957
|
+
// commands": Amazon Q's generated files are prompt-library entries invoked
|
|
958
|
+
// with @, so a restart line promising slash commands would be wrong for it.
|
|
959
|
+
if ((results.createdTools.length > 0 || results.refreshedTools.length > 0) && (commandsGenerated || skillsGenerated)) {
|
|
562
960
|
console.log();
|
|
563
|
-
console.log(chalk.white(
|
|
961
|
+
console.log(chalk.white(commandsGenerated
|
|
962
|
+
? 'Restart your IDE for the new commands to take effect.'
|
|
963
|
+
: 'Restart your IDE for the new skills to take effect.'));
|
|
564
964
|
}
|
|
565
965
|
console.log();
|
|
566
966
|
}
|
|
@@ -572,18 +972,19 @@ export class InitCommand {
|
|
|
572
972
|
spinner: PROGRESS_SPINNER,
|
|
573
973
|
}).start();
|
|
574
974
|
}
|
|
575
|
-
async removeSkillDirs(skillsDir) {
|
|
975
|
+
async removeSkillDirs(skillsRoot, skillsDir) {
|
|
576
976
|
let removed = 0;
|
|
577
977
|
for (const workflow of ALL_WORKFLOWS) {
|
|
578
978
|
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
579
979
|
if (!dirName)
|
|
580
980
|
continue;
|
|
581
981
|
const skillDir = path.join(skillsDir, dirName);
|
|
982
|
+
if (!fs.existsSync(skillDir))
|
|
983
|
+
continue;
|
|
984
|
+
FileSystemUtils.assertPathWithin(skillsRoot, skillDir);
|
|
582
985
|
try {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
removed++;
|
|
586
|
-
}
|
|
986
|
+
await fs.promises.rm(skillDir, { recursive: true, force: true });
|
|
987
|
+
removed++;
|
|
587
988
|
}
|
|
588
989
|
catch {
|
|
589
990
|
// Ignore errors
|
|
@@ -598,7 +999,7 @@ export class InitCommand {
|
|
|
598
999
|
return 0;
|
|
599
1000
|
for (const workflow of ALL_WORKFLOWS) {
|
|
600
1001
|
const cmdPath = adapter.getFilePath(workflow);
|
|
601
|
-
const fullPath =
|
|
1002
|
+
const fullPath = FileSystemUtils.resolveProjectArtifactPath(projectPath, cmdPath);
|
|
602
1003
|
try {
|
|
603
1004
|
if (fs.existsSync(fullPath)) {
|
|
604
1005
|
await fs.promises.unlink(fullPath);
|