@sk8metal/michi-cli 0.10.1 → 0.11.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/README.md +77 -847
- package/dist/scripts/phase-runner.js +1 -1
- package/dist/scripts/phase-runner.js.map +1 -1
- package/dist/scripts/utils/multi-repo-validator.d.ts +18 -0
- package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -1
- package/dist/scripts/utils/multi-repo-validator.js +42 -0
- package/dist/scripts/utils/multi-repo-validator.js.map +1 -1
- package/dist/scripts/utils/tasks-format-validator.js +3 -3
- package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
- package/docs/README.md +20 -83
- package/docs/getting-started/configuration.md +379 -0
- package/docs/getting-started/installation.md +59 -0
- package/docs/getting-started/quick-start.md +76 -0
- package/docs/guides/ai-tools.md +311 -0
- package/docs/guides/atlassian-integration.md +116 -0
- package/docs/guides/claude-code.md +155 -0
- package/docs/guides/multi-repo.md +117 -0
- package/docs/guides/workflow.md +382 -0
- package/docs/reference/ai-commands.md +92 -0
- package/docs/reference/cli.md +756 -0
- package/docs/reference/environment-variables.md +192 -0
- package/docs/troubleshooting.md +543 -0
- package/package.json +1 -1
- package/scripts/phase-runner.ts +1 -1
- package/scripts/utils/__tests__/multi-repo-validator.test.ts +159 -1
- package/scripts/utils/multi-repo-validator.ts +50 -0
- package/scripts/utils/tasks-format-validator.ts +3 -3
- package/templates/claude/agents/e2e-first-planner/AGENT.md +1 -1
- package/templates/claude/agents/pr-resolver/AGENT.md +15 -3
- package/templates/claude/commands/michi/e2e-plan.md +1 -1
- package/templates/claude/commands/michi/spec-design.md +2 -2
- package/templates/claude/commands/michi/spec-tasks.md +156 -0
- package/templates/claude/commands/michi/test-planning.md +1 -1
- package/templates/claude/commands/michi/validate-design.md +3 -3
- package/templates/claude/commands/michi-multi-repo/impl-all.md +30 -1
- package/templates/claude/commands/michi-multi-repo/propagate-specs.md +14 -1
- package/templates/claude/commands/michi-multi-repo/spec-review.md +16 -2
- package/templates/claude-agent/agents/repo-spec-executor.md +1 -1
- package/templates/claude-agent/commands/michi/spec-tasks.md +117 -0
- package/templates/claude-agent/rules/code-size-monitor.md +26 -0
- package/templates/claude-agent/rules/code-size-rules.md +32 -0
- package/templates/codex/AGENTS.override.md +1 -1
- package/templates/codex/rules/README.md +2 -2
- package/templates/cursor/commands/michi/spec-tasks.md +117 -0
- package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +1 -1
- package/docs/context.md +0 -59
- package/docs/michi-development/contributing/development.md +0 -341
- package/docs/michi-development/contributing/release.md +0 -365
- package/docs/michi-development/design/config-unification.md +0 -733
- package/docs/michi-development/design/design-config-current-state.md +0 -330
- package/docs/michi-development/design/design-config-implementation.md +0 -628
- package/docs/michi-development/design/design-config-migration.md +0 -952
- package/docs/michi-development/design/design-config-security.md +0 -771
- package/docs/michi-development/design/design-config-solution.md +0 -583
- package/docs/michi-development/design/design-config-testing.md +0 -892
- package/docs/michi-development/testing/manual-verification-flow.md +0 -871
- package/docs/michi-development/testing/manual-verification-other-tools.md +0 -1279
- package/docs/michi-development/testing/manual-verification-troubleshooting.md +0 -122
- package/docs/michi-development/testing/pre-publish-checklist.md +0 -560
- package/docs/michi-development/testing-strategy.md +0 -87
- package/docs/plan.md +0 -275
- package/docs/user-guide/getting-started/github-token-setup.md +0 -510
- package/docs/user-guide/getting-started/new-repository-setup.md +0 -704
- package/docs/user-guide/getting-started/quick-start.md +0 -212
- package/docs/user-guide/getting-started/setup.md +0 -819
- package/docs/user-guide/guides/agent-skills-integration.md +0 -222
- package/docs/user-guide/guides/customization.md +0 -537
- package/docs/user-guide/guides/internationalization.md +0 -540
- package/docs/user-guide/guides/migration-guide.md +0 -138
- package/docs/user-guide/guides/multi-project.md +0 -368
- package/docs/user-guide/guides/multi-repo-guide.md +0 -1590
- package/docs/user-guide/guides/phase-automation.md +0 -419
- package/docs/user-guide/guides/workflow.md +0 -574
- package/docs/user-guide/hands-on/README.md +0 -142
- package/docs/user-guide/hands-on/claude-agent-setup.md +0 -597
- package/docs/user-guide/hands-on/claude-setup.md +0 -452
- package/docs/user-guide/hands-on/cursor-setup.md +0 -353
- package/docs/user-guide/hands-on/troubleshooting.md +0 -964
- package/docs/user-guide/hands-on/verification-checklist.md +0 -439
- package/docs/user-guide/hands-on/workflow-walkthrough.md +0 -1078
- package/docs/user-guide/reference/config.md +0 -589
- package/docs/user-guide/reference/multi-repo-api.md +0 -771
- package/docs/user-guide/reference/quick-reference.md +0 -297
- package/docs/user-guide/reference/security-test-payloads.md +0 -50
- package/docs/user-guide/reference/tasks-template.md +0 -550
- package/docs/user-guide/release/ci-setup-java.md +0 -114
- package/docs/user-guide/release/ci-setup-nodejs.md +0 -94
- package/docs/user-guide/release/ci-setup-php.md +0 -102
- package/docs/user-guide/release/ci-setup-troubleshooting.md +0 -94
- package/docs/user-guide/release/ci-setup.md +0 -188
- package/docs/user-guide/release/release-flow.md +0 -476
- package/docs/user-guide/templates/test-specs/README.md +0 -173
- package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +0 -553
- package/docs/user-guide/templates/test-specs/integration-test-spec-template.md +0 -435
- package/docs/user-guide/templates/test-specs/performance-test-spec-template.md +0 -454
- package/docs/user-guide/templates/test-specs/security-test-spec-template.md +0 -625
- package/docs/user-guide/templates/test-specs/unit-test-spec-template.md +0 -328
- package/docs/user-guide/testing/integration-tests.md +0 -312
- package/docs/user-guide/testing/tdd-cycle.md +0 -349
- package/docs/user-guide/testing/test-execution-flow.md +0 -396
- package/docs/user-guide/testing/test-failure-handling.md +0 -521
- package/docs/user-guide/testing/test-planning-flow.md +0 -185
- package/docs/user-guide/testing-strategy.md +0 -185
- package/docs/verification-guide.md +0 -518
|
@@ -1,628 +0,0 @@
|
|
|
1
|
-
# Michi 設定統合設計書 - 実装詳細
|
|
2
|
-
|
|
3
|
-
**バージョン**: 1.0
|
|
4
|
-
**作成日**: 2025-01-11
|
|
5
|
-
**ステータス**: Draft
|
|
6
|
-
**親ドキュメント**: [config-unification.md](./config-unification.md)
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 6. 実装詳細
|
|
11
|
-
|
|
12
|
-
### 6.1 ConfigLoader クラス設計
|
|
13
|
-
|
|
14
|
-
ConfigLoaderは、複数の設定ファイルを読み込み、優先順位に従ってマージし、型安全なアクセスを提供するクラスです。
|
|
15
|
-
|
|
16
|
-
**ファイルパス:** `scripts/utils/config-loader-v2.ts`
|
|
17
|
-
|
|
18
|
-
**責務:**
|
|
19
|
-
1. 複数の設定ファイルを読み込み
|
|
20
|
-
2. 優先順位に従ってマージ
|
|
21
|
-
3. バリデーション
|
|
22
|
-
4. 型安全なアクセス提供
|
|
23
|
-
5. キャッシュによるパフォーマンス向上
|
|
24
|
-
|
|
25
|
-
**クラス定義:**
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import { z } from 'zod';
|
|
29
|
-
import { existsSync, readFileSync } from 'fs';
|
|
30
|
-
import { join } from 'path';
|
|
31
|
-
import * as dotenv from 'dotenv';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 設定の読み込み元
|
|
35
|
-
*/
|
|
36
|
-
type ConfigSource =
|
|
37
|
-
| 'global-env' // ~/.michi/global.env
|
|
38
|
-
| 'global-config' // ~/.michi/config.json
|
|
39
|
-
| 'project-meta' // .kiro/project.json
|
|
40
|
-
| 'project-config' // .michi/config.json
|
|
41
|
-
| 'project-env'; // .env
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 読み込まれた設定(ソース情報付き)
|
|
45
|
-
*/
|
|
46
|
-
interface ConfigWithSource<T> {
|
|
47
|
-
value: T;
|
|
48
|
-
source: ConfigSource;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 統合設定
|
|
53
|
-
*/
|
|
54
|
-
interface MergedConfig {
|
|
55
|
-
// Atlassian認証
|
|
56
|
-
atlassian: {
|
|
57
|
-
url: string;
|
|
58
|
-
email: string;
|
|
59
|
-
apiToken: string;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// GitHub設定
|
|
63
|
-
github: {
|
|
64
|
-
org: string; // 組織名(グローバル設定から)
|
|
65
|
-
token: string; // アクセストークン(グローバル設定から)
|
|
66
|
-
repository: string; // フルURL(project.jsonから)
|
|
67
|
-
repositoryShort: string; // "org/repo" 形式(自動抽出)
|
|
68
|
-
repositoryOrg: string; // リポジトリの組織名(自動抽出)
|
|
69
|
-
repositoryName: string; // リポジトリ名(自動抽出)
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Confluenceスペース
|
|
73
|
-
confluence: {
|
|
74
|
-
spaces: {
|
|
75
|
-
prd: string;
|
|
76
|
-
qa: string;
|
|
77
|
-
release: string;
|
|
78
|
-
};
|
|
79
|
-
// 設定
|
|
80
|
-
pageCreationGranularity: string;
|
|
81
|
-
pageTitleFormat?: string;
|
|
82
|
-
hierarchy?: unknown;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// JIRA設定
|
|
86
|
-
jira: {
|
|
87
|
-
issueTypes: {
|
|
88
|
-
story: string;
|
|
89
|
-
subtask: string;
|
|
90
|
-
};
|
|
91
|
-
projectKeys: string; // プロジェクト固有
|
|
92
|
-
createEpic: boolean;
|
|
93
|
-
storyCreationGranularity: string;
|
|
94
|
-
selectedPhases?: string[];
|
|
95
|
-
storyPoints: string;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// ワークフロー設定
|
|
99
|
-
workflow: {
|
|
100
|
-
enabledPhases: string[];
|
|
101
|
-
approvalGates?: {
|
|
102
|
-
requirements?: string[];
|
|
103
|
-
design?: string[];
|
|
104
|
-
release?: string[];
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
// プロジェクトメタデータ
|
|
109
|
-
project: {
|
|
110
|
-
id: string;
|
|
111
|
-
name: string;
|
|
112
|
-
language: string;
|
|
113
|
-
jiraProjectKey: string;
|
|
114
|
-
confluenceLabels: string[];
|
|
115
|
-
status: string;
|
|
116
|
-
team: string[];
|
|
117
|
-
stakeholders: string[];
|
|
118
|
-
repository: string;
|
|
119
|
-
description: string;
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 読み込みオプション
|
|
125
|
-
*/
|
|
126
|
-
interface LoadOptions {
|
|
127
|
-
projectRoot?: string; // プロジェクトルート(デフォルト: process.cwd())
|
|
128
|
-
skipValidation?: boolean; // バリデーションをスキップ
|
|
129
|
-
useCache?: boolean; // キャッシュを使用(デフォルト: true)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* 設定ローダー
|
|
134
|
-
*/
|
|
135
|
-
export class ConfigLoader {
|
|
136
|
-
private cache: MergedConfig | null = null;
|
|
137
|
-
private cacheTimestamp: number = 0;
|
|
138
|
-
private cacheTTL: number = 60000; // 1分
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* 設定を読み込んでマージ
|
|
142
|
-
*/
|
|
143
|
-
async load(options?: LoadOptions): Promise<MergedConfig> {
|
|
144
|
-
// キャッシュチェック
|
|
145
|
-
if (this.cache && options?.useCache !== false) {
|
|
146
|
-
const now = Date.now();
|
|
147
|
-
if (now - this.cacheTimestamp < this.cacheTTL) {
|
|
148
|
-
return this.cache;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const projectRoot = options?.projectRoot || process.cwd();
|
|
153
|
-
|
|
154
|
-
// パフォーマンス計測開始
|
|
155
|
-
const start = performance.now();
|
|
156
|
-
|
|
157
|
-
// 並列読み込み
|
|
158
|
-
const [globalEnv, globalConfig, projectMeta, projectConfig, projectEnv] =
|
|
159
|
-
await Promise.all([
|
|
160
|
-
this.loadGlobalEnv(),
|
|
161
|
-
this.loadGlobalConfig(),
|
|
162
|
-
this.loadProjectMeta(projectRoot),
|
|
163
|
-
this.loadProjectConfig(projectRoot),
|
|
164
|
-
this.loadProjectEnv(projectRoot),
|
|
165
|
-
]);
|
|
166
|
-
|
|
167
|
-
// マージ
|
|
168
|
-
const merged = this.merge([
|
|
169
|
-
globalEnv,
|
|
170
|
-
globalConfig,
|
|
171
|
-
projectMeta,
|
|
172
|
-
projectConfig,
|
|
173
|
-
projectEnv,
|
|
174
|
-
]);
|
|
175
|
-
|
|
176
|
-
// リポジトリURLのパース(project.jsonから取得)
|
|
177
|
-
if (merged.project?.repository) {
|
|
178
|
-
const parsed = this.parseGitHubRepository(merged.project.repository);
|
|
179
|
-
merged.github = {
|
|
180
|
-
...merged.github,
|
|
181
|
-
repository: parsed.url,
|
|
182
|
-
repositoryShort: parsed.shortForm,
|
|
183
|
-
repositoryOrg: parsed.org,
|
|
184
|
-
repositoryName: parsed.repo,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// バリデーション
|
|
189
|
-
if (!options?.skipValidation) {
|
|
190
|
-
this.validate(merged);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// キャッシュ更新
|
|
194
|
-
this.cache = merged;
|
|
195
|
-
this.cacheTimestamp = Date.now();
|
|
196
|
-
|
|
197
|
-
// パフォーマンス計測終了
|
|
198
|
-
const elapsed = performance.now() - start;
|
|
199
|
-
if (elapsed > 100) {
|
|
200
|
-
console.warn(`⚠️ 設定の読み込みに ${elapsed.toFixed(2)}ms かかりました(目標: <100ms)`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return merged;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 設定をリロード(キャッシュをクリア)
|
|
208
|
-
*/
|
|
209
|
-
reload(): void {
|
|
210
|
-
this.cache = null;
|
|
211
|
-
this.cacheTimestamp = 0;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* 特定の設定値を取得
|
|
216
|
-
*/
|
|
217
|
-
get<T>(path: string): T | undefined {
|
|
218
|
-
if (!this.cache) {
|
|
219
|
-
throw new Error('Configuration not loaded. Call load() first.');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ドットパスで設定値を取得
|
|
223
|
-
return this.getValueByPath(this.cache, path);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* 設定値の設定元を取得
|
|
228
|
-
*/
|
|
229
|
-
getSource(path: string): ConfigSource | undefined {
|
|
230
|
-
// TODO: 実装
|
|
231
|
-
// 各設定値がどのファイルから来たかを追跡する
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* グローバル.envを読み込み
|
|
237
|
-
*/
|
|
238
|
-
private async loadGlobalEnv(): Promise<Partial<MergedConfig>> {
|
|
239
|
-
const globalEnvPath = this.getGlobalEnvPath();
|
|
240
|
-
if (!existsSync(globalEnvPath)) {
|
|
241
|
-
return {};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const parsed = dotenv.parse(readFileSync(globalEnvPath, 'utf-8'));
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
atlassian: {
|
|
248
|
-
url: parsed.ATLASSIAN_URL || '',
|
|
249
|
-
email: parsed.ATLASSIAN_EMAIL || '',
|
|
250
|
-
apiToken: parsed.ATLASSIAN_API_TOKEN || '',
|
|
251
|
-
},
|
|
252
|
-
github: {
|
|
253
|
-
org: parsed.GITHUB_ORG || '',
|
|
254
|
-
token: parsed.GITHUB_TOKEN || '',
|
|
255
|
-
// repository関連はproject.jsonから取得
|
|
256
|
-
repository: '',
|
|
257
|
-
repositoryShort: '',
|
|
258
|
-
repositoryOrg: '',
|
|
259
|
-
repositoryName: '',
|
|
260
|
-
},
|
|
261
|
-
confluence: {
|
|
262
|
-
spaces: {
|
|
263
|
-
prd: parsed.CONFLUENCE_PRD_SPACE || 'PRD',
|
|
264
|
-
qa: parsed.CONFLUENCE_QA_SPACE || 'QA',
|
|
265
|
-
release: parsed.CONFLUENCE_RELEASE_SPACE || 'RELEASE',
|
|
266
|
-
},
|
|
267
|
-
},
|
|
268
|
-
jira: {
|
|
269
|
-
issueTypes: {
|
|
270
|
-
story: parsed.JIRA_ISSUE_TYPE_STORY || '',
|
|
271
|
-
subtask: parsed.JIRA_ISSUE_TYPE_SUBTASK || '',
|
|
272
|
-
},
|
|
273
|
-
projectKeys: '', // プロジェクト固有
|
|
274
|
-
},
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* グローバル設定を読み込み
|
|
280
|
-
*/
|
|
281
|
-
private async loadGlobalConfig(): Promise<Partial<MergedConfig>> {
|
|
282
|
-
const globalConfigPath = this.getGlobalConfigPath();
|
|
283
|
-
if (!existsSync(globalConfigPath)) {
|
|
284
|
-
return {};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const content = readFileSync(globalConfigPath, 'utf-8');
|
|
288
|
-
const config = JSON.parse(content);
|
|
289
|
-
|
|
290
|
-
// TODO: 変換処理
|
|
291
|
-
return config;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* プロジェクトメタデータを読み込み
|
|
296
|
-
*/
|
|
297
|
-
private async loadProjectMeta(projectRoot: string): Promise<Partial<MergedConfig>> {
|
|
298
|
-
const projectMetaPath = join(projectRoot, '.kiro/project.json');
|
|
299
|
-
if (!existsSync(projectMetaPath)) {
|
|
300
|
-
return {};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const content = readFileSync(projectMetaPath, 'utf-8');
|
|
304
|
-
const meta = JSON.parse(content);
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
project: meta,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* プロジェクト設定を読み込み
|
|
313
|
-
*/
|
|
314
|
-
private async loadProjectConfig(projectRoot: string): Promise<Partial<MergedConfig>> {
|
|
315
|
-
const projectConfigPath = join(projectRoot, '.michi/config.json');
|
|
316
|
-
if (!existsSync(projectConfigPath)) {
|
|
317
|
-
return {};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const content = readFileSync(projectConfigPath, 'utf-8');
|
|
321
|
-
const config = JSON.parse(content);
|
|
322
|
-
|
|
323
|
-
// TODO: 変換処理
|
|
324
|
-
return config;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* プロジェクト.envを読み込み
|
|
329
|
-
*/
|
|
330
|
-
private async loadProjectEnv(projectRoot: string): Promise<Partial<MergedConfig>> {
|
|
331
|
-
const projectEnvPath = join(projectRoot, '.env');
|
|
332
|
-
if (!existsSync(projectEnvPath)) {
|
|
333
|
-
return {};
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const parsed = dotenv.parse(readFileSync(projectEnvPath, 'utf-8'));
|
|
337
|
-
|
|
338
|
-
return {
|
|
339
|
-
jira: {
|
|
340
|
-
projectKeys: parsed.JIRA_PROJECT_KEYS || '',
|
|
341
|
-
},
|
|
342
|
-
// GITHUB_REPO は削除(project.jsonのrepositoryから取得)
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* マージ処理
|
|
348
|
-
*/
|
|
349
|
-
private merge(configs: Partial<MergedConfig>[]): MergedConfig {
|
|
350
|
-
// deep merge with priority
|
|
351
|
-
return this.deepMerge({}, ...configs) as MergedConfig;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* ディープマージ
|
|
356
|
-
*/
|
|
357
|
-
private deepMerge(target: any, ...sources: any[]): any {
|
|
358
|
-
for (const source of sources) {
|
|
359
|
-
if (!source) continue;
|
|
360
|
-
|
|
361
|
-
for (const key in source) {
|
|
362
|
-
const targetValue = target[key];
|
|
363
|
-
const sourceValue = source[key];
|
|
364
|
-
|
|
365
|
-
if (this.isObject(sourceValue) && this.isObject(targetValue)) {
|
|
366
|
-
target[key] = this.deepMerge(targetValue, sourceValue);
|
|
367
|
-
} else if (sourceValue !== undefined && sourceValue !== '') {
|
|
368
|
-
target[key] = sourceValue;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return target;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* オブジェクトチェック
|
|
378
|
-
*/
|
|
379
|
-
private isObject(value: any): boolean {
|
|
380
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* バリデーション
|
|
385
|
-
*/
|
|
386
|
-
private validate(config: MergedConfig): void {
|
|
387
|
-
try {
|
|
388
|
-
MergedConfigSchema.parse(config);
|
|
389
|
-
} catch (error) {
|
|
390
|
-
if (error instanceof z.ZodError) {
|
|
391
|
-
throw new ConfigValidationError(
|
|
392
|
-
'Configuration validation failed',
|
|
393
|
-
error.issues
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
throw error;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* パスで値を取得
|
|
402
|
-
*/
|
|
403
|
-
private getValueByPath(obj: any, path: string): any {
|
|
404
|
-
const keys = path.split('.');
|
|
405
|
-
let current = obj;
|
|
406
|
-
|
|
407
|
-
for (const key of keys) {
|
|
408
|
-
if (current === undefined || current === null) {
|
|
409
|
-
return undefined;
|
|
410
|
-
}
|
|
411
|
-
current = current[key];
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return current;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* グローバル.envのパスを取得
|
|
419
|
-
*
|
|
420
|
-
* 注: 後方互換性のため、旧形式(global.env)も確認する
|
|
421
|
-
*/
|
|
422
|
-
private getGlobalEnvPath(): string {
|
|
423
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
424
|
-
if (!homeDir) {
|
|
425
|
-
throw new Error('Could not determine home directory');
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const newPath = join(homeDir, '.michi', '.env');
|
|
429
|
-
const oldPath = join(homeDir, '.michi', 'global.env');
|
|
430
|
-
|
|
431
|
-
// 旧形式が存在し、新形式が存在しない場合は警告
|
|
432
|
-
if (existsSync(oldPath) && !existsSync(newPath)) {
|
|
433
|
-
console.warn('⚠️ 古い設定ファイルが見つかりました: ~/.michi/global.env');
|
|
434
|
-
console.warn(' ~/.michi/.env に移行してください');
|
|
435
|
-
console.warn(' コマンド: michi migrate config');
|
|
436
|
-
return oldPath; // 後方互換性のため旧パスを返す
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return newPath;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/**
|
|
443
|
-
* グローバル設定のパスを取得
|
|
444
|
-
*/
|
|
445
|
-
private getGlobalConfigPath(): string {
|
|
446
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
447
|
-
if (!homeDir) {
|
|
448
|
-
throw new Error('Could not determine home directory');
|
|
449
|
-
}
|
|
450
|
-
return join(homeDir, '.michi', 'config.json');
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* GitHubリポジトリURLをパース
|
|
455
|
-
*
|
|
456
|
-
* @param repoUrl - GitHub リポジトリURL(HTTPS または SSH)
|
|
457
|
-
* @returns パースされたリポジトリ情報
|
|
458
|
-
* @throws {ConfigValidationError} 無効なURLの場合
|
|
459
|
-
*/
|
|
460
|
-
private parseGitHubRepository(repoUrl: string): {
|
|
461
|
-
url: string;
|
|
462
|
-
org: string;
|
|
463
|
-
repo: string;
|
|
464
|
-
shortForm: string;
|
|
465
|
-
} {
|
|
466
|
-
// HTTPSとSSH両方のURLをサポート
|
|
467
|
-
const httpsMatch = repoUrl.match(/github\.com\/([^/]+)\/([^/]+?)(\.git)?$/);
|
|
468
|
-
const sshMatch = repoUrl.match(/github\.com:([^/]+)\/([^/]+?)(\.git)?$/);
|
|
469
|
-
const match = httpsMatch || sshMatch;
|
|
470
|
-
|
|
471
|
-
if (!match) {
|
|
472
|
-
throw new ConfigValidationError(
|
|
473
|
-
'REPOSITORY_URL_INVALID',
|
|
474
|
-
`Invalid GitHub repository URL: ${repoUrl}`,
|
|
475
|
-
[]
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const org = match[1];
|
|
480
|
-
const repo = match[2];
|
|
481
|
-
|
|
482
|
-
return {
|
|
483
|
-
url: repoUrl,
|
|
484
|
-
org,
|
|
485
|
-
repo,
|
|
486
|
-
shortForm: `${org}/${repo}`,
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* シングルトンインスタンス
|
|
493
|
-
*/
|
|
494
|
-
export const configLoader = new ConfigLoader();
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* ヘルパー関数(後方互換性のため)
|
|
498
|
-
*/
|
|
499
|
-
export async function loadConfig(options?: LoadOptions): Promise<MergedConfig> {
|
|
500
|
-
return configLoader.load(options);
|
|
501
|
-
}
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### 6.2 Zodスキーマ定義
|
|
505
|
-
|
|
506
|
-
```typescript
|
|
507
|
-
// scripts/utils/config-schema.ts
|
|
508
|
-
|
|
509
|
-
import { z } from 'zod';
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* MergedConfig のZodスキーマ
|
|
513
|
-
*/
|
|
514
|
-
export const MergedConfigSchema = z.object({
|
|
515
|
-
atlassian: z.object({
|
|
516
|
-
url: z.string().url(),
|
|
517
|
-
email: z.string().email(),
|
|
518
|
-
apiToken: z.string().min(1),
|
|
519
|
-
}),
|
|
520
|
-
|
|
521
|
-
github: z.object({
|
|
522
|
-
org: z.string().min(1),
|
|
523
|
-
token: z.string().min(1),
|
|
524
|
-
repository: z.string().url(),
|
|
525
|
-
repositoryShort: z.string().min(1),
|
|
526
|
-
repositoryOrg: z.string().min(1),
|
|
527
|
-
repositoryName: z.string().min(1),
|
|
528
|
-
}),
|
|
529
|
-
|
|
530
|
-
confluence: z.object({
|
|
531
|
-
spaces: z.object({
|
|
532
|
-
prd: z.string().min(1),
|
|
533
|
-
qa: z.string().min(1),
|
|
534
|
-
release: z.string().min(1),
|
|
535
|
-
}),
|
|
536
|
-
pageCreationGranularity: z.enum(['single', 'by-section', 'by-hierarchy', 'manual']),
|
|
537
|
-
pageTitleFormat: z.string().optional(),
|
|
538
|
-
hierarchy: z.any().optional(),
|
|
539
|
-
}),
|
|
540
|
-
|
|
541
|
-
jira: z.object({
|
|
542
|
-
issueTypes: z.object({
|
|
543
|
-
story: z.string().min(1),
|
|
544
|
-
subtask: z.string().min(1),
|
|
545
|
-
}),
|
|
546
|
-
projectKeys: z.string().min(1),
|
|
547
|
-
createEpic: z.boolean(),
|
|
548
|
-
storyCreationGranularity: z.enum(['all', 'by-phase', 'selected-phases']),
|
|
549
|
-
selectedPhases: z.array(z.string()).optional(),
|
|
550
|
-
storyPoints: z.enum(['auto', 'manual', 'disabled']),
|
|
551
|
-
}),
|
|
552
|
-
|
|
553
|
-
workflow: z.object({
|
|
554
|
-
enabledPhases: z.array(z.string()).min(1),
|
|
555
|
-
approvalGates: z.object({
|
|
556
|
-
requirements: z.array(z.string()).optional(),
|
|
557
|
-
design: z.array(z.string()).optional(),
|
|
558
|
-
release: z.array(z.string()).optional(),
|
|
559
|
-
}).optional(),
|
|
560
|
-
}),
|
|
561
|
-
|
|
562
|
-
project: z.object({
|
|
563
|
-
id: z.string().min(1),
|
|
564
|
-
name: z.string().min(1),
|
|
565
|
-
language: z.enum(['ja', 'en']),
|
|
566
|
-
jiraProjectKey: z.string().regex(/^[A-Z]{2,10}$/),
|
|
567
|
-
confluenceLabels: z.array(z.string()),
|
|
568
|
-
status: z.string(),
|
|
569
|
-
team: z.array(z.string()),
|
|
570
|
-
stakeholders: z.array(z.string()),
|
|
571
|
-
repository: z.string().url(),
|
|
572
|
-
description: z.string(),
|
|
573
|
-
}),
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
export type MergedConfig = z.infer<typeof MergedConfigSchema>;
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
### 6.3 エラークラス定義
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
// scripts/utils/config-errors.ts
|
|
583
|
-
|
|
584
|
-
import { z } from 'zod';
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* 設定関連のエラー基底クラス
|
|
588
|
-
*/
|
|
589
|
-
export class ConfigError extends Error {
|
|
590
|
-
constructor(message: string, public code: string) {
|
|
591
|
-
super(message);
|
|
592
|
-
this.name = 'ConfigError';
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* バリデーションエラー
|
|
598
|
-
*/
|
|
599
|
-
export class ConfigValidationError extends ConfigError {
|
|
600
|
-
constructor(message: string, public details: z.ZodIssue[]) {
|
|
601
|
-
super(message, 'CONFIG_VALIDATION_ERROR');
|
|
602
|
-
this.name = 'ConfigValidationError';
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* 必須設定が見つからない
|
|
608
|
-
*/
|
|
609
|
-
export class ConfigNotFoundError extends ConfigError {
|
|
610
|
-
constructor(message: string, public missingKeys: string[]) {
|
|
611
|
-
super(message, 'CONFIG_NOT_FOUND');
|
|
612
|
-
this.name = 'ConfigNotFoundError';
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* マイグレーションエラー
|
|
618
|
-
*/
|
|
619
|
-
export class MigrationError extends ConfigError {
|
|
620
|
-
constructor(message: string, public phase: string) {
|
|
621
|
-
super(message, 'MIGRATION_ERROR');
|
|
622
|
-
this.name = 'MigrationError';
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
---
|
|
628
|
-
|