@exodus/openspec 1.2.0
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 +203 -0
- package/bin/openspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +482 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +277 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +257 -0
- package/dist/commands/config.d.ts +36 -0
- package/dist/commands/config.js +552 -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/show.d.ts +14 -0
- package/dist/commands/show.js +132 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +225 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +294 -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 +29 -0
- package/dist/commands/workflow/instructions.js +381 -0
- package/dist/commands/workflow/new-change.d.ts +11 -0
- package/dist/commands/workflow/new-change.js +44 -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 +57 -0
- package/dist/commands/workflow/shared.js +116 -0
- package/dist/commands/workflow/status.d.ts +14 -0
- package/dist/commands/workflow/status.js +75 -0
- package/dist/commands/workflow/templates.d.ts +16 -0
- package/dist/commands/workflow/templates.js +68 -0
- package/dist/core/archive.d.ts +11 -0
- package/dist/core/archive.js +318 -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 +7 -0
- package/dist/core/artifact-graph/index.js +13 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
- package/dist/core/artifact-graph/instruction-loader.js +214 -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 +54 -0
- package/dist/core/artifact-graph/types.d.ts +45 -0
- package/dist/core/artifact-graph/types.js +43 -0
- package/dist/core/available-tools.d.ts +16 -0
- package/dist/core/available-tools.js +30 -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/claude.d.ts +13 -0
- package/dist/core/command-generation/adapters/claude.js +50 -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 +44 -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 +29 -0
- package/dist/core/command-generation/adapters/index.js +29 -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/opencode.d.ts +13 -0
- package/dist/core/command-generation/adapters/opencode.js +29 -0
- package/dist/core/command-generation/adapters/pi.d.ts +14 -0
- package/dist/core/command-generation/adapters/pi.js +41 -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 +51 -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 +92 -0
- package/dist/core/command-generation/types.d.ts +56 -0
- package/dist/core/command-generation/types.js +8 -0
- package/dist/core/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +461 -0
- package/dist/core/completions/completion-provider.d.ts +60 -0
- package/dist/core/completions/completion-provider.js +102 -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 +32 -0
- package/dist/core/completions/generators/bash-generator.js +174 -0
- package/dist/core/completions/generators/fish-generator.d.ts +32 -0
- package/dist/core/completions/generators/fish-generator.js +157 -0
- package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
- package/dist/core/completions/generators/powershell-generator.js +207 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
- package/dist/core/completions/generators/zsh-generator.js +250 -0
- package/dist/core/completions/installers/bash-installer.d.ts +87 -0
- package/dist/core/completions/installers/bash-installer.js +318 -0
- package/dist/core/completions/installers/fish-installer.d.ts +43 -0
- package/dist/core/completions/installers/fish-installer.js +143 -0
- package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
- package/dist/core/completions/installers/powershell-installer.js +327 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
- package/dist/core/completions/installers/zsh-installer.js +449 -0
- package/dist/core/completions/templates/bash-templates.d.ts +6 -0
- package/dist/core/completions/templates/bash-templates.js +24 -0
- package/dist/core/completions/templates/fish-templates.d.ts +7 -0
- package/dist/core/completions/templates/fish-templates.js +39 -0
- package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
- package/dist/core/completions/templates/powershell-templates.js +25 -0
- package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
- package/dist/core/completions/templates/zsh-templates.js +36 -0
- package/dist/core/completions/types.d.ts +79 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-prompts.d.ts +9 -0
- package/dist/core/config-prompts.js +34 -0
- package/dist/core/config-schema.d.ts +86 -0
- package/dist/core/config-schema.js +213 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +33 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +44 -0
- package/dist/core/global-config.js +125 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/init.d.ts +38 -0
- package/dist/core/init.js +625 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +512 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -0
- package/dist/core/migration.d.ts +23 -0
- package/dist/core/migration.js +108 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +193 -0
- package/dist/core/parsers/markdown-parser.d.ts +22 -0
- package/dist/core/parsers/markdown-parser.js +187 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -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 +64 -0
- package/dist/core/project-config.js +223 -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 +73 -0
- package/dist/core/specs-apply.js +384 -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 +362 -0
- package/dist/core/templates/workflows/archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/archive-change.js +331 -0
- package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
- package/dist/core/templates/workflows/bulk-archive-change.js +488 -0
- package/dist/core/templates/workflows/continue-change.d.ts +10 -0
- package/dist/core/templates/workflows/continue-change.js +232 -0
- package/dist/core/templates/workflows/explore.d.ts +10 -0
- package/dist/core/templates/workflows/explore.js +527 -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 +198 -0
- package/dist/core/templates/workflows/new-change.d.ts +10 -0
- package/dist/core/templates/workflows/new-change.js +143 -0
- package/dist/core/templates/workflows/onboard.d.ts +10 -0
- package/dist/core/templates/workflows/onboard.js +565 -0
- package/dist/core/templates/workflows/propose.d.ts +10 -0
- package/dist/core/templates/workflows/propose.js +306 -0
- package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
- package/dist/core/templates/workflows/sync-specs.js +272 -0
- package/dist/core/templates/workflows/verify-change.d.ts +10 -0
- package/dist/core/templates/workflows/verify-change.js +332 -0
- package/dist/core/update.d.ts +77 -0
- package/dist/core/update.js +537 -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 +33 -0
- package/dist/core/validation/validator.js +409 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/core/workspace.d.ts +18 -0
- package/dist/core/workspace.js +103 -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 +32 -0
- package/dist/telemetry/config.js +68 -0
- package/dist/telemetry/index.d.ts +31 -0
- package/dist/telemetry/index.js +145 -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 +51 -0
- package/dist/utils/change-metadata.js +147 -0
- package/dist/utils/change-utils.d.ts +62 -0
- package/dist/utils/change-utils.js +121 -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 +36 -0
- package/dist/utils/file-system.js +281 -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 +83 -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 +147 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII art animation patterns for the welcome screen.
|
|
3
|
+
* OpenSpec logo animation - diamond/rhombus shape with hollow center "O".
|
|
4
|
+
*/
|
|
5
|
+
// Detect if full Unicode is supported
|
|
6
|
+
const supportsUnicode = process.platform !== 'win32' ||
|
|
7
|
+
!!process.env.WT_SESSION || // Windows Terminal
|
|
8
|
+
!!process.env.TERM_PROGRAM; // Modern terminal
|
|
9
|
+
// Character set based on Unicode support
|
|
10
|
+
// Block characters for pixel-art aesthetic
|
|
11
|
+
const CHARS = supportsUnicode
|
|
12
|
+
? { full: '██', dim: '░░', empty: ' ' }
|
|
13
|
+
: { full: '##', dim: '++', empty: ' ' };
|
|
14
|
+
const _ = CHARS.empty;
|
|
15
|
+
const F = CHARS.full;
|
|
16
|
+
const D = CHARS.dim;
|
|
17
|
+
/**
|
|
18
|
+
* Welcome animation frames - OpenSpec logo building from center
|
|
19
|
+
* 7 rows × 6 columns diamond with hollow center "O"
|
|
20
|
+
* Center bar is 2 cols × 3 rows (rows 3,4,5 cols 3,4)
|
|
21
|
+
* Each frame is an array of strings (lines of ASCII art)
|
|
22
|
+
* Grid: 6 cols × 2 chars = 12 chars wide
|
|
23
|
+
*/
|
|
24
|
+
export const WELCOME_ANIMATION = {
|
|
25
|
+
interval: 120,
|
|
26
|
+
frames: [
|
|
27
|
+
// Frame 1: Empty
|
|
28
|
+
[
|
|
29
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
30
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
31
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
32
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
33
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
34
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
35
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
36
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
37
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
38
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
39
|
+
],
|
|
40
|
+
// Frame 2: Center blocks appear (dim) - 2x3 center bar
|
|
41
|
+
[
|
|
42
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
43
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
44
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
45
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
46
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
47
|
+
`${_}${_}${_}${_}${D}${D}${_}${_}`,
|
|
48
|
+
`${_}${_}${_}${_}${D}${D}${_}${_}`,
|
|
49
|
+
`${_}${_}${_}${_}${D}${D}${_}${_}`,
|
|
50
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
51
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
52
|
+
],
|
|
53
|
+
// Frame 3: Center blocks solidify
|
|
54
|
+
[
|
|
55
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
56
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
57
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
58
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
59
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
60
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
61
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
62
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
63
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
64
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
65
|
+
],
|
|
66
|
+
// Frame 4: Top and bottom points appear
|
|
67
|
+
[
|
|
68
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
69
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
70
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
71
|
+
`${_}${_}${_}${_}${D}${D}${_}${_}`,
|
|
72
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
73
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
74
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
75
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
76
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`,
|
|
77
|
+
`${_}${_}${_}${_}${D}${D}${_}${_}`,
|
|
78
|
+
],
|
|
79
|
+
// Frame 5: Inner ring forming
|
|
80
|
+
[
|
|
81
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
82
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
83
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
84
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
85
|
+
`${_}${_}${_}${D}${_}${_}${D}${_}`,
|
|
86
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
87
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
88
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
89
|
+
`${_}${_}${_}${D}${_}${_}${D}${_}`,
|
|
90
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
91
|
+
],
|
|
92
|
+
// Frame 6: Outer ring appearing
|
|
93
|
+
[
|
|
94
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
95
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
96
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
97
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
98
|
+
`${_}${_}${_}${F}${_}${_}${F}${_}`,
|
|
99
|
+
`${_}${_}${D}${_}${F}${F}${_}${D}`,
|
|
100
|
+
`${_}${_}${D}${_}${F}${F}${_}${D}`,
|
|
101
|
+
`${_}${_}${D}${_}${F}${F}${_}${D}`,
|
|
102
|
+
`${_}${_}${_}${F}${_}${_}${F}${_}`,
|
|
103
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
104
|
+
],
|
|
105
|
+
// Frame 7: Full logo
|
|
106
|
+
[
|
|
107
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
108
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
109
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
110
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
111
|
+
`${_}${_}${_}${F}${_}${_}${F}${_}`,
|
|
112
|
+
`${_}${_}${F}${_}${F}${F}${_}${F}`,
|
|
113
|
+
`${_}${_}${F}${_}${F}${F}${_}${F}`,
|
|
114
|
+
`${_}${_}${F}${_}${F}${F}${_}${F}`,
|
|
115
|
+
`${_}${_}${_}${F}${_}${_}${F}${_}`,
|
|
116
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
117
|
+
],
|
|
118
|
+
// Frame 8: Hold complete logo
|
|
119
|
+
[
|
|
120
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
|
|
121
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
|
|
122
|
+
`${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
|
|
123
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
124
|
+
`${_}${_}${_}${F}${_}${_}${F}${_}`,
|
|
125
|
+
`${_}${_}${F}${_}${F}${F}${_}${F}`,
|
|
126
|
+
`${_}${_}${F}${_}${F}${F}${_}${F}`,
|
|
127
|
+
`${_}${_}${F}${_}${F}${F}${_}${F}`,
|
|
128
|
+
`${_}${_}${_}${F}${_}${_}${F}${_}`,
|
|
129
|
+
`${_}${_}${_}${_}${F}${F}${_}${_}`,
|
|
130
|
+
],
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=ascii-patterns.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animated welcome screen for the experimental artifact workflow setup.
|
|
3
|
+
* Shows side-by-side layout with animated ASCII art on left and welcome text on right.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Shows the animated welcome screen.
|
|
7
|
+
* Returns when user presses Enter.
|
|
8
|
+
*/
|
|
9
|
+
export declare function showWelcomeScreen(): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=welcome-screen.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animated welcome screen for the experimental artifact workflow setup.
|
|
3
|
+
* Shows side-by-side layout with animated ASCII art on left and welcome text on right.
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { WELCOME_ANIMATION } from './ascii-patterns.js';
|
|
7
|
+
// Minimum terminal width for side-by-side layout
|
|
8
|
+
const MIN_WIDTH = 60;
|
|
9
|
+
// Width of the ASCII art column (with padding)
|
|
10
|
+
const ART_COLUMN_WIDTH = 24;
|
|
11
|
+
/**
|
|
12
|
+
* Welcome text content (right column)
|
|
13
|
+
*/
|
|
14
|
+
function getWelcomeText() {
|
|
15
|
+
return [
|
|
16
|
+
chalk.white.bold('Welcome to OpenSpec'),
|
|
17
|
+
chalk.dim('A lightweight spec-driven framework'),
|
|
18
|
+
'',
|
|
19
|
+
chalk.white('This setup will configure:'),
|
|
20
|
+
chalk.dim(' • Agent Skills for AI tools'),
|
|
21
|
+
chalk.dim(' • /opsx:* slash commands'),
|
|
22
|
+
'',
|
|
23
|
+
chalk.white('Quick start after setup:'),
|
|
24
|
+
` ${chalk.yellow('/opsx:new')} ${chalk.dim('Create a change')}`,
|
|
25
|
+
` ${chalk.yellow('/opsx:continue')} ${chalk.dim('Next artifact')}`,
|
|
26
|
+
` ${chalk.yellow('/opsx:apply')} ${chalk.dim('Implement tasks')}`,
|
|
27
|
+
'',
|
|
28
|
+
chalk.cyan('Press Enter to select tools...'),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Renders a single frame with side-by-side layout
|
|
33
|
+
*/
|
|
34
|
+
function renderFrame(artLines, textLines) {
|
|
35
|
+
const maxLines = Math.max(artLines.length, textLines.length);
|
|
36
|
+
const lines = [];
|
|
37
|
+
for (let i = 0; i < maxLines; i++) {
|
|
38
|
+
const artLine = artLines[i] || '';
|
|
39
|
+
const textLine = textLines[i] || '';
|
|
40
|
+
// Pad the art column to fixed width
|
|
41
|
+
const paddedArt = artLine.padEnd(ART_COLUMN_WIDTH);
|
|
42
|
+
// Color the ASCII art with cyan for visual appeal
|
|
43
|
+
const coloredArt = chalk.cyan(paddedArt);
|
|
44
|
+
// Clear line before writing to prevent residual characters
|
|
45
|
+
lines.push(`\x1b[2K${coloredArt}${textLine}`);
|
|
46
|
+
}
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Checks if the terminal supports animation
|
|
51
|
+
*/
|
|
52
|
+
function canAnimate() {
|
|
53
|
+
// Must be TTY
|
|
54
|
+
if (!process.stdout.isTTY)
|
|
55
|
+
return false;
|
|
56
|
+
// Respect NO_COLOR
|
|
57
|
+
if (process.env.NO_COLOR)
|
|
58
|
+
return false;
|
|
59
|
+
// Check terminal width
|
|
60
|
+
const columns = process.stdout.columns || 80;
|
|
61
|
+
if (columns < MIN_WIDTH)
|
|
62
|
+
return false;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Wait for Enter key press
|
|
67
|
+
*/
|
|
68
|
+
function waitForEnter() {
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
const { stdin } = process;
|
|
71
|
+
// Handle non-TTY gracefully
|
|
72
|
+
if (!stdin.isTTY) {
|
|
73
|
+
resolve();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const wasRaw = stdin.isRaw;
|
|
77
|
+
stdin.setRawMode(true);
|
|
78
|
+
stdin.resume();
|
|
79
|
+
const onData = (data) => {
|
|
80
|
+
const char = data.toString();
|
|
81
|
+
// Enter key or Ctrl+C
|
|
82
|
+
if (char === '\r' || char === '\n' || char === '\u0003') {
|
|
83
|
+
stdin.removeListener('data', onData);
|
|
84
|
+
stdin.setRawMode(wasRaw);
|
|
85
|
+
stdin.pause();
|
|
86
|
+
// Handle Ctrl+C
|
|
87
|
+
if (char === '\u0003') {
|
|
88
|
+
process.stdout.write('\n');
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
resolve();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
stdin.on('data', onData);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Shows the animated welcome screen.
|
|
99
|
+
* Returns when user presses Enter.
|
|
100
|
+
*/
|
|
101
|
+
export async function showWelcomeScreen() {
|
|
102
|
+
const textLines = getWelcomeText();
|
|
103
|
+
if (!canAnimate()) {
|
|
104
|
+
// Fallback: show static welcome
|
|
105
|
+
const frame = WELCOME_ANIMATION.frames[3]; // Peak frame
|
|
106
|
+
process.stdout.write('\n' + renderFrame(frame, textLines) + '\n\n');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
let frameIndex = 0;
|
|
110
|
+
let running = true;
|
|
111
|
+
let isFirstRender = true;
|
|
112
|
+
// Content height for cursor movement between frames
|
|
113
|
+
const numContentLines = Math.max(WELCOME_ANIMATION.frames[0].length, textLines.length);
|
|
114
|
+
const frameHeight = numContentLines + 1; // internal newlines (11) + trailing newlines (2) = 13
|
|
115
|
+
// Total height including initial newline (for cleanup)
|
|
116
|
+
const totalHeight = frameHeight + 1; // 14
|
|
117
|
+
// Initial render
|
|
118
|
+
process.stdout.write('\n');
|
|
119
|
+
// Animation loop
|
|
120
|
+
const interval = setInterval(() => {
|
|
121
|
+
if (!running)
|
|
122
|
+
return;
|
|
123
|
+
const frame = WELCOME_ANIMATION.frames[frameIndex];
|
|
124
|
+
// Move cursor up to overwrite previous frame (always after first render)
|
|
125
|
+
if (!isFirstRender) {
|
|
126
|
+
process.stdout.write(`\x1b[${frameHeight}A`);
|
|
127
|
+
}
|
|
128
|
+
isFirstRender = false;
|
|
129
|
+
// Render current frame
|
|
130
|
+
process.stdout.write(renderFrame(frame, textLines) + '\n\n');
|
|
131
|
+
// Advance to next frame
|
|
132
|
+
frameIndex = (frameIndex + 1) % WELCOME_ANIMATION.frames.length;
|
|
133
|
+
}, WELCOME_ANIMATION.interval);
|
|
134
|
+
// Wait for Enter
|
|
135
|
+
await waitForEnter();
|
|
136
|
+
// Stop animation
|
|
137
|
+
running = false;
|
|
138
|
+
clearInterval(interval);
|
|
139
|
+
// Clear the welcome screen and move on
|
|
140
|
+
process.stdout.write(`\x1b[${totalHeight}A`);
|
|
141
|
+
for (let i = 0; i < totalHeight; i++) {
|
|
142
|
+
process.stdout.write('\x1b[2K\n'); // Clear line
|
|
143
|
+
}
|
|
144
|
+
process.stdout.write(`\x1b[${totalHeight}A`); // Move back up
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=welcome-screen.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ChangeMetadata } from '../core/artifact-graph/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when change metadata validation fails.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ChangeMetadataError extends Error {
|
|
6
|
+
readonly metadataPath: string;
|
|
7
|
+
readonly cause?: Error | undefined;
|
|
8
|
+
constructor(message: string, metadataPath: string, cause?: Error | undefined);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates that a schema name is valid (exists in available schemas).
|
|
12
|
+
*
|
|
13
|
+
* @param schemaName - The schema name to validate
|
|
14
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
15
|
+
* @returns The validated schema name
|
|
16
|
+
* @throws Error if schema is not found
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateSchemaName(schemaName: string, projectRoot?: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Writes change metadata to .openspec.yaml in the change directory.
|
|
21
|
+
*
|
|
22
|
+
* @param changeDir - The path to the change directory
|
|
23
|
+
* @param metadata - The metadata to write
|
|
24
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
25
|
+
* @throws ChangeMetadataError if validation fails or write fails
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeChangeMetadata(changeDir: string, metadata: ChangeMetadata, projectRoot?: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Reads change metadata from .openspec.yaml in the change directory.
|
|
30
|
+
*
|
|
31
|
+
* @param changeDir - The path to the change directory
|
|
32
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
33
|
+
* @returns The validated metadata, or null if no metadata file exists
|
|
34
|
+
* @throws ChangeMetadataError if the file exists but is invalid
|
|
35
|
+
*/
|
|
36
|
+
export declare function readChangeMetadata(changeDir: string, projectRoot?: string): ChangeMetadata | null;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the schema for a change, with explicit override taking precedence.
|
|
39
|
+
*
|
|
40
|
+
* Resolution order:
|
|
41
|
+
* 1. Explicit schema (if provided)
|
|
42
|
+
* 2. Schema from .openspec.yaml metadata (if exists)
|
|
43
|
+
* 3. Schema from openspec/config.yaml (if exists)
|
|
44
|
+
* 4. Default 'spec-driven'
|
|
45
|
+
*
|
|
46
|
+
* @param changeDir - The path to the change directory
|
|
47
|
+
* @param explicitSchema - Optional explicit schema override
|
|
48
|
+
* @returns The resolved schema name
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveSchemaForChange(changeDir: string, explicitSchema?: string): string;
|
|
51
|
+
//# sourceMappingURL=change-metadata.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as yaml from 'yaml';
|
|
4
|
+
import { ChangeMetadataSchema } from '../core/artifact-graph/types.js';
|
|
5
|
+
import { listSchemas } from '../core/artifact-graph/resolver.js';
|
|
6
|
+
import { readProjectConfig } from '../core/project-config.js';
|
|
7
|
+
const METADATA_FILENAME = '.openspec.yaml';
|
|
8
|
+
/**
|
|
9
|
+
* Error thrown when change metadata validation fails.
|
|
10
|
+
*/
|
|
11
|
+
export class ChangeMetadataError extends Error {
|
|
12
|
+
metadataPath;
|
|
13
|
+
cause;
|
|
14
|
+
constructor(message, metadataPath, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.metadataPath = metadataPath;
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
this.name = 'ChangeMetadataError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validates that a schema name is valid (exists in available schemas).
|
|
23
|
+
*
|
|
24
|
+
* @param schemaName - The schema name to validate
|
|
25
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
26
|
+
* @returns The validated schema name
|
|
27
|
+
* @throws Error if schema is not found
|
|
28
|
+
*/
|
|
29
|
+
export function validateSchemaName(schemaName, projectRoot) {
|
|
30
|
+
const availableSchemas = listSchemas(projectRoot);
|
|
31
|
+
if (!availableSchemas.includes(schemaName)) {
|
|
32
|
+
throw new Error(`Unknown schema '${schemaName}'. Available: ${availableSchemas.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
return schemaName;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Writes change metadata to .openspec.yaml in the change directory.
|
|
38
|
+
*
|
|
39
|
+
* @param changeDir - The path to the change directory
|
|
40
|
+
* @param metadata - The metadata to write
|
|
41
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
42
|
+
* @throws ChangeMetadataError if validation fails or write fails
|
|
43
|
+
*/
|
|
44
|
+
export function writeChangeMetadata(changeDir, metadata, projectRoot) {
|
|
45
|
+
const metaPath = path.join(changeDir, METADATA_FILENAME);
|
|
46
|
+
// Validate schema exists
|
|
47
|
+
validateSchemaName(metadata.schema, projectRoot);
|
|
48
|
+
// Validate with Zod
|
|
49
|
+
const parseResult = ChangeMetadataSchema.safeParse(metadata);
|
|
50
|
+
if (!parseResult.success) {
|
|
51
|
+
throw new ChangeMetadataError(`Invalid metadata: ${parseResult.error.message}`, metaPath);
|
|
52
|
+
}
|
|
53
|
+
// Write YAML file
|
|
54
|
+
const content = yaml.stringify(parseResult.data);
|
|
55
|
+
try {
|
|
56
|
+
fs.writeFileSync(metaPath, content, 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const ioError = err instanceof Error ? err : new Error(String(err));
|
|
60
|
+
throw new ChangeMetadataError(`Failed to write metadata: ${ioError.message}`, metaPath, ioError);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Reads change metadata from .openspec.yaml in the change directory.
|
|
65
|
+
*
|
|
66
|
+
* @param changeDir - The path to the change directory
|
|
67
|
+
* @param projectRoot - Optional project root for project-local schema resolution
|
|
68
|
+
* @returns The validated metadata, or null if no metadata file exists
|
|
69
|
+
* @throws ChangeMetadataError if the file exists but is invalid
|
|
70
|
+
*/
|
|
71
|
+
export function readChangeMetadata(changeDir, projectRoot) {
|
|
72
|
+
const metaPath = path.join(changeDir, METADATA_FILENAME);
|
|
73
|
+
if (!fs.existsSync(metaPath)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
let content;
|
|
77
|
+
try {
|
|
78
|
+
content = fs.readFileSync(metaPath, 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const ioError = err instanceof Error ? err : new Error(String(err));
|
|
82
|
+
throw new ChangeMetadataError(`Failed to read metadata: ${ioError.message}`, metaPath, ioError);
|
|
83
|
+
}
|
|
84
|
+
let parsed;
|
|
85
|
+
try {
|
|
86
|
+
parsed = yaml.parse(content);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
const parseError = err instanceof Error ? err : new Error(String(err));
|
|
90
|
+
throw new ChangeMetadataError(`Invalid YAML in metadata file: ${parseError.message}`, metaPath, parseError);
|
|
91
|
+
}
|
|
92
|
+
// Validate with Zod
|
|
93
|
+
const parseResult = ChangeMetadataSchema.safeParse(parsed);
|
|
94
|
+
if (!parseResult.success) {
|
|
95
|
+
throw new ChangeMetadataError(`Invalid metadata: ${parseResult.error.message}`, metaPath);
|
|
96
|
+
}
|
|
97
|
+
// Validate that the schema exists
|
|
98
|
+
const availableSchemas = listSchemas(projectRoot);
|
|
99
|
+
if (!availableSchemas.includes(parseResult.data.schema)) {
|
|
100
|
+
throw new ChangeMetadataError(`Unknown schema '${parseResult.data.schema}'. Available: ${availableSchemas.join(', ')}`, metaPath);
|
|
101
|
+
}
|
|
102
|
+
return parseResult.data;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolves the schema for a change, with explicit override taking precedence.
|
|
106
|
+
*
|
|
107
|
+
* Resolution order:
|
|
108
|
+
* 1. Explicit schema (if provided)
|
|
109
|
+
* 2. Schema from .openspec.yaml metadata (if exists)
|
|
110
|
+
* 3. Schema from openspec/config.yaml (if exists)
|
|
111
|
+
* 4. Default 'spec-driven'
|
|
112
|
+
*
|
|
113
|
+
* @param changeDir - The path to the change directory
|
|
114
|
+
* @param explicitSchema - Optional explicit schema override
|
|
115
|
+
* @returns The resolved schema name
|
|
116
|
+
*/
|
|
117
|
+
export function resolveSchemaForChange(changeDir, explicitSchema) {
|
|
118
|
+
// Derive project root from changeDir (changeDir is typically projectRoot/openspec/changes/change-name)
|
|
119
|
+
const projectRoot = path.resolve(changeDir, '../../..');
|
|
120
|
+
// 1. Explicit override wins
|
|
121
|
+
if (explicitSchema) {
|
|
122
|
+
return explicitSchema;
|
|
123
|
+
}
|
|
124
|
+
// 2. Try reading from metadata
|
|
125
|
+
try {
|
|
126
|
+
const metadata = readChangeMetadata(changeDir, projectRoot);
|
|
127
|
+
if (metadata?.schema) {
|
|
128
|
+
return metadata.schema;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// If metadata read fails, continue to next option
|
|
133
|
+
}
|
|
134
|
+
// 3. Try reading from project config
|
|
135
|
+
try {
|
|
136
|
+
const config = readProjectConfig(projectRoot);
|
|
137
|
+
if (config?.schema) {
|
|
138
|
+
return config.schema;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// If config read fails, fall back to default
|
|
143
|
+
}
|
|
144
|
+
// 4. Default
|
|
145
|
+
return 'spec-driven';
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=change-metadata.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for creating a change.
|
|
3
|
+
*/
|
|
4
|
+
export interface CreateChangeOptions {
|
|
5
|
+
/** The workflow schema to use (default: 'spec-driven') */
|
|
6
|
+
schema?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Result of creating a change.
|
|
10
|
+
*/
|
|
11
|
+
export interface CreateChangeResult {
|
|
12
|
+
/** The schema that was actually used (resolved from options, config, or default) */
|
|
13
|
+
schema: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Result of validating a change name.
|
|
17
|
+
*/
|
|
18
|
+
export interface ValidationResult {
|
|
19
|
+
valid: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validates that a change name follows kebab-case conventions.
|
|
24
|
+
*
|
|
25
|
+
* Valid names:
|
|
26
|
+
* - Start with a lowercase letter
|
|
27
|
+
* - Contain only lowercase letters, numbers, and hyphens
|
|
28
|
+
* - Do not start or end with a hyphen
|
|
29
|
+
* - Do not contain consecutive hyphens
|
|
30
|
+
*
|
|
31
|
+
* @param name - The change name to validate
|
|
32
|
+
* @returns Validation result with `valid: true` or `valid: false` with an error message
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* validateChangeName('add-auth') // { valid: true }
|
|
36
|
+
* validateChangeName('Add-Auth') // { valid: false, error: '...' }
|
|
37
|
+
*/
|
|
38
|
+
export declare function validateChangeName(name: string): ValidationResult;
|
|
39
|
+
/**
|
|
40
|
+
* Creates a new change directory with metadata file.
|
|
41
|
+
*
|
|
42
|
+
* @param projectRoot - The root directory of the project (where `openspec/` lives)
|
|
43
|
+
* @param name - The change name (must be valid kebab-case)
|
|
44
|
+
* @param options - Optional settings for the change
|
|
45
|
+
* @throws Error if the change name is invalid
|
|
46
|
+
* @throws Error if the schema name is invalid
|
|
47
|
+
* @throws Error if the change directory already exists
|
|
48
|
+
*
|
|
49
|
+
* @returns Result containing the resolved schema name
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Creates openspec/changes/add-auth/ with default schema
|
|
53
|
+
* const result = await createChange('/path/to/project', 'add-auth')
|
|
54
|
+
* console.log(result.schema) // 'spec-driven' or value from config
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Creates openspec/changes/add-auth/ with custom schema
|
|
58
|
+
* const result = await createChange('/path/to/project', 'add-auth', { schema: 'my-workflow' })
|
|
59
|
+
* console.log(result.schema) // 'my-workflow'
|
|
60
|
+
*/
|
|
61
|
+
export declare function createChange(projectRoot: string, name: string, options?: CreateChangeOptions): Promise<CreateChangeResult>;
|
|
62
|
+
//# sourceMappingURL=change-utils.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { FileSystemUtils } from './file-system.js';
|
|
3
|
+
import { writeChangeMetadata, validateSchemaName } from './change-metadata.js';
|
|
4
|
+
import { readProjectConfig } from '../core/project-config.js';
|
|
5
|
+
const DEFAULT_SCHEMA = 'spec-driven';
|
|
6
|
+
/**
|
|
7
|
+
* Validates that a change name follows kebab-case conventions.
|
|
8
|
+
*
|
|
9
|
+
* Valid names:
|
|
10
|
+
* - Start with a lowercase letter
|
|
11
|
+
* - Contain only lowercase letters, numbers, and hyphens
|
|
12
|
+
* - Do not start or end with a hyphen
|
|
13
|
+
* - Do not contain consecutive hyphens
|
|
14
|
+
*
|
|
15
|
+
* @param name - The change name to validate
|
|
16
|
+
* @returns Validation result with `valid: true` or `valid: false` with an error message
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* validateChangeName('add-auth') // { valid: true }
|
|
20
|
+
* validateChangeName('Add-Auth') // { valid: false, error: '...' }
|
|
21
|
+
*/
|
|
22
|
+
export function validateChangeName(name) {
|
|
23
|
+
// Pattern: starts with lowercase letter, followed by lowercase letters/numbers,
|
|
24
|
+
// optionally followed by hyphen + lowercase letters/numbers (repeatable)
|
|
25
|
+
const kebabCasePattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
26
|
+
if (!name) {
|
|
27
|
+
return { valid: false, error: 'Change name cannot be empty' };
|
|
28
|
+
}
|
|
29
|
+
if (!kebabCasePattern.test(name)) {
|
|
30
|
+
// Provide specific error messages for common mistakes
|
|
31
|
+
if (/[A-Z]/.test(name)) {
|
|
32
|
+
return { valid: false, error: 'Change name must be lowercase (use kebab-case)' };
|
|
33
|
+
}
|
|
34
|
+
if (/\s/.test(name)) {
|
|
35
|
+
return { valid: false, error: 'Change name cannot contain spaces (use hyphens instead)' };
|
|
36
|
+
}
|
|
37
|
+
if (/_/.test(name)) {
|
|
38
|
+
return { valid: false, error: 'Change name cannot contain underscores (use hyphens instead)' };
|
|
39
|
+
}
|
|
40
|
+
if (name.startsWith('-')) {
|
|
41
|
+
return { valid: false, error: 'Change name cannot start with a hyphen' };
|
|
42
|
+
}
|
|
43
|
+
if (name.endsWith('-')) {
|
|
44
|
+
return { valid: false, error: 'Change name cannot end with a hyphen' };
|
|
45
|
+
}
|
|
46
|
+
if (/--/.test(name)) {
|
|
47
|
+
return { valid: false, error: 'Change name cannot contain consecutive hyphens' };
|
|
48
|
+
}
|
|
49
|
+
if (/[^a-z0-9-]/.test(name)) {
|
|
50
|
+
return { valid: false, error: 'Change name can only contain lowercase letters, numbers, and hyphens' };
|
|
51
|
+
}
|
|
52
|
+
if (/^[0-9]/.test(name)) {
|
|
53
|
+
return { valid: false, error: 'Change name must start with a letter' };
|
|
54
|
+
}
|
|
55
|
+
return { valid: false, error: 'Change name must follow kebab-case convention (e.g., add-auth, refactor-db)' };
|
|
56
|
+
}
|
|
57
|
+
return { valid: true };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a new change directory with metadata file.
|
|
61
|
+
*
|
|
62
|
+
* @param projectRoot - The root directory of the project (where `openspec/` lives)
|
|
63
|
+
* @param name - The change name (must be valid kebab-case)
|
|
64
|
+
* @param options - Optional settings for the change
|
|
65
|
+
* @throws Error if the change name is invalid
|
|
66
|
+
* @throws Error if the schema name is invalid
|
|
67
|
+
* @throws Error if the change directory already exists
|
|
68
|
+
*
|
|
69
|
+
* @returns Result containing the resolved schema name
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Creates openspec/changes/add-auth/ with default schema
|
|
73
|
+
* const result = await createChange('/path/to/project', 'add-auth')
|
|
74
|
+
* console.log(result.schema) // 'spec-driven' or value from config
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Creates openspec/changes/add-auth/ with custom schema
|
|
78
|
+
* const result = await createChange('/path/to/project', 'add-auth', { schema: 'my-workflow' })
|
|
79
|
+
* console.log(result.schema) // 'my-workflow'
|
|
80
|
+
*/
|
|
81
|
+
export async function createChange(projectRoot, name, options = {}) {
|
|
82
|
+
// Validate the name first
|
|
83
|
+
const validation = validateChangeName(name);
|
|
84
|
+
if (!validation.valid) {
|
|
85
|
+
throw new Error(validation.error);
|
|
86
|
+
}
|
|
87
|
+
// Determine schema: explicit option → project config → hardcoded default
|
|
88
|
+
let schemaName;
|
|
89
|
+
if (options.schema) {
|
|
90
|
+
schemaName = options.schema;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Try to read from project config
|
|
94
|
+
try {
|
|
95
|
+
const config = readProjectConfig(projectRoot);
|
|
96
|
+
schemaName = config?.schema ?? DEFAULT_SCHEMA;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// If config read fails, use default
|
|
100
|
+
schemaName = DEFAULT_SCHEMA;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Validate the resolved schema
|
|
104
|
+
validateSchemaName(schemaName, projectRoot);
|
|
105
|
+
// Build the change directory path
|
|
106
|
+
const changeDir = path.join(projectRoot, 'openspec', 'changes', name);
|
|
107
|
+
// Check if change already exists
|
|
108
|
+
if (await FileSystemUtils.directoryExists(changeDir)) {
|
|
109
|
+
throw new Error(`Change '${name}' already exists at ${changeDir}`);
|
|
110
|
+
}
|
|
111
|
+
// Create the directory (including parent directories if needed)
|
|
112
|
+
await FileSystemUtils.createDirectory(changeDir);
|
|
113
|
+
// Write metadata file with schema and creation date
|
|
114
|
+
const today = new Date().toISOString().split('T')[0];
|
|
115
|
+
writeChangeMetadata(changeDir, {
|
|
116
|
+
schema: schemaName,
|
|
117
|
+
created: today,
|
|
118
|
+
}, projectRoot);
|
|
119
|
+
return { schema: schemaName };
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=change-utils.js.map
|