@sk8metal/michi-cli 0.0.1 → 0.0.2

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 (47) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +24 -24
  3. package/dist/scripts/__tests__/validate-phase.test.d.ts +5 -0
  4. package/dist/scripts/__tests__/validate-phase.test.d.ts.map +1 -0
  5. package/dist/scripts/__tests__/validate-phase.test.js +162 -0
  6. package/dist/scripts/__tests__/validate-phase.test.js.map +1 -0
  7. package/dist/scripts/utils/__tests__/config-validator.test.d.ts +5 -0
  8. package/dist/scripts/utils/__tests__/config-validator.test.d.ts.map +1 -0
  9. package/dist/scripts/utils/__tests__/config-validator.test.js +247 -0
  10. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -0
  11. package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts +5 -0
  12. package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts.map +1 -0
  13. package/dist/scripts/utils/__tests__/feature-name-validator.test.js +106 -0
  14. package/dist/scripts/utils/__tests__/feature-name-validator.test.js.map +1 -0
  15. package/dist/src/__tests__/cli.test.d.ts +5 -0
  16. package/dist/src/__tests__/cli.test.d.ts.map +1 -0
  17. package/dist/src/__tests__/cli.test.js +58 -0
  18. package/dist/src/__tests__/cli.test.js.map +1 -0
  19. package/docs/setup.md +1 -1
  20. package/package.json +5 -3
  21. package/scripts/__tests__/README.md +101 -0
  22. package/scripts/__tests__/validate-phase.test.ts +185 -0
  23. package/scripts/config/config-schema.ts +130 -0
  24. package/scripts/config/default-config.json +57 -0
  25. package/scripts/config-interactive.ts +494 -0
  26. package/scripts/confluence-sync.ts +503 -0
  27. package/scripts/create-project.ts +293 -0
  28. package/scripts/jira-sync.ts +644 -0
  29. package/scripts/list-projects.ts +85 -0
  30. package/scripts/markdown-to-confluence.ts +161 -0
  31. package/scripts/multi-project-estimate.ts +255 -0
  32. package/scripts/phase-runner.ts +303 -0
  33. package/scripts/pr-automation.ts +67 -0
  34. package/scripts/pre-flight-check.ts +285 -0
  35. package/scripts/resource-dashboard.ts +124 -0
  36. package/scripts/setup-env.sh +52 -0
  37. package/scripts/setup-existing-project.ts +381 -0
  38. package/scripts/setup-existing.sh +145 -0
  39. package/scripts/utils/__tests__/config-validator.test.ts +302 -0
  40. package/scripts/utils/__tests__/feature-name-validator.test.ts +129 -0
  41. package/scripts/utils/config-loader.ts +326 -0
  42. package/scripts/utils/config-validator.ts +347 -0
  43. package/scripts/utils/confluence-hierarchy.ts +854 -0
  44. package/scripts/utils/feature-name-validator.ts +135 -0
  45. package/scripts/utils/project-meta.ts +69 -0
  46. package/scripts/validate-phase.ts +279 -0
  47. package/scripts/workflow-orchestrator.ts +178 -0
@@ -0,0 +1,106 @@
1
+ /**
2
+ * feature-name-validator.ts のテスト
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { validateFeatureName, suggestFeatureName } from '../feature-name-validator.js';
6
+ describe('validateFeatureName', () => {
7
+ describe('有効なfeature名', () => {
8
+ it('小文字の英数字とハイフンのみ', () => {
9
+ const result = validateFeatureName('user-auth');
10
+ expect(result.valid).toBe(true);
11
+ expect(result.errors).toHaveLength(0);
12
+ });
13
+ it('単一単語', () => {
14
+ const result = validateFeatureName('payment');
15
+ expect(result.valid).toBe(true);
16
+ expect(result.errors).toHaveLength(0);
17
+ });
18
+ it('複数単語(ハイフン区切り)', () => {
19
+ const result = validateFeatureName('health-check-endpoint');
20
+ expect(result.valid).toBe(true);
21
+ expect(result.errors).toHaveLength(0);
22
+ });
23
+ it('数字を含む', () => {
24
+ const result = validateFeatureName('api-v2');
25
+ expect(result.valid).toBe(true);
26
+ expect(result.errors).toHaveLength(0);
27
+ });
28
+ });
29
+ describe('無効なfeature名', () => {
30
+ it('大文字を含む', () => {
31
+ const result = validateFeatureName('UserAuth');
32
+ expect(result.valid).toBe(false);
33
+ expect(result.errors.some(e => e.includes('大文字'))).toBe(true);
34
+ });
35
+ it('日本語を含む', () => {
36
+ const result = validateFeatureName('ユーザー認証');
37
+ expect(result.valid).toBe(false);
38
+ expect(result.errors.some(e => e.includes('日本語'))).toBe(true);
39
+ });
40
+ it('アンダースコアを含む', () => {
41
+ const result = validateFeatureName('user_auth');
42
+ expect(result.valid).toBe(false);
43
+ expect(result.errors.some(e => e.includes('アンダースコア'))).toBe(true);
44
+ });
45
+ it('スペースを含む', () => {
46
+ const result = validateFeatureName('user auth');
47
+ expect(result.valid).toBe(false);
48
+ expect(result.errors.some(e => e.includes('スペース'))).toBe(true);
49
+ });
50
+ it('先頭にハイフン', () => {
51
+ const result = validateFeatureName('-user-auth');
52
+ expect(result.valid).toBe(false);
53
+ expect(result.errors.some(e => e.includes('先頭'))).toBe(true);
54
+ });
55
+ it('末尾にハイフン', () => {
56
+ const result = validateFeatureName('user-auth-');
57
+ expect(result.valid).toBe(false);
58
+ expect(result.errors.some(e => e.includes('末尾'))).toBe(true);
59
+ });
60
+ it('連続したハイフン', () => {
61
+ const result = validateFeatureName('user--auth');
62
+ expect(result.valid).toBe(false);
63
+ expect(result.errors.some(e => e.includes('連続'))).toBe(true);
64
+ });
65
+ it('空文字', () => {
66
+ const result = validateFeatureName('');
67
+ expect(result.valid).toBe(false);
68
+ expect(result.errors.some(e => e.includes('空'))).toBe(true);
69
+ });
70
+ it('特殊文字を含む', () => {
71
+ const result = validateFeatureName('user@auth');
72
+ expect(result.valid).toBe(false);
73
+ expect(result.errors.some(e => e.includes('使用できない文字'))).toBe(true);
74
+ });
75
+ });
76
+ });
77
+ describe('suggestFeatureName', () => {
78
+ it('大文字を小文字に変換', () => {
79
+ expect(suggestFeatureName('UserAuth')).toBe('userauth');
80
+ });
81
+ it('アンダースコアをハイフンに変換', () => {
82
+ expect(suggestFeatureName('user_auth')).toBe('user-auth');
83
+ });
84
+ it('スペースをハイフンに変換', () => {
85
+ expect(suggestFeatureName('user auth')).toBe('user-auth');
86
+ });
87
+ it('日本語を削除', () => {
88
+ expect(suggestFeatureName('ユーザーuser認証auth')).toBe('userauth');
89
+ });
90
+ it('複数のスペースを1つのハイフンに', () => {
91
+ expect(suggestFeatureName('user auth')).toBe('user-auth');
92
+ });
93
+ it('先頭・末尾のハイフンを削除', () => {
94
+ expect(suggestFeatureName('-user-auth-')).toBe('user-auth');
95
+ });
96
+ it('連続ハイフンを1つに', () => {
97
+ expect(suggestFeatureName('user--auth')).toBe('user-auth');
98
+ });
99
+ it('特殊文字を削除', () => {
100
+ expect(suggestFeatureName('user@#$auth')).toBe('userauth');
101
+ });
102
+ it('複合的な変換', () => {
103
+ expect(suggestFeatureName('User_Auth Feature!')).toBe('user-auth-feature');
104
+ });
105
+ });
106
+ //# sourceMappingURL=feature-name-validator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-name-validator.test.js","sourceRoot":"","sources":["../../../../scripts/utils/__tests__/feature-name-validator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * CLIツールの単体テスト
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=cli.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/cli.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * CLIツールの単体テスト
3
+ */
4
+ import { describe, it, expect, beforeEach } from 'vitest';
5
+ import { Command } from 'commander';
6
+ import { createCLI } from '../cli.js';
7
+ describe('CLI Tool', () => {
8
+ let program;
9
+ beforeEach(() => {
10
+ program = createCLI();
11
+ });
12
+ describe('基本動作', () => {
13
+ it('CLIツールが正しく作成される', () => {
14
+ expect(program).toBeInstanceOf(Command);
15
+ expect(program.name()).toBe('michi');
16
+ });
17
+ it('すべてのコマンドが登録されている', () => {
18
+ const commandNames = program.commands.map(cmd => cmd.name());
19
+ expect(commandNames).toContain('jira:sync');
20
+ expect(commandNames).toContain('confluence:sync');
21
+ expect(commandNames).toContain('phase:run');
22
+ expect(commandNames).toContain('validate:phase');
23
+ expect(commandNames).toContain('preflight');
24
+ expect(commandNames).toContain('project:list');
25
+ expect(commandNames).toContain('project:dashboard');
26
+ expect(commandNames).toContain('workflow:run');
27
+ });
28
+ });
29
+ describe('jira:syncコマンド', () => {
30
+ it('コマンドが存在する', () => {
31
+ const command = program.commands.find(cmd => cmd.name() === 'jira:sync');
32
+ expect(command).toBeDefined();
33
+ expect(command?.description()).toContain('JIRA');
34
+ });
35
+ });
36
+ describe('confluence:syncコマンド', () => {
37
+ it('コマンドが存在する', () => {
38
+ const command = program.commands.find(cmd => cmd.name() === 'confluence:sync');
39
+ expect(command).toBeDefined();
40
+ expect(command?.description()).toContain('Confluence');
41
+ });
42
+ });
43
+ describe('phase:runコマンド', () => {
44
+ it('コマンドが存在する', () => {
45
+ const command = program.commands.find(cmd => cmd.name() === 'phase:run');
46
+ expect(command).toBeDefined();
47
+ expect(command?.description()).toContain('phase');
48
+ });
49
+ });
50
+ describe('validate:phaseコマンド', () => {
51
+ it('コマンドが存在する', () => {
52
+ const command = program.commands.find(cmd => cmd.name() === 'validate:phase');
53
+ expect(command).toBeDefined();
54
+ expect(command?.description()).toContain('Validate');
55
+ });
56
+ });
57
+ });
58
+ //# sourceMappingURL=cli.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../../../src/__tests__/cli.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,IAAI,OAAgB,CAAC;IAErB,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,SAAS,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;YACzB,MAAM,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;YAC1B,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAE7D,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;YAClD,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;YACjD,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;YAC/C,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;YACpD,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;YACnB,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,WAAW,CAAC,CAAC;YACzE,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;YACnB,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,iBAAiB,CAAC,CAAC;YAC/E,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;YACnB,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,WAAW,CAAC,CAAC;YACzE,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;YACnB,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,gBAAgB,CAAC,CAAC;YAC9E,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;YAC9B,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/docs/setup.md CHANGED
@@ -78,7 +78,7 @@ ATLASSIAN_EMAIL=your-email@company.com
78
78
  ATLASSIAN_API_TOKEN=<ATLASSIANトークン>
79
79
 
80
80
  # GitHub設定
81
- GITHUB_ORG=sk8metalme
81
+ GITHUB_ORG=sk8metal
82
82
  GITHUB_TOKEN=<GitHubトークン>
83
83
  GITHUB_REPO=sk8metalme/michi
84
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sk8metal/michi-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Managed Intelligent Comprehensive Hub for Integration - AI-driven development workflow automation",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/sk8metalme/michi.git"
12
+ "url": "https://github.com/sk8metalme/michi.git"
13
13
  },
14
14
  "homepage": "https://github.com/sk8metalme/michi#readme",
15
15
  "bugs": {
@@ -29,12 +29,14 @@
29
29
  "github"
30
30
  ],
31
31
  "bin": {
32
- "michi": "dist/src/cli.js"
32
+ "michi": "./dist/src/cli.js"
33
33
  },
34
34
  "files": [
35
35
  "dist",
36
+ "scripts",
36
37
  "docs",
37
38
  "README.md",
39
+ "CHANGELOG.md",
38
40
  "LICENSE",
39
41
  "env.example",
40
42
  "mcp.json.example"
@@ -0,0 +1,101 @@
1
+ # スクリプト単体テスト
2
+
3
+ ## 概要
4
+
5
+ このディレクトリには、`scripts/` 内の各スクリプトの単体テストが含まれています。
6
+
7
+ ## テストファイル
8
+
9
+ - `validate-phase.test.ts`: フェーズバリデーションのテスト(7テストケース)
10
+
11
+ **注**: pre-flight-check.tsとjira-sync.tsは内部関数がexportされていないため、
12
+ 統合テスト(E2E)でカバーする方針としました。
13
+
14
+ ## テスト実行
15
+
16
+ ```bash
17
+ # すべてのテストを実行
18
+ npm test
19
+
20
+ # 特定のテストファイルのみ実行
21
+ npm test validate-phase.test.ts
22
+
23
+ # ウォッチモード(開発時)
24
+ npm test -- --watch
25
+
26
+ # カバレッジ付き
27
+ npm test -- --coverage
28
+ ```
29
+
30
+ ## テスト戦略
31
+
32
+ ### モック化
33
+
34
+ 以下のモジュールをモック化:
35
+ - `fs`: ファイルシステム操作
36
+ - `axios`: HTTP API呼び出し
37
+ - `dotenv`: 環境変数読み込み
38
+ - `./utils/project-meta.js`: プロジェクトメタデータ
39
+
40
+ ### テスト方針
41
+
42
+ **単体テスト**: validate-phase.ts のみ
43
+ - ✅ 基本動作(成功ケース、Confluence未作成)
44
+ - ✅ フェーズ固有の検証(前提条件、JIRA、営業日表記)
45
+ - ✅ エッジケース(不正なフェーズ名)
46
+
47
+ **統合テスト(E2E)**: その他のスクリプト
48
+ - pre-flight-check.ts: 実際の環境で実行して確認
49
+ - jira-sync.ts: 実際のJIRA APIで動作確認
50
+ - phase-runner.ts: エンドツーエンドで動作確認
51
+
52
+ **理由**:
53
+ - 内部関数をexportするリファクタリングは過剰
54
+ - 統合テストの方が実際の動作を保証できる
55
+ - テストのメンテナンスコストを削減
56
+
57
+ ## テスト戦略の原則
58
+
59
+ ### ✅ 単体テストを書くべき
60
+
61
+ - ビジネスロジック(validate-phase.ts)
62
+ - 純粋関数(引数→戻り値が明確)
63
+ - エッジケースが多い処理
64
+
65
+ ### ❌ 単体テストを避けるべき
66
+
67
+ - API呼び出しが中心のスクリプト(pre-flight-check.ts、jira-sync.ts)
68
+ - 外部依存が多いスクリプト(phase-runner.ts)
69
+ - CLIラッパースクリプト
70
+
71
+ → これらは**統合テスト(E2E)**で十分
72
+
73
+ ## 統合テスト(E2E)
74
+
75
+ 単体テストでカバーできないスクリプトは、統合テストとして実行:
76
+
77
+ ```bash
78
+ # プリフライトチェック
79
+ npm run preflight
80
+
81
+ # フェーズ実行(実際のConfluence/JIRA API使用)
82
+ npm run phase:run test-feature requirements
83
+ npm run phase:run test-feature design
84
+ npm run phase:run test-feature tasks
85
+
86
+ # バリデーション
87
+ npm run validate:phase test-feature requirements
88
+ ```
89
+
90
+ **利点**:
91
+ - 実際のAPI動作を確認できる
92
+ - モックの複雑さを回避
93
+ - メンテナンスコストが低い
94
+
95
+ ## カバレッジ目標
96
+
97
+ - **validate-phase.ts**: 80%以上(単体テストでカバー)
98
+ - **その他のスクリプト**: 統合テスト(E2E)でカバー
99
+
100
+ **方針**: 過剰テストを避け、実用的なテストに絞る
101
+
@@ -0,0 +1,185 @@
1
+ /**
2
+ * validate-phase.ts の単体テスト
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
6
+ import { existsSync, readFileSync } from 'fs';
7
+ import { validatePhase } from '../validate-phase.js';
8
+
9
+ // fsモジュールのモック
10
+ vi.mock('fs', () => ({
11
+ existsSync: vi.fn(),
12
+ readFileSync: vi.fn()
13
+ }));
14
+
15
+ // project-metaのモック
16
+ vi.mock('../utils/project-meta.js', () => ({
17
+ loadProjectMeta: vi.fn(() => ({
18
+ projectId: 'test-project',
19
+ projectName: 'テストプロジェクト',
20
+ jiraProjectKey: 'TEST'
21
+ }))
22
+ }));
23
+
24
+ describe('validatePhase', () => {
25
+ beforeEach(() => {
26
+ vi.clearAllMocks();
27
+ });
28
+
29
+ describe('基本動作', () => {
30
+ it('すべての必須項目が揃っている場合、validationが成功する', () => {
31
+ // Arrange: requirementsフェーズを代表例としてテスト
32
+ vi.mocked(existsSync).mockReturnValue(true);
33
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({
34
+ confluence: {
35
+ spaceKey: 'TEST',
36
+ requirementsPageId: '12345'
37
+ },
38
+ milestones: {
39
+ requirements: {
40
+ completed: true
41
+ }
42
+ }
43
+ }));
44
+
45
+ // Act
46
+ const result = validatePhase('test-feature', 'requirements');
47
+
48
+ // Assert
49
+ expect(result.valid).toBe(true);
50
+ expect(result.errors).toHaveLength(0);
51
+ expect(result.phase).toBe('requirements');
52
+ });
53
+
54
+ it('Confluenceページが作成されていない場合、エラーを返す', () => {
55
+ // Arrange
56
+ vi.mocked(existsSync).mockReturnValue(true);
57
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({
58
+ confluence: {
59
+ spaceKey: 'TEST'
60
+ // requirementsPageId が存在しない
61
+ }
62
+ }));
63
+
64
+ // Act
65
+ const result = validatePhase('test-feature', 'requirements');
66
+
67
+ // Assert
68
+ expect(result.valid).toBe(false);
69
+ expect(result.errors).toContain('❌ Confluenceページ(要件定義)が作成されていません');
70
+ });
71
+ });
72
+
73
+ describe('フェーズ固有の検証', () => {
74
+ it('designフェーズ: 前提条件(requirements完了)をチェックする', () => {
75
+ // Arrange
76
+ vi.mocked(existsSync).mockReturnValue(true);
77
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({
78
+ confluence: {
79
+ designPageId: '67890'
80
+ },
81
+ milestones: {
82
+ requirements: {
83
+ completed: false // 前提条件が満たされていない
84
+ }
85
+ }
86
+ }));
87
+
88
+ // Act
89
+ const result = validatePhase('test-feature', 'design');
90
+
91
+ // Assert
92
+ expect(result.valid).toBe(false);
93
+ expect(result.errors).toContain('❌ 要件定義が完了していません(前提条件)');
94
+ });
95
+
96
+ it('tasksフェーズ: JIRA Epic/Story作成をチェックする', () => {
97
+ // Arrange
98
+ vi.mocked(existsSync).mockReturnValue(true);
99
+ vi.mocked(readFileSync).mockImplementation((path) => {
100
+ if (String(path).includes('tasks.md')) {
101
+ return '11/06(木)Day 1';
102
+ }
103
+ return JSON.stringify({
104
+ milestones: {
105
+ design: {
106
+ completed: true
107
+ }
108
+ },
109
+ jira: {
110
+ // epicKey が存在しない(重要なチェック)
111
+ }
112
+ });
113
+ });
114
+
115
+ // Act
116
+ const result = validatePhase('test-feature', 'tasks');
117
+
118
+ // Assert
119
+ expect(result.valid).toBe(false);
120
+ expect(result.errors).toContain('❌ JIRA Epicが作成されていません');
121
+ });
122
+
123
+ it('tasksフェーズ: 営業日表記をチェックする(重要な独自機能)', () => {
124
+ // Arrange
125
+ vi.mocked(existsSync).mockReturnValue(true);
126
+ vi.mocked(readFileSync).mockImplementation((path) => {
127
+ if (String(path).includes('tasks.md')) {
128
+ return 'タスク一覧(曜日表記なし)'; // 営業日表記がない
129
+ }
130
+ return JSON.stringify({
131
+ milestones: {
132
+ design: {
133
+ completed: true
134
+ }
135
+ },
136
+ jira: {
137
+ epicKey: 'TEST-1',
138
+ stories: {
139
+ created: 5,
140
+ total: 5
141
+ }
142
+ }
143
+ });
144
+ });
145
+
146
+ // Act
147
+ const result = validatePhase('test-feature', 'tasks');
148
+
149
+ // Assert
150
+ expect(result.valid).toBe(true); // 警告だけなのでvalid
151
+ expect(result.warnings).toContain('⚠️ tasks.mdに曜日表記(月、火、水...)が含まれていません');
152
+ });
153
+ });
154
+
155
+ describe('エッジケース', () => {
156
+ it('spec.jsonが存在しない場合、エラーを返す', () => {
157
+ // Arrange
158
+ // requirements.mdは存在するが、spec.jsonは存在しない
159
+ vi.mocked(existsSync).mockImplementation((path: string | Buffer | URL) => {
160
+ const pathStr = typeof path === 'string' ? path : path instanceof URL ? path.pathname : path.toString();
161
+ if (pathStr.includes('requirements.md')) {
162
+ return true;
163
+ }
164
+ if (pathStr.includes('spec.json')) {
165
+ return false;
166
+ }
167
+ return false;
168
+ });
169
+
170
+ // Act
171
+ const result = validatePhase('test-feature', 'requirements');
172
+
173
+ // Assert: エラーをスローせず、errors配列にエラーを含めて返す
174
+ expect(result.valid).toBe(false);
175
+ expect(result.errors).toContainEqual(expect.stringContaining('spec.json読み込みエラー'));
176
+ expect(result.errors).toContainEqual(expect.stringContaining('spec.json not found'));
177
+ });
178
+
179
+ it('不正なフェーズ名の場合、エラーをスローする', () => {
180
+ // Act & Assert
181
+ expect(() => validatePhase('test-feature', 'invalid' as any)).toThrow('Unknown phase: invalid');
182
+ });
183
+ });
184
+ });
185
+
@@ -0,0 +1,130 @@
1
+ /**
2
+ * 設定スキーマ定義
3
+ * Zodを使用して設定ファイルの型安全性を保証
4
+ */
5
+
6
+ import { z } from 'zod';
7
+
8
+ /**
9
+ * Confluence階層構造のモード
10
+ */
11
+ export const ConfluenceHierarchyModeSchema = z.enum(['simple', 'nested']);
12
+
13
+ /**
14
+ * Confluenceページ作成粒度
15
+ */
16
+ export const ConfluencePageCreationGranularitySchema = z.enum([
17
+ 'single',
18
+ 'by-section',
19
+ 'by-hierarchy',
20
+ 'manual'
21
+ ]);
22
+
23
+ /**
24
+ * Confluence階層構造設定(by-hierarchyまたはmanualの場合)
25
+ */
26
+ export const ConfluenceHierarchyStructureSchema = z.object({
27
+ mode: ConfluenceHierarchyModeSchema.optional(),
28
+ parentPageTitle: z.string().optional(),
29
+ createDocTypeParents: z.boolean().optional(),
30
+ structure: z.record(
31
+ z.string(),
32
+ z.object({
33
+ parent: z.string().optional(),
34
+ title: z.string().optional(),
35
+ children: z.array(
36
+ z.object({
37
+ section: z.string(),
38
+ title: z.string()
39
+ })
40
+ ).optional(),
41
+ pages: z.array(
42
+ z.object({
43
+ title: z.string(),
44
+ sections: z.array(z.string()),
45
+ labels: z.array(z.string()).optional()
46
+ })
47
+ ).optional()
48
+ })
49
+ ).optional()
50
+ });
51
+
52
+ /**
53
+ * Confluence設定スキーマ
54
+ */
55
+ export const ConfluenceConfigSchema = z.object({
56
+ pageCreationGranularity: ConfluencePageCreationGranularitySchema.default('single'),
57
+ pageTitleFormat: z.string().default('[{projectName}] {featureName} {docTypeLabel}'),
58
+ autoLabels: z.array(z.string()).default(['{projectLabel}', '{docType}', '{featureName}', 'github-sync']),
59
+ spaces: z.object({
60
+ requirements: z.string().optional(),
61
+ design: z.string().optional(),
62
+ tasks: z.string().optional()
63
+ }).optional(),
64
+ hierarchy: ConfluenceHierarchyStructureSchema.optional()
65
+ });
66
+
67
+ /**
68
+ * JIRA Story作成粒度
69
+ */
70
+ export const JiraStoryCreationGranularitySchema = z.enum([
71
+ 'all',
72
+ 'by-phase',
73
+ 'selected-phases'
74
+ ]);
75
+
76
+ /**
77
+ * JIRA Story Points設定
78
+ */
79
+ export const JiraStoryPointsSchema = z.enum(['auto', 'manual', 'disabled']);
80
+
81
+ /**
82
+ * JIRA設定スキーマ
83
+ */
84
+ export const JiraConfigSchema = z.object({
85
+ storyCreationGranularity: JiraStoryCreationGranularitySchema.default('all'),
86
+ createEpic: z.boolean().default(true),
87
+ storyPoints: JiraStoryPointsSchema.default('auto'),
88
+ autoLabels: z.array(z.string()).default(['{projectLabel}', '{featureName}', '{phaseLabel}']),
89
+ issueTypes: z.object({
90
+ epic: z.string().default('Epic'),
91
+ story: z.string().nullish().default(null), // null | undefined | string
92
+ subtask: z.string().nullish().default(null) // null | undefined | string
93
+ }).optional(),
94
+ selectedPhases: z.array(z.string()).optional()
95
+ });
96
+
97
+ /**
98
+ * ワークフロー設定スキーマ
99
+ */
100
+ export const WorkflowConfigSchema = z.object({
101
+ enabledPhases: z.array(z.string()).default(['requirements', 'design', 'tasks']),
102
+ approvalGates: z.object({
103
+ requirements: z.array(z.string()).optional(),
104
+ design: z.array(z.string()).optional(),
105
+ release: z.array(z.string()).optional()
106
+ }).optional()
107
+ });
108
+
109
+ /**
110
+ * 全体設定スキーマ
111
+ */
112
+ export const AppConfigSchema = z.object({
113
+ confluence: ConfluenceConfigSchema.optional(),
114
+ jira: JiraConfigSchema.optional(),
115
+ workflow: WorkflowConfigSchema.optional()
116
+ });
117
+
118
+ /**
119
+ * 設定の型定義
120
+ */
121
+ export type ConfluenceHierarchyMode = z.infer<typeof ConfluenceHierarchyModeSchema>;
122
+ export type ConfluencePageCreationGranularity = z.infer<typeof ConfluencePageCreationGranularitySchema>;
123
+ export type ConfluenceHierarchyStructure = z.infer<typeof ConfluenceHierarchyStructureSchema>;
124
+ export type ConfluenceConfig = z.infer<typeof ConfluenceConfigSchema>;
125
+ export type JiraStoryCreationGranularity = z.infer<typeof JiraStoryCreationGranularitySchema>;
126
+ export type JiraStoryPoints = z.infer<typeof JiraStoryPointsSchema>;
127
+ export type JiraConfig = z.infer<typeof JiraConfigSchema>;
128
+ export type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>;
129
+ export type AppConfig = z.infer<typeof AppConfigSchema>;
130
+