openspec-cn 0.23.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 +153 -0
- package/bin/openspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +480 -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 +8 -0
- package/dist/commands/config.js +198 -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 +52 -0
- package/dist/commands/workflow/shared.js +111 -0
- package/dist/commands/workflow/status.d.ts +14 -0
- package/dist/commands/workflow/status.js +58 -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 +280 -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/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 +13 -0
- package/dist/core/command-generation/adapters/codex.js +27 -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 +27 -0
- package/dist/core/command-generation/adapters/index.js +27 -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/opencode.d.ts +13 -0
- package/dist/core/command-generation/adapters/opencode.js +26 -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 +88 -0
- package/dist/core/command-generation/types.d.ts +55 -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 +456 -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 +76 -0
- package/dist/core/config-schema.js +200 -0
- package/dist/core/config.d.ts +17 -0
- package/dist/core/config.js +30 -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 +39 -0
- package/dist/core/global-config.js +115 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/init.d.ts +32 -0
- package/dist/core/init.js +433 -0
- package/dist/core/legacy-cleanup.d.ts +162 -0
- package/dist/core/legacy-cleanup.js +501 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -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/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 +41 -0
- package/dist/core/shared/skill-generation.js +74 -0
- package/dist/core/shared/tool-detection.d.ts +66 -0
- package/dist/core/shared/tool-detection.js +140 -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 +112 -0
- package/dist/core/templates/skill-templates.js +2893 -0
- package/dist/core/update.d.ts +42 -0
- package/dist/core/update.js +306 -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/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/prompts/searchable-multi-select.d.ts +27 -0
- package/dist/prompts/searchable-multi-select.js +149 -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/file-system.d.ts +36 -0
- package/dist/utils/file-system.js +281 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +7 -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 +148 -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/schemas/tdd/schema.yaml +213 -0
- package/schemas/tdd/templates/docs.md +15 -0
- package/schemas/tdd/templates/implementation.md +11 -0
- package/schemas/tdd/templates/spec.md +11 -0
- package/schemas/tdd/templates/test.md +11 -0
- package/scripts/postinstall.js +147 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
5
|
+
import { getSchemaDir, getProjectSchemasDir, getUserSchemasDir, getPackageSchemasDir, listSchemas, } from '../core/artifact-graph/resolver.js';
|
|
6
|
+
import { parseSchema, SchemaValidationError } from '../core/artifact-graph/schema.js';
|
|
7
|
+
/**
|
|
8
|
+
* Check all three locations for a schema and return which ones exist.
|
|
9
|
+
*/
|
|
10
|
+
function checkAllLocations(name, projectRoot) {
|
|
11
|
+
const locations = [];
|
|
12
|
+
// Project location
|
|
13
|
+
const projectDir = path.join(getProjectSchemasDir(projectRoot), name);
|
|
14
|
+
const projectSchemaPath = path.join(projectDir, 'schema.yaml');
|
|
15
|
+
locations.push({
|
|
16
|
+
source: 'project',
|
|
17
|
+
path: projectDir,
|
|
18
|
+
exists: fs.existsSync(projectSchemaPath),
|
|
19
|
+
});
|
|
20
|
+
// User location
|
|
21
|
+
const userDir = path.join(getUserSchemasDir(), name);
|
|
22
|
+
const userSchemaPath = path.join(userDir, 'schema.yaml');
|
|
23
|
+
locations.push({
|
|
24
|
+
source: 'user',
|
|
25
|
+
path: userDir,
|
|
26
|
+
exists: fs.existsSync(userSchemaPath),
|
|
27
|
+
});
|
|
28
|
+
// Package location
|
|
29
|
+
const packageDir = path.join(getPackageSchemasDir(), name);
|
|
30
|
+
const packageSchemaPath = path.join(packageDir, 'schema.yaml');
|
|
31
|
+
locations.push({
|
|
32
|
+
source: 'package',
|
|
33
|
+
path: packageDir,
|
|
34
|
+
exists: fs.existsSync(packageSchemaPath),
|
|
35
|
+
});
|
|
36
|
+
return locations;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get resolution info for a schema including shadow detection.
|
|
40
|
+
*/
|
|
41
|
+
function getSchemaResolution(name, projectRoot) {
|
|
42
|
+
const locations = checkAllLocations(name, projectRoot);
|
|
43
|
+
const existingLocations = locations.filter((loc) => loc.exists);
|
|
44
|
+
if (existingLocations.length === 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const active = existingLocations[0];
|
|
48
|
+
const shadows = existingLocations.slice(1).map((loc) => ({
|
|
49
|
+
source: loc.source,
|
|
50
|
+
path: loc.path,
|
|
51
|
+
}));
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
source: active.source,
|
|
55
|
+
path: active.path,
|
|
56
|
+
shadows,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get all schemas with resolution info.
|
|
61
|
+
*/
|
|
62
|
+
function getAllSchemasWithResolution(projectRoot) {
|
|
63
|
+
const schemaNames = listSchemas(projectRoot);
|
|
64
|
+
const results = [];
|
|
65
|
+
for (const name of schemaNames) {
|
|
66
|
+
const resolution = getSchemaResolution(name, projectRoot);
|
|
67
|
+
if (resolution) {
|
|
68
|
+
results.push(resolution);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate a schema and return issues.
|
|
75
|
+
*/
|
|
76
|
+
function validateSchema(schemaDir, verbose = false) {
|
|
77
|
+
const issues = [];
|
|
78
|
+
const schemaPath = path.join(schemaDir, 'schema.yaml');
|
|
79
|
+
// Check schema.yaml exists
|
|
80
|
+
if (verbose) {
|
|
81
|
+
console.log(' Checking schema.yaml exists...');
|
|
82
|
+
}
|
|
83
|
+
if (!fs.existsSync(schemaPath)) {
|
|
84
|
+
issues.push({
|
|
85
|
+
level: 'error',
|
|
86
|
+
path: 'schema.yaml',
|
|
87
|
+
message: 'schema.yaml not found',
|
|
88
|
+
});
|
|
89
|
+
return { valid: false, issues };
|
|
90
|
+
}
|
|
91
|
+
// Parse YAML
|
|
92
|
+
if (verbose) {
|
|
93
|
+
console.log(' Parsing YAML...');
|
|
94
|
+
}
|
|
95
|
+
let content;
|
|
96
|
+
try {
|
|
97
|
+
content = fs.readFileSync(schemaPath, 'utf-8');
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
issues.push({
|
|
101
|
+
level: 'error',
|
|
102
|
+
path: 'schema.yaml',
|
|
103
|
+
message: `Failed to read file: ${err.message}`,
|
|
104
|
+
});
|
|
105
|
+
return { valid: false, issues };
|
|
106
|
+
}
|
|
107
|
+
// Validate against Zod schema
|
|
108
|
+
if (verbose) {
|
|
109
|
+
console.log(' Validating schema structure...');
|
|
110
|
+
}
|
|
111
|
+
let schema;
|
|
112
|
+
try {
|
|
113
|
+
schema = parseSchema(content);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err instanceof SchemaValidationError) {
|
|
117
|
+
issues.push({
|
|
118
|
+
level: 'error',
|
|
119
|
+
path: 'schema.yaml',
|
|
120
|
+
message: err.message,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
issues.push({
|
|
125
|
+
level: 'error',
|
|
126
|
+
path: 'schema.yaml',
|
|
127
|
+
message: `Parse error: ${err.message}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return { valid: false, issues };
|
|
131
|
+
}
|
|
132
|
+
// Check template files exist
|
|
133
|
+
// Templates can be in schemaDir directly or in a templates/ subdirectory
|
|
134
|
+
if (verbose) {
|
|
135
|
+
console.log(' Checking template files...');
|
|
136
|
+
}
|
|
137
|
+
for (const artifact of schema.artifacts) {
|
|
138
|
+
// Try templates subdirectory first (standard location), then root
|
|
139
|
+
const templatePathInTemplates = path.join(schemaDir, 'templates', artifact.template);
|
|
140
|
+
const templatePathInRoot = path.join(schemaDir, artifact.template);
|
|
141
|
+
if (!fs.existsSync(templatePathInTemplates) && !fs.existsSync(templatePathInRoot)) {
|
|
142
|
+
issues.push({
|
|
143
|
+
level: 'error',
|
|
144
|
+
path: `artifacts.${artifact.id}.template`,
|
|
145
|
+
message: `Template file '${artifact.template}' not found for artifact '${artifact.id}'`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Dependency graph validation is already done by parseSchema
|
|
150
|
+
// (it throws on cycles and invalid references)
|
|
151
|
+
if (verbose) {
|
|
152
|
+
console.log(' Dependency graph validation passed (via parseSchema)');
|
|
153
|
+
}
|
|
154
|
+
return { valid: issues.length === 0, issues };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Validate schema name format (kebab-case).
|
|
158
|
+
*/
|
|
159
|
+
function isValidSchemaName(name) {
|
|
160
|
+
return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Copy a directory recursively.
|
|
164
|
+
*/
|
|
165
|
+
function copyDirRecursive(src, dest) {
|
|
166
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
167
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
const srcPath = path.join(src, entry.name);
|
|
170
|
+
const destPath = path.join(dest, entry.name);
|
|
171
|
+
if (entry.isDirectory()) {
|
|
172
|
+
copyDirRecursive(srcPath, destPath);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
fs.copyFileSync(srcPath, destPath);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Default artifacts with descriptions for schema init.
|
|
181
|
+
*/
|
|
182
|
+
const DEFAULT_ARTIFACTS = [
|
|
183
|
+
{
|
|
184
|
+
id: 'proposal',
|
|
185
|
+
description: 'High-level description of the change, its motivation, and scope',
|
|
186
|
+
generates: 'proposal.md',
|
|
187
|
+
template: 'proposal.md',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'specs',
|
|
191
|
+
description: 'Detailed specifications with requirements and scenarios',
|
|
192
|
+
generates: 'specs/**/*.md',
|
|
193
|
+
template: 'specs/spec.md',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'design',
|
|
197
|
+
description: 'Technical design decisions and implementation approach',
|
|
198
|
+
generates: 'design.md',
|
|
199
|
+
template: 'design.md',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: 'tasks',
|
|
203
|
+
description: 'Implementation checklist with trackable tasks',
|
|
204
|
+
generates: 'tasks.md',
|
|
205
|
+
template: 'tasks.md',
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
/**
|
|
209
|
+
* Register the schema command and all its subcommands.
|
|
210
|
+
*/
|
|
211
|
+
export function registerSchemaCommand(program) {
|
|
212
|
+
const schemaCmd = program
|
|
213
|
+
.command('schema')
|
|
214
|
+
.description('Manage workflow schemas [experimental]');
|
|
215
|
+
// Experimental warning
|
|
216
|
+
schemaCmd.hook('preAction', () => {
|
|
217
|
+
console.error('Note: Schema commands are experimental and may change.');
|
|
218
|
+
});
|
|
219
|
+
// schema which
|
|
220
|
+
schemaCmd
|
|
221
|
+
.command('which [name]')
|
|
222
|
+
.description('Show where a schema resolves from')
|
|
223
|
+
.option('--json', 'Output as JSON')
|
|
224
|
+
.option('--all', 'List all schemas with their resolution sources')
|
|
225
|
+
.action(async (name, options) => {
|
|
226
|
+
try {
|
|
227
|
+
const projectRoot = process.cwd();
|
|
228
|
+
if (options?.all) {
|
|
229
|
+
// List all schemas
|
|
230
|
+
const schemas = getAllSchemasWithResolution(projectRoot);
|
|
231
|
+
if (options?.json) {
|
|
232
|
+
console.log(JSON.stringify(schemas, null, 2));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
if (schemas.length === 0) {
|
|
236
|
+
console.log('No schemas found.');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// Group by source
|
|
240
|
+
const bySource = {
|
|
241
|
+
project: schemas.filter((s) => s.source === 'project'),
|
|
242
|
+
user: schemas.filter((s) => s.source === 'user'),
|
|
243
|
+
package: schemas.filter((s) => s.source === 'package'),
|
|
244
|
+
};
|
|
245
|
+
if (bySource.project.length > 0) {
|
|
246
|
+
console.log('\nProject schemas:');
|
|
247
|
+
for (const schema of bySource.project) {
|
|
248
|
+
const shadowInfo = schema.shadows.length > 0
|
|
249
|
+
? ` (shadows: ${schema.shadows.map((s) => s.source).join(', ')})`
|
|
250
|
+
: '';
|
|
251
|
+
console.log(` ${schema.name}${shadowInfo}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (bySource.user.length > 0) {
|
|
255
|
+
console.log('\nUser schemas:');
|
|
256
|
+
for (const schema of bySource.user) {
|
|
257
|
+
const shadowInfo = schema.shadows.length > 0
|
|
258
|
+
? ` (shadows: ${schema.shadows.map((s) => s.source).join(', ')})`
|
|
259
|
+
: '';
|
|
260
|
+
console.log(` ${schema.name}${shadowInfo}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (bySource.package.length > 0) {
|
|
264
|
+
console.log('\nPackage schemas:');
|
|
265
|
+
for (const schema of bySource.package) {
|
|
266
|
+
console.log(` ${schema.name}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (!name) {
|
|
273
|
+
console.error('Error: Schema name is required (or use --all to list all schemas)');
|
|
274
|
+
process.exitCode = 1;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const resolution = getSchemaResolution(name, projectRoot);
|
|
278
|
+
if (!resolution) {
|
|
279
|
+
const available = listSchemas(projectRoot);
|
|
280
|
+
if (options?.json) {
|
|
281
|
+
console.log(JSON.stringify({
|
|
282
|
+
error: `Schema '${name}' not found`,
|
|
283
|
+
available,
|
|
284
|
+
}, null, 2));
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
console.error(`Error: Schema '${name}' not found`);
|
|
288
|
+
console.error(`Available schemas: ${available.join(', ')}`);
|
|
289
|
+
}
|
|
290
|
+
process.exitCode = 1;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (options?.json) {
|
|
294
|
+
console.log(JSON.stringify(resolution, null, 2));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
console.log(`Schema: ${resolution.name}`);
|
|
298
|
+
console.log(`Source: ${resolution.source}`);
|
|
299
|
+
console.log(`Path: ${resolution.path}`);
|
|
300
|
+
if (resolution.shadows.length > 0) {
|
|
301
|
+
console.log('\nShadows:');
|
|
302
|
+
for (const shadow of resolution.shadows) {
|
|
303
|
+
console.log(` ${shadow.source}: ${shadow.path}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
console.error(`Error: ${error.message}`);
|
|
310
|
+
process.exitCode = 1;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
// schema validate
|
|
314
|
+
schemaCmd
|
|
315
|
+
.command('validate [name]')
|
|
316
|
+
.description('Validate a schema structure and templates')
|
|
317
|
+
.option('--json', 'Output as JSON')
|
|
318
|
+
.option('--verbose', 'Show detailed validation steps')
|
|
319
|
+
.action(async (name, options) => {
|
|
320
|
+
try {
|
|
321
|
+
const projectRoot = process.cwd();
|
|
322
|
+
if (!name) {
|
|
323
|
+
// Validate all project schemas
|
|
324
|
+
const projectSchemasDir = getProjectSchemasDir(projectRoot);
|
|
325
|
+
if (!fs.existsSync(projectSchemasDir)) {
|
|
326
|
+
if (options?.json) {
|
|
327
|
+
console.log(JSON.stringify({
|
|
328
|
+
valid: true,
|
|
329
|
+
message: 'No project schemas directory found',
|
|
330
|
+
schemas: [],
|
|
331
|
+
}, null, 2));
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
console.log('No project schemas directory found.');
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const entries = fs.readdirSync(projectSchemasDir, { withFileTypes: true });
|
|
339
|
+
const schemaResults = [];
|
|
340
|
+
let anyInvalid = false;
|
|
341
|
+
for (const entry of entries) {
|
|
342
|
+
if (!entry.isDirectory())
|
|
343
|
+
continue;
|
|
344
|
+
const schemaDir = path.join(projectSchemasDir, entry.name);
|
|
345
|
+
const schemaPath = path.join(schemaDir, 'schema.yaml');
|
|
346
|
+
if (!fs.existsSync(schemaPath))
|
|
347
|
+
continue;
|
|
348
|
+
if (options?.verbose && !options?.json) {
|
|
349
|
+
console.log(`\nValidating ${entry.name}...`);
|
|
350
|
+
}
|
|
351
|
+
const result = validateSchema(schemaDir, options?.verbose && !options?.json);
|
|
352
|
+
schemaResults.push({
|
|
353
|
+
name: entry.name,
|
|
354
|
+
path: schemaDir,
|
|
355
|
+
valid: result.valid,
|
|
356
|
+
issues: result.issues,
|
|
357
|
+
});
|
|
358
|
+
if (!result.valid) {
|
|
359
|
+
anyInvalid = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (options?.json) {
|
|
363
|
+
console.log(JSON.stringify({
|
|
364
|
+
valid: !anyInvalid,
|
|
365
|
+
schemas: schemaResults,
|
|
366
|
+
}, null, 2));
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
if (schemaResults.length === 0) {
|
|
370
|
+
console.log('No schemas found in project.');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
console.log('\nValidation Results:');
|
|
374
|
+
for (const result of schemaResults) {
|
|
375
|
+
const status = result.valid ? '✓' : '✗';
|
|
376
|
+
console.log(` ${status} ${result.name}`);
|
|
377
|
+
for (const issue of result.issues) {
|
|
378
|
+
console.log(` ${issue.level}: ${issue.message}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (anyInvalid) {
|
|
382
|
+
process.exitCode = 1;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
// Validate specific schema
|
|
388
|
+
const schemaDir = getSchemaDir(name, projectRoot);
|
|
389
|
+
if (!schemaDir) {
|
|
390
|
+
const available = listSchemas(projectRoot);
|
|
391
|
+
if (options?.json) {
|
|
392
|
+
console.log(JSON.stringify({
|
|
393
|
+
valid: false,
|
|
394
|
+
error: `Schema '${name}' not found`,
|
|
395
|
+
available,
|
|
396
|
+
}, null, 2));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
console.error(`Error: Schema '${name}' not found`);
|
|
400
|
+
console.error(`Available schemas: ${available.join(', ')}`);
|
|
401
|
+
}
|
|
402
|
+
process.exitCode = 1;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (options?.verbose && !options?.json) {
|
|
406
|
+
console.log(`Validating ${name}...`);
|
|
407
|
+
}
|
|
408
|
+
const result = validateSchema(schemaDir, options?.verbose && !options?.json);
|
|
409
|
+
if (options?.json) {
|
|
410
|
+
console.log(JSON.stringify({
|
|
411
|
+
name,
|
|
412
|
+
path: schemaDir,
|
|
413
|
+
valid: result.valid,
|
|
414
|
+
issues: result.issues,
|
|
415
|
+
}, null, 2));
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
if (result.valid) {
|
|
419
|
+
console.log(`✓ Schema '${name}' is valid`);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
console.log(`✗ Schema '${name}' has errors:`);
|
|
423
|
+
for (const issue of result.issues) {
|
|
424
|
+
console.log(` ${issue.level}: ${issue.message}`);
|
|
425
|
+
}
|
|
426
|
+
process.exitCode = 1;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (options?.json) {
|
|
432
|
+
console.log(JSON.stringify({
|
|
433
|
+
valid: false,
|
|
434
|
+
error: error.message,
|
|
435
|
+
}, null, 2));
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
console.error(`Error: ${error.message}`);
|
|
439
|
+
}
|
|
440
|
+
process.exitCode = 1;
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
// schema fork
|
|
444
|
+
schemaCmd
|
|
445
|
+
.command('fork <source> [name]')
|
|
446
|
+
.description('Copy an existing schema to project for customization')
|
|
447
|
+
.option('--json', 'Output as JSON')
|
|
448
|
+
.option('--force', 'Overwrite existing destination')
|
|
449
|
+
.action(async (source, name, options) => {
|
|
450
|
+
const spinner = options?.json ? null : ora();
|
|
451
|
+
try {
|
|
452
|
+
const projectRoot = process.cwd();
|
|
453
|
+
const destinationName = name || `${source}-custom`;
|
|
454
|
+
// Validate destination name
|
|
455
|
+
if (!isValidSchemaName(destinationName)) {
|
|
456
|
+
if (options?.json) {
|
|
457
|
+
console.log(JSON.stringify({
|
|
458
|
+
forked: false,
|
|
459
|
+
error: `Invalid schema name '${destinationName}'. Use kebab-case (e.g., my-workflow)`,
|
|
460
|
+
}, null, 2));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.error(`Error: Invalid schema name '${destinationName}'`);
|
|
464
|
+
console.error('Schema names must be kebab-case (e.g., my-workflow)');
|
|
465
|
+
}
|
|
466
|
+
process.exitCode = 1;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
// Find source schema
|
|
470
|
+
const sourceDir = getSchemaDir(source, projectRoot);
|
|
471
|
+
if (!sourceDir) {
|
|
472
|
+
const available = listSchemas(projectRoot);
|
|
473
|
+
if (options?.json) {
|
|
474
|
+
console.log(JSON.stringify({
|
|
475
|
+
forked: false,
|
|
476
|
+
error: `Schema '${source}' not found`,
|
|
477
|
+
available,
|
|
478
|
+
}, null, 2));
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
console.error(`Error: Schema '${source}' not found`);
|
|
482
|
+
console.error(`Available schemas: ${available.join(', ')}`);
|
|
483
|
+
}
|
|
484
|
+
process.exitCode = 1;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// Determine source location
|
|
488
|
+
const sourceResolution = getSchemaResolution(source, projectRoot);
|
|
489
|
+
const sourceLocation = sourceResolution?.source || 'package';
|
|
490
|
+
// Check destination
|
|
491
|
+
const destinationDir = path.join(getProjectSchemasDir(projectRoot), destinationName);
|
|
492
|
+
if (fs.existsSync(destinationDir)) {
|
|
493
|
+
if (!options?.force) {
|
|
494
|
+
if (options?.json) {
|
|
495
|
+
console.log(JSON.stringify({
|
|
496
|
+
forked: false,
|
|
497
|
+
error: `Schema '${destinationName}' already exists`,
|
|
498
|
+
suggestion: 'Use --force to overwrite',
|
|
499
|
+
}, null, 2));
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
console.error(`Error: Schema '${destinationName}' already exists at ${destinationDir}`);
|
|
503
|
+
console.error('Use --force to overwrite');
|
|
504
|
+
}
|
|
505
|
+
process.exitCode = 1;
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
// Remove existing
|
|
509
|
+
if (spinner)
|
|
510
|
+
spinner.start(`Removing existing schema '${destinationName}'...`);
|
|
511
|
+
fs.rmSync(destinationDir, { recursive: true });
|
|
512
|
+
}
|
|
513
|
+
// Copy schema
|
|
514
|
+
if (spinner)
|
|
515
|
+
spinner.start(`Forking '${source}' to '${destinationName}'...`);
|
|
516
|
+
copyDirRecursive(sourceDir, destinationDir);
|
|
517
|
+
// Update name in schema.yaml
|
|
518
|
+
const destSchemaPath = path.join(destinationDir, 'schema.yaml');
|
|
519
|
+
const schemaContent = fs.readFileSync(destSchemaPath, 'utf-8');
|
|
520
|
+
const schema = parseSchema(schemaContent);
|
|
521
|
+
schema.name = destinationName;
|
|
522
|
+
fs.writeFileSync(destSchemaPath, stringifyYaml(schema));
|
|
523
|
+
if (spinner)
|
|
524
|
+
spinner.succeed(`Forked '${source}' to '${destinationName}'`);
|
|
525
|
+
if (options?.json) {
|
|
526
|
+
console.log(JSON.stringify({
|
|
527
|
+
forked: true,
|
|
528
|
+
source,
|
|
529
|
+
sourcePath: sourceDir,
|
|
530
|
+
sourceLocation,
|
|
531
|
+
destination: destinationName,
|
|
532
|
+
destinationPath: destinationDir,
|
|
533
|
+
}, null, 2));
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
console.log(`\nSource: ${sourceDir} (${sourceLocation})`);
|
|
537
|
+
console.log(`Destination: ${destinationDir}`);
|
|
538
|
+
console.log(`\nYou can now customize the schema at:`);
|
|
539
|
+
console.log(` ${destinationDir}/schema.yaml`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
if (spinner)
|
|
544
|
+
spinner.fail(`Fork failed`);
|
|
545
|
+
if (options?.json) {
|
|
546
|
+
console.log(JSON.stringify({
|
|
547
|
+
forked: false,
|
|
548
|
+
error: error.message,
|
|
549
|
+
}, null, 2));
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
console.error(`Error: ${error.message}`);
|
|
553
|
+
}
|
|
554
|
+
process.exitCode = 1;
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
// schema init
|
|
558
|
+
schemaCmd
|
|
559
|
+
.command('init <name>')
|
|
560
|
+
.description('Create a new project-local schema')
|
|
561
|
+
.option('--json', 'Output as JSON')
|
|
562
|
+
.option('--description <text>', 'Schema description')
|
|
563
|
+
.option('--artifacts <list>', 'Comma-separated artifact IDs (proposal,specs,design,tasks)')
|
|
564
|
+
.option('--default', 'Set as project default schema')
|
|
565
|
+
.option('--no-default', 'Do not prompt to set as default')
|
|
566
|
+
.option('--force', 'Overwrite existing schema')
|
|
567
|
+
.action(async (name, options) => {
|
|
568
|
+
const spinner = options?.json ? null : ora();
|
|
569
|
+
try {
|
|
570
|
+
const projectRoot = process.cwd();
|
|
571
|
+
// Validate name
|
|
572
|
+
if (!isValidSchemaName(name)) {
|
|
573
|
+
if (options?.json) {
|
|
574
|
+
console.log(JSON.stringify({
|
|
575
|
+
created: false,
|
|
576
|
+
error: `Invalid schema name '${name}'. Use kebab-case (e.g., my-workflow)`,
|
|
577
|
+
}, null, 2));
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
console.error(`Error: Invalid schema name '${name}'`);
|
|
581
|
+
console.error('Schema names must be kebab-case (e.g., my-workflow)');
|
|
582
|
+
}
|
|
583
|
+
process.exitCode = 1;
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const schemaDir = path.join(getProjectSchemasDir(projectRoot), name);
|
|
587
|
+
// Check if exists
|
|
588
|
+
if (fs.existsSync(schemaDir)) {
|
|
589
|
+
if (!options?.force) {
|
|
590
|
+
if (options?.json) {
|
|
591
|
+
console.log(JSON.stringify({
|
|
592
|
+
created: false,
|
|
593
|
+
error: `Schema '${name}' already exists`,
|
|
594
|
+
suggestion: 'Use --force to overwrite or "openspec schema fork" to copy',
|
|
595
|
+
}, null, 2));
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
console.error(`Error: Schema '${name}' already exists at ${schemaDir}`);
|
|
599
|
+
console.error('Use --force to overwrite or "openspec schema fork" to copy');
|
|
600
|
+
}
|
|
601
|
+
process.exitCode = 1;
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (spinner)
|
|
605
|
+
spinner.start(`Removing existing schema '${name}'...`);
|
|
606
|
+
fs.rmSync(schemaDir, { recursive: true });
|
|
607
|
+
}
|
|
608
|
+
// Determine artifacts and description
|
|
609
|
+
let description;
|
|
610
|
+
let selectedArtifactIds;
|
|
611
|
+
// Check if we have explicit flags (non-interactive mode)
|
|
612
|
+
const hasExplicitOptions = options?.description !== undefined || options?.artifacts !== undefined;
|
|
613
|
+
const isInteractive = !options?.json && !hasExplicitOptions && process.stdout.isTTY;
|
|
614
|
+
if (isInteractive) {
|
|
615
|
+
// Interactive mode
|
|
616
|
+
const { input, checkbox, confirm } = await import('@inquirer/prompts');
|
|
617
|
+
description = await input({
|
|
618
|
+
message: 'Schema description:',
|
|
619
|
+
default: `Custom workflow schema for ${name}`,
|
|
620
|
+
});
|
|
621
|
+
const artifactChoices = DEFAULT_ARTIFACTS.map((a) => ({
|
|
622
|
+
name: a.id,
|
|
623
|
+
value: a.id,
|
|
624
|
+
checked: true,
|
|
625
|
+
}));
|
|
626
|
+
selectedArtifactIds = await checkbox({
|
|
627
|
+
message: 'Select artifacts to include:',
|
|
628
|
+
choices: artifactChoices,
|
|
629
|
+
});
|
|
630
|
+
if (selectedArtifactIds.length === 0) {
|
|
631
|
+
console.error('Error: At least one artifact must be selected');
|
|
632
|
+
process.exitCode = 1;
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
// Ask about setting as default (unless --no-default was passed)
|
|
636
|
+
if (options?.default === undefined) {
|
|
637
|
+
const setAsDefault = await confirm({
|
|
638
|
+
message: 'Set as project default schema?',
|
|
639
|
+
default: false,
|
|
640
|
+
});
|
|
641
|
+
if (setAsDefault) {
|
|
642
|
+
options = { ...options, default: true };
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
// Non-interactive mode
|
|
648
|
+
description = options?.description || `Custom workflow schema for ${name}`;
|
|
649
|
+
if (options?.artifacts) {
|
|
650
|
+
selectedArtifactIds = options.artifacts.split(',').map((a) => a.trim());
|
|
651
|
+
// Validate artifact IDs
|
|
652
|
+
const validIds = DEFAULT_ARTIFACTS.map((a) => a.id);
|
|
653
|
+
for (const id of selectedArtifactIds) {
|
|
654
|
+
if (!validIds.includes(id)) {
|
|
655
|
+
if (options?.json) {
|
|
656
|
+
console.log(JSON.stringify({
|
|
657
|
+
created: false,
|
|
658
|
+
error: `Unknown artifact '${id}'`,
|
|
659
|
+
valid: validIds,
|
|
660
|
+
}, null, 2));
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
console.error(`Error: Unknown artifact '${id}'`);
|
|
664
|
+
console.error(`Valid artifacts: ${validIds.join(', ')}`);
|
|
665
|
+
}
|
|
666
|
+
process.exitCode = 1;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
// Default to all artifacts
|
|
673
|
+
selectedArtifactIds = DEFAULT_ARTIFACTS.map((a) => a.id);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Create schema directory
|
|
677
|
+
if (spinner)
|
|
678
|
+
spinner.start(`Creating schema '${name}'...`);
|
|
679
|
+
fs.mkdirSync(schemaDir, { recursive: true });
|
|
680
|
+
// Build artifacts array with proper dependencies
|
|
681
|
+
const selectedArtifacts = selectedArtifactIds.map((id) => {
|
|
682
|
+
const template = DEFAULT_ARTIFACTS.find((a) => a.id === id);
|
|
683
|
+
const artifact = {
|
|
684
|
+
id: template.id,
|
|
685
|
+
generates: template.generates,
|
|
686
|
+
description: template.description,
|
|
687
|
+
template: template.template,
|
|
688
|
+
requires: [],
|
|
689
|
+
};
|
|
690
|
+
// Set up dependencies based on typical workflow
|
|
691
|
+
if (id === 'specs' && selectedArtifactIds.includes('proposal')) {
|
|
692
|
+
artifact.requires = ['proposal'];
|
|
693
|
+
}
|
|
694
|
+
else if (id === 'design' && selectedArtifactIds.includes('specs')) {
|
|
695
|
+
artifact.requires = ['specs'];
|
|
696
|
+
}
|
|
697
|
+
else if (id === 'tasks') {
|
|
698
|
+
const requires = [];
|
|
699
|
+
if (selectedArtifactIds.includes('design'))
|
|
700
|
+
requires.push('design');
|
|
701
|
+
else if (selectedArtifactIds.includes('specs'))
|
|
702
|
+
requires.push('specs');
|
|
703
|
+
artifact.requires = requires;
|
|
704
|
+
}
|
|
705
|
+
return artifact;
|
|
706
|
+
});
|
|
707
|
+
// Create schema.yaml
|
|
708
|
+
const schema = {
|
|
709
|
+
name,
|
|
710
|
+
version: 1,
|
|
711
|
+
description,
|
|
712
|
+
artifacts: selectedArtifacts,
|
|
713
|
+
};
|
|
714
|
+
// Add apply phase if tasks is included
|
|
715
|
+
if (selectedArtifactIds.includes('tasks')) {
|
|
716
|
+
schema.apply = {
|
|
717
|
+
requires: ['tasks'],
|
|
718
|
+
tracks: 'tasks.md',
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
fs.writeFileSync(path.join(schemaDir, 'schema.yaml'), stringifyYaml(schema));
|
|
722
|
+
// Create template files in templates/ subdirectory (standard location)
|
|
723
|
+
const templatesDir = path.join(schemaDir, 'templates');
|
|
724
|
+
for (const artifact of selectedArtifacts) {
|
|
725
|
+
const templatePath = path.join(templatesDir, artifact.template);
|
|
726
|
+
const templateDir = path.dirname(templatePath);
|
|
727
|
+
if (!fs.existsSync(templateDir)) {
|
|
728
|
+
fs.mkdirSync(templateDir, { recursive: true });
|
|
729
|
+
}
|
|
730
|
+
// Create default template content
|
|
731
|
+
const templateContent = createDefaultTemplate(artifact.id);
|
|
732
|
+
fs.writeFileSync(templatePath, templateContent);
|
|
733
|
+
}
|
|
734
|
+
// Update config if --default
|
|
735
|
+
if (options?.default) {
|
|
736
|
+
const configPath = path.join(projectRoot, 'openspec', 'config.yaml');
|
|
737
|
+
if (fs.existsSync(configPath)) {
|
|
738
|
+
const { parse: parseYaml, stringify: stringifyYaml2 } = await import('yaml');
|
|
739
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
740
|
+
const config = parseYaml(configContent) || {};
|
|
741
|
+
config.defaultSchema = name;
|
|
742
|
+
fs.writeFileSync(configPath, stringifyYaml2(config));
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
// Create config file
|
|
746
|
+
const configDir = path.dirname(configPath);
|
|
747
|
+
if (!fs.existsSync(configDir)) {
|
|
748
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
749
|
+
}
|
|
750
|
+
fs.writeFileSync(configPath, stringifyYaml({ defaultSchema: name }));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (spinner)
|
|
754
|
+
spinner.succeed(`Created schema '${name}'`);
|
|
755
|
+
if (options?.json) {
|
|
756
|
+
console.log(JSON.stringify({
|
|
757
|
+
created: true,
|
|
758
|
+
path: schemaDir,
|
|
759
|
+
schema: name,
|
|
760
|
+
artifacts: selectedArtifactIds,
|
|
761
|
+
setAsDefault: options?.default || false,
|
|
762
|
+
}, null, 2));
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
console.log(`\nSchema created at: ${schemaDir}`);
|
|
766
|
+
console.log(`\nArtifacts: ${selectedArtifactIds.join(', ')}`);
|
|
767
|
+
if (options?.default) {
|
|
768
|
+
console.log(`\nSet as project default schema.`);
|
|
769
|
+
}
|
|
770
|
+
console.log(`\nNext steps:`);
|
|
771
|
+
console.log(` 1. Edit ${schemaDir}/schema.yaml to customize artifacts`);
|
|
772
|
+
console.log(` 2. Modify templates in the schema directory`);
|
|
773
|
+
console.log(` 3. Use with: openspec new --schema ${name}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
if (spinner)
|
|
778
|
+
spinner.fail(`Creation failed`);
|
|
779
|
+
if (options?.json) {
|
|
780
|
+
console.log(JSON.stringify({
|
|
781
|
+
created: false,
|
|
782
|
+
error: error.message,
|
|
783
|
+
}, null, 2));
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
console.error(`Error: ${error.message}`);
|
|
787
|
+
}
|
|
788
|
+
process.exitCode = 1;
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Create default template content for an artifact.
|
|
794
|
+
*/
|
|
795
|
+
function createDefaultTemplate(artifactId) {
|
|
796
|
+
switch (artifactId) {
|
|
797
|
+
case 'proposal':
|
|
798
|
+
return `## Why
|
|
799
|
+
|
|
800
|
+
<!-- Describe the motivation for this change -->
|
|
801
|
+
|
|
802
|
+
## What Changes
|
|
803
|
+
|
|
804
|
+
<!-- Describe what will change -->
|
|
805
|
+
|
|
806
|
+
## Capabilities
|
|
807
|
+
|
|
808
|
+
### New Capabilities
|
|
809
|
+
<!-- List new capabilities -->
|
|
810
|
+
|
|
811
|
+
### Modified Capabilities
|
|
812
|
+
<!-- List modified capabilities -->
|
|
813
|
+
|
|
814
|
+
## Impact
|
|
815
|
+
|
|
816
|
+
<!-- Describe the impact on existing functionality -->
|
|
817
|
+
`;
|
|
818
|
+
case 'specs':
|
|
819
|
+
return `## ADDED Requirements
|
|
820
|
+
|
|
821
|
+
### Requirement: Example requirement
|
|
822
|
+
|
|
823
|
+
Description of the requirement.
|
|
824
|
+
|
|
825
|
+
#### Scenario: Example scenario
|
|
826
|
+
- **WHEN** some condition
|
|
827
|
+
- **THEN** some outcome
|
|
828
|
+
`;
|
|
829
|
+
case 'design':
|
|
830
|
+
return `## Context
|
|
831
|
+
|
|
832
|
+
<!-- Background and context -->
|
|
833
|
+
|
|
834
|
+
## Goals / Non-Goals
|
|
835
|
+
|
|
836
|
+
**Goals:**
|
|
837
|
+
<!-- List goals -->
|
|
838
|
+
|
|
839
|
+
**Non-Goals:**
|
|
840
|
+
<!-- List non-goals -->
|
|
841
|
+
|
|
842
|
+
## Decisions
|
|
843
|
+
|
|
844
|
+
### 1. Decision Name
|
|
845
|
+
|
|
846
|
+
Description and rationale.
|
|
847
|
+
|
|
848
|
+
**Alternatives considered:**
|
|
849
|
+
- Alternative 1: Rejected because...
|
|
850
|
+
|
|
851
|
+
## Risks / Trade-offs
|
|
852
|
+
|
|
853
|
+
<!-- List risks and trade-offs -->
|
|
854
|
+
`;
|
|
855
|
+
case 'tasks':
|
|
856
|
+
return `## Implementation Tasks
|
|
857
|
+
|
|
858
|
+
- [ ] Task 1
|
|
859
|
+
- [ ] Task 2
|
|
860
|
+
- [ ] Task 3
|
|
861
|
+
`;
|
|
862
|
+
default:
|
|
863
|
+
return `## ${artifactId}
|
|
864
|
+
|
|
865
|
+
<!-- Add content here -->
|
|
866
|
+
`;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
//# sourceMappingURL=schema.js.map
|