@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,37 @@
|
|
|
1
|
+
export interface RequirementBlock {
|
|
2
|
+
headerLine: string;
|
|
3
|
+
name: string;
|
|
4
|
+
raw: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RequirementsSectionParts {
|
|
7
|
+
before: string;
|
|
8
|
+
headerLine: string;
|
|
9
|
+
preamble: string;
|
|
10
|
+
bodyBlocks: RequirementBlock[];
|
|
11
|
+
after: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function normalizeRequirementName(name: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Extracts the Requirements section from a spec file and parses requirement blocks.
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractRequirementsSection(content: string): RequirementsSectionParts;
|
|
18
|
+
export interface DeltaPlan {
|
|
19
|
+
added: RequirementBlock[];
|
|
20
|
+
modified: RequirementBlock[];
|
|
21
|
+
removed: string[];
|
|
22
|
+
renamed: Array<{
|
|
23
|
+
from: string;
|
|
24
|
+
to: string;
|
|
25
|
+
}>;
|
|
26
|
+
sectionPresence: {
|
|
27
|
+
added: boolean;
|
|
28
|
+
modified: boolean;
|
|
29
|
+
removed: boolean;
|
|
30
|
+
renamed: boolean;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseDeltaSpec(content: string): DeltaPlan;
|
|
37
|
+
//# sourceMappingURL=requirement-blocks.d.ts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
export function normalizeRequirementName(name) {
|
|
2
|
+
return name.trim();
|
|
3
|
+
}
|
|
4
|
+
const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement:\s*(.+)\s*$/i;
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the Requirements section from a spec file and parses requirement blocks.
|
|
7
|
+
*/
|
|
8
|
+
export function extractRequirementsSection(content) {
|
|
9
|
+
const normalized = normalizeLineEndings(content);
|
|
10
|
+
const lines = normalized.split('\n');
|
|
11
|
+
const reqHeaderIndex = lines.findIndex(l => /^##\s+Requirements\s*$/i.test(l));
|
|
12
|
+
if (reqHeaderIndex === -1) {
|
|
13
|
+
// No requirements section; create an empty one at the end
|
|
14
|
+
const before = content.trimEnd();
|
|
15
|
+
const headerLine = '## Requirements';
|
|
16
|
+
return {
|
|
17
|
+
before: before ? before + '\n\n' : '',
|
|
18
|
+
headerLine,
|
|
19
|
+
preamble: '',
|
|
20
|
+
bodyBlocks: [],
|
|
21
|
+
after: '\n',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Find end of this section: next line that starts with '## ' at same or higher level
|
|
25
|
+
let endIndex = lines.length;
|
|
26
|
+
for (let i = reqHeaderIndex + 1; i < lines.length; i++) {
|
|
27
|
+
if (/^##\s+/.test(lines[i])) {
|
|
28
|
+
endIndex = i;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const before = lines.slice(0, reqHeaderIndex).join('\n');
|
|
33
|
+
const headerLine = lines[reqHeaderIndex];
|
|
34
|
+
const sectionBodyLines = lines.slice(reqHeaderIndex + 1, endIndex);
|
|
35
|
+
// Parse requirement blocks within section body
|
|
36
|
+
const blocks = [];
|
|
37
|
+
let cursor = 0;
|
|
38
|
+
let preambleLines = [];
|
|
39
|
+
// Collect preamble lines until first requirement header
|
|
40
|
+
while (cursor < sectionBodyLines.length && !REQUIREMENT_HEADER_REGEX.test(sectionBodyLines[cursor])) {
|
|
41
|
+
preambleLines.push(sectionBodyLines[cursor]);
|
|
42
|
+
cursor++;
|
|
43
|
+
}
|
|
44
|
+
while (cursor < sectionBodyLines.length) {
|
|
45
|
+
const headerStart = cursor;
|
|
46
|
+
const headerLineCandidate = sectionBodyLines[cursor];
|
|
47
|
+
const headerMatch = headerLineCandidate.match(REQUIREMENT_HEADER_REGEX);
|
|
48
|
+
if (!headerMatch) {
|
|
49
|
+
// Not a requirement header; skip line defensively
|
|
50
|
+
cursor++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const name = normalizeRequirementName(headerMatch[1]);
|
|
54
|
+
cursor++;
|
|
55
|
+
// Gather lines until next requirement header or end of section
|
|
56
|
+
const bodyLines = [headerLineCandidate];
|
|
57
|
+
while (cursor < sectionBodyLines.length && !REQUIREMENT_HEADER_REGEX.test(sectionBodyLines[cursor]) && !/^##\s+/.test(sectionBodyLines[cursor])) {
|
|
58
|
+
bodyLines.push(sectionBodyLines[cursor]);
|
|
59
|
+
cursor++;
|
|
60
|
+
}
|
|
61
|
+
const raw = bodyLines.join('\n').trimEnd();
|
|
62
|
+
blocks.push({ headerLine: headerLineCandidate, name, raw });
|
|
63
|
+
}
|
|
64
|
+
const after = lines.slice(endIndex).join('\n');
|
|
65
|
+
const preamble = preambleLines.join('\n').trimEnd();
|
|
66
|
+
return {
|
|
67
|
+
before: before.trimEnd() ? before + '\n' : before,
|
|
68
|
+
headerLine,
|
|
69
|
+
preamble,
|
|
70
|
+
bodyBlocks: blocks,
|
|
71
|
+
after: after.startsWith('\n') ? after : '\n' + after,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function normalizeLineEndings(content) {
|
|
75
|
+
return content.replace(/\r\n?/g, '\n');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse a delta-formatted spec change file content into a DeltaPlan with raw blocks.
|
|
79
|
+
*/
|
|
80
|
+
export function parseDeltaSpec(content) {
|
|
81
|
+
const normalized = normalizeLineEndings(content);
|
|
82
|
+
const sections = splitTopLevelSections(normalized);
|
|
83
|
+
const addedLookup = getSectionCaseInsensitive(sections, 'ADDED Requirements');
|
|
84
|
+
const modifiedLookup = getSectionCaseInsensitive(sections, 'MODIFIED Requirements');
|
|
85
|
+
const removedLookup = getSectionCaseInsensitive(sections, 'REMOVED Requirements');
|
|
86
|
+
const renamedLookup = getSectionCaseInsensitive(sections, 'RENAMED Requirements');
|
|
87
|
+
const added = parseRequirementBlocksFromSection(addedLookup.body);
|
|
88
|
+
const modified = parseRequirementBlocksFromSection(modifiedLookup.body);
|
|
89
|
+
const removedNames = parseRemovedNames(removedLookup.body);
|
|
90
|
+
const renamedPairs = parseRenamedPairs(renamedLookup.body);
|
|
91
|
+
return {
|
|
92
|
+
added,
|
|
93
|
+
modified,
|
|
94
|
+
removed: removedNames,
|
|
95
|
+
renamed: renamedPairs,
|
|
96
|
+
sectionPresence: {
|
|
97
|
+
added: addedLookup.found,
|
|
98
|
+
modified: modifiedLookup.found,
|
|
99
|
+
removed: removedLookup.found,
|
|
100
|
+
renamed: renamedLookup.found,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function splitTopLevelSections(content) {
|
|
105
|
+
const lines = content.split('\n');
|
|
106
|
+
const result = {};
|
|
107
|
+
const indices = [];
|
|
108
|
+
for (let i = 0; i < lines.length; i++) {
|
|
109
|
+
const m = lines[i].match(/^(##)\s+(.+)$/);
|
|
110
|
+
if (m) {
|
|
111
|
+
const level = m[1].length; // only care for '##'
|
|
112
|
+
indices.push({ title: m[2].trim(), index: i, level });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (let i = 0; i < indices.length; i++) {
|
|
116
|
+
const current = indices[i];
|
|
117
|
+
const next = indices[i + 1];
|
|
118
|
+
const body = lines.slice(current.index + 1, next ? next.index : lines.length).join('\n');
|
|
119
|
+
result[current.title] = body;
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function getSectionCaseInsensitive(sections, desired) {
|
|
124
|
+
const target = desired.toLowerCase();
|
|
125
|
+
for (const [title, body] of Object.entries(sections)) {
|
|
126
|
+
if (title.toLowerCase() === target)
|
|
127
|
+
return { body, found: true };
|
|
128
|
+
}
|
|
129
|
+
return { body: '', found: false };
|
|
130
|
+
}
|
|
131
|
+
function parseRequirementBlocksFromSection(sectionBody) {
|
|
132
|
+
if (!sectionBody)
|
|
133
|
+
return [];
|
|
134
|
+
const lines = normalizeLineEndings(sectionBody).split('\n');
|
|
135
|
+
const blocks = [];
|
|
136
|
+
let i = 0;
|
|
137
|
+
while (i < lines.length) {
|
|
138
|
+
// Seek next requirement header
|
|
139
|
+
while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i]))
|
|
140
|
+
i++;
|
|
141
|
+
if (i >= lines.length)
|
|
142
|
+
break;
|
|
143
|
+
const headerLine = lines[i];
|
|
144
|
+
const m = headerLine.match(REQUIREMENT_HEADER_REGEX);
|
|
145
|
+
if (!m) {
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const name = normalizeRequirementName(m[1]);
|
|
150
|
+
const buf = [headerLine];
|
|
151
|
+
i++;
|
|
152
|
+
while (i < lines.length && !REQUIREMENT_HEADER_REGEX.test(lines[i]) && !/^##\s+/.test(lines[i])) {
|
|
153
|
+
buf.push(lines[i]);
|
|
154
|
+
i++;
|
|
155
|
+
}
|
|
156
|
+
blocks.push({ headerLine, name, raw: buf.join('\n').trimEnd() });
|
|
157
|
+
}
|
|
158
|
+
return blocks;
|
|
159
|
+
}
|
|
160
|
+
function parseRemovedNames(sectionBody) {
|
|
161
|
+
if (!sectionBody)
|
|
162
|
+
return [];
|
|
163
|
+
const names = [];
|
|
164
|
+
const lines = normalizeLineEndings(sectionBody).split('\n');
|
|
165
|
+
for (const line of lines) {
|
|
166
|
+
const m = line.match(REQUIREMENT_HEADER_REGEX);
|
|
167
|
+
if (m) {
|
|
168
|
+
names.push(normalizeRequirementName(m[1]));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Also support bullet list of headers
|
|
172
|
+
const bullet = line.match(/^\s*-\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
|
|
173
|
+
if (bullet) {
|
|
174
|
+
names.push(normalizeRequirementName(bullet[1]));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return names;
|
|
178
|
+
}
|
|
179
|
+
function parseRenamedPairs(sectionBody) {
|
|
180
|
+
if (!sectionBody)
|
|
181
|
+
return [];
|
|
182
|
+
const pairs = [];
|
|
183
|
+
const lines = normalizeLineEndings(sectionBody).split('\n');
|
|
184
|
+
let current = {};
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
const fromMatch = line.match(/^\s*-?\s*FROM:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
|
|
187
|
+
const toMatch = line.match(/^\s*-?\s*TO:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
|
|
188
|
+
if (fromMatch) {
|
|
189
|
+
current.from = normalizeRequirementName(fromMatch[1]);
|
|
190
|
+
}
|
|
191
|
+
else if (toMatch) {
|
|
192
|
+
current.to = normalizeRequirementName(toMatch[1]);
|
|
193
|
+
if (current.from && current.to) {
|
|
194
|
+
pairs.push({ from: current.from, to: current.to });
|
|
195
|
+
current = {};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return pairs;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=requirement-blocks.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface MainSpecStructureIssue {
|
|
2
|
+
kind: 'delta-header' | 'requirement-outside-requirements';
|
|
3
|
+
line: number;
|
|
4
|
+
header: string;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function findMainSpecStructureIssues(content: string): MainSpecStructureIssue[];
|
|
8
|
+
export declare function stripFencedCodeBlocksPreservingLines(content: string): string;
|
|
9
|
+
//# sourceMappingURL=spec-structure.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const REQUIREMENTS_SECTION_HEADER = /^##\s+Requirements\s*$/i;
|
|
2
|
+
const TOP_LEVEL_SECTION_HEADER = /^##\s+/;
|
|
3
|
+
const DELTA_HEADER = /^##\s+(ADDED|MODIFIED|REMOVED|RENAMED)\s+Requirements\s*$/i;
|
|
4
|
+
const REQUIREMENT_HEADER = /^###\s+Requirement:\s*(.+)\s*$/i;
|
|
5
|
+
export function findMainSpecStructureIssues(content) {
|
|
6
|
+
const normalized = content.replace(/\r\n?/g, '\n');
|
|
7
|
+
const stripped = stripFencedCodeBlocksPreservingLines(normalized);
|
|
8
|
+
const lines = stripped.split('\n');
|
|
9
|
+
const issues = [];
|
|
10
|
+
const requirementsHeaderIndex = lines.findIndex(line => REQUIREMENTS_SECTION_HEADER.test(line));
|
|
11
|
+
let requirementsEndIndex = lines.length;
|
|
12
|
+
if (requirementsHeaderIndex !== -1) {
|
|
13
|
+
for (let i = requirementsHeaderIndex + 1; i < lines.length; i++) {
|
|
14
|
+
if (TOP_LEVEL_SECTION_HEADER.test(lines[i])) {
|
|
15
|
+
requirementsEndIndex = i;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (let i = 0; i < lines.length; i++) {
|
|
21
|
+
const line = lines[i];
|
|
22
|
+
const trimmed = line.trim();
|
|
23
|
+
if (!trimmed) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (DELTA_HEADER.test(line)) {
|
|
27
|
+
issues.push({
|
|
28
|
+
kind: 'delta-header',
|
|
29
|
+
line: i + 1,
|
|
30
|
+
header: trimmed,
|
|
31
|
+
message: `Main spec contains delta header "${trimmed}". ` +
|
|
32
|
+
'Delta headers are only valid inside openspec/changes/<name>/specs/<capability>/spec.md ' +
|
|
33
|
+
'and truncate the parsed ## Requirements section.',
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const requirementMatch = line.match(REQUIREMENT_HEADER);
|
|
38
|
+
if (!requirementMatch) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const insideRequirements = requirementsHeaderIndex !== -1 &&
|
|
42
|
+
i > requirementsHeaderIndex &&
|
|
43
|
+
i < requirementsEndIndex;
|
|
44
|
+
if (!insideRequirements) {
|
|
45
|
+
issues.push({
|
|
46
|
+
kind: 'requirement-outside-requirements',
|
|
47
|
+
line: i + 1,
|
|
48
|
+
header: trimmed,
|
|
49
|
+
message: `Requirement header "${trimmed}" appears outside the main ## Requirements section. ` +
|
|
50
|
+
'Main specs only parse requirements inside that section, so this requirement is currently invisible to validate, list, and archive.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return issues;
|
|
55
|
+
}
|
|
56
|
+
export function stripFencedCodeBlocksPreservingLines(content) {
|
|
57
|
+
const lines = content.split('\n');
|
|
58
|
+
const output = [];
|
|
59
|
+
let activeFence = null;
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const fenceMatch = line.match(/^\s*(`{3,}|~{3,})(.*)$/);
|
|
62
|
+
if (!activeFence) {
|
|
63
|
+
if (fenceMatch) {
|
|
64
|
+
activeFence = {
|
|
65
|
+
marker: fenceMatch[1][0],
|
|
66
|
+
length: fenceMatch[1].length,
|
|
67
|
+
};
|
|
68
|
+
output.push('');
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
output.push(line);
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
output.push('');
|
|
76
|
+
if (isClosingFence(line, activeFence)) {
|
|
77
|
+
activeFence = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return output.join('\n');
|
|
81
|
+
}
|
|
82
|
+
function isClosingFence(line, activeFence) {
|
|
83
|
+
const fenceMatch = line.match(/^\s*(`{3,}|~{3,})\s*$/);
|
|
84
|
+
return Boolean(fenceMatch &&
|
|
85
|
+
fenceMatch[1][0] === activeFence.marker &&
|
|
86
|
+
fenceMatch[1].length >= activeFence.length);
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=spec-structure.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type PlanningHomeKind = 'repo';
|
|
2
|
+
export interface PlanningHome {
|
|
3
|
+
kind: PlanningHomeKind;
|
|
4
|
+
root: string;
|
|
5
|
+
changesDir: string;
|
|
6
|
+
defaultSchema: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ResolvePlanningHomeOptions {
|
|
9
|
+
startPath?: string;
|
|
10
|
+
allowImplicitRepoRoot?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function findRepoPlanningRootSync(startPath?: string): string | null;
|
|
13
|
+
export declare function resolveCurrentPlanningHomeSync(options?: ResolvePlanningHomeOptions): PlanningHome;
|
|
14
|
+
export declare function getChangeDir(planningHome: PlanningHome, changeName: string): string;
|
|
15
|
+
export declare function formatChangeLocation(planningHome: PlanningHome, changeName: string): string;
|
|
16
|
+
//# sourceMappingURL=planning-home.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
4
|
+
const REPO_DEFAULT_SCHEMA = 'spec-driven';
|
|
5
|
+
function pathExistsAsDirectory(candidatePath) {
|
|
6
|
+
try {
|
|
7
|
+
return fs.statSync(candidatePath).isDirectory();
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function getSearchStartDirectory(startPath) {
|
|
14
|
+
const resolved = path.resolve(startPath);
|
|
15
|
+
try {
|
|
16
|
+
const stats = fs.statSync(resolved);
|
|
17
|
+
const searchStart = stats.isDirectory() ? resolved : path.dirname(resolved);
|
|
18
|
+
return FileSystemUtils.canonicalizeExistingPath(searchStart);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return resolved;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function findNearestAncestor(startPath, predicate) {
|
|
25
|
+
let currentDir = getSearchStartDirectory(startPath);
|
|
26
|
+
while (true) {
|
|
27
|
+
if (predicate(currentDir)) {
|
|
28
|
+
return FileSystemUtils.canonicalizeExistingPath(currentDir);
|
|
29
|
+
}
|
|
30
|
+
const parentDir = path.dirname(currentDir);
|
|
31
|
+
if (parentDir === currentDir) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
currentDir = parentDir;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function findRepoPlanningRootSync(startPath = process.cwd()) {
|
|
38
|
+
return findNearestAncestor(startPath, (dirPath) => pathExistsAsDirectory(path.join(dirPath, 'openspec')));
|
|
39
|
+
}
|
|
40
|
+
function repoPlanningHome(repoRoot) {
|
|
41
|
+
return {
|
|
42
|
+
kind: 'repo',
|
|
43
|
+
root: repoRoot,
|
|
44
|
+
changesDir: path.join(repoRoot, 'openspec', 'changes'),
|
|
45
|
+
defaultSchema: REPO_DEFAULT_SCHEMA,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function resolveCurrentPlanningHomeSync(options = {}) {
|
|
49
|
+
const startPath = options.startPath ?? process.cwd();
|
|
50
|
+
const searchStart = getSearchStartDirectory(startPath);
|
|
51
|
+
const repoRoot = findRepoPlanningRootSync(searchStart);
|
|
52
|
+
if (repoRoot) {
|
|
53
|
+
return repoPlanningHome(repoRoot);
|
|
54
|
+
}
|
|
55
|
+
if (options.allowImplicitRepoRoot === false) {
|
|
56
|
+
throw new Error('No OpenSpec planning home found from the current directory.');
|
|
57
|
+
}
|
|
58
|
+
return repoPlanningHome(FileSystemUtils.canonicalizeExistingPath(searchStart));
|
|
59
|
+
}
|
|
60
|
+
export function getChangeDir(planningHome, changeName) {
|
|
61
|
+
return FileSystemUtils.joinPath(planningHome.changesDir, changeName);
|
|
62
|
+
}
|
|
63
|
+
export function formatChangeLocation(planningHome, changeName) {
|
|
64
|
+
// Repo homes always nest changesDir under the root.
|
|
65
|
+
return path.relative(planningHome.root, getChangeDir(planningHome, changeName));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=planning-home.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Delivery } from './global-config.js';
|
|
2
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
3
|
+
type WorkflowId = (typeof ALL_WORKFLOWS)[number];
|
|
4
|
+
/**
|
|
5
|
+
* Maps workflow IDs to their skill directory names.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WORKFLOW_TO_SKILL_DIR: Record<WorkflowId, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Checks whether a tool has at least one generated OpenSpec command file.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toolHasAnyConfiguredCommand(projectPath: string, toolId: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Returns tools with at least one generated command file on disk.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCommandConfiguredTools(projectPath: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Returns tools that are configured via either skills or commands.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getConfiguredToolsForProfileSync(projectPath: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Detects if a single tool has profile/delivery drift against the desired state.
|
|
22
|
+
*
|
|
23
|
+
* This function covers:
|
|
24
|
+
* - required artifacts missing for selected workflows
|
|
25
|
+
* - artifacts that should not exist for the selected delivery mode
|
|
26
|
+
* - artifacts for workflows that were deselected from the current profile
|
|
27
|
+
*/
|
|
28
|
+
export declare function hasToolProfileOrDeliveryDrift(projectPath: string, toolId: string, desiredWorkflows: readonly string[], delivery: Delivery): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Returns configured tools that currently need a profile/delivery sync.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getToolsNeedingProfileSync(projectPath: string, desiredWorkflows: readonly string[], delivery: Delivery, configuredTools?: readonly string[]): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Detects whether the current project has any profile/delivery drift.
|
|
35
|
+
*/
|
|
36
|
+
export declare function hasProjectConfigDrift(projectPath: string, desiredWorkflows: readonly string[], delivery: Delivery): boolean;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=profile-sync-drift.d.ts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { AI_TOOLS } from './config.js';
|
|
4
|
+
import { ALL_WORKFLOWS } from './profiles.js';
|
|
5
|
+
import { CommandAdapterRegistry } from './command-generation/index.js';
|
|
6
|
+
import { COMMAND_IDS, getConfiguredTools } from './shared/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Maps workflow IDs to their skill directory names.
|
|
9
|
+
*/
|
|
10
|
+
export const WORKFLOW_TO_SKILL_DIR = {
|
|
11
|
+
'explore': 'openspec-explore',
|
|
12
|
+
'new': 'openspec-new-change',
|
|
13
|
+
'continue': 'openspec-continue-change',
|
|
14
|
+
'apply': 'openspec-apply-change',
|
|
15
|
+
'ff': 'openspec-ff-change',
|
|
16
|
+
'sync': 'openspec-sync-specs',
|
|
17
|
+
'archive': 'openspec-archive-change',
|
|
18
|
+
'bulk-archive': 'openspec-bulk-archive-change',
|
|
19
|
+
'verify': 'openspec-verify-change',
|
|
20
|
+
'onboard': 'openspec-onboard',
|
|
21
|
+
'propose': 'openspec-propose',
|
|
22
|
+
};
|
|
23
|
+
function toKnownWorkflows(workflows) {
|
|
24
|
+
return workflows.filter((workflow) => ALL_WORKFLOWS.includes(workflow));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks whether a tool has at least one generated OpenSpec command file.
|
|
28
|
+
*/
|
|
29
|
+
export function toolHasAnyConfiguredCommand(projectPath, toolId) {
|
|
30
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
31
|
+
if (!adapter)
|
|
32
|
+
return false;
|
|
33
|
+
for (const commandId of COMMAND_IDS) {
|
|
34
|
+
const cmdPath = adapter.getFilePath(commandId);
|
|
35
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
36
|
+
if (fs.existsSync(fullPath)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns tools with at least one generated command file on disk.
|
|
44
|
+
*/
|
|
45
|
+
export function getCommandConfiguredTools(projectPath) {
|
|
46
|
+
return AI_TOOLS
|
|
47
|
+
.filter((tool) => {
|
|
48
|
+
if (!tool.skillsDir)
|
|
49
|
+
return false;
|
|
50
|
+
const toolDir = path.join(projectPath, tool.skillsDir);
|
|
51
|
+
try {
|
|
52
|
+
return fs.statSync(toolDir).isDirectory();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
.map((tool) => tool.value)
|
|
59
|
+
.filter((toolId) => toolHasAnyConfiguredCommand(projectPath, toolId));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Returns tools that are configured via either skills or commands.
|
|
63
|
+
*/
|
|
64
|
+
export function getConfiguredToolsForProfileSync(projectPath) {
|
|
65
|
+
const skillConfigured = getConfiguredTools(projectPath);
|
|
66
|
+
const commandConfigured = getCommandConfiguredTools(projectPath);
|
|
67
|
+
return [...new Set([...skillConfigured, ...commandConfigured])];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Detects if a single tool has profile/delivery drift against the desired state.
|
|
71
|
+
*
|
|
72
|
+
* This function covers:
|
|
73
|
+
* - required artifacts missing for selected workflows
|
|
74
|
+
* - artifacts that should not exist for the selected delivery mode
|
|
75
|
+
* - artifacts for workflows that were deselected from the current profile
|
|
76
|
+
*/
|
|
77
|
+
export function hasToolProfileOrDeliveryDrift(projectPath, toolId, desiredWorkflows, delivery) {
|
|
78
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
79
|
+
if (!tool?.skillsDir)
|
|
80
|
+
return false;
|
|
81
|
+
const knownDesiredWorkflows = toKnownWorkflows(desiredWorkflows);
|
|
82
|
+
const desiredWorkflowSet = new Set(knownDesiredWorkflows);
|
|
83
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
84
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
85
|
+
const shouldGenerateSkills = delivery !== 'commands';
|
|
86
|
+
const shouldGenerateCommands = delivery !== 'skills';
|
|
87
|
+
if (shouldGenerateSkills) {
|
|
88
|
+
for (const workflow of knownDesiredWorkflows) {
|
|
89
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
90
|
+
const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
|
|
91
|
+
if (!fs.existsSync(skillFile)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Deselecting workflows in a profile should trigger sync.
|
|
96
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
97
|
+
if (desiredWorkflowSet.has(workflow))
|
|
98
|
+
continue;
|
|
99
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
100
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
101
|
+
if (fs.existsSync(skillDir)) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
108
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
109
|
+
const skillDir = path.join(skillsDir, dirName);
|
|
110
|
+
if (fs.existsSync(skillDir)) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (shouldGenerateCommands && adapter) {
|
|
116
|
+
for (const workflow of knownDesiredWorkflows) {
|
|
117
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
118
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
119
|
+
if (!fs.existsSync(fullPath)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Deselecting workflows in a profile should trigger sync.
|
|
124
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
125
|
+
if (desiredWorkflowSet.has(workflow))
|
|
126
|
+
continue;
|
|
127
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
128
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
129
|
+
if (fs.existsSync(fullPath)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (!shouldGenerateCommands && adapter) {
|
|
135
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
136
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
137
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
138
|
+
if (fs.existsSync(fullPath)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns configured tools that currently need a profile/delivery sync.
|
|
147
|
+
*/
|
|
148
|
+
export function getToolsNeedingProfileSync(projectPath, desiredWorkflows, delivery, configuredTools) {
|
|
149
|
+
const tools = configuredTools ? [...new Set(configuredTools)] : getConfiguredToolsForProfileSync(projectPath);
|
|
150
|
+
return tools.filter((toolId) => hasToolProfileOrDeliveryDrift(projectPath, toolId, desiredWorkflows, delivery));
|
|
151
|
+
}
|
|
152
|
+
function getInstalledWorkflowsForTool(projectPath, toolId, options) {
|
|
153
|
+
const tool = AI_TOOLS.find((t) => t.value === toolId);
|
|
154
|
+
if (!tool?.skillsDir)
|
|
155
|
+
return [];
|
|
156
|
+
const installed = new Set();
|
|
157
|
+
const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
|
|
158
|
+
if (options.includeSkills) {
|
|
159
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
160
|
+
const dirName = WORKFLOW_TO_SKILL_DIR[workflow];
|
|
161
|
+
const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
|
|
162
|
+
if (fs.existsSync(skillFile)) {
|
|
163
|
+
installed.add(workflow);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (options.includeCommands) {
|
|
168
|
+
const adapter = CommandAdapterRegistry.get(toolId);
|
|
169
|
+
if (adapter) {
|
|
170
|
+
for (const workflow of ALL_WORKFLOWS) {
|
|
171
|
+
const cmdPath = adapter.getFilePath(workflow);
|
|
172
|
+
const fullPath = path.isAbsolute(cmdPath) ? cmdPath : path.join(projectPath, cmdPath);
|
|
173
|
+
if (fs.existsSync(fullPath)) {
|
|
174
|
+
installed.add(workflow);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return [...installed];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Detects whether the current project has any profile/delivery drift.
|
|
183
|
+
*/
|
|
184
|
+
export function hasProjectConfigDrift(projectPath, desiredWorkflows, delivery) {
|
|
185
|
+
const configuredTools = getConfiguredToolsForProfileSync(projectPath);
|
|
186
|
+
if (getToolsNeedingProfileSync(projectPath, desiredWorkflows, delivery, configuredTools).length > 0) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
const desiredSet = new Set(toKnownWorkflows(desiredWorkflows));
|
|
190
|
+
const includeSkills = delivery !== 'commands';
|
|
191
|
+
const includeCommands = delivery !== 'skills';
|
|
192
|
+
for (const toolId of configuredTools) {
|
|
193
|
+
const installed = getInstalledWorkflowsForTool(projectPath, toolId, { includeSkills, includeCommands });
|
|
194
|
+
if (installed.some((workflow) => !desiredSet.has(workflow))) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=profile-sync-drift.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
import type { Profile } from './global-config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Core workflows included in the 'core' profile.
|
|
10
|
+
* These provide the streamlined experience for new users.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CORE_WORKFLOWS: readonly ["propose", "explore", "apply", "sync", "archive"];
|
|
13
|
+
/**
|
|
14
|
+
* All available workflows in the system.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "ff", "sync", "archive", "bulk-archive", "verify", "onboard"];
|
|
17
|
+
export type WorkflowId = (typeof ALL_WORKFLOWS)[number];
|
|
18
|
+
export type CoreWorkflowId = (typeof CORE_WORKFLOWS)[number];
|
|
19
|
+
/**
|
|
20
|
+
* Resolves which workflows should be active for a given profile configuration.
|
|
21
|
+
*
|
|
22
|
+
* - 'core' profile always returns CORE_WORKFLOWS
|
|
23
|
+
* - 'custom' profile returns the provided customWorkflows, or empty array if not provided
|
|
24
|
+
*/
|
|
25
|
+
export declare function getProfileWorkflows(profile: Profile, customWorkflows?: string[]): readonly string[];
|
|
26
|
+
//# sourceMappingURL=profiles.d.ts.map
|