@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
@@ -67,6 +67,25 @@ function parseArgs(): ProjectConfig {
67
67
  return config as ProjectConfig;
68
68
  }
69
69
 
70
+ /**
71
+ * jj/git の依存性をチェックし、利用可能なVCSを返す
72
+ */
73
+ function checkDependencies(): { vcs: 'jj' | 'git'; version: string } {
74
+ try {
75
+ const jjVersion = execSync('jj --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
76
+ return { vcs: 'jj', version: jjVersion };
77
+ } catch {
78
+ try {
79
+ const gitVersion = execSync('git --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
80
+ return { vcs: 'git', version: gitVersion };
81
+ } catch {
82
+ console.error('❌ Neither jj nor git is installed');
83
+ console.error(' Please install jj (https://github.com/martinvonz/jj) or git');
84
+ process.exit(1);
85
+ }
86
+ }
87
+ }
88
+
70
89
  async function createProject(config: ProjectConfig): Promise<void> {
71
90
  const org = config.org || process.env.GITHUB_ORG;
72
91
 
@@ -79,10 +98,14 @@ async function createProject(config: ProjectConfig): Promise<void> {
79
98
  const repoName = config.name;
80
99
  const repoUrl = `https://github.com/${org}/${repoName}`;
81
100
  const projectDir = resolve(`../${repoName}`);
82
-
101
+
102
+ // VCS依存性チェック
103
+ const deps = checkDependencies();
104
+
83
105
  console.log(`🚀 Creating new project: ${config.projectName}`);
84
106
  console.log(` Repository: ${org}/${repoName}`);
85
107
  console.log(` JIRA: ${config.jiraKey}`);
108
+ console.log(` VCS: ${deps.vcs} (${deps.version})`);
86
109
  console.log('');
87
110
 
88
111
  // Step 1: GitHubリポジトリ作成
@@ -93,16 +116,20 @@ async function createProject(config: ProjectConfig): Promise<void> {
93
116
  { stdio: 'inherit' }
94
117
  );
95
118
  console.log(' ✅ Repository created');
96
- } catch (error) {
119
+ } catch {
97
120
  console.log(' ⚠️ Repository may already exist');
98
121
  }
99
122
 
100
123
  // Step 2: リポジトリクローン
101
124
  console.log('\n📥 Step 2: Cloning repository...');
102
125
  try {
103
- execSync(`jj git clone ${repoUrl} ${projectDir}`, { stdio: 'inherit' });
126
+ if (deps.vcs === 'jj') {
127
+ execSync(`jj git clone ${repoUrl} ${projectDir}`, { stdio: 'inherit' });
128
+ } else {
129
+ execSync(`git clone ${repoUrl} ${projectDir}`, { stdio: 'inherit' });
130
+ }
104
131
  console.log(' ✅ Repository cloned');
105
- } catch (error) {
132
+ } catch {
106
133
  console.log(' ⚠️ Clone failed, checking if directory exists...');
107
134
  }
108
135
 
@@ -113,175 +140,211 @@ async function createProject(config: ProjectConfig): Promise<void> {
113
140
  process.exit(1);
114
141
  }
115
142
 
116
- process.chdir(projectDir);
117
- console.log(` 📂 Working directory: ${projectDir}`);
118
-
119
- // Step 4: cc-sdd導入
120
- console.log('\n⚙️ Step 4: Installing cc-sdd...');
121
- execSync('npx cc-sdd@latest --cursor --lang ja --yes', { stdio: 'inherit' });
122
- console.log(' ✅ cc-sdd installed');
123
-
124
- // Step 5: .kiro/project.json 作成
125
- console.log('\n📝 Step 5: Creating project metadata...');
126
- mkdirSync('.kiro', { recursive: true });
143
+ // 元の作業ディレクトリを保存
144
+ const originalCwd = process.cwd();
127
145
 
128
- // ラベル生成(安全なフォールバック付き)
129
- const labels = config.labels || (() => {
130
- // プロジェクトIDからプロジェクトラベル生成
131
- const projectLabel = repoName.toLowerCase().replace(/[^a-z0-9-]/g, '');
132
- const labelSet = new Set([`project:${projectLabel}`]);
146
+ try {
147
+ process.chdir(projectDir);
148
+ console.log(` 📂 Working directory: ${projectDir}`);
149
+
150
+ // Step 4: projects/ディレクトリとプロジェクトディレクトリを作成
151
+ console.log('\n📁 Step 4: Creating project directory structure...');
152
+ const projectsDir = join(projectDir, 'projects');
153
+ const actualProjectDir = join(projectsDir, repoName);
154
+ mkdirSync(actualProjectDir, { recursive: true });
155
+ console.log(` ✅ Project directory created: ${actualProjectDir}`);
156
+
157
+ // Step 5: プロジェクトディレクトリに移動
158
+ process.chdir(actualProjectDir);
159
+ console.log(` 📂 Working directory: ${actualProjectDir}`);
160
+
161
+ // Step 6: cc-sdd導入
162
+ console.log('\n⚙️ Step 6: Installing cc-sdd...');
163
+ execSync('npx cc-sdd@latest --cursor --lang ja --yes', { stdio: 'inherit' });
164
+ console.log(' ✅ cc-sdd installed');
165
+
166
+ // Step 7: .kiro/project.json 作成
167
+ console.log('\n📝 Step 7: Creating project metadata...');
168
+ mkdirSync('.kiro', { recursive: true });
169
+
170
+ // ラベル生成(安全なフォールバック付き)
171
+ const labels = config.labels || (() => {
172
+ // プロジェクトIDからプロジェクトラベル生成
173
+ const projectLabel = repoName.toLowerCase().replace(/[^a-z0-9-]/g, '');
174
+ const labelSet = new Set([`project:${projectLabel}`]);
175
+
176
+ // ハイフンが存在する場合のみサービスラベルを生成
177
+ if (repoName.includes('-')) {
178
+ const parts = repoName.split('-');
179
+ const servicePart = parts[parts.length - 1];
180
+ const serviceLabel = servicePart.toLowerCase().replace(/[^a-z0-9-]/g, '');
181
+
182
+ // サービスラベルがプロジェクトラベルと異なる場合のみ追加
183
+ if (serviceLabel !== projectLabel) {
184
+ labelSet.add(`service:${serviceLabel}`);
185
+ }
186
+ }
187
+
188
+ return Array.from(labelSet);
189
+ })();
133
190
 
134
- // ハイフンが存在する場合のみサービスラベルを生成
135
- if (repoName.includes('-')) {
136
- const parts = repoName.split('-');
137
- const servicePart = parts[parts.length - 1];
138
- const serviceLabel = servicePart.toLowerCase().replace(/[^a-z0-9-]/g, '');
191
+ const projectJson = {
192
+ projectId: repoName,
193
+ projectName: config.projectName,
194
+ jiraProjectKey: config.jiraKey,
195
+ confluenceLabels: labels,
196
+ status: 'active',
197
+ team: [],
198
+ stakeholders: ['@企画', '@部長'],
199
+ repository: repoUrl,
200
+ description: `${config.projectName}の開発`
201
+ };
202
+
203
+ writeFileSync('.kiro/project.json', JSON.stringify(projectJson, null, 2));
204
+ console.log(' ✅ project.json created');
205
+
206
+ // Step 8: Michiから共通ファイルをコピー
207
+ console.log('\n📋 Step 8: Copying common files from Michi...');
208
+ const michiPath = resolve(__dirname, '..');
139
209
 
140
- // サービスラベルがプロジェクトラベルと異なる場合のみ追加
141
- if (serviceLabel !== projectLabel) {
142
- labelSet.add(`service:${serviceLabel}`);
210
+ // コピー先ディレクトリを事前に作成(actualProjectDir 配下に作成)
211
+ console.log(` 📂 Copying to: ${actualProjectDir}`);
212
+ mkdirSync(join(actualProjectDir, '.cursor/rules'), { recursive: true });
213
+ mkdirSync(join(actualProjectDir, '.cursor/commands/kiro'), { recursive: true });
214
+ mkdirSync(join(actualProjectDir, '.kiro/steering'), { recursive: true });
215
+ mkdirSync(join(actualProjectDir, '.kiro/settings/templates'), { recursive: true });
216
+ mkdirSync(join(actualProjectDir, 'scripts/utils'), { recursive: true });
217
+
218
+ // ルールファイル
219
+ const rulesToCopy = [
220
+ 'multi-project.mdc',
221
+ 'github-ssot.mdc',
222
+ 'atlassian-mcp.mdc'
223
+ ];
224
+
225
+ for (const rule of rulesToCopy) {
226
+ const src = join(michiPath, '.cursor/rules', rule);
227
+ const dest = join(actualProjectDir, '.cursor/rules', rule);
228
+ if (existsSync(src)) {
229
+ cpSync(src, dest);
230
+ console.log(` ✅ Copied: .cursor/rules/${rule}`);
143
231
  }
144
232
  }
145
233
 
146
- return Array.from(labelSet);
147
- })();
148
-
149
- const projectJson = {
150
- projectId: repoName,
151
- projectName: config.projectName,
152
- jiraProjectKey: config.jiraKey,
153
- confluenceLabels: labels,
154
- status: 'active',
155
- team: [],
156
- stakeholders: ['@企画', '@部長'],
157
- repository: repoUrl,
158
- description: `${config.projectName}の開発`
159
- };
160
-
161
- writeFileSync('.kiro/project.json', JSON.stringify(projectJson, null, 2));
162
- console.log(' ✅ project.json created');
163
-
164
- // Step 6: Michiから共通ファイルをコピー
165
- console.log('\n📋 Step 6: Copying common files from Michi...');
166
- const michiPath = resolve(__dirname, '..');
167
-
168
- // コピー先ディレクトリを事前に作成
169
- mkdirSync('.cursor/rules', { recursive: true });
170
- mkdirSync('.cursor/commands/kiro', { recursive: true });
171
- mkdirSync('.kiro/steering', { recursive: true });
172
- mkdirSync('.kiro/settings/templates', { recursive: true });
173
- mkdirSync('scripts/utils', { recursive: true });
174
-
175
- // ルールファイル
176
- const rulesToCopy = [
177
- 'multi-project.mdc',
178
- 'github-ssot.mdc',
179
- 'atlassian-mcp.mdc'
180
- ];
181
-
182
- for (const rule of rulesToCopy) {
183
- const src = join(michiPath, '.cursor/rules', rule);
184
- const dest = join(projectDir, '.cursor/rules', rule);
185
- if (existsSync(src)) {
186
- cpSync(src, dest);
187
- console.log(` ✅ Copied: .cursor/rules/${rule}`);
188
- }
189
- }
190
-
191
- // カスタムコマンド
192
- const commandsToCopy = [
193
- 'confluence-sync.md',
194
- 'project-switch.md'
195
- ];
196
-
197
- for (const cmd of commandsToCopy) {
198
- const src = join(michiPath, '.cursor/commands/kiro', cmd);
199
- const dest = join(projectDir, '.cursor/commands/kiro', cmd);
200
- if (existsSync(src)) {
201
- cpSync(src, dest);
202
- console.log(` ✅ Copied: .cursor/commands/kiro/${cmd}`);
203
- }
204
- }
205
-
206
- // Steering
207
- const steeringDir = join(michiPath, '.kiro/steering');
208
- if (existsSync(steeringDir)) {
209
- mkdirSync('.kiro/steering', { recursive: true });
210
- cpSync(steeringDir, '.kiro/steering', { recursive: true });
211
- console.log(' ✅ Copied: .kiro/steering/');
212
- }
213
-
214
- // Scripts(必要なスクリプトのみコピー)
215
- const scriptsDir = join(michiPath, 'scripts');
216
- if (existsSync(scriptsDir)) {
217
- const scriptsToCopy = [
218
- 'confluence-sync.ts',
219
- 'jira-sync.ts',
220
- 'pr-automation.ts',
221
- 'markdown-to-confluence.ts',
222
- 'workflow-orchestrator.ts',
223
- 'list-projects.ts',
224
- 'resource-dashboard.ts',
225
- 'multi-project-estimate.ts',
226
- 'utils/project-meta.ts'
234
+ // カスタムコマンド
235
+ const commandsToCopy = [
236
+ 'confluence-sync.md',
237
+ 'project-switch.md'
227
238
  ];
228
239
 
229
- for (const script of scriptsToCopy) {
230
- const src = join(scriptsDir, script);
231
- const dest = join(projectDir, 'scripts', script);
240
+ for (const cmd of commandsToCopy) {
241
+ const src = join(michiPath, '.cursor/commands/kiro', cmd);
242
+ const dest = join(actualProjectDir, '.cursor/commands/kiro', cmd);
232
243
  if (existsSync(src)) {
233
- // ディレクトリが必要な場合は作成
234
- const destDir = dirname(dest);
235
- mkdirSync(destDir, { recursive: true });
236
244
  cpSync(src, dest);
237
- console.log(` ✅ Copied: scripts/${script}`);
245
+ console.log(` ✅ Copied: .cursor/commands/kiro/${cmd}`);
238
246
  }
239
247
  }
240
- }
241
-
242
- // package.json, tsconfig.json
243
- ['package.json', 'tsconfig.json'].forEach(file => {
244
- const src = join(michiPath, file);
245
- if (existsSync(src)) {
246
- cpSync(src, file);
247
- console.log(` ✅ Copied: ${file}`);
248
+
249
+ // Steering
250
+ const steeringDir = join(michiPath, '.kiro/steering');
251
+ if (existsSync(steeringDir)) {
252
+ const destSteeringDir = join(actualProjectDir, '.kiro/steering');
253
+ mkdirSync(destSteeringDir, { recursive: true });
254
+ cpSync(steeringDir, destSteeringDir, { recursive: true });
255
+ console.log(' ✅ Copied: .kiro/steering/');
248
256
  }
249
- });
250
-
251
- // Step 7: .env テンプレート作成
252
- console.log('\n🔐 Step 7: Creating .env template...');
253
- execSync('npm run setup:env', { stdio: 'inherit' });
254
- console.log('.env created');
255
-
256
- // Step 8: npm install
257
- console.log('\n📦 Step 8: Installing dependencies...');
258
- execSync('npm install', { stdio: 'inherit' });
259
- console.log(' ✅ Dependencies installed');
260
-
261
- // Step 9: 初期コミット
262
- console.log('\n💾 Step 9: Creating initial commit...');
263
- execSync(`jj commit -m "chore: プロジェクト初期化
257
+
258
+ // Scripts(必要なスクリプトのみコピー)
259
+ const scriptsDir = join(michiPath, 'scripts');
260
+ if (existsSync(scriptsDir)) {
261
+ const scriptsToCopy = [
262
+ 'confluence-sync.ts',
263
+ 'jira-sync.ts',
264
+ 'pr-automation.ts',
265
+ 'markdown-to-confluence.ts',
266
+ 'workflow-orchestrator.ts',
267
+ 'list-projects.ts',
268
+ 'resource-dashboard.ts',
269
+ 'multi-project-estimate.ts',
270
+ 'utils/project-meta.ts'
271
+ ];
272
+
273
+ for (const script of scriptsToCopy) {
274
+ const src = join(scriptsDir, script);
275
+ const dest = join(actualProjectDir, 'scripts', script);
276
+ if (existsSync(src)) {
277
+ // ディレクトリが必要な場合は作成
278
+ const destDir = dirname(dest);
279
+ mkdirSync(destDir, { recursive: true });
280
+ cpSync(src, dest);
281
+ console.log(` ✅ Copied: scripts/${script}`);
282
+ }
283
+ }
284
+ }
285
+
286
+ // package.json, tsconfig.jsonをリポジトリルートにコピー
287
+ ['package.json', 'tsconfig.json'].forEach(file => {
288
+ const src = join(michiPath, file);
289
+ const dest = join(projectDir, file); // リポジトリルートにコピー
290
+ if (existsSync(src)) {
291
+ cpSync(src, dest);
292
+ console.log(` ✅ Copied: ${file} (to repository root)`);
293
+ }
294
+ });
295
+
296
+ // Step 9: npm install(リポジトリルートで実行)
297
+ // 注意: setup:env は bash スクリプトなので依存関係不要だが、
298
+ // 将来的に setup:env が tsx やローカルスクリプトに依存する場合は
299
+ // npm install を先に実行する必要があるため、順序を入れ替え
300
+ console.log('\n📦 Step 9: Installing dependencies...');
301
+ process.chdir(projectDir);
302
+ execSync('npm install', { stdio: 'inherit' });
303
+ console.log(' ✅ Dependencies installed');
304
+
305
+ // Step 10: .env テンプレート作成(プロジェクトディレクトリで実行)
306
+ console.log('\n🔐 Step 10: Creating .env template...');
307
+ // package.jsonはprojectDirにあるため、そこで実行
308
+ execSync('npm run setup:env', { cwd: actualProjectDir, stdio: 'inherit' });
309
+ console.log(' ✅ .env created');
310
+
311
+ // Step 11: 初期コミット
312
+ console.log('\n💾 Step 11: Creating initial commit...');
313
+
314
+ const commitMessage = `chore: プロジェクト初期化
264
315
 
265
316
  - cc-sdd導入
266
317
  - プロジェクトメタデータ設定(${config.jiraKey})
267
318
  - 自動化スクリプト追加
268
- - Confluence/JIRA連携設定"`, { stdio: 'inherit' });
269
-
270
- execSync('jj bookmark create main -r "@-"', { stdio: 'inherit' });
271
- console.log(' ✅ Initial commit created');
272
-
273
- // 完了メッセージ
274
- console.log('\n');
275
- console.log('🎉 プロジェクトセットアップ完了!');
276
- console.log('');
277
- console.log('次のステップ:');
278
- console.log(` 1. cd ${projectDir}`);
279
- console.log(' 2. .env ファイルを編集して認証情報を設定');
280
- console.log(' 3. jj git push --bookmark main --allow-new');
281
- console.log(' 4. Cursor で開く: cursor .');
282
- console.log(' 5. /kiro:spec-init <機能説明> で開発開始');
283
- console.log('');
284
- console.log('詳細: docs/new-project-setup.md');
319
+ - Confluence/JIRA連携設定`;
320
+
321
+ if (deps.vcs === 'jj') {
322
+ execSync(`jj commit -m "${commitMessage}"`, { stdio: 'inherit' });
323
+ execSync('jj bookmark create main -r "@-"', { stdio: 'inherit' });
324
+ } else {
325
+ execSync('git add .', { stdio: 'inherit' });
326
+ execSync(`git commit -m "${commitMessage}"`, { stdio: 'inherit' });
327
+ execSync('git branch -M main', { stdio: 'inherit' });
328
+ }
329
+ console.log(' ✅ Initial commit created');
330
+
331
+ // 完了メッセージ
332
+ console.log('\n');
333
+ console.log('🎉 プロジェクトセットアップ完了!');
334
+ console.log('');
335
+ console.log('次のステップ:');
336
+ console.log(` 1. cd ${actualProjectDir}`);
337
+ console.log(' 2. .env ファイルを編集して認証情報を設定');
338
+ console.log(' 3. jj git push --bookmark main --allow-new');
339
+ console.log(' 4. Cursor で開く: cursor .');
340
+ console.log(' 5. /kiro:spec-init <機能説明> で開発開始');
341
+ console.log('');
342
+ console.log('詳細: docs/new-repository-setup.md');
343
+
344
+ } finally {
345
+ // 元の作業ディレクトリに戻る
346
+ process.chdir(originalCwd);
347
+ }
285
348
  }
286
349
 
287
350
  // 実行
@@ -25,6 +25,7 @@ import { loadProjectMeta } from './utils/project-meta.js';
25
25
  import { validateFeatureNameOrThrow } from './utils/feature-name-validator.js';
26
26
  import { getConfig, getConfigPath } from './utils/config-loader.js';
27
27
  import { validateForJiraSync } from './utils/config-validator.js';
28
+ import { updateSpecJsonAfterJiraSync } from './utils/spec-updater.js';
28
29
 
29
30
  config();
30
31
 
@@ -617,10 +618,24 @@ async function syncTasksToJIRA(featureName: string): Promise<void> {
617
618
  // 新規作成数と再利用数を正確に計算
618
619
  const newStoryCount = createdStories.filter(key => !existingStoryKeys.has(key)).length;
619
620
  const reusedStoryCount = createdStories.filter(key => existingStoryKeys.has(key)).length;
620
-
621
+
621
622
  console.log('\n✅ JIRA sync completed');
622
623
  console.log(` Epic: ${epic.key}`);
623
624
  console.log(` Stories: ${createdStories.length} processed (${newStoryCount} new, ${reusedStoryCount} reused)`);
625
+
626
+ // spec.json を更新
627
+ const jiraBaseUrl = process.env.ATLASSIAN_URL || '';
628
+ try {
629
+ updateSpecJsonAfterJiraSync(featureName, {
630
+ projectKey: projectMeta.jiraProjectKey,
631
+ epicKey: epic.key,
632
+ epicUrl: `${jiraBaseUrl}/browse/${epic.key}`,
633
+ storyKeys: createdStories
634
+ });
635
+ } catch (error) {
636
+ console.warn(`⚠️ Failed to update spec.json after JIRA sync: ${error instanceof Error ? error.message : 'Unknown error'}`);
637
+ // spec.json更新の失敗はスクリプト全体の失敗とはしない(JIRA同期は成功しているため)
638
+ }
624
639
  }
625
640
 
626
641
  // CLI実行
@@ -5,7 +5,6 @@
5
5
 
6
6
  import { Octokit } from '@octokit/rest';
7
7
  import { config } from 'dotenv';
8
- import axios from 'axios';
9
8
 
10
9
  config();
11
10
 
@@ -20,42 +19,70 @@ interface ProjectInfo {
20
19
  async function listProjects(): Promise<void> {
21
20
  const token = process.env.GITHUB_TOKEN;
22
21
  const org = process.env.GITHUB_ORG;
23
-
22
+
24
23
  if (!token || !org) {
25
24
  throw new Error('Missing GitHub credentials');
26
25
  }
27
-
26
+
28
27
  const octokit = new Octokit({ auth: token });
29
-
28
+
30
29
  console.log(`Fetching projects for organization: ${org}`);
31
-
32
- const { data: repos } = await octokit.repos.listForOrg({ org });
33
-
30
+
31
+ // ページネーション対応:全リポジトリを取得
32
+ const repos = await octokit.paginate(octokit.repos.listForOrg, {
33
+ org,
34
+ per_page: 100
35
+ });
36
+
37
+ console.log(`Found ${repos.length} repositories`);
38
+
34
39
  const projects: ProjectInfo[] = [];
35
-
40
+
36
41
  for (const repo of repos) {
37
42
  try {
38
- // .kiro/project.json を取得
39
- const { data } = await octokit.repos.getContent({
43
+ // ページネーション対応:projects/ディレクトリの全コンテンツを取得
44
+ const projectsDir = await octokit.paginate('GET /repos/{owner}/{repo}/contents/{path}', {
40
45
  owner: org,
41
46
  repo: repo.name,
42
- path: '.kiro/project.json'
47
+ path: 'projects',
48
+ per_page: 100
43
49
  });
44
-
45
- if ('content' in data) {
46
- const content = Buffer.from(data.content, 'base64').toString('utf-8');
47
- const projectMeta = JSON.parse(content);
48
-
49
- projects.push({
50
- name: projectMeta.projectName,
51
- projectId: projectMeta.projectId,
52
- status: projectMeta.status,
53
- jiraKey: projectMeta.jiraProjectKey,
54
- team: projectMeta.team
55
- });
50
+
51
+ if (Array.isArray(projectsDir)) {
52
+ // projects/配下の各プロジェクトディレクトリを処理
53
+ for (const projectEntry of projectsDir) {
54
+ if (typeof projectEntry === 'object' && projectEntry !== null && 'type' in projectEntry && projectEntry.type === 'dir' && 'name' in projectEntry) {
55
+ try {
56
+ // projects/{project-id}/.kiro/project.json を取得
57
+ const { data } = await octokit.repos.getContent({
58
+ owner: org,
59
+ repo: repo.name,
60
+ path: `projects/${(projectEntry as any).name}/.kiro/project.json`
61
+ });
62
+
63
+ if ('content' in data) {
64
+ const content = Buffer.from(data.content, 'base64').toString('utf-8');
65
+ const projectMeta = JSON.parse(content);
66
+
67
+ projects.push({
68
+ name: projectMeta.projectName,
69
+ projectId: projectMeta.projectId,
70
+ status: projectMeta.status,
71
+ jiraKey: projectMeta.jiraProjectKey,
72
+ team: projectMeta.team
73
+ });
74
+ }
75
+ } catch (error) {
76
+ // プロジェクトディレクトリに.kiro/project.jsonがない場合はスキップ
77
+ console.warn(`⚠️ Skipping project ${(projectEntry as any).name} in ${repo.name}:`, error instanceof Error ? error.message : 'Unknown error');
78
+ continue;
79
+ }
80
+ }
81
+ }
56
82
  }
57
83
  } catch (error) {
58
- // .kiro/project.json が存在しない場合はスキップ
84
+ // projects/ディレクトリが存在しない、または API エラーの場合はスキップ
85
+ console.warn(`⚠️ Skipping ${repo.name}:`, error instanceof Error ? error.message : 'Unknown error');
59
86
  continue;
60
87
  }
61
88
  }