@sk8metal/michi-cli 0.0.4 → 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 (44) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +24 -25
  3. package/dist/scripts/config-interactive.d.ts +1 -1
  4. package/dist/scripts/config-interactive.d.ts.map +1 -1
  5. package/dist/scripts/config-interactive.js +4 -4
  6. package/dist/scripts/config-interactive.js.map +1 -1
  7. package/dist/scripts/confluence-sync.js +2 -2
  8. package/dist/scripts/confluence-sync.js.map +1 -1
  9. package/dist/scripts/create-project.d.ts +2 -2
  10. package/dist/scripts/create-project.js +2 -2
  11. package/dist/scripts/jira-sync.js +3 -3
  12. package/dist/scripts/jira-sync.js.map +1 -1
  13. package/dist/scripts/utils/__tests__/config-loader.test.d.ts +5 -0
  14. package/dist/scripts/utils/__tests__/config-loader.test.d.ts.map +1 -0
  15. package/dist/scripts/utils/__tests__/config-loader.test.js +201 -0
  16. package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -0
  17. package/dist/scripts/utils/__tests__/config-validator.test.js +29 -16
  18. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  19. package/dist/scripts/utils/config-loader.d.ts +4 -0
  20. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  21. package/dist/scripts/utils/config-loader.js +24 -2
  22. package/dist/scripts/utils/config-loader.js.map +1 -1
  23. package/dist/scripts/utils/config-validator.js +8 -9
  24. package/dist/scripts/utils/config-validator.js.map +1 -1
  25. package/dist/src/cli.js +2 -2
  26. package/dist/src/cli.js.map +1 -1
  27. package/docs/config-reference.md +76 -197
  28. package/docs/customization-guide.md +61 -9
  29. package/docs/multi-project.md +157 -25
  30. package/docs/new-project-setup.md +36 -36
  31. package/docs/quick-reference.md +23 -20
  32. package/docs/setup.md +5 -3
  33. package/env.example +3 -1
  34. package/package.json +1 -1
  35. package/scripts/config-interactive.ts +4 -3
  36. package/scripts/confluence-sync.ts +2 -2
  37. package/scripts/create-project.ts +4 -4
  38. package/scripts/jira-sync.ts +3 -3
  39. package/scripts/setup-existing.sh +1 -1
  40. package/scripts/utils/__tests__/config-loader.test.ts +254 -0
  41. package/scripts/utils/__tests__/config-validator.test.ts +32 -16
  42. package/scripts/utils/config-loader.ts +30 -2
  43. package/scripts/utils/config-validator.ts +8 -8
  44. 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
  );
@@ -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
+
@@ -7,7 +7,7 @@ import { resolve } from 'path';
7
7
  import { AppConfigSchema } from '../config/config-schema.js';
8
8
  import type { ZodIssue } from 'zod';
9
9
  import type { AppConfig } from '../config/config-schema.js';
10
- import { getConfig } from './config-loader.js';
10
+ import { getConfig, getConfigPath } from './config-loader.js';
11
11
 
12
12
  /**
13
13
  * バリデーション結果
@@ -27,7 +27,7 @@ export function validateProjectConfig(projectRoot: string = process.cwd()): Vali
27
27
  const warnings: string[] = [];
28
28
  const info: string[] = [];
29
29
 
30
- const configPath = resolve(projectRoot, '.kiro/config.json');
30
+ const configPath = getConfigPath(projectRoot);
31
31
 
32
32
  if (!existsSync(configPath)) {
33
33
  // 設定ファイルが存在しない場合は情報メッセージ(デフォルト設定を使用)
@@ -184,7 +184,7 @@ export function validateForConfluenceSync(
184
184
  const info: string[] = [];
185
185
 
186
186
  const config = getConfig(projectRoot);
187
- const configPath = resolve(projectRoot, '.kiro/config.json');
187
+ const configPath = getConfigPath(projectRoot);
188
188
 
189
189
  // Confluence設定のチェック
190
190
  if (!config.confluence) {
@@ -198,7 +198,7 @@ export function validateForConfluenceSync(
198
198
  warnings.push(
199
199
  `confluence.spaces.${docType}が設定されていません。` +
200
200
  '環境変数CONFLUENCE_PRD_SPACEも設定されていないため、デフォルト値(PRD)を使用します。' +
201
- '\n 推奨: .kiro/config.jsonに以下を追加してください:\n' +
201
+ '\n 推奨: .michi/config.jsonに以下を追加してください:\n' +
202
202
  ' {\n' +
203
203
  ' "confluence": {\n' +
204
204
  ' "spaces": {\n' +
@@ -218,7 +218,7 @@ export function validateForConfluenceSync(
218
218
  errors.push(
219
219
  'confluence.hierarchyが設定されていません。' +
220
220
  `pageCreationGranularityが"${confluence.pageCreationGranularity}"の場合、hierarchy設定が必須です。` +
221
- '\n 解決方法: .kiro/config.jsonに以下を追加してください:\n' +
221
+ '\n 解決方法: .michi/config.jsonに以下を追加してください:\n' +
222
222
  ' {\n' +
223
223
  ' "confluence": {\n' +
224
224
  ' "hierarchy": {\n' +
@@ -263,7 +263,7 @@ export function validateForJiraSync(projectRoot: string = process.cwd()): Valida
263
263
  const info: string[] = [];
264
264
 
265
265
  const config = getConfig(projectRoot);
266
- const configPath = resolve(projectRoot, '.kiro/config.json');
266
+ const configPath = getConfigPath(projectRoot);
267
267
 
268
268
  // JIRA設定のチェック
269
269
  if (!config.jira) {
@@ -279,7 +279,7 @@ export function validateForJiraSync(projectRoot: string = process.cwd()): Valida
279
279
  '環境変数JIRA_ISSUE_TYPE_STORYも設定されていないため、JIRA同期を実行できません。' +
280
280
  '\n 解決方法1: 環境変数を設定:\n' +
281
281
  ' export JIRA_ISSUE_TYPE_STORY=10036 # JIRAインスタンス固有のID\n' +
282
- '\n 解決方法2: .kiro/config.jsonに以下を追加:\n' +
282
+ '\n 解決方法2: .michi/config.jsonに以下を追加:\n' +
283
283
  ' {\n' +
284
284
  ' "jira": {\n' +
285
285
  ' "issueTypes": {\n' +
@@ -299,7 +299,7 @@ export function validateForJiraSync(projectRoot: string = process.cwd()): Valida
299
299
  errors.push(
300
300
  'jira.issueTypes.storyが設定されていません。' +
301
301
  '環境変数JIRA_ISSUE_TYPE_STORYも設定されていないため、JIRA同期を実行できません。' +
302
- '\n 解決方法: .kiro/config.jsonのjira.issueTypes.storyに値を設定するか、' +
302
+ '\n 解決方法: .michi/config.jsonのjira.issueTypes.storyに値を設定するか、' +
303
303
  '環境変数JIRA_ISSUE_TYPE_STORYを設定してください。'
304
304
  );
305
305
  } else {