@codewalla_india/openspec 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +225 -0
- package/bin/openspec.js +5 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +548 -0
- package/dist/commands/change.d.ts +39 -0
- package/dist/commands/change.js +279 -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 +552 -0
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +155 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +163 -0
- package/dist/commands/feedback.d.ts +9 -0
- package/dist/commands/feedback.js +183 -0
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/schema.js +869 -0
- package/dist/commands/shared-gather.d.ts +14 -0
- package/dist/commands/shared-gather.js +31 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/show.d.ts +19 -0
- package/dist/commands/show.js +177 -0
- package/dist/commands/spec.d.ts +19 -0
- package/dist/commands/spec.js +236 -0
- package/dist/commands/store.d.ts +3 -0
- package/dist/commands/store.js +547 -0
- package/dist/commands/validate.d.ts +26 -0
- package/dist/commands/validate.js +330 -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 +45 -0
- package/dist/commands/workflow/instructions.js +500 -0
- package/dist/commands/workflow/new-change.d.ts +20 -0
- package/dist/commands/workflow/new-change.js +106 -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 +84 -0
- package/dist/commands/workflow/shared.js +133 -0
- package/dist/commands/workflow/status.d.ts +16 -0
- package/dist/commands/workflow/status.js +92 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +69 -0
- package/dist/commands/workset-input.d.ts +19 -0
- package/dist/commands/workset-input.js +112 -0
- package/dist/commands/workset-prompts.d.ts +12 -0
- package/dist/commands/workset-prompts.js +143 -0
- package/dist/commands/workset.d.ts +25 -0
- package/dist/commands/workset.js +446 -0
- package/dist/core/archive.d.ts +22 -0
- package/dist/core/archive.js +471 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +9 -0
- package/dist/core/artifact-graph/index.js +14 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +188 -0
- package/dist/core/artifact-graph/instruction-loader.js +233 -0
- package/dist/core/artifact-graph/outputs.d.ts +14 -0
- package/dist/core/artifact-graph/outputs.js +39 -0
- package/dist/core/artifact-graph/resolver.d.ts +81 -0
- package/dist/core/artifact-graph/resolver.js +257 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +31 -0
- package/dist/core/artifact-graph/types.d.ts +40 -0
- package/dist/core/artifact-graph/types.js +29 -0
- package/dist/core/available-tools.d.ts +17 -0
- package/dist/core/available-tools.js +43 -0
- package/dist/core/change-metadata/index.d.ts +2 -0
- package/dist/core/change-metadata/index.js +2 -0
- package/dist/core/change-metadata/schema.d.ts +19 -0
- package/dist/core/change-metadata/schema.js +30 -0
- package/dist/core/change-status-policy.d.ts +37 -0
- package/dist/core/change-status-policy.js +35 -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 +32 -0
- package/dist/core/command-generation/adapters/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +37 -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 +31 -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 +42 -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 +38 -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/command-generation/yaml.d.ts +22 -0
- package/dist/core/command-generation/yaml.js +38 -0
- package/dist/core/completions/command-registry.d.ts +3 -0
- package/dist/core/completions/command-registry.js +778 -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 +276 -0
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +321 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +151 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
- package/dist/core/completions/installers/powershell-installer.js +415 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
- package/dist/core/completions/installers/zsh-installer.js +424 -0
- package/dist/core/completions/shared-flags.d.ts +13 -0
- package/dist/core/completions/shared-flags.js +33 -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/comprehension/config.d.ts +20 -0
- package/dist/core/comprehension/config.js +23 -0
- package/dist/core/comprehension/fingerprint.d.ts +5 -0
- package/dist/core/comprehension/fingerprint.js +25 -0
- package/dist/core/comprehension/index.d.ts +49 -0
- package/dist/core/comprehension/index.js +78 -0
- package/dist/core/comprehension/pass-record.d.ts +29 -0
- package/dist/core/comprehension/pass-record.js +64 -0
- package/dist/core/comprehension/stats.d.ts +18 -0
- package/dist/core/comprehension/stats.js +41 -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 +87 -0
- package/dist/core/config-schema.js +239 -0
- package/dist/core/config.d.ts +18 -0
- package/dist/core/config.js +39 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +112 -0
- package/dist/core/global-config.d.ts +51 -0
- package/dist/core/global-config.js +124 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +7 -0
- package/dist/core/init.d.ts +37 -0
- package/dist/core/init.js +613 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +514 -0
- package/dist/core/list.d.ts +11 -0
- package/dist/core/list.js +185 -0
- package/dist/core/migration.d.ts +23 -0
- package/dist/core/migration.js +108 -0
- package/dist/core/openers.d.ts +77 -0
- package/dist/core/openers.js +251 -0
- package/dist/core/openspec-root.d.ts +45 -0
- package/dist/core/openspec-root.js +192 -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 +16 -0
- package/dist/core/planning-home.js +67 -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 +120 -0
- package/dist/core/project-config.js +406 -0
- package/dist/core/references.d.ts +63 -0
- package/dist/core/references.js +310 -0
- package/dist/core/relationship-health.d.ts +65 -0
- package/dist/core/relationship-health.js +64 -0
- package/dist/core/root-selection.d.ts +122 -0
- package/dist/core/root-selection.js +337 -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 +78 -0
- package/dist/core/specs-apply.js +394 -0
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/store/errors.js +22 -0
- package/dist/core/store/foundation.d.ts +56 -0
- package/dist/core/store/foundation.js +251 -0
- package/dist/core/store/git.d.ts +23 -0
- package/dist/core/store/git.js +137 -0
- package/dist/core/store/index.d.ts +5 -0
- package/dist/core/store/index.js +5 -0
- package/dist/core/store/operations.d.ts +114 -0
- package/dist/core/store/operations.js +783 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -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 +337 -0
- package/dist/core/templates/workflows/archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/archive-change.js +278 -0
- package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/bulk-archive-change.js +493 -0
- package/dist/core/templates/workflows/comprehension-guidance.d.ts +9 -0
- package/dist/core/templates/workflows/comprehension-guidance.js +58 -0
- package/dist/core/templates/workflows/continue-change.d.ts +10 -0
- package/dist/core/templates/workflows/continue-change.js +239 -0
- package/dist/core/templates/workflows/explore.d.ts +10 -0
- package/dist/core/templates/workflows/explore.js +464 -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 +205 -0
- package/dist/core/templates/workflows/mcp-guidance.d.ts +13 -0
- package/dist/core/templates/workflows/mcp-guidance.js +116 -0
- package/dist/core/templates/workflows/new-change.d.ts +10 -0
- package/dist/core/templates/workflows/new-change.js +148 -0
- package/dist/core/templates/workflows/onboard.d.ts +10 -0
- package/dist/core/templates/workflows/onboard.js +566 -0
- package/dist/core/templates/workflows/propose.d.ts +10 -0
- package/dist/core/templates/workflows/propose.js +228 -0
- package/dist/core/templates/workflows/store-selection.d.ts +8 -0
- package/dist/core/templates/workflows/store-selection.js +8 -0
- package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
- package/dist/core/templates/workflows/sync-specs.js +291 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +346 -0
- package/dist/core/update.d.ts +82 -0
- package/dist/core/update.js +557 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +44 -0
- package/dist/core/validation/validator.js +435 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/core/working-set.d.ts +47 -0
- package/dist/core/working-set.js +43 -0
- package/dist/core/worksets.d.ts +75 -0
- package/dist/core/worksets.js +245 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -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 +55 -0
- package/dist/utils/change-metadata.js +141 -0
- package/dist/utils/change-utils.d.ts +71 -0
- package/dist/utils/change-utils.js +138 -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 +320 -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 +84 -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/scripts/postinstall.js +83 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workset command's interactive prompt flows (the compose wizard,
|
|
3
|
+
* the open-time tool select, the remove confirm). @inquirer is always
|
|
4
|
+
* imported dynamically at the call site - never at module top.
|
|
5
|
+
*/
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { pathIsDirectory } from '../core/file-state.js';
|
|
8
|
+
import { listOpenerChoices, } from '../core/openers.js';
|
|
9
|
+
import { expandUserPath } from '../core/store/operations.js';
|
|
10
|
+
import { memberLabelProblem, validateWorksetName, } from '../core/worksets.js';
|
|
11
|
+
import { asErrorMessage } from './shared-output.js';
|
|
12
|
+
import { assertKnownTool, finalizeWorkset, formatMemberRows, resolveMemberFlags, } from './workset-input.js';
|
|
13
|
+
export async function composeInteractively(givenName, input, table) {
|
|
14
|
+
const prompts = await import('@inquirer/prompts');
|
|
15
|
+
console.log('[1/3] Name the workset');
|
|
16
|
+
let name;
|
|
17
|
+
if (givenName !== undefined) {
|
|
18
|
+
name = validateWorksetName(givenName);
|
|
19
|
+
console.log(` Workset name: ${name}`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
name = await prompts.input({
|
|
23
|
+
message: 'Workset name:',
|
|
24
|
+
required: true,
|
|
25
|
+
validate(value) {
|
|
26
|
+
try {
|
|
27
|
+
validateWorksetName(value);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return asErrorMessage(error);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Flag-provided pieces are validated before any prompting, so a
|
|
37
|
+
// bad flag or tool cannot discard a finished wizard walk.
|
|
38
|
+
if (input.tool !== undefined) {
|
|
39
|
+
assertKnownTool(input.tool, table);
|
|
40
|
+
}
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log('[2/3] Add member folders (the first one is the primary - sessions start there)');
|
|
43
|
+
const members = await resolveMemberFlags(input.memberFlags);
|
|
44
|
+
if (members.length > 0) {
|
|
45
|
+
finalizeWorkset(name, members, input.tool, table);
|
|
46
|
+
for (const member of members) {
|
|
47
|
+
console.log(` Added '${member.name}' (${member.path})`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
while (true) {
|
|
51
|
+
if (members.length > 0) {
|
|
52
|
+
const next = await prompts.select({
|
|
53
|
+
message: 'Add another folder or finish:',
|
|
54
|
+
choices: [
|
|
55
|
+
{ name: 'Finish', value: 'finish' },
|
|
56
|
+
{ name: 'Add another folder', value: 'add' },
|
|
57
|
+
],
|
|
58
|
+
default: 'finish',
|
|
59
|
+
});
|
|
60
|
+
if (next === 'finish') {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const rawPath = await prompts.input({
|
|
65
|
+
message: 'Folder path:',
|
|
66
|
+
...(members.length === 0 ? { default: '.', prefill: 'editable' } : {}),
|
|
67
|
+
required: true,
|
|
68
|
+
async validate(value) {
|
|
69
|
+
const resolved = path.resolve(expandUserPath(value));
|
|
70
|
+
if (!(await pathIsDirectory(resolved))) {
|
|
71
|
+
return `'${value}' is not an existing folder`;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const resolvedPath = path.resolve(expandUserPath(rawPath));
|
|
77
|
+
let label = path.basename(resolvedPath);
|
|
78
|
+
const collision = members.some((member) => member.name === label);
|
|
79
|
+
if (memberLabelProblem(label) !== null || collision) {
|
|
80
|
+
label = await prompts.input({
|
|
81
|
+
message: 'Name this member (the folder label):',
|
|
82
|
+
required: true,
|
|
83
|
+
validate(value) {
|
|
84
|
+
const problem = memberLabelProblem(value);
|
|
85
|
+
if (problem !== null) {
|
|
86
|
+
return problem;
|
|
87
|
+
}
|
|
88
|
+
if (members.some((member) => member.name === value)) {
|
|
89
|
+
return `duplicate member name '${value}'`;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
members.push({ name: label, path: resolvedPath });
|
|
96
|
+
console.log(` Added '${label}' (${resolvedPath})`);
|
|
97
|
+
}
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log('[3/3] Choose your tool');
|
|
100
|
+
let tool = input.tool;
|
|
101
|
+
if (tool === undefined) {
|
|
102
|
+
const choices = listOpenerChoices(table);
|
|
103
|
+
const available = choices.filter((choice) => choice.available);
|
|
104
|
+
if (available.length === 0) {
|
|
105
|
+
console.log(' None of the known tools is on PATH; not saving a preference.');
|
|
106
|
+
console.log(` (Known tools: ${choices.map((choice) => `${choice.opener.id} ${choice.note ?? ''}`.trim()).join(', ')})`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
tool = await promptToolFromChoices(available);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return finalizeWorkset(name, members, tool, table);
|
|
113
|
+
}
|
|
114
|
+
export async function promptToolFromChoices(available) {
|
|
115
|
+
const { select } = await import('@inquirer/prompts');
|
|
116
|
+
return select({
|
|
117
|
+
message: 'Open with:',
|
|
118
|
+
choices: available.map((choice) => ({
|
|
119
|
+
name: choice.opener.label,
|
|
120
|
+
value: choice.opener.id,
|
|
121
|
+
})),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
export async function promptOpenNow(label) {
|
|
125
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
126
|
+
return confirm({
|
|
127
|
+
message: `Open it now in ${label}?`,
|
|
128
|
+
default: true,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Prints the workset (decision 13: remove shows what it removes). */
|
|
132
|
+
export async function confirmRemoveInteractively(workset) {
|
|
133
|
+
const { confirm } = await import('@inquirer/prompts');
|
|
134
|
+
console.log(`Workset '${workset.name}':`);
|
|
135
|
+
for (const row of formatMemberRows(workset.members)) {
|
|
136
|
+
console.log(` ${row}`);
|
|
137
|
+
}
|
|
138
|
+
return confirm({
|
|
139
|
+
message: `Remove workset '${workset.name}'? (member folders are never touched)`,
|
|
140
|
+
default: false,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=workset-prompts.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { type LaunchCommand } from '../core/openers.js';
|
|
4
|
+
interface LaunchResult {
|
|
5
|
+
code: number | null;
|
|
6
|
+
signal: NodeJS.Signals | null;
|
|
7
|
+
}
|
|
8
|
+
export interface LaunchOptions {
|
|
9
|
+
spawnFn?: typeof nodeSpawn;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Spawns the opener with this terminal's stdio. Resolves with the
|
|
13
|
+
* child's exit facts (never rejects for a nonzero exit - for a
|
|
14
|
+
* terminal handoff, the session is the command); rejects with
|
|
15
|
+
* workset_launch_failed only when the spawn itself fails. While the
|
|
16
|
+
* child runs, SIGINT/SIGTERM are ignored in this parent: the terminal
|
|
17
|
+
* delivers Ctrl-C to the child, and the parent must survive to report
|
|
18
|
+
* the child's real exit facts (the 128+n contract).
|
|
19
|
+
*/
|
|
20
|
+
export declare function launchOpenerCommand(command: LaunchCommand, options?: LaunchOptions): Promise<LaunchResult>;
|
|
21
|
+
/** 130 for SIGINT, 143 for SIGTERM - the shell's 128+n convention. */
|
|
22
|
+
export declare function exitCodeForLaunch(result: LaunchResult): number;
|
|
23
|
+
export declare function registerWorksetCommand(program: Command): void;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=workset.d.ts.map
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `workset` command group (slice 7.1): compose, keep, and open
|
|
3
|
+
* personal working views. A workset is purely local and personal -
|
|
4
|
+
* never committed, never shared, never derived from declarations, and
|
|
5
|
+
* never a membership truth. Opening hands the view to the user's tool:
|
|
6
|
+
* editors get the generated .code-workspace; CLI agents take over this
|
|
7
|
+
* terminal with every member attached and no starter prompt.
|
|
8
|
+
*/
|
|
9
|
+
import * as os from 'node:os';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { Option } from 'commander';
|
|
12
|
+
import { buildWorksetCodeWorkspaceJson, getWorkset, getWorksetCodeWorkspacePath, listWorksets, readWorksetsState, removeWorkset, updateWorksetsState, validateWorksetName, withWorkset, withWorksetsLock, worksetNotFoundError, } from '../core/worksets.js';
|
|
13
|
+
import { buildLaunchCommand, findOpener, isOpenerCommandAvailable, isOpenerEnabled, listOpenerChoices, mergeOpenerTable, } from '../core/openers.js';
|
|
14
|
+
import { pathIsDirectory, writeFileAtomically } from '../core/file-state.js';
|
|
15
|
+
import { getGlobalConfig, getGlobalConfigPath, } from '../core/global-config.js';
|
|
16
|
+
import { StoreError } from '../core/store/errors.js';
|
|
17
|
+
import { isInteractive } from '../utils/interactive.js';
|
|
18
|
+
import { asErrorMessage, emitFailure, isPromptCancellationError, printJson, } from './shared-output.js';
|
|
19
|
+
import { finalizeWorkset, firstInstalledAlternative, formatMemberRows, noToolInstalledError, resolveMemberFlags, toolUnavailableError, toolUnknownError, } from './workset-input.js';
|
|
20
|
+
import { composeInteractively, confirmRemoveInteractively, promptOpenNow, promptToolFromChoices, } from './workset-prompts.js';
|
|
21
|
+
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
|
22
|
+
// cross-spawn is CJS with no types and only `workset open` needs it -
|
|
23
|
+
// loaded lazily so every other CLI invocation skips its module graph.
|
|
24
|
+
let cachedSpawn;
|
|
25
|
+
function defaultSpawn() {
|
|
26
|
+
if (cachedSpawn === undefined) {
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
cachedSpawn = require('cross-spawn');
|
|
29
|
+
}
|
|
30
|
+
return cachedSpawn;
|
|
31
|
+
}
|
|
32
|
+
function readOpenerTable() {
|
|
33
|
+
return mergeOpenerTable(getGlobalConfig().openers, getGlobalConfigPath());
|
|
34
|
+
}
|
|
35
|
+
function worksetCliOpenerDisabledError(opener, name) {
|
|
36
|
+
return new StoreError(`Opening a workset in ${opener.label} is temporarily disabled while CLI-agent opening is reworked. Worksets open in an IDE for now.`, 'workset_cli_opener_disabled', {
|
|
37
|
+
target: 'workset.tool',
|
|
38
|
+
fix: `Open in VS Code or Cursor: openspec workset open ${name} --tool code`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Spawns the opener with this terminal's stdio. Resolves with the
|
|
43
|
+
* child's exit facts (never rejects for a nonzero exit - for a
|
|
44
|
+
* terminal handoff, the session is the command); rejects with
|
|
45
|
+
* workset_launch_failed only when the spawn itself fails. While the
|
|
46
|
+
* child runs, SIGINT/SIGTERM are ignored in this parent: the terminal
|
|
47
|
+
* delivers Ctrl-C to the child, and the parent must survive to report
|
|
48
|
+
* the child's real exit facts (the 128+n contract).
|
|
49
|
+
*/
|
|
50
|
+
export function launchOpenerCommand(command, options = {}) {
|
|
51
|
+
const spawnFn = options.spawnFn ?? defaultSpawn();
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const launchFailure = (error) => new StoreError(`Could not launch ${command.label}: ${asErrorMessage(error)}`, 'workset_launch_failed', {
|
|
54
|
+
target: 'workset.tool',
|
|
55
|
+
fix: `Check that '${command.executable}' runs from this terminal, or pass --tool with another installed tool.`,
|
|
56
|
+
});
|
|
57
|
+
let child;
|
|
58
|
+
try {
|
|
59
|
+
child = spawnFn(command.executable, command.args, {
|
|
60
|
+
cwd: command.cwd,
|
|
61
|
+
stdio: 'inherit',
|
|
62
|
+
shell: false,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// Some spawn failures throw synchronously (platform-dependent);
|
|
67
|
+
// they are the same launch failure.
|
|
68
|
+
reject(launchFailure(error));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const ignoreSignal = () => undefined;
|
|
72
|
+
process.on('SIGINT', ignoreSignal);
|
|
73
|
+
process.on('SIGTERM', ignoreSignal);
|
|
74
|
+
const cleanup = () => {
|
|
75
|
+
process.removeListener('SIGINT', ignoreSignal);
|
|
76
|
+
process.removeListener('SIGTERM', ignoreSignal);
|
|
77
|
+
};
|
|
78
|
+
child.on('error', (error) => {
|
|
79
|
+
cleanup();
|
|
80
|
+
reject(launchFailure(error));
|
|
81
|
+
});
|
|
82
|
+
child.on('close', (code, signal) => {
|
|
83
|
+
cleanup();
|
|
84
|
+
resolve({ code, signal });
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** 130 for SIGINT, 143 for SIGTERM - the shell's 128+n convention. */
|
|
89
|
+
export function exitCodeForLaunch(result) {
|
|
90
|
+
if (result.signal !== null) {
|
|
91
|
+
const signalNumber = os.constants.signals[result.signal];
|
|
92
|
+
return 128 + (signalNumber ?? 1);
|
|
93
|
+
}
|
|
94
|
+
return result.code ?? 0;
|
|
95
|
+
}
|
|
96
|
+
class WorksetCommand {
|
|
97
|
+
async create(name, options = {}) {
|
|
98
|
+
try {
|
|
99
|
+
const interactive = !options.json && isInteractive();
|
|
100
|
+
let workset;
|
|
101
|
+
let table;
|
|
102
|
+
if (interactive) {
|
|
103
|
+
table = readOpenerTable();
|
|
104
|
+
workset = await composeInteractively(name, { memberFlags: options.member ?? [], tool: options.tool }, table);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
workset = await this.composeFromFlags(name, options);
|
|
108
|
+
}
|
|
109
|
+
await updateWorksetsState((state) => withWorkset(state, workset));
|
|
110
|
+
if (options.json) {
|
|
111
|
+
printJson({ workset, status: [] });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log(`Saved workset '${workset.name}' (${workset.members.length} member${workset.members.length === 1 ? '' : 's'}) to your machine.`);
|
|
116
|
+
if (interactive && workset.tool !== undefined && table !== undefined) {
|
|
117
|
+
const label = findOpener(table, workset.tool)?.label ?? workset.tool;
|
|
118
|
+
let openNow = false;
|
|
119
|
+
try {
|
|
120
|
+
openNow = await promptOpenNow(label);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
// The workset is already durably saved: Ctrl-C here declines
|
|
124
|
+
// the offer, it does not cancel the create.
|
|
125
|
+
if (!isPromptCancellationError(error)) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (openNow) {
|
|
130
|
+
console.log('');
|
|
131
|
+
await this.open(workset.name, {});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
console.log(`Open it any time with: openspec workset open ${workset.name}`);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
emitFailure(options.json, { workset: null, status: [] }, error, 'workset_error');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async composeFromFlags(name, options) {
|
|
142
|
+
if (!name) {
|
|
143
|
+
throw new StoreError('Pass a workset name.', 'workset_name_required', {
|
|
144
|
+
target: 'workset.name',
|
|
145
|
+
fix: 'openspec workset create <name> --member <path>',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
validateWorksetName(name);
|
|
149
|
+
const memberFlags = options.member ?? [];
|
|
150
|
+
if (memberFlags.length === 0) {
|
|
151
|
+
throw new StoreError('Pass at least one member folder.', 'workset_members_required', {
|
|
152
|
+
target: 'workset.member',
|
|
153
|
+
fix: `openspec workset create ${name} --member <path> --member <name>=<path>`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const members = await resolveMemberFlags(memberFlags);
|
|
157
|
+
// The opener table is read only when a tool is actually named - a
|
|
158
|
+
// tool-less scripted create must not fail on unrelated config rows.
|
|
159
|
+
const table = options.tool !== undefined ? readOpenerTable() : [];
|
|
160
|
+
if (options.tool !== undefined) {
|
|
161
|
+
const chosen = findOpener(table, options.tool);
|
|
162
|
+
if (chosen !== null && !isOpenerEnabled(chosen)) {
|
|
163
|
+
throw worksetCliOpenerDisabledError(chosen, name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return finalizeWorkset(name, members, options.tool, table);
|
|
167
|
+
}
|
|
168
|
+
async list(options = {}) {
|
|
169
|
+
try {
|
|
170
|
+
const state = await readWorksetsState();
|
|
171
|
+
const worksets = listWorksets(state);
|
|
172
|
+
if (options.json) {
|
|
173
|
+
printJson({ worksets, status: [] });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (worksets.length === 0) {
|
|
177
|
+
console.log('No worksets saved. Create one with: openspec workset create');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// The table is consulted only to render tool labels.
|
|
181
|
+
const table = worksets.some((workset) => workset.tool !== undefined)
|
|
182
|
+
? readOpenerTable()
|
|
183
|
+
: [];
|
|
184
|
+
for (const workset of worksets) {
|
|
185
|
+
const toolLabel = workset.tool !== undefined
|
|
186
|
+
? ` (opens in ${findOpener(table, workset.tool)?.label ?? workset.tool})`
|
|
187
|
+
: '';
|
|
188
|
+
console.log(`${workset.name}${toolLabel}`);
|
|
189
|
+
for (const row of formatMemberRows(workset.members)) {
|
|
190
|
+
console.log(` ${row}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
emitFailure(options.json, { worksets: [], status: [] }, error, 'workset_error');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async open(name, options = {}) {
|
|
199
|
+
let prepared;
|
|
200
|
+
try {
|
|
201
|
+
if (options.json) {
|
|
202
|
+
throw new StoreError('workset open hands this terminal to the chosen tool and has no JSON mode.', 'workset_open_json_unsupported', {
|
|
203
|
+
target: 'workset.tool',
|
|
204
|
+
fix: 'Inspect worksets with: openspec workset list --json',
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// Regenerate the derived file FIRST (under the lock), so every
|
|
208
|
+
// cannot-drive failure below can name an existing, current file.
|
|
209
|
+
prepared = await withWorksetsLock(async (state) => {
|
|
210
|
+
const workset = getWorkset(state, name);
|
|
211
|
+
if (workset === null) {
|
|
212
|
+
throw worksetNotFoundError(name, state);
|
|
213
|
+
}
|
|
214
|
+
const checks = await Promise.all(workset.members.map(async (member) => ({
|
|
215
|
+
member,
|
|
216
|
+
exists: await pathIsDirectory(member.path),
|
|
217
|
+
})));
|
|
218
|
+
const surviving = checks
|
|
219
|
+
.filter((check) => check.exists)
|
|
220
|
+
.map((check) => check.member);
|
|
221
|
+
const skipped = checks
|
|
222
|
+
.filter((check) => !check.exists)
|
|
223
|
+
.map((check) => check.member);
|
|
224
|
+
if (surviving.length === 0) {
|
|
225
|
+
throw new StoreError(`No member folder of workset '${name}' exists on this machine.`, 'workset_no_members_available', {
|
|
226
|
+
target: 'workset.member',
|
|
227
|
+
fix: `Recompose it: openspec workset remove ${name} --yes && openspec workset create ${name} --member <path>`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
const codeWorkspacePath = getWorksetCodeWorkspacePath(name);
|
|
231
|
+
await writeFileAtomically(codeWorkspacePath, buildWorksetCodeWorkspaceJson(surviving));
|
|
232
|
+
return { workset, surviving, skipped, codeWorkspacePath };
|
|
233
|
+
});
|
|
234
|
+
for (const member of prepared.skipped) {
|
|
235
|
+
console.error(`Skipped '${member.name}' (${member.path} is not available).`);
|
|
236
|
+
}
|
|
237
|
+
if (prepared.workset.members[0] !== prepared.surviving[0]) {
|
|
238
|
+
const primary = prepared.surviving[0];
|
|
239
|
+
console.error(`Using '${primary.name}' (${primary.path}) as the primary for this open.`);
|
|
240
|
+
}
|
|
241
|
+
const table = readOpenerTable();
|
|
242
|
+
const toolId = options.tool ?? prepared.workset.tool;
|
|
243
|
+
let opener;
|
|
244
|
+
if (toolId !== undefined) {
|
|
245
|
+
const found = findOpener(table, toolId);
|
|
246
|
+
if (found === null) {
|
|
247
|
+
throw toolUnknownError(toolId, table);
|
|
248
|
+
}
|
|
249
|
+
if (!isOpenerEnabled(found)) {
|
|
250
|
+
throw worksetCliOpenerDisabledError(found, name);
|
|
251
|
+
}
|
|
252
|
+
if (!isOpenerCommandAvailable(found.command)) {
|
|
253
|
+
throw toolUnavailableError(found, table, name);
|
|
254
|
+
}
|
|
255
|
+
opener = found;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
if (!isInteractive()) {
|
|
259
|
+
throw new StoreError(`Workset '${name}' has no saved tool.`, 'workset_tool_required', {
|
|
260
|
+
target: 'workset.tool',
|
|
261
|
+
fix: `openspec workset open ${name} --tool <id>`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// The prompt offers only available openers, so the selection
|
|
265
|
+
// needs no second scan.
|
|
266
|
+
const available = listOpenerChoices(table).filter((choice) => choice.available);
|
|
267
|
+
if (available.length === 0) {
|
|
268
|
+
throw noToolInstalledError(table, name);
|
|
269
|
+
}
|
|
270
|
+
const selectedId = await promptToolFromChoices(available);
|
|
271
|
+
opener = available.find((choice) => choice.opener.id === selectedId).opener;
|
|
272
|
+
}
|
|
273
|
+
const launch = buildLaunchCommand(opener, {
|
|
274
|
+
members: prepared.surviving,
|
|
275
|
+
codeWorkspacePath: prepared.codeWorkspacePath,
|
|
276
|
+
});
|
|
277
|
+
if (opener.style === 'workspace-file') {
|
|
278
|
+
console.log(`Opening '${name}' in ${opener.label} (a window opens; this command returns).`);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
console.log(`Handing this terminal to ${opener.label} for '${name}' (the session ends when you exit).`);
|
|
282
|
+
}
|
|
283
|
+
let result;
|
|
284
|
+
try {
|
|
285
|
+
result = await launchOpenerCommand(launch);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
// Make the launch-failure fix pasteable when an alternative is
|
|
289
|
+
// installed (the launcher itself does not know the table).
|
|
290
|
+
if (error instanceof StoreError &&
|
|
291
|
+
error.diagnostic.code === 'workset_launch_failed') {
|
|
292
|
+
const alternative = firstInstalledAlternative(table, opener.id);
|
|
293
|
+
if (alternative !== null) {
|
|
294
|
+
throw new StoreError(error.message, 'workset_launch_failed', {
|
|
295
|
+
target: 'workset.tool',
|
|
296
|
+
fix: `Run: openspec workset open ${name} --tool ${alternative}`,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
const exitCode = exitCodeForLaunch(result);
|
|
303
|
+
if (exitCode !== 0) {
|
|
304
|
+
process.exitCode = exitCode;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
emitFailure(options.json, { status: [] }, error, 'workset_error');
|
|
309
|
+
// Never strand the user: once the derived file is regenerated,
|
|
310
|
+
// every failure (except a prompt cancellation) carries the
|
|
311
|
+
// manual route - the file path plus the members it contains.
|
|
312
|
+
if (!options.json &&
|
|
313
|
+
prepared !== undefined &&
|
|
314
|
+
!isPromptCancellationError(error)) {
|
|
315
|
+
console.error('Open manually:');
|
|
316
|
+
console.error(` Workspace file: ${prepared.codeWorkspacePath}`);
|
|
317
|
+
console.error(' Members:');
|
|
318
|
+
for (const row of formatMemberRows(prepared.surviving)) {
|
|
319
|
+
console.error(` ${row}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async remove(name, options = {}) {
|
|
325
|
+
try {
|
|
326
|
+
if (!options.yes) {
|
|
327
|
+
// The pre-read serves the not-found priority and the confirm
|
|
328
|
+
// display; the --yes path skips it (removeWorkset re-checks
|
|
329
|
+
// under the lock anyway).
|
|
330
|
+
const state = await readWorksetsState();
|
|
331
|
+
const workset = getWorkset(state, name);
|
|
332
|
+
if (workset === null) {
|
|
333
|
+
throw worksetNotFoundError(name, state);
|
|
334
|
+
}
|
|
335
|
+
if (options.json || !isInteractive()) {
|
|
336
|
+
throw new StoreError('Pass --yes to remove a workset non-interactively.', 'workset_remove_confirmation_required', {
|
|
337
|
+
target: 'workset.name',
|
|
338
|
+
fix: `openspec workset remove ${name} --yes`,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const confirmed = await confirmRemoveInteractively(workset);
|
|
342
|
+
if (!confirmed) {
|
|
343
|
+
throw new StoreError('Workset remove cancelled.', 'workset_remove_cancelled', {
|
|
344
|
+
target: 'workset.name',
|
|
345
|
+
fix: 'Rerun remove when you are ready.',
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
await removeWorkset(name);
|
|
350
|
+
if (options.json) {
|
|
351
|
+
printJson({ removed: { name }, status: [] });
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
console.log(`Removed workset '${name}'. Member folders were not touched.`);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
emitFailure(options.json, { removed: null, status: [] }, error, 'workset_error');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function collectMember(value, previous) {
|
|
362
|
+
return [...previous, value];
|
|
363
|
+
}
|
|
364
|
+
export function registerWorksetCommand(program) {
|
|
365
|
+
const worksetCommand = new WorksetCommand();
|
|
366
|
+
const groupDescription = COMMAND_REGISTRY.find((entry) => entry.name === 'workset')?.description ??
|
|
367
|
+
'Compose, keep, and open personal working views (purely local)';
|
|
368
|
+
const workset = program.command('workset').description(groupDescription);
|
|
369
|
+
// Parsed at the group level so `openspec workset --json` keeps the
|
|
370
|
+
// one-JSON-document contract instead of a raw Commander error. The
|
|
371
|
+
// parent option matches anywhere; actions read optsWithGlobals().
|
|
372
|
+
workset.addOption(new Option('--json', 'Output as JSON').hideHelp());
|
|
373
|
+
workset
|
|
374
|
+
.command('create [name]')
|
|
375
|
+
.description('Compose and save a named working view of folders you choose')
|
|
376
|
+
.option('--member <member>', 'Member folder as <path> or <name>=<path>; repeatable, first is the primary', collectMember, [])
|
|
377
|
+
.option('--tool <id>', 'Preferred tool to open this workset with')
|
|
378
|
+
.option('--json', 'Output as JSON')
|
|
379
|
+
.action(async (name, _options, command) => {
|
|
380
|
+
await worksetCommand.create(name, command.optsWithGlobals());
|
|
381
|
+
});
|
|
382
|
+
workset
|
|
383
|
+
.command('list')
|
|
384
|
+
.alias('ls')
|
|
385
|
+
.description('Show saved worksets with their members')
|
|
386
|
+
.option('--json', 'Output as JSON')
|
|
387
|
+
.action(async (_options, command) => {
|
|
388
|
+
await worksetCommand.list(command.optsWithGlobals());
|
|
389
|
+
});
|
|
390
|
+
workset
|
|
391
|
+
.command('open <name>')
|
|
392
|
+
.description('Open a saved workset in your tool (editor window or agent session)')
|
|
393
|
+
.option('--tool <id>', 'Open with this tool just this once')
|
|
394
|
+
.addOption(
|
|
395
|
+
// Parsed so Commander never owns the error; rejected in the
|
|
396
|
+
// action with one JSON document. Hidden because help should not
|
|
397
|
+
// advertise a mode that only rejects.
|
|
398
|
+
new Option('--json', 'Not supported for open').hideHelp())
|
|
399
|
+
.action(async (name, _options, command) => {
|
|
400
|
+
await worksetCommand.open(name, command.optsWithGlobals());
|
|
401
|
+
});
|
|
402
|
+
workset
|
|
403
|
+
.command('remove <name>')
|
|
404
|
+
.description('Delete a saved workset (member folders are never touched)')
|
|
405
|
+
.option('--yes', 'Confirm removal non-interactively')
|
|
406
|
+
.option('--json', 'Output as JSON')
|
|
407
|
+
.action(async (name, _options, command) => {
|
|
408
|
+
await worksetCommand.remove(name, command.optsWithGlobals());
|
|
409
|
+
});
|
|
410
|
+
const subcommandsLine = workset.commands
|
|
411
|
+
.map((subcommand) => {
|
|
412
|
+
const aliases = subcommand.aliases();
|
|
413
|
+
return aliases.length > 0
|
|
414
|
+
? `${subcommand.name()} (${aliases.join(', ')})`
|
|
415
|
+
: subcommand.name();
|
|
416
|
+
})
|
|
417
|
+
.join(', ');
|
|
418
|
+
// One handler owns missing AND unknown subcommands: known
|
|
419
|
+
// subcommands dispatch above; everything else lands in this action
|
|
420
|
+
// (allowExcessArguments routes the unknown operand here), keeping
|
|
421
|
+
// the one-JSON-document contract for `--json` probes.
|
|
422
|
+
workset.allowExcessArguments(true);
|
|
423
|
+
workset.action(() => {
|
|
424
|
+
const attempted = workset.args.filter((operand) => !operand.startsWith('-'));
|
|
425
|
+
const message = attempted.length > 0
|
|
426
|
+
? `Unknown command '${attempted[0]}' for 'openspec workset'. Workset subcommands: ${subcommandsLine}.`
|
|
427
|
+
: `Missing subcommand for 'openspec workset'. Workset subcommands: ${subcommandsLine}.`;
|
|
428
|
+
if (workset.opts().json) {
|
|
429
|
+
printJson({
|
|
430
|
+
status: [
|
|
431
|
+
{
|
|
432
|
+
severity: 'error',
|
|
433
|
+
code: 'unknown_workset_subcommand',
|
|
434
|
+
message,
|
|
435
|
+
fix: 'Run one of the workset subcommands.',
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
console.error(`Error: ${message}`);
|
|
442
|
+
}
|
|
443
|
+
process.exitCode = 1;
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=workset.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ArchiveOptions {
|
|
2
|
+
yes?: boolean;
|
|
3
|
+
skipSpecs?: boolean;
|
|
4
|
+
noValidate?: boolean;
|
|
5
|
+
validate?: boolean;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
store?: string;
|
|
8
|
+
storePath?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class ArchiveCommand {
|
|
11
|
+
execute(changeName?: string, options?: ArchiveOptions): Promise<void>;
|
|
12
|
+
private printJsonFailure;
|
|
13
|
+
/**
|
|
14
|
+
* Shared archive flow. In human mode (json=false) prompts and prose match
|
|
15
|
+
* the historical behavior and cancellations return null. In JSON mode no
|
|
16
|
+
* prose reaches stdout and every blocked path throws.
|
|
17
|
+
*/
|
|
18
|
+
private run;
|
|
19
|
+
private selectChange;
|
|
20
|
+
private getArchiveDate;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=archive.d.ts.map
|