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,64 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Zod schema for project configuration.
4
+ *
5
+ * Purpose:
6
+ * 1. Documentation - clearly defines the config file structure
7
+ * 2. Type safety - TypeScript infers ProjectConfig type from schema
8
+ * 3. Runtime validation - uses safeParse() for resilient field-by-field validation
9
+ *
10
+ * Why Zod over manual validation:
11
+ * - Helps understand OpenSpec's data interfaces at a glance
12
+ * - Single source of truth for type and validation
13
+ * - Consistent with other OpenSpec schemas
14
+ */
15
+ export declare const ProjectConfigSchema: z.ZodObject<{
16
+ schema: z.ZodString;
17
+ context: z.ZodOptional<z.ZodString>;
18
+ rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
19
+ }, z.core.$strip>;
20
+ export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
21
+ /**
22
+ * Read and parse openspec/config.yaml from project root.
23
+ * Uses resilient parsing - validates each field independently using Zod safeParse.
24
+ * Returns null if file doesn't exist.
25
+ * Returns partial config if some fields are invalid (with warnings).
26
+ *
27
+ * Performance note (Jan 2025):
28
+ * Benchmarks showed direct file reads are fast enough without caching:
29
+ * - Typical config (1KB): ~0.5ms per read
30
+ * - Large config (50KB): ~1.6ms per read
31
+ * - Missing config: ~0.01ms per read
32
+ * Config is read 1-2 times per command (schema resolution + instruction loading),
33
+ * adding ~1-3ms total overhead. Caching would add complexity (mtime checks,
34
+ * invalidation logic) for negligible benefit. Direct reads also ensure config
35
+ * changes are reflected immediately without stale cache issues.
36
+ *
37
+ * @param projectRoot - The root directory of the project (where `openspec/` lives)
38
+ * @returns Parsed config or null if file doesn't exist
39
+ */
40
+ export declare function readProjectConfig(projectRoot: string): ProjectConfig | null;
41
+ /**
42
+ * Validate artifact IDs in rules against a schema's artifacts.
43
+ * Called during instruction loading (when schema is known).
44
+ * Returns warnings for unknown artifact IDs.
45
+ *
46
+ * @param rules - The rules object from config
47
+ * @param validArtifactIds - Set of valid artifact IDs from the schema
48
+ * @param schemaName - Name of the schema for error messages
49
+ * @returns Array of warning messages for unknown artifact IDs
50
+ */
51
+ export declare function validateConfigRules(rules: Record<string, string[]>, validArtifactIds: Set<string>, schemaName: string): string[];
52
+ /**
53
+ * Suggest valid schema names when user provides invalid schema.
54
+ * Uses fuzzy matching to find similar names.
55
+ *
56
+ * @param invalidSchemaName - The invalid schema name from config
57
+ * @param availableSchemas - List of available schemas with their type (built-in or project-local)
58
+ * @returns Error message with suggestions and available schemas
59
+ */
60
+ export declare function suggestSchemas(invalidSchemaName: string, availableSchemas: {
61
+ name: string;
62
+ isBuiltIn: boolean;
63
+ }[]): string;
64
+ //# sourceMappingURL=project-config.d.ts.map
@@ -0,0 +1,223 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import { z } from 'zod';
5
+ /**
6
+ * Zod schema for project configuration.
7
+ *
8
+ * Purpose:
9
+ * 1. Documentation - clearly defines the config file structure
10
+ * 2. Type safety - TypeScript infers ProjectConfig type from schema
11
+ * 3. Runtime validation - uses safeParse() for resilient field-by-field validation
12
+ *
13
+ * Why Zod over manual validation:
14
+ * - Helps understand OpenSpec's data interfaces at a glance
15
+ * - Single source of truth for type and validation
16
+ * - Consistent with other OpenSpec schemas
17
+ */
18
+ export const ProjectConfigSchema = z.object({
19
+ // Required: which schema to use (e.g., "spec-driven", "tdd", or project-local schema name)
20
+ schema: z
21
+ .string()
22
+ .min(1)
23
+ .describe('The workflow schema to use (e.g., "spec-driven", "tdd")'),
24
+ // Optional: project context (injected into all artifact instructions)
25
+ // Max size: 50KB (enforced during parsing)
26
+ context: z
27
+ .string()
28
+ .optional()
29
+ .describe('Project context injected into all artifact instructions'),
30
+ // Optional: per-artifact rules (additive to schema's built-in guidance)
31
+ rules: z
32
+ .record(z.string(), // artifact ID
33
+ z.array(z.string()) // list of rules
34
+ )
35
+ .optional()
36
+ .describe('Per-artifact rules, keyed by artifact ID'),
37
+ });
38
+ const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit
39
+ /**
40
+ * Read and parse openspec/config.yaml from project root.
41
+ * Uses resilient parsing - validates each field independently using Zod safeParse.
42
+ * Returns null if file doesn't exist.
43
+ * Returns partial config if some fields are invalid (with warnings).
44
+ *
45
+ * Performance note (Jan 2025):
46
+ * Benchmarks showed direct file reads are fast enough without caching:
47
+ * - Typical config (1KB): ~0.5ms per read
48
+ * - Large config (50KB): ~1.6ms per read
49
+ * - Missing config: ~0.01ms per read
50
+ * Config is read 1-2 times per command (schema resolution + instruction loading),
51
+ * adding ~1-3ms total overhead. Caching would add complexity (mtime checks,
52
+ * invalidation logic) for negligible benefit. Direct reads also ensure config
53
+ * changes are reflected immediately without stale cache issues.
54
+ *
55
+ * @param projectRoot - The root directory of the project (where `openspec/` lives)
56
+ * @returns Parsed config or null if file doesn't exist
57
+ */
58
+ export function readProjectConfig(projectRoot) {
59
+ // Try both .yaml and .yml, prefer .yaml
60
+ let configPath = path.join(projectRoot, 'openspec', 'config.yaml');
61
+ if (!existsSync(configPath)) {
62
+ configPath = path.join(projectRoot, 'openspec', 'config.yml');
63
+ if (!existsSync(configPath)) {
64
+ return null; // No config is OK
65
+ }
66
+ }
67
+ try {
68
+ const content = readFileSync(configPath, 'utf-8');
69
+ const raw = parseYaml(content);
70
+ if (!raw || typeof raw !== 'object') {
71
+ console.warn(`openspec/config.yaml is not a valid YAML object`);
72
+ return null;
73
+ }
74
+ const config = {};
75
+ // Parse schema field using Zod
76
+ const schemaField = z.string().min(1);
77
+ const schemaResult = schemaField.safeParse(raw.schema);
78
+ if (schemaResult.success) {
79
+ config.schema = schemaResult.data;
80
+ }
81
+ else if (raw.schema !== undefined) {
82
+ console.warn(`Invalid 'schema' field in config (must be non-empty string)`);
83
+ }
84
+ // Parse context field with size limit
85
+ if (raw.context !== undefined) {
86
+ const contextField = z.string();
87
+ const contextResult = contextField.safeParse(raw.context);
88
+ if (contextResult.success) {
89
+ const contextSize = Buffer.byteLength(contextResult.data, 'utf-8');
90
+ if (contextSize > MAX_CONTEXT_SIZE) {
91
+ console.warn(`Context too large (${(contextSize / 1024).toFixed(1)}KB, limit: ${MAX_CONTEXT_SIZE / 1024}KB)`);
92
+ console.warn(`Ignoring context field`);
93
+ }
94
+ else {
95
+ config.context = contextResult.data;
96
+ }
97
+ }
98
+ else {
99
+ console.warn(`Invalid 'context' field in config (must be string)`);
100
+ }
101
+ }
102
+ // Parse rules field using Zod
103
+ if (raw.rules !== undefined) {
104
+ const rulesField = z.record(z.string(), z.array(z.string()));
105
+ // First check if it's an object structure (guard against null since typeof null === 'object')
106
+ if (typeof raw.rules === 'object' && raw.rules !== null && !Array.isArray(raw.rules)) {
107
+ const parsedRules = {};
108
+ let hasValidRules = false;
109
+ for (const [artifactId, rules] of Object.entries(raw.rules)) {
110
+ const rulesArrayResult = z.array(z.string()).safeParse(rules);
111
+ if (rulesArrayResult.success) {
112
+ // Filter out empty strings
113
+ const validRules = rulesArrayResult.data.filter((r) => r.length > 0);
114
+ if (validRules.length > 0) {
115
+ parsedRules[artifactId] = validRules;
116
+ hasValidRules = true;
117
+ }
118
+ if (validRules.length < rulesArrayResult.data.length) {
119
+ console.warn(`Some rules for '${artifactId}' are empty strings, ignoring them`);
120
+ }
121
+ }
122
+ else {
123
+ console.warn(`Rules for '${artifactId}' must be an array of strings, ignoring this artifact's rules`);
124
+ }
125
+ }
126
+ if (hasValidRules) {
127
+ config.rules = parsedRules;
128
+ }
129
+ }
130
+ else {
131
+ console.warn(`Invalid 'rules' field in config (must be object)`);
132
+ }
133
+ }
134
+ // Return partial config even if some fields failed
135
+ return Object.keys(config).length > 0 ? config : null;
136
+ }
137
+ catch (error) {
138
+ console.warn(`Failed to parse openspec/config.yaml:`, error);
139
+ return null;
140
+ }
141
+ }
142
+ /**
143
+ * Validate artifact IDs in rules against a schema's artifacts.
144
+ * Called during instruction loading (when schema is known).
145
+ * Returns warnings for unknown artifact IDs.
146
+ *
147
+ * @param rules - The rules object from config
148
+ * @param validArtifactIds - Set of valid artifact IDs from the schema
149
+ * @param schemaName - Name of the schema for error messages
150
+ * @returns Array of warning messages for unknown artifact IDs
151
+ */
152
+ export function validateConfigRules(rules, validArtifactIds, schemaName) {
153
+ const warnings = [];
154
+ for (const artifactId of Object.keys(rules)) {
155
+ if (!validArtifactIds.has(artifactId)) {
156
+ const validIds = Array.from(validArtifactIds).sort().join(', ');
157
+ warnings.push(`Unknown artifact ID in rules: "${artifactId}". ` +
158
+ `Valid IDs for schema "${schemaName}": ${validIds}`);
159
+ }
160
+ }
161
+ return warnings;
162
+ }
163
+ /**
164
+ * Suggest valid schema names when user provides invalid schema.
165
+ * Uses fuzzy matching to find similar names.
166
+ *
167
+ * @param invalidSchemaName - The invalid schema name from config
168
+ * @param availableSchemas - List of available schemas with their type (built-in or project-local)
169
+ * @returns Error message with suggestions and available schemas
170
+ */
171
+ export function suggestSchemas(invalidSchemaName, availableSchemas) {
172
+ // Simple fuzzy match: Levenshtein distance
173
+ function levenshtein(a, b) {
174
+ const matrix = [];
175
+ for (let i = 0; i <= b.length; i++) {
176
+ matrix[i] = [i];
177
+ }
178
+ for (let j = 0; j <= a.length; j++) {
179
+ matrix[0][j] = j;
180
+ }
181
+ for (let i = 1; i <= b.length; i++) {
182
+ for (let j = 1; j <= a.length; j++) {
183
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
184
+ matrix[i][j] = matrix[i - 1][j - 1];
185
+ }
186
+ else {
187
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
188
+ }
189
+ }
190
+ }
191
+ return matrix[b.length][a.length];
192
+ }
193
+ // Find closest matches (distance <= 3)
194
+ const suggestions = availableSchemas
195
+ .map((s) => ({ ...s, distance: levenshtein(invalidSchemaName, s.name) }))
196
+ .filter((s) => s.distance <= 3)
197
+ .sort((a, b) => a.distance - b.distance)
198
+ .slice(0, 3);
199
+ const builtIn = availableSchemas.filter((s) => s.isBuiltIn).map((s) => s.name);
200
+ const projectLocal = availableSchemas.filter((s) => !s.isBuiltIn).map((s) => s.name);
201
+ let message = `Schema '${invalidSchemaName}' not found in openspec/config.yaml\n\n`;
202
+ if (suggestions.length > 0) {
203
+ message += `Did you mean one of these?\n`;
204
+ suggestions.forEach((s) => {
205
+ const type = s.isBuiltIn ? 'built-in' : 'project-local';
206
+ message += ` - ${s.name} (${type})\n`;
207
+ });
208
+ message += '\n';
209
+ }
210
+ message += `Available schemas:\n`;
211
+ if (builtIn.length > 0) {
212
+ message += ` Built-in: ${builtIn.join(', ')}\n`;
213
+ }
214
+ if (projectLocal.length > 0) {
215
+ message += ` Project-local: ${projectLocal.join(', ')}\n`;
216
+ }
217
+ else {
218
+ message += ` Project-local: (none found)\n`;
219
+ }
220
+ message += `\nFix: Edit openspec/config.yaml and change 'schema: ${invalidSchemaName}' to a valid schema name`;
221
+ return message;
222
+ }
223
+ //# sourceMappingURL=project-config.js.map
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ export declare const ScenarioSchema: z.ZodObject<{
3
+ rawText: z.ZodString;
4
+ }, z.core.$strip>;
5
+ export declare const RequirementSchema: z.ZodObject<{
6
+ text: z.ZodString;
7
+ scenarios: z.ZodArray<z.ZodObject<{
8
+ rawText: z.ZodString;
9
+ }, z.core.$strip>>;
10
+ }, z.core.$strip>;
11
+ export type Scenario = z.infer<typeof ScenarioSchema>;
12
+ export type Requirement = z.infer<typeof RequirementSchema>;
13
+ //# sourceMappingURL=base.schema.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { z } from 'zod';
2
+ import { VALIDATION_MESSAGES } from '../validation/constants.js';
3
+ export const ScenarioSchema = z.object({
4
+ rawText: z.string().min(1, VALIDATION_MESSAGES.SCENARIO_EMPTY),
5
+ });
6
+ export const RequirementSchema = z.object({
7
+ text: z.string()
8
+ .min(1, VALIDATION_MESSAGES.REQUIREMENT_EMPTY)
9
+ .refine((text) => text.includes('SHALL') || text.includes('MUST'), VALIDATION_MESSAGES.REQUIREMENT_NO_SHALL),
10
+ scenarios: z.array(ScenarioSchema)
11
+ .min(1, VALIDATION_MESSAGES.REQUIREMENT_NO_SCENARIOS),
12
+ });
13
+ //# sourceMappingURL=base.schema.js.map
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ export declare const DeltaOperationType: z.ZodEnum<{
3
+ ADDED: "ADDED";
4
+ MODIFIED: "MODIFIED";
5
+ REMOVED: "REMOVED";
6
+ RENAMED: "RENAMED";
7
+ }>;
8
+ export declare const DeltaSchema: z.ZodObject<{
9
+ spec: z.ZodString;
10
+ operation: z.ZodEnum<{
11
+ ADDED: "ADDED";
12
+ MODIFIED: "MODIFIED";
13
+ REMOVED: "REMOVED";
14
+ RENAMED: "RENAMED";
15
+ }>;
16
+ description: z.ZodString;
17
+ requirement: z.ZodOptional<z.ZodObject<{
18
+ text: z.ZodString;
19
+ scenarios: z.ZodArray<z.ZodObject<{
20
+ rawText: z.ZodString;
21
+ }, z.core.$strip>>;
22
+ }, z.core.$strip>>;
23
+ requirements: z.ZodOptional<z.ZodArray<z.ZodObject<{
24
+ text: z.ZodString;
25
+ scenarios: z.ZodArray<z.ZodObject<{
26
+ rawText: z.ZodString;
27
+ }, z.core.$strip>>;
28
+ }, z.core.$strip>>>;
29
+ rename: z.ZodOptional<z.ZodObject<{
30
+ from: z.ZodString;
31
+ to: z.ZodString;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>;
34
+ export declare const ChangeSchema: z.ZodObject<{
35
+ name: z.ZodString;
36
+ why: z.ZodString;
37
+ whatChanges: z.ZodString;
38
+ deltas: z.ZodArray<z.ZodObject<{
39
+ spec: z.ZodString;
40
+ operation: z.ZodEnum<{
41
+ ADDED: "ADDED";
42
+ MODIFIED: "MODIFIED";
43
+ REMOVED: "REMOVED";
44
+ RENAMED: "RENAMED";
45
+ }>;
46
+ description: z.ZodString;
47
+ requirement: z.ZodOptional<z.ZodObject<{
48
+ text: z.ZodString;
49
+ scenarios: z.ZodArray<z.ZodObject<{
50
+ rawText: z.ZodString;
51
+ }, z.core.$strip>>;
52
+ }, z.core.$strip>>;
53
+ requirements: z.ZodOptional<z.ZodArray<z.ZodObject<{
54
+ text: z.ZodString;
55
+ scenarios: z.ZodArray<z.ZodObject<{
56
+ rawText: z.ZodString;
57
+ }, z.core.$strip>>;
58
+ }, z.core.$strip>>>;
59
+ rename: z.ZodOptional<z.ZodObject<{
60
+ from: z.ZodString;
61
+ to: z.ZodString;
62
+ }, z.core.$strip>>;
63
+ }, z.core.$strip>>;
64
+ metadata: z.ZodOptional<z.ZodObject<{
65
+ version: z.ZodDefault<z.ZodString>;
66
+ format: z.ZodLiteral<"openspec-change">;
67
+ sourcePath: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>>;
69
+ }, z.core.$strip>;
70
+ export type DeltaOperation = z.infer<typeof DeltaOperationType>;
71
+ export type Delta = z.infer<typeof DeltaSchema>;
72
+ export type Change = z.infer<typeof ChangeSchema>;
73
+ //# sourceMappingURL=change.schema.d.ts.map
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import { RequirementSchema } from './base.schema.js';
3
+ import { MIN_WHY_SECTION_LENGTH, MAX_WHY_SECTION_LENGTH, MAX_DELTAS_PER_CHANGE, VALIDATION_MESSAGES } from '../validation/constants.js';
4
+ export const DeltaOperationType = z.enum(['ADDED', 'MODIFIED', 'REMOVED', 'RENAMED']);
5
+ export const DeltaSchema = z.object({
6
+ spec: z.string().min(1, VALIDATION_MESSAGES.DELTA_SPEC_EMPTY),
7
+ operation: DeltaOperationType,
8
+ description: z.string().min(1, VALIDATION_MESSAGES.DELTA_DESCRIPTION_EMPTY),
9
+ requirement: RequirementSchema.optional(),
10
+ requirements: z.array(RequirementSchema).optional(),
11
+ rename: z.object({
12
+ from: z.string(),
13
+ to: z.string(),
14
+ }).optional(),
15
+ });
16
+ export const ChangeSchema = z.object({
17
+ name: z.string().min(1, VALIDATION_MESSAGES.CHANGE_NAME_EMPTY),
18
+ why: z.string()
19
+ .min(MIN_WHY_SECTION_LENGTH, VALIDATION_MESSAGES.CHANGE_WHY_TOO_SHORT)
20
+ .max(MAX_WHY_SECTION_LENGTH, VALIDATION_MESSAGES.CHANGE_WHY_TOO_LONG),
21
+ whatChanges: z.string().min(1, VALIDATION_MESSAGES.CHANGE_WHAT_EMPTY),
22
+ deltas: z.array(DeltaSchema)
23
+ .min(1, VALIDATION_MESSAGES.CHANGE_NO_DELTAS)
24
+ .max(MAX_DELTAS_PER_CHANGE, VALIDATION_MESSAGES.CHANGE_TOO_MANY_DELTAS),
25
+ metadata: z.object({
26
+ version: z.string().default('1.0.0'),
27
+ format: z.literal('openspec-change'),
28
+ sourcePath: z.string().optional(),
29
+ }).optional(),
30
+ });
31
+ //# sourceMappingURL=change.schema.js.map
@@ -0,0 +1,4 @@
1
+ export { ScenarioSchema, RequirementSchema, type Scenario, type Requirement, } from './base.schema.js';
2
+ export { SpecSchema, type Spec, } from './spec.schema.js';
3
+ export { DeltaOperationType, DeltaSchema, ChangeSchema, type DeltaOperation, type Delta, type Change, } from './change.schema.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export { ScenarioSchema, RequirementSchema, } from './base.schema.js';
2
+ export { SpecSchema, } from './spec.schema.js';
3
+ export { DeltaOperationType, DeltaSchema, ChangeSchema, } from './change.schema.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ export declare const SpecSchema: z.ZodObject<{
3
+ name: z.ZodString;
4
+ overview: z.ZodString;
5
+ requirements: z.ZodArray<z.ZodObject<{
6
+ text: z.ZodString;
7
+ scenarios: z.ZodArray<z.ZodObject<{
8
+ rawText: z.ZodString;
9
+ }, z.core.$strip>>;
10
+ }, z.core.$strip>>;
11
+ metadata: z.ZodOptional<z.ZodObject<{
12
+ version: z.ZodDefault<z.ZodString>;
13
+ format: z.ZodLiteral<"openspec">;
14
+ sourcePath: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$strip>>;
16
+ }, z.core.$strip>;
17
+ export type Spec = z.infer<typeof SpecSchema>;
18
+ //# sourceMappingURL=spec.schema.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import { RequirementSchema } from './base.schema.js';
3
+ import { VALIDATION_MESSAGES } from '../validation/constants.js';
4
+ export const SpecSchema = z.object({
5
+ name: z.string().min(1, VALIDATION_MESSAGES.SPEC_NAME_EMPTY),
6
+ overview: z.string().min(1, VALIDATION_MESSAGES.SPEC_PURPOSE_EMPTY),
7
+ requirements: z.array(RequirementSchema)
8
+ .min(1, VALIDATION_MESSAGES.SPEC_NO_REQUIREMENTS),
9
+ metadata: z.object({
10
+ version: z.string().default('1.0.0'),
11
+ format: z.literal('openspec'),
12
+ sourcePath: z.string().optional(),
13
+ }).optional(),
14
+ });
15
+ //# sourceMappingURL=spec.schema.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared Utilities
3
+ *
4
+ * Common code shared between init and update commands.
5
+ */
6
+ export { SKILL_NAMES, type SkillName, type ToolSkillStatus, type ToolVersionStatus, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
7
+ export { type SkillTemplateEntry, type CommandTemplateEntry, getSkillTemplates, getCommandTemplates, getCommandContents, generateSkillContent, } from './skill-generation.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared Utilities
3
+ *
4
+ * Common code shared between init and update commands.
5
+ */
6
+ export { SKILL_NAMES, getToolsWithSkillsDir, getToolSkillStatus, getToolStates, extractGeneratedByVersion, getToolVersionStatus, getConfiguredTools, getAllToolVersionStatus, } from './tool-detection.js';
7
+ export { getSkillTemplates, getCommandTemplates, getCommandContents, generateSkillContent, } from './skill-generation.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Skill Generation Utilities
3
+ *
4
+ * Shared utilities for generating skill and command files.
5
+ */
6
+ import { getOpsxExploreCommandTemplate, type SkillTemplate } from '../templates/skill-templates.js';
7
+ import type { CommandContent } from '../command-generation/index.js';
8
+ /**
9
+ * Skill template with directory name mapping.
10
+ */
11
+ export interface SkillTemplateEntry {
12
+ template: SkillTemplate;
13
+ dirName: string;
14
+ }
15
+ /**
16
+ * Command template with ID mapping.
17
+ */
18
+ export interface CommandTemplateEntry {
19
+ template: ReturnType<typeof getOpsxExploreCommandTemplate>;
20
+ id: string;
21
+ }
22
+ /**
23
+ * Gets all skill templates with their directory names.
24
+ */
25
+ export declare function getSkillTemplates(): SkillTemplateEntry[];
26
+ /**
27
+ * Gets all command templates with their IDs.
28
+ */
29
+ export declare function getCommandTemplates(): CommandTemplateEntry[];
30
+ /**
31
+ * Converts command templates to CommandContent array.
32
+ */
33
+ export declare function getCommandContents(): CommandContent[];
34
+ /**
35
+ * Generates skill file content with YAML frontmatter.
36
+ *
37
+ * @param template - The skill template
38
+ * @param generatedByVersion - The OpenSpec version to embed in the file
39
+ */
40
+ export declare function generateSkillContent(template: SkillTemplate, generatedByVersion: string): string;
41
+ //# sourceMappingURL=skill-generation.d.ts.map
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Skill Generation Utilities
3
+ *
4
+ * Shared utilities for generating skill and command files.
5
+ */
6
+ import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOpsxExploreCommandTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxBulkArchiveCommandTemplate, getOpsxVerifyCommandTemplate, } from '../templates/skill-templates.js';
7
+ /**
8
+ * Gets all skill templates with their directory names.
9
+ */
10
+ export function getSkillTemplates() {
11
+ return [
12
+ { template: getExploreSkillTemplate(), dirName: 'openspec-explore' },
13
+ { template: getNewChangeSkillTemplate(), dirName: 'openspec-new-change' },
14
+ { template: getContinueChangeSkillTemplate(), dirName: 'openspec-continue-change' },
15
+ { template: getApplyChangeSkillTemplate(), dirName: 'openspec-apply-change' },
16
+ { template: getFfChangeSkillTemplate(), dirName: 'openspec-ff-change' },
17
+ { template: getSyncSpecsSkillTemplate(), dirName: 'openspec-sync-specs' },
18
+ { template: getArchiveChangeSkillTemplate(), dirName: 'openspec-archive-change' },
19
+ { template: getBulkArchiveChangeSkillTemplate(), dirName: 'openspec-bulk-archive-change' },
20
+ { template: getVerifyChangeSkillTemplate(), dirName: 'openspec-verify-change' },
21
+ ];
22
+ }
23
+ /**
24
+ * Gets all command templates with their IDs.
25
+ */
26
+ export function getCommandTemplates() {
27
+ return [
28
+ { template: getOpsxExploreCommandTemplate(), id: 'explore' },
29
+ { template: getOpsxNewCommandTemplate(), id: 'new' },
30
+ { template: getOpsxContinueCommandTemplate(), id: 'continue' },
31
+ { template: getOpsxApplyCommandTemplate(), id: 'apply' },
32
+ { template: getOpsxFfCommandTemplate(), id: 'ff' },
33
+ { template: getOpsxSyncCommandTemplate(), id: 'sync' },
34
+ { template: getOpsxArchiveCommandTemplate(), id: 'archive' },
35
+ { template: getOpsxBulkArchiveCommandTemplate(), id: 'bulk-archive' },
36
+ { template: getOpsxVerifyCommandTemplate(), id: 'verify' },
37
+ ];
38
+ }
39
+ /**
40
+ * Converts command templates to CommandContent array.
41
+ */
42
+ export function getCommandContents() {
43
+ const commandTemplates = getCommandTemplates();
44
+ return commandTemplates.map(({ template, id }) => ({
45
+ id,
46
+ name: template.name,
47
+ description: template.description,
48
+ category: template.category,
49
+ tags: template.tags,
50
+ body: template.content,
51
+ }));
52
+ }
53
+ /**
54
+ * Generates skill file content with YAML frontmatter.
55
+ *
56
+ * @param template - The skill template
57
+ * @param generatedByVersion - The OpenSpec version to embed in the file
58
+ */
59
+ export function generateSkillContent(template, generatedByVersion) {
60
+ return `---
61
+ name: ${template.name}
62
+ description: ${template.description}
63
+ license: ${template.license || 'MIT'}
64
+ compatibility: ${template.compatibility || 'Requires openspec CLI.'}
65
+ metadata:
66
+ author: ${template.metadata?.author || 'openspec'}
67
+ version: "${template.metadata?.version || '1.0'}"
68
+ generatedBy: "${generatedByVersion}"
69
+ ---
70
+
71
+ ${template.instructions}
72
+ `;
73
+ }
74
+ //# sourceMappingURL=skill-generation.js.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Tool Detection Utilities
3
+ *
4
+ * Shared utilities for detecting tool configurations and version status.
5
+ */
6
+ /**
7
+ * Names of skill directories created by openspec init.
8
+ */
9
+ export declare const SKILL_NAMES: readonly ["openspec-explore", "openspec-new-change", "openspec-continue-change", "openspec-apply-change", "openspec-ff-change", "openspec-sync-specs", "openspec-archive-change", "openspec-bulk-archive-change", "openspec-verify-change"];
10
+ export type SkillName = (typeof SKILL_NAMES)[number];
11
+ /**
12
+ * Status of skill configuration for a tool.
13
+ */
14
+ export interface ToolSkillStatus {
15
+ /** Whether the tool has any skills configured */
16
+ configured: boolean;
17
+ /** Whether all 9 skills are configured */
18
+ fullyConfigured: boolean;
19
+ /** Number of skills currently configured (0-9) */
20
+ skillCount: number;
21
+ }
22
+ /**
23
+ * Version information for a tool's skills.
24
+ */
25
+ export interface ToolVersionStatus {
26
+ /** The tool ID */
27
+ toolId: string;
28
+ /** The tool's display name */
29
+ toolName: string;
30
+ /** Whether the tool has any skills configured */
31
+ configured: boolean;
32
+ /** The generatedBy version found in the skill files, or null if not found */
33
+ generatedByVersion: string | null;
34
+ /** Whether the tool needs updating (version mismatch or missing) */
35
+ needsUpdate: boolean;
36
+ }
37
+ /**
38
+ * Gets the list of tools with skillsDir configured.
39
+ */
40
+ export declare function getToolsWithSkillsDir(): string[];
41
+ /**
42
+ * Checks which skill files exist for a tool.
43
+ */
44
+ export declare function getToolSkillStatus(projectRoot: string, toolId: string): ToolSkillStatus;
45
+ /**
46
+ * Gets the skill status for all tools with skillsDir configured.
47
+ */
48
+ export declare function getToolStates(projectRoot: string): Map<string, ToolSkillStatus>;
49
+ /**
50
+ * Extracts the generatedBy version from a skill file's YAML frontmatter.
51
+ * Returns null if the field is not found or the file doesn't exist.
52
+ */
53
+ export declare function extractGeneratedByVersion(skillFilePath: string): string | null;
54
+ /**
55
+ * Gets version status for a tool by reading the first available skill file.
56
+ */
57
+ export declare function getToolVersionStatus(projectRoot: string, toolId: string, currentVersion: string): ToolVersionStatus;
58
+ /**
59
+ * Gets all configured tools in the project.
60
+ */
61
+ export declare function getConfiguredTools(projectRoot: string): string[];
62
+ /**
63
+ * Gets version status for all configured tools.
64
+ */
65
+ export declare function getAllToolVersionStatus(projectRoot: string, currentVersion: string): ToolVersionStatus[];
66
+ //# sourceMappingURL=tool-detection.d.ts.map