@sk8metal/michi-cli 0.0.7 → 0.0.8

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 (92) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -2
  3. package/dist/scripts/__tests__/create-project.test.d.ts +2 -0
  4. package/dist/scripts/__tests__/create-project.test.d.ts.map +1 -0
  5. package/dist/scripts/__tests__/create-project.test.js +247 -0
  6. package/dist/scripts/__tests__/create-project.test.js.map +1 -0
  7. package/dist/scripts/__tests__/multi-project-estimate.test.d.ts +2 -0
  8. package/dist/scripts/__tests__/multi-project-estimate.test.d.ts.map +1 -0
  9. package/dist/scripts/__tests__/multi-project-estimate.test.js +119 -0
  10. package/dist/scripts/__tests__/multi-project-estimate.test.js.map +1 -0
  11. package/dist/scripts/__tests__/setup-existing-project.test.d.ts +2 -0
  12. package/dist/scripts/__tests__/setup-existing-project.test.d.ts.map +1 -0
  13. package/dist/scripts/__tests__/setup-existing-project.test.js +67 -0
  14. package/dist/scripts/__tests__/setup-existing-project.test.js.map +1 -0
  15. package/dist/scripts/__tests__/setup-interactive.test.d.ts +2 -0
  16. package/dist/scripts/__tests__/setup-interactive.test.d.ts.map +1 -0
  17. package/dist/scripts/__tests__/setup-interactive.test.js +160 -0
  18. package/dist/scripts/__tests__/setup-interactive.test.js.map +1 -0
  19. package/dist/scripts/config/default-config.json +57 -0
  20. package/dist/scripts/confluence-sync.d.ts +4 -0
  21. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  22. package/dist/scripts/confluence-sync.js +12 -23
  23. package/dist/scripts/confluence-sync.js.map +1 -1
  24. package/dist/scripts/create-project.js +198 -137
  25. package/dist/scripts/create-project.js.map +1 -1
  26. package/dist/scripts/jira-sync.d.ts.map +1 -1
  27. package/dist/scripts/jira-sync.js +15 -0
  28. package/dist/scripts/jira-sync.js.map +1 -1
  29. package/dist/scripts/list-projects.d.ts.map +1 -1
  30. package/dist/scripts/list-projects.js +42 -15
  31. package/dist/scripts/list-projects.js.map +1 -1
  32. package/dist/scripts/multi-project-estimate.d.ts.map +1 -1
  33. package/dist/scripts/multi-project-estimate.js +56 -21
  34. package/dist/scripts/multi-project-estimate.js.map +1 -1
  35. package/dist/scripts/resource-dashboard.d.ts.map +1 -1
  36. package/dist/scripts/resource-dashboard.js +74 -17
  37. package/dist/scripts/resource-dashboard.js.map +1 -1
  38. package/dist/scripts/setup-existing-project.js +248 -214
  39. package/dist/scripts/setup-existing-project.js.map +1 -1
  40. package/dist/scripts/setup-interactive.d.ts +10 -0
  41. package/dist/scripts/setup-interactive.d.ts.map +1 -0
  42. package/dist/scripts/setup-interactive.js +413 -0
  43. package/dist/scripts/setup-interactive.js.map +1 -0
  44. package/dist/scripts/utils/__tests__/config-validator.test.js +5 -0
  45. package/dist/scripts/utils/__tests__/config-validator.test.js.map +1 -1
  46. package/dist/scripts/utils/__tests__/spec-updater.test.d.ts +5 -0
  47. package/dist/scripts/utils/__tests__/spec-updater.test.d.ts.map +1 -0
  48. package/dist/scripts/utils/__tests__/spec-updater.test.js +158 -0
  49. package/dist/scripts/utils/__tests__/spec-updater.test.js.map +1 -0
  50. package/dist/scripts/utils/confluence-hierarchy.d.ts +2 -1
  51. package/dist/scripts/utils/confluence-hierarchy.d.ts.map +1 -1
  52. package/dist/scripts/utils/confluence-hierarchy.js +5 -0
  53. package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
  54. package/dist/scripts/utils/project-finder.d.ts +30 -0
  55. package/dist/scripts/utils/project-finder.d.ts.map +1 -0
  56. package/dist/scripts/utils/project-finder.js +147 -0
  57. package/dist/scripts/utils/project-finder.js.map +1 -0
  58. package/dist/scripts/utils/spec-updater.d.ts +72 -0
  59. package/dist/scripts/utils/spec-updater.d.ts.map +1 -0
  60. package/dist/scripts/utils/spec-updater.js +141 -0
  61. package/dist/scripts/utils/spec-updater.js.map +1 -0
  62. package/dist/vitest.config.d.ts.map +1 -1
  63. package/dist/vitest.config.js +8 -6
  64. package/dist/vitest.config.js.map +1 -1
  65. package/docs/README.md +2 -2
  66. package/docs/contributing/development.md +37 -0
  67. package/docs/getting-started/{new-project-setup.md → new-repository-setup.md} +66 -19
  68. package/docs/getting-started/setup.md +305 -182
  69. package/docs/guides/customization.md +1 -1
  70. package/docs/guides/multi-project.md +11 -8
  71. package/docs/reference/quick-reference.md +2 -2
  72. package/docs/testing-strategy.md +87 -0
  73. package/package.json +17 -5
  74. package/scripts/__tests__/create-project.test.ts +292 -0
  75. package/scripts/__tests__/multi-project-estimate.test.ts +145 -0
  76. package/scripts/__tests__/setup-existing-project.test.ts +79 -0
  77. package/scripts/__tests__/setup-interactive.test.ts +199 -0
  78. package/scripts/confluence-sync.ts +17 -29
  79. package/scripts/copy-static-assets.js +50 -0
  80. package/scripts/create-project.ts +219 -156
  81. package/scripts/jira-sync.ts +16 -1
  82. package/scripts/list-projects.ts +51 -24
  83. package/scripts/multi-project-estimate.ts +58 -22
  84. package/scripts/resource-dashboard.ts +91 -26
  85. package/scripts/setup-existing-project.ts +264 -223
  86. package/scripts/setup-existing.sh +29 -22
  87. package/scripts/setup-interactive.ts +565 -0
  88. package/scripts/utils/__tests__/config-validator.test.ts +6 -0
  89. package/scripts/utils/__tests__/spec-updater.test.ts +220 -0
  90. package/scripts/utils/confluence-hierarchy.ts +7 -1
  91. package/scripts/utils/project-finder.ts +184 -0
  92. package/scripts/utils/spec-updater.ts +212 -0
@@ -179,50 +179,86 @@ async function aggregateEstimates(): Promise<void> {
179
179
  const estimates: EstimateData[] = [];
180
180
 
181
181
  console.log('Aggregating estimates from all projects...');
182
-
183
- const { data: repos } = await octokit.repos.listForOrg({ org });
182
+
183
+ // pagination対応: 100リポジトリ以上でも全件取得
184
+ const repos = await octokit.paginate(octokit.repos.listForOrg, {
185
+ org,
186
+ per_page: 100
187
+ });
188
+
189
+ console.log(`Found ${repos.length} repositories`);
184
190
 
185
191
  for (const repo of repos) {
186
192
  try {
187
- // .kiro/specs/ ディレクトリを取得
188
- const { data: specs } = await octokit.repos.getContent({
193
+ // projects/ディレクトリを取得(pagination対応)
194
+ const projectsDir = await octokit.paginate('GET /repos/{owner}/{repo}/contents/{path}', {
189
195
  owner: org,
190
196
  repo: repo.name,
191
- path: '.kiro/specs'
197
+ path: 'projects',
198
+ per_page: 100
192
199
  });
193
200
 
194
- if (Array.isArray(specs)) {
195
- for (const spec of specs) {
196
- if (spec.type === 'dir') {
197
- // design.md を取得
201
+ if (Array.isArray(projectsDir)) {
202
+ // projects/配下の各プロジェクトディレクトリを処理
203
+ for (const projectEntry of projectsDir) {
204
+ // 型ガード: projectEntry が必要なプロパティを持つことを確認
205
+ if (typeof projectEntry === 'object' && projectEntry !== null &&
206
+ 'type' in projectEntry && projectEntry.type === 'dir' &&
207
+ 'name' in projectEntry) {
198
208
  try {
199
- const { data: designFile } = await octokit.repos.getContent({
209
+ // projects/{project-id}/.kiro/specs/ ディレクトリを取得(pagination対応)
210
+ const specs = await octokit.paginate('GET /repos/{owner}/{repo}/contents/{path}', {
200
211
  owner: org,
201
212
  repo: repo.name,
202
- path: `.kiro/specs/${spec.name}/design.md`
213
+ path: `projects/${(projectEntry as any).name}/.kiro/specs`,
214
+ per_page: 100
203
215
  });
204
216
 
205
- if ('content' in designFile) {
206
- const content = Buffer.from(designFile.content, 'base64').toString('utf-8');
207
-
208
- // コンテンツから見積もりを抽出
209
- try {
210
- const estimateData = parseEstimateFromContent(content, `${repo.name}/${spec.name}`);
211
- if (estimateData) {
212
- estimates.push(estimateData);
213
- console.log(` ✅ Parsed: ${repo.name}/${spec.name} (${estimateData.totalDays}日)`);
217
+ if (Array.isArray(specs)) {
218
+ for (const spec of specs) {
219
+ // 型ガード: spec が必要なプロパティを持つことを確認
220
+ if (typeof spec === 'object' && spec !== null &&
221
+ 'type' in spec && spec.type === 'dir' &&
222
+ 'name' in spec) {
223
+ // design.md を取得
224
+ try {
225
+ const projectName = (projectEntry as any).name;
226
+ const specName = (spec as any).name;
227
+ const { data: designFile } = await octokit.repos.getContent({
228
+ owner: org,
229
+ repo: repo.name,
230
+ path: `projects/${projectName}/.kiro/specs/${specName}/design.md`
231
+ });
232
+
233
+ if ('content' in designFile) {
234
+ const content = Buffer.from(designFile.content, 'base64').toString('utf-8');
235
+
236
+ // コンテンツから見積もりを抽出
237
+ try {
238
+ const estimateData = parseEstimateFromContent(content, `${repo.name}/${projectName}/${specName}`);
239
+ if (estimateData) {
240
+ estimates.push(estimateData);
241
+ console.log(` ✅ Parsed: ${repo.name}/${projectName}/${specName} (${estimateData.totalDays}日)`);
242
+ }
243
+ } catch (error) {
244
+ console.warn(` ⚠️ Failed to parse ${repo.name}/${projectName}/${specName}:`, error instanceof Error ? error.message : error);
245
+ }
246
+ }
247
+ } catch {
248
+ continue;
249
+ }
214
250
  }
215
- } catch (error) {
216
- console.warn(` ⚠️ Failed to parse ${repo.name}/${spec.name}:`, error instanceof Error ? error.message : error);
217
251
  }
218
252
  }
219
253
  } catch {
254
+ // プロジェクトディレクトリに.kiro/specsがない場合はスキップ
220
255
  continue;
221
256
  }
222
257
  }
223
258
  }
224
259
  }
225
260
  } catch {
261
+ // projects/ディレクトリが存在しない場合はスキップ
226
262
  continue;
227
263
  }
228
264
  }
@@ -5,7 +5,8 @@
5
5
 
6
6
  import { Octokit } from '@octokit/rest';
7
7
  import { config } from 'dotenv';
8
- import { ConfluenceClient } from './confluence-sync.js';
8
+ import { ConfluenceClient, getConfluenceConfig } from './confluence-sync.js';
9
+ import { getConfig } from './utils/config-loader.js';
9
10
 
10
11
  config();
11
12
 
@@ -32,39 +33,67 @@ function escapeHtml(text: string): string {
32
33
  async function createResourceDashboard(): Promise<void> {
33
34
  const token = process.env.GITHUB_TOKEN;
34
35
  const org = process.env.GITHUB_ORG;
35
-
36
+
36
37
  if (!token || !org) {
37
38
  throw new Error('Missing GitHub credentials');
38
39
  }
39
-
40
+
40
41
  const octokit = new Octokit({ auth: token });
41
42
  const projects: ProjectResource[] = [];
42
-
43
+
43
44
  console.log('Gathering project resources...');
44
-
45
- const { data: repos } = await octokit.repos.listForOrg({ org });
46
-
45
+
46
+ // ページネーション対応:全リポジトリを取得
47
+ const repos = await octokit.paginate(octokit.repos.listForOrg, {
48
+ org,
49
+ per_page: 100
50
+ });
51
+
52
+ console.log(`Found ${repos.length} repositories`);
53
+
47
54
  for (const repo of repos) {
48
55
  try {
49
- const { data } = await octokit.repos.getContent({
56
+ // ページネーション対応:projects/ディレクトリの全コンテンツを取得
57
+ const projectsDir = await octokit.paginate('GET /repos/{owner}/{repo}/contents/{path}', {
50
58
  owner: org,
51
59
  repo: repo.name,
52
- path: '.kiro/project.json'
60
+ path: 'projects',
61
+ per_page: 100
53
62
  });
54
-
55
- if ('content' in data) {
56
- const content = Buffer.from(data.content, 'base64').toString('utf-8');
57
- const meta = JSON.parse(content);
58
-
59
- projects.push({
60
- projectName: meta.projectName,
61
- projectId: meta.projectId,
62
- status: meta.status,
63
- team: meta.team,
64
- progress: 0 // TODO: JIRAから進捗を取得
65
- });
63
+
64
+ if (Array.isArray(projectsDir)) {
65
+ // projects/配下の各プロジェクトディレクトリを処理
66
+ for (const projectEntry of projectsDir) {
67
+ if (typeof projectEntry === 'object' && projectEntry !== null && 'type' in projectEntry && projectEntry.type === 'dir' && 'name' in projectEntry) {
68
+ try {
69
+ // projects/{project-id}/.kiro/project.json を取得
70
+ const { data } = await octokit.repos.getContent({
71
+ owner: org,
72
+ repo: repo.name,
73
+ path: `projects/${(projectEntry as any).name}/.kiro/project.json`
74
+ });
75
+
76
+ if ('content' in data) {
77
+ const content = Buffer.from(data.content, 'base64').toString('utf-8');
78
+ const meta = JSON.parse(content);
79
+
80
+ projects.push({
81
+ projectName: meta.projectName,
82
+ projectId: meta.projectId,
83
+ status: meta.status,
84
+ team: meta.team,
85
+ progress: 0 // TODO: JIRAから進捗を取得
86
+ });
87
+ }
88
+ } catch {
89
+ // プロジェクトディレクトリに.kiro/project.jsonがない場合はスキップ
90
+ continue;
91
+ }
92
+ }
93
+ }
66
94
  }
67
95
  } catch (error) {
96
+ // projects/ディレクトリが存在しない場合はスキップ
68
97
  console.warn(`⚠️ Skipping ${repo.name}:`, error instanceof Error ? error.message : 'Unknown error');
69
98
  continue;
70
99
  }
@@ -100,11 +129,47 @@ async function createResourceDashboard(): Promise<void> {
100
129
 
101
130
  console.log('Dashboard content generated');
102
131
  console.log(dashboardContent);
103
-
104
- // TODO: Confluenceに保存
105
- // const confluenceConfig = getConfluenceConfig();
106
- // const client = new ConfluenceClient(confluenceConfig);
107
- // await client.createPage('PRD', 'プロジェクトリソースダッシュボード', dashboardContent);
132
+
133
+ // Confluenceに保存
134
+ try {
135
+ console.log('\n📝 Creating Confluence page...');
136
+ const confluenceConfig = getConfluenceConfig();
137
+ const client = new ConfluenceClient(confluenceConfig);
138
+
139
+ // 設定からスペースキーを取得(デフォルト: PRD)
140
+ const appConfig = getConfig();
141
+ const spaceKey = appConfig.confluence?.spaces?.requirements || confluenceConfig.space || 'PRD';
142
+
143
+ console.log(`Using Confluence space: ${spaceKey}`);
144
+
145
+ const pageTitle = 'プロジェクトリソースダッシュボード';
146
+
147
+ // 既存ページを検索
148
+ const existingPage = await client.searchPage(spaceKey, pageTitle);
149
+
150
+ let pageUrl: string;
151
+ if (existingPage) {
152
+ // 既存ページを更新
153
+ const updated = await client.updatePage(
154
+ existingPage.id,
155
+ pageTitle,
156
+ dashboardContent,
157
+ existingPage.version.number
158
+ );
159
+ const baseUrl = process.env.ATLASSIAN_URL || '';
160
+ pageUrl = `${baseUrl}/wiki${updated._links.webui}`;
161
+ console.log(`✅ Dashboard page updated: ${pageUrl}`);
162
+ } else {
163
+ // 新規ページを作成
164
+ const created = await client.createPage(spaceKey, pageTitle, dashboardContent, ['dashboard', 'resource-management']);
165
+ const baseUrl = process.env.ATLASSIAN_URL || '';
166
+ pageUrl = `${baseUrl}/wiki${created._links.webui}`;
167
+ console.log(`✅ Dashboard page created: ${pageUrl}`);
168
+ }
169
+ } catch (error) {
170
+ console.error('⚠️ Failed to create Confluence page:', error instanceof Error ? error.message : 'Unknown error');
171
+ console.error('Dashboard content has been generated above. You can manually create the page in Confluence.');
172
+ }
108
173
  }
109
174
 
110
175
  // CLI実行