@sk8metal/michi-cli 0.5.0 → 0.8.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 (284) hide show
  1. package/CHANGELOG.md +136 -1
  2. package/README.md +2 -1
  3. package/dist/scripts/config/config-schema.d.ts +35 -0
  4. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  5. package/dist/scripts/config/config-schema.js +56 -0
  6. package/dist/scripts/config/config-schema.js.map +1 -1
  7. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  8. package/dist/scripts/confluence-sync.js +15 -2
  9. package/dist/scripts/confluence-sync.js.map +1 -1
  10. package/dist/scripts/github-actions-client.d.ts +79 -0
  11. package/dist/scripts/github-actions-client.d.ts.map +1 -0
  12. package/dist/scripts/github-actions-client.js +182 -0
  13. package/dist/scripts/github-actions-client.js.map +1 -0
  14. package/dist/scripts/health-check-service.d.ts +46 -0
  15. package/dist/scripts/health-check-service.d.ts.map +1 -0
  16. package/dist/scripts/health-check-service.js +114 -0
  17. package/dist/scripts/health-check-service.js.map +1 -0
  18. package/dist/scripts/markdown-to-confluence.d.ts.map +1 -1
  19. package/dist/scripts/markdown-to-confluence.js +25 -3
  20. package/dist/scripts/markdown-to-confluence.js.map +1 -1
  21. package/dist/scripts/mermaid-converter.d.ts +24 -0
  22. package/dist/scripts/mermaid-converter.d.ts.map +1 -0
  23. package/dist/scripts/mermaid-converter.js +49 -0
  24. package/dist/scripts/mermaid-converter.js.map +1 -0
  25. package/dist/scripts/template/multi-repo-renderer.d.ts +67 -0
  26. package/dist/scripts/template/multi-repo-renderer.d.ts.map +1 -0
  27. package/dist/scripts/template/multi-repo-renderer.js +123 -0
  28. package/dist/scripts/template/multi-repo-renderer.js.map +1 -0
  29. package/dist/scripts/template/renderer.d.ts +4 -0
  30. package/dist/scripts/template/renderer.d.ts.map +1 -1
  31. package/dist/scripts/template/renderer.js.map +1 -1
  32. package/dist/scripts/test-execution-generator.d.ts.map +1 -1
  33. package/dist/scripts/test-execution-generator.js +94 -11
  34. package/dist/scripts/test-execution-generator.js.map +1 -1
  35. package/dist/scripts/test-script-runner.d.ts +33 -0
  36. package/dist/scripts/test-script-runner.d.ts.map +1 -0
  37. package/dist/scripts/test-script-runner.js +77 -0
  38. package/dist/scripts/test-script-runner.js.map +1 -0
  39. package/dist/scripts/utils/config-loader.d.ts +21 -1
  40. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  41. package/dist/scripts/utils/config-loader.js +149 -3
  42. package/dist/scripts/utils/config-loader.js.map +1 -1
  43. package/dist/scripts/utils/multi-repo-validator.d.ts +30 -0
  44. package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -0
  45. package/dist/scripts/utils/multi-repo-validator.js +105 -0
  46. package/dist/scripts/utils/multi-repo-validator.js.map +1 -0
  47. package/dist/scripts/utils/spec-archiver.d.ts +38 -0
  48. package/dist/scripts/utils/spec-archiver.d.ts.map +1 -0
  49. package/dist/scripts/utils/spec-archiver.js +210 -0
  50. package/dist/scripts/utils/spec-archiver.js.map +1 -0
  51. package/dist/scripts/utils/spec-updater.d.ts +4 -0
  52. package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
  53. package/dist/scripts/utils/spec-updater.js.map +1 -1
  54. package/dist/src/cli.d.ts.map +1 -1
  55. package/dist/src/cli.js +262 -14
  56. package/dist/src/cli.js.map +1 -1
  57. package/dist/src/commands/multi-repo-add-repo.d.ts +26 -0
  58. package/dist/src/commands/multi-repo-add-repo.d.ts.map +1 -0
  59. package/dist/src/commands/multi-repo-add-repo.js +56 -0
  60. package/dist/src/commands/multi-repo-add-repo.js.map +1 -0
  61. package/dist/src/commands/multi-repo-ci-status.d.ts +46 -0
  62. package/dist/src/commands/multi-repo-ci-status.d.ts.map +1 -0
  63. package/dist/src/commands/multi-repo-ci-status.js +285 -0
  64. package/dist/src/commands/multi-repo-ci-status.js.map +1 -0
  65. package/dist/src/commands/multi-repo-confluence-sync.d.ts +45 -0
  66. package/dist/src/commands/multi-repo-confluence-sync.d.ts.map +1 -0
  67. package/dist/src/commands/multi-repo-confluence-sync.js +135 -0
  68. package/dist/src/commands/multi-repo-confluence-sync.js.map +1 -0
  69. package/dist/src/commands/multi-repo-init.d.ts +26 -0
  70. package/dist/src/commands/multi-repo-init.d.ts.map +1 -0
  71. package/dist/src/commands/multi-repo-init.js +101 -0
  72. package/dist/src/commands/multi-repo-init.js.map +1 -0
  73. package/dist/src/commands/multi-repo-list.d.ts +28 -0
  74. package/dist/src/commands/multi-repo-list.d.ts.map +1 -0
  75. package/dist/src/commands/multi-repo-list.js +38 -0
  76. package/dist/src/commands/multi-repo-list.js.map +1 -0
  77. package/dist/src/commands/multi-repo-test.d.ts +56 -0
  78. package/dist/src/commands/multi-repo-test.d.ts.map +1 -0
  79. package/dist/src/commands/multi-repo-test.js +70 -0
  80. package/dist/src/commands/multi-repo-test.js.map +1 -0
  81. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  82. package/dist/src/commands/setup-existing.js +30 -8
  83. package/dist/src/commands/setup-existing.js.map +1 -1
  84. package/dist/src/commands/spec-archive.d.ts +17 -0
  85. package/dist/src/commands/spec-archive.d.ts.map +1 -0
  86. package/dist/src/commands/spec-archive.js +40 -0
  87. package/dist/src/commands/spec-archive.js.map +1 -0
  88. package/dist/src/commands/spec-list.d.ts +15 -0
  89. package/dist/src/commands/spec-list.d.ts.map +1 -0
  90. package/dist/src/commands/spec-list.js +55 -0
  91. package/dist/src/commands/spec-list.js.map +1 -0
  92. package/docs/user-guide/guides/multi-repo-guide.md +591 -0
  93. package/docs/user-guide/guides/multi-repo-migration-guide.md +516 -0
  94. package/docs/user-guide/reference/multi-repo-api.md +771 -0
  95. package/docs/user-guide/reference/quick-reference.md +22 -37
  96. package/package.json +1 -4
  97. package/scripts/__tests__/config-loader-multi-repo.test.ts +342 -0
  98. package/scripts/__tests__/github-actions-client.test.ts +543 -0
  99. package/scripts/__tests__/health-check-service.test.ts +142 -0
  100. package/scripts/__tests__/markdown-to-confluence.test.ts +262 -0
  101. package/scripts/__tests__/mermaid-converter.test.ts +236 -0
  102. package/scripts/__tests__/multi-repo-config-schema.test.ts +335 -0
  103. package/scripts/__tests__/multi-repo-validator.test.ts +524 -0
  104. package/scripts/__tests__/spec-archiver.test.ts +512 -0
  105. package/scripts/__tests__/test-script-runner.test.ts +217 -0
  106. package/scripts/config/config-schema.ts +64 -0
  107. package/scripts/confluence-sync.ts +16 -2
  108. package/scripts/github-actions-client.ts +258 -0
  109. package/scripts/health-check-service.ts +171 -0
  110. package/scripts/markdown-to-confluence.ts +37 -6
  111. package/scripts/mermaid-converter.ts +56 -0
  112. package/scripts/template/__tests__/multi-repo-renderer.test.ts +261 -0
  113. package/scripts/template/multi-repo-renderer.ts +172 -0
  114. package/scripts/template/renderer.ts +5 -0
  115. package/scripts/test-execution-generator.ts +104 -11
  116. package/scripts/test-script-runner.ts +130 -0
  117. package/scripts/utils/__tests__/config-validator.test.ts +104 -6
  118. package/scripts/utils/__tests__/multi-repo-validator.test.ts +335 -0
  119. package/scripts/utils/config-loader.ts +197 -3
  120. package/scripts/utils/multi-repo-validator.ts +141 -0
  121. package/scripts/utils/spec-archiver.ts +260 -0
  122. package/scripts/utils/spec-updater.ts +4 -0
  123. package/templates/claude/agents/pr-size-monitor/AGENT.md +330 -0
  124. package/templates/claude/commands/michi/spec-impl.md +208 -35
  125. package/templates/claude-agent/agents/doc-reviewer.md +176 -0
  126. package/templates/claude-agent/rules/doc-review-rules.md +98 -0
  127. package/templates/claude-agent/rules/doc-review.md +91 -0
  128. package/templates/multi-repo/docs/ci-status.md +51 -0
  129. package/templates/multi-repo/docs/release-notes.md +99 -0
  130. package/templates/multi-repo/overview/architecture.md +102 -0
  131. package/templates/multi-repo/overview/requirements.md +68 -0
  132. package/templates/multi-repo/overview/sequence.md +79 -0
  133. package/templates/multi-repo/steering/multi-repo.md +74 -0
  134. package/templates/multi-repo/tests/strategy.md +89 -0
  135. package/dist/scripts/__tests__/create-project.test.d.ts +0 -2
  136. package/dist/scripts/__tests__/create-project.test.d.ts.map +0 -1
  137. package/dist/scripts/__tests__/create-project.test.js +0 -243
  138. package/dist/scripts/__tests__/create-project.test.js.map +0 -1
  139. package/dist/scripts/__tests__/jira-transitions.test.d.ts +0 -5
  140. package/dist/scripts/__tests__/jira-transitions.test.d.ts.map +0 -1
  141. package/dist/scripts/__tests__/jira-transitions.test.js +0 -172
  142. package/dist/scripts/__tests__/jira-transitions.test.js.map +0 -1
  143. package/dist/scripts/__tests__/multi-project-estimate.test.d.ts +0 -2
  144. package/dist/scripts/__tests__/multi-project-estimate.test.d.ts.map +0 -1
  145. package/dist/scripts/__tests__/multi-project-estimate.test.js +0 -118
  146. package/dist/scripts/__tests__/multi-project-estimate.test.js.map +0 -1
  147. package/dist/scripts/__tests__/setup-existing-project.test.d.ts +0 -2
  148. package/dist/scripts/__tests__/setup-existing-project.test.d.ts.map +0 -1
  149. package/dist/scripts/__tests__/setup-existing-project.test.js +0 -208
  150. package/dist/scripts/__tests__/setup-existing-project.test.js.map +0 -1
  151. package/dist/scripts/__tests__/setup-interactive.test.d.ts +0 -2
  152. package/dist/scripts/__tests__/setup-interactive.test.d.ts.map +0 -1
  153. package/dist/scripts/__tests__/setup-interactive.test.js +0 -166
  154. package/dist/scripts/__tests__/setup-interactive.test.js.map +0 -1
  155. package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts +0 -5
  156. package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts.map +0 -1
  157. package/dist/scripts/__tests__/spec-impl-workflow.test.js +0 -323
  158. package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +0 -1
  159. package/dist/scripts/__tests__/spec-loader.test.d.ts +0 -5
  160. package/dist/scripts/__tests__/spec-loader.test.d.ts.map +0 -1
  161. package/dist/scripts/__tests__/spec-loader.test.js +0 -153
  162. package/dist/scripts/__tests__/spec-loader.test.js.map +0 -1
  163. package/dist/scripts/__tests__/validate-phase.test.d.ts +0 -5
  164. package/dist/scripts/__tests__/validate-phase.test.d.ts.map +0 -1
  165. package/dist/scripts/__tests__/validate-phase.test.js +0 -249
  166. package/dist/scripts/__tests__/validate-phase.test.js.map +0 -1
  167. package/dist/scripts/constants/__tests__/environments.test.d.ts +0 -2
  168. package/dist/scripts/constants/__tests__/environments.test.d.ts.map +0 -1
  169. package/dist/scripts/constants/__tests__/environments.test.js +0 -125
  170. package/dist/scripts/constants/__tests__/environments.test.js.map +0 -1
  171. package/dist/scripts/constants/__tests__/languages.test.d.ts +0 -2
  172. package/dist/scripts/constants/__tests__/languages.test.d.ts.map +0 -1
  173. package/dist/scripts/constants/__tests__/languages.test.js +0 -82
  174. package/dist/scripts/constants/__tests__/languages.test.js.map +0 -1
  175. package/dist/scripts/create-project.d.ts +0 -16
  176. package/dist/scripts/create-project.d.ts.map +0 -1
  177. package/dist/scripts/create-project.js +0 -334
  178. package/dist/scripts/create-project.js.map +0 -1
  179. package/dist/scripts/list-projects.d.ts +0 -7
  180. package/dist/scripts/list-projects.d.ts.map +0 -1
  181. package/dist/scripts/list-projects.js +0 -88
  182. package/dist/scripts/list-projects.js.map +0 -1
  183. package/dist/scripts/template/__tests__/renderer.test.d.ts +0 -2
  184. package/dist/scripts/template/__tests__/renderer.test.d.ts.map +0 -1
  185. package/dist/scripts/template/__tests__/renderer.test.js +0 -165
  186. package/dist/scripts/template/__tests__/renderer.test.js.map +0 -1
  187. package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts +0 -5
  188. package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts.map +0 -1
  189. package/dist/scripts/utils/__tests__/aidlc-parser.test.js +0 -315
  190. package/dist/scripts/utils/__tests__/aidlc-parser.test.js.map +0 -1
  191. package/dist/scripts/utils/__tests__/business-days.test.d.ts +0 -5
  192. package/dist/scripts/utils/__tests__/business-days.test.d.ts.map +0 -1
  193. package/dist/scripts/utils/__tests__/business-days.test.js +0 -171
  194. package/dist/scripts/utils/__tests__/business-days.test.js.map +0 -1
  195. package/dist/scripts/utils/__tests__/config-loader.test.d.ts +0 -5
  196. package/dist/scripts/utils/__tests__/config-loader.test.d.ts.map +0 -1
  197. package/dist/scripts/utils/__tests__/config-loader.test.js +0 -314
  198. package/dist/scripts/utils/__tests__/config-loader.test.js.map +0 -1
  199. package/dist/scripts/utils/__tests__/config-validator.test.d.ts +0 -5
  200. package/dist/scripts/utils/__tests__/config-validator.test.d.ts.map +0 -1
  201. package/dist/scripts/utils/__tests__/config-validator.test.js +0 -396
  202. package/dist/scripts/utils/__tests__/config-validator.test.js.map +0 -1
  203. package/dist/scripts/utils/__tests__/env-config.test.d.ts +0 -5
  204. package/dist/scripts/utils/__tests__/env-config.test.d.ts.map +0 -1
  205. package/dist/scripts/utils/__tests__/env-config.test.js +0 -216
  206. package/dist/scripts/utils/__tests__/env-config.test.js.map +0 -1
  207. package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts +0 -5
  208. package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts.map +0 -1
  209. package/dist/scripts/utils/__tests__/feature-name-validator.test.js +0 -106
  210. package/dist/scripts/utils/__tests__/feature-name-validator.test.js.map +0 -1
  211. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts +0 -5
  212. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts.map +0 -1
  213. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js +0 -202
  214. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js.map +0 -1
  215. package/dist/scripts/utils/__tests__/project-meta.test.d.ts +0 -6
  216. package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +0 -1
  217. package/dist/scripts/utils/__tests__/project-meta.test.js +0 -154
  218. package/dist/scripts/utils/__tests__/project-meta.test.js.map +0 -1
  219. package/dist/scripts/utils/__tests__/security-validator.test.d.ts +0 -6
  220. package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +0 -1
  221. package/dist/scripts/utils/__tests__/security-validator.test.js +0 -219
  222. package/dist/scripts/utils/__tests__/security-validator.test.js.map +0 -1
  223. package/dist/scripts/utils/__tests__/spec-updater.test.d.ts +0 -5
  224. package/dist/scripts/utils/__tests__/spec-updater.test.d.ts.map +0 -1
  225. package/dist/scripts/utils/__tests__/spec-updater.test.js +0 -158
  226. package/dist/scripts/utils/__tests__/spec-updater.test.js.map +0 -1
  227. package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts +0 -5
  228. package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts.map +0 -1
  229. package/dist/scripts/utils/__tests__/tasks-converter.test.js +0 -500
  230. package/dist/scripts/utils/__tests__/tasks-converter.test.js.map +0 -1
  231. package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts +0 -5
  232. package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts.map +0 -1
  233. package/dist/scripts/utils/__tests__/tasks-format-validator.test.js +0 -314
  234. package/dist/scripts/utils/__tests__/tasks-format-validator.test.js.map +0 -1
  235. package/dist/scripts/utils/__tests__/test-runner.test.d.ts +0 -5
  236. package/dist/scripts/utils/__tests__/test-runner.test.d.ts.map +0 -1
  237. package/dist/scripts/utils/__tests__/test-runner.test.js +0 -64
  238. package/dist/scripts/utils/__tests__/test-runner.test.js.map +0 -1
  239. package/dist/src/__tests__/cli.test.d.ts +0 -5
  240. package/dist/src/__tests__/cli.test.d.ts.map +0 -1
  241. package/dist/src/__tests__/cli.test.js +0 -58
  242. package/dist/src/__tests__/cli.test.js.map +0 -1
  243. package/dist/src/__tests__/integration/internationalization.test.d.ts +0 -8
  244. package/dist/src/__tests__/integration/internationalization.test.d.ts.map +0 -1
  245. package/dist/src/__tests__/integration/internationalization.test.js +0 -333
  246. package/dist/src/__tests__/integration/internationalization.test.js.map +0 -1
  247. package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts +0 -5
  248. package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts.map +0 -1
  249. package/dist/src/__tests__/integration/setup/claude-agent.test.js +0 -122
  250. package/dist/src/__tests__/integration/setup/claude-agent.test.js.map +0 -1
  251. package/dist/src/__tests__/integration/setup/claude.test.d.ts +0 -5
  252. package/dist/src/__tests__/integration/setup/claude.test.d.ts.map +0 -1
  253. package/dist/src/__tests__/integration/setup/claude.test.js +0 -193
  254. package/dist/src/__tests__/integration/setup/claude.test.js.map +0 -1
  255. package/dist/src/__tests__/integration/setup/cursor.test.d.ts +0 -5
  256. package/dist/src/__tests__/integration/setup/cursor.test.d.ts.map +0 -1
  257. package/dist/src/__tests__/integration/setup/cursor.test.js +0 -166
  258. package/dist/src/__tests__/integration/setup/cursor.test.js.map +0 -1
  259. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts +0 -32
  260. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts.map +0 -1
  261. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js +0 -72
  262. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js.map +0 -1
  263. package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts +0 -38
  264. package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts.map +0 -1
  265. package/dist/src/__tests__/integration/setup/helpers/test-project.js +0 -83
  266. package/dist/src/__tests__/integration/setup/helpers/test-project.js.map +0 -1
  267. package/dist/src/__tests__/integration/setup/init.test.d.ts +0 -5
  268. package/dist/src/__tests__/integration/setup/init.test.d.ts.map +0 -1
  269. package/dist/src/__tests__/integration/setup/init.test.js +0 -352
  270. package/dist/src/__tests__/integration/setup/init.test.js.map +0 -1
  271. package/dist/src/__tests__/integration/setup/validation.test.d.ts +0 -5
  272. package/dist/src/__tests__/integration/setup/validation.test.d.ts.map +0 -1
  273. package/dist/src/__tests__/integration/setup/validation.test.js +0 -301
  274. package/dist/src/__tests__/integration/setup/validation.test.js.map +0 -1
  275. package/dist/src/commands/__tests__/init.test.d.ts +0 -5
  276. package/dist/src/commands/__tests__/init.test.d.ts.map +0 -1
  277. package/dist/src/commands/__tests__/init.test.js +0 -255
  278. package/dist/src/commands/__tests__/init.test.js.map +0 -1
  279. package/dist/src/commands/__tests__/migrate.test.d.ts +0 -5
  280. package/dist/src/commands/__tests__/migrate.test.d.ts.map +0 -1
  281. package/dist/src/commands/__tests__/migrate.test.js +0 -216
  282. package/dist/src/commands/__tests__/migrate.test.js.map +0 -1
  283. package/scripts/create-project.ts +0 -386
  284. package/scripts/list-projects.ts +0 -112
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Multi-Repo template renderer
3
+ *
4
+ * Provides template rendering functionality for Multi-Repo projects
5
+ */
6
+
7
+ import { readFileSync } from 'fs';
8
+ import { resolve, relative, isAbsolute } from 'path';
9
+ import { renderTemplate, type TemplateContext } from './renderer.js';
10
+
11
+ export interface MultiRepoTemplateContext {
12
+ PROJECT_NAME: string;
13
+ JIRA_KEY: string;
14
+ CONFLUENCE_SPACE: string;
15
+ CREATED_AT: string;
16
+ }
17
+
18
+ /**
19
+ * Create Multi-Repo template context
20
+ *
21
+ * @param projectName - Project name
22
+ * @param jiraKey - JIRA project key
23
+ * @param confluenceSpace - Confluence space key
24
+ * @param createdAt - Created timestamp (ISO 8601 format)
25
+ * @returns Multi-Repo template context
26
+ */
27
+ export const createMultiRepoTemplateContext = (
28
+ projectName: string,
29
+ jiraKey: string,
30
+ confluenceSpace: string,
31
+ createdAt?: string
32
+ ): MultiRepoTemplateContext => ({
33
+ PROJECT_NAME: projectName,
34
+ JIRA_KEY: jiraKey,
35
+ CONFLUENCE_SPACE: confluenceSpace,
36
+ CREATED_AT: createdAt || new Date().toISOString(),
37
+ });
38
+
39
+ /**
40
+ * Load Multi-Repo template file
41
+ *
42
+ * Security: Path traversal prevention with three-layer validation:
43
+ * 1. Validate template name (no path separators)
44
+ * 2. Resolve absolute paths
45
+ * 3. Verify path containment
46
+ *
47
+ * @param templateName - Template name (e.g., "overview/requirements", "steering/multi-repo")
48
+ * @param projectRoot - Project root directory
49
+ * @returns Template content
50
+ * @throws {Error} If template file not found or path traversal detected
51
+ */
52
+ export const loadMultiRepoTemplate = (
53
+ templateName: string,
54
+ projectRoot: string = process.cwd()
55
+ ): string => {
56
+ // Security Layer 1: Validate template name
57
+ // Reject path traversal characters (../, ..\, absolute paths)
58
+ if (templateName.includes('..') || templateName.includes('\\')) {
59
+ throw new Error(
60
+ `Invalid template name: ${templateName}\n` +
61
+ 'Template name must not contain path traversal characters (\\, ..)'
62
+ );
63
+ }
64
+
65
+ // Security Layer 2: Resolve absolute paths
66
+ const templateDir = resolve(projectRoot, 'templates', 'multi-repo');
67
+ const templatePath = resolve(templateDir, `${templateName}.md`);
68
+
69
+ // Security Layer 3: Verify path containment
70
+ const relativePath = relative(templateDir, templatePath);
71
+ if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
72
+ throw new Error(
73
+ `Invalid template path: ${templateName}\n` +
74
+ `Template path is outside template directory: ${templateDir}`
75
+ );
76
+ }
77
+
78
+ try {
79
+ return readFileSync(templatePath, 'utf-8');
80
+ } catch (error) {
81
+ const errorMessage = error instanceof Error ? error.message : String(error);
82
+ throw new Error(
83
+ `Multi-Repo template not found: ${templateName}.md\n` +
84
+ `Path: ${templatePath}\n` +
85
+ `Error: ${errorMessage}`
86
+ );
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Render Multi-Repo template with placeholder replacement
92
+ *
93
+ * @param template - Template string
94
+ * @param context - Multi-Repo template context
95
+ * @returns Rendered template string
96
+ */
97
+ export const renderMultiRepoTemplate = (
98
+ template: string,
99
+ context: MultiRepoTemplateContext
100
+ ): string => {
101
+ // Convert MultiRepoTemplateContext to TemplateContext
102
+ const templateContext: TemplateContext = {
103
+ LANG_CODE: 'ja', // Default language
104
+ DEV_GUIDELINES: '', // Not used for Multi-Repo templates
105
+ KIRO_DIR: '.kiro',
106
+ AGENT_DIR: '.claude',
107
+ PROJECT_NAME: context.PROJECT_NAME,
108
+ JIRA_KEY: context.JIRA_KEY,
109
+ CONFLUENCE_SPACE: context.CONFLUENCE_SPACE,
110
+ CREATED_AT: context.CREATED_AT,
111
+ };
112
+
113
+ return renderTemplate(template, templateContext);
114
+ };
115
+
116
+ /**
117
+ * Load and render Multi-Repo template
118
+ *
119
+ * @param templateName - Template name
120
+ * @param context - Multi-Repo template context
121
+ * @param projectRoot - Project root directory
122
+ * @returns Rendered template string
123
+ */
124
+ export const loadAndRenderMultiRepoTemplate = (
125
+ templateName: string,
126
+ context: MultiRepoTemplateContext,
127
+ projectRoot: string = process.cwd()
128
+ ): string => {
129
+ const template = loadMultiRepoTemplate(templateName, projectRoot);
130
+ return renderMultiRepoTemplate(template, context);
131
+ };
132
+
133
+ /**
134
+ * Batch render multiple Multi-Repo templates
135
+ *
136
+ * @param templateNames - Array of template names
137
+ * @param context - Multi-Repo template context
138
+ * @param projectRoot - Project root directory
139
+ * @returns Map of template names to rendered strings
140
+ */
141
+ export const renderMultiRepoTemplates = (
142
+ templateNames: string[],
143
+ context: MultiRepoTemplateContext,
144
+ projectRoot: string = process.cwd()
145
+ ): Record<string, string> => {
146
+ const rendered: Record<string, string> = {};
147
+
148
+ for (const templateName of templateNames) {
149
+ rendered[templateName] = loadAndRenderMultiRepoTemplate(
150
+ templateName,
151
+ context,
152
+ projectRoot
153
+ );
154
+ }
155
+
156
+ return rendered;
157
+ };
158
+
159
+ /**
160
+ * List of all Multi-Repo template names
161
+ */
162
+ export const MULTI_REPO_TEMPLATES = [
163
+ 'overview/requirements',
164
+ 'overview/architecture',
165
+ 'overview/sequence',
166
+ 'steering/multi-repo',
167
+ 'tests/strategy',
168
+ 'docs/ci-status',
169
+ 'docs/release-notes',
170
+ ] as const;
171
+
172
+ export type MultiRepoTemplateName = typeof MULTI_REPO_TEMPLATES[number];
@@ -14,6 +14,11 @@ export interface TemplateContext {
14
14
  PROJECT_ID?: string;
15
15
  FEATURE_NAME?: string;
16
16
  TIMESTAMP?: string;
17
+ // Multi-Repo specific placeholders
18
+ PROJECT_NAME?: string;
19
+ JIRA_KEY?: string;
20
+ CONFLUENCE_SPACE?: string;
21
+ CREATED_AT?: string;
17
22
  }
18
23
 
19
24
  /**
@@ -6,6 +6,73 @@
6
6
  import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'fs';
7
7
  import { join } from 'path';
8
8
 
9
+ /**
10
+ * セキュリティ: URLバリデーション
11
+ * HTTP/HTTPS URLのみ許可、特殊文字やコマンドインジェクションを防ぐ
12
+ */
13
+ function validateUrl(url: string): void {
14
+ // 基本的なURL形式チェック
15
+ try {
16
+ const parsed = new URL(url);
17
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
18
+ throw new Error(`Invalid protocol: ${parsed.protocol}. Only http: and https: are allowed.`);
19
+ }
20
+ } catch (_error) {
21
+ throw new Error(`Invalid URL format: ${url}`);
22
+ }
23
+
24
+ // コマンドインジェクション対策: 危険な文字を検出
25
+ const dangerousChars = /[;`$()&|<>]/;
26
+ if (dangerousChars.test(url)) {
27
+ throw new Error(`URL contains dangerous characters: ${url}`);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * セキュリティ: Bash用文字列エスケープ
33
+ * シェルスクリプト内で安全に使用できる形式にエスケープ
34
+ */
35
+ function escapeBash(str: string): string {
36
+ // シングルクォートで囲み、シングルクォート自体をエスケープ
37
+ return `'${str.replace(/'/g, '\'\\\'\'')}'`;
38
+ }
39
+
40
+ /**
41
+ * セキュリティ: Python用文字列エスケープ
42
+ * Pythonコード内で安全に使用できる形式にエスケープ
43
+ */
44
+ function escapePython(str: string): string {
45
+ return str
46
+ .replace(/\\/g, '\\\\') // バックスラッシュ
47
+ .replace(/"/g, '\\"') // ダブルクォート
48
+ .replace(/\n/g, '\\n') // 改行
49
+ .replace(/\r/g, '\\r') // キャリッジリターン
50
+ .replace(/\t/g, '\\t'); // タブ
51
+ }
52
+
53
+ /**
54
+ * セキュリティ: Python識別子バリデーション
55
+ * Pythonのクラス名として有効な形式かチェック
56
+ */
57
+ function validatePythonIdentifier(name: string): void {
58
+ // Python識別子のルール: 英字またはアンダースコアで始まり、英数字とアンダースコアのみ
59
+ const pythonIdentifierPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
60
+ if (!pythonIdentifierPattern.test(name)) {
61
+ throw new Error(`Invalid Python identifier: ${name}. Must start with letter or underscore, and contain only letters, numbers, and underscores.`);
62
+ }
63
+
64
+ // Pythonの予約語チェック
65
+ const pythonKeywords = [
66
+ 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
67
+ 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
68
+ 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda',
69
+ 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'
70
+ ];
71
+ if (pythonKeywords.includes(name)) {
72
+ throw new Error(`Invalid Python identifier: ${name} is a reserved keyword.`);
73
+ }
74
+ }
75
+
9
76
  /**
10
77
  * テスト実行ファイルの生成オプション
11
78
  */
@@ -151,28 +218,47 @@ function generateLocustFile(
151
218
  endpoint: { endpoint: string; method: string; baseUrl: string },
152
219
  perfReqs: { targetRps: string; targetResponseTime: string }
153
220
  ): string {
221
+ // セキュリティ: URLバリデーション
222
+ validateUrl(endpoint.baseUrl);
223
+
224
+ // セキュリティ: Pythonエスケープ
225
+ const escapedFeature = escapePython(feature);
226
+ const escapedEndpoint = escapePython(endpoint.endpoint);
227
+ const escapedMethod = escapePython(endpoint.method);
228
+ const escapedBaseUrl = escapePython(endpoint.baseUrl);
229
+ const escapedTargetRps = escapePython(perfReqs.targetRps);
230
+ const escapedTargetResponseTime = escapePython(perfReqs.targetResponseTime);
231
+
232
+ // セキュリティ: Pythonクラス名の生成と検証
233
+ let className = feature.replace(/[^a-zA-Z0-9]/g, '_');
234
+ // クラス名は大文字で始まる必要がある(Python慣例)
235
+ if (!/^[A-Z]/.test(className)) {
236
+ className = 'Test' + className.charAt(0).toUpperCase() + className.slice(1);
237
+ }
238
+ // Python識別子として有効かチェック
239
+ validatePythonIdentifier(className);
240
+
154
241
  const methodLower = endpoint.method.toLowerCase();
155
242
  const hasBody = ['post', 'put', 'patch'].includes(methodLower);
156
- const className = feature.replace(/[^a-zA-Z0-9]/g, '');
157
243
 
158
244
  let taskCode = '';
159
245
  if (hasBody) {
160
246
  taskCode = ` self.client.${methodLower}(
161
- "${endpoint.endpoint}",
247
+ "${escapedEndpoint}",
162
248
  json={},
163
249
  headers={"Content-Type": "application/json"}
164
250
  )`;
165
251
  } else {
166
- taskCode = ` self.client.${methodLower}("${endpoint.endpoint}")`;
252
+ taskCode = ` self.client.${methodLower}("${escapedEndpoint}")`;
167
253
  }
168
254
 
169
255
  return `"""
170
- ${feature} 負荷テスト
171
- 自動生成: michi phase:run ${feature} phase-b
256
+ ${escapedFeature} 負荷テスト
257
+ 自動生成: michi phase:run ${escapedFeature} phase-b
172
258
 
173
259
  目標:
174
- - RPS: ${perfReqs.targetRps}
175
- - 応答時間: ${perfReqs.targetResponseTime}ms以内
260
+ - RPS: ${escapedTargetRps}
261
+ - 応答時間: ${escapedTargetResponseTime}ms以内
176
262
  """
177
263
 
178
264
  from locust import HttpUser, task, between
@@ -182,11 +268,11 @@ class ${className}User(HttpUser):
182
268
  """テスト対象ユーザーシミュレーション"""
183
269
 
184
270
  wait_time = between(1, 3)
185
- host = "${endpoint.baseUrl}"
271
+ host = "${escapedBaseUrl}"
186
272
 
187
273
  @task
188
274
  def test_endpoint(self):
189
- """${endpoint.endpoint}への${endpoint.method}リクエスト"""
275
+ """${escapedEndpoint}への${escapedMethod}リクエスト"""
190
276
  ${taskCode}
191
277
 
192
278
 
@@ -369,14 +455,21 @@ function generateZapScript(
369
455
  feature: string,
370
456
  endpoint: { baseUrl: string }
371
457
  ): string {
458
+ // セキュリティ: URLバリデーション(コマンドインジェクション対策)
459
+ validateUrl(endpoint.baseUrl);
460
+
461
+ // セキュリティ: Bashエスケープ(追加の防御層)
462
+ const escapedUrl = escapeBash(endpoint.baseUrl);
463
+ const escapedFeature = escapeBash(feature);
464
+
372
465
  return `#!/bin/bash
373
466
  # OWASP ZAPセキュリティスキャン実行スクリプト
374
- # 自動生成: michi phase:run ${feature} phase-b
467
+ # 自動生成: michi phase:run ${escapedFeature} phase-b
375
468
 
376
469
  set -e
377
470
 
378
471
  # 変数定義
379
- TARGET_URL="${endpoint.baseUrl}"
472
+ TARGET_URL=${escapedUrl}
380
473
  CONFIG_FILE="zap-config.yaml"
381
474
  REPORT_DIR="./reports"
382
475
 
@@ -0,0 +1,130 @@
1
+ /**
2
+ * テストスクリプトランナー
3
+ * Multi-Repoプロジェクトのテストスクリプトを実行
4
+ */
5
+
6
+ import { execSync } from 'child_process';
7
+ import { join } from 'path';
8
+
9
+ /**
10
+ * テストタイプ
11
+ */
12
+ export type TestType = 'e2e' | 'integration' | 'performance';
13
+
14
+ /**
15
+ * テスト実行結果
16
+ */
17
+ export interface TestExecutionResult {
18
+ success: boolean;
19
+ exitCode: number;
20
+ executionTime: number; // 秒単位
21
+ outputPath: string;
22
+ error?: string;
23
+ }
24
+
25
+ /**
26
+ * execSync実行時のエラー型
27
+ */
28
+ interface ExecError extends Error {
29
+ status?: number;
30
+ stderr?: Buffer | string;
31
+ }
32
+
33
+ /**
34
+ * テストスクリプトランナー
35
+ */
36
+ export class TestScriptRunner {
37
+ /**
38
+ * テストスクリプトを実行
39
+ *
40
+ * @param projectName プロジェクト名
41
+ * @param testType テストタイプ (e2e | integration | performance)
42
+ * @param projectRoot プロジェクトルートディレクトリ(デフォルト: process.cwd())
43
+ * @returns テスト実行結果
44
+ */
45
+ async runTestScript(
46
+ projectName: string,
47
+ testType: TestType,
48
+ projectRoot: string = process.cwd()
49
+ ): Promise<TestExecutionResult> {
50
+ // 1. テストスクリプトパスを生成
51
+ const scriptPath = join(
52
+ projectRoot,
53
+ 'docs',
54
+ 'michi',
55
+ projectName,
56
+ 'tests',
57
+ 'scripts',
58
+ `run-${testType}.sh`
59
+ );
60
+
61
+ // 2. テスト結果の出力先パスを生成(スクリプト側で使用)
62
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
63
+ const outputPath = join(
64
+ projectRoot,
65
+ 'docs',
66
+ 'michi',
67
+ projectName,
68
+ 'tests',
69
+ 'results',
70
+ `${testType}-${timestamp}.log`
71
+ );
72
+
73
+ // 3. 実行前のログ出力
74
+ console.log('🚀 テストスクリプトを実行中...');
75
+ console.log(` テストタイプ: ${testType}`);
76
+ console.log(` スクリプトパス: ${scriptPath}`);
77
+ console.log(` 実行開始時刻: ${new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })}`);
78
+
79
+ // 4. テストスクリプトを実行
80
+ const startTime = Date.now();
81
+ let exitCode = 0;
82
+ let error: string | undefined;
83
+
84
+ try {
85
+ execSync(scriptPath, {
86
+ stdio: 'inherit', // リアルタイム出力
87
+ encoding: 'utf-8',
88
+ });
89
+ } catch (err) {
90
+ // スクリプト実行エラー
91
+ const execError = err as ExecError;
92
+ exitCode = execError.status !== undefined ? execError.status : 1;
93
+ error = execError.message;
94
+
95
+ // stderrがある場合は追加
96
+ if (execError.stderr) {
97
+ const stderrStr = execError.stderr.toString();
98
+ error = stderrStr || error;
99
+ }
100
+ }
101
+
102
+ const endTime = Date.now();
103
+ const executionTime = (endTime - startTime) / 1000; // ミリ秒から秒に変換
104
+
105
+ // 5. 実行後のログ出力
106
+ console.log('\n📊 テスト実行結果:');
107
+ console.log(` 終了コード: ${exitCode}`);
108
+ console.log(` 実行時間: ${executionTime.toFixed(2)}秒`);
109
+ console.log(` テスト結果ファイル: ${outputPath}`);
110
+
111
+ const success = exitCode === 0;
112
+
113
+ if (success) {
114
+ console.log('✅ テスト実行が成功しました');
115
+ } else {
116
+ console.log('❌ テスト実行が失敗しました');
117
+ if (error) {
118
+ console.log(` エラー: ${error}`);
119
+ }
120
+ }
121
+
122
+ return {
123
+ success,
124
+ exitCode,
125
+ executionTime,
126
+ outputPath,
127
+ error,
128
+ };
129
+ }
130
+ }
@@ -147,6 +147,22 @@ describe('config-validator', () => {
147
147
  });
148
148
 
149
149
  describe('validateForConfluenceSync', () => {
150
+ let savedConfluenceSpace: string | undefined;
151
+
152
+ beforeEach(() => {
153
+ // 環境変数を保存
154
+ savedConfluenceSpace = process.env.CONFLUENCE_PRD_SPACE;
155
+ });
156
+
157
+ afterEach(() => {
158
+ // 環境変数を復元
159
+ if (savedConfluenceSpace !== undefined) {
160
+ process.env.CONFLUENCE_PRD_SPACE = savedConfluenceSpace;
161
+ } else {
162
+ delete process.env.CONFLUENCE_PRD_SPACE;
163
+ }
164
+ });
165
+
150
166
  it('spaces設定がない場合は警告を返す', () => {
151
167
  // 環境変数をクリア
152
168
  delete process.env.CONFLUENCE_PRD_SPACE;
@@ -236,6 +252,7 @@ describe('config-validator', () => {
236
252
  });
237
253
 
238
254
  it('環境変数CONFLUENCE_PRD_SPACEがある場合は情報メッセージ', () => {
255
+ // 環境変数を明示的に設定
239
256
  process.env.CONFLUENCE_PRD_SPACE = 'Michi';
240
257
  const configPath = join(testProjectRoot, '.michi/config.json');
241
258
  writeFileSync(
@@ -259,23 +276,48 @@ describe('config-validator', () => {
259
276
  });
260
277
 
261
278
  describe('validateForJiraSync', () => {
279
+ let savedStoryEnv: string | undefined;
280
+ let savedSubtaskEnv: string | undefined;
281
+
262
282
  beforeEach(() => {
263
- // 環境変数をクリア
283
+ // 環境変数を保存して削除
284
+ savedStoryEnv = process.env.JIRA_ISSUE_TYPE_STORY;
285
+ savedSubtaskEnv = process.env.JIRA_ISSUE_TYPE_SUBTASK;
264
286
  delete process.env.JIRA_ISSUE_TYPE_STORY;
265
287
  delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
266
288
  });
267
289
 
268
- it('issueTypes.story設定がない場合はエラー', () => {
290
+ afterEach(() => {
291
+ // 環境変数を復元
292
+ if (savedStoryEnv !== undefined) {
293
+ process.env.JIRA_ISSUE_TYPE_STORY = savedStoryEnv;
294
+ } else {
295
+ delete process.env.JIRA_ISSUE_TYPE_STORY;
296
+ }
297
+ if (savedSubtaskEnv !== undefined) {
298
+ process.env.JIRA_ISSUE_TYPE_SUBTASK = savedSubtaskEnv;
299
+ } else {
300
+ delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
301
+ }
302
+ });
303
+
304
+ it('issueTypes.story設定がない場合はエラー(デフォルト設定なし)', () => {
269
305
  const configPath = join(testProjectRoot, '.michi/config.json');
306
+ // issueTypes.storyを明示的にnullに設定してデフォルト値を無効化
270
307
  writeFileSync(
271
308
  configPath,
272
309
  JSON.stringify({
273
- jira: {},
310
+ jira: {
311
+ issueTypes: {
312
+ story: null,
313
+ },
314
+ },
274
315
  }),
275
316
  );
276
317
 
277
318
  const result = validateForJiraSync(testProjectRoot);
278
319
 
320
+ // story=nullの場合、環境変数もないのでエラーになる
279
321
  expect(result.valid).toBe(false);
280
322
  expect(result.errors.length).toBeGreaterThan(0);
281
323
  expect(result.errors[0]).toContain('issueTypes.story');
@@ -301,6 +343,7 @@ describe('config-validator', () => {
301
343
  });
302
344
 
303
345
  it('環境変数JIRA_ISSUE_TYPE_STORYがある場合は情報メッセージ', () => {
346
+ // 環境変数を明示的に設定
304
347
  process.env.JIRA_ISSUE_TYPE_STORY = '10036';
305
348
  const configPath = join(testProjectRoot, '.michi/config.json');
306
349
  writeFileSync(
@@ -322,7 +365,7 @@ describe('config-validator', () => {
322
365
  }
323
366
  });
324
367
 
325
- it('issueTypes.subtask設定がない場合は警告', () => {
368
+ it('issueTypes.subtask設定がない場合は警告(デフォルト設定なし)', () => {
326
369
  const configPath = join(testProjectRoot, '.michi/config.json');
327
370
  writeFileSync(
328
371
  configPath,
@@ -330,6 +373,7 @@ describe('config-validator', () => {
330
373
  jira: {
331
374
  issueTypes: {
332
375
  story: '10036',
376
+ subtask: null, // 明示的にnullに設定してデフォルト値を無効化
333
377
  },
334
378
  },
335
379
  }),
@@ -337,6 +381,7 @@ describe('config-validator', () => {
337
381
 
338
382
  const result = validateForJiraSync(testProjectRoot);
339
383
 
384
+ // subtask=nullの場合、環境変数もないので警告になる
340
385
  expect(result.valid).toBe(true);
341
386
  expect(result.warnings.length).toBeGreaterThan(0);
342
387
  expect(result.warnings[0]).toContain('subtask');
@@ -365,8 +410,23 @@ describe('config-validator', () => {
365
410
  });
366
411
 
367
412
  describe('validateForJiraSyncAsync', () => {
413
+ let savedEnv: {
414
+ JIRA_ISSUE_TYPE_STORY?: string;
415
+ JIRA_ISSUE_TYPE_SUBTASK?: string;
416
+ ATLASSIAN_URL?: string;
417
+ ATLASSIAN_EMAIL?: string;
418
+ ATLASSIAN_API_TOKEN?: string;
419
+ };
420
+
368
421
  beforeEach(() => {
369
- // 環境変数をクリア
422
+ // 環境変数を保存して削除
423
+ savedEnv = {
424
+ JIRA_ISSUE_TYPE_STORY: process.env.JIRA_ISSUE_TYPE_STORY,
425
+ JIRA_ISSUE_TYPE_SUBTASK: process.env.JIRA_ISSUE_TYPE_SUBTASK,
426
+ ATLASSIAN_URL: process.env.ATLASSIAN_URL,
427
+ ATLASSIAN_EMAIL: process.env.ATLASSIAN_EMAIL,
428
+ ATLASSIAN_API_TOKEN: process.env.ATLASSIAN_API_TOKEN,
429
+ };
370
430
  delete process.env.JIRA_ISSUE_TYPE_STORY;
371
431
  delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
372
432
  delete process.env.ATLASSIAN_URL;
@@ -374,6 +434,35 @@ describe('config-validator', () => {
374
434
  delete process.env.ATLASSIAN_API_TOKEN;
375
435
  });
376
436
 
437
+ afterEach(() => {
438
+ // 環境変数を復元
439
+ if (savedEnv.JIRA_ISSUE_TYPE_STORY !== undefined) {
440
+ process.env.JIRA_ISSUE_TYPE_STORY = savedEnv.JIRA_ISSUE_TYPE_STORY;
441
+ } else {
442
+ delete process.env.JIRA_ISSUE_TYPE_STORY;
443
+ }
444
+ if (savedEnv.JIRA_ISSUE_TYPE_SUBTASK !== undefined) {
445
+ process.env.JIRA_ISSUE_TYPE_SUBTASK = savedEnv.JIRA_ISSUE_TYPE_SUBTASK;
446
+ } else {
447
+ delete process.env.JIRA_ISSUE_TYPE_SUBTASK;
448
+ }
449
+ if (savedEnv.ATLASSIAN_URL !== undefined) {
450
+ process.env.ATLASSIAN_URL = savedEnv.ATLASSIAN_URL;
451
+ } else {
452
+ delete process.env.ATLASSIAN_URL;
453
+ }
454
+ if (savedEnv.ATLASSIAN_EMAIL !== undefined) {
455
+ process.env.ATLASSIAN_EMAIL = savedEnv.ATLASSIAN_EMAIL;
456
+ } else {
457
+ delete process.env.ATLASSIAN_EMAIL;
458
+ }
459
+ if (savedEnv.ATLASSIAN_API_TOKEN !== undefined) {
460
+ process.env.ATLASSIAN_API_TOKEN = savedEnv.ATLASSIAN_API_TOKEN;
461
+ } else {
462
+ delete process.env.ATLASSIAN_API_TOKEN;
463
+ }
464
+ });
465
+
377
466
  it('認証情報が未設定の場合は同期版と同じ結果を返す', async () => {
378
467
  const configPath = join(testProjectRoot, '.michi/config.json');
379
468
  writeFileSync(
@@ -406,6 +495,7 @@ describe('config-validator', () => {
406
495
  });
407
496
 
408
497
  it('認証情報が設定されていて、Issue Type IDが存在する場合は成功', async () => {
498
+ // 環境変数を明示的に設定
409
499
  process.env.ATLASSIAN_URL = 'https://test.atlassian.net';
410
500
  process.env.ATLASSIAN_EMAIL = 'test@example.com';
411
501
  process.env.ATLASSIAN_API_TOKEN = 'test-token';
@@ -451,16 +541,23 @@ describe('config-validator', () => {
451
541
  });
452
542
 
453
543
  it('認証情報が設定されていて、Issue Type IDが存在しない場合はエラー', async () => {
544
+ // 環境変数を明示的に設定(既存の環境変数を上書き)
454
545
  process.env.ATLASSIAN_URL = 'https://test.atlassian.net';
455
546
  process.env.ATLASSIAN_EMAIL = 'test@example.com';
456
547
  process.env.ATLASSIAN_API_TOKEN = 'test-token';
457
548
  process.env.JIRA_ISSUE_TYPE_STORY = '99999'; // 存在しないID
458
549
 
459
550
  const configPath = join(testProjectRoot, '.michi/config.json');
551
+ // issueTypes.storyを環境変数から取得するように空にする
552
+ // (デフォルト値を使わないように明示的にnullに設定)
460
553
  writeFileSync(
461
554
  configPath,
462
555
  JSON.stringify({
463
- jira: {},
556
+ jira: {
557
+ issueTypes: {
558
+ story: null, // 環境変数から取得
559
+ },
560
+ },
464
561
  }),
465
562
  );
466
563
 
@@ -501,6 +598,7 @@ describe('config-validator', () => {
501
598
  });
502
599
 
503
600
  it('JIRA API取得に失敗した場合は警告を追加するがエラーにはしない', async () => {
601
+ // 環境変数を明示的に設定
504
602
  process.env.ATLASSIAN_URL = 'https://test.atlassian.net';
505
603
  process.env.ATLASSIAN_EMAIL = 'test@example.com';
506
604
  process.env.ATLASSIAN_API_TOKEN = 'test-token';