@sk8metal/michi-cli 0.3.0 → 0.4.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.
Files changed (157) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/scripts/config-global.d.ts +10 -0
  3. package/dist/scripts/config-global.d.ts.map +1 -0
  4. package/dist/scripts/config-global.js +111 -0
  5. package/dist/scripts/config-global.js.map +1 -0
  6. package/dist/scripts/confluence-sync.d.ts +22 -4
  7. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  8. package/dist/scripts/confluence-sync.js +22 -12
  9. package/dist/scripts/confluence-sync.js.map +1 -1
  10. package/dist/scripts/jira-sync.d.ts.map +1 -1
  11. package/dist/scripts/jira-sync.js +201 -167
  12. package/dist/scripts/jira-sync.js.map +1 -1
  13. package/dist/scripts/list-projects.js.map +1 -1
  14. package/dist/scripts/multi-project-estimate.js.map +1 -1
  15. package/dist/scripts/phase-runner.d.ts +1 -1
  16. package/dist/scripts/phase-runner.d.ts.map +1 -1
  17. package/dist/scripts/phase-runner.js +295 -522
  18. package/dist/scripts/phase-runner.js.map +1 -1
  19. package/dist/scripts/pre-flight-check.d.ts.map +1 -1
  20. package/dist/scripts/pre-flight-check.js +10 -6
  21. package/dist/scripts/pre-flight-check.js.map +1 -1
  22. package/dist/scripts/resource-dashboard.js.map +1 -1
  23. package/dist/scripts/spec-impl-workflow.js +1 -1
  24. package/dist/scripts/spec-impl-workflow.js.map +1 -1
  25. package/dist/scripts/template/renderer.d.ts +1 -1
  26. package/dist/scripts/template/renderer.d.ts.map +1 -1
  27. package/dist/scripts/test-interactive.d.ts.map +1 -1
  28. package/dist/scripts/test-interactive.js +0 -15
  29. package/dist/scripts/test-interactive.js.map +1 -1
  30. package/dist/scripts/test-new-features.js +6 -3
  31. package/dist/scripts/test-new-features.js.map +1 -1
  32. package/dist/scripts/test-spec-generator.d.ts.map +1 -1
  33. package/dist/scripts/test-spec-generator.js +1 -2
  34. package/dist/scripts/test-spec-generator.js.map +1 -1
  35. package/dist/scripts/utils/config-loader.d.ts +7 -2
  36. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  37. package/dist/scripts/utils/config-loader.js +79 -8
  38. package/dist/scripts/utils/config-loader.js.map +1 -1
  39. package/dist/scripts/utils/config-sections.d.ts +54 -0
  40. package/dist/scripts/utils/config-sections.d.ts.map +1 -0
  41. package/dist/scripts/utils/config-sections.js +178 -0
  42. package/dist/scripts/utils/config-sections.js.map +1 -0
  43. package/dist/scripts/utils/config-validator.d.ts +4 -0
  44. package/dist/scripts/utils/config-validator.d.ts.map +1 -1
  45. package/dist/scripts/utils/config-validator.js +57 -1
  46. package/dist/scripts/utils/config-validator.js.map +1 -1
  47. package/dist/scripts/utils/confluence-approval.d.ts.map +1 -1
  48. package/dist/scripts/utils/confluence-approval.js +5 -3
  49. package/dist/scripts/utils/confluence-approval.js.map +1 -1
  50. package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
  51. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  52. package/dist/scripts/utils/interactive-helpers.d.ts +32 -0
  53. package/dist/scripts/utils/interactive-helpers.d.ts.map +1 -0
  54. package/dist/scripts/utils/interactive-helpers.js +92 -0
  55. package/dist/scripts/utils/interactive-helpers.js.map +1 -0
  56. package/dist/scripts/utils/jira-issue-type-fetcher.d.ts.map +1 -1
  57. package/dist/scripts/utils/jira-issue-type-fetcher.js +27 -18
  58. package/dist/scripts/utils/jira-issue-type-fetcher.js.map +1 -1
  59. package/dist/scripts/utils/release-notes-generator.d.ts.map +1 -1
  60. package/dist/scripts/utils/release-notes-generator.js +2 -1
  61. package/dist/scripts/utils/release-notes-generator.js.map +1 -1
  62. package/dist/scripts/utils/spec-updater.d.ts +19 -0
  63. package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
  64. package/dist/scripts/utils/spec-updater.js.map +1 -1
  65. package/dist/scripts/utils/tasks-converter.d.ts.map +1 -1
  66. package/dist/scripts/utils/tasks-converter.js +2 -2
  67. package/dist/scripts/utils/tasks-converter.js.map +1 -1
  68. package/dist/scripts/utils/tasks-format-validator.d.ts.map +1 -1
  69. package/dist/scripts/utils/tasks-format-validator.js +0 -12
  70. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  71. package/dist/scripts/utils/test-runner.d.ts.map +1 -1
  72. package/dist/scripts/utils/test-runner.js +3 -2
  73. package/dist/scripts/utils/test-runner.js.map +1 -1
  74. package/dist/scripts/validate-phase.d.ts +1 -1
  75. package/dist/scripts/validate-phase.d.ts.map +1 -1
  76. package/dist/scripts/validate-phase.js +12 -62
  77. package/dist/scripts/validate-phase.js.map +1 -1
  78. package/dist/scripts/workflow-orchestrator.d.ts.map +1 -1
  79. package/dist/scripts/workflow-orchestrator.js +11 -16
  80. package/dist/scripts/workflow-orchestrator.js.map +1 -1
  81. package/dist/src/__tests__/integration/setup/init.test.d.ts +5 -0
  82. package/dist/src/__tests__/integration/setup/init.test.d.ts.map +1 -0
  83. package/dist/src/__tests__/integration/setup/init.test.js +352 -0
  84. package/dist/src/__tests__/integration/setup/init.test.js.map +1 -0
  85. package/dist/src/cli.d.ts.map +1 -1
  86. package/dist/src/cli.js +28 -20
  87. package/dist/src/cli.js.map +1 -1
  88. package/dist/src/commands/init.d.ts +28 -0
  89. package/dist/src/commands/init.d.ts.map +1 -0
  90. package/dist/src/commands/init.js +490 -0
  91. package/dist/src/commands/init.js.map +1 -0
  92. package/docs/user-guide/getting-started/setup.md +31 -3
  93. package/docs/user-guide/guides/customization.md +64 -11
  94. package/docs/user-guide/guides/workflow.md +35 -21
  95. package/docs/user-guide/reference/config.md +30 -5
  96. package/docs/user-guide/reference/quick-reference.md +68 -74
  97. package/docs/user-guide/testing/test-planning-flow.md +4 -0
  98. package/package.json +2 -4
  99. package/scripts/config-global.ts +160 -0
  100. package/scripts/confluence-sync.ts +91 -27
  101. package/scripts/jira-sync.ts +284 -218
  102. package/scripts/list-projects.ts +2 -2
  103. package/scripts/multi-project-estimate.ts +3 -3
  104. package/scripts/phase-runner.ts +391 -594
  105. package/scripts/pre-flight-check.ts +20 -9
  106. package/scripts/pre-publish-check.sh +3 -34
  107. package/scripts/resource-dashboard.ts +4 -4
  108. package/scripts/spec-impl-workflow.ts +1 -1
  109. package/scripts/template/renderer.ts +1 -1
  110. package/scripts/test-interactive.ts +0 -19
  111. package/scripts/test-new-features.ts +10 -7
  112. package/scripts/test-npm-package.sh +3 -34
  113. package/scripts/test-spec-generator.ts +3 -7
  114. package/scripts/utils/config-loader.ts +107 -26
  115. package/scripts/utils/config-sections.ts +316 -0
  116. package/scripts/utils/config-validator.ts +66 -1
  117. package/scripts/utils/confluence-approval.ts +8 -6
  118. package/scripts/utils/confluence-hierarchy.ts +27 -27
  119. package/scripts/utils/interactive-helpers.ts +135 -0
  120. package/scripts/utils/jira-issue-type-fetcher.ts +29 -21
  121. package/scripts/utils/release-notes-generator.ts +3 -2
  122. package/scripts/utils/spec-updater.ts +37 -15
  123. package/scripts/utils/tasks-converter.ts +4 -6
  124. package/scripts/utils/tasks-format-validator.ts +0 -13
  125. package/scripts/utils/test-runner.ts +4 -3
  126. package/scripts/validate-phase.ts +21 -80
  127. package/scripts/workflow-orchestrator.ts +16 -25
  128. package/templates/claude/commands/kiro/kiro-spec-impl.md +4 -0
  129. package/templates/claude/commands/kiro/kiro-spec-tasks.md +3 -1
  130. package/templates/claude/commands/michi/confluence-sync.md +8 -2
  131. package/templates/claude/commands/michi/design-review.md +4 -0
  132. package/templates/claude/commands/michi/e2e-plan.md +4 -0
  133. package/templates/claude/commands/michi/license-check.md +4 -0
  134. package/templates/claude/commands/michi/pr-resolve.md +4 -0
  135. package/templates/claude/commands/michi/project-switch.md +8 -2
  136. package/templates/claude/commands/michi/spec-design.md +78 -0
  137. package/templates/claude/commands/michi/spec-impl.md +716 -0
  138. package/templates/claude/commands/michi/test-planning.md +174 -0
  139. package/templates/claude/commands/michi/validate-design.md +58 -0
  140. package/templates/claude/commands/michi/version-audit.md +4 -0
  141. package/templates/michi/cc-sdd-overrides/README.md +8 -0
  142. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +53 -0
  143. package/dist/scripts/config-interactive.d.ts +0 -10
  144. package/dist/scripts/config-interactive.d.ts.map +0 -1
  145. package/dist/scripts/config-interactive.js +0 -372
  146. package/dist/scripts/config-interactive.js.map +0 -1
  147. package/dist/scripts/setup-existing-project.d.ts +0 -15
  148. package/dist/scripts/setup-existing-project.d.ts.map +0 -1
  149. package/dist/scripts/setup-existing-project.js +0 -455
  150. package/dist/scripts/setup-existing-project.js.map +0 -1
  151. package/dist/scripts/setup-interactive.d.ts +0 -10
  152. package/dist/scripts/setup-interactive.d.ts.map +0 -1
  153. package/dist/scripts/setup-interactive.js +0 -413
  154. package/dist/scripts/setup-interactive.js.map +0 -1
  155. package/scripts/config-interactive.ts +0 -550
  156. package/scripts/setup-existing-project.ts +0 -585
  157. package/scripts/setup-interactive.ts +0 -565
@@ -16,6 +16,13 @@ interface PreFlightResult {
16
16
  warnings: string[];
17
17
  }
18
18
 
19
+ interface ProjectMeta {
20
+ projectId?: string;
21
+ projectName?: string;
22
+ jiraProjectKey?: string;
23
+ [key: string]: unknown;
24
+ }
25
+
19
26
  /**
20
27
  * .env設定をチェック
21
28
  */
@@ -78,7 +85,7 @@ function checkProjectJson(): { errors: string[], warnings: string[] } {
78
85
  return { errors, warnings };
79
86
  }
80
87
 
81
- let projectMeta: any;
88
+ let projectMeta: ProjectMeta;
82
89
  try {
83
90
  projectMeta = JSON.parse(readFileSync(projectJsonPath, 'utf-8'));
84
91
  } catch {
@@ -125,16 +132,18 @@ async function checkConfluenceSpace(spaceKey: string): Promise<{ errors: string[
125
132
  if (response.data) {
126
133
  console.log(` ✅ Confluenceスペース確認: ${spaceKey} (${response.data.name})`);
127
134
  }
128
- } catch (error: any) {
129
- if (error.response?.status === 404) {
135
+ } catch (error: unknown) {
136
+ const isAxiosError = axios.isAxiosError(error);
137
+ if (isAxiosError && error.response?.status === 404) {
130
138
  errors.push(`❌ Confluenceスペースが存在しません: ${spaceKey}`);
131
139
  errors.push(` → Confluenceで新しいスペースを作成: ${url}/wiki/spaces`);
132
140
  errors.push(' → または、.envのCONFLUENCE_PRD_SPACEを修正してください');
133
- } else if (error.response?.status === 401) {
141
+ } else if (isAxiosError && error.response?.status === 401) {
134
142
  errors.push('❌ Confluence認証エラー(.envの認証情報を確認)');
135
143
  errors.push(` → API Token管理: ${url.replace('atlassian.net', 'atlassian.net/manage/profile/security/api-tokens')}`);
136
144
  } else {
137
- warnings.push(`⚠️ Confluenceスペースチェック失敗: ${error.message}`);
145
+ const message = error instanceof Error ? error.message : String(error);
146
+ warnings.push(`⚠️ Confluenceスペースチェック失敗: ${message}`);
138
147
  }
139
148
  }
140
149
 
@@ -169,18 +178,20 @@ async function checkJiraProject(projectKey: string): Promise<{ errors: string[],
169
178
  if (response.data) {
170
179
  console.log(` ✅ JIRAプロジェクト確認: ${projectKey} (${response.data.name})`);
171
180
  }
172
- } catch (error: any) {
173
- if (error.response?.status === 404) {
181
+ } catch (error: unknown) {
182
+ const isAxiosError = axios.isAxiosError(error);
183
+ if (isAxiosError && error.response?.status === 404) {
174
184
  errors.push(`❌ JIRAプロジェクトが存在しません: ${projectKey}`);
175
185
  errors.push(` → JIRAプロジェクト作成: ${url}/jira/projects/create`);
176
186
  errors.push(` → プロジェクト一覧: ${url}/jira/settings/projects`);
177
187
  errors.push(' → または、.kiro/project.jsonのjiraProjectKeyを修正してください');
178
188
  errors.push(` 現在の設定: "${projectKey}" → 実際に存在するキーに変更`);
179
- } else if (error.response?.status === 401) {
189
+ } else if (isAxiosError && error.response?.status === 401) {
180
190
  errors.push('❌ JIRA認証エラー(.envの認証情報を確認)');
181
191
  errors.push(' → API Token管理: https://id.atlassian.com/manage-profile/security/api-tokens');
182
192
  } else {
183
- warnings.push(`⚠️ JIRAプロジェクトチェック失敗: ${error.message}`);
193
+ const message = error instanceof Error ? error.message : String(error);
194
+ warnings.push(`⚠️ JIRAプロジェクトチェック失敗: ${message}`);
184
195
  }
185
196
  }
186
197
 
@@ -142,23 +142,8 @@ echo_success "Test feature initialized"
142
142
  # ========================================
143
143
  echo_step "Testing New Phases (0.3-0.4, 1, A, B)"
144
144
 
145
- # Phase 0.3: テストタイプ選択
146
- echo_info "Testing Phase 0.3: test-type-selection"
147
- if npm run phase:run "${TEST_FEATURE}" test-type-selection > /dev/null 2>&1; then
148
- echo_success "Phase 0.3 succeeded"
149
- else
150
- echo_error "Phase 0.3 failed"
151
- exit 1
152
- fi
153
-
154
- # Phase 0.4: テスト仕様書作成
155
- echo_info "Testing Phase 0.4: test-spec"
156
- if npm run phase:run "${TEST_FEATURE}" test-spec > /dev/null 2>&1; then
157
- echo_success "Phase 0.4 succeeded"
158
- else
159
- echo_error "Phase 0.4 failed"
160
- exit 1
161
- fi
145
+ # Phase 0.3-0.4: Test planning is now handled by AI command /michi:test-planning
146
+ # (CLI commands removed)
162
147
 
163
148
  # Phase 1: 環境構築
164
149
  echo_info "Testing Phase 1: environment-setup"
@@ -192,23 +177,7 @@ fi
192
177
  # ========================================
193
178
  echo_step "Testing Phase Validations"
194
179
 
195
- # Phase 0.3
196
- echo_info "Validating Phase 0.3"
197
- if npm run validate:phase "${TEST_FEATURE}" test-type-selection > /dev/null 2>&1; then
198
- echo_success "Phase 0.3 validation passed"
199
- else
200
- echo_error "Phase 0.3 validation failed"
201
- exit 1
202
- fi
203
-
204
- # Phase 0.4
205
- echo_info "Validating Phase 0.4"
206
- if npm run validate:phase "${TEST_FEATURE}" test-spec > /dev/null 2>&1; then
207
- echo_success "Phase 0.4 validation passed"
208
- else
209
- echo_error "Phase 0.4 validation failed"
210
- exit 1
211
- fi
180
+ # Phase 0.3-0.4: Validation removed (test planning is now handled by AI command)
212
181
 
213
182
  # Phase 1
214
183
  echo_info "Validating Phase 1"
@@ -70,7 +70,7 @@ async function createResourceDashboard(): Promise<void> {
70
70
  const { data } = await octokit.repos.getContent({
71
71
  owner: org,
72
72
  repo: repo.name,
73
- path: `projects/${(projectEntry as any).name}/.kiro/project.json`
73
+ path: `projects/${(projectEntry as { name: string }).name}/.kiro/project.json`
74
74
  });
75
75
 
76
76
  if ('content' in data) {
@@ -154,16 +154,16 @@ async function createResourceDashboard(): Promise<void> {
154
154
  existingPage.id,
155
155
  pageTitle,
156
156
  dashboardContent,
157
- existingPage.version.number
157
+ existingPage.version!.number
158
158
  );
159
159
  const baseUrl = process.env.ATLASSIAN_URL || '';
160
- pageUrl = `${baseUrl}/wiki${updated._links.webui}`;
160
+ pageUrl = `${baseUrl}/wiki${updated._links!.webui}`;
161
161
  console.log(`✅ Dashboard page updated: ${pageUrl}`);
162
162
  } else {
163
163
  // 新規ページを作成
164
164
  const created = await client.createPage(spaceKey, pageTitle, dashboardContent, ['dashboard', 'resource-management']);
165
165
  const baseUrl = process.env.ATLASSIAN_URL || '';
166
- pageUrl = `${baseUrl}/wiki${created._links.webui}`;
166
+ pageUrl = `${baseUrl}/wiki${created._links!.webui}`;
167
167
  console.log(`✅ Dashboard page created: ${pageUrl}`);
168
168
  }
169
169
  } catch (error) {
@@ -299,7 +299,7 @@ ${jiraLink}
299
299
  // 2. Epic と Story を「レビュー待ち」に遷移
300
300
  try {
301
301
  await transitionEpicAndStory(jiraInfo, statusMapping.readyForReview);
302
- } catch (error) {
302
+ } catch (_error) {
303
303
  console.error(
304
304
  `⚠️ JIRA ステータス更新に失敗しましたが、PR は作成されています: ${prUrl}`,
305
305
  );
@@ -80,7 +80,7 @@ export const renderTemplate = (
80
80
  * // Result: { lang: "ja", dir: ".kiro" }
81
81
  * ```
82
82
  */
83
- export const renderJsonTemplate = <T = any>(
83
+ export const renderJsonTemplate = <T = unknown>(
84
84
  template: string,
85
85
  context: TemplateContext
86
86
  ): T => {
@@ -120,20 +120,6 @@ function question(rl: readline.Interface, query: string): Promise<string> {
120
120
  });
121
121
  }
122
122
 
123
- /**
124
- * Yes/No質問
125
- */
126
- async function confirm(rl: readline.Interface, prompt: string, defaultValue: boolean = true): Promise<boolean> {
127
- const defaultText = defaultValue ? '[Y/n]' : '[y/N]';
128
- const answer = await question(rl, `${prompt} ${defaultText}: `);
129
-
130
- if (!answer) {
131
- return defaultValue;
132
- }
133
-
134
- return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
135
- }
136
-
137
123
  /**
138
124
  * 選択肢を表示
139
125
  */
@@ -676,11 +662,6 @@ async function main(): Promise<number> {
676
662
  console.log('Phase Bテスト(手動回帰、負荷、セキュリティ)を対話的に作成します。\n');
677
663
 
678
664
  // テストタイプを選択
679
- const testTypeChoices = TEST_TYPES.map(t => ({
680
- value: t.value,
681
- label: `${t.label} - ${t.description}`
682
- }));
683
-
684
665
  console.log('テストタイプを選択してください:');
685
666
  TEST_TYPES.forEach((t, index) => {
686
667
  console.log(` ${index + 1}. ${t.label}`);
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { executeTests, generateTestReport } from './utils/test-runner.js';
6
- import { createReleaseNotes, getCommits, generateReleaseNotes, formatReleaseNotes } from './utils/release-notes-generator.js';
6
+ import { getCommits, generateReleaseNotes, formatReleaseNotes } from './utils/release-notes-generator.js';
7
7
  import { getApprovalStatus } from './utils/confluence-approval.js';
8
8
 
9
9
  async function testTestRunner() {
@@ -32,8 +32,9 @@ async function testTestRunner() {
32
32
  console.log(report.substring(0, 500) + '...');
33
33
 
34
34
  return true;
35
- } catch (error: any) {
36
- console.error('❌ テストランナーエラー:', error.message);
35
+ } catch (error: unknown) {
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ console.error('❌ テストランナーエラー:', message);
37
38
  return false;
38
39
  }
39
40
  }
@@ -74,8 +75,9 @@ async function testReleaseNotesGenerator() {
74
75
  console.log('⚠️ コミットが見つかりませんでした');
75
76
  return false;
76
77
  }
77
- } catch (error: any) {
78
- console.error('❌ リリースノート生成エラー:', error.message);
78
+ } catch (error: unknown) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ console.error('❌ リリースノート生成エラー:', message);
79
81
  return false;
80
82
  }
81
83
  }
@@ -123,8 +125,9 @@ async function testConfluenceApproval() {
123
125
  console.log(` 承認者: ${status.approvers.join(', ') || 'なし'}`);
124
126
 
125
127
  return true;
126
- } catch (error: any) {
127
- console.error('❌ Confluence承認状態確認エラー:', error.message);
128
+ } catch (error: unknown) {
129
+ const message = error instanceof Error ? error.message : String(error);
130
+ console.error('❌ Confluence承認状態確認エラー:', message);
128
131
  return false;
129
132
  }
130
133
  }
@@ -208,23 +208,8 @@ fi
208
208
 
209
209
  echo_info "Testing phase:run commands..."
210
210
 
211
- # Phase 0.3: test-type-selection
212
- echo_info "Testing phase:run test-type-selection"
213
- if "$MICHI_CMD" phase:run test-feature test-type-selection > /dev/null 2>&1; then
214
- echo_success "phase:run test-type-selection succeeded"
215
- else
216
- echo_error "phase:run test-type-selection failed"
217
- exit 1
218
- fi
219
-
220
- # Phase 0.4: test-spec
221
- echo_info "Testing phase:run test-spec"
222
- if "$MICHI_CMD" phase:run test-feature test-spec > /dev/null 2>&1; then
223
- echo_success "phase:run test-spec succeeded"
224
- else
225
- echo_error "phase:run test-spec failed"
226
- exit 1
227
- fi
211
+ # Phase 0.3-0.4: test planning is now handled by AI command /michi:test-planning
212
+ # (CLI commands removed)
228
213
 
229
214
  # Phase 1: environment-setup
230
215
  echo_info "Testing phase:run environment-setup"
@@ -260,23 +245,7 @@ echo_step "Testing Validation Commands"
260
245
 
261
246
  echo_info "Testing validate:phase commands..."
262
247
 
263
- # Phase 0.3
264
- echo_info "Validating test-type-selection"
265
- if "$MICHI_CMD" validate:phase test-feature test-type-selection > /dev/null 2>&1; then
266
- echo_success "validate:phase test-type-selection succeeded"
267
- else
268
- echo_error "validate:phase test-type-selection failed"
269
- exit 1
270
- fi
271
-
272
- # Phase 0.4
273
- echo_info "Validating test-spec"
274
- if "$MICHI_CMD" validate:phase test-feature test-spec > /dev/null 2>&1; then
275
- echo_success "validate:phase test-spec succeeded"
276
- else
277
- echo_error "validate:phase test-spec failed"
278
- exit 1
279
- fi
248
+ # Phase 0.3-0.4: validation removed (test planning is now handled by AI command)
280
249
 
281
250
  # Phase 1
282
251
  echo_info "Validating environment-setup"
@@ -8,10 +8,7 @@ import { join } from 'path';
8
8
  import {
9
9
  extractComponents,
10
10
  extractFlows,
11
- extractRequirements,
12
- type Component,
13
- type Flow,
14
- type Requirement
11
+ extractRequirements
15
12
  } from './utils/markdown-parser.js';
16
13
  import {
17
14
  loadTestSpecTemplate,
@@ -24,9 +21,8 @@ import {
24
21
  * 単体テスト仕様書を生成
25
22
  */
26
23
  export async function generateUnitTestSpec(feature: string, projectRoot: string = process.cwd()): Promise<string> {
27
- const requirementsPath = join(projectRoot, '.kiro', 'specs', feature, 'requirements.md');
28
24
  const designPath = join(projectRoot, '.kiro', 'specs', feature, 'design.md');
29
-
25
+
30
26
  if (!existsSync(designPath)) {
31
27
  throw new Error(`design.mdが見つかりません: ${designPath}`);
32
28
  }
@@ -229,7 +225,7 @@ export async function generateE2ETestSpec(feature: string, projectRoot: string =
229
225
  'テストユーザーが作成されている',
230
226
  'テストデータが準備されている'
231
227
  ],
232
- steps: req.acceptanceCriteria.slice(0, 3).map((ac, idx) =>
228
+ steps: req.acceptanceCriteria.slice(0, 3).map(ac =>
233
229
  `${ac.substring(0, 100)}...を実行`
234
230
  ),
235
231
  expectedResults: req.acceptanceCriteria.map(ac =>
@@ -6,27 +6,45 @@
6
6
  import { readFileSync, existsSync, statSync } from 'fs';
7
7
  import { resolve, relative, isAbsolute } from 'path';
8
8
  import { fileURLToPath } from 'url';
9
+ import { homedir } from 'os';
9
10
  import { config } from 'dotenv';
10
11
  import { AppConfigSchema, type AppConfig } from '../config/config-schema.js';
11
12
 
12
13
  // 環境変数読み込み
13
14
  config();
14
15
 
16
+ /**
17
+ * グローバル設定ファイルのパス定数
18
+ */
19
+ const GLOBAL_CONFIG_DIR = '.michi';
20
+ const GLOBAL_CONFIG_FILE = 'config.json';
21
+
22
+ /**
23
+ * グローバル設定ファイルのパスを取得
24
+ */
25
+ export function getGlobalConfigPath(): string {
26
+ const home = homedir();
27
+ return resolve(home, GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_FILE);
28
+ }
29
+
15
30
  /**
16
31
  * 深いマージ(Deep Merge)
17
32
  * オブジェクトを再帰的にマージする
18
33
  */
19
- function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
34
+ function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
20
35
  const result = { ...target };
21
-
36
+
22
37
  for (const key in source) {
23
38
  if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
24
- result[key] = deepMerge(result[key] || {} as T[Extract<keyof T, string>], source[key] as Partial<T[Extract<keyof T, string>]>);
39
+ result[key] = deepMerge(
40
+ (result[key] || {}) as Record<string, unknown>,
41
+ source[key] as Record<string, unknown>
42
+ ) as T[Extract<keyof T, string>];
25
43
  } else if (source[key] !== undefined) {
26
44
  result[key] = source[key] as T[Extract<keyof T, string>];
27
45
  }
28
46
  }
29
-
47
+
30
48
  return result;
31
49
  }
32
50
 
@@ -60,7 +78,7 @@ function expandEnvVars(str: string): string {
60
78
  * 設定オブジェクト内の文字列値を環境変数で展開
61
79
  * 循環参照を防ぐため、処理済みオブジェクトを追跡
62
80
  */
63
- function expandEnvVarsInConfig(config: any, visited: WeakSet<object> = new WeakSet()): any {
81
+ function expandEnvVarsInConfig(config: unknown, visited: WeakSet<object> = new WeakSet()): unknown {
64
82
  if (typeof config === 'string') {
65
83
  return expandEnvVars(config);
66
84
  }
@@ -77,9 +95,9 @@ function expandEnvVarsInConfig(config: any, visited: WeakSet<object> = new WeakS
77
95
  }
78
96
 
79
97
  visited.add(config);
80
- const result: any = {};
81
- for (const key in config) {
82
- result[key] = expandEnvVarsInConfig(config[key], visited);
98
+ const result: Record<string, unknown> = {};
99
+ for (const key in config as Record<string, unknown>) {
100
+ result[key] = expandEnvVarsInConfig((config as Record<string, unknown>)[key], visited);
83
101
  }
84
102
  visited.delete(config);
85
103
  return result;
@@ -113,7 +131,8 @@ function loadDefaultConfig(): AppConfig {
113
131
  return AppConfigSchema.parse(expanded);
114
132
  } catch (error) {
115
133
  if (error instanceof SyntaxError) {
116
- throw new Error(`Invalid JSON in default config file ${defaultConfigPath}: ${error.message}\nLine: ${(error as any).line}, Column: ${(error as any).column}`);
134
+ // SyntaxErrorには標準的なlinecolumnプロパティはないため、messageのみ使用
135
+ throw new Error(`Invalid JSON in default config file ${defaultConfigPath}: ${error.message}`);
117
136
  }
118
137
  if (error instanceof Error && error.name === 'ZodError') {
119
138
  throw new Error(`Default config validation failed: ${error.message}\nFile: ${defaultConfigPath}`);
@@ -166,23 +185,23 @@ function resolveConfigPath(projectRoot: string): string {
166
185
  */
167
186
  function loadProjectConfig(projectRoot: string = process.cwd()): Partial<AppConfig> | null {
168
187
  const projectConfigPath = resolveConfigPath(projectRoot);
169
-
188
+
170
189
  // パストラバーサル対策: パスを検証
171
190
  if (!validateConfigPath(projectConfigPath, projectRoot)) {
172
191
  throw new Error(`Invalid config path: ${projectConfigPath} is outside project root`);
173
192
  }
174
-
193
+
175
194
  if (!existsSync(projectConfigPath)) {
176
195
  return null;
177
196
  }
178
-
197
+
179
198
  try {
180
199
  const content = readFileSync(projectConfigPath, 'utf-8');
181
200
  const parsed = JSON.parse(content);
182
-
201
+
183
202
  // 環境変数を展開
184
203
  const expanded = expandEnvVarsInConfig(parsed);
185
-
204
+
186
205
  // 部分的な設定なので、スキーマで厳密にバリデーションしない
187
206
  // ただし、存在するキーについては型チェック
188
207
  return expanded as Partial<AppConfig>;
@@ -197,25 +216,63 @@ function loadProjectConfig(projectRoot: string = process.cwd()): Partial<AppConf
197
216
  }
198
217
  }
199
218
 
219
+ /**
220
+ * グローバル設定を読み込む
221
+ */
222
+ function loadGlobalConfig(): Partial<AppConfig> | null {
223
+ const globalConfigPath = getGlobalConfigPath();
224
+
225
+ if (!existsSync(globalConfigPath)) {
226
+ return null;
227
+ }
228
+
229
+ try {
230
+ const content = readFileSync(globalConfigPath, 'utf-8');
231
+ const parsed = JSON.parse(content);
232
+
233
+ // 環境変数を展開
234
+ const expanded = expandEnvVarsInConfig(parsed);
235
+
236
+ return expanded as Partial<AppConfig>;
237
+ } catch (error) {
238
+ if (error instanceof SyntaxError) {
239
+ console.warn(`⚠️ Invalid JSON in global config ${globalConfigPath}: ${error.message}`);
240
+ } else {
241
+ console.warn(`⚠️ Failed to load global config: ${error instanceof Error ? error.message : 'Unknown error'}`);
242
+ }
243
+ return null;
244
+ }
245
+ }
246
+
200
247
  /**
201
248
  * 設定を読み込んでマージ
202
- *
249
+ *
203
250
  * マージ順序:
204
251
  * 1. デフォルト設定
205
- * 2. プロジェクト固有設定(上書き)
206
- * 3. 環境変数(最終上書き、既存の動作を維持)
252
+ * 2. グローバル設定(上書き)
253
+ * 3. プロジェクト固有設定(上書き)
254
+ * 4. 環境変数(最終上書き、既存の動作を維持)
207
255
  */
208
256
  export function loadConfig(projectRoot: string = process.cwd()): AppConfig {
209
257
  // デフォルト設定を読み込み
210
258
  const defaultConfig = loadDefaultConfig();
211
-
259
+
260
+ // グローバル設定を読み込み
261
+ const globalConfig = loadGlobalConfig();
262
+
212
263
  // プロジェクト固有設定を読み込み
213
264
  const projectConfig = loadProjectConfig(projectRoot);
214
-
215
- // マージ(プロジェクト設定がデフォルトを上書き)
216
- const mergedConfig: AppConfig = projectConfig
217
- ? deepMerge(defaultConfig, projectConfig)
218
- : defaultConfig;
265
+
266
+ // マージ(デフォルト → グローバル → プロジェクト)
267
+ let mergedConfig: AppConfig = defaultConfig;
268
+
269
+ if (globalConfig) {
270
+ mergedConfig = deepMerge(mergedConfig, globalConfig);
271
+ }
272
+
273
+ if (projectConfig) {
274
+ mergedConfig = deepMerge(mergedConfig, projectConfig);
275
+ }
219
276
 
220
277
  // 環境変数で最終上書き(条件付き)
221
278
  // 注意: config.jsonにspaces設定がある場合は環境変数を無視(config.jsonを優先)
@@ -266,14 +323,16 @@ let cachedConfig: AppConfig | null = null;
266
323
  let cachedProjectRoot: string | null = null;
267
324
  let cachedConfigMtime: number | null = null;
268
325
  let cachedDefaultConfigMtime: number | null = null;
326
+ let cachedGlobalConfigMtime: number | null = null;
269
327
 
270
328
  export function getConfig(projectRoot: string = process.cwd()): AppConfig {
271
329
  const projectConfigPath = resolveConfigPath(projectRoot);
330
+ const globalConfigPath = getGlobalConfigPath();
272
331
  const currentFileUrl = import.meta.url;
273
332
  const currentFilePath = fileURLToPath(currentFileUrl);
274
333
  const currentDir = resolve(currentFilePath, '..');
275
334
  const defaultConfigPath = resolve(currentDir, '../config/default-config.json');
276
-
335
+
277
336
  // デフォルト設定ファイルの更新時刻をチェック
278
337
  let defaultConfigChanged = false;
279
338
  try {
@@ -295,7 +354,27 @@ export function getConfig(projectRoot: string = process.cwd()): AppConfig {
295
354
  defaultConfigChanged = true;
296
355
  cachedDefaultConfigMtime = null;
297
356
  }
298
-
357
+
358
+ // グローバル設定ファイルの更新時刻をチェック
359
+ let globalConfigChanged = false;
360
+ try {
361
+ if (existsSync(globalConfigPath)) {
362
+ const globalStats = statSync(globalConfigPath);
363
+ if (cachedGlobalConfigMtime !== globalStats.mtimeMs) {
364
+ globalConfigChanged = true;
365
+ cachedGlobalConfigMtime = globalStats.mtimeMs;
366
+ }
367
+ } else {
368
+ if (cachedGlobalConfigMtime !== null) {
369
+ globalConfigChanged = true;
370
+ cachedGlobalConfigMtime = null;
371
+ }
372
+ }
373
+ } catch {
374
+ globalConfigChanged = true;
375
+ cachedGlobalConfigMtime = null;
376
+ }
377
+
299
378
  // プロジェクト設定ファイルの更新時刻をチェック
300
379
  let projectConfigChanged = false;
301
380
  try {
@@ -317,12 +396,13 @@ export function getConfig(projectRoot: string = process.cwd()): AppConfig {
317
396
  projectConfigChanged = true;
318
397
  cachedConfigMtime = null;
319
398
  }
320
-
399
+
321
400
  // キャッシュが有効で、設定ファイルが変更されていない場合はキャッシュを返す
322
401
  if (
323
402
  cachedConfig &&
324
403
  cachedProjectRoot === projectRoot &&
325
404
  !defaultConfigChanged &&
405
+ !globalConfigChanged &&
326
406
  !projectConfigChanged
327
407
  ) {
328
408
  return cachedConfig;
@@ -343,6 +423,7 @@ export function clearConfigCache(): void {
343
423
  cachedProjectRoot = null;
344
424
  cachedConfigMtime = null;
345
425
  cachedDefaultConfigMtime = null;
426
+ cachedGlobalConfigMtime = null;
346
427
  }
347
428
 
348
429
  /**