prr-kit 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -25
- package/package.json +1 -1
- 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/README.md
CHANGED
|
@@ -12,13 +12,13 @@ Module system, agent YAML, step-file workflows, CLI installer with full IDE inte
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# Install into your repo (interactive — recommended)
|
|
15
|
-
npx
|
|
15
|
+
npx prr-kit install
|
|
16
16
|
|
|
17
|
-
# Or
|
|
18
|
-
|
|
17
|
+
# Or use the alias
|
|
18
|
+
npx pr-review install
|
|
19
19
|
|
|
20
20
|
# Silent install with defaults (edit config.yaml afterward)
|
|
21
|
-
|
|
21
|
+
npx prr-kit install --directory /path/to/repo --modules prr --tools claude-code --yes
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Then open your IDE in the installed project and use `/prr-master` to start.
|
|
@@ -72,13 +72,15 @@ After install, edit `_prr/prr/config.yaml` in your project:
|
|
|
72
72
|
user_name: YourName
|
|
73
73
|
communication_language: English
|
|
74
74
|
target_repo: . # path to the git repo to review (. = current dir)
|
|
75
|
-
|
|
75
|
+
platform: auto # auto-detect from git remote, or: github / gitlab / azure / bitbucket
|
|
76
|
+
platform_repo: "owner/repo" # required for PR listing and posting inline comments
|
|
76
77
|
output_folder: _prr-output
|
|
77
78
|
review_output: /abs/path/_prr-output/reviews
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
> `
|
|
81
|
-
>
|
|
81
|
+
> `platform` defaults to `auto` — detects GitHub/GitLab/Azure/Bitbucket from the git remote URL.
|
|
82
|
+
> `platform_repo` is required for PR listing (`gh pr list`, `glab mr list`, etc.) and posting inline comments.
|
|
83
|
+
> Leave `platform_repo` empty to use local branch selection only.
|
|
82
84
|
|
|
83
85
|
## Platform Support
|
|
84
86
|
|
|
@@ -127,14 +129,14 @@ Only pauses once to ask which PR/branch to review.
|
|
|
127
129
|
|
|
128
130
|
### Selecting a PR (SP step)
|
|
129
131
|
|
|
130
|
-
**With `
|
|
132
|
+
**With `platform_repo` configured** — lists open PRs/MRs via platform CLI:
|
|
131
133
|
```
|
|
132
134
|
#45 "Add OAuth2 login" feature/oauth → main @alice 3h ago
|
|
133
135
|
#44 "Fix memory leak" fix/memory → main @bob 1d ago
|
|
134
136
|
```
|
|
135
|
-
Enter PR number → base and head resolved automatically
|
|
137
|
+
Enter PR number → base and head resolved automatically.
|
|
136
138
|
|
|
137
|
-
**Without `
|
|
139
|
+
**Without `platform_repo`** — asks explicitly for both branches:
|
|
138
140
|
```
|
|
139
141
|
🎯 Head branch (the branch to review)?
|
|
140
142
|
• Enter a number from the list (e.g., 1)
|
|
@@ -149,13 +151,10 @@ Enter PR number → base and head resolved automatically from GitHub.
|
|
|
149
151
|
|
|
150
152
|
| Agent | Slash Command | Speciality |
|
|
151
153
|
|-------|--------------|------------|
|
|
152
|
-
| PRR Master | `/prr-master` | Orchestrator — routes all workflows |
|
|
153
|
-
|
|
|
154
|
-
| Sam | `/security-reviewer` | OWASP Top 10, secrets, auth |
|
|
155
|
-
| Petra | `/performance-reviewer` | N+1 queries, memory leaks, async |
|
|
156
|
-
| Arch | `/architecture-reviewer` | SOLID, layers, coupling, consistency |
|
|
154
|
+
| PRR Master | `/prr-master` | Orchestrator — routes all workflows, full menu |
|
|
155
|
+
| PRR Quick | `/prr-quick` | One-command full pipeline (select → review → report) |
|
|
157
156
|
|
|
158
|
-
|
|
157
|
+
Specialist reviewer agents (Alex, Sam, Petra, Arch) are orchestrated internally by the master agent and party-mode workflow. Use `[PM] Party Mode` from the master menu to run all 4 reviewers in a collaborative session.
|
|
159
158
|
|
|
160
159
|
## Severity Levels
|
|
161
160
|
|
|
@@ -166,24 +165,34 @@ All findings use a standard format:
|
|
|
166
165
|
- 🟢 **[SUGGESTION]** — Nice-to-have improvement
|
|
167
166
|
- 📌 **[QUESTION]** — Needs clarification from author
|
|
168
167
|
|
|
169
|
-
## Inline
|
|
168
|
+
## Inline Code Comments
|
|
170
169
|
|
|
171
|
-
When `[PC] Post Comments` is run with `
|
|
170
|
+
When `[PC] Post Comments` is run with `platform_repo` configured, it posts findings as **inline code comments** on the exact file and line — the same experience as a human reviewer.
|
|
172
171
|
|
|
173
|
-
|
|
172
|
+
| Platform | Method | Required CLI |
|
|
173
|
+
|----------|--------|-------------|
|
|
174
|
+
| GitHub | Reviews API | `gh auth login` |
|
|
175
|
+
| GitLab | MR Discussions API | `glab auth login` |
|
|
176
|
+
| Azure DevOps | PR Threads API | `az login` |
|
|
177
|
+
| Bitbucket | Inline Comments REST API | `bb` / `curl` |
|
|
174
178
|
|
|
175
179
|
## Supported IDEs
|
|
176
180
|
|
|
177
|
-
|
|
178
|
-
- **Cursor**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
**Preferred:**
|
|
182
|
+
- **Claude Code**, **Cursor**, **Windsurf**
|
|
183
|
+
|
|
184
|
+
**Also supported:**
|
|
185
|
+
- Antigravity, Auggie, Cline, Codex, Crush, Gemini CLI, GitHub Copilot, iFlow, Kilo, Kiro, OpenCode, QwenCoder, Roo Cline, Rovo Dev, Trae
|
|
181
186
|
|
|
182
187
|
## Requirements
|
|
183
188
|
|
|
184
|
-
- Node.js
|
|
189
|
+
- Node.js 20+
|
|
185
190
|
- Git
|
|
186
|
-
-
|
|
191
|
+
- Platform CLI (optional — only needed for PR listing and inline comments):
|
|
192
|
+
- GitHub: [`gh`](https://cli.github.com/)
|
|
193
|
+
- GitLab: [`glab`](https://gitlab.com/gitlab-org/cli)
|
|
194
|
+
- Azure DevOps: [`az`](https://learn.microsoft.com/en-us/cli/azure/) + Azure DevOps extension
|
|
195
|
+
- Bitbucket: [`bb`](https://bitbucket.org/atlassian/bitbucket-cli) or `curl`
|
|
187
196
|
|
|
188
197
|
## Development
|
|
189
198
|
|
package/package.json
CHANGED
|
@@ -17,6 +17,11 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
17
17
|
async setup(projectDir, prrDir, options = {}) {
|
|
18
18
|
if (!this.installerConfig) return { success: false, reason: 'no-config' };
|
|
19
19
|
|
|
20
|
+
// Multi-target support (e.g. opencode: agents → one dir, workflows → another)
|
|
21
|
+
if (this.installerConfig.targets) {
|
|
22
|
+
return this._setupMultiTarget(projectDir, prrDir, options);
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
await this.cleanup(projectDir, options);
|
|
21
26
|
|
|
22
27
|
const { target_dir, template_type } = this.installerConfig;
|
|
@@ -114,6 +119,71 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
|
|
|
114
119
|
return { success: true, results };
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
async _setupMultiTarget(projectDir, prrDir, options = {}) {
|
|
123
|
+
const results = { agents: 0, workflows: 0 };
|
|
124
|
+
const selectedModules = options.selectedModules || ['prr'];
|
|
125
|
+
const allModules = ['core', ...selectedModules.filter((m) => m !== 'core')];
|
|
126
|
+
|
|
127
|
+
for (const target of this.installerConfig.targets) {
|
|
128
|
+
const { target_dir, template_type, artifact_types } = target;
|
|
129
|
+
const targetPath = path.join(projectDir, target_dir);
|
|
130
|
+
await this.ensureDir(targetPath);
|
|
131
|
+
|
|
132
|
+
const installAgents = !artifact_types || artifact_types.includes('agents');
|
|
133
|
+
const installWorkflows = !artifact_types || artifact_types.includes('workflows');
|
|
134
|
+
|
|
135
|
+
if (installAgents) {
|
|
136
|
+
for (const mod of allModules) {
|
|
137
|
+
const agentsDir = path.join(prrDir, mod, 'agents');
|
|
138
|
+
if (!(await fs.pathExists(agentsDir))) continue;
|
|
139
|
+
const agentFiles = await glob('**/*.md', { cwd: agentsDir });
|
|
140
|
+
for (const agentFile of agentFiles) {
|
|
141
|
+
const agentContent = await fs.readFile(path.join(agentsDir, agentFile), 'utf8');
|
|
142
|
+
if (/^no-launcher:\s*true/m.test(agentContent)) continue;
|
|
143
|
+
const nameMatch = agentContent.match(/^name:\s*[\"']?([^\"'\n]+)[\"']?/m);
|
|
144
|
+
const descMatch = agentContent.match(/^description:\s*[\"']?([^\"'\n]+)[\"']?/m);
|
|
145
|
+
const agentName = nameMatch?.[1]?.trim() || path.basename(agentFile, '.md');
|
|
146
|
+
const agentDesc = descMatch?.[1]?.trim() || `${agentName} reviewer`;
|
|
147
|
+
const relAgentPath = `${mod}/agents/${agentFile}`.replaceAll('\\', '/');
|
|
148
|
+
const launcherContent = this.generateAgentLauncher(agentName, agentDesc, relAgentPath, template_type);
|
|
149
|
+
const launcherFileName = `${path.basename(agentFile, '.md')}.md`;
|
|
150
|
+
await fs.writeFile(path.join(targetPath, launcherFileName), launcherContent, 'utf8');
|
|
151
|
+
results.agents++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (installWorkflows) {
|
|
157
|
+
for (const mod of allModules) {
|
|
158
|
+
const workflowsDir = path.join(prrDir, mod, 'workflows');
|
|
159
|
+
if (!(await fs.pathExists(workflowsDir))) continue;
|
|
160
|
+
const workflowFiles = await glob('**/workflow{.md,.yaml}', { cwd: workflowsDir });
|
|
161
|
+
for (const wfFile of workflowFiles) {
|
|
162
|
+
const wfContent = await fs.readFile(path.join(workflowsDir, wfFile), 'utf8');
|
|
163
|
+
let wfName = path.basename(path.dirname(wfFile));
|
|
164
|
+
let wfDesc = '';
|
|
165
|
+
if (wfFile.endsWith('.yaml')) {
|
|
166
|
+
try { const d = require('yaml').parse(wfContent); wfName = d.name || wfName; wfDesc = d.description || ''; } catch { /* ignore */ }
|
|
167
|
+
} else {
|
|
168
|
+
const normalized = wfContent.replace(/\r\n/g, '\n');
|
|
169
|
+
const fmMatch = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
170
|
+
if (fmMatch) { try { const fm = require('yaml').parse(fmMatch[1]); wfName = fm.name || wfName; wfDesc = fm.description || ''; } catch { /* ignore */ } }
|
|
171
|
+
}
|
|
172
|
+
const dirParts = path.dirname(wfFile).split(path.sep).filter(Boolean);
|
|
173
|
+
const leafDir = dirParts.at(-1) || wfName;
|
|
174
|
+
const relWfPath = `${mod}/workflows/${wfFile}`.replaceAll('\\', '/');
|
|
175
|
+
const launcherContent = this.generateWorkflowLauncher(wfName, wfDesc, relWfPath, template_type);
|
|
176
|
+
const launcherFileName = `prr-${leafDir}.md`;
|
|
177
|
+
await fs.writeFile(path.join(targetPath, launcherFileName), launcherContent, 'utf8');
|
|
178
|
+
results.workflows++;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { success: true, results };
|
|
185
|
+
}
|
|
186
|
+
|
|
117
187
|
generateAgentLauncher(name, description, agentRelPath, templateType) {
|
|
118
188
|
const template = this.loadTemplate(`${templateType}-agent`) || this.loadTemplate('default-agent');
|
|
119
189
|
return template
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex CLI setup handler
|
|
3
|
+
* Installs prompts to ~/.codex/prompts (global) or .codex/prompts (project)
|
|
4
|
+
*/
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const os = require('node:os');
|
|
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 CodexSetup extends BaseIdeSetup {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('codex', 'Codex', false);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async setup(projectDir, prrDir, options = {}) {
|
|
18
|
+
const installLocation = options.preCollectedConfig?.installLocation || 'global';
|
|
19
|
+
const destDir = this._getPromptDir(projectDir, installLocation);
|
|
20
|
+
await fs.ensureDir(destDir);
|
|
21
|
+
await this._clearPrrFiles(destDir);
|
|
22
|
+
|
|
23
|
+
const selectedModules = options.selectedModules || ['prr'];
|
|
24
|
+
const allModules = ['core', ...selectedModules.filter((m) => m !== 'core')];
|
|
25
|
+
let count = 0;
|
|
26
|
+
|
|
27
|
+
// Write agent launchers
|
|
28
|
+
for (const mod of allModules) {
|
|
29
|
+
const agentsSrcDir = path.join(prrDir, mod, 'agents');
|
|
30
|
+
if (!(await fs.pathExists(agentsSrcDir))) continue;
|
|
31
|
+
|
|
32
|
+
const agentFiles = await glob('**/*.md', { cwd: agentsSrcDir });
|
|
33
|
+
for (const agentFile of agentFiles) {
|
|
34
|
+
const agentContent = await fs.readFile(path.join(agentsSrcDir, agentFile), 'utf8');
|
|
35
|
+
if (/^no-launcher:\s*true/m.test(agentContent)) continue;
|
|
36
|
+
|
|
37
|
+
const nameMatch = agentContent.match(/^name:\s*[\"']?([^\"'\n]+)[\"']?/m);
|
|
38
|
+
const agentName = nameMatch?.[1]?.trim() || path.basename(agentFile, '.md');
|
|
39
|
+
const relPath = `${this.prrFolderName}/${mod}/agents/${agentFile}`.replaceAll('\\', '/');
|
|
40
|
+
|
|
41
|
+
const content = `---
|
|
42
|
+
name: '${agentName}'
|
|
43
|
+
description: '${agentName} agent'
|
|
44
|
+
disable-model-invocation: true
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
|
48
|
+
|
|
49
|
+
<agent-activation CRITICAL="TRUE">
|
|
50
|
+
1. LOAD the FULL agent file from {project-root}/${relPath}
|
|
51
|
+
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
52
|
+
3. FOLLOW every step in the <activation> section precisely
|
|
53
|
+
4. DISPLAY the welcome/greeting as instructed
|
|
54
|
+
5. PRESENT the numbered menu
|
|
55
|
+
6. WAIT for user input before proceeding
|
|
56
|
+
</agent-activation>
|
|
57
|
+
`;
|
|
58
|
+
const fileName = `prr-${mod}-${path.basename(agentFile, '.md')}.md`;
|
|
59
|
+
await fs.writeFile(path.join(destDir, fileName), content, 'utf8');
|
|
60
|
+
count++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Write workflow launchers
|
|
65
|
+
for (const mod of allModules) {
|
|
66
|
+
const workflowsSrcDir = path.join(prrDir, mod, 'workflows');
|
|
67
|
+
if (!(await fs.pathExists(workflowsSrcDir))) continue;
|
|
68
|
+
|
|
69
|
+
const workflowFiles = await glob('**/workflow{.md,.yaml}', { cwd: workflowsSrcDir });
|
|
70
|
+
for (const wfFile of workflowFiles) {
|
|
71
|
+
const wfContent = await fs.readFile(path.join(workflowsSrcDir, wfFile), 'utf8');
|
|
72
|
+
let wfName = path.basename(path.dirname(wfFile));
|
|
73
|
+
|
|
74
|
+
if (wfFile.endsWith('.yaml')) {
|
|
75
|
+
try { const d = yaml.parse(wfContent); wfName = d.name || wfName; } catch { /* ignore */ }
|
|
76
|
+
} else {
|
|
77
|
+
const normalized = wfContent.replace(/\r\n/g, '\n');
|
|
78
|
+
const fmMatch = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
79
|
+
if (fmMatch) { try { const fm = yaml.parse(fmMatch[1]); wfName = fm.name || wfName; } catch { /* ignore */ } }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const dirParts = path.dirname(wfFile).split(path.sep).filter(Boolean);
|
|
83
|
+
const leafDir = dirParts.at(-1) || wfName;
|
|
84
|
+
const relPath = `${this.prrFolderName}/${mod}/workflows/${wfFile}`.replaceAll('\\', '/');
|
|
85
|
+
|
|
86
|
+
const content = `---
|
|
87
|
+
name: 'prr-${leafDir}'
|
|
88
|
+
description: '${wfName}'
|
|
89
|
+
disable-model-invocation: true
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{project-root}/${relPath}, READ its entire contents and follow its directions exactly!
|
|
93
|
+
`;
|
|
94
|
+
const fileName = `prr-${mod}-${leafDir}.md`;
|
|
95
|
+
await fs.writeFile(path.join(destDir, fileName), content, 'utf8');
|
|
96
|
+
count++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { success: true, results: { agents: count, workflows: 0 } };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async cleanup(projectDir) {
|
|
104
|
+
for (const loc of ['global', 'project']) {
|
|
105
|
+
const dir = this._getPromptDir(projectDir, loc);
|
|
106
|
+
await this._clearPrrFiles(dir);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_getPromptDir(projectDir, location) {
|
|
111
|
+
if (location === 'project' && projectDir) {
|
|
112
|
+
return path.join(projectDir, '.codex', 'prompts');
|
|
113
|
+
}
|
|
114
|
+
return path.join(os.homedir(), '.codex', 'prompts');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async _clearPrrFiles(dir) {
|
|
118
|
+
if (!(await fs.pathExists(dir))) return;
|
|
119
|
+
const entries = await fs.readdir(dir);
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (entry.startsWith('prr-') && entry.endsWith('.md')) {
|
|
122
|
+
await fs.remove(path.join(dir, entry));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { CodexSetup };
|
|
@@ -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 Framework — 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>
|
|
@@ -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,10 @@
|
|
|
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
|
+
Read the entire agent file at: {project-root}/{{prrFolderName}}/{{path}}
|
|
9
|
+
|
|
10
|
+
Follow all instructions in the agent file exactly as written.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
## Instructions
|
|
6
|
+
|
|
7
|
+
You must fully embody this agent's persona and follow all activation instructions exactly as specified.
|
|
8
|
+
|
|
9
|
+
Read the entire agent file at: {project-root}/{{prrFolderName}}/{{path}}
|
|
10
|
+
|
|
11
|
+
Follow all instructions in the agent file exactly as written.
|