@sk8metal/michi-cli 0.20.0 → 0.22.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.
- package/CHANGELOG.md +129 -0
- package/README.md +1 -1
- package/dist/scripts/multi-project-estimate.js +4 -4
- package/dist/scripts/multi-project-estimate.js.map +1 -1
- package/dist/scripts/phase-runner.js +16 -16
- package/dist/scripts/phase-runner.js.map +1 -1
- package/dist/scripts/pr-automation.js +2 -2
- package/dist/scripts/pr-automation.js.map +1 -1
- package/dist/scripts/pre-flight-check.js +4 -4
- package/dist/scripts/pre-flight-check.js.map +1 -1
- package/dist/scripts/spec-impl-workflow.js +4 -4
- package/dist/scripts/spec-impl-workflow.js.map +1 -1
- package/dist/scripts/template/multi-repo-renderer.js +1 -1
- package/dist/scripts/template/multi-repo-renderer.js.map +1 -1
- package/dist/scripts/template/renderer.d.ts +6 -6
- package/dist/scripts/template/renderer.js +7 -7
- package/dist/scripts/test-execution-generator.js +11 -11
- package/dist/scripts/test-execution-generator.js.map +1 -1
- package/dist/scripts/utils/config-loader.d.ts +1 -1
- package/dist/scripts/utils/config-loader.js +4 -4
- package/dist/scripts/utils/config-loader.js.map +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js +1 -1
- package/dist/scripts/utils/confluence-hierarchy.js.map +1 -1
- package/dist/scripts/utils/docker-requirement-detector.js +3 -3
- package/dist/scripts/utils/docker-requirement-detector.js.map +1 -1
- package/dist/scripts/utils/language-detector.js +2 -2
- package/dist/scripts/utils/language-detector.js.map +1 -1
- package/dist/scripts/utils/multi-repo-validator.d.ts +1 -1
- package/dist/scripts/utils/multi-repo-validator.js +3 -3
- package/dist/scripts/utils/multi-repo-validator.js.map +1 -1
- package/dist/scripts/utils/project-analyzer.d.ts +1 -1
- package/dist/scripts/utils/project-analyzer.js +2 -2
- package/dist/scripts/utils/project-analyzer.js.map +1 -1
- package/dist/scripts/utils/spec-archiver.d.ts +1 -1
- package/dist/scripts/utils/spec-archiver.js +5 -5
- package/dist/scripts/utils/spec-archiver.js.map +1 -1
- package/dist/scripts/utils/spec-updater.js +2 -2
- package/dist/scripts/utils/spec-updater.js.map +1 -1
- package/dist/scripts/utils/tasks-converter.js +2 -2
- package/dist/scripts/utils/tasks-converter.js.map +1 -1
- package/dist/scripts/utils/template-applier.js +1 -1
- package/dist/scripts/utils/template-applier.js.map +1 -1
- package/dist/scripts/validate-phase.js +4 -4
- package/dist/scripts/validate-phase.js.map +1 -1
- package/dist/src/application/use-cases/spec/archive-spec.js +1 -1
- package/dist/src/application/use-cases/spec/archive-spec.js.map +1 -1
- package/dist/src/commands/migrate.js +14 -14
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/infrastructure/external-apis/atlassian/confluence/hierarchy.js +1 -1
- package/dist/src/infrastructure/external-apis/atlassian/confluence/hierarchy.js.map +1 -1
- package/dist/src/infrastructure/external-apis/atlassian/confluence/sync-service.js +2 -2
- package/dist/src/infrastructure/external-apis/atlassian/confluence/sync-service.js.map +1 -1
- package/dist/src/infrastructure/external-apis/atlassian/jira/issue-builder.js +1 -1
- package/dist/src/infrastructure/external-apis/atlassian/jira/issue-builder.js.map +1 -1
- package/dist/src/infrastructure/external-apis/atlassian/jira/sync-service.js +3 -3
- package/dist/src/infrastructure/external-apis/atlassian/jira/sync-service.js.map +1 -1
- package/dist/src/infrastructure/filesystem/project-meta.d.ts +1 -1
- package/dist/src/infrastructure/filesystem/project-meta.js +2 -2
- package/dist/src/infrastructure/filesystem/project-meta.js.map +1 -1
- package/dist/src/presentation/commands/init/handler.js +1 -1
- package/dist/src/presentation/commands/init/handler.js.map +1 -1
- package/dist/src/presentation/commands/init/register.js +2 -2
- package/dist/src/presentation/commands/init/register.js.map +1 -1
- package/dist/src/presentation/commands/init/setup.d.ts +1 -1
- package/dist/src/presentation/commands/init/setup.js +6 -6
- package/dist/src/presentation/commands/init/setup.js.map +1 -1
- package/dist/src/presentation/commands/init/templates.js +5 -5
- package/dist/src/presentation/commands/init/templates.js.map +1 -1
- package/dist/src/presentation/commands/workflow/orchestrator.js +2 -2
- package/dist/src/presentation/commands/workflow/orchestrator.js.map +1 -1
- package/docs/MIGRATION.md +2 -2
- package/docs/architecture.md +5 -5
- package/docs/getting-started/configuration.md +2 -2
- package/docs/guides/claude-code.md +10 -10
- package/docs/guides/comprehensive-verification-guide.md +29 -29
- package/docs/guides/interactive-config.md +2 -2
- package/docs/guides/workflow.md +25 -25
- package/docs/onion-architecture-phase0-complete.md +1 -1
- package/docs/reference/ai-commands.md +12 -12
- package/docs/reference/cli.md +4 -4
- package/docs/reference/environment-variables.md +1 -1
- package/docs/troubleshooting.md +6 -6
- package/package.json +1 -1
- package/scripts/README.md +2 -3
- package/scripts/__tests__/create-project.test.ts +6 -6
- package/scripts/__tests__/multi-project-estimate.test.ts +1 -1
- package/scripts/build/README.md +1 -1
- package/scripts/multi-project-estimate.ts +4 -4
- package/scripts/phase-runner.ts +16 -16
- package/scripts/pr-automation.ts +2 -2
- package/scripts/pre-flight-check.ts +4 -4
- package/scripts/spec-impl-workflow.ts +4 -4
- package/scripts/template/__tests__/renderer.test.ts +34 -34
- package/scripts/template/multi-repo-renderer.ts +1 -1
- package/scripts/template/renderer.ts +9 -9
- package/scripts/test-execution-generator.ts +11 -11
- package/scripts/utils/__tests__/config-loader.test.ts +14 -9
- package/scripts/utils/__tests__/config-validator.test.ts +8 -8
- package/scripts/utils/__tests__/multi-repo-validator.test.ts +17 -17
- package/scripts/utils/__tests__/project-analyzer.test.ts +28 -28
- package/scripts/utils/__tests__/project-meta.test.ts +22 -22
- package/scripts/utils/__tests__/spec-updater.test.ts +3 -3
- package/scripts/utils/config-loader.ts +6 -6
- package/scripts/utils/confluence-hierarchy.ts +1 -1
- package/scripts/utils/docker-requirement-detector.ts +3 -3
- package/scripts/utils/language-detector.ts +2 -2
- package/scripts/utils/multi-repo-validator.ts +3 -3
- package/scripts/utils/project-analyzer.ts +2 -2
- package/scripts/utils/spec-archiver.ts +5 -5
- package/scripts/utils/spec-updater.ts +2 -2
- package/scripts/utils/tasks-converter.ts +2 -2
- package/scripts/utils/template-applier.ts +1 -1
- package/scripts/validate-phase.ts +4 -4
- package/templates/claude-agent/README.md +1 -1
- package/templates/claude-agent/agents/designer.md +4 -4
- package/templates/claude-agent/agents/developer.md +2 -2
- package/templates/claude-agent/agents/doc-reviewer.md +4 -4
- package/templates/claude-agent/agents/manager-agent.md +3 -3
- package/templates/claude-agent/agents/repo-spec-executor.md +1 -1
- package/templates/claude-agent/agents/tester.md +3 -3
- package/templates/claude-agent/commands/kiro/kiro-spec-impl.md +6 -6
- package/templates/claude-agent/commands/kiro/kiro-spec-tasks.md +10 -10
- package/templates/claude-agent/rules/doc-review.md +1 -1
- package/templates/common/.kiro/project.json.template +0 -21
|
@@ -175,9 +175,9 @@ export async function generateLoadTestExecution(
|
|
|
175
175
|
const files: string[] = [];
|
|
176
176
|
|
|
177
177
|
try {
|
|
178
|
-
const designPath = join(projectRoot, '.
|
|
179
|
-
const requirementsPath = join(projectRoot, '.
|
|
180
|
-
const outputDir = join(projectRoot, '.
|
|
178
|
+
const designPath = join(projectRoot, '.michi', 'specs', feature, 'design.md');
|
|
179
|
+
const requirementsPath = join(projectRoot, '.michi', 'specs', feature, 'requirements.md');
|
|
180
|
+
const outputDir = join(projectRoot, '.michi', 'specs', feature, 'test-execution', 'performance');
|
|
181
181
|
|
|
182
182
|
mkdirSync(outputDir, { recursive: true });
|
|
183
183
|
|
|
@@ -327,7 +327,7 @@ pip install locust
|
|
|
327
327
|
|
|
328
328
|
\`\`\`bash
|
|
329
329
|
# このディレクトリに移動
|
|
330
|
-
cd .
|
|
330
|
+
cd .michi/specs/${feature}/test-execution/performance
|
|
331
331
|
|
|
332
332
|
# Locust起動(Web UI)
|
|
333
333
|
locust -f locustfile.py
|
|
@@ -369,8 +369,8 @@ export async function generateSecurityTestExecution(
|
|
|
369
369
|
const files: string[] = [];
|
|
370
370
|
|
|
371
371
|
try {
|
|
372
|
-
const designPath = join(projectRoot, '.
|
|
373
|
-
const outputDir = join(projectRoot, '.
|
|
372
|
+
const designPath = join(projectRoot, '.michi', 'specs', feature, 'design.md');
|
|
373
|
+
const outputDir = join(projectRoot, '.michi', 'specs', feature, 'test-execution', 'security');
|
|
374
374
|
|
|
375
375
|
mkdirSync(outputDir, { recursive: true });
|
|
376
376
|
|
|
@@ -530,7 +530,7 @@ function generateSecurityTestPlan(
|
|
|
530
530
|
|
|
531
531
|
\`\`\`bash
|
|
532
532
|
# このディレクトリに移動
|
|
533
|
-
cd .
|
|
533
|
+
cd .michi/specs/${feature}/test-execution/security
|
|
534
534
|
|
|
535
535
|
# スキャン実行
|
|
536
536
|
./run-zap-scan.sh
|
|
@@ -564,8 +564,8 @@ export async function generateManualRegressionExecution(
|
|
|
564
564
|
const files: string[] = [];
|
|
565
565
|
|
|
566
566
|
try {
|
|
567
|
-
const designPath = join(projectRoot, '.
|
|
568
|
-
const outputDir = join(projectRoot, '.
|
|
567
|
+
const designPath = join(projectRoot, '.michi', 'specs', feature, 'design.md');
|
|
568
|
+
const outputDir = join(projectRoot, '.michi', 'specs', feature, 'test-execution', 'manual-regression');
|
|
569
569
|
|
|
570
570
|
mkdirSync(outputDir, { recursive: true });
|
|
571
571
|
|
|
@@ -658,7 +658,7 @@ export async function generateIntegrationE2EExecution(
|
|
|
658
658
|
const files: string[] = [];
|
|
659
659
|
|
|
660
660
|
try {
|
|
661
|
-
const outputDir = join(projectRoot, '.
|
|
661
|
+
const outputDir = join(projectRoot, '.michi', 'specs', feature, 'test-execution', testType);
|
|
662
662
|
mkdirSync(outputDir, { recursive: true });
|
|
663
663
|
|
|
664
664
|
// チェックリストを生成
|
|
@@ -766,7 +766,7 @@ export async function generateAllTestExecutions(
|
|
|
766
766
|
feature: string,
|
|
767
767
|
projectRoot: string = process.cwd()
|
|
768
768
|
): Promise<GenerationResult[]> {
|
|
769
|
-
const selectionPath = join(projectRoot, '.
|
|
769
|
+
const selectionPath = join(projectRoot, '.michi', 'specs', feature, 'test-type-selection.json');
|
|
770
770
|
|
|
771
771
|
if (!existsSync(selectionPath)) {
|
|
772
772
|
throw new Error('test-type-selection.jsonが存在しません。先にtest-type-selectionフェーズを実行してください');
|
|
@@ -143,6 +143,10 @@ describe('config-loader', () => {
|
|
|
143
143
|
rmSync(legacyDir, { recursive: true, force: true });
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// .michiディレクトリを作成
|
|
147
|
+
const michiDir = join(testProjectRoot, '.michi');
|
|
148
|
+
mkdirSync(michiDir, { recursive: true });
|
|
149
|
+
|
|
146
150
|
const configPath = join(testProjectRoot, '.michi/config.json');
|
|
147
151
|
writeFileSync(configPath, JSON.stringify({
|
|
148
152
|
confluence: {
|
|
@@ -209,7 +213,7 @@ describe('config-loader', () => {
|
|
|
209
213
|
rmSync(michiDir, { recursive: true, force: true });
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
// legacy
|
|
216
|
+
// legacyパス(.kiro)にファイルを作成
|
|
213
217
|
mkdirSync(join(testProjectRoot, '.kiro'), { recursive: true });
|
|
214
218
|
const legacyConfigPath = join(testProjectRoot, '.kiro/config.json');
|
|
215
219
|
writeFileSync(legacyConfigPath, JSON.stringify({
|
|
@@ -230,13 +234,14 @@ describe('config-loader', () => {
|
|
|
230
234
|
|
|
231
235
|
consoleWarnSpy.mockRestore();
|
|
232
236
|
|
|
233
|
-
// 次のテストのために.michi
|
|
237
|
+
// 次のテストのために.michiディレクトリを作成
|
|
234
238
|
mkdirSync(michiDir, { recursive: true });
|
|
235
239
|
});
|
|
236
240
|
|
|
237
241
|
it('legacyパスと新規パスの両方が存在する場合は警告を表示しない', () => {
|
|
238
242
|
// 両方のパスにファイルを作成
|
|
239
243
|
mkdirSync(join(testProjectRoot, '.kiro'), { recursive: true });
|
|
244
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
240
245
|
const legacyConfigPath = join(testProjectRoot, '.kiro/config.json');
|
|
241
246
|
const michiConfigPath = join(testProjectRoot, '.michi/config.json');
|
|
242
247
|
writeFileSync(legacyConfigPath, JSON.stringify({}));
|
|
@@ -297,9 +302,9 @@ describe('config-loader', () => {
|
|
|
297
302
|
it('project.jsonからプロジェクトメタデータを読み込む', () => {
|
|
298
303
|
clearConfigCache();
|
|
299
304
|
|
|
300
|
-
// .
|
|
301
|
-
const projectJsonPath = join(testProjectRoot, '.
|
|
302
|
-
mkdirSync(join(testProjectRoot, '.
|
|
305
|
+
// .michi/project.json を作成
|
|
306
|
+
const projectJsonPath = join(testProjectRoot, '.michi', 'project.json');
|
|
307
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
303
308
|
writeFileSync(projectJsonPath, JSON.stringify({
|
|
304
309
|
projectId: 'test-project',
|
|
305
310
|
projectName: 'Test Project',
|
|
@@ -316,8 +321,8 @@ describe('config-loader', () => {
|
|
|
316
321
|
it('project.jsonが存在しない場合でもエラーにならない', () => {
|
|
317
322
|
clearConfigCache();
|
|
318
323
|
|
|
319
|
-
// .
|
|
320
|
-
const projectJsonPath = join(testProjectRoot, '.
|
|
324
|
+
// .michi/project.json を削除
|
|
325
|
+
const projectJsonPath = join(testProjectRoot, '.michi', 'project.json');
|
|
321
326
|
if (existsSync(projectJsonPath)) {
|
|
322
327
|
unlinkSync(projectJsonPath);
|
|
323
328
|
}
|
|
@@ -374,8 +379,8 @@ describe('config-loader', () => {
|
|
|
374
379
|
clearConfigCache();
|
|
375
380
|
|
|
376
381
|
// 最初の設定
|
|
377
|
-
const projectJsonPath = join(testProjectRoot, '.
|
|
378
|
-
mkdirSync(join(testProjectRoot, '.
|
|
382
|
+
const projectJsonPath = join(testProjectRoot, '.michi', 'project.json');
|
|
383
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
379
384
|
writeFileSync(projectJsonPath, JSON.stringify({
|
|
380
385
|
projectId: 'initial-id',
|
|
381
386
|
projectName: 'Initial Name'
|
|
@@ -477,8 +477,8 @@ describe('config-validator', () => {
|
|
|
477
477
|
);
|
|
478
478
|
|
|
479
479
|
// project.jsonを作成
|
|
480
|
-
const projectJsonPath = join(testProjectRoot, '.
|
|
481
|
-
mkdirSync(join(testProjectRoot, '.
|
|
480
|
+
const projectJsonPath = join(testProjectRoot, '.michi/project.json');
|
|
481
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
482
482
|
writeFileSync(
|
|
483
483
|
projectJsonPath,
|
|
484
484
|
JSON.stringify({
|
|
@@ -510,8 +510,8 @@ describe('config-validator', () => {
|
|
|
510
510
|
);
|
|
511
511
|
|
|
512
512
|
// project.jsonを作成(すべての必須フィールドを含める)
|
|
513
|
-
const projectJsonPath = join(testProjectRoot, '.
|
|
514
|
-
mkdirSync(join(testProjectRoot, '.
|
|
513
|
+
const projectJsonPath = join(testProjectRoot, '.michi/project.json');
|
|
514
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
515
515
|
writeFileSync(
|
|
516
516
|
projectJsonPath,
|
|
517
517
|
JSON.stringify({
|
|
@@ -562,8 +562,8 @@ describe('config-validator', () => {
|
|
|
562
562
|
);
|
|
563
563
|
|
|
564
564
|
// project.jsonを作成(すべての必須フィールドを含める)
|
|
565
|
-
const projectJsonPath = join(testProjectRoot, '.
|
|
566
|
-
mkdirSync(join(testProjectRoot, '.
|
|
565
|
+
const projectJsonPath = join(testProjectRoot, '.michi/project.json');
|
|
566
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
567
567
|
writeFileSync(
|
|
568
568
|
projectJsonPath,
|
|
569
569
|
JSON.stringify({
|
|
@@ -613,8 +613,8 @@ describe('config-validator', () => {
|
|
|
613
613
|
);
|
|
614
614
|
|
|
615
615
|
// project.jsonを作成(すべての必須フィールドを含める)
|
|
616
|
-
const projectJsonPath = join(testProjectRoot, '.
|
|
617
|
-
mkdirSync(join(testProjectRoot, '.
|
|
616
|
+
const projectJsonPath = join(testProjectRoot, '.michi/project.json');
|
|
617
|
+
mkdirSync(join(testProjectRoot, '.michi'), { recursive: true });
|
|
618
618
|
writeFileSync(
|
|
619
619
|
projectJsonPath,
|
|
620
620
|
JSON.stringify({
|
|
@@ -355,13 +355,13 @@ describe('hasMichiSetup', () => {
|
|
|
355
355
|
});
|
|
356
356
|
|
|
357
357
|
describe('Michi導入済みの場合', () => {
|
|
358
|
-
it('.
|
|
359
|
-
// .
|
|
360
|
-
const
|
|
361
|
-
fs.mkdirSync(
|
|
358
|
+
it('.michi/project.json が存在する場合はtrueを返す', () => {
|
|
359
|
+
// .michi ディレクトリを作成
|
|
360
|
+
const specDir = path.join(tempDir, '.michi');
|
|
361
|
+
fs.mkdirSync(specDir);
|
|
362
362
|
|
|
363
363
|
// project.json を作成
|
|
364
|
-
const projectJson = path.join(
|
|
364
|
+
const projectJson = path.join(specDir, 'project.json');
|
|
365
365
|
fs.writeFileSync(projectJson, '{}');
|
|
366
366
|
|
|
367
367
|
const result = hasMichiSetup(tempDir);
|
|
@@ -370,17 +370,17 @@ describe('hasMichiSetup', () => {
|
|
|
370
370
|
});
|
|
371
371
|
|
|
372
372
|
describe('Michi未導入の場合', () => {
|
|
373
|
-
it('.
|
|
374
|
-
// .
|
|
375
|
-
const
|
|
376
|
-
fs.mkdirSync(
|
|
373
|
+
it('.michi/project.json が存在しない場合はfalseを返す', () => {
|
|
374
|
+
// .michi ディレクトリのみ作成(project.jsonなし)
|
|
375
|
+
const specDir = path.join(tempDir, '.michi');
|
|
376
|
+
fs.mkdirSync(specDir);
|
|
377
377
|
|
|
378
378
|
const result = hasMichiSetup(tempDir);
|
|
379
379
|
expect(result).toBe(false);
|
|
380
380
|
});
|
|
381
381
|
|
|
382
|
-
it('.
|
|
383
|
-
// .
|
|
382
|
+
it('.michi ディレクトリ自体が存在しない場合はfalseを返す', () => {
|
|
383
|
+
// .michi ディレクトリを作成しない
|
|
384
384
|
const result = hasMichiSetup(tempDir);
|
|
385
385
|
expect(result).toBe(false);
|
|
386
386
|
});
|
|
@@ -455,10 +455,10 @@ describe('validateLocalPath - Michi導入チェック', () => {
|
|
|
455
455
|
// .git ディレクトリを作成(Gitリポジトリとして認識させる)
|
|
456
456
|
fs.mkdirSync(path.join(tempDir, '.git'));
|
|
457
457
|
|
|
458
|
-
// .
|
|
459
|
-
const
|
|
460
|
-
fs.mkdirSync(
|
|
461
|
-
fs.writeFileSync(path.join(
|
|
458
|
+
// .michi/project.json を作成(Michi導入済み)
|
|
459
|
+
const specDir = path.join(tempDir, '.michi');
|
|
460
|
+
fs.mkdirSync(specDir);
|
|
461
|
+
fs.writeFileSync(path.join(specDir, 'project.json'), '{}');
|
|
462
462
|
|
|
463
463
|
const result = validateLocalPath(repository);
|
|
464
464
|
expect(result.hasMichiSetup).toBe(true);
|
|
@@ -469,13 +469,13 @@ describe('validateLocalPath - Michi導入チェック', () => {
|
|
|
469
469
|
// .git ディレクトリを作成(Gitリポジトリとして認識させる)
|
|
470
470
|
fs.mkdirSync(path.join(tempDir, '.git'));
|
|
471
471
|
|
|
472
|
-
// .
|
|
472
|
+
// .michi/project.json は作成しない(Michi未導入)
|
|
473
473
|
|
|
474
474
|
const result = validateLocalPath(repository);
|
|
475
475
|
expect(result.hasMichiSetup).toBe(false);
|
|
476
476
|
expect(result.michiSetupCommand).not.toBeNull();
|
|
477
477
|
expect(result.warnings).toContain(
|
|
478
|
-
`Repository 'test-repo' does not have Michi setup (.
|
|
478
|
+
`Repository 'test-repo' does not have Michi setup (.michi/project.json not found)`,
|
|
479
479
|
);
|
|
480
480
|
});
|
|
481
481
|
|
|
@@ -129,10 +129,10 @@ describe('ProjectAnalyzer', () => {
|
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
describe('getProjectMetadata', () => {
|
|
132
|
-
it('should load project metadata from .
|
|
133
|
-
// Setup: create .
|
|
134
|
-
const
|
|
135
|
-
mkdirSync(
|
|
132
|
+
it('should load project metadata from .michi/project.json', () => {
|
|
133
|
+
// Setup: create .michi/project.json
|
|
134
|
+
const specDir = join(testDir, '.michi');
|
|
135
|
+
mkdirSync(specDir);
|
|
136
136
|
const projectJson = {
|
|
137
137
|
projectId: 'test-project',
|
|
138
138
|
projectName: 'Test Project',
|
|
@@ -143,7 +143,7 @@ describe('ProjectAnalyzer', () => {
|
|
|
143
143
|
stakeholders: ['pm1'],
|
|
144
144
|
repository: 'https://github.com/test/repo'
|
|
145
145
|
};
|
|
146
|
-
writeFileSync(join(
|
|
146
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectJson));
|
|
147
147
|
|
|
148
148
|
// Execute
|
|
149
149
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -157,7 +157,7 @@ describe('ProjectAnalyzer', () => {
|
|
|
157
157
|
}
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
it('should return error if .
|
|
160
|
+
it('should return error if .michi/project.json does not exist', () => {
|
|
161
161
|
// Execute
|
|
162
162
|
const result = analyzer.getProjectMetadata(testDir);
|
|
163
163
|
|
|
@@ -169,10 +169,10 @@ describe('ProjectAnalyzer', () => {
|
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
it('should return error if project.json has invalid JSON', () => {
|
|
172
|
-
// Setup: create .
|
|
173
|
-
const
|
|
174
|
-
mkdirSync(
|
|
175
|
-
writeFileSync(join(
|
|
172
|
+
// Setup: create .michi/project.json with invalid JSON
|
|
173
|
+
const specDir = join(testDir, '.michi');
|
|
174
|
+
mkdirSync(specDir);
|
|
175
|
+
writeFileSync(join(specDir, 'project.json'), '{ invalid json }');
|
|
176
176
|
|
|
177
177
|
// Execute
|
|
178
178
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -186,15 +186,15 @@ describe('ProjectAnalyzer', () => {
|
|
|
186
186
|
|
|
187
187
|
it('should reject path traversal attacks in projectId (..)', () => {
|
|
188
188
|
// Setup: malicious projectId
|
|
189
|
-
const
|
|
190
|
-
mkdirSync(
|
|
189
|
+
const specDir = join(testDir, '.michi');
|
|
190
|
+
mkdirSync(specDir);
|
|
191
191
|
const projectJson = {
|
|
192
192
|
projectId: '../tmp/evil',
|
|
193
193
|
projectName: 'Evil Project',
|
|
194
194
|
jiraProjectKey: 'EVIL',
|
|
195
195
|
confluenceLabels: ['test']
|
|
196
196
|
};
|
|
197
|
-
writeFileSync(join(
|
|
197
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectJson));
|
|
198
198
|
|
|
199
199
|
// Execute
|
|
200
200
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -208,15 +208,15 @@ describe('ProjectAnalyzer', () => {
|
|
|
208
208
|
|
|
209
209
|
it('should reject path traversal attacks in projectId (/)', () => {
|
|
210
210
|
// Setup: malicious projectId
|
|
211
|
-
const
|
|
212
|
-
mkdirSync(
|
|
211
|
+
const specDir = join(testDir, '.michi');
|
|
212
|
+
mkdirSync(specDir);
|
|
213
213
|
const projectJson = {
|
|
214
214
|
projectId: 'foo/bar',
|
|
215
215
|
projectName: 'Evil Project',
|
|
216
216
|
jiraProjectKey: 'EVIL',
|
|
217
217
|
confluenceLabels: ['test']
|
|
218
218
|
};
|
|
219
|
-
writeFileSync(join(
|
|
219
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectJson));
|
|
220
220
|
|
|
221
221
|
// Execute
|
|
222
222
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -230,15 +230,15 @@ describe('ProjectAnalyzer', () => {
|
|
|
230
230
|
|
|
231
231
|
it('should reject path traversal attacks in projectId (\\)', () => {
|
|
232
232
|
// Setup: malicious projectId
|
|
233
|
-
const
|
|
234
|
-
mkdirSync(
|
|
233
|
+
const specDir = join(testDir, '.michi');
|
|
234
|
+
mkdirSync(specDir);
|
|
235
235
|
const projectJson = {
|
|
236
236
|
projectId: 'foo\\bar',
|
|
237
237
|
projectName: 'Evil Project',
|
|
238
238
|
jiraProjectKey: 'EVIL',
|
|
239
239
|
confluenceLabels: ['test']
|
|
240
240
|
};
|
|
241
|
-
writeFileSync(join(
|
|
241
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectJson));
|
|
242
242
|
|
|
243
243
|
// Execute
|
|
244
244
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -252,15 +252,15 @@ describe('ProjectAnalyzer', () => {
|
|
|
252
252
|
|
|
253
253
|
it('should reject empty projectId', () => {
|
|
254
254
|
// Setup: empty projectId
|
|
255
|
-
const
|
|
256
|
-
mkdirSync(
|
|
255
|
+
const specDir = join(testDir, '.michi');
|
|
256
|
+
mkdirSync(specDir);
|
|
257
257
|
const projectJson = {
|
|
258
258
|
projectId: ' ',
|
|
259
259
|
projectName: 'Test Project',
|
|
260
260
|
jiraProjectKey: 'TEST',
|
|
261
261
|
confluenceLabels: ['test']
|
|
262
262
|
};
|
|
263
|
-
writeFileSync(join(
|
|
263
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectJson));
|
|
264
264
|
|
|
265
265
|
// Execute
|
|
266
266
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -274,15 +274,15 @@ describe('ProjectAnalyzer', () => {
|
|
|
274
274
|
|
|
275
275
|
it('should reject projectId with invalid characters', () => {
|
|
276
276
|
// Setup: invalid characters in projectId
|
|
277
|
-
const
|
|
278
|
-
mkdirSync(
|
|
277
|
+
const specDir = join(testDir, '.michi');
|
|
278
|
+
mkdirSync(specDir);
|
|
279
279
|
const projectJson = {
|
|
280
280
|
projectId: 'test@project!',
|
|
281
281
|
projectName: 'Test Project',
|
|
282
282
|
jiraProjectKey: 'TEST',
|
|
283
283
|
confluenceLabels: ['test']
|
|
284
284
|
};
|
|
285
|
-
writeFileSync(join(
|
|
285
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectJson));
|
|
286
286
|
|
|
287
287
|
// Execute
|
|
288
288
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -296,8 +296,8 @@ describe('ProjectAnalyzer', () => {
|
|
|
296
296
|
|
|
297
297
|
it('should accept valid projectId with alphanumeric, hyphens, and underscores', () => {
|
|
298
298
|
// Setup: valid projectId
|
|
299
|
-
const
|
|
300
|
-
mkdirSync(
|
|
299
|
+
const specDir = join(testDir, '.michi');
|
|
300
|
+
mkdirSync(specDir);
|
|
301
301
|
const projectJson = {
|
|
302
302
|
projectId: 'Valid-Project_123',
|
|
303
303
|
projectName: 'Test Project',
|
|
@@ -308,7 +308,7 @@ describe('ProjectAnalyzer', () => {
|
|
|
308
308
|
stakeholders: [],
|
|
309
309
|
repository: ''
|
|
310
310
|
};
|
|
311
|
-
writeFileSync(join(
|
|
311
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectJson));
|
|
312
312
|
|
|
313
313
|
// Execute
|
|
314
314
|
const result = analyzer.getProjectMetadata(testDir);
|
|
@@ -26,8 +26,8 @@ describe('project-meta', () => {
|
|
|
26
26
|
|
|
27
27
|
describe('loadProjectMeta', () => {
|
|
28
28
|
it('正しいproject.jsonを読み込める', () => {
|
|
29
|
-
const
|
|
30
|
-
mkdirSync(
|
|
29
|
+
const specDir = join(testRoot, '.michi');
|
|
30
|
+
mkdirSync(specDir, { recursive: true });
|
|
31
31
|
|
|
32
32
|
const projectMeta = {
|
|
33
33
|
projectId: 'test-project',
|
|
@@ -40,7 +40,7 @@ describe('project-meta', () => {
|
|
|
40
40
|
repository: 'https://github.com/owner/repo.git',
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
writeFileSync(join(
|
|
43
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(projectMeta, null, 2));
|
|
44
44
|
|
|
45
45
|
const result = loadProjectMeta(testRoot);
|
|
46
46
|
|
|
@@ -51,21 +51,21 @@ describe('project-meta', () => {
|
|
|
51
51
|
expect(() => loadProjectMeta(testRoot)).toThrow('Project metadata not found');
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
it('.
|
|
54
|
+
it('.michiディレクトリが存在しない場合はエラー', () => {
|
|
55
55
|
expect(() => loadProjectMeta(testRoot)).toThrow('Project metadata not found');
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
it('不正なJSON形式の場合はエラー', () => {
|
|
59
|
-
const
|
|
60
|
-
mkdirSync(
|
|
61
|
-
writeFileSync(join(
|
|
59
|
+
const specDir = join(testRoot, '.michi');
|
|
60
|
+
mkdirSync(specDir, { recursive: true });
|
|
61
|
+
writeFileSync(join(specDir, 'project.json'), 'invalid json');
|
|
62
62
|
|
|
63
63
|
expect(() => loadProjectMeta(testRoot)).toThrow('Invalid JSON');
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it('必須フィールド projectName が欠けている場合はエラー', () => {
|
|
67
|
-
const
|
|
68
|
-
mkdirSync(
|
|
67
|
+
const specDir = join(testRoot, '.michi');
|
|
68
|
+
mkdirSync(specDir, { recursive: true });
|
|
69
69
|
|
|
70
70
|
const invalidMeta = {
|
|
71
71
|
projectId: 'test-project',
|
|
@@ -78,14 +78,14 @@ describe('project-meta', () => {
|
|
|
78
78
|
repository: 'https://github.com/owner/repo.git',
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
writeFileSync(join(
|
|
81
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(invalidMeta));
|
|
82
82
|
|
|
83
83
|
expect(() => loadProjectMeta(testRoot)).toThrow('Required field missing');
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
it('必須フィールド confluenceLabels が欠けている場合はエラー', () => {
|
|
87
|
-
const
|
|
88
|
-
mkdirSync(
|
|
87
|
+
const specDir = join(testRoot, '.michi');
|
|
88
|
+
mkdirSync(specDir, { recursive: true });
|
|
89
89
|
|
|
90
90
|
const invalidMeta = {
|
|
91
91
|
projectId: 'test-project',
|
|
@@ -98,7 +98,7 @@ describe('project-meta', () => {
|
|
|
98
98
|
repository: 'https://github.com/owner/repo.git',
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
writeFileSync(join(
|
|
101
|
+
writeFileSync(join(specDir, 'project.json'), JSON.stringify(invalidMeta));
|
|
102
102
|
|
|
103
103
|
expect(() => loadProjectMeta(testRoot)).toThrow('Required field missing');
|
|
104
104
|
});
|
|
@@ -117,13 +117,13 @@ describe('project-meta', () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
beforeEach(() => {
|
|
120
|
-
const
|
|
121
|
-
mkdirSync(
|
|
120
|
+
const specDir = join(testRoot, '.michi');
|
|
121
|
+
mkdirSync(specDir, { recursive: true });
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
it('HTTPS URL (.git付き) から owner/repo を抽出できる', () => {
|
|
125
125
|
const projectMeta = createValidProjectMeta('https://github.com/owner/repo.git');
|
|
126
|
-
writeFileSync(join(testRoot, '.
|
|
126
|
+
writeFileSync(join(testRoot, '.michi/project.json'), JSON.stringify(projectMeta));
|
|
127
127
|
|
|
128
128
|
const result = getRepositoryInfo(testRoot);
|
|
129
129
|
expect(result).toBe('owner/repo');
|
|
@@ -131,7 +131,7 @@ describe('project-meta', () => {
|
|
|
131
131
|
|
|
132
132
|
it('HTTPS URL (.gitなし) から owner/repo を抽出できる', () => {
|
|
133
133
|
const projectMeta = createValidProjectMeta('https://github.com/owner/repo');
|
|
134
|
-
writeFileSync(join(testRoot, '.
|
|
134
|
+
writeFileSync(join(testRoot, '.michi/project.json'), JSON.stringify(projectMeta));
|
|
135
135
|
|
|
136
136
|
const result = getRepositoryInfo(testRoot);
|
|
137
137
|
expect(result).toBe('owner/repo');
|
|
@@ -139,7 +139,7 @@ describe('project-meta', () => {
|
|
|
139
139
|
|
|
140
140
|
it('SSH URL (.git付き) から owner/repo を抽出できる', () => {
|
|
141
141
|
const projectMeta = createValidProjectMeta('git@github.com:owner/repo.git');
|
|
142
|
-
writeFileSync(join(testRoot, '.
|
|
142
|
+
writeFileSync(join(testRoot, '.michi/project.json'), JSON.stringify(projectMeta));
|
|
143
143
|
|
|
144
144
|
const result = getRepositoryInfo(testRoot);
|
|
145
145
|
expect(result).toBe('owner/repo');
|
|
@@ -147,7 +147,7 @@ describe('project-meta', () => {
|
|
|
147
147
|
|
|
148
148
|
it('SSH URL (.gitなし) から owner/repo を抽出できる', () => {
|
|
149
149
|
const projectMeta = createValidProjectMeta('git@github.com:owner/repo');
|
|
150
|
-
writeFileSync(join(testRoot, '.
|
|
150
|
+
writeFileSync(join(testRoot, '.michi/project.json'), JSON.stringify(projectMeta));
|
|
151
151
|
|
|
152
152
|
const result = getRepositoryInfo(testRoot);
|
|
153
153
|
expect(result).toBe('owner/repo');
|
|
@@ -165,21 +165,21 @@ describe('project-meta', () => {
|
|
|
165
165
|
repository: '',
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
-
writeFileSync(join(testRoot, '.
|
|
168
|
+
writeFileSync(join(testRoot, '.michi/project.json'), JSON.stringify(projectMeta));
|
|
169
169
|
|
|
170
170
|
expect(() => getRepositoryInfo(testRoot)).toThrow('Repository information not found');
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
it('不正な repository 形式の場合はエラー', () => {
|
|
174
174
|
const projectMeta = createValidProjectMeta('https://example.com/not-github/repo.git');
|
|
175
|
-
writeFileSync(join(testRoot, '.
|
|
175
|
+
writeFileSync(join(testRoot, '.michi/project.json'), JSON.stringify(projectMeta));
|
|
176
176
|
|
|
177
177
|
expect(() => getRepositoryInfo(testRoot)).toThrow('Invalid GitHub repository format');
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
it('owner/repo にハイフンが含まれる場合も正しく抽出できる', () => {
|
|
181
181
|
const projectMeta = createValidProjectMeta('https://github.com/my-org/my-repo.git');
|
|
182
|
-
writeFileSync(join(testRoot, '.
|
|
182
|
+
writeFileSync(join(testRoot, '.michi/project.json'), JSON.stringify(projectMeta));
|
|
183
183
|
|
|
184
184
|
const result = getRepositoryInfo(testRoot);
|
|
185
185
|
expect(result).toBe('my-org/my-repo');
|
|
@@ -22,7 +22,7 @@ describe('spec-updater', () => {
|
|
|
22
22
|
if (existsSync(testDir)) {
|
|
23
23
|
rmSync(testDir, { recursive: true });
|
|
24
24
|
}
|
|
25
|
-
mkdirSync(resolve(testDir, '.
|
|
25
|
+
mkdirSync(resolve(testDir, '.michi/specs', testFeatureName), { recursive: true });
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
afterEach(() => {
|
|
@@ -45,7 +45,7 @@ describe('spec-updater', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
it('spec.jsonが存在する場合、その内容を返す', () => {
|
|
48
|
-
const specPath = resolve(testDir, '.
|
|
48
|
+
const specPath = resolve(testDir, '.michi/specs', testFeatureName, 'spec.json');
|
|
49
49
|
const testSpec: SpecJson = {
|
|
50
50
|
featureName: testFeatureName,
|
|
51
51
|
projectName: 'Test Project',
|
|
@@ -74,7 +74,7 @@ describe('spec-updater', () => {
|
|
|
74
74
|
|
|
75
75
|
saveSpecJson(testFeatureName, spec, testDir);
|
|
76
76
|
|
|
77
|
-
const specPath = resolve(testDir, '.
|
|
77
|
+
const specPath = resolve(testDir, '.michi/specs', testFeatureName, 'spec.json');
|
|
78
78
|
expect(existsSync(specPath)).toBe(true);
|
|
79
79
|
|
|
80
80
|
const saved = JSON.parse(readFileSync(specPath, 'utf-8'));
|
|
@@ -190,7 +190,7 @@ function validateConfigPath(configPath: string, projectRoot: string): boolean {
|
|
|
190
190
|
function resolveConfigPath(projectRoot: string): string {
|
|
191
191
|
const michiConfigPath = resolve(projectRoot, '.michi/config.json');
|
|
192
192
|
const legacyConfigPath = resolve(projectRoot, '.kiro/config.json');
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
// legacyパスが存在する場合は警告(移行推奨)
|
|
195
195
|
if (existsSync(legacyConfigPath) && !existsSync(michiConfigPath)) {
|
|
196
196
|
console.warn(
|
|
@@ -199,7 +199,7 @@ function resolveConfigPath(projectRoot: string): string {
|
|
|
199
199
|
' The legacy path will not be supported in future versions.\n'
|
|
200
200
|
);
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
|
|
203
203
|
return michiConfigPath;
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -306,10 +306,10 @@ function loadProjectEnv(projectRoot: string): Record<string, string> {
|
|
|
306
306
|
|
|
307
307
|
/**
|
|
308
308
|
* プロジェクトメタデータを読み込み
|
|
309
|
-
* .
|
|
309
|
+
* .michi/project.json からプロジェクト情報を読み込む
|
|
310
310
|
*/
|
|
311
311
|
function loadProjectMetadata(projectRoot: string): Partial<AppConfig> | null {
|
|
312
|
-
const projectJsonPath = resolve(projectRoot, '.
|
|
312
|
+
const projectJsonPath = resolve(projectRoot, '.michi/project.json');
|
|
313
313
|
|
|
314
314
|
if (!existsSync(projectJsonPath)) {
|
|
315
315
|
return null;
|
|
@@ -392,7 +392,7 @@ function applyEnvVarsToConfig(
|
|
|
392
392
|
* 1. デフォルト設定
|
|
393
393
|
* 2. グローバル.env(~/.michi/.env)
|
|
394
394
|
* 3. グローバル設定(~/.michi/config.json)
|
|
395
|
-
* 4. プロジェクトメタデータ(.
|
|
395
|
+
* 4. プロジェクトメタデータ(.michi/project.json)
|
|
396
396
|
* 5. プロジェクト設定(.michi/config.json)
|
|
397
397
|
* 6. プロジェクト.env(.env)- 最高優先度
|
|
398
398
|
*/
|
|
@@ -451,7 +451,7 @@ export function getConfig(projectRoot: string = process.cwd()): AppConfig {
|
|
|
451
451
|
const projectConfigPath = resolveConfigPath(projectRoot);
|
|
452
452
|
const globalConfigPath = getGlobalConfigPath();
|
|
453
453
|
const globalEnvPath = getGlobalEnvPath();
|
|
454
|
-
const projectMetaPath = resolve(projectRoot, '.
|
|
454
|
+
const projectMetaPath = resolve(projectRoot, '.michi/project.json');
|
|
455
455
|
const projectEnvPath = resolve(projectRoot, '.env');
|
|
456
456
|
const currentFileUrl = import.meta.url;
|
|
457
457
|
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
@@ -209,7 +209,7 @@ async function getOrCreateParentPage(
|
|
|
209
209
|
// 親ページを作成
|
|
210
210
|
const parentContent = createConfluencePage({
|
|
211
211
|
title: parentTitle,
|
|
212
|
-
githubUrl: `${projectMeta.repository}/tree/main/.
|
|
212
|
+
githubUrl: `${projectMeta.repository}/tree/main/.michi/specs/${featureName}`,
|
|
213
213
|
content: `<p>機能: <strong>${featureName}</strong></p><p>このページの下に、要件定義・設計・タスク分割のページが配置されます。</p>`,
|
|
214
214
|
approvers: projectMeta.stakeholders,
|
|
215
215
|
projectName: projectMeta.projectName
|