@sk8metal/michi-cli 0.0.1 → 0.0.3

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 (57) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +60 -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/scripts/utils/config-loader.js +1 -1
  16. package/dist/scripts/utils/config-loader.js.map +1 -1
  17. package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
  18. package/dist/scripts/utils/confluence-hierarchy.js +2 -1
  19. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  20. package/dist/src/__tests__/cli.test.d.ts +5 -0
  21. package/dist/src/__tests__/cli.test.d.ts.map +1 -0
  22. package/dist/src/__tests__/cli.test.js +58 -0
  23. package/dist/src/__tests__/cli.test.js.map +1 -0
  24. package/dist/src/cli.js +0 -0
  25. package/dist/vitest.config.d.ts +3 -0
  26. package/dist/vitest.config.d.ts.map +1 -0
  27. package/dist/vitest.config.js +29 -0
  28. package/dist/vitest.config.js.map +1 -0
  29. package/docs/setup.md +1 -1
  30. package/package.json +8 -4
  31. package/scripts/__tests__/README.md +101 -0
  32. package/scripts/__tests__/validate-phase.test.ts +185 -0
  33. package/scripts/config/config-schema.ts +130 -0
  34. package/scripts/config/default-config.json +57 -0
  35. package/scripts/config-interactive.ts +494 -0
  36. package/scripts/confluence-sync.ts +503 -0
  37. package/scripts/create-project.ts +293 -0
  38. package/scripts/jira-sync.ts +644 -0
  39. package/scripts/list-projects.ts +85 -0
  40. package/scripts/markdown-to-confluence.ts +161 -0
  41. package/scripts/multi-project-estimate.ts +255 -0
  42. package/scripts/phase-runner.ts +303 -0
  43. package/scripts/pr-automation.ts +67 -0
  44. package/scripts/pre-flight-check.ts +285 -0
  45. package/scripts/resource-dashboard.ts +124 -0
  46. package/scripts/setup-env.sh +52 -0
  47. package/scripts/setup-existing-project.ts +381 -0
  48. package/scripts/setup-existing.sh +145 -0
  49. package/scripts/utils/__tests__/config-validator.test.ts +302 -0
  50. package/scripts/utils/__tests__/feature-name-validator.test.ts +129 -0
  51. package/scripts/utils/config-loader.ts +326 -0
  52. package/scripts/utils/config-validator.ts +347 -0
  53. package/scripts/utils/confluence-hierarchy.ts +855 -0
  54. package/scripts/utils/feature-name-validator.ts +135 -0
  55. package/scripts/utils/project-meta.ts +69 -0
  56. package/scripts/validate-phase.ts +279 -0
  57. package/scripts/workflow-orchestrator.ts +178 -0
@@ -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
+
@@ -0,0 +1,57 @@
1
+ {
2
+ "confluence": {
3
+ "pageCreationGranularity": "single",
4
+ "pageTitleFormat": "[{projectName}] {featureName} {docTypeLabel}",
5
+ "autoLabels": [
6
+ "{projectLabel}",
7
+ "{docType}",
8
+ "{featureName}",
9
+ "github-sync"
10
+ ],
11
+ "spaces": {
12
+ "requirements": "${CONFLUENCE_PRD_SPACE}",
13
+ "design": "${CONFLUENCE_PRD_SPACE}",
14
+ "tasks": "${CONFLUENCE_PRD_SPACE}"
15
+ },
16
+ "hierarchy": {
17
+ "enabled": false
18
+ }
19
+ },
20
+ "jira": {
21
+ "storyCreationGranularity": "all",
22
+ "createEpic": true,
23
+ "storyPoints": "auto",
24
+ "autoLabels": [
25
+ "{projectLabel}",
26
+ "{featureName}",
27
+ "{phaseLabel}"
28
+ ],
29
+ "issueTypes": {
30
+ "epic": "Epic",
31
+ "story": null,
32
+ "subtask": null
33
+ }
34
+ },
35
+ "workflow": {
36
+ "enabledPhases": [
37
+ "requirements",
38
+ "design",
39
+ "tasks"
40
+ ],
41
+ "approvalGates": {
42
+ "requirements": [
43
+ "pm",
44
+ "director"
45
+ ],
46
+ "design": [
47
+ "architect",
48
+ "director"
49
+ ],
50
+ "release": [
51
+ "sm",
52
+ "director"
53
+ ]
54
+ }
55
+ }
56
+ }
57
+