@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,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile System
|
|
3
|
+
*
|
|
4
|
+
* Defines workflow profiles that control which workflows are installed.
|
|
5
|
+
* Profiles determine WHICH workflows; delivery (in global config) determines HOW.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Core workflows included in the 'core' profile.
|
|
9
|
+
* These provide the streamlined experience for new users.
|
|
10
|
+
*/
|
|
11
|
+
export const CORE_WORKFLOWS = ['propose', 'explore', 'apply', 'sync', 'archive'];
|
|
12
|
+
/**
|
|
13
|
+
* All available workflows in the system.
|
|
14
|
+
*/
|
|
15
|
+
export const ALL_WORKFLOWS = [
|
|
16
|
+
'propose',
|
|
17
|
+
'explore',
|
|
18
|
+
'new',
|
|
19
|
+
'continue',
|
|
20
|
+
'apply',
|
|
21
|
+
'ff',
|
|
22
|
+
'sync',
|
|
23
|
+
'archive',
|
|
24
|
+
'bulk-archive',
|
|
25
|
+
'verify',
|
|
26
|
+
'onboard',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Resolves which workflows should be active for a given profile configuration.
|
|
30
|
+
*
|
|
31
|
+
* - 'core' profile always returns CORE_WORKFLOWS
|
|
32
|
+
* - 'custom' profile returns the provided customWorkflows, or empty array if not provided
|
|
33
|
+
*/
|
|
34
|
+
export function getProfileWorkflows(profile, customWorkflows) {
|
|
35
|
+
if (profile === 'custom') {
|
|
36
|
+
return customWorkflows ?? [];
|
|
37
|
+
}
|
|
38
|
+
return CORE_WORKFLOWS;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for project configuration.
|
|
4
|
+
*
|
|
5
|
+
* Purpose:
|
|
6
|
+
* 1. Documentation - clearly defines the config file structure
|
|
7
|
+
* 2. Type safety - TypeScript infers ProjectConfig type from schema
|
|
8
|
+
* 3. Runtime validation - uses safeParse() for resilient field-by-field validation
|
|
9
|
+
*
|
|
10
|
+
* Why Zod over manual validation:
|
|
11
|
+
* - Helps understand OpenSpec's data interfaces at a glance
|
|
12
|
+
* - Single source of truth for type and validation
|
|
13
|
+
* - Consistent with other OpenSpec schemas
|
|
14
|
+
*/
|
|
15
|
+
export declare const ProjectConfigSchema: z.ZodObject<{
|
|
16
|
+
schema: z.ZodString;
|
|
17
|
+
context: z.ZodOptional<z.ZodString>;
|
|
18
|
+
rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
19
|
+
store: z.ZodOptional<z.ZodString>;
|
|
20
|
+
comprehension: z.ZodOptional<z.ZodObject<{
|
|
21
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
22
|
+
threshold_percent: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
min_questions: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
max_questions: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
}, z.core.$strip>>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
/** Normalized in-memory shape of a referenced store declaration. */
|
|
28
|
+
export interface DeclarationEntry {
|
|
29
|
+
id: string;
|
|
30
|
+
/** Clone source rendered into onboarding fixes. */
|
|
31
|
+
remote?: string;
|
|
32
|
+
}
|
|
33
|
+
export type ProjectConfig = z.infer<typeof ProjectConfigSchema> & {
|
|
34
|
+
references?: DeclarationEntry[];
|
|
35
|
+
/** Normalized comprehension settings (camelCase in memory). */
|
|
36
|
+
comprehension?: {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
thresholdPercent?: number;
|
|
39
|
+
minQuestions?: number;
|
|
40
|
+
maxQuestions?: number;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export declare const MAX_CONTEXT_SIZE: number;
|
|
44
|
+
/**
|
|
45
|
+
* Read and parse openspec/config.yaml from project root.
|
|
46
|
+
* Uses resilient parsing - validates each field independently using Zod safeParse.
|
|
47
|
+
* Returns null if file doesn't exist.
|
|
48
|
+
* Returns partial config if some fields are invalid (with warnings).
|
|
49
|
+
*
|
|
50
|
+
* Performance note (Jan 2025):
|
|
51
|
+
* Benchmarks showed direct file reads are fast enough without caching:
|
|
52
|
+
* - Typical config (1KB): ~0.5ms per read
|
|
53
|
+
* - Large config (50KB): ~1.6ms per read
|
|
54
|
+
* - Missing config: ~0.01ms per read
|
|
55
|
+
* Config is read 1-2 times per command (schema resolution + instruction loading),
|
|
56
|
+
* adding ~1-3ms total overhead. Caching would add complexity (mtime checks,
|
|
57
|
+
* invalidation logic) for negligible benefit. Direct reads also ensure config
|
|
58
|
+
* changes are reflected immediately without stale cache issues.
|
|
59
|
+
*
|
|
60
|
+
* @param projectRoot - The root directory of the project (where `openspec/` lives)
|
|
61
|
+
* @returns Parsed config or null if file doesn't exist
|
|
62
|
+
*/
|
|
63
|
+
export declare function readProjectConfig(projectRoot: string): ProjectConfig | null;
|
|
64
|
+
/**
|
|
65
|
+
* Validate artifact IDs in rules against a schema's artifacts.
|
|
66
|
+
* Called during instruction loading (when schema is known).
|
|
67
|
+
* Returns warnings for unknown artifact IDs.
|
|
68
|
+
*
|
|
69
|
+
* @param rules - The rules object from config
|
|
70
|
+
* @param validArtifactIds - Set of valid artifact IDs from the schema
|
|
71
|
+
* @param schemaName - Name of the schema for error messages
|
|
72
|
+
* @returns Array of warning messages for unknown artifact IDs
|
|
73
|
+
*/
|
|
74
|
+
export declare function validateConfigRules(rules: Record<string, string[]>, validArtifactIds: Set<string>, schemaName: string): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Suggest valid schema names when user provides invalid schema.
|
|
77
|
+
* Uses fuzzy matching to find similar names.
|
|
78
|
+
*
|
|
79
|
+
* @param invalidSchemaName - The invalid schema name from config
|
|
80
|
+
* @param availableSchemas - List of available schemas with their type (built-in or project-local)
|
|
81
|
+
* @returns Error message with suggestions and available schemas
|
|
82
|
+
*/
|
|
83
|
+
export declare function suggestSchemas(invalidSchemaName: string, availableSchemas: {
|
|
84
|
+
name: string;
|
|
85
|
+
isBuiltIn: boolean;
|
|
86
|
+
}[]): string;
|
|
87
|
+
export interface StorePointerRead {
|
|
88
|
+
/** The declared store id, when present and a string. */
|
|
89
|
+
value?: string;
|
|
90
|
+
/** Set when the pointer cannot be trusted: the config file could not be
|
|
91
|
+
* read as YAML, or the store key is present but not a string. An empty
|
|
92
|
+
* or comments-only config is NOT malformed - it simply has no pointer. */
|
|
93
|
+
malformed?: 'unparseable' | 'non_string';
|
|
94
|
+
/** Absolute path of the config file actually read, or null when none exists. */
|
|
95
|
+
filePath: string | null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Warning-silent targeted read of the `store:` pointer. Used by root
|
|
99
|
+
* resolution (which must not re-emit the resilient parser's field
|
|
100
|
+
* warnings) and by `openspec init`'s pointer guard. Unlike
|
|
101
|
+
* `readProjectConfig`, a malformed value is REPORTED, not dropped —
|
|
102
|
+
* a dropped pointer would silently flip where work lands.
|
|
103
|
+
*/
|
|
104
|
+
export declare function readStorePointer(projectRoot: string): StorePointerRead;
|
|
105
|
+
/** Shared .yaml/.yml probe used by readProjectConfig and readStorePointer. */
|
|
106
|
+
export declare function resolveConfigFilePath(projectRoot: string): string | null;
|
|
107
|
+
/** Human rendering of a malformed pointer reason, shared by every surface. */
|
|
108
|
+
export declare function storePointerProblem(reason: 'unparseable' | 'non_string'): string;
|
|
109
|
+
export interface OpenSpecDirClassification {
|
|
110
|
+
/** True when openspec/specs or openspec/changes exists as a directory. */
|
|
111
|
+
hasPlanningShape: boolean;
|
|
112
|
+
pointer: StorePointerRead;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* One classification for "real root vs config-only pointer dir", shared
|
|
116
|
+
* by root resolution and the init pointer guard so they can never
|
|
117
|
+
* disagree (slice 3.2).
|
|
118
|
+
*/
|
|
119
|
+
export declare function classifyOpenSpecDir(projectRoot: string): OpenSpecDirClassification;
|
|
120
|
+
//# sourceMappingURL=project-config.d.ts.map
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for project configuration.
|
|
7
|
+
*
|
|
8
|
+
* Purpose:
|
|
9
|
+
* 1. Documentation - clearly defines the config file structure
|
|
10
|
+
* 2. Type safety - TypeScript infers ProjectConfig type from schema
|
|
11
|
+
* 3. Runtime validation - uses safeParse() for resilient field-by-field validation
|
|
12
|
+
*
|
|
13
|
+
* Why Zod over manual validation:
|
|
14
|
+
* - Helps understand OpenSpec's data interfaces at a glance
|
|
15
|
+
* - Single source of truth for type and validation
|
|
16
|
+
* - Consistent with other OpenSpec schemas
|
|
17
|
+
*/
|
|
18
|
+
export const ProjectConfigSchema = z.object({
|
|
19
|
+
// Required: which schema to use (e.g., "spec-driven", or project-local schema name)
|
|
20
|
+
schema: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.describe('The workflow schema to use (e.g., "spec-driven")'),
|
|
24
|
+
// Optional: project context (injected into all artifact instructions)
|
|
25
|
+
// Max size: 50KB (enforced during parsing)
|
|
26
|
+
context: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Project context injected into all artifact instructions'),
|
|
30
|
+
// Optional: per-artifact rules (additive to schema's built-in guidance)
|
|
31
|
+
rules: z
|
|
32
|
+
.record(z.string(), // artifact ID
|
|
33
|
+
z.array(z.string()) // list of rules
|
|
34
|
+
)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Per-artifact rules, keyed by artifact ID'),
|
|
37
|
+
// Note: the `references` field (id strings or {id, remote} maps) is
|
|
38
|
+
// deliberately absent here — readProjectConfig parses and normalizes
|
|
39
|
+
// it by hand (see DeclarationEntry below); a schema entry nothing
|
|
40
|
+
// parses would only drift from the real behavior.
|
|
41
|
+
// Optional: the declared default store. Only consulted by root
|
|
42
|
+
// resolution when this openspec/ directory is config-only (no specs/
|
|
43
|
+
// or changes/); a fallback, never an override.
|
|
44
|
+
store: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Store id used as the OpenSpec root when no local planning shape exists'),
|
|
48
|
+
// Optional: apply comprehension quiz gate (default enabled when absent)
|
|
49
|
+
comprehension: z
|
|
50
|
+
.object({
|
|
51
|
+
enabled: z.boolean().optional(),
|
|
52
|
+
threshold_percent: z.number().int().min(0).max(100).optional(),
|
|
53
|
+
min_questions: z.number().int().positive().optional(),
|
|
54
|
+
max_questions: z.number().int().positive().optional(),
|
|
55
|
+
})
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Comprehension quiz gate before apply'),
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Parser for `references:` declarations: string entries or
|
|
61
|
+
* {id, remote} maps, normalized to DeclarationEntry[]. Dedup keys on
|
|
62
|
+
* id and keeps the first position; the first entry carrying a remote
|
|
63
|
+
* supplies it (a later duplicate fills a missing remote, never
|
|
64
|
+
* overrides). Invalid entries drop with a warning like other resilient
|
|
65
|
+
* fields; returns undefined when the field is absent or normalizes to
|
|
66
|
+
* empty.
|
|
67
|
+
*/
|
|
68
|
+
function parseDeclarationList(raw) {
|
|
69
|
+
const fieldName = 'references';
|
|
70
|
+
if (raw === undefined) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
if (!Array.isArray(raw)) {
|
|
74
|
+
console.warn(`Invalid '${fieldName}' field in config (must be an array of store ids)`);
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
const byId = new Map();
|
|
78
|
+
let droppedEntries = false;
|
|
79
|
+
let droppedRemotes = false;
|
|
80
|
+
for (const entry of raw) {
|
|
81
|
+
let declaration = null;
|
|
82
|
+
if (typeof entry === 'string') {
|
|
83
|
+
declaration = { id: entry };
|
|
84
|
+
}
|
|
85
|
+
else if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
86
|
+
const candidate = entry;
|
|
87
|
+
if (typeof candidate.id === 'string') {
|
|
88
|
+
declaration = { id: candidate.id };
|
|
89
|
+
if (typeof candidate.remote === 'string' && candidate.remote.length > 0) {
|
|
90
|
+
declaration.remote = candidate.remote;
|
|
91
|
+
}
|
|
92
|
+
else if (candidate.remote !== undefined) {
|
|
93
|
+
droppedRemotes = true; // remote dropped, id kept
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!declaration) {
|
|
98
|
+
droppedEntries = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const existing = byId.get(declaration.id);
|
|
102
|
+
if (!existing) {
|
|
103
|
+
byId.set(declaration.id, declaration);
|
|
104
|
+
}
|
|
105
|
+
else if (existing.remote === undefined && declaration.remote !== undefined) {
|
|
106
|
+
existing.remote = declaration.remote;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (droppedEntries) {
|
|
110
|
+
console.warn(`Some '${fieldName}' entries are invalid, ignoring them`);
|
|
111
|
+
}
|
|
112
|
+
if (droppedRemotes) {
|
|
113
|
+
console.warn(`Some '${fieldName}' remotes are not non-empty strings; the ids are kept without a clone source`);
|
|
114
|
+
}
|
|
115
|
+
return byId.size > 0 ? [...byId.values()] : undefined;
|
|
116
|
+
}
|
|
117
|
+
export const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit, shared with the references index
|
|
118
|
+
/**
|
|
119
|
+
* Read and parse openspec/config.yaml from project root.
|
|
120
|
+
* Uses resilient parsing - validates each field independently using Zod safeParse.
|
|
121
|
+
* Returns null if file doesn't exist.
|
|
122
|
+
* Returns partial config if some fields are invalid (with warnings).
|
|
123
|
+
*
|
|
124
|
+
* Performance note (Jan 2025):
|
|
125
|
+
* Benchmarks showed direct file reads are fast enough without caching:
|
|
126
|
+
* - Typical config (1KB): ~0.5ms per read
|
|
127
|
+
* - Large config (50KB): ~1.6ms per read
|
|
128
|
+
* - Missing config: ~0.01ms per read
|
|
129
|
+
* Config is read 1-2 times per command (schema resolution + instruction loading),
|
|
130
|
+
* adding ~1-3ms total overhead. Caching would add complexity (mtime checks,
|
|
131
|
+
* invalidation logic) for negligible benefit. Direct reads also ensure config
|
|
132
|
+
* changes are reflected immediately without stale cache issues.
|
|
133
|
+
*
|
|
134
|
+
* @param projectRoot - The root directory of the project (where `openspec/` lives)
|
|
135
|
+
* @returns Parsed config or null if file doesn't exist
|
|
136
|
+
*/
|
|
137
|
+
export function readProjectConfig(projectRoot) {
|
|
138
|
+
const configPath = resolveConfigFilePath(projectRoot);
|
|
139
|
+
if (configPath === null) {
|
|
140
|
+
return null; // No config is OK
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
144
|
+
const raw = parseYaml(content);
|
|
145
|
+
if (!raw || typeof raw !== 'object') {
|
|
146
|
+
console.warn(`openspec/config.yaml is not a valid YAML object`);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const config = {};
|
|
150
|
+
// Parse schema field using Zod
|
|
151
|
+
const schemaField = z.string().min(1);
|
|
152
|
+
const schemaResult = schemaField.safeParse(raw.schema);
|
|
153
|
+
if (schemaResult.success) {
|
|
154
|
+
config.schema = schemaResult.data;
|
|
155
|
+
}
|
|
156
|
+
else if (raw.schema !== undefined) {
|
|
157
|
+
console.warn(`Invalid 'schema' field in config (must be non-empty string)`);
|
|
158
|
+
}
|
|
159
|
+
// Parse context field with size limit
|
|
160
|
+
if (raw.context !== undefined) {
|
|
161
|
+
const contextField = z.string();
|
|
162
|
+
const contextResult = contextField.safeParse(raw.context);
|
|
163
|
+
if (contextResult.success) {
|
|
164
|
+
const contextSize = Buffer.byteLength(contextResult.data, 'utf-8');
|
|
165
|
+
if (contextSize > MAX_CONTEXT_SIZE) {
|
|
166
|
+
console.warn(`Context too large (${(contextSize / 1024).toFixed(1)}KB, limit: ${MAX_CONTEXT_SIZE / 1024}KB)`);
|
|
167
|
+
console.warn(`Ignoring context field`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
config.context = contextResult.data;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
console.warn(`Invalid 'context' field in config (must be string)`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Parse rules field using Zod
|
|
178
|
+
if (raw.rules !== undefined) {
|
|
179
|
+
const rulesField = z.record(z.string(), z.array(z.string()));
|
|
180
|
+
// First check if it's an object structure (guard against null since typeof null === 'object')
|
|
181
|
+
if (typeof raw.rules === 'object' && raw.rules !== null && !Array.isArray(raw.rules)) {
|
|
182
|
+
const parsedRules = {};
|
|
183
|
+
let hasValidRules = false;
|
|
184
|
+
for (const [artifactId, rules] of Object.entries(raw.rules)) {
|
|
185
|
+
const rulesArrayResult = z.array(z.string()).safeParse(rules);
|
|
186
|
+
if (rulesArrayResult.success) {
|
|
187
|
+
// Filter out empty strings
|
|
188
|
+
const validRules = rulesArrayResult.data.filter((r) => r.length > 0);
|
|
189
|
+
if (validRules.length > 0) {
|
|
190
|
+
parsedRules[artifactId] = validRules;
|
|
191
|
+
hasValidRules = true;
|
|
192
|
+
}
|
|
193
|
+
if (validRules.length < rulesArrayResult.data.length) {
|
|
194
|
+
console.warn(`Some rules for '${artifactId}' are empty strings, ignoring them`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
console.warn(`Rules for '${artifactId}' must be an array of strings, ignoring this artifact's rules`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (hasValidRules) {
|
|
202
|
+
config.rules = parsedRules;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.warn(`Invalid 'rules' field in config (must be object)`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const references = parseDeclarationList(raw.references);
|
|
210
|
+
if (references) {
|
|
211
|
+
config.references = references;
|
|
212
|
+
}
|
|
213
|
+
// Parse store pointer field: a string, or dropped with a warning.
|
|
214
|
+
// (Root resolution does NOT use this parse — it uses readStorePointer
|
|
215
|
+
// below, which errors on malformed pointers instead of dropping.)
|
|
216
|
+
if (raw.store !== undefined) {
|
|
217
|
+
if (typeof raw.store === 'string') {
|
|
218
|
+
config.store = raw.store;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
console.warn(`Warning: ignoring invalid store: field in ${configPathForWarnings(projectRoot)} (must be a single store id string).`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (raw.comprehension !== undefined) {
|
|
225
|
+
const comprehensionField = z.object({
|
|
226
|
+
enabled: z.boolean().optional(),
|
|
227
|
+
threshold_percent: z.number().int().min(0).max(100).optional(),
|
|
228
|
+
min_questions: z.number().int().positive().optional(),
|
|
229
|
+
max_questions: z.number().int().positive().optional(),
|
|
230
|
+
});
|
|
231
|
+
const comprehensionResult = comprehensionField.safeParse(raw.comprehension);
|
|
232
|
+
if (comprehensionResult.success) {
|
|
233
|
+
const c = comprehensionResult.data;
|
|
234
|
+
config.comprehension = {
|
|
235
|
+
...(c.enabled !== undefined ? { enabled: c.enabled } : {}),
|
|
236
|
+
...(c.threshold_percent !== undefined
|
|
237
|
+
? { thresholdPercent: c.threshold_percent }
|
|
238
|
+
: {}),
|
|
239
|
+
...(c.min_questions !== undefined ? { minQuestions: c.min_questions } : {}),
|
|
240
|
+
...(c.max_questions !== undefined ? { maxQuestions: c.max_questions } : {}),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
console.warn(`Invalid 'comprehension' field in config (must be an object)`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Return partial config even if some fields failed
|
|
248
|
+
return Object.keys(config).length > 0 ? config : null;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.warn(`Warning: could not parse ${configPathForWarnings(projectRoot)} (${error instanceof Error ? error.message.split('\n')[0] : String(error)}); ignoring it.`);
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function configPathForWarnings(projectRoot) {
|
|
256
|
+
return resolveConfigFilePath(projectRoot) ?? path.join(projectRoot, 'openspec', 'config.yaml');
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Validate artifact IDs in rules against a schema's artifacts.
|
|
260
|
+
* Called during instruction loading (when schema is known).
|
|
261
|
+
* Returns warnings for unknown artifact IDs.
|
|
262
|
+
*
|
|
263
|
+
* @param rules - The rules object from config
|
|
264
|
+
* @param validArtifactIds - Set of valid artifact IDs from the schema
|
|
265
|
+
* @param schemaName - Name of the schema for error messages
|
|
266
|
+
* @returns Array of warning messages for unknown artifact IDs
|
|
267
|
+
*/
|
|
268
|
+
export function validateConfigRules(rules, validArtifactIds, schemaName) {
|
|
269
|
+
const warnings = [];
|
|
270
|
+
for (const artifactId of Object.keys(rules)) {
|
|
271
|
+
if (!validArtifactIds.has(artifactId)) {
|
|
272
|
+
const validIds = Array.from(validArtifactIds).sort().join(', ');
|
|
273
|
+
warnings.push(`Unknown artifact ID in rules: "${artifactId}". ` +
|
|
274
|
+
`Valid IDs for schema "${schemaName}": ${validIds}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return warnings;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Suggest valid schema names when user provides invalid schema.
|
|
281
|
+
* Uses fuzzy matching to find similar names.
|
|
282
|
+
*
|
|
283
|
+
* @param invalidSchemaName - The invalid schema name from config
|
|
284
|
+
* @param availableSchemas - List of available schemas with their type (built-in or project-local)
|
|
285
|
+
* @returns Error message with suggestions and available schemas
|
|
286
|
+
*/
|
|
287
|
+
export function suggestSchemas(invalidSchemaName, availableSchemas) {
|
|
288
|
+
// Simple fuzzy match: Levenshtein distance
|
|
289
|
+
function levenshtein(a, b) {
|
|
290
|
+
const matrix = [];
|
|
291
|
+
for (let i = 0; i <= b.length; i++) {
|
|
292
|
+
matrix[i] = [i];
|
|
293
|
+
}
|
|
294
|
+
for (let j = 0; j <= a.length; j++) {
|
|
295
|
+
matrix[0][j] = j;
|
|
296
|
+
}
|
|
297
|
+
for (let i = 1; i <= b.length; i++) {
|
|
298
|
+
for (let j = 1; j <= a.length; j++) {
|
|
299
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
300
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return matrix[b.length][a.length];
|
|
308
|
+
}
|
|
309
|
+
// Find closest matches (distance <= 3)
|
|
310
|
+
const suggestions = availableSchemas
|
|
311
|
+
.map((s) => ({ ...s, distance: levenshtein(invalidSchemaName, s.name) }))
|
|
312
|
+
.filter((s) => s.distance <= 3)
|
|
313
|
+
.sort((a, b) => a.distance - b.distance)
|
|
314
|
+
.slice(0, 3);
|
|
315
|
+
const builtIn = availableSchemas.filter((s) => s.isBuiltIn).map((s) => s.name);
|
|
316
|
+
const projectLocal = availableSchemas.filter((s) => !s.isBuiltIn).map((s) => s.name);
|
|
317
|
+
let message = `Schema '${invalidSchemaName}' not found in openspec/config.yaml\n\n`;
|
|
318
|
+
if (suggestions.length > 0) {
|
|
319
|
+
message += `Did you mean one of these?\n`;
|
|
320
|
+
suggestions.forEach((s) => {
|
|
321
|
+
const type = s.isBuiltIn ? 'built-in' : 'project-local';
|
|
322
|
+
message += ` - ${s.name} (${type})\n`;
|
|
323
|
+
});
|
|
324
|
+
message += '\n';
|
|
325
|
+
}
|
|
326
|
+
message += `Available schemas:\n`;
|
|
327
|
+
if (builtIn.length > 0) {
|
|
328
|
+
message += ` Built-in: ${builtIn.join(', ')}\n`;
|
|
329
|
+
}
|
|
330
|
+
if (projectLocal.length > 0) {
|
|
331
|
+
message += ` Project-local: ${projectLocal.join(', ')}\n`;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
message += ` Project-local: (none found)\n`;
|
|
335
|
+
}
|
|
336
|
+
message += `\nFix: Edit openspec/config.yaml and change 'schema: ${invalidSchemaName}' to a valid schema name`;
|
|
337
|
+
return message;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Warning-silent targeted read of the `store:` pointer. Used by root
|
|
341
|
+
* resolution (which must not re-emit the resilient parser's field
|
|
342
|
+
* warnings) and by `openspec init`'s pointer guard. Unlike
|
|
343
|
+
* `readProjectConfig`, a malformed value is REPORTED, not dropped —
|
|
344
|
+
* a dropped pointer would silently flip where work lands.
|
|
345
|
+
*/
|
|
346
|
+
export function readStorePointer(projectRoot) {
|
|
347
|
+
const configPath = resolveConfigFilePath(projectRoot);
|
|
348
|
+
if (configPath === null) {
|
|
349
|
+
return { filePath: null };
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const raw = parseYaml(readFileSync(configPath, 'utf-8'));
|
|
353
|
+
// Empty, comments-only, or non-mapping configs carry no pointer;
|
|
354
|
+
// they are imperfect, not malformed (readProjectConfig owns the
|
|
355
|
+
// field warnings for those).
|
|
356
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
357
|
+
return { filePath: configPath };
|
|
358
|
+
}
|
|
359
|
+
const value = raw.store;
|
|
360
|
+
if (value === undefined) {
|
|
361
|
+
return { filePath: configPath };
|
|
362
|
+
}
|
|
363
|
+
if (typeof value === 'string') {
|
|
364
|
+
return { value, filePath: configPath };
|
|
365
|
+
}
|
|
366
|
+
return { malformed: 'non_string', filePath: configPath };
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return { malformed: 'unparseable', filePath: configPath };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/** Shared .yaml/.yml probe used by readProjectConfig and readStorePointer. */
|
|
373
|
+
export function resolveConfigFilePath(projectRoot) {
|
|
374
|
+
const yamlPath = path.join(projectRoot, 'openspec', 'config.yaml');
|
|
375
|
+
if (existsSync(yamlPath)) {
|
|
376
|
+
return yamlPath;
|
|
377
|
+
}
|
|
378
|
+
const ymlPath = path.join(projectRoot, 'openspec', 'config.yml');
|
|
379
|
+
return existsSync(ymlPath) ? ymlPath : null;
|
|
380
|
+
}
|
|
381
|
+
/** Human rendering of a malformed pointer reason, shared by every surface. */
|
|
382
|
+
export function storePointerProblem(reason) {
|
|
383
|
+
return reason === 'unparseable'
|
|
384
|
+
? 'the config file could not be read as YAML'
|
|
385
|
+
: 'the store key must be a single store id string';
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* One classification for "real root vs config-only pointer dir", shared
|
|
389
|
+
* by root resolution and the init pointer guard so they can never
|
|
390
|
+
* disagree (slice 3.2).
|
|
391
|
+
*/
|
|
392
|
+
export function classifyOpenSpecDir(projectRoot) {
|
|
393
|
+
const openspecDir = path.join(projectRoot, 'openspec');
|
|
394
|
+
const hasPlanningShape = isDirectorySync(path.join(openspecDir, 'specs')) ||
|
|
395
|
+
isDirectorySync(path.join(openspecDir, 'changes'));
|
|
396
|
+
return { hasPlanningShape, pointer: readStorePointer(projectRoot) };
|
|
397
|
+
}
|
|
398
|
+
function isDirectorySync(candidatePath) {
|
|
399
|
+
try {
|
|
400
|
+
return statSync(candidatePath).isDirectory();
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=project-config.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type StoreDiagnostic } from './store/errors.js';
|
|
2
|
+
import { listStoreRegistryEntries } from './store/foundation.js';
|
|
3
|
+
import { type ResolvedOpenSpecRoot } from './root-selection.js';
|
|
4
|
+
import { type DeclarationEntry } from './project-config.js';
|
|
5
|
+
export interface ReferenceSpecEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ReferenceIndexEntry {
|
|
10
|
+
store_id: string;
|
|
11
|
+
root?: string;
|
|
12
|
+
specs?: ReferenceSpecEntry[];
|
|
13
|
+
fetch?: string;
|
|
14
|
+
status: StoreDiagnostic[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Tolerant first-Purpose-line extraction. parseSpec() throws on specs
|
|
18
|
+
* without Purpose/Requirements sections; the index must never fail on an
|
|
19
|
+
* imperfect upstream spec, so this scans for the heading directly —
|
|
20
|
+
* fence-aware, so `## Purpose` inside a code block never matches, and
|
|
21
|
+
* tolerant of CommonMark closing hashes (`## Purpose ##`).
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractFirstPurposeLine(markdown: string): string;
|
|
24
|
+
export declare function fetchRecipe(storeId: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Pure renderer for the artifact-instructions XML block. Also the byte
|
|
27
|
+
* budget's measuring stick (it is the larger rendering).
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderReferencedStoresBlock(entries: ReferenceIndexEntry[]): string;
|
|
30
|
+
/** Pure renderer for the apply-instructions markdown section. */
|
|
31
|
+
export declare function renderReferencedStoresSection(entries: ReferenceIndexEntry[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Strings rendered into agent guidance can come from cloned content
|
|
34
|
+
* (spec directory names, Purpose lines, config-declared remotes). One
|
|
35
|
+
* line in, one line out: control characters and newlines must never
|
|
36
|
+
* let hostile content forge instruction lines (slice 6.1 hardening).
|
|
37
|
+
*/
|
|
38
|
+
export declare function sanitizeInline(value: string, maxLength?: number): string;
|
|
39
|
+
export interface AssembleReferenceIndexInput {
|
|
40
|
+
references: DeclarationEntry[];
|
|
41
|
+
resolvedRoot: ResolvedOpenSpecRoot;
|
|
42
|
+
globalDataDir?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Health mode (3.6): false skips the spec-file reads AND the byte
|
|
45
|
+
* budget — entries carry no `specs`/`fetch` keys at all, and the
|
|
46
|
+
* content-only truncation diagnostic can never appear.
|
|
47
|
+
*/
|
|
48
|
+
includeSpecs?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Pre-read registry entries (3.6): `[]` = registry empty or absent,
|
|
51
|
+
* `null` = unreadable, undefined = read internally as before.
|
|
52
|
+
* (Mirrors the internal post-read variable — never inject a raw
|
|
53
|
+
* read result: a healthy-absent registry reads as null.)
|
|
54
|
+
*/
|
|
55
|
+
registryEntries?: ReturnType<typeof listStoreRegistryEntries> | null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds the referenced-store index. One registry read per call; one
|
|
59
|
+
* level deep (a referenced store's own references are never followed);
|
|
60
|
+
* self-references omitted; every failure degrades to a warning entry.
|
|
61
|
+
*/
|
|
62
|
+
export declare function assembleReferenceIndex(input: AssembleReferenceIndexInput): Promise<ReferenceIndexEntry[]>;
|
|
63
|
+
//# sourceMappingURL=references.d.ts.map
|