@sk8metal/michi-cli 0.0.8 → 0.0.9
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 +33 -0
- package/README.md +202 -9
- package/dist/scripts/__tests__/setup-existing-project.test.js +67 -5
- package/dist/scripts/__tests__/setup-existing-project.test.js.map +1 -1
- package/dist/scripts/constants/__tests__/environments.test.d.ts +2 -0
- package/dist/scripts/constants/__tests__/environments.test.d.ts.map +1 -0
- package/dist/scripts/constants/__tests__/environments.test.js +91 -0
- package/dist/scripts/constants/__tests__/environments.test.js.map +1 -0
- package/dist/scripts/constants/__tests__/languages.test.d.ts +2 -0
- package/dist/scripts/constants/__tests__/languages.test.d.ts.map +1 -0
- package/dist/scripts/constants/__tests__/languages.test.js +82 -0
- package/dist/scripts/constants/__tests__/languages.test.js.map +1 -0
- package/dist/scripts/constants/environments.d.ts +33 -0
- package/dist/scripts/constants/environments.d.ts.map +1 -0
- package/dist/scripts/constants/environments.js +49 -0
- package/dist/scripts/constants/environments.js.map +1 -0
- package/dist/scripts/constants/languages.d.ts +23 -0
- package/dist/scripts/constants/languages.d.ts.map +1 -0
- package/dist/scripts/constants/languages.js +53 -0
- package/dist/scripts/constants/languages.js.map +1 -0
- package/dist/scripts/create-project.d.ts +4 -0
- package/dist/scripts/create-project.d.ts.map +1 -1
- package/dist/scripts/create-project.js +51 -22
- package/dist/scripts/create-project.js.map +1 -1
- package/dist/scripts/setup-existing-project.d.ts +3 -1
- package/dist/scripts/setup-existing-project.d.ts.map +1 -1
- package/dist/scripts/setup-existing-project.js +110 -55
- package/dist/scripts/setup-existing-project.js.map +1 -1
- package/dist/scripts/template/__tests__/renderer.test.d.ts +2 -0
- package/dist/scripts/template/__tests__/renderer.test.d.ts.map +1 -0
- package/dist/scripts/template/__tests__/renderer.test.js +165 -0
- package/dist/scripts/template/__tests__/renderer.test.js.map +1 -0
- package/dist/scripts/template/renderer.d.ts +70 -0
- package/dist/scripts/template/renderer.d.ts.map +1 -0
- package/dist/scripts/template/renderer.js +99 -0
- package/dist/scripts/template/renderer.js.map +1 -0
- package/dist/scripts/utils/template-finder.d.ts +37 -0
- package/dist/scripts/utils/template-finder.d.ts.map +1 -0
- package/dist/scripts/utils/template-finder.js +63 -0
- package/dist/scripts/utils/template-finder.js.map +1 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.js +125 -0
- package/dist/src/__tests__/integration/setup/claude-agent.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/claude.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/claude.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/claude.test.js +111 -0
- package/dist/src/__tests__/integration/setup/claude.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/cursor.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/cursor.test.js +162 -0
- package/dist/src/__tests__/integration/setup/cursor.test.js.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts +32 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js +72 -0
- package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts +38 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.js +83 -0
- package/dist/src/__tests__/integration/setup/helpers/test-project.js.map +1 -0
- package/dist/src/__tests__/integration/setup/validation.test.d.ts +5 -0
- package/dist/src/__tests__/integration/setup/validation.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/setup/validation.test.js +318 -0
- package/dist/src/__tests__/integration/setup/validation.test.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +20 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/setup-existing.d.ts +22 -0
- package/dist/src/commands/setup-existing.d.ts.map +1 -0
- package/dist/src/commands/setup-existing.js +408 -0
- package/dist/src/commands/setup-existing.js.map +1 -0
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +4 -3
- package/dist/vitest.config.js.map +1 -1
- package/docs/getting-started/new-repository-setup.md +108 -28
- package/docs/getting-started/quick-start.md +57 -6
- package/docs/getting-started/setup.md +250 -2
- package/docs/guides/customization.md +3 -3
- package/docs/guides/multi-project.md +1 -1
- package/docs/reference/quick-reference.md +25 -16
- package/docs/testing/integration-tests.md +297 -0
- package/package.json +7 -2
- package/scripts/__tests__/setup-existing-project.test.ts +73 -5
- package/scripts/constants/__tests__/environments.test.ts +110 -0
- package/scripts/constants/__tests__/languages.test.ts +100 -0
- package/scripts/constants/environments.ts +61 -0
- package/scripts/constants/languages.ts +70 -0
- package/scripts/create-project.ts +52 -22
- package/scripts/setup-existing-project.ts +136 -57
- package/scripts/setup-existing.sh +130 -3
- package/scripts/template/__tests__/renderer.test.ts +207 -0
- package/scripts/template/renderer.ts +133 -0
- package/scripts/utils/template-finder.ts +75 -0
- package/templates/claude/commands/michi/confluence-sync.md +38 -0
- package/templates/claude/commands/michi/project-switch.md +36 -0
- package/templates/claude/rules/atlassian-integration.md +35 -0
- package/templates/claude/rules/michi-core.md +54 -0
- package/templates/claude-agent/README.md +25 -0
- package/templates/cursor/commands/michi/confluence-sync.md +76 -0
- package/templates/cursor/commands/michi/project-switch.md +69 -0
- package/templates/cursor/rules/atlassian-mcp.mdc +188 -0
- package/templates/cursor/rules/github-ssot.mdc +151 -0
- package/templates/cursor/rules/multi-project.mdc +81 -0
- package/scripts/setup-env.sh +0 -52
|
@@ -2,19 +2,143 @@
|
|
|
2
2
|
#
|
|
3
3
|
# 既存プロジェクトにMichiワークフローを追加(簡易版)
|
|
4
4
|
#
|
|
5
|
+
# ⚠️ 非推奨: このスクリプトは非推奨です
|
|
6
|
+
# 代わりに以下を使用してください:
|
|
7
|
+
# npx @sk8metal/michi-cli setup-existing --cursor --lang ja
|
|
8
|
+
#
|
|
5
9
|
# 使い方:
|
|
6
10
|
# cd /path/to/existing-repo
|
|
7
|
-
# bash /path/to/michi/scripts/setup-existing.sh
|
|
11
|
+
# bash /path/to/michi/scripts/setup-existing.sh [options]
|
|
12
|
+
#
|
|
13
|
+
# オプション:
|
|
14
|
+
# --claude Claude Code環境を使用(デフォルト)
|
|
15
|
+
# --claude-agent Claude Code Subagents環境を使用
|
|
16
|
+
# --cursor Cursor IDE環境を使用
|
|
17
|
+
# --lang <code> 言語コード(デフォルト: ja)
|
|
18
|
+
# --help ヘルプメッセージを表示
|
|
8
19
|
#
|
|
9
20
|
|
|
10
21
|
set -e
|
|
11
22
|
|
|
23
|
+
# 非推奨警告を表示
|
|
24
|
+
echo ""
|
|
25
|
+
echo "⚠️ Warning: This script is deprecated."
|
|
26
|
+
echo " Please use: npx @sk8metal/michi-cli setup-existing --cursor --lang ja"
|
|
27
|
+
echo ""
|
|
28
|
+
echo " Benefits of the new command:"
|
|
29
|
+
echo " - No git clone required"
|
|
30
|
+
echo " - Always uses the latest version"
|
|
31
|
+
echo " - Cross-platform support"
|
|
32
|
+
echo " - Templates included in NPM package"
|
|
33
|
+
echo ""
|
|
34
|
+
read -p "Continue with this deprecated script? [y/N]: " -n 1 -r
|
|
35
|
+
echo
|
|
36
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
37
|
+
echo "Cancelled. Please use: npx @sk8metal/michi-cli setup-existing"
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
echo ""
|
|
41
|
+
|
|
12
42
|
# 色付きメッセージ
|
|
13
43
|
GREEN='\033[0;32m'
|
|
14
44
|
YELLOW='\033[1;33m'
|
|
15
45
|
BLUE='\033[0;34m'
|
|
46
|
+
RED='\033[0;31m'
|
|
16
47
|
NC='\033[0m' # No Color
|
|
17
48
|
|
|
49
|
+
# デフォルト値
|
|
50
|
+
ENVIRONMENT="claude"
|
|
51
|
+
LANG_CODE="ja"
|
|
52
|
+
|
|
53
|
+
# v0.0.9 サポート環境
|
|
54
|
+
SUPPORTED_ENVIRONMENTS=("claude" "claude-agent" "cursor")
|
|
55
|
+
# v0.0.9 サポート言語
|
|
56
|
+
SUPPORTED_LANGUAGES=("ja")
|
|
57
|
+
|
|
58
|
+
# ヘルプメッセージを表示
|
|
59
|
+
show_help() {
|
|
60
|
+
echo "Usage: bash setup-existing.sh [options]"
|
|
61
|
+
echo ""
|
|
62
|
+
echo "Options:"
|
|
63
|
+
echo " --claude Use Claude Code environment (default)"
|
|
64
|
+
echo " --claude-agent Use Claude Code Subagents environment"
|
|
65
|
+
echo " --cursor Use Cursor IDE environment"
|
|
66
|
+
echo " --lang <code> Language code (default: ja)"
|
|
67
|
+
echo " --help Show this help message"
|
|
68
|
+
echo ""
|
|
69
|
+
echo "Supported in v0.0.9:"
|
|
70
|
+
echo " Environments: claude, claude-agent, cursor"
|
|
71
|
+
echo " Languages: ja"
|
|
72
|
+
echo ""
|
|
73
|
+
echo "Examples:"
|
|
74
|
+
echo " bash setup-existing.sh # Default (Claude Code, Japanese)"
|
|
75
|
+
echo " bash setup-existing.sh --cursor # Cursor IDE, Japanese"
|
|
76
|
+
echo " bash setup-existing.sh --claude-agent --lang ja # Claude Subagents, Japanese"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# 環境のバリデーション
|
|
80
|
+
validate_environment() {
|
|
81
|
+
local env=$1
|
|
82
|
+
for supported in "${SUPPORTED_ENVIRONMENTS[@]}"; do
|
|
83
|
+
if [[ "$env" == "$supported" ]]; then
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
done
|
|
87
|
+
|
|
88
|
+
echo -e "${RED}❌ Error: Environment '${env}' is not yet supported in v0.0.9${NC}"
|
|
89
|
+
echo "Supported environments: ${SUPPORTED_ENVIRONMENTS[*]}"
|
|
90
|
+
exit 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# 言語のバリデーション
|
|
94
|
+
validate_language() {
|
|
95
|
+
local lang=$1
|
|
96
|
+
for supported in "${SUPPORTED_LANGUAGES[@]}"; do
|
|
97
|
+
if [[ "$lang" == "$supported" ]]; then
|
|
98
|
+
return 0
|
|
99
|
+
fi
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
echo -e "${RED}❌ Error: Language '${lang}' is not yet supported in v0.0.9${NC}"
|
|
103
|
+
echo "Supported languages: ${SUPPORTED_LANGUAGES[*]}"
|
|
104
|
+
exit 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# 引数解析
|
|
108
|
+
while [[ $# -gt 0 ]]; do
|
|
109
|
+
case $1 in
|
|
110
|
+
--claude)
|
|
111
|
+
ENVIRONMENT="claude"
|
|
112
|
+
shift
|
|
113
|
+
;;
|
|
114
|
+
--claude-agent)
|
|
115
|
+
ENVIRONMENT="claude-agent"
|
|
116
|
+
shift
|
|
117
|
+
;;
|
|
118
|
+
--cursor)
|
|
119
|
+
ENVIRONMENT="cursor"
|
|
120
|
+
shift
|
|
121
|
+
;;
|
|
122
|
+
--lang)
|
|
123
|
+
LANG_CODE="$2"
|
|
124
|
+
shift 2
|
|
125
|
+
;;
|
|
126
|
+
--help)
|
|
127
|
+
show_help
|
|
128
|
+
exit 0
|
|
129
|
+
;;
|
|
130
|
+
*)
|
|
131
|
+
echo -e "${RED}❌ Unknown option: $1${NC}"
|
|
132
|
+
echo "Run 'bash setup-existing.sh --help' for usage information."
|
|
133
|
+
exit 1
|
|
134
|
+
;;
|
|
135
|
+
esac
|
|
136
|
+
done
|
|
137
|
+
|
|
138
|
+
# バリデーション
|
|
139
|
+
validate_environment "$ENVIRONMENT"
|
|
140
|
+
validate_language "$LANG_CODE"
|
|
141
|
+
|
|
18
142
|
echo -e "${BLUE}🚀 既存プロジェクトにMichiワークフローを追加${NC}"
|
|
19
143
|
echo ""
|
|
20
144
|
|
|
@@ -24,6 +148,8 @@ MICHI_PATH="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
|
24
148
|
|
|
25
149
|
echo -e "${BLUE}📂 Michiパス: ${MICHI_PATH}${NC}"
|
|
26
150
|
echo -e "${BLUE}📂 現在のディレクトリ: $(pwd)${NC}"
|
|
151
|
+
echo -e "${BLUE}🔧 環境: ${ENVIRONMENT}${NC}"
|
|
152
|
+
echo -e "${BLUE}🌐 言語: ${LANG_CODE}${NC}"
|
|
27
153
|
echo ""
|
|
28
154
|
|
|
29
155
|
# プロジェクト情報を入力
|
|
@@ -74,7 +200,9 @@ echo -e "${BLUE}🔧 セットアップスクリプトを実行...${NC}"
|
|
|
74
200
|
if ! npx tsx "${SETUP_SCRIPT}" \
|
|
75
201
|
--michi-path "${MICHI_PATH}" \
|
|
76
202
|
--project-name "${PROJECT_NAME}" \
|
|
77
|
-
--jira-key "${JIRA_KEY}"
|
|
203
|
+
--jira-key "${JIRA_KEY}" \
|
|
204
|
+
--environment "${ENVIRONMENT}" \
|
|
205
|
+
--lang "${LANG_CODE}"; then
|
|
78
206
|
echo ""
|
|
79
207
|
echo -e "${YELLOW}❌ セットアップスクリプトが失敗しました${NC}"
|
|
80
208
|
exit 1
|
|
@@ -149,4 +277,3 @@ echo " - クイックリファレンス: ${MICHI_PATH}/docs/quick-reference.md
|
|
|
149
277
|
echo ""
|
|
150
278
|
echo -e "${GREEN}✨ 準備完了!開発を始めましょう!${NC}"
|
|
151
279
|
echo ""
|
|
152
|
-
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createTemplateContext,
|
|
4
|
+
renderTemplate,
|
|
5
|
+
renderJsonTemplate,
|
|
6
|
+
renderTemplates,
|
|
7
|
+
type TemplateContext
|
|
8
|
+
} from '../renderer.js';
|
|
9
|
+
|
|
10
|
+
describe('renderer', () => {
|
|
11
|
+
describe('createTemplateContext', () => {
|
|
12
|
+
it('should create context with all required fields', () => {
|
|
13
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
14
|
+
|
|
15
|
+
expect(context).toHaveProperty('LANG_CODE');
|
|
16
|
+
expect(context).toHaveProperty('DEV_GUIDELINES');
|
|
17
|
+
expect(context).toHaveProperty('KIRO_DIR');
|
|
18
|
+
expect(context).toHaveProperty('AGENT_DIR');
|
|
19
|
+
expect(context.LANG_CODE).toBe('ja');
|
|
20
|
+
expect(context.KIRO_DIR).toBe('.kiro');
|
|
21
|
+
expect(context.AGENT_DIR).toBe('.cursor');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should include language-specific guidelines', () => {
|
|
25
|
+
const contextJa = createTemplateContext('ja', '.kiro', '.cursor');
|
|
26
|
+
expect(contextJa.DEV_GUIDELINES).toContain('日本語');
|
|
27
|
+
|
|
28
|
+
const contextEn = createTemplateContext('en', '.kiro', '.cursor');
|
|
29
|
+
expect(contextEn.DEV_GUIDELINES).toContain('English');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('renderTemplate', () => {
|
|
34
|
+
it('should replace single placeholder', () => {
|
|
35
|
+
const template = 'Language: {{LANG_CODE}}';
|
|
36
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
37
|
+
const result = renderTemplate(template, context);
|
|
38
|
+
|
|
39
|
+
expect(result).toBe('Language: ja');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should replace multiple placeholders', () => {
|
|
43
|
+
const template = 'Lang: {{LANG_CODE}}, Kiro: {{KIRO_DIR}}, Agent: {{AGENT_DIR}}';
|
|
44
|
+
const context = createTemplateContext('en', '.kiro', '.claude');
|
|
45
|
+
const result = renderTemplate(template, context);
|
|
46
|
+
|
|
47
|
+
expect(result).toBe('Lang: en, Kiro: .kiro, Agent: .claude');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should replace same placeholder multiple times', () => {
|
|
51
|
+
const template = '{{LANG_CODE}} is {{LANG_CODE}}';
|
|
52
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
53
|
+
const result = renderTemplate(template, context);
|
|
54
|
+
|
|
55
|
+
expect(result).toBe('ja is ja');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should leave unknown placeholders unchanged', () => {
|
|
59
|
+
const template = 'Known: {{LANG_CODE}}, Unknown: {{UNKNOWN}}';
|
|
60
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
61
|
+
const result = renderTemplate(template, context);
|
|
62
|
+
|
|
63
|
+
expect(result).toBe('Known: ja, Unknown: {{UNKNOWN}}');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle DEV_GUIDELINES placeholder', () => {
|
|
67
|
+
const template = '{{DEV_GUIDELINES}}';
|
|
68
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
69
|
+
const result = renderTemplate(template, context);
|
|
70
|
+
|
|
71
|
+
expect(result).toContain('Think in English');
|
|
72
|
+
expect(result).toContain('日本語');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle empty template', () => {
|
|
76
|
+
const template = '';
|
|
77
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
78
|
+
const result = renderTemplate(template, context);
|
|
79
|
+
|
|
80
|
+
expect(result).toBe('');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle template with no placeholders', () => {
|
|
84
|
+
const template = 'No placeholders here';
|
|
85
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
86
|
+
const result = renderTemplate(template, context);
|
|
87
|
+
|
|
88
|
+
expect(result).toBe('No placeholders here');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should handle multiline template', () => {
|
|
92
|
+
const template = `Line 1: {{LANG_CODE}}
|
|
93
|
+
Line 2: {{KIRO_DIR}}
|
|
94
|
+
Line 3: {{AGENT_DIR}}`;
|
|
95
|
+
const context = createTemplateContext('en', '.kiro', '.cursor');
|
|
96
|
+
const result = renderTemplate(template, context);
|
|
97
|
+
|
|
98
|
+
expect(result).toBe(`Line 1: en
|
|
99
|
+
Line 2: .kiro
|
|
100
|
+
Line 3: .cursor`);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('renderJsonTemplate', () => {
|
|
105
|
+
it('should render and parse JSON template', () => {
|
|
106
|
+
const template = '{"lang": "{{LANG_CODE}}", "dir": "{{KIRO_DIR}}"}';
|
|
107
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
108
|
+
const result = renderJsonTemplate(template, context);
|
|
109
|
+
|
|
110
|
+
expect(result).toEqual({ lang: 'ja', dir: '.kiro' });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should handle nested JSON', () => {
|
|
114
|
+
const template = '{"config": {"lang": "{{LANG_CODE}}", "paths": {"kiro": "{{KIRO_DIR}}"}}}';
|
|
115
|
+
const context = createTemplateContext('en', '.kiro', '.claude');
|
|
116
|
+
const result = renderJsonTemplate(template, context);
|
|
117
|
+
|
|
118
|
+
expect(result).toEqual({
|
|
119
|
+
config: {
|
|
120
|
+
lang: 'en',
|
|
121
|
+
paths: {
|
|
122
|
+
kiro: '.kiro'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should handle JSON arrays', () => {
|
|
129
|
+
const template = '["{{LANG_CODE}}", "{{KIRO_DIR}}", "{{AGENT_DIR}}"]';
|
|
130
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
131
|
+
const result = renderJsonTemplate(template, context);
|
|
132
|
+
|
|
133
|
+
expect(result).toEqual(['ja', '.kiro', '.cursor']);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should throw on invalid JSON', () => {
|
|
137
|
+
const template = 'invalid json {{LANG_CODE}}';
|
|
138
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
139
|
+
|
|
140
|
+
expect(() => renderJsonTemplate(template, context)).toThrow();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should throw with descriptive error for invalid JSON', () => {
|
|
144
|
+
const template = '{"incomplete": {{LANG_CODE}}';
|
|
145
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
renderJsonTemplate(template, context);
|
|
149
|
+
expect.fail('Should have thrown an error');
|
|
150
|
+
} catch (error) {
|
|
151
|
+
expect(error).toBeInstanceOf(Error);
|
|
152
|
+
const errorMessage = (error as Error).message;
|
|
153
|
+
|
|
154
|
+
// Should contain descriptive information
|
|
155
|
+
expect(errorMessage).toContain('Failed to parse rendered JSON template');
|
|
156
|
+
expect(errorMessage).toContain('Original error:');
|
|
157
|
+
expect(errorMessage).toContain('Rendered output');
|
|
158
|
+
expect(errorMessage).toContain('Template context');
|
|
159
|
+
expect(errorMessage).toContain('LANG_CODE=ja');
|
|
160
|
+
expect(errorMessage).toContain('KIRO_DIR=.kiro');
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should preserve original error stack', () => {
|
|
165
|
+
const template = '{invalid json}';
|
|
166
|
+
const context = createTemplateContext('en', '.kiro', '.cursor');
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
renderJsonTemplate(template, context);
|
|
170
|
+
expect.fail('Should have thrown an error');
|
|
171
|
+
} catch (error) {
|
|
172
|
+
expect(error).toBeInstanceOf(Error);
|
|
173
|
+
const errorStack = (error as Error).stack;
|
|
174
|
+
|
|
175
|
+
// Should contain original error stack
|
|
176
|
+
expect(errorStack).toContain('Original error stack:');
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('renderTemplates', () => {
|
|
182
|
+
it('should render multiple templates', () => {
|
|
183
|
+
const templates = {
|
|
184
|
+
template1: 'Lang: {{LANG_CODE}}',
|
|
185
|
+
template2: 'Dir: {{KIRO_DIR}}',
|
|
186
|
+
template3: 'Agent: {{AGENT_DIR}}'
|
|
187
|
+
};
|
|
188
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
189
|
+
const results = renderTemplates(templates, context);
|
|
190
|
+
|
|
191
|
+
expect(results).toEqual({
|
|
192
|
+
template1: 'Lang: ja',
|
|
193
|
+
template2: 'Dir: .kiro',
|
|
194
|
+
template3: 'Agent: .cursor'
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should handle empty templates object', () => {
|
|
199
|
+
const templates = {};
|
|
200
|
+
const context = createTemplateContext('ja', '.kiro', '.cursor');
|
|
201
|
+
const results = renderTemplates(templates, context);
|
|
202
|
+
|
|
203
|
+
expect(results).toEqual({});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template renderer with placeholder replacement
|
|
3
|
+
*
|
|
4
|
+
* Issue #37: 環境別コピー実装
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { SupportedLanguage, getDevGuidelines } from '../constants/languages.js';
|
|
8
|
+
|
|
9
|
+
export interface TemplateContext {
|
|
10
|
+
LANG_CODE: SupportedLanguage;
|
|
11
|
+
DEV_GUIDELINES: string;
|
|
12
|
+
KIRO_DIR: string;
|
|
13
|
+
AGENT_DIR: string;
|
|
14
|
+
PROJECT_ID?: string;
|
|
15
|
+
FEATURE_NAME?: string;
|
|
16
|
+
TIMESTAMP?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create template context for rendering
|
|
21
|
+
*
|
|
22
|
+
* @param lang - Language code
|
|
23
|
+
* @param kiroDir - .kiro directory name
|
|
24
|
+
* @param agentDir - Agent directory name (e.g., .cursor, .claude)
|
|
25
|
+
* @returns Template context object
|
|
26
|
+
*/
|
|
27
|
+
export const createTemplateContext = (
|
|
28
|
+
lang: SupportedLanguage,
|
|
29
|
+
kiroDir: string,
|
|
30
|
+
agentDir: string
|
|
31
|
+
): TemplateContext => ({
|
|
32
|
+
LANG_CODE: lang,
|
|
33
|
+
DEV_GUIDELINES: getDevGuidelines(lang),
|
|
34
|
+
KIRO_DIR: kiroDir,
|
|
35
|
+
AGENT_DIR: agentDir,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render template with placeholder replacement
|
|
40
|
+
*
|
|
41
|
+
* Replaces {{KEY}} patterns with values from context
|
|
42
|
+
*
|
|
43
|
+
* @param template - Template string with {{PLACEHOLDER}} patterns
|
|
44
|
+
* @param context - Template context with replacement values
|
|
45
|
+
* @returns Rendered template string
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const template = "Hello {{NAME}}, welcome to {{PLACE}}!";
|
|
50
|
+
* const context = { NAME: "Alice", PLACE: "Wonderland" };
|
|
51
|
+
* const result = renderTemplate(template, context);
|
|
52
|
+
* // Result: "Hello Alice, welcome to Wonderland!"
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const renderTemplate = (
|
|
56
|
+
template: string,
|
|
57
|
+
context: TemplateContext
|
|
58
|
+
): string => {
|
|
59
|
+
return template.replace(/\{\{([A-Z_]+)\}\}/g, (match, key) => {
|
|
60
|
+
const value = context[key as keyof TemplateContext];
|
|
61
|
+
return value !== undefined ? String(value) : match;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Render JSON template with placeholder replacement
|
|
67
|
+
*
|
|
68
|
+
* Parses the template as JSON after placeholder replacement
|
|
69
|
+
*
|
|
70
|
+
* @param template - JSON template string with {{PLACEHOLDER}} patterns
|
|
71
|
+
* @param context - Template context with replacement values
|
|
72
|
+
* @returns Parsed JSON object
|
|
73
|
+
* @throws {Error} If the rendered template is not valid JSON
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const template = '{"lang": "{{LANG_CODE}}", "dir": "{{KIRO_DIR}}"}';
|
|
78
|
+
* const context = { LANG_CODE: "ja", KIRO_DIR: ".kiro" };
|
|
79
|
+
* const result = renderJsonTemplate(template, context);
|
|
80
|
+
* // Result: { lang: "ja", dir: ".kiro" }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export const renderJsonTemplate = <T = any>(
|
|
84
|
+
template: string,
|
|
85
|
+
context: TemplateContext
|
|
86
|
+
): T => {
|
|
87
|
+
const rendered = renderTemplate(template, context);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(rendered);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
93
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
94
|
+
|
|
95
|
+
// Create descriptive error with context for debugging
|
|
96
|
+
const debugInfo = [
|
|
97
|
+
'Failed to parse rendered JSON template',
|
|
98
|
+
`Original error: ${errorMessage}`,
|
|
99
|
+
`Rendered output (first 500 chars): ${rendered.substring(0, 500)}${rendered.length > 500 ? '...' : ''}`,
|
|
100
|
+
`Template context: LANG_CODE=${context.LANG_CODE}, KIRO_DIR=${context.KIRO_DIR}, AGENT_DIR=${context.AGENT_DIR}`
|
|
101
|
+
].join('\n');
|
|
102
|
+
|
|
103
|
+
const detailedError = new Error(debugInfo);
|
|
104
|
+
|
|
105
|
+
// Preserve original error stack for diagnostics
|
|
106
|
+
if (errorStack) {
|
|
107
|
+
detailedError.stack = `${detailedError.stack}\n\nOriginal error stack:\n${errorStack}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
throw detailedError;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Batch render multiple templates
|
|
116
|
+
*
|
|
117
|
+
* @param templates - Map of template names to template strings
|
|
118
|
+
* @param context - Template context with replacement values
|
|
119
|
+
* @returns Map of template names to rendered strings
|
|
120
|
+
*/
|
|
121
|
+
export const renderTemplates = (
|
|
122
|
+
templates: Record<string, string>,
|
|
123
|
+
context: TemplateContext
|
|
124
|
+
): Record<string, string> => {
|
|
125
|
+
const rendered: Record<string, string> = {};
|
|
126
|
+
|
|
127
|
+
for (const [name, template] of Object.entries(templates)) {
|
|
128
|
+
rendered[name] = renderTemplate(template, context);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return rendered;
|
|
132
|
+
};
|
|
133
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* テンプレートファイル検索ユーティリティ
|
|
3
|
+
*
|
|
4
|
+
* Issue #35: cc-sdd準拠のテンプレート検索
|
|
5
|
+
* Cursor/Claude両環境のテンプレートを優先順位付きで検索
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* templates/ディレクトリからファイルを検索
|
|
13
|
+
*
|
|
14
|
+
* @param michiPath - Michiリポジトリのルートパス
|
|
15
|
+
* @param relativePath - templates/配下の相対パス(例: 'rules/github-ssot.mdc')
|
|
16
|
+
* @returns 見つかったファイルの絶対パス、見つからない場合はnull
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const path = findTemplateFile('/path/to/michi', 'rules/github-ssot.mdc');
|
|
21
|
+
* // returns: '/path/to/michi/templates/cursor/rules/github-ssot.mdc'
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function findTemplateFile(michiPath: string, relativePath: string): string | null {
|
|
25
|
+
// 優先順位1: templates/cursor/
|
|
26
|
+
const cursorPath = join(michiPath, 'templates/cursor', relativePath);
|
|
27
|
+
if (existsSync(cursorPath)) {
|
|
28
|
+
return cursorPath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 優先順位2: templates/claude/
|
|
32
|
+
const claudePath = join(michiPath, 'templates/claude', relativePath);
|
|
33
|
+
if (existsSync(claudePath)) {
|
|
34
|
+
return claudePath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 必須テンプレートファイルのバリデーション
|
|
42
|
+
*
|
|
43
|
+
* @param michiPath - Michiリポジトリのルートパス
|
|
44
|
+
* @param requiredFiles - 必須ファイルのリスト
|
|
45
|
+
* @throws Error - 必須ファイルが見つからない場合
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* validateRequiredTemplates('/path/to/michi', [
|
|
50
|
+
* 'rules/github-ssot.mdc',
|
|
51
|
+
* 'commands/michi/confluence-sync.md'
|
|
52
|
+
* ]);
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function validateRequiredTemplates(
|
|
56
|
+
michiPath: string,
|
|
57
|
+
requiredFiles: string[]
|
|
58
|
+
): void {
|
|
59
|
+
const missingFiles: string[] = [];
|
|
60
|
+
|
|
61
|
+
for (const file of requiredFiles) {
|
|
62
|
+
const path = findTemplateFile(michiPath, file);
|
|
63
|
+
if (!path) {
|
|
64
|
+
missingFiles.push(file);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (missingFiles.length > 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Missing required template files:\n - ${missingFiles.join('\n - ')}\n\n` +
|
|
71
|
+
`Please check that Michi templates are properly installed at: ${michiPath}/templates/`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: /michi:confluence-sync
|
|
3
|
+
description: Sync specifications to Confluence
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Confluence Sync Command
|
|
7
|
+
|
|
8
|
+
**Important**: Generate output in language specified in {{KIRO_DIR}}/project.json.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/michi:confluence-sync <feature_name>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Parameters**:
|
|
17
|
+
- `feature_name`: Feature name (e.g., user-auth, payment-api)
|
|
18
|
+
|
|
19
|
+
## Execution Steps
|
|
20
|
+
|
|
21
|
+
1. Read specifications from {{KIRO_DIR}}/specs/{{FEATURE_NAME}}/
|
|
22
|
+
2. Load project metadata from {{KIRO_DIR}}/project.json
|
|
23
|
+
3. Sync requirements.md to Confluence
|
|
24
|
+
4. Sync design.md to Confluence
|
|
25
|
+
5. Create/update Confluence pages with proper labels
|
|
26
|
+
6. Link pages to JIRA Epic (if exists)
|
|
27
|
+
|
|
28
|
+
## Language Handling
|
|
29
|
+
|
|
30
|
+
- Read language from {{KIRO_DIR}}/project.json
|
|
31
|
+
- Generate all output in the specified language
|
|
32
|
+
- Use Confluence labels from project.json for page organization
|
|
33
|
+
|
|
34
|
+
## Project Metadata
|
|
35
|
+
|
|
36
|
+
- Project ID: {{PROJECT_ID}}
|
|
37
|
+
- Kiro directory: {{KIRO_DIR}}
|
|
38
|
+
- Agent directory: {{AGENT_DIR}}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: /michi:project-switch
|
|
3
|
+
description: Switch between projects
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Project Switch Command
|
|
7
|
+
|
|
8
|
+
**Important**: Generate output in language specified in {{KIRO_DIR}}/project.json.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/michi:project-switch <project_id>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Parameters**:
|
|
17
|
+
- `project_id`: Project ID (e.g., customer-a-service-1, michi)
|
|
18
|
+
|
|
19
|
+
**Examples**:
|
|
20
|
+
```
|
|
21
|
+
/michi:project-switch michi
|
|
22
|
+
/michi:project-switch customer-a-service-1
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Execution Steps
|
|
26
|
+
|
|
27
|
+
1. Identify GitHub repository corresponding to project ID
|
|
28
|
+
2. Clone locally (if not cloned) or checkout
|
|
29
|
+
3. Load and display {{KIRO_DIR}}/project.json
|
|
30
|
+
4. Display corresponding Confluence project page URL
|
|
31
|
+
|
|
32
|
+
## Language Handling
|
|
33
|
+
|
|
34
|
+
- Read language from {{KIRO_DIR}}/project.json
|
|
35
|
+
- Generate all output in the specified language
|
|
36
|
+
- Default to English if language field is missing
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Atlassian Integration
|
|
3
|
+
description: Integration rules for Confluence and JIRA via MCP
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Atlassian Integration
|
|
7
|
+
|
|
8
|
+
## Development Guidelines
|
|
9
|
+
{{DEV_GUIDELINES}}
|
|
10
|
+
|
|
11
|
+
## Language
|
|
12
|
+
All generated documents should be in: **{{LANG_CODE}}**
|
|
13
|
+
|
|
14
|
+
## MCP Integration
|
|
15
|
+
|
|
16
|
+
### Confluence Sync
|
|
17
|
+
- Sync specifications from {{KIRO_DIR}}/specs/ to Confluence
|
|
18
|
+
- Use MCP server for Confluence operations
|
|
19
|
+
- Project labels: Check {{KIRO_DIR}}/project.json
|
|
20
|
+
|
|
21
|
+
### JIRA Sync
|
|
22
|
+
- Create Epic and Stories from {{KIRO_DIR}}/specs/{{FEATURE_NAME}}/tasks.md
|
|
23
|
+
- Use JIRA project key: {{PROJECT_ID}}
|
|
24
|
+
- Link Confluence pages automatically
|
|
25
|
+
|
|
26
|
+
## Project Metadata
|
|
27
|
+
- Project ID: {{PROJECT_ID}}
|
|
28
|
+
- Kiro directory: {{KIRO_DIR}}
|
|
29
|
+
- Agent directory: {{AGENT_DIR}}
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
1. Create spec in {{KIRO_DIR}}/specs/{{FEATURE_NAME}}/
|
|
33
|
+
2. Sync to Confluence via MCP
|
|
34
|
+
3. Create JIRA Epic/Stories
|
|
35
|
+
4. Link Confluence pages to JIRA
|