gmc-openspec 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +207 -0
- package/bin/openspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +494 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +277 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +264 -0
- package/dist/commands/config.d.ts +36 -0
- package/dist/commands/config.js +611 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +183 -0
- package/dist/commands/jira.d.ts +3 -0
- package/dist/commands/jira.js +249 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -0
- package/dist/commands/show.d.ts +14 -0
- package/dist/commands/show.js +132 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +225 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +294 -0
- package/dist/commands/workflow/index.d.ts +17 -0
- package/dist/commands/workflow/index.js +12 -0
- package/dist/commands/workflow/instructions.d.ts +29 -0
- package/dist/commands/workflow/instructions.js +336 -0
- package/dist/commands/workflow/new-change.d.ts +13 -0
- package/dist/commands/workflow/new-change.js +92 -0
- package/dist/commands/workflow/schemas.d.ts +10 -0
- package/dist/commands/workflow/schemas.js +34 -0
- package/dist/commands/workflow/shared.d.ts +57 -0
- package/dist/commands/workflow/shared.js +116 -0
- package/dist/commands/workflow/status.d.ts +14 -0
- package/dist/commands/workflow/status.js +87 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +69 -0
- package/dist/commands/workspace/open.d.ts +29 -0
- package/dist/commands/workspace/open.js +84 -0
- package/dist/commands/workspace/operations.d.ts +23 -0
- package/dist/commands/workspace/operations.js +475 -0
- package/dist/commands/workspace/selection.d.ts +6 -0
- package/dist/commands/workspace/selection.js +113 -0
- package/dist/commands/workspace/types.d.ts +88 -0
- package/dist/commands/workspace/types.js +36 -0
- package/dist/commands/workspace.d.ts +6 -0
- package/dist/commands/workspace.js +868 -0
- package/dist/core/archive.d.ts +11 -0
- package/dist/core/archive.js +318 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +8 -0
- package/dist/core/artifact-graph/index.js +14 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +196 -0
- package/dist/core/artifact-graph/instruction-loader.js +317 -0
- package/dist/core/artifact-graph/outputs.d.ts +14 -0
- package/dist/core/artifact-graph/outputs.js +39 -0
- package/dist/core/artifact-graph/resolver.d.ts +81 -0
- package/dist/core/artifact-graph/resolver.js +257 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +31 -0
- package/dist/core/artifact-graph/types.d.ts +47 -0
- package/dist/core/artifact-graph/types.js +48 -0
- package/dist/core/available-tools.d.ts +17 -0
- package/dist/core/available-tools.js +43 -0
- package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
- package/dist/core/command-generation/adapters/amazon-q.js +26 -0
- package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
- package/dist/core/command-generation/adapters/antigravity.js +26 -0
- package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
- package/dist/core/command-generation/adapters/auggie.js +27 -0
- package/dist/core/command-generation/adapters/bob.d.ts +14 -0
- package/dist/core/command-generation/adapters/bob.js +45 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +50 -0
- package/dist/core/command-generation/adapters/cline.d.ts +14 -0
- package/dist/core/command-generation/adapters/cline.js +27 -0
- package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
- package/dist/core/command-generation/adapters/codebuddy.js +28 -0
- package/dist/core/command-generation/adapters/codex.d.ts +16 -0
- package/dist/core/command-generation/adapters/codex.js +39 -0
- package/dist/core/command-generation/adapters/continue.d.ts +13 -0
- package/dist/core/command-generation/adapters/continue.js +28 -0
- package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
- package/dist/core/command-generation/adapters/costrict.js +27 -0
- package/dist/core/command-generation/adapters/crush.d.ts +13 -0
- package/dist/core/command-generation/adapters/crush.js +30 -0
- package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
- package/dist/core/command-generation/adapters/cursor.js +44 -0
- package/dist/core/command-generation/adapters/factory.d.ts +13 -0
- package/dist/core/command-generation/adapters/factory.js +27 -0
- package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
- package/dist/core/command-generation/adapters/gemini.js +26 -0
- package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
- package/dist/core/command-generation/adapters/github-copilot.js +26 -0
- package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
- package/dist/core/command-generation/adapters/iflow.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +32 -0
- package/dist/core/command-generation/adapters/index.js +32 -0
- package/dist/core/command-generation/adapters/junie.d.ts +13 -0
- package/dist/core/command-generation/adapters/junie.js +26 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/kilocode.js +23 -0
- package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
- package/dist/core/command-generation/adapters/kiro.js +26 -0
- package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
- package/dist/core/command-generation/adapters/lingma.js +30 -0
- package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
- package/dist/core/command-generation/adapters/opencode.js +29 -0
- package/dist/core/command-generation/adapters/pi.d.ts +18 -0
- package/dist/core/command-generation/adapters/pi.js +55 -0
- package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
- package/dist/core/command-generation/adapters/qoder.js +30 -0
- package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
- package/dist/core/command-generation/adapters/qwen.js +26 -0
- package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
- package/dist/core/command-generation/adapters/roocode.js +27 -0
- package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
- package/dist/core/command-generation/adapters/windsurf.js +51 -0
- package/dist/core/command-generation/generator.d.ts +21 -0
- package/dist/core/command-generation/generator.js +27 -0
- package/dist/core/command-generation/index.d.ts +22 -0
- package/dist/core/command-generation/index.js +24 -0
- package/dist/core/command-generation/registry.d.ts +36 -0
- package/dist/core/command-generation/registry.js +98 -0
- package/dist/core/command-generation/types.d.ts +56 -0
- package/dist/core/command-generation/types.js +8 -0
- package/dist/core/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +626 -0
- package/dist/core/completions/completion-provider.d.ts +71 -0
- package/dist/core/completions/completion-provider.js +129 -0
- package/dist/core/completions/factory.d.ts +64 -0
- package/dist/core/completions/factory.js +75 -0
- package/dist/core/completions/generators/bash-generator.d.ts +35 -0
- package/dist/core/completions/generators/bash-generator.js +230 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +160 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
- package/dist/core/completions/generators/powershell-generator.js +266 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
- package/dist/core/completions/generators/zsh-generator.js +274 -0
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +318 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +143 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
- package/dist/core/completions/installers/powershell-installer.js +387 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
- package/dist/core/completions/installers/zsh-installer.js +421 -0
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +30 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +45 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +34 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +45 -0
- package/dist/core/completions/types.d.ts +101 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +34 -0
- package/dist/core/config-schema.d.ts +86 -0
- package/dist/core/config-schema.js +213 -0
- package/dist/core/config.d.ts +18 -0
- package/dist/core/config.js +38 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +49 -0
- package/dist/core/global-config.js +124 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +6 -0
- package/dist/core/init.d.ts +37 -0
- package/dist/core/init.js +593 -0
- package/dist/core/jira/config.d.ts +35 -0
- package/dist/core/jira/config.js +151 -0
- package/dist/core/jira/constants.d.ts +20 -0
- package/dist/core/jira/constants.js +49 -0
- package/dist/core/jira/doctor.d.ts +19 -0
- package/dist/core/jira/doctor.js +173 -0
- package/dist/core/jira/hash.d.ts +3 -0
- package/dist/core/jira/hash.js +9 -0
- package/dist/core/jira/index.d.ts +11 -0
- package/dist/core/jira/index.js +11 -0
- package/dist/core/jira/intake.d.ts +40 -0
- package/dist/core/jira/intake.js +54 -0
- package/dist/core/jira/mcp-config.d.ts +13 -0
- package/dist/core/jira/mcp-config.js +259 -0
- package/dist/core/jira/paths.d.ts +12 -0
- package/dist/core/jira/paths.js +66 -0
- package/dist/core/jira/setup.d.ts +30 -0
- package/dist/core/jira/setup.js +99 -0
- package/dist/core/jira/templates.d.ts +12 -0
- package/dist/core/jira/templates.js +204 -0
- package/dist/core/jira/validation.d.ts +17 -0
- package/dist/core/jira/validation.js +341 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +514 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -0
- package/dist/core/migration.d.ts +23 -0
- package/dist/core/migration.js +108 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +197 -0
- package/dist/core/parsers/markdown-parser.d.ts +26 -0
- package/dist/core/parsers/markdown-parser.js +227 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -0
- package/dist/core/parsers/spec-structure.d.ts +9 -0
- package/dist/core/parsers/spec-structure.js +88 -0
- package/dist/core/planning-home.d.ts +21 -0
- package/dist/core/planning-home.js +124 -0
- package/dist/core/profile-sync-drift.d.ts +38 -0
- package/dist/core/profile-sync-drift.js +200 -0
- package/dist/core/profiles.d.ts +26 -0
- package/dist/core/profiles.js +40 -0
- package/dist/core/project-config.d.ts +64 -0
- package/dist/core/project-config.js +223 -0
- package/dist/core/schemas/base.schema.d.ts +13 -0
- package/dist/core/schemas/base.schema.js +13 -0
- package/dist/core/schemas/change.schema.d.ts +73 -0
- package/dist/core/schemas/change.schema.js +31 -0
- package/dist/core/schemas/index.d.ts +4 -0
- package/dist/core/schemas/index.js +4 -0
- package/dist/core/schemas/spec.schema.d.ts +18 -0
- package/dist/core/schemas/spec.schema.js +15 -0
- package/dist/core/shared/index.d.ts +8 -0
- package/dist/core/shared/index.js +8 -0
- package/dist/core/shared/skill-generation.d.ts +49 -0
- package/dist/core/shared/skill-generation.js +96 -0
- package/dist/core/shared/tool-detection.d.ts +71 -0
- package/dist/core/shared/tool-detection.js +158 -0
- package/dist/core/specs-apply.d.ts +73 -0
- package/dist/core/specs-apply.js +392 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/index.d.ts +8 -0
- package/dist/core/templates/index.js +9 -0
- package/dist/core/templates/skill-templates.d.ts +19 -0
- package/dist/core/templates/skill-templates.js +18 -0
- package/dist/core/templates/types.d.ts +19 -0
- package/dist/core/templates/types.js +5 -0
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +314 -0
- package/dist/core/templates/workflows/archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/archive-change.js +277 -0
- package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
- package/dist/core/templates/workflows/continue-change.d.ts +10 -0
- package/dist/core/templates/workflows/continue-change.js +234 -0
- package/dist/core/templates/workflows/explore.d.ts +10 -0
- package/dist/core/templates/workflows/explore.js +459 -0
- package/dist/core/templates/workflows/feedback.d.ts +9 -0
- package/dist/core/templates/workflows/feedback.js +108 -0
- package/dist/core/templates/workflows/ff-change.d.ts +10 -0
- package/dist/core/templates/workflows/ff-change.js +200 -0
- package/dist/core/templates/workflows/new-change.d.ts +10 -0
- package/dist/core/templates/workflows/new-change.js +143 -0
- package/dist/core/templates/workflows/onboard.d.ts +10 -0
- package/dist/core/templates/workflows/onboard.js +563 -0
- package/dist/core/templates/workflows/propose.d.ts +10 -0
- package/dist/core/templates/workflows/propose.js +218 -0
- package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
- package/dist/core/templates/workflows/sync-specs.js +290 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +338 -0
- package/dist/core/update.d.ts +83 -0
- package/dist/core/update.js +573 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +33 -0
- package/dist/core/validation/validator.js +418 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/core/workspace/foundation.d.ts +87 -0
- package/dist/core/workspace/foundation.js +379 -0
- package/dist/core/workspace/index.d.ts +6 -0
- package/dist/core/workspace/index.js +6 -0
- package/dist/core/workspace/link-input.d.ts +9 -0
- package/dist/core/workspace/link-input.js +32 -0
- package/dist/core/workspace/open-surface.d.ts +24 -0
- package/dist/core/workspace/open-surface.js +137 -0
- package/dist/core/workspace/openers.d.ts +21 -0
- package/dist/core/workspace/openers.js +119 -0
- package/dist/core/workspace/skills.d.ts +55 -0
- package/dist/core/workspace/skills.js +334 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +28 -0
- package/dist/prompts/searchable-multi-select.js +159 -0
- package/dist/telemetry/config.d.ts +38 -0
- package/dist/telemetry/config.js +136 -0
- package/dist/telemetry/index.d.ts +31 -0
- package/dist/telemetry/index.js +164 -0
- package/dist/ui/ascii-patterns.d.ts +16 -0
- package/dist/ui/ascii-patterns.js +133 -0
- package/dist/ui/welcome-screen.d.ts +10 -0
- package/dist/ui/welcome-screen.js +146 -0
- package/dist/utils/change-metadata.d.ts +51 -0
- package/dist/utils/change-metadata.js +147 -0
- package/dist/utils/change-utils.d.ts +71 -0
- package/dist/utils/change-utils.js +123 -0
- package/dist/utils/command-references.d.ts +18 -0
- package/dist/utils/command-references.js +20 -0
- package/dist/utils/file-system.d.ts +41 -0
- package/dist/utils/file-system.js +301 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/interactive.d.ts +18 -0
- package/dist/utils/interactive.js +21 -0
- package/dist/utils/item-discovery.d.ts +4 -0
- package/dist/utils/item-discovery.js +72 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/dist/utils/task-progress.d.ts +8 -0
- package/dist/utils/task-progress.js +36 -0
- package/package.json +79 -0
- package/schemas/spec-driven/schema.yaml +153 -0
- package/schemas/spec-driven/templates/design.md +19 -0
- package/schemas/spec-driven/templates/proposal.md +23 -0
- package/schemas/spec-driven/templates/spec.md +8 -0
- package/schemas/spec-driven/templates/tasks.md +9 -0
- package/schemas/workspace-planning/schema.yaml +72 -0
- package/schemas/workspace-planning/templates/design.md +33 -0
- package/schemas/workspace-planning/templates/proposal.md +28 -0
- package/schemas/workspace-planning/templates/spec.md +9 -0
- package/schemas/workspace-planning/templates/tasks.md +15 -0
- package/scripts/postinstall.js +83 -0
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as nodeFs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { createWorkspaceSkillSkippedReport, generateWorkspaceAgentSkills, getDefaultWorkspaceOpenerChoiceValue, getWorkspaceSkillCapableTools, getWorkspaceSkillToolIds, getWorkspaceOpenerLabel, isWorkspaceAgentOpenerId, listWorkspaceOpenerChoices, parseWorkspacePreferredOpenerValue, parseWorkspaceSkillToolsValue, updateWorkspaceAgentSkills, listWorkspaceRegistryEntries, readOptionalWorkspaceLocalState, writeWorkspaceLocalState, } from '../core/workspace/index.js';
|
|
5
|
+
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
|
6
|
+
import { addWorkspaceLink, createManagedWorkspace, inferLinkName, loadWorkspaceForDoctor, loadWorkspaceForList, parseSetupLinks, readWorkspaceForMutation, readRegistry, recordSelectedWorkspaceAfterMutation, resolveExistingDirectory, updateWorkspaceLink, validateLinkNameForCommand, validateWorkspaceNameForSetup, } from './workspace/operations.js';
|
|
7
|
+
import { selectWorkspaceForCommand, selectWorkspaceRootForCommand, } from './workspace/selection.js';
|
|
8
|
+
import { assertWorkspaceOpenerAvailable, buildWorkspaceOpenCommandForState, launchWorkspaceOpenCommand, readWorkspaceOpenState, } from './workspace/open.js';
|
|
9
|
+
import { WorkspaceCliError, appendStatus, asErrorMessage, asStatus, } from './workspace/types.js';
|
|
10
|
+
function printJson(payload) {
|
|
11
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
12
|
+
}
|
|
13
|
+
const workspacePromptTheme = {
|
|
14
|
+
prefix: '',
|
|
15
|
+
style: {
|
|
16
|
+
answer: (text) => chalk.cyan(text),
|
|
17
|
+
defaultAnswer: (text) => chalk.dim(text),
|
|
18
|
+
error: (text) => chalk.red(text),
|
|
19
|
+
help: (text) => chalk.dim(text),
|
|
20
|
+
highlight: (text) => chalk.cyan(text),
|
|
21
|
+
key: (text) => chalk.cyan(text),
|
|
22
|
+
message: (text) => chalk.bold(text),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const workspaceSelectTheme = {
|
|
26
|
+
...workspacePromptTheme,
|
|
27
|
+
icon: {
|
|
28
|
+
cursor: chalk.cyan('>'),
|
|
29
|
+
},
|
|
30
|
+
style: {
|
|
31
|
+
...workspacePromptTheme.style,
|
|
32
|
+
keysHelpTip: (keys) => chalk.dim(keys.map(([key, action]) => `${key}: ${action}`).join(' | ')),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
function printWorkspaceSetupIntro() {
|
|
36
|
+
console.log(chalk.bold('Workspace setup'));
|
|
37
|
+
console.log('');
|
|
38
|
+
}
|
|
39
|
+
function isPromptCancellationError(error) {
|
|
40
|
+
return (error instanceof Error &&
|
|
41
|
+
(error.name === 'ExitPromptError' || error.message.includes('force closed the prompt with SIGINT')));
|
|
42
|
+
}
|
|
43
|
+
async function promptWorkspaceName(initialName) {
|
|
44
|
+
if (initialName) {
|
|
45
|
+
return validateWorkspaceNameForSetup(initialName);
|
|
46
|
+
}
|
|
47
|
+
const { input } = await import('@inquirer/prompts');
|
|
48
|
+
console.log(chalk.bold('[1/5] Name the workspace'));
|
|
49
|
+
console.log(chalk.dim('Use a stable name for the repo group, e.g. platform.'));
|
|
50
|
+
console.log('');
|
|
51
|
+
return input({
|
|
52
|
+
message: 'Workspace name:',
|
|
53
|
+
required: true,
|
|
54
|
+
theme: workspacePromptTheme,
|
|
55
|
+
validate(value) {
|
|
56
|
+
try {
|
|
57
|
+
validateWorkspaceNameForSetup(value);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return 'Workspace names must be kebab-case with lowercase letters, numbers, and single hyphen separators.';
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function promptExistingPath(message, defaultPath) {
|
|
67
|
+
const { input } = await import('@inquirer/prompts');
|
|
68
|
+
const pathInput = await input({
|
|
69
|
+
message,
|
|
70
|
+
default: defaultPath,
|
|
71
|
+
prefill: defaultPath ? 'editable' : undefined,
|
|
72
|
+
required: true,
|
|
73
|
+
theme: workspacePromptTheme,
|
|
74
|
+
validate(value) {
|
|
75
|
+
const resolvedPath = path.isAbsolute(value)
|
|
76
|
+
? path.resolve(value)
|
|
77
|
+
: path.resolve(process.cwd(), value);
|
|
78
|
+
return nodeFs.existsSync(resolvedPath) && nodeFs.statSync(resolvedPath).isDirectory()
|
|
79
|
+
? true
|
|
80
|
+
: 'Enter an existing repo or folder path.';
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
return resolveExistingDirectory(pathInput);
|
|
84
|
+
}
|
|
85
|
+
async function promptLinkName(existingLinks) {
|
|
86
|
+
const { input } = await import('@inquirer/prompts');
|
|
87
|
+
return input({
|
|
88
|
+
message: 'Link name:',
|
|
89
|
+
required: true,
|
|
90
|
+
theme: workspacePromptTheme,
|
|
91
|
+
validate(value) {
|
|
92
|
+
try {
|
|
93
|
+
validateLinkNameForCommand(value);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return asErrorMessage(error);
|
|
97
|
+
}
|
|
98
|
+
if (existingLinks[value]) {
|
|
99
|
+
return `Link name '${value}' is already linked to ${existingLinks[value]}.`;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async function promptSetupLinks() {
|
|
106
|
+
const { select } = await import('@inquirer/prompts');
|
|
107
|
+
const links = {};
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.bold('[2/5] Link repos or folders'));
|
|
110
|
+
console.log(chalk.dim('Start with the current directory, or enter another repo path.'));
|
|
111
|
+
console.log('');
|
|
112
|
+
while (true) {
|
|
113
|
+
const linkCount = Object.keys(links).length;
|
|
114
|
+
const resolvedPath = await promptExistingPath(linkCount === 0 ? 'Repo or folder path:' : 'Another repo or folder path:', linkCount === 0 ? '.' : undefined);
|
|
115
|
+
let linkName = inferLinkName(resolvedPath);
|
|
116
|
+
try {
|
|
117
|
+
validateLinkNameForCommand(linkName);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
linkName = await promptLinkName(links);
|
|
121
|
+
}
|
|
122
|
+
if (links[linkName]) {
|
|
123
|
+
console.log(`Link name '${linkName}' is already linked to ${links[linkName]}.`);
|
|
124
|
+
linkName = await promptLinkName(links);
|
|
125
|
+
}
|
|
126
|
+
links[linkName] = resolvedPath;
|
|
127
|
+
console.log(chalk.green(`Added link '${linkName}'`));
|
|
128
|
+
console.log(chalk.dim(` ${resolvedPath}`));
|
|
129
|
+
const nextAction = await select({
|
|
130
|
+
message: 'Continue',
|
|
131
|
+
default: 'finish',
|
|
132
|
+
choices: [
|
|
133
|
+
{
|
|
134
|
+
name: 'Create workspace files',
|
|
135
|
+
short: 'Create workspace files',
|
|
136
|
+
value: 'finish',
|
|
137
|
+
description: 'Run a workspace check after setup',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'Add another repo or folder',
|
|
141
|
+
short: 'Add another',
|
|
142
|
+
value: 'add',
|
|
143
|
+
description: 'Include another local directory in this workspace',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
theme: workspaceSelectTheme,
|
|
147
|
+
});
|
|
148
|
+
if (nextAction === 'finish') {
|
|
149
|
+
return links;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function formatOpenerChoiceName(choice) {
|
|
154
|
+
return choice.unavailableNote ? `${choice.label} (${choice.unavailableNote})` : choice.label;
|
|
155
|
+
}
|
|
156
|
+
async function promptPreferredOpener(message, openerChoices = listWorkspaceOpenerChoices()) {
|
|
157
|
+
const { select } = await import('@inquirer/prompts');
|
|
158
|
+
const selectedValue = await select({
|
|
159
|
+
message,
|
|
160
|
+
default: getDefaultWorkspaceOpenerChoiceValue(openerChoices),
|
|
161
|
+
choices: openerChoices.map((choice) => ({
|
|
162
|
+
name: formatOpenerChoiceName(choice),
|
|
163
|
+
short: choice.label,
|
|
164
|
+
value: choice.value,
|
|
165
|
+
description: choice.unavailableNote ?? `Use ${choice.label}`,
|
|
166
|
+
})),
|
|
167
|
+
theme: workspaceSelectTheme,
|
|
168
|
+
});
|
|
169
|
+
return parseWorkspacePreferredOpenerValue(selectedValue);
|
|
170
|
+
}
|
|
171
|
+
function parseSetupOpenerOption(opener) {
|
|
172
|
+
if (!opener) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
return parseWorkspacePreferredOpenerValue(opener);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new WorkspaceCliError(asErrorMessage(error), 'unsupported_workspace_opener', {
|
|
180
|
+
target: 'workspace.opener',
|
|
181
|
+
fix: 'Use --opener codex, --opener claude, --opener github-copilot, or --opener editor.',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function parseSetupToolsOption(tools) {
|
|
186
|
+
try {
|
|
187
|
+
return parseWorkspaceSkillToolsValue(tools);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
throw new WorkspaceCliError(asErrorMessage(error), 'invalid_workspace_setup_tools', {
|
|
191
|
+
target: 'workspace.skills',
|
|
192
|
+
fix: `Use --tools all, --tools none, or one of: ${getWorkspaceSkillToolIds().join(', ')}`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function parseUpdateToolsOption(tools) {
|
|
197
|
+
try {
|
|
198
|
+
return parseWorkspaceSkillToolsValue(tools);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
throw new WorkspaceCliError(asErrorMessage(error), 'invalid_workspace_update_tools', {
|
|
202
|
+
target: 'workspace.skills',
|
|
203
|
+
fix: `Use --tools all, --tools none, or one of: ${getWorkspaceSkillToolIds().join(', ')}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function getPreferredWorkspaceSkillAgentId(preferredOpener) {
|
|
208
|
+
if (!preferredOpener || preferredOpener.kind !== 'agent') {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
return getWorkspaceSkillToolIds().includes(preferredOpener.id) ? preferredOpener.id : null;
|
|
212
|
+
}
|
|
213
|
+
async function promptWorkspaceSkillAgents(preferredOpener) {
|
|
214
|
+
const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
|
|
215
|
+
const preferredAgentId = getPreferredWorkspaceSkillAgentId(preferredOpener);
|
|
216
|
+
const tools = getWorkspaceSkillCapableTools();
|
|
217
|
+
const sortedChoices = tools
|
|
218
|
+
.map((tool) => ({
|
|
219
|
+
name: tool.name,
|
|
220
|
+
value: tool.value,
|
|
221
|
+
preSelected: tool.value === preferredAgentId,
|
|
222
|
+
}))
|
|
223
|
+
.sort((a, b) => {
|
|
224
|
+
if (a.preSelected !== b.preSelected) {
|
|
225
|
+
return a.preSelected ? -1 : 1;
|
|
226
|
+
}
|
|
227
|
+
return a.name.localeCompare(b.name);
|
|
228
|
+
});
|
|
229
|
+
if (preferredAgentId) {
|
|
230
|
+
const preferredTool = tools.find((tool) => tool.value === preferredAgentId);
|
|
231
|
+
if (preferredTool) {
|
|
232
|
+
console.log(`${preferredTool.name} matches your preferred opener and is pre-selected.`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return searchableMultiSelect({
|
|
236
|
+
message: 'Which agents should get OpenSpec skills in this workspace?',
|
|
237
|
+
pageSize: 15,
|
|
238
|
+
choices: sortedChoices,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function parseAgentOverride(agent) {
|
|
242
|
+
if (!isWorkspaceAgentOpenerId(agent)) {
|
|
243
|
+
throw new WorkspaceCliError(`Unsupported workspace agent '${agent}'. Supported agents: codex, claude, github-copilot.`, 'unsupported_workspace_agent', {
|
|
244
|
+
target: 'workspace.opener',
|
|
245
|
+
fix: 'Use --agent codex, --agent claude, or --agent github-copilot.',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
kind: 'agent',
|
|
250
|
+
id: agent,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function printStatusLines(statuses) {
|
|
254
|
+
for (const status of statuses) {
|
|
255
|
+
const label = status.severity === 'warning' ? 'Warning' : 'Issue';
|
|
256
|
+
console.log(`${label}: ${status.message}`);
|
|
257
|
+
if (status.fix) {
|
|
258
|
+
console.log(`Fix: ${status.fix}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function printLinksHuman(links) {
|
|
263
|
+
if (links.length === 0) {
|
|
264
|
+
console.log(' (no linked repos or folders)');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
for (const link of links) {
|
|
268
|
+
const suffix = link.status.some((status) => status.severity === 'error') ? ' [issue]' : '';
|
|
269
|
+
console.log(` ${link.name} -> ${link.path ?? '(no local path recorded)'}${suffix}`);
|
|
270
|
+
if (link.repo_specs_path) {
|
|
271
|
+
console.log(` repo specs: ${link.repo_specs_path}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function collectWorkspaceIssues(workspace) {
|
|
276
|
+
return [
|
|
277
|
+
...workspace.status,
|
|
278
|
+
...workspace.links.flatMap((link) => link.status),
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
function printDoctorHuman(result) {
|
|
282
|
+
console.log(`Workspace: ${result.workspace.name}`);
|
|
283
|
+
console.log(`Location: ${result.workspace.root}`);
|
|
284
|
+
console.log(`Planning path: ${result.workspace.planning_path}`);
|
|
285
|
+
console.log('');
|
|
286
|
+
printStatusLines(result.status);
|
|
287
|
+
if (result.status.length > 0) {
|
|
288
|
+
console.log('');
|
|
289
|
+
}
|
|
290
|
+
console.log('Linked repos or folders:');
|
|
291
|
+
printLinksHuman(result.workspace.links);
|
|
292
|
+
const issues = collectWorkspaceIssues(result.workspace);
|
|
293
|
+
if (issues.length === 0) {
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log('No workspace issues found.');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
console.log('');
|
|
299
|
+
console.log('Issues:');
|
|
300
|
+
for (const issue of issues) {
|
|
301
|
+
console.log(` - ${issue.message}`);
|
|
302
|
+
if (issue.target) {
|
|
303
|
+
console.log(` Target: ${issue.target}`);
|
|
304
|
+
}
|
|
305
|
+
if (issue.fix) {
|
|
306
|
+
console.log(` Fix: ${issue.fix}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function printWorkspaceListHuman(workspaces) {
|
|
311
|
+
console.log(chalk.bold(`OpenSpec workspaces (${workspaces.length})`));
|
|
312
|
+
for (const workspace of workspaces) {
|
|
313
|
+
console.log('');
|
|
314
|
+
console.log(chalk.bold(workspace.name));
|
|
315
|
+
console.log(` Location: ${workspace.root}`);
|
|
316
|
+
if (workspace.status.length > 0) {
|
|
317
|
+
console.log(' Status:');
|
|
318
|
+
for (const status of workspace.status) {
|
|
319
|
+
const statusLabel = status.severity === 'warning' ? chalk.yellow('Warning') : chalk.red('Issue');
|
|
320
|
+
console.log(` ${statusLabel}: ${status.message}`);
|
|
321
|
+
if (status.fix) {
|
|
322
|
+
console.log(` Fix: ${status.fix}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
console.log(` Linked repos or folders (${workspace.links.length}):`);
|
|
327
|
+
if (workspace.links.length === 0) {
|
|
328
|
+
console.log(chalk.dim(' (none)'));
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
for (const link of workspace.links) {
|
|
332
|
+
const suffix = link.status.some((status) => status.severity === 'error') ? chalk.red(' [issue]') : '';
|
|
333
|
+
console.log(` ${link.name} -> ${link.path ?? '(no local path recorded)'}${suffix}`);
|
|
334
|
+
if (link.repo_specs_path) {
|
|
335
|
+
console.log(chalk.dim(` repo specs: ${link.repo_specs_path}`));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function printWorkspaceCheckSummaryHuman(result) {
|
|
341
|
+
printStatusLines(result.status);
|
|
342
|
+
const issues = collectWorkspaceIssues(result.workspace);
|
|
343
|
+
if (issues.length === 0) {
|
|
344
|
+
console.log(' No workspace issues found.');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
console.log(' Issues:');
|
|
348
|
+
for (const issue of issues) {
|
|
349
|
+
console.log(` - ${issue.message}`);
|
|
350
|
+
if (issue.target) {
|
|
351
|
+
console.log(` Target: ${issue.target}`);
|
|
352
|
+
}
|
|
353
|
+
if (issue.fix) {
|
|
354
|
+
console.log(` Fix: ${issue.fix}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function printLinkMutationHuman(heading, payload) {
|
|
359
|
+
printStatusLines(payload.status);
|
|
360
|
+
console.log(heading);
|
|
361
|
+
console.log(` ${payload.link.name} -> ${payload.link.path}`);
|
|
362
|
+
console.log(`Workspace: ${payload.workspace.name}`);
|
|
363
|
+
}
|
|
364
|
+
function formatWorkspaceSkillAgentResult(result) {
|
|
365
|
+
const workflowCount = result.workflow_ids?.length ?? 0;
|
|
366
|
+
const workflowLabel = workflowCount === 1 ? '1 workflow' : `${workflowCount} workflows`;
|
|
367
|
+
return `${result.name} (${workflowLabel})`;
|
|
368
|
+
}
|
|
369
|
+
function formatWorkspaceSkillRemovedResult(result) {
|
|
370
|
+
const workflowCount = result.workflow_ids?.length ?? 0;
|
|
371
|
+
const workflowLabel = workflowCount === 1 ? '1 workflow' : `${workflowCount} workflows`;
|
|
372
|
+
return `${result.name} (${workflowLabel} removed)`;
|
|
373
|
+
}
|
|
374
|
+
function printWorkspaceSkillReportHuman(report) {
|
|
375
|
+
console.log('Agent skills:');
|
|
376
|
+
console.log(` Profile: ${report.profile}`);
|
|
377
|
+
console.log(` Workflows: ${report.workflow_ids.length > 0 ? report.workflow_ids.join(', ') : '(none selected)'}`);
|
|
378
|
+
if (report.generated.length > 0) {
|
|
379
|
+
console.log(` Generated: ${report.generated.map(formatWorkspaceSkillAgentResult).join(', ')}`);
|
|
380
|
+
}
|
|
381
|
+
if (report.added.length > 0) {
|
|
382
|
+
console.log(` Added: ${report.added.map(formatWorkspaceSkillAgentResult).join(', ')}`);
|
|
383
|
+
}
|
|
384
|
+
if (report.refreshed.length > 0) {
|
|
385
|
+
console.log(` Refreshed: ${report.refreshed.map(formatWorkspaceSkillAgentResult).join(', ')}`);
|
|
386
|
+
}
|
|
387
|
+
if (report.removed.length > 0) {
|
|
388
|
+
console.log(` Removed: ${report.removed.map(formatWorkspaceSkillRemovedResult).join(', ')}`);
|
|
389
|
+
}
|
|
390
|
+
if (report.skipped.length > 0) {
|
|
391
|
+
for (const skipped of report.skipped) {
|
|
392
|
+
const prefix = skipped.name ? `${skipped.name}: ` : '';
|
|
393
|
+
console.log(` Skipped: ${prefix}${skipped.message}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (report.failed.length > 0) {
|
|
397
|
+
console.log(chalk.red(` Failed: ${report.failed.map((failure) => `${failure.name} (${failure.error})`).join(', ')}`));
|
|
398
|
+
}
|
|
399
|
+
if (report.delivery_notice) {
|
|
400
|
+
console.log(chalk.dim(` ${report.delivery_notice}`));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function hasWorkspaceSkillFailures(report) {
|
|
404
|
+
return report.failed.length > 0;
|
|
405
|
+
}
|
|
406
|
+
function setWorkspaceSkillFailureExitCode(report) {
|
|
407
|
+
if (hasWorkspaceSkillFailures(report)) {
|
|
408
|
+
process.exitCode = 1;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function writeWorkspaceSkillState(workspaceRoot, selectedAgentIds, report) {
|
|
412
|
+
const localState = (await readOptionalWorkspaceLocalState(workspaceRoot)) ?? {
|
|
413
|
+
version: 1,
|
|
414
|
+
paths: {},
|
|
415
|
+
};
|
|
416
|
+
await writeWorkspaceLocalState(workspaceRoot, {
|
|
417
|
+
...localState,
|
|
418
|
+
workspace_skills: {
|
|
419
|
+
selected_agents: selectedAgentIds,
|
|
420
|
+
last_applied_profile: report.profile,
|
|
421
|
+
last_applied_delivery: report.delivery,
|
|
422
|
+
last_applied_workflow_ids: report.workflow_ids,
|
|
423
|
+
last_applied_at: new Date().toISOString(),
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async function resolveWorkspaceOpenOpener(localState, options) {
|
|
428
|
+
if (options.agent && options.editor) {
|
|
429
|
+
throw new WorkspaceCliError('workspace open accepts either --agent <tool> or --editor, not both.', 'workspace_opener_conflict', {
|
|
430
|
+
target: 'workspace.opener',
|
|
431
|
+
fix: 'Choose one opener override.',
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
if (options.agent) {
|
|
435
|
+
return parseAgentOverride(options.agent);
|
|
436
|
+
}
|
|
437
|
+
if (options.editor) {
|
|
438
|
+
return parseWorkspacePreferredOpenerValue('editor');
|
|
439
|
+
}
|
|
440
|
+
if (localState.preferred_opener) {
|
|
441
|
+
return localState.preferred_opener;
|
|
442
|
+
}
|
|
443
|
+
if (!resolveNoInteractive(options) && isInteractive(options)) {
|
|
444
|
+
const openerChoices = listWorkspaceOpenerChoices().filter((choice) => choice.available);
|
|
445
|
+
if (openerChoices.length === 0) {
|
|
446
|
+
throw new WorkspaceCliError('No supported workspace opener is available on PATH.', 'workspace_no_available_openers', {
|
|
447
|
+
target: 'workspace.opener',
|
|
448
|
+
fix: "Install VS Code ('code'), Codex ('codex'), or Claude ('claude'), then retry.",
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return promptPreferredOpener('Open with:', openerChoices);
|
|
452
|
+
}
|
|
453
|
+
throw new WorkspaceCliError('This workspace does not have a preferred opener yet.', 'workspace_opener_unset', {
|
|
454
|
+
target: 'workspace.opener',
|
|
455
|
+
fix: 'Pass --agent <tool> or --editor, or run workspace setup interactively to choose a default opener.',
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
function assertWorkspaceOpenSupportedOptions(options) {
|
|
459
|
+
if (options.prepareOnly) {
|
|
460
|
+
throw new WorkspaceCliError('workspace open supports launching through a selected opener; preview output is reserved for a future context/query surface.', 'workspace_open_prepare_only_unsupported', {
|
|
461
|
+
target: 'workspace.open',
|
|
462
|
+
fix: 'Run openspec workspace open with --agent <tool> or --editor.',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (options.json) {
|
|
466
|
+
throw new WorkspaceCliError('workspace open supports launching through a selected opener; machine-readable context is reserved for a future context/query surface.', 'workspace_open_json_unsupported', {
|
|
467
|
+
target: 'workspace.open',
|
|
468
|
+
fix: 'Use openspec workspace doctor --json for current workspace status.',
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (options.change) {
|
|
472
|
+
throw new WorkspaceCliError('workspace open currently supports root workspace open only; change-scoped open belongs to future workspace change planning.', 'workspace_open_change_unsupported', {
|
|
473
|
+
target: 'workspace.change',
|
|
474
|
+
fix: 'Open the root workspace, then start implementation from an explicit change workflow.',
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function resolveOpenWorkspaceName(positionalName, options) {
|
|
479
|
+
if (positionalName && options.workspace && positionalName !== options.workspace) {
|
|
480
|
+
throw new WorkspaceCliError(`Conflicting workspace selectors: positional '${positionalName}' and --workspace '${options.workspace}'.`, 'workspace_selection_conflict', {
|
|
481
|
+
target: 'workspace.name',
|
|
482
|
+
fix: 'Use either the positional workspace name or --workspace with the same value.',
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
return positionalName ?? options.workspace;
|
|
486
|
+
}
|
|
487
|
+
function resolveUpdateWorkspaceName(positionalName, options) {
|
|
488
|
+
if (positionalName && options.workspace && positionalName !== options.workspace) {
|
|
489
|
+
throw new WorkspaceCliError(`Conflicting workspace selectors: positional '${positionalName}' and --workspace '${options.workspace}'.`, 'workspace_selection_conflict', {
|
|
490
|
+
target: 'workspace.name',
|
|
491
|
+
fix: 'Use either the positional workspace name or --workspace with the same value.',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return positionalName ?? options.workspace;
|
|
495
|
+
}
|
|
496
|
+
function printWorkspaceOpenHuman(selectedName, selectedRoot, opener, skipped) {
|
|
497
|
+
console.log(`Opening workspace: ${selectedName}`);
|
|
498
|
+
console.log(`Location: ${selectedRoot}`);
|
|
499
|
+
console.log(`Opener: ${getWorkspaceOpenerLabel(opener)}`);
|
|
500
|
+
if (skipped.length === 0) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
console.log('');
|
|
504
|
+
console.log('Skipped linked repos or folders:');
|
|
505
|
+
for (const link of skipped) {
|
|
506
|
+
const location = link.path ?? '(no local path recorded)';
|
|
507
|
+
console.log(` ${link.name} -> ${location}`);
|
|
508
|
+
}
|
|
509
|
+
console.log('Repair skipped links with openspec workspace doctor.');
|
|
510
|
+
}
|
|
511
|
+
class WorkspaceCommand {
|
|
512
|
+
async setup(options = {}) {
|
|
513
|
+
try {
|
|
514
|
+
const noInteractive = resolveNoInteractive(options);
|
|
515
|
+
if (options.json && !noInteractive) {
|
|
516
|
+
throw new WorkspaceCliError('workspace setup --json requires --no-interactive.', 'setup_json_requires_no_interactive', {
|
|
517
|
+
fix: 'openspec workspace setup --no-interactive --json --name <name> --link <path>',
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
const interactive = !noInteractive && isInteractive(options);
|
|
521
|
+
if (interactive) {
|
|
522
|
+
printWorkspaceSetupIntro();
|
|
523
|
+
}
|
|
524
|
+
if (!interactive && (!options.name || (options.link ?? []).length === 0)) {
|
|
525
|
+
throw new WorkspaceCliError('workspace setup --no-interactive requires --name <name> and at least one --link <path>.', 'missing_setup_inputs', {
|
|
526
|
+
fix: 'openspec workspace setup --no-interactive --name platform --link /path/to/repo',
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
const workspaceName = interactive
|
|
530
|
+
? await promptWorkspaceName(options.name)
|
|
531
|
+
: validateWorkspaceNameForSetup(options.name ?? '');
|
|
532
|
+
const links = interactive ? await promptSetupLinks() : await parseSetupLinks(options.link);
|
|
533
|
+
if (interactive) {
|
|
534
|
+
console.log('');
|
|
535
|
+
console.log(chalk.bold('[3/5] Choose preferred opener'));
|
|
536
|
+
}
|
|
537
|
+
const preferredOpener = interactive
|
|
538
|
+
? await promptPreferredOpener('Preferred opener:')
|
|
539
|
+
: parseSetupOpenerOption(options.opener);
|
|
540
|
+
let selectedWorkspaceSkillAgents;
|
|
541
|
+
if (options.tools !== undefined) {
|
|
542
|
+
selectedWorkspaceSkillAgents = parseSetupToolsOption(options.tools);
|
|
543
|
+
}
|
|
544
|
+
else if (interactive) {
|
|
545
|
+
console.log('');
|
|
546
|
+
console.log(chalk.bold('[4/5] Install agent skills'));
|
|
547
|
+
console.log(chalk.dim('Choose which coding agents should get OpenSpec skills in this workspace.'));
|
|
548
|
+
console.log(chalk.dim('Press Enter with no agents selected to skip skill installation for now.'));
|
|
549
|
+
console.log('');
|
|
550
|
+
selectedWorkspaceSkillAgents = await promptWorkspaceSkillAgents(preferredOpener);
|
|
551
|
+
}
|
|
552
|
+
if (Object.keys(links).length === 0) {
|
|
553
|
+
throw new WorkspaceCliError('workspace setup --no-interactive requires --name <name> and at least one --link <path>.', 'missing_setup_inputs', {
|
|
554
|
+
fix: 'openspec workspace setup --no-interactive --name platform --link /path/to/repo',
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
if (interactive) {
|
|
558
|
+
console.log('');
|
|
559
|
+
console.log(chalk.bold('[5/5] Create workspace files'));
|
|
560
|
+
}
|
|
561
|
+
const workspace = await createManagedWorkspace(workspaceName, links, preferredOpener);
|
|
562
|
+
const skillReport = selectedWorkspaceSkillAgents === undefined
|
|
563
|
+
? createWorkspaceSkillSkippedReport('tools_omitted', 'No workspace skills were installed. Run openspec workspace update --tools <ids> to install them later.')
|
|
564
|
+
: await generateWorkspaceAgentSkills(workspace.root, selectedWorkspaceSkillAgents);
|
|
565
|
+
if (selectedWorkspaceSkillAgents !== undefined && !hasWorkspaceSkillFailures(skillReport)) {
|
|
566
|
+
await writeWorkspaceSkillState(workspace.root, selectedWorkspaceSkillAgents, skillReport);
|
|
567
|
+
}
|
|
568
|
+
const doctorResult = await loadWorkspaceForDoctor({
|
|
569
|
+
name: workspace.name,
|
|
570
|
+
root: workspace.root,
|
|
571
|
+
status: [],
|
|
572
|
+
unregisteredCurrentWorkspace: false,
|
|
573
|
+
});
|
|
574
|
+
if (options.json) {
|
|
575
|
+
printJson({
|
|
576
|
+
workspace: doctorResult.workspace,
|
|
577
|
+
workspace_skills: skillReport,
|
|
578
|
+
status: doctorResult.status,
|
|
579
|
+
});
|
|
580
|
+
setWorkspaceSkillFailureExitCode(skillReport);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
console.log(chalk.green('Workspace setup complete'));
|
|
584
|
+
console.log('');
|
|
585
|
+
printWorkspaceListHuman([doctorResult.workspace]);
|
|
586
|
+
console.log('');
|
|
587
|
+
console.log(`Planning path: ${doctorResult.workspace.planning_path}`);
|
|
588
|
+
console.log('');
|
|
589
|
+
console.log('Workspace check:');
|
|
590
|
+
printWorkspaceCheckSummaryHuman(doctorResult);
|
|
591
|
+
console.log('');
|
|
592
|
+
printWorkspaceSkillReportHuman(skillReport);
|
|
593
|
+
console.log('');
|
|
594
|
+
console.log('Next useful commands:');
|
|
595
|
+
console.log(` openspec workspace doctor --workspace ${workspace.name}`);
|
|
596
|
+
console.log(` openspec workspace update --workspace ${workspace.name} --tools <ids>`);
|
|
597
|
+
console.log(' openspec workspace list');
|
|
598
|
+
setWorkspaceSkillFailureExitCode(skillReport);
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
this.handleFailure(options.json, { workspace: null, status: [] }, error);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async list(options = {}) {
|
|
605
|
+
try {
|
|
606
|
+
const registry = await readRegistry();
|
|
607
|
+
const entries = listWorkspaceRegistryEntries(registry);
|
|
608
|
+
const workspaces = await Promise.all(entries.map((entry) => loadWorkspaceForList(entry)));
|
|
609
|
+
const payload = { workspaces, status: [] };
|
|
610
|
+
if (options.json) {
|
|
611
|
+
printJson(payload);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (workspaces.length === 0) {
|
|
615
|
+
console.log("No OpenSpec workspaces found. Run 'openspec workspace setup' first.");
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
printWorkspaceListHuman(workspaces);
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
this.handleFailure(options.json, { workspaces: [], status: [] }, error);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async link(nameOrPath, linkPath, options = {}) {
|
|
625
|
+
try {
|
|
626
|
+
if (!nameOrPath) {
|
|
627
|
+
throw new WorkspaceCliError('workspace link requires a repo or folder path.', 'missing_link_path', {
|
|
628
|
+
fix: 'openspec workspace link /path/to/repo',
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
const selected = await selectWorkspaceForCommand(options, 'link');
|
|
632
|
+
const payload = await addWorkspaceLink(selected, nameOrPath, linkPath);
|
|
633
|
+
if (options.json) {
|
|
634
|
+
printJson(payload);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
printLinkMutationHuman('Linked repo or folder:', payload);
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
this.handleFailure(options.json, { workspace: null, link: null, status: [] }, error);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
async relink(linkNameInput, linkPath, options = {}) {
|
|
644
|
+
try {
|
|
645
|
+
if (!linkNameInput || !linkPath) {
|
|
646
|
+
throw new WorkspaceCliError('workspace relink requires a link name and repo or folder path.', 'missing_relink_arguments', {
|
|
647
|
+
fix: 'openspec workspace relink <name> /path/to/repo',
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
const selected = await selectWorkspaceForCommand(options, 'relink');
|
|
651
|
+
const payload = await updateWorkspaceLink(selected, linkNameInput, linkPath);
|
|
652
|
+
if (options.json) {
|
|
653
|
+
printJson(payload);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
printLinkMutationHuman('Relinked repo or folder:', payload);
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
this.handleFailure(options.json, { workspace: null, link: null, status: [] }, error);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
async doctor(options = {}) {
|
|
663
|
+
try {
|
|
664
|
+
const selected = await selectWorkspaceForCommand(options, 'doctor');
|
|
665
|
+
const result = await loadWorkspaceForDoctor(selected);
|
|
666
|
+
if (options.json) {
|
|
667
|
+
printJson(result);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
printDoctorHuman(result);
|
|
671
|
+
}
|
|
672
|
+
catch (error) {
|
|
673
|
+
this.handleFailure(options.json, { workspace: null, status: [] }, error);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
async update(positionalName, options = {}) {
|
|
677
|
+
try {
|
|
678
|
+
const workspaceName = resolveUpdateWorkspaceName(positionalName, options);
|
|
679
|
+
const selected = await selectWorkspaceForCommand({
|
|
680
|
+
...options,
|
|
681
|
+
workspace: workspaceName,
|
|
682
|
+
}, 'update', { preferPositionalName: Boolean(positionalName) });
|
|
683
|
+
await this.updateSelected(selected, options);
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
async updateRoot(workspaceRoot, options = {}) {
|
|
690
|
+
try {
|
|
691
|
+
const selected = await selectWorkspaceRootForCommand(workspaceRoot);
|
|
692
|
+
await this.updateSelected(selected, options);
|
|
693
|
+
}
|
|
694
|
+
catch (error) {
|
|
695
|
+
this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async updateSelected(selected, options) {
|
|
699
|
+
const { localState } = await readWorkspaceForMutation(selected);
|
|
700
|
+
const hasExplicitToolSelection = options.tools !== undefined;
|
|
701
|
+
const selectedAgentIds = hasExplicitToolSelection
|
|
702
|
+
? parseUpdateToolsOption(options.tools ?? '')
|
|
703
|
+
: localState.workspace_skills?.selected_agents ?? [];
|
|
704
|
+
const previousSkillState = hasExplicitToolSelection
|
|
705
|
+
? localState.workspace_skills ?? { selected_agents: [] }
|
|
706
|
+
: localState.workspace_skills;
|
|
707
|
+
const skillReport = await updateWorkspaceAgentSkills(selected.root, selectedAgentIds, previousSkillState);
|
|
708
|
+
const shouldStoreSelection = hasExplicitToolSelection || Boolean(localState.workspace_skills);
|
|
709
|
+
if (shouldStoreSelection && !hasWorkspaceSkillFailures(skillReport)) {
|
|
710
|
+
await writeWorkspaceSkillState(selected.root, selectedAgentIds, skillReport);
|
|
711
|
+
await recordSelectedWorkspaceAfterMutation(selected);
|
|
712
|
+
}
|
|
713
|
+
const doctorResult = await loadWorkspaceForDoctor(selected);
|
|
714
|
+
if (options.json) {
|
|
715
|
+
printJson({
|
|
716
|
+
workspace: doctorResult.workspace,
|
|
717
|
+
workspace_skills: skillReport,
|
|
718
|
+
status: doctorResult.status,
|
|
719
|
+
});
|
|
720
|
+
setWorkspaceSkillFailureExitCode(skillReport);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
console.log(chalk.green('Workspace update complete'));
|
|
724
|
+
console.log(`Workspace: ${doctorResult.workspace.name}`);
|
|
725
|
+
console.log(`Location: ${doctorResult.workspace.root}`);
|
|
726
|
+
console.log('');
|
|
727
|
+
printStatusLines(doctorResult.status);
|
|
728
|
+
if (doctorResult.status.length > 0) {
|
|
729
|
+
console.log('');
|
|
730
|
+
}
|
|
731
|
+
printWorkspaceSkillReportHuman(skillReport);
|
|
732
|
+
console.log('');
|
|
733
|
+
console.log('Next useful commands:');
|
|
734
|
+
console.log(` openspec workspace doctor --workspace ${doctorResult.workspace.name}`);
|
|
735
|
+
console.log(` openspec workspace update --workspace ${doctorResult.workspace.name} --tools <ids>`);
|
|
736
|
+
setWorkspaceSkillFailureExitCode(skillReport);
|
|
737
|
+
}
|
|
738
|
+
async open(positionalName, options = {}) {
|
|
739
|
+
try {
|
|
740
|
+
assertWorkspaceOpenSupportedOptions(options);
|
|
741
|
+
const workspaceName = resolveOpenWorkspaceName(positionalName, options);
|
|
742
|
+
const selected = await selectWorkspaceForCommand({
|
|
743
|
+
...options,
|
|
744
|
+
workspace: workspaceName,
|
|
745
|
+
}, 'open', { preferPositionalName: true });
|
|
746
|
+
const state = await readWorkspaceOpenState(selected);
|
|
747
|
+
const opener = await resolveWorkspaceOpenOpener(state.localState, options);
|
|
748
|
+
assertWorkspaceOpenerAvailable(opener, state.codeWorkspacePath);
|
|
749
|
+
const { command, skipped } = await buildWorkspaceOpenCommandForState(opener, selected.root, state);
|
|
750
|
+
printStatusLines(selected.status);
|
|
751
|
+
if (selected.status.length > 0) {
|
|
752
|
+
console.log('');
|
|
753
|
+
}
|
|
754
|
+
printWorkspaceOpenHuman(selected.name, selected.root, opener, skipped);
|
|
755
|
+
await launchWorkspaceOpenCommand(command);
|
|
756
|
+
}
|
|
757
|
+
catch (error) {
|
|
758
|
+
this.handleFailure(options.json, { workspace: null, status: [] }, error);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
handleFailure(json, payload, error) {
|
|
762
|
+
if (!json && isPromptCancellationError(error)) {
|
|
763
|
+
console.error('Cancelled.');
|
|
764
|
+
process.exitCode = 130;
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (json) {
|
|
768
|
+
printJson(appendStatus(payload, asStatus(error)));
|
|
769
|
+
process.exitCode = 1;
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const status = asStatus(error);
|
|
773
|
+
console.error(`Error: ${status.message}`);
|
|
774
|
+
if (status.fix) {
|
|
775
|
+
console.error(`Fix: ${status.fix}`);
|
|
776
|
+
}
|
|
777
|
+
process.exitCode = 1;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
export async function runWorkspaceUpdate(positionalName, options = {}) {
|
|
781
|
+
const workspaceCommand = new WorkspaceCommand();
|
|
782
|
+
await workspaceCommand.update(positionalName, options);
|
|
783
|
+
}
|
|
784
|
+
export async function runWorkspaceUpdateForRoot(workspaceRoot, options = {}) {
|
|
785
|
+
const workspaceCommand = new WorkspaceCommand();
|
|
786
|
+
await workspaceCommand.updateRoot(workspaceRoot, options);
|
|
787
|
+
}
|
|
788
|
+
function collectOption(value, previous) {
|
|
789
|
+
return [...previous, value];
|
|
790
|
+
}
|
|
791
|
+
function addWorkspaceSelectionOptions(command) {
|
|
792
|
+
return command
|
|
793
|
+
.option('--workspace <name>', 'Workspace name from the local workspace registry')
|
|
794
|
+
.option('--json', 'Output as JSON')
|
|
795
|
+
.option('--no-interactive', 'Disable prompts');
|
|
796
|
+
}
|
|
797
|
+
export function registerWorkspaceCommand(program) {
|
|
798
|
+
const workspaceCommand = new WorkspaceCommand();
|
|
799
|
+
const workspace = program
|
|
800
|
+
.command('workspace')
|
|
801
|
+
.description('Set up and inspect coordination workspaces');
|
|
802
|
+
workspace
|
|
803
|
+
.command('setup')
|
|
804
|
+
.description('Set up a workspace and link existing repos or folders')
|
|
805
|
+
.option('--name <name>', 'Workspace name')
|
|
806
|
+
.option('--link <link>', 'Repo or folder link. Use <path> or <name>=<path>.', collectOption, [])
|
|
807
|
+
.option('--opener <id>', 'Preferred opener: codex, claude, github-copilot, or editor')
|
|
808
|
+
.option('--tools <tools>', `Install OpenSpec skills for agents. Use "all", "none", or a comma-separated list of: ${getWorkspaceSkillToolIds().join(', ')}`)
|
|
809
|
+
.option('--json', 'Output as JSON')
|
|
810
|
+
.option('--no-interactive', 'Disable prompts')
|
|
811
|
+
.action(async (options) => {
|
|
812
|
+
await workspaceCommand.setup(options);
|
|
813
|
+
});
|
|
814
|
+
workspace
|
|
815
|
+
.command('list')
|
|
816
|
+
.description('List known OpenSpec workspaces')
|
|
817
|
+
.option('--json', 'Output as JSON')
|
|
818
|
+
.action(async (options) => {
|
|
819
|
+
await workspaceCommand.list(options);
|
|
820
|
+
});
|
|
821
|
+
workspace
|
|
822
|
+
.command('ls')
|
|
823
|
+
.description('List known OpenSpec workspaces')
|
|
824
|
+
.option('--json', 'Output as JSON')
|
|
825
|
+
.action(async (options) => {
|
|
826
|
+
await workspaceCommand.list(options);
|
|
827
|
+
});
|
|
828
|
+
addWorkspaceSelectionOptions(workspace
|
|
829
|
+
.command('link [nameOrPath] [path]')
|
|
830
|
+
.description('Link an existing repo or folder to a workspace')).action(async (nameOrPath, linkPath, options) => {
|
|
831
|
+
await workspaceCommand.link(nameOrPath, linkPath, options);
|
|
832
|
+
});
|
|
833
|
+
addWorkspaceSelectionOptions(workspace
|
|
834
|
+
.command('relink <name> <path>')
|
|
835
|
+
.description('Update the local path for an existing workspace link')).action(async (linkName, linkPath, options) => {
|
|
836
|
+
await workspaceCommand.relink(linkName, linkPath, options);
|
|
837
|
+
});
|
|
838
|
+
addWorkspaceSelectionOptions(workspace
|
|
839
|
+
.command('doctor')
|
|
840
|
+
.description('Check what a workspace can resolve on this machine')).action(async (options) => {
|
|
841
|
+
await workspaceCommand.doctor(options);
|
|
842
|
+
});
|
|
843
|
+
workspace
|
|
844
|
+
.command('update [name]')
|
|
845
|
+
.description('Refresh workspace-local OpenSpec agent skills from the active global profile')
|
|
846
|
+
.option('--workspace <name>', 'Workspace name from the local workspace registry')
|
|
847
|
+
.option('--tools <tools>', `Select agents for workspace skills. Use "all", "none", or a comma-separated list of: ${getWorkspaceSkillToolIds().join(', ')}. Global profile selects workflows; --tools selects agents.`)
|
|
848
|
+
.option('--json', 'Output as JSON')
|
|
849
|
+
.option('--no-interactive', 'Disable prompts')
|
|
850
|
+
.action(async (name, options) => {
|
|
851
|
+
await workspaceCommand.update(name, options);
|
|
852
|
+
});
|
|
853
|
+
workspace
|
|
854
|
+
.command('open [name]')
|
|
855
|
+
.description('Open a workspace in an agent or VS Code editor')
|
|
856
|
+
.option('--workspace <name>', 'Workspace name from the local workspace registry')
|
|
857
|
+
.option('--agent <tool>', 'Use an agent for this session: codex, claude, or github-copilot')
|
|
858
|
+
.option('--editor', 'Open the workspace in VS Code editor mode')
|
|
859
|
+
.option('--prepare-only', 'Unsupported: preview surfaces belong to a future context/query command')
|
|
860
|
+
.option('--json', 'Unsupported: machine-readable context belongs to a future context/query command')
|
|
861
|
+
.option('--change <id>', 'Unsupported: change-scoped open belongs to future workspace change planning')
|
|
862
|
+
.option('--no-interactive', 'Disable prompts')
|
|
863
|
+
.action(async (name, options) => {
|
|
864
|
+
await workspaceCommand.open(name, options);
|
|
865
|
+
});
|
|
866
|
+
// Intentionally no public `workspace create` command in this slice.
|
|
867
|
+
}
|
|
868
|
+
//# sourceMappingURL=workspace.js.map
|