@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,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working-set assembly (slice 4.1): the full set a root's declarations
|
|
3
|
+
* describe — the OpenSpec root and its referenced stores — as an
|
|
4
|
+
* agent-consumable brief. A local convenience
|
|
5
|
+
* computed from declared relationships, never a planning system; no
|
|
6
|
+
* clone/sync/launch machinery. Unresolvable members are reported, not
|
|
7
|
+
* guessed.
|
|
8
|
+
*/
|
|
9
|
+
import type { StoreDiagnostic } from './store/errors.js';
|
|
10
|
+
import { type ReferenceIndexEntry } from './references.js';
|
|
11
|
+
import { type ResolvedOpenSpecRoot } from './root-selection.js';
|
|
12
|
+
export type WorkingSetRole = 'referenced_store';
|
|
13
|
+
export interface WorkingSetMember {
|
|
14
|
+
role: WorkingSetRole;
|
|
15
|
+
id: string;
|
|
16
|
+
path?: string;
|
|
17
|
+
remote?: string;
|
|
18
|
+
fetch?: string;
|
|
19
|
+
status: StoreDiagnostic[];
|
|
20
|
+
}
|
|
21
|
+
export interface WorkingSet {
|
|
22
|
+
root: {
|
|
23
|
+
path: string;
|
|
24
|
+
source: ResolvedOpenSpecRoot['source'];
|
|
25
|
+
store_id?: string;
|
|
26
|
+
role: 'openspec_root';
|
|
27
|
+
};
|
|
28
|
+
members: WorkingSetMember[];
|
|
29
|
+
status: StoreDiagnostic[];
|
|
30
|
+
}
|
|
31
|
+
export interface AssembleWorkingSetInput {
|
|
32
|
+
root: ResolvedOpenSpecRoot;
|
|
33
|
+
referenceEntries: ReferenceIndexEntry[];
|
|
34
|
+
/** The composition's top-level status; the working set keeps only
|
|
35
|
+
* the registry-unreadable degradation (selected by code, never by
|
|
36
|
+
* position). */
|
|
37
|
+
topLevelStatus?: StoreDiagnostic[];
|
|
38
|
+
}
|
|
39
|
+
/** AVAILABLE = path present AND per-entry status empty. */
|
|
40
|
+
export declare function isAvailableMember(member: WorkingSetMember): boolean;
|
|
41
|
+
export declare function assembleWorkingSet(input: AssembleWorkingSetInput): WorkingSet;
|
|
42
|
+
/**
|
|
43
|
+
* Pure builder for the `.code-workspace` editor view — one consumer of
|
|
44
|
+
* assembly, not the feature. Available members only.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildCodeWorkspaceJson(workingSet: WorkingSet, rootName: string): string;
|
|
47
|
+
//# sourceMappingURL=working-set.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { fetchRecipe } from './references.js';
|
|
2
|
+
import { toRootOutput } from './root-selection.js';
|
|
3
|
+
/** AVAILABLE = path present AND per-entry status empty. */
|
|
4
|
+
export function isAvailableMember(member) {
|
|
5
|
+
return member.path !== undefined && member.status.length === 0;
|
|
6
|
+
}
|
|
7
|
+
export function assembleWorkingSet(input) {
|
|
8
|
+
const members = [];
|
|
9
|
+
for (const entry of input.referenceEntries) {
|
|
10
|
+
members.push({
|
|
11
|
+
role: 'referenced_store',
|
|
12
|
+
id: entry.store_id,
|
|
13
|
+
...(entry.root !== undefined ? { path: entry.root } : {}),
|
|
14
|
+
...(entry.root !== undefined && entry.status.length === 0
|
|
15
|
+
? { fetch: fetchRecipe(entry.store_id) }
|
|
16
|
+
: {}),
|
|
17
|
+
status: entry.status,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const status = (input.topLevelStatus ?? []).filter((entry) => entry.code === 'relationship_registry_unreadable');
|
|
21
|
+
return {
|
|
22
|
+
root: { ...toRootOutput(input.root), role: 'openspec_root' },
|
|
23
|
+
members,
|
|
24
|
+
status,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pure builder for the `.code-workspace` editor view — one consumer of
|
|
29
|
+
* assembly, not the feature. Available members only.
|
|
30
|
+
*/
|
|
31
|
+
export function buildCodeWorkspaceJson(workingSet, rootName) {
|
|
32
|
+
const folders = [
|
|
33
|
+
{ name: rootName, path: workingSet.root.path },
|
|
34
|
+
];
|
|
35
|
+
for (const member of workingSet.members) {
|
|
36
|
+
if (!isAvailableMember(member)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
folders.push({ name: `ref:${member.id}`, path: member.path });
|
|
40
|
+
}
|
|
41
|
+
return JSON.stringify({ folders }, null, 2) + '\n';
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=working-set.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { StoreError } from './store/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Personal worksets (slice 7.1): purely local, manually composed,
|
|
4
|
+
* named working views. The whole feature's state lives under
|
|
5
|
+
* <globalDataDir>/worksets/ - the saved-views file plus the generated
|
|
6
|
+
* .code-workspace files - so deleting that one directory removes
|
|
7
|
+
* every trace. Nothing here is committed, shared, or derived from
|
|
8
|
+
* declarations, and nothing is ever written into a member folder.
|
|
9
|
+
*/
|
|
10
|
+
export declare const WORKSETS_DIR_NAME = "worksets";
|
|
11
|
+
export declare const WORKSETS_FILE_NAME = "worksets.yaml";
|
|
12
|
+
export interface WorksetPathOptions {
|
|
13
|
+
globalDataDir?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WorksetMember {
|
|
16
|
+
/** Display label; the .code-workspace folder name. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Absolute path to the member directory. */
|
|
19
|
+
path: string;
|
|
20
|
+
}
|
|
21
|
+
export interface Workset {
|
|
22
|
+
name: string;
|
|
23
|
+
/** Preferred opener id; validated only at open time. */
|
|
24
|
+
tool?: string;
|
|
25
|
+
/** Ordered; the first member is the primary (session cwd). */
|
|
26
|
+
members: WorksetMember[];
|
|
27
|
+
}
|
|
28
|
+
export interface WorksetsState {
|
|
29
|
+
version: 1;
|
|
30
|
+
worksets: Record<string, {
|
|
31
|
+
tool?: string;
|
|
32
|
+
members: WorksetMember[];
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare function getWorksetsDir(options?: WorksetPathOptions): string;
|
|
36
|
+
export declare function getWorksetsFilePath(options?: WorksetPathOptions): string;
|
|
37
|
+
export declare function getWorksetCodeWorkspacePath(name: string, options?: WorksetPathOptions): string;
|
|
38
|
+
export declare function validateWorksetName(name: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* Returns a problem description for a member list, or null when valid.
|
|
41
|
+
* Shared by the file parser (wrapping as invalid_workset_file) and the
|
|
42
|
+
* compose flow (wrapping as workset_member_invalid).
|
|
43
|
+
*/
|
|
44
|
+
export declare function memberListProblem(members: WorksetMember[]): string | null;
|
|
45
|
+
export declare function memberLabelProblem(label: string): string | null;
|
|
46
|
+
export declare function parseWorksetsState(content: string, options?: WorksetPathOptions): WorksetsState;
|
|
47
|
+
export declare function serializeWorksetsState(state: WorksetsState, options?: WorksetPathOptions): string;
|
|
48
|
+
/** Absent file reads as the empty state; a corrupt file throws. */
|
|
49
|
+
export declare function readWorksetsState(options?: WorksetPathOptions): Promise<WorksetsState>;
|
|
50
|
+
export declare function updateWorksetsState(updater: (state: WorksetsState) => WorksetsState | Promise<WorksetsState>, options?: WorksetPathOptions): Promise<WorksetsState>;
|
|
51
|
+
/**
|
|
52
|
+
* Lock-scoped read without a write-back of the saved-views file.
|
|
53
|
+
* `open` uses this to read the state and regenerate the derived
|
|
54
|
+
* .code-workspace coherently; the lock is released before any spawn.
|
|
55
|
+
*/
|
|
56
|
+
export declare function withWorksetsLock<T>(fn: (state: WorksetsState) => T | Promise<T>, options?: WorksetPathOptions): Promise<T>;
|
|
57
|
+
export declare function worksetNotFoundError(name: string, state: WorksetsState): StoreError;
|
|
58
|
+
export declare function withWorkset(state: WorksetsState, workset: Workset): WorksetsState;
|
|
59
|
+
export declare function withoutWorkset(state: WorksetsState, name: string): WorksetsState;
|
|
60
|
+
/**
|
|
61
|
+
* Removes a saved workset and its derived .code-workspace under one
|
|
62
|
+
* lock. The derived-file cleanup runs AFTER the durable write (a
|
|
63
|
+
* failed write must not have already destroyed the artifact); a
|
|
64
|
+
* never-opened workset has no file - ENOENT is fine.
|
|
65
|
+
*/
|
|
66
|
+
export declare function removeWorkset(name: string, options?: WorksetPathOptions): Promise<void>;
|
|
67
|
+
export declare function listWorksets(state: WorksetsState): Workset[];
|
|
68
|
+
export declare function getWorkset(state: WorksetsState, name: string): Workset | null;
|
|
69
|
+
/**
|
|
70
|
+
* The generated .code-workspace content: members in saved order with
|
|
71
|
+
* their saved names, absolute paths, two-space JSON, trailing newline
|
|
72
|
+
* (the working-set builder's conventions).
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildWorksetCodeWorkspaceJson(members: WorksetMember[]): string;
|
|
75
|
+
//# sourceMappingURL=worksets.d.ts.map
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import * as nodeFs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { getGlobalDataDir } from './global-config.js';
|
|
6
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
7
|
+
import { acquireFileLock, makeLockErrorFactory, pathIsFile, releaseFileLock, writeFileAtomically, } from './file-state.js';
|
|
8
|
+
import { StoreError } from './store/errors.js';
|
|
9
|
+
import { folderStyleNameProblem, isKebabId, KEBAB_ID_DESCRIPTION, KEBAB_ID_FIX, } from './id.js';
|
|
10
|
+
import { formatZodIssues } from './zod-issues.js';
|
|
11
|
+
const fs = nodeFs.promises;
|
|
12
|
+
/**
|
|
13
|
+
* Personal worksets (slice 7.1): purely local, manually composed,
|
|
14
|
+
* named working views. The whole feature's state lives under
|
|
15
|
+
* <globalDataDir>/worksets/ - the saved-views file plus the generated
|
|
16
|
+
* .code-workspace files - so deleting that one directory removes
|
|
17
|
+
* every trace. Nothing here is committed, shared, or derived from
|
|
18
|
+
* declarations, and nothing is ever written into a member folder.
|
|
19
|
+
*/
|
|
20
|
+
export const WORKSETS_DIR_NAME = 'worksets';
|
|
21
|
+
export const WORKSETS_FILE_NAME = 'worksets.yaml';
|
|
22
|
+
const CODE_WORKSPACE_EXTENSION = '.code-workspace';
|
|
23
|
+
export function getWorksetsDir(options = {}) {
|
|
24
|
+
return FileSystemUtils.joinPath(options.globalDataDir ?? getGlobalDataDir(), WORKSETS_DIR_NAME);
|
|
25
|
+
}
|
|
26
|
+
export function getWorksetsFilePath(options = {}) {
|
|
27
|
+
return FileSystemUtils.joinPath(getWorksetsDir(options), WORKSETS_FILE_NAME);
|
|
28
|
+
}
|
|
29
|
+
export function getWorksetCodeWorkspacePath(name, options = {}) {
|
|
30
|
+
return FileSystemUtils.joinPath(getWorksetsDir(options), `${name}${CODE_WORKSPACE_EXTENSION}`);
|
|
31
|
+
}
|
|
32
|
+
export function validateWorksetName(name) {
|
|
33
|
+
if (!isKebabId(name)) {
|
|
34
|
+
throw new StoreError(`Workset name '${name}' ${KEBAB_ID_DESCRIPTION}.`, 'invalid_workset_name', {
|
|
35
|
+
target: 'workset.name',
|
|
36
|
+
fix: KEBAB_ID_FIX,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns a problem description for a member list, or null when valid.
|
|
43
|
+
* Shared by the file parser (wrapping as invalid_workset_file) and the
|
|
44
|
+
* compose flow (wrapping as workset_member_invalid).
|
|
45
|
+
*/
|
|
46
|
+
export function memberListProblem(members) {
|
|
47
|
+
if (members.length === 0) {
|
|
48
|
+
return 'members must not be empty';
|
|
49
|
+
}
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
for (const member of members) {
|
|
52
|
+
const labelProblem = memberLabelProblem(member.name);
|
|
53
|
+
if (labelProblem !== null) {
|
|
54
|
+
return labelProblem;
|
|
55
|
+
}
|
|
56
|
+
if (seen.has(member.name)) {
|
|
57
|
+
return `duplicate member name '${member.name}' (use the name=path form to label members distinctly)`;
|
|
58
|
+
}
|
|
59
|
+
seen.add(member.name);
|
|
60
|
+
if (!path.isAbsolute(member.path)) {
|
|
61
|
+
return `member path '${member.path}' must be absolute`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
export function memberLabelProblem(label) {
|
|
67
|
+
return folderStyleNameProblem(label, 'member name');
|
|
68
|
+
}
|
|
69
|
+
const WorksetMemberSchema = z
|
|
70
|
+
.object({
|
|
71
|
+
name: z.string(),
|
|
72
|
+
path: z.string(),
|
|
73
|
+
})
|
|
74
|
+
.strict();
|
|
75
|
+
const WorksetEntrySchema = z
|
|
76
|
+
.object({
|
|
77
|
+
tool: z.string().min(1).optional(),
|
|
78
|
+
members: z.array(WorksetMemberSchema),
|
|
79
|
+
})
|
|
80
|
+
.strict();
|
|
81
|
+
const WorksetsStateSchema = z
|
|
82
|
+
.object({
|
|
83
|
+
version: z.literal(1),
|
|
84
|
+
worksets: z.record(z.string(), WorksetEntrySchema),
|
|
85
|
+
})
|
|
86
|
+
.strict();
|
|
87
|
+
function invalidWorksetsFileError(message, options) {
|
|
88
|
+
return new StoreError(`Invalid worksets file: ${message}`, 'invalid_workset_file', {
|
|
89
|
+
target: 'workset.file',
|
|
90
|
+
fix: `Repair or remove ${getWorksetsFilePath(options)}.`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function parseWorksetsState(content, options = {}) {
|
|
94
|
+
let raw;
|
|
95
|
+
try {
|
|
96
|
+
raw = parseYaml(content);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
throw invalidWorksetsFileError(message, options);
|
|
101
|
+
}
|
|
102
|
+
const result = WorksetsStateSchema.safeParse(raw);
|
|
103
|
+
if (!result.success) {
|
|
104
|
+
throw invalidWorksetsFileError(formatZodIssues(result.error), options);
|
|
105
|
+
}
|
|
106
|
+
for (const [name, entry] of Object.entries(result.data.worksets)) {
|
|
107
|
+
if (!isKebabId(name)) {
|
|
108
|
+
throw invalidWorksetsFileError(`workset name '${name}' ${KEBAB_ID_DESCRIPTION}`, options);
|
|
109
|
+
}
|
|
110
|
+
const problem = memberListProblem(entry.members);
|
|
111
|
+
if (problem !== null) {
|
|
112
|
+
throw invalidWorksetsFileError(`workset '${name}': ${problem}`, options);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return result.data;
|
|
116
|
+
}
|
|
117
|
+
export function serializeWorksetsState(state, options = {}) {
|
|
118
|
+
const result = WorksetsStateSchema.safeParse(state);
|
|
119
|
+
if (!result.success) {
|
|
120
|
+
throw invalidWorksetsFileError(formatZodIssues(result.error), options);
|
|
121
|
+
}
|
|
122
|
+
// The strict schema already guarantees the entry shape; the sort is
|
|
123
|
+
// the only real work here.
|
|
124
|
+
return stringifyYaml({
|
|
125
|
+
version: 1,
|
|
126
|
+
worksets: Object.fromEntries(Object.entries(result.data.worksets).sort(([a], [b]) => a.localeCompare(b))),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** Absent file reads as the empty state; a corrupt file throws. */
|
|
130
|
+
export async function readWorksetsState(options = {}) {
|
|
131
|
+
const filePath = getWorksetsFilePath(options);
|
|
132
|
+
if (!(await pathIsFile(filePath))) {
|
|
133
|
+
return { version: 1, worksets: {} };
|
|
134
|
+
}
|
|
135
|
+
return parseWorksetsState(await fs.readFile(filePath, 'utf-8'), options);
|
|
136
|
+
}
|
|
137
|
+
const worksetsLockError = makeLockErrorFactory({
|
|
138
|
+
createSubject: 'the worksets lock file',
|
|
139
|
+
busyMessage: 'The worksets file is busy.',
|
|
140
|
+
code: 'workset_file_busy',
|
|
141
|
+
target: 'workset.file',
|
|
142
|
+
});
|
|
143
|
+
export async function updateWorksetsState(updater, options = {}) {
|
|
144
|
+
return withWorksetsLock(async (state) => {
|
|
145
|
+
const next = await updater(state);
|
|
146
|
+
await writeFileAtomically(getWorksetsFilePath(options), serializeWorksetsState(next, options));
|
|
147
|
+
return next;
|
|
148
|
+
}, options);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Lock-scoped read without a write-back of the saved-views file.
|
|
152
|
+
* `open` uses this to read the state and regenerate the derived
|
|
153
|
+
* .code-workspace coherently; the lock is released before any spawn.
|
|
154
|
+
*/
|
|
155
|
+
export async function withWorksetsLock(fn, options = {}) {
|
|
156
|
+
const lockPath = `${getWorksetsFilePath(options)}.lock`;
|
|
157
|
+
const lock = await acquireFileLock({
|
|
158
|
+
lockPath,
|
|
159
|
+
errorFor: worksetsLockError,
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
return await fn(await readWorksetsState(options));
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
await releaseFileLock(lock, lockPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export function worksetNotFoundError(name, state) {
|
|
169
|
+
const savedNames = Object.keys(state.worksets).sort((a, b) => a.localeCompare(b));
|
|
170
|
+
return new StoreError(`Workset '${name}' is not saved on this machine.`, 'workset_not_found', {
|
|
171
|
+
target: 'workset.name',
|
|
172
|
+
fix: savedNames.length > 0
|
|
173
|
+
? `Saved worksets: ${savedNames.join(', ')}. See them with: openspec workset list`
|
|
174
|
+
: `Create it first: openspec workset create ${name}`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
export function withWorkset(state, workset) {
|
|
178
|
+
if (state.worksets[workset.name] !== undefined) {
|
|
179
|
+
throw new StoreError(`Workset '${workset.name}' already exists.`, 'workset_exists', {
|
|
180
|
+
target: 'workset.name',
|
|
181
|
+
fix: `Choose another name, or remove it first: openspec workset remove ${workset.name}`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
version: 1,
|
|
186
|
+
worksets: {
|
|
187
|
+
...state.worksets,
|
|
188
|
+
[workset.name]: {
|
|
189
|
+
...(workset.tool !== undefined ? { tool: workset.tool } : {}),
|
|
190
|
+
members: workset.members,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export function withoutWorkset(state, name) {
|
|
196
|
+
if (state.worksets[name] === undefined) {
|
|
197
|
+
throw worksetNotFoundError(name, state);
|
|
198
|
+
}
|
|
199
|
+
const remaining = { ...state.worksets };
|
|
200
|
+
delete remaining[name];
|
|
201
|
+
return { version: 1, worksets: remaining };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Removes a saved workset and its derived .code-workspace under one
|
|
205
|
+
* lock. The derived-file cleanup runs AFTER the durable write (a
|
|
206
|
+
* failed write must not have already destroyed the artifact); a
|
|
207
|
+
* never-opened workset has no file - ENOENT is fine.
|
|
208
|
+
*/
|
|
209
|
+
export async function removeWorkset(name, options = {}) {
|
|
210
|
+
await withWorksetsLock(async (state) => {
|
|
211
|
+
const next = withoutWorkset(state, name);
|
|
212
|
+
await writeFileAtomically(getWorksetsFilePath(options), serializeWorksetsState(next, options));
|
|
213
|
+
await fs.rm(getWorksetCodeWorkspacePath(name, options), { force: true });
|
|
214
|
+
}, options);
|
|
215
|
+
}
|
|
216
|
+
function toWorkset(name, entry) {
|
|
217
|
+
return {
|
|
218
|
+
name,
|
|
219
|
+
...(entry.tool !== undefined ? { tool: entry.tool } : {}),
|
|
220
|
+
members: entry.members,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
export function listWorksets(state) {
|
|
224
|
+
return Object.entries(state.worksets)
|
|
225
|
+
.map(([name, entry]) => toWorkset(name, entry))
|
|
226
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
227
|
+
}
|
|
228
|
+
export function getWorkset(state, name) {
|
|
229
|
+
const entry = state.worksets[name];
|
|
230
|
+
return entry === undefined ? null : toWorkset(name, entry);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The generated .code-workspace content: members in saved order with
|
|
234
|
+
* their saved names, absolute paths, two-space JSON, trailing newline
|
|
235
|
+
* (the working-set builder's conventions).
|
|
236
|
+
*/
|
|
237
|
+
export function buildWorksetCodeWorkspaceJson(members) {
|
|
238
|
+
return (JSON.stringify({
|
|
239
|
+
folders: members.map((member) => ({
|
|
240
|
+
name: member.name,
|
|
241
|
+
path: member.path,
|
|
242
|
+
})),
|
|
243
|
+
}, null, 2) + '\n');
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=worksets.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One rendering for zod issues across every state/config parser. */
|
|
2
|
+
export function formatZodIssues(error, fallbackLocation = 'root') {
|
|
3
|
+
return error.issues
|
|
4
|
+
.map((issue) => {
|
|
5
|
+
const location = issue.path.length > 0 ? issue.path.join('.') : fallbackLocation;
|
|
6
|
+
return `${location}: ${issue.message}`;
|
|
7
|
+
})
|
|
8
|
+
.join('; ');
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=zod-issues.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface Choice {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
configured?: boolean;
|
|
6
|
+
detected?: boolean;
|
|
7
|
+
configuredLabel?: string;
|
|
8
|
+
preSelected?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface Config {
|
|
11
|
+
message: string;
|
|
12
|
+
choices: Choice[];
|
|
13
|
+
pageSize?: number;
|
|
14
|
+
validate?: (selected: string[]) => boolean | string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A searchable multi-select prompt with visible search box,
|
|
18
|
+
* selected items display, and intuitive keyboard navigation.
|
|
19
|
+
*
|
|
20
|
+
* - Type to filter choices
|
|
21
|
+
* - ↑↓ to navigate
|
|
22
|
+
* - Space to toggle highlighted item selection
|
|
23
|
+
* - Backspace to remove last selected item (or delete search char)
|
|
24
|
+
* - Enter to confirm selections
|
|
25
|
+
*/
|
|
26
|
+
export declare function searchableMultiSelect(config: Config): Promise<string[]>;
|
|
27
|
+
export default searchableMultiSelect;
|
|
28
|
+
//# sourceMappingURL=searchable-multi-select.d.ts.map
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
/**
|
|
3
|
+
* Create the searchable multi-select prompt.
|
|
4
|
+
* Uses dynamic import to prevent pre-commit hook hangs (see #367).
|
|
5
|
+
*/
|
|
6
|
+
async function createSearchableMultiSelect() {
|
|
7
|
+
const { createPrompt, useState, useKeypress, useMemo, usePrefix, isEnterKey, isBackspaceKey, isUpKey, isDownKey, } = await import('@inquirer/core');
|
|
8
|
+
return createPrompt((config, done) => {
|
|
9
|
+
const { message, choices, pageSize = 15, validate } = config;
|
|
10
|
+
const [searchText, setSearchText] = useState('');
|
|
11
|
+
const [selectedValues, setSelectedValues] = useState(() => choices.filter(c => c.preSelected).map(c => c.value));
|
|
12
|
+
const [cursor, setCursor] = useState(0);
|
|
13
|
+
const [status, setStatus] = useState('idle');
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const prefix = usePrefix({ status });
|
|
16
|
+
// Filter choices by search
|
|
17
|
+
const filteredChoices = useMemo(() => {
|
|
18
|
+
if (!searchText.trim())
|
|
19
|
+
return choices;
|
|
20
|
+
const term = searchText.toLowerCase();
|
|
21
|
+
return choices.filter((c) => c.name.toLowerCase().includes(term) ||
|
|
22
|
+
c.value.toLowerCase().includes(term));
|
|
23
|
+
}, [searchText, choices]);
|
|
24
|
+
const selectedSet = useMemo(() => new Set(selectedValues), [selectedValues]);
|
|
25
|
+
const choiceMap = useMemo(() => new Map(choices.map((c) => [c.value, c])), [choices]);
|
|
26
|
+
useKeypress((key) => {
|
|
27
|
+
if (status === 'done')
|
|
28
|
+
return;
|
|
29
|
+
// Enter to confirm/submit
|
|
30
|
+
if (isEnterKey(key)) {
|
|
31
|
+
if (validate) {
|
|
32
|
+
const result = validate(selectedValues);
|
|
33
|
+
if (result !== true) {
|
|
34
|
+
setError(typeof result === 'string' ? result : 'Invalid');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
setStatus('done');
|
|
39
|
+
done(selectedValues);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Space to toggle selection
|
|
43
|
+
if (key.name === 'space') {
|
|
44
|
+
const choice = filteredChoices[cursor];
|
|
45
|
+
if (choice) {
|
|
46
|
+
if (selectedSet.has(choice.value)) {
|
|
47
|
+
setSelectedValues(selectedValues.filter(v => v !== choice.value));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
setSelectedValues([...selectedValues, choice.value]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Backspace to remove or delete search char
|
|
56
|
+
if (isBackspaceKey(key)) {
|
|
57
|
+
if (searchText === '' && selectedValues.length > 0) {
|
|
58
|
+
setSelectedValues(selectedValues.slice(0, -1));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
setSearchText(searchText.slice(0, -1));
|
|
62
|
+
setCursor(0);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Navigation
|
|
67
|
+
if (isUpKey(key)) {
|
|
68
|
+
setCursor(Math.max(0, cursor - 1));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (isDownKey(key)) {
|
|
72
|
+
setCursor(Math.min(filteredChoices.length - 1, cursor + 1));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Character input - handle printable characters
|
|
76
|
+
if (key.name && key.name.length === 1 && !key.ctrl) {
|
|
77
|
+
setSearchText(searchText + key.name);
|
|
78
|
+
setCursor(0);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// Render done state
|
|
82
|
+
if (status === 'done') {
|
|
83
|
+
const names = selectedValues
|
|
84
|
+
.map((v) => choiceMap.get(v)?.name ?? v)
|
|
85
|
+
.join(', ');
|
|
86
|
+
return `${prefix} ${chalk.bold(message)} ${chalk.cyan(names || '(none)')}`;
|
|
87
|
+
}
|
|
88
|
+
// Render active state
|
|
89
|
+
const lines = [];
|
|
90
|
+
lines.push(`${prefix} ${chalk.bold(message)}`);
|
|
91
|
+
// Selected chips
|
|
92
|
+
const chips = selectedValues.length > 0
|
|
93
|
+
? selectedValues
|
|
94
|
+
.map((v) => chalk.bgCyan.black(` ${choiceMap.get(v)?.name} `))
|
|
95
|
+
.join(' ')
|
|
96
|
+
: chalk.dim('(none selected)');
|
|
97
|
+
lines.push(` Selected: ${chips}`);
|
|
98
|
+
// Search box
|
|
99
|
+
lines.push(` Search: ${chalk.yellow('[')}${searchText || chalk.dim('type to filter')}${chalk.yellow(']')}`);
|
|
100
|
+
// Instructions
|
|
101
|
+
lines.push(` ${chalk.cyan('↑↓')} navigate • ${chalk.cyan('Space')} toggle • ${chalk.cyan('Backspace')} remove • ${chalk.cyan('Enter')} confirm`);
|
|
102
|
+
// List
|
|
103
|
+
if (filteredChoices.length === 0) {
|
|
104
|
+
lines.push(chalk.yellow(' No matches'));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Calculate pagination
|
|
108
|
+
const startIndex = Math.max(0, Math.min(cursor - Math.floor(pageSize / 2), filteredChoices.length - pageSize));
|
|
109
|
+
const endIndex = Math.min(startIndex + pageSize, filteredChoices.length);
|
|
110
|
+
const visibleChoices = filteredChoices.slice(startIndex, endIndex);
|
|
111
|
+
for (let i = 0; i < visibleChoices.length; i++) {
|
|
112
|
+
const item = visibleChoices[i];
|
|
113
|
+
const actualIndex = startIndex + i;
|
|
114
|
+
const isActive = actualIndex === cursor;
|
|
115
|
+
const selected = selectedSet.has(item.value);
|
|
116
|
+
const icon = selected ? chalk.green('◉') : chalk.dim('○');
|
|
117
|
+
const arrow = isActive ? chalk.cyan('›') : ' ';
|
|
118
|
+
const name = isActive ? chalk.cyan(item.name) : item.name;
|
|
119
|
+
const isRefresh = selected && item.configured;
|
|
120
|
+
const statusLabel = !selected
|
|
121
|
+
? item.configured
|
|
122
|
+
? ' (configured)'
|
|
123
|
+
: item.detected
|
|
124
|
+
? ' (detected)'
|
|
125
|
+
: ''
|
|
126
|
+
: '';
|
|
127
|
+
const suffix = selected
|
|
128
|
+
? chalk.dim(isRefresh ? ' (refresh)' : ' (selected)')
|
|
129
|
+
: chalk.dim(statusLabel);
|
|
130
|
+
lines.push(` ${arrow} ${icon} ${name}${suffix}`);
|
|
131
|
+
}
|
|
132
|
+
// Show pagination indicator if needed
|
|
133
|
+
if (filteredChoices.length > pageSize) {
|
|
134
|
+
const currentPage = Math.floor(cursor / pageSize) + 1;
|
|
135
|
+
const totalPages = Math.ceil(filteredChoices.length / pageSize);
|
|
136
|
+
lines.push(chalk.dim(` (${currentPage}/${totalPages})`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (error)
|
|
140
|
+
lines.push(chalk.red(` ${error}`));
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A searchable multi-select prompt with visible search box,
|
|
146
|
+
* selected items display, and intuitive keyboard navigation.
|
|
147
|
+
*
|
|
148
|
+
* - Type to filter choices
|
|
149
|
+
* - ↑↓ to navigate
|
|
150
|
+
* - Space to toggle highlighted item selection
|
|
151
|
+
* - Backspace to remove last selected item (or delete search char)
|
|
152
|
+
* - Enter to confirm selections
|
|
153
|
+
*/
|
|
154
|
+
export async function searchableMultiSelect(config) {
|
|
155
|
+
const prompt = await createSearchableMultiSelect();
|
|
156
|
+
return prompt(config);
|
|
157
|
+
}
|
|
158
|
+
export default searchableMultiSelect;
|
|
159
|
+
//# sourceMappingURL=searchable-multi-select.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const CONFIG_DIR_NAME = "openspec";
|
|
2
|
+
export declare const CONFIG_FILE_NAME = "config.json";
|
|
3
|
+
export interface TelemetryConfig {
|
|
4
|
+
anonymousId?: string;
|
|
5
|
+
noticeSeen?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface GlobalConfig {
|
|
8
|
+
telemetry?: TelemetryConfig;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get the path to the global config file.
|
|
13
|
+
* Follows XDG Base Directory Specification and platform conventions.
|
|
14
|
+
*
|
|
15
|
+
* - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set
|
|
16
|
+
* - Unix/macOS fallback: ~/.config/openspec/
|
|
17
|
+
* - Windows fallback: %APPDATA%/openspec/
|
|
18
|
+
*/
|
|
19
|
+
export declare function getConfigPath(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Read the global config file.
|
|
22
|
+
* Returns an empty object if the file doesn't exist.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readConfig(): Promise<GlobalConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* Write to the global config file.
|
|
27
|
+
* Preserves existing fields and merges in new values.
|
|
28
|
+
*/
|
|
29
|
+
export declare function writeConfig(updates: Partial<GlobalConfig>): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the telemetry config section.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getTelemetryConfig(): Promise<TelemetryConfig>;
|
|
34
|
+
/**
|
|
35
|
+
* Update the telemetry config section.
|
|
36
|
+
*/
|
|
37
|
+
export declare function updateTelemetryConfig(updates: Partial<TelemetryConfig>): Promise<void>;
|
|
38
|
+
//# sourceMappingURL=config.d.ts.map
|