@sk8metal/michi-cli 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +25 -25
  3. package/dist/scripts/config/config-schema.d.ts +109 -600
  4. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  5. package/dist/scripts/config/config-schema.js.map +1 -1
  6. package/dist/scripts/config-interactive.d.ts +1 -1
  7. package/dist/scripts/config-interactive.d.ts.map +1 -1
  8. package/dist/scripts/config-interactive.js +7 -7
  9. package/dist/scripts/config-interactive.js.map +1 -1
  10. package/dist/scripts/confluence-sync.js +5 -5
  11. package/dist/scripts/confluence-sync.js.map +1 -1
  12. package/dist/scripts/create-project.d.ts +2 -2
  13. package/dist/scripts/create-project.js +2 -2
  14. package/dist/scripts/create-project.js.map +1 -1
  15. package/dist/scripts/jira-sync.js +8 -8
  16. package/dist/scripts/jira-sync.js.map +1 -1
  17. package/dist/scripts/markdown-to-confluence.js +1 -1
  18. package/dist/scripts/markdown-to-confluence.js.map +1 -1
  19. package/dist/scripts/multi-project-estimate.js +1 -1
  20. package/dist/scripts/phase-runner.js.map +1 -1
  21. package/dist/scripts/pre-flight-check.js +1 -1
  22. package/dist/scripts/setup-existing-project.js.map +1 -1
  23. package/dist/scripts/utils/__tests__/config-loader.test.d.ts +5 -0
  24. package/dist/scripts/utils/__tests__/config-loader.test.d.ts.map +1 -0
  25. package/dist/scripts/utils/__tests__/config-loader.test.js +201 -0
  26. package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -0
  27. package/dist/scripts/utils/__tests__/config-validator.test.js +29 -16
  28. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  29. package/dist/scripts/utils/config-loader.d.ts +4 -0
  30. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  31. package/dist/scripts/utils/config-loader.js +24 -2
  32. package/dist/scripts/utils/config-loader.js.map +1 -1
  33. package/dist/scripts/utils/config-validator.d.ts.map +1 -1
  34. package/dist/scripts/utils/config-validator.js +50 -51
  35. package/dist/scripts/utils/config-validator.js.map +1 -1
  36. package/dist/scripts/utils/confluence-hierarchy.js +7 -7
  37. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  38. package/dist/scripts/validate-phase.js.map +1 -1
  39. package/dist/scripts/workflow-orchestrator.js.map +1 -1
  40. package/dist/src/cli.js +2 -2
  41. package/dist/src/cli.js.map +1 -1
  42. package/dist/vitest.config.d.ts.map +1 -1
  43. package/dist/vitest.config.js +8 -4
  44. package/dist/vitest.config.js.map +1 -1
  45. package/docs/config-reference.md +76 -197
  46. package/docs/customization-guide.md +61 -9
  47. package/docs/multi-project.md +157 -25
  48. package/docs/new-project-setup.md +36 -36
  49. package/docs/quick-reference.md +23 -20
  50. package/docs/release.md +365 -0
  51. package/docs/setup.md +5 -3
  52. package/env.example +3 -1
  53. package/package.json +15 -13
  54. package/scripts/config/config-schema.ts +5 -5
  55. package/scripts/config-interactive.ts +7 -6
  56. package/scripts/confluence-sync.ts +5 -5
  57. package/scripts/create-project.ts +21 -21
  58. package/scripts/jira-sync.ts +8 -8
  59. package/scripts/markdown-to-confluence.ts +1 -1
  60. package/scripts/multi-project-estimate.ts +1 -1
  61. package/scripts/phase-runner.ts +8 -8
  62. package/scripts/pre-flight-check.ts +1 -1
  63. package/scripts/setup-existing-project.ts +9 -9
  64. package/scripts/setup-existing.sh +1 -1
  65. package/scripts/utils/__tests__/config-loader.test.ts +254 -0
  66. package/scripts/utils/__tests__/config-validator.test.ts +32 -16
  67. package/scripts/utils/config-loader.ts +30 -2
  68. package/scripts/utils/config-validator.ts +51 -50
  69. package/scripts/utils/confluence-hierarchy.ts +51 -51
  70. package/scripts/validate-phase.ts +11 -11
  71. package/scripts/workflow-orchestrator.ts +27 -27
  72. package/docs/testing.md +0 -202
@@ -23,7 +23,7 @@ import axios from 'axios';
23
23
  import { config } from 'dotenv';
24
24
  import { loadProjectMeta } from './utils/project-meta.js';
25
25
  import { validateFeatureNameOrThrow } from './utils/feature-name-validator.js';
26
- import { getConfig } from './utils/config-loader.js';
26
+ import { getConfig, getConfigPath } from './utils/config-loader.js';
27
27
  import { validateForJiraSync } from './utils/config-validator.js';
28
28
 
29
29
  config();
@@ -355,7 +355,7 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
355
355
  if (validation.errors.length > 0) {
356
356
  console.error('❌ Configuration errors:');
357
357
  validation.errors.forEach(error => console.error(` ${error}`));
358
- const configPath = resolve('.kiro/config.json');
358
+ const configPath = getConfigPath();
359
359
  console.error(`\n設定ファイル: ${configPath}`);
360
360
  throw new Error('JIRA同期に必要な設定値が不足しています。上記のエラーを確認して設定を修正してください。');
361
361
  }
@@ -370,7 +370,7 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
370
370
  if (!storyIssueTypeId) {
371
371
  throw new Error(
372
372
  'JIRA Story issue type ID is not configured. ' +
373
- 'Please set JIRA_ISSUE_TYPE_STORY environment variable or configure it in .kiro/config.json. ' +
373
+ 'Please set JIRA_ISSUE_TYPE_STORY environment variable or configure it in .michi/config.json. ' +
374
374
  'You can find the issue type ID in JIRA UI (Settings > Issues > Issue types) or via REST API: ' +
375
375
  'GET https://your-domain.atlassian.net/rest/api/3/issuetype'
376
376
  );
@@ -600,13 +600,13 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
600
600
 
601
601
  // JIRA APIエラーの詳細を表示
602
602
  if (error.response?.data) {
603
- console.error(` 📋 JIRA API Error Details:`, JSON.stringify(error.response.data, null, 2));
603
+ console.error(' 📋 JIRA API Error Details:', JSON.stringify(error.response.data, null, 2));
604
604
 
605
605
  // Story Pointsフィールドのエラーの場合、警告を表示
606
606
  if (error.response.data.errors && Object.keys(error.response.data.errors).some(key => key.includes('customfield'))) {
607
- console.error(` ⚠️ Story Pointsフィールドの設定に失敗しました。`);
608
- console.error(` 💡 環境変数 JIRA_STORY_POINTS_FIELD を正しいカスタムフィールドIDに設定してください。`);
609
- console.error(` 💡 JIRA管理画面でStory PointsのカスタムフィールドIDを確認してください。`);
607
+ console.error(' ⚠️ Story Pointsフィールドの設定に失敗しました。');
608
+ console.error(' 💡 環境変数 JIRA_STORY_POINTS_FIELD を正しいカスタムフィールドIDに設定してください。');
609
+ console.error(' 💡 JIRA管理画面でStory PointsのカスタムフィールドIDを確認してください。');
610
610
  }
611
611
  }
612
612
 
@@ -618,7 +618,7 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
618
618
  const newStoryCount = createdStories.filter(key => !existingStoryKeys.has(key)).length;
619
619
  const reusedStoryCount = createdStories.filter(key => existingStoryKeys.has(key)).length;
620
620
 
621
- console.log(`\n✅ JIRA sync completed`);
621
+ console.log('\n✅ JIRA sync completed');
622
622
  console.log(` Epic: ${epic.key}`);
623
623
  console.log(` Stories: ${createdStories.length} processed (${newStoryCount} new, ${reusedStoryCount} reused)`);
624
624
  }
@@ -93,7 +93,7 @@ function decodeHtmlEntities(text: string): string {
93
93
  '&gt;': '>',
94
94
  '&amp;': '&',
95
95
  '&quot;': '"',
96
- '&#39;': "'",
96
+ '&#39;': '\'',
97
97
  '&nbsp;': ' '
98
98
  };
99
99
 
@@ -153,7 +153,7 @@ function parseEstimateFromContent(content: string, featureName: string): Estimat
153
153
 
154
154
  return estimate;
155
155
  } catch (error) {
156
- console.warn(` ⚠️ Failed to parse estimate:`, error instanceof Error ? error.message : error);
156
+ console.warn(' ⚠️ Failed to parse estimate:', error instanceof Error ? error.message : error);
157
157
  return null;
158
158
  } finally {
159
159
  // 一時ファイルをクリーンアップ
@@ -256,14 +256,14 @@ export async function runPhase(feature: string, phase: Phase): Promise<PhaseRunR
256
256
  validateFeatureNameOrThrow(feature);
257
257
 
258
258
  switch (phase) {
259
- case 'requirements':
260
- return await runRequirementsPhase(feature);
261
- case 'design':
262
- return await runDesignPhase(feature);
263
- case 'tasks':
264
- return await runTasksPhase(feature);
265
- default:
266
- throw new Error(`Unknown phase: ${phase}`);
259
+ case 'requirements':
260
+ return await runRequirementsPhase(feature);
261
+ case 'design':
262
+ return await runDesignPhase(feature);
263
+ case 'tasks':
264
+ return await runTasksPhase(feature);
265
+ default:
266
+ throw new Error(`Unknown phase: ${phase}`);
267
267
  }
268
268
  }
269
269
 
@@ -178,7 +178,7 @@ async function checkJiraProject(projectKey: string): Promise<{ errors: string[],
178
178
  errors.push(` 現在の設定: "${projectKey}" → 実際に存在するキーに変更`);
179
179
  } else if (error.response?.status === 401) {
180
180
  errors.push('❌ JIRA認証エラー(.envの認証情報を確認)');
181
- errors.push(` → API Token管理: https://id.atlassian.com/manage-profile/security/api-tokens`);
181
+ errors.push(' → API Token管理: https://id.atlassian.com/manage-profile/security/api-tokens');
182
182
  } else {
183
183
  warnings.push(`⚠️ JIRAプロジェクトチェック失敗: ${error.message}`);
184
184
  }
@@ -30,15 +30,15 @@ function parseArgs(): SetupConfig {
30
30
  const value = args[i + 1];
31
31
 
32
32
  switch (key) {
33
- case 'michi-path':
34
- config.michiPath = value;
35
- break;
36
- case 'project-name':
37
- config.projectName = value;
38
- break;
39
- case 'jira-key':
40
- config.jiraKey = value;
41
- break;
33
+ case 'michi-path':
34
+ config.michiPath = value;
35
+ break;
36
+ case 'project-name':
37
+ config.projectName = value;
38
+ break;
39
+ case 'jira-key':
40
+ config.jiraKey = value;
41
+ break;
42
42
  }
43
43
  }
44
44
 
@@ -30,7 +30,7 @@ echo ""
30
30
  echo -e "${YELLOW}プロジェクト情報を入力してください:${NC}"
31
31
  echo ""
32
32
 
33
- read -p "プロジェクト名(例: A社 サービス1): " PROJECT_NAME
33
+ read -p "プロジェクト名(例: プロジェクトA): " PROJECT_NAME
34
34
  read -p "JIRAプロジェクトキー(例: PRJA): " JIRA_KEY
35
35
 
36
36
  # 入力値の検証
@@ -0,0 +1,254 @@
1
+ /**
2
+ * config-loader.ts のユニットテスト
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+ import { existsSync, writeFileSync, unlinkSync, mkdirSync, rmSync } from 'fs';
7
+ import { resolve, join } from 'path';
8
+ import { tmpdir } from 'os';
9
+ import {
10
+ loadConfig,
11
+ getConfig,
12
+ getConfigPath,
13
+ clearConfigCache
14
+ } from '../config-loader.js';
15
+
16
+ describe('config-loader', () => {
17
+ let testProjectRoot: string;
18
+ let originalEnv: NodeJS.ProcessEnv;
19
+
20
+ beforeEach(() => {
21
+ // テスト用の一時ディレクトリを作成(ランダム要素を追加して衝突を防ぐ)
22
+ testProjectRoot = resolve(tmpdir(), `michi-test-${Date.now()}-${Math.random().toString(36).substring(7)}`);
23
+
24
+ // 既存のディレクトリがあれば削除
25
+ if (existsSync(testProjectRoot)) {
26
+ rmSync(testProjectRoot, { recursive: true, force: true });
27
+ }
28
+
29
+ mkdirSync(testProjectRoot, { recursive: true });
30
+ mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
31
+
32
+ // 環境変数をバックアップ
33
+ originalEnv = { ...process.env };
34
+
35
+ // キャッシュをクリア(クリーンな状態から開始)
36
+ clearConfigCache();
37
+ });
38
+
39
+ afterEach(() => {
40
+ // 環境変数を復元
41
+ process.env = originalEnv;
42
+
43
+ // テスト用ディレクトリを削除
44
+ if (existsSync(testProjectRoot)) {
45
+ // ファイルを削除
46
+ const configPath = join(testProjectRoot, '.michi/config.json');
47
+ if (existsSync(configPath)) {
48
+ unlinkSync(configPath);
49
+ }
50
+ // ディレクトリを削除
51
+ try {
52
+ rmSync(testProjectRoot, { recursive: true, force: true });
53
+ } catch (e) {
54
+ // 削除失敗は無視
55
+ }
56
+ }
57
+
58
+ // キャッシュをクリア(ディレクトリ削除後に実行)
59
+ clearConfigCache();
60
+ });
61
+
62
+ describe('getConfigPath', () => {
63
+ it('.michi/config.jsonのパスを返す', () => {
64
+ clearConfigCache();
65
+ const configPath = getConfigPath(testProjectRoot);
66
+ expect(configPath).toBe(join(testProjectRoot, '.michi/config.json'));
67
+ });
68
+
69
+ it('設定ファイルが存在しない場合でも.michi/config.jsonのパスを返す', () => {
70
+ clearConfigCache();
71
+ const configPath = getConfigPath(testProjectRoot);
72
+ expect(configPath).toBe(join(testProjectRoot, '.michi/config.json'));
73
+ expect(existsSync(configPath)).toBe(false);
74
+ });
75
+ });
76
+
77
+ describe('loadConfig', () => {
78
+ it('設定ファイルが存在しない場合はデフォルト設定を返す', () => {
79
+ clearConfigCache();
80
+ const config = loadConfig(testProjectRoot);
81
+
82
+ expect(config).toBeDefined();
83
+ expect(config.confluence).toBeDefined();
84
+ expect(config.confluence?.pageCreationGranularity).toBe('single');
85
+ });
86
+
87
+ it('設定ファイルが存在する場合はマージされた設定を返す', () => {
88
+ clearConfigCache();
89
+ const configPath = join(testProjectRoot, '.michi/config.json');
90
+ writeFileSync(configPath, JSON.stringify({
91
+ confluence: {
92
+ pageCreationGranularity: 'by-hierarchy',
93
+ spaces: {
94
+ requirements: 'TestSpace'
95
+ }
96
+ }
97
+ }));
98
+
99
+ const config = loadConfig(testProjectRoot);
100
+
101
+ expect(config.confluence?.pageCreationGranularity).toBe('by-hierarchy');
102
+ expect(config.confluence?.spaces?.requirements).toBe('TestSpace');
103
+ });
104
+
105
+ it('無効なJSONの場合はエラーをスロー', () => {
106
+ clearConfigCache(); // キャッシュをクリア
107
+ const configPath = join(testProjectRoot, '.michi/config.json');
108
+ writeFileSync(configPath, '{ invalid json }');
109
+
110
+ expect(() => {
111
+ loadConfig(testProjectRoot);
112
+ }).toThrow(/Invalid JSON/);
113
+
114
+ // テスト後にファイルを削除して次のテストへの影響を防ぐ
115
+ if (existsSync(configPath)) {
116
+ unlinkSync(configPath);
117
+ }
118
+ });
119
+ });
120
+
121
+ describe('getConfig (キャッシュ付き)', () => {
122
+ // キャッシュテストでは、beforeEachでキャッシュをクリアしない
123
+ // 代わりに、各テストの最後にキャッシュをクリアする
124
+
125
+ it('設定ファイルが存在しない場合はデフォルト設定を返す', () => {
126
+ clearConfigCache(); // テスト開始時にクリア
127
+ const config = getConfig(testProjectRoot);
128
+
129
+ expect(config).toBeDefined();
130
+ expect(config.confluence).toBeDefined();
131
+ });
132
+
133
+ it('同じ設定ファイルを2回読み込む場合はキャッシュが使用される', () => {
134
+ clearConfigCache(); // テスト開始時にクリア
135
+
136
+ // .kiro/が存在しないことを確認(legacy警告を防ぐ)
137
+ const legacyDir = join(testProjectRoot, '.kiro');
138
+ if (existsSync(legacyDir)) {
139
+ rmSync(legacyDir, { recursive: true, force: true });
140
+ }
141
+
142
+ const configPath = join(testProjectRoot, '.michi/config.json');
143
+ writeFileSync(configPath, JSON.stringify({
144
+ confluence: {
145
+ pageCreationGranularity: 'by-hierarchy'
146
+ }
147
+ }));
148
+
149
+ const config1 = getConfig(testProjectRoot);
150
+ const config2 = getConfig(testProjectRoot);
151
+
152
+ // 同じオブジェクト参照であることを確認(キャッシュが使用されている)
153
+ expect(config1).toBe(config2);
154
+ });
155
+
156
+ it('設定ファイルを変更するとキャッシュが無効化される', async () => {
157
+ clearConfigCache(); // テスト開始時にクリア
158
+
159
+ // .michiディレクトリが存在することを確認(前のテストで削除されている可能性)
160
+ const michiDir = join(testProjectRoot, '.michi');
161
+ if (!existsSync(michiDir)) {
162
+ mkdirSync(michiDir, { recursive: true });
163
+ }
164
+
165
+ const configPath = join(testProjectRoot, '.michi/config.json');
166
+ writeFileSync(configPath, JSON.stringify({
167
+ confluence: {
168
+ pageCreationGranularity: 'single'
169
+ }
170
+ }));
171
+
172
+ const config1 = getConfig(testProjectRoot);
173
+ expect(config1.confluence?.pageCreationGranularity).toBe('single');
174
+
175
+ // ファイルシステムのmtime精度を考慮して少し待つ
176
+ await new Promise(resolve => setTimeout(resolve, 10));
177
+
178
+ // 設定ファイルを更新(ディレクトリが削除されている可能性があるため再確認)
179
+ if (!existsSync(michiDir)) {
180
+ mkdirSync(michiDir, { recursive: true });
181
+ }
182
+
183
+ writeFileSync(configPath, JSON.stringify({
184
+ confluence: {
185
+ pageCreationGranularity: 'by-hierarchy'
186
+ }
187
+ }));
188
+
189
+ const config2 = getConfig(testProjectRoot);
190
+ expect(config2.confluence?.pageCreationGranularity).toBe('by-hierarchy');
191
+ });
192
+ });
193
+
194
+ describe('警告メッセージ', () => {
195
+ it('legacyパス(.kiro/config.json)が存在する場合は警告を表示', () => {
196
+ // .michi/config.jsonが存在しないことを確認(警告が表示される条件)
197
+ const michiConfigPath = join(testProjectRoot, '.michi/config.json');
198
+ const michiDir = join(testProjectRoot, '.michi');
199
+
200
+ // .michi/config.jsonとディレクトリを削除
201
+ if (existsSync(michiConfigPath)) {
202
+ unlinkSync(michiConfigPath);
203
+ }
204
+ if (existsSync(michiDir)) {
205
+ rmSync(michiDir, { recursive: true, force: true });
206
+ }
207
+
208
+ // legacyパスにファイルを作成
209
+ mkdirSync(join(testProjectRoot, '.kiro'), { recursive: true });
210
+ const legacyConfigPath = join(testProjectRoot, '.kiro/config.json');
211
+ writeFileSync(legacyConfigPath, JSON.stringify({
212
+ confluence: {
213
+ pageCreationGranularity: 'single'
214
+ }
215
+ }));
216
+
217
+ // 警告が表示されることを確認(console.warnをモック)
218
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
219
+
220
+ // loadConfigを呼ぶことでresolveConfigPathが実行され、警告が表示される
221
+ loadConfig(testProjectRoot);
222
+
223
+ expect(consoleWarnSpy).toHaveBeenCalled();
224
+ expect(consoleWarnSpy.mock.calls[0][0]).toContain('Deprecated');
225
+ expect(consoleWarnSpy.mock.calls[0][0]).toContain('.kiro/config.json');
226
+
227
+ consoleWarnSpy.mockRestore();
228
+
229
+ // 次のテストのために.michiディレクトリを再作成
230
+ mkdirSync(michiDir, { recursive: true });
231
+ });
232
+
233
+ it('legacyパスと新規パスの両方が存在する場合は警告を表示しない', () => {
234
+ // 両方のパスにファイルを作成
235
+ mkdirSync(join(testProjectRoot, '.kiro'), { recursive: true });
236
+ const legacyConfigPath = join(testProjectRoot, '.kiro/config.json');
237
+ const michiConfigPath = join(testProjectRoot, '.michi/config.json');
238
+ writeFileSync(legacyConfigPath, JSON.stringify({}));
239
+ writeFileSync(michiConfigPath, JSON.stringify({}));
240
+
241
+ // 警告が表示されないことを確認
242
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
243
+
244
+ // loadConfigを呼ぶことでresolveConfigPathが実行される
245
+ loadConfig(testProjectRoot);
246
+
247
+ // 警告は表示されない(新規パスが存在するため)
248
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
249
+
250
+ consoleWarnSpy.mockRestore();
251
+ });
252
+ });
253
+ });
254
+
@@ -12,6 +12,7 @@ import {
12
12
  validateForJiraSync,
13
13
  validateAndReport
14
14
  } from '../config-validator.js';
15
+ import { clearConfigCache } from '../config-loader.js';
15
16
 
16
17
  describe('config-validator', () => {
17
18
  let testProjectRoot: string;
@@ -21,10 +22,13 @@ describe('config-validator', () => {
21
22
  // テスト用の一時ディレクトリを作成
22
23
  testProjectRoot = resolve(tmpdir(), `michi-test-${Date.now()}`);
23
24
  mkdirSync(testProjectRoot, { recursive: true });
24
- mkdirSync(join(testProjectRoot, '.kiro'), { recursive: true });
25
+ mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
25
26
 
26
27
  // 環境変数をバックアップ
27
28
  originalEnv = { ...process.env };
29
+
30
+ // キャッシュをクリア
31
+ clearConfigCache();
28
32
  });
29
33
 
30
34
  afterEach(() => {
@@ -34,7 +38,7 @@ describe('config-validator', () => {
34
38
  // テスト用ディレクトリを削除
35
39
  if (existsSync(testProjectRoot)) {
36
40
  // ファイルを削除
37
- const configPath = join(testProjectRoot, '.kiro/config.json');
41
+ const configPath = join(testProjectRoot, '.michi/config.json');
38
42
  if (existsSync(configPath)) {
39
43
  unlinkSync(configPath);
40
44
  }
@@ -49,6 +53,12 @@ describe('config-validator', () => {
49
53
 
50
54
  describe('validateProjectConfig', () => {
51
55
  it('設定ファイルが存在しない場合は情報メッセージを返す', () => {
56
+ // .michi/config.jsonが存在しないことを確認
57
+ const michiConfigPath = join(testProjectRoot, '.michi/config.json');
58
+ if (existsSync(michiConfigPath)) {
59
+ unlinkSync(michiConfigPath);
60
+ }
61
+
52
62
  const result = validateProjectConfig(testProjectRoot);
53
63
 
54
64
  expect(result.valid).toBe(true);
@@ -59,7 +69,13 @@ describe('config-validator', () => {
59
69
  });
60
70
 
61
71
  it('有効な設定ファイルの場合は成功', () => {
62
- const configPath = join(testProjectRoot, '.kiro/config.json');
72
+ // .michiディレクトリが存在することを確認
73
+ const michiDir = join(testProjectRoot, '.michi');
74
+ if (!existsSync(michiDir)) {
75
+ mkdirSync(michiDir, { recursive: true });
76
+ }
77
+
78
+ const configPath = join(testProjectRoot, '.michi/config.json');
63
79
  writeFileSync(configPath, JSON.stringify({
64
80
  confluence: {
65
81
  pageCreationGranularity: 'single',
@@ -76,7 +92,7 @@ describe('config-validator', () => {
76
92
  });
77
93
 
78
94
  it('無効なJSONの場合はエラーを返す', () => {
79
- const configPath = join(testProjectRoot, '.kiro/config.json');
95
+ const configPath = join(testProjectRoot, '.michi/config.json');
80
96
  writeFileSync(configPath, '{ invalid json }');
81
97
 
82
98
  const result = validateProjectConfig(testProjectRoot);
@@ -87,7 +103,7 @@ describe('config-validator', () => {
87
103
  });
88
104
 
89
105
  it('by-hierarchyモードでhierarchy設定がない場合はエラー', () => {
90
- const configPath = join(testProjectRoot, '.kiro/config.json');
106
+ const configPath = join(testProjectRoot, '.michi/config.json');
91
107
  writeFileSync(configPath, JSON.stringify({
92
108
  confluence: {
93
109
  pageCreationGranularity: 'by-hierarchy'
@@ -102,7 +118,7 @@ describe('config-validator', () => {
102
118
  });
103
119
 
104
120
  it('selected-phasesモードでselectedPhases設定がない場合はエラー', () => {
105
- const configPath = join(testProjectRoot, '.kiro/config.json');
121
+ const configPath = join(testProjectRoot, '.michi/config.json');
106
122
  writeFileSync(configPath, JSON.stringify({
107
123
  jira: {
108
124
  storyCreationGranularity: 'selected-phases'
@@ -122,7 +138,7 @@ describe('config-validator', () => {
122
138
  // 環境変数をクリア
123
139
  delete process.env.CONFLUENCE_PRD_SPACE;
124
140
 
125
- const configPath = join(testProjectRoot, '.kiro/config.json');
141
+ const configPath = join(testProjectRoot, '.michi/config.json');
126
142
  writeFileSync(configPath, JSON.stringify({
127
143
  confluence: {
128
144
  pageCreationGranularity: 'single'
@@ -140,7 +156,7 @@ describe('config-validator', () => {
140
156
  });
141
157
 
142
158
  it('spaces設定がある場合は成功', () => {
143
- const configPath = join(testProjectRoot, '.kiro/config.json');
159
+ const configPath = join(testProjectRoot, '.michi/config.json');
144
160
  writeFileSync(configPath, JSON.stringify({
145
161
  confluence: {
146
162
  spaces: {
@@ -158,7 +174,7 @@ describe('config-validator', () => {
158
174
  it('by-hierarchyモードでhierarchy設定がない場合はエラー', () => {
159
175
  // デフォルト設定を上書きするため、hierarchyキーを削除
160
176
  // デフォルト設定にhierarchyがあるため、実際にはエラーにならない可能性がある
161
- const configPath = join(testProjectRoot, '.kiro/config.json');
177
+ const configPath = join(testProjectRoot, '.michi/config.json');
162
178
  writeFileSync(configPath, JSON.stringify({
163
179
  confluence: {
164
180
  pageCreationGranularity: 'by-hierarchy',
@@ -177,7 +193,7 @@ describe('config-validator', () => {
177
193
  });
178
194
 
179
195
  it('manualモードでstructure設定がない場合はエラー', () => {
180
- const configPath = join(testProjectRoot, '.kiro/config.json');
196
+ const configPath = join(testProjectRoot, '.michi/config.json');
181
197
  writeFileSync(configPath, JSON.stringify({
182
198
  confluence: {
183
199
  pageCreationGranularity: 'manual',
@@ -196,7 +212,7 @@ describe('config-validator', () => {
196
212
 
197
213
  it('環境変数CONFLUENCE_PRD_SPACEがある場合は情報メッセージ', () => {
198
214
  process.env.CONFLUENCE_PRD_SPACE = 'Michi';
199
- const configPath = join(testProjectRoot, '.kiro/config.json');
215
+ const configPath = join(testProjectRoot, '.michi/config.json');
200
216
  writeFileSync(configPath, JSON.stringify({
201
217
  confluence: {
202
218
  pageCreationGranularity: 'single'
@@ -216,7 +232,7 @@ describe('config-validator', () => {
216
232
 
217
233
  describe('validateForJiraSync', () => {
218
234
  it('issueTypes.story設定がない場合はエラー', () => {
219
- const configPath = join(testProjectRoot, '.kiro/config.json');
235
+ const configPath = join(testProjectRoot, '.michi/config.json');
220
236
  writeFileSync(configPath, JSON.stringify({
221
237
  jira: {}
222
238
  }));
@@ -229,7 +245,7 @@ describe('config-validator', () => {
229
245
  });
230
246
 
231
247
  it('issueTypes.story設定がある場合は成功', () => {
232
- const configPath = join(testProjectRoot, '.kiro/config.json');
248
+ const configPath = join(testProjectRoot, '.michi/config.json');
233
249
  writeFileSync(configPath, JSON.stringify({
234
250
  jira: {
235
251
  issueTypes: {
@@ -246,7 +262,7 @@ describe('config-validator', () => {
246
262
 
247
263
  it('環境変数JIRA_ISSUE_TYPE_STORYがある場合は情報メッセージ', () => {
248
264
  process.env.JIRA_ISSUE_TYPE_STORY = '10036';
249
- const configPath = join(testProjectRoot, '.kiro/config.json');
265
+ const configPath = join(testProjectRoot, '.michi/config.json');
250
266
  writeFileSync(configPath, JSON.stringify({
251
267
  jira: {
252
268
  createEpic: true
@@ -264,7 +280,7 @@ describe('config-validator', () => {
264
280
  });
265
281
 
266
282
  it('issueTypes.subtask設定がない場合は警告', () => {
267
- const configPath = join(testProjectRoot, '.kiro/config.json');
283
+ const configPath = join(testProjectRoot, '.michi/config.json');
268
284
  writeFileSync(configPath, JSON.stringify({
269
285
  jira: {
270
286
  issueTypes: {
@@ -281,7 +297,7 @@ describe('config-validator', () => {
281
297
  });
282
298
 
283
299
  it('selected-phasesモードでselectedPhases設定がない場合はエラー', () => {
284
- const configPath = join(testProjectRoot, '.kiro/config.json');
300
+ const configPath = join(testProjectRoot, '.michi/config.json');
285
301
  writeFileSync(configPath, JSON.stringify({
286
302
  jira: {
287
303
  storyCreationGranularity: 'selected-phases',
@@ -140,11 +140,32 @@ function validateConfigPath(configPath: string, projectRoot: string): boolean {
140
140
  return !relativePath.startsWith('..') && !isAbsolute(relativePath);
141
141
  }
142
142
 
143
+ /**
144
+ * 設定ファイルのパスを解決
145
+ * 新規パス: .michi/config.json
146
+ * legacyパス(.kiro/config.json)が存在する場合は警告のみ表示
147
+ */
148
+ function resolveConfigPath(projectRoot: string): string {
149
+ const michiConfigPath = resolve(projectRoot, '.michi/config.json');
150
+ const legacyConfigPath = resolve(projectRoot, '.kiro/config.json');
151
+
152
+ // legacyパスが存在する場合は警告(移行推奨)
153
+ if (existsSync(legacyConfigPath) && !existsSync(michiConfigPath)) {
154
+ console.warn(
155
+ '⚠️ Deprecated: .kiro/config.json is deprecated.\n' +
156
+ ' Please migrate to .michi/config.json\n' +
157
+ ' The legacy path will not be supported in future versions.\n'
158
+ );
159
+ }
160
+
161
+ return michiConfigPath;
162
+ }
163
+
143
164
  /**
144
165
  * プロジェクト固有設定を読み込む
145
166
  */
146
167
  function loadProjectConfig(projectRoot: string = process.cwd()): Partial<AppConfig> | null {
147
- const projectConfigPath = resolve(projectRoot, '.kiro/config.json');
168
+ const projectConfigPath = resolveConfigPath(projectRoot);
148
169
 
149
170
  // パストラバーサル対策: パスを検証
150
171
  if (!validateConfigPath(projectConfigPath, projectRoot)) {
@@ -247,7 +268,7 @@ let cachedConfigMtime: number | null = null;
247
268
  let cachedDefaultConfigMtime: number | null = null;
248
269
 
249
270
  export function getConfig(projectRoot: string = process.cwd()): AppConfig {
250
- const projectConfigPath = resolve(projectRoot, '.kiro/config.json');
271
+ const projectConfigPath = resolveConfigPath(projectRoot);
251
272
  const currentFileUrl = import.meta.url;
252
273
  const currentFilePath = fileURLToPath(currentFileUrl);
253
274
  const currentDir = resolve(currentFilePath, '..');
@@ -324,3 +345,10 @@ export function clearConfigCache(): void {
324
345
  cachedDefaultConfigMtime = null;
325
346
  }
326
347
 
348
+ /**
349
+ * 設定ファイルのパスを解決(外部から使用可能)
350
+ */
351
+ export function getConfigPath(projectRoot: string = process.cwd()): string {
352
+ return resolveConfigPath(projectRoot);
353
+ }
354
+