prr-kit 1.0.0 → 1.1.1
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/README.md +235 -226
- package/package.json +60 -60
- package/src/prr/workflows/2-analyze/describe-pr/templates/pr-description.template.md +51 -51
- package/src/prr/workflows/3-review/architecture-review/workflow.yaml +18 -18
- package/src/prr/workflows/3-review/general-review/workflow.yaml +18 -18
- package/src/prr/workflows/3-review/performance-review/workflow.yaml +18 -18
- package/src/prr/workflows/3-review/security-review/workflow.yaml +19 -19
- package/src/prr/workflows/4-improve/improve-code/workflow.yaml +18 -18
- package/src/prr/workflows/6-report/generate-report/templates/review-report.template.md +78 -78
- package/tools/cli/installers/lib/core/installer.js +162 -162
- package/tools/cli/installers/lib/ide/_config-driven.js +70 -0
- package/tools/cli/installers/lib/ide/codex.js +128 -0
- package/tools/cli/installers/lib/ide/github-copilot.js +262 -0
- package/tools/cli/installers/lib/ide/kilo.js +132 -0
- package/tools/cli/installers/lib/ide/manager.js +37 -1
- package/tools/cli/installers/lib/ide/platform-codes.yaml +104 -4
- package/tools/cli/installers/lib/ide/templates/combined/antigravity-agent.md +15 -0
- package/tools/cli/installers/lib/ide/templates/combined/antigravity-workflow.md +8 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-agent.md +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.md +7 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-agent.md +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-agent.md +15 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow.md +6 -0
- package/tools/cli/installers/lib/ide/templates/combined/rovodev-agent.md +10 -0
- package/tools/cli/installers/lib/ide/templates/combined/rovodev-workflow.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/trae-agent.md +11 -0
- package/tools/cli/installers/lib/ide/templates/combined/trae-workflow.md +9 -0
- package/tools/cli/prr-cli.js +36 -36
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot setup handler
|
|
3
|
+
* Creates agents in .github/agents/, prompts in .github/prompts/,
|
|
4
|
+
* and generates copilot-instructions.md
|
|
5
|
+
*/
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { BaseIdeSetup } = require('./_base-ide');
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const { glob } = require('glob');
|
|
10
|
+
const yaml = require('yaml');
|
|
11
|
+
|
|
12
|
+
class GitHubCopilotSetup extends BaseIdeSetup {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('github-copilot', 'GitHub Copilot', false);
|
|
15
|
+
this.configDir = null;
|
|
16
|
+
this.githubDir = '.github';
|
|
17
|
+
this.agentsDir = 'agents';
|
|
18
|
+
this.promptsDir = 'prompts';
|
|
19
|
+
this.detectionPaths = ['.github/copilot-instructions.md', '.github/agents'];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async setup(projectDir, prrDir, options = {}) {
|
|
23
|
+
const githubDir = path.join(projectDir, this.githubDir);
|
|
24
|
+
const agentsDir = path.join(githubDir, this.agentsDir);
|
|
25
|
+
const promptsDir = path.join(githubDir, this.promptsDir);
|
|
26
|
+
await this.ensureDir(agentsDir);
|
|
27
|
+
await this.ensureDir(promptsDir);
|
|
28
|
+
|
|
29
|
+
await this.cleanup(projectDir);
|
|
30
|
+
|
|
31
|
+
const selectedModules = options.selectedModules || ['prr'];
|
|
32
|
+
const allModules = ['core', ...selectedModules.filter((m) => m !== 'core')];
|
|
33
|
+
|
|
34
|
+
let agentCount = 0;
|
|
35
|
+
let promptCount = 0;
|
|
36
|
+
|
|
37
|
+
// Install agent files to .github/agents/
|
|
38
|
+
for (const mod of allModules) {
|
|
39
|
+
const agentsSrcDir = path.join(prrDir, mod, 'agents');
|
|
40
|
+
if (!(await fs.pathExists(agentsSrcDir))) continue;
|
|
41
|
+
|
|
42
|
+
const agentFiles = await glob('**/*.md', { cwd: agentsSrcDir });
|
|
43
|
+
for (const agentFile of agentFiles) {
|
|
44
|
+
const agentPath = path.join(agentsSrcDir, agentFile);
|
|
45
|
+
const agentContent = await fs.readFile(agentPath, 'utf8');
|
|
46
|
+
|
|
47
|
+
if (/^no-launcher:\s*true/m.test(agentContent)) continue;
|
|
48
|
+
|
|
49
|
+
const nameMatch = agentContent.match(/^name:\s*[\"']?([^\"'\n]+)[\"']?/m);
|
|
50
|
+
const descMatch = agentContent.match(/^description:\s*[\"']?([^\"'\n]+)[\"']?/m);
|
|
51
|
+
const agentName = nameMatch?.[1]?.trim() || path.basename(agentFile, '.md');
|
|
52
|
+
const agentDesc = descMatch?.[1]?.trim() || `${agentName} reviewer`;
|
|
53
|
+
const relAgentPath = `${this.prrFolderName}/${mod}/agents/${agentFile}`.replaceAll('\\', '/');
|
|
54
|
+
|
|
55
|
+
const content = this._agentFileContent(agentName, agentDesc, relAgentPath);
|
|
56
|
+
const fileName = `prr-${path.basename(agentFile, '.md')}.agent.md`;
|
|
57
|
+
await fs.writeFile(path.join(agentsDir, fileName), content, 'utf8');
|
|
58
|
+
agentCount++;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Install workflow prompts to .github/prompts/
|
|
63
|
+
for (const mod of allModules) {
|
|
64
|
+
const workflowsSrcDir = path.join(prrDir, mod, 'workflows');
|
|
65
|
+
if (!(await fs.pathExists(workflowsSrcDir))) continue;
|
|
66
|
+
|
|
67
|
+
const workflowFiles = await glob('**/workflow{.md,.yaml}', { cwd: workflowsSrcDir });
|
|
68
|
+
for (const wfFile of workflowFiles) {
|
|
69
|
+
const wfPath = path.join(workflowsSrcDir, wfFile);
|
|
70
|
+
const wfContent = await fs.readFile(wfPath, 'utf8');
|
|
71
|
+
|
|
72
|
+
let wfName = path.basename(path.dirname(wfFile));
|
|
73
|
+
let wfDesc = '';
|
|
74
|
+
|
|
75
|
+
if (wfFile.endsWith('.yaml')) {
|
|
76
|
+
try { const d = yaml.parse(wfContent); wfName = d.name || wfName; wfDesc = d.description || ''; } catch { /* ignore */ }
|
|
77
|
+
} else {
|
|
78
|
+
const normalized = wfContent.replace(/\r\n/g, '\n');
|
|
79
|
+
const fmMatch = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
80
|
+
if (fmMatch) {
|
|
81
|
+
try { const fm = yaml.parse(fmMatch[1]); wfName = fm.name || wfName; wfDesc = fm.description || ''; } catch { /* ignore */ }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const dirParts = path.dirname(wfFile).split(path.sep).filter(Boolean);
|
|
86
|
+
const leafDir = dirParts.at(-1) || wfName;
|
|
87
|
+
const relWfPath = `${this.prrFolderName}/${mod}/workflows/${wfFile}`.replaceAll('\\', '/');
|
|
88
|
+
const isYaml = wfFile.endsWith('.yaml');
|
|
89
|
+
|
|
90
|
+
const content = this._workflowPromptContent(wfName, wfDesc, relWfPath, isYaml);
|
|
91
|
+
const fileName = `prr-${leafDir}.prompt.md`;
|
|
92
|
+
await fs.writeFile(path.join(promptsDir, fileName), content, 'utf8');
|
|
93
|
+
promptCount++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Generate copilot-instructions.md
|
|
98
|
+
await this._generateCopilotInstructions(projectDir, prrDir, options);
|
|
99
|
+
|
|
100
|
+
return { success: true, results: { agents: agentCount, workflows: promptCount } };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_agentFileContent(name, description, agentPath) {
|
|
104
|
+
return `---
|
|
105
|
+
description: '${description.replaceAll("'", "''")}'
|
|
106
|
+
tools: ['read', 'edit', 'search', 'execute']
|
|
107
|
+
disable-model-invocation: true
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified.
|
|
111
|
+
|
|
112
|
+
<agent-activation CRITICAL="TRUE">
|
|
113
|
+
1. LOAD the FULL agent file from {project-root}/${agentPath}
|
|
114
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
115
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
116
|
+
4. DISPLAY the welcome/greeting as instructed
|
|
117
|
+
5. PRESENT the numbered menu
|
|
118
|
+
6. WAIT for user input before proceeding
|
|
119
|
+
</agent-activation>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
_workflowPromptContent(name, description, wfPath, isYaml) {
|
|
124
|
+
const safeDesc = (description || name).replaceAll("'", "''");
|
|
125
|
+
let body;
|
|
126
|
+
if (isYaml) {
|
|
127
|
+
body = `1. Load {project-root}/${this.prrFolderName}/prr/config.yaml and store ALL fields as session variables
|
|
128
|
+
2. Load the workflow engine at {project-root}/${this.prrFolderName}/core/tasks/workflow.xml
|
|
129
|
+
3. Load and execute the workflow at {project-root}/${wfPath} using the engine from step 2`;
|
|
130
|
+
} else {
|
|
131
|
+
body = `1. Load {project-root}/${this.prrFolderName}/prr/config.yaml and store ALL fields as session variables
|
|
132
|
+
2. Load and follow the workflow at {project-root}/${wfPath}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return `---
|
|
136
|
+
description: '${safeDesc}'
|
|
137
|
+
agent: 'agent'
|
|
138
|
+
tools: ['read', 'edit', 'search', 'execute']
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
${body}
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async _generateCopilotInstructions(projectDir, prrDir, options = {}) {
|
|
146
|
+
const configPath = path.join(prrDir, 'prr', 'config.yaml');
|
|
147
|
+
let config = {};
|
|
148
|
+
if (await fs.pathExists(configPath)) {
|
|
149
|
+
try { config = yaml.parse(await fs.readFile(configPath, 'utf8')) || {}; } catch { /* ignore */ }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const prr = this.prrFolderName;
|
|
153
|
+
const section = `# PR Review Kit — Project Instructions
|
|
154
|
+
|
|
155
|
+
## Project Configuration
|
|
156
|
+
|
|
157
|
+
- **User**: ${config.user_name || 'Dev'}
|
|
158
|
+
- **Communication Language**: ${config.communication_language || 'English'}
|
|
159
|
+
- **Target Repo**: ${config.target_repo || '.'}
|
|
160
|
+
- **Output Folder**: ${config.output_folder || '_prr-output'}
|
|
161
|
+
- **Review Output**: ${config.review_output || '_prr-output/reviews'}
|
|
162
|
+
|
|
163
|
+
## PRR Runtime Structure
|
|
164
|
+
|
|
165
|
+
- **Agent definitions**: \`${prr}/core/agents/\` and \`${prr}/prr/agents/\`
|
|
166
|
+
- **Workflow definitions**: \`${prr}/prr/workflows/\` (organized by phase)
|
|
167
|
+
- **Core tasks**: \`${prr}/core/tasks/\`
|
|
168
|
+
- **Module configuration**: \`${prr}/prr/config.yaml\`
|
|
169
|
+
|
|
170
|
+
## Key Conventions
|
|
171
|
+
|
|
172
|
+
- Always load \`${prr}/prr/config.yaml\` before any agent activation or workflow execution
|
|
173
|
+
- MD-based workflows execute directly — load and follow the \`.md\` file
|
|
174
|
+
- YAML-based workflows require the workflow engine — load \`core/tasks/workflow.xml\` first
|
|
175
|
+
- The \`{project-root}\` variable resolves to the workspace root at runtime
|
|
176
|
+
|
|
177
|
+
## Available Agents
|
|
178
|
+
|
|
179
|
+
| Agent | Slash Command | Speciality |
|
|
180
|
+
|-------|--------------|------------|
|
|
181
|
+
| PRR Master | \`/prr-master\` | Orchestrator — routes all workflows |
|
|
182
|
+
| PRR Quick | \`/prr-quick\` | One-command full pipeline |
|
|
183
|
+
|
|
184
|
+
## Slash Commands
|
|
185
|
+
|
|
186
|
+
Use \`#prr-\` in Copilot Chat to access PR Review prompts, or select agents from the agents dropdown.`;
|
|
187
|
+
|
|
188
|
+
const instructionsPath = path.join(projectDir, this.githubDir, 'copilot-instructions.md');
|
|
189
|
+
const markerStart = '<!-- PRR:START -->';
|
|
190
|
+
const markerEnd = '<!-- PRR:END -->';
|
|
191
|
+
const markedContent = `${markerStart}\n${section}\n${markerEnd}`;
|
|
192
|
+
|
|
193
|
+
if (await fs.pathExists(instructionsPath)) {
|
|
194
|
+
const existing = await fs.readFile(instructionsPath, 'utf8');
|
|
195
|
+
const startIdx = existing.indexOf(markerStart);
|
|
196
|
+
const endIdx = existing.indexOf(markerEnd);
|
|
197
|
+
|
|
198
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
199
|
+
const merged = existing.slice(0, startIdx) + markedContent + existing.slice(endIdx + markerEnd.length);
|
|
200
|
+
await fs.writeFile(instructionsPath, merged, 'utf8');
|
|
201
|
+
} else {
|
|
202
|
+
const backupPath = `${instructionsPath}.bak`;
|
|
203
|
+
await fs.copy(instructionsPath, backupPath);
|
|
204
|
+
await fs.writeFile(instructionsPath, `${markedContent}\n`, 'utf8');
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
await fs.writeFile(instructionsPath, `${markedContent}\n`, 'utf8');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async cleanup(projectDir, options = {}) {
|
|
212
|
+
// Clean agents
|
|
213
|
+
const agentsDir = path.join(projectDir, this.githubDir, this.agentsDir);
|
|
214
|
+
if (await fs.pathExists(agentsDir)) {
|
|
215
|
+
const files = await fs.readdir(agentsDir);
|
|
216
|
+
for (const file of files) {
|
|
217
|
+
if (file.startsWith('prr-') && file.endsWith('.agent.md')) {
|
|
218
|
+
await fs.remove(path.join(agentsDir, file));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Clean prompts
|
|
224
|
+
const promptsDir = path.join(projectDir, this.githubDir, this.promptsDir);
|
|
225
|
+
if (await fs.pathExists(promptsDir)) {
|
|
226
|
+
const files = await fs.readdir(promptsDir);
|
|
227
|
+
for (const file of files) {
|
|
228
|
+
if (file.startsWith('prr-') && file.endsWith('.prompt.md')) {
|
|
229
|
+
await fs.remove(path.join(promptsDir, file));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// On uninstall, strip PRR markers from copilot-instructions.md
|
|
235
|
+
if (options.isUninstall) {
|
|
236
|
+
await this._cleanupCopilotInstructions(projectDir);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async _cleanupCopilotInstructions(projectDir) {
|
|
241
|
+
const instructionsPath = path.join(projectDir, this.githubDir, 'copilot-instructions.md');
|
|
242
|
+
if (!(await fs.pathExists(instructionsPath))) return;
|
|
243
|
+
|
|
244
|
+
const content = await fs.readFile(instructionsPath, 'utf8');
|
|
245
|
+
const markerStart = '<!-- PRR:START -->';
|
|
246
|
+
const markerEnd = '<!-- PRR:END -->';
|
|
247
|
+
const startIdx = content.indexOf(markerStart);
|
|
248
|
+
const endIdx = content.indexOf(markerEnd);
|
|
249
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return;
|
|
250
|
+
|
|
251
|
+
const cleaned = content.slice(0, startIdx) + content.slice(endIdx + markerEnd.length);
|
|
252
|
+
if (cleaned.trim().length === 0) {
|
|
253
|
+
await fs.remove(instructionsPath);
|
|
254
|
+
const backupPath = `${instructionsPath}.bak`;
|
|
255
|
+
if (await fs.pathExists(backupPath)) await fs.rename(backupPath, instructionsPath);
|
|
256
|
+
} else {
|
|
257
|
+
await fs.writeFile(instructionsPath, cleaned, 'utf8');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = { GitHubCopilotSetup };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KiloCoder IDE setup handler
|
|
3
|
+
* Creates custom modes in .kilocodemodes + workflow files in .kilocode/workflows/
|
|
4
|
+
*/
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { BaseIdeSetup } = require('./_base-ide');
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const { glob } = require('glob');
|
|
9
|
+
const yaml = require('yaml');
|
|
10
|
+
|
|
11
|
+
class KiloSetup extends BaseIdeSetup {
|
|
12
|
+
constructor() {
|
|
13
|
+
super('kilo', 'KiloCoder', false);
|
|
14
|
+
this.configFile = '.kilocodemodes';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async setup(projectDir, prrDir, options = {}) {
|
|
18
|
+
await this.cleanup(projectDir);
|
|
19
|
+
|
|
20
|
+
const kiloModesPath = path.join(projectDir, this.configFile);
|
|
21
|
+
let config = {};
|
|
22
|
+
if (await fs.pathExists(kiloModesPath)) {
|
|
23
|
+
try { config = yaml.parse(await fs.readFile(kiloModesPath, 'utf8')) || {}; } catch { config = {}; }
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(config.customModes)) config.customModes = [];
|
|
26
|
+
|
|
27
|
+
const selectedModules = options.selectedModules || ['prr'];
|
|
28
|
+
const allModules = ['core', ...selectedModules.filter((m) => m !== 'core')];
|
|
29
|
+
let modeCount = 0;
|
|
30
|
+
|
|
31
|
+
// Add custom modes from agents
|
|
32
|
+
for (const mod of allModules) {
|
|
33
|
+
const agentsSrcDir = path.join(prrDir, mod, 'agents');
|
|
34
|
+
if (!(await fs.pathExists(agentsSrcDir))) continue;
|
|
35
|
+
|
|
36
|
+
const agentFiles = await glob('**/*.md', { cwd: agentsSrcDir });
|
|
37
|
+
for (const agentFile of agentFiles) {
|
|
38
|
+
const agentContent = await fs.readFile(path.join(agentsSrcDir, agentFile), 'utf8');
|
|
39
|
+
if (/^no-launcher:\s*true/m.test(agentContent)) continue;
|
|
40
|
+
|
|
41
|
+
const nameMatch = agentContent.match(/^name:\s*[\"']?([^\"'\n]+)[\"']?/m);
|
|
42
|
+
const descMatch = agentContent.match(/^description:\s*[\"']?([^\"'\n]+)[\"']?/m);
|
|
43
|
+
const agentName = nameMatch?.[1]?.trim() || path.basename(agentFile, '.md');
|
|
44
|
+
const agentDesc = descMatch?.[1]?.trim() || `${agentName} reviewer`;
|
|
45
|
+
const relPath = `${this.prrFolderName}/${mod}/agents/${agentFile}`.replaceAll('\\', '/');
|
|
46
|
+
const slug = `prr-${mod}-${path.basename(agentFile, '.md')}`;
|
|
47
|
+
|
|
48
|
+
config.customModes.push({
|
|
49
|
+
slug,
|
|
50
|
+
name: agentName,
|
|
51
|
+
roleDefinition: agentDesc,
|
|
52
|
+
whenToUse: `Use for ${agentName} tasks`,
|
|
53
|
+
customInstructions: `Read the full agent file from {project-root}/${relPath} and follow all activation instructions exactly.\n`,
|
|
54
|
+
groups: ['read', 'edit', 'browser', 'command', 'mcp'],
|
|
55
|
+
});
|
|
56
|
+
modeCount++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }), 'utf8');
|
|
61
|
+
|
|
62
|
+
// Write workflow files to .kilocode/workflows/
|
|
63
|
+
const workflowsDir = path.join(projectDir, '.kilocode', 'workflows');
|
|
64
|
+
await this.ensureDir(workflowsDir);
|
|
65
|
+
let wfCount = 0;
|
|
66
|
+
|
|
67
|
+
for (const mod of allModules) {
|
|
68
|
+
const workflowsSrcDir = path.join(prrDir, mod, 'workflows');
|
|
69
|
+
if (!(await fs.pathExists(workflowsSrcDir))) continue;
|
|
70
|
+
|
|
71
|
+
const workflowFiles = await glob('**/workflow{.md,.yaml}', { cwd: workflowsSrcDir });
|
|
72
|
+
for (const wfFile of workflowFiles) {
|
|
73
|
+
const wfContent = await fs.readFile(path.join(workflowsSrcDir, wfFile), 'utf8');
|
|
74
|
+
let wfName = path.basename(path.dirname(wfFile));
|
|
75
|
+
let wfDesc = '';
|
|
76
|
+
|
|
77
|
+
if (wfFile.endsWith('.yaml')) {
|
|
78
|
+
try { const d = yaml.parse(wfContent); wfName = d.name || wfName; wfDesc = d.description || ''; } catch { /* ignore */ }
|
|
79
|
+
} else {
|
|
80
|
+
const normalized = wfContent.replace(/\r\n/g, '\n');
|
|
81
|
+
const fmMatch = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
82
|
+
if (fmMatch) { try { const fm = yaml.parse(fmMatch[1]); wfName = fm.name || wfName; wfDesc = fm.description || ''; } catch { /* ignore */ } }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const dirParts = path.dirname(wfFile).split(path.sep).filter(Boolean);
|
|
86
|
+
const leafDir = dirParts.at(-1) || wfName;
|
|
87
|
+
const relPath = `${this.prrFolderName}/${mod}/workflows/${wfFile}`.replaceAll('\\', '/');
|
|
88
|
+
|
|
89
|
+
const content = `---
|
|
90
|
+
name: 'prr-${leafDir}'
|
|
91
|
+
description: '${wfDesc || wfName}'
|
|
92
|
+
disable-model-invocation: true
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{project-root}/${relPath}, READ its entire contents and follow its directions exactly!
|
|
96
|
+
`;
|
|
97
|
+
const fileName = `prr-${leafDir}.md`;
|
|
98
|
+
await fs.writeFile(path.join(workflowsDir, fileName), content, 'utf8');
|
|
99
|
+
wfCount++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { success: true, results: { agents: modeCount, workflows: wfCount } };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async cleanup(projectDir) {
|
|
107
|
+
// Remove prr- modes from .kilocodemodes
|
|
108
|
+
const kiloModesPath = path.join(projectDir, this.configFile);
|
|
109
|
+
if (await fs.pathExists(kiloModesPath)) {
|
|
110
|
+
try {
|
|
111
|
+
const config = yaml.parse(await fs.readFile(kiloModesPath, 'utf8')) || {};
|
|
112
|
+
if (Array.isArray(config.customModes)) {
|
|
113
|
+
config.customModes = config.customModes.filter((m) => !m.slug?.startsWith('prr-'));
|
|
114
|
+
await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }), 'utf8');
|
|
115
|
+
}
|
|
116
|
+
} catch { /* ignore */ }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Remove prr- workflow files
|
|
120
|
+
const workflowsDir = path.join(projectDir, '.kilocode', 'workflows');
|
|
121
|
+
if (await fs.pathExists(workflowsDir)) {
|
|
122
|
+
const files = await fs.readdir(workflowsDir);
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
if (file.startsWith('prr-') && file.endsWith('.md')) {
|
|
125
|
+
await fs.remove(path.join(workflowsDir, file));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { KiloSetup };
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* IDE Manager — dynamically loads and manages IDE handlers
|
|
3
|
+
*
|
|
4
|
+
* Loading strategy:
|
|
5
|
+
* 1. Custom installer files (github-copilot.js, codex.js, kilo.js) — unique installation logic
|
|
6
|
+
* 2. Config-driven handlers (from platform-codes.yaml) — standard IDE installation patterns
|
|
3
7
|
*/
|
|
4
8
|
const path = require('node:path');
|
|
5
9
|
const fs = require('fs-extra');
|
|
@@ -30,6 +34,36 @@ class IdeManager {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
async loadHandlers() {
|
|
37
|
+
// 1. Load custom installer files first
|
|
38
|
+
await this._loadCustomHandlers();
|
|
39
|
+
|
|
40
|
+
// 2. Load config-driven handlers from platform-codes.yaml (skips platforms with custom handlers)
|
|
41
|
+
await this._loadConfigDrivenHandlers();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async _loadCustomHandlers() {
|
|
45
|
+
const customFiles = ['github-copilot.js', 'codex.js', 'kilo.js'];
|
|
46
|
+
const ideDir = __dirname;
|
|
47
|
+
|
|
48
|
+
for (const file of customFiles) {
|
|
49
|
+
const filePath = path.join(ideDir, file);
|
|
50
|
+
if (!fs.existsSync(filePath)) continue;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const HandlerModule = require(filePath);
|
|
54
|
+
const HandlerClass = HandlerModule.default || Object.values(HandlerModule)[0];
|
|
55
|
+
if (!HandlerClass) continue;
|
|
56
|
+
|
|
57
|
+
const instance = new HandlerClass();
|
|
58
|
+
if (typeof instance.setPrrFolderName === 'function') {
|
|
59
|
+
instance.setPrrFolderName(this.prrFolderName);
|
|
60
|
+
}
|
|
61
|
+
this.handlers.set(instance.name, instance);
|
|
62
|
+
} catch { /* ignore load errors */ }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async _loadConfigDrivenHandlers() {
|
|
33
67
|
const platformCodesPath = path.join(__dirname, 'platform-codes.yaml');
|
|
34
68
|
const content = await fs.readFile(platformCodesPath, 'utf8');
|
|
35
69
|
const config = yaml.parse(content);
|
|
@@ -37,7 +71,9 @@ class IdeManager {
|
|
|
37
71
|
const { ConfigDrivenIdeSetup } = require('./_config-driven');
|
|
38
72
|
|
|
39
73
|
for (const [code, info] of Object.entries(config.platforms || {})) {
|
|
40
|
-
if (!info.installer) continue;
|
|
74
|
+
if (!info.installer) continue; // No installer = custom handler only
|
|
75
|
+
if (this.handlers.has(code)) continue; // Already loaded as custom handler
|
|
76
|
+
|
|
41
77
|
const handler = new ConfigDrivenIdeSetup(code, info);
|
|
42
78
|
handler.setPrrFolderName(this.prrFolderName);
|
|
43
79
|
this.handlers.set(code, handler);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# IDE/tool registry — defines where to install agent/workflow launchers
|
|
3
3
|
|
|
4
4
|
platforms:
|
|
5
|
+
# Recommended Platforms
|
|
5
6
|
claude-code:
|
|
6
7
|
name: "Claude Code"
|
|
7
8
|
preferred: true
|
|
@@ -29,6 +30,25 @@ platforms:
|
|
|
29
30
|
target_dir: .windsurf/workflows
|
|
30
31
|
template_type: windsurf
|
|
31
32
|
|
|
33
|
+
# Other IDEs and Tools
|
|
34
|
+
antigravity:
|
|
35
|
+
name: "Google Antigravity"
|
|
36
|
+
preferred: false
|
|
37
|
+
category: ide
|
|
38
|
+
description: "Google's AI development environment"
|
|
39
|
+
installer:
|
|
40
|
+
target_dir: .agent/workflows
|
|
41
|
+
template_type: antigravity
|
|
42
|
+
|
|
43
|
+
auggie:
|
|
44
|
+
name: "Auggie"
|
|
45
|
+
preferred: false
|
|
46
|
+
category: cli
|
|
47
|
+
description: "AI development tool"
|
|
48
|
+
installer:
|
|
49
|
+
target_dir: .augment/commands
|
|
50
|
+
template_type: default
|
|
51
|
+
|
|
32
52
|
cline:
|
|
33
53
|
name: "Cline"
|
|
34
54
|
preferred: false
|
|
@@ -38,13 +58,20 @@ platforms:
|
|
|
38
58
|
target_dir: .clinerules/workflows
|
|
39
59
|
template_type: windsurf
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
name: "
|
|
61
|
+
codex:
|
|
62
|
+
name: "Codex"
|
|
63
|
+
preferred: false
|
|
64
|
+
category: cli
|
|
65
|
+
description: "OpenAI Codex CLI"
|
|
66
|
+
# No installer config - uses custom codex.js
|
|
67
|
+
|
|
68
|
+
crush:
|
|
69
|
+
name: "Crush"
|
|
43
70
|
preferred: false
|
|
44
71
|
category: ide
|
|
45
|
-
description: "
|
|
72
|
+
description: "AI development assistant"
|
|
46
73
|
installer:
|
|
47
|
-
target_dir: .
|
|
74
|
+
target_dir: .crush/commands
|
|
48
75
|
template_type: default
|
|
49
76
|
|
|
50
77
|
gemini:
|
|
@@ -54,8 +81,31 @@ platforms:
|
|
|
54
81
|
description: "Google's CLI for Gemini"
|
|
55
82
|
installer:
|
|
56
83
|
target_dir: .gemini/commands
|
|
84
|
+
template_type: gemini
|
|
85
|
+
|
|
86
|
+
github-copilot:
|
|
87
|
+
name: "GitHub Copilot"
|
|
88
|
+
preferred: false
|
|
89
|
+
category: ide
|
|
90
|
+
description: "GitHub's AI pair programmer"
|
|
91
|
+
# No installer config - uses custom github-copilot.js
|
|
92
|
+
|
|
93
|
+
iflow:
|
|
94
|
+
name: "iFlow"
|
|
95
|
+
preferred: false
|
|
96
|
+
category: ide
|
|
97
|
+
description: "AI workflow automation"
|
|
98
|
+
installer:
|
|
99
|
+
target_dir: .iflow/commands
|
|
57
100
|
template_type: default
|
|
58
101
|
|
|
102
|
+
kilo:
|
|
103
|
+
name: "KiloCoder"
|
|
104
|
+
preferred: false
|
|
105
|
+
category: ide
|
|
106
|
+
description: "AI coding platform"
|
|
107
|
+
# No installer config - uses custom kilo.js
|
|
108
|
+
|
|
59
109
|
kiro:
|
|
60
110
|
name: "Kiro"
|
|
61
111
|
preferred: false
|
|
@@ -63,8 +113,58 @@ platforms:
|
|
|
63
113
|
description: "Amazon's AI-powered IDE"
|
|
64
114
|
installer:
|
|
65
115
|
target_dir: .kiro/steering
|
|
116
|
+
template_type: kiro
|
|
117
|
+
|
|
118
|
+
opencode:
|
|
119
|
+
name: "OpenCode"
|
|
120
|
+
preferred: false
|
|
121
|
+
category: ide
|
|
122
|
+
description: "OpenCode terminal coding assistant"
|
|
123
|
+
installer:
|
|
124
|
+
targets:
|
|
125
|
+
- target_dir: .opencode/agent
|
|
126
|
+
template_type: opencode
|
|
127
|
+
artifact_types: [agents]
|
|
128
|
+
- target_dir: .opencode/command
|
|
129
|
+
template_type: opencode
|
|
130
|
+
artifact_types: [workflows]
|
|
131
|
+
|
|
132
|
+
qwen:
|
|
133
|
+
name: "QwenCoder"
|
|
134
|
+
preferred: false
|
|
135
|
+
category: ide
|
|
136
|
+
description: "Qwen AI coding assistant"
|
|
137
|
+
installer:
|
|
138
|
+
target_dir: .qwen/commands
|
|
66
139
|
template_type: default
|
|
67
140
|
|
|
141
|
+
roo:
|
|
142
|
+
name: "Roo Cline"
|
|
143
|
+
preferred: false
|
|
144
|
+
category: ide
|
|
145
|
+
description: "Enhanced Cline fork"
|
|
146
|
+
installer:
|
|
147
|
+
target_dir: .roo/commands
|
|
148
|
+
template_type: default
|
|
149
|
+
|
|
150
|
+
rovo-dev:
|
|
151
|
+
name: "Rovo Dev"
|
|
152
|
+
preferred: false
|
|
153
|
+
category: ide
|
|
154
|
+
description: "Atlassian's Rovo development environment"
|
|
155
|
+
installer:
|
|
156
|
+
target_dir: .rovodev/workflows
|
|
157
|
+
template_type: rovodev
|
|
158
|
+
|
|
159
|
+
trae:
|
|
160
|
+
name: "Trae"
|
|
161
|
+
preferred: false
|
|
162
|
+
category: ide
|
|
163
|
+
description: "AI coding tool"
|
|
164
|
+
installer:
|
|
165
|
+
target_dir: .trae/rules
|
|
166
|
+
template_type: trae
|
|
167
|
+
|
|
68
168
|
categories:
|
|
69
169
|
ide:
|
|
70
170
|
name: "Integrated Development Environment"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: '{{name}}'
|
|
3
|
+
description: '{{description}}'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
|
7
|
+
|
|
8
|
+
<agent-activation CRITICAL="TRUE">
|
|
9
|
+
1. LOAD the FULL agent file from {project-root}/{{prrFolderName}}/{{path}}
|
|
10
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
11
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
12
|
+
4. DISPLAY the welcome/greeting as instructed
|
|
13
|
+
5. PRESENT the numbered menu
|
|
14
|
+
6. WAIT for user input before proceeding
|
|
15
|
+
</agent-activation>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: '{{name}}'
|
|
3
|
+
description: '{{description}}'
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
|
8
|
+
|
|
9
|
+
<agent-activation CRITICAL="TRUE">
|
|
10
|
+
1. LOAD the FULL agent file from {project-root}/{{prrFolderName}}/{{path}}
|
|
11
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
12
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
13
|
+
4. DISPLAY the welcome/greeting as instructed
|
|
14
|
+
5. PRESENT the numbered menu
|
|
15
|
+
6. WAIT for user input before proceeding
|
|
16
|
+
</agent-activation>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
inclusion: manual
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# {{name}}
|
|
6
|
+
|
|
7
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
|
8
|
+
|
|
9
|
+
<agent-activation CRITICAL="TRUE">
|
|
10
|
+
1. LOAD the FULL agent file from #[[file:{{prrFolderName}}/{{path}}]]
|
|
11
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
12
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
13
|
+
4. DISPLAY the welcome/greeting as instructed
|
|
14
|
+
5. PRESENT the numbered menu
|
|
15
|
+
6. WAIT for user input before proceeding
|
|
16
|
+
</agent-activation>
|