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.
Files changed (235) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +153 -0
  3. package/bin/openspec.js +3 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +480 -0
  6. package/dist/commands/change.d.ts +35 -0
  7. package/dist/commands/change.js +277 -0
  8. package/dist/commands/completion.d.ts +72 -0
  9. package/dist/commands/completion.js +257 -0
  10. package/dist/commands/config.d.ts +8 -0
  11. package/dist/commands/config.js +198 -0
  12. package/dist/commands/feedback.d.ts +9 -0
  13. package/dist/commands/feedback.js +183 -0
  14. package/dist/commands/schema.d.ts +6 -0
  15. package/dist/commands/schema.js +869 -0
  16. package/dist/commands/show.d.ts +14 -0
  17. package/dist/commands/show.js +132 -0
  18. package/dist/commands/spec.d.ts +15 -0
  19. package/dist/commands/spec.js +225 -0
  20. package/dist/commands/validate.d.ts +24 -0
  21. package/dist/commands/validate.js +294 -0
  22. package/dist/commands/workflow/index.d.ts +17 -0
  23. package/dist/commands/workflow/index.js +12 -0
  24. package/dist/commands/workflow/instructions.d.ts +29 -0
  25. package/dist/commands/workflow/instructions.js +381 -0
  26. package/dist/commands/workflow/new-change.d.ts +11 -0
  27. package/dist/commands/workflow/new-change.js +44 -0
  28. package/dist/commands/workflow/schemas.d.ts +10 -0
  29. package/dist/commands/workflow/schemas.js +34 -0
  30. package/dist/commands/workflow/shared.d.ts +52 -0
  31. package/dist/commands/workflow/shared.js +111 -0
  32. package/dist/commands/workflow/status.d.ts +14 -0
  33. package/dist/commands/workflow/status.js +58 -0
  34. package/dist/commands/workflow/templates.d.ts +16 -0
  35. package/dist/commands/workflow/templates.js +68 -0
  36. package/dist/core/archive.d.ts +11 -0
  37. package/dist/core/archive.js +280 -0
  38. package/dist/core/artifact-graph/graph.d.ts +56 -0
  39. package/dist/core/artifact-graph/graph.js +141 -0
  40. package/dist/core/artifact-graph/index.d.ts +7 -0
  41. package/dist/core/artifact-graph/index.js +13 -0
  42. package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
  43. package/dist/core/artifact-graph/instruction-loader.js +214 -0
  44. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  45. package/dist/core/artifact-graph/resolver.js +257 -0
  46. package/dist/core/artifact-graph/schema.d.ts +13 -0
  47. package/dist/core/artifact-graph/schema.js +108 -0
  48. package/dist/core/artifact-graph/state.d.ts +12 -0
  49. package/dist/core/artifact-graph/state.js +54 -0
  50. package/dist/core/artifact-graph/types.d.ts +45 -0
  51. package/dist/core/artifact-graph/types.js +43 -0
  52. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  53. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  54. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  55. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  56. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  57. package/dist/core/command-generation/adapters/auggie.js +27 -0
  58. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  59. package/dist/core/command-generation/adapters/claude.js +50 -0
  60. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  61. package/dist/core/command-generation/adapters/cline.js +27 -0
  62. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  63. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  64. package/dist/core/command-generation/adapters/codex.d.ts +13 -0
  65. package/dist/core/command-generation/adapters/codex.js +27 -0
  66. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  67. package/dist/core/command-generation/adapters/continue.js +28 -0
  68. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  69. package/dist/core/command-generation/adapters/costrict.js +27 -0
  70. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/crush.js +30 -0
  72. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  73. package/dist/core/command-generation/adapters/cursor.js +44 -0
  74. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  75. package/dist/core/command-generation/adapters/factory.js +27 -0
  76. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  77. package/dist/core/command-generation/adapters/gemini.js +26 -0
  78. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  79. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  80. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/iflow.js +29 -0
  82. package/dist/core/command-generation/adapters/index.d.ts +27 -0
  83. package/dist/core/command-generation/adapters/index.js +27 -0
  84. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  85. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  86. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/opencode.js +26 -0
  88. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  89. package/dist/core/command-generation/adapters/qoder.js +30 -0
  90. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  91. package/dist/core/command-generation/adapters/qwen.js +26 -0
  92. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  93. package/dist/core/command-generation/adapters/roocode.js +27 -0
  94. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  95. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  96. package/dist/core/command-generation/generator.d.ts +21 -0
  97. package/dist/core/command-generation/generator.js +27 -0
  98. package/dist/core/command-generation/index.d.ts +22 -0
  99. package/dist/core/command-generation/index.js +24 -0
  100. package/dist/core/command-generation/registry.d.ts +36 -0
  101. package/dist/core/command-generation/registry.js +88 -0
  102. package/dist/core/command-generation/types.d.ts +55 -0
  103. package/dist/core/command-generation/types.js +8 -0
  104. package/dist/core/completions/command-registry.d.ts +7 -0
  105. package/dist/core/completions/command-registry.js +456 -0
  106. package/dist/core/completions/completion-provider.d.ts +60 -0
  107. package/dist/core/completions/completion-provider.js +102 -0
  108. package/dist/core/completions/factory.d.ts +64 -0
  109. package/dist/core/completions/factory.js +75 -0
  110. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  111. package/dist/core/completions/generators/bash-generator.js +174 -0
  112. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  113. package/dist/core/completions/generators/fish-generator.js +157 -0
  114. package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
  115. package/dist/core/completions/generators/powershell-generator.js +207 -0
  116. package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
  117. package/dist/core/completions/generators/zsh-generator.js +250 -0
  118. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  119. package/dist/core/completions/installers/bash-installer.js +318 -0
  120. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  121. package/dist/core/completions/installers/fish-installer.js +143 -0
  122. package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
  123. package/dist/core/completions/installers/powershell-installer.js +327 -0
  124. package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
  125. package/dist/core/completions/installers/zsh-installer.js +449 -0
  126. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  127. package/dist/core/completions/templates/bash-templates.js +24 -0
  128. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  129. package/dist/core/completions/templates/fish-templates.js +39 -0
  130. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  131. package/dist/core/completions/templates/powershell-templates.js +25 -0
  132. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  133. package/dist/core/completions/templates/zsh-templates.js +36 -0
  134. package/dist/core/completions/types.d.ts +79 -0
  135. package/dist/core/completions/types.js +2 -0
  136. package/dist/core/config-prompts.d.ts +9 -0
  137. package/dist/core/config-prompts.js +34 -0
  138. package/dist/core/config-schema.d.ts +76 -0
  139. package/dist/core/config-schema.js +200 -0
  140. package/dist/core/config.d.ts +17 -0
  141. package/dist/core/config.js +30 -0
  142. package/dist/core/converters/json-converter.d.ts +6 -0
  143. package/dist/core/converters/json-converter.js +51 -0
  144. package/dist/core/global-config.d.ts +39 -0
  145. package/dist/core/global-config.js +115 -0
  146. package/dist/core/index.d.ts +2 -0
  147. package/dist/core/index.js +3 -0
  148. package/dist/core/init.d.ts +32 -0
  149. package/dist/core/init.js +433 -0
  150. package/dist/core/legacy-cleanup.d.ts +162 -0
  151. package/dist/core/legacy-cleanup.js +501 -0
  152. package/dist/core/list.d.ts +9 -0
  153. package/dist/core/list.js +171 -0
  154. package/dist/core/parsers/change-parser.d.ts +13 -0
  155. package/dist/core/parsers/change-parser.js +193 -0
  156. package/dist/core/parsers/markdown-parser.d.ts +22 -0
  157. package/dist/core/parsers/markdown-parser.js +187 -0
  158. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  159. package/dist/core/parsers/requirement-blocks.js +201 -0
  160. package/dist/core/project-config.d.ts +64 -0
  161. package/dist/core/project-config.js +223 -0
  162. package/dist/core/schemas/base.schema.d.ts +13 -0
  163. package/dist/core/schemas/base.schema.js +13 -0
  164. package/dist/core/schemas/change.schema.d.ts +73 -0
  165. package/dist/core/schemas/change.schema.js +31 -0
  166. package/dist/core/schemas/index.d.ts +4 -0
  167. package/dist/core/schemas/index.js +4 -0
  168. package/dist/core/schemas/spec.schema.d.ts +18 -0
  169. package/dist/core/schemas/spec.schema.js +15 -0
  170. package/dist/core/shared/index.d.ts +8 -0
  171. package/dist/core/shared/index.js +8 -0
  172. package/dist/core/shared/skill-generation.d.ts +41 -0
  173. package/dist/core/shared/skill-generation.js +74 -0
  174. package/dist/core/shared/tool-detection.d.ts +66 -0
  175. package/dist/core/shared/tool-detection.js +140 -0
  176. package/dist/core/specs-apply.d.ts +73 -0
  177. package/dist/core/specs-apply.js +384 -0
  178. package/dist/core/styles/palette.d.ts +7 -0
  179. package/dist/core/styles/palette.js +8 -0
  180. package/dist/core/templates/index.d.ts +8 -0
  181. package/dist/core/templates/index.js +9 -0
  182. package/dist/core/templates/skill-templates.d.ts +112 -0
  183. package/dist/core/templates/skill-templates.js +2893 -0
  184. package/dist/core/update.d.ts +42 -0
  185. package/dist/core/update.js +306 -0
  186. package/dist/core/validation/constants.d.ts +34 -0
  187. package/dist/core/validation/constants.js +40 -0
  188. package/dist/core/validation/types.d.ts +18 -0
  189. package/dist/core/validation/types.js +2 -0
  190. package/dist/core/validation/validator.d.ts +33 -0
  191. package/dist/core/validation/validator.js +409 -0
  192. package/dist/core/view.d.ts +8 -0
  193. package/dist/core/view.js +168 -0
  194. package/dist/index.d.ts +3 -0
  195. package/dist/index.js +3 -0
  196. package/dist/prompts/searchable-multi-select.d.ts +27 -0
  197. package/dist/prompts/searchable-multi-select.js +149 -0
  198. package/dist/telemetry/config.d.ts +32 -0
  199. package/dist/telemetry/config.js +68 -0
  200. package/dist/telemetry/index.d.ts +31 -0
  201. package/dist/telemetry/index.js +145 -0
  202. package/dist/ui/ascii-patterns.d.ts +16 -0
  203. package/dist/ui/ascii-patterns.js +133 -0
  204. package/dist/ui/welcome-screen.d.ts +10 -0
  205. package/dist/ui/welcome-screen.js +146 -0
  206. package/dist/utils/change-metadata.d.ts +51 -0
  207. package/dist/utils/change-metadata.js +147 -0
  208. package/dist/utils/change-utils.d.ts +62 -0
  209. package/dist/utils/change-utils.js +121 -0
  210. package/dist/utils/file-system.d.ts +36 -0
  211. package/dist/utils/file-system.js +281 -0
  212. package/dist/utils/index.d.ts +5 -0
  213. package/dist/utils/index.js +7 -0
  214. package/dist/utils/interactive.d.ts +18 -0
  215. package/dist/utils/interactive.js +21 -0
  216. package/dist/utils/item-discovery.d.ts +4 -0
  217. package/dist/utils/item-discovery.js +72 -0
  218. package/dist/utils/match.d.ts +3 -0
  219. package/dist/utils/match.js +22 -0
  220. package/dist/utils/shell-detection.d.ts +20 -0
  221. package/dist/utils/shell-detection.js +41 -0
  222. package/dist/utils/task-progress.d.ts +8 -0
  223. package/dist/utils/task-progress.js +36 -0
  224. package/package.json +84 -0
  225. package/schemas/spec-driven/schema.yaml +148 -0
  226. package/schemas/spec-driven/templates/design.md +19 -0
  227. package/schemas/spec-driven/templates/proposal.md +23 -0
  228. package/schemas/spec-driven/templates/spec.md +8 -0
  229. package/schemas/spec-driven/templates/tasks.md +9 -0
  230. package/schemas/tdd/schema.yaml +213 -0
  231. package/schemas/tdd/templates/docs.md +15 -0
  232. package/schemas/tdd/templates/implementation.md +11 -0
  233. package/schemas/tdd/templates/spec.md +11 -0
  234. package/schemas/tdd/templates/test.md +11 -0
  235. 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