@sk8metal/michi-cli 0.10.1 → 0.12.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.
Files changed (139) hide show
  1. package/README.md +71 -848
  2. package/dist/scripts/constants/environments.d.ts +1 -1
  3. package/dist/scripts/constants/environments.d.ts.map +1 -1
  4. package/dist/scripts/constants/environments.js +0 -20
  5. package/dist/scripts/constants/environments.js.map +1 -1
  6. package/dist/scripts/phase-runner.js +1 -1
  7. package/dist/scripts/phase-runner.js.map +1 -1
  8. package/dist/scripts/utils/multi-repo-validator.d.ts +18 -0
  9. package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -1
  10. package/dist/scripts/utils/multi-repo-validator.js +42 -0
  11. package/dist/scripts/utils/multi-repo-validator.js.map +1 -1
  12. package/dist/scripts/utils/tasks-format-validator.js +3 -3
  13. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  14. package/dist/scripts/utils/template-finder.d.ts +2 -2
  15. package/dist/scripts/utils/template-finder.d.ts.map +1 -1
  16. package/dist/scripts/utils/template-finder.js +3 -8
  17. package/dist/scripts/utils/template-finder.js.map +1 -1
  18. package/dist/src/cli.d.ts.map +1 -1
  19. package/dist/src/cli.js +0 -8
  20. package/dist/src/cli.js.map +1 -1
  21. package/dist/src/commands/init.d.ts +0 -4
  22. package/dist/src/commands/init.d.ts.map +1 -1
  23. package/dist/src/commands/init.js +6 -30
  24. package/dist/src/commands/init.js.map +1 -1
  25. package/dist/src/commands/setup-existing.d.ts +2 -6
  26. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  27. package/dist/src/commands/setup-existing.js +8 -142
  28. package/dist/src/commands/setup-existing.js.map +1 -1
  29. package/docs/README.md +20 -83
  30. package/docs/getting-started/configuration.md +350 -0
  31. package/docs/getting-started/installation.md +59 -0
  32. package/docs/getting-started/quick-start.md +76 -0
  33. package/docs/guides/atlassian-integration.md +116 -0
  34. package/docs/guides/claude-code.md +155 -0
  35. package/docs/guides/multi-repo.md +117 -0
  36. package/docs/guides/workflow.md +382 -0
  37. package/docs/reference/ai-commands.md +92 -0
  38. package/docs/reference/cli.md +752 -0
  39. package/docs/reference/environment-variables.md +192 -0
  40. package/docs/troubleshooting.md +498 -0
  41. package/package.json +1 -3
  42. package/scripts/__tests__/create-project.test.ts +12 -12
  43. package/scripts/__tests__/setup-existing-project.test.ts +22 -22
  44. package/scripts/constants/__tests__/environments.test.ts +7 -50
  45. package/scripts/constants/environments.ts +1 -27
  46. package/scripts/phase-runner.ts +1 -1
  47. package/scripts/template/__tests__/renderer.test.ts +21 -21
  48. package/scripts/utils/__tests__/multi-repo-validator.test.ts +159 -1
  49. package/scripts/utils/multi-repo-validator.ts +50 -0
  50. package/scripts/utils/tasks-format-validator.ts +3 -3
  51. package/scripts/utils/template-finder.ts +5 -11
  52. package/templates/claude/agents/e2e-first-planner/AGENT.md +1 -1
  53. package/templates/claude/agents/pr-resolver/AGENT.md +15 -3
  54. package/templates/claude/commands/michi/e2e-plan.md +1 -1
  55. package/templates/claude/commands/michi/spec-design.md +2 -2
  56. package/templates/claude/commands/michi/spec-tasks.md +156 -0
  57. package/templates/claude/commands/michi/test-planning.md +1 -1
  58. package/templates/claude/commands/michi/validate-design.md +3 -3
  59. package/templates/claude/commands/michi-multi-repo/impl-all.md +30 -1
  60. package/templates/claude/commands/michi-multi-repo/propagate-specs.md +14 -1
  61. package/templates/claude/commands/michi-multi-repo/spec-review.md +16 -2
  62. package/templates/claude-agent/agents/repo-spec-executor.md +1 -1
  63. package/templates/claude-agent/commands/michi/spec-tasks.md +117 -0
  64. package/templates/claude-agent/rules/code-size-monitor.md +26 -0
  65. package/templates/claude-agent/rules/code-size-rules.md +32 -0
  66. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +1 -1
  67. package/docs/context.md +0 -59
  68. package/docs/michi-development/contributing/development.md +0 -341
  69. package/docs/michi-development/contributing/release.md +0 -365
  70. package/docs/michi-development/design/config-unification.md +0 -733
  71. package/docs/michi-development/design/design-config-current-state.md +0 -330
  72. package/docs/michi-development/design/design-config-implementation.md +0 -628
  73. package/docs/michi-development/design/design-config-migration.md +0 -952
  74. package/docs/michi-development/design/design-config-security.md +0 -771
  75. package/docs/michi-development/design/design-config-solution.md +0 -583
  76. package/docs/michi-development/design/design-config-testing.md +0 -892
  77. package/docs/michi-development/testing/manual-verification-flow.md +0 -871
  78. package/docs/michi-development/testing/manual-verification-other-tools.md +0 -1279
  79. package/docs/michi-development/testing/manual-verification-troubleshooting.md +0 -122
  80. package/docs/michi-development/testing/pre-publish-checklist.md +0 -560
  81. package/docs/michi-development/testing-strategy.md +0 -87
  82. package/docs/plan.md +0 -275
  83. package/docs/user-guide/getting-started/github-token-setup.md +0 -510
  84. package/docs/user-guide/getting-started/new-repository-setup.md +0 -704
  85. package/docs/user-guide/getting-started/quick-start.md +0 -212
  86. package/docs/user-guide/getting-started/setup.md +0 -819
  87. package/docs/user-guide/guides/agent-skills-integration.md +0 -222
  88. package/docs/user-guide/guides/customization.md +0 -537
  89. package/docs/user-guide/guides/internationalization.md +0 -540
  90. package/docs/user-guide/guides/migration-guide.md +0 -138
  91. package/docs/user-guide/guides/multi-project.md +0 -368
  92. package/docs/user-guide/guides/multi-repo-guide.md +0 -1590
  93. package/docs/user-guide/guides/phase-automation.md +0 -419
  94. package/docs/user-guide/guides/workflow.md +0 -574
  95. package/docs/user-guide/hands-on/README.md +0 -142
  96. package/docs/user-guide/hands-on/claude-agent-setup.md +0 -597
  97. package/docs/user-guide/hands-on/claude-setup.md +0 -452
  98. package/docs/user-guide/hands-on/cursor-setup.md +0 -353
  99. package/docs/user-guide/hands-on/troubleshooting.md +0 -964
  100. package/docs/user-guide/hands-on/verification-checklist.md +0 -439
  101. package/docs/user-guide/hands-on/workflow-walkthrough.md +0 -1078
  102. package/docs/user-guide/reference/config.md +0 -589
  103. package/docs/user-guide/reference/multi-repo-api.md +0 -771
  104. package/docs/user-guide/reference/quick-reference.md +0 -297
  105. package/docs/user-guide/reference/security-test-payloads.md +0 -50
  106. package/docs/user-guide/reference/tasks-template.md +0 -550
  107. package/docs/user-guide/release/ci-setup-java.md +0 -114
  108. package/docs/user-guide/release/ci-setup-nodejs.md +0 -94
  109. package/docs/user-guide/release/ci-setup-php.md +0 -102
  110. package/docs/user-guide/release/ci-setup-troubleshooting.md +0 -94
  111. package/docs/user-guide/release/ci-setup.md +0 -188
  112. package/docs/user-guide/release/release-flow.md +0 -476
  113. package/docs/user-guide/templates/test-specs/README.md +0 -173
  114. package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +0 -553
  115. package/docs/user-guide/templates/test-specs/integration-test-spec-template.md +0 -435
  116. package/docs/user-guide/templates/test-specs/performance-test-spec-template.md +0 -454
  117. package/docs/user-guide/templates/test-specs/security-test-spec-template.md +0 -625
  118. package/docs/user-guide/templates/test-specs/unit-test-spec-template.md +0 -328
  119. package/docs/user-guide/testing/integration-tests.md +0 -312
  120. package/docs/user-guide/testing/tdd-cycle.md +0 -349
  121. package/docs/user-guide/testing/test-execution-flow.md +0 -396
  122. package/docs/user-guide/testing/test-failure-handling.md +0 -521
  123. package/docs/user-guide/testing/test-planning-flow.md +0 -185
  124. package/docs/user-guide/testing-strategy.md +0 -185
  125. package/docs/verification-guide.md +0 -518
  126. package/templates/cline/rules/atlassian-integration.md +0 -36
  127. package/templates/cline/rules/michi-core.md +0 -56
  128. package/templates/codex/AGENTS.override.md +0 -277
  129. package/templates/codex/prompts/confluence-sync.md +0 -177
  130. package/templates/codex/rules/README.md +0 -210
  131. package/templates/cursor/commands/kiro/kiro-spec-impl.md +0 -244
  132. package/templates/cursor/commands/kiro/kiro-spec-tasks.md +0 -354
  133. package/templates/cursor/commands/michi/confluence-sync.md +0 -76
  134. package/templates/cursor/commands/michi/project-switch.md +0 -69
  135. package/templates/cursor/rules/atlassian-mcp.mdc +0 -188
  136. package/templates/cursor/rules/github-ssot.mdc +0 -151
  137. package/templates/cursor/rules/multi-project.mdc +0 -81
  138. package/templates/gemini/commands/README.md +0 -41
  139. package/templates/gemini/rules/GEMINI.md +0 -80
@@ -17,23 +17,23 @@ describe('create-project.ts パス問題', () => {
17
17
  vi.clearAllMocks();
18
18
  });
19
19
 
20
- it('.cursor, .kiro, scripts が actualProjectDir にコピーされる', () => {
20
+ it('.claude, .kiro, scripts が actualProjectDir にコピーされる', () => {
21
21
  const projectDir = '/test/repo';
22
22
  const actualProjectDir = '/test/repo/projects/test-project';
23
23
 
24
- // .cursor/rules のコピー先パスを検証
25
- const cursorRulesPath = join(actualProjectDir, '.cursor/rules', 'test.mdc');
26
- expect(cursorRulesPath).toContain('projects/test-project/.cursor/rules');
27
- expect(cursorRulesPath).not.toContain(join(projectDir, '.cursor'));
24
+ // .claude/rules のコピー先パスを検証
25
+ const claudeRulesPath = join(actualProjectDir, '.claude/rules', 'test.mdc');
26
+ expect(claudeRulesPath).toContain('projects/test-project/.claude/rules');
27
+ expect(claudeRulesPath).not.toContain(join(projectDir, '.claude'));
28
28
 
29
- // .cursor/commands のコピー先パスを検証
30
- const cursorCommandsPath = join(
29
+ // .claude/commands のコピー先パスを検証
30
+ const claudeCommandsPath = join(
31
31
  actualProjectDir,
32
- '.cursor/commands/kiro',
32
+ '.claude/commands',
33
33
  'test.md',
34
34
  );
35
- expect(cursorCommandsPath).toContain(
36
- 'projects/test-project/.cursor/commands',
35
+ expect(claudeCommandsPath).toContain(
36
+ 'projects/test-project/.claude/commands',
37
37
  );
38
38
 
39
39
  // .kiro/steering のコピー先パスを検証
@@ -63,8 +63,8 @@ describe('create-project.ts パス問題', () => {
63
63
  const actualProjectDir = '/test/repo/projects/test-project';
64
64
 
65
65
  const directories = [
66
- join(actualProjectDir, '.cursor/rules'),
67
- join(actualProjectDir, '.cursor/commands/kiro'),
66
+ join(actualProjectDir, '.claude/rules'),
67
+ join(actualProjectDir, '.claude/commands'),
68
68
  join(actualProjectDir, '.kiro/steering'),
69
69
  join(actualProjectDir, '.kiro/settings/templates'),
70
70
  join(actualProjectDir, 'scripts/utils'),
@@ -21,9 +21,9 @@ vi.mock('../utils/project-finder.js', () => ({
21
21
  }));
22
22
  vi.mock('../constants/environments.js', () => ({
23
23
  getEnvironmentConfig: vi.fn(() => ({
24
- rulesDir: '.cursor/rules',
25
- commandsDir: '.cursor/commands/kiro',
26
- templateSource: 'cursor'
24
+ rulesDir: '.claude/rules',
25
+ commandsDir: '.claude/commands',
26
+ templateSource: 'claude'
27
27
  })),
28
28
  isSupportedEnvironment: vi.fn(() => true)
29
29
  }));
@@ -35,7 +35,7 @@ vi.mock('../template/renderer.js', () => ({
35
35
  LANG_CODE: 'ja',
36
36
  DEV_GUIDELINES: '- Think in English, but generate responses in Japanese',
37
37
  KIRO_DIR: '.kiro',
38
- AGENT_DIR: '.cursor'
38
+ AGENT_DIR: '.claude'
39
39
  })),
40
40
  renderTemplate: vi.fn((content: string) => content)
41
41
  }));
@@ -143,18 +143,18 @@ ATLASSIAN_API_TOKEN=existing_token
143
143
 
144
144
  it('ディレクトリがコピー前に作成される', () => {
145
145
  const projectDir = '/test/repo/projects/test-project';
146
-
147
- // .cursor/rules ディレクトリの作成
148
- const rulesDir = join(projectDir, '.cursor/rules');
146
+
147
+ // .claude/rules ディレクトリの作成
148
+ const rulesDir = join(projectDir, '.claude/rules');
149
149
  mkdirSync(rulesDir, { recursive: true });
150
-
151
- // .cursor/commands/kiro ディレクトリの作成
152
- const commandsDir = join(projectDir, '.cursor/commands/kiro');
150
+
151
+ // .claude/commands ディレクトリの作成
152
+ const commandsDir = join(projectDir, '.claude/commands');
153
153
  mkdirSync(commandsDir, { recursive: true });
154
-
154
+
155
155
  // ディレクトリが正しく作成されることを確認
156
- expect(rulesDir).toContain('.cursor/rules');
157
- expect(commandsDir).toContain('.cursor/commands/kiro');
156
+ expect(rulesDir).toContain('.claude/rules');
157
+ expect(commandsDir).toContain('.claude/commands');
158
158
  });
159
159
 
160
160
  it('パッケージ名が @sk8metal/michi-cli に統一されている', () => {
@@ -177,8 +177,8 @@ ATLASSIAN_API_TOKEN=existing_token
177
177
  it('完了メッセージに scripts/ ディレクトリが含まれていない', () => {
178
178
  const projectDir = '/test/repo/projects/test-project';
179
179
  const repoRoot = '/test/repo';
180
- const envConfigRulesDir = '.cursor/rules';
181
- const envConfigCommandsDir = '.cursor/commands/kiro';
180
+ const envConfigRulesDir = '.claude/rules';
181
+ const envConfigCommandsDir = '.claude/commands';
182
182
 
183
183
  // 実際に作成されるファイルのリスト(環境別)
184
184
  const createdFiles = [
@@ -217,14 +217,14 @@ ATLASSIAN_API_TOKEN=existing_token
217
217
 
218
218
  it('環境別のディレクトリマッピングが正しい', () => {
219
219
  const envConfig = {
220
- rulesDir: '.cursor/rules',
221
- commandsDir: '.cursor/commands/kiro',
222
- templateSource: 'cursor'
220
+ rulesDir: '.claude/rules',
221
+ commandsDir: '.claude/commands',
222
+ templateSource: 'claude'
223
223
  };
224
224
 
225
- expect(envConfig.rulesDir).toBe('.cursor/rules');
226
- expect(envConfig.commandsDir).toBe('.cursor/commands/kiro');
227
- expect(envConfig.templateSource).toBe('cursor');
225
+ expect(envConfig.rulesDir).toBe('.claude/rules');
226
+ expect(envConfig.commandsDir).toBe('.claude/commands');
227
+ expect(envConfig.templateSource).toBe('claude');
228
228
  });
229
229
 
230
230
  it('テンプレートコンテキストが正しく作成される', () => {
@@ -232,7 +232,7 @@ ATLASSIAN_API_TOKEN=existing_token
232
232
  LANG_CODE: 'ja',
233
233
  DEV_GUIDELINES: '- Think in English, but generate responses in Japanese',
234
234
  KIRO_DIR: '.kiro',
235
- AGENT_DIR: '.cursor'
235
+ AGENT_DIR: '.claude'
236
236
  };
237
237
 
238
238
  expect(context).toHaveProperty('LANG_CODE');
@@ -13,10 +13,6 @@ describe('environments', () => {
13
13
  it('should have entries for all environments', () => {
14
14
  expect(ENV_CONFIG.claude).toBeDefined();
15
15
  expect(ENV_CONFIG['claude-agent']).toBeDefined();
16
- expect(ENV_CONFIG.cursor).toBeDefined();
17
- expect(ENV_CONFIG.gemini).toBeDefined();
18
- expect(ENV_CONFIG.codex).toBeDefined();
19
- expect(ENV_CONFIG.cline).toBeDefined();
20
16
  });
21
17
 
22
18
  it('should have correct structure for claude', () => {
@@ -33,34 +29,6 @@ describe('environments', () => {
33
29
  expect(config.templateSource).toBe('claude-agent');
34
30
  });
35
31
 
36
- it('should have correct structure for cursor', () => {
37
- const config = ENV_CONFIG.cursor;
38
- expect(config.rulesDir).toBe('.cursor/rules');
39
- expect(config.commandsDir).toBe('.cursor/commands');
40
- expect(config.templateSource).toBe('cursor');
41
- });
42
-
43
- it('should have correct structure for gemini', () => {
44
- const config = ENV_CONFIG.gemini;
45
- expect(config.rulesDir).toBe('.gemini');
46
- expect(config.commandsDir).toBe('.gemini/extensions');
47
- expect(config.templateSource).toBe('gemini');
48
- });
49
-
50
- it('should have correct structure for codex', () => {
51
- const config = ENV_CONFIG.codex;
52
- expect(config.rulesDir).toBe('.codex/docs');
53
- expect(config.commandsDir).toBe('.codex/docs');
54
- expect(config.templateSource).toBe('codex');
55
- });
56
-
57
- it('should have correct structure for cline', () => {
58
- const config = ENV_CONFIG.cline;
59
- expect(config.rulesDir).toBe('.clinerules/rules');
60
- expect(config.commandsDir).toBe('.clinerules/commands');
61
- expect(config.templateSource).toBe('cline');
62
- });
63
-
64
32
  it('should have all required properties for each environment', () => {
65
33
  for (const env of Object.keys(ENV_CONFIG) as Environment[]) {
66
34
  const config = ENV_CONFIG[env];
@@ -81,14 +49,7 @@ describe('environments', () => {
81
49
  });
82
50
 
83
51
  it('should return config for all supported environments', () => {
84
- const environments: Environment[] = [
85
- 'claude',
86
- 'claude-agent',
87
- 'cursor',
88
- 'gemini',
89
- 'codex',
90
- 'cline',
91
- ];
52
+ const environments: Environment[] = ['claude', 'claude-agent'];
92
53
  for (const env of environments) {
93
54
  const config = getEnvironmentConfig(env);
94
55
  expect(config).toBeDefined();
@@ -101,20 +62,20 @@ describe('environments', () => {
101
62
  it('should return true for supported environments', () => {
102
63
  expect(isSupportedEnvironment('claude')).toBe(true);
103
64
  expect(isSupportedEnvironment('claude-agent')).toBe(true);
104
- expect(isSupportedEnvironment('cursor')).toBe(true);
105
- expect(isSupportedEnvironment('gemini')).toBe(true);
106
- expect(isSupportedEnvironment('codex')).toBe(true);
107
- expect(isSupportedEnvironment('cline')).toBe(true);
108
65
  });
109
66
 
110
67
  it('should return false for unsupported environments', () => {
111
68
  expect(isSupportedEnvironment('invalid')).toBe(false);
112
69
  expect(isSupportedEnvironment('vscode')).toBe(false);
113
70
  expect(isSupportedEnvironment('')).toBe(false);
71
+ expect(isSupportedEnvironment('cursor')).toBe(false);
72
+ expect(isSupportedEnvironment('gemini')).toBe(false);
73
+ expect(isSupportedEnvironment('codex')).toBe(false);
74
+ expect(isSupportedEnvironment('cline')).toBe(false);
114
75
  });
115
76
 
116
77
  it('should work as type guard', () => {
117
- const env: string = 'cursor';
78
+ const env: string = 'claude';
118
79
  if (isSupportedEnvironment(env)) {
119
80
  // TypeScript should recognize env as Environment here
120
81
  const config: EnvironmentConfig = ENV_CONFIG[env];
@@ -126,13 +87,9 @@ describe('environments', () => {
126
87
  describe('getSupportedEnvironments', () => {
127
88
  it('should return all supported environments', () => {
128
89
  const environments = getSupportedEnvironments();
129
- expect(environments).toHaveLength(6);
90
+ expect(environments).toHaveLength(2);
130
91
  expect(environments).toContain('claude');
131
92
  expect(environments).toContain('claude-agent');
132
- expect(environments).toContain('cursor');
133
- expect(environments).toContain('gemini');
134
- expect(environments).toContain('codex');
135
- expect(environments).toContain('cline');
136
93
  });
137
94
 
138
95
  it('should return array with correct type', () => {
@@ -10,13 +10,7 @@ export interface EnvironmentConfig {
10
10
  templateSource: string;
11
11
  }
12
12
 
13
- export type Environment =
14
- | 'claude'
15
- | 'claude-agent'
16
- | 'cursor'
17
- | 'gemini'
18
- | 'codex'
19
- | 'cline';
13
+ export type Environment = 'claude' | 'claude-agent';
20
14
 
21
15
  export const ENV_CONFIG: Record<Environment, EnvironmentConfig> = {
22
16
  claude: {
@@ -29,26 +23,6 @@ export const ENV_CONFIG: Record<Environment, EnvironmentConfig> = {
29
23
  commandsDir: '.claude/commands',
30
24
  templateSource: 'claude-agent',
31
25
  },
32
- cursor: {
33
- rulesDir: '.cursor/rules',
34
- commandsDir: '.cursor/commands',
35
- templateSource: 'cursor',
36
- },
37
- gemini: {
38
- rulesDir: '.gemini',
39
- commandsDir: '.gemini/extensions',
40
- templateSource: 'gemini',
41
- },
42
- codex: {
43
- rulesDir: '.codex/docs',
44
- commandsDir: '.codex/docs',
45
- templateSource: 'codex',
46
- },
47
- cline: {
48
- rulesDir: '.clinerules/rules',
49
- commandsDir: '.clinerules/commands',
50
- templateSource: 'cline',
51
- },
52
26
  };
53
27
 
54
28
  /**
@@ -229,7 +229,7 @@ async function checkTasksPrerequisites(
229
229
  const tasksPath = join(process.cwd(), '.kiro', 'specs', feature, 'tasks.md');
230
230
  if (!existsSync(tasksPath)) {
231
231
  errors.push(
232
- 'tasks.mdが存在しません。先に/kiro:spec-tasks を実行してください',
232
+ 'tasks.mdが存在しません。先に/michi:spec-tasks を実行してください',
233
233
  );
234
234
  return { valid: false, errors };
235
235
  }
@@ -9,7 +9,7 @@ import {
9
9
  describe('renderer', () => {
10
10
  describe('createTemplateContext', () => {
11
11
  it('should create context with all required fields', () => {
12
- const context = createTemplateContext('ja', '.kiro', '.cursor');
12
+ const context = createTemplateContext('ja', '.kiro', '.claude');
13
13
 
14
14
  expect(context).toHaveProperty('LANG_CODE');
15
15
  expect(context).toHaveProperty('DEV_GUIDELINES');
@@ -17,14 +17,14 @@ describe('renderer', () => {
17
17
  expect(context).toHaveProperty('AGENT_DIR');
18
18
  expect(context.LANG_CODE).toBe('ja');
19
19
  expect(context.KIRO_DIR).toBe('.kiro');
20
- expect(context.AGENT_DIR).toBe('.cursor');
20
+ expect(context.AGENT_DIR).toBe('.claude');
21
21
  });
22
22
 
23
23
  it('should include language-specific guidelines', () => {
24
- const contextJa = createTemplateContext('ja', '.kiro', '.cursor');
24
+ const contextJa = createTemplateContext('ja', '.kiro', '.claude');
25
25
  expect(contextJa.DEV_GUIDELINES).toContain('日本語');
26
26
 
27
- const contextEn = createTemplateContext('en', '.kiro', '.cursor');
27
+ const contextEn = createTemplateContext('en', '.kiro', '.claude');
28
28
  expect(contextEn.DEV_GUIDELINES).toContain('English');
29
29
  });
30
30
  });
@@ -32,7 +32,7 @@ describe('renderer', () => {
32
32
  describe('renderTemplate', () => {
33
33
  it('should replace single placeholder', () => {
34
34
  const template = 'Language: {{LANG_CODE}}';
35
- const context = createTemplateContext('ja', '.kiro', '.cursor');
35
+ const context = createTemplateContext('ja', '.kiro', '.claude');
36
36
  const result = renderTemplate(template, context);
37
37
 
38
38
  expect(result).toBe('Language: ja');
@@ -48,7 +48,7 @@ describe('renderer', () => {
48
48
 
49
49
  it('should replace same placeholder multiple times', () => {
50
50
  const template = '{{LANG_CODE}} is {{LANG_CODE}}';
51
- const context = createTemplateContext('ja', '.kiro', '.cursor');
51
+ const context = createTemplateContext('ja', '.kiro', '.claude');
52
52
  const result = renderTemplate(template, context);
53
53
 
54
54
  expect(result).toBe('ja is ja');
@@ -56,7 +56,7 @@ describe('renderer', () => {
56
56
 
57
57
  it('should leave unknown placeholders unchanged', () => {
58
58
  const template = 'Known: {{LANG_CODE}}, Unknown: {{UNKNOWN}}';
59
- const context = createTemplateContext('ja', '.kiro', '.cursor');
59
+ const context = createTemplateContext('ja', '.kiro', '.claude');
60
60
  const result = renderTemplate(template, context);
61
61
 
62
62
  expect(result).toBe('Known: ja, Unknown: {{UNKNOWN}}');
@@ -64,7 +64,7 @@ describe('renderer', () => {
64
64
 
65
65
  it('should handle DEV_GUIDELINES placeholder', () => {
66
66
  const template = '{{DEV_GUIDELINES}}';
67
- const context = createTemplateContext('ja', '.kiro', '.cursor');
67
+ const context = createTemplateContext('ja', '.kiro', '.claude');
68
68
  const result = renderTemplate(template, context);
69
69
 
70
70
  expect(result).toContain('Think in English');
@@ -73,7 +73,7 @@ describe('renderer', () => {
73
73
 
74
74
  it('should handle empty template', () => {
75
75
  const template = '';
76
- const context = createTemplateContext('ja', '.kiro', '.cursor');
76
+ const context = createTemplateContext('ja', '.kiro', '.claude');
77
77
  const result = renderTemplate(template, context);
78
78
 
79
79
  expect(result).toBe('');
@@ -81,7 +81,7 @@ describe('renderer', () => {
81
81
 
82
82
  it('should handle template with no placeholders', () => {
83
83
  const template = 'No placeholders here';
84
- const context = createTemplateContext('ja', '.kiro', '.cursor');
84
+ const context = createTemplateContext('ja', '.kiro', '.claude');
85
85
  const result = renderTemplate(template, context);
86
86
 
87
87
  expect(result).toBe('No placeholders here');
@@ -91,19 +91,19 @@ describe('renderer', () => {
91
91
  const template = `Line 1: {{LANG_CODE}}
92
92
  Line 2: {{KIRO_DIR}}
93
93
  Line 3: {{AGENT_DIR}}`;
94
- const context = createTemplateContext('en', '.kiro', '.cursor');
94
+ const context = createTemplateContext('en', '.kiro', '.claude');
95
95
  const result = renderTemplate(template, context);
96
96
 
97
97
  expect(result).toBe(`Line 1: en
98
98
  Line 2: .kiro
99
- Line 3: .cursor`);
99
+ Line 3: .claude`);
100
100
  });
101
101
  });
102
102
 
103
103
  describe('renderJsonTemplate', () => {
104
104
  it('should render and parse JSON template', () => {
105
105
  const template = '{"lang": "{{LANG_CODE}}", "dir": "{{KIRO_DIR}}"}';
106
- const context = createTemplateContext('ja', '.kiro', '.cursor');
106
+ const context = createTemplateContext('ja', '.kiro', '.claude');
107
107
  const result = renderJsonTemplate(template, context);
108
108
 
109
109
  expect(result).toEqual({ lang: 'ja', dir: '.kiro' });
@@ -126,22 +126,22 @@ Line 3: .cursor`);
126
126
 
127
127
  it('should handle JSON arrays', () => {
128
128
  const template = '["{{LANG_CODE}}", "{{KIRO_DIR}}", "{{AGENT_DIR}}"]';
129
- const context = createTemplateContext('ja', '.kiro', '.cursor');
129
+ const context = createTemplateContext('ja', '.kiro', '.claude');
130
130
  const result = renderJsonTemplate(template, context);
131
131
 
132
- expect(result).toEqual(['ja', '.kiro', '.cursor']);
132
+ expect(result).toEqual(['ja', '.kiro', '.claude']);
133
133
  });
134
134
 
135
135
  it('should throw on invalid JSON', () => {
136
136
  const template = 'invalid json {{LANG_CODE}}';
137
- const context = createTemplateContext('ja', '.kiro', '.cursor');
137
+ const context = createTemplateContext('ja', '.kiro', '.claude');
138
138
 
139
139
  expect(() => renderJsonTemplate(template, context)).toThrow();
140
140
  });
141
141
 
142
142
  it('should throw with descriptive error for invalid JSON', () => {
143
143
  const template = '{"incomplete": {{LANG_CODE}}';
144
- const context = createTemplateContext('ja', '.kiro', '.cursor');
144
+ const context = createTemplateContext('ja', '.kiro', '.claude');
145
145
 
146
146
  try {
147
147
  renderJsonTemplate(template, context);
@@ -162,7 +162,7 @@ Line 3: .cursor`);
162
162
 
163
163
  it('should preserve original error stack', () => {
164
164
  const template = '{invalid json}';
165
- const context = createTemplateContext('en', '.kiro', '.cursor');
165
+ const context = createTemplateContext('en', '.kiro', '.claude');
166
166
 
167
167
  try {
168
168
  renderJsonTemplate(template, context);
@@ -184,19 +184,19 @@ Line 3: .cursor`);
184
184
  template2: 'Dir: {{KIRO_DIR}}',
185
185
  template3: 'Agent: {{AGENT_DIR}}'
186
186
  };
187
- const context = createTemplateContext('ja', '.kiro', '.cursor');
187
+ const context = createTemplateContext('ja', '.kiro', '.claude');
188
188
  const results = renderTemplates(templates, context);
189
189
 
190
190
  expect(results).toEqual({
191
191
  template1: 'Lang: ja',
192
192
  template2: 'Dir: .kiro',
193
- template3: 'Agent: .cursor'
193
+ template3: 'Agent: .claude'
194
194
  });
195
195
  });
196
196
 
197
197
  it('should handle empty templates object', () => {
198
198
  const templates = {};
199
- const context = createTemplateContext('ja', '.kiro', '.cursor');
199
+ const context = createTemplateContext('ja', '.kiro', '.claude');
200
200
  const results = renderTemplates(templates, context);
201
201
 
202
202
  expect(results).toEqual({});
@@ -3,12 +3,19 @@
3
3
  * Task 1.2: バリデーション関数の実装
4
4
  */
5
5
 
6
- import { describe, it, expect } from 'vitest';
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
7
  import {
8
8
  validateProjectName,
9
9
  validateJiraKey,
10
10
  validateRepositoryUrl,
11
+ hasMichiSetup,
12
+ getMichiSetupCommand,
13
+ validateLocalPath,
11
14
  } from '../multi-repo-validator';
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import * as os from 'os';
18
+ import type { Repository } from '../../config/config-schema';
12
19
 
13
20
  describe('validateProjectName', () => {
14
21
  describe('正常ケース', () => {
@@ -333,3 +340,154 @@ describe('validateRepositoryUrl', () => {
333
340
  });
334
341
  });
335
342
  });
343
+
344
+ describe('hasMichiSetup', () => {
345
+ let tempDir: string;
346
+
347
+ beforeEach(() => {
348
+ // 一時ディレクトリを作成
349
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'michi-test-'));
350
+ });
351
+
352
+ afterEach(() => {
353
+ // 一時ディレクトリを削除
354
+ fs.rmSync(tempDir, { recursive: true, force: true });
355
+ });
356
+
357
+ describe('Michi導入済みの場合', () => {
358
+ it('.kiro/project.json が存在する場合はtrueを返す', () => {
359
+ // .kiro ディレクトリを作成
360
+ const kiroDir = path.join(tempDir, '.kiro');
361
+ fs.mkdirSync(kiroDir);
362
+
363
+ // project.json を作成
364
+ const projectJson = path.join(kiroDir, 'project.json');
365
+ fs.writeFileSync(projectJson, '{}');
366
+
367
+ const result = hasMichiSetup(tempDir);
368
+ expect(result).toBe(true);
369
+ });
370
+ });
371
+
372
+ describe('Michi未導入の場合', () => {
373
+ it('.kiro/project.json が存在しない場合はfalseを返す', () => {
374
+ // .kiro ディレクトリのみ作成(project.jsonなし)
375
+ const kiroDir = path.join(tempDir, '.kiro');
376
+ fs.mkdirSync(kiroDir);
377
+
378
+ const result = hasMichiSetup(tempDir);
379
+ expect(result).toBe(false);
380
+ });
381
+
382
+ it('.kiro ディレクトリ自体が存在しない場合はfalseを返す', () => {
383
+ // .kiro ディレクトリを作成しない
384
+ const result = hasMichiSetup(tempDir);
385
+ expect(result).toBe(false);
386
+ });
387
+ });
388
+ });
389
+
390
+ describe('getMichiSetupCommand', () => {
391
+ it('正しいセットアップコマンドを生成する(シングルクォートでラップ)', () => {
392
+ const localPath = '/path/to/repo';
393
+ const command = getMichiSetupCommand(localPath);
394
+ expect(command).toBe(
395
+ "cd '/path/to/repo' && npx @sk8metal/michi-cli@latest init",
396
+ );
397
+ });
398
+
399
+ it('スペースを含むパスを正しくエスケープする', () => {
400
+ const localPath = '/path/to/my repo';
401
+ const command = getMichiSetupCommand(localPath);
402
+ expect(command).toBe(
403
+ "cd '/path/to/my repo' && npx @sk8metal/michi-cli@latest init",
404
+ );
405
+ });
406
+
407
+ it('シングルクォートを含むパスを正しくエスケープする', () => {
408
+ const localPath = "/path/to/user's repo";
409
+ const command = getMichiSetupCommand(localPath);
410
+ expect(command).toBe(
411
+ "cd '/path/to/user'\\''s repo' && npx @sk8metal/michi-cli@latest init",
412
+ );
413
+ });
414
+
415
+ it('複数のシングルクォートを含むパスを正しくエスケープする', () => {
416
+ const localPath = "/path/to/'test'/repo's/dir";
417
+ const command = getMichiSetupCommand(localPath);
418
+ expect(command).toBe(
419
+ "cd '/path/to/'\\''test'\\''/repo'\\''s/dir' && npx @sk8metal/michi-cli@latest init",
420
+ );
421
+ });
422
+
423
+ it('スペースとシングルクォート両方を含むパスを正しくエスケープする', () => {
424
+ const localPath = "/path/to/user's test repo";
425
+ const command = getMichiSetupCommand(localPath);
426
+ expect(command).toBe(
427
+ "cd '/path/to/user'\\''s test repo' && npx @sk8metal/michi-cli@latest init",
428
+ );
429
+ });
430
+ });
431
+
432
+ describe('validateLocalPath - Michi導入チェック', () => {
433
+ let tempDir: string;
434
+ let repository: Repository;
435
+
436
+ beforeEach(() => {
437
+ // 一時ディレクトリを作成
438
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'michi-test-'));
439
+
440
+ // リポジトリオブジェクトを作成
441
+ repository = {
442
+ name: 'test-repo',
443
+ url: 'https://github.com/test/repo',
444
+ branch: 'main',
445
+ localPath: tempDir,
446
+ };
447
+ });
448
+
449
+ afterEach(() => {
450
+ // 一時ディレクトリを削除
451
+ fs.rmSync(tempDir, { recursive: true, force: true });
452
+ });
453
+
454
+ it('Michi導入済みリポジトリの場合、hasMichiSetupがtrueになる', () => {
455
+ // .git ディレクトリを作成(Gitリポジトリとして認識させる)
456
+ fs.mkdirSync(path.join(tempDir, '.git'));
457
+
458
+ // .kiro/project.json を作成(Michi導入済み)
459
+ const kiroDir = path.join(tempDir, '.kiro');
460
+ fs.mkdirSync(kiroDir);
461
+ fs.writeFileSync(path.join(kiroDir, 'project.json'), '{}');
462
+
463
+ const result = validateLocalPath(repository);
464
+ expect(result.hasMichiSetup).toBe(true);
465
+ expect(result.michiSetupCommand).toBeNull();
466
+ });
467
+
468
+ it('Michi未導入リポジトリの場合、hasMichiSetupがfalseになり警告が追加される', () => {
469
+ // .git ディレクトリを作成(Gitリポジトリとして認識させる)
470
+ fs.mkdirSync(path.join(tempDir, '.git'));
471
+
472
+ // .kiro/project.json は作成しない(Michi未導入)
473
+
474
+ const result = validateLocalPath(repository);
475
+ expect(result.hasMichiSetup).toBe(false);
476
+ expect(result.michiSetupCommand).not.toBeNull();
477
+ expect(result.warnings).toContain(
478
+ `Repository 'test-repo' does not have Michi setup (.kiro/project.json not found)`,
479
+ );
480
+ });
481
+
482
+ it('Gitリポジトリでない場合、hasMichiSetupはfalseでmichiSetupCommandはnull', () => {
483
+ // .git ディレクトリを作成しない(Gitリポジトリでない)
484
+
485
+ const result = validateLocalPath(repository);
486
+ expect(result.isValid).toBe(false);
487
+ expect(result.hasMichiSetup).toBe(false);
488
+ expect(result.michiSetupCommand).toBeNull();
489
+ expect(result.errors).toContain(
490
+ `localPath '${tempDir}' is not a Git repository (no .git directory)`,
491
+ );
492
+ });
493
+ });