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