@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,185 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { MarkdownParser } from './parsers/markdown-parser.js';
|
|
7
|
+
/**
|
|
8
|
+
* Get the most recent modification time of any file in a directory (recursive).
|
|
9
|
+
* Falls back to the directory's own mtime if no files are found.
|
|
10
|
+
*/
|
|
11
|
+
async function getLastModified(dirPath) {
|
|
12
|
+
let latest = null;
|
|
13
|
+
async function walk(dir) {
|
|
14
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(dir, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
await walk(fullPath);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const stat = await fs.stat(fullPath);
|
|
22
|
+
if (latest === null || stat.mtime > latest) {
|
|
23
|
+
latest = stat.mtime;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
await walk(dirPath);
|
|
29
|
+
// If no files found, use the directory's own modification time
|
|
30
|
+
if (latest === null) {
|
|
31
|
+
const dirStat = await fs.stat(dirPath);
|
|
32
|
+
return dirStat.mtime;
|
|
33
|
+
}
|
|
34
|
+
return latest;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Format a date as relative time (e.g., "2 hours ago", "3 days ago")
|
|
38
|
+
*/
|
|
39
|
+
function formatRelativeTime(date) {
|
|
40
|
+
const now = new Date();
|
|
41
|
+
const diffMs = now.getTime() - date.getTime();
|
|
42
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
43
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
44
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
45
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
46
|
+
if (diffDays > 30) {
|
|
47
|
+
return date.toLocaleDateString();
|
|
48
|
+
}
|
|
49
|
+
else if (diffDays > 0) {
|
|
50
|
+
return `${diffDays}d ago`;
|
|
51
|
+
}
|
|
52
|
+
else if (diffHours > 0) {
|
|
53
|
+
return `${diffHours}h ago`;
|
|
54
|
+
}
|
|
55
|
+
else if (diffMins > 0) {
|
|
56
|
+
return `${diffMins}m ago`;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
return 'just now';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export class ListCommand {
|
|
63
|
+
async execute(targetPath = '.', mode = 'changes', options = {}) {
|
|
64
|
+
const { sort = 'recent', json = false, root } = options;
|
|
65
|
+
if (mode === 'changes') {
|
|
66
|
+
const changesDir = path.join(targetPath, 'openspec', 'changes');
|
|
67
|
+
// Check if changes directory exists
|
|
68
|
+
try {
|
|
69
|
+
await fs.access(changesDir);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new Error("No OpenSpec changes directory found. Run 'openspec init' first.");
|
|
73
|
+
}
|
|
74
|
+
// Get all directories in changes (excluding archive)
|
|
75
|
+
const entries = await fs.readdir(changesDir, { withFileTypes: true });
|
|
76
|
+
const changeDirs = entries
|
|
77
|
+
.filter(entry => entry.isDirectory() && entry.name !== 'archive')
|
|
78
|
+
.map(entry => entry.name);
|
|
79
|
+
if (changeDirs.length === 0) {
|
|
80
|
+
if (json) {
|
|
81
|
+
console.log(JSON.stringify({ changes: [], ...(root ? { root } : {}) }, null, 2));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log('No active changes found.');
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Collect information about each change
|
|
89
|
+
const changes = [];
|
|
90
|
+
for (const changeDir of changeDirs) {
|
|
91
|
+
const progress = await getTaskProgressForChange(changesDir, changeDir);
|
|
92
|
+
const changePath = path.join(changesDir, changeDir);
|
|
93
|
+
const lastModified = await getLastModified(changePath);
|
|
94
|
+
changes.push({
|
|
95
|
+
name: changeDir,
|
|
96
|
+
completedTasks: progress.completed,
|
|
97
|
+
totalTasks: progress.total,
|
|
98
|
+
lastModified
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Sort by preference (default: recent first)
|
|
102
|
+
if (sort === 'recent') {
|
|
103
|
+
changes.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
changes.sort((a, b) => a.name.localeCompare(b.name));
|
|
107
|
+
}
|
|
108
|
+
// JSON output for programmatic use
|
|
109
|
+
if (json) {
|
|
110
|
+
const jsonOutput = changes.map(c => ({
|
|
111
|
+
name: c.name,
|
|
112
|
+
completedTasks: c.completedTasks,
|
|
113
|
+
totalTasks: c.totalTasks,
|
|
114
|
+
lastModified: c.lastModified.toISOString(),
|
|
115
|
+
status: c.totalTasks === 0 ? 'no-tasks' : c.completedTasks === c.totalTasks ? 'complete' : 'in-progress'
|
|
116
|
+
}));
|
|
117
|
+
console.log(JSON.stringify({ changes: jsonOutput, ...(root ? { root } : {}) }, null, 2));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Display results
|
|
121
|
+
console.log('Changes:');
|
|
122
|
+
const padding = ' ';
|
|
123
|
+
const nameWidth = Math.max(...changes.map(c => c.name.length));
|
|
124
|
+
for (const change of changes) {
|
|
125
|
+
const paddedName = change.name.padEnd(nameWidth);
|
|
126
|
+
const status = formatTaskStatus({ total: change.totalTasks, completed: change.completedTasks });
|
|
127
|
+
const timeAgo = formatRelativeTime(change.lastModified);
|
|
128
|
+
console.log(`${padding}${paddedName} ${status.padEnd(12)} ${timeAgo}`);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// specs mode
|
|
133
|
+
const specsDir = path.join(targetPath, 'openspec', 'specs');
|
|
134
|
+
try {
|
|
135
|
+
await fs.access(specsDir);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
if (json) {
|
|
139
|
+
console.log(JSON.stringify({ specs: [], ...(root ? { root } : {}) }, null, 2));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.log('No specs found.');
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const entries = await fs.readdir(specsDir, { withFileTypes: true });
|
|
147
|
+
const specDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
148
|
+
if (specDirs.length === 0) {
|
|
149
|
+
if (json) {
|
|
150
|
+
console.log(JSON.stringify({ specs: [], ...(root ? { root } : {}) }, null, 2));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log('No specs found.');
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const specs = [];
|
|
158
|
+
for (const id of specDirs) {
|
|
159
|
+
const specPath = join(specsDir, id, 'spec.md');
|
|
160
|
+
try {
|
|
161
|
+
const content = readFileSync(specPath, 'utf-8');
|
|
162
|
+
const parser = new MarkdownParser(content);
|
|
163
|
+
const spec = parser.parseSpec(id);
|
|
164
|
+
specs.push({ id, requirementCount: spec.requirements.length });
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// If spec cannot be read or parsed, include with 0 count
|
|
168
|
+
specs.push({ id, requirementCount: 0 });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
specs.sort((a, b) => a.id.localeCompare(b.id));
|
|
172
|
+
if (json) {
|
|
173
|
+
console.log(JSON.stringify({ specs, ...(root ? { root } : {}) }, null, 2));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
console.log('Specs:');
|
|
177
|
+
const padding = ' ';
|
|
178
|
+
const nameWidth = Math.max(...specs.map(s => s.id.length));
|
|
179
|
+
for (const spec of specs) {
|
|
180
|
+
const padded = spec.id.padEnd(nameWidth);
|
|
181
|
+
console.log(`${padding}${padded} requirements ${spec.requirementCount}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration Utilities
|
|
3
|
+
*
|
|
4
|
+
* One-time migration logic for existing projects when profile system is introduced.
|
|
5
|
+
* Called by both init and update commands before profile resolution.
|
|
6
|
+
*/
|
|
7
|
+
import type { AIToolOption } from './config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Scans installed workflow files across all detected tools and returns
|
|
10
|
+
* the union of installed workflow IDs.
|
|
11
|
+
*/
|
|
12
|
+
export declare function scanInstalledWorkflows(projectPath: string, tools: AIToolOption[]): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Performs one-time migration if the global config does not yet have a profile field.
|
|
15
|
+
* Called by both init and update before profile resolution.
|
|
16
|
+
*
|
|
17
|
+
* - If no profile field exists and workflows are installed: sets profile to 'custom'
|
|
18
|
+
* with the detected workflows, preserving the user's existing setup.
|
|
19
|
+
* - If no profile field exists and no workflows are installed: no-op (defaults apply).
|
|
20
|
+
* - If profile field already exists: no-op.
|
|
21
|
+
*/
|
|
22
|
+
export declare function migrateIfNeeded(projectPath: string, tools: AIToolOption[]): void;
|
|
23
|
+
//# sourceMappingURL=migration.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration Utilities
|
|
3
|
+
*
|
|
4
|
+
* One-time migration logic for existing projects when profile system is introduced.
|
|
5
|
+
* Called by both init and update commands before profile resolution.
|
|
6
|
+
*/
|
|
7
|
+
import { getGlobalConfig, getGlobalConfigPath, saveGlobalConfig } from './global-config.js';
|
|
8
|
+
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
9
|
+
import { WORKFLOW_TO_SKILL_DIR } from './profile-sync-drift.js';
|
|
10
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import * as fs from 'fs';
|
|
13
|
+
function scanInstalledWorkflowArtifacts(projectPath, tools) {
|
|
14
|
+
const installed = new Set();
|
|
15
|
+
let hasSkills = false;
|
|
16
|
+
let hasCommands = false;
|
|
17
|
+
for (const tool of tools) {
|
|
18
|
+
if (!tool.skillsDir)
|
|
19
|
+
continue;
|
|
20
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
21
|
+
for (const workflowId of ALL_WORKFLOWS) {
|
|
22
|
+
const skillDirName = WORKFLOW_TO_SKILL_DIR[workflowId];
|
|
23
|
+
const skillFile = path.join(skillsDir, skillDirName, 'SKILL.md');
|
|
24
|
+
if (fs.existsSync(skillFile)) {
|
|
25
|
+
installed.add(workflowId);
|
|
26
|
+
hasSkills = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const adapter = CommandAdapterRegistry.get(tool.value);
|
|
30
|
+
if (!adapter)
|
|
31
|
+
continue;
|
|
32
|
+
for (const workflowId of ALL_WORKFLOWS) {
|
|
33
|
+
const commandPath = adapter.getFilePath(workflowId);
|
|
34
|
+
const fullPath = path.isAbsolute(commandPath)
|
|
35
|
+
? commandPath
|
|
36
|
+
: path.join(projectPath, commandPath);
|
|
37
|
+
if (fs.existsSync(fullPath)) {
|
|
38
|
+
installed.add(workflowId);
|
|
39
|
+
hasCommands = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
workflows: ALL_WORKFLOWS.filter((workflowId) => installed.has(workflowId)),
|
|
45
|
+
hasSkills,
|
|
46
|
+
hasCommands,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Scans installed workflow files across all detected tools and returns
|
|
51
|
+
* the union of installed workflow IDs.
|
|
52
|
+
*/
|
|
53
|
+
export function scanInstalledWorkflows(projectPath, tools) {
|
|
54
|
+
return scanInstalledWorkflowArtifacts(projectPath, tools).workflows;
|
|
55
|
+
}
|
|
56
|
+
function inferDelivery(artifacts) {
|
|
57
|
+
if (artifacts.hasSkills && artifacts.hasCommands) {
|
|
58
|
+
return 'both';
|
|
59
|
+
}
|
|
60
|
+
if (artifacts.hasCommands) {
|
|
61
|
+
return 'commands';
|
|
62
|
+
}
|
|
63
|
+
return 'skills';
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Performs one-time migration if the global config does not yet have a profile field.
|
|
67
|
+
* Called by both init and update before profile resolution.
|
|
68
|
+
*
|
|
69
|
+
* - If no profile field exists and workflows are installed: sets profile to 'custom'
|
|
70
|
+
* with the detected workflows, preserving the user's existing setup.
|
|
71
|
+
* - If no profile field exists and no workflows are installed: no-op (defaults apply).
|
|
72
|
+
* - If profile field already exists: no-op.
|
|
73
|
+
*/
|
|
74
|
+
export function migrateIfNeeded(projectPath, tools) {
|
|
75
|
+
const config = getGlobalConfig();
|
|
76
|
+
// Check raw config file for profile field presence
|
|
77
|
+
const configPath = getGlobalConfigPath();
|
|
78
|
+
let rawConfig = {};
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(configPath)) {
|
|
81
|
+
rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return; // Can't read config, skip migration
|
|
86
|
+
}
|
|
87
|
+
// If profile is already explicitly set, no migration needed
|
|
88
|
+
if (rawConfig.profile !== undefined) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Scan for installed workflows
|
|
92
|
+
const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools);
|
|
93
|
+
const installedWorkflows = artifacts.workflows;
|
|
94
|
+
if (installedWorkflows.length === 0) {
|
|
95
|
+
// No workflows installed, new user — defaults will apply
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Migrate: set profile to custom with detected workflows
|
|
99
|
+
config.profile = 'custom';
|
|
100
|
+
config.workflows = installedWorkflows;
|
|
101
|
+
if (rawConfig.delivery === undefined) {
|
|
102
|
+
config.delivery = inferDelivery(artifacts);
|
|
103
|
+
}
|
|
104
|
+
saveGlobalConfig(config);
|
|
105
|
+
console.log(`Migrated: custom profile with ${installedWorkflows.length} workflows`);
|
|
106
|
+
console.log("New in this version: /opsx:propose. Try 'openspec config profile core' for the streamlined experience.");
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { WorksetMember } from './worksets.js';
|
|
2
|
+
/**
|
|
3
|
+
* The workset opener table (slice 7.1). Supporting a new tool is
|
|
4
|
+
* configuration, not code: every tool is an instance of one of exactly
|
|
5
|
+
* two launch styles - 'workspace-file' (invoke with the generated
|
|
6
|
+
* .code-workspace) or 'attach-dirs' (pre-args plus one attach flag per
|
|
7
|
+
* member; no positional, ever - agent sessions open clean). Users add
|
|
8
|
+
* tools or adjust parameters under the global config file's `openers`
|
|
9
|
+
* key (the git difftool/mergetool pattern).
|
|
10
|
+
*/
|
|
11
|
+
export type OpenerStyle = 'workspace-file' | 'attach-dirs';
|
|
12
|
+
export interface OpenerDefinition {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
style: OpenerStyle;
|
|
16
|
+
command: string;
|
|
17
|
+
/** Pre-args before any attach flags or the workspace-file path. */
|
|
18
|
+
args: string[];
|
|
19
|
+
/** attach-dirs only; one flag + path pair per member. */
|
|
20
|
+
attachFlag: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Temporary kill-switch (2026-06): worksets open only in IDE-style
|
|
24
|
+
* ('workspace-file') tools while the CLI-agent ('attach-dirs') open flow
|
|
25
|
+
* is reworked. The agents (Claude Code, codex) launch in a single primary
|
|
26
|
+
* cwd rather than a true combined multi-root view, which makes "where does
|
|
27
|
+
* my change land?" ambiguous. Default off; set
|
|
28
|
+
* OPENSPEC_ENABLE_CLI_AGENT_OPENERS=1 to restore them (internal rollback seam).
|
|
29
|
+
*/
|
|
30
|
+
export declare function isCliAgentOpenersEnabled(): boolean;
|
|
31
|
+
/** Whether a tool can be opened right now (CLI-agent styles are gated). */
|
|
32
|
+
export declare function isOpenerEnabled(opener: OpenerDefinition): boolean;
|
|
33
|
+
export declare const BUILTIN_OPENERS: readonly OpenerDefinition[];
|
|
34
|
+
export declare function mergeOpenerTable(rawOpeners: unknown, configPath: string): OpenerDefinition[];
|
|
35
|
+
export interface OpenerScanOptions {
|
|
36
|
+
env?: NodeJS.ProcessEnv;
|
|
37
|
+
platform?: NodeJS.Platform;
|
|
38
|
+
/** Stat seam for tests (win32 candidate paths on posix hosts). */
|
|
39
|
+
isExecutableFile?: (candidatePath: string) => boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* PATH availability scan (ported from the deleted workspace openers
|
|
43
|
+
* at f858c19^, sharpened for injectability: the path module is keyed
|
|
44
|
+
* by the injected platform, and a command already carrying a known
|
|
45
|
+
* executable extension matches as-is).
|
|
46
|
+
*/
|
|
47
|
+
export declare function isOpenerCommandAvailable(command: string, options?: OpenerScanOptions): boolean;
|
|
48
|
+
export interface OpenerChoice {
|
|
49
|
+
opener: OpenerDefinition;
|
|
50
|
+
available: boolean;
|
|
51
|
+
/** `(<command> not found on PATH)` when unavailable. */
|
|
52
|
+
note: string | null;
|
|
53
|
+
}
|
|
54
|
+
/** Table order preserved, available tools first (stable sort). */
|
|
55
|
+
export declare function listOpenerChoices(table: OpenerDefinition[], options?: OpenerScanOptions): OpenerChoice[];
|
|
56
|
+
export declare function findOpener(table: OpenerDefinition[], id: string): OpenerDefinition | null;
|
|
57
|
+
export interface LaunchCommand {
|
|
58
|
+
executable: string;
|
|
59
|
+
args: string[];
|
|
60
|
+
/** The surviving primary member's path. */
|
|
61
|
+
cwd: string;
|
|
62
|
+
label: string;
|
|
63
|
+
style: OpenerStyle;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Pure argv builder. workspace-file: pre-args + the generated file's
|
|
67
|
+
* absolute path (which also defuses the cursor shim's `agent`
|
|
68
|
+
* first-arg hijack). attach-dirs: pre-args + one attach flag + path
|
|
69
|
+
* pair per surviving member, the primary included (the locked "one
|
|
70
|
+
* attach flag per member"); never a trailing positional - both agent
|
|
71
|
+
* CLIs would read one as a starter prompt, which 7.1 locks out.
|
|
72
|
+
*/
|
|
73
|
+
export declare function buildLaunchCommand(opener: OpenerDefinition, input: {
|
|
74
|
+
members: WorksetMember[];
|
|
75
|
+
codeWorkspacePath: string;
|
|
76
|
+
}): LaunchCommand;
|
|
77
|
+
//# sourceMappingURL=openers.d.ts.map
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import * as nodeFs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { StoreError } from './store/errors.js';
|
|
6
|
+
import { formatZodIssues } from './zod-issues.js';
|
|
7
|
+
const DEFAULT_ATTACH_FLAG = '--add-dir';
|
|
8
|
+
/**
|
|
9
|
+
* Temporary kill-switch (2026-06): worksets open only in IDE-style
|
|
10
|
+
* ('workspace-file') tools while the CLI-agent ('attach-dirs') open flow
|
|
11
|
+
* is reworked. The agents (Claude Code, codex) launch in a single primary
|
|
12
|
+
* cwd rather than a true combined multi-root view, which makes "where does
|
|
13
|
+
* my change land?" ambiguous. Default off; set
|
|
14
|
+
* OPENSPEC_ENABLE_CLI_AGENT_OPENERS=1 to restore them (internal rollback seam).
|
|
15
|
+
*/
|
|
16
|
+
export function isCliAgentOpenersEnabled() {
|
|
17
|
+
return process.env.OPENSPEC_ENABLE_CLI_AGENT_OPENERS === '1';
|
|
18
|
+
}
|
|
19
|
+
/** Whether a tool can be opened right now (CLI-agent styles are gated). */
|
|
20
|
+
export function isOpenerEnabled(opener) {
|
|
21
|
+
return isCliAgentOpenersEnabled() || opener.style !== 'attach-dirs';
|
|
22
|
+
}
|
|
23
|
+
export const BUILTIN_OPENERS = [
|
|
24
|
+
{
|
|
25
|
+
id: 'code',
|
|
26
|
+
label: 'VS Code',
|
|
27
|
+
style: 'workspace-file',
|
|
28
|
+
command: 'code',
|
|
29
|
+
args: [],
|
|
30
|
+
attachFlag: DEFAULT_ATTACH_FLAG,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'cursor',
|
|
34
|
+
label: 'Cursor',
|
|
35
|
+
style: 'workspace-file',
|
|
36
|
+
command: 'cursor',
|
|
37
|
+
args: [],
|
|
38
|
+
attachFlag: DEFAULT_ATTACH_FLAG,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'claude',
|
|
42
|
+
label: 'Claude Code',
|
|
43
|
+
style: 'attach-dirs',
|
|
44
|
+
command: 'claude',
|
|
45
|
+
args: [],
|
|
46
|
+
attachFlag: DEFAULT_ATTACH_FLAG,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'codex',
|
|
50
|
+
label: 'codex',
|
|
51
|
+
style: 'attach-dirs',
|
|
52
|
+
command: 'codex',
|
|
53
|
+
args: ['--sandbox', 'workspace-write'],
|
|
54
|
+
attachFlag: DEFAULT_ATTACH_FLAG,
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
const OPENER_STYLES = ['workspace-file', 'attach-dirs'];
|
|
58
|
+
const OpenerConfigRowSchema = z
|
|
59
|
+
.object({
|
|
60
|
+
style: z.enum(OPENER_STYLES).optional(),
|
|
61
|
+
label: z.string().min(1).optional(),
|
|
62
|
+
command: z.string().min(1).optional(),
|
|
63
|
+
args: z.array(z.string()).optional(),
|
|
64
|
+
attach_flag: z.string().min(1).optional(),
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
const OpenersConfigSchema = z.record(z.string(), OpenerConfigRowSchema);
|
|
68
|
+
function invalidOpenerConfigError(message, configPath) {
|
|
69
|
+
return new StoreError(`Invalid openers config: ${message}`, 'invalid_opener_config', {
|
|
70
|
+
target: 'openers.config',
|
|
71
|
+
fix: `Each entry under "openers" in ${configPath} may set style ('workspace-file' or 'attach-dirs'), label, command, args, and attach_flag; new tools must set style.`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Merges the global config file's raw `openers` value over the
|
|
76
|
+
* built-in table. A row keyed by a built-in id overrides only the
|
|
77
|
+
* fields it sets; a new id adds a tool (style required, command and
|
|
78
|
+
* label default to the id). Malformed rows fail typed - never
|
|
79
|
+
* silently ignored.
|
|
80
|
+
*/
|
|
81
|
+
function cloneOpener(opener) {
|
|
82
|
+
return { ...opener, args: [...opener.args] };
|
|
83
|
+
}
|
|
84
|
+
export function mergeOpenerTable(rawOpeners, configPath) {
|
|
85
|
+
if (rawOpeners === undefined || rawOpeners === null) {
|
|
86
|
+
return BUILTIN_OPENERS.map(cloneOpener);
|
|
87
|
+
}
|
|
88
|
+
const result = OpenersConfigSchema.safeParse(rawOpeners);
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
throw invalidOpenerConfigError(formatZodIssues(result.error, 'openers'), configPath);
|
|
91
|
+
}
|
|
92
|
+
const table = BUILTIN_OPENERS.map(cloneOpener);
|
|
93
|
+
for (const [id, row] of Object.entries(result.data)) {
|
|
94
|
+
const builtinIndex = table.findIndex((opener) => opener.id === id);
|
|
95
|
+
if (builtinIndex >= 0) {
|
|
96
|
+
const builtin = table[builtinIndex];
|
|
97
|
+
table[builtinIndex] = {
|
|
98
|
+
...builtin,
|
|
99
|
+
...(row.style !== undefined ? { style: row.style } : {}),
|
|
100
|
+
...(row.label !== undefined ? { label: row.label } : {}),
|
|
101
|
+
...(row.command !== undefined ? { command: row.command } : {}),
|
|
102
|
+
...(row.args !== undefined ? { args: row.args } : {}),
|
|
103
|
+
...(row.attach_flag !== undefined
|
|
104
|
+
? { attachFlag: row.attach_flag }
|
|
105
|
+
: {}),
|
|
106
|
+
};
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (row.style === undefined) {
|
|
110
|
+
throw invalidOpenerConfigError(`'${id}' adds a new tool and must set style ('workspace-file' or 'attach-dirs')`, configPath);
|
|
111
|
+
}
|
|
112
|
+
table.push({
|
|
113
|
+
id,
|
|
114
|
+
label: row.label ?? id,
|
|
115
|
+
style: row.style,
|
|
116
|
+
command: row.command ?? id,
|
|
117
|
+
args: row.args ?? [],
|
|
118
|
+
attachFlag: row.attach_flag ?? DEFAULT_ATTACH_FLAG,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return table;
|
|
122
|
+
}
|
|
123
|
+
function getPathValue(env) {
|
|
124
|
+
return env.PATH ?? env.Path ?? env.path ?? '';
|
|
125
|
+
}
|
|
126
|
+
function getPathExtensions(platform, env) {
|
|
127
|
+
if (platform !== 'win32') {
|
|
128
|
+
return [''];
|
|
129
|
+
}
|
|
130
|
+
return (env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD')
|
|
131
|
+
.split(';')
|
|
132
|
+
.map((extension) => extension.trim())
|
|
133
|
+
.filter((extension) => extension.length > 0);
|
|
134
|
+
}
|
|
135
|
+
function defaultIsExecutableFile(candidatePath, platform) {
|
|
136
|
+
try {
|
|
137
|
+
if (!nodeFs.statSync(candidatePath).isFile()) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (platform === 'win32') {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
nodeFs.accessSync(candidatePath, nodeFs.constants.X_OK);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* PATH availability scan (ported from the deleted workspace openers
|
|
157
|
+
* at f858c19^, sharpened for injectability: the path module is keyed
|
|
158
|
+
* by the injected platform, and a command already carrying a known
|
|
159
|
+
* executable extension matches as-is).
|
|
160
|
+
*/
|
|
161
|
+
export function isOpenerCommandAvailable(command, options = {}) {
|
|
162
|
+
const env = options.env ?? process.env;
|
|
163
|
+
const platform = options.platform ?? os.platform();
|
|
164
|
+
const pathModule = platform === 'win32' ? path.win32 : path.posix;
|
|
165
|
+
const isExecutable = options.isExecutableFile ??
|
|
166
|
+
((candidate) => defaultIsExecutableFile(candidate, platform));
|
|
167
|
+
const extensions = getPathExtensions(platform, env);
|
|
168
|
+
const lowerCommand = command.toLowerCase();
|
|
169
|
+
const carriesKnownExtension = extensions.some((extension) => extension.length > 0 && lowerCommand.endsWith(extension.toLowerCase()));
|
|
170
|
+
// One suffix policy: a command already carrying a known executable
|
|
171
|
+
// extension matches as-is and never gets a second extension appended
|
|
172
|
+
// - agreeing with spawn-time resolution.
|
|
173
|
+
const suffixes = carriesKnownExtension ? [''] : extensions;
|
|
174
|
+
if (/[\\/]/u.test(command)) {
|
|
175
|
+
// Direct paths additionally match bare even on win32 (the spawn
|
|
176
|
+
// call receives the literal path).
|
|
177
|
+
const directSuffixes = Array.from(new Set(['', ...suffixes]));
|
|
178
|
+
return directSuffixes.some((suffix) => isExecutable(command + suffix));
|
|
179
|
+
}
|
|
180
|
+
for (const directory of getPathValue(env).split(pathModule.delimiter)) {
|
|
181
|
+
if (directory.length === 0) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (suffixes.some((suffix) => isExecutable(pathModule.join(directory, command + suffix)))) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
/** Table order preserved, available tools first (stable sort). */
|
|
191
|
+
export function listOpenerChoices(table, options = {}) {
|
|
192
|
+
return table
|
|
193
|
+
.filter((opener) => isOpenerEnabled(opener))
|
|
194
|
+
.map((opener) => {
|
|
195
|
+
const available = isOpenerCommandAvailable(opener.command, options);
|
|
196
|
+
return {
|
|
197
|
+
opener,
|
|
198
|
+
available,
|
|
199
|
+
note: available ? null : `(${opener.command} not found on PATH)`,
|
|
200
|
+
};
|
|
201
|
+
})
|
|
202
|
+
.sort((a, b) => {
|
|
203
|
+
if (a.available === b.available) {
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
return a.available ? -1 : 1;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
export function findOpener(table, id) {
|
|
210
|
+
return table.find((opener) => opener.id === id) ?? null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Pure argv builder. workspace-file: pre-args + the generated file's
|
|
214
|
+
* absolute path (which also defuses the cursor shim's `agent`
|
|
215
|
+
* first-arg hijack). attach-dirs: pre-args + one attach flag + path
|
|
216
|
+
* pair per surviving member, the primary included (the locked "one
|
|
217
|
+
* attach flag per member"); never a trailing positional - both agent
|
|
218
|
+
* CLIs would read one as a starter prompt, which 7.1 locks out.
|
|
219
|
+
*/
|
|
220
|
+
export function buildLaunchCommand(opener, input) {
|
|
221
|
+
if (input.members.length === 0) {
|
|
222
|
+
throw new Error('buildLaunchCommand requires at least one member.');
|
|
223
|
+
}
|
|
224
|
+
// The no-hijack and no-positional guarantees lean on absolute paths
|
|
225
|
+
// (the child resolves relative argv against its own cwd) - keep the
|
|
226
|
+
// invariant local instead of three modules away.
|
|
227
|
+
if (!path.isAbsolute(input.codeWorkspacePath)) {
|
|
228
|
+
throw new Error(`buildLaunchCommand requires an absolute workspace-file path (got '${input.codeWorkspacePath}').`);
|
|
229
|
+
}
|
|
230
|
+
const cwd = input.members[0].path;
|
|
231
|
+
if (opener.style === 'workspace-file') {
|
|
232
|
+
return {
|
|
233
|
+
executable: opener.command,
|
|
234
|
+
args: [...opener.args, input.codeWorkspacePath],
|
|
235
|
+
cwd,
|
|
236
|
+
label: opener.label,
|
|
237
|
+
style: opener.style,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
executable: opener.command,
|
|
242
|
+
args: [
|
|
243
|
+
...opener.args,
|
|
244
|
+
...input.members.flatMap((member) => [opener.attachFlag, member.path]),
|
|
245
|
+
],
|
|
246
|
+
cwd,
|
|
247
|
+
label: opener.label,
|
|
248
|
+
style: opener.style,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=openers.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type StoreDiagnostic } from './store/errors.js';
|
|
2
|
+
export declare const OPENSPEC_ROOT_DIR = "openspec";
|
|
3
|
+
export declare const OPENSPEC_CONFIG_YAML = "openspec/config.yaml";
|
|
4
|
+
export declare const OPENSPEC_CONFIG_YML = "openspec/config.yml";
|
|
5
|
+
export declare const OPENSPEC_SPECS_DIR = "openspec/specs";
|
|
6
|
+
export declare const OPENSPEC_CHANGES_DIR = "openspec/changes";
|
|
7
|
+
export declare const OPENSPEC_ARCHIVE_DIR = "openspec/changes/archive";
|
|
8
|
+
export declare const DEFAULT_OPENSPEC_SCHEMA = "spec-driven";
|
|
9
|
+
export declare const DIRECTORY_ANCHOR_FILE_NAME = ".gitkeep";
|
|
10
|
+
export declare const ANCHORED_OPENSPEC_DIRS: readonly ["openspec/specs", "openspec/changes/archive"];
|
|
11
|
+
export interface CreatedPathLedgerEntry {
|
|
12
|
+
relativePath: string;
|
|
13
|
+
absolutePath: string;
|
|
14
|
+
kind: 'directory' | 'file';
|
|
15
|
+
}
|
|
16
|
+
export interface OpenSpecRootInspection {
|
|
17
|
+
present: boolean | null;
|
|
18
|
+
config: {
|
|
19
|
+
present: boolean | null;
|
|
20
|
+
path?: string;
|
|
21
|
+
};
|
|
22
|
+
specs: {
|
|
23
|
+
present: boolean | null;
|
|
24
|
+
};
|
|
25
|
+
changes: {
|
|
26
|
+
present: boolean | null;
|
|
27
|
+
};
|
|
28
|
+
archive: {
|
|
29
|
+
present: boolean | null;
|
|
30
|
+
};
|
|
31
|
+
healthy: boolean;
|
|
32
|
+
diagnostics: StoreDiagnostic[];
|
|
33
|
+
}
|
|
34
|
+
export interface EnsureOpenSpecRootResult {
|
|
35
|
+
inspection: OpenSpecRootInspection;
|
|
36
|
+
createdArtifacts: string[];
|
|
37
|
+
createdPaths: CreatedPathLedgerEntry[];
|
|
38
|
+
}
|
|
39
|
+
export declare function inspectOpenSpecRoot(storeRoot: string): Promise<OpenSpecRootInspection>;
|
|
40
|
+
export interface EnsureOpenSpecRootOptions {
|
|
41
|
+
anchorEmptyDirectories?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function ensureOpenSpecRoot(storeRoot: string, options?: EnsureOpenSpecRootOptions): Promise<EnsureOpenSpecRootResult>;
|
|
44
|
+
export declare function rollbackCreatedPaths(entries: CreatedPathLedgerEntry[]): Promise<void>;
|
|
45
|
+
//# sourceMappingURL=openspec-root.d.ts.map
|