@sk8metal/michi-cli 0.0.9 → 0.1.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 (371) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +235 -57
  3. package/dist/scripts/__tests__/create-project.test.js +24 -28
  4. package/dist/scripts/__tests__/create-project.test.js.map +1 -1
  5. package/dist/scripts/__tests__/jira-transitions.test.d.ts +5 -0
  6. package/dist/scripts/__tests__/jira-transitions.test.d.ts.map +1 -0
  7. package/dist/scripts/__tests__/jira-transitions.test.js +172 -0
  8. package/dist/scripts/__tests__/jira-transitions.test.js.map +1 -0
  9. package/dist/scripts/__tests__/multi-project-estimate.test.js +14 -15
  10. package/dist/scripts/__tests__/multi-project-estimate.test.js.map +1 -1
  11. package/dist/scripts/__tests__/setup-existing-project.test.js +79 -0
  12. package/dist/scripts/__tests__/setup-existing-project.test.js.map +1 -1
  13. package/dist/scripts/__tests__/setup-interactive.test.js +23 -17
  14. package/dist/scripts/__tests__/setup-interactive.test.js.map +1 -1
  15. package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts +5 -0
  16. package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts.map +1 -0
  17. package/dist/scripts/__tests__/spec-impl-workflow.test.js +321 -0
  18. package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +1 -0
  19. package/dist/scripts/__tests__/spec-loader.test.d.ts +5 -0
  20. package/dist/scripts/__tests__/spec-loader.test.d.ts.map +1 -0
  21. package/dist/scripts/__tests__/spec-loader.test.js +153 -0
  22. package/dist/scripts/__tests__/spec-loader.test.js.map +1 -0
  23. package/dist/scripts/__tests__/validate-phase.test.js +26 -22
  24. package/dist/scripts/__tests__/validate-phase.test.js.map +1 -1
  25. package/dist/scripts/config/config-schema.d.ts +17 -0
  26. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  27. package/dist/scripts/config/config-schema.js +55 -26
  28. package/dist/scripts/config/config-schema.js.map +1 -1
  29. package/dist/scripts/config-interactive.d.ts.map +1 -1
  30. package/dist/scripts/config-interactive.js +53 -38
  31. package/dist/scripts/config-interactive.js.map +1 -1
  32. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  33. package/dist/scripts/confluence-sync.js +0 -11
  34. package/dist/scripts/confluence-sync.js.map +1 -1
  35. package/dist/scripts/constants/__tests__/environments.test.js +39 -5
  36. package/dist/scripts/constants/__tests__/environments.test.js.map +1 -1
  37. package/dist/scripts/constants/environments.d.ts +1 -1
  38. package/dist/scripts/constants/environments.d.ts.map +1 -1
  39. package/dist/scripts/constants/environments.js +22 -7
  40. package/dist/scripts/constants/environments.js.map +1 -1
  41. package/dist/scripts/constants/test-commands.d.ts +36 -0
  42. package/dist/scripts/constants/test-commands.d.ts.map +1 -0
  43. package/dist/scripts/constants/test-commands.js +70 -0
  44. package/dist/scripts/constants/test-commands.js.map +1 -0
  45. package/dist/scripts/jira-sync.d.ts +89 -3
  46. package/dist/scripts/jira-sync.d.ts.map +1 -1
  47. package/dist/scripts/jira-sync.js +366 -96
  48. package/dist/scripts/jira-sync.js.map +1 -1
  49. package/dist/scripts/markdown-to-confluence.js +1 -1
  50. package/dist/scripts/markdown-to-confluence.js.map +1 -1
  51. package/dist/scripts/phase-runner.d.ts +1 -1
  52. package/dist/scripts/phase-runner.d.ts.map +1 -1
  53. package/dist/scripts/phase-runner.js +809 -13
  54. package/dist/scripts/phase-runner.js.map +1 -1
  55. package/dist/scripts/pr-automation.d.ts.map +1 -1
  56. package/dist/scripts/pr-automation.js.map +1 -1
  57. package/dist/scripts/pre-flight-check.js +1 -1
  58. package/dist/scripts/pre-flight-check.js.map +1 -1
  59. package/dist/scripts/setup-existing-project.js +61 -29
  60. package/dist/scripts/setup-existing-project.js.map +1 -1
  61. package/dist/scripts/setup-interactive.js +3 -3
  62. package/dist/scripts/setup-interactive.js.map +1 -1
  63. package/dist/scripts/spec-impl-workflow.d.ts +94 -0
  64. package/dist/scripts/spec-impl-workflow.d.ts.map +1 -0
  65. package/dist/scripts/spec-impl-workflow.js +354 -0
  66. package/dist/scripts/spec-impl-workflow.js.map +1 -0
  67. package/dist/scripts/template/__tests__/renderer.test.js.map +1 -1
  68. package/dist/scripts/test-execution-generator.d.ts +52 -0
  69. package/dist/scripts/test-execution-generator.d.ts.map +1 -0
  70. package/dist/scripts/test-execution-generator.js +576 -0
  71. package/dist/scripts/test-execution-generator.js.map +1 -0
  72. package/dist/scripts/test-interactive.d.ts +10 -0
  73. package/dist/scripts/test-interactive.d.ts.map +1 -0
  74. package/dist/scripts/test-interactive.js +627 -0
  75. package/dist/scripts/test-interactive.js.map +1 -0
  76. package/dist/scripts/test-new-features.d.ts +5 -0
  77. package/dist/scripts/test-new-features.d.ts.map +1 -0
  78. package/dist/scripts/test-new-features.js +145 -0
  79. package/dist/scripts/test-new-features.js.map +1 -0
  80. package/dist/scripts/test-spec-generator.d.ts +29 -0
  81. package/dist/scripts/test-spec-generator.d.ts.map +1 -0
  82. package/dist/scripts/test-spec-generator.js +494 -0
  83. package/dist/scripts/test-spec-generator.js.map +1 -0
  84. package/dist/scripts/test-workflow-stages.d.ts +6 -0
  85. package/dist/scripts/test-workflow-stages.d.ts.map +1 -0
  86. package/dist/scripts/test-workflow-stages.js +43 -0
  87. package/dist/scripts/test-workflow-stages.js.map +1 -0
  88. package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts +5 -0
  89. package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts.map +1 -0
  90. package/dist/scripts/utils/__tests__/aidlc-parser.test.js +315 -0
  91. package/dist/scripts/utils/__tests__/aidlc-parser.test.js.map +1 -0
  92. package/dist/scripts/utils/__tests__/business-days.test.d.ts +5 -0
  93. package/dist/scripts/utils/__tests__/business-days.test.d.ts.map +1 -0
  94. package/dist/scripts/utils/__tests__/business-days.test.js +171 -0
  95. package/dist/scripts/utils/__tests__/business-days.test.js.map +1 -0
  96. package/dist/scripts/utils/__tests__/config-loader.test.js +1 -1
  97. package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
  98. package/dist/scripts/utils/__tests__/config-validator.test.js +164 -35
  99. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  100. package/dist/scripts/utils/__tests__/env-config.test.d.ts +5 -0
  101. package/dist/scripts/utils/__tests__/env-config.test.d.ts.map +1 -0
  102. package/dist/scripts/utils/__tests__/env-config.test.js +218 -0
  103. package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -0
  104. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts +5 -0
  105. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts.map +1 -0
  106. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js +202 -0
  107. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js.map +1 -0
  108. package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts +5 -0
  109. package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts.map +1 -0
  110. package/dist/scripts/utils/__tests__/tasks-converter.test.js +500 -0
  111. package/dist/scripts/utils/__tests__/tasks-converter.test.js.map +1 -0
  112. package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts +5 -0
  113. package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts.map +1 -0
  114. package/dist/scripts/utils/__tests__/tasks-format-validator.test.js +314 -0
  115. package/dist/scripts/utils/__tests__/tasks-format-validator.test.js.map +1 -0
  116. package/dist/scripts/utils/__tests__/test-runner.test.d.ts +5 -0
  117. package/dist/scripts/utils/__tests__/test-runner.test.d.ts.map +1 -0
  118. package/dist/scripts/utils/__tests__/test-runner.test.js +64 -0
  119. package/dist/scripts/utils/__tests__/test-runner.test.js.map +1 -0
  120. package/dist/scripts/utils/aidlc-parser.d.ts +86 -0
  121. package/dist/scripts/utils/aidlc-parser.d.ts.map +1 -0
  122. package/dist/scripts/utils/aidlc-parser.js +208 -0
  123. package/dist/scripts/utils/aidlc-parser.js.map +1 -0
  124. package/dist/scripts/utils/business-days.d.ts +52 -0
  125. package/dist/scripts/utils/business-days.d.ts.map +1 -0
  126. package/dist/scripts/utils/business-days.js +98 -0
  127. package/dist/scripts/utils/business-days.js.map +1 -0
  128. package/dist/scripts/utils/ci-generator.d.ts +14 -0
  129. package/dist/scripts/utils/ci-generator.d.ts.map +1 -0
  130. package/dist/scripts/utils/ci-generator.js +61 -0
  131. package/dist/scripts/utils/ci-generator.js.map +1 -0
  132. package/dist/scripts/utils/config-loader.js +2 -2
  133. package/dist/scripts/utils/config-loader.js.map +1 -1
  134. package/dist/scripts/utils/config-validator.d.ts +7 -1
  135. package/dist/scripts/utils/config-validator.d.ts.map +1 -1
  136. package/dist/scripts/utils/config-validator.js +136 -23
  137. package/dist/scripts/utils/config-validator.js.map +1 -1
  138. package/dist/scripts/utils/confluence-approval.d.ts +46 -0
  139. package/dist/scripts/utils/confluence-approval.d.ts.map +1 -0
  140. package/dist/scripts/utils/confluence-approval.js +118 -0
  141. package/dist/scripts/utils/confluence-approval.js.map +1 -0
  142. package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
  143. package/dist/scripts/utils/confluence-hierarchy.js +1 -1
  144. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  145. package/dist/scripts/utils/docker-generator.d.ts +9 -0
  146. package/dist/scripts/utils/docker-generator.d.ts.map +1 -0
  147. package/dist/scripts/utils/docker-generator.js +132 -0
  148. package/dist/scripts/utils/docker-generator.js.map +1 -0
  149. package/dist/scripts/utils/docker-requirement-detector.d.ts +15 -0
  150. package/dist/scripts/utils/docker-requirement-detector.d.ts.map +1 -0
  151. package/dist/scripts/utils/docker-requirement-detector.js +124 -0
  152. package/dist/scripts/utils/docker-requirement-detector.js.map +1 -0
  153. package/dist/scripts/utils/env-config.d.ts +54 -0
  154. package/dist/scripts/utils/env-config.d.ts.map +1 -0
  155. package/dist/scripts/utils/env-config.js +414 -0
  156. package/dist/scripts/utils/env-config.js.map +1 -0
  157. package/dist/scripts/utils/jira-issue-type-fetcher.d.ts +70 -0
  158. package/dist/scripts/utils/jira-issue-type-fetcher.d.ts.map +1 -0
  159. package/dist/scripts/utils/jira-issue-type-fetcher.js +147 -0
  160. package/dist/scripts/utils/jira-issue-type-fetcher.js.map +1 -0
  161. package/dist/scripts/utils/language-detector.d.ts +14 -0
  162. package/dist/scripts/utils/language-detector.d.ts.map +1 -0
  163. package/dist/scripts/utils/language-detector.js +119 -0
  164. package/dist/scripts/utils/language-detector.js.map +1 -0
  165. package/dist/scripts/utils/markdown-parser.d.ts +55 -0
  166. package/dist/scripts/utils/markdown-parser.d.ts.map +1 -0
  167. package/dist/scripts/utils/markdown-parser.js +289 -0
  168. package/dist/scripts/utils/markdown-parser.js.map +1 -0
  169. package/dist/scripts/utils/project-detector.d.ts +17 -0
  170. package/dist/scripts/utils/project-detector.d.ts.map +1 -0
  171. package/dist/scripts/utils/project-detector.js +166 -0
  172. package/dist/scripts/utils/project-detector.js.map +1 -0
  173. package/dist/scripts/utils/project-finder.js +2 -2
  174. package/dist/scripts/utils/project-finder.js.map +1 -1
  175. package/dist/scripts/utils/release-notes-generator.d.ts +56 -0
  176. package/dist/scripts/utils/release-notes-generator.d.ts.map +1 -0
  177. package/dist/scripts/utils/release-notes-generator.js +162 -0
  178. package/dist/scripts/utils/release-notes-generator.js.map +1 -0
  179. package/dist/scripts/utils/spec-loader.d.ts +79 -0
  180. package/dist/scripts/utils/spec-loader.d.ts.map +1 -0
  181. package/dist/scripts/utils/spec-loader.js +80 -0
  182. package/dist/scripts/utils/spec-loader.js.map +1 -0
  183. package/dist/scripts/utils/spec-updater.d.ts +7 -0
  184. package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
  185. package/dist/scripts/utils/spec-updater.js.map +1 -1
  186. package/dist/scripts/utils/tasks-converter.d.ts +57 -0
  187. package/dist/scripts/utils/tasks-converter.d.ts.map +1 -0
  188. package/dist/scripts/utils/tasks-converter.js +322 -0
  189. package/dist/scripts/utils/tasks-converter.js.map +1 -0
  190. package/dist/scripts/utils/tasks-format-validator.d.ts +36 -0
  191. package/dist/scripts/utils/tasks-format-validator.d.ts.map +1 -0
  192. package/dist/scripts/utils/tasks-format-validator.js +158 -0
  193. package/dist/scripts/utils/tasks-format-validator.js.map +1 -0
  194. package/dist/scripts/utils/template-applier.d.ts +37 -0
  195. package/dist/scripts/utils/template-applier.d.ts.map +1 -0
  196. package/dist/scripts/utils/template-applier.js +129 -0
  197. package/dist/scripts/utils/template-applier.js.map +1 -0
  198. package/dist/scripts/utils/test-config-generator.d.ts +12 -0
  199. package/dist/scripts/utils/test-config-generator.d.ts.map +1 -0
  200. package/dist/scripts/utils/test-config-generator.js +185 -0
  201. package/dist/scripts/utils/test-config-generator.js.map +1 -0
  202. package/dist/scripts/utils/test-runner.d.ts +31 -0
  203. package/dist/scripts/utils/test-runner.d.ts.map +1 -0
  204. package/dist/scripts/utils/test-runner.js +103 -0
  205. package/dist/scripts/utils/test-runner.js.map +1 -0
  206. package/dist/scripts/validate-phase.d.ts +1 -1
  207. package/dist/scripts/validate-phase.d.ts.map +1 -1
  208. package/dist/scripts/validate-phase.js +153 -5
  209. package/dist/scripts/validate-phase.js.map +1 -1
  210. package/dist/scripts/workflow-orchestrator.d.ts +8 -0
  211. package/dist/scripts/workflow-orchestrator.d.ts.map +1 -1
  212. package/dist/scripts/workflow-orchestrator.js +108 -7
  213. package/dist/scripts/workflow-orchestrator.js.map +1 -1
  214. package/dist/src/__tests__/integration/internationalization.test.d.ts +8 -0
  215. package/dist/src/__tests__/integration/internationalization.test.d.ts.map +1 -0
  216. package/dist/src/__tests__/integration/internationalization.test.js +333 -0
  217. package/dist/src/__tests__/integration/internationalization.test.js.map +1 -0
  218. package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts +1 -1
  219. package/dist/src/__tests__/integration/setup/claude-agent.test.js +17 -20
  220. package/dist/src/__tests__/integration/setup/claude-agent.test.js.map +1 -1
  221. package/dist/src/__tests__/integration/setup/cursor.test.js +23 -19
  222. package/dist/src/__tests__/integration/setup/cursor.test.js.map +1 -1
  223. package/dist/src/__tests__/integration/setup/validation.test.js +41 -58
  224. package/dist/src/__tests__/integration/setup/validation.test.js.map +1 -1
  225. package/dist/src/cli.d.ts.map +1 -1
  226. package/dist/src/cli.js +208 -18
  227. package/dist/src/cli.js.map +1 -1
  228. package/dist/src/commands/setup-existing.d.ts +3 -0
  229. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  230. package/dist/src/commands/setup-existing.js +334 -47
  231. package/dist/src/commands/setup-existing.js.map +1 -1
  232. package/docs/README.md +3 -1
  233. package/docs/context.md +59 -0
  234. package/docs/design-issue-55.md +240 -0
  235. package/docs/design-issue-56.md +181 -0
  236. package/docs/michi-development/testing/manual-verification-flow.md +2242 -0
  237. package/docs/michi-development/testing/pre-publish-checklist.md +560 -0
  238. package/docs/plan.md +275 -0
  239. package/docs/user-guide/getting-started/github-token-setup.md +509 -0
  240. package/docs/{getting-started → user-guide/getting-started}/quick-start.md +16 -0
  241. package/docs/{getting-started → user-guide/getting-started}/setup.md +28 -1
  242. package/docs/user-guide/guides/internationalization.md +540 -0
  243. package/docs/{guides → user-guide/guides}/multi-project.md +1 -1
  244. package/docs/{guides → user-guide/guides}/phase-automation.md +67 -9
  245. package/docs/user-guide/guides/workflow.md +582 -0
  246. package/docs/user-guide/hands-on/README.md +142 -0
  247. package/docs/user-guide/hands-on/claude-agent-setup.md +455 -0
  248. package/docs/user-guide/hands-on/claude-setup.md +398 -0
  249. package/docs/user-guide/hands-on/cursor-setup.md +352 -0
  250. package/docs/user-guide/hands-on/troubleshooting.md +964 -0
  251. package/docs/user-guide/hands-on/verification-checklist.md +438 -0
  252. package/docs/user-guide/hands-on/workflow-walkthrough.md +906 -0
  253. package/docs/user-guide/reference/config.md +564 -0
  254. package/docs/{reference → user-guide/reference}/quick-reference.md +53 -40
  255. package/docs/user-guide/release/ci-setup.md +541 -0
  256. package/docs/user-guide/release/release-flow.md +476 -0
  257. package/docs/user-guide/templates/test-specs/README.md +173 -0
  258. package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +547 -0
  259. package/docs/user-guide/templates/test-specs/integration-test-spec-template.md +435 -0
  260. package/docs/user-guide/templates/test-specs/performance-test-spec-template.md +454 -0
  261. package/docs/user-guide/templates/test-specs/security-test-spec-template.md +664 -0
  262. package/docs/user-guide/templates/test-specs/unit-test-spec-template.md +328 -0
  263. package/docs/{testing → user-guide/testing}/integration-tests.md +24 -9
  264. package/docs/user-guide/testing/tdd-cycle.md +349 -0
  265. package/docs/user-guide/testing/test-execution-flow.md +396 -0
  266. package/docs/user-guide/testing/test-failure-handling.md +521 -0
  267. package/docs/user-guide/testing/test-planning-flow.md +181 -0
  268. package/docs/user-guide/testing-strategy.md +185 -0
  269. package/docs/verification-guide.md +518 -0
  270. package/package.json +7 -2
  271. package/scripts/__tests__/create-project.test.ts +67 -49
  272. package/scripts/__tests__/jira-transitions.test.ts +225 -0
  273. package/scripts/__tests__/multi-project-estimate.test.ts +36 -30
  274. package/scripts/__tests__/setup-existing-project.test.ts +98 -1
  275. package/scripts/__tests__/setup-interactive.test.ts +52 -46
  276. package/scripts/__tests__/spec-impl-workflow.test.ts +429 -0
  277. package/scripts/__tests__/spec-loader.test.ts +199 -0
  278. package/scripts/__tests__/validate-phase.test.ts +78 -54
  279. package/scripts/config/config-schema.ts +89 -50
  280. package/scripts/config-interactive.ts +191 -136
  281. package/scripts/confluence-sync.ts +0 -12
  282. package/scripts/constants/__tests__/environments.test.ts +42 -6
  283. package/scripts/constants/environments.ts +33 -13
  284. package/scripts/constants/test-commands.ts +96 -0
  285. package/scripts/jira-sync.ts +767 -232
  286. package/scripts/markdown-to-confluence.ts +1 -1
  287. package/scripts/phase-runner.ts +1056 -63
  288. package/scripts/pr-automation.ts +0 -1
  289. package/scripts/pre-flight-check.ts +1 -1
  290. package/scripts/pre-publish-check.sh +311 -0
  291. package/scripts/quick-verify.sh +115 -0
  292. package/scripts/setup-existing-project.ts +201 -117
  293. package/scripts/setup-interactive.ts +4 -4
  294. package/scripts/spec-impl-workflow.ts +505 -0
  295. package/scripts/template/__tests__/renderer.test.ts +1 -2
  296. package/scripts/test-execution-generator.ts +695 -0
  297. package/scripts/test-interactive.ts +779 -0
  298. package/scripts/test-new-features.ts +168 -0
  299. package/scripts/test-npm-package.sh +345 -0
  300. package/scripts/test-spec-generator.ts +574 -0
  301. package/scripts/test-workflow-stages.ts +53 -0
  302. package/scripts/utils/__tests__/aidlc-parser.test.ts +349 -0
  303. package/scripts/utils/__tests__/business-days.test.ts +214 -0
  304. package/scripts/utils/__tests__/config-loader.test.ts +1 -1
  305. package/scripts/utils/__tests__/config-validator.test.ts +309 -88
  306. package/scripts/utils/__tests__/env-config.test.ts +259 -0
  307. package/scripts/utils/__tests__/jira-issue-type-fetcher.test.ts +272 -0
  308. package/scripts/utils/__tests__/tasks-converter.test.ts +582 -0
  309. package/scripts/utils/__tests__/tasks-format-validator.test.ts +338 -0
  310. package/scripts/utils/__tests__/test-runner.test.ts +77 -0
  311. package/scripts/utils/aidlc-parser.ts +289 -0
  312. package/scripts/utils/business-days.ts +115 -0
  313. package/scripts/utils/ci-generator.ts +84 -0
  314. package/scripts/utils/config-loader.ts +2 -2
  315. package/scripts/utils/config-validator.ts +304 -117
  316. package/scripts/utils/confluence-approval.ts +167 -0
  317. package/scripts/utils/confluence-hierarchy.ts +2 -4
  318. package/scripts/utils/docker-generator.ts +151 -0
  319. package/scripts/utils/docker-requirement-detector.ts +153 -0
  320. package/scripts/utils/env-config.ts +526 -0
  321. package/scripts/utils/jira-issue-type-fetcher.ts +199 -0
  322. package/scripts/utils/language-detector.ts +139 -0
  323. package/scripts/utils/markdown-parser.ts +376 -0
  324. package/scripts/utils/project-detector.ts +192 -0
  325. package/scripts/utils/project-finder.ts +2 -2
  326. package/scripts/utils/release-notes-generator.ts +210 -0
  327. package/scripts/utils/spec-loader.ts +125 -0
  328. package/scripts/utils/spec-updater.ts +8 -1
  329. package/scripts/utils/tasks-converter.ts +601 -0
  330. package/scripts/utils/tasks-format-validator.ts +193 -0
  331. package/scripts/utils/template-applier.ts +202 -0
  332. package/scripts/utils/test-config-generator.ts +210 -0
  333. package/scripts/utils/test-runner.ts +133 -0
  334. package/scripts/validate-phase.ts +186 -9
  335. package/scripts/workflow-orchestrator.ts +130 -12
  336. package/templates/ci/github-actions/java.yml +54 -0
  337. package/templates/ci/github-actions/nodejs.yml +46 -0
  338. package/templates/ci/github-actions/php.yml +52 -0
  339. package/templates/ci/screwdriver/java.yaml +17 -0
  340. package/templates/ci/screwdriver/nodejs.yaml +17 -0
  341. package/templates/ci/screwdriver/php.yaml +20 -0
  342. package/templates/claude/commands/kiro/kiro-spec-impl.md +244 -0
  343. package/templates/claude/commands/kiro/kiro-spec-tasks.md +354 -0
  344. package/templates/claude-agent/README.md +7 -1
  345. package/templates/claude-agent/agents/.gitkeep +0 -0
  346. package/templates/claude-agent/agents/designer.md +79 -0
  347. package/templates/claude-agent/agents/developer.md +68 -0
  348. package/templates/claude-agent/agents/manager-agent.md +59 -0
  349. package/templates/claude-agent/agents/tester.md +101 -0
  350. package/templates/claude-agent/commands/kiro/.gitkeep +0 -0
  351. package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +244 -0
  352. package/templates/claude-agent/commands/kiro/kiro-spec-tasks.md +354 -0
  353. package/templates/cline/rules/atlassian-integration.md +36 -0
  354. package/templates/cline/rules/michi-core.md +56 -0
  355. package/templates/codex/AGENTS.override.md +277 -0
  356. package/templates/codex/prompts/confluence-sync.md +177 -0
  357. package/templates/codex/rules/README.md +210 -0
  358. package/templates/common/.kiro/project.json.template +21 -0
  359. package/templates/cursor/commands/kiro/kiro-spec-impl.md +244 -0
  360. package/templates/cursor/commands/kiro/kiro-spec-tasks.md +354 -0
  361. package/templates/gemini/commands/README.md +41 -0
  362. package/templates/gemini/rules/GEMINI.md +80 -0
  363. package/docs/guides/workflow.md +0 -342
  364. package/docs/reference/config.md +0 -545
  365. package/scripts/setup-existing.sh +0 -279
  366. /package/docs/{contributing → michi-development/contributing}/development.md +0 -0
  367. /package/docs/{contributing → michi-development/contributing}/release.md +0 -0
  368. /package/docs/{testing-strategy.md → michi-development/testing-strategy.md} +0 -0
  369. /package/docs/{getting-started → user-guide/getting-started}/new-repository-setup.md +0 -0
  370. /package/docs/{guides → user-guide/guides}/customization.md +0 -0
  371. /package/docs/{reference → user-guide/reference}/tasks-template.md +0 -0
@@ -1,19 +1,19 @@
1
1
  /**
2
2
  * JIRA連携スクリプト
3
3
  * tasks.md から JIRA Epic/Story/Subtask を自動作成
4
- *
4
+ *
5
5
  * 【重要】Epic Link について:
6
6
  * JIRA Cloud では Story を Epic に紐付けるには、Epic Link カスタムフィールド
7
7
  * (通常 customfield_10014)を使用する必要があります。
8
- *
8
+ *
9
9
  * 現在の実装では parent フィールドを使用していますが、これは Subtask 専用です。
10
10
  * Story 作成時に 400 エラーが発生する可能性があります。
11
- *
11
+ *
12
12
  * 対処方法:
13
13
  * 1. JIRA 管理画面で Epic Link のカスタムフィールドIDを確認
14
14
  * 2. 環境変数 JIRA_EPIC_LINK_FIELD に設定(例: customfield_10014)
15
15
  * 3. または、Story 作成後に手動で Epic Link を設定
16
- *
16
+ *
17
17
  * 参考: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-post
18
18
  */
19
19
 
@@ -24,8 +24,11 @@ import { config } from 'dotenv';
24
24
  import { loadProjectMeta } from './utils/project-meta.js';
25
25
  import { validateFeatureNameOrThrow } from './utils/feature-name-validator.js';
26
26
  import { getConfig, getConfigPath } from './utils/config-loader.js';
27
- import { validateForJiraSync } from './utils/config-validator.js';
28
- import { updateSpecJsonAfterJiraSync } from './utils/spec-updater.js';
27
+ import { validateForJiraSyncAsync } from './utils/config-validator.js';
28
+ import {
29
+ updateSpecJsonAfterJiraSync,
30
+ type SpecJson,
31
+ } from './utils/spec-updater.js';
29
32
 
30
33
  config();
31
34
 
@@ -33,7 +36,7 @@ config();
33
36
  * リクエスト間のスリープ処理(レートリミット対策)
34
37
  */
35
38
  function sleep(ms: number): Promise<void> {
36
- return new Promise(resolve => setTimeout(resolve, ms));
39
+ return new Promise((resolve) => setTimeout(resolve, ms));
37
40
  }
38
41
 
39
42
  /**
@@ -44,6 +47,73 @@ function getRequestDelay(): number {
44
47
  return parseInt(process.env.ATLASSIAN_REQUEST_DELAY || '500', 10);
45
48
  }
46
49
 
50
+ /**
51
+ * Atlassian Document Format (ADF) の型定義
52
+ */
53
+ interface ADFNode {
54
+ type: string;
55
+ attrs?: Record<string, unknown>;
56
+ content?: ADFNode[];
57
+ text?: string;
58
+ marks?: Array<{ type: string; attrs?: Record<string, unknown> }>;
59
+ }
60
+
61
+ interface ADFDocument {
62
+ version: number;
63
+ type: 'doc';
64
+ content: ADFNode[];
65
+ }
66
+
67
+ /**
68
+ * JIRA Issue型定義(必要最小限)
69
+ */
70
+ interface JIRAIssue {
71
+ id: string;
72
+ key: string;
73
+ self: string;
74
+ fields: {
75
+ summary: string;
76
+ issuetype?: { id: string; name: string };
77
+ status?: { name: string };
78
+ [key: string]: unknown;
79
+ };
80
+ }
81
+
82
+ /**
83
+ * JIRA Issue作成/更新ペイロード型
84
+ */
85
+ interface JIRAIssuePayload {
86
+ fields: {
87
+ project: { key: string };
88
+ summary: string;
89
+ description?: ADFDocument;
90
+ issuetype: { id: string };
91
+ labels?: string[];
92
+ parent?: { key: string };
93
+ [key: string]: unknown;
94
+ };
95
+ update?: Record<string, unknown>;
96
+ }
97
+
98
+ /**
99
+ * JIRA Issue作成レスポンス型
100
+ */
101
+ interface JIRAIssueCreateResponse {
102
+ id: string;
103
+ key: string;
104
+ self: string;
105
+ }
106
+
107
+ /**
108
+ * JIRA Issue Type型
109
+ */
110
+ interface JIRAIssueType {
111
+ id: string;
112
+ name: string;
113
+ description?: string;
114
+ subtask: boolean;
115
+ }
116
+
47
117
  /**
48
118
  * Storyの詳細情報を抽出
49
119
  */
@@ -59,159 +129,181 @@ interface StoryDetails {
59
129
  dueDate?: string;
60
130
  }
61
131
 
62
- function extractStoryDetails(tasksContent: string, storyTitle: string): StoryDetails {
132
+ function extractStoryDetails(
133
+ tasksContent: string,
134
+ storyTitle: string,
135
+ ): StoryDetails {
63
136
  const details: StoryDetails = { title: storyTitle };
64
-
137
+
65
138
  // Story セクションを抽出(ReDoS対策: [\s\S]*? → [^]*? に変更)
66
139
  const escapedTitle = storyTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
67
- const storyPattern = new RegExp(`### Story [\\d.]+: ${escapedTitle}\\n([^]*?)(?=\\n### Story|\\n## Phase|$)`, 'i');
140
+ const storyPattern = new RegExp(
141
+ `### Story [\\d.]+: ${escapedTitle}\\n([^]*?)(?=\\n### Story|\\n## Phase|$)`,
142
+ 'i',
143
+ );
68
144
  const storyMatch = tasksContent.match(storyPattern);
69
-
145
+
70
146
  if (!storyMatch) return details;
71
-
147
+
72
148
  const storySection = storyMatch[1];
73
-
149
+
74
150
  // 優先度抽出
75
151
  const priorityMatch = storySection.match(/\*\*優先度\*\*:\s*(.+)/);
76
152
  if (priorityMatch) details.priority = priorityMatch[1].trim();
77
-
153
+
78
154
  // 見積もり抽出
79
155
  const estimateMatch = storySection.match(/\*\*見積もり\*\*:\s*(.+)/);
80
156
  if (estimateMatch) details.estimate = estimateMatch[1].trim();
81
-
157
+
82
158
  // 担当抽出
83
159
  const assigneeMatch = storySection.match(/\*\*担当\*\*:\s*(.+)/);
84
160
  if (assigneeMatch) details.assignee = assigneeMatch[1].trim();
85
-
161
+
86
162
  // 期限抽出
87
- const dueDateMatch = storySection.match(/\*\*期限\*\*:\s*(\d{4}-\d{2}-\d{2})/);
163
+ const dueDateMatch = storySection.match(
164
+ /\*\*期限\*\*:\s*(\d{4}-\d{2}-\d{2})/,
165
+ );
88
166
  if (dueDateMatch) details.dueDate = dueDateMatch[1];
89
-
167
+
90
168
  // 説明抽出(改行あり・なし両方に対応)
91
- const descriptionMatch = storySection.match(/\*\*説明\*\*:\s*\n?(.+?)(?=\n\*\*|$)/s);
169
+ const descriptionMatch = storySection.match(
170
+ /\*\*説明\*\*:\s*\n?(.+?)(?=\n\*\*|$)/s,
171
+ );
92
172
  if (descriptionMatch) details.description = descriptionMatch[1].trim();
93
-
173
+
94
174
  // 完了条件抽出
95
- const criteriaMatch = storySection.match(/\*\*完了条件\*\*:\s*\n((?:- \[.\].*\n?)+)/);
175
+ const criteriaMatch = storySection.match(
176
+ /\*\*完了条件\*\*:\s*\n((?:- \[.\].*\n?)+)/,
177
+ );
96
178
  if (criteriaMatch) {
97
179
  details.acceptanceCriteria = criteriaMatch[1]
98
180
  .split('\n')
99
- .filter(line => line.trim().startsWith('- ['))
100
- .map(line => line.replace(/^- \[.\]\s*/, '').trim())
101
- .filter(line => line.length > 0);
181
+ .filter((line) => line.trim().startsWith('- ['))
182
+ .map((line) => line.replace(/^- \[.\]\s*/, '').trim())
183
+ .filter((line) => line.length > 0);
102
184
  }
103
-
185
+
104
186
  // サブタスク抽出
105
- const subtasksMatch = storySection.match(/\*\*サブタスク\*\*:\s*\n((?:- \[.\].*\n?)+)/);
187
+ const subtasksMatch = storySection.match(
188
+ /\*\*サブタスク\*\*:\s*\n((?:- \[.\].*\n?)+)/,
189
+ );
106
190
  if (subtasksMatch) {
107
191
  details.subtasks = subtasksMatch[1]
108
192
  .split('\n')
109
- .filter(line => line.trim().startsWith('- ['))
110
- .map(line => line.replace(/^- \[.\]\s*/, '').trim())
111
- .filter(line => line.length > 0);
193
+ .filter((line) => line.trim().startsWith('- ['))
194
+ .map((line) => line.replace(/^- \[.\]\s*/, '').trim())
195
+ .filter((line) => line.length > 0);
112
196
  }
113
-
197
+
114
198
  // 依存関係抽出
115
199
  const dependenciesMatch = storySection.match(/\*\*依存関係\*\*:\s*(.+)/);
116
200
  if (dependenciesMatch) details.dependencies = dependenciesMatch[1].trim();
117
-
201
+
118
202
  return details;
119
203
  }
120
204
 
121
205
  /**
122
206
  * リッチなADF形式を生成
123
207
  */
124
- function createRichADF(details: StoryDetails, phaseLabel: string, githubUrl: string): any {
125
- const content: any[] = [];
126
-
208
+ function createRichADF(
209
+ details: StoryDetails,
210
+ phaseLabel: string,
211
+ githubUrl: string,
212
+ ): ADFDocument {
213
+ const content: ADFNode[] = [];
214
+
127
215
  // 説明セクション
128
216
  if (details.description) {
129
217
  content.push({
130
218
  type: 'heading',
131
219
  attrs: { level: 2 },
132
- content: [{ type: 'text', text: '説明' }]
220
+ content: [{ type: 'text', text: '説明' }],
133
221
  });
134
222
  content.push({
135
223
  type: 'paragraph',
136
- content: [{ type: 'text', text: details.description }]
224
+ content: [{ type: 'text', text: details.description }],
137
225
  });
138
226
  }
139
-
227
+
140
228
  // メタデータセクション
141
229
  const metadata: string[] = [];
142
230
  if (details.priority) metadata.push(`優先度: ${details.priority}`);
143
231
  if (details.estimate) metadata.push(`見積もり: ${details.estimate}`);
144
232
  if (details.assignee) metadata.push(`担当: ${details.assignee}`);
145
233
  if (details.dependencies) metadata.push(`依存関係: ${details.dependencies}`);
146
-
234
+
147
235
  if (metadata.length > 0) {
148
236
  content.push({
149
237
  type: 'heading',
150
238
  attrs: { level: 2 },
151
- content: [{ type: 'text', text: 'メタデータ' }]
239
+ content: [{ type: 'text', text: 'メタデータ' }],
152
240
  });
153
- metadata.forEach(item => {
241
+ metadata.forEach((item) => {
154
242
  content.push({
155
243
  type: 'paragraph',
156
- content: [{ type: 'text', text: item }]
244
+ content: [{ type: 'text', text: item }],
157
245
  });
158
246
  });
159
247
  }
160
-
248
+
161
249
  // 完了条件セクション
162
250
  if (details.acceptanceCriteria && details.acceptanceCriteria.length > 0) {
163
251
  content.push({
164
252
  type: 'heading',
165
253
  attrs: { level: 2 },
166
- content: [{ type: 'text', text: '完了条件' }]
254
+ content: [{ type: 'text', text: '完了条件' }],
167
255
  });
168
-
169
- const listItems = details.acceptanceCriteria.map(criterion => ({
256
+
257
+ const listItems = details.acceptanceCriteria.map((criterion) => ({
170
258
  type: 'listItem',
171
- content: [{
172
- type: 'paragraph',
173
- content: [{ type: 'text', text: criterion }]
174
- }]
259
+ content: [
260
+ {
261
+ type: 'paragraph',
262
+ content: [{ type: 'text', text: criterion }],
263
+ },
264
+ ],
175
265
  }));
176
-
266
+
177
267
  content.push({
178
268
  type: 'bulletList',
179
- content: listItems
269
+ content: listItems,
180
270
  });
181
271
  }
182
-
272
+
183
273
  // サブタスクセクション
184
274
  if (details.subtasks && details.subtasks.length > 0) {
185
275
  content.push({
186
276
  type: 'heading',
187
277
  attrs: { level: 2 },
188
- content: [{ type: 'text', text: 'サブタスク' }]
278
+ content: [{ type: 'text', text: 'サブタスク' }],
189
279
  });
190
-
191
- const listItems = details.subtasks.map(subtask => ({
280
+
281
+ const listItems = details.subtasks.map((subtask) => ({
192
282
  type: 'listItem',
193
- content: [{
194
- type: 'paragraph',
195
- content: [{ type: 'text', text: subtask }]
196
- }]
283
+ content: [
284
+ {
285
+ type: 'paragraph',
286
+ content: [{ type: 'text', text: subtask }],
287
+ },
288
+ ],
197
289
  }));
198
-
290
+
199
291
  content.push({
200
292
  type: 'bulletList',
201
- content: listItems
293
+ content: listItems,
202
294
  });
203
295
  }
204
-
296
+
205
297
  // フッター(Phase、GitHubリンク)
206
298
  content.push({
207
- type: 'rule'
299
+ type: 'rule',
208
300
  });
209
301
  content.push({
210
302
  type: 'paragraph',
211
303
  content: [
212
304
  { type: 'text', text: 'Phase: ', marks: [{ type: 'strong' }] },
213
- { type: 'text', text: phaseLabel }
214
- ]
305
+ { type: 'text', text: phaseLabel },
306
+ ],
215
307
  });
216
308
  content.push({
217
309
  type: 'paragraph',
@@ -220,40 +312,42 @@ function createRichADF(details: StoryDetails, phaseLabel: string, githubUrl: str
220
312
  {
221
313
  type: 'text',
222
314
  text: githubUrl,
223
- marks: [{
224
- type: 'link',
225
- attrs: { href: githubUrl }
226
- }]
227
- }
228
- ]
315
+ marks: [
316
+ {
317
+ type: 'link',
318
+ attrs: { href: githubUrl },
319
+ },
320
+ ],
321
+ },
322
+ ],
229
323
  });
230
-
324
+
231
325
  return {
232
326
  type: 'doc',
233
327
  version: 1,
234
- content: content
328
+ content: content,
235
329
  };
236
330
  }
237
331
 
238
332
  /**
239
333
  * プレーンテキストをAtlassian Document Format(ADF)に変換
240
334
  */
241
- function textToADF(text: string): any {
335
+ function textToADF(text: string): ADFDocument {
242
336
  // 改行で分割して段落を作成
243
- const paragraphs = text.split('\n').filter(line => line.trim().length > 0);
244
-
337
+ const paragraphs = text.split('\n').filter((line) => line.trim().length > 0);
338
+
245
339
  return {
246
340
  type: 'doc',
247
341
  version: 1,
248
- content: paragraphs.map(para => ({
342
+ content: paragraphs.map((para) => ({
249
343
  type: 'paragraph',
250
344
  content: [
251
345
  {
252
346
  type: 'text',
253
- text: para.trim()
254
- }
255
- ]
256
- }))
347
+ text: para.trim(),
348
+ },
349
+ ],
350
+ })),
257
351
  };
258
352
  }
259
353
 
@@ -267,11 +361,11 @@ function getJIRAConfig(): JIRAConfig {
267
361
  const url = process.env.ATLASSIAN_URL;
268
362
  const email = process.env.ATLASSIAN_EMAIL;
269
363
  const apiToken = process.env.ATLASSIAN_API_TOKEN;
270
-
364
+
271
365
  if (!url || !email || !apiToken) {
272
366
  throw new Error('Missing JIRA credentials in .env');
273
367
  }
274
-
368
+
275
369
  return { url, email, apiToken };
276
370
  }
277
371
 
@@ -279,122 +373,382 @@ class JIRAClient {
279
373
  private baseUrl: string;
280
374
  private auth: string;
281
375
  private requestDelay: number;
282
-
376
+
283
377
  constructor(config: JIRAConfig) {
284
378
  this.baseUrl = `${config.url}/rest/api/3`;
285
- this.auth = Buffer.from(`${config.email}:${config.apiToken}`).toString('base64');
379
+ this.auth = Buffer.from(`${config.email}:${config.apiToken}`).toString(
380
+ 'base64',
381
+ );
286
382
  this.requestDelay = getRequestDelay();
287
383
  }
288
-
384
+
289
385
  /**
290
386
  * JQL検索でIssueを検索
291
387
  * @throws 検索エラー時は例外を再スロー(呼び出し元で処理)
292
388
  */
293
- async searchIssues(jql: string): Promise<any[]> {
389
+ async searchIssues(jql: string): Promise<JIRAIssue[]> {
294
390
  // レートリミット対策: リクエスト前に待機
295
391
  await sleep(this.requestDelay);
296
-
392
+
297
393
  try {
394
+ // JIRA API v3の検索エンドポイントを使用
395
+ // GET /rest/api/3/search でJQL検索を実行(GETメソッドが推奨)
298
396
  const response = await axios.get(`${this.baseUrl}/search`, {
299
- params: { jql, maxResults: 100 },
397
+ params: {
398
+ jql,
399
+ maxResults: 100,
400
+ fields: 'summary,issuetype,status,key',
401
+ },
300
402
  headers: {
301
- 'Authorization': `Basic ${this.auth}`,
302
- 'Content-Type': 'application/json'
303
- }
403
+ Authorization: `Basic ${this.auth}`,
404
+ 'Content-Type': 'application/json',
405
+ },
304
406
  });
305
407
  return response.data.issues || [];
306
408
  } catch (error) {
307
- console.error('Error searching issues:', error instanceof Error ? error.message : error);
409
+ // エラーハンドリング改善
410
+ if (axios.isAxiosError(error)) {
411
+ const status = error.response?.status;
412
+ const errorMessages = error.response?.data?.errorMessages || [];
413
+ const message = errorMessages.join(', ') || error.message;
414
+
415
+ console.error(`Error searching issues (HTTP ${status}): ${message}`);
416
+
417
+ if (status === 410) {
418
+ console.error(
419
+ '💡 Hint: The search API endpoint returned 410 (Gone).',
420
+ );
421
+ console.error(
422
+ ' This may indicate the endpoint has been deprecated or disabled.',
423
+ );
424
+ console.error(
425
+ ' Check JIRA instance configuration or try alternative search methods.',
426
+ );
427
+ } else if (status === 401) {
428
+ console.error(
429
+ '💡 Hint: Authentication failed. Check ATLASSIAN_API_TOKEN in .env',
430
+ );
431
+ } else if (status === 403) {
432
+ console.error(
433
+ '💡 Hint: Permission denied. Check API token permissions in JIRA.',
434
+ );
435
+ }
436
+ } else {
437
+ console.error(
438
+ 'Error searching issues:',
439
+ error instanceof Error ? error.message : error,
440
+ );
441
+ }
308
442
  throw error; // エラーを再スローして呼び出し元で処理
309
443
  }
310
444
  }
311
-
312
- async createIssue(payload: any): Promise<any> {
445
+
446
+ async createIssue(
447
+ payload: JIRAIssuePayload,
448
+ ): Promise<JIRAIssueCreateResponse> {
313
449
  // レートリミット対策: リクエスト前に待機
314
450
  await sleep(this.requestDelay);
315
-
316
- const response = await axios.post(`${this.baseUrl}/issue`, payload, {
317
- headers: {
318
- 'Authorization': `Basic ${this.auth}`,
319
- 'Content-Type': 'application/json'
320
- }
321
- });
451
+
452
+ const response = await axios.post<JIRAIssueCreateResponse>(
453
+ `${this.baseUrl}/issue`,
454
+ payload,
455
+ {
456
+ headers: {
457
+ Authorization: `Basic ${this.auth}`,
458
+ 'Content-Type': 'application/json',
459
+ },
460
+ },
461
+ );
322
462
  return response.data;
323
463
  }
324
-
325
- async updateIssue(issueKey: string, payload: any): Promise<void> {
464
+
465
+ async updateIssue(
466
+ issueKey: string,
467
+ payload: Partial<JIRAIssuePayload>,
468
+ ): Promise<void> {
326
469
  // レートリミット対策: リクエスト前に待機
327
470
  await sleep(this.requestDelay);
328
-
471
+
329
472
  await axios.put(`${this.baseUrl}/issue/${issueKey}`, payload, {
330
473
  headers: {
331
- 'Authorization': `Basic ${this.auth}`,
332
- 'Content-Type': 'application/json'
333
- }
474
+ Authorization: `Basic ${this.auth}`,
475
+ 'Content-Type': 'application/json',
476
+ },
334
477
  });
335
478
  }
479
+
480
+ /**
481
+ * JIRAチケットのステータスを変更(トランジション実行)
482
+ * @param issueKey JIRAチケットキー (例: "PROJ-123")
483
+ * @param transitionName 遷移先ステータス名 (例: "In Progress", "Ready for Review")
484
+ * @throws トランジションが見つからない場合はエラー
485
+ */
486
+ async transitionIssue(
487
+ issueKey: string,
488
+ transitionName: string,
489
+ ): Promise<void> {
490
+ // レートリミット対策: リクエスト前に待機
491
+ await sleep(this.requestDelay);
492
+
493
+ try {
494
+ // 1. 利用可能なトランジションを取得
495
+ const transitionsResponse = await axios.get(
496
+ `${this.baseUrl}/issue/${issueKey}/transitions`,
497
+ {
498
+ headers: {
499
+ Authorization: `Basic ${this.auth}`,
500
+ 'Content-Type': 'application/json',
501
+ },
502
+ },
503
+ );
504
+
505
+ const transitions = transitionsResponse.data.transitions || [];
506
+
507
+ // 2. transitionNameに一致するトランジションIDを特定
508
+ // 名前の完全一致または部分一致で検索
509
+ const transition = transitions.find(
510
+ (t: { id: string; name: string }) =>
511
+ t.name.toLowerCase() === transitionName.toLowerCase() ||
512
+ t.name.toLowerCase().includes(transitionName.toLowerCase()),
513
+ );
514
+
515
+ if (!transition) {
516
+ const availableTransitions = transitions
517
+ .map((t: { name: string }) => t.name)
518
+ .join(', ');
519
+ throw new Error(
520
+ `Transition "${transitionName}" not found for issue ${issueKey}. ` +
521
+ `Available transitions: ${availableTransitions || 'none'}`,
522
+ );
523
+ }
524
+
525
+ // レートリミット対策: リクエスト前に待機
526
+ await sleep(this.requestDelay);
527
+
528
+ // 3. トランジションを実行
529
+ await axios.post(
530
+ `${this.baseUrl}/issue/${issueKey}/transitions`,
531
+ {
532
+ transition: { id: transition.id },
533
+ },
534
+ {
535
+ headers: {
536
+ Authorization: `Basic ${this.auth}`,
537
+ 'Content-Type': 'application/json',
538
+ },
539
+ },
540
+ );
541
+
542
+ console.log(
543
+ `✅ ${issueKey} のステータスを「${transition.name}」に変更しました`,
544
+ );
545
+ } catch (error) {
546
+ if (axios.isAxiosError(error)) {
547
+ const status = error.response?.status;
548
+ const errorMessages = error.response?.data?.errorMessages || [];
549
+ const message = errorMessages.join(', ') || error.message;
550
+
551
+ console.error(
552
+ `Error transitioning issue ${issueKey} (HTTP ${status}): ${message}`,
553
+ );
554
+
555
+ if (status === 404) {
556
+ console.error(
557
+ `💡 Hint: Issue ${issueKey} was not found. Check the issue key.`,
558
+ );
559
+ } else if (status === 400) {
560
+ console.error(
561
+ '💡 Hint: The transition may not be valid from the current status.',
562
+ );
563
+ }
564
+ }
565
+ throw error;
566
+ }
567
+ }
568
+
569
+ /**
570
+ * JIRAチケットにコメントを追加
571
+ * @param issueKey JIRAチケットキー
572
+ * @param commentText コメント内容
573
+ */
574
+ async addComment(issueKey: string, commentText: string): Promise<void> {
575
+ // レートリミット対策: リクエスト前に待機
576
+ await sleep(this.requestDelay);
577
+
578
+ try {
579
+ // Atlassian Document Format (ADF) でコメントを作成
580
+ const commentBody: ADFDocument = {
581
+ type: 'doc',
582
+ version: 1,
583
+ content: [
584
+ {
585
+ type: 'paragraph',
586
+ content: [
587
+ {
588
+ type: 'text',
589
+ text: commentText,
590
+ },
591
+ ],
592
+ },
593
+ ],
594
+ };
595
+
596
+ await axios.post(
597
+ `${this.baseUrl}/issue/${issueKey}/comment`,
598
+ {
599
+ body: commentBody,
600
+ },
601
+ {
602
+ headers: {
603
+ Authorization: `Basic ${this.auth}`,
604
+ 'Content-Type': 'application/json',
605
+ },
606
+ },
607
+ );
608
+
609
+ console.log(`✅ ${issueKey} にコメントを追加しました`);
610
+ } catch (error) {
611
+ if (axios.isAxiosError(error)) {
612
+ const status = error.response?.status;
613
+ const errorMessages = error.response?.data?.errorMessages || [];
614
+ const message = errorMessages.join(', ') || error.message;
615
+
616
+ console.error(
617
+ `Error adding comment to ${issueKey} (HTTP ${status}): ${message}`,
618
+ );
619
+
620
+ if (status === 404) {
621
+ console.error(
622
+ `💡 Hint: Issue ${issueKey} was not found. Check the issue key.`,
623
+ );
624
+ }
625
+ }
626
+ throw error;
627
+ }
628
+ }
629
+
630
+ /**
631
+ * プロジェクトのIssue Type IDを取得
632
+ * @param projectKey プロジェクトキー
633
+ * @param issueTypeName Issue Type名(例: "Epic", "Story")
634
+ * @returns Issue Type ID
635
+ */
636
+ async getIssueTypeId(
637
+ projectKey: string,
638
+ issueTypeName: string,
639
+ ): Promise<string | null> {
640
+ await sleep(this.requestDelay);
641
+
642
+ try {
643
+ const response = await axios.get(
644
+ `${this.baseUrl}/project/${projectKey}`,
645
+ {
646
+ headers: {
647
+ Authorization: `Basic ${this.auth}`,
648
+ 'Content-Type': 'application/json',
649
+ },
650
+ },
651
+ );
652
+
653
+ const issueTypes = (response.data.issueTypes || []) as JIRAIssueType[];
654
+ const issueType = issueTypes.find(
655
+ (it: JIRAIssueType) =>
656
+ it.name.toLowerCase() === issueTypeName.toLowerCase() ||
657
+ it.name === issueTypeName,
658
+ );
659
+
660
+ return issueType ? issueType.id : null;
661
+ } catch (error) {
662
+ console.error(
663
+ `Error getting issue type ID for ${issueTypeName}:`,
664
+ error instanceof Error ? error.message : error,
665
+ );
666
+ return null;
667
+ }
668
+ }
336
669
  }
337
670
 
338
671
  async function syncTasksToJIRA(featureName: string): Promise<void> {
339
672
  console.log(`Syncing tasks for feature: ${featureName}`);
340
-
673
+
341
674
  // feature名のバリデーション(必須)
342
675
  validateFeatureNameOrThrow(featureName);
343
-
344
- // 実行前の必須設定値チェック
345
- const validation = validateForJiraSync();
346
-
676
+
677
+ // 実行前の必須設定値チェック(非同期版:Issue Type IDの存在チェック付き)
678
+ const validation = await validateForJiraSyncAsync();
679
+
347
680
  if (validation.info.length > 0) {
348
- validation.info.forEach(msg => console.log(`ℹ️ ${msg}`));
681
+ validation.info.forEach((msg) => console.log(`ℹ️ ${msg}`));
349
682
  }
350
-
683
+
351
684
  if (validation.warnings.length > 0) {
352
685
  console.warn('⚠️ Warnings:');
353
- validation.warnings.forEach(warning => console.warn(` ${warning}`));
686
+ validation.warnings.forEach((warning) => console.warn(` ${warning}`));
354
687
  }
355
-
688
+
356
689
  if (validation.errors.length > 0) {
357
690
  console.error('❌ Configuration errors:');
358
- validation.errors.forEach(error => console.error(` ${error}`));
691
+ validation.errors.forEach((error) => console.error(` ${error}`));
359
692
  const configPath = getConfigPath();
360
693
  console.error(`\n設定ファイル: ${configPath}`);
361
- throw new Error('JIRA同期に必要な設定値が不足しています。上記のエラーを確認して設定を修正してください。');
694
+ throw new Error(
695
+ 'JIRA同期に必要な設定値が不足しています。上記のエラーを確認して設定を修正してください。',
696
+ );
362
697
  }
363
-
364
- console.log(`⏳ Request delay: ${getRequestDelay()}ms (set ATLASSIAN_REQUEST_DELAY to adjust)`);
365
-
698
+
699
+ console.log(
700
+ `⏳ Request delay: ${getRequestDelay()}ms (set ATLASSIAN_REQUEST_DELAY to adjust)`,
701
+ );
702
+
366
703
  // 設定からissue type IDを取得(検索と作成の両方で使用)
367
704
  const appConfig = getConfig();
368
- const storyIssueTypeId = appConfig.jira?.issueTypes?.story || process.env.JIRA_ISSUE_TYPE_STORY;
369
- const subtaskIssueTypeId = appConfig.jira?.issueTypes?.subtask || process.env.JIRA_ISSUE_TYPE_SUBTASK;
370
-
705
+ const projectMeta = loadProjectMeta();
706
+ const config = getJIRAConfig();
707
+ const client = new JIRAClient(config);
708
+
709
+ // StoryタイプのIDを動的に取得(日本語JIRAでは "ストーリー" という名前の場合がある)
710
+ let storyIssueTypeId: string | undefined =
711
+ appConfig.jira?.issueTypes?.story || process.env.JIRA_ISSUE_TYPE_STORY;
712
+ console.log(
713
+ `📋 Story Issue Type ID from config/env: ${storyIssueTypeId || 'not found'}`,
714
+ );
715
+
716
+ if (!storyIssueTypeId) {
717
+ console.log('🔍 Attempting to find Story issue type dynamically...');
718
+ const foundId =
719
+ (await client.getIssueTypeId(projectMeta.jiraProjectKey, 'Story')) ||
720
+ (await client.getIssueTypeId(projectMeta.jiraProjectKey, 'ストーリー'));
721
+ storyIssueTypeId = foundId ?? undefined;
722
+ console.log(
723
+ `📋 Story Issue Type ID from API: ${storyIssueTypeId || 'not found'}`,
724
+ );
725
+ }
726
+
371
727
  if (!storyIssueTypeId) {
372
728
  throw new Error(
373
- 'JIRA Story issue type ID is not configured. ' +
374
- 'Please set JIRA_ISSUE_TYPE_STORY environment variable or configure it in .michi/config.json. ' +
375
- 'You can find the issue type ID in JIRA UI (Settings > Issues > Issue types) or via REST API: ' +
376
- 'GET https://your-domain.atlassian.net/rest/api/3/issuetype'
729
+ 'JIRA Story issue type ID is not configured and could not be found in project. ' +
730
+ 'Please set JIRA_ISSUE_TYPE_STORY environment variable or configure it in .michi/config.json. ' +
731
+ 'You can find the issue type ID in JIRA UI (Settings > Issues > Issue types) or via REST API: ' +
732
+ 'GET https://your-domain.atlassian.net/rest/api/3/project/{projectKey}',
377
733
  );
378
734
  }
379
-
380
- const projectMeta = loadProjectMeta();
735
+
736
+ console.log(`✅ Using Story Issue Type ID: ${storyIssueTypeId}`);
737
+
381
738
  const tasksPath = resolve(`.kiro/specs/${featureName}/tasks.md`);
382
739
  const tasksContent = readFileSync(tasksPath, 'utf-8');
383
-
384
- const config = getJIRAConfig();
385
- const client = new JIRAClient(config);
386
-
740
+
387
741
  // spec.jsonを読み込んで既存のEpicキーを確認
388
742
  const specPath = resolve(`.kiro/specs/${featureName}/spec.json`);
389
- let spec: any = {};
743
+ let spec: SpecJson = {};
390
744
  try {
391
- spec = JSON.parse(readFileSync(specPath, 'utf-8'));
392
- } catch (error) {
745
+ spec = JSON.parse(readFileSync(specPath, 'utf-8')) as SpecJson;
746
+ } catch {
393
747
  console.error('spec.json not found or invalid');
394
748
  }
395
-
396
- let epic: any;
397
-
749
+
750
+ let epic: { key: string } | undefined;
751
+
398
752
  // 既存のEpicをチェック
399
753
  if (spec.jira?.epicKey) {
400
754
  console.log(`Existing Epic found: ${spec.jira.epicKey}`);
@@ -404,135 +758,287 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
404
758
  // Epic作成
405
759
  console.log('Creating Epic...');
406
760
  const epicSummary = `[${featureName}] ${projectMeta.projectName}`;
407
-
761
+
408
762
  // 同じタイトルのEpicがすでに存在するかJQLで検索
409
763
  const jql = `project = ${projectMeta.jiraProjectKey} AND issuetype = Epic AND summary ~ "${featureName}"`;
410
- let existingEpics: any[] = [];
764
+ let existingEpics: JIRAIssue[] = [];
411
765
  try {
412
766
  existingEpics = await client.searchIssues(jql);
413
767
  } catch (error) {
414
- console.error('❌ Failed to search existing Epics:', error instanceof Error ? error.message : error);
415
- console.error('⚠️ Cannot verify idempotency - Epic creation may result in duplicates');
416
- throw new Error(`JIRA Epic search failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
768
+ console.error(
769
+ ' Failed to search existing Epics:',
770
+ error instanceof Error ? error.message : error,
771
+ );
772
+ console.error(
773
+ '⚠️ Cannot verify idempotency - proceeding with Epic creation',
774
+ );
775
+ console.error(
776
+ ' If Epic already exists, manual cleanup may be required',
777
+ );
778
+ // 検索失敗時はフォールバック: 新規作成を試みる(重複リスクあり)
779
+ existingEpics = [];
417
780
  }
418
-
781
+
419
782
  if (existingEpics.length > 0) {
420
- console.log(`Found existing Epic with similar title: ${existingEpics[0].key}`);
783
+ console.log(
784
+ `Found existing Epic with similar title: ${existingEpics[0].key}`,
785
+ );
421
786
  console.log('Using existing Epic instead of creating new one');
422
787
  epic = existingEpics[0];
423
788
  } else {
789
+ // EpicタイプのIDを取得(日本語JIRAでは "エピック" という名前の場合がある)
790
+ const epicTypeId =
791
+ (await client.getIssueTypeId(projectMeta.jiraProjectKey, 'Epic')) ||
792
+ (await client.getIssueTypeId(projectMeta.jiraProjectKey, 'エピック'));
793
+
794
+ if (!epicTypeId) {
795
+ throw new Error(
796
+ 'Epic issue type not found in project. ' +
797
+ 'Please ensure the project has Epic issue type enabled.',
798
+ );
799
+ }
800
+
424
801
  const epicDescription = `機能: ${featureName}\nGitHub: ${projectMeta.repository}/tree/main/.kiro/specs/${featureName}`;
425
-
802
+
426
803
  const epicPayload = {
427
804
  fields: {
428
805
  project: { key: projectMeta.jiraProjectKey },
429
806
  summary: epicSummary,
430
- description: textToADF(epicDescription), // ADF形式に変換
431
- issuetype: { name: 'Epic' },
432
- labels: projectMeta.confluenceLabels
433
- }
807
+ description: textToADF(epicDescription), // ADF形式に変換
808
+ issuetype: { id: epicTypeId }, // IDを使用(nameではなく)
809
+ labels: projectMeta.confluenceLabels,
810
+ },
434
811
  };
435
-
812
+
436
813
  epic = await client.createIssue(epicPayload);
437
814
  console.log(`✅ Epic created: ${epic.key}`);
438
815
  }
439
816
  }
440
-
817
+
818
+ // Epicが確実に設定されていることを確認
819
+ if (!epic) {
820
+ throw new Error('Epic creation or retrieval failed');
821
+ }
822
+
441
823
  // 既存のStoryを検索(重複防止)
442
824
  // ラベルで検索(summary検索では "Story: タイトル" 形式に一致しないため)
443
825
  // issuetype検索にはIDを使用(名前は言語依存のため)
444
- const jql = `project = ${projectMeta.jiraProjectKey} AND issuetype = ${storyIssueTypeId} AND labels = "${featureName}"`;
826
+ const storyJql = `project = ${projectMeta.jiraProjectKey} AND issuetype = ${storyIssueTypeId} AND labels = "${featureName}"`;
445
827
  let existingStories: any[] = [];
446
828
  try {
447
- existingStories = await client.searchIssues(jql);
829
+ existingStories = await client.searchIssues(storyJql);
448
830
  } catch (error) {
449
- console.error('❌ Failed to search existing Stories:', error instanceof Error ? error.message : error);
450
- console.error('⚠️ Cannot verify idempotency - Story creation may result in duplicates');
451
- console.error('⚠️ Continuing with story creation (duplicates may be created)...');
831
+ console.error(
832
+ ' Failed to search existing Stories:',
833
+ error instanceof Error ? error.message : error,
834
+ );
835
+ console.error(
836
+ '⚠️ Cannot verify idempotency - Story creation may result in duplicates',
837
+ );
838
+ console.error(
839
+ '⚠️ Continuing with story creation (duplicates may be created)...',
840
+ );
452
841
  // 検索失敗時も処理を継続(既存ストーリーなしとして扱う)
453
842
  existingStories = [];
454
843
  }
455
-
456
- const existingStorySummaries = new Set(existingStories.map((s: any) => s.fields.summary));
457
- const existingStoryKeys = new Set(existingStories.map((s: any) => s.key));
458
-
459
- console.log(`Found ${existingStories.length} existing stories for this feature`);
460
-
844
+
845
+ const existingStorySummaries = new Set(
846
+ existingStories
847
+ .filter((s: JIRAIssue) => s?.fields?.summary)
848
+ .map((s: JIRAIssue) => s.fields.summary),
849
+ );
850
+ const existingStoryKeys = new Set(
851
+ existingStories
852
+ .filter((s: JIRAIssue) => s?.key)
853
+ .map((s: JIRAIssue) => s.key),
854
+ );
855
+
856
+ console.log(
857
+ `Found ${existingStories.length} existing stories for this feature`,
858
+ );
859
+
461
860
  // フェーズラベル検出用の正規表現
462
861
  // Phase X: フェーズ名(ラベル)の形式を検出
463
- const phasePattern = /## Phase [\d.]+:\s*(.+?)((.+?))/;
464
-
862
+ // Phase番号: 数字(0, 1, 2...)、ドット付き数字(0.1, 0.2...)、英字(A, B)に対応
863
+ const phasePattern = /## Phase [\d.A-Z]+:\s*(.+?)(?:((.+?)))?/;
864
+
465
865
  // Story作成(フェーズ検出付きパーサー)
466
866
  const lines = tasksContent.split('\n');
467
867
  let currentPhaseLabel = 'implementation'; // デフォルトは実装フェーズ
468
868
  const createdStories: string[] = [];
469
-
869
+
470
870
  for (let i = 0; i < lines.length; i++) {
471
871
  const line = lines[i];
472
-
872
+
473
873
  // フェーズ検出
474
874
  const phaseMatch = line.match(phasePattern);
475
875
  if (phaseMatch) {
476
- const phaseName = phaseMatch[2]; // 括弧内のラベル(例: Requirements)
477
-
478
- // フェーズ名からラベルを決定
479
- if (phaseName.includes('要件定義') || phaseName.toLowerCase().includes('requirements')) {
876
+ const phaseTitle = phaseMatch[1]; // フェーズタイトル全体
877
+ const phaseName = phaseMatch[2] || phaseTitle; // 括弧内のラベル(例: Requirements)または全体
878
+
879
+ // Phase番号を抽出(例: "0.1", "2", "A")
880
+ const phaseNumberMatch = line.match(/## Phase ([\d.A-Z]+):/);
881
+ const phaseNumber = phaseNumberMatch ? phaseNumberMatch[1] : '';
882
+
883
+ // フェーズ番号またはフェーズ名からラベルを決定
884
+ // 新ワークフロー構造に対応
885
+ if (
886
+ phaseNumber === '0.0' ||
887
+ phaseName.includes('初期化') ||
888
+ phaseName.toLowerCase().includes('init')
889
+ ) {
890
+ currentPhaseLabel = 'spec-init';
891
+ } else if (
892
+ phaseNumber === '0.1' ||
893
+ phaseName.includes('要件定義') ||
894
+ phaseName.toLowerCase().includes('requirements')
895
+ ) {
480
896
  currentPhaseLabel = 'requirements';
481
- } else if (phaseName.includes('設計') || phaseName.toLowerCase().includes('design')) {
897
+ } else if (
898
+ phaseNumber === '0.2' ||
899
+ phaseName.includes('設計') ||
900
+ phaseName.toLowerCase().includes('design')
901
+ ) {
482
902
  currentPhaseLabel = 'design';
483
- } else if (phaseName.includes('実装') || phaseName.toLowerCase().includes('implementation')) {
903
+ } else if (
904
+ phaseNumber === '0.3' ||
905
+ phaseName.includes('テストタイプ') ||
906
+ phaseName.toLowerCase().includes('test-type') ||
907
+ phaseName.toLowerCase().includes('test type')
908
+ ) {
909
+ currentPhaseLabel = 'test-type-selection';
910
+ } else if (
911
+ phaseNumber === '0.4' ||
912
+ phaseName.includes('テスト仕様') ||
913
+ phaseName.toLowerCase().includes('test-spec') ||
914
+ phaseName.toLowerCase().includes('test spec')
915
+ ) {
916
+ currentPhaseLabel = 'test-spec';
917
+ } else if (
918
+ phaseNumber === '0.5' ||
919
+ phaseName.includes('タスク分割') ||
920
+ phaseName.toLowerCase().includes('tasks') ||
921
+ phaseName.toLowerCase().includes('task breakdown')
922
+ ) {
923
+ currentPhaseLabel = 'spec-tasks';
924
+ } else if (
925
+ phaseNumber === '0.6' ||
926
+ phaseName.includes('JIRA') ||
927
+ phaseName.toLowerCase().includes('jira')
928
+ ) {
929
+ currentPhaseLabel = 'jira-sync';
930
+ } else if (
931
+ phaseNumber === '1' ||
932
+ phaseName.includes('環境構築') ||
933
+ phaseName.toLowerCase().includes('environment') ||
934
+ phaseName.toLowerCase().includes('setup')
935
+ ) {
936
+ currentPhaseLabel = 'environment-setup';
937
+ } else if (
938
+ phaseNumber === '2' ||
939
+ phaseName.includes('実装') ||
940
+ phaseName.includes('TDD') ||
941
+ phaseName.toLowerCase().includes('implementation')
942
+ ) {
484
943
  currentPhaseLabel = 'implementation';
485
- } else if (phaseName.includes('試験') || phaseName.toLowerCase().includes('testing')) {
486
- currentPhaseLabel = 'testing';
487
- } else if (phaseName.includes('リリース準備') || phaseName.toLowerCase().includes('release-prep') || phaseName.toLowerCase().includes('release preparation')) {
944
+ } else if (
945
+ phaseNumber === 'A' ||
946
+ phaseNumber.toLowerCase() === 'a' ||
947
+ phaseName.includes('PR前') ||
948
+ phaseName.toLowerCase().includes('pr-test') ||
949
+ phaseName.toLowerCase().includes('pr test')
950
+ ) {
951
+ currentPhaseLabel = 'phase-a';
952
+ } else if (
953
+ phaseNumber === '3' ||
954
+ phaseName.includes('追加QA') ||
955
+ phaseName.includes('QA') ||
956
+ phaseName.includes('試験') ||
957
+ phaseName.toLowerCase().includes('testing') ||
958
+ phaseName.toLowerCase().includes('additional qa')
959
+ ) {
960
+ currentPhaseLabel = 'additional-qa';
961
+ } else if (
962
+ phaseNumber === 'B' ||
963
+ phaseNumber.toLowerCase() === 'b' ||
964
+ phaseName.includes('リリース準備テスト') ||
965
+ phaseName.toLowerCase().includes('release-test') ||
966
+ phaseName.toLowerCase().includes('release test')
967
+ ) {
968
+ currentPhaseLabel = 'phase-b';
969
+ } else if (
970
+ phaseNumber === '4' ||
971
+ phaseName.includes('リリース準備') ||
972
+ phaseName.toLowerCase().includes('release-prep') ||
973
+ phaseName.toLowerCase().includes('release preparation')
974
+ ) {
488
975
  currentPhaseLabel = 'release-prep';
489
- } else if (phaseName.includes('リリース') || phaseName.toLowerCase().includes('release')) {
976
+ } else if (
977
+ phaseNumber === '5' ||
978
+ (phaseName.includes('リリース') && !phaseName.includes('準備')) ||
979
+ (phaseName.toLowerCase().includes('release') &&
980
+ !phaseName.toLowerCase().includes('prep'))
981
+ ) {
490
982
  currentPhaseLabel = 'release';
491
983
  }
492
-
493
- console.log(`📌 Phase detected: ${phaseName} (label: ${currentPhaseLabel})`);
984
+
985
+ console.log(
986
+ `📌 Phase detected: ${phaseTitle} (number: ${phaseNumber}, label: ${currentPhaseLabel})`,
987
+ );
494
988
  continue;
495
989
  }
496
-
990
+
497
991
  // Story検出
498
992
  const storyMatch = line.match(/### Story [\d.]+: (.+)/);
499
993
  if (!storyMatch) continue;
500
-
994
+
501
995
  const storyTitle = storyMatch[1];
502
996
  const storySummary = `Story: ${storyTitle}`;
503
-
997
+
504
998
  // 既に同じタイトルのStoryが存在するかチェック
505
999
  if (existingStorySummaries.has(storySummary)) {
506
1000
  console.log(`Skipping Story (already exists): ${storyTitle}`);
507
- const existing = existingStories.find((s: any) => s.fields.summary === storySummary);
1001
+ const existing = existingStories.find(
1002
+ (s: any) => s?.fields?.summary === storySummary,
1003
+ );
508
1004
  if (existing) {
509
1005
  createdStories.push(existing.key);
1006
+ existingStoryKeys.add(existing.key);
1007
+ } else {
1008
+ console.warn(
1009
+ `⚠️ Warning: Story "${storyTitle}" is in summary set but not found in existingStories array`,
1010
+ );
510
1011
  }
511
1012
  continue;
512
1013
  }
513
-
1014
+
514
1015
  console.log(`Creating Story: ${storyTitle} [${currentPhaseLabel}]`);
515
-
1016
+
516
1017
  try {
517
1018
  // Storyの詳細情報を抽出(新しい実装を使用)
518
1019
  const storyDetails = extractStoryDetails(tasksContent, storyTitle);
519
-
1020
+
520
1021
  // GitHubリンク
521
1022
  const githubUrl = `${projectMeta.repository}/tree/main/.kiro/specs/${featureName}/tasks.md`;
522
-
1023
+
523
1024
  // リッチなADF形式で説明文を生成
524
- const richDescription = createRichADF(storyDetails, currentPhaseLabel, githubUrl);
525
-
1025
+ const richDescription = createRichADF(
1026
+ storyDetails,
1027
+ currentPhaseLabel,
1028
+ githubUrl,
1029
+ );
1030
+
526
1031
  // 優先度のマッピング(デフォルト: Medium)
527
1032
  const priorityMap: { [key: string]: string } = {
528
- 'High': 'High',
529
- 'Medium': 'Medium',
530
- 'Low': 'Low'
1033
+ High: 'High',
1034
+ Medium: 'Medium',
1035
+ Low: 'Low',
531
1036
  };
532
- const priority = storyDetails.priority && priorityMap[storyDetails.priority]
533
- ? priorityMap[storyDetails.priority]
534
- : 'Medium';
535
-
1037
+ const priority =
1038
+ storyDetails.priority && priorityMap[storyDetails.priority]
1039
+ ? priorityMap[storyDetails.priority]
1040
+ : 'Medium';
1041
+
536
1042
  // 見積もり(Story Points)を取得
537
1043
  let storyPoints: number | undefined;
538
1044
  if (storyDetails.estimate) {
@@ -541,41 +1047,46 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
541
1047
  storyPoints = parseInt(spMatch[1], 10);
542
1048
  }
543
1049
  }
544
-
1050
+
545
1051
  // JIRAペイロードを作成(issue type IDは既に取得済み)
546
1052
  const storyPayload: any = {
547
1053
  fields: {
548
1054
  project: { key: projectMeta.jiraProjectKey },
549
1055
  summary: storySummary,
550
- description: richDescription, // リッチなADF形式
1056
+ description: richDescription, // リッチなADF形式
551
1057
  issuetype: { id: storyIssueTypeId },
552
- labels: [...projectMeta.confluenceLabels, featureName, currentPhaseLabel],
553
- priority: { name: priority }
554
- }
1058
+ labels: [
1059
+ ...projectMeta.confluenceLabels,
1060
+ featureName,
1061
+ currentPhaseLabel,
1062
+ ],
1063
+ priority: { name: priority },
1064
+ },
555
1065
  };
556
-
1066
+
557
1067
  // 期限(Due Date)を設定
558
1068
  if (storyDetails.dueDate) {
559
- storyPayload.fields.duedate = storyDetails.dueDate; // YYYY-MM-DD形式
1069
+ storyPayload.fields.duedate = storyDetails.dueDate; // YYYY-MM-DD形式
560
1070
  }
561
-
1071
+
562
1072
  // Story Pointsを設定(カスタムフィールド)
563
1073
  // 注意: JIRAプロジェクトによってカスタムフィールドIDが異なる場合があります
564
1074
  // 環境変数 JIRA_STORY_POINTS_FIELD で設定可能(例: customfield_10016)
565
1075
  if (storyPoints !== undefined) {
566
- const storyPointsField = process.env.JIRA_STORY_POINTS_FIELD || 'customfield_10016';
1076
+ const storyPointsField =
1077
+ process.env.JIRA_STORY_POINTS_FIELD || 'customfield_10016';
567
1078
  storyPayload.fields[storyPointsField] = storyPoints;
568
1079
  }
569
-
1080
+
570
1081
  // 担当者を設定(アカウントIDが必要な場合があるため、オプション)
571
1082
  // 注意: JIRAのアカウントIDが必要な場合があります
572
1083
  // if (storyInfo?.assignee) {
573
1084
  // storyPayload.fields.assignee = { name: storyInfo.assignee };
574
1085
  // }
575
-
1086
+
576
1087
  const story = await client.createIssue(storyPayload);
577
1088
  console.log(` ✅ Story created: ${story.key} [${currentPhaseLabel}]`);
578
-
1089
+
579
1090
  // 期限とStory Pointsの情報を表示
580
1091
  if (storyDetails.dueDate) {
581
1092
  console.log(` 期限: ${storyDetails.dueDate}`);
@@ -586,42 +1097,65 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
586
1097
  if (storyPoints !== undefined) {
587
1098
  console.log(` Story Points: ${storyPoints} SP`);
588
1099
  }
589
-
1100
+
590
1101
  createdStories.push(story.key);
591
-
1102
+
592
1103
  // 進捗表示(大量作成時の見通し向上)
593
1104
  if (createdStories.length % 5 === 0) {
594
- console.log(` 📊 Progress: ${createdStories.length} stories created so far...`);
1105
+ console.log(
1106
+ ` 📊 Progress: ${createdStories.length} stories created so far...`,
1107
+ );
595
1108
  }
596
-
1109
+
597
1110
  // Epic Linkは手動設定が必要(JIRA Cloudの制約)
598
1111
  console.log(` ℹ️ Epic: ${epic.key} に手動でリンクしてください`);
599
1112
  } catch (error: any) {
600
- console.error(` ❌ Failed to create Story "${storyTitle}":`, error instanceof Error ? error.message : error);
601
-
1113
+ console.error(
1114
+ ` ❌ Failed to create Story "${storyTitle}":`,
1115
+ error instanceof Error ? error.message : error,
1116
+ );
1117
+
602
1118
  // JIRA APIエラーの詳細を表示
603
1119
  if (error.response?.data) {
604
- console.error(' 📋 JIRA API Error Details:', JSON.stringify(error.response.data, null, 2));
605
-
1120
+ console.error(
1121
+ ' 📋 JIRA API Error Details:',
1122
+ JSON.stringify(error.response.data, null, 2),
1123
+ );
1124
+
606
1125
  // Story Pointsフィールドのエラーの場合、警告を表示
607
- if (error.response.data.errors && Object.keys(error.response.data.errors).some(key => key.includes('customfield'))) {
1126
+ if (
1127
+ error.response.data.errors &&
1128
+ Object.keys(error.response.data.errors).some((key) =>
1129
+ key.includes('customfield'),
1130
+ )
1131
+ ) {
608
1132
  console.error(' ⚠️ Story Pointsフィールドの設定に失敗しました。');
609
- console.error(' 💡 環境変数 JIRA_STORY_POINTS_FIELD を正しいカスタムフィールドIDに設定してください。');
610
- console.error(' 💡 JIRA管理画面でStory PointsのカスタムフィールドIDを確認してください。');
1133
+ console.error(
1134
+ ' 💡 環境変数 JIRA_STORY_POINTS_FIELD を正しいカスタムフィールドIDに設定してください。',
1135
+ );
1136
+ console.error(
1137
+ ' 💡 JIRA管理画面でStory PointsのカスタムフィールドIDを確認してください。',
1138
+ );
611
1139
  }
612
1140
  }
613
-
1141
+
614
1142
  // エラーがあっても他のStoryの作成は継続
615
1143
  }
616
1144
  }
617
-
1145
+
618
1146
  // 新規作成数と再利用数を正確に計算
619
- const newStoryCount = createdStories.filter(key => !existingStoryKeys.has(key)).length;
620
- const reusedStoryCount = createdStories.filter(key => existingStoryKeys.has(key)).length;
1147
+ const newStoryCount = createdStories.filter(
1148
+ (key) => !existingStoryKeys.has(key),
1149
+ ).length;
1150
+ const reusedStoryCount = createdStories.filter((key) =>
1151
+ existingStoryKeys.has(key),
1152
+ ).length;
621
1153
 
622
1154
  console.log('\n✅ JIRA sync completed');
623
1155
  console.log(` Epic: ${epic.key}`);
624
- console.log(` Stories: ${createdStories.length} processed (${newStoryCount} new, ${reusedStoryCount} reused)`);
1156
+ console.log(
1157
+ ` Stories: ${createdStories.length} processed (${newStoryCount} new, ${reusedStoryCount} reused)`,
1158
+ );
625
1159
 
626
1160
  // spec.json を更新
627
1161
  const jiraBaseUrl = process.env.ATLASSIAN_URL || '';
@@ -630,10 +1164,12 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
630
1164
  projectKey: projectMeta.jiraProjectKey,
631
1165
  epicKey: epic.key,
632
1166
  epicUrl: `${jiraBaseUrl}/browse/${epic.key}`,
633
- storyKeys: createdStories
1167
+ storyKeys: createdStories,
634
1168
  });
635
1169
  } catch (error) {
636
- console.warn(`⚠️ Failed to update spec.json after JIRA sync: ${error instanceof Error ? error.message : 'Unknown error'}`);
1170
+ console.warn(
1171
+ `⚠️ Failed to update spec.json after JIRA sync: ${error instanceof Error ? error.message : 'Unknown error'}`,
1172
+ );
637
1173
  // spec.json更新の失敗はスクリプト全体の失敗とはしない(JIRA同期は成功しているため)
638
1174
  }
639
1175
  }
@@ -641,12 +1177,12 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
641
1177
  // CLI実行
642
1178
  if (import.meta.url === `file://${process.argv[1]}`) {
643
1179
  const args = process.argv.slice(2);
644
-
1180
+
645
1181
  if (args.length === 0) {
646
1182
  console.error('Usage: npm run jira:sync <feature-name>');
647
1183
  process.exit(1);
648
1184
  }
649
-
1185
+
650
1186
  syncTasksToJIRA(args[0])
651
1187
  .then(() => process.exit(0))
652
1188
  .catch((error) => {
@@ -656,4 +1192,3 @@ if (import.meta.url === `file://${process.argv[1]}`) {
656
1192
  }
657
1193
 
658
1194
  export { syncTasksToJIRA, JIRAClient };
659
-