@sk8metal/michi-cli 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/scripts/__tests__/spec-impl-workflow.test.js +4 -2
  3. package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +1 -1
  4. package/dist/scripts/config/config-schema.d.ts +52 -0
  5. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  6. package/dist/scripts/config/config-schema.js +25 -0
  7. package/dist/scripts/config/config-schema.js.map +1 -1
  8. package/dist/scripts/config-global.d.ts +10 -0
  9. package/dist/scripts/config-global.d.ts.map +1 -0
  10. package/dist/scripts/config-global.js +111 -0
  11. package/dist/scripts/config-global.js.map +1 -0
  12. package/dist/scripts/confluence-sync.d.ts +22 -4
  13. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  14. package/dist/scripts/confluence-sync.js +22 -12
  15. package/dist/scripts/confluence-sync.js.map +1 -1
  16. package/dist/scripts/jira-sync.d.ts.map +1 -1
  17. package/dist/scripts/jira-sync.js +201 -167
  18. package/dist/scripts/jira-sync.js.map +1 -1
  19. package/dist/scripts/list-projects.js.map +1 -1
  20. package/dist/scripts/multi-project-estimate.js.map +1 -1
  21. package/dist/scripts/phase-runner.d.ts +1 -1
  22. package/dist/scripts/phase-runner.d.ts.map +1 -1
  23. package/dist/scripts/phase-runner.js +295 -522
  24. package/dist/scripts/phase-runner.js.map +1 -1
  25. package/dist/scripts/pr-automation.d.ts.map +1 -1
  26. package/dist/scripts/pr-automation.js +11 -3
  27. package/dist/scripts/pr-automation.js.map +1 -1
  28. package/dist/scripts/pre-flight-check.d.ts.map +1 -1
  29. package/dist/scripts/pre-flight-check.js +10 -6
  30. package/dist/scripts/pre-flight-check.js.map +1 -1
  31. package/dist/scripts/resource-dashboard.js.map +1 -1
  32. package/dist/scripts/spec-impl-workflow.d.ts.map +1 -1
  33. package/dist/scripts/spec-impl-workflow.js +23 -7
  34. package/dist/scripts/spec-impl-workflow.js.map +1 -1
  35. package/dist/scripts/template/renderer.d.ts +1 -1
  36. package/dist/scripts/template/renderer.d.ts.map +1 -1
  37. package/dist/scripts/test-interactive.d.ts.map +1 -1
  38. package/dist/scripts/test-interactive.js +0 -15
  39. package/dist/scripts/test-interactive.js.map +1 -1
  40. package/dist/scripts/test-new-features.js +6 -3
  41. package/dist/scripts/test-new-features.js.map +1 -1
  42. package/dist/scripts/test-spec-generator.d.ts.map +1 -1
  43. package/dist/scripts/test-spec-generator.js +1 -2
  44. package/dist/scripts/test-spec-generator.js.map +1 -1
  45. package/dist/scripts/utils/__tests__/config-loader.test.js +114 -1
  46. package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
  47. package/dist/scripts/utils/__tests__/config-validator.test.js +2 -0
  48. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  49. package/dist/scripts/utils/__tests__/env-config.test.js +0 -2
  50. package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -1
  51. package/dist/scripts/utils/__tests__/project-meta.test.d.ts +6 -0
  52. package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +1 -0
  53. package/dist/scripts/utils/__tests__/project-meta.test.js +154 -0
  54. package/dist/scripts/utils/__tests__/project-meta.test.js.map +1 -0
  55. package/dist/scripts/utils/__tests__/security-validator.test.d.ts +6 -0
  56. package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +1 -0
  57. package/dist/scripts/utils/__tests__/security-validator.test.js +219 -0
  58. package/dist/scripts/utils/__tests__/security-validator.test.js.map +1 -0
  59. package/dist/scripts/utils/config-loader.d.ts +14 -3
  60. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  61. package/dist/scripts/utils/config-loader.js +284 -46
  62. package/dist/scripts/utils/config-loader.js.map +1 -1
  63. package/dist/scripts/utils/config-sections.d.ts +54 -0
  64. package/dist/scripts/utils/config-sections.d.ts.map +1 -0
  65. package/dist/scripts/utils/config-sections.js +178 -0
  66. package/dist/scripts/utils/config-sections.js.map +1 -0
  67. package/dist/scripts/utils/config-validator.d.ts +4 -0
  68. package/dist/scripts/utils/config-validator.d.ts.map +1 -1
  69. package/dist/scripts/utils/config-validator.js +57 -1
  70. package/dist/scripts/utils/config-validator.js.map +1 -1
  71. package/dist/scripts/utils/confluence-approval.d.ts.map +1 -1
  72. package/dist/scripts/utils/confluence-approval.js +5 -3
  73. package/dist/scripts/utils/confluence-approval.js.map +1 -1
  74. package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
  75. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  76. package/dist/scripts/utils/env-config.d.ts +1 -1
  77. package/dist/scripts/utils/env-config.d.ts.map +1 -1
  78. package/dist/scripts/utils/env-config.js +2 -14
  79. package/dist/scripts/utils/env-config.js.map +1 -1
  80. package/dist/scripts/utils/interactive-helpers.d.ts +32 -0
  81. package/dist/scripts/utils/interactive-helpers.d.ts.map +1 -0
  82. package/dist/scripts/utils/interactive-helpers.js +92 -0
  83. package/dist/scripts/utils/interactive-helpers.js.map +1 -0
  84. package/dist/scripts/utils/jira-issue-type-fetcher.d.ts.map +1 -1
  85. package/dist/scripts/utils/jira-issue-type-fetcher.js +27 -18
  86. package/dist/scripts/utils/jira-issue-type-fetcher.js.map +1 -1
  87. package/dist/scripts/utils/project-meta.d.ts +9 -0
  88. package/dist/scripts/utils/project-meta.d.ts.map +1 -1
  89. package/dist/scripts/utils/project-meta.js +22 -0
  90. package/dist/scripts/utils/project-meta.js.map +1 -1
  91. package/dist/scripts/utils/release-notes-generator.d.ts.map +1 -1
  92. package/dist/scripts/utils/release-notes-generator.js +2 -1
  93. package/dist/scripts/utils/release-notes-generator.js.map +1 -1
  94. package/dist/scripts/utils/security-validator.d.ts +55 -0
  95. package/dist/scripts/utils/security-validator.d.ts.map +1 -0
  96. package/dist/scripts/utils/security-validator.js +232 -0
  97. package/dist/scripts/utils/security-validator.js.map +1 -0
  98. package/dist/scripts/utils/spec-updater.d.ts +19 -0
  99. package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
  100. package/dist/scripts/utils/spec-updater.js.map +1 -1
  101. package/dist/scripts/utils/tasks-converter.d.ts.map +1 -1
  102. package/dist/scripts/utils/tasks-converter.js +2 -2
  103. package/dist/scripts/utils/tasks-converter.js.map +1 -1
  104. package/dist/scripts/utils/tasks-format-validator.d.ts.map +1 -1
  105. package/dist/scripts/utils/tasks-format-validator.js +0 -12
  106. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  107. package/dist/scripts/utils/test-runner.d.ts.map +1 -1
  108. package/dist/scripts/utils/test-runner.js +3 -2
  109. package/dist/scripts/utils/test-runner.js.map +1 -1
  110. package/dist/scripts/validate-phase.d.ts +1 -1
  111. package/dist/scripts/validate-phase.d.ts.map +1 -1
  112. package/dist/scripts/validate-phase.js +12 -62
  113. package/dist/scripts/validate-phase.js.map +1 -1
  114. package/dist/scripts/workflow-orchestrator.d.ts.map +1 -1
  115. package/dist/scripts/workflow-orchestrator.js +11 -16
  116. package/dist/scripts/workflow-orchestrator.js.map +1 -1
  117. package/dist/src/__tests__/integration/setup/init.test.d.ts +5 -0
  118. package/dist/src/__tests__/integration/setup/init.test.d.ts.map +1 -0
  119. package/dist/src/__tests__/integration/setup/init.test.js +352 -0
  120. package/dist/src/__tests__/integration/setup/init.test.js.map +1 -0
  121. package/dist/src/cli.d.ts.map +1 -1
  122. package/dist/src/cli.js +67 -21
  123. package/dist/src/cli.js.map +1 -1
  124. package/dist/src/commands/__tests__/init.test.d.ts +5 -0
  125. package/dist/src/commands/__tests__/init.test.d.ts.map +1 -0
  126. package/dist/src/commands/__tests__/init.test.js +255 -0
  127. package/dist/src/commands/__tests__/init.test.js.map +1 -0
  128. package/dist/src/commands/__tests__/migrate.test.d.ts +5 -0
  129. package/dist/src/commands/__tests__/migrate.test.d.ts.map +1 -0
  130. package/dist/src/commands/__tests__/migrate.test.js +216 -0
  131. package/dist/src/commands/__tests__/migrate.test.js.map +1 -0
  132. package/dist/src/commands/config-validate.d.ts +9 -0
  133. package/dist/src/commands/config-validate.d.ts.map +1 -0
  134. package/dist/src/commands/config-validate.js +90 -0
  135. package/dist/src/commands/config-validate.js.map +1 -0
  136. package/dist/src/commands/init.d.ts +29 -0
  137. package/dist/src/commands/init.d.ts.map +1 -0
  138. package/dist/src/commands/init.js +513 -0
  139. package/dist/src/commands/init.js.map +1 -0
  140. package/dist/src/commands/migrate.d.ts +25 -0
  141. package/dist/src/commands/migrate.d.ts.map +1 -0
  142. package/dist/src/commands/migrate.js +341 -0
  143. package/dist/src/commands/migrate.js.map +1 -0
  144. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  145. package/dist/src/commands/setup-existing.js +0 -1
  146. package/dist/src/commands/setup-existing.js.map +1 -1
  147. package/dist/vitest.config.d.ts.map +1 -1
  148. package/dist/vitest.config.js +32 -8
  149. package/dist/vitest.config.js.map +1 -1
  150. package/docs/michi-development/design/config-unification.md +4789 -0
  151. package/docs/user-guide/getting-started/github-token-setup.md +2 -1
  152. package/docs/user-guide/getting-started/new-repository-setup.md +1 -1
  153. package/docs/user-guide/getting-started/quick-start.md +1 -1
  154. package/docs/user-guide/getting-started/setup.md +35 -14
  155. package/docs/user-guide/guides/customization.md +64 -11
  156. package/docs/user-guide/guides/workflow.md +35 -21
  157. package/docs/user-guide/hands-on/claude-agent-setup.md +2 -2
  158. package/docs/user-guide/hands-on/claude-setup.md +2 -2
  159. package/docs/user-guide/hands-on/cursor-setup.md +2 -2
  160. package/docs/user-guide/hands-on/workflow-walkthrough.md +4 -1
  161. package/docs/user-guide/reference/config.md +30 -5
  162. package/docs/user-guide/reference/quick-reference.md +68 -74
  163. package/docs/user-guide/testing/test-planning-flow.md +4 -0
  164. package/env.example +1 -1
  165. package/package.json +3 -5
  166. package/scripts/__tests__/spec-impl-workflow.test.ts +5 -2
  167. package/scripts/config/config-schema.ts +40 -0
  168. package/scripts/config-global.ts +160 -0
  169. package/scripts/confluence-sync.ts +91 -27
  170. package/scripts/jira-sync.ts +284 -218
  171. package/scripts/list-projects.ts +2 -2
  172. package/scripts/multi-project-estimate.ts +3 -3
  173. package/scripts/phase-runner.ts +391 -594
  174. package/scripts/pr-automation.ts +15 -5
  175. package/scripts/pre-flight-check.ts +20 -9
  176. package/scripts/pre-publish-check.sh +3 -34
  177. package/scripts/resource-dashboard.ts +4 -4
  178. package/scripts/spec-impl-workflow.ts +23 -7
  179. package/scripts/template/renderer.ts +1 -1
  180. package/scripts/test-interactive.ts +0 -19
  181. package/scripts/test-new-features.ts +10 -7
  182. package/scripts/test-npm-package.sh +3 -34
  183. package/scripts/test-spec-generator.ts +3 -7
  184. package/scripts/utils/__tests__/config-loader.test.ts +149 -0
  185. package/scripts/utils/__tests__/config-validator.test.ts +2 -0
  186. package/scripts/utils/__tests__/env-config.test.ts +0 -2
  187. package/scripts/utils/__tests__/project-meta.test.ts +192 -0
  188. package/scripts/utils/__tests__/security-validator.test.ts +272 -0
  189. package/scripts/utils/config-loader.ts +328 -68
  190. package/scripts/utils/config-sections.ts +316 -0
  191. package/scripts/utils/config-validator.ts +66 -1
  192. package/scripts/utils/confluence-approval.ts +8 -6
  193. package/scripts/utils/confluence-hierarchy.ts +27 -27
  194. package/scripts/utils/env-config.ts +2 -14
  195. package/scripts/utils/interactive-helpers.ts +135 -0
  196. package/scripts/utils/jira-issue-type-fetcher.ts +29 -21
  197. package/scripts/utils/project-meta.ts +27 -0
  198. package/scripts/utils/release-notes-generator.ts +3 -2
  199. package/scripts/utils/security-validator.ts +286 -0
  200. package/scripts/utils/spec-updater.ts +37 -15
  201. package/scripts/utils/tasks-converter.ts +4 -6
  202. package/scripts/utils/tasks-format-validator.ts +0 -13
  203. package/scripts/utils/test-runner.ts +4 -3
  204. package/scripts/validate-phase.ts +21 -80
  205. package/scripts/workflow-orchestrator.ts +16 -25
  206. package/templates/claude/commands/kiro/kiro-spec-impl.md +5 -1
  207. package/templates/claude/commands/kiro/kiro-spec-tasks.md +3 -1
  208. package/templates/claude/commands/michi/confluence-sync.md +8 -2
  209. package/templates/claude/commands/michi/design-review.md +4 -0
  210. package/templates/claude/commands/michi/e2e-plan.md +4 -0
  211. package/templates/claude/commands/michi/license-check.md +4 -0
  212. package/templates/claude/commands/michi/pr-resolve.md +4 -0
  213. package/templates/claude/commands/michi/project-switch.md +8 -2
  214. package/templates/claude/commands/michi/spec-design.md +78 -0
  215. package/templates/claude/commands/michi/spec-impl.md +716 -0
  216. package/templates/claude/commands/michi/test-planning.md +174 -0
  217. package/templates/claude/commands/michi/validate-design.md +58 -0
  218. package/templates/claude/commands/michi/version-audit.md +4 -0
  219. package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +1 -1
  220. package/templates/cursor/commands/kiro/kiro-spec-impl.md +1 -1
  221. package/templates/michi/cc-sdd-overrides/README.md +8 -0
  222. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +53 -0
  223. package/dist/scripts/config-interactive.d.ts +0 -10
  224. package/dist/scripts/config-interactive.d.ts.map +0 -1
  225. package/dist/scripts/config-interactive.js +0 -372
  226. package/dist/scripts/config-interactive.js.map +0 -1
  227. package/dist/scripts/setup-existing-project.d.ts +0 -15
  228. package/dist/scripts/setup-existing-project.d.ts.map +0 -1
  229. package/dist/scripts/setup-existing-project.js +0 -455
  230. package/dist/scripts/setup-existing-project.js.map +0 -1
  231. package/dist/scripts/setup-interactive.d.ts +0 -10
  232. package/dist/scripts/setup-interactive.d.ts.map +0 -1
  233. package/dist/scripts/setup-interactive.js +0 -413
  234. package/dist/scripts/setup-interactive.js.map +0 -1
  235. package/scripts/config-interactive.ts +0 -550
  236. package/scripts/setup-existing-project.ts +0 -585
  237. package/scripts/setup-interactive.ts +0 -565
@@ -6,27 +6,53 @@
6
6
  import { readFileSync, existsSync, statSync } from 'fs';
7
7
  import { resolve, relative, isAbsolute } from 'path';
8
8
  import { fileURLToPath } from 'url';
9
- import { config } from 'dotenv';
9
+ import { homedir } from 'os';
10
+ import { config, parse as dotenvParse } 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
+
30
+ /**
31
+ * グローバル.envファイルのパスを取得
32
+ */
33
+ export function getGlobalEnvPath(): string {
34
+ const home = process.env.HOME || homedir();
35
+ return resolve(home, GLOBAL_CONFIG_DIR, '.env');
36
+ }
37
+
15
38
  /**
16
39
  * 深いマージ(Deep Merge)
17
40
  * オブジェクトを再帰的にマージする
18
41
  */
19
- function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
42
+ function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
20
43
  const result = { ...target };
21
-
44
+
22
45
  for (const key in source) {
23
46
  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>]>);
47
+ result[key] = deepMerge(
48
+ (result[key] || {}) as Record<string, unknown>,
49
+ source[key] as Record<string, unknown>
50
+ ) as T[Extract<keyof T, string>];
25
51
  } else if (source[key] !== undefined) {
26
52
  result[key] = source[key] as T[Extract<keyof T, string>];
27
53
  }
28
54
  }
29
-
55
+
30
56
  return result;
31
57
  }
32
58
 
@@ -60,7 +86,7 @@ function expandEnvVars(str: string): string {
60
86
  * 設定オブジェクト内の文字列値を環境変数で展開
61
87
  * 循環参照を防ぐため、処理済みオブジェクトを追跡
62
88
  */
63
- function expandEnvVarsInConfig(config: any, visited: WeakSet<object> = new WeakSet()): any {
89
+ function expandEnvVarsInConfig(config: unknown, visited: WeakSet<object> = new WeakSet()): unknown {
64
90
  if (typeof config === 'string') {
65
91
  return expandEnvVars(config);
66
92
  }
@@ -77,9 +103,9 @@ function expandEnvVarsInConfig(config: any, visited: WeakSet<object> = new WeakS
77
103
  }
78
104
 
79
105
  visited.add(config);
80
- const result: any = {};
81
- for (const key in config) {
82
- result[key] = expandEnvVarsInConfig(config[key], visited);
106
+ const result: Record<string, unknown> = {};
107
+ for (const key in config as Record<string, unknown>) {
108
+ result[key] = expandEnvVarsInConfig((config as Record<string, unknown>)[key], visited);
83
109
  }
84
110
  visited.delete(config);
85
111
  return result;
@@ -113,7 +139,8 @@ function loadDefaultConfig(): AppConfig {
113
139
  return AppConfigSchema.parse(expanded);
114
140
  } catch (error) {
115
141
  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}`);
142
+ // SyntaxErrorには標準的なlinecolumnプロパティはないため、messageのみ使用
143
+ throw new Error(`Invalid JSON in default config file ${defaultConfigPath}: ${error.message}`);
117
144
  }
118
145
  if (error instanceof Error && error.name === 'ZodError') {
119
146
  throw new Error(`Default config validation failed: ${error.message}\nFile: ${defaultConfigPath}`);
@@ -166,23 +193,23 @@ function resolveConfigPath(projectRoot: string): string {
166
193
  */
167
194
  function loadProjectConfig(projectRoot: string = process.cwd()): Partial<AppConfig> | null {
168
195
  const projectConfigPath = resolveConfigPath(projectRoot);
169
-
196
+
170
197
  // パストラバーサル対策: パスを検証
171
198
  if (!validateConfigPath(projectConfigPath, projectRoot)) {
172
199
  throw new Error(`Invalid config path: ${projectConfigPath} is outside project root`);
173
200
  }
174
-
201
+
175
202
  if (!existsSync(projectConfigPath)) {
176
203
  return null;
177
204
  }
178
-
205
+
179
206
  try {
180
207
  const content = readFileSync(projectConfigPath, 'utf-8');
181
208
  const parsed = JSON.parse(content);
182
-
209
+
183
210
  // 環境変数を展開
184
211
  const expanded = expandEnvVarsInConfig(parsed);
185
-
212
+
186
213
  // 部分的な設定なので、スキーマで厳密にバリデーションしない
187
214
  // ただし、存在するキーについては型チェック
188
215
  return expanded as Partial<AppConfig>;
@@ -198,62 +225,199 @@ function loadProjectConfig(projectRoot: string = process.cwd()): Partial<AppConf
198
225
  }
199
226
 
200
227
  /**
201
- * 設定を読み込んでマージ
202
- *
203
- * マージ順序:
204
- * 1. デフォルト設定
205
- * 2. プロジェクト固有設定(上書き)
206
- * 3. 環境変数(最終上書き、既存の動作を維持)
228
+ * グローバル設定を読み込む
207
229
  */
208
- export function loadConfig(projectRoot: string = process.cwd()): AppConfig {
209
- // デフォルト設定を読み込み
210
- const defaultConfig = loadDefaultConfig();
211
-
212
- // プロジェクト固有設定を読み込み
213
- const projectConfig = loadProjectConfig(projectRoot);
214
-
215
- // マージ(プロジェクト設定がデフォルトを上書き)
216
- const mergedConfig: AppConfig = projectConfig
217
- ? deepMerge(defaultConfig, projectConfig)
218
- : defaultConfig;
219
-
220
- // 環境変数で最終上書き(条件付き)
221
- // 注意: config.jsonにspaces設定がある場合は環境変数を無視(config.jsonを優先)
222
- if (process.env.CONFLUENCE_PRD_SPACE) {
223
- if (!mergedConfig.confluence) {
224
- mergedConfig.confluence = {
225
- pageCreationGranularity: 'single',
226
- pageTitleFormat: '[{projectName}] {featureName} {docTypeLabel}',
227
- autoLabels: ['{projectLabel}', '{docType}', '{featureName}', 'github-sync']
228
- };
229
- }
230
- // spacesオブジェクトを確実に作成
231
- if (!mergedConfig.confluence.spaces) {
232
- mergedConfig.confluence.spaces = {};
233
- }
234
- // 各フィールドを個別にチェックし、未定義のフィールドのみ環境変数で設定
235
- // 既に定義されている値は変更しない
236
- if (!mergedConfig.confluence.spaces.requirements) {
237
- mergedConfig.confluence.spaces.requirements = process.env.CONFLUENCE_PRD_SPACE;
238
- }
239
- if (!mergedConfig.confluence.spaces.design) {
240
- mergedConfig.confluence.spaces.design = process.env.CONFLUENCE_PRD_SPACE;
230
+ function loadGlobalConfig(): Partial<AppConfig> | null {
231
+ const globalConfigPath = getGlobalConfigPath();
232
+
233
+ if (!existsSync(globalConfigPath)) {
234
+ return null;
235
+ }
236
+
237
+ try {
238
+ const content = readFileSync(globalConfigPath, 'utf-8');
239
+ const parsed = JSON.parse(content);
240
+
241
+ // 環境変数を展開
242
+ const expanded = expandEnvVarsInConfig(parsed);
243
+
244
+ return expanded as Partial<AppConfig>;
245
+ } catch (error) {
246
+ if (error instanceof SyntaxError) {
247
+ console.warn(`⚠️ Invalid JSON in global config ${globalConfigPath}: ${error.message}`);
248
+ } else {
249
+ console.warn(`⚠️ Failed to load global config: ${error instanceof Error ? error.message : 'Unknown error'}`);
241
250
  }
242
- if (!mergedConfig.confluence.spaces.tasks) {
243
- mergedConfig.confluence.spaces.tasks = process.env.CONFLUENCE_PRD_SPACE;
251
+ return null;
252
+ }
253
+ }
254
+
255
+ /**
256
+ * グローバル.envを読み込み
257
+ * ~/.michi/.env から組織共通の環境変数を読み込む
258
+ */
259
+ function loadGlobalEnv(): Record<string, string> {
260
+ const globalEnvPath = getGlobalEnvPath();
261
+
262
+ if (!existsSync(globalEnvPath)) {
263
+ return {};
264
+ }
265
+
266
+ try {
267
+ const content = readFileSync(globalEnvPath, 'utf-8');
268
+ return dotenvParse(content);
269
+ } catch (error) {
270
+ console.warn(`⚠️ Failed to load global env from ${globalEnvPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
271
+ return {};
272
+ }
273
+ }
274
+
275
+ /**
276
+ * プロジェクト.envを読み込み
277
+ * .env からプロジェクト固有の環境変数を読み込む
278
+ */
279
+ function loadProjectEnv(projectRoot: string): Record<string, string> {
280
+ const projectEnvPath = resolve(projectRoot, '.env');
281
+
282
+ if (!existsSync(projectEnvPath)) {
283
+ return {};
284
+ }
285
+
286
+ try {
287
+ const content = readFileSync(projectEnvPath, 'utf-8');
288
+ return dotenvParse(content);
289
+ } catch (error) {
290
+ console.warn(`⚠️ Failed to load project env from ${projectEnvPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
291
+ return {};
292
+ }
293
+ }
294
+
295
+ /**
296
+ * プロジェクトメタデータを読み込み
297
+ * .kiro/project.json からプロジェクト情報を読み込む
298
+ */
299
+ function loadProjectMetadata(projectRoot: string): Partial<AppConfig> | null {
300
+ const projectJsonPath = resolve(projectRoot, '.kiro/project.json');
301
+
302
+ if (!existsSync(projectJsonPath)) {
303
+ return null;
304
+ }
305
+
306
+ try {
307
+ const content = readFileSync(projectJsonPath, 'utf-8');
308
+ const meta = JSON.parse(content);
309
+
310
+ // project フィールドとして返す
311
+ return { project: meta } as Partial<AppConfig>;
312
+ } catch (error) {
313
+ if (error instanceof SyntaxError) {
314
+ console.warn(`⚠️ Invalid JSON in ${projectJsonPath}: ${error.message}`);
315
+ } else {
316
+ console.warn(`⚠️ Failed to load project metadata from ${projectJsonPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
244
317
  }
318
+ return null;
245
319
  }
246
-
247
- // JIRA issue type IDを環境変数から取得(インスタンス固有の値のため)
248
- if (mergedConfig.jira && mergedConfig.jira.issueTypes) {
249
- if (process.env.JIRA_ISSUE_TYPE_STORY) {
250
- mergedConfig.jira.issueTypes.story = process.env.JIRA_ISSUE_TYPE_STORY;
320
+ }
321
+
322
+ /**
323
+ * 環境変数から設定へのマッピング
324
+ */
325
+ const ENV_TO_CONFIG_MAPPING: Record<string, string> = {
326
+ 'ATLASSIAN_URL': 'atlassian.url',
327
+ 'ATLASSIAN_EMAIL': 'atlassian.email',
328
+ 'ATLASSIAN_API_TOKEN': 'atlassian.apiToken',
329
+ 'GITHUB_ORG': 'github.org',
330
+ 'GITHUB_TOKEN': 'github.token',
331
+ 'CONFLUENCE_PRD_SPACE': 'confluence.spaces.requirements',
332
+ 'CONFLUENCE_QA_SPACE': 'confluence.spaces.qa',
333
+ 'CONFLUENCE_RELEASE_SPACE': 'confluence.spaces.release',
334
+ 'JIRA_ISSUE_TYPE_STORY': 'jira.issueTypes.story',
335
+ 'JIRA_ISSUE_TYPE_SUBTASK': 'jira.issueTypes.subtask',
336
+ 'JIRA_PROJECT_KEYS': 'jira.projectKeys',
337
+ };
338
+
339
+ /**
340
+ * ドットパスでオブジェクトの値を設定
341
+ * 例: setValueByPath(obj, 'a.b.c', value) => obj.a.b.c = value
342
+ */
343
+ function setValueByPath(obj: Record<string, unknown>, path: string, value: string): void {
344
+ const keys = path.split('.');
345
+ let current: Record<string, unknown> = obj;
346
+
347
+ for (let i = 0; i < keys.length - 1; i++) {
348
+ const key = keys[i];
349
+ if (!current[key] || typeof current[key] !== 'object') {
350
+ current[key] = {};
251
351
  }
252
- if (process.env.JIRA_ISSUE_TYPE_SUBTASK) {
253
- mergedConfig.jira.issueTypes.subtask = process.env.JIRA_ISSUE_TYPE_SUBTASK;
352
+ current = current[key] as Record<string, unknown>;
353
+ }
354
+
355
+ current[keys[keys.length - 1]] = value;
356
+ }
357
+
358
+ /**
359
+ * 環境変数を設定オブジェクトに適用
360
+ */
361
+ function applyEnvVarsToConfig(
362
+ config: AppConfig,
363
+ envVars: Record<string, string>
364
+ ): AppConfig {
365
+ const result = { ...config } as Record<string, unknown>;
366
+
367
+ for (const [envKey, configPath] of Object.entries(ENV_TO_CONFIG_MAPPING)) {
368
+ if (envVars[envKey]) {
369
+ setValueByPath(result, configPath, envVars[envKey]);
254
370
  }
255
371
  }
256
-
372
+
373
+ return result as AppConfig;
374
+ }
375
+
376
+ /**
377
+ * 設定を読み込んでマージ
378
+ *
379
+ * マージ順序(優先度: 低 → 高):
380
+ * 1. デフォルト設定
381
+ * 2. グローバル.env(~/.michi/.env)
382
+ * 3. グローバル設定(~/.michi/config.json)
383
+ * 4. プロジェクトメタデータ(.kiro/project.json)
384
+ * 5. プロジェクト設定(.michi/config.json)
385
+ * 6. プロジェクト.env(.env)- 最高優先度
386
+ */
387
+ export function loadConfig(projectRoot: string = process.cwd()): AppConfig {
388
+ // 1. デフォルト設定を読み込み(最低優先度)
389
+ let mergedConfig: AppConfig = loadDefaultConfig();
390
+
391
+ // 2. グローバル.envを読み込み
392
+ const globalEnvVars = loadGlobalEnv();
393
+ if (Object.keys(globalEnvVars).length > 0) {
394
+ mergedConfig = applyEnvVarsToConfig(mergedConfig, globalEnvVars);
395
+ }
396
+
397
+ // 3. グローバル設定を読み込み
398
+ const globalConfig = loadGlobalConfig();
399
+ if (globalConfig) {
400
+ mergedConfig = deepMerge(mergedConfig, globalConfig);
401
+ }
402
+
403
+ // 4. プロジェクトメタデータを読み込み
404
+ const projectMeta = loadProjectMetadata(projectRoot);
405
+ if (projectMeta) {
406
+ mergedConfig = deepMerge(mergedConfig, projectMeta);
407
+ }
408
+
409
+ // 5. プロジェクト固有設定を読み込み
410
+ const projectConfig = loadProjectConfig(projectRoot);
411
+ if (projectConfig) {
412
+ mergedConfig = deepMerge(mergedConfig, projectConfig);
413
+ }
414
+
415
+ // 6. プロジェクト.envを読み込み(最高優先度)
416
+ const projectEnvVars = loadProjectEnv(projectRoot);
417
+ if (Object.keys(projectEnvVars).length > 0) {
418
+ mergedConfig = applyEnvVarsToConfig(mergedConfig, projectEnvVars);
419
+ }
420
+
257
421
  // スキーマで最終バリデーション
258
422
  return AppConfigSchema.parse(mergedConfig);
259
423
  }
@@ -266,14 +430,22 @@ let cachedConfig: AppConfig | null = null;
266
430
  let cachedProjectRoot: string | null = null;
267
431
  let cachedConfigMtime: number | null = null;
268
432
  let cachedDefaultConfigMtime: number | null = null;
433
+ let cachedGlobalConfigMtime: number | null = null;
434
+ let cachedGlobalEnvMtime: number | null = null;
435
+ let cachedProjectMetaMtime: number | null = null;
436
+ let cachedProjectEnvMtime: number | null = null;
269
437
 
270
438
  export function getConfig(projectRoot: string = process.cwd()): AppConfig {
271
439
  const projectConfigPath = resolveConfigPath(projectRoot);
440
+ const globalConfigPath = getGlobalConfigPath();
441
+ const globalEnvPath = getGlobalEnvPath();
442
+ const projectMetaPath = resolve(projectRoot, '.kiro/project.json');
443
+ const projectEnvPath = resolve(projectRoot, '.env');
272
444
  const currentFileUrl = import.meta.url;
273
445
  const currentFilePath = fileURLToPath(currentFileUrl);
274
446
  const currentDir = resolve(currentFilePath, '..');
275
447
  const defaultConfigPath = resolve(currentDir, '../config/default-config.json');
276
-
448
+
277
449
  // デフォルト設定ファイルの更新時刻をチェック
278
450
  let defaultConfigChanged = false;
279
451
  try {
@@ -295,7 +467,27 @@ export function getConfig(projectRoot: string = process.cwd()): AppConfig {
295
467
  defaultConfigChanged = true;
296
468
  cachedDefaultConfigMtime = null;
297
469
  }
298
-
470
+
471
+ // グローバル設定ファイルの更新時刻をチェック
472
+ let globalConfigChanged = false;
473
+ try {
474
+ if (existsSync(globalConfigPath)) {
475
+ const globalStats = statSync(globalConfigPath);
476
+ if (cachedGlobalConfigMtime !== globalStats.mtimeMs) {
477
+ globalConfigChanged = true;
478
+ cachedGlobalConfigMtime = globalStats.mtimeMs;
479
+ }
480
+ } else {
481
+ if (cachedGlobalConfigMtime !== null) {
482
+ globalConfigChanged = true;
483
+ cachedGlobalConfigMtime = null;
484
+ }
485
+ }
486
+ } catch {
487
+ globalConfigChanged = true;
488
+ cachedGlobalConfigMtime = null;
489
+ }
490
+
299
491
  // プロジェクト設定ファイルの更新時刻をチェック
300
492
  let projectConfigChanged = false;
301
493
  try {
@@ -317,13 +509,77 @@ export function getConfig(projectRoot: string = process.cwd()): AppConfig {
317
509
  projectConfigChanged = true;
318
510
  cachedConfigMtime = null;
319
511
  }
320
-
512
+
513
+ // グローバル.envファイルの更新時刻をチェック
514
+ let globalEnvChanged = false;
515
+ try {
516
+ if (existsSync(globalEnvPath)) {
517
+ const globalEnvStats = statSync(globalEnvPath);
518
+ if (cachedGlobalEnvMtime !== globalEnvStats.mtimeMs) {
519
+ globalEnvChanged = true;
520
+ cachedGlobalEnvMtime = globalEnvStats.mtimeMs;
521
+ }
522
+ } else {
523
+ if (cachedGlobalEnvMtime !== null) {
524
+ globalEnvChanged = true;
525
+ cachedGlobalEnvMtime = null;
526
+ }
527
+ }
528
+ } catch {
529
+ globalEnvChanged = true;
530
+ cachedGlobalEnvMtime = null;
531
+ }
532
+
533
+ // プロジェクトメタデータファイルの更新時刻をチェック
534
+ let projectMetaChanged = false;
535
+ try {
536
+ if (existsSync(projectMetaPath)) {
537
+ const projectMetaStats = statSync(projectMetaPath);
538
+ if (cachedProjectMetaMtime !== projectMetaStats.mtimeMs) {
539
+ projectMetaChanged = true;
540
+ cachedProjectMetaMtime = projectMetaStats.mtimeMs;
541
+ }
542
+ } else {
543
+ if (cachedProjectMetaMtime !== null) {
544
+ projectMetaChanged = true;
545
+ cachedProjectMetaMtime = null;
546
+ }
547
+ }
548
+ } catch {
549
+ projectMetaChanged = true;
550
+ cachedProjectMetaMtime = null;
551
+ }
552
+
553
+ // プロジェクト.envファイルの更新時刻をチェック
554
+ let projectEnvChanged = false;
555
+ try {
556
+ if (existsSync(projectEnvPath)) {
557
+ const projectEnvStats = statSync(projectEnvPath);
558
+ if (cachedProjectEnvMtime !== projectEnvStats.mtimeMs) {
559
+ projectEnvChanged = true;
560
+ cachedProjectEnvMtime = projectEnvStats.mtimeMs;
561
+ }
562
+ } else {
563
+ if (cachedProjectEnvMtime !== null) {
564
+ projectEnvChanged = true;
565
+ cachedProjectEnvMtime = null;
566
+ }
567
+ }
568
+ } catch {
569
+ projectEnvChanged = true;
570
+ cachedProjectEnvMtime = null;
571
+ }
572
+
321
573
  // キャッシュが有効で、設定ファイルが変更されていない場合はキャッシュを返す
322
574
  if (
323
575
  cachedConfig &&
324
576
  cachedProjectRoot === projectRoot &&
325
577
  !defaultConfigChanged &&
326
- !projectConfigChanged
578
+ !globalConfigChanged &&
579
+ !globalEnvChanged &&
580
+ !projectConfigChanged &&
581
+ !projectMetaChanged &&
582
+ !projectEnvChanged
327
583
  ) {
328
584
  return cachedConfig;
329
585
  }
@@ -343,6 +599,10 @@ export function clearConfigCache(): void {
343
599
  cachedProjectRoot = null;
344
600
  cachedConfigMtime = null;
345
601
  cachedDefaultConfigMtime = null;
602
+ cachedGlobalConfigMtime = null;
603
+ cachedGlobalEnvMtime = null;
604
+ cachedProjectMetaMtime = null;
605
+ cachedProjectEnvMtime = null;
346
606
  }
347
607
 
348
608
  /**