@sk8metal/michi-cli 0.3.0 → 0.5.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 (237) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/scripts/__tests__/spec-impl-workflow.test.js +4 -2
  3. package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +1 -1
  4. package/dist/scripts/config/config-schema.d.ts +52 -0
  5. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  6. package/dist/scripts/config/config-schema.js +25 -0
  7. package/dist/scripts/config/config-schema.js.map +1 -1
  8. package/dist/scripts/config-global.d.ts +10 -0
  9. package/dist/scripts/config-global.d.ts.map +1 -0
  10. package/dist/scripts/config-global.js +111 -0
  11. package/dist/scripts/config-global.js.map +1 -0
  12. package/dist/scripts/confluence-sync.d.ts +22 -4
  13. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  14. package/dist/scripts/confluence-sync.js +22 -12
  15. package/dist/scripts/confluence-sync.js.map +1 -1
  16. package/dist/scripts/jira-sync.d.ts.map +1 -1
  17. package/dist/scripts/jira-sync.js +201 -167
  18. package/dist/scripts/jira-sync.js.map +1 -1
  19. package/dist/scripts/list-projects.js.map +1 -1
  20. package/dist/scripts/multi-project-estimate.js.map +1 -1
  21. package/dist/scripts/phase-runner.d.ts +1 -1
  22. package/dist/scripts/phase-runner.d.ts.map +1 -1
  23. package/dist/scripts/phase-runner.js +295 -522
  24. package/dist/scripts/phase-runner.js.map +1 -1
  25. package/dist/scripts/pr-automation.d.ts.map +1 -1
  26. package/dist/scripts/pr-automation.js +11 -3
  27. package/dist/scripts/pr-automation.js.map +1 -1
  28. package/dist/scripts/pre-flight-check.d.ts.map +1 -1
  29. package/dist/scripts/pre-flight-check.js +10 -6
  30. package/dist/scripts/pre-flight-check.js.map +1 -1
  31. package/dist/scripts/resource-dashboard.js.map +1 -1
  32. package/dist/scripts/spec-impl-workflow.d.ts.map +1 -1
  33. package/dist/scripts/spec-impl-workflow.js +23 -7
  34. package/dist/scripts/spec-impl-workflow.js.map +1 -1
  35. package/dist/scripts/template/renderer.d.ts +1 -1
  36. package/dist/scripts/template/renderer.d.ts.map +1 -1
  37. package/dist/scripts/test-interactive.d.ts.map +1 -1
  38. package/dist/scripts/test-interactive.js +0 -15
  39. package/dist/scripts/test-interactive.js.map +1 -1
  40. package/dist/scripts/test-new-features.js +6 -3
  41. package/dist/scripts/test-new-features.js.map +1 -1
  42. package/dist/scripts/test-spec-generator.d.ts.map +1 -1
  43. package/dist/scripts/test-spec-generator.js +1 -2
  44. package/dist/scripts/test-spec-generator.js.map +1 -1
  45. package/dist/scripts/utils/__tests__/config-loader.test.js +114 -1
  46. package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
  47. package/dist/scripts/utils/__tests__/config-validator.test.js +2 -0
  48. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  49. package/dist/scripts/utils/__tests__/env-config.test.js +0 -2
  50. package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -1
  51. package/dist/scripts/utils/__tests__/project-meta.test.d.ts +6 -0
  52. package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +1 -0
  53. package/dist/scripts/utils/__tests__/project-meta.test.js +154 -0
  54. package/dist/scripts/utils/__tests__/project-meta.test.js.map +1 -0
  55. package/dist/scripts/utils/__tests__/security-validator.test.d.ts +6 -0
  56. package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +1 -0
  57. package/dist/scripts/utils/__tests__/security-validator.test.js +219 -0
  58. package/dist/scripts/utils/__tests__/security-validator.test.js.map +1 -0
  59. package/dist/scripts/utils/config-loader.d.ts +14 -3
  60. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  61. package/dist/scripts/utils/config-loader.js +284 -46
  62. package/dist/scripts/utils/config-loader.js.map +1 -1
  63. package/dist/scripts/utils/config-sections.d.ts +54 -0
  64. package/dist/scripts/utils/config-sections.d.ts.map +1 -0
  65. package/dist/scripts/utils/config-sections.js +178 -0
  66. package/dist/scripts/utils/config-sections.js.map +1 -0
  67. package/dist/scripts/utils/config-validator.d.ts +4 -0
  68. package/dist/scripts/utils/config-validator.d.ts.map +1 -1
  69. package/dist/scripts/utils/config-validator.js +57 -1
  70. package/dist/scripts/utils/config-validator.js.map +1 -1
  71. package/dist/scripts/utils/confluence-approval.d.ts.map +1 -1
  72. package/dist/scripts/utils/confluence-approval.js +5 -3
  73. package/dist/scripts/utils/confluence-approval.js.map +1 -1
  74. package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
  75. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  76. package/dist/scripts/utils/env-config.d.ts +1 -1
  77. package/dist/scripts/utils/env-config.d.ts.map +1 -1
  78. package/dist/scripts/utils/env-config.js +2 -14
  79. package/dist/scripts/utils/env-config.js.map +1 -1
  80. package/dist/scripts/utils/interactive-helpers.d.ts +32 -0
  81. package/dist/scripts/utils/interactive-helpers.d.ts.map +1 -0
  82. package/dist/scripts/utils/interactive-helpers.js +92 -0
  83. package/dist/scripts/utils/interactive-helpers.js.map +1 -0
  84. package/dist/scripts/utils/jira-issue-type-fetcher.d.ts.map +1 -1
  85. package/dist/scripts/utils/jira-issue-type-fetcher.js +27 -18
  86. package/dist/scripts/utils/jira-issue-type-fetcher.js.map +1 -1
  87. package/dist/scripts/utils/project-meta.d.ts +9 -0
  88. package/dist/scripts/utils/project-meta.d.ts.map +1 -1
  89. package/dist/scripts/utils/project-meta.js +22 -0
  90. package/dist/scripts/utils/project-meta.js.map +1 -1
  91. package/dist/scripts/utils/release-notes-generator.d.ts.map +1 -1
  92. package/dist/scripts/utils/release-notes-generator.js +2 -1
  93. package/dist/scripts/utils/release-notes-generator.js.map +1 -1
  94. package/dist/scripts/utils/security-validator.d.ts +55 -0
  95. package/dist/scripts/utils/security-validator.d.ts.map +1 -0
  96. package/dist/scripts/utils/security-validator.js +232 -0
  97. package/dist/scripts/utils/security-validator.js.map +1 -0
  98. package/dist/scripts/utils/spec-updater.d.ts +19 -0
  99. package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
  100. package/dist/scripts/utils/spec-updater.js.map +1 -1
  101. package/dist/scripts/utils/tasks-converter.d.ts.map +1 -1
  102. package/dist/scripts/utils/tasks-converter.js +2 -2
  103. package/dist/scripts/utils/tasks-converter.js.map +1 -1
  104. package/dist/scripts/utils/tasks-format-validator.d.ts.map +1 -1
  105. package/dist/scripts/utils/tasks-format-validator.js +0 -12
  106. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  107. package/dist/scripts/utils/test-runner.d.ts.map +1 -1
  108. package/dist/scripts/utils/test-runner.js +3 -2
  109. package/dist/scripts/utils/test-runner.js.map +1 -1
  110. package/dist/scripts/validate-phase.d.ts +1 -1
  111. package/dist/scripts/validate-phase.d.ts.map +1 -1
  112. package/dist/scripts/validate-phase.js +12 -62
  113. package/dist/scripts/validate-phase.js.map +1 -1
  114. package/dist/scripts/workflow-orchestrator.d.ts.map +1 -1
  115. package/dist/scripts/workflow-orchestrator.js +11 -16
  116. package/dist/scripts/workflow-orchestrator.js.map +1 -1
  117. package/dist/src/__tests__/integration/setup/init.test.d.ts +5 -0
  118. package/dist/src/__tests__/integration/setup/init.test.d.ts.map +1 -0
  119. package/dist/src/__tests__/integration/setup/init.test.js +352 -0
  120. package/dist/src/__tests__/integration/setup/init.test.js.map +1 -0
  121. package/dist/src/cli.d.ts.map +1 -1
  122. package/dist/src/cli.js +67 -21
  123. package/dist/src/cli.js.map +1 -1
  124. package/dist/src/commands/__tests__/init.test.d.ts +5 -0
  125. package/dist/src/commands/__tests__/init.test.d.ts.map +1 -0
  126. package/dist/src/commands/__tests__/init.test.js +255 -0
  127. package/dist/src/commands/__tests__/init.test.js.map +1 -0
  128. package/dist/src/commands/__tests__/migrate.test.d.ts +5 -0
  129. package/dist/src/commands/__tests__/migrate.test.d.ts.map +1 -0
  130. package/dist/src/commands/__tests__/migrate.test.js +216 -0
  131. package/dist/src/commands/__tests__/migrate.test.js.map +1 -0
  132. package/dist/src/commands/config-validate.d.ts +9 -0
  133. package/dist/src/commands/config-validate.d.ts.map +1 -0
  134. package/dist/src/commands/config-validate.js +90 -0
  135. package/dist/src/commands/config-validate.js.map +1 -0
  136. package/dist/src/commands/init.d.ts +29 -0
  137. package/dist/src/commands/init.d.ts.map +1 -0
  138. package/dist/src/commands/init.js +513 -0
  139. package/dist/src/commands/init.js.map +1 -0
  140. package/dist/src/commands/migrate.d.ts +25 -0
  141. package/dist/src/commands/migrate.d.ts.map +1 -0
  142. package/dist/src/commands/migrate.js +341 -0
  143. package/dist/src/commands/migrate.js.map +1 -0
  144. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  145. package/dist/src/commands/setup-existing.js +0 -1
  146. package/dist/src/commands/setup-existing.js.map +1 -1
  147. package/dist/vitest.config.d.ts.map +1 -1
  148. package/dist/vitest.config.js +32 -8
  149. package/dist/vitest.config.js.map +1 -1
  150. package/docs/michi-development/design/config-unification.md +4789 -0
  151. package/docs/user-guide/getting-started/github-token-setup.md +2 -1
  152. package/docs/user-guide/getting-started/new-repository-setup.md +1 -1
  153. package/docs/user-guide/getting-started/quick-start.md +1 -1
  154. package/docs/user-guide/getting-started/setup.md +35 -14
  155. package/docs/user-guide/guides/customization.md +64 -11
  156. package/docs/user-guide/guides/workflow.md +35 -21
  157. package/docs/user-guide/hands-on/claude-agent-setup.md +2 -2
  158. package/docs/user-guide/hands-on/claude-setup.md +2 -2
  159. package/docs/user-guide/hands-on/cursor-setup.md +2 -2
  160. package/docs/user-guide/hands-on/workflow-walkthrough.md +4 -1
  161. package/docs/user-guide/reference/config.md +30 -5
  162. package/docs/user-guide/reference/quick-reference.md +68 -74
  163. package/docs/user-guide/testing/test-planning-flow.md +4 -0
  164. package/env.example +1 -1
  165. package/package.json +3 -5
  166. package/scripts/__tests__/spec-impl-workflow.test.ts +5 -2
  167. package/scripts/config/config-schema.ts +40 -0
  168. package/scripts/config-global.ts +160 -0
  169. package/scripts/confluence-sync.ts +91 -27
  170. package/scripts/jira-sync.ts +284 -218
  171. package/scripts/list-projects.ts +2 -2
  172. package/scripts/multi-project-estimate.ts +3 -3
  173. package/scripts/phase-runner.ts +391 -594
  174. package/scripts/pr-automation.ts +15 -5
  175. package/scripts/pre-flight-check.ts +20 -9
  176. package/scripts/pre-publish-check.sh +3 -34
  177. package/scripts/resource-dashboard.ts +4 -4
  178. package/scripts/spec-impl-workflow.ts +23 -7
  179. package/scripts/template/renderer.ts +1 -1
  180. package/scripts/test-interactive.ts +0 -19
  181. package/scripts/test-new-features.ts +10 -7
  182. package/scripts/test-npm-package.sh +3 -34
  183. package/scripts/test-spec-generator.ts +3 -7
  184. package/scripts/utils/__tests__/config-loader.test.ts +149 -0
  185. package/scripts/utils/__tests__/config-validator.test.ts +2 -0
  186. package/scripts/utils/__tests__/env-config.test.ts +0 -2
  187. package/scripts/utils/__tests__/project-meta.test.ts +192 -0
  188. package/scripts/utils/__tests__/security-validator.test.ts +272 -0
  189. package/scripts/utils/config-loader.ts +328 -68
  190. package/scripts/utils/config-sections.ts +316 -0
  191. package/scripts/utils/config-validator.ts +66 -1
  192. package/scripts/utils/confluence-approval.ts +8 -6
  193. package/scripts/utils/confluence-hierarchy.ts +27 -27
  194. package/scripts/utils/env-config.ts +2 -14
  195. package/scripts/utils/interactive-helpers.ts +135 -0
  196. package/scripts/utils/jira-issue-type-fetcher.ts +29 -21
  197. package/scripts/utils/project-meta.ts +27 -0
  198. package/scripts/utils/release-notes-generator.ts +3 -2
  199. package/scripts/utils/security-validator.ts +286 -0
  200. package/scripts/utils/spec-updater.ts +37 -15
  201. package/scripts/utils/tasks-converter.ts +4 -6
  202. package/scripts/utils/tasks-format-validator.ts +0 -13
  203. package/scripts/utils/test-runner.ts +4 -3
  204. package/scripts/validate-phase.ts +21 -80
  205. package/scripts/workflow-orchestrator.ts +16 -25
  206. package/templates/claude/commands/kiro/kiro-spec-impl.md +5 -1
  207. package/templates/claude/commands/kiro/kiro-spec-tasks.md +3 -1
  208. package/templates/claude/commands/michi/confluence-sync.md +8 -2
  209. package/templates/claude/commands/michi/design-review.md +4 -0
  210. package/templates/claude/commands/michi/e2e-plan.md +4 -0
  211. package/templates/claude/commands/michi/license-check.md +4 -0
  212. package/templates/claude/commands/michi/pr-resolve.md +4 -0
  213. package/templates/claude/commands/michi/project-switch.md +8 -2
  214. package/templates/claude/commands/michi/spec-design.md +78 -0
  215. package/templates/claude/commands/michi/spec-impl.md +716 -0
  216. package/templates/claude/commands/michi/test-planning.md +174 -0
  217. package/templates/claude/commands/michi/validate-design.md +58 -0
  218. package/templates/claude/commands/michi/version-audit.md +4 -0
  219. package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +1 -1
  220. package/templates/cursor/commands/kiro/kiro-spec-impl.md +1 -1
  221. package/templates/michi/cc-sdd-overrides/README.md +8 -0
  222. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +53 -0
  223. package/dist/scripts/config-interactive.d.ts +0 -10
  224. package/dist/scripts/config-interactive.d.ts.map +0 -1
  225. package/dist/scripts/config-interactive.js +0 -372
  226. package/dist/scripts/config-interactive.js.map +0 -1
  227. package/dist/scripts/setup-existing-project.d.ts +0 -15
  228. package/dist/scripts/setup-existing-project.d.ts.map +0 -1
  229. package/dist/scripts/setup-existing-project.js +0 -455
  230. package/dist/scripts/setup-existing-project.js.map +0 -1
  231. package/dist/scripts/setup-interactive.d.ts +0 -10
  232. package/dist/scripts/setup-interactive.d.ts.map +0 -1
  233. package/dist/scripts/setup-interactive.js +0 -413
  234. package/dist/scripts/setup-interactive.js.map +0 -1
  235. package/scripts/config-interactive.ts +0 -550
  236. package/scripts/setup-existing-project.ts +0 -585
  237. package/scripts/setup-interactive.ts +0 -565
@@ -0,0 +1,4789 @@
1
+ # Michi 設定統合設計書
2
+
3
+ **バージョン**: 1.0
4
+ **作成日**: 2025-01-11
5
+ **ステータス**: Draft
6
+ **対象リリース**: v0.5.0 - v1.0.0
7
+
8
+ ---
9
+
10
+ ## 目次
11
+
12
+ 1. [エグゼクティブサマリー](#1-エグゼクティブサマリー)
13
+ 2. [現状分析](#2-現状分析)
14
+ 3. [問題点の特定](#3-問題点の特定)
15
+ 4. [解決策の提案](#4-解決策の提案)
16
+ 5. [新アーキテクチャ](#5-新アーキテクチャ)
17
+ 6. [実装詳細](#6-実装詳細)
18
+ 7. [マイグレーション戦略](#7-マイグレーション戦略)
19
+ 8. [テスト戦略](#8-テスト戦略)
20
+ 9. [セキュリティとパフォーマンス](#9-セキュリティとパフォーマンス)
21
+ 10. [後方互換性](#10-後方互換性)
22
+ 11. [ロードマップ](#11-ロードマップ)
23
+ 12. [付録](#12-付録)
24
+
25
+ ---
26
+
27
+ ## 1. エグゼクティブサマリー
28
+
29
+ ### 1.1 背景
30
+
31
+ Michiプロジェクトでは、現在3つのコマンド(`michi init`、`npx @sk8metal/michi-cli setup-existing`、`npm run config:global`)がプロジェクトの初期設定を担当しています。しかし、これらのコマンドには以下の問題があります:
32
+
33
+ - **重複する対話的プロンプト**: プロジェクト名、JIRAキー、環境の入力が複数のコマンドで重複
34
+ - **設定項目の分散**: 組織レベルで共通の設定が `.env` に分散し、プロジェクトごとに重複入力が必要
35
+ - **使い分けの不明瞭さ**: どのコマンドをいつ使うべきか、ユーザーにとって不明確
36
+
37
+ ### 1.2 目的
38
+
39
+ 本設計書では、以下を達成する統一的な設定管理システムを提案します:
40
+
41
+ 1. **設定の階層化**: グローバル設定(組織レベル)とプロジェクト設定(プロジェクトレベル)の明確な分離
42
+ 2. **コマンドの統一**: `init` と `setup-existing` を統合し、使い分けをシンプルに
43
+ 3. **自動マイグレーション**: 既存ユーザーが簡単に新形式に移行できるツールの提供
44
+ 4. **後方互換性**: 段階的な移行により、既存ユーザーへの影響を最小化
45
+
46
+ ### 1.3 期待される効果
47
+
48
+ - **ユーザー体験の向上**: 組織設定を一度だけ入力すれば、全プロジェクトで共有
49
+ - **保守性の向上**: 設定の一元管理により、変更が容易に
50
+ - **セキュリティの向上**: 認証情報を適切なファイル(`~/.michi/.env`)に集約し、パーミッション管理を強化
51
+
52
+ ---
53
+
54
+ ## 2. 現状分析
55
+
56
+ ### 2.1 現在の3つのコマンド
57
+
58
+ #### 2.1.1 `michi init` (新規プロジェクト用)
59
+
60
+ **対話的に取得する情報:**
61
+ - `projectId`: プロジェクトID
62
+ - `projectName`: プロジェクト名
63
+ - `jiraKey`: JIRAプロジェクトキー
64
+ - `environment`: 開発環境 (cursor/claude/gemini/codex/cline)
65
+ - `langCode`: ドキュメント言語 (ja/en)
66
+
67
+ **作成するファイル:**
68
+ - `.kiro/project.json`: プロジェクトメタデータ
69
+ - `.env`: 環境変数(テンプレート)
70
+ - `.michi/config.json`: ワークフロー設定(グローバル設定から自動コピーまたは対話的作成)
71
+ - テンプレート/ルール (--michi-path 指定時)
72
+
73
+ **動作フロー:**
74
+ ```
75
+ [開始]
76
+
77
+ [環境を決定] (cursor/claude/etc)
78
+
79
+ [対話的プロンプト] (projectId, projectName, jiraKey)
80
+
81
+ [確認]
82
+
83
+ [.kiro/ ディレクトリ作成]
84
+
85
+ [.kiro/project.json 作成]
86
+
87
+ [.env テンプレート作成]
88
+
89
+ [テンプレート/ルールコピー] (--michi-path 指定時)
90
+
91
+ [ワークフロー設定] (.michi/config.json)
92
+ ├─ グローバル設定がある場合: 自動コピー
93
+ └─ グローバル設定がない場合: 対話的作成 or デフォルト設定
94
+
95
+ [完了]
96
+ ```
97
+
98
+ #### 2.1.2 `npx @sk8metal/michi-cli setup-existing` (既存プロジェクト用)
99
+
100
+ **対話的に取得する情報:**
101
+ - `projectName`: プロジェクト名
102
+ - `jiraKey`: JIRAプロジェクトキー
103
+ - `environment`: 開発環境
104
+ - `langCode`: ドキュメント言語
105
+
106
+ **作成するファイル:**
107
+ - `.kiro/project.json`: プロジェクトメタデータ
108
+ - `.env`: 環境変数(対話的設定またはテンプレート)
109
+ - テンプレート/ルール
110
+ - スキル/サブエージェント (Claude環境の場合)
111
+
112
+ **動作フロー:**
113
+ ```
114
+ [開始]
115
+
116
+ [環境を決定]
117
+
118
+ [対話的プロンプト] (projectName, jiraKey)
119
+
120
+ [確認]
121
+
122
+ [.kiro/ ディレクトリ作成]
123
+
124
+ [.kiro/project.json 作成]
125
+
126
+ [Codex環境の場合]
127
+ └─ cc-sdd インストールプロンプト
128
+
129
+ [テンプレート/ルールコピー]
130
+ ├─ 環境別テンプレート
131
+ ├─ Steeringテンプレート
132
+ ├─ Specテンプレート
133
+ └─ cc-sdd オーバーライド
134
+
135
+ [.env 対話的設定]
136
+ ├─ 既存の .env がある場合: 上書き確認
137
+ └─ 新規の場合: 対話的設定 or テンプレート作成
138
+
139
+ [.gitignore 更新]
140
+
141
+ [スキル/サブエージェントインストール] (Claude環境)
142
+
143
+ [バリデーション]
144
+
145
+ [完了]
146
+ ```
147
+
148
+ #### 2.1.3 `npm run config:global` (グローバル設定)
149
+
150
+ **対話的に取得する情報:**
151
+ - Confluence設定
152
+ - `pageCreationGranularity`: ページ作成粒度
153
+ - `pageTitleFormat`: ページタイトル形式 (optional)
154
+ - `hierarchy`: 階層構造設定 (optional)
155
+ - JIRA設定
156
+ - `createEpic`: Epic作成有無
157
+ - `storyCreationGranularity`: Story作成粒度
158
+ - `selectedPhases`: 選択フェーズ (optional)
159
+ - `storyPoints`: Story Points設定
160
+ - ワークフロー設定
161
+ - `enabledPhases`: 有効フェーズ
162
+ - `approvalGates`: 承認ゲート (optional)
163
+
164
+ **作成するファイル:**
165
+ - `~/.michi/config.json`: グローバル設定
166
+
167
+ **動作フロー:**
168
+ ```
169
+ [開始]
170
+
171
+ [既存のグローバル設定を確認]
172
+ ├─ 存在する場合: 上書き確認
173
+ └─ 存在しない場合: 新規作成
174
+
175
+ [対話的に設定を取得]
176
+ ├─ Confluence設定をカスタマイズするか?
177
+ ├─ JIRA設定をカスタマイズするか?
178
+ └─ ワークフロー設定をカスタマイズするか?
179
+
180
+ [設定内容の確認表示]
181
+
182
+ [保存確認]
183
+
184
+ [~/.michi/ディレクトリ作成]
185
+
186
+ [~/.michi/config.json 保存]
187
+
188
+ [バリデーション]
189
+
190
+ [完了]
191
+ ```
192
+
193
+ ### 2.2 設定ファイルと設定項目
194
+
195
+ #### 2.2.1 設定ファイルの一覧
196
+
197
+ | ファイル | パス | 役割 | 作成コマンド |
198
+ |---------|------|------|------------|
199
+ | **グローバル設定** | `~/.michi/config.json` | Confluence/JIRA/ワークフロー設定 | `config:global` |
200
+ | **プロジェクト設定** | `.michi/config.json` | プロジェクト固有のオーバーライド | `init` (optional) |
201
+ | **プロジェクトメタデータ** | `.kiro/project.json` | プロジェクトID、名前、JIRA キーなど | `init`, `setup-existing` |
202
+ | **環境変数** | `.env` | 認証情報、プロジェクト固有の環境変数 | `init`, `setup-existing` |
203
+
204
+ #### 2.2.2 設定項目の完全一覧 (51項目)
205
+
206
+ **A. ~/.michi/config.json (15項目)**
207
+
208
+ | 項目 | 型 | 必須 | デフォルト | 説明 |
209
+ |------|-----|------|-----------|------|
210
+ | `confluence.pageCreationGranularity` | enum | No | `'single'` | ページ作成粒度 (`'single'` \| `'by-section'` \| `'by-hierarchy'` \| `'manual'`) |
211
+ | `confluence.pageTitleFormat` | string | No | - | ページタイトル形式 (例: `{projectName} - {featureName}`) |
212
+ | `confluence.hierarchy.mode` | enum | No | - | 階層モード (`'simple'` \| `'nested'`) |
213
+ | `confluence.hierarchy.parentPageTitle` | string | No | - | 親ページタイトル形式 |
214
+ | `confluence.hierarchy.structure` | object | No | - | カスタム階層構造 |
215
+ | `jira.createEpic` | boolean | No | `true` | Epic作成有無 |
216
+ | `jira.storyCreationGranularity` | enum | No | `'all'` | Story作成粒度 (`'all'` \| `'by-phase'` \| `'selected-phases'`) |
217
+ | `jira.selectedPhases` | array | No | - | 選択フェーズ(`storyCreationGranularity='selected-phases'` の場合) |
218
+ | `jira.storyPoints` | enum | No | `'auto'` | Story Points設定 (`'auto'` \| `'manual'` \| `'disabled'`) |
219
+ | `workflow.enabledPhases` | array | Yes | `['requirements', 'design', 'tasks']` | 有効フェーズ |
220
+ | `workflow.approvalGates.requirements` | array | No | - | 要件定義フェーズの承認者 |
221
+ | `workflow.approvalGates.design` | array | No | - | 設計フェーズの承認者 |
222
+ | `workflow.approvalGates.release` | array | No | - | リリースフェーズの承認者 |
223
+
224
+ **B. .kiro/project.json (10項目)**
225
+
226
+ | 項目 | 型 | 必須 | 例 | 説明 |
227
+ |------|-----|------|-----|------|
228
+ | `projectId` | string | Yes | `'my-project'` | プロジェクトID |
229
+ | `projectName` | string | Yes | `'マイプロジェクト'` | プロジェクト名 |
230
+ | `language` | enum | Yes | `'ja'` | ドキュメント言語 (`'ja'` \| `'en'`) |
231
+ | `jiraProjectKey` | string | Yes | `'MYPRJ'` | JIRAプロジェクトキー |
232
+ | `confluenceLabels` | array | Yes | `['project:my-project']` | Confluenceラベル |
233
+ | `status` | string | Yes | `'active'` | プロジェクトステータス |
234
+ | `team` | array | No | `[]` | チームメンバー |
235
+ | `stakeholders` | array | No | `['@企画', '@部長']` | ステークホルダー |
236
+ | `repository` | string | Yes | `'https://github.com/org/repo'` | リポジトリURL |
237
+ | `description` | string | Yes | `'プロジェクトの説明'` | プロジェクト説明 |
238
+
239
+ **C. .env (11項目)**
240
+
241
+ | 項目 | 型 | 必須 | 例 | 説明 | スコープ |
242
+ |------|-----|------|-----|------|----------|
243
+ | `ATLASSIAN_URL` | string | Yes | `'https://org.atlassian.net'` | AtlassianベースURL | 組織 |
244
+ | `ATLASSIAN_EMAIL` | string | Yes | `'user@company.com'` | Atlassian認証用メールアドレス | 組織 |
245
+ | `ATLASSIAN_API_TOKEN` | string | Yes | `'token123'` | Atlassian APIトークン | 組織 |
246
+ | `GITHUB_ORG` | string | Yes | `'my-org'` | GitHub組織名 | 組織 |
247
+ | `GITHUB_TOKEN` | string | Yes | `'ghp_xxx'` | GitHubアクセストークン | 組織 |
248
+ | `CONFLUENCE_PRD_SPACE` | string | No | `'PRD'` | 要件定義書スペース | 組織 |
249
+ | `CONFLUENCE_QA_SPACE` | string | No | `'QA'` | テスト仕様書スペース | 組織 |
250
+ | `CONFLUENCE_RELEASE_SPACE` | string | No | `'RELEASE'` | リリースノートスペース | 組織 |
251
+ | `JIRA_PROJECT_KEYS` | string | Yes | `'MYPRJ'` | JIRAプロジェクトキー | プロジェクト |
252
+ | `JIRA_ISSUE_TYPE_STORY` | string | Yes | `'10036'` | Story Issue Type ID | 組織 |
253
+ | `JIRA_ISSUE_TYPE_SUBTASK` | string | Yes | `'10037'` | Subtask Issue Type ID | 組織 |
254
+
255
+ **注**: `GITHUB_REPO` は削除されました。リポジトリ情報は `.kiro/project.json` の `repository` フィールドから自動的に抽出されます。
256
+
257
+ ### 2.3 データフロー図
258
+
259
+ **現状のデータフロー:**
260
+
261
+ ```
262
+ [ユーザー入力]
263
+ ├─ config:global
264
+ │ └─ ~/.michi/config.json (Confluence/JIRA/ワークフロー設定)
265
+
266
+ ├─ init / setup-existing
267
+ │ ├─ .kiro/project.json (プロジェクトメタデータ)
268
+ │ ├─ .env (全環境変数)
269
+ │ └─ .michi/config.json (プロジェクト固有設定、optional)
270
+
271
+ └─ [既存の.envを手動編集]
272
+
273
+ [設定の読み込み]
274
+ ├─ スクリプト実行時
275
+ │ ├─ dotenv.config() で .env を読み込み
276
+ │ ├─ .michi/config.json を読み込み (存在する場合)
277
+ │ └─ ~/.michi/config.json を読み込み (存在する場合)
278
+
279
+ └─ 優先順位が不明確(明示的なマージロジックなし)
280
+ ```
281
+
282
+ **問題点:**
283
+ 1. グローバル設定の自動読み込みがない
284
+ 2. 設定の優先順位が不明確
285
+ 3. .env に組織レベルの設定が分散
286
+ 4. 各スクリプトが独自に設定を読み込み(一元化されていない)
287
+
288
+ ---
289
+
290
+ ## 3. 問題点の特定
291
+
292
+ ### 3.1 重複する対話的プロンプト
293
+
294
+ **現状:**
295
+ - `init` と `setup-existing` の両方で、以下の情報を対話的に取得:
296
+ - `projectName`
297
+ - `jiraKey`
298
+ - `environment`
299
+ - `langCode`
300
+
301
+ **問題:**
302
+ - ユーザー体験の低下(同じ情報を複数回入力)
303
+ - コードの重複(同じプロンプトロジックが2箇所に存在)
304
+ - 保守性の低下(変更時に2箇所を修正する必要)
305
+
306
+ **影響範囲:**
307
+ - src/commands/init.ts:195-242
308
+ - src/commands/setup-existing.ts:180-229
309
+
310
+ ### 3.2 グローバル化できる項目の分散
311
+
312
+ **現状:**
313
+ `.env` ファイルに以下の組織レベルの設定が含まれている:
314
+
315
+ | 項目 | スコープ | 変更頻度 |
316
+ |------|----------|----------|
317
+ | `ATLASSIAN_URL` | 組織 | 低 |
318
+ | `ATLASSIAN_EMAIL` | 組織/ユーザー | 低 |
319
+ | `ATLASSIAN_API_TOKEN` | 組織/ユーザー | 低 |
320
+ | `GITHUB_ORG` | 組織 | 低 |
321
+ | `GITHUB_TOKEN` | 組織/ユーザー | 低 |
322
+ | `CONFLUENCE_*_SPACE` | 組織 | 低 |
323
+ | `JIRA_ISSUE_TYPE_*` | 組織 | 低 |
324
+
325
+ **問題:**
326
+ - プロジェクトごとに同じ情報を重複入力
327
+ - 組織の設定変更時に全プロジェクトの .env を更新する必要
328
+ - セキュリティリスク(認証情報が各プロジェクトに分散)
329
+
330
+ **影響:**
331
+ - 新規プロジェクト作成時の手間が大きい
332
+ - 設定の一貫性が保たれにくい
333
+ - パーミッション管理が煩雑
334
+
335
+ ### 3.3 コマンドの使い分けの不明瞭さ
336
+
337
+ **現状:**
338
+ 3つのコマンドの使い分けが不明確:
339
+
340
+ | コマンド | 用途 | 実行タイミング | ユーザーの理解度 |
341
+ |---------|------|--------------|----------------|
342
+ | `config:global` | グローバル設定 | 初回のみ(組織で一度) | 低 (いつ使うべきか不明) |
343
+ | `init` | 新規プロジェクト | プロジェクト作成時 | 中 |
344
+ | `setup-existing` | 既存プロジェクト | 既存プロジェクトに追加 | 中 |
345
+
346
+ **問題:**
347
+ - ドキュメントを読まないと使い分けが分からない
348
+ - `init` と `setup-existing` の違いが微妙(内部実装はほぼ同じ)
349
+ - `config:global` がオプション扱いで、重要性が伝わらない
350
+
351
+ **ユーザーの混乱例:**
352
+ 1. 「config:global を実行せずに init を実行 → .env の手動編集が必要に」
353
+ 2. 「新規プロジェクトで setup-existing を使用 → 問題なく動作するが、推奨ではない」
354
+ 3. 「各プロジェクトで .env を個別に編集 → 組織設定の一元管理ができていない」
355
+
356
+ ### 3.4 ドキュメントのタイポ
357
+
358
+ **問題:**
359
+ `docs/user-guide/getting-started/quick-start.md:35` に以下のタイポ:
360
+
361
+ ```markdown
362
+ npx @sk8metal/michi-cli setup-existin
363
+ ```
364
+
365
+ 正しくは:
366
+ ```markdown
367
+ npx @sk8metal/michi-cli setup-existing
368
+ ```
369
+
370
+ **影響:**
371
+ - ユーザーがコマンドをコピー&ペーストした際にエラー
372
+ - ドキュメントの信頼性低下
373
+
374
+ ---
375
+
376
+ ## 4. 解決策の提案
377
+
378
+ ### 4.1 3層の設定階層
379
+
380
+ 新しいアーキテクチャでは、設定を3つのレイヤーに分離します:
381
+
382
+ ```
383
+ Layer 1: グローバル設定 (~/.michi/)
384
+ ├─ config.json - Confluence/JIRA/ワークフロー設定
385
+ └─ .env - 認証情報・組織共通設定
386
+ ↓ (低優先度)
387
+
388
+ Layer 2: プロジェクト設定 (.michi/)
389
+ └─ config.json - プロジェクト固有のオーバーライド(optional)
390
+ ↓ (中優先度)
391
+
392
+ Layer 3: プロジェクトメタデータ (.kiro/, .env)
393
+ ├─ .kiro/project.json - projectId, projectName, jiraProjectKey
394
+ └─ .env - プロジェクト固有の環境変数のみ
395
+ ↓ (高優先度)
396
+
397
+ [マージされた設定]
398
+ ```
399
+
400
+ **利点:**
401
+ 1. **明確な階層**: どのレベルで設定を管理すべきか明確
402
+ 2. **設定の共有**: グローバル設定は全プロジェクトで自動的に共有
403
+ 3. **柔軟なオーバーライド**: プロジェクト固有の要件にも対応可能
404
+
405
+ ### 4.2 設定項目の再分類
406
+
407
+ **Category A: 組織レベル (グローバル設定)**
408
+
409
+ | 項目 | ファイル | 説明 |
410
+ |------|---------|------|
411
+ | `ATLASSIAN_URL` | `~/.michi/.env` | AtlassianベースURL |
412
+ | `ATLASSIAN_EMAIL` | `~/.michi/.env` | Atlassian認証用メールアドレス |
413
+ | `ATLASSIAN_API_TOKEN` | `~/.michi/.env` | Atlassian APIトークン |
414
+ | `GITHUB_ORG` | `~/.michi/.env` | GitHub組織名 |
415
+ | `GITHUB_TOKEN` | `~/.michi/.env` | GitHubアクセストークン |
416
+ | `CONFLUENCE_PRD_SPACE` | `~/.michi/.env` | 要件定義書スペース |
417
+ | `CONFLUENCE_QA_SPACE` | `~/.michi/.env` | テスト仕様書スペース |
418
+ | `CONFLUENCE_RELEASE_SPACE` | `~/.michi/.env` | リリースノートスペース |
419
+ | `JIRA_ISSUE_TYPE_STORY` | `~/.michi/.env` | Story Issue Type ID |
420
+ | `JIRA_ISSUE_TYPE_SUBTASK` | `~/.michi/.env` | Subtask Issue Type ID |
421
+ | `confluence.*` | `~/.michi/config.json` | Confluence設定(pageCreationGranularity等) |
422
+ | `jira.*` | `~/.michi/config.json` | JIRA設定(createEpic等) |
423
+ | `workflow.*` | `~/.michi/config.json` | ワークフロー設定 |
424
+
425
+ **Category B: プロジェクトレベル (プロジェクト設定)**
426
+
427
+ | 項目 | ファイル | 説明 |
428
+ |------|---------|------|
429
+ | `projectId` | `.kiro/project.json` | プロジェクトID |
430
+ | `projectName` | `.kiro/project.json` | プロジェクト名 |
431
+ | `jiraProjectKey` | `.kiro/project.json` | JIRAプロジェクトキー |
432
+ | `JIRA_PROJECT_KEYS` | `.env` | JIRAプロジェクトキー |
433
+ | `language` | `.kiro/project.json` | ドキュメント言語 |
434
+ | `confluenceLabels` | `.kiro/project.json` | Confluenceラベル |
435
+ | `repository` | `.kiro/project.json` | リポジトリURL(ConfigLoaderが自動的に org/repo 形式に変換) |
436
+ | `description` | `.kiro/project.json` | プロジェクト説明 |
437
+ | `confluence.*` | `.michi/config.json` (optional) | プロジェクト固有のオーバーライド |
438
+ | `jira.*` | `.michi/config.json` (optional) | プロジェクト固有のオーバーライド |
439
+ | `workflow.*` | `.michi/config.json` (optional) | プロジェクト固有のオーバーライド |
440
+
441
+ **注**: `repository` から `org/repo` 形式が必要な場合、ConfigLoaderが自動的にパースして提供します。
442
+
443
+ ### 4.3 コマンド統一案
444
+
445
+ **新しいコマンド構成:**
446
+
447
+ 1. **`michi config:global`** (初回のみ、組織で一度)
448
+ - ~/.michi/config.json 作成
449
+ - ~/.michi/.env 作成(認証情報を安全に保存)
450
+ - 全プロジェクトで共通の設定を一元管理
451
+
452
+ 2. **`michi init`** (新規・既存プロジェクト統一)
453
+ - `--existing` フラグで既存プロジェクトモードを切り替え
454
+ - 自動検出機能により、既存プロジェクトを自動判別
455
+ - プロジェクトメタデータのみ対話的に取得
456
+ - グローバル設定を自動参照
457
+
458
+ 3. **`michi migrate`** (既存ユーザー向け)
459
+ - 既存の .env を新形式に自動変換
460
+ - バックアップを作成して安全に移行
461
+
462
+ 4. **`setup-existing` の即時非推奨化**
463
+ - v0.5.0以降、setup-existing は警告を表示して `michi init --existing` に委譲
464
+ - 完全な後方互換性を維持しつつ、新しいコマンドへの移行を促す
465
+ - コマンド実行時に明確な移行メッセージを表示
466
+
467
+ **michi init vs michi init --existing の違い:**
468
+
469
+ | 項目 | `michi init` | `michi init --existing` |
470
+ |------|-------------|------------------------|
471
+ | **用途** | 新規プロジェクトの作成 | 既存プロジェクトにMichiを追加 |
472
+ | **プロジェクトID** | 対話的に入力を要求 | ディレクトリ名から自動生成 |
473
+ | **リポジトリURL** | Git設定から取得、なければ対話的入力 | Git設定から取得(必須) |
474
+ | **.gitディレクトリ** | なくてもOK(警告のみ) | 必須(なければエラー) |
475
+ | **既存ファイルの扱い** | .envが存在する場合は警告 | .envが存在する場合はマージ |
476
+ | **テンプレート** | すべてのテンプレートをコピー | 必要なテンプレートのみ追加 |
477
+ | **自動検出** | 既存プロジェクトを検出した場合、--existing モードを提案 | - |
478
+
479
+ **プロジェクトID自動生成の具体例:**
480
+
481
+ `michi init --existing` を実行した際、プロジェクトIDはカレントディレクトリ名から自動生成されます:
482
+
483
+ ```bash
484
+ # 例1: Node.jsプロジェクト
485
+ $ pwd
486
+ /Users/username/Work/git/my-awesome-project
487
+
488
+ $ michi init --existing
489
+ # → projectId: "my-awesome-project" として自動設定
490
+
491
+ # 例2: Javaプロジェクト
492
+ $ pwd
493
+ /home/developer/projects/ecommerce-api
494
+
495
+ $ michi init --existing
496
+ # → projectId: "ecommerce-api" として自動設定
497
+
498
+ # 例3: PHPプロジェクト
499
+ $ pwd
500
+ /var/www/customer-portal
501
+
502
+ $ michi init --existing
503
+ # → projectId: "customer-portal" として自動設定
504
+ ```
505
+
506
+ **注**: 自動生成された projectId は、対話的プロンプトで確認され、必要に応じて変更可能です。
507
+
508
+ **自動検出ロジック:**
509
+
510
+ `michi init` 実行時、以下のファイル/ディレクトリが存在する場合、自動的に既存プロジェクトと判断:
511
+ - `.git/` ディレクトリ
512
+ - `package.json` (Node.js)
513
+ - `pom.xml` または `build.gradle` (Java)
514
+ - `composer.json` (PHP)
515
+
516
+ 検出された場合、以下のプロンプトを表示:
517
+ ```
518
+ ⚠️ 既存のプロジェクトが検出されました
519
+ 既存プロジェクトモードで初期化しますか? (Y/n)
520
+ ```
521
+
522
+ **使い方のシンプル化:**
523
+
524
+ ```bash
525
+ # 1. グローバル設定(初回のみ、組織で一度)
526
+ michi config:global
527
+
528
+ # 2a. 新規プロジェクト初期化
529
+ cd /path/to/new-project
530
+ michi init
531
+
532
+ # 2b. 既存プロジェクトにMichiを追加
533
+ cd /path/to/existing-project
534
+ michi init --existing
535
+ # または、自動検出により michi init でもOK(確認プロンプトが表示される)
536
+
537
+ # 3. (非推奨) 既存の setup-existing も引き続き動作(警告付き)
538
+ npx @sk8metal/michi-cli setup-existing
539
+ # → 警告: "このコマンドは非推奨です。michi init --existing を使用してください"
540
+ ```
541
+
542
+ ---
543
+
544
+ ## 5. 新アーキテクチャ
545
+
546
+ ### 5.1 ファイル構成
547
+
548
+ ```
549
+ ~/.michi/ # グローバル設定ディレクトリ
550
+ ├── config.json # Confluence/JIRA/ワークフロー設定
551
+ └── .env # 認証情報・組織共通設定 (chmod 600)
552
+
553
+ <project-root>/
554
+ ├── .michi/
555
+ │ └── config.json # プロジェクト固有のオーバーライド(optional)
556
+ ├── .kiro/
557
+ │ ├── project.json # プロジェクトメタデータ
558
+ │ ├── settings/
559
+ │ ├── steering/
560
+ │ └── specs/
561
+ └── .env # プロジェクト固有の環境変数 (chmod 600)
562
+ ```
563
+
564
+ ### 5.2 各ファイルの内容例
565
+
566
+ #### 5.2.1 `~/.michi/config.json`
567
+
568
+ ```json
569
+ {
570
+ "version": "0.5.0",
571
+ "confluence": {
572
+ "pageCreationGranularity": "single",
573
+ "pageTitleFormat": "{projectName} - {featureName}",
574
+ "hierarchy": {
575
+ "mode": "simple",
576
+ "parentPageTitle": "[{projectName}] {featureName}"
577
+ }
578
+ },
579
+ "jira": {
580
+ "createEpic": true,
581
+ "storyCreationGranularity": "all",
582
+ "storyPoints": "auto"
583
+ },
584
+ "workflow": {
585
+ "enabledPhases": ["requirements", "design", "tasks"],
586
+ "approvalGates": {
587
+ "requirements": ["pm", "director"],
588
+ "design": ["architect", "director"],
589
+ "release": ["sm", "director"]
590
+ }
591
+ }
592
+ }
593
+ ```
594
+
595
+ #### 5.2.2 `~/.michi/.env`
596
+
597
+ ```bash
598
+ # Atlassian認証
599
+ ATLASSIAN_URL=https://your-org.atlassian.net
600
+ ATLASSIAN_EMAIL=your-email@company.com
601
+ ATLASSIAN_API_TOKEN=your-api-token
602
+
603
+ # GitHub認証
604
+ GITHUB_ORG=your-org
605
+ GITHUB_TOKEN=ghp_xxx
606
+
607
+ # Confluence共有スペース
608
+ CONFLUENCE_PRD_SPACE=PRD
609
+ CONFLUENCE_QA_SPACE=QA
610
+ CONFLUENCE_RELEASE_SPACE=RELEASE
611
+
612
+ # JIRA Issue Type IDs(組織固有)
613
+ JIRA_ISSUE_TYPE_STORY=10036
614
+ JIRA_ISSUE_TYPE_SUBTASK=10037
615
+ ```
616
+
617
+ #### 5.2.3 `.kiro/project.json`
618
+
619
+ ```json
620
+ {
621
+ "projectId": "my-project",
622
+ "projectName": "マイプロジェクト",
623
+ "language": "ja",
624
+ "jiraProjectKey": "MYPRJ",
625
+ "confluenceLabels": ["project:my-project"],
626
+ "status": "active",
627
+ "team": [],
628
+ "stakeholders": ["@企画", "@部長"],
629
+ "repository": "https://github.com/org/my-project",
630
+ "description": "マイプロジェクトの開発"
631
+ }
632
+ ```
633
+
634
+ #### 5.2.4 `.env` (新形式)
635
+
636
+ ```bash
637
+ # プロジェクト固有の環境変数のみ
638
+ JIRA_PROJECT_KEYS=MYPRJ
639
+
640
+ # (必要に応じて) プロジェクト固有のオーバーライド
641
+ # CONFLUENCE_PRD_SPACE=CUSTOM_PRD
642
+
643
+ # 注: GITHUB_REPO は不要です
644
+ # リポジトリ情報は .kiro/project.json の repository から自動的に抽出されます
645
+ ```
646
+
647
+ #### 5.2.5 `.michi/config.json` (optional)
648
+
649
+ ```json
650
+ {
651
+ "confluence": {
652
+ "pageCreationGranularity": "by-hierarchy"
653
+ },
654
+ "jira": {
655
+ "storyPoints": "manual"
656
+ }
657
+ }
658
+ ```
659
+
660
+ ### 5.3 設定の読み込み順序と優先度
661
+
662
+ **読み込み順序(優先度: 低 → 高):**
663
+
664
+ 1. `~/.michi/.env` (組織レベルの環境変数)
665
+ 2. `~/.michi/config.json` (組織レベルの設定)
666
+ 3. `.kiro/project.json` (プロジェクトメタデータ)
667
+ 4. `.michi/config.json` (プロジェクト固有のオーバーライド)
668
+ 5. `.env` (プロジェクト固有の環境変数)
669
+
670
+ **マージロジック:**
671
+
672
+ - 後に読み込まれた設定が、前の設定を上書き
673
+ - オブジェクトはディープマージ
674
+ - 配列は完全置換(マージしない)
675
+
676
+ **例:**
677
+
678
+ ```javascript
679
+ // ~/.michi/config.json
680
+ {
681
+ "confluence": {
682
+ "pageCreationGranularity": "single",
683
+ "pageTitleFormat": "{projectName}"
684
+ }
685
+ }
686
+
687
+ // .michi/config.json (プロジェクト固有)
688
+ {
689
+ "confluence": {
690
+ "pageCreationGranularity": "by-hierarchy"
691
+ }
692
+ }
693
+
694
+ // マージ結果
695
+ {
696
+ "confluence": {
697
+ "pageCreationGranularity": "by-hierarchy", // プロジェクト設定で上書き
698
+ "pageTitleFormat": "{projectName}" // グローバル設定を継承
699
+ }
700
+ }
701
+ ```
702
+
703
+ ### 5.4 各コマンドの新しい動作フロー
704
+
705
+ #### 5.4.1 `michi config:global`
706
+
707
+ ```
708
+ [開始]
709
+
710
+ [既存のグローバル設定を確認]
711
+ ├─ ~/.michi/config.json の存在確認
712
+ └─ ~/.michi/.env の存在確認
713
+
714
+ [存在する場合]
715
+
716
+ [上書き確認]
717
+ ├─ No → [終了]
718
+ └─ Yes → [続行]
719
+
720
+ [対話的に設定を取得]
721
+ ├─ Atlassian認証情報
722
+ │ ├─ ATLASSIAN_URL
723
+ │ ├─ ATLASSIAN_EMAIL
724
+ │ └─ ATLASSIAN_API_TOKEN
725
+ ├─ GitHub認証情報
726
+ │ ├─ GITHUB_ORG
727
+ │ └─ GITHUB_TOKEN
728
+ ├─ Confluenceスペース設定
729
+ │ ├─ CONFLUENCE_PRD_SPACE
730
+ │ ├─ CONFLUENCE_QA_SPACE
731
+ │ └─ CONFLUENCE_RELEASE_SPACE
732
+ ├─ JIRA Issue Type IDs
733
+ │ ├─ JIRA_ISSUE_TYPE_STORY
734
+ │ └─ JIRA_ISSUE_TYPE_SUBTASK
735
+ ├─ Confluence設定
736
+ │ ├─ pageCreationGranularity
737
+ │ ├─ pageTitleFormat (optional)
738
+ │ └─ hierarchy (optional)
739
+ ├─ JIRA設定
740
+ │ ├─ createEpic
741
+ │ ├─ storyCreationGranularity
742
+ │ ├─ selectedPhases (optional)
743
+ │ └─ storyPoints
744
+ └─ ワークフロー設定
745
+ ├─ enabledPhases
746
+ └─ approvalGates (optional)
747
+
748
+ [設定内容の確認表示]
749
+
750
+ [保存確認]
751
+ ├─ No → [終了]
752
+ └─ Yes → [続行]
753
+
754
+ [~/.michi/ディレクトリ作成]
755
+
756
+ [~/.michi/config.json 保存]
757
+
758
+ [~/.michi/.env 保存]
759
+ └─ chmod 600 (セキュリティ)
760
+
761
+ [バリデーション実行]
762
+
763
+ [完了メッセージ表示]
764
+ ├─ 作成されたファイルの一覧
765
+ ├─ セキュリティに関する注意事項
766
+ │ └─ ~/.michi/.env と <project>/.env の違いを明記
767
+ └─ 次のステップ(michi init の実行)
768
+ ```
769
+
770
+ #### 5.4.2 `michi init`
771
+
772
+ ```
773
+ [開始]
774
+
775
+ [オプション解析]
776
+ └─ --existing フラグの有無を確認
777
+
778
+ [グローバル設定の存在確認]
779
+ ├─ ~/.michi/config.json
780
+ └─ ~/.michi/.env
781
+
782
+ [グローバル設定がない場合]
783
+
784
+ [警告表示]
785
+ 「グローバル設定が未作成です」
786
+ 「michi config:global を先に実行することを推奨します」
787
+ 「組織共通の認証情報(Atlassian, GitHub)を全プロジェクトで共有できます」
788
+
789
+ [続行確認]
790
+ ├─ No → [終了]
791
+ └─ Yes → [後方互換モードで続行]
792
+
793
+ [environment の決定]
794
+ ├─ --cursor, --claude 等のフラグをチェック
795
+ ├─ 環境変数 (CLAUDE_CODE=1 等) をチェック
796
+ ├─ 自動検出を試みる
797
+ └─ 対話的プロンプト (自動検出できない場合)
798
+
799
+ [プロジェクトメタデータの対話的取得]
800
+ ├─ projectId
801
+ │ └─ --existing の場合はディレクトリ名を使用
802
+ ├─ projectName
803
+ ├─ jiraProjectKey
804
+ └─ language (デフォルト: ja)
805
+
806
+ [設定内容の確認表示]
807
+ ├─ projectId
808
+ ├─ projectName
809
+ ├─ jiraProjectKey
810
+ ├─ environment
811
+ ├─ language
812
+ └─ (グローバル設定が読み込まれることを明示)
813
+
814
+ [続行確認]
815
+ ├─ No → [終了]
816
+ └─ Yes → [続行]
817
+
818
+ [リポジトリルートの検出]
819
+ ├─ .git ディレクトリを探索
820
+ └─ 見つからない場合は警告(Gitリポジトリでない)
821
+
822
+ [.kiro/ ディレクトリ構造作成]
823
+ ├─ .kiro/settings/templates/
824
+ ├─ .kiro/steering/
825
+ └─ .kiro/specs/
826
+
827
+ [.kiro/project.json 作成]
828
+ ├─ projectId, projectName, jiraProjectKey
829
+ ├─ repository URL (git config から取得)
830
+ ├─ confluenceLabels (自動生成)
831
+ └─ 他のメタデータ
832
+
833
+ [.env 作成]
834
+ ├─ 既存の .env をチェック
835
+ │ └─ 存在する場合: マージ(既存値を優先)
836
+ ├─ プロジェクト固有設定のみを追加
837
+ │ ├─ GITHUB_REPO (git config から取得)
838
+ │ └─ JIRA_PROJECT_KEYS
839
+ └─ テンプレートコメントを追加
840
+
841
+ [テンプレート/ルールのコピー]
842
+ ├─ 環境別テンプレート
843
+ │ └─ cursor/claude/gemini/codex/cline
844
+ ├─ Steeringテンプレート
845
+ ├─ Specテンプレート
846
+ └─ cc-sdd オーバーライド
847
+
848
+ [環境別の追加処理]
849
+ ├─ Claude環境
850
+ │ └─ スキル/サブエージェントのインストール
851
+ ├─ Codex環境
852
+ │ └─ cc-sdd インストールプロンプト
853
+ └─ 他の環境
854
+ └─ 環境固有の処理
855
+
856
+ [.gitignore 更新]
857
+ ├─ .env エントリの追加
858
+ └─ 既に含まれている場合はスキップ
859
+
860
+ [バリデーション実行]
861
+ ├─ 設定ファイルのスキーマチェック
862
+ ├─ 必須ファイルの存在確認
863
+ └─ パーミッションのチェック (.env = 600)
864
+
865
+ [完了メッセージ表示]
866
+ ├─ 作成されたファイルの一覧
867
+ ├─ 環境別の次のステップ
868
+ └─ ドキュメントへのリンク
869
+ ```
870
+
871
+ #### 5.4.3 `michi migrate`
872
+
873
+ ```
874
+ [開始]
875
+
876
+ [現在の設定を検出]
877
+ ├─ ~/.michi/config.json の存在確認
878
+ ├─ ~/.michi/.env の存在確認(新形式)
879
+ ├─ ~/.michi/global.env の存在確認(旧形式、マイグレーション対象)
880
+ ├─ .michi/config.json の存在確認
881
+ ├─ .kiro/project.json の存在確認
882
+ └─ .env の存在確認
883
+
884
+ [検出された設定の表示]
885
+ ├─ ✓ ~/.michi/config.json
886
+ ├─ ✓ .kiro/project.json
887
+ └─ ✓ .env
888
+
889
+ [移行内容の説明]
890
+ 1. .env から組織共通設定を抽出 → ~/.michi/.env
891
+ 2. プロジェクト固有設定のみを .env に残す
892
+ 3. 既存の ~/.michi/global.env を ~/.michi/.env にリネーム(存在する場合)
893
+ 4. 既存の設定ファイルはすべてバックアップを作成
894
+
895
+ [実行確認]
896
+ ├─ No → [終了]
897
+ └─ Yes → [続行]
898
+
899
+ [バックアップ作成]
900
+ ├─ .michi-backup-YYYYMMDDHHMMSS/ ディレクトリ作成
901
+ ├─ 既存の設定ファイルをコピー
902
+ └─ 成功メッセージ表示
903
+
904
+ [.env を分析]
905
+ ├─ 全環境変数を読み込み
906
+ ├─ 組織共通設定を抽出
907
+ │ └─ ATLASSIAN_*, GITHUB_ORG, GITHUB_TOKEN, CONFLUENCE_*, JIRA_ISSUE_TYPE_*
908
+ └─ プロジェクト固有設定を抽出
909
+ └─ GITHUB_REPO, JIRA_PROJECT_KEYS
910
+
911
+ [組織共通設定の項目数を表示]
912
+ └─ "✓ 組織共通設定: N 項目"
913
+
914
+ [プロジェクト固有設定の項目数を表示]
915
+ └─ "✓ プロジェクト固有設定: M 項目"
916
+
917
+ [旧形式からのマイグレーション]
918
+ ├─ ~/.michi/global.env が存在する場合
919
+ │ └─ ~/.michi/.env にリネーム
920
+ └─ 成功メッセージ表示
921
+
922
+ [~/.michi/.env 作成または更新]
923
+ ├─ 既存の ~/.michi/.env をチェック
924
+ │ ├─ 存在する場合: 上書き確認
925
+ │ └─ 存在しない場合: 新規作成
926
+ ├─ 組織共通設定を書き込み
927
+ ├─ chmod 600 (セキュリティ)
928
+ └─ 成功メッセージ表示
929
+
930
+ [.env を更新]
931
+ ├─ プロジェクト固有設定のみを書き込み
932
+ ├─ コメントを追加
933
+ │ └─ 「組織共通設定は ~/.michi/.env を参照」
934
+ │ └─ 「このファイルにはプロジェクト固有の設定のみを記載してください」
935
+ └─ 成功メッセージ表示
936
+
937
+ [バリデーション実行]
938
+ ├─ ConfigLoader で設定を読み込み
939
+ ├─ 必須項目のチェック
940
+ └─ エラーがあれば表示
941
+
942
+ [完了メッセージ表示]
943
+ ├─ 変更内容のサマリー
944
+ ├─ バックアップの場所
945
+ └─ 次のステップ
946
+ ```
947
+
948
+ ---
949
+
950
+ ## 6. 実装詳細
951
+
952
+ ### 6.1 ConfigLoader クラス設計
953
+
954
+ ConfigLoaderは、複数の設定ファイルを読み込み、優先順位に従ってマージし、型安全なアクセスを提供するクラスです。
955
+
956
+ **ファイルパス:** `scripts/utils/config-loader-v2.ts`
957
+
958
+ **責務:**
959
+ 1. 複数の設定ファイルを読み込み
960
+ 2. 優先順位に従ってマージ
961
+ 3. バリデーション
962
+ 4. 型安全なアクセス提供
963
+ 5. キャッシュによるパフォーマンス向上
964
+
965
+ **クラス定義:**
966
+
967
+ ```typescript
968
+ import { z } from 'zod';
969
+ import { existsSync, readFileSync } from 'fs';
970
+ import { join } from 'path';
971
+ import * as dotenv from 'dotenv';
972
+
973
+ /**
974
+ * 設定の読み込み元
975
+ */
976
+ type ConfigSource =
977
+ | 'global-env' // ~/.michi/global.env
978
+ | 'global-config' // ~/.michi/config.json
979
+ | 'project-meta' // .kiro/project.json
980
+ | 'project-config' // .michi/config.json
981
+ | 'project-env'; // .env
982
+
983
+ /**
984
+ * 読み込まれた設定(ソース情報付き)
985
+ */
986
+ interface ConfigWithSource<T> {
987
+ value: T;
988
+ source: ConfigSource;
989
+ }
990
+
991
+ /**
992
+ * 統合設定
993
+ */
994
+ interface MergedConfig {
995
+ // Atlassian認証
996
+ atlassian: {
997
+ url: string;
998
+ email: string;
999
+ apiToken: string;
1000
+ };
1001
+
1002
+ // GitHub設定
1003
+ github: {
1004
+ org: string; // 組織名(グローバル設定から)
1005
+ token: string; // アクセストークン(グローバル設定から)
1006
+ repository: string; // フルURL(project.jsonから)
1007
+ repositoryShort: string; // "org/repo" 形式(自動抽出)
1008
+ repositoryOrg: string; // リポジトリの組織名(自動抽出)
1009
+ repositoryName: string; // リポジトリ名(自動抽出)
1010
+ };
1011
+
1012
+ // Confluenceスペース
1013
+ confluence: {
1014
+ spaces: {
1015
+ prd: string;
1016
+ qa: string;
1017
+ release: string;
1018
+ };
1019
+ // 設定
1020
+ pageCreationGranularity: string;
1021
+ pageTitleFormat?: string;
1022
+ hierarchy?: unknown;
1023
+ };
1024
+
1025
+ // JIRA設定
1026
+ jira: {
1027
+ issueTypes: {
1028
+ story: string;
1029
+ subtask: string;
1030
+ };
1031
+ projectKeys: string; // プロジェクト固有
1032
+ createEpic: boolean;
1033
+ storyCreationGranularity: string;
1034
+ selectedPhases?: string[];
1035
+ storyPoints: string;
1036
+ };
1037
+
1038
+ // ワークフロー設定
1039
+ workflow: {
1040
+ enabledPhases: string[];
1041
+ approvalGates?: {
1042
+ requirements?: string[];
1043
+ design?: string[];
1044
+ release?: string[];
1045
+ };
1046
+ };
1047
+
1048
+ // プロジェクトメタデータ
1049
+ project: {
1050
+ id: string;
1051
+ name: string;
1052
+ language: string;
1053
+ jiraProjectKey: string;
1054
+ confluenceLabels: string[];
1055
+ status: string;
1056
+ team: string[];
1057
+ stakeholders: string[];
1058
+ repository: string;
1059
+ description: string;
1060
+ };
1061
+ }
1062
+
1063
+ /**
1064
+ * 読み込みオプション
1065
+ */
1066
+ interface LoadOptions {
1067
+ projectRoot?: string; // プロジェクトルート(デフォルト: process.cwd())
1068
+ skipValidation?: boolean; // バリデーションをスキップ
1069
+ useCache?: boolean; // キャッシュを使用(デフォルト: true)
1070
+ }
1071
+
1072
+ /**
1073
+ * 設定ローダー
1074
+ */
1075
+ export class ConfigLoader {
1076
+ private cache: MergedConfig | null = null;
1077
+ private cacheTimestamp: number = 0;
1078
+ private cacheTTL: number = 60000; // 1分
1079
+
1080
+ /**
1081
+ * 設定を読み込んでマージ
1082
+ */
1083
+ async load(options?: LoadOptions): Promise<MergedConfig> {
1084
+ // キャッシュチェック
1085
+ if (this.cache && options?.useCache !== false) {
1086
+ const now = Date.now();
1087
+ if (now - this.cacheTimestamp < this.cacheTTL) {
1088
+ return this.cache;
1089
+ }
1090
+ }
1091
+
1092
+ const projectRoot = options?.projectRoot || process.cwd();
1093
+
1094
+ // パフォーマンス計測開始
1095
+ const start = performance.now();
1096
+
1097
+ // 並列読み込み
1098
+ const [globalEnv, globalConfig, projectMeta, projectConfig, projectEnv] =
1099
+ await Promise.all([
1100
+ this.loadGlobalEnv(),
1101
+ this.loadGlobalConfig(),
1102
+ this.loadProjectMeta(projectRoot),
1103
+ this.loadProjectConfig(projectRoot),
1104
+ this.loadProjectEnv(projectRoot),
1105
+ ]);
1106
+
1107
+ // マージ
1108
+ const merged = this.merge([
1109
+ globalEnv,
1110
+ globalConfig,
1111
+ projectMeta,
1112
+ projectConfig,
1113
+ projectEnv,
1114
+ ]);
1115
+
1116
+ // リポジトリURLのパース(project.jsonから取得)
1117
+ if (merged.project?.repository) {
1118
+ const parsed = this.parseGitHubRepository(merged.project.repository);
1119
+ merged.github = {
1120
+ ...merged.github,
1121
+ repository: parsed.url,
1122
+ repositoryShort: parsed.shortForm,
1123
+ repositoryOrg: parsed.org,
1124
+ repositoryName: parsed.repo,
1125
+ };
1126
+ }
1127
+
1128
+ // バリデーション
1129
+ if (!options?.skipValidation) {
1130
+ this.validate(merged);
1131
+ }
1132
+
1133
+ // キャッシュ更新
1134
+ this.cache = merged;
1135
+ this.cacheTimestamp = Date.now();
1136
+
1137
+ // パフォーマンス計測終了
1138
+ const elapsed = performance.now() - start;
1139
+ if (elapsed > 100) {
1140
+ console.warn(`⚠️ 設定の読み込みに ${elapsed.toFixed(2)}ms かかりました(目標: <100ms)`);
1141
+ }
1142
+
1143
+ return merged;
1144
+ }
1145
+
1146
+ /**
1147
+ * 設定をリロード(キャッシュをクリア)
1148
+ */
1149
+ reload(): void {
1150
+ this.cache = null;
1151
+ this.cacheTimestamp = 0;
1152
+ }
1153
+
1154
+ /**
1155
+ * 特定の設定値を取得
1156
+ */
1157
+ get<T>(path: string): T | undefined {
1158
+ if (!this.cache) {
1159
+ throw new Error('Configuration not loaded. Call load() first.');
1160
+ }
1161
+
1162
+ // ドットパスで設定値を取得
1163
+ return this.getValueByPath(this.cache, path);
1164
+ }
1165
+
1166
+ /**
1167
+ * 設定値の設定元を取得
1168
+ */
1169
+ getSource(path: string): ConfigSource | undefined {
1170
+ // TODO: 実装
1171
+ // 各設定値がどのファイルから来たかを追跡する
1172
+ return undefined;
1173
+ }
1174
+
1175
+ /**
1176
+ * グローバル.envを読み込み
1177
+ */
1178
+ private async loadGlobalEnv(): Promise<Partial<MergedConfig>> {
1179
+ const globalEnvPath = this.getGlobalEnvPath();
1180
+ if (!existsSync(globalEnvPath)) {
1181
+ return {};
1182
+ }
1183
+
1184
+ const parsed = dotenv.parse(readFileSync(globalEnvPath, 'utf-8'));
1185
+
1186
+ return {
1187
+ atlassian: {
1188
+ url: parsed.ATLASSIAN_URL || '',
1189
+ email: parsed.ATLASSIAN_EMAIL || '',
1190
+ apiToken: parsed.ATLASSIAN_API_TOKEN || '',
1191
+ },
1192
+ github: {
1193
+ org: parsed.GITHUB_ORG || '',
1194
+ token: parsed.GITHUB_TOKEN || '',
1195
+ // repository関連はproject.jsonから取得
1196
+ repository: '',
1197
+ repositoryShort: '',
1198
+ repositoryOrg: '',
1199
+ repositoryName: '',
1200
+ },
1201
+ confluence: {
1202
+ spaces: {
1203
+ prd: parsed.CONFLUENCE_PRD_SPACE || 'PRD',
1204
+ qa: parsed.CONFLUENCE_QA_SPACE || 'QA',
1205
+ release: parsed.CONFLUENCE_RELEASE_SPACE || 'RELEASE',
1206
+ },
1207
+ },
1208
+ jira: {
1209
+ issueTypes: {
1210
+ story: parsed.JIRA_ISSUE_TYPE_STORY || '',
1211
+ subtask: parsed.JIRA_ISSUE_TYPE_SUBTASK || '',
1212
+ },
1213
+ projectKeys: '', // プロジェクト固有
1214
+ },
1215
+ };
1216
+ }
1217
+
1218
+ /**
1219
+ * グローバル設定を読み込み
1220
+ */
1221
+ private async loadGlobalConfig(): Promise<Partial<MergedConfig>> {
1222
+ const globalConfigPath = this.getGlobalConfigPath();
1223
+ if (!existsSync(globalConfigPath)) {
1224
+ return {};
1225
+ }
1226
+
1227
+ const content = readFileSync(globalConfigPath, 'utf-8');
1228
+ const config = JSON.parse(content);
1229
+
1230
+ // TODO: 変換処理
1231
+ return config;
1232
+ }
1233
+
1234
+ /**
1235
+ * プロジェクトメタデータを読み込み
1236
+ */
1237
+ private async loadProjectMeta(projectRoot: string): Promise<Partial<MergedConfig>> {
1238
+ const projectMetaPath = join(projectRoot, '.kiro/project.json');
1239
+ if (!existsSync(projectMetaPath)) {
1240
+ return {};
1241
+ }
1242
+
1243
+ const content = readFileSync(projectMetaPath, 'utf-8');
1244
+ const meta = JSON.parse(content);
1245
+
1246
+ return {
1247
+ project: meta,
1248
+ };
1249
+ }
1250
+
1251
+ /**
1252
+ * プロジェクト設定を読み込み
1253
+ */
1254
+ private async loadProjectConfig(projectRoot: string): Promise<Partial<MergedConfig>> {
1255
+ const projectConfigPath = join(projectRoot, '.michi/config.json');
1256
+ if (!existsSync(projectConfigPath)) {
1257
+ return {};
1258
+ }
1259
+
1260
+ const content = readFileSync(projectConfigPath, 'utf-8');
1261
+ const config = JSON.parse(content);
1262
+
1263
+ // TODO: 変換処理
1264
+ return config;
1265
+ }
1266
+
1267
+ /**
1268
+ * プロジェクト.envを読み込み
1269
+ */
1270
+ private async loadProjectEnv(projectRoot: string): Promise<Partial<MergedConfig>> {
1271
+ const projectEnvPath = join(projectRoot, '.env');
1272
+ if (!existsSync(projectEnvPath)) {
1273
+ return {};
1274
+ }
1275
+
1276
+ const parsed = dotenv.parse(readFileSync(projectEnvPath, 'utf-8'));
1277
+
1278
+ return {
1279
+ jira: {
1280
+ projectKeys: parsed.JIRA_PROJECT_KEYS || '',
1281
+ },
1282
+ // GITHUB_REPO は削除(project.jsonのrepositoryから取得)
1283
+ };
1284
+ }
1285
+
1286
+ /**
1287
+ * マージ処理
1288
+ */
1289
+ private merge(configs: Partial<MergedConfig>[]): MergedConfig {
1290
+ // deep merge with priority
1291
+ return this.deepMerge({}, ...configs) as MergedConfig;
1292
+ }
1293
+
1294
+ /**
1295
+ * ディープマージ
1296
+ */
1297
+ private deepMerge(target: any, ...sources: any[]): any {
1298
+ for (const source of sources) {
1299
+ if (!source) continue;
1300
+
1301
+ for (const key in source) {
1302
+ const targetValue = target[key];
1303
+ const sourceValue = source[key];
1304
+
1305
+ if (this.isObject(sourceValue) && this.isObject(targetValue)) {
1306
+ target[key] = this.deepMerge(targetValue, sourceValue);
1307
+ } else if (sourceValue !== undefined && sourceValue !== '') {
1308
+ target[key] = sourceValue;
1309
+ }
1310
+ }
1311
+ }
1312
+
1313
+ return target;
1314
+ }
1315
+
1316
+ /**
1317
+ * オブジェクトチェック
1318
+ */
1319
+ private isObject(value: any): boolean {
1320
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
1321
+ }
1322
+
1323
+ /**
1324
+ * バリデーション
1325
+ */
1326
+ private validate(config: MergedConfig): void {
1327
+ try {
1328
+ MergedConfigSchema.parse(config);
1329
+ } catch (error) {
1330
+ if (error instanceof z.ZodError) {
1331
+ throw new ConfigValidationError(
1332
+ 'Configuration validation failed',
1333
+ error.issues
1334
+ );
1335
+ }
1336
+ throw error;
1337
+ }
1338
+ }
1339
+
1340
+ /**
1341
+ * パスで値を取得
1342
+ */
1343
+ private getValueByPath(obj: any, path: string): any {
1344
+ const keys = path.split('.');
1345
+ let current = obj;
1346
+
1347
+ for (const key of keys) {
1348
+ if (current === undefined || current === null) {
1349
+ return undefined;
1350
+ }
1351
+ current = current[key];
1352
+ }
1353
+
1354
+ return current;
1355
+ }
1356
+
1357
+ /**
1358
+ * グローバル.envのパスを取得
1359
+ *
1360
+ * 注: 後方互換性のため、旧形式(global.env)も確認する
1361
+ */
1362
+ private getGlobalEnvPath(): string {
1363
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
1364
+ if (!homeDir) {
1365
+ throw new Error('Could not determine home directory');
1366
+ }
1367
+
1368
+ const newPath = join(homeDir, '.michi', '.env');
1369
+ const oldPath = join(homeDir, '.michi', 'global.env');
1370
+
1371
+ // 旧形式が存在し、新形式が存在しない場合は警告
1372
+ if (existsSync(oldPath) && !existsSync(newPath)) {
1373
+ console.warn('⚠️ 古い設定ファイルが見つかりました: ~/.michi/global.env');
1374
+ console.warn(' ~/.michi/.env に移行してください');
1375
+ console.warn(' コマンド: michi migrate config');
1376
+ return oldPath; // 後方互換性のため旧パスを返す
1377
+ }
1378
+
1379
+ return newPath;
1380
+ }
1381
+
1382
+ /**
1383
+ * グローバル設定のパスを取得
1384
+ */
1385
+ private getGlobalConfigPath(): string {
1386
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
1387
+ if (!homeDir) {
1388
+ throw new Error('Could not determine home directory');
1389
+ }
1390
+ return join(homeDir, '.michi', 'config.json');
1391
+ }
1392
+
1393
+ /**
1394
+ * GitHubリポジトリURLをパース
1395
+ *
1396
+ * @param repoUrl - GitHub リポジトリURL(HTTPS または SSH)
1397
+ * @returns パースされたリポジトリ情報
1398
+ * @throws {ConfigValidationError} 無効なURLの場合
1399
+ */
1400
+ private parseGitHubRepository(repoUrl: string): {
1401
+ url: string;
1402
+ org: string;
1403
+ repo: string;
1404
+ shortForm: string;
1405
+ } {
1406
+ // HTTPSとSSH両方のURLをサポート
1407
+ const httpsMatch = repoUrl.match(/github\.com\/([^/]+)\/([^/]+?)(\.git)?$/);
1408
+ const sshMatch = repoUrl.match(/github\.com:([^/]+)\/([^/]+?)(\.git)?$/);
1409
+ const match = httpsMatch || sshMatch;
1410
+
1411
+ if (!match) {
1412
+ throw new ConfigValidationError(
1413
+ 'REPOSITORY_URL_INVALID',
1414
+ `Invalid GitHub repository URL: ${repoUrl}`,
1415
+ []
1416
+ );
1417
+ }
1418
+
1419
+ const org = match[1];
1420
+ const repo = match[2];
1421
+
1422
+ return {
1423
+ url: repoUrl,
1424
+ org,
1425
+ repo,
1426
+ shortForm: `${org}/${repo}`,
1427
+ };
1428
+ }
1429
+ }
1430
+
1431
+ /**
1432
+ * シングルトンインスタンス
1433
+ */
1434
+ export const configLoader = new ConfigLoader();
1435
+
1436
+ /**
1437
+ * ヘルパー関数(後方互換性のため)
1438
+ */
1439
+ export async function loadConfig(options?: LoadOptions): Promise<MergedConfig> {
1440
+ return configLoader.load(options);
1441
+ }
1442
+ ```
1443
+
1444
+ ### 6.2 Zodスキーマ定義
1445
+
1446
+ ```typescript
1447
+ // scripts/utils/config-schema.ts
1448
+
1449
+ import { z } from 'zod';
1450
+
1451
+ /**
1452
+ * MergedConfig のZodスキーマ
1453
+ */
1454
+ export const MergedConfigSchema = z.object({
1455
+ atlassian: z.object({
1456
+ url: z.string().url(),
1457
+ email: z.string().email(),
1458
+ apiToken: z.string().min(1),
1459
+ }),
1460
+
1461
+ github: z.object({
1462
+ org: z.string().min(1),
1463
+ token: z.string().min(1),
1464
+ repository: z.string().url(),
1465
+ repositoryShort: z.string().min(1),
1466
+ repositoryOrg: z.string().min(1),
1467
+ repositoryName: z.string().min(1),
1468
+ }),
1469
+
1470
+ confluence: z.object({
1471
+ spaces: z.object({
1472
+ prd: z.string().min(1),
1473
+ qa: z.string().min(1),
1474
+ release: z.string().min(1),
1475
+ }),
1476
+ pageCreationGranularity: z.enum(['single', 'by-section', 'by-hierarchy', 'manual']),
1477
+ pageTitleFormat: z.string().optional(),
1478
+ hierarchy: z.any().optional(),
1479
+ }),
1480
+
1481
+ jira: z.object({
1482
+ issueTypes: z.object({
1483
+ story: z.string().min(1),
1484
+ subtask: z.string().min(1),
1485
+ }),
1486
+ projectKeys: z.string().min(1),
1487
+ createEpic: z.boolean(),
1488
+ storyCreationGranularity: z.enum(['all', 'by-phase', 'selected-phases']),
1489
+ selectedPhases: z.array(z.string()).optional(),
1490
+ storyPoints: z.enum(['auto', 'manual', 'disabled']),
1491
+ }),
1492
+
1493
+ workflow: z.object({
1494
+ enabledPhases: z.array(z.string()).min(1),
1495
+ approvalGates: z.object({
1496
+ requirements: z.array(z.string()).optional(),
1497
+ design: z.array(z.string()).optional(),
1498
+ release: z.array(z.string()).optional(),
1499
+ }).optional(),
1500
+ }),
1501
+
1502
+ project: z.object({
1503
+ id: z.string().min(1),
1504
+ name: z.string().min(1),
1505
+ language: z.enum(['ja', 'en']),
1506
+ jiraProjectKey: z.string().regex(/^[A-Z]{2,10}$/),
1507
+ confluenceLabels: z.array(z.string()),
1508
+ status: z.string(),
1509
+ team: z.array(z.string()),
1510
+ stakeholders: z.array(z.string()),
1511
+ repository: z.string().url(),
1512
+ description: z.string(),
1513
+ }),
1514
+ });
1515
+
1516
+ export type MergedConfig = z.infer<typeof MergedConfigSchema>;
1517
+ ```
1518
+
1519
+ ### 6.3 エラークラス定義
1520
+
1521
+ ```typescript
1522
+ // scripts/utils/config-errors.ts
1523
+
1524
+ import { z } from 'zod';
1525
+
1526
+ /**
1527
+ * 設定関連のエラー基底クラス
1528
+ */
1529
+ export class ConfigError extends Error {
1530
+ constructor(message: string, public code: string) {
1531
+ super(message);
1532
+ this.name = 'ConfigError';
1533
+ }
1534
+ }
1535
+
1536
+ /**
1537
+ * バリデーションエラー
1538
+ */
1539
+ export class ConfigValidationError extends ConfigError {
1540
+ constructor(message: string, public details: z.ZodIssue[]) {
1541
+ super(message, 'CONFIG_VALIDATION_ERROR');
1542
+ this.name = 'ConfigValidationError';
1543
+ }
1544
+ }
1545
+
1546
+ /**
1547
+ * 必須設定が見つからない
1548
+ */
1549
+ export class ConfigNotFoundError extends ConfigError {
1550
+ constructor(message: string, public missingKeys: string[]) {
1551
+ super(message, 'CONFIG_NOT_FOUND');
1552
+ this.name = 'ConfigNotFoundError';
1553
+ }
1554
+ }
1555
+
1556
+ /**
1557
+ * マイグレーションエラー
1558
+ */
1559
+ export class MigrationError extends ConfigError {
1560
+ constructor(message: string, public phase: string) {
1561
+ super(message, 'MIGRATION_ERROR');
1562
+ this.name = 'MigrationError';
1563
+ }
1564
+ }
1565
+ ```
1566
+
1567
+ ---
1568
+
1569
+ ## 7. マイグレーション戦略
1570
+
1571
+ ### 7.1 移行の概要
1572
+
1573
+ #### 7.1.1 移行が必要な理由
1574
+
1575
+ 新しい設定システムへの移行により、以下の利点が得られます:
1576
+
1577
+ - **設定の一元管理**: 組織レベルの認証情報を全プロジェクトで共有
1578
+ - **セキュリティの強化**: 認証情報を適切なファイル(`~/.michi/.env`)に集約し、パーミッション管理を強化
1579
+ - **メンテナンス性の向上**: 設定変更が容易になり、チーム全体での管理が簡素化
1580
+ - **将来の拡張性**: 新機能(暗号化、複数組織サポート等)の基盤を構築
1581
+
1582
+ #### 7.1.2 移行しない場合のリスク
1583
+
1584
+ - **v1.0.0以降でサポート終了**: 旧形式(`global.env`、`GITHUB_REPO`)は完全に削除されます
1585
+ - **セキュリティ警告の継続表示**: 非推奨機能使用時に警告が表示され続けます
1586
+ - **新機能が利用不可**: v1.1.0以降の新機能(暗号化等)が使用できません
1587
+ - **互換性の問題**: 将来のバージョンで動作しなくなる可能性があります
1588
+
1589
+ ### 7.2 移行パターン
1590
+
1591
+ #### 7.2.1 パターンA: 単一プロジェクトの移行(最も一般的)
1592
+
1593
+ **対象**: 1つのプロジェクトで Michi を使用しているユーザー
1594
+
1595
+ **手順**:
1596
+ 1. グローバル設定を作成: `michi config:global`
1597
+ 2. プロジェクト設定を移行: `michi migrate`
1598
+ 3. 動作確認
1599
+
1600
+ **推定時間**: 10-15分
1601
+
1602
+ **詳細手順**:
1603
+ ```bash
1604
+ # ステップ1: グローバル設定の作成
1605
+ $ michi config:global
1606
+ # 対話的プロンプトに従って、組織共通の設定を入力
1607
+
1608
+ # ステップ2: プロジェクトディレクトリに移動
1609
+ $ cd /path/to/your/project
1610
+
1611
+ # ステップ3: 移行ツールを実行
1612
+ $ michi migrate
1613
+ # 変更内容を確認し、承認
1614
+
1615
+ # ステップ4: 動作確認
1616
+ $ michi init --help
1617
+ # コマンドが正常に動作することを確認
1618
+ ```
1619
+
1620
+ #### 7.2.2 パターンB: 複数プロジェクトの一括移行
1621
+
1622
+ **対象**: 複数のプロジェクトで Michi を使用しているユーザー
1623
+
1624
+ **手順**:
1625
+ 1. グローバル設定を一度作成
1626
+ 2. 各プロジェクトで `migrate` を実行
1627
+ 3. (オプション)スクリプト化して自動実行
1628
+
1629
+ **推定時間**: 5分/プロジェクト
1630
+
1631
+ **一括移行スクリプト例**:
1632
+ ```bash
1633
+ #!/bin/bash
1634
+
1635
+ # グローバル設定を一度だけ作成
1636
+ michi config:global
1637
+
1638
+ # プロジェクトリスト
1639
+ PROJECTS=(
1640
+ "/path/to/project-a"
1641
+ "/path/to/project-b"
1642
+ "/path/to/project-c"
1643
+ )
1644
+
1645
+ # 各プロジェクトで移行を実行
1646
+ for project in "${PROJECTS[@]}"; do
1647
+ echo "Migrating $project..."
1648
+ cd "$project" || exit
1649
+ michi migrate --force # 自動承認
1650
+ echo "✅ $project migrated"
1651
+ done
1652
+
1653
+ echo "🎉 All projects migrated successfully!"
1654
+ ```
1655
+
1656
+ #### 7.2.3 パターンC: 新規プロジェクトの開始
1657
+
1658
+ **対象**: これから Michi を使い始めるユーザー
1659
+
1660
+ **手順**:
1661
+ 1. グローバル設定を作成
1662
+ 2. `michi init` で新規プロジェクト作成
1663
+
1664
+ **推定時間**: 5分
1665
+
1666
+ ```bash
1667
+ # ステップ1: グローバル設定
1668
+ $ michi config:global
1669
+
1670
+ # ステップ2: 新規プロジェクト作成
1671
+ $ mkdir my-new-project
1672
+ $ cd my-new-project
1673
+ $ michi init
1674
+
1675
+ # または、既存プロジェクトに追加
1676
+ $ cd /path/to/existing-project
1677
+ $ michi init --existing
1678
+ ```
1679
+
1680
+ ### 7.3 自動移行ツール: `michi migrate`
1681
+
1682
+ #### 7.3.1 コマンド構文
1683
+
1684
+ ```bash
1685
+ michi migrate [options]
1686
+
1687
+ Options:
1688
+ --dry-run 実際には変更せず、変更内容をプレビュー
1689
+ --backup-dir DIR バックアップディレクトリを指定
1690
+ (デフォルト: .michi-backup-YYYYMMDDHHMMSS)
1691
+ --force 確認プロンプトをスキップ
1692
+ --verbose 詳細なログを表示
1693
+ --help ヘルプを表示
1694
+ ```
1695
+
1696
+ #### 7.3.2 実行フロー
1697
+
1698
+ ```
1699
+ [1. 現状のスキャン]
1700
+ ├─ ~/.michi/config.json の存在確認
1701
+ ├─ ~/.michi/.env の存在確認(新形式)
1702
+ ├─ ~/.michi/global.env の存在確認(旧形式)
1703
+ ├─ .kiro/project.json の存在確認
1704
+ └─ .env の存在確認
1705
+
1706
+ [2. 変更内容のプレビュー]
1707
+ ├─ 組織共通設定の抽出(N項目)
1708
+ ├─ プロジェクト固有設定の保持(M項目)
1709
+ ├─ 旧形式ファイルのリネーム(該当する場合)
1710
+ └─ 変更内容の表示
1711
+
1712
+ [3. ユーザー確認]
1713
+ ├─ 変更内容の確認
1714
+ └─ 続行するか確認(--force でスキップ)
1715
+
1716
+ [4. バックアップ作成]
1717
+ ├─ .michi-backup-YYYYMMDDHHMMSS/ ディレクトリ作成
1718
+ ├─ 既存ファイルをすべてコピー
1719
+ └─ バックアップの場所を表示
1720
+
1721
+ [5. 設定の分離・移行]
1722
+ ├─ .env から組織共通設定を抽出
1723
+ ├─ ~/.michi/.env に書き込み(chmod 600)
1724
+ ├─ .env を更新(プロジェクト固有設定のみ)
1725
+ └─ 旧形式ファイルのリネーム(該当する場合)
1726
+
1727
+ [6. バリデーション]
1728
+ ├─ ConfigLoader で設定を読み込み
1729
+ ├─ 必須項目のチェック
1730
+ └─ エラーがあれば表示
1731
+
1732
+ [7. 完了レポート]
1733
+ ├─ 変更内容のサマリー
1734
+ ├─ バックアップの場所
1735
+ ├─ 次のステップ
1736
+ └─ トラブルシューティングへのリンク
1737
+ ```
1738
+
1739
+ ##### 7.3.3 実行例
1740
+
1741
+ **例1: 単一プロジェクトの移行(Pattern A)**
1742
+
1743
+ ```bash
1744
+ $ cd /Users/username/Work/git/my-project
1745
+ $ michi migrate
1746
+
1747
+ 🔄 Michi 設定移行ツール
1748
+ ================================================
1749
+
1750
+ [1] 現在の設定を検出中...
1751
+ ✓ プロジェクトディレクトリ: /Users/username/Work/git/my-project
1752
+ ✓ .michi/config.json 検出
1753
+ ✓ .env 検出
1754
+ ✓ project.json 検出
1755
+
1756
+ [2] 移行が必要な設定を分析中...
1757
+ ℹ 以下の設定をグローバル化します:
1758
+ - CONFLUENCE_URL
1759
+ - CONFLUENCE_USERNAME
1760
+ - CONFLUENCE_API_TOKEN
1761
+ - JIRA_URL
1762
+ - JIRA_USERNAME
1763
+ - JIRA_API_TOKEN
1764
+ - GITHUB_TOKEN
1765
+ - GITHUB_USERNAME
1766
+ - GITHUB_EMAIL
1767
+ - GITHUB_ORG
1768
+
1769
+ ℹ 以下の設定はプロジェクト固有のままです:
1770
+ - GITHUB_REPO (→ project.json.repository に統合)
1771
+ - PROJECT_NAME (→ project.json.projectId)
1772
+
1773
+ [3] 変更内容の確認
1774
+ 変更されるファイル:
1775
+ - ~/.michi/.env (新規作成)
1776
+ - .env (更新: 10項目削除、1項目追加)
1777
+ - project.json (更新: repository フィールド追加)
1778
+
1779
+ 続行しますか? (y/n): y
1780
+
1781
+ [4] バックアップ作成中...
1782
+ ✓ バックアップ作成: .michi-backup-20250112143022/
1783
+
1784
+ [5] 設定の分離・移行中...
1785
+ ✓ ~/.michi/.env に組織設定を書き込みました
1786
+ ✓ .env を更新しました
1787
+ ✓ project.json を更新しました
1788
+
1789
+ [6] バリデーション実行中...
1790
+ ✓ ConfigLoader で設定を読み込みました
1791
+ ✓ すべての必須項目が設定されています
1792
+
1793
+ [7] 移行完了!
1794
+ ================================================
1795
+ ✅ 設定の移行が完了しました
1796
+
1797
+ 変更内容:
1798
+ - グローバル設定ファイル作成: ~/.michi/.env (10項目)
1799
+ - プロジェクト.env更新: 10項目削除
1800
+ - project.json更新: repository フィールド追加
1801
+
1802
+ バックアップ:
1803
+ - 場所: .michi-backup-20250112143022/
1804
+ - 復元方法: michi migrate --rollback .michi-backup-20250112143022
1805
+
1806
+ 次のステップ:
1807
+ 1. 設定を確認: michi config:validate
1808
+ 2. 動作確認: michi confluence:sync {feature} --dry-run
1809
+ 3. 問題があれば: docs/michi-development/design/config-unification.md#7.7
1810
+
1811
+ 移行ログ: .michi/migration.log
1812
+ ```
1813
+
1814
+ **例2: 強制実行(確認スキップ)**
1815
+
1816
+ ```bash
1817
+ $ michi migrate --force
1818
+
1819
+ 🔄 Michi 設定移行ツール
1820
+ ================================================
1821
+ ⚠️ --force オプションが指定されています。確認をスキップします。
1822
+
1823
+ [1] 現在の設定を検出中...
1824
+ ✓ プロジェクトディレクトリ: /Users/username/Work/git/my-project
1825
+ ...
1826
+
1827
+ [4] バックアップ作成中...
1828
+ ✓ バックアップ作成: .michi-backup-20250112143105/
1829
+
1830
+ [5] 設定の分離・移行中...
1831
+ ...
1832
+
1833
+ ✅ 設定の移行が完了しました
1834
+ ```
1835
+
1836
+ **例3: ドライラン(変更なし)**
1837
+
1838
+ ```bash
1839
+ $ michi migrate --dry-run
1840
+
1841
+ 🔄 Michi 設定移行ツール (ドライランモード)
1842
+ ================================================
1843
+ ⚠️ このモードでは実際の変更は行われません
1844
+
1845
+ [1] 現在の設定を検出中...
1846
+ ✓ プロジェクトディレクトリ: /Users/username/Work/git/my-project
1847
+ ✓ .michi/config.json 検出
1848
+ ✓ .env 検出
1849
+ ✓ project.json 検出
1850
+
1851
+ [2] 移行が必要な設定を分析中...
1852
+ ℹ 以下の設定をグローバル化します:
1853
+ - CONFLUENCE_URL
1854
+ - CONFLUENCE_USERNAME
1855
+ - ...
1856
+
1857
+ [予定される変更]
1858
+ 作成: ~/.michi/.env
1859
+ CONFLUENCE_URL=https://example.atlassian.net
1860
+ CONFLUENCE_USERNAME=admin@example.com
1861
+ ...
1862
+
1863
+ 更新: .env (10行削除)
1864
+
1865
+ 更新: project.json
1866
+ + "repository": "https://github.com/myorg/my-project.git"
1867
+
1868
+ [3] ドライラン完了
1869
+ ================================================
1870
+ ⚠️ --dry-run モードのため、実際の変更は行われませんでした
1871
+
1872
+ 実際に移行を実行する場合:
1873
+ $ michi migrate
1874
+ ```
1875
+
1876
+ #### 7.4 手動移行手順
1877
+
1878
+ 自動移行ツールを使用しない場合や、カスタマイズが必要な場合の手動移行手順です。
1879
+
1880
+ ##### 7.4.1 グローバル設定の作成
1881
+
1882
+ **ステップ1: ~/.michi/.env の作成**
1883
+
1884
+ ```bash
1885
+ # ディレクトリ作成
1886
+ mkdir -p ~/.michi
1887
+
1888
+ # .env ファイル作成
1889
+ cat > ~/.michi/.env << 'EOF'
1890
+ # Michi グローバル設定(組織共通)
1891
+
1892
+ # Confluence設定
1893
+ CONFLUENCE_URL=https://your-domain.atlassian.net
1894
+ CONFLUENCE_USERNAME=your-email@example.com
1895
+ CONFLUENCE_API_TOKEN=your-confluence-api-token
1896
+
1897
+ # JIRA設定
1898
+ JIRA_URL=https://your-domain.atlassian.net
1899
+ JIRA_USERNAME=your-email@example.com
1900
+ JIRA_API_TOKEN=your-jira-api-token
1901
+
1902
+ # GitHub設定
1903
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1904
+ GITHUB_USERNAME=your-github-username
1905
+ GITHUB_EMAIL=your-email@example.com
1906
+ GITHUB_ORG=your-organization
1907
+ EOF
1908
+
1909
+ # パーミッション設定
1910
+ chmod 600 ~/.michi/.env
1911
+ ```
1912
+
1913
+ **ステップ2: 既存プロジェクトの .env を更新**
1914
+
1915
+ ```bash
1916
+ # プロジェクトディレクトリに移動
1917
+ cd /path/to/your/project
1918
+
1919
+ # バックアップ作成
1920
+ cp .env .env.backup
1921
+
1922
+ # グローバル化される項目を削除
1923
+ # (以下は sed コマンドの例、実際には手動編集を推奨)
1924
+ sed -i '' '/^CONFLUENCE_URL=/d' .env
1925
+ sed -i '' '/^CONFLUENCE_USERNAME=/d' .env
1926
+ sed -i '' '/^CONFLUENCE_API_TOKEN=/d' .env
1927
+ sed -i '' '/^JIRA_URL=/d' .env
1928
+ sed -i '' '/^JIRA_USERNAME=/d' .env
1929
+ sed -i '' '/^JIRA_API_TOKEN=/d' .env
1930
+ sed -i '' '/^GITHUB_TOKEN=/d' .env
1931
+ sed -i '' '/^GITHUB_USERNAME=/d' .env
1932
+ sed -i '' '/^GITHUB_EMAIL=/d' .env
1933
+ sed -i '' '/^GITHUB_ORG=/d' .env
1934
+ ```
1935
+
1936
+ ##### 7.4.2 プロジェクト設定の更新
1937
+
1938
+ **ステップ3: project.json の更新**
1939
+
1940
+ ```bash
1941
+ # .env から GITHUB_REPO を取得
1942
+ GITHUB_REPO=$(grep GITHUB_REPO .env | cut -d= -f2)
1943
+
1944
+ # project.json に repository フィールドを追加
1945
+ # (jq コマンドを使用する例)
1946
+ jq --arg repo "https://github.com/$GITHUB_REPO.git" \
1947
+ '.repository = $repo' \
1948
+ .michi/project.json > .michi/project.json.tmp
1949
+
1950
+ mv .michi/project.json.tmp .michi/project.json
1951
+
1952
+ # または手動で編集
1953
+ # {
1954
+ # "projectId": "my-project",
1955
+ # "repository": "https://github.com/myorg/my-project.git",
1956
+ # ...
1957
+ # }
1958
+ ```
1959
+
1960
+ **ステップ4: .env から GITHUB_REPO を削除**
1961
+
1962
+ ```bash
1963
+ sed -i '' '/^GITHUB_REPO=/d' .env
1964
+ ```
1965
+
1966
+ ##### 7.4.3 設定の検証
1967
+
1968
+ ```bash
1969
+ # ConfigLoader でバリデーション
1970
+ npm run config:validate
1971
+
1972
+ # または
1973
+ npx tsx scripts/utils/config-validator.ts
1974
+
1975
+ # Michi CLIで動作確認(ドライラン)
1976
+ michi confluence:sync my-feature --dry-run
1977
+ ```
1978
+
1979
+ #### 7.5 検証方法
1980
+
1981
+ 移行後の設定が正しいことを確認するためのチェックリストです。
1982
+
1983
+ ##### 7.5.1 ファイル存在チェック
1984
+
1985
+ ```bash
1986
+ # グローバル設定の確認
1987
+ [ -f ~/.michi/.env ] && echo "✓ ~/.michi/.env 存在" || echo "✗ ~/.michi/.env が見つかりません"
1988
+
1989
+ # パーミッション確認
1990
+ ls -l ~/.michi/.env | grep "^-rw-------" && echo "✓ パーミッション正常 (600)" || echo "⚠️ パーミッションを確認してください"
1991
+
1992
+ # プロジェクト設定の確認
1993
+ [ -f .michi/project.json ] && echo "✓ .michi/project.json 存在" || echo "✗ .michi/project.json が見つかりません"
1994
+ [ -f .env ] && echo "✓ .env 存在" || echo "✗ .env が見つかりません"
1995
+ ```
1996
+
1997
+ ##### 7.5.2 設定内容チェック
1998
+
1999
+ **グローバル設定のチェック**
2000
+
2001
+ ```bash
2002
+ # 必須項目が含まれているか確認
2003
+ grep -q "CONFLUENCE_URL=" ~/.michi/.env && echo "✓ CONFLUENCE_URL" || echo "✗ CONFLUENCE_URL が見つかりません"
2004
+ grep -q "JIRA_URL=" ~/.michi/.env && echo "✓ JIRA_URL" || echo "✗ JIRA_URL が見つかりません"
2005
+ grep -q "GITHUB_TOKEN=" ~/.michi/.env && echo "✓ GITHUB_TOKEN" || echo "✗ GITHUB_TOKEN が見つかりません"
2006
+ ```
2007
+
2008
+ **プロジェクト設定のチェック**
2009
+
2010
+ ```bash
2011
+ # GITHUB_REPO が削除されているか確認
2012
+ ! grep -q "GITHUB_REPO=" .env && echo "✓ GITHUB_REPO は削除されています" || echo "⚠️ GITHUB_REPO がまだ残っています"
2013
+
2014
+ # project.json に repository が追加されているか確認
2015
+ grep -q '"repository"' .michi/project.json && echo "✓ project.json に repository フィールド追加済み" || echo "✗ repository フィールドが見つかりません"
2016
+ ```
2017
+
2018
+ ##### 7.5.3 バリデーション実行
2019
+
2020
+ ```bash
2021
+ # Michi の設定バリデーション
2022
+ npm run config:validate
2023
+
2024
+ # 期待される出力:
2025
+ # ✅ 設定ファイルは有効です
2026
+ # ✅ グローバル設定: ~/.michi/.env
2027
+ # ✅ プロジェクト設定: .michi/config.json
2028
+ # ✅ プロジェクト環境: .env
2029
+ # ✅ すべての必須項目が設定されています
2030
+ ```
2031
+
2032
+ ##### 7.5.4 機能テスト
2033
+
2034
+ **Confluence同期のテスト**
2035
+
2036
+ ```bash
2037
+ # ドライランで確認(実際にページは作成されない)
2038
+ michi confluence:sync my-feature requirements --dry-run
2039
+
2040
+ # 期待される動作:
2041
+ # - Confluence URLに接続できる
2042
+ # - 認証が成功する
2043
+ # - スペースにアクセスできる
2044
+ # - ページ作成のシミュレーションが成功する
2045
+ ```
2046
+
2047
+ **JIRA同期のテスト**
2048
+
2049
+ ```bash
2050
+ # ドライランで確認
2051
+ michi jira:sync my-feature --dry-run
2052
+
2053
+ # 期待される動作:
2054
+ # - JIRA URLに接続できる
2055
+ # - プロジェクトが見つかる
2056
+ # - Epic/Story作成のシミュレーションが成功する
2057
+ ```
2058
+
2059
+ **GitHub PR作成のテスト**
2060
+
2061
+ ```bash
2062
+ # 現在のブランチ情報を確認
2063
+ michi github:pr --info
2064
+
2065
+ # 期待される動作:
2066
+ # - リポジトリ情報が正しく取得できる
2067
+ # - ブランチ情報が表示される
2068
+ # - PR作成の準備ができている
2069
+ ```
2070
+
2071
+ #### 7.6 ロールバック手順
2072
+
2073
+ 移行後に問題が発生した場合の復元手順です。
2074
+
2075
+ ##### 7.6.1 自動バックアップからの復元
2076
+
2077
+ `michi migrate` コマンドは自動的にバックアップを作成します。
2078
+
2079
+ ```bash
2080
+ # バックアップディレクトリの確認
2081
+ ls -la .michi-backup-*
2082
+
2083
+ # 例: .michi-backup-20250112143022/
2084
+
2085
+ # ロールバック実行
2086
+ michi migrate --rollback .michi-backup-20250112143022
2087
+
2088
+ # または手動で復元
2089
+ cp -r .michi-backup-20250112143022/.michi .michi
2090
+ cp .michi-backup-20250112143022/.env .env
2091
+ cp .michi-backup-20250112143022/project.json .michi/project.json
2092
+ ```
2093
+
2094
+ ##### 7.6.2 手動バックアップからの復元
2095
+
2096
+ 手動移行を行った場合のロールバック手順:
2097
+
2098
+ **ステップ1: バックアップファイルを確認**
2099
+
2100
+ ```bash
2101
+ # バックアップファイルの確認
2102
+ ls -la *.backup
2103
+
2104
+ # 例:
2105
+ # .env.backup
2106
+ # project.json.backup
2107
+ ```
2108
+
2109
+ **ステップ2: ファイルを復元**
2110
+
2111
+ ```bash
2112
+ # .env の復元
2113
+ cp .env.backup .env
2114
+
2115
+ # project.json の復元
2116
+ cp .michi/project.json.backup .michi/project.json
2117
+
2118
+ # 権限の確認
2119
+ chmod 600 .env
2120
+ ```
2121
+
2122
+ **ステップ3: グローバル設定の削除(オプション)**
2123
+
2124
+ ```bash
2125
+ # グローバル設定をロールバックする場合
2126
+ rm ~/.michi/.env
2127
+
2128
+ # または、グローバル設定は残して .env を旧形式に戻すのみでもOK
2129
+ ```
2130
+
2131
+ **ステップ4: 動作確認**
2132
+
2133
+ ```bash
2134
+ # 設定が正しく復元されたか確認
2135
+ npm run config:validate
2136
+
2137
+ # 実際の機能をテスト
2138
+ michi confluence:sync my-feature --dry-run
2139
+ ```
2140
+
2141
+ ##### 7.6.3 部分的なロールバック
2142
+
2143
+ グローバル設定のみ、またはプロジェクト設定のみをロールバックする場合:
2144
+
2145
+ **グローバル設定のみロールバック**
2146
+
2147
+ ```bash
2148
+ # グローバル設定を削除
2149
+ rm ~/.michi/.env
2150
+
2151
+ # .env に組織設定を復元
2152
+ # (バックアップから CONFLUENCE_*, JIRA_*, GITHUB_* をコピー)
2153
+ ```
2154
+
2155
+ **プロジェクト設定のみロールバック**
2156
+
2157
+ ```bash
2158
+ # project.json の repository フィールドを削除
2159
+ jq 'del(.repository)' .michi/project.json > .michi/project.json.tmp
2160
+ mv .michi/project.json.tmp .michi/project.json
2161
+
2162
+ # .env に GITHUB_REPO を復元
2163
+ echo "GITHUB_REPO=myorg/my-project" >> .env
2164
+ ```
2165
+
2166
+ #### 7.7 トラブルシューティング
2167
+
2168
+ 移行中または移行後に発生する可能性のある問題と解決策です。
2169
+
2170
+ ##### 7.7.1 移行ツールのエラー
2171
+
2172
+ **エラー: "No .env file found"**
2173
+
2174
+ ```
2175
+ 原因: プロジェクトディレクトリに .env ファイルが存在しない
2176
+
2177
+ 解決策:
2178
+ 1. 現在のディレクトリを確認: pwd
2179
+ 2. .env ファイルを作成: cp env.example .env
2180
+ 3. 必要な設定を記入
2181
+ 4. 再度移行を実行: michi migrate
2182
+ ```
2183
+
2184
+ **エラー: "~/.michi/.env already exists"**
2185
+
2186
+ ```
2187
+ 原因: グローバル設定ファイルが既に存在する
2188
+
2189
+ 解決策:
2190
+ 1. 既存のファイルを確認: cat ~/.michi/.env
2191
+ 2. バックアップを作成: cp ~/.michi/.env ~/.michi/.env.backup
2192
+ 3. --force オプションで上書き: michi migrate --force
2193
+ または
2194
+ 4. 手動でマージ: 既存の ~/.michi/.env に不足している項目を追加
2195
+ ```
2196
+
2197
+ **エラー: "Invalid repository URL in project.json"**
2198
+
2199
+ ```
2200
+ 原因: project.json の repository フィールドが不正な形式
2201
+
2202
+ 解決策:
2203
+ 1. project.json を確認: cat .michi/project.json
2204
+ 2. repository フィールドを修正:
2205
+ 正しい形式: "https://github.com/org/repo.git"
2206
+ または "git@github.com:org/repo.git"
2207
+ 3. 再度移行を実行: michi migrate
2208
+ ```
2209
+
2210
+ ##### 7.7.2 バリデーションエラー
2211
+
2212
+ **エラー: "CONFLUENCE_URL is required"**
2213
+
2214
+ ```
2215
+ 原因: グローバル設定に必須項目が不足している
2216
+
2217
+ 解決策:
2218
+ 1. ~/.michi/.env を編集
2219
+ 2. 不足している項目を追加:
2220
+ CONFLUENCE_URL=https://your-domain.atlassian.net
2221
+ 3. パーミッションを確認: chmod 600 ~/.michi/.env
2222
+ 4. 再度バリデーション: npm run config:validate
2223
+ ```
2224
+
2225
+ **エラー: "Repository URL does not match GITHUB_REPO"**
2226
+
2227
+ ```
2228
+ 原因: project.json.repository と .env.GITHUB_REPO が一致しない
2229
+
2230
+ 解決策:
2231
+ 1. どちらが正しいか確認
2232
+ 2. 正しい値を project.json に設定
2233
+ 3. .env から GITHUB_REPO を削除
2234
+ 4. 再度バリデーション: npm run config:validate
2235
+ ```
2236
+
2237
+ ##### 7.7.3 機能テストの失敗
2238
+
2239
+ **エラー: "Confluence authentication failed"**
2240
+
2241
+ ```
2242
+ 原因: Confluence の認証情報が間違っている、または期限切れ
2243
+
2244
+ 解決策:
2245
+ 1. ~/.michi/.env の認証情報を確認
2246
+ 2. Atlassian でAPIトークンを再生成:
2247
+ https://id.atlassian.com/manage-profile/security/api-tokens
2248
+ 3. ~/.michi/.env を更新
2249
+ 4. 再度テスト: michi confluence:sync my-feature --dry-run
2250
+ ```
2251
+
2252
+ **エラー: "GitHub repository not found"**
2253
+
2254
+ ```
2255
+ 原因: リポジトリURLが間違っている、またはアクセス権限がない
2256
+
2257
+ 解決策:
2258
+ 1. project.json の repository を確認
2259
+ 2. GitHub でリポジトリの存在とアクセス権限を確認
2260
+ 3. 必要に応じて GITHUB_TOKEN の権限を確認
2261
+ 4. 正しいURLに修正
2262
+ 5. 再度テスト: michi github:pr --info
2263
+ ```
2264
+
2265
+ ##### 7.7.4 パーミッションの問題
2266
+
2267
+ **エラー: "Permission denied: ~/.michi/.env"**
2268
+
2269
+ ```
2270
+ 原因: ファイルのパーミッションが正しくない
2271
+
2272
+ 解決策:
2273
+ 1. 現在のパーミッションを確認: ls -l ~/.michi/.env
2274
+ 2. 正しいパーミッションに修正: chmod 600 ~/.michi/.env
2275
+ 3. 所有者を確認: ls -l ~/.michi/.env
2276
+ 4. 必要に応じて所有者を変更: sudo chown $USER ~/.michi/.env
2277
+ ```
2278
+
2279
+ ##### 7.7.5 マルチプロジェクトでの問題
2280
+
2281
+ **問題: "複数プロジェクトで異なる組織設定が必要"**
2282
+
2283
+ ```
2284
+ 原因: 複数の組織に跨ってプロジェクトを管理している
2285
+
2286
+ 解決策(現在の制限事項):
2287
+ 1. グローバル設定は1つの組織のみをサポート
2288
+ 2. 別の組織のプロジェクトでは .env に組織設定を直接記述
2289
+ 3. 将来的にはプロファイル機能で複数組織をサポート予定(Section 11参照)
2290
+
2291
+ 一時的な回避策:
2292
+ - 主に使用する組織を ~/.michi/.env に設定
2293
+ - 他の組織のプロジェクトでは .env に全設定を記述(グローバル設定を使用しない)
2294
+ ```
2295
+
2296
+ **問題: "プロジェクトAの変更がプロジェクトBに影響する"**
2297
+
2298
+ ```
2299
+ 原因: グローバル設定を誤って変更した
2300
+
2301
+ 解決策:
2302
+ 1. グローバル設定の変更は慎重に行う
2303
+ 2. プロジェクト固有の設定は必ず .env または .michi/config.json に記述
2304
+ 3. 設定の優先順位を理解する:
2305
+ プロジェクト .env > プロジェクト config.json > グローバル .env
2306
+ ```
2307
+
2308
+ ##### 7.7.6 ロールバック失敗
2309
+
2310
+ **エラー: "Backup directory not found"**
2311
+
2312
+ ```
2313
+ 原因: バックアップディレクトリが見つからない
2314
+
2315
+ 解決策:
2316
+ 1. バックアップディレクトリを検索: find . -name ".michi-backup-*"
2317
+ 2. 見つからない場合は手動バックアップを使用: .env.backup など
2318
+ 3. 最悪の場合は Git で復元: git checkout .env .michi/
2319
+ ```
2320
+
2321
+ **エラー: "Cannot restore: files are modified"**
2322
+
2323
+ ```
2324
+ 原因: 復元先のファイルが変更されている
2325
+
2326
+ 解決策:
2327
+ 1. 現在の変更を確認: git status
2328
+ 2. 変更を保存: git stash
2329
+ 3. ロールバックを実行
2330
+ 4. 必要に応じて変更を復元: git stash pop
2331
+ ```
2332
+
2333
+ ---
2334
+
2335
+ ### 参考情報
2336
+
2337
+ - **移行ログの確認**: `.michi/migration.log` に詳細なログが記録されます
2338
+ - **バックアップの保管期間**: 自動バックアップは30日後に自動削除されます(設定で変更可能)
2339
+ - **サポート**: 問題が解決しない場合は GitHub Issues で報告してください
2340
+
2341
+ ---
2342
+
2343
+ ## 8. テスト戦略
2344
+
2345
+ 設定統一に伴う変更を安全に実施するためのテスト戦略です。
2346
+
2347
+ ### 8.1 テストスコープ
2348
+
2349
+ #### 8.1.1 対象範囲
2350
+
2351
+ 設定統一により影響を受ける主要な機能をテスト対象とします:
2352
+
2353
+ | カテゴリ | テスト対象 | テストレベル |
2354
+ |---------|-----------|------------|
2355
+ | **設定読み込み** | ConfigLoader の3層マージ | 単体 + 統合 |
2356
+ | **バリデーション** | Zodスキーマによる検証 | 単体 |
2357
+ | **パス解決** | リポジトリURLのパース | 単体 |
2358
+ | **コマンドライン** | michi init / migrate | 統合 + E2E |
2359
+ | **外部API** | Confluence/JIRA/GitHub連携 | 統合 + E2E |
2360
+ | **マイグレーション** | 既存設定の移行処理 | 統合 + E2E |
2361
+ | **後方互換性** | 旧形式設定のサポート | 統合 |
2362
+
2363
+ #### 8.1.2 テスト優先順位
2364
+
2365
+ **P0 (Critical): リリースブロッカー**
2366
+ - ConfigLoader の3層マージロジック
2367
+ - 必須項目のバリデーション
2368
+ - リポジトリURLパーサー
2369
+ - `michi migrate` コマンド
2370
+
2371
+ **P1 (High): リリース前に必須**
2372
+ - `michi init` コマンド(新規・既存)
2373
+ - Confluence/JIRA/GitHub連携の動作確認
2374
+ - 設定ファイルのパーミッション
2375
+ - エラーメッセージの内容
2376
+
2377
+ **P2 (Medium): リリース後でも対応可能**
2378
+ - 詳細なエラーメッセージ
2379
+ - ドライランモード
2380
+ - ロールバック機能
2381
+ - 設定バリデーションの詳細
2382
+
2383
+ ### 8.2 単体テスト
2384
+
2385
+ #### 8.2.1 ConfigLoader のテスト
2386
+
2387
+ **ファイル**: `src/config/__tests__/config-loader.test.ts`
2388
+
2389
+ ```typescript
2390
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2391
+ import { ConfigLoader } from '../config-loader.js';
2392
+ import { mkdirSync, writeFileSync, rmSync } from 'fs';
2393
+ import { tmpdir } from 'os';
2394
+ import { join } from 'path';
2395
+
2396
+ describe('ConfigLoader', () => {
2397
+ let testDir: string;
2398
+ let globalEnvPath: string;
2399
+ let projectDir: string;
2400
+
2401
+ beforeEach(() => {
2402
+ // テスト用の一時ディレクトリ作成
2403
+ testDir = join(tmpdir(), `michi-test-${Date.now()}`);
2404
+ mkdirSync(testDir, { recursive: true });
2405
+
2406
+ // グローバル設定ディレクトリ
2407
+ globalEnvPath = join(testDir, '.michi', '.env');
2408
+ mkdirSync(join(testDir, '.michi'), { recursive: true });
2409
+
2410
+ // プロジェクトディレクトリ
2411
+ projectDir = join(testDir, 'project');
2412
+ mkdirSync(join(projectDir, '.michi'), { recursive: true });
2413
+
2414
+ // 環境変数を上書き
2415
+ process.env.HOME = testDir;
2416
+ });
2417
+
2418
+ afterEach(() => {
2419
+ // テスト後のクリーンアップ
2420
+ rmSync(testDir, { recursive: true, force: true });
2421
+ });
2422
+
2423
+ describe('3層マージ', () => {
2424
+ it('グローバル設定を正しく読み込む', () => {
2425
+ // グローバル .env を作成
2426
+ writeFileSync(globalEnvPath, `
2427
+ CONFLUENCE_URL=https://global.atlassian.net
2428
+ JIRA_URL=https://global.atlassian.net
2429
+ GITHUB_TOKEN=global-token
2430
+ `.trim());
2431
+
2432
+ const loader = new ConfigLoader(projectDir);
2433
+ const config = loader.load();
2434
+
2435
+ expect(config.confluence?.url).toBe('https://global.atlassian.net');
2436
+ expect(config.jira?.url).toBe('https://global.atlassian.net');
2437
+ expect(config.github?.token).toBe('global-token');
2438
+ });
2439
+
2440
+ it('プロジェクト設定がグローバル設定を上書きする', () => {
2441
+ // グローバル .env
2442
+ writeFileSync(globalEnvPath, `
2443
+ CONFLUENCE_URL=https://global.atlassian.net
2444
+ GITHUB_TOKEN=global-token
2445
+ `.trim());
2446
+
2447
+ // プロジェクト .env
2448
+ writeFileSync(join(projectDir, '.env'), `
2449
+ CONFLUENCE_URL=https://project.atlassian.net
2450
+ `.trim());
2451
+
2452
+ const loader = new ConfigLoader(projectDir);
2453
+ const config = loader.load();
2454
+
2455
+ // プロジェクト設定が優先される
2456
+ expect(config.confluence?.url).toBe('https://project.atlassian.net');
2457
+ // グローバル設定は保持される
2458
+ expect(config.github?.token).toBe('global-token');
2459
+ });
2460
+
2461
+ it('プロジェクト config.json がグローバル設定を上書きする', () => {
2462
+ // グローバル .env
2463
+ writeFileSync(globalEnvPath, `
2464
+ CONFLUENCE_URL=https://global.atlassian.net
2465
+ `.trim());
2466
+
2467
+ // プロジェクト config.json
2468
+ writeFileSync(join(projectDir, '.michi', 'config.json'), JSON.stringify({
2469
+ confluence: {
2470
+ pageCreationGranularity: 'by-section'
2471
+ }
2472
+ }, null, 2));
2473
+
2474
+ const loader = new ConfigLoader(projectDir);
2475
+ const config = loader.load();
2476
+
2477
+ expect(config.confluence?.url).toBe('https://global.atlassian.net');
2478
+ expect(config.confluence?.pageCreationGranularity).toBe('by-section');
2479
+ });
2480
+
2481
+ it('優先順位: プロジェクト .env > プロジェクト config.json > グローバル .env', () => {
2482
+ // グローバル .env
2483
+ writeFileSync(globalEnvPath, `
2484
+ CONFLUENCE_SPACE=GLOBAL
2485
+ JIRA_PROJECT=GLOBAL
2486
+ `.trim());
2487
+
2488
+ // プロジェクト config.json
2489
+ writeFileSync(join(projectDir, '.michi', 'config.json'), JSON.stringify({
2490
+ confluence: { space: 'CONFIG' },
2491
+ jira: { project: 'CONFIG' }
2492
+ }, null, 2));
2493
+
2494
+ // プロジェクト .env
2495
+ writeFileSync(join(projectDir, '.env'), `
2496
+ CONFLUENCE_SPACE=PROJECT
2497
+ `.trim());
2498
+
2499
+ const loader = new ConfigLoader(projectDir);
2500
+ const config = loader.load();
2501
+
2502
+ // プロジェクト .env が最優先
2503
+ expect(config.confluence?.space).toBe('PROJECT');
2504
+ // プロジェクト config.json が次
2505
+ expect(config.jira?.project).toBe('CONFIG');
2506
+ });
2507
+ });
2508
+
2509
+ describe('リポジトリURLパーサー', () => {
2510
+ it('HTTPS形式のURLを正しくパースする', () => {
2511
+ writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
2512
+ projectId: 'test-project',
2513
+ repository: 'https://github.com/myorg/myrepo.git'
2514
+ }, null, 2));
2515
+
2516
+ const loader = new ConfigLoader(projectDir);
2517
+ const config = loader.load();
2518
+
2519
+ expect(config.github?.repository).toBe('https://github.com/myorg/myrepo.git');
2520
+ expect(config.github?.repositoryOrg).toBe('myorg');
2521
+ expect(config.github?.repositoryName).toBe('myrepo');
2522
+ expect(config.github?.repositoryShort).toBe('myorg/myrepo');
2523
+ });
2524
+
2525
+ it('SSH形式のURLを正しくパースする', () => {
2526
+ writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
2527
+ projectId: 'test-project',
2528
+ repository: 'git@github.com:myorg/myrepo.git'
2529
+ }, null, 2));
2530
+
2531
+ const loader = new ConfigLoader(projectDir);
2532
+ const config = loader.load();
2533
+
2534
+ expect(config.github?.repositoryOrg).toBe('myorg');
2535
+ expect(config.github?.repositoryName).toBe('myrepo');
2536
+ expect(config.github?.repositoryShort).toBe('myorg/myrepo');
2537
+ });
2538
+
2539
+ it('.git拡張子なしのURLを正しくパースする', () => {
2540
+ writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
2541
+ projectId: 'test-project',
2542
+ repository: 'https://github.com/myorg/myrepo'
2543
+ }, null, 2));
2544
+
2545
+ const loader = new ConfigLoader(projectDir);
2546
+ const config = loader.load();
2547
+
2548
+ expect(config.github?.repositoryOrg).toBe('myorg');
2549
+ expect(config.github?.repositoryName).toBe('myrepo');
2550
+ });
2551
+
2552
+ it('不正な形式のURLでエラーをスローする', () => {
2553
+ writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
2554
+ projectId: 'test-project',
2555
+ repository: 'invalid-url'
2556
+ }, null, 2));
2557
+
2558
+ const loader = new ConfigLoader(projectDir);
2559
+
2560
+ expect(() => loader.load()).toThrow('Invalid GitHub repository URL');
2561
+ });
2562
+ });
2563
+
2564
+ describe('バリデーション', () => {
2565
+ it('必須項目が不足している場合エラーをスローする', () => {
2566
+ // グローバル .env なし、プロジェクト .env も最小限
2567
+ writeFileSync(join(projectDir, '.env'), `
2568
+ # 何も設定しない
2569
+ `.trim());
2570
+
2571
+ const loader = new ConfigLoader(projectDir);
2572
+
2573
+ expect(() => loader.load()).toThrow();
2574
+ });
2575
+
2576
+ it('CONFLUENCE_URL が不正な場合エラーをスローする', () => {
2577
+ writeFileSync(globalEnvPath, `
2578
+ CONFLUENCE_URL=not-a-url
2579
+ `.trim());
2580
+
2581
+ const loader = new ConfigLoader(projectDir);
2582
+
2583
+ expect(() => loader.load()).toThrow();
2584
+ });
2585
+ });
2586
+
2587
+ describe('後方互換性', () => {
2588
+ it('GITHUB_REPO が .env に存在する場合、警告を表示する', () => {
2589
+ const consoleSpy = vi.spyOn(console, 'warn');
2590
+
2591
+ writeFileSync(join(projectDir, '.env'), `
2592
+ GITHUB_REPO=myorg/myrepo
2593
+ `.trim());
2594
+
2595
+ const loader = new ConfigLoader(projectDir);
2596
+ loader.load();
2597
+
2598
+ expect(consoleSpy).toHaveBeenCalledWith(
2599
+ expect.stringContaining('GITHUB_REPO is deprecated')
2600
+ );
2601
+ });
2602
+
2603
+ it('project.json に repository が存在する場合、GITHUB_REPO は無視される', () => {
2604
+ writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
2605
+ projectId: 'test-project',
2606
+ repository: 'https://github.com/correct/repo.git'
2607
+ }, null, 2));
2608
+
2609
+ writeFileSync(join(projectDir, '.env'), `
2610
+ GITHUB_REPO=wrong/repo
2611
+ `.trim());
2612
+
2613
+ const loader = new ConfigLoader(projectDir);
2614
+ const config = loader.load();
2615
+
2616
+ expect(config.github?.repositoryShort).toBe('correct/repo');
2617
+ });
2618
+ });
2619
+ });
2620
+ ```
2621
+
2622
+ #### 8.2.2 バリデーションのテスト
2623
+
2624
+ **ファイル**: `src/config/__tests__/validation.test.ts`
2625
+
2626
+ ```typescript
2627
+ import { describe, it, expect } from 'vitest';
2628
+ import { AppConfigSchema } from '../config-schema.js';
2629
+
2630
+ describe('設定バリデーション', () => {
2631
+ describe('Confluence設定', () => {
2632
+ it('有効なConfluence設定を受け入れる', () => {
2633
+ const config = {
2634
+ confluence: {
2635
+ url: 'https://example.atlassian.net',
2636
+ username: 'user@example.com',
2637
+ apiToken: 'token123',
2638
+ space: 'DEV',
2639
+ pageCreationGranularity: 'single'
2640
+ }
2641
+ };
2642
+
2643
+ expect(() => AppConfigSchema.parse(config)).not.toThrow();
2644
+ });
2645
+
2646
+ it('不正なURL形式を拒否する', () => {
2647
+ const config = {
2648
+ confluence: {
2649
+ url: 'not-a-url',
2650
+ username: 'user@example.com',
2651
+ apiToken: 'token123'
2652
+ }
2653
+ };
2654
+
2655
+ expect(() => AppConfigSchema.parse(config)).toThrow();
2656
+ });
2657
+
2658
+ it('pageCreationGranularityの不正な値を拒否する', () => {
2659
+ const config = {
2660
+ confluence: {
2661
+ url: 'https://example.atlassian.net',
2662
+ pageCreationGranularity: 'invalid-value'
2663
+ }
2664
+ };
2665
+
2666
+ expect(() => AppConfigSchema.parse(config)).toThrow();
2667
+ });
2668
+ });
2669
+
2670
+ describe('JIRA設定', () => {
2671
+ it('有効なJIRA設定を受け入れる', () => {
2672
+ const config = {
2673
+ jira: {
2674
+ url: 'https://example.atlassian.net',
2675
+ username: 'user@example.com',
2676
+ apiToken: 'token123',
2677
+ project: 'MYPROJ',
2678
+ createEpic: true,
2679
+ storyCreationGranularity: 'all'
2680
+ }
2681
+ };
2682
+
2683
+ expect(() => AppConfigSchema.parse(config)).not.toThrow();
2684
+ });
2685
+ });
2686
+
2687
+ describe('GitHub設定', () => {
2688
+ it('有効なGitHub設定を受け入れる', () => {
2689
+ const config = {
2690
+ github: {
2691
+ token: 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
2692
+ username: 'octocat',
2693
+ email: 'octocat@github.com',
2694
+ org: 'myorg'
2695
+ }
2696
+ };
2697
+
2698
+ expect(() => AppConfigSchema.parse(config)).not.toThrow();
2699
+ });
2700
+
2701
+ it('トークンの形式を検証する', () => {
2702
+ const config = {
2703
+ github: {
2704
+ token: 'invalid-token-format',
2705
+ username: 'octocat'
2706
+ }
2707
+ };
2708
+
2709
+ expect(() => AppConfigSchema.parse(config)).toThrow();
2710
+ });
2711
+ });
2712
+ });
2713
+ ```
2714
+
2715
+ ### 8.3 統合テスト
2716
+
2717
+ #### 8.3.1 コマンドラインテスト
2718
+
2719
+ **ファイル**: `src/__tests__/integration/cli.test.ts`
2720
+
2721
+ ```typescript
2722
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2723
+ import { execSync } from 'child_process';
2724
+ import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
2725
+ import { tmpdir } from 'os';
2726
+ import { join } from 'path';
2727
+
2728
+ describe('CLI統合テスト', () => {
2729
+ let testDir: string;
2730
+
2731
+ beforeEach(() => {
2732
+ testDir = join(tmpdir(), `michi-cli-test-${Date.now()}`);
2733
+ mkdirSync(testDir, { recursive: true });
2734
+ process.chdir(testDir);
2735
+ });
2736
+
2737
+ afterEach(() => {
2738
+ rmSync(testDir, { recursive: true, force: true });
2739
+ });
2740
+
2741
+ describe('michi init', () => {
2742
+ it('新規プロジェクトを初期化できる', () => {
2743
+ // Git リポジトリを初期化
2744
+ execSync('git init', { cwd: testDir });
2745
+ execSync('git config user.name "Test User"', { cwd: testDir });
2746
+ execSync('git config user.email "test@example.com"', { cwd: testDir });
2747
+
2748
+ // michi init を実行(対話的入力をスキップするため --yes オプションを使用)
2749
+ const output = execSync('michi init --yes --project-id test-project', {
2750
+ cwd: testDir,
2751
+ encoding: 'utf-8'
2752
+ });
2753
+
2754
+ expect(output).toContain('プロジェクトの初期化が完了しました');
2755
+ expect(existsSync(join(testDir, '.michi', 'project.json'))).toBe(true);
2756
+ expect(existsSync(join(testDir, '.env'))).toBe(true);
2757
+ });
2758
+
2759
+ it('既存プロジェクトにMichiを追加できる (--existing)', () => {
2760
+ // 既存のGitリポジトリをシミュレート
2761
+ execSync('git init', { cwd: testDir });
2762
+ execSync('git remote add origin https://github.com/test/repo.git', { cwd: testDir });
2763
+
2764
+ const output = execSync('michi init --existing --yes', {
2765
+ cwd: testDir,
2766
+ encoding: 'utf-8'
2767
+ });
2768
+
2769
+ expect(output).toContain('既存プロジェクトへの追加が完了しました');
2770
+
2771
+ // project.json の repository が自動設定される
2772
+ const projectJson = JSON.parse(
2773
+ readFileSync(join(testDir, '.michi', 'project.json'), 'utf-8')
2774
+ );
2775
+ expect(projectJson.repository).toBe('https://github.com/test/repo.git');
2776
+ });
2777
+
2778
+ it('Gitリポジトリがない場合、--existing でエラーになる', () => {
2779
+ expect(() => {
2780
+ execSync('michi init --existing --yes', {
2781
+ cwd: testDir,
2782
+ encoding: 'utf-8'
2783
+ });
2784
+ }).toThrow();
2785
+ });
2786
+ });
2787
+
2788
+ describe('michi migrate', () => {
2789
+ beforeEach(() => {
2790
+ // 旧形式の設定ファイルを作成
2791
+ mkdirSync(join(testDir, '.michi'), { recursive: true });
2792
+
2793
+ writeFileSync(join(testDir, '.env'), `
2794
+ CONFLUENCE_URL=https://example.atlassian.net
2795
+ CONFLUENCE_USERNAME=user@example.com
2796
+ CONFLUENCE_API_TOKEN=token123
2797
+ JIRA_URL=https://example.atlassian.net
2798
+ JIRA_USERNAME=user@example.com
2799
+ JIRA_API_TOKEN=token456
2800
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2801
+ GITHUB_USERNAME=testuser
2802
+ GITHUB_EMAIL=test@example.com
2803
+ GITHUB_ORG=myorg
2804
+ GITHUB_REPO=myorg/myrepo
2805
+ `.trim());
2806
+
2807
+ writeFileSync(join(testDir, '.michi', 'project.json'), JSON.stringify({
2808
+ projectId: 'test-project'
2809
+ }, null, 2));
2810
+ });
2811
+
2812
+ it('設定を正しく移行する', () => {
2813
+ const output = execSync('michi migrate --force', {
2814
+ cwd: testDir,
2815
+ encoding: 'utf-8',
2816
+ env: { ...process.env, HOME: testDir }
2817
+ });
2818
+
2819
+ expect(output).toContain('設定の移行が完了しました');
2820
+
2821
+ // グローバル .env が作成される
2822
+ const globalEnv = join(testDir, '.michi', '.env');
2823
+ expect(existsSync(globalEnv)).toBe(true);
2824
+
2825
+ const globalContent = readFileSync(globalEnv, 'utf-8');
2826
+ expect(globalContent).toContain('CONFLUENCE_URL=');
2827
+ expect(globalContent).toContain('JIRA_URL=');
2828
+ expect(globalContent).toContain('GITHUB_TOKEN=');
2829
+
2830
+ // プロジェクト .env から組織設定が削除される
2831
+ const projectEnv = readFileSync(join(testDir, '.env'), 'utf-8');
2832
+ expect(projectEnv).not.toContain('CONFLUENCE_URL=');
2833
+ expect(projectEnv).not.toContain('GITHUB_REPO=');
2834
+
2835
+ // project.json に repository が追加される
2836
+ const projectJson = JSON.parse(
2837
+ readFileSync(join(testDir, '.michi', 'project.json'), 'utf-8')
2838
+ );
2839
+ expect(projectJson.repository).toBe('https://github.com/myorg/myrepo.git');
2840
+ });
2841
+
2842
+ it('--dry-run で変更をシミュレートする', () => {
2843
+ const output = execSync('michi migrate --dry-run', {
2844
+ cwd: testDir,
2845
+ encoding: 'utf-8',
2846
+ env: { ...process.env, HOME: testDir }
2847
+ });
2848
+
2849
+ expect(output).toContain('ドライランモード');
2850
+ expect(output).toContain('実際の変更は行われませんでした');
2851
+
2852
+ // ファイルが変更されていないことを確認
2853
+ const globalEnv = join(testDir, '.michi', '.env');
2854
+ expect(existsSync(globalEnv)).toBe(false);
2855
+ });
2856
+
2857
+ it('バックアップを作成する', () => {
2858
+ execSync('michi migrate --force', {
2859
+ cwd: testDir,
2860
+ encoding: 'utf-8',
2861
+ env: { ...process.env, HOME: testDir }
2862
+ });
2863
+
2864
+ // バックアップディレクトリが作成されている
2865
+ const backups = readdirSync(testDir).filter(f => f.startsWith('.michi-backup-'));
2866
+ expect(backups.length).toBeGreaterThan(0);
2867
+ });
2868
+ });
2869
+
2870
+ describe('michi config:validate', () => {
2871
+ it('有効な設定を検証する', () => {
2872
+ // グローバル設定
2873
+ mkdirSync(join(testDir, '.michi'), { recursive: true });
2874
+ writeFileSync(join(testDir, '.michi', '.env'), `
2875
+ CONFLUENCE_URL=https://example.atlassian.net
2876
+ JIRA_URL=https://example.atlassian.net
2877
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2878
+ `.trim());
2879
+
2880
+ // プロジェクト設定
2881
+ const projectDir = join(testDir, 'project');
2882
+ mkdirSync(join(projectDir, '.michi'), { recursive: true });
2883
+ writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
2884
+ projectId: 'test-project',
2885
+ repository: 'https://github.com/myorg/myrepo.git'
2886
+ }, null, 2));
2887
+
2888
+ const output = execSync('michi config:validate', {
2889
+ cwd: projectDir,
2890
+ encoding: 'utf-8',
2891
+ env: { ...process.env, HOME: testDir }
2892
+ });
2893
+
2894
+ expect(output).toContain('設定ファイルは有効です');
2895
+ });
2896
+
2897
+ it('不正な設定でエラーを表示する', () => {
2898
+ mkdirSync(join(testDir, '.michi'), { recursive: true });
2899
+ writeFileSync(join(testDir, '.env'), `
2900
+ CONFLUENCE_URL=not-a-url
2901
+ `.trim());
2902
+
2903
+ expect(() => {
2904
+ execSync('michi config:validate', {
2905
+ cwd: testDir,
2906
+ encoding: 'utf-8'
2907
+ });
2908
+ }).toThrow();
2909
+ });
2910
+ });
2911
+ });
2912
+ ```
2913
+
2914
+ #### 8.3.2 外部API連携テスト
2915
+
2916
+ **ファイル**: `src/__tests__/integration/external-api.test.ts`
2917
+
2918
+ ```typescript
2919
+ import { describe, it, expect, beforeAll } from 'vitest';
2920
+ import { ConfigLoader } from '../../config/config-loader.js';
2921
+ import { ConfluenceClient } from '../../confluence/client.js';
2922
+ import { JiraClient } from '../../jira/client.js';
2923
+ import { GitHubClient } from '../../github/client.js';
2924
+
2925
+ // 注: これらのテストは実際のAPIキーが必要なため、CI環境では skip される
2926
+ // ローカルで実行する場合は、~/.michi/.env に実際の認証情報を設定すること
2927
+
2928
+ describe('外部API連携テスト', () => {
2929
+ let config: ReturnType<ConfigLoader['load']>;
2930
+
2931
+ beforeAll(() => {
2932
+ const loader = new ConfigLoader(process.cwd());
2933
+ config = loader.load();
2934
+ });
2935
+
2936
+ describe('Confluence連携', () => {
2937
+ it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
2938
+ 'Confluenceに接続できる',
2939
+ async () => {
2940
+ const client = new ConfluenceClient(config);
2941
+ const spaces = await client.getSpaces();
2942
+
2943
+ expect(Array.isArray(spaces)).toBe(true);
2944
+ }
2945
+ );
2946
+
2947
+ it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
2948
+ 'スペース情報を取得できる',
2949
+ async () => {
2950
+ const client = new ConfluenceClient(config);
2951
+ const space = await client.getSpace(config.confluence!.space!);
2952
+
2953
+ expect(space).toBeDefined();
2954
+ expect(space.key).toBe(config.confluence!.space);
2955
+ }
2956
+ );
2957
+ });
2958
+
2959
+ describe('JIRA連携', () => {
2960
+ it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
2961
+ 'JIRAに接続できる',
2962
+ async () => {
2963
+ const client = new JiraClient(config);
2964
+ const projects = await client.getProjects();
2965
+
2966
+ expect(Array.isArray(projects)).toBe(true);
2967
+ }
2968
+ );
2969
+
2970
+ it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
2971
+ 'プロジェクト情報を取得できる',
2972
+ async () => {
2973
+ const client = new JiraClient(config);
2974
+ const project = await client.getProject(config.jira!.project!);
2975
+
2976
+ expect(project).toBeDefined();
2977
+ expect(project.key).toBe(config.jira!.project);
2978
+ }
2979
+ );
2980
+ });
2981
+
2982
+ describe('GitHub連携', () => {
2983
+ it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
2984
+ 'GitHubに接続できる',
2985
+ async () => {
2986
+ const client = new GitHubClient(config);
2987
+ const user = await client.getCurrentUser();
2988
+
2989
+ expect(user).toBeDefined();
2990
+ expect(user.login).toBe(config.github!.username);
2991
+ }
2992
+ );
2993
+
2994
+ it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
2995
+ 'リポジトリ情報を取得できる',
2996
+ async () => {
2997
+ const client = new GitHubClient(config);
2998
+ const repo = await client.getRepository();
2999
+
3000
+ expect(repo).toBeDefined();
3001
+ expect(repo.full_name).toBe(config.github!.repositoryShort);
3002
+ }
3003
+ );
3004
+ });
3005
+ });
3006
+ ```
3007
+
3008
+ ### 8.4 E2Eテスト
3009
+
3010
+ #### 8.4.1 完全なワークフローテスト
3011
+
3012
+ **ファイル**: `src/__tests__/e2e/full-workflow.test.ts`
3013
+
3014
+ ```typescript
3015
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
3016
+ import { execSync } from 'child_process';
3017
+ import { mkdirSync, rmSync } from 'fs';
3018
+ import { tmpdir } from 'os';
3019
+ import { join } from 'path';
3020
+
3021
+ describe('E2E: 完全なワークフロー', () => {
3022
+ let testDir: string;
3023
+ let projectDir: string;
3024
+
3025
+ beforeAll(() => {
3026
+ testDir = join(tmpdir(), `michi-e2e-${Date.now()}`);
3027
+ projectDir = join(testDir, 'my-project');
3028
+
3029
+ mkdirSync(projectDir, { recursive: true });
3030
+ process.chdir(projectDir);
3031
+
3032
+ // Git リポジトリを初期化
3033
+ execSync('git init', { cwd: projectDir });
3034
+ execSync('git config user.name "Test User"', { cwd: projectDir });
3035
+ execSync('git config user.email "test@example.com"', { cwd: projectDir });
3036
+ });
3037
+
3038
+ afterAll(() => {
3039
+ rmSync(testDir, { recursive: true, force: true });
3040
+ });
3041
+
3042
+ it('新規プロジェクト作成 → 設定 → Confluence同期', async () => {
3043
+ // ステップ1: プロジェクト初期化
3044
+ execSync('michi init --yes --project-id my-project', {
3045
+ cwd: projectDir,
3046
+ encoding: 'utf-8'
3047
+ });
3048
+
3049
+ // ステップ2: グローバル設定を作成
3050
+ execSync('npm run config:global', {
3051
+ cwd: projectDir,
3052
+ input: 'n\nn\nn\n', // すべての設定をスキップ
3053
+ encoding: 'utf-8',
3054
+ env: { ...process.env, HOME: testDir }
3055
+ });
3056
+
3057
+ // ステップ3: Confluence同期(ドライラン)
3058
+ const output = execSync(
3059
+ 'michi confluence:sync test-feature requirements --dry-run',
3060
+ {
3061
+ cwd: projectDir,
3062
+ encoding: 'utf-8',
3063
+ env: { ...process.env, HOME: testDir }
3064
+ }
3065
+ );
3066
+
3067
+ expect(output).toContain('ドライランモード');
3068
+ }, 30000); // 30秒タイムアウト
3069
+
3070
+ it('既存プロジェクト → 移行 → 検証', async () => {
3071
+ // ステップ1: 旧形式の設定を作成
3072
+ mkdirSync(join(projectDir, '.michi'), { recursive: true });
3073
+ writeFileSync(join(projectDir, '.env'), `
3074
+ CONFLUENCE_URL=https://example.atlassian.net
3075
+ GITHUB_REPO=myorg/myrepo
3076
+ `.trim());
3077
+
3078
+ // ステップ2: 移行実行
3079
+ execSync('michi migrate --force', {
3080
+ cwd: projectDir,
3081
+ encoding: 'utf-8',
3082
+ env: { ...process.env, HOME: testDir }
3083
+ });
3084
+
3085
+ // ステップ3: 設定検証
3086
+ const output = execSync('michi config:validate', {
3087
+ cwd: projectDir,
3088
+ encoding: 'utf-8',
3089
+ env: { ...process.env, HOME: testDir }
3090
+ });
3091
+
3092
+ expect(output).toContain('設定ファイルは有効です');
3093
+ }, 30000);
3094
+ });
3095
+ ```
3096
+
3097
+ ### 8.5 カバレッジ目標
3098
+
3099
+ #### 8.5.1 目標値
3100
+
3101
+ | カテゴリ | 目標カバレッジ | 現在値 | 期限 |
3102
+ |---------|-------------|--------|------|
3103
+ | **全体** | 95% | TBD | リリース前 |
3104
+ | **ConfigLoader** | 100% | TBD | Week 1 |
3105
+ | **バリデーション** | 100% | TBD | Week 1 |
3106
+ | **CLIコマンド** | 90% | TBD | Week 2 |
3107
+ | **マイグレーション** | 95% | TBD | Week 2 |
3108
+ | **外部API** | 80% | TBD | Week 3 |
3109
+
3110
+ #### 8.5.2 カバレッジ計測
3111
+
3112
+ ```bash
3113
+ # カバレッジレポートの生成
3114
+ npm run test:coverage
3115
+
3116
+ # HTMLレポートの確認
3117
+ open coverage/index.html
3118
+
3119
+ # 最小カバレッジのチェック(CI用)
3120
+ npm run test:coverage -- --min-coverage=95
3121
+ ```
3122
+
3123
+ #### 8.5.3 除外項目
3124
+
3125
+ 以下のファイルはカバレッジ計測から除外します:
3126
+
3127
+ - テストファイル: `**/*.test.ts`, `**/*.spec.ts`
3128
+ - 型定義ファイル: `**/*.d.ts`
3129
+ - 設定ファイル: `**/config/**/*.ts` (一部)
3130
+ - CLIエントリポイント: `src/cli.ts` (メイン関数のみ)
3131
+
3132
+ ### 8.6 テスト実行
3133
+
3134
+ #### 8.6.1 ローカルでのテスト実行
3135
+
3136
+ ```bash
3137
+ # すべてのテストを実行
3138
+ npm run test
3139
+
3140
+ # 単体テストのみ
3141
+ npm run test src/config/__tests__
3142
+
3143
+ # 統合テストのみ
3144
+ npm run test src/__tests__/integration
3145
+
3146
+ # E2Eテストのみ
3147
+ npm run test src/__tests__/e2e
3148
+
3149
+ # ウォッチモード(開発時)
3150
+ npm run test -- --watch
3151
+
3152
+ # 特定のファイルのみ
3153
+ npm run test src/config/__tests__/config-loader.test.ts
3154
+ ```
3155
+
3156
+ #### 8.6.2 CI/CDでのテスト実行
3157
+
3158
+ **GitHub Actions**: `.github/workflows/test.yml`
3159
+
3160
+ ```yaml
3161
+ name: Test
3162
+
3163
+ on:
3164
+ push:
3165
+ branches: [main, develop]
3166
+ pull_request:
3167
+ branches: [main, develop]
3168
+
3169
+ jobs:
3170
+ test:
3171
+ runs-on: ubuntu-latest
3172
+
3173
+ steps:
3174
+ - uses: actions/checkout@v4
3175
+
3176
+ - name: Setup Node.js
3177
+ uses: actions/setup-node@v4
3178
+ with:
3179
+ node-version: '20'
3180
+ cache: 'npm'
3181
+
3182
+ - name: Install dependencies
3183
+ run: npm ci
3184
+
3185
+ - name: Run unit tests
3186
+ run: npm run test:run
3187
+
3188
+ - name: Run integration tests
3189
+ run: npm run test:run src/__tests__/integration
3190
+ env:
3191
+ RUN_INTEGRATION_TESTS: 'false' # CI環境では外部APIテストをスキップ
3192
+
3193
+ - name: Generate coverage report
3194
+ run: npm run test:coverage
3195
+
3196
+ - name: Upload coverage to Codecov
3197
+ uses: codecov/codecov-action@v3
3198
+ with:
3199
+ files: ./coverage/coverage-final.json
3200
+
3201
+ - name: Check coverage threshold
3202
+ run: npm run test:coverage -- --min-coverage=95
3203
+ ```
3204
+
3205
+ #### 8.6.3 テスト実行のベストプラクティス
3206
+
3207
+ 1. **開発時**
3208
+ - ウォッチモードで関連するテストのみを実行
3209
+ - 変更したファイルに対応するテストを優先
3210
+
3211
+ 2. **コミット前**
3212
+ - すべての単体テストを実行
3213
+ - 関連する統合テストを実行
3214
+
3215
+ 3. **PR作成時**
3216
+ - すべてのテストを実行
3217
+ - カバレッジレポートを確認
3218
+
3219
+ 4. **リリース前**
3220
+ - すべてのテストを実行(E2E含む)
3221
+ - 外部APIテストを手動で実行
3222
+ - カバレッジが目標値を満たしているか確認
3223
+
3224
+ ---
3225
+
3226
+ ## 9. セキュリティとパフォーマンス
3227
+
3228
+ 設定統一に伴うセキュリティとパフォーマンスの考慮事項です。
3229
+
3230
+ ### 9.1 セキュリティ対策
3231
+
3232
+ #### 9.1.1 認証情報の保護
3233
+
3234
+ **脅威モデル**
3235
+
3236
+ | 脅威 | 影響 | 対策 |
3237
+ |------|------|------|
3238
+ | **認証情報の漏洩** | 高 | ファイルパーミッション、.gitignore |
3239
+ | **環境変数の漏洩** | 中 | .env.example の提供、警告メッセージ |
3240
+ | **不正アクセス** | 高 | 最小権限の原則、トークンの有効期限 |
3241
+ | **中間者攻撃** | 中 | HTTPS必須、証明書検証 |
3242
+
3243
+ **実装されるセキュリティ対策**
3244
+
3245
+ 1. **ファイルパーミッションの強制**
3246
+
3247
+ ```typescript
3248
+ // scripts/utils/security.ts
3249
+ import { chmodSync, statSync } from 'fs';
3250
+
3251
+ /**
3252
+ * 機密ファイルのパーミッションを強制する
3253
+ */
3254
+ export function enforceSecurePermissions(filePath: string): void {
3255
+ const stats = statSync(filePath);
3256
+ const mode = stats.mode & 0o777;
3257
+
3258
+ // 600 (rw-------) 以外の場合は警告
3259
+ if (mode !== 0o600) {
3260
+ console.warn(`⚠️ 警告: ${filePath} のパーミッションが不適切です`);
3261
+ console.warn(` 現在: ${mode.toString(8)}, 推奨: 600`);
3262
+ console.warn(` 自動的に 600 に変更します...`);
3263
+
3264
+ chmodSync(filePath, 0o600);
3265
+ console.log(`✅ パーミッションを 600 に変更しました`);
3266
+ }
3267
+ }
3268
+
3269
+ /**
3270
+ * グローバル .env ファイルの作成時にパーミッションを設定
3271
+ */
3272
+ export function createSecureEnvFile(filePath: string, content: string): void {
3273
+ writeFileSync(filePath, content, { mode: 0o600 });
3274
+ console.log(`✅ セキュアなファイルを作成しました: ${filePath}`);
3275
+ }
3276
+ ```
3277
+
3278
+ 2. **.gitignore の自動更新**
3279
+
3280
+ ```typescript
3281
+ // scripts/utils/gitignore.ts
3282
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
3283
+
3284
+ /**
3285
+ * .gitignore に機密ファイルを追加する
3286
+ */
3287
+ export function ensureGitignore(projectDir: string): void {
3288
+ const gitignorePath = join(projectDir, '.gitignore');
3289
+ const requiredEntries = [
3290
+ '.env',
3291
+ '.env.local',
3292
+ '~/.michi/.env',
3293
+ '.michi-backup-*/',
3294
+ '.michi/migration.log'
3295
+ ];
3296
+
3297
+ let content = existsSync(gitignorePath)
3298
+ ? readFileSync(gitignorePath, 'utf-8')
3299
+ : '';
3300
+
3301
+ let added = false;
3302
+ for (const entry of requiredEntries) {
3303
+ if (!content.includes(entry)) {
3304
+ content += `\n${entry}`;
3305
+ added = true;
3306
+ }
3307
+ }
3308
+
3309
+ if (added) {
3310
+ writeFileSync(gitignorePath, content.trim() + '\n');
3311
+ console.log('✅ .gitignore を更新しました');
3312
+ }
3313
+ }
3314
+ ```
3315
+
3316
+ 3. **環境変数の検証**
3317
+
3318
+ ```typescript
3319
+ // src/config/validation.ts
3320
+ import { z } from 'zod';
3321
+
3322
+ /**
3323
+ * 認証情報の形式を検証する
3324
+ */
3325
+ export const CredentialsSchema = z.object({
3326
+ // Atlassian API Token (24文字の英数字)
3327
+ confluenceApiToken: z.string()
3328
+ .min(24, 'API token is too short')
3329
+ .regex(/^[A-Za-z0-9]+$/, 'Invalid API token format'),
3330
+
3331
+ jiraApiToken: z.string()
3332
+ .min(24, 'API token is too short')
3333
+ .regex(/^[A-Za-z0-9]+$/, 'Invalid API token format'),
3334
+
3335
+ // GitHub Personal Access Token (ghp_ または ghp_classic プレフィックス)
3336
+ githubToken: z.string()
3337
+ .regex(
3338
+ /^(ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{82})$/,
3339
+ 'Invalid GitHub token format'
3340
+ ),
3341
+ });
3342
+
3343
+ /**
3344
+ * 機密情報が含まれていないかチェック
3345
+ */
3346
+ export function detectSecretsInLogs(message: string): string {
3347
+ // トークンのパターンにマッチする部分をマスクする
3348
+ return message
3349
+ .replace(/ghp_[A-Za-z0-9]{36}/g, 'ghp_****')
3350
+ .replace(/github_pat_[A-Za-z0-9_]{82}/g, 'github_pat_****')
3351
+ .replace(/[A-Za-z0-9]{24,}/g, (match) => {
3352
+ // 24文字以上の英数字はトークンの可能性があるのでマスク
3353
+ return match.substring(0, 4) + '****';
3354
+ });
3355
+ }
3356
+ ```
3357
+
3358
+ #### 9.1.2 ファイルパーミッションの管理
3359
+
3360
+ **パーミッション設定基準**
3361
+
3362
+ | ファイル | パーミッション | 理由 |
3363
+ |---------|--------------|------|
3364
+ | `~/.michi/.env` | 600 (rw-------) | 組織の認証情報を含む |
3365
+ | `.env` | 600 (rw-------) | プロジェクトの認証情報を含む |
3366
+ | `.michi/config.json` | 644 (rw-r--r--) | 認証情報を含まない |
3367
+ | `.michi/project.json` | 644 (rw-r--r--) | 認証情報を含まない |
3368
+ | `.michi-backup-*/*` | 700 (rwx------) | バックアップディレクトリ |
3369
+
3370
+ **自動チェック機構**
3371
+
3372
+ ```typescript
3373
+ // src/config/config-loader.ts (updated)
3374
+ export class ConfigLoader {
3375
+ private validateFilePermissions(): void {
3376
+ const sensitiveFiles = [
3377
+ this.globalEnvPath,
3378
+ join(this.projectDir, '.env')
3379
+ ];
3380
+
3381
+ for (const filePath of sensitiveFiles) {
3382
+ if (!existsSync(filePath)) continue;
3383
+
3384
+ const stats = statSync(filePath);
3385
+ const mode = stats.mode & 0o777;
3386
+
3387
+ if (mode > 0o600) {
3388
+ throw new SecurityError(
3389
+ 'INSECURE_PERMISSIONS',
3390
+ `File ${filePath} has insecure permissions: ${mode.toString(8)}`,
3391
+ { filePath, mode, expected: 0o600 }
3392
+ );
3393
+ }
3394
+ }
3395
+ }
3396
+
3397
+ public load(): AppConfig {
3398
+ // パーミッションチェック
3399
+ this.validateFilePermissions();
3400
+
3401
+ // 設定読み込み
3402
+ // ...
3403
+ }
3404
+ }
3405
+ ```
3406
+
3407
+ #### 9.1.3 入力検証
3408
+
3409
+ **URL検証**
3410
+
3411
+ ```typescript
3412
+ // src/config/validators.ts
3413
+ import { z } from 'zod';
3414
+
3415
+ /**
3416
+ * Atlassian URL(Confluence/JIRA)の検証
3417
+ */
3418
+ export const AtlassianUrlSchema = z.string()
3419
+ .url('Invalid URL format')
3420
+ .regex(
3421
+ /^https:\/\/[a-zA-Z0-9-]+\.atlassian\.net$/,
3422
+ 'Must be a valid Atlassian Cloud URL (https://xxx.atlassian.net)'
3423
+ );
3424
+
3425
+ /**
3426
+ * GitHub リポジトリURLの検証
3427
+ */
3428
+ export const GitHubRepoUrlSchema = z.string()
3429
+ .refine(
3430
+ (url) => {
3431
+ const httpsPattern = /^https:\/\/github\.com\/[^/]+\/[^/]+(\.git)?$/;
3432
+ const sshPattern = /^git@github\.com:[^/]+\/[^/]+(\.git)?$/;
3433
+ return httpsPattern.test(url) || sshPattern.test(url);
3434
+ },
3435
+ 'Must be a valid GitHub repository URL'
3436
+ );
3437
+
3438
+ /**
3439
+ * メールアドレスの検証
3440
+ */
3441
+ export const EmailSchema = z.string()
3442
+ .email('Invalid email format')
3443
+ .max(254, 'Email too long');
3444
+ ```
3445
+
3446
+ **コマンドインジェクション対策**
3447
+
3448
+ ```typescript
3449
+ // scripts/utils/exec-safe.ts
3450
+ import { execSync } from 'child_process';
3451
+
3452
+ /**
3453
+ * 安全なコマンド実行(シェルインジェクション対策)
3454
+ */
3455
+ export function execSafe(
3456
+ command: string,
3457
+ args: string[],
3458
+ options?: any
3459
+ ): string {
3460
+ // コマンドと引数を分離して実行
3461
+ // execSync の shell: false オプションを使用
3462
+ const fullCommand = `${command} ${args.map(escapeArg).join(' ')}`;
3463
+
3464
+ return execSync(fullCommand, {
3465
+ ...options,
3466
+ shell: false, // シェルを経由しない
3467
+ encoding: 'utf-8'
3468
+ });
3469
+ }
3470
+
3471
+ /**
3472
+ * シェル引数のエスケープ
3473
+ */
3474
+ function escapeArg(arg: string): string {
3475
+ // シングルクォートで囲み、内部のシングルクォートをエスケープ
3476
+ return `'${arg.replace(/'/g, "'\\''")}'`;
3477
+ }
3478
+ ```
3479
+
3480
+ #### 9.1.4 セキュアなデフォルト設定
3481
+
3482
+ **デフォルト値の設計**
3483
+
3484
+ ```typescript
3485
+ // src/config/defaults.ts
3486
+
3487
+ export const SECURE_DEFAULTS = {
3488
+ // HTTPS 強制
3489
+ confluence: {
3490
+ useHttps: true,
3491
+ verifySsl: true,
3492
+ },
3493
+
3494
+ jira: {
3495
+ useHttps: true,
3496
+ verifySsl: true,
3497
+ },
3498
+
3499
+ github: {
3500
+ useHttps: true,
3501
+ },
3502
+
3503
+ // API呼び出しのタイムアウト(DoS対策)
3504
+ api: {
3505
+ timeout: 30000, // 30秒
3506
+ maxRetries: 3,
3507
+ retryDelay: 1000, // 1秒
3508
+ },
3509
+
3510
+ // ロギング
3511
+ logging: {
3512
+ maskSecrets: true, // 機密情報を自動マスク
3513
+ level: 'info',
3514
+ },
3515
+ } as const;
3516
+ ```
3517
+
3518
+ **設定値のサニタイズ**
3519
+
3520
+ ```typescript
3521
+ // src/config/sanitize.ts
3522
+
3523
+ /**
3524
+ * ログ出力前に機密情報をマスクする
3525
+ */
3526
+ export function sanitizeForLog(config: AppConfig): AppConfig {
3527
+ return {
3528
+ ...config,
3529
+ confluence: config.confluence ? {
3530
+ ...config.confluence,
3531
+ apiToken: '****',
3532
+ password: '****',
3533
+ } : undefined,
3534
+ jira: config.jira ? {
3535
+ ...config.jira,
3536
+ apiToken: '****',
3537
+ password: '****',
3538
+ } : undefined,
3539
+ github: config.github ? {
3540
+ ...config.github,
3541
+ token: config.github.token?.substring(0, 7) + '****',
3542
+ } : undefined,
3543
+ };
3544
+ }
3545
+ ```
3546
+
3547
+ ### 9.2 パフォーマンス最適化
3548
+
3549
+ #### 9.2.1 設定読み込みの最適化
3550
+
3551
+ **問題点**
3552
+
3553
+ - 毎回ファイルを読み込むとI/Oコストが高い
3554
+ - 複数のコマンド実行時に同じファイルを何度も読む
3555
+ - Zodバリデーションが重い
3556
+
3557
+ **解決策: キャッシュ機構**
3558
+
3559
+ ```typescript
3560
+ // src/config/config-loader.ts (updated)
3561
+
3562
+ export class ConfigLoader {
3563
+ private static cache: Map<string, {
3564
+ config: AppConfig;
3565
+ timestamp: number;
3566
+ hash: string;
3567
+ }> = new Map();
3568
+
3569
+ private static CACHE_TTL = 60000; // 1分
3570
+
3571
+ /**
3572
+ * ファイルのハッシュ値を計算
3573
+ */
3574
+ private getFileHash(filePath: string): string {
3575
+ if (!existsSync(filePath)) return '';
3576
+
3577
+ const content = readFileSync(filePath, 'utf-8');
3578
+ return createHash('sha256').update(content).digest('hex');
3579
+ }
3580
+
3581
+ /**
3582
+ * キャッシュキーを生成
3583
+ */
3584
+ private getCacheKey(): string {
3585
+ const globalHash = this.getFileHash(this.globalEnvPath);
3586
+ const projectConfigHash = this.getFileHash(this.projectConfigPath);
3587
+ const projectEnvHash = this.getFileHash(this.projectEnvPath);
3588
+
3589
+ return `${globalHash}:${projectConfigHash}:${projectEnvHash}`;
3590
+ }
3591
+
3592
+ /**
3593
+ * キャッシュから設定を取得
3594
+ */
3595
+ public load(options: { noCache?: boolean } = {}): AppConfig {
3596
+ const cacheKey = this.getCacheKey();
3597
+ const now = Date.now();
3598
+
3599
+ // キャッシュチェック
3600
+ if (!options.noCache) {
3601
+ const cached = ConfigLoader.cache.get(cacheKey);
3602
+ if (cached && now - cached.timestamp < ConfigLoader.CACHE_TTL) {
3603
+ return cached.config;
3604
+ }
3605
+ }
3606
+
3607
+ // 設定を読み込み
3608
+ const config = this.loadInternal();
3609
+
3610
+ // キャッシュに保存
3611
+ ConfigLoader.cache.set(cacheKey, {
3612
+ config,
3613
+ timestamp: now,
3614
+ hash: cacheKey
3615
+ });
3616
+
3617
+ return config;
3618
+ }
3619
+
3620
+ /**
3621
+ * キャッシュをクリア
3622
+ */
3623
+ public static clearCache(): void {
3624
+ ConfigLoader.cache.clear();
3625
+ }
3626
+ }
3627
+ ```
3628
+
3629
+ **ベンチマーク目標**
3630
+
3631
+ | 操作 | 目標時間 | 現在値 |
3632
+ |------|---------|--------|
3633
+ | 初回読み込み | < 100ms | TBD |
3634
+ | キャッシュヒット | < 10ms | TBD |
3635
+ | バリデーション | < 50ms | TBD |
3636
+ | マージ処理 | < 20ms | TBD |
3637
+
3638
+ #### 9.2.2 メモリ使用量
3639
+
3640
+ **メモリプロファイリング**
3641
+
3642
+ ```typescript
3643
+ // scripts/benchmark/memory-profile.ts
3644
+ import { ConfigLoader } from '../src/config/config-loader.js';
3645
+
3646
+ function measureMemoryUsage() {
3647
+ const before = process.memoryUsage();
3648
+
3649
+ // 100回設定を読み込む
3650
+ for (let i = 0; i < 100; i++) {
3651
+ const loader = new ConfigLoader(process.cwd());
3652
+ loader.load();
3653
+ }
3654
+
3655
+ const after = process.memoryUsage();
3656
+
3657
+ console.log('Memory Usage:');
3658
+ console.log(` Heap Used: ${(after.heapUsed - before.heapUsed) / 1024 / 1024}MB`);
3659
+ console.log(` External: ${(after.external - before.external) / 1024 / 1024}MB`);
3660
+ }
3661
+
3662
+ measureMemoryUsage();
3663
+ ```
3664
+
3665
+ **最適化目標**
3666
+
3667
+ - 1回の設定読み込み: < 5MB
3668
+ - キャッシュ保持: < 10MB (100エントリ)
3669
+ - ピークメモリ: < 50MB
3670
+
3671
+ #### 9.2.3 キャッシュ戦略
3672
+
3673
+ **多層キャッシュ**
3674
+
3675
+ ```typescript
3676
+ // src/config/cache-strategy.ts
3677
+
3678
+ interface CacheStrategy {
3679
+ get(key: string): AppConfig | null;
3680
+ set(key: string, value: AppConfig): void;
3681
+ invalidate(key: string): void;
3682
+ clear(): void;
3683
+ }
3684
+
3685
+ /**
3686
+ * LRU(Least Recently Used)キャッシュ
3687
+ */
3688
+ class LRUCache implements CacheStrategy {
3689
+ private cache: Map<string, { value: AppConfig; timestamp: number }>;
3690
+ private maxSize: number;
3691
+
3692
+ constructor(maxSize: number = 100) {
3693
+ this.cache = new Map();
3694
+ this.maxSize = maxSize;
3695
+ }
3696
+
3697
+ get(key: string): AppConfig | null {
3698
+ const entry = this.cache.get(key);
3699
+ if (!entry) return null;
3700
+
3701
+ // アクセスされたエントリを最後に移動(LRU)
3702
+ this.cache.delete(key);
3703
+ this.cache.set(key, entry);
3704
+
3705
+ return entry.value;
3706
+ }
3707
+
3708
+ set(key: string, value: AppConfig): void {
3709
+ // サイズ制限チェック
3710
+ if (this.cache.size >= this.maxSize) {
3711
+ // 最も古いエントリを削除
3712
+ const oldestKey = this.cache.keys().next().value;
3713
+ this.cache.delete(oldestKey);
3714
+ }
3715
+
3716
+ this.cache.set(key, {
3717
+ value,
3718
+ timestamp: Date.now()
3719
+ });
3720
+ }
3721
+
3722
+ invalidate(key: string): void {
3723
+ this.cache.delete(key);
3724
+ }
3725
+
3726
+ clear(): void {
3727
+ this.cache.clear();
3728
+ }
3729
+ }
3730
+
3731
+ /**
3732
+ * TTL(Time To Live)付きキャッシュ
3733
+ */
3734
+ class TTLCache implements CacheStrategy {
3735
+ private cache: Map<string, { value: AppConfig; expiry: number }>;
3736
+ private ttl: number;
3737
+
3738
+ constructor(ttl: number = 60000) {
3739
+ this.cache = new Map();
3740
+ this.ttl = ttl;
3741
+ }
3742
+
3743
+ get(key: string): AppConfig | null {
3744
+ const entry = this.cache.get(key);
3745
+ if (!entry) return null;
3746
+
3747
+ // 有効期限チェック
3748
+ if (Date.now() > entry.expiry) {
3749
+ this.cache.delete(key);
3750
+ return null;
3751
+ }
3752
+
3753
+ return entry.value;
3754
+ }
3755
+
3756
+ set(key: string, value: AppConfig): void {
3757
+ this.cache.set(key, {
3758
+ value,
3759
+ expiry: Date.now() + this.ttl
3760
+ });
3761
+ }
3762
+
3763
+ invalidate(key: string): void {
3764
+ this.cache.delete(key);
3765
+ }
3766
+
3767
+ clear(): void {
3768
+ this.cache.clear();
3769
+ }
3770
+ }
3771
+ ```
3772
+
3773
+ ### 9.3 監査とロギング
3774
+
3775
+ #### 9.3.1 設定変更の監査ログ
3776
+
3777
+ ```typescript
3778
+ // src/config/audit-log.ts
3779
+ import { writeFileSync, appendFileSync, existsSync } from 'fs';
3780
+ import { join } from 'path';
3781
+
3782
+ interface AuditLogEntry {
3783
+ timestamp: string;
3784
+ user: string;
3785
+ action: string;
3786
+ target: string;
3787
+ changes: Record<string, { old: any; new: any }>;
3788
+ success: boolean;
3789
+ error?: string;
3790
+ }
3791
+
3792
+ export class AuditLogger {
3793
+ private logPath: string;
3794
+
3795
+ constructor(projectDir: string) {
3796
+ this.logPath = join(projectDir, '.michi', 'audit.log');
3797
+ }
3798
+
3799
+ /**
3800
+ * 設定変更を記録
3801
+ */
3802
+ public logConfigChange(
3803
+ action: string,
3804
+ target: string,
3805
+ changes: Record<string, { old: any; new: any }>,
3806
+ success: boolean,
3807
+ error?: string
3808
+ ): void {
3809
+ const entry: AuditLogEntry = {
3810
+ timestamp: new Date().toISOString(),
3811
+ user: process.env.USER || 'unknown',
3812
+ action,
3813
+ target,
3814
+ changes: this.sanitizeChanges(changes),
3815
+ success,
3816
+ error
3817
+ };
3818
+
3819
+ const logLine = JSON.stringify(entry) + '\n';
3820
+
3821
+ if (!existsSync(this.logPath)) {
3822
+ writeFileSync(this.logPath, logLine, { mode: 0o600 });
3823
+ } else {
3824
+ appendFileSync(this.logPath, logLine);
3825
+ }
3826
+ }
3827
+
3828
+ /**
3829
+ * 機密情報をマスク
3830
+ */
3831
+ private sanitizeChanges(
3832
+ changes: Record<string, { old: any; new: any }>
3833
+ ): Record<string, { old: any; new: any }> {
3834
+ const sensitiveKeys = ['apiToken', 'token', 'password', 'secret'];
3835
+
3836
+ const sanitized: Record<string, { old: any; new: any }> = {};
3837
+
3838
+ for (const [key, value] of Object.entries(changes)) {
3839
+ if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
3840
+ sanitized[key] = {
3841
+ old: '****',
3842
+ new: '****'
3843
+ };
3844
+ } else {
3845
+ sanitized[key] = value;
3846
+ }
3847
+ }
3848
+
3849
+ return sanitized;
3850
+ }
3851
+
3852
+ /**
3853
+ * 監査ログを取得
3854
+ */
3855
+ public getAuditLog(limit?: number): AuditLogEntry[] {
3856
+ if (!existsSync(this.logPath)) return [];
3857
+
3858
+ const content = readFileSync(this.logPath, 'utf-8');
3859
+ const lines = content.trim().split('\n');
3860
+
3861
+ const entries = lines
3862
+ .map(line => {
3863
+ try {
3864
+ return JSON.parse(line) as AuditLogEntry;
3865
+ } catch {
3866
+ return null;
3867
+ }
3868
+ })
3869
+ .filter((entry): entry is AuditLogEntry => entry !== null);
3870
+
3871
+ if (limit) {
3872
+ return entries.slice(-limit);
3873
+ }
3874
+
3875
+ return entries;
3876
+ }
3877
+ }
3878
+ ```
3879
+
3880
+ **使用例**
3881
+
3882
+ ```typescript
3883
+ // michi migrate コマンド内
3884
+ const auditLogger = new AuditLogger(projectDir);
3885
+
3886
+ try {
3887
+ // 移行前の設定を記録
3888
+ const oldConfig = readOldConfig();
3889
+ const newConfig = performMigration();
3890
+
3891
+ // 変更内容を計算
3892
+ const changes = calculateChanges(oldConfig, newConfig);
3893
+
3894
+ // 成功をログ
3895
+ auditLogger.logConfigChange(
3896
+ 'migrate',
3897
+ '~/.michi/.env',
3898
+ changes,
3899
+ true
3900
+ );
3901
+ } catch (error) {
3902
+ // 失敗をログ
3903
+ auditLogger.logConfigChange(
3904
+ 'migrate',
3905
+ '~/.michi/.env',
3906
+ {},
3907
+ false,
3908
+ error.message
3909
+ );
3910
+ throw error;
3911
+ }
3912
+ ```
3913
+
3914
+ #### 9.3.2 セキュリティイベントの記録
3915
+
3916
+ ```typescript
3917
+ // src/config/security-logger.ts
3918
+
3919
+ export class SecurityLogger {
3920
+ /**
3921
+ * 不正なアクセス試行を記録
3922
+ */
3923
+ public logUnauthorizedAccess(
3924
+ resource: string,
3925
+ reason: string
3926
+ ): void {
3927
+ const event = {
3928
+ timestamp: new Date().toISOString(),
3929
+ type: 'UNAUTHORIZED_ACCESS',
3930
+ resource,
3931
+ reason,
3932
+ user: process.env.USER,
3933
+ pid: process.pid
3934
+ };
3935
+
3936
+ console.error('🚨 セキュリティイベント:', JSON.stringify(event));
3937
+
3938
+ // セキュリティログに記録
3939
+ this.appendToSecurityLog(event);
3940
+ }
3941
+
3942
+ /**
3943
+ * 機密ファイルへのアクセスを記録
3944
+ */
3945
+ public logSensitiveFileAccess(
3946
+ filePath: string,
3947
+ operation: 'read' | 'write'
3948
+ ): void {
3949
+ const event = {
3950
+ timestamp: new Date().toISOString(),
3951
+ type: 'SENSITIVE_FILE_ACCESS',
3952
+ filePath,
3953
+ operation,
3954
+ user: process.env.USER,
3955
+ pid: process.pid
3956
+ };
3957
+
3958
+ this.appendToSecurityLog(event);
3959
+ }
3960
+
3961
+ private appendToSecurityLog(event: any): void {
3962
+ const logPath = join(os.homedir(), '.michi', 'security.log');
3963
+ const logLine = JSON.stringify(event) + '\n';
3964
+
3965
+ mkdirSync(dirname(logPath), { recursive: true });
3966
+ appendFileSync(logPath, logLine, { mode: 0o600 });
3967
+ }
3968
+ }
3969
+ ```
3970
+
3971
+ ### 9.4 セキュリティチェックリスト
3972
+
3973
+ リリース前に確認すべき項目:
3974
+
3975
+ - [ ] すべての機密ファイルが .gitignore に追加されている
3976
+ - [ ] ファイルパーミッションが適切(600 for .env files)
3977
+ - [ ] Zodバリデーションがすべての入力に適用されている
3978
+ - [ ] ログに機密情報が含まれていない
3979
+ - [ ] HTTPS が強制されている
3980
+ - [ ] エラーメッセージに内部情報が含まれていない
3981
+ - [ ] セキュリティテストが全てパスしている
3982
+ - [ ] 監査ログが正しく記録されている
3983
+ - [ ] セキュリティドキュメントが最新である
3984
+ - [ ] 脆弱性スキャンを実行している(npm audit)
3985
+
3986
+ ---
3987
+
3988
+ ## 10. 後方互換性
3989
+
3990
+ 既存ユーザーへの影響を最小限に抑えるための後方互換性戦略です。
3991
+
3992
+ ### 10.1 互換性レベル
3993
+
3994
+ #### 10.1.1 完全互換(継続サポート)
3995
+
3996
+ 以下の機能は引き続きサポートされます:
3997
+
3998
+ | 機能 | 動作 | サポート期限 |
3999
+ |------|------|------------|
4000
+ | `.env` ファイル(プロジェクト) | 引き続き使用可能 | 無期限 |
4001
+ | `.michi/config.json` | 引き続き使用可能 | 無期限 |
4002
+ | `.michi/project.json` | 引き続き使用可能 | 無期限 |
4003
+ | `npm run config:global` | グローバル設定作成 | 無期限 |
4004
+ | 既存のCLIコマンド | すべて継続動作 | 無期限 |
4005
+
4006
+ #### 10.1.2 破壊的変更(v0.5.0)
4007
+
4008
+ **重要**: v0.5.0 では、シンプルな設計を実現するために破壊的変更が含まれます。
4009
+
4010
+ **削除される機能:**
4011
+ - `GITHUB_REPO` 環境変数(`.env` 内)
4012
+ - `setup-existing` コマンド(`michi init --existing` に統一)
4013
+
4014
+ **移行が必要なユーザー:**
4015
+ - `.env` で `GITHUB_REPO` を使用しているユーザー
4016
+ - `setup-existing` コマンドを使用しているユーザー
4017
+
4018
+ **移行方法:**
4019
+ `michi migrate` コマンドで自動移行が可能です。
4020
+
4021
+ ### 10.2 移行ガイド
4022
+
4023
+ #### 10.2.1 GITHUB_REPO の移行
4024
+
4025
+ v0.5.0 では、`GITHUB_REPO` 環境変数は削除されます。代わりに `.kiro/project.json` の `repository` フィールドを使用します。
4026
+
4027
+ **移行方法:**
4028
+
4029
+ 1. **自動移行(推奨):**
4030
+ ```bash
4031
+ michi migrate
4032
+ ```
4033
+
4034
+ 2. **手動移行:**
4035
+ - `.env` から `GITHUB_REPO` を削除
4036
+ - `.kiro/project.json` に `repository` を追加:
4037
+ ```json
4038
+ {
4039
+ "repository": "https://github.com/myorg/myrepo"
4040
+ }
4041
+ ```
4042
+
4043
+ **ConfigLoader の実装:**
4044
+
4045
+ ```typescript
4046
+ // ConfigLoader は repository から org/repo 形式を自動抽出
4047
+ const parsed = this.parseGitHubRepository(merged.project.repository);
4048
+ merged.github = {
4049
+ ...merged.github,
4050
+ repository: parsed.url,
4051
+ repositoryShort: parsed.shortForm, // "org/repo"
4052
+ repositoryOrg: parsed.org,
4053
+ repositoryName: parsed.repo,
4054
+ };
4055
+ ```
4056
+
4057
+ #### 10.2.2 setup-existing コマンドの移行
4058
+
4059
+ v0.5.0 では、`setup-existing` コマンドは削除されます。代わりに `michi init --existing` を使用します。
4060
+
4061
+ **移行方法:**
4062
+
4063
+ すべての `setup-existing` の使用を `michi init --existing` に置き換えてください。
4064
+
4065
+ ```bash
4066
+ # 変更前
4067
+ npx @sk8metal/michi-cli setup-existing
4068
+
4069
+ # 変更後
4070
+ michi init --existing
4071
+ ```
4072
+
4073
+ ### 10.3 バージョン間の互換性マトリクス
4074
+
4075
+ | 機能 | v0.4.0 (現在) | v0.5.0 (Breaking Change) | v1.0.0 |
4076
+ |------|-------------|------------------------|--------|
4077
+ | **GITHUB_REPO** | ✅ サポート | ❌ **削除** | - |
4078
+ | **setup-existing** | ✅ サポート | ❌ **削除** | - |
4079
+ | **~/.michi/.env** | ❌ 未サポート | ✅ **新規追加** | ✅ サポート |
4080
+ | **project.json.repository** | ❌ 未サポート | ✅ **必須** | ✅ 必須 |
4081
+ | **michi migrate** | ❌ 未サポート | ✅ **新規追加** | ✅ サポート |
4082
+ | **michi init --existing** | ❌ 未サポート | ✅ **新規追加** | ✅ サポート |
4083
+
4084
+ ### 10.4 アップグレードガイド
4085
+
4086
+ #### 10.4.1 v0.4.0 → v0.5.0 (Breaking Change)
4087
+
4088
+ **重要**: v0.5.0 は Breaking Change を含むため、移行が必須です。
4089
+
4090
+ **アップグレード手順**:
4091
+
4092
+ 1. **バックアップ作成**
4093
+ ```bash
4094
+ cp -r .michi .michi.backup
4095
+ cp -r .kiro .kiro.backup
4096
+ cp .env .env.backup
4097
+ ```
4098
+
4099
+ 2. **Michi をアップグレード**
4100
+ ```bash
4101
+ npm install -g @sk8metal/michi-cli@0.5.0
4102
+ ```
4103
+
4104
+ 3. **移行ツールを実行(必須)**
4105
+ ```bash
4106
+ michi migrate
4107
+ ```
4108
+
4109
+ 移行ツールは以下を自動的に行います:
4110
+ - `GITHUB_REPO` を `.kiro/project.json` の `repository` に移行
4111
+ - `.env` から組織共通設定を `~/.michi/.env` に抽出
4112
+ - `.env` にプロジェクト固有設定のみを残す
4113
+
4114
+ 4. **動作確認**
4115
+ ```bash
4116
+ michi config:validate
4117
+ michi confluence:sync my-feature --dry-run
4118
+ ```
4119
+
4120
+ 5. **コマンド使用の更新**
4121
+ - `setup-existing` → `michi init --existing` に変更
4122
+ - スクリプトやドキュメントを更新
4123
+
4124
+ **移行しない場合**:
4125
+
4126
+ v0.4.0 のまま使用を続けることを推奨します。v0.5.0 は Breaking Change のため、移行なしではエラーが発生します。
4127
+
4128
+ ### 10.5 ダウングレード
4129
+
4130
+ v0.5.0 から v0.4.0 へのダウングレードは可能ですが、以下の制限があります:
4131
+
4132
+ **ダウングレード手順**:
4133
+
4134
+ ```bash
4135
+ # 1. グローバル設定を .env に戻す
4136
+ cat ~/.michi/.env >> .env
4137
+
4138
+ # 2. project.json から GITHUB_REPO を .env に追加
4139
+ echo "GITHUB_REPO=myorg/myrepo" >> .env
4140
+
4141
+ # 3. Michi をダウングレード
4142
+ npm install -g @sk8metal/michi-cli@0.4.0
4143
+
4144
+ # 4. グローバル設定を削除(オプション)
4145
+ rm ~/.michi/.env
4146
+ ```
4147
+
4148
+ **注意**:
4149
+ - 一度 v0.5.0 で作成した設定は、v0.4.0 では一部認識されません
4150
+ - ダウングレードは緊急時のみ推奨されます
4151
+
4152
+ ---
4153
+
4154
+ ## 11. ロードマップ
4155
+
4156
+ 設定統一機能の今後の開発計画です。
4157
+
4158
+ ### 11.1 短期(v0.5.0 - v0.6.0)
4159
+
4160
+ **v0.5.0: 設定統一の導入(Breaking Change)(2025 Q1)**
4161
+
4162
+ - [x] 3層設定階層の実装
4163
+ - [x] `~/.michi/.env` グローバル設定
4164
+ - [x] `michi migrate` 移行ツール
4165
+ - [x] `michi init --existing` 自動検出
4166
+ - [x] ConfigLoader のキャッシュ機構
4167
+ - [x] セキュリティ強化(パーミッション、バリデーション)
4168
+ - [x] **Breaking Change**: `GITHUB_REPO` 削除
4169
+ - [x] **Breaking Change**: `setup-existing` 削除
4170
+ - [ ] テストカバレッジ 95%
4171
+ - [ ] ドキュメント整備
4172
+
4173
+ **v0.5.1: バグ修正とフィードバック対応(2025 Q1)**
4174
+
4175
+ - [ ] ユーザーフィードバックに基づくバグ修正
4176
+ - [ ] エラーメッセージの改善
4177
+ - [ ] パフォーマンス最適化
4178
+ - [ ] ドキュメントの改善
4179
+
4180
+ **v0.6.0: 機能拡張(2025 Q2)**
4181
+
4182
+ - [ ] 移行ツールの改善
4183
+ - [ ] CI/CD テンプレートの提供
4184
+ - [ ] 設定のバリデーション強化
4185
+ - [ ] 設定のインポート/エクスポート機能
4186
+
4187
+ ### 11.2 中期(v0.7.0 - v0.9.0)
4188
+
4189
+ **v0.7.0: マルチ組織サポート(2025 Q2-Q3)**
4190
+
4191
+ 現在の制限事項:グローバル設定は1つの組織のみサポート
4192
+
4193
+ **提案される解決策:プロファイル機能**
4194
+
4195
+ ```bash
4196
+ # プロファイルの作成
4197
+ $ michi profile create work
4198
+ $ michi profile create personal
4199
+
4200
+ # プロファイルの切り替え
4201
+ $ michi profile use work
4202
+
4203
+ # プロファイル一覧
4204
+ $ michi profile list
4205
+ * work (active)
4206
+ personal
4207
+
4208
+ # プロファイルごとの設定
4209
+ ~/.michi/profiles/work/.env
4210
+ ~/.michi/profiles/personal/.env
4211
+ ```
4212
+
4213
+ **設定ファイル構造**:
4214
+
4215
+ ```
4216
+ ~/.michi/
4217
+ ├── .env (デフォルトプロファイル)
4218
+ ├── profiles/
4219
+ │ ├── work/
4220
+ │ │ └── .env
4221
+ │ └── personal/
4222
+ │ └── .env
4223
+ └── config.json (アクティブなプロファイル情報)
4224
+ ```
4225
+
4226
+ **v0.8.0: 設定のバリデーション強化(2025 Q3)**
4227
+
4228
+ - [ ] より詳細なエラーメッセージ
4229
+ - [ ] 設定の自動修復機能
4230
+ - [ ] 設定のインポート/エクスポート
4231
+ - [ ] 設定のバックアップ/復元
4232
+
4233
+ **v0.9.0: パフォーマンス最適化(2025 Q4)**
4234
+
4235
+ - [ ] 設定読み込みの高速化(目標: <50ms)
4236
+ - [ ] メモリ使用量の削減(目標: <30MB)
4237
+ - [ ] 大規模プロジェクトでのパフォーマンステスト
4238
+ - [ ] ベンチマーク結果の公開
4239
+
4240
+ ### 11.3 長期(v1.0.0+)
4241
+
4242
+ **v1.0.0: 安定版リリース(2026 Q1)**
4243
+
4244
+ - [ ] API の安定化
4245
+ - [ ] セマンティックバージョニングの厳格化
4246
+ - [ ] 長期サポート(LTS)の開始
4247
+ - [ ] パフォーマンスの最終最適化
4248
+ - [ ] セキュリティ監査の実施
4249
+
4250
+ **v1.1.0: 高度な設定管理(2026 Q2)**
4251
+
4252
+ - [ ] 設定の暗号化サポート
4253
+ - [ ] 環境変数の動的置換(`${VAR}` 構文)
4254
+ - [ ] 設定のテンプレート機能
4255
+ - [ ] チーム間での設定共有機能
4256
+
4257
+ **v1.2.0: クラウド統合(2026 Q3)**
4258
+
4259
+ - [ ] 設定のクラウド同期(オプション)
4260
+ - [ ] シークレット管理サービスとの統合
4261
+ - AWS Secrets Manager
4262
+ - Google Secret Manager
4263
+ - HashiCorp Vault
4264
+ - [ ] チーム設定の一元管理
4265
+
4266
+ ### 11.4 検討中の機能
4267
+
4268
+ 以下の機能は将来的に検討されていますが、実装は未定です:
4269
+
4270
+ **設定のGUI管理**
4271
+ - Webベースの設定管理インターフェース
4272
+ - 視覚的な設定エディタ
4273
+ - 設定の差分表示
4274
+
4275
+ **AI支援設定**
4276
+ - プロジェクトの自動検出と推奨設定
4277
+ - 設定エラーの自動修正
4278
+ - 最適な設定の提案
4279
+
4280
+ **マルチプラットフォーム対応**
4281
+ - Windows での完全サポート
4282
+ - Dockerコンテナでの使用最適化
4283
+ - CI/CD環境での専用サポート
4284
+
4285
+ ### 11.5 コミュニティフィードバック
4286
+
4287
+ ロードマップは以下の方法でフィードバックを受け付けています:
4288
+
4289
+ - **GitHub Discussions**: 機能リクエスト、質問
4290
+ - **GitHub Issues**: バグレポート、改善提案
4291
+ - **Pull Requests**: 機能実装、ドキュメント改善
4292
+
4293
+ 優先順位は以下の基準で決定されます:
4294
+
4295
+ 1. ユーザーからの要望の多さ
4296
+ 2. セキュリティへの影響
4297
+ 3. 実装の複雑さ
4298
+ 4. 既存機能との互換性
4299
+
4300
+ ---
4301
+
4302
+ ## 付録 A: 用語集
4303
+
4304
+ このドキュメントで使用される主要な用語の定義です。
4305
+
4306
+ | 用語 | 定義 |
4307
+ |------|------|
4308
+ | **グローバル設定** | `~/.michi/.env` に保存される、すべてのプロジェクトで共有される設定 |
4309
+ | **プロジェクト設定** | `.michi/config.json` に保存される、プロジェクト固有の設定 |
4310
+ | **プロジェクト環境** | `.env` に保存される、プロジェクトの環境変数 |
4311
+ | **3層マージ** | グローバル設定、プロジェクト設定、プロジェクト環境を統合するプロセス |
4312
+ | **ConfigLoader** | 設定を読み込み、マージ、バリデーションを行うクラス |
4313
+ | **マイグレーション** | 旧形式の設定を新形式に変換するプロセス |
4314
+ | **後方互換性** | 既存のコードや設定が新バージョンでも動作すること |
4315
+ | **非推奨(Deprecated)** | 将来削除される予定の機能 |
4316
+
4317
+ ---
4318
+
4319
+ ## 付録 B: FAQ(よくある質問)
4320
+
4321
+ ### B.1 一般的な質問
4322
+
4323
+ **Q: なぜグローバル設定が必要なのですか?**
4324
+
4325
+ A: 複数のMichiプロジェクトを管理している場合、Confluence/JIRA/GitHubの認証情報は組織で共通です。グローバル設定により、これらを1箇所で管理でき、各プロジェクトで重複して設定する必要がなくなります。
4326
+
4327
+ **Q: グローバル設定を使わずに、プロジェクトごとに設定したい場合は?**
4328
+
4329
+ A: グローバル設定は任意です。`.env` ファイルにすべての設定を記述すれば、グローバル設定なしでも動作します。
4330
+
4331
+ **Q: 複数の組織に所属している場合はどうすればいいですか?**
4332
+
4333
+ A: 現在(v0.5.0)はグローバル設定は1つの組織のみサポートしています。他の組織のプロジェクトでは `.env` に直接認証情報を記述してください。v0.7.0 でプロファイル機能を追加予定です(Section 11参照)。
4334
+
4335
+ ### B.2 移行に関する質問
4336
+
4337
+ **Q: v0.5.0 にアップグレードする必要がありますか?**
4338
+
4339
+ A: v0.5.0 は Breaking Change を含むため、アップグレード時には `michi migrate` を実行する必要があります。v0.4.0 のまま使用を続けることも可能ですが、新機能やバグ修正は v0.5.0 以降で提供されます。
4340
+
4341
+ **Q: 移行に失敗した場合、元に戻せますか?**
4342
+
4343
+ A: はい、`michi migrate` は自動的にバックアップを作成します。`michi migrate --rollback <backup-dir>` で元に戻せます。
4344
+
4345
+ **Q: GITHUB_REPO はどうなりますか?**
4346
+
4347
+ A: v0.5.0 で削除されます。代わりに `.kiro/project.json` の `repository` フィールドを使用します。`michi migrate` が自動的に変換します。
4348
+
4349
+ ### B.3 セキュリティに関する質問
4350
+
4351
+ **Q: ~/.michi/.env のパーミッションはどうすればいいですか?**
4352
+
4353
+ A: 600 (rw-------) が推奨です。`michi migrate` が自動的に設定します。手動で作成した場合は `chmod 600 ~/.michi/.env` を実行してください。
4354
+
4355
+ **Q: .env ファイルを Git にコミットしてしまいました。どうすればいいですか?**
4356
+
4357
+ A: 以下の手順で対処してください:
4358
+
4359
+ 1. `.env` を `.gitignore` に追加
4360
+ 2. Git履歴から `.env` を削除: `git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch .env' --prune-empty --tag-name-filter cat -- --all`
4361
+ 3. 認証情報をすべて再生成(漏洩したものとして扱う)
4362
+ 4. 新しい認証情報で `.env` を更新
4363
+
4364
+ **Q: パスワードやトークンはどのように保存されますか?**
4365
+
4366
+ A: 平文で `.env` ファイルに保存されます。ファイルパーミッション(600)により、他のユーザーからの読み取りは防止されますが、暗号化はされません。より高度なセキュリティが必要な場合は、v1.2.0 で実装予定のシークレット管理サービス統合をお待ちください。
4367
+
4368
+ ### B.4 パフォーマンスに関する質問
4369
+
4370
+ **Q: 設定の読み込みが遅いのですが?**
4371
+
4372
+ A: ConfigLoader はキャッシュ機構を持っており、2回目以降の読み込みは高速です。それでも遅い場合は、以下を確認してください:
4373
+ - ネットワークドライブ上のプロジェクトではないか
4374
+ - アンチウイルスソフトが `.env` ファイルをスキャンしていないか
4375
+
4376
+ **Q: メモリ使用量が多いのですが?**
4377
+
4378
+ A: 通常、設定読み込みは 5MB 未満のメモリを使用します。異常に多い場合は、キャッシュをクリアしてみてください:`ConfigLoader.clearCache()`
4379
+
4380
+ ### B.5 トラブルシューティング
4381
+
4382
+ **Q: "CONFLUENCE_URL is required" エラーが出ます**
4383
+
4384
+ A: グローバル設定またはプロジェクト設定に `CONFLUENCE_URL` が設定されているか確認してください。`npm run config:validate` で設定を検証できます。
4385
+
4386
+ **Q: "Invalid repository URL" エラーが出ます**
4387
+
4388
+ A: `project.json` の `repository` フィールドが正しい形式か確認してください。有効な形式:
4389
+ - `https://github.com/org/repo.git`
4390
+ - `git@github.com:org/repo.git`
4391
+
4392
+ **Q: 設定ファイルが見つからないと言われます**
4393
+
4394
+ A: 現在のディレクトリがMichiプロジェクトのルートか確認してください。`ls .michi` でディレクトリが存在するか確認できます。
4395
+
4396
+ ---
4397
+
4398
+ ## 付録 C: トラブルシューティング
4399
+
4400
+ 詳細なトラブルシューティングガイドです。Section 7.7 も参照してください。
4401
+
4402
+ ### C.1 診断コマンド
4403
+
4404
+ **設定の確認**
4405
+
4406
+ ```bash
4407
+ # 設定のバリデーション
4408
+ michi config:validate
4409
+
4410
+ # 設定の詳細表示(機密情報はマスクされる)
4411
+ michi config:show
4412
+
4413
+ # 現在読み込まれている設定のパスを表示
4414
+ michi config:paths
4415
+ ```
4416
+
4417
+ **ファイルの確認**
4418
+
4419
+ ```bash
4420
+ # グローバル設定の存在確認
4421
+ ls -la ~/.michi/.env
4422
+
4423
+ # プロジェクト設定の確認
4424
+ ls -la .michi/config.json .michi/project.json .env
4425
+
4426
+ # パーミッションの確認
4427
+ stat -f "%A %N" ~/.michi/.env .env
4428
+ ```
4429
+
4430
+ ### C.2 一般的な問題と解決策
4431
+
4432
+ **問題: "Permission denied" エラー**
4433
+
4434
+ ```bash
4435
+ # パーミッションを確認
4436
+ ls -l ~/.michi/.env
4437
+
4438
+ # 600 に修正
4439
+ chmod 600 ~/.michi/.env
4440
+ ```
4441
+
4442
+ **問題: "File not found" エラー**
4443
+
4444
+ ```bash
4445
+ # ファイルが存在するか確認
4446
+ test -f ~/.michi/.env && echo "存在する" || echo "存在しない"
4447
+
4448
+ # 存在しない場合は作成
4449
+ mkdir -p ~/.michi
4450
+ touch ~/.michi/.env
4451
+ chmod 600 ~/.michi/.env
4452
+ ```
4453
+
4454
+ **問題: "Invalid configuration" エラー**
4455
+
4456
+ ```bash
4457
+ # 設定をバリデーション
4458
+ michi config:validate
4459
+
4460
+ # エラーメッセージを確認し、該当する項目を修正
4461
+ ```
4462
+
4463
+ ### C.3 ログの確認
4464
+
4465
+ **移行ログ**
4466
+
4467
+ ```bash
4468
+ # 移行の詳細ログを確認
4469
+ cat .michi/migration.log
4470
+
4471
+ # 最新10件のエラーを表示
4472
+ grep ERROR .michi/migration.log | tail -10
4473
+ ```
4474
+
4475
+ **監査ログ**
4476
+
4477
+ ```bash
4478
+ # 設定変更の履歴を確認
4479
+ cat .michi/audit.log | jq '.'
4480
+
4481
+ # 最新の変更を表示
4482
+ cat .michi/audit.log | jq '.' | tail -1
4483
+ ```
4484
+
4485
+ **セキュリティログ**
4486
+
4487
+ ```bash
4488
+ # セキュリティイベントを確認
4489
+ cat ~/.michi/security.log | jq '.'
4490
+ ```
4491
+
4492
+ ### C.4 デバッグモード
4493
+
4494
+ **環境変数でデバッグログを有効化**
4495
+
4496
+ ```bash
4497
+ # デバッグログを有効化
4498
+ export MICHI_DEBUG=true
4499
+
4500
+ # コマンド実行
4501
+ michi config:validate
4502
+
4503
+ # 詳細ログが出力される
4504
+ ```
4505
+
4506
+ ### C.5 サポート
4507
+
4508
+ 問題が解決しない場合は、以下の情報とともに GitHub Issues で報告してください:
4509
+
4510
+ 1. **環境情報**
4511
+ ```bash
4512
+ michi --version
4513
+ node --version
4514
+ npm --version
4515
+ uname -a
4516
+ ```
4517
+
4518
+ 2. **エラーメッセージ全文**
4519
+
4520
+ 3. **再現手順**
4521
+
4522
+ 4. **設定ファイル**(機密情報は削除してください)
4523
+
4524
+ ---
4525
+
4526
+ ## 付録 D: 設定例集
4527
+
4528
+ 実際のプロジェクトでの設定例です。
4529
+
4530
+ ### D.1 シンプルな構成
4531
+
4532
+ **~/.michi/.env**
4533
+
4534
+ ```bash
4535
+ # 組織共通設定
4536
+ CONFLUENCE_URL=https://mycompany.atlassian.net
4537
+ CONFLUENCE_USERNAME=developer@mycompany.com
4538
+ CONFLUENCE_API_TOKEN=your-api-token-here
4539
+
4540
+ JIRA_URL=https://mycompany.atlassian.net
4541
+ JIRA_USERNAME=developer@mycompany.com
4542
+ JIRA_API_TOKEN=your-api-token-here
4543
+
4544
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4545
+ GITHUB_USERNAME=myusername
4546
+ GITHUB_EMAIL=developer@mycompany.com
4547
+ GITHUB_ORG=mycompany
4548
+ ```
4549
+
4550
+ **.michi/project.json**
4551
+
4552
+ ```json
4553
+ {
4554
+ "projectId": "web-app",
4555
+ "repository": "https://github.com/mycompany/web-app.git"
4556
+ }
4557
+ ```
4558
+
4559
+ **.env**
4560
+
4561
+ ```bash
4562
+ # プロジェクト固有の設定(なし)
4563
+ ```
4564
+
4565
+ ### D.2 複雑な構成
4566
+
4567
+ **~/.michi/.env**
4568
+
4569
+ ```bash
4570
+ # 組織共通設定
4571
+ CONFLUENCE_URL=https://mycompany.atlassian.net
4572
+ CONFLUENCE_USERNAME=developer@mycompany.com
4573
+ CONFLUENCE_API_TOKEN=your-api-token-here
4574
+ CONFLUENCE_SPACE=DEV
4575
+
4576
+ JIRA_URL=https://mycompany.atlassian.net
4577
+ JIRA_USERNAME=developer@mycompany.com
4578
+ JIRA_API_TOKEN=your-api-token-here
4579
+
4580
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4581
+ GITHUB_USERNAME=myusername
4582
+ GITHUB_EMAIL=developer@mycompany.com
4583
+ GITHUB_ORG=mycompany
4584
+ ```
4585
+
4586
+ **.michi/config.json**
4587
+
4588
+ ```json
4589
+ {
4590
+ "confluence": {
4591
+ "pageCreationGranularity": "by-section",
4592
+ "pageTitleFormat": "[Web App] {featureName}",
4593
+ "hierarchy": {
4594
+ "mode": "nested",
4595
+ "parentPageTitle": "[{projectName}] {featureName}"
4596
+ }
4597
+ },
4598
+ "jira": {
4599
+ "createEpic": true,
4600
+ "storyCreationGranularity": "by-phase",
4601
+ "selectedPhases": ["implementation", "testing"],
4602
+ "storyPoints": "auto"
4603
+ },
4604
+ "workflow": {
4605
+ "enabledPhases": ["requirements", "design", "tasks"],
4606
+ "approvalGates": {
4607
+ "requirements": ["pm", "architect"],
4608
+ "design": ["architect", "tech-lead"],
4609
+ "release": ["pm", "director"]
4610
+ }
4611
+ }
4612
+ }
4613
+ ```
4614
+
4615
+ **.michi/project.json**
4616
+
4617
+ ```json
4618
+ {
4619
+ "projectId": "web-app",
4620
+ "repository": "https://github.com/mycompany/web-app.git"
4621
+ }
4622
+ ```
4623
+
4624
+ **.env**
4625
+
4626
+ ```bash
4627
+ # プロジェクト固有の Confluence スペース
4628
+ CONFLUENCE_SPACE=WEBAPP
4629
+
4630
+ # プロジェクト固有の JIRA プロジェクト
4631
+ JIRA_PROJECT=WEB
4632
+ ```
4633
+
4634
+ ### D.3 マルチ環境構成
4635
+
4636
+ 本番環境と開発環境で異なる設定を使用する例:
4637
+
4638
+ **~/.michi/.env** (共通)
4639
+
4640
+ ```bash
4641
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4642
+ GITHUB_USERNAME=myusername
4643
+ GITHUB_EMAIL=developer@mycompany.com
4644
+ GITHUB_ORG=mycompany
4645
+ ```
4646
+
4647
+ **.env.development**
4648
+
4649
+ ```bash
4650
+ CONFLUENCE_URL=https://dev.atlassian.net
4651
+ CONFLUENCE_SPACE=DEV
4652
+ JIRA_URL=https://dev.atlassian.net
4653
+ JIRA_PROJECT=DEV
4654
+ ```
4655
+
4656
+ **.env.production**
4657
+
4658
+ ```bash
4659
+ CONFLUENCE_URL=https://prod.atlassian.net
4660
+ CONFLUENCE_SPACE=PROD
4661
+ JIRA_URL=https://prod.atlassian.net
4662
+ JIRA_PROJECT=PROD
4663
+ ```
4664
+
4665
+ **使用方法**:
4666
+
4667
+ ```bash
4668
+ # 開発環境
4669
+ cp .env.development .env
4670
+ michi confluence:sync my-feature
4671
+
4672
+ # 本番環境
4673
+ cp .env.production .env
4674
+ michi confluence:sync my-feature
4675
+ ```
4676
+
4677
+ ---
4678
+
4679
+ ## 付録 E: 移行チェックリスト
4680
+
4681
+ v0.4.0 から v0.5.0 への移行時に確認すべき項目のチェックリストです。
4682
+
4683
+ ### E.1 移行前の準備
4684
+
4685
+ - [ ] 現在の Michi バージョンを確認: `michi --version`
4686
+ - [ ] すべての変更をコミット: `git status` で確認
4687
+ - [ ] バックアップを作成
4688
+ - [ ] `.michi/` ディレクトリ
4689
+ - [ ] `.env` ファイル
4690
+ - [ ] `project.json` ファイル
4691
+
4692
+ ### E.2 アップグレード
4693
+
4694
+ - [ ] Michi を最新版にアップグレード: `npm install -g @sk8metal/michi-cli@latest`
4695
+ - [ ] バージョンを確認: `michi --version` が v0.5.0 以上であること
4696
+
4697
+ ### E.3 グローバル設定の作成
4698
+
4699
+ - [ ] グローバル設定ディレクトリを作成: `mkdir -p ~/.michi`
4700
+ - [ ] グローバル .env を作成: `touch ~/.michi/.env`
4701
+ - [ ] パーミッションを設定: `chmod 600 ~/.michi/.env`
4702
+ - [ ] 認証情報をグローバル .env に記入
4703
+ - [ ] CONFLUENCE_URL
4704
+ - [ ] CONFLUENCE_USERNAME
4705
+ - [ ] CONFLUENCE_API_TOKEN
4706
+ - [ ] JIRA_URL
4707
+ - [ ] JIRA_USERNAME
4708
+ - [ ] JIRA_API_TOKEN
4709
+ - [ ] GITHUB_TOKEN
4710
+ - [ ] GITHUB_USERNAME
4711
+ - [ ] GITHUB_EMAIL
4712
+ - [ ] GITHUB_ORG
4713
+
4714
+ ### E.4 プロジェクトごとの移行
4715
+
4716
+ 各プロジェクトで以下を実行:
4717
+
4718
+ - [ ] プロジェクトディレクトリに移動
4719
+ - [ ] 移行を実行: `michi migrate`
4720
+ - または、最初にドライランで確認: `michi migrate --dry-run`
4721
+ - [ ] バックアップが作成されたことを確認: `ls .michi-backup-*`
4722
+ - [ ] グローバル設定が作成されたことを確認: `ls ~/.michi/.env`
4723
+ - [ ] プロジェクト .env から組織設定が削除されたことを確認
4724
+ - [ ] `grep CONFLUENCE_URL .env` が何も返さない
4725
+ - [ ] `grep GITHUB_TOKEN .env` が何も返さない
4726
+ - [ ] project.json に repository が追加されたことを確認
4727
+ - [ ] `cat .michi/project.json | grep repository`
4728
+
4729
+ ### E.5 動作確認
4730
+
4731
+ - [ ] 設定のバリデーション: `michi config:validate`
4732
+ - [ ] Confluence同期テスト: `michi confluence:sync test-feature --dry-run`
4733
+ - [ ] JIRA同期テスト: `michi jira:sync test-feature --dry-run`
4734
+ - [ ] GitHub PRテスト: `michi github:pr --info`
4735
+
4736
+ ### E.6 クリーンアップ
4737
+
4738
+ - [ ] バックアップが不要なら削除: `rm -rf .michi-backup-*`
4739
+ - [ ] 古い .env.backup が不要なら削除: `rm .env.backup`
4740
+ - [ ] .gitignore に機密ファイルが追加されていることを確認
4741
+ - [ ] `.env`
4742
+ - [ ] `.michi-backup-*/`
4743
+ - [ ] `.michi/migration.log`
4744
+
4745
+ ### E.7 ドキュメント更新
4746
+
4747
+ - [ ] README に新しい設定方法を記載
4748
+ - [ ] チームメンバーに移行方法を共有
4749
+ - [ ] CI/CD パイプラインの更新(必要な場合)
4750
+
4751
+ ---
4752
+
4753
+ ## まとめ
4754
+
4755
+ この設計ドキュメントでは、Michiプロジェクトの設定統一について詳細に説明しました。
4756
+
4757
+ ### 主要な変更点
4758
+
4759
+ 1. **3層設定階層**: グローバル → プロジェクト → 環境の3層で設定を管理
4760
+ 2. **グローバル設定**: `~/.michi/.env` で組織共通の認証情報を一元管理
4761
+ 3. **リポジトリURL統一**: `GITHUB_REPO` を廃止し、`project.json.repository` に統一
4762
+ 4. **コマンド統一**: `setup-existing` を廃止し、`michi init --existing` に統一
4763
+ 5. **自動移行ツール**: `michi migrate` で既存プロジェクトを簡単に移行
4764
+
4765
+ ### 次のステップ
4766
+
4767
+ 1. **v0.5.0 リリース**: このドキュメントに基づいて実装
4768
+ 2. **ユーザーフィードバック収集**: 実際の使用感を確認
4769
+ 3. **v0.6.0 準備**: 非推奨機能の削除計画
4770
+ 4. **長期計画**: マルチ組織サポート、暗号化、クラウド統合
4771
+
4772
+ ### 貢献
4773
+
4774
+ このプロジェクトへの貢献を歓迎します:
4775
+
4776
+ - **バグレポート**: GitHub Issues
4777
+ - **機能リクエスト**: GitHub Discussions
4778
+ - **コード貢献**: Pull Requests
4779
+ - **ドキュメント改善**: Pull Requests
4780
+
4781
+ ---
4782
+
4783
+ **Document Version**: 1.0.0
4784
+ **Last Updated**: 2025-01-12
4785
+ **Authors**: Michi Development Team
4786
+ **Status**: Final Draft
4787
+
4788
+ ---
4789
+