gmc-openspec 1.0.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 (333) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +207 -0
  3. package/bin/openspec.js +3 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +494 -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 +264 -0
  10. package/dist/commands/config.d.ts +36 -0
  11. package/dist/commands/config.js +611 -0
  12. package/dist/commands/feedback.d.ts +9 -0
  13. package/dist/commands/feedback.js +183 -0
  14. package/dist/commands/jira.d.ts +3 -0
  15. package/dist/commands/jira.js +249 -0
  16. package/dist/commands/schema.d.ts +6 -0
  17. package/dist/commands/schema.js +869 -0
  18. package/dist/commands/show.d.ts +14 -0
  19. package/dist/commands/show.js +132 -0
  20. package/dist/commands/spec.d.ts +15 -0
  21. package/dist/commands/spec.js +225 -0
  22. package/dist/commands/validate.d.ts +24 -0
  23. package/dist/commands/validate.js +294 -0
  24. package/dist/commands/workflow/index.d.ts +17 -0
  25. package/dist/commands/workflow/index.js +12 -0
  26. package/dist/commands/workflow/instructions.d.ts +29 -0
  27. package/dist/commands/workflow/instructions.js +336 -0
  28. package/dist/commands/workflow/new-change.d.ts +13 -0
  29. package/dist/commands/workflow/new-change.js +92 -0
  30. package/dist/commands/workflow/schemas.d.ts +10 -0
  31. package/dist/commands/workflow/schemas.js +34 -0
  32. package/dist/commands/workflow/shared.d.ts +57 -0
  33. package/dist/commands/workflow/shared.js +116 -0
  34. package/dist/commands/workflow/status.d.ts +14 -0
  35. package/dist/commands/workflow/status.js +87 -0
  36. package/dist/commands/workflow/templates.d.ts +16 -0
  37. package/dist/commands/workflow/templates.js +69 -0
  38. package/dist/commands/workspace/open.d.ts +29 -0
  39. package/dist/commands/workspace/open.js +84 -0
  40. package/dist/commands/workspace/operations.d.ts +23 -0
  41. package/dist/commands/workspace/operations.js +475 -0
  42. package/dist/commands/workspace/selection.d.ts +6 -0
  43. package/dist/commands/workspace/selection.js +113 -0
  44. package/dist/commands/workspace/types.d.ts +88 -0
  45. package/dist/commands/workspace/types.js +36 -0
  46. package/dist/commands/workspace.d.ts +6 -0
  47. package/dist/commands/workspace.js +868 -0
  48. package/dist/core/archive.d.ts +11 -0
  49. package/dist/core/archive.js +318 -0
  50. package/dist/core/artifact-graph/graph.d.ts +56 -0
  51. package/dist/core/artifact-graph/graph.js +141 -0
  52. package/dist/core/artifact-graph/index.d.ts +8 -0
  53. package/dist/core/artifact-graph/index.js +14 -0
  54. package/dist/core/artifact-graph/instruction-loader.d.ts +196 -0
  55. package/dist/core/artifact-graph/instruction-loader.js +317 -0
  56. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  57. package/dist/core/artifact-graph/outputs.js +39 -0
  58. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  59. package/dist/core/artifact-graph/resolver.js +257 -0
  60. package/dist/core/artifact-graph/schema.d.ts +13 -0
  61. package/dist/core/artifact-graph/schema.js +108 -0
  62. package/dist/core/artifact-graph/state.d.ts +12 -0
  63. package/dist/core/artifact-graph/state.js +31 -0
  64. package/dist/core/artifact-graph/types.d.ts +47 -0
  65. package/dist/core/artifact-graph/types.js +48 -0
  66. package/dist/core/available-tools.d.ts +17 -0
  67. package/dist/core/available-tools.js +43 -0
  68. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  69. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  70. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  71. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  72. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  73. package/dist/core/command-generation/adapters/auggie.js +27 -0
  74. package/dist/core/command-generation/adapters/bob.d.ts +14 -0
  75. package/dist/core/command-generation/adapters/bob.js +45 -0
  76. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  77. package/dist/core/command-generation/adapters/claude.js +50 -0
  78. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  79. package/dist/core/command-generation/adapters/cline.js +27 -0
  80. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  82. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  83. package/dist/core/command-generation/adapters/codex.js +39 -0
  84. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  85. package/dist/core/command-generation/adapters/continue.js +28 -0
  86. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/costrict.js +27 -0
  88. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  89. package/dist/core/command-generation/adapters/crush.js +30 -0
  90. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  91. package/dist/core/command-generation/adapters/cursor.js +44 -0
  92. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  93. package/dist/core/command-generation/adapters/factory.js +27 -0
  94. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  95. package/dist/core/command-generation/adapters/gemini.js +26 -0
  96. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  97. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  98. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/iflow.js +29 -0
  100. package/dist/core/command-generation/adapters/index.d.ts +32 -0
  101. package/dist/core/command-generation/adapters/index.js +32 -0
  102. package/dist/core/command-generation/adapters/junie.d.ts +13 -0
  103. package/dist/core/command-generation/adapters/junie.js +26 -0
  104. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  105. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  106. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  107. package/dist/core/command-generation/adapters/kiro.js +26 -0
  108. package/dist/core/command-generation/adapters/lingma.d.ts +13 -0
  109. package/dist/core/command-generation/adapters/lingma.js +30 -0
  110. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  111. package/dist/core/command-generation/adapters/opencode.js +29 -0
  112. package/dist/core/command-generation/adapters/pi.d.ts +18 -0
  113. package/dist/core/command-generation/adapters/pi.js +55 -0
  114. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  115. package/dist/core/command-generation/adapters/qoder.js +30 -0
  116. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  117. package/dist/core/command-generation/adapters/qwen.js +26 -0
  118. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  119. package/dist/core/command-generation/adapters/roocode.js +27 -0
  120. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  121. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  122. package/dist/core/command-generation/generator.d.ts +21 -0
  123. package/dist/core/command-generation/generator.js +27 -0
  124. package/dist/core/command-generation/index.d.ts +22 -0
  125. package/dist/core/command-generation/index.js +24 -0
  126. package/dist/core/command-generation/registry.d.ts +36 -0
  127. package/dist/core/command-generation/registry.js +98 -0
  128. package/dist/core/command-generation/types.d.ts +56 -0
  129. package/dist/core/command-generation/types.js +8 -0
  130. package/dist/core/completions/command-registry.d.ts +7 -0
  131. package/dist/core/completions/command-registry.js +626 -0
  132. package/dist/core/completions/completion-provider.d.ts +71 -0
  133. package/dist/core/completions/completion-provider.js +129 -0
  134. package/dist/core/completions/factory.d.ts +64 -0
  135. package/dist/core/completions/factory.js +75 -0
  136. package/dist/core/completions/generators/bash-generator.d.ts +35 -0
  137. package/dist/core/completions/generators/bash-generator.js +230 -0
  138. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  139. package/dist/core/completions/generators/fish-generator.js +160 -0
  140. package/dist/core/completions/generators/powershell-generator.d.ts +36 -0
  141. package/dist/core/completions/generators/powershell-generator.js +266 -0
  142. package/dist/core/completions/generators/zsh-generator.d.ts +47 -0
  143. package/dist/core/completions/generators/zsh-generator.js +274 -0
  144. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  145. package/dist/core/completions/installers/bash-installer.js +318 -0
  146. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  147. package/dist/core/completions/installers/fish-installer.js +143 -0
  148. package/dist/core/completions/installers/powershell-installer.d.ts +102 -0
  149. package/dist/core/completions/installers/powershell-installer.js +387 -0
  150. package/dist/core/completions/installers/zsh-installer.d.ts +117 -0
  151. package/dist/core/completions/installers/zsh-installer.js +421 -0
  152. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  153. package/dist/core/completions/templates/bash-templates.js +30 -0
  154. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  155. package/dist/core/completions/templates/fish-templates.js +45 -0
  156. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  157. package/dist/core/completions/templates/powershell-templates.js +34 -0
  158. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  159. package/dist/core/completions/templates/zsh-templates.js +45 -0
  160. package/dist/core/completions/types.d.ts +101 -0
  161. package/dist/core/completions/types.js +2 -0
  162. package/dist/core/config-prompts.d.ts +9 -0
  163. package/dist/core/config-prompts.js +34 -0
  164. package/dist/core/config-schema.d.ts +86 -0
  165. package/dist/core/config-schema.js +213 -0
  166. package/dist/core/config.d.ts +18 -0
  167. package/dist/core/config.js +38 -0
  168. package/dist/core/converters/json-converter.d.ts +6 -0
  169. package/dist/core/converters/json-converter.js +51 -0
  170. package/dist/core/global-config.d.ts +49 -0
  171. package/dist/core/global-config.js +124 -0
  172. package/dist/core/index.d.ts +5 -0
  173. package/dist/core/index.js +6 -0
  174. package/dist/core/init.d.ts +37 -0
  175. package/dist/core/init.js +593 -0
  176. package/dist/core/jira/config.d.ts +35 -0
  177. package/dist/core/jira/config.js +151 -0
  178. package/dist/core/jira/constants.d.ts +20 -0
  179. package/dist/core/jira/constants.js +49 -0
  180. package/dist/core/jira/doctor.d.ts +19 -0
  181. package/dist/core/jira/doctor.js +173 -0
  182. package/dist/core/jira/hash.d.ts +3 -0
  183. package/dist/core/jira/hash.js +9 -0
  184. package/dist/core/jira/index.d.ts +11 -0
  185. package/dist/core/jira/index.js +11 -0
  186. package/dist/core/jira/intake.d.ts +40 -0
  187. package/dist/core/jira/intake.js +54 -0
  188. package/dist/core/jira/mcp-config.d.ts +13 -0
  189. package/dist/core/jira/mcp-config.js +259 -0
  190. package/dist/core/jira/paths.d.ts +12 -0
  191. package/dist/core/jira/paths.js +66 -0
  192. package/dist/core/jira/setup.d.ts +30 -0
  193. package/dist/core/jira/setup.js +99 -0
  194. package/dist/core/jira/templates.d.ts +12 -0
  195. package/dist/core/jira/templates.js +204 -0
  196. package/dist/core/jira/validation.d.ts +17 -0
  197. package/dist/core/jira/validation.js +341 -0
  198. package/dist/core/legacy-cleanup.d.ts +162 -0
  199. package/dist/core/legacy-cleanup.js +514 -0
  200. package/dist/core/list.d.ts +9 -0
  201. package/dist/core/list.js +171 -0
  202. package/dist/core/migration.d.ts +23 -0
  203. package/dist/core/migration.js +108 -0
  204. package/dist/core/parsers/change-parser.d.ts +13 -0
  205. package/dist/core/parsers/change-parser.js +197 -0
  206. package/dist/core/parsers/markdown-parser.d.ts +26 -0
  207. package/dist/core/parsers/markdown-parser.js +227 -0
  208. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  209. package/dist/core/parsers/requirement-blocks.js +201 -0
  210. package/dist/core/parsers/spec-structure.d.ts +9 -0
  211. package/dist/core/parsers/spec-structure.js +88 -0
  212. package/dist/core/planning-home.d.ts +21 -0
  213. package/dist/core/planning-home.js +124 -0
  214. package/dist/core/profile-sync-drift.d.ts +38 -0
  215. package/dist/core/profile-sync-drift.js +200 -0
  216. package/dist/core/profiles.d.ts +26 -0
  217. package/dist/core/profiles.js +40 -0
  218. package/dist/core/project-config.d.ts +64 -0
  219. package/dist/core/project-config.js +223 -0
  220. package/dist/core/schemas/base.schema.d.ts +13 -0
  221. package/dist/core/schemas/base.schema.js +13 -0
  222. package/dist/core/schemas/change.schema.d.ts +73 -0
  223. package/dist/core/schemas/change.schema.js +31 -0
  224. package/dist/core/schemas/index.d.ts +4 -0
  225. package/dist/core/schemas/index.js +4 -0
  226. package/dist/core/schemas/spec.schema.d.ts +18 -0
  227. package/dist/core/schemas/spec.schema.js +15 -0
  228. package/dist/core/shared/index.d.ts +8 -0
  229. package/dist/core/shared/index.js +8 -0
  230. package/dist/core/shared/skill-generation.d.ts +49 -0
  231. package/dist/core/shared/skill-generation.js +96 -0
  232. package/dist/core/shared/tool-detection.d.ts +71 -0
  233. package/dist/core/shared/tool-detection.js +158 -0
  234. package/dist/core/specs-apply.d.ts +73 -0
  235. package/dist/core/specs-apply.js +392 -0
  236. package/dist/core/styles/palette.d.ts +7 -0
  237. package/dist/core/styles/palette.js +8 -0
  238. package/dist/core/templates/index.d.ts +8 -0
  239. package/dist/core/templates/index.js +9 -0
  240. package/dist/core/templates/skill-templates.d.ts +19 -0
  241. package/dist/core/templates/skill-templates.js +18 -0
  242. package/dist/core/templates/types.d.ts +19 -0
  243. package/dist/core/templates/types.js +5 -0
  244. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  245. package/dist/core/templates/workflows/apply-change.js +314 -0
  246. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  247. package/dist/core/templates/workflows/archive-change.js +277 -0
  248. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  249. package/dist/core/templates/workflows/bulk-archive-change.js +492 -0
  250. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  251. package/dist/core/templates/workflows/continue-change.js +234 -0
  252. package/dist/core/templates/workflows/explore.d.ts +10 -0
  253. package/dist/core/templates/workflows/explore.js +459 -0
  254. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  255. package/dist/core/templates/workflows/feedback.js +108 -0
  256. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  257. package/dist/core/templates/workflows/ff-change.js +200 -0
  258. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  259. package/dist/core/templates/workflows/new-change.js +143 -0
  260. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  261. package/dist/core/templates/workflows/onboard.js +563 -0
  262. package/dist/core/templates/workflows/propose.d.ts +10 -0
  263. package/dist/core/templates/workflows/propose.js +218 -0
  264. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  265. package/dist/core/templates/workflows/sync-specs.js +290 -0
  266. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  267. package/dist/core/templates/workflows/verify-change.js +338 -0
  268. package/dist/core/update.d.ts +83 -0
  269. package/dist/core/update.js +573 -0
  270. package/dist/core/validation/constants.d.ts +34 -0
  271. package/dist/core/validation/constants.js +40 -0
  272. package/dist/core/validation/types.d.ts +18 -0
  273. package/dist/core/validation/types.js +2 -0
  274. package/dist/core/validation/validator.d.ts +33 -0
  275. package/dist/core/validation/validator.js +418 -0
  276. package/dist/core/view.d.ts +8 -0
  277. package/dist/core/view.js +168 -0
  278. package/dist/core/workspace/foundation.d.ts +87 -0
  279. package/dist/core/workspace/foundation.js +379 -0
  280. package/dist/core/workspace/index.d.ts +6 -0
  281. package/dist/core/workspace/index.js +6 -0
  282. package/dist/core/workspace/link-input.d.ts +9 -0
  283. package/dist/core/workspace/link-input.js +32 -0
  284. package/dist/core/workspace/open-surface.d.ts +24 -0
  285. package/dist/core/workspace/open-surface.js +137 -0
  286. package/dist/core/workspace/openers.d.ts +21 -0
  287. package/dist/core/workspace/openers.js +119 -0
  288. package/dist/core/workspace/skills.d.ts +55 -0
  289. package/dist/core/workspace/skills.js +334 -0
  290. package/dist/index.d.ts +3 -0
  291. package/dist/index.js +3 -0
  292. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  293. package/dist/prompts/searchable-multi-select.js +159 -0
  294. package/dist/telemetry/config.d.ts +38 -0
  295. package/dist/telemetry/config.js +136 -0
  296. package/dist/telemetry/index.d.ts +31 -0
  297. package/dist/telemetry/index.js +164 -0
  298. package/dist/ui/ascii-patterns.d.ts +16 -0
  299. package/dist/ui/ascii-patterns.js +133 -0
  300. package/dist/ui/welcome-screen.d.ts +10 -0
  301. package/dist/ui/welcome-screen.js +146 -0
  302. package/dist/utils/change-metadata.d.ts +51 -0
  303. package/dist/utils/change-metadata.js +147 -0
  304. package/dist/utils/change-utils.d.ts +71 -0
  305. package/dist/utils/change-utils.js +123 -0
  306. package/dist/utils/command-references.d.ts +18 -0
  307. package/dist/utils/command-references.js +20 -0
  308. package/dist/utils/file-system.d.ts +41 -0
  309. package/dist/utils/file-system.js +301 -0
  310. package/dist/utils/index.d.ts +6 -0
  311. package/dist/utils/index.js +9 -0
  312. package/dist/utils/interactive.d.ts +18 -0
  313. package/dist/utils/interactive.js +21 -0
  314. package/dist/utils/item-discovery.d.ts +4 -0
  315. package/dist/utils/item-discovery.js +72 -0
  316. package/dist/utils/match.d.ts +3 -0
  317. package/dist/utils/match.js +22 -0
  318. package/dist/utils/shell-detection.d.ts +20 -0
  319. package/dist/utils/shell-detection.js +41 -0
  320. package/dist/utils/task-progress.d.ts +8 -0
  321. package/dist/utils/task-progress.js +36 -0
  322. package/package.json +79 -0
  323. package/schemas/spec-driven/schema.yaml +153 -0
  324. package/schemas/spec-driven/templates/design.md +19 -0
  325. package/schemas/spec-driven/templates/proposal.md +23 -0
  326. package/schemas/spec-driven/templates/spec.md +8 -0
  327. package/schemas/spec-driven/templates/tasks.md +9 -0
  328. package/schemas/workspace-planning/schema.yaml +72 -0
  329. package/schemas/workspace-planning/templates/design.md +33 -0
  330. package/schemas/workspace-planning/templates/proposal.md +28 -0
  331. package/schemas/workspace-planning/templates/spec.md +9 -0
  332. package/schemas/workspace-planning/templates/tasks.md +15 -0
  333. package/scripts/postinstall.js +83 -0
@@ -0,0 +1,259 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ const ATLASSIAN_MCP_HOST = 'mcp.atlassian.com';
5
+ const PROJECT_MCP_ADAPTERS = {
6
+ 'github-copilot': {
7
+ toolId: 'github-copilot',
8
+ pathSegments: ['.vscode', 'mcp.json'],
9
+ containerKey: 'servers',
10
+ canonicalServerName: 'atlassian-mcp-server',
11
+ collisionServerName: 'atlassian-mcp-server-openspec',
12
+ includeInputs: true,
13
+ },
14
+ cursor: {
15
+ toolId: 'cursor',
16
+ pathSegments: ['.cursor', 'mcp.json'],
17
+ containerKey: 'mcpServers',
18
+ canonicalServerName: 'Atlassian-MCP-Server',
19
+ collisionServerName: 'Atlassian-MCP-Server-OpenSpec',
20
+ },
21
+ claude: {
22
+ toolId: 'claude',
23
+ pathSegments: ['.mcp.json'],
24
+ containerKey: 'mcpServers',
25
+ canonicalServerName: 'Atlassian-MCP-Server',
26
+ collisionServerName: 'Atlassian-MCP-Server-OpenSpec',
27
+ },
28
+ };
29
+ export async function ensureJiraMcpConfigs(projectRoot, toolIds, endpoint) {
30
+ const results = [];
31
+ for (const toolId of toolIds) {
32
+ results.push(await ensureJiraMcpConfig(projectRoot, toolId, endpoint));
33
+ }
34
+ return results;
35
+ }
36
+ export async function ensureJiraMcpConfig(projectRoot, toolId, endpoint) {
37
+ const computed = computeJiraMcpConfig(projectRoot, toolId, endpoint, 'ensure');
38
+ if (computed.nextContent && computed.targetPath) {
39
+ await fs.mkdir(path.dirname(computed.targetPath), { recursive: true });
40
+ await fs.writeFile(computed.targetPath, computed.nextContent, 'utf8');
41
+ }
42
+ return computed.result;
43
+ }
44
+ export function inspectJiraMcpConfig(projectRoot, toolId, endpoint) {
45
+ return computeJiraMcpConfig(projectRoot, toolId, endpoint, 'inspect').result;
46
+ }
47
+ export function getManualJiraMcpSetupHint(toolId, endpoint) {
48
+ if (toolId === 'codex') {
49
+ return `Codex MCP auto-write is not supported for project-local setup yet. Run: codex mcp add Atlassian-MCP-Server --url ${endpoint}`;
50
+ }
51
+ return `Configure this AI client with an official Atlassian MCP server using endpoint ${endpoint}.`;
52
+ }
53
+ function computeJiraMcpConfig(projectRoot, toolId, endpoint, mode) {
54
+ const adapter = PROJECT_MCP_ADAPTERS[toolId];
55
+ if (!adapter) {
56
+ return {
57
+ result: {
58
+ toolId,
59
+ status: 'unsupported',
60
+ message: getManualJiraMcpSetupHint(toolId, endpoint),
61
+ },
62
+ };
63
+ }
64
+ const targetPath = path.join(projectRoot, ...adapter.pathSegments);
65
+ if (!existsSync(targetPath)) {
66
+ const serverName = adapter.canonicalServerName;
67
+ return {
68
+ targetPath,
69
+ nextContent: serializeJson(createMcpConfig(adapter, endpoint, serverName)),
70
+ result: {
71
+ toolId,
72
+ status: 'created',
73
+ path: targetPath,
74
+ serverName,
75
+ message: mode === 'ensure'
76
+ ? `Created official Atlassian MCP config for ${toolId}.`
77
+ : `Official Atlassian MCP config is missing for ${toolId}; setup would create it.`,
78
+ },
79
+ };
80
+ }
81
+ let parsed;
82
+ try {
83
+ parsed = JSON.parse(readFileSync(targetPath, 'utf8'));
84
+ }
85
+ catch (error) {
86
+ return {
87
+ targetPath,
88
+ result: {
89
+ toolId,
90
+ status: 'invalid-json',
91
+ path: targetPath,
92
+ message: `Could not parse ${toDisplayPath(projectRoot, targetPath)}; fix the JSON before OpenSpec can add Atlassian MCP config. ${error instanceof Error ? error.message : String(error)}`,
93
+ },
94
+ };
95
+ }
96
+ if (!isRecord(parsed)) {
97
+ return {
98
+ targetPath,
99
+ result: {
100
+ toolId,
101
+ status: 'invalid-json',
102
+ path: targetPath,
103
+ message: `${toDisplayPath(projectRoot, targetPath)} must contain a JSON object before OpenSpec can add Atlassian MCP config.`,
104
+ },
105
+ };
106
+ }
107
+ const root = cloneRecord(parsed);
108
+ const existingContainer = root[adapter.containerKey];
109
+ if (existingContainer !== undefined && !isRecord(existingContainer)) {
110
+ return {
111
+ targetPath,
112
+ result: {
113
+ toolId,
114
+ status: 'invalid-json',
115
+ path: targetPath,
116
+ message: `${toDisplayPath(projectRoot, targetPath)} has a non-object ${adapter.containerKey} field; fix it before OpenSpec can add Atlassian MCP config.`,
117
+ },
118
+ };
119
+ }
120
+ const container = existingContainer ? cloneRecord(existingContainer) : {};
121
+ root[adapter.containerKey] = container;
122
+ const officialServerName = findOfficialAtlassianServerName(container);
123
+ if (officialServerName) {
124
+ const normalization = normalizeOfficialAtlassianServer(container[officialServerName], endpoint);
125
+ if (normalization.changed) {
126
+ container[officialServerName] = normalization.value;
127
+ return {
128
+ targetPath,
129
+ nextContent: serializeJson(root),
130
+ result: {
131
+ toolId,
132
+ status: 'updated',
133
+ path: targetPath,
134
+ serverName: officialServerName,
135
+ message: mode === 'ensure'
136
+ ? `Updated official Atlassian MCP endpoint for ${toolId}.`
137
+ : `Official Atlassian MCP endpoint for ${toolId} needs refresh.`,
138
+ },
139
+ };
140
+ }
141
+ return {
142
+ targetPath,
143
+ result: {
144
+ toolId,
145
+ status: 'already-present',
146
+ path: targetPath,
147
+ serverName: officialServerName,
148
+ message: `Official Atlassian MCP config already exists for ${toolId}.`,
149
+ },
150
+ };
151
+ }
152
+ const serverName = chooseServerName(container, adapter);
153
+ container[serverName] = createAtlassianServerConfig(endpoint);
154
+ if (adapter.includeInputs && root.inputs === undefined) {
155
+ root.inputs = [];
156
+ }
157
+ return {
158
+ targetPath,
159
+ nextContent: serializeJson(root),
160
+ result: {
161
+ toolId,
162
+ status: 'updated',
163
+ path: targetPath,
164
+ serverName,
165
+ message: mode === 'ensure'
166
+ ? `Added official Atlassian MCP config for ${toolId}.`
167
+ : `Official Atlassian MCP config is missing for ${toolId}; setup would add it.`,
168
+ },
169
+ };
170
+ }
171
+ function createMcpConfig(adapter, endpoint, serverName) {
172
+ const config = {
173
+ [adapter.containerKey]: {
174
+ [serverName]: createAtlassianServerConfig(endpoint),
175
+ },
176
+ };
177
+ if (adapter.includeInputs) {
178
+ config.inputs = [];
179
+ }
180
+ return config;
181
+ }
182
+ function createAtlassianServerConfig(endpoint) {
183
+ return {
184
+ url: endpoint,
185
+ type: 'http',
186
+ };
187
+ }
188
+ function findOfficialAtlassianServerName(container) {
189
+ return Object.entries(container).find(([, server]) => containsAtlassianMcpHost(server))?.[0];
190
+ }
191
+ function chooseServerName(container, adapter) {
192
+ if (!(adapter.canonicalServerName in container)) {
193
+ return adapter.canonicalServerName;
194
+ }
195
+ if (!(adapter.collisionServerName in container)) {
196
+ return adapter.collisionServerName;
197
+ }
198
+ let suffix = 2;
199
+ while (`${adapter.collisionServerName}-${suffix}` in container) {
200
+ suffix++;
201
+ }
202
+ return `${adapter.collisionServerName}-${suffix}`;
203
+ }
204
+ function normalizeOfficialAtlassianServer(server, endpoint) {
205
+ if (!isRecord(server)) {
206
+ return { value: server, changed: false };
207
+ }
208
+ const next = cloneRecord(server);
209
+ let changed = false;
210
+ if (typeof next.url === 'string' && next.url.includes(ATLASSIAN_MCP_HOST)) {
211
+ if (next.url !== endpoint) {
212
+ next.url = endpoint;
213
+ changed = true;
214
+ }
215
+ if (next.type !== 'http') {
216
+ next.type = 'http';
217
+ changed = true;
218
+ }
219
+ }
220
+ if (Array.isArray(next.args)) {
221
+ const normalizedArgs = next.args.map((arg) => {
222
+ if (typeof arg !== 'string' || !arg.includes(ATLASSIAN_MCP_HOST)) {
223
+ return arg;
224
+ }
225
+ if (arg === endpoint) {
226
+ return arg;
227
+ }
228
+ changed = true;
229
+ return endpoint;
230
+ });
231
+ next.args = normalizedArgs;
232
+ }
233
+ return { value: next, changed };
234
+ }
235
+ function containsAtlassianMcpHost(value) {
236
+ if (typeof value === 'string') {
237
+ return value.includes(ATLASSIAN_MCP_HOST);
238
+ }
239
+ if (Array.isArray(value)) {
240
+ return value.some((item) => containsAtlassianMcpHost(item));
241
+ }
242
+ if (isRecord(value)) {
243
+ return Object.values(value).some((child) => containsAtlassianMcpHost(child));
244
+ }
245
+ return false;
246
+ }
247
+ function isRecord(value) {
248
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
249
+ }
250
+ function cloneRecord(value) {
251
+ return { ...value };
252
+ }
253
+ function serializeJson(value) {
254
+ return `${JSON.stringify(value, null, 2)}\n`;
255
+ }
256
+ function toDisplayPath(projectRoot, targetPath) {
257
+ return path.relative(projectRoot, targetPath).split(path.sep).join('/');
258
+ }
259
+ //# sourceMappingURL=mcp-config.js.map
@@ -0,0 +1,12 @@
1
+ export declare function normalizeJiraId(jiraId: string): string;
2
+ export declare function validateJiraId(jiraId: string): {
3
+ valid: boolean;
4
+ error?: string;
5
+ };
6
+ export declare function slugify(input: string): string;
7
+ export declare function getJiraConfigPath(projectRoot: string): string;
8
+ export declare function getJiraRoot(projectRoot: string): string;
9
+ export declare function getJiraIssueDir(projectRoot: string, jiraId: string, slug?: string): string;
10
+ export declare function getJiraIssueFile(projectRoot: string, jiraId: string, fileName: string): string;
11
+ export declare function toDisplayPath(projectRoot: string, targetPath: string): string;
12
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1,66 @@
1
+ import path from 'path';
2
+ import { existsSync, readdirSync, statSync } from 'fs';
3
+ import { OPENSPEC_DIR_NAME } from '../config.js';
4
+ import { JIRA_CONFIG_FILE, JIRA_DIR_NAME } from './constants.js';
5
+ const ISSUE_ID_PATTERN = /^[A-Z][A-Z0-9]+-\d+$/;
6
+ export function normalizeJiraId(jiraId) {
7
+ return jiraId.trim().toUpperCase();
8
+ }
9
+ export function validateJiraId(jiraId) {
10
+ const normalized = normalizeJiraId(jiraId);
11
+ if (!normalized) {
12
+ return { valid: false, error: 'Jira issue id cannot be empty' };
13
+ }
14
+ if (!ISSUE_ID_PATTERN.test(normalized)) {
15
+ return { valid: false, error: 'Jira issue id must look like ABC-123' };
16
+ }
17
+ return { valid: true };
18
+ }
19
+ export function slugify(input) {
20
+ const slug = input
21
+ .trim()
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]+/g, '-')
24
+ .replace(/^-+|-+$/g, '')
25
+ .replace(/-{2,}/g, '-');
26
+ return slug || 'jira-intake';
27
+ }
28
+ export function getJiraConfigPath(projectRoot) {
29
+ return path.join(projectRoot, OPENSPEC_DIR_NAME, JIRA_CONFIG_FILE);
30
+ }
31
+ export function getJiraRoot(projectRoot) {
32
+ return path.join(projectRoot, JIRA_DIR_NAME);
33
+ }
34
+ export function getJiraIssueDir(projectRoot, jiraId, slug) {
35
+ const normalized = normalizeJiraId(jiraId);
36
+ const root = getJiraRoot(projectRoot);
37
+ if (slug) {
38
+ return path.join(root, `${normalized}-${slugify(slug)}`);
39
+ }
40
+ if (existsSync(root)) {
41
+ const prefix = `${normalized}-`;
42
+ const matches = readdirSync(root)
43
+ .filter((entry) => entry === normalized || entry.startsWith(prefix))
44
+ .map((entry) => path.join(root, entry))
45
+ .filter((candidate) => {
46
+ try {
47
+ return statSync(candidate).isDirectory();
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ })
53
+ .sort();
54
+ if (matches.length > 0) {
55
+ return matches[0];
56
+ }
57
+ }
58
+ return path.join(root, normalized);
59
+ }
60
+ export function getJiraIssueFile(projectRoot, jiraId, fileName) {
61
+ return path.join(getJiraIssueDir(projectRoot, jiraId), fileName);
62
+ }
63
+ export function toDisplayPath(projectRoot, targetPath) {
64
+ return path.relative(projectRoot, targetPath).split(path.sep).join('/');
65
+ }
66
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,30 @@
1
+ import type { Delivery } from '../global-config.js';
2
+ import { type JiraConfig } from './config.js';
3
+ import { type JiraMcpConfigResult } from './mcp-config.js';
4
+ export interface JiraSetupOptions {
5
+ tools?: string;
6
+ provider?: string;
7
+ endpoint?: string;
8
+ force?: boolean;
9
+ delivery?: Delivery;
10
+ generatedByVersion: string;
11
+ }
12
+ export interface JiraSetupResult {
13
+ configPath: string;
14
+ config: JiraConfig;
15
+ tools: string[];
16
+ skillsWritten: number;
17
+ commandsWritten: number;
18
+ commandsSkipped: string[];
19
+ mcpConfigResults: JiraMcpConfigResult[];
20
+ snippets: Record<string, string>;
21
+ }
22
+ export declare function resolveJiraToolIds(projectRoot: string, toolsArg?: string): string[];
23
+ export declare function setupJiraIntegration(projectRoot: string, options: JiraSetupOptions): Promise<JiraSetupResult>;
24
+ export declare function installJiraArtifacts(projectRoot: string, toolIds: string[], delivery: Delivery, generatedByVersion: string): Promise<{
25
+ skillsWritten: number;
26
+ commandsWritten: number;
27
+ commandsSkipped: string[];
28
+ }>;
29
+ export declare function getMcpSnippet(endpoint: string): string;
30
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1,99 @@
1
+ import path from 'path';
2
+ import { AI_TOOLS } from '../config.js';
3
+ import { getAvailableTools } from '../available-tools.js';
4
+ import { generateCommands, CommandAdapterRegistry } from '../command-generation/index.js';
5
+ import { generateSkillContent } from '../shared/skill-generation.js';
6
+ import { FileSystemUtils } from '../../utils/file-system.js';
7
+ import { transformToHyphenCommands } from '../../utils/command-references.js';
8
+ import { createDefaultJiraConfig, writeJiraConfig } from './config.js';
9
+ import { JIRA_DEFAULT_ENDPOINT } from './constants.js';
10
+ import { ensureJiraMcpConfigs } from './mcp-config.js';
11
+ import { getJiraCommandContents, getJiraSkillTemplates } from './templates.js';
12
+ export function resolveJiraToolIds(projectRoot, toolsArg) {
13
+ const validToolIds = AI_TOOLS.filter((tool) => tool.skillsDir).map((tool) => tool.value);
14
+ const validSet = new Set(validToolIds);
15
+ const requested = toolsArg?.trim() || 'detected';
16
+ if (requested === 'none')
17
+ return [];
18
+ if (requested === 'all')
19
+ return validToolIds;
20
+ if (requested === 'detected') {
21
+ return getAvailableTools(projectRoot).map((tool) => tool.value);
22
+ }
23
+ const ids = requested.split(',').map((tool) => tool.trim()).filter(Boolean);
24
+ for (const id of ids) {
25
+ if (!validSet.has(id)) {
26
+ throw new Error(`Unknown Jira setup tool "${id}". Use all, none, detected, or one of: ${validToolIds.join(', ')}`);
27
+ }
28
+ }
29
+ return [...new Set(ids)];
30
+ }
31
+ export async function setupJiraIntegration(projectRoot, options) {
32
+ if (options.provider && options.provider !== 'atlassian-rovo') {
33
+ throw new Error('V1 Jira intake supports only provider "atlassian-rovo"');
34
+ }
35
+ const tools = resolveJiraToolIds(projectRoot, options.tools);
36
+ const config = createDefaultJiraConfig(tools);
37
+ config.provider = 'atlassian-rovo';
38
+ config.endpoint = options.endpoint ?? JIRA_DEFAULT_ENDPOINT;
39
+ config.tools = tools;
40
+ const configPath = await writeJiraConfig(projectRoot, config, { force: options.force });
41
+ const installResult = await installJiraArtifacts(projectRoot, tools, options.delivery ?? 'both', options.generatedByVersion);
42
+ const mcpConfigResults = await ensureJiraMcpConfigs(projectRoot, tools, config.endpoint);
43
+ return {
44
+ configPath,
45
+ config,
46
+ tools,
47
+ ...installResult,
48
+ mcpConfigResults,
49
+ snippets: Object.fromEntries(tools.map((toolId) => [toolId, getMcpSnippet(config.endpoint)])),
50
+ };
51
+ }
52
+ export async function installJiraArtifacts(projectRoot, toolIds, delivery, generatedByVersion) {
53
+ const shouldGenerateSkills = delivery !== 'commands';
54
+ const shouldGenerateCommands = delivery !== 'skills';
55
+ const skillTemplates = getJiraSkillTemplates();
56
+ const commandContents = getJiraCommandContents();
57
+ const commandsSkipped = [];
58
+ let skillsWritten = 0;
59
+ let commandsWritten = 0;
60
+ for (const toolId of toolIds) {
61
+ const tool = AI_TOOLS.find((candidate) => candidate.value === toolId);
62
+ if (!tool?.skillsDir)
63
+ continue;
64
+ if (shouldGenerateSkills) {
65
+ const skillsDir = path.join(projectRoot, tool.skillsDir, 'skills');
66
+ for (const { template, dirName } of skillTemplates) {
67
+ const skillFile = path.join(skillsDir, dirName, 'SKILL.md');
68
+ const transformer = tool.value === 'opencode' || tool.value === 'pi' ? transformToHyphenCommands : undefined;
69
+ await FileSystemUtils.writeFile(skillFile, generateSkillContent(template, generatedByVersion, transformer));
70
+ skillsWritten++;
71
+ }
72
+ }
73
+ if (shouldGenerateCommands) {
74
+ const adapter = CommandAdapterRegistry.get(tool.value);
75
+ if (!adapter) {
76
+ commandsSkipped.push(tool.value);
77
+ continue;
78
+ }
79
+ const generatedCommands = generateCommands(commandContents, adapter);
80
+ for (const command of generatedCommands) {
81
+ const commandFile = path.isAbsolute(command.path) ? command.path : path.join(projectRoot, command.path);
82
+ await FileSystemUtils.writeFile(commandFile, command.fileContent);
83
+ commandsWritten++;
84
+ }
85
+ }
86
+ }
87
+ return { skillsWritten, commandsWritten, commandsSkipped };
88
+ }
89
+ export function getMcpSnippet(endpoint) {
90
+ return JSON.stringify({
91
+ mcpServers: {
92
+ atlassian: {
93
+ command: 'npx',
94
+ args: ['-y', 'mcp-remote', endpoint],
95
+ },
96
+ },
97
+ }, null, 2);
98
+ }
99
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,12 @@
1
+ import type { CommandContent } from '../command-generation/index.js';
2
+ import type { SkillTemplate } from '../templates/types.js';
3
+ import { type JiraWorkflowId } from './constants.js';
4
+ export interface JiraSkillTemplateEntry {
5
+ workflowId: JiraWorkflowId;
6
+ dirName: string;
7
+ template: SkillTemplate;
8
+ }
9
+ export declare function getJiraSkillTemplates(): JiraSkillTemplateEntry[];
10
+ export declare function getJiraCommandContents(): CommandContent[];
11
+ export declare function isJiraWorkflowId(value: string): value is JiraWorkflowId;
12
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1,204 @@
1
+ import { JIRA_WORKFLOW_IDS, JIRA_WORKFLOW_TO_SKILL_DIR } from './constants.js';
2
+ const SHARED_GUARDRAILS = `
3
+ ## Boundary Guardrails
4
+
5
+ - Treat Jira as the business input source, not the final engineering specification.
6
+ - Never create or modify \`openspec/changes/*\` directly from raw Jira description or comments.
7
+ - Never modify \`source.md\`; it is a read-only Jira snapshot protected by \`sourceHashSha256\` in \`intake.yaml\`.
8
+ - Blocking questions must be answered before requirements can be marked reviewed.
9
+ - Assumptions must be explicit in \`assumptions.md\` and approved before story split or OpenSpec proposal.
10
+ - Keep \`mapping.yaml\` current so Jira AC maps to requirements, stories, and OpenSpec changes.
11
+ - Use official Atlassian MCP Jira tools by their concrete names; do not enumerate every MCP tool to discover Jira capabilities.
12
+ - If the named Atlassian MCP tools are unavailable, stop and report MCP setup or OAuth authorization as the blocker.
13
+ `;
14
+ const WORKFLOWS = [
15
+ {
16
+ id: 'jira-import',
17
+ name: 'OPSX: Jira Import',
18
+ description: 'Import a Jira epic or feature into a local auditable intake folder before OpenSpec planning.',
19
+ body: `Import a Jira ticket into OpenSpec intake.
20
+
21
+ **Input**: Jira issue id such as \`ABC-123\`.
22
+
23
+ ${SHARED_GUARDRAILS}
24
+
25
+ ## Steps
26
+
27
+ 1. Use the official Atlassian MCP Jira tool \`getJiraIssue\` to read the issue by id.
28
+ - Read summary, description, acceptance criteria, comments, linked issues, labels, components, and attachments metadata where available.
29
+ - Use \`getJiraIssueRemoteIssueLinks\` and \`searchJiraIssuesUsingJql\` only when linked issues or related ticket lookup is needed.
30
+ - Do not write to Jira.
31
+ 2. Create \`jira/<JIRA-ID>-<slug>/\` using a short slug from the Jira summary.
32
+ 3. Write \`source.md\` as an auditable snapshot with field names and source sections.
33
+ 4. Compute SHA-256 of \`source.md\` and write \`intake.yaml\`:
34
+ \`schemaVersion: 1\`, \`jira\`, \`status: imported\`, \`sourceHashSha256\`.
35
+ 5. Write \`requirement-draft.md\` as a concise product requirement draft.
36
+ 6. Write initial \`questions.md\`, \`assumptions.md\`, and \`mapping.yaml\` placeholders.
37
+ 7. Run \`openspec jira validate <JIRA-ID> --stage source\` and report the result.
38
+
39
+ ## Output
40
+
41
+ Summarize the intake folder, the source hash, and the next command: \`/opsx:jira-refine <JIRA-ID>\`.`,
42
+ },
43
+ {
44
+ id: 'jira-refine',
45
+ name: 'OPSX: Jira Refine',
46
+ description: 'Clarify imported Jira requirements until blocking ambiguity is resolved.',
47
+ body: `Refine an imported Jira ticket into a clear reviewed requirement.
48
+
49
+ **Input**: Jira issue id such as \`ABC-123\`.
50
+
51
+ ${SHARED_GUARDRAILS}
52
+
53
+ ## Steps
54
+
55
+ 1. Run \`openspec jira status <JIRA-ID> --json\` and read the intake folder.
56
+ 2. Read \`source.md\`, \`requirement-draft.md\`, \`questions.md\`, \`assumptions.md\`, and \`mapping.yaml\`.
57
+ 3. Identify ambiguity as either:
58
+ - **Blocking**: affects scope, AC, permissions, data contract, external behavior, or story boundaries.
59
+ - **Non-Blocking**: can be captured as an explicit assumption.
60
+ 4. Ask the user all blocking questions before proceeding. Update \`questions.md\` with statuses.
61
+ 5. Record non-blocking assumptions in \`assumptions.md\`; do not hide assumptions in requirement prose.
62
+ 6. When blocking questions are resolved, write \`requirement.md\` and set \`intake.yaml\` status to \`ready-for-review\`.
63
+ 7. Run \`openspec jira validate <JIRA-ID> --stage requirement\`.
64
+
65
+ ## Output
66
+
67
+ Summarize resolved questions, remaining assumptions, and ask the user to explicitly review via \`/opsx:jira-review <JIRA-ID>\`.`,
68
+ },
69
+ {
70
+ id: 'jira-review',
71
+ name: 'OPSX: Jira Review',
72
+ description: 'Review and approve the refined Jira requirement before story splitting.',
73
+ body: `Review a refined Jira requirement.
74
+
75
+ **Input**: Jira issue id such as \`ABC-123\`.
76
+
77
+ ${SHARED_GUARDRAILS}
78
+
79
+ ## Steps
80
+
81
+ 1. Read \`requirement.md\`, \`questions.md\`, and \`assumptions.md\`.
82
+ 2. Run \`openspec jira validate <JIRA-ID> --stage requirement --json\`.
83
+ 3. If validation fails, explain the blockers and return to \`/opsx:jira-refine\`.
84
+ 4. Ask the user to approve or request edits to the requirement and assumptions.
85
+ 5. Only after explicit approval, set \`intake.yaml\` status to \`reviewed\`.
86
+
87
+ ## Output
88
+
89
+ State whether the requirement is reviewed and ready for \`/opsx:jira-split <JIRA-ID>\`.`,
90
+ },
91
+ {
92
+ id: 'jira-split',
93
+ name: 'OPSX: Jira Split',
94
+ description: 'Split a reviewed Jira requirement into implementation-ready stories.',
95
+ body: `Split a reviewed Jira requirement into stories.
96
+
97
+ **Input**: Jira issue id such as \`ABC-123\`.
98
+
99
+ ${SHARED_GUARDRAILS}
100
+
101
+ ## Story Criteria
102
+
103
+ Each story must have independent review value, clear AC, scope, non-goals, dependencies, and a traceable requirement source. Avoid one giant change and avoid tiny implementation-only fragments.
104
+
105
+ ## Steps
106
+
107
+ 1. Run \`openspec jira validate <JIRA-ID> --stage requirement --json\`.
108
+ 2. Read \`requirement.md\`, \`mapping.yaml\`, and existing OpenSpec specs if capability choices are unclear.
109
+ 3. Write \`stories.md\` with story ids, titles, scope, non-goals, AC, dependencies, risks, and suggested OpenSpec change names.
110
+ 4. Update \`mapping.yaml\` so every Jira AC maps to at least one story.
111
+ 5. Set \`intake.yaml\` status to \`split\` and ask the user to review the story split.
112
+ 6. After explicit user approval, set status to \`stories-reviewed\` and run \`openspec jira validate <JIRA-ID> --stage stories\`.
113
+
114
+ ## Output
115
+
116
+ Summarize story count, deferred items, and next command: \`/opsx:jira-propose <JIRA-ID>\`.`,
117
+ },
118
+ {
119
+ id: 'jira-propose',
120
+ name: 'OPSX: Jira Propose',
121
+ description: 'Create one OpenSpec change per reviewed Jira story.',
122
+ body: `Create OpenSpec changes from reviewed Jira stories.
123
+
124
+ **Input**: Jira issue id such as \`ABC-123\`.
125
+
126
+ ${SHARED_GUARDRAILS}
127
+
128
+ ## Steps
129
+
130
+ 1. Run \`openspec jira validate <JIRA-ID> --stage stories --json\`. Stop if it fails.
131
+ 2. Read \`stories.md\`, \`mapping.yaml\`, and \`requirement.md\`.
132
+ 3. For each non-deferred reviewed story, create exactly one OpenSpec change:
133
+ \`openspec new change <jira-id>-<story-slug>\`.
134
+ 4. Generate artifacts through the existing OpenSpec flow:
135
+ - Run \`openspec status --change <change> --json\`.
136
+ - For each ready artifact, run \`openspec instructions <artifact> --change <change> --json\`.
137
+ - Create \`proposal.md\`, delta specs, \`design.md\`, and \`tasks.md\` from the reviewed story and requirement, not raw Jira text.
138
+ 5. Update \`mapping.yaml\` so every story records its OpenSpec change and spec files.
139
+ 6. Set \`intake.yaml\` status to \`proposed\` and run \`openspec jira validate <JIRA-ID> --stage propose\`.
140
+
141
+ ## Output
142
+
143
+ List created changes and remind the user to review before \`/opsx:apply\`.`,
144
+ },
145
+ {
146
+ id: 'jira-sync',
147
+ name: 'OPSX: Jira Sync',
148
+ description: 'Synchronize reviewed OpenSpec progress back to Jira with explicit confirmation.',
149
+ body: `Sync OpenSpec intake progress back to Jira.
150
+
151
+ **Input**: Jira issue id such as \`ABC-123\`.
152
+
153
+ ${SHARED_GUARDRAILS}
154
+
155
+ ## Writeback Rules
156
+
157
+ - This is the only Jira intake workflow allowed to write to Jira.
158
+ - Before any Jira comment, child issue creation, link update, or status change, show a concrete diff of the write.
159
+ - Ask for explicit confirmation before each batch of writes.
160
+ - If \`openspec/jira.yaml\` has \`writeback.enabled: false\`, ask the user to confirm this one-time sync before writing.
161
+ - Use only the official Atlassian MCP Jira write tools needed for the confirmed diff: \`addCommentToJiraIssue\`, \`editJiraIssue\`, \`getTransitionsForJiraIssue\`, \`transitionJiraIssue\`, and \`createJiraIssue\`.
162
+
163
+ ## Steps
164
+
165
+ 1. Run \`openspec jira validate <JIRA-ID> --stage propose --json\` when syncing proposed OpenSpec changes.
166
+ 2. Read \`mapping.yaml\`, \`stories.md\`, and current OpenSpec change status.
167
+ 3. Prepare a writeback plan: comments, links, child issues, or status moves.
168
+ 4. Show the diff and wait for explicit user approval.
169
+ 5. Use the named Atlassian MCP write tools only after approval.
170
+ 6. Update \`intake.yaml\` status to \`synced\` only after successful writeback.
171
+
172
+ ## Output
173
+
174
+ Summarize exactly what was written to Jira and what remains local-only.`,
175
+ },
176
+ ];
177
+ export function getJiraSkillTemplates() {
178
+ return WORKFLOWS.map((workflow) => ({
179
+ workflowId: workflow.id,
180
+ dirName: JIRA_WORKFLOW_TO_SKILL_DIR[workflow.id],
181
+ template: {
182
+ name: `openspec-${workflow.id}`,
183
+ description: workflow.description,
184
+ instructions: workflow.body,
185
+ license: 'MIT',
186
+ compatibility: 'Requires openspec CLI and a configured Jira MCP server.',
187
+ metadata: { author: 'openspec', version: '1.0' },
188
+ },
189
+ }));
190
+ }
191
+ export function getJiraCommandContents() {
192
+ return WORKFLOWS.map((workflow) => ({
193
+ id: workflow.id,
194
+ name: workflow.name,
195
+ description: workflow.description,
196
+ category: 'Jira Intake',
197
+ tags: ['jira', 'intake', 'openspec'],
198
+ body: workflow.body,
199
+ }));
200
+ }
201
+ export function isJiraWorkflowId(value) {
202
+ return JIRA_WORKFLOW_IDS.includes(value);
203
+ }
204
+ //# sourceMappingURL=templates.js.map