@kbdavis07/openspec 1.5.0-nov.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 +239 -0
- package/bin/openspec.js +5 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +544 -0
- package/dist/commands/change.d.ts +38 -0
- package/dist/commands/change.js +245 -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 +34 -0
- package/dist/commands/validate.js +342 -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 +39 -0
- package/dist/commands/workflow/instructions.js +393 -0
- package/dist/commands/workflow/new-change.d.ts +20 -0
- package/dist/commands/workflow/new-change.js +109 -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 +74 -0
- package/dist/commands/workflow/shared.js +170 -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 +496 -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 +39 -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 +34 -0
- package/dist/core/command-generation/adapters/index.js +34 -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/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +44 -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/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +48 -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 +102 -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 +759 -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/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 +40 -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 +614 -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 +214 -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 +195 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +198 -0
- package/dist/core/parsers/markdown-parser.d.ts +23 -0
- package/dist/core/parsers/markdown-parser.js +173 -0
- package/dist/core/parsers/requirement-blocks.d.ts +48 -0
- package/dist/core/parsers/requirement-blocks.js +235 -0
- package/dist/core/parsers/requirement-text.d.ts +55 -0
- package/dist/core/parsers/requirement-text.js +136 -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 +201 -0
- package/dist/core/profiles.d.ts +26 -0
- package/dist/core/profiles.js +41 -0
- package/dist/core/project-config.d.ts +107 -0
- package/dist/core/project-config.js +373 -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 +18 -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/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -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 +100 -0
- package/dist/core/shared/tool-detection.d.ts +71 -0
- package/dist/core/shared/tool-detection.js +160 -0
- package/dist/core/specs-apply.d.ts +78 -0
- package/dist/core/specs-apply.js +428 -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 +803 -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 +20 -0
- package/dist/core/templates/skill-templates.js +19 -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 +315 -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/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/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 +223 -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/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +167 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +339 -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 +51 -0
- package/dist/core/validation/validator.js +498 -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 +85 -0
- package/dist/utils/change-utils.js +173 -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 +17 -0
- package/dist/utils/task-progress.js +100 -0
- package/package.json +85 -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,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Types and Utilities for Artifact Workflow Commands
|
|
3
|
+
*
|
|
4
|
+
* This module contains types, constants, and validation helpers used across
|
|
5
|
+
* multiple artifact workflow commands.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import { getSchemaDir, listSchemas } from '../../core/artifact-graph/index.js';
|
|
11
|
+
import { isRootSelectionError } from '../../core/root-selection.js';
|
|
12
|
+
import { validateChangeExistingPath } from '../../utils/change-utils.js';
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// Constants
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
export const DEFAULT_SCHEMA = 'spec-driven';
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// Utility Functions
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
export function printJson(payload) {
|
|
21
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
22
|
+
}
|
|
23
|
+
export function statusFromError(error) {
|
|
24
|
+
if (isRootSelectionError(error)) {
|
|
25
|
+
return { ...error.diagnostic };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
severity: 'error',
|
|
29
|
+
code: 'change_error',
|
|
30
|
+
message: error instanceof Error ? error.message : String(error),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Checks if color output is disabled via NO_COLOR env or --no-color flag.
|
|
35
|
+
*/
|
|
36
|
+
export function isColorDisabled() {
|
|
37
|
+
return process.env.NO_COLOR === '1' || process.env.NO_COLOR === 'true';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Gets the color function based on status.
|
|
41
|
+
*/
|
|
42
|
+
export function getStatusColor(status) {
|
|
43
|
+
if (isColorDisabled()) {
|
|
44
|
+
return (text) => text;
|
|
45
|
+
}
|
|
46
|
+
switch (status) {
|
|
47
|
+
case 'done':
|
|
48
|
+
return chalk.green;
|
|
49
|
+
case 'ready':
|
|
50
|
+
return chalk.yellow;
|
|
51
|
+
case 'blocked':
|
|
52
|
+
return chalk.red;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gets the status indicator for an artifact.
|
|
57
|
+
*/
|
|
58
|
+
export function getStatusIndicator(status) {
|
|
59
|
+
const color = getStatusColor(status);
|
|
60
|
+
switch (status) {
|
|
61
|
+
case 'done':
|
|
62
|
+
return color('[x]');
|
|
63
|
+
case 'ready':
|
|
64
|
+
return color('[ ]');
|
|
65
|
+
case 'blocked':
|
|
66
|
+
return color('[-]');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Returns true if the directory at dirPath is an actual change
|
|
71
|
+
* (i.e. it contains a .openspec.yaml file directly).
|
|
72
|
+
*/
|
|
73
|
+
async function isChangeDir(dirPath) {
|
|
74
|
+
try {
|
|
75
|
+
await fs.promises.access(path.join(dirPath, '.openspec.yaml'));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Returns the list of available change directory names under openspec/changes/.
|
|
84
|
+
* Supports a one-level feature-group structure: if a top-level directory does
|
|
85
|
+
* not contain .openspec.yaml it is treated as a group folder and its children
|
|
86
|
+
* are returned as "group/change" paths.
|
|
87
|
+
* Excludes the archive directory and hidden directories.
|
|
88
|
+
*/
|
|
89
|
+
export async function getAvailableChanges(projectRoot, changesDir = path.join(projectRoot, 'openspec', 'changes')) {
|
|
90
|
+
const changesPath = changesDir;
|
|
91
|
+
try {
|
|
92
|
+
const entries = await fs.promises.readdir(changesPath, { withFileTypes: true });
|
|
93
|
+
const result = [];
|
|
94
|
+
for (const e of entries) {
|
|
95
|
+
if (!e.isDirectory() || e.name === 'archive' || e.name.startsWith('.'))
|
|
96
|
+
continue;
|
|
97
|
+
const dirPath = path.join(changesPath, e.name);
|
|
98
|
+
if (await isChangeDir(dirPath)) {
|
|
99
|
+
result.push(e.name);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Feature-group folder — recurse one level
|
|
103
|
+
try {
|
|
104
|
+
const subEntries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
105
|
+
for (const sub of subEntries) {
|
|
106
|
+
if (sub.isDirectory() && !sub.name.startsWith('.')) {
|
|
107
|
+
result.push(`${e.name}/${sub.name}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// unreadable subfolder — skip
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error.code === 'ENOENT')
|
|
120
|
+
return [];
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Validates that a change exists and returns available changes if not.
|
|
126
|
+
* Checks directory existence directly to support scaffolded changes (without proposal.md).
|
|
127
|
+
*/
|
|
128
|
+
export async function validateChangeExists(changeName, projectRoot, changesDir = path.join(projectRoot, 'openspec', 'changes'), hints = {}) {
|
|
129
|
+
// Hints must stay pasteable: callers with a selected store pass a
|
|
130
|
+
// store-carrying hint so following it lands in the same root.
|
|
131
|
+
const newChangeHint = hints.newChangeHint ?? 'openspec new change <name>';
|
|
132
|
+
if (!changeName) {
|
|
133
|
+
const available = await getAvailableChanges(projectRoot, changesDir);
|
|
134
|
+
if (available.length === 0) {
|
|
135
|
+
throw new Error(`No changes found. Create one with: ${newChangeHint}`);
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
|
|
138
|
+
}
|
|
139
|
+
// Validate the path to prevent path traversal (accepts group/change paths)
|
|
140
|
+
const nameValidation = validateChangeExistingPath(changeName);
|
|
141
|
+
if (!nameValidation.valid) {
|
|
142
|
+
throw new Error(`Invalid change name '${changeName}': ${nameValidation.error}`);
|
|
143
|
+
}
|
|
144
|
+
// Check directory existence directly
|
|
145
|
+
const changePath = path.join(changesDir, changeName);
|
|
146
|
+
const exists = fs.existsSync(changePath) && fs.statSync(changePath).isDirectory();
|
|
147
|
+
if (!exists) {
|
|
148
|
+
const available = await getAvailableChanges(projectRoot, changesDir);
|
|
149
|
+
if (available.length === 0) {
|
|
150
|
+
throw new Error(`Change '${changeName}' not found. No changes exist. Create one with: ${newChangeHint}`);
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Change '${changeName}' not found. Available changes:\n ${available.join('\n ')}`);
|
|
153
|
+
}
|
|
154
|
+
return changeName;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Validates that a schema exists and returns available schemas if not.
|
|
158
|
+
*
|
|
159
|
+
* @param schemaName - The schema name to validate
|
|
160
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
161
|
+
*/
|
|
162
|
+
export function validateSchemaExists(schemaName, projectRoot) {
|
|
163
|
+
const schemaDir = getSchemaDir(schemaName, projectRoot);
|
|
164
|
+
if (!schemaDir) {
|
|
165
|
+
const availableSchemas = listSchemas(projectRoot);
|
|
166
|
+
throw new Error(`Schema '${schemaName}' not found. Available schemas:\n ${availableSchemas.join('\n ')}`);
|
|
167
|
+
}
|
|
168
|
+
return schemaName;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Command
|
|
3
|
+
*
|
|
4
|
+
* Displays artifact completion status for a change.
|
|
5
|
+
*/
|
|
6
|
+
import { type ChangeStatus } from '../../core/artifact-graph/index.js';
|
|
7
|
+
export interface StatusOptions {
|
|
8
|
+
change?: string;
|
|
9
|
+
schema?: string;
|
|
10
|
+
store?: string;
|
|
11
|
+
storePath?: string;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function statusCommand(options: StatusOptions): Promise<void>;
|
|
15
|
+
export declare function printStatusText(status: ChangeStatus): void;
|
|
16
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Command
|
|
3
|
+
*
|
|
4
|
+
* Displays artifact completion status for a change.
|
|
5
|
+
*/
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { getChangeDir } from '../../core/planning-home.js';
|
|
9
|
+
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
|
|
10
|
+
import { loadChangeContext, formatChangeStatus, } from '../../core/artifact-graph/index.js';
|
|
11
|
+
import { validateChangeExists, validateSchemaExists, getAvailableChanges, getStatusIndicator, getStatusColor, } from './shared.js';
|
|
12
|
+
// -----------------------------------------------------------------------------
|
|
13
|
+
// Command Implementation
|
|
14
|
+
// -----------------------------------------------------------------------------
|
|
15
|
+
export async function statusCommand(options) {
|
|
16
|
+
// The root resolves (and the store banner prints) before the spinner starts
|
|
17
|
+
// so the two do not fight over stderr.
|
|
18
|
+
const root = await resolveRootForCommand(options, { json: options.json });
|
|
19
|
+
if (!root) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const spinner = options.json ? undefined : ora('Loading change status...').start();
|
|
23
|
+
try {
|
|
24
|
+
const planningHome = toPlanningHome(root);
|
|
25
|
+
const projectRoot = root.path;
|
|
26
|
+
const rootOutput = toRootOutput(root);
|
|
27
|
+
const newChangeHint = withStoreFlag(root, 'openspec new change <name>');
|
|
28
|
+
// Handle no-changes case gracefully — status is informational,
|
|
29
|
+
// so "no changes" is a valid state, not an error.
|
|
30
|
+
if (!options.change) {
|
|
31
|
+
const available = await getAvailableChanges(projectRoot, root.changesDir);
|
|
32
|
+
if (available.length === 0) {
|
|
33
|
+
spinner?.stop();
|
|
34
|
+
if (options.json) {
|
|
35
|
+
console.log(JSON.stringify({ changes: [], message: 'No active changes.', root: rootOutput }, null, 2));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(`No active changes. Create one with: ${newChangeHint}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Changes exist but --change not provided
|
|
42
|
+
spinner?.stop();
|
|
43
|
+
throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
|
|
44
|
+
}
|
|
45
|
+
const changeName = await validateChangeExists(options.change, projectRoot, root.changesDir, { newChangeHint });
|
|
46
|
+
// Validate schema if explicitly provided
|
|
47
|
+
if (options.schema) {
|
|
48
|
+
validateSchemaExists(options.schema, projectRoot);
|
|
49
|
+
}
|
|
50
|
+
// loadChangeContext will auto-detect schema from metadata if not provided
|
|
51
|
+
const context = loadChangeContext(projectRoot, changeName, options.schema, {
|
|
52
|
+
changeDir: getChangeDir(planningHome, changeName),
|
|
53
|
+
planningHome,
|
|
54
|
+
});
|
|
55
|
+
const status = formatChangeStatus(context, isStoreSelectedRoot(root) ? { storeId: root.storeId } : {});
|
|
56
|
+
spinner?.stop();
|
|
57
|
+
if (options.json) {
|
|
58
|
+
console.log(JSON.stringify({ ...status, root: rootOutput }, null, 2));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
printStatusText(status);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
spinner?.stop();
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function printStatusText(status) {
|
|
69
|
+
const doneCount = status.artifacts.filter((a) => a.status === 'done').length;
|
|
70
|
+
const total = status.artifacts.length;
|
|
71
|
+
console.log(`Change: ${status.changeName}`);
|
|
72
|
+
console.log(`Schema: ${status.schemaName}`);
|
|
73
|
+
if (status.changeRoot) {
|
|
74
|
+
console.log(`Change root: ${status.changeRoot}`);
|
|
75
|
+
}
|
|
76
|
+
console.log(`Progress: ${doneCount}/${total} artifacts complete`);
|
|
77
|
+
console.log();
|
|
78
|
+
for (const artifact of status.artifacts) {
|
|
79
|
+
const indicator = getStatusIndicator(artifact.status);
|
|
80
|
+
const color = getStatusColor(artifact.status);
|
|
81
|
+
let line = `${indicator} ${artifact.id}`;
|
|
82
|
+
if (artifact.status === 'blocked' && artifact.missingDeps && artifact.missingDeps.length > 0) {
|
|
83
|
+
line += color(` (blocked by: ${artifact.missingDeps.join(', ')})`);
|
|
84
|
+
}
|
|
85
|
+
console.log(line);
|
|
86
|
+
}
|
|
87
|
+
if (status.isComplete) {
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.green('All artifacts complete!'));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Templates Command
|
|
3
|
+
*
|
|
4
|
+
* Shows resolved template paths for all artifacts in a schema.
|
|
5
|
+
*/
|
|
6
|
+
export interface TemplatesOptions {
|
|
7
|
+
schema?: string;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface TemplateInfo {
|
|
11
|
+
artifactId: string;
|
|
12
|
+
templatePath: string;
|
|
13
|
+
source: 'project' | 'user' | 'package';
|
|
14
|
+
}
|
|
15
|
+
export declare function templatesCommand(options: TemplatesOptions): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Templates Command
|
|
3
|
+
*
|
|
4
|
+
* Shows resolved template paths for all artifacts in a schema.
|
|
5
|
+
*/
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { resolveSchema, getSchemaDir, ArtifactGraph, } from '../../core/artifact-graph/index.js';
|
|
9
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
10
|
+
import { validateSchemaExists, DEFAULT_SCHEMA } from './shared.js';
|
|
11
|
+
// -----------------------------------------------------------------------------
|
|
12
|
+
// Command Implementation
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
export async function templatesCommand(options) {
|
|
15
|
+
const spinner = options.json ? undefined : ora('Loading templates...').start();
|
|
16
|
+
try {
|
|
17
|
+
const projectRoot = process.cwd();
|
|
18
|
+
const schemaName = validateSchemaExists(options.schema ?? DEFAULT_SCHEMA, projectRoot);
|
|
19
|
+
const schema = resolveSchema(schemaName, projectRoot);
|
|
20
|
+
const graph = ArtifactGraph.fromSchema(schema);
|
|
21
|
+
const schemaDir = getSchemaDir(schemaName, projectRoot);
|
|
22
|
+
// Determine the source (project, user, or package)
|
|
23
|
+
const { getUserSchemasDir, getProjectSchemasDir, } = await import('../../core/artifact-graph/resolver.js');
|
|
24
|
+
const projectSchemasDir = getProjectSchemasDir(projectRoot);
|
|
25
|
+
const userSchemasDir = getUserSchemasDir();
|
|
26
|
+
// Determine source by checking if schemaDir is inside each base directory
|
|
27
|
+
// Using path.relative is more robust than startsWith for path comparisons
|
|
28
|
+
const isInsideDir = (child, parent) => {
|
|
29
|
+
const relative = path.relative(parent, child);
|
|
30
|
+
return !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
31
|
+
};
|
|
32
|
+
let source;
|
|
33
|
+
if (isInsideDir(schemaDir, projectSchemasDir)) {
|
|
34
|
+
source = 'project';
|
|
35
|
+
}
|
|
36
|
+
else if (isInsideDir(schemaDir, userSchemasDir)) {
|
|
37
|
+
source = 'user';
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
source = 'package';
|
|
41
|
+
}
|
|
42
|
+
const templates = graph.getAllArtifacts().map((artifact) => ({
|
|
43
|
+
artifactId: artifact.id,
|
|
44
|
+
templatePath: FileSystemUtils.canonicalizeExistingPath(path.join(schemaDir, 'templates', artifact.template)),
|
|
45
|
+
source,
|
|
46
|
+
}));
|
|
47
|
+
spinner?.stop();
|
|
48
|
+
if (options.json) {
|
|
49
|
+
const output = {};
|
|
50
|
+
for (const t of templates) {
|
|
51
|
+
output[t.artifactId] = { path: t.templatePath, source: t.source };
|
|
52
|
+
}
|
|
53
|
+
console.log(JSON.stringify(output, null, 2));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
console.log(`Schema: ${schemaName}`);
|
|
57
|
+
console.log(`Source: ${source}`);
|
|
58
|
+
console.log();
|
|
59
|
+
for (const t of templates) {
|
|
60
|
+
console.log(`${t.artifactId}:`);
|
|
61
|
+
console.log(` ${t.templatePath}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
spinner?.stop();
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type OpenerDefinition, type OpenerScanOptions } from '../core/openers.js';
|
|
2
|
+
import { StoreError } from '../core/store/errors.js';
|
|
3
|
+
import { type Workset, type WorksetMember } from '../core/worksets.js';
|
|
4
|
+
/** Concurrent stats; the first invalid flag (by flag order) reports. */
|
|
5
|
+
export declare function resolveMemberFlags(flags: string[]): Promise<WorksetMember[]>;
|
|
6
|
+
/** One spelling of "this tool id must exist in the merged table". */
|
|
7
|
+
export declare function assertKnownTool(tool: string, table: OpenerDefinition[]): void;
|
|
8
|
+
/** Final assembly shared by both compose paths: one validation rule. */
|
|
9
|
+
export declare function finalizeWorkset(name: string, members: WorksetMember[], tool: string | undefined, table: OpenerDefinition[]): Workset;
|
|
10
|
+
/** The aligned `<name> <path>` rows used by list, remove, and the
|
|
11
|
+
* open fallback; callers pick the stream and indent. */
|
|
12
|
+
export declare function formatMemberRows(members: WorksetMember[]): string[];
|
|
13
|
+
export declare function toolUnknownError(toolId: string, table: OpenerDefinition[]): StoreError;
|
|
14
|
+
/** Stops at the first installed alternative instead of scanning all. */
|
|
15
|
+
export declare function firstInstalledAlternative(table: OpenerDefinition[], excludeId: string | undefined, scan?: OpenerScanOptions): string | null;
|
|
16
|
+
export declare function toolUnavailableError(opener: OpenerDefinition, table: OpenerDefinition[], worksetName: string, scan?: OpenerScanOptions): StoreError;
|
|
17
|
+
/** Interactive open with no saved tool and nothing installed at all. */
|
|
18
|
+
export declare function noToolInstalledError(table: OpenerDefinition[], worksetName: string): StoreError;
|
|
19
|
+
//# sourceMappingURL=workset-input.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input resolution and error builders shared by the workset command
|
|
3
|
+
* and its interactive prompt flows.
|
|
4
|
+
*/
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { pathIsDirectory } from '../core/file-state.js';
|
|
7
|
+
import { findOpener, isOpenerCommandAvailable, isOpenerEnabled, } from '../core/openers.js';
|
|
8
|
+
import { StoreError } from '../core/store/errors.js';
|
|
9
|
+
import { expandUserPath } from '../core/store/operations.js';
|
|
10
|
+
import { getGlobalConfigPath } from '../core/global-config.js';
|
|
11
|
+
import { memberLabelProblem, memberListProblem, } from '../core/worksets.js';
|
|
12
|
+
function memberInvalidError(problem) {
|
|
13
|
+
return new StoreError(`Invalid workset member: ${problem}.`, 'workset_member_invalid', {
|
|
14
|
+
target: 'workset.member',
|
|
15
|
+
fix: 'Pass --member <path> with an existing folder, or --member <name>=<path> to label it.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** `--member <path>` or `--member <name>=<path>` (the first `=` splits). */
|
|
19
|
+
async function resolveMemberFlag(raw) {
|
|
20
|
+
const separator = raw.indexOf('=');
|
|
21
|
+
const label = separator > 0 ? raw.slice(0, separator) : undefined;
|
|
22
|
+
const rawPath = separator > 0 ? raw.slice(separator + 1) : raw;
|
|
23
|
+
if (rawPath.length === 0) {
|
|
24
|
+
throw memberInvalidError(`'${raw}' has no path`);
|
|
25
|
+
}
|
|
26
|
+
const resolvedPath = path.resolve(expandUserPath(rawPath));
|
|
27
|
+
if (!(await pathIsDirectory(resolvedPath))) {
|
|
28
|
+
throw memberInvalidError(`'${rawPath}' is not an existing folder`);
|
|
29
|
+
}
|
|
30
|
+
const name = label ?? path.basename(resolvedPath);
|
|
31
|
+
const labelProblem = memberLabelProblem(name);
|
|
32
|
+
if (labelProblem !== null) {
|
|
33
|
+
throw memberInvalidError(labelProblem);
|
|
34
|
+
}
|
|
35
|
+
return { name, path: resolvedPath };
|
|
36
|
+
}
|
|
37
|
+
/** Concurrent stats; the first invalid flag (by flag order) reports. */
|
|
38
|
+
export async function resolveMemberFlags(flags) {
|
|
39
|
+
const settled = await Promise.allSettled(flags.map(resolveMemberFlag));
|
|
40
|
+
const members = [];
|
|
41
|
+
for (const result of settled) {
|
|
42
|
+
if (result.status === 'rejected') {
|
|
43
|
+
throw result.reason;
|
|
44
|
+
}
|
|
45
|
+
members.push(result.value);
|
|
46
|
+
}
|
|
47
|
+
return members;
|
|
48
|
+
}
|
|
49
|
+
/** One spelling of "this tool id must exist in the merged table". */
|
|
50
|
+
export function assertKnownTool(tool, table) {
|
|
51
|
+
if (findOpener(table, tool) === null) {
|
|
52
|
+
throw toolUnknownError(tool, table);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Final assembly shared by both compose paths: one validation rule. */
|
|
56
|
+
export function finalizeWorkset(name, members, tool, table) {
|
|
57
|
+
const problem = memberListProblem(members);
|
|
58
|
+
if (problem !== null) {
|
|
59
|
+
throw memberInvalidError(problem);
|
|
60
|
+
}
|
|
61
|
+
if (tool !== undefined) {
|
|
62
|
+
assertKnownTool(tool, table);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
...(tool !== undefined ? { tool } : {}),
|
|
67
|
+
members,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** The aligned `<name> <path>` rows used by list, remove, and the
|
|
71
|
+
* open fallback; callers pick the stream and indent. */
|
|
72
|
+
export function formatMemberRows(members) {
|
|
73
|
+
const width = Math.max(...members.map((member) => member.name.length));
|
|
74
|
+
return members.map((member) => `${member.name.padEnd(width)} ${member.path}`);
|
|
75
|
+
}
|
|
76
|
+
export function toolUnknownError(toolId, table) {
|
|
77
|
+
const knownIds = table
|
|
78
|
+
.filter((opener) => isOpenerEnabled(opener))
|
|
79
|
+
.map((opener) => opener.id)
|
|
80
|
+
.join(', ');
|
|
81
|
+
return new StoreError(`Unknown tool '${toolId}'.`, 'workset_tool_unknown', {
|
|
82
|
+
target: 'workset.tool',
|
|
83
|
+
fix: `Known tools: ${knownIds}. Add new tools under "openers" in ${getGlobalConfigPath()}.`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Stops at the first installed alternative instead of scanning all. */
|
|
87
|
+
export function firstInstalledAlternative(table, excludeId, scan) {
|
|
88
|
+
return (table.find((candidate) => candidate.id !== excludeId &&
|
|
89
|
+
isOpenerEnabled(candidate) &&
|
|
90
|
+
isOpenerCommandAvailable(candidate.command, scan))?.id ?? null);
|
|
91
|
+
}
|
|
92
|
+
export function toolUnavailableError(opener, table, worksetName, scan) {
|
|
93
|
+
const alternative = firstInstalledAlternative(table, opener.id, scan);
|
|
94
|
+
return new StoreError(`${opener.label} ('${opener.command}') is not on PATH.`, 'workset_tool_unavailable', {
|
|
95
|
+
target: 'workset.tool',
|
|
96
|
+
fix: alternative !== null
|
|
97
|
+
? `Install '${opener.command}' or run: openspec workset open ${worksetName} --tool ${alternative}`
|
|
98
|
+
: `Install '${opener.command}', then rerun: openspec workset open ${worksetName}`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Interactive open with no saved tool and nothing installed at all. */
|
|
102
|
+
export function noToolInstalledError(table, worksetName) {
|
|
103
|
+
const commands = table
|
|
104
|
+
.filter((opener) => isOpenerEnabled(opener))
|
|
105
|
+
.map((opener) => opener.command)
|
|
106
|
+
.join(', ');
|
|
107
|
+
return new StoreError('None of the known tools is on PATH.', 'workset_tool_unavailable', {
|
|
108
|
+
target: 'workset.tool',
|
|
109
|
+
fix: `Install one of: ${commands}. Then rerun: openspec workset open ${worksetName}`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=workset-input.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type OpenerChoice, type OpenerDefinition } from '../core/openers.js';
|
|
2
|
+
import { type Workset } from '../core/worksets.js';
|
|
3
|
+
export interface ComposeInput {
|
|
4
|
+
memberFlags: string[];
|
|
5
|
+
tool?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function composeInteractively(givenName: string | undefined, input: ComposeInput, table: OpenerDefinition[]): Promise<Workset>;
|
|
8
|
+
export declare function promptToolFromChoices(available: OpenerChoice[]): Promise<string>;
|
|
9
|
+
export declare function promptOpenNow(label: string): Promise<boolean>;
|
|
10
|
+
/** Prints the workset (decision 13: remove shows what it removes). */
|
|
11
|
+
export declare function confirmRemoveInteractively(workset: Workset): Promise<boolean>;
|
|
12
|
+
//# sourceMappingURL=workset-prompts.d.ts.map
|
|
@@ -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
|