@sk8metal/michi-cli 0.4.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 (99) hide show
  1. package/CHANGELOG.md +40 -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/pr-automation.d.ts.map +1 -1
  9. package/dist/scripts/pr-automation.js +11 -3
  10. package/dist/scripts/pr-automation.js.map +1 -1
  11. package/dist/scripts/spec-impl-workflow.d.ts.map +1 -1
  12. package/dist/scripts/spec-impl-workflow.js +22 -6
  13. package/dist/scripts/spec-impl-workflow.js.map +1 -1
  14. package/dist/scripts/utils/__tests__/config-loader.test.js +114 -1
  15. package/dist/scripts/utils/__tests__/config-loader.test.js.map +1 -1
  16. package/dist/scripts/utils/__tests__/config-validator.test.js +2 -0
  17. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  18. package/dist/scripts/utils/__tests__/env-config.test.js +0 -2
  19. package/dist/scripts/utils/__tests__/env-config.test.js.map +1 -1
  20. package/dist/scripts/utils/__tests__/project-meta.test.d.ts +6 -0
  21. package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +1 -0
  22. package/dist/scripts/utils/__tests__/project-meta.test.js +154 -0
  23. package/dist/scripts/utils/__tests__/project-meta.test.js.map +1 -0
  24. package/dist/scripts/utils/__tests__/security-validator.test.d.ts +6 -0
  25. package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +1 -0
  26. package/dist/scripts/utils/__tests__/security-validator.test.js +219 -0
  27. package/dist/scripts/utils/__tests__/security-validator.test.js.map +1 -0
  28. package/dist/scripts/utils/config-loader.d.ts +10 -4
  29. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  30. package/dist/scripts/utils/config-loader.js +214 -47
  31. package/dist/scripts/utils/config-loader.js.map +1 -1
  32. package/dist/scripts/utils/env-config.d.ts +1 -1
  33. package/dist/scripts/utils/env-config.d.ts.map +1 -1
  34. package/dist/scripts/utils/env-config.js +2 -14
  35. package/dist/scripts/utils/env-config.js.map +1 -1
  36. package/dist/scripts/utils/project-meta.d.ts +9 -0
  37. package/dist/scripts/utils/project-meta.d.ts.map +1 -1
  38. package/dist/scripts/utils/project-meta.js +22 -0
  39. package/dist/scripts/utils/project-meta.js.map +1 -1
  40. package/dist/scripts/utils/security-validator.d.ts +55 -0
  41. package/dist/scripts/utils/security-validator.d.ts.map +1 -0
  42. package/dist/scripts/utils/security-validator.js +232 -0
  43. package/dist/scripts/utils/security-validator.js.map +1 -0
  44. package/dist/src/cli.d.ts.map +1 -1
  45. package/dist/src/cli.js +41 -3
  46. package/dist/src/cli.js.map +1 -1
  47. package/dist/src/commands/__tests__/init.test.d.ts +5 -0
  48. package/dist/src/commands/__tests__/init.test.d.ts.map +1 -0
  49. package/dist/src/commands/__tests__/init.test.js +255 -0
  50. package/dist/src/commands/__tests__/init.test.js.map +1 -0
  51. package/dist/src/commands/__tests__/migrate.test.d.ts +5 -0
  52. package/dist/src/commands/__tests__/migrate.test.d.ts.map +1 -0
  53. package/dist/src/commands/__tests__/migrate.test.js +216 -0
  54. package/dist/src/commands/__tests__/migrate.test.js.map +1 -0
  55. package/dist/src/commands/config-validate.d.ts +9 -0
  56. package/dist/src/commands/config-validate.d.ts.map +1 -0
  57. package/dist/src/commands/config-validate.js +90 -0
  58. package/dist/src/commands/config-validate.js.map +1 -0
  59. package/dist/src/commands/init.d.ts +1 -0
  60. package/dist/src/commands/init.d.ts.map +1 -1
  61. package/dist/src/commands/init.js +29 -6
  62. package/dist/src/commands/init.js.map +1 -1
  63. package/dist/src/commands/migrate.d.ts +25 -0
  64. package/dist/src/commands/migrate.d.ts.map +1 -0
  65. package/dist/src/commands/migrate.js +341 -0
  66. package/dist/src/commands/migrate.js.map +1 -0
  67. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  68. package/dist/src/commands/setup-existing.js +0 -1
  69. package/dist/src/commands/setup-existing.js.map +1 -1
  70. package/dist/vitest.config.d.ts.map +1 -1
  71. package/dist/vitest.config.js +32 -8
  72. package/dist/vitest.config.js.map +1 -1
  73. package/docs/michi-development/design/config-unification.md +4789 -0
  74. package/docs/user-guide/getting-started/github-token-setup.md +2 -1
  75. package/docs/user-guide/getting-started/new-repository-setup.md +1 -1
  76. package/docs/user-guide/getting-started/quick-start.md +1 -1
  77. package/docs/user-guide/getting-started/setup.md +4 -11
  78. package/docs/user-guide/hands-on/claude-agent-setup.md +2 -2
  79. package/docs/user-guide/hands-on/claude-setup.md +2 -2
  80. package/docs/user-guide/hands-on/cursor-setup.md +2 -2
  81. package/docs/user-guide/hands-on/workflow-walkthrough.md +4 -1
  82. package/env.example +1 -1
  83. package/package.json +2 -2
  84. package/scripts/__tests__/spec-impl-workflow.test.ts +5 -2
  85. package/scripts/config/config-schema.ts +40 -0
  86. package/scripts/pr-automation.ts +15 -5
  87. package/scripts/spec-impl-workflow.ts +22 -6
  88. package/scripts/utils/__tests__/config-loader.test.ts +149 -0
  89. package/scripts/utils/__tests__/config-validator.test.ts +2 -0
  90. package/scripts/utils/__tests__/env-config.test.ts +0 -2
  91. package/scripts/utils/__tests__/project-meta.test.ts +192 -0
  92. package/scripts/utils/__tests__/security-validator.test.ts +272 -0
  93. package/scripts/utils/config-loader.ts +232 -53
  94. package/scripts/utils/env-config.ts +2 -14
  95. package/scripts/utils/project-meta.ts +27 -0
  96. package/scripts/utils/security-validator.ts +286 -0
  97. package/templates/claude/commands/kiro/kiro-spec-impl.md +1 -1
  98. package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +1 -1
  99. package/templates/cursor/commands/kiro/kiro-spec-impl.md +1 -1
@@ -7,7 +7,7 @@ import { readFileSync, existsSync, statSync } from 'fs';
7
7
  import { resolve, relative, isAbsolute } from 'path';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { homedir } from 'os';
10
- import { config } from 'dotenv';
10
+ import { config, parse as dotenvParse } from 'dotenv';
11
11
  import { AppConfigSchema, type AppConfig } from '../config/config-schema.js';
12
12
 
13
13
  // 環境変数読み込み
@@ -27,6 +27,14 @@ export function getGlobalConfigPath(): string {
27
27
  return resolve(home, GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_FILE);
28
28
  }
29
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
+
30
38
  /**
31
39
  * 深いマージ(Deep Merge)
32
40
  * オブジェクトを再帰的にマージする
@@ -244,73 +252,172 @@ function loadGlobalConfig(): Partial<AppConfig> | null {
244
252
  }
245
253
  }
246
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'}`);
317
+ }
318
+ return null;
319
+ }
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] = {};
351
+ }
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]);
370
+ }
371
+ }
372
+
373
+ return result as AppConfig;
374
+ }
375
+
247
376
  /**
248
377
  * 設定を読み込んでマージ
249
378
  *
250
- * マージ順序:
379
+ * マージ順序(優先度: 低 → 高):
251
380
  * 1. デフォルト設定
252
- * 2. グローバル設定(上書き)
253
- * 3. プロジェクト固有設定(上書き)
254
- * 4. 環境変数(最終上書き、既存の動作を維持)
381
+ * 2. グローバル.env(~/.michi/.env)
382
+ * 3. グローバル設定(~/.michi/config.json)
383
+ * 4. プロジェクトメタデータ(.kiro/project.json)
384
+ * 5. プロジェクト設定(.michi/config.json)
385
+ * 6. プロジェクト.env(.env)- 最高優先度
255
386
  */
256
387
  export function loadConfig(projectRoot: string = process.cwd()): AppConfig {
257
- // デフォルト設定を読み込み
258
- const defaultConfig = loadDefaultConfig();
259
-
260
- // グローバル設定を読み込み
261
- const globalConfig = loadGlobalConfig();
388
+ // 1. デフォルト設定を読み込み(最低優先度)
389
+ let mergedConfig: AppConfig = loadDefaultConfig();
262
390
 
263
- // プロジェクト固有設定を読み込み
264
- const projectConfig = loadProjectConfig(projectRoot);
265
-
266
- // マージ(デフォルト グローバル → プロジェクト)
267
- let mergedConfig: AppConfig = defaultConfig;
391
+ // 2. グローバル.envを読み込み
392
+ const globalEnvVars = loadGlobalEnv();
393
+ if (Object.keys(globalEnvVars).length > 0) {
394
+ mergedConfig = applyEnvVarsToConfig(mergedConfig, globalEnvVars);
395
+ }
268
396
 
397
+ // 3. グローバル設定を読み込み
398
+ const globalConfig = loadGlobalConfig();
269
399
  if (globalConfig) {
270
400
  mergedConfig = deepMerge(mergedConfig, globalConfig);
271
401
  }
272
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);
273
411
  if (projectConfig) {
274
412
  mergedConfig = deepMerge(mergedConfig, projectConfig);
275
413
  }
276
-
277
- // 環境変数で最終上書き(条件付き)
278
- // 注意: config.jsonにspaces設定がある場合は環境変数を無視(config.jsonを優先)
279
- if (process.env.CONFLUENCE_PRD_SPACE) {
280
- if (!mergedConfig.confluence) {
281
- mergedConfig.confluence = {
282
- pageCreationGranularity: 'single',
283
- pageTitleFormat: '[{projectName}] {featureName} {docTypeLabel}',
284
- autoLabels: ['{projectLabel}', '{docType}', '{featureName}', 'github-sync']
285
- };
286
- }
287
- // spacesオブジェクトを確実に作成
288
- if (!mergedConfig.confluence.spaces) {
289
- mergedConfig.confluence.spaces = {};
290
- }
291
- // 各フィールドを個別にチェックし、未定義のフィールドのみ環境変数で設定
292
- // 既に定義されている値は変更しない
293
- if (!mergedConfig.confluence.spaces.requirements) {
294
- mergedConfig.confluence.spaces.requirements = process.env.CONFLUENCE_PRD_SPACE;
295
- }
296
- if (!mergedConfig.confluence.spaces.design) {
297
- mergedConfig.confluence.spaces.design = process.env.CONFLUENCE_PRD_SPACE;
298
- }
299
- if (!mergedConfig.confluence.spaces.tasks) {
300
- mergedConfig.confluence.spaces.tasks = process.env.CONFLUENCE_PRD_SPACE;
301
- }
302
- }
303
-
304
- // JIRA issue type IDを環境変数から取得(インスタンス固有の値のため)
305
- if (mergedConfig.jira && mergedConfig.jira.issueTypes) {
306
- if (process.env.JIRA_ISSUE_TYPE_STORY) {
307
- mergedConfig.jira.issueTypes.story = process.env.JIRA_ISSUE_TYPE_STORY;
308
- }
309
- if (process.env.JIRA_ISSUE_TYPE_SUBTASK) {
310
- mergedConfig.jira.issueTypes.subtask = process.env.JIRA_ISSUE_TYPE_SUBTASK;
311
- }
414
+
415
+ // 6. プロジェクト.envを読み込み(最高優先度)
416
+ const projectEnvVars = loadProjectEnv(projectRoot);
417
+ if (Object.keys(projectEnvVars).length > 0) {
418
+ mergedConfig = applyEnvVarsToConfig(mergedConfig, projectEnvVars);
312
419
  }
313
-
420
+
314
421
  // スキーマで最終バリデーション
315
422
  return AppConfigSchema.parse(mergedConfig);
316
423
  }
@@ -324,10 +431,16 @@ let cachedProjectRoot: string | null = null;
324
431
  let cachedConfigMtime: number | null = null;
325
432
  let cachedDefaultConfigMtime: number | null = null;
326
433
  let cachedGlobalConfigMtime: number | null = null;
434
+ let cachedGlobalEnvMtime: number | null = null;
435
+ let cachedProjectMetaMtime: number | null = null;
436
+ let cachedProjectEnvMtime: number | null = null;
327
437
 
328
438
  export function getConfig(projectRoot: string = process.cwd()): AppConfig {
329
439
  const projectConfigPath = resolveConfigPath(projectRoot);
330
440
  const globalConfigPath = getGlobalConfigPath();
441
+ const globalEnvPath = getGlobalEnvPath();
442
+ const projectMetaPath = resolve(projectRoot, '.kiro/project.json');
443
+ const projectEnvPath = resolve(projectRoot, '.env');
331
444
  const currentFileUrl = import.meta.url;
332
445
  const currentFilePath = fileURLToPath(currentFileUrl);
333
446
  const currentDir = resolve(currentFilePath, '..');
@@ -397,13 +510,76 @@ export function getConfig(projectRoot: string = process.cwd()): AppConfig {
397
510
  cachedConfigMtime = null;
398
511
  }
399
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
+
400
573
  // キャッシュが有効で、設定ファイルが変更されていない場合はキャッシュを返す
401
574
  if (
402
575
  cachedConfig &&
403
576
  cachedProjectRoot === projectRoot &&
404
577
  !defaultConfigChanged &&
405
578
  !globalConfigChanged &&
406
- !projectConfigChanged
579
+ !globalEnvChanged &&
580
+ !projectConfigChanged &&
581
+ !projectMetaChanged &&
582
+ !projectEnvChanged
407
583
  ) {
408
584
  return cachedConfig;
409
585
  }
@@ -424,6 +600,9 @@ export function clearConfigCache(): void {
424
600
  cachedConfigMtime = null;
425
601
  cachedDefaultConfigMtime = null;
426
602
  cachedGlobalConfigMtime = null;
603
+ cachedGlobalEnvMtime = null;
604
+ cachedProjectMetaMtime = null;
605
+ cachedProjectEnvMtime = null;
427
606
  }
428
607
 
429
608
  /**
@@ -61,11 +61,6 @@ const ENV_CONFIG: EnvValue[] = [
61
61
  required: false,
62
62
  sensitive: true,
63
63
  },
64
- {
65
- key: 'GITHUB_REPO',
66
- description: 'GitHubリポジトリ (例: owner/repo)',
67
- required: false,
68
- },
69
64
  {
70
65
  key: 'CONFLUENCE_PRD_SPACE',
71
66
  description: 'Confluence PRDスペースキー',
@@ -435,7 +430,7 @@ export function generateEnvContent(values: Map<string, string>): string {
435
430
 
436
431
  // GitHub設定
437
432
  content += '\n# GitHub設定\n';
438
- const githubKeys = ['GITHUB_ORG', 'GITHUB_TOKEN', 'GITHUB_REPO'];
433
+ const githubKeys = ['GITHUB_ORG', 'GITHUB_TOKEN'];
439
434
  for (const key of githubKeys) {
440
435
  const value = values.get(key) || '';
441
436
  content += `${key}=${value}\n`;
@@ -477,7 +472,7 @@ export function generateEnvContent(values: Map<string, string>): string {
477
472
  export async function configureEnvInteractive(
478
473
  existingValues?: Map<string, string>,
479
474
  jiraKey?: string,
480
- repoUrl?: string,
475
+ _repoUrl?: string,
481
476
  ): Promise<Map<string, string>> {
482
477
  const rl = readline.createInterface({
483
478
  input: process.stdin,
@@ -500,13 +495,6 @@ export async function configureEnvInteractive(
500
495
  if (config.key === 'JIRA_PROJECT_KEYS' && jiraKey && !existingValue) {
501
496
  existingValue = jiraKey;
502
497
  }
503
- if (config.key === 'GITHUB_REPO' && repoUrl && !existingValue) {
504
- // URLからリポジトリ名を抽出 (例: https://github.com/owner/repo -> owner/repo)
505
- const match = repoUrl.match(/github\.com[/:]([\w-]+\/[\w-]+)/);
506
- if (match) {
507
- existingValue = match[1];
508
- }
509
- }
510
498
 
511
499
  // JIRA Issue Type ID設定時は、プロジェクトキーを渡す
512
500
  const projectKey = jiraKey || existingValues?.get('JIRA_PROJECT_KEYS');
@@ -67,3 +67,30 @@ Team: ${meta.team.join(', ')}
67
67
  `.trim();
68
68
  }
69
69
 
70
+ /**
71
+ * GitHub リポジトリ情報を取得
72
+ * repository フィールドから owner/repo 形式を抽出
73
+ *
74
+ * @param projectRoot プロジェクトルートディレクトリ(デフォルト: カレントディレクトリ)
75
+ * @returns owner/repo 形式のリポジトリ情報
76
+ * @throws リポジトリ情報が見つからない、または無効な形式の場合
77
+ */
78
+ export function getRepositoryInfo(projectRoot: string = process.cwd()): string {
79
+ const meta = loadProjectMeta(projectRoot);
80
+
81
+ if (!meta.repository) {
82
+ throw new Error('Repository information not found in project.json');
83
+ }
84
+
85
+ // URL形式から owner/repo を抽出
86
+ // 例: https://github.com/owner/repo.git -> owner/repo
87
+ // 例: git@github.com:owner/repo.git -> owner/repo
88
+ const match = meta.repository.match(/github\.com[:/]([\w-]+\/[\w-]+)(\.git)?/);
89
+
90
+ if (!match) {
91
+ throw new Error(`Invalid GitHub repository format: ${meta.repository}`);
92
+ }
93
+
94
+ return match[1];
95
+ }
96
+