@sk8metal/michi-cli 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +37 -14
- package/dist/src/commands/init.js.map +1 -1
- package/docs/michi-development/design/config-unification.md +38 -4094
- package/docs/michi-development/design/design-config-current-state.md +330 -0
- package/docs/michi-development/design/design-config-implementation.md +628 -0
- package/docs/michi-development/design/design-config-migration.md +952 -0
- package/docs/michi-development/design/design-config-security.md +771 -0
- package/docs/michi-development/design/design-config-solution.md +583 -0
- package/docs/michi-development/design/design-config-testing.md +892 -0
- package/docs/michi-development/testing/manual-verification-flow.md +6 -1377
- package/docs/michi-development/testing/manual-verification-other-tools.md +1277 -0
- package/docs/michi-development/testing/manual-verification-troubleshooting.md +122 -0
- package/docs/user-guide/getting-started/setup.md +14 -32
- package/docs/user-guide/guides/multi-repo-guide.md +367 -44
- package/docs/user-guide/reference/config.md +1 -1
- package/docs/user-guide/reference/security-test-payloads.md +50 -0
- package/docs/user-guide/release/ci-setup-java.md +114 -0
- package/docs/user-guide/release/ci-setup-nodejs.md +94 -0
- package/docs/user-guide/release/ci-setup-php.md +102 -0
- package/docs/user-guide/release/ci-setup-troubleshooting.md +94 -0
- package/docs/user-guide/release/ci-setup.md +17 -370
- package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +9 -3
- package/docs/user-guide/templates/test-specs/security-test-spec-template.md +4 -43
- package/package.json +2 -3
- package/docs/design-issue-55.md +0 -240
- package/docs/design-issue-56.md +0 -181
- package/docs/user-guide/guides/multi-repo-migration-guide.md +0 -516
|
@@ -51,4103 +51,47 @@ Michiプロジェクトでは、現在3つのコマンド(`michi init`、`npx
|
|
|
51
51
|
|
|
52
52
|
---
|
|
53
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
54
|
|
|
3986
55
|
---
|
|
3987
56
|
|
|
3988
|
-
##
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
###
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
-
|
|
4012
|
-
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
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
|
-
- ダウングレードは緊急時のみ推奨されます
|
|
57
|
+
## 詳細ドキュメント
|
|
58
|
+
|
|
59
|
+
本設計書は、可読性と保守性を向上させるため、以下のドキュメントに分割されています:
|
|
60
|
+
|
|
61
|
+
### 現状分析と課題
|
|
62
|
+
- [現状分析と問題点](./design-config-current-state.md)
|
|
63
|
+
- 現在の3つのコマンド分析
|
|
64
|
+
- 設定ファイルと設定項目の一覧
|
|
65
|
+
- データフロー図
|
|
66
|
+
- 特定された問題点
|
|
67
|
+
|
|
68
|
+
### 解決策と設計
|
|
69
|
+
- [解決策と新アーキテクチャ](./design-config-solution.md)
|
|
70
|
+
- 統一的な設定管理システムの提案
|
|
71
|
+
- 新しいアーキテクチャ設計
|
|
72
|
+
- 設定の階層化と優先順位
|
|
73
|
+
|
|
74
|
+
### 実装とマイグレーション
|
|
75
|
+
- [実装詳細](./design-config-implementation.md)
|
|
76
|
+
- コマンド実装詳細
|
|
77
|
+
- 設定管理ライブラリ
|
|
78
|
+
- ファイル構造
|
|
79
|
+
|
|
80
|
+
- [マイグレーション戦略と後方互換性](./design-config-migration.md)
|
|
81
|
+
- 段階的マイグレーション計画
|
|
82
|
+
- マイグレーションツール
|
|
83
|
+
- 後方互換性の維持
|
|
84
|
+
|
|
85
|
+
### 品質保証
|
|
86
|
+
- [テスト戦略](./design-config-testing.md)
|
|
87
|
+
- 統合テスト計画
|
|
88
|
+
- マイグレーションテスト
|
|
89
|
+
- パフォーマンステスト
|
|
90
|
+
|
|
91
|
+
- [セキュリティとパフォーマンス](./design-config-security.md)
|
|
92
|
+
- セキュリティ考慮事項
|
|
93
|
+
- パーミッション管理
|
|
94
|
+
- パフォーマンス最適化
|
|
4151
95
|
|
|
4152
96
|
---
|
|
4153
97
|
|