@wangjibins/openspec 1.2.2

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 (272) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +203 -0
  3. package/bin/openspec.js +3 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +482 -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 +36 -0
  11. package/dist/commands/config.js +552 -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 +57 -0
  31. package/dist/commands/workflow/shared.js +116 -0
  32. package/dist/commands/workflow/status.d.ts +14 -0
  33. package/dist/commands/workflow/status.js +75 -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 +318 -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/available-tools.d.ts +16 -0
  53. package/dist/core/available-tools.js +30 -0
  54. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  55. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  56. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  57. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  58. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  59. package/dist/core/command-generation/adapters/auggie.js +27 -0
  60. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  61. package/dist/core/command-generation/adapters/claude.js +50 -0
  62. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  63. package/dist/core/command-generation/adapters/cline.js +27 -0
  64. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  65. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  66. package/dist/core/command-generation/adapters/codeflicker.d.ts +14 -0
  67. package/dist/core/command-generation/adapters/codeflicker.js +44 -0
  68. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  69. package/dist/core/command-generation/adapters/codex.js +39 -0
  70. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/continue.js +28 -0
  72. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  73. package/dist/core/command-generation/adapters/costrict.js +27 -0
  74. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  75. package/dist/core/command-generation/adapters/crush.js +30 -0
  76. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  77. package/dist/core/command-generation/adapters/cursor.js +44 -0
  78. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  79. package/dist/core/command-generation/adapters/factory.js +27 -0
  80. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/gemini.js +26 -0
  82. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  84. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  85. package/dist/core/command-generation/adapters/iflow.js +29 -0
  86. package/dist/core/command-generation/adapters/index.d.ts +30 -0
  87. package/dist/core/command-generation/adapters/index.js +30 -0
  88. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  89. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  90. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  91. package/dist/core/command-generation/adapters/kiro.js +26 -0
  92. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  93. package/dist/core/command-generation/adapters/opencode.js +29 -0
  94. package/dist/core/command-generation/adapters/pi.d.ts +14 -0
  95. package/dist/core/command-generation/adapters/pi.js +41 -0
  96. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/qoder.js +30 -0
  98. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/qwen.js +26 -0
  100. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  101. package/dist/core/command-generation/adapters/roocode.js +27 -0
  102. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  103. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  104. package/dist/core/command-generation/generator.d.ts +21 -0
  105. package/dist/core/command-generation/generator.js +27 -0
  106. package/dist/core/command-generation/index.d.ts +22 -0
  107. package/dist/core/command-generation/index.js +24 -0
  108. package/dist/core/command-generation/registry.d.ts +36 -0
  109. package/dist/core/command-generation/registry.js +94 -0
  110. package/dist/core/command-generation/types.d.ts +56 -0
  111. package/dist/core/command-generation/types.js +8 -0
  112. package/dist/core/completions/command-registry.d.ts +7 -0
  113. package/dist/core/completions/command-registry.js +461 -0
  114. package/dist/core/completions/completion-provider.d.ts +60 -0
  115. package/dist/core/completions/completion-provider.js +102 -0
  116. package/dist/core/completions/factory.d.ts +64 -0
  117. package/dist/core/completions/factory.js +75 -0
  118. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  119. package/dist/core/completions/generators/bash-generator.js +174 -0
  120. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  121. package/dist/core/completions/generators/fish-generator.js +157 -0
  122. package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
  123. package/dist/core/completions/generators/powershell-generator.js +207 -0
  124. package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
  125. package/dist/core/completions/generators/zsh-generator.js +250 -0
  126. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  127. package/dist/core/completions/installers/bash-installer.js +318 -0
  128. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  129. package/dist/core/completions/installers/fish-installer.js +143 -0
  130. package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
  131. package/dist/core/completions/installers/powershell-installer.js +327 -0
  132. package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
  133. package/dist/core/completions/installers/zsh-installer.js +449 -0
  134. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  135. package/dist/core/completions/templates/bash-templates.js +24 -0
  136. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  137. package/dist/core/completions/templates/fish-templates.js +39 -0
  138. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  139. package/dist/core/completions/templates/powershell-templates.js +25 -0
  140. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  141. package/dist/core/completions/templates/zsh-templates.js +36 -0
  142. package/dist/core/completions/types.d.ts +79 -0
  143. package/dist/core/completions/types.js +2 -0
  144. package/dist/core/config-prompts.d.ts +9 -0
  145. package/dist/core/config-prompts.js +34 -0
  146. package/dist/core/config-schema.d.ts +86 -0
  147. package/dist/core/config-schema.js +213 -0
  148. package/dist/core/config.d.ts +17 -0
  149. package/dist/core/config.js +34 -0
  150. package/dist/core/converters/json-converter.d.ts +6 -0
  151. package/dist/core/converters/json-converter.js +51 -0
  152. package/dist/core/global-config.d.ts +44 -0
  153. package/dist/core/global-config.js +125 -0
  154. package/dist/core/index.d.ts +2 -0
  155. package/dist/core/index.js +3 -0
  156. package/dist/core/init.d.ts +37 -0
  157. package/dist/core/init.js +593 -0
  158. package/dist/core/legacy-cleanup.d.ts +162 -0
  159. package/dist/core/legacy-cleanup.js +512 -0
  160. package/dist/core/list.d.ts +9 -0
  161. package/dist/core/list.js +171 -0
  162. package/dist/core/migration.d.ts +23 -0
  163. package/dist/core/migration.js +108 -0
  164. package/dist/core/parsers/change-parser.d.ts +13 -0
  165. package/dist/core/parsers/change-parser.js +193 -0
  166. package/dist/core/parsers/markdown-parser.d.ts +22 -0
  167. package/dist/core/parsers/markdown-parser.js +187 -0
  168. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  169. package/dist/core/parsers/requirement-blocks.js +201 -0
  170. package/dist/core/profile-sync-drift.d.ts +38 -0
  171. package/dist/core/profile-sync-drift.js +200 -0
  172. package/dist/core/profiles.d.ts +26 -0
  173. package/dist/core/profiles.js +40 -0
  174. package/dist/core/project-config.d.ts +64 -0
  175. package/dist/core/project-config.js +223 -0
  176. package/dist/core/schemas/base.schema.d.ts +13 -0
  177. package/dist/core/schemas/base.schema.js +13 -0
  178. package/dist/core/schemas/change.schema.d.ts +73 -0
  179. package/dist/core/schemas/change.schema.js +31 -0
  180. package/dist/core/schemas/index.d.ts +4 -0
  181. package/dist/core/schemas/index.js +4 -0
  182. package/dist/core/schemas/spec.schema.d.ts +18 -0
  183. package/dist/core/schemas/spec.schema.js +15 -0
  184. package/dist/core/shared/index.d.ts +8 -0
  185. package/dist/core/shared/index.js +8 -0
  186. package/dist/core/shared/skill-generation.d.ts +49 -0
  187. package/dist/core/shared/skill-generation.js +96 -0
  188. package/dist/core/shared/tool-detection.d.ts +71 -0
  189. package/dist/core/shared/tool-detection.js +158 -0
  190. package/dist/core/specs-apply.d.ts +73 -0
  191. package/dist/core/specs-apply.js +384 -0
  192. package/dist/core/styles/palette.d.ts +7 -0
  193. package/dist/core/styles/palette.js +8 -0
  194. package/dist/core/templates/index.d.ts +8 -0
  195. package/dist/core/templates/index.js +9 -0
  196. package/dist/core/templates/skill-templates.d.ts +19 -0
  197. package/dist/core/templates/skill-templates.js +18 -0
  198. package/dist/core/templates/types.d.ts +19 -0
  199. package/dist/core/templates/types.js +5 -0
  200. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  201. package/dist/core/templates/workflows/apply-change.js +308 -0
  202. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  203. package/dist/core/templates/workflows/archive-change.js +271 -0
  204. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  205. package/dist/core/templates/workflows/bulk-archive-change.js +488 -0
  206. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  207. package/dist/core/templates/workflows/continue-change.js +232 -0
  208. package/dist/core/templates/workflows/explore.d.ts +10 -0
  209. package/dist/core/templates/workflows/explore.js +461 -0
  210. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  211. package/dist/core/templates/workflows/feedback.js +108 -0
  212. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  213. package/dist/core/templates/workflows/ff-change.js +198 -0
  214. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  215. package/dist/core/templates/workflows/new-change.js +143 -0
  216. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  217. package/dist/core/templates/workflows/onboard.js +565 -0
  218. package/dist/core/templates/workflows/propose.d.ts +10 -0
  219. package/dist/core/templates/workflows/propose.js +216 -0
  220. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  221. package/dist/core/templates/workflows/sync-specs.js +272 -0
  222. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  223. package/dist/core/templates/workflows/verify-change.js +332 -0
  224. package/dist/core/update.d.ts +77 -0
  225. package/dist/core/update.js +537 -0
  226. package/dist/core/validation/constants.d.ts +34 -0
  227. package/dist/core/validation/constants.js +40 -0
  228. package/dist/core/validation/types.d.ts +18 -0
  229. package/dist/core/validation/types.js +2 -0
  230. package/dist/core/validation/validator.d.ts +33 -0
  231. package/dist/core/validation/validator.js +409 -0
  232. package/dist/core/view.d.ts +8 -0
  233. package/dist/core/view.js +168 -0
  234. package/dist/index.d.ts +3 -0
  235. package/dist/index.js +3 -0
  236. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  237. package/dist/prompts/searchable-multi-select.js +159 -0
  238. package/dist/telemetry/config.d.ts +32 -0
  239. package/dist/telemetry/config.js +68 -0
  240. package/dist/telemetry/index.d.ts +31 -0
  241. package/dist/telemetry/index.js +145 -0
  242. package/dist/ui/ascii-patterns.d.ts +16 -0
  243. package/dist/ui/ascii-patterns.js +133 -0
  244. package/dist/ui/welcome-screen.d.ts +10 -0
  245. package/dist/ui/welcome-screen.js +146 -0
  246. package/dist/utils/change-metadata.d.ts +51 -0
  247. package/dist/utils/change-metadata.js +147 -0
  248. package/dist/utils/change-utils.d.ts +62 -0
  249. package/dist/utils/change-utils.js +121 -0
  250. package/dist/utils/command-references.d.ts +18 -0
  251. package/dist/utils/command-references.js +20 -0
  252. package/dist/utils/file-system.d.ts +36 -0
  253. package/dist/utils/file-system.js +281 -0
  254. package/dist/utils/index.d.ts +6 -0
  255. package/dist/utils/index.js +9 -0
  256. package/dist/utils/interactive.d.ts +18 -0
  257. package/dist/utils/interactive.js +21 -0
  258. package/dist/utils/item-discovery.d.ts +4 -0
  259. package/dist/utils/item-discovery.js +72 -0
  260. package/dist/utils/match.d.ts +3 -0
  261. package/dist/utils/match.js +22 -0
  262. package/dist/utils/shell-detection.d.ts +20 -0
  263. package/dist/utils/shell-detection.js +41 -0
  264. package/dist/utils/task-progress.d.ts +8 -0
  265. package/dist/utils/task-progress.js +36 -0
  266. package/package.json +83 -0
  267. package/schemas/spec-driven/schema.yaml +153 -0
  268. package/schemas/spec-driven/templates/design.md +19 -0
  269. package/schemas/spec-driven/templates/proposal.md +23 -0
  270. package/schemas/spec-driven/templates/spec.md +8 -0
  271. package/schemas/spec-driven/templates/tasks.md +9 -0
  272. package/scripts/postinstall.js +147 -0
@@ -0,0 +1,171 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
4
+ import { readFileSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { MarkdownParser } from './parsers/markdown-parser.js';
7
+ /**
8
+ * Get the most recent modification time of any file in a directory (recursive).
9
+ * Falls back to the directory's own mtime if no files are found.
10
+ */
11
+ async function getLastModified(dirPath) {
12
+ let latest = null;
13
+ async function walk(dir) {
14
+ const entries = await fs.readdir(dir, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ const fullPath = path.join(dir, entry.name);
17
+ if (entry.isDirectory()) {
18
+ await walk(fullPath);
19
+ }
20
+ else {
21
+ const stat = await fs.stat(fullPath);
22
+ if (latest === null || stat.mtime > latest) {
23
+ latest = stat.mtime;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ await walk(dirPath);
29
+ // If no files found, use the directory's own modification time
30
+ if (latest === null) {
31
+ const dirStat = await fs.stat(dirPath);
32
+ return dirStat.mtime;
33
+ }
34
+ return latest;
35
+ }
36
+ /**
37
+ * Format a date as relative time (e.g., "2 hours ago", "3 days ago")
38
+ */
39
+ function formatRelativeTime(date) {
40
+ const now = new Date();
41
+ const diffMs = now.getTime() - date.getTime();
42
+ const diffSecs = Math.floor(diffMs / 1000);
43
+ const diffMins = Math.floor(diffSecs / 60);
44
+ const diffHours = Math.floor(diffMins / 60);
45
+ const diffDays = Math.floor(diffHours / 24);
46
+ if (diffDays > 30) {
47
+ return date.toLocaleDateString();
48
+ }
49
+ else if (diffDays > 0) {
50
+ return `${diffDays}d ago`;
51
+ }
52
+ else if (diffHours > 0) {
53
+ return `${diffHours}h ago`;
54
+ }
55
+ else if (diffMins > 0) {
56
+ return `${diffMins}m ago`;
57
+ }
58
+ else {
59
+ return 'just now';
60
+ }
61
+ }
62
+ export class ListCommand {
63
+ async execute(targetPath = '.', mode = 'changes', options = {}) {
64
+ const { sort = 'recent', json = false } = options;
65
+ if (mode === 'changes') {
66
+ const changesDir = path.join(targetPath, 'openspec', 'changes');
67
+ // Check if changes directory exists
68
+ try {
69
+ await fs.access(changesDir);
70
+ }
71
+ catch {
72
+ throw new Error("No OpenSpec changes directory found. Run 'openspec init' first.");
73
+ }
74
+ // Get all directories in changes (excluding archive)
75
+ const entries = await fs.readdir(changesDir, { withFileTypes: true });
76
+ const changeDirs = entries
77
+ .filter(entry => entry.isDirectory() && entry.name !== 'archive')
78
+ .map(entry => entry.name);
79
+ if (changeDirs.length === 0) {
80
+ if (json) {
81
+ console.log(JSON.stringify({ changes: [] }));
82
+ }
83
+ else {
84
+ console.log('No active changes found.');
85
+ }
86
+ return;
87
+ }
88
+ // Collect information about each change
89
+ const changes = [];
90
+ for (const changeDir of changeDirs) {
91
+ const progress = await getTaskProgressForChange(changesDir, changeDir);
92
+ const changePath = path.join(changesDir, changeDir);
93
+ const lastModified = await getLastModified(changePath);
94
+ changes.push({
95
+ name: changeDir,
96
+ completedTasks: progress.completed,
97
+ totalTasks: progress.total,
98
+ lastModified
99
+ });
100
+ }
101
+ // Sort by preference (default: recent first)
102
+ if (sort === 'recent') {
103
+ changes.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
104
+ }
105
+ else {
106
+ changes.sort((a, b) => a.name.localeCompare(b.name));
107
+ }
108
+ // JSON output for programmatic use
109
+ if (json) {
110
+ const jsonOutput = changes.map(c => ({
111
+ name: c.name,
112
+ completedTasks: c.completedTasks,
113
+ totalTasks: c.totalTasks,
114
+ lastModified: c.lastModified.toISOString(),
115
+ status: c.totalTasks === 0 ? 'no-tasks' : c.completedTasks === c.totalTasks ? 'complete' : 'in-progress'
116
+ }));
117
+ console.log(JSON.stringify({ changes: jsonOutput }, null, 2));
118
+ return;
119
+ }
120
+ // Display results
121
+ console.log('Changes:');
122
+ const padding = ' ';
123
+ const nameWidth = Math.max(...changes.map(c => c.name.length));
124
+ for (const change of changes) {
125
+ const paddedName = change.name.padEnd(nameWidth);
126
+ const status = formatTaskStatus({ total: change.totalTasks, completed: change.completedTasks });
127
+ const timeAgo = formatRelativeTime(change.lastModified);
128
+ console.log(`${padding}${paddedName} ${status.padEnd(12)} ${timeAgo}`);
129
+ }
130
+ return;
131
+ }
132
+ // specs mode
133
+ const specsDir = path.join(targetPath, 'openspec', 'specs');
134
+ try {
135
+ await fs.access(specsDir);
136
+ }
137
+ catch {
138
+ console.log('No specs found.');
139
+ return;
140
+ }
141
+ const entries = await fs.readdir(specsDir, { withFileTypes: true });
142
+ const specDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
143
+ if (specDirs.length === 0) {
144
+ console.log('No specs found.');
145
+ return;
146
+ }
147
+ const specs = [];
148
+ for (const id of specDirs) {
149
+ const specPath = join(specsDir, id, 'spec.md');
150
+ try {
151
+ const content = readFileSync(specPath, 'utf-8');
152
+ const parser = new MarkdownParser(content);
153
+ const spec = parser.parseSpec(id);
154
+ specs.push({ id, requirementCount: spec.requirements.length });
155
+ }
156
+ catch {
157
+ // If spec cannot be read or parsed, include with 0 count
158
+ specs.push({ id, requirementCount: 0 });
159
+ }
160
+ }
161
+ specs.sort((a, b) => a.id.localeCompare(b.id));
162
+ console.log('Specs:');
163
+ const padding = ' ';
164
+ const nameWidth = Math.max(...specs.map(s => s.id.length));
165
+ for (const spec of specs) {
166
+ const padded = spec.id.padEnd(nameWidth);
167
+ console.log(`${padding}${padded} requirements ${spec.requirementCount}`);
168
+ }
169
+ }
170
+ }
171
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Migration Utilities
3
+ *
4
+ * One-time migration logic for existing projects when profile system is introduced.
5
+ * Called by both init and update commands before profile resolution.
6
+ */
7
+ import type { AIToolOption } from './config.js';
8
+ /**
9
+ * Scans installed workflow files across all detected tools and returns
10
+ * the union of installed workflow IDs.
11
+ */
12
+ export declare function scanInstalledWorkflows(projectPath: string, tools: AIToolOption[]): string[];
13
+ /**
14
+ * Performs one-time migration if the global config does not yet have a profile field.
15
+ * Called by both init and update before profile resolution.
16
+ *
17
+ * - If no profile field exists and workflows are installed: sets profile to 'custom'
18
+ * with the detected workflows, preserving the user's existing setup.
19
+ * - If no profile field exists and no workflows are installed: no-op (defaults apply).
20
+ * - If profile field already exists: no-op.
21
+ */
22
+ export declare function migrateIfNeeded(projectPath: string, tools: AIToolOption[]): void;
23
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Migration Utilities
3
+ *
4
+ * One-time migration logic for existing projects when profile system is introduced.
5
+ * Called by both init and update commands before profile resolution.
6
+ */
7
+ import { getGlobalConfig, getGlobalConfigPath, saveGlobalConfig } from './global-config.js';
8
+ import { CommandAdapterRegistry } from './command-generation/index.js';
9
+ import { WORKFLOW_TO_SKILL_DIR } from './profile-sync-drift.js';
10
+ import { ALL_WORKFLOWS } from './profiles.js';
11
+ import path from 'path';
12
+ import * as fs from 'fs';
13
+ function scanInstalledWorkflowArtifacts(projectPath, tools) {
14
+ const installed = new Set();
15
+ let hasSkills = false;
16
+ let hasCommands = false;
17
+ for (const tool of tools) {
18
+ if (!tool.skillsDir)
19
+ continue;
20
+ const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
21
+ for (const workflowId of ALL_WORKFLOWS) {
22
+ const skillDirName = WORKFLOW_TO_SKILL_DIR[workflowId];
23
+ const skillFile = path.join(skillsDir, skillDirName, 'SKILL.md');
24
+ if (fs.existsSync(skillFile)) {
25
+ installed.add(workflowId);
26
+ hasSkills = true;
27
+ }
28
+ }
29
+ const adapter = CommandAdapterRegistry.get(tool.value);
30
+ if (!adapter)
31
+ continue;
32
+ for (const workflowId of ALL_WORKFLOWS) {
33
+ const commandPath = adapter.getFilePath(workflowId);
34
+ const fullPath = path.isAbsolute(commandPath)
35
+ ? commandPath
36
+ : path.join(projectPath, commandPath);
37
+ if (fs.existsSync(fullPath)) {
38
+ installed.add(workflowId);
39
+ hasCommands = true;
40
+ }
41
+ }
42
+ }
43
+ return {
44
+ workflows: ALL_WORKFLOWS.filter((workflowId) => installed.has(workflowId)),
45
+ hasSkills,
46
+ hasCommands,
47
+ };
48
+ }
49
+ /**
50
+ * Scans installed workflow files across all detected tools and returns
51
+ * the union of installed workflow IDs.
52
+ */
53
+ export function scanInstalledWorkflows(projectPath, tools) {
54
+ return scanInstalledWorkflowArtifacts(projectPath, tools).workflows;
55
+ }
56
+ function inferDelivery(artifacts) {
57
+ if (artifacts.hasSkills && artifacts.hasCommands) {
58
+ return 'both';
59
+ }
60
+ if (artifacts.hasCommands) {
61
+ return 'commands';
62
+ }
63
+ return 'skills';
64
+ }
65
+ /**
66
+ * Performs one-time migration if the global config does not yet have a profile field.
67
+ * Called by both init and update before profile resolution.
68
+ *
69
+ * - If no profile field exists and workflows are installed: sets profile to 'custom'
70
+ * with the detected workflows, preserving the user's existing setup.
71
+ * - If no profile field exists and no workflows are installed: no-op (defaults apply).
72
+ * - If profile field already exists: no-op.
73
+ */
74
+ export function migrateIfNeeded(projectPath, tools) {
75
+ const config = getGlobalConfig();
76
+ // Check raw config file for profile field presence
77
+ const configPath = getGlobalConfigPath();
78
+ let rawConfig = {};
79
+ try {
80
+ if (fs.existsSync(configPath)) {
81
+ rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
82
+ }
83
+ }
84
+ catch {
85
+ return; // Can't read config, skip migration
86
+ }
87
+ // If profile is already explicitly set, no migration needed
88
+ if (rawConfig.profile !== undefined) {
89
+ return;
90
+ }
91
+ // Scan for installed workflows
92
+ const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools);
93
+ const installedWorkflows = artifacts.workflows;
94
+ if (installedWorkflows.length === 0) {
95
+ // No workflows installed, new user — defaults will apply
96
+ return;
97
+ }
98
+ // Migrate: set profile to custom with detected workflows
99
+ config.profile = 'custom';
100
+ config.workflows = installedWorkflows;
101
+ if (rawConfig.delivery === undefined) {
102
+ config.delivery = inferDelivery(artifacts);
103
+ }
104
+ saveGlobalConfig(config);
105
+ console.log(`Migrated: custom profile with ${installedWorkflows.length} workflows`);
106
+ console.log("New in this version: /opsx:propose. Try 'openspec config profile core' for the streamlined experience.");
107
+ }
108
+ //# sourceMappingURL=migration.js.map
@@ -0,0 +1,13 @@
1
+ import { MarkdownParser } from './markdown-parser.js';
2
+ import { Change } from '../schemas/index.js';
3
+ export declare class ChangeParser extends MarkdownParser {
4
+ private changeDir;
5
+ constructor(content: string, changeDir: string);
6
+ parseChangeWithDeltas(name: string): Promise<Change>;
7
+ private parseDeltaSpecs;
8
+ private parseSpecDeltas;
9
+ private parseRenames;
10
+ private parseSectionsFromContent;
11
+ private getContentUntilNextHeaderFromLines;
12
+ }
13
+ //# sourceMappingURL=change-parser.d.ts.map
@@ -0,0 +1,193 @@
1
+ import { MarkdownParser } from './markdown-parser.js';
2
+ import path from 'path';
3
+ import { promises as fs } from 'fs';
4
+ export class ChangeParser extends MarkdownParser {
5
+ changeDir;
6
+ constructor(content, changeDir) {
7
+ super(content);
8
+ this.changeDir = changeDir;
9
+ }
10
+ async parseChangeWithDeltas(name) {
11
+ const sections = this.parseSections();
12
+ const why = this.findSection(sections, 'Why')?.content || '';
13
+ const whatChanges = this.findSection(sections, 'What Changes')?.content || '';
14
+ if (!why) {
15
+ throw new Error('Change must have a Why section');
16
+ }
17
+ if (!whatChanges) {
18
+ throw new Error('Change must have a What Changes section');
19
+ }
20
+ // Parse deltas from the What Changes section (simple format)
21
+ const simpleDeltas = this.parseDeltas(whatChanges);
22
+ // Check if there are spec files with delta format
23
+ const specsDir = path.join(this.changeDir, 'specs');
24
+ const deltaDeltas = await this.parseDeltaSpecs(specsDir);
25
+ // Combine both types of deltas, preferring delta format if available
26
+ const deltas = deltaDeltas.length > 0 ? deltaDeltas : simpleDeltas;
27
+ return {
28
+ name,
29
+ why: why.trim(),
30
+ whatChanges: whatChanges.trim(),
31
+ deltas,
32
+ metadata: {
33
+ version: '1.0.0',
34
+ format: 'openspec-change',
35
+ },
36
+ };
37
+ }
38
+ async parseDeltaSpecs(specsDir) {
39
+ const deltas = [];
40
+ try {
41
+ const specDirs = await fs.readdir(specsDir, { withFileTypes: true });
42
+ for (const dir of specDirs) {
43
+ if (!dir.isDirectory())
44
+ continue;
45
+ const specName = dir.name;
46
+ const specFile = path.join(specsDir, specName, 'spec.md');
47
+ try {
48
+ const content = await fs.readFile(specFile, 'utf-8');
49
+ const specDeltas = this.parseSpecDeltas(specName, content);
50
+ deltas.push(...specDeltas);
51
+ }
52
+ catch (error) {
53
+ // Spec file might not exist, which is okay
54
+ continue;
55
+ }
56
+ }
57
+ }
58
+ catch (error) {
59
+ // Specs directory might not exist, which is okay
60
+ return [];
61
+ }
62
+ return deltas;
63
+ }
64
+ parseSpecDeltas(specName, content) {
65
+ const deltas = [];
66
+ const sections = this.parseSectionsFromContent(content);
67
+ // Parse ADDED requirements
68
+ const addedSection = this.findSection(sections, 'ADDED Requirements');
69
+ if (addedSection) {
70
+ const requirements = this.parseRequirements(addedSection);
71
+ requirements.forEach(req => {
72
+ deltas.push({
73
+ spec: specName,
74
+ operation: 'ADDED',
75
+ description: `Add requirement: ${req.text}`,
76
+ // Provide both single and plural forms for compatibility
77
+ requirement: req,
78
+ requirements: [req],
79
+ });
80
+ });
81
+ }
82
+ // Parse MODIFIED requirements
83
+ const modifiedSection = this.findSection(sections, 'MODIFIED Requirements');
84
+ if (modifiedSection) {
85
+ const requirements = this.parseRequirements(modifiedSection);
86
+ requirements.forEach(req => {
87
+ deltas.push({
88
+ spec: specName,
89
+ operation: 'MODIFIED',
90
+ description: `Modify requirement: ${req.text}`,
91
+ requirement: req,
92
+ requirements: [req],
93
+ });
94
+ });
95
+ }
96
+ // Parse REMOVED requirements
97
+ const removedSection = this.findSection(sections, 'REMOVED Requirements');
98
+ if (removedSection) {
99
+ const requirements = this.parseRequirements(removedSection);
100
+ requirements.forEach(req => {
101
+ deltas.push({
102
+ spec: specName,
103
+ operation: 'REMOVED',
104
+ description: `Remove requirement: ${req.text}`,
105
+ requirement: req,
106
+ requirements: [req],
107
+ });
108
+ });
109
+ }
110
+ // Parse RENAMED requirements
111
+ const renamedSection = this.findSection(sections, 'RENAMED Requirements');
112
+ if (renamedSection) {
113
+ const renames = this.parseRenames(renamedSection.content);
114
+ renames.forEach(rename => {
115
+ deltas.push({
116
+ spec: specName,
117
+ operation: 'RENAMED',
118
+ description: `Rename requirement from "${rename.from}" to "${rename.to}"`,
119
+ rename,
120
+ });
121
+ });
122
+ }
123
+ return deltas;
124
+ }
125
+ parseRenames(content) {
126
+ const renames = [];
127
+ const lines = ChangeParser.normalizeContent(content).split('\n');
128
+ let currentRename = {};
129
+ for (const line of lines) {
130
+ const fromMatch = line.match(/^\s*-?\s*FROM:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
131
+ const toMatch = line.match(/^\s*-?\s*TO:\s*`?###\s*Requirement:\s*(.+?)`?\s*$/);
132
+ if (fromMatch) {
133
+ currentRename.from = fromMatch[1].trim();
134
+ }
135
+ else if (toMatch) {
136
+ currentRename.to = toMatch[1].trim();
137
+ if (currentRename.from && currentRename.to) {
138
+ renames.push({
139
+ from: currentRename.from,
140
+ to: currentRename.to,
141
+ });
142
+ currentRename = {};
143
+ }
144
+ }
145
+ }
146
+ return renames;
147
+ }
148
+ parseSectionsFromContent(content) {
149
+ const normalizedContent = ChangeParser.normalizeContent(content);
150
+ const lines = normalizedContent.split('\n');
151
+ const sections = [];
152
+ const stack = [];
153
+ for (let i = 0; i < lines.length; i++) {
154
+ const line = lines[i];
155
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
156
+ if (headerMatch) {
157
+ const level = headerMatch[1].length;
158
+ const title = headerMatch[2].trim();
159
+ const contentLines = this.getContentUntilNextHeaderFromLines(lines, i + 1, level);
160
+ const section = {
161
+ level,
162
+ title,
163
+ content: contentLines.join('\n').trim(),
164
+ children: [],
165
+ };
166
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) {
167
+ stack.pop();
168
+ }
169
+ if (stack.length === 0) {
170
+ sections.push(section);
171
+ }
172
+ else {
173
+ stack[stack.length - 1].children.push(section);
174
+ }
175
+ stack.push(section);
176
+ }
177
+ }
178
+ return sections;
179
+ }
180
+ getContentUntilNextHeaderFromLines(lines, startLine, currentLevel) {
181
+ const contentLines = [];
182
+ for (let i = startLine; i < lines.length; i++) {
183
+ const line = lines[i];
184
+ const headerMatch = line.match(/^(#{1,6})\s+/);
185
+ if (headerMatch && headerMatch[1].length <= currentLevel) {
186
+ break;
187
+ }
188
+ contentLines.push(line);
189
+ }
190
+ return contentLines;
191
+ }
192
+ }
193
+ //# sourceMappingURL=change-parser.js.map
@@ -0,0 +1,22 @@
1
+ import { Spec, Change, Requirement, Scenario, Delta } from '../schemas/index.js';
2
+ export interface Section {
3
+ level: number;
4
+ title: string;
5
+ content: string;
6
+ children: Section[];
7
+ }
8
+ export declare class MarkdownParser {
9
+ private lines;
10
+ private currentLine;
11
+ constructor(content: string);
12
+ protected static normalizeContent(content: string): string;
13
+ parseSpec(name: string): Spec;
14
+ parseChange(name: string): Change;
15
+ protected parseSections(): Section[];
16
+ protected getContentUntilNextHeader(startLine: number, currentLevel: number): string;
17
+ protected findSection(sections: Section[], title: string): Section | undefined;
18
+ protected parseRequirements(section: Section): Requirement[];
19
+ protected parseScenarios(requirementSection: Section): Scenario[];
20
+ protected parseDeltas(content: string): Delta[];
21
+ }
22
+ //# sourceMappingURL=markdown-parser.d.ts.map